Skip to content

Commit 433d6cb

Browse files
authored
Merge pull request #304 from mydevtools-tech/feat/landing-offline-workstation
Feat/landing offline workstation
2 parents 6d0b37a + 5c6c593 commit 433d6cb

8 files changed

Lines changed: 396 additions & 692 deletions

File tree

apps/desktop-ui/src/app/app/to-do/KanbanCard.tsx

Lines changed: 38 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import React, { useState, useEffect, lazy, Suspense } from "react";
44
import { useCopyToClipboard } from "@/hooks/use-copy-to-clipboard";
55
import { useSortable } from "@dnd-kit/sortable";
66
import { CSS } from "@dnd-kit/utilities";
7-
import { GripVertical, Edit, Calendar, Tag, CheckCircle2, MoreHorizontal, Trash2, Copy, Check, Play, Pause, Timer, Archive, ArchiveRestore } from "lucide-react";
7+
import { GripVertical, Edit, Calendar, CheckCircle2, MoreHorizontal, Trash2, Copy, Check, Play, Pause, Timer, Archive, ArchiveRestore } from "lucide-react";
88
import { formatElapsed, getElapsedMinutes } from "@/app/app/to-do/utils/taskTimeUtils";
99
import { Button } from "@/components/ui/button";
1010
import { Badge } from "@/components/ui/badge";
@@ -103,10 +103,9 @@ export default function KanbanCard({ task, onUpdateTask, onDeleteTask }: KanbanC
103103
{...attributes}
104104
{...listeners}
105105
className={cn(
106-
"group relative p-3 md:p-4 rounded-xl border transition-all duration-200",
107-
"hover:shadow-md hover:-translate-y-0.5 bg-card",
108-
"cursor-grab active:cursor-grabbing border-border",
109-
"hover:border-primary/30",
106+
"group relative p-3 rounded-md border border-border/50 bg-card shadow-xs transition-all duration-200",
107+
"hover:shadow-sm hover:border-border",
108+
"cursor-grab active:cursor-grabbing",
110109
isDragging && "shadow-2xl scale-105 z-50 rotate-1 opacity-90",
111110
task.status === "completed" && "opacity-75 bg-muted/30"
112111
)}
@@ -121,52 +120,46 @@ export default function KanbanCard({ task, onUpdateTask, onDeleteTask }: KanbanC
121120
<GripVertical className="h-4 w-4 text-muted-foreground" />
122121
</div>
123122

124-
{/* Status Icon and Task Text - Enhanced */}
125-
<div className="flex items-start gap-2.5 mb-3 pr-6">
126-
<div
127-
className={cn(
128-
"flex items-center justify-center p-2 rounded-lg transition-all flex-shrink-0",
129-
statusConfig.bgColor,
130-
"group-hover:scale-110"
123+
{/* Header (priority left, status right) + content */}
124+
<div className="mb-2">
125+
<div className="flex items-center justify-between gap-1.5 mb-1.5 pr-6">
126+
{task.priority && task.priority !== "medium" ? (
127+
<TooltipProvider>
128+
<Tooltip>
129+
<TooltipTrigger asChild>
130+
<span className={cn(PRIORITY_CONFIG[task.priority].color, "flex-shrink-0")}>
131+
{React.createElement(PRIORITY_CONFIG[task.priority].icon, { className: "h-3.5 w-3.5" })}
132+
</span>
133+
</TooltipTrigger>
134+
<TooltipContent>
135+
<p>{PRIORITY_CONFIG[task.priority].label} Priority</p>
136+
</TooltipContent>
137+
</Tooltip>
138+
</TooltipProvider>
139+
) : (
140+
<span />
131141
)}
132-
>
133-
<StatusIcon className={cn("h-4 w-4", statusConfig.color)} />
142+
<StatusIcon className={cn("h-4 w-4 flex-shrink-0", statusConfig.color)} />
134143
</div>
135-
<div className="flex-1 min-w-0 space-y-1.5">
136-
<div className="flex items-start gap-1.5">
137-
<h3
138-
className={cn(
139-
"text-sm font-semibold flex-1 leading-snug",
140-
task.status === "completed"
141-
? "text-muted-foreground line-through decoration-muted-foreground/50"
142-
: "text-foreground"
143-
)}
144-
title={task.text}
145-
>
146-
{task.text}
147-
</h3>
148-
{task.priority && task.priority !== "medium" && (
149-
<TooltipProvider>
150-
<Tooltip>
151-
<TooltipTrigger asChild>
152-
<span className={cn(PRIORITY_CONFIG[task.priority].color, "flex-shrink-0")}>
153-
{React.createElement(PRIORITY_CONFIG[task.priority].icon, { className: "h-3.5 w-3.5" })}
154-
</span>
155-
</TooltipTrigger>
156-
<TooltipContent>
157-
<p>{PRIORITY_CONFIG[task.priority].label} Priority</p>
158-
</TooltipContent>
159-
</Tooltip>
160-
</TooltipProvider>
144+
<div className="min-w-0 space-y-1.5">
145+
<h3
146+
className={cn(
147+
"text-sm font-semibold leading-snug line-clamp-2",
148+
task.status === "completed"
149+
? "text-muted-foreground line-through decoration-muted-foreground/50"
150+
: "text-foreground"
161151
)}
162-
</div>
152+
title={task.text}
153+
>
154+
{task.text}
155+
</h3>
163156

164157
{/* Project Badge */}
165158
{project && (
166-
<div className="mb-1.5">
159+
<div>
167160
<Badge
168161
variant="outline"
169-
className="gap-1.5 px-2 py-0.5 border text-[10px] font-normal inline-flex"
162+
className="gap-1.5 px-2 py-0 h-5 rounded-full border text-[10px] font-normal text-muted-foreground bg-background inline-flex"
170163
>
171164
<div className={cn("w-1.5 h-1.5 rounded-full", project.color)} />
172165
{project.name}
@@ -187,10 +180,9 @@ export default function KanbanCard({ task, onUpdateTask, onDeleteTask }: KanbanC
187180
<Badge
188181
key={tag.id}
189182
variant="outline"
190-
style={{ borderColor: tag.color, color: tag.color }}
191-
className="text-[10px] px-1.5 py-0.5 h-5 gap-1 hover:bg-muted/50 transition-colors"
183+
className="text-[10px] px-2 py-0 h-5 gap-1.5 rounded-full text-muted-foreground bg-background hover:bg-muted/50 transition-colors"
192184
>
193-
<Tag className="h-2.5 w-2.5" />
185+
<span className="w-1.5 h-1.5 rounded-full" style={{ backgroundColor: tag.color }} aria-hidden />
194186
{tag.name}
195187
</Badge>
196188
))}

apps/desktop-ui/src/app/app/to-do/TaskContainer.tsx

Lines changed: 67 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
import { useState, useMemo, useRef, useEffect, useCallback, lazy, Suspense } from "react";
44
import { Card, CardContent, CardHeader } from "@/components/ui/card";
55
import { Input } from "@/components/ui/input";
6-
import TaskForm from "@/app/app/to-do/TaskForm";
76
import TaskList from "@/app/app/to-do/TaskList";
7+
import type { Task } from "@/app/app/to-do/types/Task";
88
import PaginationDemo from "@/app/app/to-do/PaginationS";
99
import { LazyBoundary } from "@/app/app/to-do/components/LazyBoundary";
1010

@@ -16,6 +16,7 @@ const TaskCommandPalette = lazy(() =>
1616
}))
1717
);
1818
const ManageStatusesDialog = lazy(() => import("@/app/app/to-do/ManageStatusesDialog"));
19+
const TaskEditDialog = lazy(() => import("@/app/app/to-do/TaskEditDialog"));
1920
const ProjectManagerDialog = lazy(() =>
2021
import("@/app/app/to-do/components/ProjectManagerDialog").then((m) => ({
2122
default: m.ProjectManagerDialog,
@@ -36,16 +37,6 @@ import { useStatuses } from "./hooks/useStatuses";
3637
import { cn } from "@/lib/utils";
3738
import { useIsMobile } from "@/components/hooks/use-mobile";
3839
import { useTranslations } from "next-intl";
39-
import {
40-
Drawer,
41-
DrawerContent,
42-
DrawerHeader,
43-
DrawerTitle,
44-
DrawerDescription,
45-
DrawerFooter,
46-
DrawerClose,
47-
DrawerTrigger,
48-
} from "@/components/ui/drawer";
4940
import { Badge } from "@/components/ui/badge";
5041
import {
5142
Select,
@@ -74,10 +65,10 @@ export const TaskContainer = () => {
7465
}, [isMobile]);
7566

7667
const [searchQuery, setSearchQuery] = useState("");
77-
const [isDrawerOpen, setIsDrawerOpen] = useState(false);
68+
const [isAddTaskOpen, setIsAddTaskOpen] = useState(false);
69+
const [addTaskStatus, setAddTaskStatus] = useState<string | null>(null);
7870
const [isPaletteOpen, setIsPaletteOpen] = useState(false);
7971
const searchInputRef = useRef<HTMLInputElement>(null);
80-
const taskFormInputRef = useRef<HTMLInputElement>(null);
8172
const {
8273
tasks,
8374
isLoading,
@@ -150,13 +141,34 @@ export const TaskContainer = () => {
150141
// Calculate statistics using all tasks stats
151142
const completionRate = allTaskStats.total > 0 ? Math.round((allTaskStats.completed / allTaskStats.total) * 100) : 0;
152143

153-
// New tasks land in the centrally selected project; "all" creates them unassigned.
154-
const handleAddTask = useCallback(
155-
(taskText: string) => {
156-
addTask(taskText, filterProject !== "all" ? filterProject : undefined);
157-
setIsDrawerOpen(false);
144+
// Template for the create dialog — preselects the centrally selected project
145+
// and, when opened from a status group's "+", that status.
146+
const blankTask = useMemo<Task>(
147+
() => ({
148+
id: "",
149+
text: "",
150+
status: addTaskStatus ?? "not-started",
151+
statusOrder: 0,
152+
createdAt: "",
153+
created_by: "",
154+
tags: [],
155+
subTasks: [],
156+
projectId: filterProject !== "all" ? filterProject : undefined,
157+
}),
158+
[filterProject, addTaskStatus]
159+
);
160+
161+
const openAddTask = useCallback((statusId?: string) => {
162+
setAddTaskStatus(statusId ?? null);
163+
setIsAddTaskOpen(true);
164+
}, []);
165+
166+
const handleCreateTask = useCallback(
167+
async (updates: Partial<Task>) => {
168+
const { text = "", projectId, ...rest } = updates;
169+
await addTask(text, projectId, rest);
158170
},
159-
[addTask, filterProject]
171+
[addTask]
160172
);
161173

162174
const [isExportDialogOpen, setIsExportDialogOpen] = useState(false);
@@ -213,7 +225,7 @@ export const TaskContainer = () => {
213225

214226
if (!isMobile && event.key?.toLowerCase() === "n" && !isTypingInField) {
215227
event.preventDefault();
216-
taskFormInputRef.current?.focus();
228+
openAddTask();
217229
}
218230
};
219231

@@ -426,6 +438,16 @@ export const TaskContainer = () => {
426438
)}
427439
</div>
428440

441+
{/* Add Task */}
442+
<Button
443+
size="sm"
444+
onClick={() => openAddTask()}
445+
className="h-9 px-3 gap-2"
446+
>
447+
<Plus className="h-3.5 w-3.5" />
448+
<span className="text-xs font-medium">{tDrawer("addTaskSrOnly")}</span>
449+
</Button>
450+
429451
{/* Project filter lives in the ToolSidebarLayout panel. */}
430452

431453
{/* Assignee Filter — only meaningful in a shared workspace */}
@@ -618,11 +640,6 @@ export const TaskContainer = () => {
618640
</Card>
619641
)}
620642

621-
{/* Task Form - Hidden on mobile, visible on desktop */}
622-
<div className="hidden md:block">
623-
<TaskForm onAddTask={handleAddTask} inputRef={taskFormInputRef} />
624-
</div>
625-
626643
{/* Task View */}
627644
<div className="flex-1 overflow-hidden flex flex-col">
628645
{viewMode === "kanban" ? (
@@ -658,6 +675,7 @@ export const TaskContainer = () => {
658675
onUpdateStatus={updateTaskStatus}
659676
onUpdateTask={updateTask}
660677
onDeleteTask={deleteTask}
678+
onAddInStatus={openAddTask}
661679
/>
662680
</div>
663681
) : (
@@ -670,6 +688,7 @@ export const TaskContainer = () => {
670688
onUpdateStatus={updateTaskStatus}
671689
onUpdateTask={updateTask}
672690
onDeleteTask={deleteTask}
691+
onAddInStatus={openAddTask}
673692
/>
674693
</CardContent>
675694
</Card>
@@ -694,35 +713,30 @@ export const TaskContainer = () => {
694713

695714
{/* Floating Action Button (FAB) - Mobile Only */}
696715
<div className="md:hidden">
697-
<Drawer open={isDrawerOpen} onOpenChange={setIsDrawerOpen}>
698-
<DrawerTrigger asChild>
699-
<Button
700-
size="icon"
701-
className="fab fab-pulse h-14 w-14 bg-primary hover:bg-primary/90 text-primary-foreground transition-transform active:scale-95"
702-
>
703-
<Plus className="h-6 w-6" />
704-
<span className="sr-only">{tDrawer("addTaskSrOnly")}</span>
705-
</Button>
706-
</DrawerTrigger>
707-
<DrawerContent>
708-
<div className="mx-auto w-full max-w-sm">
709-
<DrawerHeader>
710-
<DrawerTitle>{tDrawer("addNewTaskTitle")}</DrawerTitle>
711-
<DrawerDescription>{tDrawer("addNewTaskDescription")}</DrawerDescription>
712-
</DrawerHeader>
713-
<div className="p-4 pb-0">
714-
<TaskForm onAddTask={handleAddTask} />
715-
</div>
716-
<DrawerFooter>
717-
<DrawerClose asChild>
718-
<Button variant="outline">{tDrawer("cancel")}</Button>
719-
</DrawerClose>
720-
</DrawerFooter>
721-
</div>
722-
</DrawerContent>
723-
</Drawer>
716+
<Button
717+
size="icon"
718+
onClick={() => openAddTask()}
719+
className="fab fab-pulse h-14 w-14 bg-primary hover:bg-primary/90 text-primary-foreground transition-transform active:scale-95"
720+
>
721+
<Plus className="h-6 w-6" />
722+
<span className="sr-only">{tDrawer("addTaskSrOnly")}</span>
723+
</Button>
724724
</div>
725725

726+
{isAddTaskOpen && (
727+
<LazyBoundary fallback={null}>
728+
<Suspense fallback={null}>
729+
<TaskEditDialog
730+
task={blankTask}
731+
mode="create"
732+
open={isAddTaskOpen}
733+
onOpenChange={setIsAddTaskOpen}
734+
onSave={handleCreateTask}
735+
/>
736+
</Suspense>
737+
</LazyBoundary>
738+
)}
739+
726740
{isExportDialogOpen && (
727741
<LazyBoundary fallback={null}>
728742
<Suspense fallback={null}>
@@ -755,10 +769,7 @@ export const TaskContainer = () => {
755769
onOpenChange={setIsPaletteOpen}
756770
viewMode={viewMode}
757771
onViewModeChange={setViewMode}
758-
onNewTask={() => {
759-
if (isMobile) setIsDrawerOpen(true);
760-
else taskFormInputRef.current?.focus();
761-
}}
772+
onNewTask={() => openAddTask()}
762773
/>
763774
</Suspense>
764775
</LazyBoundary>

0 commit comments

Comments
 (0)