@bettoredge/calcutta 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/package.json +46 -0
  2. package/src/components/CalcuttaAuction.tsx +453 -0
  3. package/src/components/CalcuttaAuctionItem.tsx +292 -0
  4. package/src/components/CalcuttaBidInput.tsx +214 -0
  5. package/src/components/CalcuttaCard.tsx +131 -0
  6. package/src/components/CalcuttaDetail.tsx +377 -0
  7. package/src/components/CalcuttaEscrow.tsx +464 -0
  8. package/src/components/CalcuttaItemResults.tsx +207 -0
  9. package/src/components/CalcuttaLeaderboard.tsx +179 -0
  10. package/src/components/CalcuttaPayoutPreview.tsx +194 -0
  11. package/src/components/CalcuttaRoundResults.tsx +250 -0
  12. package/src/components/CalcuttaTemplateSelector.tsx +124 -0
  13. package/src/components/sealed/AuctionResultsModal.tsx +165 -0
  14. package/src/components/sealed/EscrowBottomSheet.tsx +185 -0
  15. package/src/components/sealed/SealedBidAuction.tsx +541 -0
  16. package/src/components/sealed/SealedBidHeader.tsx +116 -0
  17. package/src/components/sealed/SealedBidInfoTab.tsx +247 -0
  18. package/src/components/sealed/SealedBidItemCard.tsx +385 -0
  19. package/src/components/sealed/SealedBidItemsTab.tsx +235 -0
  20. package/src/components/sealed/SealedBidMyBidsTab.tsx +512 -0
  21. package/src/components/sealed/SealedBidPlayersTab.tsx +220 -0
  22. package/src/components/sealed/SealedBidStatusBar.tsx +415 -0
  23. package/src/components/sealed/SealedBidTabBar.tsx +172 -0
  24. package/src/helpers/formatting.ts +56 -0
  25. package/src/helpers/lifecycleState.ts +71 -0
  26. package/src/helpers/payout.ts +39 -0
  27. package/src/helpers/validation.ts +64 -0
  28. package/src/hooks/useCalcuttaAuction.ts +164 -0
  29. package/src/hooks/useCalcuttaBid.ts +43 -0
  30. package/src/hooks/useCalcuttaCompetition.ts +63 -0
  31. package/src/hooks/useCalcuttaEscrow.ts +52 -0
  32. package/src/hooks/useCalcuttaItemImages.ts +79 -0
  33. package/src/hooks/useCalcuttaPlayers.ts +46 -0
  34. package/src/hooks/useCalcuttaResults.ts +58 -0
  35. package/src/hooks/useCalcuttaSocket.ts +131 -0
  36. package/src/hooks/useCalcuttaTemplates.ts +36 -0
  37. package/src/index.ts +74 -0
  38. package/src/types.ts +31 -0
