diff --git a/package.json b/package.json index bf9628928..588b91442 100644 --- a/package.json +++ b/package.json @@ -63,6 +63,7 @@ "redux-thunk": "^2.4.1", "sass": "^1.49.8", "styled-components": "^5.3.5", + "swr": "^1.3.0", "tc-auth-lib": "topcoder-platform/tc-auth-lib#1.0.4", "typescript": "^4.6.3", "uuid": "^8.3.2" diff --git a/src-ts/lib/global-config.model.ts b/src-ts/lib/global-config.model.ts index ddf9b8ce4..10fde8e9c 100644 --- a/src-ts/lib/global-config.model.ts +++ b/src-ts/lib/global-config.model.ts @@ -11,6 +11,9 @@ export interface GlobalConfig { } DISABLED_TOOLS?: Array ENV: string + GAMIFICATION: { + ORG_ID: string + }, LOGGING: { PUBLIC_TOKEN: string SERVICE: string diff --git a/src-ts/lib/pagination/index.ts b/src-ts/lib/pagination/index.ts index 3b90b0623..b8e69f71a 100644 --- a/src-ts/lib/pagination/index.ts +++ b/src-ts/lib/pagination/index.ts @@ -1,2 +1,5 @@ +export * from './infinite-page-dao.model' +export * from './infinite-page-handler.model' export * from './page.model' export * from './sort.model' +export * from './use-infinite-page.hook' diff --git a/src-ts/lib/pagination/infinite-page-dao.model.ts b/src-ts/lib/pagination/infinite-page-dao.model.ts new file mode 100644 index 000000000..ae1ca01da --- /dev/null +++ b/src-ts/lib/pagination/infinite-page-dao.model.ts @@ -0,0 +1,5 @@ +export interface InfinitePageDao { + count: number + // TODO: rename this 'items' so it can be used in a grid/card view + rows: ReadonlyArray +} diff --git a/src-ts/lib/pagination/infinite-page-handler.model.ts b/src-ts/lib/pagination/infinite-page-handler.model.ts new file mode 100644 index 000000000..cab09cc1a --- /dev/null +++ b/src-ts/lib/pagination/infinite-page-handler.model.ts @@ -0,0 +1,5 @@ +export interface InfinitePageHandler { + data?: ReadonlyArray + getAndSetNext: () => void + hasMore: boolean +} diff --git a/src-ts/lib/pagination/use-infinite-page.hook.ts b/src-ts/lib/pagination/use-infinite-page.hook.ts new file mode 100644 index 000000000..6f1367ea2 --- /dev/null +++ b/src-ts/lib/pagination/use-infinite-page.hook.ts @@ -0,0 +1,25 @@ +import { flatten, map } from 'lodash' +// tslint:disable-next-line: no-submodule-imports +import useSWRInfinite, { SWRInfiniteResponse } from 'swr/infinite' + +import { InfinitePageDao } from './infinite-page-dao.model' +import { InfinitePageHandler } from './infinite-page-handler.model' + +export function useGetInfinitePage(getKey: (index: number, previousPageData: InfinitePageDao) => string | undefined): + InfinitePageHandler { + + const { data, setSize, size }: SWRInfiniteResponse> = useSWRInfinite(getKey, { revalidateFirstPage: false }) + + // flatten version of badges paginated data + const outputData: ReadonlyArray = flatten(map(data, dao => dao.rows)) + + function getAndSetNext(): void { + setSize(size + 1) + } + + return { + data: outputData, + getAndSetNext, + hasMore: outputData.length < (data?.[0]?.count || 0), + } +} diff --git a/src-ts/lib/table/Table.module.scss b/src-ts/lib/table/Table.module.scss index eeb951071..5582e4c7c 100644 --- a/src-ts/lib/table/Table.module.scss +++ b/src-ts/lib/table/Table.module.scss @@ -61,6 +61,10 @@ margin-right: -29px; } } + + &.centerHeader { + justify-content: center; + } } .tooltip { @@ -90,3 +94,8 @@ .tootlipBody { min-width: 200px; } + +.loadBtnWrap { + display: flex; + justify-content: center; +} \ No newline at end of file diff --git a/src-ts/lib/table/Table.tsx b/src-ts/lib/table/Table.tsx index 25c439700..ad3443d25 100644 --- a/src-ts/lib/table/Table.tsx +++ b/src-ts/lib/table/Table.tsx @@ -1,6 +1,7 @@ import classNames from 'classnames' import { Dispatch, MouseEvent, SetStateAction, useEffect, useState } from 'react' +import { Button } from '../button' import { Sort } from '../pagination' import '../styles/_includes.scss' import { IconOutline } from '../svgs' @@ -15,7 +16,10 @@ import styles from './Table.module.scss' interface TableProps { readonly columns: ReadonlyArray> readonly data: ReadonlyArray + readonly moreToLoad?: boolean + readonly onLoadMoreClick?: () => void readonly onRowClick?: (data: T) => void + readonly onToggleSort?: (sort: Sort) => void } interface DefaultSortDirectionMap { @@ -34,7 +38,7 @@ const Table: (props: TableProps) = Dispatch> ] = useState() - const [sortedData, setSortData]: [ReadonlyArray, Dispatch>>] + const [sortedData, setSortedData]: [ReadonlyArray, Dispatch>>] = useState>(props.data) useEffect(() => { @@ -47,7 +51,11 @@ const Table: (props: TableProps) = setDefaultSortDirectionMap(map) } - setSortData(tableGetSorted(data, columns, sort)) + // if we have a sort handler, don't worry about getting the sorted data; + // otherwise, get the sorted data for the table + const sorted: ReadonlyArray = !!props.onToggleSort ? data : tableGetSorted(data, columns, sort) + + setSortedData(sorted) }, [ columns, @@ -75,6 +83,9 @@ const Table: (props: TableProps) = fieldName, } setSort(newSort) + + // call the callback to notify parent for sort update + props.onToggleSort?.(newSort) } const headerRow: Array = props.columns @@ -136,7 +147,7 @@ const Table: (props: TableProps) = // return the entire row return ( @@ -158,6 +169,18 @@ const Table: (props: TableProps) = {rowCells} + { + !!props.moreToLoad && !!props.onLoadMoreClick && ( +
+
+ ) + } ) } diff --git a/src-ts/lib/table/index.ts b/src-ts/lib/table/index.ts index d7b124c44..396d852ea 100644 --- a/src-ts/lib/table/index.ts +++ b/src-ts/lib/table/index.ts @@ -1,2 +1,3 @@ export * from './table-column.model' +export { tableGetDefaultSort } from './table-functions' export { default as Table } from './Table' diff --git a/src-ts/lib/table/table-functions/table.functions.ts b/src-ts/lib/table/table-functions/table.functions.ts index cfd4d25af..72e050f05 100644 --- a/src-ts/lib/table/table-functions/table.functions.ts +++ b/src-ts/lib/table/table-functions/table.functions.ts @@ -1,17 +1,21 @@ import { Sort } from '../../pagination' import { TableColumn } from '../table-column.model' -export function getDefaultSort(columns: ReadonlyArray>): Sort | undefined { +export function getDefaultSort(columns: ReadonlyArray>): Sort { const defaultSortColumn: TableColumn | undefined = columns.find(col => col.isDefaultSort) || columns.find(col => !!col.propertyName) + || columns?.[0] - const defaultSort: Sort | undefined = !defaultSortColumn?.propertyName - ? undefined - : { - direction: defaultSortColumn.defaultSortDirection || 'asc', - fieldName: defaultSortColumn.propertyName, - } + // if we didn't find a default sort, we have a problem + if (!defaultSortColumn) { + throw new Error('A table must have at least one column.') + } + + const defaultSort: Sort = { + direction: defaultSortColumn.defaultSortDirection || 'asc', + fieldName: defaultSortColumn.propertyName || '', + } return defaultSort } diff --git a/src-ts/tools/gamification-admin/GamificationAdmin.tsx b/src-ts/tools/gamification-admin/GamificationAdmin.tsx index 860b2c3ae..3d85c3598 100644 --- a/src-ts/tools/gamification-admin/GamificationAdmin.tsx +++ b/src-ts/tools/gamification-admin/GamificationAdmin.tsx @@ -1,9 +1,11 @@ import { FC, useContext } from 'react' import { Outlet, Routes } from 'react-router-dom' +import { SWRConfig } from 'swr' import { routeContext, RouteContextData, + xhrGetAsync, } from '../../lib' export const toolTitle: string = 'Gamification Admin' @@ -13,12 +15,17 @@ const GamificationAdmin: FC<{}> = () => { const { getChildRoutes }: RouteContextData = useContext(routeContext) return ( - <> + xhrGetAsync(resource), + refreshInterval: 60000, // 1 min + }} + > {getChildRoutes(toolTitle)} - + ) } diff --git a/src-ts/tools/gamification-admin/game-config/gamification-config.model.ts b/src-ts/tools/gamification-admin/game-config/gamification-config.model.ts new file mode 100644 index 000000000..c1b0124fe --- /dev/null +++ b/src-ts/tools/gamification-admin/game-config/gamification-config.model.ts @@ -0,0 +1,4 @@ +export interface GamificationConfigModel { + ORG_ID: string + PAGE_SIZE: number +} diff --git a/src-ts/tools/gamification-admin/game-config/gamification.config.ts b/src-ts/tools/gamification-admin/game-config/gamification.config.ts new file mode 100644 index 000000000..e204a4040 --- /dev/null +++ b/src-ts/tools/gamification-admin/game-config/gamification.config.ts @@ -0,0 +1,27 @@ +import { EnvironmentConfig } from '../../../config' + +import { GamificationConfigModel } from './gamification-config.model' +import { GamificationConfigDefault } from './gamification.default.config' +import { GamificationConfigDev } from './gamification.dev.config' +import { GamificationConfigProd } from './gamification.prod.config' + +function getConfig(): GamificationConfigModel { + + switch (EnvironmentConfig.ENV) { + + case 'dev': + return GamificationConfigDev + + case 'prod': + return GamificationConfigProd + + default: + return GamificationConfigDefault + } +} + +const GamificationConfig: GamificationConfigModel = { + ...getConfig(), +} + +export default GamificationConfig diff --git a/src-ts/tools/gamification-admin/game-config/gamification.default.config.ts b/src-ts/tools/gamification-admin/game-config/gamification.default.config.ts new file mode 100644 index 000000000..b2c40e4d8 --- /dev/null +++ b/src-ts/tools/gamification-admin/game-config/gamification.default.config.ts @@ -0,0 +1,6 @@ +import { GamificationConfigModel } from './gamification-config.model' + +export const GamificationConfigDefault: GamificationConfigModel = { + ORG_ID: '6052dd9b-ea80-494b-b258-edd1331e27a3', + PAGE_SIZE: 12, +} diff --git a/src-ts/tools/gamification-admin/game-config/gamification.dev.config.ts b/src-ts/tools/gamification-admin/game-config/gamification.dev.config.ts new file mode 100644 index 000000000..02fec4630 --- /dev/null +++ b/src-ts/tools/gamification-admin/game-config/gamification.dev.config.ts @@ -0,0 +1,6 @@ +import { GamificationConfigModel } from './gamification-config.model' +import { GamificationConfigDefault } from './gamification.default.config' + +export const GamificationConfigDev: GamificationConfigModel = { + ...GamificationConfigDefault, +} diff --git a/src-ts/tools/gamification-admin/game-config/gamification.prod.config.ts b/src-ts/tools/gamification-admin/game-config/gamification.prod.config.ts new file mode 100644 index 000000000..e49185be0 --- /dev/null +++ b/src-ts/tools/gamification-admin/game-config/gamification.prod.config.ts @@ -0,0 +1,7 @@ +import { GamificationConfigModel } from './gamification-config.model' +import { GamificationConfigDefault } from './gamification.default.config' + +export const GamificationConfigProd: GamificationConfigModel = { + ...GamificationConfigDefault, + ORG_ID: 'e111f8df-6ac8-44d1-b4da-bb916f5e3425', +} diff --git a/src-ts/tools/gamification-admin/game-config/index.ts b/src-ts/tools/gamification-admin/game-config/index.ts new file mode 100644 index 000000000..60ed70fc0 --- /dev/null +++ b/src-ts/tools/gamification-admin/game-config/index.ts @@ -0,0 +1 @@ +export { default as GamificationConfig } from './gamification.config' diff --git a/src-ts/tools/gamification-admin/game-lib/game-badge.model.ts b/src-ts/tools/gamification-admin/game-lib/game-badge.model.ts new file mode 100644 index 000000000..7c4c719b2 --- /dev/null +++ b/src-ts/tools/gamification-admin/game-lib/game-badge.model.ts @@ -0,0 +1,10 @@ +// TODO: add factory to convert snake case property names to camel case +export interface GameBadge { + active: boolean + badge_description: string + badge_image_url: string + badge_name: string + badge_status: string + id: string + organization_id: string +} diff --git a/src-ts/tools/gamification-admin/game-lib/index.ts b/src-ts/tools/gamification-admin/game-lib/index.ts new file mode 100644 index 000000000..6cb57fe53 --- /dev/null +++ b/src-ts/tools/gamification-admin/game-lib/index.ts @@ -0,0 +1,3 @@ +export * from './game-badge.model' +export * from './use-get-game-badges-page.hook' +export * from './use-gamification-breadcrumb.hook' diff --git a/src-ts/tools/gamification-admin/game-lib/use-gamification-breadcrumb.hook.tsx b/src-ts/tools/gamification-admin/game-lib/use-gamification-breadcrumb.hook.tsx new file mode 100644 index 000000000..274022b16 --- /dev/null +++ b/src-ts/tools/gamification-admin/game-lib/use-gamification-breadcrumb.hook.tsx @@ -0,0 +1,16 @@ +import { BreadcrumbItemModel } from '../../../lib' +import { basePath } from '../gamification-admin.routes' +import { toolTitle } from '../GamificationAdmin' + +export function useGamificationBreadcrumb(items: Array): Array { + + const breadcrumb: Array = [ + { + name: toolTitle, + url: basePath, + }, + ...items, + ] + + return breadcrumb +} diff --git a/src-ts/tools/gamification-admin/game-lib/use-get-game-badges-page.hook.ts b/src-ts/tools/gamification-admin/game-lib/use-get-game-badges-page.hook.ts new file mode 100644 index 000000000..885d1af51 --- /dev/null +++ b/src-ts/tools/gamification-admin/game-lib/use-get-game-badges-page.hook.ts @@ -0,0 +1,30 @@ +import { EnvironmentConfig } from '../../../config' +import { InfinitePageDao, InfinitePageHandler, Sort, useGetInfinitePage } from '../../../lib' +import { GamificationConfig } from '../game-config' + +import { GameBadge } from './game-badge.model' + +export function useGetGameBadgesPage(sort: Sort): InfinitePageHandler { + + function getKey(index: number, previousPageData: InfinitePageDao): string | undefined { + + // reached the end + if (!!previousPageData && !previousPageData.rows.length) { + return undefined + } + + const params: Record = { + limit: `${GamificationConfig.PAGE_SIZE}`, + offset: `${index * GamificationConfig.PAGE_SIZE}`, + order_by: sort.fieldName, + order_type: sort.direction, + organization_id: GamificationConfig.ORG_ID, + } + + const badgeEndpointUrl: URL = new URL(`${EnvironmentConfig.API.V5}/gamification/badges?${new URLSearchParams(params)}`) + + return badgeEndpointUrl.toString() + } + + return useGetInfinitePage(getKey) +} diff --git a/src-ts/tools/gamification-admin/gamification-admin.routes.tsx b/src-ts/tools/gamification-admin/gamification-admin.routes.tsx index 5b8a60acd..108d2cf53 100644 --- a/src-ts/tools/gamification-admin/gamification-admin.routes.tsx +++ b/src-ts/tools/gamification-admin/gamification-admin.routes.tsx @@ -5,8 +5,16 @@ import BadgeDetailPage from './pages/badge-detail/BadgeDetailPage' import BadgeListingPage from './pages/badge-listing/BadgeListingPage' import CreateBadgePage from './pages/create-badge/CreateBadgePage' -export const baseUrl: string = '/gamification-admin' -export const rolesRequired: Array = [UserRole.gamificationAdmin] +const baseDetailPath: string = '/badge-detail' +const createBadgePath: string = '/create-badge' + +export const basePath: string = '/gamification-admin' + +export function badgeDetailPath(badgeId: string, view?: 'edit' | 'award'): string { + return `${basePath}${baseDetailPath}/${badgeId}${!!view ? `#${view}` : ''}` +} + +export const createBadgeRoute: string = `${basePath}${createBadgePath}` export const gamificationAdminRoutes: Array = [ { @@ -18,17 +26,19 @@ export const gamificationAdminRoutes: Array = [ }, { element: , - route: '/create-badge', + route: createBadgePath, }, { element: , - route: '/badge-detail', + route: `${baseDetailPath}/:id`, }, ], element: , hidden: true, - rolesRequired, - route: baseUrl, + rolesRequired: [ + UserRole.gamificationAdmin, + ], + route: basePath, title: toolTitle, }, ] diff --git a/src-ts/tools/gamification-admin/pages/badge-detail/BadgeDetailPage.tsx b/src-ts/tools/gamification-admin/pages/badge-detail/BadgeDetailPage.tsx index 4923e6c95..5283989c4 100644 --- a/src-ts/tools/gamification-admin/pages/badge-detail/BadgeDetailPage.tsx +++ b/src-ts/tools/gamification-admin/pages/badge-detail/BadgeDetailPage.tsx @@ -1,30 +1,34 @@ -import { FC, useMemo } from 'react' +import { FC } from 'react' import { Breadcrumb, BreadcrumbItemModel, ContentLayout } from '../../../../lib' -import { baseUrl } from '../../gamification-admin.routes' -import { toolTitle } from '../../GamificationAdmin' +import { useGamificationBreadcrumb } from '../../game-lib' import styles from './BadgeDetailPage.module.scss' const BadgeDetailPage: FC = () => { - const breadcrumb: Array = useMemo(() => [ - { name: toolTitle, url: baseUrl }, - { name: 'badge detail', url: '#' }, - ], []) + // TDOD: use whit GAME-78 + // const { id: badgeID } : { badgeID: string } = useParams() - return ( - - -
+ const breadcrumb: Array = useGamificationBreadcrumb([ + { + name: 'badge detail', + url: '#', + }, + ]) -
-
- ) + return ( + + +
+ +
+
+ ) } export default BadgeDetailPage diff --git a/src-ts/tools/gamification-admin/pages/badge-listing/BadgeListingPage.module.scss b/src-ts/tools/gamification-admin/pages/badge-listing/BadgeListingPage.module.scss index 7da13b030..36db3452d 100644 --- a/src-ts/tools/gamification-admin/pages/badge-listing/BadgeListingPage.module.scss +++ b/src-ts/tools/gamification-admin/pages/badge-listing/BadgeListingPage.module.scss @@ -1,17 +1,106 @@ -.contentLayout { - width: 100%; - padding-bottom: 0; +@import "../../../../lib/styles/includes"; +@import "../../../../lib/styles/variables"; - .contentLayout-outer { - width: 100%; +.container { + display: flex; + flex-direction: column; + padding-top: $space-xxxxl; + + .badges-table-header { + display: flex; + justify-content: space-between; + padding-bottom: $space-lg; + color: $black-60; + @include font-barlow; + @include font-weight-semibold; + font-size: 11px; + line-height: 11px; + + .col-sort { + display: flex; + margin-left: 94px; + align-items: center; + + svg { + color: $black-100; + } + } + + div:last-child { + margin-right: 100px; - .contentLayout-inner { - width: 100%; - overflow: visible; + @include ltemd { + display: none; + } } } -} -.container { - display: flex; + .badges-table { + display: flex; + flex-direction: column; + + .badge-row { + display: flex; + justify-content: space-between; + padding: $space-lg $space-xxl; + + @include ltemd { + flex-direction: column; + } + + &:nth-child(odd) { + background-color: $black-5; + border-radius: 8px; + } + + .badge { + display: flex; + align-items: center; + + .badge-image { + width: 48px; + height: 48px; + margin-right: $space-xl; + } + + .badge-image-disabled { + width: 48px; + height: 48px; + margin-right: $space-xl; + opacity: 0.5; + filter: grayscale(1); + } + + .badge-name { + font-size: 16px; + font-weight: 700; + line-height: 24px; + color: $black-100; + } + } + + .actions { + display: flex; + align-items: center; + + @include ltemd { + margin: $space-sm 0; + } + + .action-btn { + margin-right: $space-sm; + + &:last-child { + margin-right: 0; + } + } + } + } + } + + .loadbtn-wrap { + display: flex; + justify-content: center; + flex: 1; + } } diff --git a/src-ts/tools/gamification-admin/pages/badge-listing/BadgeListingPage.tsx b/src-ts/tools/gamification-admin/pages/badge-listing/BadgeListingPage.tsx index 04fca91ee..dfa788084 100644 --- a/src-ts/tools/gamification-admin/pages/badge-listing/BadgeListingPage.tsx +++ b/src-ts/tools/gamification-admin/pages/badge-listing/BadgeListingPage.tsx @@ -1,23 +1,64 @@ -import { FC } from 'react' +import { Dispatch, FC, SetStateAction, useState } from 'react' +import { NavigateFunction, useNavigate } from 'react-router-dom' -import { ContentLayout } from '../../../../lib' +import { + ButtonProps, + ContentLayout, + InfinitePageHandler, + LoadingSpinner, + Sort, + Table, + TableColumn, + tableGetDefaultSort +} from '../../../../lib' +import { GameBadge, useGetGameBadgesPage } from '../../game-lib' +import { createBadgeRoute } from '../../gamification-admin.routes' +import { badgeListingColumns } from './badge-listing-table' import styles from './BadgeListingPage.module.scss' const BadgeListingPage: FC = () => { - return ( - -
- -
-
- ) + const [sort, setSort]: [Sort, Dispatch>] = useState(tableGetDefaultSort(badgeListingColumns)) + const [columns]: [ + ReadonlyArray>, + Dispatch>>>, + ] + = useState>>([...badgeListingColumns]) + + const pageHandler: InfinitePageHandler = useGetGameBadgesPage(sort) + const navigate: NavigateFunction = useNavigate() + + function onSortClick(newSort: Sort): void { + setSort({ ...newSort }) + } + + // header button config + const buttonConfig: ButtonProps = { + label: 'Create New Badge', + onClick: () => navigate(createBadgeRoute), + } + + if (!pageHandler.data) { + return + } + + return ( + +
+ + + + ) } export default BadgeListingPage diff --git a/src-ts/tools/gamification-admin/pages/badge-listing/badge-listing-table/badge-action-renderer/BadgeActionRenderer.module.scss b/src-ts/tools/gamification-admin/pages/badge-listing/badge-listing-table/badge-action-renderer/BadgeActionRenderer.module.scss new file mode 100644 index 000000000..0ce6473f5 --- /dev/null +++ b/src-ts/tools/gamification-admin/pages/badge-listing/badge-listing-table/badge-action-renderer/BadgeActionRenderer.module.scss @@ -0,0 +1,27 @@ +@import "../../../../../../lib/styles/includes"; +@import "../../../../../../lib/styles/variables"; + +.badge-actions { + display: flex; + align-items: center; + justify-content: center; + padding-top: $space-lg; + + @include ltemd { + flex-direction: column; + align-items: flex-end; + } + + a { + margin-right: $space-sm; + + @include ltemd { + margin-right: 0; + margin-bottom: $space-sm; + } + + &:last-child { + margin-right: 0; + } + } +} \ No newline at end of file diff --git a/src-ts/tools/gamification-admin/pages/badge-listing/badge-listing-table/badge-action-renderer/BadgeActionRenderer.tsx b/src-ts/tools/gamification-admin/pages/badge-listing/badge-listing-table/badge-action-renderer/BadgeActionRenderer.tsx new file mode 100644 index 000000000..6ca456669 --- /dev/null +++ b/src-ts/tools/gamification-admin/pages/badge-listing/badge-listing-table/badge-action-renderer/BadgeActionRenderer.tsx @@ -0,0 +1,49 @@ +import { Button, ButtonProps, useCheckIsMobile } from '../../../../../../lib' +import { GameBadge } from '../../../../game-lib' +import { badgeDetailPath } from '../../../../gamification-admin.routes' + +import styles from './BadgeActionRenderer.module.scss' + +function BadgeActionRenderer(badge: GameBadge): JSX.Element { + + const isMobile: boolean = useCheckIsMobile() + + const buttonProps: ButtonProps = { + buttonStyle: 'secondary', + size: isMobile ? 'xs' : 'sm', + } + + const actionButtons: Array<{ + label: string + view?: 'edit' | 'award' + }> = [ + { + label: 'View', + }, + { + label: 'Edit', + view: 'edit', + }, + { + label: 'Award', + view: 'award', + }, + ] + + return ( +
+ {actionButtons.map((button, index) => { + return ( +
+ ) +} + +export default BadgeActionRenderer diff --git a/src-ts/tools/gamification-admin/pages/badge-listing/badge-listing-table/badge-action-renderer/index.ts b/src-ts/tools/gamification-admin/pages/badge-listing/badge-listing-table/badge-action-renderer/index.ts new file mode 100644 index 000000000..8324241f1 --- /dev/null +++ b/src-ts/tools/gamification-admin/pages/badge-listing/badge-listing-table/badge-action-renderer/index.ts @@ -0,0 +1 @@ +export { default as BadgeActionRenderer } from './BadgeActionRenderer' diff --git a/src-ts/tools/gamification-admin/pages/badge-listing/badge-listing-table/badge-listing-table.config.tsx b/src-ts/tools/gamification-admin/pages/badge-listing/badge-listing-table/badge-listing-table.config.tsx new file mode 100644 index 000000000..1d6d54ef4 --- /dev/null +++ b/src-ts/tools/gamification-admin/pages/badge-listing/badge-listing-table/badge-listing-table.config.tsx @@ -0,0 +1,20 @@ +import { TableColumn } from '../../../../../lib' +import { GameBadge } from '../../../game-lib' + +import { BadgeActionRenderer } from './badge-action-renderer' +import { BadgeListingNameRenderer } from './badge-name-renderer' + +export const badgeListingColumns: ReadonlyArray> = [ + { + defaultSortDirection: 'asc', + isDefaultSort: true, + label: 'Badge Name', + propertyName: 'badge_name', + renderer: BadgeListingNameRenderer, + type: 'element', + }, + { + renderer: BadgeActionRenderer, + type: 'action', + }, +] diff --git a/src-ts/tools/gamification-admin/pages/badge-listing/badge-listing-table/badge-name-renderer/BadgeListingNameRenderer.module.scss b/src-ts/tools/gamification-admin/pages/badge-listing/badge-listing-table/badge-name-renderer/BadgeListingNameRenderer.module.scss new file mode 100644 index 000000000..0bbb9a231 --- /dev/null +++ b/src-ts/tools/gamification-admin/pages/badge-listing/badge-listing-table/badge-name-renderer/BadgeListingNameRenderer.module.scss @@ -0,0 +1,28 @@ +@import "../../../../../../lib/styles/includes"; +@import "../../../../../../lib/styles/variables"; + +.badge { + display: flex; + align-items: center; + + .badge-image { + width: 48px; + height: 48px; + margin-right: $space-xl; + } + + .badge-image-disabled { + width: 48px; + height: 48px; + margin-right: $space-xl; + opacity: 0.5; + filter: grayscale(1); + } + + .badge-name { + font-size: 16px; + font-weight: 700; + line-height: 24px; + color: $black-100; + } +} \ No newline at end of file diff --git a/src-ts/tools/gamification-admin/pages/badge-listing/badge-listing-table/badge-name-renderer/BadgeListingNameRenderer.tsx b/src-ts/tools/gamification-admin/pages/badge-listing/badge-listing-table/badge-name-renderer/BadgeListingNameRenderer.tsx new file mode 100644 index 000000000..7b9c604ca --- /dev/null +++ b/src-ts/tools/gamification-admin/pages/badge-listing/badge-listing-table/badge-name-renderer/BadgeListingNameRenderer.tsx @@ -0,0 +1,18 @@ +import { GameBadge } from '../../../../game-lib' + +import styles from './BadgeListingNameRenderer.module.scss' + +function BadgeListingNameRenderer(badge: GameBadge): JSX.Element { + return ( +
+ {badge.badge_name} +

{badge.badge_name}

+
+ ) +} + +export default BadgeListingNameRenderer diff --git a/src-ts/tools/gamification-admin/pages/badge-listing/badge-listing-table/badge-name-renderer/index.ts b/src-ts/tools/gamification-admin/pages/badge-listing/badge-listing-table/badge-name-renderer/index.ts new file mode 100644 index 000000000..dac5de575 --- /dev/null +++ b/src-ts/tools/gamification-admin/pages/badge-listing/badge-listing-table/badge-name-renderer/index.ts @@ -0,0 +1 @@ +export { default as BadgeListingNameRenderer } from './BadgeListingNameRenderer' diff --git a/src-ts/tools/gamification-admin/pages/badge-listing/badge-listing-table/index.ts b/src-ts/tools/gamification-admin/pages/badge-listing/badge-listing-table/index.ts new file mode 100644 index 000000000..8fa32ec69 --- /dev/null +++ b/src-ts/tools/gamification-admin/pages/badge-listing/badge-listing-table/index.ts @@ -0,0 +1 @@ +export * from './badge-listing-table.config' diff --git a/src-ts/tools/gamification-admin/pages/create-badge/CreateBadgePage.tsx b/src-ts/tools/gamification-admin/pages/create-badge/CreateBadgePage.tsx index 020b7f8eb..22d7e59d6 100644 --- a/src-ts/tools/gamification-admin/pages/create-badge/CreateBadgePage.tsx +++ b/src-ts/tools/gamification-admin/pages/create-badge/CreateBadgePage.tsx @@ -1,30 +1,32 @@ -import { FC, useMemo } from 'react' +import { FC } from 'react' import { Breadcrumb, BreadcrumbItemModel, ContentLayout } from '../../../../lib' -import { baseUrl } from '../../gamification-admin.routes' -import { toolTitle } from '../../GamificationAdmin' +import { useGamificationBreadcrumb } from '../../game-lib' import styles from './CreateBadgePage.module.scss' const CreateBadgePage: FC = () => { - const breadcrumb: Array = useMemo(() => [ - { name: toolTitle, url: baseUrl }, - { name: 'create badge', url: '#' }, - ], []) - return ( - - -
+ const breadcrumb: Array = useGamificationBreadcrumb([ + { + name: 'create badge', + url: '#', + }, + ]) -
-
- ) + return ( + + +
+ +
+
+ ) } export default CreateBadgePage diff --git a/yarn.lock b/yarn.lock index 301fcda10..734710196 100644 --- a/yarn.lock +++ b/yarn.lock @@ -14537,6 +14537,11 @@ svgo@^2.7.0: picocolors "^1.0.0" stable "^0.1.8" +swr@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/swr/-/swr-1.3.0.tgz#c6531866a35b4db37b38b72c45a63171faf9f4e8" + integrity sha512-dkghQrOl2ORX9HYrMDtPa7LTVHJjCTeZoB1dqTbnnEDlSvN8JEKpYIYurDfvbQFUUS8Cg8PceFVZNkW0KNNYPw== + symbol-tree@^3.2.2, symbol-tree@^3.2.4: version "3.2.4" resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2"