diff --git a/.playwright-mcp/console-2026-08-05T23-55-29-175Z.log b/.playwright-mcp/console-2026-08-05T23-55-29-175Z.log new file mode 100644 index 0000000..bae0bff --- /dev/null +++ b/.playwright-mcp/console-2026-08-05T23-55-29-175Z.log @@ -0,0 +1,79 @@ +[ 544ms] [INFO] %cDownload the React DevTools for a better development experience: https://react.dev/link/react-devtools font-weight:bold @ webpack-internal:///(app-pages-browser)/./node_modules/next/dist/next-devtools/shared/forward-logs-shared.js:27 +[ 599ms] [LOG] [HMR] connected @ webpack-internal:///(app-pages-browser)/./node_modules/next/dist/next-devtools/shared/forward-logs-shared.js:27 +[ 1044ms] [LOG] SW registered: http://localhost:4001/ @ webpack-internal:///(app-pages-browser)/./node_modules/next/dist/next-devtools/shared/forward-logs-shared.js:27 +[ 1684ms] [LOG] [Fast Refresh] rebuilding @ webpack-internal:///(app-pages-browser)/./node_modules/next/dist/next-devtools/shared/forward-logs-shared.js:27 +[ 1765ms] [LOG] [Fast Refresh] done in 81ms @ webpack-internal:///(app-pages-browser)/./node_modules/next/dist/next-devtools/shared/forward-logs-shared.js:27 +[ 7254ms] [INFO] %cDownload the React DevTools for a better development experience: https://react.dev/link/react-devtools font-weight:bold @ webpack-internal:///(app-pages-browser)/./node_modules/next/dist/next-devtools/shared/forward-logs-shared.js:27 +[ 7298ms] [LOG] [HMR] connected @ webpack-internal:///(app-pages-browser)/./node_modules/next/dist/next-devtools/shared/forward-logs-shared.js:27 +[ 7314ms] [LOG] [Fast Refresh] rebuilding @ webpack-internal:///(app-pages-browser)/./node_modules/next/dist/next-devtools/shared/forward-logs-shared.js:27 +[ 7659ms] [LOG] SW registered: http://localhost:4001/ @ webpack-internal:///(app-pages-browser)/./node_modules/next/dist/next-devtools/shared/forward-logs-shared.js:27 +[ 8318ms] [LOG] [Fast Refresh] rebuilding @ webpack-internal:///(app-pages-browser)/./node_modules/next/dist/next-devtools/shared/forward-logs-shared.js:27 +[ 8381ms] [LOG] [Fast Refresh] done in 63ms @ webpack-internal:///(app-pages-browser)/./node_modules/next/dist/next-devtools/shared/forward-logs-shared.js:27 +[ 8791ms] [LOG] [Fast Refresh] rebuilding @ webpack-internal:///(app-pages-browser)/./node_modules/next/dist/next-devtools/shared/forward-logs-shared.js:27 +[ 8815ms] [LOG] [Fast Refresh] done in 25ms @ webpack-internal:///(app-pages-browser)/./node_modules/next/dist/next-devtools/shared/forward-logs-shared.js:27 +[ 8840ms] [LOG] [Fast Refresh] rebuilding @ webpack-internal:///(app-pages-browser)/./node_modules/next/dist/next-devtools/shared/forward-logs-shared.js:27 +[ 8881ms] [LOG] [Fast Refresh] done in 42ms @ webpack-internal:///(app-pages-browser)/./node_modules/next/dist/next-devtools/shared/forward-logs-shared.js:27 +[ 8891ms] [LOG] [Fast Refresh] rebuilding @ webpack-internal:///(app-pages-browser)/./node_modules/next/dist/next-devtools/shared/forward-logs-shared.js:27 +[ 9165ms] [LOG] [Fast Refresh] done in 274ms @ webpack-internal:///(app-pages-browser)/./node_modules/next/dist/next-devtools/shared/forward-logs-shared.js:27 +[ 9209ms] [LOG] [Fast Refresh] rebuilding @ webpack-internal:///(app-pages-browser)/./node_modules/next/dist/next-devtools/shared/forward-logs-shared.js:27 +[ 9236ms] [LOG] [Fast Refresh] done in 26ms @ webpack-internal:///(app-pages-browser)/./node_modules/next/dist/next-devtools/shared/forward-logs-shared.js:27 +[ 9683ms] [LOG] [Fast Refresh] done in 474ms @ webpack-internal:///(app-pages-browser)/./node_modules/next/dist/next-devtools/shared/forward-logs-shared.js:27 +[ 9735ms] [LOG] [Fast Refresh] rebuilding @ webpack-internal:///(app-pages-browser)/./node_modules/next/dist/next-devtools/shared/forward-logs-shared.js:27 +[ 10844ms] Invalid or unexpected token +[ 12299ms] [INFO] %cDownload the React DevTools for a better development experience: https://react.dev/link/react-devtools font-weight:bold @ webpack-internal:///(app-pages-browser)/./node_modules/next/dist/next-devtools/shared/forward-logs-shared.js:27 +[ 12333ms] [LOG] [HMR] connected @ webpack-internal:///(app-pages-browser)/./node_modules/next/dist/next-devtools/shared/forward-logs-shared.js:27 +[ 12574ms] [LOG] [Fast Refresh] rebuilding @ webpack-internal:///(app-pages-browser)/./node_modules/next/dist/next-devtools/shared/forward-logs-shared.js:27 +[ 12980ms] [ERROR] Can't perform a React state update on a component that hasn't mounted yet. This indicates that you have a side-effect in your render function that asynchronously tries to update the component. Move this work to useEffect instead. @ webpack-internal:///(app-pages-browser)/./node_modules/next/dist/next-devtools/userspace/app/errors/intercept-console-error.js:57 +[ 12990ms] [LOG] [Fast Refresh] rebuilding @ webpack-internal:///(app-pages-browser)/./node_modules/next/dist/next-devtools/shared/forward-logs-shared.js:27 +[ 13867ms] [LOG] [Fast Refresh] done in 876ms @ webpack-internal:///(app-pages-browser)/./node_modules/next/dist/next-devtools/shared/forward-logs-shared.js:27 +[ 13980ms] [LOG] [Fast Refresh] rebuilding @ webpack-internal:///(app-pages-browser)/./node_modules/next/dist/next-devtools/shared/forward-logs-shared.js:27 +[ 14031ms] [LOG] [Fast Refresh] done in 51ms @ webpack-internal:///(app-pages-browser)/./node_modules/next/dist/next-devtools/shared/forward-logs-shared.js:27 +[ 14194ms] [LOG] [Fast Refresh] rebuilding @ webpack-internal:///(app-pages-browser)/./node_modules/next/dist/next-devtools/shared/forward-logs-shared.js:27 +[ 14235ms] [LOG] [Fast Refresh] done in 42ms @ webpack-internal:///(app-pages-browser)/./node_modules/next/dist/next-devtools/shared/forward-logs-shared.js:27 +[ 14397ms] [LOG] [Fast Refresh] rebuilding @ webpack-internal:///(app-pages-browser)/./node_modules/next/dist/next-devtools/shared/forward-logs-shared.js:27 +[ 14470ms] [LOG] [Fast Refresh] done in 72ms @ webpack-internal:///(app-pages-browser)/./node_modules/next/dist/next-devtools/shared/forward-logs-shared.js:27 +[ 14682ms] [LOG] [Fast Refresh] rebuilding @ webpack-internal:///(app-pages-browser)/./node_modules/next/dist/next-devtools/shared/forward-logs-shared.js:27 +[ 14853ms] [LOG] [Fast Refresh] done in 170ms @ webpack-internal:///(app-pages-browser)/./node_modules/next/dist/next-devtools/shared/forward-logs-shared.js:27 +[ 15173ms] [LOG] [Fast Refresh] rebuilding @ webpack-internal:///(app-pages-browser)/./node_modules/next/dist/next-devtools/shared/forward-logs-shared.js:27 +[ 15213ms] [LOG] [Fast Refresh] done in 40ms @ webpack-internal:///(app-pages-browser)/./node_modules/next/dist/next-devtools/shared/forward-logs-shared.js:27 +[ 15380ms] [LOG] [Fast Refresh] rebuilding @ webpack-internal:///(app-pages-browser)/./node_modules/next/dist/next-devtools/shared/forward-logs-shared.js:27 +[ 15411ms] [LOG] [Fast Refresh] done in 31ms @ webpack-internal:///(app-pages-browser)/./node_modules/next/dist/next-devtools/shared/forward-logs-shared.js:27 +[ 15550ms] [LOG] [Fast Refresh] rebuilding @ webpack-internal:///(app-pages-browser)/./node_modules/next/dist/next-devtools/shared/forward-logs-shared.js:27 +[ 16518ms] [LOG] [Fast Refresh] done in 969ms @ webpack-internal:///(app-pages-browser)/./node_modules/next/dist/next-devtools/shared/forward-logs-shared.js:27 +[ 16720ms] [LOG] [Fast Refresh] rebuilding @ webpack-internal:///(app-pages-browser)/./node_modules/next/dist/next-devtools/shared/forward-logs-shared.js:27 +[ 16803ms] [LOG] [Fast Refresh] done in 84ms @ webpack-internal:///(app-pages-browser)/./node_modules/next/dist/next-devtools/shared/forward-logs-shared.js:27 +[ 16956ms] [LOG] [Fast Refresh] rebuilding @ webpack-internal:///(app-pages-browser)/./node_modules/next/dist/next-devtools/shared/forward-logs-shared.js:27 +[ 17950ms] [LOG] [Fast Refresh] done in 994ms @ webpack-internal:///(app-pages-browser)/./node_modules/next/dist/next-devtools/shared/forward-logs-shared.js:27 +[ 18202ms] [LOG] [Fast Refresh] rebuilding @ webpack-internal:///(app-pages-browser)/./node_modules/next/dist/next-devtools/shared/forward-logs-shared.js:27 +[ 19219ms] [LOG] [Fast Refresh] done in 1018ms @ webpack-internal:///(app-pages-browser)/./node_modules/next/dist/next-devtools/shared/forward-logs-shared.js:27 +[ 23326ms] [INFO] %cDownload the React DevTools for a better development experience: https://react.dev/link/react-devtools font-weight:bold @ webpack-internal:///(app-pages-browser)/./node_modules/next/dist/next-devtools/shared/forward-logs-shared.js:27 +[ 23475ms] [LOG] [HMR] connected @ webpack-internal:///(app-pages-browser)/./node_modules/next/dist/next-devtools/shared/forward-logs-shared.js:27 +[ 23499ms] [LOG] [Fast Refresh] rebuilding @ webpack-internal:///(app-pages-browser)/./node_modules/next/dist/next-devtools/shared/forward-logs-shared.js:27 +[ 23517ms] [LOG] [Fast Refresh] rebuilding @ webpack-internal:///(app-pages-browser)/./node_modules/next/dist/next-devtools/shared/forward-logs-shared.js:27 +[ 23740ms] [LOG] SW registered: http://localhost:4001/ @ webpack-internal:///(app-pages-browser)/./node_modules/next/dist/next-devtools/shared/forward-logs-shared.js:27 +[ 24424ms] [LOG] [Fast Refresh] done in 907ms @ webpack-internal:///(app-pages-browser)/./node_modules/next/dist/next-devtools/shared/forward-logs-shared.js:27 +[ 24503ms] [LOG] [Fast Refresh] done in 986ms @ webpack-internal:///(app-pages-browser)/./node_modules/next/dist/next-devtools/shared/forward-logs-shared.js:27 +[ 26965ms] [LOG] [Fast Refresh] rebuilding @ webpack-internal:///(app-pages-browser)/./node_modules/next/dist/next-devtools/shared/forward-logs-shared.js:27 +[ 27025ms] [LOG] [Fast Refresh] rebuilding @ webpack-internal:///(app-pages-browser)/./node_modules/next/dist/next-devtools/shared/forward-logs-shared.js:27 +[ 27070ms] [LOG] [Fast Refresh] done in 45ms @ webpack-internal:///(app-pages-browser)/./node_modules/next/dist/next-devtools/shared/forward-logs-shared.js:27 +[ 27081ms] [LOG] [Fast Refresh] done in 57ms @ webpack-internal:///(app-pages-browser)/./node_modules/next/dist/next-devtools/shared/forward-logs-shared.js:27 +[ 27081ms] [LOG] [Fast Refresh] rebuilding @ webpack-internal:///(app-pages-browser)/./node_modules/next/dist/next-devtools/shared/forward-logs-shared.js:27 +[ 28248ms] [LOG] [Fast Refresh] done in 1166ms @ webpack-internal:///(app-pages-browser)/./node_modules/next/dist/next-devtools/shared/forward-logs-shared.js:27 +[ 28322ms] [LOG] [Fast Refresh] rebuilding @ webpack-internal:///(app-pages-browser)/./node_modules/next/dist/next-devtools/shared/forward-logs-shared.js:27 +[ 28371ms] [LOG] [Fast Refresh] done in 49ms @ webpack-internal:///(app-pages-browser)/./node_modules/next/dist/next-devtools/shared/forward-logs-shared.js:27 +[ 30632ms] [LOG] [Fast Refresh] done in 2311ms @ webpack-internal:///(app-pages-browser)/./node_modules/next/dist/next-devtools/shared/forward-logs-shared.js:27 +[ 30657ms] [LOG] [Fast Refresh] rebuilding @ webpack-internal:///(app-pages-browser)/./node_modules/next/dist/next-devtools/shared/forward-logs-shared.js:27 +[ 31856ms] [LOG] [Fast Refresh] rebuilding @ webpack-internal:///(app-pages-browser)/./node_modules/next/dist/next-devtools/shared/forward-logs-shared.js:27 +[ 32591ms] [LOG] [Fast Refresh] done in 736ms @ webpack-internal:///(app-pages-browser)/./node_modules/next/dist/next-devtools/shared/forward-logs-shared.js:27 +[ 35593ms] [WARNING] The resource http://localhost:4001/_next/static/media/4de1fea1a954a5b6-s.p.woff2?v=1785974148275 was preloaded using link preload but not used within a few seconds from the window's load event. Please make sure it has an appropriate `as` value and it is preloaded intentionally. @ http://localhost:4001/dashboard:0 +[ 35593ms] [WARNING] The resource http://localhost:4001/_next/static/css/app/layout.css?v=1785974159605 was preloaded using link preload but not used within a few seconds from the window's load event. Please make sure it has an appropriate `as` value and it is preloaded intentionally. @ http://localhost:4001/dashboard:0 +[ 35593ms] [WARNING] The resource http://localhost:4001/_next/static/media/22a5144ee8d83bca-s.p.woff2?v=1785974148275 was preloaded using link preload but not used within a few seconds from the window's load event. Please make sure it has an appropriate `as` value and it is preloaded intentionally. @ http://localhost:4001/dashboard:0 +[ 35593ms] [WARNING] The resource http://localhost:4001/_next/static/media/36966cca54120369-s.p.woff2?v=1785974148275 was preloaded using link preload but not used within a few seconds from the window's load event. Please make sure it has an appropriate `as` value and it is preloaded intentionally. @ http://localhost:4001/dashboard:0 +[ 35593ms] [WARNING] The resource http://localhost:4001/_next/static/media/eaead17c7dbfcd5d-s.p.woff2?v=1785974148275 was preloaded using link preload but not used within a few seconds from the window's load event. Please make sure it has an appropriate `as` value and it is preloaded intentionally. @ http://localhost:4001/dashboard:0 +[ 35593ms] [WARNING] The resource http://localhost:4001/_next/static/media/ee40bb094c99a29a-s.p.woff2?v=1785974148275 was preloaded using link preload but not used within a few seconds from the window's load event. Please make sure it has an appropriate `as` value and it is preloaded intentionally. @ http://localhost:4001/dashboard:0 +[ 35593ms] [WARNING] The resource http://localhost:4001/_next/static/media/558ca1a6aa3cb55e-s.p.woff2?v=1785974148275 was preloaded using link preload but not used within a few seconds from the window's load event. Please make sure it has an appropriate `as` value and it is preloaded intentionally. @ http://localhost:4001/dashboard:0 +[ 35594ms] [WARNING] The resource http://localhost:4001/_next/static/media/7b0b24f36b1a6d0b-s.p.woff2?v=1785974148275 was preloaded using link preload but not used within a few seconds from the window's load event. Please make sure it has an appropriate `as` value and it is preloaded intentionally. @ http://localhost:4001/dashboard:0 +[ 35594ms] [WARNING] The resource http://localhost:4001/_next/static/media/e4af272ccee01ff0-s.p.woff2?v=1785974148275 was preloaded using link preload but not used within a few seconds from the window's load event. Please make sure it has an appropriate `as` value and it is preloaded intentionally. @ http://localhost:4001/dashboard:0 +[ 35594ms] [WARNING] The resource http://localhost:4001/_next/static/media/7d4881bb7e1bf84d-s.p.woff2?v=1785974148275 was preloaded using link preload but not used within a few seconds from the window's load event. Please make sure it has an appropriate `as` value and it is preloaded intentionally. @ http://localhost:4001/dashboard:0 +[ 35594ms] [WARNING] The resource http://localhost:4001/_next/static/media/13971731025ec697-s.p.woff2?v=1785974148275 was preloaded using link preload but not used within a few seconds from the window's load event. Please make sure it has an appropriate `as` value and it is preloaded intentionally. @ http://localhost:4001/dashboard:0 +[ 35594ms] [WARNING] The resource http://localhost:4001/_next/static/media/6d664cce900333ee-s.p.woff2?v=1785974148275 was preloaded using link preload but not used within a few seconds from the window's load event. Please make sure it has an appropriate `as` value and it is preloaded intentionally. @ http://localhost:4001/dashboard:0 diff --git a/.playwright-mcp/console-2026-08-05T23-56-08-778Z.log b/.playwright-mcp/console-2026-08-05T23-56-08-778Z.log new file mode 100644 index 0000000..c86ae35 --- /dev/null +++ b/.playwright-mcp/console-2026-08-05T23-56-08-778Z.log @@ -0,0 +1,11 @@ +[ 5124ms] [WARNING] The resource http://localhost:4001/_next/static/media/6d664cce900333ee-s.p.woff2?v=1785974168950 was preloaded using link preload but not used within a few seconds from the window's load event. Please make sure it has an appropriate `as` value and it is preloaded intentionally. @ http://localhost:4001/quotes/ybqit9d6150vmg0:0 +[ 5124ms] [WARNING] The resource http://localhost:4001/_next/static/media/4de1fea1a954a5b6-s.p.woff2?v=1785974168950 was preloaded using link preload but not used within a few seconds from the window's load event. Please make sure it has an appropriate `as` value and it is preloaded intentionally. @ http://localhost:4001/quotes/ybqit9d6150vmg0:0 +[ 5124ms] [WARNING] The resource http://localhost:4001/_next/static/media/22a5144ee8d83bca-s.p.woff2?v=1785974168950 was preloaded using link preload but not used within a few seconds from the window's load event. Please make sure it has an appropriate `as` value and it is preloaded intentionally. @ http://localhost:4001/quotes/ybqit9d6150vmg0:0 +[ 5124ms] [WARNING] The resource http://localhost:4001/_next/static/media/36966cca54120369-s.p.woff2?v=1785974168950 was preloaded using link preload but not used within a few seconds from the window's load event. Please make sure it has an appropriate `as` value and it is preloaded intentionally. @ http://localhost:4001/quotes/ybqit9d6150vmg0:0 +[ 5124ms] [WARNING] The resource http://localhost:4001/_next/static/media/13971731025ec697-s.p.woff2?v=1785974168950 was preloaded using link preload but not used within a few seconds from the window's load event. Please make sure it has an appropriate `as` value and it is preloaded intentionally. @ http://localhost:4001/quotes/ybqit9d6150vmg0:0 +[ 5124ms] [WARNING] The resource http://localhost:4001/_next/static/media/7b0b24f36b1a6d0b-s.p.woff2?v=1785974168950 was preloaded using link preload but not used within a few seconds from the window's load event. Please make sure it has an appropriate `as` value and it is preloaded intentionally. @ http://localhost:4001/quotes/ybqit9d6150vmg0:0 +[ 5124ms] [WARNING] The resource http://localhost:4001/_next/static/media/7d4881bb7e1bf84d-s.p.woff2?v=1785974168950 was preloaded using link preload but not used within a few seconds from the window's load event. Please make sure it has an appropriate `as` value and it is preloaded intentionally. @ http://localhost:4001/quotes/ybqit9d6150vmg0:0 +[ 5124ms] [WARNING] The resource http://localhost:4001/_next/static/media/558ca1a6aa3cb55e-s.p.woff2?v=1785974168950 was preloaded using link preload but not used within a few seconds from the window's load event. Please make sure it has an appropriate `as` value and it is preloaded intentionally. @ http://localhost:4001/quotes/ybqit9d6150vmg0:0 +[ 5124ms] [WARNING] The resource http://localhost:4001/_next/static/media/eaead17c7dbfcd5d-s.p.woff2?v=1785974168950 was preloaded using link preload but not used within a few seconds from the window's load event. Please make sure it has an appropriate `as` value and it is preloaded intentionally. @ http://localhost:4001/quotes/ybqit9d6150vmg0:0 +[ 5124ms] [WARNING] The resource http://localhost:4001/_next/static/media/ee40bb094c99a29a-s.p.woff2?v=1785974168950 was preloaded using link preload but not used within a few seconds from the window's load event. Please make sure it has an appropriate `as` value and it is preloaded intentionally. @ http://localhost:4001/quotes/ybqit9d6150vmg0:0 +[ 5124ms] [WARNING] The resource http://localhost:4001/_next/static/media/e4af272ccee01ff0-s.p.woff2?v=1785974168950 was preloaded using link preload but not used within a few seconds from the window's load event. Please make sure it has an appropriate `as` value and it is preloaded intentionally. @ http://localhost:4001/quotes/ybqit9d6150vmg0:0 diff --git a/.playwright-mcp/page-2026-08-05T23-55-29-859Z.yml b/.playwright-mcp/page-2026-08-05T23-55-29-859Z.yml new file mode 100644 index 0000000..293a778 --- /dev/null +++ b/.playwright-mcp/page-2026-08-05T23-55-29-859Z.yml @@ -0,0 +1,3 @@ +- generic [active] [ref=e1]: + - region "Notifications alt+T" + - button "Open Next.js Dev Tools" [ref=e9] [cursor=pointer] \ No newline at end of file diff --git a/.playwright-mcp/page-2026-08-05T23-55-37-598Z.yml b/.playwright-mcp/page-2026-08-05T23-55-37-598Z.yml new file mode 100644 index 0000000..58382d8 --- /dev/null +++ b/.playwright-mcp/page-2026-08-05T23-55-37-598Z.yml @@ -0,0 +1,10 @@ +- generic [active] [ref=f1e1]: + - region "Notifications alt+T" + - button "Open Next.js Dev Tools" [ref=f1e7] [cursor=pointer]: + - generic [ref=f1e10]: + - text: Compiling + - generic [ref=f1e11]: + - generic [ref=f1e12]: . + - generic [ref=f1e13]: . + - generic [ref=f1e14]: . + - alert [ref=f1e15] \ No newline at end of file diff --git a/.playwright-mcp/page-2026-08-05T23-55-49-667Z.yml b/.playwright-mcp/page-2026-08-05T23-55-49-667Z.yml new file mode 100644 index 0000000..532a34d --- /dev/null +++ b/.playwright-mcp/page-2026-08-05T23-55-49-667Z.yml @@ -0,0 +1,2 @@ +- generic [active] [ref=f3e1]: + - region "Notifications alt+T" \ No newline at end of file diff --git a/.playwright-mcp/page-2026-08-05T23-56-05-361Z.yml b/.playwright-mcp/page-2026-08-05T23-56-05-361Z.yml new file mode 100644 index 0000000..12d7423 --- /dev/null +++ b/.playwright-mcp/page-2026-08-05T23-56-05-361Z.yml @@ -0,0 +1,44 @@ +- generic [active] [ref=f3e1]: + - generic [ref=f3e20]: + - complementary [ref=f3e21]: + - generic [ref=f3e22]: + - link "WFDASH WFDASH Westcott Fasteners" [ref=f3e23] [cursor=pointer]: + - /url: /dashboard + - img "WFDASH" [ref=f3e24] + - generic [ref=f3e25]: + - generic [ref=f3e26]: WFDASH + - generic [ref=f3e27]: Westcott Fasteners + - separator [ref=f3e28] + - navigation [ref=f3e31]: + - link "Dashboard" [ref=f3e32] [cursor=pointer]: + - /url: /dashboard + - separator [ref=f3e41] + - button "Management" [ref=f3e43] + - button "Quotes" [ref=f3e47] + - button "Orders" [ref=f3e51] + - button "Logistics" [ref=f3e55] + - button "Operations" [ref=f3e59] + - button "Stock" [ref=f3e63] + - button "Other" [ref=f3e67] + - separator [ref=f3e70] + - generic [ref=f3e71]: + - link "Settings" [ref=f3e72] [cursor=pointer]: + - /url: /settings + - button "Dark mode" [ref=f3e76] + - separator [ref=f3e79] + - generic [ref=f3e80]: + - generic [ref=f3e81]: D + - generic [ref=f3e83]: + - paragraph [ref=f3e84]: David Els + - paragraph [ref=f3e85]: david.els1@outlook.com + - button "Logout" [ref=f3e86] + - main [ref=f3e91] + - region "Notifications alt+T" + - button "Open Next.js Dev Tools" [ref=f3e117] [cursor=pointer]: + - generic [ref=f3e120]: + - text: Rendering + - generic [ref=f3e121]: + - generic [ref=f3e122]: . + - generic [ref=f3e123]: . + - generic [ref=f3e124]: . + - alert [ref=f3e17] \ No newline at end of file diff --git a/.playwright-mcp/page-2026-08-05T23-56-10-918Z.yml b/.playwright-mcp/page-2026-08-05T23-56-10-918Z.yml new file mode 100644 index 0000000..1aa0c1a --- /dev/null +++ b/.playwright-mcp/page-2026-08-05T23-56-10-918Z.yml @@ -0,0 +1,2 @@ +- generic [active] [ref=f5e1]: + - region "Notifications alt+T" \ No newline at end of file diff --git a/.playwright-mcp/page-2026-08-05T23-56-19-956Z.yml b/.playwright-mcp/page-2026-08-05T23-56-19-956Z.yml new file mode 100644 index 0000000..1aa0c1a --- /dev/null +++ b/.playwright-mcp/page-2026-08-05T23-56-19-956Z.yml @@ -0,0 +1,2 @@ +- generic [active] [ref=f5e1]: + - region "Notifications alt+T" \ No newline at end of file diff --git a/.playwright-mcp/page-2026-08-05T23-56-33-115Z.yml b/.playwright-mcp/page-2026-08-05T23-56-33-115Z.yml new file mode 100644 index 0000000..1aa0c1a --- /dev/null +++ b/.playwright-mcp/page-2026-08-05T23-56-33-115Z.yml @@ -0,0 +1,2 @@ +- generic [active] [ref=f5e1]: + - region "Notifications alt+T" \ No newline at end of file diff --git a/src/app/(app)/quotes/[id]/page.tsx b/src/app/(app)/quotes/[id]/page.tsx index aebe99f..40981a6 100644 --- a/src/app/(app)/quotes/[id]/page.tsx +++ b/src/app/(app)/quotes/[id]/page.tsx @@ -34,6 +34,12 @@ import { DropdownMenuSeparator, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; +import { + Tabs, + TabsList, + TabsTrigger, + TabsContent, +} from "@/components/ui/tabs"; import { ArrowLeft, Trash2, @@ -75,6 +81,7 @@ import { RotateCcw, Loader2, Settings, + Activity, type LucideIcon, } from "lucide-react"; import { toast } from "sonner"; @@ -234,6 +241,7 @@ function AttachmentsCard({ onUpload, onDelete, onPreview, + noWrap, }: { title: string; subtitle: string; @@ -247,6 +255,13 @@ function AttachmentsCard({ onUpload: (file: File) => Promise; onDelete: (att: QuoteAttachment) => Promise; onPreview: (att: QuoteAttachment) => void; + /** + * When true, skip the outer Card + CardHeader + CardTitle wrappers + * and render only the body (upload zone + list). Used inside tabbed + * containers where the parent already provides the Card chrome and + * tab headers — without this, we'd render a card-within-a-card. + */ + noWrap?: boolean; }) { const fileInputRef = useRef(null); const [dragOver, setDragOver] = useState(false); @@ -257,6 +272,82 @@ function AttachmentsCard({ if (fileInputRef.current) fileInputRef.current.value = ""; }; + const body = ( + <> +
{ + e.preventDefault(); + setDragOver(true); + }} + onDragLeave={() => setDragOver(false)} + onDrop={async (e) => { + e.preventDefault(); + setDragOver(false); + const file = e.dataTransfer.files?.[0]; + await handleFile(file); + }} + className={`rounded-md border-2 border-dashed p-6 text-center transition-colors ${ + dragOver + ? "border-primary bg-primary/5" + : "border-border bg-muted/20 hover:bg-muted/30" + } ${uploading ? "opacity-60 pointer-events-none" : "cursor-pointer"}`} + onClick={() => fileInputRef.current?.click()} + role="button" + tabIndex={0} + onKeyDown={(e) => { + if (e.key === "Enter" || e.key === " ") fileInputRef.current?.click(); + }} + aria-label={uploadAriaLabel} + > + + {uploading ? ( +

+ + Uploading… +

+ ) : ( + <> +

Drop a file here or click to upload

+

+ PDFs, images, Word / Excel docs +

+ + )} + { + await handleFile(e.target.files?.[0]); + }} + /> +
+ + {attachments.length === 0 ? ( +

{emptyText}

+ ) : ( +
    + {attachments.map((a) => ( + onDelete(a)} + onPreview={() => onPreview(a)} + /> + ))} +
+ )} + + ); + + if (noWrap) { + // Body-only — the parent already provides the Card chrome and + // tab headers. Wrapping in
keeps the + // spacing between the upload zone and the file list. + return
{body}
; + } + return ( @@ -268,72 +359,7 @@ function AttachmentsCard({ - -
{ - e.preventDefault(); - setDragOver(true); - }} - onDragLeave={() => setDragOver(false)} - onDrop={async (e) => { - e.preventDefault(); - setDragOver(false); - const file = e.dataTransfer.files?.[0]; - await handleFile(file); - }} - className={`rounded-md border-2 border-dashed p-6 text-center transition-colors ${ - dragOver - ? "border-primary bg-primary/5" - : "border-border bg-muted/20 hover:bg-muted/30" - } ${uploading ? "opacity-60 pointer-events-none" : "cursor-pointer"}`} - onClick={() => fileInputRef.current?.click()} - role="button" - tabIndex={0} - onKeyDown={(e) => { - if (e.key === "Enter" || e.key === " ") fileInputRef.current?.click(); - }} - aria-label={uploadAriaLabel} - > - - {uploading ? ( -

- - Uploading… -

- ) : ( - <> -

Drop a file here or click to upload

-

- PDFs, images, Word / Excel docs -

- - )} - { - await handleFile(e.target.files?.[0]); - }} - /> -
- - {attachments.length === 0 ? ( -

{emptyText}

- ) : ( -
    - {attachments.map((a) => ( - onDelete(a)} - onPreview={() => onPreview(a)} - /> - ))} -
- )} -
+ {body}
); } @@ -779,6 +805,12 @@ export default function QuoteDetailPage({ params }: { params: Promise<{ id: stri // collection exists yet). Full audit trail needs a migration; see the // History card's NOTE for details. const [historyOpen, setHistoryOpen] = useState(false); + // "Original Request" (the inbound email) section toggle. Default + // closed — when an operator opens a quote they want to land on the + // items table, not on a wall of email text. The header strip still + // shows the customer + subject at a glance so the operator can + // decide whether to expand the email for context. + const [emailOpen, setEmailOpen] = useState(false); // Approve & Send modal — opens when the operator clicks the primary // Approve button. Mirrors the old Django approve modal's layout // (customer block, send-email toggle, To/CC/BCC/Subject/Message @@ -1904,6 +1936,106 @@ export default function QuoteDetailPage({ params }: { params: Promise<{ id: stri
+ {/* + Compact customer strip — replaces the old "Customer" sidebar + card. Lifts the customer / company / dates / letterhead / + claimed-by info onto a single horizontal band directly below + the header so the operator sees "who is this quote for" + before they decide which sidebar panel to open. Mobile wraps + the columns; desktop shows them inline. + */} + + +
+
+

Customer

+

{quote.expand?.customer?.customer || "—"}

+ {quote.expand?.customer?.expand?.company && ( +

+ {quote.expand.customer.expand.company.company} +

+ )} +
+
+

Company

+

{quote.expand?.company?.company || "—"}

+
+
+

Created

+

{formatDate(quote.created)}

+
+ {quote.quote_date && ( +
+

Quote date

+

{formatDate(quote.quote_date)}

+
+ )} +
+

Letterhead

+

{quote.company_letterhead || "CNL"}

+
+
+

Claimed by

+ {quote.claimed_by ? ( +
+ + {(claimerName || quote.claimed_by).slice(0, 2).toUpperCase()} + +

+ {claimerName || (quote.claimed_by.length > 12 ? `${quote.claimed_by.slice(0, 12)}…` : quote.claimed_by)} +

+
+ ) : ( +

Unclaimed

+ )} +
+
+
+
+ + {/* + Original request (the customer's email + inbound attachments). + Moved here from the BOTTOM of the page — operators wanted it + visible at the same time as the items so they can cross-check + "did the customer say 5L or 4L?" while pricing without scrolling + past everything else. Default closed (email body is verbose); + the header still shows the customer + email subject at a glance + so the operator can decide whether to expand. + */} + {(quote.email_body || attachments.some((a) => a.direction === "inbound")) && ( + + + {emailOpen && ( + + a.direction === "inbound")} + /> + + )} + + )} + {showReject && ( @@ -2154,109 +2286,95 @@ export default function QuoteDetailPage({ params }: { params: Promise<{ id: stri )} - {/* RIGHT COLUMN — meta + admin panels. The eye should land on the - Items card first; the panels on the right are reference / config - and sit quietly. Mobile stacks them below the items. */} + {/* RIGHT COLUMN — admin panels. The eye should land on the Items + card first; the panels on the right are reference / config + and sit quietly. Customer info lives in the strip below the + header (lifted out of this column during the v3 redesign). + Mobile stacks everything below the items. + Three cards now (down from six): + 1. Attachments — tabbed (Outbound / Internal) + 2. Linked Orders — its own card (specific, deserves focus) + 3. Activity — tabbed (History / Email Status) */}
- {/* Compact meta card — Customer / Company / Dates / Letterhead / - Claimed by, packed into a single card with a header icon - rather than five separate micro-cards. */} + {/* + Attachments — single tabbed card with Outbound and Internal + tabs. Both write to the same `quote_attachments` PB + collection; they differ only in the `direction` value + stamped on the row and the labels/icon shown in the UI. + + - `direction = "outbound"` → files the customer receives + (picked up by the n8n email-send workflow) + - `direction = "internal"` → price-evidence files the + office uses to justify the prices (never sent to the + buyer) + */} - - Customer + + Attachments - -
-

{quote.expand?.customer?.customer || "—"}

- {quote.expand?.customer?.expand?.company && ( -

{quote.expand.customer.expand.company.company}

- )} -
-
-
-

Company

-

{quote.expand?.company?.company || "—"}

-
-
-

Letterhead

-

{quote.company_letterhead || "CNL"}

-
-
-

Created

-

{formatDate(quote.created)}

-
- {quote.quote_date && ( -
-

Quote date

-

{formatDate(quote.quote_date)}

-
- )} -
-
-

Claimed by

- {quote.claimed_by ? ( -
- - {(claimerName || quote.claimed_by).slice(0, 2).toUpperCase()} - -

- {claimerName || (quote.claimed_by.length > 12 ? `${quote.claimed_by.slice(0, 12)}…` : quote.claimed_by)} -

-
- ) : ( -

Unclaimed

- )} -
+ + + + + Outbound + + {outboundAttachments.length} + + + + Internal + + {internalAttachments.length} + + + + + { + // Outbound file preview reuses the email-card's + // dialog via a tiny inline state — opening the + // same attachment preview pattern the office + // already trusts. + setOutboundPreviewAtt(att); + }} + /> + + + setInternalPreviewAtt(att)} + /> + +
- { - // Outbound file preview reuses the email-card's dialog via - // a tiny inline state — opening the same attachment preview - // pattern the office already trusts. Implementation lives - // alongside the card to keep the modal scope local. - setOutboundPreviewAtt(att); - }} - /> - - {/* - * Internal / price-evidence attachments — the quoter - * drops screenshots of supplier quotes, price-list PDFs, - * distributor web pages, etc. here to justify the prices - * they set on each item. These NEVER go to the buyer — - * the `direction = "internal"` tag on the PB row tells - * the n8n outbound-email workflow to skip them. - */} - setInternalPreviewAtt(att)} - /> - {/* Linked orders — every order that has been hard-linked to this quote, OR that has us as a `potential_quote` suggestion awaiting accept. Single surface for the operator to see @@ -2296,178 +2414,174 @@ export default function QuoteDetailPage({ params }: { params: Promise<{ id: stri - {/* History sits below Linked Orders — it's a reference panel, - the same accordion pattern as before. Quote Settings has - moved to the kebab menu ("Quote Settings…") so this card - doesn't need to exist inline anymore. */} - - - - {historyOpen && ( - -

- Derived from the existing quote + email fields. Full audit trail - (with actor + per-field diffs) requires a new PB collection and - write hooks — tracked as a follow-up migration. -

-
    - } - label="Quote created" - timestamp={quote.created} - /> - } - label="Last updated" - timestamp={quote.updated} - muted={quote.updated === quote.created} - /> - } - label={`Status: ${statusLabels[quote.status] || quote.status}`} - timestamp={quote.updated} - /> - {quote.claimed_by && ( - } - label={`Claimed by ${claimerName || quote.claimed_by}`} - timestamp={null} - muted - /> - )} - {quote.email_received_at && ( - } - label="Original email received" - timestamp={quote.email_received_at} - /> - )} - {quote.email_delivered && ( - } - label="Quote email delivered" - timestamp={quote.email_delivered_at} - /> - )} - {quote.email_opened && ( - } - label="Quote email opened" - timestamp={quote.email_opened_at} - /> - )} - {quote.last_reply_at && ( - } - label="Customer replied" - timestamp={quote.last_reply_at} - /> - )} - {quote.pdf_generated_at && ( - } - label="PDF generated" - timestamp={quote.pdf_generated_at} - /> - )} - {quote.email_failed_at && ( - } - label={ - quote.email_error - ? `Email send failed — ${quote.email_error}` - : "Email send failed" - } - timestamp={quote.email_failed_at} - destructive - /> - )} - {quote.status === "sending" && quote.sending_started_at && ( - } - label="Email send in progress" - timestamp={quote.sending_started_at} - /> - )} - {quote.approved_by && ( - } - label={`Approved by ${claimerName || quote.approved_by}`} - timestamp={quote.updated} - /> - )} - {quote.follow_up_due && ( - } - label={`Follow-up due ${formatDate(quote.follow_up_due)}`} - timestamp={null} - muted - /> - )} -
