add export dialog

This commit is contained in:
Clas Wen 2025-05-24 14:24:16 +08:00
parent 6a99120e14
commit 74c95626c8
5 changed files with 132 additions and 16 deletions

View file

@ -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",

View file

@ -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)

View file

@ -129,7 +129,7 @@ export const EventActionCommit = (props: EventActionProps) => {
variant="flat"
color="primary"
isLoading={props.isLoading === "commit"}
onPress={() => onSubmit()}
onPress={onSubmit}
>
</Button>
@ -175,7 +175,7 @@ export const EventActionAlterCommit = (props: EventActionProps) => {
<Button
variant="flat"
isLoading={props.isLoading === "commit"}
onPress={() => onSubmit()}
onPress={onSubmit}
>
</Button>

View file

@ -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 (
<>
<Button onPress={openModal} color="primary">
Excel
</Button>
<Modal isOpen={isOpen} onClose={closeModal}>
<ModalContent>
<ModalHeader>Excel</ModalHeader>
<ModalBody>
<DateRangePicker
label="选择日期范围"
value={dateRange}
onChange={setDateRange}
granularity="day"
visibleMonths={2}
/>
</ModalBody>
<ModalFooter>
<Button variant="ghost" onClick={closeModal}>
</Button>
<Button
color="primary"
onClick={downloadExcel}
isLoading={loading}
disabled={loading}
>
{loading ? "导出中..." : "导出"}
</Button>
</ModalFooter>
</ModalContent>
</Modal>
</>
)
}

View file

@ -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 (
<section className="box-border mb-24">
<div className="section-content mt-6">
<div className="section-content mt-6 flex justify-between items-center">
<h2 className="text-2xl font-bold"></h2>
{
userInfo?.roles?.find(v => v.toLowerCase() == "repair admin")
? <ExportExcelModal></ExportExcelModal>
: <></>
}
</div>
<div className="section-content my-8 flex flex-col gap-4">
<Table