108 lines
5.3 KiB
TypeScript
108 lines
5.3 KiB
TypeScript
"use client";
|
|
|
|
import type { Entry } from "@/lib/shared/types";
|
|
|
|
type EntriesListProps = {
|
|
activeGroupId: number | null;
|
|
loading: boolean;
|
|
entries: Entry[];
|
|
visibleEntries: Entry[];
|
|
activeFilterCount: number;
|
|
onOpenDetails: (entry: Entry, index: number) => void;
|
|
onClearFilters: () => void;
|
|
};
|
|
|
|
export default function EntriesList({
|
|
activeGroupId,
|
|
loading,
|
|
entries,
|
|
visibleEntries,
|
|
activeFilterCount,
|
|
onOpenDetails,
|
|
onClearFilters
|
|
}: EntriesListProps) {
|
|
return (
|
|
<div className="mt-3 space-y-2">
|
|
{!activeGroupId ? (
|
|
<div className="text-sm text-muted">Select a group to view entries.</div>
|
|
) : loading ? (
|
|
<div className="space-y-2">
|
|
{[0, 1, 2].map(row => (
|
|
<div key={row} className="rounded-lg border border-accent-weak bg-panel px-3 py-3">
|
|
<div className="animate-pulse space-y-2">
|
|
<div className="h-4 w-28 rounded bg-surface" />
|
|
<div className="h-3 w-40 rounded bg-surface" />
|
|
<div className="flex flex-wrap gap-2">
|
|
<div className="h-5 w-14 rounded-full bg-surface" />
|
|
<div className="h-5 w-12 rounded-full bg-surface" />
|
|
<div className="h-5 w-16 rounded-full bg-surface" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
) : entries.length ? (
|
|
visibleEntries.length ? (
|
|
visibleEntries.map((entry, index) => {
|
|
const tags = entry.tags ?? [];
|
|
const mobileTagLimit = 2;
|
|
const mobileTags = tags.slice(0, mobileTagLimit);
|
|
const extraTagCount = Math.max(tags.length - mobileTagLimit, 0);
|
|
|
|
return (
|
|
<div
|
|
key={entry.id}
|
|
className="flex items-center justify-between rounded-lg border border-accent-weak bg-panel px-3 py-2 text-sm transition hover:border-accent hover:bg-accent-soft"
|
|
onClick={() => onOpenDetails(entry, index)}
|
|
>
|
|
<div className="flex flex-col gap-1">
|
|
<div className="text-base font-semibold">${entry.amountDollars.toFixed(2)}</div>
|
|
<div className="text-xs text-muted">
|
|
{new Date(entry.occurredAt).toISOString().slice(0, 10)} - {entry.necessity}
|
|
</div>
|
|
</div>
|
|
{tags.length ? (
|
|
<>
|
|
<div className="flex flex-wrap justify-end gap-2 md:hidden">
|
|
{mobileTags.map(tag => (
|
|
<span key={tag} className="rounded-full border border-accent-weak bg-accent-soft px-2 py-0.5 text-xs">
|
|
#{tag}
|
|
</span>
|
|
))}
|
|
{extraTagCount ? (
|
|
<span className="rounded-full border border-accent-weak bg-panel px-2 py-0.5 text-xs text-soft">
|
|
{extraTagCount} more...
|
|
</span>
|
|
) : null}
|
|
</div>
|
|
<div className="hidden flex-wrap justify-end gap-2 md:flex">
|
|
{tags.map(tag => (
|
|
<span key={tag} className="rounded-full border border-accent-weak bg-accent-soft px-2 py-0.5 text-xs">
|
|
#{tag}
|
|
</span>
|
|
))}
|
|
</div>
|
|
</>
|
|
) : (
|
|
<span className="rounded-full border border-accent-weak px-2 py-0.5 text-xs text-soft">No tags</span>
|
|
)}
|
|
</div>
|
|
);
|
|
})
|
|
) : (
|
|
<div className="space-y-2 text-sm text-muted">
|
|
<div>No matching entries.</div>
|
|
{activeFilterCount ? (
|
|
<button type="button" className="rounded-lg btn-outline-accent px-3 py-1 text-xs" onClick={onClearFilters}>
|
|
Clear filters
|
|
</button>
|
|
) : null}
|
|
</div>
|
|
)
|
|
) : (
|
|
<div className="text-sm text-muted">No entries yet.</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|