Skip to content

Commit 7ace29a

Browse files
committed
chore(dashboard): bug fixes, update ability version selections
1 parent d2139f4 commit 7ace29a

18 files changed

+1015
-216
lines changed

packages/apps/app-dashboard/src/components/developer-dashboard/AbilitySelectorModal.tsx

Lines changed: 203 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { useState } from 'react';
12
import { AgGridReact } from 'ag-grid-react';
23
import {
34
ColDef,
@@ -6,6 +7,7 @@ import {
67
AllCommunityModule,
78
RowClickedEvent,
89
} from 'ag-grid-community';
10+
import { CheckCircle2, X } from 'lucide-react';
911

1012
import { Ability } from '@/types/developer-dashboard/appTypes';
1113
import {
@@ -17,22 +19,49 @@ import {
1719
} from '@/components/shared/ui/dialog';
1820
import { Button } from '@/components/shared/ui/button';
1921
import { theme, fonts } from '@/components/user-dashboard/connect/ui/theme';
22+
import { AbilityVersionSelectorModal } from './AbilityVersionSelectorModal';
2023

2124
// Register AG Grid modules
2225
ModuleRegistry.registerModules([AllCommunityModule]);
2326

24-
// Static column definitions
25-
const TOOL_GRID_COLUMNS: ColDef[] = [
27+
interface SelectedAbilityWithVersion {
28+
ability: Ability;
29+
version: string;
30+
}
31+
32+
// Dynamic column definitions based on selected abilities
33+
const createToolGridColumns = (
34+
selectedAbilities: Map<string, SelectedAbilityWithVersion>,
35+
): ColDef[] => [
36+
{
37+
headerName: '',
38+
field: 'packageName',
39+
width: 50,
40+
maxWidth: 50,
41+
minWidth: 50,
42+
suppressNavigable: true,
43+
cellRenderer: (params: ICellRendererParams) => {
44+
const isSelected = selectedAbilities.has(params.value);
45+
return (
46+
<div className="flex items-center justify-center h-full">
47+
{isSelected && <CheckCircle2 className="w-5 h-5" style={{ color: theme.brandOrange }} />}
48+
</div>
49+
);
50+
},
51+
},
2652
{
2753
headerName: 'Ability Name',
2854
field: 'title',
2955
flex: 2,
3056
minWidth: 200,
3157
cellRenderer: (params: ICellRendererParams) => {
58+
const isSelected = selectedAbilities.has(params.data.packageName);
3259
return (
3360
<div className="flex items-center justify-between h-full">
3461
<div>
35-
<div className="font-medium">{params.value || params.data.packageName}</div>
62+
<div className={`font-medium ${isSelected ? 'font-semibold' : ''}`}>
63+
{params.value || params.data.packageName}
64+
</div>
3665
</div>
3766
</div>
3867
);
@@ -74,11 +103,21 @@ const TOOL_GRID_COLUMNS: ColDef[] = [
74103
headerName: 'Version',
75104
field: 'activeVersion',
76105
flex: 1,
77-
minWidth: 100,
106+
minWidth: 120,
78107
cellRenderer: (params: ICellRendererParams) => {
108+
const selected = selectedAbilities.get(params.data.packageName);
109+
const displayVersion = selected ? selected.version : params.value;
110+
const isSelected = !!selected;
79111
return (
80112
<div className="flex items-center h-full">
81-
<span>{params.value}</span>
113+
<span className={isSelected ? 'font-semibold' : ''}>
114+
{displayVersion}
115+
{isSelected && (
116+
<span className="ml-2 text-xs" style={{ color: theme.brandOrange }}>
117+
(selected)
118+
</span>
119+
)}
120+
</span>
82121
</div>
83122
);
84123
},
@@ -123,9 +162,16 @@ export function AbilitySelectorModal({
123162
existingAbilities,
124163
availableAbilities,
125164
}: AbilitySelectorModalProps) {
126-
// Filter out already added abilities
165+
const [selectedAbility, setSelectedAbility] = useState<Ability | null>(null);
166+
const [isVersionSelectorOpen, setIsVersionSelectorOpen] = useState(false);
167+
const [selectedAbilities, setSelectedAbilities] = useState<
168+
Map<string, SelectedAbilityWithVersion>
169+
>(new Map());
170+
const [isSubmitting, setIsSubmitting] = useState(false);
171+
172+
// Filter out already added abilities and deleted abilities
127173
const filteredAbilities = availableAbilities.filter(
128-
(ability) => !existingAbilities.includes(ability.packageName),
174+
(ability) => !existingAbilities.includes(ability.packageName) && !ability.isDeleted,
129175
);
130176

131177
const handleRowClick = async (event: RowClickedEvent) => {
@@ -134,17 +180,83 @@ export function AbilitySelectorModal({
134180
return;
135181
}
136182

137-
await onAbilityAdd(ability);
183+
// If already selected, deselect it
184+
if (selectedAbilities.has(ability.packageName)) {
185+
handleRemoveSelection(ability.packageName);
186+
return;
187+
}
188+
189+
// Open version selector modal to select
190+
setSelectedAbility(ability);
191+
setIsVersionSelectorOpen(true);
192+
};
193+
194+
const handleVersionSelect = async (version: string) => {
195+
if (!selectedAbility) return;
196+
197+
// Add to selected abilities map
198+
setSelectedAbilities((prev) => {
199+
const newMap = new Map(prev);
200+
newMap.set(selectedAbility.packageName, {
201+
ability: selectedAbility,
202+
version: version,
203+
});
204+
return newMap;
205+
});
206+
207+
// Close version selector modal but keep ability selector open
208+
setIsVersionSelectorOpen(false);
209+
setSelectedAbility(null);
210+
};
211+
212+
const handleRemoveSelection = (packageName: string) => {
213+
setSelectedAbilities((prev) => {
214+
const newMap = new Map(prev);
215+
newMap.delete(packageName);
216+
return newMap;
217+
});
218+
};
219+
220+
const handleAddAbilities = async () => {
221+
if (selectedAbilities.size === 0) return;
222+
223+
setIsSubmitting(true);
224+
try {
225+
// Add all selected abilities in parallel
226+
await Promise.all(
227+
Array.from(selectedAbilities.values()).map(({ ability, version }) => {
228+
const abilityWithVersion = {
229+
...ability,
230+
activeVersion: version,
231+
};
232+
return onAbilityAdd(abilityWithVersion);
233+
}),
234+
);
235+
236+
// Clear selections but keep modal open for more selections
237+
setSelectedAbilities(new Map());
238+
} catch (error) {
239+
console.error('Failed to add abilities:', error);
240+
} finally {
241+
setIsSubmitting(false);
242+
}
243+
};
244+
245+
const handleVersionSelectorClose = () => {
246+
setIsVersionSelectorOpen(false);
247+
setSelectedAbility(null);
138248
};
139249

140250
const handleOpenChange = (open: boolean) => {
141251
if (!open) {
252+
setSelectedAbilities(new Map());
142253
onClose();
143254
}
144255
};
145256

146-
const getRowClass = () => {
147-
return 'cursor-pointer hover:bg-gray-50 dark:hover:bg-neutral-700';
257+
const getRowClass = (params: any) => {
258+
const isSelected = selectedAbilities.has(params.data?.packageName);
259+
return `cursor-pointer hover:bg-gray-50 dark:hover:bg-neutral-700 ${isSelected ? 'bg-orange-50 dark:bg-orange-900/10' : ''}`;
148260
};
149261

150262
return (
@@ -158,9 +270,8 @@ export function AbilitySelectorModal({
158270
Add Abilities to App Version
159271
</DialogTitle>
160272
<DialogDescription className={`${theme.textMuted}`} style={fonts.body}>
161-
Click any ability to add it immediately to your app version.
162-
{existingAbilities.length > 0 &&
163-
` (${existingAbilities.length} abilities already added)`}
273+
Click any ability to select a version. Click again to deselect. You can select multiple
274+
abilities before adding them.
164275
</DialogDescription>
165276
</DialogHeader>
166277

@@ -182,7 +293,7 @@ export function AbilitySelectorModal({
182293
>
183294
<AgGridReact
184295
rowData={filteredAbilities}
185-
columnDefs={TOOL_GRID_COLUMNS}
296+
columnDefs={createToolGridColumns(selectedAbilities)}
186297
defaultColDef={DEFAULT_COL_DEF}
187298
onRowClicked={handleRowClick}
188299
getRowClass={getRowClass}
@@ -195,12 +306,86 @@ export function AbilitySelectorModal({
195306
</div>
196307
</div>
197308

198-
<div className={`flex justify-end gap-2 pt-4 border-t ${theme.cardBorder} flex-shrink-0`}>
199-
<Button variant="outline" onClick={onClose}>
200-
Close
201-
</Button>
309+
{/* Selected Abilities List */}
310+
{selectedAbilities.size > 0 && (
311+
<div className={`flex-shrink-0 border ${theme.mainCardBorder} rounded-lg p-4`}>
312+
<div className="flex items-center justify-between mb-2">
313+
<h4 className={`text-sm font-semibold ${theme.text}`} style={fonts.heading}>
314+
Selected Abilities ({selectedAbilities.size})
315+
</h4>
316+
</div>
317+
<div className="space-y-2 max-h-32 overflow-y-auto">
318+
{Array.from(selectedAbilities.values()).map(({ ability, version }) => (
319+
<div
320+
key={ability.packageName}
321+
className={`flex items-center justify-between px-3 py-2 rounded-lg ${theme.itemBg}`}
322+
>
323+
<div className="flex-1 min-w-0">
324+
<div className="flex items-center gap-2">
325+
<span className={`text-sm font-medium ${theme.text} truncate`}>
326+
{ability.title || ability.packageName}
327+
</span>
328+
<span
329+
className="text-xs px-2 py-0.5 rounded"
330+
style={{ backgroundColor: theme.brandOrange, color: 'white' }}
331+
>
332+
v{version}
333+
</span>
334+
</div>
335+
<div className={`text-xs ${theme.textMuted} font-mono truncate`}>
336+
{ability.packageName}
337+
</div>
338+
</div>
339+
<button
340+
onClick={() => handleRemoveSelection(ability.packageName)}
341+
className={`ml-2 p-1 rounded hover:bg-red-100 dark:hover:bg-red-900/30`}
342+
title="Remove from selection"
343+
>
344+
<X className="w-4 h-4 text-red-600 dark:text-red-400" />
345+
</button>
346+
</div>
347+
))}
348+
</div>
349+
</div>
350+
)}
351+
352+
<div
353+
className={`flex justify-between items-center gap-2 pt-4 border-t ${theme.cardBorder} flex-shrink-0`}
354+
>
355+
<div className={`text-sm ${theme.textMuted}`}>
356+
{selectedAbilities.size > 0 ? (
357+
<span>
358+
{selectedAbilities.size} {selectedAbilities.size === 1 ? 'ability' : 'abilities'}{' '}
359+
selected
360+
</span>
361+
) : (
362+
<span>Select abilities to add to your app version</span>
363+
)}
364+
</div>
365+
<div className="flex gap-2">
366+
<Button variant="outline" onClick={onClose} disabled={isSubmitting}>
367+
Cancel
368+
</Button>
369+
<Button
370+
onClick={handleAddAbilities}
371+
disabled={selectedAbilities.size === 0 || isSubmitting}
372+
style={{ backgroundColor: theme.brandOrange, ...fonts.body }}
373+
>
374+
{isSubmitting
375+
? 'Adding Abilities...'
376+
: `Add ${selectedAbilities.size > 0 ? selectedAbilities.size : ''} ${selectedAbilities.size === 1 ? 'Ability' : 'Abilities'}`}
377+
</Button>
378+
</div>
202379
</div>
203380
</DialogContent>
381+
382+
{/* Version Selector Modal */}
383+
<AbilityVersionSelectorModal
384+
isOpen={isVersionSelectorOpen}
385+
onClose={handleVersionSelectorClose}
386+
onVersionSelect={handleVersionSelect}
387+
ability={selectedAbility}
388+
/>
204389
</Dialog>
205390
);
206391
}

0 commit comments

Comments
 (0)