From 0cf6b405f12faeda5c47a6433e91e70746753302 Mon Sep 17 00:00:00 2001 From: Danijel Martinek Date: Tue, 2 Dec 2025 12:57:42 +0100 Subject: [PATCH] feat: redesign meal ordering system --- .eslintrc.cjs | 10 +- README.md | 2 +- package.json | 6 + pnpm-lock.yaml | 1690 ++++++++++++++++- src/app/(app)/caregiver/dashboard/page.tsx | 847 ++++++++- src/app/(app)/caregiver/orders/[id]/page.tsx | 1128 +++++++++++ src/app/(app)/caregiver/orders/page.tsx | 170 +- src/app/(payload)/admin/importMap.js | 2 +- .../admin/views/KitchenDashboard/index.tsx | 10 +- src/collections/MealOrders/index.ts | 401 +--- .../endpoints/kitchenReport.ts | 52 +- .../hooks/generateTitle.ts | 2 +- .../hooks/setCreatedBy.ts | 0 src/collections/Meals/index.ts | 448 +++++ src/collections/Residents/index.ts | 2 +- .../Users/endpoints/externalUsersLogin.ts | 4 +- .../Users/hooks/ensureUniqueUsername.ts | 5 +- src/components/ui/dialog.tsx | 143 ++ src/components/ui/progress.tsx | 31 + src/components/ui/sheet.tsx | 139 ++ src/components/ui/tooltip.tsx | 61 + src/payload-types.ts | 86 +- src/payload.config.ts | 45 +- src/seed.ts | 178 +- 24 files changed, 4823 insertions(+), 639 deletions(-) create mode 100644 src/app/(app)/caregiver/orders/[id]/page.tsx rename src/collections/{MealOrders => Meals}/endpoints/kitchenReport.ts (87%) rename src/collections/{MealOrders => Meals}/hooks/generateTitle.ts (96%) rename src/collections/{MealOrders => Meals}/hooks/setCreatedBy.ts (100%) create mode 100644 src/collections/Meals/index.ts create mode 100644 src/components/ui/dialog.tsx create mode 100644 src/components/ui/progress.tsx create mode 100644 src/components/ui/sheet.tsx create mode 100644 src/components/ui/tooltip.tsx diff --git a/.eslintrc.cjs b/.eslintrc.cjs index b69f00e..90abec0 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -1,4 +1,12 @@ module.exports = { root: true, - extends: ['@payloadcms'], + extends: ['next/core-web-vitals', 'next/typescript'], + rules: { + '@typescript-eslint/no-explicit-any': 'warn', + '@typescript-eslint/no-unused-vars': ['warn', { + argsIgnorePattern: '^_', + varsIgnorePattern: '^_', + caughtErrorsIgnorePattern: '^_', + }], + }, } diff --git a/README.md b/README.md index 40bd924..2bd7be9 100644 --- a/README.md +++ b/README.md @@ -186,7 +186,7 @@ pnpm generate:types # Generate TypeScript types ### Database -The application uses SQLite by default (`payload.db`). To migrate to PostgreSQL: +The application uses SQLite by default (`meal-planner.db`). To migrate to PostgreSQL: 1. Install the PostgreSQL adapter: `pnpm add @payloadcms/db-postgres` 2. Update `payload.config.ts`: diff --git a/package.json b/package.json index 3eaeb1f..d25cd01 100644 --- a/package.json +++ b/package.json @@ -16,17 +16,23 @@ "start": "cross-env NODE_OPTIONS=--no-deprecation next start" }, "dependencies": { + "@payloadcms/db-postgres": "^3.65.0", "@payloadcms/db-sqlite": "3.65.0", "@payloadcms/next": "3.65.0", + "@payloadcms/plugin-cloud-storage": "^3.65.0", "@payloadcms/plugin-multi-tenant": "3.65.0", "@payloadcms/richtext-lexical": "3.65.0", + "@payloadcms/storage-s3": "^3.65.0", "@payloadcms/ui": "3.65.0", "@radix-ui/react-checkbox": "^1.3.3", + "@radix-ui/react-dialog": "^1.1.15", "@radix-ui/react-label": "^2.1.8", + "@radix-ui/react-progress": "^1.1.8", "@radix-ui/react-radio-group": "^1.3.8", "@radix-ui/react-select": "^2.2.6", "@radix-ui/react-separator": "^1.1.8", "@radix-ui/react-slot": "^1.2.4", + "@radix-ui/react-tooltip": "^1.2.8", "@tailwindcss/postcss": "^4.1.17", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d7308d3..b4437d7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,27 +8,42 @@ importers: .: dependencies: + '@payloadcms/db-postgres': + specifier: ^3.65.0 + version: 3.65.0(@libsql/client@0.14.0)(payload@3.65.0(graphql@16.12.0)(typescript@5.5.2)) '@payloadcms/db-sqlite': specifier: 3.65.0 version: 3.65.0(@types/pg@8.10.2)(payload@3.65.0(graphql@16.12.0)(typescript@5.5.2))(pg@8.16.3) '@payloadcms/next': specifier: 3.65.0 version: 3.65.0(@types/react@19.0.1)(graphql@16.12.0)(monaco-editor@0.55.1)(next@15.5.6(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.77.4))(payload@3.65.0(graphql@16.12.0)(typescript@5.5.2))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.5.2) + '@payloadcms/plugin-cloud-storage': + specifier: ^3.65.0 + version: 3.65.0(@types/react@19.0.1)(monaco-editor@0.55.1)(next@15.5.6(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.77.4))(payload@3.65.0(graphql@16.12.0)(typescript@5.5.2))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.5.2) '@payloadcms/plugin-multi-tenant': specifier: 3.65.0 version: 3.65.0(@payloadcms/ui@3.65.0(@types/react@19.0.1)(monaco-editor@0.55.1)(next@15.5.6(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.77.4))(payload@3.65.0(graphql@16.12.0)(typescript@5.5.2))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.5.2))(next@15.5.6(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.77.4))(payload@3.65.0(graphql@16.12.0)(typescript@5.5.2)) '@payloadcms/richtext-lexical': specifier: 3.65.0 version: 3.65.0(@faceless-ui/modal@3.0.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(@faceless-ui/scroll-info@2.0.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(@payloadcms/next@3.65.0(@types/react@19.0.1)(graphql@16.12.0)(monaco-editor@0.55.1)(next@15.5.6(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.77.4))(payload@3.65.0(graphql@16.12.0)(typescript@5.5.2))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.5.2))(@types/react@19.0.1)(monaco-editor@0.55.1)(next@15.5.6(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.77.4))(payload@3.65.0(graphql@16.12.0)(typescript@5.5.2))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.5.2)(yjs@13.6.27) + '@payloadcms/storage-s3': + specifier: ^3.65.0 + version: 3.65.0(@types/react@19.0.1)(monaco-editor@0.55.1)(next@15.5.6(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.77.4))(payload@3.65.0(graphql@16.12.0)(typescript@5.5.2))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.5.2) '@payloadcms/ui': specifier: 3.65.0 version: 3.65.0(@types/react@19.0.1)(monaco-editor@0.55.1)(next@15.5.6(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.77.4))(payload@3.65.0(graphql@16.12.0)(typescript@5.5.2))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.5.2) '@radix-ui/react-checkbox': specifier: ^1.3.3 version: 1.3.3(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-dialog': + specifier: ^1.1.15 + version: 1.1.15(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@radix-ui/react-label': specifier: ^2.1.8 version: 2.1.8(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-progress': + specifier: ^1.1.8 + version: 1.1.8(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@radix-ui/react-radio-group': specifier: ^1.3.8 version: 1.3.8(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) @@ -41,6 +56,9 @@ importers: '@radix-ui/react-slot': specifier: ^1.2.4 version: 1.2.4(@types/react@19.0.1)(react@19.0.0) + '@radix-ui/react-tooltip': + specifier: ^1.2.8 + version: 1.2.8(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@tailwindcss/postcss': specifier: ^4.1.17 version: 4.1.17 @@ -131,6 +149,179 @@ packages: resolution: {integrity: sha512-60vepv88RwcJtSHrD6MjIL6Ta3SOYbgfnkHb+ppAVK+o9mXprRtulx7VlRl3lN3bbvysAfCS7WMVfhUYemB0IQ==} engines: {node: '>= 16'} + '@aws-crypto/crc32@5.2.0': + resolution: {integrity: sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==} + engines: {node: '>=16.0.0'} + + '@aws-crypto/crc32c@5.2.0': + resolution: {integrity: sha512-+iWb8qaHLYKrNvGRbiYRHSdKRWhto5XlZUEBwDjYNf+ly5SVYG6zEoYIdxvf5R3zyeP16w4PLBn3rH1xc74Rag==} + + '@aws-crypto/sha1-browser@5.2.0': + resolution: {integrity: sha512-OH6lveCFfcDjX4dbAvCFSYUjJZjDr/3XJ3xHtjn3Oj5b9RjojQo8npoLeA/bNwkOkrSQ0wgrHzXk4tDRxGKJeg==} + + '@aws-crypto/sha256-browser@5.2.0': + resolution: {integrity: sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==} + + '@aws-crypto/sha256-js@5.2.0': + resolution: {integrity: sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==} + engines: {node: '>=16.0.0'} + + '@aws-crypto/supports-web-crypto@5.2.0': + resolution: {integrity: sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==} + + '@aws-crypto/util@5.2.0': + resolution: {integrity: sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==} + + '@aws-sdk/client-s3@3.940.0': + resolution: {integrity: sha512-Wi4qnBT6shRRMXuuTgjMFTU5mu2KFWisgcigEMPptjPGUtJvBVi4PTGgS64qsLoUk/obqDAyOBOfEtRZ2ddC2w==} + engines: {node: '>=18.0.0'} + + '@aws-sdk/client-sso@3.940.0': + resolution: {integrity: sha512-SdqJGWVhmIURvCSgkDditHRO+ozubwZk9aCX9MK8qxyOndhobCndW1ozl3hX9psvMAo9Q4bppjuqy/GHWpjB+A==} + engines: {node: '>=18.0.0'} + + '@aws-sdk/core@3.940.0': + resolution: {integrity: sha512-KsGD2FLaX5ngJao1mHxodIVU9VYd1E8810fcYiGwO1PFHDzf5BEkp6D9IdMeQwT8Q6JLYtiiT1Y/o3UCScnGoA==} + engines: {node: '>=18.0.0'} + + '@aws-sdk/credential-provider-env@3.940.0': + resolution: {integrity: sha512-/G3l5/wbZYP2XEQiOoIkRJmlv15f1P3MSd1a0gz27lHEMrOJOGq66rF1Ca4OJLzapWt3Fy9BPrZAepoAX11kMw==} + engines: {node: '>=18.0.0'} + + '@aws-sdk/credential-provider-http@3.940.0': + resolution: {integrity: sha512-dOrc03DHElNBD6N9Okt4U0zhrG4Wix5QUBSZPr5VN8SvmjD9dkrrxOkkJaMCl/bzrW7kbQEp7LuBdbxArMmOZQ==} + engines: {node: '>=18.0.0'} + + '@aws-sdk/credential-provider-ini@3.940.0': + resolution: {integrity: sha512-gn7PJQEzb/cnInNFTOaDoCN/hOKqMejNmLof1W5VW95Qk0TPO52lH8R4RmJPnRrwFMswOWswTOpR1roKNLIrcw==} + engines: {node: '>=18.0.0'} + + '@aws-sdk/credential-provider-login@3.940.0': + resolution: {integrity: sha512-fOKC3VZkwa9T2l2VFKWRtfHQPQuISqqNl35ZhcXjWKVwRwl/o7THPMkqI4XwgT2noGa7LLYVbWMwnsgSsBqglg==} + engines: {node: '>=18.0.0'} + + '@aws-sdk/credential-provider-node@3.940.0': + resolution: {integrity: sha512-M8NFAvgvO6xZjiti5kztFiAYmSmSlG3eUfr4ZHSfXYZUA/KUdZU/D6xJyaLnU8cYRWBludb6K9XPKKVwKfqm4g==} + engines: {node: '>=18.0.0'} + + '@aws-sdk/credential-provider-process@3.940.0': + resolution: {integrity: sha512-pILBzt5/TYCqRsJb7vZlxmRIe0/T+FZPeml417EK75060ajDGnVJjHcuVdLVIeKoTKm9gmJc9l45gon6PbHyUQ==} + engines: {node: '>=18.0.0'} + + '@aws-sdk/credential-provider-sso@3.940.0': + resolution: {integrity: sha512-q6JMHIkBlDCOMnA3RAzf8cGfup+8ukhhb50fNpghMs1SNBGhanmaMbZSgLigBRsPQW7fOk2l8jnzdVLS+BB9Uw==} + engines: {node: '>=18.0.0'} + + '@aws-sdk/credential-provider-web-identity@3.940.0': + resolution: {integrity: sha512-9QLTIkDJHHaYL0nyymO41H8g3ui1yz6Y3GmAN1gYQa6plXisuFBnGAbmKVj7zNvjWaOKdF0dV3dd3AFKEDoJ/w==} + engines: {node: '>=18.0.0'} + + '@aws-sdk/lib-storage@3.940.0': + resolution: {integrity: sha512-4pHgz9tuFJNSy/qoTbW5FqXPjoR4B18jB656UsE+TP5GWd7EPx7m4F0EUwIsD3OF5+KPiiyICi8zkxOs7erfQw==} + engines: {node: '>=18.0.0'} + peerDependencies: + '@aws-sdk/client-s3': ^3.940.0 + + '@aws-sdk/middleware-bucket-endpoint@3.936.0': + resolution: {integrity: sha512-XLSVVfAorUxZh6dzF+HTOp4R1B5EQcdpGcPliWr0KUj2jukgjZEcqbBmjyMF/p9bmyQsONX80iURF1HLAlW0qg==} + engines: {node: '>=18.0.0'} + + '@aws-sdk/middleware-expect-continue@3.936.0': + resolution: {integrity: sha512-Eb4ELAC23bEQLJmUMYnPWcjD3FZIsmz2svDiXEcxRkQU9r7NRID7pM7C5NPH94wOfiCk0b2Y8rVyFXW0lGQwbA==} + engines: {node: '>=18.0.0'} + + '@aws-sdk/middleware-flexible-checksums@3.940.0': + resolution: {integrity: sha512-WdsxDAVj5qaa5ApAP+JbpCOMHFGSmzjs2Y2OBSbWPeR9Ew7t/Okj+kUub94QJPsgzhvU1/cqNejhsw5VxeFKSQ==} + engines: {node: '>=18.0.0'} + + '@aws-sdk/middleware-host-header@3.936.0': + resolution: {integrity: sha512-tAaObaAnsP1XnLGndfkGWFuzrJYuk9W0b/nLvol66t8FZExIAf/WdkT2NNAWOYxljVs++oHnyHBCxIlaHrzSiw==} + engines: {node: '>=18.0.0'} + + '@aws-sdk/middleware-location-constraint@3.936.0': + resolution: {integrity: sha512-SCMPenDtQMd9o5da9JzkHz838w3327iqXk3cbNnXWqnNRx6unyW8FL0DZ84gIY12kAyVHz5WEqlWuekc15ehfw==} + engines: {node: '>=18.0.0'} + + '@aws-sdk/middleware-logger@3.936.0': + resolution: {integrity: sha512-aPSJ12d3a3Ea5nyEnLbijCaaYJT2QjQ9iW+zGh5QcZYXmOGWbKVyPSxmVOboZQG+c1M8t6d2O7tqrwzIq8L8qw==} + engines: {node: '>=18.0.0'} + + '@aws-sdk/middleware-recursion-detection@3.936.0': + resolution: {integrity: sha512-l4aGbHpXM45YNgXggIux1HgsCVAvvBoqHPkqLnqMl9QVapfuSTjJHfDYDsx1Xxct6/m7qSMUzanBALhiaGO2fA==} + engines: {node: '>=18.0.0'} + + '@aws-sdk/middleware-sdk-s3@3.940.0': + resolution: {integrity: sha512-JYkLjgS1wLoKHJ40G63+afM1ehmsPsjcmrHirKh8+kSCx4ip7+nL1e/twV4Zicxr8RJi9Y0Ahq5mDvneilDDKQ==} + engines: {node: '>=18.0.0'} + + '@aws-sdk/middleware-ssec@3.936.0': + resolution: {integrity: sha512-/GLC9lZdVp05ozRik5KsuODR/N7j+W+2TbfdFL3iS+7un+gnP6hC8RDOZd6WhpZp7drXQ9guKiTAxkZQwzS8DA==} + engines: {node: '>=18.0.0'} + + '@aws-sdk/middleware-user-agent@3.940.0': + resolution: {integrity: sha512-nJbLrUj6fY+l2W2rIB9P4Qvpiy0tnTdg/dmixRxrU1z3e8wBdspJlyE+AZN4fuVbeL6rrRrO/zxQC1bB3cw5IA==} + engines: {node: '>=18.0.0'} + + '@aws-sdk/nested-clients@3.940.0': + resolution: {integrity: sha512-x0mdv6DkjXqXEcQj3URbCltEzW6hoy/1uIL+i8gExP6YKrnhiZ7SzuB4gPls2UOpK5UqLiqXjhRLfBb1C9i4Dw==} + engines: {node: '>=18.0.0'} + + '@aws-sdk/region-config-resolver@3.936.0': + resolution: {integrity: sha512-wOKhzzWsshXGduxO4pqSiNyL9oUtk4BEvjWm9aaq6Hmfdoydq6v6t0rAGHWPjFwy9z2haovGRi3C8IxdMB4muw==} + engines: {node: '>=18.0.0'} + + '@aws-sdk/s3-request-presigner@3.940.0': + resolution: {integrity: sha512-TgTUDM2H7revReDfkVwVtIqxV3K0cJLdyuLDIkefVHRUNKwU1Vd5FB2TaFrs6STO0kx5pTckDCOLh0iy7nW5WQ==} + engines: {node: '>=18.0.0'} + + '@aws-sdk/signature-v4-multi-region@3.940.0': + resolution: {integrity: sha512-ugHZEoktD/bG6mdgmhzLDjMP2VrYRAUPRPF1DpCyiZexkH7DCU7XrSJyXMvkcf0DHV+URk0q2sLf/oqn1D2uYw==} + engines: {node: '>=18.0.0'} + + '@aws-sdk/token-providers@3.940.0': + resolution: {integrity: sha512-k5qbRe/ZFjW9oWEdzLIa2twRVIEx7p/9rutofyrRysrtEnYh3HAWCngAnwbgKMoiwa806UzcTRx0TjyEpnKcCg==} + engines: {node: '>=18.0.0'} + + '@aws-sdk/types@3.936.0': + resolution: {integrity: sha512-uz0/VlMd2pP5MepdrHizd+T+OKfyK4r3OA9JI+L/lPKg0YFQosdJNCKisr6o70E3dh8iMpFYxF1UN/4uZsyARg==} + engines: {node: '>=18.0.0'} + + '@aws-sdk/util-arn-parser@3.893.0': + resolution: {integrity: sha512-u8H4f2Zsi19DGnwj5FSZzDMhytYF/bCh37vAtBsn3cNDL3YG578X5oc+wSX54pM3tOxS+NY7tvOAo52SW7koUA==} + engines: {node: '>=18.0.0'} + + '@aws-sdk/util-endpoints@3.936.0': + resolution: {integrity: sha512-0Zx3Ntdpu+z9Wlm7JKUBOzS9EunwKAb4KdGUQQxDqh5Lc3ta5uBoub+FgmVuzwnmBu9U1Os8UuwVTH0Lgu+P5w==} + engines: {node: '>=18.0.0'} + + '@aws-sdk/util-format-url@3.936.0': + resolution: {integrity: sha512-MS5eSEtDUFIAMHrJaMERiHAvDPdfxc/T869ZjDNFAIiZhyc037REw0aoTNeimNXDNy2txRNZJaAUn/kE4RwN+g==} + engines: {node: '>=18.0.0'} + + '@aws-sdk/util-locate-window@3.893.0': + resolution: {integrity: sha512-T89pFfgat6c8nMmpI8eKjBcDcgJq36+m9oiXbcUzeU55MP9ZuGgBomGjGnHaEyF36jenW9gmg3NfZDm0AO2XPg==} + engines: {node: '>=18.0.0'} + + '@aws-sdk/util-user-agent-browser@3.936.0': + resolution: {integrity: sha512-eZ/XF6NxMtu+iCma58GRNRxSq4lHo6zHQLOZRIeL/ghqYJirqHdenMOwrzPettj60KWlv827RVebP9oNVrwZbw==} + + '@aws-sdk/util-user-agent-node@3.940.0': + resolution: {integrity: sha512-dlD/F+L/jN26I8Zg5x0oDGJiA+/WEQmnSE27fi5ydvYnpfQLwThtQo9SsNS47XSR/SOULaaoC9qx929rZuo74A==} + engines: {node: '>=18.0.0'} + peerDependencies: + aws-crt: '>=1.0.0' + peerDependenciesMeta: + aws-crt: + optional: true + + '@aws-sdk/xml-builder@3.930.0': + resolution: {integrity: sha512-YIfkD17GocxdmlUVc3ia52QhcWuRIUJonbF8A2CYfcWNV3HzvAqpcPeC0bYUhkK+8e8YO1ARnLKZQE0TlwzorA==} + engines: {node: '>=18.0.0'} + + '@aws/lambda-invoke-store@0.2.1': + resolution: {integrity: sha512-sIyFcoPZkTtNu9xFeEoynMef3bPJIAbOfUh+ueYcfhVl6xm2VRtMcMclSxmZCMnHHd4hlYKJeq/aggmBEWynww==} + engines: {node: '>=18.0.0'} + '@babel/code-frame@7.27.1': resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} engines: {node: '>=6.9.0'} @@ -1224,6 +1415,11 @@ packages: resolution: {integrity: sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==} engines: {node: '>=12.4.0'} + '@payloadcms/db-postgres@3.65.0': + resolution: {integrity: sha512-VtYnNOirrbxzw58PoASuxiFTpJKZIGjGUBpSSVoDZu2mNE0AL3IpkLolzElt6xfTdjC4F88fR9puph7QaJK9bA==} + peerDependencies: + payload: 3.65.0 + '@payloadcms/db-sqlite@3.65.0': resolution: {integrity: sha512-pBBnExJecItoe5wp2rLKYwEkQC5cCZBSJmb8RO2WbfgvXxayyy8/y8wUsqDIz75vxqoGn1RHJ7Cj4BzAFgdKsQ==} peerDependencies: @@ -1255,6 +1451,13 @@ packages: next: ^15.2.3 payload: 3.65.0 + '@payloadcms/plugin-cloud-storage@3.65.0': + resolution: {integrity: sha512-fHOlBQY1gyFxcejmoFqYM3ztIDKs8toVqJTfQDQrPgd4zq0+GLFGqkn0VcVEDq+Q+XWaYV+fJmpy2g73/ZnoVg==} + peerDependencies: + payload: 3.65.0 + react: ^19.0.0 || ^19.0.0-rc-65a56d0e-20241020 + react-dom: ^19.0.0 || ^19.0.0-rc-65a56d0e-20241020 + '@payloadcms/plugin-multi-tenant@3.65.0': resolution: {integrity: sha512-r4ZN6FMuwuzzhq7NmORT72E8vIT87tjr2TK4NxzIkb2MjkMD43tNlsq2+7s6AFxopwpHdbhxY/ggPwelbooSMw==} peerDependencies: @@ -1273,6 +1476,12 @@ packages: react: ^19.0.0 || ^19.0.0-rc-65a56d0e-20241020 react-dom: ^19.0.0 || ^19.0.0-rc-65a56d0e-20241020 + '@payloadcms/storage-s3@3.65.0': + resolution: {integrity: sha512-46K4w5LOHM6K8rr6e/T/aEZiV1CTPXPfJq1cRXn6F+H4YhIth/ot3yO+SVwVegm9u7LZC1F5D40GSXqpgHj0Mg==} + engines: {node: ^18.20.2 || >=20.9.0} + peerDependencies: + payload: 3.65.0 + '@payloadcms/translations@3.65.0': resolution: {integrity: sha512-wjKY0jHdudLeMZwSfJi2yQxyeRV6AkOgV9k2NuiltvwglYjBXM35XP15Bq9UYYS5UJ9nhajK1WZdHRn5qJ0gEA==} @@ -1351,6 +1560,28 @@ packages: '@types/react': optional: true + '@radix-ui/react-context@1.1.3': + resolution: {integrity: sha512-ieIFACdMpYfMEjF0rEf5KLvfVyIkOz6PDGyNnP+u+4xQ6jny3VCgA4OgXOwNx2aUkxn8zx9fiVcM8CfFYv9Lxw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-dialog@1.1.15': + resolution: {integrity: sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw==} + 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-direction@1.1.1': resolution: {integrity: sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==} peerDependencies: @@ -1482,6 +1713,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-progress@1.1.8': + resolution: {integrity: sha512-+gISHcSPUJ7ktBy9RnTqbdKW78bcGke3t6taawyZ71pio1JewwGSJizycs7rLhGTvMJYCQB1DBK4KQsxs7U8dA==} + 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-radio-group@1.3.8': resolution: {integrity: sha512-VBKYIYImA5zsxACdisNQ3BjCBfmbGH3kQlnFVqlWU4tXwjy7cGX8ta80BcrO+WJXIn5iBylEH3K6ZTlee//lgQ==} peerDependencies: @@ -1552,6 +1796,19 @@ packages: '@types/react': optional: true + '@radix-ui/react-tooltip@1.2.8': + resolution: {integrity: sha512-tY7sVt1yL9ozIxvmbtN5qtmH2krXcBCfjEiCgKGLqunJHvgvZG2Pcl2oQ3kbcZARb1BGEHdkLzcYGO8ynVlieg==} + 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-use-callback-ref@1.1.1': resolution: {integrity: sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==} peerDependencies: @@ -1646,6 +1903,222 @@ packages: '@rushstack/eslint-patch@1.15.0': resolution: {integrity: sha512-ojSshQPKwVvSMR8yT2L/QtUkV5SXi/IfDiJ4/8d6UbTPjiHVmxZzUAzGD8Tzks1b9+qQkZa0isUOvYObedITaw==} + '@smithy/abort-controller@4.2.5': + resolution: {integrity: sha512-j7HwVkBw68YW8UmFRcjZOmssE77Rvk0GWAIN1oFBhsaovQmZWYCIcGa9/pwRB0ExI8Sk9MWNALTjftjHZea7VA==} + engines: {node: '>=18.0.0'} + + '@smithy/chunked-blob-reader-native@4.2.1': + resolution: {integrity: sha512-lX9Ay+6LisTfpLid2zZtIhSEjHMZoAR5hHCR4H7tBz/Zkfr5ea8RcQ7Tk4mi0P76p4cN+Btz16Ffno7YHpKXnQ==} + engines: {node: '>=18.0.0'} + + '@smithy/chunked-blob-reader@5.2.0': + resolution: {integrity: sha512-WmU0TnhEAJLWvfSeMxBNe5xtbselEO8+4wG0NtZeL8oR21WgH1xiO37El+/Y+H/Ie4SCwBy3MxYWmOYaGgZueA==} + engines: {node: '>=18.0.0'} + + '@smithy/config-resolver@4.4.3': + resolution: {integrity: sha512-ezHLe1tKLUxDJo2LHtDuEDyWXolw8WGOR92qb4bQdWq/zKenO5BvctZGrVJBK08zjezSk7bmbKFOXIVyChvDLw==} + engines: {node: '>=18.0.0'} + + '@smithy/core@3.18.6': + resolution: {integrity: sha512-8Q/ugWqfDUEU1Exw71+DoOzlONJ2Cn9QA8VeeDzLLjzO/qruh9UKFzbszy4jXcIYgGofxYiT0t1TT6+CT/GupQ==} + engines: {node: '>=18.0.0'} + + '@smithy/credential-provider-imds@4.2.5': + resolution: {integrity: sha512-BZwotjoZWn9+36nimwm/OLIcVe+KYRwzMjfhd4QT7QxPm9WY0HiOV8t/Wlh+HVUif0SBVV7ksq8//hPaBC/okQ==} + engines: {node: '>=18.0.0'} + + '@smithy/eventstream-codec@4.2.5': + resolution: {integrity: sha512-Ogt4Zi9hEbIP17oQMd68qYOHUzmH47UkK7q7Gl55iIm9oKt27MUGrC5JfpMroeHjdkOliOA4Qt3NQ1xMq/nrlA==} + engines: {node: '>=18.0.0'} + + '@smithy/eventstream-serde-browser@4.2.5': + resolution: {integrity: sha512-HohfmCQZjppVnKX2PnXlf47CW3j92Ki6T/vkAT2DhBR47e89pen3s4fIa7otGTtrVxmj7q+IhH0RnC5kpR8wtw==} + engines: {node: '>=18.0.0'} + + '@smithy/eventstream-serde-config-resolver@4.3.5': + resolution: {integrity: sha512-ibjQjM7wEXtECiT6my1xfiMH9IcEczMOS6xiCQXoUIYSj5b1CpBbJ3VYbdwDy8Vcg5JHN7eFpOCGk8nyZAltNQ==} + engines: {node: '>=18.0.0'} + + '@smithy/eventstream-serde-node@4.2.5': + resolution: {integrity: sha512-+elOuaYx6F2H6x1/5BQP5ugv12nfJl66GhxON8+dWVUEDJ9jah/A0tayVdkLRP0AeSac0inYkDz5qBFKfVp2Gg==} + engines: {node: '>=18.0.0'} + + '@smithy/eventstream-serde-universal@4.2.5': + resolution: {integrity: sha512-G9WSqbST45bmIFaeNuP/EnC19Rhp54CcVdX9PDL1zyEB514WsDVXhlyihKlGXnRycmHNmVv88Bvvt4EYxWef/Q==} + engines: {node: '>=18.0.0'} + + '@smithy/fetch-http-handler@5.3.6': + resolution: {integrity: sha512-3+RG3EA6BBJ/ofZUeTFJA7mHfSYrZtQIrDP9dI8Lf7X6Jbos2jptuLrAAteDiFVrmbEmLSuRG/bUKzfAXk7dhg==} + engines: {node: '>=18.0.0'} + + '@smithy/hash-blob-browser@4.2.6': + resolution: {integrity: sha512-8P//tA8DVPk+3XURk2rwcKgYwFvwGwmJH/wJqQiSKwXZtf/LiZK+hbUZmPj/9KzM+OVSwe4o85KTp5x9DUZTjw==} + engines: {node: '>=18.0.0'} + + '@smithy/hash-node@4.2.5': + resolution: {integrity: sha512-DpYX914YOfA3UDT9CN1BM787PcHfWRBB43fFGCYrZFUH0Jv+5t8yYl+Pd5PW4+QzoGEDvn5d5QIO4j2HyYZQSA==} + engines: {node: '>=18.0.0'} + + '@smithy/hash-stream-node@4.2.5': + resolution: {integrity: sha512-6+do24VnEyvWcGdHXomlpd0m8bfZePpUKBy7m311n+JuRwug8J4dCanJdTymx//8mi0nlkflZBvJe+dEO/O12Q==} + engines: {node: '>=18.0.0'} + + '@smithy/invalid-dependency@4.2.5': + resolution: {integrity: sha512-2L2erASEro1WC5nV+plwIMxrTXpvpfzl4e+Nre6vBVRR2HKeGGcvpJyyL3/PpiSg+cJG2KpTmZmq934Olb6e5A==} + engines: {node: '>=18.0.0'} + + '@smithy/is-array-buffer@2.2.0': + resolution: {integrity: sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==} + engines: {node: '>=14.0.0'} + + '@smithy/is-array-buffer@4.2.0': + resolution: {integrity: sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ==} + engines: {node: '>=18.0.0'} + + '@smithy/md5-js@4.2.5': + resolution: {integrity: sha512-Bt6jpSTMWfjCtC0s79gZ/WZ1w90grfmopVOWqkI2ovhjpD5Q2XRXuecIPB9689L2+cCySMbaXDhBPU56FKNDNg==} + engines: {node: '>=18.0.0'} + + '@smithy/middleware-content-length@4.2.5': + resolution: {integrity: sha512-Y/RabVa5vbl5FuHYV2vUCwvh/dqzrEY/K2yWPSqvhFUwIY0atLqO4TienjBXakoy4zrKAMCZwg+YEqmH7jaN7A==} + engines: {node: '>=18.0.0'} + + '@smithy/middleware-endpoint@4.3.13': + resolution: {integrity: sha512-X4za1qCdyx1hEVVXuAWlZuK6wzLDv1uw1OY9VtaYy1lULl661+frY7FeuHdYdl7qAARUxH2yvNExU2/SmRFfcg==} + engines: {node: '>=18.0.0'} + + '@smithy/middleware-retry@4.4.13': + resolution: {integrity: sha512-RzIDF9OrSviXX7MQeKOm8r/372KTyY8Jmp6HNKOOYlrguHADuM3ED/f4aCyNhZZFLG55lv5beBin7nL0Nzy1Dw==} + engines: {node: '>=18.0.0'} + + '@smithy/middleware-serde@4.2.6': + resolution: {integrity: sha512-VkLoE/z7e2g8pirwisLz8XJWedUSY8my/qrp81VmAdyrhi94T+riBfwP+AOEEFR9rFTSonC/5D2eWNmFabHyGQ==} + engines: {node: '>=18.0.0'} + + '@smithy/middleware-stack@4.2.5': + resolution: {integrity: sha512-bYrutc+neOyWxtZdbB2USbQttZN0mXaOyYLIsaTbJhFsfpXyGWUxJpEuO1rJ8IIJm2qH4+xJT0mxUSsEDTYwdQ==} + engines: {node: '>=18.0.0'} + + '@smithy/node-config-provider@4.3.5': + resolution: {integrity: sha512-UTurh1C4qkVCtqggI36DGbLB2Kv8UlcFdMXDcWMbqVY2uRg0XmT9Pb4Vj6oSQ34eizO1fvR0RnFV4Axw4IrrAg==} + engines: {node: '>=18.0.0'} + + '@smithy/node-http-handler@4.4.5': + resolution: {integrity: sha512-CMnzM9R2WqlqXQGtIlsHMEZfXKJVTIrqCNoSd/QpAyp+Dw0a1Vps13l6ma1fH8g7zSPNsA59B/kWgeylFuA/lw==} + engines: {node: '>=18.0.0'} + + '@smithy/property-provider@4.2.5': + resolution: {integrity: sha512-8iLN1XSE1rl4MuxvQ+5OSk/Zb5El7NJZ1td6Tn+8dQQHIjp59Lwl6bd0+nzw6SKm2wSSriH2v/I9LPzUic7EOg==} + engines: {node: '>=18.0.0'} + + '@smithy/protocol-http@5.3.5': + resolution: {integrity: sha512-RlaL+sA0LNMp03bf7XPbFmT5gN+w3besXSWMkA8rcmxLSVfiEXElQi4O2IWwPfxzcHkxqrwBFMbngB8yx/RvaQ==} + engines: {node: '>=18.0.0'} + + '@smithy/querystring-builder@4.2.5': + resolution: {integrity: sha512-y98otMI1saoajeik2kLfGyRp11e5U/iJYH/wLCh3aTV/XutbGT9nziKGkgCaMD1ghK7p6htHMm6b6scl9JRUWg==} + engines: {node: '>=18.0.0'} + + '@smithy/querystring-parser@4.2.5': + resolution: {integrity: sha512-031WCTdPYgiQRYNPXznHXof2YM0GwL6SeaSyTH/P72M1Vz73TvCNH2Nq8Iu2IEPq9QP2yx0/nrw5YmSeAi/AjQ==} + engines: {node: '>=18.0.0'} + + '@smithy/service-error-classification@4.2.5': + resolution: {integrity: sha512-8fEvK+WPE3wUAcDvqDQG1Vk3ANLR8Px979te96m84CbKAjBVf25rPYSzb4xU4hlTyho7VhOGnh5i62D/JVF0JQ==} + engines: {node: '>=18.0.0'} + + '@smithy/shared-ini-file-loader@4.4.0': + resolution: {integrity: sha512-5WmZ5+kJgJDjwXXIzr1vDTG+RhF9wzSODQBfkrQ2VVkYALKGvZX1lgVSxEkgicSAFnFhPj5rudJV0zoinqS0bA==} + engines: {node: '>=18.0.0'} + + '@smithy/signature-v4@5.3.5': + resolution: {integrity: sha512-xSUfMu1FT7ccfSXkoLl/QRQBi2rOvi3tiBZU2Tdy3I6cgvZ6SEi9QNey+lqps/sJRnogIS+lq+B1gxxbra2a/w==} + engines: {node: '>=18.0.0'} + + '@smithy/smithy-client@4.9.9': + resolution: {integrity: sha512-SUnZJMMo5yCmgjopJbiNeo1vlr8KvdnEfIHV9rlD77QuOGdRotIVBcOrBuMr+sI9zrnhtDtLP054bZVbpZpiQA==} + engines: {node: '>=18.0.0'} + + '@smithy/types@4.9.0': + resolution: {integrity: sha512-MvUbdnXDTwykR8cB1WZvNNwqoWVaTRA0RLlLmf/cIFNMM2cKWz01X4Ly6SMC4Kks30r8tT3Cty0jmeWfiuyHTA==} + engines: {node: '>=18.0.0'} + + '@smithy/url-parser@4.2.5': + resolution: {integrity: sha512-VaxMGsilqFnK1CeBX+LXnSuaMx4sTL/6znSZh2829txWieazdVxr54HmiyTsIbpOTLcf5nYpq9lpzmwRdxj6rQ==} + engines: {node: '>=18.0.0'} + + '@smithy/util-base64@4.3.0': + resolution: {integrity: sha512-GkXZ59JfyxsIwNTWFnjmFEI8kZpRNIBfxKjv09+nkAWPt/4aGaEWMM04m4sxgNVWkbt2MdSvE3KF/PfX4nFedQ==} + engines: {node: '>=18.0.0'} + + '@smithy/util-body-length-browser@4.2.0': + resolution: {integrity: sha512-Fkoh/I76szMKJnBXWPdFkQJl2r9SjPt3cMzLdOB6eJ4Pnpas8hVoWPYemX/peO0yrrvldgCUVJqOAjUrOLjbxg==} + engines: {node: '>=18.0.0'} + + '@smithy/util-body-length-node@4.2.1': + resolution: {integrity: sha512-h53dz/pISVrVrfxV1iqXlx5pRg3V2YWFcSQyPyXZRrZoZj4R4DeWRDo1a7dd3CPTcFi3kE+98tuNyD2axyZReA==} + engines: {node: '>=18.0.0'} + + '@smithy/util-buffer-from@2.2.0': + resolution: {integrity: sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==} + engines: {node: '>=14.0.0'} + + '@smithy/util-buffer-from@4.2.0': + resolution: {integrity: sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew==} + engines: {node: '>=18.0.0'} + + '@smithy/util-config-provider@4.2.0': + resolution: {integrity: sha512-YEjpl6XJ36FTKmD+kRJJWYvrHeUvm5ykaUS5xK+6oXffQPHeEM4/nXlZPe+Wu0lsgRUcNZiliYNh/y7q9c2y6Q==} + engines: {node: '>=18.0.0'} + + '@smithy/util-defaults-mode-browser@4.3.12': + resolution: {integrity: sha512-TKc6FnOxFULKxLgTNHYjcFqdOYzXVPFFVm5JhI30F3RdhT7nYOtOsjgaOwfDRmA/3U66O9KaBQ3UHoXwayRhAg==} + engines: {node: '>=18.0.0'} + + '@smithy/util-defaults-mode-node@4.2.15': + resolution: {integrity: sha512-94NqfQVo+vGc5gsQ9SROZqOvBkGNMQu6pjXbnn8aQvBUhc31kx49gxlkBEqgmaZQHUUfdRUin5gK/HlHKmbAwg==} + engines: {node: '>=18.0.0'} + + '@smithy/util-endpoints@3.2.5': + resolution: {integrity: sha512-3O63AAWu2cSNQZp+ayl9I3NapW1p1rR5mlVHcF6hAB1dPZUQFfRPYtplWX/3xrzWthPGj5FqB12taJJCfH6s8A==} + engines: {node: '>=18.0.0'} + + '@smithy/util-hex-encoding@4.2.0': + resolution: {integrity: sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw==} + engines: {node: '>=18.0.0'} + + '@smithy/util-middleware@4.2.5': + resolution: {integrity: sha512-6Y3+rvBF7+PZOc40ybeZMcGln6xJGVeY60E7jy9Mv5iKpMJpHgRE6dKy9ScsVxvfAYuEX4Q9a65DQX90KaQ3bA==} + engines: {node: '>=18.0.0'} + + '@smithy/util-retry@4.2.5': + resolution: {integrity: sha512-GBj3+EZBbN4NAqJ/7pAhsXdfzdlznOh8PydUijy6FpNIMnHPSMO2/rP4HKu+UFeikJxShERk528oy7GT79YiJg==} + engines: {node: '>=18.0.0'} + + '@smithy/util-stream@4.5.6': + resolution: {integrity: sha512-qWw/UM59TiaFrPevefOZ8CNBKbYEP6wBAIlLqxn3VAIo9rgnTNc4ASbVrqDmhuwI87usnjhdQrxodzAGFFzbRQ==} + engines: {node: '>=18.0.0'} + + '@smithy/util-uri-escape@4.2.0': + resolution: {integrity: sha512-igZpCKV9+E/Mzrpq6YacdTQ0qTiLm85gD6N/IrmyDvQFA4UnU3d5g3m8tMT/6zG/vVkWSU+VxeUyGonL62DuxA==} + engines: {node: '>=18.0.0'} + + '@smithy/util-utf8@2.3.0': + resolution: {integrity: sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==} + engines: {node: '>=14.0.0'} + + '@smithy/util-utf8@4.2.0': + resolution: {integrity: sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw==} + engines: {node: '>=18.0.0'} + + '@smithy/util-waiter@4.2.5': + resolution: {integrity: sha512-Dbun99A3InifQdIrsXZ+QLcC0PGBPAdrl4cj1mTgJvyc9N2zf7QSxg8TBkzsCmGJdE3TLbO9ycwpY0EkWahQ/g==} + engines: {node: '>=18.0.0'} + + '@smithy/uuid@1.1.0': + resolution: {integrity: sha512-4aUIteuyxtBUhVdiQqcDhKFitwfd9hqoSDYY2KRXiWtgoWJ9Bmise+KfEPDiVHWeJepvF8xJO9/9+WDIciMFFw==} + engines: {node: '>=18.0.0'} + '@swc/core-darwin-arm64@1.15.3': resolution: {integrity: sha512-AXfeQn0CvcQ4cndlIshETx6jrAM45oeUrK8YeEY6oUZU/qzz0Id0CyvlEywxkWVC81Ajpd8TQQ1fW5yx6zQWkQ==} engines: {node: '>=10'} @@ -2264,6 +2737,9 @@ packages: body-scroll-lock@4.0.0-beta.0: resolution: {integrity: sha512-a7tP5+0Mw3YlUJcGAKUqIBkYYGlYxk2fnCasq/FUph1hadxlTRjF+gAcZksxANnaMnALjxEddmSi/H3OR8ugcQ==} + bowser@2.13.1: + resolution: {integrity: sha512-OHawaAbjwx6rqICCKgSG0SAnT05bzd7ppyKLVUITZpANBaaMFBAsaNkto3LoQ31tyFP5kNujE8Cdx85G9VzOkw==} + brace-expansion@1.1.12: resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} @@ -2280,6 +2756,9 @@ packages: buffer-from@1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + buffer@5.6.0: + resolution: {integrity: sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw==} + buffer@5.7.1: resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} @@ -2492,6 +2971,10 @@ packages: resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} engines: {node: '>=6'} + detect-file@1.0.0: + resolution: {integrity: sha512-DtCOLG98P007x7wiiOmfI0fi3eIKyWiLTGJ2MDnVi/E04lWGbf+JzrRHMm0rgIIZJGtHpKpbVgLWHrv8xXpc3Q==} + engines: {node: '>=0.10.0'} + detect-libc@2.0.2: resolution: {integrity: sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==} engines: {node: '>=8'} @@ -2964,10 +3447,18 @@ packages: events-universal@1.0.1: resolution: {integrity: sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==} + events@3.3.0: + resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} + engines: {node: '>=0.8.x'} + expand-template@2.0.3: resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==} engines: {node: '>=6'} + expand-tilde@2.0.2: + resolution: {integrity: sha512-A5EmesHW6rfnZ9ysHQjPdJRni0SRar0tjtG5MNtm9n5TUvsYU8oozprtRD4AqHxcZWWlVuAmQo2nWKfN9oyjTw==} + engines: {node: '>=0.10.0'} + fast-copy@3.0.2: resolution: {integrity: sha512-dl0O9Vhju8IrcLndv2eU4ldt1ftXMqqfgN4H1cpmGV7P6jeB9FwpN9a2c8DPGE1Ys88rNUJVYDHq73CGAGOPfQ==} @@ -2997,6 +3488,10 @@ packages: fast-uri@3.1.0: resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==} + fast-xml-parser@5.2.5: + resolution: {integrity: sha512-pfX9uG9Ki0yekDHx2SiuRIyFdyAr1kMIMitPvb0YBo8SUfKvia7w7FIyd/l6av85pFYRhZscS75MwMnbvY+hcQ==} + hasBin: true + fastq@1.19.1: resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==} @@ -3029,6 +3524,9 @@ packages: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} + find-node-modules@2.1.3: + resolution: {integrity: sha512-UC2I2+nx1ZuOBclWVNdcnbDR5dlrOdVb7xNjmT/lHE+LsgztWks3dG7boJ37yTS/venXw84B/mAW9uHVoC5QRg==} + find-root@1.1.0: resolution: {integrity: sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==} @@ -3036,6 +3534,10 @@ packages: resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} engines: {node: '>=10'} + findup-sync@4.0.0: + resolution: {integrity: sha512-6jvvn/12IC4quLBL1KNokxC7wWTvYncaVUYSoxWw7YykPLuRrnv4qdHcSOywOI5RpkOVGeQRtWM8/q+G6W6qfQ==} + engines: {node: '>= 8'} + flat-cache@3.2.0: resolution: {integrity: sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==} engines: {node: ^10.12.0 || >=12.0.0} @@ -3120,6 +3622,14 @@ packages: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} deprecated: Glob versions prior to v9 are no longer supported + global-modules@1.0.0: + resolution: {integrity: sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==} + engines: {node: '>=0.10.0'} + + global-prefix@1.0.2: + resolution: {integrity: sha512-5lsx1NUDHtSjfg0eHlmYvZKv8/nVqX4ckFbM+FrGcQ+04KWcWFo9P5MxPZYSzUvyzmdTbI7Eix8Q4IbELDqzKg==} + engines: {node: '>=0.10.0'} + globals@13.24.0: resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==} engines: {node: '>=8'} @@ -3198,6 +3708,10 @@ packages: hoist-non-react-statics@3.3.2: resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==} + homedir-polyfill@1.0.3: + resolution: {integrity: sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==} + engines: {node: '>=0.10.0'} + http-status@2.1.0: resolution: {integrity: sha512-O5kPr7AW7wYd/BBiOezTwnVAnmSNFY+J7hlZD2X5IOxVBetjcHAiTXhzj0gMrnojQlwy+UT1/Y3H3vJ3UlmvLA==} engines: {node: '>= 0.4.0'} @@ -3379,6 +3893,10 @@ packages: resolution: {integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==} engines: {node: '>= 0.4'} + is-windows@1.0.2: + resolution: {integrity: sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==} + engines: {node: '>=0.10.0'} + isarray@2.0.5: resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} @@ -3617,6 +4135,9 @@ packages: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} + merge@2.1.1: + resolution: {integrity: sha512-jz+Cfrg9GWOZbQAnDQ4hlVnQky+341Yk5ru8bZSe6sIDTCIg8n9i/u7hSQGSVOF3C7lH6mGtqjkiT9G4wFLL0w==} + micromark-core-commonmark@2.0.3: resolution: {integrity: sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==} @@ -3848,6 +4369,10 @@ packages: resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} engines: {node: '>=8'} + parse-passwd@1.0.0: + resolution: {integrity: sha512-1Y1A//QUXEZK7YKz+rD9WydcE1+EuPr6ZBgKecAB8tmoW6UFv0NREVJe1p+jRxtThkcbbKkfwIbWJe/IeE6m2Q==} + engines: {node: '>=0.10.0'} + path-exists@4.0.0: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} @@ -4047,6 +4572,10 @@ packages: quick-format-unescaped@4.0.4: resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==} + range-parser@1.2.1: + resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} + engines: {node: '>= 0.6'} + rc@1.2.8: resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} hasBin: true @@ -4163,6 +4692,10 @@ packages: resolution: {integrity: sha512-L9jEkOi3ASd9PYit2cwRfyppc9NoABujTP8/5gFcbERmo5jUoAKovIC3fsF17pkTnGsrByysqX+Kxd2OTNI1ww==} engines: {node: '>=0.10.5'} + resolve-dir@1.0.1: + resolution: {integrity: sha512-R7uiTjECzvOsWSfdM0QKFNBVFcK27aHOUwdvK53BcW8zqnGdYp0Fbj82cy54+2A4P2tFM22J5kRfe1R+lM/1yg==} + engines: {node: '>=0.10.0'} + resolve-from@4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} @@ -4340,6 +4873,9 @@ packages: resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==} engines: {node: '>= 0.4'} + stream-browserify@3.0.0: + resolution: {integrity: sha512-H73RAHsVBapbim0tU2JwwOiXUj+fikfiaoYAKHF3VJfA0pe2BCzkhAHBlLG6REzE+2WNZcxOXjK7lkso+9euLA==} + streamsearch@1.1.0: resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==} engines: {node: '>=10.0.0'} @@ -4399,6 +4935,9 @@ packages: resolution: {integrity: sha512-1tB5mhVo7U+ETBKNf92xT4hrQa3pm0MZ0PQvuDnWgAAGHDsfp4lPSpiS6psrSiet87wyGPh9ft6wmhOMQ0hDiw==} engines: {node: '>=14.16'} + strnum@2.1.1: + resolution: {integrity: sha512-7ZvoFTiCnGxBtDqJ//Cu6fWtZtc7Y3x+QOirG15wztbdngGSkht27o2pyGWrVy0b4WAy3jbKmnoK6g5VlVNUUw==} + strtok3@8.1.0: resolution: {integrity: sha512-ExzDvHYPj6F6QkSNe/JxSlBxTh3OrI6wrAIz53ulxo1c4hBJ1bT9C/JrAthEKHWG9riVH3Xzg7B03Oxty6S2Lw==} engines: {node: '>=16'} @@ -4679,6 +5218,10 @@ packages: resolution: {integrity: sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==} engines: {node: '>= 0.4'} + which@1.3.1: + resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==} + hasBin: true + which@2.0.2: resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} engines: {node: '>= 8'} @@ -4737,6 +5280,514 @@ snapshots: '@types/json-schema': 7.0.15 js-yaml: 4.1.1 + '@aws-crypto/crc32@5.2.0': + dependencies: + '@aws-crypto/util': 5.2.0 + '@aws-sdk/types': 3.936.0 + tslib: 2.8.1 + + '@aws-crypto/crc32c@5.2.0': + dependencies: + '@aws-crypto/util': 5.2.0 + '@aws-sdk/types': 3.936.0 + tslib: 2.8.1 + + '@aws-crypto/sha1-browser@5.2.0': + dependencies: + '@aws-crypto/supports-web-crypto': 5.2.0 + '@aws-crypto/util': 5.2.0 + '@aws-sdk/types': 3.936.0 + '@aws-sdk/util-locate-window': 3.893.0 + '@smithy/util-utf8': 2.3.0 + tslib: 2.8.1 + + '@aws-crypto/sha256-browser@5.2.0': + dependencies: + '@aws-crypto/sha256-js': 5.2.0 + '@aws-crypto/supports-web-crypto': 5.2.0 + '@aws-crypto/util': 5.2.0 + '@aws-sdk/types': 3.936.0 + '@aws-sdk/util-locate-window': 3.893.0 + '@smithy/util-utf8': 2.3.0 + tslib: 2.8.1 + + '@aws-crypto/sha256-js@5.2.0': + dependencies: + '@aws-crypto/util': 5.2.0 + '@aws-sdk/types': 3.936.0 + tslib: 2.8.1 + + '@aws-crypto/supports-web-crypto@5.2.0': + dependencies: + tslib: 2.8.1 + + '@aws-crypto/util@5.2.0': + dependencies: + '@aws-sdk/types': 3.936.0 + '@smithy/util-utf8': 2.3.0 + tslib: 2.8.1 + + '@aws-sdk/client-s3@3.940.0': + dependencies: + '@aws-crypto/sha1-browser': 5.2.0 + '@aws-crypto/sha256-browser': 5.2.0 + '@aws-crypto/sha256-js': 5.2.0 + '@aws-sdk/core': 3.940.0 + '@aws-sdk/credential-provider-node': 3.940.0 + '@aws-sdk/middleware-bucket-endpoint': 3.936.0 + '@aws-sdk/middleware-expect-continue': 3.936.0 + '@aws-sdk/middleware-flexible-checksums': 3.940.0 + '@aws-sdk/middleware-host-header': 3.936.0 + '@aws-sdk/middleware-location-constraint': 3.936.0 + '@aws-sdk/middleware-logger': 3.936.0 + '@aws-sdk/middleware-recursion-detection': 3.936.0 + '@aws-sdk/middleware-sdk-s3': 3.940.0 + '@aws-sdk/middleware-ssec': 3.936.0 + '@aws-sdk/middleware-user-agent': 3.940.0 + '@aws-sdk/region-config-resolver': 3.936.0 + '@aws-sdk/signature-v4-multi-region': 3.940.0 + '@aws-sdk/types': 3.936.0 + '@aws-sdk/util-endpoints': 3.936.0 + '@aws-sdk/util-user-agent-browser': 3.936.0 + '@aws-sdk/util-user-agent-node': 3.940.0 + '@smithy/config-resolver': 4.4.3 + '@smithy/core': 3.18.6 + '@smithy/eventstream-serde-browser': 4.2.5 + '@smithy/eventstream-serde-config-resolver': 4.3.5 + '@smithy/eventstream-serde-node': 4.2.5 + '@smithy/fetch-http-handler': 5.3.6 + '@smithy/hash-blob-browser': 4.2.6 + '@smithy/hash-node': 4.2.5 + '@smithy/hash-stream-node': 4.2.5 + '@smithy/invalid-dependency': 4.2.5 + '@smithy/md5-js': 4.2.5 + '@smithy/middleware-content-length': 4.2.5 + '@smithy/middleware-endpoint': 4.3.13 + '@smithy/middleware-retry': 4.4.13 + '@smithy/middleware-serde': 4.2.6 + '@smithy/middleware-stack': 4.2.5 + '@smithy/node-config-provider': 4.3.5 + '@smithy/node-http-handler': 4.4.5 + '@smithy/protocol-http': 5.3.5 + '@smithy/smithy-client': 4.9.9 + '@smithy/types': 4.9.0 + '@smithy/url-parser': 4.2.5 + '@smithy/util-base64': 4.3.0 + '@smithy/util-body-length-browser': 4.2.0 + '@smithy/util-body-length-node': 4.2.1 + '@smithy/util-defaults-mode-browser': 4.3.12 + '@smithy/util-defaults-mode-node': 4.2.15 + '@smithy/util-endpoints': 3.2.5 + '@smithy/util-middleware': 4.2.5 + '@smithy/util-retry': 4.2.5 + '@smithy/util-stream': 4.5.6 + '@smithy/util-utf8': 4.2.0 + '@smithy/util-waiter': 4.2.5 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/client-sso@3.940.0': + dependencies: + '@aws-crypto/sha256-browser': 5.2.0 + '@aws-crypto/sha256-js': 5.2.0 + '@aws-sdk/core': 3.940.0 + '@aws-sdk/middleware-host-header': 3.936.0 + '@aws-sdk/middleware-logger': 3.936.0 + '@aws-sdk/middleware-recursion-detection': 3.936.0 + '@aws-sdk/middleware-user-agent': 3.940.0 + '@aws-sdk/region-config-resolver': 3.936.0 + '@aws-sdk/types': 3.936.0 + '@aws-sdk/util-endpoints': 3.936.0 + '@aws-sdk/util-user-agent-browser': 3.936.0 + '@aws-sdk/util-user-agent-node': 3.940.0 + '@smithy/config-resolver': 4.4.3 + '@smithy/core': 3.18.6 + '@smithy/fetch-http-handler': 5.3.6 + '@smithy/hash-node': 4.2.5 + '@smithy/invalid-dependency': 4.2.5 + '@smithy/middleware-content-length': 4.2.5 + '@smithy/middleware-endpoint': 4.3.13 + '@smithy/middleware-retry': 4.4.13 + '@smithy/middleware-serde': 4.2.6 + '@smithy/middleware-stack': 4.2.5 + '@smithy/node-config-provider': 4.3.5 + '@smithy/node-http-handler': 4.4.5 + '@smithy/protocol-http': 5.3.5 + '@smithy/smithy-client': 4.9.9 + '@smithy/types': 4.9.0 + '@smithy/url-parser': 4.2.5 + '@smithy/util-base64': 4.3.0 + '@smithy/util-body-length-browser': 4.2.0 + '@smithy/util-body-length-node': 4.2.1 + '@smithy/util-defaults-mode-browser': 4.3.12 + '@smithy/util-defaults-mode-node': 4.2.15 + '@smithy/util-endpoints': 3.2.5 + '@smithy/util-middleware': 4.2.5 + '@smithy/util-retry': 4.2.5 + '@smithy/util-utf8': 4.2.0 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/core@3.940.0': + dependencies: + '@aws-sdk/types': 3.936.0 + '@aws-sdk/xml-builder': 3.930.0 + '@smithy/core': 3.18.6 + '@smithy/node-config-provider': 4.3.5 + '@smithy/property-provider': 4.2.5 + '@smithy/protocol-http': 5.3.5 + '@smithy/signature-v4': 5.3.5 + '@smithy/smithy-client': 4.9.9 + '@smithy/types': 4.9.0 + '@smithy/util-base64': 4.3.0 + '@smithy/util-middleware': 4.2.5 + '@smithy/util-utf8': 4.2.0 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-env@3.940.0': + dependencies: + '@aws-sdk/core': 3.940.0 + '@aws-sdk/types': 3.936.0 + '@smithy/property-provider': 4.2.5 + '@smithy/types': 4.9.0 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-http@3.940.0': + dependencies: + '@aws-sdk/core': 3.940.0 + '@aws-sdk/types': 3.936.0 + '@smithy/fetch-http-handler': 5.3.6 + '@smithy/node-http-handler': 4.4.5 + '@smithy/property-provider': 4.2.5 + '@smithy/protocol-http': 5.3.5 + '@smithy/smithy-client': 4.9.9 + '@smithy/types': 4.9.0 + '@smithy/util-stream': 4.5.6 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-ini@3.940.0': + dependencies: + '@aws-sdk/core': 3.940.0 + '@aws-sdk/credential-provider-env': 3.940.0 + '@aws-sdk/credential-provider-http': 3.940.0 + '@aws-sdk/credential-provider-login': 3.940.0 + '@aws-sdk/credential-provider-process': 3.940.0 + '@aws-sdk/credential-provider-sso': 3.940.0 + '@aws-sdk/credential-provider-web-identity': 3.940.0 + '@aws-sdk/nested-clients': 3.940.0 + '@aws-sdk/types': 3.936.0 + '@smithy/credential-provider-imds': 4.2.5 + '@smithy/property-provider': 4.2.5 + '@smithy/shared-ini-file-loader': 4.4.0 + '@smithy/types': 4.9.0 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/credential-provider-login@3.940.0': + dependencies: + '@aws-sdk/core': 3.940.0 + '@aws-sdk/nested-clients': 3.940.0 + '@aws-sdk/types': 3.936.0 + '@smithy/property-provider': 4.2.5 + '@smithy/protocol-http': 5.3.5 + '@smithy/shared-ini-file-loader': 4.4.0 + '@smithy/types': 4.9.0 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/credential-provider-node@3.940.0': + dependencies: + '@aws-sdk/credential-provider-env': 3.940.0 + '@aws-sdk/credential-provider-http': 3.940.0 + '@aws-sdk/credential-provider-ini': 3.940.0 + '@aws-sdk/credential-provider-process': 3.940.0 + '@aws-sdk/credential-provider-sso': 3.940.0 + '@aws-sdk/credential-provider-web-identity': 3.940.0 + '@aws-sdk/types': 3.936.0 + '@smithy/credential-provider-imds': 4.2.5 + '@smithy/property-provider': 4.2.5 + '@smithy/shared-ini-file-loader': 4.4.0 + '@smithy/types': 4.9.0 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/credential-provider-process@3.940.0': + dependencies: + '@aws-sdk/core': 3.940.0 + '@aws-sdk/types': 3.936.0 + '@smithy/property-provider': 4.2.5 + '@smithy/shared-ini-file-loader': 4.4.0 + '@smithy/types': 4.9.0 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-sso@3.940.0': + dependencies: + '@aws-sdk/client-sso': 3.940.0 + '@aws-sdk/core': 3.940.0 + '@aws-sdk/token-providers': 3.940.0 + '@aws-sdk/types': 3.936.0 + '@smithy/property-provider': 4.2.5 + '@smithy/shared-ini-file-loader': 4.4.0 + '@smithy/types': 4.9.0 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/credential-provider-web-identity@3.940.0': + dependencies: + '@aws-sdk/core': 3.940.0 + '@aws-sdk/nested-clients': 3.940.0 + '@aws-sdk/types': 3.936.0 + '@smithy/property-provider': 4.2.5 + '@smithy/shared-ini-file-loader': 4.4.0 + '@smithy/types': 4.9.0 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/lib-storage@3.940.0(@aws-sdk/client-s3@3.940.0)': + dependencies: + '@aws-sdk/client-s3': 3.940.0 + '@smithy/abort-controller': 4.2.5 + '@smithy/middleware-endpoint': 4.3.13 + '@smithy/smithy-client': 4.9.9 + buffer: 5.6.0 + events: 3.3.0 + stream-browserify: 3.0.0 + tslib: 2.8.1 + + '@aws-sdk/middleware-bucket-endpoint@3.936.0': + dependencies: + '@aws-sdk/types': 3.936.0 + '@aws-sdk/util-arn-parser': 3.893.0 + '@smithy/node-config-provider': 4.3.5 + '@smithy/protocol-http': 5.3.5 + '@smithy/types': 4.9.0 + '@smithy/util-config-provider': 4.2.0 + tslib: 2.8.1 + + '@aws-sdk/middleware-expect-continue@3.936.0': + dependencies: + '@aws-sdk/types': 3.936.0 + '@smithy/protocol-http': 5.3.5 + '@smithy/types': 4.9.0 + tslib: 2.8.1 + + '@aws-sdk/middleware-flexible-checksums@3.940.0': + dependencies: + '@aws-crypto/crc32': 5.2.0 + '@aws-crypto/crc32c': 5.2.0 + '@aws-crypto/util': 5.2.0 + '@aws-sdk/core': 3.940.0 + '@aws-sdk/types': 3.936.0 + '@smithy/is-array-buffer': 4.2.0 + '@smithy/node-config-provider': 4.3.5 + '@smithy/protocol-http': 5.3.5 + '@smithy/types': 4.9.0 + '@smithy/util-middleware': 4.2.5 + '@smithy/util-stream': 4.5.6 + '@smithy/util-utf8': 4.2.0 + tslib: 2.8.1 + + '@aws-sdk/middleware-host-header@3.936.0': + dependencies: + '@aws-sdk/types': 3.936.0 + '@smithy/protocol-http': 5.3.5 + '@smithy/types': 4.9.0 + tslib: 2.8.1 + + '@aws-sdk/middleware-location-constraint@3.936.0': + dependencies: + '@aws-sdk/types': 3.936.0 + '@smithy/types': 4.9.0 + tslib: 2.8.1 + + '@aws-sdk/middleware-logger@3.936.0': + dependencies: + '@aws-sdk/types': 3.936.0 + '@smithy/types': 4.9.0 + tslib: 2.8.1 + + '@aws-sdk/middleware-recursion-detection@3.936.0': + dependencies: + '@aws-sdk/types': 3.936.0 + '@aws/lambda-invoke-store': 0.2.1 + '@smithy/protocol-http': 5.3.5 + '@smithy/types': 4.9.0 + tslib: 2.8.1 + + '@aws-sdk/middleware-sdk-s3@3.940.0': + dependencies: + '@aws-sdk/core': 3.940.0 + '@aws-sdk/types': 3.936.0 + '@aws-sdk/util-arn-parser': 3.893.0 + '@smithy/core': 3.18.6 + '@smithy/node-config-provider': 4.3.5 + '@smithy/protocol-http': 5.3.5 + '@smithy/signature-v4': 5.3.5 + '@smithy/smithy-client': 4.9.9 + '@smithy/types': 4.9.0 + '@smithy/util-config-provider': 4.2.0 + '@smithy/util-middleware': 4.2.5 + '@smithy/util-stream': 4.5.6 + '@smithy/util-utf8': 4.2.0 + tslib: 2.8.1 + + '@aws-sdk/middleware-ssec@3.936.0': + dependencies: + '@aws-sdk/types': 3.936.0 + '@smithy/types': 4.9.0 + tslib: 2.8.1 + + '@aws-sdk/middleware-user-agent@3.940.0': + dependencies: + '@aws-sdk/core': 3.940.0 + '@aws-sdk/types': 3.936.0 + '@aws-sdk/util-endpoints': 3.936.0 + '@smithy/core': 3.18.6 + '@smithy/protocol-http': 5.3.5 + '@smithy/types': 4.9.0 + tslib: 2.8.1 + + '@aws-sdk/nested-clients@3.940.0': + dependencies: + '@aws-crypto/sha256-browser': 5.2.0 + '@aws-crypto/sha256-js': 5.2.0 + '@aws-sdk/core': 3.940.0 + '@aws-sdk/middleware-host-header': 3.936.0 + '@aws-sdk/middleware-logger': 3.936.0 + '@aws-sdk/middleware-recursion-detection': 3.936.0 + '@aws-sdk/middleware-user-agent': 3.940.0 + '@aws-sdk/region-config-resolver': 3.936.0 + '@aws-sdk/types': 3.936.0 + '@aws-sdk/util-endpoints': 3.936.0 + '@aws-sdk/util-user-agent-browser': 3.936.0 + '@aws-sdk/util-user-agent-node': 3.940.0 + '@smithy/config-resolver': 4.4.3 + '@smithy/core': 3.18.6 + '@smithy/fetch-http-handler': 5.3.6 + '@smithy/hash-node': 4.2.5 + '@smithy/invalid-dependency': 4.2.5 + '@smithy/middleware-content-length': 4.2.5 + '@smithy/middleware-endpoint': 4.3.13 + '@smithy/middleware-retry': 4.4.13 + '@smithy/middleware-serde': 4.2.6 + '@smithy/middleware-stack': 4.2.5 + '@smithy/node-config-provider': 4.3.5 + '@smithy/node-http-handler': 4.4.5 + '@smithy/protocol-http': 5.3.5 + '@smithy/smithy-client': 4.9.9 + '@smithy/types': 4.9.0 + '@smithy/url-parser': 4.2.5 + '@smithy/util-base64': 4.3.0 + '@smithy/util-body-length-browser': 4.2.0 + '@smithy/util-body-length-node': 4.2.1 + '@smithy/util-defaults-mode-browser': 4.3.12 + '@smithy/util-defaults-mode-node': 4.2.15 + '@smithy/util-endpoints': 3.2.5 + '@smithy/util-middleware': 4.2.5 + '@smithy/util-retry': 4.2.5 + '@smithy/util-utf8': 4.2.0 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/region-config-resolver@3.936.0': + dependencies: + '@aws-sdk/types': 3.936.0 + '@smithy/config-resolver': 4.4.3 + '@smithy/node-config-provider': 4.3.5 + '@smithy/types': 4.9.0 + tslib: 2.8.1 + + '@aws-sdk/s3-request-presigner@3.940.0': + dependencies: + '@aws-sdk/signature-v4-multi-region': 3.940.0 + '@aws-sdk/types': 3.936.0 + '@aws-sdk/util-format-url': 3.936.0 + '@smithy/middleware-endpoint': 4.3.13 + '@smithy/protocol-http': 5.3.5 + '@smithy/smithy-client': 4.9.9 + '@smithy/types': 4.9.0 + tslib: 2.8.1 + + '@aws-sdk/signature-v4-multi-region@3.940.0': + dependencies: + '@aws-sdk/middleware-sdk-s3': 3.940.0 + '@aws-sdk/types': 3.936.0 + '@smithy/protocol-http': 5.3.5 + '@smithy/signature-v4': 5.3.5 + '@smithy/types': 4.9.0 + tslib: 2.8.1 + + '@aws-sdk/token-providers@3.940.0': + dependencies: + '@aws-sdk/core': 3.940.0 + '@aws-sdk/nested-clients': 3.940.0 + '@aws-sdk/types': 3.936.0 + '@smithy/property-provider': 4.2.5 + '@smithy/shared-ini-file-loader': 4.4.0 + '@smithy/types': 4.9.0 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/types@3.936.0': + dependencies: + '@smithy/types': 4.9.0 + tslib: 2.8.1 + + '@aws-sdk/util-arn-parser@3.893.0': + dependencies: + tslib: 2.8.1 + + '@aws-sdk/util-endpoints@3.936.0': + dependencies: + '@aws-sdk/types': 3.936.0 + '@smithy/types': 4.9.0 + '@smithy/url-parser': 4.2.5 + '@smithy/util-endpoints': 3.2.5 + tslib: 2.8.1 + + '@aws-sdk/util-format-url@3.936.0': + dependencies: + '@aws-sdk/types': 3.936.0 + '@smithy/querystring-builder': 4.2.5 + '@smithy/types': 4.9.0 + tslib: 2.8.1 + + '@aws-sdk/util-locate-window@3.893.0': + dependencies: + tslib: 2.8.1 + + '@aws-sdk/util-user-agent-browser@3.936.0': + dependencies: + '@aws-sdk/types': 3.936.0 + '@smithy/types': 4.9.0 + bowser: 2.13.1 + tslib: 2.8.1 + + '@aws-sdk/util-user-agent-node@3.940.0': + dependencies: + '@aws-sdk/middleware-user-agent': 3.940.0 + '@aws-sdk/types': 3.936.0 + '@smithy/node-config-provider': 4.3.5 + '@smithy/types': 4.9.0 + tslib: 2.8.1 + + '@aws-sdk/xml-builder@3.930.0': + dependencies: + '@smithy/types': 4.9.0 + fast-xml-parser: 5.2.5 + tslib: 2.8.1 + + '@aws/lambda-invoke-store@0.2.1': {} + '@babel/code-frame@7.27.1': dependencies: '@babel/helper-validator-identifier': 7.28.5 @@ -5750,6 +6801,49 @@ snapshots: '@nolyfill/is-core-module@1.0.39': {} + '@payloadcms/db-postgres@3.65.0(@libsql/client@0.14.0)(payload@3.65.0(graphql@16.12.0)(typescript@5.5.2))': + dependencies: + '@payloadcms/drizzle': 3.65.0(@libsql/client@0.14.0)(@types/pg@8.10.2)(payload@3.65.0(graphql@16.12.0)(typescript@5.5.2))(pg@8.16.3) + '@types/pg': 8.10.2 + console-table-printer: 2.12.1 + drizzle-kit: 0.31.7 + drizzle-orm: 0.44.7(@libsql/client@0.14.0)(@types/pg@8.10.2)(pg@8.16.3) + payload: 3.65.0(graphql@16.12.0)(typescript@5.5.2) + pg: 8.16.3 + prompts: 2.4.2 + to-snake-case: 1.0.0 + uuid: 10.0.0 + transitivePeerDependencies: + - '@aws-sdk/client-rds-data' + - '@cloudflare/workers-types' + - '@electric-sql/pglite' + - '@libsql/client' + - '@libsql/client-wasm' + - '@neondatabase/serverless' + - '@op-engineering/op-sqlite' + - '@opentelemetry/api' + - '@planetscale/database' + - '@prisma/client' + - '@tidbcloud/serverless' + - '@types/better-sqlite3' + - '@types/sql.js' + - '@upstash/redis' + - '@vercel/postgres' + - '@xata.io/client' + - better-sqlite3 + - bun-types + - expo-sqlite + - gel + - knex + - kysely + - mysql2 + - pg-native + - postgres + - prisma + - sql.js + - sqlite3 + - supports-color + '@payloadcms/db-sqlite@3.65.0(@types/pg@8.10.2)(payload@3.65.0(graphql@16.12.0)(typescript@5.5.2))(pg@8.16.3)': dependencies: '@libsql/client': 0.14.0 @@ -5933,6 +7027,21 @@ snapshots: - supports-color - typescript + '@payloadcms/plugin-cloud-storage@3.65.0(@types/react@19.0.1)(monaco-editor@0.55.1)(next@15.5.6(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.77.4))(payload@3.65.0(graphql@16.12.0)(typescript@5.5.2))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.5.2)': + dependencies: + '@payloadcms/ui': 3.65.0(@types/react@19.0.1)(monaco-editor@0.55.1)(next@15.5.6(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.77.4))(payload@3.65.0(graphql@16.12.0)(typescript@5.5.2))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.5.2) + find-node-modules: 2.1.3 + payload: 3.65.0(graphql@16.12.0)(typescript@5.5.2) + range-parser: 1.2.1 + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + transitivePeerDependencies: + - '@types/react' + - monaco-editor + - next + - supports-color + - typescript + '@payloadcms/plugin-multi-tenant@3.65.0(@payloadcms/ui@3.65.0(@types/react@19.0.1)(monaco-editor@0.55.1)(next@15.5.6(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.77.4))(payload@3.65.0(graphql@16.12.0)(typescript@5.5.2))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.5.2))(next@15.5.6(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.77.4))(payload@3.65.0(graphql@16.12.0)(typescript@5.5.2))': dependencies: '@payloadcms/ui': 3.65.0(@types/react@19.0.1)(monaco-editor@0.55.1)(next@15.5.6(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.77.4))(payload@3.65.0(graphql@16.12.0)(typescript@5.5.2))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.5.2) @@ -5983,6 +7092,23 @@ snapshots: - typescript - yjs + '@payloadcms/storage-s3@3.65.0(@types/react@19.0.1)(monaco-editor@0.55.1)(next@15.5.6(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.77.4))(payload@3.65.0(graphql@16.12.0)(typescript@5.5.2))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.5.2)': + dependencies: + '@aws-sdk/client-s3': 3.940.0 + '@aws-sdk/lib-storage': 3.940.0(@aws-sdk/client-s3@3.940.0) + '@aws-sdk/s3-request-presigner': 3.940.0 + '@payloadcms/plugin-cloud-storage': 3.65.0(@types/react@19.0.1)(monaco-editor@0.55.1)(next@15.5.6(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(sass@1.77.4))(payload@3.65.0(graphql@16.12.0)(typescript@5.5.2))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.5.2) + payload: 3.65.0(graphql@16.12.0)(typescript@5.5.2) + transitivePeerDependencies: + - '@types/react' + - aws-crt + - monaco-editor + - next + - react + - react-dom + - supports-color + - typescript + '@payloadcms/translations@3.65.0': dependencies: date-fns: 4.1.0 @@ -6077,6 +7203,34 @@ snapshots: optionalDependencies: '@types/react': 19.0.1 + '@radix-ui/react-context@1.1.3(@types/react@19.0.1)(react@19.0.0)': + dependencies: + react: 19.0.0 + optionalDependencies: + '@types/react': 19.0.1 + + '@radix-ui/react-dialog@1.1.15(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.0.1)(react@19.0.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.0.1)(react@19.0.0) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.0.1)(react@19.0.0) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.0.1)(react@19.0.0) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-slot': 1.2.3(@types/react@19.0.1)(react@19.0.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.0.1)(react@19.0.0) + aria-hidden: 1.2.6 + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + react-remove-scroll: 2.7.2(@types/react@19.0.1)(react@19.0.0) + optionalDependencies: + '@types/react': 19.0.1 + '@types/react-dom': 19.0.1 + '@radix-ui/react-direction@1.1.1(@types/react@19.0.1)(react@19.0.0)': dependencies: react: 19.0.0 @@ -6185,6 +7339,16 @@ snapshots: '@types/react': 19.0.1 '@types/react-dom': 19.0.1 + '@radix-ui/react-progress@1.1.8(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + dependencies: + '@radix-ui/react-context': 1.1.3(@types/react@19.0.1)(react@19.0.0) + '@radix-ui/react-primitive': 2.1.4(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + optionalDependencies: + '@types/react': 19.0.1 + '@types/react-dom': 19.0.1 + '@radix-ui/react-radio-group@1.3.8(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': dependencies: '@radix-ui/primitive': 1.1.3 @@ -6272,6 +7436,26 @@ snapshots: optionalDependencies: '@types/react': 19.0.1 + '@radix-ui/react-tooltip@1.2.8(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.0.1)(react@19.0.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.0.1)(react@19.0.0) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.0.1)(react@19.0.0) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-slot': 1.2.3(@types/react@19.0.1)(react@19.0.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.0.1)(react@19.0.0) + '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + optionalDependencies: + '@types/react': 19.0.1 + '@types/react-dom': 19.0.1 + '@radix-ui/react-use-callback-ref@1.1.1(@types/react@19.0.1)(react@19.0.0)': dependencies: react: 19.0.0 @@ -6341,6 +7525,344 @@ snapshots: '@rushstack/eslint-patch@1.15.0': {} + '@smithy/abort-controller@4.2.5': + dependencies: + '@smithy/types': 4.9.0 + tslib: 2.8.1 + + '@smithy/chunked-blob-reader-native@4.2.1': + dependencies: + '@smithy/util-base64': 4.3.0 + tslib: 2.8.1 + + '@smithy/chunked-blob-reader@5.2.0': + dependencies: + tslib: 2.8.1 + + '@smithy/config-resolver@4.4.3': + dependencies: + '@smithy/node-config-provider': 4.3.5 + '@smithy/types': 4.9.0 + '@smithy/util-config-provider': 4.2.0 + '@smithy/util-endpoints': 3.2.5 + '@smithy/util-middleware': 4.2.5 + tslib: 2.8.1 + + '@smithy/core@3.18.6': + dependencies: + '@smithy/middleware-serde': 4.2.6 + '@smithy/protocol-http': 5.3.5 + '@smithy/types': 4.9.0 + '@smithy/util-base64': 4.3.0 + '@smithy/util-body-length-browser': 4.2.0 + '@smithy/util-middleware': 4.2.5 + '@smithy/util-stream': 4.5.6 + '@smithy/util-utf8': 4.2.0 + '@smithy/uuid': 1.1.0 + tslib: 2.8.1 + + '@smithy/credential-provider-imds@4.2.5': + dependencies: + '@smithy/node-config-provider': 4.3.5 + '@smithy/property-provider': 4.2.5 + '@smithy/types': 4.9.0 + '@smithy/url-parser': 4.2.5 + tslib: 2.8.1 + + '@smithy/eventstream-codec@4.2.5': + dependencies: + '@aws-crypto/crc32': 5.2.0 + '@smithy/types': 4.9.0 + '@smithy/util-hex-encoding': 4.2.0 + tslib: 2.8.1 + + '@smithy/eventstream-serde-browser@4.2.5': + dependencies: + '@smithy/eventstream-serde-universal': 4.2.5 + '@smithy/types': 4.9.0 + tslib: 2.8.1 + + '@smithy/eventstream-serde-config-resolver@4.3.5': + dependencies: + '@smithy/types': 4.9.0 + tslib: 2.8.1 + + '@smithy/eventstream-serde-node@4.2.5': + dependencies: + '@smithy/eventstream-serde-universal': 4.2.5 + '@smithy/types': 4.9.0 + tslib: 2.8.1 + + '@smithy/eventstream-serde-universal@4.2.5': + dependencies: + '@smithy/eventstream-codec': 4.2.5 + '@smithy/types': 4.9.0 + tslib: 2.8.1 + + '@smithy/fetch-http-handler@5.3.6': + dependencies: + '@smithy/protocol-http': 5.3.5 + '@smithy/querystring-builder': 4.2.5 + '@smithy/types': 4.9.0 + '@smithy/util-base64': 4.3.0 + tslib: 2.8.1 + + '@smithy/hash-blob-browser@4.2.6': + dependencies: + '@smithy/chunked-blob-reader': 5.2.0 + '@smithy/chunked-blob-reader-native': 4.2.1 + '@smithy/types': 4.9.0 + tslib: 2.8.1 + + '@smithy/hash-node@4.2.5': + dependencies: + '@smithy/types': 4.9.0 + '@smithy/util-buffer-from': 4.2.0 + '@smithy/util-utf8': 4.2.0 + tslib: 2.8.1 + + '@smithy/hash-stream-node@4.2.5': + dependencies: + '@smithy/types': 4.9.0 + '@smithy/util-utf8': 4.2.0 + tslib: 2.8.1 + + '@smithy/invalid-dependency@4.2.5': + dependencies: + '@smithy/types': 4.9.0 + tslib: 2.8.1 + + '@smithy/is-array-buffer@2.2.0': + dependencies: + tslib: 2.8.1 + + '@smithy/is-array-buffer@4.2.0': + dependencies: + tslib: 2.8.1 + + '@smithy/md5-js@4.2.5': + dependencies: + '@smithy/types': 4.9.0 + '@smithy/util-utf8': 4.2.0 + tslib: 2.8.1 + + '@smithy/middleware-content-length@4.2.5': + dependencies: + '@smithy/protocol-http': 5.3.5 + '@smithy/types': 4.9.0 + tslib: 2.8.1 + + '@smithy/middleware-endpoint@4.3.13': + dependencies: + '@smithy/core': 3.18.6 + '@smithy/middleware-serde': 4.2.6 + '@smithy/node-config-provider': 4.3.5 + '@smithy/shared-ini-file-loader': 4.4.0 + '@smithy/types': 4.9.0 + '@smithy/url-parser': 4.2.5 + '@smithy/util-middleware': 4.2.5 + tslib: 2.8.1 + + '@smithy/middleware-retry@4.4.13': + dependencies: + '@smithy/node-config-provider': 4.3.5 + '@smithy/protocol-http': 5.3.5 + '@smithy/service-error-classification': 4.2.5 + '@smithy/smithy-client': 4.9.9 + '@smithy/types': 4.9.0 + '@smithy/util-middleware': 4.2.5 + '@smithy/util-retry': 4.2.5 + '@smithy/uuid': 1.1.0 + tslib: 2.8.1 + + '@smithy/middleware-serde@4.2.6': + dependencies: + '@smithy/protocol-http': 5.3.5 + '@smithy/types': 4.9.0 + tslib: 2.8.1 + + '@smithy/middleware-stack@4.2.5': + dependencies: + '@smithy/types': 4.9.0 + tslib: 2.8.1 + + '@smithy/node-config-provider@4.3.5': + dependencies: + '@smithy/property-provider': 4.2.5 + '@smithy/shared-ini-file-loader': 4.4.0 + '@smithy/types': 4.9.0 + tslib: 2.8.1 + + '@smithy/node-http-handler@4.4.5': + dependencies: + '@smithy/abort-controller': 4.2.5 + '@smithy/protocol-http': 5.3.5 + '@smithy/querystring-builder': 4.2.5 + '@smithy/types': 4.9.0 + tslib: 2.8.1 + + '@smithy/property-provider@4.2.5': + dependencies: + '@smithy/types': 4.9.0 + tslib: 2.8.1 + + '@smithy/protocol-http@5.3.5': + dependencies: + '@smithy/types': 4.9.0 + tslib: 2.8.1 + + '@smithy/querystring-builder@4.2.5': + dependencies: + '@smithy/types': 4.9.0 + '@smithy/util-uri-escape': 4.2.0 + tslib: 2.8.1 + + '@smithy/querystring-parser@4.2.5': + dependencies: + '@smithy/types': 4.9.0 + tslib: 2.8.1 + + '@smithy/service-error-classification@4.2.5': + dependencies: + '@smithy/types': 4.9.0 + + '@smithy/shared-ini-file-loader@4.4.0': + dependencies: + '@smithy/types': 4.9.0 + tslib: 2.8.1 + + '@smithy/signature-v4@5.3.5': + dependencies: + '@smithy/is-array-buffer': 4.2.0 + '@smithy/protocol-http': 5.3.5 + '@smithy/types': 4.9.0 + '@smithy/util-hex-encoding': 4.2.0 + '@smithy/util-middleware': 4.2.5 + '@smithy/util-uri-escape': 4.2.0 + '@smithy/util-utf8': 4.2.0 + tslib: 2.8.1 + + '@smithy/smithy-client@4.9.9': + dependencies: + '@smithy/core': 3.18.6 + '@smithy/middleware-endpoint': 4.3.13 + '@smithy/middleware-stack': 4.2.5 + '@smithy/protocol-http': 5.3.5 + '@smithy/types': 4.9.0 + '@smithy/util-stream': 4.5.6 + tslib: 2.8.1 + + '@smithy/types@4.9.0': + dependencies: + tslib: 2.8.1 + + '@smithy/url-parser@4.2.5': + dependencies: + '@smithy/querystring-parser': 4.2.5 + '@smithy/types': 4.9.0 + tslib: 2.8.1 + + '@smithy/util-base64@4.3.0': + dependencies: + '@smithy/util-buffer-from': 4.2.0 + '@smithy/util-utf8': 4.2.0 + tslib: 2.8.1 + + '@smithy/util-body-length-browser@4.2.0': + dependencies: + tslib: 2.8.1 + + '@smithy/util-body-length-node@4.2.1': + dependencies: + tslib: 2.8.1 + + '@smithy/util-buffer-from@2.2.0': + dependencies: + '@smithy/is-array-buffer': 2.2.0 + tslib: 2.8.1 + + '@smithy/util-buffer-from@4.2.0': + dependencies: + '@smithy/is-array-buffer': 4.2.0 + tslib: 2.8.1 + + '@smithy/util-config-provider@4.2.0': + dependencies: + tslib: 2.8.1 + + '@smithy/util-defaults-mode-browser@4.3.12': + dependencies: + '@smithy/property-provider': 4.2.5 + '@smithy/smithy-client': 4.9.9 + '@smithy/types': 4.9.0 + tslib: 2.8.1 + + '@smithy/util-defaults-mode-node@4.2.15': + dependencies: + '@smithy/config-resolver': 4.4.3 + '@smithy/credential-provider-imds': 4.2.5 + '@smithy/node-config-provider': 4.3.5 + '@smithy/property-provider': 4.2.5 + '@smithy/smithy-client': 4.9.9 + '@smithy/types': 4.9.0 + tslib: 2.8.1 + + '@smithy/util-endpoints@3.2.5': + dependencies: + '@smithy/node-config-provider': 4.3.5 + '@smithy/types': 4.9.0 + tslib: 2.8.1 + + '@smithy/util-hex-encoding@4.2.0': + dependencies: + tslib: 2.8.1 + + '@smithy/util-middleware@4.2.5': + dependencies: + '@smithy/types': 4.9.0 + tslib: 2.8.1 + + '@smithy/util-retry@4.2.5': + dependencies: + '@smithy/service-error-classification': 4.2.5 + '@smithy/types': 4.9.0 + tslib: 2.8.1 + + '@smithy/util-stream@4.5.6': + dependencies: + '@smithy/fetch-http-handler': 5.3.6 + '@smithy/node-http-handler': 4.4.5 + '@smithy/types': 4.9.0 + '@smithy/util-base64': 4.3.0 + '@smithy/util-buffer-from': 4.2.0 + '@smithy/util-hex-encoding': 4.2.0 + '@smithy/util-utf8': 4.2.0 + tslib: 2.8.1 + + '@smithy/util-uri-escape@4.2.0': + dependencies: + tslib: 2.8.1 + + '@smithy/util-utf8@2.3.0': + dependencies: + '@smithy/util-buffer-from': 2.2.0 + tslib: 2.8.1 + + '@smithy/util-utf8@4.2.0': + dependencies: + '@smithy/util-buffer-from': 4.2.0 + tslib: 2.8.1 + + '@smithy/util-waiter@4.2.5': + dependencies: + '@smithy/abort-controller': 4.2.5 + '@smithy/types': 4.9.0 + tslib: 2.8.1 + + '@smithy/uuid@1.1.0': + dependencies: + tslib: 2.8.1 + '@swc/core-darwin-arm64@1.15.3': optional: true @@ -6525,7 +8047,6 @@ snapshots: '@types/node': 24.10.1 pg-protocol: 1.10.3 pg-types: 4.1.0 - optional: true '@types/react-dom@19.0.1': dependencies: @@ -6555,7 +8076,7 @@ snapshots: '@typescript-eslint/eslint-plugin@8.26.1(@typescript-eslint/parser@8.26.1(eslint@8.57.1)(typescript@5.5.2))(eslint@9.22.0(jiti@2.6.1))(typescript@5.7.3)': dependencies: '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.26.1(eslint@8.57.1)(typescript@5.5.2) + '@typescript-eslint/parser': 8.26.1(eslint@9.22.0(jiti@2.6.1))(typescript@5.7.3) '@typescript-eslint/scope-manager': 8.26.1 '@typescript-eslint/type-utils': 8.26.1(eslint@9.22.0(jiti@2.6.1))(typescript@5.7.3) '@typescript-eslint/utils': 8.26.1(eslint@9.22.0(jiti@2.6.1))(typescript@5.7.3) @@ -6572,7 +8093,7 @@ snapshots: '@typescript-eslint/eslint-plugin@8.48.0(@typescript-eslint/parser@8.26.1(eslint@8.57.1)(typescript@5.5.2))(eslint@8.57.1)(typescript@5.5.2)': dependencies: '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.26.1(eslint@8.57.1)(typescript@5.5.2) + '@typescript-eslint/parser': 8.26.1(eslint@9.22.0(jiti@2.6.1))(typescript@5.7.3) '@typescript-eslint/scope-manager': 8.48.0 '@typescript-eslint/type-utils': 8.48.0(eslint@8.57.1)(typescript@5.5.2) '@typescript-eslint/utils': 8.48.0(eslint@8.57.1)(typescript@5.5.2) @@ -6604,18 +8125,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.26.1(eslint@8.57.1)(typescript@5.5.2)': - dependencies: - '@typescript-eslint/scope-manager': 8.26.1 - '@typescript-eslint/types': 8.26.1 - '@typescript-eslint/typescript-estree': 8.26.1(typescript@5.5.2) - '@typescript-eslint/visitor-keys': 8.26.1 - debug: 4.4.3 - eslint: 8.57.1 - typescript: 5.5.2 - transitivePeerDependencies: - - supports-color - '@typescript-eslint/parser@8.26.1(eslint@9.22.0(jiti@2.6.1))(typescript@5.7.3)': dependencies: '@typescript-eslint/scope-manager': 8.26.1 @@ -6715,20 +8224,6 @@ snapshots: '@typescript-eslint/types@8.48.0': {} - '@typescript-eslint/typescript-estree@8.26.1(typescript@5.5.2)': - dependencies: - '@typescript-eslint/types': 8.26.1 - '@typescript-eslint/visitor-keys': 8.26.1 - debug: 4.4.3 - fast-glob: 3.3.3 - is-glob: 4.0.3 - minimatch: 9.0.5 - semver: 7.7.3 - ts-api-utils: 2.1.0(typescript@5.5.2) - typescript: 5.5.2 - transitivePeerDependencies: - - supports-color - '@typescript-eslint/typescript-estree@8.26.1(typescript@5.7.3)': dependencies: '@typescript-eslint/types': 8.26.1 @@ -7060,6 +8555,8 @@ snapshots: body-scroll-lock@4.0.0-beta.0: {} + bowser@2.13.1: {} + brace-expansion@1.1.12: dependencies: balanced-match: 1.0.2 @@ -7077,6 +8574,11 @@ snapshots: buffer-from@1.1.2: {} + buffer@5.6.0: + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + buffer@5.7.1: dependencies: base64-js: 1.5.1 @@ -7274,6 +8776,8 @@ snapshots: dequal@2.0.3: {} + detect-file@1.0.0: {} + detect-libc@2.0.2: {} detect-libc@2.1.2: {} @@ -7542,8 +9046,8 @@ snapshots: '@typescript-eslint/parser': 8.48.0(eslint@8.57.1)(typescript@5.5.2) eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import-x@4.6.1(eslint@8.57.1)(typescript@5.5.2))(eslint-plugin-import@2.32.0)(eslint@8.57.1) - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.26.1(eslint@8.57.1)(typescript@5.5.2))(eslint@8.57.1) + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import-x@4.6.1(eslint@8.57.1)(typescript@5.5.2))(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.26.1(eslint@8.57.1)(typescript@5.5.2))(eslint@8.57.1))(eslint@8.57.1) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.48.0(eslint@8.57.1)(typescript@5.5.2))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1) eslint-plugin-jsx-a11y: 6.10.2(eslint@8.57.1) eslint-plugin-react: 7.37.5(eslint@8.57.1) eslint-plugin-react-hooks: 5.2.0(eslint@8.57.1) @@ -7566,7 +9070,7 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@3.10.1(eslint-plugin-import-x@4.6.1(eslint@8.57.1)(typescript@5.5.2))(eslint-plugin-import@2.32.0)(eslint@8.57.1): + eslint-import-resolver-typescript@3.10.1(eslint-plugin-import-x@4.6.1(eslint@8.57.1)(typescript@5.5.2))(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.26.1(eslint@8.57.1)(typescript@5.5.2))(eslint@8.57.1))(eslint@8.57.1): dependencies: '@nolyfill/is-core-module': 1.0.39 debug: 4.4.3 @@ -7577,19 +9081,19 @@ snapshots: tinyglobby: 0.2.15 unrs-resolver: 1.11.1 optionalDependencies: - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.26.1(eslint@8.57.1)(typescript@5.5.2))(eslint@8.57.1) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.48.0(eslint@8.57.1)(typescript@5.5.2))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1) eslint-plugin-import-x: 4.6.1(eslint@8.57.1)(typescript@5.5.2) transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.1(@typescript-eslint/parser@8.48.0(eslint@8.57.1)(typescript@5.5.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1): + eslint-module-utils@2.12.1(@typescript-eslint/parser@8.48.0(eslint@8.57.1)(typescript@5.5.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import-x@4.6.1(eslint@8.57.1)(typescript@5.5.2))(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.26.1(eslint@8.57.1)(typescript@5.5.2))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1): dependencies: debug: 3.2.7 optionalDependencies: '@typescript-eslint/parser': 8.48.0(eslint@8.57.1)(typescript@5.5.2) eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import-x@4.6.1(eslint@8.57.1)(typescript@5.5.2))(eslint-plugin-import@2.32.0)(eslint@8.57.1) + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import-x@4.6.1(eslint@8.57.1)(typescript@5.5.2))(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.26.1(eslint@8.57.1)(typescript@5.5.2))(eslint@8.57.1))(eslint@8.57.1) transitivePeerDependencies: - supports-color @@ -7634,7 +9138,7 @@ snapshots: - supports-color - typescript - eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.26.1(eslint@8.57.1)(typescript@5.5.2))(eslint@8.57.1): + eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.48.0(eslint@8.57.1)(typescript@5.5.2))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.9 @@ -7645,7 +9149,7 @@ snapshots: doctrine: 2.1.0 eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.48.0(eslint@8.57.1)(typescript@5.5.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1) + eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.48.0(eslint@8.57.1)(typescript@5.5.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import-x@4.6.1(eslint@8.57.1)(typescript@5.5.2))(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.26.1(eslint@8.57.1)(typescript@5.5.2))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1) hasown: 2.0.2 is-core-module: 2.16.1 is-glob: 4.0.3 @@ -7657,7 +9161,7 @@ snapshots: string.prototype.trimend: 1.0.9 tsconfig-paths: 3.15.0 optionalDependencies: - '@typescript-eslint/parser': 8.26.1(eslint@8.57.1)(typescript@5.5.2) + '@typescript-eslint/parser': 8.48.0(eslint@8.57.1)(typescript@5.5.2) transitivePeerDependencies: - eslint-import-resolver-typescript - eslint-import-resolver-webpack @@ -8026,8 +9530,14 @@ snapshots: transitivePeerDependencies: - bare-abort-controller + events@3.3.0: {} + expand-template@2.0.3: {} + expand-tilde@2.0.2: + dependencies: + homedir-polyfill: 1.0.3 + fast-copy@3.0.2: {} fast-deep-equal@3.1.3: {} @@ -8058,6 +9568,10 @@ snapshots: fast-uri@3.1.0: {} + fast-xml-parser@5.2.5: + dependencies: + strnum: 2.1.1 + fastq@1.19.1: dependencies: reusify: 1.1.0 @@ -8089,6 +9603,11 @@ snapshots: dependencies: to-regex-range: 5.0.1 + find-node-modules@2.1.3: + dependencies: + findup-sync: 4.0.0 + merge: 2.1.1 + find-root@1.1.0: {} find-up@5.0.0: @@ -8096,6 +9615,13 @@ snapshots: locate-path: 6.0.0 path-exists: 4.0.0 + findup-sync@4.0.0: + dependencies: + detect-file: 1.0.0 + is-glob: 4.0.3 + micromatch: 4.0.8 + resolve-dir: 1.0.1 + flat-cache@3.2.0: dependencies: flatted: 3.3.3 @@ -8196,6 +9722,20 @@ snapshots: once: 1.4.0 path-is-absolute: 1.0.1 + global-modules@1.0.0: + dependencies: + global-prefix: 1.0.2 + is-windows: 1.0.2 + resolve-dir: 1.0.1 + + global-prefix@1.0.2: + dependencies: + expand-tilde: 2.0.2 + homedir-polyfill: 1.0.3 + ini: 1.3.8 + is-windows: 1.0.2 + which: 1.3.1 + globals@13.24.0: dependencies: type-fest: 0.20.2 @@ -8258,6 +9798,10 @@ snapshots: dependencies: react-is: 16.13.1 + homedir-polyfill@1.0.3: + dependencies: + parse-passwd: 1.0.0 + http-status@2.1.0: {} ieee754@1.2.1: {} @@ -8429,6 +9973,8 @@ snapshots: call-bound: 1.0.4 get-intrinsic: 1.3.0 + is-windows@1.0.2: {} + isarray@2.0.5: {} isexe@2.0.0: {} @@ -8675,6 +10221,8 @@ snapshots: merge2@1.4.1: {} + merge@2.1.1: {} + micromark-core-commonmark@2.0.3: dependencies: decode-named-character-reference: 1.2.0 @@ -8964,8 +10512,7 @@ snapshots: define-properties: 1.2.1 es-object-atoms: 1.1.1 - obuf@1.1.2: - optional: true + obuf@1.1.2: {} on-exit-leak-free@2.1.2: {} @@ -9017,6 +10564,8 @@ snapshots: json-parse-even-better-errors: 2.3.1 lines-and-columns: 1.2.4 + parse-passwd@1.0.0: {} + path-exists@4.0.0: {} path-is-absolute@1.0.1: {} @@ -9073,22 +10622,17 @@ snapshots: pg-cloudflare@1.2.7: optional: true - pg-connection-string@2.9.1: - optional: true + pg-connection-string@2.9.1: {} - pg-int8@1.0.1: - optional: true + pg-int8@1.0.1: {} - pg-numeric@1.0.2: - optional: true + pg-numeric@1.0.2: {} pg-pool@3.10.1(pg@8.16.3): dependencies: pg: 8.16.3 - optional: true - pg-protocol@1.10.3: - optional: true + pg-protocol@1.10.3: {} pg-types@2.2.0: dependencies: @@ -9097,7 +10641,6 @@ snapshots: postgres-bytea: 1.0.0 postgres-date: 1.0.7 postgres-interval: 1.2.0 - optional: true pg-types@4.1.0: dependencies: @@ -9108,7 +10651,6 @@ snapshots: postgres-date: 2.1.0 postgres-interval: 3.0.0 postgres-range: 1.1.4 - optional: true pg@8.16.3: dependencies: @@ -9119,12 +10661,10 @@ snapshots: pgpass: 1.0.5 optionalDependencies: pg-cloudflare: 1.2.7 - optional: true pgpass@1.0.5: dependencies: split2: 4.2.0 - optional: true picocolors@1.1.1: {} @@ -9184,36 +10724,27 @@ snapshots: picocolors: 1.1.1 source-map-js: 1.2.1 - postgres-array@2.0.0: - optional: true + postgres-array@2.0.0: {} - postgres-array@3.0.4: - optional: true + postgres-array@3.0.4: {} - postgres-bytea@1.0.0: - optional: true + postgres-bytea@1.0.0: {} postgres-bytea@3.0.0: dependencies: obuf: 1.1.2 - optional: true - postgres-date@1.0.7: - optional: true + postgres-date@1.0.7: {} - postgres-date@2.1.0: - optional: true + postgres-date@2.1.0: {} postgres-interval@1.2.0: dependencies: xtend: 4.0.2 - optional: true - postgres-interval@3.0.0: - optional: true + postgres-interval@3.0.0: {} - postgres-range@1.1.4: - optional: true + postgres-range@1.1.4: {} prebuild-install@7.1.3: dependencies: @@ -9264,6 +10795,8 @@ snapshots: quick-format-unescaped@4.0.4: {} + range-parser@1.2.1: {} + rc@1.2.8: dependencies: deep-extend: 0.6.0 @@ -9400,6 +10933,11 @@ snapshots: requireindex@1.2.0: {} + resolve-dir@1.0.1: + dependencies: + expand-tilde: 2.0.2 + global-modules: 1.0.0 + resolve-from@4.0.0: {} resolve-pkg-maps@1.0.0: {} @@ -9627,6 +11165,11 @@ snapshots: es-errors: 1.3.0 internal-slot: 1.1.0 + stream-browserify@3.0.0: + dependencies: + inherits: 2.0.4 + readable-stream: 3.6.2 + streamsearch@1.1.0: {} streamx@2.23.0: @@ -9711,6 +11254,8 @@ snapshots: strip-json-comments@5.0.3: {} + strnum@2.1.1: {} + strtok3@8.1.0: dependencies: '@tokenizer/token': 0.3.0 @@ -10063,6 +11608,10 @@ snapshots: gopd: 1.2.0 has-tostringtag: 1.0.2 + which@1.3.1: + dependencies: + isexe: 2.0.0 + which@2.0.2: dependencies: isexe: 2.0.0 @@ -10078,8 +11627,7 @@ snapshots: commander: 2.20.3 cssfilter: 0.0.10 - xtend@4.0.2: - optional: true + xtend@4.0.2: {} yaml@1.10.2: {} diff --git a/src/app/(app)/caregiver/dashboard/page.tsx b/src/app/(app)/caregiver/dashboard/page.tsx index 9c98b00..f8f9e63 100644 --- a/src/app/(app)/caregiver/dashboard/page.tsx +++ b/src/app/(app)/caregiver/dashboard/page.tsx @@ -1,6 +1,6 @@ 'use client' -import React, { useState, useEffect } from 'react' +import React, { useState, useEffect, useCallback } from 'react' import { useRouter } from 'next/navigation' import Link from 'next/link' import { @@ -12,10 +12,56 @@ import { ClipboardList, Users, Settings, + Plus, + Pencil, + Eye, + Send, + Check, + ChefHat, + AlertTriangle, + ChevronLeft, + ChevronRight, + Calendar, + UserCheck, + UserX, } from 'lucide-react' import { Button } from '@/components/ui/button' -import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' +import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card' +import { Badge } from '@/components/ui/badge' +import { Input } from '@/components/ui/input' +import { Label } from '@/components/ui/label' +import { Progress } from '@/components/ui/progress' +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from '@/components/ui/select' +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from '@/components/ui/table' +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from '@/components/ui/dialog' +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from '@/components/ui/tooltip' +import { cn } from '@/lib/utils' interface User { id: number @@ -27,22 +73,122 @@ interface User { }> } +interface MealOrder { + id: number + title: string + date: string + mealType: 'breakfast' | 'lunch' | 'dinner' + status: 'draft' | 'submitted' | 'preparing' | 'completed' + mealCount: number + createdAt: string +} + +interface Resident { + id: number + name: string + room: string +} + +interface Meal { + id: number + resident: number | { id: number; name: string } +} + interface OrderStats { - pending: number + draft: number + submitted: number preparing: number - prepared: number + completed: number total: number } +interface PaginationInfo { + totalDocs: number + totalPages: number + page: number + limit: number + hasNextPage: boolean + hasPrevPage: boolean +} + +const ITEMS_PER_PAGE = 10 + export default function CaregiverDashboardPage() { const router = useRouter() const [user, setUser] = useState(null) - const [stats, setStats] = useState({ pending: 0, preparing: 0, prepared: 0, total: 0 }) + const [orders, setOrders] = useState([]) + const [residents, setResidents] = useState([]) + const [stats, setStats] = useState({ + draft: 0, + submitted: 0, + preparing: 0, + completed: 0, + total: 0, + }) + const [pagination, setPagination] = useState({ + totalDocs: 0, + totalPages: 1, + page: 1, + limit: ITEMS_PER_PAGE, + hasNextPage: false, + hasPrevPage: false, + }) const [loading, setLoading] = useState(true) + const [ordersLoading, setOrdersLoading] = useState(false) + + // Filters + const [dateFilter, setDateFilter] = useState('') + const [statusFilter, setStatusFilter] = useState('all') + + // Coverage dialog + const [coverageDialogOpen, setCoverageDialogOpen] = useState(false) + const [selectedOrder, setSelectedOrder] = useState(null) + const [orderMeals, setOrderMeals] = useState([]) + const [coverageLoading, setCoverageLoading] = useState(false) + + // Create order dialog + const [createDialogOpen, setCreateDialogOpen] = useState(false) + const [newOrderDate, setNewOrderDate] = useState(() => new Date().toISOString().split('T')[0]) + const [newOrderMealType, setNewOrderMealType] = useState<'breakfast' | 'lunch' | 'dinner'>('breakfast') + const [creating, setCreating] = useState(false) + + const fetchOrders = useCallback(async (page: number = 1) => { + setOrdersLoading(true) + try { + let url = `/api/meal-orders?sort=-date,-createdAt&limit=${ITEMS_PER_PAGE}&page=${page}&depth=0` + if (dateFilter) { + url += `&where[date][equals]=${dateFilter}` + } + if (statusFilter !== 'all') { + url += `&where[status][equals]=${statusFilter}` + } + + const res = await fetch(url, { credentials: 'include' }) + if (res.ok) { + const data = await res.json() + setOrders(data.docs || []) + setPagination({ + totalDocs: data.totalDocs || 0, + totalPages: data.totalPages || 1, + page: data.page || 1, + limit: data.limit || ITEMS_PER_PAGE, + hasNextPage: data.hasNextPage || false, + hasPrevPage: data.hasPrevPage || false, + }) + } else if (res.status === 401) { + router.push('/caregiver/login') + } + } catch (err) { + console.error('Error fetching orders:', err) + } finally { + setOrdersLoading(false) + } + }, [router, dateFilter, statusFilter]) useEffect(() => { - const fetchData = async () => { + const fetchInitialData = async () => { try { + // Fetch current user const userRes = await fetch('/api/users/me', { credentials: 'include' }) if (!userRes.ok) { router.push('/caregiver/login') @@ -55,18 +201,31 @@ export default function CaregiverDashboardPage() { } setUser(userData.user) - const today = new Date().toISOString().split('T')[0] - const ordersRes = await fetch(`/api/meal-orders?where[date][equals]=${today}&limit=1000`, { + // Fetch residents + const residentsRes = await fetch('/api/residents?where[active][equals]=true&limit=500', { credentials: 'include', }) - if (ordersRes.ok) { - const ordersData = await ordersRes.json() - const orders = ordersData.docs || [] + if (residentsRes.ok) { + const residentsData = await residentsRes.json() + setResidents(residentsData.docs || []) + } + + // Fetch stats (all orders from last 7 days) + const weekAgo = new Date() + weekAgo.setDate(weekAgo.getDate() - 7) + const statsRes = await fetch( + `/api/meal-orders?where[date][greater_than_equal]=${weekAgo.toISOString().split('T')[0]}&limit=1000&depth=0`, + { credentials: 'include' } + ) + if (statsRes.ok) { + const statsData = await statsRes.json() + const allOrders = statsData.docs || [] setStats({ - pending: orders.filter((o: { status: string }) => o.status === 'pending').length, - preparing: orders.filter((o: { status: string }) => o.status === 'preparing').length, - prepared: orders.filter((o: { status: string }) => o.status === 'prepared').length, - total: orders.length, + draft: allOrders.filter((o: MealOrder) => o.status === 'draft').length, + submitted: allOrders.filter((o: MealOrder) => o.status === 'submitted').length, + preparing: allOrders.filter((o: MealOrder) => o.status === 'preparing').length, + completed: allOrders.filter((o: MealOrder) => o.status === 'completed').length, + total: allOrders.length, }) } } catch (error) { @@ -75,9 +234,15 @@ export default function CaregiverDashboardPage() { setLoading(false) } } - fetchData() + fetchInitialData() }, [router]) + useEffect(() => { + if (!loading) { + fetchOrders(1) + } + }, [loading, fetchOrders]) + const handleLogout = async () => { await fetch('/api/users/logout', { method: 'POST', @@ -86,6 +251,137 @@ export default function CaregiverDashboardPage() { router.push('/caregiver/login') } + const handleCreateOrder = async () => { + setCreating(true) + try { + const res = await fetch('/api/meal-orders', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + date: newOrderDate, + mealType: newOrderMealType, + status: 'draft', + }), + credentials: 'include', + }) + + if (res.ok) { + const data = await res.json() + setCreateDialogOpen(false) + router.push(`/caregiver/orders/${data.doc.id}`) + } + } catch (err) { + console.error('Error creating order:', err) + } finally { + setCreating(false) + } + } + + const handleViewCoverage = async (order: MealOrder) => { + setSelectedOrder(order) + setCoverageDialogOpen(true) + setCoverageLoading(true) + + try { + const res = await fetch( + `/api/meals?where[order][equals]=${order.id}&depth=1&limit=500`, + { credentials: 'include' } + ) + if (res.ok) { + const data = await res.json() + setOrderMeals(data.docs || []) + } + } catch (err) { + console.error('Error fetching meals:', err) + } finally { + setCoverageLoading(false) + } + } + + const getMealTypeLabel = (type: string) => { + switch (type) { + case 'breakfast': + return 'Breakfast' + case 'lunch': + return 'Lunch' + case 'dinner': + return 'Dinner' + default: + return type + } + } + + const getMealTypeIcon = (type: string) => { + switch (type) { + case 'breakfast': + return + case 'lunch': + return + case 'dinner': + return + default: + return null + } + } + + const getStatusBadge = (status: string) => { + switch (status) { + case 'draft': + return ( + + + Draft + + ) + case 'submitted': + return ( + + + Submitted + + ) + case 'preparing': + return ( + + + Preparing + + ) + case 'completed': + return ( + + + Completed + + ) + default: + return {status} + } + } + + const getCoverageInfo = (order: MealOrder) => { + const totalResidents = residents.length + const coveredCount = order.mealCount + const percentage = totalResidents > 0 ? Math.round((coveredCount / totalResidents) * 100) : 0 + return { coveredCount, totalResidents, percentage } + } + + const getCoverageColor = (percentage: number) => { + if (percentage === 100) return 'bg-green-500' + if (percentage >= 75) return 'bg-blue-500' + if (percentage >= 50) return 'bg-yellow-500' + return 'bg-red-500' + } + + const formatDate = (dateStr: string) => { + const date = new Date(dateStr) + return date.toLocaleDateString('en-US', { + weekday: 'short', + month: 'short', + day: 'numeric', + }) + } + if (loading) { return (
@@ -99,6 +395,13 @@ export default function CaregiverDashboardPage() { ? user.tenants[0].tenant.name : 'Care Home' + // Calculate covered residents for coverage dialog + const coveredResidentIds = new Set( + orderMeals.map((m) => (typeof m.resident === 'object' ? m.resident.id : m.resident)) + ) + const coveredResidents = residents.filter((r) => coveredResidentIds.has(r.id)) + const uncoveredResidents = residents.filter((r) => !coveredResidentIds.has(r.id)) + return (
@@ -115,12 +418,19 @@ export default function CaregiverDashboardPage() {
-
-

Dashboard

-

Today's overview

+
+
+

Dashboard

+

Manage meal orders for your care home

+
+
-
+ {/* Stats Cards */} +
Total Orders @@ -128,76 +438,63 @@ export default function CaregiverDashboardPage() {
{stats.total}
-

Today

+

Last 7 days

- Pending -
+ Draft +
-
{stats.pending}
-

Awaiting preparation

-
- - - - Preparing -
- - -
{stats.preparing}
+
{stats.draft}

In progress

- Prepared + Submitted +
+ + +
{stats.submitted}
+

Sent to kitchen

+
+ + + + Preparing +
+ + +
{stats.preparing}
+

Being prepared

+
+ + + + Completed
-
{stats.prepared}
-

Ready to serve

+
{stats.completed}
+

Finished

- + {/* Quick Actions */} + Quick Actions -
- - - - - New Breakfast - - - - - - - - New Lunch - - - - - - - - New Dinner - - - +
- View Orders + All Orders @@ -217,10 +514,432 @@ export default function CaregiverDashboardPage() { + setCreateDialogOpen(true)} + > + + + New Order + +
+ + {/* Meal Orders Table */} + + +
+
+ Meal Orders + + View and manage all meal orders. Click on coverage to see resident details. + +
+
+
+ + setDateFilter(e.target.value)} + className="w-40" + placeholder="Filter by date" + /> +
+ + {(dateFilter || statusFilter !== 'all') && ( + + )} +
+
+
+ + {ordersLoading ? ( +
+ +
+ ) : orders.length === 0 ? ( +
+ +

No orders found.

+

+ Create a new order to get started. +

+ +
+ ) : ( + <> + + + + Meal + Date + Coverage + Status + Actions + + + + {orders.map((order) => { + const coverage = getCoverageInfo(order) + return ( + + +
+ {getMealTypeIcon(order.mealType)} + + {getMealTypeLabel(order.mealType)} + +
+
+ +
+ + {formatDate(order.date)} +
+
+ + + + + + + +

Click to view coverage details

+
+
+
+
+ {getStatusBadge(order.status)} + +
+ {order.status === 'draft' ? ( + + ) : ( + + )} +
+
+
+ ) + })} +
+
+ + {/* Pagination */} + {pagination.totalPages > 1 && ( +
+
+ Showing {(pagination.page - 1) * pagination.limit + 1} to{' '} + {Math.min(pagination.page * pagination.limit, pagination.totalDocs)} of{' '} + {pagination.totalDocs} orders +
+
+ +
+ {Array.from({ length: Math.min(5, pagination.totalPages) }, (_, i) => { + let pageNum: number + if (pagination.totalPages <= 5) { + pageNum = i + 1 + } else if (pagination.page <= 3) { + pageNum = i + 1 + } else if (pagination.page >= pagination.totalPages - 2) { + pageNum = pagination.totalPages - 4 + i + } else { + pageNum = pagination.page - 2 + i + } + return ( + + ) + })} +
+ +
+
+ )} + + )} +
+
+ + {/* Create Order Dialog */} + + + + Create New Meal Order + + Select a date and meal type to create a new order. + + +
+
+ + setNewOrderDate(e.target.value)} + /> +
+
+ +
+ {[ + { value: 'breakfast', label: 'Breakfast', icon: Sunrise, color: 'text-orange-500' }, + { value: 'lunch', label: 'Lunch', icon: Sun, color: 'text-yellow-500' }, + { value: 'dinner', label: 'Dinner', icon: Moon, color: 'text-indigo-500' }, + ].map(({ value, label, icon: Icon, color }) => ( + + ))} +
+
+
+ + + + +
+
+ + {/* Coverage Dialog */} + + + + + + Resident Coverage + + + {selectedOrder && ( + <> + {getMealTypeLabel(selectedOrder.mealType)} - {formatDate(selectedOrder.date)} + + )} + + + + {coverageLoading ? ( +
+ +
+ ) : ( +
+ {/* Coverage Summary */} +
+
+
+ Coverage Progress + + {coveredResidents.length}/{residents.length} residents + +
+ 0 + ? (coveredResidents.length / residents.length) * 100 + : 0 + } + className="h-3" + /> +
+
0 + ? (coveredResidents.length / residents.length) * 100 + : 0 + ) + )} + > + {residents.length > 0 + ? Math.round((coveredResidents.length / residents.length) * 100) + : 0} + % +
+
+ + {/* Uncovered Residents */} + {uncoveredResidents.length > 0 && ( +
+
+ +

+ Missing Meals ({uncoveredResidents.length}) +

+
+
+ {uncoveredResidents.map((resident) => ( +
+
+
{resident.name}
+
Room {resident.room}
+
+ +
+ ))} +
+
+ )} + + {/* Covered Residents */} + {coveredResidents.length > 0 && ( +
+
+ +

+ Covered Residents ({coveredResidents.length}) +

+
+
+ {coveredResidents.map((resident) => ( +
+
+
{resident.name}
+
Room {resident.room}
+
+ +
+ ))} +
+
+ )} +
+ )} + + + {selectedOrder?.status === 'draft' && ( + + )} + + +
+
) } diff --git a/src/app/(app)/caregiver/orders/[id]/page.tsx b/src/app/(app)/caregiver/orders/[id]/page.tsx new file mode 100644 index 0000000..7c9f371 --- /dev/null +++ b/src/app/(app)/caregiver/orders/[id]/page.tsx @@ -0,0 +1,1128 @@ +'use client' + +import React, { useState, useEffect, useCallback } from 'react' +import { useRouter, useParams } from 'next/navigation' +import Link from 'next/link' +import { + ArrowLeft, + Loader2, + Search, + Plus, + Check, + X, + AlertTriangle, + Send, + Pencil, + Eye, + Users, + ChefHat, + ClipboardList, +} from 'lucide-react' + +import { Button } from '@/components/ui/button' +import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card' +import { Input } from '@/components/ui/input' +import { Label } from '@/components/ui/label' +import { Badge } from '@/components/ui/badge' +import { Checkbox } from '@/components/ui/checkbox' +import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group' +import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert' +import { Separator } from '@/components/ui/separator' +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from '@/components/ui/table' +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from '@/components/ui/dialog' +import { + Sheet, + SheetContent, + SheetDescription, + SheetHeader, + SheetTitle, +} from '@/components/ui/sheet' +import { cn } from '@/lib/utils' + +interface Resident { + id: number + name: string + room: string + table?: string + station?: string + highCaloric?: boolean + aversions?: string + notes?: string +} + +interface Meal { + id: number + resident: Resident | number + mealType: 'breakfast' | 'lunch' | 'dinner' + status: 'pending' | 'preparing' | 'prepared' + breakfast?: BreakfastOptions + lunch?: LunchOptions + dinner?: DinnerOptions +} + +interface MealOrder { + id: number + title: string + date: string + mealType: 'breakfast' | 'lunch' | 'dinner' + status: 'draft' | 'submitted' | 'preparing' | 'completed' + mealCount: number + notes?: string +} + +interface BreakfastOptions { + accordingToPlan: boolean + bread: { + breadRoll: boolean + wholeGrainRoll: boolean + greyBread: boolean + wholeGrainBread: boolean + whiteBread: boolean + crispbread: boolean + } + porridge: boolean + preparation: { sliced: boolean; spread: boolean } + spreads: { + butter: boolean + margarine: boolean + jam: boolean + diabeticJam: boolean + honey: boolean + cheese: boolean + quark: boolean + sausage: boolean + } + beverages: { coffee: boolean; tea: boolean; hotMilk: boolean; coldMilk: boolean } + additions: { sugar: boolean; sweetener: boolean; coffeeCreamer: boolean } +} + +interface LunchOptions { + portionSize: 'small' | 'large' | 'vegetarian' + soup: boolean + dessert: boolean + specialPreparations: { + pureedFood: boolean + pureedMeat: boolean + slicedMeat: boolean + mashedPotatoes: boolean + } + restrictions: { noFish: boolean; fingerFood: boolean; onlySweet: boolean } +} + +interface DinnerOptions { + accordingToPlan: boolean + bread: { greyBread: boolean; wholeGrainBread: boolean; whiteBread: boolean; crispbread: boolean } + preparation: { spread: boolean; sliced: boolean } + spreads: { butter: boolean; margarine: boolean } + soup: boolean + porridge: boolean + noFish: boolean + beverages: { tea: boolean; cocoa: boolean; hotMilk: boolean; coldMilk: boolean } + additions: { sugar: boolean; sweetener: boolean } +} + +const defaultBreakfast: BreakfastOptions = { + accordingToPlan: false, + bread: { + breadRoll: false, + wholeGrainRoll: false, + greyBread: false, + wholeGrainBread: false, + whiteBread: false, + crispbread: false, + }, + porridge: false, + preparation: { sliced: false, spread: false }, + spreads: { + butter: false, + margarine: false, + jam: false, + diabeticJam: false, + honey: false, + cheese: false, + quark: false, + sausage: false, + }, + beverages: { coffee: false, tea: false, hotMilk: false, coldMilk: false }, + additions: { sugar: false, sweetener: false, coffeeCreamer: false }, +} + +const defaultLunch: LunchOptions = { + portionSize: 'large', + soup: false, + dessert: true, + specialPreparations: { + pureedFood: false, + pureedMeat: false, + slicedMeat: false, + mashedPotatoes: false, + }, + restrictions: { noFish: false, fingerFood: false, onlySweet: false }, +} + +const defaultDinner: DinnerOptions = { + accordingToPlan: false, + bread: { greyBread: false, wholeGrainBread: false, whiteBread: false, crispbread: false }, + preparation: { spread: false, sliced: false }, + spreads: { butter: false, margarine: false }, + soup: false, + porridge: false, + noFish: false, + beverages: { tea: false, cocoa: false, hotMilk: false, coldMilk: false }, + additions: { sugar: false, sweetener: false }, +} + +function CheckboxOption({ + id, + label, + checked, + onCheckedChange, +}: { + id: string + label: string + checked: boolean + onCheckedChange: (checked: boolean) => void +}) { + return ( +
onCheckedChange(!checked)} + role="button" + tabIndex={0} + onKeyDown={(e) => { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault() + onCheckedChange(!checked) + } + }} + > + e.stopPropagation()} + /> + +
+ ) +} + +export default function OrderDetailPage() { + const router = useRouter() + const params = useParams() + const orderId = params.id as string + + const [order, setOrder] = useState(null) + const [meals, setMeals] = useState([]) + const [residents, setResidents] = useState([]) + const [loading, setLoading] = useState(true) + const [searchQuery, setSearchQuery] = useState('') + const [showSummary, setShowSummary] = useState(false) + const [submitting, setSubmitting] = useState(false) + + // Meal form state + const [editingMeal, setEditingMeal] = useState(null) + const [selectedResident, setSelectedResident] = useState(null) + const [showMealForm, setShowMealForm] = useState(false) + const [breakfast, setBreakfast] = useState(defaultBreakfast) + const [lunch, setLunch] = useState(defaultLunch) + const [dinner, setDinner] = useState(defaultDinner) + const [savingMeal, setSavingMeal] = useState(false) + + const fetchData = useCallback(async () => { + setLoading(true) + try { + // Fetch order details + const orderRes = await fetch(`/api/meal-orders/${orderId}`, { + credentials: 'include', + }) + if (!orderRes.ok) { + if (orderRes.status === 401) { + router.push('/caregiver/login') + return + } + throw new Error('Failed to fetch order') + } + const orderData = await orderRes.json() + setOrder(orderData) + + // Fetch meals for this order + const mealsRes = await fetch( + `/api/meals?where[order][equals]=${orderId}&depth=1&limit=100`, + { credentials: 'include' }, + ) + if (mealsRes.ok) { + const mealsData = await mealsRes.json() + setMeals(mealsData.docs || []) + } + + // Fetch all active residents + const residentsRes = await fetch( + '/api/residents?where[active][equals]=true&limit=100&sort=name', + { credentials: 'include' }, + ) + if (residentsRes.ok) { + const residentsData = await residentsRes.json() + setResidents(residentsData.docs || []) + } + } catch (err) { + console.error('Error fetching data:', err) + } finally { + setLoading(false) + } + }, [orderId, router]) + + useEffect(() => { + fetchData() + }, [fetchData]) + + const getResidentMeal = (residentId: number): Meal | undefined => { + return meals.find((meal) => { + const mealResidentId = + typeof meal.resident === 'object' ? meal.resident.id : meal.resident + return mealResidentId === residentId + }) + } + + const filteredResidents = residents.filter( + (r) => + r.name.toLowerCase().includes(searchQuery.toLowerCase()) || + r.room.toLowerCase().includes(searchQuery.toLowerCase()), + ) + + const coveredResidents = residents.filter((r) => getResidentMeal(r.id)) + const uncoveredResidents = residents.filter((r) => !getResidentMeal(r.id)) + const coveragePercent = residents.length > 0 ? Math.round((coveredResidents.length / residents.length) * 100) : 0 + + const getMealTypeLabel = (type: string) => { + switch (type) { + case 'breakfast': + return 'Breakfast' + case 'lunch': + return 'Lunch' + case 'dinner': + return 'Dinner' + default: + return type + } + } + + const openMealForm = (resident: Resident, existingMeal?: Meal) => { + setSelectedResident(resident) + setEditingMeal(existingMeal || null) + + if (existingMeal) { + // Load existing meal data + if (existingMeal.breakfast) setBreakfast(existingMeal.breakfast) + if (existingMeal.lunch) setLunch(existingMeal.lunch) + if (existingMeal.dinner) setDinner(existingMeal.dinner) + } else { + // Reset to defaults + setBreakfast(defaultBreakfast) + setLunch(defaultLunch) + setDinner(defaultDinner) + } + + setShowMealForm(true) + } + + const closeMealForm = () => { + setShowMealForm(false) + setSelectedResident(null) + setEditingMeal(null) + } + + const handleSaveMeal = async () => { + if (!selectedResident || !order) return + + setSavingMeal(true) + try { + const mealData: Record = { + order: order.id, + resident: selectedResident.id, + date: order.date, + mealType: order.mealType, + status: 'pending', + } + + if (order.mealType === 'breakfast') { + mealData.breakfast = breakfast + } else if (order.mealType === 'lunch') { + mealData.lunch = lunch + } else if (order.mealType === 'dinner') { + mealData.dinner = dinner + } + + let res + if (editingMeal) { + // Update existing meal + res = await fetch(`/api/meals/${editingMeal.id}`, { + method: 'PATCH', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(mealData), + credentials: 'include', + }) + } else { + // Create new meal + res = await fetch('/api/meals', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(mealData), + credentials: 'include', + }) + } + + if (res.ok) { + // Update order meal count + await fetch(`/api/meal-orders/${order.id}`, { + method: 'PATCH', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ mealCount: editingMeal ? meals.length : meals.length + 1 }), + credentials: 'include', + }) + + closeMealForm() + fetchData() + } else { + const data = await res.json() + console.error('Error saving meal:', data) + } + } catch (err) { + console.error('Error saving meal:', err) + } finally { + setSavingMeal(false) + } + } + + const handleDeleteMeal = async (mealId: number) => { + if (!order || !confirm('Are you sure you want to remove this meal?')) return + + try { + const res = await fetch(`/api/meals/${mealId}`, { + method: 'DELETE', + credentials: 'include', + }) + + if (res.ok) { + // Update order meal count + await fetch(`/api/meal-orders/${order.id}`, { + method: 'PATCH', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ mealCount: meals.length - 1 }), + credentials: 'include', + }) + + fetchData() + } + } catch (err) { + console.error('Error deleting meal:', err) + } + } + + const handleSubmitToKitchen = async () => { + if (!order || meals.length === 0) return + + setSubmitting(true) + try { + const res = await fetch(`/api/meal-orders/${order.id}`, { + method: 'PATCH', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + status: 'submitted', + submittedAt: new Date().toISOString(), + }), + credentials: 'include', + }) + + if (res.ok) { + setShowSummary(false) + router.push('/caregiver/orders') + } + } catch (err) { + console.error('Error submitting order:', err) + } finally { + setSubmitting(false) + } + } + + const getMealSummary = (meal: Meal) => { + const items: string[] = [] + + if (meal.breakfast) { + if (meal.breakfast.accordingToPlan) items.push('According to Plan') + const breads = Object.entries(meal.breakfast.bread) + .filter(([, v]) => v) + .map(([k]) => k) + if (breads.length) items.push(`Bread: ${breads.length} types`) + if (meal.breakfast.porridge) items.push('Porridge') + const beverages = Object.entries(meal.breakfast.beverages) + .filter(([, v]) => v) + .map(([k]) => k) + if (beverages.length) items.push(`Beverages: ${beverages.join(', ')}`) + } + + if (meal.lunch) { + items.push(`Portion: ${meal.lunch.portionSize}`) + if (meal.lunch.soup) items.push('Soup') + if (meal.lunch.dessert) items.push('Dessert') + const preps = Object.entries(meal.lunch.specialPreparations) + .filter(([, v]) => v) + .map(([k]) => k) + if (preps.length) items.push(`Special: ${preps.join(', ')}`) + } + + if (meal.dinner) { + if (meal.dinner.accordingToPlan) items.push('According to Plan') + const breads = Object.entries(meal.dinner.bread) + .filter(([, v]) => v) + .map(([k]) => k) + if (breads.length) items.push(`Bread: ${breads.length} types`) + if (meal.dinner.soup) items.push('Soup') + if (meal.dinner.porridge) items.push('Porridge') + } + + return items.length > 0 ? items.slice(0, 3).join(', ') : 'Basic meal' + } + + if (loading) { + return ( +
+ +
+ ) + } + + if (!order) { + return ( +
+ + + +

Order Not Found

+

+ The order you're looking for doesn't exist. +

+ +
+
+
+ ) + } + + const isDraft = order.status === 'draft' + + return ( +
+
+
+
+ +
+

{order.title}

+

{order.date}

+
+
+
+ + {order.status.charAt(0).toUpperCase() + order.status.slice(1)} + +
+
+
+ +
+ {/* Coverage Stats */} +
+ + +
+
+ +
+
+

Total Residents

+

{residents.length}

+
+
+
+
+ + +
+
+ +
+
+

Meals Created

+

{coveredResidents.length}

+
+
+
+
+ + +
+
+ +
+
+

Missing Meals

+

{uncoveredResidents.length}

+
+
+
+
+ + +
+
+ +
+
+

Coverage

+

{coveragePercent}%

+
+
+
+
+
+ + {/* Progress Bar */} + + +
+ Meal Coverage + + {coveredResidents.length} of {residents.length} residents + +
+
+
+
+ + + + {/* Residents Table */} + + +
+
+ Residents + + {isDraft + ? 'Add meals for each resident before submitting to kitchen' + : 'View meals for all residents'} + +
+ {isDraft && meals.length > 0 && ( + + )} +
+
+ +
+ + setSearchQuery(e.target.value)} + className="pl-10" + /> +
+ + + + + Resident + Room + Station + Info + Status + Actions + + + + {filteredResidents.map((resident) => { + const meal = getResidentMeal(resident.id) + return ( + + {resident.name} + {resident.room} + {resident.station || '-'} + +
+ {resident.highCaloric && ( + + High Cal + + )} + {resident.aversions && ( + + Aversions + + )} +
+
+ + {meal ? ( + + + Meal Created + + ) : ( + + + No Meal + + )} + + + {isDraft ? ( +
+ {meal ? ( + <> + + + + ) : ( + + )} +
+ ) : meal ? ( + + ) : null} +
+
+ ) + })} +
+
+
+
+
+ + {/* Meal Form Sheet */} + + + + + {editingMeal ? 'Edit' : 'Add'} {getMealTypeLabel(order.mealType)} Meal + + + {selectedResident?.name} - Room {selectedResident?.room} + + + +
+ {(selectedResident?.aversions || selectedResident?.notes || selectedResident?.highCaloric) && ( + + + Notes for {selectedResident?.name} + + {selectedResident?.highCaloric && ( +
High caloric requirement
+ )} + {selectedResident?.aversions &&
Aversions: {selectedResident.aversions}
} + {selectedResident?.notes &&
{selectedResident.notes}
} +
+
+ )} + + {/* BREAKFAST OPTIONS */} + {order.mealType === 'breakfast' && ( + <> +
+

General

+ setBreakfast({ ...breakfast, accordingToPlan: v })} + /> +
+ + + +
+

Bread (Brot)

+
+ setBreakfast({ ...breakfast, bread: { ...breakfast.bread, breadRoll: v } })} /> + setBreakfast({ ...breakfast, bread: { ...breakfast.bread, wholeGrainRoll: v } })} /> + setBreakfast({ ...breakfast, bread: { ...breakfast.bread, greyBread: v } })} /> + setBreakfast({ ...breakfast, bread: { ...breakfast.bread, wholeGrainBread: v } })} /> + setBreakfast({ ...breakfast, bread: { ...breakfast.bread, whiteBread: v } })} /> + setBreakfast({ ...breakfast, bread: { ...breakfast.bread, crispbread: v } })} /> +
+
+ + + +
+

Preparation

+
+ setBreakfast({ ...breakfast, porridge: v })} /> + setBreakfast({ ...breakfast, preparation: { ...breakfast.preparation, sliced: v } })} /> + setBreakfast({ ...breakfast, preparation: { ...breakfast.preparation, spread: v } })} /> +
+
+ + + +
+

Spreads

+
+ setBreakfast({ ...breakfast, spreads: { ...breakfast.spreads, butter: v } })} /> + setBreakfast({ ...breakfast, spreads: { ...breakfast.spreads, margarine: v } })} /> + setBreakfast({ ...breakfast, spreads: { ...breakfast.spreads, jam: v } })} /> + setBreakfast({ ...breakfast, spreads: { ...breakfast.spreads, diabeticJam: v } })} /> + setBreakfast({ ...breakfast, spreads: { ...breakfast.spreads, honey: v } })} /> + setBreakfast({ ...breakfast, spreads: { ...breakfast.spreads, cheese: v } })} /> + setBreakfast({ ...breakfast, spreads: { ...breakfast.spreads, quark: v } })} /> + setBreakfast({ ...breakfast, spreads: { ...breakfast.spreads, sausage: v } })} /> +
+
+ + + +
+

Beverages

+
+ setBreakfast({ ...breakfast, beverages: { ...breakfast.beverages, coffee: v } })} /> + setBreakfast({ ...breakfast, beverages: { ...breakfast.beverages, tea: v } })} /> + setBreakfast({ ...breakfast, beverages: { ...breakfast.beverages, hotMilk: v } })} /> + setBreakfast({ ...breakfast, beverages: { ...breakfast.beverages, coldMilk: v } })} /> +
+
+ + + +
+

