Ref: modification components in selfcheckout
This commit is contained in:
parent
251a6a2a6a
commit
ab7a02bcca
40 changed files with 1315 additions and 557 deletions
|
|
@ -1,34 +0,0 @@
|
||||||
"use client"
|
|
||||||
import { Box, Container } from "@mui/material"
|
|
||||||
import Grid from "@mui/material/Grid2" // Usando Grid2
|
|
||||||
import ButtonPanel from "@organisms/ButtonPanel"
|
|
||||||
import Header from "@organisms/Header"
|
|
||||||
import MainPanel from "@organisms/MainPanel"
|
|
||||||
import ProductList from "@organisms/ProductList"
|
|
||||||
import React from "react"
|
|
||||||
|
|
||||||
const CheckoutScreen: React.FC = () => {
|
|
||||||
return (
|
|
||||||
<Container maxWidth="xl" sx={{ minHeight: "100vh", bgcolor: "background.default", padding: 0 }}>
|
|
||||||
<Header sx={{ minHeight: "64px", marginBottom: 1 }} />
|
|
||||||
|
|
||||||
<Box sx={{ flexGrow: 1 }}>
|
|
||||||
<Grid container spacing={2} alignItems="stretch" sx={{ height: "calc(100vh - 64px)" }}>
|
|
||||||
<Grid size={{ xs: 12, md: 4 }} sx={{ height: "100%" }}>
|
|
||||||
<ProductList />
|
|
||||||
</Grid>
|
|
||||||
|
|
||||||
<Grid size={{ xs: 12, md: 4 }} sx={{ height: "100%" }}>
|
|
||||||
<ButtonPanel />
|
|
||||||
</Grid>
|
|
||||||
|
|
||||||
<Grid size={{ xs: 12, md: 4 }} sx={{ height: "100%" }}>
|
|
||||||
<MainPanel />
|
|
||||||
</Grid>
|
|
||||||
</Grid>
|
|
||||||
</Box>
|
|
||||||
</Container>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default CheckoutScreen
|
|
||||||
|
|
@ -1,99 +0,0 @@
|
||||||
"use client"
|
|
||||||
import CheckoutScreen from "@app/selfcheckout/CheckoutScreen"
|
|
||||||
import AskForAffiliation from "@layouts/AskForAffiliation"
|
|
||||||
import IdentityDocumentNumber from "@layouts/IdentityDocumentNumber"
|
|
||||||
import TypeIdentityDocument from "@layouts/TypeIdentityDocument"
|
|
||||||
import { Box, Card, CardContent, Container } from "@mui/material"
|
|
||||||
import Grid from "@mui/material/Grid2"
|
|
||||||
import Header from "@organisms/Header"
|
|
||||||
import { getDocumentsByCountry } from "@package/utils/getDocumentsByCountry"
|
|
||||||
import { useEffect, useState } from "react"
|
|
||||||
|
|
||||||
const LoyaltyProgramScreen: React.FC = () => {
|
|
||||||
const [isAffiliated, setIsAffiliated] = useState<boolean | null>(null)
|
|
||||||
const [documentType, setDocumentType] = useState<string | null>(null)
|
|
||||||
const [documentNumber, setDocumentNumber] = useState<string | null>(null)
|
|
||||||
const [goToCheckout, setGoToCheckout] = useState<boolean>(false)
|
|
||||||
const [countryDocumentTypes, setCountryDocumentTypes] = useState<string[]>([])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const documents = getDocumentsByCountry()
|
|
||||||
setCountryDocumentTypes(documents)
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
const handleAffiliationSelect = (isAffiliated: boolean) => {
|
|
||||||
setIsAffiliated(isAffiliated)
|
|
||||||
if (!isAffiliated) {
|
|
||||||
setDocumentType("13")
|
|
||||||
setDocumentNumber("222222222222")
|
|
||||||
setGoToCheckout(true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleDocumentSelect = (selectedDocumentType: string) => {
|
|
||||||
setDocumentType(selectedDocumentType)
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleDocumentNumberSubmit = (enteredDocumentNumber: string) => {
|
|
||||||
setDocumentNumber(enteredDocumentNumber)
|
|
||||||
setGoToCheckout(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (goToCheckout) {
|
|
||||||
return <CheckoutScreen />
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Container
|
|
||||||
maxWidth={false}
|
|
||||||
sx={{
|
|
||||||
height: "100hv",
|
|
||||||
display: "flex",
|
|
||||||
justifyContent: "center",
|
|
||||||
alignItems: "center",
|
|
||||||
padding: 0,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Card sx={{ width: "100%", p: 2 }}>
|
|
||||||
<Header sx={{ minHeight: "32px", marginBottom: 1 }} />
|
|
||||||
|
|
||||||
<CardContent sx={{ padding: "4px", height: "100%" }}>
|
|
||||||
<Grid container spacing={2} sx={{ height: "100%" }} alignItems="center">
|
|
||||||
<Grid size={{ xs: 12, md: 6 }}>
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "column",
|
|
||||||
justifyContent: "center",
|
|
||||||
alignItems: "center",
|
|
||||||
height: "100%",
|
|
||||||
bgcolor: "#f5f5f5",
|
|
||||||
p: 5,
|
|
||||||
borderRadius: 5,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Box
|
|
||||||
component="img"
|
|
||||||
src="/img/logo.png"
|
|
||||||
alt="Rhiscom Logo"
|
|
||||||
sx={{ width: "600px", height: "auto", mb: 15 }}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
</Grid>
|
|
||||||
<Grid size={{ xs: 12, md: 6 }}>
|
|
||||||
{isAffiliated === null && <AskForAffiliation onAffiliationSelect={handleAffiliationSelect} />}
|
|
||||||
{isAffiliated && !documentType && (
|
|
||||||
<TypeIdentityDocument typesDocument={countryDocumentTypes} onDocumentSelect={handleDocumentSelect} />
|
|
||||||
)}
|
|
||||||
{isAffiliated && documentType && (
|
|
||||||
<IdentityDocumentNumber documentType={documentType} onSubmit={handleDocumentNumberSubmit} />
|
|
||||||
)}
|
|
||||||
</Grid>
|
|
||||||
</Grid>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
</Container>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default LoyaltyProgramScreen
|
|
||||||
|
|
@ -1,36 +0,0 @@
|
||||||
"use client"
|
|
||||||
import Title from "@atoms/Title"
|
|
||||||
import { Tooltip } from "@atoms/Tooltip"
|
|
||||||
import { Box, Button, Container } from "@mui/material"
|
|
||||||
import React from "react"
|
|
||||||
|
|
||||||
const PreCheckoutScreen: React.FC = () => {
|
|
||||||
const handleStartCheckout = () => {
|
|
||||||
console.log("Comenzar checkout")
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Container maxWidth="md" sx={{ textAlign: "center", marginTop: "20vh" }}>
|
|
||||||
<Box sx={{ backgroundColor: "#f1f1f1", padding: "40px", borderRadius: "8px", boxShadow: 3 }}>
|
|
||||||
<Title text="Esta caja está" size="h4" />
|
|
||||||
<Title text="Abierta" size="h2" />
|
|
||||||
|
|
||||||
<Tooltip explainer="Escanee un producto o pulse el botón para comenzar" withArrow side="top">
|
|
||||||
<Title text="Escanee un producto o pulse aquí para comenzar" size="h6" />
|
|
||||||
</Tooltip>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
variant="contained"
|
|
||||||
color="success"
|
|
||||||
size="large"
|
|
||||||
sx={{ marginTop: "40px" }}
|
|
||||||
onClick={handleStartCheckout}
|
|
||||||
>
|
|
||||||
Comenzar
|
|
||||||
</Button>
|
|
||||||
</Box>
|
|
||||||
</Container>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default PreCheckoutScreen
|
|
||||||
22
app/selfcheckout/checkout/page.tsx
Normal file
22
app/selfcheckout/checkout/page.tsx
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
"use client";
|
||||||
|
import { Box } from "@mui/material";
|
||||||
|
import React from "react";
|
||||||
|
import MainLayout from "@layouts/MainLayout";
|
||||||
|
|
||||||
|
const CheckoutScreen: React.FC = () => {
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
width: '100vw',
|
||||||
|
height: '100vh',
|
||||||
|
overflow: 'hidden',
|
||||||
|
bgcolor: "background.default",
|
||||||
|
boxShadow: '4'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<MainLayout />
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CheckoutScreen;
|
||||||
242
app/selfcheckout/clientSearch/page.tsx
Normal file
242
app/selfcheckout/clientSearch/page.tsx
Normal file
|
|
@ -0,0 +1,242 @@
|
||||||
|
'use client';
|
||||||
|
import { Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle, Paper, Typography } from '@mui/material';
|
||||||
|
import Grid from '@mui/material/Grid2';
|
||||||
|
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 CustomLoading from '@atoms/CustomLoading';
|
||||||
|
import Icon from '@atoms/Icon';
|
||||||
|
import Input from '@atoms/Input';
|
||||||
|
import Label from '@atoms/Label';
|
||||||
|
import CustomerAtom from '@atoms/states/CustomerAtom';
|
||||||
|
import Title from '@atoms/Title';
|
||||||
|
import { Tooltip } from '@atoms/Tooltip';
|
||||||
|
import { useClientSearchLogic } from '@hooks/useClientSearchLogic';
|
||||||
|
import { CustomerDniTypeInterface } from '@interfaces/ResponseInterfaces/Customer/DNITypeResponseInterface';
|
||||||
|
|
||||||
|
const ClientSearch: React.FC = () => {
|
||||||
|
const {
|
||||||
|
idTypes,
|
||||||
|
loading,
|
||||||
|
error,
|
||||||
|
result,
|
||||||
|
formData,
|
||||||
|
handleInputChange,
|
||||||
|
handleSubmitForm,
|
||||||
|
isAffiliated,
|
||||||
|
} = useClientSearchLogic();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const [openDialog, setOpenDialog] = useState(false);
|
||||||
|
const setCustomer = useSetAtom(CustomerAtom);
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
const handleOpenDialog = () => {
|
||||||
|
setOpenDialog(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCloseDialog = () => {
|
||||||
|
setOpenDialog(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleUseGenericCustomer = () => {
|
||||||
|
setCustomer({
|
||||||
|
id: '',
|
||||||
|
idCompany: 'default-company',
|
||||||
|
companyName: 'default-company-name',
|
||||||
|
idSubsidiary: 0,
|
||||||
|
nameSubsidiary: 'default-subsidiary',
|
||||||
|
externalClientId: null,
|
||||||
|
name: 'Cliente',
|
||||||
|
secondName: '',
|
||||||
|
firstSurname: 'Generico',
|
||||||
|
secondSurname: '',
|
||||||
|
idTypeCustomer: 'Tarjeta de identidad',
|
||||||
|
nameTypeCustomer: 'Tarjeta',
|
||||||
|
dni: '222222222222',
|
||||||
|
idGender: '',
|
||||||
|
nameGender: '',
|
||||||
|
idMaritalStatus: null,
|
||||||
|
nameMaritalStatus: null,
|
||||||
|
address: '',
|
||||||
|
idCity: null,
|
||||||
|
nameCity: null,
|
||||||
|
idCountry: null,
|
||||||
|
nameCountry: null,
|
||||||
|
mail: '',
|
||||||
|
phone: '',
|
||||||
|
idUser: '',
|
||||||
|
nameUser: '',
|
||||||
|
status: true,
|
||||||
|
birthdate: null,
|
||||||
|
group: [],
|
||||||
|
NameSurname: '',
|
||||||
|
SurnameName: ''
|
||||||
|
});
|
||||||
|
|
||||||
|
router.push("/selfcheckout/checkout");
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = (e: React.FormEvent) => {
|
||||||
|
e.preventDefault(); // Evita el comportamiento por defecto del formulario
|
||||||
|
handleSubmitForm(); // Ejecuta la función de búsqueda con los datos actuales
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Paper elevation={3} className="p-6 max-w-2xl mx-auto">
|
||||||
|
<Title text={t("lbl_client_search")} size="h2" />
|
||||||
|
|
||||||
|
<form onSubmit={handleSubmit} className="space-y-4 mt-4">
|
||||||
|
<Grid container spacing={2}>
|
||||||
|
<Grid size={{ xs: 12, sm: 6 }}>
|
||||||
|
<Label htmlFor="idType" text={t("lbl_id_type")} />
|
||||||
|
<select
|
||||||
|
name="idType"
|
||||||
|
value={formData.idType}
|
||||||
|
onChange={handleInputChange} // Asegura que el estado del input cambie
|
||||||
|
className="w-full p-2 border rounded"
|
||||||
|
disabled={loading}
|
||||||
|
>
|
||||||
|
<option value="">{t("lbl_selectid_type")}</option>
|
||||||
|
{idTypes.length > 0 ? (
|
||||||
|
idTypes.map((type: CustomerDniTypeInterface) => (
|
||||||
|
<option key={type.id} value={type.id}>
|
||||||
|
{type.name}
|
||||||
|
</option>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<option disabled>{t("msj_loading_id_types")}</option>
|
||||||
|
)}
|
||||||
|
</select>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<Grid size={{ xs: 12 }}>
|
||||||
|
<Label htmlFor="idNumber" text={t("lbl_id_number")} />
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
name="idNumber"
|
||||||
|
placeholder={t("msj_enter_id_number")}
|
||||||
|
value={formData.idNumber}
|
||||||
|
onChange={handleInputChange} // Actualiza el valor del input al escribir
|
||||||
|
disabled={loading}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<div className="flex justify-end">
|
||||||
|
<Tooltip explainer={t("mjs_search_for_client")} withArrow>
|
||||||
|
<Button
|
||||||
|
label={t("lbl_search")}
|
||||||
|
variant="primary"
|
||||||
|
type="submit"
|
||||||
|
disabled={loading}
|
||||||
|
startIcon={<Icon name="Search" />}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
{loading && (
|
||||||
|
<div className="flex justify-center mt-4">
|
||||||
|
<CustomLoading />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{error && (
|
||||||
|
<Typography color="error" className="mt-4">
|
||||||
|
{error}
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{result.length === 0 && !isAffiliated && (
|
||||||
|
<Paper elevation={2} className="mt-6 p-4">
|
||||||
|
<Title text={t("msj_no_affiliate")} size="h3" />
|
||||||
|
<Typography variant="body1" className="mt-2">
|
||||||
|
{t("msj_choose_option_for_non_affiliate")}
|
||||||
|
</Typography>
|
||||||
|
<div className="flex justify-center gap-4 mt-4">
|
||||||
|
<Button
|
||||||
|
label={t("lbl_use_generic_customer")}
|
||||||
|
variant="primary"
|
||||||
|
onClick={handleOpenDialog}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
label={t("lbl_create_new_customer")}
|
||||||
|
variant="primary"
|
||||||
|
onClick={() => {
|
||||||
|
// Lógica para crear nuevo cliente
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Paper>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{result.length > 0 && (
|
||||||
|
<Paper elevation={2} className="mt-6 p-4">
|
||||||
|
<Title text={t("lbl_search_result")} size="h3" />
|
||||||
|
{result.map((customer) => (
|
||||||
|
<Grid container spacing={2} className="mt-2" key={customer.id}>
|
||||||
|
<Grid size={{ xs: 6 }}>
|
||||||
|
<Typography variant="body1">
|
||||||
|
<strong>{t("lbl_name")}:</strong> {customer.name}
|
||||||
|
</Typography>
|
||||||
|
</Grid>
|
||||||
|
<Grid size={{ xs: 6 }}>
|
||||||
|
<Typography variant="body1">
|
||||||
|
<strong>{t("lbl_last_name")}:</strong> {customer.firstSurname}
|
||||||
|
</Typography>
|
||||||
|
</Grid>
|
||||||
|
<Grid size={{ xs: 6 }}>
|
||||||
|
<Typography variant="body1">
|
||||||
|
<strong>{t("lbl_email")}:</strong> {customer.mail}
|
||||||
|
</Typography>
|
||||||
|
</Grid>
|
||||||
|
<Grid size={{ xs: 6 }}>
|
||||||
|
<Typography variant="body1">
|
||||||
|
<strong>{t("lbl_phone")}:</strong> {customer.phone}
|
||||||
|
</Typography>
|
||||||
|
</Grid>
|
||||||
|
<Grid size={{ xs: 12 }}>
|
||||||
|
<Typography variant="body1">
|
||||||
|
<strong>{t("lbl_address")}:</strong> {customer.address}
|
||||||
|
</Typography>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
))}
|
||||||
|
<div className="flex justify-end mt-4">
|
||||||
|
<Button
|
||||||
|
label={t("lbl_continue")}
|
||||||
|
variant="primary"
|
||||||
|
onClick={() => {
|
||||||
|
router.push("/selfcheckout/checkout");
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Paper>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Dialog open={openDialog} onClose={handleCloseDialog} maxWidth="xs" fullWidth>
|
||||||
|
<DialogTitle sx={{ textAlign: 'center' }}>
|
||||||
|
{t("msj_confirm_generic_customer")}
|
||||||
|
</DialogTitle>
|
||||||
|
<DialogContent sx={{ display: 'flex', flexDirection: 'column', alignItems: 'center', textAlign: 'center' }}>
|
||||||
|
<DialogContentText>
|
||||||
|
{t("msj_are_you_sure_generic_customer")}
|
||||||
|
</DialogContentText>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions sx={{ justifyContent: 'center' }}>
|
||||||
|
<Button onClick={handleCloseDialog} label={t("lbl_cancel")} />
|
||||||
|
<Button
|
||||||
|
onClick={handleUseGenericCustomer}
|
||||||
|
label={t("lbl_confirm")}
|
||||||
|
variant="primary"
|
||||||
|
/>
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>
|
||||||
|
</Paper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ClientSearch;
|
||||||
57
app/selfcheckout/precheckout/page.tsx
Normal file
57
app/selfcheckout/precheckout/page.tsx
Normal file
|
|
@ -0,0 +1,57 @@
|
||||||
|
"use client";
|
||||||
|
import { Box, Button, Container } from "@mui/material";
|
||||||
|
import Grid from "@mui/material/Grid2";
|
||||||
|
import { useRouter } from "next/navigation";
|
||||||
|
import React, { useCallback, useEffect } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import Title from "@atoms/Title";
|
||||||
|
|
||||||
|
const PreCheckout: React.FC = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
const handleStartCheckout = useCallback(() => {
|
||||||
|
router.push("/selfcheckout/verifyAffiliation");
|
||||||
|
}, [router]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handleKeyPress = () => {
|
||||||
|
handleStartCheckout();
|
||||||
|
};
|
||||||
|
|
||||||
|
document.addEventListener("keydown", handleKeyPress);
|
||||||
|
|
||||||
|
// Cleanup the event listener
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener("keydown", handleKeyPress);
|
||||||
|
};
|
||||||
|
}, [handleStartCheckout]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container maxWidth="md" sx={{ textAlign: "center", marginTop: "20vh" }}>
|
||||||
|
<Box sx={{ backgroundColor: "#f1f1f1", padding: "80px", borderRadius: "10px", boxShadow: 6 }}>
|
||||||
|
<Grid size={{ xs: 4, md: 6 }}>
|
||||||
|
<Title text={t("msj_this_box_is")} size="h3" />
|
||||||
|
</Grid>
|
||||||
|
<Grid size={{ xs: 4, md: 6 }}>
|
||||||
|
<Title text={t("lb_open")} size="h1" />
|
||||||
|
</Grid>
|
||||||
|
<Grid size={{ xs: 4, md: 6 }}>
|
||||||
|
<Title text={t("msj_press_any_key_button_start")} size="h5" />
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
color="primary"
|
||||||
|
size="large"
|
||||||
|
sx={{ marginTop: "40px" }}
|
||||||
|
onClick={handleStartCheckout}
|
||||||
|
>
|
||||||
|
{t("lbl_start")}
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PreCheckout;
|
||||||
61
app/selfcheckout/verifyAffiliation/page.tsx
Normal file
61
app/selfcheckout/verifyAffiliation/page.tsx
Normal file
|
|
@ -0,0 +1,61 @@
|
||||||
|
"use client";
|
||||||
|
import { Box, Card, CardContent, Container } from "@mui/material";
|
||||||
|
import Grid from "@mui/material/Grid2";
|
||||||
|
import Image from "next/image";
|
||||||
|
import React from "react";
|
||||||
|
import CustomLoading from "@atoms/CustomLoading";
|
||||||
|
|
||||||
|
const ClientSearch = React.lazy(() => import("@app/selfcheckout/clientSearch/page"));
|
||||||
|
|
||||||
|
const VerifyAffiliation: React.FC = () => {
|
||||||
|
return (
|
||||||
|
<Container
|
||||||
|
maxWidth={false}
|
||||||
|
sx={{
|
||||||
|
height: "100vh",
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "center",
|
||||||
|
alignItems: "center",
|
||||||
|
padding: 0,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Card sx={{ width: "100%", p: 2 }}>
|
||||||
|
|
||||||
|
<CardContent sx={{ padding: "4px", height: "100%" }}>
|
||||||
|
<Grid container spacing={2} sx={{ height: "100%" }} alignItems="center">
|
||||||
|
<Grid size = {{xs:12, md:6}}>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
justifyContent: "center",
|
||||||
|
alignItems: "center",
|
||||||
|
height: "100%",
|
||||||
|
bgcolor: "#f5f5f5",
|
||||||
|
p: 5,
|
||||||
|
borderRadius: 5,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Image
|
||||||
|
src="/img/logo.png"
|
||||||
|
alt="Rhiscom Logo"
|
||||||
|
width={600}
|
||||||
|
height={400}
|
||||||
|
priority={true}
|
||||||
|
style={{ marginBottom: 15 }}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</Grid>
|
||||||
|
<Grid size = {{xs:12, md:6}}>
|
||||||
|
<React.Suspense fallback={<CustomLoading />}>
|
||||||
|
<ClientSearch />
|
||||||
|
</React.Suspense>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default VerifyAffiliation;
|
||||||
|
|
@ -1,24 +1,30 @@
|
||||||
"use client"
|
"use client";
|
||||||
import { Button as MUIButton } from "@mui/material"
|
import { Button as MUIButton } from "@mui/material";
|
||||||
import React from "react"
|
import React, { forwardRef } from "react";
|
||||||
|
|
||||||
interface ButtonProps {
|
interface ButtonProps {
|
||||||
label: string
|
label: string;
|
||||||
variant?: "primary" | "secondary" | "success" | "danger" | "warning" | "info"
|
type?: "button" | "submit" | "reset";
|
||||||
onClick?: () => void
|
variant?: "primary" | "secondary" | "success" | "danger" | "warning" | "info";
|
||||||
disabled?: boolean
|
onClick?: () => void;
|
||||||
className?: string
|
disabled?: boolean;
|
||||||
startIcon?: React.ReactNode
|
className?: string;
|
||||||
|
startIcon?: React.ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Button: React.FC<ButtonProps> = ({
|
const Button = forwardRef<HTMLButtonElement, ButtonProps>(
|
||||||
|
(
|
||||||
|
{
|
||||||
label,
|
label,
|
||||||
|
type = "button",
|
||||||
variant = "primary",
|
variant = "primary",
|
||||||
onClick,
|
onClick,
|
||||||
disabled,
|
disabled,
|
||||||
className = "",
|
className = "",
|
||||||
startIcon,
|
startIcon,
|
||||||
}) => {
|
},
|
||||||
|
ref
|
||||||
|
) => {
|
||||||
const colorMap: Record<string, "primary" | "secondary" | "error" | "warning" | "info" | "success"> = {
|
const colorMap: Record<string, "primary" | "secondary" | "error" | "warning" | "info" | "success"> = {
|
||||||
primary: "primary",
|
primary: "primary",
|
||||||
secondary: "secondary",
|
secondary: "secondary",
|
||||||
|
|
@ -26,10 +32,12 @@ const Button: React.FC<ButtonProps> = ({
|
||||||
danger: "error",
|
danger: "error",
|
||||||
warning: "warning",
|
warning: "warning",
|
||||||
info: "info",
|
info: "info",
|
||||||
}
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MUIButton
|
<MUIButton
|
||||||
|
ref={ref}
|
||||||
|
type={type}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
color={colorMap[variant ?? "primary"]}
|
color={colorMap[variant ?? "primary"]}
|
||||||
|
|
@ -39,7 +47,10 @@ const Button: React.FC<ButtonProps> = ({
|
||||||
>
|
>
|
||||||
{label}
|
{label}
|
||||||
</MUIButton>
|
</MUIButton>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
);
|
||||||
|
|
||||||
export default Button
|
Button.displayName = "Button";
|
||||||
|
|
||||||
|
export default Button;
|
||||||
|
|
|
||||||
43
package/components/atoms/CustomLoading.tsx
Normal file
43
package/components/atoms/CustomLoading.tsx
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
'use client';
|
||||||
|
import { CircularProgress } from '@mui/material';
|
||||||
|
import * as RadixTooltip from '@radix-ui/react-tooltip';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
const CustomLoading: React.FC = () => {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col items-center justify-center h-full space-y-4">
|
||||||
|
<CircularProgress
|
||||||
|
size={60}
|
||||||
|
thickness={4}
|
||||||
|
className="text-blue-600 animate-spin"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<RadixTooltip.Provider>
|
||||||
|
<RadixTooltip.Root>
|
||||||
|
<RadixTooltip.Trigger asChild>
|
||||||
|
<div className="text-gray-600 font-semibold text-lg cursor-pointer">
|
||||||
|
Loading, please wait...
|
||||||
|
</div>
|
||||||
|
</RadixTooltip.Trigger>
|
||||||
|
<RadixTooltip.Portal>
|
||||||
|
<RadixTooltip.Content
|
||||||
|
side="top"
|
||||||
|
sideOffset={5}
|
||||||
|
className="bg-gray-800 text-white rounded-md p-2 shadow-md text-sm"
|
||||||
|
>
|
||||||
|
Fetching data from the server. This may take a moment.
|
||||||
|
<RadixTooltip.Arrow className="fill-gray-800" />
|
||||||
|
</RadixTooltip.Content>
|
||||||
|
</RadixTooltip.Portal>
|
||||||
|
</RadixTooltip.Root>
|
||||||
|
</RadixTooltip.Provider>
|
||||||
|
|
||||||
|
{/* Tailwind CSS animation for visual effect */}
|
||||||
|
<div className="relative mt-2">
|
||||||
|
<div className="size-16 border-4 border-dashed rounded-full animate-spin border-blue-500"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CustomLoading;
|
||||||
15
package/components/atoms/Icon.tsx
Normal file
15
package/components/atoms/Icon.tsx
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
"use client"
|
||||||
|
import * as MUIIcons from "@mui/icons-material"
|
||||||
|
import React from "react"
|
||||||
|
|
||||||
|
interface IconProps {
|
||||||
|
name: keyof typeof MUIIcons
|
||||||
|
className?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const Icon: React.FC<IconProps> = ({ name, className }) => {
|
||||||
|
const IconComponent = MUIIcons[name]
|
||||||
|
return <IconComponent className={className} />
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Icon
|
||||||
|
|
@ -4,20 +4,33 @@ import React from "react"
|
||||||
|
|
||||||
interface InputProps {
|
interface InputProps {
|
||||||
type: string
|
type: string
|
||||||
|
name?: string;
|
||||||
placeholder?: string
|
placeholder?: string
|
||||||
value: string
|
value: string
|
||||||
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void
|
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void
|
||||||
|
onKeyDown?: (e: React.KeyboardEvent<HTMLInputElement>) => void;
|
||||||
autocomplete?: string
|
autocomplete?: string
|
||||||
disabled?: boolean
|
disabled?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const Input: React.FC<InputProps> = ({ type, placeholder, value, onChange, autocomplete, disabled }) => {
|
const Input: React.FC<InputProps> = ({
|
||||||
|
type,
|
||||||
|
name,
|
||||||
|
placeholder,
|
||||||
|
value,
|
||||||
|
onChange,
|
||||||
|
onKeyDown,
|
||||||
|
autocomplete,
|
||||||
|
disabled
|
||||||
|
}) => {
|
||||||
return (
|
return (
|
||||||
<TextField
|
<TextField
|
||||||
type={type}
|
type={type}
|
||||||
|
name={name}
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
value={value}
|
value={value}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
|
onKeyDown={onKeyDown}
|
||||||
autoComplete={autocomplete}
|
autoComplete={autocomplete}
|
||||||
fullWidth
|
fullWidth
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
|
|
|
||||||
|
|
@ -7,15 +7,16 @@ interface LabelProps {
|
||||||
text: string
|
text: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const Label: React.FC<LabelProps> = ({ htmlFor, text }) => {
|
const Label = React.memo(({ htmlFor, text }: LabelProps) => {
|
||||||
return (
|
return (
|
||||||
<label htmlFor={htmlFor} className="mb-2 block">
|
<label htmlFor={htmlFor} className="mb-2 block">
|
||||||
<Typography variant="subtitle1">
|
<Typography
|
||||||
{text}
|
variant="subtitle1">{text}</Typography>
|
||||||
</Typography>
|
|
||||||
</label>
|
</label>
|
||||||
)
|
);
|
||||||
}
|
});
|
||||||
|
|
||||||
|
Label.displayName = "Label";
|
||||||
|
|
||||||
export default Label
|
export default Label
|
||||||
|
|
||||||
|
|
|
||||||
7
package/components/atoms/states/CustomerAtom.tsx
Normal file
7
package/components/atoms/states/CustomerAtom.tsx
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
import { atom } from 'jotai';
|
||||||
|
import { CustomerInterface } from '@interfaces/ResponseInterfaces/Customer/CustomerResponseInterface';
|
||||||
|
|
||||||
|
|
||||||
|
const CustomerAtom = atom<CustomerInterface | null>(null); // Estado inicial vacío
|
||||||
|
|
||||||
|
export default CustomerAtom;
|
||||||
6
package/components/atoms/states/UserAtom.tsx
Normal file
6
package/components/atoms/states/UserAtom.tsx
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
import { atom } from 'jotai';
|
||||||
|
import { UserResposeInterface } from '@interfaces/ResponseInterfaces/Auth/UserResponseInterface';
|
||||||
|
|
||||||
|
const UserAtom = atom<UserResposeInterface | null>(null);
|
||||||
|
|
||||||
|
export default UserAtom;
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
"use client"
|
"use client"
|
||||||
import { Box, Card, CardContent } from "@mui/material"
|
import { Box, Card, CardContent } from "@mui/material"
|
||||||
import Grid from "@mui/material/Grid2"
|
import Grid from "@mui/material/Grid2"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
import Button from "@atoms/Button"
|
import Button from "@atoms/Button"
|
||||||
import Label from "@atoms/Label"
|
import Label from "@atoms/Label"
|
||||||
import Title from "@atoms/Title"
|
import Title from "@atoms/Title"
|
||||||
|
|
@ -10,6 +11,9 @@ interface AskForAffiliationProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
const AskForAffiliation: React.FC<AskForAffiliationProps> = ({ onAffiliationSelect }) => {
|
const AskForAffiliation: React.FC<AskForAffiliationProps> = ({ onAffiliationSelect }) => {
|
||||||
|
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
const handleYesClick = () => {
|
const handleYesClick = () => {
|
||||||
onAffiliationSelect(true)
|
onAffiliationSelect(true)
|
||||||
}
|
}
|
||||||
|
|
@ -31,14 +35,14 @@ const AskForAffiliation: React.FC<AskForAffiliationProps> = ({ onAffiliationSele
|
||||||
p: 2,
|
p: 2,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Title text="Se requiere Información" size="h3" />
|
<Title text={t("lbl_information_required")} size="h3" />
|
||||||
<Label htmlFor="loyalty" text="¿Está registrado como afiliado?" />
|
<Label htmlFor="loyalty" text= {t("msj_are_you_registered_as_affiliate")} />
|
||||||
<Box sx={{ mt: 3, display: "flex", gap: 3 }}>
|
<Box sx={{ mt: 3, display: "flex", gap: 3 }}>
|
||||||
<Grid size={{ xs: 6 }} sx={{ minWidth: "200px" }}>
|
<Grid size={{ xs: 6 }} sx={{ minWidth: "200px" }}>
|
||||||
<Button label="Sí" onClick={handleYesClick} variant="primary" className="h-16 w-full" />
|
<Button label={t("lbl_yes")} onClick={handleYesClick} variant="primary" className="h-16 w-full" />
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid size={{ xs: 6 }} sx={{ minWidth: "200px" }}>
|
<Grid size={{ xs: 6 }} sx={{ minWidth: "200px" }}>
|
||||||
<Button label="No" onClick={handleNoClick} variant="primary" className="h-16 w-full" />
|
<Button label={t('lbl_no')} onClick={handleNoClick} variant="primary" className="h-16 w-full" />
|
||||||
</Grid>
|
</Grid>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
import { Box, Card, CardContent } from "@mui/material"
|
import { Box, Card, CardContent } from "@mui/material"
|
||||||
import Grid from "@mui/material/Grid2"
|
import Grid from "@mui/material/Grid2"
|
||||||
import React, { useEffect, useState } from "react"
|
import React, { useEffect, useState } from "react"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
import Button from "@atoms/Button"
|
import Button from "@atoms/Button"
|
||||||
import Input from "@atoms/Input"
|
import Input from "@atoms/Input"
|
||||||
import Title from "@atoms/Title"
|
import Title from "@atoms/Title"
|
||||||
|
|
@ -15,9 +16,11 @@ const IdentityDocumentNumber: React.FC<IdentityDocumentNumberProps> = ({ documen
|
||||||
const [inputValue, setInputValue] = useState<string>("")
|
const [inputValue, setInputValue] = useState<string>("")
|
||||||
const [keyboardMode, setKeyboardMode] = useState<"numeric" | "alpha">("numeric")
|
const [keyboardMode, setKeyboardMode] = useState<"numeric" | "alpha">("numeric")
|
||||||
const [isAlphaEnabled, setIsAlphaEnabled] = useState<boolean>(false)
|
const [isAlphaEnabled, setIsAlphaEnabled] = useState<boolean>(false)
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const alphaDocuments = ["PASAPORTE", "CURP", "RFC", "NIE", "RUT"] // Documentos que contienen letras
|
const alphaDocuments = ["PASAPORTE", "CURP", "RFC", "NIE", "RUT"]
|
||||||
setIsAlphaEnabled(alphaDocuments.includes(documentType.toUpperCase()))
|
setIsAlphaEnabled(alphaDocuments.includes(documentType.toUpperCase()))
|
||||||
setKeyboardMode(isAlphaEnabled ? "alpha" : "numeric")
|
setKeyboardMode(isAlphaEnabled ? "alpha" : "numeric")
|
||||||
}, [documentType, isAlphaEnabled])
|
}, [documentType, isAlphaEnabled])
|
||||||
|
|
@ -30,7 +33,6 @@ const IdentityDocumentNumber: React.FC<IdentityDocumentNumberProps> = ({ documen
|
||||||
setInputValue((prev) => prev.slice(0, -1))
|
setInputValue((prev) => prev.slice(0, -1))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Renderiza el teclado basado en el modo seleccionado
|
|
||||||
const renderKeyboard = () => {
|
const renderKeyboard = () => {
|
||||||
if (keyboardMode === "numeric") {
|
if (keyboardMode === "numeric") {
|
||||||
return (
|
return (
|
||||||
|
|
@ -69,33 +71,32 @@ const IdentityDocumentNumber: React.FC<IdentityDocumentNumberProps> = ({ documen
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{/* Título */}
|
{/* Título */}
|
||||||
<Title text={`Ingrese el número de ${documentType}`} size="h4" />
|
<Title text={`${t('msj_enter_number_of')} ${documentType}`} size="h4" />
|
||||||
|
|
||||||
{/* Input que muestra el valor actual */}
|
|
||||||
<Input
|
<Input
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Ingrese el número de documento"
|
placeholder= {t('msj_enter_document_number')}
|
||||||
value={inputValue}
|
value={inputValue}
|
||||||
onChange={(e) => setInputValue(e.target.value)}
|
onChange={(e) => setInputValue(e.target.value)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Grid container spacing={2} justifyContent="center" alignItems="center" sx={{ mt: 3 }}>
|
<Grid container spacing={2} justifyContent="center" alignItems="center" sx={{ mt: 3 }}>
|
||||||
<Grid size={{ xs: 4 }}>
|
<Grid size={{ xs: 4 }}>
|
||||||
<Button label="Borrar" onClick={handleDelete} variant="danger" className="h-16 w-full" />
|
<Button label= {t('lbl_delete')} onClick={handleDelete} variant="danger" className="h-16 w-full" />
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid size={{ xs: 4 }}>
|
<Grid size={{ xs: 4 }}>
|
||||||
<Button label="Cancelar" onClick={() => setInputValue("")} variant="danger" className="h-16 w-full" />
|
<Button label={t('lbl_cancel')} onClick={() => setInputValue("")} variant="danger" className="h-16 w-full" />
|
||||||
</Grid>
|
</Grid>
|
||||||
{renderKeyboard()}
|
{renderKeyboard()}
|
||||||
<Grid size={{ xs: 4 }}>
|
<Grid size={{ xs: 4 }}>
|
||||||
<Button label="Ingresar" onClick={() => onSubmit(inputValue)} variant="success" className="h-16 w-full" />
|
<Button label={t('lbl_enter')} onClick={() => onSubmit(inputValue)} variant="success" className="h-16 w-full" />
|
||||||
</Grid>
|
</Grid>
|
||||||
{isAlphaEnabled && (
|
{isAlphaEnabled && (
|
||||||
<Grid size={{ xs: 4 }}>
|
<Grid size={{ xs: 4 }}>
|
||||||
<Button
|
<Button
|
||||||
label={`Cambiar a ${keyboardMode === "numeric" ? "Alfabético" : "Numérico"}`}
|
label={`${t('msj_change_to')} ${keyboardMode === "numeric" ? t('lbl_alphabetical') : t('lbl_numeric')}`}
|
||||||
onClick={() => setKeyboardMode(keyboardMode === "numeric" ? "alpha" : "numeric")}
|
onClick={() => setKeyboardMode(keyboardMode === "numeric" ? "alpha" : "numeric")}
|
||||||
variant="secondary"
|
variant="info"
|
||||||
className="h-16 w-full"
|
className="h-16 w-full"
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
|
||||||
55
package/components/layouts/MainLayout.tsx
Normal file
55
package/components/layouts/MainLayout.tsx
Normal file
|
|
@ -0,0 +1,55 @@
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
import { Box, Container, Paper } from '@mui/material';
|
||||||
|
import Grid from '@mui/material/Grid2';
|
||||||
|
import React from 'react';
|
||||||
|
import ControlPanel from '@organisms/Checkout/ControlPanel';
|
||||||
|
import ProductTable from '@organisms/Checkout/ProductTable';
|
||||||
|
|
||||||
|
const products = [
|
||||||
|
{ id: 1, amount: 2, name: "Product X", unitValue: 2500, tax: 110, discount: 0, total: 5220 },
|
||||||
|
{ id: 2, amount: 1, name: "Product Y", unitValue: 3500, tax: 230, discount: 0, total: 3730 },
|
||||||
|
{ id: 3, amount: 3, name: "Product Z", unitValue: 1000, tax: 100, discount: 0, total: 3300 },
|
||||||
|
];
|
||||||
|
|
||||||
|
const MainLayout: React.FC = () => {
|
||||||
|
const handleVoidOrder = () => console.log("Void Order");
|
||||||
|
const handleVoidLast = () => console.log("Void Last");
|
||||||
|
const handleResume = () => console.log("Resume");
|
||||||
|
const handleSearchPLU = (plu: string) => console.log("Search PLU:", plu);
|
||||||
|
|
||||||
|
const subtotal = products.reduce((acc, product) => acc + product.unitValue * product.amount, 0);
|
||||||
|
const discount = products.reduce((acc, product) => acc + product.discount, 0);
|
||||||
|
const tax = products.reduce((acc, product) => acc + product.tax, 0);
|
||||||
|
const total = products.reduce((acc, product) => acc + product.total, 0);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box sx={{ display: 'flex', flexDirection: 'column', bgcolor: 'background.default', boxShadow: '4' }}>
|
||||||
|
<Container maxWidth={false} sx={{ flexGrow: 1, display: 'flex', py: 2 }}>
|
||||||
|
<Grid container spacing={2}>
|
||||||
|
<Grid size={{xs:12, md:8}}>
|
||||||
|
<Paper elevation={3} sx={{ height: '100%', p: 2}}>
|
||||||
|
<ProductTable products={products} />
|
||||||
|
</Paper>
|
||||||
|
</Grid>
|
||||||
|
<Grid size={{xs:12, md:4}}>
|
||||||
|
<Paper elevation={3} sx={{ height: '100%', p: 2 }}>
|
||||||
|
<ControlPanel
|
||||||
|
onVoidOrder={handleVoidOrder}
|
||||||
|
onVoidLast={handleVoidLast}
|
||||||
|
onResume={handleResume}
|
||||||
|
onSearchPLU={handleSearchPLU}
|
||||||
|
subtotal={subtotal}
|
||||||
|
discount={discount}
|
||||||
|
tax={tax}
|
||||||
|
total={total}
|
||||||
|
/>
|
||||||
|
</Paper>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</Container>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default MainLayout;
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
"use client"
|
"use client"
|
||||||
import { Box, Card, CardContent } from "@mui/material"
|
import { Box, Card, CardContent } from "@mui/material"
|
||||||
import Grid from "@mui/material/Grid2"
|
import Grid from "@mui/material/Grid2"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
import Button from "@atoms/Button"
|
import Button from "@atoms/Button"
|
||||||
import Label from "@atoms/Label"
|
import Label from "@atoms/Label"
|
||||||
import Title from "@atoms/Title"
|
import Title from "@atoms/Title"
|
||||||
|
|
@ -11,6 +12,8 @@ interface TypesDocumentProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
const TypeIdentityDocument: React.FC<TypesDocumentProps> = ({ typesDocument, onDocumentSelect }) => {
|
const TypeIdentityDocument: React.FC<TypesDocumentProps> = ({ typesDocument, onDocumentSelect }) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
const handleDocumentClick = (documentType: string) => {
|
const handleDocumentClick = (documentType: string) => {
|
||||||
onDocumentSelect(documentType)
|
onDocumentSelect(documentType)
|
||||||
}
|
}
|
||||||
|
|
@ -25,15 +28,15 @@ const TypeIdentityDocument: React.FC<TypesDocumentProps> = ({ typesDocument, onD
|
||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
height: "100%",
|
height: "100%",
|
||||||
p: 2,
|
p: 1,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Title text="Se requiere Información" size="h4" />
|
<Title text= {t("lbl_information_required")} size="h4" />
|
||||||
<Label htmlFor="loyalty" text="Seleccione el tipo de documento de identidad" />
|
<Label htmlFor="loyalty" text={t('msj_select_type_identity_document')} />
|
||||||
|
|
||||||
<Grid container spacing={2} justifyContent="center" sx={{ mt: 3 }}>
|
<Grid container spacing={1} justifyContent="center" sx={{ mt: 2 }}>
|
||||||
{typesDocument.map((documentType, index) => (
|
{typesDocument.map((documentType, index) => (
|
||||||
<Grid key={index} size={{ xs: 6, md: 4 }} sx={{ minWidth: "200px" }}>
|
<Grid key={index} size={{ xs: 4, md: 6 }} sx={{ minWidth: "100px" }}>
|
||||||
<Button
|
<Button
|
||||||
label={documentType}
|
label={documentType}
|
||||||
onClick={() => handleDocumentClick(documentType)}
|
onClick={() => handleDocumentClick(documentType)}
|
||||||
|
|
|
||||||
63
package/components/molecules/ActionButtons.tsx
Normal file
63
package/components/molecules/ActionButtons.tsx
Normal file
|
|
@ -0,0 +1,63 @@
|
||||||
|
"use client";
|
||||||
|
import { Box } from '@mui/material';
|
||||||
|
import Grid from '@mui/material/Grid2';
|
||||||
|
import React from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import Button from '@atoms/Button';
|
||||||
|
|
||||||
|
interface ActionButtonsProps {
|
||||||
|
onVoidOrder: () => void;
|
||||||
|
onVoidLast: () => void;
|
||||||
|
onResume: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ActionButtons: React.FC<ActionButtonsProps> = ({ onVoidOrder, onVoidLast, onResume }) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
mb: 1,
|
||||||
|
p: 1,
|
||||||
|
boxShadow: 4,
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Grid
|
||||||
|
container
|
||||||
|
spacing={2}
|
||||||
|
justifyContent="center"
|
||||||
|
alignItems="center"
|
||||||
|
>
|
||||||
|
<Grid size={{xs:4}}>
|
||||||
|
<Button
|
||||||
|
label={t("lbl_void_order")}
|
||||||
|
variant="danger"
|
||||||
|
onClick={onVoidOrder}
|
||||||
|
className="full-width-button"
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid size={{xs:4}}>
|
||||||
|
<Button
|
||||||
|
label={t("lbl_void_last")}
|
||||||
|
variant="danger"
|
||||||
|
onClick={onVoidLast}
|
||||||
|
className="full-width-button"
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid size={{xs:4}}>
|
||||||
|
<Button
|
||||||
|
label={t("lbl_resume")}
|
||||||
|
variant="primary"
|
||||||
|
onClick={onResume}
|
||||||
|
className="full-width-button"
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ActionButtons;
|
||||||
20
package/components/molecules/CustomCard.tsx
Normal file
20
package/components/molecules/CustomCard.tsx
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
"use client"
|
||||||
|
import { CardContent, CardHeader, Card as MUICard } from "@mui/material"
|
||||||
|
import React from "react"
|
||||||
|
|
||||||
|
interface CardProps {
|
||||||
|
title?: string
|
||||||
|
children: React.ReactNode
|
||||||
|
className?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const CustomCard: React.FC<CardProps> = ({ title, children, className }) => {
|
||||||
|
return (
|
||||||
|
<MUICard className={className}>
|
||||||
|
{title && <CardHeader title={title} />}
|
||||||
|
<CardContent>{children}</CardContent>
|
||||||
|
</MUICard>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CustomCard
|
||||||
|
|
@ -1,20 +1,24 @@
|
||||||
"use client"
|
"use client";
|
||||||
import Grid2 from "@mui/material/Grid2"
|
import Grid2 from "@mui/material/Grid2";
|
||||||
import React from "react"
|
import React from "react";
|
||||||
import Button from "@atoms/Button"
|
import Button from "@atoms/Button";
|
||||||
|
|
||||||
interface NumericKeyProps {
|
interface NumericKeyProps {
|
||||||
label: string | number
|
label: string | number;
|
||||||
onClick: () => void
|
onClick: () => void;
|
||||||
className?: string
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const NumericKey: React.FC<NumericKeyProps> = ({ label, onClick, className }) => {
|
const NumericKey: React.FC<NumericKeyProps> = ({ label, onClick, className }) => {
|
||||||
return (
|
return (
|
||||||
<Grid2 container justifyContent="center" component="div">
|
<Grid2 container justifyContent="center" component="div">
|
||||||
<Button label={label.toString()} onClick={onClick} className={`h-16 w-full ${className}`} />
|
<Button
|
||||||
|
label={label.toString()}
|
||||||
|
onClick={onClick}
|
||||||
|
className={`h-16 w-full text-3xl font-bold ${className}`}
|
||||||
|
/>
|
||||||
</Grid2>
|
</Grid2>
|
||||||
)
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
export default NumericKey
|
export default NumericKey;
|
||||||
|
|
|
||||||
|
|
@ -1,69 +1,57 @@
|
||||||
"use client"
|
"use client";
|
||||||
import Grid from "@mui/material/Grid2"
|
import Grid from "@mui/material/Grid2";
|
||||||
import React from "react"
|
import React from "react";
|
||||||
import NumericKey from "@molecules/NumericKey"
|
import { useTranslation } from "react-i18next";
|
||||||
|
import NumericKey from "@molecules/NumericKey";
|
||||||
|
|
||||||
const NumericPad: React.FC = () => {
|
interface NumericPadProps {
|
||||||
const handleKeyClick = (key: number) => {
|
onKeyClick: (key: string) => void;
|
||||||
console.log(`Key pressed: ${key}`)
|
}
|
||||||
}
|
|
||||||
|
const NumericPad: React.FC<NumericPadProps> = ({ onKeyClick }) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Grid container spacing={0.4} justifyContent="center" alignItems="center">
|
<Grid container spacing={0.4} justifyContent="center" alignItems="center">
|
||||||
<Grid size={{ xs: 6 }}>
|
<Grid size={{ xs: 6 }}>
|
||||||
<NumericKey
|
<NumericKey
|
||||||
label="Cancel"
|
label={t("lbl_cancel")}
|
||||||
onClick={() => console.log("Cancel pressed")}
|
onClick={() => onKeyClick("cancel")}
|
||||||
className="h-12 w-full bg-danger-light text-white hover:bg-danger-dark"
|
className="h-12 w-full bg-danger-light text-white hover:bg-danger-dark"
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid size={{ xs: 6 }}>
|
<Grid size={{ xs: 6 }}>
|
||||||
<NumericKey label="clear" onClick={() => console.log("Clear pressed")} className="size-12 bg-secondary-light" />
|
|
||||||
</Grid>
|
|
||||||
<Grid size={{ xs: 3 }} sx={{ minWidth: "100px" }}>
|
|
||||||
<NumericKey
|
<NumericKey
|
||||||
label="0"
|
label={t("lbl_clear")}
|
||||||
onClick={() => console.log("0")}
|
onClick={() => onKeyClick("clear")}
|
||||||
className="size-24 bg-primary-500 text-white hover:bg-primary-600"
|
className="h-12 bg-secondary-light text-black"
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
{[...Array(9).keys()].map((i) => (
|
{[...Array(10).keys()].map((i) => (
|
||||||
<Grid key={i + 1} size={{ xs: 3 }} sx={{ minWidth: "100px" }}>
|
<Grid key={i} size={{ xs: 4 }} sx={{ minWidth: "100px" }}>
|
||||||
{" "}
|
|
||||||
<NumericKey
|
<NumericKey
|
||||||
label={i + 1}
|
label={i}
|
||||||
onClick={() => handleKeyClick(i + 1)}
|
onClick={() => onKeyClick(i.toString())}
|
||||||
className="size-24 bg-primary-500 text-white hover:bg-primary-600"
|
className="h-16 bg-primary-500 text-white hover:bg-primary-600"
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
))}
|
))}
|
||||||
<Grid size={{ xs: 3 }} sx={{ minWidth: "100px" }}>
|
<Grid size={{ xs: 4 }}>
|
||||||
<NumericKey
|
<NumericKey
|
||||||
label="."
|
label="."
|
||||||
onClick={() => console.log(".")}
|
onClick={() => onKeyClick(".")}
|
||||||
className="size-24 bg-primary-500 text-white hover:bg-primary-600"
|
className="h-16 bg-primary-500 text-white hover:bg-primary-600"
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid size={{ xs: 3 }} sx={{ minWidth: "100px" }}>
|
<Grid size={{ xs: 4 }}>
|
||||||
<NumericKey label="/" onClick={() => console.log("Divide pressed")} className="size-12 bg-secondary-light" />
|
|
||||||
</Grid>
|
|
||||||
<Grid size={{ xs: 3 }} sx={{ minWidth: "100px" }}>
|
|
||||||
<NumericKey label="QTY" onClick={() => console.log("QTY pressed")} className="size-12 bg-secondary-light" />
|
|
||||||
</Grid>
|
|
||||||
<Grid size={{ xs: 3 }} sx={{ minWidth: "100px" }}>
|
|
||||||
<NumericKey label="Price" onClick={() => console.log("Price pressed")} className="size-12 bg-secondary-light" />
|
|
||||||
</Grid>
|
|
||||||
|
|
||||||
|
|
||||||
<Grid size={{ xs: 6 }}>
|
|
||||||
<NumericKey
|
<NumericKey
|
||||||
label="Enter"
|
label={t("lbl_enter")}
|
||||||
onClick={() => console.log("Enter pressed")}
|
onClick={() => onKeyClick("enter")}
|
||||||
className="h-12 w-full bg-success-light text-white hover:bg-success-dark"
|
className="h-12 w-full bg-success-light text-white hover:bg-success-dark"
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
)
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
export default NumericPad
|
export default NumericPad;
|
||||||
|
|
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
"use client"
|
|
||||||
import { Box } from "@mui/material"
|
|
||||||
import React, { useState } from "react"
|
|
||||||
import { useTranslation } from "react-i18next"
|
|
||||||
import Input from "@atoms/Input"
|
|
||||||
|
|
||||||
const PluEntry: React.FC = () => {
|
|
||||||
const [plu, setPlu] = useState("")
|
|
||||||
const { t } = useTranslation()
|
|
||||||
|
|
||||||
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
||||||
setPlu(e.target.value)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Box sx={{ p: 1, backgroundColor: "#f5f5f5", borderRadius: 2 }}>
|
|
||||||
<Input
|
|
||||||
type="text"
|
|
||||||
value={plu}
|
|
||||||
onChange={handleInputChange}
|
|
||||||
placeholder={t("lbl_enter_PLU")}
|
|
||||||
autocomplete="off"
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default PluEntry
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
"use client"
|
|
||||||
import { Box, Typography } from "@mui/material"
|
|
||||||
import React from "react"
|
|
||||||
|
|
||||||
const ProductMatch: React.FC = () => {
|
|
||||||
return (
|
|
||||||
<Box sx={{ p: 2, backgroundColor: "#f5f5f5", borderRadius: 1 }}>
|
|
||||||
<Typography variant="h6">Coincidencia de productos:</Typography> {/* TODO: Aquí irán los productos que coincidan con el PLU */}
|
|
||||||
</Box>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default ProductMatch
|
|
||||||
28
package/components/molecules/ProductRow.tsx
Normal file
28
package/components/molecules/ProductRow.tsx
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
import { TableCell, TableRow } from '@mui/material';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
interface ProductRowProps {
|
||||||
|
amount: number;
|
||||||
|
name: string;
|
||||||
|
unitValue: number;
|
||||||
|
tax: number;
|
||||||
|
discount: number;
|
||||||
|
total: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ProductRow: React.FC<ProductRowProps> = ({ amount, name, unitValue, tax, discount, total }) => {
|
||||||
|
return (
|
||||||
|
<TableRow>
|
||||||
|
<TableCell>{amount}</TableCell>
|
||||||
|
<TableCell>{name}</TableCell>
|
||||||
|
<TableCell>${unitValue.toFixed(2)}</TableCell>
|
||||||
|
<TableCell>${tax.toFixed(2)}</TableCell>
|
||||||
|
<TableCell>${discount.toFixed(2)}</TableCell>
|
||||||
|
<TableCell>${total.toFixed(2)}</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ProductRow;
|
||||||
50
package/components/molecules/ProductSearch.tsx
Normal file
50
package/components/molecules/ProductSearch.tsx
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
// components/molecules/ProductSearch.tsx
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
import { Box } from '@mui/material';
|
||||||
|
import Grid from '@mui/material/Grid2';
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import Button from '@atoms/Button';
|
||||||
|
import Input from '@atoms/Input';
|
||||||
|
|
||||||
|
interface ProductSearchProps {
|
||||||
|
onSearch: (plu: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ProductSearch: React.FC<ProductSearchProps> = ({ onSearch }) => {
|
||||||
|
const [plu, setPlu] = useState('');
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
|
|
||||||
|
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
setPlu(e.target.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleKeyDown = (e: React.KeyboardEvent) => {
|
||||||
|
if (e.key === 'Enter') {
|
||||||
|
onSearch(plu);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box sx={{ p:1, mb: 1, boxShadow: '4' }}>
|
||||||
|
<Grid container spacing={3} alignItems="center">
|
||||||
|
<Grid size={{xs:8}}>
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
value={plu}
|
||||||
|
placeholder={t("msj_enter_plu")}
|
||||||
|
onChange={handleInputChange}
|
||||||
|
onKeyDown={handleKeyDown}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid size={{xs:4}}>
|
||||||
|
<Button label={t("lbl_search")} variant="primary" onClick={() => onSearch(plu)} />
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ProductSearch;
|
||||||
92
package/components/molecules/SaleSummary.tsx
Normal file
92
package/components/molecules/SaleSummary.tsx
Normal file
|
|
@ -0,0 +1,92 @@
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
import { Box, Table, TableBody, TableCell, TableHead, TableRow, Typography } from '@mui/material';
|
||||||
|
import Grid from '@mui/material/Grid2';
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
interface SaleSummaryProps {
|
||||||
|
subtotal: number;
|
||||||
|
discount: number;
|
||||||
|
tax: number;
|
||||||
|
total: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SaleSummary: React.FC<SaleSummaryProps> = ({ subtotal, discount, tax, total }) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box sx={{
|
||||||
|
|
||||||
|
color: 'black',
|
||||||
|
borderRadius: '5px',
|
||||||
|
boxShadow: '2',
|
||||||
|
p: 1,
|
||||||
|
mb: 1
|
||||||
|
}}>
|
||||||
|
<Box sx={{mb: 1, p: 1, boxShadow: '2', }}>
|
||||||
|
<Table size="small">
|
||||||
|
<TableHead>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell sx={{ color: 'primary', borderColor: 'black', fontWeight: 'bold', width: '30%' }}>{t("tle_product")}</TableCell>
|
||||||
|
<TableCell sx={{ color: 'primary', borderColor: 'black', fontWeight: 'bold', width: '50%' }}>{t("tle_description")}</TableCell>
|
||||||
|
<TableCell sx={{ color: 'primary', borderColor: 'black', fontWeight: 'bold', width: '20%', textAlign: 'right' }}>{t("tle_amount")}</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableHead>
|
||||||
|
<TableBody>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell sx={{ color: 'black', borderColor: 'black' }}>Product R</TableCell>
|
||||||
|
<TableCell sx={{ color: 'black', borderColor: 'black' }}>Producto para r uso</TableCell>
|
||||||
|
<TableCell sx={{ color: 'black', borderColor: 'black', textAlign: 'right' }}>3</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
<Grid container justifyContent='right'>
|
||||||
|
<Box sx={{ mt: 5, textAlign: 'right' }}>
|
||||||
|
<Grid container>
|
||||||
|
<Grid size={{xs:7}} sx={{ pr: 10 }}>
|
||||||
|
<Typography variant="body2" align="right">{t("lbl_subtotal")}:</Typography>
|
||||||
|
</Grid>
|
||||||
|
<Grid size={{xs:5}}>
|
||||||
|
<Typography variant="body2" align="left">$ 1.400</Typography>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
<Grid container>
|
||||||
|
<Grid size={{xs:7}} sx={{ pr: 10 }}>
|
||||||
|
<Typography variant="body2" align="right">{t("lbl_discount")}:</Typography>
|
||||||
|
</Grid>
|
||||||
|
<Grid size={{xs:5}}>
|
||||||
|
<Typography variant="body2" align="left">$ 12</Typography>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
<Grid container>
|
||||||
|
<Grid size={{xs:7}} sx={{ pr: 5.5 }}>
|
||||||
|
<Typography variant="body2" align="right">{t("lbl_tax")}:</Typography>
|
||||||
|
</Grid>
|
||||||
|
<Grid size={{xs:5}}>
|
||||||
|
<Typography variant="body2" align="left">$ 123</Typography>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
<Grid container>
|
||||||
|
<Grid size={{xs:7}} sx={{ pr: 5.5 }}>
|
||||||
|
<Typography variant="body2" align="right">{t("lbl_total")}:</Typography>
|
||||||
|
</Grid>
|
||||||
|
<Grid size={{xs:5}}>
|
||||||
|
<Typography variant="body2" align="left">$ 1.511</Typography>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</Box>
|
||||||
|
</Grid>
|
||||||
|
</Box>
|
||||||
|
<Box sx={{ p: 3, boxShadow: '2' }}>
|
||||||
|
<Typography variant="h6" sx={{ color: 'primary', display: 'flex', justifyContent: 'space-between' }}>
|
||||||
|
<span>{t("tle_total")}:</span>
|
||||||
|
<span>$ 12.250</span>
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SaleSummary;
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
"use client"
|
|
||||||
import { Box, Typography } from "@mui/material"
|
|
||||||
import React from "react"
|
|
||||||
|
|
||||||
const Screen: React.FC = () => {
|
|
||||||
return (
|
|
||||||
<Box sx={{ p: 2, backgroundColor: "#f5f5f5", borderRadius: 1, textAlign: "center" }}>{/* TODO: Logica para valores */}
|
|
||||||
<Typography variant="h6">Item Subtotal: $0.00</Typography>
|
|
||||||
<Typography variant="body1">Total Savings: $0.00</Typography>
|
|
||||||
<Typography variant="body1">Tax: $0.00</Typography>
|
|
||||||
</Box>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Screen
|
|
||||||
46
package/components/molecules/UserHeader.tsx
Normal file
46
package/components/molecules/UserHeader.tsx
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
import { Box } from '@mui/material';
|
||||||
|
import Grid2 from '@mui/material/Grid2';
|
||||||
|
import Typography from '@mui/material/Typography';
|
||||||
|
import { useAtom } from 'jotai';
|
||||||
|
import React from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import UserAtom from '@atoms/states/UserAtom';
|
||||||
|
|
||||||
|
const UserHeader: React.FC = () => {
|
||||||
|
const [user] = useAtom(UserAtom);
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box sx={{mb: 1, boxShadow: '4', }}>
|
||||||
|
<Grid2
|
||||||
|
container
|
||||||
|
justifyContent="space-between"
|
||||||
|
alignItems="center"
|
||||||
|
className="bg-blue-600 text-white p-4"
|
||||||
|
>
|
||||||
|
<Grid2>
|
||||||
|
<Typography variant="h6" className="font-bold">
|
||||||
|
{user ? `${t("lbl_name_customer")}: ${user.name} ${user.lastName}` : `${t("lbl_name_customer")}: Anónimo`}
|
||||||
|
</Typography>
|
||||||
|
</Grid2>
|
||||||
|
|
||||||
|
<Grid2>
|
||||||
|
<Typography variant="body2" color="textSecondary" className="text-white">
|
||||||
|
{user ? `${t("lbl_dni_customer")}: ${user.idTypeUser}` : `${t("lbl_dni_customer")}: 222222222222`}
|
||||||
|
</Typography>
|
||||||
|
</Grid2>
|
||||||
|
|
||||||
|
<Grid2>
|
||||||
|
<Typography variant="body2" className="text-white">
|
||||||
|
{user ? `${t("lbl_email_customer")}: ${user.email}` : `${t("lbl_email_customer")}: generic@example.com`}
|
||||||
|
</Typography>
|
||||||
|
</Grid2>
|
||||||
|
</Grid2>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default UserHeader;
|
||||||
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>
|
</Tooltip>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid size={{ xs: 4 }}>
|
<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%" }}>
|
<Box sx={{ width: "100%" }}>
|
||||||
<Button
|
<Button
|
||||||
label={t("msj_bag_fee")}
|
label= {t("lbl_bag_fee")}
|
||||||
onClick={() => {}}
|
onClick={() => {}}
|
||||||
variant="primary"
|
variant="primary"
|
||||||
className="full-size-button"
|
className="full-size-button"
|
||||||
startIcon={<ShoppingBagIcon />}
|
startIcon={<ShoppingBagIcon />}
|
||||||
|
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
</Tooltip>
|
</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";
|
"use client";
|
||||||
import LanguageIcon from '@mui/icons-material/Language';
|
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 Grid from '@mui/material/Grid2';
|
||||||
import IconButton from '@mui/material/IconButton';
|
import IconButton from '@mui/material/IconButton';
|
||||||
import Menu from '@mui/material/Menu';
|
import Menu from '@mui/material/Menu';
|
||||||
import MenuItem from '@mui/material/MenuItem';
|
import MenuItem from '@mui/material/MenuItem';
|
||||||
|
import { useAtom } from 'jotai';
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import UserAtom from '@atoms/states/UserAtom';
|
||||||
import i18n from '@package/utils/language/i18n';
|
import i18n from '@package/utils/language/i18n';
|
||||||
|
|
||||||
const Navbar: React.FC = () => {
|
const Navbar: React.FC = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [language, setLanguage] = useState(i18n.language || 'en');
|
const [language, setLanguage] = useState(i18n.language || 'en');
|
||||||
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
|
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
|
||||||
|
const [user] = useAtom(UserAtom);
|
||||||
|
const [currentDateTime, setCurrentDateTime] = useState<Date | null>(null);
|
||||||
|
|
||||||
const changeLanguage = (lng: string) => {
|
const changeLanguage = (lng: string) => {
|
||||||
i18n.changeLanguage(lng);
|
i18n.changeLanguage(lng);
|
||||||
|
|
@ -29,8 +35,29 @@ const Navbar: React.FC = () => {
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setLanguage(i18n.language);
|
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 (
|
return (
|
||||||
<Grid
|
<Grid
|
||||||
container
|
container
|
||||||
|
|
@ -39,10 +66,21 @@ const Navbar: React.FC = () => {
|
||||||
className="bg-blue-600 text-white p-4"
|
className="bg-blue-600 text-white p-4"
|
||||||
>
|
>
|
||||||
<Grid>
|
<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>
|
||||||
|
|
||||||
<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
|
<IconButton
|
||||||
aria-controls="language-menu"
|
aria-controls="language-menu"
|
||||||
aria-haspopup="true"
|
aria-haspopup="true"
|
||||||
|
|
@ -60,6 +98,17 @@ const Navbar: React.FC = () => {
|
||||||
<MenuItem onClick={() => changeLanguage('en')}>English</MenuItem>
|
<MenuItem onClick={() => changeLanguage('en')}>English</MenuItem>
|
||||||
<MenuItem onClick={() => changeLanguage('es')}>Español</MenuItem>
|
<MenuItem onClick={() => changeLanguage('es')}>Español</MenuItem>
|
||||||
</Menu>
|
</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>
|
||||||
</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
|
|
||||||
96
package/hooks/useClientSearchLogic.tsx
Normal file
96
package/hooks/useClientSearchLogic.tsx
Normal file
|
|
@ -0,0 +1,96 @@
|
||||||
|
import { useSetAtom } from 'jotai';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import CustomerAtom from '@atoms/states/CustomerAtom';
|
||||||
|
import { CustomerInterface, CustomerResponseInterface } from '@interfaces/ResponseInterfaces/Customer/CustomerResponseInterface';
|
||||||
|
import { CustomerDniTypeInterface, CustomerDniTypeResponseInterface } from '@interfaces/ResponseInterfaces/Customer/DNITypeResponseInterface';
|
||||||
|
import { GetCustomer } from '@package/config/api/Customer/Customer/GetCustomer/page';
|
||||||
|
import { GetTypeDni } from '@package/config/api/Customer/TypeDNI/GetTypeDNI/page';
|
||||||
|
|
||||||
|
export const useClientSearchLogic = () => {
|
||||||
|
const [idTypes, setIdTypes] = useState<CustomerDniTypeInterface[]>([]);
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [isAffiliated, setIsAffiliated] = useState<boolean>(true);
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
const [result, setResult] = useState<CustomerInterface[]>([]);
|
||||||
|
const [formData, setFormData] = useState({
|
||||||
|
idType: '',
|
||||||
|
idNumber: ''
|
||||||
|
});
|
||||||
|
|
||||||
|
const setCustomer = useSetAtom(CustomerAtom);
|
||||||
|
|
||||||
|
const fetchIdTypes = async () => {
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
const response = await GetTypeDni({
|
||||||
|
size: 100,
|
||||||
|
page: 0,
|
||||||
|
}) as CustomerDniTypeResponseInterface;
|
||||||
|
if (response?.object.list) {
|
||||||
|
setIdTypes(response.object.list);
|
||||||
|
} else {
|
||||||
|
setError('No se pudieron cargar los tipos de identificación');
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
setError('Error al obtener los tipos de identificación');
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchIdTypes();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement>) => {
|
||||||
|
setFormData({
|
||||||
|
...formData,
|
||||||
|
[e.target.name]: e.target.value
|
||||||
|
});
|
||||||
|
setResult([]);
|
||||||
|
setIsAffiliated(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmitForm = async (e?: React.FormEvent) => {
|
||||||
|
if (e) e.preventDefault();
|
||||||
|
setLoading(true);
|
||||||
|
setError(null);
|
||||||
|
setResult([]);
|
||||||
|
|
||||||
|
const response = await GetCustomer({
|
||||||
|
idTypeCustomer: formData.idType,
|
||||||
|
dni: formData.idNumber
|
||||||
|
}) as CustomerResponseInterface;
|
||||||
|
|
||||||
|
setLoading(false);
|
||||||
|
|
||||||
|
if (response.correct && response.object.list.length > 0) {
|
||||||
|
const customerData = response.object.list[0];
|
||||||
|
|
||||||
|
if (customerData) {
|
||||||
|
setIsAffiliated(true);
|
||||||
|
setCustomer({
|
||||||
|
...customerData,
|
||||||
|
idTypeCustomer: formData.idType,
|
||||||
|
dni: formData.idNumber,
|
||||||
|
id: customerData.id || '',
|
||||||
|
});
|
||||||
|
setResult(response.object.list);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setIsAffiliated(false);
|
||||||
|
setCustomer(null);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
idTypes,
|
||||||
|
loading,
|
||||||
|
error,
|
||||||
|
result,
|
||||||
|
formData,
|
||||||
|
handleInputChange,
|
||||||
|
handleSubmitForm,
|
||||||
|
isAffiliated
|
||||||
|
};
|
||||||
|
};
|
||||||
Loading…
Add table
Add a link
Reference in a new issue