diff --git a/package.json b/package.json index 50b38c0..a0c6b49 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,6 @@ "chart.js": "^4.2.1", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", - "cmdk": "1.0.0", "date-fns": "^3.6.0", "dayjs": "^1.11.7", "localforage": "^1.10.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 37f4c4a..e262518 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -44,12 +44,6 @@ importers: '@radix-ui/react-popover': specifier: ^1.1.1 version: 1.1.5(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-radio-group': - specifier: ^1.2.3 - version: 1.2.3(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-scroll-area': - specifier: ^1.2.3 - version: 1.2.3(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-select': specifier: ^2.1.1 version: 2.1.5(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -550,9 +544,6 @@ packages: '@radix-ui/number@1.1.0': resolution: {integrity: sha512-V3gRzhVNU1ldS5XhAPTom1fOIo4ccrjjJgmE+LI2h/WaFpHmx0MQApT+KZHnx8abG6Avtfcz4WoEciMnpFT3HQ==} - '@radix-ui/primitive@1.0.1': - resolution: {integrity: sha512-yQ8oGX2GVsEYMWGxcovu1uGWPCxV5BFfeeYxqPmuAzUyLT9qmaMXSAhXpb0WrspIeqYzdJpkh2vHModJPgRIaw==} - '@radix-ui/primitive@1.1.1': resolution: {integrity: sha512-SJ31y+Q/zAyShtXJc8x83i9TYdbAfHZ++tUZnvjJJqFjzsdUnKsxPL6IEtBlxKkU7yzer//GQtZSV4GbldL3YA==} @@ -595,19 +586,6 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-collapsible@1.1.3': - resolution: {integrity: sha512-jFSerheto1X03MUC0g6R7LedNW9EEGWdg9W1+MlpkMLwGkgkbUXLPBH/KIuWKXUoeYRVY11llqbTBDzuLg7qrw==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - '@radix-ui/react-collection@1.1.1': resolution: {integrity: sha512-LwT3pSho9Dljg+wY2KN2mrrh6y3qELfftINERIzBUO9e0N+t0oMTyn3k9iv+ZqgrwGkRnLpNJrsMv9BZlt2yuA==} peerDependencies: @@ -634,15 +612,6 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-compose-refs@1.0.1': - resolution: {integrity: sha512-fDSBgd44FKHa1FRMU59qBMPFcl2PZE+2nmqunj+BWFyYYjnhIDWL2ItDs3rrbJDQOtzt5nIebLCQc4QRfz6LJw==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 - peerDependenciesMeta: - '@types/react': - optional: true - '@radix-ui/react-compose-refs@1.1.1': resolution: {integrity: sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw==} peerDependencies: @@ -652,15 +621,6 @@ packages: '@types/react': optional: true - '@radix-ui/react-context@1.0.1': - resolution: {integrity: sha512-ebbrdFoYTcuZ0v4wG5tedGnp9tzcV8awzsxYph7gXUyvnNLuTIcCk1q17JEbnVhXAKG9oX3KtchwiMIAYp9NLg==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 - peerDependenciesMeta: - '@types/react': - optional: true - '@radix-ui/react-context@1.1.1': resolution: {integrity: sha512-UASk9zi+crv9WteK/NU4PLvOoL3OuE6BWVKNF6hPRBtYBDXQ2u5iu3O59zUlJiTVvkyuycnqrztsHVJwcK9K+Q==} peerDependencies: @@ -670,19 +630,6 @@ packages: '@types/react': optional: true - '@radix-ui/react-dialog@1.0.5': - resolution: {integrity: sha512-GjWJX/AUpB703eEBanuBnIWdIXg6NvJFCXcNlSZk4xdszCdhrJgBoUd1cGk67vFO+WdA2pfI/plOpqz/5GUP6Q==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 - react-dom: ^16.8 || ^17.0 || ^18.0 - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - '@radix-ui/react-dialog@1.1.5': resolution: {integrity: sha512-LaO3e5h/NOEL4OfXjxD43k9Dx+vn+8n+PCFt6uhX/BADFflllyv3WJG6rgvvSVBxpTch938Qq/LGc2MMxipXPw==} peerDependencies: @@ -705,19 +652,6 @@ packages: '@types/react': optional: true - '@radix-ui/react-dismissable-layer@1.0.5': - resolution: {integrity: sha512-aJeDjQhywg9LBu2t/At58hCvr7pEm0o2Ke1x33B+MhjNmmZ17sy4KImo0KPLgsnc/zN7GPdce8Cnn0SWvwZO7g==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 - react-dom: ^16.8 || ^17.0 || ^18.0 - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - '@radix-ui/react-dismissable-layer@1.1.4': resolution: {integrity: sha512-XDUI0IVYVSwjMXxM6P4Dfti7AH+Y4oS/TB+sglZ/EXc7cqLwGAmp1NlMrcUjj7ks6R5WTZuWKv44FBbLpwU3sA==} peerDependencies: @@ -744,15 +678,6 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-focus-guards@1.0.1': - resolution: {integrity: sha512-Rect2dWbQ8waGzhMavsIbmSVCgYxkXLxxR3ZvCX79JOglzdEy4JXMb98lq4hPxUbLr77nP0UOGf4rcMU+s1pUA==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 - peerDependenciesMeta: - '@types/react': - optional: true - '@radix-ui/react-focus-guards@1.1.1': resolution: {integrity: sha512-pSIwfrT1a6sIoDASCSpFwOasEwKTZWDw/iBdtnqKO7v6FeOzYJ7U53cPzYFVR3geGGXgVHaH+CdngrrAzqUGxg==} peerDependencies: @@ -762,19 +687,6 @@ packages: '@types/react': optional: true - '@radix-ui/react-focus-scope@1.0.4': - resolution: {integrity: sha512-sL04Mgvf+FmyvZeYfNu1EPAaaxD+aw7cYeIB9L9Fvq8+urhltTRaEo5ysKOpHuKPclsZcSUMKlN05x4u+CINpA==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 - react-dom: ^16.8 || ^17.0 || ^18.0 - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - '@radix-ui/react-focus-scope@1.1.1': resolution: {integrity: sha512-01omzJAYRxXdG2/he/+xy+c8a8gCydoQ1yOxnWNcRhrrBW5W+RQJ22EK1SaO8tb3WoUsuEw7mJjBozPzihDFjA==} peerDependencies: @@ -788,15 +700,6 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-id@1.0.1': - resolution: {integrity: sha512-tI7sT/kqYp8p96yGWY1OAnLHrqDgzHefRBKQ2YAkBS5ja7QLcZ9Z/uY7bEjPUatf8RomoXM8/1sMj1IJaE5UzQ==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 - peerDependenciesMeta: - '@types/react': - optional: true - '@radix-ui/react-id@1.1.0': resolution: {integrity: sha512-EJUrI8yYh7WOjNOqpoJaf1jlFIH2LvtgAl+YcFqNCa+4hj64ZXmPkAKOFs/ukjz3byN6bdb/AVUqHkI8/uWWMA==} peerDependencies: @@ -858,19 +761,6 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-portal@1.0.4': - resolution: {integrity: sha512-Qki+C/EuGUVCQTOTD5vzJzJuMUlewbzuKyUy+/iHM2uwGiru9gZeBJtHAPKAEkB5KWGi9mP/CHKcY0wt1aW45Q==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 - react-dom: ^16.8 || ^17.0 || ^18.0 - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - '@radix-ui/react-portal@1.1.3': resolution: {integrity: sha512-NciRqhXnGojhT93RPyDaMPfLH3ZSl4jjIFbZQ1b/vxvZEdHsBZ49wP9w8L3HzUQwep01LcWtkUvm0OVB5JAHTw==} peerDependencies: @@ -884,19 +774,6 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-presence@1.0.1': - resolution: {integrity: sha512-UXLW4UAbIY5ZjcvzjfRFo5gxva8QirC9hF7wRE4U5gz+TP0DbRk+//qyuAQ1McDxBt1xNMBTaciFGvEmJvAZCg==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 - react-dom: ^16.8 || ^17.0 || ^18.0 - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - '@radix-ui/react-presence@1.1.2': resolution: {integrity: sha512-18TFr80t5EVgL9x1SwF/YGtfG+l0BS0PRAlCWBDoBEiDQjeKgnNZRVJp/oVBl24sr3Gbfwc/Qpj4OcWTQMsAEg==} peerDependencies: @@ -910,19 +787,6 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-primitive@1.0.3': - resolution: {integrity: sha512-yi58uVyoAcK/Nq1inRY56ZSjKypBNKTa/1mcL8qdl6oJeEaDbOldlzrGn7P6Q3Id5d+SYNGc5AJgc4vGhjs5+g==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 - react-dom: ^16.8 || ^17.0 || ^18.0 - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - '@radix-ui/react-primitive@2.0.1': resolution: {integrity: sha512-sHCWTtxwNn3L3fH8qAfnF3WbUZycW93SM1j3NFDzXBiz8D6F5UTTy8G1+WFEaiCdvCVRJWj6N2R4Xq6HdiHmDg==} peerDependencies: @@ -949,19 +813,6 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-radio-group@1.2.3': - resolution: {integrity: sha512-xtCsqt8Rp09FK50ItqEqTJ7Sxanz8EM8dnkVIhJrc/wkMMomSmXHvYbhv3E7Zx4oXh98aaLt9W679SUYXg4IDA==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - '@radix-ui/react-roving-focus@1.1.1': resolution: {integrity: sha512-QE1RoxPGJ/Nm8Qmk0PxP8ojmoaS67i0s7hVssS7KuI2FQoc/uzVlZsqKfQvxPE6D8hICCPHJ4D88zNhT3OOmkw==} peerDependencies: @@ -988,19 +839,6 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-scroll-area@1.2.3': - resolution: {integrity: sha512-l7+NNBfBYYJa9tNqVcP2AGvxdE3lmE6kFTBXdvHgUaZuy+4wGCL1Cl2AfaR7RKyimj7lZURGLwFO59k4eBnDJQ==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - '@radix-ui/react-select@2.1.5': resolution: {integrity: sha512-eVV7N8jBXAXnyrc+PsOF89O9AfVgGnbLxUtBb0clJ8y8ENMWLARGMI/1/SBRLz7u4HqxLgN71BJ17eono3wcjA==} peerDependencies: @@ -1040,15 +878,6 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-slot@1.0.2': - resolution: {integrity: sha512-YeTpuq4deV+6DusvVUW4ivBgnkHwECUu0BiN43L5UCDFgdhsRUWAghhTF5MbvNTPzmiFOx90asDSUjWuCNapwg==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 - peerDependenciesMeta: - '@types/react': - optional: true - '@radix-ui/react-slot@1.1.1': resolution: {integrity: sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g==} peerDependencies: @@ -1119,15 +948,6 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-use-callback-ref@1.0.1': - resolution: {integrity: sha512-D94LjX4Sp0xJFVaoQOd3OO9k7tpBYNOXdVhkltUbGv2Qb9OXdrg/CpsjlZv7ia14Sylv398LswWBVVu5nqKzAQ==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 - peerDependenciesMeta: - '@types/react': - optional: true - '@radix-ui/react-use-callback-ref@1.1.0': resolution: {integrity: sha512-CasTfvsy+frcFkbXtSJ2Zu9JHpN8TYKxkgJGWbjiZhFivxaeW7rMeZt7QELGVLaYVfFMsKHjb7Ak0nMEe+2Vfw==} peerDependencies: @@ -1137,15 +957,6 @@ packages: '@types/react': optional: true - '@radix-ui/react-use-controllable-state@1.0.1': - resolution: {integrity: sha512-Svl5GY5FQeN758fWKrjM6Qb7asvXeiZltlT4U2gVfl8Gx5UAv2sMR0LWo8yhsIZh2oQ0eFdZ59aoOOMV7b47VA==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 - peerDependenciesMeta: - '@types/react': - optional: true - '@radix-ui/react-use-controllable-state@1.1.0': resolution: {integrity: sha512-MtfMVJiSr2NjzS0Aa90NPTnvTSg6C/JLCV7ma0W6+OMV78vd8OyRpID+Ng9LxzsPbLeuBnWBA1Nq30AtBIDChw==} peerDependencies: @@ -1155,15 +966,6 @@ packages: '@types/react': optional: true - '@radix-ui/react-use-escape-keydown@1.0.3': - resolution: {integrity: sha512-vyL82j40hcFicA+M4Ex7hVkB9vHgSse1ZWomAqV2Je3RleKGO5iM8KMOEtfoSB0PnIelMd2lATjTGMYqN5ylTg==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 - peerDependenciesMeta: - '@types/react': - optional: true - '@radix-ui/react-use-escape-keydown@1.1.0': resolution: {integrity: sha512-L7vwWlR1kTTQ3oh7g1O0CBF3YCyyTj8NmhLR+phShpyA50HCfBFKVJTpshm9PzLiKmehsrQzTYTpX9HvmC9rhw==} peerDependencies: @@ -1173,15 +975,6 @@ packages: '@types/react': optional: true - '@radix-ui/react-use-layout-effect@1.0.1': - resolution: {integrity: sha512-v/5RegiJWYdoCvMnITBkNNx6bCj20fiaJnWtRkU18yITptraXjffz5Qbn05uOiQnOvi+dbkznkoaMltz1GnszQ==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 - peerDependenciesMeta: - '@types/react': - optional: true - '@radix-ui/react-use-layout-effect@1.1.0': resolution: {integrity: sha512-+FPE0rOdziWSrH9athwI1R0HDVbWlEhd+FR+aSDk4uWGmSJ9Z54sdZVDQPZAinJhJXwfT+qnj969mCsT2gfm5w==} peerDependencies: @@ -1386,12 +1179,6 @@ packages: resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} engines: {node: '>=6'} - cmdk@1.0.0: - resolution: {integrity: sha512-gDzVf0a09TvoJ5jnuPvygTB77+XdOSwEmJ88L6XPFPlv7T3RxbP9jgenfylrAMD0+Le1aO0nVjQUzl2g+vjz5Q==} - peerDependencies: - react: ^18.0.0 - react-dom: ^18.0.0 - color-convert@2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} engines: {node: '>=7.0.0'} @@ -1875,16 +1662,6 @@ packages: '@types/react': optional: true - react-remove-scroll@2.5.5: - resolution: {integrity: sha512-ImKhrzJJsyXJfBZ4bzu8Bwpka14c/fQt0k+cyFp/PBhTfyDnU5hjOtM4AG/0AMyy8oKzOTR0lDgJIM7pYXI0kw==} - engines: {node: '>=10'} - peerDependencies: - '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - peerDependenciesMeta: - '@types/react': - optional: true - react-remove-scroll@2.6.3: resolution: {integrity: sha512-pnAi91oOk8g8ABQKGF5/M9qxmmOPxaAnopyTHYfqYEwJhyFrbbBtHuSgtKEoH0jpcxx5o3hXqH1mNd9/Oi+8iQ==} engines: {node: '>=10'} @@ -2536,10 +2313,6 @@ snapshots: '@radix-ui/number@1.1.0': {} - '@radix-ui/primitive@1.0.1': - dependencies: - '@babel/runtime': 7.26.7 - '@radix-ui/primitive@1.1.1': {} '@radix-ui/react-arrow@1.1.1(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': @@ -2579,22 +2352,6 @@ snapshots: '@types/react': 18.3.18 '@types/react-dom': 18.3.5(@types/react@18.3.18) - '@radix-ui/react-collapsible@1.1.3(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@radix-ui/primitive': 1.1.1 - '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.18)(react@18.3.1) - '@radix-ui/react-context': 1.1.1(@types/react@18.3.18)(react@18.3.1) - '@radix-ui/react-id': 1.1.0(@types/react@18.3.18)(react@18.3.1) - '@radix-ui/react-presence': 1.1.2(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-primitive': 2.0.2(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.18)(react@18.3.1) - '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.18)(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - optionalDependencies: - '@types/react': 18.3.18 - '@types/react-dom': 18.3.5(@types/react@18.3.18) - '@radix-ui/react-collection@1.1.1(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.18)(react@18.3.1) @@ -2619,55 +2376,18 @@ snapshots: '@types/react': 18.3.18 '@types/react-dom': 18.3.5(@types/react@18.3.18) - '@radix-ui/react-compose-refs@1.0.1(@types/react@18.3.18)(react@18.3.1)': - dependencies: - '@babel/runtime': 7.26.7 - react: 18.3.1 - optionalDependencies: - '@types/react': 18.3.18 - '@radix-ui/react-compose-refs@1.1.1(@types/react@18.3.18)(react@18.3.1)': dependencies: react: 18.3.1 optionalDependencies: '@types/react': 18.3.18 - '@radix-ui/react-context@1.0.1(@types/react@18.3.18)(react@18.3.1)': - dependencies: - '@babel/runtime': 7.26.7 - react: 18.3.1 - optionalDependencies: - '@types/react': 18.3.18 - '@radix-ui/react-context@1.1.1(@types/react@18.3.18)(react@18.3.1)': dependencies: react: 18.3.1 optionalDependencies: '@types/react': 18.3.18 - '@radix-ui/react-dialog@1.0.5(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@babel/runtime': 7.26.7 - '@radix-ui/primitive': 1.0.1 - '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.3.18)(react@18.3.1) - '@radix-ui/react-context': 1.0.1(@types/react@18.3.18)(react@18.3.1) - '@radix-ui/react-dismissable-layer': 1.0.5(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-focus-guards': 1.0.1(@types/react@18.3.18)(react@18.3.1) - '@radix-ui/react-focus-scope': 1.0.4(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-id': 1.0.1(@types/react@18.3.18)(react@18.3.1) - '@radix-ui/react-portal': 1.0.4(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-presence': 1.0.1(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-slot': 1.0.2(@types/react@18.3.18)(react@18.3.1) - '@radix-ui/react-use-controllable-state': 1.0.1(@types/react@18.3.18)(react@18.3.1) - aria-hidden: 1.2.4 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - react-remove-scroll: 2.5.5(@types/react@18.3.18)(react@18.3.1) - optionalDependencies: - '@types/react': 18.3.18 - '@types/react-dom': 18.3.5(@types/react@18.3.18) - '@radix-ui/react-dialog@1.1.5(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/primitive': 1.1.1 @@ -2696,20 +2416,6 @@ snapshots: optionalDependencies: '@types/react': 18.3.18 - '@radix-ui/react-dismissable-layer@1.0.5(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@babel/runtime': 7.26.7 - '@radix-ui/primitive': 1.0.1 - '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.3.18)(react@18.3.1) - '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.3.18)(react@18.3.1) - '@radix-ui/react-use-escape-keydown': 1.0.3(@types/react@18.3.18)(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - optionalDependencies: - '@types/react': 18.3.18 - '@types/react-dom': 18.3.5(@types/react@18.3.18) - '@radix-ui/react-dismissable-layer@1.1.4(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/primitive': 1.1.1 @@ -2738,31 +2444,12 @@ snapshots: '@types/react': 18.3.18 '@types/react-dom': 18.3.5(@types/react@18.3.18) - '@radix-ui/react-focus-guards@1.0.1(@types/react@18.3.18)(react@18.3.1)': - dependencies: - '@babel/runtime': 7.26.7 - react: 18.3.1 - optionalDependencies: - '@types/react': 18.3.18 - '@radix-ui/react-focus-guards@1.1.1(@types/react@18.3.18)(react@18.3.1)': dependencies: react: 18.3.1 optionalDependencies: '@types/react': 18.3.18 - '@radix-ui/react-focus-scope@1.0.4(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@babel/runtime': 7.26.7 - '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.3.18)(react@18.3.1) - '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.3.18)(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - optionalDependencies: - '@types/react': 18.3.18 - '@types/react-dom': 18.3.5(@types/react@18.3.18) - '@radix-ui/react-focus-scope@1.1.1(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.18)(react@18.3.1) @@ -2774,14 +2461,6 @@ snapshots: '@types/react': 18.3.18 '@types/react-dom': 18.3.5(@types/react@18.3.18) - '@radix-ui/react-id@1.0.1(@types/react@18.3.18)(react@18.3.1)': - dependencies: - '@babel/runtime': 7.26.7 - '@radix-ui/react-use-layout-effect': 1.0.1(@types/react@18.3.18)(react@18.3.1) - react: 18.3.1 - optionalDependencies: - '@types/react': 18.3.18 - '@radix-ui/react-id@1.1.0(@types/react@18.3.18)(react@18.3.1)': dependencies: '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.18)(react@18.3.1) @@ -2865,16 +2544,6 @@ snapshots: '@types/react': 18.3.18 '@types/react-dom': 18.3.5(@types/react@18.3.18) - '@radix-ui/react-portal@1.0.4(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@babel/runtime': 7.26.7 - '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - optionalDependencies: - '@types/react': 18.3.18 - '@types/react-dom': 18.3.5(@types/react@18.3.18) - '@radix-ui/react-portal@1.1.3(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/react-primitive': 2.0.1(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -2885,17 +2554,6 @@ snapshots: '@types/react': 18.3.18 '@types/react-dom': 18.3.5(@types/react@18.3.18) - '@radix-ui/react-presence@1.0.1(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@babel/runtime': 7.26.7 - '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.3.18)(react@18.3.1) - '@radix-ui/react-use-layout-effect': 1.0.1(@types/react@18.3.18)(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - optionalDependencies: - '@types/react': 18.3.18 - '@types/react-dom': 18.3.5(@types/react@18.3.18) - '@radix-ui/react-presence@1.1.2(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.18)(react@18.3.1) @@ -2906,16 +2564,6 @@ snapshots: '@types/react': 18.3.18 '@types/react-dom': 18.3.5(@types/react@18.3.18) - '@radix-ui/react-primitive@1.0.3(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@babel/runtime': 7.26.7 - '@radix-ui/react-slot': 1.0.2(@types/react@18.3.18)(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - optionalDependencies: - '@types/react': 18.3.18 - '@types/react-dom': 18.3.5(@types/react@18.3.18) - '@radix-ui/react-primitive@2.0.1(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/react-slot': 1.1.1(@types/react@18.3.18)(react@18.3.1) @@ -2934,24 +2582,6 @@ snapshots: '@types/react': 18.3.18 '@types/react-dom': 18.3.5(@types/react@18.3.18) - '@radix-ui/react-radio-group@1.2.3(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@radix-ui/primitive': 1.1.1 - '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.18)(react@18.3.1) - '@radix-ui/react-context': 1.1.1(@types/react@18.3.18)(react@18.3.1) - '@radix-ui/react-direction': 1.1.0(@types/react@18.3.18)(react@18.3.1) - '@radix-ui/react-presence': 1.1.2(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-primitive': 2.0.2(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-roving-focus': 1.1.2(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.18)(react@18.3.1) - '@radix-ui/react-use-previous': 1.1.0(@types/react@18.3.18)(react@18.3.1) - '@radix-ui/react-use-size': 1.1.0(@types/react@18.3.18)(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - optionalDependencies: - '@types/react': 18.3.18 - '@types/react-dom': 18.3.5(@types/react@18.3.18) - '@radix-ui/react-roving-focus@1.1.1(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/primitive': 1.1.1 @@ -2986,23 +2616,6 @@ snapshots: '@types/react': 18.3.18 '@types/react-dom': 18.3.5(@types/react@18.3.18) - '@radix-ui/react-scroll-area@1.2.3(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@radix-ui/number': 1.1.0 - '@radix-ui/primitive': 1.1.1 - '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.18)(react@18.3.1) - '@radix-ui/react-context': 1.1.1(@types/react@18.3.18)(react@18.3.1) - '@radix-ui/react-direction': 1.1.0(@types/react@18.3.18)(react@18.3.1) - '@radix-ui/react-presence': 1.1.2(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-primitive': 2.0.2(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.18)(react@18.3.1) - '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.18)(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - optionalDependencies: - '@types/react': 18.3.18 - '@types/react-dom': 18.3.5(@types/react@18.3.18) - '@radix-ui/react-select@2.1.5(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/number': 1.1.0 @@ -3060,14 +2673,6 @@ snapshots: '@types/react': 18.3.18 '@types/react-dom': 18.3.5(@types/react@18.3.18) - '@radix-ui/react-slot@1.0.2(@types/react@18.3.18)(react@18.3.1)': - dependencies: - '@babel/runtime': 7.26.7 - '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.3.18)(react@18.3.1) - react: 18.3.1 - optionalDependencies: - '@types/react': 18.3.18 - '@radix-ui/react-slot@1.1.1(@types/react@18.3.18)(react@18.3.1)': dependencies: '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.18)(react@18.3.1) @@ -3153,27 +2758,12 @@ snapshots: '@types/react': 18.3.18 '@types/react-dom': 18.3.5(@types/react@18.3.18) - '@radix-ui/react-use-callback-ref@1.0.1(@types/react@18.3.18)(react@18.3.1)': - dependencies: - '@babel/runtime': 7.26.7 - react: 18.3.1 - optionalDependencies: - '@types/react': 18.3.18 - '@radix-ui/react-use-callback-ref@1.1.0(@types/react@18.3.18)(react@18.3.1)': dependencies: react: 18.3.1 optionalDependencies: '@types/react': 18.3.18 - '@radix-ui/react-use-controllable-state@1.0.1(@types/react@18.3.18)(react@18.3.1)': - dependencies: - '@babel/runtime': 7.26.7 - '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.3.18)(react@18.3.1) - react: 18.3.1 - optionalDependencies: - '@types/react': 18.3.18 - '@radix-ui/react-use-controllable-state@1.1.0(@types/react@18.3.18)(react@18.3.1)': dependencies: '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.18)(react@18.3.1) @@ -3181,14 +2771,6 @@ snapshots: optionalDependencies: '@types/react': 18.3.18 - '@radix-ui/react-use-escape-keydown@1.0.3(@types/react@18.3.18)(react@18.3.1)': - dependencies: - '@babel/runtime': 7.26.7 - '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.3.18)(react@18.3.1) - react: 18.3.1 - optionalDependencies: - '@types/react': 18.3.18 - '@radix-ui/react-use-escape-keydown@1.1.0(@types/react@18.3.18)(react@18.3.1)': dependencies: '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.18)(react@18.3.1) @@ -3196,13 +2778,6 @@ snapshots: optionalDependencies: '@types/react': 18.3.18 - '@radix-ui/react-use-layout-effect@1.0.1(@types/react@18.3.18)(react@18.3.1)': - dependencies: - '@babel/runtime': 7.26.7 - react: 18.3.1 - optionalDependencies: - '@types/react': 18.3.18 - '@radix-ui/react-use-layout-effect@1.1.0(@types/react@18.3.18)(react@18.3.1)': dependencies: react: 18.3.1 @@ -3404,16 +2979,6 @@ snapshots: clsx@2.1.1: {} - cmdk@1.0.0(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): - dependencies: - '@radix-ui/react-dialog': 1.0.5(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - transitivePeerDependencies: - - '@types/react' - - '@types/react-dom' - color-convert@2.0.1: dependencies: color-name: 1.1.4 @@ -3846,17 +3411,6 @@ snapshots: optionalDependencies: '@types/react': 18.3.18 - react-remove-scroll@2.5.5(@types/react@18.3.18)(react@18.3.1): - dependencies: - react: 18.3.1 - react-remove-scroll-bar: 2.3.8(@types/react@18.3.18)(react@18.3.1) - react-style-singleton: 2.2.3(@types/react@18.3.18)(react@18.3.1) - tslib: 2.8.1 - use-callback-ref: 1.3.3(@types/react@18.3.18)(react@18.3.1) - use-sidecar: 1.1.3(@types/react@18.3.18)(react@18.3.1) - optionalDependencies: - '@types/react': 18.3.18 - react-remove-scroll@2.6.3(@types/react@18.3.18)(react@18.3.1): dependencies: react: 18.3.1 diff --git a/src/core/types/enum.ts b/src/core/types/enum.ts index 87e4d9b..9154850 100644 --- a/src/core/types/enum.ts +++ b/src/core/types/enum.ts @@ -13,7 +13,7 @@ export enum UserRole { } export enum UserRoleJson { - ORGANIZATION_ADMIN = "Admin", + ORGANIZATION_ADMIN = "Organization Admin", EMPLOYEE = "Employee", TEAM_ADMIN = "Team Admin" } diff --git a/src/core/types/leave.ts b/src/core/types/leave.ts index 2d1186a..0c6cbf5 100644 --- a/src/core/types/leave.ts +++ b/src/core/types/leave.ts @@ -35,6 +35,11 @@ export type LeaveTypeCreateRequest = { symbol: string; } +export enum ApprovalMode { + ALL = 'ALL', + ANY = "ANY" +} + export type LeaveTypeResponse = { id: number; name: string; @@ -49,6 +54,8 @@ export type LeavePolicyCreateRequest = { name: string; status: LeavePolicyStatus; activatedTypes: LeavePolicyActivatedTypeRequest[]; + teamApprovers?: string[]; + approvalMode?: ApprovalMode; } export enum LeavePolicyStatus { @@ -66,7 +73,9 @@ export type LeavePolicyResponse = { id: number; name: string; activatedTypes: LeavePolicyActivatedTypeResponse[]; - isDefault: boolean + isDefault: boolean; + teamApprovers?: string[]; + approvalMode?: ApprovalMode; } export type LeavePolicyActivatedTypeResponse = { @@ -109,6 +118,8 @@ export type LeavePolicyUpdateRequest = { name: string; status: LeavePolicyStatus; activatedTypes: LeavePolicyActivatedTypeRequest[]; + teamApprovers?: string[]; + approvalMode?: ApprovalMode; } export type LeaveCheckRequest = { diff --git a/src/modules/leave/components/LeavePolicyCreateDialog.tsx b/src/modules/leave/components/LeavePolicyCreateDialog.tsx index 11007bb..1663135 100644 --- a/src/modules/leave/components/LeavePolicyCreateDialog.tsx +++ b/src/modules/leave/components/LeavePolicyCreateDialog.tsx @@ -1,4 +1,3 @@ -import React from "react"; import {Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle} from "@/components/ui/dialog"; import {Input} from "@/components/ui/input"; import {Button} from "@/components/ui/button"; @@ -9,7 +8,7 @@ import {zodResolver} from "@hookform/resolvers/zod"; import {Form, FormControl, FormField, FormItem, FormLabel, FormMessage} from "@/components/ui/form"; const FormSchema = z.object({ - name: z.string().min(2, {message: "Leave policy name must be over 2 characters"}).max(50, {message: "Leave policy name must be under 50 characters"}), + name: z.string().min(2, {message: "Leave policy name must be over 2 characters"}).max(50, {message: "Leave policy name must be under 50 characters"}) }); type PolicyInputs = z.infer; @@ -21,10 +20,11 @@ type CreatePolicyDialogProps = { }; export function CreatePolicyDialog({isOpen, onClose, onSubmit}: CreatePolicyDialogProps) { + const form = useForm({ resolver: zodResolver(FormSchema), defaultValues: { - name: "", + name: "" }, }); diff --git a/src/modules/leave/components/LeavePolicyList.tsx b/src/modules/leave/components/LeavePolicyList.tsx index e2e1259..34587bb 100644 --- a/src/modules/leave/components/LeavePolicyList.tsx +++ b/src/modules/leave/components/LeavePolicyList.tsx @@ -9,7 +9,7 @@ import {Badge} from "@/components/ui/badge"; import {NavigateFunction, useNavigate} from "react-router-dom"; import {CreatePolicyDialog} from "@/modules/leave/components/LeavePolicyCreateDialog.tsx"; import {PageSection} from "@/components/layout/PageSection.tsx"; -import {LeavePolicyResponse, LeavePolicyStatus} from "@/core/types/leave.ts"; +import {ApprovalMode, LeavePolicyResponse, LeavePolicyStatus} from "@/core/types/leave.ts"; import {getErrorMessage} from "@/core/utils/errorHandler.ts"; import {createLeavesPolicy, deleteLeavePolicy, getLeavesPolicies} from "@/core/services/leaveService.ts"; import {LeaveTypeCycleJson} from "@/core/types/enum.ts"; @@ -49,6 +49,8 @@ export default function LeavePolicyList() { name: name, status: LeavePolicyStatus.ACTIVE, activatedTypes: [], + teamApprovers: [], + approvalMode: ApprovalMode.ALL, }); toast({ @@ -120,6 +122,8 @@ export default function LeavePolicyList() { Name Types + Approval Mode + Approver Actions @@ -140,7 +144,7 @@ export default function LeavePolicyList() { setIsCreateDialogOpen(false)} - onSubmit={(name) => createLeavePolicy(name)} + onSubmit={(name) => createLeavePolicy(name )} /> {isDeleteDialogOpen && selectedLeavePolicy && ( @@ -172,6 +176,12 @@ function LeavePolicyRowItem({ setIsDeleteDialogOpen, isProcessing, }: LeavePolicyRowItemProps) { + const exampleData = [ + {teamApprover: ['Rozita Hasani', 'Team Admin'], approvalMode: ApprovalMode.ALL}, + {teamApprover: ['Team Admin'], approvalMode: ApprovalMode.ANY}, + ]; + const mockData = exampleData[Math.floor(Math.random() * exampleData.length)]; + return ( {leavePolicy.name} @@ -191,6 +201,22 @@ function LeavePolicyRowItem({ ))} + + + + {mockData.approvalMode === ApprovalMode.ALL ? ApprovalMode.ALL : ApprovalMode.ANY} + + + + {mockData.teamApprover.map((approver) => ({approver}))} + +
+
+
+
+ ); + })} + + + {activatedTypes.length === 0 && ( + + + This leave policy doesn't have any leave types + yet. Please click the button above to add a leave type. + + + )} + + ); } \ No newline at end of file diff --git a/src/modules/leave/pages/LeavePolicyUpdatePage.tsx b/src/modules/leave/pages/LeavePolicyUpdatePage.tsx index b630c92..0d7c25f 100644 --- a/src/modules/leave/pages/LeavePolicyUpdatePage.tsx +++ b/src/modules/leave/pages/LeavePolicyUpdatePage.tsx @@ -1,225 +1,225 @@ import React, {useEffect, useState} from "react"; import {useNavigate, useParams} from "react-router-dom"; import {Button} from "@/components/ui/button"; -import {Card, CardHeader, CardTitle} from "@/components/ui/card"; import {Input} from "@/components/ui/input"; -import {Pencil, Plus, Save, X} from "lucide-react"; +import {Form, FormControl, FormField, FormItem, FormLabel, FormMessage} from "@/components/ui/form"; +import {Separator} from "@/components/ui/separator"; +import {Save, X, Plus} from "lucide-react"; +import {useForm} from "react-hook-form"; +import {z} from "zod"; +import {zodResolver} from "@hookform/resolvers/zod"; import {toast} from "@/components/ui/use-toast"; -import {getLeavesPolicy, getLeavesTypes, updateLeavePolicy,} from "@/core/services/leaveService"; -import LeavePolicyActivatedTypeUpdateDialog from "@/modules/leave/components/LeavePolicyActivatedTypeUpdateDialog.tsx"; -import { - LeavePolicyActivatedTypeResponse, - LeavePolicyResponse, - LeavePolicyStatus, - LeaveTypeResponse -} from "@/core/types/leave.ts"; +import {getLeavesPolicy, getLeavesTypes, updateLeavePolicy} from "@/core/services/leaveService"; +import {ApprovalMode, LeavePolicyActivatedTypeResponse, LeavePolicyResponse, LeavePolicyStatus, LeaveTypeResponse} from "@/core/types/leave"; import {getErrorMessage} from "@/core/utils/errorHandler"; -import {LeavePolicyTable} from "@/modules/leave/components/LeavePolicyTable.tsx"; -import PageContent from "@/components/layout/PageContent.tsx"; -import PageHeader from "@/components/layout/PageHeader.tsx"; -import LeavePolicyActivatedTypeCreateDialog from "@/modules/leave/components/LeavePolicyActivatedTypeCreateDialog.tsx"; +import LeavePolicyActivatedTypeCreateDialog from "@/modules/leave/components/LeavePolicyActivatedTypeCreateDialog"; +import LeavePolicyActivatedTypeUpdateDialog from "@/modules/leave/components/LeavePolicyActivatedTypeUpdateDialog"; +import {LeavePolicyTable} from "@/modules/leave/components/LeavePolicyTable"; +import PageContent from "@/components/layout/PageContent"; +import PageHeader from "@/components/layout/PageHeader"; +import {Card, CardContent } from "@/components/ui/card"; + +const FormSchema = z.object({ + name: z.string().min(1, "Policy name is required"), + teamApprovers: z.array(z.string()).min(1, "At least one approver is required"), + approvalMode: z.nativeEnum(ApprovalMode), +}); + +type LeavePolicyFormInputs = z.infer; export default function LeavePolicyUpdatePage() { const {id} = useParams(); + const navigate = useNavigate(); const [leavePolicy, setLeavePolicy] = useState(null); const [leaveTypes, setLeaveTypes] = useState([]); const [selectedLeaveType, setSelectedLeaveType] = useState(null); const [isCreateDialogOpen, setIsCreateDialogOpen] = useState(false); const [isUpdateDialogOpen, setIsUpdateDialogOpen] = useState(false); - const [isEditingName, setIsEditingName] = useState(false); - const navigate = useNavigate(); - // Fetch Leave Policy + const commonApprovers = ["Team Admin", "HR Manager", "Department Head", "Line Manager", "Team Lead"]; + + const form = useForm({ + resolver: zodResolver(FormSchema), + defaultValues: { + name: "", + teamApprovers: ["Team Admin"], + approvalMode: ApprovalMode.ANY, + }, + }); + useEffect(() => { - const fetchLeavePolicy = async () => { + const fetchData = async () => { try { const policy = await getLeavesPolicy(Number(id)); setLeavePolicy(policy); - } catch (error) { - toast({ - title: "Error", - description: getErrorMessage(error as Error | string), - variant: "destructive", + form.reset({ + name: policy.name ?? "", + teamApprovers: policy.teamApprovers ?? ["Team Admin"], + approvalMode: policy.approvalMode ?? ApprovalMode.ANY, }); + } catch (error) { + toast({ title: "Error", description: getErrorMessage(error as Error), variant: "destructive" }); } }; - fetchLeavePolicy(); - }, [id]); - - // Fetch leave Types - useEffect(() => { const fetchLeaveTypes = async () => { try { const types = await getLeavesTypes(); setLeaveTypes(types); } catch (error) { - toast({ - title: "Error", - description: getErrorMessage(error as Error), - variant: "destructive", - }); + toast({ title: "Error", description: getErrorMessage(error as Error), variant: "destructive" }); } }; + fetchData(); fetchLeaveTypes(); - }, []); + }, [id]); - // Handle Policy Name Change - const savePolicyName = async () => { + const handleSave = async (data: LeavePolicyFormInputs) => { if (!leavePolicy) return; - const updatedName = leavePolicy.name; - - if (!updatedName) { - toast({ - title: "Error", - description: "Policy name cannot be empty", - variant: "destructive", - }); - return; + try { + await updateLeavePolicy({ + name: data.name, + activatedTypes: leavePolicy.activatedTypes, + status: LeavePolicyStatus.ACTIVE, + teamApprovers: data.teamApprovers, + approvalMode: data.approvalMode, + }, leavePolicy.id); + + toast({ title: "Success", description: "Leave policy updated successfully" }); + navigate("/leaves/policies"); + } catch (error) { + toast({ title: "Error", description: getErrorMessage(error as Error), variant: "destructive" }); } - - setLeavePolicy({...leavePolicy, name: updatedName}); - setIsEditingName(false); }; - // Handle Add Leave Type const addLeaveType = (newType: LeavePolicyActivatedTypeResponse) => { if (!leavePolicy) return; - - setLeavePolicy({ - ...leavePolicy, - activatedTypes: [...leavePolicy.activatedTypes, newType], - }); - + setLeavePolicy({ ...leavePolicy, activatedTypes: [...leavePolicy.activatedTypes, newType] }); setIsCreateDialogOpen(false); }; - // Handle Update Leave Type const updateLeaveType = (updatedType: LeavePolicyActivatedTypeResponse) => { if (!leavePolicy) return; - setLeavePolicy({ ...leavePolicy, - activatedTypes: leavePolicy.activatedTypes.map((type) => + activatedTypes: leavePolicy.activatedTypes.map(type => type.typeId === updatedType.typeId ? updatedType : type ), }); - setIsUpdateDialogOpen(false); setSelectedLeaveType(null); }; - // Handle Remove Leave Type const removeLeaveType = (typeId: number) => { if (!leavePolicy) return; - setLeavePolicy({ ...leavePolicy, - activatedTypes: leavePolicy.activatedTypes.filter((type) => type.typeId !== typeId), + activatedTypes: leavePolicy.activatedTypes.filter(type => type.typeId !== typeId), }); }; - // Save Final Changes - const savePolicy = async () => { - if (!leavePolicy) return; - - try { - await updateLeavePolicy( - { - name: leavePolicy.name, - activatedTypes: leavePolicy.activatedTypes, - status: LeavePolicyStatus.ACTIVE, - }, - leavePolicy.id - ); - - toast({ - title: "Success", - description: "Leave policy updated successfully", - variant: "default", - }); - - navigate("/leaves/policies"); - } catch (error) { - toast({ - title: "Error", - description: getErrorMessage(error as Error), - variant: "destructive", - }); - } - }; - return ( <> - - - {isEditingName ? ( - - setLeavePolicy({...leavePolicy!, name: e.target.value})} - placeholder="Enter policy name" - className="flex-1" - /> - - - ) : ( - - - {leavePolicy?.name} - + + + + )} /> + + ( + + Team Approvers +
+ {commonApprovers.map((approver) => { + const isSelected = field.value.includes(approver); + return ( + + ); + })} +
+ +
+ )} /> + + +
+

Leave Types

+ + { + setSelectedLeaveType(type); + setIsUpdateDialogOpen(true); + }} + onRemove={removeLeaveType} + /> +
+ +
+ - - - )} - - - { - setSelectedLeaveType(type); - setIsUpdateDialogOpen(true); - }} - onRemove={removeLeaveType} - /> + +
+ + + - - {leavePolicy?.activatedTypes && ( -
- - -
- )}
); -} \ No newline at end of file +} diff --git a/src/modules/team/Routes.tsx b/src/modules/team/Routes.tsx index 5a701ca..39513c6 100644 --- a/src/modules/team/Routes.tsx +++ b/src/modules/team/Routes.tsx @@ -1,14 +1,12 @@ import {Route} from "react-router-dom"; import AuthenticatedRoute from "@/modules/auth/components/AuthenticatedRoute.tsx"; import TeamsPage from "@/modules/team/pages/TeamsPage.tsx"; -import TeamCreatePage from "@/modules/team/pages/TeamCreatePage.tsx"; import DashboardLayout from "@/components/layout/DashboardLayout.tsx"; export default function TeamRoutes() { return ( }> }> - }> ); } \ No newline at end of file diff --git a/src/modules/team/components/TeamCreateDialog.tsx b/src/modules/team/components/TeamCreateDialog.tsx index 9d7a511..fad686e 100644 --- a/src/modules/team/components/TeamCreateDialog.tsx +++ b/src/modules/team/components/TeamCreateDialog.tsx @@ -1,20 +1,14 @@ -import React from "react"; import {useForm} from "react-hook-form"; -import {TeamResponse} from "@/core/types/team.ts"; -import {Button} from "@/components/ui/button.tsx"; +import {Button} from "@/components/ui/button"; import {z} from "zod"; import {zodResolver} from "@hookform/resolvers/zod"; -import {Form, FormControl, FormField, FormItem, FormLabel, FormMessage,} from "@/components/ui/form.tsx"; -import {Input} from "@/components/ui/input.tsx"; -import {Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle,} from "@/components/ui/dialog" +import {Form, FormControl, FormField, FormItem, FormLabel, FormMessage} from "@/components/ui/form"; +import {Input} from "@/components/ui/input"; +import {Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle} from "@/components/ui/dialog"; import {Save, X} from "lucide-react"; const FormSchema = z.object({ - name: z.string().min(2, { - message: "Team Name must be over 2 characters" - }).max(20, { - message: "Team Name must be under 20 characters" - }), + name: z.string().min(2, {message: "Team name must be at least 2 characters long",}).max(20, {message: "Team name must be under 20 characters",}) }); type CreateTeamInputs = z.infer; @@ -22,15 +16,14 @@ type CreateTeamInputs = z.infer; interface TeamCreateDialogProps { onClose: () => void; isOpen: boolean; - teamList: TeamResponse[]; - onSubmit: (name: string) => void; + onSubmit: (name: string,) => void; } -export default function TeamCreateDialog({isOpen, onClose, onSubmit, teamList}: TeamCreateDialogProps) { +export default function TeamCreateDialog({isOpen, onClose, onSubmit}: TeamCreateDialogProps) { const form = useForm({ resolver: zodResolver(FormSchema), defaultValues: { - name: "", + name: "" }, }); @@ -53,9 +46,9 @@ export default function TeamCreateDialog({isOpen, onClose, onSubmit, teamList}: name="name" render={({field}) => ( - Name + Team Name - + @@ -69,7 +62,7 @@ export default function TeamCreateDialog({isOpen, onClose, onSubmit, teamList}: diff --git a/src/modules/team/components/TeamUpdateDialog.tsx b/src/modules/team/components/TeamUpdateDialog.tsx index a46897c..6a53709 100644 --- a/src/modules/team/components/TeamUpdateDialog.tsx +++ b/src/modules/team/components/TeamUpdateDialog.tsx @@ -1,69 +1,72 @@ -import React, {useState} from "react"; import {z} from "zod"; import {zodResolver} from "@hookform/resolvers/zod"; import {useForm} from "react-hook-form"; import {Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle} from "@/components/ui/dialog.tsx"; import {Input} from "@/components/ui/input.tsx"; import {Button} from "@/components/ui/button.tsx"; -import {TeamCreateRequest, TeamResponse} from "@/core/types/team.ts"; +import {TeamResponse} from "@/core/types/team.ts"; import {Save, X} from "lucide-react"; - -type UpdateTeamDialogProps = { - teamId: number; - teamName: string; - onClose: () => void; - onSuccess: () => void; - updateTeam: (data: TeamCreateRequest, id: number) => Promise; -}; +import {Form, FormControl, FormField, FormItem, FormLabel, FormMessage} from "@/components/ui/form"; const FormSchema = z.object({ - name: z.string().min(2, {message: "team Name must be over 2 characters"}).max(20, { - message: "team Name must be under 20 characters", - }), + name: z.string().min(2, { + message: "Team name must be at least 2 characters long", + }).max(20, { + message: "Team name must be under 20 characters", + }) }); -export default function TeamUpdateDialog({teamId, teamName, onClose, onSuccess, updateTeam}: UpdateTeamDialogProps) { - const [isProcessing, setIsProcessing] = useState(false); +type UpdateTeamInputs = z.infer; + +interface TeamUpdateDialogProps { + team: TeamResponse; + onClose: () => void; + onSubmit: (data: UpdateTeamInputs, teamId: number) => void; +} - const form = useForm>({ +export default function TeamUpdateDialog({team, onClose, onSubmit}: TeamUpdateDialogProps) { + const form = useForm({ resolver: zodResolver(FormSchema), - defaultValues: {name: teamName}, + defaultValues: { + name: team.name || '' + }, }); - const handleSubmit = async (data: z.infer) => { - try { - setIsProcessing(true); - await updateTeam({name: data.name, metadata: {}}, teamId); - onSuccess(); - onClose(); - } catch (error) { - setIsProcessing(false); - } - }; - return ( Update Team -
-
- - -
- - - - -
+ +
+ onSubmit(data, team.id))} className="space-y-4"> + ( + + Team Name + + + + + + )} + /> + + + + + + +
); diff --git a/src/modules/team/pages/TeamCreatePage.tsx b/src/modules/team/pages/TeamCreatePage.tsx deleted file mode 100644 index 8f0d3a5..0000000 --- a/src/modules/team/pages/TeamCreatePage.tsx +++ /dev/null @@ -1,126 +0,0 @@ -import React, {useEffect, useState} from "react"; -import {useForm} from "react-hook-form"; -import {useNavigate} from 'react-router-dom'; -import {toast} from "@/components/ui/use-toast.ts"; -import {getErrorMessage} from "@/core/utils/errorHandler.ts"; -import {TeamResponse} from "@/core/types/team.ts"; -import {createTeam, getTeams} from "@/core/services/teamService.ts"; -import {Button} from "@/components/ui/button.tsx"; -import {Alert, AlertDescription} from "@/components/ui/alert.tsx"; -import {Card} from "@/components/ui/card.tsx"; -import {z} from "zod"; -import {zodResolver} from "@hookform/resolvers/zod"; -import {Form, FormControl, FormField, FormItem, FormLabel, FormMessage,} from "@/components/ui/form.tsx"; -import {Input} from "@/components/ui/input.tsx"; -import PageContent from "@/components/layout/PageContent.tsx"; -import PageHeader from "@/components/layout/PageHeader.tsx"; - -const FormSchema = z.object({ - name: z.string().min(2, { - message: "team Name must be over 2 characters" - }).max(20, { - message: "team Name must be under 20 characters" - }), -}); - -type CreateTeamInputs = z.infer; - -export default function TeamCreatePage() { - const navigate = useNavigate(); - const [teamList, setTeamList] = useState([]); - const [errorMessage, setErrorMessage] = useState(""); - const [isProcessing, setIsProcessing] = useState(false); - - const form = useForm({ - resolver: zodResolver(FormSchema), - defaultValues: { - name: "", - }, - }); - - useEffect(() => { - getTeams() - .then((response: TeamResponse[]) => { - setTeamList(response); - }) - .catch((error) => { - const errorMessage = getErrorMessage(error); - toast({ - title: "Error", - description: errorMessage, - variant: "destructive", - }); - }); - }, []); - - const onSubmit = (data: CreateTeamInputs) => { - createOrganizationTeam(data); - }; - - const createOrganizationTeam = (data: CreateTeamInputs) => { - const exists = teamList.some(t => t.name === data.name); - - if (exists) { - setErrorMessage('A team already exists with this name.'); - return; - } - - const payload = { - name: data.name, - metadata: {}, - }; - setIsProcessing(true); - - createTeam(payload) - .then(() => { - setIsProcessing(false); - navigate('/teams'); - }) - .catch((error) => { - setIsProcessing(false); - const errorMessage = getErrorMessage(error?.message); - toast({ - title: "Error", - description: errorMessage, - variant: "destructive", - }); - }); - }; - - return ( - <> - {errorMessage && ( - - {errorMessage} - - )} - - - - - -
- - ( - - Name - - - - - - )} - /> - - - -
-
- - ); -} \ No newline at end of file diff --git a/src/modules/team/pages/TeamsPage.tsx b/src/modules/team/pages/TeamsPage.tsx index b730e8c..1430fa9 100644 --- a/src/modules/team/pages/TeamsPage.tsx +++ b/src/modules/team/pages/TeamsPage.tsx @@ -12,6 +12,17 @@ import {DeleteModal} from "@/modules/team/components/TeamDeleteDialog.tsx"; import PageContent from "@/components/layout/PageContent.tsx"; import PageHeader from "@/components/layout/PageHeader.tsx"; import TeamCreateDialog from "@/modules/team/components/TeamCreateDialog.tsx"; +import {z} from "zod"; + +const FormSchema = z.object({ + name: z.string().min(2, { + message: "Team name must be at least 2 characters long", + }).max(20, { + message: "Team name must be under 20 characters", + }) +}); + +type UpdateTeamInputs = z.infer; export default function TeamsPage() { const [teamList, setTeamList] = useState([]); @@ -21,40 +32,38 @@ export default function TeamsPage() { const [isCreateDialogOpen, setIsCreateDialogOpen] = useState(false); useEffect(() => { - const fetchTeams = async () => { - try { - const response = await getTeams(); - setTeamList(response); - } catch (error) { - const errorMessage = getErrorMessage(error as Error); - toast({ - title: "Error", - description: errorMessage, - variant: "destructive", - }); - } - }; - fetchTeams(); }, []); + const fetchTeams = async () => { + try { + const response = await getTeams(); + setTeamList(response); + } catch (error) { + toast({ + title: "Error", + description: getErrorMessage(error as Error), + variant: "destructive", + }); + } + }; + const handleRemoveTeam = async () => { if (!selectedTeamForDelete) return; setIsProcessing(true); try { await deleteTeam(selectedTeamForDelete.id); - setTeamList(teamList.filter((team) => team.id !== selectedTeamForDelete.id)); + setTeamList((prev) => prev.filter((team) => team.id !== selectedTeamForDelete.id)); toast({ title: "Success", - description: "team removed successfully!", + description: "Team removed successfully!", variant: "default", }); } catch (error) { - const errorMessage = getErrorMessage(error as Error); toast({ title: "Error", - description: errorMessage, + description: getErrorMessage(error as Error), variant: "destructive", }); } finally { @@ -63,27 +72,36 @@ export default function TeamsPage() { } }; - const handleUpdateSuccess = async () => { + const handleUpdateTeam = async (data: UpdateTeamInputs, teamId: number) => { try { - const response = await getTeams(); - setTeamList(response); + await updateTeam( + { + name: data.name, + metadata: {}, + }, + teamId + ); + await fetchTeams(); + toast({ + title: "Success", + description: "Team updated successfully!", + variant: "default", + }); + setSelectedTeamForUpdate(null); } catch (error) { toast({ title: "Error", - description: "Failed to refresh team list", + description: getErrorMessage(error as Error), variant: "destructive", }); } }; - const createOrganizationTeam = async (name: string) => { + const handleCreateTeam = async (name: string) => { try { setIsProcessing(true); - setIsCreateDialogOpen(false); - // Check if the team name already exists - const exists = teamList.some((t) => t.name === name); - if (exists) { + if (teamList.some((t) => t.name === name)) { toast({ title: "Error", description: "A team with this name already exists.", @@ -93,8 +111,8 @@ export default function TeamsPage() { } await createTeam({ - name: name, - metadata: {}, + name, + metadata: {} }); toast({ @@ -103,10 +121,7 @@ export default function TeamsPage() { variant: "default", }); - // Refresh team list after creation - const updatedTeams = await getTeams(); - setTeamList(updatedTeams); - + await fetchTeams(); } catch (error) { toast({ title: "Error", @@ -115,14 +130,15 @@ export default function TeamsPage() { }); } finally { setIsProcessing(false); + setIsCreateDialogOpen(false); } }; return ( <> - + @@ -151,17 +167,14 @@ export default function TeamsPage() { setIsCreateDialogOpen(false)} - teamList={teamList} - onSubmit={(name) => createOrganizationTeam(name)} + onSubmit={handleCreateTeam} /> {selectedTeamForUpdate && ( setSelectedTeamForUpdate(null)} - onSuccess={handleUpdateSuccess} - updateTeam={updateTeam} + onSubmit={handleUpdateTeam} /> )} @@ -185,7 +198,7 @@ type TeamItemProps = { setSelectedTeamForDelete: (team: TeamResponse | null) => void; }; -function TeamRowItem({ t, isProcessing, setSelectedTeamForUpdate, setSelectedTeamForDelete }: TeamItemProps) { +function TeamRowItem({t, isProcessing, setSelectedTeamForUpdate, setSelectedTeamForDelete}: TeamItemProps) { return ( {t.name}