mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-20 13:32:00 +00:00
feat(FE-65): enhance MovementForm to support file uploads with FormData conversion
This commit is contained in:
@@ -24,13 +24,17 @@ import {
|
|||||||
ProductSchema,
|
ProductSchema,
|
||||||
EkspedisiSchema,
|
EkspedisiSchema,
|
||||||
} from '@/components/pages/inventory/movement/form/MovementForm.schema';
|
} from '@/components/pages/inventory/movement/form/MovementForm.schema';
|
||||||
import { useMovementFormHandlers } from './useMovementFormHandlers';
|
import {
|
||||||
|
useMovementFormHandlers,
|
||||||
|
containsFile,
|
||||||
|
} from './useMovementFormHandlers';
|
||||||
import {
|
import {
|
||||||
ProductApi,
|
ProductApi,
|
||||||
SupplierApi,
|
SupplierApi,
|
||||||
WarehouseApi,
|
WarehouseApi,
|
||||||
} from '@/services/api/master-data';
|
} from '@/services/api/master-data';
|
||||||
import { toast } from 'react-hot-toast';
|
import { toast } from 'react-hot-toast';
|
||||||
|
import FileInput from '@/components/input/FileInput';
|
||||||
|
|
||||||
interface MovementFormProps {
|
interface MovementFormProps {
|
||||||
type?: 'add' | 'edit' | 'detail';
|
type?: 'add' | 'edit' | 'detail';
|
||||||
@@ -62,6 +66,11 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
|
|||||||
validationSchema:
|
validationSchema:
|
||||||
type === 'edit' ? UpdateMovementFormSchema : MovementFormSchema,
|
type === 'edit' ? UpdateMovementFormSchema : MovementFormSchema,
|
||||||
onSubmit: async (values) => {
|
onSubmit: async (values) => {
|
||||||
|
console.log(
|
||||||
|
'Dokumen:',
|
||||||
|
values.ekspedisi?.map((e) => e.dokumen)
|
||||||
|
);
|
||||||
|
|
||||||
setMovementFormErrorMessage('');
|
setMovementFormErrorMessage('');
|
||||||
const payload: CreateMovementPayload = {
|
const payload: CreateMovementPayload = {
|
||||||
alasan_transfer: values.alasan_transfer,
|
alasan_transfer: values.alasan_transfer,
|
||||||
@@ -88,6 +97,9 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
|
|||||||
})),
|
})),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
console.log('containsFile:', containsFile(payload));
|
||||||
|
console.log('payload:', payload);
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'add':
|
case 'add':
|
||||||
await createMovementHandler(payload);
|
await createMovementHandler(payload);
|
||||||
@@ -810,9 +822,8 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
|
|||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<TextInput
|
<FileInput
|
||||||
required
|
required
|
||||||
type='file'
|
|
||||||
name={`ekspedisi.${idx}.dokumen`}
|
name={`ekspedisi.${idx}.dokumen`}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const file = e.target.files?.[0];
|
const file = e.target.files?.[0];
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
UpdateMovementPayload,
|
UpdateMovementPayload,
|
||||||
} from '@/types/api/inventory/movement';
|
} from '@/types/api/inventory/movement';
|
||||||
import { isResponseError } from '@/lib/api-helper';
|
import { isResponseError } from '@/lib/api-helper';
|
||||||
|
import { containsFile, toFormData } from '@/lib/form-data';
|
||||||
|
|
||||||
export const useMovementFormHandlers = (initialValuesId?: number) => {
|
export const useMovementFormHandlers = (initialValuesId?: number) => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@@ -17,7 +18,11 @@ export const useMovementFormHandlers = (initialValuesId?: number) => {
|
|||||||
|
|
||||||
const createMovementHandler = useCallback(
|
const createMovementHandler = useCallback(
|
||||||
async (payload: CreateMovementPayload) => {
|
async (payload: CreateMovementPayload) => {
|
||||||
const res = await MovementApi.create(payload);
|
const finalPayload = containsFile(payload)
|
||||||
|
? (toFormData(payload) as unknown as CreateMovementPayload)
|
||||||
|
: payload;
|
||||||
|
|
||||||
|
const res = await MovementApi.create(finalPayload);
|
||||||
if (isResponseError(res)) {
|
if (isResponseError(res)) {
|
||||||
setMovementFormErrorMessage(res.message);
|
setMovementFormErrorMessage(res.message);
|
||||||
return;
|
return;
|
||||||
@@ -30,7 +35,11 @@ export const useMovementFormHandlers = (initialValuesId?: number) => {
|
|||||||
|
|
||||||
const updateMovementHandler = useCallback(
|
const updateMovementHandler = useCallback(
|
||||||
async (movementId: number, payload: UpdateMovementPayload) => {
|
async (movementId: number, payload: UpdateMovementPayload) => {
|
||||||
const res = await MovementApi.update(movementId, payload);
|
const finalPayload = containsFile(payload)
|
||||||
|
? (toFormData(payload) as unknown as UpdateMovementPayload)
|
||||||
|
: payload;
|
||||||
|
|
||||||
|
const res = await MovementApi.update(movementId, finalPayload);
|
||||||
if (res?.status === 'error') {
|
if (res?.status === 'error') {
|
||||||
setMovementFormErrorMessage(res.message);
|
setMovementFormErrorMessage(res.message);
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
export function toFormData(
|
||||||
|
value: unknown,
|
||||||
|
form = new FormData(),
|
||||||
|
parentKey?: string
|
||||||
|
) {
|
||||||
|
if (value === undefined || value === null) {
|
||||||
|
if (parentKey) form.append(parentKey, '');
|
||||||
|
return form;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value instanceof File) {
|
||||||
|
if (!parentKey) throw new Error('File must have a key');
|
||||||
|
form.append(parentKey, value);
|
||||||
|
return form;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
value.forEach((v, i) => {
|
||||||
|
const key = parentKey ? `${parentKey}[${i}]` : `${i}`;
|
||||||
|
toFormData(v, form, key);
|
||||||
|
});
|
||||||
|
return form;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof value === 'object') {
|
||||||
|
Object.entries(value as Record<string, unknown>).forEach(([k, v]) => {
|
||||||
|
const key = parentKey ? `${parentKey}[${k}]` : k;
|
||||||
|
toFormData(v, form, key);
|
||||||
|
});
|
||||||
|
return form;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parentKey) form.append(parentKey, String(value));
|
||||||
|
return form;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function containsFile(obj: unknown): boolean {
|
||||||
|
if (!obj) return false;
|
||||||
|
if (obj instanceof File) return true;
|
||||||
|
if (Array.isArray(obj)) return obj.some(containsFile);
|
||||||
|
if (typeof obj === 'object') {
|
||||||
|
return Object.values(obj as Record<string, unknown>).some(containsFile);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
@@ -4,9 +4,11 @@ import { BaseApiResponse } from '@/types/api/api-general';
|
|||||||
|
|
||||||
export class BaseApiService<T, CreatePayloadGeneric, UpdatePayloadGeneric> {
|
export class BaseApiService<T, CreatePayloadGeneric, UpdatePayloadGeneric> {
|
||||||
basePath: string;
|
basePath: string;
|
||||||
|
header?: Record<string, string>;
|
||||||
|
|
||||||
constructor(basePath: string) {
|
constructor(basePath: string, header?: Record<string, string>) {
|
||||||
this.basePath = basePath;
|
this.basePath = basePath;
|
||||||
|
this.header = header;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getAllFetcher(endpoint: string): Promise<BaseApiResponse<T[]>> {
|
async getAllFetcher(endpoint: string): Promise<BaseApiResponse<T[]>> {
|
||||||
@@ -23,42 +25,52 @@ export class BaseApiService<T, CreatePayloadGeneric, UpdatePayloadGeneric> {
|
|||||||
if (axios.isAxiosError<BaseApiResponse<T>>(error)) {
|
if (axios.isAxiosError<BaseApiResponse<T>>(error)) {
|
||||||
return error.response?.data;
|
return error.response?.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async create(payload: CreatePayloadGeneric) {
|
async create(payload: CreatePayloadGeneric) {
|
||||||
|
const isFormData =
|
||||||
|
typeof FormData !== 'undefined' && payload instanceof FormData;
|
||||||
try {
|
try {
|
||||||
|
const headers = isFormData
|
||||||
|
? { ...(this.header ?? {}) }
|
||||||
|
: { 'Content-Type': 'application/json', ...(this.header ?? {}) };
|
||||||
|
|
||||||
const createRes = await httpClient<BaseApiResponse<T>>(this.basePath, {
|
const createRes = await httpClient<BaseApiResponse<T>>(this.basePath, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: payload,
|
body: payload,
|
||||||
|
headers,
|
||||||
});
|
});
|
||||||
|
|
||||||
return createRes;
|
return createRes;
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
if (axios.isAxiosError<BaseApiResponse<T>>(error)) {
|
if (axios.isAxiosError<BaseApiResponse<T>>(error)) {
|
||||||
return error.response?.data;
|
return error.response?.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async update(id: number, payload: UpdatePayloadGeneric) {
|
async update(id: number, payload: UpdatePayloadGeneric) {
|
||||||
|
const isFormData =
|
||||||
|
typeof FormData !== 'undefined' && payload instanceof FormData;
|
||||||
try {
|
try {
|
||||||
const updatePath = `${this.basePath}/${id}`;
|
const updatePath = `${this.basePath}/${id}`;
|
||||||
|
|
||||||
|
const headers = isFormData
|
||||||
|
? { ...(this.header ?? {}) }
|
||||||
|
: { 'Content-Type': 'application/json', ...(this.header ?? {}) };
|
||||||
|
|
||||||
const updateRes = await httpClient<BaseApiResponse<T>>(updatePath, {
|
const updateRes = await httpClient<BaseApiResponse<T>>(updatePath, {
|
||||||
method: 'PATCH',
|
method: 'PATCH',
|
||||||
body: payload,
|
body: payload,
|
||||||
|
headers,
|
||||||
});
|
});
|
||||||
|
|
||||||
return updateRes;
|
return updateRes;
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
if (axios.isAxiosError<BaseApiResponse<T>>(error)) {
|
if (axios.isAxiosError<BaseApiResponse<T>>(error)) {
|
||||||
return error.response?.data;
|
return error.response?.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -69,13 +81,11 @@ export class BaseApiService<T, CreatePayloadGeneric, UpdatePayloadGeneric> {
|
|||||||
const deleteRes = await httpClient<BaseApiResponse>(deletePath, {
|
const deleteRes = await httpClient<BaseApiResponse>(deletePath, {
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
});
|
});
|
||||||
|
|
||||||
return deleteRes;
|
return deleteRes;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (axios.isAxiosError<BaseApiResponse>(error)) {
|
if (axios.isAxiosError<BaseApiResponse>(error)) {
|
||||||
return error.response?.data;
|
return error.response?.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user