Commit 7c57e406 authored by Paulo Avila's avatar Paulo Avila
Browse files

criação de nova data de conclusao e formatação de data

parent 987e8471
......@@ -14,7 +14,7 @@ import { Progress } from "@/components/ui/progress";
import { Button } from "@/components/ui/button";
import { DueBadge } from "@/components/tasks/due-badge";
import { STATUS_CHIPS } from "@/components/tasks/task-utils";
import { formatDate } from "@/lib/dates";
import { formatDateShort } from "@/lib/dates";
import { cn } from "@/lib/utils";
export const metadata = { title: "Painel — Petruz Tasks" };
......@@ -147,7 +147,7 @@ export default async function DashboardPage() {
</p>
<p className="text-xs text-muted-foreground">
{task.AssigneeName ?? "Sem responsável"} ·{" "}
{formatDate(task.DueDate)}
{formatDateShort(task.DueDate)}
</p>
</div>
<DueBadge
......
import { CalendarClock, CalendarPlus, Pencil } from "lucide-react";
import { CalendarCheck, CalendarClock, CalendarPlus, Pencil } from "lucide-react";
import { TASK_STATUSES } from "@/lib/constants";
import type { Task, WorkspaceMember } from "@/lib/data";
import { formatDate } from "@/lib/dates";
import { formatDateShort } from "@/lib/dates";
import { canEditTask } from "@/lib/permissions";
import { Button } from "@/components/ui/button";
import { Card, CardContent } from "@/components/ui/card";
......@@ -106,16 +106,25 @@ export function TaskBoard({
title="Início"
>
<CalendarPlus className="h-3 w-3" />
{formatDate(task.StartDate)}
{formatDateShort(task.StartDate)}
</span>
)}
{task.DueDate && (
<span
className="flex items-center gap-1"
title="Vencimento"
title="Previsão de término"
>
<CalendarClock className="h-3 w-3" />
{formatDate(task.DueDate)}
{formatDateShort(task.DueDate)}
</span>
)}
{task.CompletedDate && (
<span
className="flex items-center gap-1 text-green-600 dark:text-green-400"
title="Concluída em"
>
<CalendarCheck className="h-3 w-3" />
{formatDateShort(task.CompletedDate)}
</span>
)}
</div>
......
......@@ -40,6 +40,7 @@ export interface TaskFormValues {
assigneeId: number | null;
startDate: string | null; // yyyy-MM-dd
dueDate: string | null; // yyyy-MM-dd
completedDate: string | null; // yyyy-MM-dd
progress: number;
}
......@@ -224,18 +225,40 @@ export function TaskDialog({
name="startDate"
type="date"
defaultValue={task?.startDate ?? ""}
disabled={Boolean(task)}
/>
</div>
<div className="space-y-2">
<Label htmlFor="task-due">Vencimento</Label>
<Label htmlFor="task-due">Previsão de término</Label>
<Input
id="task-due"
name="dueDate"
type="date"
defaultValue={task?.dueDate ?? ""}
disabled={Boolean(task)}
/>
</div>
</div>
{task && (
<p className="-mt-2 text-xs text-muted-foreground">
Início e previsão de término não podem ser alterados após a
criação.
</p>
)}
{task?.completedDate && (
<div className="space-y-2">
<Label htmlFor="task-completed">Concluída em</Label>
<Input
id="task-completed"
type="date"
defaultValue={task.completedDate}
disabled
/>
<p className="text-xs text-muted-foreground">
Preenchida automaticamente ao concluir a tarefa.
</p>
</div>
)}
<div className="space-y-2">
<Label htmlFor="task-progress">Progresso (%)</Label>
<Input
......
......@@ -4,7 +4,7 @@ import { Fragment, useState } from "react";
import { AlertTriangle, ChevronRight, Pencil, Plus } from "lucide-react";
import type { Task, WorkspaceMember } from "@/lib/data";
import { TASK_STATUSES } from "@/lib/constants";
import { formatDate, getDueInfo } from "@/lib/dates";
import { formatDateShort, getDueInfo } from "@/lib/dates";
import { canEditTask } from "@/lib/permissions";
import { initials, toFormValues } from "./task-utils";
import { Avatar, AvatarFallback } from "@/components/ui/avatar";
......@@ -27,7 +27,7 @@ import { TaskProgress } from "./task-progress";
import { TaskProgressEditor } from "./task-progress-editor";
import { TaskStatusSelect } from "./task-status-select";
const COLUMN_COUNT = 7;
const COLUMN_COUNT = 8;
export function TaskTable({
tasks,
......@@ -147,7 +147,10 @@ export function TaskTable({
Início
</TableHead>
<TableHead className="whitespace-nowrap text-[11px] font-semibold uppercase tracking-wider text-muted-foreground">
Vencimento
Previsão
</TableHead>
<TableHead className="whitespace-nowrap text-[11px] font-semibold uppercase tracking-wider text-muted-foreground">
Conclusão
</TableHead>
<TableHead className="w-[80px]" />
</TableRow>
......@@ -252,18 +255,29 @@ export function TaskTable({
)}
</TableCell>
<TableCell className="py-3">
<span className="text-sm text-muted-foreground">
{formatDate(task.StartDate)}
<span className="whitespace-nowrap text-sm text-muted-foreground">
{formatDateShort(task.StartDate)}
</span>
</TableCell>
<TableCell className="py-3">
<div className="flex items-center gap-2">
<span className="text-sm text-muted-foreground">
{formatDate(task.DueDate)}
<span className="whitespace-nowrap text-sm text-muted-foreground">
{formatDateShort(task.DueDate)}
</span>
<DueBadge dueDate={task.DueDate} status={task.Status} />
</div>
</TableCell>
<TableCell className="py-3">
<span className="whitespace-nowrap text-sm text-muted-foreground">
{task.CompletedDate ? (
<span className="text-green-600 dark:text-green-400">
{formatDateShort(task.CompletedDate)}
</span>
) : (
""
)}
</span>
</TableCell>
<TableCell className="py-3">
{editable && (
<div className="flex items-center justify-end transition-opacity md:opacity-0 md:group-hover:opacity-100">
......@@ -370,8 +384,10 @@ export function TaskTable({
</span>
)}
<div className="flex items-center gap-1.5">
<span className="text-xs text-muted-foreground">
{formatDate(sub.DueDate)}
<span className="whitespace-nowrap text-xs text-muted-foreground">
{sub.CompletedDate
? `✓ ${formatDateShort(sub.CompletedDate)}`
: formatDateShort(sub.DueDate)}
</span>
<DueBadge
dueDate={sub.DueDate}
......
......@@ -22,6 +22,7 @@ export function toFormValues(task: Task): TaskFormValues {
assigneeId: task.AssigneeId,
startDate: toISODate(task.StartDate),
dueDate: toISODate(task.DueDate),
completedDate: toISODate(task.CompletedDate),
progress: task.Progress,
};
}
......
......@@ -41,6 +41,7 @@ export interface Task {
CreatedById: number;
StartDate: Date | null;
DueDate: Date | null;
CompletedDate: Date | null;
Progress: number;
CreatedAt: Date;
SubtaskCount: number;
......@@ -257,7 +258,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.Progress, 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
FROM dbo.Tasks t
......@@ -319,13 +320,17 @@ export async function createTask(
.input("progress", sql.Int, input.progress)
.input("createdById", sql.Int, createdById)
.query(
`INSERT INTO dbo.Tasks (WorkspaceId, Title, Description, Status, Priority, Entry, AssigneeId, StartDate, DueDate, Progress, CreatedById)
VALUES (@workspaceId, @title, @description, @status, @priority, @entry, @assigneeId, @startDate, @dueDate, @progress, @createdById)`
`INSERT INTO dbo.Tasks (WorkspaceId, Title, Description, Status, Priority, Entry, AssigneeId, StartDate, DueDate, CompletedDate, Progress, 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)`
);
}
export async function updateTask(id: number, input: TaskInput): Promise<void> {
const pool = await getPool();
// Início e vencimento são imutáveis após a criação: não entram no UPDATE.
// CompletedDate é gerenciada pelo status (preenche ao concluir, limpa ao reabrir).
await pool
.request()
.input("id", sql.Int, id)
......@@ -334,14 +339,14 @@ export async function updateTask(id: number, input: TaskInput): Promise<void> {
.input("status", sql.NVarChar(20), input.status)
.input("priority", sql.NVarChar(10), input.priority)
.input("assigneeId", sql.Int, input.assigneeId)
.input("startDate", sql.Date, input.startDate)
.input("dueDate", sql.Date, input.dueDate)
.input("progress", sql.Int, input.progress)
.query(
`UPDATE dbo.Tasks
SET Title = @title, Description = @description, Status = @status,
Priority = @priority, AssigneeId = @assigneeId,
StartDate = @startDate, DueDate = @dueDate, Progress = @progress,
Priority = @priority, AssigneeId = @assigneeId, Progress = @progress,
CompletedDate = CASE WHEN @status = 'done'
THEN COALESCE(CompletedDate, CAST(GETDATE() AS DATE))
ELSE NULL END,
UpdatedAt = SYSUTCDATETIME()
WHERE Id = @id`
);
......@@ -357,7 +362,13 @@ export async function updateTaskStatus(
.input("id", sql.Int, id)
.input("status", sql.NVarChar(20), status)
.query(
`UPDATE dbo.Tasks SET Status = @status, UpdatedAt = SYSUTCDATETIME() WHERE Id = @id`
`UPDATE dbo.Tasks
SET Status = @status,
CompletedDate = CASE WHEN @status = 'done'
THEN COALESCE(CompletedDate, CAST(GETDATE() AS DATE))
ELSE NULL END,
UpdatedAt = SYSUTCDATETIME()
WHERE Id = @id`
);
}
......
......@@ -18,6 +18,14 @@ export function formatDate(date: Date | string | null): string {
return `${day}/${month}/${year}`;
}
/** Formato curto dd/MM para exibição resumida das datas das tarefas. */
export function formatDateShort(date: Date | string | null): string {
const iso = toISODate(date);
if (!iso) return "";
const [, month, day] = iso.split("-");
return `${day}/${month}`;
}
function isoToLocalDate(iso: string): Date {
const [year, month, day] = iso.split("-").map(Number);
return new Date(year, month - 1, day);
......
......@@ -47,6 +47,7 @@ const DDL_STATEMENTS: string[] = [
CreatedById INT NOT NULL REFERENCES dbo.Users(Id),
StartDate DATE NULL,
DueDate DATE NULL,
CompletedDate DATE NULL,
Progress INT NOT NULL DEFAULT 0,
CreatedAt DATETIME2 NOT NULL DEFAULT SYSUTCDATETIME(),
UpdatedAt DATETIME2 NOT NULL DEFAULT SYSUTCDATETIME()
......@@ -56,6 +57,15 @@ const DDL_STATEMENTS: string[] = [
`IF COL_LENGTH('dbo.Tasks', 'Progress') IS NULL
ALTER TABLE dbo.Tasks ADD Progress INT NOT NULL DEFAULT 0`,
// Data de conclusão (preenchida ao mudar o status para 'done')
`IF COL_LENGTH('dbo.Tasks', 'CompletedDate') IS NULL
ALTER TABLE dbo.Tasks ADD CompletedDate DATE NULL`,
// Preenche a data de conclusão de tarefas já concluídas sem data
`UPDATE dbo.Tasks
SET CompletedDate = CAST(UpdatedAt AS DATE)
WHERE Status = 'done' AND CompletedDate IS NULL`,
`IF OBJECT_ID('dbo.Subtasks', 'U') IS NULL
CREATE TABLE dbo.Subtasks (
Id INT IDENTITY(1,1) PRIMARY KEY,
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment