From 6b1cb00587d4426daea163a57e1c0cea7111fd04 Mon Sep 17 00:00:00 2001 From: prathwik <64214685+prathwik0@users.noreply.github.com> Date: Sat, 1 Mar 2025 13:51:16 +0530 Subject: [PATCH 1/6] chore: update packages --- package.json | 11 +- pnpm-lock.yaml | 318 ++++++++++++++----------------------------------- 2 files changed, 99 insertions(+), 230 deletions(-) diff --git a/package.json b/package.json index 2637d17cb..f13bfb0f4 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "scripts": { "clean": "rimraf --glob **/node_modules **/dist **/.next pnpm-lock.yaml **/.tsbuildinfo", "build": "next build", - "dev": "next dev", + "dev": "next dev --turbopack", "start": "next start", "lint": "biome check .", "lint:fix": "biome check . --write", @@ -19,7 +19,9 @@ "db:migrate": "dotenv tsx src/db/migrate.ts", "db:drop-migration": "drizzle-kit drop", "db:seed": "dotenv tsx src/db/seed.ts", - "db:studio": "dotenv drizzle-kit studio" + "db:studio": "dotenv drizzle-kit studio", + "db:import-csv": "dotenv tsx src/scripts/import-csv.ts", + "db:import-csv:clean": "dotenv tsx src/scripts/import-csv.ts --delete-existing" }, "dependencies": { "@dnd-kit/core": "^6.3.1", @@ -44,6 +46,7 @@ "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "cmdk": "^1.0.4", + "csv-parse": "^5.6.0", "date-fns": "^4.1.0", "drizzle-orm": "^0.40.0", "export-to-csv": "^1.4.0", @@ -52,6 +55,7 @@ "nanoid": "^5.0.9", "next": "^15.1.6", "next-themes": "^0.4.4", + "node-fetch": "^3.3.2", "nuqs": "^2.3.1", "postgres": "^3.4.5", "react": "^19.0.0", @@ -84,6 +88,5 @@ }, "ct3aMetadata": { "initVersion": "7.23.1" - }, - "packageManager": "pnpm@9.15.4" + } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c114298ba..1eaa4f85c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -74,12 +74,15 @@ importers: cmdk: specifier: ^1.0.4 version: 1.0.4(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + csv-parse: + specifier: ^5.6.0 + version: 5.6.0 date-fns: specifier: ^4.1.0 version: 4.1.0 drizzle-orm: specifier: ^0.40.0 - version: 0.40.0(@libsql/client-wasm@0.14.0)(gel@2.0.0)(pg@8.13.3)(postgres@3.4.5) + version: 0.40.0(gel@2.0.0)(pg@8.13.3)(postgres@3.4.5) export-to-csv: specifier: ^1.4.0 version: 1.4.0 @@ -98,9 +101,12 @@ importers: next-themes: specifier: ^0.4.4 version: 0.4.4(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + node-fetch: + specifier: ^3.3.2 + version: 3.3.2 nuqs: specifier: ^2.3.1 - version: 2.4.0(@remix-run/react@2.13.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.8.2))(next@15.2.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react-router-dom@6.27.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react-router@6.27.0(react@19.0.0))(react@19.0.0) + version: 2.4.0(next@15.2.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react@19.0.0) postgres: specifier: ^3.4.5 version: 3.4.5 @@ -146,7 +152,7 @@ importers: version: 0.6.1 '@types/node': specifier: ^22.12.0 - version: 22.13.7 + version: 22.13.8 '@types/react': specifier: ^19.0.8 version: 19.0.10 @@ -853,14 +859,6 @@ packages: '@jridgewell/trace-mapping@0.3.25': resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} - '@libsql/client-wasm@0.14.0': - resolution: {integrity: sha512-gB/jtz0xuwrqAHApBv9e9JSew2030Fhj2edyZ83InZ4yPj/Q2LTUlEhaspEYT0T0xsAGqPy38uGrmq/OGS+DdQ==} - bundledDependencies: - - '@libsql/libsql-wasm-experimental' - - '@libsql/core@0.14.0': - resolution: {integrity: sha512-nhbuXf7GP3PSZgdCY2Ecj8vz187ptHlZQ0VRc751oB2C1W8jQUXKKklvt7t1LJiUTQBVJuadF628eUk+3cRi4Q==} - '@next/env@15.2.0': resolution: {integrity: sha512-eMgJu1RBXxxqqnuRJQh5RozhskoNUDHBFybvi+Z+yK9qzKeG7dadhv/Vp1YooSZmCnegf7JxWuapV77necLZNA==} @@ -1151,19 +1149,6 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-primitive@2.0.1': - resolution: {integrity: sha512-sHCWTtxwNn3L3fH8qAfnF3WbUZycW93SM1j3NFDzXBiz8D6F5UTTy8G1+WFEaiCdvCVRJWj6N2R4Xq6HdiHmDg==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - '@radix-ui/react-primitive@2.0.2': resolution: {integrity: sha512-Ec/0d38EIuvDF+GZjcMU/Ze6MxntVJYO/fRlCPhCaVUyPY9WTalHJw54tp9sXeJo3tlShWpy41vQRgLRGOuz+w==} peerDependencies: @@ -1216,15 +1201,6 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-slot@1.1.1': - resolution: {integrity: sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@radix-ui/react-slot@1.1.2': resolution: {integrity: sha512-YAKxaiGsSQJ38VzKH86/BPRC4rh+b1Jpa+JneA5LRE7skmLPNAyeG8kPJj/oo4STLvlrs8vkf/iYyc3A5stYCQ==} peerDependencies: @@ -1352,30 +1328,6 @@ packages: '@radix-ui/rect@1.1.0': resolution: {integrity: sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg==} - '@remix-run/react@2.13.1': - resolution: {integrity: sha512-kZevCoKMz0ZDOOzTnG95yfM7M9ju38FkWNY1wtxCy+NnUJYrmTerGQtiBsJgMzYD6i29+w4EwoQsdqys7DmMSg==} - engines: {node: '>=18.0.0'} - peerDependencies: - react: ^18.0.0 - react-dom: ^18.0.0 - typescript: ^5.1.0 - peerDependenciesMeta: - typescript: - optional: true - - '@remix-run/router@1.20.0': - resolution: {integrity: sha512-mUnk8rPJBI9loFDZ+YzPGdeniYK+FTmRD1TMCz7ev2SNIozyKKpnGgsxO34u6Z4z/t0ITuu7voi/AshfsGsgFg==} - engines: {node: '>=14.0.0'} - - '@remix-run/server-runtime@2.13.1': - resolution: {integrity: sha512-2DfBPRcHKVzE4bCNsNkKB50BhCCKF73x+jiS836OyxSIAL+x0tguV2AEjmGXefEXc5AGGzoxkus0AUUEYa29Vg==} - engines: {node: '>=18.0.0'} - peerDependencies: - typescript: ^5.1.0 - peerDependenciesMeta: - typescript: - optional: true - '@standard-schema/utils@0.3.0': resolution: {integrity: sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==} @@ -1427,11 +1379,8 @@ packages: '@total-typescript/ts-reset@0.6.1': resolution: {integrity: sha512-cka47fVSo6lfQDIATYqb/vO1nvFfbPw7uWLayIXIhGETj0wcOOlrlkobOMDNQOFr9QOafegUPq13V2+6vtD7yg==} - '@types/cookie@0.6.0': - resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==} - - '@types/node@22.13.7': - resolution: {integrity: sha512-oU2q+BsQldB9lYxHNp/5aZO+/Bs0Usa74Abo9mAKulz4ahQyXRHK6UVKYIN8KSC8HXwhWSi7b49JnX+txuac0w==} + '@types/node@22.13.8': + resolution: {integrity: sha512-G3EfaZS+iOGYWLLRCEAXdWK9my08oHNZ+FHluRiggIYJPOXzhOiDgpVCUHaUvyIC5/fj7C/p637jdzC666AOKQ==} '@types/react-dom@19.0.4': resolution: {integrity: sha512-4fSQ8vWFkg+TGhePfUzVmat3eC14TXYSsiiDSLI0dVLsrm9gZFABjPy/Qu6TKgl1tq1Bu1yDsuQgY3A3DOjCcg==} @@ -1441,9 +1390,6 @@ packages: '@types/react@19.0.10': resolution: {integrity: sha512-JuRQ9KXLEjaUNjTWpzuR231Z2WpIwczOkBEIvbHNCzQefFIT0L8IqE6NV6ULLyC1SI/i234JnDoMkfg+RjQj2g==} - '@web3-storage/multipart-parser@1.0.0': - resolution: {integrity: sha512-BEO6al7BYqcnfX15W2cnGR+Q566ACXAT9UQykORCWW80lmkpWsnEob6zJS1ZVBKsSJC8+7vJkHwlp+lXG1UCdw==} - ansi-regex@5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} @@ -1511,9 +1457,6 @@ packages: resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==} engines: {node: '>= 6'} - caniuse-lite@1.0.30001696: - resolution: {integrity: sha512-pDCPkvzfa39ehJtJ+OwGT/2yvT2SbjfHhiIW2LWOAcMQ7BzwxT/XuyUp4OTOd0XFWA6BKw0JalnBHgSi5DGJBQ==} - caniuse-lite@1.0.30001701: resolution: {integrity: sha512-faRs/AW3jA9nTwmJBSO1PQ6L/EOgsB5HMQQq4iCu5zhPgVVgO/pZRHlmatwijZKetFw8/Pr4q6dEN8sJuq8qTw==} @@ -1555,10 +1498,6 @@ packages: resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} engines: {node: '>= 6'} - cookie@0.6.0: - resolution: {integrity: sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==} - engines: {node: '>= 0.6'} - cross-spawn@7.0.6: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} @@ -1571,6 +1510,13 @@ packages: csstype@3.1.3: resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} + csv-parse@5.6.0: + resolution: {integrity: sha512-l3nz3euub2QMg5ouu5U09Ew9Wf6/wQ8I++ch1loQ0ljmzhmfZYrH9fflS22i/PQEvsPvxCwxgz5q7UB8K1JO4Q==} + + data-uri-to-buffer@4.0.1: + resolution: {integrity: sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==} + engines: {node: '>= 12'} + date-fns@4.1.0: resolution: {integrity: sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==} @@ -1704,8 +1650,8 @@ packages: eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} - electron-to-chromium@1.5.90: - resolution: {integrity: sha512-C3PN4aydfW91Natdyd449Kw+BzhLmof6tzy5W1pFC5SpQxVXT+oyiyOG9AgYYSN9OdA/ik3YkCrpwqI8ug5Tug==} + electron-to-chromium@1.5.109: + resolution: {integrity: sha512-AidaH9JETVRr9DIPGfp1kAarm/W6hRJTPuCnkF+2MqhF4KaAgRIcBc8nvjk+YMXZhwfISof/7WG29eS4iGxQLQ==} emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} @@ -1749,17 +1695,25 @@ packages: resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} engines: {node: '>=8.6.0'} - fastq@1.19.0: - resolution: {integrity: sha512-7SFSRCNjBQIZH/xZR3iy5iQYR8aGBE0h3VG6/cwlbrpdciNYBMotQav8c1XI3HjHH+NikUpP53nPdlZSdWmFzA==} + fastq@1.19.1: + resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==} + + fetch-blob@3.2.0: + resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==} + engines: {node: ^12.20 || >= 14.13} fill-range@7.1.1: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} - foreground-child@3.3.0: - resolution: {integrity: sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==} + foreground-child@3.3.1: + resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} engines: {node: '>=14'} + formdata-polyfill@4.0.10: + resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==} + engines: {node: '>=12.20.0'} + fraction.js@4.3.7: resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==} @@ -1846,17 +1800,14 @@ packages: jackspeak@3.4.3: resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} - jackspeak@4.0.2: - resolution: {integrity: sha512-bZsjR/iRjl1Nk1UkjGpAzLNfQtzuijhn2g+pbZb98HQ1Gk8vM9hfbxeMBP+M2/UUdwj0RqGG3mlvk2MsAqwvEw==} + jackspeak@4.1.0: + resolution: {integrity: sha512-9DDdhb5j6cpeitCbvLO7n7J4IxnbM6hoF6O1g4HQ5TfhvvKN8ywDM7668ZhMHRqVmxqhps/F6syWK2KcPxYlkw==} engines: {node: 20 || >=22} jiti@1.21.7: resolution: {integrity: sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==} hasBin: true - js-base64@3.7.7: - resolution: {integrity: sha512-7rCnleh0z2CkXhH67J8K1Ytz0b2Y+yxTPL+/KOJoa20hfnVQ/3/T6W/KflYI4bRHRagNeXeU2bkNGI3v1oS/lw==} - lilconfig@3.1.3: resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} engines: {node: '>=14'} @@ -1945,6 +1896,14 @@ packages: sass: optional: true + node-domexception@1.0.0: + resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==} + engines: {node: '>=10.5.0'} + + node-fetch@3.3.2: + resolution: {integrity: sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + node-releases@2.0.19: resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==} @@ -2154,19 +2113,6 @@ packages: '@types/react': optional: true - react-router-dom@6.27.0: - resolution: {integrity: sha512-+bvtFWMC0DgAFrfKXKG9Fc+BcXWRUO1aJIihbB79xaeq0v5UzfvnM5houGUm1Y461WVRcgAQ+Clh5rdb1eCx4g==} - engines: {node: '>=14.0.0'} - peerDependencies: - react: '>=16.8' - react-dom: '>=16.8' - - react-router@6.27.0: - resolution: {integrity: sha512-YA+HGZXz4jaAkVoYBE98VQl+nVzI+cVI2Oj/06F5ZM+0u3TgedN9Y9kmMRo2mnkSK2nCpNQn0DVob4HCsY/WLw==} - engines: {node: '>=14.0.0'} - peerDependencies: - react: '>=16.8' - react-style-singleton@2.2.3: resolution: {integrity: sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==} engines: {node: '>=10'} @@ -2196,8 +2142,8 @@ packages: engines: {node: '>= 0.4'} hasBin: true - reusify@1.0.4: - resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} + reusify@1.1.0: + resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} rimraf@6.0.1: @@ -2219,9 +2165,6 @@ packages: server-only@0.0.1: resolution: {integrity: sha512-qepMx2JxAa5jjfzxG79yPPq+8BuFToHd1hm7kI+Z4zAq1ftQiP7HcxMhDDItrbtwVeLg/cY2JnKnrcFkmiswNA==} - set-cookie-parser@2.7.1: - resolution: {integrity: sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==} - sharp@0.33.5: resolution: {integrity: sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} @@ -2262,10 +2205,6 @@ packages: resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} engines: {node: '>=0.10.0'} - source-map@0.7.4: - resolution: {integrity: sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==} - engines: {node: '>= 8'} - split2@4.2.0: resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} engines: {node: '>= 10.x'} @@ -2347,9 +2286,6 @@ packages: engines: {node: '>=18.0.0'} hasBin: true - turbo-stream@2.4.0: - resolution: {integrity: sha512-FHncC10WpBd2eOmGwpmQsWLDoK4cqsA/UT/GqNoaKOQnT8uzhtCbg3EoUDMvqpOSAI0S26mr0rkjzbOO6S3v1g==} - typescript@5.8.2: resolution: {integrity: sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==} engines: {node: '>=14.17'} @@ -2358,8 +2294,8 @@ packages: undici-types@6.20.0: resolution: {integrity: sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==} - update-browserslist-db@1.1.2: - resolution: {integrity: sha512-PPypAm5qvlD7XMZC3BujecnaOxwhrtoFR+Dqkk5Aa/6DssiH0ibKoketaj9w8LP7Bont1rYeoV5plxD7RTEPRg==} + update-browserslist-db@1.1.3: + resolution: {integrity: sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==} hasBin: true peerDependencies: browserslist: '>= 4.21.0' @@ -2398,6 +2334,10 @@ packages: react: ^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc + web-streams-polyfill@3.3.3: + resolution: {integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==} + engines: {node: '>= 8'} + which@2.0.2: resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} engines: {node: '>= 8'} @@ -2851,17 +2791,6 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.0 - '@libsql/client-wasm@0.14.0': - dependencies: - '@libsql/core': 0.14.0 - js-base64: 3.7.7 - optional: true - - '@libsql/core@0.14.0': - dependencies: - js-base64: 3.7.7 - optional: true - '@next/env@15.2.0': {} '@next/swc-darwin-arm64@15.2.0': @@ -2898,7 +2827,7 @@ snapshots: '@nodelib/fs.walk@1.2.8': dependencies: '@nodelib/fs.scandir': 2.1.5 - fastq: 1.19.0 + fastq: 1.19.1 '@petamoriken/float16@3.9.1': {} @@ -3134,15 +3063,6 @@ snapshots: '@types/react': 19.0.10 '@types/react-dom': 19.0.4(@types/react@19.0.10) - '@radix-ui/react-primitive@2.0.1(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': - dependencies: - '@radix-ui/react-slot': 1.1.1(@types/react@19.0.10)(react@19.0.0) - react: 19.0.0 - react-dom: 19.0.0(react@19.0.0) - optionalDependencies: - '@types/react': 19.0.10 - '@types/react-dom': 19.0.4(@types/react@19.0.10) - '@radix-ui/react-primitive@2.0.2(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': dependencies: '@radix-ui/react-slot': 1.1.2(@types/react@19.0.10)(react@19.0.0) @@ -3207,13 +3127,6 @@ snapshots: '@types/react': 19.0.10 '@types/react-dom': 19.0.4(@types/react@19.0.10) - '@radix-ui/react-slot@1.1.1(@types/react@19.0.10)(react@19.0.0)': - dependencies: - '@radix-ui/react-compose-refs': 1.1.1(@types/react@19.0.10)(react@19.0.0) - react: 19.0.0 - optionalDependencies: - '@types/react': 19.0.10 - '@radix-ui/react-slot@1.1.2(@types/react@19.0.10)(react@19.0.0)': dependencies: '@radix-ui/react-compose-refs': 1.1.1(@types/react@19.0.10)(react@19.0.0) @@ -3324,35 +3237,6 @@ snapshots: '@radix-ui/rect@1.1.0': {} - '@remix-run/react@2.13.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.8.2)': - dependencies: - '@remix-run/router': 1.20.0 - '@remix-run/server-runtime': 2.13.1(typescript@5.8.2) - react: 19.0.0 - react-dom: 19.0.0(react@19.0.0) - react-router: 6.27.0(react@19.0.0) - react-router-dom: 6.27.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - turbo-stream: 2.4.0 - optionalDependencies: - typescript: 5.8.2 - optional: true - - '@remix-run/router@1.20.0': - optional: true - - '@remix-run/server-runtime@2.13.1(typescript@5.8.2)': - dependencies: - '@remix-run/router': 1.20.0 - '@types/cookie': 0.6.0 - '@web3-storage/multipart-parser': 1.0.0 - cookie: 0.6.0 - set-cookie-parser: 2.7.1 - source-map: 0.7.4 - turbo-stream: 2.4.0 - optionalDependencies: - typescript: 5.8.2 - optional: true - '@standard-schema/utils@0.3.0': {} '@swc/counter@0.1.3': {} @@ -3383,10 +3267,7 @@ snapshots: '@total-typescript/ts-reset@0.6.1': {} - '@types/cookie@0.6.0': - optional: true - - '@types/node@22.13.7': + '@types/node@22.13.8': dependencies: undici-types: 6.20.0 @@ -3398,9 +3279,6 @@ snapshots: dependencies: csstype: 3.1.3 - '@web3-storage/multipart-parser@1.0.0': - optional: true - ansi-regex@5.0.1: {} ansi-regex@6.1.0: {} @@ -3427,7 +3305,7 @@ snapshots: autoprefixer@10.4.20(postcss@8.5.3): dependencies: browserslist: 4.24.4 - caniuse-lite: 1.0.30001696 + caniuse-lite: 1.0.30001701 fraction.js: 4.3.7 normalize-range: 0.1.2 picocolors: 1.1.1 @@ -3448,10 +3326,10 @@ snapshots: browserslist@4.24.4: dependencies: - caniuse-lite: 1.0.30001696 - electron-to-chromium: 1.5.90 + caniuse-lite: 1.0.30001701 + electron-to-chromium: 1.5.109 node-releases: 2.0.19 - update-browserslist-db: 1.1.2(browserslist@4.24.4) + update-browserslist-db: 1.1.3(browserslist@4.24.4) buffer-from@1.1.2: {} @@ -3461,8 +3339,6 @@ snapshots: camelcase-css@2.0.1: {} - caniuse-lite@1.0.30001696: {} - caniuse-lite@1.0.30001701: {} chokidar@3.6.0: @@ -3489,7 +3365,7 @@ snapshots: dependencies: '@radix-ui/react-dialog': 1.1.6(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@radix-ui/react-id': 1.1.0(@types/react@19.0.10)(react@19.0.0) - '@radix-ui/react-primitive': 2.0.1(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-primitive': 2.0.2(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) react: 19.0.0 react-dom: 19.0.0(react@19.0.0) use-sync-external-store: 1.4.0(react@19.0.0) @@ -3517,9 +3393,6 @@ snapshots: commander@4.1.1: {} - cookie@0.6.0: - optional: true - cross-spawn@7.0.6: dependencies: path-key: 3.1.1 @@ -3530,6 +3403,10 @@ snapshots: csstype@3.1.3: {} + csv-parse@5.6.0: {} + + data-uri-to-buffer@4.0.1: {} + date-fns@4.1.0: {} debug@4.4.0: @@ -3566,16 +3443,15 @@ snapshots: transitivePeerDependencies: - supports-color - drizzle-orm@0.40.0(@libsql/client-wasm@0.14.0)(gel@2.0.0)(pg@8.13.3)(postgres@3.4.5): + drizzle-orm@0.40.0(gel@2.0.0)(pg@8.13.3)(postgres@3.4.5): optionalDependencies: - '@libsql/client-wasm': 0.14.0 gel: 2.0.0 pg: 8.13.3 postgres: 3.4.5 eastasianwidth@0.2.0: {} - electron-to-chromium@1.5.90: {} + electron-to-chromium@1.5.109: {} emoji-regex@8.0.0: {} @@ -3681,19 +3557,28 @@ snapshots: merge2: 1.4.1 micromatch: 4.0.8 - fastq@1.19.0: + fastq@1.19.1: + dependencies: + reusify: 1.1.0 + + fetch-blob@3.2.0: dependencies: - reusify: 1.0.4 + node-domexception: 1.0.0 + web-streams-polyfill: 3.3.3 fill-range@7.1.1: dependencies: to-regex-range: 5.0.1 - foreground-child@3.3.0: + foreground-child@3.3.1: dependencies: cross-spawn: 7.0.6 signal-exit: 4.1.0 + formdata-polyfill@4.0.10: + dependencies: + fetch-blob: 3.2.0 + fraction.js@4.3.7: {} fsevents@2.3.3: @@ -3732,7 +3617,7 @@ snapshots: glob@10.4.5: dependencies: - foreground-child: 3.3.0 + foreground-child: 3.3.1 jackspeak: 3.4.3 minimatch: 9.0.5 minipass: 7.1.2 @@ -3741,8 +3626,8 @@ snapshots: glob@11.0.1: dependencies: - foreground-child: 3.3.0 - jackspeak: 4.0.2 + foreground-child: 3.3.1 + jackspeak: 4.1.0 minimatch: 10.0.1 minipass: 7.1.2 package-json-from-dist: 1.0.1 @@ -3783,15 +3668,12 @@ snapshots: optionalDependencies: '@pkgjs/parseargs': 0.11.0 - jackspeak@4.0.2: + jackspeak@4.1.0: dependencies: '@isaacs/cliui': 8.0.2 jiti@1.21.7: {} - js-base64@3.7.7: - optional: true - lilconfig@3.1.3: {} lines-and-columns@1.2.4: {} @@ -3867,21 +3749,26 @@ snapshots: - '@babel/core' - babel-plugin-macros + node-domexception@1.0.0: {} + + node-fetch@3.3.2: + dependencies: + data-uri-to-buffer: 4.0.1 + fetch-blob: 3.2.0 + formdata-polyfill: 4.0.10 + node-releases@2.0.19: {} normalize-path@3.0.0: {} normalize-range@0.1.2: {} - nuqs@2.4.0(@remix-run/react@2.13.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.8.2))(next@15.2.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react-router-dom@6.27.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react-router@6.27.0(react@19.0.0))(react@19.0.0): + nuqs@2.4.0(next@15.2.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react@19.0.0): dependencies: mitt: 3.0.1 react: 19.0.0 optionalDependencies: - '@remix-run/react': 2.13.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.8.2) next: 15.2.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - react-router: 6.27.0(react@19.0.0) - react-router-dom: 6.27.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0) object-assign@4.1.1: {} @@ -4036,20 +3923,6 @@ snapshots: optionalDependencies: '@types/react': 19.0.10 - react-router-dom@6.27.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0): - dependencies: - '@remix-run/router': 1.20.0 - react: 19.0.0 - react-dom: 19.0.0(react@19.0.0) - react-router: 6.27.0(react@19.0.0) - optional: true - - react-router@6.27.0(react@19.0.0): - dependencies: - '@remix-run/router': 1.20.0 - react: 19.0.0 - optional: true - react-style-singleton@2.2.3(@types/react@19.0.10)(react@19.0.0): dependencies: get-nonce: 1.0.1 @@ -4076,7 +3949,7 @@ snapshots: path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 - reusify@1.0.4: {} + reusify@1.1.0: {} rimraf@6.0.1: dependencies: @@ -4093,9 +3966,6 @@ snapshots: server-only@0.0.1: {} - set-cookie-parser@2.7.1: - optional: true - sharp@0.33.5: dependencies: color: 4.2.3 @@ -4152,9 +4022,6 @@ snapshots: source-map@0.6.1: {} - source-map@0.7.4: - optional: true - split2@4.2.0: {} streamsearch@1.1.0: {} @@ -4252,14 +4119,11 @@ snapshots: optionalDependencies: fsevents: 2.3.3 - turbo-stream@2.4.0: - optional: true - typescript@5.8.2: {} undici-types@6.20.0: {} - update-browserslist-db@1.1.2(browserslist@4.24.4): + update-browserslist-db@1.1.3(browserslist@4.24.4): dependencies: browserslist: 4.24.4 escalade: 3.2.0 @@ -4295,6 +4159,8 @@ snapshots: - '@types/react' - '@types/react-dom' + web-streams-polyfill@3.3.3: {} + which@2.0.2: dependencies: isexe: 2.0.0 From 30654a6745dceee3ce4c25ba1d9033c7f1d2c90a Mon Sep 17 00:00:00 2001 From: prathwik <64214685+prathwik0@users.noreply.github.com> Date: Sat, 1 Mar 2025 13:56:53 +0530 Subject: [PATCH 2/6] feat: add scripts to import data into db from tasks.csv file --- src/app/api/revalidate/route.ts | 27 ++++++++++ src/lib/unstable-cache.ts | 2 +- src/scripts/import-csv.ts | 95 +++++++++++++++++++++++++++++++++ src/scripts/revalidate-cache.ts | 38 +++++++++++++ 4 files changed, 161 insertions(+), 1 deletion(-) create mode 100644 src/app/api/revalidate/route.ts create mode 100644 src/scripts/import-csv.ts create mode 100644 src/scripts/revalidate-cache.ts diff --git a/src/app/api/revalidate/route.ts b/src/app/api/revalidate/route.ts new file mode 100644 index 000000000..bfbb58209 --- /dev/null +++ b/src/app/api/revalidate/route.ts @@ -0,0 +1,27 @@ +import { revalidateTag } from "next/cache"; +import { NextRequest, NextResponse } from "next/server"; + +export async function POST(request: NextRequest) { + const tag = request.nextUrl.searchParams.get("tag"); + + if (!tag) { + return NextResponse.json( + { message: "Missing tag parameter" }, + { status: 400 } + ); + } + + try { + revalidateTag(tag); + return NextResponse.json({ + revalidated: true, + now: Date.now(), + tag, + }); + } catch (error) { + return NextResponse.json( + { message: "Error revalidating", error }, + { status: 500 } + ); + } +} diff --git a/src/lib/unstable-cache.ts b/src/lib/unstable-cache.ts index 499864258..0371af227 100644 --- a/src/lib/unstable-cache.ts +++ b/src/lib/unstable-cache.ts @@ -15,5 +15,5 @@ export const unstable_cache = ( */ revalidate?: number | false; tags?: string[]; - }, + } ) => cache(next_unstable_cache(cb, keyParts, options)); diff --git a/src/scripts/import-csv.ts b/src/scripts/import-csv.ts new file mode 100644 index 000000000..0a524c27a --- /dev/null +++ b/src/scripts/import-csv.ts @@ -0,0 +1,95 @@ +import fs from "fs"; +import path from "path"; +import { parse } from "csv-parse/sync"; +import { db } from "@/db/index"; +import { tasks } from "@/db/schema"; +import fetch from "node-fetch"; + +async function importTasks(options: { deleteExisting?: boolean } = {}) { + try { + console.log("ā³ Importing tasks from CSV..."); + const start = Date.now(); + + // Read the CSV file + const csvFilePath = path.resolve(process.cwd(), "tasks.csv"); + const csvContent = fs.readFileSync(csvFilePath, "utf-8"); + + // Parse the CSV content + const records = parse(csvContent, { + columns: true, + skip_empty_lines: true, + }); + + console.log(`šŸ“„ Found ${records.length} tasks in CSV file`); + + // Format the data to match the schema + const formattedTasks = records.map((record: any) => ({ + code: record.code, + title: record.title, + status: record.status, + priority: record.priority, + label: "bug", // Default label since it's not in the CSV + archived: record.archived === "true", + createdAt: new Date(record.createdAt), + })); + + // Delete existing tasks if requested + if (options.deleteExisting) { + console.log("šŸ—‘ļø Deleting existing tasks..."); + await db.delete(tasks); + } + + // Insert the data into the database + const result = await db + .insert(tasks) + .values(formattedTasks) + .onConflictDoNothing(); + + const end = Date.now(); + console.log(`āœ… Import completed in ${end - start}ms`); + console.log(`āœ… Imported ${formattedTasks.length} tasks from CSV`); + + // Try to revalidate the cache + try { + console.log("šŸ”„ Revalidating cache..."); + const revalidateResponse = await fetch( + "http://localhost:3000/api/revalidate?tag=tasks", + { + method: "POST", + } + ); + + if (revalidateResponse.ok) { + console.log("āœ… Cache revalidated successfully"); + } else { + console.log("āš ļø Cache revalidation failed, but data was imported"); + console.log(" You may need to restart the dev server to see changes"); + } + } catch (error) { + console.log("āš ļø Cache revalidation failed (is the dev server running?)"); + console.log(" You may need to restart the dev server to see changes"); + } + + console.log("\nšŸ“‹ Next steps:"); + console.log( + "1. If the cache revalidation failed, restart the development server:" + ); + console.log(" pnpm dev"); + console.log("2. Refresh your browser to see the updated data"); + + process.exit(0); + } catch (err) { + console.error("āŒ Import failed"); + console.error(err); + process.exit(1); + } +} + +// Check if delete_existing flag is passed +const deleteExisting = process.argv.includes("--delete-existing"); + +importTasks({ deleteExisting }).catch((err) => { + console.error("āŒ Unexpected error during import"); + console.error(err); + process.exit(1); +}); diff --git a/src/scripts/revalidate-cache.ts b/src/scripts/revalidate-cache.ts new file mode 100644 index 000000000..d8baadf42 --- /dev/null +++ b/src/scripts/revalidate-cache.ts @@ -0,0 +1,38 @@ +import { env } from "@/env.js"; +import fetch from "node-fetch"; + +async function revalidateCache() { + try { + console.log("ā³ Revalidating cache..."); + const start = Date.now(); + // Get the base URL from the environment or use a default + const baseUrl = process.env.NEXT_PUBLIC_APP_URL || "http://localhost:3000"; + + // Revalidate the tasks tag + const response = await fetch(`${baseUrl}/api/revalidate?tag=tasks`, { + method: "POST", + }); + + if (!response.ok) { + throw new Error(`Failed to revalidate: ${response.statusText}`); + } + + const result = await response.json(); + + const end = Date.now(); + console.log(`āœ… Cache revalidation completed in ${end - start}ms`); + console.log("Revalidation result:", result); + + process.exit(0); + } catch (err) { + console.error("āŒ Cache revalidation failed"); + console.error(err); + process.exit(1); + } +} + +revalidateCache().catch((err) => { + console.error("āŒ Unexpected error during cache revalidation"); + console.error(err); + process.exit(1); +}); From caac984404a629fec114c5d3ad752f3d7c9739c3 Mon Sep 17 00:00:00 2001 From: prathwik <64214685+prathwik0@users.noreply.github.com> Date: Sat, 1 Mar 2025 15:42:14 +0530 Subject: [PATCH 3/6] feat: make the tables resizable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit the performance is still really bad tho 🄲 nvm react complier ftw!!!! --- src/app/_components/tasks-table-columns.tsx | 30 +- src/app/_components/tasks-table.tsx | 28 +- src/components/data-table/data-table.tsx | 239 +++++++++++----- src/components/debug-panel.tsx | 92 ++++++ src/config/data-table.ts | 10 +- src/hooks/use-data-table.ts | 70 ++++- src/hooks/use-resize.ts | 293 ++++++++++++++++++++ 7 files changed, 675 insertions(+), 87 deletions(-) create mode 100644 src/components/debug-panel.tsx create mode 100644 src/hooks/use-resize.ts diff --git a/src/app/_components/tasks-table-columns.tsx b/src/app/_components/tasks-table-columns.tsx index 05a427e80..0a6100a86 100644 --- a/src/app/_components/tasks-table-columns.tsx +++ b/src/app/_components/tasks-table-columns.tsx @@ -69,7 +69,7 @@ export function getColumns({ header: ({ column }) => ( ), - cell: ({ row }) =>
{row.getValue("code")}
, + cell: ({ row }) =>
{row.getValue("code")}
, enableSorting: false, enableHiding: false, }, @@ -80,13 +80,13 @@ export function getColumns({ ), cell: ({ row }) => { const label = tasks.label.enumValues.find( - (label) => label === row.original.label, + (label) => label === row.original.label ); return (
{label && {label}} - + {row.getValue("title")}
@@ -100,7 +100,7 @@ export function getColumns({ ), cell: ({ row }) => { const status = tasks.status.enumValues.find( - (status) => status === row.original.status, + (status) => status === row.original.status ); if (!status) return null; @@ -108,12 +108,12 @@ export function getColumns({ const Icon = getStatusIcon(status); return ( -
+
); }, @@ -128,7 +128,7 @@ export function getColumns({ ), cell: ({ row }) => { const priority = tasks.priority.enumValues.find( - (priority) => priority === row.original.priority, + (priority) => priority === row.original.priority ); if (!priority) return null; @@ -138,10 +138,10 @@ export function getColumns({ return (
); }, @@ -155,7 +155,9 @@ export function getColumns({ ), cell: ({ row }) => ( - {row.original.archived ? "Yes" : "No"} + + {row.original.archived ? "Yes" : "No"} + ), }, { @@ -163,7 +165,9 @@ export function getColumns({ header: ({ column }) => ( ), - cell: ({ cell }) => formatDate(cell.getValue() as Date), + cell: ({ cell }) => ( +
{formatDate(cell.getValue() as Date)}
+ ), }, { id: "actions", @@ -203,7 +207,7 @@ export function getColumns({ loading: "Updating...", success: "Label updated", error: (err) => getErrorMessage(err), - }, + } ); }); }} diff --git a/src/app/_components/tasks-table.tsx b/src/app/_components/tasks-table.tsx index 0eb0289ea..6d4fd7c0a 100644 --- a/src/app/_components/tasks-table.tsx +++ b/src/app/_components/tasks-table.tsx @@ -26,6 +26,7 @@ import { getColumns } from "./tasks-table-columns"; import { TasksTableFloatingBar } from "./tasks-table-floating-bar"; import { TasksTableToolbarActions } from "./tasks-table-toolbar-actions"; import { UpdateTaskSheet } from "./update-task-sheet"; +import DebugPanel from "@/components/debug-panel"; interface TasksTableProps { promises: Promise< @@ -134,16 +135,36 @@ export function TasksTable({ promises }: TasksTableProps) { const enableAdvancedTable = featureFlags.includes("advancedTable"); const enableFloatingBar = featureFlags.includes("floatingBar"); + // Enable resizing if the "resizableColumns" feature flag is set + const enableResizing = featureFlags.includes("resizableColumns"); - const { table } = useDataTable({ + const { table, tableContainerRef, containerWidth } = useDataTable({ data, columns, pageCount, filterFields, enableAdvancedFilter: enableAdvancedTable, + // Enable column resizing + enableResizing, + // Default column sizing constraints + defaultColumn: { + minSize: 60, + maxSize: 800, + }, initialState: { sorting: [{ id: "createdAt", desc: true }], columnPinning: { right: ["actions"] }, + // Define specific column sizes for better layout + columnSizing: { + select: 50, + code: 120, + title: 300, + status: 150, + priority: 150, + archived: 100, + createdAt: 180, + actions: 80, + }, }, getRowId: (originalRow) => originalRow.id, shallow: false, @@ -152,8 +173,13 @@ export function TasksTable({ promises }: TasksTableProps) { return ( <> + : null } diff --git a/src/components/data-table/data-table.tsx b/src/components/data-table/data-table.tsx index 9a69c985b..8a8b3064d 100644 --- a/src/components/data-table/data-table.tsx +++ b/src/components/data-table/data-table.tsx @@ -1,5 +1,5 @@ import { type Table as TanstackTable, flexRender } from "@tanstack/react-table"; -import type * as React from "react"; +import * as React from "react"; import { DataTablePagination } from "@/components/data-table/data-table-pagination"; import { @@ -27,81 +27,194 @@ interface DataTableProps extends React.HTMLAttributes { * @example floatingBar={} */ floatingBar?: React.ReactNode | null; + + /** + * Reference to the table container div element for column resizing. + * @type React.RefObject | null + */ + tableContainerRef?: React.RefObject | null; + + /** + * Function to get column size CSS variables for the table. + * @type (table: TanstackTable) => Record + */ + getColumnSizeVars?: (table: TanstackTable) => Record; + + /** + * Width of the container for the table. + * @type number + */ + containerWidth?: number; + + /** + * Whether to enable resizing for the table. + * @default false + * @type boolean + */ + enableResizing?: boolean; +} + +// Generic type for TableBodyContent component +interface TableBodyContentProps { + table: TanstackTable; } +function TableBodyContent({ table }: TableBodyContentProps) { + return ( + <> + {table.getRowModel().rows?.length ? ( + table.getRowModel().rows.map((row) => ( + + {row.getVisibleCells().map((cell) => ( + + {flexRender(cell.column.columnDef.cell, cell.getContext())} + + ))} + + )) + ) : ( + + + No results. + + + )} + + ); +} + +// Memoized table body for better performance during resizing +const MemoizedTableBody = React.memo( + TableBodyContent, + (prev, next) => prev.table.options.data === next.table.options.data +) as typeof TableBodyContent; + export function DataTable({ table, floatingBar = null, + tableContainerRef = null, + getColumnSizeVars = () => ({}), + containerWidth = 0, + enableResizing = false, children, className, ...props }: DataTableProps) { + // Calculate column size variables for CSS + const columnSizeVars = React.useMemo(() => { + const headers = table.getFlatHeaders(); + const colSizes: { [key: string]: string } = {}; + + for (let i = 0; i < headers.length; i++) { + const header = headers[i]!; + // Set default sizes even when resizing is not enabled + const size = header.getSize(); + colSizes[`--header-${header.id}-size`] = `${size}px`; + colSizes[`--col-${header.column.id}-size`] = `${size}px`; + } + + return colSizes; + }, [table, table.getState().columnSizingInfo, table.getState().columnSizing]); + + // Get table total size safely for resizing + const totalSize = React.useMemo(() => { + if ( + enableResizing && + containerWidth && + typeof table.getTotalSize === "function" + ) { + return table.getTotalSize(); + } + return null; + }, [enableResizing, containerWidth, table]); + return ( -
+
{children} -
- - - {table.getHeaderGroups().map((headerGroup) => ( - - {headerGroup.headers.map((header) => { - return ( - - {header.isPlaceholder - ? null - : flexRender( - header.column.columnDef.header, - header.getContext(), + +
+
+
+ + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header, index) => { + return ( + +
+ {header.isPlaceholder + ? null + : flexRender( + header.column.columnDef.header, + header.getContext() + )} +
+ + {/* Add resize handle when resizing is enabled */} + {enableResizing && + header.column.getCanResize() && + index < headerGroup.headers.length && ( +
header.column.resetSize()} + onMouseDown={header.getResizeHandler()} + onTouchStart={header.getResizeHandler()} + className={cn( + "absolute right-0 top-0 h-full w-4 cursor-col-resize select-none touch-none z-10 -translate-x-1/2", + "after:content-[''] after:absolute after:right-[50%] after:top-[15%] after:h-[70%] after:w-[1px] after:transition-colors", + header.column.getIsResizing() + ? "after:bg-primary" + : "after:bg-border/60 hover:after:bg-border" + )} + /> )} - - ); - })} - - ))} - - - {table.getRowModel().rows?.length ? ( - table.getRowModel().rows.map((row) => ( - - {row.getVisibleCells().map((cell) => ( - - {flexRender( - cell.column.columnDef.cell, - cell.getContext(), - )} - - ))} + + ); + })} - )) - ) : ( - - - No results. - - - )} - -
+ ))} + + + {enableResizing && + table.getState().columnSizingInfo?.isResizingColumn ? ( + + ) : ( + + )} + + +
diff --git a/src/components/debug-panel.tsx b/src/components/debug-panel.tsx new file mode 100644 index 000000000..231fd6474 --- /dev/null +++ b/src/components/debug-panel.tsx @@ -0,0 +1,92 @@ +import React from "react"; +import { Table as TanstackTable } from "@tanstack/react-table"; + +export default function DebugPanel({ table }: { table: TanstackTable }) { + const [windowWidth, setWindowWidth] = React.useState(0); + const renderCountRef = React.useRef(0); + const lastRenderTime = React.useRef(""); + + React.useEffect(() => { + setWindowWidth(window.innerWidth); + const handleResize = () => setWindowWidth(window.innerWidth); + window.addEventListener("resize", handleResize); + return () => window.removeEventListener("resize", handleResize); + }, []); + + React.useEffect(() => { + lastRenderTime.current = new Date().toISOString(); + renderCountRef.current++; + }); + + const columnWidths = table.getAllColumns().reduce( + (acc, column) => { + acc[column.id] = column.getSize(); + return acc; + }, + {} as Record + ); + + const state = table.getState(); + + const debugSections = [ + { + title: "Widths", + data: { + pageWidth: windowWidth, + tableWidth: table.getTotalSize(), + isOverflowing: table.getTotalSize() > windowWidth, + columnWidths, + }, + }, + { + title: "Sizing Info", + data: { + columnSizingInfo: state.columnSizingInfo, + columnSizing: state.columnSizing, + }, + }, + { + title: "Table State", + data: { + rowCount: table.getRowModel().rows.length, + columnCount: table.getAllColumns().length, + isResizing: state.columnSizingInfo.isResizingColumn, + currentResizingColumn: state.columnSizingInfo.columnSizingStart, + sortedRow: state.sorting.reduce( + (acc, curr) => { + acc[curr.id] = curr.desc ? "desc" : "asc"; + return acc; + }, + {} as Record + ), + }, + }, + { + title: "Performance", + data: { + renderCount: renderCountRef.current + 1, + previousRenderTime: lastRenderTime.current || "Not rendered yet", + isMemoized: table.getState().columnSizingInfo.isResizingColumn, + info: "not showing the current render time cuz tht will inturn cause a rerender", + }, + }, + ]; + + return ( +
+ {debugSections.map((section) => ( +
+
+ {section.title} +
+
+            {JSON.stringify(section.data, null, 2)}
+          
+
+ ))} +
+ ); +} diff --git a/src/config/data-table.ts b/src/config/data-table.ts index 50c3423c9..94b7dac9d 100644 --- a/src/config/data-table.ts +++ b/src/config/data-table.ts @@ -1,4 +1,4 @@ -import { Pickaxe, SquareSquare } from "lucide-react"; +import { Pickaxe, Expand, SquareSquare } from "lucide-react"; export type DataTableConfig = typeof dataTableConfig; @@ -18,6 +18,14 @@ export const dataTableConfig = { tooltipTitle: "Toggle floating bar", tooltipDescription: "A floating bar that sticks to the top of the table.", }, + { + label: "Resizable columns", + value: "resizableColumns" as const, + icon: Expand, + tooltipTitle: "Toggle resizable columns", + tooltipDescription: + "Allows columns to be resized by dragging their edges.", + }, ], textOperators: [ { label: "Contains", value: "iLike" as const }, diff --git a/src/hooks/use-data-table.ts b/src/hooks/use-data-table.ts index 412b06e22..b9ded2404 100644 --- a/src/hooks/use-data-table.ts +++ b/src/hooks/use-data-table.ts @@ -10,6 +10,8 @@ import { type TableState, type Updater, type VisibilityState, + type ColumnSizingState, + type ColumnSizingInfoState, getCoreRowModel, getFacetedRowModel, getFacetedUniqueValues, @@ -30,6 +32,7 @@ import { import * as React from "react"; import { useDebouncedCallback } from "@/hooks/use-debounced-callback"; +import { useResize } from "@/hooks/use-resize"; import { getSortingStateParser } from "@/lib/parsers"; interface UseDataTableProps @@ -129,6 +132,22 @@ interface UseDataTableProps */ enableAdvancedFilter?: boolean; + /** + * Enable column resizing feature + * @default false + * @type boolean + */ + enableResizing?: boolean; + + /** + * Default column configuration for resizing + * @default { minSize: 40, maxSize: 800 } + */ + defaultColumn?: { + minSize?: number; + maxSize?: number; + }; + initialState?: Omit, "sorting"> & { // Extend to make the sorting id typesafe sorting?: ExtendedSortingState; @@ -139,6 +158,8 @@ export function useDataTable({ pageCount = -1, filterFields = [], enableAdvancedFilter = false, + enableResizing = false, + defaultColumn = { minSize: 40, maxSize: 4800 }, history = "replace", scroll = false, shallow = true, @@ -172,26 +193,37 @@ export function useDataTable({ ]); const [rowSelection, setRowSelection] = React.useState( - initialState?.rowSelection ?? {}, + initialState?.rowSelection ?? {} ); const [columnVisibility, setColumnVisibility] = React.useState(initialState?.columnVisibility ?? {}); + // Initialize resizing hooks and refs + const { + columnSizing, + setColumnSizing, + columnSizingInfo, + setColumnSizingInfo, + tableContainerRef, + updateColumnConstraints, + containerWidth, + } = useResize(); + const [page, setPage] = useQueryState( "page", - parseAsInteger.withOptions(queryStateOptions).withDefault(1), + parseAsInteger.withOptions(queryStateOptions).withDefault(1) ); const [perPage, setPerPage] = useQueryState( "perPage", parseAsInteger .withOptions(queryStateOptions) - .withDefault(initialState?.pagination?.pageSize ?? 10), + .withDefault(initialState?.pagination?.pageSize ?? 10) ); const [sorting, setSorting] = useQueryState( "sort", getSortingStateParser() .withOptions(queryStateOptions) - .withDefault(initialState?.sorting ?? []), + .withDefault(initialState?.sorting ?? []) ); // Create parsers for each filter field @@ -202,7 +234,7 @@ export function useDataTable({ if (field.options) { // Faceted filter acc[field.id] = parseAsArrayOf(parseAsString, ",").withOptions( - queryStateOptions, + queryStateOptions ); } else { // Search filter @@ -216,7 +248,7 @@ export function useDataTable({ const debouncedSetFilterValues = useDebouncedCallback( setFilterValues, - debounceMs, + debounceMs ); // Paginate @@ -258,7 +290,7 @@ export function useDataTable({ } return filters; }, - [], + [] ); }, [filterValues, enableAdvancedFilter]); @@ -317,11 +349,12 @@ export function useDataTable({ filterableColumns, searchableColumns, setPage, - ], + ] ); const table = useReactTable({ ...props, + defaultColumn: enableResizing ? defaultColumn : undefined, initialState, pageCount, state: { @@ -330,6 +363,7 @@ export function useDataTable({ columnVisibility, rowSelection, columnFilters: enableAdvancedFilter ? [] : columnFilters, + ...(enableResizing ? { columnSizing, columnSizingInfo } : {}), }, enableRowSelection: true, onRowSelectionChange: setRowSelection, @@ -337,6 +371,13 @@ export function useDataTable({ onSortingChange, onColumnFiltersChange, onColumnVisibilityChange: setColumnVisibility, + ...(enableResizing + ? { + onColumnSizingChange: setColumnSizing, + onColumnSizingInfoChange: setColumnSizingInfo, + columnResizeMode: "onChange", + } + : {}), getCoreRowModel: getCoreRowModel(), getFilteredRowModel: enableAdvancedFilter ? undefined @@ -352,5 +393,16 @@ export function useDataTable({ manualFiltering: true, }); - return { table }; + // Initialize column constraints when table mounts or columns change + React.useEffect(() => { + if (enableResizing) { + updateColumnConstraints(table); + } + }, [enableResizing, updateColumnConstraints, table, props.columns]); + + return { + table, + tableContainerRef, + containerWidth, + }; } diff --git a/src/hooks/use-resize.ts b/src/hooks/use-resize.ts new file mode 100644 index 000000000..409b17c23 --- /dev/null +++ b/src/hooks/use-resize.ts @@ -0,0 +1,293 @@ +import React from "react"; +import type { + ColumnSizingState, + ColumnSizingInfoState, + Table, +} from "@tanstack/react-table"; + +interface ColumnConstraint { + id: string; + size: number; + minSize: number; + maxSize: number; +} + +/** + * Distributes pixels proportionally across columns based on their sizes and constraints. + */ +function distributePixelsProportionally( + columnSizes: number[], + pixelsToDistribute: number, + constraints: ColumnConstraint[], + useFloatPrecision: boolean = true, + useAvailableSpace: boolean = true +): number[] { + // Base conditions + if (columnSizes.length === 0 || pixelsToDistribute === 0) { + return columnSizes.map(() => 0); + } + + // Scale up for precise float calculations (power of 2 to avoid precision issues) + const precisionScale = useFloatPrecision ? 8192 : 1; + const scaledPixels = pixelsToDistribute * precisionScale; + + // Calculate available space for growing or shrinking + const availableSpace = columnSizes.map((size, i) => { + if (pixelsToDistribute < 0) { + return size - constraints[i]!.minSize; + } else { + return constraints[i]!.maxSize - size; + } + }); + + // Determine basis for distribution (either available space or column sizes) + const distributionBasis = useAvailableSpace ? availableSpace : columnSizes; + const totalBasis = distributionBasis.reduce((sum, value) => sum + value, 0); + + // Calculate initial pixel distribution proportionally + const initialChanges = distributionBasis.map((basis) => + totalBasis > 0 ? Math.floor((basis * scaledPixels) / totalBasis) : 0 + ); + + // Apply constraints to ensure columns don't exceed min/max sizes + const constrainedChanges = initialChanges.map((change, i) => { + if (pixelsToDistribute < 0) { + // Don't shrink below minimum size + return Math.max(change, -availableSpace[i]! * precisionScale); + } else { + // Don't grow beyond maximum size + return Math.min(change, availableSpace[i]! * precisionScale); + } + }); + + // Calculate how much delta is left to distribute after applying constraints + const distributedPixels = constrainedChanges.reduce( + (sum, change) => sum + change, + 0 + ); + const remainingPixels = scaledPixels - distributedPixels; + + // If we have remaining pixels to distribute, allocate them to columns that can still grow + let finalChanges = [...constrainedChanges]; + if (remainingPixels > 0) { + // Find columns that can still grow + const columnsWithCapacity = constrainedChanges + .map((change, i) => ({ + index: i, + remainingCapacity: availableSpace[i]! * precisionScale - change, + })) + .filter((col) => col.remainingCapacity > 0) + .sort((a, b) => b.remainingCapacity - a.remainingCapacity); + + // Distribute remaining pixels + let unallocatedPixels = remainingPixels; + for (const column of columnsWithCapacity) { + if (unallocatedPixels <= 0) break; + const pixelsToAdd = Math.min(unallocatedPixels, column.remainingCapacity); + finalChanges[column.index]! += pixelsToAdd; + unallocatedPixels -= pixelsToAdd; + } + } + + // Scale back down if using float precision + if (useFloatPrecision) { + return finalChanges.map((change) => change / precisionScale); + } + + return finalChanges; +} + +export function useResize() { + // Reference to column sizes before resize starts + const prevColumnSizes = React.useRef({}); + + const [columnSizing, setColumnSizing] = React.useState({}); + const [columnSizingInfo, setColumnSizingInfo] = + React.useState({ + isResizingColumn: false, + columnSizingStart: [], + deltaOffset: null, + deltaPercentage: null, + startOffset: null, + startSize: null, + }); + const [columnConstraints, setColumnConstraints] = React.useState< + ColumnConstraint[] + >([]); + + const tableContainerRef = React.useRef(null); + const [containerWidth, setContainerWidth] = React.useState(0); + + // Update container width on mount and window resize + React.useEffect(() => { + const updateContainerWidth = () => { + if (tableContainerRef.current) { + setContainerWidth(tableContainerRef.current.clientWidth); + } + }; + updateContainerWidth(); + window.addEventListener("resize", updateContainerWidth); + return () => window.removeEventListener("resize", updateContainerWidth); + }, []); + + // Update column constraints when columns change + const updateColumnConstraints = React.useCallback((table: Table) => { + const newConstraints = table.getFlatHeaders().map((header) => ({ + id: header.id, + size: header.getSize(), + minSize: header.column.columnDef.minSize ?? 0, + maxSize: header.column.columnDef.maxSize ?? 4800, + })); + setColumnConstraints(newConstraints); + }, []); + + const initializeColumnSizing = React.useCallback(() => { + if (containerWidth > 0 && columnConstraints.length > 0) { + const currentTotalWidth = columnConstraints.reduce( + (sum, column) => sum + column.size, + 0 + ); + + // Calculate extra space to distribute among columns + const extraSpace = Math.max(containerWidth - currentTotalWidth, 0); + + const sizeAdjustments = distributePixelsProportionally( + columnConstraints.map((column) => column.size), + extraSpace, + columnConstraints, + true, // Use float precision + false // Use column sizes (not available space) for distribution + ); + + const newSizes: ColumnSizingState = {}; + columnConstraints.forEach((column, index) => { + newSizes[column.id] = column.size + sizeAdjustments[index]!; + }); + + setColumnSizing(newSizes); + prevColumnSizes.current = newSizes; + } + }, [containerWidth, columnConstraints]); + + // Initialize sizing when container width or constraints change + React.useEffect(() => { + initializeColumnSizing(); + }, [containerWidth, columnConstraints, initializeColumnSizing]); + + // Store column sizes when resizing ends + React.useEffect(() => { + if (!columnSizingInfo.isResizingColumn) { + prevColumnSizes.current = columnSizing; + } + }, [columnSizingInfo.isResizingColumn, columnSizing]); + + const handleColumnSizing = React.useCallback( + ( + updaterOrValue: + | ColumnSizingState + | ((old: ColumnSizingState) => ColumnSizingState) + ) => { + // Handle non-resizing updates (direct state updates) + if (!columnSizingInfo.isResizingColumn || !columnSizingInfo.deltaOffset) { + const newSizing = + typeof updaterOrValue === "function" + ? updaterOrValue(columnSizing) + : updaterOrValue; + setColumnSizing(newSizing); + return; + } + + // Handle active column resizing + const resizingColumnId = columnSizingInfo.isResizingColumn; + let pixelDelta = Math.round(columnSizingInfo.deltaOffset); + let updatedSizing = { ...prevColumnSizes.current }; + + // Find the index of the column being resized + const resizingColumnIndex = columnConstraints.findIndex( + (c) => c.id === resizingColumnId + ); + const rightColumns = columnConstraints.slice(resizingColumnIndex + 1); + + // Calculate total width of all columns before resizing + const totalTableWidth = columnConstraints.reduce( + (sum, column) => sum + prevColumnSizes.current[column.id]!, + 0 + ); + + // Process columns to the left of the resizing column (including the resizing column itself) + let remainingDelta = pixelDelta; + if (remainingDelta !== 0) { + // Start from the resizing column and work leftward + for (let i = resizingColumnIndex; i >= 0 && remainingDelta !== 0; i--) { + const column = columnConstraints[i]!; + const currentSize = prevColumnSizes.current[column.id]!; + const targetSize = currentSize + remainingDelta; + + // Apply min/max constraints + const constrainedSize = Math.min( + Math.max(targetSize, column.minSize), + column.maxSize + ); + updatedSizing[column.id] = constrainedSize; + + // Calculate remaining delta that couldn't be applied due to constraints + remainingDelta = targetSize - constrainedSize; + } + } + + // Adjust delta based on unallocated pixels + let adjustedDelta = pixelDelta; + if (remainingDelta !== 0) { + adjustedDelta = pixelDelta - remainingDelta; + } + + // Handle right columns resizing based on table width constraints + if (totalTableWidth > containerWidth) { + // When table is wider than container, adjust delta for overflow + adjustedDelta = adjustedDelta + (totalTableWidth - containerWidth); + if (adjustedDelta > 0) { + adjustedDelta = 0; // Don't allow growth when table already exceeds container + } + } else if (adjustedDelta > 0) { + // If growing, ensure we don't shrink right columns below their minimum + const availableShrinkSpace = rightColumns.reduce((sum, column) => { + return sum + prevColumnSizes.current[column.id]! - column.minSize; + }, 0); + adjustedDelta = Math.min(availableShrinkSpace, adjustedDelta); + } + + // Apply changes to columns on the right of the resizing column + if (rightColumns.length > 0 && adjustedDelta !== 0) { + const rightColumnSizes = rightColumns.map( + (column) => prevColumnSizes.current[column.id]! + ); + + // Distribute the delta proportionally among right columns (negative delta) + const rightColumnChanges = distributePixelsProportionally( + rightColumnSizes, + -adjustedDelta, // Negative because we're doing the opposite of the main column + rightColumns + ); + + // Apply the changes to the right columns + rightColumns.forEach((column, index) => { + updatedSizing[column.id] = + prevColumnSizes.current[column.id]! + rightColumnChanges[index]!; + }); + } + + setColumnSizing(updatedSizing); + }, + [columnSizingInfo, containerWidth, columnConstraints] + ); + + return { + columnSizing, + setColumnSizing: handleColumnSizing, + columnSizingInfo, + setColumnSizingInfo, + tableContainerRef, + updateColumnConstraints, + containerWidth, + }; +} From 8176dc780037e7d9dca5dcd5a68594ab039c8aed Mon Sep 17 00:00:00 2001 From: prathwik <64214685+prathwik0@users.noreply.github.com> Date: Sat, 1 Mar 2025 16:58:18 +0530 Subject: [PATCH 4/6] style: update styles to work well with resizable columns --- src/app/_components/tasks-table-columns.tsx | 36 +++++++++++-------- .../data-table/data-table-column-header.tsx | 2 +- src/components/data-table/data-table.tsx | 4 +-- src/components/ui/table.tsx | 12 +++---- 4 files changed, 30 insertions(+), 24 deletions(-) diff --git a/src/app/_components/tasks-table-columns.tsx b/src/app/_components/tasks-table-columns.tsx index 0a6100a86..e48eb67de 100644 --- a/src/app/_components/tasks-table-columns.tsx +++ b/src/app/_components/tasks-table-columns.tsx @@ -43,23 +43,29 @@ export function getColumns({ { id: "select", header: ({ table }) => ( - table.toggleAllPageRowsSelected(!!value)} - aria-label="Select all" - className="translate-y-0.5" - /> +
+ + table.toggleAllPageRowsSelected(!!value) + } + aria-label="Select all" + className="translate-y-0.5" + /> +
), cell: ({ row }) => ( - row.toggleSelected(!!value)} - aria-label="Select row" - className="translate-y-0.5" - /> +
+ row.toggleSelected(!!value)} + aria-label="Select row" + className="translate-y-0.5" + /> +
), enableSorting: false, enableHiding: false, diff --git a/src/components/data-table/data-table-column-header.tsx b/src/components/data-table/data-table-column-header.tsx index ba5564d17..1d734633b 100644 --- a/src/components/data-table/data-table-column-header.tsx +++ b/src/components/data-table/data-table-column-header.tsx @@ -32,7 +32,7 @@ export function DataTableColumnHeader({ const hideValue = `${column.id}-hide`; return ( -
+