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:
RobbStarkAustria
2025-10-14 19:10:38 +00:00
parent 5f0972c79c
commit 8676370fe2
6 changed files with 443 additions and 663 deletions

View File

@@ -43,9 +43,6 @@
},
"devDependencies": {
"@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-dom": "^19.1.6",
"@types/react-router-dom": "^5.3.3",
@@ -64,26 +61,11 @@
"prettier": "^3.5.3",
"stylelint": "^16.21.0",
"stylelint-config-standard": "^38.0.0",
"stylelint-config-tailwindcss": "^1.0.0",
"tailwindcss": "^3.4.17",
"typescript": "~5.8.3",
"typescript-eslint": "^8.30.1",
"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": {
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz",
@@ -1986,45 +1968,6 @@
"@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": {
"version": "7.20.5",
"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"
}
},
"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": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
@@ -2743,19 +2658,6 @@
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"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": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
@@ -2900,16 +2802,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": {
"version": "1.0.30001741",
"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"
}
},
"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": {
"version": "36.0.4",
"resolved": "https://registry.npmjs.org/cldr-data/-/cldr-data-36.0.4.tgz",
@@ -3054,16 +2908,6 @@
"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": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
@@ -3296,13 +3140,6 @@
"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": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
@@ -3316,13 +3153,6 @@
"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": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz",
@@ -4743,19 +4573,6 @@
"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": {
"version": "1.2.2",
"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==",
"dev": true,
"license": "MIT",
"optional": true,
"peer": true,
"bin": {
"jiti": "bin/jiti.js"
}
@@ -5302,19 +5121,6 @@
"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": {
"version": "1.2.4",
"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"
}
},
"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": {
"version": "4.6.2",
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
@@ -5483,16 +5275,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": {
"version": "9.0.5",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
@@ -5536,18 +5318,6 @@
"dev": true,
"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": {
"version": "3.3.11",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
@@ -5623,16 +5393,6 @@
"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": {
"version": "1.13.4",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
@@ -5915,26 +5675,6 @@
"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": {
"version": "1.1.0",
"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_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": {
"version": "0.1.6",
"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"
}
},
"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": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
@@ -6339,29 +5930,6 @@
"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": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz",
@@ -7129,17 +6697,6 @@
"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": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-5.0.0.tgz",
@@ -7261,29 +6818,6 @@
"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": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
@@ -7435,102 +6969,6 @@
"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": {
"version": "0.2.15",
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
@@ -7605,13 +7043,6 @@
"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": {
"version": "0.4.0",
"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==",
"dev": true,
"license": "ISC",
"optional": true,
"peer": true,
"bin": {
"yaml": "bin.mjs"
},

View File

@@ -1,6 +1,6 @@
{
"appName": "Infoscreen-Management",
"version": "2025.1.0-alpha.8",
"version": "2025.1.0-alpha.9",
"copyright": "© 2025 Third-Age-Applications",
"supportContact": "support@third-age-applications.com",
"description": "Eine zentrale Verwaltungsoberfläche für digitale Informationsbildschirme.",
@@ -30,6 +30,16 @@
"commitId": "8d1df7199cb7"
},
"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",
"date": "2025-10-11",

View File

@@ -32,8 +32,12 @@ export async function fetchEventById(eventId: string) {
return data;
}
export async function deleteEvent(eventId: string) {
const res = await fetch(`/api/events/${encodeURIComponent(eventId)}`, {
export async function deleteEvent(eventId: string, force: boolean = false) {
const url = force
? `/api/events/${encodeURIComponent(eventId)}?force=1`
: `/api/events/${encodeURIComponent(eventId)}`;
const res = await fetch(url, {
method: 'DELETE',
});
const data = await res.json();

View File

@@ -75,22 +75,6 @@ type Event = {
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)
loadCldr(
caGregorian as object,
@@ -208,6 +192,7 @@ const Appointments: React.FC = () => {
const [periods, setPeriods] = React.useState<{ id: number; label: string }[]>([]);
const [activePeriodId, setActivePeriodId] = React.useState<number | null>(null);
// Confirmation dialog state
const [confirmDialogOpen, setConfirmDialogOpen] = React.useState(false);
const [confirmDialogData, setConfirmDialogData] = React.useState<{
@@ -217,6 +202,44 @@ const Appointments: React.FC = () => {
onCancel: () => void;
} | 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
const showConfirmDialog = (title: string, message: string): Promise<boolean> => {
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
useEffect(() => {
fetchGroups()
@@ -563,6 +600,22 @@ const Appointments: React.FC = () => {
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 (
<div>
<h1 style={{ fontSize: '1.5rem', fontWeight: 700, marginBottom: 16 }}>Terminmanagement</h1>
@@ -733,17 +786,25 @@ const Appointments: React.FC = () => {
setModalOpen(false);
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);
setEditMode(false);
// Force immediate data refresh
// Refresh the data and scheduler
await fetchAndSetEvents();
// Defer refresh to avoid interfering with current React commit
setTimeout(() => {
scheduleRef.current?.refreshEvents?.();
}, 0);
console.log('Modal save cycle completed - data refreshed');
}}
initialData={modalInitialData}
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)
if (args && args.requestType === 'eventChanged') {
console.log('actionComplete: Processing eventChanged from direct UI interaction (drag/resize)');
try {
type SchedulerEvent = Partial<Event> & {
Id?: string | number;
@@ -810,18 +872,64 @@ const Appointments: React.FC = () => {
payload.end = e.toISOString();
}
// Single occurrence change from a recurring master (our manual expansion marks OccurrenceOfId)
if (changed.OccurrenceOfId) {
if (!changed.StartTime) return; // cannot determine occurrence date
// Check if this is a single occurrence edit by looking at the original master event
const eventId = String(changed.Id);
// 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 occDate = occStart.toISOString().split('T')[0];
await detachEventOccurrence(Number(changed.OccurrenceOfId), occDate, payload);
} else if (changed.RecurrenceRule) {
// Change to master series (non-manually expanded recurrences)
await updateEvent(String(changed.Id), payload);
} else if (changed.Id) {
// Regular single event
await updateEvent(String(changed.Id), payload);
await detachEventOccurrence(Number(eventId), occDate, payload);
} else {
// This is a series edit or regular single event
console.log('Updating event directly...');
await updateEvent(eventId, payload);
}
// Refresh events and scheduler cache after persisting
@@ -860,6 +968,96 @@ const Appointments: React.FC = () => {
setModalOpen(true);
}}
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') {
args.cancel = true;
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 = {
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
isSingleOccurrence,
title: eventDataToUse.Subject,
@@ -1029,54 +1229,14 @@ const Appointments: React.FC = () => {
}
}}
actionBegin={async (args: ActionEventArgs) => {
// Delete operations are now handled in popupOpen to avoid multiple dialogs
if (args.requestType === 'eventRemove') {
// args.data ist ein Array von zu löschenden Events
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
// Cancel all delete operations here - they're handled in popupOpen
args.cancel = true;
} else if (
return;
}
if (
(args.requestType === 'eventCreate' || args.requestType === 'eventChange') &&
!allowScheduleOnHolidays
) {
@@ -1156,6 +1316,167 @@ const Appointments: React.FC = () => {
</div>
</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>
);
};