From 6d68b684126b1296a35cbdb940026add2aad9d5d Mon Sep 17 00:00:00 2001 From: Admin Date: Mon, 30 Mar 2026 01:18:23 +0000 Subject: [PATCH] UI icon-only buttons: 9 components, compact header, inline edit Co-Authored-By: Claude Opus 4.6 (1M context) --- apps/tasks/app/goals/page.tsx | 102 ++++--------- apps/tasks/app/projects/page.tsx | 35 ++--- .../tasks/app/tasks/[id]/collaborate/page.tsx | 138 ++++++++---------- apps/tasks/app/tasks/[id]/page.tsx | 137 ++++++----------- 4 files changed, 143 insertions(+), 269 deletions(-) diff --git a/apps/tasks/app/goals/page.tsx b/apps/tasks/app/goals/page.tsx index 01cdad9..2eacdc3 100644 --- a/apps/tasks/app/goals/page.tsx +++ b/apps/tasks/app/goals/page.tsx @@ -18,6 +18,9 @@ import { GoalReport, Group, } from "@/lib/api"; +import PageActionBar from "@/components/features/PageActionBar"; +import GoalActionButtons from "@/components/features/GoalActionButtons"; +import IconButton from "@/components/features/IconButton"; export default function GoalsPage() { const { token } = useAuth(); @@ -33,7 +36,6 @@ export default function GoalsPage() { const [aiLoading, setAiLoading] = useState(null); const [error, setError] = useState(null); - // Form state const [formTitle, setFormTitle] = useState(""); const [formDate, setFormDate] = useState(""); const [formGroup, setFormGroup] = useState(""); @@ -172,18 +174,14 @@ export default function GoalsPage() { return (
- {/* Header */} -
-

{t("goals.title")}

- -
+ setShowForm(!showForm)} + addOpen={showForm} + t={t} + /> - {/* Error */} {error && (
{error} @@ -191,7 +189,6 @@ export default function GoalsPage() {
)} - {/* Create form */} {showForm && (
@@ -237,7 +234,6 @@ export default function GoalsPage() { )} - {/* Goals list */} {loading ? (
@@ -278,7 +274,6 @@ export default function GoalsPage() { {goal.progress_pct}%
- {/* Progress bar */}
-
+

{selectedGoal.title}

{formatDate(selectedGoal.target_date)} | {t("goals.progress")}: {selectedGoal.progress_pct}%

- + variant="default" + size="md" + />
{/* Progress slider */} @@ -325,47 +323,15 @@ export default function GoalsPage() { />
- {/* AI Action buttons */} -
- - -
+ {/* AI Action buttons - icon only */} + handleGeneratePlan(selectedGoal.id)} + onReport={() => handleGetReport(selectedGoal.id)} + onDelete={() => handleDelete(selectedGoal.id)} + planLoading={aiLoading === "plan"} + reportLoading={aiLoading === "report"} + t={t} + /> {/* Plan result */} {planResult && ( @@ -468,14 +434,6 @@ export default function GoalsPage() {
)} - - {/* Delete button */} -
)}
diff --git a/apps/tasks/app/projects/page.tsx b/apps/tasks/app/projects/page.tsx index 251af6f..3598886 100644 --- a/apps/tasks/app/projects/page.tsx +++ b/apps/tasks/app/projects/page.tsx @@ -10,6 +10,8 @@ import { deleteProject, Project, } from "@/lib/api"; +import PageActionBar from "@/components/features/PageActionBar"; +import DeleteIconButton from "@/components/features/DeleteIconButton"; export default function ProjectsPage() { const { token, user } = useAuth(); @@ -20,7 +22,6 @@ export default function ProjectsPage() { const [showForm, setShowForm] = useState(false); const [error, setError] = useState(null); - // Form state const [formName, setFormName] = useState(""); const [formDesc, setFormDesc] = useState(""); const [formColor, setFormColor] = useState("#3B82F6"); @@ -96,18 +97,14 @@ export default function ProjectsPage() { return (
- {/* Header */} -
-

{t("nav.projects")}

- -
+ setShowForm(!showForm)} + addOpen={showForm} + t={t} + /> - {/* Error */} {error && (
{error} @@ -115,7 +112,6 @@ export default function ProjectsPage() {
)} - {/* Create form */} {showForm && (
@@ -179,7 +175,6 @@ export default function ProjectsPage() { )} - {/* Projects list */} {loading ? (
@@ -219,15 +214,11 @@ export default function ProjectsPage() {
- + label={t("tasks.delete")} + size="sm" + />
diff --git a/apps/tasks/app/tasks/[id]/collaborate/page.tsx b/apps/tasks/app/tasks/[id]/collaborate/page.tsx index 0f9d836..3155db0 100644 --- a/apps/tasks/app/tasks/[id]/collaborate/page.tsx +++ b/apps/tasks/app/tasks/[id]/collaborate/page.tsx @@ -17,6 +17,9 @@ import { sendCollabRequest, searchUsers, } from "@/lib/api"; +import CollabBackButton from "@/components/features/CollabBackButton"; +import CollabActionButtons from "@/components/features/CollabActionButtons"; +import IconButton from "@/components/features/IconButton"; interface UserResult { id: string; @@ -130,7 +133,6 @@ export default function CollaboratePage() { const [loading, setLoading] = useState(true); const [error, setError] = useState(""); - // Forms const [showAssignSearch, setShowAssignSearch] = useState(false); const [showTransferSearch, setShowTransferSearch] = useState(false); const [transferMessage, setTransferMessage] = useState(""); @@ -152,19 +154,15 @@ export default function CollaboratePage() { setSubtasks(subtasksData.data || []); setHistory(historyData.data || []); - // Load assignee details const assignedIds: string[] = taskData.assigned_to || []; if (assignedIds.length > 0) { try { const usersRes = await searchUsers(token, ""); - // Filter by IDs — the workload endpoint returns all users, filter client-side - // For now just show what we can from collaboration history const knownUsers = new Map(); (historyData.data || []).forEach((h: CollabRequest) => { if (h.from_user_id && h.from_name) knownUsers.set(h.from_user_id, { id: h.from_user_id, name: h.from_name, email: "", avatar_url: null }); if (h.to_user_id && h.to_name) knownUsers.set(h.to_user_id, { id: h.to_user_id, name: h.to_name, email: "", avatar_url: null }); }); - // Merge with any search results (usersRes.data || []).forEach((u: UserResult) => knownUsers.set(u.id, u)); setAssignees(assignedIds.map((uid) => knownUsers.get(uid) || { id: uid, name: uid.slice(0, 8), email: "", avatar_url: null })); } catch { @@ -316,18 +314,13 @@ export default function CollaboratePage() { }; return ( -
+
{/* Header */}
- + label={t("common.back")} + />

{t("collab.title")}

@@ -367,47 +360,20 @@ export default function CollaboratePage() {

{t("collab.noAssignees")}

)} - {/* Action buttons */} -
- - - - - -
+ {/* Action buttons - icon only with tooltips */} + { + setShowAssignSearch(!showAssignSearch); + setShowTransferSearch(false); + }} + onTransfer={() => { + setShowTransferSearch(!showTransferSearch); + setShowAssignSearch(false); + }} + onClaim={handleClaim} + disabled={actionLoading} + t={t} + /> {/* Assign search dropdown */} {showAssignSearch && ( @@ -452,7 +418,6 @@ export default function CollaboratePage() { )}
- {/* Progress bar */} {subtasksTotal > 0 && (
)} - {/* Subtask list */}
{subtasks.map((sub) => { - const isDone = sub.status === "done" || sub.status === "completed"; + const subDone = sub.status === "done" || sub.status === "completed"; return (
handleToggleSubtask(sub)} className={`flex-shrink-0 w-5 h-5 rounded border-2 flex items-center justify-center transition-colors ${ - isDone + subDone ? "bg-green-500 border-green-500 text-white" : "border-gray-300 dark:border-gray-600 hover:border-green-400" }`} > - {isDone && ( + {subDone && ( )} - + {sub.title} {sub.assignee_name && ( @@ -495,7 +459,8 @@ export default function CollaboratePage() { )} - + variant="default" + size="md" + />
) : ( - + variant="primary" + size="md" + /> )}
diff --git a/apps/tasks/app/tasks/[id]/page.tsx b/apps/tasks/app/tasks/[id]/page.tsx index 7e9a1b7..0a5dcce 100644 --- a/apps/tasks/app/tasks/[id]/page.tsx +++ b/apps/tasks/app/tasks/[id]/page.tsx @@ -18,6 +18,8 @@ import { import TaskForm from "@/components/TaskForm"; import InviteModal from "@/components/InviteModal"; import StatusBadge from "@/components/StatusBadge"; +import TaskDetailActions from "@/components/features/TaskDetailActions"; +import InlineEditField from "@/components/features/InlineEditField"; function isDone(status: string): boolean { return status === "done" || status === "completed"; @@ -70,7 +72,6 @@ export default function TaskDetailPage() { setTask(taskData); setGroups(groupsData.data || []); setSubtasks(subtasksData.data || []); - // Load assignee names const assigned: string[] = taskData.assigned_to || []; if (assigned.length > 0) { try { @@ -106,6 +107,16 @@ export default function TaskDetailPage() { loadTask(); } + async function handleInlineUpdate(field: string, value: string) { + if (!token || !id) return; + try { + await updateTask(token, id, { [field]: value }); + loadTask(); + } catch (err) { + setError(err instanceof Error ? err.message : t("common.error")); + } + } + async function handleDelete() { if (!token || !id) return; if (!confirm(t("tasks.confirmDelete"))) return; @@ -183,90 +194,46 @@ export default function TaskDetailPage() { const taskDone = isDone(task.status); return ( -
- {/* Action bar - all buttons in one compact row */} -
- +
+ {/* Action bar - all icon buttons in one compact row */} + router.push("/tasks")} + onEdit={() => setEditing(true)} + onDelete={handleDelete} + onToggleStatus={() => handleQuickStatus(taskDone ? "pending" : "done")} + onInvite={() => setShowInvite(true)} + onCollaborate={() => router.push(`/tasks/${id}/collaborate`)} + t={t} + /> -
- - {!taskDone && ( - - )} - - {taskDone && ( - - )} - - - - -
- - {/* Task detail card */} + {/* Task detail card with inline editing */}
-
+
{task.group_icon && ( - {task.group_icon} + {task.group_icon} )} -

- {task.title} -

+ handleInlineUpdate("title", val)} + className={`text-xl font-bold ${taskDone ? "line-through text-muted" : ""}`} + />
- {task.description && ( -

- {task.description} -

- )} +
+ handleInlineUpdate("description", val)} + as="textarea" + multiline + placeholder={t("tasks.form.descPlaceholder")} + className="text-muted whitespace-pre-wrap leading-relaxed" + /> +
@@ -320,24 +287,6 @@ export default function TaskDetailPage() {

{t("collab.collaboration")}

- -
{/* Assigned users */}