Additions

+
+ setBreakfast({ ...breakfast, additions: { ...breakfast.additions, sugar: v } })} /> + setBreakfast({ ...breakfast, additions: { ...breakfast.additions, sweetener: v } })} /> + setBreakfast({ ...breakfast, additions: { ...breakfast.additions, coffeeCreamer: v } })} /> +
+
+ + )} + + {/* LUNCH OPTIONS */} + {order.mealType === 'lunch' && ( + <> +
+

Portion Size

+ setLunch({ ...lunch, portionSize: v as 'small' | 'large' | 'vegetarian' })} + className="grid gap-2 grid-cols-3" + > + {[ + { value: 'small', label: 'Small' }, + { value: 'large', label: 'Large' }, + { value: 'vegetarian', label: 'Vegetarian' }, + ].map(({ value, label }) => ( +
setLunch({ ...lunch, portionSize: value as 'small' | 'large' | 'vegetarian' })} + > + + +
+ ))} +
+
+ + + +
+

Meal Options

+
+ setLunch({ ...lunch, soup: v })} /> + setLunch({ ...lunch, dessert: v })} /> +
+
+ + + +
+

Special Preparations

+
+ setLunch({ ...lunch, specialPreparations: { ...lunch.specialPreparations, pureedFood: v } })} /> + setLunch({ ...lunch, specialPreparations: { ...lunch.specialPreparations, pureedMeat: v } })} /> + setLunch({ ...lunch, specialPreparations: { ...lunch.specialPreparations, slicedMeat: v } })} /> + setLunch({ ...lunch, specialPreparations: { ...lunch.specialPreparations, mashedPotatoes: v } })} /> +
+
+ + + +
+

