Compare commits

..

474 Commits

Author SHA1 Message Date
ragilap 1621f2ab7d fixing disable field detail 2026-03-10 17:34:52 +07:00
ragilap 5540787154 implement transition recording 2026-03-10 17:05:42 +07:00
ragilap 1b499bc967 implement transition recording 2026-03-10 17:04:44 +07:00
rstubryan 44a5c51023 refactor(FE): Refactor recording restriction logic for clarity and
accuracy
2026-03-10 14:02:07 +07:00
rstubryan aa13e989c1 feat(FE): Add week calculation utility and improve state resets 2026-03-10 11:40:10 +07:00
rstubryan ebe7c367e7 refactor(FE): Refactor isLaying logic for clarity and reuse 2026-03-10 11:34:14 +07:00
rstubryan 2f085c287f Merge branch 'development' of gitlab.com:mbugroup/lti-web-client into hotfix/adjustment-recording-fifo-stock 2026-03-10 11:13:56 +07:00
rstubryan 058f9f403d refactor(FE): Improve delete handlers with success and error feedback 2026-03-10 11:13:46 +07:00
Rivaldi A N S 8b8b7be4b7 Merge branch 'feat/daily-checklist-master-data' into 'development'
[FIX/FE] Daily Checklist

See merge request mbugroup/lti-web-client!348
2026-03-09 10:15:55 +00:00
ValdiANS efcecf4f66 fix: implement lazy loading in kandang select input 2026-03-09 17:14:35 +07:00
Rivaldi A N S a6c63a7dcb Merge branch 'feat/daily-checklist-master-data' into 'development'
[FIX/FE]: Daily Checklist Master Data Kandang

See merge request mbugroup/lti-web-client!347
2026-03-09 08:58:50 +00:00
ValdiANS 0263db9fae fix: use DailyChecklistKandangApi instead of KandangApi 2026-03-09 15:55:48 +07:00
rstubryan cc08e3af15 refactor(FE): Fix logical grouping in isLaying and isLayingCategory
checks
2026-03-09 15:04:58 +07:00
rstubryan 0929461ec5 refactor(FE): Improve transition and laying state handling in
RecordingForm
2026-03-09 15:03:42 +07:00
rstubryan ace6633f79 Merge branch 'development' of gitlab.com:mbugroup/lti-web-client into hotfix/adjustment-recording-fifo-stock 2026-03-09 14:04:22 +07:00
rstubryan f1a952ca6b refactor(FE): Refactor getRecordingRestrictionInfo for better
readability
2026-03-09 14:01:29 +07:00
rstubryan ed34a99117 refactor(FE): Refactor to use is_laying instead of
`project_flock_category`
2026-03-09 13:59:24 +07:00
Rivaldi A N S 4beaba1f15 Merge branch 'feat/daily-checklist-master-data' into 'development'
[FEAT/FE] Master Data Daily Checklist Kandang

See merge request mbugroup/lti-web-client!346
2026-03-09 06:49:31 +00:00
ValdiANS 8ea029efdd Merge branch 'development' into feat/daily-checklist-master-data 2026-03-09 13:42:01 +07:00
ValdiANS 02e4dba288 feat(FE): implement lazy loading for kandang select input 2026-03-09 13:41:18 +07:00
ValdiANS c42fdbf33d chore(FE): remove unnecessary code 2026-03-09 13:40:58 +07:00
ValdiANS 2cfa8c046b feat(FE): add onScroll prop to SelectPrimitive.Viewport 2026-03-09 13:40:47 +07:00
ValdiANS 30d5516161 fix: use DailyChecklistKandangApi instead of KandangApi 2026-03-09 12:47:12 +07:00
ValdiANS f83abc91da chore(FE): remove unncessary code 2026-03-09 12:30:49 +07:00
ValdiANS 918c51e83b fix(FE): add kandang_group to BaseKandang and add group_id to CreateKandangPayload 2026-03-09 12:30:16 +07:00
ValdiANS f1a4d9b648 feat(FE): create daily checklist kandang types 2026-03-09 12:29:52 +07:00
ValdiANS 29e33560f8 feat(FE): create daily checklist kandang API service 2026-03-09 12:29:36 +07:00
ValdiANS fb9e863862 feat(FE): create MasterKandangContent component 2026-03-09 12:29:20 +07:00
ValdiANS 1b3e5f94f1 feat(FE): add Kandang Group input 2026-03-09 12:29:04 +07:00
ValdiANS e1856926ea feat(FE): add group to kandang form schema 2026-03-09 12:28:48 +07:00
ValdiANS 2b096099d3 feat: add kandang group column 2026-03-09 12:28:29 +07:00
rstubryan ea25417e8d Merge branch 'development' of gitlab.com:mbugroup/lti-web-client into hotfix/adjustment-recording-fifo-stock 2026-03-09 11:39:16 +07:00
M1 AIR deabb1c3ee Merge origin/production into development to resolve MR conflicts 2026-03-09 10:15:20 +07:00
M1 AIR 121c44070c ci: switch build images to AWS ECR Public 2026-03-09 09:45:58 +07:00
ValdiANS 0dbad23cd5 feat(FE): implement options lazy loading by adding onLoadMore and isLoadingMore props 2026-03-09 09:31:05 +07:00
ValdiANS b9a17f472b feat: add daily checklist master data kandang permission in ROUTE_PERMISSIONS 2026-03-09 09:30:26 +07:00
ValdiANS c07b245eeb feat(FE): add Kandang submenu in Daily Checklist Master Data menu 2026-03-09 09:30:04 +07:00
ValdiANS d7e32f8f5b feat(FE): create Daily Checklist Master Data Kandang page 2026-03-09 09:29:22 +07:00
ValdiANS 698fe2e851 feat(FE): add pre-commit script 2026-03-09 09:28:45 +07:00
rstubryan cdf0442a2b refactor(FE): Add transition restrictions for recording operations 2026-03-09 09:04:14 +07:00
Adnan Zahir 422c7c9fb0 Merge branch 'development' into 'production'
refactor(FE): Refactor RowOptionsMenu to use PopoverButton and

See merge request mbugroup/lti-web-client!344
2026-03-09 06:38:03 +07:00
Adnan Zahir 3042b54577 Merge branch 'fix/product-select-include-all-param' into 'development'
fix: add include all param to adjustment stock products select

See merge request mbugroup/lti-web-client!343
2026-03-09 00:51:43 +07:00
Adnan Zahir e5a686e5ee Merge branch 'hotfix/adjustment-fifo-stock-ttl' into 'development'
[HOTFIX/FE] FIFO Stock Adjustment for TTL, Recording and Project Flock (Chickin)

See merge request mbugroup/lti-web-client!342
2026-03-09 00:50:11 +07:00
Adnan Zahir 37d5a6b675 fix: add include all param to adjustment stock products select 2026-03-09 00:49:39 +07:00
rstubryan 2ff32094ce chore(FE): Fix inconsistent indentation in ChickLogsView and
RecordingForm
2026-03-08 22:13:26 +07:00
rstubryan 7207f1ba75 refactor(FE): Add isRecordingEditable check to detail actions 2026-03-08 22:12:29 +07:00
rstubryan 41d2e8737b feat(FE): Add chickin delete functionality with modal confirmation 2026-03-08 21:54:55 +07:00
rstubryan b2016314f5 refactor(FE): Restrict edit and delete actions based on recording status 2026-03-08 16:40:36 +07:00
rstubryan 7366d6490c refactor(FE): Validate transferToLayingId before fetching data 2026-03-08 16:34:59 +07:00
rstubryan e5e9b517fd refactor(FE): Update button visibility logic in TransferToLayingsTable 2026-03-08 16:17:20 +07:00
rstubryan b6629b0bbb refactor(FE): Set maxSourceQuantity in edit mode using existing data 2026-03-08 16:12:08 +07:00
rstubryan bac6766fa2 refactor(FE): Refactor transfer logic to use maxSourceQuantity state 2026-03-08 16:06:13 +07:00
Rivaldi A N S 9389fa0354 Merge branch 'fix/cleanup-warning-and-adjust-form-field' into 'development'
[FIX/FE] Cleanup Warning and Refactoring All Form Field (No Warning on Linter)

See merge request mbugroup/lti-web-client!341
2026-03-06 04:47:39 +00:00
rstubryan 37bc7a85e5 refactor(FE): Add depletion product handling in inventory adjustment
forms
2026-03-06 10:52:59 +07:00
rstubryan 667eb41eb2 refactor(FE): Refactor toggle logic for clarity in useModal 2026-03-06 10:08:13 +07:00
rstubryan e64b55e527 refactor(FE): Fix conditional call to onBackdropClick in Drawer
component
2026-03-06 10:00:25 +07:00
rstubryan 9710998dc6 refactor(FE): Handle null or undefined values in FinanceDetail component 2026-03-06 09:57:30 +07:00
rstubryan ca0b216ba0 refactor(FE): Handle null transaction_type and fix useEffect dependency 2026-03-06 09:53:57 +07:00
rstubryan 9ff6f3a35d refactor(FE): Refactor form components to improve state handling 2026-03-05 15:59:32 +07:00
rstubryan 333dd01f92 refactor(FE): Fix dependency array in useMemo for SalesClosingTable 2026-03-05 15:33:50 +07:00
rstubryan 75f765ee69 chore(FE): Add setTableState to useEffect dependency array 2026-03-05 15:05:17 +07:00
rstubryan 945bdb8b27 refactor(FE): Refactor formik field value setter in WarehousesTable 2026-03-05 15:02:00 +07:00
rstubryan 77eae32a3d refactor(FE): Refactor Formik field updates to use setFieldValue 2026-03-05 15:00:20 +07:00
rstubryan a5ebc6d1ae refactor(FE): Fix dependency arrays and improve error handling in
purchase components
2026-03-05 14:17:42 +07:00
rstubryan 15c7452d7b chore(FE): Fix missing dependency in useEffect hook 2026-03-05 14:15:41 +07:00
rstubryan 5dac900a1a refactor(FE): Refactor date filter state management in UniformityTable 2026-03-05 14:11:58 +07:00
rstubryan 4b49cd18f5 refactor(FE): Refactor form handlers to use consistent formik utilities 2026-03-05 13:45:01 +07:00
rstubryan a4cb4e202b refactor(FE): Fix dependency arrays and refactor setFieldValue usage 2026-03-05 13:36:43 +07:00
rstubryan 4a69eef294 fix(FE): Add handler to open filter modal and trigger form validation 2026-03-05 12:03:54 +07:00
rstubryan 2de6636bbf refactor(FE): Refactor tab actions to use memoized components 2026-03-05 11:59:27 +07:00
rstubryan a7951b6c28 refactor(FE): Fix missing dependencies in UniformityTable effect hooks 2026-03-05 11:47:16 +07:00
rstubryan 03baba40a6 refactor(FE): Remove unused params argument from
generateReportExpensePDF
2026-03-05 11:41:43 +07:00
rstubryan 96ef6f8496 refactor(FE): Refactor tab actions into a memoized component 2026-03-05 11:40:32 +07:00
rstubryan 94ab48d3f6 refactor(FE): Refactor tab actions to use memoized component 2026-03-05 11:21:56 +07:00
rstubryan ea88c3ce8e refactor(FE): Refactor tab actions to use memoized component 2026-03-05 11:10:36 +07:00
rstubryan 267ef9d812 refactor(FE): Refactor CustomerPaymentTab to use filterParams instead of
formik values
2026-03-05 11:01:52 +07:00
rstubryan b5fc1d4310 refactor(FE): Refactor tab actions to use memoized component 2026-03-05 10:57:13 +07:00
rstubryan c98e7d8cb3 refactor(FE): Fix population check to ensure proper boolean evaluation 2026-03-05 10:24:39 +07:00
rstubryan 1acbc91cfe refactor(FE): Reset dependent fields when location or project flock
changes
2026-03-05 10:19:30 +07:00
rstubryan 807041834b refactor(FE): Refactor formik field updates to use destructured methods 2026-03-05 09:54:50 +07:00
rstubryan 4de21561b3 refactor(FE): Refactor delivery order value initialization logic 2026-03-05 09:41:25 +07:00
rstubryan 1938f6cbda refactor(FE): Refactor DeliveryOrderProductTable to support delivery
order rendering
2026-03-04 17:13:30 +07:00
rstubryan f4a522fc0c refactor(FE): Refactor form field updates to use destructured Formik
methods
2026-03-04 16:48:21 +07:00
rstubryan 7ae04b3f3e refactor(FE): Refactor DashboardProduction callbacks for memoization 2026-03-04 14:12:45 +07:00
rstubryan b786acf71a chore(FE): Add setTableState to useEffect dependency arrays 2026-03-04 14:11:17 +07:00
rstubryan bb28ae8613 Merge branch 'development' of gitlab.com:mbugroup/lti-web-client into dev/restu 2026-03-04 11:59:06 +07:00
rstubryan 080592ff01 chore(FE): Remove unused variables and imports across components 2026-03-04 11:58:35 +07:00
Rivaldi A N S 6d41203a5e Merge branch 'feat/global-search-store' into 'development'
[FEAT/FE] Add Global Search Store and Filter for Each Table Index (All Module)

See merge request mbugroup/lti-web-client!340
2026-03-04 04:44:27 +00:00
rstubryan 89d7d3ef91 refactor(FE): Format imports in PurchaseTable.tsx 2026-03-04 11:07:30 +07:00
rstubryan 18ebf75aa7 feat(FE): Integrate UI store and pathname handling in table components 2026-03-04 11:06:53 +07:00
rstubryan 6e7dfebacc feat(FE): Integrate UI store for table state and search handling 2026-03-04 10:56:35 +07:00
rstubryan 4b93cb4589 feat(FE): Integrate UI store state with InventoryProductTable 2026-03-04 10:51:51 +07:00
rstubryan 58f1ab82c7 refactor(FE): Refactor long lines for readability in MovementTable 2026-03-04 10:45:15 +07:00
rstubryan 1fb9687142 feat(FE): Add filter functionality to inventory tables 2026-03-04 10:44:22 +07:00
rstubryan 4f6d71f1f4 refactor(FE): Refactor payload creation in
PurchaseOrderAcceptApprovalForm
2026-03-04 10:20:50 +07:00
rstubryan d502ec707c refactor(FE): Move error message above form actions in
InventoryAdjustmentForm
2026-03-04 09:36:37 +07:00
rstubryan fa86f488e1 refactor(FE): Standardize search input placeholders to "Search" 2026-03-03 14:19:52 +07:00
rstubryan 5e5400f56b feat(FE): Add advanced filtering capabilities to master data tables 2026-03-03 14:03:29 +07:00
rstubryan 741884ac29 feat(FE): Add filter schemas and types for master data components 2026-03-03 14:02:06 +07:00
rstubryan 4e278c5687 refactor(FE): Integrate UI store and pathname sync in master-data tables 2026-03-03 10:59:26 +07:00
rstubryan 3d2e40518b refactor(FE): Add table state management with path grouping logic 2026-03-03 10:33:03 +07:00
Rivaldi A N S cbab7f52f2 Merge branch 'dev/hotfix/restu' into 'development'
[HOTFIX/FE] Fix Constant Value of Adjustment Stock (Recording Depletion In)

See merge request mbugroup/lti-web-client!339
2026-03-03 02:50:52 +00:00
rstubryan ceb316d3da refactor(FE): Refactor subtype label mapping and fix depletion subtype
value
2026-03-03 09:15:57 +07:00
Rivaldi A N S 9ea152aef9 Merge branch 'hotfix/adjustment-stock-api-usage' into 'development'
[HOTFIX/FE] Fix Inventory AdjustmentForm using Product API Instead of Product Warehouse API

See merge request mbugroup/lti-web-client!338
2026-03-02 08:58:16 +00:00
rstubryan 910ea85b62 refactor(FE): Refactor InventoryAdjustmentForm to simplify product
handling
2026-03-02 15:24:14 +07:00
rstubryan 968243c370 refactor(FE): Refactor tab actions store to use slice pattern 2026-03-02 14:44:20 +07:00
Rivaldi A N S f1ba577a97 Merge branch 'fix/master-data-product-nonstock' into 'development'
[FIX/FE] Adjustment Product and Nonstock Match with FIFO Stock V2

See merge request mbugroup/lti-web-client!337
2026-03-02 07:33:43 +00:00
rstubryan c6b906a28e refactor(FE): Fix loading state for flag and sub-flag dropdowns 2026-03-02 14:12:42 +07:00
rstubryan eafcfd2f28 refactor(FE): Fix null check for product_flag_mapping in ProductForm 2026-03-02 14:06:12 +07:00
rstubryan 0e5d38f75c refactor(FE): Update field name from 'isExpedition' to 'flags' in
handler
2026-03-02 13:57:56 +07:00
rstubryan f2b59ded3c feat(FE): Add skeleton components for master data tables 2026-03-02 13:50:25 +07:00
rstubryan 1341b1ff53 refactor(FE): Add skeleton loaders for inventory tables 2026-03-02 13:31:23 +07:00
rstubryan 9c4c750664 refactor(FE): Refactor table skeleton components for consistency 2026-03-02 12:10:06 +07:00
rstubryan d3501e5f3d refactor(FE): Add skeleton loaders for ProjectFlock and TransferToLaying
tables
2026-03-02 11:56:27 +07:00
rstubryan d96388e5f4 refactor(FE): Rename and update import for UniformityTableFilter module 2026-03-02 11:42:34 +07:00
rstubryan 749b7d6f1a refactor(FE): Refactor UniformityTable to improve loading and empty
states
2026-03-02 11:34:27 +07:00
rstubryan aadf10b8b9 refactor(FE): Refactor master-data pages to simplify component rendering 2026-03-02 11:17:54 +07:00
rstubryan 7db6ae4077 refactor(FE): Refactor NonstocksTable to use Popover for row actions 2026-03-02 10:47:17 +07:00
rstubryan 7eaf6b7a3a refactor(FE): Refactor ProductTable and RowOptionsMenu for improved
clarity
2026-03-02 10:42:34 +07:00
rstubryan 8397d76171 refactor(FE): Add fallback for product flag mapping on constants error 2026-03-02 10:15:53 +07:00
rstubryan 56d4b8a5c9 refactor(FE): Update ProductFlagMapping types and related references 2026-03-02 10:15:06 +07:00
rstubryan ef9d820c0d refactor(FE): Refactor ProductForm to fetch constants via SWR 2026-03-02 10:06:06 +07:00
rstubryan 4e3c6736ab refactor(FE): Rename "Sub Flags" header to "Kategori Flags" 2026-03-02 09:58:01 +07:00
rstubryan aa1ef7a559 feat(FE): Add constants transformation utilities and API service 2026-03-02 09:57:21 +07:00
rstubryan 04b5a7bd4d refactor(FE): Update label in NonstockForm to 'Flags Ekspedisi' 2026-02-27 15:42:24 +07:00
rstubryan 3aec412599 refactor(FE): Update label text in NonstockForm RadioGroup 2026-02-27 15:38:47 +07:00
rstubryan 80a94c48c3 refactor(FE): Format product prices using formatCurrency helper 2026-02-27 15:38:11 +07:00
rstubryan 9bd4a73a90 refactor(FE): Refactor flags field to boolean and update form handling 2026-02-27 15:36:44 +07:00
rstubryan d1c6fe8fb4 refactor(FE): Add conditional validation for sub_flags in
ProductForm.schema
2026-02-27 15:03:09 +07:00
rstubryan a4378ebd04 refactor(FE): Refactor product flags to support single flag and
sub-flags
2026-02-27 14:57:45 +07:00
rstubryan 16f2f2bc06 feat(FE): Add PRODUCT_FLAG_MAPPING with flags, sub-flags, and mappings 2026-02-27 14:08:17 +07:00
rstubryan 64843a36ab refactor(FE): Refactor date input components for consistency and layout 2026-02-27 13:34:51 +07:00
rstubryan 396a5ab5ba refactor(FE): Rename and update FinanceTableFilter schema import path 2026-02-27 13:12:12 +07:00
rstubryan 9f4c041ec8 refactor(FE): Update FinanceTable layout and conditional styling 2026-02-27 11:27:51 +07:00
rstubryan 5e3d2273d1 refactor(FE): Refactor RowOptionsMenu to use PopoverButton and
PopoverContent
2026-02-27 11:25:37 +07:00
rstubryan 0b01eefe20 refactor(FE): Update button labels for clarity in FinanceTable 2026-02-27 11:23:06 +07:00
rstubryan 43045b3f8b Merge branch 'development' of gitlab.com:mbugroup/lti-web-client into dev/restu 2026-02-27 11:21:34 +07:00
rstubryan d3ce60d3ba refactor(FE): Refactor FinanceTable and simplify Finance page structure 2026-02-27 11:14:13 +07:00
Rivaldi A N S 8c7577dcc5 Merge branch 'fix/adjustment-stock-and-ui-related' into 'development'
[FIX/FE] Refactor Adjustment Stock with Latest FIFO Stock and Adjustment UI (Production, Inventory, Pembelian, Penjualan, Biaya, Closing)

See merge request mbugroup/lti-web-client!336
2026-02-27 04:02:05 +00:00
rstubryan da3cadc37d Merge branch 'development' of gitlab.com:mbugroup/lti-web-client into fix/adjustment-stock-and-ui-related 2026-02-27 10:28:35 +07:00
rstubryan 2e6aa1b83a refactor(FE): Filter out excluded product flags in product options 2026-02-27 10:04:54 +07:00
rstubryan 8811976f53 chore(FE): Remove redundant comments in InventoryAdjustmentForm 2026-02-27 09:30:10 +07:00
rstubryan 9fb65bdacd feat(FE): Validate quantity against available stock 2026-02-27 09:29:33 +07:00
Adnan Zahir 53e018aece Merge branch 'staging' into 'production'
refactor(FE): Add tab state management and skeleton for

See merge request mbugroup/lti-web-client!335
2026-02-26 16:37:04 +07:00
rstubryan 6f6f54571f refactor(FE): Reset transaction subtype when product changes for
RECORDING
2026-02-26 16:20:00 +07:00
rstubryan 3099588141 refactor(FE): Filter transaction subtypes based on selected product
flags
2026-02-26 16:10:19 +07:00
rstubryan 47ee911852 feat(FE): Add stock availability badge to product dropdown 2026-02-26 15:56:31 +07:00
rstubryan b4d0ed1537 refactor(FE): Refactor product options handling in
InventoryAdjustmentForm
2026-02-26 15:39:49 +07:00
rstubryan 36f2368e95 refactor(FE): Refactor product selection to use ProductWarehouse API 2026-02-26 15:31:43 +07:00
rstubryan a4d5cbb117 refactor(FE): Simplify conditional formatting in InventoryAdjustmentForm 2026-02-26 15:12:19 +07:00
rstubryan c926a81756 refactor(FE): Update transaction subtype handling and labels 2026-02-26 15:11:39 +07:00
rstubryan 2476b6a4b4 refactor(FE): Format prices and totals as currency in
InventoryAdjustmentTable
2026-02-26 14:52:44 +07:00
rstubryan 01c1843fd5 refactor(FE): Refactor MovementTable to use Popover for row actions 2026-02-26 14:37:34 +07:00
rstubryan f86498e350 refactor(FE): Refactor InventoryProductTable to use Popover for row
options
2026-02-26 14:29:27 +07:00
rstubryan 5cd24f2c46 refactor(FE): Fix form error list visibility and field touch handling 2026-02-26 14:12:11 +07:00
rstubryan da0be9cb52 refactor(FE): Update input components to adjust spacing and text size 2026-02-26 14:11:33 +07:00
rstubryan da1cdfb59e refactor(FE): Refactor transaction type handling with useEffect 2026-02-26 13:58:21 +07:00
rstubryan a991150262 refactor(FE): Refactor InventoryAdjustmentForm layout for better
responsiveness
2026-02-26 13:32:52 +07:00
rstubryan ee49c91fba refactor(FE): Add type annotation for inventory adjustment payload 2026-02-26 12:08:36 +07:00
rstubryan 75463326e3 refactor(FE): Refactor InventoryAdjustmentForm to remove 'edit' type
handling
2026-02-26 12:07:14 +07:00
rstubryan e98c49ac5a refactor(FE): Update columns in InventoryAdjustmentTable 2026-02-26 11:48:27 +07:00
rstubryan aeeb0b721c refactor(FE): Refactor InventoryAdjustmentTable for improved readability 2026-02-26 11:31:17 +07:00
Rivaldi A N S b14bc00af4 Merge branch 'fix/fe/return-to-logout' into 'development'
[FIX/FE] add return fe cors

See merge request mbugroup/lti-web-client!334
2026-02-26 04:27:56 +00:00
ragilap e2a4088e77 [FEAT/BE] add return fe cors 2026-02-26 11:19:47 +07:00
rstubryan ebe80358ee refactor(FE): Remove unused import isResponseSuccess from
InventoryAdjustmentForm
2026-02-26 11:11:51 +07:00
rstubryan 9d4e9f6318 refactor(FE): Refactor InventoryAdjustmentForm to use useSelect for
Kandang
2026-02-26 11:09:13 +07:00
rstubryan e5007a285a refactor(FE): Refactor InventoryAdjustmentForm to improve state
management
2026-02-26 10:38:41 +07:00
rstubryan da82c704d5 refactor(FE): Fix validation message formatting in
InventoryAdjustmentForm
2026-02-26 10:12:48 +07:00
rstubryan 88b9c890e5 refactor(FE): Refactor inventory adjustment form 2026-02-26 10:10:59 +07:00
rstubryan 5a67901722 feat(FE): Add transaction type and subtype options to constants 2026-02-26 09:11:05 +07:00
rstubryan 0031a65f97 refactor(FE): Ensure filter modal closes on reset across components 2026-02-25 16:30:31 +07:00
rstubryan a89e83af29 refactor(FE): Fix multiline formatting for isError conditions 2026-02-25 15:57:25 +07:00
rstubryan a75d84556a refactor(FE): Refactor date validation to use shared state and cleanup 2026-02-25 15:56:12 +07:00
rstubryan 0af7b172a0 refactor(FE): Refactor PurchaseTable to use Popover for row options menu 2026-02-25 12:08:31 +07:00
rstubryan 8be33b230b refactor(FE): Refactor row options menu to use popover components 2026-02-25 11:59:17 +07:00
rstubryan 4fda2f661a refactor(FE): Remove unnecessary padding and margin classes in
components
2026-02-25 11:49:41 +07:00
rstubryan 22b1102454 refactor(FE): Refactor ExpensesTable to use ExpensesFilterModal 2026-02-25 11:40:42 +07:00
rstubryan e2e64f093f refactor(FE): Refactor TransferToLayingFilterModal to use schema
validation
2026-02-25 11:02:26 +07:00
rstubryan 90942b41b9 refactor(FE): Fix formatting of disabled condition in RecordingTable
button
2026-02-25 10:53:32 +07:00
rstubryan 93a2d99b7f refactor(FE): Disable filter button if kandang_id is not selected 2026-02-25 10:52:03 +07:00
rstubryan dae9a24a7c refactor(FE): Refactor array formatting and improve code readability 2026-02-25 10:41:34 +07:00
rstubryan f701ab0d91 refactor(FE): Replace active filter count logic with ButtonFilter
component
2026-02-25 10:39:14 +07:00
rstubryan 47a2439777 feat(FE): Add support for grouped fields in ButtonFilter active count
calculation
2026-02-25 10:07:26 +07:00
rstubryan cce84a3a6f refactor(FE): Refactor ButtonFilter to support excluded fields with
useMemo
2026-02-25 09:46:19 +07:00
Rivaldi A N S f92622cc22 Merge branch 'fix/adjustment-production-module' into 'development'
[FIX/FE] Adjustment Production Module (Recording & Uniformity)

See merge request mbugroup/lti-web-client!333
2026-02-25 02:32:21 +00:00
rstubryan f0041ca938 refactor(FE): Filter inactive kandangs in UniformityForm options 2026-02-24 21:05:22 +07:00
rstubryan 6566b881b2 refactor(FE): Use formatTitleCase for category text in StatusBadge 2026-02-24 21:02:56 +07:00
rstubryan 1a2e38568b refactor(FE): Remove unused page size selector and related logic 2026-02-24 15:46:32 +07:00
rstubryan ced3970aae refactor(FE): Add padding to RecordingTableSkeleton container class 2026-02-24 15:38:19 +07:00
rstubryan 9de31c991d refactor(FE): Refactor approval checks for readability 2026-02-24 15:34:11 +07:00
rstubryan bdca10e0ac refactor(FE): Handle rejected recordings in selection logic 2026-02-24 15:31:22 +07:00
rstubryan 9a5e2987d5 refactor(FE): Conditionally load data based on filter modal state 2026-02-24 15:22:06 +07:00
rstubryan a5b4deaac4 refactor(FE): Refactor RecordingTable layout for improved readability 2026-02-24 15:13:37 +07:00
rstubryan 82953f4c9c Merge branch 'development' of gitlab.com:mbugroup/lti-web-client into dev/restu 2026-02-24 14:48:34 +07:00
rstubryan 509fc5476d refactor(FE): Refactor recording table and add filter modal 2026-02-24 14:48:09 +07:00
Rivaldi A N S 6a10849a84 Merge branch 'fix/adjustment-pf-report-penjualan' into 'development'
[FIX/FE] Adjustment Project Flock Related, Penjualan Calculation (Both SO/DO) and Report Filter Display Issue

See merge request mbugroup/lti-web-client!332
2026-02-24 03:24:42 +00:00
rstubryan 50424a25fc refactor(FE): Remove redundant transformations and validations in
schemas
2026-02-23 16:05:12 +07:00
rstubryan 35b09b514f refactor(FE): Refactor form field change handlers for real-time
calculations
2026-02-23 15:30:54 +07:00
rstubryan 81e4e1fc6a refactor(FE): Update validation rules for SalesOrderProduct schema 2026-02-23 14:12:44 +07:00
rstubryan 62c9ab014d refactor(FE): Add period filter to ProjectFlockTable 2026-02-23 13:51:32 +07:00
rstubryan ba28d64562 refactor(FE): Format type definition for readability 2026-02-23 13:40:00 +07:00
rstubryan 75ee058818 feat(FE): Add filter functionality to ProjectFlockTable 2026-02-23 13:38:47 +07:00
rstubryan 755bddc74c refactor(FE): Fix logic for checking active projects in the same kandang
from the validation logic.
2026-02-23 12:10:44 +07:00
rstubryan 08aa1900a8 feat(FE): Add validation for disabling close button in
ProjectFlockDetail
2026-02-23 11:55:07 +07:00
rstubryan 7e1166b5e8 refactor(FE): Refactor tab actions store to use a unified implementation 2026-02-23 11:05:11 +07:00
rstubryan 75e9b06a83 refactor(FE): Set default value for show_unrecorded to false 2026-02-23 10:27:34 +07:00
Adnan Zahir ca58e19a48 Merge branch 'development' into 'staging'
Development

See merge request mbugroup/lti-web-client!331
2026-02-23 09:32:40 +07:00
Rivaldi A N S ac2d83a666 Merge branch 'fix/cleanup-code' into 'development'
[FIX/FE] Partial Remove Issue Warning on Build (1/2)

See merge request mbugroup/lti-web-client!330
2026-02-23 02:25:50 +00:00
rstubryan 03e0cebe35 refactor(FE): Fix formatting for array dependencies and className
conditions
2026-02-20 14:42:46 +07:00
rstubryan 1cc0e16c01 refactor(FE): Replace ButtonFilter with custom Filter button 2026-02-20 14:41:50 +07:00
rstubryan 1f2f3acebb refactor(FE): Remove unused imports and redundant code 2026-02-20 14:17:26 +07:00
Rivaldi A N S de0f9ae985 Merge branch 'fix/uniformity-ttl-project-flock-issue' into 'development'
[FIX/FE] Fix Isssue on Uniformity (Week Calculation), Project Flock (Modal Position), Transfer To Laying (Copywriting)

See merge request mbugroup/lti-web-client!329
2026-02-20 03:36:15 +00:00
rstubryan a0e79168b2 refactor(FE): Refactor setClosingLoading to use single-line syntax 2026-02-20 10:25:30 +07:00
rstubryan 797f88fe15 feat(FE): Add project flock closing modal and zustand store 2026-02-20 10:23:47 +07:00
rstubryan 4c3e7c615f refactor(FE): Refactor conditional text formatting in modal component 2026-02-20 10:12:06 +07:00
rstubryan b35b6c2ab8 feat(FE): Add submittedActionType state to track modal action type 2026-02-20 10:11:16 +07:00
Adnan Zahir 0971e6ddeb Merge branch 'development' into 'staging'
Development

See merge request mbugroup/lti-web-client!328
2026-02-20 09:53:46 +07:00
rstubryan bbbd767cf2 refactor(FE): Fix week calculation logic in UniformityForm 2026-02-20 09:21:42 +07:00
Rivaldi A N S 3e30dcb04e Merge branch 'fix/closing-project-flock-recording-issue' into 'development'
[FIX/FE] Adjustment Closing Related (Closing Report, Closing Detail and Closing Table), Project Flock Detail (Form, Chickin), Recording Table

See merge request mbugroup/lti-web-client!327
2026-02-19 09:45:18 +00:00
rstubryan 1a137e7500 refactor(FE): Normalize status keys to uppercase in status utilities 2026-02-19 15:37:46 +07:00
rstubryan 3be6d5bb26 refactor(FE): Update status mappings for "CREATED" to "Pengajuan" 2026-02-19 15:35:08 +07:00
rstubryan e22f95cc58 refactor(FE): Remove unused variable approval.step_name in
RecordingTable
2026-02-19 15:28:27 +07:00
rstubryan 6ac903313c refactor(FE): Refactor chickin approval modal logic into Zustand store 2026-02-19 15:22:28 +07:00
rstubryan a4ff92520a refactor(FE): Update toast message for Project Flock approval/rejection 2026-02-19 14:50:43 +07:00
rstubryan 608cf4cbe7 feat(FE): Add StatusBadge to display project status in ClosingsTable 2026-02-19 14:41:21 +07:00
rstubryan 60e360537e refactor(FE): Refactor ClosingsTable header for improved styling 2026-02-19 14:37:20 +07:00
rstubryan e9784bd5ed refactor(FE): Refactor ClosingsTable component and update styles 2026-02-19 14:33:35 +07:00
rstubryan 4f018eb2b1 feat(FE): Add filter modal and skeleton components for ClosingsTable 2026-02-19 14:18:22 +07:00
rstubryan 40b3d779bc refactor(FE): Update column header and adjust table margin 2026-02-19 13:52:32 +07:00
rstubryan 1bdf413650 feat(FE): Add badge display for category columns in tables 2026-02-19 13:33:11 +07:00
rstubryan 495b1b2869 refactor(FE):Add HPP Amount column to daily marketing export 2026-02-19 12:18:27 +07:00
rstubryan a231140bc0 refactor(FE): Refactor ProductionDataClosingTab to use Card component 2026-02-19 12:02:04 +07:00
rstubryan a0af934002 feat(FE): Add headings and improve layout for financial tables 2026-02-19 11:52:57 +07:00
rstubryan 82975219a8 refactor(FE): Update border styles for ClosingDetailTabs and
ClosingKandangList
2026-02-19 11:43:46 +07:00
rstubryan 60ae670f24 refactor(FE): Refactor UI and improve conditional rendering in closing
pages
2026-02-19 11:38:34 +07:00
rstubryan 7d79b6b957 refactor(FE): Refactor table and tab styles for consistent spacing and
layout
2026-02-19 11:27:24 +07:00
rstubryan 8a1e0f080f refactor(FE): Refactor table components for consistent styling and
cleanup
2026-02-19 11:14:39 +07:00
rstubryan c3a69bc66a refactor(FE): Add Icon component and update table styles 2026-02-19 10:55:07 +07:00
rstubryan 4c1f11d859 refactor(FE): Update table column headers from '#' to 'No' 2026-02-19 10:50:52 +07:00
rstubryan 350ff0fbbe refactor(FE): Simplify headerRowClassName formatting in tables 2026-02-19 10:46:03 +07:00
rstubryan 4c70ec7cab refactor(FE): Refactor table components to improve styling and structure 2026-02-19 10:43:37 +07:00
rstubryan 944db8dba7 refactor(FE): Refactor Card and SapronakClosingSkeleton components for
readability
2026-02-19 10:15:20 +07:00
rstubryan befc1c1217 refactor(FE): Refactor skeleton components to remove default columns 2026-02-19 10:14:28 +07:00
rstubryan 8fe19feaac feat(FE): Add skeleton components for closing pages 2026-02-19 10:03:25 +07:00
rstubryan 9c953ca382 refactor(FE): Fix incorrect import and component usage in
SapronakClosingTab
2026-02-19 09:37:54 +07:00
rstubryan c53430fa1f refactor(FE): Organize Sapronak tables into a dedicated folder 2026-02-19 09:35:42 +07:00
rstubryan 1fe722cb81 refactor(FE): Refactor code formatting for consistency and readability 2026-02-19 09:33:31 +07:00
rstubryan d9bd73d8c1 refactor(FE): Refactor sales data fetching and component structure 2026-02-19 09:32:33 +07:00
rstubryan 0235494d46 refactor(FE): Refactor HPP Expedition handling in ClosingDetailPage 2026-02-19 09:29:01 +07:00
rstubryan 32354e3c2d refactor(FE): Adjust padding on tab header wrapper in ClosingDetailTabs 2026-02-18 16:32:26 +07:00
rstubryan 14e1c59a69 refactor(FE): Refactor ClosingDetail component to use tab store 2026-02-18 16:28:56 +07:00
rstubryan 42cc0f2661 refactor(FE): Refactor component and file names for consistency 2026-02-18 16:15:27 +07:00
rstubryan 2f5d518e15 refactor(FE): Move table components to a shared "table" directory 2026-02-18 15:43:52 +07:00
rstubryan d085b18788 refactor(FE): Refactor folder structure for closing-related components 2026-02-18 15:40:06 +07:00
Rivaldi A N S d68bedf5ce Merge branch 'fix/refactor-project-flock-detail' into 'development'
[FIX/FE] Refactor Project Flock Detail UI

See merge request mbugroup/lti-web-client!326
2026-02-18 08:21:57 +00:00
rstubryan 2169c0ea62 refactor(FE): Update StatusBadge color and text in ChickinForm 2026-02-18 15:00:26 +07:00
rstubryan 02165df89c refactor(FE): Update status badge text to use English labels 2026-02-18 14:50:11 +07:00
rstubryan 15289951e6 refactor(FE): Remove unused CSS classes from table components 2026-02-18 14:42:41 +07:00
rstubryan 62674044e7 refactor(FE): Replace Badge with StatusBadge in ProjectFlockDetail 2026-02-18 14:33:28 +07:00
rstubryan e94967ea4c refactor(FE): Adjust grid column layout based on selectedKandang status 2026-02-18 14:28:55 +07:00
rstubryan ed576fc8eb feat(FE): Improve empty state handling and add "Unclose Flock"
functionality
2026-02-18 13:49:02 +07:00
rstubryan d4c6a05c0c refactor(FE): Update button behavior based on kandang status 2026-02-18 13:21:29 +07:00
rstubryan da27f4c581 refactor(FE): Refactor status badge logic in ProjectFlockClosingForm 2026-02-18 13:15:06 +07:00
rstubryan 9d6cc90162 refactor(FE): Update table columns and improve UI for Project Flock
pages
2026-02-18 11:52:30 +07:00
rstubryan 512ccddfc7 refactor(FE): Refactor ChickinForm and ProjectFlockClosingForm
components
2026-02-18 09:59:50 +07:00
Rivaldi A N S f5b16b68e9 Merge branch 'dev/hotfix/restu' into 'development'
[HOTFIX/FE] Refactor fetching logic useSelect on Depletion (Recording)

See merge request mbugroup/lti-web-client!325
2026-02-15 03:11:31 +00:00
rstubryan e8e4f7b877 refactor(FE): Simplify product filtering logic in RecordingForm 2026-02-15 08:47:07 +07:00
Rivaldi A N S b6edd8f10c Merge branch 'feat/refactor-report-module-ui' into 'development'
[FEAT/FE] Refactor UI on Report Module and Additional Adjustment (Project Flock, Product Stock, Marketing)

See merge request mbugroup/lti-web-client!324
2026-02-13 03:59:03 +00:00
rstubryan ec3a0367dd refactor(FE): Hide delete button if only one item remains in table 2026-02-13 10:37:12 +07:00
rstubryan e9da5210ad refactor(FE): Simplify type definition for ReportExpenseColumn 2026-02-13 10:22:13 +07:00
rstubryan 67f2a80f23 refactor(FE): Refactor expense report page to use tab-based layout 2026-02-13 10:19:09 +07:00
rstubryan ceb594a4cc refactor(FE): Rename and update paths for ProductionResult components 2026-02-13 09:55:22 +07:00
rstubryan d312da4c66 refactor(FE): Refactor dropdown and export button components 2026-02-13 09:53:33 +07:00
rstubryan 3a676723e4 refactor(FE): Refactor table class names in DailyMarketingTab 2026-02-13 09:42:04 +07:00
rstubryan 684f67593f refactor(FE): Refactor SelectInput styles for improved readability 2026-02-13 09:32:45 +07:00
rstubryan d5962f94a1 refactor(FE): Refactor production result filter to use OptionType 2026-02-13 09:29:37 +07:00
rstubryan 5c00893ea3 refactor(FE): Refactor production result components and improve UI 2026-02-13 09:24:42 +07:00
rstubryan 211622c7b0 refactor(FE): Format import statements in report-tab.store.ts 2026-02-12 16:19:40 +07:00
rstubryan dbb523c710 refactor(FE): Refactor tab store to use a unified ReportTabStore 2026-02-12 16:16:40 +07:00
rstubryan 6aae18df54 refactor(FE): Update import paths for finance and marketing tab stores 2026-02-12 15:49:26 +07:00
rstubryan cb171118ee refactor(FE): Restrict row selection to specific approval criteria 2026-02-12 15:32:11 +07:00
rstubryan 45ac8348fe feat(FE): Add "Stock Akhir" column to StockLogTable 2026-02-12 15:23:17 +07:00
rstubryan 5d92e6774e feat(FE): Add stock field to StockLog type 2026-02-12 15:14:00 +07:00
rstubryan 6595ff7a6e refactor(FE): Add function to export production results to Excel 2026-02-12 15:03:24 +07:00
rstubryan dc4e945a35 refactor(FE): Refactor table column definitions to remove unused row
parameter
2026-02-12 14:50:19 +07:00
rstubryan b154b478bc refactor(FE): Refactor production result components structure 2026-02-12 14:15:56 +07:00
rstubryan 510573e66f refactor(FE): Add Excel export functionality for daily marketing report 2026-02-12 13:55:31 +07:00
rstubryan dbcf469123 refactor(FE): Remove DailyMarketingsTable component and refactor related
files
2026-02-12 13:44:22 +07:00
rstubryan 325fb373a8 refactor(FE): Rename components for clarity 2026-02-12 13:19:16 +07:00
rstubryan 4b6a8b2773 refactor(FE): Refactor file names for consistency in marketing report
components
2026-02-12 11:45:50 +07:00
rstubryan 5e4619fac7 feat(FE): Add DailyMarketingReportFilter and
DailyMarketingReportSkeleton components
2026-02-12 11:38:46 +07:00
rstubryan 43d26b4833 refactor(FE): Refactor marketing report components and add HPP filter 2026-02-12 11:16:26 +07:00
rstubryan 6d2855d117 refactor(FE): Add zustand store for marketing tab actions 2026-02-12 11:15:42 +07:00
Adnan Zahir 25fbf95062 Merge branch 'development' into 'staging'
Development

See merge request mbugroup/lti-web-client!323
2026-02-12 11:06:11 +07:00
rstubryan ee53ea61cc refactor(FE): Fix missing dependency in useCallback hooks 2026-02-12 10:57:22 +07:00
rstubryan 322b519def refactor(FE): Refactor filter schema and form handling for
PurchasesPerSupplier
2026-02-12 10:55:40 +07:00
rstubryan e23b53d797 refactor(FE): Refactor CustomerPaymentTab to use StatusBadge component 2026-02-12 10:46:06 +07:00
rstubryan fd78ca6ac1 refactor(FE): Refactor CustomerPaymentTab to use Formik for filter
management
2026-02-12 10:28:36 +07:00
rstubryan 28dabcbeb6 refactor(FE): Refactor filter parameter keys to singular form 2026-02-12 09:52:21 +07:00
rstubryan 62dd1de150 refactor(FE): Reset form values on filter modal open and submit 2026-02-12 09:45:18 +07:00
rstubryan 166e95930b refactor(FE): Remove unused imports and cleanup comments 2026-02-12 09:35:24 +07:00
rstubryan 52d58d0921 refactor(FE): Refactor PurchasesPerSupplierTab to use Formik for filters 2026-02-11 16:44:10 +07:00
rstubryan 14d0dc590f refactor(FE): Make dropdown filters clearable in PurchasesPerSupplierTab 2026-02-11 16:17:15 +07:00
rstubryan ed781da372 refactor(FE): Change Tabs variant from 'lifted' to 'boxed' 2026-02-11 16:12:14 +07:00
rstubryan 4e5745d237 refactor(FE): Add tab state management and skeleton for
PurchasesPerSupplierTab
2026-02-11 15:53:32 +07:00
Rivaldi A N S b03ef4923e Merge branch 'hotfix/purchase-and-project-flock-form' into 'development'
[HOTFIX/FE] Refactor Purchase (Staff & Receipt/Receive) and Project Flock Form (Modal)

See merge request mbugroup/lti-web-client!322
2026-02-11 07:29:22 +00:00
Rivaldi A N S d7486e8b8a Merge branch 'fix/refactor-native-pdf-renderer' into 'development'
[FIX/FE] Refactor Native PDF Renderer to Component Based

See merge request mbugroup/lti-web-client!321
2026-02-11 07:28:43 +00:00
rstubryan 498602a2c9 refactor(FE): Refactor input components to use consistent color
variables
2026-02-11 14:19:52 +07:00
rstubryan 1b4d373fea refactor(FE): Improve disabled and read-only states for input components 2026-02-11 13:54:48 +07:00
rstubryan 4215b0ea7d feat(FE): Add success modal and state management for ProjectFlock 2026-02-11 13:44:00 +07:00
rstubryan c3dee6b292 feat(FE): Add Zustand store for ProjectFlock management 2026-02-11 13:42:56 +07:00
rstubryan 3834982fca feat(FE): Add "has_chickin" property to disable quantity editing 2026-02-11 11:40:29 +07:00
rstubryan 539de03a5b refactor(FE): Update section width to use full width 2026-02-11 10:41:57 +07:00
rstubryan 0f1d2ce477 refactor(FE): Refactor PDF table components to simplify imports 2026-02-11 10:39:37 +07:00
rstubryan 70b63f7773 refactor(FE): Refactor PdfTable components to support generic data types 2026-02-11 10:38:51 +07:00
rstubryan 02d13efc25 refactor(FE): Refactor table column headers for clarity and consistency 2026-02-11 09:28:24 +07:00
rstubryan 1227b7639f refactor(FE): Refactor ProductionResultReportPDF to use reusable PDF
components
2026-02-10 16:52:52 +07:00
rstubryan 5593463eab refactor(FE): Refactor marketing report components into a dedicated
folder
2026-02-10 16:20:19 +07:00
rstubryan be7b2a0f93 refactor(FE): Refactor DailyMarketingReportPDF component for cleaner
structure
2026-02-10 16:06:11 +07:00
rstubryan 4c6ac6e8e1 refactor(FE): Refactor PdfStatusBadge to use a single style prop 2026-02-10 14:04:44 +07:00
rstubryan 5cc51c52d9 feat(FE): Add PdfPageNumber component for rendering page numbers 2026-02-10 14:02:53 +07:00
rstubryan 59eb781a22 refactor(FE): Refactor PDF components to support custom styles 2026-02-10 14:00:28 +07:00
rstubryan 2af83bed8a refactor(FE): Refactor getDetailData to remove unused parameters 2026-02-10 13:37:34 +07:00
rstubryan 4775c1e115 refactor(FE): Refactor HppPerKandang PDF generation logic and UI
renderer
2026-02-10 12:01:51 +07:00
rstubryan d0dea834c1 refactor(FE): Refactor HppPerKandang export logic to use ExcelJS 2026-02-10 11:54:15 +07:00
rstubryan def894e5f4 refactor(FE): Refactor PDF generation for purchases per supplier 2026-02-10 11:43:23 +07:00
rstubryan 4f9401ed34 refactor(FE): Refactor export logic for PurchasesPerSupplier report 2026-02-10 11:36:27 +07:00
rstubryan 80763acc53 refactor(FE): Add utility for PDF badge styles and integrate into
reports
2026-02-10 11:27:43 +07:00
Rivaldi A N S 5fb065de3e Merge branch 'feat/reusable-pdf-component' into 'development'
[FEAT/FE] Reusable PDF Component and Hotfix Penjualan

See merge request mbugroup/lti-web-client!320
2026-02-10 02:09:59 +00:00
rstubryan d6b9161500 Merge branch 'development' of gitlab.com:mbugroup/lti-web-client into feat/reusable-pdf-component 2026-02-10 09:01:15 +07:00
rstubryan bcc2070ed2 refactor(FE): Refactor DebtSupplierExportPDF to use reusable PDF
components
2026-02-09 21:50:35 +07:00
rstubryan e4e6e563c9 refactor(FE): Remove unused helper function formatTitleCaseGeneral 2026-02-09 17:07:00 +07:00
rstubryan efec9b6265 feat(FE): Add formatTitleCaseGeneral helper and update usage 2026-02-09 17:05:35 +07:00
rstubryan 4cf2f77265 fix(FE): Fix marketing type value logic in SalesOrderFormModal 2026-02-09 17:01:28 +07:00
rstubryan c86f0379b5 refactor(FE): Refactor CustomerPaymentExportPDF to use reusable PDF
components
2026-02-09 16:01:12 +07:00
rstubryan 606380460e feat(FE): Add PDF helper components for badges and typography 2026-02-09 15:58:28 +07:00
Rivaldi A N S a3bcabe5c2 Merge branch 'fix/remove-fcr-related' into 'development'
[FIX/FE] Remove FCR Related

See merge request mbugroup/lti-web-client!319
2026-02-09 08:58:26 +00:00
rstubryan 89ffad398f refactor(FE): Remove FCR-related links and permissions 2026-02-09 14:32:27 +07:00
rstubryan 35986aab56 refactor(FE): Remove FCR-related components and types 2026-02-09 14:28:02 +07:00
Rivaldi A N S 4717330bc8 Merge branch 'fix/adjustment-penjualan' into 'development'
[FIX/FE] Adjustment Penjualan UI and Data Fetch (2/2)

