push rasad front on new repo

This commit is contained in:
2026-01-18 14:32:49 +03:30
commit 4fe6e70525
2139 changed files with 303150 additions and 0 deletions

View File

@@ -0,0 +1,890 @@
import React, { useContext, useEffect, useState, useCallback } from "react";
import {
Autocomplete,
Button,
Checkbox,
FormControl,
FormControlLabel,
InputAdornment,
Radio,
RadioGroup,
TextField,
Typography,
} from "@mui/material";
import { useDispatch, useSelector } from "react-redux";
import { useFormik } from "formik";
import { DatePicker } from "@mui/x-date-pickers";
import moment from "moment";
import { AppContext } from "../../../contexts/AppContext";
import { provincePolicyGetUploadImageService } from "../../province/services/province-policy-upload-image";
import { getRoleFromUrl } from "../../../utils/getRoleFromUrl";
import { slaughterGetProductsService } from "../../slaughter-house/services/slaughter-inventory-gets";
import { slaughterGetGuildsForAllocateService } from "../../slaughter-house/services/slaughter-get-guilds-for-allocate";
import { Yup } from "../../../lib/yup/yup";
import { fixBase64 } from "../../../utils/toBase64";
import { CLOSE_MODAL, DRAWER } from "../../../lib/redux/slices/appSlice";
import { NumberInput } from "../../../components/number-format-custom/NumberFormatCustom";
import { ImageUpload } from "../../../components/image-upload/ImageUpload";
import { Grid } from "../../../components/grid/Grid";
import {
slaughterAllocateStewardService,
slaughterEditAllocateStewardService,
} from "../../slaughter-house/services/slaughter-allocate-steward";
import { fetchSlaughterBroadcastAndProducts } from "../../slaughter-house/services/handle-fetch-slaughter-products";
import MonthlyDataCalendar from "../../../components/date-picker/MonthlyDataCalendar";
import PersianDate from "persian-date";
import axios from "axios";
import { LabelField } from "../../../components/label-field/LabelField";
import { SPACING } from "../../../data/spacing";
import { checkPathStartsWith } from "../../../utils/checkPathStartsWith";
export const StewardAllocationToGuild = ({
item,
key,
sellerType,
fetchData,
buyerType,
allocationType,
sellType,
updateTable,
fetchApiData,
editData,
coldHouseKey,
coldHouseItemKey,
killHouseAllocation,
priceInfo,
}) => {
const dispatch = useDispatch();
const [productData, setProductData] = useState([]);
const [guildsData, setGuildsData] = useState([]);
const [selectedInventory, setSelectedInventory] = useState("governmental");
const [approvedStatus, setApprovedStatus] = useState("true");
const [productKey, setProductKey] = useState(null);
const [openNotif] = useContext(AppContext);
const [profileImages, setProfileImages] = useState(
editData?.image ? [{ data_url: editData.image }] : []
);
const [value, setValue] = useState("own");
const [imageUploadLimit, setImageUploadLimit] = useState(1);
const [imageChanged, setImageChanged] = useState(false);
const [changeMobile, setChangeMobile] = useState(false);
const [selectedCalendarDate, setSelectedCalendarDate] = useState(null);
const [calendarDayData, setCalendarDayData] = useState({});
const [productionDate, setProductionDate] = useState(null);
const [selectedDateAmount, setSelectedDateAmount] = useState(null);
const [calendarRawData, setCalendarRawData] = useState({
governmental: [],
free: [],
});
const [selectedDate1, setSelectedDate1] = useState(
moment(new Date()).format("YYYY-MM-DD")
);
const selectedSubUser = useSelector(
(state) => state.userSlice.selectedSubUser
);
const handleChange = (event) => {
setValue(event.target.value);
setBuyerData({
key: "",
item: "",
buyerType: "",
allocationType: "",
});
};
useEffect(() => {
if (priceInfo?.active === false) {
setApprovedStatus("false");
}
}, [priceInfo?.active]);
useEffect(() => {
if (approvedStatus === "true" && priceInfo?.active) {
formik.setFieldValue("price", priceInfo?.killHousePrice);
}
}, [approvedStatus]);
const handleSellType = (event) => {
const newType = event.target.value;
setSelectedInventory(newType);
};
const handleApprovedPrice = (event) => {
const newType = event.target.value;
setApprovedStatus(newType);
};
const handleDateSelect = (dateInfo) => {
if (dateInfo && dateInfo.formattedDate) {
setSelectedCalendarDate(dateInfo.formattedDate);
const data = calendarDayData[dateInfo.formattedDate];
if (data && data.originalDay) {
setProductionDate(data.originalDay);
}
if (data && (data.amount !== undefined || data.value1 !== undefined)) {
const rawAmount = data.amount !== undefined ? data.amount : data.value1;
const normalizedAmount =
typeof rawAmount === "string"
? Number(rawAmount.replace(/,/g, ""))
: Number(rawAmount);
setSelectedDateAmount(
Number.isFinite(normalizedAmount) ? normalizedAmount : null
);
} else {
setSelectedDateAmount(null);
}
}
};
const transformCalendarData = useCallback((dataArray) => {
if (!Array.isArray(dataArray)) return {};
const transformedData = {};
dataArray.forEach((item) => {
if (item.day && item.amount !== undefined) {
const persianDate = new PersianDate(new Date(item.day));
const persianDateStr = persianDate.format("YYYY/MM/DD");
const rawAmount = item.amount;
const normalizedAmount =
typeof rawAmount === "string"
? Number(rawAmount.replace(/,/g, ""))
: Number(rawAmount);
transformedData[persianDateStr] = {
value1: normalizedAmount,
originalDay: item.day,
active: item.active === true, // Store active status
};
}
});
return transformedData;
}, []);
const updateCalendarData = useCallback((dataArray) => {
const transformed = transformCalendarData(dataArray);
setCalendarDayData(transformed);
}, []);
const fetchCalendarData = useCallback(
async (dateParam = selectedDate1) => {
try {
const response = await axios.get("/steward-remain-weight/", {
params: {
date: dateParam,
role_key: checkPathStartsWith("steward")
? selectedSubUser?.key
: "",
},
});
if (response.data) {
setCalendarRawData({
governmental: response.data.governmental || [],
free: response.data.free || [],
});
const dataToShow =
selectedInventory === "governmental"
? response.data.governmental
: response.data.free;
updateCalendarData(dataToShow || []);
}
} catch (error) {
console.error("Error fetching calendar data:", error);
}
},
[selectedInventory, updateCalendarData, selectedDate1, selectedSubUser]
);
const [buyerData, setBuyerData] = useState({
key,
item,
buyerType,
allocationType,
});
useEffect(() => {
if (getRoleFromUrl() === "Steward") {
setValue("free");
}
}, []);
useEffect(() => {
fetchCalendarData(selectedDate1);
}, [selectedDate1]);
useEffect(() => {
if (
calendarRawData.governmental.length > 0 ||
calendarRawData.free.length > 0
) {
const dataToShow =
selectedInventory === "governmental"
? calendarRawData.governmental
: calendarRawData.free;
updateCalendarData(dataToShow);
setSelectedCalendarDate(null);
setProductionDate(null);
setSelectedDateAmount(null);
}
}, [selectedInventory, calendarRawData]);
useEffect(() => {
dispatch(
provincePolicyGetUploadImageService({
role_key: checkPathStartsWith("steward") ? selectedSubUser?.key : "",
})
).then((r) => {
if (r.payload?.data) {
setImageUploadLimit(r.payload.data.killHouseAllocation);
}
});
if (!editData) {
dispatch(
slaughterGetProductsService({
role_key: checkPathStartsWith("steward") ? selectedSubUser?.key : "",
})
).then((r) => {
setProductData(r.payload.data);
});
if (!item) {
dispatch(
slaughterGetGuildsForAllocateService({
free: value === "free" ? true : false,
role_key: checkPathStartsWith("steward")
? selectedSubUser?.key
: "",
})
).then((r) => {
setGuildsData(r.payload.data);
});
}
}
}, [value, selectedSubUser?.key]);
const validationSchema = Yup.object({
mobile: Yup.string().when([], {
is: () => !editData,
then: (schema) =>
schema
.required("شماره موبایل الزامی است")
.min(11, "شماره موبایل باید 11 رقم باشد")
.max(11, "شماره موبایل باید 11 رقم باشد")
.matches(
/^09\d{9}$/,
"شماره موبایل باید با 09 شروع شود و 11 رقم باشد"
),
otherwise: (schema) => schema.notRequired(),
}),
weight: Yup.number()
.required("این فیلد اجباری است!")
.integer("عدد باید صحیح باشد!")
.min(1, "یک مقدار مثبت وارد کنید!")
.test(
"max-production-date-amount",
`وزن نمی‌تواند بیشتر از موجودی تاریخ تولید (${
selectedDateAmount?.toLocaleString() || 0
} کیلوگرم) باشد!`,
function (value) {
if (!selectedDateAmount || selectedDateAmount === null) return true;
return (
value <= selectedDateAmount + (editData?.realWeightOfCarcasses || 0)
);
}
),
price: Yup.number()
.required("این فیلد اجباری است!")
.min(1, "یک مقدار مثبت وارد کنید!"),
wholePrice: Yup.number()
.required("این فیلد اجباری است!")
.min(1, "یک مقدار مثبت وارد کنید!"),
...(killHouseAllocation && {
image: Yup.string().when([], {
is: () => (!editData || imageChanged) && imageUploadLimit > 0,
then: Yup.string().required("عکس الزامی است"),
otherwise: Yup.string().notRequired(),
}),
}),
});
const factorPaymentHandler = (imageList) => {
if (imageList[0]) {
formik.setFieldValue("image", fixBase64(imageList[0]?.data_url));
setImageChanged(true);
} else {
formik.setFieldValue("image", "");
setImageChanged(true);
}
setProfileImages(imageList);
};
const formik = useFormik({
initialValues: {
mobile: "",
weight: editData?.realWeightOfCarcasses || "",
wholePrice: editData?.totalAmount || "",
price: editData?.amount || "",
image: editData?.image || "",
},
validationSchema,
});
useEffect(() => {
formik.validateForm();
}, []);
useEffect(() => {
formik.validateForm();
}, [selectedDateAmount]);
useEffect(() => {
if (formik.values.weight && formik.values.price) {
formik.setFieldValue(
"wholePrice",
formik.values.price * formik.values.weight
);
}
}, [formik.values.price, formik.values.weight]);
const successSubmit = () => {
dispatch(
fetchSlaughterBroadcastAndProducts({
role_key: checkPathStartsWith("steward") ? selectedSubUser?.key : "",
})
);
dispatch(
DRAWER({
right: false,
bottom: false,
left: false,
content: null,
})
);
fetchApiData && fetchApiData(1);
updateTable && updateTable();
fetchData && fetchData(1);
dispatch(CLOSE_MODAL());
openNotif({
vertical: "top",
horizontal: "center",
msg: "عملیات با موفقیت انجام شد.",
severity: "success",
});
};
const [dateRangeError, setDateRangeError] = useState(null);
return (
<Grid
container
xs={12}
direction="column"
justifyContent="center"
alignItems="flex-start"
gap={1.8}
>
{!editData && (
<DatePicker
label="تاریخ ثبت توزیع"
id="date"
renderInput={(params) => (
<TextField
fullWidth
{...params}
error={Boolean(dateRangeError) || params.error}
helperText={dateRangeError || params.helperText}
/>
)}
shouldDisableDate={(date) => {
const d = moment(date);
const today = moment();
const yesterday = moment().subtract(1, "day");
return !(d.isSame(today, "day") || d.isSame(yesterday, "day"));
}}
value={selectedDate1}
onChange={(e) => {
if (!e) {
setDateRangeError(null);
return;
}
const d = moment(e);
const today = moment();
const yesterday = moment().subtract(1, "day");
const isAllowed =
d.isSame(today, "day") || d.isSame(yesterday, "day");
if (!isAllowed) {
setDateRangeError(
"تنها امکان انتخاب «امروز» یا «دیروز» وجود دارد."
);
return;
}
setDateRangeError(null);
const formatted = moment(e).format("YYYY-MM-DD");
setSelectedDate1(formatted);
fetchCalendarData(formatted);
}}
/>
)}
{!editData && !coldHouseKey && (
<Grid xs={12} container>
<Autocomplete
fullWidth
style={{ minWidth: 210 }}
disablePortal
id="hatching"
options={
productData
? productData.map((i) => {
return {
data: i,
label: `${i.name}`,
};
})
: []
}
onChange={(event, value) => {
setProductKey(value.data);
}}
renderInput={(params) => (
<TextField fullWidth {...params} label="انتخاب محصول" />
)}
/>
</Grid>
)}
{!editData && (
<LabelField label="خریداران">
<FormControl fullWidth>
<RadioGroup
row
aria-labelledby="demo-controlled-radio-buttons-group"
name="controlled-radio-buttons-group"
value={value}
onChange={handleChange}
sx={{
justifyContent: "space-between",
}}
>
<FormControlLabel
value="own"
control={<Radio />}
label="اختصاصی"
/>
<FormControlLabel value="free" control={<Radio />} label="آزاد" />
</RadioGroup>
</FormControl>
</LabelField>
)}
{!item && !editData && (
<Grid xs={12} container>
<Autocomplete
fullWidth
style={{ minWidth: 210 }}
disablePortal
id="hatching"
options={
guildsData
? guildsData.map((i) => {
return {
data: i,
label: `${i?.guildsName} ${i?.user?.fullname} (${i?.user?.mobile})`,
};
})
: []
}
onChange={(event, value) => {
setBuyerData({
item: value?.data,
key: value?.data?.key,
allocationType: "steward_guild",
buyerType: "Guild",
});
formik.setFieldValue("mobile", value?.data?.user?.mobile);
formik.setFieldTouched("mobile", true, false);
formik.validateField("mobile");
const reg = new RegExp(/^09\d{9}$/);
if (!reg.test(value?.data?.user?.mobile)) {
setChangeMobile(true);
}
}}
renderInput={(params) => (
<TextField fullWidth {...params} label="انتخاب صنف" />
)}
/>
</Grid>
)}
{!item && !editData && (
<Grid
container
xs={12}
alignItems="center"
justifyContent="center"
p={1}
gap={SPACING.TINY}
sx={{
border: 2,
borderColor: "#e6e6e6",
borderRadius: 2,
}}
>
<Typography variant="caption" color="error">
<Checkbox
sx={{ ml: -1.25 }}
checked={changeMobile}
onChange={() => setChangeMobile(!changeMobile)}
/>
از این قسمت میتوانید تلفن صنف را ویرایش کنید.
</Typography>
{buyerData?.key && changeMobile && (
<TextField
fullWidth
id="mobile"
value={formik.values.mobile}
error={
formik.touched.mobile ? Boolean(formik.errors.mobile) : null
}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
helperText={
formik.touched.mobile && Boolean(formik.errors.mobile)
? formik.errors.mobile
: null
}
label="موبایل"
autoComplete="current-password"
variant="outlined"
/>
)}
</Grid>
)}
{!item && !editData && priceInfo?.active !== false && (
<LabelField label="نوع فروش">
<FormControl fullWidth>
<RadioGroup
row
aria-labelledby="segment-type-radio-group"
name="segmentType"
value={approvedStatus}
onChange={handleApprovedPrice}
sx={{
justifyContent: "space-between",
}}
>
<FormControlLabel
value={true}
control={<Radio />}
label="قیمت دولتی"
/>
<FormControlLabel
value={false}
control={<Radio />}
label="قیمت آزاد"
/>
</RadioGroup>
</FormControl>
</LabelField>
)}
{!item && !editData && (
<LabelField label="نوع انبار">
<FormControl fullWidth>
<RadioGroup
row
aria-labelledby="segment-type-radio-group"
name="segmentType"
value={selectedInventory}
onChange={handleSellType}
sx={{
justifyContent: "space-between",
}}
>
<FormControlLabel
value="governmental"
control={<Radio />}
label="دولتی"
/>
<FormControlLabel value="free" control={<Radio />} label="آزاد" />
</RadioGroup>
</FormControl>
</LabelField>
)}
<Grid
container
xs={12}
justifyContent="center"
alignItems="center"
gap={SPACING.TINY}
sx={{ width: "100%" }}
direction="column"
>
<MonthlyDataCalendar
onDateSelect={handleDateSelect}
dayData={calendarDayData}
selectedDate={selectedCalendarDate}
maxGregorianDate={selectedDate1}
label={`تاریخ تولید گوشت ${
selectedDateAmount !== null
? `(موجودی: ${selectedDateAmount?.toLocaleString()} کیلوگرم)`
: ""
}`}
/>
{productionDate &&
selectedDate1 &&
moment(productionDate).isAfter(moment(selectedDate1), "day") && (
<Typography
sx={{
color: "#d32f2f",
fontSize: "0.75rem",
marginTop: "4px",
marginRight: "14px",
textAlign: "right",
}}
>
تاریخ تولید نمیتواند بعد از تاریخ انتخابی باشد
</Typography>
)}
</Grid>
<NumberInput
allowLeadingZeros
thousandSeparator=","
decimalScale={0}
allowNegative={false}
fullWidth
id="weight"
label="وزن لاشه"
variant="outlined"
value={formik.values.weight}
error={
!selectedDateAmount && !productionDate
? true
: formik.touched.weight
? Boolean(formik.errors.weight)
: selectedDateAmount && formik.values.weight > selectedDateAmount
}
onChange={(e) => {
const value = e.target.value;
if (value === "" || value === null || value === undefined) {
formik.setFieldValue("weight", "");
return;
}
const intValue = Math.floor(Number(value));
if (intValue > 0) {
formik.setFieldValue("weight", intValue);
} else if (intValue === 0) {
formik.setFieldValue("weight", "");
}
}}
onBlur={formik.handleBlur}
helperText={
!selectedDateAmount && !productionDate
? "لطفاً ابتدا تاریخ تولید را انتخاب کنید!"
: formik.touched.weight && Boolean(formik.errors.weight)
? formik.errors.weight
: null
}
disabled={!selectedDateAmount && !productionDate}
sx={{
"& .MuiFormHelperText-root": {
color:
selectedDateAmount && formik.values.weight > selectedDateAmount
? "error.main"
: undefined,
},
}}
/>
<NumberInput
allowLeadingZeros
thousandSeparator=","
fullWidth
id="price"
label="قیمت هر کیلوگرم"
variant="outlined"
InputProps={{
endAdornment: <InputAdornment position="start">ریال</InputAdornment>,
}}
value={formik.values.price}
error={formik.touched.price ? Boolean(formik.errors.price) : null}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
helperText={
formik.touched.price && Boolean(formik.errors.price)
? formik.errors.price
: null
}
/>
<NumberInput
disabled
allowLeadingZeros
thousandSeparator=","
fullWidth
id="wholePrice"
label="هزینه کل"
variant="outlined"
InputProps={{
endAdornment: <InputAdornment position="start">ریال</InputAdornment>,
}}
value={formik.values.wholePrice}
error={
formik.touched.wholePrice ? Boolean(formik.errors.wholePrice) : null
}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
helperText={
formik.touched.wholePrice && Boolean(formik.errors.wholePrice)
? formik.errors.wholePrice
: null
}
/>
{(killHouseAllocation || (editData && editData.image)) && (
<Grid container xs={12} justifyContent="center" alignItems="center">
<ImageUpload
onChange={factorPaymentHandler}
images={profileImages}
maxNumber={1}
title={"بارگزاری سند"}
/>
{formik.touched.image && Boolean(formik.errors.image) && (
<Typography color="error">ثبت تصویر الزامی است</Typography>
)}
</Grid>
)}
<Grid container xs={12} spacing={SPACING.SMALL}>
<Grid xs={6}>
<Button
variant="contained"
fullWidth
disabled={
editData
? !formik.isValid
: !formik.isValid ||
(coldHouseKey ? false : !productKey) ||
!buyerData?.item?.key ||
!productionDate ||
(productionDate &&
selectedDate1 &&
moment(selectedDate1).isBefore(
moment(productionDate),
"day"
))
}
onClick={() => {
let req = {};
if (coldHouseItemKey) {
req = {
allocation_key: coldHouseItemKey,
number_of_carcasses: 0,
weight_of_carcasses: formik.values.weight,
amount: formik.values.price,
total_amount: formik.values.wholePrice,
role_key: checkPathStartsWith("steward")
? selectedSubUser?.key || ""
: "",
distribution_type: "web",
...(imageChanged && { image: formik.values.image }),
};
} else if (!editData) {
req = {
seller_type: sellerType,
buyer_type: "Guild",
guild_key: buyerData?.item?.key,
cold_house_key: coldHouseKey || null,
product_key: coldHouseKey ? null : productKey.key,
type: "manual",
allocation_type: coldHouseKey ? "ColdHouse" : "steward_guild",
number_of_carcasses: 0,
weight_of_carcasses: formik.values.weight,
sell_type: sellType,
amount: formik.values.price,
total_amount: formik.values.wholePrice,
approved_price_status:
approvedStatus === "true" ? true : false,
quota: selectedInventory,
date: selectedDate1,
production_date: productionDate,
role_key: checkPathStartsWith("steward")
? selectedSubUser?.key || ""
: "",
distribution_type: "web",
...(buyerData?.item?.user?.mobile !== formik.values.mobile
? { interface_number: formik.values.mobile }
: {}),
...(profileImages.length > 0 && {
image: formik.values.image,
}),
};
req = Object.fromEntries(
Object.entries(req).filter(([, value]) => value !== null)
);
} else {
req = {
allocation_key: editData?.key,
number_of_carcasses: 0,
weight_of_carcasses: formik.values.weight,
amount: formik.values.price,
role_key: checkPathStartsWith("steward")
? selectedSubUser?.key || ""
: "",
total_amount: formik.values.wholePrice,
distribution_type: "web",
...(imageChanged && { image: formik.values.image }),
};
}
if (!editData) {
dispatch(slaughterAllocateStewardService(req)).then((r) => {
if (r.payload.error) {
openNotif({
vertical: "top",
horizontal: "center",
msg: r.payload.error,
severity: "error",
});
} else {
successSubmit();
}
});
} else {
dispatch(slaughterEditAllocateStewardService(req)).then((r) => {
if (r.payload.error) {
openNotif({
vertical: "top",
horizontal: "center",
msg: r.payload.error,
severity: "error",
});
} else {
successSubmit();
}
});
}
}}
>
{editData ? "ویرایش" : "ثبت"}
</Button>
</Grid>
<Grid xs={6}>
<Button
fullWidth
variant="outlined"
color="primary"
onClick={() => {
dispatch(DRAWER({ right: false, bottom: false, content: null }));
}}
>
انصراف
</Button>
</Grid>
</Grid>
</Grid>
);
};