-
- )} -
- + {/* + Activity — single tabbed card combining History (event log) + and Email Status (delivery state). Both are reference data + the operator checks occasionally, not work surfaces — putting + them under one roof reduces sidebar noise without hiding + either. Defaults to Email Status because that's what the + operator checks when triaging a "did the customer get it?" + question; History is one click away. + */} - - Email Status + + Activity - -
-
-

Delivered

-
- {quote.email_delivered ? ( - - ) : ( - - )} - {quote.email_delivered ? "Yes" : "No"} + + + + + Email Status + + + History + + + +
+
+

Delivered

+
+ {quote.email_delivered ? ( + + ) : ( + + )} + {quote.email_delivered ? "Yes" : "No"} +
+
+
+

Opened

+
+ {quote.email_opened ? ( + + ) : ( + + )} + {quote.email_opened ? "Yes" : "No"} +
+
+
+

Sender

+

{quote.email_sender || "—"}

+
+
+

Subject

+

{quote.email_subject || "—"}

+
-
-
-

Opened

-
- {quote.email_opened ? ( - - ) : ( - - )} - {quote.email_opened ? "Yes" : "No"} + + + {/* History body — always visible inside its tab. No + outer accordion anymore; the tab itself is the + affordance for "show less / show different". */} +
+

+ Derived from the existing quote + email fields. Full audit trail + (with actor + per-field diffs) requires a new PB collection and + write hooks — tracked as a follow-up migration. +

