Skip to content

Commit ec6fc30

Browse files
committed
feat: add archive and memorial modals in PetPanel for pet management
1 parent 71dc504 commit ec6fc30

File tree

3 files changed

+132
-15
lines changed

3 files changed

+132
-15
lines changed

src/lib/components/panels/PetPanel.svelte

Lines changed: 61 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
import { Upload, Terminal } from 'lucide-svelte';
44
import { petStore, selectedPetStore, petHelpers, selectedPetHelpers } from '$lib/stores/pets';
55
import type { PetPanelData } from '$lib/types/Pet.js';
6+
import Modal from '../ui/Modal.svelte';
7+
import MemorialModal from '../ui/MemorialModal.svelte';
68
79
let pets: PetPanelData[] = [];
810
// Derived lists
@@ -12,6 +14,12 @@
1214
let showCreateForm = false;
1315
let imageInput: HTMLInputElement;
1416
17+
// Archive workflow state
18+
let confirmArchiveOpen = false;
19+
let petToArchive: PetPanelData | null = null;
20+
let memorialOpen = false;
21+
let memorialPet: PetPanelData | null = null;
22+
1523
const speciesSuggestions = ['dog','cat','bird','reptile','fish','rabbit','hamster','other'] as const;
1624
const ageUnitSuggestions = ['years','months','weeks'] as const;
1725
const genderSuggestions = ['male','female','unknown'] as const;
@@ -209,15 +217,39 @@
209217
}
210218
211219
function archivePet(petId: string) {
212-
petHelpers.archive(petId);
213-
if (selectedPetId === petId) {
214-
selectedPetHelpers.clear();
215-
}
220+
const p = pets.find((x) => x.id === petId) || null;
221+
petToArchive = p;
222+
confirmArchiveOpen = true;
216223
}
217224
218-
function unarchivePet(petId: string) {
219-
petHelpers.unarchive(petId);
220-
}
225+
function confirmArchive() {
226+
if (!petToArchive) return;
227+
petHelpers.archive(petToArchive.id);
228+
if (selectedPetId === petToArchive.id) {
229+
selectedPetHelpers.clear();
230+
}
231+
confirmArchiveOpen = false;
232+
petToArchive = null;
233+
}
234+
235+
function cancelArchive() {
236+
confirmArchiveOpen = false;
237+
petToArchive = null;
238+
}
239+
240+
function unarchivePet(petId: string) {
241+
petHelpers.unarchive(petId);
242+
}
243+
244+
function openMemorial(petId: string) {
245+
memorialPet = pets.find((p) => p.id === petId) || null;
246+
memorialOpen = true;
247+
}
248+
249+
function closeMemorial() {
250+
memorialOpen = false;
251+
memorialPet = null;
252+
}
221253
</script>
222254

223255
<div class="pet-panel h-full" style="background: var(--petalytics-bg);">
@@ -362,37 +394,52 @@
362394
<span class="value" style="color: var(--petalytics-subtle);">
363395
{pet.species || 'pet'} | {pet.breed || ''} | {pet.age}{pet.ageUnit === 'months' ? 'm' : pet.ageUnit === 'weeks' ? 'w' : 'y'}
364396
</span>
365-
</div>
366-
<div class="px-2 pb-2 flex justify-end">
367-
<button class="arrow-btn" onclick={() => archivePet(pet.id)}>archive</button>
368-
</div>
397+
<div class="ml-2 flex items-center gap-2">
398+
<button class="arrow-btn" onclick={() => selectPet(pet.id)}>select</button>
399+
<button class="arrow-btn" onclick={() => archivePet(pet.id)}>archive</button>
400+
</div>
401+
</div>
369402
{/each}
370403
{/if}
371404

