Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
120 lines
3.9 KiB
TypeScript
120 lines
3.9 KiB
TypeScript
"use client";
|
|
|
|
import { useState } from "react";
|
|
import type { Award } from "@/payload-types";
|
|
import { Badge } from "@/components/ui/badge";
|
|
import {
|
|
Dialog,
|
|
DialogContent,
|
|
DialogDescription,
|
|
DialogHeader,
|
|
DialogTitle,
|
|
} from "@/components/ui/dialog";
|
|
import type { RibbonDesign } from "@/components/awards/ribbonDesign/types";
|
|
import { RibbonViewer } from "./RibbonViewer";
|
|
import { GrantAwardDialog } from "./GrantAwardDialog";
|
|
import { MedalIcon, RibbonIcon, ScrollTextIcon } from "lucide-react";
|
|
|
|
interface AwardPickerDialogProps {
|
|
awards: Award[];
|
|
recipientUserId: number;
|
|
recipientUsername: string;
|
|
open: boolean;
|
|
onOpenChange: (open: boolean) => void;
|
|
onGranted?: () => void;
|
|
}
|
|
|
|
/**
|
|
* Grid of grantable awards. Selecting a card closes this dialog and opens the
|
|
* GrantAwardDialog for that award with the recipient pre-filled.
|
|
*/
|
|
export function AwardPickerDialog({
|
|
awards,
|
|
recipientUserId,
|
|
recipientUsername,
|
|
open,
|
|
onOpenChange,
|
|
onGranted,
|
|
}: AwardPickerDialogProps) {
|
|
const [selectedAward, setSelectedAward] = useState<Award | null>(null);
|
|
|
|
function pick(award: Award) {
|
|
setSelectedAward(award);
|
|
onOpenChange(false);
|
|
}
|
|
|
|
return (
|
|
<>
|
|
<Dialog
|
|
open={open}
|
|
onOpenChange={(o) => {
|
|
if (!o) setSelectedAward(null);
|
|
onOpenChange(o);
|
|
}}
|
|
>
|
|
<DialogContent className="sm:max-w-2xl">
|
|
<DialogHeader>
|
|
<DialogTitle>Select an award</DialogTitle>
|
|
<DialogDescription>
|
|
Choose which award to grant to @{recipientUsername}.
|
|
</DialogDescription>
|
|
</DialogHeader>
|
|
{awards.length === 0 ? (
|
|
<p className="py-6 text-center text-sm text-muted-foreground">
|
|
No awards are available to grant.
|
|
</p>
|
|
) : (
|
|
<div className="grid max-h-[50vh] grid-cols-1 gap-3 overflow-y-auto sm:grid-cols-2 md:grid-cols-3">
|
|
{awards.map((award) => (
|
|
<button
|
|
key={award.id}
|
|
type="button"
|
|
onClick={() => pick(award)}
|
|
className="flex flex-col items-center gap-2 rounded-md border bg-card p-3 text-left transition-colors hover:border-primary/50 hover:bg-accent"
|
|
>
|
|
{award.type === "ribbon" && award.ribbonDesign ? (
|
|
<div className="flex h-16 w-full items-center justify-center">
|
|
<div className="w-32 max-w-full">
|
|
<RibbonViewer ribbonDesign={award.ribbonDesign as RibbonDesign} showRibbonOnly />
|
|
</div>
|
|
</div>
|
|
) : (
|
|
<span className="flex size-16 items-center justify-center rounded-md bg-muted">
|
|
{award.type === "medal" ? (
|
|
<MedalIcon className="size-6 text-muted-foreground" />
|
|
) : award.type === "commendation" ? (
|
|
<ScrollTextIcon className="size-6 text-muted-foreground" />
|
|
) : (
|
|
<RibbonIcon className="size-6 text-muted-foreground" />
|
|
)}
|
|
</span>
|
|
)}
|
|
<span className="line-clamp-2 text-center text-sm font-medium">
|
|
{award.name}
|
|
</span>
|
|
<Badge variant="secondary" className="w-fit capitalize">
|
|
{award.type ?? "award"}
|
|
</Badge>
|
|
</button>
|
|
))}
|
|
</div>
|
|
)}
|
|
</DialogContent>
|
|
</Dialog>
|
|
|
|
{selectedAward && (
|
|
<GrantAwardDialog
|
|
awardId={selectedAward.id}
|
|
awardName={selectedAward.name}
|
|
recipientUserId={recipientUserId}
|
|
recipientUsername={recipientUsername}
|
|
open
|
|
onOpenChange={(o) => {
|
|
if (!o) setSelectedAward(null);
|
|
}}
|
|
onGranted={onGranted}
|
|
/>
|
|
)}
|
|
</>
|
|
);
|
|
}
|