diff --git a/package-lock.json b/package-lock.json index ca42690..bf9cb1a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,12 +24,15 @@ "@radix-ui/react-tooltip": "^1.2.7", "@tailwindcss/vite": "^4.1.8", "@tanstack/react-query": "^5.80.6", + "@tanstack/react-table": "^8.21.3", + "@types/lodash": "^4.17.17", "@types/react-router-dom": "^5.3.3", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "electron-store": "^8.2.0", "jwt-decode": "^4.0.0", "leaflet": "^1.9.4", + "lodash": "^4.17.21", "lucide-react": "^0.513.0", "next-themes": "^0.4.6", "react-icons": "^5.5.0", @@ -3365,6 +3368,39 @@ "react": "^18 || ^19" } }, + "node_modules/@tanstack/react-table": { + "version": "8.21.3", + "resolved": "https://registry.npmjs.org/@tanstack/react-table/-/react-table-8.21.3.tgz", + "integrity": "sha512-5nNMTSETP4ykGegmVkhjcS8tTLW6Vl4axfEGQN3v0zdHYbK4UfoqfPChclTrJ4EoK9QynqAu9oUf8VEmrpZ5Ww==", + "license": "MIT", + "dependencies": { + "@tanstack/table-core": "8.21.3" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/@tanstack/table-core": { + "version": "8.21.3", + "resolved": "https://registry.npmjs.org/@tanstack/table-core/-/table-core-8.21.3.tgz", + "integrity": "sha512-ldZXEhOBb8Is7xLs01fR3YEc3DERiz5silj8tnGkFZytt1abEvl/GhUmCE0PMLaMPTa3Jk4HbKmRlHmu+gCftg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, "node_modules/@tootallnate/once": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", @@ -3504,6 +3540,12 @@ "@types/geojson": "*" } }, + "node_modules/@types/lodash": { + "version": "4.17.17", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.17.tgz", + "integrity": "sha512-RRVJ+J3J+WmyOTqnz3PiBLA501eKwXl2noseKOrNo/6+XEHjTAxO4xHvxQB6QuNm+s4WRbn6rSiap8+EA+ykFQ==", + "license": "MIT" + }, "node_modules/@types/ms": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", @@ -8760,7 +8802,6 @@ "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true, "license": "MIT" }, "node_modules/lodash.defaults": { diff --git a/package.json b/package.json index bfb5c6f..23322b8 100644 --- a/package.json +++ b/package.json @@ -37,12 +37,15 @@ "@radix-ui/react-tooltip": "^1.2.7", "@tailwindcss/vite": "^4.1.8", "@tanstack/react-query": "^5.80.6", + "@tanstack/react-table": "^8.21.3", + "@types/lodash": "^4.17.17", "@types/react-router-dom": "^5.3.3", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "electron-store": "^8.2.0", "jwt-decode": "^4.0.0", "leaflet": "^1.9.4", + "lodash": "^4.17.21", "lucide-react": "^0.513.0", "next-themes": "^0.4.6", "react-icons": "^5.5.0", diff --git a/src/main/index.ts b/src/main/index.ts index a4a2db7..a59e3af 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -3,6 +3,7 @@ import { join } from 'path'; import { electronApp, optimizer, is } from '@electron-toolkit/utils'; import './tokenIpc'; import WebSocket from 'ws' +import { getToken } from './store'; const WS_URL = import.meta.env.MAIN_VITE_WS_URL; let ws: WebSocket; @@ -17,6 +18,10 @@ function connect(mainWindow: BrowserWindow) { ws.on("open", () => { console.log("Connected"); reconnectAttempts = 0; + + const token = getToken(); + if (token) + ws.send(token); }); ws.on("error", () => { @@ -29,9 +34,10 @@ function connect(mainWindow: BrowserWindow) { ws.on("message", (msg: any) => { const str: string = msg.toString(); - console.log("Message received: ", str); + console.log("Message received:", str); const splitted = str.split("::"); const type = splitted[0]; + switch (type) { case "msg": mainWindow.webContents.send('ws-message-chat', [...splitted.slice(1)]); diff --git a/src/main/store.ts b/src/main/store.ts new file mode 100644 index 0000000..d71ec62 --- /dev/null +++ b/src/main/store.ts @@ -0,0 +1,36 @@ +import { safeStorage } from 'electron'; +import Store from 'electron-store'; + +type Token = { + access_token: string; +} + +const store = new Store({ + defaults: { + access_token: '', + }, +}); + +export function setToken(token: string) { + if (!safeStorage.isEncryptionAvailable()) + return; + + const encrypted = safeStorage.encryptString(token); + store.set('access_token', encrypted.toString('base64')); +} + +export function getToken() { + if (!safeStorage.isEncryptionAvailable()) + return null; + + const encryptedBase64 = store.get('access_token'); + if (!encryptedBase64) + return null; + + const buffer = Buffer.from(encryptedBase64, 'base64'); + return safeStorage.decryptString(buffer); +} + +export function clearToken() { + store.delete('access_token'); +} diff --git a/src/main/tokenIpc.ts b/src/main/tokenIpc.ts index 1dcb8c2..3d6e83d 100644 --- a/src/main/tokenIpc.ts +++ b/src/main/tokenIpc.ts @@ -1,37 +1,14 @@ -import { ipcMain, safeStorage } from 'electron'; -import Store from 'electron-store'; - - -type Token = { - access_token: string; -} - -const store = new Store({ - defaults: { - access_token: '', - }, -}); +import { ipcMain } from "electron"; +import { clearToken, getToken, setToken } from "./store"; ipcMain.handle('set-token', (_, token: string) => { - if (!safeStorage.isEncryptionAvailable()) - return; - - const encrypted = safeStorage.encryptString(token); - store.set('access_token', encrypted.toString('base64')); + setToken(token); }); ipcMain.handle('get-token', () => { - if (!safeStorage.isEncryptionAvailable()) - return null; - - const encryptedBase64 = store.get('access_token'); - if (!encryptedBase64) - return null; - - const buffer = Buffer.from(encryptedBase64, 'base64'); - return safeStorage.decryptString(buffer); + return getToken(); }); ipcMain.handle('clear-token', () => { - store.delete('access_token'); + clearToken(); }); diff --git a/src/renderer/src/Layout.tsx b/src/renderer/src/Layout.tsx index d1338d4..0057699 100644 --- a/src/renderer/src/Layout.tsx +++ b/src/renderer/src/Layout.tsx @@ -29,7 +29,8 @@ const LoadingState = () => { queryKey: ["isAlive"], queryFn: () => { return checkServer(); - } + }, + gcTime: Infinity } ); @@ -81,7 +82,6 @@ const Layout = () => { return; } - await window.api.sendWsMessage(token); const data = await getEmployeeData(token); if (data instanceof Error) { diff --git a/src/renderer/src/components/Map.tsx b/src/renderer/src/components/Map.tsx index 3e89104..64b0de0 100644 --- a/src/renderer/src/components/Map.tsx +++ b/src/renderer/src/components/Map.tsx @@ -43,7 +43,7 @@ function LocationMarker({ initalMarker, onLocationSelect }: LocationMarkerProps) export const MapPicker = ({ center, zoom, className, initalMarker, onLocationSelect }: MapPickerComponentProps) => { return ( - + diff --git a/src/renderer/src/components/PaginationButtons.tsx b/src/renderer/src/components/PaginationButtons.tsx new file mode 100644 index 0000000..e2b2b43 --- /dev/null +++ b/src/renderer/src/components/PaginationButtons.tsx @@ -0,0 +1,70 @@ +import { useEffect, useState } from "react"; +import { Button } from "./ui/button"; +import { Pagination, PaginationContent, PaginationEllipsis, PaginationItem } from "./ui/pagination"; +import { range } from "lodash"; + +interface PaginationButtonsProps { + currentPage: number; + totalPages: number; + handleOnPageClick: (page: number) => void; + className?: string +} + +const PaginationButtons = ({ currentPage, totalPages, handleOnPageClick, className }: PaginationButtonsProps) => { + const toShowButtons = 5; + const [showStartEllipsis, setShowStartEllipsis] = useState(false); + const [showEndEllipsis, setShowEndEllipsis] = useState(false); + const [currButtons, setCurrButtons] = useState([]); + + useEffect(() => { + if (currentPage === 1) + setShowStartEllipsis(false); + if (totalPages <= toShowButtons) { + setCurrButtons(range(1, totalPages + 1)); + } + else if (currentPage + toShowButtons - 1 < totalPages) { + if (currentPage !== 1) + setShowStartEllipsis(true); + setShowEndEllipsis(true); + setCurrButtons(range(currentPage, toShowButtons + currentPage)); + } + else { + setCurrButtons(range(totalPages - toShowButtons, totalPages + 1)); + setShowEndEllipsis(false); + } + }, [currentPage, totalPages]); + + return ( + + + + + + { + showStartEllipsis && + + + + } + { + currButtons.map((val) => ( + + + + )) + } + { + showEndEllipsis && + + + + } + + + + + + ); +}; + +export default PaginationButtons; \ No newline at end of file diff --git a/src/renderer/src/components/ui/pagination.tsx b/src/renderer/src/components/ui/pagination.tsx new file mode 100644 index 0000000..8698f2a --- /dev/null +++ b/src/renderer/src/components/ui/pagination.tsx @@ -0,0 +1,127 @@ +import * as React from "react" +import { + ChevronLeftIcon, + ChevronRightIcon, + MoreHorizontalIcon, +} from "lucide-react" + +import { cn } from "@renderer/lib/utils" +import { Button, buttonVariants } from "@renderer/components/ui/button" + +function Pagination({ className, ...props }: React.ComponentProps<"nav">) { + return ( +