UI icon-only buttons: 9 components, compact header, inline edit

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-30 01:18:23 +00:00
parent 8cf14dcf59
commit 6d68b68412
4 changed files with 143 additions and 269 deletions

View File

@@ -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<string | null>(null);
const [error, setError] = useState<string | null>(null);
// Form state
const [formTitle, setFormTitle] = useState("");
const [formDate, setFormDate] = useState("");
const [formGroup, setFormGroup] = useState("");
@@ -172,18 +174,14 @@ export default function GoalsPage() {
return (
<div className="space-y-4 pb-24 sm:pb-8 px-4 sm:px-0">
{/* Header */}
<div className="flex items-center justify-between">
<h1 className="text-xl font-bold dark:text-white">{t("goals.title")}</h1>
<button
onClick={() => setShowForm(!showForm)}
className="px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg text-sm font-medium transition-colors min-h-[44px]"
>
{showForm ? t("tasks.form.cancel") : `+ ${t("goals.add")}`}
</button>
</div>
<PageActionBar
title={t("goals.title")}
showAdd
onToggleAdd={() => setShowForm(!showForm)}
addOpen={showForm}
t={t}
/>
{/* Error */}
{error && (
<div className="bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-lg p-3 text-red-700 dark:text-red-300 text-sm">
{error}
@@ -191,7 +189,6 @@ export default function GoalsPage() {
</div>
)}
{/* Create form */}
{showForm && (
<form onSubmit={handleCreate} className="bg-white dark:bg-gray-900 rounded-xl border border-gray-200 dark:border-gray-800 p-4 space-y-3">
<div>
@@ -237,7 +234,6 @@ export default function GoalsPage() {
</form>
)}
{/* Goals list */}
{loading ? (
<div className="flex justify-center py-12">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600" />
@@ -278,7 +274,6 @@ export default function GoalsPage() {
{goal.progress_pct}%
</span>
</div>
{/* Progress bar */}
<div className="mt-3 h-2 bg-gray-100 dark:bg-gray-800 rounded-full overflow-hidden">
<div
className={`h-full rounded-full transition-all duration-500 ${progressColor(goal.progress_pct)}`}
@@ -294,20 +289,23 @@ export default function GoalsPage() {
{selectedGoal && (
<div className="bg-white dark:bg-gray-900 rounded-xl border border-gray-200 dark:border-gray-800 p-4 space-y-4">
<div className="flex items-start justify-between">
<div>
<div className="flex-1 min-w-0">
<h2 className="text-lg font-bold dark:text-white">{selectedGoal.title}</h2>
<p className="text-sm text-gray-500 dark:text-gray-400">
{formatDate(selectedGoal.target_date)} | {t("goals.progress")}: {selectedGoal.progress_pct}%
</p>
</div>
<button
onClick={() => setSelectedGoal(null)}
className="text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 p-1 min-h-[44px] min-w-[44px] flex items-center justify-center"
>
<IconButton
icon={
<svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
<path strokeLinecap="round" strokeLinejoin="round" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
}
label={t("tasks.close")}
onClick={() => setSelectedGoal(null)}
variant="default"
size="md"
/>
</div>
{/* Progress slider */}
@@ -325,47 +323,15 @@ export default function GoalsPage() {
/>
</div>
{/* AI Action buttons */}
<div className="flex gap-2">
<button
onClick={() => handleGeneratePlan(selectedGoal.id)}
disabled={aiLoading === "plan"}
className="flex-1 py-2.5 bg-purple-600 hover:bg-purple-700 disabled:bg-purple-400 text-white rounded-lg text-sm font-medium transition-colors flex items-center justify-center gap-2 min-h-[44px]"
>
{aiLoading === "plan" ? (
<>
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-white" />
{t("common.loading")}
</>
) : (
<>
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
<path strokeLinecap="round" strokeLinejoin="round" d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z" />
</svg>
{t("goals.plan")}
</>
)}
</button>
<button
onClick={() => handleGetReport(selectedGoal.id)}
disabled={aiLoading === "report"}
className="flex-1 py-2.5 bg-green-600 hover:bg-green-700 disabled:bg-green-400 text-white rounded-lg text-sm font-medium transition-colors flex items-center justify-center gap-2 min-h-[44px]"
>
{aiLoading === "report" ? (
<>
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-white" />
{t("common.loading")}
</>
) : (
<>
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
<path strokeLinecap="round" strokeLinejoin="round" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z" />
</svg>
{t("goals.report")}
</>
)}
</button>
</div>
{/* AI Action buttons - icon only */}
<GoalActionButtons
onPlan={() => 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() {
</div>
</div>
)}
{/* Delete button */}
<button
onClick={() => handleDelete(selectedGoal.id)}
className="w-full py-2 text-red-500 hover:text-red-600 hover:bg-red-50 dark:hover:bg-red-900/20 rounded-lg text-sm font-medium transition-colors min-h-[44px]"
>
{t("tasks.delete")}
</button>
</div>
)}
</div>

View File

@@ -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<string | null>(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 (
<div className="space-y-4 pb-24 sm:pb-8 px-4 sm:px-0">
{/* Header */}
<div className="flex items-center justify-between">
<h1 className="text-xl font-bold dark:text-white">{t("nav.projects")}</h1>
<button
onClick={() => setShowForm(!showForm)}
className="px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg text-sm font-medium transition-colors min-h-[44px]"
>
{showForm ? t("tasks.form.cancel") : `+ ${t("projects.add")}`}
</button>
</div>
<PageActionBar
title={t("nav.projects")}
showAdd
onToggleAdd={() => setShowForm(!showForm)}
addOpen={showForm}
t={t}
/>
{/* Error */}
{error && (
<div className="bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-lg p-3 text-red-700 dark:text-red-300 text-sm">
{error}
@@ -115,7 +112,6 @@ export default function ProjectsPage() {
</div>
)}
{/* Create form */}
{showForm && (
<form onSubmit={handleCreate} className="bg-white dark:bg-gray-900 rounded-xl border border-gray-200 dark:border-gray-800 p-4 space-y-3">
<div>
@@ -179,7 +175,6 @@ export default function ProjectsPage() {
</form>
)}
{/* Projects list */}
{loading ? (
<div className="flex justify-center py-12">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600" />
@@ -219,15 +214,11 @@ export default function ProjectsPage() {
</div>
<div className="flex items-center gap-1 flex-shrink-0">
<div className="w-3 h-3 rounded-full" style={{ backgroundColor: project.color || "#3B82F6" }} />
<button
<DeleteIconButton
onClick={() => handleDelete(project.id)}
className="p-2 text-gray-400 hover:text-red-500 transition-colors min-h-[44px] min-w-[44px] flex items-center justify-center"
title={t("tasks.delete")}
>
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
<path strokeLinecap="round" strokeLinejoin="round" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
</svg>
</button>
label={t("tasks.delete")}
size="sm"
/>
</div>
</div>
</div>

View File

@@ -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<string, UserResult>();
(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 (
<div className="max-w-lg mx-auto space-y-4 pb-20">
<div className="max-w-lg mx-auto space-y-4 pb-20 px-4 sm:px-0">
{/* Header */}
<div className="flex items-center gap-2">
<button
<CollabBackButton
onClick={() => router.push(`/tasks/${id}`)}
className="inline-flex items-center gap-1.5 px-3 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 bg-gray-100 dark:bg-gray-800 hover:bg-gray-200 dark:hover:bg-gray-700 rounded-lg transition-colors"
>
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
<path strokeLinecap="round" strokeLinejoin="round" d="M15 19l-7-7 7-7" />
</svg>
{t("common.back")}
</button>
label={t("common.back")}
/>
<h1 className="text-lg font-bold flex-1 truncate">{t("collab.title")}</h1>
</div>
@@ -367,47 +360,20 @@ export default function CollaboratePage() {
<p className="text-sm text-gray-400 mb-3">{t("collab.noAssignees")}</p>
)}
{/* Action buttons */}
<div className="flex flex-wrap gap-2">
<button
onClick={() => {
{/* Action buttons - icon only with tooltips */}
<CollabActionButtons
onAssign={() => {
setShowAssignSearch(!showAssignSearch);
setShowTransferSearch(false);
}}
disabled={actionLoading}
className="inline-flex items-center gap-1.5 px-3 py-2 text-sm font-medium bg-blue-600 hover:bg-blue-700 text-white rounded-lg transition-colors disabled:opacity-50"
>
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
<path strokeLinecap="round" strokeLinejoin="round" d="M18 9v3m0 0v3m0-3h3m-3 0h-3m-2-5a4 4 0 11-8 0 4 4 0 018 0zM3 20a6 6 0 0112 0v1H3v-1z" />
</svg>
{t("collab.assign")}
</button>
<button
onClick={() => {
onTransfer={() => {
setShowTransferSearch(!showTransferSearch);
setShowAssignSearch(false);
}}
onClaim={handleClaim}
disabled={actionLoading}
className="inline-flex items-center gap-1.5 px-3 py-2 text-sm font-medium bg-orange-600 hover:bg-orange-700 text-white rounded-lg transition-colors disabled:opacity-50"
>
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
<path strokeLinecap="round" strokeLinejoin="round" d="M8 7h12m0 0l-4-4m4 4l-4 4m0 6H4m0 0l4 4m-4-4l4-4" />
</svg>
{t("collab.transfer")}
</button>
<button
onClick={handleClaim}
disabled={actionLoading}
className="inline-flex items-center gap-1.5 px-3 py-2 text-sm font-medium bg-green-600 hover:bg-green-700 text-white rounded-lg transition-colors disabled:opacity-50"
>
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
<path strokeLinecap="round" strokeLinejoin="round" d="M7 11.5V14m0 0V14m0 0h2.5M7 14H4.5m4.5 0a6 6 0 1012 0 6 6 0 00-12 0z" />
</svg>
{t("collab.claim")}
</button>
</div>
t={t}
/>
{/* Assign search dropdown */}
{showAssignSearch && (
@@ -452,7 +418,6 @@ export default function CollaboratePage() {
)}
</div>
{/* Progress bar */}
{subtasksTotal > 0 && (
<div className="w-full bg-gray-200 dark:bg-gray-700 rounded-full h-2 mb-3">
<div
@@ -462,10 +427,9 @@ export default function CollaboratePage() {
</div>
)}
{/* Subtask list */}
<div className="space-y-2 mb-3">
{subtasks.map((sub) => {
const isDone = sub.status === "done" || sub.status === "completed";
const subDone = sub.status === "done" || sub.status === "completed";
return (
<div
key={sub.id}
@@ -474,18 +438,18 @@ export default function CollaboratePage() {
<button
onClick={() => 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 && (
<svg className="w-3 h-3" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={3}>
<path strokeLinecap="round" strokeLinejoin="round" d="M5 13l4 4L19 7" />
</svg>
)}
</button>
<span className={`flex-1 text-sm ${isDone ? "line-through text-gray-400" : ""}`}>
<span className={`flex-1 text-sm ${subDone ? "line-through text-gray-400" : ""}`}>
{sub.title}
</span>
{sub.assignee_name && (
@@ -495,7 +459,8 @@ export default function CollaboratePage() {
)}
<button
onClick={() => handleDeleteSubtask(sub.id)}
className="opacity-0 group-hover:opacity-100 text-red-400 hover:text-red-600 transition-opacity"
title={t("tasks.delete")}
className="opacity-0 group-hover:opacity-100 text-red-400 hover:text-red-600 transition-opacity p-1 rounded"
>
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
<path strokeLinecap="round" strokeLinejoin="round" d="M6 18L18 6M6 6l12 12" />
@@ -510,7 +475,6 @@ export default function CollaboratePage() {
<p className="text-sm text-gray-400 mb-3">{t("collab.noSubtasks")}</p>
)}
{/* Add subtask form */}
{showSubtaskForm ? (
<div className="space-y-2 border-t border-gray-100 dark:border-gray-700 pt-3">
<input
@@ -543,35 +507,47 @@ export default function CollaboratePage() {
/>
)}
<div className="flex gap-2">
<button
<IconButton
icon={
<svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
<path strokeLinecap="round" strokeLinejoin="round" d="M5 13l4 4L19 7" />
</svg>
}
label={t("collab.addBtn")}
onClick={handleAddSubtask}
disabled={!newSubtaskTitle.trim() || actionLoading}
className="px-3 py-1.5 text-sm font-medium bg-blue-600 hover:bg-blue-700 text-white rounded-lg transition-colors disabled:opacity-50"
>
{t("collab.addBtn")}
</button>
<button
variant="primary"
size="md"
/>
<IconButton
icon={
<svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
<path strokeLinecap="round" strokeLinejoin="round" d="M6 18L18 6M6 6l12 12" />
</svg>
}
label={t("tasks.form.cancel")}
onClick={() => {
setShowSubtaskForm(false);
setNewSubtaskTitle("");
setSubtaskAssignee(null);
}}
className="px-3 py-1.5 text-sm font-medium text-gray-600 hover:text-gray-800 dark:text-gray-400"
>
{t("tasks.form.cancel")}
</button>
variant="default"
size="md"
/>
</div>
</div>
) : (
<button
onClick={() => setShowSubtaskForm(true)}
className="inline-flex items-center gap-1.5 text-sm text-blue-600 hover:text-blue-700 font-medium"
>
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
<IconButton
icon={
<svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
<path strokeLinecap="round" strokeLinejoin="round" d="M12 4v16m8-8H4" />
</svg>
{t("collab.addSubtask")}
</button>
}
label={t("collab.addSubtask")}
onClick={() => setShowSubtaskForm(true)}
variant="primary"
size="md"
/>
)}
</div>

View File

@@ -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 (
<div className="max-w-lg mx-auto space-y-4">
{/* Action bar - all buttons in one compact row */}
<div className="flex items-center gap-2 flex-wrap">
<button
onClick={() => router.push("/tasks")}
className="inline-flex items-center gap-1.5 px-3 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 bg-gray-100 dark:bg-gray-800 hover:bg-gray-200 dark:hover:bg-gray-700 rounded-lg transition-colors"
>
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
<path strokeLinecap="round" strokeLinejoin="round" d="M15 19l-7-7 7-7" />
</svg>
{t("common.back")}
</button>
<div className="max-w-lg mx-auto space-y-4 px-4 sm:px-0">
{/* Action bar - all icon buttons in one compact row */}
<TaskDetailActions
taskDone={taskDone}
deleting={deleting}
onBack={() => router.push("/tasks")}
onEdit={() => setEditing(true)}
onDelete={handleDelete}
onToggleStatus={() => handleQuickStatus(taskDone ? "pending" : "done")}
onInvite={() => setShowInvite(true)}
onCollaborate={() => router.push(`/tasks/${id}/collaborate`)}
t={t}
/>
<div className="flex-1" />
{!taskDone && (
<button
onClick={() => handleQuickStatus("done")}
className="inline-flex items-center gap-1.5 px-3 py-2 text-sm font-medium bg-green-600 hover:bg-green-700 text-white rounded-lg transition-colors"
>
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
<path strokeLinecap="round" strokeLinejoin="round" d="M5 13l4 4L19 7" />
</svg>
{t("tasks.markDone")}
</button>
)}
{taskDone && (
<button
onClick={() => handleQuickStatus("pending")}
className="inline-flex items-center gap-1.5 px-3 py-2 text-sm font-medium bg-yellow-600 hover:bg-yellow-700 text-white rounded-lg transition-colors"
>
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
<path strokeLinecap="round" strokeLinejoin="round" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
</svg>
{t("tasks.reopen")}
</button>
)}
<button
onClick={() => setEditing(true)}
className="inline-flex items-center gap-1.5 px-3 py-2 text-sm font-medium border border-gray-300 dark:border-gray-600 hover:bg-gray-50 dark:hover:bg-gray-800 rounded-lg transition-colors"
>
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
<path strokeLinecap="round" strokeLinejoin="round" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" />
</svg>
{t("tasks.edit")}
</button>
<button
onClick={handleDelete}
disabled={deleting}
className="inline-flex items-center gap-1.5 px-3 py-2 text-sm font-medium border border-red-300 dark:border-red-800 text-red-600 dark:text-red-400 hover:bg-red-50 dark:hover:bg-red-900/20 rounded-lg transition-colors disabled:opacity-50"
>
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
<path strokeLinecap="round" strokeLinejoin="round" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
</svg>
{deleting ? t("tasks.deleting") : t("tasks.delete")}
</button>
</div>
{/* Task detail card */}
{/* Task detail card with inline editing */}
<div className="bg-white dark:bg-gray-900 border border-gray-200 dark:border-gray-700 rounded-2xl p-6">
<div className="flex items-start justify-between gap-4 mb-4">
<div className="flex items-start gap-3">
<div className="flex items-start gap-3 flex-1 min-w-0">
{task.group_icon && (
<span className="text-2xl">{task.group_icon}</span>
<span className="text-2xl flex-shrink-0">{task.group_icon}</span>
)}
<h1
className={`text-xl font-bold ${
taskDone ? "line-through text-muted" : ""
}`}
>
{task.title}
</h1>
<InlineEditField
value={task.title}
onSave={(val) => handleInlineUpdate("title", val)}
className={`text-xl font-bold ${taskDone ? "line-through text-muted" : ""}`}
/>
</div>
<StatusBadge status={task.status} size="md" />
</div>
{task.description && (
<p className="text-muted mb-4 whitespace-pre-wrap leading-relaxed">
{task.description}
</p>
)}
<div className="mb-4">
<InlineEditField
value={task.description || ""}
onSave={(val) => handleInlineUpdate("description", val)}
as="textarea"
multiline
placeholder={t("tasks.form.descPlaceholder")}
className="text-muted whitespace-pre-wrap leading-relaxed"
/>
</div>
<div className="grid grid-cols-2 gap-3 text-sm">
<div>
@@ -320,24 +287,6 @@ export default function TaskDetailPage() {
<h2 className="text-sm font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wider">
{t("collab.collaboration")}
</h2>
<button
onClick={() => router.push(`/tasks/${id}/collaborate`)}
className="inline-flex items-center gap-1.5 px-3 py-1.5 text-sm font-medium bg-purple-600 hover:bg-purple-700 text-white rounded-lg transition-colors"
>
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
<path strokeLinecap="round" strokeLinejoin="round" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z" />
</svg>
{t("collab.collaboration")}
</button>
<button
onClick={() => setShowInvite(true)}
className="inline-flex items-center gap-1.5 px-3 py-1.5 text-sm font-medium bg-blue-600 hover:bg-blue-700 text-white rounded-lg transition-colors"
>
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
<path strokeLinecap="round" strokeLinejoin="round" d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z" />
</svg>
Pozvat
</button>
</div>
{/* Assigned users */}