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
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
"moment": "^2.29.3",
"moment-timezone": "^0.5.34",
"prop-types": "^15.8.1",
"qs": "^6.11.0",
"rc-checkbox": "^2.3.2",
"react": "^17.0.2",
"react-apexcharts": "^1.4.0",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
@import "../styles/variables/palette";

.memberSelect {
color: $black-60;
}
94 changes: 94 additions & 0 deletions src-ts/lib/member-autocomplete/InputHandleAutocomplete.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import { FC, FocusEvent } from 'react'
import { MultiValue, StylesConfig } from 'react-select'
// tslint:disable-next-line: no-submodule-imports
import AsyncSelect from 'react-select/async'

import { InputWrapper } from '../form/form-groups/form-input/input-wrapper'

import { membersAutocompete, MembersAutocompeteResult } from './input-handle-functions'
import styles from './InputHandleAutocomplete.module.scss'

export interface InputHandleAutocompleteProps {
readonly className?: string
readonly dirty?: boolean
readonly disabled?: boolean
readonly error?: string
readonly hideInlineErrors?: boolean
readonly hint?: string
readonly label?: string | JSX.Element
readonly name: string
readonly onBlur?: (event: FocusEvent<HTMLInputElement>) => void
readonly onChange: (newValue: Array<MembersAutocompeteResult>) => void
readonly placeholder?: string
readonly tabIndex: number
readonly value?: Array<MembersAutocompeteResult>
}

const InputHandleAutocomplete: FC<InputHandleAutocompleteProps> = (props: InputHandleAutocompleteProps) => {
const customStyles: StylesConfig<any> = {
control: (provided) => ({
...provided,
border: 'none',
}),
input: (provided) => ({
...provided,
color: 'inherit',
fontSize: 16,
}),
multiValue: (provided) => ({
...provided,
borderRadius: 50,
}),
multiValueLabel: (provided) => ({
...provided,
fontSize: 12,
}),
option: (provided) => ({
...provided,
borderBottom: '1px solid #E9E9E9',
color: 'inherit',
fontSize: 16,
fontWeight: 400,
padding: 16,
}),
placeholder: (provided) => ({
...provided,
color: 'inherit',
fontSize: 16,
fontWeight: 400,
}),
valueContainer: (provided) => ({
...provided,
padding: 0,
}),
}

return (
<InputWrapper
{...props}
dirty={!!props.dirty}
disabled={!!props.disabled}
label={props.label || props.name}
hideInlineErrors={props.hideInlineErrors}
type='text'
>
<AsyncSelect
className={styles.memberSelect}
cacheOptions
getOptionLabel={({ handle }) => handle}
getOptionValue={({ userId }) => userId}
isMulti
key={props.value?.length}
loadOptions={membersAutocompete}
styles={customStyles}
placeholder={props.placeholder}
onBlur={props.onBlur}
onChange={(newValue: MultiValue<MembersAutocompeteResult>) => props.onChange(newValue as Array<MembersAutocompeteResult>)}
value={props.value}
isDisabled={props.disabled}
/>
</InputWrapper>
)
}

export default InputHandleAutocomplete
1 change: 1 addition & 0 deletions src-ts/lib/member-autocomplete/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default as InputHandleAutocomplete } from './InputHandleAutocomplete'
23 changes: 23 additions & 0 deletions src-ts/lib/member-autocomplete/input-handle-functions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import qs from 'qs'

import { xhrGetAsync } from '..'
import { EnvironmentConfig } from '../../config'

export interface MembersAutocompeteQuery {
term: string
}

export interface MembersAutocompeteResult {
firstName: string
handle: string
lastName: string
userId: string
}

export async function membersAutocompete(term: string): Promise<Array<MembersAutocompeteResult>> {
const query: MembersAutocompeteQuery = {
term,
}

return xhrGetAsync(`${EnvironmentConfig.API.V5}/members/autocomplete?${qs.stringify(query)}`)
}
6 changes: 6 additions & 0 deletions src-ts/tools/gamification-admin/game-lib/game-badge.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,11 @@ export interface GameBadge {
badge_name: string
badge_status: string
id: string
member_badges?: Array<{
awarded_at: string,
awarded_by: string,
user_handle: string,
user_id: string,
}>
organization_id: string
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.tabWrap {
display: flex;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { FC } from 'react'

import { GameBadge } from '../../../game-lib'

import styles from './AwardedMembersTab.module.scss'

export interface AwardedMembersTabProps {
awardedMembers?: GameBadge['member_badges']
}

const AwardedMembersTab: FC<AwardedMembersTabProps> = (props: AwardedMembersTabProps) => {
return (
<div className={styles.tabWrap}>

</div>
)
}

export default AwardedMembersTab
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './AwardedMembersTab'
Original file line number Diff line number Diff line change
Expand Up @@ -131,4 +131,12 @@ $badgePreviewImage: 72px;
}
}
}