See merge request mbugroup/lti-web-client!318
2026-02-09 07:16:56 +00:00
rstubryan 291eee3bce refactor(FE): Add week field handling for marketing and sales order
forms
2026-02-09 14:06:43 +07:00
rstubryan e6a572ac17 refactor(FE): Add API call for updating delivery order on approval step
3
2026-02-09 13:31:03 +07:00
rstubryan bd5b614bf8 refactor(FE): Fix modal text logic for add_delivery action 2026-02-09 12:01:13 +07:00
rstubryan ba0753428d refactor(FE): Fix form reset and selection handling in
SalesOrderFormModal
2026-02-09 11:49:08 +07:00
rstubryan 862cf38f92 refactor(FE): Refactor DeliveryOrderProductTable to improve readability 2026-02-09 11:41:08 +07:00
rstubryan 1dc6ffca5c refactor(FE): Handle modal action to set step and selected delivery
product
2026-02-09 11:36:41 +07:00
rstubryan b7fd5d3569 refactor(FE): Add approval modal and handler for marketing approval 2026-02-09 11:25:19 +07:00
rstubryan 911136981a refactor(FE): Add clickable "Belum diisi" text for delivery forms 2026-02-09 11:23:49 +07:00
rstubryan 6cbe14b36e refactor(FE): Refactor SalesOrderProductTable to use renderTableContent
helper
2026-02-09 11:00:29 +07:00
rstubryan 80c79cc14b refactor(FE): Refactor DeliveryOrderProductTable to optimize conditional
rendering
2026-02-09 10:46:32 +07:00
rstubryan cb498b01d9 chore(FE): Refactor tables to remove unused props and imports 2026-02-09 10:26:23 +07:00
Rivaldi A N S cd95b1f8ff Merge branch 'fix/adjustment-penjualan' into 'development'
[FIX/FE] Adjustment Penjualan UI and Data Fetch

See merge request mbugroup/lti-web-client!317
2026-02-09 03:04:46 +00:00
rstubryan 60ace68dae refactor(FE): Remove FCR-related fields and functionality 2026-02-09 08:59:51 +07:00
Adnan Zahir b2f6c6c485 Merge branch 'staging' into 'production'
Staging

See merge request mbugroup/lti-web-client!316
2026-02-07 17:11:45 +07:00
rstubryan a8dce9da46 refactor(FE): Refactor table actions to be part of the "Value" column 2026-02-07 10:04:15 +07:00
rstubryan b85e47f601 refactor(FE): Add top border to table rows in product tables 2026-02-07 10:00:02 +07:00
rstubryan fc4a0a58e2 refactor(FE): Refactor tables to use Card component 2026-02-07 09:58:00 +07:00
rstubryan 039dfd529e refactor(FE): Increase max height of order form modals to 50vh 2026-02-07 09:33:58 +07:00
rstubryan 3b42709577 refactor(FE): Refactor modal action handling in order form modals 2026-02-07 09:32:34 +07:00
rstubryan 3dee5c1828 refactor(FE): Add reference number column to ExpensesTable 2026-02-07 09:29:05 +07:00
rstubryan 5ac958231a refactor(FE): Remove unused invoice download functionality from
PurchaseTable
2026-02-07 09:10:39 +07:00
rstubryan 54a6e7e247 refactor(FE): Refactor RecordingForm to simplify egg product filtering 2026-02-07 09:00:04 +07:00
rstubryan 4e80c1a703 refactor(FE): Fix warehouse name display in DeliveryOrderProductTable 2026-02-06 14:46:13 +07:00
rstubryan 9ee5e95d0b refactor(FE): Remove unused conditional rendering for step 3 2026-02-06 14:33:13 +07:00
rstubryan 3bacc59dc6 refactor(FE): Add condition to hide buttons when step number is 3 2026-02-06 14:32:30 +07:00
rstubryan 4d23929924 refactor(FE): Update approval logic and conditional rendering in
delivery forms
2026-02-06 14:20:52 +07:00
rstubryan c9a5a91970 refactor(FE): Update order number display logic in
DeliveryOrderFormModal
2026-02-06 14:20:10 +07:00
rstubryan 08d1447d11 refactor(FE): Update button label based on approval step number 2026-02-06 13:41:17 +07:00
rstubryan 304be4f432 feat(FE): Add support for displaying DO number in MarketingTable 2026-02-06 13:29:38 +07:00
rstubryan 5e9ce70320 feat(FE): Add "Ditolak" option to statusOptions in MarketingFilter 2026-02-06 13:21:33 +07:00
rstubryan 42088e51a8 refactor(FE): Refactor marketing filter to use unique customer options 2026-02-06 13:13:57 +07:00
rstubryan 9dc8f05534 feat: adjust penjualan calculation and delivery order logic 2026-02-06 10:55:38 +07:00
Adnan Zahir cc86151631 Merge branch 'development' into 'staging'
Development

See merge request mbugroup/lti-web-client!315
2026-02-06 10:39:39 +07:00
Rivaldi A N S e16fa9a167 Merge branch 'fix/project-flock' into 'development'
[FIX/FE] Project Flock

See merge request mbugroup/lti-web-client!314
2026-02-06 02:58:02 +00:00
ValdiANS 869110ad2e Merge branch 'development' into fix/project-flock 2026-02-06 09:51:20 +07:00
ValdiANS d415bbba82 feat: add getApprovalLineHistory method 2026-02-06 09:47:11 +07:00
ValdiANS 1ecca83339 feat: add getApprovalLineHistory method 2026-02-06 09:47:05 +07:00
ValdiANS a6c827bb40 feat: add PROJECT_FLOCK_KANDANGS in APPROVAL_WORKFLOWS 2026-02-06 09:46:51 +07:00
ValdiANS 968d9e1f2a fix: adjust ProjectFlockForm styling and create ProjectFlockFormConfirmationTable component 2026-02-06 09:46:37 +07:00
ValdiANS 7b9ba48204 fix: adjust ProjectFlockDetail styling and content 2026-02-06 09:46:00 +07:00
ValdiANS 6e2e9da1be chore: adjust ProjectFlockClosingForm styling 2026-02-06 09:45:38 +07:00
ValdiANS 980a5674e2 feat: create ProjectFlockConfirmationModal component 2026-02-06 09:45:07 +07:00
ValdiANS f5b9c52e71 chore: set chickin form scrollable 2026-02-06 09:44:56 +07:00
ValdiANS ade8fefe0d feat: auto scroll to AlertErrorList if AlertErrorList is showing 2026-02-06 09:44:33 +07:00
ValdiANS 6b54b49443 chore: adjust divider styling 2026-02-06 09:44:06 +07:00
ValdiANS 8fb16903f8 feat: add onClick prop and set text type to react node 2026-02-06 09:43:56 +07:00
ValdiANS f0637e2ce9 chore: add title prop 2026-02-06 09:43:34 +07:00
ValdiANS f6cf4a29ad feat: add ref prop to Alert 2026-02-06 09:43:27 +07:00
ValdiANS 66f017549c chore: use modal for project flock layout 2026-02-06 09:43:15 +07:00
ValdiANS db7219e261 chore: add shadow-bg class name 2026-02-06 09:42:45 +07:00
Rivaldi A N S ac6c77bb92 Merge branch 'dev/hotfix/restu' into 'development'
[HOTFIX/FE] Adjustment Param State on Customer Payment Tab

See merge request mbugroup/lti-web-client!313
2026-02-05 08:54:05 +00:00
rstubryan d5eeadc9a7 refactor(FE): Remove handleBlurField call on week change 2026-02-05 15:47:05 +07:00
rstubryan 70a9fa15ec refactor(FE): Switch week input to SelectInputRadio 2026-02-05 15:43:03 +07:00
rstubryan 4fd4374e64 refactor(FE): Validate date range and show toast on error 2026-02-05 15:04:12 +07:00
rstubryan b4353cf834 refactor(FE): Make filter type nullable and use applied filters 2026-02-05 14:55:27 +07:00
rstubryan eb95afe9a0 refactor(FE): Separate applied and modal filter state 2026-02-05 14:39:33 +07:00
rstubryan 92886fe5e2 refactor(FE): Consolidate date filters into trans_date 2026-02-05 14:28:14 +07:00
rstubryan fb1b310d1d refactor(FE): Replace SelectInput with SelectInputRadio 2026-02-05 14:13:13 +07:00
rstubryan 3b221795ba refactor(FE): Add filter_by option to customer payment reports 2026-02-05 14:08:05 +07:00
rstubryan d41600d8e2 refactor(FE): Replace week SelectInputRadio with NumberInput 2026-02-05 13:48:07 +07:00
Rivaldi A N S 856674de75 Merge branch 'dev/hotfix/restu' into 'development'
[HOTFIX/FE] Fix Meta Page on Penjualan Table Fetching

See merge request mbugroup/lti-web-client!312
2026-02-05 06:00:35 +00:00
rstubryan 1af2b72bea refactor(FE): Prevent badge text wrapping 2026-02-05 12:06:04 +07:00
rstubryan e66f30e703 refactor(FE): Use API metadata for table pagination 2026-02-05 12:04:08 +07:00
Rivaldi A N S ca32af592f Merge branch 'fix/adjustment-param-state-keuangan' into 'development'
[FIX/FE] Adjustment Param State on Fetch Keuangan

See merge request mbugroup/lti-web-client!311
2026-02-05 04:23:21 +00:00
rstubryan 372b439ff0 refactor(FE): Validate date range and show persistent toast 2026-02-05 11:13:53 +07:00
rstubryan 4aa9d54b1e refactor(FE): Remove unused search params and yup import 2026-02-05 11:02:20 +07:00
rstubryan b45c7c8ea6 refactor(FE): Use Formik for finance table filters 2026-02-05 10:58:59 +07:00
rstubryan c164977bb9 refactor(FE): Keep select menus open for multi-selects 2026-02-05 10:50:56 +07:00
rstubryan 3153423f14 refactor(FE): Replace size-full with w-full in FinanceTable 2026-02-05 10:48:26 +07:00
rstubryan ac3fbedccd refactor(FE): Rename filter keys to plural forms 2026-02-05 10:46:37 +07:00
Adnan Zahir 755f3fa0bb Merge branch 'development' into 'staging'
Development

See merge request mbugroup/lti-web-client!310
2026-02-05 10:32:18 +07:00
Rivaldi A N S 9004de06fa Merge branch 'fix/marketing' into 'development'
[FIX/FE] Fixing UX Sales Order & Delivery Order

See merge request mbugroup/lti-web-client!309
2026-02-05 03:06:19 +00:00
randy-ar 4d7bd5213e fix(FE): fixing delivery order ui 2026-02-05 09:58:14 +07:00
randy-ar 2f1c4e3c87 fix(FE): fixing issue form not reset after success submit 2026-02-05 06:05:01 +07:00
randy-ar 43dcbf73ee feat(FE): adding 4 input scenario marketing type 2026-02-05 05:38:02 +07:00
randy-ar cb22fd1037 feat(FE): calculation penjualan telur + peti 2026-02-05 04:41:46 +07:00
randy-ar dfd86a04e0 feat(FE): scenario egg sale with type conversion qty 2026-02-05 02:39:10 +07:00
randy-ar 09cd6395e6 fix(FE): skeleton for input skenario sales order 2026-02-04 15:38:03 +07:00
Rivaldi A N S a8c9b697e3 Merge branch 'fix/purchase-adjustment-data' into 'development'
[FIX/FE] Refactor Purchase Adjustment Data Based on Latest BE API

See merge request mbugroup/lti-web-client!308
2026-02-04 07:24:16 +00:00
ValdiANS c019162390 Merge branch 'development' into fix/project-flock 2026-02-04 10:36:25 +07:00
ValdiANS 1ee92f1064 Merge branch 'development' into fix/project-flock 2026-02-03 12:07:13 +07:00
ValdiANS dc5bd6b329 chore: adjust ProjectFlockKandangTable styling 2026-02-03 09:40:34 +07:00
ValdiANS 68f3c95b81 chore: adjust ProjectFlockForm styling 2026-02-03 09:40:16 +07:00
ValdiANS d826746f29 chore: adjust DrawerHeader styling 2026-02-03 09:39:58 +07:00
ValdiANS 39b18f7efc refactor: use Modal instead of Drawer 2026-02-03 09:17:17 +07:00
ValdiANS c19a7cba68 chore: lint 2026-02-02 11:15:38 +07:00
ValdiANS ec7427b948 Merge branch 'development' into fix/project-flock 2026-02-02 11:14:54 +07:00
ValdiANS 448fb51c81 Merge branch 'development' into fix/project-flock 2026-02-02 08:50:53 +07:00
Adnan Zahir ce1114d724 Merge branch 'staging' into 'production'
Staging

See merge request mbugroup/lti-web-client!295
2026-01-31 10:39:33 +07:00
Adnan Zahir 128b765045 Merge branch 'development' into 'staging'
Development

See merge request mbugroup/lti-web-client!294
2026-01-30 17:05:12 +07:00
Adnan Zahir 92c07e7841 Merge branch 'staging' into 'production'
Staging

See merge request mbugroup/lti-web-client!292
2026-01-30 16:13:35 +07:00
Adnan Zahir 1aba297920 Merge branch 'development' into 'staging'
Development

See merge request mbugroup/lti-web-client!291
2026-01-30 15:54:42 +07:00
Adnan Zahir 2aef6522bb Merge branch 'development' into 'staging'
Development

See merge request mbugroup/lti-web-client!286
2026-01-30 13:32:04 +07:00
Adnan Zahir 3bab96c325 Merge branch 'development' into 'staging'
Development

See merge request mbugroup/lti-web-client!283
2026-01-30 11:40:25 +07:00
ValdiANS 4e801bf744 Merge branch 'development' into fix/project-flock 2026-01-30 10:10:01 +07:00
ValdiANS c5269c4fc5 fix: adjust ProjectFlockTable UI 2026-01-29 16:17:27 +07:00
ValdiANS 4e00ded843 chore: add new step in PROJECT_FLOCKS approval workflows 2026-01-29 15:56:31 +07:00
Adnan Zahir 847772616e Merge branch 'staging' into 'production'
Staging

See merge request mbugroup/lti-web-client!274
2026-01-28 13:34:05 +07:00
Adnan Zahir 344140e973 Merge branch 'development' into 'staging'
Development

See merge request mbugroup/lti-web-client!271
2026-01-28 13:26:27 +07:00
Adnan Zahir 3ce1299091 Merge branch 'development' into 'staging'
Development

See merge request mbugroup/lti-web-client!268
2026-01-28 10:07:39 +07:00
Adnan Zahir aea35d4b9f Merge branch 'staging' into 'production'
Staging

See merge request mbugroup/lti-web-client!261
2026-01-27 10:36:16 +07:00
Adnan Zahir 5b134148a5 Merge branch 'development' into 'staging'
Development

See merge request mbugroup/lti-web-client!258
2026-01-27 09:13:31 +07:00
Adnan Zahir 32f4cf411f Merge branch 'staging' into 'production'
Staging

See merge request mbugroup/lti-web-client!254
2026-01-24 14:28:37 +07:00
Adnan Zahir 04d01970aa Merge branch 'development' into 'staging'
Development

See merge request mbugroup/lti-web-client!253
2026-01-24 14:19:24 +07:00
Adnan Zahir 84cbbaf238 Merge branch 'staging' into 'production'
Staging: Transfer To Laying Rework

See merge request mbugroup/lti-web-client!249
2026-01-24 13:06:33 +07:00
Adnan Zahir 9176373072 Merge branch 'development' into 'staging'
Development: Transfer To Laying Rework

See merge request mbugroup/lti-web-client!248
2026-01-24 12:59:23 +07:00
Adnan Zahir 5c50e4a0c1 Merge branch 'staging' into 'production'
Staging

See merge request mbugroup/lti-web-client!244
2026-01-24 11:25:29 +07:00
Adnan Zahir 7e64ec0f79 Merge branch 'development' into 'staging'
Development

See merge request mbugroup/lti-web-client!243
2026-01-24 11:16:16 +07:00
Adnan Zahir e2be39af18 Merge branch 'staging' into 'production'
refactor(FE): Use local state for record date and disable

See merge request mbugroup/lti-web-client!237
2026-01-22 16:20:17 +07:00
Adnan Zahir 9322d6298c Merge branch 'development' into 'staging'
Development

See merge request mbugroup/lti-web-client!236
2026-01-22 16:14:12 +07:00
Adnan Zahir e9cd84e89e Merge branch 'staging' into 'production'
Staging

See merge request mbugroup/lti-web-client!229
2026-01-21 15:15:04 +07:00
Adnan Zahir 89cfd31155 Merge branch 'development' into 'staging'
fix(FE): change nominal to absolute value, change form state initial balance,...

See merge request mbugroup/lti-web-client!228
2026-01-21 15:08:55 +07:00
Adnan Zahir ec5962bccc Merge branch 'staging' into 'production'
Staging

See merge request mbugroup/lti-web-client!214
2026-01-20 11:52:20 +07:00
Adnan Zahir 0eb4fa99a7 Merge branch 'development' into 'staging'
Development

See merge request mbugroup/lti-web-client!215
2026-01-20 11:46:20 +07:00
Adnan Zahir 2ef8b2dc9f Merge branch 'development' into 'staging'
Merge branch 'dev/hotfix/restu' into 'staging'

See merge request mbugroup/lti-web-client!203
2026-01-17 14:36:51 +07:00
Adnan Zahir aed1a1ed01 Merge branch 'development' into 'staging'
Hotfixes flock

See merge request mbugroup/lti-web-client!201
2026-01-17 11:29:55 +07:00
Adnan Zahir 2c9c2660c0 Merge branch 'development' into 'staging'
fix(FE): fix limit fetch data kandang

See merge request mbugroup/lti-web-client!198
2026-01-17 10:41:25 +07:00
Adnan Zahir b840f42ae0 Merge branch 'development' into 'staging'
refactor(FE): Improve vehicle number validation message and set

See merge request mbugroup/lti-web-client!196
2026-01-17 09:05:42 +07:00
Adnan Zahir 6bc86af32f Merge branch 'development' into 'staging'
Development

See merge request mbugroup/lti-web-client!189
2026-01-15 16:21:40 +07:00
kris 1603ae62e0 Merge branch 'development' into 'staging'
Development

See merge request mbugroup/lti-web-client!182
2026-01-15 06:55:35 +00:00
kris 8fd442621a Update .gitlab-ci.yml file 2026-01-14 02:52:03 +00:00
kris 35471fc597 Update .gitlab-ci.yml file 2026-01-14 02:29:31 +00:00
M1 AIR bd4242c4fd chore: fix conflict gitlab yml 2026-01-13 15:38:14 +07:00
M1 AIR 56bde974ad chore: gitlab ci yml 2026 01 13 2026-01-13 15:36:56 +07:00
M1 AIR 38258e4311 Merge remote-tracking branch 'origin/development' into staging 2026-01-13 15:27:31 +07:00
kris 149e525ff4 Update .gitlab-ci.yml file 2026-01-10 02:15:18 +00:00
M1 AIR 8fb761f02c Merge remote-tracking branch 'origin/development' into staging 2026-01-09 15:53:47 +07:00
M1 AIR 3bc5a5b75e delete .gitlab 2026-01-09 13:16:42 +07:00
M1 AIR 79112e0da8 Penyesuaian flow repo 2026-01-09 10:52:56 +07:00
M1 AIR bf9eb91ea2 Merge remote-tracking branch 'origin/development' into staging 2026-01-06 19:03:21 +07:00
kris e8c8ffadfe Update .gitlab-ci.yml file 2026-01-03 11:01:19 +00:00
M1 AIR 2ae1c5b382 Merge development into staging (keep staging CI config) 2026-01-03 16:43:49 +07:00
kris 961f81411b Merge branch 'development' into 'staging'
Development

See merge request mbugroup/lti-web-client!38
2025-12-18 10:30:13 +00:00
Mitra Berlian Unggas de439275e0 Merge branch 'development' into 'staging'
refactor(FE-114): streamline cost field validation messages and enhance layout...