372405
<!-- Archived list -->
373406
<div class="my-3"><div class="border-t" style="border-color: var(--petalytics-border);"></div></div>
374-
<div class="cli-row px-2 py-1">
407+
<div class="cli-row px-2 py-1" style="background: color-mix(in oklab, var(--petalytics-overlay) 60%, transparent);">
375408
<span style="color: var(--petalytics-subtle);">#</span>
376409
<span class="ml-2" style="color: var(--petalytics-gold);">archived_pets</span>
377410
</div>
378411
{#if archivedPets.length === 0}
379412
<div class="px-2 py-2" style="color: var(--petalytics-subtle);">none</div>
380413
{:else}
381414
{#each archivedPets as pet}
382-
<div class="cli-row px-2 py-1">
415+
<div class="cli-row px-2 py-1" style="opacity: 0.9;">
383416
<span class="label" style="color: var(--petalytics-text);">{pet.name}</span>
384417
<span class="value" style="color: var(--petalytics-subtle);">
385418
{pet.species || 'pet'} | {pet.breed || ''} | {pet.age}{pet.ageUnit === 'months' ? 'm' : pet.ageUnit === 'weeks' ? 'w' : 'y'}
386419
</span>
387420
</div>
388421
<div class="px-2 pb-2 flex justify-end">
389-
<button class="arrow-btn" onclick={() => unarchivePet(pet.id)}>unarchive</button>
422+
<button class="arrow-btn" onclick={() => openMemorial(pet.id)}>view_memories</button>
390423
</div>
391424
{/each}
392425
{/if}
393426
</div>
394427
</div>
395428

429+
<!-- Archive confirmation modal -->
430+
<Modal isOpen={confirmArchiveOpen} title="Archive Pet" size="sm" onclose={cancelArchive}>
431+
<div class="space-y-4 font-mono">
432+
<p>Mark {petToArchive?.name} as passed away?</p>
433+
<div class="flex justify-end gap-2">
434+
<button class="button-secondary" onclick={cancelArchive}>Cancel</button>
435+
<button class="button" onclick={confirmArchive}>Confirm</button>
436+
</div>
437+
</div>
438+
</Modal>
439+
440+
<!-- Memorial modal for archived pet -->
441+
<MemorialModal isOpen={memorialOpen} pet={memorialPet} onclose={closeMemorial} />
442+
396443
<style>
397444
.cli-row {
398445
display: flex;

src/lib/components/panels/Viewport.svelte

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@
112112
<!-- Loading State with Skeleton -->
113113
<div class="space-y-4">
114114
<Skeleton height="h-8" />
115-
<Skeleton avatar height="h-20" />
115+
<Skeleton showAvatar height="h-20" />
116116
<Skeleton height="h-6" />
117117
<Skeleton height="h-4" />
118118
</div>
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
<script lang="ts">
2+
import Modal from './Modal.svelte';
3+
import type { PetPanelData } from '$lib/types/Pet';
4+
5+
interface Props {
6+
isOpen: boolean;
7+
pet: PetPanelData | null;
8+
onclose: () => void;
9+
}
10+
11+
let { isOpen, pet, onclose }: Props = $props();
12+
13+
function yearsTogether(p: PetPanelData): number {
14+
if (!p?.createdAt) return 0;
15+
const start = new Date(p.createdAt).getTime();
16+
const end = Date.now();
17+
const days = Math.max(0, Math.floor((end - start) / (1000 * 60 * 60 * 24)));
18+
return Math.floor(days / 365);
19+
}
20+
21+
function petLine(p: PetPanelData): string {
22+
const ageUnit = p.ageUnit === 'months' ? 'm' : p.ageUnit === 'weeks' ? 'w' : 'y';
23+
const age = p.age ? `${p.age}${ageUnit}` : '';
24+
const parts = [p.species, p.breed, age].filter(Boolean);
25+
return parts.join(' | ');
26+
}
27+
28+
const entryList = $derived((pet?.journalEntries ?? []).slice().reverse());
29+
</script>
30+
31+
<Modal isOpen={isOpen} title={pet ? `In loving memory of ${pet.name}` : 'Memories'} size="lg" onclose={onclose}>
32+
{#if pet}
33+
<div class="space-y-4 font-mono">
34+
<div class="rounded p-3" style="background: color-mix(in oklab, var(--petalytics-overlay) 60%, transparent); border: 1px solid var(--petalytics-border);">
35+
<div class="flex items-center justify-between">
36+
<div>
37+
<div class="text-sm" style="color: var(--petalytics-subtle);">{petLine(pet)}</div>
38+
<div class="text-base font-semibold" style="color: var(--petalytics-text);">
39+
{pet.name} brought you {yearsTogether(pet)} years of joy.
40+
</div>
41+
</div>
42+
<div class="text-xs px-2 py-1 rounded" style="background: var(--petalytics-surface); color: var(--petalytics-subtle);">
43+
{entryList.length} memories
44+
</div>
45+
</div>
46+
</div>
47+
48+
{#if entryList.length === 0}
49+
<div class="text-sm" style="color: var(--petalytics-subtle);">No journal entries yet.</div>
50+
{:else}
51+
<div class="space-y-3">
52+
{#each entryList as entry}
53+
<div class="rounded border p-3" style="background: var(--petalytics-surface); border-color: var(--petalytics-border);">
54+
<div class="flex items-center justify-between mb-2">
55+
<div class="text-xs" style="color: var(--petalytics-subtle);">
56+
{new Date(entry.date as any).toLocaleDateString()}
57+
</div>
58+
<div class="text-sm" style="color: var(--petalytics-text);">{entry.mood || '🐾'}</div>
59+
</div>
60+
<div class="text-sm" style="color: var(--petalytics-text);">{entry.content}</div>
61+
</div>
62+
{/each}
63+
</div>
64+
{/if}
65+
</div>
66+
{/if}
67+
</Modal>
68+
69+
<style>
70+
</style>

0 commit comments

Comments
 (0)