Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion backend/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ model Issue {
description String?
state String @default("backlog") // "backlog", "todo", "in_progress", "done", "canceled"
priority Int @default(0) // 0=no priority, 1=urgent, 2=high, 3=medium, 4=low
labels String[] @default([])
labels String[] @default([])
dueDate DateTime?
notes String?
number Int // Linear-style issue number
Expand Down
37 changes: 15 additions & 22 deletions client/app/(dashboard)/[workspaceSlug]/[teamKey]/issues/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@ import { useIssueStore } from '@/stores/issueStore'
import { IssueListView } from '@/components/features/issues/IssueListView'
import { IssueBoardView } from '@/components/features/issues/IssueBoardView'
import { IssueDetails } from '@/components/features/issues/IssueDetails'
import { CreateIssueButton } from '@/components/features/issues/CreateIssueButton'
import { ViewToggle } from '@/components/features/issues/ViewToggle'
// import { CreateIssueButton } from '@/components/features/issues/CreateIssueButton'
// import { ViewToggle } from '@/components/features/issues/ViewToggle'
import { LoadingSpinner } from '@/components/ui/atoms/loading-spinner'
import { use } from 'react'
import { ViewHeader } from '@/components/ui/organisms/ViewHeader'
import { Users } from 'lucide-react'

interface TeamIssuesPageProps {
params: Promise<{
Expand Down Expand Up @@ -59,26 +61,17 @@ export default function TeamIssuesPage({ params }: TeamIssuesPageProps) {
return (
<div className="flex flex-col h-full bg-background">
{/* Header */}
<div className="flex items-center justify-between pl-4 pr-4 pt-2 pb-2 border-b border-border">
<div className="flex items-center gap-3">
<div
className="w-4 h-4 rounded-full"
style={{ backgroundColor: currentTeam.color }}
/>
<h1 className="text-lg font-bold text-foreground">{currentTeam.name} Issues</h1>
</div>

<div className="flex items-center gap-2">
<ViewToggle view={view} setView={setView}/>
<CreateIssueButton
workspaceId={currentWorkspace?.id}
showCreateIssue={showCreateIssue}
setShowCreateIssue={setShowCreateIssue}
size="xs"
/>
</div>
</div>

<ViewHeader
title={`${currentTeam.name} Issues`}
icon={<Users size={20} className="text-muted-foreground" />}
color={currentTeam.color}
showViewToggle={true}
view={view}
setView={setView}
showCreateIssue={showCreateIssue}
setShowCreateIssue={setShowCreateIssue}
workspaceId={currentWorkspace?.id}
/>
{/* Main Content */}
<div className="flex-1">
{view === 'list' ? (
Expand Down
Empty file.
28 changes: 20 additions & 8 deletions client/app/(dashboard)/[workspaceSlug]/myissues/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,17 @@ import { useWorkspaceStore } from '@/stores/workspaceStore'
import { useIssueStore } from '@/stores/issueStore'
import { IssueListView } from '@/components/features/issues/IssueListView'
import { IssueBoardView } from '@/components/features/issues/IssueBoardView'
import { IssueDetails } from '@/components/features/issues/IssueDetails'
import { CreateIssueButton } from '@/components/features/issues/CreateIssueButton'
import { ViewToggle } from '@/components/features/issues/ViewToggle'
// import { IssueDetails } from '@/components/features/issues/IssueDetails'
// import { CreateIssueButton } from '@/components/features/issues/CreateIssueButton'
// import { ViewToggle } from '@/components/features/issues/ViewToggle'
import { LoadingSpinner } from '@/components/ui/atoms/loading-spinner'
import { ViewHeader } from '@/components/ui/organisms/ViewHeader'
import { User } from 'lucide-react'

export default function MyIssuesPage() {
const currentWorkspace = useWorkspaceStore((state) => state.currentWorkspace)
const isLoading = useWorkspaceStore((state) => state.isLoading)
const { issues, loadingStates, selectedIssueId, fetchIssuesByWorkspace, setSelectedIssue } = useIssueStore()
const { issues, loadingStates, fetchIssuesByWorkspace } = useIssueStore()

// Local UI state for view and create modal
const [view, setView] = useState<'list' | 'board'>('list')
Expand All @@ -37,7 +39,17 @@ export default function MyIssuesPage() {
return (
<div className="flex flex-col h-full bg-background">
{/* Header */}
<div className="flex items-center justify-between pl-4 pr-4 pt-2 pb-2 border-b border-border">
<ViewHeader
title="My Issues"
icon={<User size={20} className="text-muted-foreground" />}
showViewToggle={true}
view={view}
setView={setView}
showCreateIssue={showCreateIssue}
setShowCreateIssue={setShowCreateIssue}
workspaceId={currentWorkspace?.id}
/>
{/* <div className="flex items-center justify-between pl-4 pr-4 pt-2 pb-2 border-b border-border">
<div>
<h1 className="text-lg font-bold text-foreground">My Issues</h1>
</div>
Expand All @@ -51,7 +63,7 @@ export default function MyIssuesPage() {
size="xs"
/>
</div>
</div>
</div> */}

{/* Main Content */}
<div className="flex-1">
Expand All @@ -63,13 +75,13 @@ export default function MyIssuesPage() {
</div>

{/* Issue Details Sidebar */}
{selectedIssueId && (
{/* {selectedIssueId && (
<IssueDetails
key={`issue-details-${selectedIssueId}`}
issueId={selectedIssueId}
onClose={() => setSelectedIssue(null)}
/>
)}
)} */}
</div>
)
}
19 changes: 19 additions & 0 deletions client/app/(dashboard)/[workspaceSlug]/teams/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
'use client'

import React from 'react'

export default function TeamsPage() {
return (
<div className="flex flex-col h-full bg-background">
{/* Header */}
<div className="flex items-center justify-between pl-4 pr-4 pt-2 pb-2 border-b border-border">
<h1 className="text-lg font-bold text-foreground">Teams</h1>
</div>

{/* Main Content */}
<div className="flex-1 flex items-center justify-center">
<span className="text-muted-foreground">Teams management coming soon.</span>
</div>
</div>
)
}
6 changes: 3 additions & 3 deletions client/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@
--card-foreground: oklch(0 0 0);
--popover: oklch(0.9401 0 0);
--popover-foreground: oklch(0 0 0);
--primary: oklch(0.6267 0.1272 297.2303);
--primary: oklch(0.1346 0 129.63);
--primary-foreground: oklch(1.0000 0 0);
--secondary: oklch(0.6508 0.0955 296.5799);
--secondary-foreground: oklch(1.0000 0 0);
Expand Down Expand Up @@ -223,8 +223,8 @@
--card-foreground: oklch(1.0000 0 0);
--popover: oklch(0.2686 0.0218 265.7834);
--popover-foreground: oklch(1.0000 0 0);
--primary: oklch(0.5771 0.1491 288.6467);
--primary-foreground: oklch(1.0000 0 0);
--primary: oklch(0.8721 0 208);
--primary-foreground: oklch(0 0 0);
--secondary: oklch(0.4538 0.0277 258.3696);
--secondary-foreground: oklch(1.0000 0 0);
--muted: oklch(0.2598 0.0202 264.1313);
Expand Down
52 changes: 3 additions & 49 deletions client/components/features/issues/IssueBoardView.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
'use client'

import React, { useMemo, useState } from 'react'
import React, { useMemo } from 'react'
import { DragDropContext, DropResult } from '@hello-pangea/dnd'
import { IssueColumn } from '@/components/features/issues/IssueColumn'
import { IssueFilterBar } from '@/components/features/issues/IssueFilterBar'
import { useIssueStore } from '@/stores/issueStore'
import { useIssueFiltering } from '@/hooks/useIssueFiltering'
import { toast } from 'react-hot-toast'
import { Issue } from '@/types/issue'
import { useTeamStore } from '@/stores/teamStore'

interface IssueBoardViewProps {
issues: Issue[]
Expand Down Expand Up @@ -45,41 +42,18 @@ const COLUMNS = [

export function IssueBoardView({ issues }: IssueBoardViewProps) {
const { updateIssue } = useIssueStore()
const { teams: teamList } = useTeamStore()
const [teamFilter, setTeamFilter] = useState('All')
const [sortOptions] = useState([
{ label: 'None', value: 'None' },
{ label: 'Priority', value: 'Priority' },
{ label: 'Assignee', value: 'Assignee' },
{ label: 'Created At', value: 'Created At' }
])
// Use filtering hook
const {
stateFilter,
setStateFilter,
priorityFilter,
setPriorityFilter,
assigneeFilter,
setAssigneeFilter,
sortOption,
setSortOption,
filteredIssues,
clearAllFilters,
hasActiveFilters,
filterSummary
} = useIssueFiltering()

// Group filtered issues by state (using canonical state values)
const groupedIssues = useMemo(() => {
return filteredIssues.reduce((acc, issue) => {
return issues.reduce((acc, issue) => {
const state = issue.state
if (!acc[state]) {
acc[state] = []
}
acc[state].push(issue)
return acc
}, {} as Record<string, Issue[]>)
}, [filteredIssues])
}, [issues])

// Handle drag end
const handleDragEnd = async (result: DropResult) => {
Expand Down Expand Up @@ -108,26 +82,6 @@ export function IssueBoardView({ issues }: IssueBoardViewProps) {

return (
<div className="flex flex-col h-full">
{/* Filter Bar */}
<IssueFilterBar
issues={issues}
teams={teamList}
stateFilter={stateFilter}
setStateFilter={setStateFilter}
priorityFilter={priorityFilter}
setPriorityFilter={setPriorityFilter}
assigneeFilter={assigneeFilter}
setAssigneeFilter={setAssigneeFilter}
teamFilter={teamFilter}
setTeamFilter={setTeamFilter}
sortOption={sortOption}
setSortOption={setSortOption}
sortOptions={sortOptions}
hasActiveFilters={hasActiveFilters}
filterSummary={filterSummary}
onClearAll={clearAllFilters}
/>

{/* Issue Columns */}
<div className="flex-1 overflow-auto p-2">
<DragDropContext onDragEnd={handleDragEnd}>
Expand Down
79 changes: 3 additions & 76 deletions client/components/features/issues/IssueListView.tsx
Original file line number Diff line number Diff line change
@@ -1,102 +1,29 @@
'use client'

import React, { useState } from 'react'
import React from 'react'
import { Issue } from '@/types/issue'
import { IssueTable } from '@/components/features/issues/IssuesTable'
import { IssuesTableSkeleton } from '@/components/features/issues/IssuesTableSkeleton'
import { IssueFilterBar } from '@/components/features/issues/IssueFilterBar'
import { Pagination } from '@/components/features/issues/Pagination'
import { useIssueFiltering } from '@/hooks/useIssueFiltering'
import { usePagination } from '@/hooks/usePagination'
import { useTeamStore } from '@/stores/teamStore'

interface IssueListViewProps {
issues: Issue[]
isLoading?: boolean
}

export function IssueListView({ issues, isLoading = false }: IssueListViewProps) {
const [currentPage, setCurrentPage] = useState(1)
const issuesPerPage = 15
const { teams } = useTeamStore()
const [teamFilter, setTeamFilter] = useState('All')

// Use filtering hook
const {
stateFilter,
setStateFilter,
priorityFilter,
setPriorityFilter,
assigneeFilter,
setAssigneeFilter,
sortOption,
setSortOption,
filteredIssues,
clearAllFilters,
hasActiveFilters,
filterSummary
} = useIssueFiltering()

// Use pagination hook
const { currentItems: currentIssues, totalPages } = usePagination(
filteredIssues,
issuesPerPage,
currentPage
)

const sortOptions = [
{ value: "None", label: "None" },
{ value: "Due Date (Asc)", label: "Due Date (Asc)" },
{ value: "Due Date (Desc)", label: "Due Date (Desc)" },
{ value: "Priority (High → Low)", label: "Priority (High → Low)" },
{ value: "Priority (Low → High)", label: "Priority (Low → High)" },
{ value: "Title (A → Z)", label: "Title (A → Z)" },
{ value: "Title (Z → A)", label: "Title (Z → A)" },
{ value: "Date Created (Newest)", label: "Date Created (Newest)" },
{ value: "Date Created (Oldest)", label: "Date Created (Oldest)" }
]

return (
<div className="flex flex-col h-full">
{/* Filter Bar */}
<IssueFilterBar
issues={issues}
teams={teams}
stateFilter={stateFilter}
setStateFilter={setStateFilter}
priorityFilter={priorityFilter}
setPriorityFilter={setPriorityFilter}
assigneeFilter={assigneeFilter}
setAssigneeFilter={setAssigneeFilter}
teamFilter={teamFilter}
setTeamFilter={setTeamFilter}
sortOption={sortOption}
setSortOption={setSortOption}
sortOptions={sortOptions}
hasActiveFilters={hasActiveFilters}
filterSummary={filterSummary}
onClearAll={clearAllFilters}
/>

{/* Issue Table */}
<div className="flex-1 overflow-auto">
{isLoading ? (
<IssuesTableSkeleton />
) : (
<IssueTable issues={currentIssues} />
<IssueTable issues={issues} />
)}
</div>

{/* Pagination */}
{totalPages > 1 && (
<div className="border-t border-border p-4">
<Pagination
currentPage={currentPage}
totalPages={totalPages}
onPageChange={setCurrentPage}
/>
</div>
)}

</div>
)
}
Loading