feat(F-163): completed feature
This commit is contained in:
4
project/apps/pos/next-env.d.ts
vendored
4
project/apps/pos/next-env.d.ts
vendored
@@ -1,7 +1,7 @@
|
||||
/// <reference types="next" />
|
||||
/// <reference types="next/image-types/global" />
|
||||
import "./.next/dev/types/routes.d.ts";
|
||||
import "./.next/dev/types/root-params.d.ts";
|
||||
import "./.next/types/routes.d.ts";
|
||||
import "./.next/types/root-params.d.ts";
|
||||
|
||||
// NOTE: This file should not be edited
|
||||
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
|
||||
|
||||
@@ -14,7 +14,7 @@ export default function LoginPage() {
|
||||
setLoading(true);
|
||||
setError('');
|
||||
try {
|
||||
const res = await fetch('/api/auth/login', {
|
||||
const res = await fetch('/api/backoffice/auth/login', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ email, password }),
|
||||
|
||||
@@ -43,6 +43,9 @@ export default function RegisterPage() {
|
||||
const [needsBinding, setNeedsBinding] = useState(false);
|
||||
const [bindingCode, setBindingCode] = useState('');
|
||||
const [binding, setBinding] = useState(false);
|
||||
const [openingCash, setOpeningCash] = useState('0');
|
||||
const [openingSession, setOpeningSession] = useState(false);
|
||||
const [sessionError, setSessionError] = useState('');
|
||||
const [cart, setCart] = useState<CartItem[]>([]);
|
||||
const [search, setSearch] = useState('');
|
||||
const [searchResults, setSearchResults] = useState<SearchResult[]>([]);
|
||||
@@ -89,6 +92,25 @@ export default function RegisterPage() {
|
||||
}
|
||||
};
|
||||
|
||||
const openCashSession = async (event: React.FormEvent) => {
|
||||
event.preventDefault();
|
||||
const amount = Number(openingCash.replace(',', '.'));
|
||||
if (!Number.isFinite(amount) || amount < 0) {
|
||||
setSessionError('Introduce un importe válido');
|
||||
return;
|
||||
}
|
||||
setOpeningSession(true);
|
||||
setSessionError('');
|
||||
try {
|
||||
await posApi.openSession(Math.round(amount * 100));
|
||||
await loadConfig();
|
||||
} catch (err) {
|
||||
setSessionError(err instanceof Error ? err.message : 'No se pudo abrir la caja');
|
||||
} finally {
|
||||
setOpeningSession(false);
|
||||
}
|
||||
};
|
||||
|
||||
const doSearch = useCallback(async (q: string) => {
|
||||
if (q.trim().length < 2) { setSearchResults([]); return; }
|
||||
setSearching(true);
|
||||
@@ -231,9 +253,32 @@ export default function RegisterPage() {
|
||||
|
||||
if (config.session?.status !== 'OPEN') {
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center min-h-screen text-center p-8">
|
||||
<h1 className="text-4xl font-bold mb-4" style={{ color: 'var(--color-primary)' }}>Sesión cerrada</h1>
|
||||
<p className="text-gray-500 mb-6">Abre una caja desde la administración para comenzar.</p>
|
||||
<div className="flex min-h-screen items-center justify-center bg-gray-100 p-6">
|
||||
<form onSubmit={openCashSession} className="w-full max-w-md space-y-5 rounded-2xl bg-white p-8 shadow-lg">
|
||||
<div>
|
||||
<p className="text-sm font-medium text-[#2D6A4F]">{config.store.name} · {config.terminal.name}</p>
|
||||
<h1 className="mt-1 text-3xl font-bold text-gray-900">Abrir sesión de caja</h1>
|
||||
<p className="mt-2 text-sm text-gray-500">Indica el efectivo disponible al comenzar el turno. Puedes usar 0 si la caja empieza vacía.</p>
|
||||
</div>
|
||||
<label className="block text-sm font-medium text-gray-700">
|
||||
Efectivo inicial (€)
|
||||
<input
|
||||
type="number"
|
||||
min="0"
|
||||
step="0.01"
|
||||
value={openingCash}
|
||||
onChange={(event) => setOpeningCash(event.target.value)}
|
||||
className="mt-1 w-full rounded-xl border border-gray-300 px-4 py-3 text-xl outline-none focus:ring-2 focus:ring-[#2D6A4F]"
|
||||
required
|
||||
autoFocus
|
||||
/>
|
||||
</label>
|
||||
{sessionError && <p className="text-sm text-red-600">{sessionError}</p>}
|
||||
<button type="submit" disabled={openingSession} className="w-full rounded-xl bg-[#2D6A4F] py-3 font-semibold text-white hover:bg-[#1B4332] disabled:opacity-50">
|
||||
{openingSession ? 'Abriendo…' : 'Abrir caja y comenzar'}
|
||||
</button>
|
||||
<p className="text-xs text-gray-400">Una sesión de caja representa el turno diario y registra ventas, efectivo esperado y cierre.</p>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -35,6 +35,12 @@ export const posApi = {
|
||||
}),
|
||||
/** Get POS config (store + terminal + payment methods + session status). */
|
||||
config: <T>() => apiFetch<T>('/pos/config'),
|
||||
/** Open the daily cash session for the bound terminal. */
|
||||
openSession: (openingCashCents: number) =>
|
||||
apiFetch<{ id: string; status: 'OPEN' }>('/pos/sessions', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ openingCashCents }),
|
||||
}),
|
||||
/** List products by query. */
|
||||
searchProducts: (q: string, storeId?: string, limit = 20) =>
|
||||
apiFetch(`/pos/products/search?q=${encodeURIComponent(q)}&storeId=${storeId ?? ''}&limit=${limit}`),
|
||||
@@ -46,6 +52,6 @@ export const posApi = {
|
||||
|
||||
export const authApi = {
|
||||
login: (email: string, password: string) =>
|
||||
apiFetch('/auth/login', { method: 'POST', body: JSON.stringify({ email, password }) }),
|
||||
logout: () => apiFetch('/auth/logout', { method: 'POST' }),
|
||||
apiFetch('/backoffice/auth/login', { method: 'POST', body: JSON.stringify({ email, password }) }),
|
||||
logout: () => apiFetch('/backoffice/auth/logout', { method: 'POST' }),
|
||||
};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import type { NextRequest } from 'next/server';
|
||||
|
||||
const PUBLIC_PATHS = ['/login', '/api/auth'];
|
||||
const PUBLIC_PATHS = ['/login', '/api/auth', '/api/backoffice/auth'];
|
||||
|
||||
export function proxy(request: NextRequest) {
|
||||
const { pathname } = request.nextUrl;
|
||||
|
||||
Reference in New Issue
Block a user