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("view engine", "ejs");
|
||||||
app.set("views", path.resolve(__dirname, "client/views"));
|
app.set("views", path.resolve(__dirname, "client/views"));
|
||||||
app.use(express.urlencoded({ extended: true }));
|
app.use(express.urlencoded({ extended: true }));
|
||||||
|
app.use(express.json());
|
||||||
|
|
||||||
app.use("/static", express.static(path.resolve(__dirname, "client/public")));
|
app.use("/static", express.static(path.resolve(__dirname, "client/public")));
|
||||||
|
|
||||||
|
@ -46,8 +46,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.cj-table-footer {
|
.cj-table-footer {
|
||||||
@apply px-6 py-4 gap-3 flex justify-between items-center border-t
|
@apply px-6 py-4 gap-3 flex justify-between items-center;
|
||||||
border-gray-200 dark:border-neutral-700;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.cj-table-paging-btn {
|
.cj-table-paging-btn {
|
||||||
|
@ -23,7 +23,8 @@ const emptyTableHtml: string = `
|
|||||||
No results found
|
No results found
|
||||||
</h2>
|
</h2>
|
||||||
<p class="mt-2 text-sm text-gray-600 dark:text-neutral-400">
|
<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>
|
</p>
|
||||||
|
|
||||||
<div class="mt-5 flex flex-col sm:flex-row gap-2">
|
<div class="mt-5 flex flex-col sm:flex-row gap-2">
|
||||||
@ -108,8 +109,8 @@ const columnDefs: ConfigColumnDefs[] = [
|
|||||||
{
|
{
|
||||||
target: 5,
|
target: 5,
|
||||||
data: "message_style",
|
data: "message_style",
|
||||||
orderable: true,
|
orderable: false, // both should be true, but message_style doesnt exist yet
|
||||||
searchable: true,
|
searchable: false,
|
||||||
render(data: string) { return `
|
render(data: string) { return `
|
||||||
<td class="size-px whitespace-nowrap align-top">
|
<td class="size-px whitespace-nowrap align-top">
|
||||||
<div class="px-6 py-4">
|
<div class="px-6 py-4">
|
||||||
@ -154,10 +155,13 @@ const columnDefs: ConfigColumnDefs[] = [
|
|||||||
|
|
||||||
const ajaxSettings: AjaxSettings = {
|
const ajaxSettings: AjaxSettings = {
|
||||||
url: `/guild/${1204426362794811453}/feeds/api/datatable`,
|
url: `/guild/${1204426362794811453}/feeds/api/datatable`,
|
||||||
|
type: "POST",
|
||||||
|
contentType: "application/json",
|
||||||
dataSrc: "data",
|
dataSrc: "data",
|
||||||
data: (data: unknown) => {
|
data: (data: unknown) => {
|
||||||
if (data === undefined) return;
|
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">
|
<div class="bg-white border border-gray-200 rounded-lg shadow-xs dark:bg-neutral-900 dark:border-neutral-700">
|
||||||
|
|
||||||
<!-- Header -->
|
<!-- Header -->
|
||||||
<div>
|
<div class="px-6 py-4 gap-3 flex flex-nowrap justify-between items-center">
|
||||||
placeholder header content
|
|
||||||
|
<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>
|
</div>
|
||||||
|
|
||||||
<!-- Table -->
|
<!-- 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>
|
<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
|
Prev
|
||||||
</button>
|
</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="">
|
<button type="button" class="cj-table-paging-btn" data-hs-datatable-paging-next="">
|
||||||
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>
|
<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 { Request, Response } from "express";
|
||||||
import prisma, { Prisma } from "@server/prisma";
|
import prisma, { Prisma } from "@server/prisma";
|
||||||
|
import { AjaxData, AjaxResponse } from "datatables.net-dt";
|
||||||
|
|
||||||
export const get = async (request: Request, response: Response) => {
|
export const get = async (request: Request, response: Response) => {
|
||||||
if (!request.query.id) {
|
if (!request.query.id) {
|
||||||
@ -76,61 +77,41 @@ export const del = async (request: Request, response: Response) => {
|
|||||||
response.status(204).json(null);
|
response.status(204).json(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
interface DataTableResponse {
|
|
||||||
data: any;
|
|
||||||
recordsFiltered: number;
|
|
||||||
recordsTotal: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface DatatableQuery {
|
interface DatatableQuery extends AjaxData {
|
||||||
length: string;
|
|
||||||
start: string;
|
|
||||||
order: { column: string; dir: string }[];
|
|
||||||
columns: { [key: string]: { data: string; searchable: string }};
|
|
||||||
search: { value: string };
|
|
||||||
filters: { [key: string]: any };
|
filters: { [key: string]: any };
|
||||||
}
|
}
|
||||||
|
|
||||||
export const datatable = async (request: Request, response: Response) => {
|
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 orderBy: Prisma.FeedOrderByWithRelationInput = query.order?.length
|
||||||
const start: number = Number(query.start);
|
? { [query.columns[query.order[0].column].data]: query.order[0].dir }
|
||||||
const order: string = (query.order && query.columns[query.order[0].column].data) || "id";
|
: { id: "asc" };
|
||||||
const direction: string = (query.order && query.order[0].dir) || "asc";
|
|
||||||
const search: string = query.search?.value || "";
|
|
||||||
|
|
||||||
let dbQuery: any = {};
|
const where: Prisma.FeedWhereInput = query.search?.value
|
||||||
|
? {
|
||||||
// TODO: filter request
|
OR: Object.values(query.columns)
|
||||||
|
.filter(col => col.searchable)
|
||||||
if (search) {
|
.map(col => ({
|
||||||
Object.values(query.columns)
|
[col.data]: { contains: query.search.value }
|
||||||
.filter(column => column.searchable === "true")
|
})) as Prisma.FeedWhereInput[]
|
||||||
.forEach((col: any) => {
|
}
|
||||||
dbQuery["where"][col.data] = {
|
: {};
|
||||||
contains: search,
|
|
||||||
mode: "insensitive"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const orderBy: any = {};
|
|
||||||
orderBy[order] = direction;
|
|
||||||
|
|
||||||
const data = await prisma.feed.findMany({
|
const data = await prisma.feed.findMany({
|
||||||
...dbQuery,
|
skip: query.start,
|
||||||
skip: start,
|
take: query.length,
|
||||||
take: size,
|
|
||||||
orderBy: orderBy,
|
orderBy: orderBy,
|
||||||
include: { channels: true }
|
where: where,
|
||||||
|
include: { channels: true },
|
||||||
});
|
});
|
||||||
|
|
||||||
response.json(<DataTableResponse>{
|
response.json(<AjaxResponse>{
|
||||||
data: data,
|
data: data,
|
||||||
recordsFiltered: await prisma.feed.count({...dbQuery}),
|
recordsFiltered: await prisma.feed.count({ where: where }),
|
||||||
recordsTotal: await prisma.feed.count()
|
recordsTotal: await prisma.feed.count(),
|
||||||
|
draw: query.draw
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -24,7 +24,7 @@ router.get("/:guildId/content", contentController.get);
|
|||||||
|
|
||||||
// API routes
|
// 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.get("/:guildId/feeds/api", feedApiController.get);
|
||||||
router.post("/:guildId/feeds/api", feedApiController.post);
|
router.post("/:guildId/feeds/api", feedApiController.post);
|
||||||
router.patch("/:guildId/feeds/api", feedApiController.patch);
|
router.patch("/:guildId/feeds/api", feedApiController.patch);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user