diff --git a/src/components/pages/marketing/DeliveryOrderFormModal.tsx b/src/components/pages/marketing/DeliveryOrderFormModal.tsx index fe98603d..89783a9e 100644 --- a/src/components/pages/marketing/DeliveryOrderFormModal.tsx +++ b/src/components/pages/marketing/DeliveryOrderFormModal.tsx @@ -847,7 +847,8 @@ const DeliveryOrderFormModal = ({}: { initialValues?: Marketing }) => { } }} className='p-3 shadow-button-soft text-base-100 rounded-lg text-sm font-semibold' - disabled={deliveryRejected} + disabled={deliveryRejected || isLoading} + isLoading={isLoading} > {marketing?.data?.latest_approval?.step_number === 1 && 'Approve'} diff --git a/src/components/pages/marketing/MarketingFilter.tsx b/src/components/pages/marketing/MarketingFilter.tsx index 8e1ab8c0..20b4bbd3 100644 --- a/src/components/pages/marketing/MarketingFilter.tsx +++ b/src/components/pages/marketing/MarketingFilter.tsx @@ -1,6 +1,6 @@ 'use client'; -import { RefObject, useCallback, useMemo } from 'react'; +import { RefObject, useCallback, useMemo, useState } from 'react'; import { useFormik } from 'formik'; import { Icon } from '@iconify/react'; import Modal from '@/components/Modal'; @@ -9,6 +9,8 @@ import SelectInput, { OptionType, useSelect, } from '@/components/input/SelectInput'; +import DateInput from '@/components/input/DateInput'; +import SelectInputRadio from '@/components/input/SelectInputRadio'; import { MARKETING_APPROVAL_LINE } from '@/config/approval-line'; import { MarketingFilterFormValues, @@ -17,12 +19,17 @@ import { import { MarketingFilter } from '@/types/api/marketing/marketing'; import SelectInputCheckbox from '@/components/input/SelectInputCheckbox'; import { MarketingApi } from '@/services/api/marketing/marketing'; -import { CustomerApi, ProductApi } from '@/services/api/master-data'; +import { + CustomerApi, + ProductApi, + WarehouseApi, +} from '@/services/api/master-data'; import { isResponseSuccess } from '@/lib/api-helper'; import { BaseMarketing, BaseSalesOrder } from '@/types/api/marketing/marketing'; import { ProjectFlockApi } from '@/services/api/production'; import { ProjectFlock } from '@/types/api/production/project-flock'; import { Product } from '@/types/api/master-data/product'; +import { Warehouse } from '@/types/api/master-data/warehouse'; interface MarketingFilterModal { ref: RefObject; @@ -34,6 +41,10 @@ interface MarketingFilterModal { customer: OptionType | null; project_flock: OptionType | null; project_flock_kandang: OptionType | null; + warehouse: OptionType | null; + start_date: string; + end_date: string; + filter_by: OptionType | null; }; } @@ -79,6 +90,13 @@ const MarketingFilterModal = ({ 'search' ); + const { + options: warehouseOptions, + isLoadingOptions: isLoadingWarehouseOptions, + setInputValue: setWarehouseInputValue, + loadMore: loadMoreWarehouses, + } = useSelect(WarehouseApi.basePath, 'id', 'name', 'search'); + const statusOptions = [ ...MARKETING_APPROVAL_LINE.map((item) => ({ value: item.step_name.split(' ').join('_').toUpperCase(), @@ -87,6 +105,13 @@ const MarketingFilterModal = ({ { value: 'DITOLAK', label: 'Ditolak' }, ]; + const filterByOptions = [ + { value: 'so_date', label: 'Tanggal SO' }, + { value: 'created_at', label: 'Tanggal Dibuat' }, + ]; + + const [hasDateError, setHasDateError] = useState(false); + const formik = useFormik({ initialValues: initialValues || { product_ids: [], @@ -94,6 +119,10 @@ const MarketingFilterModal = ({ customer: null, project_flock: null, project_flock_kandang: null, + warehouse: null, + start_date: '', + end_date: '', + filter_by: null, }, validationSchema: MarketingFilterSchema, @@ -111,6 +140,12 @@ const MarketingFilterModal = ({ Number(values.project_flock_kandang?.value) || undefined, project_flock_kandang_name: values.project_flock_kandang?.label || undefined, + warehouse_id: Number(values.warehouse?.value) || undefined, + warehouse_name: values.warehouse?.label || undefined, + start_date: values.start_date || undefined, + end_date: values.end_date || undefined, + filter_by: values.filter_by?.value || undefined, + filter_by_name: values.filter_by?.label || undefined, }; onSubmit?.(formattedValues); @@ -133,12 +168,37 @@ const MarketingFilterModal = ({ customer: null, project_flock: null, project_flock_kandang: null, + warehouse: null, + start_date: '', + end_date: '', + filter_by: null, }, }); + setHasDateError(false); onReset?.(); closeModalHandler(); }, [resetForm, onReset, closeModalHandler]); + const handleStartDateChange = (e: React.ChangeEvent) => { + const value = e.target.value; + formik.setFieldValue('start_date', value); + if (value && formik.values.end_date) { + setHasDateError(new Date(formik.values.end_date) < new Date(value)); + } else { + setHasDateError(false); + } + }; + + const handleEndDateChange = (e: React.ChangeEvent) => { + const value = e.target.value; + formik.setFieldValue('end_date', value); + if (value && formik.values.start_date) { + setHasDateError(new Date(value) < new Date(formik.values.start_date)); + } else { + setHasDateError(false); + } + }; + const productChangeHandler = (val: OptionType | OptionType[] | null) => { formik.setFieldValue('product_ids', val as OptionType[]); }; @@ -207,6 +267,44 @@ const MarketingFilterModal = ({ {/* Modal Body */}
+
+ +
+ +
+ +
+
+ + + formik.setFieldValue( + 'filter_by', + !Array.isArray(val) ? (val ?? null) : null + ) + } + isClearable + /> + {/* select multiple product */} + + formik.setFieldValue( + 'warehouse', + !Array.isArray(val) ? (val as OptionType | null) : null + ) + } + onInputChange={setWarehouseInputValue} + onMenuScrollToBottom={loadMoreWarehouses} + />
{/* Modal Footer */} @@ -288,6 +402,7 @@ const MarketingFilterModal = ({ diff --git a/src/components/pages/marketing/MarketingTable.tsx b/src/components/pages/marketing/MarketingTable.tsx index 39384b0d..4f09642f 100644 --- a/src/components/pages/marketing/MarketingTable.tsx +++ b/src/components/pages/marketing/MarketingTable.tsx @@ -203,6 +203,12 @@ const MarketingTable = () => { project_flock_name: '', project_flock_kandang_id: '', project_flock_kandang_name: '', + warehouse_id: '', + warehouse_name: '', + start_date: '', + end_date: '', + filter_by: '', + filter_by_name: '', sort_by: '', order_by: '', }, @@ -214,6 +220,10 @@ const MarketingTable = () => { customer_id: 'customer_id', project_flock_id: 'project_flock_id', project_flock_kandang_id: 'project_flock_kandang_id', + warehouse_id: 'warehouse_id', + start_date: 'start_date', + end_date: 'end_date', + filter_by: 'filter_by', sort_by: 'sort_by', order_by: 'sort_order', }, @@ -223,6 +233,8 @@ const MarketingTable = () => { 'customer_name', 'project_flock_name', 'project_flock_kandang_name', + 'warehouse_name', + 'filter_by_name', ], persist: true, @@ -293,6 +305,16 @@ const MarketingTable = () => { values.project_flock_kandang_name ?? '', true ); + updateFilter( + 'warehouse_id', + values.warehouse_id ? values.warehouse_id.toString() : '', + true + ); + updateFilter('warehouse_name', values.warehouse_name ?? '', true); + updateFilter('start_date', values.start_date ?? '', true); + updateFilter('end_date', values.end_date ?? '', true); + updateFilter('filter_by', values.filter_by ?? '', true); + updateFilter('filter_by_name', values.filter_by_name ?? '', true); }; const [isLoadingExportingToExcel, setIsLoadingExportingToExcel] = @@ -311,6 +333,12 @@ const MarketingTable = () => { updateFilter('project_flock_name', '', true); updateFilter('project_flock_kandang_id', '', true); updateFilter('project_flock_kandang_name', '', true); + updateFilter('warehouse_id', '', true); + updateFilter('warehouse_name', '', true); + updateFilter('start_date', '', true); + updateFilter('end_date', '', true); + updateFilter('filter_by', '', true); + updateFilter('filter_by_name', '', true); }; const approveClickHandler = () => { @@ -433,6 +461,20 @@ const MarketingTable = () => { label: tableFilterState.project_flock_kandang_name, } : null, + warehouse: tableFilterState.warehouse_id + ? { + value: Number(tableFilterState.warehouse_id), + label: tableFilterState.warehouse_name, + } + : null, + start_date: tableFilterState.start_date, + end_date: tableFilterState.end_date, + filter_by: tableFilterState.filter_by + ? { + value: tableFilterState.filter_by, + label: tableFilterState.filter_by_name, + } + : null, }; const approveMarketingHandler = async (notes: string) => { @@ -707,7 +749,7 @@ const MarketingTable = () => { }, { accessorKey: 'so_date', - header: 'Tanggal', + header: 'Tanggal SO', cell: (props) => { return formatDate(props.row.original.so_date, 'DD MMM yyyy'); }, @@ -753,18 +795,17 @@ const MarketingTable = () => { cell: (props) => props.row.original.customer.name, }, { - accessorKey: 'grand_total', - accessorFn: (row) => - row.sales_order - ?.map((product) => product.total_price) - .reduce((a, b) => a + b, 0) ?? 0, - header: 'Grand Total', + accessorKey: 'grand_total_so', + header: 'Grand Total SO', cell: (props) => { - return formatCurrency( - props.row.original?.sales_order - ?.map((product) => product.total_price) - .reduce((a, b) => a + b, 0) ?? 0 - ); + return formatCurrency(props.row.original?.grand_total_so); + }, + }, + { + accessorKey: 'grand_total_do', + header: 'Grand Total DO', + cell: (props) => { + return formatCurrency(props.row.original?.grand_total_do); }, }, { @@ -911,6 +952,8 @@ const MarketingTable = () => { 'customer_name', 'project_flock_name', 'project_flock_kandang_name', + 'warehouse_name', + 'filter_by_name', 'sort_by', 'order_by', ]} diff --git a/src/components/pages/marketing/filter/MarketingFilter.ts b/src/components/pages/marketing/filter/MarketingFilter.ts index a306d89d..4b9ee07b 100644 --- a/src/components/pages/marketing/filter/MarketingFilter.ts +++ b/src/components/pages/marketing/filter/MarketingFilter.ts @@ -1,4 +1,4 @@ -import { array, mixed, object } from 'yup'; +import { array, mixed, object, string } from 'yup'; import { OptionType } from '@/components/input/SelectInput'; export const MarketingFilterSchema = object({ @@ -7,6 +7,10 @@ export const MarketingFilterSchema = object({ customer: mixed>().nullable(), project_flock: mixed>().nullable(), project_flock_kandang: mixed>().nullable(), + warehouse: mixed>().nullable(), + start_date: string().optional(), + end_date: string().optional(), + filter_by: mixed>().nullable(), }); export type MarketingFilterFormValues = { @@ -15,4 +19,8 @@ export type MarketingFilterFormValues = { customer: OptionType | null; project_flock: OptionType | null; project_flock_kandang: OptionType | null; + warehouse: OptionType | null; + start_date: string; + end_date: string; + filter_by: OptionType | null; }; diff --git a/src/components/pages/production/chickin/form/tabs/ChickLogsView.tsx b/src/components/pages/production/chickin/form/tabs/ChickLogsView.tsx index acb8c18b..6fbde87a 100644 --- a/src/components/pages/production/chickin/form/tabs/ChickLogsView.tsx +++ b/src/components/pages/production/chickin/form/tabs/ChickLogsView.tsx @@ -2,16 +2,17 @@ import Alert from '@/components/Alert'; import Button from '@/components/Button'; import Card from '@/components/Card'; import RequirePermission from '@/components/helper/RequirePermission'; +import DateInput from '@/components/input/DateInput'; import PillBadge from '@/components/PillBadge'; import { isResponseError, isResponseSuccess } from '@/lib/api-helper'; import { formatDate, formatNumber } from '@/lib/helper'; import { ChickinApi } from '@/services/api/production/chickin'; +import { useChickinStore } from '@/stores/production/chickin/chickin.store'; import { BaseApproval } from '@/types/api/api-general'; import { ProjectFlockKandang } from '@/types/api/production/project-flock-kandang'; import { Icon } from '@iconify/react'; import { useState } from 'react'; import toast from 'react-hot-toast'; -import { useChickinStore } from '@/stores/production/chickin/chickin.store'; const ChickinLogsView = ({ initialValues, @@ -23,6 +24,9 @@ const ChickinLogsView = ({ rawDataApprovals: BaseApproval[]; }) => { const [chickinErrorMessage, setChickinErrorMessage] = useState(''); + const [editingChickinId, setEditingChickinId] = useState(null); + const [editDate, setEditDate] = useState(''); + const [isEditLoading, setIsEditLoading] = useState(false); const { openChickinApproveModal, openChickinDeleteModal } = useChickinStore(); const handleClickApprove = () => { @@ -44,6 +48,23 @@ const ChickinLogsView = ({ }); }; + const handleSaveChickinDate = async () => { + setIsEditLoading(true); + const res = await ChickinApi.updateChickinDate( + initialValues.id as number, + formatDate(editDate, 'YYYY-MM-DD') + ); + setIsEditLoading(false); + if (isResponseSuccess(res)) { + toast.success(res?.message as string); + setEditingChickinId(null); + afterSubmit && afterSubmit(); + } + if (isResponseError(res)) { + toast.error(res?.message as string); + } + }; + const handleDeleteChickin = (chickinId: number) => { openChickinDeleteModal(chickinId, async () => { const deleteRes = await ChickinApi.delete(chickinId); @@ -133,9 +154,54 @@ const ChickinLogsView = ({ {' '} Tanggal Chick In -
- {formatDate(chickin.chick_in_date, 'DD MMM YYYY')} -
+ {editingChickinId === chickin.id ? ( +
+ setEditDate(e.target.value)} + /> +
+ + +
+
+ ) : ( +
+ + {formatDate(chickin.chick_in_date, 'DD MMM YYYY')} + + +
+ )} {/* Kandang */} diff --git a/src/services/api/marketing/marketing.ts b/src/services/api/marketing/marketing.ts index 2cd225a5..4ded8cc2 100644 --- a/src/services/api/marketing/marketing.ts +++ b/src/services/api/marketing/marketing.ts @@ -45,8 +45,11 @@ export class SalesOrderService extends BaseApiService< notes: notes || `${action} marketing ${id}`, }, }); - } catch (error) { - throw error; + } catch (error: unknown) { + if (axios.isAxiosError>(error)) { + return error.response?.data; + } + return undefined; } } @@ -68,8 +71,11 @@ export class SalesOrderService extends BaseApiService< notes: notes || `${action} marketing ${ids.join(', ')}`, }, }); - } catch (error) { - throw error; + } catch (error: unknown) { + if (axios.isAxiosError>(error)) { + return error.response?.data; + } + return undefined; } } @@ -110,8 +116,11 @@ export class SalesOrderService extends BaseApiService< notes: notes || `Delivery marketing ${id}`, }, }); - } catch (error) { - throw error; + } catch (error: unknown) { + if (axios.isAxiosError>(error)) { + return error.response?.data; + } + return undefined; } } } @@ -142,8 +151,11 @@ class MarketingExportService extends BaseApiService< notes: notes, }, }); - } catch (error) { - throw error; + } catch (error: unknown) { + if (axios.isAxiosError>(error)) { + return error.response?.data; + } + return undefined; } } diff --git a/src/services/api/production/chickin.ts b/src/services/api/production/chickin.ts index 0efaa0f9..d250e450 100644 --- a/src/services/api/production/chickin.ts +++ b/src/services/api/production/chickin.ts @@ -6,6 +6,7 @@ import { import { BaseApiService } from '@/services/api/base'; import { BaseApiResponse } from '@/types/api/api-general'; import { httpClient } from '@/services/http/client'; +import axios from 'axios'; export class ChickinService extends BaseApiService< Chickin, @@ -16,6 +17,29 @@ export class ChickinService extends BaseApiService< super(basePath); } + async updateChickinDate( + projectFlockKandangId: number, + chickInDate: string + ): Promise | undefined> { + try { + return await httpClient>( + `${this.basePath}/chick-in-date`, + { + method: 'PATCH', + body: { + project_flock_kandang_id: projectFlockKandangId, + chick_in_date: chickInDate, + }, + } + ); + } catch (error: unknown) { + if (axios.isAxiosError>(error)) { + return error.response?.data; + } + return undefined; + } + } + /** * Approve single marketing data */ diff --git a/src/types/api/marketing/marketing.d.ts b/src/types/api/marketing/marketing.d.ts index ee3a95d3..909e2af4 100644 --- a/src/types/api/marketing/marketing.d.ts +++ b/src/types/api/marketing/marketing.d.ts @@ -23,6 +23,8 @@ export type BaseMarketing = { latest_approval: BaseApproval; sales_order: BaseSalesOrder[]; delivery_order: BaseDeliveryOrder[]; + grand_total_do: number; + grand_total_so: number; }; export type BaseSalesOrder = { @@ -104,6 +106,12 @@ export type MarketingFilter = { project_flock_name?: string; project_flock_kandang_id?: number; project_flock_kandang_name?: string; + start_date?: string; + end_date?: string; + filter_by?: string; + filter_by_name?: string; + warehouse_id?: number; + warehouse_name?: string; }; /**