diff --git a/package.json b/package.json
index 71b8976..1f11ac5 100644
--- a/package.json
+++ b/package.json
@@ -27,6 +27,7 @@
"@logto/browser": "^2.2.18",
"@stylistic/eslint-plugin": "^2.13.0",
"astro": "^4.16.18",
+ "dayjs": "^1.11.13",
"framer-motion": "^11.18.2",
"ical.js": "^1.5.0",
"md5": "^2.3.0",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 254adc3..456a3bb 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -47,6 +47,9 @@ importers:
astro:
specifier: ^4.16.18
version: 4.16.18(@types/node@22.14.1)(rollup@4.40.0)(typescript@5.8.3)
+ dayjs:
+ specifier: ^1.11.13
+ version: 1.11.13
framer-motion:
specifier: ^11.18.2
version: 11.18.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
@@ -3096,6 +3099,9 @@ packages:
resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==}
engines: {node: '>= 0.4'}
+ dayjs@1.11.13:
+ resolution: {integrity: sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==}
+
debug@3.2.7:
resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==}
peerDependencies:
@@ -9642,6 +9648,8 @@ snapshots:
es-errors: 1.3.0
is-data-view: 1.0.2
+ dayjs@1.11.13: {}
+
debug@3.2.7:
dependencies:
ms: 2.1.3
diff --git a/src/components/header/RepairHeader.astro b/src/components/header/RepairHeader.astro
new file mode 100644
index 0000000..21a585e
--- /dev/null
+++ b/src/components/header/RepairHeader.astro
@@ -0,0 +1,17 @@
+---
+import NavigationUser from "./NavigationUser.vue"
+---
+
+
+
+
diff --git a/src/pages/repair/EventDetail.tsx b/src/pages/repair/EventDetail.tsx
new file mode 100644
index 0000000..5a2e7c5
--- /dev/null
+++ b/src/pages/repair/EventDetail.tsx
@@ -0,0 +1,163 @@
+import { useEffect, useState } from "react"
+import type { components } from "../../types/saturday"
+import { saturdayClient } from "../../utils/client"
+import { Textarea, Input, Chip } from "@heroui/react"
+import type { PublicMember } from "../../store/member"
+import dayjs from "dayjs"
+import { EventStatus, UserEventAction } from "../../types/event"
+
+type PublicEvent = components["schemas"]["PublicEvent"]
+type EventLog = components["schemas"]["EventLog"]
+
+function EventLogItem(props: {
+ eventLog: EventLog
+ actor?: PublicMember
+}) {
+ return (
+
+ {/*
+
*/}
+
+
+
+ {
+ UserEventAction.find(v => v.action === props.eventLog.action)?.text || props.eventLog.action
+ }
+
+
+ {
+ props.actor?.avatar
+ ?

+ : <>>
+ }
+
+ {
+ props.actor ? props.actor.alias : ""
+ }
+
+
+
+
+ {dayjs(props.eventLog.gmtCreate).format("YYYY-MM-DD HH:mm")}
+
+
+
+
+ )
+}
+
+function EventStatusChip(props: {
+ status: string
+}) {
+ switch (props.status) {
+ case EventStatus.open:
+ return 未开始
+ case EventStatus.accepted:
+ return 维修中
+ case EventStatus.committed:
+ return 维修中
+ case EventStatus.closed:
+ return 已完成
+ case EventStatus.cancelled:
+ return 已取消
+ }
+}
+
+const filterEventLog = (event: PublicEvent) => {
+ const eventLogs = event.logs
+ const filteredLogs: (EventLog & { actor?: PublicMember })[] = []
+ // find the first log that action is "create"
+ const createLog = eventLogs.find(log => log.action === "create")
+ filteredLogs.push(createLog)
+ // find the first log that action is "cancel"
+ const cancelLog = eventLogs.find(log => log.action === "cancel")
+ if (cancelLog) {
+ filteredLogs.push(cancelLog)
+ return filteredLogs
+ }
+ // find the last log that action is "accept"
+ const acceptLog = eventLogs.findLast(log => log.action === "accept")
+ if (acceptLog) {
+ filteredLogs.push({
+ ...acceptLog,
+ actor: event.member,
+ })
+ }
+ // find the last log that action is "close"
+ const closeLog = eventLogs.findLast(log => log.action === "close")
+ if (closeLog) {
+ filteredLogs.push({
+ ...closeLog,
+ actor: event.closedBy,
+ })
+ }
+ return filteredLogs
+}
+
+export default function EventDetail() {
+ const [event, setEvent] = useState()
+ const fetchAndSetEvent = async (eventId: number) => {
+ const { data } = await saturdayClient.GET("/events/{EventId}", {
+ params: {
+ path: {
+ EventId: eventId,
+ },
+ },
+ })
+ setEvent(data)
+ }
+ useEffect(() => {
+ // get the eventId from the url
+ const url = new URL(window.location.href)
+ const eventId = url.searchParams.get("eventId")
+ if (!eventId) {
+ return
+ }
+ fetchAndSetEvent(eventId as unknown as number)
+ }, [])
+
+ return (
+ event
+ ? (
+
+
+
维修详情
+
+
+ #{event.eventId}
+
+
+
+
+
+
+
+
+
+
+ 维修记录
+
+ {
+ filterEventLog(event).map((v, index) => {
+ return (
+
+ )
+ })
+ }
+
+
+
+ )
+ :
+ )
+}
diff --git a/src/pages/repair/RepairHeader.astro b/src/pages/repair/RepairHeader.astro
deleted file mode 100644
index cf710f9..0000000
--- a/src/pages/repair/RepairHeader.astro
+++ /dev/null
@@ -1,10 +0,0 @@
----
-import NavigationUser from "../../components/header/NavigationUser.vue"
----
-
-
diff --git a/src/pages/repair/TicketForm.tsx b/src/pages/repair/TicketForm.tsx
index 000a44b..fea00d8 100644
--- a/src/pages/repair/TicketForm.tsx
+++ b/src/pages/repair/TicketForm.tsx
@@ -3,49 +3,28 @@ import { makeLogtoClient } from "../../utils/auth"
import type { UserInfoResponse } from "@logto/browser"
import { Form, Input, Button, Textarea } from "@heroui/react"
import { saturdayClient } from "../../utils/client"
-import type LogtoClient from "@logto/browser"
+import type { components } from "../../types/saturday"
+import QRCode from "qrcode"
+import EventDetail from "./EventDetail"
-export default function App() {
- const [userInfo, setUserInfo] = useState()
- const [formData, setFormData] = useState<{
- model?: string
- phone?: string
- qq?: string
- description?: string
- }>({})
-
- let logtoClient: LogtoClient | undefined = undefined
-
- useEffect(() => {
- const check = async () => {
- logtoClient = makeLogtoClient()
- const createRepairPath = "/repair/create-ticket"
- const authenticated = await logtoClient.isAuthenticated()
- if (!authenticated) {
- window.location.href = `/repair/login-hint?redirectUrl=${createRepairPath}`
- return
- }
- const res = await logtoClient.getIdTokenClaims()
- setUserInfo(res)
- }
- check()
- }, [])
+type TicketFormData = {
+ model?: string
+ phone?: string
+ qq?: string
+ description?: string
+}
+function TicketForm(props: {
+ userInfo: UserInfoResponse
+ onSubmit: (form: TicketFormData) => Promise
+}) {
+ const [loading, setLoading] = useState()
+ const [formData, setFormData] = useState({})
const onSubmit = async (e: React.FormEvent) => {
e.preventDefault()
-
- const logtoToken = await logtoClient.getAccessToken()
- await saturdayClient.POST("/client/event", {
- headers: {
- Authorization: `Bearer ${logtoToken}`,
- },
- body: {
- Problem: formData.description,
- model: formData.model,
- phone: formData.phone,
- qq: formData.qq,
- },
- })
+ setLoading(true)
+ await props.onSubmit(formData)
+ setLoading(false)
}
return (
@@ -96,7 +75,7 @@ export default function App() {
placeholder="example@nbtca.space"
isRequired
type="email"
- value={userInfo?.email || ""}
+ value={props.userInfo?.email || ""}
readOnly
description="我们会向此邮箱发送维修相关的通知"
/>
@@ -108,6 +87,7 @@ export default function App() {
type="phone"
name="phone"
value={formData.phone || ""}
+ maxLength={11}
onChange={(e) => {
setFormData({ ...formData, phone: e.target.value })
}}
@@ -120,7 +100,7 @@ export default function App() {
setFormData({ ...formData, qq: e.target.value })
}}
/>
-