tighten views
This commit is contained in:
@@ -4,7 +4,11 @@
|
||||
import { famStore } from '$lib/stores/fam.svelte';
|
||||
import { ViewHeader, CardGrid, Card } from '$lib/components';
|
||||
import { formatShortDate } from '$lib/format';
|
||||
import type { ChoreTemplate, AssignedChore, Member, Season } from '$lib/types';
|
||||
import type { ChoreTemplate, AssignedChore, Member, Season, Completion } from '$lib/types';
|
||||
|
||||
// ── Chevron SVG icons ──
|
||||
const CHEVRON_DOWN = '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"/></svg>';
|
||||
const CHEVRON_UP = '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="18 15 12 9 6 15"/></svg>';
|
||||
|
||||
let { data, form } = $props();
|
||||
|
||||
@@ -61,6 +65,7 @@
|
||||
);
|
||||
|
||||
let seasons = $state(famStore.initialized ? famStore.seasons : data.seasons || []);
|
||||
let completions = $state((data.completions as Completion[]) || []);
|
||||
|
||||
let filteredAssigned = $derived(
|
||||
seasonFilter === 'all' ? assigned : assigned.filter((a) => a.seasonIds?.includes(seasonFilter))
|
||||
@@ -176,6 +181,11 @@
|
||||
return d.toISOString().slice(0, 10);
|
||||
}
|
||||
|
||||
// Check if a todo has been completed by the child (todos don't reset daily like chores)
|
||||
function isTodoCompleted(todoId: string): boolean {
|
||||
return completions.some((c: Completion) => c.assignedChoreId === todoId);
|
||||
}
|
||||
|
||||
// Traffic light for admin todo cards (same logic as child page)
|
||||
function todoUrgency(a: AssignedChore): 'blue' | 'green' | 'orange' | 'red' | 'black' {
|
||||
if (!a.completeBy) return 'blue';
|
||||
@@ -285,28 +295,29 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<ViewHeader title="Chore Assignment" />
|
||||
<ViewHeader title="Chore Assignment" hero />
|
||||
|
||||
{#if form?.error}
|
||||
<p class="error">{form.error}</p>
|
||||
{/if}
|
||||
|
||||
<div class="season-filter">
|
||||
<label>Season:</label>
|
||||
<select bind:value={seasonFilter}>
|
||||
<option value="all">All Chores</option>
|
||||
{#each seasons as s}
|
||||
<option value={s.id}>{s.name}</option>
|
||||
{/each}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<CardGrid>
|
||||
<!-- Templates Section (at top, like bonuses) -->
|
||||
<Card cols={3}>
|
||||
<div class="kanban">
|
||||
<div class="column templates-col">
|
||||
<h3>Templates</h3>
|
||||
<button class="add-inline" onclick={() => (showCreateModal = true)}>+ New template</button>
|
||||
<div class="template-section">
|
||||
<div class="template-header">
|
||||
<h3>Templates — Drag to assign</h3>
|
||||
<div class="season-filter">
|
||||
<label>Season:</label>
|
||||
<select bind:value={seasonFilter}>
|
||||
<option value="all">All Chores</option>
|
||||
{#each seasons as s}
|
||||
<option value={s.id}>{s.name}</option>
|
||||
{/each}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="template-list">
|
||||
{#each templates as t}
|
||||
<div class="card template" draggable="true" ondragstart={(e) => handleDragStart(e, t.id)}>
|
||||
<div class="card-body" onclick={() => openEditTemplate(t)} role="button" tabindex="0">
|
||||
@@ -343,7 +354,14 @@
|
||||
<p class="empty">No templates yet</p>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
<button class="add-inline" onclick={() => (showCreateModal = true)}>+ New template</button>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<!-- Member Chore Assignment Kanban (3-column grid) -->
|
||||
<Card cols={3} scrollX>
|
||||
<div class="kanban">
|
||||
{#each members as m}
|
||||
<div
|
||||
class="column member-col"
|
||||
@@ -356,103 +374,119 @@
|
||||
{m.name}
|
||||
</h3>
|
||||
|
||||
<!-- TODOS accordion -->
|
||||
<button
|
||||
class="accordion-head accordion-todos"
|
||||
onclick={() => toggleAccordion(m.id, 'todos')}
|
||||
>
|
||||
<span class="accordion-label">📋 Todos ({todosForMember(m.id).length})</span>
|
||||
<span class="accordion-arrow" class:open={accordionState[m.id]?.todos ?? true}> ▾ </span>
|
||||
</button>
|
||||
{#if accordionState[m.id]?.todos ?? true}
|
||||
<button class="add-inline" onclick={() => openTodo(m.id)}>+ Add a todo</button>
|
||||
{#each sortedTodosForMember(m.id) as a}
|
||||
{@const urgency = todoUrgency(a)}
|
||||
<div
|
||||
class="todo-admin-card"
|
||||
class:todo-blue-bg={urgency === 'blue'}
|
||||
class:todo-green-bg={urgency === 'green'}
|
||||
class:todo-orange-bg={urgency === 'orange'}
|
||||
class:todo-red-bg={urgency === 'red'}
|
||||
class:todo-black-bg={urgency === 'black'}
|
||||
onclick={() => openEdit(a)}
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<div class="todo-admin-body">
|
||||
<strong class="todo-admin-name">{a.customName || 'Todo'}</strong>
|
||||
<div class="todo-admin-meta">
|
||||
{#if a.type === 'emoji'}
|
||||
<span class="todo-admin-type">🎯 emoji</span>
|
||||
{:else}
|
||||
<span class="todo-admin-type">{a.value} {a.type}</span>
|
||||
{/if}
|
||||
{#if a.completeBy}
|
||||
<span class="todo-admin-deadline">due {formatShortDate(a.completeBy)}</span>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
class="del-btn"
|
||||
title="Remove todo"
|
||||
onclick={async (e) => {
|
||||
e.stopPropagation();
|
||||
const s = page.data.session as any;
|
||||
if (!s) return;
|
||||
await fetch(`/api/admin/${s.famId}/assigned-chores/${a.id}`, {
|
||||
method: 'DELETE',
|
||||
headers: { 'x-session-famid': s.famId, 'x-session-userid': s.userId }
|
||||
});
|
||||
}}>×</button
|
||||
<!-- TODOS accordion wrapper -->
|
||||
<div class="accordion-wrapper accordion-todos">
|
||||
<button class="accordion-head" onclick={() => toggleAccordion(m.id, 'todos')}>
|
||||
<span class="accordion-label">📋 Todos ({todosForMember(m.id).length})</span>
|
||||
<span class="accordion-chevron">{@html (accordionState[m.id]?.todos ?? true) ? CHEVRON_UP : CHEVRON_DOWN}</span>
|
||||
</button>
|
||||
{#if accordionState[m.id]?.todos ?? true}
|
||||
<div class="accordion-body">
|
||||
{#each sortedTodosForMember(m.id) as a}
|
||||
{@const completed = isTodoCompleted(a.id)}
|
||||
{@const urgency = todoUrgency(a)}
|
||||
<div
|
||||
class="todo-admin-card"
|
||||
class:todo-completed={completed}
|
||||
class:todo-blue-bg={!completed && urgency === 'blue'}
|
||||
class:todo-green-bg={!completed && urgency === 'green'}
|
||||
class:todo-orange-bg={!completed && urgency === 'orange'}
|
||||
class:todo-red-bg={!completed && urgency === 'red'}
|
||||
class:todo-black-bg={!completed && urgency === 'black'}
|
||||
onclick={() => !completed && openEdit(a)}
|
||||
role="button"
|
||||
tabindex={completed ? -1 : 0}
|
||||
>
|
||||
</div>
|
||||
{/each}
|
||||
{/if}
|
||||
|
||||
<!-- CHORES accordion -->
|
||||
<button class="accordion-head" onclick={() => toggleAccordion(m.id, 'chores')}>
|
||||
<span class="accordion-label">Chores ({assignedForMember(m.id).length})</span>
|
||||
<span class="accordion-arrow" class:open={accordionState[m.id]?.chores ?? true}> ▾ </span>
|
||||
</button>
|
||||
{#if accordionState[m.id]?.chores ?? true}
|
||||
{#each assignedForMember(m.id) as a}
|
||||
{@const tName = templateName(a.templateId)}
|
||||
<div class="card assigned" onclick={() => openEdit(a)} role="button" tabindex="0">
|
||||
<div class="card-body">
|
||||
<strong>{a.customName || tName}</strong>
|
||||
<span class="badge">{a.frequency}</span>
|
||||
<span class="badge type">{a.type}</span>
|
||||
{#if seasonFilter !== 'all' && isGlobalChore(a)}
|
||||
<span class="badge season">Add to</span>
|
||||
<div class="todo-admin-body">
|
||||
<strong class="todo-admin-name">{a.customName || 'Todo'}</strong>
|
||||
<div class="todo-admin-meta">
|
||||
{#if a.type === 'emoji'}
|
||||
<span class="todo-admin-type">🎯 emoji</span>
|
||||
{:else}
|
||||
<span class="todo-admin-type"><span class="badge badge-pts">{a.value} pts</span></span>
|
||||
{/if}
|
||||
{#if a.completeBy}
|
||||
<span class="todo-admin-deadline">due {formatShortDate(a.completeBy)}</span>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{#if completed}
|
||||
<span class="todo-completed-badge">✅ TBC completed</span>
|
||||
{:else}
|
||||
<button
|
||||
class="del-btn"
|
||||
title="Remove todo"
|
||||
onclick={async (e) => {
|
||||
e.stopPropagation();
|
||||
const s = page.data.session as any;
|
||||
if (!s) return;
|
||||
await fetch(`/api/admin/${s.famId}/assigned-chores/${a.id}`, {
|
||||
method: 'DELETE',
|
||||
headers: { 'x-session-famid': s.famId, 'x-session-userid': s.userId }
|
||||
});
|
||||
}}>×</button
|
||||
>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="card-value">
|
||||
{a.type === 'money' ? `£${Number(a.value).toFixed(2)}` : a.value}
|
||||
</div>
|
||||
<button
|
||||
class="del-btn"
|
||||
title="Remove assignment"
|
||||
onclick={async (e) => {
|
||||
e.stopPropagation();
|
||||
const s = page.data.session as any;
|
||||
if (!s) return;
|
||||
await fetch(`/api/admin/${s.famId}/assigned-chores/${a.id}`, {
|
||||
method: 'DELETE',
|
||||
headers: { 'x-session-famid': s.famId, 'x-session-userid': s.userId }
|
||||
});
|
||||
}}>×</button
|
||||
>
|
||||
</div>
|
||||
{/each}
|
||||
{#if assignedForMember(m.id).length === 0}
|
||||
<p class="empty">Drop a chore here</p>
|
||||
{/if}
|
||||
{/each}
|
||||
<button class="add-inline accordion-add" onclick={() => openTodo(m.id)}>+ Add a todo</button>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<!-- CHORES accordion wrapper -->
|
||||
<div class="accordion-wrapper accordion-chores">
|
||||
<button class="accordion-head" onclick={() => toggleAccordion(m.id, 'chores')}>
|
||||
<span class="accordion-label">Chores ({assignedForMember(m.id).length})</span>
|
||||
<span class="accordion-chevron">{@html (accordionState[m.id]?.chores ?? true) ? CHEVRON_UP : CHEVRON_DOWN}</span>
|
||||
</button>
|
||||
{#if accordionState[m.id]?.chores ?? true}
|
||||
<div class="accordion-body">
|
||||
{#each assignedForMember(m.id) as a}
|
||||
{@const tName = templateName(a.templateId)}
|
||||
<div class="card assigned" onclick={() => openEdit(a)} role="button" tabindex="0">
|
||||
<div class="card-body">
|
||||
<strong>{a.customName || tName}</strong>
|
||||
<span class="badge">{a.frequency}</span>
|
||||
<span class="badge type">{a.type}</span>
|
||||
{#if seasonFilter !== 'all' && isGlobalChore(a)}
|
||||
<span class="badge season">Add to</span>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="card-value">
|
||||
{a.type === 'money' ? `£${Number(a.value).toFixed(2)}` : `${a.value} pts`}
|
||||
</div>
|
||||
<button
|
||||
class="del-btn"
|
||||
title="Remove assignment"
|
||||
onclick={async (e) => {
|
||||
e.stopPropagation();
|
||||
const s = page.data.session as any;
|
||||
if (!s) return;
|
||||
await fetch(`/api/admin/${s.famId}/assigned-chores/${a.id}`, {
|
||||
method: 'DELETE',
|
||||
headers: { 'x-session-famid': s.famId, 'x-session-userid': s.userId }
|
||||
});
|
||||
}}>×</button
|
||||
>
|
||||
</div>
|
||||
{/each}
|
||||
{#if assignedForMember(m.id).length === 0}
|
||||
<p class="empty">Drop a chore here</p>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
{#if members.length < 3}
|
||||
{#each Array(3 - members.length) as _}
|
||||
<div class="column column-placeholder"></div>
|
||||
{/each}
|
||||
{/if}
|
||||
</div>
|
||||
</Card></CardGrid
|
||||
>
|
||||
</Card>
|
||||
</CardGrid>
|
||||
|
||||
<!-- Create Template Modal -->
|
||||
{#if showCreateModal}
|
||||
@@ -714,26 +748,48 @@
|
||||
margin: 0.5rem 0;
|
||||
}
|
||||
|
||||
.kanban {
|
||||
/* ── Template section (top, like bonuses) ── */
|
||||
.template-section {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.template-header {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
overflow-x: auto;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
.template-header h3 {
|
||||
margin: 0;
|
||||
font-size: 1rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
.template-list {
|
||||
display: grid;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 0.75rem;
|
||||
grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
|
||||
}
|
||||
|
||||
/* ── Kanban (3 columns, scrolls via Card scrollX) ── */
|
||||
.kanban {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 0.75rem;
|
||||
min-width: 450px;
|
||||
}
|
||||
.column {
|
||||
flex: 1;
|
||||
min-width: 200px;
|
||||
max-width: 280px;
|
||||
border: 2px solid #e5e7eb;
|
||||
border-top-width: 4px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #e5e7eb;
|
||||
border-top: 4px solid #6366f1;
|
||||
border-radius: 12px;
|
||||
padding: 0.75rem;
|
||||
background: #fafafa;
|
||||
background: #fafbff;
|
||||
min-height: 200px;
|
||||
}
|
||||
.column h3 {
|
||||
margin: 0 0 0.75rem;
|
||||
font-size: 1rem;
|
||||
font-size: 0.95rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.4rem;
|
||||
@@ -749,38 +805,58 @@
|
||||
color: #9ca3af;
|
||||
font-size: 0.85rem;
|
||||
text-align: center;
|
||||
padding: 1rem;
|
||||
border: 1px dashed #d1d5db;
|
||||
border-radius: 6px;
|
||||
padding: 1.5rem;
|
||||
}
|
||||
.add-inline {
|
||||
display: block;
|
||||
width: 100%;
|
||||
margin-bottom: 0.5rem;
|
||||
padding: 0.4rem;
|
||||
background: transparent;
|
||||
border: 1px dashed #d1d5db;
|
||||
border-radius: 6px;
|
||||
color: #6b7280;
|
||||
margin-bottom: 0.35rem;
|
||||
padding: 0.45rem;
|
||||
background: rgba(99, 102, 241, 0.04);
|
||||
border: 1px dashed #c7d2fe;
|
||||
border-radius: 8px;
|
||||
color: #6366f1;
|
||||
font-size: 0.8rem;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: background 0.15s, border-color 0.15s;
|
||||
text-align: center;
|
||||
}
|
||||
.template-section > .add-inline {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.accordion-add {
|
||||
margin-bottom: 0;
|
||||
margin-top: 0.35rem;
|
||||
background: rgba(255,255,255,0.25);
|
||||
border-color: rgba(255,255,255,0.4);
|
||||
color: #fff;
|
||||
}
|
||||
.accordion-add:hover {
|
||||
background: rgba(255,255,255,0.35);
|
||||
border-color: rgba(255,255,255,0.6);
|
||||
color: #fff;
|
||||
}
|
||||
.add-inline:hover {
|
||||
border-color: #6366f1;
|
||||
color: #6366f1;
|
||||
background: #eef2ff;
|
||||
}
|
||||
|
||||
.card {
|
||||
background: white;
|
||||
border: 1px solid #e5e7eb;
|
||||
border-radius: 6px;
|
||||
border-radius: 8px;
|
||||
padding: 0.5rem 0.75rem;
|
||||
margin-bottom: 0.5rem;
|
||||
margin-bottom: 0.4rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
position: relative;
|
||||
transition: transform 0.15s, box-shadow 0.15s, border-color 0.15s;
|
||||
}
|
||||
.card:hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 3px 8px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
.card-body {
|
||||
flex: 1;
|
||||
@@ -788,13 +864,14 @@
|
||||
}
|
||||
.card-body strong {
|
||||
display: block;
|
||||
font-size: 0.9rem;
|
||||
font-size: 0.85rem;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
color: #1f2937;
|
||||
}
|
||||
.card-value {
|
||||
font-size: 0.85rem;
|
||||
font-size: 0.8rem;
|
||||
color: #6b7280;
|
||||
font-weight: 600;
|
||||
white-space: nowrap;
|
||||
@@ -824,15 +901,19 @@
|
||||
color: #9ca3af;
|
||||
cursor: pointer;
|
||||
font-size: 1rem;
|
||||
padding: 0 4px;
|
||||
padding: 2px 6px;
|
||||
line-height: 1;
|
||||
flex-shrink: 0;
|
||||
border-radius: 4px;
|
||||
transition: color 0.15s, background 0.15s;
|
||||
}
|
||||
.edit-btn:hover {
|
||||
color: #6366f1;
|
||||
background: #eef2ff;
|
||||
}
|
||||
.del-btn:hover {
|
||||
color: #dc2626;
|
||||
background: #fef2f2;
|
||||
}
|
||||
.hint {
|
||||
color: #6b7280;
|
||||
@@ -843,18 +924,18 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
.season-filter label {
|
||||
font-size: 0.85rem;
|
||||
color: #374151;
|
||||
font-size: 0.8rem;
|
||||
color: #6b7280;
|
||||
font-weight: 500;
|
||||
}
|
||||
.season-filter select {
|
||||
padding: 0.35rem 0.6rem;
|
||||
padding: 0.3rem 0.5rem;
|
||||
border: 1px solid #d1d5db;
|
||||
border-radius: 6px;
|
||||
font-size: 0.85rem;
|
||||
font-size: 0.8rem;
|
||||
background: #fff;
|
||||
}
|
||||
.badge.season {
|
||||
background: #e0f2fe;
|
||||
@@ -889,7 +970,7 @@
|
||||
|
||||
.template {
|
||||
cursor: grab;
|
||||
border-left: 3px solid #6366f1;
|
||||
border-left: 4px solid #8b5cf6;
|
||||
}
|
||||
.template:active {
|
||||
cursor: grabbing;
|
||||
@@ -897,10 +978,7 @@
|
||||
}
|
||||
.assigned {
|
||||
cursor: pointer;
|
||||
border-left: 3px solid #10b981;
|
||||
}
|
||||
.assigned:hover {
|
||||
background: #f3f4f6;
|
||||
border-left: 4px solid #10b981;
|
||||
}
|
||||
|
||||
.overlay {
|
||||
@@ -962,38 +1040,45 @@
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* ── Accordion ── */
|
||||
/* ── Accordion wrappers ── */
|
||||
.accordion-wrapper {
|
||||
background: #6366f1;
|
||||
border-radius: 10px;
|
||||
margin: 0.75rem 0 0.3rem;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2px 6px rgba(99, 102, 241, 0.25);
|
||||
}
|
||||
.accordion-wrapper.accordion-todos {
|
||||
background: #2e8b57;
|
||||
box-shadow: 0 2px 6px rgba(46, 139, 87, 0.25);
|
||||
}
|
||||
.accordion-head {
|
||||
width: 100%;
|
||||
padding: 0.4rem 0.6rem;
|
||||
margin: 0.3rem 0 0.2rem;
|
||||
background: #f3f4f6;
|
||||
border: 1px solid #e5e7eb;
|
||||
border-radius: 6px;
|
||||
padding: 0.55rem 0.85rem;
|
||||
border: none;
|
||||
border-left: 4px solid rgba(0,0,0,0.2);
|
||||
background: transparent;
|
||||
cursor: pointer;
|
||||
font-size: 0.8rem;
|
||||
font-size: 0.82rem;
|
||||
font-weight: 700;
|
||||
color: #374151;
|
||||
color: #fff;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
transition: filter 0.15s;
|
||||
}
|
||||
.accordion-head:hover {
|
||||
background: #e5e7eb;
|
||||
filter: brightness(1.1);
|
||||
}
|
||||
.accordion-todos {
|
||||
background: #fffbeb;
|
||||
border-color: #fcd34d;
|
||||
.accordion-body {
|
||||
padding: 0.5rem 0.65rem 0.65rem;
|
||||
background: rgba(0,0,0,0.08);
|
||||
}
|
||||
.accordion-todos:hover {
|
||||
background: #fef3c7;
|
||||
}
|
||||
.accordion-arrow {
|
||||
font-size: 0.7rem;
|
||||
transition: transform 0.2s;
|
||||
}
|
||||
.accordion-arrow.open {
|
||||
transform: rotate(180deg);
|
||||
.accordion-chevron {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-shrink: 0;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
/* ── Todo admin cards ── */
|
||||
@@ -1001,16 +1086,28 @@
|
||||
background: white;
|
||||
border: 1px solid #e5e7eb;
|
||||
border-left: 4px solid #3b82f6;
|
||||
border-radius: 6px;
|
||||
padding: 0.4rem 0.6rem;
|
||||
border-radius: 8px;
|
||||
padding: 0.45rem 0.65rem;
|
||||
margin-bottom: 0.35rem;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.4rem;
|
||||
transition: transform 0.15s, box-shadow 0.15s, opacity 0.2s;
|
||||
}
|
||||
.todo-admin-card:hover {
|
||||
filter: brightness(0.97);
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 3px 8px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
.todo-admin-card.todo-completed {
|
||||
opacity: 0.55;
|
||||
cursor: default;
|
||||
background: #f0fdf4;
|
||||
border-left-color: #10b981;
|
||||
}
|
||||
.todo-admin-card.todo-completed:hover {
|
||||
transform: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
.todo-blue-bg {
|
||||
background: #eff6ff;
|
||||
@@ -1060,8 +1157,39 @@
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.todo-badge {
|
||||
background: #fef3c7;
|
||||
color: #b45309;
|
||||
.todo-completed-badge {
|
||||
font-size: 0.65rem;
|
||||
font-weight: 700;
|
||||
color: #059669;
|
||||
background: #d1fae5;
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
flex-shrink: 0;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* ── Points badge ── */
|
||||
.badge-pts {
|
||||
background: #e0e7ff;
|
||||
color: #4338ca;
|
||||
font-size: 0.68rem;
|
||||
padding: 1px 6px;
|
||||
border-radius: 10px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* ── Placeholder column ── */
|
||||
.column-placeholder {
|
||||
border-top-color: #e5e7eb !important;
|
||||
background: repeating-linear-gradient(
|
||||
45deg,
|
||||
transparent,
|
||||
transparent 8px,
|
||||
rgba(0,0,0,0.02) 8px,
|
||||
rgba(0,0,0,0.02) 16px
|
||||
);
|
||||
border-style: dashed;
|
||||
opacity: 0.5;
|
||||
min-height: 200px;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user