diff --git a/.gitignore b/.gitignore index 4fae80d..aab0b66 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,7 @@ node_modules dist dist-react dist-electron +release *.local release diff --git a/appIcon.png b/appIcon.png deleted file mode 100644 index dad91a3..0000000 Binary files a/appIcon.png and /dev/null differ diff --git a/build/icon.ico b/build/icon.ico new file mode 100644 index 0000000..582df3a Binary files /dev/null and b/build/icon.ico differ diff --git a/electron-builder.json b/electron-builder.json deleted file mode 100644 index 199fbfa..0000000 --- a/electron-builder.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "appId": "com.n-ziermann.electron-course", - "files": ["dist-electron", "dist-react"], - "extraResources": ["dist-electron/preload.cjs", "src/assets/**"], - "icon": "./desktopIcon.png", - "mac": { - "target": "dmg" - }, - "linux": { - "target": "AppImage", - "category": "Utility" - }, - "win": { - "target": ["portable", "msi"] - } -} diff --git a/src/assets/icons/appIcon.ico b/src/assets/icons/appIcon.ico index 5ac67e5..582df3a 100644 Binary files a/src/assets/icons/appIcon.ico and b/src/assets/icons/appIcon.ico differ diff --git a/src/electron/ipc/ipcHandler.ts b/src/electron/ipc/ipcHandler.ts index 64a9fb5..bc14fc3 100644 --- a/src/electron/ipc/ipcHandler.ts +++ b/src/electron/ipc/ipcHandler.ts @@ -340,6 +340,28 @@ ipcMain.handle("create-timesheet", async (event, data) => { }) +ipcMain.handle("create-leave-request", async (event, payload) => { + try { + console.log(" Leave Request Received:", payload); + + const response = await api.post("/leave/request", payload); + + console.log(" Leave Saved:", response.data); + + return { success: true, data: response.data }; + + } catch (error) { + console.error("❌ Leave Save Failed:", error); + + return { + success: false, + error: error || "Unknown error" + }; + } +}); + + + export const sendTokenExpired = () => { console.log("⚠️ Sending token-expired event to renderer"); diff --git a/src/electron/pathResolver.ts b/src/electron/pathResolver.ts index c71a383..9bcf7b7 100644 --- a/src/electron/pathResolver.ts +++ b/src/electron/pathResolver.ts @@ -1,3 +1,28 @@ +// import path from 'path'; +// import { app } from 'electron'; +// import { isDev } from './util.js'; + +// export function getPreloadPath() { +// return path.join( +// app.getAppPath(), +// isDev() ? '.' : '..', +// '/dist-electron/preload.cjs' +// ); +// } + +// export function getUIPath() { +// return path.join(app.getAppPath(), "dist-react", "index.html"); + +// // return "C:\\Users\\user\\OneDrive\\Desktop\\electron-main\\dist-react\\index.html"; +// // return path.join(app.getAppPath(), '/dist-react/index.html'); +// } + +// export function getAssetPath() { +// return path.join(app.getAppPath(), isDev() ? '.' : '..', '/src/assets'); +// } + + + import path from 'path'; import { app } from 'electron'; import { isDev } from './util.js'; @@ -6,17 +31,22 @@ export function getPreloadPath() { return path.join( app.getAppPath(), isDev() ? '.' : '..', - '/dist-electron/preload.cjs' + '/dist-electron/preload.cjs' // ✅ Changed to .js ); } export function getUIPath() { return path.join(app.getAppPath(), "dist-react", "index.html"); - - // return "C:\\Users\\user\\OneDrive\\Desktop\\electron-main\\dist-react\\index.html"; - // return path.join(app.getAppPath(), '/dist-react/index.html'); } export function getAssetPath() { - return path.join(app.getAppPath(), isDev() ? '.' : '..', '/src/assets'); + return path.join(app.getAppPath(), isDev() ? '.' : '..', '/assets'); } + +export function getIconPath() { + return path.join(getAssetPath(), 'icons', 'appIcon.ico'); +} + +export function getSplashPath() { + return path.join(getAssetPath(), 'splash.html'); +} \ No newline at end of file diff --git a/src/electron/preload.cts b/src/electron/preload.cts index 017de90..7a4c939 100644 --- a/src/electron/preload.cts +++ b/src/electron/preload.cts @@ -50,6 +50,10 @@ contextBridge.exposeInMainWorld('electron', { createTimesheet: (data: any) => ipcRenderer.invoke('create-timesheet', data), + createPTOAndSickLeaveRequest: (data) => + ipcRenderer.invoke("create-leave-request", data), + + } satisfies Window['electron']); // function ipcInvoke( diff --git a/src/electron/utils/utilityFunctions.ts b/src/electron/utils/utilityFunctions.ts index 87f80e5..c09a7ab 100644 --- a/src/electron/utils/utilityFunctions.ts +++ b/src/electron/utils/utilityFunctions.ts @@ -65,7 +65,6 @@ export const handleLogin = async (credentials: { email: string, password: string console.log("UIPATH", getUIPath()); console.log("Doom 1"); - try { console.log("➡️ Calling API..."); const response = await axios.post( @@ -190,4 +189,7 @@ export const captureSpecificApp = async (windowTitle: string): Promise = console.error('Error capturing screenshot:', error); return 'Failed to capture screenshot.'; } -} \ No newline at end of file +} + + +// timmer stop after 4-5 sec check it.. \ No newline at end of file diff --git a/src/ui/layout/DashboardLayout.tsx b/src/ui/layout/DashboardLayout.tsx index 5953436..f0f9768 100644 --- a/src/ui/layout/DashboardLayout.tsx +++ b/src/ui/layout/DashboardLayout.tsx @@ -107,7 +107,7 @@ const DashboardLayout = () => { const menuItems = [ { label: "Dashboard", path: "/dashboard", icon: , exact: true }, { label: "Timesheet", path: "/dashboard/timesheet", icon: }, - { label: "PTO", path: "/dashboard/leave-adjustments", icon: }, + { label: "Leave & Adjust", path: "/dashboard/leave-adjustments", icon: }, { label: "Settings", path: "/dashboard/settings", icon: }, { label: "VPN", path: "/dashboard/vpn", icon: }, { label: "Help & Support", path: "/dashboard/help-support", icon: }, @@ -211,6 +211,54 @@ const DashboardLayout = () => { // STEP 6: Clear active project log ID setActiveProjectLogId(""); + + // ✨ NEW STEP 7: Refresh project list to get updated work hours + try { + console.log("🔄 Refreshing project list..."); + const res = await window.electron.getProjectList(); + console.log("📊 Updated project list:", res); + + if (res?.success && Array.isArray(res.data)) { + // Update projects data + dispatch(setProjectsDataSlice(res.data)); + + // Update project summary + dispatch( + setProjectSummarySlice({ + totalProjects: res.totalProjects || 0, + activeProjects: res.activeProjects || 0, + awayProjects: res.awayProjects || 0, + totalWorkTime: res.totalWorkTime || "0h 0m 0s", + totalWorkMs: res.totalWorkMs || 0, + vaName: res.vaName || "", + vaId: res.vaId || "", + }) + ); + + // Update selected project with fresh data + const updatedProject = res.data.find( + (p: Project) => p.projectId === projectId + ); + + if (updatedProject) { + dispatch(setSelectedProjectSlice(updatedProject)); + + // Update tracking map with new total work time + const initialElapsed = Math.floor((updatedProject.totalWorkMs || 0) / 1000); + dispatch( + initializeProjectTracking({ + projectId: updatedProject.projectId, + initialTime: initialElapsed, + }) + ); + + console.log("✅ Selected project updated with fresh data"); + } + } + } catch (error) { + console.error("❌ Error refreshing project list:", error); + } + setLoadingStop(false); console.log("🧹 Active project log ID cleared"); diff --git a/src/ui/pages/LeaveAndAdjustments/LeaveAndAdjustments.tsx b/src/ui/pages/LeaveAndAdjustments/LeaveAndAdjustments.tsx index 9d8471c..030d943 100644 --- a/src/ui/pages/LeaveAndAdjustments/LeaveAndAdjustments.tsx +++ b/src/ui/pages/LeaveAndAdjustments/LeaveAndAdjustments.tsx @@ -1,3 +1,4 @@ + import React, { useState } from 'react'; import { useSelector } from 'react-redux'; import { RootState } from '../../store'; @@ -9,23 +10,46 @@ type TabType = 'pto' | 'sick' | 'adjustment'; const LeaveAndAdjustments: React.FC = () => { const [activeTab, setActiveTab] = useState('pto'); + const [isSubmitting, setIsSubmitting] = useState(false); // 🚀 NEW const projects = useSelector((state: RootState) => state.projects.projectsData); - console.log("LeaveAndAdjustments", projects); + // ------- COMMON API CALL FOR BOTH TABS ------- + const submitLeave = async (payload: { + dateFrom: string; + dateTo: string; + leaveType: "PTO" | "SICK"; + comment: string; + }) => { + try { + setIsSubmitting(true); + + console.log("Leave Payload:", payload); + + const response = await window.electron.createPTOAndSickLeaveRequest(payload) + console.log("Leave Payload:" ,response) + if (response.success) { + return true; + } + } catch (err) { + console.error(err); + return false; + } finally { + setIsSubmitting(false); + } + }; return (
- {/* Header */} -
-

Leave & Adjust Timesheet

-
+

Leave & Adjust Timesheet

- {/* Tabs Navigation */} -
+ {/* Tabs — Disabled while submitting */} + - -
); }; -export default LeaveAndAdjustments; \ No newline at end of file +export default LeaveAndAdjustments; diff --git a/src/ui/pages/LeaveAndAdjustments/tabs/AdjustmentTab.tsx b/src/ui/pages/LeaveAndAdjustments/tabs/AdjustmentTab.tsx index dfee964..436d51e 100644 --- a/src/ui/pages/LeaveAndAdjustments/tabs/AdjustmentTab.tsx +++ b/src/ui/pages/LeaveAndAdjustments/tabs/AdjustmentTab.tsx @@ -4,6 +4,7 @@ import { DateTime } from 'luxon'; interface Project { projectId: string; projectName: string; + status?: string | boolean; } interface ProjectSummaryResponse { @@ -370,7 +371,7 @@ const AdjustmentTab: React.FC = ({ projects }) => { Pick a project {projects.map((item, index) => ( - ))} diff --git a/src/ui/pages/LeaveAndAdjustments/tabs/PtoLeaveTab.tsx b/src/ui/pages/LeaveAndAdjustments/tabs/PtoLeaveTab.tsx index aafbd61..0885777 100644 --- a/src/ui/pages/LeaveAndAdjustments/tabs/PtoLeaveTab.tsx +++ b/src/ui/pages/LeaveAndAdjustments/tabs/PtoLeaveTab.tsx @@ -1,4 +1,5 @@ -import React, { useState } from 'react'; +// PtoLeaveTab.tsx +import React, { useState } from "react"; interface LeaveFormData { fromDate: string; @@ -6,144 +7,141 @@ interface LeaveFormData { comment: string; } -const PtoLeaveTab: React.FC = () => { +const PtoLeaveTab: React.FC<{ + submitLeave: Function; + isSubmitting: boolean; +}> = ({ submitLeave, isSubmitting }) => { + const [ptoLeave, setPtoLeave] = useState({ - fromDate: '', - toDate: '', - comment: '' + fromDate: "", + toDate: "", + comment: "", }); - const [ptoErrors, setPtoErrors] = useState>({}); - const validateLeaveForm = (data: LeaveFormData): Partial => { - const errors: Partial = {}; + const [errors, setErrors] = useState>({}); - if (!data.fromDate) { - errors.fromDate = 'From date is required'; + const validate = () => { + const e: Partial = {}; + + const today = new Date(); + today.setHours(0, 0, 0, 0); + + 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"; } - if (!data.toDate) { - errors.toDate = 'To date is required'; + // To date required + if (!to) { + e.toDate = "To date is required"; + } else if (to < today) { + e.toDate = "To date cannot be in the past"; } - if (!data.comment.trim()) { - errors.comment = 'Comment is required'; + // To date must be same or after From date + if (from && to && to < from) { + e.toDate = "To date cannot be before From date"; } - if (data.fromDate && data.toDate && new Date(data.fromDate) > new Date(data.toDate)) { - errors.toDate = 'To date must be after from date'; + // Comment validation + if (!ptoLeave.comment.trim()) { + e.comment = "Comment required"; } - return errors; + return e; }; - const handlePtoChange = (field: keyof LeaveFormData, value: string) => { - setPtoLeave(prev => ({ ...prev, [field]: value })); - // Clear error for the field being changed - if (ptoErrors[field]) { - setPtoErrors(prev => ({ ...prev, [field]: undefined })); - } - }; + const handleSubmit = async () => { + const e = validate(); + if (Object.keys(e).length) return setErrors(e); - const handlePtoSubmit = async () => { - const errors = validateLeaveForm(ptoLeave); + const success = await submitLeave({ + dateFrom: ptoLeave.fromDate, + dateTo: ptoLeave.toDate, + leaveType: "PTO", + comment: ptoLeave.comment, + }); - if (Object.keys(errors).length > 0) { - setPtoErrors(errors); - return; - } - - try { - console.log('Submitting PTO:', ptoLeave); - alert('PTO request submitted successfully!'); - - setPtoLeave({ fromDate: '', toDate: '', comment: "" }); - setPtoErrors({}); - } catch (error) { - alert('Failed to submit request. Please try again.'); - console.error('Error:', error); + if (success) { + setPtoLeave({ fromDate: "", toDate: "", comment: "" }); + setErrors({}); + alert("PTO submitted!"); } }; return ( -
-
-
- {/* From Date */} -
- - handlePtoChange('fromDate', e.target.value)} - className={`input input-bordered input-sm w-full focus:input-primary transition-all ${ - ptoErrors.fromDate ? 'input-error' : '' - }`} - /> - {ptoErrors.fromDate && ( - - )} -
+
+
- {/* To Date */} -
- - handlePtoChange('toDate', e.target.value)} - className={`input input-bordered input-sm w-full focus:input-primary transition-all ${ - ptoErrors.toDate ? 'input-error' : '' + {/* From Date */} +
+ + setPtoLeave({ ...ptoLeave, fromDate: e.target.value })} + className={`input input-bordered input-sm ${errors.fromDate ? "input-error" : "" }`} - /> - {ptoErrors.toDate && ( - - )} -
- - {/* Description/Comment Field */} -
- -