fix: corrige tipo JSON para String no SQLite e acentuação nos textos
- Altera typePreferences e categoryPreferences de Json para String no Prisma - Atualiza API de preferências para fazer parse/stringify de JSON - Corrige todos os textos sem acentuação nos componentes de notificação 🤖 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
f2c0298285
commit
7ecb4c1110
7 changed files with 104 additions and 89 deletions
|
|
@ -462,11 +462,11 @@ model NotificationPreferences {
|
||||||
|
|
||||||
// Preferências por tipo de notificação (JSON)
|
// Preferências por tipo de notificação (JSON)
|
||||||
// Ex: { "ticket_created": true, "ticket_resolved": true, "comment_public": false }
|
// Ex: { "ticket_created": true, "ticket_resolved": true, "comment_public": false }
|
||||||
typePreferences Json @default("{}")
|
typePreferences String @default("{}")
|
||||||
|
|
||||||
// Preferências por categoria de ticket (JSON)
|
// Preferências por categoria de ticket (JSON)
|
||||||
// Ex: { "category_id_1": true, "category_id_2": false }
|
// Ex: { "category_id_1": true, "category_id_2": false }
|
||||||
categoryPreferences Json @default("{}")
|
categoryPreferences String @default("{}")
|
||||||
|
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
|
|
|
||||||
|
|
@ -80,10 +80,17 @@ export async function GET(_request: NextRequest) {
|
||||||
? Object.keys(NOTIFICATION_TYPES)
|
? Object.keys(NOTIFICATION_TYPES)
|
||||||
: COLLABORATOR_VISIBLE_TYPES
|
: COLLABORATOR_VISIBLE_TYPES
|
||||||
|
|
||||||
|
// Parse JSON strings
|
||||||
|
const typePrefs: Record<string, boolean> = prefs!.typePreferences
|
||||||
|
? JSON.parse(prefs!.typePreferences as string)
|
||||||
|
: {}
|
||||||
|
const catPrefs = prefs!.categoryPreferences
|
||||||
|
? JSON.parse(prefs!.categoryPreferences as string)
|
||||||
|
: {}
|
||||||
|
|
||||||
// Monta resposta com configuração de cada tipo
|
// Monta resposta com configuração de cada tipo
|
||||||
const typeConfigs = availableTypes.map((type) => {
|
const typeConfigs = availableTypes.map((type) => {
|
||||||
const config = NOTIFICATION_TYPES[type as NotificationType]
|
const config = NOTIFICATION_TYPES[type as NotificationType]
|
||||||
const typePrefs = prefs!.typePreferences as Record<string, boolean>
|
|
||||||
const enabled = typePrefs[type] ?? config.defaultEnabled
|
const enabled = typePrefs[type] ?? config.defaultEnabled
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
@ -104,7 +111,7 @@ export async function GET(_request: NextRequest) {
|
||||||
timezone: prefs.timezone,
|
timezone: prefs.timezone,
|
||||||
digestFrequency: prefs.digestFrequency,
|
digestFrequency: prefs.digestFrequency,
|
||||||
types: typeConfigs,
|
types: typeConfigs,
|
||||||
categoryPreferences: prefs.categoryPreferences,
|
categoryPreferences: catPrefs,
|
||||||
isStaff,
|
isStaff,
|
||||||
})
|
})
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
@ -194,11 +201,19 @@ export async function PUT(request: NextRequest) {
|
||||||
quietHoursEnd: quietHoursEnd ?? null,
|
quietHoursEnd: quietHoursEnd ?? null,
|
||||||
timezone: timezone ?? "America/Sao_Paulo",
|
timezone: timezone ?? "America/Sao_Paulo",
|
||||||
digestFrequency: digestFrequency ?? "immediate",
|
digestFrequency: digestFrequency ?? "immediate",
|
||||||
typePreferences: validatedTypePrefs,
|
typePreferences: JSON.stringify(validatedTypePrefs),
|
||||||
categoryPreferences: categoryPreferences ?? {},
|
categoryPreferences: JSON.stringify(categoryPreferences ?? {}),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
|
// Parse existing JSON strings
|
||||||
|
const existingTypePrefs = existingPrefs.typePreferences
|
||||||
|
? JSON.parse(existingPrefs.typePreferences as string)
|
||||||
|
: {}
|
||||||
|
const existingCatPrefs = existingPrefs.categoryPreferences
|
||||||
|
? JSON.parse(existingPrefs.categoryPreferences as string)
|
||||||
|
: {}
|
||||||
|
|
||||||
// Atualiza preferências existentes
|
// Atualiza preferências existentes
|
||||||
await prisma.notificationPreferences.update({
|
await prisma.notificationPreferences.update({
|
||||||
where: { userId },
|
where: { userId },
|
||||||
|
|
@ -208,11 +223,11 @@ export async function PUT(request: NextRequest) {
|
||||||
quietHoursEnd: quietHoursEnd !== undefined ? quietHoursEnd : existingPrefs.quietHoursEnd,
|
quietHoursEnd: quietHoursEnd !== undefined ? quietHoursEnd : existingPrefs.quietHoursEnd,
|
||||||
timezone: timezone ?? existingPrefs.timezone,
|
timezone: timezone ?? existingPrefs.timezone,
|
||||||
digestFrequency: digestFrequency ?? existingPrefs.digestFrequency,
|
digestFrequency: digestFrequency ?? existingPrefs.digestFrequency,
|
||||||
typePreferences: {
|
typePreferences: JSON.stringify({
|
||||||
...(existingPrefs.typePreferences as Record<string, boolean>),
|
...existingTypePrefs,
|
||||||
...validatedTypePrefs,
|
...validatedTypePrefs,
|
||||||
},
|
}),
|
||||||
categoryPreferences: categoryPreferences ?? existingPrefs.categoryPreferences,
|
categoryPreferences: JSON.stringify(categoryPreferences ?? existingCatPrefs),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,8 @@ import { NotificationPreferencesForm } from "@/components/settings/notification-
|
||||||
import { requireAuthenticatedSession } from "@/lib/auth-server"
|
import { requireAuthenticatedSession } from "@/lib/auth-server"
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: "Preferencias de notificacao",
|
title: "Preferências de notificação",
|
||||||
description: "Configure quais notificacoes por e-mail deseja receber.",
|
description: "Configure quais notificações por e-mail deseja receber.",
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async function PortalNotificationSettingsPage() {
|
export default async function PortalNotificationSettingsPage() {
|
||||||
|
|
@ -14,7 +14,7 @@ export default async function PortalNotificationSettingsPage() {
|
||||||
const role = (session.user.role ?? "").toLowerCase()
|
const role = (session.user.role ?? "").toLowerCase()
|
||||||
const persona = (session.user.machinePersona ?? "").toLowerCase()
|
const persona = (session.user.machinePersona ?? "").toLowerCase()
|
||||||
|
|
||||||
// Colaboradores e maquinas com persona de colaborador podem acessar
|
// Colaboradores e máquinas com persona de colaborador podem acessar
|
||||||
const allowedRoles = new Set(["collaborator", "manager", "admin", "agent"])
|
const allowedRoles = new Set(["collaborator", "manager", "admin", "agent"])
|
||||||
const isMachinePersonaAllowed = role === "machine" && (persona === "collaborator" || persona === "manager")
|
const isMachinePersonaAllowed = role === "machine" && (persona === "collaborator" || persona === "manager")
|
||||||
const allowed = allowedRoles.has(role) || isMachinePersonaAllowed
|
const allowed = allowedRoles.has(role) || isMachinePersonaAllowed
|
||||||
|
|
@ -23,7 +23,7 @@ export default async function PortalNotificationSettingsPage() {
|
||||||
redirect("/portal")
|
redirect("/portal")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Staff deve usar a pagina de configuracoes completa
|
// Staff deve usar a página de configurações completa
|
||||||
const staffRoles = new Set(["admin", "manager", "agent"])
|
const staffRoles = new Set(["admin", "manager", "agent"])
|
||||||
if (staffRoles.has(role)) {
|
if (staffRoles.has(role)) {
|
||||||
redirect("/settings/notifications")
|
redirect("/settings/notifications")
|
||||||
|
|
@ -32,9 +32,9 @@ export default async function PortalNotificationSettingsPage() {
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-2xl font-semibold tracking-tight">Preferencias de notificacao</h1>
|
<h1 className="text-2xl font-semibold tracking-tight">Preferências de notificação</h1>
|
||||||
<p className="text-muted-foreground">
|
<p className="text-muted-foreground">
|
||||||
Configure quais notificacoes por e-mail deseja receber sobre seus chamados.
|
Configure quais notificações por e-mail deseja receber sobre seus chamados.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<NotificationPreferencesForm isPortal />
|
<NotificationPreferencesForm isPortal />
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ export default function RatePage() {
|
||||||
const [comment, setComment] = useState("")
|
const [comment, setComment] = useState("")
|
||||||
const [error, setError] = useState<string | null>(null)
|
const [error, setError] = useState<string | null>(null)
|
||||||
|
|
||||||
// Se ja avaliou, mostra mensagem
|
// Se já avaliou, mostra mensagem
|
||||||
if (alreadyRated && existingRating) {
|
if (alreadyRated && existingRating) {
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-background flex items-center justify-center p-4">
|
<div className="min-h-screen bg-background flex items-center justify-center p-4">
|
||||||
|
|
@ -37,9 +37,9 @@ export default function RatePage() {
|
||||||
<div className="flex justify-center mb-4">
|
<div className="flex justify-center mb-4">
|
||||||
<CheckCircle className="h-16 w-16 text-emerald-500" />
|
<CheckCircle className="h-16 w-16 text-emerald-500" />
|
||||||
</div>
|
</div>
|
||||||
<CardTitle>Chamado ja avaliado</CardTitle>
|
<CardTitle>Chamado já avaliado</CardTitle>
|
||||||
<CardDescription>
|
<CardDescription>
|
||||||
Voce ja avaliou este chamado anteriormente.
|
Você já avaliou este chamado anteriormente.
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="text-center">
|
<CardContent className="text-center">
|
||||||
|
|
@ -54,7 +54,7 @@ export default function RatePage() {
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<p className="text-muted-foreground text-sm">
|
<p className="text-muted-foreground text-sm">
|
||||||
Sua avaliacao: {existingRating} estrela{existingRating > 1 ? "s" : ""}
|
Sua avaliação: {existingRating} estrela{existingRating > 1 ? "s" : ""}
|
||||||
</p>
|
</p>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
@ -62,7 +62,7 @@ export default function RatePage() {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Se acabou de avaliar, mostra formulario para comentario
|
// Se acabou de avaliar, mostra formulário para comentário
|
||||||
if (submitted && rating > 0) {
|
if (submitted && rating > 0) {
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-background flex items-center justify-center p-4">
|
<div className="min-h-screen bg-background flex items-center justify-center p-4">
|
||||||
|
|
@ -71,9 +71,9 @@ export default function RatePage() {
|
||||||
<div className="flex justify-center mb-4">
|
<div className="flex justify-center mb-4">
|
||||||
<CheckCircle className="h-16 w-16 text-emerald-500" />
|
<CheckCircle className="h-16 w-16 text-emerald-500" />
|
||||||
</div>
|
</div>
|
||||||
<CardTitle>Obrigado pela avaliacao!</CardTitle>
|
<CardTitle>Obrigado pela avaliação!</CardTitle>
|
||||||
<CardDescription>
|
<CardDescription>
|
||||||
Sua opiniao e muito importante para nos.
|
Sua opinião é muito importante para nós.
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="space-y-6">
|
<CardContent className="space-y-6">
|
||||||
|
|
@ -89,10 +89,10 @@ export default function RatePage() {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="comment">Gostaria de deixar um comentario? (opcional)</Label>
|
<Label htmlFor="comment">Gostaria de deixar um comentário? (opcional)</Label>
|
||||||
<Textarea
|
<Textarea
|
||||||
id="comment"
|
id="comment"
|
||||||
placeholder="Conte-nos mais sobre sua experiencia..."
|
placeholder="Conte-nos mais sobre sua experiência..."
|
||||||
value={comment}
|
value={comment}
|
||||||
onChange={(e) => setComment(e.target.value)}
|
onChange={(e) => setComment(e.target.value)}
|
||||||
rows={4}
|
rows={4}
|
||||||
|
|
@ -134,7 +134,7 @@ export default function RatePage() {
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
const data = await response.json()
|
const data = await response.json()
|
||||||
throw new Error(data.error || "Erro ao enviar comentario")
|
throw new Error(data.error || "Erro ao enviar comentário")
|
||||||
}
|
}
|
||||||
|
|
||||||
window.close()
|
window.close()
|
||||||
|
|
@ -151,7 +151,7 @@ export default function RatePage() {
|
||||||
Enviando...
|
Enviando...
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
"Enviar comentario"
|
"Enviar comentário"
|
||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -161,7 +161,7 @@ export default function RatePage() {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Formulario de avaliacao
|
// Formulário de avaliação
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-background flex items-center justify-center p-4">
|
<div className="min-h-screen bg-background flex items-center justify-center p-4">
|
||||||
<Card className="w-full max-w-md">
|
<Card className="w-full max-w-md">
|
||||||
|
|
@ -174,7 +174,7 @@ export default function RatePage() {
|
||||||
</div>
|
</div>
|
||||||
<CardTitle>Como foi o atendimento?</CardTitle>
|
<CardTitle>Como foi o atendimento?</CardTitle>
|
||||||
<CardDescription>
|
<CardDescription>
|
||||||
Sua avaliacao nos ajuda a melhorar nosso servico.
|
Sua avaliação nos ajuda a melhorar nosso serviço.
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="space-y-6">
|
<CardContent className="space-y-6">
|
||||||
|
|
@ -206,12 +206,12 @@ export default function RatePage() {
|
||||||
<span>Excelente</span>
|
<span>Excelente</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Comentario */}
|
{/* Comentário */}
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="comment">Comentario (opcional)</Label>
|
<Label htmlFor="comment">Comentário (opcional)</Label>
|
||||||
<Textarea
|
<Textarea
|
||||||
id="comment"
|
id="comment"
|
||||||
placeholder="Conte-nos mais sobre sua experiencia..."
|
placeholder="Conte-nos mais sobre sua experiência..."
|
||||||
value={comment}
|
value={comment}
|
||||||
onChange={(e) => setComment(e.target.value)}
|
onChange={(e) => setComment(e.target.value)}
|
||||||
rows={4}
|
rows={4}
|
||||||
|
|
@ -225,7 +225,7 @@ export default function RatePage() {
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Botao */}
|
{/* Botão */}
|
||||||
<Button
|
<Button
|
||||||
className="w-full"
|
className="w-full"
|
||||||
size="lg"
|
size="lg"
|
||||||
|
|
@ -243,7 +243,7 @@ export default function RatePage() {
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
const data = await response.json()
|
const data = await response.json()
|
||||||
throw new Error(data.error || "Erro ao enviar avaliacao")
|
throw new Error(data.error || "Erro ao enviar avaliação")
|
||||||
}
|
}
|
||||||
|
|
||||||
setSubmitted(true)
|
setSubmitted(true)
|
||||||
|
|
@ -260,7 +260,7 @@ export default function RatePage() {
|
||||||
Enviando...
|
Enviando...
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
"Enviar avaliacao"
|
"Enviar avaliação"
|
||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
|
|
|
||||||
|
|
@ -7,15 +7,15 @@ import { NotificationPreferencesForm } from "@/components/settings/notification-
|
||||||
import { requireAuthenticatedSession } from "@/lib/auth-server"
|
import { requireAuthenticatedSession } from "@/lib/auth-server"
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: "Preferencias de notificacao",
|
title: "Preferências de notificação",
|
||||||
description: "Configure quais notificacoes por e-mail deseja receber.",
|
description: "Configure quais notificações por e-mail deseja receber.",
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async function NotificationSettingsPage() {
|
export default async function NotificationSettingsPage() {
|
||||||
const session = await requireAuthenticatedSession()
|
const session = await requireAuthenticatedSession()
|
||||||
const role = (session.user.role ?? "").toLowerCase()
|
const role = (session.user.role ?? "").toLowerCase()
|
||||||
|
|
||||||
// Apenas staff pode acessar esta pagina
|
// Apenas staff pode acessar esta página
|
||||||
const staffRoles = new Set(["admin", "manager", "agent"])
|
const staffRoles = new Set(["admin", "manager", "agent"])
|
||||||
if (!staffRoles.has(role)) {
|
if (!staffRoles.has(role)) {
|
||||||
redirect("/portal/profile/notifications")
|
redirect("/portal/profile/notifications")
|
||||||
|
|
@ -25,8 +25,8 @@ export default async function NotificationSettingsPage() {
|
||||||
<AppShell
|
<AppShell
|
||||||
header={
|
header={
|
||||||
<SiteHeader
|
<SiteHeader
|
||||||
title="Preferencias de notificacao"
|
title="Preferências de notificação"
|
||||||
lead="Configure como e quando deseja receber notificacoes por e-mail"
|
lead="Configure como e quando deseja receber notificações por e-mail"
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -114,7 +114,7 @@ export default function TicketViewPage() {
|
||||||
try {
|
try {
|
||||||
window.location.href = ravenUrl
|
window.location.href = ravenUrl
|
||||||
} catch {
|
} catch {
|
||||||
// Protocolo nao suportado
|
// Protocolo não suportado
|
||||||
}
|
}
|
||||||
|
|
||||||
// Aguarda 2 segundos para ver se o protocolo foi aceito
|
// Aguarda 2 segundos para ver se o protocolo foi aceito
|
||||||
|
|
@ -123,7 +123,7 @@ export default function TicketViewPage() {
|
||||||
document.body.removeChild(iframe)
|
document.body.removeChild(iframe)
|
||||||
|
|
||||||
if (!protocolHandled) {
|
if (!protocolHandled) {
|
||||||
// Protocolo nao foi aceito, carrega o ticket no navegador
|
// Protocolo não foi aceito, carrega o ticket no navegador
|
||||||
setTryingRaven(false)
|
setTryingRaven(false)
|
||||||
loadTicket()
|
loadTicket()
|
||||||
}
|
}
|
||||||
|
|
@ -153,10 +153,10 @@ export default function TicketViewPage() {
|
||||||
<Loader2 className="h-12 w-12 animate-spin mx-auto mb-4 text-primary" />
|
<Loader2 className="h-12 w-12 animate-spin mx-auto mb-4 text-primary" />
|
||||||
<h2 className="text-xl font-semibold mb-2">Abrindo no Raven...</h2>
|
<h2 className="text-xl font-semibold mb-2">Abrindo no Raven...</h2>
|
||||||
<p className="text-muted-foreground text-sm">
|
<p className="text-muted-foreground text-sm">
|
||||||
Se o aplicativo Raven estiver instalado, ele abrira automaticamente.
|
Se o aplicativo Raven estiver instalado, ele abrirá automaticamente.
|
||||||
</p>
|
</p>
|
||||||
<p className="text-muted-foreground text-sm mt-2">
|
<p className="text-muted-foreground text-sm mt-2">
|
||||||
Caso contrario, o chamado sera exibido aqui em instantes.
|
Caso contrário, o chamado será exibido aqui em instantes.
|
||||||
</p>
|
</p>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
@ -243,15 +243,15 @@ export default function TicketViewPage() {
|
||||||
</div>
|
</div>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="space-y-6">
|
<CardContent className="space-y-6">
|
||||||
{/* Informacoes */}
|
{/* Informações */}
|
||||||
<div className="grid grid-cols-2 gap-4 text-sm">
|
<div className="grid grid-cols-2 gap-4 text-sm">
|
||||||
<div>
|
<div>
|
||||||
<span className="text-muted-foreground">Solicitante</span>
|
<span className="text-muted-foreground">Solicitante</span>
|
||||||
<p className="font-medium">{ticket.requester.name}</p>
|
<p className="font-medium">{ticket.requester.name}</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<span className="text-muted-foreground">Responsavel</span>
|
<span className="text-muted-foreground">Responsável</span>
|
||||||
<p className="font-medium">{ticket.assignee?.name ?? "Nao atribuido"}</p>
|
<p className="font-medium">{ticket.assignee?.name ?? "Não atribuído"}</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<span className="text-muted-foreground">Criado em</span>
|
<span className="text-muted-foreground">Criado em</span>
|
||||||
|
|
@ -274,17 +274,17 @@ export default function TicketViewPage() {
|
||||||
{/* Resumo */}
|
{/* Resumo */}
|
||||||
{ticket.summary && (
|
{ticket.summary && (
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-sm font-medium text-muted-foreground mb-2">Descricao</h3>
|
<h3 className="text-sm font-medium text-muted-foreground mb-2">Descrição</h3>
|
||||||
<p className="text-sm whitespace-pre-wrap">{ticket.summary}</p>
|
<p className="text-sm whitespace-pre-wrap">{ticket.summary}</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Avaliacao */}
|
{/* Avaliação */}
|
||||||
{ticket.rating && (
|
{ticket.rating && (
|
||||||
<div className="bg-muted/50 rounded-lg p-4">
|
<div className="bg-muted/50 rounded-lg p-4">
|
||||||
<div className="flex items-center gap-2 mb-2">
|
<div className="flex items-center gap-2 mb-2">
|
||||||
<CheckCircle className="h-4 w-4 text-emerald-500" />
|
<CheckCircle className="h-4 w-4 text-emerald-500" />
|
||||||
<span className="text-sm font-medium">Avaliacao</span>
|
<span className="text-sm font-medium">Avaliação</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-1 mb-2">
|
<div className="flex items-center gap-1 mb-2">
|
||||||
{[1, 2, 3, 4, 5].map((star) => (
|
{[1, 2, 3, 4, 5].map((star) => (
|
||||||
|
|
@ -302,12 +302,12 @@ export default function TicketViewPage() {
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Comentarios */}
|
{/* Comentários */}
|
||||||
{ticket.comments.length > 0 && (
|
{ticket.comments.length > 0 && (
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-sm font-medium text-muted-foreground mb-4 flex items-center gap-2">
|
<h3 className="text-sm font-medium text-muted-foreground mb-4 flex items-center gap-2">
|
||||||
<MessageSquare className="h-4 w-4" />
|
<MessageSquare className="h-4 w-4" />
|
||||||
Ultimas atualizacoes
|
Últimas atualizações
|
||||||
</h3>
|
</h3>
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
{ticket.comments.map((comment) => (
|
{ticket.comments.map((comment) => (
|
||||||
|
|
|
||||||
|
|
@ -36,26 +36,26 @@ interface NotificationPreferencesFormProps {
|
||||||
isPortal?: boolean
|
isPortal?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
// Agrupamento de tipos de notificacao
|
// Agrupamento de tipos de notificação
|
||||||
const TYPE_GROUPS = {
|
const TYPE_GROUPS = {
|
||||||
lifecycle: {
|
lifecycle: {
|
||||||
label: "Ciclo de vida do chamado",
|
label: "Ciclo de vida do chamado",
|
||||||
description: "Notificacoes sobre abertura, resolucao e mudancas de status",
|
description: "Notificações sobre abertura, resolução e mudanças de status",
|
||||||
types: ["ticket_created", "ticket_assigned", "ticket_resolved", "ticket_reopened", "ticket_status_changed", "ticket_priority_changed"],
|
types: ["ticket_created", "ticket_assigned", "ticket_resolved", "ticket_reopened", "ticket_status_changed", "ticket_priority_changed"],
|
||||||
},
|
},
|
||||||
communication: {
|
communication: {
|
||||||
label: "Comunicacao",
|
label: "Comunicação",
|
||||||
description: "Comentarios e respostas nos chamados",
|
description: "Comentários e respostas nos chamados",
|
||||||
types: ["comment_public", "comment_response", "comment_mention"],
|
types: ["comment_public", "comment_response", "comment_mention"],
|
||||||
},
|
},
|
||||||
sla: {
|
sla: {
|
||||||
label: "SLA e alertas",
|
label: "SLA e alertas",
|
||||||
description: "Alertas de prazo e metricas",
|
description: "Alertas de prazo e métricas",
|
||||||
types: ["sla_at_risk", "sla_breached", "sla_daily_digest"],
|
types: ["sla_at_risk", "sla_breached", "sla_daily_digest"],
|
||||||
},
|
},
|
||||||
security: {
|
security: {
|
||||||
label: "Seguranca",
|
label: "Segurança",
|
||||||
description: "Notificacoes de autenticacao e acesso",
|
description: "Notificações de autenticação e acesso",
|
||||||
types: ["security_password_reset", "security_email_verify", "security_email_change", "security_new_login", "security_invite"],
|
types: ["security_password_reset", "security_email_verify", "security_email_change", "security_new_login", "security_invite"],
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
@ -74,20 +74,20 @@ export function NotificationPreferencesForm({ isPortal = false }: NotificationPr
|
||||||
try {
|
try {
|
||||||
const response = await fetch("/api/notifications/preferences")
|
const response = await fetch("/api/notifications/preferences")
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error("Erro ao carregar preferencias")
|
throw new Error("Erro ao carregar preferências")
|
||||||
}
|
}
|
||||||
const data = await response.json()
|
const data = await response.json()
|
||||||
setPreferences(data)
|
setPreferences(data)
|
||||||
|
|
||||||
// Inicializa preferencias locais
|
// Inicializa preferências locais
|
||||||
const typePrefs: Record<string, boolean> = {}
|
const typePrefs: Record<string, boolean> = {}
|
||||||
data.types.forEach((t: NotificationType) => {
|
data.types.forEach((t: NotificationType) => {
|
||||||
typePrefs[t.type] = t.enabled
|
typePrefs[t.type] = t.enabled
|
||||||
})
|
})
|
||||||
setLocalTypePrefs(typePrefs)
|
setLocalTypePrefs(typePrefs)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Erro ao carregar preferencias:", error)
|
console.error("Erro ao carregar preferências:", error)
|
||||||
toast.error("Nao foi possivel carregar suas preferencias")
|
toast.error("Não foi possível carregar suas preferências")
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
}
|
}
|
||||||
|
|
@ -112,13 +112,13 @@ export function NotificationPreferencesForm({ isPortal = false }: NotificationPr
|
||||||
})
|
})
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error("Erro ao salvar preferencias")
|
throw new Error("Erro ao salvar preferências")
|
||||||
}
|
}
|
||||||
|
|
||||||
toast.success("Preferencias salvas com sucesso")
|
toast.success("Preferências salvas com sucesso")
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Erro ao salvar preferencias:", error)
|
console.error("Erro ao salvar preferências:", error)
|
||||||
toast.error("Nao foi possivel salvar suas preferencias")
|
toast.error("Não foi possível salvar suas preferências")
|
||||||
} finally {
|
} finally {
|
||||||
setSaving(false)
|
setSaving(false)
|
||||||
}
|
}
|
||||||
|
|
@ -153,7 +153,7 @@ export function NotificationPreferencesForm({ isPortal = false }: NotificationPr
|
||||||
<Card>
|
<Card>
|
||||||
<CardContent className="py-8">
|
<CardContent className="py-8">
|
||||||
<p className="text-center text-muted-foreground">
|
<p className="text-center text-muted-foreground">
|
||||||
Nao foi possivel carregar suas preferencias de notificacao.
|
Não foi possível carregar suas preferências de notificação.
|
||||||
</p>
|
</p>
|
||||||
<div className="flex justify-center mt-4">
|
<div className="flex justify-center mt-4">
|
||||||
<Button onClick={loadPreferences}>Tentar novamente</Button>
|
<Button onClick={loadPreferences}>Tentar novamente</Button>
|
||||||
|
|
@ -163,7 +163,7 @@ export function NotificationPreferencesForm({ isPortal = false }: NotificationPr
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Filtra tipos visiveis para o usuario
|
// Filtra tipos visíveis para o usuário
|
||||||
const visibleTypes = preferences.types
|
const visibleTypes = preferences.types
|
||||||
|
|
||||||
// Agrupa tipos por categoria
|
// Agrupa tipos por categoria
|
||||||
|
|
@ -177,15 +177,15 @@ export function NotificationPreferencesForm({ isPortal = false }: NotificationPr
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
{/* Configuracao global de e-mail */}
|
{/* Configuração global de e-mail */}
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle className="flex items-center gap-2">
|
<CardTitle className="flex items-center gap-2">
|
||||||
<Mail className="h-5 w-5" />
|
<Mail className="h-5 w-5" />
|
||||||
Notificacoes por e-mail
|
Notificações por e-mail
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
<CardDescription>
|
<CardDescription>
|
||||||
Controle se deseja receber notificacoes por e-mail.
|
Controle se deseja receber notificações por e-mail.
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="space-y-6">
|
<CardContent className="space-y-6">
|
||||||
|
|
@ -194,8 +194,8 @@ export function NotificationPreferencesForm({ isPortal = false }: NotificationPr
|
||||||
<Label className="text-base">Receber e-mails</Label>
|
<Label className="text-base">Receber e-mails</Label>
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-sm text-muted-foreground">
|
||||||
{preferences.emailEnabled
|
{preferences.emailEnabled
|
||||||
? "Voce recebera notificacoes por e-mail conforme suas preferencias"
|
? "Você receberá notificações por e-mail conforme suas preferências"
|
||||||
: "Todas as notificacoes por e-mail estao desativadas"}
|
: "Todas as notificações por e-mail estão desativadas"}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<Switch
|
<Switch
|
||||||
|
|
@ -208,14 +208,14 @@ export function NotificationPreferencesForm({ isPortal = false }: NotificationPr
|
||||||
<>
|
<>
|
||||||
<Separator />
|
<Separator />
|
||||||
|
|
||||||
{/* Horario de silencio - apenas staff */}
|
{/* Horário de silêncio - apenas staff */}
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Clock className="h-4 w-4 text-muted-foreground" />
|
<Clock className="h-4 w-4 text-muted-foreground" />
|
||||||
<Label className="text-base">Horario de silencio</Label>
|
<Label className="text-base">Horário de silêncio</Label>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-sm text-muted-foreground">
|
||||||
Durante este periodo, notificacoes nao urgentes serao adiadas.
|
Durante este período, notificações não urgentes serão adiadas.
|
||||||
</p>
|
</p>
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-4">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
|
|
@ -229,7 +229,7 @@ export function NotificationPreferencesForm({ isPortal = false }: NotificationPr
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Label htmlFor="quietEnd" className="text-sm">as</Label>
|
<Label htmlFor="quietEnd" className="text-sm">às</Label>
|
||||||
<Input
|
<Input
|
||||||
id="quietEnd"
|
id="quietEnd"
|
||||||
type="time"
|
type="time"
|
||||||
|
|
@ -252,11 +252,11 @@ export function NotificationPreferencesForm({ isPortal = false }: NotificationPr
|
||||||
|
|
||||||
<Separator />
|
<Separator />
|
||||||
|
|
||||||
{/* Frequencia de resumo - apenas staff */}
|
{/* Frequência de resumo - apenas staff */}
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<Label className="text-base">Frequencia de resumo</Label>
|
<Label className="text-base">Frequência de resumo</Label>
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-sm text-muted-foreground">
|
||||||
Como voce prefere receber as notificacoes nao urgentes.
|
Como você prefere receber as notificações não urgentes.
|
||||||
</p>
|
</p>
|
||||||
<Select
|
<Select
|
||||||
value={preferences.digestFrequency}
|
value={preferences.digestFrequency}
|
||||||
|
|
@ -267,7 +267,7 @@ export function NotificationPreferencesForm({ isPortal = false }: NotificationPr
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
<SelectItem value="immediate">Imediato</SelectItem>
|
<SelectItem value="immediate">Imediato</SelectItem>
|
||||||
<SelectItem value="daily">Resumo diario</SelectItem>
|
<SelectItem value="daily">Resumo diário</SelectItem>
|
||||||
<SelectItem value="weekly">Resumo semanal</SelectItem>
|
<SelectItem value="weekly">Resumo semanal</SelectItem>
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
|
|
@ -277,19 +277,19 @@ export function NotificationPreferencesForm({ isPortal = false }: NotificationPr
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
{/* Tipos de notificacao */}
|
{/* Tipos de notificação */}
|
||||||
{preferences.emailEnabled && (
|
{preferences.emailEnabled && (
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle className="flex items-center gap-2">
|
<CardTitle className="flex items-center gap-2">
|
||||||
<Bell className="h-5 w-5" />
|
<Bell className="h-5 w-5" />
|
||||||
Tipos de notificacao
|
Tipos de notificação
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
<CardDescription>
|
<CardDescription>
|
||||||
Escolha quais tipos de notificacao deseja receber.
|
Escolha quais tipos de notificação deseja receber.
|
||||||
{!preferences.isStaff && (
|
{!preferences.isStaff && (
|
||||||
<span className="block mt-1 text-amber-600">
|
<span className="block mt-1 text-amber-600">
|
||||||
Algumas notificacoes sao obrigatorias e nao podem ser desativadas.
|
Algumas notificações são obrigatórias e não podem ser desativadas.
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
|
|
@ -323,7 +323,7 @@ export function NotificationPreferencesForm({ isPortal = false }: NotificationPr
|
||||||
</Label>
|
</Label>
|
||||||
{notifType.required && (
|
{notifType.required && (
|
||||||
<Badge variant="secondary" className="ml-2 text-xs">
|
<Badge variant="secondary" className="ml-2 text-xs">
|
||||||
Obrigatorio
|
Obrigatório
|
||||||
</Badge>
|
</Badge>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -343,7 +343,7 @@ export function NotificationPreferencesForm({ isPortal = false }: NotificationPr
|
||||||
</Card>
|
</Card>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Botao de salvar */}
|
{/* Botão de salvar */}
|
||||||
<div className="flex justify-end">
|
<div className="flex justify-end">
|
||||||
<Button onClick={savePreferences} disabled={saving}>
|
<Button onClick={savePreferences} disabled={saving}>
|
||||||
{saving ? (
|
{saving ? (
|
||||||
|
|
@ -352,7 +352,7 @@ export function NotificationPreferencesForm({ isPortal = false }: NotificationPr
|
||||||
Salvando...
|
Salvando...
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
"Salvar preferencias"
|
"Salvar preferências"
|
||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue