|
7 | 7 | */ |
8 | 8 |
|
9 | 9 | import * as React from 'react'; |
10 | | -import { cn, Button, Input, Popover, PopoverContent, PopoverTrigger, FilterBuilder } from '@object-ui/components'; |
| 10 | +import { cn, Button, Input, Popover, PopoverContent, PopoverTrigger, FilterBuilder, SortBuilder } from '@object-ui/components'; |
| 11 | +import type { SortItem } from '@object-ui/components'; |
11 | 12 | import { Search, SlidersHorizontal, ArrowUpDown, X } from 'lucide-react'; |
12 | 13 | import type { FilterGroup } from '@object-ui/components'; |
13 | 14 | import { ViewSwitcher, ViewType } from './ViewSwitcher'; |
@@ -70,8 +71,20 @@ export const ListView: React.FC<ListViewProps> = ({ |
70 | 71 | (schema.viewType as ViewType) || 'grid' |
71 | 72 | ); |
72 | 73 | const [searchTerm, setSearchTerm] = React.useState(''); |
73 | | - const [sortField] = React.useState(schema.sort?.[0]?.field || ''); |
74 | | - const [sortOrder, setSortOrder] = React.useState<'asc' | 'desc'>(schema.sort?.[0]?.order || 'asc'); |
| 74 | + |
| 75 | + // Sort State |
| 76 | + const [showSort, setShowSort] = React.useState(false); |
| 77 | + const [currentSort, setCurrentSort] = React.useState<SortItem[]>(() => { |
| 78 | + if (schema.sort && schema.sort.length > 0) { |
| 79 | + return schema.sort.map(s => ({ |
| 80 | + id: crypto.randomUUID(), |
| 81 | + field: s.field, |
| 82 | + order: (s.order as 'asc' | 'desc') || 'asc' |
| 83 | + })); |
| 84 | + } |
| 85 | + return []; |
| 86 | + }); |
| 87 | + |
75 | 88 | const [showFilters, setShowFilters] = React.useState(false); |
76 | 89 |
|
77 | 90 | const [currentFilters, setCurrentFilters] = React.useState<FilterGroup>({ |
@@ -135,7 +148,9 @@ export const ListView: React.FC<ListViewProps> = ({ |
135 | 148 |
|
136 | 149 | // Convert sort to query format |
137 | 150 | // ObjectQL uses simple object: { field: 'asc' } |
138 | | - const sort: any = sortField ? { [sortField]: sortOrder } : undefined; |
| 151 | + const sort: any = currentSort.length > 0 |
| 152 | + ? currentSort.reduce((acc, item) => ({ ...acc, [item.field]: item.order }), {}) |
| 153 | + : undefined; |
139 | 154 |
|
140 | 155 | const results = await dataSource.find(schema.objectName, { |
141 | 156 | $filter: finalFilter, |
@@ -167,7 +182,7 @@ export const ListView: React.FC<ListViewProps> = ({ |
167 | 182 | fetchData(); |
168 | 183 |
|
169 | 184 | return () => { isMounted = false; }; |
170 | | - }, [schema.objectName, dataSource, schema.filters, sortField, sortOrder, currentFilters]); // Re-fetch on filter/sort change |
| 185 | + }, [schema.objectName, dataSource, schema.filters, currentSort, currentFilters]); // Re-fetch on filter/sort change |
171 | 186 |
|
172 | 187 | // Load saved view preference |
173 | 188 | React.useEffect(() => { |
@@ -196,19 +211,13 @@ export const ListView: React.FC<ListViewProps> = ({ |
196 | 211 | onSearchChange?.(value); |
197 | 212 | }, [onSearchChange]); |
198 | 213 |
|
199 | | - const handleSortChange = React.useCallback(() => { |
200 | | - const newOrder = sortOrder === 'asc' ? 'desc' : 'asc'; |
201 | | - setSortOrder(newOrder); |
202 | | - onSortChange?.({ field: sortField, order: newOrder }); |
203 | | - }, [sortField, sortOrder, onSortChange]); |
204 | | - |
205 | 214 | // Generate the appropriate view component schema |
206 | 215 | const viewComponentSchema = React.useMemo(() => { |
207 | 216 | const baseProps = { |
208 | 217 | objectName: schema.objectName, |
209 | 218 | fields: schema.fields, |
210 | 219 | filters: schema.filters, |
211 | | - sort: [{ field: sortField, order: sortOrder }], |
| 220 | + sort: currentSort, |
212 | 221 | className: "h-full w-full", |
213 | 222 | // Disable internal controls that clash with ListView toolbar |
214 | 223 | showSearch: false, |
@@ -278,7 +287,7 @@ export const ListView: React.FC<ListViewProps> = ({ |
278 | 287 | default: |
279 | 288 | return baseProps; |
280 | 289 | } |
281 | | - }, [currentView, schema, sortField, sortOrder]); |
| 290 | + }, [currentView, schema, currentSort]); |
282 | 291 |
|
283 | 292 | // Available view types based on schema configuration |
284 | 293 | const availableViews = React.useMemo(() => { |
@@ -399,17 +408,42 @@ export const ListView: React.FC<ListViewProps> = ({ |
399 | 408 | </PopoverContent> |
400 | 409 | </Popover> |
401 | 410 |
|
402 | | - {sortField && ( |
403 | | - <Button |
404 | | - variant="ghost" |
405 | | - size="sm" |
406 | | - onClick={handleSortChange} |
407 | | - className="h-8 px-2 lg:px-3 text-muted-foreground hover:text-primary" |
408 | | - > |
409 | | - <ArrowUpDown className="h-4 w-4 mr-2" /> |
410 | | - <span className="hidden lg:inline">Sort</span> |
411 | | - </Button> |
412 | | - )} |
| 411 | + <Popover open={showSort} onOpenChange={setShowSort}> |
| 412 | + <PopoverTrigger asChild> |
| 413 | + <Button |
| 414 | + variant={currentSort.length > 0 ? "secondary" : "ghost"} |
| 415 | + size="sm" |
| 416 | + className={cn( |
| 417 | + "h-8 px-2 lg:px-3 text-muted-foreground hover:text-primary", |
| 418 | + currentSort.length > 0 && "text-primary bg-secondary/50" |
| 419 | + )} |
| 420 | + > |
| 421 | + <ArrowUpDown className="h-4 w-4 mr-2" /> |
| 422 | + <span className="hidden lg:inline">Sort</span> |
| 423 | + {currentSort.length > 0 && ( |
| 424 | + <span className="ml-1.5 flex h-4 w-4 items-center justify-center rounded-full bg-primary/10 text-[10px] font-medium text-primary"> |
| 425 | + {currentSort.length} |
| 426 | + </span> |
| 427 | + )} |
| 428 | + </Button> |
| 429 | + </PopoverTrigger> |
| 430 | + <PopoverContent align="start" className="w-[600px] p-4"> |
| 431 | + <div className="space-y-4"> |
| 432 | + <div className="flex items-center justify-between border-b pb-2"> |
| 433 | + <h4 className="font-medium text-sm">Sort Records</h4> |
| 434 | + </div> |
| 435 | + <SortBuilder |
| 436 | + fields={filterFields} |
| 437 | + value={currentSort} |
| 438 | + onChange={(newSort) => { |
| 439 | + console.log('Sort Changed:', newSort); |
| 440 | + setCurrentSort(newSort); |
| 441 | + if (onSortChange) onSortChange(newSort); |
| 442 | + }} |
| 443 | + /> |
| 444 | + </div> |
| 445 | + </PopoverContent> |
| 446 | + </Popover> |
413 | 447 |
|
414 | 448 | {/* Future: Group, Color, Height */} |
415 | 449 | </div> |
|
0 commit comments