feat(pump): show all available lots for merge, add expiry input
- Replace today-only lot filter with all available lots (fridge + freezer) - Show lot date (d MMM HH:mm) not just time for clarity - Add expiry input when creating new lot (hidden when merging) - Expiry unit label adapts: heures/jours/mois per storage location Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -140,8 +140,9 @@ export function EventModal({ type, babyId, onClose, onSaved, initialEvent }: Pro
|
|||||||
// Pump → stock state
|
// Pump → stock state
|
||||||
const [addToStock, setAddToStock] = useState(false);
|
const [addToStock, setAddToStock] = useState(false);
|
||||||
const [pumpStockLocation, setPumpStockLocation] = useState<"room" | "fridge" | "freezer">("fridge");
|
const [pumpStockLocation, setPumpStockLocation] = useState<"room" | "fridge" | "freezer">("fridge");
|
||||||
const [todayLots, setTodayLots] = useState<MilkStock[]>([]);
|
const [availableLots, setAvailableLots] = useState<MilkStock[]>([]);
|
||||||
const [mergeLotId, setMergeLotId] = useState<string | null>(null);
|
const [mergeLotId, setMergeLotId] = useState<string | null>(null);
|
||||||
|
const [pumpExpiryCustom, setPumpExpiryCustom] = useState("");
|
||||||
|
|
||||||
const selectedProfile = profiles.find((p) => p.id === selectedProfileId) ?? null;
|
const selectedProfile = profiles.find((p) => p.id === selectedProfileId) ?? null;
|
||||||
|
|
||||||
@@ -162,13 +163,10 @@ export function EventModal({ type, babyId, onClose, onSaved, initialEvent }: Pro
|
|||||||
.then((r) => r.json())
|
.then((r) => r.json())
|
||||||
.then((stocks) => {
|
.then((stocks) => {
|
||||||
if (!Array.isArray(stocks)) return;
|
if (!Array.isArray(stocks)) return;
|
||||||
const today = new Date().toDateString();
|
const lots = stocks
|
||||||
const lots = stocks.filter(
|
.filter((s: MilkStock & { used?: boolean }) => !s.used)
|
||||||
(s: MilkStock & { used?: boolean }) =>
|
.sort((a: MilkStock, b: MilkStock) => new Date(b.storedAt).getTime() - new Date(a.storedAt).getTime());
|
||||||
!s.used && new Date(s.storedAt).toDateString() === today
|
setAvailableLots(lots);
|
||||||
);
|
|
||||||
lots.sort((a: MilkStock, b: MilkStock) => new Date(b.storedAt).getTime() - new Date(a.storedAt).getTime());
|
|
||||||
setTodayLots(lots);
|
|
||||||
});
|
});
|
||||||
}, [type, babyId]);
|
}, [type, babyId]);
|
||||||
|
|
||||||
@@ -353,17 +351,24 @@ export function EventModal({ type, babyId, onClose, onSaved, initialEvent }: Pro
|
|||||||
const vol = parseFloat(form.pumpVolume);
|
const vol = parseFloat(form.pumpVolume);
|
||||||
if (vol > 0) {
|
if (vol > 0) {
|
||||||
if (mergeLotId) {
|
if (mergeLotId) {
|
||||||
const existing = todayLots.find((l) => l.id === mergeLotId);
|
const existing = availableLots.find((l) => l.id === mergeLotId);
|
||||||
await fetch(`/api/milk/${mergeLotId}`, {
|
await fetch(`/api/milk/${mergeLotId}`, {
|
||||||
method: "PATCH",
|
method: "PATCH",
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
body: JSON.stringify({ volume: (existing?.volume ?? 0) + vol }),
|
body: JSON.stringify({ volume: (existing?.volume ?? 0) + vol }),
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
let expiryHours: number | undefined;
|
||||||
|
if (pumpExpiryCustom) {
|
||||||
|
const v = parseFloat(pumpExpiryCustom);
|
||||||
|
expiryHours = pumpStockLocation === "freezer" ? v * 24 * 30
|
||||||
|
: pumpStockLocation === "fridge" ? v * 24
|
||||||
|
: v;
|
||||||
|
}
|
||||||
await fetch("/api/milk", {
|
await fetch("/api/milk", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
body: JSON.stringify({ babyId, volume: vol, location: pumpStockLocation }),
|
body: JSON.stringify({ babyId, volume: vol, location: pumpStockLocation, expiryHours }),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -536,27 +541,38 @@ export function EventModal({ type, babyId, onClose, onSaved, initialEvent }: Pro
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{todayLots.length > 0 && (
|
<div>
|
||||||
<div>
|
<label className="block text-xs font-medium text-slate-600 dark:text-slate-300 mb-1.5">Fusionner avec lot existant</label>
|
||||||
<label className="block text-xs font-medium text-slate-600 dark:text-slate-300 mb-1.5">Fusionner avec lot du jour</label>
|
<div className="flex flex-col gap-1.5 max-h-36 overflow-y-auto">
|
||||||
<div className="flex flex-col gap-1.5">
|
<button type="button" onClick={() => setMergeLotId(null)}
|
||||||
<button type="button" onClick={() => setMergeLotId(null)}
|
className={`flex items-center gap-2 px-3 py-2 rounded-lg border text-xs transition ${mergeLotId === null ? "border-indigo-500 bg-indigo-50 dark:bg-indigo-950 text-indigo-700 dark:text-indigo-300" : "border-slate-200 dark:border-slate-600 text-slate-500 dark:text-slate-400"}`}>
|
||||||
className={`flex items-center gap-2 px-3 py-2 rounded-lg border text-xs transition ${mergeLotId === null ? "border-indigo-500 bg-indigo-50 dark:bg-indigo-950 text-indigo-700 dark:text-indigo-300" : "border-slate-200 dark:border-slate-600 text-slate-500 dark:text-slate-400"}`}>
|
Nouveau lot
|
||||||
Nouveau lot
|
</button>
|
||||||
|
{availableLots.map((lot) => (
|
||||||
|
<button key={lot.id} type="button" onClick={() => setMergeLotId(lot.id)}
|
||||||
|
className={`flex items-center gap-2 px-3 py-2 rounded-lg border text-xs transition ${mergeLotId === lot.id ? "border-indigo-500 bg-indigo-50 dark:bg-indigo-950 text-indigo-700 dark:text-indigo-300" : "border-slate-200 dark:border-slate-600 text-slate-500 dark:text-slate-400"}`}>
|
||||||
|
{lot.volume} mL · {lot.location === "fridge" ? "frigo" : lot.location === "freezer" ? "congélo" : "ambiant"}
|
||||||
|
{" · "}{format(new Date(lot.storedAt), "d MMM HH:mm", { locale: fr })}
|
||||||
</button>
|
</button>
|
||||||
{todayLots.map((lot) => (
|
))}
|
||||||
<button key={lot.id} type="button" onClick={() => setMergeLotId(lot.id)}
|
</div>
|
||||||
className={`flex items-center gap-2 px-3 py-2 rounded-lg border text-xs transition ${mergeLotId === lot.id ? "border-indigo-500 bg-indigo-50 dark:bg-indigo-950 text-indigo-700 dark:text-indigo-300" : "border-slate-200 dark:border-slate-600 text-slate-500 dark:text-slate-400"}`}>
|
{mergeLotId && (
|
||||||
{lot.volume} mL · {lot.location === "fridge" ? "frigo" : lot.location === "freezer" ? "congélo" : "ambiant"}
|
<p className="text-[11px] text-indigo-500 mt-1.5">Volume ajouté. Péremption inchangée.</p>
|
||||||
{" · "}tirage {format(new Date(lot.storedAt), "HH:mm", { locale: fr })}
|
)}
|
||||||
</button>
|
</div>
|
||||||
))}
|
{!mergeLotId && (
|
||||||
</div>
|
<div>
|
||||||
{mergeLotId && (
|
<label className="block text-xs font-medium text-slate-600 dark:text-slate-300 mb-1.5">
|
||||||
<p className="text-[11px] text-indigo-500 mt-1.5">
|
Péremption ({pumpStockLocation === "freezer" ? "mois" : pumpStockLocation === "fridge" ? "jours" : "heures"})
|
||||||
Volume sera ajouté à ce lot. Péremption inchangée.
|
</label>
|
||||||
</p>
|
<input
|
||||||
)}
|
type="number"
|
||||||
|
value={pumpExpiryCustom}
|
||||||
|
onChange={(e) => setPumpExpiryCustom(e.target.value)}
|
||||||
|
className={inputCls}
|
||||||
|
placeholder={pumpStockLocation === "freezer" ? "6" : pumpStockLocation === "fridge" ? "4" : "4"}
|
||||||
|
min="1"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user