Skip to content

Commit

Permalink
feat(campaign): allow to change application state on list page
Browse files Browse the repository at this point in the history
  • Loading branch information
robertu7 committed Jul 16, 2024
1 parent 27e2f09 commit 7c1f4a1
Show file tree
Hide file tree
Showing 9 changed files with 388 additions and 11 deletions.
153 changes: 153 additions & 0 deletions src/components/Campaign/SetApplicationState/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
import * as React from 'react'
import { Select, Modal, message } from 'antd'

import ErrorMessage from '../../ErrorMessage'

import withSetState, { ChildProps } from './withSetState'
import { GQLCampaignApplicationState } from '../../../definitions'

type SetStateState = {
applicationState?: GQLCampaignApplicationState
loading: boolean
error: any
}

const PLACEHOLDER_KEY = 'placeholder'

const COMMENT_STATES: {
key: GQLCampaignApplicationState
text: string
disabled?: boolean
}[] = [
{ key: 'succeeded', text: '已通過' },
{ key: 'pending', text: '審核中' },
{ key: 'rejected', text: '已拒絕' },
]

const ApplicationStateTag = ({
state,
}: {
state: GQLCampaignApplicationState
}) => {
const stateMap = {
succeeded: '已通過',
pending: '審核中',
rejected: '已拒絕',
}
return <span>{stateMap[state]}</span>
}

class SetState extends React.Component<ChildProps, SetStateState> {
state: Readonly<SetStateState> = {
applicationState: this.props.applicationState,
loading: false,
error: null,
}

private _onSelectCampaignState = (
value: GQLCampaignApplicationState | typeof PLACEHOLDER_KEY
) => {
if (!value || value === PLACEHOLDER_KEY) {
return
}

this.setState({ applicationState: value }, () => {
if (this.props.applicationState !== value) {
this.preConfirm()
}
})
}

private _onConfirmChange = async () => {
this.setState({ loading: true, error: null })

const { mutate, user, campaign, onSuccess } = this.props
const { applicationState } = this.state

if (!applicationState) {
return
}

try {
await mutate({
variables: {
user,
campaign,
state: applicationState,
},
})
this.setState({
applicationState,
loading: false,
error: null,
})
message.success('修改成功', 1, () => {
if (onSuccess) {
onSuccess()
}
})
} catch (error) {
this.setState({ loading: false, error })
}
}

private preConfirm = () => {
Modal.confirm({
title: `確認修改申請狀態?`,
content: (
<div style={{ marginTop: 16 }}>
<span>
修改後,申請狀態將從&nbsp;&nbsp;
{this.props.applicationState && (
<ApplicationStateTag state={this.props.applicationState} />
)}
改為&nbsp;&nbsp;
{this.state.applicationState && (
<ApplicationStateTag state={this.state.applicationState} />
)}
</span>
</div>
),
cancelText: '取消',
okText: '確認',
onOk: () => {
this._onConfirmChange()
},
onCancel: this.revertChange,
})
}

private revertChange = () => {
this.setState({ applicationState: this.props.applicationState })
}

public render() {
const { disabled } = this.props
const { applicationState, loading, error } = this.state

if (error) {
return <ErrorMessage error={error} />
}

return (
<span>
<Select
value={applicationState}
onSelect={this._onSelectCampaignState}
style={{ marginRight: 8, width: 100 }}
loading={loading}
disabled={disabled}
>
<Select.Option key={PLACEHOLDER_KEY} disabled></Select.Option>
{COMMENT_STATES.map(({ key, text, disabled }) => (
<Select.Option key={key} disabled={disabled}>
{text}
</Select.Option>
))}
</Select>
</span>
)
}
}

export default withSetState(SetState)
47 changes: 47 additions & 0 deletions src/components/Campaign/SetApplicationState/withSetState.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { graphql, ChildMutateProps } from 'react-apollo'
import gql from 'graphql-tag'
import { GQLCampaignApplicationState } from '../../../definitions'

const SET_STATE = gql`
mutation updateCampaignApplicationState(
$user: ID!
$campaign: ID!
$state: CampaignApplicationState!
) {
updateCampaignApplicationState(
input: { user: $user, campaign: $campaign, state: $state }
) {
id
}
}
`

type Response = {
updateCampaignApplicationState: {
user: string
campaign: string
state: string
}
}

type InputProps = {
user: string
campaign: string
applicationState?: GQLCampaignApplicationState
disabled?: boolean
onSuccess?: () => void
}

type Variables = {
user: string
campaign: string
state: GQLCampaignApplicationState
}

export type ChildProps = ChildMutateProps<InputProps, Response, Variables>

const withSetState = graphql<InputProps, Response, Variables, ChildProps>(
SET_STATE
)

export default withSetState
87 changes: 87 additions & 0 deletions src/components/Campaign/UserDigestList/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import * as React from 'react'
import { Table } from 'antd'
import _compact from 'lodash/compact'

import UserLink from '../../User/Link'

import { GQLCampaignApplicationState, UserDigest } from '../../../definitions'
import { onPaginationChange, getCurrentPaginationFromUrl } from '../../../utils'
import { PAGE_SIZE } from '../../../constants'
import SetApplicationState from '../SetApplicationState'

type Datum = { node: UserDigest; applicationState: GQLCampaignApplicationState }

type CampaignUserDigestListProps = {
campaignId: string
data: Datum[]
loading?: boolean
pagination?: {
totalCount: number
pageSize?: number
fetchMore?: any
variables?: any
}
}

class CampaignUserDigestList extends React.Component<
CampaignUserDigestListProps
> {
private _renderNameCell(_: any, record: Datum): React.ReactNode {
return (
<UserLink
id={record.node.id}
userName={record.node.userName}
displayName={record.node.displayName}
/>
)
}

public render() {
const { data, loading = false, pagination } = this.props
const currentPagination = getCurrentPaginationFromUrl()

return (
<Table<Datum>
bordered
loading={loading}
dataSource={_compact(data)}
scroll={{ x: 1200, y: '70vh' }}
pagination={
pagination
? {
defaultCurrent: currentPagination && currentPagination.page,
pageSize: pagination.pageSize || PAGE_SIZE,
total: pagination.totalCount,
onChange: (page) => onPaginationChange({ pagination, page }),
showTotal: (t) => `共 ${t} 項`,
position: 'both',
}
: false
}
rowKey={(record) => record.node.id}
>
<Table.Column<Datum>
dataIndex="node.info.id"
title="用戶"
width={200}
render={this._renderNameCell}
/>

<Table.Column<Datum>
dataIndex="applicationState"
title="申請狀態"
width={100}
render={(_: any, record: Datum) => (
<SetApplicationState
user={record.node.id}
campaign={this.props.campaignId}
applicationState={record.applicationState}
/>
)}
/>
</Table>
)
}
}

export default CampaignUserDigestList
11 changes: 10 additions & 1 deletion src/definitions/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
GQLCampaignState,
GQLDatetimeRange,
GQLCampaignStage,
GQLCampaignApplicationState,
} from './schema'

export * from './schema'
Expand Down Expand Up @@ -190,7 +191,15 @@ export type CampaignDetail = CampaignDigest & {
cover: string
link: string
stages: Array<CampaignStage>
participants: Connection<UserDigest>
participants: {
totalCount: number
pageInfo: GQLPageInfo
edges: {
cursor: string
node: UserDigest
applicationState: GQLCampaignApplicationState
}[]
}
articles: Connection<ArticleDigest>
}

Expand Down
Loading

0 comments on commit 7c1f4a1

Please sign in to comment.