diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 00000000..2bd5a0a9 --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +22 diff --git a/apps/web/package.json b/apps/web/package.json index a10d38a7..7b7b1ab4 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -2,10 +2,13 @@ "name": "@mydevtools/web", "version": "0.1.0", "private": true, + "engines": { + "node": ">=20.19.0" + }, "scripts": { "dev": "next dev", "build": "next build", - "analyze": "ANALYZE=true next build", + "analyze": "ANALYZE=true next build --webpack", "start": "next start", "lint": "eslint .", "test": "jest", diff --git a/apps/web/src/app/app/to-do/utils/exportUtils.ts b/apps/web/src/app/app/to-do/utils/exportUtils.ts deleted file mode 100644 index 447daf3d..00000000 --- a/apps/web/src/app/app/to-do/utils/exportUtils.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { Task } from "@/app/app/to-do/types/Task"; -import * as XLSX from "xlsx"; -import { saveAs } from "file-saver"; -import { format } from "date-fns"; - -// Helper to flatten task data for export -const formatTaskForExport = (task: Task) => { - return { - ID: task.id, - Text: task.text, - Description: task.description || "", - Status: task.status, - Priority: task.priority || "", - "Due Date": task.dueDate ? (typeof task.dueDate === 'string' ? task.dueDate : format(new Date(task.dueDate as any), "yyyy-MM-dd HH:mm")) : "", - Tags: task.tags?.map((t) => t.name).join(", ") || "", - "Subtasks Total": task.subTasks?.length || 0, - "Subtasks Completed": task.subTasks?.filter((st) => st.completed).length || 0, - "Created At": task.createdAt, - "Completed At": task.completedAt || "", - "Time Estimate (min)": task.timeEstimate || 0, - "Time Logged (min)": task.timeLogged || 0, - "Project ID": task.projectId || "", - }; -}; - -export const exportToExcel = (tasks: Task[], filename: string) => { - const data = tasks.map(formatTaskForExport); - const worksheet = XLSX.utils.json_to_sheet(data); - const workbook = XLSX.utils.book_new(); - XLSX.utils.book_append_sheet(workbook, worksheet, "Tasks"); - const excelBuffer = XLSX.write(workbook, { bookType: "xlsx", type: "array" }); - const blob = new Blob([excelBuffer], { type: "application/octet-stream" }); - saveAs(blob, `${filename}.xlsx`); -}; - -export const exportToCSV = (tasks: Task[], filename: string) => { - const data = tasks.map(formatTaskForExport); - const worksheet = XLSX.utils.json_to_sheet(data); - const csvOutput = XLSX.utils.sheet_to_csv(worksheet); - const blob = new Blob([csvOutput], { type: "text/csv;charset=utf-8" }); - saveAs(blob, `${filename}.csv`); -}; diff --git a/apps/web/src/components/json-formatter/json-formatter-layout.tsx b/apps/web/src/components/json-formatter/json-formatter-layout.tsx index 01843720..bea53f22 100644 --- a/apps/web/src/components/json-formatter/json-formatter-layout.tsx +++ b/apps/web/src/components/json-formatter/json-formatter-layout.tsx @@ -13,7 +13,7 @@ import { IconRefresh, } from '@tabler/icons-react' import { toast } from 'sonner' -import { Mode, toTextContent, type Content, type OnChangeStatus } from 'vanilla-jsoneditor' +import type { Mode, Content, OnChangeStatus } from 'vanilla-jsoneditor' import { fetchAllPages } from '@/lib/fetch-all-pages' import { ToolPageHeader } from '@/components/tools/tool-page-header' import { ToolMobileTabs } from '@/components/tools/tool-mobile-tabs' @@ -38,6 +38,14 @@ import { } from '@/components/ui/responsive-modal' import { ScrollArea } from '@/components/ui/scroll-area' +// Local copy of vanilla-jsoneditor's toTextContent, kept out of the static import so the +// 485KB editor barrel isn't dragged into this route's initial chunk (editor loads lazily). +function contentToText(content: Content): string { + if ('text' in content && content.text !== undefined) return content.text + // vanilla-jsoneditor's toTextContent defaults to compact (no indentation) — match it. + return JSON.stringify((content as { json: unknown }).json) +} + const initialJson = { array: [1, 2, 3], boolean: true, @@ -179,8 +187,8 @@ export function JsonFormatterLayout() { const handleCopy = (pane: PaneKey) => { const paneState = pane === 'left' ? leftPane : rightPane - const textContent = toTextContent(paneState.content) - void copyToClipboard(textContent.text, { + const textContentText = contentToText(paneState.content) + void copyToClipboard(textContentText, { successMessage: pane === 'left' ? t('toastCopiedText') : t('toastCopiedTree'), errorMessage: t('toastCopyFailed'), }) @@ -195,11 +203,11 @@ export function JsonFormatterLayout() { const paneState = pane === 'left' ? leftPane : rightPane try { updatePane(pane, (prev) => ({ ...prev, isSaving: true })) - const textContent = toTextContent(paneState.content) + const textContentText = contentToText(paneState.content) const body = { title: paneState.documentName, pane, - content: textContent.text, + content: textContentText, } if (paneState.documentId) { @@ -306,7 +314,7 @@ export function JsonFormatterLayout() { {t('copy')} - +