- Withdrawal wording from one backend helper everywhere ("bis
einschließlich TT.MM. – ab TT.MM. wieder erntefähig", year when it
crosses New Year); withdrawal-status returns lastBlockedDay and the
ready text, the frontend no longer computes dates
- Calendar events name colony or apiary, withdrawal events carry
product and detail; compact withdrawal rows on mobile
- One bottling warning per colony and treatment; pre-save hint
- Offline: online-only actions disabled with a reason, cached data
shows its timestamp, open sheets block submit offline
- Mobile: no horizontal scroll at 360 px, touch targets >= 44 px,
toasts and the add button no longer cover content
- Cleanup sheet without destructive preselection; natural sort of
colony codes; collapsible automation rule cards; varroa list newest
first with chart axes; text fixes
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
103 lines
3.5 KiB
TypeScript
103 lines
3.5 KiB
TypeScript
"use client";
|
|
import { compareColonyCodes } from "@/lib/colonies/sort";
|
|
|
|
import { useQuery } from "@tanstack/react-query";
|
|
import { fetchApiaries } from "@/lib/apiaries/api";
|
|
import { livingColoniesFromPacks } from "@/lib/apiaries/offline-pack";
|
|
import { useCurrentUserId } from "@/lib/auth/auth-context";
|
|
import { fetchColonies } from "@/lib/colonies/api";
|
|
import { isLivingColony } from "@/lib/colonies/status";
|
|
import type { Colony } from "@/lib/colonies/types";
|
|
import { withPackFallback } from "@/lib/offline/pack-fallback";
|
|
|
|
/** What a bulk choice needs of a colony - a server colony or one from an offline pack. */
|
|
export type BulkColony = Pick<Colony, "id" | "code" | "status" | "apiaryId">;
|
|
export interface BulkApiary {
|
|
id: string;
|
|
name: string;
|
|
}
|
|
|
|
/** Where the choices came from: the server, or - only without network - the loaded packs. */
|
|
export type ChoiceSource = "server" | "packs";
|
|
|
|
interface Choices<T> {
|
|
items: T[];
|
|
source: ChoiceSource;
|
|
}
|
|
|
|
/** Prefix of the queries here; they read the server or this device's storage, never both as one. */
|
|
const BULK_CHOICES_KEY = ["bulk-choices"] as const;
|
|
|
|
function byCode(a: BulkColony, b: BulkColony): number {
|
|
return compareColonyCodes(a.code, b.code);
|
|
}
|
|
|
|
function livingOf(colonies: readonly BulkColony[]): BulkColony[] {
|
|
return colonies
|
|
.filter(isLivingColony)
|
|
.map(({ id, code, status, apiaryId }) => ({ id, code, status, apiaryId }))
|
|
.sort(byCode);
|
|
}
|
|
|
|
/**
|
|
* The apiaries to choose from: the server's active ones, or - only when the
|
|
* network failed - those of the user's offline packs. Names only: nothing
|
|
* else of an apiary (location!) is needed or kept.
|
|
*/
|
|
export function useApiaryChoices() {
|
|
const userId = useCurrentUserId();
|
|
return useQuery({
|
|
queryKey: [...BULK_CHOICES_KEY, userId, "apiaries"],
|
|
queryFn: (): Promise<Choices<BulkApiary>> =>
|
|
withPackFallback<Choices<BulkApiary>>(
|
|
async () => ({
|
|
source: "server",
|
|
items: (await fetchApiaries({ status: "ACTIVE" })).map(({ id, name }) => ({ id, name })),
|
|
}),
|
|
(packs) =>
|
|
packs.length === 0
|
|
? null
|
|
: {
|
|
source: "packs",
|
|
items: packs
|
|
.map(({ pack }) => ({ id: pack.apiary.id, name: pack.apiary.name }))
|
|
.sort((a, b) => a.name.localeCompare(b.name, "de")),
|
|
},
|
|
userId ?? "",
|
|
),
|
|
// Reads this device's storage when offline, never waits for the network.
|
|
networkMode: "always",
|
|
});
|
|
}
|
|
|
|
/**
|
|
* The living colonies of one apiary - ended ones never appear. Without
|
|
* network the apiary's offline pack answers; with no pack for it the
|
|
* request fails and the caller says so.
|
|
*/
|
|
export function useApiaryColonies(apiaryId: string | null) {
|
|
const userId = useCurrentUserId();
|
|
return useQuery({
|
|
queryKey: [...BULK_CHOICES_KEY, userId, "colonies", apiaryId],
|
|
queryFn: (): Promise<Choices<BulkColony>> =>
|
|
withPackFallback<Choices<BulkColony>>(
|
|
async () => ({
|
|
source: "server",
|
|
items: livingOf(await fetchColonies({ apiaryId: apiaryId ?? undefined })),
|
|
}),
|
|
(packs) =>
|
|
packs.some((stored) => stored.apiaryId === apiaryId)
|
|
? {
|
|
source: "packs",
|
|
items: livingOf(
|
|
livingColoniesFromPacks(packs).filter((colony) => colony.apiaryId === apiaryId),
|
|
),
|
|
}
|
|
: null,
|
|
userId ?? "",
|
|
),
|
|
enabled: apiaryId !== null && apiaryId !== "",
|
|
networkMode: "always",
|
|
});
|
|
}
|