+
    + } + label="Quote created" + timestamp={quote.created} + /> + } + label="Last updated" + timestamp={quote.updated} + muted={quote.updated === quote.created} + /> + } + label={`Status: ${statusLabels[quote.status] || quote.status}`} + timestamp={quote.updated} + /> + {quote.claimed_by && ( + } + label={`Claimed by ${claimerName || quote.claimed_by}`} + timestamp={null} + muted + /> + )} + {quote.email_received_at && ( + } + label="Original email received" + timestamp={quote.email_received_at} + /> + )} + {quote.email_delivered && ( + } + label="Quote email delivered" + timestamp={quote.email_delivered_at} + /> + )} + {quote.email_opened && ( + } + label="Quote email opened" + timestamp={quote.email_opened_at} + /> + )} + {quote.last_reply_at && ( + } + label="Customer replied" + timestamp={quote.last_reply_at} + /> + )} + {quote.pdf_generated_at && ( + } + label="PDF generated" + timestamp={quote.pdf_generated_at} + /> + )} + {quote.email_failed_at && ( + } + label={ + quote.email_error + ? `Email send failed — ${quote.email_error}` + : "Email send failed" + } + timestamp={quote.email_failed_at} + destructive + /> + )} + {quote.status === "sending" && quote.sending_started_at && ( + } + label="Email send in progress" + timestamp={quote.sending_started_at} + /> + )} + {quote.approved_by && ( + } + label={`Approved by ${claimerName || quote.approved_by}`} + timestamp={quote.updated} + /> + )} + {quote.follow_up_due && ( + } + label={`Follow-up due ${formatDate(quote.follow_up_due)}`} + timestamp={null} + muted + /> + )} +
-
-
-

Sender

-

{quote.email_sender || "—"}

-
-
-

Subject

-

{quote.email_subject || "—"}

-
-
+ +
@@ -2541,45 +2655,6 @@ export default function QuoteDetailPage({ params }: { params: Promise<{ id: stri onClose={() => setInternalPreviewAtt(null)} /> - {/* - Original request — moved to the BOTTOM of the page per - operator feedback. The previous "Compare with original - request" toggle above the items was disruptive: it pushed - the items table down when the approver opened it, and - most of the time the operator just wanted a quick - reference (e.g. "did the customer say 5L or 4L?") without - it being in the way of the pricing work. - - Now it's a dedicated section at the bottom. The same - `QuoteEmailCard` component renders the original email body - + the inbound attachments (PDFs, drawings, specs) that - the customer sent — all direction="inbound" rows. - The "inbound" direction is filtered out of the outbound - and internal cards above, so there's exactly one place - to see each attachment. - */} - {(quote.email_body || attachments.some((a) => a.direction === "inbound")) && ( -
-

- - Original request - - what the customer sent - -

- a.direction === "inbound")} - /> -
- )} -