Merge branch 'products' into develop

This commit is contained in:
Andres Alvarez 2024-02-08 16:14:16 -05:00
commit 31dc8c9478
18 changed files with 665 additions and 11 deletions

View File

@ -2,7 +2,8 @@ const locale = {
APPLICATIONS: 'Applications',
EXAMPLE: 'Example',
DASHBOARD: 'Tablero',
INVOICE: 'Factura'
INVOICE: 'Factura',
PRODUCT: 'Producto'
};
export default locale;

View File

@ -24,8 +24,8 @@ const navigationConfig: FuseNavItemType[] = [
},
{
id: 'dashboard-component',
title: 'Tablero',
translate: 'TABLERO',
title: 'Dashboard',
translate: 'DASHBOARD',
type: 'item',
icon: 'heroicons-outline:star',
url: 'dashboards/project'
@ -37,6 +37,14 @@ const navigationConfig: FuseNavItemType[] = [
type: 'item',
icon: 'heroicons-outline:document-text',
url: 'invoice/list'
},
{
id: 'producto-component',
title: 'Producto',
translate: 'PRODUCT',
type: 'item',
icon: 'heroicons-outline:clipboard-check',
url: 'product/list'
}
];

View File

@ -9,10 +9,10 @@ import SignOutConfig from '../main/sign-out/SignOutConfig';
import Error404Page from '../main/404/Error404Page';
import ExampleConfig from '../main/example/ExampleConfig';
import ProjectDashboardAppConfig from '../main/dashboard/project/ProjectDashboardAppConfig';
import ProductoConfigs from '../main/producto/ProductoConfig'
import InvoiceConfigs from '../main/invoice/InvoiceConfig';
const routeConfigs: FuseRouteConfigsType = [ExampleConfig, SignOutConfig, SignInConfig, SignUpConfig, ProjectDashboardAppConfig, ...InvoiceConfigs];
const routeConfigs: FuseRouteConfigsType = [ExampleConfig, SignOutConfig, SignInConfig, SignUpConfig, ProjectDashboardAppConfig, ...ProductoConfigs, ...InvoiceConfigs];
/**
* The routes of the application.

View File

@ -1,15 +1,10 @@
import FusePageSimple from '@fuse/core/FusePageSimple';
import Tab from '@mui/material/Tab';
import Tabs from '@mui/material/Tabs';
import { useState } from 'react';
import Box from '@mui/material/Box';
import { styled } from '@mui/material/styles';
import * as React from 'react';
import FuseLoading from '@fuse/core/FuseLoading';
import ProjectDashboardAppHeader from './ProjectDashboardAppHeader';
import HomeTab from './tabs/home/HomeTab';
import TeamTab from './tabs/team/TeamTab';
import BudgetTab from './tabs/budget/BudgetTab';
import { useGetProjectDashboardWidgetsQuery } from './ProjectDashboardApi';
const Root = styled(FusePageSimple)(({ theme }) => ({

View File

@ -0,0 +1,37 @@
import DemoContent from '@fuse/core/DemoContent';
import FusePageSimple from '@fuse/core/FusePageSimple';
import { useTranslation } from 'react-i18next';
import { styled } from '@mui/material/styles';
import ListDetalleProductoRender from './detalleProducto/DetalleProductoRender';
import ProductoRender from './ProductoRender';
const Root = styled(FusePageSimple)(({ theme }) => ({
'& .FusePageSimple-header': {
backgroundColor: theme.palette.background.paper,
borderBottomWidth: 1,
borderStyle: 'solid',
borderColor: theme.palette.divider
},
'& .FusePageSimple-content': {},
'& .FusePageSimple-sidebarHeader': {},
'& .FusePageSimple-sidebarContent': {}
}));
function Producto() {
const { t } = useTranslation('products');
return(
<Root
header={
<div className='p-24'>
<h4>{t('PRODUCTOS')}</h4>
</div>
}
content={
<ProductoRender></ProductoRender>
}
/>
)
}
export default Producto;

View File

@ -0,0 +1,10 @@
import { FuseRouteConfigsType } from "@fuse/utils/FuseUtils";
import FormularioProductoConfig from "./formularioProducto/FormularioProductoConfig";
import DetalleProductoConfig from "./detalleProducto/DetalleProductoConfig";
const productoConfigs: FuseRouteConfigsType = [
DetalleProductoConfig,
FormularioProductoConfig
]
export default productoConfigs;

View File

@ -0,0 +1,14 @@
import BuscarProducto from "./widgets/BuscarProducto";
import ListDetalleProductoRender from "./detalleProducto/DetalleProductoRender";
import DetalleProducto from "./detalleProducto/DetalleProducto";
function ProductoRender() {
return(
<div className="w-full p-12 pt-16 sm:pt-24 lg:ltr:pr-0 lg:rtl:pl-0">
<BuscarProducto></BuscarProducto>
<DetalleProducto></DetalleProducto>
</div>
)
}
export default ProductoRender;

View File

@ -0,0 +1,9 @@
import ListDetalleProductoRender from "./DetalleProductoRender"
const DetalleProducto = () => {
return(
<ListDetalleProductoRender/>
)
}
export default DetalleProducto;

View File

@ -0,0 +1,21 @@
import i18next from "i18next";
import { lazy } from 'react';
import es from '../i18n/es';
i18next.addResourceBundle('es', 'detailProduct', es);
const Producto = lazy(() => import('../Producto'));
const DetalleProductoConfig = {
settings: {
layout: {}
},
routes: [
{
path: 'product/list',
element: <Producto/>
}
]
};
export default DetalleProductoConfig;

View File

@ -0,0 +1,214 @@
import * as React from "react";
import FuseSvgIcon from "@fuse/core/FuseSvgIcon";
import {
Button,
IconButton,
Paper,
Table,
TableBody,
TableCell,
TableHead,
TablePagination,
TableRow,
Typography,
} from "@mui/material";
import { useTranslation } from "react-i18next";
import { Link } from "react-router-dom";
import clsx from "clsx";
import ResponsiveDialog from "../widgets/DialogDelete";
function ListDetalleProductoRender() {
const [page, setPage] = React.useState(0);
const [rowsPerPage, setRowsPerPage] = React.useState(10);
const handleChangePage = (event: unknown, newPage: number) => {
setPage(newPage);
};
const handleChangeRowsPerPage = (
event: React.ChangeEvent<HTMLInputElement>
) => {
setRowsPerPage(+event.target.value);
setPage(0);
};
const { t } = useTranslation("products");
const columns = [
"Código",
"Nombre",
"Descripción",
"Stock Mínimo",
"Estado",
"Acciones",
];
const rows = [
{
artCodigo: 1,
artNombre: "Servicios Profesionales",
artDescripcion: "Prestación de servicios",
artStockMinimo: 0,
artEstado: "activo",
action: true,
},
{
artCodigo: 2,
artNombre: "Servicios Varios",
artDescripcion: "",
artStockMinimo: 0,
artEstado: "inactivo",
action: true,
},
{
artCodigo: 3,
artNombre: "Servicios Profesionales",
artDescripcion: "Prestación de servicios",
artStockMinimo: 0,
artEstado: "activo",
action: true,
},
{
artCodigo: 4,
artNombre: "Servicios Varios",
artDescripcion: "",
artStockMinimo: 0,
artEstado: "inactivo",
action: true,
},
{
artCodigo: 5,
artNombre: "Servicios Profesionales",
artDescripcion: "Prestación de servicios",
artStockMinimo: 0,
artEstado: "activo",
action: true,
},
{
artCodigo: 6,
artNombre: "Servicios Varios",
artDescripcion: "",
artStockMinimo: 0,
artEstado: "inactivo",
action: true,
},
];
return (
<Paper className="flex flex-col flex-auto p-24 shadow rounded-2 overflow-hidden mt-6">
<div className="flex md:flex-row justify-between flex-col">
<div>
<Typography className="mr-16 text-lg font-medium tracking-tight leading-6 truncate">
{t("Detalle de productos")}
</Typography>
</div>
<div>
<Button
size="small"
variant="contained"
color="secondary"
component={Link}
to={"/product/form"}
startIcon={
<FuseSvgIcon
className="text-48 text-white"
size={24}
color="action"
>
heroicons-outline:plus
</FuseSvgIcon>
}
>
Registrar Producto
</Button>
</div>
</div>
<div className="table-responsive mt-24">
<Table className="simple w-full min-w-full h-full">
<TableHead>
<TableRow>
{columns.map((column, index) => (
<TableCell key={index}>
<Typography
color="text.secondary"
className="font-semibold text-12 whitespace-nowrap"
>
{column}
</Typography>
</TableCell>
))}
</TableRow>
</TableHead>
<TableBody>
{rows
.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage)
.map((row, index) => (
<TableRow key={index}>
{Object.entries(row).map(([key, value]) => {
switch (key) {
case "artEstado": {
return (
<TableCell key={key} component="th" scope="row">
<Typography
className={clsx(
"inline-flex items-center font-bold text-10 px-10 py-2 rounded-full tracking-wide uppercase",
value === "inactivo" &&
"bg-red-100 text-red-800 dark:bg-red-600 dark:text-red-50",
value === "activo" &&
"bg-green-50 text-green-800 dark:bg-green-600 dark:text-green-50"
)}
>
{value}
</Typography>
</TableCell>
);
}
case "action": {
return (
<TableCell
key={key}
component="th"
scope="row"
className="flex gap-5"
>
<div>
<IconButton size="small" color="inherit">
<FuseSvgIcon>
heroicons-outline:pencil-alt
</FuseSvgIcon>
</IconButton>
</div>
<div>
<ResponsiveDialog></ResponsiveDialog>
</div>
</TableCell>
);
}
default: {
return (
<TableCell key={key} component="th" scope="row">
<Typography>{value}</Typography>
</TableCell>
);
}
}
})}
</TableRow>
))}
</TableBody>
</Table>
<TablePagination
rowsPerPageOptions={[5, 10, 25]}
component="div"
count={rows.length}
rowsPerPage={rowsPerPage}
page={page}
onPageChange={handleChangePage}
onRowsPerPageChange={handleChangeRowsPerPage}
/>
</div>
</Paper>
);
}
export default ListDetalleProductoRender;

View File

@ -0,0 +1,9 @@
import FormularioProductoRender from "./FormularioProductoRender"
const FormularioProducto = () => {
return(
<FormularioProductoRender/>
);
};
export default FormularioProducto;

View File

@ -0,0 +1,23 @@
import i18next from "i18next";
import es from "../i18n/es";
import { lazy } from "react";
i18next.addResourceBundle('es', 'formProducto', es);
const FormularioProducto = lazy(() => import('./FormularioProducto'));
const FormularioProductoConfig = {
settings: {
layout: {
config: {},
},
},
routes: [
{
path: 'product/form',
element: <FormularioProducto />,
},
],
};
export default FormularioProductoConfig;

View File

@ -0,0 +1,154 @@
import FusePageSimple from "@fuse/core/FusePageSimple";
import {
Autocomplete,
Card,
CardActions,
CardContent,
CardHeader,
Divider,
TextField,
styled,
Button,
Grid,
} from "@mui/material";
import { useState } from "react";
const FormularioProductoRender = () => {
const [formData, setFormData] = useState({
codigo: "",
tipo: "",
nombre: "",
descripcion: "",
stockMinimo: "",
margenGanancia: "",
estado: "",
});
const [datosCargados, setDatosCargados] = useState(false);
const cargarDatosDesdeBD = () => {
setDatosCargados(true);
};
return (
<Card className="m-20 p-24">
<CardHeader title="Registro producto" className="text-16 font-bold " />
<Divider />
<CardContent className="flex">
<Grid className="grid grid-cols-1 sm:grid-cols-6 gap-24 w-full min-w-0 p-24">
<TextField
className="mt-8 mb-16 sm:col-span-3 lg:col-span-3"
required
label="Código"
id="codigo"
name="codigo"
variant="outlined"
fullWidth
/>
<Autocomplete
className="mt-8 mb-16 sm:col-span-3 lg:col-span-3"
multiple
freeSolo
options={[]}
renderInput={(params) => (
<TextField
{...params}
placeholder="Seleccione el tipo de producto"
label="Tipo"
variant="outlined"
InputLabelProps={{
shrink: true,
}}
/>
)}
/>
<TextField
className="mt-8 mb-16 sm:col-span-3 lg:col-span-3"
required
label="Nombre"
id="nombre"
name="nombre"
variant="outlined"
fullWidth
/>
<Autocomplete
className="mt-8 mb-16 sm:col-span-3"
multiple
freeSolo
options={["Activo", "Inactivo"]}
renderInput={(params) => (
<TextField
{...params}
placeholder="Seleccione el estado del producto"
label="Estado"
variant="outlined"
InputLabelProps={{
shrink: true,
}}
/>
)}
/>
<TextField
className="mt-8 mb-16 sm:col-span-3 lg:col-span-3"
required
label="Stock Mínimo"
id="stockMinimo"
name="stockMinimo"
variant="outlined"
fullWidth
/>
<TextField
className="mt-8 mb-16 sm:col-span-3 md:col-span-3"
required
label="Margen de Ganancia"
id="margenGanancia"
name="margenGanancia"
variant="outlined"
fullWidth
/>
<TextField
className="mt-8 mb-16 sm:col-span-6 lg:col-span-6"
id="descripcion"
label="Descripción"
name="descripcion"
type="text"
multiline
rows={5}
variant="outlined"
fullWidth
/>
</Grid>
</CardContent>
<CardActions
className=""
>
<Button
className="w-320"
variant="contained"
size="small"
color="secondary"
>
Guardar{" "}
</Button>
{datosCargados && (
<Button
className="w-320"
variant="contained"
size="small"
color="primary"
>
Actualizar{" "}
</Button>
)}
</CardActions>
</Card>
);
};
export default FormularioProductoRender;

View File

@ -0,0 +1,5 @@
const locale = {
TITLE: 'Producto'
};
export default locale;

View File

@ -0,0 +1,81 @@
import * as React from 'react';
import { styled, alpha } from '@mui/material/styles';
import Box from '@mui/material/Box';
import Toolbar from '@mui/material/Toolbar';
import IconButton from '@mui/material/IconButton';
import Typography from '@mui/material/Typography';
import InputBase from '@mui/material/InputBase';
import FuseSvgIcon from '@fuse/core/FuseSvgIcon';
import FuseSearch from '@fuse/core/FuseSearch';
import { Card } from '@mui/material';
import { color } from '@mui/system';
const Search = styled('div')(({ theme }) => ({
position: 'relative',
borderRadius: theme.shape.borderRadius,
backgroundColor: alpha(theme.palette.common.black, 0.06),
'&:hover': {
/* backgroundColor: alpha(theme.palette.common.white, 0.25), */
},
width: '100%',
[theme.breakpoints.up('sm')]: {
/* marginLeft: theme.spacing(), */
width: 'auto',
},
}));
const SearchIconWrapper = styled('div')(({ theme }) => ({
padding: theme.spacing(0, 2),
height: '100%',
position: 'absolute',
pointerEvents: 'none',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}));
const StyledInputBase = styled(InputBase)(({ theme }) => ({
color: 'inherit',
width: '100%',
'& .MuiInputBase-input': {
padding: theme.spacing(1, 1, 1, 0),
// vertical padding + font size from searchIcon
paddingLeft: `calc(1em + ${theme.spacing(4)})`,
transition: theme.transitions.create('width'),
},
}));
function BuscarProducto() {
return (
<Box sx={{ flexGrow: 1 }}>
<div
className='bg-white'>
<Typography className="text-lg font-medium tracking-tight leading-6 truncate"
paddingTop={1}
paddingLeft={3}
>
{("Buscar producto")}
</Typography>
<Toolbar>
<Search>
<SearchIconWrapper>
<FuseSvgIcon
className="text-48 text-black"
size={24}
color="action"
>
heroicons-outline:search
</FuseSvgIcon>
</SearchIconWrapper>
<StyledInputBase
placeholder="Buscar Producto"
inputProps={{ 'aria-label': 'search' }}
/>
</Search>
</Toolbar>
</div>
</Box>
);
}
export default BuscarProducto;

View File

@ -0,0 +1,59 @@
import FuseSvgIcon from "@fuse/core/FuseSvgIcon";
import {
Button,
useMediaQuery,
useTheme,
Dialog,
DialogTitle,
DialogContent,
DialogContentText,
DialogActions,
IconButton,
} from "@mui/material";
import React from "react";
function ResponsiveDialog() {
const [open, setOpen] = React.useState(false);
const theme = useTheme();
const fullScreen = useMediaQuery(theme.breakpoints.down("sm"));
const handleClickOpen = () => {
setOpen(true);
};
const handleClose = () => {
setOpen(false);
};
return (
<div>
<IconButton size="small" color="default"
onClick={handleClickOpen}>
<FuseSvgIcon>heroicons-outline:trash</FuseSvgIcon>
</IconButton>
<Dialog
fullScreen={fullScreen}
open={open}
onClose={handleClose}
aria-labelledby="responsive-dialog-title"
>
<DialogTitle id="responsive-dialog-title">{"Confirmar"}</DialogTitle>
<DialogContent>
<DialogContentText>
Este elemento se eliminará permanentemente. ¿Deseas continuar?.
</DialogContentText>
</DialogContent>
<DialogActions>
<Button autoFocus onClick={handleClose} color="primary">
Cancelar
</Button>
<Button onClick={handleClose} color="primary" autoFocus>
Aceptar
</Button>
</DialogActions>
</Dialog>
</div>
);
}
export default ResponsiveDialog;

View File

@ -0,0 +1,14 @@
type DetalleProductoRow = {
artCodigo: number;
artNombre: string;
artDescripcion: string;
artStockMinimo: number;
artEstado: any;
};
type DetalleProductoType = {
columns: string[];
rows: DetalleProductoRow[];
};
export default DetalleProductoType;

View File

@ -48,5 +48,5 @@
"tailwind.config.js",
"vite.config.mts",
"vite-env.d.ts"
],
, "src/app/main/producto/.tsx" ],
}