feat: basic search functionality for feed table
This commit is contained in:
parent
48cd87749e
commit
f8724162ad
@ -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")));
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -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>
|
||||
|
@ -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
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -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);
|
||||
|
Loading…
x
Reference in New Issue
Block a user