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:
575
dashboard/package-lock.json
generated
575
dashboard/package-lock.json
generated
@@ -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"
|
||||
},
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user