Restrictions

+
+ setLunch({ ...lunch, restrictions: { ...lunch.restrictions, noFish: v } })} /> + setLunch({ ...lunch, restrictions: { ...lunch.restrictions, fingerFood: v } })} /> + setLunch({ ...lunch, restrictions: { ...lunch.restrictions, onlySweet: v } })} /> +
+
+ + )} + + {/* DINNER OPTIONS */} + {order.mealType === 'dinner' && ( + <> +
+

General

+ setDinner({ ...dinner, accordingToPlan: v })} + /> +
+ + + +
+

Bread

+
+ setDinner({ ...dinner, bread: { ...dinner.bread, greyBread: v } })} /> + setDinner({ ...dinner, bread: { ...dinner.bread, wholeGrainBread: v } })} /> + setDinner({ ...dinner, bread: { ...dinner.bread, whiteBread: v } })} /> + setDinner({ ...dinner, bread: { ...dinner.bread, crispbread: v } })} /> +
+
+ + + +
+

Preparation

+
+ setDinner({ ...dinner, preparation: { ...dinner.preparation, spread: v } })} /> + setDinner({ ...dinner, preparation: { ...dinner.preparation, sliced: v } })} /> +
+
+ + + +
+

Spreads

+
+ setDinner({ ...dinner, spreads: { ...dinner.spreads, butter: v } })} /> + setDinner({ ...dinner, spreads: { ...dinner.spreads, margarine: v } })} /> +
+
+ + + +
+

