Skip to content

Commit 6360d73

Browse files
authored
Merge pull request #14 from awork-io/codex/fix-select-option-reorder
[Fix] Allow select option reordering
2 parents 73db63f + 79c91fd commit 6360d73

2 files changed

Lines changed: 107 additions & 10 deletions

File tree

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import { render, screen } from '@testing-library/react';
2+
import userEvent from '@testing-library/user-event';
3+
import { describe, expect, it, vi } from 'vitest';
4+
import '@/i18n';
5+
import { FieldConfigDialog } from './FieldConfigDialog';
6+
import type { AworkIntegrationConfig } from './AworkIntegrationSettings';
7+
import type { FormField } from '@/lib/form-types';
8+
9+
const emptyAworkConfig: AworkIntegrationConfig = {
10+
actionType: null,
11+
projectId: null,
12+
projectTypeId: null,
13+
taskListId: null,
14+
taskStatusId: null,
15+
typeOfWorkId: null,
16+
assigneeId: null,
17+
isPriority: false,
18+
taskTag: null,
19+
taskFieldMappings: [],
20+
projectFieldMappings: [],
21+
};
22+
23+
describe('FieldConfigDialog', () => {
24+
it('reorders select field options', async () => {
25+
const user = userEvent.setup();
26+
const onUpdate = vi.fn();
27+
const field: FormField = {
28+
id: 'field-1',
29+
type: 'select',
30+
label: 'Selection',
31+
required: false,
32+
options: [
33+
{ label: 'First', value: 'first' },
34+
{ label: 'Second', value: 'second' },
35+
{ label: 'Third', value: 'third' },
36+
],
37+
};
38+
39+
render(
40+
<FieldConfigDialog
41+
field={field}
42+
open
43+
onOpenChange={vi.fn()}
44+
onUpdate={onUpdate}
45+
onDelete={vi.fn()}
46+
aworkConfig={emptyAworkConfig}
47+
onAworkConfigChange={vi.fn()}
48+
aworkCustomFields={[]}
49+
/>
50+
);
51+
52+
await user.click(screen.getAllByLabelText('Move option down')[0]);
53+
54+
expect(onUpdate).toHaveBeenCalledWith('field-1', {
55+
options: [
56+
{ label: 'Second', value: 'second' },
57+
{ label: 'First', value: 'first' },
58+
{ label: 'Third', value: 'third' },
59+
],
60+
});
61+
});
62+
});

frontend/src/components/form-editor/FieldConfigDialog.tsx

Lines changed: 45 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import {
2323
DialogTitle,
2424
DialogFooter,
2525
} from '@/components/ui/dialog';
26-
import { Plus, Trash2, GripVertical, Settings2 } from 'lucide-react';
26+
import { Plus, Trash2, GripVertical, Settings2, ChevronUp, ChevronDown } from 'lucide-react';
2727
import { useTranslation } from 'react-i18next';
2828
import type { AworkCustomFieldDefinition } from '@/lib/api';
2929
import type { AworkIntegrationConfig } from '@/components/form-editor/AworkIntegrationSettings';
@@ -338,6 +338,14 @@ function SelectOptionsEditor({ options, onUpdate }: SelectOptionsEditorProps) {
338338
onUpdate(options.filter((_, i) => i !== index));
339339
};
340340

341+
const moveOption = (fromIndex: number, toIndex: number) => {
342+
if (toIndex < 0 || toIndex >= options.length) return;
343+
const newOptions = [...options];
344+
const [moved] = newOptions.splice(fromIndex, 1);
345+
newOptions.splice(toIndex, 0, moved);
346+
onUpdate(newOptions);
347+
};
348+
341349
return (
342350
<div className="space-y-3">
343351
<Label>{t('fieldConfigDialog.dropdownOptions')}</Label>
@@ -354,15 +362,41 @@ function SelectOptionsEditor({ options, onUpdate }: SelectOptionsEditorProps) {
354362
className="h-8 flex-1"
355363
placeholder={t('fieldConfigDialog.optionLabelPlaceholder')}
356364
/>
357-
<Button
358-
variant="ghost"
359-
size="icon"
360-
className="h-8 w-8 text-muted-foreground hover:text-destructive"
361-
onClick={() => removeOption(index)}
362-
disabled={options.length <= 1}
363-
>
364-
<Trash2 className="w-4 h-4" />
365-
</Button>
365+
<div className="flex items-center gap-1">
366+
<Button
367+
type="button"
368+
variant="ghost"
369+
size="icon"
370+
className="h-8 w-8 text-muted-foreground"
371+
onClick={() => moveOption(index, index - 1)}
372+
disabled={index === 0}
373+
aria-label="Move option up"
374+
>
375+
<ChevronUp className="w-4 h-4" />
376+
</Button>
377+
<Button
378+
type="button"
379+
variant="ghost"
380+
size="icon"
381+
className="h-8 w-8 text-muted-foreground"
382+
onClick={() => moveOption(index, index + 1)}
383+
disabled={index === options.length - 1}
384+
aria-label="Move option down"
385+
>
386+
<ChevronDown className="w-4 h-4" />
387+
</Button>
388+
<Button
389+
type="button"
390+
variant="ghost"
391+
size="icon"
392+
className="h-8 w-8 text-muted-foreground hover:text-destructive"
393+
onClick={() => removeOption(index)}
394+
disabled={options.length <= 1}
395+
aria-label="Remove option"
396+
>
397+
<Trash2 className="w-4 h-4" />
398+
</Button>
399+
</div>
366400
</div>
367401
))}
368402
</div>
@@ -380,6 +414,7 @@ function SelectOptionsEditor({ options, onUpdate }: SelectOptionsEditorProps) {
380414
}}
381415
/>
382416
<Button
417+
type="button"
383418
size="sm"
384419
onClick={addOption}
385420
disabled={!newOptionLabel.trim()}

0 commit comments

Comments
 (0)