mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-20 13:32:00 +00:00
Merge branch 'development' of https://gitlab.com/mbugroup/lti-web-client into fix/debt-supplier
This commit is contained in:
@@ -72,8 +72,10 @@ const RequireAuth = ({ children }: RequireAuthProps) => {
|
||||
await AuthApi.refresh();
|
||||
};
|
||||
|
||||
refreshUserSession();
|
||||
}, []);
|
||||
if (user) {
|
||||
refreshUserSession();
|
||||
}
|
||||
}, [user]);
|
||||
|
||||
if (
|
||||
(isLoadingUserResponse && !userResponse && !userErrorResponse) ||
|
||||
|
||||
@@ -58,6 +58,7 @@ const DrawerHeader = ({
|
||||
if (leftIconOnClick) {
|
||||
return (
|
||||
<button
|
||||
type='button'
|
||||
onClick={leftIconOnClick}
|
||||
className='hover:text-gray-400 bg-transparent border-none p-0'
|
||||
>
|
||||
@@ -72,12 +73,12 @@ const DrawerHeader = ({
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
'flex flex-row justify-between items-center px-4 pt-4',
|
||||
'flex flex-row justify-between items-center px-4 pt-4 pb-4 border-b border-base-content/10',
|
||||
className
|
||||
)}
|
||||
>
|
||||
{/* Left Side */}
|
||||
<div className='flex flex-row h-full gap-2 items-center'>
|
||||
<div className='flex flex-row h-full gap-3 items-center'>
|
||||
{renderLeftIcon()}
|
||||
|
||||
{showDivider && subtitle && (
|
||||
@@ -85,7 +86,12 @@ const DrawerHeader = ({
|
||||
)}
|
||||
|
||||
{subtitle && (
|
||||
<div className={cn('text-sm text-neutral', subtitleClassName)}>
|
||||
<div
|
||||
className={cn(
|
||||
'text-sm font-medium text-base-content/50',
|
||||
subtitleClassName
|
||||
)}
|
||||
>
|
||||
{subtitle}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -5,6 +5,7 @@ import UniformityGaugeChart from '@/components/pages/production/uniformity/chart
|
||||
import UniformityBarChartSkeleton from '@/components/pages/production/uniformity/skeleton/UniformityBarChartSkeleton';
|
||||
import UniformityGaugeChartSkeleton from '@/components/pages/production/uniformity/skeleton/UniformityGaugeChartSkeleton';
|
||||
import { Uniformity, type ChartData } from '@/types/api/production/uniformity';
|
||||
import { Icon } from '@iconify/react';
|
||||
|
||||
interface UniformityChartProps {
|
||||
uniformityData?: Uniformity | null;
|
||||
@@ -101,15 +102,26 @@ const UniformityChart = ({
|
||||
const shouldShowEmptyState = !isFiltered;
|
||||
|
||||
return (
|
||||
<section className='w-full grid grid-cols-1 xl:grid-cols-2 2xl:grid-cols-4 gap-4'>
|
||||
<section className='w-full grid grid-cols-1 xl:grid-cols-2 2xl:grid-cols-[1fr_350px] gap-3'>
|
||||
<Card
|
||||
title='Performance Overview ⓘ'
|
||||
variant='bordered'
|
||||
className={{
|
||||
wrapper: 'xl:col-span-1 2xl:col-span-3 w-full',
|
||||
body: 'h-96',
|
||||
wrapper:
|
||||
'2xl:col-span-1 w-full rounded-xl border border-base-content/10',
|
||||
body: 'h-96 p-4',
|
||||
}}
|
||||
>
|
||||
<div className='flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4 mb-3'>
|
||||
<div className='text-base font-semibold leading-7 flex gap-3 items-center'>
|
||||
Performance Overview{' '}
|
||||
<Icon
|
||||
icon='heroicons:information-circle'
|
||||
width={20}
|
||||
height={20}
|
||||
className='inline text-neutral-500'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className='w-full h-full flex items-center justify-center'>
|
||||
{shouldShowEmptyState ||
|
||||
!uniformityData ||
|
||||
@@ -120,26 +132,31 @@ const UniformityChart = ({
|
||||
)}
|
||||
</div>
|
||||
</Card>
|
||||
{shouldShowEmptyState || !uniformityData || !gaugeChartData ? (
|
||||
<Card
|
||||
variant='bordered'
|
||||
title='Weekly Performance ⓘ'
|
||||
className={{
|
||||
wrapper: 'xl:col-span-1 2xl:col-span-1 w-full',
|
||||
body: 'h-110',
|
||||
}}
|
||||
>
|
||||
<Card
|
||||
variant='bordered'
|
||||
className={{
|
||||
wrapper:
|
||||
'2xl:col-span-1 w-full rounded-xl border border-base-content/10',
|
||||
body:
|
||||
shouldShowEmptyState || !uniformityData || !gaugeChartData
|
||||
? 'h-110 p-4'
|
||||
: 'p-4',
|
||||
}}
|
||||
>
|
||||
<div className='flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4 mb-3'>
|
||||
<div className='text-base font-semibold leading-7 flex gap-3 items-center'>
|
||||
Weekly Performance{' '}
|
||||
<Icon
|
||||
icon='heroicons:information-circle'
|
||||
width={20}
|
||||
height={20}
|
||||
className='inline text-neutral-500'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{shouldShowEmptyState || !uniformityData || !gaugeChartData ? (
|
||||
<UniformityGaugeChartSkeleton />
|
||||
</Card>
|
||||
) : (
|
||||
<Card
|
||||
variant='bordered'
|
||||
title='Weekly Performance ⓘ'
|
||||
className={{
|
||||
wrapper: 'xl:col-span-1 2xl:col-span-1 w-full',
|
||||
body: 'p-4',
|
||||
}}
|
||||
>
|
||||
) : (
|
||||
<UniformityGaugeChart
|
||||
value={gaugeChartData.value}
|
||||
label={gaugeChartData.label}
|
||||
@@ -150,8 +167,8 @@ const UniformityChart = ({
|
||||
hasPrevWeek={gaugeChartData.hasPrevWeek}
|
||||
hasNextWeek={gaugeChartData.hasNextWeek}
|
||||
/>
|
||||
</Card>
|
||||
)}
|
||||
)}
|
||||
</Card>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import { usePathname, useRouter } from 'next/navigation';
|
||||
import Drawer from '@/components/Drawer';
|
||||
import React, { ReactNode } from 'react';
|
||||
import UniformityTable from '@/components/pages/production/uniformity/UniformityTable';
|
||||
import Uniformity from '@/app/production/uniformity/page';
|
||||
import { useUiStore } from '@/stores/ui/ui.store';
|
||||
|
||||
export default function UniformityPageWrapper({
|
||||
@@ -40,8 +40,8 @@ export default function UniformityPageWrapper({
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className='w-full p-4'>
|
||||
<UniformityTable />
|
||||
<div className='w-full'>
|
||||
<Uniformity />
|
||||
</div>
|
||||
|
||||
<Drawer
|
||||
|
||||
@@ -19,11 +19,11 @@ import { isResponseSuccess } from '@/lib/api-helper';
|
||||
import { type BaseApiResponse } from '@/types/api/api-general';
|
||||
import Table from '@/components/Table';
|
||||
import Badge from '@/components/Badge';
|
||||
import StatusBadge from '@/components/helper/StatusBadge';
|
||||
import CheckboxInput from '@/components/input/CheckboxInput';
|
||||
import { useModal } from '@/components/Modal';
|
||||
import ConfirmationModal from '@/components/modal/ConfirmationModal';
|
||||
import toast from 'react-hot-toast';
|
||||
import Card from '@/components/Card';
|
||||
import UniformityTableSkeleton from '@/components/pages/production/uniformity/skeleton/UniformityTableSkeleton';
|
||||
import RequirePermission from '@/components/helper/RequirePermission';
|
||||
import { useUniformityStore } from '@/stores/uniformity/uniformity.store';
|
||||
@@ -45,12 +45,11 @@ import {
|
||||
getStatusColor,
|
||||
getStatusIndicatorColor,
|
||||
getStatusText,
|
||||
getStatusBadgeColor,
|
||||
} from '@/components/pages/production/uniformity/uniformity-utils';
|
||||
import { generateUniformityPDF } from '@/components/pages/production/uniformity/export/UniformityExportPDF';
|
||||
import { generateUniformityExcel } from '@/components/pages/production/uniformity/export/UniformityExportExcel';
|
||||
import Dropdown from '@/components/Dropdown';
|
||||
import Menu from '@/components/menu/Menu';
|
||||
import MenuItem from '@/components/menu/MenuItem';
|
||||
import { useFormik } from 'formik';
|
||||
import {
|
||||
UniformityTableFilterSchema,
|
||||
@@ -113,12 +112,10 @@ const UniformityConfirmationPreview = ({
|
||||
|
||||
const columns: ColumnDef<DetailOptionType>[] = [
|
||||
{
|
||||
accessorKey: 'label',
|
||||
header: 'Label',
|
||||
cell: (props) => props.row.original.label,
|
||||
},
|
||||
{
|
||||
accessorKey: 'value',
|
||||
header: 'Value',
|
||||
cell: (props) => {
|
||||
const id = props.row.original.id;
|
||||
@@ -819,7 +816,7 @@ const UniformityTable = () => {
|
||||
);
|
||||
|
||||
return (
|
||||
<div className='w-full flex flex-row justify-center'>
|
||||
<div className='w-full flex flex-row xl:justify-center justify-end'>
|
||||
<CheckboxInput
|
||||
name='allRow'
|
||||
checked={isAllSelected}
|
||||
@@ -832,7 +829,7 @@ const UniformityTable = () => {
|
||||
},
|
||||
cell: ({ row }) => {
|
||||
return (
|
||||
<div>
|
||||
<div className='w-full flex flex-row xl:justify-center justify-end'>
|
||||
<CheckboxInput
|
||||
name='row'
|
||||
checked={row.getIsSelected()}
|
||||
@@ -862,8 +859,11 @@ const UniformityTable = () => {
|
||||
{
|
||||
accessorKey: 'week',
|
||||
header: 'Tanggal (Week)',
|
||||
cell: (props) =>
|
||||
`${formatDate(props.row.original.applied_at, 'DD MMM YYYY')} (${props.row.original.week})`,
|
||||
cell: (props) => (
|
||||
<span className='text-nowrap'>
|
||||
{`${formatDate(props.row.original.applied_at, 'DD MMM YYYY')} (${props.row.original.week})`}
|
||||
</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorKey: 'status',
|
||||
@@ -872,20 +872,11 @@ const UniformityTable = () => {
|
||||
const uniformity = props.row.original;
|
||||
const status =
|
||||
uniformity.latest_approval?.action ?? uniformity.status;
|
||||
return (
|
||||
<div className='w-full'>
|
||||
<Badge
|
||||
statusIndicator={true}
|
||||
variant='soft'
|
||||
className={{
|
||||
badge: `rounded-xl w-full justify-start border border-gray-200 text-black ${getStatusColor(status)}`,
|
||||
status: getStatusIndicatorColor(status),
|
||||
}}
|
||||
>
|
||||
{getStatusText(status)}
|
||||
</Badge>
|
||||
</div>
|
||||
);
|
||||
|
||||
const badgeColor = getStatusBadgeColor(status);
|
||||
const statusText = getStatusText(status);
|
||||
|
||||
return <StatusBadge color={badgeColor} text={statusText} />;
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -900,334 +891,410 @@ const UniformityTable = () => {
|
||||
[]
|
||||
);
|
||||
|
||||
// ===== CALCULATE FILTER COUNT =====
|
||||
const filterCount = useMemo(() => {
|
||||
let count = 0;
|
||||
|
||||
if (filterStartDate && filterEndDate) {
|
||||
count += 1;
|
||||
}
|
||||
|
||||
if (filterLocation) {
|
||||
count += 1;
|
||||
}
|
||||
|
||||
if (filterProjectFlock) {
|
||||
count += 1;
|
||||
}
|
||||
|
||||
if (filterKandang) {
|
||||
count += 1;
|
||||
}
|
||||
|
||||
return count;
|
||||
}, [
|
||||
filterStartDate,
|
||||
filterEndDate,
|
||||
filterLocation,
|
||||
filterProjectFlock,
|
||||
filterKandang,
|
||||
]);
|
||||
|
||||
const isFilterActive = filterCount > 0;
|
||||
|
||||
return (
|
||||
<>
|
||||
<section className='[&_button]:w-full [&_button]:sm:w-fit [&_button]:last:mt-2 [&_button]:last:sm:mt-0 sm:flex sm:justify-between grid grid-cols-1 sm:gap-0 gap-2'>
|
||||
<div className='w-full sm:w-fit flex flex-col sm:flex-row self-start gap-2'>
|
||||
<RequirePermission permissions='lti.production.uniformity.create'>
|
||||
<Button color='primary' href='/production/uniformity/add'>
|
||||
<Icon icon='ic:round-plus' width={18} height={18} />
|
||||
Add Uniformity
|
||||
</Button>
|
||||
</RequirePermission>
|
||||
</div>
|
||||
|
||||
<div className='sm:flex gap-2'>
|
||||
<Button variant='outline' onClick={filterModal.openModal}>
|
||||
<Icon icon='heroicons:funnel' width={18} height={18} />
|
||||
Filter
|
||||
</Button>
|
||||
|
||||
<Dropdown
|
||||
trigger={
|
||||
<Button variant='outline' isLoading={isAnyExportLoading}>
|
||||
<Icon
|
||||
icon='heroicons:cloud-arrow-down'
|
||||
width={18}
|
||||
height={18}
|
||||
/>
|
||||
Export
|
||||
<div className='@container w-full'>
|
||||
<div className='w-full p-3 flex flex-row justify-between gap-3 flex-wrap border-b border-base-content/10'>
|
||||
<div className='w-fit flex flex-row gap-3 flex-wrap'>
|
||||
<RequirePermission permissions='lti.production.uniformity.create'>
|
||||
<Button
|
||||
href='/production/uniformity/add'
|
||||
color='primary'
|
||||
className='px-3 py-2.5 w-fit text-sm text-base-100 rounded-lg shadow-sm'
|
||||
>
|
||||
<Icon icon='heroicons:plus' width={20} height={20} />
|
||||
Add Uniformity
|
||||
</Button>
|
||||
}
|
||||
align='end'
|
||||
>
|
||||
<Menu>
|
||||
<MenuItem title='Excel' onClick={handleExportExcel} />
|
||||
<MenuItem title='PDF' onClick={handleExportPDF} />
|
||||
</Menu>
|
||||
</Dropdown>
|
||||
</RequirePermission>
|
||||
</div>
|
||||
|
||||
<div className='flex flex-1 flex-row justify-start sm:justify-end items-center gap-3 flex-wrap'>
|
||||
<Button
|
||||
variant='outline'
|
||||
color='none'
|
||||
onClick={filterModal.openModal}
|
||||
className={cn(
|
||||
'px-3 py-2.5 gap-1.5 text-sm text-base-content/50 border border-base-content/10 rounded-xl shadow-button-soft transition-all',
|
||||
{
|
||||
'border-primary-gradient text-primary': isFilterActive,
|
||||
}
|
||||
)}
|
||||
>
|
||||
<Icon icon='heroicons:funnel' width={20} height={20} />
|
||||
Filter
|
||||
{isFilterActive && (
|
||||
<Badge
|
||||
className={{
|
||||
badge:
|
||||
'p-1.5 bg-[#FF3535] text-xs text-base-100 border border-base-300 rounded-lg',
|
||||
}}
|
||||
>
|
||||
{filterCount}
|
||||
</Badge>
|
||||
)}
|
||||
</Button>
|
||||
|
||||
<Dropdown
|
||||
align='end'
|
||||
direction='bottom'
|
||||
className={{
|
||||
content:
|
||||
'mt-1 rounded-xl border border-base-content/5 shadow-sm overflow-hidden',
|
||||
}}
|
||||
trigger={
|
||||
<Button
|
||||
variant='outline'
|
||||
color='none'
|
||||
className='px-3 py-2.5 text-sm text-base-content/50 border border-base-content/10 rounded-xl shadow-button-soft'
|
||||
>
|
||||
<div className='flex flex-row items-center gap-1.5'>
|
||||
<Icon
|
||||
icon='heroicons:cloud-arrow-down'
|
||||
width={20}
|
||||
height={20}
|
||||
/>
|
||||
|
||||
<span>Export</span>
|
||||
|
||||
<div className='w-px self-stretch bg-base-content/10' />
|
||||
|
||||
<Icon
|
||||
icon='heroicons:chevron-down'
|
||||
width={14}
|
||||
height={14}
|
||||
/>
|
||||
</div>
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
<Button
|
||||
variant='ghost'
|
||||
color='none'
|
||||
onClick={handleExportExcel}
|
||||
isLoading={isExcelExportLoading}
|
||||
className='w-full p-3 justify-start text-sm text-base-content/50 font-semibold text-nowrap'
|
||||
>
|
||||
<Icon icon='heroicons:table-cells' width={20} height={20} />
|
||||
Export to Excel
|
||||
</Button>
|
||||
<Button
|
||||
variant='ghost'
|
||||
color='none'
|
||||
onClick={handleExportPDF}
|
||||
isLoading={isPdfExportLoading}
|
||||
className='w-full p-3 justify-start text-sm text-base-content/50 font-semibold text-nowrap hover:bg-base-content/5'
|
||||
>
|
||||
<Icon icon='heroicons:document-text' width={20} height={20} />
|
||||
Export to PDF
|
||||
</Button>
|
||||
</Dropdown>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div className='my-4 divider'></div>
|
||||
<section className='p-3'>
|
||||
<UniformityChartWrapper
|
||||
uniformitySwrKey={uniformitySwrKey}
|
||||
isFiltered={isSubmitted}
|
||||
/>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<section>
|
||||
<UniformityChartWrapper
|
||||
uniformitySwrKey={uniformitySwrKey}
|
||||
isFiltered={isSubmitted}
|
||||
/>
|
||||
</section>
|
||||
|
||||
<Card
|
||||
variant='bordered'
|
||||
<Table<Uniformity>
|
||||
data={isResponseSuccess(uniformities) ? uniformities?.data : []}
|
||||
columns={uniformityColumns}
|
||||
pageSize={tableFilterState.pageSize}
|
||||
page={isResponseSuccess(uniformities) ? uniformities?.meta?.page : 0}
|
||||
totalItems={
|
||||
isResponseSuccess(uniformities)
|
||||
? uniformities?.meta?.total_results
|
||||
: 0
|
||||
}
|
||||
onPageChange={setPage}
|
||||
isLoading={isLoading}
|
||||
sorting={sorting}
|
||||
setSorting={setSorting}
|
||||
rowSelection={rowSelection}
|
||||
setRowSelection={setRowSelection}
|
||||
className={{
|
||||
wrapper: 'my-4 w-full relative',
|
||||
containerClassName: cn('p-3 pt-0', {
|
||||
'mb-20':
|
||||
isResponseSuccess(uniformities) &&
|
||||
uniformities?.data?.length === 0,
|
||||
}),
|
||||
headerColumnClassName:
|
||||
'first:pl-3 first:pr-0 xl:first:pl-3 py-3 text-nowrap',
|
||||
bodyColumnClassName:
|
||||
'first:pl-3 first:pr-0 xl:first:pl-3 py-3 text-nowrap',
|
||||
}}
|
||||
emptyContent={<UniformityTableSkeleton />}
|
||||
/>
|
||||
|
||||
<ConfirmationModal
|
||||
ref={successModal.ref}
|
||||
type='success'
|
||||
iconPosition='left'
|
||||
text='Data Berhasil Ditambahkan'
|
||||
subtitleText='Data uniformity telah berhasil disimpan.'
|
||||
closeOnBackdrop={false}
|
||||
primaryButton={{
|
||||
text: 'Ok',
|
||||
color: 'primary',
|
||||
onClick: handleSuccessModalClose,
|
||||
}}
|
||||
className={{
|
||||
modalBox: 'rounded-2xl',
|
||||
}}
|
||||
>
|
||||
<Table<Uniformity>
|
||||
data={isResponseSuccess(uniformities) ? uniformities?.data : []}
|
||||
columns={uniformityColumns}
|
||||
pageSize={tableFilterState.pageSize}
|
||||
page={isResponseSuccess(uniformities) ? uniformities?.meta?.page : 0}
|
||||
totalItems={
|
||||
isResponseSuccess(uniformities)
|
||||
? uniformities?.meta?.total_results
|
||||
: 0
|
||||
}
|
||||
onPageChange={setPage}
|
||||
isLoading={isLoading}
|
||||
sorting={sorting}
|
||||
setSorting={setSorting}
|
||||
rowSelection={rowSelection}
|
||||
setRowSelection={setRowSelection}
|
||||
className={{
|
||||
containerClassName: cn({
|
||||
'mb-20':
|
||||
isResponseSuccess(uniformities) &&
|
||||
uniformities?.data?.length === 0,
|
||||
}),
|
||||
tableWrapperClassName: 'overflow-x-auto min-h-full ',
|
||||
tableClassName: 'font-inter w-full table-auto min-h-full!',
|
||||
headerRowClassName: 'border-b border-b-gray-200',
|
||||
headerColumnClassName:
|
||||
'px-6 py-3 text-xs font-semibold text-gray-500 last:flex last:flex-row last:justify-end',
|
||||
bodyRowClassName: 'border-b border-b-gray-200',
|
||||
bodyColumnClassName:
|
||||
'px-6 py-3 last:flex last:flex-row last:justify-end',
|
||||
}}
|
||||
emptyContent={<UniformityTableSkeleton />}
|
||||
/>
|
||||
|
||||
<ConfirmationModal
|
||||
ref={successModal.ref}
|
||||
type='success'
|
||||
iconPosition='left'
|
||||
text='Data Berhasil Ditambahkan'
|
||||
subtitleText='Data uniformity telah berhasil disimpan.'
|
||||
closeOnBackdrop={false}
|
||||
primaryButton={{
|
||||
text: 'Ok',
|
||||
color: 'primary',
|
||||
onClick: handleSuccessModalClose,
|
||||
}}
|
||||
className={{
|
||||
modalBox: 'rounded-2xl',
|
||||
}}
|
||||
>
|
||||
<div className='flex flex-col gap-4 mt-4'>
|
||||
{createdUniformity ? (
|
||||
<UniformityConfirmationPreview
|
||||
uniformityDetail={createdUniformity}
|
||||
/>
|
||||
) : selectedRowIds.length === 1 ? (
|
||||
<UniformityConfirmationPreview
|
||||
uniformity={selectedUniformities[0]}
|
||||
/>
|
||||
) : (
|
||||
<div className='text-center text-gray-500'>
|
||||
{selectedRowIds.length} data dipilih
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</ConfirmationModal>
|
||||
|
||||
<ConfirmationModal
|
||||
ref={singleDeleteModal.ref}
|
||||
type='error'
|
||||
iconPosition='left'
|
||||
text={`Delete This Data?`}
|
||||
subtitleText='Are you sure you want to delete this data?'
|
||||
secondaryButton={{
|
||||
text: 'Cancel',
|
||||
}}
|
||||
primaryButton={{
|
||||
text: 'Delete',
|
||||
color: 'primary',
|
||||
isLoading: isDeleteLoading,
|
||||
onClick: singleDeleteHandler,
|
||||
}}
|
||||
className={{
|
||||
modalBox: 'rounded-2xl',
|
||||
}}
|
||||
>
|
||||
<div className='flex flex-col gap-4 mt-4'>
|
||||
<UniformityConfirmationPreview uniformity={selectedUniformity} />
|
||||
</div>
|
||||
</ConfirmationModal>
|
||||
|
||||
<ConfirmationModal
|
||||
ref={singleApproveModal.ref}
|
||||
type='success'
|
||||
iconPosition='left'
|
||||
text='Approve This Submission?'
|
||||
subtitleText='Are you sure you want to approve this submission?'
|
||||
secondaryButton={{
|
||||
text: 'Cancel',
|
||||
}}
|
||||
primaryButton={{
|
||||
text: 'Approve',
|
||||
color: 'primary',
|
||||
isLoading: isDeleteLoading,
|
||||
onClick: singleApproveHandler,
|
||||
}}
|
||||
className={{
|
||||
modalBox: 'rounded-2xl',
|
||||
}}
|
||||
>
|
||||
<div className='flex flex-col gap-4 mt-4'>
|
||||
{selectedRowIds.length === 1 ? (
|
||||
<UniformityConfirmationPreview
|
||||
uniformity={selectedUniformities[0]}
|
||||
/>
|
||||
) : (
|
||||
<div className='text-center text-gray-500'>
|
||||
{selectedRowIds.length} data dipilih
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</ConfirmationModal>
|
||||
|
||||
<ConfirmationModal
|
||||
ref={bulkApproveModal.ref}
|
||||
type='success'
|
||||
iconPosition='left'
|
||||
text={`Approve This Submission?`}
|
||||
subtitleText={`Are you sure you want to approve this submission? (${selectedRowIds.length} data)`}
|
||||
secondaryButton={{
|
||||
text: 'Cancel',
|
||||
}}
|
||||
primaryButton={{
|
||||
text: 'Approve',
|
||||
color: 'primary',
|
||||
isLoading: isBulkActionLoading,
|
||||
onClick: bulkApproveHandler,
|
||||
}}
|
||||
className={{
|
||||
modalBox: 'rounded-2xl',
|
||||
}}
|
||||
>
|
||||
<div className='flex flex-col gap-4 mt-4'>
|
||||
<UniformityConfirmationPreview uniformity={selectedUniformity} />
|
||||
</div>
|
||||
</ConfirmationModal>
|
||||
|
||||
<ConfirmationModal
|
||||
ref={singleRejectModal.ref}
|
||||
type='error'
|
||||
iconPosition='left'
|
||||
text='Reject This Submission?'
|
||||
subtitleText='Are you sure you want to reject this submission?'
|
||||
secondaryButton={{
|
||||
text: 'Cancel',
|
||||
}}
|
||||
primaryButton={{
|
||||
text: 'Reject',
|
||||
color: 'primary',
|
||||
isLoading: isDeleteLoading,
|
||||
onClick: singleRejectHandler,
|
||||
}}
|
||||
className={{
|
||||
modalBox: 'rounded-2xl',
|
||||
}}
|
||||
>
|
||||
<div className='flex flex-col gap-4 mt-4'>
|
||||
{selectedRowIds.length === 1 ? (
|
||||
<UniformityConfirmationPreview
|
||||
uniformity={selectedUniformities[0]}
|
||||
/>
|
||||
) : (
|
||||
<div className='text-center text-gray-500'>
|
||||
{selectedRowIds.length} data dipilih
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</ConfirmationModal>
|
||||
|
||||
<ConfirmationModal
|
||||
ref={bulkRejectModal.ref}
|
||||
type='error'
|
||||
iconPosition='left'
|
||||
text={`Apakah anda yakin ingin menolak ${selectedRowIds.length} data Uniformity yang dipilih?`}
|
||||
secondaryButton={{
|
||||
text: 'Cancel',
|
||||
}}
|
||||
primaryButton={{
|
||||
text: 'Reject',
|
||||
color: 'primary',
|
||||
isLoading: isBulkActionLoading,
|
||||
onClick: bulkRejectHandler,
|
||||
}}
|
||||
className={{
|
||||
modalBox: 'rounded-2xl',
|
||||
}}
|
||||
>
|
||||
<div className='flex flex-col gap-4 mt-4'>
|
||||
{selectedRowIds.length === 1 ? (
|
||||
<UniformityConfirmationPreview
|
||||
uniformity={selectedUniformities[0]}
|
||||
/>
|
||||
) : (
|
||||
<div className='text-center text-gray-500'>
|
||||
{selectedRowIds.length} data dipilih
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</ConfirmationModal>
|
||||
|
||||
{/* Filter Modal */}
|
||||
<Modal
|
||||
ref={filterModal.ref}
|
||||
className={{
|
||||
modal: 'p-0',
|
||||
modalBox: 'p-0 rounded-2xl xl:max-w-4/12 max-w-sm',
|
||||
}}
|
||||
>
|
||||
<div className='space-y-6'>
|
||||
{/* Modal Header */}
|
||||
<div className='flex items-center justify-between gap-2 py-3 border-b border-gray-300 px-4'>
|
||||
<div className='flex items-center gap-2 text-primary'>
|
||||
<Icon icon='heroicons:funnel' width={20} height={20} />
|
||||
<h3 className='font-semibold'>Filter Data</h3>
|
||||
</div>
|
||||
<Button
|
||||
variant='link'
|
||||
onClick={filterModal.closeModal}
|
||||
className='text-gray-500 hover:text-gray-700 transition-colors cursor-pointer'
|
||||
>
|
||||
<Icon icon='heroicons:x-mark' width={20} height={20} />
|
||||
</Button>
|
||||
<div className='flex flex-col gap-4 mt-4'>
|
||||
{createdUniformity ? (
|
||||
<UniformityConfirmationPreview
|
||||
uniformityDetail={createdUniformity}
|
||||
/>
|
||||
) : selectedRowIds.length === 1 ? (
|
||||
<UniformityConfirmationPreview
|
||||
uniformity={selectedUniformities[0]}
|
||||
/>
|
||||
) : (
|
||||
<div className='text-center text-gray-500'>
|
||||
{selectedRowIds.length} data dipilih
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</ConfirmationModal>
|
||||
|
||||
{/* Error List Alert */}
|
||||
{formErrorList.length > 0 && (
|
||||
<div className='w-full px-4'>
|
||||
<AlertErrorList formErrorList={formErrorList} onClose={close} />
|
||||
</div>
|
||||
)}
|
||||
<ConfirmationModal
|
||||
ref={singleDeleteModal.ref}
|
||||
type='error'
|
||||
iconPosition='left'
|
||||
text={`Delete This Data?`}
|
||||
subtitleText='Are you sure you want to delete this data?'
|
||||
secondaryButton={{
|
||||
text: 'Cancel',
|
||||
}}
|
||||
primaryButton={{
|
||||
text: 'Delete',
|
||||
color: 'primary',
|
||||
isLoading: isDeleteLoading,
|
||||
onClick: singleDeleteHandler,
|
||||
}}
|
||||
className={{
|
||||
modalBox: 'rounded-2xl',
|
||||
}}
|
||||
>
|
||||
<div className='flex flex-col gap-4 mt-4'>
|
||||
<UniformityConfirmationPreview uniformity={selectedUniformity} />
|
||||
</div>
|
||||
</ConfirmationModal>
|
||||
|
||||
<div className='space-y-4 px-4'>
|
||||
<div className='grid grid-cols-1 sm:grid-cols-2 sm:gap-4'>
|
||||
<div>
|
||||
<ConfirmationModal
|
||||
ref={singleApproveModal.ref}
|
||||
type='success'
|
||||
iconPosition='left'
|
||||
text='Approve This Submission?'
|
||||
subtitleText='Are you sure you want to approve this submission?'
|
||||
secondaryButton={{
|
||||
text: 'Cancel',
|
||||
}}
|
||||
primaryButton={{
|
||||
text: 'Approve',
|
||||
color: 'primary',
|
||||
isLoading: isDeleteLoading,
|
||||
onClick: singleApproveHandler,
|
||||
}}
|
||||
className={{
|
||||
modalBox: 'rounded-2xl',
|
||||
}}
|
||||
>
|
||||
<div className='flex flex-col gap-4 mt-4'>
|
||||
{selectedRowIds.length === 1 ? (
|
||||
<UniformityConfirmationPreview
|
||||
uniformity={selectedUniformities[0]}
|
||||
/>
|
||||
) : (
|
||||
<div className='text-center text-gray-500'>
|
||||
{selectedRowIds.length} data dipilih
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</ConfirmationModal>
|
||||
|
||||
<ConfirmationModal
|
||||
ref={bulkApproveModal.ref}
|
||||
type='success'
|
||||
iconPosition='left'
|
||||
text={`Approve This Submission?`}
|
||||
subtitleText={`Are you sure you want to approve this submission? (${selectedRowIds.length} data)`}
|
||||
secondaryButton={{
|
||||
text: 'Cancel',
|
||||
}}
|
||||
primaryButton={{
|
||||
text: 'Approve',
|
||||
color: 'primary',
|
||||
isLoading: isBulkActionLoading,
|
||||
onClick: bulkApproveHandler,
|
||||
}}
|
||||
className={{
|
||||
modalBox: 'rounded-2xl',
|
||||
}}
|
||||
>
|
||||
<div className='flex flex-col gap-4 mt-4'>
|
||||
<UniformityConfirmationPreview uniformity={selectedUniformity} />
|
||||
</div>
|
||||
</ConfirmationModal>
|
||||
|
||||
<ConfirmationModal
|
||||
ref={singleRejectModal.ref}
|
||||
type='error'
|
||||
iconPosition='left'
|
||||
text='Reject This Submission?'
|
||||
subtitleText='Are you sure you want to reject this submission?'
|
||||
secondaryButton={{
|
||||
text: 'Cancel',
|
||||
}}
|
||||
primaryButton={{
|
||||
text: 'Reject',
|
||||
color: 'primary',
|
||||
isLoading: isDeleteLoading,
|
||||
onClick: singleRejectHandler,
|
||||
}}
|
||||
className={{
|
||||
modalBox: 'rounded-2xl',
|
||||
}}
|
||||
>
|
||||
<div className='flex flex-col gap-4 mt-4'>
|
||||
{selectedRowIds.length === 1 ? (
|
||||
<UniformityConfirmationPreview
|
||||
uniformity={selectedUniformities[0]}
|
||||
/>
|
||||
) : (
|
||||
<div className='text-center text-gray-500'>
|
||||
{selectedRowIds.length} data dipilih
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</ConfirmationModal>
|
||||
|
||||
<ConfirmationModal
|
||||
ref={bulkRejectModal.ref}
|
||||
type='error'
|
||||
iconPosition='left'
|
||||
text={`Reject This Submission?`}
|
||||
subtitleText={`Are you sure you want to reject this submission? (${selectedRowIds.length} data)`}
|
||||
secondaryButton={{
|
||||
text: 'Cancel',
|
||||
}}
|
||||
primaryButton={{
|
||||
text: 'Reject',
|
||||
color: 'primary',
|
||||
isLoading: isBulkActionLoading,
|
||||
onClick: bulkRejectHandler,
|
||||
}}
|
||||
className={{
|
||||
modalBox: 'rounded-2xl',
|
||||
}}
|
||||
>
|
||||
<div className='flex flex-col gap-4 mt-4'>
|
||||
{selectedRowIds.length === 1 ? (
|
||||
<UniformityConfirmationPreview
|
||||
uniformity={selectedUniformities[0]}
|
||||
/>
|
||||
) : (
|
||||
<div className='text-center text-gray-500'>
|
||||
{selectedRowIds.length} data dipilih
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</ConfirmationModal>
|
||||
|
||||
{/* Filter Modal */}
|
||||
<Modal
|
||||
ref={filterModal.ref}
|
||||
className={{
|
||||
modal: 'p-0',
|
||||
modalBox: 'p-0 rounded-[0.875rem]',
|
||||
}}
|
||||
>
|
||||
<div className='flex flex-col'>
|
||||
{/* Modal Header */}
|
||||
<div className='flex items-center justify-between p-4 border-b border-base-content/10'>
|
||||
<div className='flex items-center gap-2 text-primary'>
|
||||
<Icon icon='heroicons:funnel' width={20} height={20} />
|
||||
<h3 className='font-medium text-sm'>Filter Data</h3>
|
||||
</div>
|
||||
<Button
|
||||
variant='link'
|
||||
onClick={filterModal.closeModal}
|
||||
className='text-gray-500 hover:text-gray-700'
|
||||
>
|
||||
<Icon icon='heroicons:x-mark' width={20} height={20} />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<form
|
||||
className='flex flex-col'
|
||||
onSubmit={handleFormSubmit}
|
||||
onReset={handleResetFilters}
|
||||
>
|
||||
<div className='flex flex-col p-4 gap-1.5'>
|
||||
{/* Rentang Waktu */}
|
||||
<div>
|
||||
<label className='flex text-xs items-center gap-2 py-2 font-semibold'>
|
||||
Tanggal
|
||||
</label>
|
||||
<div className='flex items-center gap-2'>
|
||||
<DateInput
|
||||
required
|
||||
label='Tanggal mulai'
|
||||
name='start_date'
|
||||
placeholder='Tanggal Mulai'
|
||||
value={filterFormik.values.start_date}
|
||||
errorMessage={filterFormik.errors.start_date}
|
||||
onChange={handleFilterStartDateChange}
|
||||
onBlur={filterFormik.handleBlur}
|
||||
isError={
|
||||
filterFormik.touched.start_date &&
|
||||
Boolean(filterFormik.errors.start_date)
|
||||
}
|
||||
errorMessage={filterFormik.errors.start_date}
|
||||
className={{ wrapper: 'w-full' }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<hr className='w-full max-w-3 h-px border-base-content/10'></hr>
|
||||
<DateInput
|
||||
required
|
||||
label='Tanggal akhir'
|
||||
name='end_date'
|
||||
placeholder='Tanggal Akhir'
|
||||
value={filterFormik.values.end_date}
|
||||
errorMessage={filterFormik.errors.end_date}
|
||||
onChange={handleFilterEndDateChange}
|
||||
onBlur={filterFormik.handleBlur}
|
||||
isError={
|
||||
filterFormik.touched.end_date &&
|
||||
Boolean(filterFormik.errors.end_date)
|
||||
}
|
||||
errorMessage={filterFormik.errors.end_date}
|
||||
className={{ wrapper: 'w-full' }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1299,74 +1366,83 @@ const UniformityTable = () => {
|
||||
className={{ wrapper: 'w-full' }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{formErrorList.length > 0 && (
|
||||
<div className='w-full'>
|
||||
<AlertErrorList
|
||||
formErrorList={formErrorList}
|
||||
onClose={close}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Action Buttons */}
|
||||
<div className='flex justify-between gap-4 py-4 mt-8 border-t border-gray-300 bg-gray-100'>
|
||||
<div className='flex justify-between gap-4 p-4 border-t border-base-content/10 bg-gray-100'>
|
||||
<Button
|
||||
type='reset'
|
||||
variant='soft'
|
||||
className='ms-4 min-w-36 rounded-lg'
|
||||
onClick={handleResetFilters}
|
||||
className='rounded-lg p-3 bg-gray-100 border-gray-100 text-base-content/65 hover:bg-base-content/10'
|
||||
>
|
||||
Reset Filter
|
||||
</Button>
|
||||
<Button
|
||||
className='me-4 min-w-36 rounded-lg'
|
||||
onClick={handleApplyFilters}
|
||||
type='submit'
|
||||
className='min-w-40 text-sm p-3 text-white rounded-lg'
|
||||
>
|
||||
Apply Filter
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
</form>
|
||||
</div>
|
||||
</Modal>
|
||||
|
||||
{/* Floating Actions Button */}
|
||||
<FloatingActionsButton
|
||||
actions={[
|
||||
{
|
||||
action: 'DETAIL',
|
||||
icon: 'mdi:eye-outline',
|
||||
label: 'Lihat Detail',
|
||||
hidden: selectedRowIds.length !== 1,
|
||||
onClick() {
|
||||
router.push(
|
||||
`/production/uniformity/detail?uniformityId=${selectedRowIds[0]}`
|
||||
);
|
||||
setRowSelection({});
|
||||
},
|
||||
permissions: 'lti.production.uniformity.detail',
|
||||
{/* Floating Actions Button */}
|
||||
<FloatingActionsButton
|
||||
actions={[
|
||||
{
|
||||
action: 'DETAIL',
|
||||
icon: 'mdi:eye-outline',
|
||||
label: 'Lihat Detail',
|
||||
hidden: selectedRowIds.length !== 1,
|
||||
onClick() {
|
||||
router.push(
|
||||
`/production/uniformity/detail?uniformityId=${selectedRowIds[0]}`
|
||||
);
|
||||
setRowSelection({});
|
||||
},
|
||||
{
|
||||
action: 'DELETE',
|
||||
icon: 'mdi:delete-outline',
|
||||
label: 'Delete',
|
||||
hidden: selectedRowIds.length !== 1,
|
||||
onClick: handleDelete,
|
||||
permissions: 'lti.production.uniformity.delete',
|
||||
},
|
||||
]}
|
||||
approvals={[
|
||||
{
|
||||
action: 'APPROVED',
|
||||
icon: 'mdi:check-circle-outline',
|
||||
label: 'Approve',
|
||||
onClick: handleBulkApprove,
|
||||
permissions: 'lti.production.uniformity.approve',
|
||||
disabled: !canApproveReject,
|
||||
},
|
||||
{
|
||||
action: 'REJECTED',
|
||||
icon: 'mdi:close-circle-outline',
|
||||
label: 'Reject',
|
||||
onClick: handleBulkReject,
|
||||
permissions: 'lti.production.uniformity.approve',
|
||||
disabled: !canApproveReject,
|
||||
},
|
||||
]}
|
||||
selectedRowIds={selectedRowIds}
|
||||
onClose={handleCloseFab}
|
||||
/>
|
||||
</Card>
|
||||
permissions: 'lti.production.uniformity.detail',
|
||||
},
|
||||
{
|
||||
action: 'DELETE',
|
||||
icon: 'mdi:delete-outline',
|
||||
label: 'Delete',
|
||||
hidden: selectedRowIds.length !== 1,
|
||||
onClick: handleDelete,
|
||||
permissions: 'lti.production.uniformity.delete',
|
||||
},
|
||||
]}
|
||||
approvals={[
|
||||
{
|
||||
action: 'APPROVED',
|
||||
icon: 'mdi:check-circle-outline',
|
||||
label: 'Approve',
|
||||
onClick: handleBulkApprove,
|
||||
permissions: 'lti.production.uniformity.approve',
|
||||
disabled: !canApproveReject,
|
||||
},
|
||||
{
|
||||
action: 'REJECTED',
|
||||
icon: 'mdi:close-circle-outline',
|
||||
label: 'Reject',
|
||||
onClick: handleBulkReject,
|
||||
permissions: 'lti.production.uniformity.approve',
|
||||
disabled: !canApproveReject,
|
||||
},
|
||||
]}
|
||||
selectedRowIds={selectedRowIds}
|
||||
onClose={handleCloseFab}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -319,18 +319,19 @@ const UniformityDetail: React.FC<UniformityDetailProps> = ({
|
||||
<DrawerHeader
|
||||
leftIconHref='/production/uniformity'
|
||||
subtitle={`Details`}
|
||||
subtitleClassName='text-sm text-neutral'
|
||||
subtitleClassName='text-sm font-medium text-base-content/50'
|
||||
showDivider
|
||||
/>
|
||||
|
||||
{/* Form Section */}
|
||||
<div className='divider mt-3.5'></div>
|
||||
<section className='w-full px-6 mb-6'>
|
||||
<section className='w-full p-4'>
|
||||
{initialValues ? (
|
||||
<div className='flex flex-col gap-4'>
|
||||
{/* Info Umum */}
|
||||
<div className=''>
|
||||
<p className='text-sm font-medium mb-5'>Informasi Umum</p>
|
||||
<h2 className='text-base font-medium text-base-content/50 font-roboto mb-5'>
|
||||
Informasi Umum
|
||||
</h2>
|
||||
<Table<DetailOptionType>
|
||||
data={infoUmumTableData}
|
||||
columns={columnsInfoUmum}
|
||||
@@ -345,7 +346,9 @@ const UniformityDetail: React.FC<UniformityDetailProps> = ({
|
||||
{/* Sampling and Range */}
|
||||
{initialValues.sampling && (
|
||||
<div className=''>
|
||||
<p className='text-sm font-medium mb-5'>Sampling and Range</p>
|
||||
<h2 className='text-base font-medium text-base-content/50 font-roboto mb-5'>
|
||||
Sampling and Range
|
||||
</h2>
|
||||
<Table<DetailOptionType>
|
||||
data={samplingTableData}
|
||||
columns={columnsSampling}
|
||||
@@ -361,7 +364,9 @@ const UniformityDetail: React.FC<UniformityDetailProps> = ({
|
||||
{/* Result */}
|
||||
{initialValues.result && (
|
||||
<div className=''>
|
||||
<p className='text-sm font-medium mb-5'>Result</p>
|
||||
<h2 className='text-base font-medium text-base-content/50 font-roboto mb-5'>
|
||||
Result
|
||||
</h2>
|
||||
<Table<DetailOptionType>
|
||||
data={resultTableData}
|
||||
columns={resultColumns}
|
||||
|
||||
@@ -10,11 +10,10 @@ import {
|
||||
UniformityInfoUmum,
|
||||
} from '@/types/api/production/uniformity';
|
||||
import Table from '@/components/Table';
|
||||
import Badge from '@/components/Badge';
|
||||
import StatusBadge from '@/components/helper/StatusBadge';
|
||||
import {
|
||||
getWeightStatusColor,
|
||||
getWeightStatusIndicatorColor,
|
||||
getWeightStatusText,
|
||||
getWeightStatusBadgeColor,
|
||||
} from '@/components/pages/production/uniformity/uniformity-utils';
|
||||
import { BodyWeightData } from '@/types/api/production/uniformity';
|
||||
|
||||
@@ -51,7 +50,7 @@ const UniformityDetailsPreview = ({
|
||||
() => [
|
||||
{
|
||||
accessorKey: 'number',
|
||||
header: 'No',
|
||||
header: 'Number',
|
||||
cell: (props) => props.row.original.number,
|
||||
},
|
||||
{
|
||||
@@ -65,30 +64,12 @@ const UniformityDetailsPreview = ({
|
||||
cell: (props) => {
|
||||
const status = props.row.original.status;
|
||||
return status ? (
|
||||
<div className='w-full'>
|
||||
<Badge
|
||||
statusIndicator={true}
|
||||
variant='soft'
|
||||
className={{
|
||||
badge: `rounded-xl w-full justify-start border border-gray-200 text-black ${getWeightStatusColor(status)}`,
|
||||
status: getWeightStatusIndicatorColor(status),
|
||||
}}
|
||||
>
|
||||
{getWeightStatusText(status)}
|
||||
</Badge>
|
||||
</div>
|
||||
<StatusBadge
|
||||
color={getWeightStatusBadgeColor(status)}
|
||||
text={getWeightStatusText(status)}
|
||||
/>
|
||||
) : (
|
||||
<Badge
|
||||
statusIndicator={true}
|
||||
variant='soft'
|
||||
className={{
|
||||
badge:
|
||||
'rounded-xl w-full justify-start border border-gray-200 text-black bg-info/10',
|
||||
status: 'bg-info',
|
||||
}}
|
||||
>
|
||||
Unknown
|
||||
</Badge>
|
||||
<StatusBadge color='info' text='Unknown' />
|
||||
);
|
||||
},
|
||||
},
|
||||
@@ -102,7 +83,7 @@ const UniformityDetailsPreview = ({
|
||||
<DrawerHeader
|
||||
leftIcon=''
|
||||
subtitle={info_umum?.file_name ?? 'Uniformity Details'}
|
||||
subtitleClassName='text-sm text-neutral line-clamp-1'
|
||||
subtitleClassName='text-sm font-medium text-base-content/50 line-clamp-1'
|
||||
showDivider={false}
|
||||
>
|
||||
<button
|
||||
@@ -114,8 +95,7 @@ const UniformityDetailsPreview = ({
|
||||
</DrawerHeader>
|
||||
|
||||
{/* Form Section */}
|
||||
<div className='divider mt-3.5'></div>
|
||||
<section className='w-full px-6'>
|
||||
<section className='w-full p-4'>
|
||||
{info_umum ? (
|
||||
<div className='flex flex-col gap-4'>
|
||||
{/* Body Weight Details */}
|
||||
|
||||
@@ -493,24 +493,25 @@ const UniformityForm = ({
|
||||
<>
|
||||
<section className='w-full'>
|
||||
<DrawerHeader
|
||||
leftIcon={formType == 'add' ? 'mdi:close' : 'mdi:arrow-left'}
|
||||
leftIconSize={24}
|
||||
leftIcon={formType == 'add' ? 'heroicons:x-mark' : 'mdi:arrow-left'}
|
||||
leftIconSize={20}
|
||||
leftIconHref={
|
||||
formType == 'add'
|
||||
? '/production/uniformity'
|
||||
: `/production/uniformity/detail`
|
||||
}
|
||||
leftIconClassName='hover:text-gray-400'
|
||||
leftIconClassName='hover:text-base-content'
|
||||
subtitle={formType == 'add' ? 'Add Uniformity' : 'Update Uniformity'}
|
||||
subtitleClassName='text-sm text-neutral'
|
||||
subtitleClassName='text-sm font-medium text-base-content/50'
|
||||
showDivider
|
||||
/>
|
||||
|
||||
<div className='divider mt-3'></div>
|
||||
<section className='w-full px-6 mb-6'>
|
||||
<h2 className='text-2xl font-semibold mb-6'>Informasi Umum</h2>
|
||||
<section className='w-full p-4'>
|
||||
<h2 className='text-base font-medium text-base-content/50 font-roboto'>
|
||||
Informasi Umum
|
||||
</h2>
|
||||
|
||||
<form onSubmit={handleFormSubmit} className='flex flex-col gap-6'>
|
||||
<form onSubmit={handleFormSubmit} className='flex flex-col gap-1.5'>
|
||||
<DateInput
|
||||
required
|
||||
label='Tanggal'
|
||||
@@ -756,20 +757,23 @@ const UniformityForm = ({
|
||||
</div>
|
||||
|
||||
{!isNextStep && (
|
||||
<RequirePermission permissions='lti.production.uniformity.create'>
|
||||
<Button
|
||||
type='submit'
|
||||
color='primary'
|
||||
className='w-full'
|
||||
disabled={formik.isSubmitting}
|
||||
>
|
||||
{formik.isSubmitting ? (
|
||||
<span className='loading loading-spinner'></span>
|
||||
) : (
|
||||
'Next'
|
||||
)}
|
||||
</Button>
|
||||
</RequirePermission>
|
||||
<>
|
||||
<div className='border-t border-base-content/20 mt-3' />
|
||||
<RequirePermission permissions='lti.production.uniformity.create'>
|
||||
<Button
|
||||
type='submit'
|
||||
color='primary'
|
||||
className='w-full mt-4'
|
||||
disabled={formik.isSubmitting}
|
||||
>
|
||||
{formik.isSubmitting ? (
|
||||
<span className='loading loading-spinner'></span>
|
||||
) : (
|
||||
'Next'
|
||||
)}
|
||||
</Button>
|
||||
</RequirePermission>
|
||||
</>
|
||||
)}
|
||||
</form>
|
||||
</section>
|
||||
|
||||
@@ -50,7 +50,7 @@ const UniformityPreviewForm = () => {
|
||||
() => [
|
||||
{
|
||||
accessorKey: 'number',
|
||||
header: 'No',
|
||||
header: 'Number',
|
||||
cell: (props) => props.row.original.number,
|
||||
},
|
||||
{
|
||||
@@ -68,19 +68,18 @@ const UniformityPreviewForm = () => {
|
||||
<DrawerHeader
|
||||
leftIcon=''
|
||||
subtitle={uniformityFormData?.file_name || 'Add Body Weight'}
|
||||
subtitleClassName='text-sm text-neutral line-clamp-1'
|
||||
subtitleClassName='text-sm font-medium text-base-content/50 line-clamp-1'
|
||||
showDivider={false}
|
||||
>
|
||||
<Button variant='link' className='p-0 text-error' onClick={handleClose}>
|
||||
<Tooltip content='Hapus' position='left'>
|
||||
<Icon icon='mdi:trash-can-outline' width={20} height={20} />
|
||||
<Icon icon='heroicons-outline:trash' width={18} height={18} />
|
||||
</Tooltip>
|
||||
</Button>
|
||||
</DrawerHeader>
|
||||
|
||||
{/* Form Section */}
|
||||
<div className='divider mt-3.5'></div>
|
||||
<section className='w-full px-6'>
|
||||
<section className='w-full p-4'>
|
||||
{verifyUniformityResult ? (
|
||||
<div className='flex flex-col gap-4'>
|
||||
<Table<BodyWeightData>
|
||||
|
||||
@@ -14,12 +14,11 @@ import { useRouter } from 'next/navigation';
|
||||
import toast from 'react-hot-toast';
|
||||
import { UniformityApi } from '@/services/api/uniformity';
|
||||
import { isResponseError } from '@/lib/api-helper';
|
||||
import Badge from '@/components/Badge';
|
||||
import StatusBadge from '@/components/helper/StatusBadge';
|
||||
import { formatNumber } from '@/lib/helper';
|
||||
import {
|
||||
getWeightStatusColor,
|
||||
getWeightStatusIndicatorColor,
|
||||
getWeightStatusText,
|
||||
getWeightStatusBadgeColor,
|
||||
} from '@/components/pages/production/uniformity/uniformity-utils';
|
||||
import { DetailOptionType } from '@/types/api/production/uniformity';
|
||||
import {
|
||||
@@ -190,7 +189,7 @@ const UniformityResultForm = () => {
|
||||
() => [
|
||||
{
|
||||
accessorKey: 'number',
|
||||
header: 'No',
|
||||
header: 'Number',
|
||||
cell: (props) => props.row.original.number,
|
||||
},
|
||||
{
|
||||
@@ -204,30 +203,12 @@ const UniformityResultForm = () => {
|
||||
cell: (props) => {
|
||||
const status = props.row.original.status;
|
||||
return status ? (
|
||||
<div className='w-full'>
|
||||
<Badge
|
||||
statusIndicator={true}
|
||||
variant='soft'
|
||||
className={{
|
||||
badge: `rounded-xl w-full justify-start border border-gray-200 text-black ${getWeightStatusColor(status)}`,
|
||||
status: getWeightStatusIndicatorColor(status),
|
||||
}}
|
||||
>
|
||||
{getWeightStatusText(status)}
|
||||
</Badge>
|
||||
</div>
|
||||
<StatusBadge
|
||||
color={getWeightStatusBadgeColor(status)}
|
||||
text={getWeightStatusText(status)}
|
||||
/>
|
||||
) : (
|
||||
<Badge
|
||||
statusIndicator={true}
|
||||
variant='soft'
|
||||
className={{
|
||||
badge:
|
||||
'rounded-xl w-full justify-start border border-gray-200 text-black bg-info/10',
|
||||
status: 'bg-info',
|
||||
}}
|
||||
>
|
||||
Unknown
|
||||
</Badge>
|
||||
<StatusBadge color='info' text='Unknown' />
|
||||
);
|
||||
},
|
||||
},
|
||||
@@ -241,23 +222,24 @@ const UniformityResultForm = () => {
|
||||
<DrawerHeader
|
||||
leftIcon=''
|
||||
subtitle={uniformityFormData?.document_name || 'Uniformity Result'}
|
||||
subtitleClassName='text-sm text-neutral line-clamp-1'
|
||||
subtitleClassName='text-sm font-medium text-base-content/50 line-clamp-1'
|
||||
showDivider={false}
|
||||
>
|
||||
<Button variant='link' className='p-0 text-error' onClick={handleClose}>
|
||||
<Tooltip content='Hapus' position='left'>
|
||||
<Icon icon='mdi:trash-can-outline' width={20} height={20} />
|
||||
<Icon icon='heroicons-outline:trash' width={20} height={20} />
|
||||
</Tooltip>
|
||||
</Button>
|
||||
</DrawerHeader>
|
||||
|
||||
{/* Form Section */}
|
||||
<div className='divider mt-3.5'></div>
|
||||
<section className='w-full px-6'>
|
||||
<section className='w-full p-4'>
|
||||
{verifyUniformityResult ? (
|
||||
<div className='flex flex-col gap-4'>
|
||||
<div className=''>
|
||||
<p className='text-sm font-medium mb-5'>Sampling and Range</p>
|
||||
<h2 className='text-base font-medium text-base-content/50 font-roboto mb-5'>
|
||||
Sampling and Range
|
||||
</h2>
|
||||
<Table<DetailOptionType>
|
||||
data={samplingTableData}
|
||||
columns={columnsSampling}
|
||||
@@ -270,7 +252,9 @@ const UniformityResultForm = () => {
|
||||
</div>
|
||||
|
||||
<div className=''>
|
||||
<p className='text-sm font-medium mb-5'>Result</p>
|
||||
<h2 className='text-base font-medium text-base-content/50 font-roboto mb-5'>
|
||||
Result
|
||||
</h2>
|
||||
<Table<DetailOptionType>
|
||||
data={resultTableData}
|
||||
columns={resultColumns}
|
||||
|
||||
+24
-15
@@ -1,4 +1,3 @@
|
||||
import Button from '@/components/Button';
|
||||
import { Icon } from '@iconify/react';
|
||||
|
||||
const LeftLegend = () => {
|
||||
@@ -45,11 +44,11 @@ const ChartArea = () => {
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className='flex justify-between gap-2 sm:gap-4 md:gap-8 lg:gap-12 px-2 sm:px-4 py-2'>
|
||||
<div className='flex justify-between gap-1 xs:gap-2 sm:gap-3 md:gap-4 lg:gap-6 px-1 xs:px-2 sm:px-3 md:px-4 py-2'>
|
||||
{ranges.map((range) => (
|
||||
<div
|
||||
key={range}
|
||||
className='skeleton h-3 w-8 sm:w-12 md:w-16 shrink-0'
|
||||
className='skeleton h-3 w-6 xs:w-8 sm:w-10 md:w-12 flex-1 max-w-12 xs:max-w-14 sm:max-w-16'
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
@@ -65,28 +64,38 @@ const ChartArea = () => {
|
||||
|
||||
const EmptyState = () => {
|
||||
return (
|
||||
<>
|
||||
<div className='absolute inset-0 flex flex-col items-center justify-center z-10 gap-2'>
|
||||
<div className='border border-[#18181B]/25 rounded-2xl p-1 flex items-center justify-center my-2'>
|
||||
<Button className='rounded-2xl border border-sky-500 bg-primary text-white'>
|
||||
<Icon icon={'heroicons:funnel'} className='text-4xl text-whitd' />
|
||||
</Button>
|
||||
<div className='absolute inset-0 flex items-center justify-center z-10'>
|
||||
<div className='flex flex-col items-center justify-center'>
|
||||
{/* Filter icon */}
|
||||
<div className='mb-2'>
|
||||
<div className='w-12.5 h-12.5 bg-(--main-color-base-100,#FFFFFF) border border-base-content/10 rounded-[0.875rem] shadow-[0px_25px_50px_-12px_#00000040] flex items-center justify-center'>
|
||||
<div className='w-9.5 h-9.5 bg-primary rounded-lg border border-primary flex items-center justify-center shadow-[inset_0px_4px_4px_0px_#FFFFFF80,inset_0px_2px_0px_0px_#FFFFFF80]'>
|
||||
<Icon
|
||||
icon='heroicons:funnel'
|
||||
className='text-white'
|
||||
width={20}
|
||||
height={20}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<span className='text-xl font-semibold text-[#18181B80] leading-5'>
|
||||
|
||||
{/* Empty state text */}
|
||||
<h3 className='text-base-content/50 font-semibold text-sm mb-1'>
|
||||
No Filters Selected
|
||||
</span>
|
||||
<span className='text-xs font-light text-[#18181B80] leading-4 text-center max-w-xs px-4'>
|
||||
</h3>
|
||||
<p className='text-base-content/50 text-xs text-center max-w-xs'>
|
||||
Please choose filters to narrow down your results and make your search
|
||||
easier.
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
</>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const UniformityBarChartSkeleton = () => {
|
||||
return (
|
||||
<div className='relative w-full h-full min-h-[300px] xl:min-h-[350px]'>
|
||||
<div className='relative w-full h-full min-h-[270px] xl:min-h-[330px]'>
|
||||
<div className='sm:flex hidden h-full gap-4'>
|
||||
<LeftLegend />
|
||||
<ChartArea />
|
||||
|
||||
+19
-13
@@ -1,4 +1,3 @@
|
||||
import Button from '@/components/Button';
|
||||
import { Icon } from '@iconify/react';
|
||||
import React from 'react';
|
||||
import { Cell, Pie, PieChart, ResponsiveContainer } from 'recharts';
|
||||
@@ -55,22 +54,29 @@ const UniformityGaugeChartSkeleton: React.FC<
|
||||
</Pie>
|
||||
</PieChart>
|
||||
</ResponsiveContainer>
|
||||
<div className='absolute inset-x-0 top-24 flex flex-col items-center justify-center'>
|
||||
<div className='border border-[#18181B]/25 rounded-2xl p-1 flex items-center justify-center mt-5'>
|
||||
<Button className='rounded-2xl border border-sky-500 bg-primary text-white'>
|
||||
<Icon
|
||||
icon={'heroicons:funnel'}
|
||||
className='text-4xl text-whitd'
|
||||
/>
|
||||
</Button>
|
||||
<div className='absolute inset-x-0 top-24 flex flex-col items-center justify-center mt-4'>
|
||||
{/* Filter icon */}
|
||||
<div className='mb-2 mt-5'>
|
||||
<div className='w-12.5 h-12.5 bg-(--main-color-base-100,#FFFFFF) border border-base-content/10 rounded-[0.875rem] shadow-[0px_25px_50px_-12px_#00000040] flex items-center justify-center'>
|
||||
<div className='w-9.5 h-9.5 bg-primary rounded-lg border border-primary flex items-center justify-center shadow-[inset_0px_4px_4px_0px_#FFFFFF80,inset_0px_2px_0px_0px_#FFFFFF80]'>
|
||||
<Icon
|
||||
icon='heroicons:funnel'
|
||||
className='text-white'
|
||||
width={20}
|
||||
height={20}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<span className='text-lg font-semibold text-[#18181B80] leading-5 my-3'>
|
||||
|
||||
{/* Empty state text */}
|
||||
<h3 className='text-base-content/50 font-semibold text-sm mb-1'>
|
||||
No Filters Selected
|
||||
</span>
|
||||
<span className='text-xs font-light text-[#18181B80] leading-4 text-center max-w-xs px-4'>
|
||||
</h3>
|
||||
<p className='text-base-content/50 text-xs text-center max-w-xs'>
|
||||
Please choose filters to narrow down your results and make your
|
||||
search easier.
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,24 +1,30 @@
|
||||
import Button from '@/components/Button';
|
||||
import { Icon } from '@iconify/react';
|
||||
|
||||
const UniformityTableSkeleton = () => {
|
||||
return (
|
||||
<div className='flex flex-col items-center justify-center gap-2 my-20'>
|
||||
<div className='border border-[#18181B]/25 rounded-2xl p-1 flex items-center justify-center'>
|
||||
<Button className='rounded-2xl border border-sky-500 bg-primary text-white'>
|
||||
<Icon
|
||||
icon={'heroicons-outline:chart-bar'}
|
||||
className='text-4xl text-whitd'
|
||||
/>
|
||||
</Button>
|
||||
<div className='flex flex-col items-center justify-center my-20'>
|
||||
{/* Document icon */}
|
||||
<div className='mb-2'>
|
||||
<div className='w-12.5 h-12.5 bg-(--main-color-base-100,#FFFFFF) border border-base-content/10 rounded-[0.875rem] shadow-[0px_25px_50px_-12px_#00000040] flex items-center justify-center'>
|
||||
<div className='w-9.5 h-9.5 bg-primary rounded-lg border border-primary flex items-center justify-center shadow-[inset_0px_4px_4px_0px_#FFFFFF80,inset_0px_2px_0px_0px_#FFFFFF80]'>
|
||||
<Icon
|
||||
icon='heroicons:document-text'
|
||||
className='text-white'
|
||||
width={20}
|
||||
height={20}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<span className='text-xl font-semibold text-[#18181B80] leading-5'>
|
||||
|
||||
{/* Empty state text */}
|
||||
<h3 className='text-base-content/50 font-semibold text-sm mb-1'>
|
||||
No Data Available
|
||||
</span>
|
||||
<span className='text-xs font-light text-[#18181B80] leading-4 text-center max-w-xs px-4'>
|
||||
</h3>
|
||||
<p className='text-base-content/50 text-xs text-center max-w-xs'>
|
||||
There is no uniformity data displayed. Enter uniformity check data to
|
||||
get started.
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -25,6 +25,20 @@ export const getWeightStatusText = (status: string): string => {
|
||||
return weightStatusTextMap[status] || status;
|
||||
};
|
||||
|
||||
export const weightStatusBadgeColorMap: Record<
|
||||
string,
|
||||
'success' | 'error' | 'neutral' | 'info'
|
||||
> = {
|
||||
ideal: 'success',
|
||||
outside: 'error',
|
||||
};
|
||||
|
||||
export const getWeightStatusBadgeColor = (
|
||||
status: string
|
||||
): 'success' | 'error' | 'neutral' | 'info' => {
|
||||
return weightStatusBadgeColorMap[status] || 'neutral';
|
||||
};
|
||||
|
||||
export const statusColorMap: Record<string, string> = {
|
||||
APPROVED: 'bg-[#00D39033]',
|
||||
Disetujui: 'bg-[#00D39033]',
|
||||
@@ -63,3 +77,29 @@ export const getStatusIndicatorColor = (status: string): string => {
|
||||
export const getStatusText = (status: string): string => {
|
||||
return statusTextMap[status] || status;
|
||||
};
|
||||
|
||||
export const statusBadgeColorMap: Record<
|
||||
string,
|
||||
'success' | 'error' | 'neutral' | 'info'
|
||||
> = {
|
||||
APPROVED: 'success',
|
||||
Disetujui: 'success',
|
||||
approved: 'success',
|
||||
disetujui: 'success',
|
||||
REJECTED: 'error',
|
||||
Ditolak: 'error',
|
||||
rejected: 'error',
|
||||
ditolak: 'error',
|
||||
CREATED: 'neutral',
|
||||
Pengajuan: 'neutral',
|
||||
created: 'neutral',
|
||||
pengajuan: 'neutral',
|
||||
PENDING: 'neutral',
|
||||
pending: 'neutral',
|
||||
};
|
||||
|
||||
export const getStatusBadgeColor = (
|
||||
status: string
|
||||
): 'success' | 'error' | 'neutral' | 'info' => {
|
||||
return statusBadgeColorMap[status] || 'neutral';
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user