diff --git a/package.json b/package.json index 7d71bb7..0a95e07 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "time-tracker", "productName": "TimeTracker", - "description": "Start and stop time, jump between tasks, and add details on how time was spent.", + "description": "Todo list with stopwatch on each task", "scripts": { "build": "concurrently \"yarn build:main\" \"yarn build:renderer\"", "build-dll": "cross-env NODE_ENV=development webpack --config ./.erb/configs/webpack.config.renderer.dev.dll.babel.js", @@ -210,7 +210,6 @@ "jest": "^27.0.6", "lint-staged": "^10.2.11", "mini-css-extract-plugin": "^1.3.1", - "node-sass": "^5.0.0", "opencollective-postinstall": "^2.0.3", "prettier": "^2.0.5", "react-refresh": "^0.9.0", @@ -248,6 +247,7 @@ "moment": "2.29.1", "react": "^17.0.1", "react-dom": "^17.0.1", + "react-google-charts": "4.0.0", "react-hook-media-query": "^1.0.5", "react-jss": "^10.6.0", "react-router-dom": "^5.2.0", diff --git a/src/consts/index.ts b/src/consts/index.ts index 012e828..8fb8a4d 100644 --- a/src/consts/index.ts +++ b/src/consts/index.ts @@ -1 +1,6 @@ export const PURPLE_COLOR = '#713A91'; + +export const LOCAL_STORAGE_KEY = { + HOURS_TAB: 'hours:view_tab', + HOURS_SELECTED_DATE: 'hours:view_date', +}; diff --git a/src/hooks/useLocalStorage.ts b/src/hooks/useLocalStorage.ts new file mode 100644 index 0000000..93e4f1b --- /dev/null +++ b/src/hooks/useLocalStorage.ts @@ -0,0 +1,47 @@ +import { useCallback, useState } from 'react'; + +type Value = boolean | number | string | any[] | Record; +type ParseFn = (raw: string) => T; + +function readFromStorage( + key: string, + valueParse?: ParseFn +): T | null { + const raw = window.localStorage.getItem(key); + if (raw === null) { + return raw; + } + try { + const value = JSON.parse(raw); + if (typeof valueParse === 'function') { + return valueParse(value); + } + return value; + } catch { + return null; + } +} + +export default function useLocalStorage( + key: string, + defaultValue: T, + getFromStorageFirst: boolean, + valueParse?: (raw: string) => T +): [T, (t: T) => void] { + const [value, setValue] = useState(() => { + if (getFromStorageFirst) { + return readFromStorage(key, valueParse) ?? defaultValue; + } + return defaultValue; + }); + + const setItem = useCallback( + (value: T) => { + window.localStorage.setItem(key, JSON.stringify(value)); + setValue(value); + }, + [key] + ); + + return [value, setItem]; +} diff --git a/src/menu.ts b/src/menu.ts index 66f3381..cebece1 100644 --- a/src/menu.ts +++ b/src/menu.ts @@ -27,10 +27,8 @@ export default class MenuBuilder { } const template = - process.platform === 'darwin' - ? this.buildDarwinTemplate() - : []; - // : this.buildDefaultTemplate(); // Disable menu for Win and Linux + process.platform === 'darwin' ? this.buildDarwinTemplate() : []; + // : this.buildDefaultTemplate(); // Disable menu for Win and Linux const menu = Menu.buildFromTemplate(template); Menu.setApplicationMenu(menu); @@ -54,6 +52,7 @@ export default class MenuBuilder { } buildDarwinTemplate(): MenuItemConstructorOptions[] { + // TODO const subMenuAbout: DarwinMenuItemConstructorOptions = { label: 'Electron', submenu: [ diff --git a/src/package.json b/src/package.json index 76ccac7..8fb15fd 100644 --- a/src/package.json +++ b/src/package.json @@ -1,8 +1,8 @@ { "name": "time-tracker", "productName": "TimeTracker", - "version": "1.0.9", - "description": "Start and stop time, jump between tasks, and add details on how time was spent.", + "version": "1.0.10", + "description": "Todo list with stopwatch on each task", "main": "./main.prod.js", "author": { "name": "Dmitry Yadrikhinsky", diff --git a/src/screens/Main.tsx b/src/screens/Main.tsx index 7cc471d..0c598b9 100644 --- a/src/screens/Main.tsx +++ b/src/screens/Main.tsx @@ -4,7 +4,7 @@ import { Layout } from 'antd'; import ProjectsScreen from './projects/ProjectsScreen'; import HoursScreen from './hours/HoursScreen'; -import Dashboard from './dashboard/Dashboard'; +import DashboardScreen from './dashboard/Dashboard'; import GaService from '../services/gaService/GaService'; import Header from '../components/Header'; @@ -25,7 +25,7 @@ const Main = () => { - + diff --git a/src/screens/hours/HoursScreen.tsx b/src/screens/hours/HoursScreen.tsx index 39110da..5839e17 100644 --- a/src/screens/hours/HoursScreen.tsx +++ b/src/screens/hours/HoursScreen.tsx @@ -1,39 +1,43 @@ -import React, { useMemo, useState } from 'react'; +import React, { useCallback, useMemo } from 'react'; import { Layout, Space } from 'antd'; import { observer } from 'mobx-react'; +import { createUseStyles } from 'react-jss'; +import { isToday } from 'date-fns'; import rootStore from '../../modules/RootStore'; -import HoursCard from './components/HoursCard/HoursCard'; import { getTimeItems } from '../../helpers/TaskHelper'; -import SelectDate from '../../components/SelectDate'; -import TimeRangeModal from '../../components/TimeRangeModal/TimeRangeModal'; -import TaskTimeItemModel from '../../modules/tasks/models/TaskTimeItemModel'; -import { Undefined } from '../../types/CommonTypes'; import TotalHours from './components/TotalHours/TotalHours'; -import { createUseStyles } from 'react-jss'; -import { mapCurrentNext } from '../../helpers/ArrayHelper'; -import { ITimeRangeModel } from '../../modules/tasks/models/TaskModel'; -import { msToTime } from '../../helpers/DateTime'; +import Header from './components/Header'; +import GridWithTimeItemsView from './components/GridWithTimeItemsView'; +import TimelineScreen from './components/TimelineScreen'; +import { HoursTabView } from './types'; +import useLocalStorage from '../../hooks/useLocalStorage'; +import { LOCAL_STORAGE_KEY } from '../../consts'; +import BackOnToday from './components/BackOnToday'; const { tasksStore } = rootStore; -function getDiff( - prev: ITimeRangeModel | undefined, - next: ITimeRangeModel | undefined -) { - if (prev?.end && next?.start) { - return msToTime(next.start.getTime() - prev.end.getTime()); - } - - return ''; -} +const parseDateFromString = (value: string) => new Date(value); -export default observer(function HoursView() { +export default observer(function HoursScreen() { const classes = useStyles(); - const [date, setDate] = useState(new Date()); - const [currentTaskTime, setCurrentTaskTime] = useState< - Undefined - >(); + + const [date, setDate] = useLocalStorage( + LOCAL_STORAGE_KEY.HOURS_SELECTED_DATE, + new Date(), + true, + parseDateFromString + ); + + const goTodayDate = useCallback(() => { + setDate(new Date()); + }, [setDate]); + + const [tab, setTab] = useLocalStorage( + LOCAL_STORAGE_KEY.HOURS_TAB, + HoursTabView.Edit, + true + ); const tasks = useMemo(() => tasksStore.getTasksByDate(date), [ tasksStore.tasks, @@ -42,55 +46,37 @@ export default observer(function HoursView() { const timeItems = getTimeItems(tasks, date); return ( - - - + + +
-
- {mapCurrentNext(timeItems, (item, next, index) => ( -
- setCurrentTaskTime(taskTime)} - /> -
- {getDiff(item.time, next?.time)} -
-
- ))} -
+ {!timeItems.length && !isToday(date) ? ( + + ) : tab === HoursTabView.Edit ? ( + + ) : ( + + )} - setCurrentTaskTime(undefined)} - taskTime={currentTaskTime} - /> ); }); const useStyles = createUseStyles({ - hours: { + hoursView: { overflowY: 'auto', padding: 12, + }, + space: { + flex: 1, '& .ant-space-item': { display: 'flex', justifyContent: 'center', }, - '& .ant-card-body': { - padding: 8, + '& .ant-space-item:last-child': { + flex: 1, }, }, - cards: { - display: 'flex', - flexWrap: 'wrap', - }, - breakTime: { - display: 'flex', - justifyContent: 'flex-end', - margin: '0 13px', - fontSize: 11, - }, }); diff --git a/src/screens/hours/components/BackOnToday.tsx b/src/screens/hours/components/BackOnToday.tsx new file mode 100644 index 0000000..c634916 --- /dev/null +++ b/src/screens/hours/components/BackOnToday.tsx @@ -0,0 +1,13 @@ +import React, { FC } from 'react'; +import { Button } from 'antd'; +import { observer } from 'mobx-react'; + +type Props = { + goToday(): void; +}; + +const BackOnToday: FC = ({ goToday }: Props) => { + return