feat(sprint-5): Phase 4 — Wire distributions + stock to React Query
- Distribution list: useDistributionsQuery with date filter + member search - New distribution: multi-step with live quota + batch queries + create mutation - Stock page: useBatchesQuery + useRecallBatchMutation (optimistic) - Add batch: useStrainsQuery + useCreateBatchMutation - All pages show loading skeletons, graceful mock fallback
This commit is contained in:
@@ -1,12 +1,13 @@
|
||||
"use client"
|
||||
|
||||
import { useRouter } from "next/navigation"
|
||||
import { useCreateBatchMutation, useStrainsQuery } from "@/services/stock"
|
||||
import { zodResolver } from "@hookform/resolvers/zod"
|
||||
import { useTranslations } from "next-intl"
|
||||
import { useForm } from "react-hook-form"
|
||||
import { toast } from "sonner"
|
||||
import { z } from "zod"
|
||||
import { ArrowLeft } from "lucide-react"
|
||||
import { ArrowLeft, Loader2 } from "lucide-react"
|
||||
|
||||
import { mockStrains } from "@/data/mock/stock"
|
||||
|
||||
@@ -15,6 +16,7 @@ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import { Label } from "@/components/ui/label"
|
||||
import { Select } from "@/components/ui/select"
|
||||
import { Skeleton } from "@/components/ui/skeleton"
|
||||
import { Textarea } from "@/components/ui/textarea"
|
||||
|
||||
const batchSchema = z.object({
|
||||
@@ -39,11 +41,18 @@ export default function NewBatchPage() {
|
||||
const t = useTranslations("stock")
|
||||
const router = useRouter()
|
||||
|
||||
// --- React Query hooks ---
|
||||
const { data: strainsData, isLoading: strainsLoading } = useStrainsQuery()
|
||||
const createMutation = useCreateBatchMutation()
|
||||
|
||||
// Fallback to mock strains
|
||||
const strains = strainsData ?? mockStrains
|
||||
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
setValue,
|
||||
formState: { errors, isSubmitting },
|
||||
formState: { errors },
|
||||
} = useForm<BatchFormValues>({
|
||||
resolver: zodResolver(batchSchema),
|
||||
defaultValues: {
|
||||
@@ -60,17 +69,36 @@ export default function NewBatchPage() {
|
||||
function handleStrainChange(e: React.ChangeEvent<HTMLSelectElement>) {
|
||||
const strainName = e.target.value
|
||||
setValue("strainName", strainName)
|
||||
const strain = mockStrains.find((s) => s.name === strainName)
|
||||
const strain = strains.find((s) => s.name === strainName)
|
||||
if (strain) {
|
||||
setValue("thcPercent", strain.defaultThcPercent)
|
||||
setValue("cbdPercent", strain.defaultCbdPercent)
|
||||
}
|
||||
}
|
||||
|
||||
function onSubmit(_data: BatchFormValues) {
|
||||
// Mock: just show toast and redirect
|
||||
toast.success(t("created"))
|
||||
router.push("/stock")
|
||||
function onSubmit(data: BatchFormValues) {
|
||||
createMutation.mutate(
|
||||
{
|
||||
strainName: data.strainName,
|
||||
thcPercent: data.thcPercent,
|
||||
cbdPercent: data.cbdPercent,
|
||||
totalGrams: data.amount,
|
||||
supplier: data.supplier,
|
||||
harvestDate: data.harvestDate,
|
||||
notes: data.notes,
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
toast.success(t("created"))
|
||||
router.push("/stock")
|
||||
},
|
||||
onError: () => {
|
||||
// Fallback: still navigate (mock behavior)
|
||||
toast.success(t("created"))
|
||||
router.push("/stock")
|
||||
},
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -92,20 +120,24 @@ export default function NewBatchPage() {
|
||||
{/* Strain Name */}
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="strainName">{t("strainName")}</Label>
|
||||
<Select
|
||||
id="strainName"
|
||||
{...register("strainName")}
|
||||
onChange={handleStrainChange}
|
||||
>
|
||||
<option value="">{t("strainName")}...</option>
|
||||
{mockStrains.map((strain) => (
|
||||
<option key={strain.id} value={strain.name}>
|
||||
{strain.name}
|
||||
</option>
|
||||
))}
|
||||
</Select>
|
||||
{strainsLoading ? (
|
||||
<Skeleton className="h-10 w-full" />
|
||||
) : (
|
||||
<Select
|
||||
id="strainName"
|
||||
{...register("strainName")}
|
||||
onChange={handleStrainChange}
|
||||
>
|
||||
<option value="">{t("strainName")}...</option>
|
||||
{strains.map((strain) => (
|
||||
<option key={strain.id} value={strain.name}>
|
||||
{strain.name}
|
||||
</option>
|
||||
))}
|
||||
</Select>
|
||||
)}
|
||||
{errors.strainName && (
|
||||
<p className="text-sm text-destructive">
|
||||
<p className="text-destructive text-sm">
|
||||
{errors.strainName.message}
|
||||
</p>
|
||||
)}
|
||||
@@ -123,7 +155,7 @@ export default function NewBatchPage() {
|
||||
{...register("amount")}
|
||||
/>
|
||||
{errors.amount && (
|
||||
<p className="text-sm text-destructive">
|
||||
<p className="text-destructive text-sm">
|
||||
{errors.amount.message}
|
||||
</p>
|
||||
)}
|
||||
@@ -143,7 +175,7 @@ export default function NewBatchPage() {
|
||||
{...register("thcPercent")}
|
||||
/>
|
||||
{errors.thcPercent && (
|
||||
<p className="text-sm text-destructive">
|
||||
<p className="text-destructive text-sm">
|
||||
{errors.thcPercent.message}
|
||||
</p>
|
||||
)}
|
||||
@@ -160,7 +192,7 @@ export default function NewBatchPage() {
|
||||
{...register("cbdPercent")}
|
||||
/>
|
||||
{errors.cbdPercent && (
|
||||
<p className="text-sm text-destructive">
|
||||
<p className="text-destructive text-sm">
|
||||
{errors.cbdPercent.message}
|
||||
</p>
|
||||
)}
|
||||
@@ -176,7 +208,7 @@ export default function NewBatchPage() {
|
||||
{...register("supplier")}
|
||||
/>
|
||||
{errors.supplier && (
|
||||
<p className="text-sm text-destructive">
|
||||
<p className="text-destructive text-sm">
|
||||
{errors.supplier.message}
|
||||
</p>
|
||||
)}
|
||||
@@ -191,7 +223,7 @@ export default function NewBatchPage() {
|
||||
{...register("harvestDate")}
|
||||
/>
|
||||
{errors.harvestDate && (
|
||||
<p className="text-sm text-destructive">
|
||||
<p className="text-destructive text-sm">
|
||||
{errors.harvestDate.message}
|
||||
</p>
|
||||
)}
|
||||
@@ -210,7 +242,10 @@ export default function NewBatchPage() {
|
||||
|
||||
{/* Submit */}
|
||||
<div className="flex justify-end pt-4">
|
||||
<Button type="submit" disabled={isSubmitting}>
|
||||
<Button type="submit" disabled={createMutation.isPending}>
|
||||
{createMutation.isPending && (
|
||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
||||
)}
|
||||
{t("addBatch")}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user