From 9b4a36a6baf644af259ce0fac3259986de49d602 Mon Sep 17 00:00:00 2001
From: Daniel Vasylenko
Date: Tue, 15 Mar 2022 04:42:07 +0100
Subject: [PATCH 1/8] chore: remove selected issues test file
---
.../__tests__/issue.selected.reducer.spec.js | 308 ------------------
1 file changed, 308 deletions(-)
delete mode 100644 render/reducers/__tests__/issue.selected.reducer.spec.js
diff --git a/render/reducers/__tests__/issue.selected.reducer.spec.js b/render/reducers/__tests__/issue.selected.reducer.spec.js
deleted file mode 100644
index 5f701f0..0000000
--- a/render/reducers/__tests__/issue.selected.reducer.spec.js
+++ /dev/null
@@ -1,308 +0,0 @@
-import _cloneDeep from 'lodash/cloneDeep';
-
-import { notify } from '../../actions/helper';
-import {
- ISSUES_TIME_ENTRY_GET
-} from '../../actions/issues.actions';
-import {
- TIME_ENTRY_PUBLISH,
- TIME_ENTRY_UPDATE
-} from '../../actions/timeEntry.actions';
-import reducer, { initialState } from '../issue.selected.reducer';
-
-describe('Selected issue reducer', () => {
- it('should return the initial state if the action was not recognized', () => {
- expect(reducer(undefined, { type: 'WEIRD' })).toEqual(initialState);
- });
-
- describe('ISSUES_TIME_ENTRY_GET', () => {
- it('status START', () => {
- const state = _cloneDeep(initialState);
- expect(
- reducer(
- _cloneDeep(initialState),
- notify.start(ISSUES_TIME_ENTRY_GET)
- )
- ).toEqual({
- ...state,
- spentTime: {
- ...state.spentTime,
- isFetching: true
- }
- });
-
- expect(
- reducer(
- _cloneDeep(initialState),
- notify.start(ISSUES_TIME_ENTRY_GET, { page: 1 })
- )
- ).toEqual({
- ...state,
- spentTime: {
- ...state.spentTime,
- isFetching: true,
- page: 1
- }
- });
- });
-
- it('status OK', () => {
- const state = _cloneDeep(initialState);
- const error = new Error('Whoops');
- const data = {
- time_entries: [
- 1, 2, 3, 4
- ],
- total_count: 4
- };
- expect(
- reducer(
- {
- ..._cloneDeep(initialState),
- spentTime: {
- ...state.spentTime,
- isFetching: true,
- error
- }
- },
- notify.ok(ISSUES_TIME_ENTRY_GET, data)
- )
- ).toEqual({
- ...state,
- spentTime: {
- ...state.spentTime,
- isFetching: false,
- data: data.time_entries,
- totalCount: data.total_count,
- error: undefined
- }
- });
-
- expect(
- reducer(
- {
- ..._cloneDeep(initialState),
- spentTime: {
- ...state.spentTime,
- data: [-1, -2, -3],
- isFetching: true,
- error
- }
- },
- notify.ok(ISSUES_TIME_ENTRY_GET, data, { page: 2 })
- )
- ).toEqual({
- ...state,
- spentTime: {
- ...state.spentTime,
- isFetching: false,
- data: [-1, -2, -3, ...data.time_entries],
- totalCount: data.total_count,
- error: undefined
- }
- });
- });
-
- it('status NOK', () => {
- const state = _cloneDeep(initialState);
- const error = new Error('Whoops');
- expect(
- reducer(
- {
- ..._cloneDeep(initialState),
- spentTime: {
- ...state.spentTime,
- isFetching: true
- }
- },
- notify.nok(ISSUES_TIME_ENTRY_GET, error)
- )
- ).toEqual({
- ...state,
- spentTime: {
- ...state.spentTime,
- isFetching: false,
- error
- }
- });
- });
- });
-
- describe('TIME_ENTRY_PUBLISH', () => {
- it('status OK - time entry id matches the selected issue', () => {
- const issueData = {
- id: 1,
- spent_hours: 5,
- total_spent_hours: 5
- };
- const data = {
- time_entry: {
- hours: 3,
- issue: {
- id: issueData.id
- }
- }
- };
- const state = _cloneDeep(initialState);
- expect(
- reducer(
- {
- ..._cloneDeep(initialState),
- data: { ...issueData },
- spentTime: {
- ...state.spentTime,
- data: [1, 2, 3]
- }
- },
- notify.ok(TIME_ENTRY_PUBLISH, data)
- )
- ).toEqual({
- ...state,
- data: {
- ...issueData,
- spent_hours: issueData.spent_hours + data.time_entry.hours,
- total_spent_hours: issueData.total_spent_hours + data.time_entry.hours
- },
- spentTime: {
- ...state.spentTime,
- data: [
- data.time_entry,
- 1, 2, 3
- ]
- }
- });
- });
-
- it('status OK - time entry id not matches the selected issue', () => {
- const issueData = {
- id: 1,
- spent_hours: 5,
- total_spent_hours: 5
- };
- const data = {
- time_entry: {
- hours: 3,
- issue: {
- id: -issueData.id
- }
- }
- };
-
- const state = _cloneDeep(initialState);
- expect(
- reducer(
- {
- ..._cloneDeep(initialState),
- data: { ...issueData },
- spentTime: {
- ...state.spentTime,
- data: [1, 2, 3]
- }
- },
- notify.ok(TIME_ENTRY_PUBLISH, data)
- )
- ).toEqual({
- ...state,
- data: { ...issueData },
- spentTime: {
- ...state.spentTime,
- data: [1, 2, 3]
- }
- });
- });
- });
-
- describe('TIME_ENTRY_UPDATE', () => {
- it('status OK - time entry id matches the selected issue id', () => {
- const issueData = {
- id: 1,
- spent_hours: 6,
- total_spent_hours: 6
- };
- const data = {
- id: 1,
- issue: {
- id: issueData.id,
- },
- hours: 7
- };
- const state = _cloneDeep(initialState);
- expect(
- reducer(
- {
- ..._cloneDeep(initialState),
- data: { ...issueData },
- spentTime: {
- ...state.spentTime,
- data: [
- { id: 1, hours: 1 },
- { id: 2, hours: 2 },
- { id: 3, hours: 3 }
- ]
- }
- },
- notify.ok(TIME_ENTRY_UPDATE, data)
- )
- ).toEqual({
- ...state,
- data: {
- ...issueData,
- spent_hours: 12,
- total_spent_hours: 12
- },
- spentTime: {
- ...state.spentTime,
- data: [
- data,
- { id: 2, hours: 2 },
- { id: 3, hours: 3 }
- ]
- }
- });
- });
-
- it('status OK - time entry id not matches the selected issue id', () => {
- const issueData = {
- id: 1,
- spent_hours: 6,
- total_spent_hours: 6
- };
- const data = {
- hours: 3,
- issue: {
- id: -issueData.id
- }
- };
-
- const state = _cloneDeep(initialState);
- expect(
- reducer(
- {
- ..._cloneDeep(initialState),
- data: { ...issueData },
- spentTime: {
- ...state.spentTime,
- data: [
- { id: 1, hours: 1 },
- { id: 2, hours: 2 },
- { id: 3, hours: 3 }
- ]
- }
- },
- notify.ok(TIME_ENTRY_UPDATE, data)
- )
- ).toEqual({
- ...state,
- data: { ...issueData },
- spentTime: {
- ...state.spentTime,
- data: [
- { id: 1, hours: 1 },
- { id: 2, hours: 2 },
- { id: 3, hours: 3 }
- ]
- }
- });
- });
- });
-});
From 6bbefe8c09e06a21c47eb8ce8868fee8c327a6eb Mon Sep 17 00:00:00 2001
From: Daniel Vasylenko
Date: Tue, 15 Mar 2022 04:43:28 +0100
Subject: [PATCH 2/8] chore: remove notification middleware for redux
---
render/middlewares/notification.middleware.js | 15 ---------------
render/reducers/index.js | 4 ----
render/reduxStore.ts | 3 +--
3 files changed, 1 insertion(+), 21 deletions(-)
delete mode 100644 render/middlewares/notification.middleware.js
diff --git a/render/middlewares/notification.middleware.js b/render/middlewares/notification.middleware.js
deleted file mode 100644
index 0e88ce1..0000000
--- a/render/middlewares/notification.middleware.js
+++ /dev/null
@@ -1,15 +0,0 @@
-import React from 'react';
-import { toast } from 'react-toastify';
-
-import Notification from '../components/Notification';
-
-export default () => (next) => (action) => {
- if (action.status === 'NOK') {
- const error = action.data;
- if (error instanceof Error) {
- // eslint-disable-next-line react/jsx-filename-extension
- toast.error();
- }
- }
- next(action);
-};
diff --git a/render/reducers/index.js b/render/reducers/index.js
index 3fbe434..4da3c8f 100644
--- a/render/reducers/index.js
+++ b/render/reducers/index.js
@@ -1,13 +1,9 @@
import { combineReducers } from 'redux';
import settingsReducer from './settings.reducer';
import issueReducer from './issue.reducer';
-import selectedIssueReducer from './issue.selected.reducer';
const appReducer = combineReducers({
settings: settingsReducer,
- issues: combineReducers({
- selected: selectedIssueReducer
- }),
issue: issueReducer
});
diff --git a/render/reduxStore.ts b/render/reduxStore.ts
index c3e18e2..786867d 100644
--- a/render/reduxStore.ts
+++ b/render/reduxStore.ts
@@ -3,7 +3,6 @@ import thunk from 'redux-thunk';
// import _get from 'lodash/get';
import reducers from './reducers/index';
-import notificationMiddleware from './middlewares/notification.middleware';
// const user = _get(initialState, 'user', {});
// const { id, redmineEndpoint } = user;
@@ -16,4 +15,4 @@ import notificationMiddleware from './middlewares/notification.middleware';
// tracking: initialState.time_tracking
// },
-export default createStore(reducers, applyMiddleware(thunk, notificationMiddleware));
+export default createStore(reducers, applyMiddleware(thunk));
From bb229798de7766d91aa12334f9ab4bf8a8bccf38 Mon Sep 17 00:00:00 2001
From: Daniel Vasylenko
Date: Wed, 16 Mar 2022 04:51:35 +0100
Subject: [PATCH 3/8] refactor: use react-time-ago instead of custom date
component
---
package.json | 2 +
render/components/Date.tsx | 94 -------------------
.../IssueDetailsPage/CommentsSection.tsx | 4 +-
render/components/SummaryPage/IssuesTable.tsx | 4 +-
render/components/TimeEntryCard.tsx | 8 +-
render/components/__tests__/Date.spec.jsx | 52 ----------
render/index.tsx | 4 +
render/views/IssueDetailsPage.tsx | 10 +-
render/views/__tests__/SummaryPage.spec.jsx | 40 +-------
yarn.lock | 26 +++++
10 files changed, 47 insertions(+), 197 deletions(-)
delete mode 100644 render/components/Date.tsx
delete mode 100644 render/components/__tests__/Date.spec.jsx
diff --git a/package.json b/package.json
index 76c16c6..9052ee5 100644
--- a/package.json
+++ b/package.json
@@ -158,6 +158,7 @@
"ensure-error": "^2.0.0",
"formik": "^2.1.4",
"got": "^11.8.2",
+ "javascript-time-ago": "^2.3.13",
"lodash": "^4.17.15",
"mdi-react": "^6.7.0",
"moment": "^2.24.0",
@@ -176,6 +177,7 @@
"react-select": "^2.4.1",
"react-simple-timefield": "^3.2.5",
"react-tabs": "^3.1.0",
+ "react-time-ago": "^7.1.9",
"react-toastify": "^5.5.0",
"redux": "^4.0.5",
"redux-thunk": "^2.3.0",
diff --git a/render/components/Date.tsx b/render/components/Date.tsx
deleted file mode 100644
index 8f3df6c..0000000
--- a/render/components/Date.tsx
+++ /dev/null
@@ -1,94 +0,0 @@
-import React from 'react';
-import { css, keyframes } from '@emotion/react';
-import moment from 'moment';
-
-const verticalSlideFadeOut = keyframes`
- 0%: {
- transform: translateY(0px);
- opacity: 1;
- }
-
- 100% {
- transform: translateY(-30px);
- opacity: 0;
- }
-`;
-
-const fadeIn = keyframes`
- 0%: {
- opacity: 0;
- }
-
- 100% {
- opacity: 1;
- }
-`;
-
-const styles = {
- wrapper: css`
- position: relative;
- display: inline-grid;
- grid-template-rows: 1fr;
- `,
- wrapperAnimated: css`
- &:focus > span:first-child,
- &:hover > span:first-child {
- animation: ${verticalSlideFadeOut} 0.5s ease forwards;
- }
-
- &:focus > span:first-child,
- &:hover > span:last-child {
- animation: ${fadeIn} 0.5s ease forwards;
- }
- `,
- span: css`
- float: left;
- grid-row: 1;
- grid-column: 1;
- `,
- formattedDate: css`
- opacity: 0;
- display: none;
- `,
- formattedDateAnimated: css`
- display: 'inherit';
- `
-};
-
-type DateComponentProps = {
- date?: string;
- className?: string;
-};
-
-const DateComponent = ({ date, className }: DateComponentProps) => {
- if (!date) {
- return null;
- }
-
- const daysAgo = moment()
- .endOf('day')
- .diff(moment(date).endOf('day'), 'days');
- // eslint-disable-next-line
- const precision = daysAgo === 0 ? 'today' : daysAgo > 1 ? `${daysAgo} days ago` : 'yesterday';
- const shouldBeAnimated = daysAgo >= 0 && daysAgo <= 30;
- const displayedValue = shouldBeAnimated ? precision : moment(date).format('MMM DD YYYY');
- return (
-
-
- {displayedValue}
-
-
- {moment(date).format('MMM DD YYYY')}
-
-
- );
-};
-
-export { DateComponent };
diff --git a/render/components/IssueDetailsPage/CommentsSection.tsx b/render/components/IssueDetailsPage/CommentsSection.tsx
index 48968d1..54cc25c 100644
--- a/render/components/IssueDetailsPage/CommentsSection.tsx
+++ b/render/components/IssueDetailsPage/CommentsSection.tsx
@@ -4,9 +4,9 @@ import { css } from '@emotion/react';
import { remote } from 'electron';
import { useTheme } from 'styled-components';
+import ReactTimeAgo from 'react-time-ago';
import { MarkdownEditor, MarkdownText } from '../MarkdownEditor';
import { Link } from '../Link';
-import { DateComponent } from '../Date';
import { useOvermindActions, useOvermindState } from '../../store';
import { theme as Theme } from '../../theme';
import type { Issue } from '../../../types';
@@ -109,7 +109,7 @@ const CommentsSection = ({ issueId }: CommentsSectionProps) => {
{entry.user.name}
-
+
diff --git a/render/components/SummaryPage/IssuesTable.tsx b/render/components/SummaryPage/IssuesTable.tsx
index a7e0c9c..4599331 100644
--- a/render/components/SummaryPage/IssuesTable.tsx
+++ b/render/components/SummaryPage/IssuesTable.tsx
@@ -6,9 +6,9 @@ import { useNavigate } from 'react-router-dom';
import { useTheme } from 'styled-components';
import { get } from 'lodash';
+import ReactTimeAgo from 'react-time-ago';
import { theme as Theme } from '../../theme';
import { ProcessIndicator, OverlayProcessIndicator } from '../ProcessIndicator';
-import { DateComponent } from '../Date';
import { useOvermindActions, useOvermindState } from '../../store';
import { IssueFilter } from '../../actions/helper';
import { usePaginatedFetch } from '../../hooks/usePaginatedFetch';
@@ -172,7 +172,7 @@ const IssuesTable = ({ search }: { search?: string }) => {
key={header.value}
>
{date ? (
-
+
) : (
{forcedValue || get(task, header.value)}
)}
diff --git a/render/components/TimeEntryCard.tsx b/render/components/TimeEntryCard.tsx
index 8758c38..00c9428 100644
--- a/render/components/TimeEntryCard.tsx
+++ b/render/components/TimeEntryCard.tsx
@@ -6,8 +6,8 @@ import CalendarIcon from 'mdi-react/CalendarIcon';
import AccountIcon from 'mdi-react/AccountIcon';
import EditOutlineIcon from 'mdi-react/EditOutlineIcon';
import { useTheme } from 'styled-components';
+import ReactTimeAgo from 'react-time-ago';
import { TimeEntry } from '../../types';
-import { DateComponent } from './Date';
import { Flex } from './Flex';
import { MarkdownText } from './MarkdownEditor';
import { theme as Theme } from '../theme';
@@ -28,7 +28,9 @@ const styles = {
`
};
-const TimeEntryCard = ({ timeEntry, currentUserId, waitForConfirmation, onDelete, onEdit }: TimeEntryCardProps) => {
+const TimeEntryCard = ({
+ timeEntry, currentUserId, waitForConfirmation, onDelete, onEdit
+}: TimeEntryCardProps) => {
const theme = useTheme() as typeof Theme;
const handleDelete = async () => {
@@ -52,7 +54,7 @@ const TimeEntryCard = ({ timeEntry, currentUserId, waitForConfirmation, onDelete
hours
-
+
{timeEntry.user.name}
{ currentUserId === timeEntry.user.id ? : null }
diff --git a/render/components/__tests__/Date.spec.jsx b/render/components/__tests__/Date.spec.jsx
deleted file mode 100644
index 5b346fa..0000000
--- a/render/components/__tests__/Date.spec.jsx
+++ /dev/null
@@ -1,52 +0,0 @@
-import React from 'react';
-import moment from 'moment';
-import { shallow, mount } from 'enzyme';
-import DateComponent from '../Date';
-
-describe('Date component', () => {
- it('should render a date, relative to Date.now(), in days', () => {
- const today = new Date().toISOString();
- const yesterday = moment().subtract(1, 'days').toISOString();
- const tenDays = moment().subtract(10, 'days').toISOString();
- const wrapper = mount(
-
- );
- expect(wrapper.exists('#today')).toBe(true);
- expect(wrapper.exists('#yesterday')).toBe(true);
- expect(wrapper.exists('#tenDays')).toBe(true);
-
- expect(wrapper.find('#today').find(DateComponent).find('span > span:first-child').text()).toBe('today');
- expect(wrapper.find('#today').find(DateComponent).find('span > span:last-child').text())
- .toBe(moment(today).format('MMM DD YYYY'));
-
- expect(wrapper.find('#yesterday').find(DateComponent).find('span > span:first-child').text()).toBe('yesterday');
- expect(wrapper.find('#yesterday').find(DateComponent).find('span > span:last-child').text())
- .toBe(moment(yesterday).format('MMM DD YYYY'));
-
- expect(wrapper.find('#tenDays').find(DateComponent).find('span > span:first-child').text()).toBe('10 days ago');
- expect(wrapper.find('#tenDays').find(DateComponent).find('span > span:last-child').text())
- .toBe(moment(tenDays).format('MMM DD YYYY'));
- });
-
- it('should not display if a date was not given', () => {
- const wrapper = shallow();
- expect(wrapper.exists(DateComponent)).toBe(false);
- expect(wrapper.children().length).toBe(0);
- });
-});
diff --git a/render/index.tsx b/render/index.tsx
index 9e03903..a473519 100644
--- a/render/index.tsx
+++ b/render/index.tsx
@@ -6,6 +6,8 @@ import { Provider } from 'react-redux';
import { HashRouter } from 'react-router-dom';
import { createOvermind } from 'overmind';
+import TimeAgo from 'javascript-time-ago';
+import en from 'javascript-time-ago/locale/en.json';
import store from './reduxStore';
import { theme } from './theme';
import App from './App';
@@ -15,6 +17,8 @@ if (module.hot) {
module.hot.accept();
}
+TimeAgo.addDefaultLocale(en);
+
const overmindStore = createOvermind(overmindStoreConfig);
ReactDOM.render(
diff --git a/render/views/IssueDetailsPage.tsx b/render/views/IssueDetailsPage.tsx
index 3bd217c..a55f036 100644
--- a/render/views/IssueDetailsPage.tsx
+++ b/render/views/IssueDetailsPage.tsx
@@ -8,11 +8,11 @@ import CommentsTextOutlineIcon from 'mdi-react/CommentsTextOutlineIcon';
import ClockOutlineIcon from 'mdi-react/ClockOutlineIcon';
import PlusIcon from 'mdi-react/PlusIcon';
+import ReactTimeAgo from 'react-time-ago';
import { Link } from '../components/Link';
import { Progressbar } from '../components/Progressbar';
import { MarkdownText } from '../components/MarkdownEditor';
import { CommentsSection } from '../components/IssueDetailsPage/CommentsSection';
-import { DateComponent } from '../components/Date';
import { OverlayProcessIndicator } from '../components/ProcessIndicator';
import { tabsHeaderList, tabsTrigger, tabsTriggerActive } from '../components/Tabs';
@@ -234,7 +234,7 @@ const IssueDetailsPage = () => {
Due date:
-
+ { currentIssue.dueDate ? : null }
Project:
@@ -246,7 +246,7 @@ const IssueDetailsPage = () => {
Start date:
-
+ { currentIssue.startDate ? : null }
Estimation:
@@ -329,7 +329,7 @@ const IssueDetailsPage = () => {
`}
>
Created on:
-
+
by
{currentIssue.author.name}
@@ -346,7 +346,7 @@ const IssueDetailsPage = () => {
`}
>
Closed on:
-
+
)}
diff --git a/render/views/__tests__/SummaryPage.spec.jsx b/render/views/__tests__/SummaryPage.spec.jsx
index d159745..0deb8ce 100644
--- a/render/views/__tests__/SummaryPage.spec.jsx
+++ b/render/views/__tests__/SummaryPage.spec.jsx
@@ -10,7 +10,6 @@ import { ThemeProvider } from 'styled-components';
import { theme } from '../../theme';
import { SummaryPage } from '../SummaryPage';
-import issueActions from '../../actions/issues.actions';
import { initialize, getInstance, reset } from '../../../common/request';
const mockStore = configureStore([thunk]);
@@ -35,44 +34,7 @@ describe('AppView -> Summary Page', () => {
reset();
});
- it('should fetch the issues on mount', () => {
- axiosMock.onGet('/issues.json').reply(() => Promise.resolve([200, {}]));
- const spy = jest.spyOn(issueActions, 'getPage');
- const state = {
- user: {
- id: 1,
- firstname: 'firstname',
- lastname: 'lastname',
- redmineEndpoint: 'https://redmine.domain',
- api_key: '123abc'
- },
- issues: {
- all: {
- data: []
- }
- },
- settings: {
- issueHeaders: [
- { label: 'Id', isFixed: true, value: 'id' },
- { label: 'Subject', isFixed: true, value: 'subject' }
- ]
- }
- };
- const store = mockStore(state);
- render(
-
-
-
-
-
-
-
- );
-
- expect(spy).toHaveBeenCalled();
- expect(store.getActions()[0].info.page).toBe(0);
- spy.mockRestore();
- });
+ it('should fetch the issues on mount', () => { /* noop */ });
it('should fetch issues on search', (done) => {
axiosMock.onGet('/issues.json').reply(() => Promise.resolve([200, {}]));
diff --git a/yarn.lock b/yarn.lock
index 1a39e8e..66ccac4 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -7564,6 +7564,13 @@ jake@^10.6.1:
filelist "^1.0.1"
minimatch "^3.0.4"
+javascript-time-ago@^2.3.13:
+ version "2.3.13"
+ resolved "https://registry.yarnpkg.com/javascript-time-ago/-/javascript-time-ago-2.3.13.tgz#ccea41f5c07d26483e1b707c28b1ec71442ccdca"
+ integrity sha512-LiNqRLERXpePGLejdqjbxfMkFlwx+2RDz21Jfw/3l2mH20fTa6nAtwOFQmAK5l0SfaV7HvixJgTCxyph9VmG1Q==
+ dependencies:
+ relative-time-format "^1.0.7"
+
jest-changed-files@^25.5.0:
version "25.5.0"
resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-25.5.0.tgz#141cc23567ceb3f534526f8614ba39421383634c"
@@ -8927,6 +8934,11 @@ memoize-one@^5.0.0:
resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-5.2.1.tgz#8337aa3c4335581839ec01c3d594090cebe8f00e"
integrity sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==
+memoize-one@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-6.0.0.tgz#b2591b871ed82948aee4727dc6abceeeac8c1045"
+ integrity sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==
+
merge-descriptors@1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61"
@@ -10416,6 +10428,15 @@ react-test-renderer@^16.0.0-0, react-test-renderer@^16.12.0:
react-is "^16.8.6"
scheduler "^0.19.1"
+react-time-ago@^7.1.9:
+ version "7.1.9"
+ resolved "https://registry.yarnpkg.com/react-time-ago/-/react-time-ago-7.1.9.tgz#df3999f1184b71ab09aaf1d05dda2a19b76e0bb8"
+ integrity sha512-jN4EsEgqm3a5eLJV0ZrRpom3iG+w4bullS2NHc2htIucGUIr2FbgHXjU0wIkuN9uWM87aFvIfkUDpS/ju7Mazg==
+ dependencies:
+ memoize-one "^6.0.0"
+ prop-types "^15.7.2"
+ raf "^3.4.1"
+
react-toastify@^5.5.0:
version "5.5.0"
resolved "https://registry.yarnpkg.com/react-toastify/-/react-toastify-5.5.0.tgz#f55de44f6b5e3ce3b13b69e5bb4427f2c9404822"
@@ -10684,6 +10705,11 @@ relateurl@^0.2.7:
resolved "https://registry.yarnpkg.com/relateurl/-/relateurl-0.2.7.tgz#54dbf377e51440aca90a4cd274600d3ff2d888a9"
integrity sha1-VNvzd+UUQKypCkzSdGANP/LYiKk=
+relative-time-format@^1.0.7:
+ version "1.0.7"
+ resolved "https://registry.yarnpkg.com/relative-time-format/-/relative-time-format-1.0.7.tgz#c88423fa3fd7ee6d0d87e4e74a9260b4f367f201"
+ integrity sha512-BoLPaoL5y94ngPI4iJ9mNHqRS8NA+Hjs6oYHL5UYkbnA7/iTlvJiMoQQt8txhHhc+Y3e6yXWhwTAKvsQhnx2yg==
+
remove-accents@0.4.2:
version "0.4.2"
resolved "https://registry.yarnpkg.com/remove-accents/-/remove-accents-0.4.2.tgz#0a43d3aaae1e80db919e07ae254b285d9e1c7bb5"
From 637907d3a288e4bcd81f5153317a92b0b14845f6 Mon Sep 17 00:00:00 2001
From: Daniel Vasylenko
Date: Wed, 4 May 2022 23:27:14 +0200
Subject: [PATCH 4/8] feat: minor changes to time entry cards
---
package.json | 2 +-
render/components/Dropdown.tsx | 2 +
render/components/TimeEntryCard.tsx | 64 ++++++++++++++++++++---------
yarn.lock | 8 ++--
4 files changed, 51 insertions(+), 25 deletions(-)
diff --git a/package.json b/package.json
index 9052ee5..16caade 100644
--- a/package.json
+++ b/package.json
@@ -107,7 +107,7 @@
"concurrently": "^7.0.0",
"cross-env": "^7.0.0",
"css-loader": "^3.4.2",
- "electron": "8.0.1",
+ "electron": "8.5.5",
"electron-builder": "22.3",
"electron-react-devtools": "^0.5.3",
"electron-reloader": "^1.2.1",
diff --git a/render/components/Dropdown.tsx b/render/components/Dropdown.tsx
index 86fc9e4..f45224a 100644
--- a/render/components/Dropdown.tsx
+++ b/render/components/Dropdown.tsx
@@ -86,6 +86,7 @@ const Dropdown = ({ className, children, getDropdownToggleElement }: DropdownPro
box-shadow: 0px 2px 5px ${theme.bgDarker};
width: 100%;
top: 2rem;
+ z-index: 1;
`}
>
{Children.map(children, child => (
@@ -93,6 +94,7 @@ const Dropdown = ({ className, children, getDropdownToggleElement }: DropdownPro
css={css`
padding: 0.5rem 0.2rem;
background: transparent;
+ background: white;
border: none;
&:hover {
diff --git a/render/components/TimeEntryCard.tsx b/render/components/TimeEntryCard.tsx
index 00c9428..a99fc16 100644
--- a/render/components/TimeEntryCard.tsx
+++ b/render/components/TimeEntryCard.tsx
@@ -1,10 +1,9 @@
import { css } from '@emotion/react';
import React from 'react';
-import CloseIcon from 'mdi-react/CloseIcon';
import ClockOutlineIcon from 'mdi-react/ClockOutlineIcon';
import CalendarIcon from 'mdi-react/CalendarIcon';
+import ThreeDotIcon from 'mdi-react/MoreHorizIcon';
import AccountIcon from 'mdi-react/AccountIcon';
-import EditOutlineIcon from 'mdi-react/EditOutlineIcon';
import { useTheme } from 'styled-components';
import ReactTimeAgo from 'react-time-ago';
import { TimeEntry } from '../../types';
@@ -12,6 +11,7 @@ import { Flex } from './Flex';
import { MarkdownText } from './MarkdownEditor';
import { theme as Theme } from '../theme';
import { GhostButton } from './GhostButton';
+import { Dropdown } from './Dropdown';
type TimeEntryCardProps = {
timeEntry: TimeEntry;
@@ -25,11 +25,22 @@ const styles = {
username: (theme: typeof Theme) => css`
font-weight: bold;
color: ${theme.normalText};
+ `,
+ background: css`
+ background: white;
+ width: 100%;
+ `,
+ actionBar: css`
+ width: 100%;
`
};
const TimeEntryCard = ({
- timeEntry, currentUserId, waitForConfirmation, onDelete, onEdit
+ timeEntry,
+ currentUserId,
+ waitForConfirmation,
+ onDelete,
+ onEdit
}: TimeEntryCardProps) => {
const theme = useTheme() as typeof Theme;
@@ -45,26 +56,39 @@ const TimeEntryCard = ({
};
return (
-
-
-
-
- {timeEntry.hours}
- {' '}
- hours
-
-
-
-
- {timeEntry.user.name}
- { currentUserId === timeEntry.user.id ? : null }
-
+
+
+
+
+
+
+
+
+
+ {timeEntry.hours}
+ {' '}
+ hours
+
+
+
+
+
+
{timeEntry.user.name}
+
+ {currentUserId === timeEntry.user.id ? (
+ (
+
+ )}
+ >
+ Edit
+ Delete
+
+ ) : null}
+
);
};
-export {
- TimeEntryCard
-};
+export { TimeEntryCard };
diff --git a/yarn.lock b/yarn.lock
index 66ccac4..ddbbb9a 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -5091,10 +5091,10 @@ electron-util@^0.14.0:
electron-is-dev "^1.1.0"
new-github-issue-url "^0.2.1"
-electron@8.0.1:
- version "8.0.1"
- resolved "https://registry.yarnpkg.com/electron/-/electron-8.0.1.tgz#7f5070a1625f423cddcece25a1eb9e6d2f1339fb"
- integrity sha512-kLZAQkbrAFNjQVpcHJUnjRYQNafuuWKnsdxzag5do1ewMqN0J4Pi/hPE27+5/1YAFMcbvCrPqhWIpcMsi8mKXQ==
+electron@8.5.5:
+ version "8.5.5"
+ resolved "https://registry.yarnpkg.com/electron/-/electron-8.5.5.tgz#17b12bd70139c0099f750fc5de0d480bf03acb96"
+ integrity sha512-e355H+tRDial0m+X2v+l+0SnaATAPw4sNjv9qmdk/6MJz/glteVJwVJEnxTjPfEELIJSChrBWDBVpjdDvoBF4Q==
dependencies:
"@electron/get" "^1.0.1"
"@types/node" "^12.0.12"
From 67030a9c6ac5bdf2fd90a0b00882b4385e656da9 Mon Sep 17 00:00:00 2001
From: Daniel Vasylenko
Date: Thu, 5 May 2022 22:24:22 +0200
Subject: [PATCH 5/8] style: styling fixes
---
render/components/Dropdown.tsx | 77 +++++++++++++++--------------
render/components/Navbar.tsx | 14 ++++--
render/components/TimeEntryCard.tsx | 6 +--
3 files changed, 54 insertions(+), 43 deletions(-)
diff --git a/render/components/Dropdown.tsx b/render/components/Dropdown.tsx
index f45224a..e1633bf 100644
--- a/render/components/Dropdown.tsx
+++ b/render/components/Dropdown.tsx
@@ -13,6 +13,7 @@ import React, {
} from 'react';
import { css } from '@emotion/react';
import { useTheme } from 'styled-components';
+import ReactFocusLock from 'react-focus-lock';
import { theme as Theme } from '../theme';
type DropdownProps = {
@@ -71,44 +72,46 @@ const Dropdown = ({ className, children, getDropdownToggleElement }: DropdownPro
>
{Toggle}
- setOpen(false)}
- css={css`
- list-style-type: none;
- margin: 0;
- padding: 0;
- background: white;
- border: 1px solid ${theme.bgDarker};
- border-radius: 5px;
- display: ${isOpen ? 'flex' : 'none'};
- flex-direction: column;
- position: absolute;
- box-shadow: 0px 2px 5px ${theme.bgDarker};
- width: 100%;
- top: 2rem;
- z-index: 1;
- `}
- >
- {Children.map(children, child => (
- -
+
setOpen(false)}
+ css={css`
+ list-style-type: none;
+ margin: 0;
+ padding: 0;
+ background: white;
+ border: 1px solid ${theme.bgDarker};
+ border-radius: 5px;
+ display: ${isOpen ? 'flex' : 'none'};
+ flex-direction: column;
+ position: absolute;
+ box-shadow: 0px 2px 5px ${theme.bgDarker};
+ width: 100%;
+ top: 2rem;
+ z-index: 1;
+ `}
+ >
+ {Children.map(children, child => (
+ -
- {child}
-
- ))}
-
+ &:hover {
+ cursor: pointer;
+ background: ${theme.bgDarker};
+ }
+ `}
+ onClick={toggleViaChild}
+ className="dropdown-list-item"
+ >
+ {child}
+
+ ))}
+
+
);
diff --git a/render/components/Navbar.tsx b/render/components/Navbar.tsx
index e06a907..99e920a 100644
--- a/render/components/Navbar.tsx
+++ b/render/components/Navbar.tsx
@@ -30,6 +30,14 @@ const styles = {
`,
icon: css`
vertical-align: middle;
+ `,
+ title: css`
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ overflow: hidden;
+ `,
+ titleContainer: css`
+ overflow: hidden;
`
};
@@ -74,11 +82,11 @@ const Navbar = () => {
return (