98 lines
3.7 KiB
TypeScript
98 lines
3.7 KiB
TypeScript
import Link from "next/link";
|
|
import { redirect } from "next/navigation";
|
|
import { apiGet } from "@/lib/server-api";
|
|
import { badgeClass, pauseReasonLabel, userStatusLabel } from "@/lib/format";
|
|
import { isRestricted } from "@/lib/access";
|
|
import type { Me, UserSummary } from "@/lib/types";
|
|
|
|
type Search = Promise<Record<string, string | string[] | undefined>>;
|
|
|
|
export default async function UsersPage({ searchParams }: { searchParams: Search }) {
|
|
const me = await apiGet<Me>("/auth/me");
|
|
if (isRestricted(me.account)) redirect(`/usuarios/${me.account.user_id}`);
|
|
const sp = await searchParams;
|
|
const params = new URLSearchParams();
|
|
for (const key of ["status", "q", "custom_window"]) {
|
|
const v = sp[key];
|
|
if (typeof v === "string" && v) params.set(key, v);
|
|
}
|
|
const { users } = await apiGet<{ users: UserSummary[] }>(`/users?${params}`);
|
|
|
|
return (
|
|
<>
|
|
<h1>Usuários</h1>
|
|
<form className="filters" method="get">
|
|
<input name="q" placeholder="Nome ou chat ID" defaultValue={typeof sp.q === "string" ? sp.q : ""} />
|
|
<select name="status" defaultValue={typeof sp.status === "string" ? sp.status : ""}>
|
|
<option value="">Todos os status</option>
|
|
<option value="pending">Aguardando aprovação</option>
|
|
<option value="active">Ativos</option>
|
|
<option value="disabled">Desativados</option>
|
|
</select>
|
|
<select name="custom_window" defaultValue={typeof sp.custom_window === "string" ? sp.custom_window : ""}>
|
|
<option value="">Qualquer janela</option>
|
|
<option value="true">Janela personalizada</option>
|
|
<option value="false">Janela global</option>
|
|
</select>
|
|
<button className="btn secondary" type="submit">
|
|
Filtrar
|
|
</button>
|
|
</form>
|
|
|
|
<div className="table-wrap">
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th>ID</th>
|
|
<th>Nome</th>
|
|
<th>Status</th>
|
|
<th>Credencial</th>
|
|
<th>Lembretes</th>
|
|
<th>Janela</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{users.length === 0 && (
|
|
<tr>
|
|
<td colSpan={6} className="muted">
|
|
Nenhum usuário encontrado.
|
|
</td>
|
|
</tr>
|
|
)}
|
|
{users.map((u) => (
|
|
<tr key={u.id}>
|
|
<td>{u.id}</td>
|
|
<td>
|
|
<Link href={`/usuarios/${u.id}`}>{u.display_name}</Link>
|
|
{u.is_bot_admin && <span className="badge"> admin do bot</span>}
|
|
</td>
|
|
<td>
|
|
<span className={badgeClass(u.status)}>{userStatusLabel[u.status]}</span>
|
|
</td>
|
|
<td>
|
|
{!u.has_credential ? (
|
|
<span className="badge warn">não configurada</span>
|
|
) : u.credential_validated ? (
|
|
<span className="badge ok">validada</span>
|
|
) : (
|
|
<span className="badge warn">não validada</span>
|
|
)}
|
|
{u.consecutive_failures > 0 && <span className="badge error"> {u.consecutive_failures} falha(s)</span>}
|
|
</td>
|
|
<td>
|
|
{u.notifications_enabled ? (
|
|
<span className="badge ok">ligados</span>
|
|
) : (
|
|
<span className="muted">{u.paused_reason ? pauseReasonLabel[u.paused_reason] : "desligados"}</span>
|
|
)}
|
|
</td>
|
|
<td>{u.custom_window ? <span className="badge warn">personalizada</span> : <span className="muted">global</span>}</td>
|
|
</tr>
|
|
))}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</>
|
|
);
|
|
}
|