fix(reporting): resize trend chart and repair closures detail
This commit is contained in:
@@ -1 +1 @@
|
||||
0.2.1
|
||||
0.2.2
|
||||
|
||||
4
project/apps/admin/package-lock.json
generated
4
project/apps/admin/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@mercadodevida/admin",
|
||||
"version": "0.2.1",
|
||||
"version": "0.2.2",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@mercadodevida/admin",
|
||||
"version": "0.2.1",
|
||||
"version": "0.2.2",
|
||||
"dependencies": {
|
||||
"@lexical/history": "^0.49.0",
|
||||
"@lexical/html": "^0.49.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@mercadodevida/admin",
|
||||
"version": "0.2.1",
|
||||
"version": "0.2.2",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev --port 3001",
|
||||
|
||||
@@ -13,18 +13,25 @@ interface SalesSummary {
|
||||
byState: Record<string, { count: number; totalCents: number }>;
|
||||
}
|
||||
|
||||
interface ClosedSession {
|
||||
interface CashCloseReport {
|
||||
session: {
|
||||
id: string;
|
||||
openedAt: string;
|
||||
closedAt: string;
|
||||
closedAt: string | null;
|
||||
userId: string;
|
||||
status: string;
|
||||
};
|
||||
storeId: string;
|
||||
terminalId: string;
|
||||
financial: {
|
||||
openingCashCents: number;
|
||||
closingCashCents: number | null;
|
||||
actualCashCents: number | null;
|
||||
differenceCents: number | null;
|
||||
closingCashCents: number;
|
||||
actualCashCents: number;
|
||||
expectedCashCents: number;
|
||||
differenceCents: number;
|
||||
};
|
||||
sales: SalesSummary;
|
||||
paymentsByMethod: Array<{ methodCode: string; methodName: string; totalCents: number; count: number }>;
|
||||
payments: Array<{ methodCode: string; methodName: string; totalCents: number; transactionCount: number }>;
|
||||
items: { soldCount: number; uniqueProducts: number };
|
||||
}
|
||||
|
||||
@@ -53,10 +60,11 @@ interface SessionRow {
|
||||
|
||||
export default function ReportingClosuresPage() {
|
||||
const [sessions, setSessions] = useState<SessionRow[]>([]);
|
||||
const [report, setReport] = useState<ClosedSession | null>(null);
|
||||
const [report, setReport] = useState<CashCloseReport | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [loadingReport, setLoadingReport] = useState(false);
|
||||
const [error, setError] = useState('');
|
||||
const [reportError, setReportError] = useState('');
|
||||
const [selectedStore, setSelectedStore] = useState('');
|
||||
const [stores, setStores] = useState<Array<{ id: string; name: string }>>([]);
|
||||
const [filterDays, setFilterDays] = useState(30);
|
||||
@@ -85,11 +93,14 @@ export default function ReportingClosuresPage() {
|
||||
|
||||
const loadReport = async (sessionId: string) => {
|
||||
setLoadingReport(true);
|
||||
try {
|
||||
const data = await api.get<ClosedSession>(`/api/pos/reports/cash-close/${sessionId}`);
|
||||
setReport(data);
|
||||
} catch {
|
||||
setReport(null);
|
||||
setReportError('');
|
||||
try {
|
||||
const data = await api.get<CashCloseReport>(`/api/pos/reports/cash-close/${sessionId}`);
|
||||
setReport(data);
|
||||
} catch (err) {
|
||||
setReport(null);
|
||||
setReportError(err instanceof Error ? err.message : 'Error al cargar reporte de cierre');
|
||||
} finally {
|
||||
setLoadingReport(false);
|
||||
}
|
||||
@@ -108,7 +119,7 @@ export default function ReportingClosuresPage() {
|
||||
if (selectedStore) void loadSessions();
|
||||
}, [selectedStore, filterDays, loadSessions]);
|
||||
|
||||
const diff = report?.differenceCents ?? 0;
|
||||
const diff = report?.financial.differenceCents ?? 0;
|
||||
const diffClass = diff > 0 ? 'text-green-600' : diff < 0 ? 'text-red-600' : 'text-gray-600';
|
||||
|
||||
return (
|
||||
@@ -163,7 +174,7 @@ export default function ReportingClosuresPage() {
|
||||
type="button"
|
||||
onClick={() => void loadReport(s.id)}
|
||||
className={`w-full text-left rounded-xl border p-3 text-sm transition-colors ${
|
||||
report?.id === s.id
|
||||
report?.session.id === s.id
|
||||
? 'border-[#2D6A4F] bg-[#2D6A4F]/5'
|
||||
: 'border-gray-200 hover:border-[#2D6A4F] hover:bg-gray-50'
|
||||
}`}
|
||||
@@ -193,14 +204,14 @@ export default function ReportingClosuresPage() {
|
||||
|
||||
{/* Report detail */}
|
||||
<div className="lg:col-span-3">
|
||||
{!report ? (
|
||||
<div className="flex h-64 flex-col items-center justify-center rounded-xl border border-dashed border-gray-300 text-gray-400 text-sm">
|
||||
Selecciona una sesión para ver el reporte
|
||||
</div>
|
||||
) : loadingReport ? (
|
||||
{loadingReport ? (
|
||||
<div className="flex h-64 items-center justify-center text-gray-400 text-sm">
|
||||
Cargando reporte…
|
||||
</div>
|
||||
) : !report ? (
|
||||
<div className="flex h-64 flex-col items-center justify-center rounded-xl border border-dashed border-gray-300 px-6 text-center text-gray-400 text-sm">
|
||||
{reportError || 'Selecciona una sesión para ver el reporte'}
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-4">
|
||||
{/* Financial summary */}
|
||||
@@ -208,9 +219,9 @@ export default function ReportingClosuresPage() {
|
||||
<h3 className="mb-4 text-base font-bold text-gray-900">📊 Resumen financiero</h3>
|
||||
<div className="grid grid-cols-2 gap-4 sm:grid-cols-4">
|
||||
{[
|
||||
['Saldo inicial', fmt(report.openingCashCents)],
|
||||
['Saldo inicial', fmt(report.financial.openingCashCents)],
|
||||
['Ventas', fmt(report.sales.completedTotalCents)],
|
||||
['Saldo esperado', fmt(report.openingCashCents + report.sales.completedTotalCents)],
|
||||
['Saldo esperado', fmt(report.financial.expectedCashCents)],
|
||||
].map(([label, value]) => (
|
||||
<div key={label}>
|
||||
<p className="text-xs text-gray-500">{label}</p>
|
||||
@@ -219,7 +230,7 @@ export default function ReportingClosuresPage() {
|
||||
))}
|
||||
<div>
|
||||
<p className="text-xs text-gray-500">Efectivo real</p>
|
||||
<p className="text-lg font-bold text-gray-900">{fmt(report.actualCashCents ?? 0)}</p>
|
||||
<p className="text-lg font-bold text-gray-900">{fmt(report.financial.actualCashCents)}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-xs text-gray-500">Diferencia</p>
|
||||
@@ -257,7 +268,7 @@ export default function ReportingClosuresPage() {
|
||||
</div>
|
||||
|
||||
{/* By payment method */}
|
||||
{report.paymentsByMethod.length > 0 && (
|
||||
{report.payments.length > 0 && (
|
||||
<div className="rounded-xl border border-gray-200 bg-white p-5">
|
||||
<h3 className="mb-3 text-base font-bold text-gray-900">💳 Formas de pago</h3>
|
||||
<table className="w-full text-sm">
|
||||
@@ -269,10 +280,10 @@ export default function ReportingClosuresPage() {
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{report.paymentsByMethod.map(p => (
|
||||
{report.payments.map(p => (
|
||||
<tr key={p.methodCode} className="border-b border-gray-50 last:border-0">
|
||||
<td className="py-2 font-medium text-gray-800">{p.methodName}</td>
|
||||
<td className="py-2 text-right text-gray-600">{p.count}</td>
|
||||
<td className="py-2 text-right text-gray-600">{p.transactionCount}</td>
|
||||
<td className="py-2 text-right font-bold text-gray-900">{fmt(p.totalCents)}</td>
|
||||
</tr>
|
||||
))}
|
||||
|
||||
@@ -79,7 +79,7 @@ function TrendDashboard({ filters }: { filters: FilterState }) {
|
||||
|
||||
const maxValue = Math.max(...points.map((p) => p.value), 1);
|
||||
|
||||
return <TrendChart data={points} maxValue={maxValue} height={100} />;
|
||||
return <TrendChart data={points} maxValue={maxValue} height={180} />;
|
||||
}
|
||||
|
||||
function ChannelDashboard({ filters }: { filters: FilterState }) {
|
||||
|
||||
@@ -19,14 +19,15 @@ export function TrendChart({ data, maxValue, height = 120 }: TrendChartProps) {
|
||||
const width = 100; // percentage-based SVG
|
||||
const barWidth = Math.min(3, (width * 0.9) / data.length);
|
||||
const gap = Math.max(0.2, (width - barWidth * data.length) / (data.length + 1));
|
||||
const chartHeight = height - 40; // leave room for labels
|
||||
const chartHeight = height - 34; // leave room for labels
|
||||
|
||||
return (
|
||||
<div className="w-full overflow-x-auto">
|
||||
<div className="w-full overflow-x-auto rounded-xl bg-white">
|
||||
<svg
|
||||
viewBox={`0 0 ${width} ${height}`}
|
||||
className="w-full"
|
||||
style={{ minWidth: `${Math.max(300, data.length * 8)}px` }}
|
||||
className="block w-full"
|
||||
style={{ minWidth: `${Math.max(360, data.length * 10)}px`, height: `${height}px` }}
|
||||
preserveAspectRatio="none"
|
||||
aria-label="Tendencia de ventas"
|
||||
role="img"
|
||||
>
|
||||
|
||||
4
project/apps/pos/package-lock.json
generated
4
project/apps/pos/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "mercadodevida-pos",
|
||||
"version": "0.2.1",
|
||||
"version": "0.2.2",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "mercadodevida-pos",
|
||||
"version": "0.2.1",
|
||||
"version": "0.2.2",
|
||||
"dependencies": {
|
||||
"next": "^16.3.1",
|
||||
"react": "^19.2.8",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "mercadodevida-pos",
|
||||
"version": "0.2.1",
|
||||
"version": "0.2.2",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev --port 3002",
|
||||
|
||||
4
project/frontend/package-lock.json
generated
4
project/frontend/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "frontend",
|
||||
"version": "0.2.1",
|
||||
"version": "0.2.2",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "frontend",
|
||||
"version": "0.2.1",
|
||||
"version": "0.2.2",
|
||||
"dependencies": {
|
||||
"next": "16.3.1",
|
||||
"react": "19.2.8",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "frontend",
|
||||
"version": "0.2.1",
|
||||
"version": "0.2.2",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
|
||||
4
project/package-lock.json
generated
4
project/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "mercadodevida-backend",
|
||||
"version": "0.2.1",
|
||||
"version": "0.2.2",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "mercadodevida-backend",
|
||||
"version": "0.2.1",
|
||||
"version": "0.2.2",
|
||||
"dependencies": {
|
||||
"@fastify/cookie": "^11.1.2",
|
||||
"@fastify/cors": "^11.3.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "mercadodevida-backend",
|
||||
"version": "0.2.1",
|
||||
"version": "0.2.2",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"description": "mercadodevida vNext backend - modular monolith skeleton",
|
||||
|
||||
@@ -699,13 +699,11 @@ export async function registerPosRoutes(app: FastifyInstance, deps: PosRouteDeps
|
||||
),
|
||||
pool.query<{ method_code: string; method_name: string; total: string; count: string }>(
|
||||
`SELECT pm.code AS method_code, pm.label AS method_name,
|
||||
COALESCE(SUM(pt.amount_cents), 0)::bigint AS total,
|
||||
COALESCE(SUM(rpl.amount_cents), 0)::bigint AS total,
|
||||
COUNT(*)::int AS count
|
||||
FROM payments_transactions pt
|
||||
JOIN orders_orders o ON o.id = pt.order_id
|
||||
LEFT JOIN pos_payment_methods pm ON pm.id::text = pt.provider_event_id
|
||||
AND pm.store_id = o.store_id
|
||||
WHERE o.cash_session_id = $1 AND o.source = 'pos' AND pt.status = 'succeeded'
|
||||
FROM reporting_payment_lines rpl
|
||||
LEFT JOIN pos_payment_methods pm ON pm.id = rpl.payment_method_id
|
||||
WHERE rpl.cash_session_id = $1 AND rpl.status = 'payment'
|
||||
GROUP BY pm.code, pm.label`,
|
||||
[id],
|
||||
),
|
||||
@@ -771,13 +769,11 @@ export async function registerPosRoutes(app: FastifyInstance, deps: PosRouteDeps
|
||||
),
|
||||
pool.query<{ method_code: string; method_name: string; total: string; count: string }>(
|
||||
`SELECT pm.code AS method_code, pm.label AS method_name,
|
||||
COALESCE(SUM(pt.amount_cents), 0)::bigint AS total,
|
||||
COALESCE(SUM(rpl.amount_cents), 0)::bigint AS total,
|
||||
COUNT(*)::int AS count
|
||||
FROM payments_transactions pt
|
||||
JOIN orders_orders o ON o.id = pt.order_id
|
||||
LEFT JOIN pos_payment_methods pm ON pm.id::text = pt.provider_event_id
|
||||
AND pm.store_id = o.store_id
|
||||
WHERE o.cash_session_id = $1 AND o.source = 'pos' AND pt.status = 'succeeded'
|
||||
FROM reporting_payment_lines rpl
|
||||
LEFT JOIN pos_payment_methods pm ON pm.id = rpl.payment_method_id
|
||||
WHERE rpl.cash_session_id = $1 AND rpl.status = 'payment'
|
||||
GROUP BY pm.code, pm.label`,
|
||||
[id],
|
||||
),
|
||||
|
||||
4
project/storefront/package-lock.json
generated
4
project/storefront/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "mercadodevida-storefront",
|
||||
"version": "0.2.1",
|
||||
"version": "0.2.2",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "mercadodevida-storefront",
|
||||
"version": "0.2.1",
|
||||
"version": "0.2.2",
|
||||
"dependencies": {
|
||||
"@tailwindcss/postcss": "^4.1.17",
|
||||
"next": "^16.0.5",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "mercadodevida-storefront",
|
||||
"version": "0.2.1",
|
||||
"version": "0.2.2",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"description": "mercadodevida customer storefront shell",
|
||||
|
||||
Reference in New Issue
Block a user