- Adiciona role 'agenda_viewer' para profissionais visualizarem apenas suas agendas - Implementa middleware de autorização baseado em roles - Adiciona validação de permissões nos endpoints de agenda - Melhora exibição de dados financeiros e logísticos - Atualiza componentes frontend para melhor UX - Adiciona documentação sobre o papel de visualização de agenda
198 lines
5.7 KiB
Markdown
198 lines
5.7 KiB
Markdown
# Melhorias em Filtros Financeiros e Cadastro - Changelog
|
|
|
|
**Data de Implementação:** 19 de janeiro de 2026
|
|
**Branch:** dev
|
|
|
|
## 📋 Visão Geral
|
|
|
|
Implementação de filtros avançados de data na página de Extrato Financeiro e validação obrigatória de foto de perfil no cadastro de profissionais, visando melhorar o controle financeiro e a qualidade dos dados cadastrais.
|
|
|
|
## 🚀 Funcionalidades Implementadas
|
|
|
|
### 1. Sistema de Filtros Avançados de Data - Extrato Financeiro
|
|
**Objetivo:** Fornecer controle granular sobre visualização de transações financeiras
|
|
|
|
**Implementações:**
|
|
- ✅ Filtro de data de início para definir período inicial
|
|
- ✅ Filtro de data final para definir período final
|
|
- ✅ Opção para incluir/excluir finais de semana
|
|
- ✅ Painel expansível/recolhível para organização da interface
|
|
- ✅ Botão de limpeza rápida de filtros
|
|
- ✅ Integração com filtros existentes (FOT, Evento, Serviço, etc.)
|
|
|
|
**Funcionalidades:**
|
|
- ✅ Painel de filtros com indicador visual (▶/▼)
|
|
- ✅ Layout responsivo com grid para desktop
|
|
- ✅ Cálculo automático de intervalos de datas
|
|
- ✅ Exclusão automática de sábados e domingos quando desabilitado
|
|
- ✅ Mensagem visual quando filtros estão ativos
|
|
- ✅ Preservação de filtros de coluna existentes
|
|
|
|
**Arquivos Modificados:**
|
|
- `frontend/pages/Finance.tsx`
|
|
|
|
### 2. Validação Obrigatória de Foto de Perfil
|
|
**Objetivo:** Garantir que todos os profissionais tenham foto de perfil cadastrada
|
|
|
|
**Implementações:**
|
|
|
|
**No Formulário (ProfessionalForm):**
|
|
- ✅ Validação no `handleSubmit` antes do envio
|
|
- ✅ Label atualizado com asterisco (*) indicando obrigatoriedade
|
|
- ✅ Mensagem de erro clara: "A foto de perfil é obrigatória!"
|
|
- ✅ Bloqueio do envio se foto não estiver presente
|
|
|
|
**No Registro (ProfessionalRegister):**
|
|
- ✅ Validação adicional antes do upload
|
|
- ✅ Tratamento de erro específico para foto ausente
|
|
- ✅ Upload obrigatório (não mais opcional)
|
|
- ✅ Mensagem de erro: "A foto de perfil é obrigatória."
|
|
|
|
**Arquivos Modificados:**
|
|
- `frontend/components/ProfessionalForm.tsx`
|
|
- `frontend/pages/ProfessionalRegister.tsx`
|
|
|
|
## 🔧 Detalhes Técnicos
|
|
|
|
### 1. Filtros Avançados de Data
|
|
|
|
**Novos Estados Implementados:**
|
|
```typescript
|
|
const [dateFilters, setDateFilters] = useState({
|
|
startDate: "",
|
|
endDate: "",
|
|
includeWeekends: true,
|
|
});
|
|
const [showDateFilters, setShowDateFilters] = useState(false);
|
|
```
|
|
|
|
**Algoritmo de Filtragem:**
|
|
```typescript
|
|
// Advanced date filters
|
|
if (dateFilters.startDate || dateFilters.endDate || !dateFilters.includeWeekends) {
|
|
result = result.filter(t => {
|
|
// Parse date from dataRaw (YYYY-MM-DD) or data (DD/MM/YYYY)
|
|
let dateToCheck: Date;
|
|
if (t.dataRaw) {
|
|
dateToCheck = new Date(t.dataRaw);
|
|
} else {
|
|
const parts = t.data.split('/');
|
|
if (parts.length === 3) {
|
|
dateToCheck = new Date(
|
|
parseInt(parts[2]),
|
|
parseInt(parts[1]) - 1,
|
|
parseInt(parts[0])
|
|
);
|
|
} else {
|
|
return true; // Keep if can't parse
|
|
}
|
|
}
|
|
|
|
// Check date range
|
|
if (dateFilters.startDate) {
|
|
const startDate = new Date(dateFilters.startDate);
|
|
if (dateToCheck < startDate) return false;
|
|
}
|
|
|
|
if (dateFilters.endDate) {
|
|
const endDate = new Date(dateFilters.endDate);
|
|
endDate.setHours(23, 59, 59, 999);
|
|
if (dateToCheck > endDate) return false;
|
|
}
|
|
|
|
// Check weekends
|
|
if (!dateFilters.includeWeekends) {
|
|
const dayOfWeek = dateToCheck.getDay();
|
|
if (dayOfWeek === 0 || dayOfWeek === 6) return false;
|
|
}
|
|
|
|
return true;
|
|
});
|
|
}
|
|
```
|
|
|
|
**Lógica de Exclusão de Finais de Semana:**
|
|
- `dayOfWeek === 0` → Domingo
|
|
- `dayOfWeek === 6` → Sábado
|
|
- Exclusão automática quando checkbox desmarcado
|
|
|
|
**Interface de Usuário:**
|
|
```tsx
|
|
<div className="bg-white rounded shadow p-4 grid grid-cols-1 md:grid-cols-3 gap-4 mb-4">
|
|
<div>
|
|
<label>Data Início</label>
|
|
<input type="date" />
|
|
</div>
|
|
|
|
<div>
|
|
<label>Data Final</label>
|
|
<input type="date" />
|
|
</div>
|
|
|
|
<div>
|
|
<label>
|
|
<input type="checkbox" />
|
|
Incluir finais de semana
|
|
</label>
|
|
</div>
|
|
</div>
|
|
```
|
|
|
|
### 2. Validação de Foto Obrigatória
|
|
|
|
**Fluxo de Validação:**
|
|
|
|
1. **Formulário (ProfessionalForm.tsx):**
|
|
```typescript
|
|
const handleSubmit = (e: React.FormEvent) => {
|
|
e.preventDefault();
|
|
|
|
// Validação de foto de perfil
|
|
if (!formData.avatar) {
|
|
alert("A foto de perfil é obrigatória!");
|
|
return;
|
|
}
|
|
|
|
// ... restante das validações
|
|
};
|
|
```
|
|
|
|
2. **Upload (ProfessionalRegister.tsx):**
|
|
```typescript
|
|
// Upload de Avatar (obrigatório)
|
|
if (!professionalData.avatar) {
|
|
throw new Error("A foto de perfil é obrigatória.");
|
|
}
|
|
|
|
try {
|
|
console.log("Iniciando upload do avatar...");
|
|
const uploadRes = await getUploadURL(
|
|
professionalData.avatar.name,
|
|
professionalData.avatar.type
|
|
);
|
|
|
|
if (uploadRes.error || !uploadRes.data) {
|
|
throw new Error(uploadRes.error || "Erro ao obter URL de upload");
|
|
}
|
|
|
|
await uploadFileToSignedUrl(
|
|
uploadRes.data.upload_url,
|
|
professionalData.avatar
|
|
);
|
|
|
|
avatarUrl = uploadRes.data.public_url;
|
|
console.log("Upload concluído. URL:", avatarUrl);
|
|
} catch (err) {
|
|
console.error("Erro no upload do avatar:", err);
|
|
throw new Error(
|
|
"Falha ao enviar foto de perfil: " +
|
|
(err instanceof Error ? err.message : "Erro desconhecido")
|
|
);
|
|
}
|
|
```
|
|
|
|
**Indicadores Visuais:**
|
|
- Label com asterisco vermelho: "Foto de Perfil *"
|
|
- Preview circular da foto com borda destacada (#B9CF33)
|
|
- Botão de remoção (X) quando foto está carregada
|
|
- Placeholder visual quando não há foto
|