feat: implementar sugerencia de nombre para puestos de trabajo y mejorar formularios
This commit is contained in:
parent
6d6f807486
commit
45b559ab92
@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
La sección Puestos de trabajo de cada compañía agrupa los activos utilizados habitualmente como un conjunto. Un puesto tiene UUID v7, nombre, estado activo/inactivo, sede opcional, persona opcional y hasta 200 activos exclusivos y 200 recursos compartidos. No se admite duplicar un activo ni incluirlo en ambas listas. Se puede crear vacío y completar posteriormente. Una persona puede tener varios puestos. Los puestos no contienen otros puestos.
|
La sección Puestos de trabajo de cada compañía agrupa los activos utilizados habitualmente como un conjunto. Un puesto tiene UUID v7, nombre, estado activo/inactivo, sede opcional, persona opcional y hasta 200 activos exclusivos y 200 recursos compartidos. No se admite duplicar un activo ni incluirlo en ambas listas. Se puede crear vacío y completar posteriormente. Una persona puede tener varios puestos. Los puestos no contienen otros puestos.
|
||||||
|
|
||||||
Nuevo puesto abre un modal desde la cabecera derecha; Editar abre otro modal con los datos existentes. El formulario ofrece selectores con «+» para dar de alta sedes, personas y activos sin perder sus datos. Guardar un elemento relacionado lo selecciona o incorpora al puesto; cancelarlo mantiene el formulario principal. Los elementos guardados sobreviven si posteriormente se cancela el puesto. El alta de activo desde un puesto usa plantillas y sedes existentes para evitar un tercer modal.
|
Nuevo puesto abre un modal desde la cabecera derecha; Editar abre otro modal con los datos existentes. Al crear, elegir persona y sede propone el nombre «Puesto de {persona} en {sede}»; el técnico puede modificarlo o borrarlo antes de guardar y esa edición manual no se sobrescribe. El formulario ofrece selectores con «+» para dar de alta sedes, personas y activos sin perder sus datos. Guardar un elemento relacionado lo selecciona o incorpora al puesto; cancelarlo mantiene el formulario principal. Los elementos guardados sobreviven si posteriormente se cancela el puesto. El alta de activo desde un puesto usa plantillas y sedes existentes para evitar un tercer modal.
|
||||||
|
|
||||||
La ayuda aparece inmediatamente antes de Guardar y Cancelar. El listado permite buscar por nombre, persona o sede. Historial del puesto muestra las versiones completas, con estado, asignación y composición, fecha y responsable. La ficha de cada activo muestra los puestos vigentes en los que se utiliza.
|
La ayuda aparece inmediatamente antes de Guardar y Cancelar. El listado permite buscar por nombre, persona o sede. Historial del puesto muestra las versiones completas, con estado, asignación y composición, fecha y responsable. La ficha de cada activo muestra los puestos vigentes en los que se utiliza.
|
||||||
|
|
||||||
|
|||||||
@ -144,6 +144,7 @@
|
|||||||
"noSharedWorkstationAssets": "No shared resources.",
|
"noSharedWorkstationAssets": "No shared resources.",
|
||||||
"editWorkstation": "Edit workstation",
|
"editWorkstation": "Edit workstation",
|
||||||
"workstationPerson": "Workstation assignee",
|
"workstationPerson": "Workstation assignee",
|
||||||
|
"workstationSuggestedName": "{{person}}'s workstation at {{site}}",
|
||||||
"workstationHistory": "Workstation history",
|
"workstationHistory": "Workstation history",
|
||||||
"workstationAssets": "Workstation assets",
|
"workstationAssets": "Workstation assets",
|
||||||
"noWorkstations": "This company has no workstations yet.",
|
"noWorkstations": "This company has no workstations yet.",
|
||||||
|
|||||||
@ -144,6 +144,7 @@
|
|||||||
"noSharedWorkstationAssets": "Sin recursos compartidos.",
|
"noSharedWorkstationAssets": "Sin recursos compartidos.",
|
||||||
"editWorkstation": "Editar puesto",
|
"editWorkstation": "Editar puesto",
|
||||||
"workstationPerson": "Persona del puesto",
|
"workstationPerson": "Persona del puesto",
|
||||||
|
"workstationSuggestedName": "Puesto de {{person}} en {{site}}",
|
||||||
"workstationHistory": "Historial del puesto",
|
"workstationHistory": "Historial del puesto",
|
||||||
"workstationAssets": "Activos del puesto",
|
"workstationAssets": "Activos del puesto",
|
||||||
"noWorkstations": "Esta compañía todavía no tiene puestos de trabajo.",
|
"noWorkstations": "Esta compañía todavía no tiene puestos de trabajo.",
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import { zodResolver } from "@hookform/resolvers/zod";
|
|||||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||||
import { useEffect, useRef, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
import type { WorkstationInput } from "../../../../common";
|
import type { WorkstationInput } from "../../../../common";
|
||||||
import { WorkstationInputSchema } from "../../../../common";
|
import { WorkstationInputSchema } from "../../../../common";
|
||||||
@ -10,12 +11,15 @@ import { catalogApi, catalogKeys } from "../../../catalog/shared/api";
|
|||||||
import { inventoryKeys } from "../../../shared/queries";
|
import { inventoryKeys } from "../../../shared/queries";
|
||||||
import { saveWorkstation } from "../api";
|
import { saveWorkstation } from "../api";
|
||||||
import type { WorkstationFormProps } from "../types";
|
import type { WorkstationFormProps } from "../types";
|
||||||
|
import { suggestedWorkstationName } from "../workstation-name";
|
||||||
|
|
||||||
export function useWorkstationFormController({
|
export function useWorkstationFormController({
|
||||||
companyId,
|
companyId,
|
||||||
workstation,
|
workstation,
|
||||||
onSaved,
|
onSaved,
|
||||||
|
lookups,
|
||||||
}: WorkstationFormProps) {
|
}: WorkstationFormProps) {
|
||||||
|
const { t } = useTranslation("assets");
|
||||||
const client = useQueryClient();
|
const client = useQueryClient();
|
||||||
const mounted = useRef(true);
|
const mounted = useRef(true);
|
||||||
|
|
||||||
@ -74,6 +78,26 @@ export function useWorkstationFormController({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const selectAssociation = (field: "siteId" | "personId", id: string) => {
|
||||||
|
const selectedId = id || null;
|
||||||
|
|
||||||
|
form.setValue(field, selectedId);
|
||||||
|
if (workstation || form.formState.dirtyFields.name) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const siteId = field === "siteId" ? selectedId : form.getValues("siteId");
|
||||||
|
const personId = field === "personId" ? selectedId : form.getValues("personId");
|
||||||
|
const name = suggestedWorkstationName(
|
||||||
|
lookups.people.find((item) => item.id === personId)?.name,
|
||||||
|
lookups.sites.find((item) => item.id === siteId)?.name,
|
||||||
|
(person, site) => t("workstationSuggestedName", { person, site })
|
||||||
|
);
|
||||||
|
|
||||||
|
if (name) {
|
||||||
|
form.setValue("name", name, { shouldDirty: false });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const mutation = useMutation({
|
const mutation = useMutation({
|
||||||
mutationFn: (input: WorkstationInput) => saveWorkstation(companyId, input, workstation?.id),
|
mutationFn: (input: WorkstationInput) => saveWorkstation(companyId, input, workstation?.id),
|
||||||
onSuccess: async (response) => {
|
onSuccess: async (response) => {
|
||||||
@ -87,13 +111,13 @@ export function useWorkstationFormController({
|
|||||||
|
|
||||||
const siteCreated = (id: string) => {
|
const siteCreated = (id: string) => {
|
||||||
if (mounted.current) {
|
if (mounted.current) {
|
||||||
form.setValue("siteId", id);
|
selectAssociation("siteId", id);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const personCreated = (id: string) => {
|
const personCreated = (id: string) => {
|
||||||
if (mounted.current) {
|
if (mounted.current) {
|
||||||
form.setValue("personId", id);
|
selectAssociation("personId", id);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -109,6 +133,7 @@ export function useWorkstationFormController({
|
|||||||
assets,
|
assets,
|
||||||
unavailable,
|
unavailable,
|
||||||
add,
|
add,
|
||||||
|
selectAssociation,
|
||||||
mutation,
|
mutation,
|
||||||
submit,
|
submit,
|
||||||
siteCreated,
|
siteCreated,
|
||||||
|
|||||||
@ -27,6 +27,7 @@ export function WorkstationForm(props: WorkstationFormProps) {
|
|||||||
assets,
|
assets,
|
||||||
unavailable,
|
unavailable,
|
||||||
add,
|
add,
|
||||||
|
selectAssociation,
|
||||||
mutation,
|
mutation,
|
||||||
submit,
|
submit,
|
||||||
siteCreated,
|
siteCreated,
|
||||||
@ -60,7 +61,7 @@ export function WorkstationForm(props: WorkstationFormProps) {
|
|||||||
}}
|
}}
|
||||||
id={`${prefix}-site`}
|
id={`${prefix}-site`}
|
||||||
label={t("site")}
|
label={t("site")}
|
||||||
onValueChange={(id) => form.setValue("siteId", id || null)}
|
onValueChange={(id) => selectAssociation("siteId", id)}
|
||||||
options={lookups.sites.map((item) => ({ value: item.id, label: item.name }))}
|
options={lookups.sites.map((item) => ({ value: item.id, label: item.name }))}
|
||||||
placeholder={t("noAssignment")}
|
placeholder={t("noAssignment")}
|
||||||
value={values.siteId ?? ""}
|
value={values.siteId ?? ""}
|
||||||
@ -75,7 +76,7 @@ export function WorkstationForm(props: WorkstationFormProps) {
|
|||||||
}}
|
}}
|
||||||
id={`${prefix}-person`}
|
id={`${prefix}-person`}
|
||||||
label={t("workstationPerson")}
|
label={t("workstationPerson")}
|
||||||
onValueChange={(id) => form.setValue("personId", id || null)}
|
onValueChange={(id) => selectAssociation("personId", id)}
|
||||||
options={lookups.people
|
options={lookups.people
|
||||||
.filter((item) => item.active || item.id === workstation?.values.personId)
|
.filter((item) => item.active || item.id === workstation?.values.personId)
|
||||||
.map((item) => ({ value: item.id, label: item.name }))}
|
.map((item) => ({ value: item.id, label: item.name }))}
|
||||||
|
|||||||
@ -0,0 +1,15 @@
|
|||||||
|
import assert from "node:assert/strict";
|
||||||
|
import { test } from "node:test";
|
||||||
|
|
||||||
|
import { suggestedWorkstationName } from "./workstation-name";
|
||||||
|
|
||||||
|
test("propone un nombre de puesto solo al conocer persona y sede", () => {
|
||||||
|
const format = (person: string, site: string) => `${person} @ ${site}`;
|
||||||
|
|
||||||
|
assert.equal(suggestedWorkstationName(undefined, "Fábrica", format), undefined);
|
||||||
|
assert.equal(suggestedWorkstationName("Ana", undefined, format), undefined);
|
||||||
|
assert.equal(
|
||||||
|
suggestedWorkstationName("Ana García", "Uecko Fábrica", format),
|
||||||
|
"Ana García @ Uecko Fábrica"
|
||||||
|
);
|
||||||
|
});
|
||||||
@ -0,0 +1,11 @@
|
|||||||
|
export function suggestedWorkstationName(
|
||||||
|
personName: string | undefined,
|
||||||
|
siteName: string | undefined,
|
||||||
|
format: (person: string, site: string) => string
|
||||||
|
) {
|
||||||
|
if (!(personName && siteName)) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return format(personName, siteName);
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user