diff --git a/package.json b/package.json index e85fde8..50b38c0 100644 --- a/package.json +++ b/package.json @@ -16,11 +16,13 @@ "@hookform/resolvers": "^3.9.0", "@radix-ui/react-avatar": "^1.1.2", "@radix-ui/react-checkbox": "^1.1.1", - "@radix-ui/react-dialog": "^1.1.1", + "@radix-ui/react-collapsible": "^1.1.3", + "@radix-ui/react-dialog": "^1.1.5", "@radix-ui/react-dropdown-menu": "^2.1.1", "@radix-ui/react-label": "^2.1.0", "@radix-ui/react-popover": "^1.1.1", "@radix-ui/react-radio-group": "^1.2.3", + "@radix-ui/react-scroll-area": "^1.2.3", "@radix-ui/react-select": "^2.1.1", "@radix-ui/react-separator": "^1.1.2", "@radix-ui/react-slider": "^1.2.0", @@ -34,6 +36,7 @@ "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 62c4b02..37f4c4a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -29,8 +29,11 @@ importers: '@radix-ui/react-checkbox': specifier: ^1.1.1 version: 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) + '@radix-ui/react-collapsible': + specifier: ^1.1.3 + version: 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) '@radix-ui/react-dialog': - specifier: ^1.1.1 + specifier: ^1.1.5 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-dropdown-menu': specifier: ^2.1.1 @@ -44,6 +47,9 @@ importers: '@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) @@ -83,6 +89,9 @@ importers: clsx: specifier: ^2.1.1 version: 2.1.1 + cmdk: + specifier: 1.0.0 + version: 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) date-fns: specifier: ^3.6.0 version: 3.6.0 @@ -541,6 +550,9 @@ 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==} @@ -583,6 +595,19 @@ 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: @@ -609,6 +634,15 @@ 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: @@ -618,6 +652,15 @@ 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: @@ -627,6 +670,19 @@ 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: @@ -649,6 +705,19 @@ 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: @@ -675,6 +744,15 @@ 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: @@ -684,6 +762,19 @@ 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: @@ -697,6 +788,15 @@ 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: @@ -758,6 +858,19 @@ 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: @@ -771,6 +884,19 @@ 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: @@ -784,6 +910,19 @@ 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: @@ -849,6 +988,19 @@ 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: @@ -888,6 +1040,15 @@ 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: @@ -958,6 +1119,15 @@ 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: @@ -967,6 +1137,15 @@ 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: @@ -976,6 +1155,15 @@ 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: @@ -985,6 +1173,15 @@ 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: @@ -1189,6 +1386,12 @@ 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'} @@ -1672,6 +1875,16 @@ 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'} @@ -2323,6 +2536,10 @@ 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)': @@ -2362,6 +2579,22 @@ 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) @@ -2386,18 +2619,55 @@ 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 @@ -2426,6 +2696,20 @@ 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 @@ -2454,12 +2738,31 @@ 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) @@ -2471,6 +2774,14 @@ 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) @@ -2554,6 +2865,16 @@ 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) @@ -2564,6 +2885,17 @@ 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) @@ -2574,6 +2906,16 @@ 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) @@ -2644,6 +2986,23 @@ 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 @@ -2701,6 +3060,14 @@ 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) @@ -2786,12 +3153,27 @@ 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) @@ -2799,6 +3181,14 @@ 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) @@ -2806,6 +3196,13 @@ 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 @@ -3007,6 +3404,16 @@ 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 @@ -3439,6 +3846,17 @@ 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/Routes.tsx b/src/Routes.tsx index 76f4c9e..9846a2f 100644 --- a/src/Routes.tsx +++ b/src/Routes.tsx @@ -7,6 +7,7 @@ import OrganizationRoutes from "@/modules/organization/Routes.tsx"; import SettingsRoutes from "@/modules/settings/Routes.tsx"; import TeamRoutes from "@/modules/team/Routes.tsx"; import UserRoutes from "@/modules/user/Routes.tsx"; +import NotificationRoutes from "@/modules/notification/Routes.tsx"; export default function Root() { return ( @@ -19,6 +20,7 @@ export default function Root() { {SettingsRoutes()} {TeamRoutes()} {UserRoutes()} + {NotificationRoutes()} ) } \ No newline at end of file diff --git a/src/components/FormItemInfo.tsx b/src/components/FormItemInfo.tsx new file mode 100644 index 0000000..7ffbbdc --- /dev/null +++ b/src/components/FormItemInfo.tsx @@ -0,0 +1,22 @@ +import {Tooltip, TooltipContent, TooltipProvider, TooltipTrigger} from "@/components/ui/tooltip.tsx"; +import {Info} from "lucide-react"; +import React from "react"; + +interface FormItemTooltipProps { + title: string +} + +export default function FormItemInfo({title} : FormItemTooltipProps) { + return ( + + + e.preventDefault()} className={'ml-2 relative top-0.5'}> + + + +

