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 {
isOpen: boolean;
onClose: () => void;
onAddItem: () => void;
handleAddItem: () => void;
currentStep?: number;
}
const ModalAddItem = ({
isOpen,
onClose,
onAddItem,
handleAddItem,
currentStep = 1,
}: IModalAddItem) => {
return (
@@ -32,6 +32,7 @@ const ModalAddItem = ({
{currentStep === 2 && (
<StepTwo
onClose={onClose}
handleAddItem={handleAddItem}
resetStep={() => {
console.log('Reset step');
}}
@@ -92,9 +93,10 @@ const StepOne = ({ nextStep, onClose }: IStepOneProps) => (
interface IStepTwoProps {
onClose: () => 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">
<h2 className="text-p1 font-semibold text-primary-500 mb-3">
@@ -106,10 +108,22 @@ const StepTwo = ({ onClose, resetStep }: IStepTwoProps) => (
</p>
</Modal.Header>
<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
</Button>
<Button variant="primary" size="lg" className="w-full">
<Button
variant="primary"
size="lg"
className="w-full"
onClick={() => {
handleAddItem && handleAddItem();
}}
>
Tambahkan
</Button>
</Modal.Content>
@@ -4,13 +4,13 @@ import { Modal } from '@imphnen-frontend-service/ui/molecules';
interface IModalDeleteItem {
isOpen: boolean;
onClose: () => void;
onDeleteItem: () => void;
handleDeleteItem?: () => void;
}
const ModalDeleteItem = ({
isOpen,
onClose,
onDeleteItem,
handleDeleteItem,
}: IModalDeleteItem) => {
return (
<Modal
@@ -48,7 +48,9 @@ const ModalDeleteItem = ({
variant="danger"
size="lg"
className="w-full"
onClick={() => onDeleteItem()}
onClick={() => {
handleDeleteItem && handleDeleteItem();
}}
>
Hapus Item
</Button>
@@ -4,14 +4,14 @@ import { InputForm, Modal } from '@imphnen-frontend-service/ui/molecules';
interface IModalEditItem {
isOpen: boolean;
onClose: () => void;
onEditItem: () => void;
handleEditItem?: () => void;
currentStep?: number;
}
const ModalEditItem = ({
isOpen,
onClose,
onEditItem,
handleEditItem,
currentStep = 1,
}: IModalEditItem) => {
return (
@@ -81,7 +81,7 @@ const StepOne = ({ nextStep, onClose }: IStepOneProps) => (
/>
</div>
<Button variant="primary" size="lg" className="w-full">
<Button variant="primary" size="lg" className="w-full" onClick={nextStep}>
Perbarui Item
</Button>
</Modal.Content>
@@ -90,10 +90,11 @@ const StepOne = ({ nextStep, onClose }: IStepOneProps) => (
interface IStepTwoProps {
onClose: () => void;
handleEditItem?: () => void;
resetStep: () => void;
}
const StepTwo = ({ onClose, resetStep }: IStepTwoProps) => (
const StepTwo = ({ onClose, handleEditItem, resetStep }: IStepTwoProps) => (
<>
<Modal.Header className="mb-0 text-center items-center">
<h2 className="text-p1 font-semibold text-primary-500 mb-3">
@@ -105,10 +106,22 @@ const StepTwo = ({ onClose, resetStep }: IStepTwoProps) => (
</p>
</Modal.Header>
<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
</Button>
<Button variant="primary" size="lg" className="w-full">
<Button
variant="primary"
size="lg"
className="w-full"
onClick={() => {
handleEditItem && handleEditItem();
}}
>
Update
</Button>
</Modal.Content>
+4 -4
View File
@@ -161,16 +161,16 @@ export const Components: FC = (): ReactElement => {
<ModalAddItem
isOpen={showModalAddItem}
onClose={() => setShowModalAddItem(false)}
onAddItem={() => {
handleAddItem={() => {
console.log('Item added');
}}
/>
{/* Modal Add Item */}
{/* Modal Edit Item */}
<ModalEditItem
isOpen={showModalEditItem}
onClose={() => setShowModalEditItem(false)}
onEditItem={() => {
handleEditItem={() => {
console.log('Item edited');
}}
/>
@@ -179,7 +179,7 @@ export const Components: FC = (): ReactElement => {
<ModalDeleteItem
isOpen={showModalDeleteItem}
onClose={() => setShowModalDeleteItem(false)}
onDeleteItem={() => {
handleDeleteItem={() => {
console.log('Item deleted');
}}
/>
@@ -4,10 +4,14 @@ import { InputForm, Modal } from '@imphnen-frontend-service/ui/molecules';
interface IModalProcessDelivery {
isOpen: boolean;
onClose: () => void;
onProcessDelivery?: () => void;
handleProcessDelivery?: () => void;
}
const ModalProcessDelivery = ({ isOpen, onClose }: IModalProcessDelivery) => {
const ModalProcessDelivery = ({
isOpen,
onClose,
handleProcessDelivery,
}: IModalProcessDelivery) => {
return (
<Modal
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"
size="lg"
className="w-full"
readOnly
/>
<InputForm
label="Item yang didapatkan"
@@ -41,6 +46,7 @@ const ModalProcessDelivery = ({ isOpen, onClose }: IModalProcessDelivery) => {
value="Lanyard + ID Card"
size="lg"
className="w-full"
readOnly
/>
<InputForm
label="Alamat Pengiriman"
@@ -49,6 +55,7 @@ const ModalProcessDelivery = ({ isOpen, onClose }: IModalProcessDelivery) => {
value="Jl. Pantai Cibaduyut Indah"
size="lg"
className="w-full"
readOnly
/>
<InputForm
label="Status"
@@ -57,10 +64,16 @@ const ModalProcessDelivery = ({ isOpen, onClose }: IModalProcessDelivery) => {
value="Lanyard + ID Card"
size="lg"
className="w-full"
readOnly
/>
</div>
<Button variant="primary" size="lg" className="w-full">
<Button
variant="primary"
size="lg"
className="w-full"
onClick={() => handleProcessDelivery && handleProcessDelivery()}
>
Proses Pengiriman
</Button>
</Modal.Content>
+3 -2
View File
@@ -155,6 +155,7 @@ export const Components: FC = (): ReactElement => {
variant="primary"
size="sm"
onClick={(e) => {
e.stopPropagation();
setShowModalProcessDelivery(true);
}}
className="flex items-center gap-2 w-full"
@@ -227,8 +228,8 @@ export const Components: FC = (): ReactElement => {
<ModalProcessDelivery
isOpen={showModalProcessDelivery}
onClose={() => setShowModalProcessDelivery(false)}
onProcessDelivery={() => {
console.log('Item added');
handleProcessDelivery={() => {
console.log('Action ketika user menekan tombol Proses Pengiriman');
}}
/>
</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 { FC, ReactElement, useState } from 'react';
import { FC, Fragment, ReactElement, useState } from 'react';
import {
FilterOutlined,
SearchOutlined,
@@ -16,6 +16,7 @@ import {
useReactTable,
RowSelectionState,
} from '@tanstack/react-table';
import ModalValidate from './_components/modal-validate';
type TransactionStatus = 'valid' | 'invalid' | 'unchecked';
@@ -38,81 +39,9 @@ const mockTransactions: Transaction[] = Array.from({ length: 20 }, (_, i) => ({
: '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 => {
const [showModalValidate, setShowModalValidate] = useState(false);
const [pagination, setPagination] = React.useState<PaginationState>({
pageIndex: 0,
pageSize: 9,
@@ -121,6 +50,80 @@ export const Components: FC = (): ReactElement => {
const [rowSelection, setRowSelection] = React.useState<RowSelectionState>({});
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({
data: mockTransactions,
columns,
@@ -138,48 +141,60 @@ export const Components: FC = (): ReactElement => {
});
return (
<main className="w-full px-[48px] py-[40px] flex flex-col gap-8">
{/* Header */}
<header className="bg-white py-4 px-8 rounded-lg shadow p-4">
<h1 className="text-p2 font-semibold">Validasi Transaksi</h1>
</header>
<Fragment>
<main className="w-full px-[48px] py-[40px] flex flex-col gap-8">
{/* Header */}
<header className="bg-white py-4 px-8 rounded-lg shadow p-4">
<h1 className="text-p2 font-semibold">Validasi Transaksi</h1>
</header>
{/* Account Table Section */}
<section className="flex flex-col gap-6 p-8 bg-white rounded-md">
{/* Search and Filter */}
<div className="flex justify-between items-center gap-8 mb-2">
<div className="relative w-full">
<Input
placeholder="Cari berdasarkan nama lengkap, nomor order Shopee"
className="pl-12 w-full max-h-full"
/>
<div className="absolute left-3 top-1/2 transform -translate-y-1/2 text-[16px]">
<SearchOutlined />
{/* Account Table Section */}
<section className="flex flex-col gap-6 p-8 bg-white rounded-md">
{/* Search and Filter */}
<div className="flex justify-between items-center gap-8 mb-2">
<div className="relative w-full">
<Input
placeholder="Cari berdasarkan nama lengkap, nomor order Shopee"
className="pl-12 w-full max-h-full"
/>
<div className="absolute left-3 top-1/2 transform -translate-y-1/2 text-[16px]">
<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 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>
{/* Table */}
<DataTable data={mockTransactions} columns={columns} table={table} />
</section>
</main>
{/* Table */}
<DataTable data={mockTransactions} columns={columns} table={table} />
</section>
</main>
<ModalValidate
isOpen={showModalValidate}
onClose={() => setShowModalValidate(false)}
handleValid={() => {
console.log('Action ketika user klik Valid');
}}
handleInvalid={() => {
console.log('Action ketika user klik Tidak Valid');
}}
/>
</Fragment>
);
};