diff --git a/src/App/index.tsx b/src/App/index.tsx index c350438..27138d4 100644 --- a/src/App/index.tsx +++ b/src/App/index.tsx @@ -159,10 +159,40 @@ function App() { }, }); + const handleStorageStateUpdate: typeof setStorageState = useCallback( + (val) => { + setStorageState((prevValue) => { + const newValue = typeof val === 'function' + ? val(prevValue) + : val; + + if ( + prevValue['timur-config'].value?.dailyJournalGrouping !== newValue['timur-config'].value?.dailyJournalGrouping + || prevValue['timur-config'].value?.dailyJournalAttributeOrder !== newValue['timur-config'].value?.dailyJournalAttributeOrder + ) { + const overriddenValue: typeof newValue = { + ...newValue, + 'timur-config': { + ...newValue['timur-config'], + value: { + ...(newValue['timur-config'].value ?? defaultConfigValue), + collapsedGroups: [], + }, + }, + }; + return overriddenValue; + } + + return newValue; + }); + }, + [], + ); + const storageContextValue = useMemo(() => ({ storageState, - setStorageState, - }), [storageState]); + setStorageState: handleStorageStateUpdate, + }), [storageState, handleStorageStateUpdate]); // Device Size diff --git a/src/components/SelectInputContainer/index.tsx b/src/components/SelectInputContainer/index.tsx index 028b4ff..192dda8 100644 --- a/src/components/SelectInputContainer/index.tsx +++ b/src/components/SelectInputContainer/index.tsx @@ -316,6 +316,7 @@ function SelectInputContainer< style={(searchText || !valueDisplay) ? undefined : { backgroundColor: valueBgColor ?? colorscheme[0][1], color: valueFgColor ?? colorscheme[0][0], + border: 'var(--width-separator-sm) solid var(--color-foreground)', }} id={inputId} name={name} diff --git a/src/index.css b/src/index.css index 959ae23..acb3396 100644 --- a/src/index.css +++ b/src/index.css @@ -23,15 +23,15 @@ --base-font-size: 1rem; - --font-size-2xs: calc(var(--base-font-size) * 0.625); - --font-size-xs: calc(var(--base-font-size) * 0.75); - --font-size-sm: calc(var(--base-font-size) * 0.875); - --font-size-md: var(--base-font-size); - --font-size-lg: calc(var(--base-font-size) * 1.25); - --font-size-xl: calc(var(--base-font-size) * 1.44); - --font-size-2xl: calc(var(--base-font-size) * 1.8); - --font-size-3xl: calc(var(--base-font-size) * 2.5); - --font-size-4xl: calc(var(--base-font-size) * 3.6); + --font-size-2xs: calc(var(--base-font-size) * 0.625); /* -1 */ + --font-size-xs: calc(var(--base-font-size) * 0.75); /* -0.5 */ + --font-size-sm: calc(var(--base-font-size) * 0.875); /* -0.25 */ + --font-size-md: var(--base-font-size); /* 0 */ + --font-size-lg: calc(var(--base-font-size) * 1.25); /* 0.5 */ + --font-size-xl: calc(var(--base-font-size) * 1.44); /* 1 */ + --font-size-2xl: calc(var(--base-font-size) * 1.8); /* 1.5 */ + --font-size-3xl: calc(var(--base-font-size) * 2.5); /* 2 */ + --font-size-4xl: calc(var(--base-font-size) * 3.6); /* 2.5 */ @media screen and (max-width: 40rem) { --base-font-size: 0.875rem; @@ -165,23 +165,23 @@ h1, h2, h3, h4, h5, h6 { } h1 { - font-size: var(--font-size-2xl); + font-size: var(--font-size-3xl); } h2 { - font-size: var(--font-size-xl); + font-size: var(--font-size-2xl); } h3 { - font-size: var(--font-size-lg); + font-size: var(--font-size-xl); } h4 { - font-size: var(--font-size-md); + font-size: var(--font-size-lg); } h5 { - font-size: var(--font-size-sm); + font-size: var(--font-size-md); } p { diff --git a/src/utils/common.ts b/src/utils/common.ts index 654d975..5d53fa1 100644 --- a/src/utils/common.ts +++ b/src/utils/common.ts @@ -255,6 +255,7 @@ type GroupedItem = { attribute: ATTRIBUTE; level: number; } | { + itemKey: string; type: 'list-item'; value: LIST_ITEM; level: number; @@ -273,18 +274,21 @@ export function groupListByAttributes( const groupedItems = list.flatMap((listItem, listIndex) => { if (listIndex === 0) { - const groupKey = getGroupKey(listItem, attributes); - const headings = attributes.map((attribute, i) => ({ - type: 'heading' as const, - value: listItem, - attribute, - level: i, - groupKey, - })); + const headings = attributes.map((attribute, i) => { + const groupKey = getGroupKey(listItem, attributes.slice(0, i + 1)); + return { + type: 'heading' as const, + value: listItem, + attribute, + level: i, + groupKey, + }; + }); return [ ...headings, { + itemKey: getGroupKey(listItem, attributes), type: 'list-item' as const, value: listItem, level: attributes.length, @@ -306,6 +310,7 @@ export function groupListByAttributes( if (attributeMismatchIndex === -1) { return [ { + itemKey: getGroupKey(listItem, attributes), type: 'list-item' as const, value: listItem, level: attributes.length, @@ -313,12 +318,12 @@ export function groupListByAttributes( ]; } - const groupKey = getGroupKey(listItem, attributes); const headings = attributes.map((attribute, i) => { if (i < attributeMismatchIndex) { return undefined; } + const groupKey = getGroupKey(listItem, attributes.slice(0, i + 1)); return { type: 'heading' as const, value: listItem, @@ -331,6 +336,7 @@ export function groupListByAttributes( return [ ...headings, { + itemKey: getGroupKey(listItem, attributes), type: 'list-item' as const, value: listItem, level: attributes.length, diff --git a/src/utils/constants.ts b/src/utils/constants.ts index 352e9ff..1e65564 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -10,6 +10,7 @@ export const defaultConfigValue: ConfigStorage = { indent: true, compactTextArea: false, checkboxForStatus: false, + enableCollapsibleGroups: false, startSidebarShown: window.innerWidth >= 900, endSidebarShown: false, dailyJournalGrouping: { @@ -22,6 +23,7 @@ export const defaultConfigValue: ConfigStorage = { { key: 'task', sortDirection: 1 }, { key: 'status', sortDirection: 1 }, ], + collapsedGroups: [], }; export const colorscheme: [string, string][] = [ diff --git a/src/utils/types.ts b/src/utils/types.ts index 5c31fa9..c390fd0 100644 --- a/src/utils/types.ts +++ b/src/utils/types.ts @@ -42,10 +42,12 @@ export type ConfigStorage = { checkboxForStatus: boolean, compactTextArea: boolean, indent: boolean, + enableCollapsibleGroups: boolean, dailyJournalAttributeOrder: DailyJournalAttribute[]; dailyJournalGrouping: DailyJournalGrouping; + collapsedGroups: string[], startSidebarShown: boolean, endSidebarShown: boolean, } diff --git a/src/views/DailyJournal/AddWorkItemDialog/index.tsx b/src/views/DailyJournal/AddWorkItemDialog/index.tsx index 089de64..5cf6029 100644 --- a/src/views/DailyJournal/AddWorkItemDialog/index.tsx +++ b/src/views/DailyJournal/AddWorkItemDialog/index.tsx @@ -88,7 +88,7 @@ function AddWorkItemDialog(props: Props) { { + setStoredConfig((prevConfig) => { + // FIXME: We should not need to add fallback + const prevValues = prevConfig.collapsedGroups ?? []; + + if (!prevValues.includes(value)) { + return { + ...prevConfig, + collapsedGroups: [...prevValues, value], + }; + } + return { + ...prevConfig, + collapsedGroups: prevValues.filter((prevValue) => prevValue !== value), + }; + }); + }, + [setStoredConfig], + ); + + const { + dailyJournalAttributeOrder, + dailyJournalGrouping: { + groupLevel, + joinLevel, + }, + indent, + enableCollapsibleGroups, + collapsedGroups, + } = storedConfig; const getWorkItemLabelFromAttr = useCallback(( item: WorkItem, @@ -131,15 +171,6 @@ function DayView(props: Props) { [workItems], ); - const { - dailyJournalAttributeOrder, - dailyJournalGrouping: { - groupLevel, - joinLevel, - }, - indent, - } = storedConfig; - const groupedItems = useMemo(() => { if (isNotDefined(taskById) || isNotDefined(workItems)) { return []; @@ -157,7 +188,7 @@ function DayView(props: Props) { ), ); - return groupListByAttributes( + const result = groupListByAttributes( sortedWorkItems, dailyJournalAttributeOrder.slice(0, groupLevel), (a, b, attr) => { @@ -171,6 +202,7 @@ function DayView(props: Props) { return values; }, ); + return result; }, [ taskById, workItems, @@ -209,6 +241,15 @@ function DayView(props: Props) {
{groupedItems.map((groupedItem) => { if (groupedItem.type === 'heading') { + const hidden = enableCollapsibleGroups + && collapsedGroups.some((groupKey) => ( + groupedItem.groupKey !== groupKey + && groupedItem.groupKey.startsWith(groupKey) + )); + if (hidden) { + return null; + } + // Main Heading // NOTE: Need to add 1 as groupLevel and level starts from 1 and 0 resp. if (groupedItem.level + 1 < (groupLevel - joinLevel + 1)) { @@ -221,11 +262,13 @@ function DayView(props: Props) { groupedItem.attribute, ); - const Heading = `h${bound(groupedItem.level + 2, 2, 4)}` as unknown as ElementType; + const headingLevel = bound(groupedItem.level + 2, 2, 4); + const Heading = `h${headingLevel}` as unknown as ElementType; + const key = `heading-${groupedItem.groupKey}`; return ( {indent && } @@ -237,6 +280,18 @@ function DayView(props: Props) { /> )} {headingText} + {enableCollapsibleGroups && ( + + )} ); } @@ -245,10 +300,11 @@ function DayView(props: Props) { // NOTE: We only need to show one subheading after the main headings // NOTE: Need to add 1 as groupLevel and level starts from 1 and 0 resp. if (groupedItem.level + 1 === groupLevel) { + const key = `sub-heading-${groupedItem.groupKey}`; return (

{indent && ( + {i > (groupLevel - joinLevel) && (
)} @@ -289,6 +345,18 @@ function DayView(props: Props) { ); })} + {enableCollapsibleGroups && ( + + )}

); } @@ -300,6 +368,18 @@ function DayView(props: Props) { if (!taskDetails) { return null; } + const hidden = enableCollapsibleGroups + && collapsedGroups.some( + (groupKey) => groupedItem.itemKey.startsWith(groupKey), + ); + if (hidden) { + return null; + } + + const itemErrored = groupedItem.value.status !== 'TODO' && ( + isNotDefined(groupedItem.value.type) + || isNotDefined(groupedItem.value.duration) + ); return (
)}
+ + Other Settings +
); } diff --git a/src/views/DailyJournal/index.tsx b/src/views/DailyJournal/index.tsx index e68d19c..fa6045f 100644 --- a/src/views/DailyJournal/index.tsx +++ b/src/views/DailyJournal/index.tsx @@ -599,12 +599,13 @@ export function Component() { // FIXME: memoize this const filteredWorkItems = workItems.filter((item) => item.date === selectedDate); - const entriesWithoutTask = filteredWorkItems - .filter((item) => isNotDefined(item.type) && item.status !== 'TODO') - .length; - const entriesWithoutHours = filteredWorkItems - .filter((item) => isNotDefined(item.duration) && item.status !== 'TODO') - .length; + const entriesWithError = filteredWorkItems + .filter((item) => ( + item.status !== 'TODO' && ( + isNotDefined(item.type) + || isNotDefined(item.duration) + ) + )).length; const leaveType = myTimeEntriesResult.data?.private.journal?.leaveType; const wfhType = myTimeEntriesResult.data?.private.journal?.wfhType; @@ -677,19 +678,11 @@ export function Component() { > - {entriesWithoutTask > 0 && ( + {entriesWithError > 0 && (
- {`${entriesWithoutTask} uncategorized`} - -
- )} - {entriesWithoutHours > 0 && ( -
- - - {`${entriesWithoutHours} untracked`} + {`${entriesWithError} issues`}
)} diff --git a/src/views/Settings/index.tsx b/src/views/Settings/index.tsx index d5a4841..8e9d8b8 100644 --- a/src/views/Settings/index.tsx +++ b/src/views/Settings/index.tsx @@ -91,6 +91,12 @@ export function Component() { value={storedConfig.indent} onChange={setConfigFieldValue} /> +