Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feature] Implementing Dashboard Cards/Widgets #1919

Draft
wants to merge 53 commits into
base: develop
Choose a base branch
from
Draft
Changes from 1 commit
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
a27c485
Implementing cards on the dashboard
Civolilah Jul 22, 2024
585126c
Implemented saving dashboard fields
Civolilah Jul 22, 2024
f60902c
Implementing cards on the dashboard
Civolilah Jul 28, 2024
7e6c4e8
Implemented basic displaying card on the dashboard
Civolilah Jul 28, 2024
7ffe6b2
Merge branch 'develop' into feature/1605-dashboard-charts
Civolilah Aug 1, 2024
1b9b006
Merge branch 'develop' into feature/1605-dashboard-charts
Civolilah Aug 1, 2024
90e0aee
Merge branch 'develop' into feature/1605-dashboard-charts
Civolilah Aug 2, 2024
b7e356f
Merge branch 'develop' into feature/1605-dashboard-charts
Civolilah Aug 6, 2024
9351879
Added currency_id in the payload for cards calculation endpoint
Civolilah Aug 6, 2024
58e7ef3
Implementing grid resize system on the dashboard
Civolilah Aug 9, 2024
cfc7f2b
Merge branch 'develop' into feature/1605-dashboard-charts
Civolilah Aug 13, 2024
9aafdef
Implemented logic for resizing and dragging cards on the dashboard
Civolilah Aug 19, 2024
eb6efe0
Made change from charts issue fixes
Civolilah Aug 24, 2024
9d808ad
Merge branch 'develop' into feature/1605-dashboard-charts
Civolilah Aug 24, 2024
263f37d
Fixed issues with dashboard UI
Civolilah Aug 24, 2024
252886e
Implemented responsive system
Civolilah Sep 2, 2024
966ca65
Merge branch 'develop' into feature/1605-dashboard-charts
Civolilah Sep 3, 2024
edf8b08
Implemented saving customized dashboard settings
Civolilah Sep 3, 2024
ad56f04
Merge branch 'develop' into feature/1605-dashboard-charts
Civolilah Sep 4, 2024
a28c916
Implemented adding cards into responsive grid layout system
Civolilah Sep 5, 2024
1129328
Merge branch 'develop' into feature/1605-dashboard-charts
Civolilah Sep 5, 2024
570ba7c
Merge branch 'develop' into feature/1605-dashboard-charts
Civolilah Sep 18, 2024
376cf66
Merge branch 'develop' into feature/1605-dashboard-charts
Civolilah Oct 1, 2024
2b65910
Merge branch 'develop' into feature/1605-dashboard-charts
Civolilah Oct 2, 2024
7a70eb4
Added shadcn component
Civolilah Dec 17, 2024
068afea
Merge branch 'develop' into feature/1605-dashboard-charts
Civolilah Dec 17, 2024
efa842b
Clean up
Civolilah Dec 17, 2024
bd14c33
Merge branch 'develop' into feature/1605-dashboard-charts
Civolilah Dec 18, 2024
e6cd740
Converting dashboard cards into new styling
Civolilah Dec 18, 2024
660e9a4
Improving logic on the dashboard
Civolilah Dec 19, 2024
3cab4ee
Improved logic
Civolilah Dec 19, 2024
26a1adb
Polished logic for dashboard configuration
Civolilah Dec 21, 2024
0f8733e
Implemented restoring modal for cards
Civolilah Dec 22, 2024
27459ee
Adjusted calculation of height
Civolilah Dec 22, 2024
ca5a281
Implemented new way of drag handling
Civolilah Dec 23, 2024
91a837c
Adjusted logic
Civolilah Dec 23, 2024
b7acfb4
Merge branch 'develop' into feature/1605-dashboard-charts
Civolilah Dec 24, 2024
c320edc
Adjusted logic for configuring dashboard
Civolilah Dec 24, 2024
4b97e04
Adjusted heights of cards when edit mode is turned on
Civolilah Dec 25, 2024
c191e43
Cleanup
Civolilah Dec 25, 2024
c38979b
Implemented preferences with new logic
Civolilah Dec 29, 2024
1ced757
Fixed issue with dragging
Civolilah Dec 29, 2024
18301a7
Fixed updating layout dimensions for preference cards
Civolilah Dec 29, 2024
4eb4e9e
Merge branch 'develop' into feature/1605-dashboard-charts
Civolilah Dec 29, 2024
2cafbb1
Returned previous approach for preference cards
Civolilah Dec 30, 2024
b8de71d
Cleanup
Civolilah Dec 30, 2024
a16b8e2
Merge branch 'develop' into feature/1605-dashboard-charts
Civolilah Jan 7, 2025
e70a4fc
Changing approach for a solution for preference cards
Civolilah Jan 8, 2025
a2b7e99
Merge branch 'develop' into feature/1605-dashboard-charts
Civolilah Jan 9, 2025
69fe499
Implemented main functionality
Civolilah Jan 15, 2025
4ffa413
Implemented saving preference cards settings
Civolilah Jan 15, 2025
ec110bc
Cleanup
Civolilah Jan 15, 2025
e8873c1
Additional cleanup
Civolilah Jan 15, 2025
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
Next Next commit
Implementing cards on the dashboard
Civolilah committed Jul 22, 2024
commit a27c48547f4be94f7f28303da0255457476abf60
28 changes: 28 additions & 0 deletions src/common/interfaces/company-user.ts
Original file line number Diff line number Diff line change
@@ -32,10 +32,38 @@ export interface CompanyUser {
react_settings: ReactSettings;
}

