adserver-dashboard 1.0.0

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.
Files changed (525) hide show
  1. package/.ci/staging.yml +191 -0
  2. package/.dockerignore +117 -0
  3. package/.env +40 -0
  4. package/.env.staging +38 -0
  5. package/.gitlab-ci.yml +16 -0
  6. package/DEMO_STATUS.md +579 -0
  7. package/Dockerfile +61 -0
  8. package/Influence-MW-AdServer-12-02-2026/client/index.html +17 -0
  9. package/Influence-MW-AdServer-12-02-2026/client/public/favicon.png +0 -0
  10. package/Influence-MW-AdServer-12-02-2026/client/src/App.tsx +91 -0
  11. package/Influence-MW-AdServer-12-02-2026/client/src/components/advanced-map-drawer.tsx +1131 -0
  12. package/Influence-MW-AdServer-12-02-2026/client/src/components/ai-recommendation-panel.tsx +379 -0
  13. package/Influence-MW-AdServer-12-02-2026/client/src/components/app-sidebar.tsx +183 -0
  14. package/Influence-MW-AdServer-12-02-2026/client/src/components/auto-optimize-button.tsx +184 -0
  15. package/Influence-MW-AdServer-12-02-2026/client/src/components/availability-drawer.tsx +385 -0
  16. package/Influence-MW-AdServer-12-02-2026/client/src/components/brand-insights-panel.tsx +87 -0
  17. package/Influence-MW-AdServer-12-02-2026/client/src/components/create-agency-drawer.tsx +198 -0
  18. package/Influence-MW-AdServer-12-02-2026/client/src/components/create-brand-drawer.tsx +275 -0
  19. package/Influence-MW-AdServer-12-02-2026/client/src/components/creative-assignment.tsx +526 -0
  20. package/Influence-MW-AdServer-12-02-2026/client/src/components/data-table-toolbar.tsx +148 -0
  21. package/Influence-MW-AdServer-12-02-2026/client/src/components/data-table.tsx +158 -0
  22. package/Influence-MW-AdServer-12-02-2026/client/src/components/filter-drawer.tsx +356 -0
  23. package/Influence-MW-AdServer-12-02-2026/client/src/components/form-insights-panel.tsx +82 -0
  24. package/Influence-MW-AdServer-12-02-2026/client/src/components/geography-selector.tsx +699 -0
  25. package/Influence-MW-AdServer-12-02-2026/client/src/components/header-user-menu.tsx +178 -0
  26. package/Influence-MW-AdServer-12-02-2026/client/src/components/history-drawer.tsx +313 -0
  27. package/Influence-MW-AdServer-12-02-2026/client/src/components/inventory-availability-section.tsx +176 -0
  28. package/Influence-MW-AdServer-12-02-2026/client/src/components/inventory-format-drawer.tsx +173 -0
  29. package/Influence-MW-AdServer-12-02-2026/client/src/components/inventory-selector.tsx +401 -0
  30. package/Influence-MW-AdServer-12-02-2026/client/src/components/manual-inventory-drawer.tsx +368 -0
  31. package/Influence-MW-AdServer-12-02-2026/client/src/components/mapbox-map.tsx +368 -0
  32. package/Influence-MW-AdServer-12-02-2026/client/src/components/market-insights-panel.tsx +202 -0
  33. package/Influence-MW-AdServer-12-02-2026/client/src/components/media-owner-drawer.tsx +217 -0
  34. package/Influence-MW-AdServer-12-02-2026/client/src/components/metric-card.tsx +58 -0
  35. package/Influence-MW-AdServer-12-02-2026/client/src/components/page-header.tsx +27 -0
  36. package/Influence-MW-AdServer-12-02-2026/client/src/components/player-status-indicator.tsx +137 -0
  37. package/Influence-MW-AdServer-12-02-2026/client/src/components/poi-targeting-drawer.tsx +298 -0
  38. package/Influence-MW-AdServer-12-02-2026/client/src/components/recommendation-score-badge.tsx +102 -0
  39. package/Influence-MW-AdServer-12-02-2026/client/src/components/recommended-inventories-panel.tsx +248 -0
  40. package/Influence-MW-AdServer-12-02-2026/client/src/components/searchable-combobox.tsx +134 -0
  41. package/Influence-MW-AdServer-12-02-2026/client/src/components/signal-visualizations.tsx +407 -0
  42. package/Influence-MW-AdServer-12-02-2026/client/src/components/status-badge.tsx +35 -0
  43. package/Influence-MW-AdServer-12-02-2026/client/src/components/theme-provider.tsx +73 -0
  44. package/Influence-MW-AdServer-12-02-2026/client/src/components/theme-toggle.tsx +37 -0
  45. package/Influence-MW-AdServer-12-02-2026/client/src/components/traffic-slider.tsx +75 -0
  46. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/accordion.tsx +56 -0
  47. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/alert-dialog.tsx +139 -0
  48. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/alert.tsx +59 -0
  49. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/aspect-ratio.tsx +5 -0
  50. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/avatar.tsx +51 -0
  51. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/badge.tsx +38 -0
  52. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/breadcrumb.tsx +115 -0
  53. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/button.tsx +62 -0
  54. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/calendar.tsx +68 -0
  55. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/card.tsx +85 -0
  56. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/carousel.tsx +260 -0
  57. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/chart.tsx +365 -0
  58. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/checkbox.tsx +28 -0
  59. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/collapsible.tsx +11 -0
  60. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/command.tsx +151 -0
  61. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/context-menu.tsx +198 -0
  62. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/dialog.tsx +122 -0
  63. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/drawer.tsx +118 -0
  64. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/dropdown-menu.tsx +198 -0
  65. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/form.tsx +178 -0
  66. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/hover-card.tsx +29 -0
  67. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/input-otp.tsx +69 -0
  68. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/input.tsx +23 -0
  69. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/label.tsx +24 -0
  70. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/menubar.tsx +256 -0
  71. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/navigation-menu.tsx +128 -0
  72. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/pagination.tsx +117 -0
  73. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/popover.tsx +29 -0
  74. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/progress.tsx +28 -0
  75. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/radio-group.tsx +42 -0
  76. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/resizable.tsx +45 -0
  77. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/scroll-area.tsx +46 -0
  78. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/select.tsx +160 -0
  79. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/separator.tsx +29 -0
  80. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/sheet.tsx +140 -0
  81. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/sidebar.tsx +727 -0
  82. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/skeleton.tsx +15 -0
  83. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/slider.tsx +26 -0
  84. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/switch.tsx +27 -0
  85. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/table.tsx +117 -0
  86. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/tabs.tsx +53 -0
  87. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/textarea.tsx +22 -0
  88. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/toast.tsx +127 -0
  89. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/toaster.tsx +33 -0
  90. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/toggle-group.tsx +61 -0
  91. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/toggle.tsx +43 -0
  92. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/tooltip.tsx +30 -0
  93. package/Influence-MW-AdServer-12-02-2026/client/src/components/vendor-stores-modal.tsx +336 -0
  94. package/Influence-MW-AdServer-12-02-2026/client/src/components/venue-type-drawer.tsx +359 -0
  95. package/Influence-MW-AdServer-12-02-2026/client/src/components/venue-type-selector.tsx +436 -0
  96. package/Influence-MW-AdServer-12-02-2026/client/src/hooks/use-mobile.tsx +19 -0
  97. package/Influence-MW-AdServer-12-02-2026/client/src/hooks/use-toast.ts +191 -0
  98. package/Influence-MW-AdServer-12-02-2026/client/src/index.css +244 -0
  99. package/Influence-MW-AdServer-12-02-2026/client/src/lib/queryClient.ts +57 -0
  100. package/Influence-MW-AdServer-12-02-2026/client/src/lib/utils.ts +39 -0
  101. package/Influence-MW-AdServer-12-02-2026/client/src/lib/venue-taxonomy.ts +532 -0
  102. package/Influence-MW-AdServer-12-02-2026/client/src/main.tsx +5 -0
  103. package/Influence-MW-AdServer-12-02-2026/client/src/pages/assign-creative.tsx +781 -0
  104. package/Influence-MW-AdServer-12-02-2026/client/src/pages/content-hub.tsx +995 -0
  105. package/Influence-MW-AdServer-12-02-2026/client/src/pages/custom-pois.tsx +431 -0
  106. package/Influence-MW-AdServer-12-02-2026/client/src/pages/dashboard.tsx +620 -0
  107. package/Influence-MW-AdServer-12-02-2026/client/src/pages/deal-detail.tsx +1062 -0
  108. package/Influence-MW-AdServer-12-02-2026/client/src/pages/deal-form.tsx +1570 -0
  109. package/Influence-MW-AdServer-12-02-2026/client/src/pages/deals.tsx +716 -0
  110. package/Influence-MW-AdServer-12-02-2026/client/src/pages/edit-creative-assignment.tsx +1051 -0
  111. package/Influence-MW-AdServer-12-02-2026/client/src/pages/geotargeting.tsx +675 -0
  112. package/Influence-MW-AdServer-12-02-2026/client/src/pages/integrations.tsx +425 -0
  113. package/Influence-MW-AdServer-12-02-2026/client/src/pages/line-item-creatives.tsx +622 -0
  114. package/Influence-MW-AdServer-12-02-2026/client/src/pages/line-item-form.tsx +3132 -0
  115. package/Influence-MW-AdServer-12-02-2026/client/src/pages/line-items.tsx +530 -0
  116. package/Influence-MW-AdServer-12-02-2026/client/src/pages/not-found.tsx +21 -0
  117. package/Influence-MW-AdServer-12-02-2026/client/src/pages/proof-of-play-upload.tsx +479 -0
  118. package/Influence-MW-AdServer-12-02-2026/client/src/pages/proof-of-play.tsx +880 -0
  119. package/Influence-MW-AdServer-12-02-2026/client/src/pages/reports.tsx +235 -0
  120. package/Influence-MW-AdServer-12-02-2026/client/src/pages/settings.tsx +652 -0
  121. package/Influence-MW-AdServer-12-02-2026/client/src/pages/signal-form.tsx +1117 -0
  122. package/Influence-MW-AdServer-12-02-2026/client/src/pages/signals.tsx +366 -0
  123. package/Influence-MW-AdServer-12-02-2026/client/src/pages/tags.tsx +332 -0
  124. package/Influence-MW-AdServer-12-02-2026/client/src/pages/venues.tsx +381 -0
  125. package/Influence-MW-AdServer-12-02-2026/client/src/types/mapbox-gl-draw.d.ts +37 -0
  126. package/Influence-MW-AdServer-12-02-2026/client/src/types/react-simple-maps.d.ts +57 -0
  127. package/Influence-MW-AdServer-12-02-2026/components.json +20 -0
  128. package/Influence-MW-AdServer-12-02-2026/docs/PRD.md +3373 -0
  129. package/Influence-MW-AdServer-12-02-2026/docs/influence-feature-mapping.csv +498 -0
  130. package/Influence-MW-AdServer-12-02-2026/drizzle.config.ts +14 -0
  131. package/Influence-MW-AdServer-12-02-2026/package-lock.json +9672 -0
  132. package/Influence-MW-AdServer-12-02-2026/package.json +118 -0
  133. package/Influence-MW-AdServer-12-02-2026/postcss.config.js +6 -0
  134. package/Influence-MW-AdServer-12-02-2026/replit.md +91 -0
  135. package/Influence-MW-AdServer-12-02-2026/script/build.ts +67 -0
  136. package/Influence-MW-AdServer-12-02-2026/scripts/create-miro-diagrams.cjs +318 -0
  137. package/Influence-MW-AdServer-12-02-2026/scripts/create-remaining-diagrams.cjs +270 -0
  138. package/Influence-MW-AdServer-12-02-2026/server/index.ts +103 -0
  139. package/Influence-MW-AdServer-12-02-2026/server/recommendation-service.ts +319 -0
  140. package/Influence-MW-AdServer-12-02-2026/server/routes.ts +1890 -0
  141. package/Influence-MW-AdServer-12-02-2026/server/static.ts +19 -0
  142. package/Influence-MW-AdServer-12-02-2026/server/storage.ts +2058 -0
  143. package/Influence-MW-AdServer-12-02-2026/server/vite.ts +58 -0
  144. package/Influence-MW-AdServer-12-02-2026/shared/schema.ts +1595 -0
  145. package/Influence-MW-AdServer-12-02-2026/tailwind.config.ts +107 -0
  146. package/Influence-MW-AdServer-12-02-2026/tsconfig.json +23 -0
  147. package/Influence-MW-AdServer-12-02-2026/vite.config.ts +40 -0
  148. package/LINE_ITEM_BUDGET_FIELD_MAPPING.md +178 -0
  149. package/PCM/.env.example +92 -0
  150. package/PCM/README.md +558 -0
  151. package/PCM/docs/TEST_CASES.md +422 -0
  152. package/PCM/index.js +106 -0
  153. package/PCM/package-lock.json +3282 -0
  154. package/PCM/package.json +32 -0
  155. package/PCM/replit.md +64 -0
  156. package/PCM/schema.sql +495 -0
  157. package/PCM/scripts/export-schema.js +183 -0
  158. package/PCM/scripts/seed-comprehensive.js +631 -0
  159. package/PCM/scripts/seed-production.js +477 -0
  160. package/PCM/src/config/db.js +56 -0
  161. package/PCM/src/config/swagger.js +5975 -0
  162. package/PCM/src/dto/EmailRequestDTO.js +166 -0
  163. package/PCM/src/middleware/errorHandler.js +52 -0
  164. package/PCM/src/middleware/logger.js +26 -0
  165. package/PCM/src/migrations/001_add_campaign_mode_fields.sql +36 -0
  166. package/PCM/src/migrations/002_create_deal_id_counters.sql +22 -0
  167. package/PCM/src/migrations/003_update_publishers_column.sql +15 -0
  168. package/PCM/src/migrations/004_add_direct_dealtype_and_advertiser.sql +5 -0
  169. package/PCM/src/migrations/005_add_programmatic_fields_and_update_enums.sql +31 -0
  170. package/PCM/src/migrations/006_add_line_item_programmatic_fields.sql +12 -0
  171. package/PCM/src/migrations/007_add_line_item_direct_fields.sql +15 -0
  172. package/PCM/src/migrations/008_add_inventory_fields.sql +45 -0
  173. package/PCM/src/migrations/009_move_inventory_fields_to_metadata.sql +32 -0
  174. package/PCM/src/migrations/010_add_draft_status_and_line_items_count.sql +23 -0
  175. package/PCM/src/migrations/011_add_planning_field.sql +21 -0
  176. package/PCM/src/migrations/012_fix_inventory_composite_pk.sql +17 -0
  177. package/PCM/src/migrations/013_make_external_id_optional.sql +3 -0
  178. package/PCM/src/migrations/014_create_change_history.sql +38 -0
  179. package/PCM/src/migrations/016_create_publisher_insertion_orders.sql +33 -0
  180. package/PCM/src/migrations/017_fix_line_item_id_fk_reference.sql +86 -0
  181. package/PCM/src/migrations/018_create_approval_tables.sql +44 -0
  182. package/PCM/src/migrations/019_add_encrypted_token_column.sql +2 -0
  183. package/PCM/src/migrations/020_add_rejection_reason_to_deals.sql +10 -0
  184. package/PCM/src/migrations/021_add_publisher_external_id_to_inventories.sql +12 -0
  185. package/PCM/src/migrations/022_add_line_item_extended_fields.sql +24 -0
  186. package/PCM/src/migrations/023_add_base_price_fields.sql +8 -0
  187. package/PCM/src/migrations/run-migrations.js +46 -0
  188. package/PCM/src/models/ApprovalOTP.js +51 -0
  189. package/PCM/src/models/ApprovalToken.js +79 -0
  190. package/PCM/src/models/ChangeHistory.js +107 -0
  191. package/PCM/src/models/Deal.js +186 -0
  192. package/PCM/src/models/DealIdCounter.js +28 -0
  193. package/PCM/src/models/LineItem.js +227 -0
  194. package/PCM/src/models/LineItemCreative.js +89 -0
  195. package/PCM/src/models/LineItemInventory.js +115 -0
  196. package/PCM/src/models/PublisherInsertionOrder.js +93 -0
  197. package/PCM/src/models/TransactionHistory.js +34 -0
  198. package/PCM/src/models/associations.js +81 -0
  199. package/PCM/src/routes/approval.js +321 -0
  200. package/PCM/src/routes/creatives.js +437 -0
  201. package/PCM/src/routes/deals.js +1638 -0
  202. package/PCM/src/routes/digitalSignage.js +242 -0
  203. package/PCM/src/routes/insertionOrders.js +380 -0
  204. package/PCM/src/routes/lineItems.js +926 -0
  205. package/PCM/src/routes/system.js +384 -0
  206. package/PCM/src/services/ApprovalService.js +885 -0
  207. package/PCM/src/services/CampaignImportConverter.js +631 -0
  208. package/PCM/src/services/CampaignModeService.js +273 -0
  209. package/PCM/src/services/CampaignStatusService.js +395 -0
  210. package/PCM/src/services/ChangeHistoryService.js +316 -0
  211. package/PCM/src/services/DealIdService.js +94 -0
  212. package/PCM/src/services/DealResponseFormatter.js +90 -0
  213. package/PCM/src/services/EmailNotificationService.js +315 -0
  214. package/PCM/src/services/LineItemResponseFormatter.js +122 -0
  215. package/PCM/src/services/LineItemStatusService.js +380 -0
  216. package/PCM/src/tests/comprehensiveTestRunner.js +360 -0
  217. package/PCM/src/tests/comprehensiveTests.js +1277 -0
  218. package/PCM/src/tests/dealTypeUnitTests.js +1058 -0
  219. package/PCM/src/tests/testRunner.js +248 -0
  220. package/PCM/src/utils/caseConverter.js +92 -0
  221. package/PCM/src/utils/dealCalculations.js +206 -0
  222. package/PCM/src/utils/lineItemPayloadNormalizer.js +41 -0
  223. package/PCM/src/utils/payloadNormalizer.js +34 -0
  224. package/PCM/src/utils/sourceNormalizer.js +56 -0
  225. package/PCM/src/validators/creativeValidator.js +27 -0
  226. package/PCM/src/validators/dealValidator.js +203 -0
  227. package/PCM/src/validators/lineItemValidator.js +489 -0
  228. package/PCM/tests/approval-flows.test.js +238 -0
  229. package/PCM/tests/approval-workflow.test.js +291 -0
  230. package/PCM/tests/campaign-import-converter.test.js +543 -0
  231. package/PCM/tests/campaign-import-e2e.test.js +520 -0
  232. package/PCM/tests/campaign-status.test.js +539 -0
  233. package/PCM/tests/direct-publisher-split-reimport.test.js +460 -0
  234. package/PCM/tests/e2e/digital-signage.test.js +145 -0
  235. package/PCM/tests/e2e/search-filter-pagination.test.js +399 -0
  236. package/PCM/tests/e2e-comprehensive.test.js +3446 -0
  237. package/PCM/tests/edge-cases.test.js +340 -0
  238. package/PCM/tests/line-item-status.test.js +340 -0
  239. package/PCM/tests/seller-account-external-ids.test.js +877 -0
  240. package/PCM/tests/source-validation.test.js +324 -0
  241. package/PRD.md +3373 -0
  242. package/README.md +186 -0
  243. package/client/index.html +35 -0
  244. package/client/public/DEMO_STATUS.md +579 -0
  245. package/client/public/img/MW-logo-trans_1754045676555.png +0 -0
  246. package/client/public/locales/ar/approval.json +144 -0
  247. package/client/public/locales/ar/buyer.json +61 -0
  248. package/client/public/locales/ar/campaigns.json +1 -0
  249. package/client/public/locales/ar/common.json +218 -0
  250. package/client/public/locales/ar/contentHub.json +266 -0
  251. package/client/public/locales/ar/creatives.json +79 -0
  252. package/client/public/locales/ar/dashboard.json +57 -0
  253. package/client/public/locales/ar/deals.json +886 -0
  254. package/client/public/locales/ar/dsp.json +131 -0
  255. package/client/public/locales/ar/inventory.json +201 -0
  256. package/client/public/locales/ar/lineItems.json +553 -0
  257. package/client/public/locales/ar/navigation.json +48 -0
  258. package/client/public/locales/ar/wizard.json +1 -0
  259. package/client/public/locales/en/approval.json +144 -0
  260. package/client/public/locales/en/buyer.json +65 -0
  261. package/client/public/locales/en/campaigns.json +1 -0
  262. package/client/public/locales/en/common.json +218 -0
  263. package/client/public/locales/en/contentHub.json +266 -0
  264. package/client/public/locales/en/creatives.json +79 -0
  265. package/client/public/locales/en/dashboard.json +57 -0
  266. package/client/public/locales/en/deals.json +886 -0
  267. package/client/public/locales/en/dsp.json +131 -0
  268. package/client/public/locales/en/inventory.json +201 -0
  269. package/client/public/locales/en/lineItems.json +659 -0
  270. package/client/public/locales/en/navigation.json +48 -0
  271. package/client/public/locales/en/wizard.json +1 -0
  272. package/client/public/locales/ja/approval.json +144 -0
  273. package/client/public/locales/ja/buyer.json +61 -0
  274. package/client/public/locales/ja/campaigns.json +1 -0
  275. package/client/public/locales/ja/common.json +218 -0
  276. package/client/public/locales/ja/contentHub.json +266 -0
  277. package/client/public/locales/ja/creatives.json +79 -0
  278. package/client/public/locales/ja/dashboard.json +57 -0
  279. package/client/public/locales/ja/deals.json +886 -0
  280. package/client/public/locales/ja/dsp.json +131 -0
  281. package/client/public/locales/ja/inventory.json +201 -0
  282. package/client/public/locales/ja/lineItems.json +553 -0
  283. package/client/public/locales/ja/navigation.json +48 -0
  284. package/client/public/locales/ja/wizard.json +1 -0
  285. package/client/public/locales/zh/approval.json +144 -0
  286. package/client/public/locales/zh/buyer.json +61 -0
  287. package/client/public/locales/zh/campaigns.json +1 -0
  288. package/client/public/locales/zh/common.json +218 -0
  289. package/client/public/locales/zh/contentHub.json +266 -0
  290. package/client/public/locales/zh/creatives.json +79 -0
  291. package/client/public/locales/zh/dashboard.json +57 -0
  292. package/client/public/locales/zh/deals.json +886 -0
  293. package/client/public/locales/zh/dsp.json +131 -0
  294. package/client/public/locales/zh/inventory.json +201 -0
  295. package/client/public/locales/zh/lineItems.json +553 -0
  296. package/client/public/locales/zh/navigation.json +48 -0
  297. package/client/public/locales/zh/wizard.json +1 -0
  298. package/client/public/manifest.json +36 -0
  299. package/client/src/App.tsx +464 -0
  300. package/client/src/components/app-sidebar.tsx +312 -0
  301. package/client/src/components/approval/approval-decision-form.test.tsx +294 -0
  302. package/client/src/components/approval/approval-decision-form.tsx +326 -0
  303. package/client/src/components/approval/approval-sheet.tsx +631 -0
  304. package/client/src/components/approval/line-item-details-sheet.tsx +371 -0
  305. package/client/src/components/approval/otp-verification.test.tsx +337 -0
  306. package/client/src/components/approval/otp-verification.tsx +180 -0
  307. package/client/src/components/content-hub/bulk-transcode-dialog.tsx +379 -0
  308. package/client/src/components/content-hub/content-hub-manager-v2.tsx +574 -0
  309. package/client/src/components/content-hub/content-hub-manager.tsx +330 -0
  310. package/client/src/components/content-hub/creative-card.tsx +456 -0
  311. package/client/src/components/content-hub/creative-detail-sheet.tsx +685 -0
  312. package/client/src/components/content-hub/creative-filters.tsx +457 -0
  313. package/client/src/components/content-hub/creative-grid.tsx +329 -0
  314. package/client/src/components/content-hub/creative-selector.tsx +415 -0
  315. package/client/src/components/content-hub/creative-upload.tsx +547 -0
  316. package/client/src/components/content-hub/folder-dialogs.tsx +445 -0
  317. package/client/src/components/content-hub/folder-list.tsx +280 -0
  318. package/client/src/components/content-hub/review-dialogs.tsx +268 -0
  319. package/client/src/components/content-hub/transcode-dialog.tsx +226 -0
  320. package/client/src/components/creative-library/creative-details-view.tsx +446 -0
  321. package/client/src/components/creative-library/creative-filters-panel.tsx +203 -0
  322. package/client/src/components/creative-library/creative-list.tsx +360 -0
  323. package/client/src/components/creative-library/creative-status-badge.tsx +71 -0
  324. package/client/src/components/creative-library/folder-card.tsx +78 -0
  325. package/client/src/components/creative-library/index.ts +27 -0
  326. package/client/src/components/creative-library/new-creative-card.tsx +211 -0
  327. package/client/src/components/creative-library/upload-creative-dialog.tsx +261 -0
  328. package/client/src/components/dashboard-overview.tsx +109 -0
  329. package/client/src/components/deals/approval-history-panel.test.tsx +240 -0
  330. package/client/src/components/deals/approval-history-panel.tsx +156 -0
  331. package/client/src/components/deals/deal-status-badge.tsx +92 -0
  332. package/client/src/components/deals/import-from-planner-dialog.tsx +399 -0
  333. package/client/src/components/deals/market-insights-panel.tsx +237 -0
  334. package/client/src/components/deals/reopen-deal-sheet.tsx +191 -0
  335. package/client/src/components/deals/request-approval-sheet.test.tsx +323 -0
  336. package/client/src/components/deals/request-approval-sheet.tsx +136 -0
  337. package/client/src/components/deals/resend-approval-sheet.tsx +201 -0
  338. package/client/src/components/direct-campaigns/campaign-card.tsx +283 -0
  339. package/client/src/components/direct-campaigns/deal-filter-panel.tsx +325 -0
  340. package/client/src/components/inventory/advanced-filters-panel.tsx +273 -0
  341. package/client/src/components/inventory/csv-upload-modal.tsx +639 -0
  342. package/client/src/components/inventory/inventory-availability-view.tsx +486 -0
  343. package/client/src/components/inventory/inventory-details-sheet.tsx +376 -0
  344. package/client/src/components/inventory/inventory-map-view.tsx +596 -0
  345. package/client/src/components/inventory/inventory-settings-menu.tsx +52 -0
  346. package/client/src/components/language-switcher.tsx +53 -0
  347. package/client/src/components/line-items/campaign-forecast-panel.tsx +138 -0
  348. package/client/src/components/line-items/form-insights.tsx +89 -0
  349. package/client/src/components/line-items/geofencing/LocationCsvUploadDrawer.tsx +100 -0
  350. package/client/src/components/line-items/geofencing/POIDropdown.tsx +379 -0
  351. package/client/src/components/line-items/geofencing/SelectedLocationsSidebar.tsx +436 -0
  352. package/client/src/components/line-items/geofencing/ViewFileLocationDrawer.tsx +199 -0
  353. package/client/src/components/line-items/geofencing/components/ExistingFilesTab.tsx +268 -0
  354. package/client/src/components/line-items/geofencing/components/TemplateDownloadSection.tsx +59 -0
  355. package/client/src/components/line-items/geofencing/components/UploadTab.tsx +215 -0
  356. package/client/src/components/line-items/geofencing-map.tsx +1270 -0
  357. package/client/src/components/line-items/inventory-availability-section.tsx +178 -0
  358. package/client/src/components/line-items/line-item-schedule-manager.tsx +313 -0
  359. package/client/src/components/line-items/manual-inventory-drawer.tsx +346 -0
  360. package/client/src/components/line-items/planner-inventory-card.tsx +495 -0
  361. package/client/src/components/line-items/planner-schedule-grid.tsx +495 -0
  362. package/client/src/components/line-items/schedule-rule-editor.tsx +649 -0
  363. package/client/src/components/line-items/schedule-rule-types.ts +122 -0
  364. package/client/src/components/line-items/steps/creatives-step.tsx +681 -0
  365. package/client/src/components/line-items/steps/inventory-schedule-step.tsx +1596 -0
  366. package/client/src/components/line-items/steps/inventory-step.tsx +1533 -0
  367. package/client/src/components/line-items/steps/line-item-details-step.tsx +916 -0
  368. package/client/src/components/line-items/steps/schedule-step.tsx +273 -0
  369. package/client/src/components/line-items/steps/summary-step.tsx +680 -0
  370. package/client/src/components/line-items/steps/targeting-step.tsx +1708 -0
  371. package/client/src/components/product-switcher.tsx +105 -0
  372. package/client/src/components/protected-route.tsx +49 -0
  373. package/client/src/components/skip-link.tsx +22 -0
  374. package/client/src/components/stat-card.tsx +53 -0
  375. package/client/src/components/status-badge.tsx +96 -0
  376. package/client/src/components/ui/hierarchical-venue-selector.tsx +389 -0
  377. package/client/src/components/ui/toaster.tsx +111 -0
  378. package/client/src/contexts/auth-context.tsx +181 -0
  379. package/client/src/contexts/sidebar-state.tsx +50 -0
  380. package/client/src/contexts/theme-context.tsx +66 -0
  381. package/client/src/data/campaign-data.json +107 -0
  382. package/client/src/data/countries.json +22 -0
  383. package/client/src/hooks/use-approval.ts +366 -0
  384. package/client/src/hooks/use-keyboard-shortcuts.ts +74 -0
  385. package/client/src/hooks/use-media-query.ts +46 -0
  386. package/client/src/hooks/use-mobile.tsx +19 -0
  387. package/client/src/hooks/use-page-title.ts +21 -0
  388. package/client/src/hooks/use-toast.ts +195 -0
  389. package/client/src/index.css +694 -0
  390. package/client/src/lib/__tests__/accessibility.test.ts +104 -0
  391. package/client/src/lib/__tests__/date-utils.test.ts +199 -0
  392. package/client/src/lib/__tests__/dsp-buyer-api.test.ts +127 -0
  393. package/client/src/lib/__tests__/dsp-buyer-integration.test.ts +247 -0
  394. package/client/src/lib/__tests__/storage-utils.test.ts +167 -0
  395. package/client/src/lib/__tests__/utils.test.ts +57 -0
  396. package/client/src/lib/accessibility.ts +141 -0
  397. package/client/src/lib/api-config.ts +9 -0
  398. package/client/src/lib/auth-service.ts +209 -0
  399. package/client/src/lib/campaign-creative-api.ts +82 -0
  400. package/client/src/lib/company-api.ts +61 -0
  401. package/client/src/lib/content-hub-api.ts +407 -0
  402. package/client/src/lib/creative-mapper.ts +61 -0
  403. package/client/src/lib/date-utils.ts +119 -0
  404. package/client/src/lib/deal-helpers.ts +220 -0
  405. package/client/src/lib/dsp-buyer-api.ts +196 -0
  406. package/client/src/lib/geo-import-api.ts +151 -0
  407. package/client/src/lib/google-poi-api.ts +305 -0
  408. package/client/src/lib/i18n/__tests__/formatting.test.ts +202 -0
  409. package/client/src/lib/i18n/formatting.ts +130 -0
  410. package/client/src/lib/i18n/index.ts +8 -0
  411. package/client/src/lib/i18n-compat.ts +76 -0
  412. package/client/src/lib/influence-deals-api.ts +896 -0
  413. package/client/src/lib/inventory-api.ts +399 -0
  414. package/client/src/lib/oauth-service.ts +678 -0
  415. package/client/src/lib/poi-types.ts +75 -0
  416. package/client/src/lib/queryClient.ts +144 -0
  417. package/client/src/lib/recommendation-api.ts +380 -0
  418. package/client/src/lib/storage-utils.ts +104 -0
  419. package/client/src/lib/tolgee.ts +85 -0
  420. package/client/src/lib/utils.ts +0 -0
  421. package/client/src/main.tsx +67 -0
  422. package/client/src/mapbox-draw-modes.d.ts +32 -0
  423. package/client/src/pages/all-folders.tsx +203 -0
  424. package/client/src/pages/auth-callback.tsx +115 -0
  425. package/client/src/pages/buyer-form.tsx +339 -0
  426. package/client/src/pages/buyer-list.tsx +622 -0
  427. package/client/src/pages/content-hub.tsx +1358 -0
  428. package/client/src/pages/create-deal.tsx +2093 -0
  429. package/client/src/pages/creative-assignment-page.tsx +548 -0
  430. package/client/src/pages/creatives.tsx +5 -0
  431. package/client/src/pages/custom-pois.tsx +425 -0
  432. package/client/src/pages/dashboard.tsx +615 -0
  433. package/client/src/pages/deal-history.tsx +434 -0
  434. package/client/src/pages/deal-line-items.tsx +1703 -0
  435. package/client/src/pages/demo-status.tsx +113 -0
  436. package/client/src/pages/direct-campaign-details.tsx +361 -0
  437. package/client/src/pages/direct-campaigns-new.tsx +824 -0
  438. package/client/src/pages/dsp-form.tsx +803 -0
  439. package/client/src/pages/dsp-list.tsx +239 -0
  440. package/client/src/pages/folder-content.tsx +336 -0
  441. package/client/src/pages/integrations.tsx +429 -0
  442. package/client/src/pages/line-item-creatives.tsx +789 -0
  443. package/client/src/pages/line-item-detail-page.tsx +684 -0
  444. package/client/src/pages/line-item-form-page.tsx +3261 -0
  445. package/client/src/pages/line-item-wizard.tsx +1207 -0
  446. package/client/src/pages/login.tsx +154 -0
  447. package/client/src/pages/not-found.tsx +23 -0
  448. package/client/src/pages/proof-of-play.tsx +397 -0
  449. package/client/src/pages/public-approval.tsx +551 -0
  450. package/client/src/pages/reports.tsx +231 -0
  451. package/client/src/pages/settings.tsx +760 -0
  452. package/client/src/pages/signals.tsx +389 -0
  453. package/client/src/pages/tags.tsx +318 -0
  454. package/client/src/pages/test-results.tsx +328 -0
  455. package/client/src/store/hooks.ts +5 -0
  456. package/client/src/store/index.ts +15 -0
  457. package/client/src/store/mapMarkerLocationsSlice.ts +241 -0
  458. package/client/src/styles/design-tokens.css +324 -0
  459. package/client/src/test/setup.ts +261 -0
  460. package/client/src/test/test-utils.tsx +40 -0
  461. package/client/src/types/approval.ts +221 -0
  462. package/client/src/types/content-hub.ts +209 -0
  463. package/client/src/types/geofencing.ts +67 -0
  464. package/client/src/types/transcoding.ts +140 -0
  465. package/client/src/vite-env.d.ts +18 -0
  466. package/components.json +20 -0
  467. package/creative-api.json +1 -0
  468. package/docs/AI_REFERENCE.md +459 -0
  469. package/docs/MWDesign-Prompt.md +132 -0
  470. package/docs/MWDesign-System.md +344 -0
  471. package/docs/test-plan.md +277 -0
  472. package/e2e/AUTONOMOUS-TESTING.md +406 -0
  473. package/e2e/README.md +219 -0
  474. package/e2e/autonomous-flow.spec.ts +308 -0
  475. package/e2e/debug-sso.spec.ts +163 -0
  476. package/e2e/direct-campaigns.spec.ts +219 -0
  477. package/e2e/explore-sso.spec.ts +149 -0
  478. package/e2e/fixtures/auth.ts +26 -0
  479. package/e2e/fixtures/enhanced-test.ts +331 -0
  480. package/e2e/pagination.spec.ts +280 -0
  481. package/e2e/view-toggle.spec.ts +312 -0
  482. package/generated-icon.png +0 -0
  483. package/i18next-scanner.config.cjs +46 -0
  484. package/package.json +141 -0
  485. package/playwright.config.ts +93 -0
  486. package/postcss.config.js +6 -0
  487. package/replit.md +196 -0
  488. package/screenshot-after-login.png +0 -0
  489. package/screenshot-contenthub-grid.png +0 -0
  490. package/screenshot-contenthub-list-fixed.png +0 -0
  491. package/screenshot-contenthub-list.png +0 -0
  492. package/screenshot-create-deal.png +0 -0
  493. package/screenshot-dashboard.png +0 -0
  494. package/screenshot-deals.png +0 -0
  495. package/screenshot-login-filled.png +0 -0
  496. package/screenshot-login.png +0 -0
  497. package/screenshot.mjs +24 -0
  498. package/scripts/deploy-stg.sh +185 -0
  499. package/shared/direct-io-schema.ts +383 -0
  500. package/shared/schema.ts +439 -0
  501. package/shared/screen-types.ts +149 -0
  502. package/springdocDefault.json +1 -0
  503. package/swagger-ui-bundle.js +2 -0
  504. package/swagger-ui-init.js +10316 -0
  505. package/tailwind.config.ts +282 -0
  506. package/terraform/README.md +306 -0
  507. package/terraform/cloudfront.tf +289 -0
  508. package/terraform/ecs.tf +727 -0
  509. package/terraform/environments/dev.tfvars +59 -0
  510. package/terraform/environments/production.tfvars +60 -0
  511. package/terraform/main.tf +47 -0
  512. package/terraform/outputs.tf +145 -0
  513. package/terraform/s3.tf +192 -0
  514. package/terraform/variables.tf +226 -0
  515. package/terraform/waf.tf +165 -0
  516. package/terraform-frontend/.terraform.lock.hcl +25 -0
  517. package/terraform-frontend/README.md +85 -0
  518. package/terraform-frontend/cloudfront.tf +125 -0
  519. package/terraform-frontend/main.tf +31 -0
  520. package/terraform-frontend/outputs.tf +24 -0
  521. package/terraform-frontend/terraform.tfvars +12 -0
  522. package/terraform-frontend/variables.tf +53 -0
  523. package/tsconfig.json +23 -0
  524. package/vite.config.ts +226 -0
  525. package/vitest.config.ts +56 -0
