Skip to content

Instantly share code, notes, and snippets.

@NSLog0
Created July 31, 2019 07:28
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save NSLog0/07ef170a0815eb6624ddbbc8115b9e20 to your computer and use it in GitHub Desktop.
Save NSLog0/07ef170a0815eb6624ddbbc8115b9e20 to your computer and use it in GitHub Desktop.
import React from 'react'
import PropTypes from 'prop-types'
import { get, mapKeys, snakeCase, omitBy } from 'lodash'
import className from 'classnames'
import { connect } from 'react-redux'
import moment from 'moment'
import {
getProject,
loadProject,
clearProject,
updateProjectAutoRefillCredit,
addNotification,
getBudget,
loadBudget,
updateBudget,
getBudgetHistories,
loadBudgetHistories,
clearBudgetHistories,
getDatePicker,
updateBudgetProject,
loadBudgetReport,
} from '@redux-action'
import { FormatDateWithTime } from '@helpers'
import DateSelector from '@shared/forms/DateSelector'
import DateSelectorRange from '@shared/forms/DateSelectorRange'
import CentralHeader from '@layouts/CentralHeader'
import ProjectMenu from '@shared/ProjectMenu'
import CheckBox from '@shared/forms/CheckBox'
import Switch from '@shared/forms/Switch'
import ButtonSubmit from '@shared/forms/ButtonSubmit'
import Notification from '@shared/Notification'
import SwitchGroups from '@shared/SwitchGroups'
import SectionLoader from '@shared/SectionLoader'
import Modal from '@shared/Modal'
class ProjectBudget extends React.Component {
state = {
creditRefill: this.props.creditRefill,
topupModal: false,
downloadModal: false,
autoRefillForm: {
autoRefill: false,
autoRefillCredit: 0,
creditRefill: 0,
autoRefillNotify: false,
autoRefillEndDate: moment(),
},
budgetForm: {
status: 'deposit',
credit: 0,
},
reportForm: {
type: 'csv',
},
itemTabs: ['Day', 'Week', 'Month', 'Year'],
donwloadTabs: ['CSV', 'XLSX', 'JSON'],
filter: 'Day',
}
_getMeta = (startDate, endDate) => {
let page = get(this.props, 'budgethistories.meta.page')
if (!page || page < 0) {
page = null
}
const params = omitBy(
{
project_id: get(this.props, 'match.params.id'),
page,
start_date: startDate || get(this.props, 'datePicker.startDate'),
end_date: endDate || get(this.props, 'datePicker.endDate'),
},
(x) => !x
)
return params
}
_getFile = (res, type) => {
const url = window.URL.createObjectURL(new Blob([res.payload.data]))
const link = document.createElement('a')
link.href = url
link.setAttribute('download', `file.${type}`)
document.body.appendChild(link)
link.click()
link.remove()
}
componentDidMount = () => {
this.props.clearProject()
this.props
.loadProject({
id: get(this.props, 'match.params.id'),
})
.then(() => {
this.props
.loadBudget({ project_id: get(this.props, 'match.params.id') })
.then((res) => {
const budget = get(this.props, 'budget.data', {})
if (get(res, 'type', '').endsWith('_SUCCESS')) {
if (!budget.auto_refill_end_date) {
budget.auto_refill_end_date = moment()
} else {
budget.auto_refill_end_date = moment(
budget.auto_refill_end_date
)
}
this.setState((prevState) => ({
autoRefillForm: {
...prevState.autoRefillForm,
autoRefill: budget.auto_refill,
autoRefillCredit: budget.auto_refill_credit,
autoRefillEndDate: budget.auto_refill_end_date,
autoRefillNotify: budget.auto_refill_notify,
creditRefill: budget.credit_refill,
},
}))
}
})
})
if (!get(this.props, 'budgetHistories.isLoaded')) {
this.props.loadBudgetHistories(this._getMeta())
}
}
componentDidUpdate = (prevProps, prevState) => {
if (
get(prevProps, 'datePicker.startDate') !==
get(this.props, 'datePicker.startDate') ||
get(prevProps, 'datePicker.end') !== get(this.props, 'datePicker.end')
) {
this.props.clearBudgetHistories()
this.props.loadBudgetHistories(this._getMeta())
}
if (get(prevState, 'filter') !== get(this.state, 'filter')) {
this.onFilterByTime()
}
}
onDownloadReport = (e) => {
e.preventDefault()
const data = {
project_id: get(this.props, 'match.params.id'),
start_date: get(this.props, 'datePicker.startDate'),
end_date: get(this.props, 'datePicker.endDate'),
type: get(this.state, 'reportForm.type', 'csv'),
}
this.props.loadBudgetReport(data).then((res) => {
this._getFile(res, get(this.state, 'reportForm.type', 'csv'))
})
}
onLoadMore = () => {
this.props.loadBudgetHistories({
...this._getMeta(),
page: (get(this.props, 'budgetHistories.meta.page') || 1) + 1,
})
}
onFilterByTime = () => {
let startDate = moment()
.subtract(1, this.state.filter)
.format('YYYY-MM-DD')
let endDate = moment().format('YYYY-MM-DD')
this.props.clearBudgetHistories()
this.props.loadBudgetHistories(
omitBy(
{
...this._getMeta(startDate, endDate),
page: 1,
},
(x) => !x
)
)
}
onTopupSubmit = (e) => {
e.preventDefault()
let data = Object.assign({}, this.state.budgetForm)
data = { ...data, ...{ project_id: get(this.props, 'match.params.id') } }
this.props.updateBudgetProject(data).then((res) => {
if (get(res, 'type', '').endsWith('_SUCCESS')) {
this.onToggleModal('topupModal')
this.props.addNotification({
message: `${this.state.budgetForm.status} success`,
type: Notification.NOTIFICATION_TYPES.success,
})
this.props.loadProject({ id: get(this.props, 'match.params.id') })
}
})
}
onToggleModal = (name) => (e) => {
this.setState((prevState) => ({
[name]: !prevState[name],
}))
}
onFilterTabChange = (val) => {
this.setState({ filter: val })
}
onTabReportChange = (val) => {
this.setState((prevState) => ({
reportForm: {
...prevState.reportForm,
type: val.toLowerCase(),
},
}))
}
onToggleAutoRefill = (e) => {
this.setState((prevState) => ({
autoRefillForm: {
...prevState.autoRefillForm,
autoRefill: !prevState.autoRefillForm.autoRefill,
},
}))
}
onAutoRefillChange = (e) => {
const type = e.target.type
const name = e.target.name
const value = type === 'checkbox' ? e.target.checked : e.target.value
this.setState((prevState) => ({
autoRefillForm: {
...prevState.autoRefillForm,
[name]: value,
},
}))
}
onCreditChange = (e) => {
const name = e.target.name
const value = e.target.value
this.setState((prevState) => ({
budgetForm: {
...prevState.budgetForm,
[name]: value,
},
}))
}
onSubmitRefill = (e) => {
e.preventDefault()
const {
autoRefill,
autoRefillCredit,
creditRefill,
autoRefillNotify,
autoRefillEndDate,
} = this.state.autoRefillForm
let data = { project_id: get(this.props, 'match.params.id') }
if (autoRefill) {
data = {
...data,
...{
autoRefill,
autoRefillCredit,
creditRefill,
autoRefillNotify,
autoRefillEndDate,
},
}
} else {
data = { ...data, ...{ autoRefill } }
}
data = mapKeys(data, (value, key) => snakeCase(key))
this.props.updateBudget(data).then((res) => {
if (get(res, 'type', '').endsWith('_SUCCESS')) {
this.props.addNotification({
message: `Auto refill credit has been ${
autoRefill ? 'enabled' : 'disabled'
}.`,
type: Notification.NOTIFICATION_TYPES.success,
})
}
})
}
onUpdateAutoRefillCredit = (e) => {
e.preventDefault()
this.props
.updateProjectAutoRefillCredit({
project_id: get(this.props, 'project.data.id'),
auto_refill: true,
auto_refill_credit: get(this.state, 'form.autoRefillCredit'),
credit_refill: get(this.state, 'form.creditRefill'),
})
.then(this._autoRefillCallback)
}
onClickDate = (autoRefillEndDate) => () => {
this.setState((prevState) => ({
autoRefillForm: {
...prevState.autoRefillForm,
autoRefillEndDate,
},
}))
}
render = () => {
return (
<React.Fragment>
<CentralHeader
header={get(this.props, 'project.data.name')}
breadcrumbs={[
{ label: 'Projects', to: '/app/projects' },
{ label: get(this.props, 'project.data.name') },
{ label: 'Input' },
]}
/>
<div className="Project__menu-wrapper">
<ProjectMenu
url={window.location.pathname}
id={get(this.props, 'project.data.id')}
/>
<button className="Button Button--green">launch project</button>
</div>
<div className="Central__content">
<div className="Central__content__header">
<h3 className="Central__content__h3">Project Budget</h3>
</div>
<div className="Project__detail Project__detail--budget-layout">
<div className="Item">
<div className="Item__wrapper">
<label className="Item__title">Remaining Credit</label>
<p className="Item__content">
<strong className="Item__content--bold">
{(
get(this.props, 'project.data.credit') || 0
).toLocaleString()}
</strong>{' '}
credits
</p>
</div>
</div>
<div className="Item">
<div className="Item__wrapper">
<label className="Item__title">Remaining Tasks</label>
<p className="Item__content">
<strong className="Item__content--bold">
{(
(get(this.props, 'project.data.tasks.unprocessed') || 0) +
(get(this.props, 'project.data.tasks.processing') || 0)
).toLocaleString()}
</strong>{' '}
tasks
</p>
</div>
</div>
<div className="Item">
<div className="Item__wrapper">
<label className="Item__title">Credit Per Task</label>
<p className="Item__content">
<strong className="Item__content--bold">
{(
get(this.props, 'project.data.credit_rate') || 0
).toLocaleString()}
</strong>{' '}
credits
</p>
</div>
</div>
<div className="Item">
<div className="Item__wrapper">
<label className="Item__title">Auto Refill</label>
{get(this.props, 'project.data.auto_refill', false)
? `Enabled Enabled Auto Refill ${get(
this.props,
'project.data.auto_refill_credit',
0
)} Credits`
: 'Diabled'}
</div>
</div>
</div>
<div className="ProjectBudget">
<div className="ProjectBudget__left">
<div className="Central__content__header ProjectBudget__left__header">
<h3 className="Central__content__h3 Project__content-title">
Credits Transaction
</h3>
<button
className="Button Button--gray"
onClick={this.onToggleModal('downloadModal')}
>
Download Report
</button>
</div>
<div className="Central__content__section">
<div className="ProjectBudget__left__filter">
<SwitchGroups
items={this.state.itemTabs}
onTabChange={this.onFilterTabChange}
/>
<DateSelectorRange
right
withIcon
/>
</div>
{(() => {
const data = get(this.props, 'budgetHistories.data', [])
return (
<SectionLoader
isLoading={get(this.props, 'budgetHistories.isLoading')}
isLoaded={get(this.props, 'budgetHistories.isLoaded')}
isError={get(this.props, 'budgetHistories.isError')}
isEmpty={data.length === 0}
alwaysDisplayChildren
>
<div className="Table">
{(() => {
const headers = [
'Transaction Date',
'Credits',
'Amount',
'Created by',
]
return (
<React.Fragment>
<div className="Table__tr">
{headers.map((x, i) => (
<div
key={i}
className="Table__td Table__td--th"
>
{(() => {
const sortBy = get(
this.props,
'budgetHistories.meta.sort_by'
)
const sortDirection = get(
this.props,
'budgetHistorie.meta.sort_direction'
)
if (x === 'Transaction Date') {
return [
<a
className={className({
Table__td__caret:
sortBy === 'created_at',
'Table__td__caret--up':
sortBy === 'created_at' &&
sortDirection === 'desc',
})}
onClick={() => {
this.onToggleSorting(x)
}}
>
{x}
</a>,
]
} else {
return x
}
})()}
</div>
))}
</div>
</React.Fragment>
)
})()}
<React.Fragment>
{data.map((x, i) => (
<div
className="Table__tr"
key={i}
data-x={x}
>
<div className="Table__td">
{FormatDateWithTime(get(x, 'created_at'))}
</div>
<div className="Table__td">
{get(x, 'credit')}
</div>
<div className="Table__td">
{get(x, 'total_credit')}
</div>
<div className="Table__td">
{get(x, 'created_by')}
</div>
</div>
))}
</React.Fragment>
</div>
{!get(this.props, 'budgetHistories.isLoading') &&
get(this.props, 'budgetHistories.isLoaded') &&
get(this.props, 'budgetHistories.data', []).length <
get(this.props, 'budgetHistories.meta.total', 0) && (
<a
className="LoadMore"
onClick={this.onLoadMore}
>
Load more
</a>
)}
</SectionLoader>
)
})()}
</div>
</div>
<div className="ProjectBudget__right">
<div className="Central__content__header">
<h3 className="Central__content__h3 Project__content-title">
Budget Setting
</h3>
</div>
<div className="Central__content__section">
<form className="ProjectBudget__form">
<section>
<div>
<label className="ProjectBudget__form__header">
Budget
</label>
</div>
<p>
I want to credits
<select
id="status"
name="status"
onChange={this.onCreditChange}
value={this.state.budgetForm.status}
>
<option value="deposit">Add</option>
<option value="withdraw">Withdraw</option>
</select>
to this project with
</p>
<input
type="text"
className="Input"
onChange={this.onCreditChange}
value={this.state.budgetForm.credit}
name="credit"
/>
</section>
<div className="ProjectBudget__form__button">
<button
className="Button Button--green"
type="button"
onClick={this.onToggleModal('topupModal')}
>
Submit
</button>
</div>
</form>
</div>
<div className="Central__content__section">
<form
className="ProjectBudget__form"
onSubmit={this.onSubmitRefill}
>
<section>
<div className="ProjectBudget__form__switch">
<label className="ProjectBudget__form__header">
Auto Refill Credits
</label>
<Switch
meduim
checked={this.state.autoRefillForm.autoRefill}
onInputChange={this.onToggleAutoRefill}
mode={'inline'}
/>
</div>
<p>When credits are less than…</p>
<input
type="text"
className="Input"
value={this.state.autoRefillForm.autoRefillCredit}
onChange={this.onAutoRefillChange}
name="autoRefillCredit"
disabled={!this.state.autoRefillForm.autoRefill}
/>
<p>Then I want to refill…</p>
<input
type="text"
className="Input"
value={this.state.autoRefillForm.creditRefill}
onChange={this.onAutoRefillChange}
name="creditRefill"
disabled={!this.state.autoRefillForm.autoRefill}
/>
<p>End Date:</p>
<DateSelector
left
onClickDate={this.onClickDate}
selectedDate={this.state.autoRefillForm.autoRefillEndDate}
/>
<label className="ProjectBudget__form__check-notify">
<input
disabled={!this.state.autoRefillForm.autoRefill}
type="checkbox"
defaultChecked={
this.state.autoRefillForm.autoRefillNotify
}
onChange={this.onAutoRefillChange}
name="autoRefillNotify"
/>
<CheckBox />
Notify me when refilled credits
</label>
</section>
<div className="ProjectBudget__form__button">
<ButtonSubmit
label="Submit"
isLoading={get(this.props, 'project.isLoading')}
/>
</div>
</form>
</div>
</div>
</div>
</div>
<Modal
isActive={this.state.topupModal}
toggleFunction={this.onToggleModal('topupModal')}
>
<form onSubmit={this.onTopupSubmit}>
<h3 className="Modal__h3 ProjectBudget__modal__header">{`${this.state.budgetForm.status} Budget`}</h3>
<section>
<p>
Would you like to{' '}
{`${this.state.budgetForm.status}
${this.state.budgetForm.credit}`}{' '}
credits to this project
</p>
</section>
<div className="Modal__button ProjectBudget__modal__button">
<button
className="Button Button--gray"
onClick={this.onToggleModal('topupModal')}
type="button"
>
Discard
</button>
<ButtonSubmit
label="Confirm"
isLoading={get(this.props, 'project.isLoading')}
/>
</div>
</form>
</Modal>
<Modal
isActive={this.state.downloadModal}
toggleFunction={this.onToggleModal('downloadModal')}
>
<form onSubmit={this.onDownloadReport}>
<h3 className="Modal__h3 ProjectBudget__modal__header">
Download Report
</h3>
<section className="ProjectBudget__modal__content-center">
<SwitchGroups
center
full
items={this.state.donwloadTabs}
onTabChange={this.onTabReportChange}
/>
<p className="ProjectBudget__modal__p">
<span>Select Date</span>
</p>
<DateSelectorRange withIcon />
</section>
<div className="Modal__button ProjectBudget__modal__button">
<ButtonSubmit
label="Download"
isLoading={get(this.props, 'project.isLoading')}
/>
</div>
</form>
</Modal>
</React.Fragment>
)
}
}
const mapStateToProps = (state) => ({
project: getProject(state),
budget: getBudget(state),
budgetHistories: getBudgetHistories(state),
datePicker: getDatePicker(state),
})
const mapDispatchToProps = (dispatch) => ({
loadProject: (params) => dispatch(loadProject(params)),
clearProject: (params) => dispatch(clearProject(params)),
updateProjectAutoRefillCredit: (params) =>
dispatch(updateProjectAutoRefillCredit(params)),
addNotification: (params) => dispatch(addNotification(params)),
loadBudget: (params) => dispatch(loadBudget(params)),
updateBudget: (params) => dispatch(updateBudget(params)),
loadBudgetHistories: (params) => dispatch(loadBudgetHistories(params)),
clearBudgetHistories: (params) => dispatch(clearBudgetHistories(params)),
updateBudgetProject: (params) => dispatch(updateBudgetProject(params)),
loadBudgetReport: (params) => dispatch(loadBudgetReport(params)),
})
export default connect(
mapStateToProps,
mapDispatchToProps
)(ProjectBudget)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment