Ref: modification components in selfcheckout
This commit is contained in:
parent
251a6a2a6a
commit
ab7a02bcca
40 changed files with 1315 additions and 557 deletions
77
package/components/organisms/Auth/LoginForm.tsx
Normal file
77
package/components/organisms/Auth/LoginForm.tsx
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
"use client";
|
||||
import { Box, Typography } from "@mui/material";
|
||||
import { useSetAtom } from "jotai";
|
||||
import { useRouter } from "next/navigation";
|
||||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import Button from "@atoms/Button";
|
||||
import Input from "@atoms/Input";
|
||||
import Label from "@atoms/Label";
|
||||
import UserAtom from "@atoms/states/UserAtom";
|
||||
import { LoginResponse } from "@interfaces/ResponseInterfaces/Auth/UserResponseInterface";
|
||||
import { Login } from "@package/config/api/Admin/auth/Login/page";
|
||||
|
||||
const LoginForm: React.FC = () => {
|
||||
const [email, setEmail] = useState("");
|
||||
const [password, setPassword] = useState("");
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const router = useRouter();
|
||||
const { t } = useTranslation();
|
||||
const setUser = useSetAtom(UserAtom); // Hook para actualizar el estado global del usuario
|
||||
|
||||
const handleLogin = async () => {
|
||||
try {
|
||||
const response: LoginResponse = await Login(email, password);
|
||||
if (response.correct) {
|
||||
// Almacenamos los datos del usuario logueado en el átomo global
|
||||
setUser(response.object);
|
||||
|
||||
// Redireccionamos a la página deseada
|
||||
router.push("/selfcheckout/precheckout");
|
||||
} else {
|
||||
setError(response.message || t("msj_Incorrect_credentials_try_again"));
|
||||
}
|
||||
} catch (err) {
|
||||
setError(t("msj_Incorrect_credentials_try_again"));
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Box
|
||||
component="form"
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault();
|
||||
handleLogin();
|
||||
}}
|
||||
sx={{ display: "flex", flexDirection: "column", gap: 3, width: "100%" }}
|
||||
>
|
||||
<Box>
|
||||
<Label htmlFor={t("lbl_email")} text={t("lbl_email")} />
|
||||
<Input
|
||||
type="email"
|
||||
placeholder={t("msj_enter_your_email")}
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
autocomplete="email"
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<Box>
|
||||
<Label htmlFor={t("lbl_password")} text={t("lbl_password")} />
|
||||
<Input
|
||||
type="password"
|
||||
placeholder={t("msj_enter_your_password")}
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
autocomplete="current-password"
|
||||
/>
|
||||
</Box>
|
||||
|
||||
{error && <Typography color="error">{error}</Typography>}
|
||||
|
||||
<Button label={t("lbl_login")} onClick={handleLogin} variant="primary" />
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default LoginForm;
|
||||
|
|
@ -170,14 +170,15 @@ const ButtonPanel: React.FC = () => {
|
|||
</Tooltip>
|
||||
</Grid>
|
||||
<Grid size={{ xs: 4 }}>
|
||||
<Tooltip explainer={t("lbl_bag_fee")} side="top" withArrow>
|
||||
<Tooltip explainer={t("msj_bag_fee")} side="top" withArrow>
|
||||
<Box sx={{ width: "100%" }}>
|
||||
<Button
|
||||
label={t("msj_bag_fee")}
|
||||
label= {t("lbl_bag_fee")}
|
||||
onClick={() => {}}
|
||||
variant="primary"
|
||||
className="full-size-button"
|
||||
startIcon={<ShoppingBagIcon />}
|
||||
|
||||
/>
|
||||
</Box>
|
||||
</Tooltip>
|
||||
47
package/components/organisms/Checkout/ControlPanel.tsx
Normal file
47
package/components/organisms/Checkout/ControlPanel.tsx
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
"use client";
|
||||
|
||||
import Grid2 from '@mui/material/Grid2';
|
||||
import React from 'react';
|
||||
import ActionButtons from '@molecules/ActionButtons';
|
||||
import ProductSearch from '@molecules/ProductSearch';
|
||||
import SaleSummary from '@molecules/SaleSummary';
|
||||
|
||||
interface ControlPanelProps {
|
||||
onVoidOrder: () => void;
|
||||
onVoidLast: () => void;
|
||||
onResume: () => void;
|
||||
onSearchPLU: (plu: string) => void;
|
||||
subtotal: number;
|
||||
discount: number;
|
||||
tax: number;
|
||||
total: number;
|
||||
}
|
||||
|
||||
const ControlPanel: React.FC<ControlPanelProps> = ({
|
||||
onVoidOrder,
|
||||
onVoidLast,
|
||||
onResume,
|
||||
onSearchPLU,
|
||||
subtotal,
|
||||
discount,
|
||||
tax,
|
||||
total
|
||||
}) => {
|
||||
return (
|
||||
<Grid2 container spacing={4}>
|
||||
<Grid2 size={{xs:12}}>
|
||||
<ActionButtons onVoidOrder={onVoidOrder} onVoidLast={onVoidLast} onResume={onResume} />
|
||||
</Grid2>
|
||||
|
||||
<Grid2 size={{xs:12}}>
|
||||
<ProductSearch onSearch={onSearchPLU} />
|
||||
</Grid2>
|
||||
|
||||
<Grid2 size={{xs:12}}>
|
||||
<SaleSummary subtotal={subtotal} discount={discount} tax={tax} total={total} />
|
||||
</Grid2>
|
||||
</Grid2>
|
||||
);
|
||||
};
|
||||
|
||||
export default ControlPanel;
|
||||
92
package/components/organisms/Checkout/ProductTable.tsx
Normal file
92
package/components/organisms/Checkout/ProductTable.tsx
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
"use client";
|
||||
|
||||
import { Box, Table, TableBody, TableCell, TableHead, TableRow } from '@mui/material';
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import CustomCard from '@molecules/CustomCard';
|
||||
import UserHeader from '@molecules/UserHeader';
|
||||
|
||||
interface Product {
|
||||
id: number;
|
||||
amount: number;
|
||||
name: string;
|
||||
unitValue: number;
|
||||
tax: number;
|
||||
discount: number;
|
||||
total: number;
|
||||
}
|
||||
|
||||
interface ProductTableProps {
|
||||
products: Product[];
|
||||
}
|
||||
|
||||
const ProductTable: React.FC<ProductTableProps> = ({ products }) => {
|
||||
const totalAmount = products.reduce((acc, product) => acc + product.amount, 0);
|
||||
const totalUnitValue = products.reduce((acc, product) => acc + product.unitValue * product.amount, 0);
|
||||
const totalTax = products.reduce((acc, product) => acc + product.tax, 0);
|
||||
const totalDiscount = products.reduce((acc, product) => acc + product.discount, 0);
|
||||
const grandTotal = products.reduce((acc, product) => acc + product.total, 0);
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
|
||||
<UserHeader />
|
||||
<Box sx={{ flexGrow: 1, overflow: 'auto', p: 2, boxShadow: '2'}}>
|
||||
<Table stickyHeader sx={{
|
||||
'& .MuiTableCell-root': {
|
||||
padding: '8px 16px',
|
||||
}
|
||||
}}>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell>{t("tle_amount")}</TableCell>
|
||||
<TableCell>{t("tle_product")}</TableCell>
|
||||
<TableCell>{t("tle_unit_value")}</TableCell>
|
||||
<TableCell>{t("tle_tax")}</TableCell>
|
||||
<TableCell>{t("tle_discount")}</TableCell>
|
||||
<TableCell>{t("tle_total")}</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{products.map((product) => (
|
||||
<TableRow key={product.id}>
|
||||
<TableCell>{product.amount}</TableCell>
|
||||
<TableCell>{product.name}</TableCell>
|
||||
<TableCell>${product.unitValue.toFixed(2)}</TableCell>
|
||||
<TableCell>${product.tax.toFixed(2)}</TableCell>
|
||||
<TableCell>${product.discount.toFixed(2)}</TableCell>
|
||||
<TableCell>${product.total.toFixed(2)}</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</Box>
|
||||
<Box sx={{ p:0.2, boxShadow: '2', }}>
|
||||
<CustomCard className="bg-gray-700 text-white">
|
||||
<Table>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell sx={{ color: 'RGB(255,255,255)', borderColor: 'with', fontWeight: 'bold', width: '20%' }}>{t("tle_item_count")}</TableCell>
|
||||
<TableCell sx={{ color: 'RGB(255,255,255)', borderColor: 'with', fontWeight: 'bold', width: '20%' }}>{t("tle_total_unit")}</TableCell>
|
||||
<TableCell sx={{ color: 'RGB(255,255,255)', borderColor: 'with', fontWeight: 'bold', width: '20%' }}>{t("tle_total_taxes")}</TableCell>
|
||||
<TableCell sx={{ color: 'RGB(255,255,255)', borderColor: 'with', fontWeight: 'bold', width: '20%' }}>{t("tle_total_discount")}</TableCell>
|
||||
<TableCell sx={{ color: 'RGB(255,255,255)', borderColor: 'with', fontWeight: 'bold', width: '20%' }}>{t("tle_total")}</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
<TableRow>
|
||||
<TableCell sx={{ color: 'RGB(255,255,255)', borderColor: 'with' }}>{totalAmount}</TableCell>
|
||||
<TableCell sx={{ color: 'RGB(255,255,255)', borderColor: 'with' }}>${totalUnitValue.toFixed(2)}</TableCell>
|
||||
<TableCell sx={{ color: 'RGB(255,255,255)', borderColor: 'with' }}>${totalTax.toFixed(2)}</TableCell>
|
||||
<TableCell sx={{ color: 'RGB(255,255,255)', borderColor: 'with' }}>${totalDiscount.toFixed(2)}</TableCell>
|
||||
<TableCell sx={{ color: 'RGB(255,255,255)', borderColor: 'with' }}>${grandTotal.toFixed(2)}</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
</CustomCard>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default ProductTable;
|
||||
|
|
@ -1,81 +0,0 @@
|
|||
import MoreVertIcon from "@mui/icons-material/MoreVert";
|
||||
import PersonIcon from "@mui/icons-material/Person";
|
||||
import { Card, CardContent, IconButton, Typography } from "@mui/material";
|
||||
import Grid from "@mui/material/Grid2";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
interface HeaderProps {
|
||||
sx?: object;
|
||||
onToggleButtonPanel: () => void;
|
||||
}
|
||||
|
||||
const Header: React.FC<HeaderProps> = ({ sx, onToggleButtonPanel }) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [currentDateTime, setCurrentDateTime] = useState(new Date());
|
||||
|
||||
useEffect(() => {
|
||||
const interval = setInterval(() => {
|
||||
setCurrentDateTime(new Date());
|
||||
}, 60000);
|
||||
|
||||
return () => clearInterval(interval);
|
||||
}, []);
|
||||
|
||||
const formattedDate = currentDateTime.toLocaleDateString(undefined, {
|
||||
weekday: "short",
|
||||
day: "numeric",
|
||||
month: "short",
|
||||
});
|
||||
const formattedTime = currentDateTime.toLocaleTimeString(undefined, {
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
});
|
||||
|
||||
return (
|
||||
<Card sx={{ ...sx, p: 0 }}>
|
||||
<CardContent sx={{ padding: "1px 8px" }}>
|
||||
<Grid container alignItems="center" justifyContent="space-between">
|
||||
<Grid>
|
||||
<Typography variant="body2" color="text.primary" sx={{ fontWeight: "bold" }}>
|
||||
Generic user
|
||||
</Typography>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
Add Member
|
||||
</Typography>
|
||||
</Grid>
|
||||
|
||||
<Grid>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
{formattedDate} - {formattedTime}
|
||||
</Typography>
|
||||
</Grid>
|
||||
|
||||
<Grid>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
{t("lbl_Store")}: <strong>8004</strong> | {t("lbl_register")}: <strong>317</strong>
|
||||
</Typography>
|
||||
</Grid>
|
||||
|
||||
<Grid>
|
||||
<Grid container alignItems="center">
|
||||
<PersonIcon sx={{ color: "text.secondary", fontSize: "18px", mr: 0.5 }} />
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
{t("lbl_associate")} <strong>2</strong>
|
||||
</Typography>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
<Grid>
|
||||
<IconButton size="small" onClick={onToggleButtonPanel}>
|
||||
<MoreVertIcon color="action" sx={{ fontSize: "18px" }} />
|
||||
</IconButton>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
export default Header;
|
||||
|
|
@ -1,69 +0,0 @@
|
|||
"use client"
|
||||
import { Box, Typography } from "@mui/material"
|
||||
import { useRouter } from "next/navigation"
|
||||
import React, { useState } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import Button from "@atoms/Button"
|
||||
import Input from "@atoms/Input"
|
||||
import Label from "@atoms/Label"
|
||||
import {Login} from "@package/config/api/Admin/auth/Login/page"
|
||||
|
||||
const LoginForm: React.FC = () => {
|
||||
const [email, setEmail] = useState("")
|
||||
const [password, setPassword] = useState("")
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
const router = useRouter()
|
||||
const { t } = useTranslation()
|
||||
|
||||
const handleLogin = async () => {
|
||||
try {
|
||||
const response = await Login(email, password)
|
||||
if (response.correct) {
|
||||
router.push("/selfcheckout/precheckout")
|
||||
} else {
|
||||
setError(response.message || t("msj_Incorrect_credentials_try_again"))
|
||||
}
|
||||
} catch (err) {
|
||||
setError(t("msj_Incorrect_credentials_try_again"))
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Box
|
||||
component="form"
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault()
|
||||
handleLogin()
|
||||
}}
|
||||
sx={{ display: "flex", flexDirection: "column", gap: 3, width: "100%" }}
|
||||
>
|
||||
<Box>
|
||||
<Label htmlFor={t("lbl_email")} text={t("lbl_email")} />
|
||||
<Input
|
||||
type="email"
|
||||
placeholder={t("msj_enter_your_email")}
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
autocomplete="email"
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<Box>
|
||||
<Label htmlFor={t("lbl_password")} text={t("lbl_password")} />
|
||||
<Input
|
||||
type="password"
|
||||
placeholder={t("msj_enter_your_password")}
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
autocomplete="current-password"
|
||||
/>
|
||||
</Box>
|
||||
|
||||
{error && <Typography color="error">{error}</Typography>}
|
||||
|
||||
<Button label={t("lbl_login")} onClick={handleLogin} variant="primary" />
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
export default LoginForm
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
"use client"
|
||||
import { Card, CardContent } from "@mui/material"
|
||||
import Grid from "@mui/material/Grid2"
|
||||
import React from "react"
|
||||
import NumericPad from "@molecules/NumericPad"
|
||||
import PluEntry from "@molecules/PluEntry"
|
||||
import ProductMatch from "@molecules/ProductMatch"
|
||||
import Screen from "@molecules/Screen"
|
||||
|
||||
const MainPanel: React.FC = () => {
|
||||
return (
|
||||
<Card sx={{ height: "100%" }}>
|
||||
<CardContent sx={{ padding: "8px" }}>
|
||||
<Grid container spacing={1} justifyContent="center" alignItems="stretch">
|
||||
<Grid size={{ xs: 12 }}>
|
||||
<Screen />
|
||||
</Grid>
|
||||
<Grid size={{ xs: 12 }}>
|
||||
<ProductMatch />
|
||||
</Grid>
|
||||
<Grid size={{ xs: 12 }}>
|
||||
<PluEntry />
|
||||
</Grid>
|
||||
<Grid size={{ xs: 12 }}>
|
||||
<NumericPad />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
export default MainPanel
|
||||
|
|
@ -1,17 +1,23 @@
|
|||
"use client";
|
||||
import LanguageIcon from '@mui/icons-material/Language';
|
||||
import MoreVertIcon from "@mui/icons-material/MoreVert"; // Botón de 3 puntos verticales
|
||||
import { Typography } from '@mui/material';
|
||||
import Grid from '@mui/material/Grid2';
|
||||
import IconButton from '@mui/material/IconButton';
|
||||
import Menu from '@mui/material/Menu';
|
||||
import MenuItem from '@mui/material/MenuItem';
|
||||
import { useAtom } from 'jotai';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import UserAtom from '@atoms/states/UserAtom';
|
||||
import i18n from '@package/utils/language/i18n';
|
||||
|
||||
const Navbar: React.FC = () => {
|
||||
const { t } = useTranslation();
|
||||
const [language, setLanguage] = useState(i18n.language || 'en');
|
||||
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
|
||||
const [user] = useAtom(UserAtom);
|
||||
const [currentDateTime, setCurrentDateTime] = useState<Date | null>(null);
|
||||
|
||||
const changeLanguage = (lng: string) => {
|
||||
i18n.changeLanguage(lng);
|
||||
|
|
@ -29,8 +35,29 @@ const Navbar: React.FC = () => {
|
|||
|
||||
useEffect(() => {
|
||||
setLanguage(i18n.language);
|
||||
const interval = setInterval(() => {
|
||||
setCurrentDateTime(new Date());
|
||||
}, 60000);
|
||||
|
||||
setCurrentDateTime(new Date());
|
||||
|
||||
return () => clearInterval(interval);
|
||||
}, []);
|
||||
|
||||
const formattedDate = currentDateTime
|
||||
? currentDateTime.toLocaleDateString(undefined, {
|
||||
weekday: "short",
|
||||
day: "numeric",
|
||||
month: "short",
|
||||
})
|
||||
: '';
|
||||
const formattedTime = currentDateTime
|
||||
? currentDateTime.toLocaleTimeString(undefined, {
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
})
|
||||
: '';
|
||||
|
||||
return (
|
||||
<Grid
|
||||
container
|
||||
|
|
@ -39,10 +66,21 @@ const Navbar: React.FC = () => {
|
|||
className="bg-blue-600 text-white p-4"
|
||||
>
|
||||
<Grid>
|
||||
<h1 className="text-white font-bold">{t('lbl_wellcome')}</h1>
|
||||
<h1 className="text-white font-bold">
|
||||
{user ? `${t('lbl_wellcome')} ${user.name} ${user.lastName}` : t('lbl_wellcome')}
|
||||
</h1>
|
||||
</Grid>
|
||||
|
||||
<Grid>
|
||||
{/* IconButton with globe icon to open language menu */}
|
||||
<Typography variant="body2" color="text.secondary" className="text-white">
|
||||
{formattedDate} - {formattedTime}
|
||||
</Typography>
|
||||
<Typography variant="body2" color="text.secondary" className="text-white">
|
||||
{t("lbl_Store")}: <strong>8004</strong> | {t("lbl_register")}: <strong>317</strong>
|
||||
</Typography>
|
||||
</Grid>
|
||||
|
||||
<Grid container alignItems="center">
|
||||
<IconButton
|
||||
aria-controls="language-menu"
|
||||
aria-haspopup="true"
|
||||
|
|
@ -60,6 +98,17 @@ const Navbar: React.FC = () => {
|
|||
<MenuItem onClick={() => changeLanguage('en')}>English</MenuItem>
|
||||
<MenuItem onClick={() => changeLanguage('es')}>Español</MenuItem>
|
||||
</Menu>
|
||||
|
||||
{/* Botón de 3 puntos verticales */}
|
||||
<IconButton
|
||||
size="small"
|
||||
className="text-white ml-4"
|
||||
onClick={() => {
|
||||
console.log('Menú de opciones abierto');
|
||||
}}
|
||||
>
|
||||
<MoreVertIcon />
|
||||
</IconButton>
|
||||
</Grid>
|
||||
</Grid>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,28 +0,0 @@
|
|||
"use client"
|
||||
import { Card, CardContent, Typography } from "@mui/material"
|
||||
import React from "react"
|
||||
|
||||
const ProductList: React.FC = () => {
|
||||
return (
|
||||
<Card sx={{ height: "100%" }}>
|
||||
{" "}
|
||||
{/* Ajustar la altura de la Card */}
|
||||
<CardContent>
|
||||
<Typography variant="h6" color="text.primary">
|
||||
Lista de productos
|
||||
</Typography>
|
||||
<Typography variant="body1" color="text.secondary">
|
||||
Producto 1
|
||||
</Typography>
|
||||
<Typography variant="body1" color="text.secondary">
|
||||
Producto 2
|
||||
</Typography>
|
||||
<Typography variant="body1" color="text.secondary">
|
||||
Producto 3
|
||||
</Typography>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
export default ProductList
|
||||
Loading…
Add table
Add a link
Reference in a new issue