@@ -0,0 +1,464 @@
1
+ import React, { useState, useEffect } from 'react';
2
+ import { StyleSheet, TextInput, TouchableOpacity, ActivityIndicator } from 'react-native';
3
+ import { View, Text, useTheme } from '@bettoredge/styles';
4
+ import { Ionicons } from '@expo/vector-icons';
5
+ import type { CalcuttaEscrowProps } from '@bettoredge/types';
6
+ import { useCalcuttaEscrow } from '../hooks/useCalcuttaEscrow';
7
+ import { formatCurrency } from '../helpers/formatting';
8
+
9
+ export interface CalcuttaEscrowComponentProps {
10
+ calcutta_competition_id: string;
11
+ escrow?: CalcuttaEscrowProps;
12
+ market_type: string;
13
+ max_escrow?: number;
14
+ player_balance?: number;
15
+ onDepositFunds?: (amount: number) => void;
16
+ onUpdate: (escrow: CalcuttaEscrowProps) => void;
17
+ readOnly?: boolean;
18
+ }
19
+
20
+ type EscrowAction = 'transfer_in' | 'transfer_out';
21
+
22
+ const QUICK_AMOUNTS = [10, 25, 50, 100];
23
+
24
+ export const CalcuttaEscrow: React.FC<CalcuttaEscrowComponentProps> = ({
25
+ calcutta_competition_id,
26
+ escrow: initialEscrow,
27
+ market_type,
28
+ max_escrow,
29
+ player_balance,
30
+ onDepositFunds,
31
+ onUpdate,
32
+ readOnly,
33
+ }) => {
34
+ const { theme } = useTheme();
35
+ const { escrow: hookEscrow, loading, error, fetchEscrow, deposit, withdraw } = useCalcuttaEscrow(calcutta_competition_id);
36
+ const [action, setAction] = useState<EscrowAction>('transfer_in');
37
+ const [amount, setAmount] = useState('');
38
+
39
+ const currentEscrow = hookEscrow ?? initialEscrow;
40
+
41
+ useEffect(() => {
42
+ if (!hookEscrow) {
43
+ fetchEscrow();
44
+ }
45
+ }, [hookEscrow, fetchEscrow]);
46
+
47
+ const available = Number(currentEscrow?.escrow_balance ?? 0);
48
+ const committed = Number(currentEscrow?.committed_balance ?? 0);
49
+ const total = available + committed;
50
+
51
+ // Budget cap calculations
52
+ const netDeposited = currentEscrow
53
+ ? Number(currentEscrow.total_deposited) - Number(currentEscrow.total_withdrawn)
54
+ : 0;
55
+ const budgetRemaining = max_escrow != null && Number(max_escrow) > 0
56
+ ? Math.max(0, Number(max_escrow) - netDeposited)
57
+ : null;
58
+ const budgetFullyDeposited = budgetRemaining !== null && budgetRemaining <= 0;
59
+ const budgetPct = max_escrow != null && Number(max_escrow) > 0
60
+ ? Math.min(100, (netDeposited / Number(max_escrow)) * 100)
61
+ : null;
62
+
63
+ const committedPct = total > 0 ? (committed / total) * 100 : 0;
64
+
65
+ const numericAmount = parseFloat(amount) || 0;
66
+ const hasBalance = player_balance != null;
67
+ const insufficientBalance = hasBalance && action === 'transfer_in' && numericAmount > Number(player_balance);
68
+ const shortfall = insufficientBalance ? numericAmount - Number(player_balance) : 0;
69
+
70
+ const handleAction = async () => {
71
+ if (isNaN(numericAmount) || numericAmount <= 0) return;
72
+ if (action === 'transfer_in' && budgetRemaining !== null && numericAmount > budgetRemaining) return;
73
+ if (insufficientBalance && onDepositFunds) {
74
+ onDepositFunds(Math.ceil(shortfall * 100) / 100);
75
+ return;
76
+ }
77
+ try {
78
+ let result;
79
+ if (action === 'transfer_in') {
80
+ result = await deposit(numericAmount);
81
+ } else {
82
+ result = await withdraw(numericAmount);
83
+ }
84
+ if (result) {
85
+ onUpdate(result);
86
+ setAmount('');
87
+ }
88
+ } catch {}
89
+ };
90
+
91
+ const handleQuickAmount = (amt: number) => {
92
+ setAmount(String(amt));
93
+ };
94
+
95
+ // ============================================
96
+ // READ-ONLY: big KPI cards only
97
+ // ============================================
98
+ if (readOnly) {
99
+ return (
100
+ <View variant="transparent" style={styles.container}>
101
+ <View variant="transparent" style={styles.kpiRow}>
102
+ <View variant="transparent" style={[styles.kpiCard, { backgroundColor: theme.colors.surface.elevated, borderColor: theme.colors.border.subtle }]}>
103
+ <View variant="transparent" style={[styles.kpiIcon, { backgroundColor: theme.colors.primary.subtle }]}>
104
+ <Ionicons name="wallet-outline" size={20} color={theme.colors.primary.default} />
105
+ </View>
106
+ <Text variant="caption" color="tertiary" style={styles.kpiLabel}>Available</Text>
107
+ <Text variant="h3" bold>{formatCurrency(available, market_type)}</Text>
108
+ </View>
109
+ <View variant="transparent" style={[styles.kpiCard, { backgroundColor: theme.colors.surface.elevated, borderColor: theme.colors.border.subtle }]}>
110
+ <View variant="transparent" style={[styles.kpiIcon, { backgroundColor: theme.colors.surface.base }]}>
111
+ <Ionicons name="lock-closed-outline" size={20} color={theme.colors.text.tertiary} />
112
+ </View>
113
+ <Text variant="caption" color="tertiary" style={styles.kpiLabel}>Committed</Text>
114
+ <Text variant="h3" bold>{formatCurrency(committed, market_type)}</Text>
115
+ </View>
116
+ </View>
117
+
118
+ {/* Usage bar */}
119
+ <View variant="transparent" style={styles.barWrap}>
120
+ <View variant="transparent" style={[styles.barTrack, { backgroundColor: theme.colors.surface.base }]}>
121
+ <View variant="transparent" style={[styles.barFill, { width: `${Math.min(100, committedPct)}%`, backgroundColor: theme.colors.primary.default }]} />
122
+ </View>
123
+ <Text variant="caption" color="tertiary" style={styles.barLabel}>
124
+ {formatCurrency(committed, market_type)} committed of {formatCurrency(total, market_type)} total
125
+ </Text>
126
+ </View>
127
+ </View>
128
+ );
129
+ }
130
+
131
+ // ============================================
132
+ // INTERACTIVE: full escrow management
133
+ // ============================================
134
+ return (
135
+ <View variant="transparent" style={styles.container}>
136
+ {/* KPI Cards */}
137
+ <View variant="transparent" style={styles.kpiRow}>
138
+ <View variant="transparent" style={[styles.kpiCard, { backgroundColor: theme.colors.surface.elevated, borderColor: theme.colors.border.subtle }]}>
139
+ <View variant="transparent" style={[styles.kpiIcon, { backgroundColor: theme.colors.primary.subtle }]}>
140
+ <Ionicons name="wallet-outline" size={20} color={theme.colors.primary.default} />
141
+ </View>
142
+ <Text variant="caption" color="tertiary" style={styles.kpiLabel}>Available</Text>
143
+ <Text variant="h3" bold>{formatCurrency(available, market_type)}</Text>
144
+ </View>
145
+ <View variant="transparent" style={[styles.kpiCard, { backgroundColor: theme.colors.surface.elevated, borderColor: theme.colors.border.subtle }]}>
146
+ <View variant="transparent" style={[styles.kpiIcon, { backgroundColor: theme.colors.surface.base }]}>
147
+ <Ionicons name="lock-closed-outline" size={20} color={theme.colors.text.tertiary} />
148
+ </View>
149
+ <Text variant="caption" color="tertiary" style={styles.kpiLabel}>Committed</Text>
150
+ <Text variant="h3" bold>{formatCurrency(committed, market_type)}</Text>
151
+ </View>
152
+ </View>
153
+
154
+ {/* Usage bar */}
155
+ <View variant="transparent" style={styles.barWrap}>
156
+ <View variant="transparent" style={[styles.barTrack, { backgroundColor: theme.colors.surface.base }]}>
157
+ <View variant="transparent" style={[styles.barFill, { width: `${Math.min(100, committedPct)}%`, backgroundColor: theme.colors.primary.default }]} />
158
+ </View>
159
+ </View>
160
+
161
+ {/* Wallet balance */}
162
+ {hasBalance && (
163
+ <View variant="transparent" style={[styles.walletRow, { backgroundColor: theme.colors.surface.elevated, borderColor: theme.colors.border.subtle }]}>
164
+ <Ionicons name="card-outline" size={16} color={theme.colors.text.tertiary} />
165
+ <Text variant="caption" color="secondary" style={{ marginLeft: 8, flex: 1 }}>Wallet Balance</Text>
166
+ <Text variant="body" bold>{formatCurrency(player_balance, market_type)}</Text>
167
+ </View>
168
+ )}
169
+
170
+ {/* Budget cap */}
171
+ {budgetRemaining !== null && (
172
+ <View variant="transparent" style={styles.budgetWrap}>
173
+ <View variant="transparent" style={{ flexDirection: 'row', justifyContent: 'space-between', marginBottom: 6 }}>
174
+ <Text variant="caption" color="secondary">Budget Cap</Text>
175
+ <Text variant="caption" bold style={{ color: budgetFullyDeposited ? theme.colors.status.success : theme.colors.text.primary }}>
176
+ {budgetFullyDeposited ? 'Fully deposited' : `${formatCurrency(budgetRemaining, market_type)} remaining`}
177
+ </Text>
178
+ </View>
179
+ <View variant="transparent" style={[styles.barTrack, { backgroundColor: theme.colors.surface.base }]}>
180
+ <View variant="transparent" style={[styles.barFill, {
181
+ width: `${budgetPct ?? 0}%`,
182
+ backgroundColor: budgetFullyDeposited ? theme.colors.status.success : theme.colors.primary.default,
183
+ }]} />
184
+ </View>
185
+ <Text variant="caption" color="tertiary" style={{ marginTop: 4 }}>
186
+ {formatCurrency(netDeposited, market_type)} of {formatCurrency(Number(max_escrow), market_type)}
187
+ </Text>
188
+ </View>
189
+ )}
190
+
191
+ {/* Transfer In / Out toggle */}
192
+ <View variant="transparent" style={styles.toggleRow}>
193
+ <TouchableOpacity
194
+ style={[
195
+ styles.toggleButton,
196
+ action === 'transfer_in'
197
+ ? { backgroundColor: theme.colors.primary.default }
198
+ : { backgroundColor: theme.colors.surface.elevated, borderColor: theme.colors.border.subtle, borderWidth: 1 },
199
+ ]}
200
+ onPress={() => setAction('transfer_in')}
201
+ activeOpacity={0.7}
202
+ >
203
+ <Ionicons
204
+ name="arrow-down-outline"
205
+ size={18}
206
+ color={action === 'transfer_in' ? '#FFFFFF' : theme.colors.text.secondary}
207
+ />
208
+ <Text
209
+ variant="body"
210
+ bold
211
+ style={{ color: action === 'transfer_in' ? '#FFFFFF' : theme.colors.text.secondary, marginLeft: 6 }}
212
+ >
213
+ Transfer In
214
+ </Text>
215
+ </TouchableOpacity>
216
+ <TouchableOpacity
217
+ style={[
218
+ styles.toggleButton,
219
+ { marginLeft: 10 },
220
+ action === 'transfer_out'
221
+ ? { backgroundColor: theme.colors.primary.default }
222
+ : { backgroundColor: theme.colors.surface.elevated, borderColor: theme.colors.border.subtle, borderWidth: 1 },
223
+ ]}
224
+ onPress={() => setAction('transfer_out')}
225
+ activeOpacity={0.7}
226
+ >
227
+ <Ionicons
228
+ name="arrow-up-outline"
229
+ size={18}
230
+ color={action === 'transfer_out' ? '#FFFFFF' : theme.colors.text.secondary}
231
+ />
232
+ <Text
233
+ variant="body"
234
+ bold
235
+ style={{ color: action === 'transfer_out' ? '#FFFFFF' : theme.colors.text.secondary, marginLeft: 6 }}
236
+ >
237
+ Transfer Out
238
+ </Text>
239
+ </TouchableOpacity>
240
+ </View>
241
+
242
+ {/* Quick amount buttons */}
243
+ <View variant="transparent" style={styles.quickRow}>
244
+ {QUICK_AMOUNTS.map(amt => (
245
+ <TouchableOpacity
246
+ key={amt}
247
+ style={[styles.quickBtn, {
248
+ backgroundColor: numericAmount === amt ? theme.colors.primary.subtle : theme.colors.surface.elevated,
249
+ borderColor: numericAmount === amt ? theme.colors.primary.default : theme.colors.border.subtle,
250
+ }]}
251
+ onPress={() => handleQuickAmount(amt)}
252
+ activeOpacity={0.7}
253
+ >
254
+ <Text variant="body" bold style={{
255
+ color: numericAmount === amt ? theme.colors.primary.default : theme.colors.text.primary,
256
+ }}>
257
+ {formatCurrency(amt, market_type)}
258
+ </Text>
259
+ </TouchableOpacity>
260
+ ))}
261
+ </View>
262
+
263
+ {/* Amount input + submit */}
264
+ <View variant="transparent" style={styles.inputRow}>
265
+ <View variant="transparent" style={[styles.inputWrap, {
266
+ borderColor: insufficientBalance ? theme.colors.status.error : theme.colors.border.subtle,
267
+ backgroundColor: theme.colors.surface.input,
268
+ }]}>
269
+ <Text variant="body" color="tertiary" style={{ marginRight: 4 }}>$</Text>
270
+ <TextInput
271
+ style={[styles.input, { color: theme.colors.text.primary }]}
272
+ value={amount}
273
+ onChangeText={setAmount}
274
+ keyboardType="decimal-pad"
275
+ placeholder="0.00"
276
+ placeholderTextColor={theme.colors.text.tertiary}
277
+ />
278
+ </View>
279
+ <TouchableOpacity
280
+ style={[styles.submitBtn, {
281
+ backgroundColor: insufficientBalance
282
+ ? theme.colors.status.error
283
+ : (loading || !amount || numericAmount <= 0 || (action === 'transfer_in' && budgetFullyDeposited))
284
+ ? theme.colors.surface.elevated
285
+ : theme.colors.primary.default,
286
+ }]}
287
+ onPress={handleAction}
288
+ disabled={loading || !amount || numericAmount <= 0 || (action === 'transfer_in' && budgetFullyDeposited)}
289
+ activeOpacity={0.7}
290
+ >
291
+ {loading ? (
292
+ <ActivityIndicator size="small" color="#FFFFFF" />
293
+ ) : (
294
+ <>
295
+ <Ionicons
296
+ name={action === 'transfer_in' ? 'arrow-down-circle' : 'arrow-up-circle'}
297
+ size={20}
298
+ color={(!amount || numericAmount <= 0) ? theme.colors.text.tertiary : '#FFFFFF'}
299
+ />
300
+ <Text
301
+ variant="body"
302
+ bold
303
+ style={{
304
+ color: (!amount || numericAmount <= 0) ? theme.colors.text.tertiary : '#FFFFFF',
305
+ marginLeft: 6,
306
+ }}
307
+ >
308
+ {insufficientBalance
309
+ ? 'Deposit & Transfer'
310
+ : action === 'transfer_in'
311
+ ? 'Transfer In'
312
+ : 'Transfer Out'}
313
+ </Text>
314
+ </>
315
+ )}
316
+ </TouchableOpacity>
317
+ </View>
318
+
319
+ {/* Insufficient balance */}
320
+ {insufficientBalance && (
321
+ <View variant="transparent" style={[styles.warningRow, { backgroundColor: theme.colors.status.error + '10' }]}>
322
+ <Ionicons name="warning-outline" size={16} color={theme.colors.status.error} />
323
+ <Text variant="caption" style={{ color: theme.colors.status.error, marginLeft: 8, flex: 1 }}>
324
+ You need {formatCurrency(shortfall, market_type)} more in your wallet. Tap to deposit first.
325
+ </Text>
326
+ </View>
327
+ )}
328
+
329
+ {/* Error */}
330
+ {error && (
331
+ <View variant="transparent" style={[styles.warningRow, { backgroundColor: theme.colors.status.error + '10' }]}>
332
+ <Ionicons name="alert-circle-outline" size={16} color={theme.colors.status.error} />
333
+ <Text variant="caption" style={{ color: theme.colors.status.error, marginLeft: 8, flex: 1 }}>
334
+ {error}
335
+ </Text>
336
+ </View>
337
+ )}
338
+ </View>
339
+ );
340
+ };
341
+
342
+ const styles = StyleSheet.create({
343
+ container: {
344
+ padding: 14,
345
+ },
346
+ // KPI Cards
347
+ kpiRow: {
348
+ flexDirection: 'row',
349
+ gap: 10,
350
+ },
351
+ kpiCard: {
352
+ flex: 1,
353
+ borderRadius: 12,
354
+ borderWidth: 1,
355
+ padding: 14,
356
+ alignItems: 'center',
357
+ },
358
+ kpiIcon: {
359
+ width: 40,
360
+ height: 40,
361
+ borderRadius: 20,
362
+ alignItems: 'center',
363
+ justifyContent: 'center',
364
+ marginBottom: 8,
365
+ },
366
+ kpiLabel: {
367
+ marginBottom: 2,
368
+ },
369
+ // Usage bar
370
+ barWrap: {
371
+ marginTop: 12,
372
+ },
373
+ barTrack: {
374
+ height: 6,
375
+ borderRadius: 3,
376
+ overflow: 'hidden',
377
+ },
378
+ barFill: {
379
+ height: 6,
380
+ borderRadius: 3,
381
+ },
382
+ barLabel: {
383
+ marginTop: 4,
384
+ textAlign: 'center',
385
+ },
386
+ // Wallet
387
+ walletRow: {
388
+ flexDirection: 'row',
389
+ alignItems: 'center',
390
+ padding: 12,
391
+ borderRadius: 10,
392
+ borderWidth: 1,
393
+ marginTop: 12,
394
+ },
395
+ // Budget
396
+ budgetWrap: {
397
+ marginTop: 12,
398
+ },
399
+ // Transfer toggle
400
+ toggleRow: {
401
+ flexDirection: 'row',
402
+ marginTop: 16,
403
+ },
404
+ toggleButton: {
405
+ flex: 1,
406
+ flexDirection: 'row',
407
+ height: 48,
408
+ borderRadius: 10,
409
+ alignItems: 'center',
410
+ justifyContent: 'center',
411
+ },
412
+ // Quick amounts
413
+ quickRow: {
414
+ flexDirection: 'row',
415
+ marginTop: 12,
416
+ gap: 8,
417
+ },
418
+ quickBtn: {
419
+ flex: 1,
420
+ height: 40,
421
+ borderRadius: 8,
422
+ borderWidth: 1,
423
+ alignItems: 'center',
424
+ justifyContent: 'center',
425
+ },
426
+ // Input
427
+ inputRow: {
428
+ flexDirection: 'row',
429
+ alignItems: 'center',
430
+ marginTop: 12,
431
+ gap: 10,
432
+ },
433
+ inputWrap: {
434
+ flex: 1,
435
+ flexDirection: 'row',
436
+ alignItems: 'center',
437
+ height: 48,
438
+ borderRadius: 10,
439
+ borderWidth: 1,
440
+ paddingHorizontal: 14,
441
+ },
442
+ input: {
443
+ flex: 1,
444
+ fontSize: 18,
445
+ height: 48,
446
+ padding: 0,
447
+ },
448
+ submitBtn: {
449
+ flexDirection: 'row',
450
+ height: 48,
451
+ paddingHorizontal: 16,
452
+ borderRadius: 10,
453
+ alignItems: 'center',
454
+ justifyContent: 'center',
455
+ },
456
+ // Warnings
457
+ warningRow: {
458
+ flexDirection: 'row',
459
+ alignItems: 'center',
460
+ padding: 12,
461
+ borderRadius: 10,
462
+ marginTop: 10,
463
+ },
464
+ });
@@ -0,0 +1,207 @@
1
+ import React from 'react';
2
+ import { StyleSheet, FlatList } from 'react-native';
3
+ import { View, Text, useTheme } from '@bettoredge/styles';
4
+ import { Ionicons } from '@expo/vector-icons';
5
+ import type { CalcuttaAuctionItemProps, CalcuttaItemResultProps } from '@bettoredge/types';
6
+ import { formatCurrency, getStatusLabel } from '../helpers/formatting';
7
+
8
+ export interface CalcuttaItemResultsProps {
9
+ item: CalcuttaAuctionItemProps;
10
+ item_results: CalcuttaItemResultProps[];
11
+ market_type: string;
12
+ }
13
+
14
+ export const CalcuttaItemResults: React.FC<CalcuttaItemResultsProps> = ({
15
+ item,
16
+ item_results,
17
+ market_type,
18
+ }) => {
19
+ const { theme } = useTheme();
20
+
21
+ const results = [...item_results]
22
+ .filter(r => r.calcutta_auction_item_id === item.calcutta_auction_item_id)
23
+ .sort((a, b) => (a.round_number ?? 0) - (b.round_number ?? 0));
24
+
25
+ const totalPayout = results.reduce((sum, r) => sum + Number(r.payout_earned), 0);
26
+
27
+ const getResultIcon = (result: string): { name: keyof typeof Ionicons.glyphMap; color: string } => {
28
+ switch (result) {
29
+ case 'advanced':
30
+ return { name: 'checkmark-circle', color: theme.colors.status.success };
31
+ case 'eliminated':
32
+ return { name: 'close-circle', color: theme.colors.status.error };
33
+ case 'won':
34
+ return { name: 'trophy', color: '#FFD700' };
35
+ case 'placed':
36
+ return { name: 'medal-outline', color: theme.colors.primary.default };
37
+ default:
38
+ return { name: 'ellipse-outline', color: theme.colors.text.tertiary };
39
+ }
40
+ };
41
+
42
+ const renderResult = ({ item: result }: { item: CalcuttaItemResultProps }) => {
43
+ const icon = getResultIcon(result.result);
44
+ return (
45
+ <View
46
+ variant="transparent"
47
+ style={[styles.resultRow, { borderColor: theme.colors.border.subtle }]}
48
+ >
49
+ <View variant="transparent" style={styles.roundCol}>
50
+ <Text variant="caption" color="secondary">
51
+ R{result.round_number ?? '-'}
52
+ </Text>
53
+ </View>
54
+ <Ionicons name={icon.name} size={16} color={icon.color} style={styles.resultIcon} />
55
+ <View variant="transparent" style={styles.resultInfo}>
56
+ <Text variant="body">{getStatusLabel(result.result)}</Text>
57
+ {result.placement != null && (
58
+ <Text variant="caption" color="tertiary">
59
+ Placement: {result.placement}
60
+ </Text>
61
+ )}
62
+ </View>
63
+ <View variant="transparent" style={styles.payoutCol}>
64
+ {result.payout_earned > 0 ? (
65
+ <Text variant="caption" style={{ color: theme.colors.status.success }}>
66
+ +{formatCurrency(result.payout_earned, market_type)}
67
+ </Text>
68
+ ) : (
69
+ <Text variant="caption" color="tertiary">-</Text>
70
+ )}
71
+ {result.processed && (
72
+ <Ionicons name="checkmark" size={12} color={theme.colors.status.success} style={{ marginTop: 2 }} />
73
+ )}
74
+ </View>
75
+ </View>
76
+ );
77
+ };
78
+
79
+ return (
80
+ <View variant="transparent" style={styles.container}>
81
+ {/* Item header */}
82
+ <View variant="transparent" style={[styles.header, { backgroundColor: theme.colors.surface.elevated, borderColor: theme.colors.border.subtle }]}>
83
+ <View variant="transparent" style={styles.headerLeft}>
84
+ <View variant="transparent" style={[styles.itemIcon, { backgroundColor: theme.colors.surface.base }]}>
85
+ <Ionicons
86
+ name={item.item_type === 'team' ? 'people-outline' : 'person-outline'}
87
+ size={18}
88
+ color={theme.colors.text.tertiary}
89
+ />
90
+ </View>
91
+ <View variant="transparent" style={styles.headerInfo}>
92
+ <Text variant="body" bold numberOfLines={1}>{item.item_name}</Text>
93
+ <Text variant="caption" color="tertiary">
94
+ {item.seed != null ? `#${item.seed} Seed` : item.item_type}
95
+ {' \u00B7 '}
96
+ {getStatusLabel(item.status)}
97
+ </Text>
98
+ </View>
99
+ </View>
100
+ <View variant="transparent" style={styles.headerRight}>
101
+ <Text variant="caption" color="tertiary">Won for</Text>
102
+ <Text variant="body" bold>
103
+ {item.winning_bid > 0 ? formatCurrency(item.winning_bid, market_type) : '-'}
104
+ </Text>
105
+ </View>
106
+ </View>
107
+
108
+ {/* Summary */}
109
+ <View variant="transparent" style={[styles.summary, { borderColor: theme.colors.border.subtle }]}>
110
+ <View variant="transparent" style={styles.summaryItem}>
111
+ <Text variant="caption" color="tertiary">Rounds</Text>
112
+ <Text variant="body" bold>{results.length}</Text>
113
+ </View>
114
+ <View variant="transparent" style={styles.summaryItem}>
115
+ <Text variant="caption" color="tertiary">Advanced</Text>
116
+ <Text variant="body" bold style={{ color: theme.colors.status.success }}>
117
+ {results.filter(r => r.result === 'advanced' || r.result === 'won').length}
118
+ </Text>
119
+ </View>
120
+ <View variant="transparent" style={styles.summaryItem}>
121
+ <Text variant="caption" color="tertiary">Total Payout</Text>
122
+ <Text variant="body" bold style={{ color: totalPayout > 0 ? theme.colors.status.success : theme.colors.text.primary }}>
123
+ {formatCurrency(totalPayout, market_type)}
124
+ </Text>
125
+ </View>
126
+ </View>
127
+
128
+ {/* Results list */}
129
+ <FlatList
130
+ data={results}
131
+ keyExtractor={r => r.calcutta_item_result_id}
132
+ renderItem={renderResult}
133
+ ListEmptyComponent={
134
+ <Text variant="caption" color="tertiary" style={styles.emptyText}>
135
+ No results yet
136
+ </Text>
137
+ }
138
+ />
139
+ </View>
140
+ );
141
+ };
142
+
143
+ const styles = StyleSheet.create({
144
+ container: {
145
+ flex: 1,
146
+ },
147
+ header: {
148
+ flexDirection: 'row',
149
+ alignItems: 'center',
150
+ padding: 12,
151
+ borderBottomWidth: 1,
152
+ },
153
+ headerLeft: {
154
+ flex: 1,
155
+ flexDirection: 'row',
156
+ alignItems: 'center',
157
+ },
158
+ itemIcon: {
159
+ width: 36,
160
+ height: 36,
161
+ borderRadius: 18,
162
+ alignItems: 'center',
163
+ justifyContent: 'center',
164
+ },
165
+ headerInfo: {
166
+ flex: 1,
167
+ marginLeft: 10,
168
+ },
169
+ headerRight: {
170
+ alignItems: 'flex-end',
171
+ marginLeft: 10,
172
+ },
173
+ summary: {
174
+ flexDirection: 'row',
175
+ paddingVertical: 10,
176
+ paddingHorizontal: 12,
177
+ borderBottomWidth: 1,
178
+ },
179
+ summaryItem: {
180
+ flex: 1,
181
+ alignItems: 'center',
182
+ },
183
+ resultRow: {
184
+ flexDirection: 'row',
185
+ alignItems: 'center',
186
+ paddingVertical: 10,
187
+ paddingHorizontal: 12,
188
+ borderBottomWidth: 1,
189
+ },
190
+ roundCol: {
191
+ width: 30,
192
+ },
193
+ resultIcon: {
194
+ marginRight: 8,
195
+ },
196
+ resultInfo: {
197
+ flex: 1,
198
+ },
199
+ payoutCol: {
200
+ alignItems: 'flex-end',
201
+ marginLeft: 10,
202
+ },
203
+ emptyText: {
204
+ textAlign: 'center',
205
+ padding: 20,
206
+ },
207
+ });