feat: slicing modal for backoffice 'validasi transaksi' page

- Tambah fungsi untuk handle aksi ketika button pada modal ditekan
This commit is contained in:
Hafid Nur
2025-03-31 21:40:00 +07:00
parent 3b979119ed
commit 989503f930
8 changed files with 255 additions and 136 deletions
@@ -4,14 +4,14 @@ import { InputForm, Modal } from '@imphnen-frontend-service/ui/molecules';
interface IModalAddItem { interface IModalAddItem {
isOpen: boolean; isOpen: boolean;
onClose: () => void; onClose: () => void;
onAddItem: () => void; handleAddItem: () => void;
currentStep?: number; currentStep?: number;
} }
const ModalAddItem = ({ const ModalAddItem = ({
isOpen, isOpen,
onClose, onClose,
onAddItem, handleAddItem,
currentStep = 1, currentStep = 1,
}: IModalAddItem) => { }: IModalAddItem) => {
return ( return (
@@ -32,6 +32,7 @@ const ModalAddItem = ({
{currentStep === 2 && ( {currentStep === 2 && (
<StepTwo <StepTwo
onClose={onClose} onClose={onClose}
handleAddItem={handleAddItem}
resetStep={() => { resetStep={() => {
console.log('Reset step'); console.log('Reset step');
}} }}
@@ -92,9 +93,10 @@ const StepOne = ({ nextStep, onClose }: IStepOneProps) => (
interface IStepTwoProps { interface IStepTwoProps {
onClose: () => void; onClose: () => void;
resetStep: () => void; resetStep: () => void;
handleAddItem?: () => void;
} }
const StepTwo = ({ onClose, resetStep }: IStepTwoProps) => ( const StepTwo = ({ onClose, handleAddItem, resetStep }: IStepTwoProps) => (
<> <>
<Modal.Header className="mb-0 text-center items-center"> <Modal.Header className="mb-0 text-center items-center">
<h2 className="text-p1 font-semibold text-primary-500 mb-3"> <h2 className="text-p1 font-semibold text-primary-500 mb-3">
@@ -106,10 +108,22 @@ const StepTwo = ({ onClose, resetStep }: IStepTwoProps) => (
</p> </p>
</Modal.Header> </Modal.Header>
<Modal.Content className="flex gap-4"> <Modal.Content className="flex gap-4">
<Button variant="bordered" size="lg" className="w-full" onClick={onClose}> <Button
variant="bordered"
size="lg"
className="w-full"
onClick={resetStep}
>
Batal Batal
</Button> </Button>
<Button variant="primary" size="lg" className="w-full"> <Button
variant="primary"
size="lg"
className="w-full"
onClick={() => {
handleAddItem && handleAddItem();
}}
>
Tambahkan Tambahkan
</Button> </Button>
</Modal.Content> </Modal.Content>
@@ -4,13 +4,13 @@ import { Modal } from '@imphnen-frontend-service/ui/molecules';
interface IModalDeleteItem { interface IModalDeleteItem {
isOpen: boolean; isOpen: boolean;
onClose: () => void; onClose: () => void;
onDeleteItem: () => void; handleDeleteItem?: () => void;
} }
const ModalDeleteItem = ({ const ModalDeleteItem = ({
isOpen, isOpen,
onClose, onClose,
onDeleteItem, handleDeleteItem,
}: IModalDeleteItem) => { }: IModalDeleteItem) => {
return ( return (
<Modal <Modal
@@ -48,7 +48,9 @@ const ModalDeleteItem = ({
variant="danger" variant="danger"
size="lg" size="lg"
className="w-full" className="w-full"
onClick={() => onDeleteItem()} onClick={() => {
handleDeleteItem && handleDeleteItem();
}}
> >
Hapus Item Hapus Item
</Button> </Button>
@@ -4,14 +4,14 @@ import { InputForm, Modal } from '@imphnen-frontend-service/ui/molecules';
interface IModalEditItem { interface IModalEditItem {
isOpen: boolean; isOpen: boolean;
onClose: () => void; onClose: () => void;
onEditItem: () => void; handleEditItem?: () => void;
currentStep?: number; currentStep?: number;
} }
const ModalEditItem = ({ const ModalEditItem = ({
isOpen, isOpen,
onClose, onClose,
onEditItem, handleEditItem,
currentStep = 1, currentStep = 1,
}: IModalEditItem) => { }: IModalEditItem) => {
return ( return (
@@ -81,7 +81,7 @@ const StepOne = ({ nextStep, onClose }: IStepOneProps) => (
/> />
</div> </div>
<Button variant="primary" size="lg" className="w-full"> <Button variant="primary" size="lg" className="w-full" onClick={nextStep}>
Perbarui Item Perbarui Item
</Button> </Button>
</Modal.Content> </Modal.Content>
@@ -90,10 +90,11 @@ const StepOne = ({ nextStep, onClose }: IStepOneProps) => (
interface IStepTwoProps { interface IStepTwoProps {
onClose: () => void; onClose: () => void;
handleEditItem?: () => void;
resetStep: () => void; resetStep: () => void;
} }
const StepTwo = ({ onClose, resetStep }: IStepTwoProps) => ( const StepTwo = ({ onClose, handleEditItem, resetStep }: IStepTwoProps) => (
<> <>
<Modal.Header className="mb-0 text-center items-center"> <Modal.Header className="mb-0 text-center items-center">
<h2 className="text-p1 font-semibold text-primary-500 mb-3"> <h2 className="text-p1 font-semibold text-primary-500 mb-3">
@@ -105,10 +106,22 @@ const StepTwo = ({ onClose, resetStep }: IStepTwoProps) => (
</p> </p>
</Modal.Header> </Modal.Header>
<Modal.Content className="flex gap-4"> <Modal.Content className="flex gap-4">
<Button variant="bordered" size="lg" className="w-full" onClick={onClose}> <Button
variant="bordered"
size="lg"
className="w-full"
onClick={resetStep}
>
Batal Batal
</Button> </Button>
<Button variant="primary" size="lg" className="w-full"> <Button
variant="primary"
size="lg"
className="w-full"
onClick={() => {
handleEditItem && handleEditItem();
}}
>
Update Update
</Button> </Button>
</Modal.Content> </Modal.Content>
+4 -4
View File
@@ -161,16 +161,16 @@ export const Components: FC = (): ReactElement => {
<ModalAddItem <ModalAddItem
isOpen={showModalAddItem} isOpen={showModalAddItem}
onClose={() => setShowModalAddItem(false)} onClose={() => setShowModalAddItem(false)}
onAddItem={() => { handleAddItem={() => {
console.log('Item added'); console.log('Item added');
}} }}
/> />
{/* Modal Add Item */} {/* Modal Edit Item */}
<ModalEditItem <ModalEditItem
isOpen={showModalEditItem} isOpen={showModalEditItem}
onClose={() => setShowModalEditItem(false)} onClose={() => setShowModalEditItem(false)}
onEditItem={() => { handleEditItem={() => {
console.log('Item edited'); console.log('Item edited');
}} }}
/> />
@@ -179,7 +179,7 @@ export const Components: FC = (): ReactElement => {
<ModalDeleteItem <ModalDeleteItem
isOpen={showModalDeleteItem} isOpen={showModalDeleteItem}
onClose={() => setShowModalDeleteItem(false)} onClose={() => setShowModalDeleteItem(false)}
onDeleteItem={() => { handleDeleteItem={() => {
console.log('Item deleted'); console.log('Item deleted');
}} }}
/> />
@@ -4,10 +4,14 @@ import { InputForm, Modal } from '@imphnen-frontend-service/ui/molecules';
interface IModalProcessDelivery { interface IModalProcessDelivery {
isOpen: boolean; isOpen: boolean;
onClose: () => void; onClose: () => void;
onProcessDelivery?: () => void; handleProcessDelivery?: () => void;
} }
const ModalProcessDelivery = ({ isOpen, onClose }: IModalProcessDelivery) => { const ModalProcessDelivery = ({
isOpen,
onClose,
handleProcessDelivery,
}: IModalProcessDelivery) => {
return ( return (
<Modal <Modal
className="min-w-[400px] bg-primary-50 rounded-lg p-[40px] flex flex-col gap-8 text-center" className="min-w-[400px] bg-primary-50 rounded-lg p-[40px] flex flex-col gap-8 text-center"
@@ -33,6 +37,7 @@ const ModalProcessDelivery = ({ isOpen, onClose }: IModalProcessDelivery) => {
value="Ahmad Wiyana" value="Ahmad Wiyana"
size="lg" size="lg"
className="w-full" className="w-full"
readOnly
/> />
<InputForm <InputForm
label="Item yang didapatkan" label="Item yang didapatkan"
@@ -41,6 +46,7 @@ const ModalProcessDelivery = ({ isOpen, onClose }: IModalProcessDelivery) => {
value="Lanyard + ID Card" value="Lanyard + ID Card"
size="lg" size="lg"
className="w-full" className="w-full"
readOnly
/> />
<InputForm <InputForm
label="Alamat Pengiriman" label="Alamat Pengiriman"
@@ -49,6 +55,7 @@ const ModalProcessDelivery = ({ isOpen, onClose }: IModalProcessDelivery) => {
value="Jl. Pantai Cibaduyut Indah" value="Jl. Pantai Cibaduyut Indah"
size="lg" size="lg"
className="w-full" className="w-full"
readOnly
/> />
<InputForm <InputForm
label="Status" label="Status"
@@ -57,10 +64,16 @@ const ModalProcessDelivery = ({ isOpen, onClose }: IModalProcessDelivery) => {
value="Lanyard + ID Card" value="Lanyard + ID Card"
size="lg" size="lg"
className="w-full" className="w-full"
readOnly
/> />
</div> </div>
<Button variant="primary" size="lg" className="w-full"> <Button
variant="primary"
size="lg"
className="w-full"
onClick={() => handleProcessDelivery && handleProcessDelivery()}
>
Proses Pengiriman Proses Pengiriman
</Button> </Button>
</Modal.Content> </Modal.Content>
+3 -2
View File
@@ -155,6 +155,7 @@ export const Components: FC = (): ReactElement => {
variant="primary" variant="primary"
size="sm" size="sm"
onClick={(e) => { onClick={(e) => {
e.stopPropagation();
setShowModalProcessDelivery(true); setShowModalProcessDelivery(true);
}} }}
className="flex items-center gap-2 w-full" className="flex items-center gap-2 w-full"
@@ -227,8 +228,8 @@ export const Components: FC = (): ReactElement => {
<ModalProcessDelivery <ModalProcessDelivery
isOpen={showModalProcessDelivery} isOpen={showModalProcessDelivery}
onClose={() => setShowModalProcessDelivery(false)} onClose={() => setShowModalProcessDelivery(false)}
onProcessDelivery={() => { handleProcessDelivery={() => {
console.log('Item added'); console.log('Action ketika user menekan tombol Proses Pengiriman');
}} }}
/> />
</Fragment> </Fragment>
@@ -0,0 +1,61 @@
import { Button } from '@imphnen-frontend-service/ui/atoms';
import { InputForm, Modal } from '@imphnen-frontend-service/ui/molecules';
interface IModalValidate {
isOpen: boolean;
onClose: () => void;
handleValid?: () => void;
handleInvalid?: () => void;
}
const ModalValidate = ({
isOpen,
onClose,
handleValid,
handleInvalid,
}: IModalValidate) => {
return (
<Modal
className="min-w-[400px] bg-primary-50 rounded-lg p-[40px] flex flex-col gap-8 text-center"
isOpen={isOpen}
onClose={onClose}
disableEscapeKeyDown={true}
>
<Modal.Header className="mb-0 text-center items-center">
<h2 className="text-p1 font-semibold text-primary-500 mb-3">
Validasi Transaksi
</h2>
</Modal.Header>
<Modal.Content className="flex flex-col gap-8">
<InputForm
label="Nomor Transaksi"
type="text"
placeholder="Masukkan Nomor Transaksi"
value="2502133Y9AFVBO"
size="lg"
className="w-full"
/>
<div className="flex gap-4">
<Button
variant="bordered"
size="lg"
className="w-full border-danger-500 text-danger-500 hover:border-danger-700 hover:text-danger-700"
onClick={() => handleInvalid && handleInvalid()}
>
Tidak Valid
</Button>
<Button
variant="primary"
size="lg"
className="w-full"
onClick={() => handleValid && handleValid()}
>
Valid
</Button>
</div>
</Modal.Content>
</Modal>
);
};
export default ModalValidate;
+128 -113
View File
@@ -1,5 +1,5 @@
import * as React from 'react'; import * as React from 'react';
import { FC, ReactElement, useState } from 'react'; import { FC, Fragment, ReactElement, useState } from 'react';
import { import {
FilterOutlined, FilterOutlined,
SearchOutlined, SearchOutlined,
@@ -16,6 +16,7 @@ import {
useReactTable, useReactTable,
RowSelectionState, RowSelectionState,
} from '@tanstack/react-table'; } from '@tanstack/react-table';
import ModalValidate from './_components/modal-validate';
type TransactionStatus = 'valid' | 'invalid' | 'unchecked'; type TransactionStatus = 'valid' | 'invalid' | 'unchecked';
@@ -38,81 +39,9 @@ const mockTransactions: Transaction[] = Array.from({ length: 20 }, (_, i) => ({
: 'valid') as TransactionStatus, : 'valid') as TransactionStatus,
})); }));
const columns: ColumnDef<Transaction>[] = [
{
id: 'select',
header: ({ table }) => (
<input
type="checkbox"
className="rounded"
checked={table.getIsAllRowsSelected()}
onChange={table.getToggleAllRowsSelectedHandler()}
/>
),
cell: ({ row }) => (
<input
type="checkbox"
className="rounded"
checked={row.getIsSelected()}
onChange={row.getToggleSelectedHandler()}
/>
),
},
{
header: 'No',
accessorKey: 'id',
},
{
header: 'Nama Lengkap',
accessorKey: 'name',
},
{
header: 'Nomor Transaksi',
accessorKey: 'transactionNumber',
},
{
header: 'Order Valid?',
accessorKey: 'status',
cell: ({ row }) => {
const status = row.original.status;
const statusColors: Record<TransactionStatus, string> = {
valid: 'bg-success-200 text-success-500',
invalid: 'bg-danger-200 text-danger-500',
unchecked: 'bg-warning-200 text-warning-900',
};
const statusText: Record<TransactionStatus, string> = {
valid: 'Valid',
invalid: 'Invalid',
unchecked: 'Unchecked',
};
return (
<div
className={`py-2 px-4 rounded-md text-center ${statusColors[status]}`}
>
{statusText[status]}
</div>
);
},
},
{
header: 'Action',
cell: ({ row }) => (
<Button
variant="primary"
size="sm"
onClick={(e) => {
e.stopPropagation();
// handleUpdate(row.id);
}}
className="flex items-center gap-2 w-full"
>
<AuditOutlined className="text-[16px]" /> Update
</Button>
),
},
];
export const Components: FC = (): ReactElement => { export const Components: FC = (): ReactElement => {
const [showModalValidate, setShowModalValidate] = useState(false);
const [pagination, setPagination] = React.useState<PaginationState>({ const [pagination, setPagination] = React.useState<PaginationState>({
pageIndex: 0, pageIndex: 0,
pageSize: 9, pageSize: 9,
@@ -121,6 +50,80 @@ export const Components: FC = (): ReactElement => {
const [rowSelection, setRowSelection] = React.useState<RowSelectionState>({}); const [rowSelection, setRowSelection] = React.useState<RowSelectionState>({});
const [showFilter, setShowFilter] = useState(false); const [showFilter, setShowFilter] = useState(false);
const columns: ColumnDef<Transaction>[] = [
{
id: 'select',
header: ({ table }) => (
<input
type="checkbox"
className="rounded"
checked={table.getIsAllRowsSelected()}
onChange={table.getToggleAllRowsSelectedHandler()}
/>
),
cell: ({ row }) => (
<input
type="checkbox"
className="rounded"
checked={row.getIsSelected()}
onChange={row.getToggleSelectedHandler()}
/>
),
},
{
header: 'No',
accessorKey: 'id',
},
{
header: 'Nama Lengkap',
accessorKey: 'name',
},
{
header: 'Nomor Transaksi',
accessorKey: 'transactionNumber',
},
{
header: 'Order Valid?',
accessorKey: 'status',
cell: ({ row }) => {
const status = row.original.status;
const statusColors: Record<TransactionStatus, string> = {
valid: 'bg-success-200 text-success-500',
invalid: 'bg-danger-200 text-danger-500',
unchecked: 'bg-warning-200 text-warning-900',
};
const statusText: Record<TransactionStatus, string> = {
valid: 'Valid',
invalid: 'Invalid',
unchecked: 'Unchecked',
};
return (
<div
className={`py-2 px-4 rounded-md text-center ${statusColors[status]}`}
>
{statusText[status]}
</div>
);
},
},
{
header: 'Action',
cell: ({ row }) => (
<Button
variant="primary"
size="sm"
onClick={(e) => {
e.stopPropagation();
setShowModalValidate(true);
}}
className="flex items-center gap-2 w-full"
>
<AuditOutlined className="text-[16px]" /> Update
</Button>
),
},
];
const table = useReactTable({ const table = useReactTable({
data: mockTransactions, data: mockTransactions,
columns, columns,
@@ -138,48 +141,60 @@ export const Components: FC = (): ReactElement => {
}); });
return ( return (
<main className="w-full px-[48px] py-[40px] flex flex-col gap-8"> <Fragment>
{/* Header */} <main className="w-full px-[48px] py-[40px] flex flex-col gap-8">
<header className="bg-white py-4 px-8 rounded-lg shadow p-4"> {/* Header */}
<h1 className="text-p2 font-semibold">Validasi Transaksi</h1> <header className="bg-white py-4 px-8 rounded-lg shadow p-4">
</header> <h1 className="text-p2 font-semibold">Validasi Transaksi</h1>
</header>
{/* Account Table Section */} {/* Account Table Section */}
<section className="flex flex-col gap-6 p-8 bg-white rounded-md"> <section className="flex flex-col gap-6 p-8 bg-white rounded-md">
{/* Search and Filter */} {/* Search and Filter */}
<div className="flex justify-between items-center gap-8 mb-2"> <div className="flex justify-between items-center gap-8 mb-2">
<div className="relative w-full"> <div className="relative w-full">
<Input <Input
placeholder="Cari berdasarkan nama lengkap, nomor order Shopee" placeholder="Cari berdasarkan nama lengkap, nomor order Shopee"
className="pl-12 w-full max-h-full" className="pl-12 w-full max-h-full"
/> />
<div className="absolute left-3 top-1/2 transform -translate-y-1/2 text-[16px]"> <div className="absolute left-3 top-1/2 transform -translate-y-1/2 text-[16px]">
<SearchOutlined /> <SearchOutlined />
</div>
</div>
<div className="relative">
<Button
variant="primary"
size="md"
className="flex items-center gap-3"
onClick={() => setShowFilter(!showFilter)}
>
<FilterOutlined />
Filters
</Button>
{showFilter && (
<div className="absolute right-0 top-[calc(100%+12px)] z-10 shadow-lg">
<Filter onClose={() => setShowFilter(false)} />
</div>
)}
</div> </div>
</div> </div>
<div className="relative"> {/* Table */}
<Button <DataTable data={mockTransactions} columns={columns} table={table} />
variant="primary" </section>
size="md" </main>
className="flex items-center gap-3" <ModalValidate
onClick={() => setShowFilter(!showFilter)} isOpen={showModalValidate}
> onClose={() => setShowModalValidate(false)}
<FilterOutlined /> handleValid={() => {
Filters console.log('Action ketika user klik Valid');
</Button> }}
{showFilter && ( handleInvalid={() => {
<div className="absolute right-0 top-[calc(100%+12px)] z-10 shadow-lg"> console.log('Action ketika user klik Tidak Valid');
<Filter onClose={() => setShowFilter(false)} /> }}
</div> />
)} </Fragment>
</div>
</div>
{/* Table */}
<DataTable data={mockTransactions} columns={columns} table={table} />
</section>
</main>
); );
}; };