feat(settings): final migration of settings and edits added

This commit is contained in:
2025-11-25 14:36:06 -06:00
parent 3193e07e47
commit 7e15e5d7bc
11 changed files with 760 additions and 522 deletions

View File

@@ -0,0 +1,18 @@
import { keepPreviousData, queryOptions } from "@tanstack/react-query";
import axios from "axios";
export function getSettings() {
return queryOptions({
queryKey: ["getSettings"],
queryFn: () => fetchSession(),
staleTime: 5000,
refetchOnWindowFocus: true,
placeholderData: keepPreviousData,
});
}
const fetchSession = async () => {
const { data } = await axios.get("/lst/api/system/settings");
return data.data;
};

View File

@@ -0,0 +1,26 @@
import { createColumnHelper } from "@tanstack/react-table";
import { ArrowDown, ArrowUp } from "lucide-react";
import { Button } from "@/components/ui/button";
export const GenericColumn = ({ columnName }: { columnName: string }) => {
const columnHelper = createColumnHelper();
return columnHelper.accessor(`${columnName}`, {
header: ({ column }) => {
return (
<Button
variant="ghost"
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
>
<span className="flex flex-row gap-2">{`${columnName.toUpperCase()}`}</span>
{column.getIsSorted() === "asc" ? (
<ArrowUp className="ml-2 h-4 w-4" />
) : (
<ArrowDown className="ml-2 h-4 w-4" />
)}
</Button>
);
},
cell: (i) => i.getValue(),
});
};

View File

