mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-12 22:26:13 +00:00
Coach chat bubbles and the document OCR text panel wrapped long unbroken tokens (URLs, IDs) past their column width on narrow viewports — add break-words. Drop the /90 alpha on the coach error text so it stays on the AA-retuned status token. Today hero's loading skeleton had drifted from the v1.29.1 tightened hero (gap/items-start/space-y/rail-heading/xl:grid-cols-3), causing a visible shift on every dashboard load; mirror the loaded classes 1:1. The wellness-scores strip returned null while its shared batch loaded, so it popped in late and pushed the page below it down — render the section heading + tile-shaped skeletons instead, matching the vitals dashboard's loading pattern. Priority-card actions (coach check-in keep/let-go) had no pending state, so a slow round trip could be double-tapped into firing the mutation twice; disable the action row while in flight. Priority-item dismiss is now optimistic (removes the card from the rail on tap, rolls back on error) instead of waiting on invalidate-after-success.
55 lines
1.9 KiB
TypeScript
55 lines
1.9 KiB
TypeScript
"use client";
|
|
|
|
/**
|
|
* Today rail — "dismiss / mark seen" for the OBSERVATIONAL `PriorityItem`
|
|
* kinds (`milestone`, `ecg_new_recording`, `tension_window`). Posts the
|
|
* item's own `itemKey` to `POST /api/daily/digest/dismiss` (server persists
|
|
* it so the dismissal survives reload / a second device). Keys route
|
|
* through the centralised factory — no bare arrays.
|
|
*
|
|
* The dismiss is optimistic (`onMutate` strikes the item from the cached
|
|
* digest immediately): a second tap on the same card has nothing left to
|
|
* hit, since the card itself leaves the rail before the round trip lands.
|
|
* `onError` rolls the cache back to the pre-tap snapshot; `onSettled`
|
|
* re-syncs from the server either way.
|
|
*/
|
|
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
|
|
|
import { apiPost } from "@/lib/api/api-fetch";
|
|
import { queryKeys } from "@/lib/query-keys";
|
|
import type { DailyDigest } from "@/lib/daily/digest";
|
|
|
|
export function usePriorityItemDismiss() {
|
|
const queryClient = useQueryClient();
|
|
|
|
return useMutation({
|
|
mutationFn: async (itemKey: string) => {
|
|
await apiPost("/api/daily/digest/dismiss", { itemKey });
|
|
return itemKey;
|
|
},
|
|
onMutate: async (itemKey) => {
|
|
await queryClient.cancelQueries({ queryKey: queryKeys.dailyDigest() });
|
|
const previous = queryClient.getQueryData<DailyDigest>(
|
|
queryKeys.dailyDigest(),
|
|
);
|
|
if (previous) {
|
|
queryClient.setQueryData<DailyDigest>(queryKeys.dailyDigest(), {
|
|
...previous,
|
|
worthALook: previous.worthALook.filter(
|
|
(item) => item.itemKey !== itemKey,
|
|
),
|
|
});
|
|
}
|
|
return { previous };
|
|
},
|
|
onError: (_err, _itemKey, context) => {
|
|
if (context?.previous) {
|
|
queryClient.setQueryData(queryKeys.dailyDigest(), context.previous);
|
|
}
|
|
},
|
|
onSettled: () => {
|
|
void queryClient.invalidateQueries({ queryKey: queryKeys.dailyDigest() });
|
|
},
|
|
});
|
|
}
|