feat(mvp): phase 5 - board, section & widget system

Add board/section/widget CRUD APIs with permission filtering, board view
page with collapsible sections and app widgets in responsive grid, form-based
board editor, and 9 Svelte components (Board, Section, Widget families).
This commit is contained in:
2026-03-24 21:05:00 +03:00
parent 4d941f566f
commit b0d77d3c29
23 changed files with 1564 additions and 27 deletions
@@ -0,0 +1,68 @@
<script lang="ts">
import AppHealthBadge from '$lib/components/app/AppHealthBadge.svelte';
interface AppData {
id: string;
name: string;
url: string;
icon: string | null;
iconType: string;
description: string | null;
statuses: Array<{ status: string; responseTime: number | null }>;
}
interface Props {
app: AppData;
}
let { app }: Props = $props();
const latestStatus = $derived(app.statuses[0]?.status ?? 'unknown');
const iconSrc = $derived.by(() => {
if (!app.icon) return null;
switch (app.iconType) {
case 'url':
return app.icon;
case 'simple': {
const slug = app.icon.toLowerCase().replace(/[^a-z0-9]/g, '');
return `https://cdn.simpleicons.org/${slug}`;
}
default:
return null;
}
});
</script>
<a
href={app.url}
target="_blank"
rel="noopener noreferrer"
class="group flex flex-col items-center gap-2 rounded-lg border border-gray-700 bg-gray-800/50 p-4 text-center transition-colors hover:border-indigo-500/50 hover:bg-gray-800"
>
<!-- Icon -->
<div class="flex h-12 w-12 items-center justify-center rounded-lg bg-gray-700 transition-colors group-hover:bg-gray-600">
{#if app.iconType === 'emoji' && app.icon}
<span class="text-2xl">{app.icon}</span>
{:else if iconSrc}
<img
src={iconSrc}
alt="{app.name} icon"
class="h-8 w-8 object-contain"
/>
{:else}
<span class="text-lg font-bold text-gray-400">
{app.name.charAt(0).toUpperCase()}
</span>
{/if}
</div>
<!-- Name -->
<span class="text-sm font-medium text-white group-hover:text-indigo-300 transition-colors truncate w-full">
{app.name}
</span>
<!-- Status -->
<AppHealthBadge status={latestStatus} />
</a>