diff --git a/backend/src/app.ts b/backend/src/app.ts index 1af5330..554889c 100644 --- a/backend/src/app.ts +++ b/backend/src/app.ts @@ -320,6 +320,7 @@ const birdSchema = z.object({ vetAccountNumber: z.string().trim().max(120).optional().or(z.literal('')), vetDoctorName: z.string().trim().max(160).optional().or(z.literal('')), gender: birdGenderSchema.optional(), + hatchDay: dateStringSchema.optional().or(z.literal('')), dateOfBirth: dateStringSchema.optional().or(z.literal('')), gotchaDay: dateStringSchema.optional().or(z.literal('')), chartColor: chartColorSchema.optional(), @@ -862,6 +863,7 @@ const normalizeBird = (row: BirdRow) => ({ vetAccountNumber: row.vet_account_number, vetDoctorName: row.vet_doctor_name, gender: row.gender, + hatchDay: row.date_of_birth, dateOfBirth: row.date_of_birth, gotchaDay: row.gotcha_day, chartColor: row.chart_color, @@ -890,6 +892,7 @@ const normalizePublicBirdProfile = (row: BirdRow) => ({ name: row.name, favoriteSnack: row.favorite_snack, gender: row.gender, + hatchDay: row.date_of_birth, dateOfBirth: row.date_of_birth, photoDataUrl: getBirdPhotoUrl(row), }); @@ -3978,7 +3981,7 @@ app.post('/api/birds', requireAuth, requireWriteAccess, requireWorkspaceRole(['o vetAccountNumber: emptyToNull(parsed.data.vetAccountNumber), vetDoctorName: emptyToNull(parsed.data.vetDoctorName), gender: (parsed.data.gender ?? 'unknown') as BirdGender, - dateOfBirth: emptyToNull(parsed.data.dateOfBirth), + dateOfBirth: emptyToNull(parsed.data.hatchDay || parsed.data.dateOfBirth), gotchaDay: emptyToNull(parsed.data.gotchaDay), chartColor: parsed.data.chartColor ?? '#cb3a35', photoDataUrl: photoStorage.photoDataUrl, @@ -4347,7 +4350,7 @@ app.put('/api/birds/:birdId', requireAuth, requireWriteAccess, requireWorkspaceR vetAccountNumber: emptyToNull(parsed.data.vetAccountNumber), vetDoctorName: emptyToNull(parsed.data.vetDoctorName), gender: (parsed.data.gender ?? 'unknown') as BirdGender, - dateOfBirth: emptyToNull(parsed.data.dateOfBirth), + dateOfBirth: emptyToNull(parsed.data.hatchDay || parsed.data.dateOfBirth), gotchaDay: emptyToNull(parsed.data.gotchaDay), chartColor: parsed.data.chartColor ?? '#cb3a35', photoDataUrl: photoStorage.photoDataUrl, diff --git a/frontend/index.html b/frontend/index.html index 4e1d880..fcb5097 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -8,7 +8,7 @@ type="image/svg+xml" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64'%3E%3Cdefs%3E%3ClinearGradient id='featherFill' x1='0%25' y1='0%25' x2='100%25' y2='100%25'%3E%3Cstop offset='0%25' stop-color='%23cb3a35'/%3E%3Cstop offset='30%25' stop-color='%23f0b63f'/%3E%3Cstop offset='58%25' stop-color='%23238a5a'/%3E%3Cstop offset='100%25' stop-color='%232769b3'/%3E%3C/linearGradient%3E%3C/defs%3E%3Cpath d='M50.8 10.4C37.9 10.3 27 18.5 22.7 31.1c-3.1 9.1-2.1 18.5-8.6 24.8c-1.5 1.5-0.2 4 1.9 3.6c8.4-1.5 14.6-6.7 18.6-13.7c1 0.5 2.2 0.8 3.4 0.8c3.5 0 6.5-2.3 7.5-5.4c1.9-0.4 3.7-1.3 5.1-2.7c2-2 3-4.6 3.1-7.2c3.3-5.8 4.9-12.9 1.4-20.2c-0.7-1.3-2-0.7-4.3-0.7Z' fill='url(%23featherFill)'/%3E%3Cpath d='M18 56c8.5-3.4 14.2-9.8 18.1-17.8M26.9 48.9c6.9-7.2 13.5-14.8 20.3-22.1M31.8 41.2c6.4-1.3 12.1-4.6 16.5-9.4M36.8 33.8c4.9-0.9 9.2-3.4 12.6-7.1' fill='none' stroke='%23fff8ef' stroke-linecap='round' stroke-width='2.6'/%3E%3Cpath d='M18 56c8.5-3.4 14.2-9.8 18.1-17.8' fill='none' stroke='%2363562d' stroke-linecap='round' stroke-width='2.2'/%3E%3C/svg%3E" /> - +
{searchState.error}
@@ -1024,7 +1021,7 @@ const toBirdForm = (bird: Bird): BirdFormState => ({
vetAccountNumber: bird.vetAccountNumber ?? '',
vetDoctorName: bird.vetDoctorName ?? '',
gender: bird.gender,
- dateOfBirth: bird.dateOfBirth ?? '',
+ dateOfBirth: getBirdHatchDay(bird) ?? '',
gotchaDay: bird.gotchaDay ?? '',
chartColor: bird.chartColor,
photoDataUrl: bird.photoDataUrl ?? '',
@@ -1244,7 +1241,289 @@ const sortBirdTimelineEvents = (events: BirdTimelineEvent[]) =>
return dateComparison || right.createdAt.localeCompare(left.createdAt);
});
-const getBirdTimelineGraphEvents = (events: BirdTimelineEvent[]) => sortBirdTimelineEvents(events).reverse().slice(-8);
+const TIMELINE_GRAPH_START_X = 52;
+const TIMELINE_GRAPH_END_X = 588;
+
+type BirdTimelineGraphItem = {
+ id: string;
+ eventType: BirdTimelineEvent['eventType'] | 'hatch_date';
+ date: string;
+ label: string;
+};
+
+type BirdTimelineGraphTick = {
+ id: string;
+ year: number;
+ date: string;
+ label: string;
+ isToday?: boolean;
+};
+
+type BirdTimelineGraphDomain = {
+ startTime: number;
+ endTime: number;
+};
+
+const getBirdHatchDay = (bird: Pick {birdTimelineLoading ? 'Loading...' : `${birdTimelineEvents.length} events`}