diff --git a/build/icon.icns b/build/icon.icns new file mode 100644 index 0000000..b6699d1 Binary files /dev/null and b/build/icon.icns differ diff --git a/package-lock.json b/package-lock.json index 5cd7899..f8dbeff 100644 --- a/package-lock.json +++ b/package-lock.json @@ -84,6 +84,7 @@ "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", @@ -1626,6 +1627,7 @@ "integrity": "sha512-RFA/bURkcKzx/X9oumPG9Vp3D3JUgus/d0b67KB0t5S/raciymilkOa66olh78MUI92QLbEJevO7rvqU/kjwKA==", "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "@types/prop-types": "*", "csstype": "^3.0.2" @@ -1888,6 +1890,7 @@ "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -2090,7 +2093,6 @@ "integrity": "sha512-+25nxyyznAXF7Nef3y0EbBeqmGZgeN/BxHX29Rs39djAfaFalmQ89SE6CWyDCHzGL0yt/ycBtNOmGTW0FyGWNw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "archiver-utils": "^2.1.0", "async": "^3.2.4", @@ -2110,7 +2112,6 @@ "integrity": "sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "glob": "^7.1.4", "graceful-fs": "^4.2.0", @@ -2132,8 +2133,7 @@ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/archiver-utils/node_modules/readable-stream": { "version": "2.3.8", @@ -2141,7 +2141,6 @@ "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -2157,8 +2156,7 @@ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/archiver-utils/node_modules/string_decoder": { "version": "1.1.1", @@ -2166,7 +2164,6 @@ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "safe-buffer": "~5.1.0" } @@ -2429,6 +2426,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.8.25", "caniuse-lite": "^1.0.30001754", @@ -3028,7 +3026,6 @@ "integrity": "sha512-D3uMHtGc/fcO1Gt1/L7i1e33VOvD4A9hfQLP+6ewd+BvG/gQ84Yh4oftEhAdjSMgBgwGL+jsppT7JYNpo6MHHg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "buffer-crc32": "^0.2.13", "crc32-stream": "^4.0.2", @@ -3219,7 +3216,6 @@ "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", "dev": true, "license": "Apache-2.0", - "peer": true, "bin": { "crc32": "bin/crc32.njs" }, @@ -3233,7 +3229,6 @@ "integrity": "sha512-NT7w2JVU7DFroFdYkeq8cywxrgjPHWkdX1wjpRQXPX5Asews3tA+Ght6lddQO5Mkumffp3X7GEqku3epj2toIw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "crc-32": "^1.2.0", "readable-stream": "^3.4.0" @@ -3715,6 +3710,7 @@ "integrity": "sha512-NoXo6Liy2heSklTI5OIZbCgXC1RzrDQsZkeEwXhdOro3FT1VBOvbubvscdPnjVuQ4AMwwv61oaH96AbiYg9EnQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "app-builder-lib": "25.1.8", "builder-util": "25.1.7", @@ -3907,7 +3903,6 @@ "integrity": "sha512-2ntkJ+9+0GFP6nAISiMabKt6eqBB0kX1QqHNWFWAXgi0VULKGisM46luRFpIBiU3u/TDmhZMM8tzvo2Abn3ayg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "app-builder-lib": "25.1.8", "archiver": "^5.3.1", @@ -3921,7 +3916,6 @@ "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", @@ -3937,7 +3931,6 @@ "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "universalify": "^2.0.0" }, @@ -3951,7 +3944,6 @@ "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">= 10.0.0" } @@ -4864,8 +4856,7 @@ "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/fs-extra": { "version": "8.1.0", @@ -5751,7 +5742,7 @@ "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" @@ -6502,7 +6493,6 @@ "integrity": "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "readable-stream": "^2.0.5" }, @@ -6515,8 +6505,7 @@ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/lazystream/node_modules/readable-stream": { "version": "2.3.8", @@ -6524,7 +6513,6 @@ "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -6540,8 +6528,7 @@ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/lazystream/node_modules/string_decoder": { "version": "1.1.1", @@ -6549,7 +6536,6 @@ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "safe-buffer": "~5.1.0" } @@ -6642,24 +6628,21 @@ "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/lodash.difference": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.difference/-/lodash.difference-4.5.0.tgz", "integrity": "sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/lodash.flatten": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", "integrity": "sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/lodash.isequal": { "version": "4.5.0", @@ -6673,16 +6656,14 @@ "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/lodash.union": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz", "integrity": "sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/log-symbols": { "version": "4.1.0", @@ -7487,7 +7468,6 @@ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -8173,8 +8153,7 @@ "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/progress": { "version": "2.0.3", @@ -8317,6 +8296,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", "license": "MIT", + "peer": true, "dependencies": { "loose-envify": "^1.1.0" }, @@ -8329,6 +8309,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", "license": "MIT", + "peer": true, "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" @@ -8363,6 +8344,7 @@ "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz", "integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==", "license": "MIT", + "peer": true, "dependencies": { "@types/use-sync-external-store": "^0.0.6", "use-sync-external-store": "^1.4.0" @@ -8509,7 +8491,6 @@ "integrity": "sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==", "dev": true, "license": "Apache-2.0", - "peer": true, "dependencies": { "minimatch": "^5.1.0" } @@ -8520,7 +8501,6 @@ "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "balanced-match": "^1.0.0" } @@ -8531,7 +8511,6 @@ "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", "dev": true, "license": "ISC", - "peer": true, "dependencies": { "brace-expansion": "^2.0.1" }, @@ -8575,7 +8554,8 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/redux-thunk": { "version": "3.1.0", @@ -9779,7 +9759,6 @@ "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "bl": "^4.0.3", "end-of-stream": "^1.4.1", @@ -10234,6 +10213,7 @@ "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.21.3", "postcss": "^8.4.43", @@ -10781,7 +10761,6 @@ "integrity": "sha512-9qv4rlDiopXg4E69k+vMHjNN63YFMe9sZMrdlvKnCjlCRWeCBswPPMPUfx+ipsAWq1LXHe70RcbaHdJJpS6hyQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "archiver-utils": "^3.0.4", "compress-commons": "^4.1.2", @@ -10797,7 +10776,6 @@ "integrity": "sha512-KVgf4XQVrTjhyWmx6cte4RxonPLR9onExufI1jhvw/MQ4BB6IsZD5gT8Lq+u/+pRkWna/6JoHpiQioaqFP5Rzw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "glob": "^7.2.3", "graceful-fs": "^4.2.0", diff --git a/src/electron/ipc/ipcHandler.ts b/src/electron/ipc/ipcHandler.ts index bc14fc3..e496bff 100644 --- a/src/electron/ipc/ipcHandler.ts +++ b/src/electron/ipc/ipcHandler.ts @@ -342,26 +342,44 @@ ipcMain.handle("create-timesheet", async (event, data) => { ipcMain.handle("create-leave-request", async (event, payload) => { try { - console.log(" Leave Request Received:", payload); + console.log("Leave Request Received:", payload); const response = await api.post("/leave/request", payload); - console.log(" Leave Saved:", response.data); + console.log("Leave Saved:", response); - return { success: true, data: response.data }; + return { + success: true, + data: response.data + }; - } catch (error) { - console.error("❌ Leave Save Failed:", error); + } catch (error: any) { + console.error("❌ Leave Save Failed:"); + + let message = "Unknown error"; + + if (error.response) { + console.log("Status:", error.response.status); + console.log("Message:", error.response.statusText); + console.log("Server Response:", error.response.data); + + // Extract the backend message properly + message = error.response.data?.message || error.response.statusText; + } else { + message = error.message; + } return { success: false, - error: error || "Unknown error" + message }; } }); + + export const sendTokenExpired = () => { console.log("⚠️ Sending token-expired event to renderer"); diff --git a/src/electron/main.ts b/src/electron/main.ts index e365f5d..943d6cc 100644 --- a/src/electron/main.ts +++ b/src/electron/main.ts @@ -87,7 +87,7 @@ app.on("ready", async () => { minimizable: false, maximizable: false, closable: true, - alwaysOnTop: true, + // alwaysOnTop: true, // skipTaskbar: true, autoHideMenuBar: true, frame: true, @@ -101,9 +101,24 @@ app.on("ready", async () => { }, }); - win.setAlwaysOnTop(true, 'screen-saver'); - win.setVisibleOnAllWorkspaces(true, { visibleOnFullScreen: true }); - win.setFullScreenable(false); + + + // win.setAlwaysOnTop(true, 'screen-saver'); + // win.setVisibleOnAllWorkspaces(true, { visibleOnFullScreen: true }); + // win.setFullScreenable(false); + + // Cross-platform safe always-on-top + const isMac = process.platform === "darwin"; + + if (isMac) { + win.setAlwaysOnTop(true, "floating"); // best for macOS + win.setVisibleOnAllWorkspaces(true); // NO fullscreen override + } else { + win.setAlwaysOnTop(true, "normal"); // safest for Windows + Zoom + win.setVisibleOnAllWorkspaces(true, { visibleOnFullScreen: true }); + win.setFullScreenable(false); +} + mainWindow = win; diff --git a/src/ui/App.tsx b/src/ui/App.tsx index fbbf871..72f3ab8 100644 --- a/src/ui/App.tsx +++ b/src/ui/App.tsx @@ -94,6 +94,7 @@ import Timesheets from "./pages/Timesheets/Timesheets"; import Settings from "./pages/Settings/Settings"; import HelpSupport from "./pages/HelpSupport/HelpSupport"; import Vpn from "./pages/Vpn/Vpn"; +import Schedule from "./pages/Schedule/Schedule"; const App: React.FC = () => { @@ -140,6 +141,7 @@ const App: React.FC = () => { } /> } /> } /> + } /> {/* Default Redirect */} diff --git a/src/ui/layout/DashboardLayout.tsx b/src/ui/layout/DashboardLayout.tsx index afb1615..f7909fb 100644 --- a/src/ui/layout/DashboardLayout.tsx +++ b/src/ui/layout/DashboardLayout.tsx @@ -26,6 +26,7 @@ import { Play, ChevronUp, GlobeLock, + ClipboardClock, } from "lucide-react"; import { useActiveAppTracker } from "../hooks/useActiveAppTracker"; import { useDispatch, useSelector } from "react-redux"; @@ -111,6 +112,7 @@ const DashboardLayout = () => { { label: "Dashboard", path: "/dashboard", icon: , exact: true }, { label: "Timesheet", path: "/dashboard/timesheet", icon: }, { label: "Leave & Adjust", path: "/dashboard/leave-adjustments", icon: }, + { label: "Schedule", path: "/dashboard/schedule", icon: }, { label: "Settings", path: "/dashboard/settings", icon: }, { label: "VPN", path: "/dashboard/vpn", icon: }, { label: "Help & Support", path: "/dashboard/help-support", icon: }, diff --git a/src/ui/pages/LeaveAndAdjustments/LeaveAndAdjustments.tsx b/src/ui/pages/LeaveAndAdjustments/LeaveAndAdjustments.tsx index c3c9716..8c71d47 100644 --- a/src/ui/pages/LeaveAndAdjustments/LeaveAndAdjustments.tsx +++ b/src/ui/pages/LeaveAndAdjustments/LeaveAndAdjustments.tsx @@ -25,16 +25,19 @@ const LeaveAndAdjustments: React.FC = () => { console.log("Leave /sick Payload:", payload); - return - const response = await window.electron.createPTOAndSickLeaveRequest(payload) - console.log("Leave Payload:" ,response) + console.log("Leave Payload:", response) if (response.success) { - return true; + return { success: true }; } + + return { + success: false, + message: response.message + }; } catch (err) { console.error(err); - return false; + return { success: false, message: "Request failed" }; } finally { setIsSubmitting(false); } @@ -49,9 +52,8 @@ const LeaveAndAdjustments: React.FC = () => {
setActiveTab("pto")} > PTO Leave @@ -59,9 +61,8 @@ const LeaveAndAdjustments: React.FC = () => { setActiveTab("sick")} > Sick Leave @@ -69,9 +70,8 @@ const LeaveAndAdjustments: React.FC = () => { setActiveTab("adjustment")} > Adjustment Submission diff --git a/src/ui/pages/LeaveAndAdjustments/tabs/AdjustmentTab.tsx b/src/ui/pages/LeaveAndAdjustments/tabs/AdjustmentTab.tsx index 4dcf8a3..f923f9a 100644 --- a/src/ui/pages/LeaveAndAdjustments/tabs/AdjustmentTab.tsx +++ b/src/ui/pages/LeaveAndAdjustments/tabs/AdjustmentTab.tsx @@ -222,28 +222,96 @@ const AdjustmentTab: React.FC = ({ projects }) => { useEffect(() => { const editor = quillRef.current?.getEditor(); - if (!editor) return; - editor.getModule("clipboard").addMatcher(Node.ELEMENT_NODE, (node: any, delta: any) => { - return delta; - }); + // Override image handler in toolbar + editor.getModule("toolbar").handlers.image = () => { + const input = document.createElement("input"); + input.setAttribute("type", "file"); + input.setAttribute("accept", "image/*"); + input.click(); - editor.root.addEventListener("paste", async (e: any) => { - const items = e.clipboardData?.items; - - if (!items) return; - - for (const item of items) { - if (item.type.indexOf("image") === 0) { - const file = item.getAsFile(); + input.onchange = async () => { + const file = input.files?.[0]; + if (file) { const base64 = await convertToBase64(file); - const range = editor.getSelection(); - editor.insertEmbed(range.index, "image", base64); + if (range) { + editor.insertEmbed(range.index, "image", base64); + } + } + }; + }; + + // COMPLETELY disable Quill's default paste behavior + const disableQuillPaste = () => { + // Remove all existing paste event listeners from Quill + const pasteListeners = (editor.root as any).__pasteHandlers || []; + pasteListeners.forEach((handler: any) => { + editor.root.removeEventListener('paste', handler); + }); + + // Override the clipboard module to do nothing + const clipboard = editor.getModule('clipboard'); + const originalPaste = clipboard.paste; + clipboard.paste = (e: any) => { + // Completely prevent Quill's default paste behavior + e.preventDefault(); + e.stopPropagation(); + return false; + }; + }; + + disableQuillPaste(); + + // Our custom paste handler - this is the ONLY handler that will run + const handlePaste = async (e: ClipboardEvent) => { + e.preventDefault(); + e.stopPropagation(); + + const items = e.clipboardData?.items; + if (!items || items.length === 0) return; + + // Check for images first + for (let i = 0; i < items.length; i++) { + const item = items[i]; + if (item.type.startsWith("image/")) { + const file = item.getAsFile(); + if (file) { + try { + const base64 = await convertToBase64(file); + const range = editor.getSelection(true); + if (range !== null) { + // Insert the image + editor.insertEmbed(range.index, "image", base64); + // Move cursor after the image + editor.setSelection(range.index + 1, 0); + } + } catch (error) { + console.error("Error processing pasted image:", error); + } + } + return; // Exit after handling the first image } } - }); + + // If no images found, handle text + const text = e.clipboardData?.getData('text/plain'); + if (text) { + const range = editor.getSelection(true); + if (range !== null) { + editor.insertText(range.index, text); + editor.setSelection(range.index + text.length, 0); + } + } + }; + + // Add our custom paste handler with high priority + editor.root.addEventListener("paste", handlePaste, true); + + return () => { + editor.root.removeEventListener("paste", handlePaste, true); + }; }, []); const convertToBase64 = (file: File): Promise => @@ -571,14 +639,14 @@ const AdjustmentTab: React.FC = ({ projects }) => { modules={quillModules} onChange={handleDescription} className="h-40" - // placeholder="Please enter the task name or leave notes to help us better understand your request." + // placeholder="Please enter the task name or leave notes to help us better understand your request." />
- {formErrors.comment && ( - - )} + {formErrors.comment && ( + + )} {/* Submit Button */}
diff --git a/src/ui/pages/LeaveAndAdjustments/tabs/PtoLeaveTab.tsx b/src/ui/pages/LeaveAndAdjustments/tabs/PtoLeaveTab.tsx index 74541c2..ba531d8 100644 --- a/src/ui/pages/LeaveAndAdjustments/tabs/PtoLeaveTab.tsx +++ b/src/ui/pages/LeaveAndAdjustments/tabs/PtoLeaveTab.tsx @@ -3,7 +3,7 @@ import React, { useEffect, useRef, useState } from "react"; import { quillModules } from "../../../utils/quillConfig"; import ReactQuill from "react-quill-new"; import Quill from "quill"; -import "quill/dist/quill.snow.css"; // correct CSS +import "quill/dist/quill.snow.css"; interface LeaveFormData { fromDate: string; @@ -21,43 +21,114 @@ const PtoLeaveTab: React.FC<{ toDate: "", comment: "", }); + const [serverError, setServerError] = useState(""); const quillRef = useRef(null); const [errors, setErrors] = useState>({}); useEffect(() => { const editor = quillRef.current?.getEditor(); - if (!editor) return; - editor.getModule("clipboard").addMatcher(Node.ELEMENT_NODE, (node: any, delta: any) => { - return delta; - }); + // Override image handler in toolbar + editor.getModule("toolbar").handlers.image = () => { + const input = document.createElement("input"); + input.setAttribute("type", "file"); + input.setAttribute("accept", "image/*"); + input.click(); - editor.root.addEventListener("paste", async (e: any) => { - const items = e.clipboardData?.items; - - if (!items) return; - - for (const item of items) { - if (item.type.indexOf("image") === 0) { - const file = item.getAsFile(); + input.onchange = async () => { + const file = input.files?.[0]; + if (file) { const base64 = await convertToBase64(file); - const range = editor.getSelection(); - editor.insertEmbed(range.index, "image", base64); + if (range) { + editor.insertEmbed(range.index, "image", base64); + } + } + }; + }; + + // COMPLETELY disable Quill's default paste behavior + const disableQuillPaste = () => { + // Remove all existing paste event listeners from Quill + const pasteListeners = (editor.root as any).__pasteHandlers || []; + pasteListeners.forEach((handler: any) => { + editor.root.removeEventListener('paste', handler); + }); + + // Override the clipboard module to do nothing + const clipboard = editor.getModule('clipboard'); + const originalPaste = clipboard.paste; + clipboard.paste = (e: any) => { + // Completely prevent Quill's default paste behavior + e.preventDefault(); + e.stopPropagation(); + return false; + }; + }; + + disableQuillPaste(); + + // Our custom paste handler - this is the ONLY handler that will run + const handlePaste = async (e: ClipboardEvent) => { + e.preventDefault(); + e.stopPropagation(); + + const items = e.clipboardData?.items; + if (!items || items.length === 0) return; + + // Check for images first + for (let i = 0; i < items.length; i++) { + const item = items[i]; + if (item.type.startsWith("image/")) { + const file = item.getAsFile(); + if (file) { + try { + const base64 = await convertToBase64(file); + const range = editor.getSelection(true); + if (range !== null) { + // Insert the image + editor.insertEmbed(range.index, "image", base64); + // Move cursor after the image + editor.setSelection(range.index + 1, 0); + } + } catch (error) { + console.error("Error processing pasted image:", error); + } + } + return; // Exit after handling the first image } } - }); + + // If no images found, handle text + const text = e.clipboardData?.getData('text/plain'); + if (text) { + const range = editor.getSelection(true); + if (range !== null) { + editor.insertText(range.index, text); + editor.setSelection(range.index + text.length, 0); + } + } + }; + + // Add our custom paste handler with high priority + editor.root.addEventListener("paste", handlePaste, true); + + return () => { + editor.root.removeEventListener("paste", handlePaste, true); + }; }, []); const convertToBase64 = (file: File): Promise => - new Promise((resolve) => { + new Promise((resolve, reject) => { const reader = new FileReader(); reader.onload = () => resolve(reader.result as string); + reader.onerror = reject; reader.readAsDataURL(file); }); + // ... rest of your component code remains the same const validate = () => { const e: Partial = {}; @@ -67,26 +138,22 @@ const PtoLeaveTab: React.FC<{ const from = ptoLeave.fromDate ? new Date(ptoLeave.fromDate) : null; const to = ptoLeave.toDate ? new Date(ptoLeave.toDate) : null; - // From date required if (!from) { e.fromDate = "From date is required"; } else if (from < today) { e.fromDate = "From date cannot be in the past"; } - // To date required if (!to) { e.toDate = "To date is required"; } else if (to < today) { e.toDate = "To date cannot be in the past"; } - // To date must be same or after From date if (from && to && to < from) { e.toDate = "To date cannot be before From date"; } - // Comment validation if (!ptoLeave.comment.trim()) { e.comment = "Comment required"; } @@ -100,14 +167,13 @@ const PtoLeaveTab: React.FC<{ toDate: "", comment: "" }; - + setPtoLeave(emptyForm); setErrors({}); - - // Properly reset the Quill editor + if (quillRef.current) { const editor = quillRef.current.getEditor(); - editor.setContents([]); // Clear editor contents completely + editor.setContents([]); } }; @@ -119,90 +185,100 @@ const PtoLeaveTab: React.FC<{ }; const handleSubmit = async () => { + setServerError("") const e = validate(); if (Object.keys(e).length) return setErrors(e); - const success = await submitLeave({ + const result = await submitLeave({ dateFrom: ptoLeave.fromDate, dateTo: ptoLeave.toDate, leaveType: "PTO", comment: ptoLeave.comment, }); - if (success) { - resetForm(); + if (result.success) { + resetForm(); alert("PTO submitted!"); + } else { + setServerError(result.message || "Something went wrong"); } }; return ( -
-
+ <> + +
+
- {/* From Date */} -
- - handleChange('fromDate', e.target.value)} - className={`input input-bordered input-sm ${errors.fromDate ? "input-error" : "" - }`} - /> - {errors.fromDate &&

{errors.fromDate}

} + + {/* From Date */} +
+ + handleChange('fromDate', e.target.value)} + className={`input input-bordered input-sm ${errors.fromDate ? "input-error" : "" + }`} + /> + {errors.fromDate &&

{errors.fromDate}

} +
+ + {/* To Date */} +
+ + handleChange('toDate', e.target.value)} + className={`input input-bordered input-sm ${errors.toDate ? "input-error" : "" + }`} + /> + {errors.toDate &&

{errors.toDate}

} +
+ + {/* Comment */} +
+ + handleChange('comment', val)} + className="h-60" + /> + {errors.comment &&

{errors.comment}

} +
- {/* To Date */} -
- - handleChange('toDate', e.target.value)} - className={`input input-bordered input-sm ${errors.toDate ? "input-error" : "" - }`} - /> - {errors.toDate &&

{errors.toDate}

} -
+ {serverError && ( +

{serverError}

+ )} - {/* Comment */} -
- - handleChange('comment', val)} - className="h-60" - /> - -
- {errors.comment &&

{errors.comment}

} +
- - -
+ ); }; diff --git a/src/ui/pages/LeaveAndAdjustments/tabs/SickLeaveTab.tsx b/src/ui/pages/LeaveAndAdjustments/tabs/SickLeaveTab.tsx index 2977cd8..180e6ae 100644 --- a/src/ui/pages/LeaveAndAdjustments/tabs/SickLeaveTab.tsx +++ b/src/ui/pages/LeaveAndAdjustments/tabs/SickLeaveTab.tsx @@ -19,33 +19,102 @@ const SickLeaveTab: React.FC<{ toDate: '', comment: '' }); + const [serverError, setServerError] = useState(""); const quillRef = useRef(null); const [sickErrors, setSickErrors] = useState>({}); useEffect(() => { const editor = quillRef.current?.getEditor(); - if (!editor) return; - editor.getModule("clipboard").addMatcher(Node.ELEMENT_NODE, (node: any, delta: any) => { - return delta; - }); + // Override image handler in toolbar + editor.getModule("toolbar").handlers.image = () => { + const input = document.createElement("input"); + input.setAttribute("type", "file"); + input.setAttribute("accept", "image/*"); + input.click(); - editor.root.addEventListener("paste", async (e: any) => { - const items = e.clipboardData?.items; - - if (!items) return; - - for (const item of items) { - if (item.type.indexOf("image") === 0) { - const file = item.getAsFile(); + input.onchange = async () => { + const file = input.files?.[0]; + if (file) { const base64 = await convertToBase64(file); - const range = editor.getSelection(); - editor.insertEmbed(range.index, "image", base64); + if (range) { + editor.insertEmbed(range.index, "image", base64); + } + } + }; + }; + + // COMPLETELY disable Quill's default paste behavior + const disableQuillPaste = () => { + // Remove all existing paste event listeners from Quill + const pasteListeners = (editor.root as any).__pasteHandlers || []; + pasteListeners.forEach((handler: any) => { + editor.root.removeEventListener('paste', handler); + }); + + // Override the clipboard module to do nothing + const clipboard = editor.getModule('clipboard'); + const originalPaste = clipboard.paste; + clipboard.paste = (e: any) => { + // Completely prevent Quill's default paste behavior + e.preventDefault(); + e.stopPropagation(); + return false; + }; + }; + + disableQuillPaste(); + + // Our custom paste handler - this is the ONLY handler that will run + const handlePaste = async (e: ClipboardEvent) => { + e.preventDefault(); + e.stopPropagation(); + + const items = e.clipboardData?.items; + if (!items || items.length === 0) return; + + // Check for images first + for (let i = 0; i < items.length; i++) { + const item = items[i]; + if (item.type.startsWith("image/")) { + const file = item.getAsFile(); + if (file) { + try { + const base64 = await convertToBase64(file); + const range = editor.getSelection(true); + if (range !== null) { + // Insert the image + editor.insertEmbed(range.index, "image", base64); + // Move cursor after the image + editor.setSelection(range.index + 1, 0); + } + } catch (error) { + console.error("Error processing pasted image:", error); + } + } + return; // Exit after handling the first image } } - }); + + // If no images found, handle text + const text = e.clipboardData?.getData('text/plain'); + if (text) { + const range = editor.getSelection(true); + if (range !== null) { + editor.insertText(range.index, text); + editor.setSelection(range.index + text.length, 0); + } + } + }; + + // Add our custom paste handler with high priority + editor.root.addEventListener("paste", handlePaste, true); + + return () => { + editor.root.removeEventListener("paste", handlePaste, true); + }; }, []); const convertToBase64 = (file: File): Promise => @@ -90,7 +159,7 @@ const SickLeaveTab: React.FC<{ toDate: "", comment: "" }; - + setSickLeave(emptyForm); setSickErrors({}); @@ -102,6 +171,7 @@ const SickLeaveTab: React.FC<{ }; const handleSubmit = async () => { + setServerError("") const errors = validateLeaveForm(sickLeave); if (Object.keys(errors).length > 0) { @@ -109,20 +179,23 @@ const SickLeaveTab: React.FC<{ return; } - const success = await submitLeave({ + const result = await submitLeave({ dateFrom: sickLeave.fromDate, dateTo: sickLeave.toDate, leaveType: "SICK", comment: sickLeave.comment, }); - if (success) { + if (result.success) { resetForm(); alert("SICK submitted!"); + } else { + setServerError(result.message || "Something went wrong"); } }; return ( + <>
@@ -182,13 +255,17 @@ const SickLeaveTab: React.FC<{ className="h-60" />
- {sickErrors.comment && ( - - )} + {sickErrors.comment && ( + + )}
+ {serverError && ( +

{serverError}

+ )} + {/* Submit Button */}
+ ); }; diff --git a/src/ui/pages/Schedule/Schedule.tsx b/src/ui/pages/Schedule/Schedule.tsx new file mode 100644 index 0000000..36fdcfb --- /dev/null +++ b/src/ui/pages/Schedule/Schedule.tsx @@ -0,0 +1,100 @@ +const Schedule = () => { + const rows = [ + { + period: "Oct 25, 2025 to Nov 07, 2025", + cutoff: "Nov 11, 2025 05:29:59 AM", + payDate: "Nov 14, 2025" + }, + { + period: "Nov 08, 2025 to Nov 21, 2025", + cutoff: "Nov 25, 2025 05:29:59 AM", + payDate: "Nov 28, 2025" + }, + { + period: "Nov 22, 2025 to Dec 05, 2025", + cutoff: "Dec 09, 2025 05:29:59 AM", + payDate: "Dec 12, 2025" + }, + { + period: "Dec 06, 2025 to Dec 19, 2025", + cutoff: "Dec 23, 2025 05:29:59 AM", + payDate: "Dec 26, 2025" + }, + { + period: "Dec 20, 2025 to Jan 02, 2026", + cutoff: "Jan 06, 2026 05:29:59 AM", + payDate: "Jan 09, 2026" + }, + { + period: "Jan 03, 2026 to Jan 16, 2026", + cutoff: "Jan 20, 2026 05:29:59 AM", + payDate: "Jan 23, 2026" + }, + { + period: "Jan 17, 2026 to Jan 30, 2026", + cutoff: "Feb 03, 2026 05:29:59 AM", + payDate: "Feb 06, 2026" + }, + { + period: "Jan 31, 2026 to Feb 13, 2026", + cutoff: "Feb 17, 2026 05:29:59 AM", + payDate: "Feb 20, 2026" + }, + { + period: "Feb 14, 2026 to Feb 27, 2026", + cutoff: "Mar 03, 2026 05:29:59 AM", + payDate: "Mar 06, 2026" + }, + { + period: "Feb 28, 2026 to Mar 13, 2026", + cutoff: "Mar 17, 2026 05:29:59 AM", + payDate: "Mar 20, 2026" + } + ]; + + return ( + <> +
+ {/* Header */} +
+

Schedules

+
+ +
+ + {/* ⭐ FIXED: Horizontal scroll wrapper */} +
+ + + + + + + + + + + + {rows.map((row, index) => ( + + + + + + + ))} + +
#PeriodCutoff TimePay Date
{index + 1}{row.period} + {row.cutoff} +
+ (Asia/Calcutta) +
{row.payDate}
+
+ +
+
+ + ); +}; + +export default Schedule;