diff --git a/src/js/api/Client.js b/src/js/api/Client.js index c35e59f..1cee6c2 100644 --- a/src/js/api/Client.js +++ b/src/js/api/Client.js @@ -46,12 +46,12 @@ export default class Client { params: { date: `${formatDate(fromDate)}:${formatDate(toDate)}` }, }) - bookedHours = service => { + activitiesStatus = service => { if (!service) { return Promise.resolve({ data: { hours: 0 } }) } - return this.#client.get("activities/tags", { - params: { selection: [service.id], remote_service: service.name }, + return this.#client.get("activities/status", { + params: { remote_id: service.id, remote_service: service.name }, }) } diff --git a/src/js/background.js b/src/js/background.js index 3eb9617..70ac8cb 100644 --- a/src/js/background.js +++ b/src/js/background.js @@ -33,11 +33,12 @@ chrome.runtime.onMessage.addListener(action => { .createActivity(activity) .then(() => { messenger.postMessage(tab, { type: "closePopup" }) - apiClient.bookedHours(service).then(({ data }) => { + apiClient.activitiesStatus(service).then(({ data }) => { messenger.postMessage(tab, { type: "showBubble", payload: { - bookedHours: parseFloat(data[0]?.hours) || 0, + bookedHours: parseFloat(data.hours), + timedActivity: data.timed_activity, service, }, }) diff --git a/src/js/components/Bubble.js b/src/js/components/Bubble.js index 1e680d1..b80e0b5 100644 --- a/src/js/components/Bubble.js +++ b/src/js/components/Bubble.js @@ -1,18 +1,36 @@ import React from "react" import PropTypes from "prop-types" -import logoUrl from "images/moco-159x159.png" +import mocoLogo from "images/moco-32x32.png" +import mocoTimerLogo from "images/moco-timer-32x32.png" +import { parseISO } from "date-fns" +import Timer from "./shared/Timer" -const Bubble = ({ bookedHours }) => ( -
- - {bookedHours > 0 ? ( - {bookedHours.toFixed(2)} - ) : null} -
-) +const Bubble = ({ bookedHours, timedActivity }) => { + const logo = timedActivity ? mocoTimerLogo : mocoLogo + + return ( +
+ + {!timedActivity && bookedHours > 0 && ( + {bookedHours.toFixed(2)} + )} + {timedActivity && ( + + )} +
+ ) +} Bubble.propTypes = { bookedHours: PropTypes.number, + timedActivity: PropTypes.shape({ + timer_started_at: PropTypes.string.isRequired, + seconds: PropTypes.number.isRequired, + }), } Bubble.defaultProps = { diff --git a/src/js/components/shared/Timer.js b/src/js/components/shared/Timer.js new file mode 100644 index 0000000..f454821 --- /dev/null +++ b/src/js/components/shared/Timer.js @@ -0,0 +1,28 @@ +import React, { useState } from "react" +import PropTypes from "prop-types" +import { useInterval } from "./hooks" +import { differenceInSeconds, addSeconds, startOfDay, format } from "date-fns" + +Timer.propTypes = { + startedAt: PropTypes.instanceOf(Date).isRequired, + offset: PropTypes.number, + onTick: PropTypes.func, +} + +function Timer({ startedAt, offset = 0, onTick, ...domProps }) { + const [timerLabel, setTimerLabel] = useState(formattedTimerLabel(startedAt, offset)) + + useInterval(() => { + setTimerLabel(formattedTimerLabel(startedAt, offset)) + onTick && onTick() + }, 1000) + + return {timerLabel} +} + +function formattedTimerLabel(startedAt, offset) { + const seconds = differenceInSeconds(new Date(), startedAt) + offset + return format(addSeconds(startOfDay(new Date()), seconds), "HH:mm:ss") +} + +export default Timer diff --git a/src/js/components/shared/hooks.js b/src/js/components/shared/hooks.js new file mode 100644 index 0000000..dfb6f90 --- /dev/null +++ b/src/js/components/shared/hooks.js @@ -0,0 +1,18 @@ +import { useEffect, useRef } from "react" + +export function useInterval(callback, delay) { + const savedCallback = useRef() + + useEffect(() => { + savedCallback.current = callback + }) + + useEffect(() => { + function tick() { + savedCallback.current() + } + + let id = setInterval(tick, delay) + return () => clearInterval(id) + }, [delay]) +} diff --git a/src/js/content.js b/src/js/content.js index 33bbdda..097822d 100644 --- a/src/js/content.js +++ b/src/js/content.js @@ -25,7 +25,7 @@ chrome.runtime.onConnect.addListener(function(port) { document.removeEventListener("click", clickHandler, true) }) - function updateBubble({ service, bookedHours } = {}) { + function updateBubble({ service, bookedHours, timedActivity } = {}) { if (!document.getElementById("moco-bx-root")) { const domRoot = document.createElement("div") domRoot.setAttribute("id", "moco-bx-root") @@ -47,7 +47,7 @@ chrome.runtime.onConnect.addListener(function(port) { // eslint-disable-next-line react/display-name (props => ( - + )) } @@ -86,8 +86,8 @@ chrome.runtime.onConnect.addListener(function(port) { }) }) - messenger.on("showBubble", ({ payload: { service, bookedHours } }) => { - updateBubble({ service, bookedHours }) + messenger.on("showBubble", ({ payload }) => { + updateBubble(payload) }) messenger.on("hideBubble", () => { diff --git a/src/js/utils/messageHandlers.js b/src/js/utils/messageHandlers.js index d9eb3a7..538c03e 100644 --- a/src/js/utils/messageHandlers.js +++ b/src/js/utils/messageHandlers.js @@ -25,12 +25,13 @@ export function tabUpdated(tab, { messenger, settings }) { messenger.once("newService", ({ payload: { service } }) => { apiClient - .bookedHours(service) + .activitiesStatus(service) .then(({ data }) => { messenger.postMessage(tab, { type: "showBubble", payload: { - bookedHours: parseFloat(data[0]?.hours) || 0, + bookedHours: parseFloat(data.hours), + timedActivity: data.timed_activity, service, }, }) @@ -89,6 +90,7 @@ async function openPopup(tab, { service, messenger }) { apiClient.activities(fromDate, toDate), apiClient.schedules(fromDate, toDate), ]) + const action = { type: "openPopup", payload: {