Lint errors
Signed-off-by: Rodrigo Martins <rodrigo.sousa.577@gmail.com>
This commit is contained in:
1 parent
9742d62ba2
commit
5acec6b71e
196 files changed
+10885
-6655
No files matched your search
@@ -1,10 +1,11 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { api } from '../../lib/api';
|
||||
import { toast } from 'react-toastify';
|
||||
import { Dialog } from '@headlessui/react';
|
||||
import { DocumentArrowUpIcon } from '@heroicons/react/24/outline';
|
||||
import { Editor } from '@tinymce/tinymce-react';
|
||||
import { DOCUMENT_VARIABLES } from '../../utils/documentVariables';
|
||||
import { Dialog } from "@headlessui/react";
|
||||
import { DocumentArrowUpIcon } from "@heroicons/react/24/outline";
|
||||
import { Editor } from "@tinymce/tinymce-react";
|
||||
import { useState, useEffect } from "react";
|
||||
import { toast } from "react-toastify";
|
||||
|
||||
import { api } from "../../lib/api";
|
||||
import { DOCUMENT_VARIABLES } from "../../utils/documentVariables";
|
||||
|
||||
// Definir interface para o estado
|
||||
interface ConfigState {
|
||||
@@ -24,18 +25,18 @@ interface ConfigState {
|
||||
|
||||
export function ConfiguracaoDocumento() {
|
||||
const [config, setConfig] = useState<ConfigState>({
|
||||
timbradoUrl: '',
|
||||
timbradoUrl: "",
|
||||
timbradoMarginTop: 100,
|
||||
timbradoMarginBottom: 50,
|
||||
timbradoMarginLeft: 50,
|
||||
timbradoMarginRight: 50,
|
||||
templatePadrao: '',
|
||||
templatePadrao: "",
|
||||
margensTemplate: {
|
||||
top: 35,
|
||||
bottom: 29,
|
||||
left: 25,
|
||||
right: 25
|
||||
}
|
||||
right: 25,
|
||||
},
|
||||
});
|
||||
|
||||
const [isPreviewOpen, setPreviewOpen] = useState(false);
|
||||
@@ -47,27 +48,27 @@ export function ConfiguracaoDocumento() {
|
||||
|
||||
const loadConfig = async () => {
|
||||
try {
|
||||
const response = await api.get('/documentos/configuracoes');
|
||||
console.log('Configurações carregadas:', response.data); // Debug
|
||||
const response = await api.get("/documentos/configuracoes");
|
||||
console.log("Configurações carregadas:", response.data); // Debug
|
||||
if (response.data) {
|
||||
setConfig({
|
||||
timbradoUrl: response.data.timbradoUrl || '',
|
||||
timbradoUrl: response.data.timbradoUrl || "",
|
||||
timbradoMarginTop: response.data.timbradoMarginTop || 100,
|
||||
timbradoMarginBottom: response.data.timbradoMarginBottom || 50,
|
||||
timbradoMarginLeft: response.data.timbradoMarginLeft || 50,
|
||||
timbradoMarginRight: response.data.timbradoMarginRight || 50,
|
||||
templatePadrao: response.data.templatePadrao || '',
|
||||
templatePadrao: response.data.templatePadrao || "",
|
||||
margensTemplate: {
|
||||
top: response.data.margensTemplate?.top || 35,
|
||||
bottom: response.data.margensTemplate?.bottom || 29,
|
||||
left: response.data.margensTemplate?.left || 25,
|
||||
right: response.data.margensTemplate?.right || 25
|
||||
}
|
||||
right: response.data.margensTemplate?.right || 25,
|
||||
},
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Erro ao carregar configurações:', error);
|
||||
toast.error('Erro ao carregar configurações');
|
||||
console.error("Erro ao carregar configurações:", error);
|
||||
toast.error("Erro ao carregar configurações");
|
||||
}
|
||||
};
|
||||
|
||||
@@ -76,53 +77,57 @@ export function ConfiguracaoDocumento() {
|
||||
if (!file) return;
|
||||
|
||||
// Atualizado para aceitar PDF
|
||||
if (!['image/png', 'image/svg+xml', 'application/pdf'].includes(file.type)) {
|
||||
toast.error('Por favor, use apenas arquivos PDF, PNG ou SVG');
|
||||
if (
|
||||
!["image/png", "image/svg+xml", "application/pdf"].includes(file.type)
|
||||
) {
|
||||
toast.error("Por favor, use apenas arquivos PDF, PNG ou SVG");
|
||||
return;
|
||||
}
|
||||
|
||||
if (file.size > 5 * 1024 * 1024) { // Aumentado para 5MB para acomodar PDFs
|
||||
toast.error('O arquivo deve ter no máximo 5MB');
|
||||
if (file.size > 5 * 1024 * 1024) {
|
||||
// Aumentado para 5MB para acomodar PDFs
|
||||
toast.error("O arquivo deve ter no máximo 5MB");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const formData = new FormData();
|
||||
formData.append('timbrado', file);
|
||||
formData.append("timbrado", file);
|
||||
|
||||
const response = await api.post('/documentos/upload-timbrado', formData);
|
||||
setConfig(prev => ({ ...prev, timbradoUrl: response.data.url }));
|
||||
toast.success('Timbrado atualizado com sucesso');
|
||||
const response = await api.post("/documentos/upload-timbrado", formData);
|
||||
setConfig((prev) => ({ ...prev, timbradoUrl: response.data.url }));
|
||||
toast.success("Timbrado atualizado com sucesso");
|
||||
} catch (error) {
|
||||
toast.error('Erro ao fazer upload do timbrado');
|
||||
toast.error("Erro ao fazer upload do timbrado");
|
||||
}
|
||||
};
|
||||
|
||||
const handleSave = async () => {
|
||||
try {
|
||||
setIsSaving(true);
|
||||
await api.post('/documentos/configuracoes', config);
|
||||
toast.success('Configurações salvas com sucesso');
|
||||
await api.post("/documentos/configuracoes", config);
|
||||
toast.success("Configurações salvas com sucesso");
|
||||
} catch (error) {
|
||||
toast.error('Erro ao salvar configurações');
|
||||
toast.error("Erro ao salvar configurações");
|
||||
} finally {
|
||||
setIsSaving(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleEditorSetup = (editor: any) => {
|
||||
editor.ui.registry.addMenuButton('variables', {
|
||||
text: 'Variáveis',
|
||||
editor.ui.registry.addMenuButton("variables", {
|
||||
text: "Variáveis",
|
||||
fetch: (callback: Function) => {
|
||||
const items = Object.entries(DOCUMENT_VARIABLES).flatMap(([category, vars]) =>
|
||||
Object.entries(vars).map(([name, value]) => ({
|
||||
type: 'menuitem',
|
||||
text: `${category} - ${name.replace(/_/g, ' ')}`,
|
||||
onAction: () => editor.insertContent(value as string)
|
||||
}))
|
||||
const items = Object.entries(DOCUMENT_VARIABLES).flatMap(
|
||||
([category, vars]) =>
|
||||
Object.entries(vars).map(([name, value]) => ({
|
||||
type: "menuitem",
|
||||
text: `${category} - ${name.replace(/_/g, " ")}`,
|
||||
onAction: () => editor.insertContent(value as string),
|
||||
})),
|
||||
);
|
||||
callback(items);
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@@ -131,12 +136,14 @@ export function ConfiguracaoDocumento() {
|
||||
height: 400,
|
||||
menubar: true,
|
||||
plugins: [
|
||||
'code', 'preview' // Removido 'template'
|
||||
"code",
|
||||
"preview", // Removido 'template'
|
||||
],
|
||||
toolbar: 'variables | undo redo | formatselect | ' +
|
||||
'bold italic | alignleft aligncenter alignright | ' +
|
||||
'code preview',
|
||||
setup: handleEditorSetup
|
||||
toolbar:
|
||||
"variables | undo redo | formatselect | " +
|
||||
"bold italic | alignleft aligncenter alignright | " +
|
||||
"code preview",
|
||||
setup: handleEditorSetup,
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -179,7 +186,12 @@ export function ConfiguracaoDocumento() {
|
||||
<input
|
||||
type="number"
|
||||
value={config.timbradoMarginTop}
|
||||
onChange={e => setConfig(prev => ({ ...prev, timbradoMarginTop: Number(e.target.value) }))}
|
||||
onChange={(e) =>
|
||||
setConfig((prev) => ({
|
||||
...prev,
|
||||
timbradoMarginTop: Number(e.target.value),
|
||||
}))
|
||||
}
|
||||
className="w-full p-2 border rounded"
|
||||
/>
|
||||
</div>
|
||||
@@ -188,7 +200,12 @@ export function ConfiguracaoDocumento() {
|
||||
<input
|
||||
type="number"
|
||||
value={config.timbradoMarginBottom}
|
||||
onChange={e => setConfig(prev => ({ ...prev, timbradoMarginBottom: Number(e.target.value) }))}
|
||||
onChange={(e) =>
|
||||
setConfig((prev) => ({
|
||||
...prev,
|
||||
timbradoMarginBottom: Number(e.target.value),
|
||||
}))
|
||||
}
|
||||
className="w-full p-2 border rounded"
|
||||
/>
|
||||
</div>
|
||||
@@ -197,7 +214,12 @@ export function ConfiguracaoDocumento() {
|
||||
<input
|
||||
type="number"
|
||||
value={config.timbradoMarginLeft}
|
||||
onChange={e => setConfig(prev => ({ ...prev, timbradoMarginLeft: Number(e.target.value) }))}
|
||||
onChange={(e) =>
|
||||
setConfig((prev) => ({
|
||||
...prev,
|
||||
timbradoMarginLeft: Number(e.target.value),
|
||||
}))
|
||||
}
|
||||
className="w-full p-2 border rounded"
|
||||
/>
|
||||
</div>
|
||||
@@ -206,7 +228,12 @@ export function ConfiguracaoDocumento() {
|
||||
<input
|
||||
type="number"
|
||||
value={config.timbradoMarginRight}
|
||||
onChange={e => setConfig(prev => ({ ...prev, timbradoMarginRight: Number(e.target.value) }))}
|
||||
onChange={(e) =>
|
||||
setConfig((prev) => ({
|
||||
...prev,
|
||||
timbradoMarginRight: Number(e.target.value),
|
||||
}))
|
||||
}
|
||||
className="w-full p-2 border rounded"
|
||||
/>
|
||||
</div>
|
||||
@@ -218,7 +245,9 @@ export function ConfiguracaoDocumento() {
|
||||
<Editor
|
||||
apiKey={import.meta.env.VITE_TINYMCE_API_KEY}
|
||||
value={config.templatePadrao}
|
||||
onEditorChange={(content) => setConfig(prev => ({ ...prev, templatePadrao: content }))}
|
||||
onEditorChange={(content) =>
|
||||
setConfig((prev) => ({ ...prev, templatePadrao: content }))
|
||||
}
|
||||
init={editorConfig}
|
||||
/>
|
||||
<div className="mt-2 p-4 bg-gray-50 rounded border">
|
||||
@@ -226,7 +255,9 @@ export function ConfiguracaoDocumento() {
|
||||
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4">
|
||||
{Object.entries(DOCUMENT_VARIABLES).map(([category, vars]) => (
|
||||
<div key={category}>
|
||||
<h5 className="font-medium text-sm text-gray-700">{category}</h5>
|
||||
<h5 className="font-medium text-sm text-gray-700">
|
||||
{category}
|
||||
</h5>
|
||||
<ul className="mt-1 space-y-1">
|
||||
{Object.entries(vars).map(([name, value]) => (
|
||||
<li key={name} className="text-xs text-gray-600">
|
||||
@@ -247,13 +278,15 @@ export function ConfiguracaoDocumento() {
|
||||
<input
|
||||
type="number"
|
||||
value={config.margensTemplate.top}
|
||||
onChange={e => setConfig(prev => ({
|
||||
...prev,
|
||||
margensTemplate: {
|
||||
...prev.margensTemplate,
|
||||
top: Number(e.target.value)
|
||||
}
|
||||
}))}
|
||||
onChange={(e) =>
|
||||
setConfig((prev) => ({
|
||||
...prev,
|
||||
margensTemplate: {
|
||||
...prev.margensTemplate,
|
||||
top: Number(e.target.value),
|
||||
},
|
||||
}))
|
||||
}
|
||||
className="w-full p-2 border rounded"
|
||||
/>
|
||||
</div>
|
||||
@@ -262,13 +295,15 @@ export function ConfiguracaoDocumento() {
|
||||
<input
|
||||
type="number"
|
||||
value={config.margensTemplate.bottom}
|
||||
onChange={e => setConfig(prev => ({
|
||||
...prev,
|
||||
margensTemplate: {
|
||||
...prev.margensTemplate,
|
||||
bottom: Number(e.target.value)
|
||||
}
|
||||
}))}
|
||||
onChange={(e) =>
|
||||
setConfig((prev) => ({
|
||||
...prev,
|
||||
margensTemplate: {
|
||||
...prev.margensTemplate,
|
||||
bottom: Number(e.target.value),
|
||||
},
|
||||
}))
|
||||
}
|
||||
className="w-full p-2 border rounded"
|
||||
/>
|
||||
</div>
|
||||
@@ -277,13 +312,15 @@ export function ConfiguracaoDocumento() {
|
||||
<input
|
||||
type="number"
|
||||
value={config.margensTemplate.left}
|
||||
onChange={e => setConfig(prev => ({
|
||||
...prev,
|
||||
margensTemplate: {
|
||||
...prev.margensTemplate,
|
||||
left: Number(e.target.value)
|
||||
}
|
||||
}))}
|
||||
onChange={(e) =>
|
||||
setConfig((prev) => ({
|
||||
...prev,
|
||||
margensTemplate: {
|
||||
...prev.margensTemplate,
|
||||
left: Number(e.target.value),
|
||||
},
|
||||
}))
|
||||
}
|
||||
className="w-full p-2 border rounded"
|
||||
/>
|
||||
</div>
|
||||
@@ -292,13 +329,15 @@ export function ConfiguracaoDocumento() {
|
||||
<input
|
||||
type="number"
|
||||
value={config.margensTemplate.right}
|
||||
onChange={e => setConfig(prev => ({
|
||||
...prev,
|
||||
margensTemplate: {
|
||||
...prev.margensTemplate,
|
||||
right: Number(e.target.value)
|
||||
}
|
||||
}))}
|
||||
onChange={(e) =>
|
||||
setConfig((prev) => ({
|
||||
...prev,
|
||||
margensTemplate: {
|
||||
...prev.margensTemplate,
|
||||
right: Number(e.target.value),
|
||||
},
|
||||
}))
|
||||
}
|
||||
className="w-full p-2 border rounded"
|
||||
/>
|
||||
</div>
|
||||
@@ -309,11 +348,11 @@ export function ConfiguracaoDocumento() {
|
||||
disabled={isSaving}
|
||||
className={`px-4 py-2 ${
|
||||
isSaving
|
||||
? 'bg-gray-400 cursor-not-allowed'
|
||||
: 'bg-blue-600 hover:bg-blue-700'
|
||||
? "bg-gray-400 cursor-not-allowed"
|
||||
: "bg-blue-600 hover:bg-blue-700"
|
||||
} text-white rounded`}
|
||||
>
|
||||
{isSaving ? 'Salvando...' : 'Salvar Configurações'}
|
||||
{isSaving ? "Salvando..." : "Salvar Configurações"}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,11 +1,18 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { Editor } from '@tinymce/tinymce-react';
|
||||
import { Button, Dialog, DialogTitle, DialogContent, DialogActions } from '@mui/material';
|
||||
import { Save as SaveIcon } from '@mui/icons-material';
|
||||
import { XMarkIcon } from '@heroicons/react/24/outline';
|
||||
import { toast } from 'react-toastify'; // Adicionar esta importação
|
||||
import { api } from '../../lib/api'; // Adicionar esta importação
|
||||
import { DocumentoService } from '../../services/DocumentoService'; // Adicionar esta importação
|
||||
import { XMarkIcon } from "@heroicons/react/24/outline";
|
||||
import { Save as SaveIcon } from "@mui/icons-material";
|
||||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
DialogTitle,
|
||||
DialogContent,
|
||||
DialogActions,
|
||||
} from "@mui/material";
|
||||
import { Editor } from "@tinymce/tinymce-react";
|
||||
import { useState, useEffect } from "react";
|
||||
import { toast } from "react-toastify"; // Adicionar esta importação
|
||||
|
||||
import { api } from "../../lib/api"; // Adicionar esta importação
|
||||
import { DocumentoService } from "../../services/DocumentoService"; // Adicionar esta importação
|
||||
|
||||
interface DocumentoEditorProps {
|
||||
isOpen: boolean;
|
||||
@@ -27,24 +34,24 @@ interface TemplateVariables {
|
||||
const preencherTemplate = (template: string, variables: TemplateVariables) => {
|
||||
let result = template;
|
||||
Object.entries(variables).forEach(([key, value]) => {
|
||||
const regex = new RegExp(`{{${key}}}`, 'g');
|
||||
const regex = new RegExp(`{{${key}}}`, "g");
|
||||
result = result.replace(regex, value);
|
||||
});
|
||||
return result;
|
||||
};
|
||||
|
||||
export function DocumentoEditor({
|
||||
isOpen,
|
||||
onClose,
|
||||
onSave,
|
||||
export function DocumentoEditor({
|
||||
isOpen,
|
||||
onClose,
|
||||
onSave,
|
||||
initialData,
|
||||
processoId,
|
||||
atendimentoId,
|
||||
documento // Recuperar a prop
|
||||
documento, // Recuperar a prop
|
||||
}: DocumentoEditorProps) {
|
||||
const [content, setContent] = useState('');
|
||||
const [nome, setNome] = useState('');
|
||||
const [tipo, setTipo] = useState('OUTROS');
|
||||
const [content, setContent] = useState("");
|
||||
const [nome, setNome] = useState("");
|
||||
const [tipo, setTipo] = useState("OUTROS");
|
||||
const [selectedTemplate, setSelectedTemplate] = useState(null);
|
||||
const [selectedAtendimento, setSelectedAtendimento] = useState(null);
|
||||
const [atendimentos, setAtendimentos] = useState([]);
|
||||
@@ -54,9 +61,9 @@ export function DocumentoEditor({
|
||||
|
||||
useEffect(() => {
|
||||
if (initialData) {
|
||||
setContent(initialData.conteudo || '');
|
||||
setNome(initialData.nome || '');
|
||||
setTipo(initialData.tipo || 'OUTROS');
|
||||
setContent(initialData.conteudo || "");
|
||||
setNome(initialData.nome || "");
|
||||
setTipo(initialData.tipo || "OUTROS");
|
||||
}
|
||||
}, [initialData]);
|
||||
|
||||
@@ -64,13 +71,13 @@ export function DocumentoEditor({
|
||||
const loadData = async () => {
|
||||
try {
|
||||
const [atendimentosRes, templatesRes] = await Promise.all([
|
||||
api.get('/atendimentos'),
|
||||
api.get('/documentos-modelos')
|
||||
api.get("/atendimentos"),
|
||||
api.get("/documentos-modelos"),
|
||||
]);
|
||||
setAtendimentos(atendimentosRes.data);
|
||||
setTemplates(templatesRes.data);
|
||||
} catch (error) {
|
||||
toast.error('Erro ao carregar dados');
|
||||
toast.error("Erro ao carregar dados");
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
@@ -80,36 +87,38 @@ export function DocumentoEditor({
|
||||
|
||||
useEffect(() => {
|
||||
if (documento) {
|
||||
setContent(documento.conteudo || '');
|
||||
setNome(documento.nome || '');
|
||||
setTipo(documento.tipo || 'OUTROS');
|
||||
setContent(documento.conteudo || "");
|
||||
setNome(documento.nome || "");
|
||||
setTipo(documento.tipo || "OUTROS");
|
||||
}
|
||||
}, [documento]);
|
||||
|
||||
const handleTemplateSelect = async (templateId) => {
|
||||
try {
|
||||
const template = templates.find(t => t.id === templateId);
|
||||
const template = templates.find((t) => t.id === templateId);
|
||||
if (!template) return;
|
||||
|
||||
const atendimento = atendimentos.find(a => a.id === selectedAtendimento);
|
||||
const atendimento = atendimentos.find(
|
||||
(a) => a.id === selectedAtendimento,
|
||||
);
|
||||
if (!atendimento) return;
|
||||
|
||||
// Buscar dados do atendimento com as relações necessárias
|
||||
const [atendimentoDetalhado, usuarioRes, configRes] = await Promise.all([
|
||||
api.get(`/atendimentos/${atendimento.id}`),
|
||||
api.get('/auth/me'),
|
||||
api.get('/documentos/configuracoes')
|
||||
api.get("/auth/me"),
|
||||
api.get("/documentos/configuracoes"),
|
||||
]);
|
||||
|
||||
// Formatar endereço completo e outros dados do cliente
|
||||
const cliente = atendimentoDetalhado.data.cliente;
|
||||
const enderecoCompleto = `${cliente.endereco}, ${cliente.numero}${cliente.complemento ? `, ${cliente.complemento}` : ''}, ${cliente.bairro}, ${cliente.cidade}/${cliente.estado}, CEP: ${cliente.cep}`;
|
||||
const enderecoCompleto = `${cliente.endereco}, ${cliente.numero}${cliente.complemento ? `, ${cliente.complemento}` : ""}, ${cliente.bairro}, ${cliente.cidade}/${cliente.estado}, CEP: ${cliente.cep}`;
|
||||
|
||||
// Formatar data extenso corretamente
|
||||
const formatadorData = new Intl.DateTimeFormat('pt-BR', {
|
||||
day: 'numeric',
|
||||
month: 'long',
|
||||
year: 'numeric'
|
||||
const formatadorData = new Intl.DateTimeFormat("pt-BR", {
|
||||
day: "numeric",
|
||||
month: "long",
|
||||
year: "numeric",
|
||||
});
|
||||
|
||||
// Preparar variáveis com dados formatados
|
||||
@@ -117,65 +126,73 @@ export function DocumentoEditor({
|
||||
cliente: {
|
||||
...cliente,
|
||||
enderecoCompleto,
|
||||
nome: cliente.nome || '',
|
||||
cpf: cliente.cpfCnpj || '', // Adicionar cpf como alias para cpfCnpj
|
||||
cpfCnpj: cliente.cpfCnpj || '', // Manter cpfCnpj também
|
||||
rg: cliente.rg || '',
|
||||
estadoCivil: cliente.estadoCivil || '',
|
||||
profissao: cliente.profissao || '',
|
||||
nacionalidade: cliente.nacionalidade || 'brasileiro(a)',
|
||||
nomePai: cliente.nomePai || '',
|
||||
nomeMae: cliente.nomeMae || ''
|
||||
nome: cliente.nome || "",
|
||||
cpf: cliente.cpfCnpj || "", // Adicionar cpf como alias para cpfCnpj
|
||||
cpfCnpj: cliente.cpfCnpj || "", // Manter cpfCnpj também
|
||||
rg: cliente.rg || "",
|
||||
estadoCivil: cliente.estadoCivil || "",
|
||||
profissao: cliente.profissao || "",
|
||||
nacionalidade: cliente.nacionalidade || "brasileiro(a)",
|
||||
nomePai: cliente.nomePai || "",
|
||||
nomeMae: cliente.nomeMae || "",
|
||||
},
|
||||
atendimento: atendimentoDetalhado.data,
|
||||
processo: atendimentoDetalhado.data.processo,
|
||||
advogado: {
|
||||
...usuarioRes.data,
|
||||
oabCompleta: `${usuarioRes.data.oab}/${usuarioRes.data.oabUF}`
|
||||
oabCompleta: `${usuarioRes.data.oab}/${usuarioRes.data.oabUF}`,
|
||||
},
|
||||
escritorio: configRes.data,
|
||||
data: {
|
||||
atual: new Date().toLocaleDateString('pt-BR'),
|
||||
atual: new Date().toLocaleDateString("pt-BR"),
|
||||
atualExtenso: formatadorData.format(new Date()), // Formatar a data corretamente
|
||||
dia: new Date().getDate().toString(),
|
||||
mes: (new Date().getMonth() + 1).toString(),
|
||||
mesExtenso: new Intl.DateTimeFormat('pt-BR', { month: 'long' }).format(new Date()),
|
||||
ano: new Date().getFullYear().toString()
|
||||
}
|
||||
mesExtenso: new Intl.DateTimeFormat("pt-BR", {
|
||||
month: "long",
|
||||
}).format(new Date()),
|
||||
ano: new Date().getFullYear().toString(),
|
||||
},
|
||||
};
|
||||
|
||||
// Teste para debug
|
||||
console.log('Data por extenso:', variables.data.atualExtenso);
|
||||
console.log("Data por extenso:", variables.data.atualExtenso);
|
||||
|
||||
// Preencher template
|
||||
const conteudoPreenchido = template.conteudo.replace(/\{\{([^}]+)\}\}/g, (match, path) => {
|
||||
try {
|
||||
const valor = path.split('.').reduce((obj, key) => obj?.[key], variables);
|
||||
return valor !== undefined ? valor : match;
|
||||
} catch (error) {
|
||||
console.warn(`Erro ao processar variável ${path}:`, error);
|
||||
return match;
|
||||
}
|
||||
});
|
||||
const conteudoPreenchido = template.conteudo.replace(
|
||||
/\{\{([^}]+)\}\}/g,
|
||||
(match, path) => {
|
||||
try {
|
||||
const valor = path
|
||||
.split(".")
|
||||
.reduce((obj, key) => obj?.[key], variables);
|
||||
return valor !== undefined ? valor : match;
|
||||
} catch (error) {
|
||||
console.warn(`Erro ao processar variável ${path}:`, error);
|
||||
return match;
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
setContent(conteudoPreenchido);
|
||||
setNome(`${template.nome} - ${cliente.nome}`);
|
||||
setTipo(template.tipo);
|
||||
|
||||
// Debug para verificar substituições
|
||||
console.log('Template original:', template.conteudo);
|
||||
console.log('Conteúdo processado:', conteudoPreenchido);
|
||||
|
||||
console.log("Template original:", template.conteudo);
|
||||
console.log("Conteúdo processado:", conteudoPreenchido);
|
||||
} catch (error) {
|
||||
console.error('Erro ao carregar template:', error);
|
||||
toast.error('Erro ao carregar template. Verifique se o atendimento possui um cliente vinculado.');
|
||||
console.error("Erro ao carregar template:", error);
|
||||
toast.error(
|
||||
"Erro ao carregar template. Verifique se o atendimento possui um cliente vinculado.",
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSave = async () => {
|
||||
// Verificação de atendimento apenas para novos documentos
|
||||
if (!documento && !selectedAtendimento) {
|
||||
toast.error('Selecione um atendimento');
|
||||
toast.error("Selecione um atendimento");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -186,12 +203,12 @@ export function DocumentoEditor({
|
||||
tipo,
|
||||
conteudo: content,
|
||||
atendimentoId: documento?.atendimentoId || selectedAtendimento,
|
||||
processoId: documento?.processoId || processoId
|
||||
processoId: documento?.processoId || processoId,
|
||||
});
|
||||
toast.success('Documento salvo com sucesso');
|
||||
toast.success("Documento salvo com sucesso");
|
||||
onClose();
|
||||
} catch (error) {
|
||||
toast.error('Erro ao salvar documento');
|
||||
toast.error("Erro ao salvar documento");
|
||||
} finally {
|
||||
setIsSaving(false);
|
||||
}
|
||||
@@ -203,12 +220,12 @@ export function DocumentoEditor({
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="flex gap-4">
|
||||
<select
|
||||
value={selectedAtendimento || ''}
|
||||
value={selectedAtendimento || ""}
|
||||
onChange={(e) => setSelectedAtendimento(e.target.value)}
|
||||
className="flex-1 p-2 border rounded"
|
||||
>
|
||||
<option value="">Selecione um atendimento</option>
|
||||
{atendimentos.map(atendimento => (
|
||||
{atendimentos.map((atendimento) => (
|
||||
<option key={atendimento.id} value={atendimento.id}>
|
||||
{atendimento.protocolo} - {atendimento.cliente.nome}
|
||||
</option>
|
||||
@@ -216,12 +233,12 @@ export function DocumentoEditor({
|
||||
</select>
|
||||
|
||||
<select
|
||||
value={selectedTemplate || ''}
|
||||
value={selectedTemplate || ""}
|
||||
onChange={(e) => handleTemplateSelect(e.target.value)}
|
||||
className="flex-1 p-2 border rounded"
|
||||
>
|
||||
<option value="">Selecione um template</option>
|
||||
{templates.map(template => (
|
||||
{templates.map((template) => (
|
||||
<option key={template.id} value={template.id}>
|
||||
{template.nome}
|
||||
</option>
|
||||
@@ -259,23 +276,39 @@ export function DocumentoEditor({
|
||||
height: 500,
|
||||
menubar: true,
|
||||
plugins: [
|
||||
'advlist', 'autolink', 'lists', 'link', 'image', 'charmap', 'preview',
|
||||
'anchor', 'searchreplace', 'visualblocks', 'code', 'fullscreen',
|
||||
'insertdatetime', 'media', 'table', 'help', 'wordcount'
|
||||
"advlist",
|
||||
"autolink",
|
||||
"lists",
|
||||
"link",
|
||||
"image",
|
||||
"charmap",
|
||||
"preview",
|
||||
"anchor",
|
||||
"searchreplace",
|
||||
"visualblocks",
|
||||
"code",
|
||||
"fullscreen",
|
||||
"insertdatetime",
|
||||
"media",
|
||||
"table",
|
||||
"help",
|
||||
"wordcount",
|
||||
],
|
||||
toolbar: 'undo redo | formatselect | ' +
|
||||
'bold italic backcolor | alignleft aligncenter ' +
|
||||
'alignright alignjustify | bullist numlist outdent indent | ' +
|
||||
'removeformat | help',
|
||||
language: 'pt_BR', // Adicionar configuração de idioma
|
||||
language_url: '/tinymce/langs/pt_BR.js', // Adicionar URL do arquivo de idioma
|
||||
content_style: 'body { font-family:Helvetica,Arial,sans-serif; font-size:14px }',
|
||||
toolbar:
|
||||
"undo redo | formatselect | " +
|
||||
"bold italic backcolor | alignleft aligncenter " +
|
||||
"alignright alignjustify | bullist numlist outdent indent | " +
|
||||
"removeformat | help",
|
||||
language: "pt_BR", // Adicionar configuração de idioma
|
||||
language_url: "/tinymce/langs/pt_BR.js", // Adicionar URL do arquivo de idioma
|
||||
content_style:
|
||||
"body { font-family:Helvetica,Arial,sans-serif; font-size:14px }",
|
||||
// Adicionar estas configurações
|
||||
promotion: false,
|
||||
branding: false,
|
||||
preview_styles: false,
|
||||
referrer_policy: 'origin',
|
||||
disable_stats: true // Desabilitar estatísticas
|
||||
referrer_policy: "origin",
|
||||
disable_stats: true, // Desabilitar estatísticas
|
||||
}}
|
||||
value={content}
|
||||
onEditorChange={setContent}
|
||||
@@ -286,7 +319,7 @@ export function DocumentoEditor({
|
||||
<Button onClick={onClose} startIcon={<XMarkIcon className="w-4 h-4" />}>
|
||||
Cancelar
|
||||
</Button>
|
||||
<Button
|
||||
<Button
|
||||
onClick={handleSave}
|
||||
disabled={isSaving || !nome}
|
||||
startIcon={<SaveIcon className="w-4 h-4" />}
|
||||
@@ -298,4 +331,3 @@ export function DocumentoEditor({
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
import { Dialog } from '@headlessui/react';
|
||||
import { XMarkIcon, PrinterIcon, DocumentArrowDownIcon } from '@heroicons/react/24/outline';
|
||||
import { api } from '../../lib/api';
|
||||
import { toast } from 'react-toastify';
|
||||
import { useState } from 'react';
|
||||
import { Dialog } from "@headlessui/react";
|
||||
import {
|
||||
XMarkIcon,
|
||||
PrinterIcon,
|
||||
DocumentArrowDownIcon,
|
||||
} from "@heroicons/react/24/outline";
|
||||
import { useState } from "react";
|
||||
import { toast } from "react-toastify";
|
||||
|
||||
import { api } from "../../lib/api";
|
||||
|
||||
interface DocumentoViewerProps {
|
||||
documento: any;
|
||||
@@ -10,18 +15,22 @@ interface DocumentoViewerProps {
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
export function DocumentoViewer({ documento, isOpen, onClose }: DocumentoViewerProps) {
|
||||
export function DocumentoViewer({
|
||||
documento,
|
||||
isOpen,
|
||||
onClose,
|
||||
}: DocumentoViewerProps) {
|
||||
const [isExporting, setIsExporting] = useState(false);
|
||||
|
||||
const handlePrint = () => {
|
||||
const printWindow = window.open('', '_blank');
|
||||
const printWindow = window.open("", "_blank");
|
||||
if (!printWindow) return;
|
||||
|
||||
const html = `
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>${documento?.nome || 'Documento'}</title>
|
||||
<title>${documento?.nome || "Documento"}</title>
|
||||
<style>
|
||||
@page {
|
||||
size: A4;
|
||||
@@ -88,16 +97,16 @@ export function DocumentoViewer({ documento, isOpen, onClose }: DocumentoViewerP
|
||||
</head>
|
||||
<body>
|
||||
<div class="header">
|
||||
<h2 style="margin:0;font-size:14pt">${documento?.escritorio?.nome || ''}</h2>
|
||||
<h2 style="margin:0;font-size:14pt">${documento?.escritorio?.nome || ""}</h2>
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
${documento?.conteudo || ''}
|
||||
${documento?.conteudo || ""}
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
${documento?.escritorio?.endereco || ''}<br>
|
||||
Tel: ${documento?.escritorio?.telefone || ''} | Email: ${documento?.escritorio?.email || ''}
|
||||
${documento?.escritorio?.endereco || ""}<br>
|
||||
Tel: ${documento?.escritorio?.telefone || ""} | Email: ${documento?.escritorio?.email || ""}
|
||||
</div>
|
||||
|
||||
<script>
|
||||
@@ -117,51 +126,54 @@ export function DocumentoViewer({ documento, isOpen, onClose }: DocumentoViewerP
|
||||
const handleExportPDF = async () => {
|
||||
try {
|
||||
setIsExporting(true);
|
||||
toast.info('Gerando PDF, aguarde...', { autoClose: false, toastId: 'generating-pdf' });
|
||||
|
||||
// Buscar configurações atualizadas do escritório
|
||||
const response = await api.post('/documentos/exportar-pdf', {
|
||||
conteudo: documento.conteudo,
|
||||
escritorio: documento.escritorio
|
||||
}, {
|
||||
responseType: 'blob',
|
||||
headers: {
|
||||
'Accept': 'application/pdf'
|
||||
}
|
||||
toast.info("Gerando PDF, aguarde...", {
|
||||
autoClose: false,
|
||||
toastId: "generating-pdf",
|
||||
});
|
||||
|
||||
// Buscar configurações atualizadas do escritório
|
||||
const response = await api.post(
|
||||
"/documentos/exportar-pdf",
|
||||
{
|
||||
conteudo: documento.conteudo,
|
||||
escritorio: documento.escritorio,
|
||||
},
|
||||
{
|
||||
responseType: "blob",
|
||||
headers: {
|
||||
Accept: "application/pdf",
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
// Criar e baixar o arquivo
|
||||
const blob = new Blob([response.data], { type: 'application/pdf' });
|
||||
const blob = new Blob([response.data], { type: "application/pdf" });
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
const link = document.createElement('a');
|
||||
const link = document.createElement("a");
|
||||
link.href = url;
|
||||
link.setAttribute('download', `${documento.nome || 'documento'}.pdf`);
|
||||
link.setAttribute("download", `${documento.nome || "documento"}.pdf`);
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
|
||||
|
||||
// Limpeza
|
||||
window.URL.revokeObjectURL(url);
|
||||
document.body.removeChild(link);
|
||||
|
||||
toast.dismiss('generating-pdf');
|
||||
toast.success('PDF gerado com sucesso!');
|
||||
|
||||
toast.dismiss("generating-pdf");
|
||||
toast.success("PDF gerado com sucesso!");
|
||||
} catch (error) {
|
||||
console.error('Erro ao gerar PDF:', error);
|
||||
toast.dismiss('generating-pdf');
|
||||
toast.error('Erro ao gerar o PDF');
|
||||
console.error("Erro ao gerar PDF:", error);
|
||||
toast.dismiss("generating-pdf");
|
||||
toast.error("Erro ao gerar o PDF");
|
||||
} finally {
|
||||
setIsExporting(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
open={isOpen}
|
||||
onClose={onClose}
|
||||
className="relative z-50"
|
||||
>
|
||||
<Dialog open={isOpen} onClose={onClose} className="relative z-50">
|
||||
<div className="fixed inset-0 bg-black/30" aria-hidden="true" />
|
||||
|
||||
|
||||
<div className="fixed inset-0 flex items-center justify-center p-4">
|
||||
<Dialog.Panel className="w-full max-w-4xl bg-white rounded-lg shadow-xl">
|
||||
<div className="flex items-center justify-between p-4 border-b">
|
||||
@@ -173,16 +185,32 @@ export function DocumentoViewer({ documento, isOpen, onClose }: DocumentoViewerP
|
||||
onClick={handleExportPDF}
|
||||
disabled={isExporting}
|
||||
className={`inline-flex items-center px-3 py-2 ${
|
||||
isExporting
|
||||
? 'bg-blue-400 cursor-not-allowed'
|
||||
: 'bg-blue-600 hover:bg-blue-700'
|
||||
isExporting
|
||||
? "bg-blue-400 cursor-not-allowed"
|
||||
: "bg-blue-600 hover:bg-blue-700"
|
||||
} text-white rounded`}
|
||||
>
|
||||
{isExporting ? (
|
||||
<>
|
||||
<svg className="animate-spin -ml-1 mr-3 h-5 w-5 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
||||
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
|
||||
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
||||
<svg
|
||||
className="animate-spin -ml-1 mr-3 h-5 w-5 text-white"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<circle
|
||||
className="opacity-25"
|
||||
cx="12"
|
||||
cy="12"
|
||||
r="10"
|
||||
stroke="currentColor"
|
||||
strokeWidth="4"
|
||||
></circle>
|
||||
<path
|
||||
className="opacity-75"
|
||||
fill="currentColor"
|
||||
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
||||
></path>
|
||||
</svg>
|
||||
Gerando PDF...
|
||||
</>
|
||||
@@ -208,18 +236,20 @@ export function DocumentoViewer({ documento, isOpen, onClose }: DocumentoViewerP
|
||||
<div className="max-w-[21cm] mx-auto bg-white shadow-lg p-8">
|
||||
<div className="text-center mb-8 pb-4 border-b">
|
||||
<h2 className="text-xl font-bold">
|
||||
{documento?.escritorio?.nome || 'Nome do Escritório'}
|
||||
{documento?.escritorio?.nome || "Nome do Escritório"}
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<div
|
||||
|
||||
<div
|
||||
className="prose max-w-none"
|
||||
dangerouslySetInnerHTML={{ __html: documento?.conteudo }}
|
||||
/>
|
||||
|
||||
<div className="text-center mt-8 pt-4 border-t text-sm text-gray-600">
|
||||
{documento?.escritorio?.endereco}<br/>
|
||||
{documento?.escritorio?.telefone} | {documento?.escritorio?.email}
|
||||
{documento?.escritorio?.endereco}
|
||||
<br />
|
||||
{documento?.escritorio?.telefone} |{" "}
|
||||
{documento?.escritorio?.email}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,13 +1,21 @@
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
import { useParams, useNavigate, Link } from 'react-router-dom';
|
||||
import { DocumentIcon, PlusIcon, TrashIcon, EyeIcon, CogIcon } from '@heroicons/react/24/outline';
|
||||
import { DocumentoEditor } from './DocumentoEditor';
|
||||
import { api } from '../lib/api';
|
||||
import { toast } from 'react-toastify';
|
||||
import { useAuth } from '../contexts/AuthContext';
|
||||
import { Dialog } from '@headlessui/react';
|
||||
import { XMarkIcon } from '@heroicons/react/24/outline';
|
||||
import { DocumentoViewer } from './DocumentoViewer';
|
||||
import { Dialog } from "@headlessui/react";
|
||||
import {
|
||||
DocumentIcon,
|
||||
PlusIcon,
|
||||
TrashIcon,
|
||||
EyeIcon,
|
||||
CogIcon,
|
||||
XMarkIcon,
|
||||
} from "@heroicons/react/24/outline";
|
||||
import { useState, useEffect, useCallback } from "react";
|
||||
import { useParams, useNavigate, Link } from "react-router-dom";
|
||||
import { toast } from "react-toastify";
|
||||
|
||||
import { useAuth } from "../contexts/AuthContext";
|
||||
import { api } from "../lib/api";
|
||||
|
||||
import { DocumentoEditor } from "./DocumentoEditor";
|
||||
import { DocumentoViewer } from "./DocumentoViewer";
|
||||
|
||||
export function DocumentosPage() {
|
||||
const [isEditorOpen, setIsEditorOpen] = useState(false);
|
||||
@@ -23,7 +31,7 @@ export function DocumentosPage() {
|
||||
|
||||
const loadData = useCallback(async () => {
|
||||
if (!user) {
|
||||
navigate('/login');
|
||||
navigate("/login");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -31,12 +39,12 @@ export function DocumentosPage() {
|
||||
setIsLoading(true);
|
||||
await Promise.all([loadDocumentos(), loadModelos()]);
|
||||
} catch (error) {
|
||||
console.error('Erro ao carregar dados:', error);
|
||||
console.error("Erro ao carregar dados:", error);
|
||||
if (error?.response?.status === 401) {
|
||||
signOut(); // Usar a função do AuthContext
|
||||
navigate('/login');
|
||||
navigate("/login");
|
||||
} else {
|
||||
toast.error('Erro ao carregar dados');
|
||||
toast.error("Erro ao carregar dados");
|
||||
}
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
@@ -52,12 +60,12 @@ export function DocumentosPage() {
|
||||
if (processoId) params.processoId = processoId;
|
||||
if (atendimentoId) params.atendimentoId = atendimentoId;
|
||||
|
||||
const response = await api.get('/documentos', { params });
|
||||
const response = await api.get("/documentos", { params });
|
||||
setDocumentos(response.data);
|
||||
};
|
||||
|
||||
const loadModelos = async () => {
|
||||
const response = await api.get('/documentos-modelos');
|
||||
const response = await api.get("/documentos-modelos");
|
||||
setModelos(response.data);
|
||||
};
|
||||
|
||||
@@ -66,16 +74,16 @@ export function DocumentosPage() {
|
||||
if (processoId) data.processoId = processoId;
|
||||
if (atendimentoId) data.atendimentoId = atendimentoId;
|
||||
|
||||
await api.post('/documentos', data);
|
||||
toast.success('Documento salvo com sucesso');
|
||||
await api.post("/documentos", data);
|
||||
toast.success("Documento salvo com sucesso");
|
||||
await loadDocumentos();
|
||||
setIsEditorOpen(false);
|
||||
} catch (error) {
|
||||
console.error('Erro ao salvar documento:', error);
|
||||
console.error("Erro ao salvar documento:", error);
|
||||
if (error?.response?.status === 401) {
|
||||
toast.error('Sessão expirada. Por favor, faça login novamente.');
|
||||
toast.error("Sessão expirada. Por favor, faça login novamente.");
|
||||
} else {
|
||||
toast.error('Erro ao salvar documento');
|
||||
toast.error("Erro ao salvar documento");
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -83,30 +91,30 @@ export function DocumentosPage() {
|
||||
const handleDeleteDocumento = async (id) => {
|
||||
try {
|
||||
await api.delete(`/documentos/${id}`);
|
||||
toast.success('Documento excluído com sucesso');
|
||||
toast.success("Documento excluído com sucesso");
|
||||
await loadDocumentos();
|
||||
} catch (error) {
|
||||
console.error('Erro ao excluir documento:', error);
|
||||
console.error("Erro ao excluir documento:", error);
|
||||
if (error?.response?.status === 401) {
|
||||
toast.error('Sessão expirada. Por favor, faça login novamente.');
|
||||
toast.error("Sessão expirada. Por favor, faça login novamente.");
|
||||
} else {
|
||||
toast.error('Erro ao excluir documento');
|
||||
toast.error("Erro ao excluir documento");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleVisualizar = async (documento) => {
|
||||
try {
|
||||
const configRes = await api.get('/documentos/configuracoes');
|
||||
|
||||
const configRes = await api.get("/documentos/configuracoes");
|
||||
|
||||
setDocumentoVisualizado({
|
||||
...documento,
|
||||
escritorio: configRes.data
|
||||
escritorio: configRes.data,
|
||||
});
|
||||
setVisualizadorOpen(true);
|
||||
} catch (error) {
|
||||
console.error('Erro ao visualizar documento:', error);
|
||||
toast.error('Erro ao visualizar documento');
|
||||
console.error("Erro ao visualizar documento:", error);
|
||||
toast.error("Erro ao visualizar documento");
|
||||
}
|
||||
};
|
||||
|
||||
@@ -126,17 +134,17 @@ export function DocumentosPage() {
|
||||
<PlusIcon className="w-5 h-5 mr-2" />
|
||||
Novo Documento
|
||||
</button>
|
||||
{user?.perfil === 'ADVOGADO_ADMIN' && (
|
||||
{user?.perfil === "ADVOGADO_ADMIN" && (
|
||||
<>
|
||||
<button
|
||||
onClick={() => navigate('/documentos/templates')}
|
||||
onClick={() => navigate("/documentos/templates")}
|
||||
className="inline-flex items-center px-4 py-2 border border-blue-600 text-blue-600 rounded-md hover:bg-blue-50 transition-colors"
|
||||
>
|
||||
<DocumentIcon className="w-5 h-5 mr-2" />
|
||||
Gerenciar Templates
|
||||
</button>
|
||||
<button
|
||||
onClick={() => navigate('/documentos/configuracoes')}
|
||||
onClick={() => navigate("/documentos/configuracoes")}
|
||||
className="inline-flex items-center px-4 py-2 border border-blue-600 text-blue-600 rounded-md hover:bg-blue-50 transition-colors"
|
||||
>
|
||||
<CogIcon className="w-5 h-5 mr-2" />
|
||||
@@ -153,8 +161,8 @@ export function DocumentosPage() {
|
||||
onClick={() => setTabValue(0)}
|
||||
className={`px-4 py-2 font-medium text-sm border-b-2 transition-colors duration-200 ${
|
||||
tabValue === 0
|
||||
? 'border-blue-500 text-blue-600'
|
||||
: 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300'
|
||||
? "border-blue-500 text-blue-600"
|
||||
: "border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300"
|
||||
}`}
|
||||
>
|
||||
Documentos
|
||||
@@ -163,8 +171,8 @@ export function DocumentosPage() {
|
||||
onClick={() => setTabValue(1)}
|
||||
className={`ml-8 px-4 py-2 font-medium text-sm border-b-2 transition-colors duration-200 ${
|
||||
tabValue === 1
|
||||
? 'border-blue-500 text-blue-600'
|
||||
: 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300'
|
||||
? "border-blue-500 text-blue-600"
|
||||
: "border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300"
|
||||
}`}
|
||||
>
|
||||
Modelos
|
||||
@@ -175,11 +183,16 @@ export function DocumentosPage() {
|
||||
{tabValue === 0 && (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{documentos.map((doc) => (
|
||||
<div key={doc.id} className="bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 overflow-hidden">
|
||||
<div
|
||||
key={doc.id}
|
||||
className="bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 overflow-hidden"
|
||||
>
|
||||
<div className="p-4">
|
||||
<div className="flex items-center mb-3">
|
||||
<DocumentIcon className="w-6 h-6 text-gray-500 mr-2" />
|
||||
<h3 className="font-semibold text-gray-900 dark:text-gray-100">{doc.nome}</h3>
|
||||
<h3 className="font-semibold text-gray-900 dark:text-gray-100">
|
||||
{doc.nome}
|
||||
</h3>
|
||||
</div>
|
||||
<p className="text-sm text-gray-600 dark:text-gray-400">
|
||||
Criado em: {new Date(doc.criadoEm).toLocaleDateString()}
|
||||
@@ -209,16 +222,21 @@ export function DocumentosPage() {
|
||||
{tabValue === 1 && (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{modelos.map((modelo) => (
|
||||
<div key={modelo.id} className="bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 overflow-hidden">
|
||||
<div
|
||||
key={modelo.id}
|
||||
className="bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 overflow-hidden"
|
||||
>
|
||||
<div className="p-4">
|
||||
<div className="flex items-center mb-3">
|
||||
<DocumentIcon className="w-6 h-6 text-gray-500 mr-2" />
|
||||
<h3 className="font-semibold text-gray-900 dark:text-gray-100">{modelo.nome}</h3>
|
||||
<h3 className="font-semibold text-gray-900 dark:text-gray-100">
|
||||
{modelo.nome}
|
||||
</h3>
|
||||
</div>
|
||||
<p className="text-sm text-gray-600 dark:text-gray-400 mb-4">{modelo.descricao}</p>
|
||||
<button
|
||||
className="w-full inline-flex items-center justify-center px-4 py-2 text-sm border border-blue-600 text-blue-600 rounded-md hover:bg-blue-50 dark:hover:bg-blue-900/20"
|
||||
>
|
||||
<p className="text-sm text-gray-600 dark:text-gray-400 mb-4">
|
||||
{modelo.descricao}
|
||||
</p>
|
||||
<button className="w-full inline-flex items-center justify-center px-4 py-2 text-sm border border-blue-600 text-blue-600 rounded-md hover:bg-blue-50 dark:hover:bg-blue-900/20">
|
||||
Usar Modelo
|
||||
</button>
|
||||
</div>
|
||||
@@ -242,4 +260,4 @@ export function DocumentosPage() {
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,16 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { api } from '../../lib/api';
|
||||
import { Editor } from '@tinymce/tinymce-react';
|
||||
import { toast } from 'react-toastify';
|
||||
import { Dialog } from '@headlessui/react';
|
||||
import { DocumentIcon, PencilIcon, TrashIcon, PlusIcon } from '@heroicons/react/24/outline';
|
||||
import { DOCUMENT_VARIABLES } from '../../utils/documentVariables';
|
||||
import { Dialog } from "@headlessui/react";
|
||||
import {
|
||||
DocumentIcon,
|
||||
PencilIcon,
|
||||
TrashIcon,
|
||||
PlusIcon,
|
||||
} from "@heroicons/react/24/outline";
|
||||
import { Editor } from "@tinymce/tinymce-react";
|
||||
import { useState, useEffect } from "react";
|
||||
import { toast } from "react-toastify";
|
||||
|
||||
import { api } from "../../lib/api";
|
||||
import { DOCUMENT_VARIABLES } from "../../utils/documentVariables";
|
||||
|
||||
export function GerenciarTemplates() {
|
||||
const [templates, setTemplates] = useState([]);
|
||||
@@ -12,19 +18,19 @@ export function GerenciarTemplates() {
|
||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||
const [isEditorModalOpen, setEditorModalOpen] = useState(false);
|
||||
const [formData, setFormData] = useState({
|
||||
nome: '',
|
||||
tipo: 'OUTROS',
|
||||
descricao: '',
|
||||
conteudo: '',
|
||||
variaveis: []
|
||||
nome: "",
|
||||
tipo: "OUTROS",
|
||||
descricao: "",
|
||||
conteudo: "",
|
||||
variaveis: [],
|
||||
});
|
||||
|
||||
const loadTemplates = async () => {
|
||||
try {
|
||||
const response = await api.get('/documentos-modelos');
|
||||
const response = await api.get("/documentos-modelos");
|
||||
setTemplates(response.data);
|
||||
} catch (error) {
|
||||
toast.error('Erro ao carregar templates');
|
||||
toast.error("Erro ao carregar templates");
|
||||
}
|
||||
};
|
||||
|
||||
@@ -36,15 +42,15 @@ export function GerenciarTemplates() {
|
||||
try {
|
||||
if (editingTemplate) {
|
||||
await api.put(`/documentos-modelos/${editingTemplate.id}`, data);
|
||||
toast.success('Template atualizado com sucesso');
|
||||
toast.success("Template atualizado com sucesso");
|
||||
} else {
|
||||
await api.post('/documentos-modelos', data);
|
||||
toast.success('Template criado com sucesso');
|
||||
await api.post("/documentos-modelos", data);
|
||||
toast.success("Template criado com sucesso");
|
||||
}
|
||||
loadTemplates();
|
||||
setIsModalOpen(false);
|
||||
} catch (error) {
|
||||
toast.error('Erro ao salvar template');
|
||||
toast.error("Erro ao salvar template");
|
||||
}
|
||||
};
|
||||
|
||||
@@ -55,35 +61,35 @@ export function GerenciarTemplates() {
|
||||
if (editingTemplate) {
|
||||
await api.put(`/documentos-modelos/${editingTemplate.id}`, {
|
||||
...formData,
|
||||
id: editingTemplate.id // Garantir que o ID seja enviado
|
||||
id: editingTemplate.id, // Garantir que o ID seja enviado
|
||||
});
|
||||
toast.success('Template atualizado com sucesso');
|
||||
toast.success("Template atualizado com sucesso");
|
||||
} else {
|
||||
await api.post('/documentos-modelos', formData);
|
||||
toast.success('Template criado com sucesso');
|
||||
await api.post("/documentos-modelos", formData);
|
||||
toast.success("Template criado com sucesso");
|
||||
}
|
||||
|
||||
|
||||
setEditorModalOpen(false);
|
||||
setEditingTemplate(null);
|
||||
await loadTemplates(); // Recarregar a lista após salvar
|
||||
} catch (error) {
|
||||
console.error('Erro ao salvar template:', error);
|
||||
toast.error('Erro ao salvar template');
|
||||
console.error("Erro ao salvar template:", error);
|
||||
toast.error("Erro ao salvar template");
|
||||
}
|
||||
};
|
||||
|
||||
const handleDelete = async (templateId: string) => {
|
||||
if (!window.confirm('Tem certeza que deseja excluir este template?')) {
|
||||
if (!window.confirm("Tem certeza que deseja excluir este template?")) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await api.delete(`/documentos-modelos/${templateId}`);
|
||||
toast.success('Template excluído com sucesso');
|
||||
toast.success("Template excluído com sucesso");
|
||||
loadTemplates();
|
||||
} catch (error) {
|
||||
console.error('Erro ao excluir template:', error);
|
||||
toast.error('Erro ao excluir template');
|
||||
console.error("Erro ao excluir template:", error);
|
||||
toast.error("Erro ao excluir template");
|
||||
}
|
||||
};
|
||||
|
||||
@@ -98,7 +104,7 @@ export function GerenciarTemplates() {
|
||||
setEditingTemplate(template); // Guardar o template completo
|
||||
setFormData({
|
||||
...template,
|
||||
id: template.id // Garantir que o ID seja mantido
|
||||
id: template.id, // Garantir que o ID seja mantido
|
||||
});
|
||||
setEditorModalOpen(true);
|
||||
};
|
||||
@@ -110,11 +116,11 @@ export function GerenciarTemplates() {
|
||||
<button
|
||||
onClick={() => {
|
||||
setFormData({
|
||||
nome: '',
|
||||
tipo: 'OUTROS',
|
||||
descricao: '',
|
||||
conteudo: '',
|
||||
variaveis: []
|
||||
nome: "",
|
||||
tipo: "OUTROS",
|
||||
descricao: "",
|
||||
conteudo: "",
|
||||
variaveis: [],
|
||||
});
|
||||
setEditorModalOpen(true);
|
||||
}}
|
||||
@@ -127,12 +133,17 @@ export function GerenciarTemplates() {
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{templates.map((template) => (
|
||||
<div key={template.id} className="bg-white dark:bg-gray-800 rounded-lg shadow p-4">
|
||||
<div
|
||||
key={template.id}
|
||||
className="bg-white dark:bg-gray-800 rounded-lg shadow p-4"
|
||||
>
|
||||
<div className="flex items-center mb-3">
|
||||
<DocumentIcon className="w-6 h-6 text-gray-500 mr-2" />
|
||||
<h3 className="font-semibold">{template.nome}</h3>
|
||||
</div>
|
||||
<p className="text-sm text-gray-600 dark:text-gray-400 mb-4">{template.descricao}</p>
|
||||
<p className="text-sm text-gray-600 dark:text-gray-400 mb-4">
|
||||
{template.descricao}
|
||||
</p>
|
||||
<div className="flex gap-2">
|
||||
<button
|
||||
onClick={() => handleEdit(template)}
|
||||
@@ -154,23 +165,29 @@ export function GerenciarTemplates() {
|
||||
</div>
|
||||
|
||||
{/* Modal de Edição */}
|
||||
<Dialog open={isEditorModalOpen} onClose={() => setEditorModalOpen(false)} className="relative z-50">
|
||||
<Dialog
|
||||
open={isEditorModalOpen}
|
||||
onClose={() => setEditorModalOpen(false)}
|
||||
className="relative z-50"
|
||||
>
|
||||
<div className="fixed inset-0 bg-black/30" aria-hidden="true" />
|
||||
<div className="fixed inset-0 flex items-center justify-center p-4 overflow-y-auto">
|
||||
<Dialog.Panel className="w-full max-w-4xl bg-white rounded-lg shadow-xl my-4">
|
||||
<div className="max-h-[90vh] overflow-y-auto">
|
||||
<form onSubmit={handleSubmit} className="p-6">
|
||||
<h2 className="text-xl font-bold mb-4">
|
||||
{editingTemplate ? 'Editar Template' : 'Novo Template'}
|
||||
{editingTemplate ? "Editar Template" : "Novo Template"}
|
||||
</h2>
|
||||
|
||||
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<label className="block mb-1">Nome</label>
|
||||
<input
|
||||
type="text"
|
||||
value={formData.nome}
|
||||
onChange={(e) => setFormData({...formData, nome: e.target.value})}
|
||||
onChange={(e) =>
|
||||
setFormData({ ...formData, nome: e.target.value })
|
||||
}
|
||||
className="w-full p-2 border rounded"
|
||||
required
|
||||
/>
|
||||
@@ -180,7 +197,9 @@ export function GerenciarTemplates() {
|
||||
<label className="block mb-1">Tipo</label>
|
||||
<select
|
||||
value={formData.tipo}
|
||||
onChange={(e) => setFormData({...formData, tipo: e.target.value})}
|
||||
onChange={(e) =>
|
||||
setFormData({ ...formData, tipo: e.target.value })
|
||||
}
|
||||
className="w-full p-2 border rounded"
|
||||
>
|
||||
<option value="PETICAO">Petição</option>
|
||||
@@ -195,7 +214,9 @@ export function GerenciarTemplates() {
|
||||
<label className="block mb-1">Descrição</label>
|
||||
<textarea
|
||||
value={formData.descricao}
|
||||
onChange={(e) => setFormData({...formData, descricao: e.target.value})}
|
||||
onChange={(e) =>
|
||||
setFormData({ ...formData, descricao: e.target.value })
|
||||
}
|
||||
className="w-full p-2 border rounded"
|
||||
rows={3}
|
||||
/>
|
||||
@@ -204,21 +225,27 @@ export function GerenciarTemplates() {
|
||||
<div className="mb-4">
|
||||
<label className="block mb-1">Variáveis Disponíveis</label>
|
||||
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-2 p-2 border rounded bg-gray-50">
|
||||
{Object.entries(DOCUMENT_VARIABLES).map(([category, variables]) => (
|
||||
<div key={category} className="space-y-1">
|
||||
<h4 className="font-bold text-sm text-gray-700">{category}</h4>
|
||||
{Object.entries(variables).map(([name, variable]) => (
|
||||
<button
|
||||
key={name}
|
||||
type="button"
|
||||
onClick={() => insertVariable(variable)}
|
||||
className="text-xs text-blue-600 hover:text-blue-800 block"
|
||||
>
|
||||
{name.replace(/_/g, ' ')}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
{Object.entries(DOCUMENT_VARIABLES).map(
|
||||
([category, variables]) => (
|
||||
<div key={category} className="space-y-1">
|
||||
<h4 className="font-bold text-sm text-gray-700">
|
||||
{category}
|
||||
</h4>
|
||||
{Object.entries(variables).map(
|
||||
([name, variable]) => (
|
||||
<button
|
||||
key={name}
|
||||
type="button"
|
||||
onClick={() => insertVariable(variable)}
|
||||
className="text-xs text-blue-600 hover:text-blue-800 block"
|
||||
>
|
||||
{name.replace(/_/g, " ")}
|
||||
</button>
|
||||
),
|
||||
)}
|
||||
</div>
|
||||
),
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -227,31 +254,50 @@ export function GerenciarTemplates() {
|
||||
<Editor
|
||||
apiKey={import.meta.env.VITE_TINYMCE_API_KEY}
|
||||
value={formData.conteudo}
|
||||
onEditorChange={(content) => setFormData({...formData, conteudo: content})}
|
||||
onEditorChange={(content) =>
|
||||
setFormData({ ...formData, conteudo: content })
|
||||
}
|
||||
init={{
|
||||
height: 400,
|
||||
menubar: true,
|
||||
plugins: [
|
||||
'advlist', 'autolink', 'lists', 'link', 'image', 'charmap', 'preview',
|
||||
'anchor', 'searchreplace', 'visualblocks', 'code', 'fullscreen',
|
||||
'insertdatetime', 'media', 'table', 'help', 'wordcount'
|
||||
"advlist",
|
||||
"autolink",
|
||||
"lists",
|
||||
"link",
|
||||
"image",
|
||||
"charmap",
|
||||
"preview",
|
||||
"anchor",
|
||||
"searchreplace",
|
||||
"visualblocks",
|
||||
"code",
|
||||
"fullscreen",
|
||||
"insertdatetime",
|
||||
"media",
|
||||
"table",
|
||||
"help",
|
||||
"wordcount",
|
||||
],
|
||||
toolbar: 'variables | undo redo | formatselect | bold italic | alignleft aligncenter alignright',
|
||||
toolbar:
|
||||
"variables | undo redo | formatselect | bold italic | alignleft aligncenter alignright",
|
||||
setup: (editor) => {
|
||||
editor.ui.registry.addMenuButton('variables', {
|
||||
text: 'Variáveis',
|
||||
editor.ui.registry.addMenuButton("variables", {
|
||||
text: "Variáveis",
|
||||
fetch: (callback) => {
|
||||
const items = Object.entries(DOCUMENT_VARIABLES).flatMap(([category, vars]) =>
|
||||
const items = Object.entries(
|
||||
DOCUMENT_VARIABLES,
|
||||
).flatMap(([category, vars]) =>
|
||||
Object.entries(vars).map(([name, value]) => ({
|
||||
type: 'menuitem',
|
||||
text: `${category} - ${name.replace(/_/g, ' ')}`,
|
||||
onAction: () => editor.insertContent(value)
|
||||
}))
|
||||
type: "menuitem",
|
||||
text: `${category} - ${name.replace(/_/g, " ")}`,
|
||||
onAction: () => editor.insertContent(value),
|
||||
})),
|
||||
);
|
||||
callback(items);
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -1,31 +1,32 @@
|
||||
import { useState } from 'react';
|
||||
import { DocumentoService } from '../../services/DocumentoService';
|
||||
import { TIPOS_DOCUMENTO } from '../../utils/documentTemplates';
|
||||
import { useState } from "react";
|
||||
|
||||
import { DocumentoService } from "../../services/DocumentoService";
|
||||
import { TIPOS_DOCUMENTO } from "../../utils/documentTemplates";
|
||||
|
||||
export function NovoDocumento() {
|
||||
const [tipo, setTipo] = useState(TIPOS_DOCUMENTO.PROCURACAO);
|
||||
|
||||
|
||||
const handleGerar = async () => {
|
||||
// 1. Buscar dados necessários
|
||||
const cliente = await api.get(`/clientes/${clienteId}`);
|
||||
const advogado = await api.get('/usuarios/me');
|
||||
|
||||
const advogado = await api.get("/usuarios/me");
|
||||
|
||||
// 2. Preparar variáveis
|
||||
const variaveis = {
|
||||
cliente: cliente.data,
|
||||
advogado: advogado.data,
|
||||
cidade: 'São Paulo',
|
||||
data: new Date().toLocaleDateString()
|
||||
cidade: "São Paulo",
|
||||
data: new Date().toLocaleDateString(),
|
||||
};
|
||||
|
||||
// 3. Buscar configurações do escritório
|
||||
const config = await api.get('/configuracoes');
|
||||
const config = await api.get("/configuracoes");
|
||||
|
||||
// 4. Gerar documento
|
||||
const documento = DocumentoService.gerarDocumento({
|
||||
tipo,
|
||||
variaveis,
|
||||
config: config.data
|
||||
config: config.data,
|
||||
});
|
||||
|
||||
// 5. Salvar ou exibir
|
||||
@@ -34,15 +35,13 @@ export function NovoDocumento() {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<select value={tipo} onChange={e => setTipo(e.target.value)}>
|
||||
<select value={tipo} onChange={(e) => setTipo(e.target.value)}>
|
||||
<option value={TIPOS_DOCUMENTO.PROCURACAO}>Procuração</option>
|
||||
<option value={TIPOS_DOCUMENTO.CONTRATO}>Contrato</option>
|
||||
{/* ... */}
|
||||
</select>
|
||||
|
||||
<button onClick={handleGerar}>
|
||||
Gerar Documento
|
||||
</button>
|
||||
|
||||
<button onClick={handleGerar}>Gerar Documento</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user