Skip to content

Commit 51281b2

Browse files
committed
Display categories, tags and locations in sources
Add category, tag and location data throughout the app and show them in source lists. - API: admin sources list now returns category_id, location_ids (array), and tag_ids (array) via subqueries. searchSources ordering changed to use created_at DESC, id DESC. - Types: AdminSourceListing extended with category_id, tag_ids and location_ids. - DB: add created_at column to source table (default NOW()). - UI: admin SourceView loads categories, tags and locations and adds columns for category, locations and tags. SourceTable now supports categories, highlights for matching tag/location IDs via a new highlightSettings prop, and adjusts column widths. saltiniai page passes highlightSettings to SourceTable. These changes enable name-resolution for related IDs in the UI, highlighting of matched filters, and ordering search results by recency.
1 parent f8a2717 commit 51281b2

7 files changed

Lines changed: 136 additions & 30 deletions

File tree

app/admin/sources/SourceView.tsx

Lines changed: 63 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,15 @@ import { Button } from '@/components/ui/button';
2525
import { ArrowLeft, SearchIcon, SquarePenIcon } from 'lucide-react';
2626
import { Group, GroupSeparator, GroupText } from '@/components/ui/group';
2727
import { InputGroup, InputGroupAddon, InputGroupInput } from '@/components/ui/input-group';
28-
import { useAdminSourceListings } from '@/hooks/dataFetching';
29-
import { Dialog, DialogClose, DialogDescription, DialogFooter, DialogHeader, DialogPanel, DialogPopup, DialogTitle, DialogTrigger } from '@/components/ui/dialog';
28+
import { useAdminSourceListings, useLocations, useSourceCategories, useSourceTags } from '@/hooks/dataFetching';
29+
import { Dialog, DialogClose, DialogFooter, DialogHeader, DialogPanel, DialogPopup, DialogTitle, DialogTrigger } from '@/components/ui/dialog';
3030
import { EditSourceForm } from '@/components/admin/editSourceForm';
3131

