Mobile responsiveness fixes for pipeline UI redesign

- Detail page header: stack on mobile, icon-only Advanced button on small screens
- InlineEditableText: show pencil icon on mobile (not hover-only)
- EditableCard: show Edit button on mobile (not hover-only)
- PipelineFlowchart: add right-edge fade gradient as scroll hint on mobile
- Summary cards: always 3-col grid (compact on mobile)
- Track switcher: add overflow-x-auto for horizontal scroll

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-14 01:59:42 +01:00
parent 59f90ccc37
commit ae0ac58547
4 changed files with 124 additions and 113 deletions

View File

@@ -199,127 +199,132 @@ export default function PipelineDetailPage() {
return (
<div className="space-y-6">
{/* Header */}
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<Link href="/admin/rounds/pipelines">
<Button variant="ghost" size="icon">
<ArrowLeft className="h-4 w-4" />
</Button>
</Link>
<div>
<div className="flex items-center gap-2">
<InlineEditableText
value={pipeline.name}
onSave={(newName) => updatePipeline({ name: newName })}
variant="h1"
placeholder="Untitled Pipeline"
disabled={isUpdating}
/>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<button
className={cn(
'inline-flex items-center gap-1 text-[10px] px-2 py-1 rounded-full transition-colors',
statusColors[pipeline.status] ?? '',
'hover:opacity-80'
)}
>
{pipeline.status}
<ChevronDown className="h-3 w-3" />
</button>
</DropdownMenuTrigger>
<DropdownMenuContent align="start">
<DropdownMenuItem
onClick={() => handleStatusChange('DRAFT')}
disabled={pipeline.status === 'DRAFT' || updateMutation.isPending}
>
Draft
</DropdownMenuItem>
<DropdownMenuItem
onClick={() => handleStatusChange('ACTIVE')}
disabled={pipeline.status === 'ACTIVE' || updateMutation.isPending}
>
Active
</DropdownMenuItem>
<DropdownMenuItem
onClick={() => handleStatusChange('CLOSED')}
disabled={pipeline.status === 'CLOSED' || updateMutation.isPending}
>
Closed
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem
onClick={() => handleStatusChange('ARCHIVED')}
disabled={pipeline.status === 'ARCHIVED' || updateMutation.isPending}
>
Archived
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
<div className="flex items-center gap-1 text-sm">
<span className="text-muted-foreground">slug:</span>
<InlineEditableText
value={pipeline.slug}
onSave={(newSlug) => updatePipeline({ slug: newSlug })}
variant="mono"
placeholder="pipeline-slug"
disabled={isUpdating}
/>
<div className="space-y-3">
<div className="flex items-start justify-between gap-2">
<div className="flex items-start gap-3 min-w-0">
<Link href="/admin/rounds/pipelines" className="mt-1">
<Button variant="ghost" size="icon" className="h-8 w-8 shrink-0">
<ArrowLeft className="h-4 w-4" />
</Button>
</Link>
<div className="min-w-0">
<div className="flex flex-wrap items-center gap-2">
<InlineEditableText
value={pipeline.name}
onSave={(newName) => updatePipeline({ name: newName })}
variant="h1"
placeholder="Untitled Pipeline"
disabled={isUpdating}
/>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<button
className={cn(
'inline-flex items-center gap-1 text-[10px] px-2 py-1 rounded-full transition-colors shrink-0',
statusColors[pipeline.status] ?? '',
'hover:opacity-80'
)}
>
{pipeline.status}
<ChevronDown className="h-3 w-3" />
</button>
</DropdownMenuTrigger>
<DropdownMenuContent align="start">
<DropdownMenuItem
onClick={() => handleStatusChange('DRAFT')}
disabled={pipeline.status === 'DRAFT' || updateMutation.isPending}
>
Draft
</DropdownMenuItem>
<DropdownMenuItem
onClick={() => handleStatusChange('ACTIVE')}
disabled={pipeline.status === 'ACTIVE' || updateMutation.isPending}
>
Active
</DropdownMenuItem>
<DropdownMenuItem
onClick={() => handleStatusChange('CLOSED')}
disabled={pipeline.status === 'CLOSED' || updateMutation.isPending}
>
Closed
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem
onClick={() => handleStatusChange('ARCHIVED')}
disabled={pipeline.status === 'ARCHIVED' || updateMutation.isPending}
>
Archived
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
<div className="flex items-center gap-1 text-sm">
<span className="text-muted-foreground">slug:</span>
<InlineEditableText
value={pipeline.slug}
onSave={(newSlug) => updatePipeline({ slug: newSlug })}
variant="mono"
placeholder="pipeline-slug"
disabled={isUpdating}
/>
</div>
</div>
</div>
</div>
<div className="flex items-center gap-2">
<Link href={`/admin/rounds/pipeline/${pipelineId}/advanced` as Route}>
<Button variant="outline" size="sm">
<Settings2 className="h-4 w-4 mr-1" />
Advanced
</Button>
</Link>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" size="icon" className="h-8 w-8">
<MoreHorizontal className="h-4 w-4" />
<div className="flex items-center gap-1 sm:gap-2 shrink-0">
<Link href={`/admin/rounds/pipeline/${pipelineId}/advanced` as Route}>
<Button variant="outline" size="icon" className="h-8 w-8 sm:hidden">
<Settings2 className="h-4 w-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
{pipeline.status === 'DRAFT' && (
<DropdownMenuItem
disabled={publishMutation.isPending}
onClick={() => publishMutation.mutate({ id: pipelineId })}
>
{publishMutation.isPending ? (
<Loader2 className="h-4 w-4 mr-2 animate-spin" />
) : (
<Rocket className="h-4 w-4 mr-2" />
)}
Publish
</DropdownMenuItem>
)}
{pipeline.status === 'ACTIVE' && (
<Button variant="outline" size="sm" className="hidden sm:inline-flex">
<Settings2 className="h-4 w-4 mr-1" />
Advanced
</Button>
</Link>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" size="icon" className="h-8 w-8">
<MoreHorizontal className="h-4 w-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
{pipeline.status === 'DRAFT' && (
<DropdownMenuItem
disabled={publishMutation.isPending}
onClick={() => publishMutation.mutate({ id: pipelineId })}
>
{publishMutation.isPending ? (
<Loader2 className="h-4 w-4 mr-2 animate-spin" />
) : (
<Rocket className="h-4 w-4 mr-2" />
)}
Publish
</DropdownMenuItem>
)}
{pipeline.status === 'ACTIVE' && (
<DropdownMenuItem
disabled={updateMutation.isPending}
onClick={() => handleStatusChange('CLOSED')}
>
Close Pipeline
</DropdownMenuItem>
)}
<DropdownMenuSeparator />
<DropdownMenuItem
disabled={updateMutation.isPending}
onClick={() => handleStatusChange('CLOSED')}
onClick={() => handleStatusChange('ARCHIVED')}
>
Close Pipeline
<Archive className="h-4 w-4 mr-2" />
Archive
</DropdownMenuItem>
)}
<DropdownMenuSeparator />
<DropdownMenuItem
disabled={updateMutation.isPending}
onClick={() => handleStatusChange('ARCHIVED')}
>
<Archive className="h-4 w-4 mr-2" />
Archive
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</DropdownMenuContent>
</DropdownMenu>
</div>
</div>
</div>
{/* Pipeline Summary */}
<div className="grid gap-4 sm:grid-cols-3">
<div className="grid gap-3 grid-cols-3">
<Card>
<CardContent className="pt-4">
<div className="flex items-center gap-2">
@@ -369,7 +374,7 @@ export default function PipelineDetailPage() {
{/* Track Switcher (only if multiple tracks) */}
{pipeline.tracks.length > 1 && (
<div className="flex items-center gap-2 flex-wrap">
<div className="flex items-center gap-2 flex-wrap overflow-x-auto pb-1">
{pipeline.tracks
.sort((a, b) => a.sortOrder - b.sortOrder)
.map((track) => (