From 4fec016ac55fc5af2fa2042eb8c3a87366bde07a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aleksa=20Siri=C5=A1ki?= <31509435+aleksasiriski@users.noreply.github.com> Date: Sun, 5 Jan 2025 11:14:25 +0100 Subject: [PATCH] feat: stats route (#126) --- package.json | 2 +- pnpm-lock.yaml | 49 ++++++-- .../custom/sidebar/app-sidebar.svelte | 22 ++-- .../custom/stats/countPerDepartment.svelte | 111 ++++++++++++++++++ .../insideCountPerBuilding.svelte | 8 +- src/lib/components/ui/sidebar/sidebar.svelte | 7 +- src/lib/components/ui/tabs/index.ts | 18 +++ .../components/ui/tabs/tabs-content.svelte | 19 +++ src/lib/components/ui/tabs/tabs-list.svelte | 15 +++ .../components/ui/tabs/tabs-trigger.svelte | 19 +++ src/lib/server/db/person.ts | 33 ++++++ src/routes/(dashboard)/+layout.svelte | 2 + src/routes/(dashboard)/+page.server.ts | 19 ++- src/routes/(dashboard)/+page.svelte | 22 +--- .../(dashboard)/statistics/+page.server.ts | 45 +++++++ .../(dashboard)/statistics/+page.svelte | 56 +++++++++ 16 files changed, 389 insertions(+), 58 deletions(-) create mode 100644 src/lib/components/custom/stats/countPerDepartment.svelte rename src/lib/components/custom/{inside_counter => stats}/insideCountPerBuilding.svelte (94%) create mode 100644 src/lib/components/ui/tabs/index.ts create mode 100644 src/lib/components/ui/tabs/tabs-content.svelte create mode 100644 src/lib/components/ui/tabs/tabs-list.svelte create mode 100644 src/lib/components/ui/tabs/tabs-trigger.svelte create mode 100644 src/routes/(dashboard)/statistics/+page.server.ts create mode 100644 src/routes/(dashboard)/statistics/+page.svelte diff --git a/package.json b/package.json index 0729e86..518db44 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,7 @@ "@sveltejs/vite-plugin-svelte": "^5.0.0", "@types/node": "^22.10.1", "autoprefixer": "^10.4.20", - "bits-ui": "1.0.0-next.71", + "bits-ui": "1.0.0-next.77", "clsx": "^2.1.1", "dotenv": "^16.4.7", "drizzle-kit": "^0.22.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9d1d87f..dc833be 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -46,8 +46,8 @@ importers: specifier: ^10.4.20 version: 10.4.20(postcss@8.4.49) bits-ui: - specifier: 1.0.0-next.71 - version: 1.0.0-next.71(svelte@5.9.0) + specifier: 1.0.0-next.77 + version: 1.0.0-next.77(svelte@5.9.0) clsx: specifier: ^2.1.1 version: 2.1.1 @@ -1320,11 +1320,11 @@ packages: resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} engines: {node: '>=8'} - bits-ui@1.0.0-next.71: - resolution: {integrity: sha512-ncZ42GwVpa1Ldw41opdLqP/HSkhNGNBnmfvWduYGZ0cVzDnkPwsUWvdikTsFI31ZoAMcfcUVFSVgLGCcBatUXQ==} + bits-ui@1.0.0-next.77: + resolution: {integrity: sha512-IV0AyVEvsRkXv4s/fl4iea5E9W2b9EBf98s9mRMKMc1xHxM9MmtM2r6MZMqftHQ/c+gHTIt3A9EKuTlh7uay8w==} engines: {node: '>=18', pnpm: '>=8.7.0'} peerDependencies: - svelte: ^5.0.0-next.1 + svelte: ^5.11.0 brace-expansion@1.1.11: resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} @@ -2224,10 +2224,15 @@ packages: run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} - runed@0.15.4: - resolution: {integrity: sha512-kmbpstUd7v2FdlBM+MT78IyuOVd38tq/e7MHvVb0fnVCsPSPMD/m2Xh+wUhzg9qCJgxRjBbIKu68DlH/x5VXJA==} + runed@0.20.0: + resolution: {integrity: sha512-YqPxaUdWL5nUXuSF+/v8a+NkVN8TGyEGbQwTA25fLY35MR/2bvZ1c6sCbudoo1kT4CAJPh4kUkcgGVxW127WKw==} peerDependencies: - svelte: ^5.0.0-next.1 + svelte: ^5.7.0 + + runed@0.22.0: + resolution: {integrity: sha512-ZWVXWhOr0P5xdNgtviz6D1ivLUDWKLCbeC5SUEJ3zBkqLReVqWHenFxMNFeFaiC5bfxhFxyxzyzB+98uYFtwdA==} + peerDependencies: + svelte: ^5.7.0 sade@1.8.1: resolution: {integrity: sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==} @@ -2336,6 +2341,12 @@ packages: peerDependencies: svelte: ^5.0.0-next.126 + svelte-toolbelt@0.7.0: + resolution: {integrity: sha512-i/Tv4NwAWWqJnK5H0F8y/ubDnogDYlwwyzKhrspTUFzrFuGnYshqd2g4/R43ds841wmaFiSW/HsdsdWhPOlrAA==} + engines: {node: '>=18', pnpm: '>=8.7.0'} + peerDependencies: + svelte: ^5.0.0 + svelte@5.9.0: resolution: {integrity: sha512-ZcC3BtjIDa4yfhAyAr94MxDQLD97zbpXmaUldFv2F5AkdZwYgQYB3BZVNRU5zEVaeeHoAns8ADiRMnre3QmpxQ==} engines: {node: '>=18'} @@ -3469,15 +3480,15 @@ snapshots: binary-extensions@2.3.0: {} - bits-ui@1.0.0-next.71(svelte@5.9.0): + bits-ui@1.0.0-next.77(svelte@5.9.0): dependencies: '@floating-ui/core': 1.6.8 '@floating-ui/dom': 1.6.12 '@internationalized/date': 3.6.0 esm-env: 1.2.1 - runed: 0.15.4(svelte@5.9.0) + runed: 0.22.0(svelte@5.9.0) svelte: 5.9.0 - svelte-toolbelt: 0.4.6(svelte@5.9.0) + svelte-toolbelt: 0.7.0(svelte@5.9.0) brace-expansion@1.1.11: dependencies: @@ -4294,7 +4305,12 @@ snapshots: dependencies: queue-microtask: 1.2.3 - runed@0.15.4(svelte@5.9.0): + runed@0.20.0(svelte@5.9.0): + dependencies: + esm-env: 1.2.1 + svelte: 5.9.0 + + runed@0.22.0(svelte@5.9.0): dependencies: esm-env: 1.2.1 svelte: 5.9.0 @@ -4407,6 +4423,13 @@ snapshots: style-to-object: 1.0.8 svelte: 5.9.0 + svelte-toolbelt@0.7.0(svelte@5.9.0): + dependencies: + clsx: 2.1.1 + runed: 0.20.0(svelte@5.9.0) + style-to-object: 1.0.8 + svelte: 5.9.0 + svelte@5.9.0: dependencies: '@ampproject/remapping': 2.3.0 @@ -4581,7 +4604,7 @@ snapshots: vaul-svelte@1.0.0-next.3(svelte@5.9.0): dependencies: - bits-ui: 1.0.0-next.71(svelte@5.9.0) + bits-ui: 1.0.0-next.77(svelte@5.9.0) svelte: 5.9.0 svelte-toolbelt: 0.4.6(svelte@5.9.0) diff --git a/src/lib/components/custom/sidebar/app-sidebar.svelte b/src/lib/components/custom/sidebar/app-sidebar.svelte index 106732d..dbf1be8 100644 --- a/src/lib/components/custom/sidebar/app-sidebar.svelte +++ b/src/lib/components/custom/sidebar/app-sidebar.svelte @@ -1,15 +1,18 @@ + + + + + + {#each allTypes as { type }} + + {/each} + + + + {#each departments as { department, types }} + + + {#each types as { count }} + + {/each} + + {/each} + +
Department{type}
{department}{count}
+ + + + + + + + + + + + {#each totalCountPerType as { type, count }} + + + + + {/each} + +
TypeTotal
{type}{count}
diff --git a/src/lib/components/custom/inside_counter/insideCountPerBuilding.svelte b/src/lib/components/custom/stats/insideCountPerBuilding.svelte similarity index 94% rename from src/lib/components/custom/inside_counter/insideCountPerBuilding.svelte rename to src/lib/components/custom/stats/insideCountPerBuilding.svelte index 422efc0..f623f34 100644 --- a/src/lib/components/custom/inside_counter/insideCountPerBuilding.svelte +++ b/src/lib/components/custom/stats/insideCountPerBuilding.svelte @@ -2,9 +2,9 @@ import type { PersonType } from '$lib/types/person'; let { - data + personsInsideCount }: { - data: { + personsInsideCount: { type: PersonType | null; building: string; insideCount: number; @@ -12,7 +12,7 @@ } = $props(); const allTypes = $derived( - data + personsInsideCount .filter( ( d @@ -30,7 +30,7 @@ .sort((t1, t2) => t1.type.localeCompare(t2.type)) ); const buildings = $derived.by(() => { - const dataMap = data.reduce( + const dataMap = personsInsideCount.reduce( (acc, { building, type, insideCount }) => { if (!acc[building]) acc[building] = {} as Record; if (type !== null) acc[building][type] = insideCount; diff --git a/src/lib/components/ui/sidebar/sidebar.svelte b/src/lib/components/ui/sidebar/sidebar.svelte index 2059a42..4451e42 100644 --- a/src/lib/components/ui/sidebar/sidebar.svelte +++ b/src/lib/components/ui/sidebar/sidebar.svelte @@ -35,12 +35,7 @@ {@render children?.()} {:else if sidebar.isMobile} - + + import { Tabs as TabsPrimitive } from 'bits-ui'; + import { cn } from '$lib/utils.js'; + + let { + ref = $bindable(null), + class: className, + ...restProps + }: TabsPrimitive.ContentProps = $props(); + + + diff --git a/src/lib/components/ui/tabs/tabs-list.svelte b/src/lib/components/ui/tabs/tabs-list.svelte new file mode 100644 index 0000000..1af5fd8 --- /dev/null +++ b/src/lib/components/ui/tabs/tabs-list.svelte @@ -0,0 +1,15 @@ + + + diff --git a/src/lib/components/ui/tabs/tabs-trigger.svelte b/src/lib/components/ui/tabs/tabs-trigger.svelte new file mode 100644 index 0000000..138e417 --- /dev/null +++ b/src/lib/components/ui/tabs/tabs-trigger.svelte @@ -0,0 +1,19 @@ + + + diff --git a/src/lib/server/db/person.ts b/src/lib/server/db/person.ts index bfbf2e7..9683b79 100644 --- a/src/lib/server/db/person.ts +++ b/src/lib/server/db/person.ts @@ -175,6 +175,39 @@ export async function getPersons( } } +// Gets the count of all persons per department +export async function getPersonsCountPerDepartment(): Promise< + { + type: PersonType; + department: string; + count: number; + }[] +> { + try { + const persons = await db + .select({ + type: person.type, + department: person.department, + count: count() + }) + .from(person) + .groupBy(person.type, person.department); + + return persons.map((p) => { + if (p.type !== null && !isPersonType(p.type)) { + throw new Error('Invalid type from DB (not PersonType)'); + } + return { + type: p.type, + department: p.department, + count: p.count + }; + }); + } catch (err: unknown) { + throw new Error(`Failed to get count of persons from database: ${(err as Error).message}}`); + } +} + // Gets the count of all persons that are inside, per building export async function getPersonsCountPerBuilding(): Promise< { diff --git a/src/routes/(dashboard)/+layout.svelte b/src/routes/(dashboard)/+layout.svelte index fa5a1a0..8e6c509 100644 --- a/src/routes/(dashboard)/+layout.svelte +++ b/src/routes/(dashboard)/+layout.svelte @@ -21,6 +21,8 @@ Create Student {:else if $page.url.pathname === '/create/employee'} Create Employee + {:else if $page.url.pathname === '/statistics'} + Statistics {:else if $page.url.pathname === '/instructions'} Instructions {:else} diff --git a/src/routes/(dashboard)/+page.server.ts b/src/routes/(dashboard)/+page.server.ts index 22bb634..1f3713a 100644 --- a/src/routes/(dashboard)/+page.server.ts +++ b/src/routes/(dashboard)/+page.server.ts @@ -1,4 +1,8 @@ -import { togglePersonState, getPersonsCountPerBuilding } from '$lib/server/db/person'; +import { + togglePersonState, + getPersonsCountPerBuilding, + getPersonsCountPerDepartment +} from '$lib/server/db/person'; import { fail, redirect, type Actions } from '@sveltejs/kit'; import type { PageServerLoad } from './$types'; import { invalidateSession } from '$lib/server/db/session'; @@ -8,19 +12,22 @@ import { search } from '$lib/utils/search'; export const load: PageServerLoad = async () => { try { const personsP = search(); - const personsInsideP = getPersonsCountPerBuilding(); + const personsCountP = getPersonsCountPerDepartment(); + const personsInsideCountP = getPersonsCountPerBuilding(); const persons = await personsP; - const personsInside = await personsInsideP; + const personsCount = await personsCountP; + const personsInsideCount = await personsInsideCountP; return { persons, - personsInside + personsCount, + personsInsideCount }; } catch (err: unknown) { - console.debug(`Failed to get students and employees: ${(err as Error).message}`); + console.debug(`Failed to get persons: ${(err as Error).message}`); return fail(500, { - message: 'Failed to get students and employees' + message: 'Failed to get persons' }); } }; diff --git a/src/routes/(dashboard)/+page.svelte b/src/routes/(dashboard)/+page.svelte index 5c24bfc..d4a453e 100644 --- a/src/routes/(dashboard)/+page.svelte +++ b/src/routes/(dashboard)/+page.svelte @@ -1,20 +1,17 @@
{/if} - - - - - - - Statistics - - Check how many people are currently inside. -
- -
-
-
diff --git a/src/routes/(dashboard)/statistics/+page.server.ts b/src/routes/(dashboard)/statistics/+page.server.ts new file mode 100644 index 0000000..fe310d6 --- /dev/null +++ b/src/routes/(dashboard)/statistics/+page.server.ts @@ -0,0 +1,45 @@ +import { getPersonsCountPerBuilding, getPersonsCountPerDepartment } from '$lib/server/db/person'; +import { fail, type Actions } from '@sveltejs/kit'; +import type { PageServerLoad } from './$types'; + +export const load: PageServerLoad = async () => { + try { + const personsCountP = getPersonsCountPerDepartment(); + const personsInsideCountP = getPersonsCountPerBuilding(); + + const personsCount = await personsCountP; + const personsInsideCount = await personsInsideCountP; + + return { + personsCount, + personsInsideCount + }; + } catch (err: unknown) { + console.debug(`Failed to get persons: ${(err as Error).message}`); + return fail(500, { + message: 'Failed to get persons' + }); + } +}; + +export const actions: Actions = { + default: async () => { + try { + const personsCountP = getPersonsCountPerDepartment(); + const personsInsideCountP = getPersonsCountPerBuilding(); + + const personsCount = await personsCountP; + const personsInsideCount = await personsInsideCountP; + + return { + personsCount, + personsInsideCount + }; + } catch (err: unknown) { + console.debug(`Failed to get persons: ${(err as Error).message}`); + return fail(500, { + message: 'Failed to get persons' + }); + } + } +}; diff --git a/src/routes/(dashboard)/statistics/+page.svelte b/src/routes/(dashboard)/statistics/+page.svelte new file mode 100644 index 0000000..5fed84d --- /dev/null +++ b/src/routes/(dashboard)/statistics/+page.svelte @@ -0,0 +1,56 @@ + + +
+ + + General + Inside + + + + + General Statistics + Check how many people are in the system. + + + + + + + + + + + + + Inside Statistics + Check how many people are currently inside. + + + + + + + + + + +