diff --git a/src/pages/repair/NotificationPreferences.tsx b/src/pages/repair/NotificationPreferences.tsx new file mode 100644 index 0000000..d6b426e --- /dev/null +++ b/src/pages/repair/NotificationPreferences.tsx @@ -0,0 +1,188 @@ +import { useEffect, useState } from "react" +import { + Modal, + ModalContent, + ModalHeader, + ModalBody, + ModalFooter, + Switch, + Spinner, + Button, +} from "@heroui/react" +import { saturdayClient } from "../../utils/client" +import type { components } from "../../types/saturday" + +type NotificationPreferenceItem = components["schemas"]["NotificationPreferenceItem"] +type Item = components["schemas"]["Item"] + +interface NotificationPreferencesProps { + token: string +} + +export default function NotificationPreferences({ token }: NotificationPreferencesProps) { + const [isOpen, setIsOpen] = useState(false) + const [preferences, setPreferences] = useState([]) + const [isLoading, setIsLoading] = useState(false) + const [isSaving, setIsSaving] = useState(false) + const [errorMessage, setErrorMessage] = useState("") + const [successMessage, setSuccessMessage] = useState("") + + const openModal = () => { + setIsOpen(true) + loadPreferences() + } + + const closeModal = () => { + setIsOpen(false) + setErrorMessage("") + setSuccessMessage("") + } + + const loadPreferences = async () => { + if (!token) return + + setIsLoading(true) + setErrorMessage("") + + try { + const { data, error } = await saturdayClient.GET("/member/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 as an array of Item objects + const preferencesArray: Item[] = preferences.map(pref => ({ + notificationType: pref.notificationType, + enabled: pref.notificationType === notificationType ? newValue : pref.enabled, + })) + + const { error } = await saturdayClient.PUT("/member/notification-preferences", { + params: { + header: { + Authorization: `Bearer ${token}`, + }, + }, + body: { + preferences: preferencesArray, + }, + }) + + 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) + } + } + + return ( + <> + + + + + +

通知偏好设置

+

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

+
+ + {isLoading ? ( +
+ +
+ ) : ( + <> +
+ {preferences.map((pref) => ( +
+
+

{pref.description}

+

{pref.notificationType}

+
+ handleToggle(pref.notificationType, value)} + isDisabled={isSaving} + size="sm" + /> +
+ ))} + + {preferences.length === 0 && !errorMessage && ( +
+

暂无可用的通知偏好设置

+
+ )} +
+ + {/* Messages */} + {errorMessage && ( +
+

{errorMessage}

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

{successMessage}

+
+ )} + + )} +
+ + + +
+
+ + ) +} diff --git a/src/pages/repair/RepairAdmin.tsx b/src/pages/repair/RepairAdmin.tsx index 0054e79..eb70090 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"] @@ -525,11 +526,12 @@ export default function App() {
维修管理
- { - userInfo?.roles?.find(v => v.toLowerCase() == "repair admin") - ?
- : <> - } +
+ {token && } + {userInfo?.roles?.find(v => v.toLowerCase() == "repair admin") && ( + + )} +
{/* Mobile Cards Layout */} diff --git a/src/types/saturday.d.ts b/src/types/saturday.d.ts index c831254..c79c30f 100644 --- a/src/types/saturday.d.ts +++ b/src/types/saturday.d.ts @@ -298,6 +298,24 @@ export interface paths { patch: operations["alter-commit-event"] trace?: never } + "/member/notification-preferences": { + parameters: { + query?: never + header?: never + path?: never + cookie?: never + } + /** Get member notification preferences */ + get: operations["get-notification-preferences"] + /** Update member notification preferences */ + put: operations["update-notification-preferences"] + post?: never + delete?: never + options?: never + head?: never + patch?: never + trace?: never + } "/member/token/logto": { parameters: { query?: never @@ -426,10 +444,10 @@ export interface components { schemas: { "ActivateMemberRequest": { /** - * Format: uri - * @description A URL to the JSON Schema for this object. - * @example https://api.nbtca.space/schemas/ActivateMemberRequest.json - */ + * Format: uri + * @description A URL to the JSON Schema for this object. + * @example https://api.nbtca.space/schemas/ActivateMemberRequest.json + */ readonly $schema?: string MemberId: string alias: string @@ -440,29 +458,29 @@ export interface components { } "AlterCommitEventInputBody": { /** - * Format: uri - * @description A URL to the JSON Schema for this object. - * @example https://api.nbtca.space/schemas/AlterCommitEventInputBody.json - */ + * Format: uri + * @description A URL to the JSON Schema for this object. + * @example https://api.nbtca.space/schemas/AlterCommitEventInputBody.json + */ readonly $schema?: string content: string size?: string } "Bind-member-logto-idRequest": { /** - * Format: uri - * @description A URL to the JSON Schema for this object. - * @example https://api.nbtca.space/schemas/Bind-member-logto-idRequest.json - */ + * Format: uri + * @description A URL to the JSON Schema for this object. + * @example https://api.nbtca.space/schemas/Bind-member-logto-idRequest.json + */ readonly $schema?: string password: string } "ClientTokenResponse": { /** - * Format: uri - * @description A URL to the JSON Schema for this object. - * @example https://api.nbtca.space/schemas/ClientTokenResponse.json - */ + * Format: uri + * @description A URL to the JSON Schema for this object. + * @example https://api.nbtca.space/schemas/ClientTokenResponse.json + */ readonly $schema?: string /** Format: int64 */ clientId: number @@ -474,38 +492,38 @@ export interface components { } "CommitEventInputBody": { /** - * Format: uri - * @description A URL to the JSON Schema for this object. - * @example https://api.nbtca.space/schemas/CommitEventInputBody.json - */ + * Format: uri + * @description A URL to the JSON Schema for this object. + * @example https://api.nbtca.space/schemas/CommitEventInputBody.json + */ readonly $schema?: string content: string size?: string } "Create-token-via-wechatRequest": { /** - * Format: uri - * @description A URL to the JSON Schema for this object. - * @example https://api.nbtca.space/schemas/Create-token-via-wechatRequest.json - */ + * Format: uri + * @description A URL to the JSON Schema for this object. + * @example https://api.nbtca.space/schemas/Create-token-via-wechatRequest.json + */ readonly $schema?: string code: string } "Create-tokenRequest": { /** - * Format: uri - * @description A URL to the JSON Schema for this object. - * @example https://api.nbtca.space/schemas/Create-tokenRequest.json - */ + * Format: uri + * @description A URL to the JSON Schema for this object. + * @example https://api.nbtca.space/schemas/Create-tokenRequest.json + */ readonly $schema?: string password: string } "CreateClientEventInputBody": { /** - * Format: uri - * @description A URL to the JSON Schema for this object. - * @example https://api.nbtca.space/schemas/CreateClientEventInputBody.json - */ + * Format: uri + * @description A URL to the JSON Schema for this object. + * @example https://api.nbtca.space/schemas/CreateClientEventInputBody.json + */ readonly $schema?: string contactPreference?: string model?: string @@ -515,10 +533,10 @@ export interface components { } "CreateMemberRequest": { /** - * Format: uri - * @description A URL to the JSON Schema for this object. - * @example https://api.nbtca.space/schemas/CreateMemberRequest.json - */ + * Format: uri + * @description A URL to the JSON Schema for this object. + * @example https://api.nbtca.space/schemas/CreateMemberRequest.json + */ readonly $schema?: string alias: string avatar: string @@ -533,10 +551,10 @@ export interface components { } "CreateMemberTokenResponse": { /** - * Format: uri - * @description A URL to the JSON Schema for this object. - * @example https://api.nbtca.space/schemas/CreateMemberTokenResponse.json - */ + * Format: uri + * @description A URL to the JSON Schema for this object. + * @example https://api.nbtca.space/schemas/CreateMemberTokenResponse.json + */ readonly $schema?: string alias: string avatar: string @@ -547,6 +565,7 @@ export interface components { logtoId: string memberId: string name: string + notificationPreferences: components["schemas"]["NotificationPreferences"] phone: string profile: string qq: string @@ -564,49 +583,49 @@ export interface components { } "ErrorModel": { /** - * Format: uri - * @description A URL to the JSON Schema for this object. - * @example https://api.nbtca.space/schemas/ErrorModel.json - */ + * Format: uri + * @description A URL to the JSON Schema for this object. + * @example https://api.nbtca.space/schemas/ErrorModel.json + */ readonly $schema?: string /** - * @description A human-readable explanation specific to this occurrence of the problem. - * @example Property foo is required but is missing. - */ + * @description A human-readable explanation specific to this occurrence of the problem. + * @example Property foo is required but is missing. + */ detail?: string /** @description Optional list of individual error details */ errors?: components["schemas"]["ErrorDetail"][] | null /** - * Format: uri - * @description A URI reference that identifies the specific occurrence of the problem. - * @example https://example.com/error-log/abc123 - */ + * Format: uri + * @description A URI reference that identifies the specific occurrence of the problem. + * @example https://example.com/error-log/abc123 + */ instance?: string /** - * Format: int64 - * @description HTTP status code - * @example 400 - */ + * Format: int64 + * @description HTTP status code + * @example 400 + */ status?: number /** - * @description A short, human-readable summary of the problem type. This value should not change between occurrences of the error. - * @example Bad Request - */ + * @description A short, human-readable summary of the problem type. This value should not change between occurrences of the error. + * @example Bad Request + */ title?: string /** - * Format: uri - * @description A URI reference to human-readable documentation for the error. - * @default about:blank - * @example https://example.com/errors/example - */ + * Format: uri + * @description A URI reference to human-readable documentation for the error. + * @default about:blank + * @example https://example.com/errors/example + */ type: string } "Event": { /** - * Format: uri - * @description A URL to the JSON Schema for this object. - * @example https://api.nbtca.space/schemas/Event.json - */ + * Format: uri + * @description A URL to the JSON Schema for this object. + * @example https://api.nbtca.space/schemas/Event.json + */ readonly $schema?: string /** Format: int64 */ clientId: number @@ -637,12 +656,16 @@ export interface components { logId: number memberId: string } + "Item": { + enabled: boolean + notificationType: string + } "Member": { /** - * Format: uri - * @description A URL to the JSON Schema for this object. - * @example https://api.nbtca.space/schemas/Member.json - */ + * Format: uri + * @description A URL to the JSON Schema for this object. + * @example https://api.nbtca.space/schemas/Member.json + */ readonly $schema?: string alias: string avatar: string @@ -653,12 +676,23 @@ export interface components { logtoId: string memberId: string name: string + notificationPreferences: components["schemas"]["NotificationPreferences"] phone: string profile: string qq: string role: string section: string } + "NotificationPreferenceItem": { + description: string + enabled: boolean + notificationType: string + } + "NotificationPreferences": { + event_assigned_to_me: boolean + event_status_changed: boolean + new_event_created: boolean + } "NullInt64": { /** Format: int64 */ Int64: number @@ -666,23 +700,23 @@ export interface components { } "PingResponse": { /** - * Format: uri - * @description A URL to the JSON Schema for this object. - * @example https://api.nbtca.space/schemas/PingResponse.json - */ + * Format: uri + * @description A URL to the JSON Schema for this object. + * @example https://api.nbtca.space/schemas/PingResponse.json + */ readonly $schema?: string /** - * @description Ping message - * @example ping - */ + * @description Ping message + * @example ping + */ message: string } "PublicEvent": { /** - * Format: uri - * @description A URL to the JSON Schema for this object. - * @example https://api.nbtca.space/schemas/PublicEvent.json - */ + * Format: uri + * @description A URL to the JSON Schema for this object. + * @example https://api.nbtca.space/schemas/PublicEvent.json + */ readonly $schema?: string /** Format: int64 */ clientId: number @@ -704,10 +738,10 @@ export interface components { } "PublicMember": { /** - * Format: uri - * @description A URL to the JSON Schema for this object. - * @example https://api.nbtca.space/schemas/PublicMember.json - */ + * Format: uri + * @description A URL to the JSON Schema for this object. + * @example https://api.nbtca.space/schemas/PublicMember.json + */ readonly $schema?: string alias: string avatar: string @@ -720,10 +754,10 @@ export interface components { } "UpdateClientEventInputBody": { /** - * Format: uri - * @description A URL to the JSON Schema for this object. - * @example https://api.nbtca.space/schemas/UpdateClientEventInputBody.json - */ + * Format: uri + * @description A URL to the JSON Schema for this object. + * @example https://api.nbtca.space/schemas/UpdateClientEventInputBody.json + */ readonly $schema?: string contactPreference?: string model?: string @@ -734,20 +768,20 @@ export interface components { } "UpdateMemberAvatarInputBody": { /** - * Format: uri - * @description A URL to the JSON Schema for this object. - * @example https://api.nbtca.space/schemas/UpdateMemberAvatarInputBody.json - */ + * Format: uri + * @description A URL to the JSON Schema for this object. + * @example https://api.nbtca.space/schemas/UpdateMemberAvatarInputBody.json + */ readonly $schema?: string /** @description Avatar URL */ avatar: string } "UpdateMemberBasicRequest": { /** - * Format: uri - * @description A URL to the JSON Schema for this object. - * @example https://api.nbtca.space/schemas/UpdateMemberBasicRequest.json - */ + * Format: uri + * @description A URL to the JSON Schema for this object. + * @example https://api.nbtca.space/schemas/UpdateMemberBasicRequest.json + */ readonly $schema?: string memberId: string name: string @@ -756,10 +790,10 @@ export interface components { } "UpdateMemberRequest": { /** - * Format: uri - * @description A URL to the JSON Schema for this object. - * @example https://api.nbtca.space/schemas/UpdateMemberRequest.json - */ + * Format: uri + * @description A URL to the JSON Schema for this object. + * @example https://api.nbtca.space/schemas/UpdateMemberRequest.json + */ readonly $schema?: string MemberId: string alias: string @@ -769,6 +803,15 @@ export interface components { profile: string qq: 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 + preferences: components["schemas"]["Item"][] | null + } } responses: never parameters: never @@ -823,14 +866,14 @@ export interface operations { parameters: { query?: { /** - * @description Offset - * @example 0 - */ + * @description Offset + * @example 0 + */ offset?: number /** - * @description Limit - * @example 50 - */ + * @description Limit + * @example 50 + */ limit?: number status?: string order?: string @@ -878,9 +921,9 @@ export interface operations { } path: { /** - * @description Event ID - * @example 123 - */ + * @description Event ID + * @example 123 + */ EventId: number } cookie?: never @@ -921,9 +964,9 @@ export interface operations { } path: { /** - * @description Event ID - * @example 123 - */ + * @description Event ID + * @example 123 + */ EventId: number } cookie?: never @@ -964,9 +1007,9 @@ export interface operations { } path: { /** - * @description Event ID - * @example 123 - */ + * @description Event ID + * @example 123 + */ EventId: number } cookie?: never @@ -1081,14 +1124,14 @@ export interface operations { parameters: { query?: { /** - * @description Offset - * @example 0 - */ + * @description Offset + * @example 0 + */ offset?: number /** - * @description Limit - * @example 50 - */ + * @description Limit + * @example 50 + */ limit?: number status?: string[] | null order?: string @@ -1204,9 +1247,9 @@ export interface operations { } path: { /** - * @description Event ID - * @example 123 - */ + * @description Event ID + * @example 123 + */ EventId: number } cookie?: never @@ -1247,9 +1290,9 @@ export interface operations { } path: { /** - * @description Event ID - * @example 123 - */ + * @description Event ID + * @example 123 + */ EventId: number } cookie?: never @@ -1445,14 +1488,14 @@ export interface operations { parameters: { query?: { /** - * @description Offset - * @example 0 - */ + * @description Offset + * @example 0 + */ offset?: number /** - * @description Limit - * @example 50 - */ + * @description Limit + * @example 50 + */ limit?: number status?: string order?: string @@ -1500,9 +1543,9 @@ export interface operations { } path: { /** - * @description Event ID - * @example 123 - */ + * @description Event ID + * @example 123 + */ EventId: number } cookie?: never @@ -1543,9 +1586,9 @@ export interface operations { } path: { /** - * @description Event ID - * @example 123 - */ + * @description Event ID + * @example 123 + */ EventId: number } cookie?: never @@ -1586,9 +1629,9 @@ export interface operations { } path: { /** - * @description Event ID - * @example 123 - */ + * @description Event ID + * @example 123 + */ EventId: number } cookie?: never @@ -1629,9 +1672,9 @@ export interface operations { } path: { /** - * @description Event ID - * @example 123 - */ + * @description Event ID + * @example 123 + */ EventId: number } cookie?: never @@ -1676,9 +1719,9 @@ export interface operations { } path: { /** - * @description Event ID - * @example 123 - */ + * @description Event ID + * @example 123 + */ EventId: number } cookie?: never @@ -1714,6 +1757,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"] + } + } + } + } "create-token-via-logto-token": { parameters: { query?: never @@ -1754,14 +1875,14 @@ export interface operations { parameters: { query?: { /** - * @description Offset - * @example 0 - */ + * @description Offset + * @example 0 + */ offset?: number /** - * @description Limit - * @example 50 - */ + * @description Limit + * @example 50 + */ limit?: number } header?: never @@ -1840,14 +1961,14 @@ export interface operations { parameters: { query?: { /** - * @description Offset - * @example 0 - */ + * @description Offset + * @example 0 + */ offset?: number /** - * @description Limit - * @example 50 - */ + * @description Limit + * @example 50 + */ limit?: number } header?: { @@ -1890,9 +2011,9 @@ export interface operations { header?: never path: { /** - * @description Name to greet - * @example 2333333333 - */ + * @description Name to greet + * @example 2333333333 + */ MemberId: string } cookie?: never @@ -1933,9 +2054,9 @@ export interface operations { } path: { /** - * @description Member ID - * @example 2333333333 - */ + * @description Member ID + * @example 2333333333 + */ MemberId: string } cookie?: never @@ -1980,9 +2101,9 @@ export interface operations { } path: { /** - * @description Member ID - * @example 2333333333 - */ + * @description Member ID + * @example 2333333333 + */ MemberId: string } cookie?: never @@ -2026,9 +2147,9 @@ export interface operations { } path: { /** - * @description Member Id - * @example 2333333333 - */ + * @description Member Id + * @example 2333333333 + */ MemberId: string } cookie?: never @@ -2070,9 +2191,9 @@ export interface operations { header?: never path: { /** - * @description Member Id - * @example 2333333333 - */ + * @description Member Id + * @example 2333333333 + */ MemberId: string } cookie?: never