feat(api,web): c2 consulta de clientes — list + search + auth flow

prisma: modelo Client + migração 20260527225728_add_client + seed dev (10 clientes)
api: GET /clients (list, busca, filtro atividade/financeiro, paginação) + GET /clients/:id
     rep vê carteira própria; supervisor/admin vê tudo; activityStatus calculado de lastOrderAt
@sar/api-interface: ClientSummarySchema, ClientDetailSchema, ClientListResponseSchema
web: ClientsPage (tabela AntD, busca, filtro), DevLogin (token dev), authStore, Bearer no apiFetch
oq-4 resolvida: creditLimit gerenciado no SAR

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-27 23:08:57 +00:00
parent 2a8be3fd82
commit 14c8350216
26 changed files with 1394 additions and 84 deletions

View File

@@ -0,0 +1,53 @@
// Componente de login dev — visível apenas quando NODE_ENV !== 'production' e sem token.
// Em produção o token vem do master-login real (fora do escopo do MVP).
import { useState } from 'react';
import { Alert, Button, Card, Flex, Space, Typography } from 'antd';
import { apiFetch } from '../../lib/api-client';
import { authStore } from '../../lib/auth-store';
import { AuthTokenResponseSchema } from '@sar/api-interface';
export function DevLogin({ onLogin }: { onLogin: () => void }) {
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
async function handleLogin() {
setLoading(true);
setError(null);
try {
const raw = await apiFetch('/api/v1/auth/dev/token', {
method: 'POST',
body: { userId: 'user-001', workspaceId: 'dev-workspace', role: 'rep' },
});
const { accessToken } = AuthTokenResponseSchema.parse(raw);
authStore.set(accessToken);
onLogin();
} catch (e) {
setError(e instanceof Error ? e.message : 'Erro ao obter token');
} finally {
setLoading(false);
}
}
return (
<Flex justify="center" align="center" style={{ minHeight: '100vh' }}>
<Card style={{ width: 360 }}>
<Space direction="vertical" size={20} style={{ width: '100%' }}>
<Typography.Title level={3} style={{ margin: 0 }}>
SAR · Login Dev
</Typography.Title>
<Alert
type="warning"
message="Ambiente de desenvolvimento"
description="Este login automático não existe em produção."
showIcon
/>
{error && <Alert type="error" message={error} showIcon />}
<Button type="primary" block loading={loading} onClick={() => void handleLogin()}>
Entrar como Rafael (rep · user-001)
</Button>
</Space>
</Card>
</Flex>
);
}