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