mirror of
https://github.com/hex248/sprint.git
synced 2026-02-08 02:33:01 +00:00
scrollabe issue table
This commit is contained in:
@@ -3,7 +3,6 @@ import Avatar from "@/components/avatar";
|
|||||||
import { useSelection } from "@/components/selection-provider";
|
import { useSelection } from "@/components/selection-provider";
|
||||||
import StatusTag from "@/components/status-tag";
|
import StatusTag from "@/components/status-tag";
|
||||||
import Icon, { type IconName } from "@/components/ui/icon";
|
import Icon, { type IconName } from "@/components/ui/icon";
|
||||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
|
|
||||||
import { useIssues, useSelectedOrganisation, useSelectedProject } from "@/lib/query/hooks";
|
import { useIssues, useSelectedOrganisation, useSelectedProject } from "@/lib/query/hooks";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
@@ -155,144 +154,149 @@ export function IssuesTable({
|
|||||||
const showAssignee = columns.assignee == null || columns.assignee === true;
|
const showAssignee = columns.assignee == null || columns.assignee === true;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Table className={cn("table-fixed", className)}>
|
<div className={cn("h-full overflow-auto border-t", className)}>
|
||||||
<TableHeader>
|
<table className="w-full table-fixed border-collapse text-sm border-l border-r ">
|
||||||
<TableRow hoverEffect={false} className="bg-secondary">
|
<thead className="sticky top-0 z-10 bg-secondary">
|
||||||
{showId && (
|
<tr className="border-b h-[25px]">
|
||||||
<TableHead className="text-right w-10 border-r text-xs font-medium text-muted-foreground">
|
{showId && (
|
||||||
ID
|
<th className="text-right w-10 border-r text-xs font-medium text-muted-foreground px-2 align-middle">
|
||||||
</TableHead>
|
ID
|
||||||
)}
|
</th>
|
||||||
{showTitle && <TableHead className="text-xs font-medium text-muted-foreground">Title</TableHead>}
|
)}
|
||||||
{showDescription && (
|
{showTitle && (
|
||||||
<TableHead className="text-xs font-medium text-muted-foreground">Description</TableHead>
|
<th className="text-xs font-medium text-muted-foreground px-2 align-middle text-left">Title</th>
|
||||||
)}
|
)}
|
||||||
{/* below is kept blank to fill the space, used as the "Assignee" column */}
|
{showDescription && (
|
||||||
{showAssignee && <TableHead className="w-[1%]"></TableHead>}
|
<th className="text-xs font-medium text-muted-foreground px-2 align-middle text-left">
|
||||||
</TableRow>
|
Description
|
||||||
</TableHeader>
|
</th>
|
||||||
<TableBody>
|
)}
|
||||||
{issues.map((issueData) => {
|
{showAssignee && <th className="w-[1%] px-2"></th>}
|
||||||
const isSelected = issueData.Issue.id === selectedIssueId;
|
</tr>
|
||||||
return (
|
</thead>
|
||||||
<TableRow
|
<tbody>
|
||||||
key={issueData.Issue.id}
|
{issues.map((issueData) => {
|
||||||
className={cn("cursor-pointer max-w-full")}
|
const isSelected = issueData.Issue.id === selectedIssueId;
|
||||||
onClick={() => {
|
return (
|
||||||
if (isSelected) {
|
<tr
|
||||||
selectIssue(null);
|
key={issueData.Issue.id}
|
||||||
return;
|
className={cn("cursor-pointer h-[25px] border-b hover:bg-muted/40")}
|
||||||
}
|
onClick={() => {
|
||||||
selectIssue(issueData);
|
if (isSelected) {
|
||||||
}}
|
selectIssue(null);
|
||||||
>
|
return;
|
||||||
{showId && (
|
}
|
||||||
<TableCell
|
selectIssue(issueData);
|
||||||
className={cn(
|
}}
|
||||||
"font-medium border-r text-right p-0",
|
>
|
||||||
(isSelected || highlighted?.includes(issueData.Issue.id)) &&
|
{showId && (
|
||||||
"shadow-[inset_1px_1px_0_0_var(--personality),inset_0_-1px_0_0_var(--personality)]",
|
<td
|
||||||
)}
|
className={cn(
|
||||||
>
|
"font-medium border-r text-right p-0 align-middle",
|
||||||
<a
|
(isSelected || highlighted?.includes(issueData.Issue.id)) &&
|
||||||
href={getIssueUrl(issueData.Issue.number)}
|
"shadow-[inset_1px_1px_0_0_var(--personality),inset_0_-1px_0_0_var(--personality)]",
|
||||||
onClick={handleLinkClick}
|
)}
|
||||||
className="block w-full h-full px-2 py-1 text-inherit hover:underline decoration-transparent"
|
|
||||||
>
|
>
|
||||||
{issueData.Issue.number.toString().padStart(3, "0")}
|
<a
|
||||||
</a>
|
href={getIssueUrl(issueData.Issue.number)}
|
||||||
</TableCell>
|
onClick={handleLinkClick}
|
||||||
)}
|
className="block w-full h-full px-2 py-1 text-inherit hover:underline decoration-transparent"
|
||||||
{showTitle && (
|
>
|
||||||
<TableCell
|
{issueData.Issue.number.toString().padStart(3, "0")}
|
||||||
className={cn(
|
</a>
|
||||||
"min-w-0 p-0",
|
</td>
|
||||||
(isSelected || highlighted?.includes(issueData.Issue.id)) &&
|
)}
|
||||||
"shadow-[inset_0_1px_0_0_var(--personality),inset_0_-1px_0_0_var(--personality)]",
|
{showTitle && (
|
||||||
)}
|
<td
|
||||||
>
|
className={cn(
|
||||||
<a
|
"min-w-0 p-0 align-middle",
|
||||||
href={getIssueUrl(issueData.Issue.number)}
|
(isSelected || highlighted?.includes(issueData.Issue.id)) &&
|
||||||
onClick={handleLinkClick}
|
"shadow-[inset_0_1px_0_0_var(--personality),inset_0_-1px_0_0_var(--personality)]",
|
||||||
className="flex items-center gap-2 min-w-0 w-full h-full px-2 py-1 text-inherit hover:underline decoration-transparent"
|
)}
|
||||||
>
|
>
|
||||||
{selectedOrganisation?.Organisation.features.issueTypes &&
|
<a
|
||||||
issueTypes[issueData.Issue.type] && (
|
href={getIssueUrl(issueData.Issue.number)}
|
||||||
<Icon
|
onClick={handleLinkClick}
|
||||||
icon={issueTypes[issueData.Issue.type].icon as IconName}
|
className="flex items-center gap-2 min-w-0 w-full h-full px-2 py-1 text-inherit hover:underline decoration-transparent"
|
||||||
size={16}
|
>
|
||||||
color={issueTypes[issueData.Issue.type].color}
|
{selectedOrganisation?.Organisation.features.issueTypes &&
|
||||||
/>
|
issueTypes[issueData.Issue.type] && (
|
||||||
)}
|
<Icon
|
||||||
{selectedOrganisation?.Organisation.features.issueStatus &&
|
icon={issueTypes[issueData.Issue.type].icon as IconName}
|
||||||
(columns.status == null || columns.status === true) && (
|
size={16}
|
||||||
<StatusTag
|
color={issueTypes[issueData.Issue.type].color}
|
||||||
status={issueData.Issue.status}
|
/>
|
||||||
colour={statuses[issueData.Issue.status]}
|
)}
|
||||||
/>
|
{selectedOrganisation?.Organisation.features.issueStatus &&
|
||||||
)}
|
(columns.status == null || columns.status === true) && (
|
||||||
<span className="truncate">{issueData.Issue.title}</span>
|
<StatusTag
|
||||||
</a>
|
status={issueData.Issue.status}
|
||||||
</TableCell>
|
colour={statuses[issueData.Issue.status]}
|
||||||
)}
|
/>
|
||||||
{showDescription && (
|
)}
|
||||||
<TableCell
|
<span className="truncate">{issueData.Issue.title}</span>
|
||||||
className={cn(
|
</a>
|
||||||
"overflow-hidden p-0",
|
</td>
|
||||||
(isSelected || highlighted?.includes(issueData.Issue.id)) &&
|
)}
|
||||||
"shadow-[inset_0_1px_0_0_var(--personality),inset_0_-1px_0_0_var(--personality)]",
|
{showDescription && (
|
||||||
)}
|
<td
|
||||||
>
|
className={cn(
|
||||||
<a
|
"overflow-hidden p-0 align-middle",
|
||||||
href={getIssueUrl(issueData.Issue.number)}
|
(isSelected || highlighted?.includes(issueData.Issue.id)) &&
|
||||||
onClick={handleLinkClick}
|
"shadow-[inset_0_1px_0_0_var(--personality),inset_0_-1px_0_0_var(--personality)]",
|
||||||
className="block w-full h-full px-2 py-1 text-inherit hover:underline decoration-transparent"
|
)}
|
||||||
>
|
>
|
||||||
{issueData.Issue.description}
|
<a
|
||||||
</a>
|
href={getIssueUrl(issueData.Issue.number)}
|
||||||
</TableCell>
|
onClick={handleLinkClick}
|
||||||
)}
|
className="block w-full h-full px-2 py-1 text-inherit hover:underline decoration-transparent"
|
||||||
{showAssignee && (
|
>
|
||||||
<TableCell
|
{issueData.Issue.description}
|
||||||
className={cn(
|
</a>
|
||||||
"h-[32px] p-0",
|
</td>
|
||||||
(isSelected || highlighted?.includes(issueData.Issue.id)) &&
|
)}
|
||||||
"shadow-[inset_0_1px_0_0_var(--personality),inset_-1px_0_0_0_var(--personality),inset_0_-1px_0_0_var(--personality)]",
|
{showAssignee && (
|
||||||
)}
|
<td
|
||||||
>
|
className={cn(
|
||||||
<a
|
"h-[32px] p-0 align-middle",
|
||||||
href={getIssueUrl(issueData.Issue.number)}
|
(isSelected || highlighted?.includes(issueData.Issue.id)) &&
|
||||||
onClick={handleLinkClick}
|
"shadow-[inset_0_1px_0_0_var(--personality),inset_-1px_0_0_0_var(--personality),inset_0_-1px_0_0_var(--personality)]",
|
||||||
className="flex items-center justify-end w-full h-full px-2"
|
)}
|
||||||
>
|
>
|
||||||
{selectedOrganisation?.Organisation.features.issueAssigneesShownInTable &&
|
<a
|
||||||
issueData.Assignees &&
|
href={getIssueUrl(issueData.Issue.number)}
|
||||||
issueData.Assignees.length > 0 && (
|
onClick={handleLinkClick}
|
||||||
<div className="flex items-center -space-x-2 pr-1.5">
|
className="flex items-center justify-end w-full h-full px-2"
|
||||||
{issueData.Assignees.slice(0, 3).map((assignee) => (
|
>
|
||||||
<Avatar
|
{selectedOrganisation?.Organisation.features.issueAssigneesShownInTable &&
|
||||||
key={assignee.id}
|
issueData.Assignees &&
|
||||||
name={assignee.name}
|
issueData.Assignees.length > 0 && (
|
||||||
username={assignee.username}
|
<div className="flex items-center -space-x-2 pr-1.5">
|
||||||
avatarURL={assignee.avatarURL}
|
{issueData.Assignees.slice(0, 3).map((assignee) => (
|
||||||
textClass="text-xs"
|
<Avatar
|
||||||
className="ring-1 ring-background"
|
key={assignee.id}
|
||||||
/>
|
name={assignee.name}
|
||||||
))}
|
username={assignee.username}
|
||||||
{issueData.Assignees.length > 3 && (
|
avatarURL={assignee.avatarURL}
|
||||||
<span className="flex items-center justify-center w-6 h-6 text-[10px] font-medium bg-muted text-muted-foreground rounded-full ring-1 ring-background">
|
textClass="text-xs"
|
||||||
+{issueData.Assignees.length - 3}
|
className="ring-1 ring-background"
|
||||||
</span>
|
/>
|
||||||
)}
|
))}
|
||||||
</div>
|
{issueData.Assignees.length > 3 && (
|
||||||
)}
|
<span className="flex items-center justify-center w-6 h-6 text-[10px] font-medium bg-muted text-muted-foreground rounded-full ring-1 ring-background">
|
||||||
</a>
|
+{issueData.Assignees.length - 3}
|
||||||
</TableCell>
|
</span>
|
||||||
)}
|
)}
|
||||||
</TableRow>
|
</div>
|
||||||
);
|
)}
|
||||||
})}
|
</a>
|
||||||
</TableBody>
|
</td>
|
||||||
</Table>
|
)}
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -667,15 +667,13 @@ export default function Issues() {
|
|||||||
|
|
||||||
{selectedOrganisationId && selectedProjectId && issuesData.length > 0 && (
|
{selectedOrganisationId && selectedProjectId && issuesData.length > 0 && (
|
||||||
<ResizablePanelGroup className={`flex-1`}>
|
<ResizablePanelGroup className={`flex-1`}>
|
||||||
<ResizablePanel id={"left"} minSize={400}>
|
<ResizablePanel id={"left"} minSize={400} className="h-full overflow-hidden">
|
||||||
<div className="border w-full flex-shrink">
|
<IssuesTable
|
||||||
<IssuesTable
|
columns={{ description: false }}
|
||||||
columns={{ description: false }}
|
className="w-full"
|
||||||
className="w-full"
|
filters={issueFilters}
|
||||||
filters={issueFilters}
|
highlighted={highlighted}
|
||||||
highlighted={highlighted}
|
/>
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</ResizablePanel>
|
</ResizablePanel>
|
||||||
|
|
||||||
{selectedIssue && !showIssueModal && (
|
{selectedIssue && !showIssueModal && (
|
||||||
|
|||||||
Reference in New Issue
Block a user