mirror of
https://github.com/rennf93/roboco.git
synced 2026-08-03 07:23:24 +02:00
fix(panel): UI revamp — settings layout, journals/kanban scroll, agent item, projects
- Settings: cards reordered to User Info / Appearance / Data & Refresh / Transcript Retention / Notifications / Connection Info (the 3x2 grid) - Journals: the page now fills the viewport; the agent list and the entry detail each scroll inside their own panel — removes the page + list + fixed-500px triple scrollbar (real layout, not bolted-on magic heights) - Agent item: distinct per-team avatar with initials, clear selected/hover states, truncation, focus ring (was a generic icon repeated on every row) - Kanban: the board fills the viewport and each column's card list scrolls inside it, so a full Done column no longer overflows down the page - Projects: drop the misleading Workspace column — it read the legacy per-project workspace_path (never set in the per-agent workspace model), so it always showed 'No workspace'
This commit is contained in:
@@ -136,9 +136,9 @@ function JournalsPageContent() {
|
|||||||
const selectedAgent = agents?.find((a) => a.agent_id === selectedAgentId);
|
const selectedAgent = agents?.find((a) => a.agent_id === selectedAgentId);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="flex h-full flex-col gap-6">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between shrink-0">
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-3xl font-bold tracking-tight">Agent Journals</h1>
|
<h1 className="text-3xl font-bold tracking-tight">Agent Journals</h1>
|
||||||
<p className="text-muted-foreground">
|
<p className="text-muted-foreground">
|
||||||
@@ -151,12 +151,12 @@ function JournalsPageContent() {
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Main Content */}
|
{/* Main Content — one screen; the agent list and the detail each scroll inside */}
|
||||||
<div className="grid grid-cols-12 gap-6">
|
<div className="grid grid-cols-12 gap-6 flex-1 min-h-0">
|
||||||
{/* Sidebar */}
|
{/* Sidebar */}
|
||||||
<div className="col-span-12 lg:col-span-3">
|
<div className="col-span-12 lg:col-span-3 min-h-0">
|
||||||
<Card>
|
<Card className="h-full flex flex-col">
|
||||||
<CardContent className="p-3">
|
<CardContent className="p-3 flex flex-1 flex-col min-h-0">
|
||||||
{/* Agent Search */}
|
{/* Agent Search */}
|
||||||
<div className="relative mb-3">
|
<div className="relative mb-3">
|
||||||
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-muted-foreground" />
|
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-muted-foreground" />
|
||||||
@@ -180,9 +180,9 @@ function JournalsPageContent() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Journal Content */}
|
{/* Journal Content */}
|
||||||
<div className="col-span-12 lg:col-span-9">
|
<div className="col-span-12 lg:col-span-9 min-h-0">
|
||||||
<Card>
|
<Card className="h-full flex flex-col">
|
||||||
<CardContent className="p-6">
|
<CardContent className="p-6 flex-1 min-h-0 overflow-hidden">
|
||||||
{selectedAgent ? (
|
{selectedAgent ? (
|
||||||
<JournalView
|
<JournalView
|
||||||
agent={selectedAgent}
|
agent={selectedAgent}
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ import { TranscriptRetentionCard } from "@/components/settings/transcript-retent
|
|||||||
export default function SettingsPage() {
|
export default function SettingsPage() {
|
||||||
const { theme, setTheme } = useTheme();
|
const { theme, setTheme } = useTheme();
|
||||||
const { sidebarCollapsed, setSidebarCollapsed } = useUIStore();
|
const { sidebarCollapsed, setSidebarCollapsed } = useUIStore();
|
||||||
|
|
||||||
// Local state for settings (would be persisted in a real app)
|
// Local state for settings (would be persisted in a real app)
|
||||||
const [notificationsEnabled, setNotificationsEnabled] = useState(true);
|
const [notificationsEnabled, setNotificationsEnabled] = useState(true);
|
||||||
const [soundEnabled, setSoundEnabled] = useState(true);
|
const [soundEnabled, setSoundEnabled] = useState(true);
|
||||||
@@ -52,8 +52,35 @@ export default function SettingsPage() {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Cards grid — two columns on large screens */}
|
{/* Cards grid — two columns on large screens. Order (row,col):
|
||||||
|
User Info (1,1) · Appearance (1,2) · Data & Refresh (2,1) ·
|
||||||
|
Transcript Retention (2,2) · Notifications (3,1) · Connection Info (3,2). */}
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||||
|
{/* User Info */}
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle className="flex items-center gap-2">
|
||||||
|
<User className="h-5 w-5" />
|
||||||
|
User Info
|
||||||
|
</CardTitle>
|
||||||
|
<CardDescription>Your account information</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="space-y-4">
|
||||||
|
<div className="flex items-center gap-4">
|
||||||
|
<div className="h-16 w-16 rounded-full bg-primary flex items-center justify-center">
|
||||||
|
<span className="text-primary-foreground font-bold text-2xl">CEO</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p className="font-semibold text-lg">Renzo</p>
|
||||||
|
<p className="text-sm text-muted-foreground">Chief Executive Officer</p>
|
||||||
|
<p className="text-xs text-muted-foreground mt-1">
|
||||||
|
Agent ID: 00000000-0000-0000-0000-000000000001
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
{/* Appearance */}
|
{/* Appearance */}
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
@@ -98,45 +125,6 @@ export default function SettingsPage() {
|
|||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
{/* Notifications */}
|
|
||||||
<Card>
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle className="flex items-center gap-2">
|
|
||||||
<Bell className="h-5 w-5" />
|
|
||||||
Notifications
|
|
||||||
</CardTitle>
|
|
||||||
<CardDescription>Configure how you receive updates</CardDescription>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent className="space-y-4">
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<div>
|
|
||||||
<Label>Enable Notifications</Label>
|
|
||||||
<p className="text-sm text-muted-foreground">
|
|
||||||
Receive real-time notifications from agents
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<Switch
|
|
||||||
checked={notificationsEnabled}
|
|
||||||
onCheckedChange={setNotificationsEnabled}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<Separator />
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<div>
|
|
||||||
<Label>Sound Alerts</Label>
|
|
||||||
<p className="text-sm text-muted-foreground">
|
|
||||||
Play sound for important notifications
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<Switch
|
|
||||||
checked={soundEnabled}
|
|
||||||
onCheckedChange={setSoundEnabled}
|
|
||||||
disabled={!notificationsEnabled}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
{/* Data & Refresh */}
|
{/* Data & Refresh */}
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
@@ -189,6 +177,45 @@ export default function SettingsPage() {
|
|||||||
{/* Transcript Retention (panel-tunable; persisted server-side) */}
|
{/* Transcript Retention (panel-tunable; persisted server-side) */}
|
||||||
<TranscriptRetentionCard />
|
<TranscriptRetentionCard />
|
||||||
|
|
||||||
|
{/* Notifications */}
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle className="flex items-center gap-2">
|
||||||
|
<Bell className="h-5 w-5" />
|
||||||
|
Notifications
|
||||||
|
</CardTitle>
|
||||||
|
<CardDescription>Configure how you receive updates</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="space-y-4">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div>
|
||||||
|
<Label>Enable Notifications</Label>
|
||||||
|
<p className="text-sm text-muted-foreground">
|
||||||
|
Receive real-time notifications from agents
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<Switch
|
||||||
|
checked={notificationsEnabled}
|
||||||
|
onCheckedChange={setNotificationsEnabled}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Separator />
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div>
|
||||||
|
<Label>Sound Alerts</Label>
|
||||||
|
<p className="text-sm text-muted-foreground">
|
||||||
|
Play sound for important notifications
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<Switch
|
||||||
|
checked={soundEnabled}
|
||||||
|
onCheckedChange={setSoundEnabled}
|
||||||
|
disabled={!notificationsEnabled}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
{/* Connection Info */}
|
{/* Connection Info */}
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
@@ -212,31 +239,6 @@ export default function SettingsPage() {
|
|||||||
</p>
|
</p>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
{/* User Info */}
|
|
||||||
<Card>
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle className="flex items-center gap-2">
|
|
||||||
<User className="h-5 w-5" />
|
|
||||||
User Info
|
|
||||||
</CardTitle>
|
|
||||||
<CardDescription>Your account information</CardDescription>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent className="space-y-4">
|
|
||||||
<div className="flex items-center gap-4">
|
|
||||||
<div className="h-16 w-16 rounded-full bg-primary flex items-center justify-center">
|
|
||||||
<span className="text-primary-foreground font-bold text-2xl">CEO</span>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<p className="font-semibold text-lg">Renzo</p>
|
|
||||||
<p className="text-sm text-muted-foreground">Chief Executive Officer</p>
|
|
||||||
<p className="text-xs text-muted-foreground mt-1">
|
|
||||||
Agent ID: 00000000-0000-0000-0000-000000000001
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Save Button */}
|
{/* Save Button */}
|
||||||
|
|||||||
@@ -1,10 +1,8 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { Agent } from "@/types";
|
import { Agent } from "@/types";
|
||||||
import { User } from "lucide-react";
|
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { getAgentDisplayName } from "@/lib/agent-utils";
|
import { getAgentDisplayName } from "@/lib/agent-utils";
|
||||||
import { Button } from "@/components/ui/button";
|
|
||||||
|
|
||||||
interface AgentItemProps {
|
interface AgentItemProps {
|
||||||
agent: Agent;
|
agent: Agent;
|
||||||
@@ -13,32 +11,67 @@ interface AgentItemProps {
|
|||||||
hasEntries?: boolean;
|
hasEntries?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Soft per-team avatar tints so the list reads at a glance (works on light + dark).
|
||||||
|
const TEAM_AVATAR: Record<string, string> = {
|
||||||
|
backend: "bg-blue-500/15 text-blue-600 dark:text-blue-400",
|
||||||
|
frontend: "bg-emerald-500/15 text-emerald-600 dark:text-emerald-400",
|
||||||
|
ux_ui: "bg-fuchsia-500/15 text-fuchsia-600 dark:text-fuchsia-400",
|
||||||
|
board: "bg-amber-500/15 text-amber-600 dark:text-amber-400",
|
||||||
|
main_pm: "bg-sky-500/15 text-sky-600 dark:text-sky-400",
|
||||||
|
marketing: "bg-rose-500/15 text-rose-600 dark:text-rose-400",
|
||||||
|
};
|
||||||
|
|
||||||
|
function initialsFor(name: string): string {
|
||||||
|
const parts = name.trim().split(/\s+/).filter(Boolean);
|
||||||
|
if (parts.length === 0) return "?";
|
||||||
|
if (parts.length === 1) return parts[0].slice(0, 2).toUpperCase();
|
||||||
|
return (parts[0][0] + parts[parts.length - 1][0]).toUpperCase();
|
||||||
|
}
|
||||||
|
|
||||||
export function AgentItem({ agent, isSelected, onClick, hasEntries }: AgentItemProps) {
|
export function AgentItem({ agent, isSelected, onClick, hasEntries }: AgentItemProps) {
|
||||||
|
const name = getAgentDisplayName(agent.agent_id);
|
||||||
|
const avatarTint =
|
||||||
|
TEAM_AVATAR[agent.team ?? ""] ?? "bg-muted text-muted-foreground";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button
|
<button
|
||||||
|
type="button"
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
variant="ghost"
|
aria-current={isSelected ? "true" : undefined}
|
||||||
className={cn(
|
className={cn(
|
||||||
"w-full h-auto justify-start gap-3 p-2 font-normal whitespace-normal",
|
"group flex w-full items-center gap-3 rounded-lg px-2.5 py-2 text-left transition-colors",
|
||||||
|
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background",
|
||||||
isSelected
|
isSelected
|
||||||
? "bg-primary/10 border border-primary/30 hover:bg-primary/10"
|
? "bg-primary/10 ring-1 ring-inset ring-primary/40"
|
||||||
: "hover:bg-muted/50"
|
: "hover:bg-muted",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div className="relative">
|
<div className="relative shrink-0">
|
||||||
<div className="h-8 w-8 rounded-full bg-muted flex items-center justify-center">
|
<div
|
||||||
<User className="h-4 w-4 text-muted-foreground" />
|
className={cn(
|
||||||
|
"flex h-9 w-9 items-center justify-center rounded-full text-xs font-semibold transition-colors",
|
||||||
|
isSelected ? "bg-primary/15 text-primary" : avatarTint,
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{initialsFor(name)}
|
||||||
</div>
|
</div>
|
||||||
{hasEntries && (
|
{hasEntries && (
|
||||||
<span className="absolute -top-0.5 -right-0.5 h-2.5 w-2.5 bg-primary rounded-full border-2 border-background" />
|
<span className="absolute -bottom-0.5 -right-0.5 h-2.5 w-2.5 rounded-full bg-emerald-500 ring-2 ring-background" />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1 min-w-0">
|
<div className="min-w-0 flex-1">
|
||||||
<p className="text-sm font-medium truncate">{getAgentDisplayName(agent.agent_id)}</p>
|
<p
|
||||||
<p className="text-xs text-muted-foreground capitalize">
|
className={cn(
|
||||||
|
"truncate text-sm font-medium",
|
||||||
|
isSelected && "text-primary",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{name}
|
||||||
|
</p>
|
||||||
|
<p className="truncate text-xs capitalize text-muted-foreground">
|
||||||
{agent.role.replace(/_/g, " ")}
|
{agent.role.replace(/_/g, " ")}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</Button>
|
</button>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ export function AgentList({
|
|||||||
const grouped = groupByTeam(agents);
|
const grouped = groupByTeam(agents);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="overflow-y-auto max-h-[calc(100vh-200px)]">
|
<div className="flex-1 min-h-0 overflow-y-auto">
|
||||||
<div className="p-2 space-y-4">
|
<div className="p-2 space-y-4">
|
||||||
{Object.entries(grouped).map(([teamKey, teamAgents]) => {
|
{Object.entries(grouped).map(([teamKey, teamAgents]) => {
|
||||||
if (teamAgents.length === 0) return null;
|
if (teamAgents.length === 0) return null;
|
||||||
|
|||||||
@@ -81,9 +81,9 @@ export function JournalView({
|
|||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="flex h-full flex-col gap-6">
|
||||||
{/* Agent Header */}
|
{/* Agent Header */}
|
||||||
<div className="flex items-center gap-4 pb-4 border-b">
|
<div className="flex items-center gap-4 pb-4 border-b shrink-0">
|
||||||
<div className="h-12 w-12 rounded-full bg-muted flex items-center justify-center">
|
<div className="h-12 w-12 rounded-full bg-muted flex items-center justify-center">
|
||||||
<User className="h-6 w-6 text-muted-foreground" />
|
<User className="h-6 w-6 text-muted-foreground" />
|
||||||
</div>
|
</div>
|
||||||
@@ -96,15 +96,17 @@ export function JournalView({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Growth Summary */}
|
{/* Growth Summary */}
|
||||||
<GrowthSummary
|
<div className="shrink-0">
|
||||||
journal={journal}
|
<GrowthSummary
|
||||||
growth={growth}
|
journal={journal}
|
||||||
isLoading={loadingJournal || loadingGrowth}
|
growth={growth}
|
||||||
/>
|
isLoading={loadingJournal || loadingGrowth}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Entries Section */}
|
{/* Entries Section — fills the remaining height; the entries scroll inside it */}
|
||||||
<div>
|
<div className="flex flex-1 min-h-0 flex-col">
|
||||||
<div className="flex items-center justify-between mb-4">
|
<div className="flex items-center justify-between mb-4 shrink-0">
|
||||||
<h3 className="text-lg font-semibold flex items-center gap-2">
|
<h3 className="text-lg font-semibold flex items-center gap-2">
|
||||||
<BookOpen className="h-5 w-5" />
|
<BookOpen className="h-5 w-5" />
|
||||||
Journal Entries
|
Journal Entries
|
||||||
@@ -112,7 +114,7 @@ export function JournalView({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Filter */}
|
{/* Filter */}
|
||||||
<div className="mb-4">
|
<div className="mb-4 shrink-0">
|
||||||
<EntryFilter
|
<EntryFilter
|
||||||
typeFilter={typeFilter}
|
typeFilter={typeFilter}
|
||||||
onTypeChange={handleTypeChange}
|
onTypeChange={handleTypeChange}
|
||||||
@@ -143,7 +145,7 @@ export function JournalView({
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<ScrollArea className="h-[500px] pr-4">
|
<ScrollArea className="flex-1 min-h-0 pr-4">
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
{filteredEntries.map((entry) => (
|
{filteredEntries.map((entry) => (
|
||||||
<EntryCard key={entry.id} entry={entry} />
|
<EntryCard key={entry.id} entry={entry} />
|
||||||
|
|||||||
@@ -253,9 +253,9 @@ export function KanbanBoard({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="flex h-full flex-col gap-6">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between shrink-0">
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-3xl font-bold tracking-tight">{title}</h1>
|
<h1 className="text-3xl font-bold tracking-tight">{title}</h1>
|
||||||
{description && (
|
{description && (
|
||||||
@@ -295,8 +295,8 @@ export function KanbanBoard({
|
|||||||
onDragEnd={handleDragEnd}
|
onDragEnd={handleDragEnd}
|
||||||
>
|
>
|
||||||
{/* Mobile: single-column view with prev/next navigator (hidden on sm+) */}
|
{/* Mobile: single-column view with prev/next navigator (hidden on sm+) */}
|
||||||
<div className="sm:hidden">
|
<div className="sm:hidden flex flex-1 min-h-0 flex-col">
|
||||||
<div className="flex items-center gap-2 mb-4">
|
<div className="flex items-center gap-2 mb-4 shrink-0">
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
size="icon"
|
size="icon"
|
||||||
@@ -343,7 +343,7 @@ export function KanbanBoard({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Desktop: horizontal scrolling layout (shown at sm+, i.e. >= 640px) */}
|
{/* Desktop: horizontal scrolling layout (shown at sm+, i.e. >= 640px) */}
|
||||||
<div className="hidden sm:flex gap-4 overflow-x-auto pb-4">
|
<div className="hidden sm:flex gap-4 overflow-x-auto pb-4 flex-1 min-h-0">
|
||||||
{columns.map((col) => (
|
{columns.map((col) => (
|
||||||
<KanbanColumn
|
<KanbanColumn
|
||||||
key={col.id}
|
key={col.id}
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ export function KanbanColumn({
|
|||||||
<div
|
<div
|
||||||
ref={setNodeRef}
|
ref={setNodeRef}
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex flex-col rounded-lg p-3 w-72 shrink-0 sm:w-80",
|
"flex flex-col rounded-lg p-3 w-72 shrink-0 sm:w-80 h-full",
|
||||||
color,
|
color,
|
||||||
isOver && "ring-2 ring-primary ring-offset-2",
|
isOver && "ring-2 ring-primary ring-offset-2",
|
||||||
className,
|
className,
|
||||||
@@ -53,7 +53,7 @@ export function KanbanColumn({
|
|||||||
{isLoading ? "..." : tasks.length}
|
{isLoading ? "..." : tasks.length}
|
||||||
</Badge>
|
</Badge>
|
||||||
</div>
|
</div>
|
||||||
<ScrollArea className="flex-1 max-h-[calc(100vh-280px)]">
|
<ScrollArea className="flex-1 min-h-0">
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Skeleton className="h-24" />
|
<Skeleton className="h-24" />
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import {
|
|||||||
TableRow,
|
TableRow,
|
||||||
} from "@/components/ui/table";
|
} from "@/components/ui/table";
|
||||||
import { Skeleton } from "@/components/ui/skeleton";
|
import { Skeleton } from "@/components/ui/skeleton";
|
||||||
import { ExternalLink, Pencil, GitBranch, Folder, FolderX, Key, KeyRound } from "lucide-react";
|
import { ExternalLink, Pencil, GitBranch, Key, KeyRound } from "lucide-react";
|
||||||
import type { ProjectSummary, Team } from "@/types";
|
import type { ProjectSummary, Team } from "@/types";
|
||||||
import { EditProjectDialog } from "./edit-project-dialog";
|
import { EditProjectDialog } from "./edit-project-dialog";
|
||||||
|
|
||||||
@@ -39,23 +39,6 @@ const teamColors: Record<Team, string> = {
|
|||||||
marketing: "bg-yellow-500/10 text-yellow-500 hover:bg-yellow-500/20",
|
marketing: "bg-yellow-500/10 text-yellow-500 hover:bg-yellow-500/20",
|
||||||
};
|
};
|
||||||
|
|
||||||
function getWorkspaceBadge(hasWorkspace: boolean) {
|
|
||||||
if (hasWorkspace) {
|
|
||||||
return (
|
|
||||||
<Badge className="bg-green-500/10 text-green-500">
|
|
||||||
<Folder className="h-3 w-3 mr-1" />
|
|
||||||
Ready
|
|
||||||
</Badge>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<Badge variant="outline" className="text-muted-foreground">
|
|
||||||
<FolderX className="h-3 w-3 mr-1" />
|
|
||||||
No workspace
|
|
||||||
</Badge>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getTokenBadge(hasGitToken: boolean) {
|
function getTokenBadge(hasGitToken: boolean) {
|
||||||
if (hasGitToken) {
|
if (hasGitToken) {
|
||||||
return (
|
return (
|
||||||
@@ -119,7 +102,6 @@ export function ProjectTable({ projects, isLoading }: ProjectTableProps) {
|
|||||||
<TableHead>Project</TableHead>
|
<TableHead>Project</TableHead>
|
||||||
<TableHead>Cell</TableHead>
|
<TableHead>Cell</TableHead>
|
||||||
<TableHead>Token</TableHead>
|
<TableHead>Token</TableHead>
|
||||||
<TableHead>Workspace</TableHead>
|
|
||||||
<TableHead>Status</TableHead>
|
<TableHead>Status</TableHead>
|
||||||
<TableHead className="w-[100px]">Actions</TableHead>
|
<TableHead className="w-[100px]">Actions</TableHead>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
@@ -147,7 +129,6 @@ export function ProjectTable({ projects, isLoading }: ProjectTableProps) {
|
|||||||
</Badge>
|
</Badge>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell>{getTokenBadge(project.has_git_token)}</TableCell>
|
<TableCell>{getTokenBadge(project.has_git_token)}</TableCell>
|
||||||
<TableCell>{getWorkspaceBadge(project.has_workspace)}</TableCell>
|
|
||||||
<TableCell>
|
<TableCell>
|
||||||
{project.is_active ? (
|
{project.is_active ? (
|
||||||
<Badge className="bg-green-500/10 text-green-500">Active</Badge>
|
<Badge className="bg-green-500/10 text-green-500">Active</Badge>
|
||||||
|
|||||||
Reference in New Issue
Block a user