sistema-de-chamados/src/app/rate/[token]/page.tsx
esdrasrenan 7ecb4c1110 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>
2025-12-07 21:09:02 -03:00

270 lines
9.4 KiB
TypeScript

"use client"
import { useEffect, useState } from "react"
import { useParams, useSearchParams } from "next/navigation"
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card"
import { Button } from "@/components/ui/button"
import { Textarea } from "@/components/ui/textarea"
import { Label } from "@/components/ui/label"
import { Loader2, CheckCircle, Star, AlertCircle } from "lucide-react"
export default function RatePage() {
const params = useParams()
const searchParams = useSearchParams()
const token = params.token as string
const success = searchParams.get("success") === "true"
const alreadyRated = searchParams.get("already_rated") === "true"
const existingRatingStr = searchParams.get("existing_rating")
const initialRatingStr = searchParams.get("rating")
const existingRating = existingRatingStr ? parseInt(existingRatingStr, 10) : null
const initialRating = initialRatingStr ? parseInt(initialRatingStr, 10) : null
const [loading, setLoading] = useState(false)
const [submitted, setSubmitted] = useState(success)
const [rating, setRating] = useState<number>(initialRating ?? existingRating ?? 0)
const [hoveredRating, setHoveredRating] = useState<number>(0)
const [comment, setComment] = useState("")
const [error, setError] = useState<string | null>(null)
// Se já avaliou, mostra mensagem
if (alreadyRated && existingRating) {
return (
<div className="min-h-screen bg-background flex items-center justify-center p-4">
<Card className="w-full max-w-md">
<CardHeader className="text-center">
<div className="flex justify-center mb-4">
<CheckCircle className="h-16 w-16 text-emerald-500" />
</div>
<CardTitle>Chamado avaliado</CardTitle>
<CardDescription>
Você avaliou este chamado anteriormente.
</CardDescription>
</CardHeader>
<CardContent className="text-center">
<div className="flex justify-center gap-1 mb-4">
{[1, 2, 3, 4, 5].map((star) => (
<Star
key={star}
className={`h-8 w-8 ${
star <= existingRating ? "fill-amber-400 text-amber-400" : "text-muted"
}`}
/>
))}
</div>
<p className="text-muted-foreground text-sm">
Sua avaliação: {existingRating} estrela{existingRating > 1 ? "s" : ""}
</p>
</CardContent>
</Card>
</div>
)
}
// Se acabou de avaliar, mostra formulário para comentário
if (submitted && rating > 0) {
return (
<div className="min-h-screen bg-background flex items-center justify-center p-4">
<Card className="w-full max-w-md">
<CardHeader className="text-center">
<div className="flex justify-center mb-4">
<CheckCircle className="h-16 w-16 text-emerald-500" />
</div>
<CardTitle>Obrigado pela avaliação!</CardTitle>
<CardDescription>
Sua opinião é muito importante para nós.
</CardDescription>
</CardHeader>
<CardContent className="space-y-6">
<div className="flex justify-center gap-1">
{[1, 2, 3, 4, 5].map((star) => (
<Star
key={star}
className={`h-8 w-8 ${
star <= rating ? "fill-amber-400 text-amber-400" : "text-muted"
}`}
/>
))}
</div>
<div className="space-y-2">
<Label htmlFor="comment">Gostaria de deixar um comentário? (opcional)</Label>
<Textarea
id="comment"
placeholder="Conte-nos mais sobre sua experiência..."
value={comment}
onChange={(e) => setComment(e.target.value)}
rows={4}
/>
</div>
{error && (
<div className="text-destructive text-sm text-center">{error}</div>
)}
<div className="flex gap-2">
<Button
variant="outline"
className="flex-1"
onClick={() => {
window.close()
}}
>
Fechar
</Button>
<Button
className="flex-1"
disabled={loading}
onClick={async () => {
if (!comment.trim()) {
window.close()
return
}
setLoading(true)
setError(null)
try {
const response = await fetch("/api/tickets/rate", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ token, rating, comment }),
})
if (!response.ok) {
const data = await response.json()
throw new Error(data.error || "Erro ao enviar comentário")
}
window.close()
} catch (err) {
setError(err instanceof Error ? err.message : "Erro desconhecido")
} finally {
setLoading(false)
}
}}
>
{loading ? (
<>
<Loader2 className="h-4 w-4 mr-2 animate-spin" />
Enviando...
</>
) : (
"Enviar comentário"
)}
</Button>
</div>
</CardContent>
</Card>
</div>
)
}
// Formulário de avaliação
return (
<div className="min-h-screen bg-background flex items-center justify-center p-4">
<Card className="w-full max-w-md">
<CardHeader className="text-center">
<div className="flex items-center justify-center gap-2 mb-4">
<div className="w-10 h-10 bg-primary rounded-lg flex items-center justify-center">
<span className="text-primary-foreground font-bold text-lg">R</span>
</div>
<span className="text-xl font-semibold">Raven</span>
</div>
<CardTitle>Como foi o atendimento?</CardTitle>
<CardDescription>
Sua avaliação nos ajuda a melhorar nosso serviço.
</CardDescription>
</CardHeader>
<CardContent className="space-y-6">
{/* Estrelas */}
<div className="flex justify-center gap-2">
{[1, 2, 3, 4, 5].map((star) => (
<button
key={star}
type="button"
className="p-1 transition-transform hover:scale-110 focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2 rounded"
onMouseEnter={() => setHoveredRating(star)}
onMouseLeave={() => setHoveredRating(0)}
onClick={() => setRating(star)}
>
<Star
className={`h-10 w-10 transition-colors ${
star <= (hoveredRating || rating)
? "fill-amber-400 text-amber-400"
: "text-muted hover:text-amber-200"
}`}
/>
</button>
))}
</div>
{/* Labels */}
<div className="flex justify-between text-xs text-muted-foreground px-2">
<span>Ruim</span>
<span>Excelente</span>
</div>
{/* Comentário */}
<div className="space-y-2">
<Label htmlFor="comment">Comentário (opcional)</Label>
<Textarea
id="comment"
placeholder="Conte-nos mais sobre sua experiência..."
value={comment}
onChange={(e) => setComment(e.target.value)}
rows={4}
/>
</div>
{error && (
<div className="flex items-center gap-2 text-destructive text-sm">
<AlertCircle className="h-4 w-4" />
{error}
</div>
)}
{/* Botão */}
<Button
className="w-full"
size="lg"
disabled={rating === 0 || loading}
onClick={async () => {
setLoading(true)
setError(null)
try {
const response = await fetch("/api/tickets/rate", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ token, rating, comment }),
})
if (!response.ok) {
const data = await response.json()
throw new Error(data.error || "Erro ao enviar avaliação")
}
setSubmitted(true)
} catch (err) {
setError(err instanceof Error ? err.message : "Erro desconhecido")
} finally {
setLoading(false)
}
}}
>
{loading ? (
<>
<Loader2 className="h-4 w-4 mr-2 animate-spin" />
Enviando...
</>
) : (
"Enviar avaliação"
)}
</Button>
</CardContent>
</Card>
</div>
)
}