feat(ui): redesign Plan tab with Objective, User Stories, Features, and Architecture panels
This commit is contained in:
@@ -181,23 +181,194 @@ function ObjectiveView({ plan, projectId, onChange }: { plan: Plan, projectId: s
|
||||
// ──────────────────────────────────────────────────
|
||||
// 2. USER STORIES VIEW
|
||||
// ──────────────────────────────────────────────────
|
||||
function UserStoriesView({ plan, projectId }: { plan: Plan, projectId: string, onChange: (p: Plan) => void }) {
|
||||
// In a full implementation, we'd parse spec.md or filter specific types of tasks.
|
||||
// For now, we mock the UI to show the intended architecture.
|
||||
// MOCK DATA: In production, this would be parsed from the AI's spec markdown or a JSON array in the database.
|
||||
const MOCK_JOURNEYS = [
|
||||
{
|
||||
id: "j1",
|
||||
name: "User Dashboard",
|
||||
persona: "👑 Admin",
|
||||
goal: "Provides a centralized view of all active jobs, metrics, and pending approvals.",
|
||||
stories: [
|
||||
{
|
||||
id: "s1",
|
||||
title: "View active jobs",
|
||||
description: "As an Admin, I want to see a map of today's jobs so I know where my team is.",
|
||||
criteria: [
|
||||
{ text: "Map centers on user's GPS location.", done: false },
|
||||
{ text: "Clicking a pin opens a detail modal.", done: true }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: "s2",
|
||||
title: "Approve pending payouts",
|
||||
description: "As an Admin, I want to review completed jobs and click 'Approve' to trigger Stripe payouts.",
|
||||
criteria: [
|
||||
{ text: "List displays jobs with 'completed' status.", done: false },
|
||||
{ text: "Approve button triggers success toast.", done: false }
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: "j2",
|
||||
name: "Authentication & Onboarding",
|
||||
persona: "👤 Customer",
|
||||
goal: "Allows a new user to securely create an account and fill out their initial profile.",
|
||||
stories: [
|
||||
{
|
||||
id: "s3",
|
||||
title: "Sign up via Email",
|
||||
description: "As a Customer, I want to sign up with my email and a password so my data is secure.",
|
||||
criteria: [
|
||||
{ text: "Form validates email format.", done: false },
|
||||
{ text: "Password must be > 8 characters.", done: false }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
function UserStoriesView({ plan, projectId, onChange }: { plan: Plan, projectId: string, onChange: (p: Plan) => void }) {
|
||||
const [activeJourneyId, setActiveJourneyId] = useState<string>(MOCK_JOURNEYS[0].id);
|
||||
const activeJourney = MOCK_JOURNEYS.find(j => j.id === activeJourneyId);
|
||||
|
||||
return (
|
||||
<div className="panel-container">
|
||||
<div className="panel-container" style={{ display: "flex", flexDirection: "column", height: "100%" }}>
|
||||
<div style={{ marginBottom: 24 }}>
|
||||
<h2 style={{ fontSize: "1.25rem", fontWeight: 600, margin: 0, color: INK.main }}>User Stories</h2>
|
||||
<p style={{ color: INK.muted, fontSize: "0.85rem", margin: "4px 0 0" }}>The prioritized journeys users will take through your application.</p>
|
||||
</div>
|
||||
|
||||
<div style={{ border: `1px dashed ${INK.border}`, padding: 40, borderRadius: 8, textAlign: "center", color: INK.muted }}>
|
||||
<BookOpen size={24} style={{ margin: "0 auto 12px", opacity: 0.5 }} />
|
||||
<p style={{ fontWeight: 500 }}>User Stories are being built.</p>
|
||||
<p style={{ fontSize: "0.85rem", maxWidth: 400, margin: "8px auto 0" }}>
|
||||
This view will display specific user personas and their acceptance scenarios (e.g. "As a User, I want to log in so I can see my dashboard").
|
||||
<div style={{ display: "flex", gap: 24, alignItems: "flex-start" }}>
|
||||
|
||||
{/* LEFT COLUMN: The Index */}
|
||||
<div style={{
|
||||
width: "30%",
|
||||
minWidth: 250,
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: 8
|
||||
}}>
|
||||
{MOCK_JOURNEYS.map(j => {
|
||||
const isActive = j.id === activeJourneyId;
|
||||
return (
|
||||
<button
|
||||
key={j.id}
|
||||
onClick={() => setActiveJourneyId(j.id)}
|
||||
style={{
|
||||
textAlign: "left",
|
||||
padding: "16px 16px",
|
||||
borderRadius: 8,
|
||||
background: isActive ? "#fff" : "transparent",
|
||||
border: isActive ? `1px solid ${INK.border}` : "1px solid transparent",
|
||||
boxShadow: isActive ? "0 1px 3px rgba(0,0,0,0.02)" : "none",
|
||||
cursor: "pointer",
|
||||
transition: "all 0.15s ease",
|
||||
}}
|
||||
>
|
||||
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "flex-start", marginBottom: 6 }}>
|
||||
<span style={{ fontWeight: 600, fontSize: "0.95rem", color: isActive ? INK.main : INK.muted }}>
|
||||
{j.name}
|
||||
</span>
|
||||
</div>
|
||||
<div style={{ display: "flex", gap: 12, alignItems: "center" }}>
|
||||
<span style={{ fontSize: "0.8rem", color: INK.muted, background: INK.bgHover, padding: "2px 6px", borderRadius: 4, border: `1px solid ${INK.border}` }}>
|
||||
{j.persona}
|
||||
</span>
|
||||
<span style={{ fontSize: "0.8rem", color: INK.faint }}>
|
||||
{j.stories.length} stories
|
||||
</span>
|
||||
</div>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
|
||||
<button style={{
|
||||
marginTop: 8,
|
||||
padding: "12px",
|
||||
border: `1px dashed ${INK.border}`,
|
||||
borderRadius: 8,
|
||||
background: "transparent",
|
||||
color: INK.muted,
|
||||
fontSize: "0.85rem",
|
||||
fontWeight: 500,
|
||||
cursor: "pointer",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
gap: 6
|
||||
}}>
|
||||
<Plus size={14} /> Add Journey
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* RIGHT COLUMN: The Details */}
|
||||
<div style={{
|
||||
flex: 1,
|
||||
background: "#fff",
|
||||
border: `1px solid ${INK.border}`,
|
||||
borderRadius: 8,
|
||||
padding: 32,
|
||||
boxShadow: "0 1px 3px rgba(0,0,0,0.02)"
|
||||
}}>
|
||||
{activeJourney ? (
|
||||
<>
|
||||
<div style={{ marginBottom: 32, paddingBottom: 24, borderBottom: `1px solid ${INK.border}` }}>
|
||||
<div style={{ display: "flex", alignItems: "center", gap: 12, marginBottom: 12 }}>
|
||||
<h3 style={{ fontSize: "1.4rem", fontWeight: 700, color: INK.main, margin: 0 }}>
|
||||
{activeJourney.name}
|
||||
</h3>
|
||||
<span style={{ fontSize: "0.85rem", color: INK.muted, background: INK.bgHover, padding: "4px 8px", borderRadius: 6, border: `1px solid ${INK.border}`, fontWeight: 500 }}>
|
||||
{activeJourney.persona}
|
||||
</span>
|
||||
</div>
|
||||
<p style={{ fontSize: "0.95rem", color: INK.muted, margin: 0, lineHeight: 1.5 }}>
|
||||
<strong style={{ color: INK.main }}>Goal:</strong> {activeJourney.goal}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div style={{ display: "flex", flexDirection: "column", gap: 32 }}>
|
||||
{activeJourney.stories.map((story, i) => (
|
||||
<div key={story.id}>
|
||||
<div style={{ display: "flex", alignItems: "baseline", gap: 12, marginBottom: 8 }}>
|
||||
<span style={{ color: INK.faint, fontWeight: 600, fontSize: "0.85rem" }}>US{i + 1}</span>
|
||||
<h4 style={{ fontSize: "1.05rem", fontWeight: 600, color: INK.main, margin: 0 }}>{story.title}</h4>
|
||||
</div>
|
||||
<p style={{ fontSize: "0.95rem", color: INK.muted, margin: "0 0 16px 0", fontStyle: "italic" }}>
|
||||
"{story.description}"
|
||||
</p>
|
||||
|
||||
<div style={{ background: INK.bgHover, padding: 16, borderRadius: 8, border: `1px solid ${INK.border}` }}>
|
||||
<div style={{ fontSize: "0.8rem", fontWeight: 600, textTransform: "uppercase", letterSpacing: "0.05em", color: INK.muted, marginBottom: 12 }}>
|
||||
Acceptance Criteria
|
||||
</div>
|
||||
<ul style={{ listStyle: "none", padding: 0, margin: 0, display: "flex", flexDirection: "column", gap: 10 }}>
|
||||
{story.criteria.map((c, idx) => (
|
||||
<li key={idx} style={{ display: "flex", alignItems: "flex-start", gap: 10, fontSize: "0.9rem", color: INK.main }}>
|
||||
<div style={{
|
||||
width: 16, height: 16, borderRadius: 4, border: `1px solid ${c.done ? "var(--accent)" : INK.faint}`,
|
||||
background: c.done ? "var(--accent)" : "transparent",
|
||||
display: "flex", alignItems: "center", justifyContent: "center", flexShrink: 0, marginTop: 2
|
||||
}}>
|
||||
{c.done && <Check size={12} color="#fff" strokeWidth={3} />}
|
||||
</div>
|
||||
<span style={{ textDecoration: c.done ? "line-through" : "none", opacity: c.done ? 0.5 : 1 }}>
|
||||
{c.text}
|
||||
</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<div style={{ textAlign: "center", color: INK.muted, padding: 40 }}>Select a journey to view stories</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user