See merge request mbugroup/lti-web-client!37
2025-10-28 08:44:08 +00:00
365 changed files with 30068 additions and 21437 deletions
+30 -2
View File
@@ -15,7 +15,7 @@ default:
# ==========================================================
.build_template: &build_template
stage: build
image: node:20-alpine
image: public.ecr.aws/docker/library/node:20-alpine
cache:
key: npm-cache
paths:
@@ -56,7 +56,7 @@ default:
.deploy_template: &deploy_template
stage: deploy
image:
name: amazon/aws-cli:latest
name: public.ecr.aws/aws-cli/aws-cli:latest
entrypoint: ['/bin/sh', '-c']
script:
- set -e
@@ -183,3 +183,31 @@ deploy:staging:
environment:
name: staging
url: https://stg-lti-erp.mbugroup.id
# ==========================================================
# ====== STAGING (Branch production) ======
# ==========================================================
build:production:
<<: *build_template
rules:
- if: '$CI_COMMIT_BRANCH == "production"'
environment:
name: staging
variables:
NEXT_PUBLIC_LTI_URL: 'https://lti-erp.mbugroup.id'
NEXT_PUBLIC_SSO_LOGIN_URL: 'https://auth-erp.mbugroup.id'
NEXT_PUBLIC_API_BASE_URL: 'https://api-lti.mbugroup.id/api'
NEXT_PUBLIC_CLIENT_ID: 'Lumbung-Telur-Indonesia'
deploy:production:
<<: *deploy_template
needs: ['build:production']
rules:
- if: '$CI_COMMIT_BRANCH == "production"'
variables:
S3_BUCKET: 'production-lti-erp.mbugroup.id'
AWS_REGION: 'ap-southeast-3'
CLOUDFRONT_DISTRIBUTION_ID: 'E1SSLXKYYITASJ'
environment:
name: staging
url: https://lti-erp.mbugroup.id
+1 -1
View File
@@ -1,4 +1,4 @@
FROM node:20-alpine
FROM public.ecr.aws/docker/library/node:20-alpine
RUN apk add --no-cache git bash build-base curl
+2 -1
View File
@@ -8,7 +8,8 @@
"start": "next start",
"lint": "eslint",
"prepare": "husky",
"format": "prettier --write ."
"format": "prettier --write .",
"pre-commit": "npm run format && npm run lint && npx tsc --noEmit && npm run build"
},
"dependencies": {
"@react-pdf/renderer": "^4.3.1",
+2 -41
View File
@@ -3,11 +3,10 @@
import { useRouter, useSearchParams } from 'next/navigation';
import useSWR from 'swr';
import ClosingDetail from '@/components/pages/closing/ClosingDetail';
import ClosingDetail from '@/components/pages/closing/ClosingDetailTabs';
import { ClosingApi } from '@/services/api/closing';
import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
import { FlockApi } from '@/services/api/master-data';
import { ProjectFlockApi } from '@/services/api/production/project-flock';
import { ProjectFlockKandangApi } from '@/services/api/production';
@@ -34,33 +33,6 @@ const ClosingDetailPage = () => {
() => ProjectFlockKandangApi.getSingle(Number(kandangId))
);
const { data: salesData, isLoading: isLoadingSales } = useSWR(
kandangId
? `sales-${closingId}-${kandangId}`
: closingId
? `sales-${closingId}`
: null,
() =>
kandangId
? ClosingApi.getPenjualanByKandang(Number(closingId), Number(kandangId))
: ClosingApi.getPenjualan(Number(closingId))
);
const { data: hppEkspedisiData, isLoading: isLoadingHppEkspedisi } = useSWR(
kandangId
? `hpp-ekspedisi-${closingId}-${kandangId}`
: closingId
? `hpp-ekspedisi-${closingId}`
: null,
() =>
kandangId
? ClosingApi.getHppEkspedisiByKandang(
Number(closingId),
Number(kandangId)
)
: ClosingApi.getHppEkspedisi(Number(closingId))
);
if (!closingId) {
router.back();
@@ -76,12 +48,7 @@ const ClosingDetailPage = () => {
return;
}
const isLoading =
isLoadingClosing ||
isLoadingSales ||
isLoadingHppEkspedisi ||
isLoadingProject ||
isLoadingKandang;
const isLoading = isLoadingClosing || isLoadingProject || isLoadingKandang;
return (
<div className='w-full p-4 flex flex-row justify-center'>
@@ -91,12 +58,6 @@ const ClosingDetailPage = () => {
<ClosingDetail
id={Number(closingId)}
initialValue={closing.data}
salesData={isResponseSuccess(salesData) ? salesData.data : undefined}
hppExpeditionData={
isResponseSuccess(hppEkspedisiData)
? hppEkspedisiData.data
: undefined
}
projectData={
isResponseSuccess(projectData) ? projectData.data : undefined
}
+1 -1
View File
@@ -2,7 +2,7 @@ import ClosingsTable from '@/components/pages/closing/ClosingsTable';
const Closing = () => {
return (
<section className='w-full p-4'>
<section className='w-full p-3'>
<ClosingsTable />
</section>
);
@@ -0,0 +1,11 @@
import { MasterKandangContent } from '@/figma-make/components/pages/master-data/kandang/MasterKandangContent';
const MasterKandangPage = () => {
return (
<section className='w-full'>
<MasterKandangContent />
</section>
);
};
export default MasterKandangPage;
-1
View File
@@ -5,7 +5,6 @@ import useSWR from 'swr';
import { FinanceApi } from '@/services/api/finance';
import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
import FormFinanceAdd from '@/components/pages/finance/add/FormFinanceAdd';
import FormFinanceAddInitialBalance from '@/components/pages/finance/add/initial-balance/FormFinanceAddInitialBalance';
const EditFinanceTransactionPage = () => {
const router = useRouter();
+1 -1
View File
@@ -4,7 +4,7 @@ import FinanceDetail from '@/components/pages/finance/FinanceDetail';
import useSWR from 'swr';
import { useRouter, useSearchParams } from 'next/navigation';
import { FinanceApi } from '@/services/api/finance';
import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
import { isResponseSuccess } from '@/lib/api-helper';
const FinanceDetailPage = () => {
const router = useRouter();
+1 -6
View File
@@ -3,12 +3,7 @@
import FinanceTable from '@/components/pages/finance/FinanceTable';
const Finance = () => {
return (
<section className='size-full p-6'>
<div className='flex flex-row gap-4'></div>
<FinanceTable />
</section>
);
return <FinanceTable />;
};
export default Finance;
+2
View File
@@ -68,6 +68,8 @@
--shadow-button-soft:
0 3px 2px -2px var(--color-base-200), 0 4px 3px -2px var(--color-base-200);
--shadow-bg: 0px -2px 4px 0px #00000014;
}
html {
+1 -1
View File
@@ -2,7 +2,7 @@ import InventoryAdjustmentTable from '@/components/pages/inventory/adjustment/In
const InventoryAdjustment = () => {
return (
<section className='w-full p-4'>
<section className='w-full'>
<InventoryAdjustmentTable />
</section>
);
@@ -1,11 +0,0 @@
import SuspenseHelper from '@/components/helper/SuspenseHelper';
const Layout = ({
children,
}: Readonly<{
children: React.ReactNode;
}>) => {
return <SuspenseHelper>{children}</SuspenseHelper>;
};
export default Layout;
@@ -1,54 +0,0 @@
'use client';
import MarketingForm from '@/components/pages/marketing/form/MarketingForm';
import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
import { MarketingApi } from '@/services/api/marketing/marketing';
import { useRouter, useSearchParams } from 'next/navigation';
import toast from 'react-hot-toast';
import useSWR from 'swr';
const EditMarketingDelivery = () => {
const router = useRouter();
const searchParams = useSearchParams();
const soId = searchParams.get('marketingId');
const {
data: marketing,
isLoading: isLoading,
mutate: refreshMarketing,
} = useSWR(`get-so-${soId}`, () =>
MarketingApi.getSingle(soId ? parseInt(soId) : 0)
);
if (!soId) {
router.back();
return (
<div className='w-full flex flex-row justify-center items-center p-4'>
<span className='loading loading-spinner loading-xl' />
</div>
);
}
if (!isLoading && (!marketing || isResponseError(marketing))) {
router.replace('/404');
return;
}
return (
<div className='w-full p-4'>
{isLoading && <span className='loading loading-spinner loading-xl' />}
{!isLoading && isResponseSuccess(marketing) && (
<MarketingForm
formType='add_deliver'
initialValues={marketing.data}
afterSubmit={() => {
refreshMarketing();
}}
/>
)}
</div>
);
};
export default EditMarketingDelivery;
@@ -1,11 +0,0 @@
import MarketingForm from '@/components/pages/marketing/form/MarketingForm';
const AddSalesOrder = () => {
return (
<div className='size-full p-4'>
<MarketingForm formType='add' />
</div>
);
};
export default AddSalesOrder;
@@ -1,11 +0,0 @@
import SuspenseHelper from '@/components/helper/SuspenseHelper';
const Layout = ({
children,
}: Readonly<{
children: React.ReactNode;
}>) => {
return <SuspenseHelper>{children}</SuspenseHelper>;
};
export default Layout;
@@ -1,62 +0,0 @@
'use client';
import MarketingForm from '@/components/pages/marketing/form/MarketingForm';
import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
import { MarketingApi } from '@/services/api/marketing/marketing';
import { useRouter, useSearchParams } from 'next/navigation';
import toast from 'react-hot-toast';
import useSWR from 'swr';
const EditMarketingDelivery = () => {
const router = useRouter();
const searchParams = useSearchParams();
const soId = searchParams.get('marketingId');
const {
data: marketing,
isLoading: isLoading,
mutate: refreshMarketing,
} = useSWR(`get-so-${soId}`, () =>
MarketingApi.getSingle(soId ? parseInt(soId) : 0)
);
if (!soId) {
router.back();
return (
<div className='w-full flex flex-row justify-center items-center p-4'>
<span className='loading loading-spinner loading-xl' />
</div>
);
}
if (!isLoading && (!marketing || isResponseError(marketing))) {
router.replace('/404');
return;
}
if (
isResponseSuccess(marketing) &&
marketing.data.latest_approval.step_number != 3
) {
toast.error('Data Marketing perlu dilakukan approval terlebih dahulu!');
router.back();
}
return (
<div className='w-full p-4'>
{isLoading && <span className='loading loading-spinner loading-xl' />}
{!isLoading && isResponseSuccess(marketing) && (
<MarketingForm
formType='edit_deliver'
initialValues={marketing.data}
afterSubmit={() => {
refreshMarketing();
}}
/>
)}
</div>
);
};
export default EditMarketingDelivery;
-11
View File
@@ -1,11 +0,0 @@
import SuspenseHelper from '@/components/helper/SuspenseHelper';
const Layout = ({
children,
}: Readonly<{
children: React.ReactNode;
}>) => {
return <SuspenseHelper>{children}</SuspenseHelper>;
};
export default Layout;
-49
View File
@@ -1,49 +0,0 @@
'use client';
import MarketingDetail from '@/components/pages/marketing/detail/MarketingDetail';
import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
import { MarketingApi } from '@/services/api/marketing/marketing';
import { useRouter, useSearchParams } from 'next/navigation';
import useSWR from 'swr';
const DetailMarketing = () => {
const router = useRouter();
const searchParams = useSearchParams();
const soId = searchParams.get('marketingId');
const {
data: marketing,
isLoading: isLoading,
mutate: refreshMarketing,
} = useSWR(soId, (id: number) => MarketingApi.getSingle(id));
if (!soId) {
router.back();
return (
<div className='w-full flex flex-row justify-center items-center p-4'>
<span className='loading loading-spinner loading-xl' />
</div>
);
}
if (!isLoading && (!marketing || isResponseError(marketing))) {
router.replace('/404');
return;
}
return (
<div className='w-full p-4'>
{isLoading && <span className='loading loading-spinner loading-xl' />}
{!isLoading && isResponseSuccess(marketing) && (
<MarketingDetail
initialValues={marketing.data}
refresh={refreshMarketing}
/>
)}
</div>
);
};
export default DetailMarketing;
@@ -1,11 +0,0 @@
import SuspenseHelper from '@/components/helper/SuspenseHelper';
const Layout = ({
children,
}: Readonly<{
children: React.ReactNode;
}>) => {
return <SuspenseHelper>{children}</SuspenseHelper>;
};
export default Layout;
@@ -1,52 +0,0 @@
'use client';
import MarketingForm from '@/components/pages/marketing/form/MarketingForm';
import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
import { MarketingApi } from '@/services/api/marketing/marketing';
import { useRouter, useSearchParams } from 'next/navigation';
import useSWR from 'swr';
const EditSalesOrder = () => {
const router = useRouter();
const searchParams = useSearchParams();
const soId = searchParams.get('marketingId');
const {
data: marketing,
isLoading: isLoading,
mutate: refreshMarketing,
} = useSWR(`get-so-${soId}`, () =>
MarketingApi.getSingle(soId ? parseInt(soId) : 0)
);
if (!soId) {
router.back();
return (
<div className='w-full flex flex-row justify-center items-center p-4'>
<span className='loading loading-spinner loading-xl' />
</div>
);
}
if (!isLoading && (!marketing || isResponseError(marketing))) {
router.replace('/404');
return;
}
return (
<div className='w-full p-4'>
{isLoading && <span className='loading loading-spinner loading-xl' />}
{!isLoading && isResponseSuccess(marketing) && (
<MarketingForm
formType='edit'
initialValues={marketing.data}
afterSubmit={() => {
refreshMarketing();
}}
/>
)}
</div>
);
};
export default EditSalesOrder;
+1 -5
View File
@@ -1,11 +1,7 @@
import AreasTable from '@/components/pages/master-data/area/AreasTable';
const Nonstock = () => {
return (
<section className='w-full p-4'>
<AreasTable />
</section>
);
return <AreasTable />;
};
export default Nonstock;
+1 -5
View File
@@ -1,11 +1,7 @@
import BanksTable from '@/components/pages/master-data/bank/BanksTable';
const Bank = () => {
return (
<section className='w-full p-4'>
<BanksTable />
</section>
);
return <BanksTable />;
};
export default Bank;
+1 -5
View File
@@ -1,11 +1,7 @@
import CustomersTable from '@/components/pages/master-data/customer/CustomersTable';
const Customer = () => {
return (
<section className='w-full p-4'>
<CustomersTable />
</section>
);
return <CustomersTable />;
};
export default Customer;
-11
View File
@@ -1,11 +0,0 @@
import FcrForm from '@/components/pages/master-data/fcr/form/FcrForm';
const AddFcr = () => {
return (
<div className='w-full p-4 flex flex-row justify-center'>
<FcrForm />
</div>
);
};
export default AddFcr;
@@ -1,52 +0,0 @@
'use client';
import { useRouter, useSearchParams } from 'next/navigation';
import useSWR from 'swr';
import FcrForm from '@/components/pages/master-data/fcr/form/FcrForm';
import { FcrApi } from '@/services/api/master-data';
import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
import { BaseApiResponse } from '@/types/api/api-general';
import { FcrWithStandards } from '@/types/api/master-data/fcr';
const FcrEdit = () => {
const router = useRouter();
const searchParams = useSearchParams();
const fcrId = searchParams.get('fcrId');
const { data: fcr, isLoading: isLoadingFcr } = useSWR(
fcrId,
(id: number) =>
FcrApi.getSingle(id) as Promise<
BaseApiResponse<FcrWithStandards> | undefined
>
);
if (!fcrId) {
router.back();
return (
<div className='w-full flex flex-row justify-center items-center p-4'>
<span className='loading loading-spinner loading-xl' />
</div>
);
}
if (!isLoadingFcr && (!fcr || isResponseError(fcr))) {
router.replace('/404');
return;
}
return (
<div className='w-full p-4 flex flex-row justify-center'>
{isLoadingFcr && <span className='loading loading-spinner loading-xl' />}
{!isLoadingFcr && isResponseSuccess(fcr) && (
<FcrForm type='edit' initialValues={fcr.data} />
)}
</div>
);
};
export default FcrEdit;
-11
View File
@@ -1,11 +0,0 @@
import SuspenseHelper from '@/components/helper/SuspenseHelper';
const Layout = ({
children,
}: Readonly<{
children: React.ReactNode;
}>) => {
return <SuspenseHelper>{children}</SuspenseHelper>;
};
export default Layout;
-52
View File
@@ -1,52 +0,0 @@
'use client';
import { useRouter, useSearchParams } from 'next/navigation';
import useSWR from 'swr';
import FcrForm from '@/components/pages/master-data/fcr/form/FcrForm';
import { FcrApi } from '@/services/api/master-data';
import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
import { FcrWithStandards } from '@/types/api/master-data/fcr';
import { BaseApiResponse } from '@/types/api/api-general';
const FcrDetail = () => {
const router = useRouter();
const searchParams = useSearchParams();
const fcrId = searchParams.get('fcrId');
const { data: fcr, isLoading: isLoadingFcr } = useSWR(
fcrId,
(id: number) =>
FcrApi.getSingle(id) as Promise<
BaseApiResponse<FcrWithStandards> | undefined
>
);
if (!fcrId) {
router.back();
return (
<div className='w-full flex flex-row justify-center items-center p-4'>
<span className='loading loading-spinner loading-xl' />
</div>
);
}
if (!isLoadingFcr && (!fcr || isResponseError(fcr))) {
router.replace('/404');
return;
}
return (
<div className='w-full p-4 flex flex-row justify-center'>
{isLoadingFcr && <span className='loading loading-spinner loading-xl' />}
{!isLoadingFcr && isResponseSuccess(fcr) && (
<FcrForm type='detail' initialValues={fcr.data} />
)}
</div>
);
};
export default FcrDetail;
-11
View File
@@ -1,11 +0,0 @@
import FcrsTable from '@/components/pages/master-data/fcr/FcrsTable';
const Fcr = () => {
return (
<section className='w-full p-4'>
<FcrsTable />
</section>
);
};
export default Fcr;
+1 -5
View File
@@ -1,11 +1,7 @@
import FlockTable from '@/components/pages/master-data/flock/FlocksTable';
const Flock = () => {
return (
<section className='w-full p-4'>
<FlockTable />
</section>
);
return <FlockTable />;
};
export default Flock;
+1 -5
View File
@@ -1,11 +1,7 @@
import KandangsTable from '@/components/pages/master-data/kandang/KandangsTable';
const Nonstock = () => {
return (
<section className='w-full p-4'>
<KandangsTable />
</section>
);
return <KandangsTable />;
};
export default Nonstock;
+1 -5
View File
@@ -1,11 +1,7 @@
import LocationsTable from '@/components/pages/master-data/location/LocationsTable';
const Nonstock = () => {
return (
<section className='w-full p-4'>
<LocationsTable />
</section>
);
return <LocationsTable />;
};
export default Nonstock;
+1 -5
View File
@@ -1,11 +1,7 @@
import NonstocksTable from '@/components/pages/master-data/nonstock/NonstocksTable';
const Nonstock = () => {
return (
<section className='w-full p-4'>
<NonstocksTable />
</section>
);
return <NonstocksTable />;
};
export default Nonstock;
@@ -1,11 +1,7 @@
import ProductCategoryTable from '@/components/pages/master-data/product-category/ProductCategoryTable';
const ProductCategory = () => {
return (
<section className='w-full p-4'>
<ProductCategoryTable />
</section>
);
return <ProductCategoryTable />;
};
export default ProductCategory;
+1 -5
View File
@@ -1,11 +1,7 @@
import ProductsTable from '@/components/pages/master-data/product/ProductTable';
const Product = () => {
return (
<section className='w-full p-4'>
<ProductsTable />
</section>
);
return <ProductsTable />;
};
export default Product;
@@ -1,11 +1,7 @@
import ProductionStandardTable from '@/components/pages/master-data/production-standard/ProductionStandardTable';
const ProductionStandardPage = () => {
return (
<div className='w-full'>
<ProductionStandardTable />
</div>
);
return <ProductionStandardTable />;
};
export default ProductionStandardPage;
+1 -5
View File
@@ -1,11 +1,7 @@
import SuppliersTable from '@/components/pages/master-data/supplier/SupplierTable';
const Supplier = () => {
return (
<section className='w-full p-4'>
<SuppliersTable />
</section>
);
return <SuppliersTable />;
};
export default Supplier;
+1 -5
View File
@@ -1,11 +1,7 @@
import UomsTable from '@/components/pages/master-data/uom/UomsTable';
const Nonstock = () => {
return (
<section className='w-full p-4'>
<UomsTable />
</section>
);
return <UomsTable />;
};
export default Nonstock;
+1 -5
View File
@@ -1,11 +1,7 @@
import WarehousesTable from '@/components/pages/master-data/warehouse/WarehousesTable';
const Warehouse = () => {
return (
<section className='w-full p-4'>
<WarehousesTable />
</section>
);
return <WarehousesTable />;
};
export default Warehouse;
+1 -2
View File
@@ -3,10 +3,9 @@
import { useEffect } from 'react';
import { usePathname, useRouter } from 'next/navigation';
import { useAuth } from '@/services/hooks/useAuth';
import { redirectToSSO } from '@/lib/auth-helper';
export default function Home() {
const { user, isLoadingUser } = useAuth();
const { isLoadingUser } = useAuth();
const router = useRouter();
const pathname = usePathname();
@@ -1,8 +1,8 @@
'use client';
import ProjectFlockForm from '@/components/pages/production/project-flock/form/ProjectFlockForm';
import React, { useImperativeHandle } from 'react';
import toast from 'react-hot-toast';
import React from 'react';
// import React, { useImperativeHandle } from 'react';
const AddProjectFlock = () => {
// useImperativeHandle(ref, () => ({
@@ -12,11 +12,10 @@ const ProjectFlockEdit = () => {
const projectFlockId = searchParams.get('projectFlockId');
const {
data: projectFlock,
isLoading: isLoadingProjectFlock,
mutate: refreshProjectFlocks,
} = useSWR(projectFlockId, (id: number) => ProjectFlockApi.getSingle(id));
const { data: projectFlock, isLoading: isLoadingProjectFlock } = useSWR(
projectFlockId,
(id: number) => ProjectFlockApi.getSingle(id)
);
if (!projectFlockId) {
router.back();
@@ -1,7 +1,6 @@
'use client';
import ProjectFlockDetail from '@/components/pages/production/project-flock/detail/ProjectFlockDetail';
import ProjectFlockForm from '@/components/pages/production/project-flock/form/ProjectFlockForm';
import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
import { ProjectFlockApi } from '@/services/api/production/project-flock';
import { useRouter, useSearchParams } from 'next/navigation';
@@ -13,11 +12,10 @@ const ProjectFlockDetailPage = () => {
const projectFlockId = searchParams.get('projectFlockId');
const {
data: projectFlock,
isLoading: isLoadingProjectFlock,
mutate: refreshProjectFlock,
} = useSWR(projectFlockId, (id: number) => ProjectFlockApi.getSingle(id));
const { data: projectFlock, isLoading: isLoadingProjectFlock } = useSWR(
projectFlockId,
(id: number) => ProjectFlockApi.getSingle(id)
);
if (!projectFlockId) {
router.back();
+25 -13
View File
@@ -1,10 +1,10 @@
'use client';
import { usePathname, useRouter } from 'next/navigation';
import Drawer from '@/components/Drawer';
import React, { ReactNode } from 'react';
import React, { ReactNode, useEffect } from 'react';
import ProjectFlockTable from '@/components/pages/production/project-flock/ProjectFlockTable';
import { useUiStore } from '@/stores/ui/ui.store';
import Modal, { useModal } from '@/components/Modal';
export default function ProjectFlockLayout({
children,
@@ -23,9 +23,12 @@ export default function ProjectFlockLayout({
const isOpen = isAdd || isEdit || isDetail || isChickin || isClosing;
const formModal = useModal();
const handleBackdropClick = () => {
const unsub = useUiStore.getState().subscribeIsValid((isValid) => {
if (isValid) {
formModal.closeModal();
unsub(); // berhenti listen
router.push('/production/project-flock');
}
@@ -34,6 +37,14 @@ export default function ProjectFlockLayout({
toggleValidate();
};
useEffect(() => {
if (isOpen && !formModal.open) {
formModal.openModal();
} else {
formModal.closeModal();
}
}, [isOpen]);
return (
<>
{/* List page always rendered */}
@@ -43,18 +54,19 @@ export default function ProjectFlockLayout({
/>
</div>
{/* Render Drawer only on /add */}
<Drawer
open={isOpen}
setOpen={(v) => {
if (!v) router.push('/production/project-flock');
}}
closeOnBackdropClick={isDetail ? true : false}
{/* Render Modal only on /add */}
<Modal
ref={formModal.ref}
position='end'
onBackdropClick={handleBackdropClick}
variant='right'
zIndex='99999'
sidebarContent={isOpen && <div className=''>{children}</div>}
/>
className={{
modalBox: 'w-full sm:w-fit p-3 rounded-xl bg-transparent shadow-none',
}}
>
<div className='w-full sm:w-[446px] h-full flex flex-col sm:flex-row items-stretch bg-base-100 rounded-xl overflow-hidden'>
{isOpen && children}
</div>
</Modal>
</>
);
}
+1 -1
View File
@@ -2,7 +2,7 @@ import RecordingTable from '@/components/pages/production/recording/RecordingTab
const Recording = () => {
return (
<section className='w-full p-4 sm:p-0'>
<section className='w-full'>
<RecordingTable />
</section>
);
+2 -6
View File
@@ -1,13 +1,9 @@
'use client';
import ReportExpenseTable from '@/components/pages/report/expense/ReportExpenseTable';
import ReportExpenseTabs from '@/components/pages/report/expense/ReportExpenseTabs';
const ReportExpense = () => {
return (
<div className='w-full p-4'>
<ReportExpenseTable />
</div>
);
return <ReportExpenseTabs />;
};
export default ReportExpense;
+2 -6
View File
@@ -1,11 +1,7 @@
import MarketingReportContent from '@/components/pages/report/MarketingReportContent';
import MarketingReportContent from '@/components/pages/report/marketing/MarketingTabs';
const MarketingReportPage = () => {
return (
<section className='w-full p-4'>
<MarketingReportContent />
</section>
);
return <MarketingReportContent />;
};
export default MarketingReportPage;
+3 -3
View File
@@ -1,9 +1,9 @@
import ProductionResultContent from '@/components/pages/report/production-result/ProductionResultContent';
import ProductionResultTabs from '@/components/pages/report/production-result/ProductionResultTabs';
const ProductionResultReportPage = () => {
return (
<section className='w-full max-w-7xl pb-16'>
<ProductionResultContent />
<section className='w-full max-w-full'>
<ProductionResultTabs />
</section>
);
};
+8 -3
View File
@@ -1,15 +1,16 @@
import { ReactNode } from 'react';
import { ReactNode, Ref } from 'react';
import { cn } from '@/lib/helper';
interface AlertProps {
ref?: Ref<HTMLDivElement> | undefined;
variant?: 'outline' | 'dash' | 'soft';
color?: 'info' | 'success' | 'warning' | 'error';
children?: ReactNode;
className?: string;
}
const Alert = ({ children, variant, color, className }: AlertProps) => {
const Alert = ({ children, ref, variant, color, className }: AlertProps) => {
const alertBaseClassName = cn('alert', {
'alert-soft': variant === 'soft',
'alert-outline': variant === 'outline',
@@ -21,7 +22,11 @@ const Alert = ({ children, variant, color, className }: AlertProps) => {
'alert-error': color === 'error',
});
return <div className={cn(alertBaseClassName, className)}>{children}</div>;
return (
<div ref={ref} className={cn(alertBaseClassName, className)}>
{children}
</div>
);
};
export default Alert;
+3 -1
View File
@@ -108,7 +108,9 @@ const Drawer = ({
if (closeOnBackdropClick) {
setOpen(false);
}
onBackdropClick && onBackdropClick();
if (onBackdropClick) {
onBackdropClick();
}
};
return (
-2
View File
@@ -1,6 +1,5 @@
'use client';
import { useCallback } from 'react';
import { usePathname } from 'next/navigation';
import Image from 'next/image';
@@ -13,7 +12,6 @@ import PermissionNotFound from '@/components/helper/PermissionNotFound';
import { useUiStore } from '@/stores/ui/ui.store';
import { MAIN_DRAWER_LINKS } from '@/config/constant';
import { isPathActive } from '@/lib/helper';
import { ROUTE_PERMISSIONS } from '@/config/route-permission';
import { useAuth } from '@/services/hooks/useAuth';
+5 -1
View File
@@ -31,7 +31,11 @@ export const useModal = (isNestingModal = false) => {
}, []);
const toggle = useCallback(() => {
open ? closeModal() : openModal();
if (open) {
closeModal();
} else {
openModal();
}
}, [open, closeModal, openModal]);
useEffect(() => {
+5 -1
View File
@@ -26,13 +26,17 @@ const Navbar = ({ toggleSidebar }: NavbarProps) => {
const logoutClickHandler = async () => {
const logoutRes = await AuthApi.logout();
if (isResponseError(logoutRes)) {
toast.error('Gagal logout! Coba lagi!');
return;
}
setUser(undefined);
const redirect = (logoutRes as { redirect?: string })?.redirect;
if (redirect) {
window.location.href = redirect;
return;
}
router.replace(process.env.NEXT_PUBLIC_SSO_LOGIN_URL as string);
};
+16 -6
View File
@@ -85,8 +85,8 @@ const DUMMY_SKELETON_DATA = Array.from({ length: 10 }, (_, index) => ({
}));
const emptyContentDefaultValue = (
<div className='w-full p-5 text-center'>
<span className='text-lg opacity-50'>
<div className='w-full text-center py-4'>
<span className='text-sm opacity-50'>
Tidak ada data yang dapat ditampilkan...
</span>
</div>
@@ -452,6 +452,20 @@ const Table = <TData extends object>({
</Fragment>
);
})}
{(data.length === 0 || table.getRowModel().rows.length === 0) &&
!isLoading && (
<tr>
<td
colSpan={
table.getAllLeafColumns().length + (withCheckbox ? 1 : 0)
}
className='p-0'
>
{emptyContent}
</td>
</tr>
)}
</tbody>
<tfoot
className={cn(
@@ -489,10 +503,6 @@ const Table = <TData extends object>({
</table>
</div>
{(data.length === 0 || table.getRowModel().rows.length === 0) &&
!isLoading &&
emptyContent}
{data.length > 0 &&
table.getRowModel().rows.length > 0 &&
!isLoading &&
+1 -1
View File
@@ -1,4 +1,4 @@
import { HTMLAttributes, ReactNode, useEffect, useState } from 'react';
import { HTMLAttributes, ReactNode, useState } from 'react';
import { cn } from '@/lib/helper';
export interface TabItem {
+3 -1
View File
@@ -9,6 +9,7 @@ import Button from '@/components/Button';
import { cn, formatDate } from '@/lib/helper';
interface ApprovalStepsV2Props {
title?: string;
approvals?: BaseApproval[];
steps: {
step_number: number;
@@ -23,6 +24,7 @@ interface ApprovalStepsV2Props {
}
const ApprovalStepsV2 = ({
title = 'Progress Details',
approvals,
steps,
maxVisibleSteps = 2,
@@ -99,7 +101,7 @@ const ApprovalStepsV2 = ({
)}
>
<h4 className='text-base font-medium text-base-content/50 font-roboto'>
Progress Details
{title}
</h4>
<div
+41 -7
View File
@@ -3,15 +3,51 @@ import { getFilledFormikValuesCount } from '@/lib/formik-helper';
import { cn } from '@/lib/helper';
import { Icon } from '@iconify/react';
import { FormikValues } from 'formik';
import { useMemo } from 'react';
export type ButtonFilterProps = ButtonProps & {
values: FormikValues;
onClick: () => void;
excludeFields?: string[];
fieldGroups?: string[][];
};
// 'bg-gradient-to-t from-blue-50 to-blue-100 border-blue-500 text-blue-600 hover:from-blue-100 hover:to-blue-200
const ButtonFilter = ({ values, onClick, ...props }: ButtonFilterProps) => {
const ButtonFilter = ({
values,
onClick,
excludeFields = [],
fieldGroups = [],
...props
}: ButtonFilterProps) => {
const activeCount = useMemo(() => {
const filteredValues: FormikValues = {};
Object.keys(values).forEach((key) => {
if (!excludeFields.includes(key)) {
filteredValues[key] = values[key];
}
});
let count = getFilledFormikValuesCount(filteredValues);
fieldGroups.forEach((group) => {
const groupFields = group.filter(
(field) => !excludeFields.includes(field)
);
const filledGroupFields = groupFields.filter(
(field) => filteredValues[field]
);
if (
filledGroupFields.length === groupFields.length &&
groupFields.length > 1
) {
count -= groupFields.length - 1;
}
});
return count;
}, [values, excludeFields, fieldGroups]);
return (
<Button
{...props}
@@ -21,7 +57,7 @@ const ButtonFilter = ({ values, onClick, ...props }: ButtonFilterProps) => {
className={cn(
'rounded-lg max-h-10 font-semibold text-sm gap-1.5',
'text-sm text-base-content/50 border border-base-content/10 shadow-button-soft',
getFilledFormikValuesCount(values) > 0
activeCount > 0
? 'border-primary-gradient text-primary rounded-lg!'
: 'rounded-lg',
props.className
@@ -31,14 +67,12 @@ const ButtonFilter = ({ values, onClick, ...props }: ButtonFilterProps) => {
icon='heroicons:funnel'
width={20}
height={20}
className={
getFilledFormikValuesCount(values) > 0 ? 'text-blue-600' : ''
}
className={activeCount > 0 ? 'text-blue-600' : ''}
/>
Filter
{getFilledFormikValuesCount(values) > 0 && (
{activeCount > 0 && (
<span className='w-5 h-5 text-white bg-[#FF3535] rounded-lg border border-base-300 flex items-center justify-center text-xs'>
{getFilledFormikValuesCount(values)}
{activeCount}
</span>
)}
</Button>
+7 -1
View File
@@ -1,24 +1,30 @@
import { ReactNode } from 'react';
import Badge from '@/components/Badge';
import { cn } from '@/lib/helper';
import { Color } from '@/types/theme';
interface StatusBadgeProps {
color: Color;
text: string;
text: ReactNode;
className?: {
badge?: string;
status?: string;
};
onClick?: () => void;
}
const StatusBadge = ({
color = 'neutral',
text,
className,
onClick,
}: StatusBadgeProps) => {
return (
<Badge
variant='soft'
onClick={onClick}
className={{
badge: cn(
'px-2 py-1 w-full flex flex-row justify-start gap-1 rounded-lg border border-base-content/10 text-xs font-medium text-base-content',
@@ -27,7 +27,7 @@ export interface DrawerHeaderProps {
const DrawerHeader = ({
leftIcon = 'mdi:close',
leftIconSize = 24,
leftIconSize = 20,
leftIconHref,
leftIconOnClick,
leftIconClassName,
@@ -43,7 +43,7 @@ const DrawerHeader = ({
icon={leftIcon}
width={leftIconSize}
height={leftIconSize}
className={cn('cursor-pointer', leftIconClassName)}
className={cn('cursor-pointer text-base-content ', leftIconClassName)}
/>
);
@@ -73,7 +73,7 @@ const DrawerHeader = ({
return (
<div
className={cn(
'flex flex-row justify-between items-center px-4 pt-4 pb-4 border-b border-base-content/10',
'flex flex-row justify-between items-center p-4 border-b border-base-content/10',
className
)}
>
@@ -82,7 +82,7 @@ const DrawerHeader = ({
{renderLeftIcon()}
{showDivider && subtitle && (
<div className='divider divider-horizontal p-0 m-0'></div>
<div className='w-px h-full border-none bg-base-content/10' />
)}
{subtitle && (
+16 -1
View File
@@ -1,8 +1,10 @@
'use client';
import Alert from '@/components/Alert';
import Button from '@/components/Button';
import { cn } from '@/lib/helper';
import { Icon } from '@iconify/react';
import { useState } from 'react';
import { useEffect, useRef } from 'react';
/**
* Alert Unique Error List
@@ -29,10 +31,22 @@ const AlertErrorList = ({
onClose: () => void;
title?: string;
}) => {
const alertRef = useRef<HTMLDivElement | null>(null);
useEffect(() => {
if (formErrorList.length > 0) {
alertRef.current?.scrollIntoView({
behavior: 'smooth',
block: 'start',
});
}
}, [formErrorList.length]);
if (formErrorList.length === 0) return null;
return (
<Alert
ref={alertRef}
color='error'
className={cn(
'w-full flex flex-col gap-2 px-3 rounded-lg',
@@ -57,6 +71,7 @@ const AlertErrorList = ({
</span>
</div>
<Button
type='button'
onClick={onClose}
variant='link'
className={cn('ml-auto p-0 w-fit text-white', className?.button)}
@@ -0,0 +1,27 @@
import { Text, View, StyleSheet } from '@react-pdf/renderer';
import type { Style } from '@react-pdf/types';
type PdfParamBadgeProps = {
children: React.ReactNode;
style?: Style;
};
const styles = StyleSheet.create({
parameterBadge: {
backgroundColor: '#F5F5F5',
color: '#333333',
padding: 4,
borderRadius: 4,
fontSize: 8,
marginRight: 8,
marginBottom: 4,
},
});
export const PdfParamBadge = ({ children, style }: PdfParamBadgeProps) => {
return (
<View style={[styles.parameterBadge, ...(style ? [style] : [])]}>
<Text>{children}</Text>
</View>
);
};
@@ -0,0 +1,54 @@
import { Text, View, StyleSheet } from '@react-pdf/renderer';
import type { Style } from '@react-pdf/types';
type PdfStatusBadgeProps = {
children: React.ReactNode;
style?: Style;
};
const styles = StyleSheet.create({
statusBadge: {
paddingVertical: 2,
paddingHorizontal: 4,
borderRadius: 12,
fontSize: 7,
fontWeight: 'bold',
borderWidth: 1,
borderStyle: 'solid',
backgroundColor: '#F5F5F5',
borderColor: '#E5E7EB',
},
statusBadgeText: {
fontSize: 7,
fontWeight: 'bold',
color: '#333333',
},
});
export const PdfStatusBadge = ({ children, style }: PdfStatusBadgeProps) => {
const styleRecord = style as Record<string, unknown>;
const color = styleRecord?.color as string | undefined;
const viewStyle = Object.entries(styleRecord || {}).reduce(
(acc, [key, value]) => {
if (key !== 'color') {
acc[key] = value;
}
return acc;
},
{} as Record<string, unknown>
);
return (
<View
style={[
styles.statusBadge,
...(Object.keys(viewStyle).length > 0 ? [viewStyle as Style] : []),
]}
>
<Text style={[styles.statusBadgeText, ...(color ? [{ color }] : [])]}>
{children}
</Text>
</View>
);
};
@@ -0,0 +1,48 @@
import { Text, View, StyleSheet } from '@react-pdf/renderer';
import type { Style } from '@react-pdf/types';
type PdfPageNumberProps = {
style?: Style;
/**
* Format template for page number.
* Use {pageNumber} and {totalPages} as placeholders.
* Default: "{pageNumber} / {totalPages}"
*/
format?: string;
};
const styles = StyleSheet.create({
footer: {
width: '100%',
display: 'flex',
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
paddingHorizontal: 16,
position: 'absolute',
fontSize: 8,
bottom: 30,
left: 0,
right: 0,
textAlign: 'center',
color: 'grey',
},
});
export const PdfPageNumber = ({
style,
format = '{pageNumber} / {totalPages}',
}: PdfPageNumberProps) => {
return (
<View style={style || styles.footer} fixed>
<Text
render={({ pageNumber, totalPages }) =>
format
.replace('{pageNumber}', String(pageNumber))
.replace('{totalPages}', String(totalPages))
}
fixed
/>
</View>
);
};
+21 -14
View File
@@ -1,9 +1,10 @@
'use client';
import { View, StyleSheet } from '@react-pdf/renderer';
import { PdfThead, PdfColumn } from './PdfThead';
import { PdfTbody, PdfTbodyCell } from './PdfTbody';
import { PdfTfoot, PdfTfootCell } from './PdfTfoot';
import type { PdfColumn } from './types';
import { PdfThead } from './PdfThead';
import { PdfTbody } from './PdfTbody';
import { PdfTfoot } from './PdfTfoot';
const styles = StyleSheet.create({
table: {
@@ -13,10 +14,10 @@ const styles = StyleSheet.create({
},
});
interface PdfTableProps {
columns: PdfColumn[];
data: PdfTbodyCell[][];
footer?: PdfTfootCell[];
interface PdfTableProps<TData = Record<string, unknown>> {
columns: PdfColumn<TData>[];
data: TData[];
showFooter?: boolean;
footerLabel?: string;
firstRow?: {
valueKey: string;
@@ -26,20 +27,26 @@ interface PdfTableProps {
};
}
export const PdfTable = ({
export const PdfTable = <TData = Record<string, unknown>,>({
columns,
data,
footer,
showFooter = false,
footerLabel = 'Total',
firstRow,
}: PdfTableProps) => {
}: PdfTableProps<TData>) => {
// Check if any column has footer defined
const hasFooter =
showFooter || columns.some((col) => col.footer !== undefined);
return (
<View style={styles.table}>
<PdfThead columns={columns} />
<PdfTbody columns={columns} rows={data} firstRow={firstRow} />
{footer && footer.length > 0 && (
<PdfTfoot columns={columns} cells={footer} label={footerLabel} />
<PdfThead columns={columns} data={data} />
<PdfTbody columns={columns} data={data} firstRow={firstRow} />
{hasFooter && data.length > 0 && (
<PdfTfoot columns={columns} data={data} label={footerLabel} />
)}
</View>
);
};
export type { PdfColumn };
+44 -54
View File
@@ -1,22 +1,8 @@
'use client';
import { Text, View, StyleSheet } from '@react-pdf/renderer';
export interface PdfColumn {
key: string;
header: string;
flex: number;
align?: 'left' | 'center' | 'right';
}
export interface PdfTbodyCell {
key: string;
value: string | number | React.ReactNode;
align?: 'left' | 'center' | 'right';
color?: string;
formatAs?: 'text' | 'date' | 'currency' | 'number';
formatDate?: string;
}
import { ReactNode } from 'react';
import type { PdfColumn } from './types';
const styles = StyleSheet.create({
tableRow: {
@@ -71,21 +57,22 @@ const styles = StyleSheet.create({
},
});
interface PdfTbodyProps {
columns: PdfColumn[];
rows: PdfTbodyCell[][];
interface PdfTbodyProps<TData = Record<string, unknown>> {
columns: PdfColumn<TData>[];
data: TData[];
firstRow?: {
valueKey: string;
value: number;
align?: 'right';
color?: string;
};
formatDate?: (date: string, format: string) => string;
formatNumber?: (num: number) => string;
formatCurrency?: (num: number) => string;
}
export const PdfTbody = ({ columns, rows, firstRow }: PdfTbodyProps) => {
export const PdfTbody = <TData = Record<string, unknown>,>({
columns,
data,
firstRow,
}: PdfTbodyProps<TData>) => {
return (
<>
{/* First Row */}
@@ -93,17 +80,17 @@ export const PdfTbody = ({ columns, rows, firstRow }: PdfTbodyProps) => {
<View style={[styles.tableRow, styles.tableBorderBottom]}>
{columns.map((column, index) => {
const isLastColumn = index === columns.length - 1;
const isfirstRowColumn = column.key === firstRow.valueKey;
const align = column.align || 'center';
const isFirstRowColumn = column.key === firstRow.valueKey;
const align = column.align || 'left';
const cellStyle =
column.key === 'no'
? [styles.tableCellNo, { flex: column.flex }]
: isfirstRowColumn
? [styles.tableCellNo, { flex: column.flex || 1 }]
: isFirstRowColumn
? [
styles.tableCellRight,
{
flex: column.flex,
flex: column.flex || 1,
color: firstRow.color || 'black',
borderRightWidth: isLastColumn ? 0 : 1,
},
@@ -112,7 +99,7 @@ export const PdfTbody = ({ columns, rows, firstRow }: PdfTbodyProps) => {
? [
styles.tableCellRight,
{
flex: column.flex,
flex: column.flex || 1,
borderRightWidth: isLastColumn ? 0 : 1,
},
]
@@ -120,7 +107,7 @@ export const PdfTbody = ({ columns, rows, firstRow }: PdfTbodyProps) => {
? [
styles.tableCellCenter,
{
flex: column.flex,
flex: column.flex || 1,
borderRightWidth: isLastColumn ? 0 : 1,
},
]
@@ -128,15 +115,15 @@ export const PdfTbody = ({ columns, rows, firstRow }: PdfTbodyProps) => {
? [
styles.tableCellLast,
{
flex: column.flex,
flex: column.flex || 1,
borderRightWidth: 0,
},
]
: [styles.tableCell, { flex: column.flex }];
: [styles.tableCell, { flex: column.flex || 1 }];
return (
<View key={column.key} style={cellStyle}>
<Text>{isfirstRowColumn ? firstRow.value : ''}</Text>
<Text>{isFirstRowColumn ? firstRow.value : ''}</Text>
</View>
);
})}
@@ -144,8 +131,8 @@ export const PdfTbody = ({ columns, rows, firstRow }: PdfTbodyProps) => {
)}
{/* Data Rows */}
{rows.map((row, rowIndex) => {
const isLastRow = rowIndex === rows.length - 1;
{data.map((row, rowIndex) => {
const isLastRow = rowIndex === data.length - 1;
return (
<View
@@ -156,19 +143,27 @@ export const PdfTbody = ({ columns, rows, firstRow }: PdfTbodyProps) => {
]}
>
{columns.map((column, colIndex) => {
const cell = row.find((c) => c.key === column.key);
const isLastColumn = colIndex === columns.length - 1;
const align = cell?.align || column.align || 'center';
const align = column.align || 'left';
// Get cell content from column.cell function or fallback to row value
let cellContent: ReactNode;
if (column.cell) {
cellContent = column.cell({ row, index: rowIndex });
} else {
cellContent =
((row as Record<string, unknown>)[column.key] as ReactNode) ??
'-';
}
const cellStyle =
column.key === 'no'
? [styles.tableCellNo, { flex: column.flex }]
? [styles.tableCellNo, { flex: column.flex || 1 }]
: align === 'right'
? [
styles.tableCellRight,
{
flex: column.flex,
color: cell?.color || 'black',
flex: column.flex || 1,
borderRightWidth: isLastColumn ? 0 : 1,
},
]
@@ -176,37 +171,30 @@ export const PdfTbody = ({ columns, rows, firstRow }: PdfTbodyProps) => {
? [
styles.tableCellCenter,
{
flex: column.flex,
color: cell?.color || 'black',
flex: column.flex || 1,
borderRightWidth: isLastColumn ? 0 : 1,
},
]
: isLastColumn
? [
styles.tableCellLast,
{ flex: column.flex, borderRightWidth: 0 },
{ flex: column.flex || 1, borderRightWidth: 0 },
]
: [
styles.tableCell,
{
flex: column.flex,
color: cell?.color || 'black',
flex: column.flex || 1,
borderRightWidth: isLastColumn ? 0 : 1,
},
];
return (
<View key={column.key} style={cellStyle}>
{cell?.value !== undefined &&
cell?.value !== null &&
cell?.value !== '' ? (
typeof cell.value === 'object' ? (
cell.value
) : (
<Text>{String(cell.value)}</Text>
)
{typeof cellContent === 'string' ||
typeof cellContent === 'number' ? (
<Text>{String(cellContent)}</Text>
) : (
<Text>-</Text>
cellContent
)}
</View>
);
@@ -217,3 +205,5 @@ export const PdfTbody = ({ columns, rows, firstRow }: PdfTbodyProps) => {
</>
);
};
export type { PdfColumn };
+48 -38
View File
@@ -1,21 +1,8 @@
'use client';
import { Text, View, StyleSheet } from '@react-pdf/renderer';
export interface PdfColumn {
key: string;
header: string;
flex: number;
align?: 'left' | 'center' | 'right';
}
export interface PdfTfootCell {
key: string;
value: string | number;
align?: 'left' | 'center' | 'right';
flex?: number;
color?: string;
}
import { ReactNode } from 'react';
import type { PdfColumn } from './types';
const styles = StyleSheet.create({
tableRow: {
@@ -69,63 +56,86 @@ const styles = StyleSheet.create({
},
});
interface PdfTfootProps {
columns: PdfColumn[];
cells: PdfTfootCell[];
interface PdfTfootProps<TData = Record<string, unknown>> {
columns: PdfColumn<TData>[];
data: TData[];
label?: string;
}
export const PdfTfoot = ({
export const PdfTfoot = <TData = Record<string, unknown>,>({
columns,
cells,
data,
label = 'Total',
}: PdfTfootProps) => {
}: PdfTfootProps<TData>) => {
return (
<View style={[styles.tableRow, styles.summaryRow]}>
{columns.map((column, index) => {
const isLastColumn = index === columns.length - 1;
const cellData = cells.find((c) => c.key === column.key);
// Get footer content from column definition
let footerContent: ReactNode;
if (typeof column.footer === 'function') {
footerContent = column.footer(data);
} else {
footerContent = column.footer;
}
// Use label for first column (usually 'no' column)
const displayContent = column.key === 'no' ? label : footerContent;
// Determine alignment
const align = column.footerAlign || column.align || 'left';
const color = column.footerColor || 'black';
const cellStyle =
column.key === 'no'
? [
styles.tableCellNo,
{ flex: column.flex, borderRightWidth: isLastColumn ? 0 : 1 },
{
flex: column.flex || 1,
borderRightWidth: isLastColumn ? 0 : 1,
color,
},
]
: cellData?.align === 'right'
: align === 'right'
? [
styles.tableCellRight,
{
flex: column.flex,
color: cellData?.color || 'black',
flex: column.flex || 1,
color,
borderRightWidth: isLastColumn ? 0 : 1,
},
]
: cellData?.align === 'center'
: align === 'center'
? [
styles.tableCellCenter,
{
flex: column.flex,
color: cellData?.color || 'black',
flex: column.flex || 1,
color,
borderRightWidth: isLastColumn ? 0 : 1,
},
]
: isLastColumn
? [styles.tableCellLast, { flex: column.flex }]
: [
styles.tableCell,
{
flex: column.flex,
color: cellData?.color || 'black',
},
];
? [styles.tableCellLast, { flex: column.flex || 1, color }]
: [styles.tableCell, { flex: column.flex || 1, color }];
return (
<View key={column.key} style={cellStyle}>
<Text>{column.key === 'no' ? label : cellData?.value || ''}</Text>
{displayContent !== undefined && displayContent !== null ? (
typeof displayContent === 'string' ||
typeof displayContent === 'number' ? (
<Text>{String(displayContent)}</Text>
) : (
displayContent
)
) : (
<Text>-</Text>
)}
</View>
);
})}
</View>
);
};
export type { PdfColumn };
+29 -14
View File
@@ -1,13 +1,8 @@
'use client';
import { Text, View, StyleSheet } from '@react-pdf/renderer';
export interface PdfColumn {
key: string;
header: string;
flex: number;
align?: 'left' | 'center' | 'right';
}
import { ReactNode } from 'react';
import type { PdfColumn } from './types';
const styles = StyleSheet.create({
tableRow: {
@@ -48,23 +43,37 @@ const styles = StyleSheet.create({
},
});
interface PdfTheadProps {
columns: PdfColumn[];
interface PdfTheadProps<TData = Record<string, unknown>> {
columns: PdfColumn<TData>[];
data?: TData[];
}
export const PdfThead = ({ columns }: PdfTheadProps) => {
export const PdfThead = <TData = Record<string, unknown>,>({
columns,
data,
}: PdfTheadProps<TData>) => {
return (
<View style={[styles.tableRow, styles.tableHeader]}>
{columns.map((column, index) => {
const align = column.align || 'center';
const isLastColumn = index === columns.length - 1;
// Get header content from column definition
let headerContent: ReactNode;
if (typeof column.header === 'function') {
headerContent = column.header(data || []);
} else {
headerContent = column.header || column.key;
}
// Determine alignment - columns align right by default for numeric data
const align = column.align || 'left';
const cellStyle =
align === 'right'
? [
styles.tableCellHeaderRight,
{
flex: column.flex,
flex: column.flex || 1,
textAlign: 'right' as const,
borderRightWidth: isLastColumn ? 0 : 1,
},
@@ -72,7 +81,7 @@ export const PdfThead = ({ columns }: PdfTheadProps) => {
: [
styles.tableCellHeader,
{
flex: column.flex,
flex: column.flex || 1,
textAlign: align as 'left' | 'center' | 'right',
borderRightWidth: isLastColumn ? 0 : 1,
},
@@ -80,10 +89,16 @@ export const PdfThead = ({ columns }: PdfTheadProps) => {
return (
<View key={column.key} style={cellStyle}>
<Text>{column.header}</Text>
{typeof headerContent === 'string' ? (
<Text>{headerContent}</Text>
) : (
headerContent
)}
</View>
);
})}
</View>
);
};
export type { PdfColumn };
+1 -3
View File
@@ -2,6 +2,4 @@ export { PdfTable } from './PdfTable';
export { PdfThead } from './PdfThead';
export { PdfTbody } from './PdfTbody';
export { PdfTfoot } from './PdfTfoot';
export type { PdfColumn } from './PdfThead';
export type { PdfTbodyCell } from './PdfTbody';
export type { PdfTfootCell } from './PdfTfoot';
export type { PdfColumn } from './types';
+24
View File
@@ -0,0 +1,24 @@
import { ReactNode } from 'react';
/**
* PdfColumn - Mirip dengan ColumnDef di TanStack Table
* Mengatur header (thead), body (tbody), dan footer (tfoot) dalam satu definisi
*/
export interface PdfColumn<TData = Record<string, unknown>> {
key: string;
flex?: number;
// Header configuration (thead)
header?: string | ((data: TData[]) => ReactNode);
// Body configuration (tbody)
align?: 'left' | 'center' | 'right';
cell?: (props: { row: TData; index: number }) => ReactNode | string | number;
// Footer configuration (tfoot)
footer?: string | number | ((data: TData[]) => ReactNode | string | number);
footerAlign?: 'left' | 'center' | 'right';
footerColor?: string;
}
export type { PdfColumn as default };
@@ -0,0 +1,80 @@
import { Color } from '@/types/theme';
import { Text, StyleSheet } from '@react-pdf/renderer';
import type { Style } from '@react-pdf/types';
type TypographySize = 'h1' | 'h2' | 'h3' | 'h4' | 'p' | 'small' | 'label';
type TypographyVariant = Color | 'default';
type PdfTypographyProps = {
children: React.ReactNode;
size?: TypographySize;
variant?: TypographyVariant;
color?: string;
style?: Style;
};
const styles = StyleSheet.create({
h1: {
fontSize: 14,
fontWeight: 'bold',
marginBottom: 5,
},
h2: {
fontSize: 12,
fontWeight: 'bold',
marginBottom: 8,
},
h3: {
fontSize: 10,
fontWeight: 'bold',
marginBottom: 4,
},
h4: {
fontSize: 9,
fontWeight: 'bold',
marginBottom: 3,
},
p: {
fontSize: 10,
marginBottom: 4,
},
small: {
fontSize: 8,
marginBottom: 2,
},
label: {
fontSize: 9,
marginBottom: 5,
},
});
const variantColors: Record<TypographyVariant, string> = {
default: '#333333',
primary: '#1f74bf',
secondary: '#6B7280',
accent: '#8B5CF6',
neutral: '#6B7280',
info: '#3B82F6',
success: '#065F46',
warning: '#92400E',
error: '#DC2626',
none: '#333333',
};
export const PdfTypography = ({
children,
size = 'p',
variant = 'default',
color,
style,
}: PdfTypographyProps) => {
const sizeStyle = styles[size];
const textColor = color || variantColors[variant];
return (
<Text style={[sizeStyle, { color: textColor }, ...(style ? [style] : [])]}>
{children}
</Text>
);
};
@@ -0,0 +1,65 @@
export type StatusColor = {
bg: string;
text: string;
border: string;
};
// Due status colors (for debt supplier reports)
export const dueStatusColors: Record<string, StatusColor> = {
'SUDAH JATUH TEMPO': {
bg: '#FEE2E2',
text: '#991B1B',
border: '#F87171',
}, // error/red
'BELUM JATUH TEMPO': {
bg: '#D1FAE5',
text: '#065F46',
border: '#34D399',
}, // success/green
'MENDEKATI JATUH TEMPO': {
bg: '#FEF3C7',
text: '#92400E',
border: '#FBBF24',
}, // warning/yellow
};
// Payment status colors (for customer payment & debt supplier reports)
export const paymentStatusColors: Record<string, StatusColor> = {
'BELUM LUNAS': {
bg: '#FEF3C7',
text: '#92400E',
border: '#FBBF24',
}, // warning/yellow
LUNAS: {
bg: '#DBEAFE',
text: '#1E40AF',
border: '#60A5FA',
}, // primary/blue
'PEMBAYARAN SEBAGIAN': { bg: '#D1FAE5', text: '#065F46', border: '#34D399' }, // success/green
PEMBAYARAN: {
bg: '#D1FAE5',
text: '#065F46',
border: '#34D399',
}, // success/green
};
// Fallback color for unknown statuses
export const fallbackStatusColor: StatusColor = {
bg: '#F3F4F6',
text: '#374151',
border: '#D1D5DB',
}; // neutral
export const getPDFBadgeStyle = (
statusText: string,
type: 'due' | 'payment' = 'payment'
): StatusColor => {
const normalizedStatus = statusText.toUpperCase().trim();
const colors =
type === 'due'
? dueStatusColors[normalizedStatus]
: paymentStatusColors[normalizedStatus];
return colors || fallbackStatusColor;
};
@@ -1,5 +1,4 @@
import IconSkeleton from '@/components/helper/skeleton/IconSkeleton';
import { Icon } from '@iconify/react';
const DataStateSkeleton = ({
icon,
+8 -2
View File
@@ -134,14 +134,20 @@ const DropFileInput: React.FC<DropFileInputProps> = ({
{!isError && bottomLabel && (
<p
className={cn('w-full text-sm opacity-60', className?.bottomLabel)}
className={cn(
'w-full mt-1.5 text-xs opacity-60',
className?.bottomLabel
)}
>
{bottomLabel}
</p>
)}
{isError && (
<p
className={cn('w-full text-sm text-error', className?.errorMessage)}
className={cn(
'w-full mt-1.5 text-xs text-error',
className?.errorMessage
)}
>
{errorMessage}
</p>
-1
View File
@@ -4,7 +4,6 @@ import { ChangeEvent } from 'react';
import {
PatternFormat,
NumberFormatBase,
NumberFormatBaseProps,
OnValueChange,
} from 'react-number-format';
import TextInput, { TextInputProps } from '@/components/input/TextInput';
+2 -2
View File
@@ -144,12 +144,12 @@ export const RadioGroup = ({
{/* Label bawah */}
{!isError && bottomLabel && (
<p className='text-sm opacity-60'>{bottomLabel}</p>
<p className='mt-1.5 text-xs opacity-60'>{bottomLabel}</p>
)}
{/* Pesan error */}
{isError && errorMessage && (
<p className='text-sm text-error'>{errorMessage}</p>
<p className='mt-1.5 text-xs text-error'>{errorMessage}</p>
)}
</div>
</RadioGroupContext.Provider>
+37 -40
View File
@@ -246,8 +246,8 @@ const SelectInput = <T extends OptionType>(props: SelectInputProps<T>) => {
className={cn(
'inline-flex items-center px-3 border border-r-0 border-base-content/10 rounded-l-lg transition-all duration-200',
{
'bg-gray-100 border-base-content/10': !isDisabled,
'bg-gray-50 border-base-content/10': isDisabled,
'bg-base-100 border-base-content/10': !isDisabled,
'bg-base-200 border-base-content/10': isDisabled,
'border-error': isError,
},
className?.inputPrefix
@@ -278,28 +278,28 @@ const SelectInput = <T extends OptionType>(props: SelectInputProps<T>) => {
className={cn('w-full flex-1', className?.select)}
classNames={{
control: ({ isFocused, isDisabled }) =>
cn('w-full border bg-white transition-shadow', 'rounded-lg!', {
'cursor-pointer!': !readOnly && !isDisabled,
'border-red-500! ring-2 ring-red-200': isError,
'border-indigo-500 ring-2 ring-indigo-200':
isFocused && !startAdornment,
'border-base-content/10!': !isError && !isFocused,
'bg-gray-100 text-gray-400 cursor-not-allowed':
cn('w-full border transition-shadow', 'rounded-lg!', {
'bg-base-100!': !isDisabled && !readOnly,
'bg-base-200! text-gray-400 cursor-not-allowed':
isDisabled && !readOnly,
'bg-transparent! cursor-not-allowed!': readOnly,
'cursor-pointer!': !readOnly && !isDisabled,
'border-error!': isError,
'ring-2 ring-error/20': isError,
'border-indigo-500 ring-2 ring-indigo-200':
isFocused && !startAdornment && !isError,
'border-base-content/10!': !isError && !isFocused,
'rounded-l-none!': inputPrefix && !startAdornment,
'rounded-r-none!': inputSuffix && !startAdornment,
}),
valueContainer: () => cn('flex-1 px-3! pr-2! py-2.5! gap-1'),
placeholder: () =>
cn({
'text-gray-400 text-sm leading-tight': !isError,
'text-red-300!': isError,
cn('text-gray-400 text-sm leading-tight', {
'text-error!': isError,
}),
singleValue: () =>
cn({
'm-0! text-gray-900 text-sm leading-tight': !isError,
'text-error!': isError,
cn('m-0! text-gray-900 text-sm leading-tight', {
'text-error!': isError && !readOnly,
'text-gray-900!': readOnly,
}),
input: () => cn('text-gray-900 m-0! p-0! text-sm leading-tight'),
@@ -370,8 +370,8 @@ const SelectInput = <T extends OptionType>(props: SelectInputProps<T>) => {
className={cn(
'inline-flex items-center px-3 border border-l-0 border-base-content/10 rounded-r-lg transition-all duration-200',
{
'bg-gray-100 border-base-content/10': !isDisabled,
'bg-gray-50 border-base-content/10': isDisabled,
'bg-base-100 border-base-content/10': !isDisabled,
'bg-base-200 border-base-content/10': isDisabled,
'border-error': isError,
},
className?.inputSuffix
@@ -403,31 +403,26 @@ const SelectInput = <T extends OptionType>(props: SelectInputProps<T>) => {
className={cn('w-full', className?.select)}
classNames={{
control: ({ isFocused, isDisabled }) =>
cn(
'w-full border bg-white transition-shadow',
// Gunakan rounded-lg untuk semua kasus
'rounded-lg!',
{
'cursor-pointer!': !readOnly && !isDisabled,
'border-red-500! ring-2 ring-red-200': isError,
'border-indigo-500 ring-2 ring-indigo-200':
isFocused && !startAdornment,
'border-base-content/10!': !isError && !isFocused,
'bg-gray-100 text-gray-400 cursor-not-allowed':
isDisabled && !readOnly,
'bg-transparent! cursor-not-allowed!': readOnly,
}
),
cn('w-full border transition-shadow rounded-lg!', {
'bg-base-100!': !isDisabled && !readOnly,
'bg-base-200! text-gray-400 cursor-not-allowed':
isDisabled && !readOnly,
'bg-transparent! cursor-not-allowed!': readOnly,
'cursor-pointer!': !readOnly && !isDisabled,
'border-error!': isError,
'ring-2 ring-error/20': isError,
'border-indigo-500 ring-2 ring-indigo-200':
isFocused && !startAdornment && !isError,
'border-base-content/10!': !isError && !isFocused,
}),
valueContainer: () => cn('flex-1 px-3! pr-2! py-2.5! gap-1'),
placeholder: () =>
cn({
'text-gray-400 text-sm leading-tight': !isError,
'text-red-300!': isError,
cn('text-gray-400 text-sm leading-tight', {
'text-error!': isError,
}),
singleValue: () =>
cn({
'm-0! text-gray-900 text-sm leading-tight': !isError,
'text-error!': isError,
cn('m-0! text-gray-900 text-sm leading-tight', {
'text-error!': isError && !readOnly,
'text-gray-900!': readOnly,
}),
input: () => cn('text-gray-900 m-0! p-0! text-sm leading-tight'),
@@ -493,9 +488,11 @@ const SelectInput = <T extends OptionType>(props: SelectInputProps<T>) => {
/>
)}
{isError && <p className='w-full text-sm text-error'>{errorMessage}</p>}
{isError && (
<p className='w-full mt-1.5 text-xs text-error'>{errorMessage}</p>
)}
{!isError && bottomLabel && (
<p className='w-full text-sm opacity-60'>{bottomLabel}</p>
<p className='w-full mt-1.5 text-xs opacity-60'>{bottomLabel}</p>
)}
</div>
);
+4 -2
View File
@@ -159,9 +159,11 @@ const TagInput: React.FC<TagInputProps> = ({
{/* Bottom label or error message */}
{!isError && bottomLabel && (
<p className='w-full text-sm opacity-60'>{bottomLabel}</p>
<p className='w-full mt-1.5 text-xs opacity-60'>{bottomLabel}</p>
)}
{isError && (
<p className='w-full mt-1.5 text-xs text-error'>{errorMessage}</p>
)}
{isError && <p className='w-full text-sm text-error'>{errorMessage}</p>}
</div>
);
};
+18 -8
View File
@@ -104,8 +104,8 @@ const TextInput = ({
className={cn(
'inline-flex items-center px-3 border border-r-0 border-base-content/10 rounded-l-lg transition-all duration-200',
{
'bg-gray-100 border-base-content/10': !disabled,
'bg-gray-50 border-base-content/10': disabled,
'bg-base-100 border-base-content/10': !disabled,
'bg-base-200 border-base-content/10': disabled,
'border-error': isError,
'border-success!': isValid,
},
@@ -118,7 +118,7 @@ const TextInput = ({
<div
className={cn(
'input h-fit px-3 py-2.5 gap-1.5 text-sm font-normal leading-6 flex-1 rounded-lg! outline-none! transition-all duration-200 flex items-center bg-white border-base-content/10',
'input h-fit px-3 py-2.5 gap-1.5 text-sm font-normal leading-6 flex-1 rounded-lg! outline-none! transition-all duration-200 flex items-center border-base-content/10',
{
'border-error': isError,
'border-success!': isValid,
@@ -126,7 +126,8 @@ const TextInput = ({
'rounded-r-none!': inputSuffix,
'input-disabled': disabled,
'cursor-not-allowed': disabled,
'bg-gray-50': disabled,
'bg-base-100': !disabled,
'bg-base-200': disabled,
},
className?.inputWrapper
)}
@@ -167,8 +168,8 @@ const TextInput = ({
className={cn(
'inline-flex items-center px-3 border border-l-0 border-base-content/10 rounded-r-lg transition-all duration-200',
{
'bg-gray-100 border-base-content/10': !disabled,
'bg-gray-50 border-base-content/10': disabled,
'bg-base-100 border-base-content/10': !disabled,
'bg-base-200 border-base-content/10': disabled,
'border-error': isError,
'border-success!': isValid,
},
@@ -182,10 +183,12 @@ const TextInput = ({
) : (
<div
className={cn(
'input h-fit px-3 py-2.5 gap-1.5 text-sm font-normal leading-6 w-full rounded-lg! outline-none! transition-all duration-200 bg-white border-base-content/10',
'input h-fit px-3 py-2.5 gap-1.5 text-sm font-normal leading-6 w-full rounded-lg! outline-none! transition-all duration-200 flex items-center border-base-content/10',
{
'border-error': isError,
'border-success!': isValid,
'bg-base-100': !disabled,
'bg-base-200': disabled,
},
className?.inputWrapper
)}
@@ -201,7 +204,14 @@ const TextInput = ({
onChange={onChange}
onBlur={onBlur}
disabled={disabled}
className={cn('grow', className?.input)}
className={cn(
'grow bg-transparent outline-none',
{
'cursor-not-allowed': disabled,
'text-gray-500': disabled,
},
className?.input
)}
readOnly={readOnly}
/>
@@ -56,7 +56,7 @@ const ConfirmationModalWithNotes: React.FC<ConfirmationModalWithNotesProps> = ({
closeOnBackdrop={closeOnBackdrop}
primaryButton={{
...primaryButton,
onClick: (e) => {
onClick: () => {
if (primaryButton && primaryButton?.onClick) {
primaryButton?.onClick?.(notes);
} else {
@@ -5,28 +5,23 @@ import { useMemo, useState } from 'react';
import { Icon } from '@iconify/react';
import Button from '@/components/Button';
import Tabs from '@/components/Tabs';
import ClosingGeneralInformationTable from '@/components/pages/closing/ClosingGeneralInformationTable';
import ClosingSapronakTabContent from '@/components/pages/closing/ClosingSapronakTabContent';
import ClosingProductionDataTabContent from '@/components/pages/closing/ClosingProductionDataTabContent';
import ClosingGeneralInformationTable from '@/components/pages/closing/table/ClosingGeneralInformationTable';
import SapronakClosingTab from '@/components/pages/closing/tab/SapronakClosingTab';
import ProductionDataClosingTab from '@/components/pages/closing/tab/ProductionDataClosingTab';
import {
ClosingGeneralInformation,
BaseClosingSales,
ClosingHppExpedition,
} from '@/types/api/closing';
import ClosingSapronakCalculationTabContent from '@/components/pages/closing/ClosingSapronakCalculationTabContent';
import ClosingOverheadTabContent from '@/components/pages/closing/ClosingOverheadTabContent';
import ClosingFinanceTabContent from '@/components/pages/closing/ClosingFinanceTabContent';
import SalesReportTable from '@/components/pages/closing/sale/SalesReportTable';
import HppExpeditionReportTable from './hpp-ekspedisi/HppExpeditionReportTable';
import { ClosingGeneralInformation } from '@/types/api/closing';
import SapronakCalculationClosingTab from '@/components/pages/closing/tab/SapronakCalculationClosingTab';
import OverheadClosingTab from '@/components/pages/closing/tab/OverheadClosingTab';
import FinanceClosingTab from '@/components/pages/closing/tab/FinanceClosingTab';
import SalesClosingTab from '@/components/pages/closing/tab/SalesClosingTab';
import HppExpeditionClosingTab from '@/components/pages/closing/tab/HppExpeditionClosingTab';
import ClosingKandangList from '@/components/pages/closing/ClosingKandangList';
import { ProjectFlock } from '@/types/api/production/project-flock';
import { ProjectFlockKandang } from '@/types/api/production/project-flock-kandang';
import { useTabActionsStore } from '@/stores/tab-actions/tab-actions.store';
interface ClosingDetailProps {
id: number;
initialValue?: ClosingGeneralInformation;
salesData?: BaseClosingSales;
hppExpeditionData?: ClosingHppExpedition;
projectData?: ProjectFlock;
kandangData?: ProjectFlockKandang;
}
@@ -34,25 +29,24 @@ interface ClosingDetailProps {
const ClosingDetail: React.FC<ClosingDetailProps> = ({
id,
initialValue,
salesData,
hppExpeditionData,
projectData,
kandangData,
}) => {
const [activeTab, setActiveTab] = useState<string>('sapronak');
const [activeTabId, setActiveTabId] = useState<string>('sapronak');
const tabActions = useTabActionsStore((state) => state.tabActions);
const closingDetailTabs = useMemo(() => {
const validTabs = [
{
id: 'sapronak',
label: 'Sapronak',
content: <ClosingSapronakTabContent projectFlockId={id} />,
content: <SapronakClosingTab projectFlockId={id} />,
},
{
id: 'perhitunganSapronak',
label: 'Perhitungan Sapronak',
content: (
<ClosingSapronakCalculationTabContent
<SapronakCalculationClosingTab
closingGeneralInformation={initialValue}
projectFlockId={id}
/>
@@ -61,13 +55,13 @@ const ClosingDetail: React.FC<ClosingDetailProps> = ({
{
id: 'penjualan',
label: 'Penjualan',
content: <SalesReportTable initialValues={salesData} />,
content: <SalesClosingTab projectFlockId={id} />,
},
{
id: 'overhead',
label: 'Overhead',
content: (
<ClosingOverheadTabContent
<OverheadClosingTab
projectFlockId={id}
generalInformation={initialValue}
kandangData={kandangData}
@@ -77,26 +71,26 @@ const ClosingDetail: React.FC<ClosingDetailProps> = ({
{
id: 'hppEkspedisi',
label: 'HPP Ekspedisi',
content: <HppExpeditionReportTable initialValues={hppExpeditionData} />,
content: <HppExpeditionClosingTab projectFlockId={id} />,
},
{
id: 'dataProduksi',
label: 'Data Produksi',
content: <ClosingProductionDataTabContent projectFlockId={id} />,
content: <ProductionDataClosingTab projectFlockId={id} />,
},
{
id: 'keuangan',
label: 'Keuangan',
content: <ClosingFinanceTabContent projectFlockId={id} />,
content: <FinanceClosingTab projectFlockId={id} />,
},
];
return validTabs;
}, [initialValue]);
}, [initialValue, kandangData, id]);
return (
<>
<section className='w-full max-w-7xl pb-16'>
<section className='w-full'>
<header className='flex flex-col gap-4'>
<Button
href={
@@ -126,13 +120,17 @@ const ClosingDetail: React.FC<ClosingDetailProps> = ({
)}
<Tabs
activeTabId={activeTab}
onTabChange={setActiveTab}
activeTabId={activeTabId}
onTabChange={setActiveTabId}
tabs={closingDetailTabs}
variant='lifted'
variant='boxed'
className={{
wrapper: 'w-full mt-4',
tabHeaderWrapper:
'relative justify-between items-center py-3 before:absolute before:top-0 before:left-0 before:right-0 before:-mx-4 before:border-t before:border-base-content/10 after:absolute after:bottom-0 after:left-0 after:right-0 after:-mx-4 after:border-b after:border-base-content/10',
tab: 'w-fit',
content: 'p-0 m-0',
}}
sideContent={tabActions[activeTabId] || null}
/>
</section>
</>
@@ -1,17 +0,0 @@
import ClosingFinanceTable from '@/components/pages/closing/ClosingFinanceTable';
const ClosingFinanceTabContent = ({
projectFlockId,
}: {
projectFlockId: number;
}) => {
return (
<div className='flex flex-col gap-4'>
{projectFlockId && (
<ClosingFinanceTable projectFlockId={projectFlockId} />
)}
</div>
);
};
export default ClosingFinanceTabContent;
@@ -1,399 +0,0 @@
import Card from '@/components/Card';
import Table, { TABLE_DEFAULT_STYLING } from '@/components/Table';
import { isResponseSuccess } from '@/lib/api-helper';
import { formatCurrency, formatTitleCase } from '@/lib/helper';
import { ClosingApi } from '@/services/api/closing';
import { HppItem, ProfitLossItem } from '@/types/api/closing';
import { useSearchParams } from 'next/navigation';
import { useMemo } from 'react';
import useSWR from 'swr';
const ClosingFinanceTable = ({
projectFlockId,
}: {
projectFlockId: number;
}) => {
const searchParams = useSearchParams();
const kandangId = searchParams.get('kandangId');
const { data: finance, isLoading } = useSWR(
`/closing/finance/${projectFlockId}${kandangId ? `/${kandangId}` : ''}`,
() =>
ClosingApi.getFinance(
projectFlockId,
kandangId ? Number(kandangId) : undefined
)
);
const hppTableData: HppItem[] = useMemo(() => {
if (isResponseSuccess(finance)) {
const customItems = {
label: 'HPP dan Pengeluaran',
code: 'custom_row',
} as HppItem;
const purchases = finance.data.hpp.items.filter(
(item) => item.category === 'purchase'
);
const totalBudgeting = {
label: 'HPP dan Bahan Baku',
code: 'custom_row',
} as HppItem;
const overheads = finance.data.hpp.items.filter(
(item) => item.category === 'overhead'
);
return [customItems, ...purchases, totalBudgeting, ...overheads];
}
return [];
}, [finance]);
const profitLossTableData: ProfitLossItem[] = useMemo(() => {
if (isResponseSuccess(finance)) {
const incomes = finance.data.profit_loss.items.filter(
(item) => item.type === 'income'
);
const purchases = finance.data.profit_loss.items.filter(
(item) => item.type === 'purchase'
);
const overheads = finance.data.profit_loss.items.filter(
(item) => item.type === 'overhead'
);
const grossProfit = {
label: 'LABA RUGI BRUTO',
code: 'custom_row',
type: 'gross_profit',
rp_per_bird:
finance.data.profit_loss.summary.gross_profit.rp_per_bird ?? 0,
rp_per_kg: finance.data.profit_loss.summary.gross_profit.rp_per_kg ?? 0,
amount: finance.data.profit_loss.summary.gross_profit.amount ?? 0,
} as ProfitLossItem;
const subtotal = {
label: 'Subtotal',
code: 'custom_row',
type: 'subtotal',
rp_per_bird:
finance.data.profit_loss.summary.sub_total.rp_per_bird ?? 0,
rp_per_kg: finance.data.profit_loss.summary.sub_total.rp_per_kg ?? 0,
amount: finance.data.profit_loss.summary.sub_total.amount ?? 0,
} as ProfitLossItem;
return [...incomes, ...purchases, grossProfit, ...overheads, subtotal];
}
return [];
}, [finance]);
return (
<div className='flex flex-col gap-4'>
<>
<Card
variant='bordered'
className={{
wrapper: 'w-full',
}}
>
<div className='grid grid-cols-2 gap-6'>
<div className='flex flex-col gap-1'>
<div>Laba Rugi Brutto</div>
<div className='text-lg font-bold'>
{isResponseSuccess(finance)
? formatCurrency(
finance.data.profit_loss.summary.gross_profit.amount
)
: '-'}
</div>
</div>
<div className='flex flex-col gap-1'>
<div>Laba Rugi Netto</div>
<div className='text-lg font-bold'>
{isResponseSuccess(finance)
? formatCurrency(
finance.data.profit_loss.summary.net_profit.amount
)
: '-'}
</div>
</div>
</div>
</Card>
<Card
title='HPP Purchases'
variant='bordered'
collapsible
className={{
wrapper: 'w-full',
}}
>
<div className='mt-6 p-0 mb-0'>
<Table<HppItem>
data={hppTableData}
isLoading={isLoading}
columns={[
{
header: 'No.',
enableSorting: false,
accessorFn: (item, index) => {
if (item.code === 'custom_row') return '-';
const dataRowsBefore = hppTableData
.slice(0, index)
.filter((row) => row.code !== 'custom_row').length;
return dataRowsBefore + 1;
},
footer: (props) => {
return 'HPP';
},
},
{
header: 'Jenis',
enableSorting: false,
accessorFn: (item) => formatTitleCase(item.label || '-'),
},
{
header: 'Budgeting',
enableSorting: false,
columns: [
{
header: 'Rp/Ekor',
id: 'budgeting_rp_per_bird',
enableSorting: false,
accessorFn: (item) =>
formatCurrency(item.budgeting?.rp_per_bird || 0),
footer: (props) => {
return props.column.id === 'budgeting_rp_per_bird' &&
isResponseSuccess(finance)
? formatCurrency(
finance.data.hpp.summary?.budgeting
?.rp_per_bird || 0
)
: '-';
},
},
{
header: 'Rp/Kg',
id: 'budgeting_rp_per_kg',
enableSorting: false,
accessorFn: (item) =>
formatCurrency(item.budgeting?.rp_per_kg || 0),
footer: (props) => {
return props.column.id === 'budgeting_rp_per_kg' &&
isResponseSuccess(finance)
? formatCurrency(
finance.data.hpp.summary?.budgeting?.rp_per_kg ||
0
)
: '-';
},
},
{
header: 'Jumlah (Rp)',
id: 'budgeting_amount',
enableSorting: false,
accessorFn: (item) =>
formatCurrency(item.budgeting?.amount || 0),
footer: (props) => {
return props.column.id === 'budgeting_amount' &&
isResponseSuccess(finance)
? formatCurrency(
finance.data.hpp.summary?.budgeting?.amount || 0
)
: '-';
},
},
],
},
{
header: 'Realization',
enableSorting: false,
columns: [
{
header: 'Rp/Ekor',
id: 'realization_rp_per_bird',
enableSorting: false,
accessorFn: (item) =>
formatCurrency(item.realization?.rp_per_bird || 0),
footer: (props) => {
return props.column.id === 'realization_rp_per_bird' &&
isResponseSuccess(finance)
? formatCurrency(
finance.data.hpp.summary?.realization
?.rp_per_bird || 0
)
: '-';
},
},
{
header: 'Rp/Kg',
id: 'realization_rp_per_kg',
enableSorting: false,
accessorFn: (item) =>
formatCurrency(item.realization?.rp_per_kg || 0),
footer: (props) => {
return props.column.id === 'realization_rp_per_kg' &&
isResponseSuccess(finance)
? formatCurrency(
finance.data.hpp.summary?.realization
?.rp_per_kg || 0
)
: '-';
},
},
{
header: 'Jumlah (Rp)',
id: 'realization_amount',
enableSorting: false,
accessorFn: (item) =>
formatCurrency(item.realization?.amount || 0),
footer: (props) => {
return props.column.id === 'realization_amount' &&
isResponseSuccess(finance)
? formatCurrency(
finance.data.hpp.summary?.realization?.amount || 0
)
: '-';
},
},
],
},
]}
renderCustomRow={(row) => {
const rowData = row.original;
if (rowData.code === 'custom_row') {
return (
<tr
key={row.id}
className={TABLE_DEFAULT_STYLING.bodyRowClassName}
>
<td
className={TABLE_DEFAULT_STYLING.bodyColumnClassName}
></td>
<td
colSpan={7}
className={TABLE_DEFAULT_STYLING.bodyColumnClassName}
>
<div className='font-bold'>
{formatTitleCase(rowData.label ?? '-')}
</div>
</td>
</tr>
);
}
return null;
}}
renderFooter={isResponseSuccess(finance)}
/>
</div>
</Card>
<Card
title='Profit/Loss'
variant='bordered'
collapsible
className={{
wrapper: 'w-full',
}}
>
<div className='mt-6 p-0 mb-0'>
<Table<ProfitLossItem>
data={profitLossTableData}
isLoading={isLoading}
columns={[
{
header: 'Jenis',
enableSorting: false,
accessorFn: (item) => item.label,
cell: (item) => (
<div className=''>
{formatTitleCase(item.row.original.label || '-')}
</div>
),
footer: () => (
<div className='font-bold uppercase'>LABA RUGI NETTO</div>
),
},
{
header: 'Rp/Ekor',
enableSorting: false,
accessorFn: (item) => formatCurrency(item.rp_per_bird || 0),
footer: () => (
<div className='font-bold'>
{isResponseSuccess(finance)
? formatCurrency(
finance.data.profit_loss.summary.net_profit
.rp_per_bird || 0
)
: formatCurrency(0)}
</div>
),
},
{
header: 'Rp/Kg',
enableSorting: false,
accessorFn: (item) => formatCurrency(item.rp_per_kg || 0),
footer: () => (
<div className='font-bold'>
{isResponseSuccess(finance)
? formatCurrency(
finance.data.profit_loss.summary.net_profit
.rp_per_kg || 0
)
: formatCurrency(0)}
</div>
),
},
{
header: 'Jumlah (Rp)',
enableSorting: false,
accessorFn: (item) => formatCurrency(item.amount || 0),
footer: () => (
<div className='font-bold'>
{isResponseSuccess(finance)
? formatCurrency(
finance.data.profit_loss.summary.net_profit
.amount || 0
)
: formatCurrency(0)}
</div>
),
},
]}
renderCustomRow={(row) => {
const rowData = row.original;
if (rowData.code === 'custom_row') {
return (
<tr
key={row.id}
className={TABLE_DEFAULT_STYLING.footerRowClassName}
>
<td className={TABLE_DEFAULT_STYLING.bodyColumnClassName}>
<div className='font-bold ps-6 uppercase'>
{formatTitleCase(rowData.label ?? '-')}
</div>
</td>
<td className={TABLE_DEFAULT_STYLING.bodyColumnClassName}>
<div className='font-bold'>
{formatCurrency(rowData.rp_per_bird ?? 0)}
</div>
</td>
<td className={TABLE_DEFAULT_STYLING.bodyColumnClassName}>
<div className='font-bold'>
{formatCurrency(rowData.rp_per_kg ?? 0)}
</div>
</td>
<td className={TABLE_DEFAULT_STYLING.bodyColumnClassName}>
<div className='font-bold'>
{formatCurrency(rowData.amount ?? 0)}
</div>
</td>
</tr>
);
}
return null;
}}
className={{
paginationClassName: 'hidden',
}}
renderFooter={isResponseSuccess(finance)}
/>
</div>
</Card>
</>
</div>
);
};
export default ClosingFinanceTable;
@@ -10,18 +10,18 @@ const ClosingKandangList = ({
projectData?: ProjectFlock;
}) => {
return (
<div className='w-full my-4 @container'>
<div className='w-full py-3 @container relative before:absolute before:top-0 before:left-0 before:right-0 before:-mx-4 before:border-t before:border-base-content/10'>
<div className='flex flex-col @sm:flex-row gap-4'>
<div className='w-full'>
<div className='overflow-x-auto'>
<h1 className='font-bold my-4'>Kandang</h1>
<div className='flex flex-wrap gap-2 mb-4'>
<h1 className='font-bold mb-3'>Kandang</h1>
<div className='flex flex-wrap gap-2'>
{projectData?.kandangs?.map((kandang) => (
<Button
key={kandang.id}
variant='outline'
className='px-3 py-2.5 w-fit text-sm rounded-lg shadow-sm'
href={`/closing/detail/?closingId=${initialValue?.flock_id}&kandangId=${kandang.project_flock_kandang_id}`}
className='min-w-32'
>
{kandang.name}
</Button>
@@ -1,308 +0,0 @@
'use client';
import { useSearchParams } from 'next/navigation';
import useSWR from 'swr';
import { ClosingApi } from '@/services/api/closing';
import { isResponseSuccess } from '@/lib/api-helper';
import { formatNumber } from '@/lib/helper';
interface ClosingProductionDataTabContentProps {
projectFlockId: number;
}
const ClosingProductionDataTabContent = ({
projectFlockId,
}: ClosingProductionDataTabContentProps) => {
const searchParams = useSearchParams();
const kandangId = searchParams.get('kandangId');
const { data: productionData, isLoading } = useSWR(
`${ClosingApi.basePath}/${projectFlockId}/production-data?kandang_id=${kandangId ? `${kandangId}` : ''}`,
() => ClosingApi.getProductionData(projectFlockId, Number(kandangId))
);
if (isLoading) {
return (
<div className='w-full flex justify-center py-8'>
<span className='loading loading-spinner loading-lg' />
</div>
);
}
if (!productionData || !isResponseSuccess(productionData)) {
return (
<div className='w-full text-center py-8 text-gray-500'>
Gagal memuat data produksi.
</div>
);
}
const { purchase, sales, performance } = productionData.data;
// Helper for consistent row styling
const DataRow = ({
label,
value,
unit = '',
valueClassName = 'font-bold text-gray-800',
unitClassName = 'text-gray-500 w-12 text-right',
}: {
label: string;
value: string | number;
unit?: string;
valueClassName?: string;
unitClassName?: string;
}) => (
<div className='flex justify-between items-center py-1'>
<span className='text-gray-500 text-sm font-medium w-1/2'>{label}</span>
<div className='flex gap-2 w-1/2 justify-end items-center'>
<span className={valueClassName}>{value}</span>
{unit && <span className={unitClassName}>{unit}</span>}
</div>
</div>
);
return (
<div className='w-full rounded-xl p-8 shadow-sm'>
<h2 className='text-lg font-bold mb-8 text-gray-800'>Data Produksi</h2>
<div className='grid grid-cols-1 lg:grid-cols-2 gap-x-24 gap-y-12 relative'>
{/* Left Column */}
<div className='space-y-10'>
{/* Purchase Section */}
<section>
<h3 className='font-bold text-gray-700 mb-4 text-base'>
Pembelian
</h3>
<div className='space-y-1'>
<DataRow
label='Populasi Awal'
value={formatNumber(purchase.initial_population)}
unit='Ekor'
/>
<DataRow
label='Claim Culling'
value={formatNumber(purchase.claim_culling)}
unit='Ekor'
/>
<DataRow
label='Populasi Akhir'
value={formatNumber(purchase.final_population)}
unit='Ekor'
/>
<DataRow
label='Pakan Masuk'
value={formatNumber(purchase.feed_in)}
unit='Kg'
/>
<DataRow
label='Pakan Terpakai'
value={formatNumber(purchase.feed_used)}
unit='Kg'
/>
</div>
</section>
{/* Sales Section */}
<section>
<h3 className='font-bold text-gray-700 mb-4 text-base'>
Penjualan
</h3>
<div className='space-y-4'>
{/* Chicken Sales */}
<div className='space-y-1'>
<DataRow
label='Penjualan (Ekor)'
value={formatNumber(sales.chicken.sales_population)}
unit='Ekor'
/>
<DataRow
label='Penjualan (Kg)'
value={formatNumber(sales.chicken.sales_weight)}
unit='Kg'
/>
<DataRow
label='Bobot Rata-Rata'
value={formatNumber(sales.chicken.avg_weight)}
unit='Kg/Ekor'
/>
<DataRow
label='Harga Jual Rata-Rata'
value={formatNumber(sales.chicken.avg_selling_price)}
unit='Rupiah'
/>
</div>
{/* Egg Sales (if available) */}
{sales.egg && (
<>
<div className='h-px bg-gray-100 my-2' />
<div className='space-y-1'>
<DataRow
label='Telur (Butir)'
value={formatNumber(sales.egg.egg_pieces)}
unit='Butir'
/>
<DataRow
label='Telur (Kg)'
value={formatNumber(sales.egg.egg_mass)}
unit='Kg'
/>
<DataRow
label='Berat Telur Rata-Rata'
value={formatNumber(sales.egg.avg_egg_weight)}
unit='Kg'
/>
<DataRow
label='Harga Jual Telur Rata-Rata'
value={formatNumber(sales.egg.avg_selling_price)}
unit='Rupiah'
/>
</div>
</>
)}
</div>
</section>
</div>
{/* Divider Line (Absolute centered) */}
<div className='hidden lg:block absolute left-1/2 top-0 bottom-0 w-px bg-gray-200 -translate-x-1/2' />
{/* Right Column */}
<div className='space-y-10 flex flex-col h-full'>
{/* Performance Section */}
<section>
<h3 className='font-bold text-gray-700 mb-4 text-base'>
Performance
</h3>
<div className='space-y-1'>
<DataRow
label='Deplesi'
value={formatNumber(performance.depletion)}
unit='Ekor'
/>
<DataRow
label='Umur'
value={formatNumber(performance.age_day)}
unit='Hari'
/>
<DataRow
label='Mortalitas Std'
value={formatNumber(performance.mor_std)}
unitClassName='hidden'
/>
<DataRow
label='Mortalitas Act'
value={formatNumber(performance.mor_act)}
unitClassName='hidden'
/>
<DataRow
label='DEFF Mortalitas'
value={formatNumber(performance.mor_diff)}
unitClassName='hidden'
/>
{/* <DataRow
label='AWG Std'
value={formatNumber(performance.awg_std)}
unit='Gr/Hari'
/>
<DataRow
label='AWG Act'
value={formatNumber(performance.awg_act)}
unit='Gr/Hari'
/> */}
<DataRow
label='Feed Intake Std'
value={formatNumber(performance.feed_intake_std)}
unitClassName='hidden'
/>
<DataRow
label='Feed Intake Act'
value={formatNumber(performance.feed_intake)}
unitClassName='hidden'
/>
<DataRow
label='FCR Std'
value={formatNumber(performance.fcr_std)}
unitClassName='hidden'
/>
<DataRow
label='FCR Act'
value={formatNumber(performance.fcr_act)}
unitClassName='hidden'
/>
<DataRow
label='DEFF FCR'
value={formatNumber(performance.fcr_diff)}
unitClassName='hidden'
/>
{/* Laying Specific Fields */}
{performance.hen_day_act !== undefined && (
<>
<DataRow
label='Hen Day Std'
value={formatNumber(performance.hen_day_std!)}
unit='%'
/>
<DataRow
label='Hen Day Act'
value={formatNumber(performance.hen_day_act)}
unit='%'
/>
</>
)}
{performance.egg_mass !== undefined && (
<>
<DataRow
label='Egg Mass Std'
value={formatNumber(performance.egg_mass_std!)}
unit='Kg'
/>
<DataRow
label='Egg Mass Act'
value={formatNumber(performance.egg_mass)}
unit='Kg'
/>
</>
)}
{performance.egg_weight !== undefined && (
<>
<DataRow
label='Egg Weight Std'
value={formatNumber(performance.egg_weight_std!)}
unit='Gr'
/>
<DataRow
label='Egg Weight Act'
value={formatNumber(performance.egg_weight)}
unit='Gr'
/>
</>
)}
{performance.hen_housed_act !== undefined && (
<>
<DataRow
label='Hen Housed Std'
value={formatNumber(performance.hen_housed_std!)}
unit='%'
/>
<DataRow
label='Hen Housed Act'
value={formatNumber(performance.hen_housed_act)}
unit='%'
/>
</>
)}
</div>
</section>
</div>
</div>
</div>
);
};
export default ClosingProductionDataTabContent;
@@ -1,268 +0,0 @@
'use client';
import Card from '@/components/Card';
import Table from '@/components/Table';
import { formatCurrency, formatDate, formatNumber } from '@/lib/helper';
import {
RowSapronakCalculation,
TotalSapronakCalculation,
} from '@/types/api/closing';
import { ColumnDef } from '@tanstack/react-table';
import { useMemo } from 'react';
import useSWR from 'swr';
import { ClosingApi } from '@/services/api/closing';
import { isResponseSuccess } from '@/lib/api-helper';
import { ClosingGeneralInformation } from '@/types/api/closing';
import { useSearchParams } from 'next/navigation';
interface ClosingSapronakCalculationTableProps {
projectFlockId: number;
closingGeneralInformation?: ClosingGeneralInformation;
}
const ClosingSapronakCalculationTable = ({
projectFlockId,
closingGeneralInformation,
}: ClosingSapronakCalculationTableProps) => {
const searchParams = useSearchParams();
const kandangId = searchParams.get('kandangId');
const { data: sapronakCalculation, isLoading } = useSWR(
`/closing/sapronak-calculation/${projectFlockId}${kandangId ? `/${kandangId}` : ''}`,
() => ClosingApi.getPerhitunganSapronak(projectFlockId, Number(kandangId)),
{
keepPreviousData: true,
}
);
// Helper function to create columns with footer support
const createColumns = (
total?: TotalSapronakCalculation
): ColumnDef<RowSapronakCalculation>[] => [
{
header: 'Tanggal',
accessorKey: 'date',
cell: (props) =>
props.row.original.date
? formatDate(props.row.original.date, 'DD MMM YYYY')
: '-',
footer: 'Total',
},
{
header: 'No. Referensi',
accessorKey: 'reference_number',
cell: (props) => (props.row.original.reference_number as string) || '-',
footer: '',
},
{
header: 'QTY Masuk',
accessorKey: 'qty_in',
cell: (props) =>
props.row.original.qty_in
? formatNumber(props.row.original.qty_in as number)
: '0',
footer: total
? () => (
<div className='font-semibold text-gray-900'>
{total?.qty_in ? formatNumber(total?.qty_in) : '0'}
</div>
)
: '',
},
{
header: 'QTY Keluar',
accessorKey: 'qty_out',
cell: (props) =>
props.row.original.qty_out
? formatNumber(props.row.original.qty_out as number)
: '0',
footer: total
? () => (
<div className='font-semibold text-gray-900'>
{total?.qty_out ? formatNumber(total?.qty_out) : '0'}
</div>
)
: '',
},
{
header: 'QTY Pakai',
accessorKey: 'qty_used',
cell: (props) =>
props.row.original.qty_used
? formatNumber(props.row.original.qty_used as number)
: '0',
footer: total
? () => (
<div className='font-semibold text-gray-900'>
{total?.qty_used ? formatNumber(total?.qty_used) : '0'}
</div>
)
: '',
},
{
header: 'Uraian',
accessorKey: 'description',
cell: (props) => (props.row.original.description as string) || '-',
footer: '',
},
{
header: 'Kategori Produk',
accessorKey: 'product_category',
cell: (props) => (props.row.original.product_category as string) || '-',
footer: '',
},
{
header: 'Harga Beli/Qty (Rp)',
accessorKey: 'unit_price',
cell: (props) =>
props.row.original.unit_price
? formatCurrency(props.row.original.unit_price as number)
: '-',
footer: total
? () => (
<div className='font-semibold text-gray-900'>
{total?.avg_unit_price
? formatCurrency(total?.avg_unit_price)
: '-'}
</div>
)
: '',
},
{
header: 'Total Harga (Rp)',
accessorKey: 'total_amount',
cell: (props) =>
props.row.original.total_amount
? formatCurrency(props.row.original.total_amount as number)
: '-',
footer: total
? () => (
<div className='font-semibold text-gray-900'>
{total?.total_amount ? formatCurrency(total?.total_amount) : '-'}
</div>
)
: '',
},
{
header: 'Keterangan',
accessorKey: 'notes',
cell: (props) => (props.row.original.notes as string) || '-',
footer: '',
},
];
// Memoize columns untuk setiap kategori
const docColumns = useMemo(
() =>
isResponseSuccess(sapronakCalculation)
? createColumns(sapronakCalculation.data?.doc?.total)
: createColumns(),
[sapronakCalculation]
);
const ovkColumns = useMemo(
() =>
isResponseSuccess(sapronakCalculation)
? createColumns(sapronakCalculation.data?.ovk?.total)
: createColumns(),
[sapronakCalculation]
);
const pakanColumns = useMemo(
() =>
isResponseSuccess(sapronakCalculation)
? createColumns(sapronakCalculation.data?.pakan?.total)
: createColumns(),
[sapronakCalculation]
);
return (
<div className='flex flex-col gap-4'>
{/* Table DOC jika kategori Project Flock Growing */}
<Card
title={
closingGeneralInformation?.project_type == 'GROWING'
? 'DOC'
: 'Pullet'
}
collapsible
defaultCollapsed={false}
className={{
wrapper: 'w-full',
body: 'p-4 shadow',
}}
>
<Table<RowSapronakCalculation>
data={
isResponseSuccess(sapronakCalculation)
? (sapronakCalculation.data?.doc?.rows ?? [])
: []
}
columns={docColumns}
className={{
containerClassName: 'my-4',
}}
renderFooter={
isResponseSuccess(sapronakCalculation) &&
sapronakCalculation.data?.doc?.rows.length > 0
}
/>
</Card>
<Card
title='OVK'
variant='bordered'
collapsible
defaultCollapsed={true}
className={{
wrapper: 'w-full',
}}
>
<Table<RowSapronakCalculation>
data={
isResponseSuccess(sapronakCalculation)
? (sapronakCalculation.data?.ovk?.rows ?? [])
: []
}
columns={ovkColumns}
className={{
containerClassName: 'my-4',
}}
renderFooter={
isResponseSuccess(sapronakCalculation) &&
sapronakCalculation.data?.ovk?.rows.length > 0
}
/>
</Card>
<Card
title='Pakan'
variant='bordered'
collapsible
defaultCollapsed={true}
className={{
wrapper: 'w-full',
}}
>
<Table<RowSapronakCalculation>
data={
isResponseSuccess(sapronakCalculation)
? (sapronakCalculation.data?.pakan?.rows ?? [])
: []
}
columns={pakanColumns}
className={{
containerClassName: 'my-4',
}}
renderFooter={
isResponseSuccess(sapronakCalculation) &&
sapronakCalculation.data?.pakan?.rows.length > 0
}
/>
</Card>
</div>
);
};
export default ClosingSapronakCalculationTable;
@@ -1,36 +0,0 @@
'use client';
import ClosingIncomingSapronaksTable from '@/components/pages/closing/ClosingIncomingSapronaksTable';
import ClosingOutgoingSapronaksTable from '@/components/pages/closing/ClosingOutgoingSapronaksTable';
import ClosingIncomingSapronaksSummaryTable from '@/components/pages/closing/ClosingIncomingSapronaksSummaryTable';
import ClosingOutgoingSapronaksSummaryTable from './ClosingOutgoingSapronaksSummaryTable';
interface ClosingSapronakTableProps {
projectFlockId?: number;
}
const ClosingSapronakTabContent = ({
projectFlockId,
}: ClosingSapronakTableProps) => {
return (
<div className='flex flex-col gap-4'>
{projectFlockId && (
<>
<ClosingIncomingSapronaksTable projectFlockId={projectFlockId} />
<ClosingIncomingSapronaksSummaryTable
projectFlockId={projectFlockId}
/>
<ClosingOutgoingSapronaksTable projectFlockId={projectFlockId} />
<ClosingOutgoingSapronaksSummaryTable
projectFlockId={projectFlockId}
/>
</>
)}
</div>
);
};
export default ClosingSapronakTabContent;
+344 -141
View File
@@ -1,68 +1,122 @@
'use client';
import { ChangeEventHandler, useEffect, useState } from 'react';
import { ChangeEventHandler, useEffect, useState, useMemo } from 'react';
import { usePathname } from 'next/navigation';
import { useUiStore } from '@/stores/ui/ui.store';
import useSWR from 'swr';
import { CellContext, ColumnDef, SortingState } from '@tanstack/react-table';
import { useRouter } from 'next/navigation';
import { Icon } from '@iconify/react';
import Table from '@/components/Table';
import DebouncedTextInput from '@/components/input/DebouncedTextInput';
import Button from '@/components/Button';
import SelectInput, {
OptionType,
useSelect,
} from '@/components/input/SelectInput';
import RowDropdownOptions from '@/components/table/RowDropdownOptions';
import RowCollapseOptions from '@/components/table/RowCollapseOptions';
import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper';
import SelectInput, { useSelect } from '@/components/input/SelectInput';
import PopoverButton from '@/components/popover/PopoverButton';
import PopoverContent from '@/components/popover/PopoverContent';
import RequirePermission from '@/components/helper/RequirePermission';
import StatusBadge from '@/components/helper/StatusBadge';
import Modal, { useModal } from '@/components/Modal';
import SelectInputRadio from '@/components/input/SelectInputRadio';
import { useFormik } from 'formik';
import { cn, formatCurrency, formatDate } from '@/lib/helper';
import { cn, formatDate } from '@/lib/helper';
import { isResponseSuccess } from '@/lib/api-helper';
import { useTableFilter } from '@/services/hooks/useTableFilter';
import { LocationApi } from '@/services/api/master-data';
import { Location } from '@/types/api/master-data/location';
import { ClosingApi } from '@/services/api/closing';
import { Closing } from '@/types/api/closing';
const PROJECT_STATUS_OPTIONS = [
{
value: 1,
label: 'Pengajuan',
},
{
value: 2,
label: 'Aktif',
},
];
import { Color } from '@/types/theme';
import {
ClosingFilterSchema,
ClosingFilterType,
} from '@/components/pages/closing/filter/ClosingFilter';
import ClosingTableSkeleton from '@/components/pages/closing/skeleton/ClosingTableSkeleton';
import ButtonFilter from '@/components/helper/ButtonFilter';
const RowOptionsMenu = ({
type = 'dropdown',
props,
popoverPosition = 'bottom',
detailClickHandler,
}: {
type: 'dropdown' | 'collapse';
props: CellContext<Closing, unknown>;
popoverPosition: 'bottom' | 'top';
detailClickHandler: (id: number) => void;
}) => {
const popoverId = `closing#${props.row.original.id}`;
const popoverAnchorName = `--anchor-closing#${props.row.original.id}`;
const closePopover = () => {
document.getElementById(popoverId)?.hidePopover();
};
const detailClickHandlerWrapper = () => {
detailClickHandler(props.row.original.id);
closePopover();
};
return (
<RowOptionsMenuWrapper type={type}>
<div className='w-full max-h-40 overflow-auto flex flex-col gap-1'>
<RequirePermission permissions='lti.closing.detail'>
<Button
href={`/closing/detail/?closingId=${props.row.original.id}`}
variant='ghost'
color='primary'
className='justify-start text-sm'
>
<Icon icon='mdi:eye-outline' width={16} height={16} />
Detail
</Button>
</RequirePermission>
</div>
</RowOptionsMenuWrapper>
<div className='relative'>
<PopoverButton
tabIndex={0}
variant='ghost'
color='none'
popoverTarget={popoverId}
anchorName={popoverAnchorName}
>
<Icon icon='material-symbols:more-vert' width={16} height={16} />
</PopoverButton>
<PopoverContent
id={popoverId}
anchorName={popoverAnchorName}
position={popoverPosition === 'bottom' ? 'bottom-start' : 'left'}
className='w-full max-w-40 rounded-xl border border-base-content/5 shadow-sm'
>
<div className='flex flex-col bg-base-100 rounded-xl'>
<RequirePermission permissions='lti.closing.detail'>
<Button
variant='ghost'
color='none'
onClick={detailClickHandlerWrapper}
className='p-3 justify-start text-sm font-semibold w-full'
>
<Icon icon='heroicons:eye' width={20} height={20} />
View Details
</Button>
</RequirePermission>
</div>
</PopoverContent>
</div>
);
};
const ClosingsTable = () => {
const { searchValue, setSearchValue, setTableState } = useUiStore();
const pathname = usePathname();
// ===== ROUTER =====
const router = useRouter();
// ===== STATUS BADGE COLOR HELPER =====
const getProjectStatusBadgeColor = (status: string): Color => {
const normalizedValue = status.toLowerCase();
if (normalizedValue === 'aktif') {
return 'success';
}
if (normalizedValue === 'pengajuan') {
return 'neutral';
}
return 'neutral';
};
// ===== FILTER MODAL STATE =====
const filterModal = useModal();
const {
state: tableFilterState,
updateFilter,
@@ -72,36 +126,68 @@ const ClosingsTable = () => {
} = useTableFilter({
initial: {
search: '',
nameSort: '',
transactionDate: '',
realizationDate: '',
locationId: '',
projectStatus: '',
userId: '',
// nameSort: '',
// transactionDate: '',
// realizationDate: '',
location_id: '',
project_status: '',
// userId: '',
},
paramMap: {
page: 'page',
pageSize: 'limit',
nameSort: 'sort_name',
transactionDate: 'transaction_date',
realizationDate: 'realization_date',
locationId: 'location_id',
projectStatus: 'project_status',
userId: 'user_id',
// nameSort: 'sort_name',
// transactionDate: 'transaction_date',
// realizationDate: 'realization_date',
// locationId: 'location_id',
// projectStatus: 'project_status',
// userId: 'user_id',
search: 'search',
location_id: 'location_id',
project_status: 'project_status',
},
});
// ===== FORMIK SETUP =====
const formik = useFormik<ClosingFilterType>({
initialValues: {
location_id: null,
project_status: null,
},
validationSchema: ClosingFilterSchema,
onSubmit: (values, { setSubmitting }) => {
updateFilter('location_id', values.location_id || '');
updateFilter('project_status', values.project_status || '');
filterModal.closeModal();
setSubmitting(false);
},
onReset: () => {
updateFilter('location_id', '');
updateFilter('project_status', '');
filterModal.closeModal();
},
});
// ===== DATA FETCHING =====
const { data: closings, isLoading: isLoadingClosings } = useSWR(
`${ClosingApi.basePath}${getTableFilterQueryString()}`,
ClosingApi.getAllFetcher
);
const data = useMemo(
() =>
isResponseSuccess(closings) ? (closings?.data as Closing[]) || [] : [],
[closings]
);
// ===== PAGINATION & STATE =====
const [sorting, setSorting] = useState<SortingState>([]);
const [rowSelection, setRowSelection] = useState<Record<string, boolean>>({});
// ===== TABLE COLUMNS =====
const closingsColumns: ColumnDef<Closing>[] = [
{
header: '#',
header: 'No',
cell: (props) => props.row.index + 1,
},
{
@@ -133,6 +219,19 @@ const ClosingsTable = () => {
{
accessorKey: 'project_status',
header: 'Status',
cell: (props) => {
const status = props.row.original.project_status;
const badgeColor = getProjectStatusBadgeColor(status);
return (
<StatusBadge
color={badgeColor}
text={status}
className={{
badge: 'whitespace-nowrap',
}}
/>
);
},
},
{
header: 'Aksi',
@@ -142,27 +241,24 @@ const ClosingsTable = () => {
const currentRowRelativeIndex =
currentPageRows.findIndex((r) => r.id === props.row.id) + 1;
const isLast2Rows = currentRowRelativeIndex > currentPageSize - 3;
const isLast2Rows = currentRowRelativeIndex > currentPageSize - 2;
const detailClickHandler = (id: number) => {
router.push(`/closing/detail/?closingId=${id}`);
};
return (
<>
{currentPageSize > 3 && (
<RowDropdownOptions isLast2Rows={isLast2Rows}>
<RowOptionsMenu type='dropdown' props={props} />
</RowDropdownOptions>
)}
{currentPageSize <= 3 && (
<RowCollapseOptions>
<RowOptionsMenu type='collapse' props={props} />
</RowCollapseOptions>
)}
</>
<RowOptionsMenu
props={props}
detailClickHandler={detailClickHandler}
popoverPosition={isLast2Rows ? 'top' : 'bottom'}
/>
);
},
},
];
// ===== LOCATION OPTIONS =====
const {
setInputValue: setLocationInputValue,
options: locationOptions,
@@ -170,115 +266,222 @@ const ClosingsTable = () => {
loadMore: loadMoreLocations,
} = useSelect<Location>(LocationApi.basePath, 'id', 'name');
const [selectedLocation, setSelectedLocation] = useState<OptionType | null>(
null
// ===== PROJECT STATUS OPTIONS =====
const projectStatusOptions = useMemo(
() => [
{ value: '1', label: 'Pengajuan' },
{ value: '2', label: 'Aktif' },
],
[]
);
const locationChangeHandler = (val: OptionType | OptionType[] | null) => {
setSelectedLocation(val as OptionType);
updateFilter(
'locationId',
val ? ((val as OptionType).value as string) : ''
// ===== FILTER HELPERS =====
const locationIdValue = useMemo(() => {
if (!formik.values.location_id) return null;
return (
locationOptions.find(
(opt) => String(opt.value) === formik.values.location_id
) || null
);
};
}, [formik.values.location_id, locationOptions]);
const [selectedProjectStatus, setSelectedProjectStatus] =
useState<OptionType | null>(null);
const projectStatusChangeHandler = (
val: OptionType | OptionType[] | null
) => {
setSelectedProjectStatus(val as OptionType);
updateFilter(
'projectStatus',
val ? ((val as OptionType).value as string) : ''
const projectStatusValue = useMemo(() => {
if (!formik.values.project_status) return null;
return (
projectStatusOptions.find(
(opt) => opt.value === formik.values.project_status
) || null
);
};
}, [formik.values.project_status, projectStatusOptions]);
useEffect(() => {
updateFilter('search', searchValue);
}, [searchValue, updateFilter]);
useEffect(() => {
setTableState('closing-table', pathname);
}, [pathname, setTableState]);
// ===== SEARCH CHANGE HANDLER =====
const searchChangeHandler: ChangeEventHandler<HTMLInputElement> = (e) => {
setSearchValue(e.target.value);
updateFilter('search', e.target.value);
};
// ===== HANDLE FILTER MODAL OPEN =====
const handleFilterModalOpen = () => {
filterModal.openModal();
formik.validateForm();
};
// track sorting
useEffect(() => {
const isNameSorted = sorting.find((sortItem) => sortItem.id === 'name');
if (!isNameSorted) {
updateFilter('nameSort', '');
// updateFilter('nameSort', '');
} else {
updateFilter('nameSort', isNameSorted.desc ? 'desc' : 'asc');
// updateFilter('nameSort', isNameSorted.desc ? 'desc' : 'asc');
}
}, [sorting, updateFilter]);
}, [sorting]);
return (
<>
<div className='w-full p-0 sm:p-4'>
<div className='flex flex-col gap-2 mb-4'>
<div className='flex flex-col gap-2 mb-4'>
<div className='w-full flex flex-col sm:flex-row justify-end items-end sm:items-center gap-4'>
<div className='w-full'>
<div className='flex flex-col mb-4'>
<div className='relative w-full p-3 pt-0 px-0 flex flex-row justify-between gap-3 flex-wrap after:absolute after:bottom-0 after:left-0 after:right-0 after:-mx-4 after:border-b after:border-base-content/10'>
<div className='flex flex-1 flex-row justify-start sm:justify-end items-center gap-3 flex-wrap'>
<DebouncedTextInput
name='search'
placeholder='Cari Closing'
value={tableFilterState.search}
placeholder='Search'
value={tableFilterState.search ?? ''}
onChange={searchChangeHandler}
className={{ wrapper: 'sm:max-w-3xs' }}
/>
</div>
<div className='grid grid-cols-12 justify-end gap-2'>
<SelectInput
label='Lokasi'
options={locationOptions}
isLoading={isLoadingLocationOptions}
value={selectedLocation}
onChange={locationChangeHandler}
onInputChange={setLocationInputValue}
onMenuScrollToBottom={loadMoreLocations}
isClearable
startAdornment={
<Icon
icon='heroicons:magnifying-glass'
width={20}
height={20}
/>
}
className={{
wrapper: 'col-span-12 sm:col-span-6',
wrapper: 'w-full min-w-24 max-w-3xs',
inputWrapper: 'rounded-xl! shadow-button-soft',
input:
'placeholder:font-semibold placeholder:text-base-content/50',
}}
/>
<SelectInput
label='Status Project'
placeholder='Pilih Status'
options={PROJECT_STATUS_OPTIONS}
value={selectedProjectStatus}
onChange={projectStatusChangeHandler}
isClearable
className={{
wrapper: 'col-span-12 sm:col-span-6',
}}
<ButtonFilter
values={tableFilterState}
excludeFields={['page', 'pageSize', 'search']}
onClick={handleFilterModalOpen}
className='px-3 py-2.5'
/>
</div>
</div>
</div>
<Table<Closing>
data={isResponseSuccess(closings) ? closings?.data : []}
columns={closingsColumns}
pageSize={tableFilterState.pageSize}
onPageSizeChange={setPageSize}
rowOptions={[10, 20, 50, 100]}
page={isResponseSuccess(closings) ? closings?.meta?.page : 0}
totalItems={
isResponseSuccess(closings) ? closings?.meta?.total_results : 0
}
onPageChange={setPage}
isLoading={isLoadingClosings}
sorting={sorting}
setSorting={setSorting}
rowSelection={rowSelection}
setRowSelection={setRowSelection}
className={{
containerClassName: cn({
'w-full mb-20':
isResponseSuccess(closings) && closings?.data?.length === 0,
}),
}}
/>
{isLoadingClosings ? (
<div className='w-full flex flex-row justify-center items-center p-4'>
<span className='loading loading-spinner loading-xl' />
</div>
) : data.length === 0 ? (
<div className='mt-3'>
<ClosingTableSkeleton
columns={closingsColumns}
icon={
<Icon
icon='heroicons:document-text'
className='text-white'
width={20}
height={20}
/>
}
/>
</div>
) : (
<Table<Closing>
data={isResponseSuccess(closings) ? closings?.data : []}
columns={closingsColumns}
pageSize={tableFilterState.pageSize}
onPageSizeChange={setPageSize}
rowOptions={[10, 20, 50, 100]}
page={isResponseSuccess(closings) ? closings?.meta?.page : 0}
totalItems={
isResponseSuccess(closings) ? closings?.meta?.total_results : 0
}
onPageChange={setPage}
isLoading={isLoadingClosings}
sorting={sorting}
setSorting={setSorting}
rowSelection={rowSelection}
setRowSelection={setRowSelection}
className={{
containerClassName: cn('mt-3 mb-0'),
headerColumnClassName: 'text-nowrap',
}}
/>
)}
</div>
</div>
{/* Filter Modal */}
<Modal
ref={filterModal.ref}
className={{
modal: 'p-0',
modalBox: 'p-0 rounded-[0.875rem] xl:max-w-4/12 max-w-sm',
}}
>
{/* Modal Header */}
<div className='flex items-center justify-between gap-2 border-b border-base-content/10 p-4'>
<div className='flex items-center gap-2 text-primary'>
<Icon icon='heroicons:funnel' width={20} height={20} />
<h3 className='font-medium text-sm'>Filter Data</h3>
</div>
<Button
variant='link'
onClick={filterModal.closeModal}
className='text-base-content/50 hover:text-base-content transition-colors cursor-pointer'
>
<Icon icon='heroicons:x-mark' width={20} height={20} />
</Button>
</div>
<form onSubmit={formik.handleSubmit} onReset={formik.handleReset}>
<div className='p-4 flex flex-col gap-1.5'>
<SelectInput
label='Lokasi'
placeholder='Pilih Lokasi'
options={locationOptions}
value={locationIdValue}
onChange={(val) => {
if (!Array.isArray(val)) {
formik.setFieldValue(
'location_id',
val?.value ? String(val.value) : null
);
}
}}
onInputChange={setLocationInputValue}
isLoading={isLoadingLocationOptions}
isClearable
onMenuScrollToBottom={loadMoreLocations}
className={{ wrapper: 'w-full' }}
/>
<SelectInputRadio
label='Status Project'
placeholder='Pilih Status'
options={projectStatusOptions}
value={projectStatusValue}
onChange={(val) => {
if (!Array.isArray(val)) {
formik.setFieldValue('project_status', val?.value || null);
}
}}
className={{ wrapper: 'w-full' }}
isClearable={true}
/>
</div>
{/* Modal Footer */}
<div className='flex justify-between items-center gap-4 p-4 border-t border-base-content/10 bg-gray-50'>
<Button
type='reset'
variant='soft'
className='rounded-lg text-base-content/65 bg-transparent border-none hover:bg-base-content/10 hover:text-base-content/65 transition-colors px-3 py-2'
>
Reset Filter
</Button>
<Button
type='submit'
className='min-w-40 text-sm rounded-lg py-3 text-white font-semibold'
disabled={!formik.isValid || formik.isSubmitting}
>
Apply Filter
</Button>
</div>
</form>
</Modal>
</>
);
};
@@ -0,0 +1,13 @@
import * as yup from 'yup';
export type ClosingFilterType = {
location_id: string | null;
project_status: string | null;
};
export const ClosingFilterSchema = yup.object({
location_id: yup.string().nullable(),
project_status: yup.string().nullable(),
});
export type ClosingFilterValues = yup.InferType<typeof ClosingFilterSchema>;
@@ -1,109 +0,0 @@
'use client';
import React, { useMemo } from 'react';
import { ColumnDef } from '@tanstack/react-table';
import Table from '@/components/Table';
import Card from '@/components/Card';
import { formatCurrency } from '@/lib/helper';
import { BaseHppExpedition, BaseExpeditionCost } from '@/types/api/closing';
interface HppExpeditionReportTableProps {
type?: 'detail';
initialValues?: BaseHppExpedition;
}
const HppExpeditionReportTable = ({
initialValues,
}: HppExpeditionReportTableProps) => {
const costOfRevenueExpeditionData: BaseExpeditionCost[] = useMemo(() => {
return initialValues?.expedition_costs || [];
}, [initialValues]);
const totals = useMemo(() => {
const totalHpp = initialValues?.total_hpp_amount || 0;
return {
totalHpp,
};
}, [initialValues]);
const costOfRevenueExpeditionColumns: ColumnDef<BaseExpeditionCost>[] =
useMemo(
() => [
{
id: 'id',
accessorKey: 'id',
header: 'No',
cell: (props) => {
return <div>{props.row.index + 1}</div>;
},
footer: () => (
<div className='font-semibold text-gray-900'>
Total HPP Ekspedisi
</div>
),
},
{
id: 'expedition_vendor_name',
accessorKey: 'expedition_vendor_name',
header: 'Nama Ekspedisi',
cell: (props) => props.getValue() || '-',
},
{
id: 'hpp_amount',
accessorKey: 'hpp_amount',
header: 'HPP Ekspedisi',
cell: (props) => {
const value = props.getValue() as number;
return <div className='text-right'>{formatCurrency(value)}</div>;
},
footer: () => (
<div className='text-right font-semibold text-gray-900'>
{formatCurrency(totals.totalHpp)}
</div>
),
},
],
[totals]
);
return (
<>
<section className='w-full'>
<div className='p-4'>
<h2 className='text-xl font-semibold mb-4'>HPP Ekspedisi</h2>
<Card
className={{
wrapper: 'w-full bg-base-100',
body: 'p-0',
}}
>
<Table
data={costOfRevenueExpeditionData}
columns={costOfRevenueExpeditionColumns}
renderFooter={costOfRevenueExpeditionData.length > 0}
className={{
tableWrapperClassName: 'overflow-x-auto',
tableClassName: 'w-full table-auto text-sm',
headerRowClassName: 'border-b border-b-gray-200',
headerColumnClassName:
'px-4 py-3 text-xs font-semibold text-gray-500 last:flex last:flex-row last:justify-end whitespace-nowrap',
bodyRowClassName:
'hover:bg-gray-50 transition-colors border-b border-l border-r border-b-gray-200 border-l-gray-200 border-r-gray-200',
bodyColumnClassName:
'px-4 py-3 text-xs text-gray-900 whitespace-nowrap',
tableFooterClassName:
'bg-gray-100 font-semibold border border-gray-200',
footerRowClassName: 'border-t-2 border-gray-300',
footerColumnClassName:
'px-4 py-3 text-xs text-gray-900 whitespace-nowrap',
}}
/>
</Card>
</div>
</section>
</>
);
};
export default HppExpeditionReportTable;
@@ -0,0 +1,36 @@
import DataStateSkeleton from '@/components/helper/skeleton/DataStateSkeleton';
import Table from '@/components/Table';
import { ColumnDef } from '@tanstack/react-table';
const ClosingTabSkeleton = <T extends object>({
columns,
icon,
title,
subtitle,
}: {
columns: ColumnDef<T, unknown>[];
icon: React.ReactNode;
title: string;
subtitle: string;
}) => {
return (
<div className='relative size-full'>
<Table
data={[]}
columns={columns}
isLoading={true}
className={{
skeletonCellClassName: 'animate-none w-full h-5 bg-base-content/4',
headerColumnClassName: 'whitespace-nowrap',
containerClassName: 'mb-0 overflow-hidden',
tableWrapperClassName: 'overflow-hidden',
}}
/>
<div className='absolute inset-0 flex items-center justify-center'>
<DataStateSkeleton icon={icon} title={title} description={subtitle} />
</div>
</div>
);
};
export default ClosingTabSkeleton;
@@ -0,0 +1,37 @@
import DataStateSkeleton from '@/components/helper/skeleton/DataStateSkeleton';
import Table from '@/components/Table';
import { Closing } from '@/types/api/closing';
import { ColumnDef } from '@tanstack/react-table';
const ClosingTableSkeleton = ({
columns,
icon,
title = 'No Data Available',
subtitle = 'There is no closing data displayed. Enter closing data to get started.',
}: {
columns: ColumnDef<Closing>[];
icon: React.ReactNode;
title?: string;
subtitle?: string;
}) => {
return (
<div className='relative size-full'>
<Table
data={[]}
columns={columns}
isLoading={true}
className={{
skeletonCellClassName: 'animate-none w-full h-5 bg-base-content/4',
headerColumnClassName: 'whitespace-nowrap',
containerClassName: 'mb-0 overflow-hidden',
tableWrapperClassName: 'overflow-hidden',
}}
/>
<div className='absolute inset-0 flex items-center justify-center'>
<DataStateSkeleton icon={icon} title={title} description={subtitle} />
</div>
</div>
);
};
export default ClosingTableSkeleton;
@@ -0,0 +1,40 @@
import { Icon } from '@iconify/react';
import Card from '@/components/Card';
import DataStateSkeleton from '@/components/helper/skeleton/DataStateSkeleton';
const FinanceClosingSkeleton = ({
title = 'Data Keuangan Belum Tersedia',
subtitle = 'Tidak ada data keuangan untuk periode ini.',
iconName = 'heroicons:chart-bar',
}: {
title?: string;
subtitle?: string;
iconName?: string;
}) => {
return (
<Card
variant='bordered'
className={{
wrapper: 'w-full',
body: 'p-8',
}}
>
<div className='flex items-center justify-center p-8'>
<DataStateSkeleton
icon={
<Icon
icon={iconName}
className='text-white'
width={20}
height={20}
/>
}
title={title}
description={subtitle}
/>
</div>
</Card>
);
};
export default FinanceClosingSkeleton;
@@ -0,0 +1,29 @@
import { Icon } from '@iconify/react';
import ClosingTabSkeleton from './ClosingTabSkeleton';
import { BaseExpeditionCost } from '@/types/api/closing';
import { ColumnDef } from '@tanstack/react-table';
const HppExpeditionClosingSkeleton = ({
columns,
title = 'Data HPP Ekspedisi Belum Tersedia',
subtitle = 'Tidak ada data HPP ekspedisi untuk periode ini.',
iconName = 'heroicons:chart-bar',
}: {
columns: ColumnDef<BaseExpeditionCost>[];
title?: string;
subtitle?: string;
iconName?: string;
}) => {
return (
<ClosingTabSkeleton<BaseExpeditionCost>
columns={columns}
icon={
<Icon icon={iconName} className='text-white' width={20} height={20} />
}
title={title}
subtitle={subtitle}
/>
);
};
export default HppExpeditionClosingSkeleton;
@@ -0,0 +1,29 @@
import { Icon } from '@iconify/react';
import ClosingTabSkeleton from './ClosingTabSkeleton';
import { Overhead } from '@/types/api/closing';
import { ColumnDef } from '@tanstack/react-table';
const OverheadClosingSkeleton = ({
columns,
title = 'Data Overhead Belum Tersedia',
subtitle = 'Tidak ada data overhead untuk periode ini.',
iconName = 'heroicons:chart-bar',
}: {
columns: ColumnDef<Overhead>[];
title?: string;
subtitle?: string;
iconName?: string;
}) => {
return (
<ClosingTabSkeleton<Overhead>
columns={columns}
icon={
<Icon icon={iconName} className='text-white' width={20} height={20} />
}
title={title}
subtitle={subtitle}
/>
);
};
export default OverheadClosingSkeleton;
@@ -0,0 +1,33 @@
import { Icon } from '@iconify/react';
import DataStateSkeleton from '@/components/helper/skeleton/DataStateSkeleton';
const ProductionDataClosingSkeleton = ({
title = 'Data Produksi Belum Tersedia',
subtitle = 'Tidak ada data produksi untuk periode ini.',
iconName = 'heroicons:chart-bar',
}: {
title?: string;
subtitle?: string;
iconName?: string;
}) => {
return (
<div className='w-full rounded-xl p-8 shadow-sm'>
<div className='flex items-center justify-center p-12'>
<DataStateSkeleton
icon={
<Icon
icon={iconName}
className='text-white'
width={20}
height={20}
/>
}
title={title}
description={subtitle}
/>
</div>
</div>
);
};
export default ProductionDataClosingSkeleton;
@@ -0,0 +1,29 @@
import { Icon } from '@iconify/react';
import ClosingTabSkeleton from './ClosingTabSkeleton';
import { BaseSales } from '@/types/api/closing';
import { ColumnDef } from '@tanstack/react-table';
const SalesClosingSkeleton = ({
columns,
title = 'Data Penjualan Belum Tersedia',
subtitle = 'Tidak ada data penjualan untuk periode ini.',
iconName = 'heroicons:chart-bar',
}: {
columns: ColumnDef<BaseSales>[];
title?: string;
subtitle?: string;
iconName?: string;
}) => {
return (
<ClosingTabSkeleton<BaseSales>
columns={columns}
icon={
<Icon icon={iconName} className='text-white' width={20} height={20} />
}
title={title}
subtitle={subtitle}
/>
);
};
export default SalesClosingSkeleton;
@@ -0,0 +1,29 @@
import { Icon } from '@iconify/react';
import ClosingTabSkeleton from './ClosingTabSkeleton';
import { RowSapronakCalculation } from '@/types/api/closing';
import { ColumnDef } from '@tanstack/react-table';
const SapronakCalculationClosingSkeleton = ({
columns,
title = 'Data Perhitungan Sapronak Belum Tersedia',
subtitle = 'Tidak ada data perhitungan sapronak untuk periode ini.',
iconName = 'heroicons:chart-bar',
}: {
columns: ColumnDef<RowSapronakCalculation>[];
title?: string;
subtitle?: string;
iconName?: string;
}) => {
return (
<ClosingTabSkeleton<RowSapronakCalculation>
columns={columns}
icon={
<Icon icon={iconName} className='text-white' width={20} height={20} />
}
title={title}
subtitle={subtitle}
/>
);
};
export default SapronakCalculationClosingSkeleton;
@@ -0,0 +1,40 @@
import { Icon } from '@iconify/react';
import ClosingTabSkeleton from './ClosingTabSkeleton';
import { ColumnDef } from '@tanstack/react-table';
const SapronakClosingSkeleton = <T extends object>({
columns,
type = 'incoming',
title,
subtitle,
iconName = 'heroicons:chart-bar',
}: {
columns: ColumnDef<T, unknown>[];
type?: 'incoming' | 'outgoing';
title?: string;
subtitle?: string;
iconName?: string;
}) => {
const defaultTitle =
type === 'incoming'
? 'Data Sapronak Masuk Belum Tersedia'
: 'Data Sapronak Keluar Belum Tersedia';
const defaultSubtitle =
type === 'incoming'
? 'Tidak ada data sapronak masuk untuk periode ini.'
: 'Tidak ada data sapronak keluar untuk periode ini.';
return (
<ClosingTabSkeleton<T>
columns={columns}
icon={
<Icon icon={iconName} className='text-white' width={20} height={20} />
}
title={title || defaultTitle}
subtitle={subtitle || defaultSubtitle}
/>
);
};
export default SapronakClosingSkeleton;
@@ -0,0 +1,13 @@
import FinanceClosingTable from '@/components/pages/closing/table/FinanceClosingTable';
const FinanceClosingTab = ({ projectFlockId }: { projectFlockId: number }) => {
return (
<div className='flex flex-col gap-4'>
{projectFlockId && (
<FinanceClosingTable projectFlockId={projectFlockId} />
)}
</div>
);
};
export default FinanceClosingTab;
@@ -0,0 +1,19 @@
import HppExpeditionClosingTable from '@/components/pages/closing/table/HppExpeditionClosingTable';
interface HppExpeditionClosingTabProps {
projectFlockId: number;
}
const HppExpeditionClosingTab = ({
projectFlockId,
}: HppExpeditionClosingTabProps) => {
return (
<div className='flex flex-col gap-4'>
{projectFlockId && (
<HppExpeditionClosingTable projectFlockId={projectFlockId} />
)}
</div>
);
};
export default HppExpeditionClosingTab;
@@ -1,22 +1,22 @@
import ClosingOverheadTable from '@/components/pages/closing/ClosingOverheadTable';
import OverheadClosingTable from '@/components/pages/closing/table/OverheadClosingTable';
import { ClosingGeneralInformation } from '@/types/api/closing';
import { ProjectFlockKandang } from '@/types/api/production/project-flock-kandang';
interface ClosingOverheadTabContentProps {
interface OverheadClosingTabProps {
projectFlockId: number;
generalInformation?: ClosingGeneralInformation;
kandangData?: ProjectFlockKandang;
}
const ClosingOverheadTabContent = ({
const OverheadClosingTab = ({
projectFlockId,
generalInformation,
kandangData,
}: ClosingOverheadTabContentProps) => {
}: OverheadClosingTabProps) => {
return (
<div className='flex flex-col gap-4'>
{projectFlockId && (
<ClosingOverheadTable
<OverheadClosingTable
projectFlockId={projectFlockId}
generalInformation={generalInformation}
kandangData={kandangData}
@@ -26,4 +26,4 @@ const ClosingOverheadTabContent = ({
);
};
export default ClosingOverheadTabContent;
export default OverheadClosingTab;

Some files were not shown because too many files have changed in this diff Show More