docs: clarify event deletion flows and dialog handling for all event types
- Documented unified deletion process for single, single-in-series, and recurring series events - Explained custom dialog interception of Syncfusion RecurrenceAlert and DeleteAlert - Updated both README.md and .github/copilot-instructions.md to match current frontend logic
This commit is contained in:
19
.github/copilot-instructions.md
vendored
19
.github/copilot-instructions.md
vendored
@@ -70,10 +70,15 @@ Use this as your shared context when proposing changes. Keep edits minimal and m
|
|||||||
- Period label (display_name or name) with a badge indicating whether any holidays exist in that period (overlap check)
|
- Period label (display_name or name) with a badge indicating whether any holidays exist in that period (overlap check)
|
||||||
|
|
||||||
- Recurrence & holidays (latest):
|
- Recurrence & holidays (latest):
|
||||||
- Backend stores holiday skips in `EventException` and emits `RecurrenceException` (EXDATE) for master events in `GET /api/events`. EXDATE timestamps match each occurrence start time (UTC) so Syncfusion excludes instances on holidays reliably.
|
- Backend stores holiday skips in `EventException` and emits `RecurrenceException` (EXDATE) for master events in `GET /api/events`. EXDATE tokens are formatted in RFC 5545 compact form (`yyyyMMddTHHmmssZ`) and correspond to each occurrence start time (UTC). Syncfusion uses these to exclude holiday instances reliably.
|
||||||
- Frontend manually expands recurring events due to Syncfusion EXDATE handling bugs. Daily/Weekly recurrence patterns are expanded client-side with proper EXDATE filtering and DST timezone tolerance (2-hour window).
|
- Frontend lets Syncfusion handle all recurrence patterns natively (no client-side expansion). Scheduler field mappings include `recurrenceID`, `recurrenceRule`, and `recurrenceException` so series and edited occurrences are recognized correctly.
|
||||||
- Single occurrence editing: Users can detach individual occurrences from recurring series via confirmation dialog. The detach operation creates `EventException` records, generates EXDATE entries, and creates standalone events without affecting the master series.
|
- Event deletion: All event types (single, single-in-series, entire series) are handled with custom dialogs. The frontend intercepts Syncfusion's built-in RecurrenceAlert and DeleteAlert popups to provide a unified, user-friendly deletion flow:
|
||||||
- UI: Events with `SkipHolidays` render a TentTree icon directly after the main event icon in the scheduler event template. Icon color: black.
|
- Single (non-recurring) event: deleted directly after confirmation.
|
||||||
|
- Single occurrence of a recurring series: user can delete just that instance.
|
||||||
|
- Entire recurring series: user can delete all occurrences after a final custom confirmation dialog.
|
||||||
|
- Detached occurrences (edited/broken out): treated as single events.
|
||||||
|
- Single occurrence editing: Users can detach individual occurrences from recurring series. The frontend hooks `actionComplete`/`onActionCompleted` with `requestType='eventChanged'` to persist changes: it calls `POST /api/events/<id>/occurrences/<date>/detach` for single-occurrence edits and `PUT /api/events/<id>` for series or single events as appropriate. The backend creates `EventException` and a standalone `Event` without modifying the master beyond EXDATEs.
|
||||||
|
- UI: Events with `SkipHolidays` render a TentTree icon next to the main event icon. The custom recurrence icon in the header was removed; rely on Syncfusion’s native lower-right recurrence badge.
|
||||||
|
|
||||||
- Program info page (`dashboard/src/programminfo.tsx`):
|
- Program info page (`dashboard/src/programminfo.tsx`):
|
||||||
- Loads data from `dashboard/public/program-info.json` (app name, version, build info, tech stack, changelog).
|
- Loads data from `dashboard/public/program-info.json` (app name, version, build info, tech stack, changelog).
|
||||||
@@ -124,10 +129,10 @@ Note: Syncfusion usage in the dashboard is already documented above; if a UI for
|
|||||||
- Initialization scripts: legacy DB init scripts were removed; use Alembic and `initialize_database.py` going forward.
|
- Initialization scripts: legacy DB init scripts were removed; use Alembic and `initialize_database.py` going forward.
|
||||||
|
|
||||||
### Recurrence & holidays: conventions
|
### Recurrence & holidays: conventions
|
||||||
- Do not pre-expand recurrences on the backend. Always send master event with `RecurrenceRule` + `RecurrenceException`.
|
- Do not pre-expand recurrences on the backend. Always send master events with `RecurrenceRule` + `RecurrenceException`.
|
||||||
- Ensure EXDATE tokens include the occurrence start time (HH:mm:ss) in UTC to match manual expansion logic.
|
- Ensure EXDATE tokens are RFC 5545 timestamps (`yyyyMMddTHHmmssZ`) matching the occurrence start time (UTC) so Syncfusion can exclude them natively.
|
||||||
- When `skip_holidays` or recurrence changes, regenerate `EventException` rows so `RecurrenceException` stays in sync.
|
- When `skip_holidays` or recurrence changes, regenerate `EventException` rows so `RecurrenceException` stays in sync.
|
||||||
- Single occurrence detach: Use `POST /api/events/<id>/occurrences/<date>/detach` to create standalone events and add EXDATE entries without modifying master events.
|
- Single occurrence detach: Use `POST /api/events/<id>/occurrences/<date>/detach` to create standalone events and add EXDATE entries without modifying master events. The frontend persists edits via `actionComplete` (`requestType='eventChanged'`).
|
||||||
|
|
||||||
## Quick examples
|
## Quick examples
|
||||||
- Add client description persists to DB and publishes group via MQTT: see `PUT /api/clients/<uuid>/description` in `routes/clients.py`.
|
- Add client description persists to DB and publishes group via MQTT: see `PUT /api/clients/<uuid>/description` in `routes/clients.py`.
|
||||||
|
|||||||
19
README.md
19
README.md
@@ -39,7 +39,7 @@ A comprehensive multi-service digital signage solution for educational instituti
|
|||||||
|
|
||||||
## 🌟 Key Features
|
## 🌟 Key Features
|
||||||
|
|
||||||
### 📊 **Dashboard Management**
|
|
||||||
- Modern React-based web interface with Syncfusion components
|
- Modern React-based web interface with Syncfusion components
|
||||||
- Real-time client monitoring and group management
|
- Real-time client monitoring and group management
|
||||||
- Event scheduling with academic period support
|
- Event scheduling with academic period support
|
||||||
@@ -47,13 +47,19 @@ A comprehensive multi-service digital signage solution for educational instituti
|
|||||||
- Holiday calendar integration
|
- Holiday calendar integration
|
||||||
- Visual indicators: TentTree icon next to the main event icon marks events that skip holidays (icon color: black)
|
- Visual indicators: TentTree icon next to the main event icon marks events that skip holidays (icon color: black)
|
||||||
|
|
||||||
|
- **Event Deletion**: All event types (single, single-in-series, entire series) are handled with custom dialogs. The frontend intercepts Syncfusion's built-in RecurrenceAlert and DeleteAlert popups to provide a unified, user-friendly deletion flow:
|
||||||
|
- Single (non-recurring) event: deleted directly after confirmation.
|
||||||
|
- Single occurrence of a recurring series: user can delete just that instance.
|
||||||
|
- Entire recurring series: user can delete all occurrences after a final custom confirmation dialog.
|
||||||
|
- Detached occurrences (edited/broken out): treated as single events.
|
||||||
|
|
||||||
### 🎯 **Event System**
|
### 🎯 **Event System**
|
||||||
- **Presentations**: PowerPoint/LibreOffice → PDF conversion via Gotenberg
|
- **Presentations**: PowerPoint/LibreOffice → PDF conversion via Gotenberg
|
||||||
- **Websites**: URL-based content display
|
- **Websites**: URL-based content display
|
||||||
- **Videos**: Media file streaming
|
- **Videos**: Media file streaming
|
||||||
- **Messages**: Text announcements
|
- **Messages**: Text announcements
|
||||||
- **WebUntis**: Educational schedule integration
|
- **WebUntis**: Educational schedule integration
|
||||||
- **Recurrence & Holidays**: Recurring events can be configured to skip holidays. The backend generates EXDATEs (RecurrenceException) for holiday occurrences so the calendar never shows those instances. The "Termine an Ferientagen erlauben" toggle does not affect these events.
|
- **Recurrence & Holidays**: Recurring events can be configured to skip holidays. The backend generates EXDATEs (RecurrenceException) for holiday occurrences using RFC 5545 timestamps (yyyyMMddTHHmmssZ), so the calendar never shows those instances. The "Termine an Ferientagen erlauben" toggle does not affect these events.
|
||||||
- **Single Occurrence Editing**: Users can edit individual occurrences of recurring events without affecting the master series. The system provides a confirmation dialog to choose between editing a single occurrence or the entire series.
|
- **Single Occurrence Editing**: Users can edit individual occurrences of recurring events without affecting the master series. The system provides a confirmation dialog to choose between editing a single occurrence or the entire series.
|
||||||
|
|
||||||
### 🏫 **Academic Period Management**
|
### 🏫 **Academic Period Management**
|
||||||
@@ -278,10 +284,11 @@ mosquitto_sub -h localhost -t "infoscreen/+/heartbeat" -v
|
|||||||
## 🎨 Frontend Features
|
## 🎨 Frontend Features
|
||||||
|
|
||||||
### Recurrence & holidays
|
### Recurrence & holidays
|
||||||
- The frontend manually expands recurring events due to Syncfusion EXDATE handling limitations.
|
- Recurrence is handled natively by Syncfusion. The API returns master events with `RecurrenceRule` and `RecurrenceException` (EXDATE) in RFC 5545 format (yyyyMMddTHHmmssZ, UTC) so the Scheduler excludes holiday instances reliably.
|
||||||
- The API supplies `RecurrenceException` (EXDATE) with exact occurrence start times (UTC) so holiday instances are excluded.
|
- Events with "skip holidays" display a TentTree icon next to the main event icon (icon color: black). The Scheduler’s native lower-right recurrence badge indicates series membership.
|
||||||
- Events with "skip holidays" display a TentTree icon next to the main event icon.
|
- Single occurrence editing: Users can edit either a single occurrence or the entire series. The UI persists changes using `onActionCompleted (requestType='eventChanged')`:
|
||||||
- Single occurrence editing: Users can detach individual occurrences via confirmation dialog, creating standalone events while preserving the master series.
|
- Single occurrence → `POST /api/events/<id>/occurrences/<date>/detach` (creates standalone event and adds EXDATE to master)
|
||||||
|
- Series/single event → `PUT /api/events/<id>`
|
||||||
|
|
||||||
### Syncfusion Components Used (Material 3)
|
### Syncfusion Components Used (Material 3)
|
||||||
- **Schedule**: Event calendar with drag-drop support
|
- **Schedule**: Event calendar with drag-drop support
|
||||||
|
|||||||
575
dashboard/package-lock.json
generated
575
dashboard/package-lock.json
generated
@@ -43,9 +43,6 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.25.0",
|
"@eslint/js": "^9.25.0",
|
||||||
"@tailwindcss/aspect-ratio": "^0.4.2",
|
|
||||||
"@tailwindcss/forms": "^0.5.10",
|
|
||||||
"@tailwindcss/typography": "^0.5.16",
|
|
||||||
"@types/react": "^19.1.8",
|
"@types/react": "^19.1.8",
|
||||||
"@types/react-dom": "^19.1.6",
|
"@types/react-dom": "^19.1.6",
|
||||||
"@types/react-router-dom": "^5.3.3",
|
"@types/react-router-dom": "^5.3.3",
|
||||||
@@ -64,26 +61,11 @@
|
|||||||
"prettier": "^3.5.3",
|
"prettier": "^3.5.3",
|
||||||
"stylelint": "^16.21.0",
|
"stylelint": "^16.21.0",
|
||||||
"stylelint-config-standard": "^38.0.0",
|
"stylelint-config-standard": "^38.0.0",
|
||||||
"stylelint-config-tailwindcss": "^1.0.0",
|
|
||||||
"tailwindcss": "^3.4.17",
|
|
||||||
"typescript": "~5.8.3",
|
"typescript": "~5.8.3",
|
||||||
"typescript-eslint": "^8.30.1",
|
"typescript-eslint": "^8.30.1",
|
||||||
"vite": "^6.3.5"
|
"vite": "^6.3.5"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@alloc/quick-lru": {
|
|
||||||
"version": "5.2.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz",
|
|
||||||
"integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@babel/code-frame": {
|
"node_modules/@babel/code-frame": {
|
||||||
"version": "7.27.1",
|
"version": "7.27.1",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz",
|
||||||
@@ -1986,45 +1968,6 @@
|
|||||||
"@syncfusion/ej2-popups": "~30.2.4"
|
"@syncfusion/ej2-popups": "~30.2.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@tailwindcss/aspect-ratio": {
|
|
||||||
"version": "0.4.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/@tailwindcss/aspect-ratio/-/aspect-ratio-0.4.2.tgz",
|
|
||||||
"integrity": "sha512-8QPrypskfBa7QIMuKHg2TA7BqES6vhBrDLOv8Unb6FcFyd3TjKbc6lcmb9UPQHxfl24sXoJ41ux/H7qQQvfaSQ==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"peerDependencies": {
|
|
||||||
"tailwindcss": ">=2.0.0 || >=3.0.0 || >=3.0.0-alpha.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@tailwindcss/forms": {
|
|
||||||
"version": "0.5.10",
|
|
||||||
"resolved": "https://registry.npmjs.org/@tailwindcss/forms/-/forms-0.5.10.tgz",
|
|
||||||
"integrity": "sha512-utI1ONF6uf/pPNO68kmN1b8rEwNXv3czukalo8VtJH8ksIkZXr3Q3VYudZLkCsDd4Wku120uF02hYK25XGPorw==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"mini-svg-data-uri": "^1.2.3"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"tailwindcss": ">=3.0.0 || >= 3.0.0-alpha.1 || >= 4.0.0-alpha.20 || >= 4.0.0-beta.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@tailwindcss/typography": {
|
|
||||||
"version": "0.5.16",
|
|
||||||
"resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.16.tgz",
|
|
||||||
"integrity": "sha512-0wDLwCVF5V3x3b1SGXPCDcdsbDHMBe+lkFzBRaHeLvNi+nrrnZ1lA18u+OTWO8iSWU2GxUOCvlXtDuqftc1oiA==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"lodash.castarray": "^4.4.0",
|
|
||||||
"lodash.isplainobject": "^4.0.6",
|
|
||||||
"lodash.merge": "^4.6.2",
|
|
||||||
"postcss-selector-parser": "6.0.10"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"tailwindcss": ">=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@types/babel__core": {
|
"node_modules/@types/babel__core": {
|
||||||
"version": "7.20.5",
|
"version": "7.20.5",
|
||||||
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
|
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
|
||||||
@@ -2463,34 +2406,6 @@
|
|||||||
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/any-promise": {
|
|
||||||
"version": "1.3.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz",
|
|
||||||
"integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/anymatch": {
|
|
||||||
"version": "3.1.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
|
|
||||||
"integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "ISC",
|
|
||||||
"dependencies": {
|
|
||||||
"normalize-path": "^3.0.0",
|
|
||||||
"picomatch": "^2.0.4"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 8"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/arg": {
|
|
||||||
"version": "5.0.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz",
|
|
||||||
"integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/argparse": {
|
"node_modules/argparse": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
|
||||||
@@ -2743,19 +2658,6 @@
|
|||||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
|
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/binary-extensions": {
|
|
||||||
"version": "2.3.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
|
|
||||||
"integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=8"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/brace-expansion": {
|
"node_modules/brace-expansion": {
|
||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
|
||||||
@@ -2900,16 +2802,6 @@
|
|||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/camelcase-css": {
|
|
||||||
"version": "2.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz",
|
|
||||||
"integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/caniuse-lite": {
|
"node_modules/caniuse-lite": {
|
||||||
"version": "1.0.30001741",
|
"version": "1.0.30001741",
|
||||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001741.tgz",
|
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001741.tgz",
|
||||||
@@ -2948,44 +2840,6 @@
|
|||||||
"url": "https://github.com/chalk/chalk?sponsor=1"
|
"url": "https://github.com/chalk/chalk?sponsor=1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/chokidar": {
|
|
||||||
"version": "3.6.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
|
|
||||||
"integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"anymatch": "~3.1.2",
|
|
||||||
"braces": "~3.0.2",
|
|
||||||
"glob-parent": "~5.1.2",
|
|
||||||
"is-binary-path": "~2.1.0",
|
|
||||||
"is-glob": "~4.0.1",
|
|
||||||
"normalize-path": "~3.0.0",
|
|
||||||
"readdirp": "~3.6.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 8.10.0"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://paulmillr.com/funding/"
|
|
||||||
},
|
|
||||||
"optionalDependencies": {
|
|
||||||
"fsevents": "~2.3.2"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/chokidar/node_modules/glob-parent": {
|
|
||||||
"version": "5.1.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
|
|
||||||
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "ISC",
|
|
||||||
"dependencies": {
|
|
||||||
"is-glob": "^4.0.1"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/cldr-data": {
|
"node_modules/cldr-data": {
|
||||||
"version": "36.0.4",
|
"version": "36.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/cldr-data/-/cldr-data-36.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/cldr-data/-/cldr-data-36.0.4.tgz",
|
||||||
@@ -3054,16 +2908,6 @@
|
|||||||
"node": ">= 0.8"
|
"node": ">= 0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/commander": {
|
|
||||||
"version": "4.1.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz",
|
|
||||||
"integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/concat-map": {
|
"node_modules/concat-map": {
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||||
@@ -3296,13 +3140,6 @@
|
|||||||
"node": ">=0.4.0"
|
"node": ">=0.4.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/didyoumean": {
|
|
||||||
"version": "1.2.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
|
|
||||||
"integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "Apache-2.0"
|
|
||||||
},
|
|
||||||
"node_modules/dir-glob": {
|
"node_modules/dir-glob": {
|
||||||
"version": "3.0.1",
|
"version": "3.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
|
||||||
@@ -3316,13 +3153,6 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/dlv": {
|
|
||||||
"version": "1.1.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz",
|
|
||||||
"integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/doctrine": {
|
"node_modules/doctrine": {
|
||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz",
|
||||||
@@ -4743,19 +4573,6 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/is-binary-path": {
|
|
||||||
"version": "2.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
|
|
||||||
"integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"binary-extensions": "^2.0.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=8"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/is-boolean-object": {
|
"node_modules/is-boolean-object": {
|
||||||
"version": "1.2.2",
|
"version": "1.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz",
|
||||||
@@ -5167,6 +4984,8 @@
|
|||||||
"integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==",
|
"integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"peer": true,
|
||||||
"bin": {
|
"bin": {
|
||||||
"jiti": "bin/jiti.js"
|
"jiti": "bin/jiti.js"
|
||||||
}
|
}
|
||||||
@@ -5302,19 +5121,6 @@
|
|||||||
"node": ">= 0.8.0"
|
"node": ">= 0.8.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/lilconfig": {
|
|
||||||
"version": "3.1.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz",
|
|
||||||
"integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=14"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/antonk52"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/lines-and-columns": {
|
"node_modules/lines-and-columns": {
|
||||||
"version": "1.2.4",
|
"version": "1.2.4",
|
||||||
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
|
||||||
@@ -5338,20 +5144,6 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/lodash.castarray": {
|
|
||||||
"version": "4.4.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/lodash.castarray/-/lodash.castarray-4.4.0.tgz",
|
|
||||||
"integrity": "sha512-aVx8ztPv7/2ULbArGJ2Y42bG1mEQ5mGjpdvrbJcJFU3TbYybe+QlLS4pst9zV52ymy2in1KpFPiZnAOATxD4+Q==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/lodash.isplainobject": {
|
|
||||||
"version": "4.0.6",
|
|
||||||
"resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
|
|
||||||
"integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/lodash.merge": {
|
"node_modules/lodash.merge": {
|
||||||
"version": "4.6.2",
|
"version": "4.6.2",
|
||||||
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
|
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
|
||||||
@@ -5483,16 +5275,6 @@
|
|||||||
"node": ">= 0.6"
|
"node": ">= 0.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/mini-svg-data-uri": {
|
|
||||||
"version": "1.4.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/mini-svg-data-uri/-/mini-svg-data-uri-1.4.4.tgz",
|
|
||||||
"integrity": "sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"bin": {
|
|
||||||
"mini-svg-data-uri": "cli.js"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/minimatch": {
|
"node_modules/minimatch": {
|
||||||
"version": "9.0.5",
|
"version": "9.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
|
||||||
@@ -5536,18 +5318,6 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/mz": {
|
|
||||||
"version": "2.7.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz",
|
|
||||||
"integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"any-promise": "^1.0.0",
|
|
||||||
"object-assign": "^4.0.1",
|
|
||||||
"thenify-all": "^1.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/nanoid": {
|
"node_modules/nanoid": {
|
||||||
"version": "3.3.11",
|
"version": "3.3.11",
|
||||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
|
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
|
||||||
@@ -5623,16 +5393,6 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/object-hash": {
|
|
||||||
"version": "3.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz",
|
|
||||||
"integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/object-inspect": {
|
"node_modules/object-inspect": {
|
||||||
"version": "1.13.4",
|
"version": "1.13.4",
|
||||||
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
|
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
|
||||||
@@ -5915,26 +5675,6 @@
|
|||||||
"url": "https://github.com/sponsors/jonschlinkert"
|
"url": "https://github.com/sponsors/jonschlinkert"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/pify": {
|
|
||||||
"version": "2.3.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
|
|
||||||
"integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=0.10.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/pirates": {
|
|
||||||
"version": "4.0.7",
|
|
||||||
"resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz",
|
|
||||||
"integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/possible-typed-array-names": {
|
"node_modules/possible-typed-array-names": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz",
|
||||||
@@ -5974,141 +5714,6 @@
|
|||||||
"node": "^10 || ^12 || >=14"
|
"node": "^10 || ^12 || >=14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/postcss-import": {
|
|
||||||
"version": "15.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz",
|
|
||||||
"integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"postcss-value-parser": "^4.0.0",
|
|
||||||
"read-cache": "^1.0.0",
|
|
||||||
"resolve": "^1.1.7"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=14.0.0"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"postcss": "^8.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/postcss-import/node_modules/resolve": {
|
|
||||||
"version": "1.22.10",
|
|
||||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz",
|
|
||||||
"integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"is-core-module": "^2.16.0",
|
|
||||||
"path-parse": "^1.0.7",
|
|
||||||
"supports-preserve-symlinks-flag": "^1.0.0"
|
|
||||||
},
|
|
||||||
"bin": {
|
|
||||||
"resolve": "bin/resolve"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 0.4"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/ljharb"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/postcss-js": {
|
|
||||||
"version": "4.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz",
|
|
||||||
"integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"camelcase-css": "^2.0.1"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": "^12 || ^14 || >= 16"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"type": "opencollective",
|
|
||||||
"url": "https://opencollective.com/postcss/"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"postcss": "^8.4.21"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/postcss-load-config": {
|
|
||||||
"version": "4.0.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz",
|
|
||||||
"integrity": "sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==",
|
|
||||||
"dev": true,
|
|
||||||
"funding": [
|
|
||||||
{
|
|
||||||
"type": "opencollective",
|
|
||||||
"url": "https://opencollective.com/postcss/"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "github",
|
|
||||||
"url": "https://github.com/sponsors/ai"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"lilconfig": "^3.0.0",
|
|
||||||
"yaml": "^2.3.4"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 14"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"postcss": ">=8.0.9",
|
|
||||||
"ts-node": ">=9.0.0"
|
|
||||||
},
|
|
||||||
"peerDependenciesMeta": {
|
|
||||||
"postcss": {
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"ts-node": {
|
|
||||||
"optional": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/postcss-nested": {
|
|
||||||
"version": "6.2.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz",
|
|
||||||
"integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==",
|
|
||||||
"dev": true,
|
|
||||||
"funding": [
|
|
||||||
{
|
|
||||||
"type": "opencollective",
|
|
||||||
"url": "https://opencollective.com/postcss/"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "github",
|
|
||||||
"url": "https://github.com/sponsors/ai"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"postcss-selector-parser": "^6.1.1"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=12.0"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"postcss": "^8.2.14"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/postcss-nested/node_modules/postcss-selector-parser": {
|
|
||||||
"version": "6.1.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz",
|
|
||||||
"integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"cssesc": "^3.0.0",
|
|
||||||
"util-deprecate": "^1.0.2"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=4"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/postcss-resolve-nested-selector": {
|
"node_modules/postcss-resolve-nested-selector": {
|
||||||
"version": "0.1.6",
|
"version": "0.1.6",
|
||||||
"resolved": "https://registry.npmjs.org/postcss-resolve-nested-selector/-/postcss-resolve-nested-selector-0.1.6.tgz",
|
"resolved": "https://registry.npmjs.org/postcss-resolve-nested-selector/-/postcss-resolve-nested-selector-0.1.6.tgz",
|
||||||
@@ -6143,20 +5748,6 @@
|
|||||||
"postcss": "^8.4.31"
|
"postcss": "^8.4.31"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/postcss-selector-parser": {
|
|
||||||
"version": "6.0.10",
|
|
||||||
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz",
|
|
||||||
"integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"cssesc": "^3.0.0",
|
|
||||||
"util-deprecate": "^1.0.2"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=4"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/postcss-value-parser": {
|
"node_modules/postcss-value-parser": {
|
||||||
"version": "4.2.0",
|
"version": "4.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
|
||||||
@@ -6339,29 +5930,6 @@
|
|||||||
"react-dom": ">=18"
|
"react-dom": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/read-cache": {
|
|
||||||
"version": "1.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
|
|
||||||
"integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"pify": "^2.3.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/readdirp": {
|
|
||||||
"version": "3.6.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
|
|
||||||
"integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"picomatch": "^2.2.1"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=8.10.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/reflect.getprototypeof": {
|
"node_modules/reflect.getprototypeof": {
|
||||||
"version": "1.0.10",
|
"version": "1.0.10",
|
||||||
"resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz",
|
"resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz",
|
||||||
@@ -7129,17 +6697,6 @@
|
|||||||
"stylelint": "^16.18.0"
|
"stylelint": "^16.18.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/stylelint-config-tailwindcss": {
|
|
||||||
"version": "1.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/stylelint-config-tailwindcss/-/stylelint-config-tailwindcss-1.0.0.tgz",
|
|
||||||
"integrity": "sha512-e6WUBJeLdOZ0sy8FZ1jk5Zy9iNGqqJbrMwnnV0Hpaw/yin6QO3gVv/zvyqSty8Yg6nEB5gqcyJbN387TPhEa7Q==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"peerDependencies": {
|
|
||||||
"stylelint": ">=13.13.1",
|
|
||||||
"tailwindcss": ">=2.2.16"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/stylelint/node_modules/@csstools/selector-specificity": {
|
"node_modules/stylelint/node_modules/@csstools/selector-specificity": {
|
||||||
"version": "5.0.0",
|
"version": "5.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-5.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-5.0.0.tgz",
|
||||||
@@ -7261,29 +6818,6 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/sucrase": {
|
|
||||||
"version": "3.35.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz",
|
|
||||||
"integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"@jridgewell/gen-mapping": "^0.3.2",
|
|
||||||
"commander": "^4.0.0",
|
|
||||||
"glob": "^10.3.10",
|
|
||||||
"lines-and-columns": "^1.1.6",
|
|
||||||
"mz": "^2.7.0",
|
|
||||||
"pirates": "^4.0.1",
|
|
||||||
"ts-interface-checker": "^0.1.9"
|
|
||||||
},
|
|
||||||
"bin": {
|
|
||||||
"sucrase": "bin/sucrase",
|
|
||||||
"sucrase-node": "bin/sucrase-node"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=16 || 14 >=14.17"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/supports-color": {
|
"node_modules/supports-color": {
|
||||||
"version": "7.2.0",
|
"version": "7.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
|
||||||
@@ -7435,102 +6969,6 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/tailwindcss": {
|
|
||||||
"version": "3.4.17",
|
|
||||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.17.tgz",
|
|
||||||
"integrity": "sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"@alloc/quick-lru": "^5.2.0",
|
|
||||||
"arg": "^5.0.2",
|
|
||||||
"chokidar": "^3.6.0",
|
|
||||||
"didyoumean": "^1.2.2",
|
|
||||||
"dlv": "^1.1.3",
|
|
||||||
"fast-glob": "^3.3.2",
|
|
||||||
"glob-parent": "^6.0.2",
|
|
||||||
"is-glob": "^4.0.3",
|
|
||||||
"jiti": "^1.21.6",
|
|
||||||
"lilconfig": "^3.1.3",
|
|
||||||
"micromatch": "^4.0.8",
|
|
||||||
"normalize-path": "^3.0.0",
|
|
||||||
"object-hash": "^3.0.0",
|
|
||||||
"picocolors": "^1.1.1",
|
|
||||||
"postcss": "^8.4.47",
|
|
||||||
"postcss-import": "^15.1.0",
|
|
||||||
"postcss-js": "^4.0.1",
|
|
||||||
"postcss-load-config": "^4.0.2",
|
|
||||||
"postcss-nested": "^6.2.0",
|
|
||||||
"postcss-selector-parser": "^6.1.2",
|
|
||||||
"resolve": "^1.22.8",
|
|
||||||
"sucrase": "^3.35.0"
|
|
||||||
},
|
|
||||||
"bin": {
|
|
||||||
"tailwind": "lib/cli.js",
|
|
||||||
"tailwindcss": "lib/cli.js"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=14.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/tailwindcss/node_modules/postcss-selector-parser": {
|
|
||||||
"version": "6.1.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz",
|
|
||||||
"integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"cssesc": "^3.0.0",
|
|
||||||
"util-deprecate": "^1.0.2"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=4"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/tailwindcss/node_modules/resolve": {
|
|
||||||
"version": "1.22.10",
|
|
||||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz",
|
|
||||||
"integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"is-core-module": "^2.16.0",
|
|
||||||
"path-parse": "^1.0.7",
|
|
||||||
"supports-preserve-symlinks-flag": "^1.0.0"
|
|
||||||
},
|
|
||||||
"bin": {
|
|
||||||
"resolve": "bin/resolve"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 0.4"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/ljharb"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/thenify": {
|
|
||||||
"version": "3.3.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz",
|
|
||||||
"integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"any-promise": "^1.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/thenify-all": {
|
|
||||||
"version": "1.6.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz",
|
|
||||||
"integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"thenify": ">= 3.1.0 < 4"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=0.8"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/tinyglobby": {
|
"node_modules/tinyglobby": {
|
||||||
"version": "0.2.15",
|
"version": "0.2.15",
|
||||||
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
|
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
|
||||||
@@ -7605,13 +7043,6 @@
|
|||||||
"typescript": ">=4.8.4"
|
"typescript": ">=4.8.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/ts-interface-checker": {
|
|
||||||
"version": "0.1.13",
|
|
||||||
"resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz",
|
|
||||||
"integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "Apache-2.0"
|
|
||||||
},
|
|
||||||
"node_modules/type-check": {
|
"node_modules/type-check": {
|
||||||
"version": "0.4.0",
|
"version": "0.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
|
||||||
@@ -8143,6 +7574,8 @@
|
|||||||
"integrity": "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==",
|
"integrity": "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
|
"optional": true,
|
||||||
|
"peer": true,
|
||||||
"bin": {
|
"bin": {
|
||||||
"yaml": "bin.mjs"
|
"yaml": "bin.mjs"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"appName": "Infoscreen-Management",
|
"appName": "Infoscreen-Management",
|
||||||
"version": "2025.1.0-alpha.8",
|
"version": "2025.1.0-alpha.9",
|
||||||
"copyright": "© 2025 Third-Age-Applications",
|
"copyright": "© 2025 Third-Age-Applications",
|
||||||
"supportContact": "support@third-age-applications.com",
|
"supportContact": "support@third-age-applications.com",
|
||||||
"description": "Eine zentrale Verwaltungsoberfläche für digitale Informationsbildschirme.",
|
"description": "Eine zentrale Verwaltungsoberfläche für digitale Informationsbildschirme.",
|
||||||
@@ -30,6 +30,16 @@
|
|||||||
"commitId": "8d1df7199cb7"
|
"commitId": "8d1df7199cb7"
|
||||||
},
|
},
|
||||||
"changelog": [
|
"changelog": [
|
||||||
|
{
|
||||||
|
"version": "2025.1.0-alpha.9",
|
||||||
|
"date": "2025-10-14",
|
||||||
|
"changes": [
|
||||||
|
"✨ UI: Einheitlicher Lösch-Workflow für Termine – alle Typen (Einzeltermin, Einzelinstanz, ganze Serie) werden mit eigenen, benutzerfreundlichen Dialogen behandelt.",
|
||||||
|
"🔧 Frontend: Syncfusion-RecurrenceAlert und DeleteAlert werden abgefangen und durch eigene Dialoge ersetzt (inkl. finale Bestätigung für Serienlöschung).",
|
||||||
|
"✅ Bugfix: Keine doppelten oder verwirrenden Bestätigungsdialoge mehr beim Löschen von Serienterminen.",
|
||||||
|
"📖 Doku: README und Copilot-Instructions um Lösch-Workflow und Dialoghandling erweitert."
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"version": "2025.1.0-alpha.8",
|
"version": "2025.1.0-alpha.8",
|
||||||
"date": "2025-10-11",
|
"date": "2025-10-11",
|
||||||
|
|||||||
@@ -32,8 +32,12 @@ export async function fetchEventById(eventId: string) {
|
|||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function deleteEvent(eventId: string) {
|
export async function deleteEvent(eventId: string, force: boolean = false) {
|
||||||
const res = await fetch(`/api/events/${encodeURIComponent(eventId)}`, {
|
const url = force
|
||||||
|
? `/api/events/${encodeURIComponent(eventId)}?force=1`
|
||||||
|
: `/api/events/${encodeURIComponent(eventId)}`;
|
||||||
|
|
||||||
|
const res = await fetch(url, {
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
});
|
});
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
|
|||||||
@@ -75,22 +75,6 @@ type Event = {
|
|||||||
RecurrenceException?: string;
|
RecurrenceException?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
type RawEvent = {
|
|
||||||
Id: string;
|
|
||||||
Subject: string;
|
|
||||||
StartTime: string;
|
|
||||||
EndTime: string;
|
|
||||||
IsAllDay: boolean;
|
|
||||||
MediaId?: string | number;
|
|
||||||
Icon?: string; // <--- Icon ergänzen!
|
|
||||||
Type?: string;
|
|
||||||
OccurrenceOfId?: string;
|
|
||||||
RecurrenceRule?: string | null;
|
|
||||||
RecurrenceEnd?: string | null;
|
|
||||||
SkipHolidays?: boolean;
|
|
||||||
RecurrenceException?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
// CLDR-Daten laden (direkt die JSON-Objekte übergeben)
|
// CLDR-Daten laden (direkt die JSON-Objekte übergeben)
|
||||||
loadCldr(
|
loadCldr(
|
||||||
caGregorian as object,
|
caGregorian as object,
|
||||||
@@ -208,6 +192,7 @@ const Appointments: React.FC = () => {
|
|||||||
const [periods, setPeriods] = React.useState<{ id: number; label: string }[]>([]);
|
const [periods, setPeriods] = React.useState<{ id: number; label: string }[]>([]);
|
||||||
const [activePeriodId, setActivePeriodId] = React.useState<number | null>(null);
|
const [activePeriodId, setActivePeriodId] = React.useState<number | null>(null);
|
||||||
|
|
||||||
|
|
||||||
// Confirmation dialog state
|
// Confirmation dialog state
|
||||||
const [confirmDialogOpen, setConfirmDialogOpen] = React.useState(false);
|
const [confirmDialogOpen, setConfirmDialogOpen] = React.useState(false);
|
||||||
const [confirmDialogData, setConfirmDialogData] = React.useState<{
|
const [confirmDialogData, setConfirmDialogData] = React.useState<{
|
||||||
@@ -217,6 +202,44 @@ const Appointments: React.FC = () => {
|
|||||||
onCancel: () => void;
|
onCancel: () => void;
|
||||||
} | null>(null);
|
} | null>(null);
|
||||||
|
|
||||||
|
// Recurring deletion dialog state
|
||||||
|
const [recurringDeleteDialogOpen, setRecurringDeleteDialogOpen] = React.useState(false);
|
||||||
|
const [recurringDeleteData, setRecurringDeleteData] = React.useState<{
|
||||||
|
event: Event;
|
||||||
|
onChoice: (choice: 'series' | 'occurrence' | 'cancel') => void;
|
||||||
|
} | null>(null);
|
||||||
|
|
||||||
|
// Series deletion final confirmation dialog (after choosing 'series')
|
||||||
|
const [seriesConfirmDialogOpen, setSeriesConfirmDialogOpen] = React.useState(false);
|
||||||
|
const [seriesConfirmData, setSeriesConfirmData] = React.useState<{
|
||||||
|
event: Event;
|
||||||
|
onConfirm: () => void;
|
||||||
|
onCancel: () => void;
|
||||||
|
} | null>(null);
|
||||||
|
|
||||||
|
const showSeriesConfirmDialog = (event: Event): Promise<boolean> => {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
console.log('[Delete] showSeriesConfirmDialog invoked for event', event.Id);
|
||||||
|
// Defer open to next tick to avoid race with closing previous dialog
|
||||||
|
setSeriesConfirmData({
|
||||||
|
event,
|
||||||
|
onConfirm: () => {
|
||||||
|
console.log('[Delete] Series confirm dialog: confirmed');
|
||||||
|
setSeriesConfirmDialogOpen(false);
|
||||||
|
resolve(true);
|
||||||
|
},
|
||||||
|
onCancel: () => {
|
||||||
|
console.log('[Delete] Series confirm dialog: cancelled');
|
||||||
|
setSeriesConfirmDialogOpen(false);
|
||||||
|
resolve(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
setTimeout(() => {
|
||||||
|
setSeriesConfirmDialogOpen(true);
|
||||||
|
}, 0);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
// Helper function to show confirmation dialog
|
// Helper function to show confirmation dialog
|
||||||
const showConfirmDialog = (title: string, message: string): Promise<boolean> => {
|
const showConfirmDialog = (title: string, message: string): Promise<boolean> => {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
@@ -236,6 +259,20 @@ const Appointments: React.FC = () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Helper function to show recurring event deletion dialog
|
||||||
|
const showRecurringDeleteDialog = (event: Event): Promise<'series' | 'occurrence' | 'cancel'> => {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
setRecurringDeleteData({
|
||||||
|
event,
|
||||||
|
onChoice: (choice: 'series' | 'occurrence' | 'cancel') => {
|
||||||
|
setRecurringDeleteDialogOpen(false);
|
||||||
|
resolve(choice);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
setRecurringDeleteDialogOpen(true);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
// Gruppen laden
|
// Gruppen laden
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchGroups()
|
fetchGroups()
|
||||||
@@ -563,6 +600,22 @@ const Appointments: React.FC = () => {
|
|||||||
updateHolidaysInView();
|
updateHolidaysInView();
|
||||||
}, [holidays, updateHolidaysInView]);
|
}, [holidays, updateHolidaysInView]);
|
||||||
|
|
||||||
|
// Inject global z-index fixes for dialogs (only once)
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (typeof document !== 'undefined' && !document.getElementById('series-dialog-zfix')) {
|
||||||
|
const style = document.createElement('style');
|
||||||
|
style.id = 'series-dialog-zfix';
|
||||||
|
style.textContent = `\n .final-series-dialog.e-dialog { z-index: 25000 !important; }\n .final-series-dialog + .e-dlg-overlay { z-index: 24990 !important; }\n .recurring-delete-dialog.e-dialog { z-index: 24000 !important; }\n .recurring-delete-dialog + .e-dlg-overlay { z-index: 23990 !important; }\n `;
|
||||||
|
document.head.appendChild(style);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (seriesConfirmDialogOpen) {
|
||||||
|
console.log('[Delete] Series confirm dialog now visible');
|
||||||
|
}
|
||||||
|
}, [seriesConfirmDialogOpen]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<h1 style={{ fontSize: '1.5rem', fontWeight: 700, marginBottom: 16 }}>Terminmanagement</h1>
|
<h1 style={{ fontSize: '1.5rem', fontWeight: 700, marginBottom: 16 }}>Terminmanagement</h1>
|
||||||
@@ -733,17 +786,25 @@ const Appointments: React.FC = () => {
|
|||||||
setModalOpen(false);
|
setModalOpen(false);
|
||||||
setEditMode(false); // Editiermodus zurücksetzen
|
setEditMode(false); // Editiermodus zurücksetzen
|
||||||
}}
|
}}
|
||||||
onSave={async () => {
|
onSave={async (eventData) => {
|
||||||
|
console.log('Modal saved event data:', eventData);
|
||||||
|
|
||||||
|
// The CustomEventModal already handled the API calls internally
|
||||||
|
// For now, just refresh the data (the recurring event logic is handled in the modal itself)
|
||||||
|
console.log('Modal operation completed, refreshing data');
|
||||||
|
|
||||||
setModalOpen(false);
|
setModalOpen(false);
|
||||||
setEditMode(false);
|
setEditMode(false);
|
||||||
|
|
||||||
// Force immediate data refresh
|
// Refresh the data and scheduler
|
||||||
await fetchAndSetEvents();
|
await fetchAndSetEvents();
|
||||||
|
|
||||||
// Defer refresh to avoid interfering with current React commit
|
// Defer refresh to avoid interfering with current React commit
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
scheduleRef.current?.refreshEvents?.();
|
scheduleRef.current?.refreshEvents?.();
|
||||||
}, 0);
|
}, 0);
|
||||||
|
|
||||||
|
console.log('Modal save cycle completed - data refreshed');
|
||||||
}}
|
}}
|
||||||
initialData={modalInitialData}
|
initialData={modalInitialData}
|
||||||
groupName={groups.find(g => g.id === selectedGroupId) ?? { id: selectedGroupId, name: '' }}
|
groupName={groups.find(g => g.id === selectedGroupId) ?? { id: selectedGroupId, name: '' }}
|
||||||
@@ -781,6 +842,7 @@ const Appointments: React.FC = () => {
|
|||||||
|
|
||||||
// Persist UI-driven changes (drag/resize/editor fallbacks)
|
// Persist UI-driven changes (drag/resize/editor fallbacks)
|
||||||
if (args && args.requestType === 'eventChanged') {
|
if (args && args.requestType === 'eventChanged') {
|
||||||
|
console.log('actionComplete: Processing eventChanged from direct UI interaction (drag/resize)');
|
||||||
try {
|
try {
|
||||||
type SchedulerEvent = Partial<Event> & {
|
type SchedulerEvent = Partial<Event> & {
|
||||||
Id?: string | number;
|
Id?: string | number;
|
||||||
@@ -810,18 +872,64 @@ const Appointments: React.FC = () => {
|
|||||||
payload.end = e.toISOString();
|
payload.end = e.toISOString();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Single occurrence change from a recurring master (our manual expansion marks OccurrenceOfId)
|
// Check if this is a single occurrence edit by looking at the original master event
|
||||||
if (changed.OccurrenceOfId) {
|
const eventId = String(changed.Id);
|
||||||
if (!changed.StartTime) return; // cannot determine occurrence date
|
|
||||||
|
// Debug logging to understand what Syncfusion sends
|
||||||
|
console.log('actionComplete eventChanged - Debug info:', {
|
||||||
|
eventId,
|
||||||
|
changedRecurrenceRule: changed.RecurrenceRule,
|
||||||
|
changedRecurrenceID: changed.RecurrenceID,
|
||||||
|
changedStartTime: changed.StartTime,
|
||||||
|
changedSubject: changed.Subject,
|
||||||
|
payload,
|
||||||
|
fullChangedObject: JSON.stringify(changed, null, 2)
|
||||||
|
});
|
||||||
|
|
||||||
|
// First, fetch the master event to check if it has a RecurrenceRule
|
||||||
|
let masterEvent = null;
|
||||||
|
let isMasterRecurring = false;
|
||||||
|
try {
|
||||||
|
masterEvent = await fetchEventById(eventId);
|
||||||
|
isMasterRecurring = !!masterEvent.RecurrenceRule;
|
||||||
|
console.log('Master event info:', {
|
||||||
|
masterRecurrenceRule: masterEvent.RecurrenceRule,
|
||||||
|
masterStartTime: masterEvent.StartTime,
|
||||||
|
isMasterRecurring
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Failed to fetch master event:', err);
|
||||||
|
}
|
||||||
|
|
||||||
|
// KEY DETECTION: Syncfusion sets RecurrenceID when editing a single occurrence
|
||||||
|
const hasRecurrenceID = 'RecurrenceID' in changed && !!(changed as Record<string, unknown>).RecurrenceID;
|
||||||
|
|
||||||
|
// When dragging a single occurrence, Syncfusion may not provide RecurrenceID
|
||||||
|
// but it won't provide RecurrenceRule on the changed object
|
||||||
|
const isRecurrenceRuleStripped = isMasterRecurring && !changed.RecurrenceRule;
|
||||||
|
|
||||||
|
console.log('FINAL Edit detection:', {
|
||||||
|
isMasterRecurring,
|
||||||
|
hasRecurrenceID,
|
||||||
|
isRecurrenceRuleStripped,
|
||||||
|
masterHasRule: masterEvent?.RecurrenceRule ? 'YES' : 'NO',
|
||||||
|
changedHasRule: changed.RecurrenceRule ? 'YES' : 'NO',
|
||||||
|
decision: (hasRecurrenceID || isRecurrenceRuleStripped) ? 'DETACH' : 'UPDATE'
|
||||||
|
});
|
||||||
|
|
||||||
|
// SINGLE OCCURRENCE EDIT detection:
|
||||||
|
// 1. RecurrenceID is set (explicit single occurrence marker)
|
||||||
|
// 2. OR master has RecurrenceRule but changed object doesn't (stripped during single edit)
|
||||||
|
if (isMasterRecurring && (hasRecurrenceID || isRecurrenceRuleStripped) && changed.StartTime) {
|
||||||
|
// This is a single occurrence edit - detach it
|
||||||
|
console.log('Detaching single occurrence...');
|
||||||
const occStart = changed.StartTime instanceof Date ? changed.StartTime : new Date(changed.StartTime as string);
|
const occStart = changed.StartTime instanceof Date ? changed.StartTime : new Date(changed.StartTime as string);
|
||||||
const occDate = occStart.toISOString().split('T')[0];
|
const occDate = occStart.toISOString().split('T')[0];
|
||||||
await detachEventOccurrence(Number(changed.OccurrenceOfId), occDate, payload);
|
await detachEventOccurrence(Number(eventId), occDate, payload);
|
||||||
} else if (changed.RecurrenceRule) {
|
} else {
|
||||||
// Change to master series (non-manually expanded recurrences)
|
// This is a series edit or regular single event
|
||||||
await updateEvent(String(changed.Id), payload);
|
console.log('Updating event directly...');
|
||||||
} else if (changed.Id) {
|
await updateEvent(eventId, payload);
|
||||||
// Regular single event
|
|
||||||
await updateEvent(String(changed.Id), payload);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Refresh events and scheduler cache after persisting
|
// Refresh events and scheduler cache after persisting
|
||||||
@@ -860,6 +968,96 @@ const Appointments: React.FC = () => {
|
|||||||
setModalOpen(true);
|
setModalOpen(true);
|
||||||
}}
|
}}
|
||||||
popupOpen={async args => {
|
popupOpen={async args => {
|
||||||
|
// Intercept Syncfusion's recurrence choice dialog (RecurrenceAlert) and replace with custom
|
||||||
|
if (args.type === 'RecurrenceAlert') {
|
||||||
|
// Prevent default Syncfusion dialog
|
||||||
|
args.cancel = true;
|
||||||
|
const event = args.data;
|
||||||
|
console.log('[RecurrenceAlert] Intercepted for event', event?.Id);
|
||||||
|
if (!event) return;
|
||||||
|
|
||||||
|
// Show our custom recurring delete dialog
|
||||||
|
const choice = await showRecurringDeleteDialog(event);
|
||||||
|
let didDelete = false;
|
||||||
|
try {
|
||||||
|
if (choice === 'series') {
|
||||||
|
const confirmed = await showSeriesConfirmDialog(event);
|
||||||
|
if (confirmed) {
|
||||||
|
await deleteEvent(event.Id, true);
|
||||||
|
didDelete = true;
|
||||||
|
}
|
||||||
|
} else if (choice === 'occurrence') {
|
||||||
|
const occurrenceDate = event.StartTime instanceof Date
|
||||||
|
? event.StartTime.toISOString().split('T')[0]
|
||||||
|
: new Date(event.StartTime).toISOString().split('T')[0];
|
||||||
|
// If this is the master being edited for a single occurrence, treat as occurrence delete
|
||||||
|
if (event.OccurrenceOfId) {
|
||||||
|
await deleteEventOccurrence(event.OccurrenceOfId, occurrenceDate);
|
||||||
|
} else {
|
||||||
|
await deleteEventOccurrence(event.Id, occurrenceDate);
|
||||||
|
}
|
||||||
|
didDelete = true;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Fehler bei RecurrenceAlert Löschung:', e);
|
||||||
|
}
|
||||||
|
if (didDelete) {
|
||||||
|
await fetchAndSetEvents();
|
||||||
|
setTimeout(() => scheduleRef.current?.refreshEvents?.(), 0);
|
||||||
|
}
|
||||||
|
return; // handled
|
||||||
|
}
|
||||||
|
if (args.type === 'DeleteAlert') {
|
||||||
|
// Handle delete confirmation directly here to avoid multiple dialogs
|
||||||
|
args.cancel = true;
|
||||||
|
const event = args.data;
|
||||||
|
let didDelete = false;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 1) Single occurrence of a recurring event → delete occurrence only
|
||||||
|
if (event.OccurrenceOfId && event.StartTime) {
|
||||||
|
console.log('[Delete] Deleting single occurrence via OccurrenceOfId path', {
|
||||||
|
eventId: event.Id,
|
||||||
|
masterId: event.OccurrenceOfId,
|
||||||
|
start: event.StartTime
|
||||||
|
});
|
||||||
|
const occurrenceDate = event.StartTime instanceof Date
|
||||||
|
? event.StartTime.toISOString().split('T')[0]
|
||||||
|
: new Date(event.StartTime).toISOString().split('T')[0];
|
||||||
|
await deleteEventOccurrence(event.OccurrenceOfId, occurrenceDate);
|
||||||
|
didDelete = true;
|
||||||
|
}
|
||||||
|
// 2) Recurring master event deletion → show deletion choice dialog
|
||||||
|
else if (event.RecurrenceRule) {
|
||||||
|
// For recurring events the RecurrenceAlert should have been intercepted.
|
||||||
|
console.log('[DeleteAlert] Recurring event delete without RecurrenceAlert (fallback)');
|
||||||
|
const confirmed = await showSeriesConfirmDialog(event);
|
||||||
|
if (confirmed) {
|
||||||
|
await deleteEvent(event.Id, true);
|
||||||
|
didDelete = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 3) Single non-recurring event → delete normally with simple confirmation
|
||||||
|
else {
|
||||||
|
console.log('Deleting single non-recurring event:', event.Id);
|
||||||
|
await deleteEvent(event.Id, false);
|
||||||
|
didDelete = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Refresh events only if a deletion actually occurred
|
||||||
|
if (didDelete) {
|
||||||
|
await fetchAndSetEvents();
|
||||||
|
setTimeout(() => {
|
||||||
|
scheduleRef.current?.refreshEvents?.();
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Fehler beim Löschen:', err);
|
||||||
|
}
|
||||||
|
return; // Exit early for delete operations
|
||||||
|
}
|
||||||
|
|
||||||
if (args.type === 'Editor') {
|
if (args.type === 'Editor') {
|
||||||
args.cancel = true;
|
args.cancel = true;
|
||||||
const event = args.data;
|
const event = args.data;
|
||||||
@@ -943,9 +1141,11 @@ const Appointments: React.FC = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fixed: Ensure OccurrenceOfId is set for recurring events in native recurrence mode
|
||||||
|
|
||||||
const modalData = {
|
const modalData = {
|
||||||
Id: (event.OccurrenceOfId && !isSingleOccurrence) ? event.OccurrenceOfId : event.Id, // Use master ID for series edit, occurrence ID for single edit
|
Id: (event.OccurrenceOfId && !isSingleOccurrence) ? event.OccurrenceOfId : event.Id, // Use master ID for series edit, occurrence ID for single edit
|
||||||
OccurrenceOfId: event.OccurrenceOfId, // Master event ID if this is an occurrence
|
OccurrenceOfId: event.OccurrenceOfId || (event.RecurrenceRule ? event.Id : undefined), // Master event ID - use current ID if it's a recurring master
|
||||||
occurrenceDate: isSingleOccurrence ? event.StartTime : null, // Store occurrence date for single occurrence editing
|
occurrenceDate: isSingleOccurrence ? event.StartTime : null, // Store occurrence date for single occurrence editing
|
||||||
isSingleOccurrence,
|
isSingleOccurrence,
|
||||||
title: eventDataToUse.Subject,
|
title: eventDataToUse.Subject,
|
||||||
@@ -1029,54 +1229,14 @@ const Appointments: React.FC = () => {
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
actionBegin={async (args: ActionEventArgs) => {
|
actionBegin={async (args: ActionEventArgs) => {
|
||||||
|
// Delete operations are now handled in popupOpen to avoid multiple dialogs
|
||||||
if (args.requestType === 'eventRemove') {
|
if (args.requestType === 'eventRemove') {
|
||||||
// args.data ist ein Array von zu löschenden Events
|
// Cancel all delete operations here - they're handled in popupOpen
|
||||||
const toDelete = Array.isArray(args.data) ? args.data : [args.data];
|
|
||||||
for (const ev of toDelete) {
|
|
||||||
try {
|
|
||||||
// 1) Single occurrence of a recurring event → delete occurrence only
|
|
||||||
if (ev.OccurrenceOfId && ev.StartTime) {
|
|
||||||
const occurrenceDate = ev.StartTime instanceof Date
|
|
||||||
? ev.StartTime.toISOString().split('T')[0]
|
|
||||||
: new Date(ev.StartTime).toISOString().split('T')[0];
|
|
||||||
await deleteEventOccurrence(ev.OccurrenceOfId, occurrenceDate);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2) Recurring master being removed unexpectedly → block deletion (safety)
|
|
||||||
// Syncfusion can sometimes raise eventRemove during edits; do NOT delete the series here.
|
|
||||||
if (ev.RecurrenceRule) {
|
|
||||||
console.warn('Blocked deletion of recurring master event via eventRemove.');
|
|
||||||
// If the user truly wants to delete the series, provide an explicit UI path.
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3) Single non-recurring event → delete normally
|
|
||||||
await deleteEvent(ev.Id);
|
|
||||||
} catch (err) {
|
|
||||||
console.error('Fehler beim Löschen:', err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Events nach Löschen neu laden
|
|
||||||
if (selectedGroupId) {
|
|
||||||
fetchEvents(selectedGroupId, showInactive)
|
|
||||||
.then((data: RawEvent[]) => {
|
|
||||||
const mapped: Event[] = data.map((e: RawEvent) => ({
|
|
||||||
Id: e.Id,
|
|
||||||
Subject: e.Subject,
|
|
||||||
StartTime: parseEventDate(e.StartTime),
|
|
||||||
EndTime: parseEventDate(e.EndTime),
|
|
||||||
IsAllDay: e.IsAllDay,
|
|
||||||
MediaId: e.MediaId,
|
|
||||||
SkipHolidays: e.SkipHolidays ?? false,
|
|
||||||
}));
|
|
||||||
setEvents(mapped);
|
|
||||||
})
|
|
||||||
.catch(console.error);
|
|
||||||
}
|
|
||||||
// Syncfusion soll das Event nicht selbst löschen
|
|
||||||
args.cancel = true;
|
args.cancel = true;
|
||||||
} else if (
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
(args.requestType === 'eventCreate' || args.requestType === 'eventChange') &&
|
(args.requestType === 'eventCreate' || args.requestType === 'eventChange') &&
|
||||||
!allowScheduleOnHolidays
|
!allowScheduleOnHolidays
|
||||||
) {
|
) {
|
||||||
@@ -1156,6 +1316,167 @@ const Appointments: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
</DialogComponent>
|
</DialogComponent>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Recurring Event Deletion Dialog */}
|
||||||
|
{recurringDeleteDialogOpen && recurringDeleteData && (
|
||||||
|
<DialogComponent
|
||||||
|
target="#root"
|
||||||
|
visible={recurringDeleteDialogOpen}
|
||||||
|
width="500px"
|
||||||
|
zIndex={18000}
|
||||||
|
cssClass="recurring-delete-dialog"
|
||||||
|
header={() => (
|
||||||
|
<div style={{
|
||||||
|
padding: '12px 20px',
|
||||||
|
background: '#dc3545',
|
||||||
|
color: 'white',
|
||||||
|
fontWeight: 600,
|
||||||
|
borderRadius: '6px 6px 0 0'
|
||||||
|
}}>
|
||||||
|
🗑️ Wiederkehrenden Termin löschen
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
showCloseIcon={true}
|
||||||
|
close={() => recurringDeleteData.onChoice('cancel')}
|
||||||
|
isModal={true}
|
||||||
|
footerTemplate={() => (
|
||||||
|
<div style={{ padding: '12px 20px', display: 'flex', gap: '12px', justifyContent: 'flex-end' }}>
|
||||||
|
<button
|
||||||
|
className="e-btn e-outline"
|
||||||
|
onClick={() => recurringDeleteData.onChoice('cancel')}
|
||||||
|
style={{ minWidth: '100px' }}
|
||||||
|
>
|
||||||
|
Abbrechen
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className="e-btn e-warning"
|
||||||
|
onClick={() => recurringDeleteData.onChoice('occurrence')}
|
||||||
|
style={{ minWidth: '140px' }}
|
||||||
|
>
|
||||||
|
Nur diesen Termin
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className="e-btn e-danger"
|
||||||
|
onClick={() => recurringDeleteData.onChoice('series')}
|
||||||
|
style={{ minWidth: '140px' }}
|
||||||
|
>
|
||||||
|
Gesamte Serie
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div style={{ padding: '24px', fontSize: '14px', lineHeight: 1.5 }}>
|
||||||
|
<div style={{ marginBottom: '16px', fontSize: '16px', fontWeight: 500 }}>
|
||||||
|
Sie möchten einen wiederkehrenden Termin löschen:
|
||||||
|
</div>
|
||||||
|
<div style={{
|
||||||
|
background: '#f8f9fa',
|
||||||
|
border: '1px solid #e9ecef',
|
||||||
|
borderRadius: '6px',
|
||||||
|
padding: '12px',
|
||||||
|
marginBottom: '20px',
|
||||||
|
fontWeight: 500
|
||||||
|
}}>
|
||||||
|
📅 {recurringDeleteData.event.Subject}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{ marginBottom: '16px' }}>
|
||||||
|
<strong>Was möchten Sie löschen?</strong>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{ marginBottom: '12px' }}>
|
||||||
|
<div style={{ display: 'flex', alignItems: 'flex-start', gap: '8px' }}>
|
||||||
|
<span style={{ color: '#fd7e14', fontSize: '16px' }}>📝</span>
|
||||||
|
<div>
|
||||||
|
<strong>Nur diesen Termin:</strong> Löscht nur den ausgewählten Termin. Die anderen Termine der Serie bleiben bestehen.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{ marginBottom: '20px' }}>
|
||||||
|
<div style={{ display: 'flex', alignItems: 'flex-start', gap: '8px' }}>
|
||||||
|
<span style={{ color: '#dc3545', fontSize: '16px' }}>⚠️</span>
|
||||||
|
<div>
|
||||||
|
<strong>Gesamte Serie:</strong> Löscht <u>alle Termine</u> dieser Wiederholungsserie. Diese Aktion kann nicht rückgängig gemacht werden!
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</DialogComponent>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Final Series Deletion Confirmation Dialog */}
|
||||||
|
{seriesConfirmDialogOpen && seriesConfirmData && (
|
||||||
|
<DialogComponent
|
||||||
|
target="#root"
|
||||||
|
visible={seriesConfirmDialogOpen}
|
||||||
|
width="520px"
|
||||||
|
zIndex={19000}
|
||||||
|
cssClass="final-series-dialog"
|
||||||
|
header={() => (
|
||||||
|
<div style={{
|
||||||
|
padding: '12px 20px',
|
||||||
|
background: '#b91c1c',
|
||||||
|
color: 'white',
|
||||||
|
fontWeight: 600,
|
||||||
|
borderRadius: '6px 6px 0 0'
|
||||||
|
}}>
|
||||||
|
⚠️ Serie endgültig löschen
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
showCloseIcon={true}
|
||||||
|
close={() => seriesConfirmData.onCancel()}
|
||||||
|
isModal={true}
|
||||||
|
footerTemplate={() => (
|
||||||
|
<div style={{ padding: '12px 20px', display: 'flex', gap: '12px', justifyContent: 'flex-end' }}>
|
||||||
|
<button
|
||||||
|
className="e-btn e-outline"
|
||||||
|
onClick={seriesConfirmData.onCancel}
|
||||||
|
style={{ minWidth: '110px' }}
|
||||||
|
>
|
||||||
|
Abbrechen
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className="e-btn e-danger"
|
||||||
|
onClick={seriesConfirmData.onConfirm}
|
||||||
|
style={{ minWidth: '180px' }}
|
||||||
|
>
|
||||||
|
Serie löschen
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div style={{ padding: '24px', fontSize: '14px', lineHeight: 1.55 }}>
|
||||||
|
<div style={{ marginBottom: '14px' }}>
|
||||||
|
Sie sind dabei die <strong>gesamte Terminserie</strong> zu löschen:
|
||||||
|
</div>
|
||||||
|
<div style={{
|
||||||
|
background: '#fef2f2',
|
||||||
|
border: '1px solid #fecaca',
|
||||||
|
borderRadius: 6,
|
||||||
|
padding: '10px 14px',
|
||||||
|
marginBottom: 18,
|
||||||
|
fontWeight: 500
|
||||||
|
}}>
|
||||||
|
📅 {seriesConfirmData.event.Subject}
|
||||||
|
</div>
|
||||||
|
<ul style={{ margin: '0 0 18px 18px', padding: 0 }}>
|
||||||
|
<li>Alle zukünftigen und vergangenen Vorkommen werden entfernt.</li>
|
||||||
|
<li>Dieser Vorgang kann nicht rückgängig gemacht werden.</li>
|
||||||
|
<li>Einzelne bereits abgetrennte Einzeltermine bleiben bestehen.</li>
|
||||||
|
</ul>
|
||||||
|
<div style={{
|
||||||
|
background: '#fff7ed',
|
||||||
|
border: '1px solid #ffedd5',
|
||||||
|
borderRadius: 6,
|
||||||
|
padding: '10px 14px',
|
||||||
|
fontSize: 13
|
||||||
|
}}>
|
||||||
|
Wenn Sie nur einen einzelnen Termin entfernen möchten, schließen Sie diesen Dialog und wählen Sie im vorherigen Dialog "Nur diesen Termin".
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</DialogComponent>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user