Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
} from "@atlaskit/pragmatic-drag-and-drop-hitbox/closest-edge"
import { DropIndicator } from "@atlaskit/pragmatic-drag-and-drop-react-drop-indicator/box"
import { zodResolver } from "@hookform/resolvers/zod"
import { EmptyState } from "@repo/ui/empty-state"
import { useServerFn } from "@tanstack/react-start"
import {
CheckCircle2,
Expand All @@ -30,7 +31,6 @@ import { useEffect, useRef, useState } from "react"
import { useForm } from "react-hook-form"
import { toast } from "sonner"
import { z } from "zod"
import { OrganizerEmptyState } from "@/components/organizer/empty-state"
import {
AlertDialog,
AlertDialogAction,
Expand Down Expand Up @@ -921,23 +921,29 @@ export function RegistrationQuestionsEditor({
</CardHeader>
<CardContent>
{questions.length === 0 ? (
<OrganizerEmptyState
variant="plain"
icon={HelpCircle}
title={
questionTarget === "volunteer"
? "No volunteer signup questions yet"
: "No registration questions yet"
}
description={
questionTarget === "volunteer"
<EmptyState.Root className="gap-0 px-6 py-12">
<EmptyState.Icon className="mb-4 rounded-lg">
<HelpCircle className="h-6 w-6 text-muted-foreground" />
</EmptyState.Icon>
<EmptyState.Title className="break-normal text-wrap text-lg font-semibold tracking-normal">
<h3>
{questionTarget === "volunteer"
? "No volunteer signup questions yet"
: "No registration questions yet"}
</h3>
</EmptyState.Title>
<EmptyState.Description className="mt-2 max-w-md break-normal text-wrap">
{questionTarget === "volunteer"
? "Ask volunteers about availability, certifications, shirt size, or preferred roles."
: "Ask athletes about shirt size, experience level, teammate details, or other registration needs."
}
actionLabel="Add Question"
actionIcon={<Plus className="mr-2 h-4 w-4" />}
onAction={handleAddNew}
/>
: "Ask athletes about shirt size, experience level, teammate details, or other registration needs."}
</EmptyState.Description>
<EmptyState.Actions className="mt-5 flex-col flex-nowrap items-stretch justify-start sm:flex-row sm:items-center">
<Button onClick={handleAddNew}>
<Plus className="mr-2 h-4 w-4" />
Add Question
</Button>
</EmptyState.Actions>
</EmptyState.Root>
) : (
<div className="space-y-2">
{questions.map((question, index) => (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@
// @lat: [[crew#Copy Prior Event Setup]]
import { Ban, CheckCircle2, Copy, Loader2 } from "lucide-react"
import { useEffect, useState } from "react"
import { Metric } from "@/components/ui/metric"
import {
getCrewCopyPriorEventPageFn,
type CrewCopyPriorEventPageData,
getCrewCopyPriorEventPageFn,
} from "@/server-fns/crew-copy-event-fns"

interface CrewCopyPriorEventPanelProps {
Expand Down Expand Up @@ -103,27 +104,39 @@ export function CrewCopyPriorEventPanel({
{preview.targetEvent.startDate ?? "target date"}
</p>
<div className="mt-3 grid grid-cols-3 gap-2 text-center text-xs">
<Metric
label="Copy"
value={
preview.summary.filter((item) => item.status === "copy")
.length
}
/>
<Metric
label="Skip"
value={
preview.summary.filter((item) => item.status === "skip")
.length
}
/>
<Metric
label="Deny"
value={
preview.summary.filter((item) => item.status === "deny")
.length
}
/>
<Metric.Inset className="gap-0 border bg-card px-2 py-2">
<Metric.Label className="text-xs font-normal text-muted-foreground">
Copy
</Metric.Label>
<Metric.Value className="order-first text-base leading-6 tracking-normal">
{
preview.summary.filter((item) => item.status === "copy")
.length
}
</Metric.Value>
</Metric.Inset>
<Metric.Inset className="gap-0 border bg-card px-2 py-2">
<Metric.Label className="text-xs font-normal text-muted-foreground">
Skip
</Metric.Label>
<Metric.Value className="order-first text-base leading-6 tracking-normal">
{
preview.summary.filter((item) => item.status === "skip")
.length
}
</Metric.Value>
</Metric.Inset>
<Metric.Inset className="gap-0 border bg-card px-2 py-2">
<Metric.Label className="text-xs font-normal text-muted-foreground">
Deny
</Metric.Label>
<Metric.Value className="order-first text-base leading-6 tracking-normal">
{
preview.summary.filter((item) => item.status === "deny")
.length
}
</Metric.Value>
</Metric.Inset>
</div>
</div>
) : null}
Expand Down Expand Up @@ -212,15 +225,6 @@ export function CrewCopyPriorEventPanel({
)
}

function Metric({ label, value }: { label: string; value: number }) {
return (
<div className="rounded-md border bg-card px-2 py-2">
<div className="text-base font-semibold">{value}</div>
<div className="text-muted-foreground">{label}</div>
</div>
)
}

function StatusPill({ status }: { status: "copy" | "skip" | "deny" }) {
const Icon = status === "copy" ? CheckCircle2 : status === "deny" ? Ban : Copy
const label =
Expand Down
37 changes: 25 additions & 12 deletions apps/crew/src/components/crew-templates/crew-template-panel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// @lat: [[crew#Role And Shift Templates]]
import { Save, WandSparkles } from "lucide-react"
import { useMemo, useState } from "react"
import { Metric } from "@/components/ui/metric"
import { VOLUNTEER_ROLE_LABELS } from "@/db/schemas/volunteers"
import {
buildCrewTemplateApplyPlan,
Expand Down Expand Up @@ -116,9 +117,30 @@ export function CrewTemplatePanel({
{selectedTemplate.description}
</p>
<div className="mt-3 grid grid-cols-3 gap-2 text-center text-xs">
<Metric label="Roles" value={preview.summary.roles} />
<Metric label="New" value={preview.summary.newShifts} />
<Metric label="Skipped" value={preview.summary.duplicateShifts} />
<Metric.Inset className="gap-0 border bg-card px-2 py-2">
<Metric.Label className="text-xs font-normal text-muted-foreground">
Roles
</Metric.Label>
<Metric.Value className="order-first text-xs leading-4 tracking-normal">
{preview.summary.roles}
</Metric.Value>
</Metric.Inset>
<Metric.Inset className="gap-0 border bg-card px-2 py-2">
<Metric.Label className="text-xs font-normal text-muted-foreground">
New
</Metric.Label>
<Metric.Value className="order-first text-xs leading-4 tracking-normal">
{preview.summary.newShifts}
</Metric.Value>
</Metric.Inset>
<Metric.Inset className="gap-0 border bg-card px-2 py-2">
<Metric.Label className="text-xs font-normal text-muted-foreground">
Skipped
</Metric.Label>
<Metric.Value className="order-first text-xs leading-4 tracking-normal">
{preview.summary.duplicateShifts}
</Metric.Value>
</Metric.Inset>
</div>
</div>

Expand Down Expand Up @@ -263,15 +285,6 @@ export function CrewTemplatePanel({
)
}

function Metric({ label, value }: { label: string; value: number }) {
return (
<div className="rounded-md border bg-card px-2 py-2">
<div className="font-semibold">{value}</div>
<div className="text-muted-foreground">{label}</div>
</div>
)
}

function ShiftStatus({
status,
}: {
Expand Down
76 changes: 55 additions & 21 deletions apps/crew/src/components/crew/crew-import-tabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog"
import { Metric } from "@/components/ui/metric"
import {
getImportFields,
inferColumnMapping,
Expand Down Expand Up @@ -582,19 +583,46 @@ function PreviewPanel({
</div>

<div className="grid gap-3 sm:grid-cols-3">
<SummaryMetric
label={`Ready ${importCopy.nounPlural}`}
value={impact.readyCount}
/>
<SummaryMetric label="Warnings" value={preview.warningCount} />
<SummaryMetric label="Need review" value={impact.blockedCount} />
<Metric.Inset className="border bg-background">
<Metric.Label className="font-normal text-muted-foreground">
{`Ready ${importCopy.nounPlural}`}
</Metric.Label>
<Metric.Value>{impact.readyCount}</Metric.Value>
</Metric.Inset>
<Metric.Inset className="border bg-background">
<Metric.Label className="font-normal text-muted-foreground">
Warnings
</Metric.Label>
<Metric.Value>{preview.warningCount}</Metric.Value>
</Metric.Inset>
<Metric.Inset className="border bg-background">
<Metric.Label className="font-normal text-muted-foreground">
Need review
</Metric.Label>
<Metric.Value>{impact.blockedCount}</Metric.Value>
</Metric.Inset>
</div>

{applyResult ? (
<div className="grid gap-3 sm:grid-cols-3">
<SummaryMetric label="Applied" value={appliedCount} />
<SummaryMetric label="Added" value={applyResult.createdCount} />
<SummaryMetric label="Skipped" value={applyResult.skippedCount} />
<Metric.Inset className="border bg-background">
<Metric.Label className="font-normal text-muted-foreground">
Applied
</Metric.Label>
<Metric.Value>{appliedCount}</Metric.Value>
</Metric.Inset>
<Metric.Inset className="border bg-background">
<Metric.Label className="font-normal text-muted-foreground">
Added
</Metric.Label>
<Metric.Value>{applyResult.createdCount}</Metric.Value>
</Metric.Inset>
<Metric.Inset className="border bg-background">
<Metric.Label className="font-normal text-muted-foreground">
Skipped
</Metric.Label>
<Metric.Value>{applyResult.skippedCount}</Metric.Value>
</Metric.Inset>
</div>
) : null}

Expand Down Expand Up @@ -627,9 +655,24 @@ function PreviewPanel({
<div className="space-y-3 text-sm">
<p>{importCopy.changeSummary}</p>
<div className="grid gap-3 sm:grid-cols-3">
<SummaryMetric label="Ready" value={impact.readyCount} />
<SummaryMetric label="Skipped" value={impact.skippedCount} />
<SummaryMetric label="Need review" value={impact.blockedCount} />
<Metric.Inset className="border bg-background">
<Metric.Label className="font-normal text-muted-foreground">
Ready
</Metric.Label>
<Metric.Value>{impact.readyCount}</Metric.Value>
</Metric.Inset>
<Metric.Inset className="border bg-background">
<Metric.Label className="font-normal text-muted-foreground">
Skipped
</Metric.Label>
<Metric.Value>{impact.skippedCount}</Metric.Value>
</Metric.Inset>
<Metric.Inset className="border bg-background">
<Metric.Label className="font-normal text-muted-foreground">
Need review
</Metric.Label>
<Metric.Value>{impact.blockedCount}</Metric.Value>
</Metric.Inset>
</div>
{preview.warningCount > 0 ? (
<p className="text-amber-700">
Expand Down Expand Up @@ -954,15 +997,6 @@ function IssueList({
)
}

function SummaryMetric({ label, value }: { label: string; value: number }) {
return (
<div className="rounded-md border bg-background p-3">
<p className="text-sm text-muted-foreground">{label}</p>
<p className="mt-1 text-2xl font-semibold">{value}</p>
</div>
)
}

function StatusBadge({ status }: { status: string }) {
return (
<span className="inline-flex w-fit rounded-md border bg-background px-2 py-1 text-xs font-medium">
Expand Down
Loading
Loading