diff --git a/src/components/Dashboard/Profile/sections/ContactDetails/opportunity/OpportunityContactDetails.tsx b/src/components/Dashboard/Profile/sections/ContactDetails/opportunity/OpportunityContactDetails.tsx index f8ed5691..09efe1f4 100644 --- a/src/components/Dashboard/Profile/sections/ContactDetails/opportunity/OpportunityContactDetails.tsx +++ b/src/components/Dashboard/Profile/sections/ContactDetails/opportunity/OpportunityContactDetails.tsx @@ -1,14 +1,14 @@ "use client"; -import { useUpdateOpportunityContact } from "@/hooks/useUpdateOpportunityContact"; +import { useCreateComment } from "@/hooks/useCreateComment"; +import { useUpdateComment } from "@/hooks/useUpdateComment"; import { zodResolver } from "@hookform/resolvers/zod"; -import { ApiOpportunityGet, PreferredCommunicationType } from "need4deed-sdk"; +import { ApiOpportunityGet } from "need4deed-sdk"; import { forwardRef, useEffect, useImperativeHandle, useMemo, useState } from "react"; import { FormProvider, useForm } from "react-hook-form"; import { useTranslation } from "react-i18next"; import { FormContainer } from "../../shared/styles"; import { EditableSectionProps, EditableSectionRef } from "../../shared/types"; import { useEditingChangeNotifier } from "../../shared/useEditingChangeNotifier"; -import { useEnumTranslation } from "../shared"; import { OpportunityContactDetailsDisplay } from "./OpportunityContactDetailsDisplay"; import { OpportunityContactDetailsEdit } from "./OpportunityContactDetailsEdit"; import { @@ -20,58 +20,53 @@ type Props = { opportunity: ApiOpportunityGet; } & EditableSectionProps; -const COMMUNICATION_TYPES = Object.values(PreferredCommunicationType); - export const OpportunityContactDetails = forwardRef(function OpportunityContactDetails( { opportunity, onEditingChange }, ref, ) { const { t } = useTranslation(); - const { mutate: updateContact, isPending } = useUpdateOpportunityContact(opportunity.id); const [isEditing, setIsEditing] = useState(false); useEditingChangeNotifier(isEditing, onEditingChange); - const { options, keysToLabels, labelsToKeys } = useEnumTranslation( - COMMUNICATION_TYPES, - "dashboard.opportunityProfile.contactDetails.waysToContact", + const schema = createOpportunityContactDetailsSchema(t); + + // All comments that use the <|> delimiter (structural: 5+ parts) sorted oldest-first. + // The oldest is the system comment from the public form; any newer one is a coordinator override. + const pipedComments = useMemo( + () => + [...opportunity.comments] + .filter((c) => c.content.split("<|>").length >= 5) + .sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime()), + [opportunity.comments], ); - const schema = createOpportunityContactDetailsSchema(t); + // The most recent <|> comment is the active contact data. + const latestPipedComment = pipedComments.at(-1); + + // When 2+ <|> comments exist the newest is the coordinator's override — PATCH it on save. + // When only 1 exists (system comment) POST a new comment instead. + const coordinatorCommentId = pipedComments.length > 1 ? (pipedComments.at(-1)?.id ?? null) : null; + + // Preserve address/plz from the original system comment so they survive coordinator edits. + const originalParts = (pipedComments[0]?.content ?? "").split("<|>"); + const originalAddress = originalParts[2] ?? ""; + const originalPlz = originalParts[3] ?? ""; + + const { mutate: createComment, isPending: isCreating } = useCreateComment(opportunity.id, "opportunity"); + const { mutate: updateComment, isPending: isUpdating } = useUpdateComment( + opportunity.id, + coordinatorCommentId ?? 0, + "opportunity", + ); const initialFormValues = useMemo(() => { - const raw = opportunity.contact.waysToContact; - const validTypes = new Set(COMMUNICATION_TYPES); - - const waysToContact: PreferredCommunicationType[] = Array.isArray(raw) - ? raw.filter((v): v is PreferredCommunicationType => validTypes.has(v)) - : typeof raw === "string" && validTypes.has(raw) - ? [raw as PreferredCommunicationType] - : []; - - const hasContactData = !!(opportunity.contact.name || opportunity.contact.phone || opportunity.contact.email); - - if (!hasContactData) { - const sorted = [...opportunity.comments].sort( - (a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime(), - ); - for (const comment of sorted) { - const parts = comment.content.split("<|>"); - if (parts.length >= 5) { - return { name: parts[1] ?? "", phone: parts[4] ?? "", email: parts[0] ?? "", waysToContact: [] }; - } - } + if (latestPipedComment) { + const parts = latestPipedComment.content.split("<|>"); + return { name: parts[1] ?? "", phone: parts[4] ?? "", email: parts[0] ?? "" }; } - - // Fields are listed explicitly to stay in sync with OpportunityContactDetailsFormData. - // If the schema adds or removes fields, update this object accordingly. - return { - name: opportunity.contact.name ?? "", - phone: opportunity.contact.phone ?? "", - email: opportunity.contact.email ?? "", - waysToContact, - }; - }, [opportunity.contact, opportunity.comments]); + return { name: "", phone: "", email: "" }; + }, [latestPipedComment]); const methods = useForm({ resolver: zodResolver(schema), @@ -91,18 +86,14 @@ export const OpportunityContactDetails = forwardRef(f }; const onSubmit = (values: OpportunityContactDetailsFormData) => { - updateContact( - { - contact: { - id: opportunity.contact.id, - name: values.name, - phone: values.phone, - email: values.email, - waysToContact: values.waysToContact, - }, - }, - { onSuccess: () => setIsEditing(false) }, - ); + const text = `${values.email}<|>${values.name}<|>${originalAddress}<|>${originalPlz}<|>${values.phone}`; + const onSuccess = () => setIsEditing(false); + + if (coordinatorCommentId !== null) { + updateComment({ text }, { onSuccess }); + } else { + createComment({ text, entityType: "opportunity", entityId: opportunity.id }, { onSuccess }); + } }; useEffect(() => { @@ -116,17 +107,14 @@ export const OpportunityContactDetails = forwardRef(f {isEditing ? ( ) : ( - + )} ); -}); \ No newline at end of file +}); diff --git a/src/components/Dashboard/Profile/sections/ContactDetails/opportunity/OpportunityContactDetailsDisplay.tsx b/src/components/Dashboard/Profile/sections/ContactDetails/opportunity/OpportunityContactDetailsDisplay.tsx index e7255043..d151e180 100644 --- a/src/components/Dashboard/Profile/sections/ContactDetails/opportunity/OpportunityContactDetailsDisplay.tsx +++ b/src/components/Dashboard/Profile/sections/ContactDetails/opportunity/OpportunityContactDetailsDisplay.tsx @@ -1,15 +1,10 @@ import { EditableField } from "@/components/EditableField/EditableField"; -import { PreferredCommunicationType } from "need4deed-sdk"; import { useFormContext } from "react-hook-form"; import { useTranslation } from "react-i18next"; import { FormDetails } from "../../shared/styles"; import { OpportunityContactDetailsFormData } from "./opportunityContactDetailsSchema"; -type Props = { - keysToLabels: (keys: PreferredCommunicationType[]) => string[]; -}; - -export const OpportunityContactDetailsDisplay = ({ keysToLabels }: Props) => { +export const OpportunityContactDetailsDisplay = () => { const { t } = useTranslation(); const { watch } = useFormContext(); const values = watch(); @@ -39,15 +34,6 @@ export const OpportunityContactDetailsDisplay = ({ keysToLabels }: Props) => { value={values.email} setValue={() => {}} /> - - {}} - options={[]} - /> ); }; diff --git a/src/components/Dashboard/Profile/sections/ContactDetails/opportunity/OpportunityContactDetailsEdit.tsx b/src/components/Dashboard/Profile/sections/ContactDetails/opportunity/OpportunityContactDetailsEdit.tsx index 345afffd..273f6d1d 100644 --- a/src/components/Dashboard/Profile/sections/ContactDetails/opportunity/OpportunityContactDetailsEdit.tsx +++ b/src/components/Dashboard/Profile/sections/ContactDetails/opportunity/OpportunityContactDetailsEdit.tsx @@ -1,28 +1,17 @@ import Button from "@/components/core/button/Button/Button"; import { EditableField } from "@/components/EditableField/EditableField"; -import { PreferredCommunicationType } from "need4deed-sdk"; import { Controller, useFormContext } from "react-hook-form"; import { useTranslation } from "react-i18next"; import { FormButtonRow, FormDetails } from "../../shared/styles"; import { OpportunityContactDetailsFormData } from "./opportunityContactDetailsSchema"; type Props = { - options: string[]; - keysToLabels: (keys: PreferredCommunicationType[]) => string[]; - labelsToKeys: (labels: (string | number)[]) => PreferredCommunicationType[]; onCancel: () => void; onSubmit: () => void; isPending: boolean; }; -export const OpportunityContactDetailsEdit = ({ - options, - keysToLabels, - labelsToKeys, - onCancel, - onSubmit, - isPending, -}: Props) => { +export const OpportunityContactDetailsEdit = ({ onCancel, onSubmit, isPending }: Props) => { const { t } = useTranslation(); const { control, @@ -76,22 +65,6 @@ export const OpportunityContactDetailsEdit = ({ /> )} /> - - ( - field.onChange(labelsToKeys(Array.isArray(value) ? value : [value]))} - options={options} - errorMessage={errors.waysToContact?.message} - /> - )} - /> diff --git a/src/components/Dashboard/Profile/sections/ContactDetails/opportunity/opportunityContactDetailsSchema.ts b/src/components/Dashboard/Profile/sections/ContactDetails/opportunity/opportunityContactDetailsSchema.ts index e91e7feb..9f7c4383 100644 --- a/src/components/Dashboard/Profile/sections/ContactDetails/opportunity/opportunityContactDetailsSchema.ts +++ b/src/components/Dashboard/Profile/sections/ContactDetails/opportunity/opportunityContactDetailsSchema.ts @@ -1,5 +1,4 @@ import { PHONE_NUMBER_REGEX } from "@/config/constants"; -import { PreferredCommunicationType } from "need4deed-sdk"; import { z } from "zod"; export const createOpportunityContactDetailsSchema = (t: (key: string) => string) => { @@ -13,9 +12,6 @@ export const createOpportunityContactDetailsSchema = (t: (key: string) => string .string() .min(1, t("dashboard.opportunityProfile.contactDetails.validation.emailRequired")) .email(t("dashboard.opportunityProfile.contactDetails.validation.emailInvalid")), - waysToContact: z - .array(z.enum(Object.values(PreferredCommunicationType))) - .min(1, t("dashboard.opportunityProfile.contactDetails.validation.waysToContactRequired")), }); };