Skip to content
Merged
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
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ on:

env:
APP_TITLE: Timur
APP_ENVIRONMENT: testing
APP_GRAPHQL_CODEGEN_ENDPOINT: ./backend/schema.graphql
APP_GRAPHQL_DOMAIN: https://timur-fake-endpoint.com
APP_UMAMI_SRC: https://umami-fake-endpoint.com/script.js
APP_UMAMI_ID: fake-umami-id
APP_SENTRY_DSN: htts://dsn-fake@sentry.com/9999
APP_ENVIRONMENT: testing
GITHUB_WORKFLOW: true

jobs:
Expand Down
2 changes: 1 addition & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,4 @@ services:
volumes:
- ../:/code
ports:
- 127.0.0.1:3000:3000
- 0.0.0.0:3000:3000
2 changes: 0 additions & 2 deletions src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,6 @@
--font-size-4xl: calc(var(--base-font-size) * 3.6); /* 2.5 */

@media screen and (max-width: 40rem) {
--base-font-size: 0.875rem;

--font-size-2xs: calc(var(--base-font-size) * 0.75);
--font-size-xs: calc(var(--base-font-size) * 0.875);
--font-size-sm: calc(var(--base-font-size) * 0.9375);
Expand Down
186 changes: 111 additions & 75 deletions src/views/DailyJournal/DayView/WorkItemRow/index.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
import {
useCallback,
useContext,
useEffect,
useMemo,
useRef,
useState,
} from 'react';
import {
RiDeleteBin2Line,
RiEditBoxLine,
RiFileCopyLine,
RiMoreLine,
RiSwap2Line,
} from 'react-icons/ri';
import {
TbCalendarPlus,
TbCalendarRepeat,
} from 'react-icons/tb';
import {
_cs,
isDefined,
Expand All @@ -19,9 +22,8 @@ import {

import Button from '#components/Button';
import Checkbox from '#components/Checkbox';
import ConfirmButton from '#components/ConfirmButton';
import Dialog from '#components/Dialog';
import DropdownMenu from '#components/DropdownMenu';
import DropdownMenuItem from '#components/DropdownMenuItem';
import DurationInput from '#components/DurationInput';
import MonthlyCalendar from '#components/MonthlyCalendar';
import SelectInput from '#components/SelectInput';
Expand All @@ -45,6 +47,9 @@ import styles from './styles.module.css';
type WorkItemTypeOption = EnumsQuery['enums']['TimeEntryType'][number];
type WorkItemStatusOption = EnumsQuery['enums']['TimeEntryStatus'][number];

// eslint-disable-next-line @typescript-eslint/no-empty-function
const noOp = () => {};

function taskKeySelector(item: Task) {
return item.id;
}
Expand Down Expand Up @@ -84,6 +89,8 @@ interface Props {
workItem: WorkItem;
tasks: Task[] | undefined;
contractId: string | undefined;
isExpanded?: boolean;
onToggleExpand?: (clientId: string | undefined) => void;

onClone?: (clientId: string, override?: Partial<WorkItem>) => void;
onChange?: (clientId: string, ...entries: EntriesAsList<WorkItem>) => void;
Expand All @@ -96,6 +103,8 @@ function WorkItemRow(props: Props) {
workItem,
tasks,
contractId,
isExpanded = false,
onToggleExpand,
onClone,
onDelete,
onChange,
Expand All @@ -105,6 +114,7 @@ function WorkItemRow(props: Props) {
const { screen } = useContext(SizeContext);

const inputRef = useFocusClient<HTMLTextAreaElement>(workItem.clientId);
const rowRef = useRef<HTMLDivElement>(null);
const [config] = useLocalStorage('timur-config');

const setFieldValue = useCallback(
Expand Down Expand Up @@ -188,6 +198,12 @@ function WorkItemRow(props: Props) {
[onClone, workItem.clientId],
);

const handleToggleExpand = useCallback(() => {
if (screen !== 'desktop' && onToggleExpand) {
onToggleExpand(workItem.clientId);
}
}, [screen, onToggleExpand, workItem.clientId]);

const statusInput = config.checkboxForStatus ? (
<Checkbox
checkmarkClassName={_cs(
Expand Down Expand Up @@ -221,27 +237,34 @@ function WorkItemRow(props: Props) {
options={filteredTaskList}
keySelector={taskKeySelector}
labelSelector={taskLabelSelector}
// colorSelector={defaultColorSelector}
onChange={setFieldValue}
value={workItem.task}
nonClearable
/>
);

const descriptionInput = (
<TextArea
className={styles.description}
inputClassName={_cs(
config.enableStrikethrough && workItem.status === 'DONE' && styles.strike,
)}
inputElementRef={inputRef}
name="description"
title="Description"
value={workItem.description}
onChange={setFieldValue}
placeholder="Description"
compact={config.compactTextArea}
/>
<div
className={styles.descriptionWrapper}
onClick={handleToggleExpand}
onKeyDown={noOp}
role="button"
tabIndex={0}
>
<TextArea
className={styles.description}
inputClassName={_cs(
config.enableStrikethrough && workItem.status === 'DONE' && styles.strike,
)}
inputElementRef={inputRef}
name="description"
title="Description"
value={workItem.description}
onChange={setFieldValue}
placeholder="Description"
compact={config.compactTextArea}
/>
</div>
);

const typeInput = (
Expand Down Expand Up @@ -280,74 +303,87 @@ function WorkItemRow(props: Props) {
>
<RiFileCopyLine />
</Button>
<DropdownMenu
label={<RiMoreLine />}
withoutDropdownIcon
variant="transparent"
persistent
title="Show additional entry options"
<Button
variant="quaternary"
name={workItem.clientId}
title="Copy this entry to another day"
onClick={handleCopyDialogOpen}
spacing="xs"
>
<DropdownMenuItem
type="button"
name={workItem.clientId}
title="Edit this entry"
onClick={undefined}
icons={<RiEditBoxLine />}
disabled
>
Edit entry
</DropdownMenuItem>
<DropdownMenuItem
type="button"
name={workItem.clientId}
title="Move this entry to another day"
onClick={handleCopyDialogOpen}
icons={<RiFileCopyLine />}
>
Copy to another day
</DropdownMenuItem>
<DropdownMenuItem
type="button"
name={workItem.clientId}
title="Move this entry to another day"
onClick={handleMoveDialogOpen}
icons={<RiSwap2Line />}
>
Move to another day
</DropdownMenuItem>
<DropdownMenuItem
type="confirm-button"
name={workItem.clientId}
title="Delete this entry"
onClick={onDelete}
confirmHeading="Delete entry"
confirmDescription={(
<div>
<p>
Do you want to delete this entry?
</p>
<p>
This action cannot be reverted.
</p>
</div>
)}
icons={<RiDeleteBin2Line />}
>
Delete entry
</DropdownMenuItem>
</DropdownMenu>
<TbCalendarPlus />
</Button>
<Button
name={workItem.clientId}
variant="quaternary"
title="Move this entry to another day"
onClick={handleMoveDialogOpen}
spacing="xs"
>
<TbCalendarRepeat />
</Button>
<ConfirmButton
name={workItem.clientId}
title="Delete this entry"
onClick={onDelete}
confirmHeading="Delete entry"
variant="quaternary"
spacing="xs"
confirmDescription={(
<div>
<p>
Do you want to delete this entry?
</p>
<p>
This action cannot be reverted.
</p>
</div>
)}
>
<RiDeleteBin2Line />
</ConfirmButton>
</div>
);

const { year, month } = useContext(DateContext);

useEffect(() => {
if (screen !== 'desktop' && isExpanded && onToggleExpand) {
const handleClickOutside = (e: MouseEvent) => {
const target = e.target as HTMLElement;

// Check if click is outside this row
if (rowRef.current && !rowRef.current.contains(target)) {
// Check if the click is on another work item row
const clickedOnAnotherWorkItem = target.closest(`.${styles.workItemRow}`);

// Only close if NOT clicking on another work item
if (!clickedOnAnotherWorkItem) {
onToggleExpand(undefined);
}
}
};

// Add listener on next tick to avoid immediate closure
setTimeout(() => {
document.addEventListener('mousedown', handleClickOutside);
}, 0);

return () => {
document.removeEventListener('mousedown', handleClickOutside);
};
}
return undefined;
}, [screen, isExpanded, onToggleExpand]);

return (
<>
<div
ref={rowRef}
role="listitem"
className={_cs(
styles.workItemRow,
config.checkboxForStatus && styles.checkboxForStatus,
isExpanded && styles.expanded,
className,
)}
>
Expand Down Expand Up @@ -378,7 +414,7 @@ function WorkItemRow(props: Props) {
open={isDefined(dialogState)}
mode="center"
onClose={handleDialogClose}
heading="Select date"
heading={dialogState === 'move' ? 'Move to another day' : 'Clone to another day'}
contentClassName={styles.modalContent}
className={styles.calendarDialog}
size="auto"
Expand Down
25 changes: 9 additions & 16 deletions src/views/DailyJournal/DayView/WorkItemRow/styles.module.css
Original file line number Diff line number Diff line change
@@ -1,63 +1,53 @@
.work-item-row {
display: grid;
align-items: flex-start;
grid-template-columns: 5rem 2fr 5fr 9rem 4rem 4rem;
grid-template-columns: 5rem 2fr 5fr 9rem 4rem 9rem;
padding: var(--spacing-xs) var(--spacing-sm);
gap: var(--spacing-xs);

&.checkbox-for-status {
grid-template-columns: 1rem 2fr 5fr 10rem 4rem 4rem;
grid-template-columns: 1rem 2fr 5fr 10rem 4rem 9rem;
}
.description-wrapper {
cursor: pointer;
}

.description {
.strike {
text-decoration: line-through;
}
}

.actions {
display: flex;
align-items: baseline;
flex-wrap: wrap;
justify-content: flex-end;
gap: var(--spacing-xs);
}

/* TODO: sync this color from colorscheme */
.status-checkbox {
&.doing {
color: #039BE5;
}

&.done {
color: #7CB342;
}
}

.task {
min-width: 8rem;
}

&:focus-within {
background-color: var(--color-tertiary);
}

@media screen and (max-width: 900px) {
grid-template-columns: auto;
gap: var(--spacing-sm);

&.checkbox-for-status {
grid-template-columns: 1rem 1fr;
}

.compact-options {
display: flex;
flex-direction: column;
gap: var(--spacing-xs);
grid-column: 1 / -1;
}

&:not(:focus-within) {
&:not(.expanded) {
.compact-options,
.task,
.type,
Expand All @@ -67,6 +57,9 @@
display: none;
}
}
&.expanded {
background-color: var(--color-tertiary);
}
}
}

Expand Down
Loading