From f7a7acaf65aaa06f632582affa1068361b86063c Mon Sep 17 00:00:00 2001 From: Hafid Nur Date: Mon, 31 Mar 2025 00:30:05 +0700 Subject: [PATCH 1/8] feat(backoffice): add modal delete gacha item --- .../_components/modal-delete-item.tsx | 60 ++++ apps/backoffice/src/app/dashboard/page.tsx | 259 ++++++++++-------- 2 files changed, 198 insertions(+), 121 deletions(-) create mode 100644 apps/backoffice/src/app/dashboard/_components/modal-delete-item.tsx diff --git a/apps/backoffice/src/app/dashboard/_components/modal-delete-item.tsx b/apps/backoffice/src/app/dashboard/_components/modal-delete-item.tsx new file mode 100644 index 0000000..212da7b --- /dev/null +++ b/apps/backoffice/src/app/dashboard/_components/modal-delete-item.tsx @@ -0,0 +1,60 @@ +import { Button } from '@imphnen-frontend-service/ui/atoms'; +import { Modal } from '@imphnen-frontend-service/ui/molecules'; + +interface IModalDeleteItem { + isOpen: boolean; + onClose: () => void; + onDeleteItem: () => void; +} + +const ModalDeleteItem = ({ + isOpen, + onClose, + onDeleteItem, +}: IModalDeleteItem) => { + return ( + + + Delete item? +
+

+ Delete Item +

+

+ Apakah kamu yakin untuk menghapus item ini? +

+
+
+ + + + +
+ ); +}; + +export default ModalDeleteItem; diff --git a/apps/backoffice/src/app/dashboard/page.tsx b/apps/backoffice/src/app/dashboard/page.tsx index 2567e8c..8f89cda 100644 --- a/apps/backoffice/src/app/dashboard/page.tsx +++ b/apps/backoffice/src/app/dashboard/page.tsx @@ -6,139 +6,156 @@ import { UserSwitchOutlined, } from '@ant-design/icons'; import { Button } from '@imphnen-frontend-service/ui/atoms'; -import { FC, ReactElement } from 'react'; +import { FC, Fragment, ReactElement, useState } from 'react'; +import ModalDeleteItem from './_components/modal-delete-item'; export const Components: FC = (): ReactElement => { + const [showModalDeleteItem, setShowModalDeleteItem] = useState(false); + return ( -
- {/* Dashboard Header */} -
-

Dashboard

-
+ +
+ {/* Dashboard Header */} +
+

Dashboard

+
-
-
- {/* Summary Section */} -
-

- Summary -

-
- {/* Participants */} -
-
- -
-
-

1000

-

Participants

-
-
- - {/* Roll and Reroll */} -
-
- -
-
-

1000

-

- Roll and Reroll -

-
-
- - {/* Redeem */} -
-
- -
-
-

1000

-

Redeem

-
-
- - {/* Inactive Users */} -
-
- -
-
-

1000

-

Inactive Users

-
-
-
-
- - {/* Gacha Items Section */} -
-
-

- Gacha Items +
+
+ {/* Summary Section */} +
+

+ Summary

- -
+
+ {/* Participants */} +
+
+ +
+
+

1000

+

Participants

+
+
- {/* Gacha Items List */} -
- {[1, 2, 3, 4, 5, 6].map((item) => ( -
-
-
-

- Lanyard IMPHNEN -

-
- Prize {item} - Chance Rate: (0.1%) + {/* Roll and Reroll */} +
+
+ +
+
+

1000

+

+ Roll and Reroll +

+
+
+ + {/* Redeem */} +
+
+ +
+
+

1000

+

Redeem

+
+
+ + {/* Inactive Users */} +
+
+ +
+
+

1000

+

+ Inactive Users +

+
+
+
+

+ + {/* Gacha Items Section */} +
+
+

+ Gacha Items +

+ +
+ + {/* Gacha Items List */} +
+ {[1, 2, 3, 4, 5, 6].map((item) => ( +
+
+
+

+ Lanyard IMPHNEN +

+
+ Prize {item} + Chance Rate: (0.1%) +
+
+
+ +
-
- - -
+ + {/* Lebih baik gunakan gambar yang sudah di-clip dengan size height: 78px daripada hard-code object-position dan margin */} + Lanyard IMPHNEN
+ ))} +
+
+
- {/* Lebih baik gunakan gambar yang sudah di-clip dengan size height: 78px daripada hard-code object-position dan margin */} - Lanyard IMPHNEN -
- ))} - - + {/* Right-side illustration */} + +
- {/* Right-side illustration */} - - -
+ {/* Modal Delete Item */} + setShowModalDeleteItem(false)} + onDeleteItem={() => { + console.log('Item deleted'); + }} + /> + ); }; From 3bbdba70151f1247008e4a8bef9eed726bb64b24 Mon Sep 17 00:00:00 2001 From: Hafid Nur Date: Mon, 31 Mar 2025 01:13:21 +0700 Subject: [PATCH 2/8] fix: add type=file on Input & InputForm components Juga fix script untuk serve backoffice --- libs/ui/src/atoms/input/input.tsx | 2 +- libs/ui/src/molecules/input-form/input-form.tsx | 2 +- package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/libs/ui/src/atoms/input/input.tsx b/libs/ui/src/atoms/input/input.tsx index 72f8575..ae0e849 100644 --- a/libs/ui/src/atoms/input/input.tsx +++ b/libs/ui/src/atoms/input/input.tsx @@ -9,7 +9,7 @@ import { EyeInvisibleOutlined, EyeOutlined } from '@ant-design/icons'; // Import import { cn } from '@imphnen-frontend-service/utils'; import { Button } from '../button'; -type TInputType = 'text' | 'email' | 'password'; +type TInputType = 'text' | 'email' | 'password' | 'file'; type TInputSize = 'sm' | 'md' | 'lg'; type TInputProps = Omit< diff --git a/libs/ui/src/molecules/input-form/input-form.tsx b/libs/ui/src/molecules/input-form/input-form.tsx index 1af658d..7e5c8d3 100644 --- a/libs/ui/src/molecules/input-form/input-form.tsx +++ b/libs/ui/src/molecules/input-form/input-form.tsx @@ -7,7 +7,7 @@ import { import { Input } from '../../atoms'; import { cn } from '@imphnen-frontend-service/utils'; -type TInputType = 'text' | 'email' | 'password'; +type TInputType = 'text' | 'email' | 'password' | 'file'; type TInputSize = 'sm' | 'md' | 'lg'; type TInputFormProps = Omit< diff --git a/package.json b/package.json index 4a3cf8a..3c7f58b 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "gacha:prod": "serve ./dist/apps/gacha", "backoffice:dev": "nx serve backoffice", "backoffice:build": "nx build backoffice", - "backoffice:prod": "serve ./dist/apps/gacha", + "backoffice:prod": "serve ./dist/apps/backoffice", "dimentorin:dev": "nx serve dimentorin", "dimentorin:build": "nx build dimentorin", "dimentorin:prod": "serve ./dist/apps/dimentorin", From ecec423d30ca3e1868e7c077351d2bf316c1c51e Mon Sep 17 00:00:00 2001 From: Hafid Nur Date: Mon, 31 Mar 2025 01:33:18 +0700 Subject: [PATCH 3/8] feat: slicing modal for backoffice dashboard page --- .../dashboard/_components/modal-add-item.tsx | 119 ++++++++++++++++++ .../_components/modal-delete-item.tsx | 2 +- .../dashboard/_components/modal-edit-item.tsx | 118 +++++++++++++++++ apps/backoffice/src/app/dashboard/page.tsx | 30 ++++- 4 files changed, 267 insertions(+), 2 deletions(-) create mode 100644 apps/backoffice/src/app/dashboard/_components/modal-add-item.tsx create mode 100644 apps/backoffice/src/app/dashboard/_components/modal-edit-item.tsx diff --git a/apps/backoffice/src/app/dashboard/_components/modal-add-item.tsx b/apps/backoffice/src/app/dashboard/_components/modal-add-item.tsx new file mode 100644 index 0000000..0a0a189 --- /dev/null +++ b/apps/backoffice/src/app/dashboard/_components/modal-add-item.tsx @@ -0,0 +1,119 @@ +import { Button } from '@imphnen-frontend-service/ui/atoms'; +import { InputForm, Modal } from '@imphnen-frontend-service/ui/molecules'; + +interface IModalAddItem { + isOpen: boolean; + onClose: () => void; + onAddItem: () => void; + currentStep?: number; +} + +const ModalAddItem = ({ + isOpen, + onClose, + onAddItem, + currentStep = 1, +}: IModalAddItem) => { + return ( + + {currentStep === 1 && ( + { + console.log('Next step'); + }} + onClose={onClose} + /> + )} + {currentStep === 2 && ( + { + console.log('Reset step'); + }} + /> + )} + + ); +}; + +// TODO: Functional stepper +interface IStepOneProps { + nextStep: () => void; + onClose: () => void; +} + +const StepOne = ({ nextStep, onClose }: IStepOneProps) => ( + <> + +

