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
6ccc2156
Commit
6ccc2156
authored
Jul 11, 2026
by
Paulo Avila
Browse files
Criação de tarefas com tipo de meta
parent
7a97aeb5
Changes
9
Hide whitespace changes
Inline
Side-by-side
src/components/tasks/meta-badge.tsx
0 → 100644
View file @
6ccc2156
import
{
Target
}
from
"
lucide-react
"
;
import
{
getMetaInfo
}
from
"
@/lib/meta
"
;
import
{
Badge
}
from
"
@/components/ui/badge
"
;
import
{
cn
}
from
"
@/lib/utils
"
;
/**
* Indicador da meta de subtarefas em aberto na tarefa-mãe.
* Verde = dentro da meta; vermelho = acima do teto; âmbar = abaixo do piso.
*/
export
function
MetaBadge
({
metaType
,
metaValue
,
subtaskCount
,
subtaskDone
,
className
,
}:
{
metaType
:
string
|
null
;
metaValue
:
number
|
null
;
subtaskCount
:
number
;
subtaskDone
:
number
;
className
?:
string
;
})
{
const
info
=
getMetaInfo
(
metaType
,
metaValue
,
subtaskCount
,
subtaskDone
);
if
(
!
info
)
return
null
;
const
styles
=
{
within
:
"
bg-emerald-500 text-white hover:bg-emerald-500
"
,
exceeded
:
"
bg-destructive text-destructive-foreground hover:bg-destructive
"
,
below
:
"
bg-amber-500 text-white hover:bg-amber-500
"
,
}
as
const
;
return
(
<
Badge
title
=
{
info
.
label
}
className
=
{
cn
(
"
gap-0.5 px-1.5 py-0 text-[10px] font-medium leading-4
"
,
styles
[
info
.
state
],
className
)
}
>
<
Target
className
=
"h-2.5 w-2.5 shrink-0"
/>
{
info
.
shortLabel
}
</
Badge
>
);
}
src/components/tasks/task-board.tsx
View file @
6ccc2156
...
...
@@ -7,6 +7,7 @@ import { Button } from "@/components/ui/button";
import
{
Card
,
CardContent
}
from
"
@/components/ui/card
"
;
import
{
Progress
}
from
"
@/components/ui/progress
"
;
import
{
DueBadge
}
from
"
./due-badge
"
;
import
{
MetaBadge
}
from
"
./meta-badge
"
;
import
{
PriorityBadge
}
from
"
./priority-badge
"
;
import
{
SubtaskOverdueBadge
}
from
"
./subtask-overdue-badge
"
;
import
{
StatusBadge
}
from
"
./status-badge
"
;
...
...
@@ -89,6 +90,12 @@ export function TaskBoard({
<
div
className
=
"flex flex-wrap items-center gap-1.5"
>
<
PriorityBadge
priority
=
{
task
.
Priority
}
/>
<
DueBadge
dueDate
=
{
task
.
DueDate
}
status
=
{
task
.
Status
}
/>
<
MetaBadge
metaType
=
{
task
.
MetaType
}
metaValue
=
{
task
.
MetaValue
}
subtaskCount
=
{
task
.
SubtaskCount
}
subtaskDone
=
{
task
.
SubtaskDone
}
/>
<
SubtaskOverdueBadge
subtasks
=
{
subtasksByTask
[
task
.
Id
]
??
[]
}
compact
...
...
src/components/tasks/task-dialog.tsx
View file @
6ccc2156
...
...
@@ -42,6 +42,9 @@ export interface TaskFormValues {
dueDate
:
string
|
null
;
// yyyy-MM-dd
completedDate
:
string
|
null
;
// yyyy-MM-dd
progress
:
number
;
entry
:
number
|
null
;
metaType
:
string
|
null
;
// 'teto' | 'piso' | null
metaValue
:
number
|
null
;
}
function
SubmitButton
({
...
...
@@ -91,6 +94,8 @@ export function TaskDialog({
trigger
:
ReactNode
;
})
{
const
isSubtask
=
entryId
!==
undefined
&&
!
task
;
// Meta só existe em tarefa-mãe: some ao criar/editar subtarefa.
const
showMeta
=
!
isSubtask
&&
!
(
task
&&
task
.
entry
!=
null
);
// Membros comuns só podem atribuir tarefas a si mesmos; mantém o
// responsável atual visível em modo edição.
const
selectableMembers
=
isAdmin
...
...
@@ -99,6 +104,7 @@ export function TaskDialog({
(
m
)
=>
m
.
UserId
===
currentUserId
||
m
.
UserId
===
task
?.
assigneeId
);
const
[
open
,
setOpen
]
=
useState
(
false
);
const
[
metaType
,
setMetaType
]
=
useState
<
string
>
(
task
?.
metaType
??
"
none
"
);
const
action
=
task
?
updateTaskAction
.
bind
(
null
,
task
.
id
)
:
createTaskAction
;
...
...
@@ -276,6 +282,48 @@ export function TaskDialog({
:
"
Digite quanto da tarefa já foi concluído (0 a 100).
"
}
</
p
>
</
div
>
{
showMeta
&&
(
<
div
className
=
"space-y-2 rounded-lg border bg-muted/30 p-3"
>
<
div
className
=
"grid grid-cols-2 gap-4"
>
<
div
className
=
"space-y-2"
>
<
Label
>
Meta de subtarefas em aberto
</
Label
>
<
Select
name
=
"metaType"
value
=
{
metaType
}
onValueChange
=
{
setMetaType
}
>
<
SelectTrigger
>
<
SelectValue
/>
</
SelectTrigger
>
<
SelectContent
>
<
SelectItem
value
=
"none"
>
Sem meta
</
SelectItem
>
<
SelectItem
value
=
"teto"
>
Teto (máximo)
</
SelectItem
>
<
SelectItem
value
=
"piso"
>
Piso (mínimo)
</
SelectItem
>
</
SelectContent
>
</
Select
>
</
div
>
<
div
className
=
"space-y-2"
>
<
Label
htmlFor
=
"task-meta-value"
>
Quantidade
</
Label
>
<
Input
id
=
"task-meta-value"
name
=
"metaValue"
type
=
"number"
min
=
{
1
}
defaultValue
=
{
task
?.
metaValue
??
""
}
disabled
=
{
metaType
===
"
none
"
}
placeholder
=
"Ex.: 10"
/>
</
div
>
</
div
>
<
p
className
=
"text-xs text-muted-foreground"
>
{
metaType
===
"
teto
"
?
"
Alerta quando houver mais subtarefas em aberto que a meta.
"
:
metaType
===
"
piso
"
?
"
Alerta quando houver menos subtarefas em aberto que a meta.
"
:
"
Acompanhe quantas subtarefas podem ficar em aberto sem recriar a tarefa a cada ciclo.
"
}
</
p
>
</
div
>
)
}
<
SubmitButton
isEdit
=
{
Boolean
(
task
)
}
isSubtask
=
{
isSubtask
}
/>
</
form
>
</
DialogContent
>
...
...
src/components/tasks/task-table.tsx
View file @
6ccc2156
"
use client
"
;
import
{
Fragment
,
useState
}
from
"
react
"
;
import
{
AlertTriangle
,
ChevronRight
,
Pencil
,
Plus
}
from
"
lucide-react
"
;
import
{
AlertTriangle
,
ChevronRight
,
Pencil
,
Plus
,
Target
}
from
"
lucide-react
"
;
import
type
{
Task
,
WorkspaceMember
}
from
"
@/lib/data
"
;
import
{
TASK_STATUSES
}
from
"
@/lib/constants
"
;
import
{
formatDateShort
,
getDueInfo
}
from
"
@/lib/dates
"
;
import
{
getMetaInfo
}
from
"
@/lib/meta
"
;
import
{
canEditTask
}
from
"
@/lib/permissions
"
;
import
{
initials
,
toFormValues
}
from
"
./task-utils
"
;
import
{
Avatar
,
AvatarFallback
}
from
"
@/components/ui/avatar
"
;
...
...
@@ -19,6 +20,7 @@ import {
}
from
"
@/components/ui/table
"
;
import
{
cn
}
from
"
@/lib/utils
"
;
import
{
DueBadge
}
from
"
./due-badge
"
;
import
{
MetaBadge
}
from
"
./meta-badge
"
;
import
{
PriorityBadge
}
from
"
./priority-badge
"
;
import
{
SubtaskOverdueBadge
}
from
"
./subtask-overdue-badge
"
;
import
{
StatusBadge
}
from
"
./status-badge
"
;
...
...
@@ -67,6 +69,16 @@ export function TaskTable({
const
isOverdue
=
(
t
:
Task
)
=>
getDueInfo
(
t
.
DueDate
,
t
.
Status
)?.
state
===
"
overdue
"
;
const
isMetaOffTarget
=
(
t
:
Task
)
=>
{
const
info
=
getMetaInfo
(
t
.
MetaType
,
t
.
MetaValue
,
t
.
SubtaskCount
,
t
.
SubtaskDone
);
return
info
!=
null
&&
!
info
.
ok
;
};
// Ordena por criação mais recente primeiro (ID decrescente), em todos os
// estados — a tarefa adicionada por último aparece no topo.
const
sortTasks
=
(
list
:
Task
[])
=>
[...
list
].
sort
((
a
,
b
)
=>
b
.
Id
-
a
.
Id
);
...
...
@@ -76,19 +88,33 @@ export function TaskTable({
?
tasks
:
statusFilter
===
"
overdue
"
?
tasks
.
filter
(
isOverdue
)
:
tasks
.
filter
((
t
)
=>
t
.
Status
===
statusFilter
)
:
statusFilter
===
"
off_target
"
?
tasks
.
filter
(
isMetaOffTarget
)
:
tasks
.
filter
((
t
)
=>
t
.
Status
===
statusFilter
)
);
const
overdueCount
=
tasks
.
filter
(
isOverdue
).
length
;
const
offTargetCount
=
tasks
.
filter
(
isMetaOffTarget
).
length
;
const
filterOptions
=
[
const
filterOptions
:
{
value
:
string
;
label
:
string
;
count
:
number
;
tone
?:
"
danger
"
|
"
warning
"
;
}[]
=
[
{
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
,
})),
{
value
:
"
overdue
"
,
label
:
"
Vencidas
"
,
count
:
overdueCount
},
{
value
:
"
overdue
"
,
label
:
"
Vencidas
"
,
count
:
overdueCount
,
tone
:
"
danger
"
},
{
value
:
"
off_target
"
,
label
:
"
Fora da meta
"
,
count
:
offTargetCount
,
tone
:
"
warning
"
,
},
];
return
(
...
...
@@ -96,7 +122,24 @@ export function TaskTable({
<
div
className
=
"flex flex-wrap items-center gap-2"
>
{
filterOptions
.
map
((
option
)
=>
{
const
isActive
=
statusFilter
===
option
.
value
;
const
isOverdueFilter
=
option
.
value
===
"
overdue
"
;
const
Icon
=
option
.
tone
===
"
danger
"
?
AlertTriangle
:
option
.
tone
===
"
warning
"
?
Target
:
null
;
const
activeClass
=
option
.
tone
===
"
danger
"
?
"
border-destructive bg-destructive text-destructive-foreground
"
:
option
.
tone
===
"
warning
"
?
"
border-amber-500 bg-amber-500 text-white
"
:
"
border-primary bg-primary text-primary-foreground
"
;
const
idleClass
=
option
.
tone
===
"
danger
"
&&
option
.
count
>
0
?
"
border-destructive/40 bg-destructive/10 text-destructive hover:bg-destructive/20
"
:
option
.
tone
===
"
warning
"
&&
option
.
count
>
0
?
"
border-amber-500/40 bg-amber-500/10 text-amber-600 hover:bg-amber-500/20 dark:text-amber-400
"
:
"
border-border bg-card text-muted-foreground hover:bg-muted
"
;
return
(
<
button
key
=
{
option
.
value
}
...
...
@@ -104,16 +147,10 @@ export function TaskTable({
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
"
,
isActive
?
isOverdueFilter
?
"
border-destructive bg-destructive text-destructive-foreground
"
:
"
border-primary bg-primary text-primary-foreground
"
:
isOverdueFilter
&&
option
.
count
>
0
?
"
border-destructive/40 bg-destructive/10 text-destructive hover:bg-destructive/20
"
:
"
border-border bg-card text-muted-foreground hover:bg-muted
"
isActive
?
activeClass
:
idleClass
)
}
>
{
isOverdueFilter
&&
<
AlertTriangle
className
=
"h-3 w-3"
/>
}
{
Icon
&&
<
Icon
className
=
"h-3 w-3"
/>
}
{
option
.
label
}
<
span
className
=
{
cn
(
...
...
@@ -205,6 +242,12 @@ export function TaskTable({
{
task
.
Title
}
</
span
>
<
PriorityBadge
priority
=
{
task
.
Priority
}
/>
<
MetaBadge
metaType
=
{
task
.
MetaType
}
metaValue
=
{
task
.
MetaValue
}
subtaskCount
=
{
task
.
SubtaskCount
}
subtaskDone
=
{
task
.
SubtaskDone
}
/>
<
SubtaskOverdueBadge
subtasks
=
{
subtasks
}
/>
</
div
>
{
task
.
Description
&&
(
...
...
src/components/tasks/task-utils.ts
View file @
6ccc2156
...
...
@@ -24,6 +24,9 @@ export function toFormValues(task: Task): TaskFormValues {
dueDate
:
toISODate
(
task
.
DueDate
),
completedDate
:
toISODate
(
task
.
CompletedDate
),
progress
:
task
.
Progress
,
entry
:
task
.
Entry
??
null
,
metaType
:
task
.
MetaType
,
metaValue
:
task
.
MetaValue
,
};
}
...
...
src/lib/actions.ts
View file @
6ccc2156
...
...
@@ -308,11 +308,28 @@ const taskSchema = z
.
optional
(),
// Id da tarefa-mãe quando se cria uma subtarefa.
entry
:
z
.
coerce
.
number
().
int
().
positive
().
optional
(),
// Meta de subtarefas em aberto (só na tarefa-mãe).
metaType
:
z
.
enum
([
"
teto
"
,
"
piso
"
]).
optional
(),
metaValue
:
z
.
coerce
.
number
()
.
int
()
.
positive
(
"
A meta deve ser um número maior que zero.
"
)
.
optional
(),
})
.
refine
(
(
t
)
=>
!
t
.
startDate
||
!
t
.
dueDate
||
t
.
startDate
<=
t
.
dueDate
,
{
message
:
"
A data de início não pode ser depois do vencimento.
"
}
);
)
.
refine
((
t
)
=>
!
t
.
metaType
||
t
.
metaValue
!==
undefined
,
{
message
:
"
Informe a quantidade da meta.
"
,
path
:
[
"
metaValue
"
],
});
function
parseMetaType
(
value
:
FormDataEntryValue
|
null
):
"
teto
"
|
"
piso
"
|
undefined
{
return
value
===
"
teto
"
||
value
===
"
piso
"
?
value
:
undefined
;
}
function
parseTaskForm
(
formData
:
FormData
)
{
return
taskSchema
.
safeParse
({
...
...
@@ -326,6 +343,8 @@ function parseTaskForm(formData: FormData) {
dueDate
:
formData
.
get
(
"
dueDate
"
)
||
undefined
,
progress
:
formData
.
get
(
"
progress
"
)
||
undefined
,
entry
:
formData
.
get
(
"
entry
"
)
||
undefined
,
metaType
:
parseMetaType
(
formData
.
get
(
"
metaType
"
)),
metaValue
:
formData
.
get
(
"
metaValue
"
)
||
undefined
,
});
}
...
...
@@ -364,6 +383,9 @@ export async function createTaskAction(
dueDate
:
parsed
.
data
.
dueDate
??
null
,
progress
:
parsed
.
data
.
progress
??
0
,
entry
:
parsed
.
data
.
entry
??
null
,
// Meta só vale para tarefa-mãe (subtarefa não carrega meta).
metaType
:
parsed
.
data
.
entry
?
null
:
(
parsed
.
data
.
metaType
??
null
),
metaValue
:
parsed
.
data
.
entry
?
null
:
(
parsed
.
data
.
metaValue
??
null
),
},
user
.
id
);
...
...
@@ -412,6 +434,9 @@ export async function updateTaskAction(
startDate
:
parsed
.
data
.
startDate
??
null
,
dueDate
:
parsed
.
data
.
dueDate
??
null
,
progress
:
parsed
.
data
.
progress
??
task
.
Progress
,
// Meta só vale para tarefa-mãe (subtarefa não carrega meta).
metaType
:
task
.
Entry
!=
null
?
null
:
(
parsed
.
data
.
metaType
??
null
),
metaValue
:
task
.
Entry
!=
null
?
null
:
(
parsed
.
data
.
metaValue
??
null
),
});
revalidatePath
(
`/workspaces/
${
task
.
WorkspaceId
}
`
);
}
catch
(
err
)
{
...
...
src/lib/data.ts
View file @
6ccc2156
...
...
@@ -43,6 +43,8 @@ export interface Task {
DueDate
:
Date
|
null
;
CompletedDate
:
Date
|
null
;
Progress
:
number
;
MetaType
:
string
|
null
;
MetaValue
:
number
|
null
;
CreatedAt
:
Date
;
SubtaskCount
:
number
;
SubtaskDone
:
number
;
...
...
@@ -258,7 +260,7 @@ const TASK_SELECT = `
SELECT t.Id, t.WorkspaceId, t.Title, t.Description, t.Status, t.Priority,
t.Entry,
t.AssigneeId, a.Name AS AssigneeName, t.CreatedById,
t.StartDate, t.DueDate, t.CompletedDate, t.Progress, t.CreatedAt, w.Name AS WorkspaceName,
t.StartDate, t.DueDate, t.CompletedDate, t.Progress,
t.MetaType, t.MetaValue,
t.CreatedAt, w.Name AS WorkspaceName,
(SELECT COUNT(*) FROM dbo.Tasks s WHERE s.Entry = t.Id) AS SubtaskCount,
(SELECT COUNT(*) FROM dbo.Tasks s WHERE s.Entry = t.Id AND s.Status = 'done') AS SubtaskDone
FROM dbo.Tasks t
...
...
@@ -300,6 +302,8 @@ export interface TaskInput {
dueDate
:
string
|
null
;
progress
:
number
;
entry
?:
number
|
null
;
metaType
?:
string
|
null
;
metaValue
?:
number
|
null
;
}
export
async
function
createTask
(
input
:
TaskInput
,
...
...
@@ -318,12 +322,14 @@ export async function createTask(
.
input
(
"
startDate
"
,
sql
.
Date
,
input
.
startDate
)
.
input
(
"
dueDate
"
,
sql
.
Date
,
input
.
dueDate
)
.
input
(
"
progress
"
,
sql
.
Int
,
input
.
progress
)
.
input
(
"
metaType
"
,
sql
.
NVarChar
(
10
),
input
.
metaType
??
null
)
.
input
(
"
metaValue
"
,
sql
.
Int
,
input
.
metaValue
??
null
)
.
input
(
"
createdById
"
,
sql
.
Int
,
createdById
)
.
query
(
`INSERT INTO dbo.Tasks (WorkspaceId, Title, Description, Status, Priority, Entry, AssigneeId, StartDate, DueDate, CompletedDate, Progress, CreatedById)
`INSERT INTO dbo.Tasks (WorkspaceId, Title, Description, Status, Priority, Entry, AssigneeId, StartDate, DueDate, CompletedDate, Progress,
MetaType, MetaValue,
CreatedById)
VALUES (@workspaceId, @title, @description, @status, @priority, @entry, @assigneeId, @startDate, @dueDate,
CASE WHEN @status = 'done' THEN CAST(GETDATE() AS DATE) ELSE NULL END,
@progress, @createdById)`
@progress,
@metaType, @metaValue,
@createdById)`
);
}
...
...
@@ -340,10 +346,13 @@ export async function updateTask(id: number, input: TaskInput): Promise<void> {
.
input
(
"
priority
"
,
sql
.
NVarChar
(
10
),
input
.
priority
)
.
input
(
"
assigneeId
"
,
sql
.
Int
,
input
.
assigneeId
)
.
input
(
"
progress
"
,
sql
.
Int
,
input
.
progress
)
.
input
(
"
metaType
"
,
sql
.
NVarChar
(
10
),
input
.
metaType
??
null
)
.
input
(
"
metaValue
"
,
sql
.
Int
,
input
.
metaValue
??
null
)
.
query
(
`UPDATE dbo.Tasks
SET Title = @title, Description = @description, Status = @status,
Priority = @priority, AssigneeId = @assigneeId, Progress = @progress,
MetaType = @metaType, MetaValue = @metaValue,
CompletedDate = CASE WHEN @status = 'done'
THEN COALESCE(CompletedDate, CAST(GETDATE() AS DATE))
ELSE NULL END,
...
...
src/lib/db-init.ts
View file @
6ccc2156
...
...
@@ -49,6 +49,8 @@ const DDL_STATEMENTS: string[] = [
DueDate DATE NULL,
CompletedDate DATE NULL,
Progress INT NOT NULL DEFAULT 0,
MetaType NVARCHAR(10) NULL,
MetaValue INT NULL,
CreatedAt DATETIME2 NOT NULL DEFAULT SYSUTCDATETIME(),
UpdatedAt DATETIME2 NOT NULL DEFAULT SYSUTCDATETIME()
)`
,
...
...
@@ -66,6 +68,13 @@ const DDL_STATEMENTS: string[] = [
SET CompletedDate = CAST(UpdatedAt AS DATE)
WHERE Status = 'done' AND CompletedDate IS NULL`
,
// Meta de subtarefas em aberto para tarefas recorrentes (teto/piso)
`IF COL_LENGTH('dbo.Tasks', 'MetaType') IS NULL
ALTER TABLE dbo.Tasks ADD MetaType NVARCHAR(10) NULL`
,
`IF COL_LENGTH('dbo.Tasks', 'MetaValue') IS NULL
ALTER TABLE dbo.Tasks ADD MetaValue INT NULL`
,
`IF OBJECT_ID('dbo.Subtasks', 'U') IS NULL
CREATE TABLE dbo.Subtasks (
Id INT IDENTITY(1,1) PRIMARY KEY,
...
...
src/lib/meta.ts
0 → 100644
View file @
6ccc2156
export
type
MetaKind
=
"
teto
"
|
"
piso
"
;
export
type
MetaState
=
"
within
"
|
"
exceeded
"
|
"
below
"
;
export
interface
MetaInfo
{
kind
:
MetaKind
;
state
:
MetaState
;
/** true quando está dentro da meta. */
ok
:
boolean
;
/** Subtarefas em aberto (não concluídas). */
open
:
number
;
/** Valor da meta. */
target
:
number
;
/** Texto completo (tooltip). */
label
:
string
;
/** Texto curto para o badge ("8/10"). */
shortLabel
:
string
;
}
/**
* Avalia a meta de subtarefas em aberto de uma tarefa-mãe recorrente.
* "Em aberto" = subtarefas não concluídas (total − concluídas).
* - teto: dentro quando em aberto ≤ meta; estoura acima.
* - piso: dentro quando em aberto ≥ meta; fica abaixo.
* Retorna null quando a tarefa não usa meta.
*/
export
function
getMetaInfo
(
metaType
:
string
|
null
,
metaValue
:
number
|
null
,
subtaskCount
:
number
,
subtaskDone
:
number
):
MetaInfo
|
null
{
if
((
metaType
!==
"
teto
"
&&
metaType
!==
"
piso
"
)
||
metaValue
==
null
)
{
return
null
;
}
const
open
=
Math
.
max
(
0
,
subtaskCount
-
subtaskDone
);
const
target
=
metaValue
;
if
(
metaType
===
"
teto
"
)
{
const
ok
=
open
<=
target
;
return
{
kind
:
"
teto
"
,
state
:
ok
?
"
within
"
:
"
exceeded
"
,
ok
,
open
,
target
,
label
:
ok
?
`Dentro da meta:
${
open
}
em aberto (teto
${
target
}
)`
:
`Acima da meta:
${
open
}
em aberto (teto
${
target
}
)`
,
shortLabel
:
`
${
open
}
/
${
target
}
`
,
};
}
const
ok
=
open
>=
target
;
return
{
kind
:
"
piso
"
,
state
:
ok
?
"
within
"
:
"
below
"
,
ok
,
open
,
target
,
label
:
ok
?
`Dentro da meta:
${
open
}
em aberto (piso
${
target
}
)`
:
`Abaixo da meta:
${
open
}
em aberto (piso
${
target
}
)`
,
shortLabel
:
`
${
open
}
/
${
target
}
`
,
};
}
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