summaryrefslogtreecommitdiff
path: root/lib/vendor-rfq-response/vendor-cbe-table/respond-cbe-sheet.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'lib/vendor-rfq-response/vendor-cbe-table/respond-cbe-sheet.tsx')
-rw-r--r--lib/vendor-rfq-response/vendor-cbe-table/respond-cbe-sheet.tsx427
1 files changed, 427 insertions, 0 deletions
diff --git a/lib/vendor-rfq-response/vendor-cbe-table/respond-cbe-sheet.tsx b/lib/vendor-rfq-response/vendor-cbe-table/respond-cbe-sheet.tsx
new file mode 100644
index 00000000..8cc4fa6f
--- /dev/null
+++ b/lib/vendor-rfq-response/vendor-cbe-table/respond-cbe-sheet.tsx
@@ -0,0 +1,427 @@
+"use client"
+
+import * as React from "react"
+import { zodResolver } from "@hookform/resolvers/zod"
+import { Loader } from "lucide-react"
+import { useForm } from "react-hook-form"
+import { toast } from "sonner"
+import { z } from "zod"
+
+import { Button } from "@/components/ui/button"
+import {
+ Form,
+ FormControl,
+ FormField,
+ FormItem,
+ FormLabel,
+ FormMessage,
+} from "@/components/ui/form"
+import {
+ Select,
+ SelectContent,
+ SelectGroup,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from "@/components/ui/select"
+import { Input } from "@/components/ui/input"
+import {
+ Sheet,
+ SheetClose,
+ SheetContent,
+ SheetDescription,
+ SheetFooter,
+ SheetHeader,
+ SheetTitle,
+} from "@/components/ui/sheet"
+import { Textarea } from "@/components/ui/textarea"
+import { VendorWithCbeFields } from "@/config/vendorCbeColumnsConfig"
+import { getCommercialResponseByResponseId, updateCommercialResponse } from "../service"
+
+// Define schema for form validation (client-side)
+const commercialResponseFormSchema = z.object({
+ responseStatus: z.enum(["PENDING", "IN_PROGRESS", "SUBMITTED", "REJECTED", "ACCEPTED"]),
+ totalPrice: z.coerce.number().optional(),
+ currency: z.string().default("USD"),
+ paymentTerms: z.string().optional(),
+ incoterms: z.string().optional(),
+ deliveryPeriod: z.string().optional(),
+ warrantyPeriod: z.string().optional(),
+ validityPeriod: z.string().optional(),
+ priceBreakdown: z.string().optional(),
+ commercialNotes: z.string().optional(),
+})
+
+type CommercialResponseFormInput = z.infer<typeof commercialResponseFormSchema>
+
+interface CommercialResponseSheetProps
+ extends React.ComponentPropsWithRef<typeof Sheet> {
+ rfq: VendorWithCbeFields | null
+ responseId: number | null // This is the vendor_responses.id
+ onSuccess?: () => void
+}
+
+export function CommercialResponseSheet({
+ rfq,
+ responseId,
+ onSuccess,
+ ...props
+}: CommercialResponseSheetProps) {
+ const [isSubmitting, startSubmitTransition] = React.useTransition()
+ const [isLoading, setIsLoading] = React.useState(true)
+
+ const form = useForm<CommercialResponseFormInput>({
+ resolver: zodResolver(commercialResponseFormSchema),
+ defaultValues: {
+ responseStatus: "PENDING",
+ totalPrice: undefined,
+ currency: "USD",
+ paymentTerms: "",
+ incoterms: "",
+ deliveryPeriod: "",
+ warrantyPeriod: "",
+ validityPeriod: "",
+ priceBreakdown: "",
+ commercialNotes: "",
+ },
+ })
+
+ // Load existing commercial response data when sheet opens
+ React.useEffect(() => {
+ async function loadCommercialResponse() {
+ if (!responseId) return
+
+ setIsLoading(true)
+ try {
+ // Use the helper function to get existing data
+ const existingResponse = await getCommercialResponseByResponseId(responseId)
+
+ if (existingResponse) {
+ // If we found existing data, populate the form
+ form.reset({
+ responseStatus: existingResponse.responseStatus,
+ totalPrice: existingResponse.totalPrice,
+ currency: existingResponse.currency || "USD",
+ paymentTerms: existingResponse.paymentTerms || "",
+ incoterms: existingResponse.incoterms || "",
+ deliveryPeriod: existingResponse.deliveryPeriod || "",
+ warrantyPeriod: existingResponse.warrantyPeriod || "",
+ validityPeriod: existingResponse.validityPeriod || "",
+ priceBreakdown: existingResponse.priceBreakdown || "",
+ commercialNotes: existingResponse.commercialNotes || "",
+ })
+ } else if (rfq) {
+ // If no existing data but we have rfq data with some values already
+ form.reset({
+ responseStatus: rfq.commercialResponseStatus as any || "PENDING",
+ totalPrice: rfq.totalPrice || undefined,
+ currency: rfq.currency || "USD",
+ paymentTerms: rfq.paymentTerms || "",
+ incoterms: rfq.incoterms || "",
+ deliveryPeriod: rfq.deliveryPeriod || "",
+ warrantyPeriod: rfq.warrantyPeriod || "",
+ validityPeriod: rfq.validityPeriod || "",
+ priceBreakdown: "",
+ commercialNotes: "",
+ })
+ }
+ } catch (error) {
+ console.error("Failed to load commercial response data:", error)
+ toast.error("상업 응답 데이터를 불러오는데 실패했습니다")
+ } finally {
+ setIsLoading(false)
+ }
+ }
+
+ loadCommercialResponse()
+ }, [responseId, rfq, form])
+
+ function onSubmit(formData: CommercialResponseFormInput) {
+ if (!responseId) {
+ toast.error("응답 ID를 찾을 수 없습니다")
+ return
+ }
+
+ if (!rfq?.vendorId) {
+ toast.error("협력업체 ID를 찾을 수 없습니다")
+ return
+ }
+
+ startSubmitTransition(async () => {
+ try {
+ // Pass both responseId and vendorId to the server action
+ const result = await updateCommercialResponse({
+ responseId,
+ vendorId: rfq.vendorId, // Include vendorId for revalidateTag
+ ...formData,
+ })
+
+ if (!result.success) {
+ toast.error(result.error || "응답 제출 중 오류가 발생했습니다")
+ return
+ }
+
+ toast.success("Commercial response successfully submitted")
+ props.onOpenChange?.(false)
+
+ if (onSuccess) {
+ onSuccess()
+ }
+ } catch (error) {
+ console.error("Error submitting response:", error)
+ toast.error("응답 제출 중 오류가 발생했습니다")
+ }
+ })
+ }
+
+ return (
+ <Sheet {...props}>
+ <SheetContent className="flex flex-col gap-6 sm:max-w-md">
+ <SheetHeader className="text-left">
+ <SheetTitle>Commercial Response</SheetTitle>
+ <SheetDescription>
+ {rfq?.rfqCode && <span className="font-medium">{rfq.rfqCode}</span>}
+ <div className="mt-1">Please provide your commercial response for this RFQ</div>
+ </SheetDescription>
+ </SheetHeader>
+
+ {isLoading ? (
+ <div className="flex items-center justify-center py-8">
+ <Loader className="h-8 w-8 animate-spin text-muted-foreground" />
+ </div>
+ ) : (
+ <Form {...form}>
+ <form
+ onSubmit={form.handleSubmit(onSubmit)}
+ className="flex flex-col gap-4 overflow-y-auto max-h-[calc(100vh-200px)] pr-2"
+ >
+ <FormField
+ control={form.control}
+ name="responseStatus"
+ render={({ field }) => (
+ <FormItem>
+ <FormLabel>Response Status</FormLabel>
+ <Select
+ onValueChange={field.onChange}
+ defaultValue={field.value}
+ >
+ <FormControl>
+ <SelectTrigger className="capitalize">
+ <SelectValue placeholder="Select response status" />
+ </SelectTrigger>
+ </FormControl>
+ <SelectContent>
+ <SelectGroup>
+ <SelectItem value="PENDING">Pending</SelectItem>
+ <SelectItem value="IN_PROGRESS">In Progress</SelectItem>
+ <SelectItem value="SUBMITTED">Submitted</SelectItem>
+ </SelectGroup>
+ </SelectContent>
+ </Select>
+ <FormMessage />
+ </FormItem>
+ )}
+ />
+
+ <div className="grid grid-cols-2 gap-4">
+ <FormField
+ control={form.control}
+ name="totalPrice"
+ render={({ field }) => (
+ <FormItem>
+ <FormLabel>Total Price</FormLabel>
+ <FormControl>
+ <Input
+ type="number"
+ placeholder="0.00"
+ {...field}
+ value={field.value || ''}
+ onChange={(e) => {
+ const value = e.target.value === '' ? undefined : parseFloat(e.target.value);
+ field.onChange(value);
+ }}
+ />
+ </FormControl>
+ <FormMessage />
+ </FormItem>
+ )}
+ />
+
+ <FormField
+ control={form.control}
+ name="currency"
+ render={({ field }) => (
+ <FormItem>
+ <FormLabel>Currency</FormLabel>
+ <Select
+ onValueChange={field.onChange}
+ defaultValue={field.value}
+ >
+ <FormControl>
+ <SelectTrigger>
+ <SelectValue placeholder="Select currency" />
+ </SelectTrigger>
+ </FormControl>
+ <SelectContent>
+ <SelectGroup>
+ <SelectItem value="USD">USD</SelectItem>
+ <SelectItem value="EUR">EUR</SelectItem>
+ <SelectItem value="GBP">GBP</SelectItem>
+ <SelectItem value="KRW">KRW</SelectItem>
+ <SelectItem value="JPY">JPY</SelectItem>
+ </SelectGroup>
+ </SelectContent>
+ </Select>
+ <FormMessage />
+ </FormItem>
+ )}
+ />
+ </div>
+
+ {/* Other form fields remain the same */}
+ <FormField
+ control={form.control}
+ name="paymentTerms"
+ render={({ field }) => (
+ <FormItem>
+ <FormLabel>Payment Terms</FormLabel>
+ <FormControl>
+ <Input placeholder="e.g. Net 30" {...field} />
+ </FormControl>
+ <FormMessage />
+ </FormItem>
+ )}
+ />
+
+ <FormField
+ control={form.control}
+ name="incoterms"
+ render={({ field }) => (
+ <FormItem>
+ <FormLabel>Incoterms</FormLabel>
+ <Select
+ onValueChange={field.onChange}
+ defaultValue={field.value || ''}
+ >
+ <FormControl>
+ <SelectTrigger>
+ <SelectValue placeholder="Select incoterms" />
+ </SelectTrigger>
+ </FormControl>
+ <SelectContent>
+ <SelectGroup>
+ <SelectItem value="EXW">EXW (Ex Works)</SelectItem>
+ <SelectItem value="FCA">FCA (Free Carrier)</SelectItem>
+ <SelectItem value="FOB">FOB (Free On Board)</SelectItem>
+ <SelectItem value="CIF">CIF (Cost, Insurance & Freight)</SelectItem>
+ <SelectItem value="DAP">DAP (Delivered At Place)</SelectItem>
+ <SelectItem value="DDP">DDP (Delivered Duty Paid)</SelectItem>
+ </SelectGroup>
+ </SelectContent>
+ </Select>
+ <FormMessage />
+ </FormItem>
+ )}
+ />
+
+ <FormField
+ control={form.control}
+ name="deliveryPeriod"
+ render={({ field }) => (
+ <FormItem>
+ <FormLabel>Delivery Period</FormLabel>
+ <FormControl>
+ <Input placeholder="e.g. 4-6 weeks" {...field} />
+ </FormControl>
+ <FormMessage />
+ </FormItem>
+ )}
+ />
+
+ <FormField
+ control={form.control}
+ name="warrantyPeriod"
+ render={({ field }) => (
+ <FormItem>
+ <FormLabel>Warranty Period</FormLabel>
+ <FormControl>
+ <Input placeholder="e.g. 12 months" {...field} />
+ </FormControl>
+ <FormMessage />
+ </FormItem>
+ )}
+ />
+
+ <FormField
+ control={form.control}
+ name="validityPeriod"
+ render={({ field }) => (
+ <FormItem>
+ <FormLabel>Validity Period</FormLabel>
+ <FormControl>
+ <Input placeholder="e.g. 30 days" {...field} />
+ </FormControl>
+ <FormMessage />
+ </FormItem>
+ )}
+ />
+
+ <FormField
+ control={form.control}
+ name="priceBreakdown"
+ render={({ field }) => (
+ <FormItem>
+ <FormLabel>Price Breakdown (Optional)</FormLabel>
+ <FormControl>
+ <Textarea
+ placeholder="Enter price breakdown details here"
+ className="min-h-[100px]"
+ {...field}
+ />
+ </FormControl>
+ <FormMessage />
+ </FormItem>
+ )}
+ />
+
+ <FormField
+ control={form.control}
+ name="commercialNotes"
+ render={({ field }) => (
+ <FormItem>
+ <FormLabel>Additional Notes (Optional)</FormLabel>
+ <FormControl>
+ <Textarea
+ placeholder="Any additional comments or notes"
+ className="min-h-[100px]"
+ {...field}
+ />
+ </FormControl>
+ <FormMessage />
+ </FormItem>
+ )}
+ />
+
+ <SheetFooter className="gap-2 pt-4 sm:space-x-0">
+ <SheetClose asChild>
+ <Button type="button" variant="outline">
+ Cancel
+ </Button>
+ </SheetClose>
+ <Button disabled={isSubmitting} type="submit">
+ {isSubmitting && (
+ <Loader
+ className="mr-2 size-4 animate-spin"
+ aria-hidden="true"
+ />
+ )}
+ Submit Response
+ </Button>
+ </SheetFooter>
+ </form>
+ </Form>
+ )}
+ </SheetContent>
+ </Sheet>
+ )
+} \ No newline at end of file