@@ -1,6 +1,7 @@
import {
flexRender,
getCoreRowModel,
getFilteredRowModel,
getPaginationRowModel,
getSortedRowModel,
type SortingState,
@@ -26,6 +27,9 @@ export default function TableNoExpand({
columns: any;
}) {
const [sorting, setSorting] = useState<SortingState>([]);
// const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(
// []
// )
const table = useReactTable({
data,
columns,
@@ -33,11 +37,14 @@ export default function TableNoExpand({
getPaginationRowModel: getPaginationRowModel(),
onSortingChange: setSorting,
getSortedRowModel: getSortedRowModel(),
getFilteredRowModel: getFilteredRowModel(),
//renderSubComponent: ({ row }: { row: any }) => <ExpandedRow row={row} />,
//getRowCanExpand: () => true,
filterFns: {},
state: {
sorting,
//columnFilters
},
});
return (

View File

@@ -1,7 +1,31 @@
import { createFileRoute, Link, Outlet } from "@tanstack/react-router";
import {
createFileRoute,
Link,
Outlet,
redirect,
} from "@tanstack/react-router";
import { checkUserAccess } from "@/lib/authClient";
export const Route = createFileRoute("/_app/_adminLayout/admin/_system")({
component: RouteComponent,
beforeLoad: async () => {
const auth = await checkUserAccess({
allowedRoles: ["systemAdmin", "admin"],
moduleName: "system", // optional
});
if (!auth) {
throw redirect({
to: "/login",
search: {
// Use the current location to power a redirect after login
// (Do not use `router.state.resolvedLocation` as it can
// potentially lag behind the actual current location)
redirect: location.pathname + location.search,
},
});
}
},
});
function RouteComponent() {

View File

@@ -1,11 +1,230 @@
import { createFileRoute } from '@tanstack/react-router'
import { useMutation, useQuery } from "@tanstack/react-query";
import { createFileRoute } from "@tanstack/react-router";
import { createColumnHelper } from "@tanstack/react-table";
import axios from "axios";
import { ArrowDown, ArrowUp } from "lucide-react";
import { useEffect, useRef, useState } from "react";
import { toast } from "sonner";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { getSettings } from "@/lib/querys/admin/getSettings";
import TableNoExpand from "@/lib/tableStuff/TableNoExpand";
type Settings = {
settings_id: string;
name: string;
active: boolean;
value: string;
description: string;
moduleName: string;
roles: string[];
};
const updateSettings = async (
id: string,
data: Record<string, string | number | boolean | null>,
) => {
console.log(id, data);
try {
const res = await axios.patch(`/lst/api/system/settings/${id}`, data, {
withCredentials: true,
});
toast.success(`Setting just updated`);
return res;
} catch (err) {
toast.error("Error in updating the settings");
return err;
}
};
export const Route = createFileRoute(
'/_app/_adminLayout/admin/_system/settings',
"/_app/_adminLayout/admin/_system/settings",
)({
component: RouteComponent,
})
component: RouteComponent,
});
function RouteComponent() {
return <div>Hello "/_app/_adminLayout/admin/_system/settings"!</div>
const { data, isLoading, refetch } = useQuery(getSettings());
const columnHelper = createColumnHelper<Settings>();
const submitting = useRef(false);
const updateSetting = useMutation({
mutationFn: ({
id,
field,
value,
}: {
id: string;
field: string;
value: string | number | boolean | null;
}) => updateSettings(id, { [field]: value }),
onSuccess: () => {
// refetch or update cache
refetch();
},
});
const columns = [
columnHelper.accessor("name", {
header: ({ column }) => {
return (
<Button
variant="ghost"
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
>
<span className="flex flex-row gap-2">Name</span>
{column.getIsSorted() === "asc" ? (
<ArrowUp className="ml-2 h-4 w-4" />
) : (
<ArrowDown className="ml-2 h-4 w-4" />
)}
</Button>
);
},
cell: (i) => i.getValue(),
}),
columnHelper.accessor("description", {
header: ({ column }) => {
return (
<Button
variant="ghost"
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
>
<span className="flex flex-row gap-2">Description</span>
{column.getIsSorted() === "asc" ? (
<ArrowUp className="ml-2 h-4 w-4" />
) : (
<ArrowDown className="ml-2 h-4 w-4" />
)}
</Button>
);
},
cell: (i) => i.getValue(),
}),
columnHelper.accessor("value", {
header: ({ column }) => {
return (
<Button
variant="ghost"
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
>
<span className="flex flex-row gap-2">Value</span>
{column.getIsSorted() === "asc" ? (
<ArrowUp className="ml-2 h-4 w-4" />
) : (
<ArrowDown className="ml-2 h-4 w-4" />
)}
</Button>
);
},
cell: ({ row, getValue }) => {
const initialValue = String(getValue() ?? "");
const [localValue, setLocalValue] = useState(initialValue);
const id = row.original.settings_id;
const field = "value";
useEffect(() => setLocalValue(initialValue), [initialValue]);
const handleSubmit = (newValue: string) => {
if (newValue !== initialValue) {
setLocalValue(newValue);
updateSetting.mutate({ id, field, value: newValue });
}
};
return (
<Input
value={localValue}
onChange={(e) => setLocalValue(e.currentTarget.value)}
onBlur={(e) => {
if (!submitting.current) {
submitting.current = true;
handleSubmit(e.currentTarget.value.trim());
setTimeout(() => (submitting.current = false), 100); // reset after slight delay
}
}}
onKeyDown={(e) => {
if (e.key === "Enter") {
e.preventDefault();
submitting.current = true;
handleSubmit(e.currentTarget.value.trim());
e.currentTarget.blur(); // will trigger blur, but we ignore it
setTimeout(() => (submitting.current = false), 100);
}
}}
/>
);
},
}),
columnHelper.accessor("moduleName", {
header: ({ column }) => {
return (
<Button
variant="ghost"
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
>
<span className="flex flex-row gap-2">Module Name</span>
{column.getIsSorted() === "asc" ? (
<ArrowUp className="ml-2 h-4 w-4" />
) : (
<ArrowDown className="ml-2 h-4 w-4" />
)}
</Button>
);
},
cell: ({ row, getValue }) => {
const initialValue = String(getValue() ?? "");
const [localValue, setLocalValue] = useState(initialValue);
const id = row.original.settings_id;
const field = "moduleName";
useEffect(() => setLocalValue(initialValue), [initialValue]);
const handleSubmit = (newValue: string) => {
if (newValue !== initialValue) {
setLocalValue(newValue);
updateSetting.mutate({ id, field, value: newValue });
}
};
return (
<Input
value={localValue}
onChange={(e) => setLocalValue(e.currentTarget.value)}
onBlur={(e) => {
if (!submitting.current) {
submitting.current = true;
handleSubmit(e.currentTarget.value.trim());
setTimeout(() => (submitting.current = false), 100); // reset after slight delay
}
}}
onKeyDown={(e) => {
if (e.key === "Enter") {
e.preventDefault();
submitting.current = true;
handleSubmit(e.currentTarget.value.trim());
e.currentTarget.blur(); // will trigger blur, but we ignore it
setTimeout(() => (submitting.current = false), 100);
}
}}
/>
);
},
}),
];
if (isLoading)
return (
<div>
<span>Loading settings data</span>
</div>
);
return (
<div className="m-2">
<TableNoExpand data={data} columns={columns} />
</div>
);
}