Styles, API calls, and more
This commit is contained in:
1375
package-lock.json
generated
1375
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
48
package.json
48
package.json
@@ -10,26 +10,36 @@
|
|||||||
"preview": "vite preview"
|
"preview": "vite preview"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@iconify/react": "^4.1.1",
|
"@emotion/react": "^11.13.0",
|
||||||
"@mantine/core": "^7.9.1",
|
"@iconify/react": "^5.0.2",
|
||||||
"@mantine/hooks": "^7.9.1",
|
"@mantine/core": "^7.12.1",
|
||||||
"@tanstack/react-query": "^5.36.0",
|
"@mantine/dates": "^7.12.1",
|
||||||
"jotai": "^2.8.0",
|
"@mantine/form": "^7.12.1",
|
||||||
"react": "^18.2.0",
|
"@mantine/hooks": "^7.12.1",
|
||||||
"react-dom": "^18.2.0",
|
"@mantine/modals": "^7.12.1",
|
||||||
"react-icons": "^5.2.1",
|
"@tabler/icons-react": "^3.12.0",
|
||||||
"react-query": "^3.39.3"
|
"@tanstack/react-query": "^5.51.23",
|
||||||
|
"clsx": "^2.1.1",
|
||||||
|
"dayjs": "^1.11.12",
|
||||||
|
"jotai": "^2.9.3",
|
||||||
|
"mantine-react-table": "^2.0.0-beta.6",
|
||||||
|
"react": "^18.3.1",
|
||||||
|
"react-dom": "^18.3.1",
|
||||||
|
"react-icons": "^5.3.0",
|
||||||
|
"react-query": "^3.39.3",
|
||||||
|
"react-router-dom": "^6.26.0",
|
||||||
|
"react-toastify": "^10.0.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/react": "^18.2.66",
|
"@types/react": "^18.3.3",
|
||||||
"@types/react-dom": "^18.2.22",
|
"@types/react-dom": "^18.3.0",
|
||||||
"@typescript-eslint/eslint-plugin": "^7.2.0",
|
"@typescript-eslint/eslint-plugin": "^8.1.0",
|
||||||
"@typescript-eslint/parser": "^7.2.0",
|
"@typescript-eslint/parser": "^8.1.0",
|
||||||
"@vitejs/plugin-react": "^4.2.1",
|
"@vitejs/plugin-react": "^4.3.1",
|
||||||
"eslint": "^8.57.0",
|
"eslint": "^9.9.0",
|
||||||
"eslint-plugin-react-hooks": "^4.6.0",
|
"eslint-plugin-react-hooks": "^4.6.2",
|
||||||
"eslint-plugin-react-refresh": "^0.4.6",
|
"eslint-plugin-react-refresh": "^0.4.9",
|
||||||
"typescript": "^5.2.2",
|
"typescript": "^5.5.4",
|
||||||
"vite": "^5.2.0"
|
"vite": "^5.4.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
#root {
|
#root {
|
||||||
max-width: 1280px;
|
width: 100%;
|
||||||
margin: 0 auto;
|
height: 100%;
|
||||||
padding: 2rem;
|
|
||||||
text-align: center;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.logo {
|
.logo {
|
||||||
|
|||||||
23
src/App.tsx
23
src/App.tsx
@@ -2,6 +2,14 @@ import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
|||||||
import './App.css'
|
import './App.css'
|
||||||
import Homepage from './pages/homepage/Homepage'
|
import Homepage from './pages/homepage/Homepage'
|
||||||
import { MantineProvider } from '@mantine/core'
|
import { MantineProvider } from '@mantine/core'
|
||||||
|
import {
|
||||||
|
Route,
|
||||||
|
createBrowserRouter,
|
||||||
|
createRoutesFromElements,
|
||||||
|
RouterProvider,
|
||||||
|
Navigate,
|
||||||
|
} from "react-router-dom";
|
||||||
|
import Settings from './pages/settings/Settings';
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
const queryClient = new QueryClient({
|
const queryClient = new QueryClient({
|
||||||
@@ -13,10 +21,21 @@ function App() {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const router = createBrowserRouter(
|
||||||
|
createRoutesFromElements(
|
||||||
|
<Route>
|
||||||
|
<Route path="/trigger" element={<Homepage />}></Route>
|
||||||
|
<Route path="/trigger/settings" element={<Settings />}></Route>
|
||||||
|
</Route>,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<QueryClientProvider client={queryClient}>
|
<QueryClientProvider client={queryClient}>
|
||||||
<MantineProvider>
|
<MantineProvider theme={{
|
||||||
<Homepage />
|
fontFamily: '-apple-system,Jost,BlinkMacSystemFont,Helvetica Neue,Roboto,sans-serif',
|
||||||
|
}}>
|
||||||
|
<RouterProvider router={router} />
|
||||||
</MantineProvider>
|
</MantineProvider>
|
||||||
</QueryClientProvider>
|
</QueryClientProvider>
|
||||||
)
|
)
|
||||||
|
|||||||
99
src/DataTable/DataTable.tsx
Normal file
99
src/DataTable/DataTable.tsx
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
import { ModalsProvider } from "@mantine/modals";
|
||||||
|
import { MantineReactTable, useMantineReactTable } from "mantine-react-table"
|
||||||
|
import { memo } from "react";
|
||||||
|
// import '../dataTableStyles.css';
|
||||||
|
import '@mantine/core/styles.css'; //import Mantine V7 styles needed by MRT
|
||||||
|
import '@mantine/dates/styles.css'; //if using mantine date picker features
|
||||||
|
import 'mantine-react-table/styles.css'; //import MRT styles
|
||||||
|
|
||||||
|
export function DataTableComponent({
|
||||||
|
columns,
|
||||||
|
tableData,
|
||||||
|
isFetching,
|
||||||
|
isLoading,
|
||||||
|
onCreatingRowSave,
|
||||||
|
onEditingRowSave,
|
||||||
|
}: any) {
|
||||||
|
|
||||||
|
const table = useMantineReactTable({
|
||||||
|
columns: columns,
|
||||||
|
data: tableData,
|
||||||
|
enableFacetedValues: true,
|
||||||
|
enablePagination: false,
|
||||||
|
enableRowVirtualization: true,
|
||||||
|
enableEditing: true,
|
||||||
|
createDisplayMode: "modal",
|
||||||
|
editDisplayMode: "row",
|
||||||
|
enableBottomToolbar: false,
|
||||||
|
enableToolbarInternalActions: false,
|
||||||
|
onCreatingRowSave: onCreatingRowSave,
|
||||||
|
onEditingRowSave: onEditingRowSave,
|
||||||
|
getRowId: (row: any) => row.id,
|
||||||
|
initialState: {
|
||||||
|
showProgressBars: isFetching,
|
||||||
|
showLoadingOverlay: isLoading,
|
||||||
|
isLoading: isLoading,
|
||||||
|
},
|
||||||
|
state: {
|
||||||
|
showProgressBars: isFetching,
|
||||||
|
showLoadingOverlay: isLoading,
|
||||||
|
isLoading: isLoading,
|
||||||
|
},
|
||||||
|
// mantineToolbarAlertBannerBadgeProps: {
|
||||||
|
// style: {
|
||||||
|
// color: "red",
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// mantineTopToolbarProps: {
|
||||||
|
// style: {
|
||||||
|
// color: "var(--color-text-pri)",
|
||||||
|
// background: "var(--color-background)",
|
||||||
|
// border: "none",
|
||||||
|
// boxShadow: "none",
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// mantineTableHeadProps: {
|
||||||
|
// style: {
|
||||||
|
// boxShadow: "none",
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// mantineTableHeadCellProps: {
|
||||||
|
// style: {
|
||||||
|
// color: "var(--color-text-pri)",
|
||||||
|
// background: "var(--color-background)",
|
||||||
|
// borderRight: "none",
|
||||||
|
// borderLeft: "none",
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// mantineTableBodyCellProps: {
|
||||||
|
// style: {
|
||||||
|
// background: "var(--color-background)",
|
||||||
|
// borderRight: "none",
|
||||||
|
// borderLeft: "none",
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// mantinePaperProps: {
|
||||||
|
// style: {
|
||||||
|
// border: "none",
|
||||||
|
// boxShadow: "none",
|
||||||
|
// background: "var(--color-background)",
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// mantineTableContainerProps: {
|
||||||
|
// style: {
|
||||||
|
// border: "none",
|
||||||
|
// boxShadow: "none",
|
||||||
|
// background: "var(--color-background)",
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
});
|
||||||
|
return (
|
||||||
|
<ModalsProvider>
|
||||||
|
<MantineReactTable table={table} />
|
||||||
|
</ModalsProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DataTable = memo(DataTableComponent);
|
||||||
|
|
||||||
|
export default DataTable;
|
||||||
117
src/DataTable/columns/ApplicationColumns.tsx
Normal file
117
src/DataTable/columns/ApplicationColumns.tsx
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
import { MRT_ColumnDef } from "mantine-react-table";
|
||||||
|
import { useMemo } from "react";
|
||||||
|
|
||||||
|
export default function ApplicationColumns() {
|
||||||
|
const columns = useMemo<MRT_ColumnDef<any>[]>(
|
||||||
|
() => [
|
||||||
|
{
|
||||||
|
accessorFn: (row) => {
|
||||||
|
if (row.id) {
|
||||||
|
return `${row.id}`;
|
||||||
|
} else {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
enableEditing: false,
|
||||||
|
id: "id",
|
||||||
|
header: "ID",
|
||||||
|
size: 50,
|
||||||
|
mantineCopyButtonProps: ({ cell, row }) => ({
|
||||||
|
className: "id",
|
||||||
|
id: `${cell.getValue()}-id`,
|
||||||
|
}),
|
||||||
|
Cell: ({ renderedCellValue, row }) => renderedCellValue,
|
||||||
|
Header: ({ column }) => column.columnDef.header,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorFn: (row) => {
|
||||||
|
if (row.name) {
|
||||||
|
return `${row.name}`;
|
||||||
|
} else {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
id: "name",
|
||||||
|
header: "Name",
|
||||||
|
mantineCopyButtonProps: ({ cell, row }) => ({
|
||||||
|
className: "name",
|
||||||
|
id: `${cell.getValue()}-name`,
|
||||||
|
}),
|
||||||
|
Cell: ({ renderedCellValue, row }) => renderedCellValue,
|
||||||
|
Header: ({ column }) => column.columnDef.header,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorFn: (row) => {
|
||||||
|
if (row.url) {
|
||||||
|
return `${row.url}`;
|
||||||
|
} else {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
id: "url",
|
||||||
|
header: "Url",
|
||||||
|
mantineCopyButtonProps: ({ cell, row }) => ({
|
||||||
|
className: "url",
|
||||||
|
id: `${cell.getValue()}-url`,
|
||||||
|
}),
|
||||||
|
Cell: ({ renderedCellValue, row }) => renderedCellValue,
|
||||||
|
Header: ({ column }) => column.columnDef.header,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorFn: (row) => {
|
||||||
|
if (row.icon) {
|
||||||
|
return `${row.icon}`;
|
||||||
|
} else {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
id: "icon",
|
||||||
|
header: "Icon",
|
||||||
|
mantineCopyButtonProps: ({ cell, row }) => ({
|
||||||
|
className: "icon",
|
||||||
|
id: `${cell.getValue()}-icon`,
|
||||||
|
}),
|
||||||
|
Cell: ({ renderedCellValue, row }) => renderedCellValue,
|
||||||
|
Header: ({ column }) => column.columnDef.header,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorFn: (row) => {
|
||||||
|
if (row.urlbase) {
|
||||||
|
return `${row.urlbase}`;
|
||||||
|
} else {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
id: "urlbase",
|
||||||
|
header: "Base Url",
|
||||||
|
mantineCopyButtonProps: ({ cell, row }) => ({
|
||||||
|
className: "urlbase",
|
||||||
|
id: `${cell.getValue()}-urlbase`,
|
||||||
|
}),
|
||||||
|
Cell: ({ renderedCellValue, row }) => renderedCellValue,
|
||||||
|
Header: ({ column }) => column.columnDef.header,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorFn: (row) => {
|
||||||
|
if (row.category) {
|
||||||
|
return `${row.category}`;
|
||||||
|
} else {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
id: "category",
|
||||||
|
header: "Category",
|
||||||
|
mantineCopyButtonProps: ({ cell, row }) => ({
|
||||||
|
className: "category",
|
||||||
|
id: `${cell.getValue()}-category`,
|
||||||
|
}),
|
||||||
|
Cell: ({ renderedCellValue, row }) => renderedCellValue,
|
||||||
|
Header: ({ column }) => column.columnDef.header,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
return columns;
|
||||||
|
}
|
||||||
|
|
||||||
87
src/DataTable/columns/BookmarkColumns.tsx
Normal file
87
src/DataTable/columns/BookmarkColumns.tsx
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
import { MRT_ColumnDef } from "mantine-react-table";
|
||||||
|
import { useMemo } from "react";
|
||||||
|
|
||||||
|
export default function ApplicationColumns(groups: any, groupData: any) {
|
||||||
|
const columns = useMemo<MRT_ColumnDef<any>[]>(
|
||||||
|
() => [
|
||||||
|
{
|
||||||
|
accessorFn: (row) => {
|
||||||
|
if (row.id) {
|
||||||
|
return `${row.id}`;
|
||||||
|
} else {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
enableEditing: false,
|
||||||
|
id: "id",
|
||||||
|
header: "ID",
|
||||||
|
size: 50,
|
||||||
|
mantineCopyButtonProps: ({ cell, row }) => ({
|
||||||
|
className: "id",
|
||||||
|
id: `${cell.getValue()}-id`,
|
||||||
|
}),
|
||||||
|
Cell: ({ renderedCellValue, row }) => renderedCellValue,
|
||||||
|
Header: ({ column }) => column.columnDef.header,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorFn: (row) => {
|
||||||
|
if (row.name) {
|
||||||
|
return `${row.name}`;
|
||||||
|
} else {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
id: "name",
|
||||||
|
header: "Name",
|
||||||
|
mantineCopyButtonProps: ({ cell, row }) => ({
|
||||||
|
className: "name",
|
||||||
|
id: `${cell.getValue()}-name`,
|
||||||
|
}),
|
||||||
|
Cell: ({ renderedCellValue, row }) => renderedCellValue,
|
||||||
|
Header: ({ column }) => column.columnDef.header,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorFn: (row) => {
|
||||||
|
if (row.url) {
|
||||||
|
return `${row.url}`;
|
||||||
|
} else {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
id: "url",
|
||||||
|
header: "Url",
|
||||||
|
mantineCopyButtonProps: ({ cell, row }) => ({
|
||||||
|
className: "url",
|
||||||
|
id: `${cell.getValue()}-url`,
|
||||||
|
}),
|
||||||
|
Cell: ({ renderedCellValue, row }) => renderedCellValue,
|
||||||
|
Header: ({ column }) => column.columnDef.header,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorFn: (row) => {
|
||||||
|
const index: number = row.category;
|
||||||
|
if (index !== null && index !== undefined) {
|
||||||
|
return `${index}`;
|
||||||
|
} else {
|
||||||
|
return "N/A";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
id: "category",
|
||||||
|
header: "Category",
|
||||||
|
editVariant: "select",
|
||||||
|
mantineEditSelectProps: { data: groups },
|
||||||
|
mantineCopyButtonProps: ({ cell, row }) => ({
|
||||||
|
className: "category",
|
||||||
|
id: `${cell.getValue()}-category`,
|
||||||
|
}),
|
||||||
|
Cell: ({ renderedCellValue, row }) =>
|
||||||
|
groupData[row?.original.category].category_name,
|
||||||
|
Header: ({ column }) => column.columnDef.header,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[groups],
|
||||||
|
);
|
||||||
|
|
||||||
|
return columns;
|
||||||
|
}
|
||||||
|
|
||||||
66
src/DataTable/columns/MessagesColumns.tsx
Normal file
66
src/DataTable/columns/MessagesColumns.tsx
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
import { MRT_ColumnDef } from "mantine-react-table";
|
||||||
|
import { useMemo } from "react";
|
||||||
|
|
||||||
|
export default function MessagesColumns() {
|
||||||
|
const columns = useMemo<MRT_ColumnDef<any>[]>(
|
||||||
|
() => [
|
||||||
|
{
|
||||||
|
accessorFn: (row) => {
|
||||||
|
if (row.id) {
|
||||||
|
return `${row.id}`;
|
||||||
|
} else {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
enableEditing: false,
|
||||||
|
id: "id",
|
||||||
|
header: "ID",
|
||||||
|
size: 50,
|
||||||
|
mantineCopyButtonProps: ({ cell, row }) => ({
|
||||||
|
className: "id",
|
||||||
|
id: `${cell.getValue()}-id`,
|
||||||
|
}),
|
||||||
|
Cell: ({ renderedCellValue, row }) => renderedCellValue,
|
||||||
|
Header: ({ column }) => column.columnDef.header,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorFn: (row) => {
|
||||||
|
if (row.message) {
|
||||||
|
return `${row.message}`;
|
||||||
|
} else {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
id: "message",
|
||||||
|
header: "Message",
|
||||||
|
mantineCopyButtonProps: ({ cell, row }) => ({
|
||||||
|
className: "message",
|
||||||
|
id: `${cell.getValue()}-message`,
|
||||||
|
}),
|
||||||
|
Cell: ({ renderedCellValue, row }) => renderedCellValue,
|
||||||
|
Header: ({ column }) => column.columnDef.header,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorFn: (row) => {
|
||||||
|
if (row.easteregg) {
|
||||||
|
return `${row.easteregg}`;
|
||||||
|
} else {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
id: "easteregg",
|
||||||
|
header: "Easter Egg?",
|
||||||
|
mantineCopyButtonProps: ({ cell, row }) => ({
|
||||||
|
className: "easteregg",
|
||||||
|
id: `${cell.getValue()}-easteregg`,
|
||||||
|
}),
|
||||||
|
Cell: ({ renderedCellValue, row }) => renderedCellValue,
|
||||||
|
Header: ({ column }) => column.columnDef.header,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
return columns;
|
||||||
|
}
|
||||||
|
|
||||||
65
src/DataTable/columns/PersonalizationColumns.tsx
Normal file
65
src/DataTable/columns/PersonalizationColumns.tsx
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
import { MRT_ColumnDef } from "mantine-react-table";
|
||||||
|
import { useMemo } from "react";
|
||||||
|
|
||||||
|
export default function PersonalizationColumns() {
|
||||||
|
const columns = useMemo<MRT_ColumnDef<any>[]>(
|
||||||
|
() => [
|
||||||
|
{
|
||||||
|
accessorFn: (row: any) => {
|
||||||
|
if (row.firstname) {
|
||||||
|
return `${row.firstname}`;
|
||||||
|
} else {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
id: "firstname",
|
||||||
|
header: "First Name",
|
||||||
|
size: 50,
|
||||||
|
mantineCopyButtonProps: ({ cell, row }) => ({
|
||||||
|
className: "firstname",
|
||||||
|
id: `${cell.getValue()}-firstname`,
|
||||||
|
}),
|
||||||
|
Cell: ({ renderedCellValue, row }) => renderedCellValue,
|
||||||
|
Header: ({ column }) => column.columnDef.header,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorFn: (row: any) => {
|
||||||
|
if (row.lastname) {
|
||||||
|
return `${row.lastname}`;
|
||||||
|
} else {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
id: "lastname",
|
||||||
|
header: "Last Name",
|
||||||
|
mantineCopyButtonProps: ({ cell, row }) => ({
|
||||||
|
className: "lastname",
|
||||||
|
id: `${cell.getValue()}-lastname`,
|
||||||
|
}),
|
||||||
|
Cell: ({ renderedCellValue, row }) => renderedCellValue,
|
||||||
|
Header: ({ column }) => column.columnDef.header,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorFn: (row: any) => {
|
||||||
|
if (row.email) {
|
||||||
|
return `${row.email}`;
|
||||||
|
} else {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
id: "email",
|
||||||
|
header: "Email",
|
||||||
|
mantineCopyButtonProps: ({ cell, row }) => ({
|
||||||
|
className: "email",
|
||||||
|
id: `${cell.getValue()}-email`,
|
||||||
|
}),
|
||||||
|
Cell: ({ renderedCellValue, row }) => renderedCellValue,
|
||||||
|
Header: ({ column }) => column.columnDef.header,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
return columns;
|
||||||
|
}
|
||||||
|
|
||||||
16
src/components/ToastError.tsx
Normal file
16
src/components/ToastError.tsx
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { Stack, Text } from '@mantine/core';
|
||||||
|
import { stackClasses } from '../styles';
|
||||||
|
|
||||||
|
export function ToastError({ err }: { err: any }) {
|
||||||
|
const errorResponse = JSON.parse(err.message);
|
||||||
|
return (
|
||||||
|
<Stack className={stackClasses.VStack} style={{ alignItems: 'flex-start' }}>
|
||||||
|
<Text>
|
||||||
|
Status: {errorResponse.status} {errorResponse.statusText}
|
||||||
|
</Text>
|
||||||
|
<Text>Details: {errorResponse.msg}</Text>
|
||||||
|
{errorResponse.loc && <Text>Location: {errorResponse.loc}</Text>}
|
||||||
|
{errorResponse.type && <Text>Type: {errorResponse.type}</Text>}
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
||||||
198
src/dataTableStyles.css
Normal file
198
src/dataTableStyles.css
Normal file
@@ -0,0 +1,198 @@
|
|||||||
|
.mrt-table-paper {
|
||||||
|
height: 100%;
|
||||||
|
overflow: visible;
|
||||||
|
background-color: transparent;
|
||||||
|
border: none;
|
||||||
|
border-style: none;
|
||||||
|
box-shadow: none;
|
||||||
|
&.mantine-Overlay-root {
|
||||||
|
color: var(--mantine-primary-color-7);
|
||||||
|
background-color: alpha(var(--mantine-color-appGray-9), 0.5);
|
||||||
|
}
|
||||||
|
&.mrt-toolbar-dropzone {
|
||||||
|
height: 4.5vh;
|
||||||
|
width: 35vw;
|
||||||
|
color: var(--mantine-primary-color-6);
|
||||||
|
font-size: 36px;
|
||||||
|
background-color: rgb(255, 255, 255, 1);
|
||||||
|
justify-self: center;
|
||||||
|
&.inner {
|
||||||
|
font-size: 36px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
& ::-webkit-scrollbar {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
border-radius: 5px;
|
||||||
|
background-color: transparent;
|
||||||
|
|
||||||
|
}
|
||||||
|
& ::-webkit-scrollbar-thumb:hover {
|
||||||
|
background-color: var(--mantine-primary-color-4);
|
||||||
|
}
|
||||||
|
& ::-webkit-scrollbar-track {
|
||||||
|
background-color: transparent;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
& ::-webkit-scrollbar-thumb {
|
||||||
|
border-radius: 5px;
|
||||||
|
background-color: var(--mantine-primary-color-6);
|
||||||
|
}
|
||||||
|
& ::-webkit-scrollbar-corner {
|
||||||
|
background-color: light-dark(white, var(--mantine-primary-color-8));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mrt-table-container {
|
||||||
|
height: 100%;
|
||||||
|
background-color: light-dark(white, var(--mantine-primary-color-8));
|
||||||
|
border-top: solid;
|
||||||
|
border-top-color: light-dark(
|
||||||
|
var(--mantine-color-appGray-1),
|
||||||
|
var(--mantine-primary-color-6)
|
||||||
|
);
|
||||||
|
border-top-width: 1px;
|
||||||
|
border-left: solid;
|
||||||
|
border-left-color: light-dark(
|
||||||
|
var(--mantine-color-appGray-1),
|
||||||
|
var(--mantine-primary-color-6)
|
||||||
|
);
|
||||||
|
border-left-width: 1px;
|
||||||
|
border-right: solid;
|
||||||
|
border-right-color: light-dark(
|
||||||
|
var(--mantine-color-appGray-1),
|
||||||
|
var(--mantine-primary-color-6)
|
||||||
|
);
|
||||||
|
border-right-width: 1px;
|
||||||
|
border-bottom: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
border-bottom-left-radius: 0px;
|
||||||
|
border-bottom-right-radius: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mantine-Table-th {
|
||||||
|
color: light-dark(var(--mantine-color-appGray-7), var(--mantine-primary-color-3));
|
||||||
|
background-color: light-dark(white, var(--mantine-primary-color-8));
|
||||||
|
border-color: light-dark(
|
||||||
|
var(--mantine-color-appGray-1),
|
||||||
|
var(--mantine-primary-color-7)
|
||||||
|
);
|
||||||
|
/* & .mantine-ActionIcon-root {
|
||||||
|
margin-right: 1px;
|
||||||
|
display: none;
|
||||||
|
} */
|
||||||
|
& .mrt-table-head-cell-content {
|
||||||
|
padding-top: 12px;
|
||||||
|
padding-bottom: 12px;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
white-space: nowrap;
|
||||||
|
.mrt-table-head-cell-labels {
|
||||||
|
& > * > .mantine-Text-root {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
& .mrt-table-head-cell-content-actions {
|
||||||
|
transition: 1s all ease;
|
||||||
|
& .mantine-ActionIcon-root {
|
||||||
|
color: transparent;
|
||||||
|
background-color: transparent;
|
||||||
|
display: hidden;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
& .mrt-table-head-cell-resize-handle {
|
||||||
|
display: flex;
|
||||||
|
width: 4px;
|
||||||
|
border-radius: 4px;
|
||||||
|
border-left-color: transparent;
|
||||||
|
background-color: light-dark(
|
||||||
|
var(--mantine-primary-color-1),
|
||||||
|
var(--mantine-primary-color-6)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
& .mantine-TableHeadCell-Content-Labels {
|
||||||
|
cursor: pointer;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
&:hover {
|
||||||
|
& .mrt-table-head-cell-content-actions {
|
||||||
|
& .mantine-ActionIcon-root {
|
||||||
|
color: light-dark(var(--mantine-primary-color-6), var(--mantine-primary-color-3));
|
||||||
|
background-color: light-dark(
|
||||||
|
var(--mantine-primary-color-1),
|
||||||
|
var(--mantine-primary-color-6)
|
||||||
|
);
|
||||||
|
display: grid;
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
& .mrt-table-head-cell-resize-handle {
|
||||||
|
border-left-color: var(--mantine-primary-color-2);
|
||||||
|
border-width: 4px;
|
||||||
|
border-left-style: solid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mantine-Mark-root {
|
||||||
|
background-color: light-dark(
|
||||||
|
var(--mantine-primary-color-7),
|
||||||
|
var(--mantine-primary-color-7)
|
||||||
|
);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mantine-Table-tr {
|
||||||
|
height: 56px;
|
||||||
|
box-shadow: none;
|
||||||
|
& .table-icon-device-floppy {
|
||||||
|
color: light-dark(var(--mantine-primary-color-6), var(--mantine-primary-color-3));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mantine-Table-td {
|
||||||
|
color: light-dark(var(--mantine-color-appGray-7), var(--mantine-primary-color-3));
|
||||||
|
justify-content: center;
|
||||||
|
border-color: light-dark(
|
||||||
|
var(--mantine-color-appGray-1),
|
||||||
|
var(--mantine-primary-color-7)
|
||||||
|
);
|
||||||
|
|
||||||
|
& > * > .mantine-Text-root {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mrt-table-head-sort-button {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mrt-table-head-sort-button[data-sorted='asc'] {
|
||||||
|
display: flex;
|
||||||
|
min-height: 12px !important;
|
||||||
|
min-width: 12px !important;
|
||||||
|
max-width: 16px;
|
||||||
|
max-height: 16px;
|
||||||
|
|
||||||
|
& > * > .tabler-icon-arrows-sort {
|
||||||
|
height: 12px !important;
|
||||||
|
width: 12px !important;
|
||||||
|
color: light-dark(--mantine-primary-color-6, --mantine-color-appGray-5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.mrt-table-head-sort-button[data-sorted='desc'] {
|
||||||
|
display: flex;
|
||||||
|
min-height: 12px !important;
|
||||||
|
min-width: 12px !important;
|
||||||
|
max-width: 16px;
|
||||||
|
max-height: 16px;
|
||||||
|
|
||||||
|
& > * > .tabler-icon-arrows-sort {
|
||||||
|
height: 12px !important;
|
||||||
|
width: 12px !important;
|
||||||
|
color: light-dark(--mantine-primary-color-6, --mantine-color-appGray-5);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
import { useQuery, useQueryClient } from '@tanstack/react-query';
|
import { useQuery, useQueryClient, useMutation, useInfiniteQuery } from '@tanstack/react-query';
|
||||||
|
import { useCallback } from 'react';
|
||||||
|
import { toast } from 'react-toastify';
|
||||||
|
|
||||||
export function useQueryAPI({ endpoint }: { endpoint: string }) {
|
export function useQueryAPI({ endpoint }: { endpoint: string }) {
|
||||||
const queryClient = useQueryClient();
|
|
||||||
const callEndpoint = async (endpoint: string) => {
|
const callEndpoint = async (endpoint: string) => {
|
||||||
const url = `http://localhost:5000/trigger/${endpoint}`;
|
const url = `http://localhost:5000/trigger/${endpoint}`;
|
||||||
const options: object = {
|
const options: object = {
|
||||||
@@ -16,22 +18,425 @@ export function useQueryAPI({ endpoint }: { endpoint: string }) {
|
|||||||
'Sec-Fetch-Site': 'same-site',
|
'Sec-Fetch-Site': 'same-site',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const response = await fetch(url, options);
|
const response = await fetch(url, options);
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error(`${response.status} ${response.statusText}`);
|
throw new Error(`${response.status} ${response.statusText}`);
|
||||||
}
|
}
|
||||||
const responseData = await response.json();
|
return await response.json();
|
||||||
return responseData;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return useQuery({
|
return useQuery({
|
||||||
queryKey: ['GET', endpoint],
|
queryKey: ['GET', endpoint],
|
||||||
queryFn: () => callEndpoint(endpoint),
|
queryFn: () => callEndpoint(endpoint),
|
||||||
retry: false,
|
|
||||||
refetchOnWindowFocus: false,
|
|
||||||
refetchOnMount: false,
|
|
||||||
refetchOnReconnect: false,
|
|
||||||
staleTime: 30 * (60 * 1000), // 30 minutes
|
|
||||||
// cacheTime: 60 * (60 * 1000), // 60 minutes
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function useMutationAPI() {
|
||||||
|
|
||||||
|
const callEndpoint = async (
|
||||||
|
method: string,
|
||||||
|
endpoint: string,
|
||||||
|
body: object | undefined | null,
|
||||||
|
) => {
|
||||||
|
|
||||||
|
const url = `http://localhost:5000/trigger/${endpoint}`;
|
||||||
|
const options: any = {
|
||||||
|
method: method,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Access-Control-Allow-Origin': '*',
|
||||||
|
'Access-Control-Allow-Headers': '*',
|
||||||
|
'Access-Control-Allow-Methods': 'POST, GET, OPTIONS',
|
||||||
|
'Access-Control-Allow-Credentials': true,
|
||||||
|
'Sec-Fetch-Mode': 'no-cors',
|
||||||
|
'Sec-Fetch-Site': 'same-site'
|
||||||
|
},
|
||||||
|
body: JSON.stringify(body),
|
||||||
|
};
|
||||||
|
|
||||||
|
const response = await fetch(url, options);
|
||||||
|
if (!response.ok) {
|
||||||
|
const responseData = await response.json();
|
||||||
|
if (typeof responseData.detail !== 'string') {
|
||||||
|
const errorResponse = JSON.stringify({
|
||||||
|
status: response.status,
|
||||||
|
statusText: response.statusText,
|
||||||
|
method: options.method,
|
||||||
|
endpoint: endpoint,
|
||||||
|
msg: responseData.detail[0]?.msg,
|
||||||
|
loc: responseData.detail[0]?.loc,
|
||||||
|
type: responseData.detail[0]?.type,
|
||||||
|
});
|
||||||
|
|
||||||
|
throw new Error(errorResponse);
|
||||||
|
}
|
||||||
|
if (typeof responseData.detail === 'object') {
|
||||||
|
const errorResponse = JSON.stringify({
|
||||||
|
status: response.status,
|
||||||
|
statusText: response.statusText,
|
||||||
|
method: options.method,
|
||||||
|
endpoint: endpoint,
|
||||||
|
msg: responseData.detail.message,
|
||||||
|
});
|
||||||
|
|
||||||
|
throw new Error(errorResponse);
|
||||||
|
}
|
||||||
|
const errorResponse = JSON.stringify({
|
||||||
|
status: response.status,
|
||||||
|
statusText: response.statusText,
|
||||||
|
method: options.method,
|
||||||
|
endpoint: endpoint,
|
||||||
|
msg: responseData.detail,
|
||||||
|
});
|
||||||
|
|
||||||
|
throw new Error(errorResponse);
|
||||||
|
}
|
||||||
|
return await response.json();
|
||||||
|
};
|
||||||
|
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: ({
|
||||||
|
method,
|
||||||
|
endpoint,
|
||||||
|
body = undefined,
|
||||||
|
}: {
|
||||||
|
method: string;
|
||||||
|
endpoint: string;
|
||||||
|
body?: object | undefined | null;
|
||||||
|
}) => callEndpoint(method, endpoint, body),
|
||||||
|
onMutate: () => {},
|
||||||
|
onSuccess: (data, variables) => {
|
||||||
|
queryClient.invalidateQueries({ queryKey: ['GET', variables.endpoint] });
|
||||||
|
|
||||||
|
if (variables.endpoint.includes('/')) {
|
||||||
|
if ((variables.endpoint.match(/\//g) || []).length > 2) {
|
||||||
|
const GetEndpoint = variables.endpoint.split('/');
|
||||||
|
const Endpoint = `${GetEndpoint[1]}/${GetEndpoint[2]}`;
|
||||||
|
queryClient.invalidateQueries({ queryKey: ['GET', `/${Endpoint}`] });
|
||||||
|
} else {
|
||||||
|
const GetEndpoint = variables.endpoint.split('/')[1];
|
||||||
|
queryClient.invalidateQueries({ queryKey: ['GET', `/${GetEndpoint}`] });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useFileUploadAPI() {
|
||||||
|
|
||||||
|
const callEndpoint = async (
|
||||||
|
method: string,
|
||||||
|
endpoint: string,
|
||||||
|
body?: object | FormData | undefined | null,
|
||||||
|
) => {
|
||||||
|
|
||||||
|
const url = `http://localhost:5000/trigger/${endpoint}`;
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
method: method,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Access-Control-Allow-Origin': '*',
|
||||||
|
'Access-Control-Allow-Headers': '*',
|
||||||
|
'Access-Control-Allow-Methods': 'POST, GET, OPTIONS',
|
||||||
|
'Access-Control-Allow-Credentials': true,
|
||||||
|
'Sec-Fetch-Mode': 'no-cors',
|
||||||
|
'Sec-Fetch-Site': 'same-site',
|
||||||
|
},
|
||||||
|
body: body ?? undefined,
|
||||||
|
};
|
||||||
|
const response = await fetch(url, options as unknown as Body);
|
||||||
|
if (!response.ok) {
|
||||||
|
const responseData = await response.json();
|
||||||
|
if (typeof responseData.detail !== 'string') {
|
||||||
|
const errorResponse = JSON.stringify({
|
||||||
|
status: response.status,
|
||||||
|
statusText: response.statusText,
|
||||||
|
method: options.method,
|
||||||
|
endpoint: endpoint,
|
||||||
|
msg: responseData.detail[0].msg,
|
||||||
|
loc: responseData.detail[0].loc,
|
||||||
|
type: responseData.detail[0].type,
|
||||||
|
});
|
||||||
|
|
||||||
|
throw new Error(errorResponse);
|
||||||
|
}
|
||||||
|
const errorResponse = JSON.stringify({
|
||||||
|
status: response.status,
|
||||||
|
statusText: response.statusText,
|
||||||
|
method: options.method,
|
||||||
|
endpoint: endpoint,
|
||||||
|
msg: responseData.detail,
|
||||||
|
});
|
||||||
|
|
||||||
|
throw new Error(errorResponse);
|
||||||
|
}
|
||||||
|
return await response.json();
|
||||||
|
};
|
||||||
|
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: async ({
|
||||||
|
method,
|
||||||
|
endpoint,
|
||||||
|
body,
|
||||||
|
}: {
|
||||||
|
method: string;
|
||||||
|
endpoint: string;
|
||||||
|
body: object | FormData | undefined | null;
|
||||||
|
toastID?: string;
|
||||||
|
}) => callEndpoint(method, endpoint, body),
|
||||||
|
onMutate: () => {},
|
||||||
|
onSuccess: (variables) => {
|
||||||
|
queryClient.invalidateQueries({ queryKey: ['GET', variables.endpoint] });
|
||||||
|
if (variables.endpoint?.includes('/')) {
|
||||||
|
const GetEndpoint = variables.endpoint.split('/')[1];
|
||||||
|
queryClient.invalidateQueries({ queryKey: ['GET', `/${GetEndpoint}`] });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useFetchAllRecordsAPI({ endpoint }: { endpoint: string }) {
|
||||||
|
const pageSize = 500;
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
|
const fetchPage = async (page: number, retryCount = 0): Promise<any> => {
|
||||||
|
|
||||||
|
// Start building the URL
|
||||||
|
const url = new URL(`http://localhost:5000/trigger/${endpoint}`);
|
||||||
|
|
||||||
|
// Check and append custom query parameters from the endpoint
|
||||||
|
const customParams = new URLSearchParams(endpoint.split('?')[1]);
|
||||||
|
for (const [key, value] of customParams) {
|
||||||
|
if (!url.searchParams.has(key)) {
|
||||||
|
url.searchParams.append(key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add 'page' and 'size' parameters
|
||||||
|
url.searchParams.set('page', page.toString()); // Use 'set' to ensure uniqueness
|
||||||
|
url.searchParams.set('size', pageSize.toString());
|
||||||
|
|
||||||
|
const options: any = {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Access-Control-Allow-Origin': '*',
|
||||||
|
'Access-Control-Allow-Headers': '*',
|
||||||
|
'Access-Control-Allow-Methods': 'POST, GET, OPTIONS',
|
||||||
|
'Access-Control-Allow-Credentials': true,
|
||||||
|
'Sec-Fetch-Mode': 'no-cors',
|
||||||
|
'Sec-Fetch-Site': 'same-site',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(url.toString(), options);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
// If the response is not ok and status is 401 (not authorized), reload the page
|
||||||
|
if (response.status === 401) {
|
||||||
|
window.location.reload();
|
||||||
|
}
|
||||||
|
throw new Error(
|
||||||
|
`Error fetching page ${page}: ${response.status} ${response.statusText}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return response.json();
|
||||||
|
} catch (err) {
|
||||||
|
if (retryCount < 3) {
|
||||||
|
// Retry up to 3 times with a random delay
|
||||||
|
const delay = Math.floor(Math.random() * 3000) + 1000; // Random delay between 1 to 3 seconds
|
||||||
|
console.error(
|
||||||
|
`Retrying page ${page}, attempt ${retryCount + 1} after ${delay} ms`,
|
||||||
|
);
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, delay));
|
||||||
|
return fetchPage(page, retryCount + 1);
|
||||||
|
}
|
||||||
|
console.error(`Error fetching page ${page} after 3 retries:`, err);
|
||||||
|
throw err; // Re-throw the error to handle it in the calling function
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const callEndpoint = async () => {
|
||||||
|
try {
|
||||||
|
// Fetch the first page to get the total number of items and pages
|
||||||
|
const firstPageData = await fetchPage(1);
|
||||||
|
// If the first page doesn't have a 'pages' property, return the data as-is
|
||||||
|
if (firstPageData?.pages === undefined) {
|
||||||
|
return firstPageData;
|
||||||
|
}
|
||||||
|
if (firstPageData?.pages === 1) {
|
||||||
|
return firstPageData.items;
|
||||||
|
}
|
||||||
|
|
||||||
|
let totalPages = firstPageData.pages;
|
||||||
|
const allItems = [...firstPageData.items];
|
||||||
|
// Fetch each page sequentially
|
||||||
|
for (let page = 2; page <= totalPages; page++) {
|
||||||
|
try {
|
||||||
|
const pageData = await fetchPage(page);
|
||||||
|
allItems.push(...pageData.items);
|
||||||
|
|
||||||
|
// Check and update the totalPages if it has increased
|
||||||
|
if (pageData.pages > totalPages) {
|
||||||
|
totalPages = pageData.pages;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error(`Final error fetching page ${page}:`, err);
|
||||||
|
// Continue to the next page even if the current page fails
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return allItems;
|
||||||
|
} catch (error) {
|
||||||
|
// Handling errors in fetching the first page
|
||||||
|
console.error('Error fetching the first page:', error);
|
||||||
|
return []; // Return empty array on error
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const invalidate = useCallback(() => {
|
||||||
|
queryClient.invalidateQueries({ queryKey: ['GET', endpoint] });
|
||||||
|
}, [queryClient, endpoint]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...useQuery({
|
||||||
|
queryKey: ['GET', endpoint],
|
||||||
|
queryFn: callEndpoint,
|
||||||
|
}),
|
||||||
|
invalidate,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DownloadFileOptions {
|
||||||
|
endpoint: string;
|
||||||
|
file_name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useDownloadFile() {
|
||||||
|
const downloadFile = useCallback(
|
||||||
|
async ({ endpoint, file_name }: DownloadFileOptions) => {
|
||||||
|
const url = `http://localhost:5000/trigger/${endpoint}`;
|
||||||
|
const options: any = {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Access-Control-Allow-Origin': '*',
|
||||||
|
'Access-Control-Allow-Headers': '*',
|
||||||
|
'Access-Control-Allow-Methods': 'POST, GET, OPTIONS',
|
||||||
|
'Access-Control-Allow-Credentials': true,
|
||||||
|
'Sec-Fetch-Mode': 'no-cors',
|
||||||
|
'Sec-Fetch-Site': 'same-site',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const toastID = toast.loading('Processing...');
|
||||||
|
try {
|
||||||
|
const response = await fetch(url, options);
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Network response was not ok');
|
||||||
|
}
|
||||||
|
|
||||||
|
const blob = await response.blob();
|
||||||
|
if (blob.type === 'application/json') {
|
||||||
|
const blob_text = await blob.text();
|
||||||
|
const cleaned_text = blob_text.replace(/"/g, '');
|
||||||
|
toast.update(toastID, {
|
||||||
|
render: cleaned_text,
|
||||||
|
type: 'info',
|
||||||
|
isLoading: false,
|
||||||
|
autoClose: 5000,
|
||||||
|
closeButton: null,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
const blobURL = window.URL.createObjectURL(blob);
|
||||||
|
const a = document.createElement('a');
|
||||||
|
|
||||||
|
a.style.display = 'none';
|
||||||
|
a.href = blobURL;
|
||||||
|
a.download = file_name;
|
||||||
|
|
||||||
|
document.body.appendChild(a);
|
||||||
|
a.click();
|
||||||
|
window.URL.revokeObjectURL(blobURL);
|
||||||
|
document.body.removeChild(a);
|
||||||
|
toast.update(toastID, {
|
||||||
|
render: `${file_name} Downloaded`,
|
||||||
|
type: 'success',
|
||||||
|
isLoading: false,
|
||||||
|
autoClose: 5000,
|
||||||
|
closeButton: null,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Download failed:', error);
|
||||||
|
toast.update(toastID, {
|
||||||
|
render: `Error Downloading ${file_name}. Please try again.`,
|
||||||
|
type: 'error',
|
||||||
|
isLoading: false,
|
||||||
|
autoClose: 5000,
|
||||||
|
closeButton: null,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
return downloadFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export const useFetchInfiniteQuery = ({
|
||||||
|
endpoint,
|
||||||
|
pageSize = 10,
|
||||||
|
}: {
|
||||||
|
endpoint: string;
|
||||||
|
pageSize?: number;
|
||||||
|
}) => {
|
||||||
|
const queryKey = ['GET', endpoint];
|
||||||
|
|
||||||
|
const fetchInfiniteQuery = async (endpoint: string) => {
|
||||||
|
const url = `http://localhost:5000/trigger/${endpoint}`;
|
||||||
|
const requestOptions: any = {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Access-Control-Allow-Origin': '*',
|
||||||
|
'Access-Control-Allow-Headers': '*',
|
||||||
|
'Access-Control-Allow-Methods': 'POST, GET, OPTIONS',
|
||||||
|
'Access-Control-Allow-Credentials': true,
|
||||||
|
'Sec-Fetch-Mode': 'no-cors',
|
||||||
|
'Sec-Fetch-Site': 'same-site'
|
||||||
|
},
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
const response = await fetch(url, requestOptions);
|
||||||
|
return await response.json();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching transactions:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const queryFn = ({ pageParam = 1 }) =>
|
||||||
|
fetchInfiniteQuery(`${endpoint}&page=${pageParam}&size=${pageSize}`);
|
||||||
|
const getNextPageParam = (lastPage: any, allPages: any) => {
|
||||||
|
if (lastPage.items.length < pageSize) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
return allPages.length + 1;
|
||||||
|
};
|
||||||
|
const { data, isLoading, isFetchingNextPage, fetchNextPage, isError, isFetching } =
|
||||||
|
useInfiniteQuery({
|
||||||
|
queryKey,
|
||||||
|
queryFn,
|
||||||
|
getNextPageParam,
|
||||||
|
initialPageParam: 1,
|
||||||
|
});
|
||||||
|
return { data, isLoading, isFetchingNextPage, fetchNextPage, isError, isFetching };
|
||||||
|
};
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
@import url('https://fonts.googleapis.com/css2?family=Jost:ital,wght@0,100;0,200;0,400;1,100;1,200&display=swap');
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
|
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
@@ -54,6 +56,14 @@ button:focus-visible {
|
|||||||
outline: 4px auto -webkit-focus-ring-color;
|
outline: 4px auto -webkit-focus-ring-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#settings-container {
|
||||||
|
justify-content: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
#settings-nav {
|
||||||
|
width: 25vw;
|
||||||
|
}
|
||||||
|
|
||||||
@media (prefers-color-scheme: light) {
|
@media (prefers-color-scheme: light) {
|
||||||
:root {
|
:root {
|
||||||
color: #213547;
|
color: #213547;
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { Stack } from '@mantine/core';
|
import { Stack } from '@mantine/core';
|
||||||
import { useEffect, useState } from 'react';
|
|
||||||
import stackClasses from '../../styles/Stack.module.css';
|
import stackClasses from '../../styles/Stack.module.css';
|
||||||
import {useQueryAPI} from '../../hooks/useAPI';
|
import {useQueryAPI} from '../../hooks/useAPI';
|
||||||
import Search from './components/Search';
|
import Search from './components/Search';
|
||||||
@@ -9,6 +8,7 @@ import Bookmarks from './components/Bookmarks';
|
|||||||
import SettingsButton from './components/SettingsButton';
|
import SettingsButton from './components/SettingsButton';
|
||||||
import { useAtom } from 'jotai';
|
import { useAtom } from 'jotai';
|
||||||
import { HomepageFilters } from '../../contexts/HomepageFilters';
|
import { HomepageFilters } from '../../contexts/HomepageFilters';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
|
||||||
export default function Homepage() {
|
export default function Homepage() {
|
||||||
|
|||||||
@@ -35,9 +35,10 @@ function AppItem({ url, icon, name }: any) {
|
|||||||
gap: 0,
|
gap: 0,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Text className={texts.appName}>{name}</Text>
|
<Text className={texts.appName} classNames={texts}>{name}</Text>
|
||||||
<Text
|
<Text
|
||||||
className={texts.appUrl}
|
className={texts.appUrl}
|
||||||
|
classNames={texts}
|
||||||
style={{
|
style={{
|
||||||
textDecoration: textDecoration,
|
textDecoration: textDecoration,
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -16,10 +16,10 @@ function Applications() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack id='apps' style={{ gap: 0 }}>
|
<Stack id='apps' style={{ gap: 0 }}>
|
||||||
<Text className={texts.sectionTitle}>Applications</Text>
|
<Text className={texts.sectionTitle} classNames={texts}>Applications</Text>
|
||||||
<Stack
|
<Stack
|
||||||
style={{
|
style={{
|
||||||
// background: 'var(--color-background)',
|
background: 'var(--color-background)',
|
||||||
gap: '.25rem',
|
gap: '.25rem',
|
||||||
backgroundSize: 'cover',
|
backgroundSize: 'cover',
|
||||||
borderBottom: '0px solid',
|
borderBottom: '0px solid',
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ function Links() {
|
|||||||
<>
|
<>
|
||||||
{groupsIsFetched && linksIsFetched && categories?.length > 0 && (
|
{groupsIsFetched && linksIsFetched && categories?.length > 0 && (
|
||||||
<Stack id='links' style={{ gap: 0 }}>
|
<Stack id='links' style={{ gap: 0 }}>
|
||||||
<Text className={texts.sectionTitle}>Bookmarks</Text>
|
<Text className={texts.sectionTitle} classNames={texts}>Bookmarks</Text>
|
||||||
<Stack
|
<Stack
|
||||||
style={{
|
style={{
|
||||||
display: 'grid',
|
display: 'grid',
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ function LinkGroups({ links, category }: any) {
|
|||||||
lineHeight: '1.5rem',
|
lineHeight: '1.5rem',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Text className={texts.linkCategory}>{category}</Text>
|
<Text className={texts.linkCategory} classNames={texts}>{category}</Text>
|
||||||
{links?.map((link: any) => (
|
{links?.map((link: any) => (
|
||||||
<LinkItem key={link.name} link={link} />
|
<LinkItem key={link.name} link={link} />
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import texts from '../../../styles/Text.module.css';
|
|||||||
|
|
||||||
function LinkItem({ link }: any) {
|
function LinkItem({ link }: any) {
|
||||||
return (
|
return (
|
||||||
<Text className={texts.linkItem} component='a' href={link.url}>
|
<Text className={texts.linkItem} classNames={texts} component='a' href={link.url}>
|
||||||
{link.name}
|
{link.name}
|
||||||
</Text>
|
</Text>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -24,7 +24,9 @@ function SettingsButton() {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if(data) {
|
if(data) {
|
||||||
setCategories(data.map((category: any) => {return {value: category.category, label: category.category}}));
|
let list: [any] = data.map((category: any) => {return {value: category.category, label: category.category}}) || [];
|
||||||
|
list.unshift({value: 'All', label: 'All'});
|
||||||
|
setCategories(list);
|
||||||
}
|
}
|
||||||
}, [data, isFetched]);
|
}, [data, isFetched]);
|
||||||
|
|
||||||
@@ -41,7 +43,9 @@ function SettingsButton() {
|
|||||||
withArrow
|
withArrow
|
||||||
shadow='md'
|
shadow='md'
|
||||||
position='top'
|
position='top'
|
||||||
classNames={menu}
|
classNames={{
|
||||||
|
dropdown: menu.dropdown
|
||||||
|
}}
|
||||||
styles={{
|
styles={{
|
||||||
dropdown: {
|
dropdown: {
|
||||||
backgroundColor: 'var(--color-background)',
|
backgroundColor: 'var(--color-background)',
|
||||||
@@ -49,7 +53,7 @@ function SettingsButton() {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Menu.Target>
|
<Menu.Target>
|
||||||
<Button className={button.settingsButton}>
|
<Button className={button.settingsButton} classNames={button}>
|
||||||
<FiMenu />
|
<FiMenu />
|
||||||
</Button>
|
</Button>
|
||||||
</Menu.Target>
|
</Menu.Target>
|
||||||
|
|||||||
38
src/pages/settings/Settings.tsx
Normal file
38
src/pages/settings/Settings.tsx
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
import { ActionIcon, Group, Stack, Tabs } from "@mantine/core";
|
||||||
|
import tabClasses from '../../styles/Tabs.module.css';
|
||||||
|
import Personalization from "./components/Personalization";
|
||||||
|
import { FiArrowLeftCircle } from "react-icons/fi";
|
||||||
|
import { actionIconClasses } from "../../styles";
|
||||||
|
import Messages from "./components/Messages";
|
||||||
|
import Applications from "./components/Applications";
|
||||||
|
import Bookmarks from "./components/Bookmarks";
|
||||||
|
|
||||||
|
function Settings() {
|
||||||
|
return (
|
||||||
|
<Stack h={'100vh'} px={'2rem'}>
|
||||||
|
<Group gap={0} >
|
||||||
|
<ActionIcon className={actionIconClasses.backButton} classNames={actionIconClasses} component="a" href="/trigger"><FiArrowLeftCircle/></ActionIcon>
|
||||||
|
<h1>Settings</h1>
|
||||||
|
</Group>
|
||||||
|
<Tabs keepMounted orientation="vertical" defaultValue={'personalization'} className={tabClasses.settingsTabs} classNames={tabClasses}>
|
||||||
|
<Tabs.List>
|
||||||
|
<Tabs.Tab value="personalization">Personal Info</Tabs.Tab>
|
||||||
|
<Tabs.Tab value="messages">Messages</Tabs.Tab>
|
||||||
|
<Tabs.Tab value="applications">Applications</Tabs.Tab>
|
||||||
|
<Tabs.Tab value="bookmarksCategories">Bookmark Categories</Tabs.Tab>
|
||||||
|
<Tabs.Tab value="bookmarks">Bookmarks</Tabs.Tab>
|
||||||
|
<Tabs.Tab value="themes">Themes</Tabs.Tab>
|
||||||
|
<Tabs.Tab value="providers">Search Providers</Tabs.Tab>
|
||||||
|
</Tabs.List>
|
||||||
|
<Tabs.Panel value="personalization"><Personalization/></Tabs.Panel>
|
||||||
|
<Tabs.Panel value="messages"><Messages/></Tabs.Panel>
|
||||||
|
<Tabs.Panel value="applications"><Applications/></Tabs.Panel>
|
||||||
|
<Tabs.Panel value="bookmarksCategories">Bookmark Categories</Tabs.Panel>
|
||||||
|
<Tabs.Panel value="bookmarks"><Bookmarks/></Tabs.Panel>
|
||||||
|
<Tabs.Panel value="themes">Themes</Tabs.Panel>
|
||||||
|
<Tabs.Panel value="providers">Search Providers</Tabs.Panel>
|
||||||
|
</Tabs>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
export default Settings;
|
||||||
59
src/pages/settings/components/Applications.tsx
Normal file
59
src/pages/settings/components/Applications.tsx
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import {useMutationAPI, useQueryAPI} from "../../../hooks/useAPI";
|
||||||
|
import { DataTable } from "../../../DataTable/DataTable";
|
||||||
|
import { toast } from "react-toastify";
|
||||||
|
import { MRT_TableOptions } from "mantine-react-table";
|
||||||
|
import ApplicationColumns from "../../../DataTable/columns/ApplicationColumns";
|
||||||
|
import { Stack } from "@mantine/core";
|
||||||
|
const ApplicationSettings = () => {
|
||||||
|
const endpoint = "api/apps";
|
||||||
|
const { data, isFetching, isLoading, isFetched } = useQueryAPI({
|
||||||
|
endpoint: endpoint,
|
||||||
|
});
|
||||||
|
const [applicationData, setApplicationData] = useState<any>([]);
|
||||||
|
|
||||||
|
const {mutateAsync: createRow, isPending: isCreatingRow} = useMutationAPI();
|
||||||
|
const {mutateAsync: editRow, isPending: isEditingRow} = useMutationAPI();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if(isFetched) {
|
||||||
|
setApplicationData(data);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const handleCreateRow: MRT_TableOptions<any>['onCreatingRowSave'] = async ({
|
||||||
|
values,
|
||||||
|
exitCreatingMode,
|
||||||
|
}) => {
|
||||||
|
|
||||||
|
createRow({method: "POST", endpoint: endpoint, body: values}).then(() => {
|
||||||
|
toast.success("Application created successfully");
|
||||||
|
})
|
||||||
|
exitCreatingMode();
|
||||||
|
};
|
||||||
|
const handleEditRow: MRT_TableOptions<any>['onCreatingRowSave'] = async ({
|
||||||
|
values,
|
||||||
|
table,
|
||||||
|
}) => {
|
||||||
|
editRow({method: "PATCH", endpoint: endpoint, body: values}).then(() => {
|
||||||
|
toast.success("Application updated successfully");
|
||||||
|
});
|
||||||
|
table.setEditingRow(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack>
|
||||||
|
<h2>Applications</h2>
|
||||||
|
<DataTable
|
||||||
|
tableName="Applications"
|
||||||
|
columns={ApplicationColumns()}
|
||||||
|
tableData={applicationData}
|
||||||
|
isFetching={isFetching}
|
||||||
|
isLoading={isLoading}
|
||||||
|
onCreatingRowSave={handleCreateRow}
|
||||||
|
onEditingRowSave={handleEditRow}
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
)
|
||||||
|
};
|
||||||
|
export default ApplicationSettings;
|
||||||
76
src/pages/settings/components/Bookmarks.tsx
Normal file
76
src/pages/settings/components/Bookmarks.tsx
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import {useMutationAPI, useQueryAPI} from "../../../hooks/useAPI";
|
||||||
|
import { DataTable } from "../../../DataTable/DataTable";
|
||||||
|
import { toast } from "react-toastify";
|
||||||
|
import { MRT_TableOptions } from "mantine-react-table";
|
||||||
|
import { Stack } from "@mantine/core";
|
||||||
|
import BookmarkColumns from "../../../DataTable/columns/BookmarkColumns";
|
||||||
|
const BookmarkSettings = () => {
|
||||||
|
const endpoint = "api/links";
|
||||||
|
const { data, isFetching, isLoading, isFetched } = useQueryAPI({
|
||||||
|
endpoint: endpoint,
|
||||||
|
});
|
||||||
|
const {
|
||||||
|
data: groupData,
|
||||||
|
isFetching: groupDataisFetching,
|
||||||
|
isLoading: groupDataisLoading,
|
||||||
|
isFetched: groupDataisFetched,
|
||||||
|
} = useQueryAPI({ endpoint: "api/categories" });
|
||||||
|
const [bookmarkData, setBookmarkData] = useState<any>([]);
|
||||||
|
const [groups, setGroups] = useState([]);
|
||||||
|
const [fullyFetched, setFullyFetched] = useState(false);
|
||||||
|
const {mutateAsync: createRow, isPending: isCreatingRow} = useMutationAPI();
|
||||||
|
const {mutateAsync: editRow, isPending: isEditingRow} = useMutationAPI();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if(isFetched) {
|
||||||
|
setBookmarkData(data);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (groupDataisFetched) {
|
||||||
|
let groupList = groupData.map((category: any) => {
|
||||||
|
return category.category_name;
|
||||||
|
});
|
||||||
|
setGroups(groupList);
|
||||||
|
setFullyFetched(true);
|
||||||
|
}
|
||||||
|
}, [groupDataisFetched]);
|
||||||
|
|
||||||
|
const handleCreateRow: MRT_TableOptions<any>['onCreatingRowSave'] = async ({
|
||||||
|
values,
|
||||||
|
exitCreatingMode,
|
||||||
|
}) => {
|
||||||
|
|
||||||
|
createRow({method: "POST", endpoint: endpoint, body: values}).then(() => {
|
||||||
|
toast.success("Application created successfully");
|
||||||
|
})
|
||||||
|
exitCreatingMode();
|
||||||
|
};
|
||||||
|
const handleEditRow: MRT_TableOptions<any>['onCreatingRowSave'] = async ({
|
||||||
|
values,
|
||||||
|
table,
|
||||||
|
}) => {
|
||||||
|
editRow({method: "PATCH", endpoint: endpoint, body: values}).then(() => {
|
||||||
|
toast.success("Application updated successfully");
|
||||||
|
});
|
||||||
|
table.setEditingRow(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack>
|
||||||
|
<h2>Bookmarks</h2>
|
||||||
|
<DataTable
|
||||||
|
tableName="Applications"
|
||||||
|
columns={BookmarkColumns(groups, groupData)}
|
||||||
|
tableData={bookmarkData}
|
||||||
|
isFetching={isFetching}
|
||||||
|
isLoading={isLoading}
|
||||||
|
onCreatingRowSave={handleCreateRow}
|
||||||
|
onEditingRowSave={handleEditRow}
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
)
|
||||||
|
};
|
||||||
|
export default BookmarkSettings;
|
||||||
0
src/pages/settings/components/Categories.tsx
Normal file
0
src/pages/settings/components/Categories.tsx
Normal file
59
src/pages/settings/components/Messages.tsx
Normal file
59
src/pages/settings/components/Messages.tsx
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import {useMutationAPI, useQueryAPI} from "../../../hooks/useAPI";
|
||||||
|
import { DataTable } from "../../../DataTable/DataTable";
|
||||||
|
import { toast } from "react-toastify";
|
||||||
|
import { MRT_TableOptions } from "mantine-react-table";
|
||||||
|
import MessagesColumns from "../../../DataTable/columns/MessagesColumns";
|
||||||
|
import { Stack } from "@mantine/core";
|
||||||
|
const MessageSettings = () => {
|
||||||
|
const endpoint = "api/messages";
|
||||||
|
const { data, isFetching, isLoading, isFetched } = useQueryAPI({
|
||||||
|
endpoint: endpoint,
|
||||||
|
});
|
||||||
|
const [messageData, setMessageData] = useState<any>([]);
|
||||||
|
|
||||||
|
const {mutateAsync: createRow, isPending: isCreatingRow} = useMutationAPI();
|
||||||
|
const {mutateAsync: editRow, isPending: isEditingRow} = useMutationAPI();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if(isFetched) {
|
||||||
|
setMessageData(data);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const handleCreateRow: MRT_TableOptions<any>['onCreatingRowSave'] = async ({
|
||||||
|
values,
|
||||||
|
exitCreatingMode,
|
||||||
|
}) => {
|
||||||
|
|
||||||
|
createRow({method: "POST", endpoint: endpoint, body: values}).then(() => {
|
||||||
|
toast.success("Message created successfully");
|
||||||
|
})
|
||||||
|
exitCreatingMode();
|
||||||
|
};
|
||||||
|
const handleEditRow: MRT_TableOptions<any>['onCreatingRowSave'] = async ({
|
||||||
|
values,
|
||||||
|
table,
|
||||||
|
}) => {
|
||||||
|
editRow({method: "PATCH", endpoint: endpoint, body: values}).then(() => {
|
||||||
|
toast.success("Message updated successfully");
|
||||||
|
});
|
||||||
|
table.setEditingRow(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack>
|
||||||
|
<h2>Messages</h2>
|
||||||
|
<DataTable
|
||||||
|
tableName="Messages"
|
||||||
|
columns={MessagesColumns()}
|
||||||
|
tableData={messageData}
|
||||||
|
isFetching={isFetching}
|
||||||
|
isLoading={isLoading}
|
||||||
|
onCreatingRowSave={handleCreateRow}
|
||||||
|
onEditingRowSave={handleEditRow}
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
)
|
||||||
|
};
|
||||||
|
export default MessageSettings;
|
||||||
58
src/pages/settings/components/Personalization.tsx
Normal file
58
src/pages/settings/components/Personalization.tsx
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import {useMutationAPI, useQueryAPI} from "../../../hooks/useAPI";
|
||||||
|
import { DataTable } from "../../../DataTable/DataTable";
|
||||||
|
import PersonalizationColumns from "../../../DataTable/columns/PersonalizationColumns";
|
||||||
|
import { toast } from "react-toastify";
|
||||||
|
import { MRT_TableOptions } from "mantine-react-table";
|
||||||
|
const PersonalizationSettings = () => {
|
||||||
|
const endpoint = "api/personaldata";
|
||||||
|
const { data, isFetching, isLoading, isFetched } = useQueryAPI({
|
||||||
|
endpoint: endpoint,
|
||||||
|
});
|
||||||
|
const [personalData, setPersonalData] = useState<any>([]);
|
||||||
|
|
||||||
|
const {mutateAsync: createRow, isPending: isCreatingRow} = useMutationAPI();
|
||||||
|
const {mutateAsync: editRow, isPending: isEditingRow} = useMutationAPI();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if(isFetched) {
|
||||||
|
setPersonalData(data);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const handleCreateRow: MRT_TableOptions<any>['onCreatingRowSave'] = async ({
|
||||||
|
values,
|
||||||
|
exitCreatingMode,
|
||||||
|
}) => {
|
||||||
|
|
||||||
|
createRow({method: "POST", endpoint: endpoint, body: values}).then(() => {
|
||||||
|
toast.success("Personalization created successfully");
|
||||||
|
})
|
||||||
|
exitCreatingMode();
|
||||||
|
};
|
||||||
|
const handleEditRow: MRT_TableOptions<any>['onCreatingRowSave'] = async ({
|
||||||
|
values,
|
||||||
|
table,
|
||||||
|
}) => {
|
||||||
|
editRow({method: "PATCH", endpoint: endpoint, body: values}).then(() => {
|
||||||
|
toast.success("Personalization updated successfully");
|
||||||
|
});
|
||||||
|
table.setEditingRow(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<h2>Personalization</h2>
|
||||||
|
<DataTable
|
||||||
|
tableName="Personalization"
|
||||||
|
columns={PersonalizationColumns()}
|
||||||
|
tableData={personalData}
|
||||||
|
isFetching={isFetching}
|
||||||
|
isLoading={isLoading}
|
||||||
|
onCreatingRowSave={handleCreateRow}
|
||||||
|
onEditingRowSave={handleEditRow}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
};
|
||||||
|
export default PersonalizationSettings;
|
||||||
0
src/pages/settings/components/Themes.tsx
Normal file
0
src/pages/settings/components/Themes.tsx
Normal file
@@ -1,6 +1,12 @@
|
|||||||
.root {
|
.backButton {
|
||||||
background-color: transparent;
|
&.root {
|
||||||
}
|
background-color: transparent;
|
||||||
.icon {
|
color: black;
|
||||||
color: var(--color-text-acc)
|
width: 2rem;
|
||||||
|
height: 2rem;
|
||||||
|
.icon {
|
||||||
|
font-size: 24px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,26 +1,30 @@
|
|||||||
.settingsButton {
|
.settingsButton {
|
||||||
width: 2rem;
|
&.root {
|
||||||
height: 2rem;
|
width: 2rem;
|
||||||
padding: 0px;
|
height: 2rem;
|
||||||
margin: 0px;
|
padding: 0px;
|
||||||
position: fixed;
|
margin: 0px;
|
||||||
bottom: 10px;
|
position: fixed;
|
||||||
left: 10px;
|
bottom: 10px;
|
||||||
color: var(--color-text-pri);
|
left: 10px;
|
||||||
background-color: var(--color-background);
|
color: var(--color-text-pri);
|
||||||
border-radius: 100%;
|
background-color: var(--color-background);
|
||||||
border: solid 2px var(--color-text-pri);
|
border-radius: 100%;
|
||||||
cursor: pointer;
|
border: solid 2px var(--color-text-pri);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.themeButton{
|
.themeButton{
|
||||||
font-size: 1em;
|
&.root {
|
||||||
font-weight:500;
|
font-size: 1em;
|
||||||
margin: 10px;
|
font-weight:500;
|
||||||
width:128px;
|
margin: 10px;
|
||||||
line-height: 3rem;
|
width:128px;
|
||||||
text-align: center;
|
line-height: 3rem;
|
||||||
text-transform: uppercase;
|
text-align: center;
|
||||||
border-radius: 1em;
|
text-transform: uppercase;
|
||||||
height: 3.5rem;
|
border-radius: 1em;
|
||||||
|
height: 3.5rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
.dropdown {
|
.settingsButton {
|
||||||
background-color: var(--color-background);
|
.dropdown {
|
||||||
border: solid 2px var(--color-border);
|
background-color: white;
|
||||||
border-color: var(--color-border);
|
border: solid 2px var(--color-border);
|
||||||
|
border-color: var(--color-border);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
6
src/styles/Tabs.module.css
Normal file
6
src/styles/Tabs.module.css
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
.settingsTabs {
|
||||||
|
.panel {
|
||||||
|
padding-left: 2rem;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,90 +1,106 @@
|
|||||||
.greeting {
|
.greeting {
|
||||||
margin: 0;
|
&.root {
|
||||||
margin-left: .5em;
|
margin: 0;
|
||||||
margin-right: .5em;
|
margin-left: .5em;
|
||||||
padding: 0;
|
margin-right: .5em;
|
||||||
text-align: left;
|
padding: 0;
|
||||||
font-size: 4em;
|
text-align: left;
|
||||||
font-weight: 400;
|
font-size: 4em;
|
||||||
width: max-content;
|
font-weight: 400;
|
||||||
color: var(--color-text-acc);
|
width: max-content;
|
||||||
}
|
color: var(--color-text-acc);
|
||||||
|
|
||||||
.dateTime {
|
|
||||||
display: flex;
|
|
||||||
font-size: 2em;
|
|
||||||
height: 30px;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
margin-right: 1em;
|
|
||||||
text-transform: uppercase;
|
|
||||||
font-weight: 300;
|
|
||||||
color: var(--color-text-pri);
|
|
||||||
}
|
|
||||||
|
|
||||||
.sectionTitle {
|
|
||||||
text-decoration: underline;
|
|
||||||
font-size: 20px;
|
|
||||||
font-weight: 200;
|
|
||||||
font-style: italic;
|
|
||||||
height: 10px;
|
|
||||||
text-transform: uppercase;
|
|
||||||
margin-top: 20px;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
color: var(--color-text-pri);
|
|
||||||
}
|
|
||||||
|
|
||||||
.appName {
|
|
||||||
font-weight: 400;
|
|
||||||
font-size: 0.8em;
|
|
||||||
font-style: normal;
|
|
||||||
color: light-dark(blue, red);
|
|
||||||
font-family: 'Jost', sans-serif;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.appUrl {
|
|
||||||
font-size: 0.8em;
|
|
||||||
font-weight: 400;
|
|
||||||
font-style: italic;
|
|
||||||
text-transform: uppercase;
|
|
||||||
color: var(--color-text-acc);
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.linkCategory {
|
|
||||||
font-size: 1.1em;
|
|
||||||
font-weight: 400;
|
|
||||||
height: 10px;
|
|
||||||
color: var(--color-text-acc);
|
|
||||||
text-transform: uppercase;
|
|
||||||
margin-top: 20px;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.linkItem {
|
|
||||||
display: block;
|
|
||||||
font-size: 1em;
|
|
||||||
line-height: 2;
|
|
||||||
color: var(--color-text-pri)
|
|
||||||
}
|
|
||||||
|
|
||||||
.settingsLink {
|
|
||||||
font-size: 1.25em;
|
|
||||||
display: block;
|
|
||||||
margin-left: 0;
|
|
||||||
margin-right: 0;
|
|
||||||
line-height: 2;
|
|
||||||
justify-self: center;
|
|
||||||
color: var(--color-text-pri);
|
|
||||||
border-radius: 1em;
|
|
||||||
&:hover {
|
|
||||||
background-color: var(--color-text-acc);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.linkItem:hover {
|
.dateTime {
|
||||||
text-decoration: underline;
|
&.root {
|
||||||
|
display: flex;
|
||||||
|
font-family: -apple-system, Jost, BlinkMacSystemFont, Helvetica Neue, Roboto, sans-serif !important;
|
||||||
|
font-size: 2em;
|
||||||
|
height: 30px;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
margin-right: 1em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-weight: 300;
|
||||||
|
color: var(--color-text-pri);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sectionTitle {
|
||||||
|
&.root {
|
||||||
|
text-decoration: underline;
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 200;
|
||||||
|
font-style: italic;
|
||||||
|
height: 10px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
margin-top: 20px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
color: var(--color-text-pri);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.appName {
|
||||||
|
&.root {
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 0.8em;
|
||||||
|
font-style: normal;
|
||||||
|
color: light-dark(blue, black);
|
||||||
|
font-family: 'Jost', sans-serif;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.appUrl {
|
||||||
|
&.root {
|
||||||
|
font-size: 0.8em;
|
||||||
|
font-weight: 400;
|
||||||
|
font-style: italic;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: var(--color-text-acc);
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.linkCategory {
|
||||||
|
&.root {
|
||||||
|
font-size: 1.1em;
|
||||||
|
font-weight: 400;
|
||||||
|
height: 10px;
|
||||||
|
color: var(--color-text-acc);
|
||||||
|
text-transform: uppercase;
|
||||||
|
margin-top: 20px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.linkItem {
|
||||||
|
&.root {
|
||||||
|
display: block;
|
||||||
|
font-size: 1em;
|
||||||
|
line-height: 2;
|
||||||
|
color: var(--color-text-pri);
|
||||||
|
&:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.settingsLink {
|
||||||
|
&.root {
|
||||||
|
font-size: 1.25em;
|
||||||
|
display: block;
|
||||||
|
margin-left: 0;
|
||||||
|
margin-right: 0;
|
||||||
|
line-height: 2;
|
||||||
|
justify-self: center;
|
||||||
|
color: var(--color-text-pri);
|
||||||
|
border-radius: 1em;
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--color-text-acc);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
a{
|
a{
|
||||||
|
|||||||
@@ -1,52 +1,50 @@
|
|||||||
.keyword{
|
.keyword{
|
||||||
background-color: transparent;
|
.input {
|
||||||
border: 0;
|
border: 0;
|
||||||
border-bottom: thin solid var(--color-border);
|
border-bottom: thin solid var(--color-border);
|
||||||
color: var(--color-text-pri);
|
background-color: transparent;
|
||||||
font-family: -apple-system, Jost, BlinkMacSystemFont, Helvetica Neue, Roboto, sans-serif;
|
color: var(--color-text-pri);
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
font-size: 0.8em;
|
font-size: 0.8em;
|
||||||
height: 3.5em;
|
height: 3.5em;
|
||||||
transition: all 0.4s ease;
|
transition: all 0.4s ease;
|
||||||
width: 50%;
|
width: 50%;
|
||||||
padding-left: 1em;
|
padding-left: 1em;
|
||||||
padding-right: 1em;
|
padding-right: 1em;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
&:focus {
|
||||||
|
background-color: var(--color-search-background);
|
||||||
|
border-color: var(--color-border);
|
||||||
|
outline: none;
|
||||||
|
color: var(--color-text-acc);
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 1.2em;
|
||||||
|
letter-spacing: .2em;
|
||||||
|
width:100%;
|
||||||
|
border-top-left-radius: .25em;
|
||||||
|
border-top-right-radius: .25em;
|
||||||
|
border-bottom-left-radius: .25em;
|
||||||
|
border-bottom-right-radius: .25em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.label{
|
||||||
|
color: var(--color-border);
|
||||||
|
display: block;
|
||||||
|
position: relative;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 200;
|
||||||
|
font-size: 1.2em;
|
||||||
|
letter-spacing: .2em;
|
||||||
|
opacity: 1;
|
||||||
|
padding-left: 1em;
|
||||||
|
padding-right: 1em;
|
||||||
|
text-align: center;
|
||||||
|
width: 100%;
|
||||||
|
top: 28%;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.keyword:focus{
|
|
||||||
background-color: var(--color-search-background);
|
|
||||||
border-color: var(--color-border);
|
|
||||||
outline: none;
|
|
||||||
color: var(--color-text-acc);
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 500;
|
|
||||||
font-size: 1.2em;
|
|
||||||
letter-spacing: .2em;
|
|
||||||
width:100%;
|
|
||||||
border-top-left-radius: .25em;
|
|
||||||
border-top-right-radius: .25em;
|
|
||||||
border-bottom-left-radius: .25em;
|
|
||||||
border-bottom-right-radius: .25em;
|
|
||||||
}
|
|
||||||
.keyword::placeholder{
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
.label{
|
|
||||||
color: var(--color-border);
|
|
||||||
display: block;
|
|
||||||
position: relative;
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 200;
|
|
||||||
font-size: 1.2em;
|
|
||||||
letter-spacing: .2em;
|
|
||||||
opacity: 1;
|
|
||||||
padding-left: 1em;
|
|
||||||
padding-right: 1em;
|
|
||||||
text-align: center;
|
|
||||||
width: 100%;
|
|
||||||
top: 28%;
|
|
||||||
}
|
|
||||||
.keyword:placeholder-shown + .label{
|
.keyword:placeholder-shown + .label{
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
|
|||||||
@@ -5,7 +5,8 @@ import navLinkClasses from './NavLink.module.css';
|
|||||||
import popoverClasses from './Popover.module.css';
|
import popoverClasses from './Popover.module.css';
|
||||||
import selectClasses from './Select.module.css';
|
import selectClasses from './Select.module.css';
|
||||||
import stackClasses from './Stack.module.css';
|
import stackClasses from './Stack.module.css';
|
||||||
|
import tabClasses from './Tabs.module.css';
|
||||||
import textClasses from './Text.module.css';
|
import textClasses from './Text.module.css';
|
||||||
import textInputClasses from './TextInput.module.css';
|
import textInputClasses from './TextInput.module.css';
|
||||||
|
|
||||||
export { actionIconClasses, buttonClasses, menuClasses, navLinkClasses, popoverClasses, selectClasses, stackClasses, textClasses, textInputClasses }
|
export { actionIconClasses, buttonClasses, menuClasses, navLinkClasses, popoverClasses, selectClasses, stackClasses, tabClasses, textClasses, textInputClasses }
|
||||||
Reference in New Issue
Block a user