@@ -0,0 +1,620 @@
1
+ import { useState } from "react";
2
+ import { useQuery } from "@tanstack/react-query";
3
+ import { format, subDays } from "date-fns";
4
+ import {
5
+ Megaphone,
6
+ Eye,
7
+ DollarSign,
8
+ TrendingUp,
9
+ Activity,
10
+ Zap,
11
+ FileText,
12
+ Layers,
13
+ Image,
14
+ CalendarDays,
15
+ CheckCircle2,
16
+ Clock,
17
+ Link2,
18
+ Calendar,
19
+ AlertTriangle,
20
+ ArrowUpRight,
21
+ BarChart3,
22
+ } from "lucide-react";
23
+ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
24
+ import { PageHeader } from "@/components/page-header";
25
+ import { MetricCard } from "@/components/metric-card";
26
+ import { StatusBadge } from "@/components/status-badge";
27
+ import { Skeleton } from "@/components/ui/skeleton";
28
+ import { Progress } from "@/components/ui/progress";
29
+ import { Button } from "@/components/ui/button";
30
+ import {
31
+ Select,
32
+ SelectContent,
33
+ SelectItem,
34
+ SelectTrigger,
35
+ SelectValue,
36
+ } from "@/components/ui/select";
37
+ import {
38
+ Popover,
39
+ PopoverContent,
40
+ PopoverTrigger,
41
+ } from "@/components/ui/popover";
42
+ import { Calendar as CalendarComponent } from "@/components/ui/calendar";
43
+ import { formatCurrency } from "@/lib/utils";
44
+ import type { Deal, LineItem, DashboardMetrics, Screen } from "@shared/schema";
45
+ import { useLocation } from "wouter";
46
+
47
+ type TimePeriod = "30" | "60" | "90" | "custom";
48
+
49
+ export default function Dashboard() {
50
+ const [, setLocation] = useLocation();
51
+ const [timePeriod, setTimePeriod] = useState<TimePeriod>("30");
52
+ const [customDateRange, setCustomDateRange] = useState<{
53
+ from: Date | undefined;
54
+ to: Date | undefined;
55
+ }>({
56
+ from: subDays(new Date(), 30),
57
+ to: new Date(),
58
+ });
59
+ const [calendarOpen, setCalendarOpen] = useState(false);
60
+
61
+ const { data: metrics, isLoading: metricsLoading } = useQuery<DashboardMetrics>({
62
+ queryKey: ["/api/dashboard/metrics"],
63
+ });
64
+
65
+ const { data: recentDeals, isLoading: dealsLoading } = useQuery<Deal[]>({
66
+ queryKey: ["/api/deals"],
67
+ });
68
+
69
+ const { data: lineItems, isLoading: lineItemsLoading } = useQuery<LineItem[]>({
70
+ queryKey: ["/api/line-items"],
71
+ });
72
+
73
+ const { data: screens, isLoading: screensLoading } = useQuery<Screen[]>({
74
+ queryKey: ["/api/screens"],
75
+ });
76
+
77
+ const formatNumber = (num: number) => {
78
+ if (num >= 1000000) return `${(num / 1000000).toFixed(1)}M`;
79
+ if (num >= 1000) return `${(num / 1000).toFixed(1)}K`;
80
+ return num.toString();
81
+ };
82
+
83
+ const getTimePeriodLabel = () => {
84
+ if (timePeriod === "custom" && customDateRange.from && customDateRange.to) {
85
+ return `${format(customDateRange.from, "MMM dd, yyyy")} - ${format(customDateRange.to, "MMM dd, yyyy")}`;
86
+ }
87
+ return `Last ${timePeriod} days`;
88
+ };
89
+
90
+ const handleTimePeriodChange = (value: string) => {
91
+ if (value === "custom") {
92
+ setTimePeriod("custom");
93
+ setCalendarOpen(true);
94
+ } else {
95
+ setTimePeriod(value as TimePeriod);
96
+ const days = parseInt(value);
97
+ setCustomDateRange({
98
+ from: subDays(new Date(), days),
99
+ to: new Date(),
100
+ });
101
+ }
102
+ };
103
+
104
+ const getInventoryAvailability = () => {
105
+ if (!screens || screens.length === 0) {
106
+ return { available: 0, partiallyAvailable: 0, soldOut: 0, total: 0 };
107
+ }
108
+
109
+ const seededRandom = (seed: number) => {
110
+ const x = Math.sin(seed) * 10000;
111
+ return x - Math.floor(x);
112
+ };
113
+
114
+ let available = 0;
115
+ let partiallyAvailable = 0;
116
+ let soldOut = 0;
117
+
118
+ screens.forEach((screen) => {
119
+ const idStr = String(screen.id);
120
+ const seed = parseInt(idStr.replace(/\D/g, '') || '0', 10) + 42;
121
+ const rand = seededRandom(seed);
122
+ if (rand < 0.6) {
123
+ available++;
124
+ } else if (rand < 0.85) {
125
+ partiallyAvailable++;
126
+ } else {
127
+ soldOut++;
128
+ }
129
+ });
130
+
131
+ return { available, partiallyAvailable, soldOut, total: screens.length };
132
+ };
133
+
134
+ const getLineItemDeliveryStatus = () => {
135
+ if (!lineItems || lineItems.length === 0) {
136
+ return { onTrack: 0, behind: 0, atRisk: 0, ahead: 0, total: 0 };
137
+ }
138
+
139
+ const activeLineItems = lineItems.filter(li => li.status === 'active');
140
+
141
+ const seededRandom = (seed: number) => {
142
+ const x = Math.sin(seed) * 10000;
143
+ return x - Math.floor(x);
144
+ };
145
+
146
+ let onTrack = 0;
147
+ let behind = 0;
148
+ let atRisk = 0;
149
+ let ahead = 0;
150
+
151
+ activeLineItems.forEach((lineItem) => {
152
+ const idStr = String(lineItem.id);
153
+ const seed = parseInt(idStr.replace(/\D/g, '') || '0', 10) + 123;
154
+ const rand = seededRandom(seed);
155
+ if (rand < 0.45) {
156
+ onTrack++;
157
+ } else if (rand < 0.65) {
158
+ ahead++;
159
+ } else if (rand < 0.85) {
160
+ behind++;
161
+ } else {
162
+ atRisk++;
163
+ }
164
+ });
165
+
166
+ return { onTrack, behind, atRisk, ahead, total: activeLineItems.length };
167
+ };
168
+
169
+ const availability = getInventoryAvailability();
170
+ const deliveryStatus = getLineItemDeliveryStatus();
171
+
172
+ return (
173
+ <div className="flex flex-col gap-6 p-6">
174
+ <div className="flex items-center justify-between">
175
+ <PageHeader
176
+ title="Dashboard"
177
+ description="Overview of your DOOH advertising operations"
178
+ />
179
+ <div className="flex items-center gap-2">
180
+ <Select value={timePeriod} onValueChange={handleTimePeriodChange}>
181
+ <SelectTrigger className="w-[180px]" data-testid="select-time-period">
182
+ <SelectValue placeholder="Select period" />
183
+ </SelectTrigger>
184
+ <SelectContent>
185
+ <SelectItem value="30">Last 30 days</SelectItem>
186
+ <SelectItem value="60">Last 60 days</SelectItem>
187
+ <SelectItem value="90">Last 90 days</SelectItem>
188
+ <SelectItem value="custom">Custom dates</SelectItem>
189
+ </SelectContent>
190
+ </Select>
191
+ {timePeriod === "custom" && (
192
+ <Popover open={calendarOpen} onOpenChange={setCalendarOpen}>
193
+ <PopoverTrigger asChild>
194
+ <Button variant="outline" size="sm" data-testid="button-custom-dates">
195
+ <CalendarDays className="h-4 w-4 mr-2" />
196
+ {customDateRange.from && customDateRange.to
197
+ ? `${format(customDateRange.from, "MMM dd")} - ${format(customDateRange.to, "MMM dd")}`
198
+ : "Pick dates"}
199
+ </Button>
200
+ </PopoverTrigger>
201
+ <PopoverContent className="w-auto p-0" align="end">
202
+ <CalendarComponent
203
+ mode="range"
204
+ selected={{
205
+ from: customDateRange.from,
206
+ to: customDateRange.to,
207
+ }}
208
+ onSelect={(range) => {
209
+ setCustomDateRange({
210
+ from: range?.from,
211
+ to: range?.to,
212
+ });
213
+ if (range?.from && range?.to) {
214
+ setCalendarOpen(false);
215
+ }
216
+ }}
217
+ numberOfMonths={2}
218
+ />
219
+ </PopoverContent>
220
+ </Popover>
221
+ )}
222
+ </div>
223
+ </div>
224
+
225
+ <div className="grid gap-4 md:grid-cols-2 lg:grid-cols-4">
226
+ {metricsLoading ? (
227
+ <>
228
+ {[...Array(4)].map((_, i) => (
229
+ <Card key={i}>
230
+ <CardHeader className="flex flex-row items-center justify-between gap-2 space-y-0 pb-2">
231
+ <Skeleton className="h-4 w-24" />
232
+ <Skeleton className="h-8 w-8 rounded-md" />
233
+ </CardHeader>
234
+ <CardContent>
235
+ <Skeleton className="h-8 w-20 mb-2" />
236
+ <Skeleton className="h-3 w-32" />
237
+ </CardContent>
238
+ </Card>
239
+ ))}
240
+ </>
241
+ ) : (
242
+ <>
243
+ <MetricCard
244
+ title="Active Deals"
245
+ value={metrics?.activeDeals ?? 0}
246
+ subtitle={`of ${metrics?.totalDeals ?? 0} total`}
247
+ icon={Megaphone}
248
+ trend={{ value: 12, isPositive: true }}
249
+ />
250
+ <MetricCard
251
+ title="Total Impressions"
252
+ value={formatNumber(metrics?.totalImpressions ?? 0)}
253
+ subtitle={getTimePeriodLabel()}
254
+ icon={Eye}
255
+ trend={{ value: 8, isPositive: true }}
256
+ />
257
+ <MetricCard
258
+ title="Revenue"
259
+ value={formatCurrency(metrics?.totalRevenue ?? 0)}
260
+ subtitle={getTimePeriodLabel()}
261
+ icon={DollarSign}
262
+ trend={{ value: 15, isPositive: true }}
263
+ />
264
+ <MetricCard
265
+ title="Fill Rate"
266
+ value={`${metrics?.fillRate ?? 0}%`}
267
+ subtitle={`Avg CPM: ${formatCurrency(metrics?.avgCPM ?? 0)}`}
268
+ icon={TrendingUp}
269
+ trend={{ value: 3, isPositive: true }}
270
+ />
271
+ </>
272
+ )}
273
+ </div>
274
+
275
+ <div className="grid gap-4 md:grid-cols-2 lg:grid-cols-4">
276
+ {metricsLoading ? (
277
+ <>
278
+ {[...Array(4)].map((_, i) => (
279
+ <Card key={i}>
280
+ <CardHeader className="flex flex-row items-center justify-between gap-2 space-y-0 pb-2">
281
+ <Skeleton className="h-4 w-24" />
282
+ <Skeleton className="h-8 w-8 rounded-md" />
283
+ </CardHeader>
284
+ <CardContent>
285
+ <Skeleton className="h-8 w-20 mb-2" />
286
+ <Skeleton className="h-3 w-32" />
287
+ </CardContent>
288
+ </Card>
289
+ ))}
290
+ </>
291
+ ) : (
292
+ <>
293
+ <MetricCard
294
+ title="Total Screens"
295
+ value={metrics?.totalScreens ?? 0}
296
+ subtitle={`${availability.available} available`}
297
+ icon={FileText}
298
+ trend={{ value: 5, isPositive: true }}
299
+ />
300
+ <MetricCard
301
+ title="Active Line Items"
302
+ value={metrics?.activeLineItems ?? 0}
303
+ subtitle={`of ${metrics?.totalLineItems ?? 0} total`}
304
+ icon={Layers}
305
+ trend={{ value: 10, isPositive: true }}
306
+ />
307
+ <MetricCard
308
+ title="Total Creatives"
309
+ value={metrics?.totalCreatives ?? 0}
310
+ subtitle={`${metrics?.acceptedCreatives ?? 0} approved`}
311
+ icon={Image}
312
+ trend={{ value: 7, isPositive: true }}
313
+ />
314
+ <MetricCard
315
+ title="Pending Approvals"
316
+ value={metrics?.pendingApprovalCreatives ?? 0}
317
+ subtitle={`${metrics?.assignedCreatives ?? 0} assigned`}
318
+ icon={Clock}
319
+ />
320
+ </>
321
+ )}
322
+ </div>
323
+
324
+ <div className="grid gap-4 lg:grid-cols-7">
325
+ <Card className="lg:col-span-4">
326
+ <CardHeader>
327
+ <CardTitle className="flex items-center gap-2">
328
+ <Activity className="h-5 w-5 text-primary" />
329
+ Impression Delivery
330
+ </CardTitle>
331
+ </CardHeader>
332
+ <CardContent>
333
+ <div className="space-y-6">
334
+ {metricsLoading ? (
335
+ [...Array(4)].map((_, i) => (
336
+ <div key={i} className="space-y-2">
337
+ <Skeleton className="h-4 w-full" />
338
+ <Skeleton className="h-2 w-full" />
339
+ </div>
340
+ ))
341
+ ) : (
342
+ <>
343
+ <div className="space-y-2">
344
+ <div className="flex items-center justify-between text-sm">
345
+ <span>Today</span>
346
+ <span className="font-medium">245,890 impressions</span>
347
+ </div>
348
+ <Progress value={68} className="h-2" />
349
+ </div>
350
+ <div className="space-y-2">
351
+ <div className="flex items-center justify-between text-sm">
352
+ <span>This Week</span>
353
+ <span className="font-medium">1.42M impressions</span>
354
+ </div>
355
+ <Progress value={82} className="h-2" />
356
+ </div>
357
+ <div className="space-y-2">
358
+ <div className="flex items-center justify-between text-sm">
359
+ <span>This Month</span>
360
+ <span className="font-medium">5.67M impressions</span>
361
+ </div>
362
+ <Progress value={71} className="h-2" />
363
+ </div>
364
+ <div className="space-y-2">
365
+ <div className="flex items-center justify-between text-sm">
366
+ <span>Goal Completion</span>
367
+ <span className="font-medium">89%</span>
368
+ </div>
369
+ <Progress value={89} className="h-2" />
370
+ </div>
371
+ </>
372
+ )}
373
+ </div>
374
+ </CardContent>
375
+ </Card>
376
+
377
+ <Card className="lg:col-span-3">
378
+ <CardHeader>
379
+ <CardTitle className="flex items-center gap-2">
380
+ <Calendar className="h-5 w-5 text-primary" />
381
+ Inventory Availability
382
+ </CardTitle>
383
+ </CardHeader>
384
+ <CardContent>
385
+ <div className="space-y-4">
386
+ {screensLoading ? (
387
+ [...Array(3)].map((_, i) => (
388
+ <div key={i} className="flex items-center justify-between">
389
+ <Skeleton className="h-4 w-20" />
390
+ <Skeleton className="h-6 w-16" />
391
+ </div>
392
+ ))
393
+ ) : (
394
+ <>
395
+ <div className="flex items-center justify-between p-3 rounded-md bg-emerald-500/10">
396
+ <div className="flex items-center gap-2">
397
+ <div className="h-2 w-2 rounded-full bg-emerald-500" />
398
+ <span className="text-sm font-medium">Available</span>
399
+ </div>
400
+ <span className="text-lg font-bold text-emerald-600 dark:text-emerald-400">
401
+ {availability.available}
402
+ </span>
403
+ </div>
404
+ <div className="flex items-center justify-between p-3 rounded-md bg-amber-500/10">
405
+ <div className="flex items-center gap-2">
406
+ <div className="h-2 w-2 rounded-full bg-amber-500" />
407
+ <span className="text-sm font-medium">Partially Available</span>
408
+ </div>
409
+ <span className="text-lg font-bold text-amber-600 dark:text-amber-400">
410
+ {availability.partiallyAvailable}
411
+ </span>
412
+ </div>
413
+ <div className="flex items-center justify-between p-3 rounded-md bg-red-500/10">
414
+ <div className="flex items-center gap-2">
415
+ <div className="h-2 w-2 rounded-full bg-red-500" />
416
+ <span className="text-sm font-medium">Sold Out</span>
417
+ </div>
418
+ <span className="text-lg font-bold text-red-600 dark:text-red-400">
419
+ {availability.soldOut}
420
+ </span>
421
+ </div>
422
+ <div className="pt-2 border-t">
423
+ <div className="flex items-center justify-between">
424
+ <span className="text-sm text-muted-foreground">Total Screens</span>
425
+ <span className="font-semibold">{availability.total}</span>
426
+ </div>
427
+ {availability.total > 0 && (
428
+ <div className="mt-2">
429
+ <Progress
430
+ value={(availability.available / availability.total) * 100}
431
+ className="h-2"
432
+ />
433
+ <div className="flex justify-between mt-1 text-xs text-muted-foreground">
434
+ <span>{Math.round((availability.available / availability.total) * 100)}% available</span>
435
+ </div>
436
+ </div>
437
+ )}
438
+ </div>
439
+ </>
440
+ )}
441
+ </div>
442
+ </CardContent>
443
+ </Card>
444
+ </div>
445
+
446
+ <div className="grid gap-4 lg:grid-cols-3">
447
+ <Card className="hover-elevate cursor-pointer" onClick={() => setLocation("/deals")} data-testid="card-recent-deals">
448
+ <CardHeader>
449
+ <CardTitle className="flex items-center gap-2">
450
+ <Zap className="h-5 w-5 text-primary" />
451
+ Recent Deals
452
+ </CardTitle>
453
+ </CardHeader>
454
+ <CardContent>
455
+ <div className="space-y-4">
456
+ {dealsLoading ? (
457
+ [...Array(5)].map((_, i) => (
458
+ <div key={i} className="flex items-center justify-between py-2">
459
+ <Skeleton className="h-4 w-40" />
460
+ <Skeleton className="h-6 w-16" />
461
+ </div>
462
+ ))
463
+ ) : recentDeals && recentDeals.length > 0 ? (
464
+ recentDeals.slice(0, 5).map((deal) => (
465
+ <div
466
+ key={deal.id}
467
+ className="flex items-center justify-between py-2 border-b last:border-0"
468
+ data-testid={`deal-row-${deal.id}`}
469
+ >
470
+ <div className="flex flex-col">
471
+ <span className="font-medium">{deal.name}</span>
472
+ <span className="text-xs text-muted-foreground">
473
+ {deal.dealType === "programmatic" ? "Programmatic" : "Traditional"}
474
+ </span>
475
+ </div>
476
+ <StatusBadge status={deal.status} />
477
+ </div>
478
+ ))
479
+ ) : (
480
+ <div className="text-center py-8 text-muted-foreground">
481
+ No deals yet. Create your first deal to get started.
482
+ </div>
483
+ )}
484
+ </div>
485
+ </CardContent>
486
+ </Card>
487
+
488
+ <Card>
489
+ <CardHeader>
490
+ <CardTitle className="flex items-center gap-2">
491
+ <BarChart3 className="h-5 w-5 text-primary" />
492
+ Line Item Delivery Status
493
+ </CardTitle>
494
+ </CardHeader>
495
+ <CardContent>
496
+ <div className="space-y-4">
497
+ {lineItemsLoading ? (
498
+ [...Array(4)].map((_, i) => (
499
+ <div key={i} className="flex items-center justify-between">
500
+ <Skeleton className="h-4 w-24" />
501
+ <Skeleton className="h-6 w-12" />
502
+ </div>
503
+ ))
504
+ ) : deliveryStatus.total === 0 ? (
505
+ <div className="text-center py-8 text-muted-foreground">
506
+ No active line items to track.
507
+ </div>
508
+ ) : (
509
+ <>
510
+ <div className="flex items-center justify-between p-3 rounded-md bg-emerald-500/10">
511
+ <div className="flex items-center gap-2">
512
+ <CheckCircle2 className="h-4 w-4 text-emerald-600 dark:text-emerald-400" />
513
+ <span className="text-sm font-medium">On Track</span>
514
+ </div>
515
+ <span className="text-lg font-bold text-emerald-600 dark:text-emerald-400">
516
+ {deliveryStatus.onTrack}
517
+ </span>
518
+ </div>
519
+ <div className="flex items-center justify-between p-3 rounded-md bg-blue-500/10">
520
+ <div className="flex items-center gap-2">
521
+ <ArrowUpRight className="h-4 w-4 text-blue-600 dark:text-blue-400" />
522
+ <span className="text-sm font-medium">Ahead</span>
523
+ </div>
524
+ <span className="text-lg font-bold text-blue-600 dark:text-blue-400">
525
+ {deliveryStatus.ahead}
526
+ </span>
527
+ </div>
528
+ <div className="flex items-center justify-between p-3 rounded-md bg-amber-500/10">
529
+ <div className="flex items-center gap-2">
530
+ <Clock className="h-4 w-4 text-amber-600 dark:text-amber-400" />
531
+ <span className="text-sm font-medium">Behind</span>
532
+ </div>
533
+ <span className="text-lg font-bold text-amber-600 dark:text-amber-400">
534
+ {deliveryStatus.behind}
535
+ </span>
536
+ </div>
537
+ <div className="flex items-center justify-between p-3 rounded-md bg-red-500/10">
538
+ <div className="flex items-center gap-2">
539
+ <AlertTriangle className="h-4 w-4 text-red-600 dark:text-red-400" />
540
+ <span className="text-sm font-medium">At Risk</span>
541
+ </div>
542
+ <span className="text-lg font-bold text-red-600 dark:text-red-400">
543
+ {deliveryStatus.atRisk}
544
+ </span>
545
+ </div>
546
+ <div className="pt-2 border-t">
547
+ <div className="flex items-center justify-between">
548
+ <span className="text-sm text-muted-foreground">Active Line Items</span>
549
+ <span className="font-semibold">{deliveryStatus.total}</span>
550
+ </div>
551
+ </div>
552
+ </>
553
+ )}
554
+ </div>
555
+ </CardContent>
556
+ </Card>
557
+
558
+ <Card>
559
+ <CardHeader>
560
+ <CardTitle className="flex items-center gap-2">
561
+ <Image className="h-5 w-5 text-primary" />
562
+ Creative Status
563
+ </CardTitle>
564
+ </CardHeader>
565
+ <CardContent>
566
+ <div className="space-y-4">
567
+ {metricsLoading ? (
568
+ [...Array(4)].map((_, i) => (
569
+ <div key={i} className="flex items-center justify-between">
570
+ <Skeleton className="h-4 w-24" />
571
+ <Skeleton className="h-6 w-12" />
572
+ </div>
573
+ ))
574
+ ) : (
575
+ <>
576
+ <div className="flex items-center justify-between p-3 rounded-md bg-blue-500/10">
577
+ <div className="flex items-center gap-2">
578
+ <Image className="h-4 w-4 text-blue-600 dark:text-blue-400" />
579
+ <span className="text-sm font-medium">Total Creatives</span>
580
+ </div>
581
+ <span className="text-lg font-bold text-blue-600 dark:text-blue-400">
582
+ {metrics?.totalCreatives ?? 0}
583
+ </span>
584
+ </div>
585
+ <div className="flex items-center justify-between p-3 rounded-md bg-emerald-500/10">
586
+ <div className="flex items-center gap-2">
587
+ <CheckCircle2 className="h-4 w-4 text-emerald-600 dark:text-emerald-400" />
588
+ <span className="text-sm font-medium">Accepted</span>
589
+ </div>
590
+ <span className="text-lg font-bold text-emerald-600 dark:text-emerald-400">
591
+ {metrics?.acceptedCreatives ?? 0}
592
+ </span>
593
+ </div>
594
+ <div className="flex items-center justify-between p-3 rounded-md bg-amber-500/10">
595
+ <div className="flex items-center gap-2">
596
+ <Clock className="h-4 w-4 text-amber-600 dark:text-amber-400" />
597
+ <span className="text-sm font-medium">Pending Approval</span>
598
+ </div>
599
+ <span className="text-lg font-bold text-amber-600 dark:text-amber-400">
600
+ {metrics?.pendingApprovalCreatives ?? 0}
601
+ </span>
602
+ </div>
603
+ <div className="flex items-center justify-between p-3 rounded-md bg-purple-500/10">
604
+ <div className="flex items-center gap-2">
605
+ <Link2 className="h-4 w-4 text-purple-600 dark:text-purple-400" />
606
+ <span className="text-sm font-medium">Assigned to Line Items</span>
607
+ </div>
608
+ <span className="text-lg font-bold text-purple-600 dark:text-purple-400">
609
+ {metrics?.assignedCreatives ?? 0}
610
+ </span>
611
+ </div>
612
+ </>
613
+ )}
614
+ </div>
615
+ </CardContent>
616
+ </Card>
617
+ </div>
618
+ </div>
619
+ );
620
+ }