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 + 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

-
-
-
+
+
- )
-}
+ );
+};
-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 (
-
-
-
-
-
-
{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}
-
-
- )
- : (
-
{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"],