Files
mocoapp-browser-extension/src/js/components/App.js
2020-09-10 14:08:30 +02:00

245 lines
6.6 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 TimerView from "components/App/TimerView"
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,
defaultTask,
formatDate,
} from "utils"
import { parseISO } from "date-fns"
import InvalidConfigurationError from "components/Errors/InvalidConfigurationError"
import UpgradeRequiredError from "components/Errors/UpgradeRequiredError"
import UnknownError from "components/Errors/UnknownError"
import Header from "./shared/Header"
import { head } from "lodash"
import TimeInputParser from "utils/TimeInputParser"
import {get} from "lodash/fp";
@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,
timedActivity: PropTypes.shape({
customer_name: PropTypes.string.isRequired,
assignment_name: PropTypes.string.isRequired,
task_name: PropTypes.string.isRequired,
timer_started_at: PropTypes.string.isRequired,
seconds: PropTypes.number.isRequired,
}),
lastProjectId: PropTypes.number,
lastTaskId: PropTypes.number,
fromDate: PropTypes.string,
toDate: PropTypes.string,
errorType: PropTypes.string,
errorMessage: PropTypes.string,
}
static defaultProps = {
activities: [],
schedules: [],
projects: [],
}
@observable changeset = {}
@observable formErrors = {}
@computed get project() {
const { service, projects, lastProjectId } = this.props
return (
findProjectByValue(this.changeset.assignment_id)(projects) ||
findProjectByValue(Number(lastProjectId))(projects) ||
findProjectByIdentifier(service?.projectId)(projects) ||
head(projects.flatMap(get("options")))
)
}
@computed get task() {
const { service, lastTaskId } = this.props
return (
findTask(this.changeset.task_id || service?.taskId || lastTaskId)(this.project) ||
defaultTask(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: new TimeInputParser(this.changeset.hours).parseSeconds(),
description: service?.description || "",
tag: "",
}
return { ...defaults, ...this.changeset }
}
componentDidMount() {
window.addEventListener("keydown", this.handleKeyDown)
parent.postMessage({ __mocoBX: { iFrameHeight: window.document.body.scrollHeight } }, "*")
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 = defaultTask(project?.tasks)?.value
}
}
handleSelectDate = (date) => {
this.changeset.date = formatDate(date)
}
handleStopTimer = (timedActivity) => {
const { service } = this.props
chrome.runtime.sendMessage({
type: "stopTimer",
payload: { timedActivity, service },
})
}
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,
timedActivity,
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>
{() =>
timedActivity ? (
<TimerView timedActivity={timedActivity} onStopTimer={this.handleStopTimer} />
) : (
<>
<Calendar
fromDate={parseISO(fromDate)}
toDate={parseISO(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