Skip to content
GitLab
Projects
Groups
Topics
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Register
Sign in
Toggle navigation
Menu
Paulo Avila
petruz-tasks
Commits
7264ecf5
Commit
7264ecf5
authored
Jun 15, 2026
by
Paulo Avila
Browse files
alteração na exibição das pastas
parent
47104fa0
Changes
5
Hide whitespace changes
Inline
Side-by-side
src/app/(app)/dashboard/page.tsx
View file @
7264ecf5
...
@@ -13,7 +13,7 @@ import {
...
@@ -13,7 +13,7 @@ import {
import
{
Progress
}
from
"
@/components/ui/progress
"
;
import
{
Progress
}
from
"
@/components/ui/progress
"
;
import
{
Button
}
from
"
@/components/ui/button
"
;
import
{
Button
}
from
"
@/components/ui/button
"
;
import
{
DueBadge
}
from
"
@/components/tasks/due-badge
"
;
import
{
DueBadge
}
from
"
@/components/tasks/due-badge
"
;
import
{
STATUS_CHIPS
}
from
"
@/components/tasks/task-
by-person
"
;
import
{
STATUS_CHIPS
}
from
"
@/components/tasks/task-
utils
"
;
import
{
formatDate
}
from
"
@/lib/dates
"
;
import
{
formatDate
}
from
"
@/lib/dates
"
;
import
{
cn
}
from
"
@/lib/utils
"
;
import
{
cn
}
from
"
@/lib/utils
"
;
...
...
src/components/layout/sidebar.tsx
View file @
7264ecf5
...
@@ -40,7 +40,7 @@ export function Sidebar({
...
@@ -40,7 +40,7 @@ export function Sidebar({
return
(
return
(
<
aside
<
aside
className
=
{
cn
(
className
=
{
cn
(
"
hidd
en shrink-0 flex-col bg-sidebar text-sidebar-foreground transition-[width] duration-200 md:flex
"
,
"
sticky top-0 hidden h-scre
en shrink-0 flex-col bg-sidebar text-sidebar-foreground transition-[width] duration-200 md:flex
"
,
collapsed
?
"
w-16
"
:
"
w-64
"
collapsed
?
"
w-16
"
:
"
w-64
"
)
}
)
}
>
>
...
...
src/components/tasks/task-by-person.tsx
View file @
7264ecf5
"
use client
"
;
import
{
useMemo
,
useState
}
from
"
react
"
;
import
Link
from
"
next/link
"
;
import
Link
from
"
next/link
"
;
import
{
import
{
ChevronRight
,
Plus
,
Search
,
UserRound
}
from
"
lucide-react
"
;
CheckCircle2
,
Circle
,
Eye
,
LoaderCircle
,
Plus
,
UserRound
,
}
from
"
lucide-react
"
;
import
type
{
Subtask
,
Task
,
WorkspaceMember
}
from
"
@/lib/data
"
;
import
type
{
Subtask
,
Task
,
WorkspaceMember
}
from
"
@/lib/data
"
;
import
{
Avatar
,
AvatarFallback
}
from
"
@/components/ui/avatar
"
;
import
{
Avatar
,
AvatarFallback
}
from
"
@/components/ui/avatar
"
;
import
{
Button
}
from
"
@/components/ui/button
"
;
import
{
Button
}
from
"
@/components/ui/button
"
;
import
{
Input
}
from
"
@/components/ui/input
"
;
import
{
Progress
}
from
"
@/components/ui/progress
"
;
import
{
Progress
}
from
"
@/components/ui/progress
"
;
import
{
cn
}
from
"
@/lib/utils
"
;
import
{
cn
}
from
"
@/lib/utils
"
;
import
{
TaskDialog
}
from
"
./task-dialog
"
;
import
{
TaskDialog
}
from
"
./task-dialog
"
;
import
{
taskPercent
}
from
"
./task-progress
"
;
import
{
taskPercent
}
from
"
./task-progress
"
;
import
{
initials
,
STATUS_CHIPS
}
from
"
./task-utils
"
;
function
initials
(
name
:
string
):
string
{
export
{
STATUS_CHIPS
};
return
name
.
split
(
"
"
)
.
filter
(
Boolean
)
.
slice
(
0
,
2
)
.
map
((
part
)
=>
part
[
0
]?.
toUpperCase
())
.
join
(
""
);
}
export
const
STATUS_CHIPS
=
[
{
value
:
"
todo
"
,
label
:
"
A Fazer
"
,
icon
:
Circle
,
className
:
"
text-slate-600 dark:text-slate-300
"
,
chipBg
:
"
bg-slate-500/10
"
},
{
value
:
"
in_progress
"
,
label
:
"
Em Andamento
"
,
icon
:
LoaderCircle
,
className
:
"
text-blue-600 dark:text-blue-400
"
,
chipBg
:
"
bg-blue-500/10
"
},
{
value
:
"
review
"
,
label
:
"
Em Revisão
"
,
icon
:
Eye
,
className
:
"
text-purple-600 dark:text-purple-400
"
,
chipBg
:
"
bg-purple-500/10
"
},
{
value
:
"
done
"
,
label
:
"
Concluídas
"
,
icon
:
CheckCircle2
,
className
:
"
text-green-600 dark:text-green-400
"
,
chipBg
:
"
bg-green-500/10
"
},
]
as
const
;
interface
PersonGroup
{
interface
PersonGroup
{
key
:
string
;
key
:
string
;
...
@@ -39,8 +23,9 @@ interface PersonGroup {
...
@@ -39,8 +23,9 @@ interface PersonGroup {
}
}
/**
/**
* Pastas por membro em grade: clicar no card abre a página da pessoa
* Lista de pastas por membro: busca por nome no topo e linhas compactas
* com apenas as tarefas dela.
* (mais pessoas visíveis de uma vez). Clicar na linha abre a página da
* pessoa com apenas as tarefas dela.
*/
*/
export
function
TaskByPerson
({
export
function
TaskByPerson
({
tasks
,
tasks
,
...
@@ -56,145 +41,174 @@ export function TaskByPerson({
...
@@ -56,145 +41,174 @@ export function TaskByPerson({
isAdmin
:
boolean
;
isAdmin
:
boolean
;
subtasksByTask
?:
Record
<
number
,
Subtask
[]
>
;
subtasksByTask
?:
Record
<
number
,
Subtask
[]
>
;
})
{
})
{
const
groups
:
PersonGroup
[]
=
members
.
map
((
member
)
=>
({
const
[
search
,
setSearch
]
=
useState
(
""
);
key
:
String
(
member
.
UserId
),
name
:
member
.
Name
,
const
groups
:
PersonGroup
[]
=
useMemo
(()
=>
{
userId
:
member
.
UserId
,
const
list
:
PersonGroup
[]
=
members
.
map
((
member
)
=>
({
tasks
:
tasks
.
filter
((
t
)
=>
t
.
AssigneeId
===
member
.
UserId
),
key
:
String
(
member
.
UserId
),
}));
name
:
member
.
Name
,
const
unassigned
=
tasks
.
filter
((
t
)
=>
t
.
AssigneeId
===
null
);
userId
:
member
.
UserId
,
if
(
unassigned
.
length
>
0
)
{
tasks
:
tasks
.
filter
((
t
)
=>
t
.
AssigneeId
===
member
.
UserId
),
groups
.
push
({
}));
key
:
"
unassigned
"
,
const
unassigned
=
tasks
.
filter
((
t
)
=>
t
.
AssigneeId
===
null
);
name
:
null
,
if
(
unassigned
.
length
>
0
)
{
userId
:
null
,
list
.
push
({
tasks
:
unassigned
,
key
:
"
unassigned
"
,
});
name
:
null
,
}
userId
:
null
,
tasks
:
unassigned
,
});
}
return
list
;
},
[
members
,
tasks
]);
const
term
=
search
.
trim
().
toLowerCase
();
const
visibleGroups
=
term
?
groups
.
filter
((
g
)
=>
(
g
.
name
??
"
sem responsável
"
).
toLowerCase
().
includes
(
term
))
:
groups
;
return
(
return
(
<
div
className
=
"grid gap-4 sm:grid-cols-2 xl:grid-cols-3"
>
<
div
className
=
"space-y-3"
>
{
groups
.
map
((
group
)
=>
{
<
div
className
=
"relative max-w-sm"
>
const
total
=
group
.
tasks
.
length
;
<
Search
className
=
"absolute left-2.5 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground"
/>
// Média do % das tarefas: reflete progresso parcial, não só concluídas.
<
Input
const
percent
=
value
=
{
search
}
total
>
0
onChange
=
{
(
e
)
=>
setSearch
(
e
.
target
.
value
)
}
?
Math
.
round
(
placeholder
=
"Buscar pessoa..."
group
.
tasks
.
reduce
(
className
=
"pl-9"
(
sum
,
t
)
=>
/>
sum
+
</
div
>
taskPercent
(
t
.
Status
,
t
.
SubtaskCount
,
t
.
SubtaskDone
,
t
.
Progress
),
0
<
div
className
=
"overflow-hidden rounded-xl border bg-card"
>
)
/
total
{
visibleGroups
.
length
===
0
?
(
)
<
p
className
=
"p-8 text-center text-sm text-muted-foreground"
>
:
0
;
Nenhuma pessoa encontrada para “
{
search
}
”.
const
isOwn
=
group
.
userId
===
currentUserId
;
</
p
>
const
canQuickCreate
=
)
:
(
group
.
userId
!==
null
&&
(
isAdmin
||
isOwn
);
<
ul
className
=
"divide-y"
>
{
visibleGroups
.
map
((
group
)
=>
{
const
total
=
group
.
tasks
.
length
;
const
done
=
group
.
tasks
.
filter
((
t
)
=>
t
.
Status
===
"
done
"
).
length
;
// Média do % das tarefas: reflete progresso parcial.
const
percent
=
total
>
0
?
Math
.
round
(
group
.
tasks
.
reduce
(
(
sum
,
t
)
=>
sum
+
taskPercent
(
t
.
Status
,
t
.
SubtaskCount
,
t
.
SubtaskDone
,
t
.
Progress
),
0
)
/
total
)
:
0
;
const
isOwn
=
group
.
userId
===
currentUserId
;
const
canQuickCreate
=
group
.
userId
!==
null
&&
(
isAdmin
||
isOwn
);
return
(
return
(
<
div
<
li
key
=
{
group
.
key
}
className
=
"group relative"
>
key
=
{
group
.
key
}
<
Link
className
=
"group relative rounded-xl border bg-card transition hover:border-primary/50 hover:shadow-md"
href
=
{
`/workspaces/
${
workspaceId
}
/membro/
${
group
.
userId
??
0
}
`
}
>
className
=
"flex items-center gap-3 py-3 pl-4 pr-24 transition-colors hover:bg-muted/40"
<
Link
href
=
{
`/workspaces/
${
workspaceId
}
/membro/
${
group
.
userId
??
0
}
`
}
className
=
"block space-y-4 p-5"
>
<
div
className
=
"flex items-center gap-3"
>
<
Avatar
className
=
"h-12 w-12 ring-2 ring-primary/15"
>
<
AvatarFallback
className
=
{
group
.
name
?
"
bg-primary text-primary-foreground text-sm font-semibold
"
:
"
bg-muted text-muted-foreground
"
}
>
>
{
group
.
name
?
(
<
Avatar
className
=
"h-9 w-9 shrink-0"
>
initials
(
group
.
name
)
<
AvatarFallback
)
:
(
className
=
{
<
UserRound
className
=
"h-5 w-5"
/>
group
.
name
)
}
?
"
bg-primary text-primary-foreground text-xs font-semibold
"
</
AvatarFallback
>
:
"
bg-muted text-muted-foreground
"
</
Avatar
>
}
<
div
className
=
"min-w-0"
>
>
<
p
className
=
"truncate font-semibold leading-tight"
>
{
group
.
name
?
(
{
group
.
name
??
"
Sem responsável
"
}
initials
(
group
.
name
)
{
isOwn
&&
(
)
:
(
<
span
className
=
"ml-1.5 text-xs font-normal text-primary"
>
<
UserRound
className
=
"h-4 w-4"
/>
(você)
)
}
</
span
>
</
AvatarFallback
>
)
}
</
Avatar
>
</
p
>
<
p
className
=
"text-xs text-muted-foreground"
>
{
total
===
0
?
"
Nenhuma tarefa
"
:
`
${
total
}
tarefa
${
total
>
1
?
"
s
"
:
""
}
`
}
</
p
>
</
div
>
</
div
>
<
div
className
=
"flex flex-wrap gap-1.5"
>
<
div
className
=
"min-w-0 flex-1"
>
{
STATUS_CHIPS
.
map
((
chip
)
=>
{
<
p
className
=
"truncate text-sm font-medium"
>
const
count
=
group
.
tasks
.
filter
(
{
group
.
name
??
"
Sem responsável
"
}
(
t
)
=>
t
.
Status
===
chip
.
value
{
isOwn
&&
(
).
length
;
<
span
className
=
"ml-1.5 text-xs font-normal text-primary"
>
if
(
count
===
0
)
return
null
;
(você)
const
Icon
=
chip
.
icon
;
</
span
>
return
(
)
}
<
span
</
p
>
key
=
{
chip
.
value
}
<
p
className
=
"text-xs text-muted-foreground"
>
title
=
{
chip
.
label
}
{
total
===
0
className
=
{
cn
(
?
"
Nenhuma tarefa
"
"
inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-xs font-medium
"
,
:
`
${
done
}
/
${
total
}
concluída
${
total
>
1
?
"
s
"
:
""
}
`
}
chip
.
chipBg
,
</
p
>
chip
.
className
</
div
>
)
}
>
<
Icon
className
=
"h-3 w-3"
/>
{
count
}
</
span
>
);
})
}
{
total
===
0
&&
(
<
span
className
=
"rounded-full bg-muted px-2 py-0.5 text-xs text-muted-foreground"
>
Pasta vazia
</
span
>
)
}
</
div
>
<
div
className
=
"flex items-center gap-2"
>
{
/* Contadores por status — escondem em telas estreitas */
}
<
Progress
value
=
{
percent
}
className
=
"h-1.5"
/>
<
div
className
=
"hidden items-center gap-3 lg:flex"
>
<
span
className
=
"text-xs font-semibold text-muted-foreground"
>
{
STATUS_CHIPS
.
map
((
chip
)
=>
{
{
percent
}
%
const
count
=
group
.
tasks
.
filter
(
</
span
>
(
t
)
=>
t
.
Status
===
chip
.
value
</
div
>
).
length
;
</
Link
>
const
Icon
=
chip
.
icon
;
return
(
<
span
key
=
{
chip
.
value
}
title
=
{
chip
.
label
}
className
=
{
cn
(
"
flex w-9 items-center justify-center gap-1 text-xs tabular-nums
"
,
count
>
0
?
chip
.
className
:
"
text-muted-foreground/30
"
)
}
>
<
Icon
className
=
"h-3.5 w-3.5"
/>
{
count
}
</
span
>
);
})
}
</
div
>
{
canQuickCreate
&&
(
{
/* Progresso */
}
<
div
className
=
"absolute right-3 top-3"
>
<
div
className
=
"hidden w-40 items-center gap-2 sm:flex"
>
<
TaskDialog
<
Progress
value
=
{
percent
}
className
=
"h-1.5"
/>
workspaceId
=
{
workspaceId
}
<
span
className
=
"w-9 shrink-0 text-right text-xs font-semibold tabular-nums text-muted-foreground"
>
members
=
{
members
}
{
percent
}
%
defaultAssigneeId
=
{
group
.
userId
??
undefined
}
</
span
>
currentUserId
=
{
currentUserId
}
</
div
>
isAdmin
=
{
isAdmin
}
</
Link
>
trigger
=
{
<
Button
{
/* Ações à direita, fora do Link para HTML válido */
}
variant
=
"ghost"
<
div
className
=
"absolute right-3 top-1/2 flex -translate-y-1/2 items-center gap-1"
>
size
=
"icon"
{
canQuickCreate
&&
(
className
=
"h-8 w-8 text-muted-foreground transition-opacity md:opacity-0 md:group-hover:opacity-100"
<
TaskDialog
title
=
{
`Nova tarefa para
${
group
.
name
}
`
}
workspaceId
=
{
workspaceId
}
>
members
=
{
members
}
<
Plus
className
=
"h-4 w-4"
/>
defaultAssigneeId
=
{
group
.
userId
??
undefined
}
</
Button
>
currentUserId
=
{
currentUserId
}
}
isAdmin
=
{
isAdmin
}
/>
trigger
=
{
</
div
>
<
Button
)
}
variant
=
"ghost"
</
div
>
size
=
"icon"
);
className
=
"h-8 w-8 text-muted-foreground transition-opacity md:opacity-0 md:group-hover:opacity-100"
})
}
title
=
{
`Nova tarefa para
${
group
.
name
??
"
ninguém
"
}
`
}
>
<
Plus
className
=
"h-4 w-4"
/>
</
Button
>
}
/>
)
}
<
ChevronRight
className
=
"h-4 w-4 text-muted-foreground"
/>
</
div
>
</
li
>
);
})
}
</
ul
>
)
}
</
div
>
</
div
>
</
div
>
);
);
}
}
src/components/tasks/task-table.tsx
View file @
7264ecf5
...
@@ -3,6 +3,7 @@
...
@@ -3,6 +3,7 @@
import
{
Fragment
,
useState
}
from
"
react
"
;
import
{
Fragment
,
useState
}
from
"
react
"
;
import
{
CheckCircle2
,
ChevronRight
,
Circle
,
Pencil
}
from
"
lucide-react
"
;
import
{
CheckCircle2
,
ChevronRight
,
Circle
,
Pencil
}
from
"
lucide-react
"
;
import
type
{
Subtask
,
Task
,
WorkspaceMember
}
from
"
@/lib/data
"
;
import
type
{
Subtask
,
Task
,
WorkspaceMember
}
from
"
@/lib/data
"
;
import
{
TASK_STATUSES
}
from
"
@/lib/constants
"
;
import
{
formatDate
}
from
"
@/lib/dates
"
;
import
{
formatDate
}
from
"
@/lib/dates
"
;
import
{
canEditTask
}
from
"
@/lib/permissions
"
;
import
{
canEditTask
}
from
"
@/lib/permissions
"
;
import
{
initials
,
toFormValues
}
from
"
./task-utils
"
;
import
{
initials
,
toFormValues
}
from
"
./task-utils
"
;
...
@@ -45,6 +46,7 @@ export function TaskTable({
...
@@ -45,6 +46,7 @@ export function TaskTable({
isAdmin
:
boolean
;
isAdmin
:
boolean
;
})
{
})
{
const
[
expanded
,
setExpanded
]
=
useState
<
number
[]
>
([]);
const
[
expanded
,
setExpanded
]
=
useState
<
number
[]
>
([]);
const
[
statusFilter
,
setStatusFilter
]
=
useState
<
string
>
(
"
all
"
);
function
toggleExpanded
(
taskId
:
number
)
{
function
toggleExpanded
(
taskId
:
number
)
{
setExpanded
((
prev
)
=>
setExpanded
((
prev
)
=>
...
@@ -62,8 +64,51 @@ export function TaskTable({
...
@@ -62,8 +64,51 @@ export function TaskTable({
);
);
}
}
const
filteredTasks
=
statusFilter
===
"
all
"
?
tasks
:
tasks
.
filter
((
t
)
=>
t
.
Status
===
statusFilter
);
const
filterOptions
=
[
{
value
:
"
all
"
,
label
:
"
Todas
"
,
count
:
tasks
.
length
},
...
TASK_STATUSES
.
map
((
s
)
=>
({
value
:
s
.
value
,
label
:
s
.
label
,
count
:
tasks
.
filter
((
t
)
=>
t
.
Status
===
s
.
value
).
length
,
})),
];
return
(
return
(
<
div
className
=
"overflow-hidden rounded-xl border bg-card"
>
<
div
className
=
"space-y-3"
>
<
div
className
=
"flex flex-wrap items-center gap-2"
>
{
filterOptions
.
map
((
option
)
=>
(
<
button
key
=
{
option
.
value
}
type
=
"button"
onClick
=
{
()
=>
setStatusFilter
(
option
.
value
)
}
className
=
{
cn
(
"
inline-flex items-center gap-1.5 rounded-full border px-3 py-1 text-xs font-medium transition-colors
"
,
statusFilter
===
option
.
value
?
"
border-primary bg-primary text-primary-foreground
"
:
"
border-border bg-card text-muted-foreground hover:bg-muted
"
)
}
>
{
option
.
label
}
<
span
className
=
{
cn
(
"
rounded-full px-1.5 text-[10px]
"
,
statusFilter
===
option
.
value
?
"
bg-primary-foreground/20
"
:
"
bg-muted
"
)
}
>
{
option
.
count
}
</
span
>
</
button
>
))
}
</
div
>
<
div
className
=
"overflow-hidden rounded-xl border bg-card"
>
<
Table
>
<
Table
>
<
TableHeader
>
<
TableHeader
>
<
TableRow
className
=
"border-b bg-muted/40 hover:bg-muted/40"
>
<
TableRow
className
=
"border-b bg-muted/40 hover:bg-muted/40"
>
...
@@ -86,7 +131,7 @@ export function TaskTable({
...
@@ -86,7 +131,7 @@ export function TaskTable({
</
TableRow
>
</
TableRow
>
</
TableHeader
>
</
TableHeader
>
<
TableBody
>
<
TableBody
>
{
t
asks
.
map
((
task
)
=>
{
{
filteredT
asks
.
map
((
task
)
=>
{
const
editable
=
canEditTask
(
task
,
currentUserId
,
isAdmin
);
const
editable
=
canEditTask
(
task
,
currentUserId
,
isAdmin
);
const
subtasks
=
subtasksByTask
[
task
.
Id
]
??
[];
const
subtasks
=
subtasksByTask
[
task
.
Id
]
??
[];
const
hasSubtasks
=
subtasks
.
length
>
0
;
const
hasSubtasks
=
subtasks
.
length
>
0
;
...
@@ -99,8 +144,8 @@ export function TaskTable({
...
@@ -99,8 +144,8 @@ export function TaskTable({
isOpen
?
"
border-0 bg-muted/20
"
:
"
last:border-0
"
isOpen
?
"
border-0 bg-muted/20
"
:
"
last:border-0
"
)
}
)
}
>
>
<
TableCell
className
=
"m
ax
-w-[
30
0px] py-3"
>
<
TableCell
className
=
"m
in
-w-[
26
0px] py-3
align-top
"
>
<
div
className
=
"flex items-
center
gap-1.5"
>
<
div
className
=
"flex items-
start
gap-1.5"
>
{
hasSubtasks
?
(
{
hasSubtasks
?
(
<
button
<
button
type
=
"button"
type
=
"button"
...
@@ -110,7 +155,7 @@ export function TaskTable({
...
@@ -110,7 +155,7 @@ export function TaskTable({
?
"
Recolher subtarefas
"
?
"
Recolher subtarefas
"
:
"
Expandir subtarefas
"
:
"
Expandir subtarefas
"
}
}
className
=
"flex shrink-0 items-center gap-1 rounded-md px-1 py-0.5 text-xs text-muted-foreground hover:bg-muted hover:text-foreground"
className
=
"
mt-0.5
flex shrink-0 items-center gap-1 rounded-md px-1 py-0.5 text-xs text-muted-foreground hover:bg-muted hover:text-foreground"
>
>
<
ChevronRight
<
ChevronRight
className
=
{
cn
(
className
=
{
cn
(
...
@@ -124,14 +169,20 @@ export function TaskTable({
...
@@ -124,14 +169,20 @@ export function TaskTable({
)
:
(
)
:
(
<
span
className
=
"w-5 shrink-0"
/>
<
span
className
=
"w-5 shrink-0"
/>
)
}
)
}
<
p
className
=
"truncate font-medium"
>
{
task
.
Title
}
</
p
>
<
div
className
=
"min-w-0 flex-1"
>
<
PriorityBadge
priority
=
{
task
.
Priority
}
/>
<
div
className
=
"flex flex-wrap items-center gap-1.5"
>
<
span
className
=
"font-medium leading-snug"
>
{
task
.
Title
}
</
span
>
<
PriorityBadge
priority
=
{
task
.
Priority
}
/>
</
div
>
{
task
.
Description
&&
(
<
p
className
=
"mt-0.5 text-xs text-muted-foreground"
>
{
task
.
Description
}
</
p
>
)
}
</
div
>
</
div
>
</
div
>
{
task
.
Description
&&
(
<
p
className
=
"ml-6 mt-0.5 truncate text-xs text-muted-foreground"
>
{
task
.
Description
}
</
p
>
)
}
</
TableCell
>
</
TableCell
>
<
TableCell
className
=
"py-3"
>
<
TableCell
className
=
"py-3"
>
{
editable
?
(
{
editable
?
(
...
@@ -257,6 +308,12 @@ export function TaskTable({
...
@@ -257,6 +308,12 @@ export function TaskTable({
})
}
})
}
</
TableBody
>
</
TableBody
>
</
Table
>
</
Table
>
{
filteredTasks
.
length
===
0
&&
(
<
p
className
=
"p-8 text-center text-sm text-muted-foreground"
>
Nenhuma tarefa com este status.
</
p
>
)
}
</
div
>
</
div
>
</
div
>
);
);
}
}
src/components/tasks/task-utils.ts
View file @
7264ecf5
// Utilitários compartilhados entre componentes de servidor e de cliente —
// Utilitários compartilhados entre componentes de servidor e de cliente —
// este módulo não pode ter "use client" nem importar módulos cliente.
// este módulo não pode ter "use client" nem importar módulos cliente.
import
{
CheckCircle2
,
Circle
,
Eye
,
LoaderCircle
}
from
"
lucide-react
"
;
import
type
{
Task
}
from
"
@/lib/data
"
;
import
type
{
Task
}
from
"
@/lib/data
"
;
import
{
toISODate
}
from
"
@/lib/dates
"
;
import
{
toISODate
}
from
"
@/lib/dates
"
;
import
type
{
TaskFormValues
}
from
"
./task-dialog
"
;
import
type
{
TaskFormValues
}
from
"
./task-dialog
"
;
export
const
STATUS_CHIPS
=
[
{
value
:
"
todo
"
,
label
:
"
A Fazer
"
,
icon
:
Circle
,
className
:
"
text-slate-600 dark:text-slate-300
"
,
chipBg
:
"
bg-slate-500/10
"
},
{
value
:
"
in_progress
"
,
label
:
"
Em Andamento
"
,
icon
:
LoaderCircle
,
className
:
"
text-blue-600 dark:text-blue-400
"
,
chipBg
:
"
bg-blue-500/10
"
},
{
value
:
"
review
"
,
label
:
"
Em Revisão
"
,
icon
:
Eye
,
className
:
"
text-purple-600 dark:text-purple-400
"
,
chipBg
:
"
bg-purple-500/10
"
},
{
value
:
"
done
"
,
label
:
"
Concluídas
"
,
icon
:
CheckCircle2
,
className
:
"
text-green-600 dark:text-green-400
"
,
chipBg
:
"
bg-green-500/10
"
},
]
as
const
;
export
function
toFormValues
(
task
:
Task
):
TaskFormValues
{
export
function
toFormValues
(
task
:
Task
):
TaskFormValues
{
return
{
return
{
id
:
task
.
Id
,
id
:
task
.
Id
,
...
...
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment