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

View File

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

View File

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

View File

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

View File

@ -224,13 +224,33 @@
},
"settings": {
"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": {
"title": "Cotizaciones",
"contact_information": {
"label": "Información de contacto",
"placeholder": "placeholder",
"desc": "Información de contacto"
},
"title": "Ajustes para cotizaciones",
"default_payment_method": {
"label": "Forma de pago",
"placeholder": "placeholder",
@ -241,11 +261,6 @@
"placeholder": "",
"desc": "desc"
},
"default_legal_terms": {
"label": "Cláusulas legales",
"placeholder": "",
"desc": "desc"
},
"default_quote_validity": {
"label": "Validez por defecto",
"placeholder": "",

View File

@ -7,7 +7,7 @@ import {
import { IRepositoryManager } from "@/contexts/common/domain";
import { IInfrastructureError } from "@/contexts/common/infrastructure";
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";
export interface IUpdateProfileUseCaseRequest extends IUseCaseRequest {
@ -33,6 +33,8 @@ export class UpdateProfileUseCase
async execute(request: IUpdateProfileUseCaseRequest): Promise<UpdateProfileResponseOrError> {
const { userId, profileDTO } = request;
console.log(request);
// Comprobar que existe el profile
const exitsOrError = await this._getProfileDealer(userId);
if (exitsOrError.isFailure) {
@ -47,11 +49,11 @@ export class UpdateProfileUseCase
const profile = exitsOrError.object;
// Actualizar el perfil con datos actualizados
profile.contactInformation = Note.create(profileDTO.contact_information).object;
profile.defaultPaymentMethod = Note.create(profileDTO.default_payment_method).object;
profile.defaultLegalTerms = Note.create(profileDTO.default_legal_terms).object;
profile.defaultNotes = Note.create(profileDTO.default_notes).object;
profile.defaultQuoteValidity = Note.create(profileDTO.default_quote_validity).object;
profile.contactInformation = TextValueObject.create(profileDTO.contact_information).object;
profile.defaultPaymentMethod = TextValueObject.create(profileDTO.default_payment_method).object;
profile.defaultLegalTerms = TextValueObject.create(profileDTO.default_legal_terms).object;
profile.defaultNotes = TextValueObject.create(profileDTO.default_notes).object;
profile.defaultQuoteValidity = TextValueObject.create(profileDTO.default_quote_validity).object;
// Guardar los cambios
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> {
return this._exists("Profile_Model", "id", id.toPrimitive());
return this._exists("Dealer_Model", "id", id.toPrimitive());
}
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) {
return null;
@ -37,7 +37,7 @@ export class ProfileRepository extends SequelizeRepository<Profile> implements I
// borrando y luego creando
// 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> {

View File

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