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 @@
+
+
+
+
+ Department |
+ {#each allTypes as { type }}
+ {type} |
+ {/each}
+
+
+
+ {#each departments as { department, types }}
+
+ {department} |
+ {#each types as { count }}
+ {count} |
+ {/each}
+
+ {/each}
+
+
+
+
+
+
+
+
+ Type |
+ Total |
+
+
+
+ {#each totalCountPerType as { type, count }}
+
+ {type} |
+ {count} |
+
+ {/each}
+
+
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 @@
+
+
+