|
7 | 7 | import dataExpeditions from '$lib/../data/expeditions.json'; |
8 | 8 | import { m } from '$lib/paraglide/messages'; |
9 | 9 |
|
10 | | - let { accountData = $bindable() }: { accountData: any } = $props(); |
| 10 | + interface AccountData { |
| 11 | + UserSettingsData?: { |
| 12 | + UnlockedSeasonRewards?: string[]; |
| 13 | + UnlockedTwitchRewards?: string[]; |
| 14 | + UnlockedPlatformRewards?: string[]; |
| 15 | + }; |
| 16 | + } |
11 | 17 |
|
12 | | - let seasonRewards = $state([]); |
13 | | - let twitchRewards = $state([]); |
14 | | - let platformRewards = $state([]); |
| 18 | + interface RewardItem { |
| 19 | + name: string; |
| 20 | + id: string; |
| 21 | + expedition?: number; |
| 22 | + drop?: number; |
| 23 | + platform?: string; |
| 24 | + } |
| 25 | +
|
| 26 | + interface TableGroup { |
| 27 | + label: string; |
| 28 | + items: Array<{ |
| 29 | + value: string; |
| 30 | + values: string[]; |
| 31 | + }>; |
| 32 | + } |
| 33 | +
|
| 34 | + let { accountData = $bindable() }: { accountData: AccountData } = $props(); |
| 35 | +
|
| 36 | + let seasonRewards = $state<string[]>([]); |
| 37 | + let twitchRewards = $state<string[]>([]); |
| 38 | + let platformRewards = $state<string[]>([]); |
| 39 | + let activeTab = $state('season_rewards'); |
| 40 | +
|
| 41 | + const typedExpeditions = dataExpeditions as Record<string, string>; |
| 42 | + const seasonData = dataRewardsSeason as RewardItem[]; |
| 43 | + const twitchData = dataRewardsTwitch as RewardItem[]; |
| 44 | + const platformData = dataRewardsPlatform as RewardItem[]; |
| 45 | +
|
| 46 | + // Computed values for better performance |
| 47 | + const seasonGroups = $derived<TableGroup[]>( |
| 48 | + Object.keys(typedExpeditions) |
| 49 | + .reverse() |
| 50 | + .map((expeditionID) => ({ |
| 51 | + label: `${expeditionID}: ${typedExpeditions[expeditionID]}`, |
| 52 | + items: seasonData |
| 53 | + .filter((reward) => reward.expedition === parseInt(expeditionID)) |
| 54 | + .map((reward) => ({ |
| 55 | + value: reward.id, |
| 56 | + values: [reward.name, reward.id, reward.expedition?.toString() || ''] |
| 57 | + })) |
| 58 | + })) |
| 59 | + .filter((group) => group.items.length > 0) |
| 60 | + ); |
| 61 | +
|
| 62 | + const twitchGroups = $derived<TableGroup[]>( |
| 63 | + Array.from(new Set(twitchData.map((reward) => reward.drop))) |
| 64 | + .sort((a, b) => (b || 0) - (a || 0)) |
| 65 | + .map((drop) => ({ |
| 66 | + label: `${m.page_account_table_drop()} ${drop}`, |
| 67 | + items: twitchData |
| 68 | + .filter((reward) => reward.drop === drop) |
| 69 | + .map((reward) => ({ |
| 70 | + value: reward.id, |
| 71 | + values: [reward.name, reward.id] |
| 72 | + })) |
| 73 | + })) |
| 74 | + ); |
| 75 | +
|
| 76 | + const platformGroups = $derived<TableGroup[]>( |
| 77 | + Array.from(new Set(platformData.map((reward) => reward.platform))) |
| 78 | + .filter(Boolean) |
| 79 | + .map((platform) => ({ |
| 80 | + label: platform!, |
| 81 | + items: platformData |
| 82 | + .filter((reward) => reward.platform === platform) |
| 83 | + .map((reward) => ({ |
| 84 | + value: reward.id, |
| 85 | + values: [reward.name, reward.id, reward.platform || ''] |
| 86 | + })) |
| 87 | + })) |
| 88 | + ); |
15 | 89 |
|
16 | 90 | // Initialize reward arrays when accountData is loaded |
17 | 91 | $effect(() => { |
18 | | - if (accountData && accountData.UserSettingsData) { |
| 92 | + if (accountData?.UserSettingsData) { |
19 | 93 | seasonRewards = accountData.UserSettingsData.UnlockedSeasonRewards || []; |
20 | 94 | twitchRewards = accountData.UserSettingsData.UnlockedTwitchRewards || []; |
21 | 95 | platformRewards = accountData.UserSettingsData.UnlockedPlatformRewards || []; |
|
24 | 98 |
|
25 | 99 | // Update accountData when reward arrays change |
26 | 100 | $effect(() => { |
27 | | - if (accountData && accountData.UserSettingsData) { |
| 101 | + if (accountData?.UserSettingsData) { |
28 | 102 | accountData.UserSettingsData.UnlockedSeasonRewards = seasonRewards; |
29 | 103 | accountData.UserSettingsData.UnlockedTwitchRewards = twitchRewards; |
30 | 104 | accountData.UserSettingsData.UnlockedPlatformRewards = platformRewards; |
31 | 105 | } |
32 | 106 | }); |
33 | 107 | </script> |
34 | 108 |
|
35 | | -<Tabs.Root value="season_rewards"> |
| 109 | +{#snippet statusBar(name: string, selected: number, total: number)} |
| 110 | + <div class="flex items-center justify-between rounded-lg bg-gray-100 px-4 py-2 dark:bg-gray-800/50"> |
| 111 | + <div class="flex items-center space-x-4"> |
| 112 | + <span class="text-sm font-medium text-gray-700 dark:text-gray-300"> |
| 113 | + {name} |
| 114 | + </span> |
| 115 | + <span class="text-xs text-gray-500 dark:text-gray-400"> |
| 116 | + {selected} / {total} |
| 117 | + </span> |
| 118 | + </div> |
| 119 | + <div class="flex items-center space-x-2"> |
| 120 | + <div class="h-2 w-24 rounded-full bg-gray-200 dark:bg-gray-700"> |
| 121 | + <div class="h-2 rounded-full bg-indigo-600 transition-all duration-300 ease-in-out" style="width: {total > 0 ? (selected / total) * 100 : 0}%" aria-hidden="true"></div> |
| 122 | + </div> |
| 123 | + <span class="text-xs font-medium text-gray-600 dark:text-gray-400"> |
| 124 | + {total > 0 ? Math.round((selected / total) * 100) : 0}% |
| 125 | + </span> |
| 126 | + </div> |
| 127 | + </div> |
| 128 | +{/snippet} |
| 129 | + |
| 130 | +<Tabs.Root bind:value={activeTab}> |
36 | 131 | <div class="mt-12 border-b border-white/10"> |
37 | | - <Tabs.List aria-label="Tabs" class="-mb-px flex"> |
38 | | - <!-- Current: "border-indigo-400 text-indigo-400", Default: "border-transparent text-gray-400 hover:border-white/20 hover:text-gray-300" --> |
| 132 | + <Tabs.List class="-mb-px flex"> |
39 | 133 | <Tabs.Trigger |
40 | 134 | value="season_rewards" |
41 | 135 | class="w-1/3 border-b-2 border-transparent px-1 py-4 text-center text-sm font-medium text-gray-500 hover:border-gray-300 hover:text-gray-700 data-[state=active]:border-indigo-500 data-[state=active]:text-indigo-600 dark:text-gray-400 dark:hover:border-white/20 dark:hover:text-gray-300 dark:data-[state=active]:border-indigo-400 dark:data-[state=active]:text-indigo-400" |
|
53 | 147 | > |
54 | 148 | </Tabs.List> |
55 | 149 | </div> |
56 | | - <Tabs.Content value="season_rewards"> |
57 | | - <AccountEditorPaneTable |
58 | | - header={[m.page_account_table_name(), m.page_account_table_id(), m.page_account_table_expedition()]} |
59 | | - groups={Object.keys(dataExpeditions) |
60 | | - .reverse() |
61 | | - .map((expeditionID) => ({ |
62 | | - label: expeditionID + ': ' + dataExpeditions[expeditionID], |
63 | | - items: dataRewardsSeason.filter((reward) => reward.expedition === parseInt(expeditionID)).map((reward) => ({ value: reward.id, values: [reward.name, reward.id, reward.expedition] })) |
64 | | - }))} |
65 | | - bind:values={seasonRewards} |
66 | | - /> |
| 150 | + |
| 151 | + <Tabs.Content value="season_rewards" class="mt-6 space-y-6"> |
| 152 | + {@render statusBar(m.page_account_tab_season_rewards().toString(), seasonRewards.length, seasonData.length)} |
| 153 | + <AccountEditorPaneTable header={[m.page_account_table_name(), m.page_account_table_id(), m.page_account_table_expedition()]} groups={seasonGroups} bind:values={seasonRewards} /> |
67 | 154 | </Tabs.Content> |
68 | | - <Tabs.Content value="twitch_rewards"> |
69 | | - <AccountEditorPaneTable |
70 | | - header={[m.page_account_table_name(), m.page_account_table_id()]} |
71 | | - groups={[ |
72 | | - ...Array.from(new Set(dataRewardsTwitch.map((reward) => reward.drop))) |
73 | | - .sort((a, b) => b - a) |
74 | | - .map((drop) => ({ |
75 | | - label: m.page_account_table_drop() + ` ${drop}`, |
76 | | - items: dataRewardsTwitch.filter((reward) => reward.drop === drop).map((reward) => ({ value: reward.id, values: [reward.name, reward.id] })) |
77 | | - })) |
78 | | - ]} |
79 | | - bind:values={twitchRewards} |
80 | | - /> |
| 155 | + |
| 156 | + <Tabs.Content value="twitch_rewards" class="mt-6 space-y-6"> |
| 157 | + {@render statusBar(m.page_account_tab_twitch_rewards().toString(), twitchRewards.length, twitchData.length)} |
| 158 | + <AccountEditorPaneTable header={[m.page_account_table_name(), m.page_account_table_id()]} groups={twitchGroups} bind:values={twitchRewards} /> |
81 | 159 | </Tabs.Content> |
82 | | - <Tabs.Content value="platform_rewards"> |
83 | | - <AccountEditorPaneTable |
84 | | - header={[m.page_account_table_name(), m.page_account_table_id(), m.page_account_table_platform()]} |
85 | | - groups={[ |
86 | | - ...Array.from(new Set(dataRewardsPlatform.map((reward) => reward.platform))).map((platform) => ({ |
87 | | - label: platform, |
88 | | - items: dataRewardsPlatform.filter((reward) => reward.platform === platform).map((reward) => ({ value: reward.id, values: [reward.name, reward.id, reward.platform] })) |
89 | | - })) |
90 | | - ]} |
91 | | - bind:values={platformRewards} |
92 | | - /> |
| 160 | + |
| 161 | + <Tabs.Content value="platform_rewards" class="mt-6 space-y-6"> |
| 162 | + {@render statusBar(m.page_account_tab_platform_rewards().toString(), platformRewards.length, platformData.length)} |
| 163 | + <AccountEditorPaneTable header={[m.page_account_table_name(), m.page_account_table_id(), m.page_account_table_platform()]} groups={platformGroups} bind:values={platformRewards} /> |
93 | 164 | </Tabs.Content> |
94 | 165 | </Tabs.Root> |
0 commit comments