Skip to content

Commit 44b1f9e

Browse files
committed
Implement support for sub-issues
1 parent effeed0 commit 44b1f9e

14 files changed

Lines changed: 449 additions & 62 deletions

File tree

client/src/components/CreateIssue.tsx

Lines changed: 39 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,17 @@ import {Badge} from './ui/badge';
1010
import {useDialog} from '../contexts/DialogContext';
1111
import {useAuth} from '../contexts/AuthContext';
1212
import {projectsApi} from '../services/api';
13-
import type {Project, CreatePostData, User as UserType} from '../types';
13+
import type {Project, CreatePostData, User as UserType, Post} from '../types';
1414

1515
interface CreateIssueProps {
1616
projects: Project[];
1717
selectedProject: number | null;
18+
parentIssue?: Post | null;
1819
onSubmit: (projectId: number, data: CreatePostData, files: File[]) => Promise<void>;
1920
onCancel: () => void;
2021
}
2122

22-
export const CreateIssue = ({projects, selectedProject, onSubmit, onCancel}: CreateIssueProps) => {
23+
export const CreateIssue = ({projects, selectedProject, parentIssue, onSubmit, onCancel}: CreateIssueProps) => {
2324
const {alert} = useDialog();
2425
const {user} = useAuth();
2526
const [projectId, setProjectId] = useState<string>(selectedProject?.toString() || '');
@@ -41,6 +42,14 @@ export const CreateIssue = ({projects, selectedProject, onSubmit, onCancel}: Cre
4142

4243
const canEditManagerFields = user?.is_admin || false;
4344

45+
useEffect(() => {
46+
if (parentIssue) {
47+
setProjectId(parentIssue.project_id.toString());
48+
setTitle(`[SUB] ${parentIssue.title} - `);
49+
setPriority(parentIssue.priority || 'Medium');
50+
}
51+
}, [parentIssue]);
52+
4453
useEffect(() => {
4554
if (projectId) {
4655
loadProjectMembers();
@@ -154,6 +163,10 @@ export const CreateIssue = ({projects, selectedProject, onSubmit, onCancel}: Cre
154163
priority,
155164
};
156165

166+
if (parentIssue) {
167+
postData.parent_issue_id = parentIssue.id;
168+
}
169+
157170
if (canEditManagerFields) {
158171
if (assigneeId && assigneeId !== "unassigned") postData.assignee_id = parseInt(assigneeId);
159172
if (storyPoints) postData.story_points = parseInt(storyPoints);
@@ -186,18 +199,36 @@ export const CreateIssue = ({projects, selectedProject, onSubmit, onCancel}: Cre
186199
return (
187200
<main className="container py-8 px-4 max-w-2xl mx-auto">
188201
<div className="mb-8">
189-
<h1 className="text-3xl font-bold tracking-tight mb-2 text-foreground">Create New Issue</h1>
190-
<p className="text-muted-foreground">Report a bug or request a new feature</p>
191-
</div>
202+
<h1 className="text-3xl font-bold tracking-tight mb-2 text-foreground">
203+
{parentIssue ? 'Create Sub-Issue' : 'Create New Issue'}
204+
</h1>
205+
<p className="text-muted-foreground">
206+
{parentIssue
207+
? `Create a sub-issue for: ${parentIssue.title}`
208+
: 'Report a bug or request a new feature'
209+
}
210+
</p> </div>
211+
212+
{parentIssue && (
213+
<div className="bg-accent/30 border border-accent/50 rounded-lg p-4">
214+
<div className="flex items-center space-x-3">
215+
<div className="text-sm text-muted-foreground">Parent Issue:</div>
216+
<div className="flex-1">
217+
<div className="font-medium text-sm text-foreground">{parentIssue.title}</div>
218+
<div className="text-xs text-muted-foreground">#{parentIssue.id}</div>
219+
</div>
220+
</div>
221+
</div>
222+
)}
192223

193-
<div className="space-y-6">
224+
<div className="space-y-6">
194225
<div className={`grid gap-6 ${canEditManagerFields ? 'md:grid-cols-3' : 'md:grid-cols-2'}`}>
195226
<div className="space-y-2">
196227
<Label htmlFor="project-select" className="text-foreground font-medium">
197228
Select Project
198229
</Label>
199-
<Select value={projectId} onValueChange={setProjectId}>
200-
<SelectTrigger className="bg-secondary border-border text-foreground hover:bg-accent">
230+
<Select value={projectId} onValueChange={setProjectId} disabled={!!parentIssue}>
231+
<SelectTrigger className="bg-secondary border-border text-foreground hover:bg-accent disabled:opacity-50 disabled:cursor-not-allowed">
201232
<SelectValue placeholder="Choose a project"/>
202233
</SelectTrigger>
203234
<SelectContent className="bg-popover border-border backdrop-blur-xl">

client/src/components/IssueDetail.tsx

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,18 @@ import {AttachmentDialog} from './issue/AttachmentDialog';
1212
import {CommentsList} from './issue/CommentsList';
1313
import {CommentForm} from './issue/CommentForm';
1414
import {EditIssueSheet} from './issue/EditIssueSheet';
15+
import {SubIssues} from './issue/SubIssues';
1516
import {isImageFile} from './issue/fileUtils';
1617

1718
interface IssueDetailProps {
1819
issueId: number;
1920
projectId: number;
2021
onBack: () => void;
22+
onNavigateToIssue?: (issueId: number) => void;
23+
onCreateSubIssue?: (parentIssueId: number) => void;
2124
}
2225

23-
export const IssueDetail = ({issueId, projectId, onBack}: IssueDetailProps) => {
26+
export const IssueDetail = ({issueId, projectId, onBack, onNavigateToIssue, onCreateSubIssue}: IssueDetailProps) => {
2427
const {isAuthenticated} = useAuth();
2528
const {confirm, alert} = useDialog();
2629
const [issue, setIssue] = useState<Post | null>(null);
@@ -296,6 +299,18 @@ export const IssueDetail = ({issueId, projectId, onBack}: IssueDetailProps) => {
296299
}
297300
};
298301

302+
const handleSubIssueClick = (subIssueId: number) => {
303+
if (onNavigateToIssue) {
304+
onNavigateToIssue(subIssueId);
305+
}
306+
};
307+
308+
const handleCreateSubIssue = () => {
309+
if (onCreateSubIssue && issue) {
310+
onCreateSubIssue(issue.id);
311+
}
312+
};
313+
299314
if (isLoading) {
300315
return (
301316
<div className="flex items-center justify-center h-64">
@@ -350,6 +365,14 @@ export const IssueDetail = ({issueId, projectId, onBack}: IssueDetailProps) => {
350365
onDelete={handleDeleteAttachment}
351366
/>
352367

368+
{!issue.parent_issue_id && (
369+
<SubIssues
370+
issue={issue}
371+
onSubIssueClick={handleSubIssueClick}
372+
onCreateSubIssue={handleCreateSubIssue}
373+
/>
374+
)}
375+
353376
<CommentsList
354377
comments={comments}
355378
editingComment={editingComment}

0 commit comments

Comments
 (0)