|
1 | 1 | <script lang="ts"> |
2 | 2 | import { onMount } from 'svelte'; |
3 | | - import { selectedPetStore, petHelpers, selectedPetHelpers } from '$lib/stores/pets'; |
| 3 | + import { petStore, selectedPetStore, petHelpers, selectedPetHelpers } from '$lib/stores/pets'; |
4 | 4 | import { aiAnalysisHelpers, isAnalyzing } from '$lib/stores/ai-analysis'; |
5 | | - import { PenTool, Brain, Calendar, Heart, Activity } from 'lucide-svelte'; |
| 5 | + import { PenTool, Brain, Calendar, Activity } from 'lucide-svelte'; |
6 | 6 | import AIInsightsCard from '../ui/AIInsightsCard.svelte'; |
7 | 7 | import EmptyState from '../ui/EmptyState.svelte'; |
8 | 8 | import Skeleton from '../ui/Skeleton.svelte'; |
9 | 9 | import type { PetPanelData } from '$lib/types/Pet'; |
10 | 10 | import type { JournalEntry } from '$lib/types/JournalEntry'; |
11 | 11 |
|
12 | 12 | let selectedPet: PetPanelData | null = null; |
| 13 | + let selectedPetId: string | null = null; |
| 14 | + let pets: PetPanelData[] = []; |
13 | 15 | let currentView = 'dashboard'; // dashboard, journal, history |
14 | 16 | let journalInput = ''; |
15 | 17 | let selectedMood = ''; |
|
18 | 20 | let loading = false; |
19 | 21 |
|
20 | 22 | // Computed values |
21 | | - $: lastEntry = selectedPet?.journalEntries?.length ? selectedPet.journalEntries[selectedPet.journalEntries.length - 1] : null; |
| 23 | + $: lastEntry = selectedPet?.journalEntries?.length |
| 24 | + ? selectedPet.journalEntries[selectedPet.journalEntries.length - 1] |
| 25 | + : null; |
| 26 | +
|
| 27 | + function formatAge(pet: PetPanelData): string { |
| 28 | + if (!pet || (pet.age === undefined || pet.age === null)) return ''; |
| 29 | + const unit = pet.ageUnit || 'years'; |
| 30 | + const abbr = unit === 'years' ? 'y' : unit === 'months' ? 'm' : 'w'; |
| 31 | + return `${pet.age} ${abbr}`; |
| 32 | + } |
| 33 | +
|
| 34 | + function petSubtitle(pet: PetPanelData): string { |
| 35 | + const parts: string[] = []; |
| 36 | + if (pet.species) parts.push(pet.species); |
| 37 | + if (pet.breed) parts.push(pet.breed); |
| 38 | + const age = formatAge(pet); |
| 39 | + if (age) parts.push(age); |
| 40 | + return parts.join(' | '); |
| 41 | + } |
22 | 42 |
|
23 | 43 | onMount(() => { |
24 | | - // Load pets and selected pet from storage |
25 | | - petHelpers.load(); |
26 | | - selectedPetHelpers.load(); |
| 44 | + // Subscribe first so incoming loads propagate into state |
| 45 | + petStore.subscribe((list) => { |
| 46 | + pets = list || []; |
| 47 | + if (selectedPetId) { |
| 48 | + selectedPet = pets.find((p) => p.id === selectedPetId) || null; |
| 49 | + } else if (!selectedPetId && pets.length > 0) { |
| 50 | + // Auto-select the first pet to show a summary by default |
| 51 | + selectedPetHelpers.select(pets[0].id); |
| 52 | + } |
| 53 | + }); |
27 | 54 |
|
28 | 55 | selectedPetStore.subscribe((petId) => { |
29 | | - selectedPet = petId ? petHelpers.getPet(petId) : null; |
| 56 | + selectedPetId = petId; |
| 57 | + selectedPet = petId ? pets.find((p) => p.id === petId) || null : null; |
30 | 58 | }); |
| 59 | +
|
| 60 | + // Load from storage (will trigger subscriptions above) |
| 61 | + petHelpers.load(); |
| 62 | + selectedPetHelpers.load(); |
31 | 63 | }); |
32 | 64 |
|
33 | 65 | async function submitJournalEntry() { |
|
75 | 107 | } |
76 | 108 | </script> |
77 | 109 |
|
78 | | -<div class="viewport-container h-full flex flex-col"> |
79 | | - {#if loading} |
80 | | - <!-- Loading State with Skeleton --> |
81 | | - <div class="space-y-4"> |
82 | | - <Skeleton height="h-8" /> |
83 | | - <Skeleton avatar height="h-20" /> |
84 | | - <Skeleton height="h-6" /> |
85 | | - <Skeleton height="h-4" /> |
86 | | - </div> |
87 | | - {:else if !lastEntry} |
88 | | - <!-- No entries state --> |
89 | | - <EmptyState |
90 | | - icon="file-text" |
91 | | - title="No journal entries yet" |
92 | | - description="Start documenting {selectedPet?.name || 'your pet'}'s daily activities, moods, and special moments." |
93 | | - actionText="Create First Entry" |
94 | | - onAction={() => { |
95 | | - console.log('Create entry clicked'); |
96 | | - // Could dispatch event to show journal form |
97 | | - }} |
98 | | - /> |
99 | | - {:else} |
100 | | - <!-- Enhanced Welcome Screen --> |
101 | | - <div class="welcome-screen h-full flex items-center justify-center"> |
102 | | - <EmptyState |
103 | | - icon="heart" |
104 | | - title="Welcome to Petalytics! 🐾" |
105 | | - description="Create your first pet profile to start journaling and get AI-powered insights about your furry friend's well-being." |
106 | | - actionText="Create Your First Pet" |
107 | | - onAction={() => { |
108 | | - // Focus on pet panel - could dispatch event to parent |
109 | | - console.log('Create pet clicked'); |
110 | | - }} |
111 | | - /> |
| 110 | +<div class="viewport-container h-full flex flex-col font-mono"> |
| 111 | + {#if loading} |
| 112 | + <!-- Loading State with Skeleton --> |
| 113 | + <div class="space-y-4"> |
| 114 | + <Skeleton height="h-8" /> |
| 115 | + <Skeleton avatar height="h-20" /> |
| 116 | + <Skeleton height="h-6" /> |
| 117 | + <Skeleton height="h-4" /> |
112 | 118 | </div> |
| 119 | + {:else if !selectedPet} |
| 120 | + <EmptyState |
| 121 | + icon="file-text" |
| 122 | + title="No pet selected" |
| 123 | + description="Select a pet from the left panel to view details, add journal entries, and see AI insights." |
| 124 | + actionText="Add a Pet" |
| 125 | + onAction={() => { |
| 126 | + console.log('Add a Pet clicked'); |
| 127 | + }} |
| 128 | + /> |
| 129 | + {:else} |
113 | 130 | <div class="pet-viewport h-full flex flex-col"> |
114 | 131 | <!-- Header with pet info and navigation --> |
115 | | - <div class="viewport-header p-4 border-b" style="border-color: var(--petalytics-border);"> |
| 132 | + <div class="viewport-header p-4 border-b" style="border-color: var(--petalytics-border); background: var(--petalytics-overlay);"> |
116 | 133 | <div class="flex items-center justify-between"> |
117 | 134 | <div class="flex items-center space-x-3"> |
118 | 135 | <img |
|
121 | 138 | class="w-12 h-12 rounded-full object-cover" |
122 | 139 | /> |
123 | 140 | <div> |
124 | | - <h2 class="text-xl font-bold" style="color: var(--petalytics-text);"> |
125 | | - {selectedPet.name} |
126 | | - </h2> |
127 | | - <p class="text-sm" style="color: var(--petalytics-subtle);"> |
128 | | - {selectedPet.age} year old {selectedPet.breed} |
129 | | - </p> |
| 141 | + <h2 class="text-xl font-bold" style="color: var(--petalytics-text);">{selectedPet.name}</h2> |
| 142 | + <p class="text-xs" style="color: var(--petalytics-subtle);">{petSubtitle(selectedPet)}</p> |
130 | 143 | </div> |
131 | 144 | </div> |
132 | 145 |
|
|
177 | 190 | style="background: var(--petalytics-surface);" |
178 | 191 | > |
179 | 192 | <div class="text-2xl font-bold" style="color: var(--petalytics-accent);"> |
180 | | - {selectedPet.age} |
| 193 | + {formatAge(selectedPet)} |
181 | 194 | </div> |
182 | | - <div class="text-xs" style="color: var(--petalytics-subtle);">Years Old</div> |
| 195 | + <div class="text-xs" style="color: var(--petalytics-subtle);">Age</div> |
183 | 196 | </div> |
184 | 197 | <div |
185 | 198 | class="stat-card p-4 rounded-lg text-center" |
|
200 | 213 | class="stat-card p-4 rounded-lg text-center" |
201 | 214 | style="background: var(--petalytics-surface);" |
202 | 215 | > |
203 | | - <div class="text-2xl font-bold" style="color: var(--petalytics-accent);"> |
204 | | - {selectedPet.breed} |
| 216 | + <div class="text-2xl font-bold truncate" style="color: var(--petalytics-accent);"> |
| 217 | + {selectedPet.breed || selectedPet.species || '—'} |
205 | 218 | </div> |
206 | | - <div class="text-xs" style="color: var(--petalytics-subtle);">Breed</div> |
| 219 | + <div class="text-xs" style="color: var(--petalytics-subtle);">Breed / Species</div> |
207 | 220 | </div> |
208 | 221 | </div> |
209 | 222 |
|
|
273 | 286 | well-being. |
274 | 287 | </p> |
275 | 288 | {:else} |
276 | | - <AIInsightsCard petId={selectedPet.id} /> |
| 289 | + <AIInsightsCard petId={selectedPet.id} entryId={lastEntry?.id} /> |
277 | 290 | {/if} |
278 | 291 | </div> |
279 | 292 | </div> |
|
304 | 317 | <option value="tired">😴 Tired</option> |
305 | 318 | <option value="anxious">😰 Anxious</option> |
306 | 319 | <option value="sad">😢 Sad</option> |
307 | | - <option value="sick">🤒 Unwell</option> |
| 320 | + <option value="sick">🩺 Unwell</option> |
308 | 321 | </select> |
309 | 322 | </div> |
310 | 323 |
|
|
0 commit comments