mirror of
https://github.com/m1ngsama/FUJI.git
synced 2025-12-24 10:51:27 +00:00
save
This commit is contained in:
parent
53d6723dfb
commit
7a33a00ab6
7 changed files with 154 additions and 26 deletions
5
.vscode/settings.json
vendored
5
.vscode/settings.json
vendored
|
|
@ -8,7 +8,10 @@
|
||||||
],
|
],
|
||||||
"eslint.experimental.useFlatConfig": true,
|
"eslint.experimental.useFlatConfig": true,
|
||||||
"cSpell.words": [
|
"cSpell.words": [
|
||||||
|
"ical",
|
||||||
"Logto",
|
"Logto",
|
||||||
"qrcode"
|
"qrcode",
|
||||||
|
"Subcomponents",
|
||||||
|
"vevent"
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,10 +14,10 @@ export default defineConfig({
|
||||||
syntaxHighlight: "shiki",
|
syntaxHighlight: "shiki",
|
||||||
shikiConfig: {
|
shikiConfig: {
|
||||||
themes: {
|
themes: {
|
||||||
light: 'github-light',
|
light: "github-light",
|
||||||
dark: 'github-dark',
|
dark: "github-dark",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
|
||||||
},
|
},
|
||||||
integrations: [
|
integrations: [
|
||||||
vue(),
|
vue(),
|
||||||
|
|
@ -39,7 +39,10 @@ export default defineConfig({
|
||||||
target: "http://localhost:4000",
|
target: "http://localhost:4000",
|
||||||
rewrite: path => path.replace(/^\/saturday/, ""),
|
rewrite: path => path.replace(/^\/saturday/, ""),
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
// optimizeDeps: {
|
||||||
|
// exclude: ["dayjs"],
|
||||||
|
// },
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,10 @@
|
||||||
"Rehype",
|
"Rehype",
|
||||||
"rehypePlugins",
|
"rehypePlugins",
|
||||||
"shiki",
|
"shiki",
|
||||||
"tseslint"
|
"tseslint",
|
||||||
|
"ical",
|
||||||
|
"ICAL",
|
||||||
|
"vevents",
|
||||||
|
"getAllSubcomponents"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
@ -24,12 +24,13 @@
|
||||||
"@fullcalendar/react": "^6.1.17",
|
"@fullcalendar/react": "^6.1.17",
|
||||||
"@headlessui/vue": "^1.7.23",
|
"@headlessui/vue": "^1.7.23",
|
||||||
"@heroui/react": "2.7.6",
|
"@heroui/react": "2.7.6",
|
||||||
|
"@internationalized/date": "^3.8.0",
|
||||||
"@logto/browser": "^2.2.18",
|
"@logto/browser": "^2.2.18",
|
||||||
"@stylistic/eslint-plugin": "^2.13.0",
|
"@stylistic/eslint-plugin": "^2.13.0",
|
||||||
"astro": "^4.16.18",
|
"astro": "^4.16.18",
|
||||||
"dayjs": "^1.11.13",
|
"dayjs": "^1.11.13",
|
||||||
"framer-motion": "^11.18.2",
|
"framer-motion": "^11.18.2",
|
||||||
"ical.js": "^1.5.0",
|
"ical.js": "^2.1.0",
|
||||||
"md5": "^2.3.0",
|
"md5": "^2.3.0",
|
||||||
"openapi-fetch": "^0.12.5",
|
"openapi-fetch": "^0.12.5",
|
||||||
"qrcode": "^1.5.4",
|
"qrcode": "^1.5.4",
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ importers:
|
||||||
version: 6.1.17(@fullcalendar/core@6.1.17)
|
version: 6.1.17(@fullcalendar/core@6.1.17)
|
||||||
'@fullcalendar/icalendar':
|
'@fullcalendar/icalendar':
|
||||||
specifier: ^6.1.17
|
specifier: ^6.1.17
|
||||||
version: 6.1.17(@fullcalendar/core@6.1.17)(ical.js@1.5.0)
|
version: 6.1.17(@fullcalendar/core@6.1.17)(ical.js@2.1.0)
|
||||||
'@fullcalendar/react':
|
'@fullcalendar/react':
|
||||||
specifier: ^6.1.17
|
specifier: ^6.1.17
|
||||||
version: 6.1.17(@fullcalendar/core@6.1.17)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
version: 6.1.17(@fullcalendar/core@6.1.17)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
|
|
@ -38,6 +38,9 @@ importers:
|
||||||
'@heroui/react':
|
'@heroui/react':
|
||||||
specifier: 2.7.6
|
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)
|
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.0
|
||||||
|
version: 3.8.0
|
||||||
'@logto/browser':
|
'@logto/browser':
|
||||||
specifier: ^2.2.18
|
specifier: ^2.2.18
|
||||||
version: 2.2.18
|
version: 2.2.18
|
||||||
|
|
@ -54,8 +57,8 @@ importers:
|
||||||
specifier: ^11.18.2
|
specifier: ^11.18.2
|
||||||
version: 11.18.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
version: 11.18.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
ical.js:
|
ical.js:
|
||||||
specifier: ^1.5.0
|
specifier: ^2.1.0
|
||||||
version: 1.5.0
|
version: 2.1.0
|
||||||
md5:
|
md5:
|
||||||
specifier: ^2.3.0
|
specifier: ^2.3.0
|
||||||
version: 2.3.0
|
version: 2.3.0
|
||||||
|
|
@ -3748,8 +3751,8 @@ packages:
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
ical.js@1.5.0:
|
ical.js@2.1.0:
|
||||||
resolution: {integrity: sha512-7ZxMkogUkkaCx810yp0ZGKvq1ZpRgJeornPttpoxe6nYZ3NLesZe1wWMXDdwTkj/b5NtXT+Y16Aakph/ao98ZQ==}
|
resolution: {integrity: sha512-BOVfrH55xQ6kpS3muGvIXIg2l7p+eoe12/oS7R5yrO3TL/j/bLsR0PR+tYQESFbyTbvGgPHn9zQ6tI4FWyuSaQ==}
|
||||||
|
|
||||||
ignore@5.3.2:
|
ignore@5.3.2:
|
||||||
resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==}
|
resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==}
|
||||||
|
|
@ -6190,10 +6193,10 @@ snapshots:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@fullcalendar/core': 6.1.17
|
'@fullcalendar/core': 6.1.17
|
||||||
|
|
||||||
'@fullcalendar/icalendar@6.1.17(@fullcalendar/core@6.1.17)(ical.js@1.5.0)':
|
'@fullcalendar/icalendar@6.1.17(@fullcalendar/core@6.1.17)(ical.js@2.1.0)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@fullcalendar/core': 6.1.17
|
'@fullcalendar/core': 6.1.17
|
||||||
ical.js: 1.5.0
|
ical.js: 2.1.0
|
||||||
|
|
||||||
'@fullcalendar/react@6.1.17(@fullcalendar/core@6.1.17)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
'@fullcalendar/react@6.1.17(@fullcalendar/core@6.1.17)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||||
dependencies:
|
dependencies:
|
||||||
|
|
@ -7509,7 +7512,7 @@ snapshots:
|
||||||
|
|
||||||
'@react-aria/calendar@3.7.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
'@react-aria/calendar@3.7.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@internationalized/date': 3.7.0
|
'@internationalized/date': 3.8.0
|
||||||
'@react-aria/i18n': 3.12.7(react-dom@18.3.1(react@18.3.1))(react@18.3.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/interactions': 3.24.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
'@react-aria/live-announcer': 3.4.2
|
'@react-aria/live-announcer': 3.4.2
|
||||||
|
|
@ -7561,7 +7564,7 @@ snapshots:
|
||||||
|
|
||||||
'@react-aria/datepicker@3.14.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
'@react-aria/datepicker@3.14.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@internationalized/date': 3.7.0
|
'@internationalized/date': 3.8.0
|
||||||
'@internationalized/number': 3.6.1
|
'@internationalized/number': 3.6.1
|
||||||
'@internationalized/string': 3.2.6
|
'@internationalized/string': 3.2.6
|
||||||
'@react-aria/focus': 3.20.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
'@react-aria/focus': 3.20.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
|
|
@ -8102,7 +8105,7 @@ snapshots:
|
||||||
|
|
||||||
'@react-stately/calendar@3.7.1(react@18.3.1)':
|
'@react-stately/calendar@3.7.1(react@18.3.1)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@internationalized/date': 3.7.0
|
'@internationalized/date': 3.8.0
|
||||||
'@react-stately/utils': 3.10.5(react@18.3.1)
|
'@react-stately/utils': 3.10.5(react@18.3.1)
|
||||||
'@react-types/calendar': 3.6.1(react@18.3.1)
|
'@react-types/calendar': 3.6.1(react@18.3.1)
|
||||||
'@react-types/shared': 3.28.0(react@18.3.1)
|
'@react-types/shared': 3.28.0(react@18.3.1)
|
||||||
|
|
@ -8145,7 +8148,7 @@ snapshots:
|
||||||
|
|
||||||
'@react-stately/datepicker@3.13.0(react@18.3.1)':
|
'@react-stately/datepicker@3.13.0(react@18.3.1)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@internationalized/date': 3.7.0
|
'@internationalized/date': 3.8.0
|
||||||
'@internationalized/string': 3.2.6
|
'@internationalized/string': 3.2.6
|
||||||
'@react-stately/form': 3.1.3(react@18.3.1)
|
'@react-stately/form': 3.1.3(react@18.3.1)
|
||||||
'@react-stately/overlays': 3.6.15(react@18.3.1)
|
'@react-stately/overlays': 3.6.15(react@18.3.1)
|
||||||
|
|
@ -8381,7 +8384,7 @@ snapshots:
|
||||||
|
|
||||||
'@react-types/calendar@3.6.1(react@18.3.1)':
|
'@react-types/calendar@3.6.1(react@18.3.1)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@internationalized/date': 3.7.0
|
'@internationalized/date': 3.8.0
|
||||||
'@react-types/shared': 3.28.0(react@18.3.1)
|
'@react-types/shared': 3.28.0(react@18.3.1)
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
|
|
||||||
|
|
@ -8408,7 +8411,7 @@ snapshots:
|
||||||
|
|
||||||
'@react-types/datepicker@3.11.0(react@18.3.1)':
|
'@react-types/datepicker@3.11.0(react@18.3.1)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@internationalized/date': 3.7.0
|
'@internationalized/date': 3.8.0
|
||||||
'@react-types/calendar': 3.7.0(react@18.3.1)
|
'@react-types/calendar': 3.7.0(react@18.3.1)
|
||||||
'@react-types/overlays': 3.8.14(react@18.3.1)
|
'@react-types/overlays': 3.8.14(react@18.3.1)
|
||||||
'@react-types/shared': 3.29.0(react@18.3.1)
|
'@react-types/shared': 3.29.0(react@18.3.1)
|
||||||
|
|
@ -10447,7 +10450,7 @@ snapshots:
|
||||||
|
|
||||||
husky@9.1.7: {}
|
husky@9.1.7: {}
|
||||||
|
|
||||||
ical.js@1.5.0: {}
|
ical.js@2.1.0: {}
|
||||||
|
|
||||||
ignore@5.3.2: {}
|
ignore@5.3.2: {}
|
||||||
|
|
||||||
|
|
|
||||||
114
src/pages/calendar/Schedule.tsx
Normal file
114
src/pages/calendar/Schedule.tsx
Normal file
|
|
@ -0,0 +1,114 @@
|
||||||
|
import ICAL from "ical.js"
|
||||||
|
import dayjs from "dayjs"
|
||||||
|
import { useEffect, useState } from "react"
|
||||||
|
import { Calendar } from "@heroui/react"
|
||||||
|
import { today, getLocalTimeZone } from "@internationalized/date"
|
||||||
|
|
||||||
|
type ScheduleEvent = {
|
||||||
|
start: Date
|
||||||
|
end: Date
|
||||||
|
summary: string
|
||||||
|
description: string
|
||||||
|
recurrenceId?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const parseCal = async (): Promise<ICAL.Component> => {
|
||||||
|
const res = await fetch("https://ical.nbtca.space/").then(res => res.text())
|
||||||
|
const jcalData = ICAL.parse(res)
|
||||||
|
return new ICAL.Component(jcalData)
|
||||||
|
}
|
||||||
|
|
||||||
|
const expandEventOccurrences = (
|
||||||
|
event: ICAL.Event,
|
||||||
|
rangeEnd: ICAL.Time,
|
||||||
|
): ScheduleEvent[] => {
|
||||||
|
const occurrences: ScheduleEvent[] = []
|
||||||
|
const iterator = event.iterator()
|
||||||
|
let next: ICAL.Time | null = null
|
||||||
|
|
||||||
|
while ((next = iterator.next())) {
|
||||||
|
if (next.compare(event.startDate) < 0) continue
|
||||||
|
if (next.compare(rangeEnd) > 0) break
|
||||||
|
const details = event.getOccurrenceDetails(next)
|
||||||
|
occurrences.push({
|
||||||
|
start: details.startDate.toJSDate(),
|
||||||
|
end: details.endDate.toJSDate(),
|
||||||
|
summary: event.summary,
|
||||||
|
description: event.description,
|
||||||
|
recurrenceId: event.recurrenceId,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return occurrences
|
||||||
|
}
|
||||||
|
|
||||||
|
const extractScheduleEvents = (icalComp: ICAL.Component): ScheduleEvent[] => {
|
||||||
|
const vevents = icalComp.getAllSubcomponents("vevent")
|
||||||
|
const rangeEnd = ICAL.Time.fromDateString("2026-01-01")
|
||||||
|
|
||||||
|
return vevents
|
||||||
|
.flatMap<ScheduleEvent>((vevent) => {
|
||||||
|
const event = new ICAL.Event(vevent)
|
||||||
|
if (event.iterator().complete) {
|
||||||
|
return [{
|
||||||
|
start: event.startDate.toJSDate(),
|
||||||
|
end: event.endDate.toJSDate(),
|
||||||
|
summary: event.summary,
|
||||||
|
description: event.description,
|
||||||
|
recurrenceId: "123",
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
return expandEventOccurrences(event, rangeEnd)
|
||||||
|
})
|
||||||
|
.sort((a, b) => b.start.getTime() - a.start.getTime())
|
||||||
|
}
|
||||||
|
|
||||||
|
const formatTimePair = (s: Date, e: Date): string => {
|
||||||
|
const start = dayjs(s)
|
||||||
|
const end = dayjs(e)
|
||||||
|
if (start.isSame(end, "day")) {
|
||||||
|
return `${start.format("YYYY-MM-DD HH:mm")} - ${end.format("HH:mm")}`
|
||||||
|
}
|
||||||
|
return `${start.format("YYYY-MM-DD")} - ${end.format("YYYY-MM-DD")}`
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Schedule() {
|
||||||
|
const [scheduledEvents, setScheduledEvents] = useState<ScheduleEvent[]>([])
|
||||||
|
|
||||||
|
const defaultDate = today(getLocalTimeZone())
|
||||||
|
const [focusedDate, setFocusedDate] = useState(defaultDate)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
parseCal().then((icalComp) => {
|
||||||
|
setScheduledEvents(extractScheduleEvents(icalComp))
|
||||||
|
})
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section className="w-full flex flex-col ">
|
||||||
|
<div className="section-content my-8 text-2xl font-bold">日程</div>
|
||||||
|
<div className="section-content grid grid-cols-5">
|
||||||
|
<div className="flex flex-col col-span-3">
|
||||||
|
{scheduledEvents.map((event, index) => (
|
||||||
|
<div key={index} className="p-4 m-2 border rounded-lg shadow">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<div className="text-xl font-semibold">{event.summary}</div>
|
||||||
|
<span>{ formatTimePair(event.start, event.end) }</span>
|
||||||
|
</div>
|
||||||
|
<p>{event.description}</p>
|
||||||
|
<p>{event.recurrenceId}</p>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<div className="col-span-2">
|
||||||
|
<Calendar
|
||||||
|
aria-label="Date (Controlled Focused Value)"
|
||||||
|
focusedValue={focusedDate}
|
||||||
|
value={defaultDate}
|
||||||
|
onFocusChange={setFocusedDate}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
---
|
---
|
||||||
import FullCalendar from "./Calendar"
|
import Schedule from "./Schedule"
|
||||||
import BaseLayout from "../../layouts/BaseLayout.astro"
|
import BaseLayout from "../../layouts/BaseLayout.astro"
|
||||||
---
|
---
|
||||||
|
|
||||||
<BaseLayout primaryTitle="日历">
|
<BaseLayout primaryTitle="日历">
|
||||||
<FullCalendar client:load />
|
<Schedule client:load />
|
||||||
</BaseLayout>
|
</BaseLayout>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue