Skip to content

Commit

Permalink
Merge pull request #119 from studentinovisad/as/fix/stats-type
Browse files Browse the repository at this point in the history
fix(stats): show all building even if empty and count per type
  • Loading branch information
aleksasiriski authored Jan 1, 2025
2 parents ca7b0ff + 030ce0e commit a2c3062
Show file tree
Hide file tree
Showing 3 changed files with 88 additions and 10 deletions.
Original file line number Diff line number Diff line change
@@ -1,26 +1,79 @@
<script lang="ts">
import type { PersonType } from '$lib/types/person';
let {
data
}: {
data: {
type: PersonType | null;
building: string;
insideCount: number;
}[];
} = $props();
const allTypes = $derived(
data
.filter(
(
d
): d is {
type: PersonType;
building: string;
insideCount: number;
} => d.type !== null
)
.filter((item, index, self) => self.findIndex((other) => other.type === item.type) === index)
.map((d) => ({
type: d.type,
insideCount: 0
}))
.sort((t1, t2) => t1.type.localeCompare(t2.type))
);
const buildings = $derived.by(() => {
const dataMap = data.reduce(
(acc, { building, type, insideCount }) => {
if (!acc[building]) acc[building] = {} as Record<PersonType, number>;
if (type !== null) acc[building][type] = insideCount;
return acc;
},
{} as Record<string, Record<PersonType, number>>
);
return Object.entries(dataMap)
.map(([building, types]) => ({
building,
types: [
...Object.entries(types).map(([type, insideCount]) => ({
type: type as PersonType,
insideCount
})),
...allTypes
]
.filter(
(item, index, self) => self.findIndex((other) => other.type === item.type) === index
)
.sort((t1, t2) => t1.type.localeCompare(t2.type))
}))
.sort((b1, b2) => b1.building.localeCompare(b2.building));
});
</script>

<table class="w-full">
<thead>
<tr>
<th class="w-1/3">Building</th>
<th class="w-1/3">People</th>
{#each allTypes as { type }}
<th class="w-1/3">{type}</th>
{/each}
</tr>
</thead>
<tbody class="text-center">
{#each data as { building, insideCount }}
{#each buildings as { building, types }}
<tr>
<td>{building}</td>
<td>{insideCount}</td>
{#each types as { insideCount }}
<td>{insideCount}</td>
{/each}
</tr>
{/each}
</tbody>
Expand Down
29 changes: 22 additions & 7 deletions src/lib/server/db/person.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { isInside } from '../isInside';
import { DB as db } from './connect';
import { capitalizeString, sanitizeString } from '$lib/utils/sanitize';
import { building } from './schema/building';
import type { PersonType } from '$lib/types/person';
import { isPersonType, type PersonType } from '$lib/types/person';

// Gets all persons using optional filters
export async function getPersons(
Expand Down Expand Up @@ -178,34 +178,49 @@ export async function getPersons(
// Gets the count of all persons that are inside, per building
export async function getPersonsCountPerBuilding(): Promise<
{
type: PersonType | null;
building: string;
insideCount: number;
}[]
> {
try {
const personInsideSubquery = db
.select({
personId: personEntry.personId,
personId: person.id,
type: person.type,
entryBuilding: personEntry.building,
maxEntryTimestamp: max(personEntry.timestamp),
maxExitTimestamp: max(personExit.timestamp)
})
.from(personEntry)
.leftJoin(personExit, eq(personEntry.personId, personExit.personId))
.groupBy(personEntry.personId, personEntry.building)
.from(person)
.leftJoin(personEntry, eq(personEntry.personId, person.id))
.leftJoin(personExit, eq(personExit.personId, person.id))
.groupBy(person.id, person.type, personEntry.building)
.having(({ maxEntryTimestamp, maxExitTimestamp }) =>
or(isNull(maxExitTimestamp), gt(maxEntryTimestamp, maxExitTimestamp))
)
.as('person_inside');

return await db
const persons = await db
.select({
type: personInsideSubquery.type,
building: building.name,
insideCount: count(personInsideSubquery.personId)
})
.from(building)
.leftJoin(personInsideSubquery, eq(building.name, personInsideSubquery.entryBuilding))
.groupBy(building.name);
.groupBy(personInsideSubquery.type, building.name);

return persons.map((p) => {
if (p.type !== null && !isPersonType(p.type)) {
throw new Error('Invalid type from DB (not PersonType)');
}
return {
type: p.type,
building: p.building,
insideCount: p.insideCount
};
});
} catch (err: unknown) {
throw new Error(
`Failed to get inside count of persons from database: ${(err as Error).message}}`
Expand Down
10 changes: 10 additions & 0 deletions src/lib/types/person.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,16 @@ export type PersonType = 'Student' | 'Employee';
export const Student: PersonType = 'Student';
export const Employee: PersonType = 'Employee';

export function isPersonType(s: string): s is PersonType {
switch (s) {
case Student:
case Employee:
return true;
default:
return false;
}
}

export type Person = {
id: number;
type: PersonType;
Expand Down

0 comments on commit a2c3062

Please sign in to comment.