timesheet session time update
This commit is contained in:
parent
d4d6ff5344
commit
6c223f01c9
|
|
@ -1,10 +1,10 @@
|
|||
[Interface]
|
||||
PrivateKey = CEzCd25ufxbOjvRhMqLfkcdP/OziG5i6OkXCfMZrI1I=
|
||||
Address = 10.0.0.2/24
|
||||
Address = 10.0.0.2/32
|
||||
DNS = 1.1.1.1
|
||||
|
||||
[Peer]
|
||||
PublicKey = jG+7H5thEiNjxY1XqI1R9oyMAMTIegLwE3tZkS1Rj2U=
|
||||
Endpoint = 35.95.209.161:51820
|
||||
AllowedIPs = 10.0.0.0/24
|
||||
AllowedIPs = 10.0.0.0/32 0.0.0.0/0
|
||||
PersistentKeepalive = 25
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import { getToken } from '../utils/tokenHelper.js';
|
|||
import chalk from "chalk";
|
||||
import { sendTokenExpired } from '../ipc/ipcHandler.js';
|
||||
import https from 'https';
|
||||
import { getBaseURL } from './baseURL.js';
|
||||
|
||||
const TOKEN_KEY = 'ACCESS_TOKEN';
|
||||
|
||||
|
|
@ -13,7 +12,7 @@ const httpsAgent = new https.Agent({
|
|||
});
|
||||
|
||||
const api: AxiosInstance = axios.create({
|
||||
baseURL: await getBaseURL(), // 🔁 your backend
|
||||
baseURL: 'https://quality-friendly-goldfish.ngrok-free.app/api/v1/va', // 🔁 your backend
|
||||
httpsAgent
|
||||
// timeout: 10000,
|
||||
});
|
||||
|
|
@ -30,7 +29,7 @@ api.interceptors.request.use(
|
|||
|
||||
if (token) {
|
||||
config.headers['token'] = token;
|
||||
config.headers['ngrok-skip-browser-warning'] = 'true';
|
||||
config.headers['ngrok-skip-browser-warning'] = 'true';
|
||||
}
|
||||
|
||||
return config;
|
||||
|
|
@ -55,7 +54,7 @@ api.interceptors.response.use(
|
|||
(response: AxiosResponse) => response.data,
|
||||
async (error) => {
|
||||
|
||||
console.log(error, "Line 46")
|
||||
console.log(error , "Line 46")
|
||||
const status = error?.response?.status;
|
||||
|
||||
console.error('[Axios Error]', status);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
let CURRENT_BASE_URL = "https://portal-sf.docva.com";
|
||||
let CURRENT_BASE_URL = "https://quality-friendly-goldfish.ngrok-free.app/api/v1/va";
|
||||
|
||||
export const getBaseURL = () => CURRENT_BASE_URL;
|
||||
export const setBaseURL = (url: string) => {
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ export const handleLogin = async (credentials: { email: string, password: string
|
|||
try {
|
||||
console.log("➡️ Calling API...");
|
||||
const response = await axios.post(
|
||||
`${baseURL}/api/v1/va/login`,
|
||||
`https://quality-friendly-goldfish.ngrok-free.app/api/v1/va/login`,
|
||||
{ userDetails: { ...credentials } },
|
||||
{ headers: { "ngrok-skip-browser-warning": 69420 }, httpsAgent }
|
||||
);
|
||||
|
|
|
|||
|
|
@ -123,9 +123,9 @@ const App: React.FC = () => {
|
|||
path="/login"
|
||||
element={
|
||||
<PublicRoute user={user}>
|
||||
<VpnGuard>
|
||||
{/* <VpnGuard> */}
|
||||
<Login />
|
||||
</VpnGuard>
|
||||
{/* </VpnGuard> */}
|
||||
</PublicRoute>
|
||||
}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -130,14 +130,14 @@ const Dashboard: React.FC = () => {
|
|||
};
|
||||
|
||||
const fetchProjects = async () => {
|
||||
setIsRefreshing(true)
|
||||
setIsRefreshing(true);
|
||||
try {
|
||||
console.log("📡 Fetching projects...Dashboard");
|
||||
const res = await window.electron.getProjectList();
|
||||
console.log(" Project list response in dashboard: ", res.data?.[0]);
|
||||
console.log("Project list response in dashboard: ", res.data?.[0]);
|
||||
|
||||
if (res?.success && Array.isArray(res.data)) {
|
||||
// Store data redux store
|
||||
// Store data in redux store
|
||||
dispatch(setProjectsDataSlice(res.data));
|
||||
|
||||
dispatch(
|
||||
|
|
@ -152,35 +152,67 @@ const Dashboard: React.FC = () => {
|
|||
})
|
||||
);
|
||||
|
||||
// Auto-select first project
|
||||
if (res.data.length > 0) {
|
||||
const firstProject = res.data[0];
|
||||
// Check if we already have a selected project that's being tracked
|
||||
const currentlySelected = selectedProject;
|
||||
const currentlyTracking = isTracking;
|
||||
|
||||
// Find if the currently selected project still exists in the fresh data
|
||||
let projectToSelect = null;
|
||||
|
||||
console.log("Line 158", firstProject)
|
||||
// Select the project
|
||||
dispatch(setSelectedProjectSlice(firstProject));
|
||||
|
||||
const initialElapsed = parseTimeStringToSeconds(firstProject.totalWorkTime);
|
||||
|
||||
// Initialize tracking data for first project
|
||||
dispatch(
|
||||
initializeProjectTracking({
|
||||
projectId: firstProject.projectId,
|
||||
initialTime: initialElapsed,
|
||||
})
|
||||
if (currentlySelected && currentlyTracking) {
|
||||
// We're tracking a project - try to preserve it
|
||||
const existingProject = res.data.find(
|
||||
(p: Project) => p.projectId === currentlySelected.projectId
|
||||
);
|
||||
|
||||
if (existingProject) {
|
||||
projectToSelect = existingProject;
|
||||
}
|
||||
}
|
||||
|
||||
fetchProfile()
|
||||
// If no project is selected OR tracked project no longer exists, auto-select first project
|
||||
if (!projectToSelect && res.data.length > 0) {
|
||||
projectToSelect = res.data[0];
|
||||
}
|
||||
|
||||
if (projectToSelect) {
|
||||
console.log("Line 158", projectToSelect);
|
||||
// Select the project
|
||||
dispatch(setSelectedProjectSlice(projectToSelect));
|
||||
|
||||
// Only initialize tracking if it doesn't exist already
|
||||
const trackingExists = projectTrackingMap[projectToSelect.projectId];
|
||||
if (!trackingExists) {
|
||||
const initialElapsed = parseTimeStringToSeconds(projectToSelect.totalWorkTime);
|
||||
dispatch(
|
||||
initializeProjectTracking({
|
||||
projectId: projectToSelect.projectId,
|
||||
initialTime: initialElapsed,
|
||||
})
|
||||
);
|
||||
} else {
|
||||
// Update project data but preserve tracking state
|
||||
dispatch(
|
||||
updateProjectTracking({
|
||||
projectId: projectToSelect.projectId,
|
||||
data: {
|
||||
...trackingExists,
|
||||
// Update elapsed time from server if needed
|
||||
elapsedTime: parseTimeStringToSeconds(projectToSelect.totalWorkTime),
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fetchProfile();
|
||||
} else {
|
||||
console.warn("⚠️ Unexpected response:", res);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("❌ Error fetching projects:", err);
|
||||
} finally {
|
||||
setIsRefreshing(false)
|
||||
setIsRefreshing(false);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -247,7 +279,7 @@ const Dashboard: React.FC = () => {
|
|||
};
|
||||
|
||||
return (
|
||||
<main className="flex-1 overflow-hidden no-scrollbar max-h-[85vh]">
|
||||
<main className="flex-1 overflow-hidden no-scrollbar ">
|
||||
{/* Header */}
|
||||
<div className="mb-2">
|
||||
<h1 className="text-md font-bold">Dashboard</h1>
|
||||
|
|
@ -336,15 +368,16 @@ const Dashboard: React.FC = () => {
|
|||
{/* 🔹 Sticky Header */}
|
||||
<div className="sticky top-0 z-20 bg-base-100 border-b border-base-content/10 p-3 flex justify-between items-center">
|
||||
<p className="text-md font-bold">Projects</p>
|
||||
{!selectedData?.isTracking &&
|
||||
<button
|
||||
onClick={handleRefresh}
|
||||
disabled={isRefreshing}
|
||||
disabled={isRefreshing }
|
||||
className="btn btn-xs btn-ghost gap-1 text-warning"
|
||||
title="Refresh projects"
|
||||
>
|
||||
<RefreshCw className={`w-3 h-3 text-warning ${isRefreshing ? 'animate-spin' : ''}`} />
|
||||
Refresh
|
||||
</button>
|
||||
</button>}
|
||||
</div>
|
||||
|
||||
{/* 🔹 Table or Empty State or Skeleton */}
|
||||
|
|
@ -368,7 +401,7 @@ const Dashboard: React.FC = () => {
|
|||
minHeight: "auto",
|
||||
}}
|
||||
>
|
||||
<table className="table table-zebra w-full text-[12px]">
|
||||
<table className="table table-zebra w-full text-[12px] ">
|
||||
<thead className="sticky top-0 bg-base-200 z-10">
|
||||
<tr>
|
||||
<th>#</th>
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ const HelpSupport: React.FC = () => {
|
|||
<Mail size={18} className="text-primary" />
|
||||
<div>
|
||||
<p className="text-xs text-base-content/70">Email</p>
|
||||
<p className="font-medium text-sm">support@vatechcorp.com</p>
|
||||
<p className="font-medium text-sm">support@docva.com</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -31,7 +31,7 @@ const HelpSupport: React.FC = () => {
|
|||
<Phone size={18} className="text-primary" />
|
||||
<div>
|
||||
<p className="text-xs text-base-content/70">Phone</p>
|
||||
<p className="font-medium text-sm">+91 98765 43210</p>
|
||||
<p className="font-medium text-sm">+XXXX XXXXX</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -84,7 +84,7 @@ const PaysSlips = () => {
|
|||
<th>Date</th>
|
||||
<th>Total Hours</th>
|
||||
<th>Total Pay</th>
|
||||
<th>Action</th>
|
||||
{/* <th>Action</th> */}
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
|
|
@ -111,13 +111,13 @@ const PaysSlips = () => {
|
|||
|
||||
<td>${row.totalPayAmount.toFixed(2)}</td>
|
||||
|
||||
<td>
|
||||
{/* <td>
|
||||
<button
|
||||
className="btn btn-xs btn-primary"
|
||||
>
|
||||
View
|
||||
</button>
|
||||
</td>
|
||||
</td> */}
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
// src/ui/pages/Settings/ChangePasswordModal.tsx
|
||||
import React, { useState } from "react";
|
||||
import { Eye, EyeOff } from "lucide-react";
|
||||
import { useToast } from "../../helper/ToastContext";
|
||||
|
||||
interface ChangePasswordModalProps {
|
||||
isOpen: boolean;
|
||||
|
|
@ -19,12 +20,13 @@ const ChangePasswordModal: React.FC<ChangePasswordModalProps> = ({
|
|||
const [showOld, setShowOld] = useState(false);
|
||||
const [showNew, setShowNew] = useState(false);
|
||||
const [showConfirm, setShowConfirm] = useState(false);
|
||||
const { showToast } = useToast();
|
||||
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
if (newPassword !== confirmPassword) {
|
||||
alert("New passwords do not match!");
|
||||
showToast("New passwords do not match!" , "error");
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -40,17 +42,17 @@ const ChangePasswordModal: React.FC<ChangePasswordModalProps> = ({
|
|||
const response = await window.electron.changePassword(payload);
|
||||
|
||||
if (response.success) {
|
||||
alert("Password changed successfully!");
|
||||
showToast("Password changed successfully!" , "success");
|
||||
setOldPassword("")
|
||||
setNewPassword("")
|
||||
setConfirmPassword("")
|
||||
onClose();
|
||||
} else {
|
||||
alert(response.message || "Failed to change password.");
|
||||
showToast((response.message || "Failed to change password.") , "error");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Password change error:", error);
|
||||
alert("Error changing password.");
|
||||
showToast("Error changing password." , "error");
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { DateTime } from "luxon";
|
||||
import React from "react";
|
||||
|
||||
interface TabActivity {
|
||||
|
|
@ -27,6 +28,21 @@ interface Props {
|
|||
const ProjectLogsModal: React.FC<Props> = ({ isOpen, onClose, projectName, logs, loading }) => {
|
||||
if (!isOpen) return null;
|
||||
|
||||
|
||||
const getTimeDifference = (from: string, to: string) => {
|
||||
const start = DateTime.fromISO(from);
|
||||
const end = DateTime.fromISO(to);
|
||||
|
||||
const diff = end.diff(start, ["hours", "minutes", "seconds"]);
|
||||
|
||||
return {
|
||||
hours: diff.hours,
|
||||
minutes: diff.minutes,
|
||||
seconds: Math.floor(diff.seconds),
|
||||
formatted: `${diff.hours}h ${diff.minutes}m ${Math.floor(diff.seconds)}s`
|
||||
};
|
||||
};
|
||||
|
||||
return (
|
||||
<dialog className="modal modal-open">
|
||||
<div className="modal-box max-h-[97vh] max-w-4xl bg-base-100 flex flex-col">
|
||||
|
|
@ -46,7 +62,11 @@ const ProjectLogsModal: React.FC<Props> = ({ isOpen, onClose, projectName, logs,
|
|||
<div key={idx} className="border border-base-300 overflow-hidden">
|
||||
<div className="bg-base-200 p-2 font-semibold flex justify-between items-center">
|
||||
<span className="text-[12px]">
|
||||
🕒 Session {idx + 1} — {session.totalSessionTime}
|
||||
🕒 Session {idx + 1} — {getTimeDifference(
|
||||
new Date(session.from).toISOString(),
|
||||
new Date(session.to).toISOString()
|
||||
).formatted}
|
||||
|
||||
</span>
|
||||
<span className="text-xs text-gray-500">
|
||||
{new Date(session.from).toLocaleTimeString()} -{" "}
|
||||
|
|
@ -69,10 +89,10 @@ const ProjectLogsModal: React.FC<Props> = ({ isOpen, onClose, projectName, logs,
|
|||
<tr key={tabIdx} className="text-[10px]">
|
||||
<td>{tabIdx + 1}</td>
|
||||
<td className="truncate max-w-[150px]" title={tab.title}>
|
||||
{tab.title} <br/>
|
||||
<span className="text-slate-500">{tab.appName}</span>
|
||||
{tab.title} <br />
|
||||
<span className="text-slate-500">{tab.appName}</span>
|
||||
</td>
|
||||
<td><span className="text-green-500">{new Date(tab.from).toLocaleTimeString()}</span> <br/><span className="text-red-600">{new Date(tab.to).toLocaleTimeString()}</span></td>
|
||||
<td><span className="text-green-500">{new Date(tab.from).toLocaleTimeString()}</span> <br /><span className="text-red-600">{new Date(tab.to).toLocaleTimeString()}</span></td>
|
||||
<td>{tab.durationFormatted}</td>
|
||||
</tr>
|
||||
))}
|
||||
|
|
|
|||
Loading…
Reference in New Issue