Compare commits

..

6 Commits

Author SHA1 Message Date
908a69ce0e version changed to 02.65 2026-02-08 16:53:18 +03:30
a818683247 add: multiple addresses 2026-02-08 16:52:55 +03:30
95780cfbc9 add: document upload and sign 2026-02-08 16:52:26 +03:30
071c3e159b feat: document operation 2026-02-08 16:52:00 +03:30
90f51c6899 feat: edit distribution from distribution 2026-02-08 11:20:10 +03:30
e967329108 chore: fix grammar mistake 2026-02-03 10:24:28 +03:30
7 changed files with 706 additions and 42 deletions

View File

@@ -1,11 +1,22 @@
import { useEffect, useState } from "react";
import { useParams } from "@tanstack/react-router"; import { useParams } from "@tanstack/react-router";
import { Bars3Icon, CubeIcon, SparklesIcon } from "@heroicons/react/24/outline"; import { Bars3Icon, CubeIcon, SparklesIcon } from "@heroicons/react/24/outline";
import { useApiRequest } from "../utils/useApiRequest"; import { useApiRequest } from "../utils/useApiRequest";
import { useModalStore } from "../context/zustand-store/appStore";
import { formatJustDate, formatJustTime } from "../utils/formatTime"; import { formatJustDate, formatJustTime } from "../utils/formatTime";
import ShowMoreInfo from "../components/ShowMoreInfo/ShowMoreInfo"; import ShowMoreInfo from "../components/ShowMoreInfo/ShowMoreInfo";
import { Grid } from "../components/Grid/Grid"; import { Grid } from "../components/Grid/Grid";
import Typography from "../components/Typography/Typography"; import Typography from "../components/Typography/Typography";
import Table from "../components/Table/Table"; import Table from "../components/Table/Table";
import { Popover } from "../components/PopOver/PopOver";
import Button from "../components/Button/Button";
import { Tooltip } from "../components/Tooltip/Tooltip";
import { DistributeFromDistribution } from "../partials/tagging/DistributeFromDistribution";
import { DocumentOperation } from "../components/DocumentOperation/DocumentOperation";
import { DocumentDownloader } from "../components/DocumentDownloader/DocumentDownloader";
import { BooleanQuestion } from "../components/BooleanQuestion/BooleanQuestion";
import { useUserProfileStore } from "../context/zustand-store/userStore";
import { DeleteButtonForPopOver } from "../components/PopOverButtons/PopOverButtons";
const speciesMap: Record<number, string> = { const speciesMap: Record<number, string> = {
1: "گاو", 1: "گاو",
@@ -17,14 +28,240 @@ const speciesMap: Record<number, string> = {
export default function TagDistribtutionDetails() { export default function TagDistribtutionDetails() {
const { id } = useParams({ strict: false }); const { id } = useParams({ strict: false });
const { openModal } = useModalStore();
const [childTableInfo, setChildTableInfo] = useState({
page: 1,
page_size: 10,
});
const [childTableData, setChildTableData] = useState([]);
const { data } = useApiRequest({ const { data, refetch: refetchData } = useApiRequest({
api: `/tag/web/api/v1/tag_distribution_batch/${id}/`, api: `/tag/web/api/v1/tag_distribution_batch/${id}/`,
method: "get", method: "get",
queryKey: ["tagBatchInnerDashboard", id], queryKey: ["tagBatchInnerDashboard", id],
enabled: !!id, enabled: !!id,
}); });
const { data: childData, refetch: refetchChildList } = useApiRequest({
api: `/tag/web/api/v1/tag_distribution_batch/${id}/child_list/`,
method: "get",
queryKey: ["tagDistributionChildList", id, childTableInfo],
params: {
...childTableInfo,
},
enabled: !!id,
});
const { profile } = useUserProfileStore();
const showAssignDocColumn =
childData?.results?.some(
(item: any) =>
profile?.role?.type?.key === "ADM" ||
profile?.organization?.id === item?.assigned_org?.id,
) ?? false;
const AbleToSeeAssignDoc = (item: any) => {
if (
profile?.role?.type?.key === "ADM" ||
profile?.organization?.id === item?.assigned_org?.id
) {
return (
<DocumentOperation
key={item?.id}
downloadLink={`/tag/web/api/v1/tag_distribution_batch/${item?.id}/distribution_pdf_view/`}
payloadKey="dist_exit_document"
validFiles={["pdf"]}
page="tag_distribution"
access="Upload-Assign-Document"
uploadLink={`/tag/web/api/v1/tag_distribution_batch/${item?.id}/assign_document/`}
onUploadSuccess={handleUpdate}
/>
);
} else {
return "-";
}
};
const handleUpdate = () => {
refetchData();
refetchChildList();
};
useEffect(() => {
if (childData?.results) {
const formattedData = childData.results.map(
(item: any, index: number) => {
const dist = item?.distributions;
return [
childTableInfo.page === 1
? index + 1
: index +
childTableInfo.page_size * (childTableInfo.page - 1) +
1,
item?.dist_batch_identity ?? "-",
`${formatJustDate(item?.create_date) ?? "-"} (${
formatJustDate(item?.create_date)
? (formatJustTime(item?.create_date) ?? "-")
: "-"
})`,
item?.assigner_org?.name ?? "-",
item?.assigned_org?.name ?? "-",
item?.total_tag_count?.toLocaleString() ?? "-",
item?.total_distributed_tag_count?.toLocaleString() ?? "-",
item?.remaining_tag_count?.toLocaleString() ?? "-",
item?.distribution_type === "batch"
? "توزیع گروهی"
: "توزیع تصادفی",
<ShowMoreInfo key={item?.id} title="جزئیات توزیع">
<Grid container column className="gap-4 w-full">
{dist?.map((opt: any, idx: number) => (
<Grid
key={idx}
container
column
className="gap-3 w-full rounded-xl border border-gray-200 dark:border-gray-700 p-4"
>
<Grid container className="gap-2 items-center">
<SparklesIcon className="w-5 h-5 text-gray-500 dark:text-gray-300" />
<Typography variant="body2" className="font-medium">
گونه:
</Typography>
<Typography
variant="body2"
className="text-gray-700 dark:text-gray-300"
>
{speciesMap[opt?.species_code] ?? "-"}
</Typography>
</Grid>
{item?.distribution_type === "batch" &&
opt?.serial_from != null && (
<Grid container className="gap-2 items-center">
<Bars3Icon className="w-5 h-5 text-gray-500 dark:text-gray-300" />
<Typography variant="body2" className="font-medium">
بازه سریال:
</Typography>
<Typography
variant="body2"
className="text-gray-600 dark:text-gray-400"
>
از {opt?.serial_from ?? "-"} تا{" "}
{opt?.serial_to ?? "-"}
</Typography>
</Grid>
)}
<Grid container className="gap-2 items-center">
<CubeIcon className="w-5 h-5 text-gray-500 dark:text-gray-300" />
<Typography variant="body2" className="font-medium">
تعداد پلاک:
</Typography>
<Typography
variant="body2"
className="text-gray-700 dark:text-gray-300"
>
{opt?.total_tag_count?.toLocaleString() ?? "-"}
</Typography>
</Grid>
<Grid container className="gap-2 items-center">
<CubeIcon className="w-5 h-5 text-gray-500 dark:text-gray-300" />
<Typography variant="body2" className="font-medium">
پلاک های توزیع شده:
</Typography>
<Typography
variant="body2"
className="text-gray-700 dark:text-gray-300"
>
{opt?.distributed_number?.toLocaleString() ?? "-"}
</Typography>
</Grid>
<Grid container className="gap-2 items-center">
<CubeIcon className="w-5 h-5 text-gray-500 dark:text-gray-300" />
<Typography variant="body2" className="font-medium">
پلاک های باقیمانده:
</Typography>
<Typography
variant="body2"
className="text-gray-700 dark:text-gray-300"
>
{opt?.remaining_number?.toLocaleString() ?? "-"}
</Typography>
</Grid>
</Grid>
))}
</Grid>
</ShowMoreInfo>,
...(showAssignDocColumn ? [AbleToSeeAssignDoc(item)] : []),
<DocumentDownloader
key={`doc-${item?.id}`}
link={item?.warehouse_exit_doc}
title="دانلود"
/>,
item?.exit_doc_status ? (
"تایید شده"
) : (
<Button
key={`btn-${item?.id}`}
page="tag_distribution"
access="Accept-Assign-Document"
size="small"
disabled={item?.exit_doc_status}
onClick={() => {
openModal({
title: "تایید سند خروج",
content: (
<BooleanQuestion
api={`/tag/web/api/v1/tag_distribution_batch/${item?.id}/accept_exit_doc/`}
method="post"
getData={handleUpdate}
title="آیا از تایید سند خروج مطمئنید؟"
/>
),
});
}}
>
تایید سند خروج
</Button>
),
<Popover key={`popover-${item?.id}`}>
<Tooltip title="ویرایش توزیع" position="right">
<Button
variant="edit"
page="tag_distribution"
access="Submit-Tag-Distribution"
onClick={() => {
openModal({
title: "ویرایش توزیع",
content: (
<DistributeFromDistribution
getData={handleUpdate}
item={item}
isEdit
parentDistributions={data?.distributions}
/>
),
});
}}
/>
</Tooltip>
<DeleteButtonForPopOver
page="tag_distribution"
access="Delete-Tag-Distribution"
api={`/tag/web/api/v1/tag_distribution_batch/${item?.id}/`}
getData={handleUpdate}
/>
</Popover>,
];
},
);
setChildTableData(formattedData);
} else {
setChildTableData([]);
}
}, [childData, childTableInfo]);
const dist = data?.distributions; const dist = data?.distributions;
return ( return (
@@ -145,6 +382,31 @@ export default function TagDistribtutionDetails() {
]} ]}
/> />
</Grid> </Grid>
<Table
className="mt-2"
onChange={setChildTableInfo}
count={childData?.count || 0}
isPaginated
title="توزیع های مجدد"
columns={[
"ردیف",
"شناسه توزیع",
"تاریخ ثبت",
"توزیع کننده",
"دریافت کننده",
"تعداد کل پلاک",
"پلاک های توزیع شده",
"پلاک های باقیمانده",
"نوع توزیع",
"جزئیات توزیع",
...(showAssignDocColumn ? ["امضا سند خروج از انبار"] : []),
"سند خروج از انبار",
"تایید سند خروج",
"عملیات",
]}
rows={childTableData}
/>
</Grid> </Grid>
); );
} }

