add the times pre and next functionality in timesheet modal and add the payoneerAccount user profile update
This commit is contained in:
parent
b4403c2cd5
commit
1fc065a59b
|
|
@ -1,7 +1,7 @@
|
|||
|
||||
[Interface]
|
||||
PrivateKey = yHrWqSi5ZV4YV1cVVPBt2NbmTHd/mDWtZGVWGQe5I3s=
|
||||
Address = 10.0.0.91/32
|
||||
PrivateKey = gPqf7ScXv2xtLkZz5a8qa7RNJ7uU5TQUo/rTfcprMWE=
|
||||
Address = 10.0.0.172/32
|
||||
DNS = 1.1.1.1
|
||||
|
||||
[Peer]
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ const httpsAgent = new https.Agent({
|
|||
// console.log('Base_URL$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$' ,Base_URL)
|
||||
|
||||
const api: AxiosInstance = axios.create({
|
||||
// baseURL: 'https://portal-sf.docva.com/api/v1/va', // 🔁 your backend
|
||||
// baseURL: 'http://dreadingly-unfighting-pandora.ngrok-free.dev/api/v1/va', // 🔁 your backend
|
||||
// baseURL: `${Base_URL}/api/v1/va`,
|
||||
httpsAgent
|
||||
// timeout: 10000,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { syncAxiosBaseURL } from "./axiosClient.js";
|
||||
|
||||
// let CURRENT_BASE_URL = "https://portal-sf.docva.com/api/v1/va";
|
||||
let CURRENT_BASE_URL = "https://portal-sf.docva.com";
|
||||
// let CURRENT_BASE_URL = "http://dreadingly-unfighting-pandora.ngrok-free.dev/api/v1/va";
|
||||
let CURRENT_BASE_URL = "http://dreadingly-unfighting-pandora.ngrok-free.dev";
|
||||
|
||||
export const getBaseURL = () => CURRENT_BASE_URL;
|
||||
export const setBaseURL = (url: string) => {
|
||||
|
|
|
|||
|
|
@ -202,11 +202,11 @@ ipcMain.handle("get-project-logs", async (event, { projectID, fromDate, toDate }
|
|||
|
||||
// 🧠 Append query params only if dates are provided
|
||||
if (fromDate && toDate) {
|
||||
const params = new URLSearchParams({
|
||||
from: fromDate,
|
||||
to: toDate,
|
||||
});
|
||||
url += `?${params.toString()}`;
|
||||
// const params = new URLSearchParams({
|
||||
// from: fromDate,
|
||||
// to: toDate,
|
||||
// });
|
||||
url += `/${fromDate}/${toDate}`;
|
||||
}
|
||||
|
||||
console.log("url", url)
|
||||
|
|
@ -509,15 +509,24 @@ ipcMain.handle("update-payment", async (event, data: Record<string, any>) => {
|
|||
paymentMethod: data.paymentMethod,
|
||||
};
|
||||
|
||||
// paymentMethod: 'payoneeraccount',
|
||||
// payoneerAccount: 'check@gmail.com'
|
||||
|
||||
// Add payment-specific fields
|
||||
if (data.paymentMethod === "wise" && data.wiseEmail) {
|
||||
userDetails.wiseEmail = data.wiseEmail;
|
||||
// Clear veemAccount when switching to wise
|
||||
userDetails.veemAccount = "";
|
||||
userDetails.payoneerAccount = ""
|
||||
} else if (data.paymentMethod === "veem" && data.veemAccount) {
|
||||
userDetails.veemAccount = data.veemAccount;
|
||||
// Clear wiseEmail when switching to veem
|
||||
userDetails.wiseEmail = "";
|
||||
userDetails.payoneerAccount = ""
|
||||
} else if (data.paymentMethod === "payoneeraccount" && data.payoneerAccount) {
|
||||
userDetails.payoneerAccount = data.payoneerAccount;
|
||||
userDetails.veemAccount = "";
|
||||
userDetails.wiseEmail = ""
|
||||
}
|
||||
|
||||
// Build final payload
|
||||
|
|
|
|||
|
|
@ -592,7 +592,7 @@ const DashboardLayout = () => {
|
|||
if (!selectedProject?.projectId) return
|
||||
|
||||
console.log(selectedProject?.projectId, "SelectedProject ID")
|
||||
socketRef.current = io("https://portal-sf.docva.com", {
|
||||
socketRef.current = io("http://dreadingly-unfighting-pandora.ngrok-free.dev", {
|
||||
transports: ["websocket"],
|
||||
secure: true,
|
||||
rejectUnauthorized: false,
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ const ChangePaymentModal: React.FC<ChangePaymentModalProps> = ({
|
|||
const [method, setMethod] = useState(profile?.paymentMethod || "");
|
||||
const [wiseEmail, setWiseEmail] = useState(profile?.wiseEmail || "");
|
||||
const [veemAccount, setVeemAccount] = useState(profile?.veemAccount || "");
|
||||
const [payoneerAccount, setPayoneerAccount] = useState(profile?.payoneerAccount || "");
|
||||
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState("");
|
||||
|
|
@ -28,6 +29,7 @@ const ChangePaymentModal: React.FC<ChangePaymentModalProps> = ({
|
|||
setMethod(profile?.paymentMethod || "");
|
||||
setWiseEmail(profile?.wiseEmail || "");
|
||||
setVeemAccount(profile?.veemAccount || "");
|
||||
setPayoneerAccount(profile?.payoneerAccount || "")
|
||||
setError("");
|
||||
}
|
||||
}, [isOpen]);
|
||||
|
|
@ -46,12 +48,16 @@ const ChangePaymentModal: React.FC<ChangePaymentModalProps> = ({
|
|||
return setError("Wise email is required.");
|
||||
}
|
||||
|
||||
if (method === "wise" && !/^\S+@\S+\.\S+$/.test(wiseEmail)) {
|
||||
return setError("Enter a valid Wise email.");
|
||||
if (method === "veem" && !veemAccount.trim()) {
|
||||
return setError("Veem email is required.");
|
||||
}
|
||||
|
||||
if (method === "veem" && !veemAccount.trim()) {
|
||||
return setError("Veem account number is required.");
|
||||
if (method === "payoneeraccount" && !payoneerAccount.trim()) {
|
||||
return setError("Payoneer email is required.");
|
||||
}
|
||||
|
||||
if ((method === "wise" || "veem" || "payoneeraccount") && !/^\S+@\S+\.\S+$/.test(wiseEmail)) {
|
||||
return setError("Enter a valid email.");
|
||||
}
|
||||
|
||||
setLoading(true);
|
||||
|
|
@ -67,6 +73,8 @@ const ChangePaymentModal: React.FC<ChangePaymentModalProps> = ({
|
|||
updateData.wiseEmail = wiseEmail.trim();
|
||||
} else if (method === "veem") {
|
||||
updateData.veemAccount = veemAccount.trim();
|
||||
}else if (method === "payoneeraccount") {
|
||||
updateData.payoneerAccount = payoneerAccount.trim();
|
||||
}
|
||||
|
||||
console.log("Sending to electron:", updateData);
|
||||
|
|
@ -134,6 +142,7 @@ const ChangePaymentModal: React.FC<ChangePaymentModalProps> = ({
|
|||
<option value="">Select method</option>
|
||||
<option value="wise">Wise</option>
|
||||
<option value="veem">Veem</option>
|
||||
<option value="payoneeraccount">Payoneer account</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
|
|
@ -169,6 +178,22 @@ const ChangePaymentModal: React.FC<ChangePaymentModalProps> = ({
|
|||
</div>
|
||||
)}
|
||||
|
||||
|
||||
{method === "payoneeraccount" && (
|
||||
<div className="form-control mb-4">
|
||||
<label className="label">
|
||||
<span className="text-sm font-medium">Payoneer Account Number</span>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
className="input input-bordered input-sm"
|
||||
placeholder="Enter Payoneer account"
|
||||
value={payoneerAccount}
|
||||
onChange={(e) => setPayoneerAccount(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="modal-action">
|
||||
<button className="btn btn-sm btn-ghost" onClick={onClose}>
|
||||
Cancel
|
||||
|
|
|
|||
|
|
@ -107,7 +107,7 @@ const Settings: React.FC = () => {
|
|||
{profile?.veemAccount && (
|
||||
<div className="p-2 rounded bg-base-200/40">
|
||||
<p className="text-xs text-base-content/60 flex items-center gap-1 mb-0.5">
|
||||
🏦 Veem Account
|
||||
🏦 Veem Email
|
||||
</p>
|
||||
<p className="text-xs font-medium truncate" title={profile?.veemAccount || "N/A"}>
|
||||
{profile?.veemAccount || "N/A"}
|
||||
|
|
@ -126,6 +126,19 @@ const Settings: React.FC = () => {
|
|||
</div>
|
||||
)}
|
||||
|
||||
{profile?.payoneerAccount && (
|
||||
<div className="p-2 rounded bg-base-200/40">
|
||||
<p className="text-xs text-base-content/60 flex items-center gap-1 mb-0.5 ">
|
||||
🏦 Payoneer Account
|
||||
</p>
|
||||
<p className="text-xs font-medium overflow-auto no-scrollbar" title={profile?.payoneerAccount || "N/A"}>
|
||||
{profile?.payoneerAccount || "N/A"}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
|
||||
{/* Location - Full Width */}
|
||||
<div className="p-2 rounded bg-base-200/40 col-span-2 sm:col-span-3">
|
||||
<p className="text-xs text-base-content/60 flex items-center gap-1 mb-0.5">
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { ChevronLeft, ChevronRight } from "lucide-react";
|
||||
import { DateTime } from "luxon";
|
||||
import React from "react";
|
||||
import React, { useEffect, useState } from "react";
|
||||
|
||||
interface TabActivity {
|
||||
title: string;
|
||||
|
|
@ -21,13 +22,44 @@ interface Props {
|
|||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
projectName: string;
|
||||
|
||||
logs: ProjectSession[];
|
||||
loading: boolean;
|
||||
|
||||
setLoading: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
setLogs: React.Dispatch<React.SetStateAction<ProjectSession[]>>;
|
||||
|
||||
projectID: string | null;
|
||||
}
|
||||
|
||||
const ProjectLogsModal: React.FC<Props> = ({ isOpen, onClose, projectName, logs, loading }) => {
|
||||
|
||||
const ProjectLogsModal: React.FC<Props> = ({ isOpen, onClose, projectName, logs, loading, setLoading,
|
||||
setLogs,
|
||||
projectID, }) => {
|
||||
if (!isOpen) return null;
|
||||
|
||||
const [selectedDate, setSelectedDate] = useState<Date>(new Date());
|
||||
const isFirstRender = React.useRef(true);
|
||||
|
||||
useEffect(() => {
|
||||
// ⛔ skip first render (parent already fetched)
|
||||
if (isFirstRender.current) {
|
||||
isFirstRender.current = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// ✅ fetch only when date changes
|
||||
fetchLogs(selectedDate);
|
||||
}, [selectedDate]);
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
if (!isOpen) {
|
||||
isFirstRender.current = true;
|
||||
}
|
||||
}, [isOpen]);
|
||||
|
||||
|
||||
|
||||
const getTimeDifference = (from: string, to: string) => {
|
||||
const start = DateTime.fromISO(from);
|
||||
|
|
@ -43,12 +75,81 @@ const ProjectLogsModal: React.FC<Props> = ({ isOpen, onClose, projectName, logs,
|
|||
};
|
||||
};
|
||||
|
||||
const getDayRange = (date: Date) => {
|
||||
const d = DateTime.fromJSDate(date);
|
||||
|
||||
return {
|
||||
fromDate: d.toFormat("yyyy-MM-dd"),
|
||||
toDate: d.toFormat("yyyy-MM-dd"),
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
const handlePrevDay = () => {
|
||||
const prev = new Date(selectedDate);
|
||||
prev.setDate(prev.getDate() - 1);
|
||||
|
||||
setSelectedDate(prev); // triggers useEffect
|
||||
};
|
||||
|
||||
|
||||
const handleNextDay = () => {
|
||||
const next = new Date(selectedDate);
|
||||
next.setDate(next.getDate() + 1);
|
||||
|
||||
setSelectedDate(next); // triggers useEffect
|
||||
};
|
||||
|
||||
|
||||
const fetchLogs = async (date: Date) => {
|
||||
if (!projectID) return;
|
||||
|
||||
try {
|
||||
setLoading(true);
|
||||
|
||||
// HERE
|
||||
const { fromDate, toDate } = getDayRange(date);
|
||||
|
||||
const res = await window.electron.getProjectLogs(
|
||||
projectID,
|
||||
fromDate,
|
||||
toDate
|
||||
);
|
||||
|
||||
if (res?.success && Array.isArray(res.data)) {
|
||||
setLogs(res.data[0]?.sessions || []);
|
||||
} else {
|
||||
setLogs([]);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Failed to fetch logs", err);
|
||||
setLogs([]);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<dialog className="modal modal-open">
|
||||
<div className="modal-box max-h-[97vh] max-w-4xl bg-base-100 flex flex-col">
|
||||
<h3 className="font-bold text-xs mb-3">
|
||||
🗂️ Project Logs — <span className="text-primary">{projectName}</span>
|
||||
</h3>
|
||||
<div className="flex justify-between items-center mb-3">
|
||||
<div className="font-bold text-xs">
|
||||
🗂️ Project Logs — <span className="text-primary">{projectName}</span>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2 text-xs">
|
||||
|
||||
<button className="btn btn-xs btn-primary p-1" onClick={handlePrevDay}><ChevronLeft className="" size={12}/></button>
|
||||
|
||||
<span className="font-semibold">
|
||||
{DateTime.fromJSDate(selectedDate).toFormat("dd LLL yyyy")}
|
||||
</span>
|
||||
|
||||
<button className="btn btn-xs btn-primary p-1" onClick={handleNextDay}><ChevronRight className="" size={12}/></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
{loading ? (
|
||||
<div className="flex justify-center items-center flex-grow">
|
||||
|
|
|
|||
|
|
@ -35,6 +35,13 @@ const timesheetData: TimesheetEntry[] = [
|
|||
{ id: 5, date: "2025-11-01", project: "AI Model Trainer", hours: 8, status: "Approved", remarks: "Normal day" },
|
||||
];
|
||||
|
||||
interface ProjectSession {
|
||||
from: string;
|
||||
to: string;
|
||||
totalSessionTime: string;
|
||||
tabs: TabActivity[];
|
||||
}
|
||||
|
||||
interface Project {
|
||||
projectId: string;
|
||||
projectName: string;
|
||||
|
|
@ -55,8 +62,8 @@ const Timesheets: React.FC = () => {
|
|||
|
||||
console.log(" Timesheet in component:", windowLogs);
|
||||
const [projectsData, setProjectsData] = useState<Project[]>([]);
|
||||
const [logs, setLogs] = useState([]);
|
||||
// const [selectedProject, setSelectedProject] = useState<string | null>(null);
|
||||
const [logs, setLogs] = useState<ProjectSession[]>([]);
|
||||
const [selectedProjectLogId, setSelectedProjectLogId] = useState<string | null>(null);
|
||||
const [selectedProjectName, setSelectedProjectName] = useState<string>("");
|
||||
const [modalOpen, setModalOpen] = useState(false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
|
@ -122,7 +129,7 @@ const Timesheets: React.FC = () => {
|
|||
try {
|
||||
setModalOpen(true);
|
||||
setLoading(true);
|
||||
// setSelectedProject(projectID);
|
||||
setSelectedProjectLogId(projectID);
|
||||
setSelectedProjectName(projectName);
|
||||
|
||||
console.log("projectID", projectID)
|
||||
|
|
@ -290,6 +297,9 @@ const Timesheets: React.FC = () => {
|
|||
projectName={selectedProjectName}
|
||||
logs={logs}
|
||||
loading={loading}
|
||||
setLoading={setLoading}
|
||||
setLogs={setLogs}
|
||||
projectID={selectedProjectLogId}
|
||||
/>
|
||||
</main>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import React, { useEffect, useState } from "react";
|
||||
import { Shield, ShieldAlert, RefreshCw, Wifi, Power } from "lucide-react";
|
||||
import { Shield, ShieldAlert, RefreshCw, Wifi, Power, X } from "lucide-react";
|
||||
|
||||
interface Props {
|
||||
children: React.ReactNode;
|
||||
|
|
@ -8,7 +8,7 @@ interface Props {
|
|||
const VpnGuard: React.FC<Props> = ({ children }) => {
|
||||
|
||||
const VPN_API = "http://10.0.0.1";
|
||||
const DEFAULT_API = "https://portal-sf.docva.com"
|
||||
const DEFAULT_API = "http://dreadingly-unfighting-pandora.ngrok-free.dev"
|
||||
|
||||
const [checking, setChecking] = useState(true);
|
||||
const [vpnConnected, setVpnConnected] = useState(true);
|
||||
|
|
@ -21,6 +21,7 @@ const VpnGuard: React.FC<Props> = ({ children }) => {
|
|||
const res = await window.electron?.checkRegion();
|
||||
console.log("VpnGuard", res);
|
||||
if (res.status === 200) {
|
||||
const baseRes = await window.electron.updateBaseURL(DEFAULT_API);
|
||||
setVpnConnected(true);
|
||||
} else {
|
||||
setVpnConnected(false);
|
||||
|
|
@ -130,6 +131,7 @@ const VpnGuard: React.FC<Props> = ({ children }) => {
|
|||
<div className="min-h-screen flex flex-col justify-center items-center bg-base-200 p-4">
|
||||
<div className="max-w-md w-full">
|
||||
<div className="card bg-base-100 shadow-2xl">
|
||||
<X className="mt-2 mr-2 ml-auto text-slate-300 hover:text-slate-400" onClick={() => setVpnConnected(true)} />
|
||||
<div className="card-body items-center text-center">
|
||||
|
||||
{/* Icon */}
|
||||
|
|
|
|||
Loading…
Reference in New Issue