diff --git a/src/pages/repair/NotificationPreferences.tsx b/src/pages/repair/NotificationPreferences.tsx new file mode 100644 index 0000000..852cc6c --- /dev/null +++ b/src/pages/repair/NotificationPreferences.tsx @@ -0,0 +1,157 @@ +import { useEffect, useState } from "react" +import { Card, CardBody, CardHeader, Switch, Spinner, Button } from "@heroui/react" +import { saturdayClient } from "../../utils/client" +import type { components } from "../../types/saturday" + +type NotificationPreferenceItem = components["schemas"]["NotificationPreferenceItem"] +type UpdateNotificationPreferencesInputBody = components["schemas"]["UpdateNotificationPreferencesInputBody"] + +interface NotificationPreferencesProps { + token: string +} + +export default function NotificationPreferences({ token }: NotificationPreferencesProps) { + const [preferences, setPreferences] = useState([]) + const [isLoading, setIsLoading] = useState(true) + const [isSaving, setIsSaving] = useState(false) + const [errorMessage, setErrorMessage] = useState("") + const [successMessage, setSuccessMessage] = useState("") + + useEffect(() => { + loadPreferences() + }, [token]) + + const loadPreferences = async () => { + if (!token) return + + setIsLoading(true) + setErrorMessage("") + + try { + const { data, error } = await saturdayClient.GET("/notification-preferences", { + params: { + header: { + Authorization: `Bearer ${token}`, + }, + }, + }) + + if (error || !data) { + setErrorMessage("加载通知偏好设置失败") + return + } + + setPreferences(data) + } catch (err) { + setErrorMessage("加载通知偏好设置时出错") + } finally { + setIsLoading(false) + } + } + + const handleToggle = async (notificationType: string, newValue: boolean) => { + setIsSaving(true) + setErrorMessage("") + setSuccessMessage("") + + // Optimistically update UI + const previousPreferences = [...preferences] + setPreferences(preferences.map(pref => + pref.notificationType === notificationType + ? { ...pref, enabled: newValue } + : pref + )) + + try { + // Create the request body with all preferences + const requestBody: UpdateNotificationPreferencesInputBody = preferences.reduce((acc, pref) => { + const enabled = pref.notificationType === notificationType ? newValue : pref.enabled + acc[pref.notificationType] = enabled + return acc + }, {} as UpdateNotificationPreferencesInputBody) + + const { error } = await saturdayClient.PUT("/notification-preferences", { + params: { + header: { + Authorization: `Bearer ${token}`, + }, + }, + body: requestBody, + }) + + if (error) { + // Revert on error + setPreferences(previousPreferences) + setErrorMessage("更新通知偏好设置失败") + return + } + + setSuccessMessage("通知偏好设置已更新") + // Clear success message after 3 seconds + setTimeout(() => setSuccessMessage(""), 3000) + } catch (err) { + // Revert on error + setPreferences(previousPreferences) + setErrorMessage("更新通知偏好设置时出错") + } finally { + setIsSaving(false) + } + } + + if (isLoading) { + return ( + + + + + + ) + } + + return ( + + +

通知偏好设置

+

管理您希望接收的通知类型

+
+ + {preferences.map((pref) => ( +
+
+

{pref.description}

+

{pref.notificationType}

+
+ handleToggle(pref.notificationType, value)} + isDisabled={isSaving} + size="sm" + /> +
+ ))} + + {/* Messages */} + {errorMessage && ( +
+

{errorMessage}

+
+ )} + + {successMessage && ( +
+

{successMessage}

+
+ )} + + {preferences.length === 0 && !errorMessage && ( +
+

暂无可用的通知偏好设置

+
+ )} +
+
+ ) +} diff --git a/src/pages/repair/RepairAdmin.tsx b/src/pages/repair/RepairAdmin.tsx index 0054e79..1d50ff5 100644 --- a/src/pages/repair/RepairAdmin.tsx +++ b/src/pages/repair/RepairAdmin.tsx @@ -34,6 +34,7 @@ import type { PublicMember } from "../../store/member" import type { UserInfoResponse } from "@logto/browser" import { getAvailableEventActions, type EventAction, type IdentityContext } from "./EventAction" import { ExportExcelModal } from "./ExportEventDialog" +import NotificationPreferences from "./NotificationPreferences" type PublicEvent = components["schemas"]["PublicEvent"] @@ -531,6 +532,14 @@ export default function App() { : <> } + + {/* Notification Preferences Section */} + {token && ( +
+ +
+ )} +
{/* Mobile Cards Layout */}
diff --git a/src/types/saturday.d.ts b/src/types/saturday.d.ts index c831254..e16a694 100644 --- a/src/types/saturday.d.ts +++ b/src/types/saturday.d.ts @@ -403,6 +403,24 @@ export interface paths { patch?: never trace?: never } + "/notification-preferences": { + parameters: { + query?: never + header?: never + path?: never + cookie?: never + } + /** Get notification preferences */ + get: operations["get-notification-preferences"] + /** Update notification preferences */ + put: operations["update-notification-preferences"] + post?: never + delete?: never + options?: never + head?: never + patch?: never + trace?: never + } "/ping": { parameters: { query?: never @@ -664,6 +682,23 @@ export interface components { Int64: number Valid: boolean } + "NotificationPreferenceItem": { + /** + * @description Description of the notification type + * @example 通知所有新创建的维修工单 + */ + description: string + /** + * @description Whether this notification type is enabled + * @example true + */ + enabled: boolean + /** + * @description Notification type identifier + * @example new_event_created + */ + notificationType: string + } "PingResponse": { /** * Format: uri @@ -732,6 +767,29 @@ export interface components { qq?: string size?: string } + "UpdateNotificationPreferencesInputBody": { + /** + * Format: uri + * @description A URL to the JSON Schema for this object. + * @example https://api.nbtca.space/schemas/UpdateNotificationPreferencesInputBody.json + */ + readonly $schema?: string + /** + * @description Enable/disable notifications for events assigned to me + * @example true + */ + event_assigned_to_me: boolean + /** + * @description Enable/disable notifications for event status changes + * @example true + */ + event_status_changed: boolean + /** + * @description Enable/disable notifications for new events created + * @example true + */ + new_event_created: boolean + } "UpdateMemberAvatarInputBody": { /** * Format: uri @@ -2108,6 +2166,84 @@ export interface operations { } } } + "get-notification-preferences": { + parameters: { + query?: never + header?: { + /** @description Bearer token or JWT token */ + Authorization?: string + } + path?: never + cookie?: never + } + requestBody?: never + responses: { + /** @description OK */ + 200: { + headers: { + "X-Limit"?: number | null + "X-Offset"?: number | null + "X-Page"?: number | null + "X-Total-Count"?: number | null + "X-Total-Pages"?: number | null + [name: string]: unknown + } + content: { + "application/json": components["schemas"]["NotificationPreferenceItem"][] | null + } + } + /** @description Error */ + default: { + headers: { + [name: string]: unknown + } + content: { + "application/problem+json": components["schemas"]["ErrorModel"] + } + } + } + } + "update-notification-preferences": { + parameters: { + query?: never + header?: { + /** @description Bearer token or JWT token */ + Authorization?: string + } + path?: never + cookie?: never + } + requestBody: { + content: { + "application/json": components["schemas"]["UpdateNotificationPreferencesInputBody"] + } + } + responses: { + /** @description OK */ + 200: { + headers: { + "X-Limit"?: number | null + "X-Offset"?: number | null + "X-Page"?: number | null + "X-Total-Count"?: number | null + "X-Total-Pages"?: number | null + [name: string]: unknown + } + content: { + "application/json": string + } + } + /** @description Error */ + default: { + headers: { + [name: string]: unknown + } + content: { + "application/problem+json": components["schemas"]["ErrorModel"] + } + } + } + } "ping": { parameters: { query?: never