feat(frontend): Implement Heady Version mechanics with performance ratings
Some checks are pending
Deploy Elmeg / deploy (push) Waiting to run
Some checks are pending
Deploy Elmeg / deploy (push) Waiting to run
This commit is contained in:
parent
b879f28813
commit
c4905d7470
3 changed files with 63 additions and 35 deletions
|
|
@ -86,8 +86,9 @@ export default async function ShowDetailPage({ params }: { params: Promise<{ id:
|
|||
{show.performances && show.performances.length > 0 ? (
|
||||
<div className="space-y-2">
|
||||
{show.performances.map((perf: any) => (
|
||||
<div key={perf.id} className="flex items-center gap-2 group">
|
||||
<span className="text-muted-foreground w-6 text-right text-sm">{perf.position}.</span>
|
||||
<div key={perf.id} className="flex items-center justify-between group py-1 hover:bg-muted/50 rounded px-2 -mx-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-muted-foreground w-6 text-right text-sm font-mono">{perf.position}.</span>
|
||||
<div className="font-medium">
|
||||
{perf.song?.title || "Unknown Song"}
|
||||
{perf.segue && <span className="ml-1 text-muted-foreground">></span>}
|
||||
|
|
@ -95,7 +96,7 @@ export default async function ShowDetailPage({ params }: { params: Promise<{ id:
|
|||
|
||||
{/* Nicknames */}
|
||||
{perf.nicknames && perf.nicknames.length > 0 && (
|
||||
<div className="flex gap-1">
|
||||
<div className="flex gap-1 ml-2">
|
||||
{perf.nicknames.map((nick: any) => (
|
||||
<span key={nick.id} className="text-xs bg-yellow-100 text-yellow-800 px-1.5 py-0.5 rounded-full border border-yellow-200" title={nick.description}>
|
||||
"{nick.nickname}"
|
||||
|
|
@ -105,11 +106,25 @@ export default async function ShowDetailPage({ params }: { params: Promise<{ id:
|
|||
)}
|
||||
|
||||
{/* Suggest Nickname Button */}
|
||||
<div className="opacity-0 group-hover:opacity-100 transition-opacity">
|
||||
<SuggestNicknameDialog
|
||||
performanceId={perf.id}
|
||||
songTitle={perf.song?.title || "Song"}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Rating Column */}
|
||||
<div className="flex items-center">
|
||||
<SocialWrapper type="ratings">
|
||||
<EntityRating
|
||||
entityType="performance"
|
||||
entityId={perf.id}
|
||||
compact={true}
|
||||
/>
|
||||
</SocialWrapper>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
|
|
|
|||
|
|
@ -5,11 +5,12 @@ import { StarRating } from "@/components/ui/star-rating"
|
|||
import { getApiUrl } from "@/lib/api-config"
|
||||
|
||||
interface EntityRatingProps {
|
||||
entityType: "show" | "song" | "venue" | "tour"
|
||||
entityType: "show" | "song" | "venue" | "tour" | "performance"
|
||||
entityId: number
|
||||
compact?: boolean
|
||||
}
|
||||
|
||||
export function EntityRating({ entityType, entityId }: EntityRatingProps) {
|
||||
export function EntityRating({ entityType, entityId, compact = false }: EntityRatingProps) {
|
||||
const [userRating, setUserRating] = useState(0)
|
||||
const [averageRating, setAverageRating] = useState(0)
|
||||
const [loading, setLoading] = useState(false)
|
||||
|
|
@ -22,15 +23,7 @@ export function EntityRating({ entityType, entityId }: EntityRatingProps) {
|
|||
.catch(err => console.error("Failed to fetch avg rating", err))
|
||||
|
||||
// Fetch user rating (if logged in)
|
||||
const token = localStorage.getItem("token")
|
||||
if (token) {
|
||||
// We don't have a direct "get my rating" endpoint in the snippet I saw,
|
||||
// but we can infer it or maybe we need to add one.
|
||||
// For now, let's assume we can't easily get *my* rating without a specific endpoint
|
||||
// or filtering the list.
|
||||
// Actually, the backend `create_rating` checks for existing.
|
||||
// Let's just implement setting it for now.
|
||||
}
|
||||
// TODO: Implement fetching user's existing rating
|
||||
}, [entityType, entityId])
|
||||
|
||||
const handleRate = async (score: number) => {
|
||||
|
|
@ -58,7 +51,11 @@ export function EntityRating({ entityType, entityId }: EntityRatingProps) {
|
|||
|
||||
const data = await res.json()
|
||||
setUserRating(data.score)
|
||||
// Refresh average?
|
||||
|
||||
// Re-fetch average to keep it lively
|
||||
fetch(`${getApiUrl()}/social/ratings/average?${entityType}_id=${entityId}`)
|
||||
.then(res => res.json())
|
||||
.then(setAverageRating)
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
alert("Error submitting rating")
|
||||
|
|
@ -67,6 +64,19 @@ export function EntityRating({ entityType, entityId }: EntityRatingProps) {
|
|||
}
|
||||
}
|
||||
|
||||
if (compact) {
|
||||
return (
|
||||
<div className="flex items-center gap-1.5 opacity-80 hover:opacity-100 transition-opacity">
|
||||
<StarRating value={userRating} onChange={handleRate} size="sm" />
|
||||
{averageRating > 0 && (
|
||||
<span className="text-[10px] text-muted-foreground font-mono">
|
||||
{averageRating.toFixed(1)}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-2 border-l pl-4">
|
||||
<div className="flex flex-col">
|
||||
|
|
|
|||
|
|
@ -2,17 +2,20 @@ import { useState } from "react"
|
|||
import { Star } from "lucide-react"
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
|
||||
interface RatingProps {
|
||||
value: number
|
||||
onChange?: (value: number) => void
|
||||
readonly?: boolean
|
||||
className?: string
|
||||
size?: "sm" | "md"
|
||||
}
|
||||
|
||||
export function StarRating({ value, onChange, readonly = false, className }: RatingProps) {
|
||||
export function StarRating({ value, onChange, readonly = false, className, size = "md" }: RatingProps) {
|
||||
const [hoverValue, setHoverValue] = useState<number | null>(null)
|
||||
|
||||
const stars = Array.from({ length: 10 }, (_, i) => i + 1)
|
||||
const starSize = size === "sm" ? "h-3 w-3" : "h-4 w-4"
|
||||
|
||||
return (
|
||||
<div className={cn("flex gap-0.5", className)}>
|
||||
|
|
@ -31,7 +34,7 @@ export function StarRating({ value, onChange, readonly = false, className }: Rat
|
|||
>
|
||||
<Star
|
||||
className={cn(
|
||||
"h-4 w-4",
|
||||
starSize,
|
||||
(hoverValue !== null ? star <= hoverValue : star <= value)
|
||||
? "fill-primary text-primary"
|
||||
: "fill-muted text-muted-foreground"
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue