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();