@taskmark/ui 0.3.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/app/globals.css +25 -0
- package/app/layout.tsx +15 -5
- package/app/setup/page.tsx +1 -1
- package/bin/lib/resolve-board.mjs +1 -4
- package/components/board/app-bar.tsx +3 -0
- package/components/board/board-screen.tsx +61 -117
- package/components/board/changelog-panel.tsx +26 -0
- package/components/board/epic-list.tsx +2 -0
- package/components/board/list-view-switcher.tsx +7 -3
- package/components/board/markdown-content.tsx +21 -1
- package/components/board/mermaid-diagram.tsx +88 -0
- package/components/board/metric-stat-card.tsx +4 -4
- package/components/board/overall-tree-list.tsx +467 -0
- package/components/board/overall-work-items-list.tsx +5 -1
- package/components/board/points-heatmap.tsx +183 -0
- package/components/board/project-status-metrics-strip.tsx +5 -22
- package/components/board/static-board-app.tsx +29 -94
- package/components/board/status-badge.tsx +8 -0
- package/components/board/story-list.tsx +2 -0
- package/components/board/task-list.tsx +5 -1
- package/components/board/work-item-detail-body.tsx +123 -8
- package/components/board/work-items-list.tsx +2 -0
- package/components/theme-provider.tsx +39 -0
- package/components/theme-toggle.tsx +27 -0
- package/components/ui/sonner.tsx +2 -2
- package/dist/prod-next/BUILD_ID +1 -1
- package/dist/prod-next/build-manifest.json +6 -6
- package/dist/prod-next/react-loadable-manifest.json +391 -1
- package/dist/prod-next/server/app/_global-error/page.js +2 -2
- package/dist/prod-next/server/app/_global-error/page.js.nft.json +1 -1
- package/dist/prod-next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/dist/prod-next/server/app/_global-error.html +1 -1
- package/dist/prod-next/server/app/_global-error.rsc +1 -1
- package/dist/prod-next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/dist/prod-next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/dist/prod-next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/dist/prod-next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/dist/prod-next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/dist/prod-next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/dist/prod-next/server/app/_not-found/page.js +2 -2
- package/dist/prod-next/server/app/_not-found/page.js.nft.json +1 -1
- package/dist/prod-next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/dist/prod-next/server/app/_not-found.html +1 -1
- package/dist/prod-next/server/app/_not-found.rsc +19 -17
- package/dist/prod-next/server/app/_not-found.segments/_full.segment.rsc +19 -17
- package/dist/prod-next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/dist/prod-next/server/app/_not-found.segments/_index.segment.rsc +8 -6
- package/dist/prod-next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/dist/prod-next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/dist/prod-next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/dist/prod-next/server/app/api/taskmark-reload/route.js +1 -1
- package/dist/prod-next/server/app/api/taskmark-reload/route.js.nft.json +1 -1
- package/dist/prod-next/server/app/board/page.js +2 -2
- package/dist/prod-next/server/app/board/page.js.nft.json +1 -1
- package/dist/prod-next/server/app/board/page_client-reference-manifest.js +1 -1
- package/dist/prod-next/server/app/favicon.ico/route.js +1 -1
- package/dist/prod-next/server/app/favicon.ico/route.js.nft.json +1 -1
- package/dist/prod-next/server/app/page.js +2 -2
- package/dist/prod-next/server/app/page.js.nft.json +1 -1
- package/dist/prod-next/server/app/page_client-reference-manifest.js +1 -1
- package/dist/prod-next/server/app/setup/page.js +2 -2
- package/dist/prod-next/server/app/setup/page.js.nft.json +1 -1
- package/dist/prod-next/server/app/setup/page_client-reference-manifest.js +1 -1
- package/dist/prod-next/server/chunks/102.js +90 -0
- package/dist/prod-next/server/chunks/11.js +1 -0
- package/dist/prod-next/server/chunks/1286.js +1 -0
- package/dist/prod-next/server/chunks/1412.js +38 -0
- package/dist/prod-next/server/chunks/1605.js +1 -0
- package/dist/prod-next/server/chunks/169.js +1 -0
- package/dist/prod-next/server/chunks/18.js +86 -0
- package/dist/prod-next/server/chunks/2.js +1 -0
- package/dist/prod-next/server/chunks/2022.js +1 -0
- package/dist/prod-next/server/chunks/2156.js +1 -0
- package/dist/prod-next/server/chunks/2157.js +1 -0
- package/dist/prod-next/server/chunks/2325.js +82 -0
- package/dist/prod-next/server/chunks/2838.js +1 -0
- package/dist/prod-next/server/chunks/2897.js +79 -0
- package/dist/prod-next/server/chunks/3227.js +1 -0
- package/dist/prod-next/server/chunks/3266.js +1 -0
- package/dist/prod-next/server/chunks/{192.js → 3307.js} +5 -5
- package/dist/prod-next/server/chunks/3387.js +19 -0
- package/dist/prod-next/server/chunks/3428.js +32 -0
- package/dist/prod-next/server/chunks/3479.js +1 -0
- package/dist/prod-next/server/chunks/367.js +115 -0
- package/dist/prod-next/server/chunks/3698.js +93 -0
- package/dist/prod-next/server/chunks/3709.js +148 -0
- package/dist/prod-next/server/chunks/41.js +1 -0
- package/dist/prod-next/server/chunks/4107.js +62 -0
- package/dist/prod-next/server/chunks/4569.js +34 -0
- package/dist/prod-next/server/chunks/{741.js → 4741.js} +1 -1
- package/dist/prod-next/server/chunks/4755.js +153 -0
- package/dist/prod-next/server/chunks/5002.js +1 -0
- package/dist/prod-next/server/chunks/5112.js +1 -0
- package/dist/prod-next/server/chunks/5289.js +8 -0
- package/dist/prod-next/server/chunks/5318.js +1 -0
- package/dist/prod-next/server/chunks/5452.js +23 -0
- package/dist/prod-next/server/chunks/5453.js +1 -0
- package/dist/prod-next/server/chunks/5749.js +27 -0
- package/dist/prod-next/server/chunks/5782.js +36 -0
- package/dist/prod-next/server/chunks/6163.js +104 -0
- package/dist/prod-next/server/chunks/6169.js +64 -0
- package/dist/prod-next/server/chunks/6208.js +172 -0
- package/dist/prod-next/server/chunks/6399.js +24 -0
- package/dist/prod-next/server/chunks/6496.js +1 -0
- package/dist/prod-next/server/chunks/6823.js +215 -0
- package/dist/prod-next/server/chunks/6888.js +1 -0
- package/dist/prod-next/server/chunks/6980.js +1 -0
- package/dist/prod-next/server/chunks/7245.js +77 -0
- package/dist/prod-next/server/chunks/7307.js +1 -0
- package/dist/prod-next/server/chunks/7372.js +287 -0
- package/dist/prod-next/server/chunks/7384.js +226 -0
- package/dist/prod-next/server/chunks/7516.js +1 -0
- package/dist/prod-next/server/chunks/7531.js +24 -0
- package/dist/prod-next/server/chunks/7591.js +1 -0
- package/dist/prod-next/server/chunks/7755.js +138 -0
- package/dist/prod-next/server/chunks/7916.js +1 -0
- package/dist/prod-next/server/chunks/8078.js +94 -0
- package/dist/prod-next/server/chunks/8253.js +41 -0
- package/dist/prod-next/server/chunks/8635.js +1 -0
- package/dist/prod-next/server/chunks/8688.js +1 -0
- package/dist/prod-next/server/chunks/8872.js +1 -0
- package/dist/prod-next/server/chunks/9006.js +1 -0
- package/dist/prod-next/server/chunks/9018.js +1 -0
- package/dist/prod-next/server/chunks/9047.js +1 -0
- package/dist/prod-next/server/chunks/9108.js +1 -0
- package/dist/prod-next/server/chunks/9280.js +256 -0
- package/dist/prod-next/server/chunks/9419.js +1 -0
- package/dist/prod-next/server/chunks/9430.js +1 -0
- package/dist/prod-next/server/chunks/9586.js +1 -0
- package/dist/prod-next/server/chunks/9775.js +1 -0
- package/dist/prod-next/server/chunks/992.js +1 -0
- package/dist/prod-next/server/chunks/9926.js +1 -0
- package/dist/prod-next/server/chunks/9984.js +8 -0
- package/dist/prod-next/server/middleware-build-manifest.js +1 -1
- package/dist/prod-next/server/middleware-react-loadable-manifest.js +1 -1
- package/dist/prod-next/server/pages/404.html +1 -1
- package/dist/prod-next/server/pages/500.html +1 -1
- package/dist/prod-next/server/server-reference-manifest.js +1 -1
- package/dist/prod-next/server/server-reference-manifest.json +1 -1
- package/dist/prod-next/server/webpack-runtime.js +1 -1
- package/dist/prod-next/static/chunks/1213.e5c6e70a79b5f7e6.js +24 -0
- package/dist/prod-next/static/chunks/1519.2f596e5f5af9ef57.js +62 -0
- package/dist/prod-next/static/chunks/1759-f5b8fb456f8fb420.js +1 -0
- package/dist/prod-next/static/chunks/1974.885c9a133b86d8a4.js +32 -0
- package/dist/prod-next/static/chunks/2280.6a7f540787704698.js +1 -0
- package/dist/prod-next/static/chunks/2521.7f745f50d30076e7.js +77 -0
- package/dist/prod-next/static/chunks/2585.4d6aa847c780c666.js +1 -0
- package/dist/prod-next/static/chunks/2589.1aad68c09021b1ac.js +1 -0
- package/dist/prod-next/static/chunks/2600.45ba17a0f756fc31.js +1 -0
- package/dist/prod-next/static/chunks/2619.9aac8983f30c7c8a.js +1 -0
- package/dist/prod-next/static/chunks/26d302b2.9f6383e42abdfe16.js +122 -0
- package/dist/prod-next/static/chunks/2768.305914f7900745b6.js +1 -0
- package/dist/prod-next/static/chunks/2931.c2acdbf72808b646.js +41 -0
- package/dist/prod-next/static/chunks/3012-838f69b72a0dc1b3.js +1 -0
- package/dist/prod-next/static/chunks/3016.406e568aa95ff867.js +1 -0
- package/dist/prod-next/static/chunks/3022.972047558a20553a.js +172 -0
- package/dist/prod-next/static/chunks/3064.3a2a1e7cd4fdca5f.js +1 -0
- package/dist/prod-next/static/chunks/3174.03fbed7b7d7a8256.js +1 -0
- package/dist/prod-next/static/chunks/3189.b048866768087750.js +1 -0
- package/dist/prod-next/static/chunks/3217.1a053843c58b70b5.js +24 -0
- package/dist/prod-next/static/chunks/3308.30b95d524f9c1daf.js +74 -0
- package/dist/prod-next/static/chunks/331.fc5d78e1e91daf43.js +1 -0
- package/dist/prod-next/static/chunks/3510.5d4d1c79533e080c.js +1 -0
- package/dist/prod-next/static/chunks/364.ce5400a9dd70b9dc.js +1 -0
- package/dist/prod-next/static/chunks/3661-1f08d73aaffb011d.js +1 -0
- package/dist/prod-next/static/chunks/370.57cfa27da8567918.js +1 -0
- package/dist/prod-next/static/chunks/3789.c418b69121761983.js +1 -0
- package/dist/prod-next/static/chunks/3794-c66b3d8325973459.js +32 -0
- package/dist/prod-next/static/chunks/3831.25ae62d10dccc27a.js +1 -0
- package/dist/prod-next/static/chunks/395.b41e9d76613d37b5.js +90 -0
- package/dist/prod-next/static/chunks/3a7aa434.93dbd20997cfd24f.js +82 -0
- package/dist/prod-next/static/chunks/4020.3cc19c463c49f969.js +23 -0
- package/dist/prod-next/static/chunks/4035.0acc428926d5c8dd.js +1 -0
- package/dist/prod-next/static/chunks/405.0902e4f6eb7bd9a2.js +215 -0
- package/dist/prod-next/static/chunks/4159.49ae537ff687bdc6.js +138 -0
- package/dist/prod-next/static/chunks/4208.384b381ca2757643.js +287 -0
- package/dist/prod-next/static/chunks/4263.ffa8d9bd8a0faaae.js +1 -0
- package/dist/prod-next/static/chunks/4561.d1a3aaca8bae3224.js +34 -0
- package/dist/prod-next/static/chunks/4592.d0f37bc216b9d988.js +93 -0
- package/dist/prod-next/static/chunks/4799.0dc7aa21bff2fd2a.js +1 -0
- package/dist/prod-next/static/chunks/4bd1b696-e356ca5ba0218e27.js +1 -0
- package/dist/prod-next/static/chunks/514.b804c51b26f5e126.js +1 -0
- package/dist/prod-next/static/chunks/5270.28592864d38e881c.js +1 -0
- package/dist/prod-next/static/chunks/52733464.3870f99cc446f9ff.js +1 -0
- package/dist/prod-next/static/chunks/5354.b559c717a607dc5c.js +1 -0
- package/dist/prod-next/static/chunks/5448.d3972d9fb464b54f.js +38 -0
- package/dist/prod-next/static/chunks/587.d52737d7288c11b4.js +1 -0
- package/dist/prod-next/static/chunks/5909.9e37f7b495b11995.js +115 -0
- package/dist/prod-next/static/chunks/5949.5504b3545fcc3d34.js +104 -0
- package/dist/prod-next/static/chunks/6322-4e97920c006f7933.js +1 -0
- package/dist/prod-next/static/chunks/6361.893d04ac34fa1b49.js +148 -0
- package/dist/prod-next/static/chunks/6774.8cd53427dbf32cac.js +86 -0
- package/dist/prod-next/static/chunks/6968.b80277c724dbc96e.js +1 -0
- package/dist/prod-next/static/chunks/7113.4b9e7ea870aa300a.js +8 -0
- package/dist/prod-next/static/chunks/7236.55a9728d35f5b8a6.js +1 -0
- package/dist/prod-next/static/chunks/7390.dde9b5bc2f6e24b8.js +1 -0
- package/dist/prod-next/static/chunks/7806.a903780fcfa3a30e.js +1 -0
- package/dist/prod-next/static/chunks/7997.be57e92e583fe635.js +1 -0
- package/dist/prod-next/static/chunks/8216.64a72d4d35ea7be0.js +1 -0
- package/dist/prod-next/static/chunks/830.5d5274d7ad1cb58e.js +1 -0
- package/dist/prod-next/static/chunks/8378.fa91de3406f554c9.js +226 -0
- package/dist/prod-next/static/chunks/8501.cd06aa1b0c7e1c6a.js +79 -0
- package/dist/prod-next/static/chunks/8663.6ec5792fb4952e45.js +27 -0
- package/dist/prod-next/static/chunks/872.96937e758ddce860.js +1 -0
- package/dist/prod-next/static/chunks/8803.75bde095fe3613f5.js +1 -0
- package/dist/prod-next/static/chunks/8863.72f5dbb820a0e4c3.js +1 -0
- package/dist/prod-next/static/chunks/90542734.f7a7b2972ef5a749.js +1 -0
- package/dist/prod-next/static/chunks/9379.5bc9c9343ec5a16c.js +64 -0
- package/dist/prod-next/static/chunks/9389.906fe7aa9cd6a170.js +1 -0
- package/dist/prod-next/static/chunks/9472.8818ee52cfa931a4.js +94 -0
- package/dist/prod-next/static/chunks/966.d6babbf8119162cc.js +1 -0
- package/dist/prod-next/static/chunks/9884.d4ac1a961b3366f1.js +36 -0
- package/dist/prod-next/static/chunks/9909.67cf9940b125243b.js +1 -0
- package/dist/prod-next/static/chunks/app/_global-error/page-8be5492b3b872a4c.js +1 -0
- package/dist/prod-next/static/chunks/app/_not-found/page-799e445a538b0024.js +1 -0
- package/dist/prod-next/static/chunks/app/api/taskmark-reload/route-8be5492b3b872a4c.js +1 -0
- package/dist/prod-next/static/chunks/app/board/page-5c181f45738daf1c.js +1 -0
- package/dist/prod-next/static/chunks/app/layout-21782e6d13a6f275.js +1 -0
- package/dist/prod-next/static/chunks/app/page-51bfe4356fe233e3.js +1 -0
- package/dist/prod-next/static/chunks/app/setup/page-3ccfba00c10f9b17.js +1 -0
- package/dist/prod-next/static/chunks/d3ac728e.7964f816a1ca64e5.js +1 -0
- package/dist/prod-next/static/chunks/e413bedd.3a12a8cb02e28fdf.js +153 -0
- package/dist/prod-next/static/chunks/ea233666.f00b64a7d99b4950.js +62 -0
- package/dist/prod-next/static/chunks/framework-711ef29bc66f648c.js +1 -0
- package/dist/prod-next/static/chunks/main-app-dd26d4f9d120646e.js +1 -0
- package/dist/prod-next/static/chunks/{main-bb33c46a9c25dee8.js → main-df4bcaed74f274eb.js} +2 -2
- package/dist/prod-next/static/chunks/next/dist/client/components/builtin/app-error-8be5492b3b872a4c.js +1 -0
- package/dist/prod-next/static/chunks/next/dist/client/components/builtin/forbidden-8be5492b3b872a4c.js +1 -0
- package/dist/prod-next/static/chunks/next/dist/client/components/builtin/global-error-45a0cf77e03361ed.js +1 -0
- package/dist/prod-next/static/chunks/next/dist/client/components/builtin/not-found-8be5492b3b872a4c.js +1 -0
- package/dist/prod-next/static/chunks/next/dist/client/components/builtin/unauthorized-8be5492b3b872a4c.js +1 -0
- package/dist/prod-next/static/chunks/webpack-713747aaa9716c4c.js +1 -0
- package/dist/prod-next/static/css/cb78d4b0062d9a3f.css +1 -0
- package/dist/public/taskmark-snapshot.json +1 -1
- package/lib/taskmark/board-index.ts +101 -0
- package/lib/taskmark/build-snapshot.ts +28 -7
- package/lib/taskmark/derive-parents.test.ts +23 -0
- package/lib/taskmark/derive-parents.ts +323 -0
- package/lib/taskmark/detail-load.ts +55 -7
- package/lib/taskmark/detail-types.ts +10 -2
- package/lib/taskmark/dev-reload-token.ts +1 -1
- package/lib/taskmark/discover.ts +4 -7
- package/lib/taskmark/epic-types.ts +2 -1
- package/lib/taskmark/flat-work-item-types.ts +1 -4
- package/lib/taskmark/identity.ts +3 -3
- package/lib/taskmark/item-types.ts +2 -1
- package/lib/taskmark/list-filters.test.ts +23 -0
- package/lib/taskmark/list-filters.ts +7 -2
- package/lib/taskmark/list-view-mode.test.ts +45 -0
- package/lib/taskmark/list-view-mode.ts +19 -8
- package/lib/taskmark/load-changelog.test.ts +32 -0
- package/lib/taskmark/load-changelog.ts +18 -0
- package/lib/taskmark/overall-tree.test.ts +196 -0
- package/lib/taskmark/overall-tree.ts +194 -0
- package/lib/taskmark/parse-detail.ts +5 -5
- package/lib/taskmark/parse-epics.ts +19 -16
- package/lib/taskmark/parse-flat-lists.ts +32 -28
- package/lib/taskmark/parse-items.ts +47 -9
- package/lib/taskmark/parse-stories.ts +18 -15
- package/lib/taskmark/points-calendar.test.ts +72 -0
- package/lib/taskmark/points-calendar.ts +86 -0
- package/lib/taskmark/project-metrics.ts +11 -7
- package/lib/taskmark/snapshot-types.ts +2 -0
- package/lib/taskmark/story-types.ts +2 -1
- package/lib/taskmark/table-sort.ts +4 -3
- package/lib/taskmark/timing.ts +29 -18
- package/lib/taskmark/validate.ts +1 -1
- package/lib/theme-cookie.test.ts +27 -0
- package/lib/theme-cookie.ts +43 -0
- package/package.json +3 -1
- package/public/taskmark-snapshot.json +1 -1
- package/dist/prod-next/server/chunks/463.js +0 -1
- package/dist/prod-next/server/chunks/54.js +0 -19
- package/dist/prod-next/server/chunks/57.js +0 -8
- package/dist/prod-next/server/chunks/724.js +0 -1
- package/dist/prod-next/static/chunks/4bd1b696-deb4a0a1da1923b0.js +0 -1
- package/dist/prod-next/static/chunks/613-6f794154d28bb75a.js +0 -1
- package/dist/prod-next/static/chunks/636-626c380f021b95c8.js +0 -1
- package/dist/prod-next/static/chunks/669-8103441c67b185da.js +0 -1
- package/dist/prod-next/static/chunks/701-df3ee5f1cf837b1a.js +0 -1
- package/dist/prod-next/static/chunks/794-25668765560cb197.js +0 -32
- package/dist/prod-next/static/chunks/909.932f2af3be7f1e89.js +0 -1
- package/dist/prod-next/static/chunks/app/_global-error/page-a1e3320be3da9be1.js +0 -1
- package/dist/prod-next/static/chunks/app/_not-found/page-0b3785af737e7968.js +0 -1
- package/dist/prod-next/static/chunks/app/api/taskmark-reload/route-a1e3320be3da9be1.js +0 -1
- package/dist/prod-next/static/chunks/app/board/page-636f60191db62f52.js +0 -1
- package/dist/prod-next/static/chunks/app/layout-856e8a282e69e7d0.js +0 -1
- package/dist/prod-next/static/chunks/app/page-562a33b50da7665b.js +0 -1
- package/dist/prod-next/static/chunks/app/setup/page-4cf527b758b1d51e.js +0 -1
- package/dist/prod-next/static/chunks/framework-b29f9083d3cda3b2.js +0 -1
- package/dist/prod-next/static/chunks/main-app-bfb36455d24b3e5a.js +0 -1
- package/dist/prod-next/static/chunks/next/dist/client/components/builtin/app-error-a1e3320be3da9be1.js +0 -1
- package/dist/prod-next/static/chunks/next/dist/client/components/builtin/forbidden-a1e3320be3da9be1.js +0 -1
- package/dist/prod-next/static/chunks/next/dist/client/components/builtin/global-error-dc53ab29e0491fe4.js +0 -1
- package/dist/prod-next/static/chunks/next/dist/client/components/builtin/not-found-a1e3320be3da9be1.js +0 -1
- package/dist/prod-next/static/chunks/next/dist/client/components/builtin/unauthorized-a1e3320be3da9be1.js +0 -1
- package/dist/prod-next/static/chunks/webpack-d33b8a6298c94a64.js +0 -1
- package/dist/prod-next/static/css/2e6ec4c80894e3c0.css +0 -1
- /package/dist/prod-next/static/{yyRnYAFQrJjF5_b5ZIeqh → EI1zQaa_kQ7DqUSE4o3Qo}/_buildManifest.js +0 -0
- /package/dist/prod-next/static/{yyRnYAFQrJjF5_b5ZIeqh → EI1zQaa_kQ7DqUSE4o3Qo}/_ssgManifest.js +0 -0
|
@@ -2,16 +2,12 @@ import {
|
|
|
2
2
|
CalendarDays,
|
|
3
3
|
CheckSquare,
|
|
4
4
|
Layers,
|
|
5
|
-
TrendingUp,
|
|
6
5
|
Users,
|
|
7
6
|
} from "lucide-react"
|
|
8
7
|
|
|
9
8
|
import { MetricStatCard } from "@/components/board/metric-stat-card"
|
|
10
9
|
import { ProjectContributorsPanel } from "@/components/board/project-contributors-panel"
|
|
11
|
-
import {
|
|
12
|
-
formatSpeedPtsPerWeek,
|
|
13
|
-
type ProjectStatusMetrics,
|
|
14
|
-
} from "@/lib/taskmark/project-metrics-shared"
|
|
10
|
+
import type { ProjectStatusMetrics } from "@/lib/taskmark/project-metrics-shared"
|
|
15
11
|
|
|
16
12
|
type ProjectStatusMetricsStripProps = {
|
|
17
13
|
metrics: ProjectStatusMetrics
|
|
@@ -20,14 +16,12 @@ type ProjectStatusMetricsStripProps = {
|
|
|
20
16
|
export function ProjectStatusMetricsStrip({
|
|
21
17
|
metrics,
|
|
22
18
|
}: ProjectStatusMetricsStripProps) {
|
|
23
|
-
const speedLabel = formatSpeedPtsPerWeek(metrics.currentSpeedPtsPerWeek)
|
|
24
|
-
|
|
25
19
|
return (
|
|
26
20
|
<section
|
|
27
21
|
className="flex flex-col gap-4"
|
|
28
22
|
aria-label="Project status metrics"
|
|
29
23
|
>
|
|
30
|
-
<div className="grid grid-cols-1 gap-3 sm:grid-cols-
|
|
24
|
+
<div className="grid grid-cols-1 gap-3 sm:grid-cols-3">
|
|
31
25
|
<MetricStatCard
|
|
32
26
|
title="Total work items"
|
|
33
27
|
value={metrics.totalWorkItems}
|
|
@@ -53,29 +47,18 @@ export function ProjectStatusMetricsStrip({
|
|
|
53
47
|
accentClassName="bg-emerald-400"
|
|
54
48
|
icon={<CalendarDays className="size-4" strokeWidth={2.5} />}
|
|
55
49
|
/>
|
|
56
|
-
<MetricStatCard
|
|
57
|
-
title="Current speed"
|
|
58
|
-
value={speedLabel}
|
|
59
|
-
subtitle={
|
|
60
|
-
metrics.currentSpeedPtsPerWeek == null
|
|
61
|
-
? "No completed tasks yet"
|
|
62
|
-
: `pts/week · ${metrics.speedWeekCount} active weeks (90d)`
|
|
63
|
-
}
|
|
64
|
-
accentClassName="bg-sky-400"
|
|
65
|
-
icon={<TrendingUp className="size-4" strokeWidth={2.5} />}
|
|
66
|
-
/>
|
|
67
50
|
</div>
|
|
68
51
|
|
|
69
|
-
<div className="border-2 border-black bg-
|
|
52
|
+
<div className="border-2 border-black bg-card p-4">
|
|
70
53
|
<div className="mb-3 flex items-center gap-2">
|
|
71
54
|
<div
|
|
72
|
-
className="flex size-8 items-center justify-center border-2 border-black bg-amber-300 text-black shadow-[
|
|
55
|
+
className="flex size-8 items-center justify-center border-2 border-black bg-amber-300 text-black shadow-[3px_3px_0_0_var(--shadow-color)]"
|
|
73
56
|
aria-hidden
|
|
74
57
|
>
|
|
75
58
|
<Users className="size-4" strokeWidth={2.5} />
|
|
76
59
|
</div>
|
|
77
60
|
<div>
|
|
78
|
-
<h2 className="text-sm font-medium text-
|
|
61
|
+
<h2 className="text-sm font-medium text-card-foreground">
|
|
79
62
|
Contributors
|
|
80
63
|
</h2>
|
|
81
64
|
<p className="text-xs text-muted-foreground">
|
|
@@ -1,31 +1,26 @@
|
|
|
1
1
|
"use client"
|
|
2
2
|
|
|
3
3
|
import * as React from "react"
|
|
4
|
-
import Link from "next/link"
|
|
5
4
|
import { useSearchParams } from "next/navigation"
|
|
6
5
|
|
|
7
6
|
import { AppBar } from "@/components/board/app-bar"
|
|
8
|
-
import {
|
|
7
|
+
import { ChangelogPanel } from "@/components/board/changelog-panel"
|
|
9
8
|
import { ListViewSwitcher } from "@/components/board/list-view-switcher"
|
|
10
|
-
import {
|
|
9
|
+
import { OverallTreeList } from "@/components/board/overall-tree-list"
|
|
10
|
+
import { PointsHeatmap } from "@/components/board/points-heatmap"
|
|
11
11
|
import { ProjectStatusMetricsStrip } from "@/components/board/project-status-metrics-strip"
|
|
12
|
-
import { TaskList } from "@/components/board/task-list"
|
|
13
12
|
import { WorkItemsList } from "@/components/board/work-items-list"
|
|
14
13
|
import { WorkItemSheetProvider } from "@/components/board/work-item-sheet"
|
|
15
14
|
import {
|
|
16
15
|
LIST_VIEW_LABELS,
|
|
17
|
-
boardHref,
|
|
18
16
|
parseListViewMode,
|
|
19
17
|
type ListViewMode,
|
|
20
18
|
} from "@/lib/taskmark/list-view-mode"
|
|
21
19
|
import { seedBoardSnapshot } from "@/lib/taskmark/snapshot-client"
|
|
22
|
-
import {
|
|
23
|
-
storyKey,
|
|
24
|
-
type BoardSnapshot,
|
|
25
|
-
} from "@/lib/taskmark/snapshot-types"
|
|
20
|
+
import type { BoardSnapshot } from "@/lib/taskmark/snapshot-types"
|
|
26
21
|
|
|
27
22
|
function headingForView(view: ListViewMode): string {
|
|
28
|
-
if (view === "overall") return "
|
|
23
|
+
if (view === "overall") return "Overall"
|
|
29
24
|
return LIST_VIEW_LABELS[view]
|
|
30
25
|
}
|
|
31
26
|
|
|
@@ -36,15 +31,19 @@ export function StaticBoardApp({ snapshot }: { snapshot: BoardSnapshot }) {
|
|
|
36
31
|
}, [snapshot])
|
|
37
32
|
|
|
38
33
|
const searchParams = useSearchParams()
|
|
39
|
-
const
|
|
34
|
+
const changelogMarkdown = snapshot.changelogMarkdown?.trim()
|
|
35
|
+
? snapshot.changelogMarkdown
|
|
36
|
+
: null
|
|
37
|
+
const hasChangelog = changelogMarkdown != null
|
|
38
|
+
const activeView = parseListViewMode(searchParams.get("view") ?? undefined, {
|
|
39
|
+
hasChangelog,
|
|
40
|
+
})
|
|
40
41
|
const selectedEpicId =
|
|
41
42
|
activeView === "overall" ? searchParams.get("epic")?.trim() || null : null
|
|
42
43
|
const selectedStoryId =
|
|
43
44
|
activeView === "overall" && selectedEpicId
|
|
44
45
|
? searchParams.get("story")?.trim() || null
|
|
45
46
|
: null
|
|
46
|
-
const selectedItemId = searchParams.get("item")?.trim() || null
|
|
47
|
-
|
|
48
47
|
const activeProject = snapshot.project
|
|
49
48
|
const list = snapshot.epics
|
|
50
49
|
const epicCount = list.epics.length
|
|
@@ -58,27 +57,9 @@ export function StaticBoardApp({ snapshot }: { snapshot: BoardSnapshot }) {
|
|
|
58
57
|
? list.epics.find((e) => e.id === selectedEpicId) ?? null
|
|
59
58
|
: null
|
|
60
59
|
|
|
61
|
-
const epicWorkItems =
|
|
62
|
-
selectedEpicId != null
|
|
63
|
-
? snapshot.workItemsByEpic[selectedEpicId] ?? null
|
|
64
|
-
: null
|
|
65
|
-
|
|
66
|
-
const selectedStory =
|
|
67
|
-
epicWorkItems && selectedStoryId
|
|
68
|
-
? (epicWorkItems.rows.find(
|
|
69
|
-
(row) => row.kind === "story" && row.id === selectedStoryId
|
|
70
|
-
) ?? null)
|
|
71
|
-
: null
|
|
72
|
-
|
|
73
|
-
const itemList =
|
|
74
|
-
selectedEpicId && selectedStory
|
|
75
|
-
? snapshot.itemsByStory[storyKey(selectedEpicId, selectedStory.id)] ??
|
|
76
|
-
null
|
|
77
|
-
: null
|
|
78
|
-
|
|
79
60
|
return (
|
|
80
61
|
<WorkItemSheetProvider>
|
|
81
|
-
<div className="min-h-svh
|
|
62
|
+
<div className="tm-surface min-h-svh">
|
|
82
63
|
<AppBar
|
|
83
64
|
projects={snapshot.projects}
|
|
84
65
|
activeProjectId={activeProject.id}
|
|
@@ -103,23 +84,20 @@ export function StaticBoardApp({ snapshot }: { snapshot: BoardSnapshot }) {
|
|
|
103
84
|
? ` · ${workItemsList.rows.length} item${workItemsList.rows.length === 1 ? "" : "s"}`
|
|
104
85
|
: ""}
|
|
105
86
|
{activeView === "overall" && selectedEpic
|
|
106
|
-
? ` ·
|
|
87
|
+
? ` · expanded ${selectedEpic.id}`
|
|
107
88
|
: activeView === "overall" && selectedEpicId
|
|
108
89
|
? ` · epic ${selectedEpicId} not in list`
|
|
109
|
-
: activeView === "overall"
|
|
110
|
-
? " · select an epic to view work items"
|
|
111
|
-
: ""}
|
|
112
|
-
{activeView === "overall" && selectedStory
|
|
113
|
-
? ` · story ${selectedStory.id}`
|
|
114
|
-
: activeView === "overall" && selectedStoryId
|
|
115
|
-
? ` · story ${selectedStoryId} not in list`
|
|
116
90
|
: ""}
|
|
91
|
+
{activeView === "overall" && selectedStoryId
|
|
92
|
+
? ` · story ${selectedStoryId}`
|
|
93
|
+
: ""}
|
|
117
94
|
</p>
|
|
118
95
|
</div>
|
|
119
96
|
<ListViewSwitcher
|
|
120
97
|
activeView={activeView}
|
|
121
98
|
selectedEpicId={selectedEpicId}
|
|
122
|
-
selectedStoryId={
|
|
99
|
+
selectedStoryId={selectedStoryId}
|
|
100
|
+
hasChangelog={hasChangelog}
|
|
123
101
|
/>
|
|
124
102
|
</div>
|
|
125
103
|
|
|
@@ -127,63 +105,16 @@ export function StaticBoardApp({ snapshot }: { snapshot: BoardSnapshot }) {
|
|
|
127
105
|
|
|
128
106
|
{activeView === "overall" ? (
|
|
129
107
|
<>
|
|
130
|
-
<
|
|
131
|
-
|
|
108
|
+
<PointsHeatmap samples={countableCompletions} />
|
|
109
|
+
<OverallTreeList
|
|
110
|
+
list={list}
|
|
111
|
+
workItemsByEpic={snapshot.workItemsByEpic}
|
|
112
|
+
itemsByStory={snapshot.itemsByStory}
|
|
132
113
|
selectedEpicId={selectedEpicId}
|
|
114
|
+
selectedStoryId={selectedStoryId}
|
|
133
115
|
countableCompletions={countableCompletions}
|
|
134
116
|
initialHideCompleted={hideCompletedPrefs.epics}
|
|
135
117
|
/>
|
|
136
|
-
|
|
137
|
-
{epicWorkItems ? (
|
|
138
|
-
<div className="flex flex-col gap-3">
|
|
139
|
-
<div className="flex items-baseline justify-between gap-3">
|
|
140
|
-
<h2 className="font-head text-2xl tracking-tight">
|
|
141
|
-
Work items for{" "}
|
|
142
|
-
{epicWorkItems.epicTitle ?? epicWorkItems.epicId}
|
|
143
|
-
</h2>
|
|
144
|
-
<Link
|
|
145
|
-
href={boardHref({
|
|
146
|
-
view: "overall",
|
|
147
|
-
item: selectedItemId,
|
|
148
|
-
})}
|
|
149
|
-
className="text-sm text-muted-foreground underline-offset-2 hover:underline"
|
|
150
|
-
>
|
|
151
|
-
Clear selection
|
|
152
|
-
</Link>
|
|
153
|
-
</div>
|
|
154
|
-
<OverallWorkItemsList
|
|
155
|
-
list={epicWorkItems}
|
|
156
|
-
selectedStoryId={selectedStory?.id ?? null}
|
|
157
|
-
countableCompletions={countableCompletions}
|
|
158
|
-
initialHideCompleted={hideCompletedPrefs.overallWorkItems}
|
|
159
|
-
/>
|
|
160
|
-
</div>
|
|
161
|
-
) : null}
|
|
162
|
-
|
|
163
|
-
{itemList && selectedStory ? (
|
|
164
|
-
<div className="flex flex-col gap-3">
|
|
165
|
-
<div className="flex items-baseline justify-between gap-3">
|
|
166
|
-
<h2 className="font-head text-2xl tracking-tight">
|
|
167
|
-
Sub Tasks for {itemList.storyTitle ?? itemList.storyId}
|
|
168
|
-
</h2>
|
|
169
|
-
<Link
|
|
170
|
-
href={boardHref({
|
|
171
|
-
view: "overall",
|
|
172
|
-
epic: itemList.epicId,
|
|
173
|
-
item: selectedItemId,
|
|
174
|
-
})}
|
|
175
|
-
className="text-sm text-muted-foreground underline-offset-2 hover:underline"
|
|
176
|
-
>
|
|
177
|
-
Clear story
|
|
178
|
-
</Link>
|
|
179
|
-
</div>
|
|
180
|
-
<TaskList
|
|
181
|
-
list={itemList}
|
|
182
|
-
countableCompletions={countableCompletions}
|
|
183
|
-
initialHideCompleted={hideCompletedPrefs.tasks}
|
|
184
|
-
/>
|
|
185
|
-
</div>
|
|
186
|
-
) : null}
|
|
187
118
|
</>
|
|
188
119
|
) : null}
|
|
189
120
|
|
|
@@ -194,6 +125,10 @@ export function StaticBoardApp({ snapshot }: { snapshot: BoardSnapshot }) {
|
|
|
194
125
|
initialHideCompleted={hideCompletedPrefs.workItems}
|
|
195
126
|
/>
|
|
196
127
|
) : null}
|
|
128
|
+
|
|
129
|
+
{activeView === "changelog" && changelogMarkdown ? (
|
|
130
|
+
<ChangelogPanel markdown={changelogMarkdown} />
|
|
131
|
+
) : null}
|
|
197
132
|
</div>
|
|
198
133
|
</div>
|
|
199
134
|
</WorkItemSheetProvider>
|
|
@@ -9,6 +9,8 @@ export function statusBadgeClass(status: string): string {
|
|
|
9
9
|
switch (status) {
|
|
10
10
|
case "done":
|
|
11
11
|
return "border-black bg-[var(--chart-4)] text-black"
|
|
12
|
+
case "shelved":
|
|
13
|
+
return "border-black border-dashed bg-[var(--chart-2)]/45 text-foreground"
|
|
12
14
|
case "in_progress":
|
|
13
15
|
return "border-black bg-[var(--chart-1)] text-black"
|
|
14
16
|
case "blocked":
|
|
@@ -22,6 +24,12 @@ export function statusBadgeClass(status: string): string {
|
|
|
22
24
|
}
|
|
23
25
|
}
|
|
24
26
|
|
|
27
|
+
export function statusRowClass(status: string): string | undefined {
|
|
28
|
+
return status.trim().toLowerCase() === "shelved"
|
|
29
|
+
? "bg-[var(--chart-2)]/10 text-muted-foreground"
|
|
30
|
+
: undefined
|
|
31
|
+
}
|
|
32
|
+
|
|
25
33
|
export function typeBadgeClass(type: string): string {
|
|
26
34
|
if (type === "bug") {
|
|
27
35
|
return "border-black bg-destructive/15 text-destructive"
|
|
@@ -27,6 +27,7 @@ import { ChildProgressBar } from "@/components/board/child-progress-bar"
|
|
|
27
27
|
import { ListFiltersBar } from "@/components/board/list-filters-bar"
|
|
28
28
|
import { ListPagination } from "@/components/board/list-pagination"
|
|
29
29
|
import { SortableTableHead } from "@/components/board/sortable-table-head"
|
|
30
|
+
import { statusRowClass } from "@/components/board/status-badge"
|
|
30
31
|
import { TimeframeFilter } from "@/components/board/timeframe-filter"
|
|
31
32
|
import { ViewWorkItemButton } from "@/components/board/work-item-sheet"
|
|
32
33
|
import { AttributionAvatarGroup } from "@/components/board/attribution-avatars"
|
|
@@ -250,6 +251,7 @@ export function StoryList({
|
|
|
250
251
|
key={`${project.id}:${story.id}:${story.filePath}`}
|
|
251
252
|
data-state={selected ? "selected" : undefined}
|
|
252
253
|
className={cn(
|
|
254
|
+
statusRowClass(story.status),
|
|
253
255
|
selected && "bg-muted/60",
|
|
254
256
|
"hover:bg-muted/40"
|
|
255
257
|
)}
|
|
@@ -18,7 +18,10 @@ import {
|
|
|
18
18
|
CardHeader,
|
|
19
19
|
CardTitle,
|
|
20
20
|
} from "@/components/ui/card"
|
|
21
|
-
import {
|
|
21
|
+
import {
|
|
22
|
+
statusRowClass,
|
|
23
|
+
TypeBadge,
|
|
24
|
+
} from "@/components/board/status-badge"
|
|
22
25
|
import {
|
|
23
26
|
IdCreatedTooltip,
|
|
24
27
|
StatusWithSolvedTooltip,
|
|
@@ -244,6 +247,7 @@ export function TaskList({
|
|
|
244
247
|
{pageRows.map((item) => (
|
|
245
248
|
<TableRow
|
|
246
249
|
key={`${project.id}:${item.id}:${item.filePath}`}
|
|
250
|
+
className={statusRowClass(item.status)}
|
|
247
251
|
>
|
|
248
252
|
<TableCell className="w-10 pr-0">
|
|
249
253
|
<ViewWorkItemButton
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import type { ReactNode } from "react"
|
|
4
4
|
|
|
5
|
-
import { formatActualDuration
|
|
5
|
+
import { formatActualDuration } from "@/lib/format-duration"
|
|
6
6
|
import { formatTaskmarkDate } from "@/lib/format-date"
|
|
7
7
|
import type {
|
|
8
8
|
ChecklistItem,
|
|
@@ -10,12 +10,19 @@ import type {
|
|
|
10
10
|
EpicDetail,
|
|
11
11
|
ItemDetail,
|
|
12
12
|
PromptFeedbackRow,
|
|
13
|
+
RowWorkItemRef,
|
|
13
14
|
StoryDetail,
|
|
14
15
|
WorkItemDetail,
|
|
15
16
|
WorkItemMeta,
|
|
16
17
|
WorkLogRow,
|
|
17
18
|
} from "@/lib/taskmark/detail-types"
|
|
18
|
-
import {
|
|
19
|
+
import {
|
|
20
|
+
StatusBadge,
|
|
21
|
+
TypeBadge,
|
|
22
|
+
PriorityBadge,
|
|
23
|
+
typeBadgeClass,
|
|
24
|
+
} from "@/components/board/status-badge"
|
|
25
|
+
import { cn } from "@/lib/utils"
|
|
19
26
|
import { AttributionPeopleList } from "@/components/board/attribution-avatars"
|
|
20
27
|
import { DetailChildrenList } from "@/components/board/detail-children-list"
|
|
21
28
|
import { MarkdownContent } from "@/components/board/markdown-content"
|
|
@@ -23,6 +30,12 @@ import { useWorkItemSheet } from "@/components/board/work-item-sheet"
|
|
|
23
30
|
import { Badge } from "@/components/ui/badge"
|
|
24
31
|
import { Button } from "@/components/ui/button"
|
|
25
32
|
import { Separator } from "@/components/ui/separator"
|
|
33
|
+
import {
|
|
34
|
+
Tooltip,
|
|
35
|
+
TooltipContent,
|
|
36
|
+
TooltipProvider,
|
|
37
|
+
TooltipTrigger,
|
|
38
|
+
} from "@/components/ui/tooltip"
|
|
26
39
|
|
|
27
40
|
function RelatedItemButton({ itemId }: { itemId: string }) {
|
|
28
41
|
const { openDetailById } = useWorkItemSheet()
|
|
@@ -39,22 +52,101 @@ function RelatedItemButton({ itemId }: { itemId: string }) {
|
|
|
39
52
|
)
|
|
40
53
|
}
|
|
41
54
|
|
|
55
|
+
const MAX_VISIBLE_WORK_ITEMS = 2
|
|
56
|
+
|
|
57
|
+
function workItemKindFromId(id: string): "epic" | "story" | "task" | "bug" {
|
|
58
|
+
if (id.startsWith("B-")) return "bug"
|
|
59
|
+
if (id.startsWith("E-")) return "epic"
|
|
60
|
+
if (id.startsWith("S-")) return "story"
|
|
61
|
+
return "task"
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function WorkItemTag({
|
|
65
|
+
item,
|
|
66
|
+
onOpen,
|
|
67
|
+
}: {
|
|
68
|
+
item: RowWorkItemRef
|
|
69
|
+
onOpen: (id: string) => void
|
|
70
|
+
}) {
|
|
71
|
+
return (
|
|
72
|
+
<TooltipProvider>
|
|
73
|
+
<Tooltip>
|
|
74
|
+
<TooltipTrigger
|
|
75
|
+
render={
|
|
76
|
+
<Badge
|
|
77
|
+
variant="outline"
|
|
78
|
+
title={item.title || item.id}
|
|
79
|
+
className={cn(
|
|
80
|
+
"cursor-pointer font-mono text-xs",
|
|
81
|
+
typeBadgeClass(workItemKindFromId(item.id))
|
|
82
|
+
)}
|
|
83
|
+
render={<button type="button" onClick={() => onOpen(item.id)} />}
|
|
84
|
+
/>
|
|
85
|
+
}
|
|
86
|
+
>
|
|
87
|
+
{item.id}
|
|
88
|
+
</TooltipTrigger>
|
|
89
|
+
<TooltipContent>{item.title || item.id}</TooltipContent>
|
|
90
|
+
</Tooltip>
|
|
91
|
+
</TooltipProvider>
|
|
92
|
+
)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function WorkItemsCell({ items }: { items?: RowWorkItemRef[] }) {
|
|
96
|
+
const { openDetailById } = useWorkItemSheet()
|
|
97
|
+
if (!items || items.length === 0) return "—"
|
|
98
|
+
|
|
99
|
+
const visible = items.slice(0, MAX_VISIBLE_WORK_ITEMS)
|
|
100
|
+
const hidden = items.slice(MAX_VISIBLE_WORK_ITEMS)
|
|
101
|
+
|
|
102
|
+
return (
|
|
103
|
+
<span className="inline-flex flex-nowrap items-center gap-1 align-middle">
|
|
104
|
+
{visible.map((item) => (
|
|
105
|
+
<WorkItemTag key={item.id} item={item} onOpen={openDetailById} />
|
|
106
|
+
))}
|
|
107
|
+
{hidden.length > 0 ? (
|
|
108
|
+
<TooltipProvider>
|
|
109
|
+
<Tooltip>
|
|
110
|
+
<TooltipTrigger
|
|
111
|
+
render={
|
|
112
|
+
<Badge
|
|
113
|
+
variant="outline"
|
|
114
|
+
className="cursor-default font-mono text-xs"
|
|
115
|
+
title={hidden.map((item) => item.id).join(", ")}
|
|
116
|
+
/>
|
|
117
|
+
}
|
|
118
|
+
>
|
|
119
|
+
+{hidden.length}
|
|
120
|
+
</TooltipTrigger>
|
|
121
|
+
<TooltipContent>
|
|
122
|
+
<ul className="flex flex-col gap-0.5 text-left">
|
|
123
|
+
{hidden.map((item) => (
|
|
124
|
+
<li key={item.id}>
|
|
125
|
+
<span className="font-mono">{item.id}</span>
|
|
126
|
+
{item.title ? ` — ${item.title}` : null}
|
|
127
|
+
</li>
|
|
128
|
+
))}
|
|
129
|
+
</ul>
|
|
130
|
+
</TooltipContent>
|
|
131
|
+
</Tooltip>
|
|
132
|
+
</TooltipProvider>
|
|
133
|
+
) : null}
|
|
134
|
+
</span>
|
|
135
|
+
)
|
|
136
|
+
}
|
|
137
|
+
|
|
42
138
|
function MetaGrid({ detail }: { detail: WorkItemMeta }) {
|
|
43
139
|
const rows: { label: string; value: ReactNode }[] = [
|
|
44
140
|
{ label: "Status", value: <StatusBadge status={detail.status} /> },
|
|
45
141
|
{ label: "Priority", value: <PriorityBadge priority={detail.priority} /> },
|
|
46
142
|
{
|
|
47
143
|
label: "Size",
|
|
48
|
-
value: detail.
|
|
144
|
+
value: detail.size || "—",
|
|
49
145
|
},
|
|
50
146
|
{
|
|
51
147
|
label: "Points",
|
|
52
148
|
value: detail.points == null ? "—" : String(detail.points),
|
|
53
149
|
},
|
|
54
|
-
{
|
|
55
|
-
label: "Est",
|
|
56
|
-
value: formatDurationMinutes(detail.estimateMinutes),
|
|
57
|
-
},
|
|
58
150
|
{
|
|
59
151
|
label: "Actual",
|
|
60
152
|
value: formatActualDuration(detail.actualMs, detail.actualMinutes),
|
|
@@ -67,7 +159,6 @@ function MetaGrid({ detail }: { detail: WorkItemMeta }) {
|
|
|
67
159
|
label: "Epic",
|
|
68
160
|
value: detail.epic ? <RelatedItemButton itemId={detail.epic} /> : "—",
|
|
69
161
|
},
|
|
70
|
-
{ label: "Owner", value: detail.owner || "—" },
|
|
71
162
|
{ label: "Created", value: formatTaskmarkDate(detail.created) },
|
|
72
163
|
{ label: "Updated", value: formatTaskmarkDate(detail.updated) },
|
|
73
164
|
{ label: "Started", value: formatTaskmarkDate(detail.startedAt) },
|
|
@@ -162,6 +253,7 @@ function CommitsTable({ rows }: { rows: CommitRow[] }) {
|
|
|
162
253
|
if (rows.length === 0) {
|
|
163
254
|
return <p className="text-sm text-muted-foreground">No commits logged.</p>
|
|
164
255
|
}
|
|
256
|
+
const showWorkItems = rows.some((row) => (row.workItems?.length ?? 0) > 0)
|
|
165
257
|
return (
|
|
166
258
|
<div className="w-full overflow-x-auto rounded border-2 border-border">
|
|
167
259
|
<table className="w-full text-left text-xs">
|
|
@@ -172,6 +264,7 @@ function CommitsTable({ rows }: { rows: CommitRow[] }) {
|
|
|
172
264
|
<th className="px-2 py-1.5">Date</th>
|
|
173
265
|
<th className="px-2 py-1.5">Author</th>
|
|
174
266
|
<th className="px-2 py-1.5">Message</th>
|
|
267
|
+
{showWorkItems ? <th className="px-2 py-1.5">Work items</th> : null}
|
|
175
268
|
</tr>
|
|
176
269
|
</thead>
|
|
177
270
|
<tbody>
|
|
@@ -184,6 +277,11 @@ function CommitsTable({ rows }: { rows: CommitRow[] }) {
|
|
|
184
277
|
</td>
|
|
185
278
|
<td className="px-2 py-1.5">{row.author || "—"}</td>
|
|
186
279
|
<td className="px-2 py-1.5">{row.message || "—"}</td>
|
|
280
|
+
{showWorkItems ? (
|
|
281
|
+
<td className="px-2 py-1.5 whitespace-nowrap">
|
|
282
|
+
<WorkItemsCell items={row.workItems} />
|
|
283
|
+
</td>
|
|
284
|
+
) : null}
|
|
187
285
|
</tr>
|
|
188
286
|
))}
|
|
189
287
|
</tbody>
|
|
@@ -196,6 +294,7 @@ function WorkLogTable({ rows }: { rows: WorkLogRow[] }) {
|
|
|
196
294
|
if (rows.length === 0) {
|
|
197
295
|
return <p className="text-sm text-muted-foreground">No work sessions logged.</p>
|
|
198
296
|
}
|
|
297
|
+
const showWorkItems = rows.some((row) => (row.workItems?.length ?? 0) > 0)
|
|
199
298
|
return (
|
|
200
299
|
<div className="w-full overflow-x-auto rounded border-2 border-border">
|
|
201
300
|
<table className="w-full text-left text-xs">
|
|
@@ -206,6 +305,7 @@ function WorkLogTable({ rows }: { rows: WorkLogRow[] }) {
|
|
|
206
305
|
<th className="px-2 py-1.5">Started</th>
|
|
207
306
|
<th className="px-2 py-1.5">Ended</th>
|
|
208
307
|
<th className="px-2 py-1.5">Summary</th>
|
|
308
|
+
{showWorkItems ? <th className="px-2 py-1.5">Work items</th> : null}
|
|
209
309
|
</tr>
|
|
210
310
|
</thead>
|
|
211
311
|
<tbody>
|
|
@@ -220,6 +320,11 @@ function WorkLogTable({ rows }: { rows: WorkLogRow[] }) {
|
|
|
220
320
|
{formatTaskmarkDate(row.ended)}
|
|
221
321
|
</td>
|
|
222
322
|
<td className="px-2 py-1.5">{row.summary || "—"}</td>
|
|
323
|
+
{showWorkItems ? (
|
|
324
|
+
<td className="px-2 py-1.5 whitespace-nowrap">
|
|
325
|
+
<WorkItemsCell items={row.workItems} />
|
|
326
|
+
</td>
|
|
327
|
+
) : null}
|
|
223
328
|
</tr>
|
|
224
329
|
))}
|
|
225
330
|
</tbody>
|
|
@@ -232,6 +337,7 @@ function PromptTable({ rows }: { rows: PromptFeedbackRow[] }) {
|
|
|
232
337
|
if (rows.length === 0) {
|
|
233
338
|
return <p className="text-sm text-muted-foreground">No prompts logged.</p>
|
|
234
339
|
}
|
|
340
|
+
const showWorkItems = rows.some((row) => (row.workItems?.length ?? 0) > 0)
|
|
235
341
|
return (
|
|
236
342
|
<div className="w-full overflow-x-auto rounded border-2 border-border">
|
|
237
343
|
<table className="w-full text-left text-xs">
|
|
@@ -242,6 +348,7 @@ function PromptTable({ rows }: { rows: PromptFeedbackRow[] }) {
|
|
|
242
348
|
<th className="px-2 py-1.5">Kind</th>
|
|
243
349
|
<th className="px-2 py-1.5">Author</th>
|
|
244
350
|
<th className="px-2 py-1.5">Summary</th>
|
|
351
|
+
{showWorkItems ? <th className="px-2 py-1.5">Work items</th> : null}
|
|
245
352
|
</tr>
|
|
246
353
|
</thead>
|
|
247
354
|
<tbody>
|
|
@@ -254,6 +361,11 @@ function PromptTable({ rows }: { rows: PromptFeedbackRow[] }) {
|
|
|
254
361
|
<td className="px-2 py-1.5">{row.kind || "—"}</td>
|
|
255
362
|
<td className="px-2 py-1.5">{row.author || "—"}</td>
|
|
256
363
|
<td className="px-2 py-1.5">{row.summary || "—"}</td>
|
|
364
|
+
{showWorkItems ? (
|
|
365
|
+
<td className="px-2 py-1.5 whitespace-nowrap">
|
|
366
|
+
<WorkItemsCell items={row.workItems} />
|
|
367
|
+
</td>
|
|
368
|
+
) : null}
|
|
257
369
|
</tr>
|
|
258
370
|
))}
|
|
259
371
|
</tbody>
|
|
@@ -285,6 +397,9 @@ function EpicDetailBody({ detail }: { detail: EpicDetail }) {
|
|
|
285
397
|
emptyLabel="No stories or epic-direct tasks yet."
|
|
286
398
|
/>
|
|
287
399
|
</Section>
|
|
400
|
+
<Section title="Prompt & feedback">
|
|
401
|
+
<PromptTable rows={detail.promptFeedback} />
|
|
402
|
+
</Section>
|
|
288
403
|
<Section title="Commits">
|
|
289
404
|
<CommitsTable rows={detail.commits} />
|
|
290
405
|
</Section>
|
|
@@ -22,6 +22,7 @@ import { ParentTagBadge } from "@/components/board/parent-tag-badge"
|
|
|
22
22
|
import { ChildProgressBar } from "@/components/board/child-progress-bar"
|
|
23
23
|
import {
|
|
24
24
|
PriorityBadge,
|
|
25
|
+
statusRowClass,
|
|
25
26
|
TypeBadge,
|
|
26
27
|
} from "@/components/board/status-badge"
|
|
27
28
|
import {
|
|
@@ -282,6 +283,7 @@ export function WorkItemsList({
|
|
|
282
283
|
return (
|
|
283
284
|
<TableRow
|
|
284
285
|
key={`${project.id}:${row.kind}:${row.id}:${row.filePath}`}
|
|
286
|
+
className={statusRowClass(row.status)}
|
|
285
287
|
>
|
|
286
288
|
<TableCell className="w-10 pr-0">
|
|
287
289
|
<ViewWorkItemButton
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import { ThemeProvider as NextThemesProvider, useTheme } from "next-themes"
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
parseThemePreference,
|
|
8
|
+
THEME_STORAGE_KEY,
|
|
9
|
+
writeThemeCookie,
|
|
10
|
+
} from "@/lib/theme-cookie"
|
|
11
|
+
|
|
12
|
+
type ThemeProviderProps = React.ComponentProps<typeof NextThemesProvider>
|
|
13
|
+
|
|
14
|
+
function ThemeCookieSync() {
|
|
15
|
+
const { theme } = useTheme()
|
|
16
|
+
|
|
17
|
+
React.useEffect(() => {
|
|
18
|
+
const preference = parseThemePreference(theme)
|
|
19
|
+
if (preference) writeThemeCookie(preference)
|
|
20
|
+
}, [theme])
|
|
21
|
+
|
|
22
|
+
return null
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
|
|
26
|
+
return (
|
|
27
|
+
<NextThemesProvider
|
|
28
|
+
attribute="class"
|
|
29
|
+
defaultTheme="system"
|
|
30
|
+
enableSystem
|
|
31
|
+
disableTransitionOnChange
|
|
32
|
+
storageKey={THEME_STORAGE_KEY}
|
|
33
|
+
{...props}
|
|
34
|
+
>
|
|
35
|
+
<ThemeCookieSync />
|
|
36
|
+
{children}
|
|
37
|
+
</NextThemesProvider>
|
|
38
|
+
)
|
|
39
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import { MoonIcon, SunIcon } from "lucide-react"
|
|
4
|
+
import { useTheme } from "next-themes"
|
|
5
|
+
|
|
6
|
+
import { Button } from "@/components/ui/button"
|
|
7
|
+
|
|
8
|
+
export function ThemeToggle() {
|
|
9
|
+
const { setTheme } = useTheme()
|
|
10
|
+
|
|
11
|
+
return (
|
|
12
|
+
<Button
|
|
13
|
+
type="button"
|
|
14
|
+
variant="outline"
|
|
15
|
+
size="icon"
|
|
16
|
+
aria-label="Switch between light and dark theme"
|
|
17
|
+
title="Switch between light and dark theme"
|
|
18
|
+
onClick={() => {
|
|
19
|
+
const isDark = document.documentElement.classList.contains("dark")
|
|
20
|
+
setTheme(isDark ? "light" : "dark")
|
|
21
|
+
}}
|
|
22
|
+
>
|
|
23
|
+
<SunIcon className="hidden size-4 dark:block" />
|
|
24
|
+
<MoonIcon className="size-4 dark:hidden" />
|
|
25
|
+
</Button>
|
|
26
|
+
)
|
|
27
|
+
}
|
package/components/ui/sonner.tsx
CHANGED
|
@@ -11,11 +11,11 @@ import { useTheme } from "next-themes"
|
|
|
11
11
|
import { Toaster as Sonner, type ToasterProps } from "sonner"
|
|
12
12
|
|
|
13
13
|
const Toaster = ({ ...props }: ToasterProps) => {
|
|
14
|
-
const {
|
|
14
|
+
const { resolvedTheme } = useTheme()
|
|
15
15
|
|
|
16
16
|
return (
|
|
17
17
|
<Sonner
|
|
18
|
-
theme={
|
|
18
|
+
theme={(resolvedTheme === "dark" ? "dark" : "light") as ToasterProps["theme"]}
|
|
19
19
|
className="toaster group"
|
|
20
20
|
icons={{
|
|
21
21
|
success: <CircleCheckIcon className="size-4" />,
|
package/dist/prod-next/BUILD_ID
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
EI1zQaa_kQ7DqUSE4o3Qo
|