Files
mocoapp-browser-extension/src/js/components/App.js
Manuel Bouza 81c7d0ca5d fix/asana-refactor (#14)
* Add packages eslint-plugin-prettier and eslint-config-prettier

These packages add better code formatting support in VS Code

* Fix code styles

* Update projectId query selector for asana service

* Extract constants to own computed getter methods

* Update changelog, bump version
2019-04-10 07:45:05 +02:00

221 lines
5.8 KiB
JavaScript

import React, { Component } from "react"
import PropTypes from "prop-types"
import Spinner from "components/Spinner"
import Form from "components/Form"
import Calendar from "components/Calendar"
import { observable, computed } from "mobx"
import { Observer, observer } from "mobx-react"
import { Spring, animated, config } from "react-spring/renderprops"
import {
ERROR_UNKNOWN,
ERROR_UNAUTHORIZED,
ERROR_UPGRADE_REQUIRED,
extractAndSetTag,
findProjectByValue,
findProjectByIdentifier,
findTask,
formatDate,
} from "utils"
import InvalidConfigurationError from "components/Errors/InvalidConfigurationError"
import UpgradeRequiredError from "components/Errors/UpgradeRequiredError"
import UnknownError from "components/Errors/UnknownError"
import { parse } from "date-fns"
import Header from "./shared/Header"
import { head } from "lodash"
import TimeInputParser from "utils/TimeInputParser"
@observer
class App extends Component {
static propTypes = {
loading: PropTypes.bool,
service: PropTypes.shape({
id: PropTypes.string,
url: PropTypes.string,
name: PropTypes.string,
description: PropTypes.string,
projectId: PropTypes.string,
taskId: PropTypes.string,
}),
subdomain: PropTypes.string,
activities: PropTypes.array,
schedules: PropTypes.array,
projects: PropTypes.array,
lastProjectId: PropTypes.number,
lastTaskId: PropTypes.number,
roundTimeEntries: PropTypes.bool,
fromDate: PropTypes.string,
toDate: PropTypes.string,
errorType: PropTypes.string,
errorMessage: PropTypes.string,
}
static defaultProps = {
activities: [],
schedules: [],
projects: [],
roundTimeEntries: false,
}
@observable changeset = {}
@observable formErrors = {}
@computed get project() {
const { service, projects, lastProjectId } = this.props
return (
findProjectByValue(this.changeset.assignment_id)(projects) ||
findProjectByIdentifier(service?.projectId)(projects) ||
findProjectByValue(Number(lastProjectId))(projects) ||
head(projects)
)
}
@computed get task() {
const { service, lastTaskId } = this.props
return (
findTask(this.changeset.task_id || service?.taskId || lastTaskId)(this.project) ||
head(this.project?.tasks)
)
}
@computed get billable() {
return /\(.+\)/.test(this.changeset.hours) === true ? false : !!this.task?.billable
}
@computed get changesetWithDefaults() {
const { service } = this.props
const defaults = {
remote_service: service?.name,
remote_id: service?.id,
remote_url: service?.url,
date: formatDate(new Date()),
assignment_id: this.project?.value,
task_id: this.task?.value,
billable: this.billable,
hours: "",
seconds: this.changeset.hours && new TimeInputParser(this.changeset.hours).parseSeconds(),
description: service?.description,
tag: "",
}
return { ...defaults, ...this.changeset }
}
componentDidMount() {
window.addEventListener("keydown", this.handleKeyDown)
chrome.runtime.onMessage.addListener(this.handleSetFormErrors)
}
componentWillUnmount() {
window.removeEventListener("keydown", this.handleKeyDown)
chrome.runtime.onMessage.removeListener(this.handleSetFormErrors)
}
handleChange = event => {
const { projects } = this.props
const {
target: { name, value },
} = event
this.changeset[name] = value
if (name === "assignment_id") {
const project = findProjectByValue(value)(projects)
this.changeset.task_id = head(project?.tasks)?.value
}
}
handleSelectDate = date => {
this.changeset.date = formatDate(date)
}
handleSubmit = event => {
event.preventDefault()
const { service } = this.props
chrome.runtime.sendMessage({
type: "createActivity",
payload: {
activity: extractAndSetTag(this.changesetWithDefaults),
service,
},
})
}
handleKeyDown = event => {
if (event.keyCode === 27) {
event.stopPropagation()
chrome.runtime.sendMessage({ type: "closePopup" })
}
}
handleSetFormErrors = ({ type, payload }) => {
if (type === "setFormErrors") {
this.formErrors = payload
}
}
render() {
const {
loading,
subdomain,
projects,
activities,
schedules,
fromDate,
toDate,
errorType,
errorMessage,
} = this.props
if (loading) {
return <Spinner />
}
if (errorType === ERROR_UNAUTHORIZED) {
return <InvalidConfigurationError />
}
if (errorType === ERROR_UPGRADE_REQUIRED) {
return <UpgradeRequiredError />
}
if (errorType === ERROR_UNKNOWN) {
return <UnknownError message={errorMessage} />
}
return (
<Spring native from={{ opacity: 0 }} to={{ opacity: 1 }} config={config.stiff}>
{props => (
<animated.div className="moco-bx-app-container" style={props}>
<Header subdomain={subdomain} />
<Observer>
{() => (
<>
<Calendar
fromDate={parse(fromDate)}
toDate={parse(toDate)}
activities={activities}
schedules={schedules}
selectedDate={new Date(this.changesetWithDefaults.date)}
onChange={this.handleSelectDate}
/>
<Form
changeset={this.changesetWithDefaults}
projects={projects}
errors={this.formErrors}
onChange={this.handleChange}
onSubmit={this.handleSubmit}
/>
</>
)}
</Observer>
</animated.div>
)}
</Spring>
)
}
}
export default App