113 lines
4.3 KiB
TypeScript
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>
|
|
</>
|
|
);
|
|
}
|