Additional Items

+
+ setDinner({ ...dinner, soup: v })} /> + setDinner({ ...dinner, porridge: v })} /> + setDinner({ ...dinner, noFish: v })} /> +
+
+ + + +
+

Beverages

+
+ setDinner({ ...dinner, beverages: { ...dinner.beverages, tea: v } })} /> + setDinner({ ...dinner, beverages: { ...dinner.beverages, cocoa: v } })} /> + setDinner({ ...dinner, beverages: { ...dinner.beverages, hotMilk: v } })} /> + setDinner({ ...dinner, beverages: { ...dinner.beverages, coldMilk: v } })} /> +
+
+ + + +
+

Additions

+
+ setDinner({ ...dinner, additions: { ...dinner.additions, sugar: v } })} /> + setDinner({ ...dinner, additions: { ...dinner.additions, sweetener: v } })} /> +
+
+ + )} + +
+ + {isDraft && ( +
+ + +
+ )} +
+
+ + {/* Summary Dialog */} + + + + + + Review Meals Before Submission + + + {order.title} - {meals.length} meals for {order.date} + + + +
+ {uncoveredResidents.length > 0 && ( + + + Missing Meals + + {uncoveredResidents.length} residents don't have meals:{' '} + {uncoveredResidents.map((r) => r.name).join(', ')} + + + )} + +
+ {meals.map((meal) => { + const resident = + typeof meal.resident === 'object' ? meal.resident : null + return ( +
+
+
{resident?.name || 'Unknown'}
+
+ Room {resident?.room} - {getMealSummary(meal)} +
+
+ + + Ready + +
+ ) + })} +
+
+ + + + + +
+
+
+ ) +} diff --git a/src/app/(app)/caregiver/orders/page.tsx b/src/app/(app)/caregiver/orders/page.tsx index 825b418..a06a375 100644 --- a/src/app/(app)/caregiver/orders/page.tsx +++ b/src/app/(app)/caregiver/orders/page.tsx @@ -3,7 +3,7 @@ import React, { useState, useEffect } from 'react' import { useRouter } from 'next/navigation' import Link from 'next/link' -import { ArrowLeft, Plus, Loader2 } from 'lucide-react' +import { ArrowLeft, Plus, Loader2, Send, Eye, Pencil, Check, ChefHat } from 'lucide-react' import { Button } from '@/components/ui/button' import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' @@ -26,19 +26,13 @@ import { TableRow, } from '@/components/ui/table' -interface Resident { - id: number - name: string - room: string -} - interface MealOrder { id: number title: string date: string mealType: 'breakfast' | 'lunch' | 'dinner' - status: 'pending' | 'preparing' | 'prepared' - resident: Resident | number + status: 'draft' | 'submitted' | 'preparing' | 'completed' + mealCount: number createdAt: string } @@ -47,18 +41,18 @@ export default function OrdersListPage() { const [orders, setOrders] = useState([]) const [loading, setLoading] = useState(true) const [dateFilter, setDateFilter] = useState(() => new Date().toISOString().split('T')[0]) - const [mealTypeFilter, setMealTypeFilter] = useState('all') + const [statusFilter, setStatusFilter] = useState('all') useEffect(() => { const fetchOrders = async () => { setLoading(true) try { - let url = `/api/meal-orders?sort=-createdAt&limit=100&depth=1` + let url = `/api/meal-orders?sort=-date&limit=100&depth=0` if (dateFilter) { url += `&where[date][equals]=${dateFilter}` } - if (mealTypeFilter !== 'all') { - url += `&where[mealType][equals]=${mealTypeFilter}` + if (statusFilter !== 'all') { + url += `&where[status][equals]=${statusFilter}` } const res = await fetch(url, { credentials: 'include' }) @@ -75,7 +69,7 @@ export default function OrdersListPage() { } } fetchOrders() - }, [router, dateFilter, mealTypeFilter]) + }, [router, dateFilter, statusFilter]) const getMealTypeLabel = (type: string) => { switch (type) { @@ -92,22 +86,59 @@ export default function OrdersListPage() { const getStatusBadge = (status: string) => { switch (status) { - case 'pending': - return Pending + case 'draft': + return ( + + + Draft + + ) + case 'submitted': + return ( + + + Submitted + + ) case 'preparing': - return Preparing - case 'prepared': - return Prepared + return ( + + + Preparing + + ) + case 'completed': + return ( + + + Completed + + ) default: return {status} } } - const getResidentName = (resident: Resident | number) => { - if (typeof resident === 'object') { - return resident.name + const handleCreateOrder = async (mealType: 'breakfast' | 'lunch' | 'dinner') => { + try { + const res = await fetch('/api/meal-orders', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + date: dateFilter, + mealType, + status: 'draft', + }), + credentials: 'include', + }) + + if (res.ok) { + const data = await res.json() + router.push(`/caregiver/orders/${data.doc.id}`) + } + } catch (err) { + console.error('Error creating order:', err) } - return `Resident #${resident}` } return ( @@ -123,22 +154,16 @@ export default function OrdersListPage() {

