Frontend completo (Etapas 11-18) e extensões de backend (Etapas 13 e 18)

Adiciona o frontend Next.js em frontend/ (setup, design system,
autenticação/tema, layout/navegação, área master, área tenant completa:
clientes, produtos, vendas, recebimentos, whatsapp, e-mail, alertas,
equipe e perfil). Estende o backend de forma aditiva e autorizada:
cookie httpOnly para refresh token (Etapa 13) e CRUD de equipe +
edição de perfil do tenant (Etapa 18, módulo src/modules/equipe/ novo
e PUT /tenant/perfil). Detalhes completos em Etapas/V2/*.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-16 09:41:13 -03:00
co-authored by Claude Sonnet 5
parent f9738e54da
commit 323912748f
179 changed files with 29312 additions and 22 deletions
@@ -0,0 +1,69 @@
import { apiClient } from '@/lib/api/client';
import { buildQuery, type PaginatedResult } from '@/lib/api/types';
import {
TIPO_TEMPLATE_LABEL,
TIPOS_TEMPLATE,
type AlertTemplate,
type CanalTemplate,
type TipoTemplate,
} from '@/lib/api/alert-templates';
export {
CANAIS_TEMPLATE,
CANAL_TEMPLATE_LABEL,
PLACEHOLDERS_POR_TIPO,
TIPO_TEMPLATE_LABEL,
} from '@/lib/api/alert-templates';
export type { AlertTemplate, CanalTemplate, TipoTemplate } from '@/lib/api/alert-templates';
/**
* `mensalidade_tenant` fica de fora — é o master notificando o tenant sobre
* a própria mensalidade, o tenant não define esse template (espelha
* `TIPOS_TEMPLATE_TENANT` em `src/modules/alerts/alert-template-tipos.ts`).
*/
export const TIPOS_TEMPLATE_TENANT = TIPOS_TEMPLATE.filter(
(tipo) => tipo !== 'mensalidade_tenant',
);
export type TipoTemplateTenant = (typeof TIPOS_TEMPLATE_TENANT)[number];
export const TIPO_TEMPLATE_TENANT_LABEL: Record<TipoTemplateTenant, string> =
Object.fromEntries(
TIPOS_TEMPLATE_TENANT.map((tipo) => [tipo, TIPO_TEMPLATE_LABEL[tipo]]),
) as Record<TipoTemplateTenant, string>;
/** Espelha `CreateTenantAlertTemplateDto`/`UpdateTenantAlertTemplateDto`. */
export interface TenantAlertTemplateInput {
tipo: TipoTemplateTenant;
canal: CanalTemplate;
/** Obrigatório só quando `canal === 'email'`. */
assunto?: string;
corpo: string;
ativo?: boolean;
}
export interface ListTenantAlertTemplatesParams {
tipo?: TipoTemplate;
canal?: CanalTemplate;
ativo?: boolean;
page?: number;
limit?: number;
}
export function listTenantAlertTemplates(params: ListTenantAlertTemplatesParams) {
return apiClient.get<PaginatedResult<AlertTemplate>>(
`/tenant/alertas/templates${buildQuery(params)}`,
);
}
export function createTenantAlertTemplate(input: TenantAlertTemplateInput) {
return apiClient.post<AlertTemplate>('/tenant/alertas/templates', input);
}
export function updateTenantAlertTemplate(id: string, input: Partial<TenantAlertTemplateInput>) {
return apiClient.put<AlertTemplate>(`/tenant/alertas/templates/${id}`, input);
}
/** Soft delete no backend (`ativo: false`) — não remove o registro. */
export function deleteTenantAlertTemplate(id: string) {
return apiClient.delete<AlertTemplate>(`/tenant/alertas/templates/${id}`);
}