Oleada UX 5: Servicios (duplicar, favoritos y orden para POS, mas vendidos) y Medicos (semana tipo con validacion en agenda, meta mensual con progreso)

This commit is contained in:
2026-09-17 21:28:43 +00:00
parent 9ee3343e22
commit cd945df6a5
10 changed files with 885 additions and 41 deletions

View File

@@ -3,7 +3,7 @@ import { useEffect, useState, useCallback, useMemo, useRef } from 'react';
import { Link, useSearchParams } from 'react-router-dom';
import {
Search, Plus, Minus, X, Store, AlertTriangle, ShoppingBag, Star, Wallet, Trash2, Package,
RotateCcw, Keyboard, Lock,
RotateCcw, Keyboard, Lock, TrendingUp,
} from 'lucide-react';
import Layout from '../components/Layout';
import { usePatientDrawer } from '../components/PatientDrawer';
@@ -57,6 +57,7 @@ const Pos: FC = () => {
const [search, setSearch] = useState('');
const [categoria, setCategoria] = useState('');
const [soloFavoritos, setSoloFavoritos] = useState(false);
const [ordenServicios, setOrdenServicios] = useState<'manual' | 'best_sellers'>('manual');
const [invItems, setInvItems] = useState<InventoryItem[]>([]);
// Ticket
@@ -106,10 +107,17 @@ const Pos: FC = () => {
const [ticketMovil, setTicketMovil] = useState(false);
// ---- Catálogo ----
// Orden del catálogo de servicios: manual (sequence definida en Servicios)
// o por más vendidos de los últimos 90 días.
const loadServices = useCallback(async () => {
try {
setCatalogLoading(true);
const res = await odooApi.getServices({ search: search || undefined, category: categoria || undefined, page_size: 200 });
const res = await odooApi.getServices({
search: search || undefined,
category: categoria || undefined,
page_size: 200,
sort: ordenServicios,
});
if (res.status === 'success') setServices(res.services);
} catch (err) {
toast.error('Error al cargar servicios');
@@ -117,7 +125,7 @@ const Pos: FC = () => {
} finally {
setCatalogLoading(false);
}
}, [search, categoria]);
}, [search, categoria, ordenServicios]);
const loadItems = useCallback(async () => {
try {
@@ -839,6 +847,27 @@ const Pos: FC = () => {
{c}
</button>
))}
<span className="w-px self-stretch bg-theme-border mx-1" />
<button
type="button"
onClick={() => setOrdenServicios('manual')}
className={`px-3 py-1.5 text-xs font-medium rounded-full transition ${
ordenServicios === 'manual' ? 'bg-theme-accent text-theme-inverse' : 'bg-theme-accent-bg text-theme-heading hover:bg-theme-bg'
}`}
title="Orden definido en el catálogo de Servicios"
>
Orden manual
</button>
<button
type="button"
onClick={() => setOrdenServicios('best_sellers')}
className={`px-3 py-1.5 text-xs font-medium rounded-full transition flex items-center gap-1 ${
ordenServicios === 'best_sellers' ? 'bg-theme-accent text-theme-inverse' : 'bg-theme-accent-bg text-theme-heading hover:bg-theme-bg'
}`}
title="Ordenado por ventas de los últimos 90 días"
>
<TrendingUp size={12} /> Más vendidos
</button>
</div>
)}