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:
65
project/src/modules/admin/api/notifications.routes.ts
Normal file
65
project/src/modules/admin/api/notifications.routes.ts
Normal 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 };
|
||||
});
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user