fix: frontend fixes batch - live search proxy, quick products 10 slots, categories emoji list, bell notifications with tabs, awaiting payment orders endpoint

This commit is contained in:
chattie
2026-08-24 17:22:08 +02:00
parent d9a57aaa05
commit 85ecee935b
17 changed files with 460 additions and 71 deletions

View File

@@ -0,0 +1,65 @@
import { FastifyInstance } from 'fastify';
import { Pool } from 'pg';
export function notificationsRoutes(fastify: FastifyInstance, pool: Pool) {
// GET /api/admin/notifications
// Returns pending counts for bell icon
fastify.get('/admin/notifications', async (req) => {
const [ordersRows] = await Promise.all([
pool.query<{ pending: string; shipped_24h: string; awaiting_payment: string }>(`
SELECT
COUNT(*) FILTER (WHERE state = 'PENDING') AS pending,
COUNT(*) FILTER (
WHERE state = 'SHIPPED'
AND updated_at < NOW() - INTERVAL '24 hours'
) AS shipped_24h,
COUNT(*) FILTER (
WHERE payment_status = 'AWAITING'
AND state = 'PENDING'
) AS awaiting_payment
FROM pos_orders
WHERE deleted_at IS NULL
AND created_at > NOW() - INTERVAL '30 days'
`),
]);
const row = ordersRows.rows[0];
const items = [];
const pending = parseInt(row?.pending ?? '0', 10);
if (pending > 0) {
items.push({
id: 'pending_orders',
type: 'pending_orders',
count: pending,
message: 'Pedidos pendientes de procesar',
href: '/orders?state=PENDING',
});
}
const shipped24h = parseInt(row?.shipped_24h ?? '0', 10);
if (shipped24h > 0) {
items.push({
id: 'stale_shipped',
type: 'stale_shipped',
count: shipped24h,
message: 'Enviados sin actualizar en +24h',
href: '/orders?state=SHIPPED&stale=true',
});
}
const awaiting = parseInt(row?.awaiting_payment ?? '0', 10);
if (awaiting > 0) {
items.push({
id: 'awaiting_payment',
type: 'awaiting_payment',
count: awaiting,
message: 'Esperando confirmación de pago',
href: '/orders?payment_status=AWAITING',
});
}
return { items };
});
}

View File

@@ -157,6 +157,29 @@ export async function registerBackofficeRoutes(
throw error;
}
});
// Bell notifications for admin dashboard
app.get('/notifications', async (req) => {
const rows = await deps.pool.query<{
pending: string; shipped_24h: string; awaiting_payment: string;
}>(`
SELECT
COUNT(*) FILTER (WHERE state = 'PENDING') AS pending,
COUNT(*) FILTER (WHERE state = 'SHIPPED' AND updated_at < NOW() - INTERVAL '24 hours') AS shipped_24h,
COUNT(*) FILTER (WHERE payment_status = 'AWAITING' AND state = 'PENDING') AS awaiting_payment
FROM pos_orders
WHERE deleted_at IS NULL AND created_at > NOW() - INTERVAL '30 days'
`);
const r = rows.rows[0] ?? { pending: '0', shipped_24h: '0', awaiting_payment: '0' };
const items = [];
const p = parseInt(r.pending, 10);
if (p > 0) items.push({ id: 'pending_orders', type: 'pending_orders', count: p, message: 'Pedidos pendientes de procesar', href: '/orders?state=PENDING' });
const s = parseInt(r.shipped_24h, 10);
if (s > 0) items.push({ id: 'stale_shipped', type: 'stale_shipped', count: s, message: 'Enviados sin actualizar en +24h', href: '/orders?state=SHIPPED&stale=true' });
const a = parseInt(r.awaiting_payment, 10);
if (a > 0) items.push({ id: 'awaiting_payment', type: 'awaiting_payment', count: a, message: 'Esperando confirmación de pago', href: '/orders?payment_status=AWAITING' });
return { items };
});
}
function isSecureRequest(request: FastifyRequest): boolean {

View File

@@ -530,6 +530,38 @@ export async function registerOrdersRoutes(
throw mapOrderError(error);
}
});
// Awaiting payment orders for admin notifications
app.get('/orders/notifications/awaiting-payment', async (request, reply) => {
const user = await deps.authenticate(request);
requireRole(user, 'admin');
const result = await deps.pool.query<{
id: string;
total_cents: number;
customer_email: string | null;
created_at: Date;
total_count: number;
}>(
`SELECT o.id, o.total_cents, u.email AS customer_email, o.created_at,
COUNT(*) OVER()::int AS total_count
FROM orders_orders o
LEFT JOIN identity_users u ON u.id = o.user_id
WHERE o.payment_status = 'AWAITING'
AND o.state = 'PENDING'
AND o.deleted_at IS NULL
ORDER BY o.created_at ASC
LIMIT 20`,
);
return reply.send({
total: result.rows[0]?.total_count ?? 0,
items: result.rows.map((row) => ({
id: row.id,
totalCents: row.total_cents,
customerEmail: row.customer_email,
createdAt: row.created_at.toISOString(),
})),
});
});
}
function mapOrderError(error: unknown): Error {