mejora los copis
parent
b4f2f9be92
commit
d27a4aa201
@ -1,294 +1,335 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Card from "$lib/ui/layout/Card.svelte";
|
import Card from "$lib/ui/layout/Card.svelte";
|
||||||
import TaskItem from "$lib/ui/data/TaskItem.svelte";
|
import TaskItem from "$lib/ui/data/TaskItem.svelte";
|
||||||
import Pagination from "$lib/ui/layout/Pagination.svelte";
|
import Pagination from "$lib/ui/layout/Pagination.svelte";
|
||||||
|
|
||||||
type Task = {
|
type Task = {
|
||||||
id: number;
|
id: number;
|
||||||
description: string;
|
description: string;
|
||||||
due_date: string | null;
|
due_date: string | null;
|
||||||
group_id: string | null;
|
group_id: string | null;
|
||||||
display_code: number | null;
|
display_code: number | null;
|
||||||
assignees: string[];
|
assignees: string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export let data: {
|
export let data: {
|
||||||
userId: string;
|
userId: string;
|
||||||
openTasks: Task[];
|
openTasks: Task[];
|
||||||
recentTasks: (Task & { completed?: boolean; completed_at?: string | null })[];
|
recentTasks: (Task & {
|
||||||
unassignedOpen: Task[];
|
completed?: boolean;
|
||||||
groupNames: Record<string, string>;
|
completed_at?: string | null;
|
||||||
order: 'due' | 'group';
|
})[];
|
||||||
page?: number | null;
|
unassignedOpen: Task[];
|
||||||
hasMore?: boolean | null;
|
groupNames: Record<string, string>;
|
||||||
};
|
order: "due" | "group";
|
||||||
|
page?: number | null;
|
||||||
|
hasMore?: boolean | null;
|
||||||
|
};
|
||||||
|
|
||||||
// Estado local para permitir actualización sin recargar ni perder scroll
|
// Estado local para permitir actualización sin recargar ni perder scroll
|
||||||
let openTasks: Task[] = [...data.openTasks];
|
let openTasks: Task[] = [...data.openTasks];
|
||||||
let unassignedOpen: Task[] = [...data.unassignedOpen];
|
let unassignedOpen: Task[] = [...data.unassignedOpen];
|
||||||
let recentTasks: (Task & { completed?: boolean; completed_at?: string | null })[] = [...data.recentTasks];
|
let recentTasks: (Task & {
|
||||||
|
completed?: boolean;
|
||||||
|
completed_at?: string | null;
|
||||||
|
})[] = [...data.recentTasks];
|
||||||
|
|
||||||
function buildQuery(params: { order?: 'due' | 'group'; page?: number }) {
|
function buildQuery(params: { order?: "due" | "group"; page?: number }) {
|
||||||
const sp = new URLSearchParams();
|
const sp = new URLSearchParams();
|
||||||
if (params.order) sp.set("order", params.order);
|
if (params.order) sp.set("order", params.order);
|
||||||
if (params.page && params.page > 1) sp.set("page", String(params.page));
|
if (params.page && params.page > 1) sp.set("page", String(params.page));
|
||||||
return sp.toString();
|
return sp.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
function sortByDue(items: Task[]): Task[] {
|
function sortByDue(items: Task[]): Task[] {
|
||||||
return [...items].sort((a, b) => {
|
return [...items].sort((a, b) => {
|
||||||
const ad = a.due_date, bd = b.due_date;
|
const ad = a.due_date,
|
||||||
if (ad == null && bd == null) return a.id - b.id;
|
bd = b.due_date;
|
||||||
if (ad == null) return 1;
|
if (ad == null && bd == null) return a.id - b.id;
|
||||||
if (bd == null) return -1;
|
if (ad == null) return 1;
|
||||||
if (ad < bd) return -1;
|
if (bd == null) return -1;
|
||||||
if (ad > bd) return 1;
|
if (ad < bd) return -1;
|
||||||
return a.id - b.id;
|
if (ad > bd) return 1;
|
||||||
});
|
return a.id - b.id;
|
||||||
}
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function groupByGroup(items: Task[]): { id: string; name: string; tasks: Task[] }[] {
|
function groupByGroup(
|
||||||
const map = new Map<string, Task[]>();
|
items: Task[],
|
||||||
for (const it of items) {
|
): { id: string; name: string; tasks: Task[] }[] {
|
||||||
const gid = it.group_id ? String(it.group_id) : "";
|
const map = new Map<string, Task[]>();
|
||||||
if (!map.has(gid)) map.set(gid, []);
|
for (const it of items) {
|
||||||
map.get(gid)!.push(it);
|
const gid = it.group_id ? String(it.group_id) : "";
|
||||||
}
|
if (!map.has(gid)) map.set(gid, []);
|
||||||
const groups = Array.from(map.entries()).map(([gid, tasks]) => ({
|
map.get(gid)!.push(it);
|
||||||
id: gid,
|
}
|
||||||
name: gid ? (data.groupNames[gid] || gid) : "Personal",
|
const groups = Array.from(map.entries()).map(([gid, tasks]) => ({
|
||||||
tasks
|
id: gid,
|
||||||
}));
|
name: gid ? data.groupNames[gid] || gid : "Personal",
|
||||||
// Mantener el orden provisto por el servidor (ya ordenado alfabéticamente con "Personal" al final)
|
tasks,
|
||||||
return groups;
|
}));
|
||||||
}
|
// Mantener el orden provisto por el servidor (ya ordenado alfabéticamente con "Personal" al final)
|
||||||
|
return groups;
|
||||||
|
}
|
||||||
|
|
||||||
function maintainScrollWhile(mutate: () => void) {
|
function maintainScrollWhile(mutate: () => void) {
|
||||||
const y = window.scrollY;
|
const y = window.scrollY;
|
||||||
mutate();
|
mutate();
|
||||||
queueMicrotask(() => window.scrollTo({ top: y }));
|
queueMicrotask(() => window.scrollTo({ top: y }));
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateTaskInLists(detail: { id: number; action: string; patch: Partial<Task & { completed?: boolean; completed_at?: string | null }> }) {
|
function updateTaskInLists(detail: {
|
||||||
const { id, action, patch } = detail;
|
id: number;
|
||||||
|
action: string;
|
||||||
|
patch: Partial<
|
||||||
|
Task & { completed?: boolean; completed_at?: string | null }
|
||||||
|
>;
|
||||||
|
}) {
|
||||||
|
const { id, action, patch } = detail;
|
||||||
|
|
||||||
const patchIn = (arr: Task[]) => {
|
const patchIn = (arr: Task[]) => {
|
||||||
const idx = arr.findIndex((t) => t.id === id);
|
const idx = arr.findIndex((t) => t.id === id);
|
||||||
if (idx >= 0) {
|
if (idx >= 0) {
|
||||||
arr[idx] = { ...arr[idx], ...patch };
|
arr[idx] = { ...arr[idx], ...patch };
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
if (action === 'complete') {
|
if (action === "complete") {
|
||||||
maintainScrollWhile(() => {
|
maintainScrollWhile(() => {
|
||||||
let moved = false;
|
let moved = false;
|
||||||
let idx = unassignedOpen.findIndex((t) => t.id === id);
|
let idx = unassignedOpen.findIndex((t) => t.id === id);
|
||||||
if (idx >= 0) {
|
if (idx >= 0) {
|
||||||
const [it] = unassignedOpen.splice(idx, 1);
|
const [it] = unassignedOpen.splice(idx, 1);
|
||||||
const completedItem: any = { ...it, ...patch, completed: true };
|
const completedItem: any = { ...it, ...patch, completed: true };
|
||||||
recentTasks = [completedItem, ...recentTasks];
|
recentTasks = [completedItem, ...recentTasks];
|
||||||
moved = true;
|
moved = true;
|
||||||
}
|
}
|
||||||
idx = openTasks.findIndex((t) => t.id === id);
|
idx = openTasks.findIndex((t) => t.id === id);
|
||||||
if (idx >= 0) {
|
if (idx >= 0) {
|
||||||
const [it] = openTasks.splice(idx, 1);
|
const [it] = openTasks.splice(idx, 1);
|
||||||
const completedItem: any = { ...it, ...patch, completed: true };
|
const completedItem: any = { ...it, ...patch, completed: true };
|
||||||
recentTasks = [completedItem, ...recentTasks];
|
recentTasks = [completedItem, ...recentTasks];
|
||||||
moved = true;
|
moved = true;
|
||||||
}
|
}
|
||||||
if (!moved) {
|
if (!moved) {
|
||||||
patchIn(recentTasks as any);
|
patchIn(recentTasks as any);
|
||||||
}
|
}
|
||||||
// Forzar reactividad en listas mutadas
|
// Forzar reactividad en listas mutadas
|
||||||
openTasks = [...openTasks];
|
openTasks = [...openTasks];
|
||||||
unassignedOpen = [...unassignedOpen];
|
unassignedOpen = [...unassignedOpen];
|
||||||
recentTasks = [...recentTasks];
|
recentTasks = [...recentTasks];
|
||||||
});
|
});
|
||||||
} else if (action === 'uncomplete') {
|
} else if (action === "uncomplete") {
|
||||||
maintainScrollWhile(() => {
|
maintainScrollWhile(() => {
|
||||||
const idx = recentTasks.findIndex((t) => t.id === id);
|
const idx = recentTasks.findIndex((t) => t.id === id);
|
||||||
if (idx >= 0) {
|
if (idx >= 0) {
|
||||||
const [it] = recentTasks.splice(idx, 1);
|
const [it] = recentTasks.splice(idx, 1);
|
||||||
const reopened: any = { ...it, ...patch, completed: false };
|
const reopened: any = { ...it, ...patch, completed: false };
|
||||||
openTasks = [reopened, ...openTasks];
|
openTasks = [reopened, ...openTasks];
|
||||||
} else {
|
} else {
|
||||||
patchIn(openTasks);
|
patchIn(openTasks);
|
||||||
}
|
}
|
||||||
openTasks = [...openTasks];
|
openTasks = [...openTasks];
|
||||||
recentTasks = [...recentTasks];
|
recentTasks = [...recentTasks];
|
||||||
});
|
});
|
||||||
} else if (action === 'claim') {
|
} else if (action === "claim") {
|
||||||
maintainScrollWhile(() => {
|
maintainScrollWhile(() => {
|
||||||
const idx = unassignedOpen.findIndex((t) => t.id === id);
|
const idx = unassignedOpen.findIndex((t) => t.id === id);
|
||||||
if (idx >= 0) {
|
if (idx >= 0) {
|
||||||
const [it] = unassignedOpen.splice(idx, 1);
|
const [it] = unassignedOpen.splice(idx, 1);
|
||||||
const claimed = { ...it, ...patch };
|
const claimed = { ...it, ...patch };
|
||||||
if (!openTasks.some((x) => x.id === id)) {
|
if (!openTasks.some((x) => x.id === id)) {
|
||||||
openTasks = [claimed, ...openTasks];
|
openTasks = [claimed, ...openTasks];
|
||||||
} else {
|
} else {
|
||||||
patchIn(openTasks);
|
patchIn(openTasks);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
patchIn(openTasks);
|
patchIn(openTasks);
|
||||||
}
|
}
|
||||||
openTasks = [...openTasks];
|
openTasks = [...openTasks];
|
||||||
unassignedOpen = [...unassignedOpen];
|
unassignedOpen = [...unassignedOpen];
|
||||||
});
|
});
|
||||||
} else if (action === 'unassign') {
|
} else if (action === "unassign") {
|
||||||
maintainScrollWhile(() => {
|
maintainScrollWhile(() => {
|
||||||
if (!patchIn(openTasks)) patchIn(unassignedOpen);
|
if (!patchIn(openTasks)) patchIn(unassignedOpen);
|
||||||
// Si quedó sin responsables, mover a "sin responsable"
|
// Si quedó sin responsables, mover a "sin responsable"
|
||||||
const idx = openTasks.findIndex((t) => t.id === id);
|
const idx = openTasks.findIndex((t) => t.id === id);
|
||||||
if (idx >= 0 && (openTasks[idx].assignees || []).length === 0) {
|
if (idx >= 0 && (openTasks[idx].assignees || []).length === 0) {
|
||||||
const [it] = openTasks.splice(idx, 1);
|
const [it] = openTasks.splice(idx, 1);
|
||||||
unassignedOpen = [it, ...unassignedOpen];
|
unassignedOpen = [it, ...unassignedOpen];
|
||||||
}
|
}
|
||||||
openTasks = [...openTasks];
|
openTasks = [...openTasks];
|
||||||
unassignedOpen = [...unassignedOpen];
|
unassignedOpen = [...unassignedOpen];
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// update_due, update_desc u otros parches ligeros
|
// update_due, update_desc u otros parches ligeros
|
||||||
if (!patchIn(openTasks)) {
|
if (!patchIn(openTasks)) {
|
||||||
if (!patchIn(unassignedOpen)) {
|
if (!patchIn(unassignedOpen)) {
|
||||||
patchIn(recentTasks as any);
|
patchIn(recentTasks as any);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
openTasks = [...openTasks];
|
openTasks = [...openTasks];
|
||||||
unassignedOpen = [...unassignedOpen];
|
unassignedOpen = [...unassignedOpen];
|
||||||
recentTasks = [...recentTasks];
|
recentTasks = [...recentTasks];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:head>
|
<svelte:head>
|
||||||
<title>Tareas</title>
|
<title>Tareas</title>
|
||||||
<meta name="robots" content="noindex,nofollow" />
|
<meta name="robots" content="noindex,nofollow" />
|
||||||
</svelte:head>
|
</svelte:head>
|
||||||
|
|
||||||
<p class="subtle">Sesión: <strong>{data.userId}</strong></p>
|
<p class="subtle">Sesión: <strong>{data.userId}</strong></p>
|
||||||
|
|
||||||
<div class="order-toggle">
|
<div class="order-toggle">
|
||||||
<span>Orden:</span>
|
<span>Orden:</span>
|
||||||
<a
|
<a
|
||||||
class:active={data.order === 'due'}
|
class:active={data.order === "due"}
|
||||||
href={`/app?${buildQuery({ order: 'due', page: data.page ?? 1 })}`}>Fecha</a
|
href={`/app?${buildQuery({ order: "due", page: data.page ?? 1 })}`}>Fecha</a
|
||||||
>
|
>
|
||||||
<a
|
<a
|
||||||
class:active={data.order === 'group'}
|
class:active={data.order === "group"}
|
||||||
href={`/app?${buildQuery({ order: 'group', page: data.page ?? 1 })}`}>Grupo</a
|
href={`/app?${buildQuery({ order: "group", page: data.page ?? 1 })}`}
|
||||||
>
|
>Grupo</a
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h2 class="section-title">Mis tareas (abiertas)</h2>
|
<h2 class="section-title">Mis tareas</h2>
|
||||||
{#if openTasks.length === 0}
|
{#if openTasks.length === 0}
|
||||||
<p>No tienes tareas asignadas. Crea o reclama una para empezar.</p>
|
<p>No tienes tareas asignadas. Crea o reclama una para empezar.</p>
|
||||||
{:else}
|
{:else}
|
||||||
<Card>
|
<Card>
|
||||||
<ul class="list">
|
<ul class="list">
|
||||||
{#each openTasks as t (t.id)}
|
{#each openTasks as t (t.id)}
|
||||||
<TaskItem {...t} currentUserId={data.userId} groupName={t.group_id ? (data.groupNames[t.group_id] || t.group_id) : 'Personal'} groupId={t.group_id} on:changed={(e) => updateTaskInLists(e.detail)} />
|
<TaskItem
|
||||||
{/each}
|
{...t}
|
||||||
</ul>
|
currentUserId={data.userId}
|
||||||
</Card>
|
groupName={t.group_id
|
||||||
|
? data.groupNames[t.group_id] || t.group_id
|
||||||
|
: "Personal"}
|
||||||
|
groupId={t.group_id}
|
||||||
|
on:changed={(e) => updateTaskInLists(e.detail)}
|
||||||
|
/>
|
||||||
|
{/each}
|
||||||
|
</ul>
|
||||||
|
</Card>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if (data.page ?? 1) > 1 || data.hasMore}
|
{#if (data.page ?? 1) > 1 || data.hasMore}
|
||||||
<Pagination
|
<Pagination
|
||||||
prevHref={(data.page ?? 1) > 1 ? `/app?${buildQuery({ order: data.order, page: (data.page ?? 1) - 1 })}` : null}
|
prevHref={(data.page ?? 1) > 1
|
||||||
nextHref={data.hasMore ? `/app?${buildQuery({ order: data.order, page: (data.page ?? 1) + 1 })}` : null}
|
? `/app?${buildQuery({ order: data.order, page: (data.page ?? 1) - 1 })}`
|
||||||
/>
|
: null}
|
||||||
|
nextHref={data.hasMore
|
||||||
|
? `/app?${buildQuery({ order: data.order, page: (data.page ?? 1) + 1 })}`
|
||||||
|
: null}
|
||||||
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<h2 class="section-title">Sin responsable de mis grupos</h2>
|
<h2 class="section-title">Sin responsable de mis grupos</h2>
|
||||||
{#if unassignedOpen.length === 0}
|
{#if unassignedOpen.length === 0}
|
||||||
<p>No hay tareas sin responsable en tus grupos. Crea una nueva o invita a tus compañeros.</p>
|
<p>
|
||||||
|
No hay tareas sin responsable en tus grupos. Crea una nueva o invita a
|
||||||
|
alguien.
|
||||||
|
</p>
|
||||||
|
{:else if data.order === "group"}
|
||||||
|
{#each groupByGroup(unassignedOpen) as g (g.id)}
|
||||||
|
<h3 class="group-subtitle">{g.name}</h3>
|
||||||
|
<Card>
|
||||||
|
<ul class="list">
|
||||||
|
{#each g.tasks as t (t.id)}
|
||||||
|
<TaskItem
|
||||||
|
{...t}
|
||||||
|
currentUserId={data.userId}
|
||||||
|
groupName={g.name}
|
||||||
|
groupId={t.group_id}
|
||||||
|
on:changed={(e) => updateTaskInLists(e.detail)}
|
||||||
|
/>
|
||||||
|
{/each}
|
||||||
|
</ul>
|
||||||
|
</Card>
|
||||||
|
{/each}
|
||||||
{:else}
|
{:else}
|
||||||
{#if data.order === 'group'}
|
<Card>
|
||||||
{#each groupByGroup(unassignedOpen) as g (g.id)}
|
<ul class="list">
|
||||||
<h3 class="group-subtitle">{g.name}</h3>
|
{#each unassignedOpen as t (t.id)}
|
||||||
<Card>
|
<TaskItem
|
||||||
<ul class="list">
|
{...t}
|
||||||
{#each g.tasks as t (t.id)}
|
currentUserId={data.userId}
|
||||||
<TaskItem {...t} currentUserId={data.userId} groupName={g.name} groupId={t.group_id} on:changed={(e) => updateTaskInLists(e.detail)} />
|
groupName={t.group_id
|
||||||
{/each}
|
? data.groupNames[t.group_id] || t.group_id
|
||||||
</ul>
|
: "Personal"}
|
||||||
</Card>
|
groupId={t.group_id}
|
||||||
{/each}
|
on:changed={(e) => updateTaskInLists(e.detail)}
|
||||||
{:else}
|
/>
|
||||||
<Card>
|
{/each}
|
||||||
<ul class="list">
|
</ul>
|
||||||
{#each unassignedOpen as t (t.id)}
|
</Card>
|
||||||
<TaskItem {...t} currentUserId={data.userId} groupName={t.group_id ? (data.groupNames[t.group_id] || t.group_id) : 'Personal'} groupId={t.group_id} on:changed={(e) => updateTaskInLists(e.detail)} />
|
|
||||||
{/each}
|
|
||||||
</ul>
|
|
||||||
</Card>
|
|
||||||
{/if}
|
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<h2 class="section-title">Completadas (últimas 24 h)</h2>
|
<h2 class="section-title">Completadas (últimas 24 h)</h2>
|
||||||
{#if recentTasks.length === 0}
|
{#if recentTasks.length === 0}
|
||||||
<p>No hay tareas completadas recientemente.</p>
|
<p>No hay tareas completadas recientemente.</p>
|
||||||
{:else}
|
{:else}
|
||||||
<Card>
|
<Card>
|
||||||
<ul class="list">
|
<ul class="list">
|
||||||
{#each recentTasks as t (t.id)}
|
{#each recentTasks as t (t.id)}
|
||||||
<TaskItem
|
<TaskItem
|
||||||
{...t}
|
{...t}
|
||||||
currentUserId={data.userId}
|
currentUserId={data.userId}
|
||||||
groupId={t.group_id}
|
groupId={t.group_id}
|
||||||
completed={true}
|
completed={true}
|
||||||
completed_at={t.completed_at ?? null}
|
completed_at={t.completed_at ?? null}
|
||||||
groupName={t.group_id ? (data.groupNames[t.group_id] || t.group_id) : 'Personal'}
|
groupName={t.group_id
|
||||||
on:changed={(e) => updateTaskInLists(e.detail)}
|
? data.groupNames[t.group_id] || t.group_id
|
||||||
/>
|
: "Personal"}
|
||||||
{/each}
|
on:changed={(e) => updateTaskInLists(e.detail)}
|
||||||
</ul>
|
/>
|
||||||
</Card>
|
{/each}
|
||||||
|
</ul>
|
||||||
|
</Card>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<p class="footnote">
|
|
||||||
La cookie de sesión se renueva con cada visita (idle timeout).
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.subtle {
|
.subtle {
|
||||||
color: var(--color-text-muted);
|
color: var(--color-text-muted);
|
||||||
margin: 0 0 1rem 0;
|
margin: 0 0 1rem 0;
|
||||||
}
|
}
|
||||||
.order-toggle {
|
.order-toggle {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin-bottom: 0.5rem;
|
margin-bottom: 0.5rem;
|
||||||
}
|
}
|
||||||
.order-toggle a {
|
.order-toggle a {
|
||||||
padding: 2px 8px;
|
padding: 2px 8px;
|
||||||
border: 1px solid var(--color-border);
|
border: 1px solid var(--color-border);
|
||||||
border-radius: 999px;
|
border-radius: 999px;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
color: var(--color-text);
|
color: var(--color-text);
|
||||||
}
|
}
|
||||||
.order-toggle a.active {
|
.order-toggle a.active {
|
||||||
background: var(--color-primary);
|
background: var(--color-primary);
|
||||||
border-color: var(--color-primary);
|
border-color: var(--color-primary);
|
||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
.section-title {
|
.section-title {
|
||||||
margin: 0.5rem 0;
|
margin: 0.5rem 0;
|
||||||
}
|
}
|
||||||
.group-subtitle {
|
.group-subtitle {
|
||||||
margin: 0.5rem 0 0.25rem 0;
|
margin: 0.5rem 0 0.25rem 0;
|
||||||
font-size: 0.95rem;
|
font-size: 0.95rem;
|
||||||
}
|
}
|
||||||
.list {
|
.list {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
list-style: none;
|
list-style: none;
|
||||||
}
|
}
|
||||||
.footnote {
|
.footnote {
|
||||||
margin-top: 0.75rem;
|
margin-top: 0.75rem;
|
||||||
color: var(--color-text-muted);
|
color: var(--color-text-muted);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Loading…
Reference in New Issue