type Format = 'time' | 'money';
export type Period = 'current' | 'previous' | 'total';
export type Calculate = 'sum' | 'avg' | 'count';
export type Field =
| 'active_invoices'
| 'outstanding_invoices'
| 'completed_payments'
| 'refunded_payments'
| 'active_quotes'
| 'unapproved_quotes'
| 'logged_tasks'
| 'invoiced_tasks'
| 'paid_tasks'
| 'logged_expenses'
| 'pending_expenses'
| 'invoiced_expenses'
| 'invoice_paid_expenses';

export interface DashboardField {
calculate: Calculate;
field: Field;
format: Format;
period: Period;
}

export interface Settings {
accent_color: string;
table_columns?: Record<ReactTableColumns, string[]>;
react_table_columns?: Record<ReactTableColumns, string[]>;
dashboard_fields?: DashboardField[];
dashboard_fields_per_row_desktop?: number;
dashboard_fields_per_row_mobile?: number;
}

export interface Notifications {
4 changes: 2 additions & 2 deletions src/components/DropdownDateRangePicker.tsx
Original file line number Diff line number Diff line change
@@ -71,8 +71,8 @@ export function DropdownDateRangePicker(props: Props) {
const accentColor = useAccentColor();

return (
<div className="flex justify-end items-center">
<Calendar style={{ color: accentColor }} className="mx-2" />
<div className="flex justify-end items-center space-x-2">
<Calendar style={{ color: accentColor }} />
<SelectField
value={props.value}
className={
292 changes: 292 additions & 0 deletions src/pages/dashboard/components/DashboardCardSelector.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,292 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/

import { useCurrentUser } from '$app/common/hooks/useCurrentUser';
import { useInjectUserChanges } from '$app/common/hooks/useInjectUserChanges';
import {
Calculate,
DashboardField,
Field,
Period,
} from '$app/common/interfaces/company-user';
import { Button, SelectField } from '$app/components/forms';
import { Icon } from '$app/components/icons/Icon';
import { Modal } from '$app/components/Modal';
import {
DragDropContext,
Draggable,
Droppable,
DropResult,
} from '@hello-pangea/dnd';
import { arrayMoveImmutable } from 'array-move';
import { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { CgOptions } from 'react-icons/cg';
import { MdClose, MdDragHandle } from 'react-icons/md';

const FIELDS = [
'active_invoices',
'outstanding_invoices',
'completed_payments',
'refunded_payments',
'active_quotes',
'unapproved_quotes',
'logged_tasks',
'invoiced_tasks',
'paid_tasks',
'logged_expenses',
'pending_expenses',
'invoiced_expenses',
'invoice_paid_expenses',
];

export function DashboardCardSelector() {
const [t] = useTranslation();

const currentUser = useCurrentUser();

const userChanges = useInjectUserChanges();

const [currentFields, setCurrentFields] = useState<DashboardField[]>([]);

const [currentField, setCurrentField] = useState<DashboardField>({
field: '' as Field,
period: 'current',
calculate: 'sum',
format: 'time',
});

const [isCardsModalOpen, setIsCardsModalOpen] = useState<boolean>(false);
const [isFieldsModalOpen, setIsFieldsModalOpen] = useState<boolean>(false);

const handleCardsModalClose = () => {
setIsCardsModalOpen(false);
};

const handleFieldsModalClose = () => {
setIsFieldsModalOpen(false);

setCurrentField({
field: '' as Field,
period: 'current',
calculate: 'sum',
format: 'time',
});
};

const onDragEnd = (result: DropResult) => {
const sorted = arrayMoveImmutable(
currentFields,
result.source.index,
result.destination?.index as unknown as number
);

setCurrentFields(sorted);
};

const handleDelete = (fieldKey: Field) => {
const updatedCurrentColumns = currentFields.filter(
(field) => field.field !== fieldKey
);

setCurrentFields(updatedCurrentColumns);
};

return (
<>
<div
className="flex h-full items-center cursor-pointer"
onClick={() => setIsCardsModalOpen(true)}
>
<Icon element={CgOptions} size={28} />
</div>

<Modal
title={t('settings')}
visible={isCardsModalOpen}
onClose={handleCardsModalClose}
disableClosing={isFieldsModalOpen}
>
<div className="flex flex-col space-y-4">
<DragDropContext onDragEnd={onDragEnd}>
<Droppable
droppableId="columns"
renderClone={(provided, _, rubric) => {
const dashboardField = currentFields[rubric.source.index];

return (
<div
ref={provided.innerRef}
{...provided.draggableProps}
className="flex items-center justify-between py-2 text-sm"
>
<div className="flex space-x-2 items-center">
<Icon element={MdClose} size={20} />

<div className="flex flex-col">
<p>{t(dashboardField.field)}</p>

<div>
<span>{t(dashboardField.period)}</span>
<span>&middot;</span>
<span>{t(dashboardField.calculate)}</span>
</div>
</div>
</div>

<div {...provided.dragHandleProps}>
<Icon element={MdDragHandle} size={23} />
</div>
</div>
);
}}
>
{(provided) => (
<div {...provided.droppableProps} ref={provided.innerRef}>
{currentFields.map((field, index) => (
<Draggable
key={index}
draggableId={`item-${index}`}
index={index}
>
{(provided) => (
<div
ref={provided.innerRef}
{...provided.draggableProps}
className="flex items-center justify-between py-2"
>
<div className="flex space-x-2 items-center">
<Icon
className="cursor-pointer"
element={MdClose}
size={20}
onClick={() => handleDelete(field.field)}
/>

<div className="flex flex-col">
<p>{t(field.field)}</p>

<div>
<span>{t(field.period)}</span>
<span>&middot;</span>
<span>{t(field.calculate)}</span>
</div>
</div>
</div>

<div {...provided.dragHandleProps}>
<Icon element={MdDragHandle} size={23} />
</div>
</div>
)}
</Draggable>
))}

{provided.placeholder}
</div>
)}
</Droppable>
</DragDropContext>

<Button
behavior="button"
type="secondary"
onClick={() => setIsFieldsModalOpen(true)}
>
{t('add_field')}
</Button>

<SelectField>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
<option value="4">4</option>
<option value="5">5</option>
<option value="6">6</option>
<option value="7">7</option>
<option value="8">8</option>
</SelectField>

<Button behavior="button">{t('save')}</Button>
</div>
</Modal>

<Modal
title={t('add_field')}
size="extraSmall"
visible={isFieldsModalOpen}
onClose={handleFieldsModalClose}
>
<div className="flex flex-col space-y-4">
<SelectField
label={t('field')}
value={currentField.field}
onValueChange={(value) =>
setCurrentField((currentField) => ({
...currentField,
field: value as Field,
}))
}
withBlank
>
{FIELDS.map((field) => (
<option key={field} value={field}>
{t(field)}
</option>
))}
</SelectField>

<SelectField
label={t('period')}
value={currentField.period}
onValueChange={(value) =>
setCurrentField((currentField) => ({
...currentField,
period: value as Period,
}))
}
>
<option value="current">{t('current_period')}</option>
<option value="previous">{t('previous_period')}</option>
<option value="total">{t('total')}</option>
</SelectField>

<SelectField
label={t('calculate')}
value={currentField.calculate}
onValueChange={(value) =>
setCurrentField((currentField) => ({
...currentField,
calculate: value as Calculate,
}))
}
>
<option value="sum">{t('sum')}</option>
<option value="avg">{t('average')}</option>
<option value="count">{t('count')}</option>
</SelectField>

<Button
behavior="button"
onClick={() => {
setCurrentFields((current) => [...current, currentField]);
handleFieldsModalClose();
}}
disabled={!currentField.field}
disableWithoutIcon
>
{t('add')}
</Button>
</div>
</Modal>
</>
);
}
23 changes: 12 additions & 11 deletions src/pages/dashboard/components/Totals.tsx
Original file line number Diff line number Diff line change
@@ -30,6 +30,7 @@ import collect from 'collect.js';
import { useColorScheme } from '$app/common/colors';
import { CurrencySelector } from '$app/components/CurrencySelector';
import { useQuery } from 'react-query';
import { DashboardCardSelector } from './DashboardCardSelector';

interface TotalsRecord {
revenue: { paid_to_date: string; code: string };
@@ -252,17 +253,17 @@ export function Totals() {
</Button>
</div>

<div className="flex flex-auto justify-center sm:col-start-3 ">
<DropdownDateRangePicker
handleDateChange={handleDateChange}
startDate={dates.start_date}
endDate={dates.end_date}
handleDateRangeChange={(value) =>
update('preferences.dashboard_charts.range', value)
}
value={body.date_range}
/>
</div>
<DropdownDateRangePicker
handleDateChange={handleDateChange}
startDate={dates.start_date}
endDate={dates.end_date}
handleDateRangeChange={(value) =>
update('preferences.dashboard_charts.range', value)
}
value={body.date_range}
/>

<DashboardCardSelector />

<Preferences>
<CurrencySelector