mirror of
https://github.com/alkimake/paperclip.git
synced 2026-06-18 03:30:39 +09:00
feat: show parent task and sub-tasks at bottom of issue properties panel
Move parent-task link out of the 2-column PropertyRow layout and into a dedicated full-width section at the bottom of the panel, separated by a Separator. Sub-tasks are listed in the same section when present. Each item shows a StatusIcon aligned with the first line of wrapped title text (items-start + mt-0.5 on the icon wrapper). Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
parent
e23d148be1
commit
b380d6000f
2 changed files with 50 additions and 14 deletions
|
|
@ -44,6 +44,7 @@ interface IssuePropertiesProps {
|
||||||
issue: Issue;
|
issue: Issue;
|
||||||
onUpdate: (data: Record<string, unknown>) => void;
|
onUpdate: (data: Record<string, unknown>) => void;
|
||||||
inline?: boolean;
|
inline?: boolean;
|
||||||
|
childIssues?: Issue[];
|
||||||
}
|
}
|
||||||
|
|
||||||
function PropertyRow({ label, children }: { label: string; children: React.ReactNode }) {
|
function PropertyRow({ label, children }: { label: string; children: React.ReactNode }) {
|
||||||
|
|
@ -117,7 +118,7 @@ function PropertyPicker({
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function IssueProperties({ issue, onUpdate, inline }: IssuePropertiesProps) {
|
export function IssueProperties({ issue, onUpdate, inline, childIssues }: IssuePropertiesProps) {
|
||||||
const { selectedCompanyId } = useCompany();
|
const { selectedCompanyId } = useCompany();
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const companyId = issue.companyId ?? selectedCompanyId;
|
const companyId = issue.companyId ?? selectedCompanyId;
|
||||||
|
|
@ -560,17 +561,6 @@ export function IssueProperties({ issue, onUpdate, inline }: IssuePropertiesProp
|
||||||
{projectContent}
|
{projectContent}
|
||||||
</PropertyPicker>
|
</PropertyPicker>
|
||||||
|
|
||||||
{issue.parentId && (
|
|
||||||
<PropertyRow label="Parent">
|
|
||||||
<Link
|
|
||||||
to={`/issues/${issue.ancestors?.[0]?.identifier ?? issue.parentId}`}
|
|
||||||
className="text-sm hover:underline"
|
|
||||||
>
|
|
||||||
{issue.ancestors?.[0]?.title ?? issue.parentId.slice(0, 8)}
|
|
||||||
</Link>
|
|
||||||
</PropertyRow>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{issue.requestDepth > 0 && (
|
{issue.requestDepth > 0 && (
|
||||||
<PropertyRow label="Depth">
|
<PropertyRow label="Depth">
|
||||||
<span className="text-sm font-mono">{issue.requestDepth}</span>
|
<span className="text-sm font-mono">{issue.requestDepth}</span>
|
||||||
|
|
@ -615,6 +605,52 @@ export function IssueProperties({ issue, onUpdate, inline }: IssuePropertiesProp
|
||||||
<span className="text-sm">{timeAgo(issue.updatedAt)}</span>
|
<span className="text-sm">{timeAgo(issue.updatedAt)}</span>
|
||||||
</PropertyRow>
|
</PropertyRow>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{(issue.parentId || (childIssues && childIssues.length > 0)) && (
|
||||||
|
<>
|
||||||
|
<Separator />
|
||||||
|
<div className="space-y-3">
|
||||||
|
{issue.parentId && (
|
||||||
|
<div>
|
||||||
|
<p className="text-xs text-muted-foreground mb-1">Parent task</p>
|
||||||
|
<div className="flex items-start gap-1.5">
|
||||||
|
{issue.ancestors?.[0] != null && (
|
||||||
|
<div className="shrink-0 mt-0.5">
|
||||||
|
<StatusIcon status={issue.ancestors[0].status} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<Link
|
||||||
|
to={`/issues/${issue.ancestors?.[0]?.identifier ?? issue.parentId}`}
|
||||||
|
className="text-sm hover:underline"
|
||||||
|
>
|
||||||
|
{issue.ancestors?.[0]?.title ?? issue.parentId.slice(0, 8)}
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{childIssues && childIssues.length > 0 && (
|
||||||
|
<div>
|
||||||
|
<p className="text-xs text-muted-foreground mb-1">Sub-tasks</p>
|
||||||
|
<div className="space-y-0.5">
|
||||||
|
{childIssues.map((child) => (
|
||||||
|
<div key={child.id} className="flex items-start gap-1.5">
|
||||||
|
<div className="shrink-0 mt-0.5">
|
||||||
|
<StatusIcon status={child.status} />
|
||||||
|
</div>
|
||||||
|
<Link
|
||||||
|
to={`/issues/${child.identifier ?? child.id}`}
|
||||||
|
className="text-sm hover:underline"
|
||||||
|
>
|
||||||
|
{child.title}
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -985,7 +985,7 @@ export function IssueDetail() {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (issue) {
|
if (issue) {
|
||||||
openPanel(
|
openPanel(
|
||||||
<IssueProperties issue={issue} onUpdate={(data) => updateIssue.mutate(data)} />
|
<IssueProperties issue={issue} onUpdate={(data) => updateIssue.mutate(data)} childIssues={childIssues} />
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return () => closePanel();
|
return () => closePanel();
|
||||||
|
|
@ -1699,7 +1699,7 @@ export function IssueDetail() {
|
||||||
</SheetHeader>
|
</SheetHeader>
|
||||||
<ScrollArea className="flex-1 overflow-y-auto">
|
<ScrollArea className="flex-1 overflow-y-auto">
|
||||||
<div className="px-4 pb-4">
|
<div className="px-4 pb-4">
|
||||||
<IssueProperties issue={issue} onUpdate={(data) => updateIssue.mutate(data)} inline />
|
<IssueProperties issue={issue} onUpdate={(data) => updateIssue.mutate(data)} inline childIssues={childIssues} />
|
||||||
</div>
|
</div>
|
||||||
</ScrollArea>
|
</ScrollArea>
|
||||||
</SheetContent>
|
</SheetContent>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue