-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(campaign): add pages for campaign
- Loading branch information
Showing
18 changed files
with
9,781 additions
and
14,433 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,131 @@ | ||
import * as React from 'react' | ||
import { Table } from 'antd' | ||
import _compact from 'lodash/compact' | ||
|
||
import DateTime from '../../DateTime' | ||
import CampaignLink from '../Link' | ||
import CampaignSetState from '../SetState' | ||
|
||
import { PAGE_SIZE } from '../../../constants' | ||
import { CampaignDigest } from '../../../definitions' | ||
import { onPaginationChange, getCurrentPaginationFromUrl } from '../../../utils' | ||
|
||
type CampaignDigestListProps = { | ||
data: CampaignDigest[] | ||
loading?: boolean | ||
pagination?: { | ||
totalCount: number | ||
pageSize?: number | ||
fetchMore?: any | ||
variables?: any | ||
} | ||
} | ||
|
||
type CampaignDigestListState = { | ||
selectedRowKeys: string[] | number[] | ||
selectedRows: CampaignDigest[] | ||
} | ||
|
||
class CampaignDigestList extends React.Component< | ||
CampaignDigestListProps, | ||
CampaignDigestListState | ||
> { | ||
state = { | ||
selectedRowKeys: [], | ||
selectedRows: [], | ||
} | ||
|
||
private _renderNameCell(_: any, record: CampaignDigest): React.ReactNode { | ||
return <CampaignLink id={record.id} name={record.name} /> | ||
} | ||
|
||
private _renderStateCell(_: any, record: CampaignDigest): React.ReactNode { | ||
return <CampaignSetState campaignState={record.state} ids={[record.id]} /> | ||
} | ||
|
||
private _renderApplicationPeriod( | ||
_: any, | ||
record: CampaignDigest | ||
): React.ReactNode { | ||
const { start, end } = record.applicationPeriod || {} | ||
return ( | ||
<> | ||
<DateTime date={start} /> | ||
{' ~ '} | ||
{end && <DateTime date={end} />} | ||
</> | ||
) | ||
} | ||
|
||
private _renderWritingPeriod( | ||
_: any, | ||
record: CampaignDigest | ||
): React.ReactNode { | ||
const { start, end } = record.writingPeriod || {} | ||
return ( | ||
<> | ||
<DateTime date={start} /> | ||
{' ~ '} | ||
{end && <DateTime date={end} />} | ||
</> | ||
) | ||
} | ||
|
||
public render() { | ||
const { data, loading = false, pagination } = this.props | ||
const currentPagination = getCurrentPaginationFromUrl() | ||
|
||
return ( | ||
<> | ||
<Table<CampaignDigest> | ||
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.id} | ||
> | ||
<Table.Column<CampaignDigest> | ||
dataIndex="name" | ||
title="標題" | ||
render={this._renderNameCell} | ||
/> | ||
|
||
<Table.Column<CampaignDigest> | ||
dataIndex="applicationPeriod" | ||
title="報名期" | ||
width={300} | ||
render={this._renderApplicationPeriod} | ||
/> | ||
|
||
<Table.Column<CampaignDigest> | ||
dataIndex="writingPeriod" | ||
title="活動期" | ||
width={300} | ||
render={this._renderWritingPeriod} | ||
/> | ||
|
||
<Table.Column<CampaignDigest> | ||
dataIndex="state" | ||
title="狀態" | ||
width={150} | ||
render={this._renderStateCell} | ||
/> | ||
</Table> | ||
</> | ||
) | ||
} | ||
} | ||
|
||
export default CampaignDigestList |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
import * as React from 'react' | ||
import { Link } from 'react-router-dom' | ||
|
||
import { PATH } from '../../../constants' | ||
|
||
type CampaignLinkProps = { | ||
id: string | ||
name: string | ||
} | ||
|
||
const CampaignLink: React.FunctionComponent<CampaignLinkProps> = ({ | ||
id, | ||
name, | ||
}) => { | ||
return <Link to={PATH.CAMPAIGN_DETAIL.replace(':id', id)}>{name}</Link> | ||
} | ||
|
||
export default CampaignLink |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,144 @@ | ||
import * as React from 'react' | ||
import { Select, Modal, message } from 'antd' | ||
import _get from 'lodash/get' | ||
|
||
import ErrorMessage from '../../ErrorMessage' | ||
import CampaignStateTag from '../StateTag' | ||
|
||
import withSetState, { ChildProps, CampaignState } from './withSetState' | ||
|
||
type SetStateState = { | ||
campaignState?: CampaignState | ||
loading: boolean | ||
error: any | ||
} | ||
|
||
const PLACEHOLDER_KEY = 'placeholder' | ||
|
||
const COMMENT_STATES: { | ||
key: CampaignState | ||
text: string | ||
disabled?: boolean | ||
}[] = [ | ||
{ key: 'active', text: '正常' }, | ||
{ key: 'archived', text: '已隱藏', disabled: true }, | ||
{ key: 'pending', text: '編輯中' }, | ||
{ key: 'finished', text: '已結束' }, | ||
] | ||
|
||
class SetState extends React.Component<ChildProps, SetStateState> { | ||
state: Readonly<SetStateState> = { | ||
campaignState: this.props.campaignState, | ||
loading: false, | ||
error: null, | ||
} | ||
|
||
private _onSelectCampaignState = ( | ||
value: CampaignState | typeof PLACEHOLDER_KEY | ||
) => { | ||
if (!value || value === PLACEHOLDER_KEY) { | ||
return | ||
} | ||
|
||
this.setState({ campaignState: value }, () => { | ||
if (this.props.campaignState !== value) { | ||
this.preConfirm() | ||
} | ||
}) | ||
} | ||
|
||
private _onConfirmChange = async () => { | ||
this.setState({ loading: true, error: null }) | ||
|
||
const { mutate, ids, onSuccess } = this.props | ||
const { campaignState } = this.state | ||
|
||
if (!campaignState) { | ||
return | ||
} | ||
|
||
try { | ||
const result = await mutate({ | ||
variables: { | ||
input: { | ||
ids, | ||
state: campaignState, | ||
}, | ||
}, | ||
}) | ||
const newCampaignState = _get(result, 'data.updateCommentsState.0.state') | ||
this.setState({ | ||
campaignState: newCampaignState, | ||
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> | ||
修改後,評論狀態將從 | ||
{this.props.campaignState && ( | ||
<CampaignStateTag state={this.props.campaignState} /> | ||
)} | ||
改為 | ||
{this.state.campaignState && ( | ||
<CampaignStateTag state={this.state.campaignState} /> | ||
)} | ||
</span> | ||
</div> | ||
), | ||
cancelText: '取消', | ||
okText: '確認', | ||
onOk: () => { | ||
this._onConfirmChange() | ||
}, | ||
onCancel: this.revertChange, | ||
}) | ||
} | ||
|
||
private revertChange = () => { | ||
this.setState({ campaignState: this.props.campaignState }) | ||
} | ||
|
||
public render() { | ||
const { disabled } = this.props | ||
const { campaignState, loading, error } = this.state | ||
|
||
if (error) { | ||
return <ErrorMessage error={error} /> | ||
} | ||
|
||
return ( | ||
<span> | ||
<Select | ||
value={campaignState} | ||
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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
import { graphql, ChildMutateProps } from 'react-apollo' | ||
import gql from 'graphql-tag' | ||
|
||
const SET_STATE = gql` | ||
mutation updateCampaignState($id: ID!, $state: CampaignState!) { | ||
putWritingChallenge(input: { id: $id, state: $state }) { | ||
id | ||
state | ||
} | ||
} | ||
` | ||
|
||
type Response = { | ||
updateCampaignState: { | ||
id: string | ||
state: string | ||
} | ||
} | ||
|
||
export type CampaignState = 'active' | 'pending' | 'finished' | 'archived' | ||
|
||
type InputProps = { | ||
ids: string[] | ||
campaignState?: CampaignState | ||
disabled?: boolean | ||
onSuccess?: () => void | ||
} | ||
|
||
type Variables = { | ||
input: { | ||
ids: string[] | ||
state: CampaignState | ||
} | ||
} | ||
|
||
export type ChildProps = ChildMutateProps<InputProps, Response, Variables> | ||
|
||
const withSetState = graphql<InputProps, Response, Variables, ChildProps>( | ||
SET_STATE | ||
) | ||
|
||
export default withSetState |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
import * as React from 'react' | ||
|
||
import LevelTag, { LevelEnum } from '../../../components/LevelTag' | ||
|
||
const StateMap = { | ||
active: { | ||
level: LevelEnum.SUCCESS, | ||
text: '正常', | ||
}, | ||
pending: { | ||
level: LevelEnum.INFO, | ||
text: '編輯中', | ||
}, | ||
finished: { | ||
level: LevelEnum.SUCCESS, | ||
text: '已結束', | ||
}, | ||
archived: { | ||
level: LevelEnum.ERROR, | ||
text: '已隱藏', | ||
}, | ||
} | ||
|
||
const StateTag = ({ | ||
state, | ||
}: { | ||
state: 'active' | 'pending' | 'finished' | 'archived' | ||
}) => { | ||
const { level, text } = StateMap[state] | ||
return <LevelTag level={level}>{text}</LevelTag> | ||
} | ||
|
||
export default StateTag |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.