diff --git a/package.json b/package.json index 00b09473..1efd19bc 100644 --- a/package.json +++ b/package.json @@ -20,12 +20,24 @@ "e2e": "playwright test" }, "dependencies": { - "electron-updater": "^6.1.8" + "@emotion/styled": "^11.11.5", + "@material-ui/core": "^4.12.4", + "@mui/icons-material": "^5.15.19", + "@mui/material": "^5.15.19", + "@mui/styled-engine-sc": "^6.0.0-alpha.18", + "@mui/x-charts": "^7.6.1", + "@mui/x-tree-view": "^7.6.1", + "axios": "^1.7.2", + "electron-updater": "^6.1.8", + "react-query": "^3.39.3", + "react-router-dom": "^6.23.1", + "styled-components": "^6.1.11", + "zustand": "^4.5.2" }, "devDependencies": { "@playwright/test": "^1.42.1", - "@types/react": "^18.2.64", - "@types/react-dom": "^18.2.21", + "@types/react": "^18.3.3", + "@types/react-dom": "^18.3.0", "@vitejs/plugin-react": "^4.2.1", "autoprefixer": "^10.4.18", "electron": "^29.1.1", diff --git a/src/App.css b/src/App.css index d02bb4ba..e69de29b 100644 --- a/src/App.css +++ b/src/App.css @@ -1,52 +0,0 @@ -#root { - max-width: 1280px; - margin: 0 auto; - padding: 2rem; - text-align: center; -} - -.logo-box { - position: relative; - height: 9em; -} - -.logo { - position: absolute; - left: calc(50% - 4.5em); - height: 6em; - padding: 1.5em; - will-change: filter; - transition: filter 300ms; -} -.logo:hover { - filter: drop-shadow(0 0 2em #646cffaa); -} - -@keyframes logo-spin { - from { - transform: rotate(0deg); - } - to { - transform: rotate(360deg); - } -} - -@media (prefers-reduced-motion: no-preference) { - .logo.electron { - animation: logo-spin infinite 20s linear; - } -} - -.card { - padding: 2em; -} - -.read-the-docs { - color: #888; -} - -.flex-center { - display: flex; - align-items: center; - justify-content: center; -} diff --git a/src/App.tsx b/src/App.tsx index c5462d1d..58af6543 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,38 +1,12 @@ -import { useState } from 'react' -import UpdateElectron from '@/components/update' -import logoVite from './assets/logo-vite.svg' -import logoElectron from './assets/logo-electron.svg' -import './App.css' +import React from "react"; +import { Outlet } from "react-router-dom"; -function App() { - const [count, setCount] = useState(0) +const App: React.FC = () => { return ( -
-
- - Electron + Vite logo - Electron + Vite logo - -
-

Electron + Vite + React

-
- -

- Edit src/App.tsx and save to test HMR -

-
-

- Click on the Electron + Vite logo to learn more -

-
- Place static files into the/public folder Node logo -
- - +
+
- ) -} + ); +}; -export default App \ No newline at end of file +export default App; diff --git a/src/AuthProvider.tsx b/src/AuthProvider.tsx new file mode 100644 index 00000000..5c037331 --- /dev/null +++ b/src/AuthProvider.tsx @@ -0,0 +1,26 @@ +import React, { createContext, useContext, useState, ReactNode } from "react"; + +interface AuthContextType { + token: string | null; + setToken: (token: string | null) => void; +} + +const AuthContext = createContext(undefined); + +export const useAuth = () => { + const context = useContext(AuthContext); + if (!context) { + throw new Error("useAuth must be used within an AuthProvider"); + } + return context; +}; + +export const AuthProvider = ({ children }: { children: ReactNode }) => { + const [token, setToken] = useState(null); + + return ( + + {children} + + ); +}; diff --git a/src/api/getMenus.ts b/src/api/getMenus.ts new file mode 100644 index 00000000..e312abd3 --- /dev/null +++ b/src/api/getMenus.ts @@ -0,0 +1,19 @@ +export const fetchMenus = async (token: string) => { + const response = await fetch('http://180.191.51.65:9130/api/menus', { + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${token}` + } + }); + + if (!response.ok) { + const errorData = await response.json(); + console.error('API Error Response:', errorData); + throw new Error(`Error: ${response.statusText} - ${errorData.message}`); + } + + const data = await response.json(); + return data.payload; + }; + + diff --git a/src/api/userLogin.ts b/src/api/userLogin.ts new file mode 100644 index 00000000..68fdf8b5 --- /dev/null +++ b/src/api/userLogin.ts @@ -0,0 +1,25 @@ +// loginUser.ts +export interface LoginResponse { + code: number; + status: string; + payload: { + BearerToken: string; + }; +} + +export const loginUser = async (credentials: { usrcde: string; usrpwd: string; }): Promise => { + const response = await fetch("http://180.191.51.65:9130/api/user-ess/login", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(credentials), + }); + + if (!response.ok) { + throw new Error(`Network response was not ok: ${response.statusText}`); + } + + const data = await response.json(); + return data; +}; diff --git a/src/assets/SidebarData.tsx b/src/assets/SidebarData.tsx new file mode 100644 index 00000000..52ad5020 --- /dev/null +++ b/src/assets/SidebarData.tsx @@ -0,0 +1,56 @@ +import React from "react"; +import DashboardIcon from "@mui/icons-material/Dashboard"; +import StarBorder from "@mui/icons-material/StarBorder"; +import EmojiPeopleIcon from "@mui/icons-material/EmojiPeople"; +import PaymentIcon from "@mui/icons-material/Payment"; +import ReportIcon from "@mui/icons-material/Report"; + +interface SidebarItem { + id: number; + label: string; + icon: React.ReactNode; + items?: SidebarItem[]; +} + +export const sidebarData: SidebarItem[] = [ + { + id: 1, + label: "Dashboard", + icon: , + items: [ + { id: 11, label: "Overview", icon: }, + { id: 12, label: "Stats", icon: }, + { id: 13, label: "Reports", icon: }, + ], + }, + { + id: 2, + label: "Employees", + icon: , + items: [ + { id: 21, label: "All Employees", icon: }, + { id: 22, label: "Add Employee", icon: }, + { id: 23, label: "Manage Employees", icon: }, + ], + }, + { + id: 3, + label: "Payroll", + icon: , + items: [ + { id: 31, label: "Monthly Payroll", icon: }, + { id: 32, label: "Annual Payroll", icon: }, + { id: 33, label: "Payroll Settings", icon: }, + ], + }, + { + id: 4, + label: "Reports", + icon: , + items: [ + { id: 41, label: "Payroll Reports", icon: }, + { id: 42, label: "Employee Reports", icon: }, + { id: 43, label: "Tax Reports", icon: }, + ], + }, +]; diff --git a/src/axiosConfig.ts b/src/axiosConfig.ts new file mode 100644 index 00000000..09da4190 --- /dev/null +++ b/src/axiosConfig.ts @@ -0,0 +1,10 @@ +import axios from 'axios'; + +const axiosInstance = axios.create({ + baseURL: 'http://180.191.51.65:9130/api', + headers: { + 'Content-Type': 'application/json', + }, +}); + +export default axiosInstance; diff --git a/src/components/AdvancedTable.tsx b/src/components/AdvancedTable.tsx new file mode 100644 index 00000000..e69de29b diff --git a/src/components/Chart.tsx b/src/components/Chart.tsx new file mode 100644 index 00000000..f1cfccf6 --- /dev/null +++ b/src/components/Chart.tsx @@ -0,0 +1,84 @@ +import * as React from "react"; +import { useTheme } from "@mui/material/styles"; +import { LineChart, axisClasses } from "@mui/x-charts"; +import { ChartsTextStyle } from "@mui/x-charts/ChartsText"; +import Title from "./Title"; + +// Generate Sales Data +function createData( + time: string, + amount?: number +): { time: string; amount: number | null } { + return { time, amount: amount ?? null }; +} + +const data = [ + createData("00:00", 0), + createData("03:00", 300), + createData("06:00", 600), + createData("09:00", 800), + createData("12:00", 1500), + createData("15:00", 2000), + createData("18:00", 2400), + createData("21:00", 2400), + createData("24:00"), +]; + +export default function Chart() { + const theme = useTheme(); + + return ( + + Today +
+ +
+
+ ); +} diff --git a/src/components/Deposits.tsx b/src/components/Deposits.tsx new file mode 100644 index 00000000..d6abd070 --- /dev/null +++ b/src/components/Deposits.tsx @@ -0,0 +1,27 @@ +import * as React from "react"; +import Link from "@mui/material/Link"; +import Typography from "@mui/material/Typography"; +import Title from "@/components/Title"; + +function preventDefault(event: React.MouseEvent) { + event.preventDefault(); +} + +export default function Deposits() { + return ( + + Recent Deposits + + $3,024.00 + + + on 15 March, 2019 + +
+ + View balance + +
+
+ ); +} diff --git a/src/components/ItemList.tsx b/src/components/ItemList.tsx new file mode 100644 index 00000000..4ab3cea2 --- /dev/null +++ b/src/components/ItemList.tsx @@ -0,0 +1,106 @@ +import React, { useEffect, useState } from "react"; +import { styled } from "@mui/system"; +import List from "@mui/material/List"; +import ListItem from "@mui/material/ListItem"; +import ListItemIcon from "@mui/material/ListItemIcon"; +import ListItemText from "@mui/material/ListItemText"; +import Collapse from "@mui/material/Collapse"; +import ExpandLess from "@mui/icons-material/ExpandLess"; +import ExpandMore from "@mui/icons-material/ExpandMore"; +import { Box } from "@mui/material"; +import useAuthStore from "../store/useStore"; // Adjust the import path + +const Nested = styled(ListItem)(({ theme }) => ({ + paddingLeft: theme.spacing(6), +})); + +const NestedSecondLevel = styled(ListItem)(({ theme }) => ({ + paddingLeft: theme.spacing(8), +})); + +export function ItemList() { + const [open, setOpen] = useState(null); + const [openSecondLevel, setOpenSecondLevel] = useState(null); + const fetchMenus = useAuthStore((state) => state.fetchMenus); + const menus = useAuthStore((state) => state.menus); + const isLoading = !menus.length; + + // Fetch menus + useEffect(() => { + fetchMenus().catch((error) => + console.error("Failed to fetch menus:", error) + ); + }, [fetchMenus]); + + const handleClick = (item: string) => { + setOpen(open === item ? null : item); + }; + + const handleClickSecondLevel = (item: string) => { + setOpenSecondLevel(openSecondLevel === item ? null : item); + }; + + const handleMouseLeave = () => { + setOpen(null); + setOpenSecondLevel(null); + }; + + if (isLoading) return
Loading...
; + + // Filter primary menus + const primaryMenus = menus.filter( + (menu: any) => menu.mengrp == "01" && menu.is_active == 1 + ); + + console.log("Menus", primaryMenus); + + // Create a map to hold submenus + const subMenuMap = menus.reduce((acc: any, menu: any) => { + if (menu.menlvl === "2.00000") { + if (!acc[menu.mengrp]) { + acc[menu.mengrp] = []; + } + acc[menu.mengrp].push(menu); + } + return acc; + }, {}); + + return ( + + {primaryMenus.map((menu: any) => ( + + handleClick(menu.mencap)}> + {/* Add appropriate icon here */} + + {open === menu.mencap ? : } + + + + + {subMenuMap[menu.mengrp]?.map((subItem: any) => ( + handleClickSecondLevel(subItem.mencap)} + > + {/* Add appropriate icon here */} + + {openSecondLevel === subItem.mencap ? ( + + ) : ( + + )} + + ))} + + + + ))} + + ); +} diff --git a/src/components/Orders.tsx b/src/components/Orders.tsx new file mode 100644 index 00000000..3dd51fe2 --- /dev/null +++ b/src/components/Orders.tsx @@ -0,0 +1,99 @@ +import * as React from "react"; +import Link from "@mui/material/Link"; +import Table from "@mui/material/Table"; +import TableBody from "@mui/material/TableBody"; +import TableCell from "@mui/material/TableCell"; +import TableHead from "@mui/material/TableHead"; +import TableRow from "@mui/material/TableRow"; + +// Generate Order Data +function createData( + id: number, + date: string, + name: string, + shipTo: string, + paymentMethod: string, + amount: number +) { + return { id, date, name, shipTo, paymentMethod, amount }; +} + +const rows = [ + createData( + 0, + "16 Mar, 2019", + "Elvis Presley", + "Tupelo, MS", + "VISA ⠀•••• 3719", + 312.44 + ), + createData( + 1, + "16 Mar, 2019", + "Paul McCartney", + "London, UK", + "VISA ⠀•••• 2574", + 866.99 + ), + createData( + 2, + "16 Mar, 2019", + "Tom Scholz", + "Boston, MA", + "MC ⠀•••• 1253", + 100.81 + ), + createData( + 3, + "16 Mar, 2019", + "Michael Jackson", + "Gary, IN", + "AMEX ⠀•••• 2000", + 654.39 + ), + createData( + 4, + "15 Mar, 2019", + "Bruce Springsteen", + "Long Branch, NJ", + "VISA ⠀•••• 5919", + 212.79 + ), +]; + +function preventDefault(event: React.MouseEvent) { + event.preventDefault(); +} + +export default function Orders() { + return ( + + Recent Orders + + + + Date + Name + Ship To + Payment Method + Sale Amount + + + + {rows.map((row) => ( + + {row.date} + {row.name} + {row.shipTo} + {row.paymentMethod} + {`$${row.amount}`} + + ))} + +
+ + See more orders + +
+ ); +} diff --git a/src/components/Title.tsx b/src/components/Title.tsx new file mode 100644 index 00000000..c9109cd6 --- /dev/null +++ b/src/components/Title.tsx @@ -0,0 +1,14 @@ +import * as React from "react"; +import Typography from "@mui/material/Typography"; + +interface TitleProps { + children?: React.ReactNode; +} + +export default function Title(props: TitleProps) { + return ( + + {props.children} + + ); +} diff --git a/src/components/update/Modal/index.tsx b/src/components/update/Modal/index.tsx deleted file mode 100644 index 934f1a2b..00000000 --- a/src/components/update/Modal/index.tsx +++ /dev/null @@ -1,67 +0,0 @@ -import React, { ReactNode } from 'react' -import { createPortal } from 'react-dom' -import './modal.css' - -const ModalTemplate: React.FC void - onOk?: () => void - width?: number -}>> = props => { - const { - title, - children, - footer, - cancelText = 'Cancel', - okText = 'OK', - onCancel, - onOk, - width = 530, - } = props - - return ( -
-
-
-
-
-
{title}
- - - - - - -
-
{children}
- {typeof footer !== 'undefined' ? ( -
- - -
- ) : footer} -
-
-
- ) -} - -const Modal = (props: Parameters[0] & { open: boolean }) => { - const { open, ...omit } = props - - return createPortal( - open ? ModalTemplate(omit) : null, - document.body, - ) -} - -export default Modal diff --git a/src/components/update/Modal/modal.css b/src/components/update/Modal/modal.css deleted file mode 100644 index d52cacba..00000000 --- a/src/components/update/Modal/modal.css +++ /dev/null @@ -1,87 +0,0 @@ -.update-modal { - --primary-color: rgb(224, 30, 90); - - .update-modal__mask { - width: 100vw; - height: 100vh; - position: fixed; - left: 0; - top: 0; - z-index: 9; - background: rgba(0, 0, 0, 0.45); - } - - .update-modal__warp { - position: fixed; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - z-index: 19; - } - - .update-modal__content { - box-shadow: 0 0 10px -4px rgb(130, 86, 208); - overflow: hidden; - border-radius: 4px; - - .content__header { - display: flex; - line-height: 38px; - background-color: var(--primary-color); - - .content__header-text { - font-weight: bold; - width: 0; - flex-grow: 1; - } - } - - .update-modal--close { - width: 30px; - height: 30px; - margin: 4px; - line-height: 34px; - text-align: center; - cursor: pointer; - - svg { - width: 17px; - height: 17px; - } - } - - .content__body { - padding: 10px; - background-color: #fff; - color: #333; - } - - .content__footer { - padding: 10px; - background-color: #fff; - display: flex; - justify-content: flex-end; - - button { - padding: 7px 11px; - background-color: var(--primary-color); - font-size: 14px; - margin-left: 10px; - - &:first-child { - margin-left: 0; - } - } - } - } - - .icon { - padding: 0 15px; - width: 20px; - fill: currentColor; - - &:hover { - color: rgba(0, 0, 0, 0.4); - } - } -} \ No newline at end of file diff --git a/src/components/update/Progress/index.tsx b/src/components/update/Progress/index.tsx deleted file mode 100644 index 1701dc57..00000000 --- a/src/components/update/Progress/index.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import React from 'react' -import './progress.css' - -const Progress: React.FC> = props => { - const { percent = 0 } = props - - return ( -
-
-
-
- {(percent ?? 0).toString().substring(0, 4)}% -
- ) -} - -export default Progress diff --git a/src/components/update/Progress/progress.css b/src/components/update/Progress/progress.css deleted file mode 100644 index 2cef7c1b..00000000 --- a/src/components/update/Progress/progress.css +++ /dev/null @@ -1,21 +0,0 @@ -.update-progress { - display: flex; - align-items: center; - - .update-progress-pr { - border: 1px solid #000; - border-radius: 3px; - height: 6px; - width: 300px; - } - - .update-progress-rate { - height: 6px; - border-radius: 3px; - background-image: linear-gradient(to right, rgb(130, 86, 208) 0%, var(--primary-color) 100%) - } - - .update-progress-num { - margin: 0 10px; - } -} \ No newline at end of file diff --git a/src/components/update/README.md b/src/components/update/README.md deleted file mode 100644 index d119a117..00000000 --- a/src/components/update/README.md +++ /dev/null @@ -1,49 +0,0 @@ -# electron-updater - -English | [简体中文](README.zh-CN.md) - -> Use `electron-updater` to realize the update detection, download and installation of the electric program. - -```sh -npm i electron-updater -``` - -### Main logic - -1. ##### Configuration of the update the service address and update information script: - - Add a `publish` field to `electron-builder.json5` for configuring the update address and which strategy to use as the update service. - - ``` json5 - { - "publish": { - "provider": "generic", - "channel": "latest", - "url": "https://foo.com/" - } - } - ``` - - For more information, please refer to : [electron-builder.json5...](https://github.com/electron-vite/electron-vite-react/blob/2f2880a9f19de50ff14a0785b32a4d5427477e55/electron-builder.json5#L38) - -2. ##### The update logic of Electron: - - - Checking if an update is available; - - Checking the version of the software on the server; - - Checking if an update is available; - - Downloading the new version of the software from the server (when an update is available); - - Installation method; - - For more information, please refer to : [update...](https://github.com/electron-vite/electron-vite-react/blob/main/electron/main/update.ts) - -3. ##### Updating UI pages in Electron: - - The main function is to provide a UI page for users to trigger the update logic mentioned in (2.) above. Users can click on the page to trigger different update functions in Electron. - - For more information, please refer to : [components/update...](https://github.com/electron-vite/electron-vite-react/blob/main/src/components/update/index.tsx) - ---- - -Here it is recommended to trigger updates through user actions (in this project, Electron update function is triggered after the user clicks on the "Check for updates" button). - -For more information on using `electron-updater` for Electron updates, please refer to the documentation : [auto-update](https://www.electron.build/.html) diff --git a/src/components/update/README.zh-CN.md b/src/components/update/README.zh-CN.md deleted file mode 100644 index f2c65fcd..00000000 --- a/src/components/update/README.zh-CN.md +++ /dev/null @@ -1,51 +0,0 @@ -# electron-auto-update - -[English](README.md) | 简体中文 - -使用`electron-updater`实现electron程序的更新检测、下载和安装等功能。 - -```sh -npm i electron-updater -``` - -### 主要逻辑 - -1. ##### 更新地址、更新信息脚本的配置 - - 在`electron-builder.json5`添加`publish`字段,用来配置更新地址和使用哪种策略作为更新服务 - - ``` json5 - { - "publish": { - "provider": "generic", // 提供者、提供商 - "channel": "latest", // 生成yml文件的名称 - "url": "https://foo.com/" //更新地址 - } - } - ``` - -更多见 : [electron-builder.json5...](xxx) - -2. ##### Electron更新逻辑 - - - 检测更新是否可用; - - - 检测服务端的软件版本; - - - 检测更新是否可用; - - - 下载服务端新版软件(当更新可用); - - 安装方式; - - 更多见 : [update...](https://github.com/electron-vite/electron-vite-react/blob/main/electron/main/update.ts) - -3. ##### Electron更新UI页面 - - 主要功能是:用户触发上述(2.)更新逻辑的UI页面。用户可以通过点击页面触发electron更新的不同功能。 - 更多见 : [components/update.ts...](https://github.com/electron-vite/electron-vite-react/tree/main/src/components/update/index.tsx) - ---- - -这里建议更新触发以用户操作触发(本项目的以用户点击 **更新检测** 后触发electron更新功能) - -关于更多使用`electron-updater`进行electron更新,见文档:[auto-update](https://www.electron.build/.html) diff --git a/src/components/update/index.tsx b/src/components/update/index.tsx deleted file mode 100644 index 2f73940e..00000000 --- a/src/components/update/index.tsx +++ /dev/null @@ -1,132 +0,0 @@ -import type { ProgressInfo } from 'electron-updater' -import { useCallback, useEffect, useState } from 'react' -import Modal from '@/components/update/Modal' -import Progress from '@/components/update/Progress' -import './update.css' - -const Update = () => { - const [checking, setChecking] = useState(false) - const [updateAvailable, setUpdateAvailable] = useState(false) - const [versionInfo, setVersionInfo] = useState() - const [updateError, setUpdateError] = useState() - const [progressInfo, setProgressInfo] = useState>() - const [modalOpen, setModalOpen] = useState(false) - const [modalBtn, setModalBtn] = useState<{ - cancelText?: string - okText?: string - onCancel?: () => void - onOk?: () => void - }>({ - onCancel: () => setModalOpen(false), - onOk: () => window.ipcRenderer.invoke('start-download'), - }) - - const checkUpdate = async () => { - setChecking(true) - /** - * @type {import('electron-updater').UpdateCheckResult | null | { message: string, error: Error }} - */ - const result = await window.ipcRenderer.invoke('check-update') - setProgressInfo({ percent: 0 }) - setChecking(false) - setModalOpen(true) - if (result?.error) { - setUpdateAvailable(false) - setUpdateError(result?.error) - } - } - - const onUpdateCanAvailable = useCallback((_event: Electron.IpcRendererEvent, arg1: VersionInfo) => { - setVersionInfo(arg1) - setUpdateError(undefined) - // Can be update - if (arg1.update) { - setModalBtn(state => ({ - ...state, - cancelText: 'Cancel', - okText: 'Update', - onOk: () => window.ipcRenderer.invoke('start-download'), - })) - setUpdateAvailable(true) - } else { - setUpdateAvailable(false) - } - }, []) - - const onUpdateError = useCallback((_event: Electron.IpcRendererEvent, arg1: ErrorType) => { - setUpdateAvailable(false) - setUpdateError(arg1) - }, []) - - const onDownloadProgress = useCallback((_event: Electron.IpcRendererEvent, arg1: ProgressInfo) => { - setProgressInfo(arg1) - }, []) - - const onUpdateDownloaded = useCallback((_event: Electron.IpcRendererEvent, ...args: any[]) => { - setProgressInfo({ percent: 100 }) - setModalBtn(state => ({ - ...state, - cancelText: 'Later', - okText: 'Install now', - onOk: () => window.ipcRenderer.invoke('quit-and-install'), - })) - }, []) - - useEffect(() => { - // Get version information and whether to update - window.ipcRenderer.on('update-can-available', onUpdateCanAvailable) - window.ipcRenderer.on('update-error', onUpdateError) - window.ipcRenderer.on('download-progress', onDownloadProgress) - window.ipcRenderer.on('update-downloaded', onUpdateDownloaded) - - return () => { - window.ipcRenderer.off('update-can-available', onUpdateCanAvailable) - window.ipcRenderer.off('update-error', onUpdateError) - window.ipcRenderer.off('download-progress', onDownloadProgress) - window.ipcRenderer.off('update-downloaded', onUpdateDownloaded) - } - }, []) - - return ( - <> - -
- {updateError - ? ( -
-

Error downloading the latest version.

-

{updateError.message}

-
- ) : updateAvailable - ? ( -
-
The last version is: v{versionInfo?.newVersion}
-
v{versionInfo?.version} -> v{versionInfo?.newVersion}
-
-
Update progress:
-
- -
-
-
- ) - : ( -
{JSON.stringify(versionInfo ?? {}, null, 2)}
- )} -
-
- - - ) -} - -export default Update diff --git a/src/components/update/update.css b/src/components/update/update.css deleted file mode 100644 index e7769684..00000000 --- a/src/components/update/update.css +++ /dev/null @@ -1,24 +0,0 @@ -.modal-slot { - .update-progress { - display: flex; - } - - .new-version__target, - .update__progress { - margin-left: 40px; - } - - .progress__title { - margin-right: 10px; - } - - .progress__bar { - width: 0; - flex-grow: 1; - } - - .can-not-available { - padding: 20px; - text-align: center; - } -} \ No newline at end of file diff --git a/src/demos/ipc.ts b/src/demos/ipc.ts deleted file mode 100644 index ba4daa0c..00000000 --- a/src/demos/ipc.ts +++ /dev/null @@ -1,4 +0,0 @@ - -window.ipcRenderer.on('main-process-message', (_event, ...args) => { - console.log('[Receive Main-process message]:', ...args) -}) diff --git a/src/demos/node.ts b/src/demos/node.ts deleted file mode 100644 index 277e6a34..00000000 --- a/src/demos/node.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { lstat } from 'node:fs/promises' -import { cwd } from 'node:process' - -lstat(cwd()).then(stats => { - console.log('[fs.lstat]', stats) -}).catch(err => { - console.error(err) -}) diff --git a/src/hooks/useMenu.ts b/src/hooks/useMenu.ts new file mode 100644 index 00000000..b46735d3 --- /dev/null +++ b/src/hooks/useMenu.ts @@ -0,0 +1,10 @@ +import { useQuery } from 'react-query'; +import { fetchMenus } from '../api/getMenus'; +import useAuthStore from '../store/useStore'; // Adjust the path as necessary + +export const useMenus = () => { + const token = useAuthStore((state) => state.token); + return useQuery(['menus', token], () => fetchMenus(token!), { + enabled: !!token, + }); +}; \ No newline at end of file diff --git a/src/index.css b/src/index.css index f6ec8bae..b5c61c95 100644 --- a/src/index.css +++ b/src/index.css @@ -1,94 +1,3 @@ @tailwind base; @tailwind components; @tailwind utilities; - -:root { - font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; - line-height: 1.5; - font-weight: 400; - - color-scheme: light dark; - color: rgba(255, 255, 255, 0.87); - background-color: #242424; - - font-synthesis: none; - text-rendering: optimizeLegibility; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; - -webkit-text-size-adjust: 100%; -} - -a { - font-weight: 500; - color: #646cff; - text-decoration: inherit; -} -a:hover { - color: #535bf2; -} - -body { - margin: 0; - display: flex; - place-items: center; - min-width: 320px; - min-height: 100vh; -} - -h1 { - font-size: 3.2em; - line-height: 1.1; -} - -button { - border-radius: 8px; - border: 1px solid transparent; - padding: 0.6em 1.2em; - font-size: 1em; - font-weight: 500; - font-family: inherit; - background-color: #1a1a1a; - cursor: pointer; - transition: border-color 0.25s; -} -button:hover { - border-color: #646cff; -} -button:focus, -button:focus-visible { - outline: 4px auto -webkit-focus-ring-color; -} - -code { - background-color: #1a1a1a; - padding: 2px 4px; - margin: 0 4px; - border-radius: 4px; -} - -.card { - padding: 2em; -} - -#app { - max-width: 1280px; - margin: 0 auto; - padding: 2rem; - text-align: center; -} - -@media (prefers-color-scheme: light) { - :root { - color: #213547; - background-color: #ffffff; - } - a:hover { - color: #747bff; - } - button { - background-color: #f9f9f9; - } - code { - background-color: #f9f9f9; - } -} diff --git a/src/main.tsx b/src/main.tsx index baaff398..c9d06a55 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -1,17 +1,38 @@ -import React from 'react' -import ReactDOM from 'react-dom/client' -import App from './App' +import React from "react"; +import ReactDOM from "react-dom/client"; +import { QueryClient, QueryClientProvider } from "react-query"; +import App from "./App"; +import "./index.css"; +import { createHashRouter, RouterProvider } from "react-router-dom"; +import Login from "./page/Login"; +import Dashboard from "./page/Dashboard"; +import { AuthProvider } from "./AuthProvider"; -import './index.css' +const router = createHashRouter([ + { + path: "/", + element: , + children: [ + { + path: "dashboard", + element: , + }, + { + path: "/", + element: , + }, + ], + }, +]); -import './demos/ipc' -// If you want use Node.js, the`nodeIntegration` needs to be enabled in the Main process. -// import './demos/node' +const queryClient = new QueryClient(); -ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render( +ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render( - - , -) - -postMessage({ payload: 'removeLoading' }, '*') + + + + + + +); diff --git a/src/page/Dashboard.tsx b/src/page/Dashboard.tsx new file mode 100644 index 00000000..32712553 --- /dev/null +++ b/src/page/Dashboard.tsx @@ -0,0 +1,142 @@ +import * as React from "react"; +import { styled, createTheme, ThemeProvider } from "@mui/material/styles"; +import CssBaseline from "@mui/material/CssBaseline"; +import MuiDrawer from "@mui/material/Drawer"; +import Box from "@mui/material/Box"; +import MuiAppBar, { AppBarProps as MuiAppBarProps } from "@mui/material/AppBar"; +import Toolbar from "@mui/material/Toolbar"; +import List from "@mui/material/List"; +import Typography from "@mui/material/Typography"; +import Divider from "@mui/material/Divider"; +import IconButton from "@mui/material/IconButton"; +import Badge from "@mui/material/Badge"; +import Link from "@mui/material/Link"; +import NotificationsIcon from "@mui/icons-material/Notifications"; + +import { ItemList } from "@/components/ItemList"; + +function Copyright(props: any) { + return ( + + {"Copyright © "} + + Your Website + {" "} + {new Date().getFullYear()} + {"."} + + ); +} + +const drawerWidth: number = 240; + +interface AppBarProps extends MuiAppBarProps { + open?: boolean; +} + +const AppBar = styled(MuiAppBar, { + shouldForwardProp: (prop) => prop !== "open", +})(({ theme, open }) => ({ + zIndex: theme.zIndex.drawer + 1, + transition: theme.transitions.create(["width", "margin"], { + easing: theme.transitions.easing.sharp, + duration: theme.transitions.duration.leavingScreen, + }), +})); + +const Drawer = styled(MuiDrawer, { + shouldForwardProp: (prop) => prop !== "open", +})(({ theme, open }) => ({ + "& .MuiDrawer-paper": { + position: "relative", + whiteSpace: "nowrap", + width: drawerWidth, + transition: theme.transitions.create("width", { + easing: theme.transitions.easing.sharp, + duration: theme.transitions.duration.enteringScreen, + }), + boxSizing: "border-box", + ...(!open && { + overflowX: "hidden", + transition: theme.transitions.create("width", { + easing: theme.transitions.easing.sharp, + duration: theme.transitions.duration.leavingScreen, + }), + width: theme.spacing(7), + [theme.breakpoints.up("sm")]: { + width: theme.spacing(9), + }, + }), + }, +})); + +const defaultTheme = createTheme(); + +export default function Dashboard() { + const [open, setOpen] = React.useState(true); + + const handleMouseEnter = () => { + if (!open) { + setOpen(true); + } + }; + + const handleMouseLeave = () => { + if (open) { + setOpen(false); + } + }; + return ( + + + + + + + Dashboard + + + + + + + + + + + + + + + + + + + ); +} diff --git a/src/page/Login.tsx b/src/page/Login.tsx new file mode 100644 index 00000000..49d12d51 --- /dev/null +++ b/src/page/Login.tsx @@ -0,0 +1,160 @@ +import React, { useState } from "react"; +import { + Avatar, + Button, + CssBaseline, + TextField, + Link, + Paper, + Box, + Grid, + Typography, +} from "@mui/material"; +import LockOutlinedIcon from "@mui/icons-material/LockOutlined"; +import { createTheme, ThemeProvider } from "@mui/material/styles"; +import { useNavigate } from "react-router-dom"; +import useAuthStore from "../store/useStore"; + +const defaultTheme = createTheme(); + +function Copyright(props: any) { + return ( + + {"Copyright © "} + + Your Website + {" "} + {new Date().getFullYear()} + {"."} + + ); +} + +const loginUser = async (credentials: { usrcde: string; usrpwd: string }) => { + const response = await fetch("http://180.191.51.65:9130/api/user-ess/login", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(credentials), + }); + + if (!response.ok) { + throw new Error(`Network response was not ok: ${response.statusText}`); + } + + const data = await response.json(); + localStorage.setItem("token", JSON.stringify(data.payload)); + return data; +}; + +export default function Login() { + const [error, setError] = useState(""); + const navigate = useNavigate(); + const setUser = useAuthStore((state) => state.setUser); + + const handleSubmit = async (event: React.FormEvent) => { + event.preventDefault(); + const data = new FormData(event.currentTarget); + const usrcde = data.get("username") as string; + const usrpwd = data.get("password") as string; + setError(""); + + try { + const userData = await loginUser({ usrcde, usrpwd }); + setUser(userData.payload.BearerToken); + navigate("/dashboard"); + } catch (error) { + setError("Login failed. Please check your credentials and try again."); + } + }; + + return ( + + + + + t.palette.mode === "light" + ? t.palette.grey[50] + : t.palette.grey[900], + backgroundSize: "cover", + backgroundPosition: "center", + }} + /> + + + + + + + Sign in + + + + + + {error && ( + + {error} + + )} + + + + + + + ); +} diff --git a/src/store/useStore.ts b/src/store/useStore.ts new file mode 100644 index 00000000..7f8c7ec9 --- /dev/null +++ b/src/store/useStore.ts @@ -0,0 +1,50 @@ +import { create } from 'zustand'; + +interface AuthState { + token: string | null; + menus: any[]; + setUser: (token: string) => void; + fetchMenus: () => void; +} + +const useAuthStore = create((set) => ({ + token: null, + menus: [], + setUser: (token) => set({ token }), + fetchMenus: async () => { + const token = getToken(); + if (!token) { + throw new Error('No token found'); + } + + const response = await fetch('http://180.191.51.65:9130/api/menus', { + headers: { + 'Authorization': `Bearer ${token}`, + }, + }); + + if (!response.ok) { + throw new Error('Failed to fetch menus'); + } + + const data = await response.json(); + set({ menus: data.payload }); + }, +})); + +const getToken = () => { + try { + const tokenString = localStorage.getItem('token'); + if (!tokenString) { + return null; + } + const token = JSON.parse(tokenString); + return token?.BearerToken || null; + } catch (error) { + console.error('Error parsing token from localStorage', error); + return null; + } +}; + + +export default useAuthStore; diff --git a/tsconfig.json b/tsconfig.json index c9c47659..b3bcaa79 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -19,7 +19,9 @@ "paths": { "@/*": [ "src/*" - ] + ], + "@components": ["./src/components"], + "@shared": ["./src/shared"] }, }, "include": ["src", "electron"],