diff --git a/src/components/EntityActionElement.tsx b/src/components/EntityActionElement.tsx
index 0fd2afbc4a..29085d9d61 100644
--- a/src/components/EntityActionElement.tsx
+++ b/src/components/EntityActionElement.tsx
@@ -28,6 +28,7 @@ interface Props extends CommonProps {
entity: Entity;
actionKey: string;
excludePreferences?: boolean;
+ underGroup?: boolean;
}
export function EntityActionElement(props: Props) {
@@ -43,6 +44,7 @@ export function EntityActionElement(props: Props) {
actionKey,
excludePreferences,
setVisible,
+ underGroup,
} = props;
const showActionByPreferences = useShowActionByPreferences({
@@ -75,6 +77,7 @@ export function EntityActionElement(props: Props) {
icon={}
onClick={onClick}
setVisible={setVisible}
+ underGroup={underGroup}
>
{props.children}
diff --git a/src/components/dropdown/DropdownElement.tsx b/src/components/dropdown/DropdownElement.tsx
index 2151349286..b7386c806a 100644
--- a/src/components/dropdown/DropdownElement.tsx
+++ b/src/components/dropdown/DropdownElement.tsx
@@ -20,6 +20,7 @@ interface Props extends CommonProps {
setVisible?: (value: boolean) => any;
icon?: ReactElement;
cypressRef?: string;
+ underGroup?: boolean;
}
const Button = styled.button`
@@ -51,7 +52,11 @@ export function DropdownElement(props: Props) {
{
'flex items-center': props.icon,
},
- `w-full text-left z-50 block px-4 py-2 text-sm text-gray-700 rounded-lg ${props.className}`
+ `w-full text-left z-50 block py-2 text-sm text-gray-700 rounded-lg ${props.className}`,
+ {
+ 'pl-6 pr-4': props.underGroup,
+ 'px-4': !props.underGroup,
+ }
)}
>
{props.icon}
@@ -82,7 +87,11 @@ export function DropdownElement(props: Props) {
{
'flex items-center': props.icon,
},
- `w-full text-left z-50 block px-4 py-2 text-sm rounded-lg ${props.className} `
+ `w-full text-left z-50 block py-2 text-sm rounded-lg ${props.className}`,
+ {
+ 'pl-6 pr-4': props.underGroup,
+ 'px-4': !props.underGroup,
+ }
)}
data-cy={props.cypressRef}
>
diff --git a/src/components/dropdown/DropdownGroupElement.tsx b/src/components/dropdown/DropdownGroupElement.tsx
new file mode 100644
index 0000000000..d8f07fd21d
--- /dev/null
+++ b/src/components/dropdown/DropdownGroupElement.tsx
@@ -0,0 +1,78 @@
+/**
+ * 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 { ReactNode, useState } from 'react';
+import { Icon } from '../icons/Icon';
+import { MdArrowBackIosNew } from 'react-icons/md';
+import { useColorScheme } from '$app/common/colors';
+import styled from 'styled-components';
+import { IconType } from 'react-icons';
+import classNames from 'classnames';
+
+interface Props {
+ label: string;
+ collapsed?: boolean;
+ children: ReactNode;
+ icon: IconType;
+}
+
+const Div = styled.div`
+ color: ${(props) => props.theme.color};
+ &:hover {
+ background-color: ${(props) => props.theme.hoverColor};
+ }
+`;
+
+export function DropdownGroupElement(props: Props) {
+ const { collapsed, label, children, icon } = props;
+
+ const colors = useColorScheme();
+
+ const [isCollapsed, setIsCollapsed] = useState(collapsed ?? true);
+
+ return (
+
+
event.stopPropagation()}
+ >
+
+
+
+ {label}
+
+
+
setIsCollapsed((current) => !current)}>
+ {isCollapsed ? (
+
+ ) : (
+
+ )}
+
+
+
+
+ {children}
+
+
+ );
+}
diff --git a/src/pages/clients/show/pages/Invoices.tsx b/src/pages/clients/show/pages/Invoices.tsx
index b4310cb466..dde238bdab 100644
--- a/src/pages/clients/show/pages/Invoices.tsx
+++ b/src/pages/clients/show/pages/Invoices.tsx
@@ -23,7 +23,7 @@ export default function Invoices() {
const hasPermission = useHasPermission();
- const actions = useActions();
+ const actions = useActions({ table: true });
const columns = useInvoiceColumns();
const customBulkActions = useCustomBulkActions();
const { footerColumns } = useFooterColumns();
diff --git a/src/pages/invoices/common/components/EmailInvoiceAction.tsx b/src/pages/invoices/common/components/EmailInvoiceAction.tsx
index 7c16d6f1c0..28eaadbecc 100644
--- a/src/pages/invoices/common/components/EmailInvoiceAction.tsx
+++ b/src/pages/invoices/common/components/EmailInvoiceAction.tsx
@@ -22,6 +22,7 @@ import { EntityActionElement } from '$app/components/EntityActionElement';
interface Props {
invoice: Invoice;
isDropdown?: boolean;
+ underGroup?: boolean;
}
export function EmailInvoiceAction(props: Props) {
const [t] = useTranslation();
@@ -29,7 +30,7 @@ export function EmailInvoiceAction(props: Props) {
const [isModalOpen, setIsModalOpen] = useState(false);
- const { invoice, isDropdown = false } = props;
+ const { invoice, isDropdown = false, underGroup } = props;
const hasClientEmailContacts = (client?: Client) => {
return client?.contacts.some(({ email }) => email);
@@ -53,6 +54,7 @@ export function EmailInvoiceAction(props: Props) {
}),
})}
icon={MdSend}
+ underGroup={underGroup}
>
{t('email_invoice')}
diff --git a/src/pages/invoices/edit/components/Actions.tsx b/src/pages/invoices/edit/components/Actions.tsx
index 3b4c8c628d..1bb0e9be0f 100644
--- a/src/pages/invoices/edit/components/Actions.tsx
+++ b/src/pages/invoices/edit/components/Actions.tsx
@@ -36,6 +36,7 @@ import {
MdRefresh,
MdRestore,
MdSchedule,
+ MdSettings,
} from 'react-icons/md';
import { useNavigate } from 'react-router-dom';
import { useScheduleEmailRecord } from '$app/pages/invoices/common/hooks/useScheduleEmailRecord';
@@ -56,6 +57,7 @@ import { CloneOptionsModal } from '../../common/components/CloneOptionsModal';
import { EntityActionElement } from '$app/components/EntityActionElement';
import { useChangeTemplate } from '$app/pages/settings/invoice-design/pages/custom-designs/components/ChangeTemplate';
import { useCurrentCompany } from '$app/common/hooks/useCurrentCompany';
+import { DropdownGroupElement } from '$app/components/dropdown/DropdownGroupElement';
export const isInvoiceAutoBillable = (invoice: Invoice) => {
return (
@@ -70,6 +72,7 @@ interface Params {
showEditAction?: boolean;
showCommonBulkAction?: boolean;
dropdown?: boolean;
+ table?: boolean;
}
export function useActions(params?: Params) {
const { t } = useTranslation();
@@ -78,6 +81,7 @@ export function useActions(params?: Params) {
showEditAction,
showCommonBulkAction,
dropdown = true,
+ table,
} = params || {};
const company = useCurrentCompany();
@@ -129,47 +133,123 @@ export function useActions(params?: Params) {
),
() => Boolean(showEditAction) && dropdown && ,
- (invoice: Invoice) => (
-
- ),
- (invoice: Invoice) => (
-
- {t('view_pdf')}
-
- ),
(invoice: Invoice) =>
- getEntityState(invoice) !== EntityState.Deleted && (
+ table && (
+
+
+
+ {invoice.status_id === InvoiceStatus.Draft && !invoice.is_deleted && (
+ bulk([invoice.id], 'mark_sent')}
+ icon={MdMarkEmailRead}
+ underGroup
+ >
+ {t('mark_sent')}
+
+ )}
+
+ {parseInt(invoice.status_id) < parseInt(InvoiceStatus.Paid) &&
+ !invoice.is_deleted && (
+ bulk([invoice.id], 'mark_paid')}
+ icon={MdPaid}
+ underGroup
+ >
+ {t('mark_paid')}
+
+ )}
+
+ {invoice.status_id !== InvoiceStatus.Paid && (isAdmin || isOwner) && (
+ scheduleEmailRecord(invoice.id)}
+ icon={MdSchedule}
+ underGroup
+ >
+ {t('schedule')}
+
+ )}
+
+ ),
+ (invoice: Invoice) =>
+ !table && (
+
+ ),
+ (invoice: Invoice) =>
+ !table &&
+ invoice.status_id === InvoiceStatus.Draft &&
+ !invoice.is_deleted && (
printPdf([invoice.id])}
- icon={MdPrint}
+ tooltipText={t('mark_sent')}
+ onClick={() => bulk([invoice.id], 'mark_sent')}
+ icon={MdMarkEmailRead}
>
- {t('print_pdf')}
+ {t('mark_sent')}
+
+ ),
+ (invoice: Invoice) =>
+ !table &&
+ parseInt(invoice.status_id) < parseInt(InvoiceStatus.Paid) &&
+ !invoice.is_deleted && (
+ bulk([invoice.id], 'mark_paid')}
+ icon={MdPaid}
+ >
+ {t('mark_paid')}
),
(invoice: Invoice) =>
+ !table &&
invoice.status_id !== InvoiceStatus.Paid &&
(isAdmin || isOwner) && (
),
- (invoice: Invoice) => (
-
- {t('delivery_note')} ({t('pdf')})
-
- ),
- (invoice: Invoice) => (
- downloadPdf(invoice)}
- icon={MdDownload}
- >
- {t('download')}
-
- ),
(invoice: Invoice) =>
- Boolean(company?.settings.enable_e_invoice) && (
+ table && (
+
+
+ {t('view_pdf')}
+
+
+ {getEntityState(invoice) !== EntityState.Deleted && (
+ printPdf([invoice.id])}
+ icon={MdPrint}
+ underGroup
+ >
+ {t('print_pdf')}
+
+ )}
+
+
+ {t('delivery_note')} ({t('pdf')})
+
+
+ downloadPdf(invoice)}
+ icon={MdDownload}
+ underGroup
+ >
+ {t('download')}
+
+
+ {Boolean(company?.settings.enable_e_invoice) && (
+ downloadEInvoice(invoice)}
+ icon={MdDownload}
+ underGroup
+ >
+ {t('download_e_invoice')}
+
+ )}
+
+ ),
+ (invoice: Invoice) =>
+ !table && (
downloadEInvoice(invoice)}
- icon={MdDownload}
+ tooltipText={t('view_pdf')}
+ to={route('/invoices/:id/pdf', { id: invoice.id })}
+ icon={MdPictureAsPdf}
>
- {t('download_e_invoice')}
+ {t('view_pdf')}
),
(invoice: Invoice) =>
- invoice.status_id === InvoiceStatus.Draft &&
- !invoice.is_deleted && (
+ getEntityState(invoice) !== EntityState.Deleted &&
+ !table && (
bulk([invoice.id], 'mark_sent')}
- icon={MdMarkEmailRead}
+ tooltipText={t('print_pdf')}
+ onClick={() => printPdf([invoice.id])}
+ icon={MdPrint}
>
- {t('mark_sent')}
+ {t('print_pdf')}
),
(invoice: Invoice) =>
- parseInt(invoice.status_id) < parseInt(InvoiceStatus.Paid) &&
- !invoice.is_deleted && (
+ !table && (
bulk([invoice.id], 'mark_paid')}
- icon={MdPaid}
+ tooltipText={`${t('delivery_note')} ${t('pdf')}`}
+ to={route('/invoices/:id/pdf?delivery_note=true', {
+ id: invoice.id,
+ })}
+ icon={MdPictureAsPdf}
>
- {t('mark_paid')}
+ {t('delivery_note')} ({t('pdf')})
+
+ ),
+ (invoice: Invoice) =>
+ !table && (
+ downloadPdf(invoice)}
+ icon={MdDownload}
+ >
+ {t('download')}
+
+ ),
+ (invoice: Invoice) =>
+ !table &&
+ Boolean(company?.settings.enable_e_invoice) && (
+ downloadEInvoice(invoice)}
+ icon={MdDownload}
+ >
+ {t('download_e_invoice')}
),
(invoice: Invoice) =>
diff --git a/src/pages/invoices/index/Invoices.tsx b/src/pages/invoices/index/Invoices.tsx
index af1d812927..aa43f13ee4 100644
--- a/src/pages/invoices/index/Invoices.tsx
+++ b/src/pages/invoices/index/Invoices.tsx
@@ -64,7 +64,7 @@ export default function Invoices() {
const { data: invoiceResponse } = useInvoiceQuery({ id: sliderInvoiceId });
- const actions = useActions();
+ const actions = useActions({ table: true });
const filters = useInvoiceFilters();
const columns = useInvoiceColumns();
const reactSettings = useReactSettings();