diff --git a/src/pages/repair/EventAction.tsx b/src/pages/repair/EventAction.tsx
new file mode 100644
index 0000000..46bc5f5
--- /dev/null
+++ b/src/pages/repair/EventAction.tsx
@@ -0,0 +1,288 @@
+import type { UserInfoResponse } from "@logto/browser"
+import type { PublicMember } from "../../store/member"
+import { EventStatus, type PublicEvent } from "../../types/event"
+import { saturdayApiBaseUrl } from "../../utils/client"
+import { Button, Form, Select, SelectItem, Textarea } from "@heroui/react"
+import { useState } from "react"
+
+export type IdentityContext = {
+ member: PublicMember
+ userInfo: UserInfoResponse
+ token: string
+}
+
+enum RepairRole {
+ repairAdmin = "repair admin",
+ repairMember = "repair member",
+}
+
+export type EventActionProps = {
+ event: PublicEvent
+ identityContext: IdentityContext
+ isLoading?: string
+ onUpdated: (event: PublicEvent) => void
+ onLoading: (loadingAction?: string) => void
+}
+
+const EventSizeOptions: {
+ size: string
+ description?: string
+}[] = [
+ { size: "xs", description: "无需工具,仅简单排查或软件层级操作" },
+ { size: "s", description: "简单拆装部件,操作快,风险低" },
+ { size: "m", description: "需基本工具、一定技术判断,时间较长" },
+ { size: "l", description: "较复杂的拆装和测试流程,需熟练技能、多人协作可能" },
+ { size: "xl", description: "工作量极大,涉及多个设备,需团队作业和详细记录" },
+]
+
+const EventActionCommitForm = (props: {
+ formData: {
+ size: string
+ description: string
+ }
+ setFormData: (data: {
+ size: string
+ description: string
+ }) => void
+}) => {
+ const { formData, setFormData } = props
+ return (
+
+ )
+}
+
+export const EventActionCommit = (props: EventActionProps) => {
+ const [formData, setFormData] = useState({
+ size: "",
+ description: "",
+ })
+
+ const onSubmit = async () => {
+ props.onLoading("commit")
+ const res = await fetch(`${saturdayApiBaseUrl}/member/events/${props.event.eventId}/commit`, {
+ method: "POST",
+ headers: {
+ Authorization: `Bearer ${props.identityContext.token}`,
+ },
+ body: JSON.stringify({
+ size: formData.size,
+ problem: formData.description,
+ }),
+ }).then(res => res.json())
+ props.onLoading()
+ return props.onUpdated(res)
+ }
+ return (
+
+
+
+
+
+ )
+}
+export const EventActionAlterCommit = (props: EventActionProps) => {
+ const [formData, setFormData] = useState({
+ size: props.event.size,
+ description: props.event.problem,
+ })
+
+ const onSubmit = async () => {
+ props.onLoading("alterCommit")
+ const res = await fetch(`${saturdayApiBaseUrl}/member/events/${props.event.eventId}/commit`, {
+ method: "PATCH",
+ headers: {
+ Authorization: `Bearer ${props.identityContext.token}`,
+ },
+ body: JSON.stringify({
+ size: formData.size,
+ problem: formData.description,
+ }),
+ }).then(res => res.json())
+ props.onLoading()
+ return props.onUpdated(res)
+ }
+ return (
+
+
+
+
+
+ )
+}
+
+type CommonHandler = () => Promise
+type JsxHandler = (props: EventActionProps) => JSX.Element
+export type EventAction = {
+ action: string
+ label?: string
+ handler?: CommonHandler
+ jsxHandler: JsxHandler
+}
+export const getAvailableEventActions = (event: PublicEvent, identityContext: IdentityContext) => {
+ console.log("getting event actions", event, identityContext)
+ const actions: EventAction[] = []
+
+ const makeCommonJsxHandler = (action: Omit) => {
+ return (props: EventActionProps) => {
+ const onAction = async (action: {
+ action: string
+ handler?: CommonHandler
+ }) => {
+ props.onLoading(action.action)
+ if (action.handler) {
+ const res = await action.handler()
+ props.onUpdated(res as PublicEvent)
+ }
+ props.onLoading()
+ }
+ return (
+
+
+
+ )
+ }
+ }
+
+ if (event.status == EventStatus.open) {
+ actions.push({
+ action: "accept",
+ jsxHandler: makeCommonJsxHandler({
+ action: "accept",
+ label: "接受",
+ handler: async () => {
+ return await fetch(`${saturdayApiBaseUrl}/member/events/${event.eventId}/accept`, {
+ method: "POST",
+ headers: {
+ Authorization: `Bearer ${identityContext.token}`,
+ },
+ }).then(res => res.json())
+ },
+ }),
+ })
+ }
+ else if (event.status == EventStatus.accepted && event.member?.memberId == identityContext.member.memberId) {
+ actions.push({
+ action: "commit",
+ jsxHandler: EventActionCommit,
+ })
+ actions.push({
+ action: "drop",
+ jsxHandler: makeCommonJsxHandler({
+ action: "drop",
+ label: "放弃",
+ handler: async () => {
+ return await fetch(`${saturdayApiBaseUrl}/member/events/${event.eventId}/accept`, {
+ method: "DELETE",
+ headers: {
+ Authorization: `Bearer ${identityContext.token}`,
+ },
+ }).then(res => res.json())
+ },
+ }),
+ })
+ }
+ else if (event.status == EventStatus.committed) {
+ if (event.member?.memberId == identityContext.member.memberId) {
+ actions.push({
+ action: "alterCommit",
+ jsxHandler: EventActionAlterCommit,
+ })
+ }
+ if (identityContext.userInfo.roles.find(role => role.toLocaleLowerCase() == RepairRole.repairAdmin)) {
+ actions.push({
+ action: "close",
+ jsxHandler: makeCommonJsxHandler({
+ action: "close",
+ label: "关闭",
+ handler: async () => {
+ return await fetch(`${saturdayApiBaseUrl}/events/${event.eventId}/close`, {
+ method: "POST",
+ headers: {
+ Authorization: `Bearer ${identityContext.token}`,
+ },
+ }).then(res => res.json())
+ },
+ }),
+ })
+ actions.push({
+ action: "reject",
+ jsxHandler: makeCommonJsxHandler({
+ action: "rejectCommit",
+ label: "退回",
+ handler: async () => {
+ return await fetch(`${saturdayApiBaseUrl}/events/${event.eventId}/commit`, {
+ method: "DELETE",
+ headers: {
+ Authorization: `Bearer ${identityContext.token}`,
+ },
+ }).then(res => res.json())
+ },
+ }),
+ })
+ }
+ }
+
+ return actions
+}
diff --git a/src/pages/repair/EventDetail.tsx b/src/pages/repair/EventDetail.tsx
index de2a6d3..8d6ad74 100644
--- a/src/pages/repair/EventDetail.tsx
+++ b/src/pages/repair/EventDetail.tsx
@@ -1,4 +1,4 @@
-import { useEffect, useState } from "react"
+import { forwardRef, useEffect, useImperativeHandle, useState } from "react"
import type { components } from "../../types/saturday"
import { saturdayClient } from "../../utils/client"
import { Textarea, Input, Chip } from "@heroui/react"
@@ -15,8 +15,6 @@ function EventLogItem(props: {
}) {
return (
- {/*
-
*/}
@@ -55,9 +53,9 @@ export function EventStatusChip(props: {
case EventStatus.open:
return
未开始
case EventStatus.accepted:
- return
维修中
- case EventStatus.committed:
return
维修中
+ case EventStatus.committed:
+ return
待审核
case EventStatus.closed:
return
已完成
case EventStatus.cancelled:
@@ -95,72 +93,91 @@ const filterEventLog = (event: PublicEvent) => {
}
return filteredLogs
}
-
-export default function EventDetail(props: {
- eventId?: number
-}) {
- const [event, setEvent] = useState
()
- const fetchAndSetEvent = async (eventId: number) => {
- const { data } = await saturdayClient.GET("/events/{EventId}", {
- params: {
- path: {
- EventId: eventId,
- },
- },
- })
- setEvent(data)
- }
- useEffect(() => {
- const url = new URL(window.location.href)
- const eventId = props.eventId ?? url.searchParams.get("eventId")
- if (!eventId) {
- return
- }
- fetchAndSetEvent(eventId as unknown as number)
- }, [])
-
- return (
- event
- ? (
-
-
-
维修详情
-
-
- #{event.eventId}
-
-
-
-
-
-
-
-
-
-
- 维修记录
-
- {
- filterEventLog(event).map((v, index) => {
- return (
-
- )
- })
- }
-
-
-
- )
- :
- )
+export type EventDetailRef = {
+ refresh: () => Promise
}
+const EventDetail = forwardRef void
+ action?: React.ReactNode
+}>((props, ref) => {
+ const [event, setEvent] = useState()
+
+ const fetchAndSetEvent = async (eventId: number) => {
+ const { data } = await saturdayClient.GET("/events/{EventId}", {
+ params: {
+ path: {
+ EventId: eventId,
+ },
+ },
+ })
+ setEvent(data)
+ return data
+ }
+
+ const refresh = async () => {
+ const url = new URL(window.location.href)
+ const eventId = props.eventId ?? url.searchParams.get("eventId")
+ console.log("refresh eventId", eventId)
+ if (eventId) {
+ return await fetchAndSetEvent(eventId as unknown as number)
+ }
+ }
+
+ // 初次加载
+ useEffect(() => {
+ refresh()
+ }, [])
+
+ // 暴露给父组件的方法
+ useImperativeHandle(ref, () => ({
+ refresh,
+ }))
+
+ return (
+ event
+ ? (
+
+
+
维修详情
+
+
+ #{event.eventId}
+
+
+
+
+
+
+
+
+
+
+ 维修记录
+
+ {
+ filterEventLog(event).map((v, index) => {
+ return (
+
+ )
+ })
+ }
+
+
+
+ )
+ :
+ )
+ })
+
+export default EventDetail
diff --git a/src/pages/repair/RepairAdmin.tsx b/src/pages/repair/RepairAdmin.tsx
index 83948bb..c321641 100644
--- a/src/pages/repair/RepairAdmin.tsx
+++ b/src/pages/repair/RepairAdmin.tsx
@@ -20,18 +20,21 @@ import {
DrawerBody,
DrawerFooter,
useDisclosure,
+ Chip,
} from "@heroui/react"
-import { useCallback, useEffect, useMemo, useState } from "react"
+import { useCallback, useEffect, useMemo, useRef, useState } from "react"
import { useAsyncList } from "@react-stately/data"
import type { components } from "../../types/saturday"
-import { saturdayClient } from "../../utils/client"
-import EventDetail, { EventStatusChip } from "./EventDetail"
+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"]
-// type EventLog = components["schemas"]["EventLog"]
export const EyeIcon = (props) => {
return (
@@ -63,106 +66,10 @@ export const EyeIcon = (props) => {
)
}
-export const DeleteIcon = (props) => {
- return (
-
- )
-}
-
-export const EditIcon = (props) => {
- return (
-
- )
-}
function CheckboxPopover(props: {
value: string[]
onValueChange: (value: string[]) => void
}) {
- // const [selectedValues, setSelectedValues] = useState([])
-
- // const handleSelectionChange = (values) => {
- // setSelectedValues(values)
- // }
-
return (
@@ -196,23 +103,67 @@ function CheckboxPopover(props: {
}
function TicketDetailDrawer(props: {
- eventId: number
+ 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}
-
+
+
+ {
+ availableActions?.map((action) => {
+ return (
+
{
+ setIsLoading(action)
+ }}
+ >
+
+ )
+ })
+ }
+