fix(checklist): corrige acentuação e adiciona modal de exclusão
- Corrige acentuações: Opções, Não, Descrição, Obrigatório, máx - Adiciona modal de confirmação para exclusão de itens do checklist - Remove uso de confirm() nativo 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
f1833be1ea
commit
10078c7aa7
6 changed files with 82 additions and 27 deletions
|
|
@ -29,7 +29,32 @@
|
||||||
"Bash(git commit:*)",
|
"Bash(git commit:*)",
|
||||||
"Bash(git push:*)",
|
"Bash(git push:*)",
|
||||||
"Bash(cargo check:*)",
|
"Bash(cargo check:*)",
|
||||||
"Bash(bun run:*)"
|
"Bash(bun run:*)",
|
||||||
|
"Bash(icacls \"D:\\Projetos IA\\sistema-de-chamados\\codex_ed25519\")",
|
||||||
|
"Bash(copy \"D:\\Projetos IA\\sistema-de-chamados\\codex_ed25519\" \"%TEMP%\\codex_key\")",
|
||||||
|
"Bash(icacls \"%TEMP%\\codex_key\" /inheritance:r /grant:r \"%USERNAME%:R\")",
|
||||||
|
"Bash(cmd /c \"echo %TEMP%\")",
|
||||||
|
"Bash(cmd /c \"dir \"\"%TEMP%\\codex_key\"\"\")",
|
||||||
|
"Bash(where:*)",
|
||||||
|
"Bash(ssh-keygen:*)",
|
||||||
|
"Bash(/c/Program\\ Files/Git/usr/bin/ssh:*)",
|
||||||
|
"Bash(npx convex deploy:*)",
|
||||||
|
"Bash(dir \"%LOCALAPPDATA%\\Raven\")",
|
||||||
|
"Bash(dir \"%APPDATA%\\Raven\")",
|
||||||
|
"Bash(dir \"%LOCALAPPDATA%\\com.raven.app\")",
|
||||||
|
"Bash(dir \"%APPDATA%\\com.raven.app\")",
|
||||||
|
"Bash(tasklist:*)",
|
||||||
|
"Bash(dir /s /b %LOCALAPPDATA%*raven*)",
|
||||||
|
"Bash(cmd /c \"tasklist | findstr /i raven\")",
|
||||||
|
"Bash(cmd /c \"dir /s /b %LOCALAPPDATA%\\*raven* 2>nul\")",
|
||||||
|
"Bash(powershell -Command \"Get-Process | Where-Object {$_ProcessName -like ''*raven*'' -or $_ProcessName -like ''*appsdesktop*''} | Select-Object ProcessName, Id\")",
|
||||||
|
"Bash(node:*)",
|
||||||
|
"Bash(bun scripts/test-all-emails.tsx:*)",
|
||||||
|
"Bash(bun scripts/send-test-react-email.tsx:*)",
|
||||||
|
"Bash(dir:*)",
|
||||||
|
"Bash(git reset:*)",
|
||||||
|
"Bash(npx convex:*)",
|
||||||
|
"Bash(bun tsc:*)"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -70,3 +70,4 @@ rustdesk/
|
||||||
|
|
||||||
# Prisma generated files
|
# Prisma generated files
|
||||||
src/generated/
|
src/generated/
|
||||||
|
apps/desktop/service/target/
|
||||||
|
|
|
||||||
|
|
@ -1235,7 +1235,7 @@ fn open_hub_window_with_state(app: &tauri::AppHandle, start_minimized: bool) ->
|
||||||
let (width, height) = if start_minimized {
|
let (width, height) = if start_minimized {
|
||||||
(200.0, 52.0) // Tamanho minimizado (chip)
|
(200.0, 52.0) // Tamanho minimizado (chip)
|
||||||
} else {
|
} else {
|
||||||
(340.0, 400.0) // Tamanho expandido (lista)
|
(400.0, 520.0) // Tamanho expandido (igual ao web)
|
||||||
};
|
};
|
||||||
|
|
||||||
// Posicionar no canto inferior direito
|
// Posicionar no canto inferior direito
|
||||||
|
|
@ -1284,7 +1284,7 @@ pub fn set_hub_minimized(app: &tauri::AppHandle, minimized: bool) -> Result<(),
|
||||||
let (width, height) = if minimized {
|
let (width, height) = if minimized {
|
||||||
(200.0, 52.0) // Chip minimizado
|
(200.0, 52.0) // Chip minimizado
|
||||||
} else {
|
} else {
|
||||||
(340.0, 400.0) // Lista expandida (ajustado para caber melhor)
|
(400.0, 520.0) // Lista expandida (igual ao web)
|
||||||
};
|
};
|
||||||
|
|
||||||
// Primeiro reposiciona, depois redimensiona para evitar corte
|
// Primeiro reposiciona, depois redimensiona para evitar corte
|
||||||
|
|
|
||||||
|
|
@ -58,7 +58,7 @@ function normalizeTemplateItems(
|
||||||
throw new ConvexError("Todos os itens do checklist precisam ter um texto.")
|
throw new ConvexError("Todos os itens do checklist precisam ter um texto.")
|
||||||
}
|
}
|
||||||
if (text.length > 240) {
|
if (text.length > 240) {
|
||||||
throw new ConvexError("Item do checklist muito longo (max. 240 caracteres).")
|
throw new ConvexError("Item do checklist muito longo (máx. 240 caracteres).")
|
||||||
}
|
}
|
||||||
|
|
||||||
const description = entry.description?.trim() || undefined
|
const description = entry.description?.trim() || undefined
|
||||||
|
|
@ -68,7 +68,7 @@ function normalizeTemplateItems(
|
||||||
: undefined
|
: undefined
|
||||||
|
|
||||||
if (itemType === "question" && (!itemOptions || itemOptions.length < 2)) {
|
if (itemType === "question" && (!itemOptions || itemOptions.length < 2)) {
|
||||||
throw new ConvexError(`A pergunta "${text}" precisa ter pelo menos 2 opcoes.`)
|
throw new ConvexError(`A pergunta "${text}" precisa ter pelo menos 2 opções.`)
|
||||||
}
|
}
|
||||||
|
|
||||||
const required = typeof entry.required === "boolean" ? entry.required : true
|
const required = typeof entry.required === "boolean" ? entry.required : true
|
||||||
|
|
|
||||||
|
|
@ -67,11 +67,11 @@ function normalizeTemplateItems(items: DraftItem[]) {
|
||||||
}
|
}
|
||||||
const invalid = normalized.find((item) => item.text.length > 240)
|
const invalid = normalized.find((item) => item.text.length > 240)
|
||||||
if (invalid) {
|
if (invalid) {
|
||||||
throw new Error("Item do checklist muito longo (max. 240 caracteres).")
|
throw new Error("Item do checklist muito longo (máx. 240 caracteres).")
|
||||||
}
|
}
|
||||||
const invalidQuestion = normalized.find((item) => item.type === "question" && item.options.length < 2)
|
const invalidQuestion = normalized.find((item) => item.type === "question" && item.options.length < 2)
|
||||||
if (invalidQuestion) {
|
if (invalidQuestion) {
|
||||||
throw new Error(`A pergunta "${invalidQuestion.text}" precisa ter pelo menos 2 opcoes.`)
|
throw new Error(`A pergunta "${invalidQuestion.text}" precisa ter pelo menos 2 opções.`)
|
||||||
}
|
}
|
||||||
return normalized
|
return normalized
|
||||||
}
|
}
|
||||||
|
|
@ -228,7 +228,7 @@ function TemplateEditorDialog({
|
||||||
<div className="flex items-center justify-between gap-2">
|
<div className="flex items-center justify-between gap-2">
|
||||||
<div className="space-y-0.5">
|
<div className="space-y-0.5">
|
||||||
<p className="text-sm font-semibold text-neutral-900">Itens</p>
|
<p className="text-sm font-semibold text-neutral-900">Itens</p>
|
||||||
<p className="text-xs text-muted-foreground">Defina o que precisa ser feito. Itens obrigatorios bloqueiam o encerramento.</p>
|
<p className="text-xs text-muted-foreground">Defina o que precisa ser feito. Itens obrigatórios bloqueiam o encerramento.</p>
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
|
|
@ -260,7 +260,7 @@ function TemplateEditorDialog({
|
||||||
setItems((prev) => prev.map((row) => (row.id === item.id ? { ...row, required: Boolean(checked) } : row)))
|
setItems((prev) => prev.map((row) => (row.id === item.id ? { ...row, required: Boolean(checked) } : row)))
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
Obrigatorio
|
Obrigatório
|
||||||
</label>
|
</label>
|
||||||
<label className="flex items-center gap-2 text-sm text-neutral-700">
|
<label className="flex items-center gap-2 text-sm text-neutral-700">
|
||||||
<Checkbox
|
<Checkbox
|
||||||
|
|
@ -272,7 +272,7 @@ function TemplateEditorDialog({
|
||||||
? {
|
? {
|
||||||
...row,
|
...row,
|
||||||
type: checked ? "question" : "checkbox",
|
type: checked ? "question" : "checkbox",
|
||||||
options: checked ? (row.options.length > 0 ? row.options : ["Sim", "Nao"]) : [],
|
options: checked ? (row.options.length > 0 ? row.options : ["Sim", "Não"]) : [],
|
||||||
}
|
}
|
||||||
: row
|
: row
|
||||||
)
|
)
|
||||||
|
|
@ -299,14 +299,14 @@ function TemplateEditorDialog({
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
setItems((prev) => prev.map((row) => (row.id === item.id ? { ...row, description: e.target.value } : row)))
|
setItems((prev) => prev.map((row) => (row.id === item.id ? { ...row, description: e.target.value } : row)))
|
||||||
}
|
}
|
||||||
placeholder="Descricao (opcional)"
|
placeholder="Descrição (opcional)"
|
||||||
className="h-8 text-xs"
|
className="h-8 text-xs"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{item.type === "question" && (
|
{item.type === "question" && (
|
||||||
<div className="space-y-2 rounded-lg border border-dashed border-cyan-200 bg-cyan-50/50 p-2">
|
<div className="space-y-2 rounded-lg border border-dashed border-cyan-200 bg-cyan-50/50 p-2">
|
||||||
<div className="flex items-center justify-between gap-2">
|
<div className="flex items-center justify-between gap-2">
|
||||||
<p className="text-xs font-medium text-cyan-700">Opcoes de resposta</p>
|
<p className="text-xs font-medium text-cyan-700">Opções de resposta</p>
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ import { Badge } from "@/components/ui/badge"
|
||||||
import { Button } from "@/components/ui/button"
|
import { Button } from "@/components/ui/button"
|
||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
|
||||||
import { Checkbox } from "@/components/ui/checkbox"
|
import { Checkbox } from "@/components/ui/checkbox"
|
||||||
|
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog"
|
||||||
import { Input } from "@/components/ui/input"
|
import { Input } from "@/components/ui/input"
|
||||||
import { Progress } from "@/components/ui/progress"
|
import { Progress } from "@/components/ui/progress"
|
||||||
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"
|
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"
|
||||||
|
|
@ -98,6 +99,8 @@ export function TicketChecklistCard({
|
||||||
const [applyingTemplate, setApplyingTemplate] = useState(false)
|
const [applyingTemplate, setApplyingTemplate] = useState(false)
|
||||||
const [completingAll, setCompletingAll] = useState(false)
|
const [completingAll, setCompletingAll] = useState(false)
|
||||||
const [onlyPending, setOnlyPending] = useState(false)
|
const [onlyPending, setOnlyPending] = useState(false)
|
||||||
|
const [deleteTarget, setDeleteTarget] = useState<TicketChecklistItem | null>(null)
|
||||||
|
const [deleting, setDeleting] = useState(false)
|
||||||
|
|
||||||
const handleAdd = async () => {
|
const handleAdd = async () => {
|
||||||
if (!actorId || !canEdit || isResolved) return
|
if (!actorId || !canEdit || isResolved) return
|
||||||
|
|
@ -470,21 +473,7 @@ export function TicketChecklistCard({
|
||||||
size="icon"
|
size="icon"
|
||||||
className="h-9 w-9 text-slate-500 hover:bg-red-50 hover:text-red-700"
|
className="h-9 w-9 text-slate-500 hover:bg-red-50 hover:text-red-700"
|
||||||
title="Remover"
|
title="Remover"
|
||||||
onClick={async () => {
|
onClick={() => setDeleteTarget(item)}
|
||||||
if (!actorId) return
|
|
||||||
const ok = confirm("Remover este item do checklist?")
|
|
||||||
if (!ok) return
|
|
||||||
try {
|
|
||||||
await removeChecklistItem({
|
|
||||||
ticketId: ticket.id as Id<"tickets">,
|
|
||||||
actorId,
|
|
||||||
itemId: item.id,
|
|
||||||
})
|
|
||||||
toast.success("Item removido.")
|
|
||||||
} catch (error) {
|
|
||||||
toast.error(error instanceof Error ? error.message : "Falha ao remover item.")
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<Trash2 className="size-4" />
|
<Trash2 className="size-4" />
|
||||||
</Button>
|
</Button>
|
||||||
|
|
@ -525,6 +514,46 @@ export function TicketChecklistCard({
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
</CardContent>
|
</CardContent>
|
||||||
|
|
||||||
|
<Dialog open={Boolean(deleteTarget)} onOpenChange={(open) => !open && setDeleteTarget(null)}>
|
||||||
|
<DialogContent className="max-w-md">
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>Remover item do checklist</DialogTitle>
|
||||||
|
<DialogDescription>
|
||||||
|
Tem certeza que deseja remover o item <strong>"{deleteTarget?.text}"</strong> do checklist?
|
||||||
|
</DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
<DialogFooter className="gap-3">
|
||||||
|
<Button type="button" variant="outline" onClick={() => setDeleteTarget(null)} disabled={deleting}>
|
||||||
|
Cancelar
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
variant="destructive"
|
||||||
|
disabled={deleting}
|
||||||
|
onClick={async () => {
|
||||||
|
if (!actorId || !deleteTarget) return
|
||||||
|
setDeleting(true)
|
||||||
|
try {
|
||||||
|
await removeChecklistItem({
|
||||||
|
ticketId: ticket.id as Id<"tickets">,
|
||||||
|
actorId,
|
||||||
|
itemId: deleteTarget.id,
|
||||||
|
})
|
||||||
|
toast.success("Item removido.")
|
||||||
|
setDeleteTarget(null)
|
||||||
|
} catch (error) {
|
||||||
|
toast.error(error instanceof Error ? error.message : "Falha ao remover item.")
|
||||||
|
} finally {
|
||||||
|
setDeleting(false)
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{deleting ? "Removendo..." : "Remover"}
|
||||||
|
</Button>
|
||||||
|
</DialogFooter>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
</Card>
|
</Card>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue