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/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 */}
+ ); };