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
<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.close")}
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"
>
<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>
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>