Dev #23

Merged
DeepnaPooja merged 65 commits from dev into production 2026-09-14 11:59:21 +05:30
Showing only changes of commit 3bcb42ea9b - Show all commits
@@ -0,0 +1,166 @@
'use client'
import { useEffect, useState } from 'react'
import { useForm, zodResolver, z } from '@/lib/form'
import { FormField } from '@/components/shared/form-field'
import { Input } from '@/components/ui/input'
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/components/ui/select'
import { Button } from '@/components/ui/button'
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogDescription,
DialogFooter,
} from '@/components/ui/dialog'
import { FieldGroup } from '@/components/ui/field'
import { toast } from '@/components/ui/toast'
import { stock as stockApi, users as usersApi, getApiError, getPaginatedItems } from '@/lib/api/index'
import type { StockBalanceResponse, UserResponse } from '@/types/api'
const schema = z.object({
quantity: z.number().refine((v) => v !== 0, 'Adjustment quantity cannot be 0'),
reasonCode: z.string().min(1, 'Reason is required'),
adjustedByUserId: z.string().min(1, 'Adjusted by is required'),
})
type FormValues = z.infer<typeof schema>
interface StockAdjustDialogProps {
open: boolean
onClose: () => void
onSuccess: () => void
balance: StockBalanceResponse | null
}
export function StockAdjustDialog({ open, onClose, onSuccess, balance }: StockAdjustDialogProps) {
const [usersList, setUsersList] = useState<UserResponse[]>([])
const form = useForm<FormValues>({
resolver: zodResolver(schema),
defaultValues: { quantity: 0, reasonCode: '', adjustedByUserId: '' },
})
useEffect(() => {
if (!open) return
form.reset({ quantity: 0, reasonCode: '', adjustedByUserId: '' })
usersApi.list({ pageSize: 200 })
.then((res) => setUsersList(getPaginatedItems(res)))
.catch((err) => toast.add({ title: 'Failed to load users', description: getApiError(err), type: 'error' }))
}, [open])
if (!balance) return null
async function onSubmit(values: FormValues) {
try {
await stockApi.adjust({
itemId: balance!.itemId,
locationId: balance!.locationId,
locationType: balance!.locationType,
quantity: values.quantity,
reasonCode: values.reasonCode,
adjustedByUserId: values.adjustedByUserId,
})
toast.add({ title: 'Stock adjusted', type: 'success' })
onSuccess()
onClose()
} catch (err) {
toast.add({ title: 'Failed to adjust stock', description: getApiError(err), type: 'error' })
}
}
return (
<Dialog open={open} onOpenChange={(v) => !v && onClose()}>
<DialogContent className="overflow-y-auto sm:max-w-lg">
<DialogHeader>
<DialogTitle>Adjust Stock — {balance.itemName}</DialogTitle>
<DialogDescription>
Enter a positive quantity to increase, or a negative quantity to decrease, the balance
at this location. Current quantity: {balance.quantity}.
</DialogDescription>
</DialogHeader>
<form onSubmit={form.handleSubmit(onSubmit)} className="flex flex-col gap-6">
<FieldGroup>
<FormField
control={form.control}
name="quantity"
label="Adjustment Quantity"
required
render={({ field, invalid }) => (
<Input
id="quantity"
name={field.name}
ref={field.ref}
type="number"
step="0.01"
value={Number(field.value as number)}
onChange={(e) => field.onChange(Number(e.target.value))}
onBlur={field.onBlur}
placeholder="e.g. 10 or -5"
aria-invalid={invalid}
/>
)}
/>
<FormField
control={form.control}
name="reasonCode"
label="Reason"
required
render={({ field, invalid }) => (
<Input
id="reasonCode"
name={field.name}
ref={field.ref}
value={field.value as string}
onChange={field.onChange}
onBlur={field.onBlur}
placeholder="e.g. Physical count correction"
aria-invalid={invalid}
/>
)}
/>
<FormField
control={form.control}
name="adjustedByUserId"
label="Adjusted By"
description="Temporary — required until auth resolves the logged-in user automatically."
required
render={({ field }) => (
<Select value={field.value as string} onValueChange={field.onChange}>
<SelectTrigger>
<SelectValue placeholder="Select user..." />
</SelectTrigger>
<SelectContent>
<SelectItem value="" disabled>Select user...</SelectItem>
{usersList.map((u) => (
<SelectItem key={u.id} value={u.id}>{u.fullName}</SelectItem>
))}
</SelectContent>
</Select>
)}
/>
</FieldGroup>
<DialogFooter>
<Button type="button" variant="outline" onClick={onClose}>
Cancel
</Button>
<Button type="submit" disabled={form.formState.isSubmitting}>
{form.formState.isSubmitting ? 'Saving…' : 'Apply Adjustment'}
</Button>
</DialogFooter>
</form>
</DialogContent>
</Dialog>
)
}