diff --git a/package.json b/package.json index 98815e3..3a9f32e 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "@fullcalendar/react": "^6.1.17", "@headlessui/vue": "^1.7.23", "@heroui/react": "2.7.6", + "@internationalized/date": "^3.8.1", "@logto/browser": "^2.2.18", "@react-stately/data": "^3.13.0", "@stylistic/eslint-plugin": "^2.13.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2a1d1f2..37608ee 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -38,6 +38,9 @@ importers: '@heroui/react': specifier: 2.7.6 version: 2.7.6(@types/react@18.3.20)(framer-motion@11.18.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(tailwindcss@3.4.17) + '@internationalized/date': + specifier: ^3.8.1 + version: 3.8.1 '@logto/browser': specifier: ^2.2.18 version: 2.2.18 @@ -1468,8 +1471,8 @@ packages: '@internationalized/date@3.7.0': resolution: {integrity: sha512-VJ5WS3fcVx0bejE/YHfbDKR/yawZgKqn/if+oEeLqNwBtPzVB06olkfcnojTmEMX+gTpH+FlQ69SHNitJ8/erQ==} - '@internationalized/date@3.8.0': - resolution: {integrity: sha512-J51AJ0fEL68hE4CwGPa6E0PO6JDaVLd8aln48xFCSy7CZkZc96dGEGmLs2OEEbBxcsVZtfrqkXJwI2/MSG8yKw==} + '@internationalized/date@3.8.1': + resolution: {integrity: sha512-PgVE6B6eIZtzf9Gu5HvJxRK3ufUFz9DhspELuhW/N0GuMGMTLvPQNRkHP2hTuP9lblOk+f+1xi96sPiPXANXAA==} '@internationalized/message@3.1.7': resolution: {integrity: sha512-gLQlhEW4iO7DEFPf/U7IrIdA3UyLGS0opeqouaFwlMObLUzwexRjbygONHDVbC9G9oFLXsLyGKYkJwqXw/QADg==} @@ -7404,7 +7407,7 @@ snapshots: dependencies: '@swc/helpers': 0.5.17 - '@internationalized/date@3.8.0': + '@internationalized/date@3.8.1': dependencies: '@swc/helpers': 0.5.17 @@ -7522,7 +7525,7 @@ snapshots: '@react-aria/calendar@3.7.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: - '@internationalized/date': 3.7.0 + '@internationalized/date': 3.8.1 '@react-aria/i18n': 3.12.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@react-aria/interactions': 3.24.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@react-aria/live-announcer': 3.4.2 @@ -7574,7 +7577,7 @@ snapshots: '@react-aria/datepicker@3.14.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: - '@internationalized/date': 3.7.0 + '@internationalized/date': 3.8.1 '@internationalized/number': 3.6.1 '@internationalized/string': 3.2.6 '@react-aria/focus': 3.20.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -7666,7 +7669,7 @@ snapshots: '@react-aria/i18n@3.12.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: - '@internationalized/date': 3.8.0 + '@internationalized/date': 3.8.1 '@internationalized/message': 3.1.7 '@internationalized/number': 3.6.1 '@internationalized/string': 3.2.6 @@ -7679,7 +7682,7 @@ snapshots: '@react-aria/i18n@3.12.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: - '@internationalized/date': 3.8.0 + '@internationalized/date': 3.8.1 '@internationalized/message': 3.1.7 '@internationalized/number': 3.6.1 '@internationalized/string': 3.2.6 @@ -8115,7 +8118,7 @@ snapshots: '@react-stately/calendar@3.7.1(react@18.3.1)': dependencies: - '@internationalized/date': 3.7.0 + '@internationalized/date': 3.8.1 '@react-stately/utils': 3.10.5(react@18.3.1) '@react-types/calendar': 3.6.1(react@18.3.1) '@react-types/shared': 3.28.0(react@18.3.1) @@ -8164,7 +8167,7 @@ snapshots: '@react-stately/datepicker@3.13.0(react@18.3.1)': dependencies: - '@internationalized/date': 3.7.0 + '@internationalized/date': 3.8.1 '@internationalized/string': 3.2.6 '@react-stately/form': 3.1.3(react@18.3.1) '@react-stately/overlays': 3.6.15(react@18.3.1) @@ -8400,13 +8403,13 @@ snapshots: '@react-types/calendar@3.6.1(react@18.3.1)': dependencies: - '@internationalized/date': 3.7.0 + '@internationalized/date': 3.8.1 '@react-types/shared': 3.28.0(react@18.3.1) react: 18.3.1 '@react-types/calendar@3.7.0(react@18.3.1)': dependencies: - '@internationalized/date': 3.8.0 + '@internationalized/date': 3.8.1 '@react-types/shared': 3.29.0(react@18.3.1) react: 18.3.1 @@ -8427,7 +8430,7 @@ snapshots: '@react-types/datepicker@3.11.0(react@18.3.1)': dependencies: - '@internationalized/date': 3.7.0 + '@internationalized/date': 3.8.1 '@react-types/calendar': 3.7.0(react@18.3.1) '@react-types/overlays': 3.8.14(react@18.3.1) '@react-types/shared': 3.29.0(react@18.3.1) diff --git a/src/pages/repair/EventAction.tsx b/src/pages/repair/EventAction.tsx index 71f27a6..950ee65 100644 --- a/src/pages/repair/EventAction.tsx +++ b/src/pages/repair/EventAction.tsx @@ -129,7 +129,7 @@ export const EventActionCommit = (props: EventActionProps) => { variant="flat" color="primary" isLoading={props.isLoading === "commit"} - onPress={() => onSubmit()} + onPress={onSubmit} > 提交 @@ -175,7 +175,7 @@ export const EventActionAlterCommit = (props: EventActionProps) => { diff --git a/src/pages/repair/ExportEventDialog.tsx b/src/pages/repair/ExportEventDialog.tsx new file mode 100644 index 0000000..2047b59 --- /dev/null +++ b/src/pages/repair/ExportEventDialog.tsx @@ -0,0 +1,107 @@ +import { useState } from "react" +import { + Modal, + ModalContent, + ModalHeader, + ModalBody, + ModalFooter, + Button, + DateRangePicker, +} from "@heroui/react" +import { parseDate } from "@internationalized/date" +import { saturdayApiBaseUrl } from "../../utils/client" +import { makeLogtoClient } from "../../utils/auth" + +export function ExportExcelModal() { + const [isOpen, setIsOpen] = useState(false) + const [dateRange, setDateRange] = useState({ + start: parseDate("2025-01-01"), + end: parseDate("2025-05-16"), + }) + const [loading, setLoading] = useState(false) + + const openModal = () => setIsOpen(true) + const closeModal = () => setIsOpen(false) + + const downloadExcel = async () => { + if (!dateRange.start || !dateRange.end) return + + setLoading(true) + try { + const start = dateRange.start.toString() // Format: 'YYYY-MM-DD' + const end = dateRange.end.toString() + const url = `${saturdayApiBaseUrl}/events/xlsx?start_time=${start}&end_time=${end}` + + const token = await makeLogtoClient().getAccessToken() + const response = await fetch(url, { + headers: { + Authorization: `Bearer ${token}`, + }, + }) + if (!response.ok) throw new Error("Download failed") + + // Extract filename from Content-Disposition header + const disposition = response.headers.get("Content-Disposition") + let filename = "export.xlsx" // Default filename + if (disposition && disposition.includes("filename=")) { + const filenameMatch = disposition.match(/filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/) + if (filenameMatch != null && filenameMatch[1]) { + filename = filenameMatch[1].replace(/['"]/g, "") + } + } + + const blob = await response.blob() + const downloadUrl = window.URL.createObjectURL(blob) + const link = document.createElement("a") + link.href = downloadUrl + link.setAttribute("download", filename) + document.body.appendChild(link) + link.click() + link.remove() + window.URL.revokeObjectURL(downloadUrl) // Clean up + } + catch (error) { + alert("Failed to download Excel file: " + error.message) + } + finally { + setLoading(false) + closeModal() + } + } + + return ( + <> + + + + + 导出为Excel + + + + + + + + + + + ) +} diff --git a/src/pages/repair/RepairAdmin.tsx b/src/pages/repair/RepairAdmin.tsx index e37c86a..dea45d4 100644 --- a/src/pages/repair/RepairAdmin.tsx +++ b/src/pages/repair/RepairAdmin.tsx @@ -33,6 +33,7 @@ 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" +import { ExportExcelModal } from "./ExportEventDialog" type PublicEvent = components["schemas"]["PublicEvent"] @@ -385,11 +386,15 @@ export default function App() { return cellValue } }, []) - return (
-
+

维修管理

+ { + userInfo?.roles?.find(v => v.toLowerCase() == "repair admin") + ? + : <> + }