diff --git a/ami/main/api/views.py b/ami/main/api/views.py index 5a6d5aece..57664217c 100644 --- a/ami/main/api/views.py +++ b/ami/main/api/views.py @@ -155,6 +155,7 @@ class ProjectViewSet(DefaultViewSet, ProjectMixin): serializer_class = ProjectSerializer pagination_class = ProjectPagination permission_classes = [ObjectPermission] + ordering_fields = ["name", "created_at", "updated_at"] def get_queryset(self): qs: ProjectQuerySet = super().get_queryset() # type: ignore diff --git a/ui/package.json b/ui/package.json index 7c32af6e1..bd8f41ac5 100644 --- a/ui/package.json +++ b/ui/package.json @@ -29,7 +29,7 @@ "leaflet": "^1.9.3", "lodash": "^4.17.21", "lucide-react": "^1.0.1", - "nova-ui-kit": "^1.1.34", + "nova-ui-kit": "^1.1.36", "plotly.js": "^2.25.2", "react": "^18.2.0", "react-dom": "^18.2.0", diff --git a/ui/src/design-system/components/sort-control.tsx b/ui/src/design-system/components/sort-control.tsx index 87a18e2e0..b9887130f 100644 --- a/ui/src/design-system/components/sort-control.tsx +++ b/ui/src/design-system/components/sort-control.tsx @@ -1,6 +1,6 @@ import classNames from 'classnames' -import { ArrowUpDownIcon } from 'lucide-react' -import { buttonVariants, Select } from 'nova-ui-kit' +import { ArrowDownIcon, ArrowUpDownIcon } from 'lucide-react' +import { Button, buttonVariants, Select } from 'nova-ui-kit' import { STRING, translate } from 'utils/language' import { TableSortSettings } from './table/types' import { BasicTooltip } from './tooltip/basic-tooltip' @@ -16,33 +16,73 @@ export const SortControl = ({ columns, setSort, sort }: SortControlProps) => { ? columns.find((column) => column.sortField === sort.field) : undefined + const changeSortField = (field: string) => { + if (sort) { + setSort({ field, order: sort.order }) + } else { + setSort({ field, order: 'asc' }) + } + } + + const changeSortOrder = () => { + if (sort) { + setSort({ + field: sort.field, + order: sort.order === 'asc' ? 'desc' : 'asc', + }) + } + } + return ( - { - setSort({ field: value, order: 'asc' }) - }} - > - - - - {column ? column.name : translate(STRING.SORT_BY)} - - - - {columns - .filter((column) => column.sortField) - .map((column) => ( - - {column.name} - - ))} - - +
+ + + + {column ? column.name : translate(STRING.SORT_BY)} + {sort ? ( + + ) : null} + + + + {columns + .filter((column) => column.sortField) + .map((column) => ( + + {column.name} + + ))} + + + {sort ? ( + + + + ) : null} +
) } diff --git a/ui/src/pages/jobs/jobs.tsx b/ui/src/pages/jobs/jobs.tsx index c001cb859..e9696f18c 100644 --- a/ui/src/pages/jobs/jobs.tsx +++ b/ui/src/pages/jobs/jobs.tsx @@ -68,8 +68,8 @@ export const Jobs = () => { title={translate(STRING.NAV_ITEM_JOBS)} tooltip={translate(STRING.TOOLTIP_JOB)} > - {canCreate ? : null} + { to={APP_ROUTES.EXPORTS({ projectId: projectId as string })} > - Export + Export - + [] => [ { id: 'user', - sortField: 'name', name: translate(STRING.FIELD_LABEL_USER), renderCell: (item: Member) => ( diff --git a/ui/src/pages/project/team/team.tsx b/ui/src/pages/project/team/team.tsx index 6885a6065..1b04d61b6 100644 --- a/ui/src/pages/project/team/team.tsx +++ b/ui/src/pages/project/team/team.tsx @@ -24,7 +24,7 @@ export const Team = () => { }>() const { pagination, setPage } = usePagination() const { sort, setSort } = useSort({ - field: 'name', + field: 'created_at', order: 'asc', }) const { members, userPermissions, total, isLoading, isFetching, error } = diff --git a/ui/src/pages/projects/project-gallery.tsx b/ui/src/pages/projects/project-gallery.tsx index 80d76d139..d15b91f5a 100644 --- a/ui/src/pages/projects/project-gallery.tsx +++ b/ui/src/pages/projects/project-gallery.tsx @@ -34,7 +34,6 @@ export const ProjectGallery = ({ return ( ( @@ -49,7 +48,6 @@ export const ProjectGallery = ({ /> )} - style={{ gridTemplateColumns: '1fr 1fr 1fr' }} /> ) } diff --git a/ui/src/pages/projects/projects.tsx b/ui/src/pages/projects/projects.tsx index 267ec93f0..9c1de4033 100644 --- a/ui/src/pages/projects/projects.tsx +++ b/ui/src/pages/projects/projects.tsx @@ -2,6 +2,7 @@ import { useProjects } from 'data-services/hooks/projects/useProjects' import { PageFooter } from 'design-system/components/page-footer/page-footer' import { PageHeader } from 'design-system/components/page-header/page-header' import { PaginationBar } from 'design-system/components/pagination-bar/pagination-bar' +import { SortControl } from 'design-system/components/sort-control' import * as Tabs from 'design-system/components/tabs/tabs' import { Button } from 'nova-ui-kit' import { NewProjectDialog } from 'pages/project-details/new-project-dialog' @@ -12,7 +13,7 @@ import { UserPermission } from 'utils/user/types' import { useUser } from 'utils/user/userContext' import { useUserInfo } from 'utils/user/userInfoContext' import { useSelectedView } from 'utils/useSelectedView' -import { useWindowSize } from 'utils/useWindowSize' +import { useSort } from 'utils/useSort' import { ProjectGallery } from './project-gallery' export const TABS = { @@ -20,21 +21,25 @@ export const TABS = { ALL_PROJECTS: 'all-projects', } +const SORT_FIELDS = [ + { id: 'name', name: translate(STRING.FIELD_LABEL_NAME) }, + { id: 'created_at', name: translate(STRING.FIELD_LABEL_CREATED_AT) }, + { id: 'updated_at', name: translate(STRING.FIELD_LABEL_UPDATED_AT) }, +] + export const Projects = () => { const { user } = useUser() const { userInfo } = useUserInfo() const { selectedView: selectedTab, setSelectedView: setSelectedTab } = useSelectedView(user.loggedIn ? TABS.MY_PROJECTS : TABS.ALL_PROJECTS) - const [windowWidth] = useWindowSize() - const { pagination, setPage } = usePagination({ - perPage: windowWidth > 1024 ? 21 : 20, // Adjust page size based on page width to avoid gallery gaps - }) + const { sort, setSort } = useSort() + const { pagination, setPage } = usePagination({ perPage: 40 }) const filters = user.loggedIn && selectedTab === TABS.MY_PROJECTS ? [{ field: 'user_id', value: userInfo?.id }] : [] const { projects, total, userPermissions, isLoading, isFetching, error } = - useProjects({ pagination, filters }) + useProjects({ pagination, filters, sort }) const canCreate = userPermissions?.includes(UserPermission.Create) return ( @@ -67,6 +72,14 @@ export const Projects = () => { ) : null} {canCreate ? : null} + ({ + ...field, + sortField: field.id, + }))} + setSort={setSort} + sort={sort} + /> {projects && projects.length === 0 && canCreate ? (
diff --git a/ui/src/utils/language.ts b/ui/src/utils/language.ts index cb54f81c7..5e240cbd6 100644 --- a/ui/src/utils/language.ts +++ b/ui/src/utils/language.ts @@ -5,12 +5,14 @@ export enum STRING { BACK, CANCEL, CHANGE_IMAGE, + CHANGE_SORT_ORDER, CHOOSE_IMAGE, CLEAR_FILTERS, CLEAR, COLLAPSE, CONFIRM, CONFIRMED, + CREATE_NEW, CURRENT_LOCATION, DELETE, DELETED, @@ -345,12 +347,14 @@ const ENGLISH_STRINGS: { [key in STRING]: string } = { [STRING.BACK]: 'Back', [STRING.CANCEL]: 'Cancel', [STRING.CHANGE_IMAGE]: 'Change image', + [STRING.CHANGE_SORT_ORDER]: 'Change sort order', [STRING.CHOOSE_IMAGE]: 'Choose image', [STRING.CLEAR_FILTERS]: 'Clear filters', [STRING.CLEAR]: 'Clear', [STRING.COLLAPSE]: 'Collapse', [STRING.CONFIRM]: 'Confirm', [STRING.CONFIRMED]: 'Confirmed', + [STRING.CREATE_NEW]: 'Create new', [STRING.CURRENT_LOCATION]: 'Use current location', [STRING.DELETE]: 'Delete', [STRING.DELETED]: 'Deleted', diff --git a/ui/yarn.lock b/ui/yarn.lock index 2492bfe7e..92b044a26 100644 --- a/ui/yarn.lock +++ b/ui/yarn.lock @@ -5002,7 +5002,7 @@ __metadata: leaflet: "npm:^1.9.3" lodash: "npm:^4.17.21" lucide-react: "npm:^1.0.1" - nova-ui-kit: "npm:^1.1.34" + nova-ui-kit: "npm:^1.1.36" plotly.js: "npm:^2.25.2" postcss: "npm:^8.4.47" prettier: "npm:2.8.4" @@ -10562,9 +10562,9 @@ __metadata: languageName: node linkType: hard -"nova-ui-kit@npm:^1.1.34": - version: 1.1.34 - resolution: "nova-ui-kit@npm:1.1.34" +"nova-ui-kit@npm:^1.1.36": + version: 1.1.36 + resolution: "nova-ui-kit@npm:1.1.36" dependencies: "@radix-ui/react-checkbox": "npm:^1.1.4" "@radix-ui/react-collapsible": "npm:^1.1.1" @@ -10584,7 +10584,7 @@ __metadata: react-dom: "npm:^18.3.1" tailwind-merge: "npm:^2.5.4" tailwindcss-animate: "npm:^1.0.7" - checksum: 4b3341ebebc715c79b5172870b30b33f65381bbf8d2acdcdda292e7be662592e32d188e9e33efd650c548211e45bef62efbb6af2ba464fb1eae37a31a02512db + checksum: c066ce1bc6d29339ee0596655135a9450bc55181eeb811970c93216889ef0361604897f3263905d2d0f01507f4296896111dff29d02770cd4da5f423aa46fef9 languageName: node linkType: hard