- Dashboard with stats grid, checked-out/low-stock/recent items - Scan route with check-in/out flow - Items list with search, filters (category/type/custody), sorting - Item detail with full metadata, check-in/out, edit, delete - New item form wired to inventory store - Locations browser with tree view and items-at-location - Labels page with batch generation and PDF download - Settings page with DB stats - Fixed Svelte 5 event modifier syntax (no pipe modifiers) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
87 lines
2.7 KiB
Svelte
87 lines
2.7 KiB
Svelte
<script lang="ts">
|
|
import { inventory } from '$lib/stores/inventory.svelte';
|
|
import ItemCard from '$lib/components/ItemCard.svelte';
|
|
import { goto } from '$app/navigation';
|
|
|
|
const totalItems = $derived(inventory.items.length);
|
|
const checkedOut = $derived(inventory.checkedOutItems.length);
|
|
const overdue = $derived(inventory.overdueItems.length);
|
|
const lowStock = $derived(inventory.lowStockItems.length);
|
|
|
|
const stats = $derived([
|
|
{ label: 'Total Items', value: totalItems, color: 'text-blue-400' },
|
|
{ label: 'Checked Out', value: checkedOut, color: 'text-amber-400' },
|
|
{ label: 'Overdue', value: overdue, color: overdue > 0 ? 'text-red-400' : 'text-slate-400' },
|
|
{ label: 'Low Stock', value: lowStock, color: lowStock > 0 ? 'text-red-400' : 'text-slate-400' },
|
|
]);
|
|
|
|
const recentItems = $derived(
|
|
[...inventory.items]
|
|
.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt))
|
|
.slice(0, 5)
|
|
);
|
|
</script>
|
|
|
|
<div class="p-4 space-y-6">
|
|
<div>
|
|
<h1 class="text-2xl font-bold">SolidHaus</h1>
|
|
<p class="text-slate-400 text-sm">Household inventory at a glance</p>
|
|
</div>
|
|
|
|
<!-- Stats Grid -->
|
|
<div class="grid grid-cols-2 gap-3">
|
|
{#each stats as stat}
|
|
<div class="bg-slate-800 rounded-lg p-3">
|
|
<div class="text-2xl font-bold {stat.color}">{stat.value}</div>
|
|
<div class="text-xs text-slate-400">{stat.label}</div>
|
|
</div>
|
|
{/each}
|
|
</div>
|
|
|
|
<!-- Checked Out Items -->
|
|
{#if inventory.checkedOutItems.length > 0}
|
|
<section>
|
|
<h2 class="text-sm font-medium text-slate-400 uppercase mb-2">Checked Out</h2>
|
|
<div class="space-y-2">
|
|
{#each inventory.checkedOutItems.slice(0, 5) as item}
|
|
<ItemCard {item} onclick={() => goto(`/items/${item.shortId}`)} />
|
|
{/each}
|
|
</div>
|
|
</section>
|
|
{/if}
|
|
|
|
<!-- Low Stock -->
|
|
{#if inventory.lowStockItems.length > 0}
|
|
<section>
|
|
<h2 class="text-sm font-medium text-slate-400 uppercase mb-2">Low Stock</h2>
|
|
<div class="space-y-2">
|
|
{#each inventory.lowStockItems.slice(0, 5) as item}
|
|
<ItemCard {item} onclick={() => goto(`/items/${item.shortId}`)} />
|
|
{/each}
|
|
</div>
|
|
</section>
|
|
{/if}
|
|
|
|
<!-- Recent Items -->
|
|
{#if recentItems.length > 0}
|
|
<section>
|
|
<h2 class="text-sm font-medium text-slate-400 uppercase mb-2">Recently Updated</h2>
|
|
<div class="space-y-2">
|
|
{#each recentItems as item}
|
|
<ItemCard {item} onclick={() => goto(`/items/${item.shortId}`)} />
|
|
{/each}
|
|
</div>
|
|
</section>
|
|
{:else}
|
|
<div class="text-center py-8">
|
|
<p class="text-slate-500 mb-4">No items yet. Start by scanning or adding an item.</p>
|
|
<a
|
|
href="/items/new"
|
|
class="inline-block px-6 py-3 bg-blue-500 text-white rounded-lg font-medium
|
|
hover:bg-blue-600 transition-colors"
|
|
>
|
|
Add First Item
|
|
</a>
|
|
</div>
|
|
{/if}
|
|
</div>
|