diff --git a/src/app/globals.css b/src/app/globals.css index 96339bf2..fde8d2b4 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -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 { diff --git a/src/app/production/project-flock/layout.tsx b/src/app/production/project-flock/layout.tsx index 3e9a65b7..1b1b4edf 100644 --- a/src/app/production/project-flock/layout.tsx +++ b/src/app/production/project-flock/layout.tsx @@ -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({ /> - {/* Render Drawer only on /add */} - { - if (!v) router.push('/production/project-flock'); - }} - closeOnBackdropClick={isDetail ? true : false} + {/* Render Modal only on /add */} + {children}} - /> + className={{ + modalBox: 'w-full sm:w-fit p-3 rounded-xl bg-transparent shadow-none', + }} + > +
+ {isOpen && children} +
+
); } diff --git a/src/components/Alert.tsx b/src/components/Alert.tsx index 61792d0c..6b243cf9 100644 --- a/src/components/Alert.tsx +++ b/src/components/Alert.tsx @@ -1,15 +1,16 @@ -import { ReactNode } from 'react'; +import { ReactNode, Ref } from 'react'; import { cn } from '@/lib/helper'; interface AlertProps { + ref?: Ref | 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
{children}
; + return ( +
+ {children} +
+ ); }; export default Alert; diff --git a/src/components/helper/ApprovalStepsV2.tsx b/src/components/helper/ApprovalStepsV2.tsx index dc21453e..0288b0f9 100644 --- a/src/components/helper/ApprovalStepsV2.tsx +++ b/src/components/helper/ApprovalStepsV2.tsx @@ -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 = ({ )} >

- Progress Details + {title}

