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
cfed0f56
Commit
cfed0f56
authored
Aug 03, 2026
by
Paulo Avila
Browse files
Commit e remoção de funcionalidade de metas não aplicaveis
parent
24d624aa
Changes
9
Hide whitespace changes
Inline
Side-by-side
src/components/tasks/meta-badge.tsx
deleted
100644 → 0
View file @
24d624aa
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 @
cfed0f56
...
...
@@ -7,7 +7,6 @@ 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
"
;
...
...
@@ -93,12 +92,6 @@ 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 @
cfed0f56
...
...
@@ -44,8 +44,6 @@ export interface TaskFormValues {
completedDate
:
string
|
null
;
// yyyy-MM-dd
progress
:
number
;
entry
:
number
|
null
;
metaType
:
string
|
null
;
// 'teto' | 'piso' | null
metaValue
:
number
|
null
;
collaboratorIds
:
number
[];
}
...
...
@@ -96,7 +94,7 @@ export function TaskDialog({
trigger
:
ReactNode
;
})
{
const
isSubtask
=
entryId
!==
undefined
&&
!
task
;
//
Meta e c
ompartilhamento só existe
m
em tarefa-mãe.
//
C
ompartilhamento só existe em tarefa-mãe.
const
isMotherTask
=
!
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.
...
...
@@ -106,7 +104,6 @@ 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
[
collabs
,
setCollabs
]
=
useState
<
number
[]
>
(
task
?.
collaboratorIds
??
[]
);
...
...
@@ -330,48 +327,6 @@ export function TaskDialog({
:
"
Digite quanto da tarefa já foi concluído (0 a 100).
"
}
</
p
>
</
div
>
{
isMotherTask
&&
(
<
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 @
cfed0f56
"
use client
"
;
import
{
Fragment
,
useState
}
from
"
react
"
;
import
{
AlertTriangle
,
ChevronRight
,
Pencil
,
Plus
,
Target
}
from
"
lucide-react
"
;
import
{
AlertTriangle
,
ChevronRight
,
Pencil
,
Plus
}
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
{
canDeleteTask
,
canEditTask
}
from
"
@/lib/permissions
"
;
import
{
initials
,
toFormValues
}
from
"
./task-utils
"
;
import
{
Avatar
,
AvatarFallback
}
from
"
@/components/ui/avatar
"
;
...
...
@@ -20,7 +19,6 @@ 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
"
;
...
...
@@ -69,16 +67,6 @@ 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
);
...
...
@@ -88,19 +76,16 @@ export function TaskTable({
?
tasks
:
statusFilter
===
"
overdue
"
?
tasks
.
filter
(
isOverdue
)
:
statusFilter
===
"
off_target
"
?
tasks
.
filter
(
isMetaOffTarget
)
:
tasks
.
filter
((
t
)
=>
t
.
Status
===
statusFilter
)
:
tasks
.
filter
((
t
)
=>
t
.
Status
===
statusFilter
)
);
const
overdueCount
=
tasks
.
filter
(
isOverdue
).
length
;
const
offTargetCount
=
tasks
.
filter
(
isMetaOffTarget
).
length
;
const
filterOptions
:
{
value
:
string
;
label
:
string
;
count
:
number
;
tone
?:
"
danger
"
|
"
warning
"
;
tone
?:
"
danger
"
;
}[]
=
[
{
value
:
"
all
"
,
label
:
"
Todas
"
,
count
:
tasks
.
length
},
...
TASK_STATUSES
.
map
((
s
)
=>
({
...
...
@@ -109,12 +94,6 @@ export function TaskTable({
count
:
tasks
.
filter
((
t
)
=>
t
.
Status
===
s
.
value
).
length
,
})),
{
value
:
"
overdue
"
,
label
:
"
Vencidas
"
,
count
:
overdueCount
,
tone
:
"
danger
"
},
{
value
:
"
off_target
"
,
label
:
"
Fora da meta
"
,
count
:
offTargetCount
,
tone
:
"
warning
"
,
},
];
return
(
...
...
@@ -122,24 +101,15 @@ export function TaskTable({
<
div
className
=
"flex flex-wrap items-center gap-2"
>
{
filterOptions
.
map
((
option
)
=>
{
const
isActive
=
statusFilter
===
option
.
value
;
const
Icon
=
option
.
tone
===
"
danger
"
?
AlertTriangle
:
option
.
tone
===
"
warning
"
?
Target
:
null
;
const
Icon
=
option
.
tone
===
"
danger
"
?
AlertTriangle
:
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
"
;
:
"
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
"
;
:
"
border-border bg-card text-muted-foreground hover:bg-muted
"
;
return
(
<
button
key
=
{
option
.
value
}
...
...
@@ -243,12 +213,6 @@ 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 @
cfed0f56
...
...
@@ -25,8 +25,6 @@ export function toFormValues(task: Task): TaskFormValues {
completedDate
:
toISODate
(
task
.
CompletedDate
),
progress
:
task
.
Progress
,
entry
:
task
.
Entry
??
null
,
metaType
:
task
.
MetaType
,
metaValue
:
task
.
MetaValue
,
collaboratorIds
:
task
.
Collaborators
.
map
((
c
)
=>
c
.
UserId
),
};
}
...
...
src/lib/actions.ts
View file @
cfed0f56
...
...
@@ -309,30 +309,13 @@ 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
(),
// Usuários vinculados (compartilhamento) — só na tarefa-mãe.
collaborators
:
z
.
array
(
z
.
coerce
.
number
().
int
().
positive
()).
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
({
...
...
@@ -346,8 +329,6 @@ 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
,
collaborators
:
formData
.
getAll
(
"
collaborators
"
),
});
}
...
...
@@ -405,9 +386,6 @@ 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
);
...
...
@@ -465,9 +443,6 @@ 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
),
});
// Compartilhamento só em tarefa-mãe.
if
(
task
.
Entry
==
null
)
{
...
...
src/lib/data.ts
View file @
cfed0f56
...
...
@@ -43,8 +43,6 @@ export interface Task {
DueDate
:
Date
|
null
;
CompletedDate
:
Date
|
null
;
Progress
:
number
;
MetaType
:
string
|
null
;
MetaValue
:
number
|
null
;
CreatedAt
:
Date
;
SubtaskCount
:
number
;
SubtaskDone
:
number
;
...
...
@@ -272,7 +270,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.MetaType, t.MetaValue,
t.CreatedAt, w.Name AS WorkspaceName,
t.StartDate, t.DueDate, t.CompletedDate, t.Progress, 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,
(SELECT u.Id AS UserId, u.Name
...
...
@@ -319,8 +317,6 @@ export interface TaskInput {
dueDate
:
string
|
null
;
progress
:
number
;
entry
?:
number
|
null
;
metaType
?:
string
|
null
;
metaValue
?:
number
|
null
;
}
export
async
function
createTask
(
input
:
TaskInput
,
...
...
@@ -339,15 +335,13 @@ 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,
MetaType, MetaValue,
CreatedById)
`INSERT INTO dbo.Tasks (WorkspaceId, Title, Description, Status, Priority, Entry, AssigneeId, StartDate, DueDate, CompletedDate, Progress, CreatedById)
OUTPUT INSERTED.Id
VALUES (@workspaceId, @title, @description, @status, @priority, @entry, @assigneeId, @startDate, @dueDate,
CASE WHEN @status = 'done' THEN CAST(GETDATE() AS DATE) ELSE NULL END,
@progress,
@metaType, @metaValue,
@createdById)`
@progress, @createdById)`
);
return
result
.
recordset
[
0
].
Id
;
}
...
...
@@ -394,13 +388,10 @@ 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 @
cfed0f56
...
...
@@ -49,8 +49,6 @@ 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()
)`
,
...
...
@@ -68,13 +66,6 @@ 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`
,
// Tarefas compartilhadas: usuários vinculados além do responsável
`IF OBJECT_ID('dbo.TaskCollaborators', 'U') IS NULL
CREATE TABLE dbo.TaskCollaborators (
...
...
src/lib/meta.ts
deleted
100644 → 0
View file @
24d624aa
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