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
170 changes: 115 additions & 55 deletions app/(dashboard)/plans/plans-client.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@ import { Button } from '@/components/ui/button'
import { Card, CardContent } from '@/components/ui/card'
import { Badge } from '@/components/ui/badge'
import { Progress } from '@/components/ui/progress'
import { Switch } from '@/components/ui/switch'
import { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs'
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
import { Plus, Calendar, Users, Filter, ArrowUpRight, Clock } from 'lucide-react'
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'
import { Plus, Calendar, Users, Filter, ArrowUpRight, Clock, List, LayoutGrid } from 'lucide-react'
import type { CodePlanStatus, CodePlanType } from '@/lib/types'
import { cn, formatDate } from '@/lib/utils'
import { PlanCreatePanel } from './plan-create-panel'
Expand Down Expand Up @@ -57,6 +59,7 @@ export function PlansClient({ plans, products, currentUserId }: { plans: Plan[];
const [mineOnly, setMineOnly] = useState(false)
const [statusFilter, setStatusFilter] = useState<CodePlanStatus | 'all' | 'open'>('open')
const [productFilter, setProductFilter] = useState<string>('all')
const [view, setView] = useState<'card' | 'list'>('card')

const filteredPlans = plans.filter((plan) => {
if (mineOnly && plan.ownerId !== currentUserId && !plan.assigneeIds.includes(currentUserId ?? '')) return false
Expand Down Expand Up @@ -131,13 +134,10 @@ export function PlansClient({ plans, products, currentUserId }: { plans: Plan[];
<TabsTrigger value="completed">Completed</TabsTrigger>
</TabsList>
</Tabs>
<Button
variant={mineOnly ? 'secondary' : 'outline'}
size="sm"
onClick={() => setMineOnly((v) => !v)}
>
My plans
</Button>
<label className="flex items-center gap-2 text-sm cursor-pointer select-none">
<Switch checked={mineOnly} onCheckedChange={setMineOnly} />
<span className="text-muted-foreground">My plans</span>
</label>
<div className="flex items-center gap-2 sm:ml-auto">
<Filter className="h-4 w-4 text-muted-foreground" />
<Select value={productFilter} onValueChange={setProductFilter}>
Expand All @@ -151,20 +151,104 @@ export function PlansClient({ plans, products, currentUserId }: { plans: Plan[];
))}
</SelectContent>
</Select>
<div className="flex border border-border rounded-md">
<Button variant={view === 'card' ? 'secondary' : 'ghost'} size="icon" className="h-9 w-9 rounded-r-none" title="Card view" onClick={() => setView('card')}>
<LayoutGrid className="h-4 w-4" />
</Button>
<Button variant={view === 'list' ? 'secondary' : 'ghost'} size="icon" className="h-9 w-9 rounded-l-none" title="List view" onClick={() => setView('list')}>
<List className="h-4 w-4" />
</Button>
</div>
</div>
</div>

{/* Plans List */}
<div className="space-y-4">
{filteredPlans.map((plan) => (
<Card key={plan.id} className="bg-card border-border hover:border-muted-foreground/30 transition-colors">
<CardContent className="p-6">
<div className="flex flex-col gap-4 lg:flex-row lg:items-start lg:justify-between">
<div className="flex-1 min-w-0">
<div className="flex items-center gap-3 flex-wrap mb-2">
<Link href={`/plans/${plan.id}`} className="text-lg font-semibold hover:text-accent transition-colors">
{/* Plans */}
{filteredPlans.length === 0 ? (
<Card className="border-dashed">
<CardContent className="flex flex-col items-center justify-center py-12">
<Plus className="h-12 w-12 text-muted-foreground mb-4" />
<h3 className="text-lg font-medium mb-1">No plans found</h3>
<p className="text-sm text-muted-foreground mb-4">
{statusFilter !== 'all' || productFilter !== 'all'
? 'Try adjusting your filters'
: 'Create your first code plan to get started'}
</p>
<PlanCreatePanel
products={products}
defaultProductId={productFilter !== 'all' ? productFilter : undefined}
trigger={
<Button>
<Plus className="mr-2 h-4 w-4" />
Create Plan
</Button>
}
/>
</CardContent>
</Card>
) : view === 'list' ? (
<Card className="bg-card border-border">
<Table>
<TableHeader>
<TableRow className="hover:bg-transparent">
<TableHead>Plan</TableHead>
<TableHead>Type</TableHead>
<TableHead>Status</TableHead>
<TableHead>Product</TableHead>
<TableHead>Progress</TableHead>
<TableHead>Deadline</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{filteredPlans.map((plan) => (
<TableRow key={plan.id}>
<TableCell className="max-w-[240px]">
<Link
href={`/plans/${plan.id}`}
title={plan.title}
className="font-medium hover:text-accent transition-colors truncate block"
>
{plan.title}
</Link>
</TableCell>
<TableCell>
<Badge variant="secondary" className={cn('text-xs', typeStyles[plan.type])}>
{typeLabels[plan.type]}
</Badge>
</TableCell>
<TableCell>
<Badge variant="secondary" className={cn('text-xs', statusStyles[plan.status])}>
{plan.status}
</Badge>
</TableCell>
<TableCell className="text-sm text-muted-foreground max-w-[160px]">
<div className="truncate" title={plan.productName}>{plan.productName}</div>
</TableCell>
<TableCell>
<div className="flex items-center gap-2 w-32">
<Progress value={plan.progress} className="h-1.5" />
<span className="text-xs text-muted-foreground shrink-0">{plan.progress}%</span>
</div>
</TableCell>
<TableCell className="text-sm text-muted-foreground">
{plan.deadline ? formatDate(plan.deadline) : '-'}
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</Card>
) : (
<div className="grid gap-4 md:grid-cols-2 xl:grid-cols-3">
{filteredPlans.map((plan) => (
<Card key={plan.id} className="bg-card border-border hover:border-muted-foreground/30 transition-colors flex flex-col">
<CardContent className="p-6 flex flex-col gap-4 flex-1">
<div>
<div className="mb-2">
<Link href={`/plans/${plan.id}`} className="text-base font-semibold hover:text-accent transition-colors line-clamp-1">
{plan.title}
</Link>
</div>
<div className="flex items-center gap-1.5 flex-wrap mb-2">
<Badge variant="secondary" className={cn('text-xs', typeStyles[plan.type])}>
{typeLabels[plan.type]}
</Badge>
Expand All @@ -173,7 +257,7 @@ export function PlansClient({ plans, products, currentUserId }: { plans: Plan[];
</Badge>
</div>
<p className="text-sm text-muted-foreground mb-3 line-clamp-2">{plan.description}</p>
<div className="flex items-center gap-4 text-sm text-muted-foreground flex-wrap">
<div className="flex items-center gap-3 text-sm text-muted-foreground flex-wrap">
<span className="text-foreground font-medium">{plan.productName}</span>
{plan.deadline && (
<div className="flex items-center gap-1">
Expand All @@ -189,7 +273,7 @@ export function PlansClient({ plans, products, currentUserId }: { plans: Plan[];
)}
</div>
</div>
<div className="w-full lg:w-48">
<div className="mt-auto">
<div className="flex items-center justify-between text-sm mb-2">
<span className="text-muted-foreground">Progress</span>
<span className="font-medium">{plan.progress}%</span>
Expand All @@ -199,42 +283,18 @@ export function PlansClient({ plans, products, currentUserId }: { plans: Plan[];
{plan.completedTaskCount} / {plan.taskCount} tasks
</div>
</div>
</div>
{plan.tags.length > 0 && (
<div className="flex flex-wrap gap-1.5 mt-4 pt-4 border-t border-border">
{plan.tags.map((tag) => (
<Badge key={tag} variant="outline" className="text-xs">{tag}</Badge>
))}
</div>
)}
</CardContent>
</Card>
))}

{filteredPlans.length === 0 && (
<Card className="border-dashed">
<CardContent className="flex flex-col items-center justify-center py-12">
<Plus className="h-12 w-12 text-muted-foreground mb-4" />
<h3 className="text-lg font-medium mb-1">No plans found</h3>
<p className="text-sm text-muted-foreground mb-4">
{statusFilter !== 'all' || productFilter !== 'all'
? 'Try adjusting your filters'
: 'Create your first code plan to get started'}
</p>
<PlanCreatePanel
products={products}
defaultProductId={productFilter !== 'all' ? productFilter : undefined}
trigger={
<Button>
<Plus className="mr-2 h-4 w-4" />
Create Plan
</Button>
}
/>
</CardContent>
</Card>
)}
</div>
{plan.tags.length > 0 && (
<div className="flex flex-wrap gap-1.5 pt-4 border-t border-border">
{plan.tags.map((tag) => (
<Badge key={tag} variant="outline" className="text-xs">{tag}</Badge>
))}
</div>
)}
</CardContent>
</Card>
))}
</div>
)}
</>
)
}
23 changes: 14 additions & 9 deletions app/(dashboard)/tasks/tasks-client.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { Avatar, AvatarFallback } from '@/components/ui/avatar'
import { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs'
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'
import { Switch } from '@/components/ui/switch'
import { Clock, Filter, CheckCircle2, Circle, Play, LayoutGrid, List, Plus } from 'lucide-react'
import type { TaskStatus } from '@/lib/types'
import { cn } from '@/lib/utils'
Expand Down Expand Up @@ -285,13 +286,13 @@ export function TasksClient({
<TabsTrigger value="done">Done</TabsTrigger>
</TabsList>
</Tabs>
<Button
variant={mineOnly ? 'secondary' : 'outline'}
size="sm"
onClick={() => { setMineOnly((v) => !v); setPage(0) }}
>
Assigned to me
</Button>
<label className="flex items-center gap-2 text-sm cursor-pointer select-none">
<Switch
checked={mineOnly}
onCheckedChange={(v) => { setMineOnly(v); setPage(0) }}
/>
<span className="text-muted-foreground">Assigned to me</span>
</label>
<div className="flex items-center gap-2 sm:ml-auto">
<Filter className="h-4 w-4 text-muted-foreground" />
<Select value={planFilter} onValueChange={(v) => { setPlanFilter(v); setPage(0) }}>
Expand Down Expand Up @@ -357,8 +358,12 @@ export function TasksClient({
)}
</div>
</TableCell>
<TableCell onClick={(e) => e.stopPropagation()}>
<Link href={`/plans/${task.codePlanId}`} className="text-sm hover:text-accent transition-colors">
<TableCell className="max-w-[200px]" onClick={(e) => e.stopPropagation()}>
<Link
href={`/plans/${task.codePlanId}`}
title={task.planTitle}
className="text-sm hover:text-accent transition-colors truncate block"
>
{task.planTitle}
</Link>
</TableCell>
Expand Down
19 changes: 10 additions & 9 deletions app/(dashboard)/work-items/work-items-client.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -198,17 +198,17 @@ export function WorkItemsClient({
<TableBody>
{pageItems.map((item) => (
<TableRow key={item.id} className="cursor-pointer" onClick={() => openPanel(item)}>
<TableCell>
<TableCell className="max-w-[240px]">
<div>
<p className={cn('font-medium flex items-center gap-1.5', item.status === 'wont_do' && 'line-through text-muted-foreground')}>
{item.title}
<span className="truncate min-w-0" title={item.title}>{item.title}</span>
{item.source !== 'native' && (
<span title={`Mirrored from ${item.source}`}>
<span title={`Mirrored from ${item.source}`} className="shrink-0">
<ExternalLink className="h-3 w-3 text-muted-foreground" />
</span>
)}
</p>
<p className="text-xs text-muted-foreground">{item.productName}</p>
<p className="text-xs text-muted-foreground truncate" title={item.productName}>{item.productName}</p>
</div>
</TableCell>
<TableCell>
Expand All @@ -221,18 +221,19 @@ export function WorkItemsClient({
{item.severity}
</Badge>
</TableCell>
<TableCell className="text-sm text-muted-foreground">
{item.assetName ?? '-'}
{item.area && <span className="text-xs block">{item.area}</span>}
<TableCell className="text-sm text-muted-foreground max-w-[160px]">
<div className="truncate" title={item.assetName ?? undefined}>{item.assetName ?? '-'}</div>
{item.area && <div className="text-xs truncate" title={item.area}>{item.area}</div>}
</TableCell>
<TableCell onClick={(e) => e.stopPropagation()}>
<TableCell className="max-w-[180px]" onClick={(e) => e.stopPropagation()}>
{item.linkedPlans.length > 0 ? (
<div className="flex flex-col gap-0.5">
{item.linkedPlans.slice(0, 2).map((plan) => (
<Link
key={plan.id}
href={`/plans/${plan.id}`}
className="text-sm hover:text-accent transition-colors truncate max-w-[180px]"
title={plan.title}
className="text-sm hover:text-accent transition-colors truncate block"
>
{plan.title}
</Link>
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "codeplans",
"version": "0.3.18",
"version": "0.3.19",
"description": "Manage and track coordinated changes across your software architecture.",
"author": "Sai Prakash <sylonzero@gmail.com>",
"homepage": "https://codeplans.ai",
Expand Down
Loading