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,684 @@
1
+ import { useMemo } from "react";
2
+ import { useParams, useLocation } from "wouter";
3
+ import { useQuery } from "@tanstack/react-query";
4
+ import { Button } from "@moving-walls/design-system";
5
+ import { Badge } from "@moving-walls/design-system";
6
+ import { AgGridTable } from '@moving-walls/design-system';
7
+ import type { ColDef } from '@moving-walls/design-system';
8
+
9
+ import {
10
+ ArrowLeft,
11
+ Calendar,
12
+ DollarSign,
13
+ BarChart3,
14
+ Monitor,
15
+ Image,
16
+ Pencil,
17
+ Target,
18
+ Layers,
19
+ MapPin,
20
+ Users,
21
+ Eye,
22
+ Clock,
23
+ Zap,
24
+ TrendingUp,
25
+ ChevronRight,
26
+ Hash,
27
+ FileText,
28
+ } from "lucide-react";
29
+ import {
30
+ InfluenceDealsAPI,
31
+ influenceDealsRequest,
32
+ isDealEditable,
33
+ type Deal,
34
+ } from "@/lib/influence-deals-api";
35
+ import { usePageTitle } from "@/hooks/use-page-title";
36
+
37
+ export default function LineItemDetailPage() {
38
+ const { dealId, lineItemId } = useParams<{ dealId: string; lineItemId: string }>();
39
+ const [, setLocation] = useLocation();
40
+
41
+ const { data: deal } = useQuery({
42
+ queryKey: ["deal", dealId],
43
+ queryFn: async () => {
44
+ const response = await influenceDealsRequest<Deal>(
45
+ InfluenceDealsAPI.deals.get(dealId!),
46
+ );
47
+ return response;
48
+ },
49
+ enabled: !!dealId,
50
+ });
51
+
52
+ const { data: lineItem, isLoading: isLoadingLineItem } = useQuery({
53
+ queryKey: ["line-item-details", dealId, lineItemId],
54
+ queryFn: async () => {
55
+ const response = await influenceDealsRequest<any>(
56
+ InfluenceDealsAPI.lineItems.get(dealId!, lineItemId!),
57
+ );
58
+ return response;
59
+ },
60
+ enabled: !!dealId && !!lineItemId,
61
+ });
62
+
63
+ const { data: inventoriesResponse } = useQuery({
64
+ queryKey: ["line-item-inventories", dealId, lineItemId],
65
+ queryFn: async () => {
66
+ const response = await influenceDealsRequest<{
67
+ data: any[];
68
+ pagination?: any;
69
+ }>(
70
+ InfluenceDealsAPI.lineItems.inventories(dealId!, lineItemId!, { page: 1, limit: 100 }),
71
+ );
72
+ return {
73
+ data: response?.data || response || [],
74
+ };
75
+ },
76
+ enabled: !!dealId && !!lineItemId,
77
+ });
78
+
79
+ const { data: creativesResponse } = useQuery({
80
+ queryKey: ["line-item-creatives", dealId, lineItemId],
81
+ queryFn: async () => {
82
+ const response = await influenceDealsRequest<{
83
+ data: any[];
84
+ pagination?: any;
85
+ }>(
86
+ InfluenceDealsAPI.creatives.listByLineItem(dealId!, lineItemId!, { page: 1, limit: 100 }),
87
+ );
88
+ return {
89
+ data: response?.data || response || [],
90
+ };
91
+ },
92
+ enabled: !!dealId && !!lineItemId,
93
+ });
94
+
95
+ usePageTitle("Line Item Detail", lineItem?.name || deal?.name);
96
+
97
+ const inventories = Array.isArray(inventoriesResponse?.data) ? inventoriesResponse.data : [];
98
+ const creatives = Array.isArray(creativesResponse?.data) ? creativesResponse.data : [];
99
+
100
+ const inventoryColumnDefs = useMemo<ColDef<any>[]>(() => [
101
+ {
102
+ colId: "name",
103
+ headerName: "Name",
104
+ field: "name",
105
+ cellRenderer: (params: { data: any }) => (
106
+ <span className="text-sm font-medium text-mw-neutral-800 dark:text-white">
107
+ {params.data?.name || params.data?.inventoryName || "Unnamed"}
108
+ </span>
109
+ ),
110
+ },
111
+ {
112
+ colId: "address",
113
+ headerName: "Address",
114
+ field: "address",
115
+ cellRenderer: (params: { data: any }) => (
116
+ <span className="text-sm text-mw-neutral-500">
117
+ {params.data?.address || params.data?.location || "--"}
118
+ </span>
119
+ ),
120
+ },
121
+ {
122
+ colId: "venueType",
123
+ headerName: "Venue Type",
124
+ field: "venueType",
125
+ cellRenderer: (params: { value: string }) => (
126
+ <Badge variant="outline" className="text-xs rounded-full">
127
+ {params.value || "--"}
128
+ </Badge>
129
+ ),
130
+ },
131
+ {
132
+ colId: "publisher",
133
+ headerName: "Publisher",
134
+ field: "publisher",
135
+ cellRenderer: (params: { data: any }) => (
136
+ <span className="text-sm text-mw-neutral-500">
137
+ {params.data?.publisher?.name || params.data?.mediaOwnerName || "--"}
138
+ </span>
139
+ ),
140
+ },
141
+ ], []);
142
+
143
+ const canEdit = isDealEditable(deal?.status);
144
+
145
+ const formatDate = (dateStr?: string | null): string => {
146
+ if (!dateStr) return "--";
147
+ const date = new Date(dateStr);
148
+ return date.toLocaleDateString("en-US", {
149
+ month: "short",
150
+ day: "2-digit",
151
+ year: "numeric",
152
+ });
153
+ };
154
+
155
+ const formatCurrency = (amount: number | undefined, currency: string = "USD"): string => {
156
+ if (!amount && amount !== 0) return `${currency} 0`;
157
+ return `${currency} ${amount.toLocaleString()}`;
158
+ };
159
+
160
+ const calculateDuration = (startDate?: string | null, endDate?: string | null): string => {
161
+ if (!startDate || !endDate) return "--";
162
+ const start = new Date(startDate);
163
+ const end = new Date(endDate);
164
+ const days = Math.ceil((end.getTime() - start.getTime()) / (1000 * 60 * 60 * 24));
165
+ if (days < 0) return "--";
166
+ return `${days} day${days !== 1 ? 's' : ''}`;
167
+ };
168
+
169
+ const getStatusVariant = (status?: string): "default" | "secondary" | "destructive" | "outline" => {
170
+ switch (status?.toUpperCase()) {
171
+ case "LIVE":
172
+ case "ACTIVE":
173
+ return "default";
174
+ case "DRAFT":
175
+ return "secondary";
176
+ case "REJECTED":
177
+ return "destructive";
178
+ default:
179
+ return "outline";
180
+ }
181
+ };
182
+
183
+ const getStatusColor = (status?: string): string => {
184
+ switch (status?.toUpperCase()) {
185
+ case "LIVE":
186
+ case "ACTIVE":
187
+ return "bg-green-100 text-green-700 border-green-200";
188
+ case "DRAFT":
189
+ return "bg-slate-100 text-slate-600 border-slate-200";
190
+ case "PENDING":
191
+ return "bg-amber-100 text-amber-700 border-amber-200";
192
+ case "APPROVED":
193
+ return "bg-blue-100 text-blue-700 border-blue-200";
194
+ case "PAUSED":
195
+ return "bg-orange-100 text-orange-700 border-orange-200";
196
+ case "COMPLETED":
197
+ case "ENDED":
198
+ return "bg-purple-100 text-purple-700 border-purple-200";
199
+ case "REJECTED":
200
+ return "bg-red-100 text-red-700 border-red-200";
201
+ default:
202
+ return "bg-slate-100 text-slate-600 border-slate-200";
203
+ }
204
+ };
205
+
206
+ const li = lineItem as any;
207
+ const budget = li?.direct?.budgetSetup?.budgetAmount || li?.budget || li?.totalBudget || li?.programmatic?.netCost || 0;
208
+ const impressions = li?.impressions || li?.targetImpressions || li?.totalImpressions || li?.direct?.campaignGoal?.targetValue || li?.programmatic?.impressions || 0;
209
+ const revenue = li?.revenue || li?.estimatedRevenue || 0;
210
+ const currency = li?.currency || deal?.currency || "USD";
211
+ const dealMode = deal?.mode || (li?.programmatic ? "PROGRAMMATIC" : "DIRECT");
212
+
213
+ const targeting = li?.targeting || {};
214
+ const demographics = targeting?.demographics || targeting?.audience || {};
215
+ const venueTypes = targeting?.venueTypes || targeting?.venues || [];
216
+ const geography = targeting?.geography || targeting?.geo || targeting?.geofencing?.locations || [];
217
+ const signals = li?.deliveryTargeting?.signals;
218
+
219
+ if (isLoadingLineItem) {
220
+ return (
221
+ <div className="h-[calc(100vh-72px)] w-full flex items-center justify-center bg-mw-neutral-50 dark:bg-mw-neutral-900">
222
+ <div className="flex flex-col items-center gap-3">
223
+ <div className="h-8 w-8 border-2 border-mw-primary-500 border-t-transparent rounded-full animate-spin" />
224
+ <p className="text-sm text-mw-neutral-500">Loading line item details...</p>
225
+ </div>
226
+ </div>
227
+ );
228
+ }
229
+
230
+ if (!lineItem) {
231
+ return (
232
+ <div className="h-[calc(100vh-72px)] w-full flex items-center justify-center bg-mw-neutral-50 dark:bg-mw-neutral-900">
233
+ <div className="flex flex-col items-center gap-3">
234
+ <p className="text-sm text-mw-neutral-500">Line item not found</p>
235
+ <Button variant="outline" onClick={() => setLocation(`/deals/${dealId}/line-items`)}>
236
+ <ArrowLeft className="h-4 w-4 mr-2" />
237
+ Back to Line Items
238
+ </Button>
239
+ </div>
240
+ </div>
241
+ );
242
+ }
243
+
244
+ const summaryCards = [
245
+ {
246
+ label: "Budget",
247
+ value: formatCurrency(budget, currency),
248
+ icon: DollarSign,
249
+ gradient: "from-emerald-500 to-teal-600",
250
+ bgLight: "bg-emerald-50",
251
+ },
252
+ {
253
+ label: "Impressions",
254
+ value: impressions ? impressions.toLocaleString() : "0",
255
+ icon: Eye,
256
+ gradient: "from-violet-500 to-purple-600",
257
+ bgLight: "bg-violet-50",
258
+ },
259
+ {
260
+ label: "Revenue",
261
+ value: formatCurrency(revenue, currency),
262
+ icon: TrendingUp,
263
+ gradient: "from-blue-500 to-indigo-600",
264
+ bgLight: "bg-blue-50",
265
+ },
266
+ {
267
+ label: "Flight Dates",
268
+ value: li?.startDate ? `${formatDate(li.startDate)} – ${formatDate(li.endDate)}` : "--",
269
+ icon: Calendar,
270
+ gradient: "from-amber-500 to-orange-600",
271
+ bgLight: "bg-amber-50",
272
+ },
273
+ ];
274
+
275
+ return (
276
+ <div className="h-[calc(100vh-72px)] w-full flex flex-col bg-slate-50 dark:bg-mw-neutral-900 overflow-auto">
277
+ <div className="flex-shrink-0 bg-white dark:bg-mw-neutral-800 border-b border-mw-neutral-200 dark:border-mw-neutral-700">
278
+ <div className="px-6 py-4">
279
+ <div className="flex items-center gap-2 text-xs text-mw-neutral-400 mb-3">
280
+ <Button
281
+ variant="ghost"
282
+ size="sm"
283
+ onClick={() => setLocation("/deals")}
284
+ >
285
+ Deals
286
+ </Button>
287
+ <ChevronRight className="h-3 w-3" />
288
+ <Button
289
+ variant="ghost"
290
+ size="sm"
291
+ onClick={() => setLocation(`/deals/${dealId}/line-items`)}
292
+ >
293
+ {deal?.name || "Deal"}
294
+ </Button>
295
+ <ChevronRight className="h-3 w-3" />
296
+ <span className="text-mw-neutral-600 dark:text-mw-neutral-300">{li?.name || "Line Item"}</span>
297
+ </div>
298
+
299
+ <div className="flex items-center justify-between">
300
+ <div className="flex items-center gap-4">
301
+ <Button
302
+ variant="outline"
303
+ size="sm"
304
+ className="h-8 px-3"
305
+ onClick={() => setLocation(`/deals/${dealId}/line-items`)}
306
+ >
307
+ <ArrowLeft className="h-3.5 w-3.5 mr-1.5" />
308
+ Back
309
+ </Button>
310
+ <div className="h-6 w-px bg-mw-neutral-200 dark:bg-mw-neutral-700" />
311
+ <div>
312
+ <div className="flex items-center gap-3">
313
+ <h1 className="text-lg font-semibold text-mw-neutral-900 dark:text-white">
314
+ {li?.name || "Untitled Line Item"}
315
+ </h1>
316
+ <Badge className={`${getStatusColor(li?.status)} text-xs font-medium px-2.5 py-0.5 rounded-full border`}>
317
+ {li?.status || "DRAFT"}
318
+ </Badge>
319
+ {dealMode && (
320
+ <Badge variant="outline" className="text-xs font-normal text-mw-neutral-500 rounded-full">
321
+ {dealMode}
322
+ </Badge>
323
+ )}
324
+ </div>
325
+ <div className="flex items-center gap-3 mt-1">
326
+ <span className="text-xs text-mw-neutral-400 flex items-center gap-1">
327
+ <Hash className="h-3 w-3" />
328
+ {lineItemId?.slice(0, 8)}
329
+ </span>
330
+ <span className="text-xs text-mw-neutral-300">·</span>
331
+ <span className="text-xs text-mw-neutral-400 flex items-center gap-1">
332
+ <Clock className="h-3 w-3" />
333
+ {calculateDuration(li?.startDate, li?.endDate)}
334
+ </span>
335
+ </div>
336
+ </div>
337
+ </div>
338
+ <div className="flex items-center gap-2">
339
+ {canEdit && (
340
+ <Button
341
+ size="sm"
342
+ className="bg-mw-primary-500 hover:bg-mw-primary-600 text-white h-9"
343
+ onClick={() => setLocation(`/deals/${dealId}/line-items/${lineItemId}/edit`)}
344
+ >
345
+ <Pencil className="h-3.5 w-3.5 mr-1.5" />
346
+ Edit Line Item
347
+ </Button>
348
+ )}
349
+ </div>
350
+ </div>
351
+ </div>
352
+ </div>
353
+
354
+ <div className="flex-1 p-6">
355
+ <div className="max-w-[1400px] mx-auto space-y-6">
356
+ <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
357
+ {summaryCards.map((card) => (
358
+ <div
359
+ key={card.label}
360
+ className="bg-white dark:bg-mw-neutral-800 rounded-xl border border-mw-neutral-200 dark:border-mw-neutral-700 p-4 hover:shadow-sm transition-shadow"
361
+ >
362
+ <div className="flex items-start justify-between">
363
+ <div className="flex-1">
364
+ <p className="text-xs font-medium text-mw-neutral-400 uppercase tracking-wider">{card.label}</p>
365
+ <p className="text-lg font-bold text-mw-neutral-900 dark:text-white mt-1.5">{card.value}</p>
366
+ </div>
367
+ <div className={`w-10 h-10 rounded-xl bg-gradient-to-br ${card.gradient} flex items-center justify-center shadow-sm`}>
368
+ <card.icon className="h-5 w-5 text-white" />
369
+ </div>
370
+ </div>
371
+ </div>
372
+ ))}
373
+ </div>
374
+
375
+ <div className="grid grid-cols-1 lg:grid-cols-12 gap-6">
376
+ <div className="lg:col-span-8 space-y-6">
377
+ <div className="bg-white dark:bg-mw-neutral-800 rounded-xl border border-mw-neutral-200 dark:border-mw-neutral-700 overflow-hidden">
378
+ <div className="px-5 py-4 border-b border-mw-neutral-100 dark:border-mw-neutral-700">
379
+ <div className="flex items-center gap-2.5">
380
+ <div className="w-7 h-7 rounded-lg bg-blue-50 dark:bg-blue-900/30 flex items-center justify-center">
381
+ <FileText className="h-3.5 w-3.5 text-blue-500" />
382
+ </div>
383
+ <h3 className="text-sm font-semibold text-mw-neutral-900 dark:text-white">Overview</h3>
384
+ </div>
385
+ </div>
386
+ <div className="p-5">
387
+ <div className="grid grid-cols-2 md:grid-cols-4 gap-y-5 gap-x-6">
388
+ {[
389
+ { label: "Status", value: li?.status || "--" },
390
+ { label: "Priority", value: li?.priority || li?.direct?.campaignGoal?.priority || "--" },
391
+ { label: "Creative Type", value: li?.creativeType || li?.direct?.campaignGoal?.creativeType || "--" },
392
+ { label: "Media Type", value: li?.mediaType || "DOOH" },
393
+ { label: "Start Date", value: formatDate(li?.startDate) },
394
+ { label: "End Date", value: formatDate(li?.endDate) },
395
+ { label: "Duration", value: calculateDuration(li?.startDate, li?.endDate) },
396
+ { label: "Pacing", value: li?.direct?.pacing?.type || (typeof li?.pacing === 'object' ? li?.pacing?.type : li?.pacing) || "--" },
397
+ ].map((item) => (
398
+ <div key={item.label}>
399
+ <p className="text-[11px] font-medium text-mw-neutral-400 uppercase tracking-wider mb-1">{item.label}</p>
400
+ <p className="text-sm font-medium text-mw-neutral-800 dark:text-mw-neutral-200">{item.value}</p>
401
+ </div>
402
+ ))}
403
+ </div>
404
+ </div>
405
+ </div>
406
+
407
+ <div className="bg-white dark:bg-mw-neutral-800 rounded-xl border border-mw-neutral-200 dark:border-mw-neutral-700 overflow-hidden">
408
+ <div className="px-5 py-4 border-b border-mw-neutral-100 dark:border-mw-neutral-700">
409
+ <div className="flex items-center gap-2.5">
410
+ <div className="w-7 h-7 rounded-lg bg-green-50 dark:bg-green-900/30 flex items-center justify-center">
411
+ <Target className="h-3.5 w-3.5 text-green-500" />
412
+ </div>
413
+ <h3 className="text-sm font-semibold text-mw-neutral-900 dark:text-white">Targeting</h3>
414
+ </div>
415
+ </div>
416
+ <div className="divide-y divide-mw-neutral-100 dark:divide-mw-neutral-700">
417
+ <div className="px-5 py-4">
418
+ <div className="flex items-center gap-2 mb-2.5">
419
+ <Users className="h-3.5 w-3.5 text-mw-neutral-400" />
420
+ <p className="text-xs font-semibold text-mw-neutral-500 uppercase tracking-wider">Demographics</p>
421
+ </div>
422
+ {demographics?.ageGroups?.length || demographics?.genders?.length || demographics?.gender ? (
423
+ <div className="flex flex-wrap gap-1.5">
424
+ {demographics.gender && (
425
+ <Badge variant="outline" className="text-xs rounded-full px-2.5 py-0.5 bg-violet-50 text-violet-600 border-violet-200">{demographics.gender}</Badge>
426
+ )}
427
+ {demographics.genders?.map((g: string, i: number) => (
428
+ <Badge key={`g-${i}`} variant="outline" className="text-xs rounded-full px-2.5 py-0.5 bg-violet-50 text-violet-600 border-violet-200">{g}</Badge>
429
+ ))}
430
+ {demographics.ageGroups?.map((age: string, i: number) => (
431
+ <Badge key={`a-${i}`} variant="outline" className="text-xs rounded-full px-2.5 py-0.5 bg-blue-50 text-blue-600 border-blue-200">{age}</Badge>
432
+ ))}
433
+ </div>
434
+ ) : (
435
+ <p className="text-sm text-mw-neutral-400 italic">No demographic targeting set</p>
436
+ )}
437
+ </div>
438
+ <div className="px-5 py-4">
439
+ <div className="flex items-center gap-2 mb-2.5">
440
+ <Layers className="h-3.5 w-3.5 text-mw-neutral-400" />
441
+ <p className="text-xs font-semibold text-mw-neutral-500 uppercase tracking-wider">Venue Types</p>
442
+ </div>
443
+ {Array.isArray(venueTypes) && venueTypes.length > 0 ? (
444
+ <div className="flex flex-wrap gap-1.5">
445
+ {venueTypes.map((v: any, i: number) => (
446
+ <Badge key={i} variant="outline" className="text-xs rounded-full px-2.5 py-0.5 bg-teal-50 text-teal-600 border-teal-200">
447
+ {typeof v === "string" ? v : v?.name || v?.label || "--"}
448
+ </Badge>
449
+ ))}
450
+ </div>
451
+ ) : (
452
+ <p className="text-sm text-mw-neutral-400 italic">No venue type targeting set</p>
453
+ )}
454
+ </div>
455
+ <div className="px-5 py-4">
456
+ <div className="flex items-center gap-2 mb-2.5">
457
+ <MapPin className="h-3.5 w-3.5 text-mw-neutral-400" />
458
+ <p className="text-xs font-semibold text-mw-neutral-500 uppercase tracking-wider">Geography</p>
459
+ </div>
460
+ {Array.isArray(geography) && geography.length > 0 ? (
461
+ <div className="flex flex-wrap gap-1.5">
462
+ {geography.map((g: any, i: number) => (
463
+ <Badge key={i} variant="outline" className="text-xs rounded-full px-2.5 py-0.5 bg-amber-50 text-amber-600 border-amber-200">
464
+ {typeof g === "string" ? g : g?.name || g?.city || g?.country || "--"}
465
+ </Badge>
466
+ ))}
467
+ </div>
468
+ ) : (
469
+ <p className="text-sm text-mw-neutral-400 italic">No geographic targeting set</p>
470
+ )}
471
+ </div>
472
+ {signals && (
473
+ <div className="px-5 py-4">
474
+ <div className="flex items-center gap-2 mb-2.5">
475
+ <Zap className="h-3.5 w-3.5 text-mw-neutral-400" />
476
+ <p className="text-xs font-semibold text-mw-neutral-500 uppercase tracking-wider">Signals</p>
477
+ </div>
478
+ <div className="flex flex-wrap gap-1.5">
479
+ {signals.signalEnabled ? (
480
+ <Badge variant="outline" className="text-xs rounded-full px-2.5 py-0.5 bg-orange-50 text-orange-600 border-orange-200">
481
+ {signals.signalName || signals.signalId || "Auto-activate enabled"}
482
+ </Badge>
483
+ ) : Array.isArray(signals) && signals.length > 0 ? (
484
+ signals.map((s: any, i: number) => (
485
+ <Badge key={i} variant="outline" className="text-xs rounded-full px-2.5 py-0.5 bg-orange-50 text-orange-600 border-orange-200">
486
+ {s.name || s.id || "--"}
487
+ </Badge>
488
+ ))
489
+ ) : (
490
+ <p className="text-sm text-mw-neutral-400 italic">No signals configured</p>
491
+ )}
492
+ </div>
493
+ </div>
494
+ )}
495
+ </div>
496
+ </div>
497
+
498
+ <div className="bg-white dark:bg-mw-neutral-800 rounded-xl border border-mw-neutral-200 dark:border-mw-neutral-700 overflow-hidden">
499
+ <div className="px-5 py-4 border-b border-mw-neutral-100 dark:border-mw-neutral-700">
500
+ <div className="flex items-center justify-between">
501
+ <div className="flex items-center gap-2.5">
502
+ <div className="w-7 h-7 rounded-lg bg-indigo-50 dark:bg-indigo-900/30 flex items-center justify-center">
503
+ <Monitor className="h-3.5 w-3.5 text-indigo-500" />
504
+ </div>
505
+ <h3 className="text-sm font-semibold text-mw-neutral-900 dark:text-white">
506
+ Inventory
507
+ </h3>
508
+ <Badge variant="secondary" className="text-xs rounded-full ml-1">{inventories.length}</Badge>
509
+ </div>
510
+ </div>
511
+ </div>
512
+ <div>
513
+ {inventories.length > 0 ? (
514
+ <div style={{ height: Math.min(inventories.length * 60 + 44, 400) }}>
515
+ <AgGridTable
516
+ rowData={inventories}
517
+ columnDefs={inventoryColumnDefs}
518
+ getRowId={(data) => String(data.id || data.inventoryId || Math.random())}
519
+ pagination={false}
520
+ emptyMessage="No inventory assigned"
521
+ />
522
+ </div>
523
+ ) : (
524
+ <div className="flex flex-col items-center justify-center py-12 text-center">
525
+ <div className="w-12 h-12 rounded-xl bg-slate-100 dark:bg-mw-neutral-700 flex items-center justify-center mb-3">
526
+ <Monitor className="h-6 w-6 text-mw-neutral-300" />
527
+ </div>
528
+ <p className="text-sm font-medium text-mw-neutral-500">No inventory assigned</p>
529
+ <p className="text-xs text-mw-neutral-400 mt-1">Inventory will appear here once assigned to this line item</p>
530
+ </div>
531
+ )}
532
+ </div>
533
+ </div>
534
+ </div>
535
+
536
+ <div className="lg:col-span-4 space-y-6">
537
+ <div className="bg-white dark:bg-mw-neutral-800 rounded-xl border border-mw-neutral-200 dark:border-mw-neutral-700 overflow-hidden">
538
+ <div className="px-5 py-4 border-b border-mw-neutral-100 dark:border-mw-neutral-700">
539
+ <div className="flex items-center justify-between">
540
+ <div className="flex items-center gap-2.5">
541
+ <div className="w-7 h-7 rounded-lg bg-pink-50 dark:bg-pink-900/30 flex items-center justify-center">
542
+ <Image className="h-3.5 w-3.5 text-pink-500" />
543
+ </div>
544
+ <h3 className="text-sm font-semibold text-mw-neutral-900 dark:text-white">
545
+ Creatives
546
+ </h3>
547
+ <Badge variant="secondary" className="text-xs rounded-full ml-1">{creatives.length}</Badge>
548
+ </div>
549
+ {canEdit && creatives.length > 0 && (
550
+ <Button
551
+ variant="outline"
552
+ size="sm"
553
+ className="h-7 text-xs"
554
+ onClick={() => setLocation(`/deals/${dealId}/line-items/${lineItemId}/creatives`)}
555
+ >
556
+ Manage
557
+ </Button>
558
+ )}
559
+ </div>
560
+ </div>
561
+ <div className="p-4">
562
+ {creatives.length > 0 ? (
563
+ <div className="space-y-3">
564
+ {creatives.map((cr: any, idx: number) => (
565
+ <div
566
+ key={cr.id || cr.creativeId || idx}
567
+ className="flex items-center gap-3 p-2.5 rounded-lg border border-mw-neutral-100 dark:border-mw-neutral-700 hover:border-mw-neutral-200 dark:hover:border-mw-neutral-600 transition-colors"
568
+ >
569
+ <div className="w-14 h-14 rounded-lg bg-slate-100 dark:bg-mw-neutral-700 overflow-hidden flex-shrink-0">
570
+ {(cr.thumbnail || cr.thumbnailUrl || cr.creativeUri || cr.previewUrl) ? (
571
+ <img
572
+ src={cr.thumbnail || cr.thumbnailUrl || cr.creativeUri || cr.previewUrl}
573
+ alt={cr.name || "Creative"}
574
+ className="w-full h-full object-cover"
575
+ onError={(e) => {
576
+ (e.target as HTMLImageElement).style.display = "none";
577
+ }}
578
+ />
579
+ ) : (
580
+ <div className="w-full h-full flex items-center justify-center">
581
+ <Image className="h-5 w-5 text-mw-neutral-300" />
582
+ </div>
583
+ )}
584
+ </div>
585
+ <div className="flex-1 min-w-0">
586
+ <p className="text-sm font-medium text-mw-neutral-800 dark:text-white truncate">
587
+ {cr.name || cr.creativeName || "Unnamed"}
588
+ </p>
589
+ {(cr.dimensions || cr.resolution) && (
590
+ <p className="text-xs text-mw-neutral-400 mt-0.5">
591
+ {cr.dimensions || cr.resolution}
592
+ </p>
593
+ )}
594
+ {cr.status && (
595
+ <Badge variant="outline" className="text-[10px] rounded-full mt-1 h-4 px-1.5">
596
+ {cr.status}
597
+ </Badge>
598
+ )}
599
+ </div>
600
+ </div>
601
+ ))}
602
+ </div>
603
+ ) : (
604
+ <div className="flex flex-col items-center justify-center py-10 text-center">
605
+ <div className="w-12 h-12 rounded-xl bg-slate-100 dark:bg-mw-neutral-700 flex items-center justify-center mb-3">
606
+ <Image className="h-6 w-6 text-mw-neutral-300" />
607
+ </div>
608
+ <p className="text-sm font-medium text-mw-neutral-500">No creatives assigned</p>
609
+ <p className="text-xs text-mw-neutral-400 mt-1">Assign creatives to this line item</p>
610
+ {canEdit && (
611
+ <Button
612
+ variant="outline"
613
+ size="sm"
614
+ className="mt-3 h-8 text-xs"
615
+ onClick={() => setLocation(`/deals/${dealId}/line-items/${lineItemId}/creatives`)}
616
+ >
617
+ <Image className="h-3.5 w-3.5 mr-1.5" />
618
+ Assign Creatives
619
+ </Button>
620
+ )}
621
+ </div>
622
+ )}
623
+ </div>
624
+ </div>
625
+
626
+ {(dealMode === "DIRECT" && li?.direct) && (
627
+ <div className="bg-white dark:bg-mw-neutral-800 rounded-xl border border-mw-neutral-200 dark:border-mw-neutral-700 overflow-hidden">
628
+ <div className="px-5 py-4 border-b border-mw-neutral-100 dark:border-mw-neutral-700">
629
+ <div className="flex items-center gap-2.5">
630
+ <div className="w-7 h-7 rounded-lg bg-emerald-50 dark:bg-emerald-900/30 flex items-center justify-center">
631
+ <BarChart3 className="h-3.5 w-3.5 text-emerald-500" />
632
+ </div>
633
+ <h3 className="text-sm font-semibold text-mw-neutral-900 dark:text-white">Budget & Goals</h3>
634
+ </div>
635
+ </div>
636
+ <div className="p-5 space-y-3">
637
+ {[
638
+ { label: "Budget Type", value: li.direct.budgetSetup?.budgetType || "--" },
639
+ { label: "Budget Amount", value: formatCurrency(li.direct.budgetSetup?.budgetAmount, currency) },
640
+ { label: "Goal Type", value: li.direct.campaignGoal?.type || "--" },
641
+ { label: "Target Value", value: li.direct.campaignGoal?.targetValue?.toLocaleString() || "--" },
642
+ { label: "Pacing", value: li.direct.pacing?.type || "--" },
643
+ ].map((item) => (
644
+ <div key={item.label} className="flex items-center justify-between py-1">
645
+ <p className="text-xs text-mw-neutral-400">{item.label}</p>
646
+ <p className="text-sm font-medium text-mw-neutral-700 dark:text-mw-neutral-300">{item.value}</p>
647
+ </div>
648
+ ))}
649
+ </div>
650
+ </div>
651
+ )}
652
+
653
+ {(dealMode === "PROGRAMMATIC" && li?.programmatic) && (
654
+ <div className="bg-white dark:bg-mw-neutral-800 rounded-xl border border-mw-neutral-200 dark:border-mw-neutral-700 overflow-hidden">
655
+ <div className="px-5 py-4 border-b border-mw-neutral-100 dark:border-mw-neutral-700">
656
+ <div className="flex items-center gap-2.5">
657
+ <div className="w-7 h-7 rounded-lg bg-emerald-50 dark:bg-emerald-900/30 flex items-center justify-center">
658
+ <BarChart3 className="h-3.5 w-3.5 text-emerald-500" />
659
+ </div>
660
+ <h3 className="text-sm font-semibold text-mw-neutral-900 dark:text-white">Programmatic</h3>
661
+ </div>
662
+ </div>
663
+ <div className="p-5 space-y-3">
664
+ {[
665
+ { label: "Bid Floor", value: formatCurrency(li.programmatic.bidFloor, currency) },
666
+ { label: "Net Cost", value: formatCurrency(li.programmatic.netCost, currency) },
667
+ { label: "Impressions", value: li.programmatic.impressions?.toLocaleString() || "--" },
668
+ { label: "Imp Multiplier", value: typeof li.programmatic.impMultiplier === 'object' ? (li.programmatic.impMultiplier?.type || JSON.stringify(li.programmatic.impMultiplier)) : (li.programmatic.impMultiplier?.toString() || "--") },
669
+ ].map((item) => (
670
+ <div key={item.label} className="flex items-center justify-between py-1">
671
+ <p className="text-xs text-mw-neutral-400">{item.label}</p>
672
+ <p className="text-sm font-medium text-mw-neutral-700 dark:text-mw-neutral-300">{item.value}</p>
673
+ </div>
674
+ ))}
675
+ </div>
676
+ </div>
677
+ )}
678
+ </div>
679
+ </div>
680
+ </div>
681
+ </div>
682
+ </div>
683
+ );
684
+ }