mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-18 05:34:09 +00:00
Refact/project (#189)
* refactor: cleanup project entry point * refactor: remove unused * refactor: folder restructure
This commit is contained in:
@@ -12,7 +12,7 @@ import { Tooltip } from '@chakra-ui/tooltip';
|
||||
import { FiPower } from '@react-icons/all-files/fi/FiPower';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { LoggingContext } from '../../../app/context/LoggingContext';
|
||||
import { LoggingContext } from '../../context/LoggingContext';
|
||||
|
||||
export default function QuitIconBtn(props) {
|
||||
const { clickHandler, size = 'lg', ...rest } = props;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@use '../../../styles/main' as *;
|
||||
@use '../../../theme/main' as *;
|
||||
|
||||
.header,
|
||||
.headerRoll {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@use '../../../styles/main' as *;
|
||||
@use '../../../theme/main' as *;
|
||||
|
||||
/* Common */
|
||||
.countdownClock,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/* eslint-disable react/destructuring-assignment */
|
||||
import React from 'react';
|
||||
|
||||
import { LoggingContext } from '../../../app/context/LoggingContext';
|
||||
import { LoggingContext } from '../../context/LoggingContext';
|
||||
|
||||
import style from './ErrorBoundary.module.scss';
|
||||
const appVersion = require('../../../../package.json').version;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@use '../../../styles/main' as *;
|
||||
@use '../../../theme/main' as *;
|
||||
|
||||
.errorContainer {
|
||||
width: 100%;
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import React, { useCallback, useContext } from 'react';
|
||||
import EditableTimer from 'common/input/EditableTimer';
|
||||
import EditableTimer from 'common/components/input/EditableTimer';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { LoggingContext } from '../../../app/context/LoggingContext';
|
||||
import { validateTimes } from '../../../app/entryValidator';
|
||||
import { LoggingContext } from '../../context/LoggingContext';
|
||||
import { validateTimes } from '../../utils/entryValidator';
|
||||
|
||||
export default function EventTimes(props) {
|
||||
const { actionHandler, delay, timeStart, timeEnd, previousEnd } = props;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import React, { useCallback, useContext } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { LoggingContext } from '../../../app/context/LoggingContext';
|
||||
import { validateTimes } from '../../../app/entryValidator';
|
||||
import { LoggingContext } from '../../context/LoggingContext';
|
||||
import { validateTimes } from '../../utils/entryValidator';
|
||||
|
||||
import Times from './Times';
|
||||
import TimesDelayed from './TimesDelayed';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import EditableTimer from '../../input/EditableTimer';
|
||||
import EditableTimer from '../input/EditableTimer';
|
||||
|
||||
import style from './Times.module.scss'
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@use '../../../styles/main' as *;
|
||||
@use '../../../theme/main' as *;
|
||||
|
||||
.label {
|
||||
font-size: 0.75em;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import EditableTimer from '../../input/EditableTimer';
|
||||
import { stringFromMillis } from '../../utils/time';
|
||||
import EditableTimer from '../input/EditableTimer';
|
||||
|
||||
import style from './Times.module.scss'
|
||||
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import { Textarea } from '@chakra-ui/react';
|
||||
import autosize from 'autosize/dist/autosize';
|
||||
|
||||
export const AutoTextArea = (props) => {
|
||||
const ref = useRef();
|
||||
|
||||
useEffect(() => {
|
||||
const node = ref.current;
|
||||
autosize(ref.current);
|
||||
return () => {
|
||||
autosize.destroy(node);
|
||||
};
|
||||
}, []);
|
||||
|
||||
|
||||
return (
|
||||
<Textarea
|
||||
overflow='hidden'
|
||||
w='100%'
|
||||
resize='none'
|
||||
ref={ref}
|
||||
transition='height none'
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,62 @@
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import { Editable, EditableInput, EditablePreview } from '@chakra-ui/react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { clamp } from '../../utils/math';
|
||||
|
||||
import style from './TimeInput.module.css';
|
||||
|
||||
const inputProps = {
|
||||
width: 20,
|
||||
fontWeight: 400,
|
||||
backgroundColor: 'rgba(0,0,0,0.05)',
|
||||
color: '#fff',
|
||||
border: '1px solid #ecc94b55',
|
||||
borderRadius: '8px',
|
||||
placeholder: '-',
|
||||
textAlign: 'center',
|
||||
};
|
||||
|
||||
export default function DelayInput(props) {
|
||||
const { actionHandler, value } = props;
|
||||
const [_value, setValue] = useState(value);
|
||||
|
||||
useEffect(() => {
|
||||
if (value == null) return;
|
||||
setValue(value);
|
||||
}, [value]);
|
||||
|
||||
const handleSubmit = useCallback(
|
||||
(newValue) => {
|
||||
if (newValue === value) return;
|
||||
if (newValue === '') setValue(0);
|
||||
|
||||
// convert to ms and updates
|
||||
const msVal = clamp(newValue, -60, 60) * 60000;
|
||||
actionHandler('update', { field: 'duration', value: msVal });
|
||||
},
|
||||
[actionHandler, value]
|
||||
);
|
||||
|
||||
const labelText = `minutes ${value >= 0 ? 'delayed' : 'ahead'}`;
|
||||
|
||||
return (
|
||||
<div className={style.timeInput}>
|
||||
<Editable
|
||||
{...inputProps}
|
||||
value={_value}
|
||||
onChange={(v) => setValue(v)}
|
||||
onSubmit={(v) => handleSubmit(v)}
|
||||
>
|
||||
<EditablePreview />
|
||||
<EditableInput type='number' min='-60' max='60' />
|
||||
</Editable>
|
||||
<span className={style.label}>{labelText}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
DelayInput.propTypes = {
|
||||
actionHandler: PropTypes.func,
|
||||
value: PropTypes.number,
|
||||
};
|
||||
@@ -0,0 +1,56 @@
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import { Editable, EditableInput, EditablePreview } from '@chakra-ui/editable';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import style from './EditableText.module.scss';
|
||||
|
||||
export default function EditableText(props) {
|
||||
const { label, defaultValue, placeholder, submitHandler, maxchar = 40, ...rest } = props;
|
||||
const [text, setText] = useState(defaultValue || '');
|
||||
|
||||
useEffect(() => {
|
||||
if (defaultValue == null) setText('');
|
||||
else setText(defaultValue);
|
||||
}, [defaultValue]);
|
||||
|
||||
const handleSubmit = useCallback((submittedVal) => {
|
||||
// No need to update if it hasnt changed
|
||||
if (submittedVal === defaultValue) return;
|
||||
// submit a cleaned up version of the string
|
||||
const cleanVal = submittedVal.trim();
|
||||
submitHandler(cleanVal);
|
||||
|
||||
if (cleanVal !== submittedVal) {
|
||||
setText(cleanVal);
|
||||
}
|
||||
},[defaultValue, submitHandler]);
|
||||
|
||||
const handleChange = useCallback((val) => {
|
||||
if (val.length < maxchar) setText(val);
|
||||
},[maxchar]);
|
||||
|
||||
return (
|
||||
<div className={style.block}>
|
||||
<span className={style.title}>{label}</span>
|
||||
<Editable
|
||||
onChange={(v) => handleChange(v)}
|
||||
onSubmit={(v) => handleSubmit(v)}
|
||||
value={text}
|
||||
placeholder={placeholder}
|
||||
className={style.inline}
|
||||
{...rest}
|
||||
>
|
||||
<EditablePreview className={text === '' ? style.preview : ''} />
|
||||
<EditableInput />
|
||||
</Editable>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
EditableText.propTypes = {
|
||||
label: PropTypes.string,
|
||||
defaultValue: PropTypes.string,
|
||||
placeholder: PropTypes.string,
|
||||
submitHandler: PropTypes.func.isRequired,
|
||||
maxchar: PropTypes.number,
|
||||
};
|
||||
@@ -0,0 +1,28 @@
|
||||
@use '../../../theme/main' as *;
|
||||
|
||||
.block {
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
|
||||
.title {
|
||||
padding-left: 1em;
|
||||
font-size: 0.75em;
|
||||
color: $label-gray;
|
||||
display: inline-block;
|
||||
min-width: 6em;
|
||||
}
|
||||
|
||||
.preview {
|
||||
color: $bg-gray-500;
|
||||
}
|
||||
|
||||
.inline {
|
||||
display: inline;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
import React, { useCallback, useContext, useEffect, useState } from 'react';
|
||||
import { Editable, EditableInput, EditablePreview } from '@chakra-ui/editable';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { LoggingContext } from '../../context/LoggingContext';
|
||||
import { forgivingStringToMillis } from '../../utils/dateConfig';
|
||||
import { stringFromMillis } from '../../utils/time';
|
||||
|
||||
import style from './EditableTimer.module.scss';
|
||||
|
||||
export default function EditableTimer(props) {
|
||||
const { name, actionHandler, time = 0, delay, validate, previousEnd } = props;
|
||||
const { emitError } = useContext(LoggingContext);
|
||||
const [value, setValue] = useState('');
|
||||
|
||||
|
||||
const handleSubmit = useCallback((value) => {
|
||||
// Check if there is anything there
|
||||
if (value === '') return false;
|
||||
|
||||
let newValMillis = 0;
|
||||
|
||||
// check for known aliases
|
||||
if (value === 'p' || value === 'prev' || value === 'previous') {
|
||||
// string to pass should be the time of the end before
|
||||
if (previousEnd != null) {
|
||||
newValMillis = previousEnd;
|
||||
}
|
||||
} else if (value.startsWith('+') || value.startsWith('p+') || value.startsWith('p +')) {
|
||||
// string to pass should add to the end before
|
||||
const val = value.substring(1);
|
||||
newValMillis = previousEnd + forgivingStringToMillis(val);
|
||||
} else {
|
||||
// convert entered value to milliseconds
|
||||
newValMillis = forgivingStringToMillis(value);
|
||||
}
|
||||
|
||||
// Time now and time submittedVal
|
||||
const originalMillis = time + delay;
|
||||
|
||||
// check if time is different from before
|
||||
if (newValMillis === originalMillis) return false;
|
||||
|
||||
// validate with parent
|
||||
if (!validate(name, newValMillis)) return false;
|
||||
|
||||
// update entry
|
||||
actionHandler('update', { field: name, value: newValMillis });
|
||||
|
||||
return true;
|
||||
},[actionHandler, delay, name, previousEnd, time, validate]);
|
||||
|
||||
// prepare time fields
|
||||
const validateValue = useCallback((value) => {
|
||||
const success = handleSubmit(value);
|
||||
if (success) {
|
||||
const ms = forgivingStringToMillis(value);
|
||||
setValue(stringFromMillis(ms + delay));
|
||||
} else {
|
||||
setValue(stringFromMillis(time + delay));
|
||||
}
|
||||
},[delay, handleSubmit, time]);
|
||||
|
||||
useEffect(() => {
|
||||
if (time == null) return;
|
||||
try {
|
||||
setValue(stringFromMillis(time + delay));
|
||||
} catch (error) {
|
||||
emitError(`Unable to parse date: ${error.text}`);
|
||||
}
|
||||
}, [time, delay, emitError]);
|
||||
|
||||
const isDelayed = delay != null && delay !== 0;
|
||||
|
||||
return (
|
||||
<Editable
|
||||
data-testid='editable-timer'
|
||||
onChange={(v) => setValue(v)}
|
||||
onSubmit={(v) => validateValue(v)}
|
||||
onCancel={() => setValue(stringFromMillis(time + delay, true))}
|
||||
value={value}
|
||||
className={isDelayed ? style.delayedEditable : style.editable}
|
||||
>
|
||||
<EditablePreview />
|
||||
<EditableInput type='text' placeholder='--:--:--' data-testid='editable-timer-input' />
|
||||
</Editable>
|
||||
);
|
||||
}
|
||||
|
||||
EditableTimer.propTypes = {
|
||||
name: PropTypes.string.isRequired,
|
||||
actionHandler: PropTypes.func.isRequired,
|
||||
time: PropTypes.number,
|
||||
delay: PropTypes.number,
|
||||
validate: PropTypes.func.isRequired,
|
||||
previousEnd: PropTypes.number,
|
||||
};
|
||||
@@ -0,0 +1,17 @@
|
||||
@use '../../../theme/main'as *;
|
||||
|
||||
.editable,
|
||||
.delayedEditable {
|
||||
background-color: $input-bg;
|
||||
border: $input-border;
|
||||
width: 6.5em;
|
||||
letter-spacing: 1px;
|
||||
height: fit-content;
|
||||
|
||||
text-align: center;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.delayedEditable {
|
||||
border: $input-delayed-border;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
.timeInput {
|
||||
display: flex;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.label {
|
||||
padding-left: 0.8em;
|
||||
align-self: center;
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
import React from 'react';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
|
||||
import EditableTimer from '../EditableTimer';
|
||||
|
||||
const renderEditableTimer = (
|
||||
name = 'test',
|
||||
actionHandler = () => undefined,
|
||||
validate = () => undefined
|
||||
) => render(<EditableTimer name={name} actionHandler={actionHandler} validate={validate} />);
|
||||
|
||||
describe('test EditableTimer component', () => {
|
||||
const testName = "test";
|
||||
const actionHandler = jest.fn();
|
||||
const validate = jest.fn();
|
||||
|
||||
renderEditableTimer(testName, actionHandler, validate);
|
||||
const editableTimer = screen.getByTestId('editable-timer');
|
||||
const editableInput = screen.getByTestId('editable-timer-input');
|
||||
|
||||
// skipping for now as error seems to come from beta library
|
||||
it.skip('renders correctly', () => {
|
||||
expect(editableTimer).toBeInTheDocument();
|
||||
expect(editableInput).toBeInTheDocument();
|
||||
|
||||
const myTypedString = 'verylongandcool'
|
||||
userEvent.type(editableInput, myTypedString);
|
||||
expect(editableInput).toHaveValue(myTypedString);
|
||||
|
||||
userEvent.type(editableInput, '{enter}');
|
||||
|
||||
// no previous is given, defaults to 0
|
||||
expect(validate).toHaveBeenCalledWith(testName, 0);
|
||||
});
|
||||
});
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import { clamp } from 'app/utils/math';
|
||||
import { clamp } from 'common/utils/math';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import styles from './MyProgressBar.module.scss';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@use '../../../styles/main' as *;
|
||||
@use '../../../theme/main' as *;
|
||||
|
||||
.progress,
|
||||
.progressCountdown {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@use '../../../styles/main' as *;
|
||||
@use '../../../theme/main' as *;
|
||||
|
||||
.navContainer {
|
||||
position: absolute;
|
||||
|
||||
@@ -4,7 +4,7 @@ import { HStack, PinInput, PinInputField } from '@chakra-ui/react';
|
||||
import { FiCheck } from '@react-icons/all-files/fi/FiCheck';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { AppContext } from '../../../app/context/AppContext';
|
||||
import { AppContext } from '../../context/AppContext';
|
||||
|
||||
import style from './ProtectRoute.module.scss';
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
@use '../../../styles/main' as *;
|
||||
@use '../../../styles/mixins' as *;
|
||||
@use '../../../theme/main' as *;
|
||||
@use '../../../theme/mixins' as *;
|
||||
|
||||
.container {
|
||||
background: $bg-black;
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
import React from 'react';
|
||||
import { ReactComponent as Emptyimage } from 'assets/images/empty.svg';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import style from './Empty.module.scss';
|
||||
|
||||
export default function Empty(props) {
|
||||
const { text, dark, ...rest } = props;
|
||||
return (
|
||||
<div className={`${style.emptyContainer} ${dark ? style.dark : ''}`} {...rest}>
|
||||
<Emptyimage className={style.empty} />
|
||||
<span className={style.text}>{text}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Empty.propTypes = {
|
||||
text: PropTypes.string,
|
||||
dark: PropTypes.bool,
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
@use '../../../theme/main' as *;
|
||||
|
||||
.emptyContainer {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
text-align: center;
|
||||
color: $bg-black-300;
|
||||
|
||||
.empty {
|
||||
width: 100%;
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
.text {
|
||||
font-weight: 600;
|
||||
font-size: 2em;
|
||||
}
|
||||
|
||||
&.dark {
|
||||
background: $bg-black;
|
||||
color: $bg-gray-500;
|
||||
|
||||
.empty {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useInterval } from 'app/hooks/useInterval';
|
||||
import { useInterval } from 'common/hooks/useInterval';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import Empty from '../../state/Empty';
|
||||
import Empty from '../state/Empty';
|
||||
|
||||
import TodayItem from './TodayItem';
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@use '../../../styles/main' as *;
|
||||
@use '../../../theme/main' as *;
|
||||
|
||||
.entries {
|
||||
width: 100%;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@use '../../../styles/main' as *;
|
||||
@use '../../../theme/main' as *;
|
||||
|
||||
.label {
|
||||
@include card-label;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@use '../../../styles/main' as *;
|
||||
@use '../../../theme/main' as *;
|
||||
|
||||
.label {
|
||||
@include card-label;
|
||||
|
||||
Reference in New Issue
Block a user