2018-10-25 15:51:47 +02:00
|
|
|
import React from "react"
|
|
|
|
import { Link } from "react-router-dom"
|
2019-03-09 08:46:47 +01:00
|
|
|
import {
|
|
|
|
Row,
|
|
|
|
Col,
|
|
|
|
Modal,
|
|
|
|
Form,
|
|
|
|
Input,
|
|
|
|
Select,
|
|
|
|
Button,
|
|
|
|
Table,
|
|
|
|
Icon,
|
|
|
|
Tooltip,
|
|
|
|
Tag,
|
|
|
|
Popconfirm,
|
|
|
|
Spin,
|
|
|
|
notification
|
|
|
|
} from "antd"
|
2018-10-25 15:51:47 +02:00
|
|
|
|
|
|
|
import Utils from "./utils"
|
|
|
|
import * as cs from "./constants"
|
|
|
|
|
2019-01-04 08:10:10 +01:00
|
|
|
const tagColors = {
|
2019-03-09 08:46:47 +01:00
|
|
|
private: "orange",
|
|
|
|
public: "green"
|
2019-01-04 08:10:10 +01:00
|
|
|
}
|
|
|
|
|
2018-10-25 15:51:47 +02:00
|
|
|
class CreateFormDef extends React.PureComponent {
|
2019-03-09 08:46:47 +01:00
|
|
|
state = {
|
|
|
|
confirmDirty: false,
|
|
|
|
modalWaiting: false
|
|
|
|
}
|
2018-10-25 15:51:47 +02:00
|
|
|
|
2019-03-09 08:46:47 +01:00
|
|
|
// Handle create / edit form submission.
|
|
|
|
handleSubmit = e => {
|
|
|
|
e.preventDefault()
|
|
|
|
this.props.form.validateFields((err, values) => {
|
|
|
|
if (err) {
|
|
|
|
return
|
|
|
|
}
|
2018-10-25 15:51:47 +02:00
|
|
|
|
2019-03-09 08:46:47 +01:00
|
|
|
this.setState({ modalWaiting: true })
|
|
|
|
if (this.props.formType === cs.FormCreate) {
|
|
|
|
// Create a new list.
|
|
|
|
this.props
|
|
|
|
.modelRequest(
|
|
|
|
cs.ModelLists,
|
|
|
|
cs.Routes.CreateList,
|
|
|
|
cs.MethodPost,
|
|
|
|
values
|
|
|
|
)
|
|
|
|
.then(() => {
|
|
|
|
notification["success"]({
|
|
|
|
placement: cs.MsgPosition,
|
|
|
|
message: "List created",
|
|
|
|
description: `"${values["name"]}" created`
|
|
|
|
})
|
|
|
|
this.props.fetchRecords()
|
|
|
|
this.props.onClose()
|
|
|
|
this.setState({ modalWaiting: false })
|
|
|
|
})
|
|
|
|
.catch(e => {
|
|
|
|
notification["error"]({ message: "Error", description: e.message })
|
|
|
|
this.setState({ modalWaiting: false })
|
|
|
|
})
|
|
|
|
} else {
|
|
|
|
// Edit a list.
|
|
|
|
this.props
|
|
|
|
.modelRequest(cs.ModelLists, cs.Routes.UpdateList, cs.MethodPut, {
|
|
|
|
...values,
|
|
|
|
id: this.props.record.id
|
|
|
|
})
|
|
|
|
.then(() => {
|
|
|
|
notification["success"]({
|
|
|
|
placement: cs.MsgPosition,
|
|
|
|
message: "List modified",
|
|
|
|
description: `"${values["name"]}" modified`
|
|
|
|
})
|
|
|
|
this.props.fetchRecords()
|
|
|
|
this.props.onClose()
|
|
|
|
this.setState({ modalWaiting: false })
|
|
|
|
})
|
|
|
|
.catch(e => {
|
|
|
|
notification["error"]({
|
|
|
|
placement: cs.MsgPosition,
|
|
|
|
message: "Error",
|
|
|
|
description: e.message
|
|
|
|
})
|
|
|
|
this.setState({ modalWaiting: false })
|
|
|
|
})
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
2019-01-04 08:10:10 +01:00
|
|
|
|
2019-03-09 08:46:47 +01:00
|
|
|
modalTitle(formType, record) {
|
|
|
|
if (formType === cs.FormCreate) {
|
|
|
|
return "Create a list"
|
2019-01-04 08:10:10 +01:00
|
|
|
}
|
|
|
|
|
2019-03-09 08:46:47 +01:00
|
|
|
return (
|
|
|
|
<div>
|
|
|
|
<Tag
|
|
|
|
color={
|
|
|
|
tagColors.hasOwnProperty(record.type) ? tagColors[record.type] : ""
|
|
|
|
}
|
|
|
|
>
|
|
|
|
{record.type}
|
|
|
|
</Tag>{" "}
|
|
|
|
{record.name}
|
|
|
|
<br />
|
|
|
|
<span className="text-tiny text-grey">
|
|
|
|
ID {record.id} / UUID {record.uuid}
|
|
|
|
</span>
|
|
|
|
</div>
|
|
|
|
)
|
|
|
|
}
|
2018-10-25 15:51:47 +02:00
|
|
|
|
2019-03-09 08:46:47 +01:00
|
|
|
render() {
|
|
|
|
const { formType, record, onClose } = this.props
|
|
|
|
const { getFieldDecorator } = this.props.form
|
2018-10-25 15:51:47 +02:00
|
|
|
|
2019-03-09 08:46:47 +01:00
|
|
|
const formItemLayout = {
|
|
|
|
labelCol: { xs: { span: 16 }, sm: { span: 4 } },
|
|
|
|
wrapperCol: { xs: { span: 16 }, sm: { span: 18 } }
|
|
|
|
}
|
2018-10-25 15:51:47 +02:00
|
|
|
|
2019-03-09 08:46:47 +01:00
|
|
|
if (formType === null) {
|
|
|
|
return null
|
2018-10-25 15:51:47 +02:00
|
|
|
}
|
2019-03-09 08:46:47 +01:00
|
|
|
|
|
|
|
return (
|
|
|
|
<Modal
|
|
|
|
visible={true}
|
|
|
|
title={this.modalTitle(this.state.form, record)}
|
|
|
|
okText={this.state.form === cs.FormCreate ? "Create" : "Save"}
|
|
|
|
confirmLoading={this.state.modalWaiting}
|
|
|
|
onCancel={onClose}
|
|
|
|
onOk={this.handleSubmit}
|
|
|
|
>
|
|
|
|
<div id="modal-alert-container" />
|
|
|
|
|
|
|
|
<Spin
|
|
|
|
spinning={this.props.reqStates[cs.ModelLists] === cs.StatePending}
|
|
|
|
>
|
|
|
|
<Form onSubmit={this.handleSubmit}>
|
|
|
|
<Form.Item {...formItemLayout} label="Name">
|
|
|
|
{getFieldDecorator("name", {
|
|
|
|
initialValue: record.name,
|
|
|
|
rules: [{ required: true }]
|
2019-03-28 13:34:27 +01:00
|
|
|
})(<Input autoFocus maxLength={200} />)}
|
2019-03-09 08:46:47 +01:00
|
|
|
</Form.Item>
|
|
|
|
<Form.Item
|
|
|
|
{...formItemLayout}
|
|
|
|
name="type"
|
|
|
|
label="Type"
|
|
|
|
extra="Public lists are open to the world to subscribe"
|
|
|
|
>
|
|
|
|
{getFieldDecorator("type", {
|
|
|
|
initialValue: record.type ? record.type : "private",
|
|
|
|
rules: [{ required: true }]
|
|
|
|
})(
|
|
|
|
<Select style={{ maxWidth: 120 }}>
|
|
|
|
<Select.Option value="private">Private</Select.Option>
|
|
|
|
<Select.Option value="public">Public</Select.Option>
|
|
|
|
</Select>
|
|
|
|
)}
|
|
|
|
</Form.Item>
|
|
|
|
<Form.Item
|
|
|
|
{...formItemLayout}
|
|
|
|
label="Tags"
|
|
|
|
extra="Hit Enter after typing a word to add multiple tags"
|
|
|
|
>
|
|
|
|
{getFieldDecorator("tags", { initialValue: record.tags })(
|
|
|
|
<Select mode="tags" />
|
|
|
|
)}
|
|
|
|
</Form.Item>
|
|
|
|
</Form>
|
|
|
|
</Spin>
|
|
|
|
</Modal>
|
|
|
|
)
|
|
|
|
}
|
2018-10-25 15:51:47 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
const CreateForm = Form.create()(CreateFormDef)
|
|
|
|
|
|
|
|
class Lists extends React.PureComponent {
|
2019-05-14 13:11:05 +02:00
|
|
|
defaultPerPage = 20
|
2019-03-09 08:46:47 +01:00
|
|
|
state = {
|
|
|
|
formType: null,
|
2019-05-14 13:11:05 +02:00
|
|
|
record: {},
|
|
|
|
queryParams: {}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Pagination config.
|
|
|
|
paginationOptions = {
|
|
|
|
hideOnSinglePage: false,
|
|
|
|
showSizeChanger: true,
|
|
|
|
showQuickJumper: true,
|
|
|
|
defaultPageSize: this.defaultPerPage,
|
|
|
|
pageSizeOptions: ["20", "50", "70", "100"],
|
|
|
|
position: "both",
|
|
|
|
showTotal: (total, range) => `${range[0]} to ${range[1]} of ${total}`,
|
|
|
|
onChange: (page, perPage) => {
|
|
|
|
this.fetchRecords({ page: page, per_page: perPage })
|
|
|
|
},
|
|
|
|
onShowSizeChange: (page, perPage) => {
|
|
|
|
this.fetchRecords({ page: page, per_page: perPage })
|
|
|
|
}
|
2019-03-09 08:46:47 +01:00
|
|
|
}
|
2018-10-25 15:51:47 +02:00
|
|
|
|
2019-03-09 08:46:47 +01:00
|
|
|
constructor(props) {
|
|
|
|
super(props)
|
2018-10-25 15:51:47 +02:00
|
|
|
|
2019-03-09 08:46:47 +01:00
|
|
|
this.columns = [
|
|
|
|
{
|
|
|
|
title: "Name",
|
|
|
|
dataIndex: "name",
|
|
|
|
sorter: true,
|
|
|
|
width: "40%",
|
|
|
|
render: (text, record) => {
|
|
|
|
const out = []
|
|
|
|
out.push(
|
|
|
|
<div className="name" key={`name-${record.id}`}>
|
2019-03-28 13:34:27 +01:00
|
|
|
<a role="button" onClick={() => this.handleShowEditForm(record)}>
|
|
|
|
{text}
|
|
|
|
</a>
|
2019-03-09 08:46:47 +01:00
|
|
|
</div>
|
|
|
|
)
|
2018-10-25 15:51:47 +02:00
|
|
|
|
2019-03-09 08:46:47 +01:00
|
|
|
if (record.tags.length > 0) {
|
|
|
|
for (let i = 0; i < record.tags.length; i++) {
|
|
|
|
out.push(<Tag key={`tag-${i}`}>{record.tags[i]}</Tag>)
|
2018-10-25 15:51:47 +02:00
|
|
|
}
|
2019-03-09 08:46:47 +01:00
|
|
|
}
|
2018-10-25 15:51:47 +02:00
|
|
|
|
2019-03-09 08:46:47 +01:00
|
|
|
return out
|
|
|
|
}
|
|
|
|
},
|
|
|
|
{
|
|
|
|
title: "Type",
|
|
|
|
dataIndex: "type",
|
|
|
|
width: "10%",
|
|
|
|
render: (type, _) => {
|
|
|
|
let color = type === "private" ? "orange" : "green"
|
|
|
|
return <Tag color={color}>{type}</Tag>
|
|
|
|
}
|
|
|
|
},
|
|
|
|
{
|
|
|
|
title: "Subscribers",
|
|
|
|
dataIndex: "subscriber_count",
|
|
|
|
width: "15%",
|
|
|
|
align: "center",
|
|
|
|
render: (text, record) => {
|
|
|
|
return (
|
|
|
|
<div className="name" key={`name-${record.id}`}>
|
|
|
|
<Link to={`/subscribers/lists/${record.id}`}>{text}</Link>
|
|
|
|
</div>
|
|
|
|
)
|
|
|
|
}
|
|
|
|
},
|
|
|
|
{
|
|
|
|
title: "Created",
|
|
|
|
dataIndex: "created_at",
|
|
|
|
render: (date, _) => {
|
|
|
|
return Utils.DateString(date)
|
|
|
|
}
|
|
|
|
},
|
|
|
|
{
|
|
|
|
title: "Updated",
|
|
|
|
dataIndex: "updated_at",
|
|
|
|
render: (date, _) => {
|
|
|
|
return Utils.DateString(date)
|
|
|
|
}
|
|
|
|
},
|
|
|
|
{
|
|
|
|
title: "",
|
|
|
|
dataIndex: "actions",
|
|
|
|
width: "10%",
|
|
|
|
render: (text, record) => {
|
|
|
|
return (
|
|
|
|
<div className="actions">
|
|
|
|
<Tooltip title="Send a campaign">
|
2019-03-28 13:48:26 +01:00
|
|
|
<Link to={`/campaigns/new?list_id=${record.id}`}>
|
2019-03-09 08:46:47 +01:00
|
|
|
<Icon type="rocket" />
|
2019-03-28 13:48:26 +01:00
|
|
|
</Link>
|
2019-03-09 08:46:47 +01:00
|
|
|
</Tooltip>
|
|
|
|
<Tooltip title="Edit list">
|
|
|
|
<a
|
|
|
|
role="button"
|
|
|
|
onClick={() => this.handleShowEditForm(record)}
|
|
|
|
>
|
|
|
|
<Icon type="edit" />
|
|
|
|
</a>
|
|
|
|
</Tooltip>
|
|
|
|
<Popconfirm
|
|
|
|
title="Are you sure?"
|
|
|
|
onConfirm={() => this.deleteRecord(record)}
|
|
|
|
>
|
|
|
|
<Tooltip title="Delete list" placement="bottom">
|
|
|
|
<a role="button">
|
|
|
|
<Icon type="delete" />
|
|
|
|
</a>
|
|
|
|
</Tooltip>
|
|
|
|
</Popconfirm>
|
|
|
|
</div>
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
]
|
|
|
|
}
|
2018-10-25 15:51:47 +02:00
|
|
|
|
2019-03-09 08:46:47 +01:00
|
|
|
componentDidMount() {
|
|
|
|
this.props.pageTitle("Lists")
|
|
|
|
this.fetchRecords()
|
|
|
|
}
|
2018-10-25 15:51:47 +02:00
|
|
|
|
2019-05-14 13:11:05 +02:00
|
|
|
fetchRecords = params => {
|
|
|
|
let qParams = {
|
|
|
|
page: this.state.queryParams.page,
|
|
|
|
per_page: this.state.queryParams.per_page
|
|
|
|
}
|
|
|
|
if (params) {
|
|
|
|
qParams = { ...qParams, ...params }
|
|
|
|
}
|
|
|
|
|
|
|
|
this.props
|
|
|
|
.modelRequest(cs.ModelLists, cs.Routes.GetLists, cs.MethodGet, qParams)
|
|
|
|
.then(() => {
|
|
|
|
this.setState({
|
|
|
|
queryParams: {
|
|
|
|
...this.state.queryParams,
|
|
|
|
total: this.props.data[cs.ModelLists].total,
|
|
|
|
perPage: this.props.data[cs.ModelLists].per_page,
|
|
|
|
page: this.props.data[cs.ModelLists].page,
|
|
|
|
query: this.props.data[cs.ModelLists].query
|
|
|
|
}
|
|
|
|
})
|
|
|
|
})
|
2019-03-09 08:46:47 +01:00
|
|
|
}
|
2018-10-25 15:51:47 +02:00
|
|
|
|
2019-03-09 08:46:47 +01:00
|
|
|
deleteRecord = record => {
|
|
|
|
this.props
|
|
|
|
.modelRequest(cs.ModelLists, cs.Routes.DeleteList, cs.MethodDelete, {
|
|
|
|
id: record.id
|
|
|
|
})
|
|
|
|
.then(() => {
|
|
|
|
notification["success"]({
|
|
|
|
placement: cs.MsgPosition,
|
|
|
|
message: "List deleted",
|
|
|
|
description: `"${record.name}" deleted`
|
|
|
|
})
|
2018-10-25 15:51:47 +02:00
|
|
|
|
2019-03-09 08:46:47 +01:00
|
|
|
// Reload the table.
|
|
|
|
this.fetchRecords()
|
|
|
|
})
|
|
|
|
.catch(e => {
|
|
|
|
notification["error"]({
|
|
|
|
placement: cs.MsgPosition,
|
|
|
|
message: "Error",
|
|
|
|
description: e.message
|
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|
2018-10-25 15:51:47 +02:00
|
|
|
|
2019-03-09 08:46:47 +01:00
|
|
|
handleHideForm = () => {
|
|
|
|
this.setState({ formType: null })
|
|
|
|
}
|
2018-10-25 15:51:47 +02:00
|
|
|
|
2019-03-09 08:46:47 +01:00
|
|
|
handleShowCreateForm = () => {
|
|
|
|
this.setState({ formType: cs.FormCreate, record: {} })
|
|
|
|
}
|
2018-10-25 15:51:47 +02:00
|
|
|
|
2019-03-09 08:46:47 +01:00
|
|
|
handleShowEditForm = record => {
|
|
|
|
this.setState({ formType: cs.FormEdit, record: record })
|
|
|
|
}
|
2018-10-25 15:51:47 +02:00
|
|
|
|
2019-03-09 08:46:47 +01:00
|
|
|
render() {
|
|
|
|
return (
|
|
|
|
<section className="content">
|
|
|
|
<Row>
|
2019-08-04 17:50:28 +02:00
|
|
|
<Col xs={12} sm={18}>
|
2019-05-14 13:11:05 +02:00
|
|
|
<h1>Lists ({this.props.data[cs.ModelLists].total}) </h1>
|
2019-03-09 08:46:47 +01:00
|
|
|
</Col>
|
2019-08-04 17:50:28 +02:00
|
|
|
<Col xs={12} sm={6} className="align-right">
|
2019-03-09 08:46:47 +01:00
|
|
|
<Button
|
|
|
|
type="primary"
|
|
|
|
icon="plus"
|
|
|
|
onClick={this.handleShowCreateForm}
|
|
|
|
>
|
|
|
|
Create list
|
|
|
|
</Button>
|
|
|
|
</Col>
|
|
|
|
</Row>
|
|
|
|
<br />
|
|
|
|
|
|
|
|
<Table
|
|
|
|
className="lists"
|
|
|
|
columns={this.columns}
|
|
|
|
rowKey={record => record.uuid}
|
2019-05-14 13:11:05 +02:00
|
|
|
dataSource={(() => {
|
|
|
|
if (
|
|
|
|
!this.props.data[cs.ModelLists] ||
|
|
|
|
!this.props.data[cs.ModelLists].hasOwnProperty("results")
|
|
|
|
) {
|
|
|
|
return []
|
|
|
|
}
|
|
|
|
return this.props.data[cs.ModelLists].results
|
|
|
|
})()}
|
2019-03-09 08:46:47 +01:00
|
|
|
loading={this.props.reqStates[cs.ModelLists] !== cs.StateDone}
|
2019-05-14 13:11:05 +02:00
|
|
|
pagination={{ ...this.paginationOptions, ...this.state.queryParams }}
|
2019-03-09 08:46:47 +01:00
|
|
|
/>
|
|
|
|
|
|
|
|
<CreateForm
|
|
|
|
{...this.props}
|
|
|
|
formType={this.state.formType}
|
|
|
|
record={this.state.record}
|
|
|
|
onClose={this.handleHideForm}
|
|
|
|
fetchRecords={this.fetchRecords}
|
|
|
|
/>
|
|
|
|
</section>
|
|
|
|
)
|
|
|
|
}
|
2018-10-25 15:51:47 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
export default Lists
|