diff --git a/backend/package-lock.json b/backend/package-lock.json index bd3e76c..f6bcd9b 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -1,12 +1,12 @@ { "name": "medassist-ng-backend", - "version": "0.1.0", + "version": "1.0.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "medassist-ng-backend", - "version": "0.1.0", + "version": "1.0.1", "dependencies": { "@fastify/cookie": "^10.0.1", "@fastify/cors": "^10.0.1", diff --git a/backend/src/db/schema.ts b/backend/src/db/schema.ts index 463c3e1..aae7fe8 100644 --- a/backend/src/db/schema.ts +++ b/backend/src/db/schema.ts @@ -109,6 +109,6 @@ export const doseTracking = sqliteTable("dose_tracking", { id: integer("id").primaryKey({ autoIncrement: true }), userId: integer("user_id").notNull().references(() => users.id, { onDelete: "cascade" }), doseId: text("dose_id", { length: 255 }).notNull(), // e.g. "med-5-1-86400000-1735200000000" - takenAt: integer("taken_at", { mode: "timestamp" }).notNull().default(sql`CURRENT_TIMESTAMP`), + takenAt: integer("taken_at", { mode: "timestamp" }).notNull().default(sql`(strftime('%s','now'))`), markedBy: text("marked_by", { length: 100 }), // null = user, "Daniel" = via share link }); diff --git a/frontend/package-lock.json b/frontend/package-lock.json index c9a367f..36149a3 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1,12 +1,12 @@ { "name": "medassist-ng-frontend", - "version": "0.1.0", + "version": "1.0.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "medassist-ng-frontend", - "version": "0.1.0", + "version": "1.0.1", "dependencies": { "i18next": "^24.2.2", "i18next-browser-languagedetector": "^8.0.4", diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 6eadfb0..35672cf 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -3625,31 +3625,32 @@ function SharedSchedule() { fetchData(); }, [token, t]); - // Build schedule from medications + // Build schedule from medications - matches buildSchedulePreview logic exactly const schedule = useMemo(() => { if (!data) return []; - const todayStart = new Date(); - todayStart.setHours(0, 0, 0, 0); + // Use same logic as buildSchedulePreview in main app + const now = new Date(); + const todayStart = new Date(now.getFullYear(), now.getMonth(), now.getDate()); // Midnight today const todayStartTime = todayStart.getTime(); - // Calculate end time: today midnight + scheduleDays days - const endDate = new Date(todayStart); - endDate.setDate(endDate.getDate() + data.scheduleDays); - const endTime = endDate.getTime(); + // Use 180 days horizon like main app (scheduleDays only limits futureDays display) + const end = new Date(); + end.setDate(end.getDate() + 180); + const endTime = end.getTime(); - const doses: { id: string; when: number; medName: string; usage: number; timeStr: string; isPast: boolean; takenBy: string[] }[] = []; + const doses: { id: string; when: number; medName: string; usage: number; timeStr: string; isPast: boolean; takenBy: string[]; dateStr: string }[] = []; for (const med of data.medications) { med.blisters.forEach((blister, blisterIdx) => { const startDate = new Date(blister.start); - const intervalMs = blister.every * 24 * 60 * 60 * 1000; - let t = startDate.getTime(); - - // Start from the very first dose (blister start) - while (t <= endTime) { - const d = new Date(t); - const isPast = t < todayStartTime; + if (Number.isNaN(startDate.getTime())) return; + + // Use the same iteration method as buildSchedulePreview (setDate instead of adding ms) + // This ensures identical timestamps even across DST changes + for (let d = new Date(startDate); d <= end; d.setDate(d.getDate() + blister.every)) { + const t = d.getTime(); + const isPast = d < todayStart; // Generate dose ID matching Dashboard format: ${med.id}-${blisterIdx}-${whenMs} const doseId = `${med.id}-${blisterIdx}-${t}`; doses.push({ @@ -3660,48 +3661,40 @@ function SharedSchedule() { isPast, takenBy: med.takenBy || [], timeStr: d.toLocaleTimeString(i18n.language, { hour: "2-digit", minute: "2-digit" }), + dateStr: d.toLocaleDateString(i18n.language, { weekday: "short", day: "2-digit", month: "short" }), }); - t += intervalMs; } }); } doses.sort((a, b) => a.when - b.when); - // Group by date - const grouped: { dateStr: string; date: Date; isPast: boolean; meds: { medName: string; total: number; lastWhen: number; doses: typeof doses }[] }[] = []; - const byDate = new Map(); - - for (const dose of doses) { - const dateKey = new Date(dose.when).toLocaleDateString(i18n.language, { - weekday: "long", - day: "2-digit", - month: "short", - }); - if (!byDate.has(dateKey)) byDate.set(dateKey, []); - byDate.get(dateKey)!.push(dose); + // Group by date - matches groupedSchedule logic in main app + type DoseInfo = typeof doses[number]; + const days = new Map }>(); + + for (const dose of doses.slice(0, 2000)) { + const day = days.get(dose.dateStr) ?? { dateStr: dose.dateStr, date: new Date(dose.when), isPast: dose.isPast, meds: new Map() }; + const medEntry = day.meds.get(dose.medName) ?? { medName: dose.medName, total: 0, doses: [], lastWhen: dose.when }; + medEntry.total += dose.usage; + medEntry.doses.push(dose); + medEntry.lastWhen = Math.max(medEntry.lastWhen, dose.when); + day.meds.set(dose.medName, medEntry); + days.set(dose.dateStr, day); } - - for (const [dateStr, dayDoses] of byDate) { - const byMed = new Map(); - for (const dose of dayDoses) { - if (!byMed.has(dose.medName)) byMed.set(dose.medName, []); - byMed.get(dose.medName)!.push(dose); - } - const meds = Array.from(byMed.entries()).map(([medName, medDoses]) => ({ - medName, - total: medDoses.reduce((sum, d) => sum + d.usage, 0), - lastWhen: Math.max(...medDoses.map(d => d.when)), - doses: medDoses, - })); - grouped.push({ dateStr, date: new Date(dayDoses[0].when), isPast: dayDoses[0].isPast, meds }); - } - - return grouped; + + return Array.from(days.values()).map((d) => ({ + dateStr: d.dateStr, + date: d.date, + isPast: d.isPast, + meds: Array.from(d.meds.values()) + })); }, [data, i18n.language]); + // Split into past and future - matches main app logic const pastDays = useMemo(() => schedule.filter(d => d.isPast), [schedule]); - const futureDays = useMemo(() => schedule.filter(d => !d.isPast), [schedule]); + // Limit future days by scheduleDays setting (same as main app) + const futureDays = useMemo(() => schedule.filter(d => !d.isPast).slice(0, data?.scheduleDays ?? 30), [schedule, data?.scheduleDays]); // Calculate coverage for stock status colors (matches main app logic) // This needs to account for taken doses and calculate depletion time diff --git a/scripts/release.sh b/scripts/release.sh index 99eae9f..0d03f1b 100755 --- a/scripts/release.sh +++ b/scripts/release.sh @@ -95,8 +95,8 @@ if git rev-parse "v${NEW_VERSION}" >/dev/null 2>&1; then fi # Create and push tag -echo -e "${BLUE}Creating tag v${NEW_VERSION}...${NC}" -git tag -a "v${NEW_VERSION}" -m "Release v${NEW_VERSION}" +echo -e "${BLUE}Creating signed tag v${NEW_VERSION}...${NC}" +git tag -s "v${NEW_VERSION}" -m "Release v${NEW_VERSION}" # Push echo -e "${BLUE}Pushing to origin (GitHub)...${NC}"