View File

@@ -0,0 +1,229 @@
import React, { useRef, useState, useEffect, ChangeEvent } from "react";
import {
ArrowDownTrayIcon,
ArrowUpTrayIcon,
CheckCircleIcon,
} from "@heroicons/react/24/outline";
import api from "../../utils/axios";
import { useBackdropStore } from "../../context/zustand-store/appStore";
import { useToast } from "../../hooks/useToast";
import { useUserProfileStore } from "../../context/zustand-store/userStore";
import { RolesContextMenu } from "../Button/RolesContextMenu";
interface DocumentOperationProps {
downloadLink: string;
uploadLink: string;
validFiles?: string[];
payloadKey: string;
onUploadSuccess?: () => void;
page?: string;
access?: string;
}
const buildAcceptString = (extensions: string[]): string => {
const mimeTypes: string[] = [];
extensions.forEach((ext) => {
const lower = ext.toLowerCase().replace(".", "");
if (lower === "img" || lower === "image") {
mimeTypes.push("image/*");
} else {
mimeTypes.push(`.${lower}`);
}
});
return mimeTypes.join(",");
};
export const DocumentOperation = ({
downloadLink,
uploadLink,
validFiles = [],
payloadKey,
onUploadSuccess,
page = "",
access = "",
}: DocumentOperationProps) => {
const { openBackdrop, closeBackdrop } = useBackdropStore();
const showToast = useToast();
const fileInputRef = useRef<HTMLInputElement>(null);
const [uploadedFileName, setUploadedFileName] = useState<string>("");
const { profile } = useUserProfileStore();
const [contextMenu, setContextMenu] = useState<{
x: number;
y: number;
} | null>(null);
const isAdmin = profile?.role?.type?.key === "ADM";
const ableToSee = () => {
if (!access || !page) {
return true;
}
const found = profile?.permissions?.find(
(item: any) => item.page_name === page,
);
if (found && found.page_access.includes(access)) {
return true;
}
return false;
};
const handleContextMenu = (e: React.MouseEvent<HTMLDivElement>) => {
if (isAdmin && page && access) {
e.preventDefault();
e.stopPropagation();
setContextMenu({
x: e.clientX,
y: e.clientY,
});
}
};
useEffect(() => {
const handleClick = () => {
if (contextMenu) {
setContextMenu(null);
}
};
if (contextMenu) {
document.addEventListener("click", handleClick);
}
return () => {
document.removeEventListener("click", handleClick);
};
}, [contextMenu]);
const handleDownload = async () => {
if (!downloadLink) return;
openBackdrop();
try {
const response = await api.get(downloadLink, {
responseType: "blob",
});
const contentDisposition = response.headers["content-disposition"];
let fileName = "document";
if (contentDisposition) {
const match = contentDisposition.match(
/filename\*?=(?:UTF-8''|"?)([^";]+)/i,
);
if (match?.[1]) {
fileName = decodeURIComponent(match[1].replace(/"/g, ""));
}
} else {
const urlParts = downloadLink.split("/").filter(Boolean);
const lastPart = urlParts[urlParts.length - 1];
if (lastPart && lastPart.includes(".")) {
fileName = lastPart;
}
}
const url = window.URL.createObjectURL(new Blob([response.data]));
const link = document.createElement("a");
link.href = url;
link.setAttribute("download", fileName);
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
window.URL.revokeObjectURL(url);
showToast("فایل با موفقیت دانلود شد", "success");
} catch {
showToast("خطا در دانلود فایل", "error");
} finally {
closeBackdrop();
}
};
const handleUploadClick = () => {
fileInputRef.current?.click();
};
const handleFileChange = async (e: ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
if (!file) return;
if (fileInputRef.current) {
fileInputRef.current.value = "";
}
openBackdrop();
try {
const formData = new FormData();
formData.append(payloadKey, file);
await api.post(uploadLink, formData, {
headers: {
"Content-Type": "multipart/form-data",
},
});
setUploadedFileName(file.name);
showToast("فایل با موفقیت آپلود شد", "success");
onUploadSuccess?.();
} catch {
showToast("خطا در آپلود فایل", "error");
} finally {
closeBackdrop();
}
};
const acceptString =
validFiles.length > 0 ? buildAcceptString(validFiles) : undefined;
return (
<>
<div
className="inline-flex items-center"
onContextMenu={handleContextMenu}
>
<input
type="file"
ref={fileInputRef}
onChange={handleFileChange}
className="hidden"
accept={acceptString}
/>
<button
type="button"
onClick={handleDownload}
disabled={!downloadLink || !ableToSee()}
className="flex items-center gap-1.5 px-3 py-1.5 text-sm rounded-r-lg border border-l-0 border-primary-300 dark:border-dark-400 bg-primary-50 dark:bg-dark-600 text-primary-700 dark:text-primary-200 hover:bg-primary-100 dark:hover:bg-dark-500 transition-colors duration-200 cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed"
>
<ArrowDownTrayIcon className="w-4 h-4" />
<span>دانلود</span>
</button>
<button
type="button"
onClick={handleUploadClick}
disabled={!uploadLink || !ableToSee()}
className="flex items-center gap-1.5 px-3 py-1.5 text-sm rounded-l-lg border border-primary-300 dark:border-dark-400 bg-primary-600 dark:bg-primary-700 text-white hover:bg-primary-500 dark:hover:bg-primary-800 transition-colors duration-200 cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed"
>
{uploadedFileName ? (
<CheckCircleIcon className="w-4 h-4 text-green-300" />
) : (
<ArrowUpTrayIcon className="w-4 h-4" />
)}
<span>{uploadedFileName ? "آپلود شده" : "آپلود"}</span>
</button>
</div>
{contextMenu && page && access && (
<RolesContextMenu
page={page}
access={access}
position={contextMenu}
onClose={() => setContextMenu(null)}
/>
)}
</>
);
};

View File

@@ -8,7 +8,6 @@ import {
zValidateNumber, zValidateNumber,
zValidateNumberOptional, zValidateNumberOptional,
zValidateString, zValidateString,
zValidateStringOptional,
} from "../../data/getFormTypeErrors"; } from "../../data/getFormTypeErrors";
import { z } from "zod"; import { z } from "zod";
import { useApiMutation } from "../../utils/useApiRequest"; import { useApiMutation } from "../../utils/useApiRequest";
@@ -21,11 +20,11 @@ import { getToastResponse } from "../../data/getToastResponse";
import { useUserProfileStore } from "../../context/zustand-store/userStore"; import { useUserProfileStore } from "../../context/zustand-store/userStore";
import { useState } from "react"; import { useState } from "react";
import Checkbox from "../../components/CheckBox/CheckBox"; import Checkbox from "../../components/CheckBox/CheckBox";
import { PlusIcon, TrashIcon } from "@heroicons/react/24/outline";
const schema = z.object({ const schema = z.object({
name: zValidateString("نام سازمان"), name: zValidateString("نام سازمان"),
national_unique_id: zValidateString("شناسه کشوری"), national_unique_id: zValidateString("شناسه کشوری"),
address: zValidateStringOptional("آدرس"),
field_of_activity: zValidateAutoComplete("حوزه فعالیت"), field_of_activity: zValidateAutoComplete("حوزه فعالیت"),
province: zValidateNumber("استان"), province: zValidateNumber("استان"),
city: zValidateNumber("شهر"), city: zValidateNumber("شهر"),
@@ -75,7 +74,6 @@ export const AddOrganization = ({ getData, item }: AddPageProps) => {
resolver: zodResolver(schema), resolver: zodResolver(schema),
defaultValues: { defaultValues: {
name: item?.name || "", name: item?.name || "",
address: item?.address || "",
national_unique_id: item?.national_unique_id || "", national_unique_id: item?.national_unique_id || "",
free_visibility_by_scope: item?.free_visibility_by_scope || false, free_visibility_by_scope: item?.free_visibility_by_scope || false,
field_of_activity: field_of_activity:
@@ -95,9 +93,41 @@ export const AddOrganization = ({ getData, item }: AddPageProps) => {
city: string | any; city: string | any;
}>({ province: "", city: "" }); }>({ province: "", city: "" });
const [addresses, setAddresses] = useState<
{ postal_code: string; address: string }[]
>(
item?.addresses?.length
? item.addresses.map((a: any) => ({
postal_code: a.postal_code || "",
address: a.address || "",
}))
: [{ postal_code: "", address: "" }],
);
const handleAddAddress = () => {
setAddresses((prev) => [...prev, { postal_code: "", address: "" }]);
};
const handleRemoveAddress = (index: number) => {
setAddresses((prev) => prev.filter((_, i) => i !== index));
};
const handleAddressChange = (
index: number,
field: "postal_code" | "address",
value: string,
) => {
setAddresses((prev) =>
prev.map((item, i) => (i === index ? { ...item, [field]: value } : item)),
);
};
const onSubmit = async (data: FormValues) => { const onSubmit = async (data: FormValues) => {
try { try {
await mutation.mutateAsync({ await mutation.mutateAsync({
addresses: addresses.filter(
(a) => a.postal_code.trim() || a.address.trim(),
),
organization: { organization: {
name: `${data?.name} ${ name: `${data?.name} ${
data?.is_repeatable data?.is_repeatable
@@ -118,7 +148,6 @@ export const AddOrganization = ({ getData, item }: AddPageProps) => {
}), }),
field_of_activity: data.field_of_activity[0], field_of_activity: data.field_of_activity[0],
free_visibility_by_scope: data.free_visibility_by_scope, free_visibility_by_scope: data.free_visibility_by_scope,
address: data.address,
}, },
}); });
showToast(getToastResponse(item, "سازمان"), "success"); showToast(getToastResponse(item, "سازمان"), "success");
@@ -128,12 +157,12 @@ export const AddOrganization = ({ getData, item }: AddPageProps) => {
if (error?.status === 403) { if (error?.status === 403) {
showToast( showToast(
error?.response?.data?.message || "این سازمان تکراری است!", error?.response?.data?.message || "این سازمان تکراری است!",
"error" "error",
); );
} else { } else {
showToast( showToast(
error?.response?.data?.message || "خطا در ثبت اطلاعات!", error?.response?.data?.message || "خطا در ثبت اطلاعات!",
"error" "error",
); );
} }
} }
@@ -258,7 +287,7 @@ export const AddOrganization = ({ getData, item }: AddPageProps) => {
defaultKey={item?.parent_organization?.id} defaultKey={item?.parent_organization?.id}
title="سازمان والد (اختیاری)" title="سازمان والد (اختیاری)"
api={`auth/api/v1/organization/organizations_by_province?province=${getValues( api={`auth/api/v1/organization/organizations_by_province?province=${getValues(
"province" "province",
)}`} )}`}
keyField="id" keyField="id"
valueField="name" valueField="name"
@@ -273,20 +302,53 @@ export const AddOrganization = ({ getData, item }: AddPageProps) => {
)} )}
/> />
<Controller <div className="flex flex-col gap-2 w-full">
name="address" <div className="flex items-center justify-between">
control={control} <span className="text-sm font-medium text-gray-700">آدرسها</span>
render={({ field }) => ( <button
type="button"
onClick={handleAddAddress}
className="flex items-center gap-1 text-sm text-blue-600 hover:text-blue-800 transition-colors"
>
<PlusIcon className="w-4 h-4" />
افزودن آدرس
</button>
</div>
{addresses.map((addr, index) => (
<div
key={index}
className="flex items-start gap-2 p-3 border border-gray-200 rounded-lg"
>
<div className="flex flex-col gap-2 flex-1">
<Textfield <Textfield
fullWidth fullWidth
placeholder="آدرس (اختیاری)" placeholder="کد پستی"
value={field.value} value={addr.postal_code}
onChange={field.onChange} onChange={(e) =>
error={!!errors.address} handleAddressChange(index, "postal_code", e.target.value)
helperText={errors.address?.message} }
/> />
<Textfield
fullWidth
placeholder="آدرس"
value={addr.address}
onChange={(e) =>
handleAddressChange(index, "address", e.target.value)
}
/>
</div>
{addresses.length > 1 && (
<button
type="button"
onClick={() => handleRemoveAddress(index)}
className="mt-2 text-red-500 hover:text-red-700 transition-colors shrink-0"
>
<TrashIcon className="w-5 h-5" />
</button>
)} )}
/> </div>
))}
</div>
<Controller <Controller
name="free_visibility_by_scope" name="free_visibility_by_scope"

View File

@@ -59,7 +59,14 @@ export const OrganizationsList = () => {
: "نامشخص", : "نامشخص",
item?.province?.name, item?.province?.name,
item?.city?.name, item?.city?.name,
item?.address || "-", <ShowMoreInfo
key={`address-${i}`}
title="آدرس‌ها"
disabled={!item?.addresses?.length}
data={item?.addresses}
columns={["کد پستی", "آدرس"]}
accessKeys={[["postal_code"], ["address"]]}
/>,
<ShowMoreInfo <ShowMoreInfo
key={i} key={i}
title="اطلاعات حساب" title="اطلاعات حساب"

View File

@@ -27,7 +27,12 @@ type ParentDistItem = {
label?: string; label?: string;
}; };
export const DistributeFromDistribution = ({ item, getData }: any) => { export const DistributeFromDistribution = ({
item,
getData,
isEdit,
parentDistributions,
}: any) => {
const showToast = useToast(); const showToast = useToast();
const { closeModal } = useModalStore(); const { closeModal } = useModalStore();
@@ -54,12 +59,12 @@ export const DistributeFromDistribution = ({ item, getData }: any) => {
api: `/tag/web/api/v1/tag_distribution_batch/${item?.id}/`, api: `/tag/web/api/v1/tag_distribution_batch/${item?.id}/`,
method: "get", method: "get",
queryKey: ["tagDistributionBatchDetail", item?.id], queryKey: ["tagDistributionBatchDetail", item?.id],
enabled: !!item?.id, enabled: !!item?.id && !isEdit,
}); });
const mutation = useApiMutation({ const mutation = useApiMutation({
api: `/tag/web/api/v1/tag_distribution/${item?.id}/distribute_distribution/`, api: `/tag/web/api/v1/tag_distribution/${item?.id}/${isEdit ? "edit_" : ""}distribute_distribution/`,
method: "post", method: isEdit ? "put" : "post",
}); });
const { data: speciesData } = useApiRequest({ const { data: speciesData } = useApiRequest({
@@ -70,7 +75,9 @@ export const DistributeFromDistribution = ({ item, getData }: any) => {
}); });
useEffect(() => { useEffect(() => {
const sourceDistributions = item?.distributions?.length const sourceDistributions = isEdit
? parentDistributions
: item?.distributions?.length
? item.distributions ? item.distributions
: batchDetail?.distributions; : batchDetail?.distributions;
@@ -96,9 +103,32 @@ export const DistributeFromDistribution = ({ item, getData }: any) => {
}); });
setDists(parentDists); setDists(parentDists);
if (isEdit && item?.distributions?.length) {
const defaultCounts: Record<number, number | ""> = {};
const defaultSpeciesKeys: (string | number)[] = [];
parentDists.forEach((pd) => {
const childDist = item.distributions.find(
(cd: any) =>
cd.parent_tag_distribution === pd.id ||
cd.species_code === pd.species_code,
);
if (childDist) {
defaultCounts[pd.id] = childDist.total_tag_count || 0;
if (!defaultSpeciesKeys.includes(pd.species_code)) {
defaultSpeciesKeys.push(pd.species_code);
}
}
});
setCounts(defaultCounts);
setSelectedSpeciesKeys(defaultSpeciesKeys);
} else {
setCounts({}); setCounts({});
setSelectedSpeciesKeys([]); setSelectedSpeciesKeys([]);
}, [item?.distributions, batchDetail?.distributions]); }
}, [item?.distributions, batchDetail?.distributions, parentDistributions]);
const speciesOptions = () => const speciesOptions = () =>
speciesData?.results?.map((opt: any) => ({ speciesData?.results?.map((opt: any) => ({
@@ -145,7 +175,12 @@ export const DistributeFromDistribution = ({ item, getData }: any) => {
dists: distsPayload, dists: distsPayload,
}); });
showToast("توزیع از توزیع با موفقیت انجام شد", "success"); showToast(
isEdit
? "ویرایش توزیع با موفقیت انجام شد"
: "توزیع از توزیع با موفقیت انجام شد",
"success",
);
getData(); getData();
closeModal(); closeModal();
} catch (error: any) { } catch (error: any) {
@@ -188,6 +223,7 @@ export const DistributeFromDistribution = ({ item, getData }: any) => {
control={control} control={control}
render={() => ( render={() => (
<FormApiBasedAutoComplete <FormApiBasedAutoComplete
defaultKey={item?.assigned_org?.id}
title="انتخاب سازمان (دریافت‌کننده)" title="انتخاب سازمان (دریافت‌کننده)"
api={`auth/api/v1/organization/organizations_by_province?exclude=PSP&province=${item?.assigner_org?.province}`} api={`auth/api/v1/organization/organizations_by_province?exclude=PSP&province=${item?.assigner_org?.province}`}
keyField="id" keyField="id"

View File

@@ -23,6 +23,9 @@ import { TableButton } from "../../components/TableButton/TableButton";
import { DistributionSpeciesModal } from "./DistributionSpeciesModal"; import { DistributionSpeciesModal } from "./DistributionSpeciesModal";
import { useNavigate } from "@tanstack/react-router"; import { useNavigate } from "@tanstack/react-router";
import { TAG_DISTRIBUTION } from "../../routes/paths"; import { TAG_DISTRIBUTION } from "../../routes/paths";
import { DocumentOperation } from "../../components/DocumentOperation/DocumentOperation";
import { DocumentDownloader } from "../../components/DocumentDownloader/DocumentDownloader";
import { useUserProfileStore } from "../../context/zustand-store/userStore";
export default function TagActiveDistributions() { export default function TagActiveDistributions() {
const { openModal } = useModalStore(); const { openModal } = useModalStore();
@@ -39,12 +42,43 @@ export default function TagActiveDistributions() {
}, },
}); });
const { profile } = useUserProfileStore();
const { data: tagDashboardData, refetch: updateDashboard } = useApiRequest({ const { data: tagDashboardData, refetch: updateDashboard } = useApiRequest({
api: "/tag/web/api/v1/tag_distribution_batch/main_dashboard/?is_closed=false", api: "/tag/web/api/v1/tag_distribution_batch/main_dashboard/?is_closed=false",
method: "get", method: "get",
queryKey: ["tagDistributionActivesDashboard"], queryKey: ["tagDistributionActivesDashboard"],
}); });
const showAssignDocColumn =
(profile?.role?.type?.key === "ADM" ||
tagsData?.results?.some(
(item: any) => profile?.organization?.id === item?.assigned_org?.id,
)) ??
false;
const AbleToSeeAssignDoc = (item: any) => {
if (
profile?.role?.type?.key === "ADM" ||
profile?.organization?.id === item?.assigned_org?.id
) {
return (
<DocumentOperation
key={item?.id}
downloadLink={`/tag/web/api/v1/tag_distribution_batch/${item?.id}/distribution_pdf_view/`}
payloadKey="dist_exit_document"
validFiles={["pdf"]}
page="tag_distribution"
access="Upload-Assign-Document"
uploadLink={`/tag/web/api/v1/tag_distribution_batch/${item?.id}/assign_document/`}
onUploadSuccess={handleUpdate}
/>
);
} else {
return "-";
}
};
const handleUpdate = () => { const handleUpdate = () => {
refetch(); refetch();
updateDashboard(); updateDashboard();
@@ -155,8 +189,39 @@ export default function TagActiveDistributions() {
))} ))}
</Grid> </Grid>
</ShowMoreInfo>, </ShowMoreInfo>,
...(showAssignDocColumn ? [AbleToSeeAssignDoc(item)] : []),
<DocumentDownloader
key={index}
link={item?.warehouse_exit_doc}
title="دانلود"
/>,
item?.exit_doc_status ? (
"تایید شده"
) : (
<Button
page="tag_distribution"
access="Accept-Assign-Document"
size="small"
disabled={item?.exit_doc_status}
onClick={() => {
openModal({
title: "تایید سند خروج",
content: (
<BooleanQuestion
api={`/tag/web/api/v1/tag_distribution_batch/${item?.id}/accept_exit_doc/`}
method="post"
getData={handleUpdate}
title="آیا از تایید سند خروج مطمئنید؟"
/>
),
});
}}
>
تایید سند خروج
</Button>
),
<Popover key={index}> <Popover key={index}>
<Tooltip title="جزيٓیات توزیع" position="right"> <Tooltip title="جزئیات توزیع" position="right">
<Button <Button
variant="detail" variant="detail"
page="tag_distribution_detail" page="tag_distribution_detail"
@@ -321,6 +386,9 @@ export default function TagActiveDistributions() {
"پلاک های باقیمانده", "پلاک های باقیمانده",
"نوع توزیع", "نوع توزیع",
"جزئیات توزیع", "جزئیات توزیع",
...(showAssignDocColumn ? ["امضا سند خروج از انبار"] : []),
"سند خروج از انبار",
"تایید سند خروج",
"عملیات", "عملیات",
]} ]}
rows={tagsTableData} rows={tagsTableData}

View File

@@ -1 +1 @@
02.64 02.65