Files
bot-pontos/web/app/(painel)/notificacoes/page.tsx
2026-10-23 22:31:02 -03:00

113 lines
4.3 KiB
TypeScript

import Link from "next/link";
import { ActionButton } from "@/components/ActionButton";
import { apiGet } from "@/lib/server-api";
import { badgeClass, fmtClock, fmtDay, notificationStatusLabel } from "@/lib/format";
import type { Me, Notification } from "@/lib/types";
type Search = Promise<Record<string, string | string[] | undefined>>;
export default async function NotificationsPage({ searchParams }: { searchParams: Search }) {
const sp = await searchParams;
const params = new URLSearchParams();
for (const key of ["day", "status", "type", "user_id"]) {
const v = sp[key];
if (typeof v === "string" && v) params.set(key, v);
}
const [{ notifications }, me] = await Promise.all([
apiGet<{ notifications: Notification[] }>(`/notifications?${params}`),
apiGet<Me>("/auth/me"),
]);
const canEdit = me.account.role !== "viewer";
const value = (key: string) => (typeof sp[key] === "string" ? (sp[key] as string) : "");
return (
<>
<h1>Lembretes</h1>
<form className="filters" method="get">
<input type="date" name="day" defaultValue={value("day")} />
<select name="status" defaultValue={value("status")}>
<option value="">Todos os status</option>
{Object.entries(notificationStatusLabel).map(([k, label]) => (
<option key={k} value={k}>
{label}
</option>
))}
</select>
<select name="type" defaultValue={value("type")}>
<option value="">Todos os tipos</option>
<option value="entrada2">Retorno do almoço</option>
<option value="saida2">Saída</option>
</select>
{value("user_id") && <input type="hidden" name="user_id" value={value("user_id")} />}
<button className="btn secondary" type="submit">
Filtrar
</button>
<Link href="/notificacoes">Limpar</Link>
</form>
<p className="muted">
<strong>expirado</strong> = chegou tarde demais (mais de 10 min de atraso) e não foi enviado;{" "}
<strong>falhou</strong> = erro no envio pelo Telegram. Só lembretes com falha do dia atual podem ser reenviados.
</p>
<div className="table-wrap">
<table>
<thead>
<tr>
<th>Dia</th>
<th>Usuário</th>
<th>Tipo</th>
<th>Aviso</th>
<th>Previsto</th>
<th>Status</th>
<th>Detalhe</th>
{canEdit && <th></th>}
</tr>
</thead>
<tbody>
{notifications.length === 0 && (
<tr>
<td colSpan={canEdit ? 8 : 7} className="muted">
Nenhum lembrete encontrado.
</td>
</tr>
)}
{notifications.map((n) => (
<tr key={n.id}>
<td>{fmtDay(n.reference_date)}</td>
<td>
<Link href={`/usuarios/${n.user_id}`}>{n.user_name}</Link>
</td>
<td>{n.type === "entrada2" ? "retorno" : "saída"}</td>
<td>{fmtClock(n.scheduled_at)}</td>
<td>{fmtClock(n.predicted_at)}</td>
<td>
<span className={badgeClass(n.status)}>{notificationStatusLabel[n.status]}</span>
</td>
<td className="muted small">{n.error_message ?? (n.sent_at ? `enviado às ${fmtClock(n.sent_at)}` : "")}</td>
{canEdit && (
<td>
{n.status === "scheduled" && (
<ActionButton path={`/notifications/${n.id}/cancel`} label="Cancelar" confirm="Cancelar este lembrete?" />
)}
{n.status === "failed" && (
<ActionButton
path={`/notifications/${n.id}/resend`}
label="Reenviar"
variant="primary"
confirm={
"Reenviar este lembrete agora?\n\nSe a falha foi um travamento durante o envio, o usuário pode já ter recebido a mensagem original."
}
/>
)}
</td>
)}
</tr>
))}
</tbody>
</table>
</div>
</>
);
}