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:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user