{title}

+
+
+
+ ) +} \ No newline at end of file diff --git a/src/components/sidebar/Sidebar.tsx b/src/components/sidebar/Sidebar.tsx index 26d839e..0f3716e 100644 --- a/src/components/sidebar/Sidebar.tsx +++ b/src/components/sidebar/Sidebar.tsx @@ -1,10 +1,10 @@ import React, {useContext, useState} from 'react'; import {Link, useLocation} from 'react-router-dom'; -import {Bell, Building, Calendar, CalendarCheck, Home, Settings, TreePalm, User, Users} from 'lucide-react'; -import {Button} from "@/components/ui/button.tsx"; +import {Building, Calendar, CalendarCheck, Home, Settings, TreePalm, User, Users} from 'lucide-react'; import {UserContext} from "@/contexts/UserContext.tsx"; import SidebarAccountDropdown from "@/components/sidebar/SidebarAccountDropdown.tsx"; import Logo from "@/components/icon/Logo.tsx"; +import {NotificationBell} from "@/modules/notification/components/NotificationBell.tsx"; import {UserRole} from "@/core/types/enum.ts"; const navigationItems = { @@ -44,15 +44,12 @@ export default function Sidebar() {
-
+
Teampilot - +
diff --git a/src/components/sidebar/SidebarAccountDropdown.tsx b/src/components/sidebar/SidebarAccountDropdown.tsx index 1635f86..a4ac84c 100644 --- a/src/components/sidebar/SidebarAccountDropdown.tsx +++ b/src/components/sidebar/SidebarAccountDropdown.tsx @@ -46,7 +46,7 @@ export default function SidebarAccountDropdown({isActive, onClick}: AccountDropd
- +

{user?.firstName} {user?.lastName}

diff --git a/src/components/ui/collapsible.tsx b/src/components/ui/collapsible.tsx new file mode 100644 index 0000000..a23e7a2 --- /dev/null +++ b/src/components/ui/collapsible.tsx @@ -0,0 +1,9 @@ +import * as CollapsiblePrimitive from "@radix-ui/react-collapsible" + +const Collapsible = CollapsiblePrimitive.Root + +const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger + +const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent + +export { Collapsible, CollapsibleTrigger, CollapsibleContent } diff --git a/src/components/ui/command.tsx b/src/components/ui/command.tsx new file mode 100644 index 0000000..0db642a --- /dev/null +++ b/src/components/ui/command.tsx @@ -0,0 +1,151 @@ +import * as React from "react" +import { type DialogProps } from "@radix-ui/react-dialog" +import { Command as CommandPrimitive } from "cmdk" +import { Search } from "lucide-react" + +import { cn } from "@/lib/utils" +import { Dialog, DialogContent } from "@/components/ui/dialog" + +const Command = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +Command.displayName = CommandPrimitive.displayName + +const CommandDialog = ({ children, ...props }: DialogProps) => { + return ( + + + + {children} + + + + ) +} + +const CommandInput = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( +
+ + +
+)) + +CommandInput.displayName = CommandPrimitive.Input.displayName + +const CommandList = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) + +CommandList.displayName = CommandPrimitive.List.displayName + +const CommandEmpty = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>((props, ref) => ( + +)) + +CommandEmpty.displayName = CommandPrimitive.Empty.displayName + +const CommandGroup = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) + +CommandGroup.displayName = CommandPrimitive.Group.displayName + +const CommandSeparator = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +CommandSeparator.displayName = CommandPrimitive.Separator.displayName + +const CommandItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) + +CommandItem.displayName = CommandPrimitive.Item.displayName + +const CommandShortcut = ({ + className, + ...props +}: React.HTMLAttributes) => { + return ( + + ) +} +CommandShortcut.displayName = "CommandShortcut" + +export { + Command, + CommandDialog, + CommandInput, + CommandList, + CommandEmpty, + CommandGroup, + CommandItem, + CommandShortcut, + CommandSeparator, +} diff --git a/src/components/ui/radio-group.tsx b/src/components/ui/radio-group.tsx new file mode 100644 index 0000000..9d3a26e --- /dev/null +++ b/src/components/ui/radio-group.tsx @@ -0,0 +1,42 @@ +import * as React from "react" +import * as RadioGroupPrimitive from "@radix-ui/react-radio-group" +import { Circle } from "lucide-react" + +import { cn } from "@/lib/utils" + +const RadioGroup = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => { + return ( + + ) +}) +RadioGroup.displayName = RadioGroupPrimitive.Root.displayName + +const RadioGroupItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => { + return ( + + + + + + ) +}) +RadioGroupItem.displayName = RadioGroupPrimitive.Item.displayName + +export { RadioGroup, RadioGroupItem } diff --git a/src/components/ui/scroll-area.tsx b/src/components/ui/scroll-area.tsx new file mode 100644 index 0000000..cf253cf --- /dev/null +++ b/src/components/ui/scroll-area.tsx @@ -0,0 +1,46 @@ +import * as React from "react" +import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area" + +import { cn } from "@/lib/utils" + +const ScrollArea = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + + {children} + + + + +)) +ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName + +const ScrollBar = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, orientation = "vertical", ...props }, ref) => ( + + + +)) +ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName + +export { ScrollArea, ScrollBar } diff --git a/src/components/ui/skeleton.tsx b/src/components/ui/skeleton.tsx new file mode 100644 index 0000000..d7e45f7 --- /dev/null +++ b/src/components/ui/skeleton.tsx @@ -0,0 +1,15 @@ +import { cn } from "@/lib/utils" + +function Skeleton({ + className, + ...props +}: React.HTMLAttributes) { + return ( +
+ ) +} + +export { Skeleton } diff --git a/src/core/services/notificationService.ts b/src/core/services/notificationService.ts new file mode 100644 index 0000000..5ee26ea --- /dev/null +++ b/src/core/services/notificationService.ts @@ -0,0 +1,53 @@ +import { PagedResponse } from '../types/common'; +import axiosInstance from './httpService'; +import {EventSchema, NotificationFilterRequest, NotificationTrigger, NotificationTriggerCreateRequest, Notification, NotificationsCountResponse, NotificationTriggerUpdateRequest +} from '@/core/types/notifications.ts'; + +const baseURL = '/v1/notifications'; + +async function getNotificationTriggers(): Promise { + const response = await axiosInstance.get(`${baseURL}/triggers`); + return response.data; +} + +async function createNotificationTrigger(payload: NotificationTriggerCreateRequest): Promise { + const response = await axiosInstance.post(`${baseURL}/triggers`, payload); + return response.data; +} + +async function getNotifications(payload: NotificationFilterRequest,page: number,size: number): Promise> { + const response = await axiosInstance.get(`${baseURL}`, { params: { ...payload, page, size } }); + return response.data; +} + +async function getNotificationEventSchemas() : Promise { + const response = await axiosInstance.get(`${baseURL}/events`); + return response.data; +} + +async function deleteNotificationTrigger(id:number) { + const response = await axiosInstance.delete(`${baseURL}/triggers/${id}`); + return response.data; +} + +async function updateNotificationTrigger(payload: NotificationTriggerUpdateRequest, id: number): Promise { + const response = await axiosInstance.put(`${baseURL}/triggers/${id}`, payload); + return response.data; +} + +async function getNotificationTrigger(id:number): Promise { + const response = await axiosInstance.get(`${baseURL}/triggers/${id}`); + return response.data; +} + +async function getNotificationsCount() : Promise { + const response = await axiosInstance.get(`${baseURL}/count`); + return response.data; +} + +async function createNotificationRead(payload: number[]): Promise { + const response = await axiosInstance.post(`${baseURL}/read`, payload); + return response.data; +} + +export {getNotificationTriggers, createNotificationTrigger, getNotifications, getNotificationEventSchemas, deleteNotificationTrigger,getNotificationsCount, createNotificationRead, getNotificationTrigger, updateNotificationTrigger}; diff --git a/src/core/types/common.ts b/src/core/types/common.ts index 758441d..ca61d38 100644 --- a/src/core/types/common.ts +++ b/src/core/types/common.ts @@ -6,22 +6,7 @@ export type PagedResponse = { totalContents: number } -export type Navigation = { - name: string; - icon: React.ComponentType>; - href: string -} - export type Country = { name: string; code: string -} - -export type Balance = { - label: 'Vacation' | 'Sick leave' | 'Paid time off'; - leaveQuantity: number; - leaveUsed: number; - leaveColor: string; -}; - -export type Nullable = T | null; +} \ No newline at end of file diff --git a/src/core/types/enum.ts b/src/core/types/enum.ts index e2cb6cf..87e4d9b 100644 --- a/src/core/types/enum.ts +++ b/src/core/types/enum.ts @@ -57,5 +57,4 @@ export enum Week { FRIDAY = "FRIDAY", SATURDAY = "SATURDAY", SUNDAY = "SUNDAY" -} - +} \ No newline at end of file diff --git a/src/core/types/leave.ts b/src/core/types/leave.ts index e92b62e..2d1186a 100644 --- a/src/core/types/leave.ts +++ b/src/core/types/leave.ts @@ -121,6 +121,7 @@ export type LeaveCheckResponse = { isAllowed: boolean; message: string; duration: number; + totalDays: number; yourConflicts: LeaveResponse[]; teamConflicts: LeaveResponse[]; holidays: HolidayResponse[]; diff --git a/src/core/types/notifications.ts b/src/core/types/notifications.ts new file mode 100644 index 0000000..6886938 --- /dev/null +++ b/src/core/types/notifications.ts @@ -0,0 +1,123 @@ +import { UserResponse } from "@/core/types/user.ts"; + +export enum NotificationChannel { + EMAIL = 'EMAIL', + SLACK = 'SLACK' +} + +export enum NotificationTriggerStatus { + ENABLED = 'ENABLED', + DISABLED = 'DISABLED', + ARCHIVED = 'ARCHIVED' +} + +export enum EventType { + ORGANIZATION_CREATED = 'ORGANIZATION_CREATED', + USER_CREATED = 'USER_CREATED', + LEAVE_CREATED = 'LEAVE_CREATED', + LEAVE_STATUS_UPDATED = 'LEAVE_STATUS_UPDATED', + TEAM_CREATED = 'TEAM_CREATED', + NOTIFICATION_CREATED = 'NOTIFICATION_CREATED' +} + +export enum NotificationReceptor { + USER = 'USER', + TEAM_ADMIN = 'TEAM_ADMIN', + ORGANIZATION_ADMIN = 'ORGANIZATION_ADMIN', + ALL_TEAM_MEMBERS = 'ALL_TEAM_MEMBERS', + REVIEWERS = 'REVIEWERS' +} + + +export interface NotificationTrigger { + id: number; + eventType: EventType; + name:string; + title:string; + textTemplate:string; + htmlTemplate:string; + channels: NotificationChannel[]; + receptors: NotificationReceptor; + status: NotificationTriggerStatus; +} + +export interface NotificationTriggerCreateRequest { + title: string; + name: string; + textTemplate: string; + htmlTemplate: string; + eventType: EventType; + channels: NotificationChannel[]; + receptors: NotificationReceptor; +} + +export interface NotificationFilterRequest { + eventType?: EventType; + channel?: NotificationChannel[]; + startDate?: string; + endDate?: string; +} + +export interface Notification { + id: number; + title: string, + user: UserResponse; + trigger: NotificationTrigger; + textContent: string; + htmlContent: string; + event: EventType; + params: Record; + channels: NotificationChannel[]; + sentAt: string; + status: NotificationStatus; +} + +export enum NotificationStatus { + PENDING = 'PENDING', + SENT = 'SENT', + FAILED = 'FAILED', + READ = "READ" +} + +export interface NotificationsCountResponse { + unreadCount: number; + totalCount: number +} + +export interface EventSchema { + name: string; + description: string; + schema: SchemaObject; + receptors: NotificationReceptor[]; +} + +export interface SchemaObject { + type: string; + properties?: FieldSchema[]; +} + +export interface FieldSchema { + name: string; + type: string; + required: boolean; + description?: string; + enumValues?: string[]; + properties?: FieldSchema[]; + items?: ItemSchema; +} + +export interface ItemSchema { + type: string; + enumValues?: string[]; + properties?: FieldSchema[]; +} + +export interface NotificationTriggerUpdateRequest { + title: string + name: string; + textTemplate: string; + htmlTemplate: string; + eventType: EventType; + channels: NotificationChannel[]; + receptors: NotificationReceptor +} \ No newline at end of file diff --git a/src/core/utils/timeAgo.ts b/src/core/utils/timeAgo.ts new file mode 100644 index 0000000..261c703 --- /dev/null +++ b/src/core/utils/timeAgo.ts @@ -0,0 +1,9 @@ +import dayjs from 'dayjs'; +import relativeTime from 'dayjs/plugin/relativeTime'; + +dayjs.extend(relativeTime); + +export function formatTimeAgo(timestamp: string): string { + if (!timestamp) return ''; + return dayjs(timestamp).fromNow(); +} \ No newline at end of file diff --git a/src/modules/calendar/components/CalendarDayBox.tsx b/src/modules/calendar/components/CalendarDayBox.tsx index 67c4be1..e3d692e 100644 --- a/src/modules/calendar/components/CalendarDayBox.tsx +++ b/src/modules/calendar/components/CalendarDayBox.tsx @@ -4,6 +4,7 @@ import {Popover, PopoverContent, PopoverTrigger} from "@radix-ui/react-popover"; import CalendarLeaveItem from "@/modules/calendar/components/CalendarLeaveItem.tsx"; import {LeaveResponse} from "@/core/types/leave.ts"; import {Badge} from "@/components/ui/badge.tsx"; +import {LeaveStatus} from "@/core/types/enum.ts"; interface DayBoxProps { date: Date; @@ -17,10 +18,10 @@ interface DayBoxProps { export const CalendarDayBox: React.FC = ({date, isHoliday, isWeekend, leaves, isSelected, onClick}) => { const isToday = dayjs().isSame(date, "day"); + const filteredLeaves = leaves.filter(leave => leave.status !== LeaveStatus.REJECTED); - - const displayedLeaves = leaves.slice(0, 3); - const remainingLeavesCount = leaves.length - displayedLeaves.length; + const displayedLeaves = filteredLeaves.slice(0, 3); + const remainingLeavesCount = filteredLeaves.length - displayedLeaves.length; return ( @@ -59,7 +60,7 @@ export const CalendarDayBox: React.FC = ({date, isHoliday, isWeeken
- {leaves.slice(3).map((leave) => ( + {filteredLeaves.slice(3).map((leave) => ( ))}
diff --git a/src/modules/leave/components/LeavePolicyList.tsx b/src/modules/leave/components/LeavePolicyList.tsx index 41c7fee..e2e1259 100644 --- a/src/modules/leave/components/LeavePolicyList.tsx +++ b/src/modules/leave/components/LeavePolicyList.tsx @@ -181,7 +181,7 @@ function LeavePolicyRowItem({ {activatedType.symbol} {activatedType.name}: diff --git a/src/modules/leave/pages/LeaveCreatePage.tsx b/src/modules/leave/pages/LeaveCreatePage.tsx index 3da41f5..b6e555b 100644 --- a/src/modules/leave/pages/LeaveCreatePage.tsx +++ b/src/modules/leave/pages/LeaveCreatePage.tsx @@ -56,6 +56,7 @@ export default function LeaveCreatePage() { const [weekendsDays, setWeekendsDays] = useState([]); const [userLeaves, setUserLeaves] = useState([]); const [duration, setDuration] = useState(0); + const [totalLeaveDays, setTotalLeaveDays] = useState(0); const [conflicts, setConflicts] = useState([]); const [errorMessage, setErrorMessage] = useState(null); const {user, organization} = useContext(UserContext); @@ -149,6 +150,7 @@ export default function LeaveCreatePage() { end: dayjs(endDate).toISOString(), }); setDuration(response.duration); + setTotalLeaveDays(response.totalDays); setConflicts([...response.yourConflicts, ...response.teamConflicts]); } catch (error) { setDuration(null); @@ -229,16 +231,16 @@ export default function LeaveCreatePage() { return ( <> - {errorMessage && ( - - {errorMessage} - - )} -
+ {errorMessage && ( + + {errorMessage} + + )} +
@@ -267,7 +269,7 @@ export default function LeaveCreatePage() {
Total Days
-
{duration}
+
{totalLeaveDays}
Working Days
@@ -276,7 +278,7 @@ export default function LeaveCreatePage() {
Non-Working Days
- {dayjs(endDate).diff(dayjs(startDate), 'days') + 1 - duration} + {totalLeaveDays - duration}
diff --git a/src/modules/notification/Routes.tsx b/src/modules/notification/Routes.tsx new file mode 100644 index 0000000..c495244 --- /dev/null +++ b/src/modules/notification/Routes.tsx @@ -0,0 +1,17 @@ +import { Route } from "react-router-dom"; +import { NotificationTriggersPage } from "./pages/NotificationTriggersPage.tsx"; +import AuthenticatedRoute from "../auth/components/AuthenticatedRoute"; +import NotificationsPage from "./pages/NotificationsPage"; +import NotificationTriggerCreatePage from "./pages/NotificationTriggerCreatePage.tsx"; +import DashboardLayout from "@/components/layout/DashboardLayout.tsx"; +import NotificationTriggerUpdatePage from "@/modules/notification/pages/NotificationTriggerUpdatePage.tsx"; +export default function NotificationRoutes() { + return ( + }> + }> + }> + }> + }> + + ); +} \ No newline at end of file diff --git a/src/modules/notification/components/NotificationBell.tsx b/src/modules/notification/components/NotificationBell.tsx new file mode 100644 index 0000000..446721d --- /dev/null +++ b/src/modules/notification/components/NotificationBell.tsx @@ -0,0 +1,63 @@ +import React, {useEffect, useState} from "react"; +import {Button} from "@/components/ui/button.tsx"; +import {Bell} from "lucide-react"; +import {NotificationPopover} from "@/modules/notification/components/NotificationPopover.tsx"; +import {getNotificationsCount} from "@/core/services/notificationService.ts"; +import {getErrorMessage} from "@/core/utils/errorHandler.ts"; +import {toast} from "@/components/ui/use-toast.ts"; + +export const NotificationBell: React.FC = () => { + const [isOpen, setIsOpen] = useState(false); + const [errorMessage, setErrorMessage] = useState(null); + const [unreadCount, setUnreadCount] = useState(0); + + useEffect(() => { + const intervalId = setInterval(async () => { + await fetchNotificationsCount(); + }, 30_000); + fetchNotificationsCount(); + return () => clearInterval(intervalId); + }, []); + + const fetchNotificationsCount = async () => { + try { + const response = await getNotificationsCount(); + setUnreadCount(response.unreadCount); + } catch (error) { + const errorMessage = getErrorMessage(error as Error); + setErrorMessage(errorMessage); + toast({title: "Error", description: errorMessage, variant: "destructive",}); + } + }; + + const toggleNotificationPanel = () => { + setIsOpen(!isOpen); + }; + + return ( +
+ + + {isOpen && ( + + )} +
+ ); +}; \ No newline at end of file diff --git a/src/modules/notification/components/NotificationChannelBadge.tsx b/src/modules/notification/components/NotificationChannelBadge.tsx new file mode 100644 index 0000000..e0e79d9 --- /dev/null +++ b/src/modules/notification/components/NotificationChannelBadge.tsx @@ -0,0 +1,35 @@ +import { NotificationChannel } from "@/core/types/notifications"; +import { Badge } from "@/components/ui/badge"; +import { Mail, Slack } from "lucide-react"; +import React from "react"; + +interface NotificationChannelBadgeProps { + channels: NotificationChannel[]; +} + +export default function NotificationChannelBadge({ channels }: NotificationChannelBadgeProps) { + const getChannelBadge = (channel: NotificationChannel) => { + switch (channel) { + case NotificationChannel.EMAIL: + return ( + + Email + + ); + case NotificationChannel.SLACK: + return ( + + Slack + + ); + default: + return null; + } + }; + + return ( +

+ {channels.map((ch) => getChannelBadge(ch))} +

+ ); +} \ No newline at end of file diff --git a/src/modules/notification/components/NotificationPopover.tsx b/src/modules/notification/components/NotificationPopover.tsx new file mode 100644 index 0000000..c637fe2 --- /dev/null +++ b/src/modules/notification/components/NotificationPopover.tsx @@ -0,0 +1,174 @@ +import React, {useEffect, useState} from "react"; +import {Card, CardContent, CardHeader} from "@/components/ui/card.tsx"; +import {Bell, Check, X} from "lucide-react"; +import {Notification, NotificationStatus} from '@/core/types/notifications.ts'; +import {formatTimeAgo} from "@/core/utils/timeAgo.ts"; +import {Tooltip, TooltipContent, TooltipProvider, TooltipTrigger} from "@/components/ui/tooltip.tsx"; +import {Dialog, DialogContent, DialogHeader, DialogTitle} from "@/components/ui/dialog.tsx"; +import {createNotificationRead, getNotifications} from "@/core/services/notificationService.ts"; +import {getErrorMessage} from "@/core/utils/errorHandler.ts"; +import {toast} from "@/components/ui/use-toast.ts"; + +interface NotificationPanelProps { + onClose: () => void; + setUnreadCount: React.Dispatch>; +} + +export const NotificationPopover = ({onClose, setUnreadCount}: NotificationPanelProps) => { + const [dialogOpen, setDialogOpen] = useState(false); + const [selectedNotification, setSelectedNotification] = useState(); + const [notifications, setNotifications] = useState([]); + const [errorMessage, setErrorMessage] = useState(null); + + useEffect(() => { + fetchNotifications(); + }, []); + + const fetchNotifications = async () => { + try { + const response = await getNotifications({}, 1, 10); + setNotifications(response.contents); + } catch (error) { + const errorMessage = getErrorMessage(error as Error); + setErrorMessage(errorMessage); + toast({title: "Error", description: errorMessage, variant: "destructive",}); } + }; + + const markAsRead = async (id: number) => { + try { + await createNotificationRead([id]); + + setNotifications((prevNotifications) => + prevNotifications.map((n) => n.id === id ? {...n, status: NotificationStatus.READ} : n) + ); + setUnreadCount((prev) => prev - 1); + } catch (error) { + const errorMessage = getErrorMessage(error as Error); + setErrorMessage(errorMessage); + toast({title: "Error", description: errorMessage, variant: "destructive",}); + } + }; + + const markAllAsRead = async () => { + try { + const unreadIds = notifications + .filter((n) => n.status === NotificationStatus.SENT) + .map((n) => n.id); + + if (unreadIds.length === 0) return; + + await createNotificationRead(unreadIds); + setNotifications((prevNotifications) => + prevNotifications.map((n) => ({...n, status: NotificationStatus.READ})) + ); + setUnreadCount(0); + } catch (error) { + const errorMessage = getErrorMessage(error as Error); + setErrorMessage(errorMessage); + toast({title: "Error", description: errorMessage, variant: "destructive",}); + } + }; + + const handleNotificationClick = (notification: Notification) => { + markAsRead(notification.id); + setSelectedNotification(notification); + setDialogOpen(true); + }; + + const hasUnreadNotifications = notifications.some(n => n.status === NotificationStatus.SENT); + + return ( + <> + + +
+

Notifications

+
+
+ {hasUnreadNotifications && ( + + + + + + +

Mark all as read

+
+
+
+ )} + +
+
+ + + {notifications.length === 0 ? ( +
+ +

No notifications

+

We'll notify you when something important happens

+
+ ) : ( +
+ {notifications.map((notification) => { + const isUnread = notification.status === NotificationStatus.SENT; + return ( +
handleNotificationClick(notification)} + > +
+
+

{notification.title}

+
+ {formatTimeAgo(notification.sentAt)} +
+
+ {isUnread && ()} +
+
+ ); + })} +
+ )} +
+
+ + { + setDialogOpen(false); + }}> + {selectedNotification && ( + + + {selectedNotification.trigger.name} + + +
+
+

{selectedNotification.textContent}

+
+ +
+

{formatTimeAgo(selectedNotification.sentAt)}

+
+
+
+ )} +
+ + ); +}; \ No newline at end of file diff --git a/src/modules/notification/components/NotificationStatusIcon.tsx b/src/modules/notification/components/NotificationStatusIcon.tsx new file mode 100644 index 0000000..1581487 --- /dev/null +++ b/src/modules/notification/components/NotificationStatusIcon.tsx @@ -0,0 +1,26 @@ +import {NotificationStatus} from "@/core/types/notifications.ts"; +import {Check, CheckCheck, Clock, X} from "lucide-react"; +import React from "react"; + +interface NotificationStatusIconProps { + status: NotificationStatus; +} + +export default function NotificationStatusIcon({status}: NotificationStatusIconProps) { + const getStatusIcon = (status: NotificationStatus) => { + switch (status) { + case NotificationStatus.SENT: + return ; + case NotificationStatus.FAILED: + return ; + case NotificationStatus.PENDING: + return ; + case NotificationStatus.READ: + return ; + } + }; + + return ( + {getStatusIcon(status)} + ) +} \ No newline at end of file diff --git a/src/modules/notification/components/NotificationTriggerDeleteDialog.tsx b/src/modules/notification/components/NotificationTriggerDeleteDialog.tsx new file mode 100644 index 0000000..33b9497 --- /dev/null +++ b/src/modules/notification/components/NotificationTriggerDeleteDialog.tsx @@ -0,0 +1,34 @@ +import {Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle} from "@/components/ui/dialog.tsx"; +import {Button} from "@/components/ui/button.tsx"; +import React from "react"; +import {Check, X} from "lucide-react"; + +type DeleteDialogProps = { + name: string; + label: string; + handleAccept: () => void; + handleReject: () => void; +}; + +export function NotificationTriggerDeleteDialog({name, label, handleAccept, handleReject}: DeleteDialogProps) { + return ( + + + + Remove {name} + + Are you sure you want to remove {label}? + + + + + + + ); +} \ No newline at end of file diff --git a/src/modules/notification/components/NotificationTriggerForm.tsx b/src/modules/notification/components/NotificationTriggerForm.tsx new file mode 100644 index 0000000..1607452 --- /dev/null +++ b/src/modules/notification/components/NotificationTriggerForm.tsx @@ -0,0 +1,368 @@ +import {Button} from "@/components/ui/button.tsx"; +import {ChevronRight, Save, X} from "lucide-react"; +import {Form, FormControl, FormField, FormItem, FormLabel, FormMessage} from "@/components/ui/form.tsx"; +import React, {useState} from "react"; +import {useForm, UseFormReturn} from "react-hook-form"; +import {EventSchema, EventType, NotificationChannel, NotificationReceptor, NotificationTrigger} from "@/core/types/notifications.ts"; +import {useNavigate} from "react-router-dom"; +import {Separator} from "@/components/ui/separator.tsx"; +import {Input} from "@/components/ui/input.tsx"; +import {Select, SelectContent, SelectItem, SelectTrigger, SelectValue} from "@/components/ui/select.tsx"; +import {Tabs, TabsList, TabsTrigger} from "@/components/ui/tabs.tsx"; +import {Textarea} from "@/components/ui/textarea.tsx"; +import {Badge} from "@/components/ui/badge.tsx"; +import {ScrollArea} from "@/components/ui/scroll-area.tsx"; +import {toast} from "@/components/ui/use-toast.ts"; +import {getVariablesFromSchema} from "@/modules/notification/utils/eventSchema.ts"; +import FormItemInfo from "@/components/FormItemInfo.tsx"; +import {z} from "zod"; +import {zodResolver} from "@hookform/resolvers/zod"; + +interface NotificationTriggerFormProps { + onSubmit: (data: TriggerInputs) => void; + isProcessing: boolean; + eventSchemas: EventSchema[]; + trigger?: NotificationTrigger; + pageTitle: string +} + +const FormSchema = z.object({ + title: z.string().min(1, 'Title is required'), + name: z.string().min(1, 'Name is required'), + textTemplate: z.string().min(1, 'Text template is required'), + htmlTemplate: z.string().min(1, 'HTML template is required'), + eventType: z.nativeEnum(EventType, {errorMap: () => ({message: "Event type is required"})}), + channels: z.array(z.nativeEnum(NotificationChannel)).min(1, 'Select at least one channel'), + receptors: z.nativeEnum(NotificationReceptor, {errorMap: () => ({message: "Receptor is required"})}), +}); + +export type TriggerInputs = z.infer; + +export default function NotificationTriggerForm({onSubmit, isProcessing, eventSchemas, trigger, pageTitle}: NotificationTriggerFormProps) { + const navigate = useNavigate(); + const [activeTab, setActiveTab] = useState<'text' | 'html'>('text'); + const [selectedEventSchema, setSelectedEventSchema] = useState( + trigger ? eventSchemas.find(value => value.name === trigger.eventType) : null + ); + + const form = useForm({ + resolver: zodResolver(FormSchema), + defaultValues: { + channels: trigger?.channels ?? [], + receptors: trigger?.receptors, + title: trigger?.title ?? '', + name: trigger?.name ?? '', + textTemplate: trigger?.textTemplate ?? '', + htmlTemplate: trigger?.htmlTemplate ?? '', + eventType: trigger?.eventType, + }, + }); + + return ( + + + + + + +
+ + +
+ + + ) +} + +interface BasicInformationSectionProps { + form: UseFormReturn; +} + +function NotificationTriggerCreateBasicInformation({form}: BasicInformationSectionProps) { + return ( +
+

Basic Information

+ + ( +
+ + + Name + + + + + + + +
+ )}/> +
+ ); +} + +interface EventConfigurationProps { + form: UseFormReturn; + eventSchemas: EventSchema[]; + setSelectedEventSchema: (schema: EventSchema | null) => void; +} + +function NotificationTriggerCreateEventDetails({form, eventSchemas, setSelectedEventSchema}: EventConfigurationProps) { + return ( +
+

Event Details

+ +
+ ( + + + Title + + + + + + + + )}/> + + ( + + + Event Type + + + + + + )} + /> +
+
+ ); +} + +interface TemplateContentProps { + form: UseFormReturn; + activeTab: 'text' | 'html'; + setActiveTab: (tab: 'text' | 'html') => void; + selectedSchema: EventSchema; +} + +function NotificationTriggerCreateTemplateContent({form, activeTab, setActiveTab, selectedSchema}: TemplateContentProps) { + const availableVariables = selectedSchema ? getVariablesFromSchema(selectedSchema.schema) : {}; + + const copyVariables = () => { + const allVariables = Object.values(availableVariables) + .flat() + .map((v) => v.name) + .join("\n"); + navigator.clipboard.writeText(allVariables); + toast({ + title: "Copied", + description: "Variables copied to clipboard", + }); + }; + + return ( +
+

Template Content

+ + +
+
+ setActiveTab(value as 'text' | 'html')}> + + Text Template + HTML Template + + + +
+ {activeTab === 'text' && ( + ( + + +