Skip to content
This repository was archived by the owner on Dec 26, 2022. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down
5 changes: 5 additions & 0 deletions src/consts/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,6 @@
export const PURPLE_COLOR = '#713A91';

export const LOCAL_STORAGE_KEY = {
HOURS_TAB: 'hours:view_tab',
HOURS_SELECTED_DATE: 'hours:view_date',
};
47 changes: 47 additions & 0 deletions src/hooks/useLocalStorage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { useCallback, useState } from 'react';

type Value = boolean | number | string | any[] | Record<string, any>;
type ParseFn<T> = (raw: string) => T;

function readFromStorage<T extends Value>(
key: string,
valueParse?: ParseFn<T>
): 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<T extends Value>(
key: string,
defaultValue: T,
getFromStorageFirst: boolean,
valueParse?: (raw: string) => T
): [T, (t: T) => void] {
const [value, setValue] = useState<T>(() => {
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];
}
7 changes: 3 additions & 4 deletions src/menu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -54,6 +52,7 @@ export default class MenuBuilder {
}

buildDarwinTemplate(): MenuItemConstructorOptions[] {
// TODO
const subMenuAbout: DarwinMenuItemConstructorOptions = {
label: 'Electron',
submenu: [
Expand Down
4 changes: 2 additions & 2 deletions src/package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
4 changes: 2 additions & 2 deletions src/screens/Main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -25,7 +25,7 @@ const Main = () => {
<Switch>
<Route path="/hours" component={HoursScreen} />
<Route path="/projects" component={ProjectsScreen} />
<Route path="/dashboard" component={Dashboard} />
<Route path="/dashboard" component={DashboardScreen} />
<Redirect from="*" to="/projects" />
</Switch>
</Layout>
Expand Down
104 changes: 45 additions & 59 deletions src/screens/hours/HoursScreen.tsx
Original file line number Diff line number Diff line change
@@ -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<Date>(new Date());
const [currentTaskTime, setCurrentTaskTime] = useState<
Undefined<TaskTimeItemModel>
>();

const [date, setDate] = useLocalStorage<Date>(
LOCAL_STORAGE_KEY.HOURS_SELECTED_DATE,
new Date(),
true,
parseDateFromString
);

const goTodayDate = useCallback(() => {
setDate(new Date());
}, [setDate]);

const [tab, setTab] = useLocalStorage<HoursTabView>(
LOCAL_STORAGE_KEY.HOURS_TAB,
HoursTabView.Edit,
true
);

const tasks = useMemo(() => tasksStore.getTasksByDate(date), [
tasksStore.tasks,
Expand All @@ -42,55 +46,37 @@ export default observer(function HoursView() {
const timeItems = getTimeItems(tasks, date);

return (
<Layout className={classes.hours}>
<Space direction="vertical">
<SelectDate date={date} onChange={setDate} />
<Layout className={classes.hoursView}>
<Space direction="vertical" className={classes.space}>
<Header date={date} setDate={setDate} tab={tab} setTab={setTab} />
<TotalHours timeItems={timeItems} />
<div className={classes.cards}>
{mapCurrentNext(timeItems, (item, next, index) => (
<div key={index}>
<HoursCard
taskTime={item}
onClick={(taskTime) => setCurrentTaskTime(taskTime)}
/>
<div className={classes.breakTime}>
{getDiff(item.time, next?.time)}
</div>
</div>
))}
</div>
{!timeItems.length && !isToday(date) ? (
<BackOnToday goToday={goTodayDate} />
) : tab === HoursTabView.Edit ? (
<GridWithTimeItemsView date={date} />
) : (
<TimelineScreen date={date} />
)}
</Space>
<TimeRangeModal
visible={!!currentTaskTime}
onClose={() => setCurrentTaskTime(undefined)}
taskTime={currentTaskTime}
/>
</Layout>
);
});

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,
},
});
13 changes: 13 additions & 0 deletions src/screens/hours/components/BackOnToday.tsx
Original file line number Diff line number Diff line change
@@ -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<Props> = ({ goToday }: Props) => {
return <Button onClick={goToday} title={'Go back'} />;
};

export default observer(BackOnToday);
86 changes: 86 additions & 0 deletions src/screens/hours/components/GridWithTimeItemsView.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import React, { FC, useMemo, useState } from 'react';
import { observer } from 'mobx-react';
import { createUseStyles } from 'react-jss';

import HoursCard from './HoursCard/HoursCard';
import TimeRangeModal from '../../../components/TimeRangeModal/TimeRangeModal';
import { mapCurrentNext } from '../../../helpers/ArrayHelper';
import { msToTime } from '../../../helpers/DateTime';
import { getTimeItems } from '../../../helpers/TaskHelper';
import rootStore from '../../../modules/RootStore';
import { ITimeRangeModel } from '../../../modules/tasks/models/TaskModel';
import TaskTimeItemModel from '../../../modules/tasks/models/TaskTimeItemModel';
import { Undefined } from '../../../types/CommonTypes';

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 '';
}

type Props = {
date: Date;
};

const GridWithTimeItemsView: FC<Props> = ({ date }: Props) => {
const classes = useStyles();

const [currentTaskTime, setCurrentTaskTime] = useState<
Undefined<TaskTimeItemModel>
>();

const timeItems = useMemo(() => {
// TODO Doesn't update
const tasks = tasksStore.getTasksByDate(date);
return getTimeItems(tasks, date);
}, [tasksStore.tasks, date]);

return (
<>
<div className={classes.cards}>
{mapCurrentNext(timeItems, (item, next, index) => (
<div key={index}>
<HoursCard
taskTime={item}
onClick={(taskTime) => setCurrentTaskTime(taskTime)}
/>
<div className={classes.breakTime}>
{getDiff(item.time, next?.time)}
</div>
</div>
))}
</div>
<TimeRangeModal
visible={!!currentTaskTime}
onClose={() => setCurrentTaskTime(undefined)}
taskTime={currentTaskTime}
/>
</>
);
};

export default observer(GridWithTimeItemsView);

const useStyles = createUseStyles({
cards: {
display: 'flex',
flexWrap: 'wrap',

'& .ant-card-body': {
padding: 8,
},
},
breakTime: {
display: 'flex',
justifyContent: 'flex-end',
margin: '0 13px',
fontSize: 11,
},
});
Loading