Skip to content

Commit

Permalink
feat(campaign): add pages for campaign
Browse files Browse the repository at this point in the history
  • Loading branch information
robertu7 committed Jul 14, 2024
1 parent 036f514 commit f720b7a
Show file tree
Hide file tree
Showing 18 changed files with 9,781 additions and 14,433 deletions.
262 changes: 64 additions & 198 deletions package-lock.json

Large diffs are not rendered by default.

131 changes: 131 additions & 0 deletions src/components/Campaign/DigestList/index.tsx
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
18 changes: 18 additions & 0 deletions src/components/Campaign/Link/index.tsx
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
144 changes: 144 additions & 0 deletions src/components/Campaign/SetState/index.tsx
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>
修改後,評論狀態將從&nbsp;&nbsp;
{this.props.campaignState && (
<CampaignStateTag state={this.props.campaignState} />
)}
改為&nbsp;&nbsp;
{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)
42 changes: 42 additions & 0 deletions src/components/Campaign/SetState/withSetState.tsx
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
33 changes: 33 additions & 0 deletions src/components/Campaign/StateTag/index.tsx
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
10 changes: 8 additions & 2 deletions src/components/Layout/Sider/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,14 @@ export const Sider: React.FC<RouteComponentProps> = ({ location }) => {

<SubMenu key="comment" title={<strong>評論</strong>}>
<Menu.Item key={PATH.COMMENT_LIST}>
<Link to={PATH.COMMENT_LIST}>
{PAGE_TITLE[PATH.COMMENT_DETAIL]}
<Link to={PATH.COMMENT_LIST}>{PAGE_TITLE[PATH.COMMENT_LIST]}</Link>
</Menu.Item>
</SubMenu>

<SubMenu key="comment" title={<strong>自由寫</strong>}>
<Menu.Item key={PATH.CAMPAIGN_LIST}>
<Link to={PATH.CAMPAIGN_LIST}>
{PAGE_TITLE[PATH.CAMPAIGN_LIST]}
</Link>
</Menu.Item>
</SubMenu>
Expand Down
Loading

0 comments on commit f720b7a

Please sign in to comment.