Fix optional empty value update
Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
This commit is contained in:
@@ -25,6 +25,7 @@ import type { TaskFormDialogFragment$key } from "./__generated__/TaskFormDialogF
|
||||
import { MeasureSelectField } from "/components/form/MeasureSelectField";
|
||||
import { Controller } from "react-hook-form";
|
||||
import { updateStoreCounter } from "/hooks/useMutationWithIncrement";
|
||||
import { formatDatetime } from "@probo/helpers";
|
||||
|
||||
const taskFragment = graphql`
|
||||
fragment TaskFormDialogFragment on Task {
|
||||
@@ -74,11 +75,7 @@ const schema = z.object({
|
||||
timeEstimate: z.string().nullable(),
|
||||
assignedToId: z.string(),
|
||||
measureId: z.string(),
|
||||
deadline: z
|
||||
.date({
|
||||
coerce: true,
|
||||
})
|
||||
.nullable(),
|
||||
deadline: z.string().optional(),
|
||||
});
|
||||
|
||||
type Props = {
|
||||
@@ -113,7 +110,7 @@ export default function TaskFormDialog(props: Props) {
|
||||
timeEstimate: task?.timeEstimate ?? "",
|
||||
assignedToId: task?.assignedTo?.id ?? "",
|
||||
measureId: task?.measure?.id ?? props.measureId ?? "",
|
||||
deadline: task?.deadline?.split("T")[0] ?? null,
|
||||
deadline: task?.deadline?.split("T")[0] ?? "",
|
||||
},
|
||||
});
|
||||
|
||||
@@ -126,7 +123,7 @@ export default function TaskFormDialog(props: Props) {
|
||||
name: data.name,
|
||||
description: data.description,
|
||||
timeEstimate: data.timeEstimate || null,
|
||||
deadline: data.deadline,
|
||||
deadline: formatDatetime(data.deadline) ?? null,
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -138,7 +135,7 @@ export default function TaskFormDialog(props: Props) {
|
||||
name: data.name,
|
||||
description: data.description,
|
||||
timeEstimate: data.timeEstimate || null,
|
||||
deadline: data.deadline || null,
|
||||
deadline: formatDatetime(data.deadline) ?? null,
|
||||
assignedToId: data.assignedToId,
|
||||
measureId: data.measureId,
|
||||
},
|
||||
|
||||
@@ -188,8 +188,8 @@ export const useUpdateAudit = () => {
|
||||
return (input: {
|
||||
id: string;
|
||||
name?: string;
|
||||
validFrom?: string;
|
||||
validUntil?: string;
|
||||
validFrom?: string | null;
|
||||
validUntil?: string | null;
|
||||
state?: string;
|
||||
}) => {
|
||||
if (!input.id) {
|
||||
|
||||
@@ -189,7 +189,7 @@ export const useUpdateContinualImprovement = () => {
|
||||
description?: string;
|
||||
source?: string;
|
||||
ownerId?: string;
|
||||
targetDate?: string;
|
||||
targetDate?: string | null;
|
||||
status?: string;
|
||||
priority?: string;
|
||||
}) => {
|
||||
|
||||
@@ -222,12 +222,12 @@ export const useUpdateNonconformity = () => {
|
||||
id: string;
|
||||
referenceId?: string;
|
||||
description?: string;
|
||||
dateIdentified?: string;
|
||||
dateIdentified?: string | null;
|
||||
rootCause?: string;
|
||||
correctiveAction?: string;
|
||||
ownerId?: string;
|
||||
auditId?: string;
|
||||
dueDate?: string;
|
||||
dueDate?: string | null;
|
||||
status?: string;
|
||||
effectivenessCheck?: string;
|
||||
}) => {
|
||||
|
||||
@@ -195,8 +195,8 @@ export const useUpdateObligation = () => {
|
||||
actionsToBeImplemented?: string;
|
||||
regulator?: string;
|
||||
ownerId?: string;
|
||||
lastReviewDate?: string;
|
||||
dueDate?: string;
|
||||
lastReviewDate?: string | null;
|
||||
dueDate?: string | null;
|
||||
status?: string;
|
||||
}) => {
|
||||
if (!input.id) {
|
||||
|
||||
@@ -23,12 +23,11 @@ import {
|
||||
Tr,
|
||||
useConfirm,
|
||||
useDialogRef,
|
||||
useToast,
|
||||
} from "@probo/ui";
|
||||
import { useTranslate } from "@probo/i18n";
|
||||
import type { PreloadedQuery } from "react-relay";
|
||||
import type { OrganizationGraph_ViewQuery } from "/hooks/graph/__generated__/OrganizationGraph_ViewQuery.graphql";
|
||||
import { useFragment, useMutation, usePreloadedQuery, usePaginationFragment } from "react-relay";
|
||||
import { useFragment, usePreloadedQuery, usePaginationFragment } from "react-relay";
|
||||
import { organizationViewQuery } from "/hooks/graph/OrganizationGraph";
|
||||
import { graphql } from "relay-runtime";
|
||||
import { SortableTable, SortableTh } from "/components/SortableTable";
|
||||
@@ -45,7 +44,7 @@ import type {
|
||||
SettingsPageInvitationsFragment$data,
|
||||
SettingsPageInvitationsFragment$key
|
||||
} from "./__generated__/SettingsPageInvitationsFragment.graphql";
|
||||
import { useState, type ChangeEventHandler, useEffect } from "react";
|
||||
import { useState, type ChangeEventHandler, useEffect, useRef } from "react";
|
||||
import { sprintf } from "@probo/helpers";
|
||||
import { useFormWithSchema } from "/hooks/useFormWithSchema";
|
||||
import { z } from "zod";
|
||||
@@ -226,7 +225,6 @@ export default function SettingsPage({ queryRef }: Props) {
|
||||
organizationViewQuery,
|
||||
queryRef
|
||||
).node;
|
||||
const { toast } = useToast();
|
||||
const organization = useFragment<SettingsPageFragment$key>(
|
||||
organizationFragment,
|
||||
organizationKey
|
||||
@@ -250,7 +248,13 @@ export default function SettingsPage({ queryRef }: Props) {
|
||||
invitationsPagination.refetch({}, { fetchPolicy: 'network-only' });
|
||||
};
|
||||
|
||||
const [updateOrganization] = useMutation(updateOrganizationMutation);
|
||||
const [updateOrganization, isUpdatingOrganization] = useMutationWithToasts(
|
||||
updateOrganizationMutation,
|
||||
{
|
||||
successMessage: __("Organization updated successfully"),
|
||||
errorMessage: __("Failed to update organization"),
|
||||
}
|
||||
);
|
||||
const [deleteHorizontalLogo, isDeletingHorizontalLogo] = useMutationWithToasts(
|
||||
deleteHorizontalLogoMutation,
|
||||
{
|
||||
@@ -262,8 +266,6 @@ export default function SettingsPage({ queryRef }: Props) {
|
||||
const memberships = membershipsPagination.data.memberships?.edges.map((edge) => edge.node) || [];
|
||||
const invitations = invitationsPagination.data.invitations?.edges.map((edge) => edge.node) || [];
|
||||
const [activeTab, setActiveTab] = useState<"memberships" | "invitations">("memberships");
|
||||
const [logoFile, setLogoFile] = useState<File | null>(null);
|
||||
const [horizontalLogoFile, setHorizontalLogoFile] = useState<File | null>(null);
|
||||
const [logoPreview, setLogoPreview] = useState<string | null>(null);
|
||||
const [horizontalLogoPreview, setHorizontalLogoPreview] = useState<string | null>(null);
|
||||
|
||||
@@ -280,31 +282,45 @@ export default function SettingsPage({ queryRef }: Props) {
|
||||
}
|
||||
);
|
||||
|
||||
const prevOrgDataRef = useRef({
|
||||
name: organization.name,
|
||||
description: organization.description,
|
||||
websiteUrl: organization.websiteUrl,
|
||||
email: organization.email,
|
||||
headquarterAddress: organization.headquarterAddress,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
reset({
|
||||
name: organization.name || "",
|
||||
description: organization.description || "",
|
||||
websiteUrl: organization.websiteUrl || "",
|
||||
email: organization.email || "",
|
||||
headquarterAddress: organization.headquarterAddress || "",
|
||||
});
|
||||
setLogoFile(null);
|
||||
setHorizontalLogoFile(null);
|
||||
setLogoPreview(null);
|
||||
setHorizontalLogoPreview(null);
|
||||
const prev = prevOrgDataRef.current;
|
||||
const hasFormFieldChanges =
|
||||
prev.name !== organization.name ||
|
||||
prev.description !== organization.description ||
|
||||
prev.websiteUrl !== organization.websiteUrl ||
|
||||
prev.email !== organization.email ||
|
||||
prev.headquarterAddress !== organization.headquarterAddress;
|
||||
|
||||
if (hasFormFieldChanges) {
|
||||
reset({
|
||||
name: organization.name || "",
|
||||
description: organization.description || "",
|
||||
websiteUrl: organization.websiteUrl || "",
|
||||
email: organization.email || "",
|
||||
headquarterAddress: organization.headquarterAddress || "",
|
||||
});
|
||||
setLogoPreview(null);
|
||||
setHorizontalLogoPreview(null);
|
||||
|
||||
prevOrgDataRef.current = {
|
||||
name: organization.name,
|
||||
description: organization.description,
|
||||
websiteUrl: organization.websiteUrl,
|
||||
email: organization.email,
|
||||
headquarterAddress: organization.headquarterAddress,
|
||||
};
|
||||
}
|
||||
}, [organization, reset]);
|
||||
|
||||
const onSubmit = handleSubmit((data: OrganizationFormData) => {
|
||||
const uploadables: Record<string, File> = {};
|
||||
|
||||
if (logoFile) {
|
||||
uploadables["input.logo"] = logoFile;
|
||||
}
|
||||
|
||||
if (horizontalLogoFile) {
|
||||
uploadables["input.horizontalLogoFile"] = horizontalLogoFile;
|
||||
}
|
||||
|
||||
updateOrganization({
|
||||
variables: {
|
||||
input: {
|
||||
@@ -314,27 +330,8 @@ export default function SettingsPage({ queryRef }: Props) {
|
||||
websiteUrl: data.websiteUrl || undefined,
|
||||
email: data.email || undefined,
|
||||
headquarterAddress: data.headquarterAddress || undefined,
|
||||
logo: logoFile ? null : undefined,
|
||||
horizontalLogoFile: horizontalLogoFile ? null : undefined,
|
||||
},
|
||||
},
|
||||
uploadables: Object.keys(uploadables).length > 0 ? uploadables : undefined,
|
||||
onError() {
|
||||
toast({
|
||||
title: __("Error"),
|
||||
description: __("Failed to update organization."),
|
||||
variant: "error",
|
||||
});
|
||||
},
|
||||
onCompleted() {
|
||||
toast({
|
||||
title: __("Organization updated"),
|
||||
description: __(
|
||||
"Your organization details have been updated successfully."
|
||||
),
|
||||
variant: "success",
|
||||
});
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
@@ -343,12 +340,27 @@ export default function SettingsPage({ queryRef }: Props) {
|
||||
if (!file) {
|
||||
return;
|
||||
}
|
||||
setLogoFile(file);
|
||||
|
||||
const reader = new FileReader();
|
||||
reader.onloadend = () => {
|
||||
setLogoPreview(reader.result as string);
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
|
||||
updateOrganization({
|
||||
variables: {
|
||||
input: {
|
||||
organizationId: organization.id,
|
||||
logoFile: null,
|
||||
},
|
||||
},
|
||||
uploadables: {
|
||||
"input.logoFile": file,
|
||||
},
|
||||
onSuccess: () => {
|
||||
setLogoPreview(null);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const handleHorizontalLogoChange: ChangeEventHandler<HTMLInputElement> = (e) => {
|
||||
@@ -356,12 +368,27 @@ export default function SettingsPage({ queryRef }: Props) {
|
||||
if (!file) {
|
||||
return;
|
||||
}
|
||||
setHorizontalLogoFile(file);
|
||||
|
||||
const reader = new FileReader();
|
||||
reader.onloadend = () => {
|
||||
setHorizontalLogoPreview(reader.result as string);
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
|
||||
updateOrganization({
|
||||
variables: {
|
||||
input: {
|
||||
organizationId: organization.id,
|
||||
horizontalLogoFile: null,
|
||||
},
|
||||
},
|
||||
uploadables: {
|
||||
"input.horizontalLogoFile": file,
|
||||
},
|
||||
onSuccess: () => {
|
||||
setHorizontalLogoPreview(null);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const deleteDialogRef = useDialogRef();
|
||||
@@ -416,13 +443,13 @@ export default function SettingsPage({ queryRef }: Props) {
|
||||
size="xl"
|
||||
/>
|
||||
<FileButton
|
||||
disabled={formState.isSubmitting}
|
||||
disabled={formState.isSubmitting || isUpdatingOrganization}
|
||||
onChange={handleLogoChange}
|
||||
variant="secondary"
|
||||
className="ml-auto"
|
||||
accept="image/png,image/jpeg,image/jpg"
|
||||
>
|
||||
{__("Change logo")}
|
||||
{isUpdatingOrganization ? __("Uploading...") : __("Change logo")}
|
||||
</FileButton>
|
||||
</div>
|
||||
</div>
|
||||
@@ -442,14 +469,18 @@ export default function SettingsPage({ queryRef }: Props) {
|
||||
</div>
|
||||
)}
|
||||
<FileButton
|
||||
disabled={formState.isSubmitting}
|
||||
disabled={formState.isSubmitting || isUpdatingOrganization}
|
||||
onChange={handleHorizontalLogoChange}
|
||||
variant="secondary"
|
||||
accept="image/png,image/jpeg,image/jpg"
|
||||
>
|
||||
{(horizontalLogoPreview || organization.horizontalLogoUrl) ? __("Change horizontal logo") : __("Upload horizontal logo")}
|
||||
{isUpdatingOrganization
|
||||
? __("Uploading...")
|
||||
: (horizontalLogoPreview || organization.horizontalLogoUrl)
|
||||
? __("Change horizontal logo")
|
||||
: __("Upload horizontal logo")}
|
||||
</FileButton>
|
||||
{(organization.horizontalLogoUrl && !horizontalLogoFile) && (
|
||||
{organization.horizontalLogoUrl && (
|
||||
<Dialog
|
||||
ref={deleteDialogRef}
|
||||
trigger={
|
||||
@@ -533,10 +564,10 @@ export default function SettingsPage({ queryRef }: Props) {
|
||||
/>
|
||||
</div>
|
||||
|
||||
{(formState.isDirty || logoFile || horizontalLogoFile) && (
|
||||
{formState.isDirty && (
|
||||
<div className="flex justify-end pt-6">
|
||||
<Button type="submit" disabled={formState.isSubmitting}>
|
||||
{formState.isSubmitting
|
||||
<Button type="submit" disabled={formState.isSubmitting || isUpdatingOrganization}>
|
||||
{(formState.isSubmitting || isUpdatingOrganization)
|
||||
? __("Updating...")
|
||||
: __("Update Organization")}
|
||||
</Button>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @generated SignedSource<<ce571f7246b6f7500a132f28c8081f84>>
|
||||
* @generated SignedSource<<ac93ed76dd8e53876d200e40f5b62edb>>
|
||||
* @lightSyntaxTransform
|
||||
* @nogrep
|
||||
*/
|
||||
@@ -14,7 +14,7 @@ export type UpdateOrganizationInput = {
|
||||
email?: string | null | undefined;
|
||||
headquarterAddress?: string | null | undefined;
|
||||
horizontalLogoFile?: any | null | undefined;
|
||||
logo?: any | null | undefined;
|
||||
logoFile?: any | null | undefined;
|
||||
name?: string | null | undefined;
|
||||
organizationId: string;
|
||||
websiteUrl?: string | null | undefined;
|
||||
|
||||
@@ -83,8 +83,8 @@ export default function AuditDetailsPage(props: Props) {
|
||||
await updateAudit({
|
||||
id: auditEntry.id,
|
||||
name: formData.name,
|
||||
validFrom: formatDatetime(formData.validFrom),
|
||||
validUntil: formatDatetime(formData.validUntil),
|
||||
validFrom: formatDatetime(formData.validFrom) ?? null,
|
||||
validUntil: formatDatetime(formData.validUntil) ?? null,
|
||||
state: formData.state,
|
||||
});
|
||||
reset(formData);
|
||||
|
||||
@@ -98,7 +98,7 @@ export default function ContinualImprovementDetailsPage(props: Props) {
|
||||
referenceId: formData.referenceId,
|
||||
description: formData.description || undefined,
|
||||
source: formData.source || undefined,
|
||||
targetDate: formatDatetime(formData.targetDate),
|
||||
targetDate: formatDatetime(formData.targetDate) ?? null,
|
||||
status: formData.status,
|
||||
priority: formData.priority,
|
||||
ownerId: formData.ownerId,
|
||||
|
||||
@@ -103,8 +103,8 @@ export default function NonconformityDetailsPage(props: Props) {
|
||||
id: nonconformity.id,
|
||||
referenceId: formData.referenceId,
|
||||
description: formData.description,
|
||||
dateIdentified: formatDatetime(formData.dateIdentified),
|
||||
dueDate: formatDatetime(formData.dueDate),
|
||||
dateIdentified: formatDatetime(formData.dateIdentified) ?? null,
|
||||
dueDate: formatDatetime(formData.dueDate) ?? null,
|
||||
rootCause: formData.rootCause,
|
||||
correctiveAction: formData.correctiveAction,
|
||||
effectivenessCheck: formData.effectivenessCheck,
|
||||
|
||||
@@ -109,8 +109,8 @@ export default function ObligationDetailsPage(props: Props) {
|
||||
requirement: formData.requirement || undefined,
|
||||
actionsToBeImplemented: formData.actionsToBeImplemented || undefined,
|
||||
regulator: formData.regulator || undefined,
|
||||
lastReviewDate: formatDatetime(formData.lastReviewDate),
|
||||
dueDate: formatDatetime(formData.dueDate),
|
||||
lastReviewDate: formatDatetime(formData.lastReviewDate) ?? null,
|
||||
dueDate: formatDatetime(formData.dueDate) ?? null,
|
||||
status: formData.status,
|
||||
ownerId: formData.ownerId,
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user