diff --git a/src/components/Button/Button.tsx b/src/components/Button/Button.tsx index f930785..6387a2a 100644 --- a/src/components/Button/Button.tsx +++ b/src/components/Button/Button.tsx @@ -7,6 +7,7 @@ import React, { } from "react"; import clsx from "clsx"; import { + ArrowUpCircleIcon, ChartBarIcon, DocumentChartBarIcon, EyeIcon, @@ -56,7 +57,8 @@ type ButtonProps = { | "view" | "info" | "chart" - | "set"; + | "set" + | "share"; page?: string; access?: string; height?: string | number; @@ -161,6 +163,10 @@ const Button: React.FC = ({ return ( ); + case "share": + return ( + + ); default: return null; } @@ -181,7 +187,7 @@ const Button: React.FC = ({ return true; } else { const finded = profile?.permissions?.find( - (item: any) => item.page_name === page + (item: any) => item.page_name === page, ); if (finded && finded.page_access.includes(access)) { return true; @@ -237,7 +243,7 @@ const Button: React.FC = ({ sizeStyles.padding, sizeStyles.text, className, - checkIsMobile() && !icon && !variant && children && mobileBorders + checkIsMobile() && !icon && !variant && children && mobileBorders, )} style={{ height }} > @@ -256,7 +262,7 @@ const Button: React.FC = ({ .then((response) => { closeBackdrop(); const url = window.URL.createObjectURL( - new Blob([response.data]) + new Blob([response.data]), ); const link = document.createElement("a"); diff --git a/src/partials/tagging/DistributeFromDistribution.tsx b/src/partials/tagging/DistributeFromDistribution.tsx new file mode 100644 index 0000000..958c9b1 --- /dev/null +++ b/src/partials/tagging/DistributeFromDistribution.tsx @@ -0,0 +1,255 @@ +import { z } from "zod"; +import { useEffect, useState } from "react"; +import { Controller, useForm } from "react-hook-form"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { Grid } from "../../components/Grid/Grid"; +import Button from "../../components/Button/Button"; +import Textfield from "../../components/Textfeild/Textfeild"; +import { FormApiBasedAutoComplete } from "../../components/FormItems/FormApiBasedAutoComplete"; +import AutoComplete from "../../components/AutoComplete/AutoComplete"; +import { zValidateAutoComplete } from "../../data/getFormTypeErrors"; +import { useApiMutation, useApiRequest } from "../../utils/useApiRequest"; +import { useToast } from "../../hooks/useToast"; +import { useModalStore } from "../../context/zustand-store/appStore"; + +const schema = z.object({ + organization: zValidateAutoComplete("سازمان"), +}); + +type FormValues = z.infer; + +type ParentDistItem = { + id: number; + dist_identity?: number; + batch_identity: string | number | null; + species_code: number; + maxCount: number; + label?: string; +}; + +export const DistributeFromDistribution = ({ item, getData }: any) => { + const showToast = useToast(); + const { closeModal } = useModalStore(); + + const [dists, setDists] = useState([]); + const [selectedSpeciesKeys, setSelectedSpeciesKeys] = useState< + (string | number)[] + >([]); + const [counts, setCounts] = useState>({}); + + const { + control, + handleSubmit, + setValue, + trigger, + formState: { errors }, + } = useForm({ + resolver: zodResolver(schema), + defaultValues: { + organization: [], + }, + }); + + const { data: batchDetail } = useApiRequest({ + api: `/tag/web/api/v1/tag_distribution_batch/${item?.id}/`, + method: "get", + queryKey: ["tagDistributionBatchDetail", item?.id], + enabled: !!item?.id, + }); + + const mutation = useApiMutation({ + api: `/tag/web/api/v1/tag_distribution/${item?.id}/distribute_distribution/`, + method: "post", + }); + + const { data: speciesData } = useApiRequest({ + api: "/livestock/web/api/v1/livestock_species", + method: "get", + params: { page: 1, pageSize: 1000 }, + queryKey: ["species"], + }); + + useEffect(() => { + const sourceDistributions = item?.distributions?.length + ? item.distributions + : batchDetail?.distributions; + + if (!sourceDistributions?.length) { + setDists([]); + setCounts({}); + return; + } + + const parentDists: ParentDistItem[] = sourceDistributions.map((d: any) => { + const maxCount = + d?.remaining_number ?? d?.total_tag_count ?? d?.distributed_number ?? 0; + return { + id: d?.id ?? d?.dist_identity, + dist_identity: d?.dist_identity, + batch_identity: d?.batch_identity ?? null, + species_code: d?.species_code, + maxCount: Number(maxCount) || 0, + label: + d?.serial_from != null && d?.serial_to != null + ? `از ${d.serial_from} تا ${d.serial_to}` + : undefined, + }; + }); + + setDists(parentDists); + setCounts({}); + setSelectedSpeciesKeys([]); + }, [item?.distributions, batchDetail?.distributions]); + + const speciesOptions = () => + speciesData?.results?.map((opt: any) => ({ + key: opt?.value, + value: opt?.name, + })) ?? []; + + const getSpeciesName = (speciesCode: number) => + speciesOptions().find((s: any) => s.key === speciesCode)?.value ?? "نامشخص"; + + const visibleDists = dists.filter((d) => + selectedSpeciesKeys.includes(d.species_code), + ); + + const onSubmit = async (data: FormValues) => { + const distsPayload = visibleDists + .filter((d) => { + const c = counts[d.id]; + return c !== "" && c !== undefined && c !== null && Number(c) > 0; + }) + .map((d) => { + const fromItem = item?.distributions?.find( + (x: any) => x.id === d.id || x.dist_identity === d.id, + ); + const batch_identity = + fromItem != null ? fromItem.batch_identity : d.batch_identity; + return { + parent_tag_distribution: d.id, + batch_identity: batch_identity != null ? batch_identity : null, + species_code: d.species_code, + count: Number(counts[d.id] ?? 0), + }; + }); + + if (distsPayload.length === 0) { + showToast("حداقل یک گونه با تعداد معتبر وارد کنید", "error"); + return; + } + + try { + await mutation.mutateAsync({ + assigned_org: data.organization[0], + parent_distribution_batch: item.id, + dists: distsPayload, + }); + + showToast("توزیع از توزیع با موفقیت انجام شد", "success"); + getData(); + closeModal(); + } catch (error: any) { + showToast( + error?.response?.data?.message || "خطا در ثبت اطلاعات!", + "error", + ); + } + }; + + const handleCountChange = (distId: number, value: number | "") => { + setCounts((prev) => ({ ...prev, [distId]: value })); + }; + + const isValidCount = (dist: ParentDistItem) => { + const c = counts[dist.id]; + if (c === "" || c === undefined || c === null) return false; + const num = Number(c); + return num > 0 && num <= dist.maxCount; + }; + + const speciesOptionsFromParent = () => { + const uniqueSpecies = Array.from( + new Map(dists.map((d) => [d.species_code, d])).values(), + ); + return uniqueSpecies.map((d) => ({ + key: d.species_code, + value: getSpeciesName(d.species_code), + })); + }; + + const hasValidDists = + visibleDists.length > 0 && visibleDists.every((d) => isValidCount(d)); + + return ( +
+ + ( + { + setValue("organization", [r]); + trigger("organization"); + }} + /> + )} + /> + + {dists.length > 0 && speciesData?.results && ( + <> + { + setSelectedSpeciesKeys(keys); + }} + title="گونه" + /> + {visibleDists.map((dist) => { + const countVal = counts[dist.id]; + const numCount = + countVal !== "" && countVal !== undefined && countVal !== null + ? Number(countVal) + : null; + const isOverMax = numCount !== null && numCount > dist.maxCount; + const isEmpty = countVal === "" || countVal === undefined; + const helperText = isOverMax + ? `تعداد نباید بیشتر از ${dist.maxCount.toLocaleString()} باشد` + : isEmpty + ? "لطفا تعداد را وارد کنید" + : undefined; + + return ( + + handleCountChange(dist.id, Number(e.target.value)) + } + error={isOverMax} + helperText={helperText} + /> + ); + })} + + )} + + + +
+ ); +}; diff --git a/src/partials/tagging/TagActiveDistributions.tsx b/src/partials/tagging/TagActiveDistributions.tsx index 4ebb6d9..3e53159 100644 --- a/src/partials/tagging/TagActiveDistributions.tsx +++ b/src/partials/tagging/TagActiveDistributions.tsx @@ -16,6 +16,7 @@ import Button from "../../components/Button/Button"; import { Tooltip } from "../../components/Tooltip/Tooltip"; import { DeleteButtonForPopOver } from "../../components/PopOverButtons/PopOverButtons"; import { SubmitTagDistribution } from "./SubmitTagDistribution"; +import { DistributeFromDistribution } from "./DistributeFromDistribution"; import Table from "../../components/Table/Table"; import { BooleanQuestion } from "../../components/BooleanQuestion/BooleanQuestion"; import { TableButton } from "../../components/TableButton/TableButton"; @@ -163,6 +164,24 @@ export default function TagActiveDistributions() { }} /> + +