feat: basic search functionality for feed table

This commit is contained in:
Corban-Lee Jones 2025-04-26 22:52:03 +01:00
parent 48cd87749e
commit f8724162ad
6 changed files with 57 additions and 52 deletions

View File

@ -17,6 +17,7 @@ app.engine("ejs", engine);
app.set("view engine", "ejs");
app.set("views", path.resolve(__dirname, "client/views"));
app.use(express.urlencoded({ extended: true }));
app.use(express.json());
app.use("/static", express.static(path.resolve(__dirname, "client/public")));

View File

@ -46,8 +46,7 @@
}
.cj-table-footer {
@apply px-6 py-4 gap-3 flex justify-between items-center border-t
border-gray-200 dark:border-neutral-700;
@apply px-6 py-4 gap-3 flex justify-between items-center;
}
.cj-table-paging-btn {

View File

@ -23,7 +23,8 @@ const emptyTableHtml: string = `
No results found
</h2>
<p class="mt-2 text-sm text-gray-600 dark:text-neutral-400">
Create a feed and it will appear here.
Refine your search or create a new feed.
Alternatively, use a template to deploy a ready-made feed.
</p>
<div class="mt-5 flex flex-col sm:flex-row gap-2">
@ -108,8 +109,8 @@ const columnDefs: ConfigColumnDefs[] = [
{
target: 5,
data: "message_style",
orderable: true,
searchable: true,
orderable: false, // both should be true, but message_style doesnt exist yet
searchable: false,
render(data: string) { return `
<td class="size-px whitespace-nowrap align-top">
<div class="px-6 py-4">
@ -154,10 +155,13 @@ const columnDefs: ConfigColumnDefs[] = [
const ajaxSettings: AjaxSettings = {
url: `/guild/${1204426362794811453}/feeds/api/datatable`,
type: "POST",
contentType: "application/json",
dataSrc: "data",
data: (data: unknown) => {
if (data === undefined) return;
// TODO
// TODO,
return JSON.stringify(data);
}
};

View File

@ -9,8 +9,28 @@
<div class="bg-white border border-gray-200 rounded-lg shadow-xs dark:bg-neutral-900 dark:border-neutral-700">
<!-- Header -->
<div>
placeholder header content
<div class="px-6 py-4 gap-3 flex flex-nowrap justify-between items-center">
<div class="hidden sm:block sm:col-span-1">
<label for="search" class="sr-only">Search</label>
<div class="relative">
<input type="text" id="search" class="form-input px-3 ps-11 block w-full border-gray-200 rounded-lg text-sm focus:border-blue-500 focus:ring-blue-500 disabled:opacity-50 disabled:pointer-events-none dark:bg-neutral-900 dark:border-neutral-700 dark:text-neutral-400 dark:placeholder-neutral-500 dark:focus:ring-neutral-600" placeholder="Search" data-hs-datatable-search="">
<div class="absolute inset-y-0 start-0 flex items-center pointer-events-none ps-4">
<svg class="shrink-0 size-4 text-gray-400 dark:text-neutral-500" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/></svg>
</div>
</div>
</div>
<div class="sm:col-span-2">
<button type="button" class="py-2 px-3 inline-flex items-center gap-x-2 text-sm font-medium rounded-lg border border-transparent bg-blue-600 text-white hover:bg-blue-700 focus:outline-hidden focus:bg-blue-700 disabled:opacity-50 disabled:pointer-events-none">
<svg class="shrink-0 size-4" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14"/><path d="M12 5v14"/></svg>
<span>
Create
<span class="hidden sm:inline">a feed</span>
</span>
</button>
</div>
</div>
<!-- Table -->
@ -125,7 +145,7 @@
<svg class="shrink-0 size-4" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m15 18-6-6 6-6"/></svg>
Prev
</button>
<div class="flex items-center space-x-1 " data-hs-datatable-paging-pages=""></div>
<div class="flex items-center space-x-1" data-hs-datatable-paging-pages=""></div>
<button type="button" class="cj-table-paging-btn" data-hs-datatable-paging-next="">
Next
<svg class="shrink-0 size-4" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m9 18 6-6-6-6"/></svg>

View File

@ -1,5 +1,6 @@
import { Request, Response } from "express";
import prisma, { Prisma } from "@server/prisma";
import { AjaxData, AjaxResponse } from "datatables.net-dt";
export const get = async (request: Request, response: Response) => {
if (!request.query.id) {
@ -76,61 +77,41 @@ export const del = async (request: Request, response: Response) => {
response.status(204).json(null);
};
interface DataTableResponse {
data: any;
recordsFiltered: number;
recordsTotal: number;
}
interface DatatableQuery {
length: string;
start: string;
order: { column: string; dir: string }[];
columns: { [key: string]: { data: string; searchable: string }};
search: { value: string };
interface DatatableQuery extends AjaxData {
filters: { [key: string]: any };
}
export const datatable = async (request: Request, response: Response) => {
const query = request.query as unknown as DatatableQuery;
const query = request.body as unknown as DatatableQuery;
const size: number = Number(query.length) || 10;
const start: number = Number(query.start);
const order: string = (query.order && query.columns[query.order[0].column].data) || "id";
const direction: string = (query.order && query.order[0].dir) || "asc";
const search: string = query.search?.value || "";
const orderBy: Prisma.FeedOrderByWithRelationInput = query.order?.length
? { [query.columns[query.order[0].column].data]: query.order[0].dir }
: { id: "asc" };
let dbQuery: any = {};
// TODO: filter request
if (search) {
Object.values(query.columns)
.filter(column => column.searchable === "true")
.forEach((col: any) => {
dbQuery["where"][col.data] = {
contains: search,
mode: "insensitive"
}
}
);
}
const orderBy: any = {};
orderBy[order] = direction;
const where: Prisma.FeedWhereInput = query.search?.value
? {
OR: Object.values(query.columns)
.filter(col => col.searchable)
.map(col => ({
[col.data]: { contains: query.search.value }
})) as Prisma.FeedWhereInput[]
}
: {};
const data = await prisma.feed.findMany({
...dbQuery,
skip: start,
take: size,
skip: query.start,
take: query.length,
orderBy: orderBy,
include: { channels: true }
where: where,
include: { channels: true },
});
response.json(<DataTableResponse>{
response.json(<AjaxResponse>{
data: data,
recordsFiltered: await prisma.feed.count({...dbQuery}),
recordsTotal: await prisma.feed.count()
recordsFiltered: await prisma.feed.count({ where: where }),
recordsTotal: await prisma.feed.count(),
draw: query.draw
});
};

View File

@ -24,7 +24,7 @@ router.get("/:guildId/content", contentController.get);
// API routes
router.get("/:guildId/feeds/api/datatable", feedApiController.datatable);
router.post("/:guildId/feeds/api/datatable", feedApiController.datatable);
router.get("/:guildId/feeds/api", feedApiController.get);
router.post("/:guildId/feeds/api", feedApiController.post);
router.patch("/:guildId/feeds/api", feedApiController.patch);