Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src-ts/lib/pagination/index.ts
Original file line number Diff line number Diff line change
@@ -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'
5 changes: 5 additions & 0 deletions src-ts/lib/pagination/infinite-page-dao.model.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export interface InfinitePageDao<T> {
count: number
// TODO: rename this 'items' so it can be used in a grid/card view
rows: ReadonlyArray<T>
}
5 changes: 5 additions & 0 deletions src-ts/lib/pagination/infinite-page-handler.model.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export interface InfinitePageHandler<T> {
data?: ReadonlyArray<T>
getAndSetNext: () => void
hasMore: boolean
}
25 changes: 25 additions & 0 deletions src-ts/lib/pagination/use-infinite-page.hook.ts
Original file line number Diff line number Diff line change
@@ -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<T>(getKey: (index: number, previousPageData: InfinitePageDao<T>) => string | undefined):
InfinitePageHandler<T> {

const { data, setSize, size }: SWRInfiniteResponse<InfinitePageDao<T>> = useSWRInfinite(getKey, { revalidateFirstPage: false })

// flatten version of badges paginated data
const outputData: ReadonlyArray<T> = flatten(map(data, dao => dao.rows))

function getAndSetNext(): void {
setSize(size + 1)
}

return {
data: outputData,
getAndSetNext,
hasMore: outputData.length < (data?.[0]?.count || 0),
}
}
37 changes: 21 additions & 16 deletions src-ts/lib/table/Table.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import classNames from 'classnames'
import { Dispatch, MouseEvent, SetStateAction, useEffect, useState } from 'react'

import { Button, ButtonSize, ButtonStyle } from '../button'
import { Button } from '../button'
import { Sort } from '../pagination'
import '../styles/_includes.scss'
import { IconOutline } from '../svgs'
Expand All @@ -16,13 +16,10 @@ import styles from './Table.module.scss'
interface TableProps<T> {
readonly columns: ReadonlyArray<TableColumn<T>>
readonly data: ReadonlyArray<T>
readonly loadMoreBtnLabel?: string
readonly loadMoreBtnSize?: ButtonSize
readonly loadMoreBtnStyle?: ButtonStyle
readonly moreToLoad?: boolean
readonly onLoadMoreClick?: (data: T) => void
readonly onLoadMoreClick?: () => void
readonly onRowClick?: (data: T) => void
readonly onToggleSort?: (sort: Sort | undefined) => void
readonly onToggleSort?: (sort: Sort) => void
}

interface DefaultSortDirectionMap {
Expand All @@ -41,7 +38,7 @@ const Table: <T extends { [propertyName: string]: any }>(props: TableProps<T>) =
Dispatch<SetStateAction<DefaultSortDirectionMap | undefined>>
]
= useState<DefaultSortDirectionMap | undefined>()
const [sortedData, setSortData]: [ReadonlyArray<T>, Dispatch<SetStateAction<ReadonlyArray<T>>>]
const [sortedData, setSortedData]: [ReadonlyArray<T>, Dispatch<SetStateAction<ReadonlyArray<T>>>]
= useState<ReadonlyArray<T>>(props.data)