3232
export default function SourceView() {
3333
const { data: sources, isLoading: sourcesLoading } = useAdminSourceListings();
34+
const { data: categories } = useSourceCategories();
35+
const { data: tags } = useSourceTags();
36+
const { data: locations } = useLocations();
3437

3538
const columnHelper = createColumnHelper<AdminSourceListing>();
3639

@@ -51,6 +54,64 @@ export default function SourceView() {
5154
</div>
5255
),
5356
}),
57+
columnHelper.accessor('category_id', {
58+
header: 'Kategorija',
59+
cell: info => {
60+
const categoryId = info.getValue();
61+
62+
const categoryName = categories?.find(cat => cat.id === categoryId)?.name || "—";
63+
64+
return <span>{categoryName}</span>;
65+
},
66+
}),
67+
columnHelper.accessor('location_ids', {
68+
header: 'Vietovardžiai',
69+
cell: info => {
70+
const locationIds = info.getValue();
71+
72+
if (!locationIds || !locations) return <span></span>;
73+
74+
const locationNames = locationIds
75+
.map(id => locations.find(loc => loc.id === id)?.name)
76+
.filter(Boolean);
77+
78+
return (
79+
<div className="flex flex-wrap gap-1">
80+
{locationNames.length > 0 ? locationNames.map((name, idx) => (
81+
<span
82+
key={idx}
83+
className="px-2 py-0.5 rounded-full text-xs bg-blue-100 text-blue-800"
84+
>
85+
{name}
86+
</span>
87+
)) : <span></span>}
88+
</div>
89+
);
90+
},
91+
}),
92+
columnHelper.accessor('tag_ids', {
93+
header: 'Žymos',
94+
cell: info => {
95+
const tagIds = info.getValue();
96+
97+
const tagNames = tagIds && tags
98+
? tagIds.map(id => tags.find(tag => tag.id == id)?.name).filter(Boolean)
99+
: [];
100+
101+
return (
102+
<div className="flex flex-wrap gap-1">
103+
{tagNames.length > 0 ? tagNames.map((name, idx) => (
104+
<span
105+
key={idx}
106+
className="px-2 py-0.5 rounded-full text-xs bg-green-100 text-green-800"
107+
>
108+
{name}
109+
</span>
110+
)) : <span></span>}
111+
</div>
112+
);
113+
},
114+
}),
54115
columnHelper.accessor('state', {
55116
header: '',
56117
cell: info => (

app/api/admin/sources/list/route.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import { pool } from "@/lib/db";
22
import { authOptions } from "@/lib/security/auth";
33
import { AdminSourceListing, hasPermission, SourceState } from "@/types";
44
import { getServerSession } from "next-auth";
5-
import { cacheTag } from "next/cache";
65

76
async function getData(): Promise<AdminSourceListing[]> {
87
//"use cache: remote";
@@ -12,7 +11,10 @@ async function getData(): Promise<AdminSourceListing[]> {
1211
SELECT
1312
s.id,
1413
s.title,
15-
s.description
14+
s.description,
15+
s.category_id,
16+
ARRAY(SELECT location_id FROM source_locations WHERE source_id = s.id) AS location_ids,
17+
ARRAY(SELECT tag_id FROM source_tags WHERE source_id = s.id) AS tag_ids
1618
1719
FROM source s
1820
ORDER BY s.id ASC
@@ -22,6 +24,9 @@ async function getData(): Promise<AdminSourceListing[]> {
2224
id: row.id,
2325
title: row.title,
2426
description: row.description,
27+
category_id: row.category_id,
28+
location_ids: row.location_ids,
29+
tag_ids: row.tag_ids,
2530
state: "OK" as SourceState,
2631
})));
2732
}

app/api/searchSources/route.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ function buildSearchQuery(params: SearchSourcesRequest): { query: string; args:
7373
7474
WHERE ${filterQuery}
7575
76-
ORDER BY s.id ASC
76+
ORDER BY s.created_at DESC, s.id DESC
7777
LIMIT ${pageSize} OFFSET ${pageOffset}
7878
`;
7979

app/saltiniai/page.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,11 @@ export default function SourcesPage() {
168168
<SourceTable
169169
className="hidden md:table"
170170
displayData={allSources}
171+
highlightSettings={{
172+
textQuery: filterText,
173+
tagIds: selectedTags,
174+
locationIds: selectedLocations.map(loc => Number(loc)),
175+
}}
171176
/>
172177

173178
<MobileSourceView

components/sources/SourceTable.tsx

Lines changed: 53 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,18 @@ import { Button } from "../ui/button";
55
import { MessageCircleIcon } from "lucide-react";
66
import { Dialog, DialogPopup, DialogTrigger } from "../ui/dialog";
77
import SourceReportForm from "./SourceReportForm";
8-
import { useSourceTags, useLocations } from "@/hooks/dataFetching";
8+
import { useSourceTags, useLocations, useSourceCategories } from "@/hooks/dataFetching";
9+
10+
interface HighlightSettings {
11+
textQuery?: string;
12+
tagIds?: number[];
13+
locationIds?: number[];
14+
}
915

1016
interface SourceTableProps {
1117
className?: string;
1218
displayData: SearchSourcesResponseItem[];
19+
highlightSettings?: HighlightSettings;
1320
}
1421

1522
function ExpandableCell({
@@ -26,37 +33,21 @@ function ExpandableCell({
2633
);
2734
}
2835

29-
export function SourceTable({ className, displayData }: SourceTableProps) {
36+
export function SourceTable({ className, displayData, highlightSettings }: SourceTableProps) {
3037
const [expandedRowId, setExpandedRowId] = useState<number | null>(null);
3138

3239
// Load tags and locations for ID-to-name mapping
3340
const { data: tags } = useSourceTags();
3441
const { data: locations } = useLocations();
35-
36-
// Helper functions to map IDs to names
37-
const getLocationNames = (locationIds: number[]) => {
38-
if (!locations || !locationIds?.length) return "—";
39-
40-
return locationIds
41-
.map(id => locations.find(loc => loc.id == id)?.name)
42-
.filter(Boolean)
43-
.join(", ") || "—";
44-
};
45-
46-
const getTagNames = (tagIds: number[]) => {
47-
if (!tags || !tagIds?.length) return "—";
48-
return tagIds
49-
.map(id => tags.find(tag => tag.id === id)?.name)
50-
.filter(Boolean)
51-
.join(", ") || "—";
52-
};
42+
const { data: categories } = useSourceCategories();
5343

5444
return (
5545
<Table className={`w-full table-fixed ${className}`}>
5646
<TableHeader>
5747
<TableRow>
58-
<TableHead className="w-[25%]">Pavadinimas</TableHead>
59-
<TableHead className="w-[40%]">Aprašymas</TableHead>
48+
<TableHead className="w-[20%]">Pavadinimas</TableHead>
49+
<TableHead className="w-[35%]">Aprašymas</TableHead>
50+
<TableHead className="w-[10%] hidden xl:table-cell">Kategorija</TableHead>
6051
<TableHead className="w-[10%] hidden xl:table-cell">Vietovardžiai</TableHead>
6152
<TableHead className="w-[10%] hidden xl:table-cell">Žymos</TableHead>
6253
<TableHead className="w-[10%]">Nuoroda</TableHead>
@@ -96,13 +87,51 @@ export function SourceTable({ className, displayData }: SourceTableProps) {
9687

9788
<TableCell className="overflow-hidden hidden xl:table-cell">
9889
<ExpandableCell expanded={expanded}>
99-
{getLocationNames(item.location_ids)}
90+
{item.category_id && categories ? (
91+
categories.find(cat => cat.id === item.category_id)?.name || "—"
92+
) : "—"}
93+
</ExpandableCell>
94+
</TableCell>
95+
96+
<TableCell className="overflow-hidden hidden xl:table-cell">
97+
<ExpandableCell expanded={expanded}>
98+
{item.location_ids && locations ? (
99+
item.location_ids
100+
.map(id => locations.find(loc => loc.id == id)?.name)
101+
.filter(Boolean)
102+
.map((name, index) => (
103+
<span key={index}>
104+
{highlightSettings?.locationIds?.includes(item.location_ids[index]) ? (
105+
<span className="bg-yellow-200">{name}</span>
106+
) : (
107+
name
108+
)}
109+
{index < item.location_ids.length - 1 && ", "}
110+
</span>
111+
))
112+
) : "—"}
100113
</ExpandableCell>
101114
</TableCell>
102115

103116
<TableCell className="overflow-hidden hidden xl:table-cell">
104117
<ExpandableCell expanded={expanded}>
105-
{getTagNames(item.tag_ids)}
118+
{item.tag_ids && tags ? (
119+
item.tag_ids
120+
.map(id => tags.find(tag => tag.id === id)?.name)
121+
.filter(Boolean)
122+
.map((name, index) => (
123+
<span key={index}>
124+
{highlightSettings?.tagIds?.includes(item.tag_ids[index]) ? (
125+
<span className="bg-yellow-200">{name}</span>
126+
) : (
127+
name
128+
)}
129+
{index < item.tag_ids.length - 1 && ", "}
130+
</span>
131+
))
132+
) : (
133+
"—"
134+
)}
106135
</ExpandableCell>
107136
</TableCell>
108137

database_schema.sql

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,12 @@ CREATE TABLE IF NOT EXISTS source(
3333
title text NOT NULL,
3434
description text,
3535
link text,
36-
category_id bigint REFERENCES source_category(id) ON DELETE SET NULL
36+
category_id bigint REFERENCES source_category(id) ON DELETE SET NULL,
3737
);
3838

39+
ALTER TABLE source
40+
ADD COLUMN IF NOT EXISTS created_at timestamptz NOT NULL DEFAULT NOW();
41+
3942
ALTER TABLE source
4043
ADD COLUMN IF NOT EXISTS title_en text;
4144

types.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@ export interface AdminSourceListing {
4141
id: number;
4242
title: string;
4343
description: string;
44+
category_id: number | null;
45+
tag_ids: number[];
46+
location_ids: number[];
4447
state: SourceState;
4548
};
4649

0 commit comments

Comments
 (0)