mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-20 13:32:00 +00:00
refactor(FE-114): enhance type safety and improve checkbox input handling
This commit is contained in:
@@ -16,13 +16,31 @@ import RowDropdownOptions from '@/components/table/RowDropdownOptions';
|
|||||||
import RowCollapseOptions from '@/components/table/RowCollapseOptions';
|
import RowCollapseOptions from '@/components/table/RowCollapseOptions';
|
||||||
import { type CellContext } from '@tanstack/react-table';
|
import { type CellContext } from '@tanstack/react-table';
|
||||||
import { type Recording } from '@/types/api/production/recording';
|
import { type Recording } from '@/types/api/production/recording';
|
||||||
|
import { type ProjectFlock } from '@/types/api/production/project-flock';
|
||||||
|
|
||||||
const dummyRecordings: Recording[] = [
|
// Extended type that includes related data
|
||||||
|
type RecordingWithRelations = Recording & {
|
||||||
|
project_flock?: ProjectFlock;
|
||||||
|
};
|
||||||
|
|
||||||
|
const dummyRecordings: RecordingWithRelations[] = [
|
||||||
{
|
{
|
||||||
id: 1,
|
id: 1,
|
||||||
flock: {
|
project_flock_kandang_id: 1,
|
||||||
id: 1,
|
record_date: '2024-01-01',
|
||||||
name: 'Flock Recording 1',
|
ontime: true,
|
||||||
|
day: 10,
|
||||||
|
status: 1,
|
||||||
|
total_depletion: 10,
|
||||||
|
cum_depletion_rate: 1.0,
|
||||||
|
daily_gain: 50,
|
||||||
|
avg_daily_gain: 5.0,
|
||||||
|
cum_intake: 200,
|
||||||
|
fcr_value: 1.5,
|
||||||
|
total_chick: 1000,
|
||||||
|
daily_depletion_rate: 0.5,
|
||||||
|
cum_depletion: 20,
|
||||||
|
record_datetime: '2024-01-01T08:00:00Z',
|
||||||
created_at: '2024-01-01',
|
created_at: '2024-01-01',
|
||||||
updated_at: '2024-01-01',
|
updated_at: '2024-01-01',
|
||||||
created_user: {
|
created_user: {
|
||||||
@@ -30,88 +48,10 @@ const dummyRecordings: Recording[] = [
|
|||||||
id_user: 1,
|
id_user: 1,
|
||||||
email: 'admin@example.com',
|
email: 'admin@example.com',
|
||||||
name: 'Admin',
|
name: 'Admin',
|
||||||
},
|
image: null,
|
||||||
},
|
npk: '0001',
|
||||||
recording_date: '2024-01-01',
|
|
||||||
location: {
|
|
||||||
id: 1,
|
|
||||||
name: 'Location 1',
|
|
||||||
address: 'Jl. Contoh No. 1',
|
|
||||||
area: {
|
|
||||||
id: 1,
|
|
||||||
name: 'Area 1',
|
|
||||||
},
|
|
||||||
created_at: '2024-01-01',
|
created_at: '2024-01-01',
|
||||||
updated_at: '2024-01-01',
|
updated_at: '2024-01-01',
|
||||||
created_user: {
|
|
||||||
id: 1,
|
|
||||||
id_user: 1,
|
|
||||||
email: 'admin@example.com',
|
|
||||||
name: 'Admin',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
coop: {
|
|
||||||
id: 1,
|
|
||||||
name: 'Coop 1',
|
|
||||||
status: 'ACTIVE',
|
|
||||||
location: {
|
|
||||||
id: 1,
|
|
||||||
name: 'Location 1',
|
|
||||||
address: 'Jl. Contoh No. 1',
|
|
||||||
area: {
|
|
||||||
id: 1,
|
|
||||||
name: 'Area 1',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
pic: {
|
|
||||||
id: 1,
|
|
||||||
id_user: 1,
|
|
||||||
email: 'pic@example.com',
|
|
||||||
name: 'PIC User',
|
|
||||||
},
|
|
||||||
created_at: '2024-01-01',
|
|
||||||
updated_at: '2024-01-01',
|
|
||||||
created_user: {
|
|
||||||
id: 1,
|
|
||||||
id_user: 1,
|
|
||||||
email: 'admin@example.com',
|
|
||||||
name: 'Admin',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
feed_data: [
|
|
||||||
{
|
|
||||||
feed_name: 'Feed 1',
|
|
||||||
feed_qty: 100,
|
|
||||||
feed_stock: 500,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
body_weight: [
|
|
||||||
{
|
|
||||||
chicken_weight: 2.5,
|
|
||||||
chicken_count: 1000,
|
|
||||||
average_chicken_weight: 2.5,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
vaccination: [
|
|
||||||
{
|
|
||||||
vaccine_name: 'Vaccine 1',
|
|
||||||
total_stock: 200,
|
|
||||||
used_stock: 150,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
mortality: [
|
|
||||||
{
|
|
||||||
condition: 'NORMAL',
|
|
||||||
count: 5,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
created_at: '2024-01-01',
|
|
||||||
updated_at: '2024-01-01',
|
|
||||||
created_user: {
|
|
||||||
id: 1,
|
|
||||||
id_user: 1,
|
|
||||||
email: 'admin@example.com',
|
|
||||||
name: 'Admin',
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
@@ -122,7 +62,7 @@ const RowOptionsMenu = ({
|
|||||||
deleteClickHandler,
|
deleteClickHandler,
|
||||||
}: {
|
}: {
|
||||||
type: 'dropdown' | 'collapse';
|
type: 'dropdown' | 'collapse';
|
||||||
props: CellContext<Recording, unknown>;
|
props: CellContext<RecordingWithRelations, unknown>;
|
||||||
deleteClickHandler: () => void;
|
deleteClickHandler: () => void;
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
@@ -178,7 +118,7 @@ const RecordingTable = () => {
|
|||||||
const [pageSize, setPageSize] = useState(10);
|
const [pageSize, setPageSize] = useState(10);
|
||||||
const [sorting, setSorting] = useState<SortingState>([]);
|
const [sorting, setSorting] = useState<SortingState>([]);
|
||||||
const [selectedRecordings, setSelectedRecordings] = useState<number[]>([]);
|
const [selectedRecordings, setSelectedRecordings] = useState<number[]>([]);
|
||||||
const [, setSelectedRecording] = useState<Recording | undefined>(undefined);
|
const [, setSelectedRecording] = useState<RecordingWithRelations | undefined>(undefined);
|
||||||
const [isDeleteLoading, setIsDeleteLoading] = useState(false);
|
const [isDeleteLoading, setIsDeleteLoading] = useState(false);
|
||||||
const [isBulkApproveLoading, setIsBulkApproveLoading] = useState(false);
|
const [isBulkApproveLoading, setIsBulkApproveLoading] = useState(false);
|
||||||
const [isBulkRejectLoading, setIsBulkRejectLoading] = useState(false);
|
const [isBulkRejectLoading, setIsBulkRejectLoading] = useState(false);
|
||||||
@@ -206,10 +146,15 @@ const RecordingTable = () => {
|
|||||||
|
|
||||||
const paginatedData = useMemo(() => {
|
const paginatedData = useMemo(() => {
|
||||||
const filteredData = dummyRecordings.filter(
|
const filteredData = dummyRecordings.filter(
|
||||||
(recording) =>
|
(recording: RecordingWithRelations) => {
|
||||||
recording.flock.name.toLowerCase().includes(search.toLowerCase()) ||
|
const projectName = recording.project_flock?.name || '';
|
||||||
recording.location.name.toLowerCase().includes(search.toLowerCase()) ||
|
const locationName = recording.project_flock?.location?.name || '';
|
||||||
recording.coop.name.toLowerCase().includes(search.toLowerCase())
|
const coopName = recording.project_flock?.kandangs?.[0]?.name || '';
|
||||||
|
|
||||||
|
return projectName.toLowerCase().includes(search.toLowerCase()) ||
|
||||||
|
locationName.toLowerCase().includes(search.toLowerCase()) ||
|
||||||
|
coopName.toLowerCase().includes(search.toLowerCase());
|
||||||
|
}
|
||||||
);
|
);
|
||||||
const start = (page - 1) * pageSize;
|
const start = (page - 1) * pageSize;
|
||||||
return filteredData.slice(start, start + pageSize);
|
return filteredData.slice(start, start + pageSize);
|
||||||
@@ -383,35 +328,34 @@ const RecordingTable = () => {
|
|||||||
cell: (props) => pageSize * (page - 1) + props.row.index + 1,
|
cell: (props) => pageSize * (page - 1) + props.row.index + 1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: 'flock.name',
|
|
||||||
header: 'Flock',
|
header: 'Flock',
|
||||||
|
cell: (props) => props.row.original.project_flock?.name || '-',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: 'recording_date',
|
accessorKey: 'record_date',
|
||||||
header: 'Tanggal Recording',
|
header: 'Tanggal Recording',
|
||||||
cell: (props) =>
|
cell: (props) =>
|
||||||
new Date(props.row.original.recording_date).toLocaleDateString(),
|
new Date(props.row.original.record_date).toLocaleDateString(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: 'location.name',
|
|
||||||
header: 'Lokasi',
|
header: 'Lokasi',
|
||||||
|
cell: (props) => props.row.original.project_flock?.location?.name || '-',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: 'coop.name',
|
|
||||||
header: 'Kandang',
|
header: 'Kandang',
|
||||||
|
cell: (props) => {
|
||||||
|
const coopName = props.row.original.project_flock?.kandangs?.[0]?.name;
|
||||||
|
return coopName || '-';
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: 'mortality',
|
accessorKey: 'total_depletion',
|
||||||
header: 'Total Mortality',
|
header: 'Total Depletion',
|
||||||
cell: (props) =>
|
cell: (props) => props.row.original.total_depletion,
|
||||||
props.row.original.mortality.reduce(
|
|
||||||
(acc, curr) => acc + curr.count,
|
|
||||||
0
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
header: 'Aksi',
|
header: 'Aksi',
|
||||||
cell: (props: CellContext<Recording, unknown>) => {
|
cell: (props: CellContext<RecordingWithRelations, unknown>) => {
|
||||||
const currentPageSize =
|
const currentPageSize =
|
||||||
props.table.getPaginationRowModel().rows.length;
|
props.table.getPaginationRowModel().rows.length;
|
||||||
const currentPageRows =
|
const currentPageRows =
|
||||||
|
|||||||
@@ -334,7 +334,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
label='On Time'
|
label='On Time'
|
||||||
name='ontime'
|
name='ontime'
|
||||||
checked={formik.values.ontime || false}
|
checked={formik.values.ontime || false}
|
||||||
onChange={(e) => {
|
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
formik.setFieldValue('ontime', e.target.checked);
|
formik.setFieldValue('ontime', e.target.checked);
|
||||||
}}
|
}}
|
||||||
disabled={type === 'detail'}
|
disabled={type === 'detail'}
|
||||||
@@ -363,7 +363,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
selectedBodyWeights.length &&
|
selectedBodyWeights.length &&
|
||||||
formik.values.body_weights?.length > 0
|
formik.values.body_weights?.length > 0
|
||||||
}
|
}
|
||||||
onChange={(e) => {
|
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
if (e.target.checked) {
|
if (e.target.checked) {
|
||||||
setSelectedBodyWeights(
|
setSelectedBodyWeights(
|
||||||
formik.values.body_weights?.map(
|
formik.values.body_weights?.map(
|
||||||
@@ -374,8 +374,6 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
setSelectedBodyWeights([]);
|
setSelectedBodyWeights([]);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
naked={true}
|
|
||||||
size='sm'
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</th>
|
</th>
|
||||||
@@ -411,7 +409,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
<CheckboxInput
|
<CheckboxInput
|
||||||
name={`body-weight-${idx}`}
|
name={`body-weight-${idx}`}
|
||||||
checked={selectedBodyWeights.includes(idx)}
|
checked={selectedBodyWeights.includes(idx)}
|
||||||
onChange={(e) => {
|
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
if (e.target.checked) {
|
if (e.target.checked) {
|
||||||
setSelectedBodyWeights([
|
setSelectedBodyWeights([
|
||||||
...selectedBodyWeights,
|
...selectedBodyWeights,
|
||||||
@@ -423,8 +421,6 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
naked={true}
|
|
||||||
size='sm'
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
@@ -558,7 +554,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
selectedStocks.length &&
|
selectedStocks.length &&
|
||||||
formik.values.stocks?.length > 0
|
formik.values.stocks?.length > 0
|
||||||
}
|
}
|
||||||
onChange={(e) => {
|
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
if (e.target.checked) {
|
if (e.target.checked) {
|
||||||
setSelectedStocks(
|
setSelectedStocks(
|
||||||
formik.values.stocks?.map((_, idx) => idx) ??
|
formik.values.stocks?.map((_, idx) => idx) ??
|
||||||
@@ -568,8 +564,6 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
setSelectedStocks([]);
|
setSelectedStocks([]);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
naked={true}
|
|
||||||
size='sm'
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</th>
|
</th>
|
||||||
@@ -599,7 +593,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
<CheckboxInput
|
<CheckboxInput
|
||||||
name={`stock-${idx}`}
|
name={`stock-${idx}`}
|
||||||
checked={selectedStocks.includes(idx)}
|
checked={selectedStocks.includes(idx)}
|
||||||
onChange={(e) => {
|
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
if (e.target.checked) {
|
if (e.target.checked) {
|
||||||
setSelectedStocks([...selectedStocks, idx]);
|
setSelectedStocks([...selectedStocks, idx]);
|
||||||
} else {
|
} else {
|
||||||
@@ -608,8 +602,6 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
naked={true}
|
|
||||||
size='sm'
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
@@ -796,7 +788,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
selectedDepletions.length &&
|
selectedDepletions.length &&
|
||||||
formik.values.depletions?.length > 0
|
formik.values.depletions?.length > 0
|
||||||
}
|
}
|
||||||
onChange={(e) => {
|
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
if (e.target.checked) {
|
if (e.target.checked) {
|
||||||
setSelectedDepletions(
|
setSelectedDepletions(
|
||||||
formik.values.depletions?.map(
|
formik.values.depletions?.map(
|
||||||
@@ -807,8 +799,6 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
setSelectedDepletions([]);
|
setSelectedDepletions([]);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
naked={true}
|
|
||||||
size='sm'
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</th>
|
</th>
|
||||||
@@ -853,7 +843,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
<CheckboxInput
|
<CheckboxInput
|
||||||
name={`depletion-${idx}`}
|
name={`depletion-${idx}`}
|
||||||
checked={selectedDepletions.includes(idx)}
|
checked={selectedDepletions.includes(idx)}
|
||||||
onChange={(e) => {
|
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
if (e.target.checked) {
|
if (e.target.checked) {
|
||||||
setSelectedDepletions([
|
setSelectedDepletions([
|
||||||
...selectedDepletions,
|
...selectedDepletions,
|
||||||
@@ -865,8 +855,6 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
naked={true}
|
|
||||||
size='sm'
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
|
|||||||
Reference in New Issue
Block a user