diff --git a/src/components/areas/AreasView.tsx b/src/components/areas/AreasView.tsx
index 7e33cf3..33ffae6 100644
--- a/src/components/areas/AreasView.tsx
+++ b/src/components/areas/AreasView.tsx
@@ -38,9 +38,21 @@ export function AreasView() {
const [totalRegistros, setTotalRegistros] = useState(0);
const [totalPaginas, setTotalPaginas] = useState(1);
const [loadingList, setLoadingList] = useState(true);
+ const normalizedAreas = useMemo(
+ () => new Set(areas.map((area) => area.trim().toLocaleLowerCase())),
+ [areas]
+ );
const totalPages = Math.max(1, totalPaginas);
const isFiltering = filterNome.trim().length > 0;
+ const createName = newName.trim();
+ const isCreateNameDuplicated = createName.length > 0 && normalizedAreas.has(createName.toLocaleLowerCase());
+ const editNameNormalized = editName.trim().toLocaleLowerCase();
+ const selectedAreaNormalized = selectedAreaItem?.nome.trim().toLocaleLowerCase();
+ const isEditNameDuplicated =
+ editName.trim().length > 0 &&
+ editNameNormalized !== selectedAreaNormalized &&
+ normalizedAreas.has(editNameNormalized);
useEffect(() => {
let cancelled = false;
@@ -90,7 +102,7 @@ export function AreasView() {
const name = newName.trim();
const description = newDescription.trim();
if (!name) return;
- if (areas.includes(name)) {
+ if (normalizedAreas.has(name.toLocaleLowerCase())) {
toast.error("Já existe uma área com esse nome.");
return;
}
@@ -146,7 +158,10 @@ export function AreasView() {
}
const name = editName.trim();
const description = editDescription.trim();
- if (name !== selectedAreaItem.nome && areas.includes(name)) {
+ if (
+ name.toLocaleLowerCase() !== selectedAreaItem.nome.trim().toLocaleLowerCase() &&
+ normalizedAreas.has(name.toLocaleLowerCase())
+ ) {
toast.error("Já existe uma área com esse nome.");
return;
}
@@ -412,7 +427,14 @@ export function AreasView() {
onChange={(e) => setNewName(e.target.value)}
placeholder="Ex: Suporte"
onKeyDown={(e) => e.key === "Enter" && handleCreate()}
+ aria-invalid={isCreateNameDuplicated}
+ className={isCreateNameDuplicated ? "border-destructive focus-visible:ring-destructive" : undefined}
/>
+ {isCreateNameDuplicated && (
+
+ Já existe uma área com esse nome. Escolha um nome diferente.
+
+ )}
@@ -430,7 +452,7 @@ export function AreasView() {
-
@@ -470,7 +499,7 @@ export function AreasView() {
setIsEditOpen(false)} disabled={isSavingEdit}>
Cancelar
-
+
{isSavingEdit ? "Salvando..." : "Salvar"}
diff --git a/src/components/parecer-juridico/ParecerJuridicoDetailView.tsx b/src/components/parecer-juridico/ParecerJuridicoDetailView.tsx
index c5cd0c6..0ecb75e 100644
--- a/src/components/parecer-juridico/ParecerJuridicoDetailView.tsx
+++ b/src/components/parecer-juridico/ParecerJuridicoDetailView.tsx
@@ -56,6 +56,20 @@ function formatarDataDetalhe(iso: string): string {
}
}
+/** Evita exibir expressões n8n ($('...')) como nome de usuário. */
+function textoAtualizadoPorValido(val: string | null | undefined): string | null {
+ const v = val?.trim();
+ if (!v) return null;
+ if (v.includes("$(") || v.includes("{{") || v.startsWith("$")) return null;
+ return v;
+}
+
+function textoUsuarioParecer(p: ParecerDetalhe): string {
+ const email = p.user_email?.trim();
+ const criado = p.criado_por?.trim();
+ return email || criado || "—";
+}
+
interface ParecerPreviewState {
titulo?: string;
conteudoMarkdown?: string;
@@ -83,7 +97,7 @@ export function ParecerJuridicoDetailView() {
const [detailsOpen, setDetailsOpen] = useState(false);
const [deleteOpen, setDeleteOpen] = useState(false);
const [deleting, setDeleting] = useState(false);
- const { areaItems } = usePrompts();
+ const { areaItems, refreshAreas } = usePrompts();
const [editOpen, setEditOpen] = useState(false);
const [editTitulo, setEditTitulo] = useState("");
@@ -107,6 +121,25 @@ export function ParecerJuridicoDetailView() {
const dragStartHeight = useRef(0);
const chatEndRef = useRef(null);
+ /** Preenche título e área com os dados atuais do parecer (sempre que abrir o modal de edição). */
+ const aplicarDadosAtuaisNoFormularioEdicao = useCallback(() => {
+ if (!parecer) return;
+ setEditTitulo(parecer.titulo?.trim() ?? "");
+ setEditAreaId(parecer.area_id?.trim() ?? "");
+ }, [parecer]);
+
+ const openEditModal = useCallback(() => {
+ aplicarDadosAtuaisNoFormularioEdicao();
+ setEditOpen(true);
+ }, [aplicarDadosAtuaisNoFormularioEdicao]);
+
+ /** Fecha detalhes e abre edição já com título e área atuais. */
+ const openEditFromDetailsModal = useCallback(() => {
+ aplicarDadosAtuaisNoFormularioEdicao();
+ setDetailsOpen(false);
+ setEditOpen(true);
+ }, [aplicarDadosAtuaisNoFormularioEdicao]);
+
const MIN_INPUT_HEIGHT = 60;
const MAX_INPUT_HEIGHT = 400;
@@ -135,6 +168,10 @@ export function ParecerJuridicoDetailView() {
.finally(() => setLoading(false));
}, [id]);
+ useEffect(() => {
+ refreshAreas();
+ }, [refreshAreas]);
+
const carregarChat = useCallback(
async (silent = false) => {
if (!id?.trim()) return;
@@ -255,11 +292,29 @@ export function ParecerJuridicoDetailView() {
}
};
- const abrirModalEdicao = () => {
- if (!parecer) return;
- setEditTitulo(parecer.titulo ?? "");
- setEditAreaId(parecer.area_id ?? "");
- setEditOpen(true);
+ const handleDownloadAnexo = async () => {
+ if (!parecer?.anexo_url) return;
+ try {
+ const response = await fetch(parecer.anexo_url);
+ if (!response.ok) {
+ throw new Error(`Erro ${response.status} ao baixar anexo.`);
+ }
+
+ const blob = await response.blob();
+ const fallbackName = parecer.anexo_nome?.trim() || "anexo";
+ const disposition = response.headers.get("Content-Disposition");
+ const match = disposition?.match(/filename\*?=(?:UTF-8'')?"?([^";\n]+)"?/i) || disposition?.match(/filename="?([^";\n]+)"?/i);
+ const fileName = match ? decodeURIComponent(match[1].trim()) : fallbackName;
+
+ const url = URL.createObjectURL(blob);
+ const a = document.createElement("a");
+ a.href = url;
+ a.download = fileName;
+ a.click();
+ URL.revokeObjectURL(url);
+ } catch (e) {
+ toast.error(e instanceof Error ? e.message : "Erro ao baixar anexo.");
+ }
};
const handleSalvarEdicao = async () => {
@@ -441,7 +496,7 @@ export function ParecerJuridicoDetailView() {
size="sm"
className="gap-2"
disabled={!parecer || !id || editSaving}
- onClick={abrirModalEdicao}
+ onClick={openEditModal}
>
Editar
@@ -487,14 +542,14 @@ export function ParecerJuridicoDetailView() {