Skip to content

Commit 6328952

Browse files
committed
feat: enhance PetPanel with species, age unit, gender, and size fields; add autocomplete suggestions
1 parent 570602e commit 6328952

File tree

2 files changed

+144
-17
lines changed

2 files changed

+144
-17
lines changed

src/lib/components/panels/PetPanel.svelte

Lines changed: 140 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,76 @@
99
let showCreateForm = false;
1010
let imageInput: HTMLInputElement;
1111
12+
const speciesSuggestions = ['dog','cat','bird','reptile','fish','rabbit','hamster','other'] as const;
13+
const ageUnitSuggestions = ['years','months','weeks'] as const;
14+
const genderSuggestions = ['male','female','unknown'] as const;
15+
const sizeSuggestions = ['tiny','small','medium','large','extra_large'] as const;
16+
17+
const breedSuggestionsMap: Record<string, string[]> = {
18+
dog: ['mixed','labrador','golden_retriever','german_shepherd','bulldog'],
19+
cat: ['persian','siamese','maine_coon','sphynx','mixed'],
20+
bird: ['budgie','cockatiel','parrot','canary','finch'],
21+
reptile: ['snake','lizard','turtle','gecko','iguana'],
22+
fish: ['goldfish','betta','tropical','saltwater'],
23+
rabbit: ['lop','rex','lionhead','netherland_dwarf'],
24+
hamster: ['syrian','dwarf','roborovski','chinese'],
25+
other: [],
26+
};
27+
28+
function getBreedSuggestions(species: string): string[] {
29+
return breedSuggestionsMap[species] || [];
30+
}
31+
32+
const norm = (s: string) => (s || '').trim().toLowerCase();
33+
34+
function normalizeAgeUnit(u: string): 'years' | 'months' | 'weeks' {
35+
const v = norm(u);
36+
if (v.startsWith('m')) {
37+
// could be months
38+
return 'months';
39+
}
40+
if (v.startsWith('w')) {
41+
return 'weeks';
42+
}
43+
return 'years';
44+
}
45+
46+
function firstSuggestion(suggestions: readonly string[] | string[], value: string): string | null {
47+
const p = norm(value);
48+
if (!p) return null;
49+
const list = Array.from(suggestions);
50+
return list.find((s) => s.startsWith(p)) || null;
51+
}
52+
53+
function handleAutocomplete(field: 'species'|'breed'|'ageUnit'|'gender'|'size', suggestions: readonly string[] | string[], e: KeyboardEvent) {
54+
if (e.key === 'Enter') {
55+
const current = (newPet as any)[field] as string;
56+
const choice = firstSuggestion(suggestions, current);
57+
if (choice) {
58+
(newPet as any)[field] = choice;
59+
e.preventDefault();
60+
}
61+
}
62+
}
63+
64+
function onSpeciesInput(e: Event) {
65+
const target = e.target as HTMLInputElement;
66+
const prev = norm(newPet.species);
67+
newPet.species = target.value;
68+
const curr = norm(target.value);
69+
if (curr !== prev) {
70+
newPet.breed = '';
71+
}
72+
}
73+
1274
let newPet = {
1375
name: '',
76+
species: '',
1477
breed: '',
1578
age: '',
16-
gender: '',
79+
ageUnit: 'years',
80+
gender: 'unknown',
81+
size: 'medium',
1782
profileImageUrl: '',
1883
};
1984
@@ -40,9 +105,12 @@
40105
function resetForm() {
41106
newPet = {
42107
name: '',
108+
species: '',
43109
breed: '',
44110
age: '',
45-
gender: '',
111+
ageUnit: 'years',
112+
gender: 'unknown',
113+
size: 'medium',
46114
profileImageUrl: '',
47115
};
48116
formErrors = {};
@@ -74,13 +142,21 @@
74142
formErrors.name = 'Pet name is required';
75143
}
76144
145+
if (!newPet.species) {
146+
formErrors.species = 'Please enter species';
147+
}
148+
77149
if (!newPet.breed.trim()) {
78-
formErrors.breed = 'Breed is required';
150+
// Breed can be optional for some species; provide hint if suggestions exist
151+
const suggestions = getBreedSuggestions(newPet.species);
152+
if (suggestions.length) {
153+
formErrors.breed = `Consider selecting a type e.g. ${suggestions.slice(0,3).join('|')}`;
154+
}
79155
}
80156
81157
const age = parseInt(newPet.age);
82-
if (!newPet.age || age < 0 || age > 30) {
83-
formErrors.age = 'Please enter a valid age (0-30 years)';
158+
if (Number.isNaN(age) || age < 0) {
159+
formErrors.age = 'Please enter a valid age';
84160
}
85161
86162
if (!newPet.gender) {
@@ -96,9 +172,12 @@
96172
const pet: PetPanelData = {
97173
id: Date.now().toString(),
98174
name: newPet.name.trim(),
175+
species: newPet.species || 'other',
99176
breed: newPet.breed.trim(),
100177
age: parseInt(newPet.age),
101-
gender: newPet.gender as 'male' | 'female',
178+
ageUnit: normalizeAgeUnit(newPet.ageUnit),
179+
gender: (newPet.gender as 'male'|'female'|'unknown'),
180+
size: newPet.size as 'tiny'|'small'|'medium'|'large'|'extra_large',
102181
profileImageUrl: newPet.profileImageUrl || '/images/default-pet.png',
103182
createdAt: new Date().toISOString(),
104183
journalEntries: [],
@@ -137,6 +216,11 @@
137216

138217
{#if showCreateForm}
139218
<div class="mt-2 p-2 rounded" style="background: var(--petalytics-overlay);">
219+
<!-- section header -->
220+
<div class="cli-row px-2 py-1">
221+
<span style="color: var(--petalytics-subtle);">#</span>
222+
<span class="ml-2" style="color: var(--petalytics-gold);">new_pet</span>
223+
</div>
140224
<!-- name -->
141225
<div class="cli-row px-2 py-1">
142226
<span class="label">name</span>
@@ -146,37 +230,77 @@
146230
<p class="px-2 text-xs" style="color: var(--petalytics-love);">{formErrors.name}</p>
147231
{/if}
148232

149-
<!-- breed -->
233+
<!-- species (text + autocomplete) -->
234+
<div class="cli-row px-2 py-1">
235+
<span class="label">species</span>
236+
<input class="value bg-transparent border-none outline-none input-inline" bind:value={newPet.species} placeholder="e.g. bird" list="species-suggestions" oninput={onSpeciesInput} onkeydown={(e) => handleAutocomplete('species', speciesSuggestions, e)} />
237+
</div>
238+
<datalist id="species-suggestions">
239+
{#each speciesSuggestions as s}
240+
<option value={s}></option>
241+
{/each}
242+
</datalist>
243+
{#if formErrors.species}
244+
<p class="px-2 text-xs" style="color: var(--petalytics-love);">{formErrors.species}</p>
245+
{/if}
246+
247+
<!-- breed (text + species-based autocomplete) -->
150248
<div class="cli-row px-2 py-1">
151249
<span class="label">breed</span>
152-
<input class="value bg-transparent border-none outline-none input-inline" bind:value={newPet.breed} placeholder="Breed" />
250+
<input class="value bg-transparent border-none outline-none input-inline" bind:value={newPet.breed} placeholder="Breed / type" list="breed-suggestions" onkeydown={(e) => handleAutocomplete('breed', getBreedSuggestions(norm(newPet.species)), e)} />
153251
</div>
252+
<!-- suggestions datalist -->
253+
<datalist id="breed-suggestions">
254+
{#each getBreedSuggestions(newPet.species) as suggestion}
255+
<option value={suggestion}></option>
256+
{/each}
257+
</datalist>
154258
{#if formErrors.breed}
155259
<p class="px-2 text-xs" style="color: var(--petalytics-love);">{formErrors.breed}</p>
156260
{/if}
157261

158262
<!-- age -->
159263
<div class="cli-row px-2 py-1">
160264
<span class="label">age</span>
161-
<input class="value bg-transparent border-none outline-none input-inline" type="number" min="0" max="30" bind:value={newPet.age} placeholder="Age" />
265+
<div class="value flex items-center justify-end gap-2">
266+
<input class="bg-transparent border-none outline-none input-inline w-20 text-right" type="number" min="0" bind:value={newPet.age} placeholder="0" />
267+
<input class="bg-transparent border-none outline-none input-inline w-28" bind:value={newPet.ageUnit} placeholder="years|months|weeks" list="age-unit-suggestions" onkeydown={(e) => handleAutocomplete('ageUnit', ageUnitSuggestions, e)} />
268+
</div>
162269
</div>
270+
<datalist id="age-unit-suggestions">
271+
{#each ageUnitSuggestions as u}
272+
<option value={u}></option>
273+
{/each}
274+
</datalist>
163275
{#if formErrors.age}
164276
<p class="px-2 text-xs" style="color: var(--petalytics-love);">{formErrors.age}</p>
165277
{/if}
166278

167279
<!-- gender -->
168280
<div class="cli-row px-2 py-1">
169281
<span class="label">gender</span>
170-
<select class="value bg-transparent border-none outline-none input-inline" bind:value={newPet.gender}>
171-
<option value="">Select gender</option>
172-
<option value="male">male</option>
173-
<option value="female">female</option>
174-
</select>
282+
<input class="value bg-transparent border-none outline-none input-inline" bind:value={newPet.gender} placeholder="male|female|unknown" list="gender-suggestions" onkeydown={(e) => handleAutocomplete('gender', genderSuggestions, e)} />
175283
</div>
284+
<datalist id="gender-suggestions">
285+
{#each genderSuggestions as g}
286+
<option value={g}></option>
287+
{/each}
288+
</datalist>
176289
{#if formErrors.gender}
177290
<p class="px-2 text-xs" style="color: var(--petalytics-love);">{formErrors.gender}</p>
178291
{/if}
179292

293+
<!-- size -->
294+
<div class="cli-row px-2 py-1">
295+
<span class="label">size</span>
296+
<input class="value bg-transparent border-none outline-none input-inline" bind:value={newPet.size} placeholder="tiny|small|medium|large|extra_large" list="size-suggestions" onkeydown={(e) => handleAutocomplete('size', sizeSuggestions, e)} />
297+
</div>
298+
<datalist id="size-suggestions">
299+
{#each sizeSuggestions as s}
300+
<option value={s}></option>
301+
{/each}
302+
</datalist>
303+
180304
<!-- profile image upload -->
181305
<div class="cli-row px-2 py-1" style="align-items: flex-start;">
182306
<span class="label">profile_image</span>
@@ -205,7 +329,7 @@
205329
<!-- Pets list -->
206330
<div class="cli-row px-2 py-1">
207331
<span style="color: var(--petalytics-subtle);">#</span>
208-
<span class="ml-2" style="color: var(--petalytics-gold);">pets</span>
332+
<span class="ml-2" style="color: var(--petalytics-gold);">active_pets</span>
209333
</div>
210334

211335
{#if pets.length === 0}
@@ -215,7 +339,7 @@
215339
<div class="cli-row px-2 py-1" role="button" tabindex="0" data-selected={selectedPetId === pet.id} onclick={() => selectPet(pet.id)} onkeydown={(e) => handleActivate(e, () => selectPet(pet.id))}>
216340
<span class="label" style="color: var(--petalytics-text);">{pet.name}</span>
217341
<span class="value" style="color: var(--petalytics-subtle);">
218-
{pet.breed} | {pet.age}{pet.age === 1 ? 'y' : 'y'}
342+
{pet.species || 'pet'} | {pet.breed || ''} | {pet.age}{pet.ageUnit === 'months' ? 'm' : pet.ageUnit === 'weeks' ? 'w' : 'y'}
219343
</span>
220344
</div>
221345
{/each}

src/lib/types/Pet.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,12 @@ export interface Pet {
2323
export interface PetPanelData {
2424
id: string;
2525
name: string;
26+
species?: string;
2627
breed: string;
2728
age: number;
28-
gender: 'male' | 'female';
29+
ageUnit?: 'years' | 'months' | 'weeks';
30+
gender: 'male' | 'female' | 'unknown';
31+
size?: 'tiny' | 'small' | 'medium' | 'large' | 'extra_large';
2932
profileImageUrl?: string;
3033
createdAt: string;
3134
journalEntries: PetJournalEntry[];

0 commit comments

Comments
 (0)