diff --git a/application/src/main/webapp/src/Routes.tsx b/application/src/main/webapp/src/Routes.tsx index 1c1c7b8a..0e82cc4e 100644 --- a/application/src/main/webapp/src/Routes.tsx +++ b/application/src/main/webapp/src/Routes.tsx @@ -1,69 +1,52 @@ import { lazy, Suspense } from "react"; import { Navigate, Route, Routes } from "react-router-dom"; -import ProjectEditGeneral from "pages/project-edit/general"; +const GettingStarted = lazy(() => import("./pages/getting-started")); -const ProjectList = lazy(() => import("./pages/project-list")); -const ProjectEdit = lazy(() => import("./pages/project-edit")); -// const ProjectEditGeneral = lazy(() => import("./pages/project-edit/general")); -const ProjectEditSunat = lazy(() => import("./pages/project-edit/sunat")); -const ProjectEditCertificates = lazy( - () => import("./pages/project-edit/certificates") -); -const ProjectEditCompanies = lazy( - () => import("./pages/project-edit/companies") -); - -const DocumentList = lazy(() => import("./pages/document-list")); +const ProjectWrapper = lazy(() => import("./pages/project")); +const ProjectSettings = lazy(() => import("./pages/project/settings")); +const ProjectSunat = lazy(() => import("./pages/project/sunat")); +const ProjectCertificates = lazy(() => import("./pages/project/certificates")); +const ProjectCompanies = lazy(() => import("./pages/project/companies")); +const ProjectDocuments = lazy(() => import("./pages/project/documents")); export const AppRoutes = () => { const routes = [ { - Component: ProjectList, - path: "/projects", - hasDescendant: true, + Component: GettingStarted, + path: "/getting-started", + hasDescendant: false, }, { - Component: ProjectEdit, + Component: ProjectWrapper, path: "/projects/:projectName", children: [ { - Component: () => , + Component: () => , path: "", }, { - Component: ProjectEditGeneral, - path: "general", + Component: ProjectSettings, + path: "settings", }, { - Component: ProjectEditSunat, + Component: ProjectSunat, path: "sunat", }, { - Component: ProjectEditCertificates, + Component: ProjectCertificates, path: "certificates", }, { - Component: ProjectEditCompanies, + Component: ProjectCompanies, path: "companies", }, + { + Component: ProjectDocuments, + path: "documents", + }, ], }, - { - Component: ProjectList, - path: "/projects", - hasDescendant: true, - }, - { - Component: DocumentList, - path: "/documents", - hasDescendant: false, - }, - { - Component: DocumentList, - path: "/documents/projects/:projectName", - hasDescendant: false, - }, ]; return ( @@ -86,7 +69,7 @@ export const AppRoutes = () => { )} ))} - } /> + } /> } /> diff --git a/application/src/main/webapp/src/layout/sidebar.css b/application/src/main/webapp/src/layout/sidebar.css new file mode 100644 index 00000000..d0ee38b4 --- /dev/null +++ b/application/src/main/webapp/src/layout/sidebar.css @@ -0,0 +1,32 @@ +.project { + padding: var(--pf-c-nav__link--PaddingTop) var(--pf-c-nav__link--PaddingRight) + var(--pf-global--spacer--lg) var(--pf-c-nav__link--PaddingLeft); + border-bottom: 1px solid var(--pf-c-nav__link--before--BorderColor); +} + +.project .pf-c-context-selector__toggle { + color: #FFFFFF; +} + +.project .pf-c-select .pf-c-select__toggle { + color: var(--pf-global--BackgroundColor--100); + background-color: var(--pf-global--Color--100); +} + +.project .pf-c-select .pf-c-select__toggle::before { + border-color: var(--pf-global--BackgroundColor--dark-400) + var(--pf-global--BackgroundColor--dark-400) + var(--pf-c-select__toggle--before--BorderBottomColor) + var(--pf-global--BackgroundColor--dark-400); +} + +.project .pf-c-select .pf-c-select__toggle:focus::before, +.project .pf-c-select .pf-c-select__toggle:hover::before { + border-bottom-color: var(--pf-c-select__toggle--before--BorderBottomColor); +} + +.project .pf-c-select .pf-c-select__toggle:focus::before { + border-bottom-width: var( + --pf-c-select__toggle--m-expanded--before--BorderBottomWidth + ); +} diff --git a/application/src/main/webapp/src/layout/sidebar.tsx b/application/src/main/webapp/src/layout/sidebar.tsx index 4412f3da..cc675efc 100644 --- a/application/src/main/webapp/src/layout/sidebar.tsx +++ b/application/src/main/webapp/src/layout/sidebar.tsx @@ -1,42 +1,136 @@ -import React from "react"; -import { NavLink } from "react-router-dom"; +import React, { useReducer } from "react"; +import { NavLink, useMatch, useNavigate } from "react-router-dom"; -import { Nav, NavList, PageSidebar } from "@patternfly/react-core"; +import { + Button, + ContextSelector, + ContextSelectorFooter, + ContextSelectorItem, + Nav, + NavList, + PageSidebar, +} from "@patternfly/react-core"; import { css } from "@patternfly/react-styles"; -import { useProjectContext } from "shared/context"; import { LayoutTheme } from "./layout-constants"; +import "./sidebar.css"; +import { useProjectsQuery } from "queries/projects"; +import { AddProjectWizard } from "shared/components"; + export const SidebarApp: React.FC = () => { - const { currentContext } = useProjectContext(); + const navigate = useNavigate(); + const routeParams = useMatch("/projects/:projectName/*"); + + const projectsQuery = useProjectsQuery(); + + const [isCreateProjectWizardOpen, toggleCreateProjectWizard] = useReducer( + (val) => !val, + false + ); + const [isCtxSelectorToggled, toggleCtxSelector] = useReducer( + (val) => !val, + false + ); + + const navigateToSelectedProject = (projectName: string) => { + navigate(`/projects/${projectName}`); + }; const renderPageNav = () => { return ( - - - css("pf-c-nav__link", isActive ? "pf-m-current" : "") + + + + Crear proyecto + + } > - Proyectos - - - - - css("pf-c-nav__link", isActive ? "pf-m-current" : "") - } - > - Documentos - - + {projectsQuery.data?.map((project) => ( + { + toggleCtxSelector(); + navigateToSelectedProject(project.name); + }} + > + {project.name} + + ))} + + + {isCreateProjectWizardOpen && ( + { + toggleCreateProjectWizard(); + navigateToSelectedProject(project.name); + }} + onClose={toggleCreateProjectWizard} + /> + )} + + + {routeParams?.params.projectName && ( + <> + + { + return css("pf-c-nav__link", isActive ? "pf-m-current" : ""); + }} + > + Configuración + + { + return css("pf-c-nav__link", isActive ? "pf-m-current" : ""); + }} + > + Certificados + + { + return css("pf-c-nav__link", isActive ? "pf-m-current" : ""); + }} + > + SUNAT + + { + return css("pf-c-nav__link", isActive ? "pf-m-current" : ""); + }} + > + Empresas + + { + return css("pf-c-nav__link", isActive ? "pf-m-current" : ""); + }} + > + Documentos + + + > + )} ); }; diff --git a/application/src/main/webapp/src/pages/document-list/index.ts b/application/src/main/webapp/src/pages/document-list/index.ts deleted file mode 100644 index 098a1fcf..00000000 --- a/application/src/main/webapp/src/pages/document-list/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { DocumentList as default } from "./document-list"; diff --git a/application/src/main/webapp/src/pages/getting-started/getting-started.tsx b/application/src/main/webapp/src/pages/getting-started/getting-started.tsx new file mode 100644 index 00000000..23528ac8 --- /dev/null +++ b/application/src/main/webapp/src/pages/getting-started/getting-started.tsx @@ -0,0 +1,66 @@ +import React from "react"; + +import { + Bullseye, + Button, + EmptyState, + EmptyStateBody, + EmptyStateIcon, + EmptyStateSecondaryActions, + SelectVariant, + Title, +} from "@patternfly/react-core"; + +import FileCodeIcon from "@patternfly/react-icons/dist/esm/icons/file-code-icon"; +import { SimpleSelect, useModal } from "@project-openubl/lib-ui"; +import { ProjectDto } from "api/models"; +import { AddProjectWizard } from "shared/components"; +import { useNavigate } from "react-router-dom"; +import { useProjectsQuery } from "queries/projects"; + +export const GettingStarted: React.FC = () => { + const navigate = useNavigate(); + const projectModal = useModal<"ADD", ProjectDto>(); + + const projectsQuery = useProjectsQuery(); + + return ( + <> + + + + + Bienvenido a Ublhub + + + Selecciona un proyecto o crea uno nuevo. + + projectModal.open("ADD")}> + Crear proyecto + + + elem.name)} + onChange={(selection) => { + navigate(`/projects/${selection}`); + }} + /> + + + + {projectModal.isOpen && projectModal.isAction("ADD") && ( + { + projectModal.close(); + navigate(`/projects/${project.name}`); + }} + onClose={projectModal.close} + /> + )} + > + ); +}; diff --git a/application/src/main/webapp/src/pages/getting-started/index.ts b/application/src/main/webapp/src/pages/getting-started/index.ts new file mode 100644 index 00000000..702d1f06 --- /dev/null +++ b/application/src/main/webapp/src/pages/getting-started/index.ts @@ -0,0 +1 @@ +export { GettingStarted as default } from "./getting-started"; diff --git a/application/src/main/webapp/src/pages/project-edit/companies.tsx b/application/src/main/webapp/src/pages/project-edit/companies.tsx deleted file mode 100644 index bf92f6cd..00000000 --- a/application/src/main/webapp/src/pages/project-edit/companies.tsx +++ /dev/null @@ -1,380 +0,0 @@ -import React, { useState } from "react"; -import { Link, useOutletContext } from "react-router-dom"; -import { Trans, useTranslation } from "react-i18next"; - -import { useSelectionState } from "@migtools/lib-ui"; -import { - ConditionalRender, - useConfirmationContext, - useModal, - useTable, - useTableControls, -} from "@project-openubl/lib-ui"; - -import { - Bullseye, - Button, - ButtonVariant, - DataList, - DataListAction, - DataListCell, - DataListItem, - DataListItemCells, - DataListItemRow, - Drawer, - DrawerActions, - DrawerCloseButton, - DrawerContent, - DrawerContentBody, - DrawerHead, - DrawerPanelContent, - Dropdown, - DropdownItem, - DropdownPosition, - EmptyState, - EmptyStateBody, - EmptyStateIcon, - Icon, - KebabToggle, - Modal, - SearchInput, - Stack, - StackItem, - Text, - TextContent, - Title, - Toolbar, - ToolbarContent, - ToolbarGroup, - ToolbarItem, - ToolbarToggleGroup, -} from "@patternfly/react-core"; -import { - CheckCircleIcon, - FilterIcon, - InfoAltIcon, - InfrastructureIcon, -} from "@patternfly/react-icons"; - -import { CompanyDto, ProjectDto } from "api/models"; -import { useCompaniesQuery, useDeleteCompanyMutation } from "queries/companies"; - -import { AddCompanyForm } from "./components/add-company-form"; -import { CompanyDetailsTabs } from "./components/company-details-tabs"; -import { CompanyLogo } from "./components/company-logo"; - -export const compareByColumnIndex = ( - a: CompanyDto, - b: CompanyDto, - columnIndex?: number -) => { - switch (columnIndex) { - default: - return 0; - } -}; - -export const filterByText = (filterText: string, item: CompanyDto) => { - const text = filterText.toLowerCase(); - return ( - item.ruc.toString().toLowerCase().indexOf(text) !== -1 || - item.name.toString().toLowerCase().indexOf(text) !== -1 || - item.description?.toString().toLowerCase().indexOf(text) !== -1 - ); -}; - -const Companies: React.FC = () => { - const { t } = useTranslation(); - - const confirmationModal = useConfirmationContext(); - - const project = useOutletContext(); - const companiesQuery = useCompaniesQuery(project?.name || null); - const deleteCompanyMutation = useDeleteCompanyMutation( - project?.name || null, - () => { - confirmationModal.close(); - } - ); - - const companyModal = useModal<"ADD", CompanyDto>(); - const drawerModal = useModal<"VIEW", CompanyDto>(); - - const { - isItemSelected: isActionsKebabExpanded, - toggleItemSelected: toggleActionsKebab, - } = useSelectionState({ - items: companiesQuery.data || [], - isEqual: (a, b) => { - return a.ruc === b.ruc; - }, - }); - - const [filterText, setFilterText] = useState(""); - const { page: currentPage, sortBy: currentSortBy } = useTableControls({ - page: { page: 1, perPage: 1_000 }, - sortBy: { index: 0, direction: "asc" }, - }); - - const { filteredItems } = useTable({ - items: companiesQuery.data || [], - currentPage: currentPage, - currentSortBy: currentSortBy, - compareToByColumn: compareByColumnIndex, - filterItem: (item) => filterByText(filterText, item), - }); - - const deleteCompany = (row: CompanyDto) => { - confirmationModal.open({ - title: t("modal.confirm-delete.title", { - what: t("terms.company"), - }), - titleIconVariant: "warning", - message: ( - - ¿Estas seguro de querer eliminar este(a) type? Esta acción - eliminará name permanentemente. - - ), - confirmBtnVariant: ButtonVariant.danger, - confirmBtnLabel: t("actions.delete"), - cancelBtnLabel: t("actions.cancel"), - onConfirm: () => { - confirmationModal.enableProcessing(); - deleteCompanyMutation.mutate(row); - }, - }); - }; - - return ( - <> - - - - - - {drawerModal.data?.name} - - - - - - - {project && drawerModal.data && ( - - )} - - - } - > - - - - } - breakpoint="xl" - > - - setFilterText(value)} - /> - - - - - companyModal.open("ADD")} - > - {t("actions.create-object", { - what: t("terms.company").toLowerCase(), - })} - - - - - - - - - - - Vacío - - - No hay empresas registradas - - - - } - > - - {filteredItems.map((item) => ( - - - - {item.name} - {item.description} - , - - RUC: {item.ruc} - , - - {item.sunat ? ( - - - - - - {" "} - SUNAT - - - - ) : ( - - - - - - {" "} - SUNAT - - - - Proyecto - - - )} - , - - {project?.name && item.ruc && ( - - )} - , - - - - - drawerModal.open("VIEW", item) - } - > - {t("actions.edit")} - - - - , - ]} - /> - - toggleActionsKebab(item)} - /> - } - dropdownItems={[ - { - drawerModal.open("VIEW", item); - toggleActionsKebab(item); - }} - > - {t("actions.edit")} - , - deleteCompany(item)} - > - {t("actions.delete")} - , - ]} - menuAppendTo={() => - document.getElementById("data-list") as any - } - /> - - - - ))} - - - - - - - - - {project && ( - - )} - - > - ); -}; - -export default Companies; diff --git a/application/src/main/webapp/src/pages/project-edit/general.tsx b/application/src/main/webapp/src/pages/project-edit/general.tsx deleted file mode 100644 index 120063db..00000000 --- a/application/src/main/webapp/src/pages/project-edit/general.tsx +++ /dev/null @@ -1,198 +0,0 @@ -import React, { useEffect } from "react"; -import { useNavigate, useOutletContext } from "react-router-dom"; -import { useTranslation } from "react-i18next"; - -import { ResolvedQueries } from "@migtools/lib-ui"; -import { NotificationContext } from "@project-openubl/lib-ui"; - -import { Controller, useForm } from "react-hook-form"; -import { yupResolver } from "@hookform/resolvers/yup"; -import { object, string } from "yup"; - -import { - ActionGroup, - Button, - Card, - CardBody, - Form, - FormGroup, - TextArea, - TextInput, -} from "@patternfly/react-core"; -import spacing from "@patternfly/react-styles/css/utilities/Spacing/spacing"; - -import { ProjectDto } from "api/models"; -import { useUpdateProjectMutation } from "queries/projects"; -import { - getValidatedFromError, - getValidatedFromErrorTouched, -} from "utils/modelUtils"; - -interface IGeneralForm { - name: string; - description: string; -} - -const General: React.FC = () => { - const { t } = useTranslation(); - - const navigate = useNavigate(); - const { pushNotification } = React.useContext(NotificationContext); - - const project = useOutletContext(); - const updateProjectMutation = useUpdateProjectMutation((p) => { - pushNotification({ - title: t("info.data-saved"), - message: "", - key: p.name, - variant: "success", - actionClose: true, - timeout: 4000, - }); - }); - - const { - control, - formState: { errors, isValid, isValidating, isDirty }, - reset, - getValues, - } = useForm({ - defaultValues: { name: "", description: "" }, - resolver: yupResolver( - object().shape({ - name: string() - .trim() - .required() - .matches(/[a-z0-9]([-a-z0-9]*[a-z0-9])?/) - .max(250), - // .test("duplicateName", (value, options) => { - // return axios - // .post("/projects/check-name", { name: value }) - // .then(() => true) - // .catch((error: AxiosError) => { - // return value === project?.name - // ? true - // : options.createError({ message: error.response?.data }); - // }); - // }), - description: string().trim().max(250), - }) - ), - mode: "onChange", - }); - - useEffect(() => { - if (project) { - reset({ name: project.name, description: project.description }); - } - }, [project, reset]); - - const save = () => { - if (!project) { - return; - } - - updateProjectMutation.mutate({ - ...project, - ...getValues(), - }); - }; - - return ( - - - - - ( - - )} - /> - - - ( - - )} - /> - - - - - {t("actions.save")} - - navigate("/projects")}> - {t("actions.cancel")} - - - - - - - - ); -}; - -export default General; diff --git a/application/src/main/webapp/src/pages/project-edit/sunat.tsx b/application/src/main/webapp/src/pages/project-edit/sunat.tsx deleted file mode 100644 index f4f7745f..00000000 --- a/application/src/main/webapp/src/pages/project-edit/sunat.tsx +++ /dev/null @@ -1,296 +0,0 @@ -import React, { useEffect } from "react"; -import { useNavigate, useOutletContext } from "react-router-dom"; -import { useTranslation } from "react-i18next"; - -import { ResolvedQueries } from "@migtools/lib-ui"; -import { NotificationContext } from "@project-openubl/lib-ui"; - -import { Controller, useForm } from "react-hook-form"; -import { yupResolver } from "@hookform/resolvers/yup"; -import { object, string } from "yup"; - -import { - ActionGroup, - Button, - Card, - CardBody, - Form, - FormGroup, - FormSection, - TextInput, -} from "@patternfly/react-core"; -import spacing from "@patternfly/react-styles/css/utilities/Spacing/spacing"; - -import { ProjectDto } from "api/models"; -import { useUpdateProjectMutation } from "queries/projects"; -import { - getValidatedFromError, - getValidatedFromErrorTouched, -} from "utils/modelUtils"; - -interface ISunatForm { - facturaURL: string; - guiaURL: string; - retencionURL: string; - username: string; - password: string; -} - -const Sunat: React.FC = () => { - const { t } = useTranslation(); - - const navigate = useNavigate(); - const { pushNotification } = React.useContext(NotificationContext); - - const project = useOutletContext(); - const updateProjectMutation = useUpdateProjectMutation((p) => { - pushNotification({ - title: t("info.data-saved"), - message: "", - key: p.name, - variant: "success", - actionClose: true, - timeout: 4000, - }); - }); - - const { - control, - formState: { errors, isValid, isValidating, isDirty }, - reset, - getValues, - } = useForm({ - defaultValues: { - facturaURL: "", - guiaURL: "", - retencionURL: "", - username: "", - password: "", - }, - resolver: yupResolver( - object().shape({ - facturaURL: string().trim().required().max(250), - guiaURL: string().trim().required().max(250), - retencionURL: string().trim().required().max(250), - username: string().trim().required().max(250), - password: string().trim().required().max(250), - }) - ), - mode: "onChange", - }); - - useEffect(() => { - if (project) { - reset({ - facturaURL: project.sunat.facturaUrl, - guiaURL: project.sunat.guiaUrl, - retencionURL: project.sunat.retencionUrl, - username: project.sunat.username, - password: "******", - }); - } - }, [project, reset]); - - const save = () => { - if (!project) { - return; - } - - updateProjectMutation.mutate({ - ...project, - sunat: { - facturaUrl: getValues().facturaURL, - guiaUrl: getValues().guiaURL, - retencionUrl: getValues().retencionURL, - username: getValues().username, - password: control.getFieldState("password").isDirty - ? getValues().password - : undefined, - }, - }); - }; - - return ( - - - - - - ( - - )} - /> - - - ( - - )} - /> - - - ( - - )} - /> - - - - - - ( - - )} - /> - - - ( - - )} - /> - - - - - - {t("actions.save")} - - navigate("/projects")}> - {t("actions.cancel")} - - - - - - - - ); -}; - -export default Sunat; diff --git a/application/src/main/webapp/src/pages/project-list/index.ts b/application/src/main/webapp/src/pages/project-list/index.ts deleted file mode 100644 index 5ab108ea..00000000 --- a/application/src/main/webapp/src/pages/project-list/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { ProjectList as default } from "./project-list"; diff --git a/application/src/main/webapp/src/pages/project-list/project-list.tsx b/application/src/main/webapp/src/pages/project-list/project-list.tsx deleted file mode 100644 index ded0f3f2..00000000 --- a/application/src/main/webapp/src/pages/project-list/project-list.tsx +++ /dev/null @@ -1,228 +0,0 @@ -import React, { useState } from "react"; -import { Link, useNavigate } from "react-router-dom"; -import { Trans, useTranslation } from "react-i18next"; - -import { - SimpleTableWithToolbar, - useConfirmationContext, - useModal, - useTable, - useTableControls, -} from "@project-openubl/lib-ui"; - -import { - Button, - ButtonVariant, - PageSection, - PageSectionVariants, - SearchInput, - Text, - TextContent, - ToolbarGroup, - ToolbarItem, -} from "@patternfly/react-core"; -import { - cellWidth, - IActions, - ICell, - IExtraData, - IRow, - IRowData, - sortable, -} from "@patternfly/react-table"; - -import { useDeleteProjectMutation, useProjectsQuery } from "queries/projects"; -import { AddProjectWizard } from "shared/components"; -import { ProjectDto } from "api/models"; - -const ROW_FIELD = "row_field"; -const getRow = (rowData: IRowData): ProjectDto => { - return rowData[ROW_FIELD]; -}; - -const itemsToRow = (items: ProjectDto[]) => { - return items.map((item) => ({ - [ROW_FIELD]: item, - cells: [ - { - title: {item.name}, - }, - { - title: item.description, - }, - ], - })); -}; - -export const compareByColumnIndex = ( - a: ProjectDto, - b: ProjectDto, - columnIndex?: number -) => { - switch (columnIndex) { - case 0: // name - return a.name.localeCompare(b.name); - default: - return 0; - } -}; - -export const filterByText = (filterText: string, item: ProjectDto) => { - return ( - item.name.toString().toLowerCase().indexOf(filterText.toLowerCase()) !== -1 - ); -}; - -export const ProjectList: React.FC = () => { - const { t } = useTranslation(); - const navigate = useNavigate(); - - const confirmationModal = useConfirmationContext(); - const modal = useModal<"ADD", ProjectDto>(); - - const [filterText, setFilterText] = useState(""); - const { - page: currentPage, - sortBy: currentSortBy, - changePage: onPageChange, - changeSortBy: onChangeSortBy, - } = useTableControls({ sortBy: { index: 0, direction: "asc" } }); - - // - const projectsQuery = useProjectsQuery(); - const deleteProjectMutation = useDeleteProjectMutation(() => { - confirmationModal.close(); - }); - - const { pageItems, filteredItems } = useTable({ - items: projectsQuery.data || [], - currentPage: currentPage, - currentSortBy: currentSortBy, - compareToByColumn: compareByColumnIndex, - filterItem: (item) => filterByText(filterText, item), - }); - - const columns: ICell[] = [ - { title: t("terms.name"), transforms: [sortable, cellWidth(40)] }, - { title: t("terms.description"), transforms: [cellWidth(60)] }, - ]; - - const rows: IRow[] = itemsToRow(pageItems || []); - - const actions: IActions = [ - { - title: t("actions.edit"), - onClick: ( - event: React.MouseEvent, - rowIndex: number, - rowData: IRowData, - extraData: IExtraData - ) => { - const row: ProjectDto = getRow(rowData); - navigate(`/projects/${row.name}`); - }, - }, - { - title: t("actions.delete"), - onClick: ( - event: React.MouseEvent, - rowIndex: number, - rowData: IRowData, - extraData: IExtraData - ) => { - const row: ProjectDto = getRow(rowData); - - confirmationModal.open({ - title: t("modal.confirm-delete.title", { - what: t("terms.project").toLowerCase(), - }), - titleIconVariant: "warning", - message: ( - - ¿Estas seguro de querer eliminar este(a) type? Esta acción - eliminará name permanentemente. - - ), - confirmBtnVariant: ButtonVariant.danger, - confirmBtnLabel: t("actions.delete"), - cancelBtnLabel: t("actions.cancel"), - onConfirm: () => { - confirmationModal.enableProcessing(); - deleteProjectMutation.mutate(row); - }, - }); - }, - }, - ]; - - return ( - <> - - - {t("terms.projects")} - Lista de Proyectos disponibles. - - - - 0} - toolbarToggle={ - - setFilterText(value)} - /> - - } - toolbarActions={ - - - modal.open("ADD")} - > - {t("actions.create-object", { - what: t("terms.project").toLowerCase(), - })} - - - - } - /> - - - {modal.isOpen && modal.isAction("ADD") && ( - { - modal.close(); - navigate(`/projects/${project.name}`); - }} - onClose={modal.close} - /> - )} - > - ); -}; diff --git a/application/src/main/webapp/src/pages/project-edit/certificates.tsx b/application/src/main/webapp/src/pages/project/certificates/certificates.tsx similarity index 73% rename from application/src/main/webapp/src/pages/project-edit/certificates.tsx rename to application/src/main/webapp/src/pages/project/certificates/certificates.tsx index c1ac6874..45c8f3ec 100644 --- a/application/src/main/webapp/src/pages/project-edit/certificates.tsx +++ b/application/src/main/webapp/src/pages/project/certificates/certificates.tsx @@ -13,11 +13,16 @@ import { import { Button, ButtonVariant, + Divider, Dropdown, DropdownItem, DropdownToggle, Modal, + PageSection, + PageSectionVariants, SearchInput, + Text, + TextContent, ToolbarGroup, ToolbarItem, } from "@patternfly/react-core"; @@ -47,7 +52,7 @@ import { ProjectDto, } from "api/models"; -import { ComponentForm } from "./components/component-form"; +import { ComponentForm } from "shared/components/component-form"; const ROW_FIELD = "row_field"; const getRow = (rowData: IRowData): KeyMetadataDto => { @@ -74,7 +79,7 @@ export const filterByText = (filterText: string, item: KeyMetadataDto) => { ); }; -const Certificates: React.FC = () => { +export const Certificates: React.FC = () => { const { t } = useTranslation(); const confirmationModal = useConfirmationContext(); @@ -242,71 +247,80 @@ const Certificates: React.FC = () => { return ( <> - 0} - toolbarToggle={ - - setFilterText(value)} - /> - - } - toolbarActions={ - - - - {t("actions.create-object", { - what: t("terms.certificate").toLowerCase(), - })} - - } - isOpen={isNewKeyBtnOpen} - dropdownItems={serverInfoQuery.data?.componentTypes[ - KEY_PROVIDERS - ].map((e) => ( - { - componentFormModal.open("create", { - componentType: e, - }); - }} - > - {e.id} - - ))} + + + {t("terms.certificates")} + Certificados usados para firmar archivos XML. + + + + + 0} + toolbarToggle={ + + setFilterText(value)} /> - - } - /> + } + toolbarActions={ + + + + {t("actions.create-object", { + what: t("terms.certificate").toLowerCase(), + })} + + } + isOpen={isNewKeyBtnOpen} + dropdownItems={serverInfoQuery.data?.componentTypes[ + KEY_PROVIDERS + ].map((e) => ( + { + componentFormModal.open("create", { + componentType: e, + }); + }} + > + {e.id} + + ))} + /> + + + } + /> + { > ); }; - -export default Certificates; diff --git a/application/src/main/webapp/src/pages/project/certificates/index.ts b/application/src/main/webapp/src/pages/project/certificates/index.ts new file mode 100644 index 00000000..f61334a4 --- /dev/null +++ b/application/src/main/webapp/src/pages/project/certificates/index.ts @@ -0,0 +1 @@ +export { Certificates as default } from "./certificates"; diff --git a/application/src/main/webapp/src/pages/project/companies/companies.tsx b/application/src/main/webapp/src/pages/project/companies/companies.tsx new file mode 100644 index 00000000..cda2ef5b --- /dev/null +++ b/application/src/main/webapp/src/pages/project/companies/companies.tsx @@ -0,0 +1,391 @@ +import React, { useState } from "react"; +import { Link, useOutletContext } from "react-router-dom"; +import { Trans, useTranslation } from "react-i18next"; + +import { useSelectionState } from "@migtools/lib-ui"; +import { + ConditionalRender, + useConfirmationContext, + useModal, + useTable, + useTableControls, +} from "@project-openubl/lib-ui"; + +import { + Bullseye, + Button, + ButtonVariant, + DataList, + DataListAction, + DataListCell, + DataListItem, + DataListItemCells, + DataListItemRow, + Divider, + Drawer, + DrawerActions, + DrawerCloseButton, + DrawerContent, + DrawerContentBody, + DrawerHead, + DrawerPanelContent, + Dropdown, + DropdownItem, + DropdownPosition, + EmptyState, + EmptyStateBody, + EmptyStateIcon, + Icon, + KebabToggle, + Modal, + PageSection, + PageSectionVariants, + SearchInput, + Stack, + StackItem, + Text, + TextContent, + Title, + Toolbar, + ToolbarContent, + ToolbarGroup, + ToolbarItem, + ToolbarToggleGroup, +} from "@patternfly/react-core"; +import { + CheckCircleIcon, + FilterIcon, + InfoAltIcon, + InfrastructureIcon, +} from "@patternfly/react-icons"; + +import { CompanyDto, ProjectDto } from "api/models"; +import { useCompaniesQuery, useDeleteCompanyMutation } from "queries/companies"; + +import { AddCompanyForm } from "./components/add-company-form"; +import { CompanyDetailsTabs } from "./components/company-details-tabs"; +import { CompanyLogo } from "shared/components/company-logo"; + +export const compareByColumnIndex = ( + a: CompanyDto, + b: CompanyDto, + columnIndex?: number +) => { + switch (columnIndex) { + default: + return 0; + } +}; + +export const filterByText = (filterText: string, item: CompanyDto) => { + const text = filterText.toLowerCase(); + return ( + item.ruc.toString().toLowerCase().indexOf(text) !== -1 || + item.name.toString().toLowerCase().indexOf(text) !== -1 || + item.description?.toString().toLowerCase().indexOf(text) !== -1 + ); +}; + +export const Companies: React.FC = () => { + const { t } = useTranslation(); + + const confirmationModal = useConfirmationContext(); + + const project = useOutletContext(); + const companiesQuery = useCompaniesQuery(project?.name || null); + const deleteCompanyMutation = useDeleteCompanyMutation( + project?.name || null, + () => { + confirmationModal.close(); + } + ); + + const companyModal = useModal<"ADD", CompanyDto>(); + const drawerModal = useModal<"VIEW", CompanyDto>(); + + const { + isItemSelected: isActionsKebabExpanded, + toggleItemSelected: toggleActionsKebab, + } = useSelectionState({ + items: companiesQuery.data || [], + isEqual: (a, b) => { + return a.ruc === b.ruc; + }, + }); + + const [filterText, setFilterText] = useState(""); + const { page: currentPage, sortBy: currentSortBy } = useTableControls({ + page: { page: 1, perPage: 1_000 }, + sortBy: { index: 0, direction: "asc" }, + }); + + const { filteredItems } = useTable({ + items: companiesQuery.data || [], + currentPage: currentPage, + currentSortBy: currentSortBy, + compareToByColumn: compareByColumnIndex, + filterItem: (item) => filterByText(filterText, item), + }); + + const deleteCompany = (row: CompanyDto) => { + confirmationModal.open({ + title: t("modal.confirm-delete.title", { + what: t("terms.company"), + }), + titleIconVariant: "warning", + message: ( + + ¿Estas seguro de querer eliminar este(a) type? Esta acción + eliminará name permanentemente. + + ), + confirmBtnVariant: ButtonVariant.danger, + confirmBtnLabel: t("actions.delete"), + cancelBtnLabel: t("actions.cancel"), + onConfirm: () => { + confirmationModal.enableProcessing(); + deleteCompanyMutation.mutate(row); + }, + }); + }; + + return ( + <> + + + Empresas + Configura multiples RUC para tu proyecto. + + + + + + + + + + {drawerModal.data?.name} + + + + + + + {project && drawerModal.data && ( + + )} + + + } + > + + + + } + breakpoint="xl" + > + + setFilterText(value)} + /> + + + + + companyModal.open("ADD")} + > + {t("actions.create-object", { + what: t("terms.company").toLowerCase(), + })} + + + + + + + + + + + Vacío + + + No hay empresas registradas + + + + } + > + + {filteredItems.map((item) => ( + + + + {item.name} + {item.description} + , + + RUC: {item.ruc} + , + + {item.sunat ? ( + + + + + + {" "} + SUNAT + + + + ) : ( + + + + + + {" "} + SUNAT + + + + Proyecto + + + )} + , + + {project?.name && item.ruc && ( + + )} + , + + + + + drawerModal.open("VIEW", item) + } + > + {t("actions.edit")} + + + + , + ]} + /> + + toggleActionsKebab(item)} + /> + } + dropdownItems={[ + { + drawerModal.open("VIEW", item); + toggleActionsKebab(item); + }} + > + {t("actions.edit")} + , + deleteCompany(item)} + > + {t("actions.delete")} + , + ]} + menuAppendTo={() => + document.getElementById("data-list") as any + } + /> + + + + ))} + + + + + + + + + + {project && ( + + )} + + > + ); +}; diff --git a/application/src/main/webapp/src/pages/project-edit/components/add-company-form/add-company-form.tsx b/application/src/main/webapp/src/pages/project/companies/components/add-company-form/add-company-form.tsx similarity index 100% rename from application/src/main/webapp/src/pages/project-edit/components/add-company-form/add-company-form.tsx rename to application/src/main/webapp/src/pages/project/companies/components/add-company-form/add-company-form.tsx diff --git a/application/src/main/webapp/src/pages/project-edit/components/add-company-form/index.ts b/application/src/main/webapp/src/pages/project/companies/components/add-company-form/index.ts similarity index 100% rename from application/src/main/webapp/src/pages/project-edit/components/add-company-form/index.ts rename to application/src/main/webapp/src/pages/project/companies/components/add-company-form/index.ts diff --git a/application/src/main/webapp/src/pages/project-edit/components/company-details-tabs/certificates.tsx b/application/src/main/webapp/src/pages/project/companies/components/company-details-tabs/certificates.tsx similarity index 99% rename from application/src/main/webapp/src/pages/project-edit/components/company-details-tabs/certificates.tsx rename to application/src/main/webapp/src/pages/project/companies/components/company-details-tabs/certificates.tsx index 6463b3c0..d1926fba 100644 --- a/application/src/main/webapp/src/pages/project-edit/components/company-details-tabs/certificates.tsx +++ b/application/src/main/webapp/src/pages/project/companies/components/company-details-tabs/certificates.tsx @@ -48,7 +48,7 @@ import { useServerInfoQuery } from "queries/server-info"; import { KEY_PROVIDERS } from "Constants"; -import { ComponentForm } from "../component-form"; +import { ComponentForm } from "shared/components/component-form"; const ROW_FIELD = "row_field"; const getRow = (rowData: IRowData): KeyMetadataDto => { diff --git a/application/src/main/webapp/src/pages/project-edit/components/company-details-tabs/company-details-tabs.tsx b/application/src/main/webapp/src/pages/project/companies/components/company-details-tabs/company-details-tabs.tsx similarity index 100% rename from application/src/main/webapp/src/pages/project-edit/components/company-details-tabs/company-details-tabs.tsx rename to application/src/main/webapp/src/pages/project/companies/components/company-details-tabs/company-details-tabs.tsx diff --git a/application/src/main/webapp/src/pages/project-edit/components/company-details-tabs/general-form.tsx b/application/src/main/webapp/src/pages/project/companies/components/company-details-tabs/general-form.tsx similarity index 100% rename from application/src/main/webapp/src/pages/project-edit/components/company-details-tabs/general-form.tsx rename to application/src/main/webapp/src/pages/project/companies/components/company-details-tabs/general-form.tsx diff --git a/application/src/main/webapp/src/pages/project-edit/components/company-details-tabs/general.tsx b/application/src/main/webapp/src/pages/project/companies/components/company-details-tabs/general.tsx similarity index 98% rename from application/src/main/webapp/src/pages/project-edit/components/company-details-tabs/general.tsx rename to application/src/main/webapp/src/pages/project/companies/components/company-details-tabs/general.tsx index 60159112..dec74047 100644 --- a/application/src/main/webapp/src/pages/project-edit/components/company-details-tabs/general.tsx +++ b/application/src/main/webapp/src/pages/project/companies/components/company-details-tabs/general.tsx @@ -21,7 +21,7 @@ import { useCompaniesQuery } from "queries/companies"; import { CompanyDto, ProjectDto } from "api/models"; import { GeneralForm } from "./general-form"; -import { CompanyLogo } from "../company-logo"; +import { CompanyLogo } from "shared/components/company-logo"; interface IGeneralProps { project: ProjectDto; diff --git a/application/src/main/webapp/src/pages/project-edit/components/company-details-tabs/index.ts b/application/src/main/webapp/src/pages/project/companies/components/company-details-tabs/index.ts similarity index 100% rename from application/src/main/webapp/src/pages/project-edit/components/company-details-tabs/index.ts rename to application/src/main/webapp/src/pages/project/companies/components/company-details-tabs/index.ts diff --git a/application/src/main/webapp/src/pages/project-edit/components/company-details-tabs/sunat-form.tsx b/application/src/main/webapp/src/pages/project/companies/components/company-details-tabs/sunat-form.tsx similarity index 100% rename from application/src/main/webapp/src/pages/project-edit/components/company-details-tabs/sunat-form.tsx rename to application/src/main/webapp/src/pages/project/companies/components/company-details-tabs/sunat-form.tsx diff --git a/application/src/main/webapp/src/pages/project-edit/components/company-details-tabs/sunat.tsx b/application/src/main/webapp/src/pages/project/companies/components/company-details-tabs/sunat.tsx similarity index 100% rename from application/src/main/webapp/src/pages/project-edit/components/company-details-tabs/sunat.tsx rename to application/src/main/webapp/src/pages/project/companies/components/company-details-tabs/sunat.tsx diff --git a/application/src/main/webapp/src/pages/project/companies/index.ts b/application/src/main/webapp/src/pages/project/companies/index.ts new file mode 100644 index 00000000..302d964b --- /dev/null +++ b/application/src/main/webapp/src/pages/project/companies/index.ts @@ -0,0 +1 @@ +export { Companies as default } from "./companies"; diff --git a/application/src/main/webapp/src/pages/document-list/components/document-editor/document-editor.tsx b/application/src/main/webapp/src/pages/project/documents/components/document-editor/document-editor.tsx similarity index 100% rename from application/src/main/webapp/src/pages/document-list/components/document-editor/document-editor.tsx rename to application/src/main/webapp/src/pages/project/documents/components/document-editor/document-editor.tsx diff --git a/application/src/main/webapp/src/pages/document-list/components/document-editor/index.ts b/application/src/main/webapp/src/pages/project/documents/components/document-editor/index.ts similarity index 100% rename from application/src/main/webapp/src/pages/document-list/components/document-editor/index.ts rename to application/src/main/webapp/src/pages/project/documents/components/document-editor/index.ts diff --git a/application/src/main/webapp/src/pages/document-list/components/pdf-preview/index.ts b/application/src/main/webapp/src/pages/project/documents/components/pdf-preview/index.ts similarity index 100% rename from application/src/main/webapp/src/pages/document-list/components/pdf-preview/index.ts rename to application/src/main/webapp/src/pages/project/documents/components/pdf-preview/index.ts diff --git a/application/src/main/webapp/src/pages/document-list/components/pdf-preview/pdf-preview.tsx b/application/src/main/webapp/src/pages/project/documents/components/pdf-preview/pdf-preview.tsx similarity index 100% rename from application/src/main/webapp/src/pages/document-list/components/pdf-preview/pdf-preview.tsx rename to application/src/main/webapp/src/pages/project/documents/components/pdf-preview/pdf-preview.tsx diff --git a/application/src/main/webapp/src/pages/document-list/components/xml-cdr-preview/index.ts b/application/src/main/webapp/src/pages/project/documents/components/xml-cdr-preview/index.ts similarity index 100% rename from application/src/main/webapp/src/pages/document-list/components/xml-cdr-preview/index.ts rename to application/src/main/webapp/src/pages/project/documents/components/xml-cdr-preview/index.ts diff --git a/application/src/main/webapp/src/pages/document-list/components/xml-cdr-preview/xml-cdr-preview.tsx b/application/src/main/webapp/src/pages/project/documents/components/xml-cdr-preview/xml-cdr-preview.tsx similarity index 100% rename from application/src/main/webapp/src/pages/document-list/components/xml-cdr-preview/xml-cdr-preview.tsx rename to application/src/main/webapp/src/pages/project/documents/components/xml-cdr-preview/xml-cdr-preview.tsx diff --git a/application/src/main/webapp/src/pages/document-list/document-list.tsx b/application/src/main/webapp/src/pages/project/documents/documents.tsx similarity index 74% rename from application/src/main/webapp/src/pages/document-list/document-list.tsx rename to application/src/main/webapp/src/pages/project/documents/documents.tsx index 01237127..de367968 100644 --- a/application/src/main/webapp/src/pages/document-list/document-list.tsx +++ b/application/src/main/webapp/src/pages/project/documents/documents.tsx @@ -1,5 +1,5 @@ import React, { useEffect, useState } from "react"; -import { useMatch, useNavigate } from "react-router-dom"; +import { useMatch, useNavigate, useOutletContext } from "react-router-dom"; import { useTranslation } from "react-i18next"; import { StatusIcon, StatusType, useSelectionState } from "@migtools/lib-ui"; @@ -65,7 +65,7 @@ import { import { ContextOption, ProjectContextSelector } from "shared/context"; import { formatTimestamp } from "utils/dateUtils"; -import { DocumentDto } from "api/models"; +import { DocumentDto, ProjectDto } from "api/models"; import { DocumentEditor } from "./components/document-editor"; import { XmlCdrPreview } from "./components/xml-cdr-preview"; @@ -231,8 +231,7 @@ export const DocumentList: React.FC = () => { const { t } = useTranslation(); const navigate = useNavigate(); - const matchSingleProjectPage = useMatch("/documents/projects/:projectName"); - const projectName = matchSingleProjectPage?.params.projectName; + const project = useOutletContext(); const onProjectContextChange = (context: ContextOption) => { navigate("/documents/projects/" + context.key); @@ -280,7 +279,7 @@ export const DocumentList: React.FC = () => { setQueryParams(params); }, [filterText, currentPage]); - const documentsQuery = useDocumentsQuery(projectName || null, queryParams); + const documentsQuery = useDocumentsQuery(project?.name || null, queryParams); const { isItemSelected: isRowExpanded, @@ -387,38 +386,6 @@ export const DocumentList: React.FC = () => { } return ( <> - - - - {t("terms.projects")}: - - - navigate("/projects")} - > - {t("actions.create-object", { - what: t("terms.project").toLowerCase(), - })} - - - ), - }} - /> - - - - - {t("terms.documents")} @@ -427,73 +394,56 @@ export const DocumentList: React.FC = () => { - - - - - Selecciona un proyecto - - - Selecciona el proyecto al cual deseas acceder. - - - + 0} + toolbarToggle={ + + setFilterTextTemp(value)} + onSearch={() => setFilterText(filterTextTemp)} + attributes={[{ attr: "ruc", display: "RUC" }]} + advancedSearchDelimiter={":"} + hasWordsAttrLabel="Filter text" + /> + } - > - 0} - toolbarToggle={ - - setFilterTextTemp(value)} - onSearch={() => setFilterText(filterTextTemp)} - attributes={[{ attr: "ruc", display: "RUC" }]} - advancedSearchDelimiter={":"} - hasWordsAttrLabel="Filter text" - /> + toolbarActions={ + + + documentModal.open("ADD")} + > + {t("actions.create-object", { + what: t("terms.document").toLowerCase(), + })} + - } - toolbarActions={ - - - documentModal.open("ADD")} - > - {t("actions.create-object", { - what: t("terms.document").toLowerCase(), - })} - - - - } - /> - + + } + /> { isOpen={documentModal.isOpen} onClose={documentModal.close} > - {projectName && ( + {project && ( @@ -523,17 +473,17 @@ export const DocumentList: React.FC = () => { , ]} > - {projectName && + {project && rowModal.data && (rowModal.action === "xml" || rowModal.action === "cdr") && ( )} - {projectName && rowModal.data && rowModal.action === "pdf" && ( - + {project && rowModal.data && rowModal.action === "pdf" && ( + )} > diff --git a/application/src/main/webapp/src/pages/project/documents/index.ts b/application/src/main/webapp/src/pages/project/documents/index.ts new file mode 100644 index 00000000..0bf7609c --- /dev/null +++ b/application/src/main/webapp/src/pages/project/documents/index.ts @@ -0,0 +1 @@ +export { DocumentList as default } from "./documents"; diff --git a/application/src/main/webapp/src/pages/project/index.ts b/application/src/main/webapp/src/pages/project/index.ts new file mode 100644 index 00000000..872c2836 --- /dev/null +++ b/application/src/main/webapp/src/pages/project/index.ts @@ -0,0 +1 @@ +export { ProjectWrapper as default } from "./project-wrapper"; diff --git a/application/src/main/webapp/src/pages/project/project-wrapper.tsx b/application/src/main/webapp/src/pages/project/project-wrapper.tsx new file mode 100644 index 00000000..cea89bae --- /dev/null +++ b/application/src/main/webapp/src/pages/project/project-wrapper.tsx @@ -0,0 +1,18 @@ +import React, { useMemo } from "react"; +import { Outlet, useMatch } from "react-router-dom"; +import { useProjectsQuery } from "queries/projects"; + +export const ProjectWrapper: React.FC = () => { + const routeParams = useMatch("/projects/:projectName/*"); + + const projectsQuery = useProjectsQuery(); + const project = useMemo(() => { + const projectName = routeParams?.params.projectName; + return ( + projectsQuery.data?.find((project) => project.name === projectName) || + null + ); + }, [routeParams?.params, projectsQuery.data]); + + return ; +}; diff --git a/application/src/main/webapp/src/pages/project/settings/index.ts b/application/src/main/webapp/src/pages/project/settings/index.ts new file mode 100644 index 00000000..d0e285a0 --- /dev/null +++ b/application/src/main/webapp/src/pages/project/settings/index.ts @@ -0,0 +1 @@ +export { Settings as default } from "./settings"; diff --git a/application/src/main/webapp/src/pages/project/settings/settings.tsx b/application/src/main/webapp/src/pages/project/settings/settings.tsx new file mode 100644 index 00000000..fd527f66 --- /dev/null +++ b/application/src/main/webapp/src/pages/project/settings/settings.tsx @@ -0,0 +1,202 @@ +import React, { useEffect } from "react"; +import { useNavigate, useOutletContext } from "react-router-dom"; +import { useTranslation } from "react-i18next"; + +import { ResolvedQueries } from "@migtools/lib-ui"; +import { NotificationContext } from "@project-openubl/lib-ui"; + +import { Controller, useForm } from "react-hook-form"; +import { yupResolver } from "@hookform/resolvers/yup"; +import { object, string } from "yup"; + +import { + ActionGroup, + Button, + Card, + CardBody, + Divider, + Form, + FormGroup, + PageSection, + PageSectionVariants, + Text, + TextArea, + TextContent, + TextInput, +} from "@patternfly/react-core"; +import spacing from "@patternfly/react-styles/css/utilities/Spacing/spacing"; + +import { ProjectDto } from "api/models"; +import { useUpdateProjectMutation } from "queries/projects"; +import { + getValidatedFromError, + getValidatedFromErrorTouched, +} from "utils/modelUtils"; + +interface IGeneralForm { + name: string; + description: string; +} + +export const Settings: React.FC = () => { + const { t } = useTranslation(); + + const navigate = useNavigate(); + const { pushNotification } = React.useContext(NotificationContext); + + const project = useOutletContext(); + const updateProjectMutation = useUpdateProjectMutation((p) => { + pushNotification({ + title: t("info.data-saved"), + message: "", + key: p.name, + variant: "success", + actionClose: true, + timeout: 4000, + }); + }); + + const { + control, + formState: { errors, isValid, isValidating, isDirty }, + reset, + getValues, + } = useForm({ + defaultValues: { name: "", description: "" }, + resolver: yupResolver( + object().shape({ + name: string() + .trim() + .required() + .matches(/[a-z0-9]([-a-z0-9]*[a-z0-9])?/) + .max(250), + description: string().trim().max(250), + }) + ), + mode: "onChange", + }); + + useEffect(() => { + if (project) { + reset({ name: project.name, description: project.description }); + } + }, [project, reset]); + + const save = () => { + if (!project) { + return; + } + + updateProjectMutation.mutate({ + ...project, + ...getValues(), + }); + }; + + return ( + <> + + + {project?.name} + Configuración general del proyecto. + + + + + + + + + ( + + )} + /> + + + ( + + )} + /> + + + + + {t("actions.save")} + + navigate("/projects")}> + {t("actions.cancel")} + + + + + + + + + > + ); +}; diff --git a/application/src/main/webapp/src/pages/project/sunat/index.ts b/application/src/main/webapp/src/pages/project/sunat/index.ts new file mode 100644 index 00000000..d14fa1a5 --- /dev/null +++ b/application/src/main/webapp/src/pages/project/sunat/index.ts @@ -0,0 +1 @@ +export { Sunat as default } from "./sunat"; diff --git a/application/src/main/webapp/src/pages/project/sunat/sunat.tsx b/application/src/main/webapp/src/pages/project/sunat/sunat.tsx new file mode 100644 index 00000000..79fd5ba4 --- /dev/null +++ b/application/src/main/webapp/src/pages/project/sunat/sunat.tsx @@ -0,0 +1,325 @@ +import React, { useEffect } from "react"; +import { useNavigate, useOutletContext } from "react-router-dom"; +import { useTranslation } from "react-i18next"; + +import { ResolvedQueries } from "@migtools/lib-ui"; +import { NotificationContext } from "@project-openubl/lib-ui"; + +import { Controller, useForm } from "react-hook-form"; +import { yupResolver } from "@hookform/resolvers/yup"; +import { object, string } from "yup"; + +import { + ActionGroup, + Button, + Card, + CardBody, + Divider, + Form, + FormGroup, + FormSection, + PageSection, + PageSectionVariants, + Text, + TextContent, + TextInput, +} from "@patternfly/react-core"; +import spacing from "@patternfly/react-styles/css/utilities/Spacing/spacing"; + +import { ProjectDto } from "api/models"; +import { useUpdateProjectMutation } from "queries/projects"; +import { + getValidatedFromError, + getValidatedFromErrorTouched, +} from "utils/modelUtils"; + +interface ISunatForm { + facturaURL: string; + guiaURL: string; + retencionURL: string; + username: string; + password: string; +} + +export const Sunat: React.FC = () => { + const { t } = useTranslation(); + + const navigate = useNavigate(); + const { pushNotification } = React.useContext(NotificationContext); + + const project = useOutletContext(); + const updateProjectMutation = useUpdateProjectMutation((p) => { + pushNotification({ + title: t("info.data-saved"), + message: "", + key: p.name, + variant: "success", + actionClose: true, + timeout: 4000, + }); + }); + + const { + control, + formState: { errors, isValid, isValidating, isDirty }, + reset, + getValues, + } = useForm({ + defaultValues: { + facturaURL: "", + guiaURL: "", + retencionURL: "", + username: "", + password: "", + }, + resolver: yupResolver( + object().shape({ + facturaURL: string().trim().required().max(250), + guiaURL: string().trim().required().max(250), + retencionURL: string().trim().required().max(250), + username: string().trim().required().max(250), + password: string().trim().required().max(250), + }) + ), + mode: "onChange", + }); + + useEffect(() => { + if (project) { + reset({ + facturaURL: project.sunat.facturaUrl, + guiaURL: project.sunat.guiaUrl, + retencionURL: project.sunat.retencionUrl, + username: project.sunat.username, + password: "******", + }); + } + }, [project, reset]); + + const save = () => { + if (!project) { + return; + } + + updateProjectMutation.mutate({ + ...project, + sunat: { + facturaUrl: getValues().facturaURL, + guiaUrl: getValues().guiaURL, + retencionUrl: getValues().retencionURL, + username: getValues().username, + password: control.getFieldState("password").isDirty + ? getValues().password + : undefined, + }, + }); + }; + + return ( + <> + + + SUNAT + Configuración para envío de XMLs a la SUNAT. + + + + + + + + + + ( + + )} + /> + + + ( + + )} + /> + + + ( + + )} + /> + + + + + + ( + + )} + /> + + + ( + + )} + /> + + + + + + {t("actions.save")} + + navigate("/projects")}> + {t("actions.cancel")} + + + + + + + + + > + ); +}; diff --git a/application/src/main/webapp/src/pages/project-edit/components/company-logo/company-logo.tsx b/application/src/main/webapp/src/shared/components/company-logo/company-logo.tsx similarity index 100% rename from application/src/main/webapp/src/pages/project-edit/components/company-logo/company-logo.tsx rename to application/src/main/webapp/src/shared/components/company-logo/company-logo.tsx diff --git a/application/src/main/webapp/src/pages/project-edit/components/company-logo/index.ts b/application/src/main/webapp/src/shared/components/company-logo/index.ts similarity index 100% rename from application/src/main/webapp/src/pages/project-edit/components/company-logo/index.ts rename to application/src/main/webapp/src/shared/components/company-logo/index.ts diff --git a/application/src/main/webapp/src/pages/project-edit/components/component-form/component-form.tsx b/application/src/main/webapp/src/shared/components/component-form/component-form.tsx similarity index 100% rename from application/src/main/webapp/src/pages/project-edit/components/component-form/component-form.tsx rename to application/src/main/webapp/src/shared/components/component-form/component-form.tsx diff --git a/application/src/main/webapp/src/pages/project-edit/components/component-form/index.ts b/application/src/main/webapp/src/shared/components/component-form/index.ts similarity index 100% rename from application/src/main/webapp/src/pages/project-edit/components/component-form/index.ts rename to application/src/main/webapp/src/shared/components/component-form/index.ts diff --git a/application/src/main/webapp/src/shared/components/index.ts b/application/src/main/webapp/src/shared/components/index.ts index dfc81f08..6e135436 100644 --- a/application/src/main/webapp/src/shared/components/index.ts +++ b/application/src/main/webapp/src/shared/components/index.ts @@ -1,2 +1,4 @@ export * from "./add-project-wizard"; export * from "./page-header"; +export * from "./company-logo"; +export * from "./component-form"; diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000..2e74e903 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,6 @@ +{ + "name": "ublhub", + "lockfileVersion": 2, + "requires": true, + "packages": {} +}