+ Tambah Item Gacha +

+

+ Lengkapi detail di bawah ini untuk menambahkan item gacha +

+
+ +
+ + + +
+ + +
+ +); + +interface IStepTwoProps { + onClose: () => void; + resetStep: () => void; +} + +const StepTwo = ({ onClose, resetStep }: IStepTwoProps) => ( + <> + +

+ Tambah Item +

+

+ Apakah kamu yakin ingin +
menambahkan item ini? +

+
+ + + + + +); + +export default ModalAddItem; diff --git a/apps/backoffice/src/app/dashboard/_components/modal-delete-item.tsx b/apps/backoffice/src/app/dashboard/_components/modal-delete-item.tsx index 212da7b..fc7ce78 100644 --- a/apps/backoffice/src/app/dashboard/_components/modal-delete-item.tsx +++ b/apps/backoffice/src/app/dashboard/_components/modal-delete-item.tsx @@ -40,7 +40,7 @@ const ModalDeleteItem = ({ variant="secondary" size="lg" className="w-full" - onClick={() => onClose()} + onClick={onClose} > Batal Hapus diff --git a/apps/backoffice/src/app/dashboard/_components/modal-edit-item.tsx b/apps/backoffice/src/app/dashboard/_components/modal-edit-item.tsx new file mode 100644 index 0000000..7dc82e2 --- /dev/null +++ b/apps/backoffice/src/app/dashboard/_components/modal-edit-item.tsx @@ -0,0 +1,118 @@ +import { Button } from '@imphnen-frontend-service/ui/atoms'; +import { InputForm, Modal } from '@imphnen-frontend-service/ui/molecules'; + +interface IModalEditItem { + isOpen: boolean; + onClose: () => void; + onEditItem: () => void; + currentStep?: number; +} + +const ModalEditItem = ({ + isOpen, + onClose, + onEditItem, + currentStep = 1, +}: IModalEditItem) => { + return ( + + {currentStep === 1 && ( + { + console.log('Next step'); + }} + onClose={onClose} + /> + )} + {currentStep === 2 && ( + { + console.log('Reset step'); + }} + /> + )} + + ); +}; + +interface IStepOneProps { + nextStep: () => void; + onClose: () => void; +} + +const StepOne = ({ nextStep, onClose }: IStepOneProps) => ( + <> + +

+ Edit Item Gacha +

+

+ Silakan mengubah detail dari item yang diperlukan +

+
+ +
+ + + +
+ + +
+ +); + +interface IStepTwoProps { + onClose: () => void; + resetStep: () => void; +} + +const StepTwo = ({ onClose, resetStep }: IStepTwoProps) => ( + <> + +

+ Update Item +

+

+ Apakah kamu yakin dengan +
perubahan yang dilakukan? +

+
+ + + + + +); + +export default ModalEditItem; diff --git a/apps/backoffice/src/app/dashboard/page.tsx b/apps/backoffice/src/app/dashboard/page.tsx index 8f89cda..a009bf4 100644 --- a/apps/backoffice/src/app/dashboard/page.tsx +++ b/apps/backoffice/src/app/dashboard/page.tsx @@ -7,9 +7,13 @@ import { } from '@ant-design/icons'; import { Button } from '@imphnen-frontend-service/ui/atoms'; import { FC, Fragment, ReactElement, useState } from 'react'; +import ModalAddItem from './_components/modal-add-item'; +import ModalEditItem from './_components/modal-edit-item'; import ModalDeleteItem from './_components/modal-delete-item'; export const Components: FC = (): ReactElement => { + const [showModalAddItem, setShowModalAddItem] = useState(false); + const [showModalEditItem, setShowModalEditItem] = useState(false); const [showModalDeleteItem, setShowModalDeleteItem] = useState(false); return ( @@ -84,7 +88,12 @@ export const Components: FC = (): ReactElement => {

Gacha Items

- @@ -112,6 +121,7 @@ export const Components: FC = (): ReactElement => { variant="text" size="sm" className="text-[10px] text-neutral-500 p-0 font-normal hover:bg-transparent hover:text-primary-500" + onClick={() => setShowModalEditItem(true)} > Edit @@ -147,6 +157,24 @@ export const Components: FC = (): ReactElement => { + {/* Modal Add Item */} + setShowModalAddItem(false)} + onAddItem={() => { + console.log('Item added'); + }} + /> + + {/* Modal Add Item */} + setShowModalEditItem(false)} + onEditItem={() => { + console.log('Item edited'); + }} + /> + {/* Modal Delete Item */} Date: Mon, 31 Mar 2025 17:32:30 +0700 Subject: [PATCH 4/8] feat: slicing modal for backoffice 'data pengiriman hadiah' page --- .../prizes/_components/modal-process-item.tsx | 71 ++++ apps/backoffice/src/app/prizes/page.tsx | 306 +++++++++--------- 2 files changed, 227 insertions(+), 150 deletions(-) create mode 100644 apps/backoffice/src/app/prizes/_components/modal-process-item.tsx diff --git a/apps/backoffice/src/app/prizes/_components/modal-process-item.tsx b/apps/backoffice/src/app/prizes/_components/modal-process-item.tsx new file mode 100644 index 0000000..3472c45 --- /dev/null +++ b/apps/backoffice/src/app/prizes/_components/modal-process-item.tsx @@ -0,0 +1,71 @@ +import { Button } from '@imphnen-frontend-service/ui/atoms'; +import { InputForm, Modal } from '@imphnen-frontend-service/ui/molecules'; + +interface IModalProcessDelivery { + isOpen: boolean; + onClose: () => void; + onProcessDelivery?: () => void; +} + +const ModalProcessDelivery = ({ isOpen, onClose }: IModalProcessDelivery) => { + return ( + + +

+ Delivery Process +

+

+ Lakukan pengiriman hadiah gacha untuk pengguna di bawah ini, jika + sudah ubah status menjadi “Delivered”. +

+
+ +
+ + + + +
+ + +
+
+ ); +}; + +export default ModalProcessDelivery; diff --git a/apps/backoffice/src/app/prizes/page.tsx b/apps/backoffice/src/app/prizes/page.tsx index f7828c4..ffac78d 100644 --- a/apps/backoffice/src/app/prizes/page.tsx +++ b/apps/backoffice/src/app/prizes/page.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; -import { FC, ReactElement, useState } from 'react'; +import { FC, Fragment, ReactElement, useState } from 'react'; import { FilterOutlined, SearchOutlined, @@ -17,13 +17,15 @@ import { useReactTable, RowSelectionState, } from '@tanstack/react-table'; +import ModalProcessDelivery from './_components/modal-process-item'; -type Status = 'valid' | 'invalid' | 'unchecked'; +type OrderValid = 'valid' | 'invalid' | 'unchecked'; +type Status = 'undelivered' | 'delivered'; interface Prize { id: number; name: string; - orderValid: Status; + orderValid: OrderValid; items: string; address: string; status: Status; @@ -46,119 +48,16 @@ const mockData: Prize[] = Array.from({ length: 90 }, (_, i) => ({ ? 'invalid' : i % 5 === 0 ? 'unchecked' - : 'valid') as Status, + : 'valid') as OrderValid, items: items[i % items.length], address: 'Jl. Pantai Cibaduyut Indah', - status: (i % 3 === 0 - ? 'unchecked' - : i % 5 === 0 - ? 'invalid' - : 'valid') as Status, + status: (i % 3 === 0 ? 'undelivered' : 'delivered') as Status, })); -const columns: ColumnDef[] = [ - { - id: 'select', - header: ({ table }) => ( - - ), - cell: ({ row }) => ( - - ), - }, - { - header: 'No', - accessorKey: 'id', - }, - { - header: 'Nama Lengkap', - accessorKey: 'name', - }, - { - header: 'Order Valid?', - accessorKey: 'orderValid', - cell: ({ row }) => { - const status = row.original.orderValid; - const statusColors: Record = { - valid: 'bg-success-200 text-success-500', - invalid: 'bg-danger-200 text-danger-500', - unchecked: 'bg-warning-200 text-warning-900', - }; - const statusText: Record = { - valid: 'Valid', - invalid: 'Invalid', - unchecked: 'Unchecked', - }; - return ( -
- {statusText[status]} -
- ); - }, - }, - { - header: 'Items', - accessorKey: 'items', - }, - { - header: 'Alamat Pengiriman', - accessorKey: 'address', - }, - { - header: 'Status', - accessorKey: 'status', - cell: ({ row }) => { - const status = row.original.status; - const statusColors: Record = { - valid: 'bg-success-200 text-success-500', - invalid: 'bg-danger-200 text-danger-500', - unchecked: 'bg-warning-200 text-warning-900', - }; - const statusText: Record = { - valid: 'Valid', - invalid: 'Invalid', - unchecked: 'Unchecked', - }; - return ( -
- {statusText[status]} -
- ); - }, - }, - { - header: 'Action', - cell: ({ row }) => ( - - ), - }, -]; - export const Components: FC = (): ReactElement => { + const [showModalProcessDelivery, setShowModalProcessDelivery] = + useState(false); + const [pagination, setPagination] = React.useState({ pageIndex: 0, pageSize: 9, @@ -167,6 +66,105 @@ export const Components: FC = (): ReactElement => { const [rowSelection, setRowSelection] = React.useState({}); const [showFilter, setShowFilter] = useState(false); + const columns: ColumnDef[] = [ + { + id: 'select', + header: ({ table }) => ( + + ), + cell: ({ row }) => ( + + ), + }, + { + header: 'No', + accessorKey: 'id', + }, + { + header: 'Nama Lengkap', + accessorKey: 'name', + }, + { + header: 'Order Valid?', + accessorKey: 'orderValid', + cell: ({ row }) => { + const status = row.original.orderValid; + const statusColors: Record = { + valid: 'bg-success-200 text-success-500', + invalid: 'bg-danger-200 text-danger-500', + unchecked: 'bg-warning-200 text-warning-900', + }; + const statusText: Record = { + valid: 'Valid', + invalid: 'Invalid', + unchecked: 'Unchecked', + }; + return ( +
+ {statusText[status]} +
+ ); + }, + }, + { + header: 'Items', + accessorKey: 'items', + }, + { + header: 'Alamat Pengiriman', + accessorKey: 'address', + }, + { + header: 'Status', + accessorKey: 'status', + cell: ({ row }) => { + const status = row.original.status; + const statusColors: Record = { + delivered: 'bg-success-200 text-success-500', + undelivered: 'bg-danger-200 text-danger-500', + }; + const statusText: Record = { + delivered: 'Delivered', + undelivered: 'Undelivered', + }; + return ( +
+ {statusText[status]} +
+ ); + }, + }, + { + header: 'Action', + cell: ({ row }) => ( + + ), + }, + ]; + const table = useReactTable({ data: mockData, columns, @@ -184,48 +182,56 @@ export const Components: FC = (): ReactElement => { }); return ( -
- {/* Header */} -
-

Data Pengiriman Hadiah

-
- - {/* Account Table Section */} -
- {/* Search and Filter */} -
-
- -
- + +
+ {/* Header */} +
+

Data Pengiriman Hadiah

+
+ {/* Account Table Section */} +
+ {/* Search and Filter */} +
+
+ +
+ +
+
+
+ + {showFilter && ( +
+ setShowFilter(false)} /> +
+ )}
+ {/* Table */} + +
+
-
- - {showFilter && ( -
- setShowFilter(false)} /> -
- )} -
-
- - {/* Table */} - -
-
+ {/* Modal Process Delivery */} + setShowModalProcessDelivery(false)} + onProcessDelivery={() => { + console.log('Item added'); + }} + /> + ); }; From 989503f93027be9714a8d95a1b240472cca532c4 Mon Sep 17 00:00:00 2001 From: Hafid Nur Date: Mon, 31 Mar 2025 21:40:00 +0700 Subject: [PATCH 5/8] feat: slicing modal for backoffice 'validasi transaksi' page - Tambah fungsi untuk handle aksi ketika button pada modal ditekan --- .../dashboard/_components/modal-add-item.tsx | 24 +- .../_components/modal-delete-item.tsx | 8 +- .../dashboard/_components/modal-edit-item.tsx | 25 +- apps/backoffice/src/app/dashboard/page.tsx | 8 +- .../prizes/_components/modal-process-item.tsx | 19 +- apps/backoffice/src/app/prizes/page.tsx | 5 +- .../_components/modal-validate.tsx | 61 +++++ apps/backoffice/src/app/transactions/page.tsx | 241 ++++++++++-------- 8 files changed, 255 insertions(+), 136 deletions(-) create mode 100644 apps/backoffice/src/app/transactions/_components/modal-validate.tsx diff --git a/apps/backoffice/src/app/dashboard/_components/modal-add-item.tsx b/apps/backoffice/src/app/dashboard/_components/modal-add-item.tsx index 0a0a189..2b561f1 100644 --- a/apps/backoffice/src/app/dashboard/_components/modal-add-item.tsx +++ b/apps/backoffice/src/app/dashboard/_components/modal-add-item.tsx @@ -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 && ( { 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) => ( <>

@@ -106,10 +108,22 @@ const StepTwo = ({ onClose, resetStep }: IStepTwoProps) => (

- - diff --git a/apps/backoffice/src/app/dashboard/_components/modal-delete-item.tsx b/apps/backoffice/src/app/dashboard/_components/modal-delete-item.tsx index fc7ce78..3663ee0 100644 --- a/apps/backoffice/src/app/dashboard/_components/modal-delete-item.tsx +++ b/apps/backoffice/src/app/dashboard/_components/modal-delete-item.tsx @@ -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 ( onDeleteItem()} + onClick={() => { + handleDeleteItem && handleDeleteItem(); + }} > Hapus Item diff --git a/apps/backoffice/src/app/dashboard/_components/modal-edit-item.tsx b/apps/backoffice/src/app/dashboard/_components/modal-edit-item.tsx index 7dc82e2..6166933 100644 --- a/apps/backoffice/src/app/dashboard/_components/modal-edit-item.tsx +++ b/apps/backoffice/src/app/dashboard/_components/modal-edit-item.tsx @@ -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) => ( /> - @@ -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) => ( <>

@@ -105,10 +106,22 @@ const StepTwo = ({ onClose, resetStep }: IStepTwoProps) => (

- - diff --git a/apps/backoffice/src/app/dashboard/page.tsx b/apps/backoffice/src/app/dashboard/page.tsx index a009bf4..3559af7 100644 --- a/apps/backoffice/src/app/dashboard/page.tsx +++ b/apps/backoffice/src/app/dashboard/page.tsx @@ -161,16 +161,16 @@ export const Components: FC = (): ReactElement => { setShowModalAddItem(false)} - onAddItem={() => { + handleAddItem={() => { console.log('Item added'); }} /> - {/* Modal Add Item */} + {/* Modal Edit Item */} setShowModalEditItem(false)} - onEditItem={() => { + handleEditItem={() => { console.log('Item edited'); }} /> @@ -179,7 +179,7 @@ export const Components: FC = (): ReactElement => { setShowModalDeleteItem(false)} - onDeleteItem={() => { + handleDeleteItem={() => { console.log('Item deleted'); }} /> diff --git a/apps/backoffice/src/app/prizes/_components/modal-process-item.tsx b/apps/backoffice/src/app/prizes/_components/modal-process-item.tsx index 3472c45..19a907b 100644 --- a/apps/backoffice/src/app/prizes/_components/modal-process-item.tsx +++ b/apps/backoffice/src/app/prizes/_components/modal-process-item.tsx @@ -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 ( { value="Ahmad Wiyana" size="lg" className="w-full" + readOnly /> { value="Lanyard + ID Card" size="lg" className="w-full" + readOnly /> { value="Jl. Pantai Cibaduyut Indah" size="lg" className="w-full" + readOnly /> { value="Lanyard + ID Card" size="lg" className="w-full" + readOnly /> - diff --git a/apps/backoffice/src/app/prizes/page.tsx b/apps/backoffice/src/app/prizes/page.tsx index ffac78d..72a77ec 100644 --- a/apps/backoffice/src/app/prizes/page.tsx +++ b/apps/backoffice/src/app/prizes/page.tsx @@ -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 => { setShowModalProcessDelivery(false)} - onProcessDelivery={() => { - console.log('Item added'); + handleProcessDelivery={() => { + console.log('Action ketika user menekan tombol Proses Pengiriman'); }} /> diff --git a/apps/backoffice/src/app/transactions/_components/modal-validate.tsx b/apps/backoffice/src/app/transactions/_components/modal-validate.tsx new file mode 100644 index 0000000..5938490 --- /dev/null +++ b/apps/backoffice/src/app/transactions/_components/modal-validate.tsx @@ -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 ( + + +

+ Validasi Transaksi +

+
+ + +
+ + +
+
+
+ ); +}; + +export default ModalValidate; diff --git a/apps/backoffice/src/app/transactions/page.tsx b/apps/backoffice/src/app/transactions/page.tsx index a03b161..dec6413 100644 --- a/apps/backoffice/src/app/transactions/page.tsx +++ b/apps/backoffice/src/app/transactions/page.tsx @@ -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[] = [ - { - id: 'select', - header: ({ table }) => ( - - ), - cell: ({ row }) => ( - - ), - }, - { - 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 = { - valid: 'bg-success-200 text-success-500', - invalid: 'bg-danger-200 text-danger-500', - unchecked: 'bg-warning-200 text-warning-900', - }; - const statusText: Record = { - valid: 'Valid', - invalid: 'Invalid', - unchecked: 'Unchecked', - }; - return ( -
- {statusText[status]} -
- ); - }, - }, - { - header: 'Action', - cell: ({ row }) => ( - - ), - }, -]; - export const Components: FC = (): ReactElement => { + const [showModalValidate, setShowModalValidate] = useState(false); + const [pagination, setPagination] = React.useState({ pageIndex: 0, pageSize: 9, @@ -121,6 +50,80 @@ export const Components: FC = (): ReactElement => { const [rowSelection, setRowSelection] = React.useState({}); const [showFilter, setShowFilter] = useState(false); + const columns: ColumnDef[] = [ + { + id: 'select', + header: ({ table }) => ( + + ), + cell: ({ row }) => ( + + ), + }, + { + 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 = { + valid: 'bg-success-200 text-success-500', + invalid: 'bg-danger-200 text-danger-500', + unchecked: 'bg-warning-200 text-warning-900', + }; + const statusText: Record = { + valid: 'Valid', + invalid: 'Invalid', + unchecked: 'Unchecked', + }; + return ( +
+ {statusText[status]} +
+ ); + }, + }, + { + header: 'Action', + cell: ({ row }) => ( + + ), + }, + ]; + const table = useReactTable({ data: mockTransactions, columns, @@ -138,48 +141,60 @@ export const Components: FC = (): ReactElement => { }); return ( -
- {/* Header */} -
-

Validasi Transaksi

-
+ +
+ {/* Header */} +
+

Validasi Transaksi

+
- {/* Account Table Section */} -
- {/* Search and Filter */} -
-
- -
- + {/* Account Table Section */} +
+ {/* Search and Filter */} +
+
+ +
+ +
+
+ +
+ + {showFilter && ( +
+ setShowFilter(false)} /> +
+ )}
-
- - {showFilter && ( -
- setShowFilter(false)} /> -
- )} -
-
- - {/* Table */} - -
-
+ {/* Table */} + + +
+ setShowModalValidate(false)} + handleValid={() => { + console.log('Action ketika user klik Valid'); + }} + handleInvalid={() => { + console.log('Action ketika user klik Tidak Valid'); + }} + /> + ); }; From a575e534b542dcd5d0617a1bf49fd2bf353157a5 Mon Sep 17 00:00:00 2001 From: Hafid Nur Date: Mon, 31 Mar 2025 21:53:18 +0700 Subject: [PATCH 6/8] feat: slicing modal for backoffice 'data akun' page --- .../_components/modal-edit-account.tsx | 157 +++++++++++++ apps/backoffice/src/app/accounts/page.tsx | 209 +++++++++--------- 2 files changed, 267 insertions(+), 99 deletions(-) create mode 100644 apps/backoffice/src/app/accounts/_components/modal-edit-account.tsx diff --git a/apps/backoffice/src/app/accounts/_components/modal-edit-account.tsx b/apps/backoffice/src/app/accounts/_components/modal-edit-account.tsx new file mode 100644 index 0000000..0e23a97 --- /dev/null +++ b/apps/backoffice/src/app/accounts/_components/modal-edit-account.tsx @@ -0,0 +1,157 @@ +import { Button } from '@imphnen-frontend-service/ui/atoms'; +import { InputForm, Modal } from '@imphnen-frontend-service/ui/molecules'; +import { useState } from 'react'; + +interface IModalEditAccount { + isOpen: boolean; + onClose: () => void; + handleEditAccount?: () => void; + currentStep?: number; +} + +const ModalEditAccount = ({ + isOpen, + onClose, + handleEditAccount, + currentStep = 1, +}: IModalEditAccount) => { + return ( + + {currentStep === 1 && ( + { + console.log('Next step'); + }} + onClose={onClose} + /> + )} + {currentStep === 2 && ( + { + console.log('Reset step'); + }} + /> + )} + + ); +}; + +interface IStepOneProps { + nextStep: () => void; + onClose: () => void; +} + +const StepOne = ({ nextStep, onClose }: IStepOneProps) => { + const [fullName, setFullName] = useState('Ahmad Wiyana'); + const [email, setEmail] = useState('fullname23@gmail.com'); + const [phoneNumber, setPhoneNumber] = useState('081904423804'); + const [address, setAddress] = useState('Jl. Pantai Cibaduyut Indah'); + + return ( + <> + +

+ Edit Data Akun +

+
+ +
+ setFullName(e.target.value)} + size="lg" + className="w-full" + /> + setEmail(e.target.value)} + size="lg" + className="w-full" + /> + setPhoneNumber(e.target.value)} + size="lg" + className="w-full" + /> + setAddress(e.target.value)} + size="lg" + className="w-full" + /> +
+ + +
+ + ); +}; + +interface IStepTwoProps { + onClose: () => void; + handleEditAccount?: () => void; + resetStep: () => void; +} + +const StepTwo = ({ onClose, handleEditAccount, resetStep }: IStepTwoProps) => ( + <> + +

+ Update Data +

+

+ Apakah kamu yakin dengan +
perubahan yang dilakukan? +

+
+ + + + + +); + +export default ModalEditAccount; diff --git a/apps/backoffice/src/app/accounts/page.tsx b/apps/backoffice/src/app/accounts/page.tsx index 404b462..9ce96b6 100644 --- a/apps/backoffice/src/app/accounts/page.tsx +++ b/apps/backoffice/src/app/accounts/page.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; -import { FC, ReactElement, useState } from 'react'; +import { FC, Fragment, ReactElement, useState } from 'react'; import { FilterOutlined, SearchOutlined, @@ -17,6 +17,7 @@ import { useReactTable, RowSelectionState, } from '@tanstack/react-table'; +import ModalEditAccount from './_components/modal-edit-account'; interface Account { id: number; @@ -35,65 +36,9 @@ const mockData: Account[] = Array.from({ length: 90 }, (_, i) => ({ address: 'Jl. Pantai Cibaduyut Indah', })); -const columns: ColumnDef[] = [ - { - id: 'select', - header: ({ table }) => ( - - ), - cell: ({ row }) => ( - - ), - }, - { - header: 'No', - accessorKey: 'id', - }, - { - header: 'Nama Lengkap', - accessorKey: 'name', - }, - { - header: 'Email', - accessorKey: 'email', - }, - { - header: 'Nomor Telp', - accessorKey: 'phone', - }, - { - header: 'Alamat Pengiriman', - accessorKey: 'address', - }, - { - header: 'Action', - cell: ({ row }) => ( - - ), - }, -]; - export const Components: FC = (): ReactElement => { + const [showModalEditAccount, setShowModalEditAccount] = useState(false); + const [pagination, setPagination] = React.useState({ pageIndex: 0, pageSize: 9, @@ -102,6 +47,64 @@ export const Components: FC = (): ReactElement => { const [rowSelection, setRowSelection] = React.useState({}); const [showFilter, setShowFilter] = useState(false); + const columns: ColumnDef[] = [ + { + id: 'select', + header: ({ table }) => ( + + ), + cell: ({ row }) => ( + + ), + }, + { + header: 'No', + accessorKey: 'id', + }, + { + header: 'Nama Lengkap', + accessorKey: 'name', + }, + { + header: 'Email', + accessorKey: 'email', + }, + { + header: 'Nomor Telp', + accessorKey: 'phone', + }, + { + header: 'Alamat Pengiriman', + accessorKey: 'address', + }, + { + header: 'Action', + cell: ({ row }) => ( + + ), + }, + ]; + const table = useReactTable({ data: mockData, columns, @@ -119,49 +122,57 @@ export const Components: FC = (): ReactElement => { }); return ( -
- {/* Header */} -
-

Data Akun

-
- - {/* Account Table Section */} -
- {/* Search and Filter */} -
-
- -
- + +
+ {/* Header */} +
+

Data Akun

+
+ {/* Account Table Section */} +
+ {/* Search and Filter */} +
+
+ +
+ +
+
+
+ + {showFilter && ( +
+ setShowFilter(false)} /> +
+ )}
+ {/* Table */} + +
+
-
- - {showFilter && ( -
- setShowFilter(false)} /> -
- )} -
-
- - {/* Table */} - -
-
+ {/* Modal Edit Account */} + setShowModalEditAccount(false)} + handleEditAccount={() => { + console.log('Account updated'); + }} + /> + ); }; From 984a31c587dc3d451e7831b4a120d31aaf1b5d0a Mon Sep 17 00:00:00 2001 From: Hafid Nur Date: Mon, 31 Mar 2025 22:36:37 +0700 Subject: [PATCH 7/8] fix: create custom hook and stepper to make multi-step modal functional - Tambah custom hook 'use-query-state' dan implementasi stepper pada modal dengan tahapan lebih dari 1 - Hapus component ModalBackoffice karena sudah menggunakan component umum Modal --- .../_components/modal-edit-account.tsx | 35 +- apps/backoffice/src/app/accounts/page.tsx | 16 + .../dashboard/_components/modal-add-item.tsx | 39 +- .../dashboard/_components/modal-edit-item.tsx | 35 +- apps/backoffice/src/app/dashboard/page.tsx | 20 + apps/backoffice/src/hook/use-query-state.tsx | 71 ++++ .../src/organisms/modal-backoffice/index.ts | 1 - .../modal-backoffice/modal-backoffice.tsx | 25 -- .../modal-backoffice/modal.stories.tsx | 373 ------------------ 9 files changed, 168 insertions(+), 447 deletions(-) create mode 100644 apps/backoffice/src/hook/use-query-state.tsx delete mode 100644 libs/ui/src/organisms/modal-backoffice/index.ts delete mode 100644 libs/ui/src/organisms/modal-backoffice/modal-backoffice.tsx delete mode 100644 libs/ui/src/organisms/modal-backoffice/modal.stories.tsx diff --git a/apps/backoffice/src/app/accounts/_components/modal-edit-account.tsx b/apps/backoffice/src/app/accounts/_components/modal-edit-account.tsx index 0e23a97..adb382a 100644 --- a/apps/backoffice/src/app/accounts/_components/modal-edit-account.tsx +++ b/apps/backoffice/src/app/accounts/_components/modal-edit-account.tsx @@ -7,36 +7,36 @@ interface IModalEditAccount { onClose: () => void; handleEditAccount?: () => void; currentStep?: number; + nextStep: () => void; + prevStep: () => void; + resetStep: () => void; } const ModalEditAccount = ({ isOpen, onClose, + currentStep, + nextStep, + prevStep, + resetStep, handleEditAccount, - currentStep = 1, }: IModalEditAccount) => { return ( { + onClose(); + resetStep(); + }} disableEscapeKeyDown={true} > - {currentStep === 1 && ( - { - console.log('Next step'); - }} - onClose={onClose} - /> - )} + {currentStep === 1 && } {currentStep === 2 && ( { - console.log('Reset step'); - }} + resetStep={resetStep} /> )} @@ -48,7 +48,7 @@ interface IStepOneProps { onClose: () => void; } -const StepOne = ({ nextStep, onClose }: IStepOneProps) => { +const StepOne = ({ nextStep }: IStepOneProps) => { const [fullName, setFullName] = useState('Ahmad Wiyana'); const [email, setEmail] = useState('fullname23@gmail.com'); const [phoneNumber, setPhoneNumber] = useState('081904423804'); @@ -136,7 +136,10 @@ const StepTwo = ({ onClose, handleEditAccount, resetStep }: IStepTwoProps) => ( variant="bordered" size="lg" className="w-full" - onClick={resetStep} + onClick={() => { + onClose(); + resetStep(); + }} > Batal @@ -146,6 +149,8 @@ const StepTwo = ({ onClose, handleEditAccount, resetStep }: IStepTwoProps) => ( className="w-full" onClick={() => { handleEditAccount && handleEditAccount(); + onClose(); + resetStep(); }} > Update diff --git a/apps/backoffice/src/app/accounts/page.tsx b/apps/backoffice/src/app/accounts/page.tsx index 9ce96b6..e762610 100644 --- a/apps/backoffice/src/app/accounts/page.tsx +++ b/apps/backoffice/src/app/accounts/page.tsx @@ -18,6 +18,7 @@ import { RowSelectionState, } from '@tanstack/react-table'; import ModalEditAccount from './_components/modal-edit-account'; +import { useQueryState } from '../../hook/use-query-state'; interface Account { id: number; @@ -39,6 +40,17 @@ const mockData: Account[] = Array.from({ length: 90 }, (_, i) => ({ export const Components: FC = (): ReactElement => { const [showModalEditAccount, setShowModalEditAccount] = useState(false); + const { + step: currentStep, + nextStep, + prevStep, + resetStep, + } = useQueryState('step', { + defaultValue: 1, + maxValue: 2, + minValue: 1, + }); + const [pagination, setPagination] = React.useState({ pageIndex: 0, pageSize: 9, @@ -166,11 +178,15 @@ export const Components: FC = (): ReactElement => { {/* Modal Edit Account */} setShowModalEditAccount(false)} handleEditAccount={() => { console.log('Account updated'); }} + nextStep={nextStep} + prevStep={prevStep} + resetStep={resetStep} /> ); diff --git a/apps/backoffice/src/app/dashboard/_components/modal-add-item.tsx b/apps/backoffice/src/app/dashboard/_components/modal-add-item.tsx index 2b561f1..85c92b4 100644 --- a/apps/backoffice/src/app/dashboard/_components/modal-add-item.tsx +++ b/apps/backoffice/src/app/dashboard/_components/modal-add-item.tsx @@ -6,49 +6,47 @@ interface IModalAddItem { onClose: () => void; handleAddItem: () => void; currentStep?: number; + nextStep: () => void; + prevStep: () => void; + resetStep: () => void; } const ModalAddItem = ({ isOpen, onClose, + currentStep, + nextStep, + resetStep, handleAddItem, - currentStep = 1, }: IModalAddItem) => { return ( { + onClose(); + resetStep(); + }} disableEscapeKeyDown={true} > - {currentStep === 1 && ( - { - console.log('Next step'); - }} - onClose={onClose} - /> - )} + {currentStep === 1 && } {currentStep === 2 && ( { - console.log('Reset step'); - }} + resetStep={resetStep} /> )} ); }; -// TODO: Functional stepper interface IStepOneProps { nextStep: () => void; onClose: () => void; } -const StepOne = ({ nextStep, onClose }: IStepOneProps) => ( +const StepOne = ({ nextStep }: IStepOneProps) => ( <>

@@ -83,7 +81,7 @@ const StepOne = ({ nextStep, onClose }: IStepOneProps) => ( /> - @@ -92,8 +90,8 @@ const StepOne = ({ nextStep, onClose }: IStepOneProps) => ( interface IStepTwoProps { onClose: () => void; - resetStep: () => void; handleAddItem?: () => void; + resetStep: () => void; } const StepTwo = ({ onClose, handleAddItem, resetStep }: IStepTwoProps) => ( @@ -112,7 +110,10 @@ const StepTwo = ({ onClose, handleAddItem, resetStep }: IStepTwoProps) => ( variant="bordered" size="lg" className="w-full" - onClick={resetStep} + onClick={() => { + onClose(); + resetStep(); + }} > Batal @@ -122,6 +123,8 @@ const StepTwo = ({ onClose, handleAddItem, resetStep }: IStepTwoProps) => ( className="w-full" onClick={() => { handleAddItem && handleAddItem(); + onClose(); + resetStep(); }} > Tambahkan diff --git a/apps/backoffice/src/app/dashboard/_components/modal-edit-item.tsx b/apps/backoffice/src/app/dashboard/_components/modal-edit-item.tsx index 6166933..8e88a89 100644 --- a/apps/backoffice/src/app/dashboard/_components/modal-edit-item.tsx +++ b/apps/backoffice/src/app/dashboard/_components/modal-edit-item.tsx @@ -6,35 +6,35 @@ interface IModalEditItem { onClose: () => void; handleEditItem?: () => void; currentStep?: number; + nextStep: () => void; + prevStep: () => void; + resetStep: () => void; } const ModalEditItem = ({ isOpen, onClose, + currentStep, + nextStep, + resetStep, handleEditItem, - currentStep = 1, }: IModalEditItem) => { return ( { + onClose(); + resetStep(); + }} disableEscapeKeyDown={true} > - {currentStep === 1 && ( - { - console.log('Next step'); - }} - onClose={onClose} - /> - )} + {currentStep === 1 && } {currentStep === 2 && ( { - console.log('Reset step'); - }} + handleEditItem={handleEditItem} + resetStep={resetStep} /> )} @@ -46,7 +46,7 @@ interface IStepOneProps { onClose: () => void; } -const StepOne = ({ nextStep, onClose }: IStepOneProps) => ( +const StepOne = ({ nextStep }: IStepOneProps) => ( <>

@@ -110,7 +110,10 @@ const StepTwo = ({ onClose, handleEditItem, resetStep }: IStepTwoProps) => ( variant="bordered" size="lg" className="w-full" - onClick={resetStep} + onClick={() => { + onClose(); + resetStep(); + }} > Batal @@ -120,6 +123,8 @@ const StepTwo = ({ onClose, handleEditItem, resetStep }: IStepTwoProps) => ( className="w-full" onClick={() => { handleEditItem && handleEditItem(); + onClose(); + resetStep(); }} > Update diff --git a/apps/backoffice/src/app/dashboard/page.tsx b/apps/backoffice/src/app/dashboard/page.tsx index 3559af7..d337e53 100644 --- a/apps/backoffice/src/app/dashboard/page.tsx +++ b/apps/backoffice/src/app/dashboard/page.tsx @@ -10,12 +10,24 @@ import { FC, Fragment, ReactElement, useState } from 'react'; import ModalAddItem from './_components/modal-add-item'; import ModalEditItem from './_components/modal-edit-item'; import ModalDeleteItem from './_components/modal-delete-item'; +import { useQueryState } from '../../hook/use-query-state'; export const Components: FC = (): ReactElement => { const [showModalAddItem, setShowModalAddItem] = useState(false); const [showModalEditItem, setShowModalEditItem] = useState(false); const [showModalDeleteItem, setShowModalDeleteItem] = useState(false); + const { + step: currentStep, + nextStep, + prevStep, + resetStep, + } = useQueryState('step', { + defaultValue: 1, + maxValue: 2, + minValue: 1, + }); + return (
@@ -159,20 +171,28 @@ export const Components: FC = (): ReactElement => { {/* Modal Add Item */} setShowModalAddItem(false)} handleAddItem={() => { console.log('Item added'); }} + nextStep={nextStep} + prevStep={prevStep} + resetStep={resetStep} /> {/* Modal Edit Item */} setShowModalEditItem(false)} handleEditItem={() => { console.log('Item edited'); }} + nextStep={nextStep} + prevStep={prevStep} + resetStep={resetStep} /> {/* Modal Delete Item */} diff --git a/apps/backoffice/src/hook/use-query-state.tsx b/apps/backoffice/src/hook/use-query-state.tsx new file mode 100644 index 0000000..3943f95 --- /dev/null +++ b/apps/backoffice/src/hook/use-query-state.tsx @@ -0,0 +1,71 @@ +import { useCallback, useEffect, useState } from 'react'; + +interface UseQueryStateOptions { + defaultValue: number; + maxValue?: number; + minValue?: number; +} + +export const useQueryState = (key: string, options: UseQueryStateOptions) => { + const { defaultValue, maxValue = Infinity, minValue = 1 } = options; + + const getQueryParam = (param: string): string | null => { + if (typeof window === 'undefined') return null; + const searchParams = new URLSearchParams(window.location.search); + return searchParams.get(param); + }; + + const setQueryParam = (param: string, value: string) => { + const searchParams = new URLSearchParams(window.location.search); + searchParams.set(param, value); + const newUrl = `${window.location.pathname}?${searchParams.toString()}`; + window.history.replaceState(null, '', newUrl); + }; + + const initialValue = () => { + const queryValue = getQueryParam(key); + const parsedValue = queryValue ? parseInt(queryValue, 10) : defaultValue; + return Math.max(minValue, Math.min(maxValue, parsedValue)); + }; + + const [value, setValue] = useState(initialValue); + + useEffect(() => { + const handlePopState = () => { + const queryValue = getQueryParam(key); + const newValue = queryValue ? parseInt(queryValue, 10) : defaultValue; + setValue(Math.max(minValue, Math.min(maxValue, newValue))); + }; + + window.addEventListener('popstate', handlePopState); + return () => window.removeEventListener('popstate', handlePopState); + }, [key, defaultValue, minValue, maxValue]); + + const updateValue = useCallback( + (newValue: number) => { + const constrainedValue = Math.max(minValue, Math.min(maxValue, newValue)); + setValue(constrainedValue); + setQueryParam(key, constrainedValue.toString()); + }, + [key, minValue, maxValue] + ); + + const nextStep = useCallback(() => { + updateValue(value + 1); + }, [value, updateValue]); + + const prevStep = useCallback(() => { + updateValue(value - 1); + }, [value, updateValue]); + + const resetStep = useCallback(() => { + updateValue(defaultValue); + }, [defaultValue, updateValue]); + + return { + step: value, + nextStep, + prevStep, + resetStep, + }; +}; diff --git a/libs/ui/src/organisms/modal-backoffice/index.ts b/libs/ui/src/organisms/modal-backoffice/index.ts deleted file mode 100644 index 47fe845..0000000 --- a/libs/ui/src/organisms/modal-backoffice/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './modal-backoffice'; diff --git a/libs/ui/src/organisms/modal-backoffice/modal-backoffice.tsx b/libs/ui/src/organisms/modal-backoffice/modal-backoffice.tsx deleted file mode 100644 index 5c9ba0d..0000000 --- a/libs/ui/src/organisms/modal-backoffice/modal-backoffice.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { ReactElement, ReactNode } from 'react'; - -interface ModalBackofficeProps { - children: ReactNode; - className?: string; -} - -export const ModalBackoffice = ({ - children, - className, - ...rest -}: ModalBackofficeProps): ReactElement => { - return ( -
- {children} -
- ); -}; - -export default ModalBackoffice; diff --git a/libs/ui/src/organisms/modal-backoffice/modal.stories.tsx b/libs/ui/src/organisms/modal-backoffice/modal.stories.tsx deleted file mode 100644 index 2eb38e1..0000000 --- a/libs/ui/src/organisms/modal-backoffice/modal.stories.tsx +++ /dev/null @@ -1,373 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/react'; -import { ModalBackoffice } from './modal-backoffice'; -import { InputForm } from '../../molecules'; -import { Button } from '../../atoms'; - -const meta: Meta = { - title: 'Organisms/Modal Backoffice', - component: ModalBackoffice, - tags: ['autodocs'], - parameters: { - layout: 'centered', - }, -} satisfies Meta; - -export default meta; -type Story = StoryObj; - -export const Default: Story = { - args: { - // isOpen: true, - // onClose: () => console.log('Modal closed'), - // title: 'Sample Modal', - children: ( - <> -

- Modal Title -

-

- Lorem ipsum dolor sit amet consectetur adipisicing elit. Quas repellat - rerum doloremque dolorem fugit architecto eos blanditiis ratione - facere? Esse dolores inventore deleniti. Dicta voluptatem dolore vel - quia amet dolorem? -

-
- - ), - }, -}; - -export const TambahItemGacha: Story = { - args: { - children: ( -
-
-

- Tambah Item Gacha -

-

- Lengkapi detail di bawah ini untuk menambahkan item gacha -

-
-
- - - -
- -
- ), - }, -}; - -export const TambahItem: Story = { - args: { - children: ( -
-
-

- Tambah Item -

-

- Apakah kamu yakin ingin -
menambahkan item ini? -

-
-
- - -
-
- ), - }, -}; - -export const EditItemGacha: Story = { - args: { - children: ( -
-
-

- Edit Item Gacha -

-

- Silakan mengubah detail dari item yang diperlukan -

-
-
- - - -
- -
- ), - }, -}; - -export const UpdateItem: Story = { - args: { - children: ( -
-
-

- Update Item -

-

- Apakah kamu yakin dengan -
perubahan yang dilakukan? -

-
-
- - -
-
- ), - }, -}; - -export const DeleteItem: Story = { - args: { - children: ( -
- Delete item? -
-

- Delete Item -

-

- Apakah kamu yakin untuk menghapus item ini? -

-
-
- - -
-
- ), - }, -}; - -export const EditDataAkun: Story = { - args: { - children: ( -
-

- Edit Data Akun -

- -
- - - - -
- -
- ), - }, -}; - -export const UpdateDataAkun: Story = { - args: { - children: ( -
-
-

- Update Data -

-

- Apakah kamu yakin dengan -
perubahan yang dilakukan? -

-
-
- - -
-
- ), - }, -}; - -export const ValidasiTransaksi: Story = { - args: { - children: ( -
-

- Validasi Transaksi -

- - -
- - -
-
- ), - }, -}; - -export const ProsesDelivery: Story = { - args: { - children: ( -
-
-

- Delivery Process -

- -

- Lakukan pengiriman hadiah gacha untuk pengguna di bawah ini, jika - sudah ubah status menjadi “Delivered”. -

-
- -
- - - - -
- -
- ), - }, -}; From d7a90f47d0ab5431a86fdbdeca43dd22327a65fb Mon Sep 17 00:00:00 2001 From: Hafid Nur Date: Mon, 31 Mar 2025 22:46:09 +0700 Subject: [PATCH 8/8] feat: dynamic radio button for Filter component --- apps/backoffice/src/app/prizes/page.tsx | 14 ++++- apps/backoffice/src/app/transactions/page.tsx | 15 +++++- libs/ui/src/organisms/filter/filter.tsx | 53 ++++++++++++------- 3 files changed, 60 insertions(+), 22 deletions(-) diff --git a/apps/backoffice/src/app/prizes/page.tsx b/apps/backoffice/src/app/prizes/page.tsx index 72a77ec..1e15a2a 100644 --- a/apps/backoffice/src/app/prizes/page.tsx +++ b/apps/backoffice/src/app/prizes/page.tsx @@ -66,6 +66,11 @@ export const Components: FC = (): ReactElement => { const [rowSelection, setRowSelection] = React.useState({}); const [showFilter, setShowFilter] = useState(false); + const deliveryOptions = [ + { id: 'option1', value: 'undelivered', label: 'Undelivered' }, + { id: 'option1', value: 'delivered', label: 'Delivered' }, + ]; + const columns: ColumnDef[] = [ { id: 'select', @@ -214,7 +219,14 @@ export const Components: FC = (): ReactElement => { {showFilter && (
- setShowFilter(false)} /> + setShowFilter(false)} + onFilterChange={(value) => { + console.log('Selected filter:', value); + // Filter logic di sini + }} + />
)} diff --git a/apps/backoffice/src/app/transactions/page.tsx b/apps/backoffice/src/app/transactions/page.tsx index dec6413..f2f5070 100644 --- a/apps/backoffice/src/app/transactions/page.tsx +++ b/apps/backoffice/src/app/transactions/page.tsx @@ -50,6 +50,12 @@ export const Components: FC = (): ReactElement => { const [rowSelection, setRowSelection] = React.useState({}); const [showFilter, setShowFilter] = useState(false); + const validationOptions = [ + { id: 'option1', value: 'unchecked', label: 'Unchecked' }, + { id: 'option2', value: 'valid', label: 'Valid' }, + { id: 'option3', value: 'invalid', label: 'Invalid' }, + ]; + const columns: ColumnDef[] = [ { id: 'select', @@ -174,7 +180,14 @@ export const Components: FC = (): ReactElement => { {showFilter && (
- setShowFilter(false)} /> + setShowFilter(false)} + onFilterChange={(value) => { + console.log('Selected filter:', value); + // Filter logic di sini + }} + />
)} diff --git a/libs/ui/src/organisms/filter/filter.tsx b/libs/ui/src/organisms/filter/filter.tsx index 268a36c..b0119cb 100644 --- a/libs/ui/src/organisms/filter/filter.tsx +++ b/libs/ui/src/organisms/filter/filter.tsx @@ -42,13 +42,31 @@ const Radio = ({ interface FilterProps { onClose?: () => void; + options: Array<{ + id: string; + value: string; + label: string; + }>; + selectedValue?: string; + onFilterChange?: (value: string) => void; + title?: string; } -export const Filter = ({ onClose }: FilterProps) => { - const [selectedStatus, setSelectedStatus] = useState('delivered'); +export const Filter = ({ + onClose, + options, + selectedValue, + onFilterChange, + title = 'Status', +}: FilterProps) => { + const [selectedStatus, setSelectedStatus] = useState( + selectedValue || options[0]?.value || '' + ); const handleStatusChange = (e: React.ChangeEvent) => { - setSelectedStatus(e.target.value); + const newValue = e.target.value; + setSelectedStatus(newValue); + onFilterChange?.(newValue); }; return ( @@ -63,24 +81,19 @@ export const Filter = ({ onClose }: FilterProps) => {
- Status + {title}
- - + {options.map((option) => ( + + ))}
);