This commit is contained in:
@@ -21,6 +21,7 @@ const CreateCompany = ({ defaultValues, editMode, id }: IProps) => {
|
||||
control,
|
||||
watch,
|
||||
setValue,
|
||||
persistDocumentFileIds,
|
||||
companyCategories,
|
||||
} = useCreateCompanyPresenter({ editMode, defaultValues, id });
|
||||
|
||||
@@ -38,6 +39,7 @@ const CreateCompany = ({ defaultValues, editMode, id }: IProps) => {
|
||||
categoryItems,
|
||||
watch,
|
||||
setValue,
|
||||
persistDocumentFileIds,
|
||||
})}
|
||||
register={register}
|
||||
control={control}
|
||||
|
||||
@@ -25,6 +25,7 @@ interface CompanyFormSectionsOptions {
|
||||
categoryItems: ILabelValue[];
|
||||
watch: UseFormWatch<ICreateCompanyCredentials>;
|
||||
setValue: UseFormSetValue<ICreateCompanyCredentials>;
|
||||
persistDocumentFileIds?: (ids: string[]) => Promise<void>;
|
||||
}
|
||||
|
||||
const optionalNumber = (value: unknown) => {
|
||||
@@ -240,6 +241,7 @@ export const createCompanyFormSections = ({
|
||||
categoryItems,
|
||||
watch,
|
||||
setValue,
|
||||
persistDocumentFileIds,
|
||||
}: CompanyFormSectionsOptions): DynamicFormSection<ICreateCompanyCredentials>[] => [
|
||||
{
|
||||
id: companyInformationSectionId,
|
||||
@@ -652,7 +654,14 @@ export const createCompanyFormSections = ({
|
||||
<CompanyDocuments
|
||||
companyId={editMode ? id : undefined}
|
||||
value={watch("document_file_ids") ?? []}
|
||||
onChange={(ids) => setValue("document_file_ids", ids)}
|
||||
onChange={(ids) =>
|
||||
setValue("document_file_ids", ids, {
|
||||
shouldDirty: true,
|
||||
shouldTouch: true,
|
||||
shouldValidate: true,
|
||||
})
|
||||
}
|
||||
onRemovePersist={persistDocumentFileIds}
|
||||
/>
|
||||
),
|
||||
},
|
||||
|
||||
@@ -8,9 +8,15 @@ interface IProps {
|
||||
companyId?: string;
|
||||
value: string[];
|
||||
onChange: (ids: string[]) => void;
|
||||
onRemovePersist?: (ids: string[]) => Promise<void>;
|
||||
}
|
||||
|
||||
const CompanyDocuments = ({ companyId, value, onChange }: IProps) => {
|
||||
const CompanyDocuments = ({
|
||||
companyId,
|
||||
value,
|
||||
onChange,
|
||||
onRemovePersist,
|
||||
}: IProps) => {
|
||||
const {
|
||||
isUploading,
|
||||
isBusy,
|
||||
@@ -20,13 +26,18 @@ const CompanyDocuments = ({ companyId, value, onChange }: IProps) => {
|
||||
errorMessage,
|
||||
onSelectFiles,
|
||||
onRemove,
|
||||
} = useCompanyDocumentsPresenter({ companyId, value, onChange });
|
||||
} = useCompanyDocumentsPresenter({
|
||||
companyId,
|
||||
value,
|
||||
onChange,
|
||||
onRemovePersist,
|
||||
});
|
||||
|
||||
const inputId = "company-documents-input";
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="relative overflow-hidden rounded-2xl border border-stroke/70 bg-gradient-to-br from-white via-white to-primary/[0.04] p-4 shadow-theme-xs dark:border-dark-3 dark:from-dark-2 dark:via-dark-2 dark:to-primary/[0.12]">
|
||||
<div className="shadow-theme-xs relative overflow-hidden rounded-2xl border border-stroke/70 bg-gradient-to-br from-white via-white to-primary/[0.04] p-4 dark:border-dark-3 dark:from-dark-2 dark:via-dark-2 dark:to-primary/[0.12]">
|
||||
<input
|
||||
id={inputId}
|
||||
type="file"
|
||||
@@ -45,8 +56,13 @@ const CompanyDocuments = ({ companyId, value, onChange }: IProps) => {
|
||||
: "cursor-pointer hover:-translate-y-0.5 hover:border-primary hover:bg-primary/[0.03] dark:hover:bg-primary/[0.08]"
|
||||
}`}
|
||||
>
|
||||
<div className="mb-3 rounded-2xl bg-primary/[0.12] p-3 text-primary shadow-theme-xs transition-transform duration-300 group-hover:scale-105">
|
||||
<svg className="h-6 w-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<div className="shadow-theme-xs mb-3 rounded-2xl bg-primary/[0.12] p-3 text-primary transition-transform duration-300 group-hover:scale-105">
|
||||
<svg
|
||||
className="h-6 w-6"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
@@ -80,7 +96,9 @@ const CompanyDocuments = ({ companyId, value, onChange }: IProps) => {
|
||||
</div>
|
||||
) : removingFileId ? (
|
||||
<div className="mt-3">
|
||||
<p className="text-xs font-medium text-primary">Removing document…</p>
|
||||
<p className="text-xs font-medium text-primary">
|
||||
Removing document…
|
||||
</p>
|
||||
<div className="mt-1.5 h-1.5 w-full overflow-hidden rounded-full bg-gray-2 dark:bg-dark-3">
|
||||
<div className="h-full w-full animate-pulse rounded-full bg-gradient-to-r from-primary to-purple-500" />
|
||||
</div>
|
||||
@@ -115,8 +133,12 @@ const CompanyDocuments = ({ companyId, value, onChange }: IProps) => {
|
||||
</p>
|
||||
<ul className="space-y-1">
|
||||
{failed.map((item, index) => (
|
||||
<li key={`${item.filename}-${index}`} className="text-sm text-error">
|
||||
<span className="font-medium">{item.filename}</span>: {item.error}
|
||||
<li
|
||||
key={`${item.filename}-${index}`}
|
||||
className="text-sm text-error"
|
||||
>
|
||||
<span className="font-medium">{item.filename}</span>:{" "}
|
||||
{item.error}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
"use client";
|
||||
import { useUploadCompanyDocuments } from "@/hooks/queries";
|
||||
import { fileService, FailedFile } from "@/lib/api";
|
||||
import {
|
||||
MAX_DOCUMENT_FILES,
|
||||
validateDocumentFile,
|
||||
} from "@/utils/shared";
|
||||
import { MAX_DOCUMENT_FILES, validateDocumentFile } from "@/utils/shared";
|
||||
import { ChangeEvent, useState } from "react";
|
||||
|
||||
interface IProps {
|
||||
@@ -14,6 +11,8 @@ interface IProps {
|
||||
value: string[];
|
||||
/** Persists the updated document file id list back to the form. */
|
||||
onChange: (ids: string[]) => void;
|
||||
/** Persists document removal for an existing company. */
|
||||
onRemovePersist?: (ids: string[]) => Promise<void>;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -28,6 +27,7 @@ const useCompanyDocumentsPresenter = ({
|
||||
companyId,
|
||||
value,
|
||||
onChange,
|
||||
onRemovePersist,
|
||||
}: IProps) => {
|
||||
const [isUploading, setIsUploading] = useState(false);
|
||||
const [removingFileId, setRemovingFileId] = useState<string | null>(null);
|
||||
@@ -106,15 +106,18 @@ const useCompanyDocumentsPresenter = ({
|
||||
setErrorMessage(undefined);
|
||||
|
||||
try {
|
||||
const nextValue = value.filter((id) => id !== fileId);
|
||||
|
||||
// In create mode the file is an orphan in storage (not linked to any
|
||||
// company), so clean it up before dropping it from the form. In edit mode
|
||||
// the removal is persisted by the form's update (PUT replaces the full
|
||||
// document list).
|
||||
// persist the replacement document list immediately.
|
||||
if (!companyId) {
|
||||
await fileService.deleteFile(fileId);
|
||||
} else {
|
||||
await onRemovePersist?.(nextValue);
|
||||
}
|
||||
|
||||
onChange(value.filter((id) => id !== fileId));
|
||||
onChange(nextValue);
|
||||
} catch (error) {
|
||||
console.error("ERROR caught while deleting company document:", error);
|
||||
setErrorMessage("Failed to remove document. Please try again.");
|
||||
|
||||
@@ -49,6 +49,13 @@ const useCreateCompanyPresenter = ({ defaultValues, editMode, id }: IProps) => {
|
||||
const { mutate: updateCompany, isPending: isUpdating } = useUpdateCompany(
|
||||
id as string,
|
||||
);
|
||||
const { mutateAsync: updateCompanyDocuments } = useUpdateCompany(
|
||||
id as string,
|
||||
{
|
||||
redirectOnSuccess: false,
|
||||
showSuccessToast: false,
|
||||
},
|
||||
);
|
||||
|
||||
const optionalNumber = (value: unknown) => {
|
||||
if (value === "" || value === null || value === undefined) return undefined;
|
||||
@@ -56,18 +63,18 @@ const useCreateCompanyPresenter = ({ defaultValues, editMode, id }: IProps) => {
|
||||
return Number(value);
|
||||
};
|
||||
|
||||
const onSubmit: SubmitHandler<ICreateCompanyCredentials> = (data) => {
|
||||
const formattedData = {
|
||||
const formatCompanyData = (
|
||||
data: ICreateCompanyCredentials,
|
||||
documentFileIds = data.document_file_ids,
|
||||
) => ({
|
||||
...data,
|
||||
founded_year: optionalNumber(data.founded_year),
|
||||
employee_count: optionalNumber(data.employee_count),
|
||||
annual_revenue: optionalNumber(data.annual_revenue),
|
||||
document_file_ids: data.document_file_ids ?? [],
|
||||
document_file_ids: documentFileIds ?? [],
|
||||
tags: {
|
||||
keywords: data?.tags?.keywords?.length ? data?.tags?.keywords : [],
|
||||
categories: data?.tags?.categories?.length
|
||||
? data?.tags?.categories
|
||||
: [],
|
||||
categories: data?.tags?.categories?.length ? data?.tags?.categories : [],
|
||||
cpv_codes: data?.tags?.cpv_codes?.length ? data?.tags?.cpv_codes : [],
|
||||
specializations: data?.tags?.specializations?.length
|
||||
? data?.tags?.specializations
|
||||
@@ -76,7 +83,10 @@ const useCreateCompanyPresenter = ({ defaultValues, editMode, id }: IProps) => {
|
||||
? data?.tags?.certifications
|
||||
: [],
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
const onSubmit: SubmitHandler<ICreateCompanyCredentials> = (data) => {
|
||||
const formattedData = formatCompanyData(data);
|
||||
if (editMode) {
|
||||
updateCompany({
|
||||
id: id as string,
|
||||
@@ -87,6 +97,18 @@ const useCreateCompanyPresenter = ({ defaultValues, editMode, id }: IProps) => {
|
||||
}
|
||||
};
|
||||
|
||||
const persistDocumentFileIds = async (documentFileIds: string[]) => {
|
||||
if (!editMode || !id) return;
|
||||
|
||||
await updateCompanyDocuments({
|
||||
id,
|
||||
credentials: formatCompanyData(
|
||||
watch() as ICreateCompanyCredentials,
|
||||
documentFileIds,
|
||||
) as ICreateCompanyCredentials,
|
||||
});
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
reset();
|
||||
router.back();
|
||||
@@ -104,6 +126,7 @@ const useCreateCompanyPresenter = ({ defaultValues, editMode, id }: IProps) => {
|
||||
router,
|
||||
watch,
|
||||
setValue,
|
||||
persistDocumentFileIds,
|
||||
companyCategories,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -31,7 +31,18 @@ export const useCreateCompany = () => {
|
||||
});
|
||||
};
|
||||
|
||||
export const useUpdateCompany = (id: string) => {
|
||||
interface UseUpdateCompanyOptions {
|
||||
redirectOnSuccess?: boolean;
|
||||
showSuccessToast?: boolean;
|
||||
}
|
||||
|
||||
export const useUpdateCompany = (
|
||||
id: string,
|
||||
{
|
||||
redirectOnSuccess = true,
|
||||
showSuccessToast = true,
|
||||
}: UseUpdateCompanyOptions = {},
|
||||
) => {
|
||||
const queryClient = useQueryClient();
|
||||
const router = useRouter();
|
||||
const mutationKey = useMemo(
|
||||
@@ -42,14 +53,21 @@ export const useUpdateCompany = (id: string) => {
|
||||
mutationKey,
|
||||
mutationFn: companiesService.updateCompany,
|
||||
onSuccess: () => {
|
||||
if (showSuccessToast) {
|
||||
toast.success("Company updated successfully");
|
||||
}
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [API_ENDPOINTS.COMPANIES.READ_ALL],
|
||||
});
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [API_ENDPOINTS.COMPANIES.READ_ALL, "details"],
|
||||
});
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [API_ENDPOINTS.COMPANIES.READ_ALL, id, "details"],
|
||||
});
|
||||
if (redirectOnSuccess) {
|
||||
router.push("/companies");
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user