added challan
This commit is contained in:
parent
1b3e090211
commit
b8d3e58428
@ -234,7 +234,9 @@ font-weight: normal;
|
||||
.h-min { height: min-content; }
|
||||
.h-max { height: max-content; }
|
||||
|
||||
|
||||
.vh-50 {
|
||||
height: 50vh !important;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@ -2,54 +2,51 @@ import React from "react";
|
||||
import { formatFileSize, getIconByFileType } from "../../utils/appUtils";
|
||||
import Tooltip from "../common/Tooltip";
|
||||
|
||||
const Filelist = ({ files, removeFile, expenseToEdit,sm=6,md=4 }) => {
|
||||
const Filelist = ({ files, removeFile, expenseToEdit, sm = 6, md = 4 }) => {
|
||||
return (
|
||||
<div className="d-flex flex-wrap gap-2 my-1">
|
||||
{files
|
||||
.filter((file) => {
|
||||
if (expenseToEdit) {
|
||||
return file.isActive;
|
||||
}
|
||||
return true;
|
||||
})
|
||||
.map((file, idx) => (
|
||||
<div className={`col-12 col-sm-${sm} col-md-${md} mb-2`} key={idx}>
|
||||
<div className="d-flex align-items-center justify-content-between bg-white border rounded p-1">
|
||||
{/* File icon and info */}
|
||||
<div className="d-flex align-items-center flex-grow-1 gap-2 overflow-hidden">
|
||||
<i
|
||||
className={`bx ${getIconByFileType(
|
||||
file?.contentType
|
||||
)} fs-3 `}
|
||||
style={{ minWidth: "30px" }}
|
||||
></i>
|
||||
{files
|
||||
.filter((file) => {
|
||||
if (expenseToEdit) {
|
||||
return file.isActive;
|
||||
}
|
||||
return true;
|
||||
})
|
||||
.map((file, idx) => (
|
||||
<div className={`col-12 col-sm-${sm} col-md-${md} mb-2`} key={idx}>
|
||||
<div className="d-flex align-items-center justify-content-between bg-white border rounded p-1">
|
||||
{/* File icon and info */}
|
||||
<div className="d-flex align-items-center flex-grow-1 gap-2 overflow-hidden">
|
||||
<i
|
||||
className={`bx ${getIconByFileType(file?.contentType)} fs-3 `}
|
||||
style={{ minWidth: "30px" }}
|
||||
></i>
|
||||
|
||||
<div className="d-flex flex-column text-truncate">
|
||||
<span className="fw-semibold small text-truncate">
|
||||
{file.fileName}
|
||||
</span>
|
||||
<span className="text-body-secondary small">
|
||||
{file.fileSize ? formatFileSize(file.fileSize) : ""}
|
||||
</span>
|
||||
<div className="d-flex flex-column text-truncate">
|
||||
<span className="fw-semibold small text-truncate">
|
||||
{file.fileName}
|
||||
</span>
|
||||
<span className="text-body-secondary small">
|
||||
{file.fileSize ? formatFileSize(file.fileSize) : ""}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Delete icon */}
|
||||
<Tooltip text="Remove file">
|
||||
<i
|
||||
className="bx bx-sm bx-trash text-danger fs-4 cursor-pointer ms-2"
|
||||
role="button"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
removeFile(expenseToEdit ? file.documentId : idx);
|
||||
}}
|
||||
></i>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Delete icon */}
|
||||
<Tooltip text="Remove file">
|
||||
<i
|
||||
className="bx bx-sm bx-trash text-danger fs-4 cursor-pointer ms-2"
|
||||
role="button"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
removeFile(expenseToEdit ? file.documentId : idx);
|
||||
}}
|
||||
></i>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@ -58,13 +55,11 @@ export const FilelistView = ({ files, viewFile }) => {
|
||||
return (
|
||||
<div className="d-flex flex-wrap gap-2 mt-2">
|
||||
{files?.map((file, idx) => (
|
||||
<div className=" bg-white " key={idx}>
|
||||
<div className=" bg-white " key={idx}>
|
||||
<div className="row align-items-center">
|
||||
{/* File icon and info */}
|
||||
<div className="col-12 d-flex align-items-center gap-2">
|
||||
<i
|
||||
className={`bx ${getIconByFileType(file?.fileName)} fs-3`}
|
||||
></i>
|
||||
<i className={`bx ${getIconByFileType(file?.fileName)} fs-3`}></i>
|
||||
|
||||
<div
|
||||
className="d-flex flex-column text-truncate"
|
||||
@ -93,3 +88,44 @@ export const FilelistView = ({ files, viewFile }) => {
|
||||
</div>
|
||||
);
|
||||
};
|
||||
export const FileView = ({ file, viewFile }) => {
|
||||
if (!file) {
|
||||
return (
|
||||
<div className="d-flex justify-content-center align-items-center py-4">
|
||||
<p className="mb-0 text-muted small">No file uploaded</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className=" bg-white ">
|
||||
<div className="row align-items-center">
|
||||
{/* File icon and info */}
|
||||
<div className="col-12 d-flex align-items-center gap-2">
|
||||
<i className={`bx ${getIconByFileType(file?.fileName)} fs-4`}></i>
|
||||
|
||||
<div
|
||||
className="d-flex flex-column text-truncate"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
viewFile({
|
||||
IsOpen: true,
|
||||
Image: file.preSignedUrl,
|
||||
});
|
||||
}}
|
||||
>
|
||||
<span className="text-muted small text-truncate">
|
||||
{file.fileName}
|
||||
</span>
|
||||
<span className="text-body-secondary small">
|
||||
<Tooltip text={"Click on file"}>
|
||||
{" "}
|
||||
{file.fileSize ? formatFileSize(file.fileSize) : ""}
|
||||
</Tooltip>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
111
src/components/common/SigleFileUploader.jsx
Normal file
111
src/components/common/SigleFileUploader.jsx
Normal file
@ -0,0 +1,111 @@
|
||||
import { useRef } from "react";
|
||||
import Label from "./Label";
|
||||
import Tooltip from "./Tooltip";
|
||||
import { formatFileSize, getIconByFileType } from "../../utils/appUtils";
|
||||
|
||||
const SingleFileUploader = ({
|
||||
label = "Upload Document",
|
||||
required = false,
|
||||
value,
|
||||
onChange,
|
||||
onRemove,
|
||||
disabled,
|
||||
error,
|
||||
accept = ".pdf,.jpg,.jpeg,.png",
|
||||
maxSizeMB = 25,
|
||||
hint = "(PDF, JPG, PNG, max 5MB)",
|
||||
}) => {
|
||||
const inputRef = useRef(null);
|
||||
|
||||
const handleFileSelect = async (e) => {
|
||||
const file = e.target.files?.[0];
|
||||
if (!file) return;
|
||||
|
||||
// Validate size
|
||||
if (file.size > maxSizeMB * 1024 * 1024) {
|
||||
alert(`File size cannot exceed ${maxSizeMB}MB`);
|
||||
e.target.value = "";
|
||||
return;
|
||||
}
|
||||
|
||||
// Convert to base64
|
||||
const base64Data = await new Promise((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
reader.readAsDataURL(file);
|
||||
reader.onload = () => resolve(reader.result.split(",")[1]);
|
||||
reader.onerror = (err) => reject(err);
|
||||
});
|
||||
|
||||
const attachmentObj = {
|
||||
fileName: file.name,
|
||||
base64Data,
|
||||
invoiceAttachmentTypeId: "", // set dynamically if needed
|
||||
contentType: file.type,
|
||||
fileSize: file.size,
|
||||
description: "",
|
||||
isActive: true,
|
||||
};
|
||||
|
||||
onChange(attachmentObj);
|
||||
e.target.value = "";
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="col-md-12">
|
||||
<Label className="form-label" required={required}>
|
||||
{label}
|
||||
</Label>
|
||||
|
||||
<div
|
||||
className="border border-secondary border-dashed rounded p-4 text-center bg-textMuted position-relative"
|
||||
style={{ cursor: "pointer" }}
|
||||
onClick={() => inputRef.current.click()}
|
||||
>
|
||||
<i className="bx bx-cloud-upload d-block bx-lg"></i>
|
||||
<span className="text-muted d-block">
|
||||
Click to select or click here to browse
|
||||
</span>
|
||||
<small className="text-muted">{hint}</small>
|
||||
|
||||
<input
|
||||
type="file"
|
||||
ref={inputRef}
|
||||
accept={accept}
|
||||
style={{ display: "none" }}
|
||||
onChange={handleFileSelect}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{error && <small className="danger-text">{error}</small>}
|
||||
|
||||
{value && (
|
||||
<div className="mt-3">
|
||||
<div className="d-flex align-items-center justify-content-between bg-white border rounded p-2">
|
||||
<div className="d-flex align-items-center gap-2">
|
||||
<i
|
||||
className={`bx ${getIconByFileType(value.contentType)} fs-3`}
|
||||
></i>
|
||||
|
||||
<div className="d-flex flex-column text-truncate">
|
||||
<span className="fw-semibold small text-truncate">
|
||||
{value.fileName}
|
||||
</span>
|
||||
<small className="text-muted">
|
||||
{value.fileSize ? formatFileSize(value.fileSize) : ""}
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<i
|
||||
className="bx bx-trash text-danger fs-5 cursor-pointer"
|
||||
onClick={onRemove}
|
||||
></i>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SingleFileUploader;
|
||||
68
src/components/purchase/DeliverChallanList.jsx
Normal file
68
src/components/purchase/DeliverChallanList.jsx
Normal file
@ -0,0 +1,68 @@
|
||||
import React from "react";
|
||||
import { useDeliverChallane } from "../../hooks/usePurchase";
|
||||
import { SpinnerLoader } from "../common/Loader";
|
||||
import { formatUTCToLocalTime } from "../../utils/dateUtils";
|
||||
import { FileView } from "../Expenses/Filelist";
|
||||
|
||||
const DeliverChallanList = ({ purchaseId }) => {
|
||||
const { data, isLoading, isError, error } = useDeliverChallane(purchaseId);
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="d-flex justify-content-center align-items-center text-center vh-50">
|
||||
<SpinnerLoader />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (isError) {
|
||||
return (
|
||||
<div className="py-3">
|
||||
<Error error={error} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
if (!isLoading && data.length === 0)
|
||||
return (
|
||||
<div className="d-flex justify-content-center align-items-center text-center vh-50">
|
||||
<p className="mb-0">Not Yet</p>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="list-group list-group-flush text-start">
|
||||
{data.map((item) => (
|
||||
<div
|
||||
key={item.id}
|
||||
className="list-group-item d-flex justify-content-between align-items-start flex-wrap px-3 w-full"
|
||||
>
|
||||
{/* LEFT SIDE */}
|
||||
<div className=" pe-3 text-break w-full">
|
||||
<div className="d-flex justify-content-between">
|
||||
<p className="mb-1 fw-semibold">{item.deliveryChallanNumber}</p>
|
||||
<small className="text-muted text-end">
|
||||
{formatUTCToLocalTime(item.deliveryChallanDate, true)}
|
||||
</small>
|
||||
</div>
|
||||
|
||||
<div className="mb-1 small text-muted text-break">
|
||||
<span className="fw-semibold me-1">Invoice:</span>
|
||||
{item.purchaseInvoice?.title} (
|
||||
{item.purchaseInvoice?.purchaseInvoiceUId})
|
||||
</div>
|
||||
|
||||
<p className="mb-1 text-break">
|
||||
<span className="fw-semibold">Description:</span>{" "}
|
||||
{item.description || "-"}
|
||||
</p>
|
||||
|
||||
{item.attachment?.preSignedUrl && (
|
||||
<FileView file={item.attachment} />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default DeliverChallanList;
|
||||
205
src/components/purchase/DeliveryChallane.jsx
Normal file
205
src/components/purchase/DeliveryChallane.jsx
Normal file
@ -0,0 +1,205 @@
|
||||
import React, { useState } from "react";
|
||||
import {
|
||||
useAddDeliverChallan,
|
||||
useDeliverChallane,
|
||||
} from "../../hooks/usePurchase";
|
||||
import { SpinnerLoader } from "../common/Loader";
|
||||
import { formatUTCToLocalTime } from "../../utils/dateUtils";
|
||||
import DeliverChallanList from "./DeliverChallanList";
|
||||
import { AppFormController, useAppForm } from "../../hooks/appHooks/useAppForm";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import {
|
||||
DeliveryChallanDefaultValue,
|
||||
DeliveryChallanSchema,
|
||||
} from "./PurchaseSchema";
|
||||
import Label from "../common/Label";
|
||||
import DatePicker from "../common/DatePicker";
|
||||
import { useInvoiceAttachmentTypes } from "../../hooks/masterHook/useMaster";
|
||||
import SelectField from "../common/Forms/SelectField";
|
||||
import Filelist from "../Expenses/Filelist";
|
||||
import SingleFileUploader from "../common/SigleFileUploader";
|
||||
import { localToUtc } from "../../utils/appUtils";
|
||||
|
||||
const DeliveryChallane = ({ purchaseId }) => {
|
||||
const [file, setFile] = useState(null);
|
||||
|
||||
const {
|
||||
register,
|
||||
control,
|
||||
handleSubmit,
|
||||
setValue,
|
||||
trigger,
|
||||
watch,
|
||||
reset,
|
||||
formState: { errors },
|
||||
} = useAppForm({
|
||||
resolver: zodResolver(DeliveryChallanSchema),
|
||||
defaultValues: DeliveryChallanDefaultValue,
|
||||
});
|
||||
|
||||
const { data, isLoading } = useInvoiceAttachmentTypes();
|
||||
const { mutate: AddChallan, isPending } = useAddDeliverChallan(() => {
|
||||
setFile(null);
|
||||
setValue("attachment", null, { shouldValidate: false });
|
||||
reset({
|
||||
...DeliveryChallanDefaultValue,
|
||||
attachment: null,
|
||||
});
|
||||
});
|
||||
const onSubmit = async (formData) => {
|
||||
const valid = await trigger();
|
||||
if (!valid) return;
|
||||
const { invoiceAttachmentTypeId, ...rest } = formData;
|
||||
|
||||
const payload = {
|
||||
...rest,
|
||||
attachment: formData.attachment
|
||||
? {
|
||||
...formData.attachment,
|
||||
invoiceAttachmentTypeId,
|
||||
}
|
||||
: null,
|
||||
purchaseInvoiceId: purchaseId,
|
||||
deliveryChallanDate: formData.deliveryChallanDate
|
||||
? localToUtc(formData.deliveryChallanDate)
|
||||
: null,
|
||||
};
|
||||
|
||||
AddChallan(payload);
|
||||
};
|
||||
const isUploaded = watch("attachment");
|
||||
const isDocumentType = watch("invoiceAttachmentTypeId");
|
||||
return (
|
||||
<div className="row px-3 px-sm-1">
|
||||
<div className="fw-semibold mb-3 fs-5">Delivery Challans</div>
|
||||
|
||||
<div className="col-md-6">
|
||||
<form
|
||||
onSubmit={handleSubmit(onSubmit)}
|
||||
className="row g-3 text-start"
|
||||
noValidate
|
||||
>
|
||||
{/* Challan Number */}
|
||||
<div className="col-md-6">
|
||||
<Label required>Delivery Challan No</Label>
|
||||
|
||||
<input
|
||||
type="text"
|
||||
className="form-control form-control-sm"
|
||||
{...register("deliveryChallanNumber")}
|
||||
/>
|
||||
|
||||
{errors?.deliveryChallanNumber && (
|
||||
<small className="danger-text">
|
||||
{errors.deliveryChallanNumber.message}
|
||||
</small>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="col-md-6">
|
||||
<Label required>Delivery Date</Label>
|
||||
|
||||
<DatePicker
|
||||
name="deliveryChallanDate"
|
||||
control={control}
|
||||
className="w-100"
|
||||
/>
|
||||
|
||||
{errors?.deliveryChallanDate && (
|
||||
<small className="danger-text">
|
||||
{errors.deliveryChallanDate.message}
|
||||
</small>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="col-12">
|
||||
<Label required>Description</Label>
|
||||
|
||||
<textarea
|
||||
className="form-control form-control-sm"
|
||||
rows="3"
|
||||
{...register("description")}
|
||||
/>
|
||||
|
||||
{errors?.description && (
|
||||
<small className="danger-text">
|
||||
{errors.description.message}
|
||||
</small>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="col-12">
|
||||
<AppFormController
|
||||
name="invoiceAttachmentTypeId"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<SelectField
|
||||
label="Select Status"
|
||||
options={data ?? []}
|
||||
placeholder="Choose a Status"
|
||||
labelKeyKey="name"
|
||||
valueKeyKey="id"
|
||||
value={field.value}
|
||||
onChange={field.onChange}
|
||||
isLoading={isLoading}
|
||||
className="m-0"
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
||||
{errors?.invoiceAttachmentTypeId && (
|
||||
<small className="danger-text">
|
||||
{errors.invoiceAttachmentTypeId.message}
|
||||
</small>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<SingleFileUploader
|
||||
label="Upload Bill"
|
||||
required
|
||||
value={file}
|
||||
onChange={(selectedFile) => {
|
||||
setFile(selectedFile);
|
||||
setValue("attachment", selectedFile, { shouldValidate: true });
|
||||
}}
|
||||
disabled={!isDocumentType}
|
||||
onRemove={() => {
|
||||
setFile(null);
|
||||
setValue("attachment", null, { shouldValidate: true });
|
||||
}}
|
||||
error={errors?.attachment?.message}
|
||||
/>
|
||||
|
||||
<div className="col-12 text-end">
|
||||
<button type="submit" className="btn btn-sm btn-primary px-4">
|
||||
{isPending ? "Please Wait..." : "Submit"}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
{!isUploaded && (
|
||||
<div className="col-12 my-1">
|
||||
<div className="d-flex align-items-center gap-2 p-3 rounded bg-warning bg-opacity-10 border border-warning-subtle text-start align-item-start">
|
||||
<i className="bx bx-info-circle text-warning fs-5"></i>
|
||||
<p className="mb-0 small">
|
||||
If want upload document, Please select a document type before
|
||||
uploading the document.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="col-md-6 text-start">
|
||||
<div className="d-flex justiffy-content-start align-items-center gap-1">
|
||||
<i className="bx bx-history bx-xs"></i>{" "}
|
||||
<p className="fw-medium mb-0">History</p>
|
||||
</div>
|
||||
<div className="overflow-auto " style={{ maxHeight: "420px" }}>
|
||||
<DeliverChallanList purchaseId={purchaseId} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default DeliveryChallane;
|
||||
@ -8,7 +8,8 @@ import { useDebounce } from "../../utils/appUtils";
|
||||
import { usePurchaseContext } from "../../pages/purchase/PurchasePage";
|
||||
|
||||
const PurchaseList = ({ searchString }) => {
|
||||
const { setViewPurchase, setManagePurchase } = usePurchaseContext();
|
||||
const { setViewPurchase, setManagePurchase, setChallan } =
|
||||
usePurchaseContext();
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const debounceSearch = useDebounce(searchString, 300);
|
||||
const { data, isLoading } = usePurchasesList(
|
||||
@ -120,7 +121,15 @@ const PurchaseList = ({ searchString }) => {
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a className="dropdown-item cursor-pointer">
|
||||
<a
|
||||
className="dropdown-item cursor-pointer"
|
||||
onClick={() =>
|
||||
setChallan({
|
||||
isOpen: true,
|
||||
purchaseId: item.id,
|
||||
})
|
||||
}
|
||||
>
|
||||
<i className="bx bx-trash me-2"></i>
|
||||
<span className="align-left">Delete</span>
|
||||
</a>
|
||||
|
||||
@ -144,3 +144,49 @@ export const getStepFields = (stepIndex) => {
|
||||
|
||||
return stepFieldMap[stepIndex] || [];
|
||||
};
|
||||
|
||||
export const SingleAttachmentSchema = z.object({
|
||||
fileName: z.string().min(1, { message: "File name is required" }),
|
||||
base64Data: z.string().min(1, { message: "File data is required" }),
|
||||
invoiceAttachmentTypeId: z.string().nullable(),
|
||||
|
||||
contentType: z
|
||||
.string()
|
||||
.min(1, { message: "MIME type is required" })
|
||||
.refine(
|
||||
(val) =>
|
||||
["application/pdf", "image/jpeg", "image/jpg", "image/png"].includes(
|
||||
val
|
||||
),
|
||||
{
|
||||
message: "File type must be PDF, JPG, JPEG or PNG",
|
||||
}
|
||||
),
|
||||
|
||||
fileSize: z
|
||||
.number()
|
||||
.int()
|
||||
.nonnegative("fileSize must be ≥ 0")
|
||||
.max(5 * 1024 * 1024, "File size must be ≤ 5MB"),
|
||||
|
||||
description: z.string().optional().default(""),
|
||||
isActive: z.boolean(),
|
||||
});
|
||||
|
||||
export const DeliveryChallanSchema = z.object({
|
||||
deliveryChallanNumber: z
|
||||
.string()
|
||||
.min(1, { message: "Challan Number required" }),
|
||||
invoiceAttachmentTypeId: z.string().nullable(),
|
||||
deliveryChallanDate: z.string().min(1, { message: "Deliver date required" }),
|
||||
description: z.string().min(1, { message: "Description required" }),
|
||||
attachment: z.object(SingleAttachmentSchema.shape).nullable(),
|
||||
});
|
||||
|
||||
export const DeliveryChallanDefaultValue = {
|
||||
deliveryChallanNumber: "",
|
||||
deliveryChallanDate: "",
|
||||
description: "",
|
||||
attachment: null,
|
||||
invoiceAttachmentTypeId: null,
|
||||
};
|
||||
|
||||
@ -31,6 +31,19 @@ export const usePurchasesList = (
|
||||
});
|
||||
};
|
||||
|
||||
export const useDeliverChallane = (purchaseInvoiceId) => {
|
||||
return useQuery({
|
||||
queryKey: ["deliverliy_challane", purchaseInvoiceId],
|
||||
queryFn: async () => {
|
||||
const resp = await PurchaseRepository.GetDeliveryChallan(
|
||||
purchaseInvoiceId
|
||||
);
|
||||
return resp.data;
|
||||
},
|
||||
enabled: !!purchaseInvoiceId,
|
||||
});
|
||||
};
|
||||
|
||||
export const usePurchase = (id) => {
|
||||
return useQuery({
|
||||
queryKey: ["purchase", id],
|
||||
@ -67,7 +80,8 @@ export const useUpdatePurchaseInvoice = (onSuccessCallback) => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async ({purchaseId,payload}) => PurchaseRepository.UpdatePurchase(purchaseId, payload),
|
||||
mutationFn: async ({ purchaseId, payload }) =>
|
||||
PurchaseRepository.UpdatePurchase(purchaseId, payload),
|
||||
onSuccess: (data, variables) => {
|
||||
showToast("Purchase Invoice Updated successfully", "success");
|
||||
if (onSuccessCallback) onSuccessCallback();
|
||||
@ -82,3 +96,24 @@ export const useUpdatePurchaseInvoice = (onSuccessCallback) => {
|
||||
},
|
||||
});
|
||||
};
|
||||
export const useAddDeliverChallan = (onSuccessCallback) => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async (payload) =>
|
||||
PurchaseRepository.addDelievryChallan(payload),
|
||||
onSuccess: (data, variables) => {
|
||||
queryClient.invalidateQueries({ queryKey: ["deliverliy_challane"] });
|
||||
showToast("Challan added successfully", "success");
|
||||
if (onSuccessCallback) onSuccessCallback();
|
||||
},
|
||||
onError: (error) => {
|
||||
showToast(
|
||||
error?.response?.data?.message ||
|
||||
error.message ||
|
||||
"Failed to create invoice",
|
||||
"error"
|
||||
);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@ -5,6 +5,7 @@ import GlobalModel from "../../components/common/GlobalModel";
|
||||
import ManagePurchase from "../../components/purchase/ManagePurchase";
|
||||
import PurchaseList from "../../components/purchase/PurchaseList";
|
||||
import ViewPurchase from "../../components/purchase/ViewPurchase";
|
||||
import DeliveryChallane from "../../components/purchase/DeliveryChallane";
|
||||
|
||||
export const PurchaseContext = createContext();
|
||||
export const usePurchaseContext = () => {
|
||||
@ -18,6 +19,10 @@ export const usePurchaseContext = () => {
|
||||
};
|
||||
const PurchasePage = () => {
|
||||
const [searchText, setSearchText] = useState("");
|
||||
const [addChallan, setChallan] = useState({
|
||||
isOpen: false,
|
||||
purchaseId: null,
|
||||
});
|
||||
const [managePurchase, setManagePurchase] = useState({
|
||||
isOpen: false,
|
||||
purchaseId: null,
|
||||
@ -30,6 +35,7 @@ const PurchasePage = () => {
|
||||
const contextValue = {
|
||||
setViewPurchase,
|
||||
setManagePurchase,
|
||||
setChallan,
|
||||
};
|
||||
return (
|
||||
<PurchaseContext.Provider value={contextValue}>
|
||||
@ -107,6 +113,18 @@ const PurchasePage = () => {
|
||||
<ViewPurchase purchaseId={viewPurchaseState.purchaseId} />
|
||||
</GlobalModel>
|
||||
)}
|
||||
|
||||
{addChallan.isOpen && (
|
||||
<GlobalModel size="xl"
|
||||
isOpen={addChallan.isOpen}
|
||||
closeModal={() => setChallan({ isOpen: false, purchaseId: null })}
|
||||
>
|
||||
<DeliveryChallane
|
||||
purchaseId={addChallan.purchaseId}
|
||||
onClose={() => setChallan({ isOpen: false, purchaseId: null })}
|
||||
/>
|
||||
</GlobalModel>
|
||||
)}
|
||||
</div>
|
||||
</PurchaseContext.Provider>
|
||||
);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user