create-mithaq 0.0.0-stage → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +22 -0
- package/LICENSES/payload.txt +22 -0
- package/LICENSES/ponytail.txt +21 -0
- package/index.js +127 -0
- package/package.json +19 -4
- package/template/apps/asas-platform/components.json +25 -0
- package/template/apps/asas-platform/gitignore +3 -0
- package/template/apps/asas-platform/package.json +55 -0
- package/template/apps/asas-platform/public/favicon.svg +1 -0
- package/template/apps/asas-platform/src/api.ts +147 -0
- package/template/apps/asas-platform/src/auth-frame.tsx +150 -0
- package/template/apps/asas-platform/src/components/mithaq/cells.tsx +112 -0
- package/template/apps/asas-platform/src/components/mithaq/controls.tsx +650 -0
- package/template/apps/asas-platform/src/components/mithaq/field-shell.tsx +53 -0
- package/template/apps/asas-platform/src/components/mithaq/filter-operators.ts +108 -0
- package/template/apps/asas-platform/src/components/mithaq/form.tsx +1467 -0
- package/template/apps/asas-platform/src/components/mithaq/list-columns.tsx +108 -0
- package/template/apps/asas-platform/src/components/mithaq/list-filters.tsx +368 -0
- package/template/apps/asas-platform/src/components/mithaq/list-pagination.tsx +153 -0
- package/template/apps/asas-platform/src/components/mithaq/list-query.ts +432 -0
- package/template/apps/asas-platform/src/components/mithaq/list-selection.tsx +74 -0
- package/template/apps/asas-platform/src/components/mithaq/list.tsx +865 -0
- package/template/apps/asas-platform/src/components/mithaq/nav.tsx +871 -0
- package/template/apps/asas-platform/src/components/mithaq/nodes.tsx +1024 -0
- package/template/apps/asas-platform/src/components/mithaq/relationship-cell.tsx +127 -0
- package/template/apps/asas-platform/src/components/mithaq/relationship-control.tsx +400 -0
- package/template/apps/asas-platform/src/components/mithaq/rich-text-nodes.ts +6 -0
- package/template/apps/asas-platform/src/components/mithaq/rich-text.tsx +1083 -0
- package/template/apps/asas-platform/src/components/mithaq/upload.tsx +686 -0
- package/template/apps/asas-platform/src/components/ui/accordion.tsx +74 -0
- package/template/apps/asas-platform/src/components/ui/alert-dialog.tsx +185 -0
- package/template/apps/asas-platform/src/components/ui/avatar.tsx +106 -0
- package/template/apps/asas-platform/src/components/ui/badge.tsx +48 -0
- package/template/apps/asas-platform/src/components/ui/breadcrumb.tsx +124 -0
- package/template/apps/asas-platform/src/components/ui/button-group.tsx +87 -0
- package/template/apps/asas-platform/src/components/ui/button.tsx +55 -0
- package/template/apps/asas-platform/src/components/ui/calendar.tsx +223 -0
- package/template/apps/asas-platform/src/components/ui/card.tsx +102 -0
- package/template/apps/asas-platform/src/components/ui/chart.tsx +371 -0
- package/template/apps/asas-platform/src/components/ui/checkbox.tsx +28 -0
- package/template/apps/asas-platform/src/components/ui/collapsible.tsx +19 -0
- package/template/apps/asas-platform/src/components/ui/combobox.tsx +308 -0
- package/template/apps/asas-platform/src/components/ui/dialog.tsx +158 -0
- package/template/apps/asas-platform/src/components/ui/direction.tsx +6 -0
- package/template/apps/asas-platform/src/components/ui/directional-icon.tsx +20 -0
- package/template/apps/asas-platform/src/components/ui/drawer.tsx +216 -0
- package/template/apps/asas-platform/src/components/ui/dropdown-menu.tsx +267 -0
- package/template/apps/asas-platform/src/components/ui/field.tsx +236 -0
- package/template/apps/asas-platform/src/components/ui/input-group.tsx +155 -0
- package/template/apps/asas-platform/src/components/ui/input-otp.tsx +84 -0
- package/template/apps/asas-platform/src/components/ui/input.tsx +19 -0
- package/template/apps/asas-platform/src/components/ui/label.tsx +19 -0
- package/template/apps/asas-platform/src/components/ui/popover.tsx +90 -0
- package/template/apps/asas-platform/src/components/ui/radio-group.tsx +37 -0
- package/template/apps/asas-platform/src/components/ui/resizable.tsx +47 -0
- package/template/apps/asas-platform/src/components/ui/scroll-area.tsx +54 -0
- package/template/apps/asas-platform/src/components/ui/select.tsx +204 -0
- package/template/apps/asas-platform/src/components/ui/separator.tsx +24 -0
- package/template/apps/asas-platform/src/components/ui/sheet.tsx +139 -0
- package/template/apps/asas-platform/src/components/ui/sidebar.tsx +714 -0
- package/template/apps/asas-platform/src/components/ui/skeleton.tsx +13 -0
- package/template/apps/asas-platform/src/components/ui/slider.tsx +55 -0
- package/template/apps/asas-platform/src/components/ui/spinner.tsx +10 -0
- package/template/apps/asas-platform/src/components/ui/switch.tsx +29 -0
- package/template/apps/asas-platform/src/components/ui/table.tsx +115 -0
- package/template/apps/asas-platform/src/components/ui/tabs.tsx +79 -0
- package/template/apps/asas-platform/src/components/ui/textarea.tsx +17 -0
- package/template/apps/asas-platform/src/components/ui/toggle-group.tsx +89 -0
- package/template/apps/asas-platform/src/components/ui/toggle.tsx +44 -0
- package/template/apps/asas-platform/src/components/ui/tooltip.tsx +63 -0
- package/template/apps/asas-platform/src/copy.ts +1 -0
- package/template/apps/asas-platform/src/hooks/use-mobile.ts +19 -0
- package/template/apps/asas-platform/src/lib/utils.ts +1 -0
- package/template/apps/asas-platform/src/links.ts +38 -0
- package/template/apps/asas-platform/src/locale-switch.tsx +26 -0
- package/template/apps/asas-platform/src/redirect.ts +1 -0
- package/template/apps/asas-platform/src/request-headers.ts +11 -0
- package/template/apps/asas-platform/src/routeTree.gen.ts +181 -0
- package/template/apps/asas-platform/src/router.tsx +7 -0
- package/template/apps/asas-platform/src/routes/(screens)/platform/files.ts +4 -0
- package/template/apps/asas-platform/src/routes/(screens)/platform/screen.json +3609 -0
- package/template/apps/asas-platform/src/routes/(screens)/platform/types.ts +145 -0
- package/template/apps/asas-platform/src/routes/(screens)/platform.$.tsx +17 -0
- package/template/apps/asas-platform/src/routes/(screens)/platform.index.tsx +10 -0
- package/template/apps/asas-platform/src/routes/(screens)/platform.tsx +13 -0
- package/template/apps/asas-platform/src/routes/__root.tsx +95 -0
- package/template/apps/asas-platform/src/routes/api/$.ts +30 -0
- package/template/apps/asas-platform/src/routes/auth.sign-in.tsx +72 -0
- package/template/apps/asas-platform/src/routes/unauthorized.tsx +122 -0
- package/template/apps/asas-platform/src/screen-form.tsx +34 -0
- package/template/apps/asas-platform/src/screen-list.tsx +139 -0
- package/template/apps/asas-platform/src/screen-names.ts +10 -0
- package/template/apps/asas-platform/src/screens.tsx +868 -0
- package/template/apps/asas-platform/src/server.ts +20 -0
- package/template/apps/asas-platform/src/slide.ts +22 -0
- package/template/apps/asas-platform/src/start.ts +42 -0
- package/template/apps/asas-platform/src/styles.css +200 -0
- package/template/apps/asas-platform/src/theme.ts +2 -0
- package/template/apps/asas-platform/tsconfig.json +19 -0
- package/template/apps/asas-platform/vite.config.ts +26 -0
- package/template/apps/asas-tanstack/components.json +25 -0
- package/template/apps/asas-tanstack/gitignore +3 -0
- package/template/apps/asas-tanstack/package.json +56 -0
- package/template/apps/asas-tanstack/public/favicon.svg +1 -0
- package/template/apps/asas-tanstack/src/api.ts +147 -0
- package/template/apps/asas-tanstack/src/auth-frame.tsx +150 -0
- package/template/apps/asas-tanstack/src/components/mithaq/cells.tsx +112 -0
- package/template/apps/asas-tanstack/src/components/mithaq/controls.tsx +650 -0
- package/template/apps/asas-tanstack/src/components/mithaq/field-shell.tsx +53 -0
- package/template/apps/asas-tanstack/src/components/mithaq/filter-operators.ts +108 -0
- package/template/apps/asas-tanstack/src/components/mithaq/form.tsx +1467 -0
- package/template/apps/asas-tanstack/src/components/mithaq/list-columns.tsx +108 -0
- package/template/apps/asas-tanstack/src/components/mithaq/list-filters.tsx +368 -0
- package/template/apps/asas-tanstack/src/components/mithaq/list-pagination.tsx +153 -0
- package/template/apps/asas-tanstack/src/components/mithaq/list-query.ts +432 -0
- package/template/apps/asas-tanstack/src/components/mithaq/list-selection.tsx +74 -0
- package/template/apps/asas-tanstack/src/components/mithaq/list.tsx +865 -0
- package/template/apps/asas-tanstack/src/components/mithaq/nav.tsx +871 -0
- package/template/apps/asas-tanstack/src/components/mithaq/nodes.tsx +1024 -0
- package/template/apps/asas-tanstack/src/components/mithaq/relationship-cell.tsx +127 -0
- package/template/apps/asas-tanstack/src/components/mithaq/relationship-control.tsx +400 -0
- package/template/apps/asas-tanstack/src/components/mithaq/rich-text-nodes.ts +6 -0
- package/template/apps/asas-tanstack/src/components/mithaq/rich-text.tsx +1083 -0
- package/template/apps/asas-tanstack/src/components/mithaq/upload.tsx +686 -0
- package/template/apps/asas-tanstack/src/components/ui/accordion.tsx +10 -0
- package/template/apps/asas-tanstack/src/components/ui/alert-dialog.tsx +173 -0
- package/template/apps/asas-tanstack/src/components/ui/avatar.tsx +104 -0
- package/template/apps/asas-tanstack/src/components/ui/badge.tsx +48 -0
- package/template/apps/asas-tanstack/src/components/ui/breadcrumb.tsx +125 -0
- package/template/apps/asas-tanstack/src/components/ui/button-group.tsx +87 -0
- package/template/apps/asas-tanstack/src/components/ui/button.tsx +56 -0
- package/template/apps/asas-tanstack/src/components/ui/calendar.tsx +223 -0
- package/template/apps/asas-tanstack/src/components/ui/card.tsx +94 -0
- package/template/apps/asas-tanstack/src/components/ui/chart.tsx +369 -0
- package/template/apps/asas-tanstack/src/components/ui/checkbox.tsx +27 -0
- package/template/apps/asas-tanstack/src/components/ui/collapsible.tsx +19 -0
- package/template/apps/asas-tanstack/src/components/ui/combobox.tsx +300 -0
- package/template/apps/asas-tanstack/src/components/ui/dialog.tsx +149 -0
- package/template/apps/asas-tanstack/src/components/ui/direction.tsx +5 -0
- package/template/apps/asas-tanstack/src/components/ui/directional-icon.tsx +20 -0
- package/template/apps/asas-tanstack/src/components/ui/drawer.tsx +215 -0
- package/template/apps/asas-tanstack/src/components/ui/dropdown-menu.tsx +267 -0
- package/template/apps/asas-tanstack/src/components/ui/field.tsx +225 -0
- package/template/apps/asas-tanstack/src/components/ui/input-group.tsx +147 -0
- package/template/apps/asas-tanstack/src/components/ui/input-otp.tsx +20 -0
- package/template/apps/asas-tanstack/src/components/ui/input.tsx +20 -0
- package/template/apps/asas-tanstack/src/components/ui/label.tsx +18 -0
- package/template/apps/asas-tanstack/src/components/ui/popover.tsx +50 -0
- package/template/apps/asas-tanstack/src/components/ui/radio-group.tsx +10 -0
- package/template/apps/asas-tanstack/src/components/ui/resizable.tsx +47 -0
- package/template/apps/asas-tanstack/src/components/ui/scroll-area.tsx +52 -0
- package/template/apps/asas-tanstack/src/components/ui/select.tsx +190 -0
- package/template/apps/asas-tanstack/src/components/ui/separator.tsx +23 -0
- package/template/apps/asas-tanstack/src/components/ui/sheet.tsx +127 -0
- package/template/apps/asas-tanstack/src/components/ui/sidebar.tsx +716 -0
- package/template/apps/asas-tanstack/src/components/ui/skeleton.tsx +13 -0
- package/template/apps/asas-tanstack/src/components/ui/slider.tsx +13 -0
- package/template/apps/asas-tanstack/src/components/ui/spinner.tsx +11 -0
- package/template/apps/asas-tanstack/src/components/ui/switch.tsx +8 -0
- package/template/apps/asas-tanstack/src/components/ui/table.tsx +119 -0
- package/template/apps/asas-tanstack/src/components/ui/tabs.tsx +80 -0
- package/template/apps/asas-tanstack/src/components/ui/textarea.tsx +18 -0
- package/template/apps/asas-tanstack/src/components/ui/toggle-group.tsx +8 -0
- package/template/apps/asas-tanstack/src/components/ui/toggle.tsx +44 -0
- package/template/apps/asas-tanstack/src/components/ui/tooltip.tsx +68 -0
- package/template/apps/asas-tanstack/src/copy.ts +870 -0
- package/template/apps/asas-tanstack/src/hooks/use-mobile.ts +19 -0
- package/template/apps/asas-tanstack/src/lib/utils.ts +6 -0
- package/template/apps/asas-tanstack/src/links.ts +38 -0
- package/template/apps/asas-tanstack/src/locale-switch.tsx +26 -0
- package/template/apps/asas-tanstack/src/quiet-abort.ts +25 -0
- package/template/apps/asas-tanstack/src/redirect.ts +20 -0
- package/template/apps/asas-tanstack/src/request-headers.ts +11 -0
- package/template/apps/asas-tanstack/src/routeTree.gen.ts +382 -0
- package/template/apps/asas-tanstack/src/router.tsx +7 -0
- package/template/apps/asas-tanstack/src/routes/(screens)/dashboard/files.ts +4 -0
- package/template/apps/asas-tanstack/src/routes/(screens)/dashboard/screen.json +3883 -0
- package/template/apps/asas-tanstack/src/routes/(screens)/dashboard/types.ts +158 -0
- package/template/apps/asas-tanstack/src/routes/(screens)/dashboard.$.tsx +17 -0
- package/template/apps/asas-tanstack/src/routes/(screens)/dashboard.index.tsx +10 -0
- package/template/apps/asas-tanstack/src/routes/(screens)/dashboard.tsx +13 -0
- package/template/apps/asas-tanstack/src/routes/(screens)/portal/files.ts +4 -0
- package/template/apps/asas-tanstack/src/routes/(screens)/portal/screen.json +1800 -0
- package/template/apps/asas-tanstack/src/routes/(screens)/portal/types.ts +92 -0
- package/template/apps/asas-tanstack/src/routes/(screens)/portal.$.tsx +17 -0
- package/template/apps/asas-tanstack/src/routes/(screens)/portal.index.tsx +10 -0
- package/template/apps/asas-tanstack/src/routes/(screens)/portal.tsx +13 -0
- package/template/apps/asas-tanstack/src/routes/__root.tsx +99 -0
- package/template/apps/asas-tanstack/src/routes/api/$.ts +11 -0
- package/template/apps/asas-tanstack/src/routes/auth.accept-invitation.tsx +101 -0
- package/template/apps/asas-tanstack/src/routes/auth.forgot-password.tsx +94 -0
- package/template/apps/asas-tanstack/src/routes/auth.handoff.ts +39 -0
- package/template/apps/asas-tanstack/src/routes/auth.reset-password.tsx +109 -0
- package/template/apps/asas-tanstack/src/routes/auth.sign-in.tsx +86 -0
- package/template/apps/asas-tanstack/src/routes/auth.sign-up.tsx +122 -0
- package/template/apps/asas-tanstack/src/routes/index.tsx +142 -0
- package/template/apps/asas-tanstack/src/routes/unauthorized.tsx +122 -0
- package/template/apps/asas-tanstack/src/screen-form.tsx +34 -0
- package/template/apps/asas-tanstack/src/screen-list.tsx +139 -0
- package/template/apps/asas-tanstack/src/screen-names.ts +10 -0
- package/template/apps/asas-tanstack/src/screens.tsx +868 -0
- package/template/apps/asas-tanstack/src/server.ts +16 -0
- package/template/apps/asas-tanstack/src/slide.ts +22 -0
- package/template/apps/asas-tanstack/src/start.ts +57 -0
- package/template/apps/asas-tanstack/src/styles.css +159 -0
- package/template/apps/asas-tanstack/src/theme.ts +5 -0
- package/template/apps/asas-tanstack/tsconfig.json +19 -0
- package/template/apps/asas-tanstack/vite.config.ts +28 -0
- package/template/package.json +16 -0
- package/template/templates/asas/.env.example +16 -0
- package/template/templates/asas/components.json +22 -0
- package/template/templates/asas/mithaq.hints.json +25 -0
- package/template/templates/asas/package.json +38 -0
- package/template/templates/asas/src/asas.ts +93 -0
- package/template/templates/asas/src/audit-log/audit-log.ts +187 -0
- package/template/templates/asas/src/audit-log/sink.ts +89 -0
- package/template/templates/asas/src/audit-log/ui/activity.tsx +243 -0
- package/template/templates/asas/src/courses/courses.ts +612 -0
- package/template/templates/asas/src/courses/ui/archive-button.tsx +25 -0
- package/template/templates/asas/src/courses/ui/fee-cell.tsx +19 -0
- package/template/templates/asas/src/courses/ui/no-courses-on-offer.tsx +11 -0
- package/template/templates/asas/src/courses/ui/no-courses.tsx +13 -0
- package/template/templates/asas/src/courses/ui/on-offer.ts +6 -0
- package/template/templates/asas/src/courses/ui/status-cell.tsx +31 -0
- package/template/templates/asas/src/enrollments/enrollments.ts +137 -0
- package/template/templates/asas/src/enrollments/ui/session-cell.tsx +65 -0
- package/template/templates/asas/src/kayan/auth/user/account.access.ts +8 -0
- package/template/templates/asas/src/kayan/auth/user/account.ts +31 -0
- package/template/templates/asas/src/kayan/auth/user/index.ts +18 -0
- package/template/templates/asas/src/kayan/auth/user/invitation.access.ts +4 -0
- package/template/templates/asas/src/kayan/auth/user/invitation.ts +27 -0
- package/template/templates/asas/src/kayan/auth/user/member.access.ts +10 -0
- package/template/templates/asas/src/kayan/auth/user/member.screens.ts +37 -0
- package/template/templates/asas/src/kayan/auth/user/member.ts +25 -0
- package/template/templates/asas/src/kayan/auth/user/organization.access.ts +20 -0
- package/template/templates/asas/src/kayan/auth/user/organization.screens.ts +37 -0
- package/template/templates/asas/src/kayan/auth/user/organization.ts +24 -0
- package/template/templates/asas/src/kayan/auth/user/organizationRole.access.ts +8 -0
- package/template/templates/asas/src/kayan/auth/user/organizationRole.ts +25 -0
- package/template/templates/asas/src/kayan/auth/user/session.access.ts +8 -0
- package/template/templates/asas/src/kayan/auth/user/session.ts +28 -0
- package/template/templates/asas/src/kayan/auth/user/user.access.ts +29 -0
- package/template/templates/asas/src/kayan/auth/user/user.screens.ts +45 -0
- package/template/templates/asas/src/kayan/auth/user/user.ts +31 -0
- package/template/templates/asas/src/kayan/auth/user/verification.access.ts +8 -0
- package/template/templates/asas/src/kayan/auth/user/verification.ts +23 -0
- package/template/templates/asas/src/local-users/local-users.ts +143 -0
- package/template/templates/asas/src/media/media.ts +137 -0
- package/template/templates/asas/src/media/ui/preview.tsx +143 -0
- package/template/templates/asas/src/member-roles/member-roles.ts +207 -0
- package/template/templates/asas/src/messages/account.json +106 -0
- package/template/templates/asas/src/messages/auditLog.json +186 -0
- package/template/templates/asas/src/messages/courses.json +270 -0
- package/template/templates/asas/src/messages/enrollments.json +42 -0
- package/template/templates/asas/src/messages/invitation.json +66 -0
- package/template/templates/asas/src/messages/localUsers.json +42 -0
- package/template/templates/asas/src/messages/media.json +66 -0
- package/template/templates/asas/src/messages/member.json +42 -0
- package/template/templates/asas/src/messages/memberRoles.json +42 -0
- package/template/templates/asas/src/messages/mithaq.json +170 -0
- package/template/templates/asas/src/messages/organization.json +50 -0
- package/template/templates/asas/src/messages/organizationRole.json +50 -0
- package/template/templates/asas/src/messages/roles.json +58 -0
- package/template/templates/asas/src/messages/session.json +82 -0
- package/template/templates/asas/src/messages/sessions.json +106 -0
- package/template/templates/asas/src/messages/settings.json +66 -0
- package/template/templates/asas/src/messages/tags.json +38 -0
- package/template/templates/asas/src/messages/user.json +90 -0
- package/template/templates/asas/src/messages/verification.json +50 -0
- package/template/templates/asas/src/migrations/20260920080701_parched_tombstone/migration.sql +177 -0
- package/template/templates/asas/src/migrations/20260920080701_parched_tombstone/snapshot.json +2169 -0
- package/template/templates/asas/src/migrations/20260920230505_zippy_ben_urich/migration.sql +1 -0
- package/template/templates/asas/src/migrations/20260920230505_zippy_ben_urich/snapshot.json +2182 -0
- package/template/templates/asas/src/migrations/20260920231219_slim_retro_girl/migration.sql +20 -0
- package/template/templates/asas/src/migrations/20260920231219_slim_retro_girl/snapshot.json +2409 -0
- package/template/templates/asas/src/migrations/20260922082945_military_ironclad/migration.sql +15 -0
- package/template/templates/asas/src/migrations/20260922082945_military_ironclad/snapshot.json +2627 -0
- package/template/templates/asas/src/migrations/20260922112630_bitter_chat/migration.sql +5 -0
- package/template/templates/asas/src/migrations/20260922112630_bitter_chat/snapshot.json +2691 -0
- package/template/templates/asas/src/migrations/20260923235915_foamy_rachel_grey/migration.sql +8 -0
- package/template/templates/asas/src/migrations/20260923235915_foamy_rachel_grey/snapshot.json +2795 -0
- package/template/templates/asas/src/migrations/20260926145709_clean_the_initiative/migration.sql +3 -0
- package/template/templates/asas/src/migrations/20260926145709_clean_the_initiative/snapshot.json +2834 -0
- package/template/templates/asas/src/migrations/20260927212525_bent_prowler/migration.sql +9 -0
- package/template/templates/asas/src/migrations/20260927212525_bent_prowler/snapshot.json +2870 -0
- package/template/templates/asas/src/migrations/20260927223441_tranquil_bromley/migration.sql +1 -0
- package/template/templates/asas/src/migrations/20260927223441_tranquil_bromley/snapshot.json +2881 -0
- package/template/templates/asas/src/migrations/20260928030047_outgoing_wraith/migration.sql +15 -0
- package/template/templates/asas/src/migrations/20260928030047_outgoing_wraith/snapshot.json +3073 -0
- package/template/templates/asas/src/migrations/20260929024407_grey_morlun/migration.sql +21 -0
- package/template/templates/asas/src/migrations/20260929024407_grey_morlun/snapshot.json +3098 -0
- package/template/templates/asas/src/migrations/20260929130530_bored_jackal/migration.sql +21 -0
- package/template/templates/asas/src/migrations/20260929130530_bored_jackal/snapshot.json +3124 -0
- package/template/templates/asas/src/migrations/20260930102924_material_newton_destine/migration.sql +32 -0
- package/template/templates/asas/src/migrations/20260930102924_material_newton_destine/snapshot.json +3324 -0
- package/template/templates/asas/src/migrations/20261003022353_clumsy_taskmaster/migration.sql +2 -0
- package/template/templates/asas/src/migrations/20261003022353_clumsy_taskmaster/snapshot.json +3350 -0
- package/template/templates/asas/src/migrations/20261003143249_premium_red_shift/migration.sql +21 -0
- package/template/templates/asas/src/migrations/20261003143249_premium_red_shift/snapshot.json +3635 -0
- package/template/templates/asas/src/migrations/20261008215349_square_norrin_radd/migration.sql +8 -0
- package/template/templates/asas/src/migrations/20261008215349_square_norrin_radd/snapshot.json +3635 -0
- package/template/templates/asas/src/migrations/20261008230204_many_scarecrow/migration.sql +18 -0
- package/template/templates/asas/src/migrations/20261008230204_many_scarecrow/snapshot.json +3635 -0
- package/template/templates/asas/src/migrations/20261010005521_elite_giant_girl/migration.sql +18 -0
- package/template/templates/asas/src/migrations/20261010005521_elite_giant_girl/snapshot.json +3635 -0
- package/template/templates/asas/src/migrations/20261010120822_needy_thunderbolt/migration.sql +11 -0
- package/template/templates/asas/src/migrations/20261010120822_needy_thunderbolt/snapshot.json +3667 -0
- package/template/templates/asas/src/migrations/20261012000752_organization_ownership/migration.sql +48 -0
- package/template/templates/asas/src/migrations/20261012000752_organization_ownership/snapshot.json +3891 -0
- package/template/templates/asas/src/migrations/drizzle.config.ts +11 -0
- package/template/templates/asas/src/migrations/schema.ts +29 -0
- package/template/templates/asas/src/mithaq.ts +406 -0
- package/template/templates/asas/src/pages.ts +74 -0
- package/template/templates/asas/src/platform-http.ts +96 -0
- package/template/templates/asas/src/rich-text-nodes.ts +11 -0
- package/template/templates/asas/src/roles/grants.ts +47 -0
- package/template/templates/asas/src/roles/roles.ts +184 -0
- package/template/templates/asas/src/roles/ui/role-form.tsx +226 -0
- package/template/templates/asas/src/roles/ui/roles.tsx +166 -0
- package/template/templates/asas/src/roles.ts +16 -0
- package/template/templates/asas/src/seed.ts +325 -0
- package/template/templates/asas/src/sessions/sessions.ts +155 -0
- package/template/templates/asas/src/settings/settings.ts +69 -0
- package/template/templates/asas/src/statements.ts +11 -0
- package/template/templates/asas/src/steps/expected-updated-at.ts +41 -0
- package/template/templates/asas/src/storage/defaults.ts +156 -0
- package/template/templates/asas/src/storage/index.ts +95 -0
- package/template/templates/asas/src/storage/local.ts +143 -0
- package/template/templates/asas/src/storage/r2.ts +5 -0
- package/template/templates/asas/src/storage/s3.ts +91 -0
- package/template/templates/asas/src/storage/uploadthing.ts +59 -0
- package/template/templates/asas/src/tags/tags.ts +80 -0
- package/template/templates/asas/src/tenant-access.ts +221 -0
- package/template/templates/asas/src/tenant-roles.ts +262 -0
- package/template/templates/asas/src/ui/app-shell.tsx +260 -0
- package/template/templates/asas/src/ui/kpi-card.tsx +25 -0
- package/template/templates/asas/src/ui/record-page.tsx +50 -0
- package/template/templates/asas/src/ui/rich-text-nodes.tsx +15 -0
- package/template/templates/asas/src/ui/screen-nav.tsx +28 -0
- package/template/templates/asas/src/ui/session-labels.tsx +36 -0
- package/template/templates/asas/src/ui/table-page.tsx +9 -0
- package/template/templates/asas/src/ui/top-shell.tsx +60 -0
- package/template/templates/asas/src/ui-dashboard/dashboard.tsx +294 -0
- package/template/templates/asas/src/ui-dashboard/members.tsx +630 -0
- package/template/templates/asas/src/ui-dashboard/theme-provider.tsx +10 -0
- package/template/templates/asas/src/ui-dashboard/versions.tsx +432 -0
- package/template/templates/asas/src/ui-platform/home.tsx +63 -0
- package/template/templates/asas/src/ui-platform/organization-actions.tsx +114 -0
- package/template/templates/asas/src/ui-platform/record-page.tsx +30 -0
- package/template/templates/asas/src/ui-platform/role-cells.tsx +52 -0
- package/template/templates/asas/src/ui-platform/user-actions.tsx +251 -0
- package/template/templates/asas/src/ui-portal/home.tsx +77 -0
- package/template/templates/asas/src/ui-portal/page.tsx +11 -0
- package/template/templates/auth/package.json +14 -0
- package/template/templates/auth/src/index.ts +690 -0
- package/template/templates/auth/src/platform-roles.ts +87 -0
- package/README.md +0 -3
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
// The figures are read by the page's own `loader`, which the screen host runs
|
|
2
|
+
// where the page is loaded, as the viewer: on the server for a page load, so the
|
|
3
|
+
// numbers and the series are in the first HTML, and in the browser for a client
|
|
4
|
+
// navigation. A figure over a kayan the report does not let the actor read is not
|
|
5
|
+
// requested and not drawn.
|
|
6
|
+
import { useEffect, useState } from 'react'
|
|
7
|
+
import { Banknote, BookOpen, ClipboardList, TrendingUp } from 'lucide-react'
|
|
8
|
+
import { Area, AreaChart, CartesianGrid, XAxis, YAxis } from 'recharts'
|
|
9
|
+
import type { LoaderTools, PageProps, Row } from 'mithaq/admin'
|
|
10
|
+
import { useLocale, useScreen } from 'mithaq/admin/react'
|
|
11
|
+
import { Button } from '@/components/ui/button'
|
|
12
|
+
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
|
|
13
|
+
import { ChartContainer, ChartTooltip, ChartTooltipContent, type ChartConfig } from '@/components/ui/chart'
|
|
14
|
+
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'
|
|
15
|
+
import { counted, formatDate } from '@/components/mithaq/controls.tsx'
|
|
16
|
+
import { AppShell } from '../ui/app-shell.tsx'
|
|
17
|
+
import { KpiCard } from '../ui/kpi-card.tsx'
|
|
18
|
+
|
|
19
|
+
const ranges = [7, 30, 90] as const
|
|
20
|
+
type Range = (typeof ranges)[number]
|
|
21
|
+
|
|
22
|
+
/** The range `?range=` names, and 30 for anything else, since the URL is the reader's to write. */
|
|
23
|
+
const rangeOf = (search: string): Range => {
|
|
24
|
+
const asked = Number(new URLSearchParams(search).get('range'))
|
|
25
|
+
return ranges.find((one) => one === asked) ?? 30
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const day = 24 * 60 * 60 * 1000
|
|
29
|
+
|
|
30
|
+
/** The `range` UTC days ending today, oldest first, as `YYYY-MM-DD`. */
|
|
31
|
+
const daysOf = (range: Range) => Array.from({ length: range }, (_, at) => new Date(Date.now() - (range - 1 - at) * day).toISOString().slice(0, 10))
|
|
32
|
+
|
|
33
|
+
/** Every row a read matches, page by page, since a served read answers at most the build's `maxLimit` at once. */
|
|
34
|
+
const readAll = async ({ tools: { client, maxLimit }, kayan, where, draft }: { tools: LoaderTools; kayan: string; where?: Row; draft?: boolean }) => {
|
|
35
|
+
const rows: Row[] = []
|
|
36
|
+
for (let page = 1, pages = 1; page <= pages; page++) {
|
|
37
|
+
const found = (await client({ kayan, operation: 'find', input: { limit: maxLimit, page, depth: 0, ...(where && { where }) }, ...(draft && { draft }) })) as { docs: Row[]; totalPages: number }
|
|
38
|
+
rows.push(...found.docs)
|
|
39
|
+
pages = found.totalPages
|
|
40
|
+
}
|
|
41
|
+
return rows
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** What the page draws: the counts, the fees per currency, and the enrollments of each of the widest range's days. */
|
|
45
|
+
export type Figures = {
|
|
46
|
+
courses: number
|
|
47
|
+
open: number
|
|
48
|
+
/** One sum per currency, where the actor reads both a course's fee and its currency. */
|
|
49
|
+
fees?: [currency: string, sum: number][]
|
|
50
|
+
enrollments?: number
|
|
51
|
+
perDay?: Record<string, number>
|
|
52
|
+
/** The next sessions to start, each beside its course's title, where the actor reads sessions. */
|
|
53
|
+
upcoming?: { id: string; course: string; startsAt: string; venue?: string; capacity?: number }[]
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** How many sessions the home's table lists, which is a card's worth. */
|
|
57
|
+
const upcomingLimit = 8
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* The figures, read as the viewer. The two totals are the store's `count`.
|
|
61
|
+
* The enrollments of the widest range are read once, so choosing a narrower
|
|
62
|
+
* one asks the server nothing.
|
|
63
|
+
*/
|
|
64
|
+
export const loader = async (tools: LoaderTools): Promise<Figures> => {
|
|
65
|
+
const { client, access } = tools
|
|
66
|
+
const reads = access.collections?.enrollments?.read === true
|
|
67
|
+
// The table filters and orders on a session's start and names its course,
|
|
68
|
+
// so it is read only where the report lets the actor read both fields: a
|
|
69
|
+
// role holding `sessions.find` and neither field is refused the filter.
|
|
70
|
+
const sessionFields = access.collections?.sessions?.fields
|
|
71
|
+
const readsSessions = access.collections?.sessions?.read === true && sessionFields?.startsAt?.read === true && sessionFields.course?.read === true
|
|
72
|
+
// A fee is summed only beside its currency: a reader of the fee alone would
|
|
73
|
+
// be handed riyals and dollars added into one number.
|
|
74
|
+
const courseFields = access.collections?.courses?.fields
|
|
75
|
+
const readsFees = courseFields?.fee?.read === true && courseFields.currency?.read === true
|
|
76
|
+
const since = `${daysOf(90)[0]}T00:00:00.000Z`
|
|
77
|
+
// The courses the way the Courses list reads them: the latest draft of every
|
|
78
|
+
// row where the actor holds `findVersions`, so the card and the list count one
|
|
79
|
+
// set, a course nobody has published yet among it.
|
|
80
|
+
const draft = access.collections?.courses?.operations?.findVersions === true
|
|
81
|
+
const [rows, enrollments, recent, next] = await Promise.all([
|
|
82
|
+
readAll({ tools, kayan: 'courses', draft }),
|
|
83
|
+
reads ? (client({ kayan: 'enrollments', operation: 'count', input: {} }) as Promise<{ totalDocs: number }>) : undefined,
|
|
84
|
+
reads ? readAll({ tools, kayan: 'enrollments', where: { createdAt: { greater_than_equal: since } } }) : undefined,
|
|
85
|
+
readsSessions
|
|
86
|
+
? (client({
|
|
87
|
+
kayan: 'sessions',
|
|
88
|
+
operation: 'find',
|
|
89
|
+
input: { where: { startsAt: { greater_than_equal: new Date().toISOString() } }, sort: 'startsAt', limit: Math.min(upcomingLimit, tools.maxLimit), depth: 0 },
|
|
90
|
+
}) as Promise<{ docs: Row[] }>)
|
|
91
|
+
: undefined,
|
|
92
|
+
])
|
|
93
|
+
const titles = new Map(rows.map((course) => [String(course.id), String(course.title ?? course.id)]))
|
|
94
|
+
const fees: Record<string, number> = {}
|
|
95
|
+
// One sum per currency, because a riyal and a dollar do not add up, and
|
|
96
|
+
// none for a fee with no currency, which is no amount of any of them.
|
|
97
|
+
// Of the courses not archived, the ones the card's second line counts.
|
|
98
|
+
const open = rows.filter((course) => !course.archived)
|
|
99
|
+
for (const course of open) if (course.currency) fees[String(course.currency)] = (fees[String(course.currency)] ?? 0) + Number(course.fee ?? 0)
|
|
100
|
+
const perDay: Record<string, number> = {}
|
|
101
|
+
for (const row of recent ?? []) {
|
|
102
|
+
// A `Date` in process on the server, an ISO string over HTTP in the browser.
|
|
103
|
+
const key = new Date(row.createdAt as string | Date).toISOString().slice(0, 10)
|
|
104
|
+
perDay[key] = (perDay[key] ?? 0) + 1
|
|
105
|
+
}
|
|
106
|
+
return {
|
|
107
|
+
courses: rows.length,
|
|
108
|
+
open: open.length,
|
|
109
|
+
...(readsFees && { fees: Object.entries(fees) }),
|
|
110
|
+
...(reads && { enrollments: enrollments?.totalDocs, perDay }),
|
|
111
|
+
...(next && {
|
|
112
|
+
upcoming: next.docs.map((session) => ({
|
|
113
|
+
id: String(session.id),
|
|
114
|
+
course: titles.get(String(session.course)) ?? String(session.course ?? ''),
|
|
115
|
+
startsAt: new Date(session.startsAt as string | Date).toISOString(),
|
|
116
|
+
...(typeof session.venue === 'string' && { venue: session.venue }),
|
|
117
|
+
...(typeof session.capacity === 'number' && { capacity: session.capacity }),
|
|
118
|
+
})),
|
|
119
|
+
}),
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export function Dashboard({ loaderData: figures }: PageProps<Figures>) {
|
|
124
|
+
const { dir, locale, t } = useLocale()
|
|
125
|
+
const screen = useScreen()
|
|
126
|
+
const reads = figures?.perDay !== undefined
|
|
127
|
+
const sessions = screen.kayan.sessions
|
|
128
|
+
const sessionHref = (id: string) => `${screen.path}${sessions?.pages.edit?.path.replace(':id', encodeURIComponent(id)) ?? ''}`
|
|
129
|
+
const venueOf = (value: string) => sessions?.fields.find((field) => field.name === 'venue')?.options?.find((one) => one.value === value)?.label[locale] ?? value
|
|
130
|
+
const [range, setRange] = useState<Range>(30)
|
|
131
|
+
|
|
132
|
+
// The range is in the URL, so a reload or a link keeps it. The server has no
|
|
133
|
+
// location, so the first paint draws the default and this reads the URL once
|
|
134
|
+
// the page is in the browser.
|
|
135
|
+
useEffect(() => setRange(rangeOf(window.location.search)), [])
|
|
136
|
+
const choose = (next: Range) => {
|
|
137
|
+
const url = new URL(window.location.href)
|
|
138
|
+
url.searchParams.set('range', String(next))
|
|
139
|
+
window.history.replaceState(window.history.state, '', url)
|
|
140
|
+
setRange(next)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const count = (value: number | undefined) => (value === undefined ? '…' : new Intl.NumberFormat(locale).format(value))
|
|
144
|
+
const money = (value: number, currency: string) => {
|
|
145
|
+
try {
|
|
146
|
+
return new Intl.NumberFormat(locale, { style: 'currency', currency }).format(value)
|
|
147
|
+
} catch {
|
|
148
|
+
return `${count(value)} ${currency}`
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
const days = daysOf(range)
|
|
152
|
+
const points = days.map((key) => ({
|
|
153
|
+
day: new Date(key).toLocaleDateString(locale, { month: 'short', day: 'numeric', timeZone: 'UTC' }),
|
|
154
|
+
enrollments: figures?.perDay?.[key] ?? 0,
|
|
155
|
+
}))
|
|
156
|
+
const chartConfig = { enrollments: { label: t('dashboard.kpiEnrollments', 'Enrollments'), color: 'var(--chart-1)' } } satisfies ChartConfig
|
|
157
|
+
// Counted in each locale's own plural forms, so Arabic says "7 أيام" and "30 يومًا".
|
|
158
|
+
const inRange = counted({ t, locale, key: 'dashboard.kpiInRange', count: range, fallback: 'In the last {count} days' })
|
|
159
|
+
|
|
160
|
+
return (
|
|
161
|
+
<AppShell
|
|
162
|
+
regions={{
|
|
163
|
+
header: <h1>{t('dashboard.dashboard', 'Dashboard')}</h1>,
|
|
164
|
+
main: (
|
|
165
|
+
<section data-dashboard="dashboard" className="@container flex flex-col gap-4 md:gap-6">
|
|
166
|
+
{/* Says enrollments only to a reader of them, who is drawn their figures. */}
|
|
167
|
+
<p className="text-muted-foreground">
|
|
168
|
+
{reads ? t('dashboard.dashboardAbout', 'What this organization holds, and how many have enrolled lately.') : t('dashboard.dashboardAboutCatalogue', 'What this organization holds.')}
|
|
169
|
+
</p>
|
|
170
|
+
{/* Two to a row on a phone, so four figures fit on one screen. Four to a
|
|
171
|
+
row once the section itself is 672 pixels wide, which is what the
|
|
172
|
+
main area holds beside the sidebar at `lg`, so a narrower frame
|
|
173
|
+
keeps two and cuts no figure. */}
|
|
174
|
+
<div className="grid grid-cols-2 gap-4 @2xl:grid-cols-4">
|
|
175
|
+
<KpiCard
|
|
176
|
+
kpi="courses"
|
|
177
|
+
icon={<BookOpen className="size-4" />}
|
|
178
|
+
title={t('dashboard.kpiCourses', 'Courses')}
|
|
179
|
+
value={count(figures?.courses)}
|
|
180
|
+
footer={t('dashboard.kpiCoursesOpen', '{count} not archived')!.replace('{count}', count(figures?.open))}
|
|
181
|
+
/>
|
|
182
|
+
{/* One sum per line, each a left-to-right isolate, so Arabic
|
|
183
|
+
neither reorders "US$" into "$US" nor strands a separator at
|
|
184
|
+
a line's end. */}
|
|
185
|
+
{!figures || figures.fees ? (
|
|
186
|
+
<KpiCard
|
|
187
|
+
kpi="fees"
|
|
188
|
+
icon={<Banknote className="size-4" />}
|
|
189
|
+
title={t('dashboard.kpiFees', 'Total fees')}
|
|
190
|
+
// The placeholder every card draws until the figures arrive, so the grid does not shift.
|
|
191
|
+
value={!figures?.fees ? '…' : figures.fees.length ? figures.fees.map(([currency, sum]) => <bdi key={currency} dir="ltr" className="block w-fit">{money(sum, currency)}</bdi>) : '—'}
|
|
192
|
+
footer={t('dashboard.kpiFeesFooter', 'The fee of every course not archived, summed per currency')!}
|
|
193
|
+
/>
|
|
194
|
+
) : null}
|
|
195
|
+
{reads ? (
|
|
196
|
+
<>
|
|
197
|
+
<KpiCard
|
|
198
|
+
kpi="enrollments"
|
|
199
|
+
icon={<ClipboardList className="size-4" />}
|
|
200
|
+
title={t('dashboard.kpiEnrollments', 'Enrollments')}
|
|
201
|
+
value={count(figures?.enrollments)}
|
|
202
|
+
footer={t('dashboard.kpiAllTime', 'Since the first one')!}
|
|
203
|
+
/>
|
|
204
|
+
<KpiCard
|
|
205
|
+
kpi="recent"
|
|
206
|
+
icon={<TrendingUp className="size-4" />}
|
|
207
|
+
title={t('dashboard.kpiRecent', 'New enrollments')}
|
|
208
|
+
value={count(figures && points.reduce((sum, point) => sum + point.enrollments, 0))}
|
|
209
|
+
footer={inRange}
|
|
210
|
+
/>
|
|
211
|
+
</>
|
|
212
|
+
) : null}
|
|
213
|
+
</div>
|
|
214
|
+
{reads ? (
|
|
215
|
+
<Card data-chart-card="enrollments">
|
|
216
|
+
<CardHeader>
|
|
217
|
+
<CardTitle>{t('dashboard.chartEnrollments', 'Enrollments per day')}</CardTitle>
|
|
218
|
+
<CardDescription>{inRange}</CardDescription>
|
|
219
|
+
<div className="flex items-center gap-1 pt-2">
|
|
220
|
+
{ranges.map((one) => (
|
|
221
|
+
<Button key={one} size="sm" variant={one === range ? 'secondary' : 'ghost'} className="max-sm:h-11" aria-pressed={one === range} onClick={() => choose(one)}>
|
|
222
|
+
{counted({ t, locale, key: 'dashboard.chartDays', count: one, fallback: '{count} days' })}
|
|
223
|
+
</Button>
|
|
224
|
+
))}
|
|
225
|
+
</div>
|
|
226
|
+
</CardHeader>
|
|
227
|
+
<CardContent>
|
|
228
|
+
<ChartContainer config={chartConfig} className="aspect-auto h-[280px] w-full">
|
|
229
|
+
<AreaChart data={points}>
|
|
230
|
+
<CartesianGrid vertical={false} />
|
|
231
|
+
{/* Time runs from the edge the text starts at: in Arabic
|
|
232
|
+
the oldest day is on the right and the figures too. */}
|
|
233
|
+
<XAxis dataKey="day" reversed={dir === 'rtl'} tickLine={false} axisLine={false} tickMargin={8} minTickGap={24} />
|
|
234
|
+
<YAxis orientation={dir === 'rtl' ? 'right' : 'left'} allowDecimals={false} tickLine={false} axisLine={false} width={40} tickFormatter={(value: number) => count(value)} />
|
|
235
|
+
<ChartTooltip content={<ChartTooltipContent indicator="dot" />} />
|
|
236
|
+
<Area dataKey="enrollments" type="monotone" fill="var(--color-enrollments)" fillOpacity={0.4} stroke="var(--color-enrollments)" />
|
|
237
|
+
</AreaChart>
|
|
238
|
+
</ChartContainer>
|
|
239
|
+
</CardContent>
|
|
240
|
+
</Card>
|
|
241
|
+
) : null}
|
|
242
|
+
{figures?.upcoming ? (
|
|
243
|
+
<Card data-home-table="sessions">
|
|
244
|
+
<CardHeader>
|
|
245
|
+
<CardTitle>{t('dashboard.upcoming', 'Upcoming sessions')}</CardTitle>
|
|
246
|
+
<CardDescription>{t('dashboard.upcomingAbout', 'The next sessions to start, soonest first.')}</CardDescription>
|
|
247
|
+
</CardHeader>
|
|
248
|
+
<CardContent>
|
|
249
|
+
{figures.upcoming.length === 0 ? (
|
|
250
|
+
<p className="text-muted-foreground text-sm">{t('dashboard.upcomingNone', 'No session is about to start.')}</p>
|
|
251
|
+
) : (
|
|
252
|
+
<div className="overflow-hidden rounded-lg border">
|
|
253
|
+
<Table>
|
|
254
|
+
<TableHeader className="bg-muted/50">
|
|
255
|
+
<TableRow>
|
|
256
|
+
<TableHead>{t('sessions.course', 'Course')}</TableHead>
|
|
257
|
+
<TableHead>{t('sessions.startsAt', 'Starts at')}</TableHead>
|
|
258
|
+
<TableHead className="max-sm:hidden">{t('sessions.venue', 'Venue')}</TableHead>
|
|
259
|
+
<TableHead className="text-end max-sm:hidden">{t('sessions.capacity', 'Capacity')}</TableHead>
|
|
260
|
+
</TableRow>
|
|
261
|
+
</TableHeader>
|
|
262
|
+
<TableBody>
|
|
263
|
+
{figures.upcoming.map((session) => (
|
|
264
|
+
<TableRow key={session.id}>
|
|
265
|
+
<TableCell className="font-medium">
|
|
266
|
+
<a href={sessionHref(session.id)} className="hover:underline">{session.course}</a>
|
|
267
|
+
{/* Below `sm` the venue and the capacity fold under the course's name, so the table
|
|
268
|
+
fits a phone's width with no column cut at its edge. */}
|
|
269
|
+
{session.venue || session.capacity !== undefined ? (
|
|
270
|
+
<span className="text-muted-foreground block text-xs font-normal sm:hidden">
|
|
271
|
+
{[...(session.venue ? [venueOf(session.venue)] : []), ...(session.capacity === undefined ? [] : [`${t('sessions.capacity', 'Capacity')}: ${count(session.capacity)}`])].join(' · ')}
|
|
272
|
+
</span>
|
|
273
|
+
) : null}
|
|
274
|
+
</TableCell>
|
|
275
|
+
{/* The viewer's own clock, which the server's may not be, so the browser's reading replaces the server's without a warning.
|
|
276
|
+
One line, so a phone does not break the date and the time over five. */}
|
|
277
|
+
<TableCell className="whitespace-nowrap tabular-nums" suppressHydrationWarning>{formatDate({ value: session.startsAt, locale })}</TableCell>
|
|
278
|
+
<TableCell className="max-sm:hidden">{session.venue ? venueOf(session.venue) : '—'}</TableCell>
|
|
279
|
+
<TableCell className="text-end tabular-nums max-sm:hidden">{session.capacity === undefined ? '—' : count(session.capacity)}</TableCell>
|
|
280
|
+
</TableRow>
|
|
281
|
+
))}
|
|
282
|
+
</TableBody>
|
|
283
|
+
</Table>
|
|
284
|
+
</div>
|
|
285
|
+
)}
|
|
286
|
+
</CardContent>
|
|
287
|
+
</Card>
|
|
288
|
+
) : null}
|
|
289
|
+
</section>
|
|
290
|
+
),
|
|
291
|
+
}}
|
|
292
|
+
/>
|
|
293
|
+
)
|
|
294
|
+
}
|