- Add mailbox persistence mapper for domain object mapping. - Create Sequelize repository for mailbox data access. - Define common mailbox schemas and types for validation. - Implement mailbox API for frontend communication. - Develop mailbox form component for creating and editing mailboxes. - Add internationalization support for mailbox labels and messages. - Create mailbox use form for managing mailbox usage periods. - Build mailboxes page to display and manage mailboxes and their usage.
231 lines
7.7 KiB
TypeScript
231 lines
7.7 KiB
TypeScript
import { zodResolver } from "@hookform/resolvers/zod";
|
|
import { Button } from "@repo/shadcn-ui/components/button";
|
|
import { ContextHelp } from "@repo/shadcn-ui/components/context-help";
|
|
import { FormActions, FormDialog } from "@repo/shadcn-ui/components/form-dialog";
|
|
import { Input } from "@repo/shadcn-ui/components/input";
|
|
import { Label } from "@repo/shadcn-ui/components/label";
|
|
import { SelectWithAction } from "@repo/shadcn-ui/components/select-with-action";
|
|
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
|
import { useEffect, useId, useRef, useState } from "react";
|
|
import { useForm } from "react-hook-form";
|
|
import { useTranslation } from "react-i18next";
|
|
|
|
import type { CompanyDTO } from "../../common";
|
|
import {
|
|
type MailboxUseDTO,
|
|
type MailboxUseInput,
|
|
MailboxUseInputSchema,
|
|
} from "../../common/mailboxes";
|
|
import { PersonForm } from "../components/person-form";
|
|
|
|
import { mailKeys } from "./mail-api";
|
|
import { MailFeedback } from "./mail-feedback";
|
|
import { mailboxApi, mailboxKeys } from "./mailbox-api";
|
|
import { MailboxForm } from "./mailbox-form";
|
|
|
|
export function MailboxUseForm({
|
|
company,
|
|
csrfToken,
|
|
use,
|
|
initialMailboxId = "",
|
|
initialPersonId = "",
|
|
onSaved,
|
|
allowQuickCreate = true,
|
|
}: {
|
|
company: CompanyDTO;
|
|
csrfToken: string;
|
|
use?: MailboxUseDTO | undefined;
|
|
initialMailboxId?: string;
|
|
initialPersonId?: string;
|
|
onSaved: () => void;
|
|
allowQuickCreate?: boolean;
|
|
}) {
|
|
const { t } = useTranslation("companies");
|
|
const prefix = useId();
|
|
const client = useQueryClient();
|
|
const [quick, setQuick] = useState<"person" | "mailbox">();
|
|
const mounted = useRef(true);
|
|
useEffect(() => {
|
|
mounted.current = true;
|
|
return () => {
|
|
mounted.current = false;
|
|
};
|
|
}, []);
|
|
const query = useQuery({
|
|
queryKey: mailboxKeys.company(company.id),
|
|
queryFn: () => mailboxApi.request(company.id),
|
|
});
|
|
const today = new Date().toISOString().slice(0, 10);
|
|
const form = useForm<MailboxUseInput>({
|
|
resolver: zodResolver(MailboxUseInputSchema),
|
|
defaultValues: useDefaults(use, initialMailboxId, initialPersonId, today),
|
|
});
|
|
const values = form.watch();
|
|
const mutation = useMutation({
|
|
mutationFn: (input: MailboxUseInput) =>
|
|
mailboxApi.request(company.id, input, csrfToken, use?.id),
|
|
onSuccess: async () => {
|
|
await Promise.all([
|
|
client.invalidateQueries({ queryKey: mailboxKeys.company(company.id) }),
|
|
client.invalidateQueries({ queryKey: mailKeys.company(company.id) }),
|
|
]);
|
|
if (mounted.current) {
|
|
onSaved();
|
|
}
|
|
},
|
|
});
|
|
return (
|
|
<>
|
|
<form className="space-y-5" onSubmit={form.handleSubmit((input) => mutation.mutate(input))}>
|
|
<fieldset className="space-y-4" disabled={mutation.isPending}>
|
|
<div className="space-y-2">
|
|
<Label htmlFor={`${prefix}-mailbox`}>{t("mailbox.titleSingle")}</Label>
|
|
<SelectWithAction
|
|
action={
|
|
canCreate(!!use, initialMailboxId, allowQuickCreate)
|
|
? { label: t("mailbox.new"), onClick: () => setQuick("mailbox") }
|
|
: undefined
|
|
}
|
|
disabled={!!use || !!initialMailboxId}
|
|
id={`${prefix}-mailbox`}
|
|
label={t("mailbox.titleSingle")}
|
|
onValueChange={(id) => form.setValue("mailboxId", id)}
|
|
options={(query.data?.mailboxes ?? [])
|
|
.filter((item) => item.values.active || item.id === values.mailboxId)
|
|
.map((item) => ({ value: item.id, label: item.values.address }))}
|
|
placeholder={t("mail.choose")}
|
|
required
|
|
value={values.mailboxId}
|
|
/>
|
|
</div>
|
|
<div className="space-y-2">
|
|
<Label htmlFor={`${prefix}-person`}>{t("mail.person")}</Label>
|
|
<SelectWithAction
|
|
action={
|
|
canCreate(!!use, initialPersonId, allowQuickCreate)
|
|
? { label: t("newPerson"), onClick: () => setQuick("person") }
|
|
: undefined
|
|
}
|
|
disabled={!!use || !!initialPersonId}
|
|
id={`${prefix}-person`}
|
|
label={t("mail.person")}
|
|
onValueChange={(id) => form.setValue("personId", id)}
|
|
options={company.people
|
|
.filter((item) => item.active || item.id === values.personId)
|
|
.map((item) => ({ value: item.id, label: item.name }))}
|
|
placeholder={t("mail.choose")}
|
|
required
|
|
value={values.personId}
|
|
/>
|
|
</div>
|
|
<div className="grid gap-4 sm:grid-cols-2">
|
|
<div className="space-y-2">
|
|
<Label htmlFor={`${prefix}-start`}>{t("mailbox.start")}</Label>
|
|
<Input
|
|
disabled={!!use}
|
|
id={`${prefix}-start`}
|
|
required
|
|
type="date"
|
|
{...form.register("startDate")}
|
|
/>
|
|
</div>
|
|
<div className="space-y-2">
|
|
<Label htmlFor={`${prefix}-end`}>{t("mailbox.end")}</Label>
|
|
<Input
|
|
id={`${prefix}-end`}
|
|
onChange={(event) => form.setValue("endDate", event.target.value || null)}
|
|
required={!!use}
|
|
type="date"
|
|
value={values.endDate ?? ""}
|
|
/>
|
|
</div>
|
|
</div>
|
|
</fieldset>
|
|
{Object.keys(form.formState.errors).length > 0 && (
|
|
<p role="alert">{t("mail.errors.INVALID_REQUEST")}</p>
|
|
)}
|
|
<MailFeedback error={mutation.error ?? query.error} />
|
|
<ContextHelp
|
|
description={t("mailbox.useDescription")}
|
|
examples={t("mailbox.useExamples")}
|
|
examplesLabel={t("mail.examplesLabel")}
|
|
guidance={t("mailbox.useGuidance")}
|
|
title={t("mailbox.useHelp")}
|
|
/>
|
|
<FormActions>
|
|
<Button disabled={mutation.isPending || !query.data} type="submit">
|
|
{t(mutation.isPending ? "saving" : "save")}
|
|
</Button>
|
|
</FormActions>
|
|
</form>
|
|
{quick === "person" && (
|
|
<FormDialog
|
|
cancelLabel={t("cancel")}
|
|
formActions
|
|
onOpenChange={(open) => {
|
|
if (!open) {
|
|
setQuick(undefined);
|
|
}
|
|
}}
|
|
open
|
|
title={t("newPerson")}
|
|
>
|
|
<PersonForm
|
|
companyId={company.id}
|
|
csrfToken={csrfToken}
|
|
onSaved={(id) => {
|
|
if (mounted.current) {
|
|
form.setValue("personId", id);
|
|
setQuick(undefined);
|
|
}
|
|
}}
|
|
requireActive
|
|
/>
|
|
</FormDialog>
|
|
)}
|
|
{quick === "mailbox" && (
|
|
<FormDialog
|
|
cancelLabel={t("cancel")}
|
|
formActions
|
|
onOpenChange={(open) => {
|
|
if (!open) {
|
|
setQuick(undefined);
|
|
}
|
|
}}
|
|
open
|
|
title={t("mailbox.new")}
|
|
>
|
|
<MailboxForm
|
|
allowQuickCreate={false}
|
|
company={company}
|
|
csrfToken={csrfToken}
|
|
onSaved={(id) => {
|
|
if (mounted.current) {
|
|
form.setValue("mailboxId", id);
|
|
setQuick(undefined);
|
|
}
|
|
}}
|
|
/>
|
|
</FormDialog>
|
|
)}
|
|
</>
|
|
);
|
|
}
|
|
|
|
function useDefaults(
|
|
existing: MailboxUseDTO | undefined,
|
|
mailboxId: string,
|
|
personId: string,
|
|
today: string
|
|
): MailboxUseInput {
|
|
if (existing) {
|
|
const values = existing.values;
|
|
return { ...values, endDate: today > values.startDate ? today : values.startDate };
|
|
}
|
|
return { mailboxId, personId, startDate: today, endDate: null };
|
|
}
|
|
|
|
function canCreate(editing: boolean, initial: string, allowed: boolean) {
|
|
return !(editing || initial) && allowed;
|
|
}
|