Android_Retail/src/Pages/ShiftMaster/ShiftMasterForm.jsx

789 lines
26 KiB
React
Raw Normal View History

2026-01-27 18:27:29 +05:30
import { useState, useEffect, useRef, useCallback } from 'react';
import { useDispatch } from 'react-redux';
import { useNavigate, useLocation } from 'react-router-dom';
import moment from 'moment';
import { Form, Tooltip, Collapse, Space, Modal, Table } from 'antd';
import {
ArrowRightOutlined,
PlusCircleOutlined,
DeleteFilled,
EditFilled,
InfoCircleOutlined,
ReloadOutlined,
} from '@ant-design/icons';
import { InputField } from '../../Components/Forms/InputField.jsx';
import Buttons from '../../Components/Forms/Buttons.jsx';
import { Messages } from '../../Components/Notifications/Messages';
import { TimePickers } from '../../Components/Forms/TimePicker.jsx';
import FormHeader from '../PageComponents/FormHeader.jsx';
import { changeBreadCrumb } from '../../Features/AppPage/CenterPage.js';
import { getSession, validateSafeInput } from '../../Services/Others';
import { postshift, putshift } from '../../Features/ShiftMaster/ShiftMaster.js';
import './ShiftMaster.scss';
import { FaPlus } from 'react-icons/fa6';
2026-02-11 11:35:57 +05:30
import "../../Styles/OverAllStyle/OverAllStyle.scss";
2026-01-27 18:27:29 +05:30
const subDirectory = import.meta.env.BASE_URL;
const ShiftForm = ({ formType }) => {
const formRef = useRef(null);
const dispatch = useDispatch();
const navigate = useNavigate();
const location = useLocation();
const state = location?.state;
const editstate = state?.editstate;
const CompId = getSession('CompId');
const BranchId = getSession('BranchId');
const AppId = getSession('AppId');
const UserId = getSession('UserId');
const [messageType, setMessageType] = useState(null);
const [messageData, setMessageData] = useState(null);
const [fromTime, setFromTime] = useState();
const [toTime, setToTime] = useState();
const [BrakeFromTime, setBrakeFromTime] = useState();
const [BrakeToTime, setBrakeToTime] = useState();
const [Tabledata, setTabledata] = useState([]);
const [searchedText, setSearchedText] = useState('');
const [sortedInfo, setSortedInfo] = useState({});
const [editDataval, setEditDataval] = useState(false);
const [page, setpage] = useState(1);
const [editIndex, setEditIndex] = useState();
const [BrakeId, setBrakeId] = useState();
const items = [
{
name: 'Home',
link: `${subDirectory}app-page/home`,
},
{
name: 'Shift',
link: `${subDirectory}setting/shift-master`,
},
{
name: editstate ? 'Edit' : 'New',
link: null,
// link: editstate
// ? `${subDirectory}setting/shift-master/update`
// : `${subDirectory}setting/shift-master/new`,
},
];
useEffect(() => {
try {
dispatch(changeBreadCrumb({ items: items }));
if (formType === 'edit') {
if (editstate) {
setFromTime(editstate?.StartTime);
setToTime(editstate?.EndTime);
setBrakeFromTime(editstate?.BreakStartTime);
setBrakeToTime(editstate?.BreakEndTime);
setTabledata(editstate?.BreakDtl);
formRef.current?.setFieldsValue(editstate);
} else {
navigate(`${subDirectory}setting/shift-master/`);
}
}
} catch (err) {
console.log(err, 'err');
}
}, []);
const onFinish = async (values) => {
// Validate all break times are within shift time range
const shiftStart = moment(fromTime, 'HH:mm:ss');
const shiftEnd = moment(toTime, 'HH:mm:ss');
for (const breakItem of Tabledata) {
const breakStart = moment(breakItem.BreakStartTime, 'HH:mm:ss');
const breakEnd = moment(breakItem.BreakEndTime, 'HH:mm:ss');
const isValidBreak = shiftEnd.isBefore(shiftStart)
? (breakStart.isAfter(shiftStart) || breakStart.isBefore(shiftEnd)) &&
(breakEnd.isAfter(shiftStart) || breakEnd.isBefore(shiftEnd))
: breakStart.isBetween(shiftStart, shiftEnd, null, '[]') &&
breakEnd.isBetween(shiftStart, shiftEnd, null, '[]');
if (!isValidBreak) {
setMessageType('error');
setMessageData(`Break "${breakItem.BreakName}" is not within shift time range`);
return;
}
}
let postData = {
AppId: getSession('AppId'),
BranchId: getSession('BranchId'),
CompId: getSession('CompId'),
ShiftName: values.ShiftName,
StartTime: fromTime,
EndTime: toTime,
GracePeriod: values.GracePeriod,
BreakDtl: Tabledata.map((item) => ({
BreakName: item.BreakName,
BreakStartTime: item.BreakStartTime,
BreakEndTime: item.BreakEndTime,
BreakId: item.BreakId ? item.BreakId : '',
})),
CreatedBy: getSession('UserId'),
};
console.log(postData, 'postDatapostDatapostDatapostDatapostData');
let response = {};
if (formType === 'add') {
response = await dispatch(postshift(postData)).unwrap();
} else if (formType === 'edit') {
if (editstate) {
postData['UpdatedBy'] = getSession('UserId');
postData['ShiftId'] = editstate?.ShiftId;
}
response = await dispatch(putshift(postData)).unwrap();
}
if (response?.data?.statusCode == 1) {
navigate(`${subDirectory}setting/shift-master/`, {
state: {
Notiffy: {
messageType: 'success',
messageData: response?.data?.response,
},
},
});
} else {
setMessageType('error');
setMessageData(response?.data?.response);
}
};
const validateTime = (rule, value, callback) => {
// if either value is not set yet, bail out (required rule will show message)
if (!fromTime || !toTime) {
return callback();
}
// parse safely (allow formats like "HH:mm" or "HH:mm:ss")
const fromParts = (fromTime || '').split(':').map(s => parseInt(s, 10) || 0);
const toParts = (toTime || '').split(':').map(s => parseInt(s, 10) || 0);
const [fromHour = 0, fromMinute = 0] = fromParts;
const [toHour = 0, toMinute = 0] = toParts;
const fromTotalMinutes = fromHour * 60 + fromMinute;
const toTotalMinutes = toHour * 60 + toMinute;
const minutesInDay = 24 * 60;
let timeDifference = toTotalMinutes - fromTotalMinutes;
if (timeDifference < 0) {
timeDifference = minutesInDay - fromTotalMinutes + toTotalMinutes;
}
const maxAllowedInterval = 12 * 60;
if (timeDifference <= maxAllowedInterval) {
return callback();
} else {
return callback('Shift should not exceed 12 hours');
}
};
const validateBreakTime1 = (rule, value, callback) => {
if (!value) return callback();
const brakeFromTime = moment(BrakeFromTime, 'HH:mm');
const startTime = moment(fromTime, 'HH:mm');
const endTime = moment(toTime, 'HH:mm');
if (endTime.isBefore(startTime)) {
// Handle shifts that span across midnight
if (brakeFromTime.isAfter(startTime) || brakeFromTime.isBefore(endTime)) {
callback(); // Valid time interval
} else {
callback(
'Break Start Time should be between Shift Start Time and Shift End Time'
);
}
} else {
if (
brakeFromTime.isBetween(startTime, endTime, null, '[]') ||
brakeFromTime.isSame(startTime) ||
brakeFromTime.isSame(endTime)
) {
callback(); // Valid time interval
} else {
callback(
'Break Start Time should be between Shift Start Time and Shift End Time'
);
}
}
};
const validateBreakTime2 = (rule, value, callback) => {
if (!value) return callback();
const brakeToTime = moment(BrakeToTime, 'HH:mm');
const brakeFromTime = moment(BrakeFromTime, 'HH:mm');
const startTime = moment(fromTime, 'HH:mm');
const endTime = moment(toTime, 'HH:mm');
if (endTime.isBefore(startTime)) {
// Handle shifts that span across midnight
if (brakeToTime.isAfter(startTime) || brakeToTime.isBefore(endTime)) {
// Check if BrakeToTime is always greater than BrakeFromTime
if (brakeToTime.isAfter(brakeFromTime)) {
callback(); // Valid time interval
} else {
callback('Break End Time should be greater than Break Start Time');
}
} else {
callback(
'Break End Time should be between Shift Start Time and Shift End Time'
);
}
} else {
if (
brakeToTime.isBetween(startTime, endTime, null, '[]') ||
brakeToTime.isSame(startTime) ||
brakeToTime.isSame(endTime)
) {
// Check if BrakeToTime is always greater than BrakeFromTime
if (brakeToTime.isAfter(brakeFromTime)) {
callback(); // Valid time interval
} else {
callback('Break End Time should be greater than Break Start Time');
}
} else {
callback(
'Break End Time should be between Shift Start Time and Shift End Time'
);
}
}
};
const onFromTimeChange = (time, timeString) => {
if (timeString) {
const fromTime = moment(timeString, ['h:mm a']).format('HH:mm:ss');
setToTime(null);
setFromTime(fromTime);
} else {
setFromTime(null);
}
};
const onToTimeChange = (time, timeString) => {
if (timeString) {
const toTime = moment(timeString, ['h:mm a']).format('HH:mm:ss');
setToTime(toTime);
} else {
setToTime(null);
}
};
const onBrakeFromTimeChange = (time, timeString) => {
if (timeString) {
const FromBrakeTime = moment(timeString, ['h:mm a']).format('HH:mm:ss');
setBrakeFromTime(FromBrakeTime);
setBrakeToTime(null);
} else {
setBrakeFromTime(null);
}
};
const onBrakeToTimeChange = (time, timeString) => {
if (timeString) {
const toBrakeTime = moment(timeString, ['h:mm a']).format('HH:mm:ss');
setBrakeToTime(toBrakeTime);
} else {
setBrakeToTime(null);
}
};
const Columns = [
{
title: 'Sl.No',
key: 'sno',
align: 'center',
render: (text, object, index) => (
<a style={{ color: 'black' }}>{(page - 1) * 10 + index + 1}</a>
),
},
{
title: 'Break Name',
dataIndex: 'BreakName',
key: 'BreakName',
editable: true,
render: (text) => <a style={{ color: 'black' }}>{text}</a>,
filteredValue: [searchedText],
onFilter: (value, record) => {
return (
String(record.TableName)
?.toLowerCase()
?.includes(value?.toLowerCase()) ||
String(record.TableName)
?.toLowerCase()
?.includes(value?.toLowerCase()) ||
String(record.ChairCount)
?.toLowerCase()
?.includes(value?.toLowerCase()) ||
String(record.ChairLevelSer)
?.toLowerCase()
?.includes(value?.toLowerCase()) ||
String(record.DineInTypeName)
?.toLowerCase()
?.includes(value?.toLowerCase())
);
},
sorter: (a, b) => a?.BreakName?.length - b?.BreakName?.length,
sortOrder: sortedInfo.columnKey === 'BreakName' ? sortedInfo.order : null,
ellipsis: true,
},
{
title: 'Break Start',
dataIndex: 'BreakStartTime',
key: 'BreakStartTime',
},
{
title: 'Break End',
dataIndex: 'BreakEndTime',
key: 'BreakEndTime',
},
{
title: 'Duration (mins)',
key: 'Duration',
align: 'center',
render: (_, record) => {
const startTime = moment(record.BreakStartTime, 'HH:mm:ss');
const endTime = moment(record.BreakEndTime, 'HH:mm:ss');
const duration = endTime.diff(startTime, 'minutes');
return duration > 0 ? duration : 0;
},
},
{
title: 'Action',
key: 'Action',
dataIndex: 'Action',
align: 'center',
render: (_, record, index) => (
// Tabledata.length >= 1 ? (
<Space size="middle">
{/* {record.ActiveStatus === "A" ? ( */}
<a>
<EditFilled
style={{ color: '#1292EE' }}
onClick={
() =>
// ((UserType === 'Super Admin') ||
// (UserType === "Super Admin User" && SAAccessCommonMaster?.UpdateAccess === "Y") ||
// (UserType === 'Employee' && empData?.UpdateAccess === "Y") ||
// (UserType === 'Admin'))
// ?
actionsFormatter(record, index)
// : null
}
/>
</a>
{/* ) : (
""
)} */}
<a>
{/* {record.ActiveStatus === "A" ? ( */}
<DeleteFilled
style={{
color: '#FF4D4F',
}}
onClick={() => statusFormatter(record)}
/>
{/* ) : (
<ReloadOutlined
style={{
color: "#ff4d4f",
pointerEvents: "none"
}}
/>
)} */}
</a>
</Space>
),
// ) : null,
},
];
const handleadd = async (values) => {
try {
// Validate break time fields before adding
await formRef.current?.validateFields([
'BreakName',
'BreakStartTime',
'BreakEndTime'
]);
const Formdata = formRef.current?.getFieldsValue();
const alreadyExists = Tabledata.some(
(item) => item.BreakName === Formdata?.BreakName
);
if (BrakeFromTime && BrakeToTime && Formdata?.BreakName != undefined) {
if (!alreadyExists) {
setTabledata((prev) => {
const newIndex = prev.length + 1;
const datas = {
BreakName: Formdata?.BreakName,
BreakStartTime: BrakeFromTime,
BreakEndTime: BrakeToTime,
key: newIndex,
};
return [...prev, datas];
});
setBrakeFromTime();
setBrakeToTime();
formRef.current.resetFields([
'BreakEndTime',
'BreakStartTime',
'BreakName',
]);
} else {
setMessageType('error');
setMessageData('Break Time Already Exist');
}
} else {
setMessageType('error');
setMessageData('Add Break Time');
}
} catch (error) {
setMessageType('error');
setMessageData('Please fix validation errors before adding break time');
}
};
const handleEdit = async (values) => {
try {
// Validate break time fields before editing
await formRef.current?.validateFields([
'BreakName',
'BreakStartTime',
'BreakEndTime',
]);
const Formdata = formRef.current?.getFieldsValue();
const alreadyExists = Tabledata.some(
(item, idx) =>
item.BreakName === Formdata?.BreakName && idx !== editIndex
);
if (BrakeFromTime && BrakeToTime && Formdata?.BreakName !== undefined) {
if (!alreadyExists) {
const updatedArray = [...Tabledata];
updatedArray[editIndex] = {
...updatedArray[editIndex],
BreakName: Formdata?.BreakName,
BreakStartTime: BrakeFromTime,
BreakEndTime: BrakeToTime,
BreakId: BrakeId,
};
setTabledata(updatedArray);
setBrakeFromTime();
setBrakeToTime();
setEditDataval(false);
formRef.current.resetFields([
'BreakEndTime',
'BreakStartTime',
'BreakName',
]);
} else {
setMessageType('error');
setMessageData('Break Time Already Exists');
}
} else {
setMessageType('error');
setMessageData('Add Break Time');
}
} catch (error) {
setMessageType('error');
setMessageData('Please fix validation errors before editing break time');
}
};
const actionsFormatter = async (row, index, rowIndex) => {
setEditDataval(true);
setEditIndex(index);
console.log(row, rowIndex, 'row, rowIndexrow, rowIndex');
formRef.current?.setFieldsValue({
BreakName: row?.BreakName,
BreakStartTime: row?.BreakStartTime,
BreakEndTime: row?.BreakEndTime,
});
(setBrakeFromTime(row?.BreakStartTime), setBrakeToTime(row?.BreakEndTime));
setBrakeId(row?.BreakId);
};
const statusFormatter = (record, index) => {
let Deleteddata = Tabledata.filter(
(item) => item.BreakName != record?.BreakName
);
setTabledata(Deleteddata);
};
const onComplete = useCallback(() => {
setMessageData(null);
setMessageType(null);
}, []);
return (
<div className="shiftMasterForm-Master" style={{ fontFamily: 'Poppins' }}>
<div className="pageOverAll">
<div className="userPage">
<div className="userPageContent">
<Messages
messageType={messageType}
messageData={messageData}
onComplete={onComplete}
/>
<div className="formName">
<FormHeader title={'Shift'} />
</div>
<div className="formDiv">
<Form
ref={formRef}
className="formDivAnt"
onFinish={onFinish}
initialValues={{ ...editstate }}
>
<div className="formDivS">
<div className="inputForm">
<Form.Item
name="ShiftName"
rules={[
{
required: true,
pattern: /^(?!\s*$).+/,
message: 'Please Enter Shift Name',
},
{
validator: async (_, value) => {
await validateSafeInput(value);
if (value?.length > 20) {
return Promise.reject(
'Shift Name Max 20 characters allowed'
);
} else {
return Promise.resolve();
}
},
},
]}
>
<InputField
label={<label class="required">Shift Name</label>}
isOnChange={
formType == 'edit' || editDataval ? true : false
}
autocomplete="off"
/>
</Form.Item>
<Form.Item
name="StartTime"
rules={[
{
required: true,
message: 'Please Select Shift Start Time',
},
]}
>
<TimePickers
label={<label class="required">Start Time</label>}
isOnChange={true}
onChange={onFromTimeChange}
valueData={fromTime}
/>
</Form.Item>
<Form.Item
name="EndTime"
rules={[
{
required: true,
message: 'Please Select Shift End Time',
},
{ validator: validateTime },
]}
>
<TimePickers
label={<label class="required">End Time</label>}
isOnChange={true}
onChange={onToTimeChange}
valueData={toTime}
/>
</Form.Item>
<Form.Item
name="GracePeriod"
rules={[
{
required: true,
pattern: /^(?!\s*$).+/,
message: 'Please Enter Grace Period',
},
{
validator: async (_, value) => {
if (
value === undefined ||
value === null ||
value === ''
) {
return Promise.resolve(); // ✅ Allow empty value (optional field)
}
await validateSafeInput(value);
if (value <= 60 && value >= 0) {
return Promise.resolve();
} else if (!value) {
return Promise.reject(
'Please Enter Grace Period'
);
} else {
return Promise.reject(
'Please Enter below or equal to (60 mins)'
);
}
},
},
]}
>
<InputField
label={
<label class="required">Grace Period (Min)</label>
}
type="number"
autoComplete="off"
min={1}
max={60}
maxLength={2}
isOnChange={
formType == 'edit' || editDataval ? true : false
}
/>
</Form.Item>
<Collapse className="breakTimeSection">
<Collapse.Panel header="Break Time Configuration" key="1">
<div
style={{
display: 'flex',
gap: '16px',
alignItems: 'start',
flexWrap: 'wrap',
}}
>
<Form.Item
name="BreakName"
rules={[
{
validator: async (_, value) => {
await validateSafeInput(value);
if (value?.length > 20) {
return Promise.reject(
'Break Name Max 20 characters allowed'
);
} else {
return Promise.resolve();
}
},
},
]}
>
<InputField
label={<label class="required">Break Name</label>}
autocomplete="off"
isOnChange={
formType == 'edit' || editDataval ? true : false
}
/>
</Form.Item>
<Form.Item
name="BreakStartTime"
rules={[{ validator: validateBreakTime1 }]}
>
<TimePickers
label={
<label class="required">Break Start Time</label>
}
isOnChange={true}
onChange={onBrakeFromTimeChange}
valueData={BrakeFromTime}
/>
</Form.Item>
<Form.Item
name="BreakEndTime"
rules={[{ validator: validateBreakTime2 }]}
>
<TimePickers
label={
<label class="required">Break End Time</label>
}
isOnChange={true}
onChange={onBrakeToTimeChange}
valueData={BrakeToTime}
/>
</Form.Item>
<button
type="button"
className="break-add"
onClick={() => {
editDataval ? handleEdit() : handleadd();
}}
>
<FaPlus />
<p>Add Break</p>
</button>
</div>
</Collapse.Panel>
</Collapse>
<div
className="Quotation-tables Quotation-tablesShift"
style={{ maxwidth: '500px', overflowX: 'scroll' }}
>
<Table
bordered
// components={components}
// rowClassName="editable-row"
columns={Columns}
dataSource={Tabledata}
// pagination={handlePageChange}
// onChange={handleChange}
/>
</div>
</div>
</div>
<div className="submitButton">
<Buttons
buttonText="SUBMIT"
color="901D77"
icon={<ArrowRightOutlined />}
// handleSubmit={handleSubmit}
/>
</div>
</Form>
</div>
</div>
</div>
</div>
</div>
);
};
export default ShiftForm;