@burdenoff/microfe-kadaikodi 2026.704.3 → 2026.704.5
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/dist/kadaikodi/KadaikodiRoutes.js +34 -26
- package/dist/kadaikodi/KadaikodiRoutes.js.map +1 -1
- package/dist/kadaikodi/components/viz/AllocationBar.js +68 -0
- package/dist/kadaikodi/components/viz/AllocationBar.js.map +1 -0
- package/dist/kadaikodi/components/viz/AreaChart.js +102 -0
- package/dist/kadaikodi/components/viz/AreaChart.js.map +1 -0
- package/dist/kadaikodi/components/viz/AvatarStack.js +36 -0
- package/dist/kadaikodi/components/viz/AvatarStack.js.map +1 -0
- package/dist/kadaikodi/components/viz/BarChart.js +83 -0
- package/dist/kadaikodi/components/viz/BarChart.js.map +1 -0
- package/dist/kadaikodi/components/viz/CertificateCard.js +4 -0
- package/dist/kadaikodi/components/viz/DonutChart.js +109 -0
- package/dist/kadaikodi/components/viz/DonutChart.js.map +1 -0
- package/dist/kadaikodi/components/viz/HBarFunnel.js +54 -0
- package/dist/kadaikodi/components/viz/HBarFunnel.js.map +1 -0
- package/dist/kadaikodi/components/viz/Heatmap.js +3 -0
- package/dist/kadaikodi/components/viz/KanbanBoard.js +50 -0
- package/dist/kadaikodi/components/viz/KanbanBoard.js.map +1 -0
- package/dist/kadaikodi/components/viz/LineChart.js +4 -0
- package/dist/kadaikodi/components/viz/OrgTree.js +2 -0
- package/dist/kadaikodi/components/viz/PathSteps.js +3 -0
- package/dist/kadaikodi/components/viz/ProgressRing.js +68 -0
- package/dist/kadaikodi/components/viz/ProgressRing.js.map +1 -0
- package/dist/kadaikodi/components/viz/RatingStars.js +51 -0
- package/dist/kadaikodi/components/viz/RatingStars.js.map +1 -0
- package/dist/kadaikodi/components/viz/SegmentedTabs.js +69 -0
- package/dist/kadaikodi/components/viz/SegmentedTabs.js.map +1 -0
- package/dist/kadaikodi/components/viz/SkillBar.js +3 -0
- package/dist/kadaikodi/components/viz/Sparkline.js +74 -0
- package/dist/kadaikodi/components/viz/Sparkline.js.map +1 -0
- package/dist/kadaikodi/components/viz/StatCard.js +60 -0
- package/dist/kadaikodi/components/viz/StatCard.js.map +1 -0
- package/dist/kadaikodi/components/viz/StepProgress.js +64 -0
- package/dist/kadaikodi/components/viz/StepProgress.js.map +1 -0
- package/dist/kadaikodi/components/viz/Timeline.js +66 -0
- package/dist/kadaikodi/components/viz/Timeline.js.map +1 -0
- package/dist/kadaikodi/components/viz/index.js +23 -0
- package/dist/kadaikodi/components/viz/lib/Avatar.js +30 -0
- package/dist/kadaikodi/components/viz/lib/Avatar.js.map +1 -0
- package/dist/kadaikodi/components/viz/lib/CartesianFrame.js +119 -0
- package/dist/kadaikodi/components/viz/lib/CartesianFrame.js.map +1 -0
- package/dist/kadaikodi/components/viz/lib/ResponsiveSvg.js +35 -0
- package/dist/kadaikodi/components/viz/lib/ResponsiveSvg.js.map +1 -0
- package/dist/kadaikodi/components/viz/lib/format.js +21 -0
- package/dist/kadaikodi/components/viz/lib/format.js.map +1 -0
- package/dist/kadaikodi/components/viz/lib/geometry.js +61 -0
- package/dist/kadaikodi/components/viz/lib/geometry.js.map +1 -0
- package/dist/kadaikodi/components/viz/lib/hooks.js +39 -0
- package/dist/kadaikodi/components/viz/lib/hooks.js.map +1 -0
- package/dist/kadaikodi/components/viz/lib/palette.js +27 -0
- package/dist/kadaikodi/components/viz/lib/palette.js.map +1 -0
- package/dist/kadaikodi/hooks/useKadaikodiApi.js +84 -6
- package/dist/kadaikodi/hooks/useKadaikodiApi.js.map +1 -1
- package/dist/kadaikodi/pages/HomePage.js +373 -631
- package/dist/kadaikodi/pages/HomePage.js.map +1 -1
- package/dist/kadaikodi/pages/admin/AdminAnalyticsPage.js +304 -169
- package/dist/kadaikodi/pages/admin/AdminAnalyticsPage.js.map +1 -1
- package/dist/kadaikodi/pages/admin/AdminDashboard.js +255 -271
- package/dist/kadaikodi/pages/admin/AdminDashboard.js.map +1 -1
- package/dist/kadaikodi/pages/admin/AdminFinancialPage.js +185 -125
- package/dist/kadaikodi/pages/admin/AdminFinancialPage.js.map +1 -1
- package/dist/kadaikodi/pages/admin/AdminOrdersPage.js +141 -102
- package/dist/kadaikodi/pages/admin/AdminOrdersPage.js.map +1 -1
- package/dist/kadaikodi/pages/admin/AdminProfilePage.js.map +1 -1
- package/dist/kadaikodi/pages/admin/AdminSettingsPage.js +332 -276
- package/dist/kadaikodi/pages/admin/AdminSettingsPage.js.map +1 -1
- package/dist/kadaikodi/pages/admin/CartManagementPage.js +564 -729
- package/dist/kadaikodi/pages/admin/CartManagementPage.js.map +1 -1
- package/dist/kadaikodi/pages/admin/UserManagementPage.js +310 -366
- package/dist/kadaikodi/pages/admin/UserManagementPage.js.map +1 -1
- package/dist/kadaikodi/pages/cart-owner/AnalyticsDashboard.js +201 -120
- package/dist/kadaikodi/pages/cart-owner/AnalyticsDashboard.js.map +1 -1
- package/dist/kadaikodi/pages/cart-owner/BusinessSettingsPage.js +290 -257
- package/dist/kadaikodi/pages/cart-owner/BusinessSettingsPage.js.map +1 -1
- package/dist/kadaikodi/pages/cart-owner/CartOwnerDashboard.js +173 -194
- package/dist/kadaikodi/pages/cart-owner/CartOwnerDashboard.js.map +1 -1
- package/dist/kadaikodi/pages/cart-owner/CartOwnerProfilePage.js +270 -228
- package/dist/kadaikodi/pages/cart-owner/CartOwnerProfilePage.js.map +1 -1
- package/dist/kadaikodi/pages/cart-owner/CartServiceConfigPage.js +194 -173
- package/dist/kadaikodi/pages/cart-owner/CartServiceConfigPage.js.map +1 -1
- package/dist/kadaikodi/pages/cart-owner/InventoryManagementPage.js +428 -347
- package/dist/kadaikodi/pages/cart-owner/InventoryManagementPage.js.map +1 -1
- package/dist/kadaikodi/pages/cart-owner/InvestmentListingPage.js +332 -0
- package/dist/kadaikodi/pages/cart-owner/InvestmentListingPage.js.map +1 -0
- package/dist/kadaikodi/pages/cart-owner/MerchantOnboardingPage.js +373 -0
- package/dist/kadaikodi/pages/cart-owner/MerchantOnboardingPage.js.map +1 -0
- package/dist/kadaikodi/pages/cart-owner/OrderManagementPage.js +294 -162
- package/dist/kadaikodi/pages/cart-owner/OrderManagementPage.js.map +1 -1
- package/dist/kadaikodi/pages/cart-owner/StaffManagementPage.js +180 -181
- package/dist/kadaikodi/pages/cart-owner/StaffManagementPage.js.map +1 -1
- package/dist/kadaikodi/pages/common/HelpSupportPage.js +310 -317
- package/dist/kadaikodi/pages/common/HelpSupportPage.js.map +1 -1
- package/dist/kadaikodi/pages/common/NotificationCenterPage.js.map +1 -1
- package/dist/kadaikodi/pages/common/SettingsPage.js +502 -586
- package/dist/kadaikodi/pages/common/SettingsPage.js.map +1 -1
- package/dist/kadaikodi/pages/customer/BookingDetailsPage.js +239 -156
- package/dist/kadaikodi/pages/customer/BookingDetailsPage.js.map +1 -1
- package/dist/kadaikodi/pages/customer/CartDetailPage.js +405 -248
- package/dist/kadaikodi/pages/customer/CartDetailPage.js.map +1 -1
- package/dist/kadaikodi/pages/customer/CartDiscoveryPage.js +258 -232
- package/dist/kadaikodi/pages/customer/CartDiscoveryPage.js.map +1 -1
- package/dist/kadaikodi/pages/customer/CheckoutPage.js +254 -205
- package/dist/kadaikodi/pages/customer/CheckoutPage.js.map +1 -1
- package/dist/kadaikodi/pages/customer/CustomerDashboard.js +5 -5
- package/dist/kadaikodi/pages/customer/CustomerDashboard.js.map +1 -1
- package/dist/kadaikodi/pages/customer/CustomerProfilePage.js +189 -199
- package/dist/kadaikodi/pages/customer/CustomerProfilePage.js.map +1 -1
- package/dist/kadaikodi/pages/customer/DirectServiceBookingPage.js +272 -230
- package/dist/kadaikodi/pages/customer/DirectServiceBookingPage.js.map +1 -1
- package/dist/kadaikodi/pages/customer/FavoritesPage.js +150 -413
- package/dist/kadaikodi/pages/customer/FavoritesPage.js.map +1 -1
- package/dist/kadaikodi/pages/customer/OrderHistoryPage.js +173 -134
- package/dist/kadaikodi/pages/customer/OrderHistoryPage.js.map +1 -1
- package/dist/kadaikodi/pages/customer/OrderTrackingPage.js +411 -305
- package/dist/kadaikodi/pages/customer/OrderTrackingPage.js.map +1 -1
- package/dist/kadaikodi/pages/customer/ReviewsPage.js +310 -406
- package/dist/kadaikodi/pages/customer/ReviewsPage.js.map +1 -1
- package/dist/kadaikodi/pages/customer/SavedAddressesPage.js.map +1 -1
- package/dist/kadaikodi/pages/customer/shared.js +181 -0
- package/dist/kadaikodi/pages/customer/shared.js.map +1 -0
- package/dist/kadaikodi/pages/investor/InvestmentDetailsPage.js +438 -311
- package/dist/kadaikodi/pages/investor/InvestmentDetailsPage.js.map +1 -1
- package/dist/kadaikodi/pages/investor/InvestmentOpportunitiesPage.js +320 -293
- package/dist/kadaikodi/pages/investor/InvestmentOpportunitiesPage.js.map +1 -1
- package/dist/kadaikodi/pages/investor/InvestorAnalyticsPage.js +159 -91
- package/dist/kadaikodi/pages/investor/InvestorAnalyticsPage.js.map +1 -1
- package/dist/kadaikodi/pages/investor/InvestorDashboard.js +137 -107
- package/dist/kadaikodi/pages/investor/InvestorDashboard.js.map +1 -1
- package/dist/kadaikodi/pages/investor/InvestorInvestmentsPage.js +157 -135
- package/dist/kadaikodi/pages/investor/InvestorInvestmentsPage.js.map +1 -1
- package/dist/kadaikodi/pages/investor/InvestorPortfolioPage.js +280 -206
- package/dist/kadaikodi/pages/investor/InvestorPortfolioPage.js.map +1 -1
- package/dist/kadaikodi/pages/investor/InvestorProfilePage.js +441 -514
- package/dist/kadaikodi/pages/investor/InvestorProfilePage.js.map +1 -1
- package/dist/kadaikodi/pages/worker/TaskDetailsPage.js +428 -431
- package/dist/kadaikodi/pages/worker/TaskDetailsPage.js.map +1 -1
- package/dist/kadaikodi/pages/worker/WorkerDashboard.js +328 -445
- package/dist/kadaikodi/pages/worker/WorkerDashboard.js.map +1 -1
- package/dist/kadaikodi/pages/worker/WorkerOrdersPage.js +141 -164
- package/dist/kadaikodi/pages/worker/WorkerOrdersPage.js.map +1 -1
- package/dist/kadaikodi/pages/worker/WorkerProfilePage.js +360 -288
- package/dist/kadaikodi/pages/worker/WorkerProfilePage.js.map +1 -1
- package/dist/kadaikodi/pages/worker/WorkerSchedulePage.js +303 -281
- package/dist/kadaikodi/pages/worker/WorkerSchedulePage.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"InvestmentOpportunitiesPage.js","names":[],"sources":["../../../../src/kadaikodi/pages/investor/InvestmentOpportunitiesPage.tsx"],"sourcesContent":["/**\n * Investment Opportunities Page\n * @module kadaikodi/pages/investor\n *\n * Platform stats, 6 filters (search/category/risk/amount/location/sort),\n * invest modal with amount calculator, opportunity cards with images/funding\n * progress/business metrics/documents.\n */\n\nimport { useState, useCallback, useMemo } from 'react';\nimport {\n Search,\n FileText,\n IndianRupee,\n Eye,\n X,\n Briefcase,\n CheckCircle2,\n} from 'lucide-react';\nimport { EmphasisPanel, PagePurpose } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { EmptyState } from '../../components/EmptyState';\nimport { useKadaikodi } from '../../context/KadaikodiContext';\nimport { useInvestmentOpportunities, useInvest } from '../../hooks/useKadaikodiApi';\nimport { cn } from '../../utils/cn';\nimport { formatCurrency } from '../../utils/formatters';\nimport type { InvestmentOpportunity as DomainInvestmentOpportunity } from '../../types';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\ninterface InvestmentOpportunity {\n id: string;\n cartId: string;\n cartName: string;\n cartOwner: string;\n description: string;\n targetAmount: number;\n raisedAmount: number;\n minimumInvestment: number;\n maximumInvestment: number;\n investorCount: number;\n daysLeft: number;\n expectedReturns: number;\n paybackPeriod: number;\n riskLevel: 'low' | 'medium' | 'high';\n category: string;\n location: string;\n image: string;\n businessMetrics: {\n monthlyRevenue: number;\n monthlyOrders: number;\n averageOrderValue: number;\n rating: number;\n reviewCount: number;\n operatingSince: string;\n };\n financials: {\n grossMargin: number;\n netMargin: number;\n monthlyExpenses: number;\n breakEvenAchieved: boolean;\n };\n investmentTerms: {\n equityOffered: number;\n dividendFrequency: 'monthly' | 'quarterly' | 'yearly';\n lockInPeriod: number;\n exitOptions: string[];\n };\n documents: Array<{ name: string; type: string; uploadedOn: string }>;\n status: 'active' | 'funded' | 'closed';\n}\n\ninterface OpportunityFilters {\n searchQuery: string;\n category: string;\n riskLevel: string;\n minimumAmount: string;\n sortBy: string;\n}\n\n// ---------------------------------------------------------------------------\n// Mapping (real domain opportunity → this page's richer view-model)\n// ---------------------------------------------------------------------------\n\nfunction mapOpportunity(o: DomainInvestmentOpportunity): InvestmentOpportunity {\n const daysLeft = o.expiresAt\n ? Math.max(0, Math.ceil((new Date(o.expiresAt).getTime() - Date.now()) / 86400000))\n : 0;\n return {\n id: o.id,\n cartId: o.cartId,\n cartName: o.cartName,\n cartOwner: o.businessOwner || '',\n description: o.description,\n targetAmount: o.targetAmount,\n raisedAmount: o.raisedAmount,\n minimumInvestment: o.minimumInvestment,\n // The backend exposes a single target, not a per-investor cap; use the\n // remaining amount to target as the sensible maximum (never fabricated).\n maximumInvestment: Math.max(o.minimumInvestment, o.targetAmount - o.raisedAmount),\n investorCount: o.investors.length,\n daysLeft,\n expectedReturns: o.expectedReturns,\n paybackPeriod: o.investmentPeriod,\n riskLevel: o.riskLevel,\n category: '',\n location: '',\n image: '',\n businessMetrics: {\n monthlyRevenue: 0,\n monthlyOrders: 0,\n averageOrderValue: 0,\n rating: 0,\n reviewCount: 0,\n operatingSince: '',\n },\n financials: { grossMargin: 0, netMargin: 0, monthlyExpenses: 0, breakEvenAchieved: false },\n investmentTerms: {\n equityOffered: 0,\n dividendFrequency: 'quarterly',\n lockInPeriod: o.investmentPeriod,\n exitOptions: [],\n },\n documents: o.documents.map((d) => ({ name: d, type: 'PDF', uploadedOn: '' })),\n status: o.status,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction getRiskBadgeColor(risk: string): string {\n switch (risk) {\n case 'low':\n return 'bg-status-success-bg-subtle text-status-success-text';\n case 'medium':\n return 'bg-status-warning-bg-subtle text-status-warning-text';\n case 'high':\n return 'bg-status-error-bg-subtle text-status-error-text';\n default:\n return 'bg-bg-sunken text-text-secondary';\n }\n}\n\nfunction getRiskLabel(risk: string): string {\n switch (risk) {\n case 'low':\n return 'Low Risk';\n case 'medium':\n return 'Medium Risk';\n case 'high':\n return 'High Risk';\n default:\n return risk;\n }\n}\n\nfunction getFundingPercentage(raised: number, target: number): number {\n return target > 0 ? Math.min((raised / target) * 100, 100) : 0;\n}\n\n// ---------------------------------------------------------------------------\n// Component\n// ---------------------------------------------------------------------------\n\nexport function InvestmentOpportunitiesPage() {\n const { navigateTo, currentUser } = useKadaikodi();\n const { data: rawOpportunities = [], isLoading } = useInvestmentOpportunities();\n const invest = useInvest();\n const [selectedOpportunity, setSelectedOpportunity] = useState<InvestmentOpportunity | null>(\n null\n );\n const [showInvestModal, setShowInvestModal] = useState(false);\n const [investmentAmount, setInvestmentAmount] = useState('');\n const [investStatus, setInvestStatus] = useState<'idle' | 'success' | 'error'>('idle');\n\n const [filters, setFilters] = useState<OpportunityFilters>({\n searchQuery: '',\n category: '',\n riskLevel: '',\n minimumAmount: '',\n sortBy: 'featured',\n });\n\n const opportunities = useMemo(() => rawOpportunities.map(mapOpportunity), [rawOpportunities]);\n\n const platformStats = useMemo(() => {\n const count = opportunities.length;\n return {\n totalOpportunities: count,\n averageReturns: count\n ? Number((opportunities.reduce((s, o) => s + o.expectedReturns, 0) / count).toFixed(1))\n : 0,\n totalFunded: opportunities.reduce((s, o) => s + o.raisedAmount, 0),\n totalTarget: opportunities.reduce((s, o) => s + o.targetAmount, 0),\n };\n }, [opportunities]);\n\n const handleFilterChange = useCallback((key: keyof OpportunityFilters, value: string) => {\n setFilters((prev) => ({ ...prev, [key]: value }));\n }, []);\n\n const filteredOpportunities = useMemo(() => {\n let filtered = [...opportunities];\n\n // Search\n if (filters.searchQuery.trim()) {\n const query = filters.searchQuery.toLowerCase();\n filtered = filtered.filter(\n (opp) =>\n opp.cartName.toLowerCase().includes(query) ||\n opp.description.toLowerCase().includes(query) ||\n opp.category.toLowerCase().includes(query)\n );\n }\n\n // Category\n if (filters.category) {\n filtered = filtered.filter((opp) => opp.category === filters.category);\n }\n\n // Risk level\n if (filters.riskLevel) {\n filtered = filtered.filter((opp) => opp.riskLevel === filters.riskLevel);\n }\n\n // Minimum amount\n if (filters.minimumAmount) {\n const minAmount = parseInt(filters.minimumAmount, 10);\n filtered = filtered.filter((opp) => opp.minimumInvestment <= minAmount);\n }\n\n // Sort\n switch (filters.sortBy) {\n case 'returns':\n filtered.sort((a, b) => b.expectedReturns - a.expectedReturns);\n break;\n case 'funding':\n filtered.sort((a, b) => b.raisedAmount / b.targetAmount - a.raisedAmount / a.targetAmount);\n break;\n case 'closing':\n filtered.sort((a, b) => a.daysLeft - b.daysLeft);\n break;\n case 'minimum':\n filtered.sort((a, b) => a.minimumInvestment - b.minimumInvestment);\n break;\n default:\n break;\n }\n\n return filtered;\n }, [filters, opportunities]);\n\n const handleInvest = useCallback(() => {\n if (!selectedOpportunity || !investmentAmount) return;\n\n const amount = parseInt(investmentAmount, 10);\n if (isNaN(amount)) return;\n\n if (amount < selectedOpportunity.minimumInvestment) {\n setInvestStatus('error');\n return;\n }\n if (amount > selectedOpportunity.maximumInvestment) {\n setInvestStatus('error');\n return;\n }\n\n invest.mutate(\n {\n amount,\n investorId: currentUser?.id ?? '',\n opportunityId: selectedOpportunity.id,\n },\n {\n onSuccess: () => {\n setInvestStatus('success');\n setTimeout(() => {\n setShowInvestModal(false);\n setInvestmentAmount('');\n setInvestStatus('idle');\n navigateTo('/investor/portfolio');\n }, 1500);\n },\n onError: () => setInvestStatus('error'),\n }\n );\n }, [selectedOpportunity, investmentAmount, navigateTo, invest, currentUser?.id]);\n\n const openInvestModal = useCallback((opportunity: InvestmentOpportunity) => {\n setSelectedOpportunity(opportunity);\n setInvestmentAmount('');\n setInvestStatus('idle');\n setShowInvestModal(true);\n }, []);\n\n if (isLoading) {\n return (\n <PageLayout title=\"Investment Opportunities\" description=\"Loading opportunities...\">\n <div className=\"flex items-center justify-center py-20\">\n <div className=\"text-center\">\n <div className=\"mx-auto mb-4 h-12 w-12 animate-spin rounded-full border-b-2 border-primary\" />\n <p className=\"text-sm text-text-secondary\">Loading opportunities...</p>\n </div>\n </div>\n </PageLayout>\n );\n }\n\n return (\n <PageLayout\n title=\"Investment Opportunities\"\n description=\"Discover and invest in growing local businesses\"\n actions={\n <span className=\"text-sm text-text-secondary\">\n {filteredOpportunities.length} opportunities\n </span>\n }\n >\n <PagePurpose className=\"mb-6\">\n The deal flow — local carts and businesses looking for funding right now. Use this page to\n discover opportunities that fit your strategy, compare projected returns and equity on\n offer, filter by category or funding stage, and back the ones you believe in. The stats up\n top show how the platform is performing so you can gauge typical returns and success rates\n before you commit.\n </PagePurpose>\n\n {/* Platform Stats — the one emphasis zone for this page */}\n <EmphasisPanel>\n <h2 className=\"mb-4 text-base font-semibold text-text-primary\">Platform Performance</h2>\n <div className=\"grid grid-cols-2 gap-3 sm:gap-4 lg:grid-cols-4\">\n <div className=\"rounded-lg border border-border-subtle bg-bg-surface p-3 sm:p-4\">\n <p className=\"text-xs text-text-secondary\">Active Opportunities</p>\n <p className=\"text-lg font-bold text-text-primary sm:text-2xl\">\n {platformStats.totalOpportunities}\n </p>\n </div>\n <div className=\"rounded-lg border border-border-subtle bg-bg-surface p-3 sm:p-4\">\n <p className=\"text-xs text-text-secondary\">Average Returns</p>\n <p className=\"text-lg font-bold text-status-success-text sm:text-2xl\">\n {platformStats.averageReturns}%\n </p>\n </div>\n <div className=\"rounded-lg border border-border-subtle bg-bg-surface p-3 sm:p-4\">\n <p className=\"text-xs text-text-secondary\">Total Funded</p>\n <p className=\"text-lg font-bold text-text-primary sm:text-2xl\">\n {formatCurrency(platformStats.totalFunded, {\n minimumFractionDigits: 0,\n notation: 'compact',\n })}\n </p>\n </div>\n <div className=\"rounded-lg border border-border-subtle bg-bg-surface p-3 sm:p-4\">\n <p className=\"text-xs text-text-secondary\">Total Target</p>\n <p className=\"text-lg font-bold text-text-primary sm:text-2xl\">\n {formatCurrency(platformStats.totalTarget, {\n minimumFractionDigits: 0,\n notation: 'compact',\n })}\n </p>\n </div>\n </div>\n </EmphasisPanel>\n\n {/* Filters */}\n <div className=\"mt-6 rounded-lg border border-border-subtle bg-bg-surface p-4 sm:p-6\">\n <div className=\"grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-5\">\n {/* Search */}\n <div className=\"relative sm:col-span-2 lg:col-span-1\">\n <Search className=\"absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-text-secondary\" />\n <input\n type=\"text\"\n placeholder=\"Search opportunities...\"\n value={filters.searchQuery}\n onChange={(e) => handleFilterChange('searchQuery', e.target.value)}\n className=\"w-full rounded-lg border border-border-subtle bg-bg-surface py-2 pl-10 pr-4 text-sm text-text-primary placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n />\n </div>\n\n {/* Category */}\n <select\n value={filters.category}\n onChange={(e) => handleFilterChange('category', e.target.value)}\n className=\"rounded-lg border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n >\n <option value=\"\">All Categories</option>\n <option value=\"Healthy Food\">Healthy Food</option>\n <option value=\"Fast Food\">Fast Food</option>\n <option value=\"Fusion Food\">Fusion Food</option>\n <option value=\"Beverages\">Beverages</option>\n <option value=\"Street Food\">Street Food</option>\n </select>\n\n {/* Risk Level */}\n <select\n value={filters.riskLevel}\n onChange={(e) => handleFilterChange('riskLevel', e.target.value)}\n className=\"rounded-lg border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n >\n <option value=\"\">All Risk Levels</option>\n <option value=\"low\">Low Risk</option>\n <option value=\"medium\">Medium Risk</option>\n <option value=\"high\">High Risk</option>\n </select>\n\n {/* Minimum Amount */}\n <select\n value={filters.minimumAmount}\n onChange={(e) => handleFilterChange('minimumAmount', e.target.value)}\n className=\"rounded-lg border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n >\n <option value=\"\">Any Amount</option>\n <option value=\"15000\">Under ₹15,000</option>\n <option value=\"25000\">Under ₹25,000</option>\n <option value=\"50000\">Under ₹50,000</option>\n <option value=\"100000\">Under ₹1,00,000</option>\n </select>\n\n {/* Sort By */}\n <select\n value={filters.sortBy}\n onChange={(e) => handleFilterChange('sortBy', e.target.value)}\n className=\"rounded-lg border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n >\n <option value=\"featured\">Featured</option>\n <option value=\"returns\">Highest Returns</option>\n <option value=\"funding\">Most Funded</option>\n <option value=\"closing\">Closing Soon</option>\n <option value=\"minimum\">Lowest Minimum</option>\n </select>\n </div>\n </div>\n\n {/* Opportunities Grid */}\n {filteredOpportunities.length === 0 ? (\n <div className=\"mt-6\">\n <EmptyState\n icon={<Search className=\"h-10 w-10\" />}\n title=\"No opportunities found\"\n description=\"Try adjusting your filters to find more opportunities\"\n action={\n <button\n type=\"button\"\n onClick={() =>\n setFilters({\n searchQuery: '',\n category: '',\n riskLevel: '',\n minimumAmount: '',\n sortBy: 'featured',\n })\n }\n className=\"rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90 transition-colors\"\n >\n Clear Filters\n </button>\n }\n />\n </div>\n ) : (\n <div className=\"mt-6 grid grid-cols-1 gap-4 lg:grid-cols-2\">\n {filteredOpportunities.map((opportunity) => {\n const fundingPct = getFundingPercentage(\n opportunity.raisedAmount,\n opportunity.targetAmount\n );\n\n return (\n <div\n key={opportunity.id}\n className=\"rounded-lg border border-border-subtle bg-bg-surface shadow-sm hover:shadow-md transition-shadow\"\n >\n {/* Banner (providers have no image asset — show a neutral placeholder) */}\n <div className=\"relative\">\n <div className=\"flex h-40 w-full items-center justify-center overflow-hidden rounded-t-lg bg-bg-sunken sm:h-48\">\n <Briefcase className=\"h-10 w-10 text-text-secondary\" />\n </div>\n {opportunity.status === 'funded' && (\n <div className=\"absolute right-2 top-2 rounded-full bg-status-success-text px-2 py-1 text-xs font-medium text-white sm:right-4 sm:top-4 sm:px-3\">\n Fully Funded\n </div>\n )}\n {opportunity.daysLeft > 0 && opportunity.daysLeft <= 7 && (\n <div className=\"absolute left-2 top-2 rounded-full bg-status-error-text px-2 py-1 text-xs font-medium text-white sm:left-4 sm:top-4 sm:px-3\">\n {opportunity.daysLeft} days left\n </div>\n )}\n </div>\n\n <div className=\"p-4 sm:p-6\">\n {/* Header */}\n <div className=\"mb-4 flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between\">\n <div className=\"flex-1\">\n <h3 className=\"text-base font-semibold text-text-primary sm:text-xl\">\n {opportunity.cartName}\n </h3>\n <p className=\"text-xs text-text-secondary sm:text-sm\">\n by {opportunity.cartOwner}\n </p>\n <div className=\"mt-2 flex flex-wrap items-center gap-2\">\n <span className=\"text-xs text-text-secondary sm:text-sm\">\n {opportunity.category}\n </span>\n <span\n className={cn(\n 'rounded-full px-2 py-0.5 text-xs font-medium',\n getRiskBadgeColor(opportunity.riskLevel)\n )}\n >\n {getRiskLabel(opportunity.riskLevel)}\n </span>\n </div>\n </div>\n <div className=\"text-left sm:text-right\">\n <p className=\"text-xl font-bold text-status-success-text sm:text-2xl\">\n {opportunity.expectedReturns}%\n </p>\n <p className=\"text-xs text-text-secondary\">Expected Returns</p>\n </div>\n </div>\n\n {/* Description */}\n <p className=\"mb-4 text-sm leading-relaxed text-text-secondary line-clamp-2\">\n {opportunity.description}\n </p>\n\n {/* Funding Progress */}\n <div className=\"mb-4\">\n <div className=\"mb-1 flex justify-between text-xs sm:text-sm\">\n <span className=\"font-medium text-text-primary\">\n {formatCurrency(opportunity.raisedAmount, { minimumFractionDigits: 0 })}{' '}\n raised\n </span>\n <span className=\"text-text-secondary\">\n of {formatCurrency(opportunity.targetAmount, { minimumFractionDigits: 0 })}\n </span>\n </div>\n <div className=\"h-2 w-full overflow-hidden rounded-full bg-bg-sunken\">\n <div\n className=\"h-full rounded-full bg-action-primary-bg transition-all duration-300\"\n style={{ width: `${fundingPct}%` }}\n />\n </div>\n <div className=\"mt-1 flex justify-between text-xs text-text-secondary\">\n <span>{opportunity.investorCount} investors</span>\n <span>{fundingPct.toFixed(0)}% funded</span>\n </div>\n </div>\n\n {/* Key Metrics */}\n <div className=\"mb-4 grid grid-cols-2 gap-2 text-center sm:gap-4\">\n <div>\n <p className=\"text-sm font-bold text-text-primary sm:text-lg\">\n {formatCurrency(opportunity.minimumInvestment, {\n minimumFractionDigits: 0,\n })}\n </p>\n <p className=\"text-xs text-text-secondary\">Min Investment</p>\n </div>\n <div>\n <p className=\"text-sm font-bold text-text-primary sm:text-lg\">\n {opportunity.paybackPeriod} mo\n </p>\n <p className=\"text-xs text-text-secondary\">Lock-in Period</p>\n </div>\n </div>\n\n {/* Documents */}\n {opportunity.documents.length > 0 && (\n <div className=\"mb-4 border-t border-border-subtle pt-3\">\n <div className=\"flex items-center gap-1.5 text-xs text-text-secondary\">\n <FileText className=\"h-3.5 w-3.5\" />\n <span>{opportunity.documents.length} documents available</span>\n </div>\n </div>\n )}\n\n {/* Actions */}\n <div className=\"flex flex-col gap-2 sm:flex-row sm:gap-3\">\n <button\n type=\"button\"\n onClick={() => openInvestModal(opportunity)}\n disabled={opportunity.status === 'funded'}\n className={cn(\n 'flex-1 rounded-lg px-4 py-2.5 text-sm font-medium transition-colors',\n opportunity.status === 'funded'\n ? 'cursor-not-allowed bg-bg-sunken text-text-secondary'\n : 'bg-action-primary-bg text-action-primary-text hover:bg-action-primary-bg/90'\n )}\n >\n {opportunity.status === 'funded' ? 'Fully Funded' : 'Invest Now'}\n </button>\n <button\n type=\"button\"\n onClick={() => navigateTo(`/investor/opportunity/${opportunity.id}`)}\n className=\"flex flex-1 items-center justify-center gap-2 rounded-lg border border-border-subtle px-4 py-2.5 text-sm font-medium text-text-primary hover:bg-accent transition-colors\"\n >\n <Eye className=\"h-4 w-4\" />\n View Details\n </button>\n </div>\n </div>\n </div>\n );\n })}\n </div>\n )}\n\n {/* Investment Modal */}\n {showInvestModal && selectedOpportunity && (\n <div className=\"fixed inset-0 z-50 flex items-center justify-center bg-black/50 p-4\">\n <div className=\"relative max-h-[90vh] w-full max-w-md overflow-y-auto rounded-lg border border-border-subtle bg-bg-surface p-4 sm:p-6\">\n {/* Close button */}\n <button\n type=\"button\"\n onClick={() => {\n setShowInvestModal(false);\n setInvestmentAmount('');\n setInvestStatus('idle');\n }}\n className=\"absolute right-3 top-3 rounded-lg p-1 text-text-secondary hover:bg-accent transition-colors\"\n >\n <X className=\"h-5 w-5\" />\n </button>\n\n <h3 className=\"mb-4 text-base font-semibold text-text-primary sm:text-lg\">\n Invest in {selectedOpportunity.cartName}\n </h3>\n\n {investStatus === 'success' ? (\n <div className=\"py-8 text-center\">\n <CheckCircle2 className=\"mx-auto mb-3 h-12 w-12 text-status-success-text\" />\n <p className=\"text-sm font-medium text-text-primary\">Investment successful!</p>\n <p className=\"mt-1 text-xs text-text-secondary\">Redirecting to your portfolio...</p>\n </div>\n ) : (\n <div className=\"space-y-4\">\n {/* Investment Range */}\n <div>\n <p className=\"mb-1 text-xs text-text-secondary sm:text-sm\">Investment Range</p>\n <p className=\"text-sm font-medium text-text-primary sm:text-base\">\n {formatCurrency(selectedOpportunity.minimumInvestment, {\n minimumFractionDigits: 0,\n })}{' '}\n -{' '}\n {formatCurrency(selectedOpportunity.maximumInvestment, {\n minimumFractionDigits: 0,\n })}\n </p>\n </div>\n\n {/* Amount Input */}\n <div>\n <label className=\"mb-1 block text-xs font-medium text-text-primary sm:text-sm\">\n Investment Amount\n </label>\n <div className=\"relative\">\n <IndianRupee className=\"absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-text-secondary\" />\n <input\n type=\"number\"\n value={investmentAmount}\n onChange={(e) => {\n setInvestmentAmount(e.target.value);\n setInvestStatus('idle');\n }}\n placeholder=\"Enter amount\"\n className=\"w-full rounded-lg border border-border-subtle bg-bg-surface py-2 pl-10 pr-4 text-sm text-text-primary placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n />\n </div>\n {investStatus === 'error' && (\n <p className=\"mt-1 text-xs text-status-error-text\">\n Amount must be between{' '}\n {formatCurrency(selectedOpportunity.minimumInvestment, {\n minimumFractionDigits: 0,\n })}{' '}\n and{' '}\n {formatCurrency(selectedOpportunity.maximumInvestment, {\n minimumFractionDigits: 0,\n })}\n </p>\n )}\n </div>\n\n {/* Returns Calculator */}\n {investmentAmount &&\n !isNaN(parseInt(investmentAmount, 10)) &&\n parseInt(investmentAmount, 10) > 0 && (\n <div className=\"rounded-lg border border-border-subtle bg-bg-sunken/50 p-3 sm:p-4\">\n <p className=\"mb-2 text-xs text-text-secondary sm:text-sm\">\n Expected Returns\n </p>\n <div className=\"space-y-1.5\">\n <div className=\"flex justify-between\">\n <span className=\"text-xs text-text-primary sm:text-sm\">Investment:</span>\n <span className=\"text-xs font-medium text-text-primary sm:text-sm\">\n {formatCurrency(parseInt(investmentAmount, 10), {\n minimumFractionDigits: 0,\n })}\n </span>\n </div>\n <div className=\"flex justify-between\">\n <span className=\"text-xs text-text-primary sm:text-sm\">\n Expected Return ({selectedOpportunity.expectedReturns}%):\n </span>\n <span className=\"text-xs font-medium text-status-success-text sm:text-sm\">\n +\n {formatCurrency(\n (parseInt(investmentAmount, 10) *\n selectedOpportunity.expectedReturns) /\n 100,\n { minimumFractionDigits: 0 }\n )}\n </span>\n </div>\n <div className=\"flex justify-between border-t border-border-subtle pt-2\">\n <span className=\"text-xs font-medium text-text-primary sm:text-sm\">\n Total Value:\n </span>\n <span className=\"text-xs font-bold text-text-primary sm:text-sm\">\n {formatCurrency(\n parseInt(investmentAmount, 10) *\n (1 + selectedOpportunity.expectedReturns / 100),\n { minimumFractionDigits: 0 }\n )}\n </span>\n </div>\n </div>\n </div>\n )}\n\n {/* Investment Terms */}\n <div className=\"space-y-1 text-xs text-text-secondary sm:text-sm\">\n <p>\n • Minimum lock-in period:{' '}\n {selectedOpportunity.investmentTerms.lockInPeriod} months\n </p>\n <p>\n • Expected returns: {selectedOpportunity.expectedReturns}% over the lock-in\n period\n </p>\n </div>\n\n {/* Actions */}\n <div className=\"flex flex-col gap-2 pt-2 sm:flex-row sm:gap-3\">\n <button\n type=\"button\"\n onClick={() => {\n setShowInvestModal(false);\n setInvestmentAmount('');\n setInvestStatus('idle');\n }}\n className=\"flex-1 rounded-lg border border-border-subtle px-4 py-2.5 text-sm font-medium text-text-primary hover:bg-accent transition-colors\"\n >\n Cancel\n </button>\n <button\n type=\"button\"\n onClick={handleInvest}\n disabled={\n !investmentAmount ||\n isNaN(parseInt(investmentAmount, 10)) ||\n parseInt(investmentAmount, 10) < selectedOpportunity.minimumInvestment\n }\n className={cn(\n 'flex-1 rounded-lg px-4 py-2.5 text-sm font-medium transition-colors',\n !investmentAmount ||\n isNaN(parseInt(investmentAmount, 10)) ||\n parseInt(investmentAmount, 10) < selectedOpportunity.minimumInvestment\n ? 'cursor-not-allowed bg-bg-sunken text-text-secondary'\n : 'bg-action-primary-bg text-action-primary-text hover:bg-action-primary-bg/90'\n )}\n >\n Confirm Investment\n </button>\n </div>\n </div>\n )}\n </div>\n </div>\n )}\n </PageLayout>\n );\n}\n"],"mappings":";;;;;;;;;;;AAsFA,SAAS,EAAe,GAAuD;CAC7E,IAAM,IAAW,EAAE,YACf,KAAK,IAAI,GAAG,KAAK,MAAM,IAAI,KAAK,EAAE,UAAU,CAAC,SAAS,GAAG,KAAK,KAAK,IAAI,MAAS,CAAC,GACjF;AACJ,QAAO;EACL,IAAI,EAAE;EACN,QAAQ,EAAE;EACV,UAAU,EAAE;EACZ,WAAW,EAAE,iBAAiB;EAC9B,aAAa,EAAE;EACf,cAAc,EAAE;EAChB,cAAc,EAAE;EAChB,mBAAmB,EAAE;EAGrB,mBAAmB,KAAK,IAAI,EAAE,mBAAmB,EAAE,eAAe,EAAE,aAAa;EACjF,eAAe,EAAE,UAAU;EAC3B;EACA,iBAAiB,EAAE;EACnB,eAAe,EAAE;EACjB,WAAW,EAAE;EACb,UAAU;EACV,UAAU;EACV,OAAO;EACP,iBAAiB;GACf,gBAAgB;GAChB,eAAe;GACf,mBAAmB;GACnB,QAAQ;GACR,aAAa;GACb,gBAAgB;GACjB;EACD,YAAY;GAAE,aAAa;GAAG,WAAW;GAAG,iBAAiB;GAAG,mBAAmB;GAAO;EAC1F,iBAAiB;GACf,eAAe;GACf,mBAAmB;GACnB,cAAc,EAAE;GAChB,aAAa,EAAE;GAChB;EACD,WAAW,EAAE,UAAU,KAAK,OAAO;GAAE,MAAM;GAAG,MAAM;GAAO,YAAY;GAAI,EAAE;EAC7E,QAAQ,EAAE;EACX;;AAOH,SAAS,EAAkB,GAAsB;AAC/C,SAAQ,GAAR;EACE,KAAK,MACH,QAAO;EACT,KAAK,SACH,QAAO;EACT,KAAK,OACH,QAAO;EACT,QACE,QAAO;;;AAIb,SAAS,EAAa,GAAsB;AAC1C,SAAQ,GAAR;EACE,KAAK,MACH,QAAO;EACT,KAAK,SACH,QAAO;EACT,KAAK,OACH,QAAO;EACT,QACE,QAAO;;;AAIb,SAAS,EAAqB,GAAgB,GAAwB;AACpE,QAAO,IAAS,IAAI,KAAK,IAAK,IAAS,IAAU,KAAK,IAAI,GAAG;;AAO/D,SAAgB,IAA8B;CAC5C,IAAM,EAAE,eAAY,mBAAgB,GAAc,EAC5C,EAAE,MAAM,IAAmB,EAAE,EAAE,iBAAc,GAA4B,EACzE,IAAS,GAAW,EACpB,CAAC,GAAqB,KAA0B,EACpD,KACD,EACK,CAAC,GAAiB,KAAsB,EAAS,GAAM,EACvD,CAAC,GAAkB,KAAuB,EAAS,GAAG,EACtD,CAAC,GAAc,KAAmB,EAAuC,OAAO,EAEhF,CAAC,GAAS,KAAc,EAA6B;EACzD,aAAa;EACb,UAAU;EACV,WAAW;EACX,eAAe;EACf,QAAQ;EACT,CAAC,EAEI,IAAgB,QAAc,EAAiB,IAAI,EAAe,EAAE,CAAC,EAAiB,CAAC,EAEvF,IAAgB,QAAc;EAClC,IAAM,IAAQ,EAAc;AAC5B,SAAO;GACL,oBAAoB;GACpB,gBAAgB,IACZ,QAAQ,EAAc,QAAQ,GAAG,MAAM,IAAI,EAAE,iBAAiB,EAAE,GAAG,GAAO,QAAQ,EAAE,CAAC,GACrF;GACJ,aAAa,EAAc,QAAQ,GAAG,MAAM,IAAI,EAAE,cAAc,EAAE;GAClE,aAAa,EAAc,QAAQ,GAAG,MAAM,IAAI,EAAE,cAAc,EAAE;GACnE;IACA,CAAC,EAAc,CAAC,EAEb,IAAqB,GAAa,GAA+B,MAAkB;AACvF,KAAY,OAAU;GAAE,GAAG;IAAO,IAAM;GAAO,EAAE;IAChD,EAAE,CAAC,EAEA,IAAwB,QAAc;EAC1C,IAAI,IAAW,CAAC,GAAG,EAAc;AAGjC,MAAI,EAAQ,YAAY,MAAM,EAAE;GAC9B,IAAM,IAAQ,EAAQ,YAAY,aAAa;AAC/C,OAAW,EAAS,QACjB,MACC,EAAI,SAAS,aAAa,CAAC,SAAS,EAAM,IAC1C,EAAI,YAAY,aAAa,CAAC,SAAS,EAAM,IAC7C,EAAI,SAAS,aAAa,CAAC,SAAS,EAAM,CAC7C;;AAcH,MAVI,EAAQ,aACV,IAAW,EAAS,QAAQ,MAAQ,EAAI,aAAa,EAAQ,SAAS,GAIpE,EAAQ,cACV,IAAW,EAAS,QAAQ,MAAQ,EAAI,cAAc,EAAQ,UAAU,GAItE,EAAQ,eAAe;GACzB,IAAM,IAAY,SAAS,EAAQ,eAAe,GAAG;AACrD,OAAW,EAAS,QAAQ,MAAQ,EAAI,qBAAqB,EAAU;;AAIzE,UAAQ,EAAQ,QAAhB;GACE,KAAK;AACH,MAAS,MAAM,GAAG,MAAM,EAAE,kBAAkB,EAAE,gBAAgB;AAC9D;GACF,KAAK;AACH,MAAS,MAAM,GAAG,MAAM,EAAE,eAAe,EAAE,eAAe,EAAE,eAAe,EAAE,aAAa;AAC1F;GACF,KAAK;AACH,MAAS,MAAM,GAAG,MAAM,EAAE,WAAW,EAAE,SAAS;AAChD;GACF,KAAK;AACH,MAAS,MAAM,GAAG,MAAM,EAAE,oBAAoB,EAAE,kBAAkB;AAClE;GACF,QACE;;AAGJ,SAAO;IACN,CAAC,GAAS,EAAc,CAAC,EAEtB,IAAe,QAAkB;AACrC,MAAI,CAAC,KAAuB,CAAC,EAAkB;EAE/C,IAAM,IAAS,SAAS,GAAkB,GAAG;AACzC,aAAM,EAAO,EAEjB;OAAI,IAAS,EAAoB,mBAAmB;AAClD,MAAgB,QAAQ;AACxB;;AAEF,OAAI,IAAS,EAAoB,mBAAmB;AAClD,MAAgB,QAAQ;AACxB;;AAGF,KAAO,OACL;IACE;IACA,YAAY,GAAa,MAAM;IAC/B,eAAe,EAAoB;IACpC,EACD;IACE,iBAAiB;AAEf,KADA,EAAgB,UAAU,EAC1B,iBAAiB;AAIf,MAHA,EAAmB,GAAM,EACzB,EAAoB,GAAG,EACvB,EAAgB,OAAO,EACvB,EAAW,sBAAsB;QAChC,KAAK;;IAEV,eAAe,EAAgB,QAAQ;IACxC,CACF;;IACA;EAAC;EAAqB;EAAkB;EAAY;EAAQ,GAAa;EAAG,CAAC,EAE1E,IAAkB,GAAa,MAAuC;AAI1E,EAHA,EAAuB,EAAY,EACnC,EAAoB,GAAG,EACvB,EAAgB,OAAO,EACvB,EAAmB,GAAK;IACvB,EAAE,CAAC;AAeN,QAbI,IAEA,kBAAC,GAAD;EAAY,OAAM;EAA2B,aAAY;YACvD,kBAAC,OAAD;GAAK,WAAU;aACb,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,OAAD,EAAK,WAAU,8EAA+E,CAAA,EAC9F,kBAAC,KAAD;KAAG,WAAU;eAA8B;KAA4B,CAAA,CACnE;;GACF,CAAA;EACK,CAAA,GAKf,kBAAC,GAAD;EACE,OAAM;EACN,aAAY;EACZ,SACE,kBAAC,QAAD;GAAM,WAAU;aAAhB,CACG,EAAsB,QAAO,iBACzB;;YANX;GASE,kBAAC,GAAD;IAAa,WAAU;cAAO;IAMhB,CAAA;GAGd,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,MAAD;IAAI,WAAU;cAAiD;IAAyB,CAAA,EACxF,kBAAC,OAAD;IAAK,WAAU;cAAf;KACE,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,KAAD;OAAG,WAAU;iBAA8B;OAAwB,CAAA,EACnE,kBAAC,KAAD;OAAG,WAAU;iBACV,EAAc;OACb,CAAA,CACA;;KACN,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,KAAD;OAAG,WAAU;iBAA8B;OAAmB,CAAA,EAC9D,kBAAC,KAAD;OAAG,WAAU;iBAAb,CACG,EAAc,gBAAe,IAC5B;SACA;;KACN,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,KAAD;OAAG,WAAU;iBAA8B;OAAgB,CAAA,EAC3D,kBAAC,KAAD;OAAG,WAAU;iBACV,EAAe,EAAc,aAAa;QACzC,uBAAuB;QACvB,UAAU;QACX,CAAC;OACA,CAAA,CACA;;KACN,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,KAAD;OAAG,WAAU;iBAA8B;OAAgB,CAAA,EAC3D,kBAAC,KAAD;OAAG,WAAU;iBACV,EAAe,EAAc,aAAa;QACzC,uBAAuB;QACvB,UAAU;QACX,CAAC;OACA,CAAA,CACA;;KACF;MACQ,EAAA,CAAA;GAGhB,kBAAC,OAAD;IAAK,WAAU;cACb,kBAAC,OAAD;KAAK,WAAU;eAAf;MAEE,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,GAAD,EAAQ,WAAU,wEAAyE,CAAA,EAC3F,kBAAC,SAAD;QACE,MAAK;QACL,aAAY;QACZ,OAAO,EAAQ;QACf,WAAW,MAAM,EAAmB,eAAe,EAAE,OAAO,MAAM;QAClE,WAAU;QACV,CAAA,CACE;;MAGN,kBAAC,UAAD;OACE,OAAO,EAAQ;OACf,WAAW,MAAM,EAAmB,YAAY,EAAE,OAAO,MAAM;OAC/D,WAAU;iBAHZ;QAKE,kBAAC,UAAD;SAAQ,OAAM;mBAAG;SAAuB,CAAA;QACxC,kBAAC,UAAD;SAAQ,OAAM;mBAAe;SAAqB,CAAA;QAClD,kBAAC,UAAD;SAAQ,OAAM;mBAAY;SAAkB,CAAA;QAC5C,kBAAC,UAAD;SAAQ,OAAM;mBAAc;SAAoB,CAAA;QAChD,kBAAC,UAAD;SAAQ,OAAM;mBAAY;SAAkB,CAAA;QAC5C,kBAAC,UAAD;SAAQ,OAAM;mBAAc;SAAoB,CAAA;QACzC;;MAGT,kBAAC,UAAD;OACE,OAAO,EAAQ;OACf,WAAW,MAAM,EAAmB,aAAa,EAAE,OAAO,MAAM;OAChE,WAAU;iBAHZ;QAKE,kBAAC,UAAD;SAAQ,OAAM;mBAAG;SAAwB,CAAA;QACzC,kBAAC,UAAD;SAAQ,OAAM;mBAAM;SAAiB,CAAA;QACrC,kBAAC,UAAD;SAAQ,OAAM;mBAAS;SAAoB,CAAA;QAC3C,kBAAC,UAAD;SAAQ,OAAM;mBAAO;SAAkB,CAAA;QAChC;;MAGT,kBAAC,UAAD;OACE,OAAO,EAAQ;OACf,WAAW,MAAM,EAAmB,iBAAiB,EAAE,OAAO,MAAM;OACpE,WAAU;iBAHZ;QAKE,kBAAC,UAAD;SAAQ,OAAM;mBAAG;SAAmB,CAAA;QACpC,kBAAC,UAAD;SAAQ,OAAM;mBAAQ;SAA6B,CAAA;QACnD,kBAAC,UAAD;SAAQ,OAAM;mBAAQ;SAA6B,CAAA;QACnD,kBAAC,UAAD;SAAQ,OAAM;mBAAQ;SAA6B,CAAA;QACnD,kBAAC,UAAD;SAAQ,OAAM;mBAAS;SAA+B,CAAA;QAC/C;;MAGT,kBAAC,UAAD;OACE,OAAO,EAAQ;OACf,WAAW,MAAM,EAAmB,UAAU,EAAE,OAAO,MAAM;OAC7D,WAAU;iBAHZ;QAKE,kBAAC,UAAD;SAAQ,OAAM;mBAAW;SAAiB,CAAA;QAC1C,kBAAC,UAAD;SAAQ,OAAM;mBAAU;SAAwB,CAAA;QAChD,kBAAC,UAAD;SAAQ,OAAM;mBAAU;SAAoB,CAAA;QAC5C,kBAAC,UAAD;SAAQ,OAAM;mBAAU;SAAqB,CAAA;QAC7C,kBAAC,UAAD;SAAQ,OAAM;mBAAU;SAAuB,CAAA;QACxC;;MACL;;IACF,CAAA;GAGL,EAAsB,WAAW,IAChC,kBAAC,OAAD;IAAK,WAAU;cACb,kBAAC,GAAD;KACE,MAAM,kBAAC,GAAD,EAAQ,WAAU,aAAc,CAAA;KACtC,OAAM;KACN,aAAY;KACZ,QACE,kBAAC,UAAD;MACE,MAAK;MACL,eACE,EAAW;OACT,aAAa;OACb,UAAU;OACV,WAAW;OACX,eAAe;OACf,QAAQ;OACT,CAAC;MAEJ,WAAU;gBACX;MAEQ,CAAA;KAEX,CAAA;IACE,CAAA,GAEN,kBAAC,OAAD;IAAK,WAAU;cACZ,EAAsB,KAAK,MAAgB;KAC1C,IAAM,IAAa,EACjB,EAAY,cACZ,EAAY,aACb;AAED,YACE,kBAAC,OAAD;MAEE,WAAU;gBAFZ,CAKE,kBAAC,OAAD;OAAK,WAAU;iBAAf;QACE,kBAAC,OAAD;SAAK,WAAU;mBACb,kBAAC,GAAD,EAAW,WAAU,iCAAkC,CAAA;SACnD,CAAA;QACL,EAAY,WAAW,YACtB,kBAAC,OAAD;SAAK,WAAU;mBAAkI;SAE3I,CAAA;QAEP,EAAY,WAAW,KAAK,EAAY,YAAY,KACnD,kBAAC,OAAD;SAAK,WAAU;mBAAf,CACG,EAAY,UAAS,aAClB;;QAEJ;UAEN,kBAAC,OAAD;OAAK,WAAU;iBAAf;QAEE,kBAAC,OAAD;SAAK,WAAU;mBAAf,CACE,kBAAC,OAAD;UAAK,WAAU;oBAAf;WACE,kBAAC,MAAD;YAAI,WAAU;sBACX,EAAY;YACV,CAAA;WACL,kBAAC,KAAD;YAAG,WAAU;sBAAb,CAAsD,OAChD,EAAY,UACd;;WACJ,kBAAC,OAAD;YAAK,WAAU;sBAAf,CACE,kBAAC,QAAD;aAAM,WAAU;uBACb,EAAY;aACR,CAAA,EACP,kBAAC,QAAD;aACE,WAAW,EACT,gDACA,EAAkB,EAAY,UAAU,CACzC;uBAEA,EAAa,EAAY,UAAU;aAC/B,CAAA,CACH;;WACF;aACN,kBAAC,OAAD;UAAK,WAAU;oBAAf,CACE,kBAAC,KAAD;WAAG,WAAU;qBAAb,CACG,EAAY,iBAAgB,IAC3B;cACJ,kBAAC,KAAD;WAAG,WAAU;qBAA8B;WAAoB,CAAA,CAC3D;YACF;;QAGN,kBAAC,KAAD;SAAG,WAAU;mBACV,EAAY;SACX,CAAA;QAGJ,kBAAC,OAAD;SAAK,WAAU;mBAAf;UACE,kBAAC,OAAD;WAAK,WAAU;qBAAf,CACE,kBAAC,QAAD;YAAM,WAAU;sBAAhB;aACG,EAAe,EAAY,cAAc,EAAE,uBAAuB,GAAG,CAAC;aAAE;aAAI;aAExE;eACP,kBAAC,QAAD;YAAM,WAAU;sBAAhB,CAAsC,OAChC,EAAe,EAAY,cAAc,EAAE,uBAAuB,GAAG,CAAC,CACrE;cACH;;UACN,kBAAC,OAAD;WAAK,WAAU;qBACb,kBAAC,OAAD;YACE,WAAU;YACV,OAAO,EAAE,OAAO,GAAG,EAAW,IAAI;YAClC,CAAA;WACE,CAAA;UACN,kBAAC,OAAD;WAAK,WAAU;qBAAf,CACE,kBAAC,QAAD,EAAA,UAAA,CAAO,EAAY,eAAc,aAAiB,EAAA,CAAA,EAClD,kBAAC,QAAD,EAAA,UAAA,CAAO,EAAW,QAAQ,EAAE,EAAC,WAAe,EAAA,CAAA,CACxC;;UACF;;QAGN,kBAAC,OAAD;SAAK,WAAU;mBAAf,CACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,KAAD;UAAG,WAAU;oBACV,EAAe,EAAY,mBAAmB,EAC7C,uBAAuB,GACxB,CAAC;UACA,CAAA,EACJ,kBAAC,KAAD;UAAG,WAAU;oBAA8B;UAAkB,CAAA,CACzD,EAAA,CAAA,EACN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,KAAD;UAAG,WAAU;oBAAb,CACG,EAAY,eAAc,MACzB;aACJ,kBAAC,KAAD;UAAG,WAAU;oBAA8B;UAAkB,CAAA,CACzD,EAAA,CAAA,CACF;;QAGL,EAAY,UAAU,SAAS,KAC9B,kBAAC,OAAD;SAAK,WAAU;mBACb,kBAAC,OAAD;UAAK,WAAU;oBAAf,CACE,kBAAC,GAAD,EAAU,WAAU,eAAgB,CAAA,EACpC,kBAAC,QAAD,EAAA,UAAA,CAAO,EAAY,UAAU,QAAO,uBAA2B,EAAA,CAAA,CAC3D;;SACF,CAAA;QAIR,kBAAC,OAAD;SAAK,WAAU;mBAAf,CACE,kBAAC,UAAD;UACE,MAAK;UACL,eAAe,EAAgB,EAAY;UAC3C,UAAU,EAAY,WAAW;UACjC,WAAW,EACT,uEACA,EAAY,WAAW,WACnB,wDACA,8EACL;oBAEA,EAAY,WAAW,WAAW,iBAAiB;UAC7C,CAAA,EACT,kBAAC,UAAD;UACE,MAAK;UACL,eAAe,EAAW,yBAAyB,EAAY,KAAK;UACpE,WAAU;oBAHZ,CAKE,kBAAC,GAAD,EAAK,WAAU,WAAY,CAAA,EAAA,eAEpB;YACL;;QACF;SACF;QArIC,EAAY,GAqIb;MAER;IACE,CAAA;GAIP,KAAmB,KAClB,kBAAC,OAAD;IAAK,WAAU;cACb,kBAAC,OAAD;KAAK,WAAU;eAAf;MAEE,kBAAC,UAAD;OACE,MAAK;OACL,eAAe;AAGb,QAFA,EAAmB,GAAM,EACzB,EAAoB,GAAG,EACvB,EAAgB,OAAO;;OAEzB,WAAU;iBAEV,kBAAC,GAAD,EAAG,WAAU,WAAY,CAAA;OAClB,CAAA;MAET,kBAAC,MAAD;OAAI,WAAU;iBAAd,CAA0E,cAC7D,EAAoB,SAC5B;;MAEJ,MAAiB,YAChB,kBAAC,OAAD;OAAK,WAAU;iBAAf;QACE,kBAAC,GAAD,EAAc,WAAU,mDAAoD,CAAA;QAC5E,kBAAC,KAAD;SAAG,WAAU;mBAAwC;SAA0B,CAAA;QAC/E,kBAAC,KAAD;SAAG,WAAU;mBAAmC;SAAoC,CAAA;QAChF;WAEN,kBAAC,OAAD;OAAK,WAAU;iBAAf;QAEE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,KAAD;SAAG,WAAU;mBAA8C;SAAoB,CAAA,EAC/E,kBAAC,KAAD;SAAG,WAAU;mBAAb;UACG,EAAe,EAAoB,mBAAmB,EACrD,uBAAuB,GACxB,CAAC;UAAE;UAAI;UACN;UACD,EAAe,EAAoB,mBAAmB,EACrD,uBAAuB,GACxB,CAAC;UACA;WACA,EAAA,CAAA;QAGN,kBAAC,OAAD,EAAA,UAAA;SACE,kBAAC,SAAD;UAAO,WAAU;oBAA8D;UAEvE,CAAA;SACR,kBAAC,OAAD;UAAK,WAAU;oBAAf,CACE,kBAAC,GAAD,EAAa,WAAU,wEAAyE,CAAA,EAChG,kBAAC,SAAD;WACE,MAAK;WACL,OAAO;WACP,WAAW,MAAM;AAEf,YADA,EAAoB,EAAE,OAAO,MAAM,EACnC,EAAgB,OAAO;;WAEzB,aAAY;WACZ,WAAU;WACV,CAAA,CACE;;SACL,MAAiB,WAChB,kBAAC,KAAD;UAAG,WAAU;oBAAb;WAAmD;WAC1B;WACtB,EAAe,EAAoB,mBAAmB,EACrD,uBAAuB,GACxB,CAAC;WAAE;WAAI;WACJ;WACH,EAAe,EAAoB,mBAAmB,EACrD,uBAAuB,GACxB,CAAC;WACA;;SAEF,EAAA,CAAA;QAGL,KACC,CAAC,MAAM,SAAS,GAAkB,GAAG,CAAC,IACtC,SAAS,GAAkB,GAAG,GAAG,KAC/B,kBAAC,OAAD;SAAK,WAAU;mBAAf,CACE,kBAAC,KAAD;UAAG,WAAU;oBAA8C;UAEvD,CAAA,EACJ,kBAAC,OAAD;UAAK,WAAU;oBAAf;WACE,kBAAC,OAAD;YAAK,WAAU;sBAAf,CACE,kBAAC,QAAD;aAAM,WAAU;uBAAuC;aAAkB,CAAA,EACzE,kBAAC,QAAD;aAAM,WAAU;uBACb,EAAe,SAAS,GAAkB,GAAG,EAAE,EAC9C,uBAAuB,GACxB,CAAC;aACG,CAAA,CACH;;WACN,kBAAC,OAAD;YAAK,WAAU;sBAAf,CACE,kBAAC,QAAD;aAAM,WAAU;uBAAhB;cAAuD;cACnC,EAAoB;cAAgB;cACjD;gBACP,kBAAC,QAAD;aAAM,WAAU;uBAAhB,CAA0E,KAEvE,EACE,SAAS,GAAkB,GAAG,GAC7B,EAAoB,kBACpB,KACF,EAAE,uBAAuB,GAAG,CAC7B,CACI;eACH;;WACN,kBAAC,OAAD;YAAK,WAAU;sBAAf,CACE,kBAAC,QAAD;aAAM,WAAU;uBAAmD;aAE5D,CAAA,EACP,kBAAC,QAAD;aAAM,WAAU;uBACb,EACC,SAAS,GAAkB,GAAG,IAC3B,IAAI,EAAoB,kBAAkB,MAC7C,EAAE,uBAAuB,GAAG,CAC7B;aACI,CAAA,CACH;;WACF;YACF;;QAIV,kBAAC,OAAD;SAAK,WAAU;mBAAf,CACE,kBAAC,KAAD,EAAA,UAAA;UAAG;UAC8B;UAC9B,EAAoB,gBAAgB;UAAa;UAChD,EAAA,CAAA,EACJ,kBAAC,KAAD,EAAA,UAAA;UAAG;UACyB,EAAoB;UAAgB;UAE5D,EAAA,CAAA,CACA;;QAGN,kBAAC,OAAD;SAAK,WAAU;mBAAf,CACE,kBAAC,UAAD;UACE,MAAK;UACL,eAAe;AAGb,WAFA,EAAmB,GAAM,EACzB,EAAoB,GAAG,EACvB,EAAgB,OAAO;;UAEzB,WAAU;oBACX;UAEQ,CAAA,EACT,kBAAC,UAAD;UACE,MAAK;UACL,SAAS;UACT,UACE,CAAC,KACD,MAAM,SAAS,GAAkB,GAAG,CAAC,IACrC,SAAS,GAAkB,GAAG,GAAG,EAAoB;UAEvD,WAAW,EACT,uEACA,CAAC,KACC,MAAM,SAAS,GAAkB,GAAG,CAAC,IACrC,SAAS,GAAkB,GAAG,GAAG,EAAoB,oBACnD,wDACA,8EACL;oBACF;UAEQ,CAAA,CACL;;QACF;;MAEJ;;IACF,CAAA;GAEG"}
|
|
1
|
+
{"version":3,"file":"InvestmentOpportunitiesPage.js","names":[],"sources":["../../../../src/kadaikodi/pages/investor/InvestmentOpportunitiesPage.tsx"],"sourcesContent":["/**\n * Investment Opportunities Page\n * @module kadaikodi/pages/investor\n *\n * Platform stats, 6 filters (search/category/risk/amount/location/sort),\n * invest modal with amount calculator, opportunity cards with images/funding\n * progress/business metrics/documents.\n */\n\nimport { useState, useCallback, useMemo } from 'react';\nimport {\n Search,\n FileText,\n IndianRupee,\n Eye,\n Briefcase,\n CheckCircle2,\n X,\n Target,\n Lock,\n TrendingUp,\n} from 'lucide-react';\nimport { EmphasisPanel, GlassCard, PagePurpose } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { EmptyState } from '../../components/EmptyState';\nimport { AllocationBar } from '../../components/viz';\nimport { useKadaikodi } from '../../context/KadaikodiContext';\nimport { useInvestmentOpportunities, useInvest } from '../../hooks/useKadaikodiApi';\nimport { cn } from '../../utils/cn';\nimport { formatCurrency } from '../../utils/formatters';\nimport type { InvestmentOpportunity as DomainInvestmentOpportunity } from '../../types';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\ninterface InvestmentOpportunity {\n id: string;\n cartId: string;\n cartName: string;\n cartOwner: string;\n description: string;\n targetAmount: number;\n raisedAmount: number;\n minimumInvestment: number;\n maximumInvestment: number;\n investorCount: number;\n daysLeft: number;\n expectedReturns: number;\n paybackPeriod: number;\n riskLevel: 'low' | 'medium' | 'high';\n category: string;\n location: string;\n image: string;\n businessMetrics: {\n monthlyRevenue: number;\n monthlyOrders: number;\n averageOrderValue: number;\n rating: number;\n reviewCount: number;\n operatingSince: string;\n };\n financials: {\n grossMargin: number;\n netMargin: number;\n monthlyExpenses: number;\n breakEvenAchieved: boolean;\n };\n investmentTerms: {\n equityOffered: number;\n dividendFrequency: 'monthly' | 'quarterly' | 'yearly';\n lockInPeriod: number;\n exitOptions: string[];\n };\n documents: Array<{ name: string; type: string; uploadedOn: string }>;\n status: 'active' | 'funded' | 'closed';\n}\n\ninterface OpportunityFilters {\n searchQuery: string;\n category: string;\n riskLevel: string;\n minimumAmount: string;\n sortBy: string;\n}\n\n// ---------------------------------------------------------------------------\n// Mapping (real domain opportunity → this page's richer view-model)\n// ---------------------------------------------------------------------------\n\nfunction mapOpportunity(o: DomainInvestmentOpportunity): InvestmentOpportunity {\n const daysLeft = o.expiresAt\n ? Math.max(0, Math.ceil((new Date(o.expiresAt).getTime() - Date.now()) / 86400000))\n : 0;\n return {\n id: o.id,\n cartId: o.cartId,\n cartName: o.cartName,\n cartOwner: o.businessOwner || '',\n description: o.description,\n targetAmount: o.targetAmount,\n raisedAmount: o.raisedAmount,\n minimumInvestment: o.minimumInvestment,\n // The backend exposes a single target, not a per-investor cap; use the\n // remaining amount to target as the sensible maximum (never fabricated).\n maximumInvestment: Math.max(o.minimumInvestment, o.targetAmount - o.raisedAmount),\n investorCount: o.investors.length,\n daysLeft,\n expectedReturns: o.expectedReturns,\n paybackPeriod: o.investmentPeriod,\n riskLevel: o.riskLevel,\n category: '',\n location: '',\n image: '',\n businessMetrics: {\n monthlyRevenue: 0,\n monthlyOrders: 0,\n averageOrderValue: 0,\n rating: 0,\n reviewCount: 0,\n operatingSince: '',\n },\n financials: { grossMargin: 0, netMargin: 0, monthlyExpenses: 0, breakEvenAchieved: false },\n investmentTerms: {\n equityOffered: 0,\n dividendFrequency: 'quarterly',\n lockInPeriod: o.investmentPeriod,\n exitOptions: [],\n },\n documents: o.documents.map((d) => ({ name: d, type: 'PDF', uploadedOn: '' })),\n status: o.status,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction getRiskBadgeColor(risk: string): string {\n switch (risk) {\n case 'low':\n return 'bg-status-success-bg-subtle text-status-success-text';\n case 'medium':\n return 'bg-status-warning-bg-subtle text-status-warning-text';\n case 'high':\n return 'bg-status-error-bg-subtle text-status-error-text';\n default:\n return 'bg-bg-sunken text-text-secondary';\n }\n}\n\nfunction getRiskLabel(risk: string): string {\n switch (risk) {\n case 'low':\n return 'Low Risk';\n case 'medium':\n return 'Medium Risk';\n case 'high':\n return 'High Risk';\n default:\n return risk;\n }\n}\n\nfunction getFundingPercentage(raised: number, target: number): number {\n return target > 0 ? Math.min((raised / target) * 100, 100) : 0;\n}\n\n// ---------------------------------------------------------------------------\n// Component\n// ---------------------------------------------------------------------------\n\nexport function InvestmentOpportunitiesPage() {\n const { navigateTo, currentUser } = useKadaikodi();\n const { data: rawOpportunities = [], isLoading } = useInvestmentOpportunities();\n const invest = useInvest();\n const [selectedOpportunity, setSelectedOpportunity] = useState<InvestmentOpportunity | null>(\n null\n );\n const [showInvestModal, setShowInvestModal] = useState(false);\n const [investmentAmount, setInvestmentAmount] = useState('');\n const [investStatus, setInvestStatus] = useState<'idle' | 'success' | 'error'>('idle');\n\n const [filters, setFilters] = useState<OpportunityFilters>({\n searchQuery: '',\n category: '',\n riskLevel: '',\n minimumAmount: '',\n sortBy: 'featured',\n });\n\n const opportunities = useMemo(() => rawOpportunities.map(mapOpportunity), [rawOpportunities]);\n\n const platformStats = useMemo(() => {\n const count = opportunities.length;\n return {\n totalOpportunities: count,\n averageReturns: count\n ? Number((opportunities.reduce((s, o) => s + o.expectedReturns, 0) / count).toFixed(1))\n : 0,\n totalFunded: opportunities.reduce((s, o) => s + o.raisedAmount, 0),\n totalTarget: opportunities.reduce((s, o) => s + o.targetAmount, 0),\n };\n }, [opportunities]);\n\n const handleFilterChange = useCallback((key: keyof OpportunityFilters, value: string) => {\n setFilters((prev) => ({ ...prev, [key]: value }));\n }, []);\n\n const filteredOpportunities = useMemo(() => {\n let filtered = [...opportunities];\n\n // Search\n if (filters.searchQuery.trim()) {\n const query = filters.searchQuery.toLowerCase();\n filtered = filtered.filter(\n (opp) =>\n opp.cartName.toLowerCase().includes(query) ||\n opp.description.toLowerCase().includes(query) ||\n opp.category.toLowerCase().includes(query)\n );\n }\n\n // Category\n if (filters.category) {\n filtered = filtered.filter((opp) => opp.category === filters.category);\n }\n\n // Risk level\n if (filters.riskLevel) {\n filtered = filtered.filter((opp) => opp.riskLevel === filters.riskLevel);\n }\n\n // Minimum amount\n if (filters.minimumAmount) {\n const minAmount = parseInt(filters.minimumAmount, 10);\n filtered = filtered.filter((opp) => opp.minimumInvestment <= minAmount);\n }\n\n // Sort\n switch (filters.sortBy) {\n case 'returns':\n filtered.sort((a, b) => b.expectedReturns - a.expectedReturns);\n break;\n case 'funding':\n filtered.sort((a, b) => b.raisedAmount / b.targetAmount - a.raisedAmount / a.targetAmount);\n break;\n case 'closing':\n filtered.sort((a, b) => a.daysLeft - b.daysLeft);\n break;\n case 'minimum':\n filtered.sort((a, b) => a.minimumInvestment - b.minimumInvestment);\n break;\n default:\n break;\n }\n\n return filtered;\n }, [filters, opportunities]);\n\n const handleInvest = useCallback(() => {\n if (!selectedOpportunity || !investmentAmount) return;\n\n const amount = parseInt(investmentAmount, 10);\n if (isNaN(amount)) return;\n\n if (amount < selectedOpportunity.minimumInvestment) {\n setInvestStatus('error');\n return;\n }\n if (amount > selectedOpportunity.maximumInvestment) {\n setInvestStatus('error');\n return;\n }\n\n invest.mutate(\n {\n amount,\n investorId: currentUser?.id ?? '',\n opportunityId: selectedOpportunity.id,\n },\n {\n onSuccess: () => {\n setInvestStatus('success');\n setTimeout(() => {\n setShowInvestModal(false);\n setInvestmentAmount('');\n setInvestStatus('idle');\n navigateTo('/investor/portfolio');\n }, 1500);\n },\n onError: () => setInvestStatus('error'),\n }\n );\n }, [selectedOpportunity, investmentAmount, navigateTo, invest, currentUser?.id]);\n\n const openInvestModal = useCallback((opportunity: InvestmentOpportunity) => {\n setSelectedOpportunity(opportunity);\n setInvestmentAmount('');\n setInvestStatus('idle');\n setShowInvestModal(true);\n }, []);\n\n if (isLoading) {\n return (\n <PageLayout title=\"Investment Opportunities\" description=\"Loading opportunities...\">\n <div className=\"flex items-center justify-center py-20\">\n <div className=\"text-center\">\n <div className=\"mx-auto mb-4 h-12 w-12 animate-spin rounded-full border-b-2 border-primary\" />\n <p className=\"text-sm text-text-secondary\">Loading opportunities...</p>\n </div>\n </div>\n </PageLayout>\n );\n }\n\n return (\n <PageLayout\n title=\"Investment Opportunities\"\n description=\"Discover and invest in growing local businesses\"\n actions={\n <span className=\"text-sm text-text-secondary\">\n {filteredOpportunities.length} opportunities\n </span>\n }\n >\n <PagePurpose className=\"mb-6\">\n The deal flow — local carts and businesses looking for funding right now. Use this page to\n discover opportunities that fit your strategy, compare projected returns and equity on\n offer, filter by category or funding stage, and back the ones you believe in. The stats up\n top show how the platform is performing so you can gauge typical returns and success rates\n before you commit.\n </PagePurpose>\n\n {/* Platform Stats — the one emphasis zone for this page */}\n <EmphasisPanel>\n <h2 className=\"mb-4 text-base font-semibold text-text-primary\">Platform Performance</h2>\n <div className=\"grid grid-cols-2 gap-3 sm:gap-4 lg:grid-cols-4\">\n <div className=\"rounded-lg border border-border-subtle bg-bg-surface p-3 sm:p-4\">\n <p className=\"text-xs text-text-secondary\">Active Opportunities</p>\n <p className=\"text-lg font-bold text-text-primary sm:text-2xl\">\n {platformStats.totalOpportunities}\n </p>\n </div>\n <div className=\"rounded-lg border border-border-subtle bg-bg-surface p-3 sm:p-4\">\n <p className=\"text-xs text-text-secondary\">Average Returns</p>\n <p className=\"text-lg font-bold text-status-success-text sm:text-2xl\">\n {platformStats.averageReturns}%\n </p>\n </div>\n <div className=\"rounded-lg border border-border-subtle bg-bg-surface p-3 sm:p-4\">\n <p className=\"text-xs text-text-secondary\">Total Funded</p>\n <p className=\"text-lg font-bold text-text-primary sm:text-2xl\">\n {formatCurrency(platformStats.totalFunded, {\n minimumFractionDigits: 0,\n notation: 'compact',\n })}\n </p>\n </div>\n <div className=\"rounded-lg border border-border-subtle bg-bg-surface p-3 sm:p-4\">\n <p className=\"text-xs text-text-secondary\">Total Target</p>\n <p className=\"text-lg font-bold text-text-primary sm:text-2xl\">\n {formatCurrency(platformStats.totalTarget, {\n minimumFractionDigits: 0,\n notation: 'compact',\n })}\n </p>\n </div>\n </div>\n </EmphasisPanel>\n\n {/* Filters */}\n <div className=\"mt-6 rounded-lg border border-border-subtle bg-bg-surface p-4 sm:p-6\">\n <div className=\"grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-5\">\n {/* Search */}\n <div className=\"relative sm:col-span-2 lg:col-span-1\">\n <Search className=\"absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-text-secondary\" />\n <input\n type=\"text\"\n placeholder=\"Search opportunities...\"\n value={filters.searchQuery}\n onChange={(e) => handleFilterChange('searchQuery', e.target.value)}\n className=\"w-full rounded-lg border border-border-subtle bg-bg-surface py-2 pl-10 pr-4 text-sm text-text-primary placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n />\n </div>\n\n {/* Category */}\n <select\n value={filters.category}\n onChange={(e) => handleFilterChange('category', e.target.value)}\n className=\"rounded-lg border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n >\n <option value=\"\">All Categories</option>\n <option value=\"Healthy Food\">Healthy Food</option>\n <option value=\"Fast Food\">Fast Food</option>\n <option value=\"Fusion Food\">Fusion Food</option>\n <option value=\"Beverages\">Beverages</option>\n <option value=\"Street Food\">Street Food</option>\n </select>\n\n {/* Risk Level */}\n <select\n value={filters.riskLevel}\n onChange={(e) => handleFilterChange('riskLevel', e.target.value)}\n className=\"rounded-lg border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n >\n <option value=\"\">All Risk Levels</option>\n <option value=\"low\">Low Risk</option>\n <option value=\"medium\">Medium Risk</option>\n <option value=\"high\">High Risk</option>\n </select>\n\n {/* Minimum Amount */}\n <select\n value={filters.minimumAmount}\n onChange={(e) => handleFilterChange('minimumAmount', e.target.value)}\n className=\"rounded-lg border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n >\n <option value=\"\">Any Amount</option>\n <option value=\"15000\">Under ₹15,000</option>\n <option value=\"25000\">Under ₹25,000</option>\n <option value=\"50000\">Under ₹50,000</option>\n <option value=\"100000\">Under ₹1,00,000</option>\n </select>\n\n {/* Sort By */}\n <select\n value={filters.sortBy}\n onChange={(e) => handleFilterChange('sortBy', e.target.value)}\n className=\"rounded-lg border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n >\n <option value=\"featured\">Featured</option>\n <option value=\"returns\">Highest Returns</option>\n <option value=\"funding\">Most Funded</option>\n <option value=\"closing\">Closing Soon</option>\n <option value=\"minimum\">Lowest Minimum</option>\n </select>\n </div>\n </div>\n\n {/* Opportunities Grid */}\n {filteredOpportunities.length === 0 ? (\n <div className=\"mt-6\">\n <EmptyState\n icon={<Search className=\"h-10 w-10\" />}\n title=\"No opportunities found\"\n description=\"Try adjusting your filters to find more opportunities\"\n action={\n <button\n type=\"button\"\n onClick={() =>\n setFilters({\n searchQuery: '',\n category: '',\n riskLevel: '',\n minimumAmount: '',\n sortBy: 'featured',\n })\n }\n className=\"rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90 transition-colors\"\n >\n Clear Filters\n </button>\n }\n />\n </div>\n ) : (\n <div className=\"mt-6 grid grid-cols-1 gap-4 md:grid-cols-2 xl:grid-cols-3\">\n {filteredOpportunities.map((opportunity) => {\n const fundingPct = getFundingPercentage(\n opportunity.raisedAmount,\n opportunity.targetAmount\n );\n const remaining = Math.max(0, opportunity.targetAmount - opportunity.raisedAmount);\n const isFunded = opportunity.status === 'funded';\n\n return (\n <GlassCard\n key={opportunity.id}\n treatment=\"plain\"\n className=\"flex flex-col p-5\"\n >\n {/* Header — avatar, name, owner, risk badge */}\n <div className=\"flex items-start gap-3\">\n <div className=\"flex h-12 w-12 shrink-0 items-center justify-center rounded-full bg-[var(--color-accent-soft)]\">\n <Briefcase className=\"h-5 w-5 text-text-primary\" />\n </div>\n <div className=\"min-w-0 flex-1\">\n <p className=\"truncate text-xs text-text-secondary\">\n {opportunity.cartOwner || 'Local business'}\n </p>\n <h3 className=\"truncate text-base font-semibold text-text-primary\">\n {opportunity.cartName}\n </h3>\n </div>\n <span\n className={cn(\n 'shrink-0 rounded-full px-2.5 py-1 text-xs font-semibold',\n getRiskBadgeColor(opportunity.riskLevel)\n )}\n >\n {getRiskLabel(opportunity.riskLevel)}\n </span>\n </div>\n\n {/* Description */}\n {opportunity.description && (\n <p className=\"mt-3 line-clamp-2 text-sm leading-relaxed text-text-secondary\">\n {opportunity.description}\n </p>\n )}\n\n {/* Metric strip — Target / Min / Returns / Lock-in */}\n <div className=\"mt-4 grid grid-cols-4 gap-2 rounded-lg border border-border-subtle bg-bg-sunken/40 p-3 text-center\">\n <div>\n <div className=\"flex items-center justify-center gap-1 text-text-secondary\">\n <Target className=\"h-3.5 w-3.5\" />\n </div>\n <p className=\"mt-1 text-xs text-text-secondary\">Target</p>\n <p className=\"text-sm font-bold text-text-primary\">\n {formatCurrency(opportunity.targetAmount, {\n minimumFractionDigits: 0,\n notation: 'compact',\n })}\n </p>\n </div>\n <div>\n <div className=\"flex items-center justify-center gap-1 text-text-secondary\">\n <IndianRupee className=\"h-3.5 w-3.5\" />\n </div>\n <p className=\"mt-1 text-xs text-text-secondary\">Min</p>\n <p className=\"text-sm font-bold text-text-primary\">\n {formatCurrency(opportunity.minimumInvestment, {\n minimumFractionDigits: 0,\n notation: 'compact',\n })}\n </p>\n </div>\n <div>\n <div className=\"flex items-center justify-center gap-1 text-text-secondary\">\n <TrendingUp className=\"h-3.5 w-3.5\" />\n </div>\n <p className=\"mt-1 text-xs text-text-secondary\">Returns</p>\n <p className=\"text-sm font-bold text-status-success-text\">\n {opportunity.expectedReturns}%\n </p>\n </div>\n <div>\n <div className=\"flex items-center justify-center gap-1 text-text-secondary\">\n <Lock className=\"h-3.5 w-3.5\" />\n </div>\n <p className=\"mt-1 text-xs text-text-secondary\">Lock-in</p>\n <p className=\"text-sm font-bold text-text-primary\">\n {opportunity.paybackPeriod}mo\n </p>\n </div>\n </div>\n\n {/* Funding — raised vs remaining allocation */}\n <div className=\"mt-4\">\n <AllocationBar\n height={10}\n showLegend={false}\n label={\n <span className=\"text-text-primary\">\n {formatCurrency(opportunity.raisedAmount, { minimumFractionDigits: 0 })}{' '}\n raised\n </span>\n }\n rightLabel={`${fundingPct.toFixed(0)}%`}\n segments={[\n { label: 'Raised', value: opportunity.raisedAmount },\n {\n label: 'Remaining',\n value: remaining,\n color: 'var(--color-bg-sunken)',\n },\n ]}\n />\n <p className=\"mt-1.5 text-xs text-text-secondary\">\n {opportunity.investorCount} investors ·{' '}\n {formatCurrency(opportunity.targetAmount, {\n minimumFractionDigits: 0,\n notation: 'compact',\n })}{' '}\n goal\n </p>\n </div>\n\n {/* Documents */}\n {opportunity.documents.length > 0 && (\n <div className=\"mt-3 flex items-center gap-1.5 text-xs text-text-secondary\">\n <FileText className=\"h-3.5 w-3.5\" />\n <span>{opportunity.documents.length} documents available</span>\n </div>\n )}\n\n {/* Actions — pinned to the bottom for even card heights */}\n <div className=\"mt-auto flex flex-col gap-2 pt-4 sm:flex-row\">\n <button\n type=\"button\"\n onClick={() => openInvestModal(opportunity)}\n disabled={isFunded}\n className={cn(\n 'flex-1 rounded-lg px-4 py-2.5 text-sm font-semibold transition-colors',\n isFunded\n ? 'cursor-not-allowed bg-bg-sunken text-text-secondary'\n : 'bg-action-primary-bg text-action-primary-text hover:bg-action-primary-bg-hover'\n )}\n >\n {isFunded ? 'Fully Funded' : 'Invest'}\n </button>\n <button\n type=\"button\"\n onClick={() => navigateTo(`/investor/opportunity/${opportunity.id}`)}\n className=\"flex flex-1 items-center justify-center gap-2 rounded-lg border border-border-subtle px-4 py-2.5 text-sm font-medium text-text-primary hover:bg-accent transition-colors\"\n >\n <Eye className=\"h-4 w-4\" />\n Details\n </button>\n </div>\n </GlassCard>\n );\n })}\n </div>\n )}\n\n {/* Investment Modal */}\n {showInvestModal && selectedOpportunity && (\n <div className=\"fixed inset-0 z-50 flex items-center justify-center bg-black/50 p-4\">\n <div className=\"relative max-h-[90vh] w-full max-w-md overflow-y-auto rounded-lg border border-border-subtle bg-bg-surface p-4 sm:p-6\">\n {/* Close button */}\n <button\n type=\"button\"\n onClick={() => {\n setShowInvestModal(false);\n setInvestmentAmount('');\n setInvestStatus('idle');\n }}\n className=\"absolute right-3 top-3 rounded-lg p-1 text-text-secondary hover:bg-accent transition-colors\"\n >\n <X className=\"h-5 w-5\" />\n </button>\n\n <h3 className=\"mb-4 text-base font-semibold text-text-primary sm:text-lg\">\n Invest in {selectedOpportunity.cartName}\n </h3>\n\n {investStatus === 'success' ? (\n <div className=\"py-8 text-center\">\n <CheckCircle2 className=\"mx-auto mb-3 h-12 w-12 text-status-success-text\" />\n <p className=\"text-sm font-medium text-text-primary\">Investment successful!</p>\n <p className=\"mt-1 text-xs text-text-secondary\">Redirecting to your portfolio...</p>\n </div>\n ) : (\n <div className=\"space-y-4\">\n {/* Investment Range */}\n <div>\n <p className=\"mb-1 text-xs text-text-secondary sm:text-sm\">Investment Range</p>\n <p className=\"text-sm font-medium text-text-primary sm:text-base\">\n {formatCurrency(selectedOpportunity.minimumInvestment, {\n minimumFractionDigits: 0,\n })}{' '}\n -{' '}\n {formatCurrency(selectedOpportunity.maximumInvestment, {\n minimumFractionDigits: 0,\n })}\n </p>\n </div>\n\n {/* Amount Input */}\n <div>\n <label className=\"mb-1 block text-xs font-medium text-text-primary sm:text-sm\">\n Investment Amount\n </label>\n <div className=\"relative\">\n <IndianRupee className=\"absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-text-secondary\" />\n <input\n type=\"number\"\n value={investmentAmount}\n onChange={(e) => {\n setInvestmentAmount(e.target.value);\n setInvestStatus('idle');\n }}\n placeholder=\"Enter amount\"\n className=\"w-full rounded-lg border border-border-subtle bg-bg-surface py-2 pl-10 pr-4 text-sm text-text-primary placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n />\n </div>\n {investStatus === 'error' && (\n <p className=\"mt-1 text-xs text-status-error-text\">\n Amount must be between{' '}\n {formatCurrency(selectedOpportunity.minimumInvestment, {\n minimumFractionDigits: 0,\n })}{' '}\n and{' '}\n {formatCurrency(selectedOpportunity.maximumInvestment, {\n minimumFractionDigits: 0,\n })}\n </p>\n )}\n </div>\n\n {/* Returns Calculator */}\n {investmentAmount &&\n !isNaN(parseInt(investmentAmount, 10)) &&\n parseInt(investmentAmount, 10) > 0 && (\n <div className=\"rounded-lg border border-border-subtle bg-bg-sunken/50 p-3 sm:p-4\">\n <p className=\"mb-2 text-xs text-text-secondary sm:text-sm\">\n Expected Returns\n </p>\n <div className=\"space-y-1.5\">\n <div className=\"flex justify-between\">\n <span className=\"text-xs text-text-primary sm:text-sm\">Investment:</span>\n <span className=\"text-xs font-medium text-text-primary sm:text-sm\">\n {formatCurrency(parseInt(investmentAmount, 10), {\n minimumFractionDigits: 0,\n })}\n </span>\n </div>\n <div className=\"flex justify-between\">\n <span className=\"text-xs text-text-primary sm:text-sm\">\n Expected Return ({selectedOpportunity.expectedReturns}%):\n </span>\n <span className=\"text-xs font-medium text-status-success-text sm:text-sm\">\n +\n {formatCurrency(\n (parseInt(investmentAmount, 10) *\n selectedOpportunity.expectedReturns) /\n 100,\n { minimumFractionDigits: 0 }\n )}\n </span>\n </div>\n <div className=\"flex justify-between border-t border-border-subtle pt-2\">\n <span className=\"text-xs font-medium text-text-primary sm:text-sm\">\n Total Value:\n </span>\n <span className=\"text-xs font-bold text-text-primary sm:text-sm\">\n {formatCurrency(\n parseInt(investmentAmount, 10) *\n (1 + selectedOpportunity.expectedReturns / 100),\n { minimumFractionDigits: 0 }\n )}\n </span>\n </div>\n </div>\n </div>\n )}\n\n {/* Investment Terms */}\n <div className=\"space-y-1 text-xs text-text-secondary sm:text-sm\">\n <p>\n • Minimum lock-in period:{' '}\n {selectedOpportunity.investmentTerms.lockInPeriod} months\n </p>\n <p>\n • Expected returns: {selectedOpportunity.expectedReturns}% over the lock-in\n period\n </p>\n </div>\n\n {/* Actions */}\n <div className=\"flex flex-col gap-2 pt-2 sm:flex-row sm:gap-3\">\n <button\n type=\"button\"\n onClick={() => {\n setShowInvestModal(false);\n setInvestmentAmount('');\n setInvestStatus('idle');\n }}\n className=\"flex-1 rounded-lg border border-border-subtle px-4 py-2.5 text-sm font-medium text-text-primary hover:bg-accent transition-colors\"\n >\n Cancel\n </button>\n <button\n type=\"button\"\n onClick={handleInvest}\n disabled={\n !investmentAmount ||\n isNaN(parseInt(investmentAmount, 10)) ||\n parseInt(investmentAmount, 10) < selectedOpportunity.minimumInvestment\n }\n className={cn(\n 'flex-1 rounded-lg px-4 py-2.5 text-sm font-medium transition-colors',\n !investmentAmount ||\n isNaN(parseInt(investmentAmount, 10)) ||\n parseInt(investmentAmount, 10) < selectedOpportunity.minimumInvestment\n ? 'cursor-not-allowed bg-bg-sunken text-text-secondary'\n : 'bg-action-primary-bg text-action-primary-text hover:bg-action-primary-bg/90'\n )}\n >\n Confirm Investment\n </button>\n </div>\n </div>\n )}\n </div>\n </div>\n )}\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1,18 +1,28 @@
|
|
|
1
1
|
import { useKadaikodi as e } from "../../context/KadaikodiContext.js";
|
|
2
2
|
import { cn as t } from "../../utils/cn.js";
|
|
3
3
|
import { PageLayout as n } from "../../components/PageLayout.js";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import
|
|
4
|
+
import { EmptyState as r } from "../../components/EmptyState.js";
|
|
5
|
+
import { useInvestments as i } from "../../hooks/useKadaikodiApi.js";
|
|
6
|
+
import { formatCurrency as a, formatDate as o } from "../../utils/formatters.js";
|
|
7
|
+
import { MetricsCard as s } from "../../components/MetricsCard.js";
|
|
8
|
+
import { AreaChart as c } from "../../components/viz/AreaChart.js";
|
|
9
|
+
import { BarChart as l } from "../../components/viz/BarChart.js";
|
|
10
|
+
import { DonutChart as u } from "../../components/viz/DonutChart.js";
|
|
11
|
+
import "../../components/viz/index.js";
|
|
12
|
+
import { useMemo as d } from "react";
|
|
13
|
+
import { ArrowDown as f, ArrowUp as p, BarChart3 as m, IndianRupee as h, PieChart as g, Target as _, TrendingUp as v, Wallet as y } from "lucide-react";
|
|
14
|
+
import { jsx as b, jsxs as x } from "react/jsx-runtime";
|
|
15
|
+
import { EmphasisPanel as S, GlassCard as C, PagePurpose as w } from "@burdenoff/fe-libs/ui";
|
|
12
16
|
//#region src/kadaikodi/pages/investor/InvestorAnalyticsPage.tsx
|
|
13
|
-
function
|
|
14
|
-
|
|
15
|
-
|
|
17
|
+
function T(e) {
|
|
18
|
+
return a(e, {
|
|
19
|
+
minimumFractionDigits: 0,
|
|
20
|
+
notation: "compact"
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
function E() {
|
|
24
|
+
let { navigateTo: E, currentUser: D } = e(), { data: O = [], isLoading: k } = i(D?.id || ""), A = d(() => {
|
|
25
|
+
let e = O.reduce((e, t) => e + t.investedAmount, 0), t = O.reduce((e, t) => e + t.currentValue, 0), n = t - e, r = e > 0 ? n / e * 100 : 0, i = O.map((e) => {
|
|
16
26
|
let t = e.currentValue - e.investedAmount, n = e.investedAmount > 0 ? t / e.investedAmount * 100 : 0;
|
|
17
27
|
return {
|
|
18
28
|
id: e.id,
|
|
@@ -22,143 +32,201 @@ function b() {
|
|
|
22
32
|
returns: t,
|
|
23
33
|
roi: n
|
|
24
34
|
};
|
|
25
|
-
}), a = i.length ? i.reduce((e, t) => e + t.roi, 0) / i.length : 0,
|
|
35
|
+
}), a = i.length ? i.reduce((e, t) => e + t.roi, 0) / i.length : 0, s = [...i].sort((e, t) => t.roi - e.roi), c = [...i].sort((e, t) => t.currentValue - e.currentValue).map((e) => ({
|
|
36
|
+
label: e.cartName,
|
|
37
|
+
value: e.currentValue
|
|
38
|
+
})), l = [...O].sort((e, t) => new Date(e.investmentDate).getTime() - new Date(t.investmentDate).getTime()), u = 0, d = l.map((e) => (u += e.currentValue, {
|
|
39
|
+
label: o(e.investmentDate, {
|
|
40
|
+
day: void 0,
|
|
41
|
+
hour: void 0,
|
|
42
|
+
minute: void 0
|
|
43
|
+
}),
|
|
44
|
+
value: u
|
|
45
|
+
}));
|
|
26
46
|
return {
|
|
27
47
|
totalInvested: e,
|
|
28
48
|
totalValue: t,
|
|
29
49
|
totalReturns: n,
|
|
30
50
|
portfolioGrowth: r,
|
|
31
51
|
averageRoi: a,
|
|
32
|
-
performanceByCart:
|
|
33
|
-
|
|
34
|
-
|
|
52
|
+
performanceByCart: s,
|
|
53
|
+
valueByCart: c,
|
|
54
|
+
valueTrend: d.length === 1 ? [{
|
|
55
|
+
label: "Start",
|
|
56
|
+
value: 0
|
|
57
|
+
}, ...d] : d,
|
|
58
|
+
bestPerforming: s[0]?.cartName ?? "",
|
|
59
|
+
worstPerforming: s[s.length - 1]?.cartName ?? ""
|
|
35
60
|
};
|
|
36
|
-
}, [
|
|
37
|
-
return
|
|
61
|
+
}, [O]);
|
|
62
|
+
return k ? /* @__PURE__ */ b(n, {
|
|
38
63
|
title: "Investment Analytics",
|
|
39
64
|
description: "Loading analytics...",
|
|
40
|
-
children: /* @__PURE__ */
|
|
41
|
-
className: "
|
|
42
|
-
children: /* @__PURE__ */
|
|
43
|
-
className: "
|
|
44
|
-
children:
|
|
45
|
-
|
|
46
|
-
children: "Loading analytics data..."
|
|
47
|
-
})]
|
|
48
|
-
})
|
|
65
|
+
children: /* @__PURE__ */ x("div", {
|
|
66
|
+
className: "space-y-4",
|
|
67
|
+
children: [/* @__PURE__ */ b("div", {
|
|
68
|
+
className: "grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4",
|
|
69
|
+
children: Array.from({ length: 4 }).map((e, t) => /* @__PURE__ */ b("div", { className: "h-28 animate-pulse rounded-[var(--radius-card)] bg-bg-sunken" }, t))
|
|
70
|
+
}), /* @__PURE__ */ b("div", { className: "h-72 animate-pulse rounded-[var(--radius-card)] bg-bg-sunken" })]
|
|
49
71
|
})
|
|
50
|
-
}) :
|
|
72
|
+
}) : O.length === 0 ? /* @__PURE__ */ b(n, {
|
|
51
73
|
title: "Investment Analytics",
|
|
52
74
|
description: "Comprehensive analysis of your portfolio performance",
|
|
53
|
-
children: /* @__PURE__ */
|
|
75
|
+
children: /* @__PURE__ */ b(r, {
|
|
54
76
|
illustration: "empty-data",
|
|
55
77
|
title: "No analytics yet",
|
|
56
78
|
description: "Once you back a cart, your portfolio performance and per-cart analytics will appear here.",
|
|
57
|
-
action: /* @__PURE__ */
|
|
79
|
+
action: /* @__PURE__ */ b("button", {
|
|
58
80
|
type: "button",
|
|
59
|
-
onClick: () =>
|
|
60
|
-
className: "rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg
|
|
81
|
+
onClick: () => E("/investor/opportunities"),
|
|
82
|
+
className: "rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg-hover transition-colors",
|
|
61
83
|
children: "Browse Opportunities"
|
|
62
84
|
})
|
|
63
85
|
})
|
|
64
|
-
}) : /* @__PURE__ */
|
|
86
|
+
}) : /* @__PURE__ */ x(n, {
|
|
65
87
|
title: "Investment Analytics",
|
|
66
88
|
description: "Comprehensive analysis of your portfolio performance",
|
|
67
89
|
children: [
|
|
68
|
-
/* @__PURE__ */
|
|
90
|
+
/* @__PURE__ */ b(w, {
|
|
69
91
|
className: "mb-6",
|
|
70
|
-
children: "The analytical lens on your whole portfolio.
|
|
92
|
+
children: "The analytical lens on your whole portfolio. Judge how well your money is working — overall value and returns, average ROI, how value has grown, and where it's concentrated — then see your best and worst performers so you can rebalance, double down, or trim exposure."
|
|
71
93
|
}),
|
|
72
|
-
/* @__PURE__ */
|
|
94
|
+
/* @__PURE__ */ x(S, { children: [/* @__PURE__ */ b("h2", {
|
|
73
95
|
className: "mb-4 text-base font-semibold text-text-primary",
|
|
74
96
|
children: "Portfolio Performance"
|
|
75
|
-
}), /* @__PURE__ */
|
|
97
|
+
}), /* @__PURE__ */ x("div", {
|
|
76
98
|
className: "grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4",
|
|
77
99
|
children: [
|
|
78
|
-
/* @__PURE__ */
|
|
100
|
+
/* @__PURE__ */ b(s, {
|
|
79
101
|
featured: !0,
|
|
80
|
-
icon: /* @__PURE__ */
|
|
81
|
-
value:
|
|
102
|
+
icon: /* @__PURE__ */ b(y, { className: "h-5 w-5" }),
|
|
103
|
+
value: a(A.totalValue, { minimumFractionDigits: 0 }),
|
|
82
104
|
label: "Portfolio Value",
|
|
83
|
-
trend:
|
|
84
|
-
trendValue: `${
|
|
105
|
+
trend: A.portfolioGrowth >= 0 ? "up" : "down",
|
|
106
|
+
trendValue: `${A.portfolioGrowth >= 0 ? "+" : ""}${A.portfolioGrowth.toFixed(1)}%`
|
|
85
107
|
}),
|
|
86
|
-
/* @__PURE__ */
|
|
87
|
-
icon: /* @__PURE__ */
|
|
88
|
-
value:
|
|
108
|
+
/* @__PURE__ */ b(s, {
|
|
109
|
+
icon: /* @__PURE__ */ b(h, { className: "h-5 w-5" }),
|
|
110
|
+
value: a(A.totalInvested, { minimumFractionDigits: 0 }),
|
|
89
111
|
label: "Total Invested"
|
|
90
112
|
}),
|
|
91
|
-
/* @__PURE__ */
|
|
92
|
-
icon: /* @__PURE__ */
|
|
93
|
-
value:
|
|
113
|
+
/* @__PURE__ */ b(s, {
|
|
114
|
+
icon: /* @__PURE__ */ b(v, { className: "h-5 w-5" }),
|
|
115
|
+
value: a(A.totalReturns, { minimumFractionDigits: 0 }),
|
|
94
116
|
label: "Total Returns",
|
|
95
|
-
trend:
|
|
117
|
+
trend: A.totalReturns >= 0 ? "up" : "down"
|
|
96
118
|
}),
|
|
97
|
-
/* @__PURE__ */
|
|
98
|
-
icon: /* @__PURE__ */
|
|
99
|
-
value: `${
|
|
119
|
+
/* @__PURE__ */ b(s, {
|
|
120
|
+
icon: /* @__PURE__ */ b(_, { className: "h-5 w-5" }),
|
|
121
|
+
value: `${A.averageRoi.toFixed(1)}%`,
|
|
100
122
|
label: "Average ROI"
|
|
101
123
|
})
|
|
102
124
|
]
|
|
103
125
|
})] }),
|
|
104
|
-
|
|
105
|
-
className: "mt-
|
|
106
|
-
children: [/* @__PURE__ */
|
|
126
|
+
/* @__PURE__ */ x("div", {
|
|
127
|
+
className: "mt-6 grid gap-6 lg:grid-cols-3",
|
|
128
|
+
children: [/* @__PURE__ */ x(C, {
|
|
129
|
+
treatment: "plain",
|
|
130
|
+
className: "p-5 sm:p-6 lg:col-span-2",
|
|
131
|
+
children: [/* @__PURE__ */ b("h2", {
|
|
132
|
+
className: "mb-4 text-lg font-semibold text-text-primary",
|
|
133
|
+
children: "Portfolio Value Trend"
|
|
134
|
+
}), /* @__PURE__ */ b(c, {
|
|
135
|
+
series: A.valueTrend,
|
|
136
|
+
height: 240,
|
|
137
|
+
valueFormatter: T,
|
|
138
|
+
title: "Cumulative portfolio value by holding date"
|
|
139
|
+
})]
|
|
140
|
+
}), /* @__PURE__ */ x(C, {
|
|
141
|
+
treatment: "plain",
|
|
142
|
+
className: "p-5 sm:p-6",
|
|
143
|
+
children: [/* @__PURE__ */ b("h2", {
|
|
144
|
+
className: "mb-4 text-lg font-semibold text-text-primary",
|
|
145
|
+
children: "Allocation"
|
|
146
|
+
}), /* @__PURE__ */ b("div", {
|
|
147
|
+
className: "flex justify-center",
|
|
148
|
+
children: /* @__PURE__ */ b(u, {
|
|
149
|
+
segments: A.valueByCart,
|
|
150
|
+
size: 180,
|
|
151
|
+
centerLabel: T(A.totalValue),
|
|
152
|
+
centerCaption: "total value",
|
|
153
|
+
valueFormatter: T
|
|
154
|
+
})
|
|
155
|
+
})]
|
|
156
|
+
})]
|
|
157
|
+
}),
|
|
158
|
+
A.valueByCart.length > 1 && /* @__PURE__ */ x(C, {
|
|
159
|
+
treatment: "plain",
|
|
160
|
+
className: "mt-6 p-5 sm:p-6",
|
|
161
|
+
children: [/* @__PURE__ */ b("h2", {
|
|
162
|
+
className: "mb-4 text-lg font-semibold text-text-primary",
|
|
163
|
+
children: "Value by Cart"
|
|
164
|
+
}), /* @__PURE__ */ b(l, {
|
|
165
|
+
series: A.valueByCart,
|
|
166
|
+
height: 240,
|
|
167
|
+
colorful: !0,
|
|
168
|
+
valueFormatter: T,
|
|
169
|
+
title: "Current value held per cart"
|
|
170
|
+
})]
|
|
171
|
+
}),
|
|
172
|
+
A.performanceByCart.length > 1 && /* @__PURE__ */ x("div", {
|
|
173
|
+
className: "mt-6 grid grid-cols-1 gap-4 sm:grid-cols-2",
|
|
174
|
+
children: [/* @__PURE__ */ x("div", {
|
|
107
175
|
className: "flex items-center gap-3 rounded-lg border border-border-subtle bg-bg-surface p-4",
|
|
108
|
-
children: [/* @__PURE__ */
|
|
176
|
+
children: [/* @__PURE__ */ b("div", {
|
|
109
177
|
className: "flex h-10 w-10 shrink-0 items-center justify-center rounded-full bg-status-success-bg-subtle",
|
|
110
|
-
children: /* @__PURE__ */
|
|
111
|
-
}), /* @__PURE__ */
|
|
178
|
+
children: /* @__PURE__ */ b(p, { className: "h-5 w-5 text-status-success-text" })
|
|
179
|
+
}), /* @__PURE__ */ x("div", { children: [/* @__PURE__ */ b("p", {
|
|
112
180
|
className: "text-xs text-text-secondary",
|
|
113
181
|
children: "Best Performing"
|
|
114
|
-
}), /* @__PURE__ */
|
|
182
|
+
}), /* @__PURE__ */ b("p", {
|
|
115
183
|
className: "text-sm font-semibold text-status-success-text",
|
|
116
|
-
children:
|
|
184
|
+
children: A.bestPerforming
|
|
117
185
|
})] })]
|
|
118
|
-
}), /* @__PURE__ */
|
|
186
|
+
}), /* @__PURE__ */ x("div", {
|
|
119
187
|
className: "flex items-center gap-3 rounded-lg border border-border-subtle bg-bg-surface p-4",
|
|
120
|
-
children: [/* @__PURE__ */
|
|
188
|
+
children: [/* @__PURE__ */ b("div", {
|
|
121
189
|
className: "flex h-10 w-10 shrink-0 items-center justify-center rounded-full bg-status-error-bg-subtle",
|
|
122
|
-
children: /* @__PURE__ */
|
|
123
|
-
}), /* @__PURE__ */
|
|
190
|
+
children: /* @__PURE__ */ b(f, { className: "h-5 w-5 text-status-error-text" })
|
|
191
|
+
}), /* @__PURE__ */ x("div", { children: [/* @__PURE__ */ b("p", {
|
|
124
192
|
className: "text-xs text-text-secondary",
|
|
125
193
|
children: "Worst Performing"
|
|
126
|
-
}), /* @__PURE__ */
|
|
194
|
+
}), /* @__PURE__ */ b("p", {
|
|
127
195
|
className: "text-sm font-semibold text-text-primary",
|
|
128
|
-
children:
|
|
196
|
+
children: A.worstPerforming
|
|
129
197
|
})] })]
|
|
130
198
|
})]
|
|
131
199
|
}),
|
|
132
|
-
/* @__PURE__ */
|
|
200
|
+
/* @__PURE__ */ x("section", {
|
|
133
201
|
className: "mt-6",
|
|
134
|
-
children: [/* @__PURE__ */
|
|
202
|
+
children: [/* @__PURE__ */ x("div", {
|
|
135
203
|
className: "mb-4 flex items-center gap-2",
|
|
136
|
-
children: [/* @__PURE__ */
|
|
204
|
+
children: [/* @__PURE__ */ b(m, { className: "h-5 w-5 text-text-secondary" }), /* @__PURE__ */ b("h2", {
|
|
137
205
|
className: "text-lg font-semibold text-text-primary",
|
|
138
|
-
children: "Performance
|
|
206
|
+
children: "Performance Ranking"
|
|
139
207
|
})]
|
|
140
|
-
}), /* @__PURE__ */
|
|
208
|
+
}), /* @__PURE__ */ b("div", {
|
|
141
209
|
className: "space-y-3",
|
|
142
|
-
children:
|
|
210
|
+
children: A.performanceByCart.map((e, n) => /* @__PURE__ */ x("div", {
|
|
143
211
|
className: "flex items-center justify-between rounded-lg border border-border-subtle bg-bg-surface p-3",
|
|
144
|
-
children: [/* @__PURE__ */
|
|
212
|
+
children: [/* @__PURE__ */ x("div", {
|
|
145
213
|
className: "flex items-center gap-3",
|
|
146
|
-
children: [/* @__PURE__ */
|
|
214
|
+
children: [/* @__PURE__ */ b("div", {
|
|
147
215
|
className: "flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-action-primary-bg text-xs font-bold text-action-primary-text",
|
|
148
216
|
children: n + 1
|
|
149
|
-
}), /* @__PURE__ */
|
|
217
|
+
}), /* @__PURE__ */ x("div", { children: [/* @__PURE__ */ b("p", {
|
|
150
218
|
className: "text-sm font-medium text-text-primary",
|
|
151
219
|
children: e.cartName
|
|
152
|
-
}), /* @__PURE__ */
|
|
220
|
+
}), /* @__PURE__ */ x("p", {
|
|
153
221
|
className: "text-xs text-text-secondary",
|
|
154
|
-
children: [
|
|
222
|
+
children: [a(e.investedAmount, { minimumFractionDigits: 0 }), " invested"]
|
|
155
223
|
})] })]
|
|
156
|
-
}), /* @__PURE__ */
|
|
224
|
+
}), /* @__PURE__ */ x("div", {
|
|
157
225
|
className: "text-right",
|
|
158
|
-
children: [/* @__PURE__ */
|
|
226
|
+
children: [/* @__PURE__ */ x("p", {
|
|
159
227
|
className: t("text-sm font-semibold", e.returns >= 0 ? "text-status-success-text" : "text-status-error-text"),
|
|
160
|
-
children: [e.returns >= 0 ? "+" : "",
|
|
161
|
-
}), /* @__PURE__ */
|
|
228
|
+
children: [e.returns >= 0 ? "+" : "", a(e.returns, { minimumFractionDigits: 0 })]
|
|
229
|
+
}), /* @__PURE__ */ x("p", {
|
|
162
230
|
className: t("text-xs", e.roi >= 0 ? "text-status-success-text" : "text-status-error-text"),
|
|
163
231
|
children: [
|
|
164
232
|
e.roi >= 0 ? "+" : "",
|
|
@@ -170,26 +238,26 @@ function b() {
|
|
|
170
238
|
}, e.id))
|
|
171
239
|
})]
|
|
172
240
|
}),
|
|
173
|
-
/* @__PURE__ */
|
|
241
|
+
/* @__PURE__ */ x("div", {
|
|
174
242
|
className: "mt-8 flex flex-wrap gap-3",
|
|
175
243
|
children: [
|
|
176
|
-
/* @__PURE__ */
|
|
244
|
+
/* @__PURE__ */ x("button", {
|
|
177
245
|
type: "button",
|
|
178
|
-
onClick: () =>
|
|
246
|
+
onClick: () => E("/investor/portfolio"),
|
|
179
247
|
className: "flex items-center gap-2 rounded-lg border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-accent transition-colors",
|
|
180
|
-
children: [/* @__PURE__ */ g
|
|
248
|
+
children: [/* @__PURE__ */ b(g, { className: "h-4 w-4" }), "View Portfolio"]
|
|
181
249
|
}),
|
|
182
|
-
/* @__PURE__ */
|
|
250
|
+
/* @__PURE__ */ x("button", {
|
|
183
251
|
type: "button",
|
|
184
|
-
onClick: () =>
|
|
252
|
+
onClick: () => E("/investor/opportunities"),
|
|
185
253
|
className: "flex items-center gap-2 rounded-lg border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-accent transition-colors",
|
|
186
|
-
children: [/* @__PURE__ */
|
|
254
|
+
children: [/* @__PURE__ */ b(_, { className: "h-4 w-4" }), "Browse Opportunities"]
|
|
187
255
|
}),
|
|
188
|
-
/* @__PURE__ */
|
|
256
|
+
/* @__PURE__ */ x("button", {
|
|
189
257
|
type: "button",
|
|
190
|
-
onClick: () =>
|
|
258
|
+
onClick: () => E("/investor/investments"),
|
|
191
259
|
className: "flex items-center gap-2 rounded-lg border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-accent transition-colors",
|
|
192
|
-
children: [/* @__PURE__ */
|
|
260
|
+
children: [/* @__PURE__ */ b(y, { className: "h-4 w-4" }), "My Investments"]
|
|
193
261
|
})
|
|
194
262
|
]
|
|
195
263
|
})
|
|
@@ -197,6 +265,6 @@ function b() {
|
|
|
197
265
|
});
|
|
198
266
|
}
|
|
199
267
|
//#endregion
|
|
200
|
-
export {
|
|
268
|
+
export { E as InvestorAnalyticsPage };
|
|
201
269
|
|
|
202
270
|
//# sourceMappingURL=InvestorAnalyticsPage.js.map
|