Skip to content

Commit

Permalink
feat: stats route (#126)
Browse files Browse the repository at this point in the history
  • Loading branch information
aleksasiriski authored Jan 5, 2025
1 parent ad1edeb commit 4fec016
Show file tree
Hide file tree
Showing 16 changed files with 389 additions and 58 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
49 changes: 36 additions & 13 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

22 changes: 15 additions & 7 deletions src/lib/components/custom/sidebar/app-sidebar.svelte
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
<script lang="ts">
import House from 'lucide-svelte/icons/house';
import OpenBook from 'lucide-svelte/icons/book-open-text';
import Briefcase from 'lucide-svelte/icons/briefcase-business';
import Clipboard from 'lucide-svelte/icons/clipboard-list';
import Github from 'lucide-svelte/icons/github';
import * as Sidebar from '$lib/components/ui/sidebar/index.js';
import SidebarFooter from '$lib/components/ui/sidebar/sidebar-footer.svelte';
import Button from '$lib/components/ui/button/button.svelte';
import { useSidebar } from '$lib/components/ui/sidebar/context.svelte.js';
import LogoLight from '$lib/assets/images/light.svg';
import LogoDark from '$lib/assets/images/dark.svg';
import {
BookOpenText,
Briefcase,
ChartNoAxesCombined,
ClipboardList,
Github,
House
} from 'lucide-svelte';
// Menu items.
const items = [
Expand All @@ -21,17 +24,22 @@
{
title: 'Create Student',
url: '/create/student',
icon: OpenBook
icon: BookOpenText
},
{
title: 'Create Employee',
url: '/create/employee',
icon: Briefcase
},
{
title: 'Statistics',
url: '/statistics',
icon: ChartNoAxesCombined
},
{
title: 'Instructions',
url: '/instructions',
icon: Clipboard
icon: ClipboardList
}
];
Expand Down
111 changes: 111 additions & 0 deletions src/lib/components/custom/stats/countPerDepartment.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
<script lang="ts">
import Separator from '$lib/components/ui/separator/separator.svelte';
import type { PersonType } from '$lib/types/person';
let {
personsCount
}: {
personsCount: {
type: PersonType;
department: string;
count: number;
}[];
} = $props();
const allTypes = $derived(
personsCount
.filter((item, index, self) => self.findIndex((other) => other.type === item.type) === index)
.map((d) => ({
type: d.type,
count: 0
}))
.sort((t1, t2) => t1.type.localeCompare(t2.type))
);
const departments = $derived.by(() => {
const dataMap = personsCount.reduce(
(acc, { department, type, count }) => {
if (!acc[department]) acc[department] = {} as Record<PersonType, number>;
if (type !== null) acc[department][type] = count;
return acc;
},
{} as Record<string, Record<PersonType, number>>
);
return Object.entries(dataMap)
.map(([department, types]) => ({
department,
types: [
...Object.entries(types).map(([type, count]) => ({
type: type as PersonType,
count
})),
...allTypes
]
.filter(
(item, index, self) => self.findIndex((other) => other.type === item.type) === index
)
.sort((t1, t2) => t1.type.localeCompare(t2.type))
}))
.sort((d1, d2) => d1.department.localeCompare(d2.department));
});
const totalCountPerType = $derived(
departments
.flatMap((d) => d.types)
.reduce(
(acc, curr) => {
const existingType = acc.find((item) => item.type === curr.type);
if (!existingType) {
return [...acc, curr];
}
existingType.count += curr.count;
return acc;
},
[] as {
type: PersonType;
count: number;
}[]
)
.sort((t1, t2) => t1.type.localeCompare(t2.type))
);
</script>

<table class="w-full">
<thead>
<tr>
<th class="w-1/3">Department</th>
{#each allTypes as { type }}
<th class="w-1/3">{type}</th>
{/each}
</tr>
</thead>
<tbody class="text-center">
{#each departments as { department, types }}
<tr>
<td>{department}</td>
{#each types as { count }}
<td>{count}</td>
{/each}
</tr>
{/each}
</tbody>
</table>

<Separator class="my-3" />

<table class="w-full">
<thead>
<tr>
<th class="w-1/3">Type</th>
<th class="w-1/3">Total</th>
</tr>
</thead>
<tbody class="text-center">
{#each totalCountPerType as { type, count }}
<tr>
<td>{type}</td>
<td>{count}</td>
</tr>
{/each}
</tbody>
</table>
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,17 @@
import type { PersonType } from '$lib/types/person';
let {
data
personsInsideCount
}: {
data: {
personsInsideCount: {
type: PersonType | null;
building: string;
insideCount: number;
}[];
} = $props();
const allTypes = $derived(
data
personsInsideCount
.filter(
(
d
Expand All @@ -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<PersonType, number>;
if (type !== null) acc[building][type] = insideCount;
Expand Down
7 changes: 1 addition & 6 deletions src/lib/components/ui/sidebar/sidebar.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,7 @@
{@render children?.()}
</div>
{:else if sidebar.isMobile}
<Sheet.Root
controlledOpen
open={sidebar.openMobile}
onOpenChange={sidebar.setOpenMobile}
{...restProps}
>
<Sheet.Root open={sidebar.openMobile} onOpenChange={sidebar.setOpenMobile} {...restProps}>
<Sheet.Content
data-sidebar="sidebar"
data-mobile="true"
Expand Down
18 changes: 18 additions & 0 deletions src/lib/components/ui/tabs/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Tabs as TabsPrimitive } from 'bits-ui';
import Content from './tabs-content.svelte';
import List from './tabs-list.svelte';
import Trigger from './tabs-trigger.svelte';

const Root = TabsPrimitive.Root;

export {
Root,
Content,
List,
Trigger,
//
Root as Tabs,
Content as TabsContent,
List as TabsList,
Trigger as TabsTrigger
};
19 changes: 19 additions & 0 deletions src/lib/components/ui/tabs/tabs-content.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<script lang="ts">
import { Tabs as TabsPrimitive } from 'bits-ui';
import { cn } from '$lib/utils.js';
let {
ref = $bindable(null),
class: className,
...restProps
}: TabsPrimitive.ContentProps = $props();
</script>

<TabsPrimitive.Content
bind:ref
class={cn(
'mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',
className
)}
{...restProps}
/>
15 changes: 15 additions & 0 deletions src/lib/components/ui/tabs/tabs-list.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<script lang="ts">
import { Tabs as TabsPrimitive } from 'bits-ui';
import { cn } from '$lib/utils.js';
let { ref = $bindable(null), class: className, ...restProps }: TabsPrimitive.ListProps = $props();
</script>

<TabsPrimitive.List
bind:ref
class={cn(
'inline-flex h-10 items-center justify-center rounded-md bg-muted p-1 text-muted-foreground',
className
)}
{...restProps}
/>
Loading

0 comments on commit 4fec016

Please sign in to comment.