import { Table, TableHeader, TableColumn, TableBody, TableRow, TableCell, User, Pagination, Spinner, Popover, PopoverTrigger, PopoverContent, Button, CheckboxGroup, Checkbox, Drawer, DrawerContent, DrawerHeader, DrawerBody, DrawerFooter, useDisclosure, Chip, } from "@heroui/react" import { useCallback, useEffect, useMemo, useRef, useState } from "react" import { useAsyncList } from "@react-stately/data" import type { components } from "../../types/saturday" import { saturdayApiBaseUrl, saturdayClient } from "../../utils/client" import EventDetail, { EventStatusChip, type EventDetailRef } from "./EventDetail" import dayjs from "dayjs" import { UserEventStatus } from "../../types/event" import { makeLogtoClient } from "../../utils/auth" import type { PublicMember } from "../../store/member" import type { UserInfoResponse } from "@logto/browser" import { getAvailableEventActions, type EventAction, type IdentityContext } from "./EventAction" type PublicEvent = components["schemas"]["PublicEvent"] export const EyeIcon = (props) => { return ( ) } function CheckboxPopover(props: { value: string[] onValueChange: (value: string[]) => void }) { return ( { UserEventStatus.map((status) => { return ( ) }) } ) } function TicketDetailDrawer(props: { event: PublicEvent identity: IdentityContext isOpen: boolean onEventUpdated: (event: PublicEvent) => void onOpenChange: (isOpen: boolean) => void onClose: () => void onDelete: () => void onEdit: () => void }) { const { isOpen, onOpenChange, onClose } = props const [isLoading, setIsLoading] = useState("") const eventDetailRef = useRef(null) const [availableActions, setAvailableActions] = useState([]) useEffect(() => { if (!props.event || !props.identity?.member || !props.identity?.userInfo?.roles) { return } setAvailableActions(getAvailableEventActions(props.event, props.identity)) }, [props.event, props.identity]) const onEventUpdated = async (event: PublicEvent) => { props.onEventUpdated(event) const res = await eventDetailRef.current?.refresh() console.log("onEventUpdated", res) if (!res || !props.identity?.member || !props.identity?.userInfo?.roles) { return } setAvailableActions(getAvailableEventActions(res, props.identity)) } return ( 维修详情 {isLoading} { event => ( { availableActions?.map((action) => { return ( { setIsLoading(action) }} > ) }) || <>> } ) } 关闭 ) } export const validateRepairRole = (roles: string[]) => { const acceptableRoles = ["repair admin", "repair member"] return roles.some(role => acceptableRoles.includes(role.toLowerCase())) } export default function App() { const [isLoading, setIsLoading] = useState(true) const [page, setPage] = useState(1) const rowsPerPage = 10 const [statusFilter, setStatusFilter] = useState([]) const { isOpen, onOpen, onOpenChange } = useDisclosure() const [userInfo, setUserInfo] = useState() const [currentMember, setCurrentMember] = useState() const [token, setToken] = useState() useEffect(() => { const check = async () => { const adminPath = "/repair/admin" const authenticated = await makeLogtoClient().isAuthenticated() if (!authenticated) { window.location.href = `/repair/login-hint?redirectUrl=${adminPath}` return } const res = await makeLogtoClient().getIdTokenClaims() const token = await makeLogtoClient().getAccessToken() setToken(token) const hasRole = validateRepairRole(res.roles) if (!hasRole) { window.location.href = `/repair/login-hint?redirectUrl=${adminPath}` return } setUserInfo(res) const currentMember = await fetch(`${saturdayApiBaseUrl}/member`, { method: "GET", headers: { Authorization: `Bearer ${token}`, }, }).then(res => res.json()) setCurrentMember(currentMember) } check() }, []) const list = useAsyncList({ async load() { const { data } = await saturdayClient.GET("/events", { params: { query: { order: "DESC", offset: 1, limit: 1000, }, }, }) setIsLoading(false) return { items: data, } }, async sort({ items, sortDescriptor }) { return { items: items.sort((a, b) => { const first = a[sortDescriptor.column] const second = b[sortDescriptor.column] let cmp = (parseInt(first) || first) < (parseInt(second) || second) ? -1 : 1 if (sortDescriptor.direction === "descending") { cmp *= -1 } return cmp }), } }, }) const filteredList = useMemo(() => { if (statusFilter.length > 0) { return list.items.filter(item => statusFilter.includes(item.status)) } return list.items }, [list, statusFilter]) const items = useMemo(() => { const start = (page - 1) * rowsPerPage const end = start + rowsPerPage return filteredList.slice(start, end) }, [filteredList, page, rowsPerPage]) const pages = useMemo(() => { return Math.ceil(filteredList.length / rowsPerPage) }, [filteredList, rowsPerPage]) useEffect(() => { setPage(1) }, [statusFilter]) const columns: { key: string label: string allowSorting?: boolean content?: JSX.Element }[] = [ { key: "eventId", label: "单号", }, { key: "problem", label: "问题描述", }, { key: "model", label: "型号", }, { key: "size", label: "工作量", }, { key: "memberId", label: "处理人", }, { key: "gmtCreate", label: "创建时间", }, { key: "status", label: "状态", content: ( 状态 ), }, { key: "actions", label: "操作", }, ] const [activeEvent, setActiveEvent] = useState() const onOpenEventDetail = (event: PublicEvent) => { setActiveEvent(event) onOpen() } const renderCell = useCallback((event: PublicEvent, columnKey: string | number) => { const cellValue = event[columnKey] switch (columnKey) { case "problem": return ( {cellValue} ) case "memberId": return ( event.member ? ( {event.member.alias} ) : <>> ) case "size": return ( cellValue ? {"size:" + cellValue} : <>> ) case "gmtCreate": return ( {dayjs(cellValue).format("YYYY-MM-DD HH:mm")} ) case "status": return EventStatusChip({ status: cellValue, size: "sm", }) case "actions": return ( onOpenEventDetail(event)} size="sm" isIconOnly variant="light"> ) default: return cellValue } }, []) return ( 维修管理 setPage(page)} /> )} > {column => {column.label}}>} }> {item => ( {columnKey => {renderCell(item, columnKey)}} )} { onOpenChange() }} onDelete={() => {}} onEdit={() => {}} > ) }