.activeTabElement {
margin-top: $space-xxxxl;

@include ltemd {
margin-top: $space-md;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,18 @@ import { Breadcrumb, BreadcrumbItemModel, Button, ButtonProps, ContentLayout, Ic
import { GamificationConfig } from '../../game-config'
import { BadgeDetailPageHandler, GameBadge, useGamificationBreadcrumb, useGetGameBadgeDetails } from '../../game-lib'

import AwardedMembersTab from './AwardedMembersTab/AwardedMembersTab'
import { badgeDetailsTabs, BadgeDetailsTabViews } from './badge-details-tabs.config'
import { submitRequestAsync as updateBadgeAsync } from './badge-details.functions'
import styles from './BadgeDetailPage.module.scss'
import BatchAwardTab from './BatchAwardTab/BatchAwardTab'
import ManualAwardTab from './ManualAwardTab/ManualAwardTab'

const md: MarkdownIt = new MarkdownIt({
html: true,
linkify: true,
typographer: true,
// TODO: check with PM ig those are needed?
// linkify: true,
// typographer: true,
})

/* tslint:disable:cyclomatic-complexity */
Expand All @@ -38,7 +42,9 @@ const BadgeDetailPage: FC = () => {

const { hash }: { hash: string } = useLocation()

const activeTab: string = hash === '#award' ? BadgeDetailsTabViews.manualAward : BadgeDetailsTabViews.awardedMembers
const [activeTab, setActiveTab]: [string, Dispatch<SetStateAction<string>>] = useState<string>(
hash === '#award' ? BadgeDetailsTabViews.manualAward : BadgeDetailsTabViews.awardedMembers
)

const [tabs]: [
ReadonlyArray<TabsNavItem>,
Expand Down Expand Up @@ -118,19 +124,15 @@ const BadgeDetailPage: FC = () => {
badgeDetailsHandler.data,
])

// define the tabs so they can be displayed on various results
// define the tabs so they can be displayed on various tasks
const tabsElement: JSX.Element = (
<TabsNavbar
tabs={tabs}
defaultActive={activeTab}
onChange={onChangeTab}
onChange={setActiveTab}
/>
)

function onChangeTab(active: string): void {
// TODO: implement in GAME-129
}

function onActivateBadge(): void {
// TODO: implement in GAME-127
}
Expand Down Expand Up @@ -189,6 +191,19 @@ const BadgeDetailPage: FC = () => {
}
}

// default tab
let activeTabElement: JSX.Element
= <AwardedMembersTab
awardedMembers={badgeDetailsHandler.data?.member_badges}
/>
if (activeTab === BadgeDetailsTabViews.manualAward) {
activeTabElement = <ManualAwardTab awardedMembers={badgeDetailsHandler.data?.member_badges} />
}
if (activeTab === BadgeDetailsTabViews.batchAward) {
activeTabElement = <BatchAwardTab />
}

// show page loader if we fetching results
if (!badgeDetailsHandler.data && !badgeDetailsHandler.error) {
return <LoadingSpinner />
}
Expand Down Expand Up @@ -268,6 +283,9 @@ const BadgeDetailPage: FC = () => {
</div>
<PageDivider />
{tabsElement}
<div className={styles.activeTabElement}>
{activeTabElement}
</div>
</>
)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.tabWrap {
display: flex;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { FC } from 'react'

import styles from './BatchAwardTab.module.scss'

const BatchAwardTab: FC = () => {
return (
<div className={styles.tabWrap}>
<h3>Batch Award</h3>
</div>
)
}

export default BatchAwardTab
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './BatchAwardTab'
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
@import "../../../../../lib/styles/variables/palette";
@import "../../../../../lib/styles/includes";

.tabWrap {
display: flex;
flex-direction: column;

.manualFormWrap {
display: grid;
grid-template-columns: 1fr 1fr;
gap: $space-xxxxl;
margin-top: $space-xxl;

@include ltemd {
grid-template-columns: 1fr;
}

.manualForm {
display: flex;
flex-direction: column;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { Dispatch, FC, SetStateAction, useState } from 'react'

import { Button } from '../../../../../lib'
import { InputHandleAutocomplete } from '../../../../../lib/member-autocomplete'
import { MembersAutocompeteResult } from '../../../../../lib/member-autocomplete/input-handle-functions'
import { GameBadge } from '../../../game-lib'

import styles from './ManualAwardTab.module.scss'

export interface ManualAwardTabProps {
awardedMembers?: GameBadge['member_badges']
}

const ManualAwardTab: FC<ManualAwardTabProps> = (props: ManualAwardTabProps) => {

const [selectedMembers, setSelectedMembers]: [Array<MembersAutocompeteResult>, Dispatch<SetStateAction<Array<MembersAutocompeteResult>>>]
= useState<Array<MembersAutocompeteResult>>([])

function onAward(): void {
setSelectedMembers([])
}

return (
<div className={styles.tabWrap}>
<h3>Manual Award</h3>
<div className={styles.manualFormWrap}>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Neque ullamcorper neque sed orci, enim amet, sed.</p>
<div className={styles.manualForm}>
<InputHandleAutocomplete
label={'Select Member'}
name='manual-award-member-select'
placeholder='Type and select member to award'
onChange={setSelectedMembers}
tabIndex={0}
value={selectedMembers}
/>
<div className={styles.actionsWrap}>
<Button
buttonStyle='secondary'
label='Award'
className={styles.awardBtn}
disable={!selectedMembers.length}
onClick={onAward}
/>
</div>
</div>
</div>
</div>
)
}

export default ManualAwardTab
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './ManualAwardTab'
2 changes: 1 addition & 1 deletion yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -12798,7 +12798,7 @@ qs@6.10.3:
dependencies:
side-channel "^1.0.4"

qs@^6.9.4:
qs@^6.11.0, qs@^6.9.4:
version "6.11.0"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.0.tgz#fd0d963446f7a65e1367e01abd85429453f0c37a"
integrity sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==
Expand Down