Meal Orders

-
- Filter Orders + Filter & Create Orders -
+
- - - + - All Types - Breakfast - Lunch - Dinner + All Statuses + Draft + Submitted + Preparing + Completed
+
+ +
+ + + +
+
@@ -174,28 +232,48 @@ export default function OrdersListPage() {
) : orders.length === 0 ? (
-

No orders found for the selected criteria.

- +

No orders found for the selected date.

+

+ Create a new order using the buttons above. +

) : ( - Resident + Meal Type Date - Meal + Meals Status + Actions {orders.map((order) => ( - {getResidentName(order.resident)} + + {getMealTypeLabel(order.mealType)} + {order.date} - {getMealTypeLabel(order.mealType)} + {order.mealCount} residents {getStatusBadge(order.status)} + + + ))} diff --git a/src/app/(payload)/admin/importMap.js b/src/app/(payload)/admin/importMap.js index 51d1abf..f849e46 100644 --- a/src/app/(payload)/admin/importMap.js +++ b/src/app/(payload)/admin/importMap.js @@ -3,7 +3,7 @@ import { TenantField as TenantField_1d0591e3cf4f332c83a86da13a0de59a } from '@pa import { AssignTenantFieldTrigger as AssignTenantFieldTrigger_1d0591e3cf4f332c83a86da13a0de59a } from '@payloadcms/plugin-multi-tenant/client' import { TenantSelector as TenantSelector_d6d5f193a167989e2ee7d14202901e62 } from '@payloadcms/plugin-multi-tenant/rsc' import { TenantSelectionProvider as TenantSelectionProvider_d6d5f193a167989e2ee7d14202901e62 } from '@payloadcms/plugin-multi-tenant/rsc' -import { KitchenDashboard as KitchenDashboard_466f0c465119ff8e562eb80399daabc0 } from './views/KitchenDashboard' +import { KitchenDashboard as KitchenDashboard_466f0c465119ff8e562eb80399daabc0 } from '../../../../app/(payload)/admin/views/KitchenDashboard' export const importMap = { "@payloadcms/plugin-multi-tenant/client#WatchTenantCollection": WatchTenantCollection_1d0591e3cf4f332c83a86da13a0de59a, diff --git a/src/app/(payload)/admin/views/KitchenDashboard/index.tsx b/src/app/(payload)/admin/views/KitchenDashboard/index.tsx index 92c7284..4c16919 100644 --- a/src/app/(payload)/admin/views/KitchenDashboard/index.tsx +++ b/src/app/(payload)/admin/views/KitchenDashboard/index.tsx @@ -7,7 +7,7 @@ import './styles.scss' interface KitchenReportResponse { date: string mealType: string - totalOrders: number + totalMeals: number ingredients: Record labels: Record portionSizes?: Record @@ -31,7 +31,7 @@ export const KitchenDashboard: React.FC = () => { try { const response = await fetch( - `/api/meal-orders/kitchen-report?date=${date}&mealType=${mealType}`, + `/api/meals/kitchen-report?date=${date}&mealType=${mealType}`, { credentials: 'include', }, @@ -138,14 +138,14 @@ export const KitchenDashboard: React.FC = () => { Meal: {getMealTypeLabel(report.mealType)} - Total Orders: {report.totalOrders} + Total Meals: {report.totalMeals} - {report.totalOrders === 0 ? ( + {report.totalMeals === 0 ? (
-

No orders found for this date and meal type.

+

No meals found for this date and meal type.

) : ( <> diff --git a/src/collections/MealOrders/index.ts b/src/collections/MealOrders/index.ts index a7e7870..8e647a8 100644 --- a/src/collections/MealOrders/index.ts +++ b/src/collections/MealOrders/index.ts @@ -1,19 +1,18 @@ import type { CollectionConfig } from 'payload' import { isSuperAdmin } from '@/access/isSuperAdmin' import { hasTenantRole } from '@/access/roles' -import { setCreatedBy } from './hooks/setCreatedBy' -import { generateTitle } from './hooks/generateTitle' -import { kitchenReportEndpoint } from './endpoints/kitchenReport' /** * Meal Orders Collection * - * Represents a single meal order for a resident, including: - * - Date and meal type (breakfast, lunch, dinner) - * - Status tracking (pending, preparing, prepared) - * - Meal-specific options from the paper forms + * Represents a batch of meals for a specific date and meal type. + * Caregivers create an order, add individual meals for residents, then submit to kitchen. * - * Multi-tenant: each order belongs to a specific care home. + * Workflow: + * 1. Caregiver creates an order (draft status) + * 2. Caregiver adds individual meals for residents + * 3. Caregiver reviews summary and submits (submitted status) + * 4. Kitchen processes orders (preparing -> completed statuses) */ export const MealOrders: CollectionConfig = { slug: 'meal-orders', @@ -23,15 +22,10 @@ export const MealOrders: CollectionConfig = { }, admin: { useAsTitle: 'title', - description: 'Manage meal orders for residents', - defaultColumns: ['title', 'resident', 'date', 'mealType', 'status'], + description: 'Batch meals by date and meal type', + defaultColumns: ['title', 'date', 'mealType', 'status', 'mealCount'], group: 'Meal Planning', }, - endpoints: [kitchenReportEndpoint], - hooks: { - beforeChange: [setCreatedBy], - beforeValidate: [generateTitle], - }, access: { // Admin and caregiver can create orders create: ({ req }) => { @@ -39,23 +33,22 @@ export const MealOrders: CollectionConfig = { if (isSuperAdmin(req.user)) return true return hasTenantRole(req.user, 'admin') || hasTenantRole(req.user, 'caregiver') }, - // All authenticated users within the tenant can read orders + // All authenticated users within the tenant can read read: ({ req }) => { if (!req.user) return false return true // Multi-tenant plugin will filter by tenant }, - // Admin can update all, caregiver can update own pending orders, kitchen can update status + // Admin, caregiver (if draft), and kitchen can update update: ({ req }) => { if (!req.user) return false if (isSuperAdmin(req.user)) return true - // All tenant roles can update (with field-level restrictions) return ( hasTenantRole(req.user, 'admin') || hasTenantRole(req.user, 'caregiver') || hasTenantRole(req.user, 'kitchen') ) }, - // Only admin can delete orders + // Only admin can delete delete: ({ req }) => { if (!req.user) return false if (isSuperAdmin(req.user)) return true @@ -63,7 +56,6 @@ export const MealOrders: CollectionConfig = { }, }, fields: [ - // Core Fields { name: 'title', type: 'text', @@ -71,15 +63,26 @@ export const MealOrders: CollectionConfig = { readOnly: true, description: 'Auto-generated title', }, - }, - { - name: 'resident', - type: 'relationship', - relationTo: 'residents', - required: true, - index: true, - admin: { - description: 'Select the resident for this meal order', + hooks: { + beforeChange: [ + ({ data, operation }) => { + if (operation === 'create' || operation === 'update') { + const mealLabels: Record = { + breakfast: 'Breakfast', + lunch: 'Lunch', + dinner: 'Dinner', + } + const date = data?.date ? new Date(data.date).toLocaleDateString('en-US', { + weekday: 'short', + month: 'short', + day: 'numeric', + }) : '' + const mealType = mealLabels[data?.mealType || ''] || data?.mealType || '' + return `${mealType} - ${date}` + } + return data?.title + }, + ], }, }, { @@ -118,16 +121,39 @@ export const MealOrders: CollectionConfig = { name: 'status', type: 'select', required: true, - defaultValue: 'pending', + defaultValue: 'draft', index: true, options: [ - { label: 'Pending', value: 'pending' }, + { label: 'Draft (In Progress)', value: 'draft' }, + { label: 'Submitted to Kitchen', value: 'submitted' }, { label: 'Preparing', value: 'preparing' }, - { label: 'Prepared', value: 'prepared' }, + { label: 'Completed', value: 'completed' }, ], admin: { position: 'sidebar', - description: 'Order status for kitchen tracking', + description: 'Order status for workflow tracking', + }, + }, + { + name: 'mealCount', + type: 'number', + defaultValue: 0, + admin: { + position: 'sidebar', + readOnly: true, + description: 'Number of meals in this order', + }, + }, + { + name: 'submittedAt', + type: 'date', + admin: { + position: 'sidebar', + readOnly: true, + description: 'When the order was submitted to kitchen', + date: { + pickerAppearance: 'dayAndTime', + }, }, }, { @@ -140,300 +166,27 @@ export const MealOrders: CollectionConfig = { description: 'User who created this order', }, }, - - // Override Fields (optional per-order overrides) { - type: 'collapsible', - label: 'Order Overrides', + name: 'notes', + type: 'textarea', admin: { - initCollapsed: true, + description: 'General notes for this batch of meals', }, - fields: [ - { - name: 'highCaloric', - type: 'checkbox', - defaultValue: false, - admin: { - description: 'Override: high-caloric requirement for this order', - }, - }, - { - name: 'aversions', - type: 'textarea', - admin: { - description: 'Override: specific aversions for this order', - }, - }, - { - name: 'notes', - type: 'textarea', - admin: { - description: 'Special notes for this order', - }, - }, - ], - }, - - // ============================================ - // BREAKFAST FIELDS GROUP - // ============================================ - { - type: 'group', - name: 'breakfast', - label: 'Breakfast Options (Frühstück)', - admin: { - condition: (data) => data?.mealType === 'breakfast', - }, - fields: [ - { - name: 'accordingToPlan', - type: 'checkbox', - label: 'According to Plan (Frühstück lt. Plan)', - defaultValue: false, - }, - { - type: 'row', - fields: [ - { - type: 'group', - name: 'bread', - label: 'Bread Selection', - fields: [ - { name: 'breadRoll', type: 'checkbox', label: 'Bread Roll (Brötchen)' }, - { - name: 'wholeGrainRoll', - type: 'checkbox', - label: 'Whole Grain Roll (Vollkornbrötchen)', - }, - { name: 'greyBread', type: 'checkbox', label: 'Grey Bread (Graubrot)' }, - { - name: 'wholeGrainBread', - type: 'checkbox', - label: 'Whole Grain Bread (Vollkornbrot)', - }, - { name: 'whiteBread', type: 'checkbox', label: 'White Bread (Weißbrot)' }, - { name: 'crispbread', type: 'checkbox', label: 'Crispbread (Knäckebrot)' }, - ], - }, - ], - }, - { - name: 'porridge', - type: 'checkbox', - label: 'Porridge/Puree (Brei)', - }, - { - type: 'row', - fields: [ - { - type: 'group', - name: 'preparation', - label: 'Bread Preparation', - fields: [ - { name: 'sliced', type: 'checkbox', label: 'Sliced (geschnitten)' }, - { name: 'spread', type: 'checkbox', label: 'Spread (geschmiert)' }, - ], - }, - ], - }, - { - type: 'row', - fields: [ - { - type: 'group', - name: 'spreads', - label: 'Spreads', - fields: [ - { name: 'butter', type: 'checkbox', label: 'Butter' }, - { name: 'margarine', type: 'checkbox', label: 'Margarine' }, - { name: 'jam', type: 'checkbox', label: 'Jam (Konfitüre)' }, - { name: 'diabeticJam', type: 'checkbox', label: 'Diabetic Jam (Diab. Konfitüre)' }, - { name: 'honey', type: 'checkbox', label: 'Honey (Honig)' }, - { name: 'cheese', type: 'checkbox', label: 'Cheese (Käse)' }, - { name: 'quark', type: 'checkbox', label: 'Quark' }, - { name: 'sausage', type: 'checkbox', label: 'Sausage (Wurst)' }, - ], - }, - ], - }, - { - type: 'row', - fields: [ - { - type: 'group', - name: 'beverages', - label: 'Beverages', - fields: [ - { name: 'coffee', type: 'checkbox', label: 'Coffee (Kaffee)' }, - { name: 'tea', type: 'checkbox', label: 'Tea (Tee)' }, - { name: 'hotMilk', type: 'checkbox', label: 'Hot Milk (Milch heiß)' }, - { name: 'coldMilk', type: 'checkbox', label: 'Cold Milk (Milch kalt)' }, - ], - }, - ], - }, - { - type: 'row', - fields: [ - { - type: 'group', - name: 'additions', - label: 'Additions', - fields: [ - { name: 'sugar', type: 'checkbox', label: 'Sugar (Zucker)' }, - { name: 'sweetener', type: 'checkbox', label: 'Sweetener (Süßstoff)' }, - { name: 'coffeeCreamer', type: 'checkbox', label: 'Coffee Creamer (Kaffeesahne)' }, - ], - }, - ], - }, - ], - }, - - // ============================================ - // LUNCH FIELDS GROUP - // ============================================ - { - type: 'group', - name: 'lunch', - label: 'Lunch Options (Mittagessen)', - admin: { - condition: (data) => data?.mealType === 'lunch', - }, - fields: [ - { - name: 'portionSize', - type: 'select', - label: 'Portion Size', - options: [ - { label: 'Small Portion (Kleine Portion)', value: 'small' }, - { label: 'Large Portion (Große Portion)', value: 'large' }, - { - label: 'Vegetarian Whole-Food (Vollwertkost vegetarisch)', - value: 'vegetarian', - }, - ], - }, - { - type: 'row', - fields: [ - { name: 'soup', type: 'checkbox', label: 'Soup (Suppe)', admin: { width: '50%' } }, - { name: 'dessert', type: 'checkbox', label: 'Dessert', admin: { width: '50%' } }, - ], - }, - { - type: 'group', - name: 'specialPreparations', - label: 'Special Preparations', - fields: [ - { name: 'pureedFood', type: 'checkbox', label: 'Pureed Food (passierte Kost)' }, - { name: 'pureedMeat', type: 'checkbox', label: 'Pureed Meat (passiertes Fleisch)' }, - { name: 'slicedMeat', type: 'checkbox', label: 'Sliced Meat (geschnittenes Fleisch)' }, - { name: 'mashedPotatoes', type: 'checkbox', label: 'Mashed Potatoes (Kartoffelbrei)' }, - ], - }, - { - type: 'group', - name: 'restrictions', - label: 'Restrictions', - fields: [ - { name: 'noFish', type: 'checkbox', label: 'No Fish (ohne Fisch)' }, - { name: 'fingerFood', type: 'checkbox', label: 'Finger Food' }, - { name: 'onlySweet', type: 'checkbox', label: 'Only Sweet (nur süß)' }, - ], - }, - ], - }, - - // ============================================ - // DINNER FIELDS GROUP - // ============================================ - { - type: 'group', - name: 'dinner', - label: 'Dinner Options (Abendessen)', - admin: { - condition: (data) => data?.mealType === 'dinner', - }, - fields: [ - { - name: 'accordingToPlan', - type: 'checkbox', - label: 'According to Plan (Abendessen lt. Plan)', - defaultValue: false, - }, - { - type: 'group', - name: 'bread', - label: 'Bread Selection', - fields: [ - { name: 'greyBread', type: 'checkbox', label: 'Grey Bread (Graubrot)' }, - { - name: 'wholeGrainBread', - type: 'checkbox', - label: 'Whole Grain Bread (Vollkornbrot)', - }, - { name: 'whiteBread', type: 'checkbox', label: 'White Bread (Weißbrot)' }, - { name: 'crispbread', type: 'checkbox', label: 'Crispbread (Knäckebrot)' }, - ], - }, - { - type: 'group', - name: 'preparation', - label: 'Bread Preparation', - fields: [ - { name: 'spread', type: 'checkbox', label: 'Spread (geschmiert)' }, - { name: 'sliced', type: 'checkbox', label: 'Sliced (geschnitten)' }, - ], - }, - { - type: 'group', - name: 'spreads', - label: 'Spreads', - fields: [ - { name: 'butter', type: 'checkbox', label: 'Butter' }, - { name: 'margarine', type: 'checkbox', label: 'Margarine' }, - ], - }, - { - type: 'row', - fields: [ - { name: 'soup', type: 'checkbox', label: 'Soup (Suppe)', admin: { width: '33%' } }, - { - name: 'porridge', - type: 'checkbox', - label: 'Porridge (Brei)', - admin: { width: '33%' }, - }, - { - name: 'noFish', - type: 'checkbox', - label: 'No Fish (ohne Fisch)', - admin: { width: '33%' }, - }, - ], - }, - { - type: 'group', - name: 'beverages', - label: 'Beverages', - fields: [ - { name: 'tea', type: 'checkbox', label: 'Tea (Tee)' }, - { name: 'cocoa', type: 'checkbox', label: 'Cocoa (Kakao)' }, - { name: 'hotMilk', type: 'checkbox', label: 'Hot Milk (Milch heiß)' }, - { name: 'coldMilk', type: 'checkbox', label: 'Cold Milk (Milch kalt)' }, - ], - }, - { - type: 'group', - name: 'additions', - label: 'Additions', - fields: [ - { name: 'sugar', type: 'checkbox', label: 'Sugar (Zucker)' }, - { name: 'sweetener', type: 'checkbox', label: 'Sweetener (Süßstoff)' }, - ], - }, - ], }, ], + hooks: { + beforeChange: [ + // Set createdBy on create + ({ req, operation, data }) => { + if (operation === 'create' && req.user) { + data.createdBy = req.user.id + } + // Set submittedAt when status changes to submitted + if (data.status === 'submitted' && !data.submittedAt) { + data.submittedAt = new Date().toISOString() + } + return data + }, + ], + }, } diff --git a/src/collections/MealOrders/endpoints/kitchenReport.ts b/src/collections/Meals/endpoints/kitchenReport.ts similarity index 87% rename from src/collections/MealOrders/endpoints/kitchenReport.ts rename to src/collections/Meals/endpoints/kitchenReport.ts index 69e5ae8..b8e5446 100644 --- a/src/collections/MealOrders/endpoints/kitchenReport.ts +++ b/src/collections/Meals/endpoints/kitchenReport.ts @@ -82,11 +82,12 @@ function getNestedValue(obj: Record, path: string): unknown { /** * Kitchen Report API Endpoint * - * GET /api/meal-orders/kitchen-report + * GET /api/meals/kitchen-report * * Query Parameters: * - date (required): YYYY-MM-DD format * - mealType (required): breakfast | lunch | dinner + * - order (optional): filter by meal order ID * * Returns aggregated ingredient counts for the specified date and meal type. * Only accessible by users with admin or kitchen role. @@ -114,6 +115,7 @@ export const kitchenReportEndpoint: Endpoint = { const url = new URL(req.url || '', 'http://localhost') const date = url.searchParams.get('date') const mealType = url.searchParams.get('mealType') + const orderId = url.searchParams.get('order') // Validate parameters if (!date) { @@ -134,24 +136,26 @@ export const kitchenReportEndpoint: Endpoint = { } try { - // Query meal orders for the specified date and meal type - const orders = await payload.find({ - collection: 'meal-orders', - where: { - and: [ - { - date: { - equals: date, - }, - }, - { - mealType: { - equals: mealType, - }, - }, - ], - }, - limit: 1000, // Get all orders for the day + // Build the where clause + const whereClause: { + and: Array<{ date?: { equals: string }; mealType?: { equals: string }; order?: { equals: number } }> + } = { + and: [ + { date: { equals: date } }, + { mealType: { equals: mealType } }, + ], + } + + // Optionally filter by meal order + if (orderId) { + whereClause.and.push({ order: { equals: Number(orderId) } }) + } + + // Query meals for the specified date and meal type + const meals = await payload.find({ + collection: 'meals', + where: whereClause, + limit: 1000, // Get all meals for the day depth: 0, }) @@ -179,18 +183,18 @@ export const kitchenReportEndpoint: Endpoint = { } // Count occurrences - for (const order of orders.docs) { + for (const meal of meals.docs) { // Count boolean fields for (const fieldPath of Object.keys(fieldMapping)) { - const value = getNestedValue(order as unknown as Record, fieldPath) + const value = getNestedValue(meal as unknown as Record, fieldPath) if (value === true) { ingredients[fieldPath].count++ } } // Count lunch portion sizes - if (mealType === 'lunch' && order.lunch?.portionSize) { - const size = order.lunch.portionSize as string + if (mealType === 'lunch' && meal.lunch?.portionSize) { + const size = meal.lunch.portionSize as string if (size in portionSizes) { portionSizes[size]++ } @@ -214,7 +218,7 @@ export const kitchenReportEndpoint: Endpoint = { const response: Record = { date, mealType, - totalOrders: orders.totalDocs, + totalMeals: meals.totalDocs, ingredients: ingredientCounts, labels: ingredientLabels, } diff --git a/src/collections/MealOrders/hooks/generateTitle.ts b/src/collections/Meals/hooks/generateTitle.ts similarity index 96% rename from src/collections/MealOrders/hooks/generateTitle.ts rename to src/collections/Meals/hooks/generateTitle.ts index 0efa9e9..481f4f6 100644 --- a/src/collections/MealOrders/hooks/generateTitle.ts +++ b/src/collections/Meals/hooks/generateTitle.ts @@ -4,7 +4,7 @@ import type { CollectionBeforeValidateHook } from 'payload' * Hook to auto-generate the title field from date, meal type, and resident name * Format: "Breakfast - 2024-01-15 - John Doe" */ -export const generateTitle: CollectionBeforeValidateHook = async ({ data, req, operation }) => { +export const generateTitle: CollectionBeforeValidateHook = async ({ data, req, operation: _operation }) => { if (!data) return data const mealType = data.mealType diff --git a/src/collections/MealOrders/hooks/setCreatedBy.ts b/src/collections/Meals/hooks/setCreatedBy.ts similarity index 100% rename from src/collections/MealOrders/hooks/setCreatedBy.ts rename to src/collections/Meals/hooks/setCreatedBy.ts diff --git a/src/collections/Meals/index.ts b/src/collections/Meals/index.ts new file mode 100644 index 0000000..070a4f1 --- /dev/null +++ b/src/collections/Meals/index.ts @@ -0,0 +1,448 @@ +import type { CollectionConfig } from 'payload' +import { isSuperAdmin } from '@/access/isSuperAdmin' +import { hasTenantRole } from '@/access/roles' +import { setCreatedBy } from './hooks/setCreatedBy' +import { generateTitle } from './hooks/generateTitle' +import { kitchenReportEndpoint } from './endpoints/kitchenReport' + +/** + * Meals Collection + * + * Represents a single meal for a resident, including: + * - Date and meal type (breakfast, lunch, dinner) + * - Status tracking (pending, preparing, prepared) + * - Meal-specific options from the paper forms + * + * Multi-tenant: each meal belongs to a specific care home. + */ +export const Meals: CollectionConfig = { + slug: 'meals', + labels: { + singular: 'Meal', + plural: 'Meals', + }, + admin: { + useAsTitle: 'title', + description: 'Manage meals for residents', + defaultColumns: ['title', 'resident', 'date', 'mealType', 'status'], + group: 'Meal Planning', + }, + endpoints: [kitchenReportEndpoint], + hooks: { + beforeChange: [setCreatedBy], + beforeValidate: [generateTitle], + }, + access: { + // Admin and caregiver can create meals + create: ({ req }) => { + if (!req.user) return false + if (isSuperAdmin(req.user)) return true + return hasTenantRole(req.user, 'admin') || hasTenantRole(req.user, 'caregiver') + }, + // All authenticated users within the tenant can read meals + read: ({ req }) => { + if (!req.user) return false + return true // Multi-tenant plugin will filter by tenant + }, + // Admin can update all, caregiver can update own pending meals, kitchen can update status + update: ({ req }) => { + if (!req.user) return false + if (isSuperAdmin(req.user)) return true + // All tenant roles can update (with field-level restrictions) + return ( + hasTenantRole(req.user, 'admin') || + hasTenantRole(req.user, 'caregiver') || + hasTenantRole(req.user, 'kitchen') + ) + }, + // Only admin can delete meals + delete: ({ req }) => { + if (!req.user) return false + if (isSuperAdmin(req.user)) return true + return hasTenantRole(req.user, 'admin') + }, + }, + fields: [ + // Core Fields + { + name: 'title', + type: 'text', + admin: { + readOnly: true, + description: 'Auto-generated title', + }, + }, + { + name: 'order', + type: 'relationship', + relationTo: 'meal-orders', + index: true, + admin: { + description: 'The meal order this meal belongs to', + }, + }, + { + name: 'resident', + type: 'relationship', + relationTo: 'residents', + required: true, + index: true, + admin: { + description: 'Select the resident for this meal', + }, + }, + { + type: 'row', + fields: [ + { + name: 'date', + type: 'date', + required: true, + index: true, + admin: { + date: { + pickerAppearance: 'dayOnly', + displayFormat: 'yyyy-MM-dd', + }, + width: '50%', + }, + }, + { + name: 'mealType', + type: 'select', + required: true, + index: true, + options: [ + { label: 'Breakfast (Frühstück)', value: 'breakfast' }, + { label: 'Lunch (Mittagessen)', value: 'lunch' }, + { label: 'Dinner (Abendessen)', value: 'dinner' }, + ], + admin: { + width: '50%', + }, + }, + ], + }, + { + name: 'status', + type: 'select', + required: true, + defaultValue: 'pending', + index: true, + options: [ + { label: 'Pending', value: 'pending' }, + { label: 'Preparing', value: 'preparing' }, + { label: 'Prepared', value: 'prepared' }, + ], + admin: { + position: 'sidebar', + description: 'Meal status for kitchen tracking', + }, + }, + { + name: 'createdBy', + type: 'relationship', + relationTo: 'users', + admin: { + position: 'sidebar', + readOnly: true, + description: 'User who created this meal', + }, + }, + + // Override Fields (optional per-meal overrides) + { + type: 'collapsible', + label: 'Meal Overrides', + admin: { + initCollapsed: true, + }, + fields: [ + { + name: 'highCaloric', + type: 'checkbox', + defaultValue: false, + admin: { + description: 'Override: high-caloric requirement for this meal', + }, + }, + { + name: 'aversions', + type: 'textarea', + admin: { + description: 'Override: specific aversions for this meal', + }, + }, + { + name: 'notes', + type: 'textarea', + admin: { + description: 'Special notes for this meal', + }, + }, + ], + }, + + // ============================================ + // BREAKFAST FIELDS GROUP + // ============================================ + { + type: 'group', + name: 'breakfast', + label: 'Breakfast Options (Frühstück)', + admin: { + condition: (data) => data?.mealType === 'breakfast', + }, + fields: [ + { + name: 'accordingToPlan', + type: 'checkbox', + label: 'According to Plan (Frühstück lt. Plan)', + defaultValue: false, + }, + { + type: 'row', + fields: [ + { + type: 'group', + name: 'bread', + label: 'Bread Selection', + fields: [ + { name: 'breadRoll', type: 'checkbox', label: 'Bread Roll (Brötchen)' }, + { + name: 'wholeGrainRoll', + type: 'checkbox', + label: 'Whole Grain Roll (Vollkornbrötchen)', + }, + { name: 'greyBread', type: 'checkbox', label: 'Grey Bread (Graubrot)' }, + { + name: 'wholeGrainBread', + type: 'checkbox', + label: 'Whole Grain Bread (Vollkornbrot)', + }, + { name: 'whiteBread', type: 'checkbox', label: 'White Bread (Weißbrot)' }, + { name: 'crispbread', type: 'checkbox', label: 'Crispbread (Knäckebrot)' }, + ], + }, + ], + }, + { + name: 'porridge', + type: 'checkbox', + label: 'Porridge/Puree (Brei)', + }, + { + type: 'row', + fields: [ + { + type: 'group', + name: 'preparation', + label: 'Bread Preparation', + fields: [ + { name: 'sliced', type: 'checkbox', label: 'Sliced (geschnitten)' }, + { name: 'spread', type: 'checkbox', label: 'Spread (geschmiert)' }, + ], + }, + ], + }, + { + type: 'row', + fields: [ + { + type: 'group', + name: 'spreads', + label: 'Spreads', + fields: [ + { name: 'butter', type: 'checkbox', label: 'Butter' }, + { name: 'margarine', type: 'checkbox', label: 'Margarine' }, + { name: 'jam', type: 'checkbox', label: 'Jam (Konfitüre)' }, + { name: 'diabeticJam', type: 'checkbox', label: 'Diabetic Jam (Diab. Konfitüre)' }, + { name: 'honey', type: 'checkbox', label: 'Honey (Honig)' }, + { name: 'cheese', type: 'checkbox', label: 'Cheese (Käse)' }, + { name: 'quark', type: 'checkbox', label: 'Quark' }, + { name: 'sausage', type: 'checkbox', label: 'Sausage (Wurst)' }, + ], + }, + ], + }, + { + type: 'row', + fields: [ + { + type: 'group', + name: 'beverages', + label: 'Beverages', + fields: [ + { name: 'coffee', type: 'checkbox', label: 'Coffee (Kaffee)' }, + { name: 'tea', type: 'checkbox', label: 'Tea (Tee)' }, + { name: 'hotMilk', type: 'checkbox', label: 'Hot Milk (Milch heiß)' }, + { name: 'coldMilk', type: 'checkbox', label: 'Cold Milk (Milch kalt)' }, + ], + }, + ], + }, + { + type: 'row', + fields: [ + { + type: 'group', + name: 'additions', + label: 'Additions', + fields: [ + { name: 'sugar', type: 'checkbox', label: 'Sugar (Zucker)' }, + { name: 'sweetener', type: 'checkbox', label: 'Sweetener (Süßstoff)' }, + { name: 'coffeeCreamer', type: 'checkbox', label: 'Coffee Creamer (Kaffeesahne)' }, + ], + }, + ], + }, + ], + }, + + // ============================================ + // LUNCH FIELDS GROUP + // ============================================ + { + type: 'group', + name: 'lunch', + label: 'Lunch Options (Mittagessen)', + admin: { + condition: (data) => data?.mealType === 'lunch', + }, + fields: [ + { + name: 'portionSize', + type: 'select', + label: 'Portion Size', + options: [ + { label: 'Small Portion (Kleine Portion)', value: 'small' }, + { label: 'Large Portion (Große Portion)', value: 'large' }, + { + label: 'Vegetarian Whole-Food (Vollwertkost vegetarisch)', + value: 'vegetarian', + }, + ], + }, + { + type: 'row', + fields: [ + { name: 'soup', type: 'checkbox', label: 'Soup (Suppe)', admin: { width: '50%' } }, + { name: 'dessert', type: 'checkbox', label: 'Dessert', admin: { width: '50%' } }, + ], + }, + { + type: 'group', + name: 'specialPreparations', + label: 'Special Preparations', + fields: [ + { name: 'pureedFood', type: 'checkbox', label: 'Pureed Food (passierte Kost)' }, + { name: 'pureedMeat', type: 'checkbox', label: 'Pureed Meat (passiertes Fleisch)' }, + { name: 'slicedMeat', type: 'checkbox', label: 'Sliced Meat (geschnittenes Fleisch)' }, + { name: 'mashedPotatoes', type: 'checkbox', label: 'Mashed Potatoes (Kartoffelbrei)' }, + ], + }, + { + type: 'group', + name: 'restrictions', + label: 'Restrictions', + fields: [ + { name: 'noFish', type: 'checkbox', label: 'No Fish (ohne Fisch)' }, + { name: 'fingerFood', type: 'checkbox', label: 'Finger Food' }, + { name: 'onlySweet', type: 'checkbox', label: 'Only Sweet (nur süß)' }, + ], + }, + ], + }, + + // ============================================ + // DINNER FIELDS GROUP + // ============================================ + { + type: 'group', + name: 'dinner', + label: 'Dinner Options (Abendessen)', + admin: { + condition: (data) => data?.mealType === 'dinner', + }, + fields: [ + { + name: 'accordingToPlan', + type: 'checkbox', + label: 'According to Plan (Abendessen lt. Plan)', + defaultValue: false, + }, + { + type: 'group', + name: 'bread', + label: 'Bread Selection', + fields: [ + { name: 'greyBread', type: 'checkbox', label: 'Grey Bread (Graubrot)' }, + { + name: 'wholeGrainBread', + type: 'checkbox', + label: 'Whole Grain Bread (Vollkornbrot)', + }, + { name: 'whiteBread', type: 'checkbox', label: 'White Bread (Weißbrot)' }, + { name: 'crispbread', type: 'checkbox', label: 'Crispbread (Knäckebrot)' }, + ], + }, + { + type: 'group', + name: 'preparation', + label: 'Bread Preparation', + fields: [ + { name: 'spread', type: 'checkbox', label: 'Spread (geschmiert)' }, + { name: 'sliced', type: 'checkbox', label: 'Sliced (geschnitten)' }, + ], + }, + { + type: 'group', + name: 'spreads', + label: 'Spreads', + fields: [ + { name: 'butter', type: 'checkbox', label: 'Butter' }, + { name: 'margarine', type: 'checkbox', label: 'Margarine' }, + ], + }, + { + type: 'row', + fields: [ + { name: 'soup', type: 'checkbox', label: 'Soup (Suppe)', admin: { width: '33%' } }, + { + name: 'porridge', + type: 'checkbox', + label: 'Porridge (Brei)', + admin: { width: '33%' }, + }, + { + name: 'noFish', + type: 'checkbox', + label: 'No Fish (ohne Fisch)', + admin: { width: '33%' }, + }, + ], + }, + { + type: 'group', + name: 'beverages', + label: 'Beverages', + fields: [ + { name: 'tea', type: 'checkbox', label: 'Tea (Tee)' }, + { name: 'cocoa', type: 'checkbox', label: 'Cocoa (Kakao)' }, + { name: 'hotMilk', type: 'checkbox', label: 'Hot Milk (Milch heiß)' }, + { name: 'coldMilk', type: 'checkbox', label: 'Cold Milk (Milch kalt)' }, + ], + }, + { + type: 'group', + name: 'additions', + label: 'Additions', + fields: [ + { name: 'sugar', type: 'checkbox', label: 'Sugar (Zucker)' }, + { name: 'sweetener', type: 'checkbox', label: 'Sweetener (Süßstoff)' }, + ], + }, + ], + }, + ], +} diff --git a/src/collections/Residents/index.ts b/src/collections/Residents/index.ts index b134796..b977640 100644 --- a/src/collections/Residents/index.ts +++ b/src/collections/Residents/index.ts @@ -1,5 +1,5 @@ import type { CollectionConfig } from 'payload' -import { isSuperAdmin, isSuperAdminAccess } from '@/access/isSuperAdmin' +import { isSuperAdmin } from '@/access/isSuperAdmin' import { hasTenantRole } from '@/access/roles' /** diff --git a/src/collections/Users/endpoints/externalUsersLogin.ts b/src/collections/Users/endpoints/externalUsersLogin.ts index c6ccde3..8d567cd 100644 --- a/src/collections/Users/endpoints/externalUsersLogin.ts +++ b/src/collections/Users/endpoints/externalUsersLogin.ts @@ -13,7 +13,7 @@ export const externalUsersLogin: Endpoint = { if (typeof req.json === 'function') { data = await req.json() } - } catch (error) { + } catch (_error) { // swallow error, data is already empty object } const { password, tenantSlug, tenantDomain, username } = data @@ -113,7 +113,7 @@ export const externalUsersLogin: Endpoint = { null, true, ) - } catch (e) { + } catch (_e) { throw new APIError( 'Unable to login with the provided username and password.', 400, diff --git a/src/collections/Users/hooks/ensureUniqueUsername.ts b/src/collections/Users/hooks/ensureUniqueUsername.ts index 787e210..61a5eba 100644 --- a/src/collections/Users/hooks/ensureUniqueUsername.ts +++ b/src/collections/Users/hooks/ensureUniqueUsername.ts @@ -3,11 +3,10 @@ import type { FieldHook, Where } from 'payload' import { ValidationError } from 'payload' import { getUserTenantIDs } from '../../../utilities/getUserTenantIDs' -import { extractID } from '@/utilities/extractID' import { getTenantFromCookie } from '@payloadcms/plugin-multi-tenant/utilities' import { getCollectionIDType } from '@/utilities/getCollectionIDType' -export const ensureUniqueUsername: FieldHook = async ({ data, originalDoc, req, value }) => { +export const ensureUniqueUsername: FieldHook = async ({ data: _data, originalDoc, req, value }) => { // if value is unchanged, skip validation if (originalDoc.username === value) { return value @@ -47,7 +46,7 @@ export const ensureUniqueUsername: FieldHook = async ({ data, originalDoc, req, // provide a more specific error message if (req.user.roles?.includes('super-admin') || tenantIDs.length > 1) { const attemptedTenantChange = await req.payload.findByID({ - // @ts-ignore - selectedTenant will match DB ID type + // @ts-expect-error - selectedTenant will match DB ID type id: selectedTenant, collection: 'tenants', }) diff --git a/src/components/ui/dialog.tsx b/src/components/ui/dialog.tsx new file mode 100644 index 0000000..d9ccec9 --- /dev/null +++ b/src/components/ui/dialog.tsx @@ -0,0 +1,143 @@ +"use client" + +import * as React from "react" +import * as DialogPrimitive from "@radix-ui/react-dialog" +import { XIcon } from "lucide-react" + +import { cn } from "@/lib/utils" + +function Dialog({ + ...props +}: React.ComponentProps) { + return +} + +function DialogTrigger({ + ...props +}: React.ComponentProps) { + return +} + +function DialogPortal({ + ...props +}: React.ComponentProps) { + return +} + +function DialogClose({ + ...props +}: React.ComponentProps) { + return +} + +function DialogOverlay({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function DialogContent({ + className, + children, + showCloseButton = true, + ...props +}: React.ComponentProps & { + showCloseButton?: boolean +}) { + return ( + + + + {children} + {showCloseButton && ( + + + Close + + )} + + + ) +} + +function DialogHeader({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function DialogFooter({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function DialogTitle({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function DialogDescription({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +export { + Dialog, + DialogClose, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogOverlay, + DialogPortal, + DialogTitle, + DialogTrigger, +} diff --git a/src/components/ui/progress.tsx b/src/components/ui/progress.tsx new file mode 100644 index 0000000..e7a416c --- /dev/null +++ b/src/components/ui/progress.tsx @@ -0,0 +1,31 @@ +"use client" + +import * as React from "react" +import * as ProgressPrimitive from "@radix-ui/react-progress" + +import { cn } from "@/lib/utils" + +function Progress({ + className, + value, + ...props +}: React.ComponentProps) { + return ( + + + + ) +} + +export { Progress } diff --git a/src/components/ui/sheet.tsx b/src/components/ui/sheet.tsx new file mode 100644 index 0000000..84649ad --- /dev/null +++ b/src/components/ui/sheet.tsx @@ -0,0 +1,139 @@ +"use client" + +import * as React from "react" +import * as SheetPrimitive from "@radix-ui/react-dialog" +import { XIcon } from "lucide-react" + +import { cn } from "@/lib/utils" + +function Sheet({ ...props }: React.ComponentProps) { + return +} + +function SheetTrigger({ + ...props +}: React.ComponentProps) { + return +} + +function SheetClose({ + ...props +}: React.ComponentProps) { + return +} + +function SheetPortal({ + ...props +}: React.ComponentProps) { + return +} + +function SheetOverlay({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function SheetContent({ + className, + children, + side = "right", + ...props +}: React.ComponentProps & { + side?: "top" | "right" | "bottom" | "left" +}) { + return ( + + + + {children} + + + Close + + + + ) +} + +function SheetHeader({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function SheetFooter({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function SheetTitle({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function SheetDescription({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +export { + Sheet, + SheetTrigger, + SheetClose, + SheetContent, + SheetHeader, + SheetFooter, + SheetTitle, + SheetDescription, +} diff --git a/src/components/ui/tooltip.tsx b/src/components/ui/tooltip.tsx new file mode 100644 index 0000000..a4e90d4 --- /dev/null +++ b/src/components/ui/tooltip.tsx @@ -0,0 +1,61 @@ +"use client" + +import * as React from "react" +import * as TooltipPrimitive from "@radix-ui/react-tooltip" + +import { cn } from "@/lib/utils" + +function TooltipProvider({ + delayDuration = 0, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function Tooltip({ + ...props +}: React.ComponentProps) { + return ( + + + + ) +} + +function TooltipTrigger({ + ...props +}: React.ComponentProps) { + return +} + +function TooltipContent({ + className, + sideOffset = 0, + children, + ...props +}: React.ComponentProps) { + return ( + + + {children} + + + + ) +} + +export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider } diff --git a/src/payload-types.ts b/src/payload-types.ts index f44598e..65e9b6d 100644 --- a/src/payload-types.ts +++ b/src/payload-types.ts @@ -71,6 +71,7 @@ export interface Config { tenants: Tenant; residents: Resident; 'meal-orders': MealOrder; + meals: Meal; 'payload-kv': PayloadKv; 'payload-locked-documents': PayloadLockedDocument; 'payload-preferences': PayloadPreference; @@ -82,6 +83,7 @@ export interface Config { tenants: TenantsSelect | TenantsSelect; residents: ResidentsSelect | ResidentsSelect; 'meal-orders': MealOrdersSelect | MealOrdersSelect; + meals: MealsSelect | MealsSelect; 'payload-kv': PayloadKvSelect | PayloadKvSelect; 'payload-locked-documents': PayloadLockedDocumentsSelect | PayloadLockedDocumentsSelect; 'payload-preferences': PayloadPreferencesSelect | PayloadPreferencesSelect; @@ -242,7 +244,7 @@ export interface Resident { createdAt: string; } /** - * Manage meal orders for residents + * Batch meals by date and meal type * * This interface was referenced by `Config`'s JSON-Schema * via the `definition` "meal-orders". @@ -254,30 +256,72 @@ export interface MealOrder { * Auto-generated title */ title?: string | null; - /** - * Select the resident for this meal order - */ - resident: number | Resident; date: string; mealType: 'breakfast' | 'lunch' | 'dinner'; /** - * Order status for kitchen tracking + * Order status for workflow tracking */ - status: 'pending' | 'preparing' | 'prepared'; + status: 'draft' | 'submitted' | 'preparing' | 'completed'; + /** + * Number of meals in this order + */ + mealCount?: number | null; + /** + * When the order was submitted to kitchen + */ + submittedAt?: string | null; /** * User who created this order */ createdBy?: (number | null) | User; /** - * Override: high-caloric requirement for this order + * General notes for this batch of meals + */ + notes?: string | null; + updatedAt: string; + createdAt: string; +} +/** + * Manage meals for residents + * + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "meals". + */ +export interface Meal { + id: number; + tenant?: (number | null) | Tenant; + /** + * Auto-generated title + */ + title?: string | null; + /** + * The meal order this meal belongs to + */ + order?: (number | null) | MealOrder; + /** + * Select the resident for this meal + */ + resident: number | Resident; + date: string; + mealType: 'breakfast' | 'lunch' | 'dinner'; + /** + * Meal status for kitchen tracking + */ + status: 'pending' | 'preparing' | 'prepared'; + /** + * User who created this meal + */ + createdBy?: (number | null) | User; + /** + * Override: high-caloric requirement for this meal */ highCaloric?: boolean | null; /** - * Override: specific aversions for this order + * Override: specific aversions for this meal */ aversions?: string | null; /** - * Special notes for this order + * Special notes for this meal */ notes?: string | null; breakfast?: { @@ -405,6 +449,10 @@ export interface PayloadLockedDocument { | ({ relationTo: 'meal-orders'; value: number | MealOrder; + } | null) + | ({ + relationTo: 'meals'; + value: number | Meal; } | null); globalSlug?: string | null; user: { @@ -518,6 +566,24 @@ export interface ResidentsSelect { export interface MealOrdersSelect { tenant?: T; title?: T; + date?: T; + mealType?: T; + status?: T; + mealCount?: T; + submittedAt?: T; + createdBy?: T; + notes?: T; + updatedAt?: T; + createdAt?: T; +} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "meals_select". + */ +export interface MealsSelect { + tenant?: T; + title?: T; + order?: T; resident?: T; date?: T; mealType?: T; diff --git a/src/payload.config.ts b/src/payload.config.ts index 7902e61..c9a165f 100644 --- a/src/payload.config.ts +++ b/src/payload.config.ts @@ -1,13 +1,16 @@ import { sqliteAdapter } from '@payloadcms/db-sqlite' +import { postgresAdapter } from '@payloadcms/db-postgres' import { lexicalEditor } from '@payloadcms/richtext-lexical' import path from 'path' import { buildConfig } from 'payload' import { fileURLToPath } from 'url' +import { s3Storage } from '@payloadcms/storage-s3' import { Tenants } from './collections/Tenants' import Users from './collections/Users' import { Residents } from './collections/Residents' import { MealOrders } from './collections/MealOrders' +import { Meals } from './collections/Meals' import { multiTenantPlugin } from '@payloadcms/plugin-multi-tenant' import { isSuperAdmin } from './access/isSuperAdmin' import type { Config } from './payload-types' @@ -33,12 +36,18 @@ export default buildConfig({ }, }, }, - collections: [Users, Tenants, Residents, MealOrders], - db: sqliteAdapter({ - client: { - url: 'file:./payload.db', - }, - }), + collections: [Users, Tenants, Residents, MealOrders, Meals], + db: process.env.DATABASE_URI + ? postgresAdapter({ + pool: { + connectionString: process.env.DATABASE_URI, + }, + }) + : sqliteAdapter({ + client: { + url: 'file:./meal-planner.db', + }, + }), onInit: async (args) => { if (process.env.SEED_DB) { await seed(args) @@ -53,11 +62,33 @@ export default buildConfig({ outputFile: path.resolve(dirname, 'payload-types.ts'), }, plugins: [ + // Conditionally add S3 storage when MINIO_ENDPOINT is set (Docker environment) + ...(process.env.MINIO_ENDPOINT + ? [ + s3Storage({ + collections: { + // You can add specific collections here if needed + // For now, it applies to all collections with upload fields + }, + bucket: process.env.S3_BUCKET || 'meal-planner', + config: { + endpoint: process.env.MINIO_ENDPOINT, + region: process.env.S3_REGION || 'us-east-1', + credentials: { + accessKeyId: process.env.S3_ACCESS_KEY_ID || '', + secretAccessKey: process.env.S3_SECRET_ACCESS_KEY || '', + }, + forcePathStyle: true, // Required for MinIO + }, + }), + ] + : []), multiTenantPlugin({ collections: { - // Enable multi-tenancy for residents and meal orders + // Enable multi-tenancy for residents, meal orders, and meals residents: {}, 'meal-orders': {}, + meals: {}, }, tenantField: { access: { diff --git a/src/seed.ts b/src/seed.ts index 823d661..adb58b8 100644 --- a/src/seed.ts +++ b/src/seed.ts @@ -7,7 +7,7 @@ import type { Payload } from 'payload' * - 1 care home (tenant) * - 3 users (admin, caregiver, kitchen) * - 8 residents with varied data - * - 20+ meal orders covering multiple dates and meal types + * - Meal orders with individual meals */ export const seed = async (payload: Payload): Promise => { // Check if already seeded @@ -185,7 +185,7 @@ export const seed = async (payload: Payload): Promise => { payload.logger.info(`Created ${residents.length} residents`) // ============================================ - // CREATE MEAL ORDERS + // CREATE MEAL ORDERS AND MEALS // ============================================ const today = new Date() const yesterday = new Date(today) @@ -195,32 +195,75 @@ export const seed = async (payload: Payload): Promise => { const formatDate = (date: Date) => date.toISOString().split('T')[0] - const dates = [formatDate(yesterday), formatDate(today), formatDate(tomorrow)] + type MealType = 'breakfast' | 'lunch' | 'dinner' + type OrderStatus = 'draft' | 'submitted' | 'preparing' | 'completed' - const statuses: Array<'pending' | 'preparing' | 'prepared'> = [ - 'pending', - 'preparing', - 'prepared', + const mealOrders: Array<{ + date: string + mealType: MealType + status: OrderStatus + }> = [ + // Yesterday - all completed + { date: formatDate(yesterday), mealType: 'breakfast', status: 'completed' }, + { date: formatDate(yesterday), mealType: 'lunch', status: 'completed' }, + { date: formatDate(yesterday), mealType: 'dinner', status: 'completed' }, + // Today - mixed statuses + { date: formatDate(today), mealType: 'breakfast', status: 'completed' }, + { date: formatDate(today), mealType: 'lunch', status: 'preparing' }, + { date: formatDate(today), mealType: 'dinner', status: 'submitted' }, + // Tomorrow - draft (in progress) + { date: formatDate(tomorrow), mealType: 'breakfast', status: 'draft' }, + { date: formatDate(tomorrow), mealType: 'lunch', status: 'draft' }, ] - let orderCount = 0 + let totalMeals = 0 - // Create varied breakfast orders - for (let i = 0; i < residents.length; i++) { - const resident = residents[i] - const dateIndex = i % dates.length - const statusIndex = i % statuses.length - - await payload.create({ + for (const orderData of mealOrders) { + // Create the meal order + const order = await payload.create({ collection: 'meal-orders', data: { - resident: resident.id, - date: dates[dateIndex], - mealType: 'breakfast', - status: statuses[statusIndex], + date: orderData.date, + mealType: orderData.mealType, + status: orderData.status, createdBy: caregiver.id, tenant: careHome.id, - breakfast: { + submittedAt: orderData.status !== 'draft' ? new Date().toISOString() : undefined, + }, + }) + + // Determine meal status based on order status + const mealStatus = + orderData.status === 'completed' + ? 'prepared' + : orderData.status === 'preparing' + ? 'preparing' + : 'pending' + + // Create meals for each resident in the order + // For draft orders, only add some residents to demonstrate partial completion + const residentsToUse = + orderData.status === 'draft' + ? residents.slice(0, Math.floor(residents.length / 2)) + : residents + + for (let i = 0; i < residentsToUse.length; i++) { + const resident = residentsToUse[i] + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const mealData: any = { + order: order.id, + resident: resident.id, + date: orderData.date, + mealType: orderData.mealType, + status: mealStatus, + createdBy: caregiver.id, + tenant: careHome.id, + } + + // Add meal-specific options based on meal type + if (orderData.mealType === 'breakfast') { + mealData.breakfast = { accordingToPlan: i % 3 === 0, bread: { breadRoll: i % 2 === 0, @@ -239,7 +282,7 @@ export const seed = async (payload: Payload): Promise => { butter: true, margarine: false, jam: i % 2 === 0, - diabeticJam: i === 3, // For diabetic resident + diabeticJam: i === 3, honey: i % 4 === 0, cheese: i % 3 === 0, quark: i % 5 === 0, @@ -252,70 +295,35 @@ export const seed = async (payload: Payload): Promise => { coldMilk: i % 5 === 0, }, additions: { - sugar: i !== 3, // Not for diabetic - sweetener: i === 3, // For diabetic + sugar: i !== 3, + sweetener: i === 3, coffeeCreamer: i % 3 === 0, }, - }, - }, - }) - orderCount++ - } - - // Create varied lunch orders - for (let i = 0; i < residents.length; i++) { - const resident = residents[i] - const dateIndex = (i + 1) % dates.length - const statusIndex = (i + 1) % statuses.length - - const portionOptions: Array<'small' | 'large' | 'vegetarian'> = ['small', 'large', 'vegetarian'] - - await payload.create({ - collection: 'meal-orders', - data: { - resident: resident.id, - date: dates[dateIndex], - mealType: 'lunch', - status: statuses[statusIndex], - createdBy: caregiver.id, - tenant: careHome.id, - lunch: { + } + } else if (orderData.mealType === 'lunch') { + const portionOptions: Array<'small' | 'large' | 'vegetarian'> = [ + 'small', + 'large', + 'vegetarian', + ] + mealData.lunch = { portionSize: portionOptions[i % 3], soup: i % 2 === 0, dessert: true, specialPreparations: { - pureedFood: i === 7, // For resident who needs pureed food + pureedFood: i === 7, pureedMeat: i === 7, slicedMeat: i % 3 === 0 && i !== 7, mashedPotatoes: i % 4 === 0, }, restrictions: { - noFish: i === 2, // For resident with fish aversion + noFish: i === 2, fingerFood: i % 6 === 0, onlySweet: false, }, - }, - }, - }) - orderCount++ - } - - // Create varied dinner orders - for (let i = 0; i < residents.length; i++) { - const resident = residents[i] - const dateIndex = (i + 2) % dates.length - const statusIndex = (i + 2) % statuses.length - - await payload.create({ - collection: 'meal-orders', - data: { - resident: resident.id, - date: dates[dateIndex], - mealType: 'dinner', - status: statuses[statusIndex], - createdBy: caregiver.id, - tenant: careHome.id, - dinner: { + } + } else if (orderData.mealType === 'dinner') { + mealData.dinner = { accordingToPlan: i % 2 === 0, bread: { greyBread: i % 2 === 0, @@ -332,8 +340,8 @@ export const seed = async (payload: Payload): Promise => { margarine: i % 3 === 0, }, soup: i % 2 === 0, - porridge: i === 7, // For resident who needs pureed food - noFish: i === 2, // For resident with fish aversion + porridge: i === 7, + noFish: i === 2, beverages: { tea: i % 2 === 0, cocoa: i % 4 === 0, @@ -341,16 +349,30 @@ export const seed = async (payload: Payload): Promise => { coldMilk: i % 5 === 0, }, additions: { - sugar: i !== 3, // Not for diabetic - sweetener: i === 3, // For diabetic + sugar: i !== 3, + sweetener: i === 3, }, - }, + } + } + + await payload.create({ + collection: 'meals', + data: mealData, + }) + totalMeals++ + } + + // Update order meal count + await payload.update({ + collection: 'meal-orders', + id: order.id, + data: { + mealCount: residentsToUse.length, }, }) - orderCount++ } - payload.logger.info(`Created ${orderCount} meal orders`) + payload.logger.info(`Created ${mealOrders.length} meal orders with ${totalMeals} total meals`) payload.logger.info('Database seeding complete!') payload.logger.info('') payload.logger.info('Login credentials:')