Revisión de ajustes

This commit is contained in:
David Arranz 2024-08-09 20:12:47 +02:00
parent cf56733ab6
commit 006ef44ffd
8 changed files with 313 additions and 259 deletions

View File

@ -1,4 +1,4 @@
import { FormTextAreaField } from "@/components"; import { ErrorOverlay, FormTextAreaField, LoadingOverlay } from "@/components";
import { import {
Alert, Alert,
AlertDescription, AlertDescription,
@ -15,39 +15,39 @@ import {
import { t } from "i18next"; import { t } from "i18next";
import { AlertCircleIcon } from "lucide-react"; import { AlertCircleIcon } from "lucide-react";
import { useState } from "react"; import { useMemo } from "react";
import { SubmitHandler, useForm } from "react-hook-form"; import { SubmitHandler, useForm } from "react-hook-form";
import { Trans } from "react-i18next"; import { Trans } from "react-i18next";
import { IUpdateProfile_Request_DTO } from "@shared/contexts";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import { toast } from "react-toastify";
import { useSettings } from "./hooks"; import { useSettings } from "./hooks";
type SettingsDataForm = { type SettingsDataForm = IUpdateProfile_Request_DTO;
contact_information: string;
default_payment_method: string;
default_notes: string;
default_legal_terms: string;
default_quote_validity: string;
};
export const SettingsEditor = () => { export const SettingsEditor = () => {
const [loading, setLoading] = useState(false); const { useOne, useUpdate } = useSettings();
const { useQuery, useMutation } = useSettings(); const { data, status, error: queryError } = useOne();
const { data } = useQuery; const defaultValues = useMemo(
const { mutate } = useMutation; () => ({
const form = useForm<SettingsDataForm>({
mode: "onBlur",
values: data,
defaultValues: {
contact_information: "", contact_information: "",
default_payment_method: "", default_payment_method: "",
default_notes: "", default_notes: "",
default_legal_terms: "", default_legal_terms: "",
default_quote_validity: "", default_quote_validity: "",
}, }),
[]
);
const { mutate } = useUpdate();
const form = useForm<SettingsDataForm>({
mode: "onBlur",
values: data?.dealer,
defaultValues,
/*resolver: joiResolver( /*resolver: joiResolver(
Joi.object({ Joi.object({
email: Joi.string() email: Joi.string()
@ -61,175 +61,198 @@ export const SettingsEditor = () => {
),*/ ),*/
}); });
const { formState } = form;
const { isSubmitting } = formState;
const onSubmit: SubmitHandler<SettingsDataForm> = async (data) => { const onSubmit: SubmitHandler<SettingsDataForm> = async (data) => {
try { console.log(data);
setLoading(true); mutate(data, {
console.log(data); onError: (error) => {
mutate(data); console.debug(error);
} finally { toast.error(error.message);
setLoading(false); //alert(error.message);
} },
//onSettled: () => {},
onSuccess: () => {
toast("Ajustes guardados");
//clear();
},
});
}; };
if (isSubmitting) {
return <LoadingOverlay title='Guardando ajustes' />;
}
if (status === "error") {
return <ErrorOverlay errorMessage={queryError.message} />;
}
if (status !== "success") {
return <LoadingOverlay />;
}
return ( return (
<Form {...form}> <>
<form onSubmit={form.handleSubmit(onSubmit)}> <div className='grid w-full max-w-6xl gap-2 mx-auto'>
<div className='mx-auto grid w-full max-w-6xl items-start gap-6 md:grid-cols-[180px_1fr] lg:grid-cols-[250px_1fr]'> <h1 className='text-2xl font-semibold md:text-3xl'>
{form.formState.errors.root?.message && ( <Trans i18nKey='settings.edit.title' />
<Alert variant='destructive'> </h1>
<AlertCircleIcon className='w-4 h-4' /> </div>
<AlertTitle>
<Trans i18nKey='common.error' />
</AlertTitle>
<AlertDescription>{form.formState.errors.root?.message}</AlertDescription>
</Alert>
)}
<nav className='grid gap-4 text-sm text-muted-foreground'> <Form {...form}>
<Link to='#' className='font-semibold text-primary'> <form onSubmit={form.handleSubmit(onSubmit)}>
<Trans i18nKey='settings.quotes.title' /> <div className='mx-auto grid w-full max-w-6xl items-start gap-6 md:grid-cols-[180px_1fr] lg:grid-cols-[250px_1fr]'>
</Link> {form.formState.errors.root?.message && (
<Link to='#'> <Alert variant='destructive'>
<Trans i18nKey='settings.quotes.general' /> <AlertCircleIcon className='w-4 h-4' />
</Link> <AlertTitle>
<Link to='#'>Integrations</Link> <Trans i18nKey='common.error' />
<Link to='#'>Support</Link> </AlertTitle>
<Link to='#'>Organizations</Link> <AlertDescription>{form.formState.errors.root?.message}</AlertDescription>
<Link to='#'>Advanced</Link> </Alert>
</nav> )}
<div className='grid gap-6'>
<Card>
<CardHeader>
<CardTitle>
<Trans i18nKey='settings.quotes.contact_information.label' />
</CardTitle>
<CardDescription>
<Trans i18nKey='settings.quotes.contact_information.desc' />
</CardDescription>
</CardHeader>
<CardContent>
<FormTextAreaField
disabled={loading}
placeholder={t("settings.quotes.contact_information.placeholder")}
{...form.register("contact_information", {
required: true,
})}
errors={form.formState.errors}
/>
</CardContent>
<CardFooter className='px-6 py-4 border-t'>
<Button>
<Trans i18nKey='common.save' />
</Button>
</CardFooter>
</Card>
<Card> <nav className='grid gap-4 text-sm text-muted-foreground'>
<CardHeader> <Link to='#' className='font-semibold text-primary'>
<CardTitle> <Trans i18nKey='settings.edit.tabs.profile' />
<Trans i18nKey='settings.quotes.default_payment_method.label' /> </Link>
</CardTitle> <Link to='#'>
<CardDescription> <Trans i18nKey='settings.edit.tabs.quotes' />
<Trans i18nKey='settings.quotes.default_payment_method.desc' /> </Link>
</CardDescription> <Link to='#'>
</CardHeader> <Trans i18nKey='settings.edit.tabs.legal' />
<CardContent> </Link>
<FormTextAreaField </nav>
disabled={loading} <div className='grid gap-6'>
placeholder={t("settings.quotes.default_payment_method.placeholder")} <Card>
{...form.register("default_payment_method", { <CardHeader>
required: true, <CardTitle>
})} <Trans i18nKey='settings.form_fields.contact_information.label' />
errors={form.formState.errors} </CardTitle>
/> <CardDescription>
</CardContent> <Trans i18nKey='settings.form_fields.contact_information.desc' />
<CardFooter className='px-6 py-4 border-t'> </CardDescription>
<Button> </CardHeader>
<Trans i18nKey='common.save' /> <CardContent>
</Button> <FormTextAreaField
</CardFooter> placeholder={t("settings.form_fields.contact_information.placeholder")}
</Card> {...form.register("contact_information", {
required: true,
})}
errors={form.formState.errors}
/>
</CardContent>
<CardFooter className='px-6 py-4 border-t'>
<Button>
<Trans i18nKey='common.save' />
</Button>
</CardFooter>
</Card>
<Card> <Card>
<CardHeader> <CardHeader>
<CardTitle> <CardTitle>
<Trans i18nKey='settings.quotes.default_quote_validity.label' /> <Trans i18nKey='settings.form_fields.default_payment_method.label' />
</CardTitle> </CardTitle>
<CardDescription> <CardDescription>
<Trans i18nKey='settings.quotes.default_quote_validity.desc' /> <Trans i18nKey='settings.form_fields.default_payment_method.desc' />
</CardDescription> </CardDescription>
</CardHeader> </CardHeader>
<CardContent> <CardContent>
<FormTextAreaField <FormTextAreaField
disabled={loading} placeholder={t("settings.form_fields.default_payment_method.placeholder")}
placeholder={t("settings.quotes.default_quote_validity.placeholder")} {...form.register("default_payment_method", {
{...form.register("default_quote_validity", { required: true,
required: true, })}
})} errors={form.formState.errors}
errors={form.formState.errors} />
/> </CardContent>
</CardContent> <CardFooter className='px-6 py-4 border-t'>
<CardFooter className='px-6 py-4 border-t'> <Button>
<Button> <Trans i18nKey='common.save' />
<Trans i18nKey='common.save' /> </Button>
</Button> </CardFooter>
</CardFooter> </Card>
</Card>
<Card> <Card>
<CardHeader> <CardHeader>
<CardTitle> <CardTitle>
<Trans i18nKey='settings.quotes.default_notes.label' /> <Trans i18nKey='settings.form_fields.default_quote_validity.label' />
</CardTitle> </CardTitle>
<CardDescription> <CardDescription>
<Trans i18nKey='settings.quotes.default_notes.desc' /> <Trans i18nKey='settings.form_fields.default_quote_validity.desc' />
</CardDescription> </CardDescription>
</CardHeader> </CardHeader>
<CardContent> <CardContent>
<FormTextAreaField <FormTextAreaField
disabled={loading} placeholder={t("settings.form_fields.default_quote_validity.placeholder")}
placeholder={t("settings.quotes.default_notes.placeholder")} {...form.register("default_quote_validity", {
{...form.register("default_notes", { required: true,
required: true, })}
})} errors={form.formState.errors}
errors={form.formState.errors} />
/> </CardContent>
</CardContent> <CardFooter className='px-6 py-4 border-t'>
<CardFooter className='px-6 py-4 border-t'> <Button>
<Button> <Trans i18nKey='common.save' />
<Trans i18nKey='common.save' /> </Button>
</Button> </CardFooter>
</CardFooter> </Card>
</Card>
<Card> <Card>
<CardHeader> <CardHeader>
<CardTitle> <CardTitle>
<Trans i18nKey='settings.quotes.default_legal_terms.label' /> <Trans i18nKey='settings.form_fields.default_notes.label' />
</CardTitle> </CardTitle>
<CardDescription> <CardDescription>
<Trans i18nKey='settings.quotes.default_legal_terms.desc' /> <Trans i18nKey='settings.form_fields.default_notes.desc' />
</CardDescription> </CardDescription>
</CardHeader> </CardHeader>
<CardContent> <CardContent>
<FormTextAreaField <FormTextAreaField
disabled={loading} placeholder={t("settings.form_fields.default_notes.placeholder")}
placeholder={t("settings.quotes.default_legal_terms.placeholder")} {...form.register("default_notes", {
{...form.register("default_legal_terms", { required: true,
required: true, })}
})} errors={form.formState.errors}
errors={form.formState.errors} />
/> </CardContent>
</CardContent> <CardFooter className='px-6 py-4 border-t'>
<CardFooter className='px-6 py-4 border-t'> <Button>
<Button> <Trans i18nKey='common.save' />
<Trans i18nKey='common.save' /> </Button>
</Button> </CardFooter>
</CardFooter> </Card>
</Card>
<Card>
<CardHeader>
<CardTitle>
<Trans i18nKey='settings.form_fields.default_legal_terms.label' />
</CardTitle>
<CardDescription>
<Trans i18nKey='settings.form_fields.default_legal_terms.desc' />
</CardDescription>
</CardHeader>
<CardContent>
<FormTextAreaField
placeholder={t("settings.form_fields.default_legal_terms.placeholder")}
{...form.register("default_legal_terms", {
required: true,
})}
errors={form.formState.errors}
/>
</CardContent>
<CardFooter className='px-6 py-4 border-t'>
<Button>
<Trans i18nKey='common.save' />
</Button>
</CardFooter>
</Card>
</div>
</div> </div>
</div> </form>
</form> </Form>
</Form> </>
); );
}; };

View File

@ -1,7 +1,12 @@
import { useOne, useSave } from "@/lib/hooks/useDataSource"; import { useOne, useSave } from "@/lib/hooks/useDataSource";
import { TDataSourceError } from "@/lib/hooks/useDataSource/types";
import { useDataSource } from "@/lib/hooks/useDataSource/useDataSource"; import { useDataSource } from "@/lib/hooks/useDataSource/useDataSource";
import { useQueryKey } from "@/lib/hooks/useQueryKey"; import { useQueryKey } from "@/lib/hooks/useQueryKey";
import { IGetProfileResponse_DTO } from "@shared/contexts"; import {
IGetProfileResponse_DTO,
IUpdateProfile_Request_DTO,
IUpdateProfileResponse_DTO,
} from "@shared/contexts";
export type UseSettingsGetParamsType = { export type UseSettingsGetParamsType = {
enabled?: boolean; enabled?: boolean;
@ -13,23 +18,25 @@ export const useSettings = (params?: UseSettingsGetParamsType) => {
const keys = useQueryKey(); const keys = useQueryKey();
return { return {
useQuery: useOne<IGetProfileResponse_DTO>({ useOne: () =>
queryKey: keys().data().resource("settings").action("one").id("").params().get(), useOne<IGetProfileResponse_DTO>({
queryFn: () => queryKey: keys().data().resource("settings").action("one").id("me").params().get(),
dataSource.getOne({ queryFn: () =>
resource: "profile", dataSource.getOne({
id: "", resource: "profile",
}), id: "",
...params, }),
}), ...params,
useMutation: useSave({ }),
mutationKey: keys().data().resource("settings").action("one").id("").params().get(), useUpdate: () =>
mutationFn: (data) => useSave<IUpdateProfileResponse_DTO, TDataSourceError, IUpdateProfile_Request_DTO>({
dataSource.updateOne({ mutationKey: keys().data().resource("settings").action("one").id("me").params().get(),
resource: "profile", mutationFn: (data) =>
data, dataSource.updateOne({
id: "", resource: "profile",
}), data,
}), id: "",
}),
}),
}; };
}; };

View File

@ -1,19 +1,14 @@
import { Layout, LayoutContent, LayoutHeader } from "@/components"; import { Layout, LayoutContent, LayoutHeader } from "@/components";
import { PropsWithChildren } from "react"; import { PropsWithChildren } from "react";
import { Trans } from "react-i18next"; import { SettingsProvider } from "./SettingsContext";
export const SettingsLayout = ({ children }: PropsWithChildren) => { export const SettingsLayout = ({ children }: PropsWithChildren) => {
return ( return (
<Layout> <SettingsProvider>
<LayoutHeader /> <Layout>
<LayoutContent> <LayoutHeader />
<div className='grid w-full max-w-6xl gap-2 mx-auto'> <LayoutContent>{children}</LayoutContent>
<h1 className='text-2xl font-semibold md:text-3xl'> </Layout>
<Trans i18nKey='settings.title' /> </SettingsProvider>
</h1>
</div>
{children}
</LayoutContent>
</Layout>
); );
}; };

View File

@ -6,7 +6,7 @@
"yes": "Yes", "yes": "Yes",
"save": "Save", "save": "Save",
"accept": "OK", "accept": "OK",
"hide": "Ocultar", "hide": "Hide",
"back": "Back", "back": "Back",
"upload": "Upload", "upload": "Upload",
"continue": "Continue", "continue": "Continue",
@ -25,9 +25,9 @@
"filter_placeholder": "Escribe aquí para filtrar...", "filter_placeholder": "Escribe aquí para filtrar...",
"reset_filter": "Quitar el filtro", "reset_filter": "Quitar el filtro",
"error": "Error", "error": "Error",
"actions": "Acciones", "actions": "Actions",
"open_menu": "Abrir el menú", "open_menu": "Open menu",
"duplicate_rows": "Duplicar", "duplicate_rows": "Duplicate",
"duplicate_rows_tooltip": "Duplica las fila(s) seleccionadas(s)", "duplicate_rows_tooltip": "Duplica las fila(s) seleccionadas(s)",
"pick_date": "Elige una fecha", "pick_date": "Elige una fecha",
"required_field": "Este campo es obligatorio", "required_field": "Este campo es obligatorio",
@ -35,19 +35,19 @@
"edit": "Editar" "edit": "Editar"
}, },
"main_menu": { "main_menu": {
"home": "Inicio", "home": "Home",
"settings": "Ajustes", "settings": "Settings",
"dealers": "Distribuidores", "dealers": "Dealers",
"catalog": "Catálogo", "catalog": "Catalog",
"quotes": "Cotizaciones", "quotes": "Quotes",
"search_placeholder": "Buscar productos, cotizaciones, etc...", "search_placeholder": "Type here for search quotes and articles",
"user": { "user": {
"user_menu": "Menú del usuario", "user_menu": "User menu",
"my_account": "Mi cuenta", "my_account": "My account",
"profile": "Perfil", "profile": "Profile",
"settings": "Ajustes", "settings": "Settings",
"support": "Soporte", "support": "Support",
"logout": "Salir" "logout": "Logout"
}, },
"logout": {} "logout": {}
}, },
@ -212,14 +212,31 @@
} }
}, },
"settings": { "settings": {
"title": "Ajustes", "edit": {
"quotes": { "title": "Settings",
"title": "Cotizaciones", "subtitle": "",
"tabs": {
"profile": "Profile settings",
"quotes": "Quote settings",
"legal": "Legal settings"
}
},
"form_fields": {
"image": {
"label": "Información de contacto",
"placeholder": "placeholder",
"desc": "Información de contacto"
},
"contact_information": { "contact_information": {
"label": "Información de contacto", "label": "Información de contacto",
"placeholder": "placeholder", "placeholder": "placeholder",
"desc": "Información de contacto" "desc": "Información de contacto"
}, },
"default_legal_terms": {
"label": "Cláusulas legales",
"placeholder": "",
"desc": "desc"
},
"default_payment_method": { "default_payment_method": {
"label": "Forma de pago", "label": "Forma de pago",
"placeholder": "placeholder", "placeholder": "placeholder",
@ -230,11 +247,6 @@
"placeholder": "", "placeholder": "",
"desc": "desc" "desc": "desc"
}, },
"default_legal_terms": {
"label": "Cláusulas legales",
"placeholder": "",
"desc": "desc"
},
"default_quote_validity": { "default_quote_validity": {
"label": "Validez por defecto", "label": "Validez por defecto",
"placeholder": "", "placeholder": "",

View File

@ -224,13 +224,33 @@
}, },
"settings": { "settings": {
"title": "Ajustes", "title": "Ajustes",
"profile": {
"title": "Ajustes de perfil",
"items": {
"image": {
"label": "Información de contacto",
"placeholder": "placeholder",
"desc": "Información de contacto"
},
"contact_information": {
"label": "Información de contacto",
"placeholder": "placeholder",
"desc": "Información de contacto"
}
}
},
"legal": {
"title": "Ajustes legales",
"items": {
"default_legal_terms": {
"label": "Cláusulas legales",
"placeholder": "",
"desc": "desc"
}
}
},
"quotes": { "quotes": {
"title": "Cotizaciones", "title": "Ajustes para cotizaciones",
"contact_information": {
"label": "Información de contacto",
"placeholder": "placeholder",
"desc": "Información de contacto"
},
"default_payment_method": { "default_payment_method": {
"label": "Forma de pago", "label": "Forma de pago",
"placeholder": "placeholder", "placeholder": "placeholder",
@ -241,11 +261,6 @@
"placeholder": "", "placeholder": "",
"desc": "desc" "desc": "desc"
}, },
"default_legal_terms": {
"label": "Cláusulas legales",
"placeholder": "",
"desc": "desc"
},
"default_quote_validity": { "default_quote_validity": {
"label": "Validez por defecto", "label": "Validez por defecto",
"placeholder": "", "placeholder": "",

View File

@ -7,7 +7,7 @@ import {
import { IRepositoryManager } from "@/contexts/common/domain"; import { IRepositoryManager } from "@/contexts/common/domain";
import { IInfrastructureError } from "@/contexts/common/infrastructure"; import { IInfrastructureError } from "@/contexts/common/infrastructure";
import { ISequelizeAdapter } from "@/contexts/common/infrastructure/sequelize"; import { ISequelizeAdapter } from "@/contexts/common/infrastructure/sequelize";
import { IUpdateProfile_Request_DTO, Note, Result, UniqueID } from "@shared/contexts"; import { IUpdateProfile_Request_DTO, Result, TextValueObject, UniqueID } from "@shared/contexts";
import { IProfileRepository, Profile } from "../domain"; import { IProfileRepository, Profile } from "../domain";
export interface IUpdateProfileUseCaseRequest extends IUseCaseRequest { export interface IUpdateProfileUseCaseRequest extends IUseCaseRequest {
@ -33,6 +33,8 @@ export class UpdateProfileUseCase
async execute(request: IUpdateProfileUseCaseRequest): Promise<UpdateProfileResponseOrError> { async execute(request: IUpdateProfileUseCaseRequest): Promise<UpdateProfileResponseOrError> {
const { userId, profileDTO } = request; const { userId, profileDTO } = request;
console.log(request);
// Comprobar que existe el profile // Comprobar que existe el profile
const exitsOrError = await this._getProfileDealer(userId); const exitsOrError = await this._getProfileDealer(userId);
if (exitsOrError.isFailure) { if (exitsOrError.isFailure) {
@ -47,11 +49,11 @@ export class UpdateProfileUseCase
const profile = exitsOrError.object; const profile = exitsOrError.object;
// Actualizar el perfil con datos actualizados // Actualizar el perfil con datos actualizados
profile.contactInformation = Note.create(profileDTO.contact_information).object; profile.contactInformation = TextValueObject.create(profileDTO.contact_information).object;
profile.defaultPaymentMethod = Note.create(profileDTO.default_payment_method).object; profile.defaultPaymentMethod = TextValueObject.create(profileDTO.default_payment_method).object;
profile.defaultLegalTerms = Note.create(profileDTO.default_legal_terms).object; profile.defaultLegalTerms = TextValueObject.create(profileDTO.default_legal_terms).object;
profile.defaultNotes = Note.create(profileDTO.default_notes).object; profile.defaultNotes = TextValueObject.create(profileDTO.default_notes).object;
profile.defaultQuoteValidity = Note.create(profileDTO.default_quote_validity).object; profile.defaultQuoteValidity = TextValueObject.create(profileDTO.default_quote_validity).object;
// Guardar los cambios // Guardar los cambios
return this._saveProfile(profile); return this._saveProfile(profile);

View File

@ -19,11 +19,11 @@ export class ProfileRepository extends SequelizeRepository<Profile> implements I
} }
public async exists(id: UniqueID): Promise<boolean> { public async exists(id: UniqueID): Promise<boolean> {
return this._exists("Profile_Model", "id", id.toPrimitive()); return this._exists("Dealer_Model", "id", id.toPrimitive());
} }
public async getById(id: UniqueID): Promise<Profile | null> { public async getById(id: UniqueID): Promise<Profile | null> {
const rawProfile: any = await this._getById("Profile_Model", id); const rawProfile: any = await this._getById("Dealer_Model", id);
if (!rawProfile === true) { if (!rawProfile === true) {
return null; return null;
@ -37,7 +37,7 @@ export class ProfileRepository extends SequelizeRepository<Profile> implements I
// borrando y luego creando // borrando y luego creando
// await this.removeById(user.id, true); // await this.removeById(user.id, true);
await this._save("Profile_Model", profile.id, userData, {}); await this._save("Dealer_Model", profile.id, userData, {});
} }
public async getByUserId(userId: UniqueID): Promise<Profile | null> { public async getByUserId(userId: UniqueID): Promise<Profile | null> {

View File

@ -11,11 +11,11 @@ export interface IUpdateProfile_Request_DTO {
export function ensureUpdateProfile_Request_DTOIsValid(userDTO: IUpdateProfile_Request_DTO) { export function ensureUpdateProfile_Request_DTOIsValid(userDTO: IUpdateProfile_Request_DTO) {
const schema = Joi.object({ const schema = Joi.object({
contact_information: Joi.string(), contact_information: Joi.string().optional().allow(null).allow("").default(""),
default_payment_method: Joi.string(), default_payment_method: Joi.string().optional().allow(null).allow("").default(""),
default_notes: Joi.string(), default_notes: Joi.string().optional().allow(null).allow("").default(""),
default_legal_terms: Joi.string(), default_legal_terms: Joi.string().optional().allow(null).allow("").default(""),
default_quote_validity: Joi.string(), default_quote_validity: Joi.string().optional().allow(null).allow("").default(""),
}).unknown(true); }).unknown(true);
const result = RuleValidator.validate<IUpdateProfile_Request_DTO>(schema, userDTO); const result = RuleValidator.validate<IUpdateProfile_Request_DTO>(schema, userDTO);