void; } const StatusBadge = ({ color = 'neutral', text, className, + onClick, }: StatusBadgeProps) => { return ( ); @@ -73,7 +73,7 @@ const DrawerHeader = ({ return (
@@ -82,7 +82,7 @@ const DrawerHeader = ({ {renderLeftIcon()} {showDivider && subtitle && ( -
+
)} {subtitle && ( diff --git a/src/components/helper/form/FormErrors.tsx b/src/components/helper/form/FormErrors.tsx index 60ec1687..e1de97ed 100644 --- a/src/components/helper/form/FormErrors.tsx +++ b/src/components/helper/form/FormErrors.tsx @@ -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(null); + + useEffect(() => { + if (formErrorList.length > 0) { + alertRef.current?.scrollIntoView({ + behavior: 'smooth', + block: 'start', + }); + } + }, [formErrorList.length]); + if (formErrorList.length === 0) return null; return (
@@ -539,6 +612,7 @@ const FinanceTable = () => { label='Jenis Transaksi' value={selectedTransactionType} onChange={transactionTypeChangeHandler} + closeMenuOnSelect={false} isClearable isMulti /> @@ -550,6 +624,7 @@ const FinanceTable = () => { onInputChange={customerInputValue} onMenuScrollToBottom={customerLoadMore} isLoading={customerIsLoadingOptions} + closeMenuOnSelect={false} isClearable isMulti /> @@ -561,31 +636,18 @@ const FinanceTable = () => { onInputChange={supplierInputValue} onMenuScrollToBottom={supplierLoadMore} isLoading={supplierIsLoadingOptions} + closeMenuOnSelect={false} isClearable isMulti /> ({ - label: - bankRawData.data.find((data) => data.id === bank?.value) - ?.alias + - ' - ' + - bankRawData.data.find((data) => data.id === bank?.value) - ?.account_number + - ' - ' + - bankRawData.data.find((data) => data.id === bank?.value) - ?.owner, - value: bank?.value, - })) - : [] - } + options={bankSelectOptions} label='Bank' value={selectedBank} onChange={bankChangeHandler} onInputChange={bankInputValue} onMenuScrollToBottom={bankLoadMore} + closeMenuOnSelect={false} isClearable isMulti /> @@ -597,22 +659,32 @@ const FinanceTable = () => { isClearable />
diff --git a/src/components/pages/finance/FinanceTableFilter.schema.ts b/src/components/pages/finance/FinanceTableFilter.schema.ts new file mode 100644 index 00000000..fecfc35d --- /dev/null +++ b/src/components/pages/finance/FinanceTableFilter.schema.ts @@ -0,0 +1,38 @@ +import * as yup from 'yup'; + +export type FinanceTableFilterType = { + search: string; + transaction_types: string; + bank_ids: string; + customer_ids: string; + supplier_ids: string; + sort_by: string; + start_date: string; + end_date: string; +}; + +export const FinanceTableFilterSchema = yup.object({ + search: yup.string().optional(), + transaction_types: yup.string().optional(), + bank_ids: yup.string().optional(), + customer_ids: yup.string().optional(), + supplier_ids: yup.string().optional(), + sort_by: yup.string().optional(), + start_date: yup.string().optional(), + end_date: yup + .string() + .optional() + .test( + 'is-greater-than-start', + 'Tanggal akhir tidak boleh masa lampau', + function (value) { + const { start_date } = this.parent; + if (!start_date || !value) return true; + return new Date(value) >= new Date(start_date); + } + ), +}) as yup.ObjectSchema; + +export type FinanceTableFilterValues = yup.InferType< + typeof FinanceTableFilterSchema +>; diff --git a/src/components/pages/marketing/MarketingTable.tsx b/src/components/pages/marketing/MarketingTable.tsx index e09617aa..0a35a8bc 100644 --- a/src/components/pages/marketing/MarketingTable.tsx +++ b/src/components/pages/marketing/MarketingTable.tsx @@ -623,7 +623,10 @@ const MarketingTable = () => { data={allData} columns={columns} pageSize={tableFilterState.pageSize} - page={tableFilterState.page} + page={isResponseSuccess(marketing) ? marketing?.meta?.page : 1} + totalItems={ + isResponseSuccess(marketing) ? marketing?.meta?.total_results : 0 + } isLoading={isLoadingMarketing} className={{ containerClassName: cn('p-3', { diff --git a/src/components/pages/production/chickin/form/ChickinForm.tsx b/src/components/pages/production/chickin/form/ChickinForm.tsx index d484e1c6..bd3ff57c 100644 --- a/src/components/pages/production/chickin/form/ChickinForm.tsx +++ b/src/components/pages/production/chickin/form/ChickinForm.tsx @@ -53,7 +53,7 @@ const ChickinFormKandang = ({ }; return ( - <> +
- +
); }; diff --git a/src/components/pages/production/project-flock/ProjectFlockConfirmationModal.tsx b/src/components/pages/production/project-flock/ProjectFlockConfirmationModal.tsx new file mode 100644 index 00000000..45834ee5 --- /dev/null +++ b/src/components/pages/production/project-flock/ProjectFlockConfirmationModal.tsx @@ -0,0 +1,159 @@ +'use client'; + +import { ChangeEventHandler, RefObject, useId, useState } from 'react'; +import useSWR from 'swr'; + +import ConfirmationModal, { + ConfirmationModalProps, +} from '@/components/modal/ConfirmationModal'; +import TextArea from '@/components/input/TextArea'; +import { ProjectFlockFormConfirmationTable } from '@/components/pages/production/project-flock/form/ProjectFlockForm'; + +import { ProjectFlockFormValues } from '@/components/pages/production/project-flock/form/ProjectFlockForm.schema'; +import { Color } from '@/types/theme'; +import { isResponseSuccess } from '@/lib/api-helper'; +import { KandangApi } from '@/services/api/master-data'; + +interface ProjectFlockConfirmationModalProps + extends Omit { + ref: RefObject; + type?: 'info' | 'success' | 'error'; + projectFlockIds?: number[]; + projectFlockForm?: ProjectFlockFormValues; + onClose?: () => void; + + withNote?: boolean; + noteLabel?: string; + rows?: number; + placeholder?: string; + + primaryButton?: { + text?: string; + color?: Color; + isLoading?: boolean; + onClick?: (notes: string) => void; + }; +} + +const ProjectFlockConfirmationModal = ({ + ref, + type = 'success', + projectFlockForm, + projectFlockIds, + onClose, + withNote, + rows = 4, + noteLabel, + placeholder = 'Alasan', + primaryButton, + secondaryButton, + ...props +}: ProjectFlockConfirmationModalProps) => { + const randomId = useId(); + + const [notes, setNotes] = useState(''); + + const kandangUrl = `${KandangApi.basePath}?${new URLSearchParams({ + search: '', + location_id: projectFlockForm?.location_id + ? String(projectFlockForm?.location_id) + : '', + limit: '500', + }).toString()}`; + const { + data: kandang, + isLoading: isLoadingKandang, + mutate: refreshKandang, + } = useSWR(kandangUrl, KandangApi.getAllFetcher); + + const notesChangeHandler: ChangeEventHandler = (e) => { + setNotes(e.target.value); + }; + + const closeModalHandler = () => { + onClose?.(); + ref.current?.close(); + }; + + return ( + { + if (withNote) { + primaryButton?.onClick?.(notes); + } else if (primaryButton && primaryButton?.onClick) { + primaryButton?.onClick?.(''); + } else { + closeModalHandler(); + } + + setNotes(''); + }, + }} + secondaryButton={ + secondaryButton + ? { + text: secondaryButton?.text ?? 'Cancel', + color: secondaryButton?.color ?? 'none', + onClick: (e) => { + if (secondaryButton && secondaryButton?.onClick) { + secondaryButton.onClick?.(e); + } else { + closeModalHandler(); + } + + setNotes(''); + }, + } + : undefined + } + className={{ + modalBox: 'max-h-full', + }} + {...props} + > +
+ {!projectFlockIds && projectFlockForm && ( + + )} + + {/* {projectFlockIds && + !projectFlockForm && + projectFlockIds.map((projectFlockId, idx) => ( + + ))} */} + + {withNote && ( +