1715 lines
70 KiB
JavaScript
1715 lines
70 KiB
JavaScript
import {
|
|
useEffect, useState, Fragment
|
|
} from 'react';
|
|
import {
|
|
useLocation,
|
|
useNavigate
|
|
} from 'react-router-dom';
|
|
import axios from 'axios';
|
|
import {
|
|
EnvironmentOutlined,
|
|
EditTwoTone,
|
|
DeleteFilled,
|
|
NotificationOutlined,
|
|
CloseOutlined,
|
|
WarningOutlined
|
|
} from '@ant-design/icons';
|
|
var utc = require('dayjs/plugin/utc')
|
|
|
|
import MainCard from 'components/MainCard';
|
|
import Loader from 'components/Loader';
|
|
import dayjs from 'dayjs';
|
|
dayjs.extend(utc)
|
|
|
|
import { enqueueSnackbar } from 'notistack';
|
|
import {
|
|
MdLocalShipping,
|
|
MdHourglassEmpty,
|
|
MdCheckCircle,
|
|
MdCancel,
|
|
MdAccessTime,
|
|
MdHistoryToggleOff,
|
|
MdAssignmentTurnedIn,
|
|
MdEdit,
|
|
MdArrowBack,
|
|
MdReceiptLong,
|
|
MdDirectionsCar,
|
|
MdKeyboardArrowUp,
|
|
MdKeyboardArrowDown
|
|
} from 'react-icons/md';
|
|
|
|
import { Button } from '@astryxdesign/core/Button';
|
|
import { IconButton } from '@astryxdesign/core/IconButton';
|
|
import { Tooltip } from '@astryxdesign/core/Tooltip';
|
|
import { Stack } from '@astryxdesign/core/Stack';
|
|
import { Layout, LayoutHeader, LayoutContent, LayoutFooter } from '@astryxdesign/core/Layout';
|
|
import { Dialog as AstryxDialog } from '@astryxdesign/core/Dialog';
|
|
import { TextInput } from '@astryxdesign/core/TextInput';
|
|
import { CheckboxInput } from '@astryxdesign/core/CheckboxInput';
|
|
import { Skeleton } from '@astryxdesign/core/Skeleton';
|
|
import { Spinner } from '@astryxdesign/core/Spinner';
|
|
import { Banner } from '@astryxdesign/core/Banner';
|
|
import { Link as AstryxLink } from '@astryxdesign/core/Link';
|
|
|
|
import logger from 'utils/logger';
|
|
// ============================================================================
|
|
// Design tokens — shared with the rest of the redesigned operator pages.
|
|
// ============================================================================
|
|
const DT = {
|
|
radiusPill: 999,
|
|
radiusCard: 16,
|
|
shadowSoft: '0 14px 40px rgba(15, 23, 42, 0.10)',
|
|
shadowMd: '0 8px 24px rgba(15, 23, 42, 0.08)',
|
|
shadowPop: '0 18px 50px rgba(15, 23, 42, 0.18)',
|
|
textPrimary: '#0f172a',
|
|
textSecondary: '#64748b',
|
|
textMuted: '#94a3b8',
|
|
borderSubtle: '#e2e8f0',
|
|
divider: '#f1f5f9',
|
|
surface: '#ffffff',
|
|
surfaceAlt: '#f8fafc'
|
|
};
|
|
const dtA = (c, suffix) => `${c}${suffix}`;
|
|
const tint = (c) => dtA(c, '08');
|
|
const edge = (c) => dtA(c, '55');
|
|
const ring = (c) => dtA(c, '26');
|
|
|
|
const BRAND = '#C01227';
|
|
const BRAND_LIGHT = '#D25463';
|
|
|
|
// Legacy MUI palette names used throughout this page's chips/badges — mapped
|
|
// to real hex so the visual result matches the previous theme.palette.* colors.
|
|
const LEGACY_COLOR_MAP = {
|
|
primary: BRAND,
|
|
secondary: '#8c8c8c',
|
|
success: '#52c41a',
|
|
error: '#ff4d4f',
|
|
warning: '#faad14',
|
|
info: '#13c2c2',
|
|
default: DT.textMuted
|
|
};
|
|
const resolveColor = (c) => LEGACY_COLOR_MAP[c] || c || DT.textMuted;
|
|
|
|
// Semantic per-status palette — also drives StatusBadge.
|
|
const STATUS_META = {
|
|
pending: { label: 'Pending', color: '#f59e0b', icon: MdHourglassEmpty },
|
|
assigned: { label: 'Assigned', color: '#0ea5e9', icon: MdAssignmentTurnedIn },
|
|
confirmed: { label: 'Confirmed', color: '#10b981', icon: MdCheckCircle },
|
|
modified: { label: 'Modified', color: '#06b6d4', icon: MdHistoryToggleOff },
|
|
processing: { label: 'Processing', color: BRAND, icon: MdAccessTime },
|
|
active: { label: 'Active', color: '#8b5cf6', icon: MdLocalShipping },
|
|
closed: { label: 'Closed', color: '#06b6d4', icon: MdCheckCircle },
|
|
completed: { label: 'Completed', color: '#10b981', icon: MdCheckCircle },
|
|
cancelled: { label: 'Cancelled', color: '#ef4444', icon: MdCancel }
|
|
};
|
|
|
|
const StatusBadge = ({ status, size = 'md' }) => {
|
|
if (!status) return null;
|
|
const meta = STATUS_META[String(status).toLowerCase()] || {
|
|
label: status,
|
|
color: DT.textMuted,
|
|
icon: MdHistoryToggleOff
|
|
};
|
|
const Icon = meta.icon;
|
|
const px = size === 'lg' ? 10 : 8;
|
|
const py = size === 'lg' ? 4 : 3;
|
|
const fs = size === 'lg' ? 12 : 11;
|
|
return (
|
|
<span
|
|
style={{
|
|
display: 'inline-flex',
|
|
alignItems: 'center',
|
|
gap: 4,
|
|
padding: `${py}px ${px}px`,
|
|
borderRadius: 999,
|
|
backgroundColor: tint(meta.color),
|
|
border: `1px solid ${edge(meta.color)}`,
|
|
color: meta.color,
|
|
fontSize: fs,
|
|
fontWeight: 500,
|
|
whiteSpace: 'nowrap'
|
|
}}
|
|
>
|
|
<Icon size={12} /> {meta.label}
|
|
</span>
|
|
);
|
|
};
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// Txt — lightweight typography helper replacing MUI Typography variants.
|
|
// Keeps the pixel-level sizing this page relies on without adopting the full
|
|
// Astryx Text/Heading API for every single label on a very dense page.
|
|
// ----------------------------------------------------------------------------
|
|
const TYPOGRAPHY_STYLES = {
|
|
h3: { fontSize: '1.5rem', fontWeight: 500, lineHeight: 1.2 },
|
|
h4: { fontSize: '1.25rem', fontWeight: 500, lineHeight: 1.2 },
|
|
h5: { fontSize: '1.0625rem', fontWeight: 400, lineHeight: 1.3 },
|
|
subtitle1: { fontSize: '0.95rem', fontWeight: 400, lineHeight: 1.3 },
|
|
body1: { fontSize: '0.875rem', fontWeight: 400, lineHeight: 1.4 },
|
|
body2: { fontSize: '0.8125rem', fontWeight: 400, lineHeight: 1.4 },
|
|
caption: { fontSize: '0.75rem', fontWeight: 400, lineHeight: 1.4 }
|
|
};
|
|
const Txt = ({ variant = 'body1', color, align, style = {}, children, ...rest }) => {
|
|
const base = TYPOGRAPHY_STYLES[variant] || TYPOGRAPHY_STYLES.body1;
|
|
const resolvedColor = color === 'secondary' || color === 'textSecondary' ? DT.textSecondary : color || DT.textPrimary;
|
|
return (
|
|
<div
|
|
{...rest}
|
|
style={{
|
|
margin: 0,
|
|
color: resolvedColor,
|
|
textAlign: align,
|
|
...base,
|
|
...style
|
|
}}
|
|
>
|
|
{children}
|
|
</div>
|
|
);
|
|
};
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// Pill — lightweight chip helper replacing MUI Chip (Astryx has no Chip;
|
|
// Token's fixed color set doesn't cover the arbitrary legacy palette used
|
|
// here, so this mirrors the tint/edge pattern already used by StatusBadge).
|
|
// ----------------------------------------------------------------------------
|
|
const Pill = ({ label, color = 'default', variant = 'filled', size = 'medium', icon, style = {} }) => {
|
|
const c = resolveColor(color);
|
|
const isSmall = size === 'small';
|
|
const base = {
|
|
display: 'inline-flex',
|
|
alignItems: 'center',
|
|
gap: 4,
|
|
borderRadius: 999,
|
|
fontWeight: 400,
|
|
whiteSpace: 'nowrap',
|
|
fontSize: isSmall ? 11 : 12.5,
|
|
padding: isSmall ? '2px 8px' : '4px 10px',
|
|
lineHeight: 1.6
|
|
};
|
|
const variantStyle =
|
|
variant === 'outlined'
|
|
? { backgroundColor: '#fff', border: `1px solid ${edge(c)}`, color: c }
|
|
: variant === 'light'
|
|
? { backgroundColor: tint(c), border: `1px solid ${edge(c)}`, color: c }
|
|
: { backgroundColor: c, color: '#fff' };
|
|
return (
|
|
<span style={{ ...base, ...variantStyle, ...style }}>
|
|
{icon}
|
|
{label}
|
|
</span>
|
|
);
|
|
};
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// CircleAvatar — plain circular avatar replacing MUI Avatar (icon avatars &
|
|
// initials avatars). Matches the AccentAvatar pattern used elsewhere.
|
|
// ----------------------------------------------------------------------------
|
|
const CircleAvatar = ({ size = 32, bg = DT.textMuted, color = '#fff', style = {}, children }) => (
|
|
<div
|
|
style={{
|
|
width: size,
|
|
height: size,
|
|
borderRadius: '50%',
|
|
backgroundColor: bg,
|
|
color,
|
|
display: 'inline-flex',
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
flexShrink: 0,
|
|
fontSize: Math.max(11, Math.round(size * 0.42)),
|
|
fontWeight: 400,
|
|
...style
|
|
}}
|
|
>
|
|
{children}
|
|
</div>
|
|
);
|
|
|
|
const Details = () => {
|
|
// const [searchParams] = useSearchParams();
|
|
const [orderarr, setOrderarr] = useState([]);
|
|
const [loading, setLoading] = useState(true);
|
|
const location = useLocation();
|
|
const [orderid, setOrderid] = useState('');
|
|
const [eventlocation, setEventlocation] = useState('');
|
|
const [orderdate, setOrderdate] = useState('');
|
|
const [eventname, setEventname] = useState('');
|
|
const [open, setOpen] = useState(false);
|
|
const [clientname, setClientname] = useState('');
|
|
// const [duedate, setDuedate] = useState('');
|
|
const [tenantaddress, setTenantaddress] = useState('');
|
|
const [dialogopen, setDialogopen] = useState(false);
|
|
const [orderstatus, setOrderstatus] = useState('');
|
|
const [currentrole, setCurrentrole] = useState('');
|
|
const [taxamount, setTaxamount] = useState('');
|
|
const [subtotal, setSubtotal] = useState('');
|
|
const [grandtotal, setGrandtotal] = useState('');
|
|
const [venuetype, setVenuetype] = useState('');
|
|
const [orderaddons, setOrderaddons] = useState([]);
|
|
const [otherinstructions, setOtherinstructions] = useState('');
|
|
const [cancelleddate, setCancelleddate] = useState('');
|
|
const [stafflist, setStafflist] = useState([]);
|
|
const [staffarr, setStaffarr] = useState([]);
|
|
const [orderheaderid, setOrderheaderid] = useState('');
|
|
const [tenantid, setTenantid] = useState('');
|
|
const [starttime, setStarttime] = useState('');
|
|
const [endtime, setEndtime] = useState('');
|
|
// const [orderstatus,setOrderstatus]=useStatus('');
|
|
const [pendingtime, setPendingtime] = useState('');
|
|
// const [processdate,setProcessdate]=useState('');
|
|
const [orderdetailid, setOrderdetailid] = useState('');
|
|
const [productid, setProductid] = useState('');
|
|
const [categoryarr, setcategoryarr] = useState([])
|
|
const [currentshiftobj, setCurrentshiftobj] = useState({
|
|
shifts: 0,
|
|
assigned: 0,
|
|
remaining: 0,
|
|
shiftid: 0,
|
|
price: 0
|
|
});
|
|
const [tabstatus, setTabstatus] = useState(0)
|
|
const { state } = useLocation();
|
|
const [assignedpendingcount, setAssignedpendingcount] = useState('');
|
|
// const [, forceUpdate] = useReducer(x => x + 1, 0);
|
|
// const dispatch = useDispatch();
|
|
const navigate = useNavigate();
|
|
const [expandopen, setExpandopen] = useState(['', '']);
|
|
const [startdate, setStartdate] = useState('')
|
|
const [invoiceeligible, setInvoiceeligible] = useState(false)
|
|
|
|
useEffect(() => {
|
|
logger.info("categoryarr")
|
|
|
|
logger.info(orderarr, eventlocation, venuetype, starttime, endtime);
|
|
// forceUpdate();
|
|
}, [categoryarr])
|
|
|
|
// const navigate = useNavigate();
|
|
useEffect(() => {
|
|
if (state) {
|
|
setOrderheaderid(state.orderheaderid);
|
|
setTenantid(state.tenantid);
|
|
}
|
|
logger.info(state)
|
|
// fetchroleslist(1)
|
|
logger.info(Date.now())
|
|
}, [])
|
|
useEffect(() => {
|
|
if (state) {
|
|
setOrderheaderid(state.orderheaderid );
|
|
setTenantid(state.tenantid );
|
|
}
|
|
logger.info(state)
|
|
// fetchroleslist(1)
|
|
logger.info(Date.now())
|
|
}, [
|
|
state.orderheaderid, state.tenantid
|
|
])
|
|
|
|
// const fetchorderdetails = async () => {
|
|
// setLoading(true);
|
|
// await axios
|
|
// .get(`${process.env.REACT_APP_URL}/orders/orderbyid/?orderheaderid=${orderheaderid}`)
|
|
|
|
// .then((res) => {
|
|
// logger.info(res);
|
|
|
|
// setLoading(false);
|
|
// })
|
|
// .catch((err) => {
|
|
// logger.error(err);
|
|
// setLoading(false);
|
|
// });
|
|
|
|
// };
|
|
|
|
const fetchorderaddons = async () => {
|
|
setLoading(true);
|
|
await axios
|
|
.get(`${process.env.REACT_APP_URL2}/orders/getordersbystatus?tenantid=${tenantid}`)
|
|
|
|
.then((res) => {
|
|
logger.info(res);
|
|
let result = res.data.Details.find((res1) => res1.orderheaderid == orderheaderid)
|
|
// orderheaderid
|
|
logger.info("result")
|
|
|
|
logger.info(result)
|
|
setOrderaddons(result.orderaddons);
|
|
setVenuetype(result.venuetype)
|
|
setOtherinstructions(result.remarks)
|
|
setStartdate(result.startdate)
|
|
|
|
|
|
|
|
setClientname(result.tenantname);
|
|
setEventlocation('');
|
|
setTenantaddress(result.tenantaddress);
|
|
setEventname(result.eventname);
|
|
|
|
|
|
|
|
setOrderdate(dayjs(result.orderdate).format('MM/DD/YYYY') || '');
|
|
setOrderid(result.orderid);
|
|
setOrderstatus(result.orderstatus);
|
|
setTaxamount(result.taxamount);
|
|
setSubtotal(result.orderamount)
|
|
setGrandtotal(result.ordervalue);
|
|
setCancelleddate(dayjs(result.cancelled).format('MM/DD/YYYY') || '');
|
|
|
|
// if (result.orderstatus === 'pending') {
|
|
setPendingtime(result.pending)
|
|
// }
|
|
|
|
|
|
setLoading(false);
|
|
})
|
|
.catch((err) => {
|
|
logger.error(err);
|
|
setLoading(false);
|
|
});
|
|
}
|
|
|
|
|
|
|
|
const fetchorderattires = async () => {
|
|
setLoading(true);
|
|
await axios
|
|
.get(`${process.env.REACT_APP_URL}/orders/getorderdetails?orderheaderid=${orderheaderid}`)
|
|
|
|
.then((res) => {
|
|
logger.info('res');
|
|
logger.info(res);
|
|
setOrderarr(res.data.details || []);
|
|
// let result = res.data.Details.find((res1) => res1.orderheaderid == searchParams.get('id'))
|
|
// orderheaderid
|
|
// logger.info(result)
|
|
// setOrderaddons(result.orderaddons);
|
|
// setVenuetype(result.venuetype)
|
|
// setOtherinstructions(result.remarks)
|
|
// logger.info("res");
|
|
// let result = _.chain(res.data.Details)
|
|
|
|
// .groupBy("shiftid")
|
|
|
|
// .map((value, key) => ({shiftid:key, locationaddress: value[0].locationaddress, roles: value }))
|
|
// .value()
|
|
|
|
// setcategoryarr(result);
|
|
logger.info('categoryarr');
|
|
setcategoryarr(res.data.details)
|
|
logger.info(res.data.details);
|
|
|
|
setLoading(false);
|
|
})
|
|
.catch((err) => {
|
|
logger.error(err);
|
|
setLoading(false);
|
|
});
|
|
}
|
|
|
|
const fetchroleslist = async (cid, starttime1, endtime1, hid, sid) => {
|
|
let fromdate = dayjs(starttime1).subtract(59, 'minutes')
|
|
.format('YYYY-MM-DD HH:mm:ss');
|
|
// let todate = dayjs(endtime1).utc().format('YYYY-MM-DD HH:mm:ss');
|
|
// let fromdate = dayjs(starttime1).utc().format('YYYY-MM-DD HH:mm:ss');
|
|
let todate = dayjs(endtime1)
|
|
.format('YYYY-MM-DD HH:mm:ss');
|
|
let url1;
|
|
if (starttime1) {
|
|
url1 = `${process.env.REACT_APP_URL2}/staffs/pools/getstaffs/?categoryid=${cid}&headerid=${hid}&shiftid=${sid}&starttime=${fromdate}&endtime=${todate}`;
|
|
} else {
|
|
url1 = `${process.env.REACT_APP_URL2}/staffs/pools/getstaffs/?categoryid=${cid}&headerid=${hid}&shiftid=${sid}`
|
|
}
|
|
setLoading(true);
|
|
setStafflist([])
|
|
await axios
|
|
// .get(`${process.env.REACT_APP_URL2}/staffs/pools/getstaffs/?categoryid=${cid}&headerid=${hid}&starttime=${fromdate}&endtime=${todate}`)
|
|
.get(url1)
|
|
.then((res) => {
|
|
logger.info('rolelist');
|
|
logger.info(res);
|
|
// logger.info(fromdate, todate)
|
|
// logger.info(dayjs(starttime1).format('YYYY-MM-DD HH:mm:ss'))
|
|
// logger.info(dayjs(endtime1).format('HH:mm:ss'))
|
|
|
|
|
|
setStafflist(res.data.Details || [])
|
|
// let result = res.data.Details.find((res1) => res1.orderheaderid == searchParams.get('id'))
|
|
// orderheaderid
|
|
// logger.info(result)
|
|
// setOrderaddons(result.orderaddons);
|
|
// setVenuetype(result.venuetype)
|
|
// setOtherinstructions(result.remarks)
|
|
setLoading(false);
|
|
})
|
|
.catch((err) => {
|
|
logger.error(err);
|
|
setLoading(false);
|
|
});
|
|
}
|
|
|
|
|
|
const fetchstafflist = async (odid) => {
|
|
setLoading(true)
|
|
try {
|
|
// await axios.get(`${process.env.REACT_APP_URL}/orders/orderanalytics?orderdate=${chosendate}`)
|
|
await axios.get(`${process.env.REACT_APP_URL2}/orders/getassignedinfo?orderdetailid=${odid}`)
|
|
|
|
.then((res) => {
|
|
logger.info(res)
|
|
if (res.data.status) {
|
|
setStafflist(res.data.Details)
|
|
}
|
|
|
|
setLoading(false)
|
|
}).catch((err) => {
|
|
logger.error(err)
|
|
setLoading(false)
|
|
})
|
|
|
|
} catch (err) {
|
|
logger.error(err);
|
|
setLoading(false)
|
|
}
|
|
|
|
}
|
|
const cancelorder = async () => {
|
|
|
|
await axios.put(`${process.env.REACT_APP_URL2}/orders/cancel`, {
|
|
|
|
// "Orderheaderid": parseInt(orderheaderid),
|
|
// "Tenantid": parseInt(tenantid),
|
|
// "Orderstatus": "cancelled",
|
|
// "Currentdatetime": dayjs().format('YYYY-MM-DD HH:mm:ss'),
|
|
// "Cod": false,
|
|
// "Remarks": "",
|
|
"orderheaderid": parseInt(orderheaderid),
|
|
// "orderdetailid":78,
|
|
// "shiftid":788,
|
|
"orderstatus": "cancelled",
|
|
"cancelled": dayjs().format('YYYY-MM-DD HH:mm:ss'),
|
|
"unserviceable": (invoiceeligible) ? 0 : 1,
|
|
|
|
})
|
|
.then((res) => {
|
|
logger.info(res)
|
|
if (res.data.status) {
|
|
if (orderheaderid && tenantid) {
|
|
// fetchorderdetails();
|
|
fetchorderaddons();
|
|
fetchorderattires();
|
|
}
|
|
}
|
|
|
|
}).catch((err) => {
|
|
logger.error(err)
|
|
})
|
|
|
|
}
|
|
|
|
const unassign = async (val) => {
|
|
|
|
let obj = {
|
|
orderheaderid: orderheaderid,
|
|
orderprocessid: val.orderprocessid,
|
|
orderdetailid: val.orderdetailid,
|
|
orderstatus: "pending",
|
|
pending: dayjs().format('YYYY-MM-DD HH:mm:ss'),
|
|
// processing:0,
|
|
// cancelled:0,
|
|
// completed:0,
|
|
// accepted:0,
|
|
status: 1
|
|
}
|
|
logger.info(obj)
|
|
|
|
await axios.put(`${process.env.REACT_APP_URL2}/orders/updateprocessstatus`, obj)
|
|
.then((res) => {
|
|
logger.info(res)
|
|
if (res.data.message === "Successful") {
|
|
// if (orderheaderid && tenantid) {
|
|
enqueueSnackbar('Role unassigned successfully', {
|
|
variant: 'success', anchorOrigin: { vertical: 'top', horizontal: 'right' },
|
|
autoHideDuration: 2000
|
|
})
|
|
if (currentshiftobj.assigned > currentshiftobj.shifts) {
|
|
sendunassignnotification(val)
|
|
}
|
|
|
|
|
|
|
|
fetchorderaddons();
|
|
fetchorderattires();
|
|
setOpen(false);
|
|
fetchassignedcount();
|
|
dialogclose()
|
|
setTimeout(() => {
|
|
fetchassignedcount();
|
|
|
|
}, 2000)
|
|
}
|
|
|
|
}).catch((err) => {
|
|
logger.error(err)
|
|
})
|
|
|
|
}
|
|
|
|
const sendunassignnotification = (val) => {
|
|
logger.info(val)
|
|
let data2;
|
|
let tokenarr = [val.userfcmtoken]
|
|
let arr1 = [{
|
|
"notificationid": 0,
|
|
"notificationdate": dayjs().format('YYYY-MM-DD HH:mm:ss'),
|
|
"Title": "Staff Un-Asigned",
|
|
"message": `${val.firstname} has been Un-assigned to the order ${orderid}`,
|
|
"configid": 2,
|
|
"tenantid": tenantid,
|
|
"orderheaderid": orderheaderid,
|
|
"orderprocessid": val.orderprocessid,
|
|
"shiftid": val.shiftid,
|
|
"userid": val.userid,
|
|
"orderid": orderid,
|
|
"success": 0,
|
|
"orderstatus": 'assigned',
|
|
"processing": dayjs().format('YYYY-MM-DD HH:mm:ss'),
|
|
"notifytype": 2,
|
|
"notifyreason": 'Staff Un-Assigned'
|
|
// "sound": "ring",
|
|
// "click_action": "FLUTTER_NOTIFICATION_CLICK",
|
|
// "firstname": val.firstname
|
|
|
|
}];
|
|
|
|
|
|
|
|
|
|
data2 = {
|
|
"Title": "Staff Un-Asigned",
|
|
"message": `A Staff has been Un-assigned to the order ${orderid}`,
|
|
"tenantid": tenantid,
|
|
"orderheaderid": orderheaderid,
|
|
"orderid": orderid,
|
|
"configid": 2,
|
|
// "click_action": "FLUTTER_NOTIFICATION_CLICK"
|
|
}
|
|
let fcmmodel = {
|
|
"priority": "high",
|
|
"registration_ids": tokenarr,
|
|
"data": data2,
|
|
"notification": {
|
|
"body": `An order has been Un-assigned ${orderid}`,
|
|
"title": "Legendary",
|
|
"sound": "ring",
|
|
"content_available": true,
|
|
"click_action": "FLUTTER_NOTIFICATION_CLICK"
|
|
}
|
|
}
|
|
let grpnotifyobj = {
|
|
"notifications": arr1,
|
|
"fcmmodel": fcmmodel
|
|
}
|
|
logger.info("grpnotifyobj unassign")
|
|
logger.info(grpnotifyobj)
|
|
sendgroupnotification(grpnotifyobj)
|
|
}
|
|
|
|
useEffect(() => {
|
|
logger.info(orderheaderid);
|
|
if (orderheaderid && tenantid) {
|
|
// fetchorderdetails();
|
|
fetchorderaddons();
|
|
fetchorderattires();
|
|
fetchassignedcount()
|
|
// fetchuserdetails();
|
|
logger.info(location.state || '');
|
|
// setOrderid(location.state.orderid || '');
|
|
// setEventlocation(location.state.eventlocation || '');
|
|
// setEventlocation(address || []);
|
|
|
|
// setOrderdate(dayjs(location.state.orderdate.substring(0, 10)).format('MM/DD/YYYY') || '');
|
|
// setDuedate(dayjs(location.state.orderdate.substring(0, 10)).format('MM/DD/YYYY') || '')
|
|
// setEventname(location.state.eventname || '');
|
|
// setClientname(location.state.tenantname || '')
|
|
} else {
|
|
setLoading(false);
|
|
}
|
|
// fetchorderdetails();
|
|
logger.info(orderheaderid, tenantid)
|
|
}, [orderheaderid, tenantid, assignedpendingcount]);
|
|
|
|
const handleClose = () => {
|
|
setOpen(false);
|
|
};
|
|
const dialogclose = () => {
|
|
setDialogopen(false);
|
|
setStaffarr([]);
|
|
setExpandopen(['', ''])
|
|
};
|
|
useEffect(() => {
|
|
logger.info(currentshiftobj)
|
|
})
|
|
|
|
|
|
const assignok = async () => {
|
|
let arr = []
|
|
let arr1 = [];
|
|
staffarr.map((val) => {
|
|
arr.push({
|
|
"orderprocessid": 0,
|
|
// "processdate": `${dayjs(new Date()).format('YYYY-MM-DD')} ${dayjs(new Date()).format('HH:mm:ss')}`, //current date
|
|
"processdate": dayjs().format('YYYY-MM-DD HH:mm:ss'), //current date
|
|
"tenantid": tenantid,
|
|
"orderheaderid": orderheaderid,
|
|
"orderdetailid": val.orderdetailid,////
|
|
"productid": val.productid,/////
|
|
"userid": val.userid,
|
|
"orderstatus": "pending",
|
|
"pending": `${dayjs(pendingtime).format('YYYY-MM-DD')} ${dayjs(pendingtime).format('HH:mm:ss')}`, // if pending
|
|
"starttime": `${dayjs(starttime).format('YYYY-MM-DD')} ${dayjs(starttime).format('HH:mm:ss')}`,
|
|
"endtime": `${dayjs(endtime).format('YYYY-MM-DD')} ${dayjs(endtime).format('HH:mm:ss')}`,
|
|
"appuserid": parseInt(localStorage.getItem("appuserid")), //loginuserid
|
|
"shiftid": val.shiftid,
|
|
"userrate": val.userrate,
|
|
"productrate": val.productrate
|
|
})
|
|
|
|
staffarr.map((val) => {
|
|
arr1.push({
|
|
"notificationid": 0,
|
|
"notificationdate": dayjs().format('YYYY-MM-DD HH:mm:ss'),
|
|
"Title": "Staff Asigned",
|
|
"message": `A Staff has been assigned to the order ${orderid}`,
|
|
"configid": 2,
|
|
"tenantid": tenantid,
|
|
"orderheaderid": orderheaderid,
|
|
"orderprocessid": 0,
|
|
"shiftid": val.shiftid,
|
|
"userid": val.userid,
|
|
"orderid": orderid,
|
|
"sound": "ring",
|
|
"click_action": "FLUTTER_NOTIFICATION_CLICK",
|
|
"firstname": val.firstname
|
|
})
|
|
|
|
})
|
|
|
|
})
|
|
logger.info('arr')
|
|
logger.info(arr)
|
|
|
|
await axios.post(`${process.env.REACT_APP_URL2}/orders/createorderprocess`, arr)
|
|
.then((res) => {
|
|
logger.info(res)
|
|
if (res.data.message === "Successfully created") {
|
|
// if (orderheaderid && tenantid) {
|
|
// fetchorderdetails();
|
|
// fetchorderaddons();
|
|
// fetchorderattires();
|
|
// }
|
|
|
|
|
|
enqueueSnackbar('Roles assigned successfully', {
|
|
variant: 'success', anchorOrigin: { vertical: 'top', horizontal: 'right' },
|
|
autoHideDuration: 2000
|
|
})
|
|
// fetchroleslist(productid, '', '', orderheaderid, arr1[0].shiftid);
|
|
logger.info(productid, '', '', orderheaderid, arr1[0].shiftid)
|
|
|
|
|
|
|
|
|
|
// arr1.map((val2) => {
|
|
// notificationpush(val2,val2.Title);
|
|
// })
|
|
setDialogopen(false);
|
|
fetchorderattires();
|
|
fetchassignedcount();
|
|
}
|
|
|
|
}).catch((err) => {
|
|
logger.error(err)
|
|
})
|
|
logger.info(arr)
|
|
}
|
|
|
|
const notificationpush = async (val) => {
|
|
let fcmtoken = val.userfcmtoken
|
|
let obj1 = {
|
|
"notificationid": 0,
|
|
"notificationdate": dayjs().format('YYYY-MM-DD HH:mm:ss'),
|
|
"Title": "Staff Asigned",
|
|
"message": `A Staff has been assigned to the order ${orderid}`,
|
|
"configid": 2,
|
|
"tenantid": tenantid,
|
|
"orderheaderid": orderheaderid,
|
|
"orderprocessid": val.orderprocessid,
|
|
"shiftid": val.shiftid,
|
|
"userid": val.userid,
|
|
"orderid": orderid,
|
|
"sound": "ring",
|
|
"click_action": "FLUTTER_NOTIFICATION_CLICK",
|
|
// "firstname": val.firstname
|
|
|
|
};
|
|
logger.info(obj1, fcmtoken)
|
|
await axios.post(`${process.env.REACT_APP_URL2}/utils/notification/send`,
|
|
{
|
|
"priority": "high",
|
|
"registration_ids": [fcmtoken],
|
|
"data": obj1,
|
|
"notification": {
|
|
"body": `A Staff has been assigned to ${orderid}`,
|
|
"title": "Legendary",
|
|
"sound": "ring",
|
|
"content_available": true
|
|
}
|
|
}
|
|
, {
|
|
headers: {
|
|
'Authorization': `Bearer ${process.env.REACT_APP_STAFF_TOKEN}`
|
|
}
|
|
}
|
|
)
|
|
.then((res) => {
|
|
logger.info(res)
|
|
// if(res.data.status){
|
|
enqueueSnackbar('Notification sent successfully', {
|
|
variant: 'success', anchorOrigin: { vertical: 'top', horizontal: 'right' },
|
|
autoHideDuration: 2000
|
|
})
|
|
// }
|
|
})
|
|
.catch((err) => {
|
|
logger.error(err)
|
|
})
|
|
|
|
}
|
|
|
|
const fetchassignedstaffs = async () => {
|
|
|
|
// logger.info(obj1)
|
|
await axios.get(`${process.env.REACT_APP_URL2}/orders/getnotificationusers?orderheaderid=${orderheaderid}`)
|
|
.then((res) => {
|
|
if (res.data.status) {
|
|
let arr1 = [];
|
|
let data2;
|
|
let tokenarr = []
|
|
logger.info(res)
|
|
|
|
res.data.details.map((val) => {
|
|
arr1.push({
|
|
"notificationid": 0,
|
|
"notificationdate": dayjs().format('YYYY-MM-DD HH:mm:ss'),
|
|
"Title": "Staff Asigned",
|
|
"message": `${val.staffname} has been assigned to the order ${orderid}`,
|
|
"configid": 2,
|
|
"tenantid": tenantid,
|
|
"orderheaderid": orderheaderid,
|
|
"orderprocessid": val.orderprocessid,
|
|
"shiftid": val.shiftid,
|
|
"userid": val.userid,
|
|
"orderid": orderid,
|
|
"success": 0,
|
|
"orderstatus": 'assigned',
|
|
"processing": dayjs().format('YYYY-MM-DD HH:mm:ss'),
|
|
"notifytype": 1,
|
|
"notifyreason": 'Staff Assigned'
|
|
// "sound": "ring",
|
|
// "click_action": "FLUTTER_NOTIFICATION_CLICK",
|
|
// "firstname": val.firstname
|
|
|
|
});
|
|
tokenarr.push(val.userfcmtoken);
|
|
|
|
})
|
|
|
|
|
|
|
|
data2 = {
|
|
"Title": "Staff Asigned",
|
|
"message": `A Staff has been assigned to the order ${orderid}`,
|
|
"tenantid": tenantid,
|
|
"orderheaderid": orderheaderid,
|
|
"orderid": orderid,
|
|
"configid": 2,
|
|
// "click_action": "FLUTTER_NOTIFICATION_CLICK"
|
|
}
|
|
let fcmmodel = {
|
|
"priority": "high",
|
|
"registration_ids": tokenarr,
|
|
"data": data2,
|
|
"notification": {
|
|
"body": `An order has been assigned ${orderid}`,
|
|
"title": "Legendary",
|
|
"sound": "ring",
|
|
"content_available": true,
|
|
"click_action": "FLUTTER_NOTIFICATION_CLICK"
|
|
}
|
|
}
|
|
let grpnotifyobj = {
|
|
"notifications": arr1,
|
|
"fcmmodel": fcmmodel
|
|
}
|
|
logger.info("grpnotifyobj")
|
|
logger.info(grpnotifyobj)
|
|
sendgroupnotification(grpnotifyobj)
|
|
// notificationpush(obj2, val.userfcmtoken);
|
|
// notificationpush(arr1,tokenarr);
|
|
// enqueueSnackbar('Notifications sent successfully', {
|
|
// variant: 'success', anchorOrigin: { vertical: 'top', horizontal: 'right' },
|
|
// autoHideDuration: 2000
|
|
// })
|
|
}
|
|
})
|
|
.catch((err) => {
|
|
logger.error(err)
|
|
})
|
|
|
|
}
|
|
|
|
const sendgroupnotification = async (obj1) => {
|
|
|
|
logger.info(obj1)
|
|
await axios.post(`${process.env.REACT_APP_URL2}/utils/notification/sendall`, obj1, {
|
|
headers: {
|
|
'Authorization': `Bearer ${process.env.REACT_APP_STAFF_TOKEN}`
|
|
}
|
|
}
|
|
)
|
|
.then((res) => {
|
|
logger.info(res)
|
|
if (res.data.status) {
|
|
// updateorderstatus();
|
|
enqueueSnackbar('Notification sent successfully', {
|
|
variant: 'success', anchorOrigin: { vertical: 'top', horizontal: 'right' },
|
|
autoHideDuration: 2000
|
|
})
|
|
fetchorderaddons();
|
|
}
|
|
})
|
|
.catch((err) => {
|
|
logger.error(err)
|
|
})
|
|
|
|
}
|
|
|
|
// const updateorderstatus = async () => {
|
|
|
|
// await axios.put(`${process.env.REACT_APP_URL2}/orders/updateorderstatus`,{
|
|
// "orderheaderid":orderheaderid,
|
|
// "tenantid":tenantid,
|
|
// "orderstatus":"processing",
|
|
// "pending":"",
|
|
// "processing":dayjs().format('YYYY-MM-DD HH:mm:ss'),
|
|
// "completed":""
|
|
// })
|
|
// .then((res) => {
|
|
// logger.info(res)
|
|
// fetchorderdetails();
|
|
// fetchorderaddons();
|
|
// fetchorderattires();
|
|
|
|
// })
|
|
// .catch((err) => {
|
|
// logger.error(err)
|
|
// fetchorderdetails();
|
|
// fetchorderaddons();
|
|
// fetchorderattires();
|
|
// })
|
|
|
|
// }
|
|
|
|
const fetchassignedcount = async () => {
|
|
|
|
// logger.info(obj1)
|
|
await axios.get(`${process.env.REACT_APP_URL2}/orders/getorderstatuscount?orderheaderid=${orderheaderid}`)
|
|
.then((res) => {
|
|
if (res.data.status) {
|
|
// let arr1=[];
|
|
logger.info(res)
|
|
setAssignedpendingcount(res.data.pendingcount)
|
|
fetchorderaddons()
|
|
} else {
|
|
setAssignedpendingcount(res.data.pendingcount)
|
|
fetchorderaddons()
|
|
}
|
|
})
|
|
.catch((err) => {
|
|
logger.error(err)
|
|
})
|
|
|
|
}
|
|
|
|
function AlertCustomerDelete({ open, handleClose }) {
|
|
const [deletepassword, setDeletepassword] = useState('');
|
|
const isMatch = deletepassword === orderid.slice(4);
|
|
|
|
return (
|
|
<AstryxDialog
|
|
isOpen={open}
|
|
onOpenChange={(o) => !o && handleClose(false)}
|
|
purpose="form"
|
|
width={420}
|
|
>
|
|
<Layout
|
|
header={
|
|
<LayoutHeader hasDivider padding={0}>
|
|
<div
|
|
style={{
|
|
width: '100%',
|
|
padding: '20px',
|
|
background: `linear-gradient(135deg, ${tint('#ef4444')} 0%, ${tint('#f59e0b')} 100%)`
|
|
}}
|
|
>
|
|
<Stack direction="horizontal" vAlign="center" gap={1.5}>
|
|
<CircleAvatar size={40} bg="#ef4444">
|
|
<DeleteFilled />
|
|
</CircleAvatar>
|
|
<Stack direction="vertical" gap={0}>
|
|
<Txt variant="h5" style={{ fontWeight: 500 }}>Cancel Order</Txt>
|
|
<Txt variant="caption" color="secondary" style={{ fontWeight: 400 }}>
|
|
Confirm to permanently cancel this order
|
|
</Txt>
|
|
</Stack>
|
|
</Stack>
|
|
</div>
|
|
</LayoutHeader>
|
|
}
|
|
content={
|
|
<LayoutContent>
|
|
<Stack direction="vertical" hAlign="center" gap={2.5}>
|
|
<Pill label={orderid.slice(4)} color="warning" variant="light" icon={<MdReceiptLong size={12} />} />
|
|
|
|
{invoiceeligible && (
|
|
<Banner
|
|
status="warning"
|
|
title="Order is within 24Hrs time frame."
|
|
description="The order will be invoiced with standard pricing as agreed."
|
|
container="section"
|
|
>
|
|
<AstryxLink href="https://thelegendarystaff.com/" isExternalLink>
|
|
Terms & Condition link
|
|
</AstryxLink>
|
|
</Banner>
|
|
)}
|
|
|
|
<Txt variant="body1" align="center" color="secondary" style={{ fontWeight: 400 }}>
|
|
Please type in the order number to confirm.
|
|
</Txt>
|
|
<TextInput
|
|
label="Order number"
|
|
isLabelHidden
|
|
width="100%"
|
|
onChange={(v) => setDeletepassword(v)}
|
|
status={isMatch ? undefined : { type: 'error' }}
|
|
value={deletepassword}
|
|
placeholder={orderid.slice(4)}
|
|
/>
|
|
</Stack>
|
|
</LayoutContent>
|
|
}
|
|
footer={
|
|
<LayoutFooter hasDivider>
|
|
<Stack direction="horizontal" gap={1.5} width="100%">
|
|
<Button label="No" variant="secondary" width="100%" onClick={() => handleClose(false)} />
|
|
<Button
|
|
label="Yes, Cancel"
|
|
variant="destructive"
|
|
width="100%"
|
|
onClick={() => {
|
|
if (isMatch) {
|
|
cancelorder();
|
|
handleClose(true);
|
|
}
|
|
}}
|
|
/>
|
|
</Stack>
|
|
</LayoutFooter>
|
|
}
|
|
/>
|
|
</AstryxDialog>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<>
|
|
{loading && <Loader />}
|
|
|
|
<AlertCustomerDelete open={open} handleClose={handleClose} />
|
|
|
|
{/* ============================================= || Assign Roles dialog || ============================================= */}
|
|
<AstryxDialog isOpen={dialogopen} onOpenChange={(o) => !o && dialogclose()} purpose="form" width="min(1100px, 92vw)" maxHeight="88vh">
|
|
<Layout
|
|
header={
|
|
<LayoutHeader hasDivider padding={0}>
|
|
<div
|
|
style={{
|
|
width: '100%',
|
|
padding: '20px',
|
|
background: `linear-gradient(135deg, ${tint(BRAND)} 0%, ${tint(BRAND_LIGHT)} 100%)`
|
|
}}
|
|
>
|
|
<Stack direction="horizontal" hAlign="between" vAlign="center" gap={1.5} wrap="wrap">
|
|
<Stack direction="horizontal" vAlign="center" gap={1.5}>
|
|
<CircleAvatar size={40} bg={BRAND} style={{ boxShadow: `0 6px 18px ${ring(BRAND)}` }}>
|
|
<MdAssignmentTurnedIn size={20} />
|
|
</CircleAvatar>
|
|
<Stack direction="vertical" gap={0}>
|
|
<Txt variant="h4" style={{ fontWeight: 500, lineHeight: 1.1 }}>Assign Roles</Txt>
|
|
<Txt variant="caption" color="secondary" style={{ fontWeight: 400 }}>
|
|
{clientname} · {currentrole}
|
|
</Txt>
|
|
</Stack>
|
|
</Stack>
|
|
<Pill label={orderid} color="warning" variant="light" icon={<MdReceiptLong size={12} />} />
|
|
</Stack>
|
|
|
|
<div
|
|
style={{
|
|
display: 'grid',
|
|
gridTemplateColumns: 'repeat(auto-fit, minmax(220px, 1fr))',
|
|
gap: 12,
|
|
marginTop: 16
|
|
}}
|
|
>
|
|
<div
|
|
role="button"
|
|
tabIndex={0}
|
|
onClick={() => setTabstatus((e) => (e === 0 ? 1 : 0))}
|
|
onKeyDown={(e) => (e.key === 'Enter' || e.key === ' ') && setTabstatus((v) => (v === 0 ? 1 : 0))}
|
|
style={{
|
|
display: 'inline-flex',
|
|
alignItems: 'center',
|
|
gap: 6,
|
|
padding: '5px 10px',
|
|
borderRadius: 999,
|
|
cursor: 'pointer',
|
|
backgroundColor: BRAND,
|
|
color: '#fff',
|
|
fontWeight: 500,
|
|
fontSize: 12,
|
|
width: 'fit-content',
|
|
boxShadow: `0 6px 18px ${ring(BRAND)}`
|
|
}}
|
|
>
|
|
<CircleAvatar size={22} bg="rgba(255,255,255,0.22)">
|
|
<MdAssignmentTurnedIn size={12} />
|
|
</CircleAvatar>
|
|
{currentrole || 'Role'}
|
|
</div>
|
|
<Stack direction="horizontal" hAlign="end" gap={1} wrap="wrap">
|
|
{[
|
|
{ label: 'Required', value: currentshiftobj.shifts, color: BRAND },
|
|
{ label: 'Assigned', value: currentshiftobj.assigned, color: '#10b981' },
|
|
{ label: 'Remaining', value: currentshiftobj.remaining, color: '#ef4444' }
|
|
].map((c) => (
|
|
<div
|
|
key={c.label}
|
|
style={{
|
|
display: 'inline-flex',
|
|
alignItems: 'center',
|
|
gap: 5,
|
|
padding: '4px 8px',
|
|
borderRadius: 999,
|
|
backgroundColor: tint(c.color),
|
|
border: `1px solid ${edge(c.color)}`,
|
|
color: c.color,
|
|
fontSize: 11.5,
|
|
fontWeight: 500
|
|
}}
|
|
>
|
|
{c.label}
|
|
<span
|
|
style={{
|
|
minWidth: 22,
|
|
height: 18,
|
|
padding: '0 4px',
|
|
display: 'inline-flex',
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
borderRadius: 999,
|
|
fontSize: 11,
|
|
fontWeight: 500,
|
|
backgroundColor: '#fff',
|
|
color: c.color,
|
|
border: `1px solid ${edge(c.color)}`
|
|
}}
|
|
>
|
|
{c.value ?? 0}
|
|
</span>
|
|
</div>
|
|
))}
|
|
</Stack>
|
|
</div>
|
|
</div>
|
|
</LayoutHeader>
|
|
}
|
|
content={
|
|
<LayoutContent padding={0}>
|
|
{(stafflist.length === 0) ? (
|
|
(loading) ? (
|
|
<Stack direction="vertical" hAlign="center" style={{ padding: 24 }}>
|
|
<Spinner size="lg" />
|
|
</Stack>
|
|
) : (
|
|
<Txt style={{ padding: 20 }}>No Staffs Available</Txt>
|
|
)
|
|
) : (
|
|
<div style={{ overflowX: 'auto' }}>
|
|
<table style={{ width: '100%', borderCollapse: 'collapse' }}>
|
|
<thead>
|
|
<tr style={{ backgroundColor: 'var(--color-background-muted)' }}>
|
|
{['#', 'Staff', 'Category', 'Price', 'Experience', 'Level', 'City', 'Action', ''].map((h) => (
|
|
<th key={h} style={{ textAlign: 'left', padding: '10px 12px' }}>{h}</th>
|
|
))}
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{stafflist.map((val, i) => {
|
|
const isSelected = !!staffarr.find((res) => res.userid == val.userid);
|
|
return (
|
|
<tr key={i} style={{ backgroundColor: isSelected ? '#f5f5f5' : 'transparent' }}>
|
|
<td style={{ padding: '10px 12px', borderTop: `1px solid ${DT.borderSubtle}` }}>{i + 1}</td>
|
|
<td style={{ padding: '10px 12px', borderTop: `1px solid ${DT.borderSubtle}` }}>
|
|
<Stack direction="horizontal" vAlign="center" gap={1}>
|
|
<CircleAvatar size={25} bg={DT.textMuted} />
|
|
<Stack direction="vertical" gap={0}>
|
|
<Txt variant="caption">{val.firstname}</Txt>
|
|
<Txt variant="caption" color="secondary">{val.contactno}</Txt>
|
|
</Stack>
|
|
</Stack>
|
|
</td>
|
|
<td style={{ padding: '10px 12px', borderTop: `1px solid ${DT.borderSubtle}` }}>
|
|
<Stack direction="vertical" hAlign="start" gap={0.5}>
|
|
<Txt variant="caption" color="secondary">{val.cateoryname}</Txt>
|
|
<Pill label={val.subcategoryname} color="info" size="small" variant="light" />
|
|
</Stack>
|
|
</td>
|
|
<td style={{ padding: '10px 12px', borderTop: `1px solid ${DT.borderSubtle}` }}>{val.rolecost}</td>
|
|
<td style={{ padding: '10px 12px', borderTop: `1px solid ${DT.borderSubtle}` }}>{val.experience} Years</td>
|
|
<td style={{ padding: '10px 12px', borderTop: `1px solid ${DT.borderSubtle}` }}>
|
|
<Pill label={val.levelofexperience} size="small" color="primary" variant="light" />
|
|
</td>
|
|
<td style={{ padding: '10px 12px', borderTop: `1px solid ${DT.borderSubtle}` }}>{val.city}</td>
|
|
<td style={{ padding: '10px 12px', borderTop: `1px solid ${DT.borderSubtle}` }}>
|
|
{(val.orderdetailid !== orderdetailid) ? (
|
|
<CheckboxInput
|
|
label="Select staff"
|
|
isLabelHidden
|
|
isDisabled={val.orderdetailid !== 0}
|
|
value={isSelected}
|
|
onChange={(checked) => {
|
|
logger.info(currentshiftobj)
|
|
if (currentshiftobj.remaining >= 0) {
|
|
if (checked && currentshiftobj.remaining != 0) {
|
|
let arr = staffarr;
|
|
arr.push({
|
|
userid: val.userid,
|
|
orderdetailid,
|
|
productid,
|
|
shiftid: currentshiftobj.shiftid,
|
|
userrate: currentshiftobj.price,
|
|
productrate: val.rolecost,
|
|
firstname: val.firstname
|
|
});
|
|
setStaffarr([...arr])
|
|
let obj = currentshiftobj;
|
|
obj.assigned++;
|
|
obj.remaining = obj.shifts - obj.assigned;
|
|
setCurrentshiftobj({ ...obj })
|
|
} else if (currentshiftobj.assigned != currentshiftobj.shifts || (currentshiftobj.remaining === 0 && (!checked))) {
|
|
let arr = staffarr;
|
|
let index = arr.findIndex((val1) => val1.userid === val.userid)
|
|
arr.splice(index, 1);
|
|
setStaffarr([...arr]);
|
|
let obj = currentshiftobj;
|
|
obj.assigned--;
|
|
obj.remaining = obj.shifts - obj.assigned;
|
|
setCurrentshiftobj({ ...obj })
|
|
}
|
|
logger.info(staffarr);
|
|
}
|
|
}}
|
|
/>
|
|
) : (
|
|
<Stack direction="horizontal" vAlign="center" gap={0.5}>
|
|
<Pill label="Assigned" color="success" size="small" />
|
|
<Tooltip content="Unassign">
|
|
<IconButton
|
|
label="Unassign"
|
|
icon={<CloseOutlined />}
|
|
variant="ghost"
|
|
onClick={() => {
|
|
logger.info(val)
|
|
unassign(val)
|
|
}}
|
|
/>
|
|
</Tooltip>
|
|
<Tooltip content="Send Notification">
|
|
<IconButton
|
|
label="Send Notification"
|
|
icon={<NotificationOutlined />}
|
|
variant="ghost"
|
|
onClick={() => {
|
|
logger.info(val)
|
|
notificationpush(val)
|
|
}}
|
|
/>
|
|
</Tooltip>
|
|
</Stack>
|
|
)}
|
|
</td>
|
|
<td style={{ padding: '10px 12px', borderTop: `1px solid ${DT.borderSubtle}` }}>
|
|
{val.orderid && <Pill label={val.orderid} color="warning" size="small" variant="light" />}
|
|
</td>
|
|
</tr>
|
|
);
|
|
})}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
)}
|
|
</LayoutContent>
|
|
}
|
|
footer={
|
|
<LayoutFooter hasDivider>
|
|
<Stack direction="horizontal" hAlign="end" gap={2}>
|
|
{stafflist.length > 0 && (
|
|
<>
|
|
<Button label="OK" variant="primary" style={{ width: 130 }} onClick={assignok} />
|
|
<Button
|
|
label="clear"
|
|
variant="secondary"
|
|
style={{ width: 130 }}
|
|
onClick={() => {
|
|
setStaffarr([]);
|
|
let obj = currentshiftobj;
|
|
obj.remaining = obj.shifts;
|
|
obj.assigned = 0;
|
|
setCurrentshiftobj(obj);
|
|
}}
|
|
/>
|
|
</>
|
|
)}
|
|
<Button label="Close" variant="destructive" style={{ width: 130 }} onClick={() => { dialogclose() }} />
|
|
</Stack>
|
|
</LayoutFooter>
|
|
}
|
|
/>
|
|
</AstryxDialog>
|
|
|
|
<div style={{ display: 'flex', flexDirection: 'column', gap: 20 }}>
|
|
{/* ============================================= || Sticky header || ============================================= */}
|
|
<div
|
|
style={{
|
|
position: 'sticky',
|
|
// Offset below the Astryx AppShell top nav instead of a hardcoded
|
|
// pixel value — otherwise this block slides up and covers the nav
|
|
// bar (and swallows clicks meant for fields underneath) as the
|
|
// page scrolls.
|
|
top: 'var(--appshell-header-height, 0px)',
|
|
zIndex: 100,
|
|
padding: '16px 20px',
|
|
borderRadius: DT.radiusCard,
|
|
border: `1px solid ${DT.borderSubtle}`,
|
|
background: `linear-gradient(135deg, ${tint(BRAND)} 0%, ${tint(BRAND_LIGHT)} 100%)`,
|
|
boxShadow: DT.shadowMd
|
|
}}
|
|
>
|
|
<Stack direction="horizontal" hAlign="between" vAlign="center" gap={1.5} wrap="wrap">
|
|
<Stack direction="horizontal" vAlign="center" gap={1.5}>
|
|
<IconButton
|
|
label="Back"
|
|
icon={<MdArrowBack size={18} />}
|
|
variant="secondary"
|
|
onClick={() => history.back()}
|
|
style={{
|
|
backgroundColor: '#fff',
|
|
border: `1px solid ${DT.borderSubtle}`,
|
|
color: DT.textPrimary
|
|
}}
|
|
/>
|
|
<CircleAvatar size={48} bg={BRAND} style={{ boxShadow: `0 6px 18px ${ring(BRAND)}` }}>
|
|
<MdReceiptLong size={22} />
|
|
</CircleAvatar>
|
|
<Stack direction="vertical" gap={0.5}>
|
|
<Txt variant="h3" style={{ fontWeight: 500, lineHeight: 1.1 }}>Order Details</Txt>
|
|
<Stack direction="horizontal" vAlign="center" gap={0.75} wrap="wrap">
|
|
<div
|
|
style={{
|
|
display: 'inline-flex',
|
|
alignItems: 'center',
|
|
gap: 4,
|
|
padding: '3px 8px',
|
|
borderRadius: 999,
|
|
backgroundColor: tint('#f59e0b'),
|
|
border: `1px solid ${edge('#f59e0b')}`,
|
|
color: '#f59e0b',
|
|
fontSize: 11,
|
|
fontWeight: 500
|
|
}}
|
|
>
|
|
<MdReceiptLong size={11} />
|
|
{orderid === '' ? <Skeleton width={80} height={14} /> : orderid}
|
|
</div>
|
|
<div
|
|
style={{
|
|
display: 'inline-flex',
|
|
alignItems: 'center',
|
|
gap: 4,
|
|
padding: '3px 8px',
|
|
borderRadius: 999,
|
|
backgroundColor: tint(BRAND),
|
|
border: `1px solid ${edge(BRAND)}`,
|
|
color: BRAND,
|
|
fontSize: 11,
|
|
fontWeight: 500
|
|
}}
|
|
>
|
|
<MdAccessTime size={11} />
|
|
{orderdate === '' ? <Skeleton width={80} height={14} /> : orderdate}
|
|
</div>
|
|
<StatusBadge status={orderstatus} />
|
|
</Stack>
|
|
</Stack>
|
|
</Stack>
|
|
|
|
<Stack direction="horizontal" gap={1.5} vAlign="center" wrap="wrap">
|
|
{((orderstatus === 'pending') ||
|
|
(orderstatus === 'assigned') ||
|
|
(orderstatus === 'confirmed') ||
|
|
(orderstatus === 'modified')) && (
|
|
<Tooltip content="Edit">
|
|
<Button
|
|
label="Edit Order"
|
|
variant="secondary"
|
|
icon={<MdEdit size={16} />}
|
|
style={{ borderRadius: 999, borderColor: edge(BRAND), color: BRAND, fontWeight: 400 }}
|
|
onClick={(e) => {
|
|
e.stopPropagation();
|
|
if (dayjs(dayjs().format('MM-DD-YYYY')).isBefore(dayjs(dayjs(startdate).format('MM-DD-YYYY')))) {
|
|
navigate(`/editorder`, {
|
|
state: {
|
|
orderheaderid: orderheaderid,
|
|
tenantid: tenantid
|
|
}
|
|
});
|
|
} else {
|
|
enqueueSnackbar('Order cannot be edited.\n Order date is not valid at this time', {
|
|
variant: 'error',
|
|
anchorOrigin: { vertical: 'top', horizontal: 'right' },
|
|
autoHideDuration: 4000,
|
|
style: { whiteSpace: 'pre-line' }
|
|
});
|
|
}
|
|
}}
|
|
/>
|
|
</Tooltip>
|
|
)}
|
|
|
|
{orderstatus !== 'cancelled' && orderstatus !== '' && orderstatus !== 'completed' && orderstatus !== 'closed' && (
|
|
<Button
|
|
label="Cancel Order"
|
|
variant="secondary"
|
|
icon={<MdCancel size={16} />}
|
|
style={{ borderRadius: 999, borderColor: edge('#ef4444'), color: '#ef4444', fontWeight: 400 }}
|
|
onClick={() => {
|
|
if ((dayjs(startdate).diff(dayjs(), 'm') / 60) > 24) {
|
|
setInvoiceeligible(false);
|
|
setOpen(true);
|
|
} else {
|
|
setInvoiceeligible(true);
|
|
setOpen(true);
|
|
}
|
|
}}
|
|
/>
|
|
)}
|
|
|
|
{orderstatus === 'cancelled' && (
|
|
<div
|
|
style={{
|
|
display: 'inline-flex',
|
|
alignItems: 'center',
|
|
gap: 4,
|
|
padding: '4px 10px',
|
|
borderRadius: 999,
|
|
backgroundColor: tint('#ef4444'),
|
|
border: `1px solid ${edge('#ef4444')}`,
|
|
color: '#ef4444',
|
|
fontSize: 12,
|
|
fontWeight: 500
|
|
}}
|
|
>
|
|
<MdCancel size={12} /> Cancelled on {cancelleddate}
|
|
</div>
|
|
)}
|
|
</Stack>
|
|
</Stack>
|
|
</div>
|
|
|
|
{/* ============================================= || Body || ============================================= */}
|
|
<MainCard content={false}>
|
|
<div style={{ padding: 20, display: 'flex', flexDirection: 'column', gap: 20 }}>
|
|
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(320px, 1fr))', gap: 20 }}>
|
|
<MainCard sx={{ height: '100%' }}>
|
|
<Stack direction="vertical" gap={1}>
|
|
<Txt variant="h5">Client</Txt>
|
|
<Txt color="secondary">{clientname === '' ? <Skeleton width="40%" height={16} /> : clientname}</Txt>
|
|
<div style={{ width: '100%' }}>
|
|
<Txt color="secondary">{tenantaddress === '' ? <Skeleton width="90%" height={16} /> : tenantaddress}</Txt>
|
|
</div>
|
|
</Stack>
|
|
</MainCard>
|
|
|
|
<MainCard sx={{ height: '100%' }}>
|
|
<Stack direction="vertical" gap={1}>
|
|
<Txt variant="h5">Event</Txt>
|
|
<div style={{ width: '100%' }}>
|
|
<Txt color="secondary">{eventname === '' ? <Skeleton width="40%" height={16} /> : eventname}</Txt>
|
|
</div>
|
|
</Stack>
|
|
</MainCard>
|
|
</div>
|
|
|
|
{categoryarr.map((val5, j) => (
|
|
<Fragment key={val5.locationaddress}>
|
|
<MainCard sx={{ opacity: val5.shiftstatus == 0 ? '' : '0.7' }}>
|
|
<Stack direction="horizontal" hAlign="between" vAlign="center" gap={2} wrap="wrap" style={{ padding: 16 }}>
|
|
<Stack direction="horizontal" gap={2} vAlign="center" wrap="wrap">
|
|
<Txt variant="h5">Shift {j + 1}</Txt>
|
|
<Txt color="secondary">
|
|
<EnvironmentOutlined /> {val5.locationaddress}
|
|
</Txt>
|
|
{val5.shiftstatus === 1 && <Pill label="Cancelled" color="secondary" />}
|
|
</Stack>
|
|
<Stack direction="horizontal" gap={1}>
|
|
{val5.ordercontacts.map((val11) => (
|
|
<Tooltip key={val11.contactname} content={val11.contactname}>
|
|
<CircleAvatar size={30} bg="#2196f3">{val11.contactname.charAt(0).toUpperCase()}</CircleAvatar>
|
|
</Tooltip>
|
|
))}
|
|
</Stack>
|
|
</Stack>
|
|
<div style={{ overflowX: 'auto' }}>
|
|
<table style={{ width: '100%', borderCollapse: 'collapse' }}>
|
|
<thead>
|
|
<tr style={{ backgroundColor: 'var(--color-background-muted)' }}>
|
|
{['#', 'Role', 'Start Date', 'End Date', 'Unpaid break', 'Count', 'Assigned', 'Attire', 'Price', 'Amount', 'Action'].map((h) => (
|
|
<th key={h} style={{ textAlign: 'left', padding: '10px 12px' }}>{h}</th>
|
|
))}
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{val5.orderdetails.length === 0 && (
|
|
<tr>
|
|
{Array.from({ length: 11 }).map((_, ci) => (
|
|
<td key={ci} style={{ padding: '10px 12px', borderTop: `1px solid ${DT.borderSubtle}` }}>
|
|
<Skeleton height={14} />
|
|
</td>
|
|
))}
|
|
</tr>
|
|
)}
|
|
|
|
{val5.orderdetails.map((row, i) => {
|
|
const isExpanded = expandopen[0] === j && expandopen[1] === i;
|
|
return (
|
|
<Fragment key={i}>
|
|
<tr style={{ opacity: row.status == 0 ? '' : '0.7' }}>
|
|
<td style={{ padding: '10px 12px', borderTop: `1px solid ${DT.borderSubtle}` }}>{i + 1}</td>
|
|
<td style={{ padding: '10px 12px', borderTop: `1px solid ${DT.borderSubtle}` }}>{row.productname}</td>
|
|
<td style={{ padding: '10px 12px', borderTop: `1px solid ${DT.borderSubtle}` }}>
|
|
<Stack direction="vertical" gap={0}>
|
|
<Txt variant="body1">{dayjs(row.starttime).format('MM/DD/YYYY')}</Txt>
|
|
<Txt variant="body2">{dayjs(row.starttime).format('hh:mm A')}</Txt>
|
|
</Stack>
|
|
</td>
|
|
<td style={{ padding: '10px 12px', borderTop: `1px solid ${DT.borderSubtle}` }}>
|
|
<Stack direction="vertical" gap={0}>
|
|
<Txt variant="body1">{dayjs(row.endtime).format('MM/DD/YYYY')}</Txt>
|
|
<Txt variant="body2">{dayjs(row.endtime).format('hh:mm A')}</Txt>
|
|
</Stack>
|
|
</td>
|
|
<td style={{ padding: '10px 12px', borderTop: `1px solid ${DT.borderSubtle}` }}>{row.unpaidbreak || 0}</td>
|
|
<td style={{ padding: '10px 12px', borderTop: `1px solid ${DT.borderSubtle}`, textAlign: 'center' }}>
|
|
<Pill label={row.orderqty} color="success" variant="light" size="small" />
|
|
</td>
|
|
<td style={{ padding: '10px 12px', borderTop: `1px solid ${DT.borderSubtle}`, textAlign: 'center' }}>
|
|
<Pill label={row.supplyqty} color={row.supplyqty === 0 ? 'error' : 'warning'} variant="light" size="small" />
|
|
</td>
|
|
<td style={{ padding: '10px 12px', borderTop: `1px solid ${DT.borderSubtle}`, width: 100 }} />
|
|
<td style={{ padding: '10px 12px', borderTop: `1px solid ${DT.borderSubtle}` }}>${row.price}</td>
|
|
<td style={{ padding: '10px 12px', borderTop: `1px solid ${DT.borderSubtle}` }}>${row.landingamount}</td>
|
|
<td style={{ padding: '10px 12px', borderTop: `1px solid ${DT.borderSubtle}` }}>
|
|
<Stack direction="horizontal" gap={0.5} vAlign="center">
|
|
<Tooltip content="Expand">
|
|
<IconButton
|
|
label="Expand row"
|
|
variant="ghost"
|
|
icon={isExpanded ? <MdKeyboardArrowUp color={BRAND} /> : <MdKeyboardArrowDown color={BRAND} />}
|
|
onClick={() => {
|
|
setStafflist([]);
|
|
setExpandopen(isExpanded ? ['', ''] : [j, i])
|
|
fetchstafflist(row.orderdetailid)
|
|
}}
|
|
/>
|
|
</Tooltip>
|
|
|
|
{orderstatus === 'cancelled' && (
|
|
<IconButton label="Edit (disabled)" icon={<EditTwoTone />} variant="ghost" isDisabled />
|
|
)}
|
|
|
|
{row.status === 1 && (
|
|
<Tooltip content="Cancelled">
|
|
<span style={{ display: 'inline-flex', padding: 6, color: '#ef4444' }}>
|
|
<MdCancel size={18} />
|
|
</span>
|
|
</Tooltip>
|
|
)}
|
|
{row.supplyqty > row.orderqty && (
|
|
<Tooltip content="Assigned count is greater than ordered count">
|
|
<span style={{ display: 'inline-flex', padding: 6, color: '#faad14' }}>
|
|
<WarningOutlined />
|
|
</span>
|
|
</Tooltip>
|
|
)}
|
|
</Stack>
|
|
</td>
|
|
</tr>
|
|
{isExpanded && (
|
|
<tr>
|
|
<td colSpan={11} style={{ padding: 0, width: '100%' }}>
|
|
<div style={{ padding: 16 }}>
|
|
<MainCard sx={{ width: '100%' }}>
|
|
{stafflist.length === 0 ? (
|
|
loading ? (
|
|
<Stack direction="vertical" hAlign="center">
|
|
<Spinner size="lg" />
|
|
</Stack>
|
|
) : (
|
|
<Txt style={{ padding: 8 }}>No Staffs has been Assigned</Txt>
|
|
)
|
|
) : (
|
|
<div style={{ overflowX: 'auto' }}>
|
|
<table style={{ width: '100%', borderCollapse: 'collapse' }}>
|
|
<thead>
|
|
<tr style={{ backgroundColor: 'var(--color-background-muted)' }}>
|
|
{['#', 'Staff', 'Start Time', 'End Time', 'Pay Rate', 'Clockin', 'Clockout', 'Hours Worked', 'Status'].map((h) => (
|
|
<th key={h} style={{ textAlign: 'left', padding: '10px 12px' }}>{h}</th>
|
|
))}
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{stafflist.map((val, si) => (
|
|
<tr key={si}>
|
|
<td style={{ padding: '10px 12px', borderTop: `1px solid ${DT.borderSubtle}` }}>{si + 1}</td>
|
|
<td style={{ padding: '10px 12px', borderTop: `1px solid ${DT.borderSubtle}` }}>
|
|
<Stack direction="vertical" gap={0.5}>
|
|
<Txt variant="caption">{val.staffname}</Txt>
|
|
<Pill label={val.productname} color="info" variant="light" size="small" />
|
|
</Stack>
|
|
</td>
|
|
<td style={{ padding: '10px 12px', borderTop: `1px solid ${DT.borderSubtle}` }}>
|
|
<Stack direction="vertical" gap={0}>
|
|
<Txt variant="body2">{dayjs(val.Starttime).format('MM/DD/YYYY')}</Txt>
|
|
<Txt variant="caption">{dayjs(val.Starttime).format('hh:mm A')}</Txt>
|
|
</Stack>
|
|
</td>
|
|
<td style={{ padding: '10px 12px', borderTop: `1px solid ${DT.borderSubtle}` }}>
|
|
<Stack direction="vertical" gap={0}>
|
|
<Txt variant="body2">{dayjs(val.Endtime).format('MM/DD/YYYY')}</Txt>
|
|
<Txt variant="caption">{dayjs(val.Endtime).format('hh:mm A')}</Txt>
|
|
</Stack>
|
|
</td>
|
|
<td style={{ padding: '10px 12px', borderTop: `1px solid ${DT.borderSubtle}` }}>{val.rolecost}</td>
|
|
<td style={{ padding: '10px 12px', borderTop: `1px solid ${DT.borderSubtle}` }}>
|
|
<Stack direction="vertical" gap={0.5} hAlign="center">
|
|
<Pill label={val.clockin ? dayjs(val.clockin).format('MM/DD/YYYY') : ''} color="primary" variant="light" size="small" />
|
|
<Pill label={val.clockin ? dayjs(val.clockin).format('hh:mm A') : ''} color="info" variant="light" size="small" />
|
|
</Stack>
|
|
</td>
|
|
<td style={{ padding: '10px 12px', borderTop: `1px solid ${DT.borderSubtle}` }}>
|
|
<Stack direction="vertical" gap={0.5} hAlign="center">
|
|
<Pill label={val.clockout ? dayjs(val.clockout).format('MM/DD/YYYY') : ''} color="primary" variant="light" size="small" />
|
|
<Pill label={val.clockout ? dayjs(val.clockout).format('hh:mm A') : ''} color="info" variant="light" size="small" />
|
|
</Stack>
|
|
</td>
|
|
<td style={{ padding: '10px 12px', borderTop: `1px solid ${DT.borderSubtle}` }}>{val.hoursworked}</td>
|
|
<td style={{ padding: '10px 12px', borderTop: `1px solid ${DT.borderSubtle}` }}>
|
|
<Stack direction="horizontal">
|
|
{val.orderstatus === 'pending' && <Pill label="Pending" color="error" size="small" />}
|
|
{val.orderstatus === 'cancelled' && <Pill label="Cancelled" color="secondary" size="small" />}
|
|
{val.orderstatus === 'completed' && <Pill label="Completed" color="primary" size="small" />}
|
|
{val.orderstatus === 'processing' && <Pill label="Processing" color="primary" size="small" />}
|
|
{val.orderstatus === 'assigned' && <Pill label="Assigned" color="warning" size="small" />}
|
|
{val.orderstatus === 'confirmed' && <Pill label="Confirmed" color="success" size="small" />}
|
|
{val.orderstatus === 'active' && <Pill label="Active" color="info" size="small" />}
|
|
{val.orderstatus === 'closed' && <Pill label="Closed" color="info" size="small" />}
|
|
</Stack>
|
|
</td>
|
|
</tr>
|
|
))}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
)}
|
|
</MainCard>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
)}
|
|
</Fragment>
|
|
);
|
|
})}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</MainCard>
|
|
</Fragment>
|
|
))}
|
|
|
|
<hr style={{ border: 'none', borderTop: `1px solid ${DT.borderSubtle}`, margin: 0 }} />
|
|
|
|
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(320px, 1fr))', gap: 20 }}>
|
|
<MainCard title="Order Addons" sx={{ height: '100%' }}>
|
|
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 8 }}>
|
|
{(orderaddons || []).map((val) => (
|
|
<Pill key={val.addon} label={val.addon} color="error" variant="outlined" icon={<MdDirectionsCar size={14} />} />
|
|
))}
|
|
</div>
|
|
</MainCard>
|
|
|
|
<MainCard title="Total" sx={{ height: '100%' }}>
|
|
<Stack direction="vertical" gap={2}>
|
|
<Stack direction="horizontal" hAlign="between">
|
|
<Txt color="secondary">Sub Total:</Txt>
|
|
<Txt>${subtotal === '' ? <Skeleton width={30} height={14} /> : subtotal}</Txt>
|
|
</Stack>
|
|
<Stack direction="horizontal" hAlign="between">
|
|
<Txt color="secondary">Tax:</Txt>
|
|
<Txt>{taxamount === '' ? <Skeleton width={30} height={14} /> : taxamount}</Txt>
|
|
</Stack>
|
|
<Stack direction="horizontal" hAlign="between">
|
|
<Txt variant="subtitle1">Grand Total:</Txt>
|
|
<Txt variant="subtitle1">{grandtotal === '' ? <Skeleton width={30} height={14} /> : `$${grandtotal}`}</Txt>
|
|
</Stack>
|
|
</Stack>
|
|
</MainCard>
|
|
</div>
|
|
|
|
<Stack direction="vertical">
|
|
<Txt color="secondary">Other Instructions:</Txt>
|
|
<Txt style={{ marginLeft: 24 }}>{otherinstructions}</Txt>
|
|
</Stack>
|
|
</div>
|
|
</MainCard>
|
|
</div>
|
|
</>
|
|
);
|
|
};
|
|
|
|
export default Details;
|