useEffect(() => {
Expand All @@ -54,7 +51,11 @@ const Table: <T extends { [propertyName: string]: any }>(props: TableProps<T>) =
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<T> = !!props.onToggleSort ? data : tableGetSorted(data, columns, sort)

setSortedData(sorted)
},
[
columns,
Expand Down Expand Up @@ -84,9 +85,7 @@ const Table: <T extends { [propertyName: string]: any }>(props: TableProps<T>) =
setSort(newSort)

// call the callback to notify parent for sort update
if (props.onToggleSort) {
props.onToggleSort(newSort)
}
props.onToggleSort?.(newSort)
}

const headerRow: Array<JSX.Element> = props.columns
Expand All @@ -95,13 +94,12 @@ const Table: <T extends { [propertyName: string]: any }>(props: TableProps<T>) =
const isCurrentlySorted: boolean = isSortable && col.propertyName === sort?.fieldName
const colorClass: string = isCurrentlySorted ? 'black-100' : 'black-60'
const sortableClass: string | undefined = isSortable ? styles.sortable : undefined
const centerClass: string | undefined = col.centerHeader ? styles.centerHeader : undefined
return (
<th
className={styles.th}
key={index}
>
<div className={classNames(styles['header-container'], styles[col.type], colorClass, sortableClass, centerClass)}>
<div className={classNames(styles['header-container'], styles[col.type], colorClass, sortableClass)}>
{col.label}
{!!col.tooltip && (
<div className={styles.tooltip}>
Expand Down Expand Up @@ -172,9 +170,16 @@ const Table: <T extends { [propertyName: string]: any }>(props: TableProps<T>) =
</tbody>
</table>
{
props.moreToLoad && <div className={styles['loadBtnWrap']}>
<Button buttonStyle={props.loadMoreBtnStyle} label={props.loadMoreBtnLabel} size={props.loadMoreBtnSize} onClick={props.onLoadMoreClick} />
</div>
!!props.moreToLoad && !!props.onLoadMoreClick && (
<div className={styles['loadBtnWrap']}>
<Button
buttonStyle='tertiary'
label='Load More'
size='lg'
onClick={props.onLoadMoreClick}
/>
</div>
)
}
</div>
)
Expand Down
1 change: 1 addition & 0 deletions src-ts/lib/table/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from './table-column.model'
export { tableGetDefaultSort } from './table-functions'
export { default as Table } from './Table'
1 change: 0 additions & 1 deletion src-ts/lib/table/table-column.model.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { TableCellType } from './table-cell.type'

export interface TableColumn<T> {
readonly centerHeader?: boolean
readonly defaultSortDirection?: 'asc' | 'desc'
readonly isDefaultSort?: boolean
readonly label?: string
Expand Down
18 changes: 11 additions & 7 deletions src-ts/lib/table/table-functions/table.functions.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,21 @@
import { Sort } from '../../pagination'
import { TableColumn } from '../table-column.model'

export function getDefaultSort<T>(columns: ReadonlyArray<TableColumn<T>>): Sort | undefined {
export function getDefaultSort<T>(columns: ReadonlyArray<TableColumn<T>>): Sort {

const defaultSortColumn: TableColumn<T> | 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
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export interface GamificationConfigModel {
ORG_ID: string
PAGE_SIZE: number
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ import { GamificationConfigModel } from './gamification-config.model'

export const GamificationConfigDefault: GamificationConfigModel = {
ORG_ID: '6052dd9b-ea80-494b-b258-edd1331e27a3',
PAGE_SIZE: 12,
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { GamificationConfigModel } from './gamification-config.model'
import { GamificationConfigDefault } from './gamification.default.config'

export const GamificationConfigDev: GamificationConfigModel = {
ORG_ID: '6052dd9b-ea80-494b-b258-edd1331e27a3',
...GamificationConfigDefault,
}
Original file line number Diff line number Diff line change
@@ -1,5 +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',
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export interface Badge {
// TODO: add factory to convert snake case property names to camel case
export interface GameBadge {
active: boolean
badge_description: string
badge_image_url: string
Expand Down
3 changes: 3 additions & 0 deletions src-ts/tools/gamification-admin/game-lib/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from './game-badge.model'
export * from './use-get-game-badges-page.hook'
export * from './use-gamification-breadcrumb.hook'
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { BreadcrumbItemModel } from '../../../lib'
import { basePath } from '../gamification-admin.routes'
import { toolTitle } from '../GamificationAdmin'

export function useGamificationBreadcrumb(items: Array<BreadcrumbItemModel>): Array<BreadcrumbItemModel> {

const breadcrumb: Array<BreadcrumbItemModel> = [
{
name: toolTitle,
url: basePath,
},
...items,
]

return breadcrumb
}
Original file line number Diff line number Diff line change
@@ -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<GameBadge> {

function getKey(index: number, previousPageData: InfinitePageDao<GameBadge>): string | undefined {

// reached the end
if (!!previousPageData && !previousPageData.rows.length) {
return undefined
}

const params: Record<string, string> = {
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)
}
22 changes: 16 additions & 6 deletions src-ts/tools/gamification-admin/gamification-admin.routes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<string> = [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<PlatformRoute> = [
{
Expand All @@ -18,17 +26,19 @@ export const gamificationAdminRoutes: Array<PlatformRoute> = [
},
{
element: <CreateBadgePage />,
route: '/create-badge',
route: createBadgePath,
},
{
element: <BadgeDetailPage />,
route: '/badge-detail/:id',
route: `${baseDetailPath}/:id`,
},
],
element: <GamificationAdmin />,
hidden: true,
rolesRequired,
route: baseUrl,
rolesRequired: [
UserRole.gamificationAdmin,
],
route: basePath,
title: toolTitle,
},
]
5 changes: 0 additions & 5 deletions src-ts/tools/gamification-admin/lib/hooks/getDataSource.ts

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,32 +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 = () => {
// TDOD: use whit GAME-78
// const { id: badgeID } : { badgeID: string } = useParams()
const breadcrumb: Array<BreadcrumbItemModel> = useMemo(() => [
{ name: toolTitle, url: baseUrl },
{ name: 'badge detail', url: '#' },
], [])
// TDOD: use whit GAME-78
// const { id: badgeID } : { badgeID: string } = useParams()

return (
<ContentLayout
contentClass={styles['contentLayout']}
outerClass={styles['contentLayout-outer']}
innerClass={styles['contentLayout-inner']}
title='Badge Detail'
>
<Breadcrumb items={breadcrumb} />
<div className={styles.container}>
const breadcrumb: Array<BreadcrumbItemModel> = useGamificationBreadcrumb([
{
name: 'badge detail',
url: '#',
},
])

</div>
</ContentLayout>
)
return (
<ContentLayout
contentClass={styles['contentLayout']}
outerClass={styles['contentLayout-outer']}
innerClass={styles['contentLayout-inner']}
title='Badge Detail'
>
<Breadcrumb items={breadcrumb} />
<div className={styles.container}>

</div>
</ContentLayout>
)
}

export default BadgeDetailPage
Loading