redesign: THERMAL warm brutalist dashboard UI
Complete visual redesign of all dashboard components with a warm brutalist command terminal aesthetic. Features editorial section numbering, IBM Plex typography, sharp zero-radius cards with colored accent strips, film grain overlay, and data-glow effects. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
f6a42c2dd2
commit
e94a7706ab
12 changed files with 641 additions and 548 deletions
|
|
@ -13,15 +13,13 @@ const TABS: { key: TabKey; label: string }[] = [
|
|||
{ key: "sams", label: "Sam's" },
|
||||
];
|
||||
|
||||
/** Colour for task priority (1 = highest). */
|
||||
function priorityIndicator(priority: number): { color: string; label: string } {
|
||||
if (priority <= 1) return { color: "bg-red-500", label: "Hoch" };
|
||||
if (priority <= 2) return { color: "bg-amber-500", label: "Mittel" };
|
||||
if (priority <= 3) return { color: "bg-blue-500", label: "Normal" };
|
||||
return { color: "bg-slate-500", label: "Niedrig" };
|
||||
if (priority <= 1) return { color: "bg-cherry", label: "Hoch" };
|
||||
if (priority <= 2) return { color: "bg-gold", label: "Mittel" };
|
||||
if (priority <= 3) return { color: "bg-azure", label: "Normal" };
|
||||
return { color: "bg-base-500", label: "Niedrig" };
|
||||
}
|
||||
|
||||
/** Format a due date string to a short German date. */
|
||||
function formatDueDate(iso: string | null): string | null {
|
||||
if (!iso) return null;
|
||||
try {
|
||||
|
|
@ -30,7 +28,7 @@ function formatDueDate(iso: string | null): string | null {
|
|||
const now = new Date();
|
||||
const diffDays = Math.ceil((d.getTime() - now.getTime()) / (1000 * 60 * 60 * 24));
|
||||
|
||||
if (diffDays < 0) return "Ueberfaellig";
|
||||
if (diffDays < 0) return "Überfällig";
|
||||
if (diffDays === 0) return "Heute";
|
||||
if (diffDays === 1) return "Morgen";
|
||||
return d.toLocaleDateString("de-DE", { day: "2-digit", month: "short" });
|
||||
|
|
@ -46,12 +44,12 @@ export default function TasksCard({ data }: TasksCardProps) {
|
|||
|
||||
if (data.error) {
|
||||
return (
|
||||
<div className="glass-card p-5 animate-fade-in">
|
||||
<div className="flex items-center gap-3 text-red-400">
|
||||
<div className="deck-card p-5 animate-fade-in">
|
||||
<div className="flex items-center gap-3 text-cherry">
|
||||
<AlertTriangle className="w-5 h-5" />
|
||||
<div>
|
||||
<p className="text-sm font-medium">Aufgaben nicht verfuegbar</p>
|
||||
<p className="text-xs text-slate-500 mt-0.5">Verbindung fehlgeschlagen</p>
|
||||
<p className="text-sm font-medium">Aufgaben nicht verfügbar</p>
|
||||
<p className="text-xs text-base-600 mt-0.5">Verbindung fehlgeschlagen</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -62,19 +60,15 @@ export default function TasksCard({ data }: TasksCardProps) {
|
|||
const tasks = group?.open ?? [];
|
||||
|
||||
return (
|
||||
<div className="glass-card p-5 animate-fade-in bg-gradient-to-br from-blue-500/[0.04] to-transparent">
|
||||
<div className="deck-card p-5 animate-fade-in" data-accent="azure">
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<div className="flex items-center gap-2.5">
|
||||
<div className="flex items-center justify-center w-8 h-8 rounded-lg bg-blue-500/10 border border-blue-500/20">
|
||||
<CheckSquare className="w-4 h-4 text-blue-400" />
|
||||
</div>
|
||||
<h3 className="text-sm font-semibold text-white">Aufgaben</h3>
|
||||
</div>
|
||||
<div className="flex items-center gap-3 mb-4">
|
||||
<CheckSquare className="w-4 h-4 text-azure" />
|
||||
<h3 className="text-sm font-semibold text-base-900">Aufgaben</h3>
|
||||
</div>
|
||||
|
||||
{/* Tab buttons */}
|
||||
<div className="flex gap-2 mb-4">
|
||||
{/* Tabs */}
|
||||
<div className="flex gap-1 mb-4">
|
||||
{TABS.map((tab) => {
|
||||
const tabGroup = tab.key === "private" ? data.private : data.sams;
|
||||
const openCount = tabGroup?.open_count ?? 0;
|
||||
|
|
@ -84,23 +78,13 @@ export default function TasksCard({ data }: TasksCardProps) {
|
|||
<button
|
||||
key={tab.key}
|
||||
onClick={() => setActiveTab(tab.key)}
|
||||
className={`
|
||||
flex items-center gap-2 px-3.5 py-2 rounded-xl text-xs font-medium transition-all duration-200
|
||||
${
|
||||
isActive
|
||||
? "bg-blue-500/15 text-blue-300 border border-blue-500/20"
|
||||
: "bg-white/[0.03] text-slate-400 border border-white/[0.04] hover:bg-white/[0.06] hover:text-slate-300"
|
||||
}
|
||||
`}
|
||||
className={`tab-btn ${isActive ? "active" : ""} flex items-center gap-2`}
|
||||
>
|
||||
{tab.label}
|
||||
{openCount > 0 && (
|
||||
<span
|
||||
className={`
|
||||
inline-flex items-center justify-center min-w-[1.25rem] h-5 px-1.5 rounded-full text-[10px] font-bold
|
||||
${isActive ? "bg-blue-500/25 text-blue-200" : "bg-white/[0.06] text-slate-500"}
|
||||
`}
|
||||
>
|
||||
<span className={`text-[10px] font-bold ${
|
||||
isActive ? "text-gold" : "text-base-500"
|
||||
}`}>
|
||||
{openCount}
|
||||
</span>
|
||||
)}
|
||||
|
|
@ -109,14 +93,14 @@ export default function TasksCard({ data }: TasksCardProps) {
|
|||
})}
|
||||
</div>
|
||||
|
||||
{/* Task list */}
|
||||
{/* Tasks */}
|
||||
{tasks.length === 0 ? (
|
||||
<div className="flex flex-col items-center justify-center py-8 text-slate-600">
|
||||
<CheckSquare className="w-8 h-8 mb-2 opacity-30" />
|
||||
<p className="text-xs">Alles erledigt!</p>
|
||||
<div className="flex flex-col items-center justify-center py-8 text-base-500">
|
||||
<CheckSquare className="w-8 h-8 mb-2 opacity-20" />
|
||||
<p className="text-xs font-mono">Alles erledigt!</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-1.5 max-h-80 overflow-y-auto pr-1">
|
||||
<div className="space-y-px max-h-80 overflow-y-auto">
|
||||
{tasks.map((task) => (
|
||||
<TaskItem key={task.id} task={task} />
|
||||
))}
|
||||
|
|
@ -129,44 +113,36 @@ export default function TasksCard({ data }: TasksCardProps) {
|
|||
function TaskItem({ task }: { task: Task }) {
|
||||
const p = priorityIndicator(task.priority);
|
||||
const due = formatDueDate(task.due_date);
|
||||
const isOverdue = due === "Ueberfaellig";
|
||||
const isOverdue = due === "Überfällig";
|
||||
|
||||
return (
|
||||
<div className="flex items-start gap-3 px-3 py-2.5 rounded-xl bg-white/[0.02] border border-white/[0.04] hover:bg-white/[0.04] transition-colors group">
|
||||
{/* Visual checkbox */}
|
||||
<div className="flex items-start gap-3 px-3 py-2.5 bg-base-100 border-l-2 border-base-300 hover:border-azure transition-colors group">
|
||||
<div className="mt-0.5 flex-shrink-0">
|
||||
{task.done ? (
|
||||
<CheckSquare className="w-4 h-4 text-blue-400" />
|
||||
<CheckSquare className="w-4 h-4 text-azure" />
|
||||
) : (
|
||||
<Square className="w-4 h-4 text-slate-600 group-hover:text-slate-400 transition-colors" />
|
||||
<Square className="w-4 h-4 text-base-400 group-hover:text-base-600 transition-colors" />
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<div className="flex-1 min-w-0">
|
||||
<p
|
||||
className={`text-sm leading-snug ${
|
||||
task.done ? "text-slate-600 line-through" : "text-slate-200"
|
||||
}`}
|
||||
>
|
||||
<p className={`text-sm leading-snug ${
|
||||
task.done ? "text-base-500 line-through" : "text-base-800"
|
||||
}`}>
|
||||
{task.title}
|
||||
</p>
|
||||
|
||||
<div className="flex items-center gap-2 mt-1.5 flex-wrap">
|
||||
{/* Project badge */}
|
||||
{task.project_name && (
|
||||
<span className="badge bg-indigo-500/15 text-indigo-300">
|
||||
<span className="tag border-iris/30 text-iris bg-iris/5">
|
||||
{task.project_name}
|
||||
</span>
|
||||
)}
|
||||
|
||||
{/* Due date */}
|
||||
{due && (
|
||||
<span
|
||||
className={`flex items-center gap-1 text-[10px] font-medium ${
|
||||
isOverdue ? "text-red-400" : "text-slate-500"
|
||||
}`}
|
||||
>
|
||||
<span className={`flex items-center gap-1 text-[10px] font-mono font-medium ${
|
||||
isOverdue ? "text-cherry" : "text-base-500"
|
||||
}`}>
|
||||
<Calendar className="w-2.5 h-2.5" />
|
||||
{due}
|
||||
</span>
|
||||
|
|
@ -174,9 +150,8 @@ function TaskItem({ task }: { task: Task }) {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
{/* Priority dot */}
|
||||
<div className="flex-shrink-0 mt-1.5" title={p.label}>
|
||||
<div className={`w-2 h-2 rounded-full ${p.color}`} />
|
||||
<div className={`w-1.5 h-1.5 ${p.color}`} style={{ borderRadius: 0 }} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue