Compare commits
9 Commits
93704a70f6
...
adfd911f7b
| Author | SHA1 | Date |
|---|---|---|
|
|
adfd911f7b | |
|
|
3177ac630d | |
|
|
bebb4fb980 | |
|
|
203a8d692c | |
|
|
de44bdac72 | |
|
|
9c8499ff2f | |
|
|
e4eb851bc1 | |
|
|
f5f94f016b | |
|
|
52e45f8328 |
|
|
@ -0,0 +1,8 @@
|
||||||
|
import figlet from "figlet";
|
||||||
|
import chalk from "chalk";
|
||||||
|
|
||||||
|
figlet("Jai Pandey DocVA", (err, data) => {
|
||||||
|
if (err) return console.log("Error generating banner");
|
||||||
|
console.log(chalk.cyan(data));
|
||||||
|
console.log(chalk.green("\n🚀 Development Mode Starting...\n"));
|
||||||
|
});
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -5,7 +5,8 @@
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "dist-electron/main.js",
|
"main": "dist-electron/main.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "npm-run-all --parallel transpile:electron dev:react dev:electron",
|
"banner": "node banner.js",
|
||||||
|
"dev": "npm-run-all banner --parallel transpile:electron dev:react dev:electron",
|
||||||
"dev:react": "vite",
|
"dev:react": "vite",
|
||||||
"dev:electron": "npm run build && cross-env NODE_ENV=development electron --inspect=5858 .",
|
"dev:electron": "npm run build && cross-env NODE_ENV=development electron --inspect=5858 .",
|
||||||
"prod:electron": "npm run build && cross-env NODE_ENV=production electron .",
|
"prod:electron": "npm run build && cross-env NODE_ENV=production electron .",
|
||||||
|
|
@ -24,6 +25,7 @@
|
||||||
"axios": "^1.13.2",
|
"axios": "^1.13.2",
|
||||||
"chalk": "^5.6.2",
|
"chalk": "^5.6.2",
|
||||||
"electron-store": "^11.0.2",
|
"electron-store": "^11.0.2",
|
||||||
|
"figlet": "^1.9.4",
|
||||||
"https": "^1.0.0",
|
"https": "^1.0.0",
|
||||||
"lucide-react": "^0.552.0",
|
"lucide-react": "^0.552.0",
|
||||||
"luxon": "^3.7.2",
|
"luxon": "^3.7.2",
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ const httpsAgent = new https.Agent({
|
||||||
});
|
});
|
||||||
|
|
||||||
const api: AxiosInstance = axios.create({
|
const api: AxiosInstance = axios.create({
|
||||||
baseURL: 'https://quality-friendly-goldfish.ngrok-free.app/api/v1/va', // 🔁 your backend
|
baseURL: 'https://daniela-nonprofessed-boastfully.ngrok-free.app/api/v1/va', // 🔁 your backend
|
||||||
httpsAgent
|
httpsAgent
|
||||||
// timeout: 10000,
|
// timeout: 10000,
|
||||||
});
|
});
|
||||||
|
|
@ -38,14 +38,33 @@ api.interceptors.request.use(
|
||||||
);
|
);
|
||||||
|
|
||||||
// --- Response Interceptor ---
|
// --- Response Interceptor ---
|
||||||
|
// api.interceptors.response.use(
|
||||||
|
// (response: AxiosResponse) => response.data,
|
||||||
|
// async (error) => {
|
||||||
|
// console.error('[Axios Error]', error.status);
|
||||||
|
// if (error.status === 401 || 403) {
|
||||||
|
// console.warn('Token expired — trigger refresh logic if needed');
|
||||||
|
// // sendTokenExpired();
|
||||||
|
// }
|
||||||
|
// return Promise.reject(error);
|
||||||
|
// }
|
||||||
|
// );
|
||||||
|
|
||||||
api.interceptors.response.use(
|
api.interceptors.response.use(
|
||||||
(response: AxiosResponse) => response.data,
|
(response: AxiosResponse) => response.data,
|
||||||
async (error) => {
|
async (error) => {
|
||||||
// console.error('[Axios Error]', error.message);
|
|
||||||
if (error.response?.status === 401 || 403) {
|
console.log(error , "Line 46")
|
||||||
|
const status = error?.response?.status;
|
||||||
|
|
||||||
|
console.error('[Axios Error]', status);
|
||||||
|
|
||||||
|
// Only logout when token actually expired
|
||||||
|
if (status === 401 || status === 403) {
|
||||||
console.warn('Token expired — trigger refresh logic if needed');
|
console.warn('Token expired — trigger refresh logic if needed');
|
||||||
sendTokenExpired();
|
sendTokenExpired();
|
||||||
}
|
}
|
||||||
|
|
||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -345,6 +345,8 @@ ipcMain.handle("create-leave-request", async (event, payload) => {
|
||||||
|
|
||||||
let message = "Unknown error";
|
let message = "Unknown error";
|
||||||
|
|
||||||
|
console.log(error.response)
|
||||||
|
|
||||||
if (error.response) {
|
if (error.response) {
|
||||||
console.log("Status:", error.response.status);
|
console.log("Status:", error.response.status);
|
||||||
console.log("Message:", error.response.statusText);
|
console.log("Message:", error.response.statusText);
|
||||||
|
|
@ -405,11 +407,10 @@ ipcMain.handle("get-payslips", async (event, vaId: string) => {
|
||||||
console.log("get-payslips", vaId)
|
console.log("get-payslips", vaId)
|
||||||
try {
|
try {
|
||||||
const response = await api.get(`/paySlip/${vaId}`);
|
const response = await api.get(`/paySlip/${vaId}`);
|
||||||
|
console.log(response.data , "Line 409")
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
data: response.data?.data || [],
|
data: response.data || [],
|
||||||
};
|
};
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
let message = "Unknown error occurred";
|
let message = "Unknown error occurred";
|
||||||
|
|
|
||||||
|
|
@ -68,7 +68,7 @@ app.on("ready", async () => {
|
||||||
show: true,
|
show: true,
|
||||||
skipTaskbar: true,
|
skipTaskbar: true,
|
||||||
webPreferences: {
|
webPreferences: {
|
||||||
devTools: true,
|
devTools: false,
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -164,17 +164,17 @@ app.on("ready", async () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
// app.on('browser-window-focus', () => {
|
app.on('browser-window-focus', () => {
|
||||||
// globalShortcut.register('CommandOrControl+R', () => {
|
globalShortcut.register('CommandOrControl+R', () => {
|
||||||
// // Do nothing; disables reload
|
// Do nothing; disables reload
|
||||||
// });
|
});
|
||||||
// globalShortcut.register('F5', () => {
|
globalShortcut.register('F5', () => {
|
||||||
// // Do nothing; disables reload
|
// Do nothing; disables reload
|
||||||
// });
|
});
|
||||||
// globalShortcut.register('CommandOrControl+Shift+I', () => {
|
globalShortcut.register('CommandOrControl+Shift+I', () => {
|
||||||
// // Do nothing; disables DevTools
|
// Do nothing; disables DevTools
|
||||||
// });
|
});
|
||||||
// });
|
});
|
||||||
|
|
||||||
// app.on('browser-window-blur', () => {
|
// app.on('browser-window-blur', () => {
|
||||||
// globalShortcut.unregisterAll();
|
// globalShortcut.unregisterAll();
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,7 @@ test('', () => {
|
||||||
expect(template[0].label).toEqual('Show');
|
expect(template[0].label).toEqual('Show');
|
||||||
template[0]?.click?.(null as any, null as any, null as any);
|
template[0]?.click?.(null as any, null as any, null as any);
|
||||||
expect(mainWindow.show).toHaveBeenCalled();
|
expect(mainWindow.show).toHaveBeenCalled();
|
||||||
expect(app.dock.show).toHaveBeenCalled();
|
expect(app.dock!.show).toHaveBeenCalled();
|
||||||
|
|
||||||
template[1]?.click?.(null as any, null as any, null as any);
|
template[1]?.click?.(null as any, null as any, null as any);
|
||||||
expect(app.quit).toHaveBeenCalled();
|
expect(app.quit).toHaveBeenCalled();
|
||||||
|
|
|
||||||
|
|
@ -8,14 +8,12 @@ interface DailyHours {
|
||||||
remaining: string;
|
remaining: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export const useActiveAppTracker = (
|
export const useActiveAppTracker = (
|
||||||
isTracking: boolean,
|
isTracking: boolean,
|
||||||
activeProjectLogId: string | null,
|
activeProjectLogId: string | null,
|
||||||
projectId: string | null,
|
projectId: string | null,
|
||||||
projectName: string | null,
|
projectName: string | null,
|
||||||
onLimitExceeded?: () => void // Callback when limit is reached
|
onLimitExceeded?: () => void
|
||||||
) => {
|
) => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const lastActiveRef = useRef<WindowInfo | null>(null);
|
const lastActiveRef = useRef<WindowInfo | null>(null);
|
||||||
|
|
@ -24,8 +22,9 @@ export const useActiveAppTracker = (
|
||||||
const intervalIdRef = useRef<number | null>(null);
|
const intervalIdRef = useRef<number | null>(null);
|
||||||
const finalizeTrackingRef = useRef<(() => Promise<void>) | null>(null);
|
const finalizeTrackingRef = useRef<(() => Promise<void>) | null>(null);
|
||||||
const limitCheckedRef = useRef<boolean>(false);
|
const limitCheckedRef = useRef<boolean>(false);
|
||||||
|
const isStoppingRef = useRef<boolean>(false); // ✅ Flag to prevent new requests
|
||||||
|
|
||||||
const canSend = !!(activeProjectLogId && projectId);
|
const canSend = !!(activeProjectLogId && projectId && !isStoppingRef.current);
|
||||||
|
|
||||||
// ✅ Parse time string to minutes
|
// ✅ Parse time string to minutes
|
||||||
const parseTimeToMinutes = (timeStr: string): number => {
|
const parseTimeToMinutes = (timeStr: string): number => {
|
||||||
|
|
@ -53,7 +52,6 @@ export const useActiveAppTracker = (
|
||||||
exceeded: remainingMins <= 0
|
exceeded: remainingMins <= 0
|
||||||
});
|
});
|
||||||
|
|
||||||
// If remaining time is 0 or negative, limit is exceeded
|
|
||||||
if (remainingMins <= 0) {
|
if (remainingMins <= 0) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
@ -61,6 +59,7 @@ export const useActiveAppTracker = (
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// ✅ Send activity log (POST for new, PATCH for update)
|
||||||
const sendActivityLog = async (
|
const sendActivityLog = async (
|
||||||
projectLogId: string,
|
projectLogId: string,
|
||||||
projectId: string,
|
projectId: string,
|
||||||
|
|
@ -68,8 +67,14 @@ export const useActiveAppTracker = (
|
||||||
appName: string,
|
appName: string,
|
||||||
isNew: boolean
|
isNew: boolean
|
||||||
) => {
|
) => {
|
||||||
|
// ✅ CRITICAL: Block all requests when stopping
|
||||||
|
if (isStoppingRef.current) {
|
||||||
|
console.log("🚫 Stopping in progress — blocking activity log");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
if (!canSend) {
|
if (!canSend) {
|
||||||
console.log("🚫 Skipping API — missing activeProjectLogId or projectId");
|
console.log("🚫 Skipping API — missing required IDs");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -99,20 +104,18 @@ export const useActiveAppTracker = (
|
||||||
|
|
||||||
// Store the log ID for future updates
|
// Store the log ID for future updates
|
||||||
if (isNew && res?.data?._id) {
|
if (isNew && res?.data?._id) {
|
||||||
console.log("res?.data?._id", res?.data?._id)
|
|
||||||
lastLogIdRef.current = res.data._id;
|
lastLogIdRef.current = res.data._id;
|
||||||
|
console.log("💾 Stored log ID:", res.data._id);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ✅ Check daily limit after every API call
|
// ✅ Check daily limit after every API call
|
||||||
if (res?.dailyHours) {
|
if (res?.dailyHours) {
|
||||||
console.log("res?.data?.dailyHours", res?.dailyHours)
|
const limitExceeded = checkDailyLimit(res.dailyHours);
|
||||||
const limitExceeded = checkDailyLimit(res?.dailyHours);
|
|
||||||
|
|
||||||
if (limitExceeded && !limitCheckedRef.current) {
|
if (limitExceeded && !limitCheckedRef.current) {
|
||||||
limitCheckedRef.current = true;
|
limitCheckedRef.current = true;
|
||||||
console.warn("🚨 Daily limit exceeded! Stopping tracking...");
|
console.warn("🚨 Daily limit exceeded!");
|
||||||
|
|
||||||
// Trigger stop tracking callback
|
|
||||||
if (onLimitExceeded) {
|
if (onLimitExceeded) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
onLimitExceeded();
|
onLimitExceeded();
|
||||||
|
|
@ -128,54 +131,69 @@ export const useActiveAppTracker = (
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// ✅ Final PATCH before stopping
|
// ✅ Stop the interval immediately
|
||||||
|
const stopInterval = useCallback(() => {
|
||||||
|
console.log("🛑 Stopping activity tracking interval...");
|
||||||
|
if (intervalIdRef.current) {
|
||||||
|
clearInterval(intervalIdRef.current);
|
||||||
|
intervalIdRef.current = null;
|
||||||
|
console.log("✅ Interval cleared");
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// ✅ Send final PATCH before stopping (called by handleStopTracking)
|
||||||
const sendFinalPatch = useCallback(async () => {
|
const sendFinalPatch = useCallback(async () => {
|
||||||
if (!canSend) {
|
console.log("=".repeat(60));
|
||||||
console.log("🚫 Skipping final PATCH — no active project log ID");
|
console.log("🛑 FINAL PATCH INITIATED");
|
||||||
|
console.log("=".repeat(60));
|
||||||
|
|
||||||
|
// ✅ STEP 1: Set stopping flag IMMEDIATELY
|
||||||
|
isStoppingRef.current = true;
|
||||||
|
console.log("🔒 Stopping flag set — no new requests allowed");
|
||||||
|
|
||||||
|
// ✅ STEP 2: Stop the interval IMMEDIATELY
|
||||||
|
stopInterval();
|
||||||
|
|
||||||
|
if (!activeProjectLogId || !projectId) {
|
||||||
|
console.log("⚠️ Skipping final PATCH — no active project log ID");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log("=".repeat(60));
|
// ✅ STEP 3: Send ONE final PATCH for the last active window
|
||||||
console.log("🛑 FINAL PATCH INITIATED - Recording last activity");
|
|
||||||
console.log("=".repeat(60));
|
|
||||||
|
|
||||||
if (lastLogIdRef.current && lastTitleRef.current && lastActiveRef.current) {
|
if (lastLogIdRef.current && lastTitleRef.current && lastActiveRef.current) {
|
||||||
try {
|
try {
|
||||||
console.log("📊 Final Activity Details:");
|
console.log("📊 Final Activity Details:");
|
||||||
console.log(` • Window Title: ${lastTitleRef.current}`);
|
console.log(` • Window: ${lastTitleRef.current}`);
|
||||||
console.log(` • App Name: ${lastActiveRef.current.owner.name}`);
|
console.log(` • App: ${lastActiveRef.current.owner.name}`);
|
||||||
console.log(` • Log ID: ${lastLogIdRef.current}`);
|
console.log(` • Log ID: ${lastLogIdRef.current}`);
|
||||||
console.log(` • Project ID: ${projectId}`);
|
console.log(` • Project: ${projectId}`);
|
||||||
|
|
||||||
const result = await sendActivityLog(
|
const result = await sendActivityLog(
|
||||||
activeProjectLogId!,
|
activeProjectLogId,
|
||||||
projectId!,
|
projectId,
|
||||||
lastTitleRef.current,
|
lastTitleRef.current,
|
||||||
lastActiveRef.current.owner.name || "Unknown",
|
lastActiveRef.current.owner.name || "Unknown",
|
||||||
false // This is a PATCH (update), not a POST (new)
|
false // PATCH (update existing log)
|
||||||
);
|
);
|
||||||
|
|
||||||
console.log("✅ Final PATCH sent successfully!");
|
console.log("✅ Final PATCH sent successfully!");
|
||||||
|
|
||||||
if (result?.dailyHours) {
|
if (result?.dailyHours) {
|
||||||
console.log("📈 Final Daily Hours Summary:");
|
console.log("📈 Final Daily Hours:");
|
||||||
console.log(` • Today Total: ${result.dailyHours.todayTotal}`);
|
console.log(` • Today: ${result.dailyHours.todayTotal}`);
|
||||||
console.log(` • Daily Limit: ${result.dailyHours.dailyLimit}`);
|
console.log(` • Limit: ${result.dailyHours.dailyLimit}`);
|
||||||
console.log(` • Remaining: ${result.dailyHours.remaining}`);
|
console.log(` • Remaining: ${result.dailyHours.remaining}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("❌ Final PATCH failed:", error);
|
console.error("❌ Final PATCH failed:", error);
|
||||||
throw error; // Re-throw to let caller handle
|
throw error;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.log("ℹ️ No active window to finalize");
|
console.log("ℹ️ No active window to finalize");
|
||||||
console.log(" • Last Log ID:", lastLogIdRef.current || "None");
|
|
||||||
console.log(" • Last Title:", lastTitleRef.current || "None");
|
|
||||||
console.log(" • Last Active Window:", lastActiveRef.current ? "Yes" : "No");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clean up all refs
|
// ✅ STEP 4: Clean up all refs
|
||||||
console.log("🧹 Cleaning up tracking references...");
|
console.log("🧹 Cleaning up tracking references...");
|
||||||
lastActiveRef.current = null;
|
lastActiveRef.current = null;
|
||||||
lastTitleRef.current = null;
|
lastTitleRef.current = null;
|
||||||
|
|
@ -183,70 +201,88 @@ export const useActiveAppTracker = (
|
||||||
limitCheckedRef.current = false;
|
limitCheckedRef.current = false;
|
||||||
console.log("✅ Cleanup complete");
|
console.log("✅ Cleanup complete");
|
||||||
console.log("=".repeat(60));
|
console.log("=".repeat(60));
|
||||||
}, [activeProjectLogId, projectId, canSend]);
|
}, [activeProjectLogId, projectId, stopInterval]);
|
||||||
|
|
||||||
|
// ✅ Update ref whenever sendFinalPatch changes
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
finalizeTrackingRef.current = sendFinalPatch;
|
finalizeTrackingRef.current = sendFinalPatch;
|
||||||
}, [sendFinalPatch]);
|
}, [sendFinalPatch]);
|
||||||
|
|
||||||
|
// ✅ Main tracking interval effect
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Reset limit check when tracking starts
|
// Reset flags when tracking starts
|
||||||
if (isTracking) {
|
if (isTracking) {
|
||||||
limitCheckedRef.current = false;
|
limitCheckedRef.current = false;
|
||||||
|
isStoppingRef.current = false;
|
||||||
|
console.log("🟢 Tracking started — flags reset");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isTracking || !canSend) {
|
if (!isTracking || !canSend) {
|
||||||
console.log("🟡 Tracking inactive or missing IDs — skipping tracker setup");
|
console.log("🟡 Tracking inactive — cleaning up");
|
||||||
|
stopInterval();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`🟢 Starting active tracker for: ${projectName}`);
|
console.log(`🟢 Starting active tracker for: ${projectName}`);
|
||||||
|
|
||||||
const fetchActiveApp = async () => {
|
const fetchActiveApp = async () => {
|
||||||
|
// ✅ Double-check stopping flag before each fetch
|
||||||
|
if (isStoppingRef.current) {
|
||||||
|
console.log("🚫 Stopping in progress — skipping fetch");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const details = (await window.electron.getActiveAppDetails()) as WindowInfo;
|
const details = (await window.electron.getActiveAppDetails()) as WindowInfo;
|
||||||
if (!details || !details.id) return;
|
if (!details || !details.id) return;
|
||||||
|
|
||||||
console.log("details" , details)
|
|
||||||
|
|
||||||
const title = details.title;
|
const title = details.title;
|
||||||
const appName = details.owner.name;
|
const appName = details.owner.name;
|
||||||
const prevTitle = lastTitleRef.current;
|
const prevTitle = lastTitleRef.current;
|
||||||
const hasChanged = !prevTitle || prevTitle !== title;
|
const hasChanged = !prevTitle || prevTitle !== title;
|
||||||
|
|
||||||
if (hasChanged) {
|
if (hasChanged) {
|
||||||
|
// PATCH previous window before creating new log
|
||||||
if (lastLogIdRef.current && prevTitle) {
|
if (lastLogIdRef.current && prevTitle) {
|
||||||
console.log("🔄 Patching previous log:", prevTitle);
|
console.log("🔄 Updating previous window:", prevTitle);
|
||||||
await sendActivityLog(activeProjectLogId!, projectId!, prevTitle, appName, false);
|
await sendActivityLog(activeProjectLogId!, projectId!, prevTitle, appName, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log("🆕 New title detected → creating POST:", title);
|
// POST new window log
|
||||||
|
console.log("🆕 New window detected:", title);
|
||||||
await sendActivityLog(activeProjectLogId!, projectId!, title, appName, true);
|
await sendActivityLog(activeProjectLogId!, projectId!, title, appName, true);
|
||||||
|
|
||||||
|
// Update refs and Redux
|
||||||
lastActiveRef.current = details;
|
lastActiveRef.current = details;
|
||||||
lastTitleRef.current = title;
|
lastTitleRef.current = title;
|
||||||
dispatch(setActiveWindow({ window: details, projectId, projectName }));
|
dispatch(setActiveWindow({ window: details, projectId, projectName }));
|
||||||
} else {
|
} else {
|
||||||
console.log("⏸ Same window, skipping log");
|
console.log("⏸️ Same window, skipping");
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to fetch active app:", error);
|
console.error("❌ Failed to fetch active app:", error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Initial fetch
|
||||||
fetchActiveApp();
|
fetchActiveApp();
|
||||||
|
|
||||||
|
// Set up interval (every 5 seconds)
|
||||||
intervalIdRef.current = setInterval(fetchActiveApp, 5000);
|
intervalIdRef.current = setInterval(fetchActiveApp, 5000);
|
||||||
|
|
||||||
|
// Cleanup on unmount
|
||||||
return () => {
|
return () => {
|
||||||
console.log("🧹 Cleaning up tracker interval");
|
console.log("🧹 Cleaning up tracker interval");
|
||||||
if (intervalIdRef.current) {
|
stopInterval();
|
||||||
clearInterval(intervalIdRef.current);
|
|
||||||
intervalIdRef.current = null;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}, [isTracking, activeProjectLogId]);
|
}, [isTracking, activeProjectLogId, projectId, projectName, dispatch, stopInterval]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
finalizeTracking: finalizeTrackingRef.current,
|
finalizeTracking: finalizeTrackingRef.current,
|
||||||
|
stopInterval,
|
||||||
|
setIsStopping: (value: boolean) => {
|
||||||
|
isStoppingRef.current = value;
|
||||||
|
console.log(`🔒 Stopping flag manually set to: ${value}`);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
@ -135,155 +135,10 @@ const DashboardLayout = () => {
|
||||||
document.documentElement.setAttribute("data-theme", theme);
|
document.documentElement.setAttribute("data-theme", theme);
|
||||||
}, [theme]);
|
}, [theme]);
|
||||||
|
|
||||||
|
|
||||||
// Create a ref to store the stop function so it can be called from the hook
|
// Create a ref to store the stop function so it can be called from the hook
|
||||||
const stopTrackingRef = useRef<(() => Promise<void>) | null>(null);
|
const stopTrackingRef = useRef<(() => Promise<void>) | null>(null);
|
||||||
const limitAlertShownRef = useRef(false);
|
const limitAlertShownRef = useRef(false);
|
||||||
|
|
||||||
// Define handleStopTracking FIRST
|
|
||||||
const handleStopTracking = useCallback(async () => {
|
|
||||||
if (loadingStop || loadingStart) return;
|
|
||||||
|
|
||||||
if (!selectedProject) {
|
|
||||||
console.warn("No selected project to stop");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reset alert flag when stopping
|
|
||||||
limitAlertShownRef.current = false;
|
|
||||||
|
|
||||||
let currentStopTime = new Date().toISOString();
|
|
||||||
const projectId = selectedProject.projectId;
|
|
||||||
|
|
||||||
console.log(" Stop tracking initiated for:", selectedProject.projectName);
|
|
||||||
console.log(" Current elapsed time:", elapsedTime);
|
|
||||||
|
|
||||||
setIsStopping(true);
|
|
||||||
setLoadingStop(true);
|
|
||||||
handleSocketDisconnect();
|
|
||||||
|
|
||||||
// STEP 1: Clear timer FIRST to prevent further client-side updates
|
|
||||||
if (timerRef.current) {
|
|
||||||
clearInterval(timerRef.current);
|
|
||||||
timerRef.current = null;
|
|
||||||
console.log("Timer cleared");
|
|
||||||
}
|
|
||||||
|
|
||||||
// STEP 2: Send final PATCH with last active window data
|
|
||||||
if (stopTrackingRef.current) {
|
|
||||||
try {
|
|
||||||
console.log(" Sending final PATCH to record last activity...");
|
|
||||||
await stopTrackingRef.current();
|
|
||||||
console.log(" Final tracking data sent successfully");
|
|
||||||
} catch (error) {
|
|
||||||
console.error(" Error sending final tracking data:", error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// STEP 3: Send LOGOUT to backend
|
|
||||||
try {
|
|
||||||
const payload = {
|
|
||||||
vaId: projectSummary.vaId,
|
|
||||||
projectId: projectId,
|
|
||||||
type: "LOGOUT",
|
|
||||||
time: currentStopTime
|
|
||||||
};
|
|
||||||
|
|
||||||
console.log("📡 Sending LOGOUT request:", payload);
|
|
||||||
const res = await window.electron.stopProjectTracking(payload);
|
|
||||||
console.log(" LOGOUT successful:", res);
|
|
||||||
} catch (error) {
|
|
||||||
console.error(" Failed to send LOGOUT:", error);
|
|
||||||
}
|
|
||||||
|
|
||||||
// STEP 4: Update Redux tracking state to stop tracking
|
|
||||||
console.log("🔄 Updating Redux state to stop tracking...");
|
|
||||||
dispatch(
|
|
||||||
stopTrackingReducer({
|
|
||||||
projectId,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
// Allow React to process the state update
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 50));
|
|
||||||
|
|
||||||
// STEP 5: Refresh project list to get updated work hours from server
|
|
||||||
try {
|
|
||||||
console.log("🔄 Refreshing project list...");
|
|
||||||
const projectListRes = await window.electron.getProjectList();
|
|
||||||
console.log("📊 Updated project list:", projectListRes);
|
|
||||||
|
|
||||||
if (projectListRes?.success && Array.isArray(projectListRes.data)) {
|
|
||||||
// Update projects data
|
|
||||||
dispatch(setProjectsDataSlice(projectListRes.data));
|
|
||||||
|
|
||||||
// Update project summary
|
|
||||||
dispatch(
|
|
||||||
setProjectSummarySlice({
|
|
||||||
totalProjects: projectListRes.totalProjects || 0,
|
|
||||||
activeProjects: projectListRes.activeProjects || 0,
|
|
||||||
awayProjects: projectListRes.awayProjects || 0,
|
|
||||||
totalWorkTime: projectListRes.totalWorkTime || "0h 0m 0s",
|
|
||||||
totalWorkMs: projectListRes.totalWorkMs || 0,
|
|
||||||
vaName: projectListRes.vaName || "",
|
|
||||||
vaId: projectListRes.vaId || "",
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
// Update selected project with fresh data
|
|
||||||
const updatedProject = projectListRes.data.find(
|
|
||||||
(p: Project) => p.projectId === projectId
|
|
||||||
);
|
|
||||||
|
|
||||||
if (updatedProject) {
|
|
||||||
dispatch(setSelectedProjectSlice(updatedProject));
|
|
||||||
|
|
||||||
// CRITICAL FIX: Use server time instead of client elapsedTime
|
|
||||||
const serverElapsedTime = parseTimeStringToSeconds(updatedProject.totalWorkTime);
|
|
||||||
|
|
||||||
console.log("🔄 Syncing client time with server time:", {
|
|
||||||
serverTime: updatedProject.totalWorkTime,
|
|
||||||
serverElapsedSeconds: serverElapsedTime,
|
|
||||||
previousClientTime: elapsedTime,
|
|
||||||
difference: serverElapsedTime - elapsedTime
|
|
||||||
});
|
|
||||||
|
|
||||||
// Force update the tracking map with server time
|
|
||||||
dispatch(
|
|
||||||
updateProjectTracking({
|
|
||||||
projectId: updatedProject.projectId,
|
|
||||||
data: {
|
|
||||||
elapsedTime: serverElapsedTime,
|
|
||||||
accumulatedTime: serverElapsedTime,
|
|
||||||
isTracking: false,
|
|
||||||
isPaused: false,
|
|
||||||
startTime: null,
|
|
||||||
endTime: Date.now(),
|
|
||||||
pauseStartTime: null,
|
|
||||||
totalPausedTime: 0
|
|
||||||
}
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
console.log("✅ Selected project updated with server time");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// STEP 6: Update UI (window restore)
|
|
||||||
await window.electron.toggleCloseBtn(true);
|
|
||||||
await window.electron.resizeWindow(500, 600);
|
|
||||||
toggleWindowSize.current = true;
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.error("❌ Error refreshing project list:", error);
|
|
||||||
} finally {
|
|
||||||
// Clear loading states
|
|
||||||
setLoadingStop(false);
|
|
||||||
setIsStopping(false);
|
|
||||||
console.log("🎉 TRACKING STOPPED SUCCESSFULLY");
|
|
||||||
}
|
|
||||||
}, [selectedProject, projectSummary, elapsedTime, dispatch]);
|
|
||||||
|
|
||||||
// NOW define handleLimitExceeded using the defined handleStopTracking
|
// NOW define handleLimitExceeded using the defined handleStopTracking
|
||||||
const handleLimitExceeded = useCallback(async () => {
|
const handleLimitExceeded = useCallback(async () => {
|
||||||
console.log("🚨 LIMIT EXCEEDED - Showing alert (not stopping)");
|
console.log("🚨 LIMIT EXCEEDED - Showing alert (not stopping)");
|
||||||
|
|
@ -316,7 +171,7 @@ const DashboardLayout = () => {
|
||||||
}, [selectedProject]);
|
}, [selectedProject]);
|
||||||
|
|
||||||
// Initialize useActiveAppTracker and store finalizeTracking in ref
|
// Initialize useActiveAppTracker and store finalizeTracking in ref
|
||||||
const { finalizeTracking } = useActiveAppTracker(
|
const { finalizeTracking, setIsStopping: setIsStoppingInTracker } = useActiveAppTracker(
|
||||||
isTracking,
|
isTracking,
|
||||||
activeProjectLogId,
|
activeProjectLogId,
|
||||||
selectedProject?.projectId ?? null,
|
selectedProject?.projectId ?? null,
|
||||||
|
|
@ -324,6 +179,186 @@ const DashboardLayout = () => {
|
||||||
handleLimitExceeded
|
handleLimitExceeded
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Define handleStopTracking FIRST
|
||||||
|
const handleStopTracking = useCallback(async () => {
|
||||||
|
if (loadingStop || loadingStart) {
|
||||||
|
console.log("⏸️ Already processing, skipping...");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!selectedProject) {
|
||||||
|
console.warn("⚠️ No selected project to stop");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("=".repeat(70));
|
||||||
|
console.log("🛑 STOP TRACKING INITIATED");
|
||||||
|
console.log("=".repeat(70));
|
||||||
|
console.log(`📦 Project: ${selectedProject.projectName}`);
|
||||||
|
console.log(`⏱️ Elapsed Time: ${elapsedTime}s`);
|
||||||
|
|
||||||
|
// Reset alert flag when stopping
|
||||||
|
limitAlertShownRef.current = false;
|
||||||
|
|
||||||
|
const currentStopTime = new Date().toISOString();
|
||||||
|
const projectId = selectedProject.projectId;
|
||||||
|
|
||||||
|
// ✅ CRITICAL: Set stopping flag IMMEDIATELY to block all new tab tracking
|
||||||
|
console.log("🔒 Setting stopping flag...");
|
||||||
|
if (setIsStoppingInTracker) {
|
||||||
|
setIsStoppingInTracker(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsStopping(true);
|
||||||
|
setLoadingStop(true);
|
||||||
|
handleSocketDisconnect();
|
||||||
|
|
||||||
|
// STEP 1: Clear local timer to prevent further client-side updates
|
||||||
|
console.log("⏹️ STEP 1: Clearing local timer...");
|
||||||
|
if (timerRef.current) {
|
||||||
|
clearInterval(timerRef.current);
|
||||||
|
timerRef.current = null;
|
||||||
|
console.log("✅ Timer cleared");
|
||||||
|
}
|
||||||
|
|
||||||
|
// STEP 2: Send final PATCH with last active window data
|
||||||
|
console.log("📤 STEP 2: Sending final activity tracking...");
|
||||||
|
if (stopTrackingRef.current) {
|
||||||
|
try {
|
||||||
|
await stopTrackingRef.current(); // This calls sendFinalPatch() in the hook
|
||||||
|
console.log("✅ Final activity tracking completed");
|
||||||
|
} catch (error) {
|
||||||
|
console.error("❌ Error during final tracking:", error);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log("⚠️ No tracking ref available");
|
||||||
|
}
|
||||||
|
|
||||||
|
// STEP 3: Send LOGOUT to backend
|
||||||
|
console.log("🚪 STEP 3: Sending LOGOUT request...");
|
||||||
|
try {
|
||||||
|
const payload = {
|
||||||
|
vaId: projectSummary.vaId,
|
||||||
|
projectId: projectId,
|
||||||
|
type: "LOGOUT",
|
||||||
|
time: currentStopTime
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log("📡 Logout payload:", payload);
|
||||||
|
const res = await window.electron.stopProjectTracking(payload);
|
||||||
|
console.log("✅ LOGOUT successful:", res);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("❌ LOGOUT failed:", error);
|
||||||
|
}
|
||||||
|
|
||||||
|
// STEP 4: Update Redux tracking state
|
||||||
|
console.log("🔄 STEP 4: Updating Redux state...");
|
||||||
|
dispatch(
|
||||||
|
stopTrackingReducer({
|
||||||
|
projectId,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
// STEP 6: Update UI (restore window)
|
||||||
|
console.log("🪟 STEP 6: Restoring window...");
|
||||||
|
await window.electron.toggleCloseBtn(true);
|
||||||
|
await window.electron.resizeWindow(500, 600);
|
||||||
|
toggleWindowSize.current = true;
|
||||||
|
console.log("✅ Window restored");
|
||||||
|
|
||||||
|
// Allow React to process the state update
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 50));
|
||||||
|
|
||||||
|
// STEP 5: Refresh project list to get updated work hours from server
|
||||||
|
console.log("📊 STEP 5: Refreshing project list...");
|
||||||
|
try {
|
||||||
|
const projectListRes = await window.electron.getProjectList();
|
||||||
|
console.log("✅ Project list fetched:", projectListRes);
|
||||||
|
|
||||||
|
if (projectListRes?.success && Array.isArray(projectListRes.data)) {
|
||||||
|
// Update projects data
|
||||||
|
dispatch(setProjectsDataSlice(projectListRes.data));
|
||||||
|
|
||||||
|
// Update project summary
|
||||||
|
dispatch(
|
||||||
|
setProjectSummarySlice({
|
||||||
|
totalProjects: projectListRes.totalProjects || 0,
|
||||||
|
activeProjects: projectListRes.activeProjects || 0,
|
||||||
|
awayProjects: projectListRes.awayProjects || 0,
|
||||||
|
totalWorkTime: projectListRes.totalWorkTime || "0h 0m 0s",
|
||||||
|
totalWorkMs: projectListRes.totalWorkMs || 0,
|
||||||
|
vaName: projectListRes.vaName || "",
|
||||||
|
vaId: projectListRes.vaId || "",
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
// Update selected project with fresh data from server
|
||||||
|
const updatedProject = projectListRes.data.find(
|
||||||
|
(p: Project) => p.projectId === projectId
|
||||||
|
);
|
||||||
|
|
||||||
|
if (updatedProject) {
|
||||||
|
dispatch(setSelectedProjectSlice(updatedProject));
|
||||||
|
|
||||||
|
// Sync client time with server time
|
||||||
|
const serverElapsedTime = parseTimeStringToSeconds(updatedProject.totalWorkTime);
|
||||||
|
|
||||||
|
console.log("🔄 Syncing with server time:", {
|
||||||
|
serverTime: updatedProject.totalWorkTime,
|
||||||
|
serverSeconds: serverElapsedTime,
|
||||||
|
clientSeconds: elapsedTime,
|
||||||
|
difference: serverElapsedTime - elapsedTime
|
||||||
|
});
|
||||||
|
|
||||||
|
// Force update the tracking map with server time
|
||||||
|
dispatch(
|
||||||
|
updateProjectTracking({
|
||||||
|
projectId: updatedProject.projectId,
|
||||||
|
data: {
|
||||||
|
elapsedTime: serverElapsedTime,
|
||||||
|
accumulatedTime: serverElapsedTime,
|
||||||
|
isTracking: false,
|
||||||
|
isPaused: false,
|
||||||
|
startTime: null,
|
||||||
|
endTime: Date.now(),
|
||||||
|
pauseStartTime: null,
|
||||||
|
totalPausedTime: 0
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log("✅ Project synced with server");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error("❌ Error refreshing project list:", error);
|
||||||
|
} finally {
|
||||||
|
// Clear loading states
|
||||||
|
setLoadingStop(false);
|
||||||
|
setIsStopping(false);
|
||||||
|
|
||||||
|
// ✅ Reset stopping flag for next session
|
||||||
|
if (setIsStoppingInTracker) {
|
||||||
|
setIsStoppingInTracker(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("=".repeat(70));
|
||||||
|
console.log("🎉 TRACKING STOPPED SUCCESSFULLY");
|
||||||
|
console.log("=".repeat(70));
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
selectedProject,
|
||||||
|
projectSummary,
|
||||||
|
elapsedTime,
|
||||||
|
dispatch,
|
||||||
|
loadingStop,
|
||||||
|
loadingStart,
|
||||||
|
setIsStoppingInTracker
|
||||||
|
]);
|
||||||
|
|
||||||
// Update the ref whenever finalizeTracking changes
|
// Update the ref whenever finalizeTracking changes
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
stopTrackingRef.current = finalizeTracking;
|
stopTrackingRef.current = finalizeTracking;
|
||||||
|
|
@ -362,7 +397,7 @@ const DashboardLayout = () => {
|
||||||
return () => {
|
return () => {
|
||||||
console.log("🧹 Cleaning token-expired listener");
|
console.log("🧹 Cleaning token-expired listener");
|
||||||
};
|
};
|
||||||
}, [isTracking, handleStopTracking]);
|
}, [isTracking]);
|
||||||
|
|
||||||
|
|
||||||
const handleLogout = async () => {
|
const handleLogout = async () => {
|
||||||
|
|
@ -553,7 +588,7 @@ const DashboardLayout = () => {
|
||||||
if (!selectedProject?.projectId) return
|
if (!selectedProject?.projectId) return
|
||||||
|
|
||||||
console.log(selectedProject?.projectId, "SelectedProject ID")
|
console.log(selectedProject?.projectId, "SelectedProject ID")
|
||||||
socketRef.current = io("https://daniela-nonprofessed-boastfully.ngrok-free.app", {
|
socketRef.current = io("https://quality-friendly-goldfish.ngrok-free.app", {
|
||||||
transports: ["websocket"],
|
transports: ["websocket"],
|
||||||
secure: true,
|
secure: true,
|
||||||
rejectUnauthorized: false,
|
rejectUnauthorized: false,
|
||||||
|
|
@ -585,7 +620,7 @@ const DashboardLayout = () => {
|
||||||
// socketRef.current?.emit("getVisibleApps", { token: await token, apps });
|
// socketRef.current?.emit("getVisibleApps", { token: await token, apps });
|
||||||
// });
|
// });
|
||||||
|
|
||||||
socketRef.current.on("startSendAppsDetails", async ({projId}) => {
|
socketRef.current.on("startSendAppsDetails", async ({ projId }) => {
|
||||||
if (projId !== selectedProject?.projectId) return;
|
if (projId !== selectedProject?.projectId) return;
|
||||||
console.log("▶️ startSendAppsDetails received");
|
console.log("▶️ startSendAppsDetails received");
|
||||||
|
|
||||||
|
|
@ -609,7 +644,7 @@ const DashboardLayout = () => {
|
||||||
}, 5000);
|
}, 5000);
|
||||||
});
|
});
|
||||||
|
|
||||||
socketRef.current.on("stopSendAppsDetails", async ({projId}) => {
|
socketRef.current.on("stopSendAppsDetails", async ({ projId }) => {
|
||||||
if (projId !== selectedProject?.projectId) return;
|
if (projId !== selectedProject?.projectId) return;
|
||||||
console.log("⏹ stopSendAppsDetails received");
|
console.log("⏹ stopSendAppsDetails received");
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@ const LeaveAndAdjustments: React.FC = () => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const { projectSummary } = useSelector((state: any) => state.projects);
|
const { projectSummary } = useSelector((state: any) => state.projects);
|
||||||
console.log("projectSummary46" , projectSummary)
|
console.log("projectSummary46", projectSummary)
|
||||||
|
|
||||||
// Tabs state
|
// Tabs state
|
||||||
const [activeTab, setActiveTab] = useState<"leave" | "adjustments">("leave");
|
const [activeTab, setActiveTab] = useState<"leave" | "adjustments">("leave");
|
||||||
|
|
@ -95,18 +95,25 @@ console.log("projectSummary46" , projectSummary)
|
||||||
|
|
||||||
// Combined refresh function
|
// Combined refresh function
|
||||||
const handleRefresh = async () => {
|
const handleRefresh = async () => {
|
||||||
if (activeTab === "leave") {
|
// if (activeTab === "leave") {
|
||||||
await fetchLeaves();
|
// await fetchLeaves();
|
||||||
} else {
|
// } else {
|
||||||
await fetchAdjustments();
|
// await fetchAdjustments();
|
||||||
}
|
// }
|
||||||
|
|
||||||
|
fetchLeaves();
|
||||||
|
setTimeout(() => {
|
||||||
|
fetchAdjustments();
|
||||||
|
}, 0);
|
||||||
setLastRefreshed(new Date());
|
setLastRefreshed(new Date());
|
||||||
};
|
};
|
||||||
|
|
||||||
// Initial data fetch
|
// Initial data fetch
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchLeaves();
|
fetchLeaves();
|
||||||
|
setTimeout(() => {
|
||||||
fetchAdjustments();
|
fetchAdjustments();
|
||||||
|
}, 0);
|
||||||
setLastRefreshed(new Date());
|
setLastRefreshed(new Date());
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -126,6 +126,33 @@ const SickLeaveTab: React.FC<{
|
||||||
reader.readAsDataURL(file);
|
reader.readAsDataURL(file);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const editor = quillRef.current?.getEditor();
|
||||||
|
|
||||||
|
if (!editor) return;
|
||||||
|
|
||||||
|
editor.getModule("clipboard").addMatcher(Node.ELEMENT_NODE, (node: any, delta: any) => {
|
||||||
|
return delta;
|
||||||
|
});
|
||||||
|
|
||||||
|
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();
|
||||||
|
const base64 = await convertToBase64(file);
|
||||||
|
|
||||||
|
const range = editor.getSelection();
|
||||||
|
editor.insertEmbed(range.index, "image", base64);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
|
||||||
const validateLeaveForm = (data: LeaveFormData): Partial<LeaveFormData> => {
|
const validateLeaveForm = (data: LeaveFormData): Partial<LeaveFormData> => {
|
||||||
const errors: Partial<LeaveFormData> = {};
|
const errors: Partial<LeaveFormData> = {};
|
||||||
|
|
||||||
|
|
@ -161,7 +188,6 @@ const SickLeaveTab: React.FC<{
|
||||||
toDate: "",
|
toDate: "",
|
||||||
comment: ""
|
comment: ""
|
||||||
};
|
};
|
||||||
|
|
||||||
setSickLeave(emptyForm);
|
setSickLeave(emptyForm);
|
||||||
setSickErrors({});
|
setSickErrors({});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,8 @@ const PaysSlips = () => {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log(response , "getPayslips")
|
||||||
|
|
||||||
// Extract payrolls array from first index
|
// Extract payrolls array from first index
|
||||||
const payrolls = response[0]?.payrolls || [];
|
const payrolls = response[0]?.payrolls || [];
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,54 @@
|
||||||
|
import React, { useEffect, useState } from "react";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
children: React.ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
const VpnGuard: React.FC<Props> = ({ children }) => {
|
||||||
|
const [checking, setChecking] = useState(true);
|
||||||
|
const [vpnConnected, setVpnConnected] = useState(true);
|
||||||
|
|
||||||
|
// const checkVPN = async () => {
|
||||||
|
// try {
|
||||||
|
// const res = await window.electron.checkVpnConnection();
|
||||||
|
// setVpnConnected(res?.success);
|
||||||
|
// } catch {
|
||||||
|
// setVpnConnected(false);
|
||||||
|
// } finally {
|
||||||
|
// setChecking(false);
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// checkVPN();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
if (checking) {
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen flex justify-center items-center">
|
||||||
|
<span className="loading loading-spinner loading-lg"></span>
|
||||||
|
<span className="ml-3">Checking VPN...</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!vpnConnected) {
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen flex justify-center items-center text-center">
|
||||||
|
<div className="card shadow-xl bg-base-100 p-6">
|
||||||
|
<h2 className="text-xl font-bold text-red-600">VPN Required</h2>
|
||||||
|
<p className="mt-2">Please connect to your company VPN to continue.</p>
|
||||||
|
|
||||||
|
<button className="btn btn-primary mt-4"
|
||||||
|
onClick={() => window.location.reload()}>
|
||||||
|
Retry
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return <>{children}</>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default VpnGuard;
|
||||||
|
|
@ -1,3 +1,13 @@
|
||||||
|
declare module "quill";
|
||||||
|
declare module "quill-image-resize-module";
|
||||||
|
declare module "quill-image-drop-module";
|
||||||
|
|
||||||
|
declare module "quill-image-resize-module-react" {
|
||||||
|
const ImageResize: any;
|
||||||
|
export default ImageResize;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
type Statistics = {
|
type Statistics = {
|
||||||
cpuUsage: number;
|
cpuUsage: number;
|
||||||
ramUsage: number;
|
ramUsage: number;
|
||||||
|
|
@ -144,14 +154,16 @@ interface Window {
|
||||||
vaId: string;
|
vaId: string;
|
||||||
projectId: string;
|
projectId: string;
|
||||||
type: string;
|
type: string;
|
||||||
|
time:string
|
||||||
}) => Promise<any>;
|
}) => Promise<any>;
|
||||||
stopProjectTracking: (data: {
|
stopProjectTracking: (data: {
|
||||||
vaId: string;
|
vaId: string;
|
||||||
projectId: string;
|
projectId: string;
|
||||||
type: string;
|
type: string;
|
||||||
|
time:string
|
||||||
}) => Promise<any>;
|
}) => Promise<any>;
|
||||||
trackingTabsOpeningClosing: (data: {
|
trackingTabsOpeningClosing: (data: {
|
||||||
id?: string; // optional, only present if updating
|
id?: string;
|
||||||
logId: string;
|
logId: string;
|
||||||
projectId: string;
|
projectId: string;
|
||||||
title: string;
|
title: string;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue