Skip to content

Commit 06a57cf

Browse files
committed
Merge
2 parents 601bf57 + 107c092 commit 06a57cf

File tree

22 files changed

+325
-46
lines changed

22 files changed

+325
-46
lines changed

src/base/repositories/AbstractFileRepository.ts

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,43 @@
1-
import PromiseQueue from '../../helpers/PromiseQueueHellper';
2-
31
const fs = require('fs');
42
const path = require('path');
53

64
import FsHelper from '../../helpers/FsHelper';
5+
import PromiseQueue from '../../helpers/PromiseQueueHellper';
76

8-
const APP_FOLDER = 'YadroTimeTracker';
9-
const PROFILE_FOLDER = 'profile1';
7+
const APP_DIR = 'YadroTimeTracker';
108

119
export default abstract class AbstractFileRepository<T = any> {
12-
folderWithProfile: string = 'profile1';
10+
dirWithProfileData: string = 'profile1';
1311
fileName: string = 'defaultFileName.json';
12+
saveInRoot: boolean = false;
13+
14+
private logPrefix = `Repository[${this.fileName}]:`;
1415

1516
writeFileQueue = new PromiseQueue();
1617

1718
private static get appDataFolder() {
1819
return process.env.APPDATA || '';
1920
}
2021

21-
private static get profileFolder() {
22-
return path.join(
23-
AbstractFileRepository.appDataFolder,
24-
APP_FOLDER,
25-
PROFILE_FOLDER
26-
);
22+
private get destFolder() {
23+
const pathItems = [AbstractFileRepository.appDataFolder, APP_DIR];
24+
if (!this.saveInRoot) {
25+
pathItems.push(this.dirWithProfileData);
26+
}
27+
return path.join(...pathItems);
2728
}
2829

2930
private get filePath() {
30-
return path.join(AbstractFileRepository.profileFolder, this.fileName);
31+
return path.join(this.destFolder, this.fileName);
32+
}
33+
34+
public setProfile(profile: string | null) {
35+
if (profile) {
36+
this.dirWithProfileData = profile;
37+
console.log(`${this.logPrefix} set profile=${profile}`);
38+
} else {
39+
console.error(`${this.logPrefix} set profile=null`);
40+
}
3141
}
3242

3343
public restore(defaultValue: T): T {
@@ -40,7 +50,7 @@ export default abstract class AbstractFileRepository<T = any> {
4050
}
4151

4252
public save(data: T) {
43-
FsHelper.mkdirIfNotExists(AbstractFileRepository.profileFolder);
53+
FsHelper.mkdirIfNotExists(this.destFolder);
4454
this.writeFileQueue.add(() =>
4555
FsHelper.writeFile(this.filePath, data).catch(() => {
4656
console.error(

src/components/Profile/Profile.tsx

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import React from 'react';
2+
import { Space } from 'antd';
3+
import { SettingOutlined } from '@ant-design/icons';
4+
import { createUseStyles } from 'react-jss';
5+
6+
import rootStore from '../../modules/RootStore';
7+
import useModal from '../../hooks/ModalHook';
8+
import SettingsModal from '../SettingsModal/SettingsModal';
9+
10+
const { settingsStore } = rootStore;
11+
12+
const Profile: React.VFC = () => {
13+
const classes = useStyles();
14+
const { settings } = settingsStore;
15+
const { open, openModal, closeModal } = useModal();
16+
17+
return (
18+
<Space className={classes.root}>
19+
<span className={classes.white}>{settings.currentProfile}</span>
20+
<SettingOutlined className={classes.white} onClick={openModal} />
21+
<SettingsModal visible={open} onClose={closeModal} />
22+
</Space>
23+
);
24+
};
25+
26+
const useStyles = createUseStyles({
27+
root: {
28+
marginLeft: 16,
29+
},
30+
white: {
31+
color: 'white',
32+
},
33+
});
34+
35+
export default Profile;
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import React, { ChangeEvent, useCallback, useState } from 'react';
2+
import { createUseStyles } from 'react-jss';
3+
import { Button, Input, Popover, Space } from 'antd';
4+
import { UserAddOutlined } from '@ant-design/icons';
5+
import rootStore from '../../modules/RootStore';
6+
7+
interface INewProfilePopoverProps {
8+
visible: boolean;
9+
setVisible: (visible: boolean) => void;
10+
}
11+
12+
const { settingsStore } = rootStore;
13+
14+
const NewProfilePopover: React.FC<INewProfilePopoverProps> = (
15+
props: INewProfilePopoverProps
16+
) => {
17+
const { visible, setVisible } = props;
18+
const classes = useStyles();
19+
20+
const [profile, setProfile] = useState<string>('');
21+
const [isValid, setIsValid] = useState<boolean>(false);
22+
23+
const isProfileValid = useCallback((profile: string) => {
24+
if (!/^[\w\d ]{2,}$/.test(profile)) {
25+
return false;
26+
}
27+
return !settingsStore.settings.profiles.includes(profile);
28+
}, []);
29+
30+
const handleChange = useCallback((event: ChangeEvent<HTMLInputElement>) => {
31+
const profile = event.target.value;
32+
setProfile(profile);
33+
setIsValid(isProfileValid(profile));
34+
}, []);
35+
36+
return (
37+
<Popover
38+
content={
39+
<Space>
40+
<Input
41+
placeholder="Enter a new profile name"
42+
value={profile}
43+
onChange={handleChange}
44+
/>
45+
<Button type="primary" disabled={!isValid}>
46+
Create
47+
</Button>
48+
</Space>
49+
}
50+
title="Create a new profile"
51+
trigger="click"
52+
visible={visible}
53+
onVisibleChange={setVisible}
54+
>
55+
<Button type="primary">
56+
<UserAddOutlined />
57+
</Button>
58+
</Popover>
59+
);
60+
};
61+
62+
const useStyles = createUseStyles({
63+
root: {},
64+
});
65+
66+
export default NewProfilePopover;
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import React, { useState } from 'react';
2+
import { Col, Form, Modal, Row, Select, Space } from 'antd';
3+
import { createUseStyles } from 'react-jss';
4+
5+
import rootStore from '../../modules/RootStore';
6+
import IModalProps from '../../types/IModalProps';
7+
import NewProfilePopover from './NewProfilePopover';
8+
9+
const { settingsStore } = rootStore;
10+
11+
interface ISettingsModalProps extends IModalProps {}
12+
13+
const SettingsModal: React.VFC<ISettingsModalProps> = (
14+
props: ISettingsModalProps
15+
) => {
16+
const { visible, onClose } = props;
17+
const [showNewProfilePopover, setShowNewProfilePopover] = useState<boolean>(
18+
false
19+
);
20+
21+
return (
22+
<Modal
23+
title="Settings"
24+
visible={visible}
25+
// okButtonProps={{ disabled: !valid }}
26+
okText="Save"
27+
onOk={onClose}
28+
onCancel={onClose}
29+
>
30+
<Row>
31+
<Col span={24}>
32+
<Form.Item label="Profile" labelCol={{ span: 24 }}>
33+
<Space>
34+
<Select>
35+
{settingsStore.settings.profiles.map((profile) => (
36+
<Select.Option key={profile} value={profile}>
37+
{profile}
38+
</Select.Option>
39+
))}
40+
</Select>
41+
<NewProfilePopover
42+
visible={showNewProfilePopover}
43+
setVisible={setShowNewProfilePopover}
44+
/>
45+
</Space>
46+
</Form.Item>
47+
</Col>
48+
</Row>
49+
</Modal>
50+
);
51+
};
52+
53+
const useStyles = createUseStyles({});
54+
55+
export default SettingsModal;

src/components/TimeRangeModal/TimeRangeModal.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import TaskTimeItemModel from '../../modules/tasks/models/TaskTimeItemModel';
1111
import { ITimeRangeModel } from '../../modules/tasks/models/TaskModel';
1212
import { Undefined } from '../../types/CommonTypes';
1313
import TimeRangeDuration from './components/TimeRangeDuration';
14+
import IModalProps from '../../types/IModalProps';
1415

1516
const { tasksStore } = rootStore;
1617

@@ -19,10 +20,8 @@ enum RangeField {
1920
end = 'end',
2021
}
2122

22-
interface TimeRangeModalProps {
23+
interface TimeRangeModalProps extends IModalProps {
2324
taskTime?: TaskTimeItemModel;
24-
visible: boolean;
25-
onClose: () => void;
2625
}
2726

2827
const TimeRangeModal = observer(

src/helpers/ClassNameHelper.ts

Lines changed: 0 additions & 5 deletions
This file was deleted.

src/hooks/ModalHook.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { useCallback, useState } from 'react';
2+
3+
const useModal = () => {
4+
const [open, setOpen] = useState(false);
5+
6+
const openModal = useCallback(() => {
7+
setOpen(true);
8+
}, []);
9+
10+
const closeModal = useCallback(() => {
11+
setOpen(false);
12+
}, []);
13+
14+
return {
15+
open,
16+
openModal,
17+
closeModal,
18+
};
19+
};
20+
21+
export default useModal;

src/modules/RootStore.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
import TaskStore from './tasks/TaskStore';
22
import ProjectStore from './projects/ProjectStore';
33
import ProjectModel from './projects/models/ProjectModel';
4+
import SettingsStore from './settings/SettingsStore';
45

5-
class RootStore {
6-
tasksStore = new TaskStore();
7-
projectStore = new ProjectStore();
6+
export class RootStore {
7+
settingsStore = new SettingsStore();
8+
tasksStore = new TaskStore(this);
9+
projectStore = new ProjectStore(this);
810

911
restore() {
12+
this.settingsStore.restore();
1013
this.tasksStore.restore();
1114
this.projectStore.restore();
1215
}

src/modules/projects/ProjectModel.ts

Whitespace-only changes.

src/modules/projects/ProjectService.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,19 @@ import ProjectFactory from './ProjectFactory';
44
import ProjectRepository from './ProjectRepository';
55

66
export default class ProjectService implements IService<ProjectModel[]> {
7-
projectFactory = new ProjectFactory();
8-
projectRepository = new ProjectRepository();
7+
factory = new ProjectFactory();
8+
repository = new ProjectRepository();
9+
10+
setProfile(profile: string) {
11+
this.repository.setProfile(profile);
12+
}
913

1014
getAll(): ProjectModel[] {
11-
const data = this.projectRepository.restore(DEFAULT_PROJECTS);
12-
return this.projectFactory.createList(ProjectModel, data);
15+
const data = this.repository.restore(DEFAULT_PROJECTS);
16+
return this.factory.createList(ProjectModel, data);
1317
}
1418

1519
save(data: ProjectModel[]): void {
16-
this.projectRepository.save(data);
20+
this.repository.save(data);
1721
}
1822
}

0 commit comments

Comments
 (0)