From fbb68a4488c5af39b3d3ce78716ac68f27dcb19d Mon Sep 17 00:00:00 2001 From: Kartik Sharma Date: Fri, 10 Oct 2025 15:24:34 +0530 Subject: [PATCH 1/9] Correction in DataRangePicker for date range box and Calender. --- src/components/common/DateRangePicker.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/common/DateRangePicker.jsx b/src/components/common/DateRangePicker.jsx index 8e53b5e6..8e815fec 100644 --- a/src/components/common/DateRangePicker.jsx +++ b/src/components/common/DateRangePicker.jsx @@ -183,7 +183,7 @@ export const DateRangePicker1 = ({
{ From 9de0b1a0df506c615ea8d0cb5695ed630c806613 Mon Sep 17 00:00:00 2001 From: Kartik Sharma Date: Fri, 10 Oct 2025 16:00:52 +0530 Subject: [PATCH 2/9] Project List view UI implementation. --- src/components/Project/ProjectListView.jsx | 241 +++++++++++---------- 1 file changed, 121 insertions(+), 120 deletions(-) diff --git a/src/components/Project/ProjectListView.jsx b/src/components/Project/ProjectListView.jsx index fb81428f..0c1a5c41 100644 --- a/src/components/Project/ProjectListView.jsx +++ b/src/components/Project/ProjectListView.jsx @@ -131,148 +131,149 @@ const ProjectListView = ({ const handleMoveDetails = (project) => { dispatch(setProjectId(project)); - localStorage.setItem("lastActiveProjectTab","profile") + localStorage.setItem("lastActiveProjectTab", "profile") navigate("/projects/details"); }; return (
- - - - {projectColumns.map((col) => ( - - ))} - - - - - {currentItems?.map((project) => ( - +
+
- {col.label} - Action
+ + {projectColumns.map((col) => ( - + ))} - + + + {currentItems?.map((project) => ( + + {projectColumns.map((col) => ( + + ))} + - - ))} - -
- {col.getValue - ? col.getValue(project) - : project[col.key] || "N/A"} - + {col.label} + -
-
- - -
    -
  • handleMoveDetails(project.id)}> - - - View details - -
  • + {col.getValue + ? col.getValue(project) + : project[col.key] || "N/A"} +
+
+ + -
-
+ +
+ + + ))} + + - {isLoading && ( -
- {" "} - {isLoading &&

Loading...

} - {!isLoading && filteredProjects.length === 0 && ( + {isLoading && ( +
+ {" "} + {isLoading &&

Loading...

} + {!isLoading && filteredProjects.length === 0 && ( +

No projects found.

+ )} +
+ )} + {!isLoading && currentItems.length === 0 && ( +

No projects found.

- )} -
- )} - {!isLoading && currentItems.length === 0 && ( -
-

No projects found.

-
- )} - {!isLoading && totalPages > 1 && ( -
+ )} + {!isLoading && totalPages > 1 && ( + - )} + + + )} +
); }; From 44b3f842f20ec3cf650be2fbf30b251c6bb1fc94 Mon Sep 17 00:00:00 2001 From: Kartik Sharma Date: Fri, 10 Oct 2025 16:36:39 +0530 Subject: [PATCH 3/9] Correction in Date Ranger in Attendance Logs. --- src/components/common/DateRangePicker.jsx | 31 +++++++++++------------ 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/src/components/common/DateRangePicker.jsx b/src/components/common/DateRangePicker.jsx index 8e815fec..fd0cc6e3 100644 --- a/src/components/common/DateRangePicker.jsx +++ b/src/components/common/DateRangePicker.jsx @@ -33,7 +33,7 @@ const DateRangePicker = ({ startDate.setHours(0, 0, 0, 0); } - const fp = flatpickr(inputRef.current, { + const fp = flatpickr(inputRef.current, { mode: "range", dateFormat: "Y-m-d", altInput: true, @@ -63,19 +63,18 @@ const DateRangePicker = ({ }; return ( -
+
); @@ -130,7 +129,7 @@ export const DateRangePicker1 = ({ mode: "range", dateFormat: "d-m-Y", allowInput: allowText, - maxDate , + maxDate, onChange: (selectedDates) => { if (selectedDates.length === 2) { const [start, end] = selectedDates; @@ -160,19 +159,19 @@ export const DateRangePicker1 = ({ }, []); useEffect(() => { - if (resetSignal !== undefined) { - if (defaultRange) { - applyDefaultDates(); - } else { - setValue(startField, "", { shouldValidate: true }); - setValue(endField, "", { shouldValidate: true }); + if (resetSignal !== undefined) { + if (defaultRange) { + applyDefaultDates(); + } else { + setValue(startField, "", { shouldValidate: true }); + setValue(endField, "", { shouldValidate: true }); - if (inputRef.current?._flatpickr) { - inputRef.current._flatpickr.clear(); + if (inputRef.current?._flatpickr) { + inputRef.current._flatpickr.clear(); + } } } - } -}, [resetSignal, defaultRange, setValue, startField, endField]); + }, [resetSignal, defaultRange, setValue, startField, endField]); const start = getValues(startField); From 9580c9234c3e29b1e290ee50dd3de0a858950513 Mon Sep 17 00:00:00 2001 From: Kartik Sharma Date: Fri, 10 Oct 2025 15:44:24 +0530 Subject: [PATCH 4/9] UI updation in Expense Breakdown --- src/components/Dashboard/ExpenseAnalysis.jsx | 74 +++++++++----------- 1 file changed, 35 insertions(+), 39 deletions(-) diff --git a/src/components/Dashboard/ExpenseAnalysis.jsx b/src/components/Dashboard/ExpenseAnalysis.jsx index 283e64c8..50528055 100644 --- a/src/components/Dashboard/ExpenseAnalysis.jsx +++ b/src/components/Dashboard/ExpenseAnalysis.jsx @@ -1,4 +1,4 @@ -import React, { useEffect, useMemo, useState } from "react"; +import React, { useEffect, useMemo } from "react"; import Chart from "react-apexcharts"; import { useExpenseAnalysis } from "../../hooks/useDashboard_Data"; import { useSelectedProject } from "../../slices/apiDataManager"; @@ -10,36 +10,27 @@ const ExpenseAnalysis = () => { const projectId = useSelectedProject(); const methods = useForm({ - defaultValues: { - startDate: "", - endDate: "", - }, + defaultValues: { startDate: "", endDate: "" }, }); const { watch } = methods; - - const [startDate, endDate] = watch(["startDate", "endDate"]); + const { data, isLoading, isError, error, isFetching } = useExpenseAnalysis( - projectId, - startDate ? localToUtc(startDate) : null, - endDate ? localToUtc(endDate) : null + projectId, + startDate ? localToUtc(startDate) : null, + endDate ? localToUtc(endDate) : null ); if (isError) return
{error.message}
; - - - const report = data?.report ?? []; - -const { labels, series, total } = useMemo(() => { - const labels = report.map((item) => item.projectName); - const series = report.map((item) => item.totalApprovedAmount || 0); - const total = formatCurrency(data?.totalAmount || 0); - - return { labels, series, total }; -}, [report, data?.totalAmount]); + const { labels, series, total } = useMemo(() => { + const labels = report.map((item) => item.projectName); + const series = report.map((item) => item.totalApprovedAmount || 0); + const total = formatCurrency(data?.totalAmount || 0); + return { labels, series, total }; + }, [report, data?.totalAmount]); const donutOptions = { chart: { type: "donut" }, @@ -63,26 +54,34 @@ const { labels, series, total } = useMemo(() => { }, }, }, + responsive: [ + { + breakpoint: 576, // mobile breakpoint + options: { + chart: { width: "100%" }, + }, + }, + ], }; - return ( <> -
-
-
Expense Breakdown
-

Category Wise Expense Breakdown

+ {/* Header */} +
+
+
Expense Breakdown
+

Category Wise Expense Breakdown

-
+
+ {/* Card body */}
- {/* Initial loading: show full loader */} {isLoading && (
{
)} - {/* Data display */} {!isLoading && report.length === 0 && ( -
No data found
+
No data found
)} {!isLoading && report.length > 0 && ( <> - {/* Overlay spinner for refetch */} {isFetching && (
Loading... @@ -109,17 +106,18 @@ const { labels, series, total } = useMemo(() => {
item.totalApprovedAmount || 0)} + series={series} type="donut" - width="320" + width="100%" + height={320} />
-
+
{report.map((item, idx) => (
@@ -127,16 +125,14 @@ const { labels, series, total } = useMemo(() => { className="avatar-initial rounded-2" style={{ backgroundColor: - donutOptions.colors[ - idx % donutOptions.colors.length - ], + donutOptions.colors[idx % donutOptions.colors.length], }} >
- {item.projectName} + {item.projectName} {formatCurrency(item.totalApprovedAmount)} From 9939973d8d2f95e4b5ea8bcf86b6c773ce236138 Mon Sep 17 00:00:00 2001 From: Kartik Sharma Date: Fri, 10 Oct 2025 14:43:11 +0530 Subject: [PATCH 5/9] Adding Excel and PDF export in Directory. --- src/pages/Directory/DirectoryPage.jsx | 85 ++++++++++++------ src/pages/Directory/NotesPage.jsx | 4 +- src/utils/tableExportUtils.jsx | 120 ++++++++++++++++++++++++-- 3 files changed, 173 insertions(+), 36 deletions(-) diff --git a/src/pages/Directory/DirectoryPage.jsx b/src/pages/Directory/DirectoryPage.jsx index 5f819e69..7a0c4cce 100644 --- a/src/pages/Directory/DirectoryPage.jsx +++ b/src/pages/Directory/DirectoryPage.jsx @@ -1,3 +1,4 @@ + import { useState, Suspense, @@ -19,9 +20,9 @@ import BucketList from "../../components/Directory/BucketList"; import { MainDirectoryPageSkeleton } from "../../components/Directory/DirectoryPageSkeleton"; import ContactProfile from "../../components/Directory/ContactProfile"; import GlobalModel from "../../components/common/GlobalModel"; -import { exportToCSV } from "../../utils/exportUtils"; import ConfirmModal from "../../components/common/ConfirmModal"; import { useSelectedProject } from "../../slices/apiDataManager"; +import { exportToCSV, exportToExcel, exportToPDF, exportToPDF1, printTable } from "../../utils/tableExportUtils"; const NotesPage = lazy(() => import("./NotesPage")); const ContactsPage = lazy(() => import("./ContactsPage")); @@ -64,13 +65,46 @@ export default function DirectoryPage({ IsPage = true, projectId = null }) { const [ContactData, setContactData] = useState([]); const handleExport = (type) => { - if (activeTab === "notes" && type === "csv") { - exportToCSV(notesData, "notes.csv"); - } - if (activeTab === "contacts" && type === "csv") { - exportToCSV(ContactData, "contact.csv"); - } - }; + let exportData = activeTab === "notes" ? notesData : ContactData; + if (!exportData?.length) return; + + switch (type) { + case "csv": + exportToCSV(exportData, activeTab === "notes" ? "Notes" : "Contacts"); + break; + case "excel": + exportToExcel(exportData, activeTab === "notes" ? "Notes" : "Contacts"); + break; + case "pdf": + if (activeTab === "notes") { + exportToPDF1(exportData, "Notes"); + } else { + // Columns for Contacts PDF + const columns = ["Email", "Phone", "Organization", "Category", "Tags"]; + + // Sanitize and trim long text to avoid PDF overflow + const sanitizedData = exportData.map(item => ({ + Email: (item.Email || "").slice(0, 40), + Phone: (item.Phone || "").slice(0, 20), + Organization: (item.Organization || "").slice(0, 30), + Category: (item.Category || "").slice(0, 20), + Tags: (item.Tags || "").slice(0, 40), + })); + + // Export with proper spacing + exportToPDF(sanitizedData, "Contacts", columns, { + columnWidths: [200, 120, 180, 120, 200], // Adjust widths per column + fontSizeHeader: 12, + fontSizeRow: 10, + rowHeight: 25, + }); + } + break; + default: + console.warn("Unsupported export type"); + } +}; + const { data, isLoading, isError, error } = useBucketList(); @@ -213,19 +247,6 @@ export default function DirectoryPage({ IsPage = true, projectId = null }) {
    -
  • - -
  • - - {/* Divider */} - {activeTab === "contacts" &&

  • } - {activeTab === "contacts" && (
  • @@ -241,17 +262,31 @@ export default function DirectoryPage({ IsPage = true, projectId = null }) { {showActive ? "Active Contacts" : "Inactive Contacts"}
  • )} +
  • + +
  • +
  • + +
  • +
  • + +
  • + + {/* Divider */} + {activeTab === "contacts" &&

  • }
-
-
-
-
}> {activeTab === "notes" && ( diff --git a/src/pages/Directory/NotesPage.jsx b/src/pages/Directory/NotesPage.jsx index 58790562..3f0d4348 100644 --- a/src/pages/Directory/NotesPage.jsx +++ b/src/pages/Directory/NotesPage.jsx @@ -42,7 +42,7 @@ const NotesPage = ({ projectId, searchText, onExport }) => { }; }, []); - // 🔹 Format data for export + // Format data for export const formatExportData = (notes) => { return notes.map((n) => ({ ContactName: n.contactName || "", @@ -59,7 +59,7 @@ const NotesPage = ({ projectId, searchText, onExport }) => { })); }; - // 🔹 Pass formatted notes to parent for export + // Pass formatted notes to parent for export useEffect(() => { if (data?.data && onExport) { onExport(formatExportData(data.data)); diff --git a/src/utils/tableExportUtils.jsx b/src/utils/tableExportUtils.jsx index 57af704f..768ff5ac 100644 --- a/src/utils/tableExportUtils.jsx +++ b/src/utils/tableExportUtils.jsx @@ -40,39 +40,52 @@ export const exportToExcel = (data, fileName = "data") => { * @param {Array} data - Array of objects to export * @param {string} fileName - File name for the PDF (optional) */ -export const exportToPDF = async (data, fileName = "data", columns = null) => { +const sanitizeText = (text) => { + if (!text) return ""; + // Replace all non-ASCII characters with "?" or remove them + return text.replace(/[^\x00-\x7F]/g, "?"); +}; + +export const exportToPDF = async (data, fileName = "data", columns = null, options = {}) => { if (!data || data.length === 0) return; const pdfDoc = await PDFDocument.create(); const font = await pdfDoc.embedFont(StandardFonts.Helvetica); - // Landscape dimensions - const pageWidth = 1000; // wider for more space + // Default options + const { + columnWidths = [], // array of widths per column + fontSizeHeader = 12, + fontSizeRow = 10, + rowHeight = 25, + } = options; + + const pageWidth = 1000; const pageHeight = 600; let page = pdfDoc.addPage([pageWidth, pageHeight]); const margin = 30; let y = pageHeight - margin; const headers = columns || Object.keys(data[0]); - const rowHeight = 25; // slightly taller rows for readability - const columnSpacing = 150; // increase space between columns // Draw headers headers.forEach((header, i) => { - page.drawText(header, { x: margin + i * columnSpacing, y, font, size: 12 }); + const x = margin + (columnWidths[i] ? columnWidths.slice(0, i).reduce((a, b) => a + b, 0) : i * 150); + page.drawText(header, { x, y, font, size: fontSizeHeader }); }); y -= rowHeight; // Draw rows data.forEach(row => { headers.forEach((header, i) => { - const text = row[header] ? row[header].toString() : ''; - page.drawText(text, { x: margin + i * columnSpacing, y, font, size: 10 }); + const x = margin + (columnWidths[i] ? columnWidths.slice(0, i).reduce((a, b) => a + b, 0) : i * 150); + const text = row[header] || ''; + page.drawText(text, { x, y, font, size: fontSizeRow }); }); y -= rowHeight; if (y < margin) { - page = pdfDoc.addPage([pageWidth, pageHeight]); // landscape for new page + page = pdfDoc.addPage([pageWidth, pageHeight]); y = pageHeight - margin; } }); @@ -87,6 +100,95 @@ export const exportToPDF = async (data, fileName = "data", columns = null) => { + + +/** + * Export JSON data to PDF in a card-style format + * @param {Array} data - Array of objects to export + * @param {string} fileName - File name for the PDF (optional) + */ +export const exportToPDF1 = async (data, fileName = "data") => { + if (!data || data.length === 0) return; + + const pdfDoc = await PDFDocument.create(); + const font = await pdfDoc.embedFont(StandardFonts.Helvetica); + const boldFont = await pdfDoc.embedFont(StandardFonts.HelveticaBold); + + const pageWidth = 600; + const pageHeight = 800; + const margin = 30; + const cardSpacing = 20; + const cardPadding = 10; + let page = pdfDoc.addPage([pageWidth, pageHeight]); + let y = pageHeight - margin; + + for (const item of data) { + const title = item.ContactName || ""; + const subtitle = `by ${item.CreatedBy || ""} on ${item.CreatedAt || ""}`; + const body = item.Note || ""; + + const cardHeight = 80 + (body.length / 60) * 14; // approximate height for body text + + if (y - cardHeight < margin) { + page = pdfDoc.addPage([pageWidth, pageHeight]); + y = pageHeight - margin; + } + + // Draw card border + page.drawRectangle({ + x: margin, + y: y - cardHeight, + width: pageWidth - 2 * margin, + height: cardHeight, + borderColor: rgb(0.7, 0.7, 0.7), + borderWidth: 1, + color: rgb(1, 1, 1), + }); + + // Draw title + page.drawText(title, { + x: margin + cardPadding, + y: y - 20, + font: boldFont, + size: 12, + color: rgb(0.1, 0.1, 0.1), + }); + + // Draw subtitle + page.drawText(subtitle, { + x: margin + cardPadding, + y: y - 35, + font, + size: 10, + color: rgb(0.4, 0.4, 0.4), + }); + + // Draw body text (wrap manually) + const lines = body.match(/(.|[\r\n]){1,80}/g) || []; + lines.forEach((line, i) => { + page.drawText(line, { + x: margin + cardPadding, + y: y - 50 - i * 12, + font, + size: 10, + color: rgb(0.2, 0.2, 0.2), + }); + }); + + y -= cardHeight + cardSpacing; + } + + const pdfBytes = await pdfDoc.save(); + const blob = new Blob([pdfBytes], { type: 'application/pdf' }); + const link = document.createElement('a'); + link.href = URL.createObjectURL(blob); + link.download = `${fileName}.pdf`; + link.click(); +}; + + + + /** * Print the HTML table by accepting the table element or a reference. * @param {HTMLElement} table - The table element (or ref) to print From 4ba7c72e784b531e6c4dc3951c924371718fb6c4 Mon Sep 17 00:00:00 2001 From: Kartik Sharma Date: Fri, 10 Oct 2025 14:48:34 +0530 Subject: [PATCH 6/9] Removing ExportUtils.js file. --- src/utils/exportUtils.js | 27 --------------------------- 1 file changed, 27 deletions(-) delete mode 100644 src/utils/exportUtils.js diff --git a/src/utils/exportUtils.js b/src/utils/exportUtils.js deleted file mode 100644 index 40f7d423..00000000 --- a/src/utils/exportUtils.js +++ /dev/null @@ -1,27 +0,0 @@ -// utils/exportUtils.js -export const exportToCSV = (data, filename = "export.csv") => { - if (!data || data.length === 0) return; - - const headers = Object.keys(data[0]); - const csvRows = []; - - // Add headers - csvRows.push(headers.join(",")); - - // Add values - data.forEach(row => { - const values = headers.map(header => `"${row[header] ?? ""}"`); - csvRows.push(values.join(",")); - }); - - // Create CSV Blob - const csvContent = csvRows.join("\n"); - const blob = new Blob([csvContent], { type: "text/csv;charset=utf-8;" }); - - // Create download link - const link = document.createElement("a"); - const url = URL.createObjectURL(blob); - link.setAttribute("href", url); - link.setAttribute("download", filename); - link.click(); -}; From c921dbff09733c466d1c870bb273278ebff0268c Mon Sep 17 00:00:00 2001 From: "pramod.mahajan" Date: Fri, 10 Oct 2025 17:49:14 +0530 Subject: [PATCH 7/9] fixed u --- src/components/Activities/AttendcesLogs.jsx | 10 +++++----- src/components/Dashboard/ExpenseByProject.jsx | 4 ++-- src/pages/Activities/AttendancePage.jsx | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/components/Activities/AttendcesLogs.jsx b/src/components/Activities/AttendcesLogs.jsx index 465dc341..439529ac 100644 --- a/src/components/Activities/AttendcesLogs.jsx +++ b/src/components/Activities/AttendcesLogs.jsx @@ -250,12 +250,12 @@ const AttendanceLog = ({ handleModalData, searchTerm, organizationId }) => { className="dataTables_length text-start py-2 d-flex justify-content-between" id="DataTables_Table_0_length" > -
+
-
+
{
-
+
{isLoading ? ( -
+

Loading...

) : filteredSearchData?.length > 0 ? ( @@ -370,7 +370,7 @@ const AttendanceLog = ({ handleModalData, searchTerm, organizationId }) => { ) : ( -
No data available for the selected date range. Please Select another date.
+
No data for this date range. Please choose another.
)}
{paginatedAttendances?.length == 0 && filteredSearchData?.length > 0 && ( diff --git a/src/components/Dashboard/ExpenseByProject.jsx b/src/components/Dashboard/ExpenseByProject.jsx index c0189306..819824ca 100644 --- a/src/components/Dashboard/ExpenseByProject.jsx +++ b/src/components/Dashboard/ExpenseByProject.jsx @@ -113,7 +113,7 @@ const ExpenseByProject = () => {
{/* Range Buttons + Expense Dropdown */} -
+
{["1M", "3M", "6M", "12M", "All"].map((item) => (