From 10552cd21cc39fd219893b6ebf72ef072bb77efc Mon Sep 17 00:00:00 2001 From: Andres Morelos Date: Tue, 7 Jun 2022 22:44:42 -0500 Subject: [PATCH 1/4] Subitems Init. --- app/actions/form.jsx | 27 +++++++++++++++++++++------ app/components/form/ItemRow.jsx | 10 ++++++++++ app/components/form/ItemsList.jsx | 3 ++- app/constants/actions.jsx | 4 ++++ 4 files changed, 37 insertions(+), 7 deletions(-) diff --git a/app/actions/form.jsx b/app/actions/form.jsx index aa4d9f9..1e23a5a 100644 --- a/app/actions/form.jsx +++ b/app/actions/form.jsx @@ -4,7 +4,7 @@ import * as ACTION_TYPES from '../constants/actions.jsx'; // Recipient export const updateRecipient = createAction( ACTION_TYPES.FORM_RECIPIENT_UPDATE, - data => data + (data) => data ); // ItemsRow @@ -12,12 +12,27 @@ export const addItem = createAction(ACTION_TYPES.FORM_ITEM_ADD); export const removeItem = createAction( ACTION_TYPES.FORM_ITEM_REMOVE, - itemID => itemID + (itemID) => itemID ); export const updateItem = createAction( ACTION_TYPES.FORM_ITEM_UPDATE, - itemData => itemData + (itemData) => itemData +); + +export const addSubItem = createAction( + ACTION_TYPES.FORM_ITEM_ADD_SUBITEM, + (itemID) => itemID +); + +export const removeSubItem = createAction( + ACTION_TYPES.FORM_ITEM_REMOVE_SUBITEM, + (itemID) => itemID +); + +export const updateSubItem = createAction( + ACTION_TYPES.FORM_ITEM_UPDATE_SUBITEM, + (itemData) => itemData ); export const moveRow = createAction( @@ -30,12 +45,12 @@ export const addPaymentItem = createAction(ACTION_TYPES.FORM_PAYMENT_ITEM_ADD); export const removePaymentItem = createAction( ACTION_TYPES.FORM_PAYMENT_ITEM_REMOVE, - itemID => itemID + (itemID) => itemID ); export const updatePaymentItem = createAction( ACTION_TYPES.FORM_PAYMENT_ITEM_UPDATE, - itemData => itemData + (itemData) => itemData ); export const movePaymentRow = createAction( @@ -72,5 +87,5 @@ export const updateFieldData = createAction( export const toggleField = createAction( ACTION_TYPES.FORM_FIELD_TOGGLE, - field => field + (field) => field ); diff --git a/app/components/form/ItemRow.jsx b/app/components/form/ItemRow.jsx index b63baa1..1048c4d 100644 --- a/app/components/form/ItemRow.jsx +++ b/app/components/form/ItemRow.jsx @@ -63,6 +63,9 @@ export class ItemRow extends Component { this.updateSubtotal = this.updateSubtotal.bind(this); this.uploadRowState = this.uploadRowState.bind(this); this.removeRow = this.removeRow.bind(this); + this.addSubItem = this.addSubItem.bind(this); + this.removeSubItem = this.removeSubItem.bind(this); + this.updateSubItem = this.updateSubItem.bind(this); } UNSAFE_componentWillMount() { @@ -126,6 +129,10 @@ export class ItemRow extends Component { this.props.removeRow(this.state.id); } + addSubItem() { + this.props.addSubItem(this.state.id); + } + render() { const { t, actions, hasHandler } = this.props; return ( @@ -193,6 +200,9 @@ ItemRow.propTypes = { index: PropTypes.number.isRequired, removeRow: PropTypes.func.isRequired, updateRow: PropTypes.func.isRequired, + addSubItem: PropTypes.func.isRequired, + removeSubItem: PropTypes.func.isRequired, + updateSubItem: PropTypes.func.isRequired, }; export default compose(_withDraggable)(ItemRow); diff --git a/app/components/form/ItemsList.jsx b/app/components/form/ItemsList.jsx index 67c3ebb..70faf4c 100644 --- a/app/components/form/ItemsList.jsx +++ b/app/components/form/ItemsList.jsx @@ -69,7 +69,7 @@ export class ItemsList extends PureComponent { render() { // Bound Actions - const { addItem, removeItem, updateItem } = this.props.boundActionCreators; + const { addItem, removeItem, updateItem, addSubItem } = this.props.boundActionCreators; // Item Rows const { t, rows } = this.props; @@ -84,6 +84,7 @@ export class ItemsList extends PureComponent { updateRow={updateItem} removeRow={removeItem} addItem={addItem} + addSubItem={addSubItem} /> )); diff --git a/app/constants/actions.jsx b/app/constants/actions.jsx index 418ea88..3436ce9 100644 --- a/app/constants/actions.jsx +++ b/app/constants/actions.jsx @@ -8,6 +8,9 @@ export const FORM_ITEM_ADD = 'FORM_ITEM_ADD'; export const FORM_ITEM_REMOVE = 'FORM_ITEM_REMOVE'; export const FORM_ITEM_UPDATE = 'FORM_ITEM_UPDATE'; export const FORM_ITEM_MOVE = 'FORM_ITEM_MOVE'; +export const FORM_ITEM_ADD_SUBITEM = 'FORM_ITEM_ADD_SUBITEM'; +export const FORM_ITEM_REMOVE_SUBITEM = 'FORM_ITEM_REMOVE_SUBITEM'; +export const FORM_ITEM_UPDATE_SUBITEM = 'FORM_ITEM_UPDATE_SUBITEM'; // Payment Row items export const FORM_PAYMENT_ITEM_ADD = 'FORM_PAYMENT_ITEM_ADD'; @@ -15,6 +18,7 @@ export const FORM_PAYMENT_ITEM_REMOVE = 'FORM_PAYMENT_ITEM_REMOVE'; export const FORM_PAYMENT_ITEM_UPDATE = 'FORM_PAYMENT_ITEM_UPDATE'; export const FORM_PAYMENT_ITEM_MOVE = 'FORM_PAYMENT_ITEM_MOVE'; + // Fields export const FORM_SAVE = 'FORM_SAVE'; export const FORM_CLEAR = 'FORM_CLEAR'; From 9bf4d3b128d04d4fd84c4447a4fedcbcadf6582c Mon Sep 17 00:00:00 2001 From: Andres Morelos Date: Sun, 19 Jun 2022 03:09:11 -0500 Subject: [PATCH 2/4] Adding SubItems. --- app/actions/form.jsx | 4 +- app/components/form/ItemRow.jsx | 162 ++++++++++++++++++---------- app/components/form/ItemsList.jsx | 26 +++-- app/components/form/SubItemsRow.jsx | 109 +++++++++++++++++++ app/middlewares/FormMW.jsx | 152 +++++++++++++------------- app/middlewares/UIMiddleware.jsx | 110 ++++++++++--------- app/reducers/FormReducer.jsx | 44 +++++++- static/css/layout.css | 72 +------------ 8 files changed, 412 insertions(+), 267 deletions(-) create mode 100644 app/components/form/SubItemsRow.jsx diff --git a/app/actions/form.jsx b/app/actions/form.jsx index 1e23a5a..011a75e 100644 --- a/app/actions/form.jsx +++ b/app/actions/form.jsx @@ -27,12 +27,12 @@ export const addSubItem = createAction( export const removeSubItem = createAction( ACTION_TYPES.FORM_ITEM_REMOVE_SUBITEM, - (itemID) => itemID + (parentItemId, itemID) => ({ id: itemID, parentItemId }) ); export const updateSubItem = createAction( ACTION_TYPES.FORM_ITEM_UPDATE_SUBITEM, - (itemData) => itemData + (parentItemId, itemData) => ({ subItem: itemData, parentItemId }) ); export const moveRow = createAction( diff --git a/app/components/form/ItemRow.jsx b/app/components/form/ItemRow.jsx index 1048c4d..b1bdff4 100644 --- a/app/components/form/ItemRow.jsx +++ b/app/components/form/ItemRow.jsx @@ -7,8 +7,17 @@ import { compose } from 'recompose'; import styled from 'styled-components'; import _withDraggable from './hoc/_withDraggable'; +// Custom Components +import SubItemsRow from './SubItemsRow'; +import { Section } from '../shared/Section'; + // Styles +const ItemsListDiv = styled.div` + position: relative; + margin-bottom: 10px; +`; + const ItemDiv = styled.div` position: relative; display: flex; @@ -53,6 +62,13 @@ const ItemRemoveBtn = styled.a` } `; +const ItemAddSubiTemBtn = styled.a` + > i { + color: #00a8ff; + margin-right: 10px; + } +`; + // Component export class ItemRow extends Component { constructor(props) { @@ -69,8 +85,8 @@ export class ItemRow extends Component { } UNSAFE_componentWillMount() { - const { id, description, quantity, price, subtotal } = this.props.item; - const index = this.props.index; + const { subItems, item, index } = this.props; + const { id, description, quantity, price, subtotal } = item; this.setState({ id, index, @@ -78,6 +94,7 @@ export class ItemRow extends Component { price: price || '', quantity: quantity || '', subtotal: subtotal || '', + subitems: subItems || [], }); } @@ -121,8 +138,11 @@ export class ItemRow extends Component { } uploadRowState() { - const { updateRow } = this.props; - updateRow(this.state); + const { updateRow, subItems } = this.props; + updateRow({ + ...this.state, + subitems: [...subItems, ...this.state.subitems], + }); } removeRow() { @@ -133,70 +153,102 @@ export class ItemRow extends Component { this.props.addSubItem(this.state.id); } + removeSubItem(itemId) { + this.props.removeSubItem(this.state.id, itemId); + } + + updateSubItem(item) { + this.props.updateSubItem(this.state.id, item); + } + render() { - const { t, actions, hasHandler } = this.props; + const { t, actions, hasHandler, subitemActions, subItems } = this.props; + + const SubItemsRowsComponent = subItems.map((subItem, index) => ( + + )); + return ( - - {hasHandler && ( -
- + <> + + {hasHandler && ( +
+ +
+ )} +
+
- )} -
- -
- -
- -
- -
- -
- - {(actions || hasHandler) && ( - - {actions && ( - - - - )} - - )} -
+ +
+ +
+ +
+ +
+ + {(actions || hasHandler || subitemActions) && ( + + {subitemActions && ( + + + + )} + {actions && ( + + + + )} + + )} + +
+ {SubItemsRowsComponent} +
+ ); } } ItemRow.propTypes = { actions: PropTypes.bool.isRequired, + subitemActions: PropTypes.bool.isRequired, addItem: PropTypes.func.isRequired, t: PropTypes.func.isRequired, hasHandler: PropTypes.bool.isRequired, item: PropTypes.object.isRequired, + subItems: PropTypes.array.isRequired, index: PropTypes.number.isRequired, removeRow: PropTypes.func.isRequired, updateRow: PropTypes.func.isRequired, diff --git a/app/components/form/ItemsList.jsx b/app/components/form/ItemsList.jsx index 70faf4c..09db193 100644 --- a/app/components/form/ItemsList.jsx +++ b/app/components/form/ItemsList.jsx @@ -69,22 +69,34 @@ export class ItemsList extends PureComponent { render() { // Bound Actions - const { addItem, removeItem, updateItem, addSubItem } = this.props.boundActionCreators; + const { + addItem, + removeItem, + updateItem, + addSubItem, + removeSubItem, + updateSubItem, + } = this.props.boundActionCreators; + // Item Rows const { t, rows } = this.props; - + const rowsComponent = rows.map((item, index) => ( 1} actions={index !== 0} + subitemActions updateRow={updateItem} removeRow={removeItem} addItem={addItem} addSubItem={addSubItem} + removeSubItem={removeSubItem} + updateSubItem={updateSubItem} /> )); @@ -95,11 +107,7 @@ export class ItemsList extends PureComponent { - - - {rowsComponent} - - + {rowsComponent}
{t('form:fields:items:add')} @@ -116,12 +124,12 @@ ItemsList.propTypes = { rows: PropTypes.arrayOf(PropTypes.object).isRequired, }; -const mapStateToProps = state => ({ +const mapStateToProps = (state) => ({ formState: state.form, // Make drag & drop works rows: getRows(state), }); -const mapDispatchToProps = dispatch => ({ +const mapDispatchToProps = (dispatch) => ({ boundActionCreators: bindActionCreators(Actions, dispatch), }); diff --git a/app/components/form/SubItemsRow.jsx b/app/components/form/SubItemsRow.jsx new file mode 100644 index 0000000..c78f4c8 --- /dev/null +++ b/app/components/form/SubItemsRow.jsx @@ -0,0 +1,109 @@ +// Libs +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; + +// HOCs +import styled from 'styled-components'; + +// Styles + +const ItemDiv = styled.div` + position: relative; + display: flex; + flex-direction: row; + justify-content: space-between; + flex: 1; + margin-top: 10px; + + & > div { + display: flex; + flex-direction: row; + margin-right: 10px; + &:last-child { + margin-right: 0px; + } + } +`; + +const ItemDivInput = styled.input` + min-height: 36px; + border-radius: 4px; + padding: 0 10px; + font-size: 16px; + display: block; + width: 100%; + border: 1px solid #f2f3f4; + color: #3a3e42; + font-size: 14px; +`; + +const ItemRemoveBtn = styled.a` + > i { + color: #ec476e; + } +`; + +// Component +export class SubItemsRow extends Component { + constructor(props) { + super(props); + this.handleTextInputChange = this.handleTextInputChange.bind(this); + this.removeSubItem = this.removeSubItem.bind(this); + } + + UNSAFE_componentWillMount() { + const { id, description } = this.props.item; + + this.setState({ + id, + description: description || '', + }); + } + + handleTextInputChange(event) { + const name = event.target.name; + const value = event.target.value; + this.setState({ [name]: value }, () => { + this.uploadRowState(); + }); + } + + uploadRowState() { + const { updateSubItem } = this.props; + updateSubItem(this.state); + } + + removeSubItem() { + this.props.removeSubItem(this.state.id); + } + + render() { + const { t } = this.props; + return ( + +
+ +
+ + + + +
+ ); + } +} + +SubItemsRow.propTypes = { + t: PropTypes.func.isRequired, + item: PropTypes.object.isRequired, + removeSubItem: PropTypes.func.isRequired, + updateSubItem: PropTypes.func.isRequired, +}; + +export default SubItemsRow; diff --git a/app/middlewares/FormMW.jsx b/app/middlewares/FormMW.jsx index 53ffa72..1ab0dcc 100644 --- a/app/middlewares/FormMW.jsx +++ b/app/middlewares/FormMW.jsx @@ -10,93 +10,101 @@ import * as ContactsActions from '../actions/contacts'; import * as SettingsActions from '../actions/settings'; import * as UIActions from '../actions/ui'; - // Helper import { getInvoiceData, validateFormData } from '../helpers/form'; // Node Libs import i18n from '../../i18n/i18n'; -const { require: RemoteRequire } = require('@electron/remote') +const { require: RemoteRequire } = require('@electron/remote'); const appConfig = RemoteRequire('electron-settings'); -const FormMW = ({ dispatch, getState }) => next => action => { - switch (action.type) { - case ACTION_TYPES.FORM_SAVE: { - const currentFormData = getState().form; - const secretKey = getState().login.secretKey; +const FormMW = + ({ dispatch, getState }) => + (next) => + (action) => { + switch (action.type) { + case ACTION_TYPES.FORM_SAVE: { + const currentFormData = getState().form; + const secretKey = getState().login.secretKey; - // Validate Form Data - if (!validateFormData(currentFormData)) return; - const { currentInvoiceData, recipient } = getInvoiceData(currentFormData, secretKey); - // UPDATE DOC - if (currentFormData.settings.editMode.active) { - // Update existing invoice - dispatch(InvoicesActions.updateInvoice(currentInvoiceData)); - // Change Tab to invoices - dispatch(UIActions.changeActiveTab('invoices')); - } else { - // CREATE DOC - dispatch(InvoicesActions.saveInvoice(currentInvoiceData)); + // Validate Form Data + if (!validateFormData(currentFormData)) return; + const { currentInvoiceData, recipient } = getInvoiceData( + currentFormData, + secretKey + ); + // UPDATE DOC + if (currentFormData.settings.editMode.active) { + // Update existing invoice + dispatch(InvoicesActions.updateInvoice(currentInvoiceData)); + // Change Tab to invoices + dispatch(UIActions.changeActiveTab('invoices')); + } else { + // CREATE DOC + dispatch(InvoicesActions.saveInvoice(currentInvoiceData)); + } + // Save Contact to DB if it's a new one + if (currentFormData.recipient.newRecipient) { + const newContactData = recipient; + dispatch(ContactsActions.saveContact(newContactData)); + } + // Clear The Form + dispatch(FormActions.clearForm(null, true)); + break; } - // Save Contact to DB if it's a new one - if (currentFormData.recipient.newRecipient) { - const newContactData = recipient; - dispatch(ContactsActions.saveContact(newContactData)); + + case ACTION_TYPES.FORM_ITEM_ADD: { + return next({ ...action, payload: { id: uuidv4() } }); } - // Clear The Form - dispatch(FormActions.clearForm(null, true)); - break; - } - case ACTION_TYPES.FORM_ITEM_ADD: { - return next( - { ...action, payload: { id: uuidv4() }, } - ); - } + case ACTION_TYPES.FORM_ITEM_ADD_SUBITEM: { + return next({ + ...action, + payload: { parentItemId: action.payload, id: uuidv4() }, + }); + } - case ACTION_TYPES.FORM_PAYMENT_ITEM_ADD: { - return next( - { ...action, payload: { id: uuidv4() }, } - ); - } + case ACTION_TYPES.FORM_PAYMENT_ITEM_ADD: { + return next({ ...action, payload: { id: uuidv4() } }); + } - case ACTION_TYPES.FORM_CLEAR: { - // Close Setting Panel - dispatch(FormActions.closeFormSettings()); - // Clear The Form - next(action); - // Create An item - dispatch(FormActions.addItem()); - break; - } + case ACTION_TYPES.FORM_CLEAR: { + // Close Setting Panel + dispatch(FormActions.closeFormSettings()); + // Clear The Form + next(action); + // Create An item + dispatch(FormActions.addItem()); + break; + } - case ACTION_TYPES.SAVED_FORM_SETTING_UPDATE: { - // Save setting to DB - const { setting, data } = action.payload; - appConfig.setSync(`invoice.${setting}`, data); - // Dispatch notification - dispatch({ - type: ACTION_TYPES.UI_NOTIFICATION_NEW, - payload: { - type: 'success', - message: i18n.t('messages:settings:saved'), - }, - }); - // Pass new data to action and continue - next({ - type: ACTION_TYPES.SAVED_FORM_SETTING_UPDATE, - payload: appConfig.getSync('invoice'), - }); - // Reload app settings so that - // Settings tab will have up-to-date information - dispatch(SettingsActions.getInitialSettings()); - break; - } + case ACTION_TYPES.SAVED_FORM_SETTING_UPDATE: { + // Save setting to DB + const { setting, data } = action.payload; + appConfig.setSync(`invoice.${setting}`, data); + // Dispatch notification + dispatch({ + type: ACTION_TYPES.UI_NOTIFICATION_NEW, + payload: { + type: 'success', + message: i18n.t('messages:settings:saved'), + }, + }); + // Pass new data to action and continue + next({ + type: ACTION_TYPES.SAVED_FORM_SETTING_UPDATE, + payload: appConfig.getSync('invoice'), + }); + // Reload app settings so that + // Settings tab will have up-to-date information + dispatch(SettingsActions.getInitialSettings()); + break; + } - default: { - return next(action); + default: { + return next(action); + } } - } -}; + }; export default FormMW; diff --git a/app/middlewares/UIMiddleware.jsx b/app/middlewares/UIMiddleware.jsx index eba5657..1168028 100644 --- a/app/middlewares/UIMiddleware.jsx +++ b/app/middlewares/UIMiddleware.jsx @@ -2,71 +2,69 @@ import { v4 as uuidv4 } from 'uuid'; import * as ACTION_TYPES from '../constants/actions.jsx'; import sounds from '../../libs/sounds'; -const UIMiddleware = ({ getState }) => next => action => { - switch (action.type) { - // Changing Tabs - case ACTION_TYPES.UI_TAB_CHANGE: { - const currentState = getState(); - const currentTab = currentState.ui.activeTab; - if (action.payload !== currentTab) { - sounds.play('TAP'); - next(action); +const UIMiddleware = + ({ getState }) => + (next) => + (action) => { + switch (action.type) { + // Changing Tabs + case ACTION_TYPES.UI_TAB_CHANGE: { + const currentState = getState(); + const currentTab = currentState.ui.activeTab; + if (action.payload !== currentTab) { + sounds.play('TAP'); + next(action); + } + break; } - break; - } - // New Notification - case ACTION_TYPES.UI_NOTIFICATION_NEW: { - // Play a sound based on notification type - switch (action.payload.type) { - case 'success': { - sounds.play('SUCCESS'); - break; - } - case 'warning': { - sounds.play('WARNING'); - break; + // New Notification + case ACTION_TYPES.UI_NOTIFICATION_NEW: { + // Play a sound based on notification type + switch (action.payload.type) { + case 'success': { + sounds.play('SUCCESS'); + break; + } + case 'warning': { + sounds.play('WARNING'); + break; + } } + // Create a new ID for the notification + return next({ + ...action, + payload: { ...action.payload, id: uuidv4() }, + }); } - // Create a new ID for the notification - return next( - { ...action, payload: { ...action.payload, id: uuidv4(), }, } - ); - } - - // Others Actions - case ACTION_TYPES.FORM_ITEM_ADD: { - sounds.play('ADD'); - return next(action); - } - - case ACTION_TYPES.FORM_ITEM_REMOVE: { - sounds.play('REMOVE'); - return next(action); - } - case ACTION_TYPES.FORM_PAYMENT_ITEM_ADD: { - sounds.play('ADD'); - return next(action); - } + // Others Actions + case ACTION_TYPES.FORM_ITEM_ADD: + case ACTION_TYPES.FORM_PAYMENT_ITEM_ADD: + case ACTION_TYPES.FORM_ITEM_ADD_SUBITEM: { + sounds.play('ADD'); + return next(action); + } - case ACTION_TYPES.FORM_PAYMENT_ITEM_REMOVE: { - sounds.play('REMOVE'); - return next(action); - } + case ACTION_TYPES.FORM_ITEM_REMOVE: + case ACTION_TYPES.FORM_ITEM_REMOVE_SUBITEM: + case ACTION_TYPES.FORM_PAYMENT_ITEM_REMOVE: { + sounds.play('REMOVE'); + return next(action); + } - case ACTION_TYPES.FORM_CLEAR: { - if (!action.payload) { - sounds.play('RELOAD'); + case ACTION_TYPES.FORM_CLEAR: { + if (!action.payload) { + sounds.play('RELOAD'); + } + return next(action); } - return next(action); - } - // Default - default: { - return next(action); + // Default + default: { + return next(action); + } } - } -}; + }; export default UIMiddleware; diff --git a/app/reducers/FormReducer.jsx b/app/reducers/FormReducer.jsx index 8228a18..f5859c2 100644 --- a/app/reducers/FormReducer.jsx +++ b/app/reducers/FormReducer.jsx @@ -73,6 +73,44 @@ const FormReducer = handleActions( ), }), + [ACTION_TYPES.FORM_ITEM_ADD_SUBITEM]: (state, action) => ({ + ...state, + rows: state.rows.map((item) => { + if (item.id === action.payload.parentItemId) { + if (item.subitems) { + item.subitems = [...item.subitems, { id: action.payload.id }]; + } else { + item.subitems = [{ id: action.payload.id }]; + } + } + return item; + }), + }), + + [ACTION_TYPES.FORM_ITEM_REMOVE_SUBITEM]: (state, action) => ({ + ...state, + rows: state.rows.map((item) => { + if (item.id === action.payload.parentItemId) { + item.subitems = item.subitems.filter( + (subitem) => subitem.id !== action.payload.id + ); + } + return item; + }), + }), + + [ACTION_TYPES.FORM_ITEM_UPDATE_SUBITEM]: (state, action) => ({ + ...state, + rows: state.rows.map((item) => { + if (item.id === action.payload.parentItemId) { + item.subitems = item.subitems.map((subitem) => + subitem.id !== action.payload.subItem.id ? subitem : action.payload.subItem + ); + } + return item; + }), + }), + [ACTION_TYPES.FORM_ITEM_MOVE]: (state, action) => { const { dragIndex, hoverIndex } = action.payload; const dragRow = state.rows[dragIndex]; @@ -89,7 +127,9 @@ const FormReducer = handleActions( [ACTION_TYPES.FORM_PAYMENT_ITEM_REMOVE]: (state, action) => ({ ...state, - paymentRows: state.paymentRows.filter((item) => item.id !== action.payload), + paymentRows: state.paymentRows.filter( + (item) => item.id !== action.payload + ), }), [ACTION_TYPES.FORM_PAYMENT_ITEM_UPDATE]: (state, action) => ({ @@ -252,7 +292,7 @@ export const getRows = createSelector( export const getPaymentRows = createSelector( getFormState, (formState) => formState.paymentRows -) +); export const getRecipient = createSelector( getFormState, diff --git a/static/css/layout.css b/static/css/layout.css index 2d79344..957053a 100644 --- a/static/css/layout.css +++ b/static/css/layout.css @@ -1,74 +1,3 @@ -/* App Wrapper */ -/* =================================================================== */ -/* .appWrapper { */ -/* display: flex; */ -/* flex-direction: row; */ -/* } */ - -/* Sidebar Wrapper */ -/* =================================================================== */ -/* .sideBarWrapper { */ -/* background: #f9fafa; */ -/* min-width: 190px; */ -/* max-width: 190px; */ -/* width: 190px; */ -/* border-right: 1px solid #f2f3f4; */ -/* } */ -/* ul.sideBar { */ -/* list-style: none; */ -/* margin-top: 0; */ -/* margin-bottom: 0; */ -/* padding: 0; */ -/* padding-top: 50px; */ -/* } */ -/* */ -/* ul.sideBar li { */ -/* display: flex; */ -/* justify-content: flex-start; */ -/* align-items: center; */ -/* padding: 0 10px; */ -/* } */ -/* */ -/* ul.sideBar li a { */ -/* color: #3a3e42; */ -/* font-size: 14px; */ -/* line-height: 21px; */ -/* text-decoration: none; */ -/* display: flex; */ -/* padding: 2px 20px; */ -/* width: 100%; */ -/* align-items: center; */ -/* margin-bottom: 5px; */ -/* border-radius: 4px; */ -/* } */ -/* ul.sideBar a.active { */ -/* background: #eceeef; */ -/* } */ - -/* i#icon-archive { */ -/* color: #cbc189; */ -/* font-size: 24px; */ -/* margin-right: 8px; */ -/* } */ -/* */ -/* #icon-form { */ -/* color: #6bbb69; */ -/* margin-right: 8px; */ -/* font-size: 18px; */ -/* } */ -/* */ -/* #icon-contacts { */ -/* color: #469fe5; */ -/* margin-right: 8px; */ -/* font-size: 18px; */ -/* } */ -/* */ -/* #icon-settings { */ -/* color: #3a3e42; */ -/* margin-right: 8px; */ -/* font-size: 18px; */ -/* } */ - /* Main Content */ /* =================================================================== */ .mainContentWrapper { @@ -82,3 +11,4 @@ .flex2 { flex: 2; } .flex3 { flex: 3; } .flex4 { flex: 4; } +.flex5 { flex: 5; } From e251567b78f2593f7960655cf7c99d3999556925 Mon Sep 17 00:00:00 2001 From: Andres Morelos Date: Sun, 19 Jun 2022 03:31:56 -0500 Subject: [PATCH 3/4] Adding SubItems to template. --- .../templates/business/components/Main.jsx | 108 ++++++++++-------- .../businessQuote/components/Main.jsx | 105 +++++++++-------- preview/templates/minimal/components/Main.jsx | 71 +++++++----- 3 files changed, 162 insertions(+), 122 deletions(-) diff --git a/preview/templates/business/components/Main.jsx b/preview/templates/business/components/Main.jsx index 2803c91..fd7e6f9 100644 --- a/preview/templates/business/components/Main.jsx +++ b/preview/templates/business/components/Main.jsx @@ -7,13 +7,12 @@ import { formatNumber } from '../../../../helpers/formatNumber'; import { getInvoiceValue } from '../../../../app/helpers/invoice'; import currencies from '../../../../libs/currencies.json'; - const InvoiceContent = styled.div` flex: 1; display: flex; margin-top: 1.5em; margin-bottom: 1.5em; - ${props => + ${(props) => props.alignItems && ` align-items: ${props.alignItems}; @@ -30,7 +29,7 @@ const Table = styled.table` text-align: right; } } - ${props => + ${(props) => props.customAccentColor && ` th { border-bottom: 4px solid ${props.accentColor};} @@ -70,7 +69,7 @@ const InvoiceTotal = styled.tr` } } - ${props => + ${(props) => props.customAccentColor && ` td { @@ -111,9 +110,9 @@ function setAlignItems(configs) { } // Component -const Main = function({ invoice, configs, t }) { +const Main = function ({ invoice, configs, t }) { // Set language - const { language, accentColor, customAccentColor } = configs; + const { language, accentColor, customAccentColor } = configs; // Others const { tax, discount } = invoice; const { code, placement, fraction, separator } = invoice.currency; @@ -123,36 +122,53 @@ const Main = function({ invoice, configs, t }) { const currency = configs.useSymbol ? currencies[code].symbol : code; // Render Items const itemComponents = invoice.rows.map((row, index) => ( - - {padStart(index + 1, 2, 0)}. - {row.description} - - {currencyBefore ? currency : null}{' '} - {formatNumber(row.price, fraction, separator)}{' '} - {currencyBefore ? null : currency} - - {row.quantity} - - {currencyBefore ? currency : null}{' '} - {formatNumber(row.subtotal, fraction, separator)}{' '} - {currencyBefore ? null : currency} - - + <> + + {padStart(index + 1, 2, 0)}. + {row.description} + + {currencyBefore ? currency : null}{' '} + {formatNumber(row.price, fraction, separator)}{' '} + {currencyBefore ? null : currency} + + {row.quantity} + + {currencyBefore ? currency : null}{' '} + {formatNumber(row.subtotal, fraction, separator)}{' '} + {currencyBefore ? null : currency} + + + {row.subitems && + row.subitems.length > 0 && + row.subitems.map((subitem) => ( + + + + {subitem.description} + + + ))} + )); return ( - +
- - - - - + + + + + {itemComponents} @@ -160,13 +176,11 @@ const Main = function({ invoice, configs, t }) { @@ -175,7 +189,7 @@ const Main = function({ invoice, configs, t }) { {tax.method === 'reverse' ? ( ) : ( )} @@ -220,12 +238,12 @@ const Main = function({ invoice, configs, t }) { customAccentColor={customAccentColor} > + @@ -233,7 +251,7 @@ const Main = function({ invoice, configs, t }) {
{t('preview:common:order', {lng: language})}{t('preview:common:itemDescription', {lng: language})}{t('preview:common:price', {lng: language})}{t('preview:common:qty', {lng: language})}{t('preview:common:subtotal', {lng: language})} + {t('preview:common:order', { lng: language })} + {t('preview:common:itemDescription', { lng: language })} + {t('preview:common:price', { lng: language })} + + {t('preview:common:qty', { lng: language })} + + {t('preview:common:subtotal', { lng: language })} +
- {t('preview:common:subtotal', {lng: language})} + {t('preview:common:subtotal', { lng: language })} - {currencyBefore ? currency : null} - {' '} - {formatNumber(invoice.subtotal, fraction, separator)} - {' '} + {currencyBefore ? currency : null}{' '} + {formatNumber(invoice.subtotal, fraction, separator)}{' '} {currencyBefore ? null : currency}
- {t('form:fields:discount:name', {lng: language})}{' '} + {t('form:fields:discount:name', { lng: language })}{' '} {discount.type === 'percentage' && ( {discount.amount}% )} @@ -196,19 +210,23 @@ const Main = function({ invoice, configs, t }) { - {t('form:fields:tax:name', {lng: language})} {tax.amount}% + {t('form:fields:tax:name', { lng: language })} {tax.amount}% - {t('form:fields:tax:reverse', {lng: language})} + {t('form:fields:tax:reverse', { lng: language })} {currencyBefore ? currency : null}{' '} - {formatNumber(getInvoiceValue(invoice).taxAmount, fraction, separator)}{' '} + {formatNumber( + getInvoiceValue(invoice).taxAmount, + fraction, + separator + )}{' '} {currencyBefore ? null : currency} - {t('preview:common:total', {lng: language})} + {t('preview:common:total', { lng: language })} + - {currencyBefore ? currency : null} - {' '} - {formatNumber(invoice.grandTotal, fraction, separator)} - {' '} + {currencyBefore ? currency : null}{' '} + {formatNumber(invoice.grandTotal, fraction, separator)}{' '} {currencyBefore ? null : currency}
); -} +}; Main.propTypes = { configs: PropTypes.object.isRequired, diff --git a/preview/templates/businessQuote/components/Main.jsx b/preview/templates/businessQuote/components/Main.jsx index 8687ea4..3a21760 100644 --- a/preview/templates/businessQuote/components/Main.jsx +++ b/preview/templates/businessQuote/components/Main.jsx @@ -14,7 +14,7 @@ const InvoiceContent = styled.div` display: flex; margin-top: 1.5em; margin-bottom: 1.5em; - ${props => + ${(props) => props.alignItems && ` align-items: ${props.alignItems}; @@ -31,7 +31,7 @@ const Table = styled.table` text-align: right; } } - ${props => + ${(props) => props.customAccentColor && ` th { border-bottom: 4px solid ${props.accentColor};} @@ -71,7 +71,7 @@ const InvoiceTotal = styled.tr` } } - ${props => + ${(props) => props.customAccentColor && ` td { @@ -130,21 +130,33 @@ const Main = function ({ invoice, configs, t }) { const currency = configs.useSymbol ? currencies[code].symbol : code; // Render Items const itemComponents = invoice.rows.map((row, index) => ( - - {padStart(index + 1, 2, 0)}. - {row.description} - - {currencyBefore ? currency : null}{' '} - {formatNumber(row.price, fraction, separator)}{' '} - {currencyBefore ? null : currency} - - {row.quantity} - - {currencyBefore ? currency : null}{' '} - {formatNumber(row.subtotal, fraction, separator)}{' '} - {currencyBefore ? null : currency} - - + <> + + {padStart(index + 1, 2, 0)}. + {row.description} + + {currencyBefore ? currency : null}{' '} + {formatNumber(row.price, fraction, separator)}{' '} + {currencyBefore ? null : currency} + + {row.quantity} + + {currencyBefore ? currency : null}{' '} + {formatNumber(row.subtotal, fraction, separator)}{' '} + {currencyBefore ? null : currency} + + + {row.subitems && + row.subitems.length > 0 && + row.subitems.map((subitem) => ( + + + + {subitem.description} + + + ))} + )); // Render Prepayment items @@ -157,36 +169,33 @@ const Main = function ({ invoice, configs, t }) { {currencyBefore ? currency : null}{' '} - {formatNumber( - getInvoiceValue(invoice).prepayment, - fraction, - separator - )}{' '} + {formatNumber(getInvoiceValue(invoice).prepayment, fraction, separator)}{' '} {currencyBefore ? null : currency} )); - - return ( - +
- + - - - + + + - - {itemComponents} - + {itemComponents} @@ -241,7 +248,11 @@ const Main = function ({ invoice, configs, t }) { ) : ( )} @@ -253,12 +264,12 @@ const Main = function ({ invoice, configs, t }) { customAccentColor={customAccentColor} > + @@ -266,7 +277,7 @@ const Main = function ({ invoice, configs, t }) {
{t('preview:common:order', { lng: language })} + {t('preview:common:order', { lng: language })} + {t('preview:common:itemDescription', { lng: language })}{t('preview:common:price', { lng: language })}{t('preview:common:qty', { lng: language })}{t('preview:common:subtotal', { lng: language })} + {t('preview:common:price', { lng: language })} + + {t('preview:common:qty', { lng: language })} + + {t('preview:common:subtotal', { lng: language })} +
@@ -194,10 +203,8 @@ const Main = function ({ invoice, configs, t }) { {t('preview:common:subtotal', { lng: language })} - {currencyBefore ? currency : null} - {' '} - {formatNumber(invoice.subtotal, fraction, separator)} - {' '} + {currencyBefore ? currency : null}{' '} + {formatNumber(invoice.subtotal, fraction, separator)}{' '} {currencyBefore ? null : currency}
{currencyBefore ? currency : null}{' '} - {formatNumber(getInvoiceValue(invoice).taxAmount, fraction, separator)}{' '} + {formatNumber( + getInvoiceValue(invoice).taxAmount, + fraction, + separator + )}{' '} {currencyBefore ? null : currency} - {t('preview:common:total', { lng: language })} + {t('preview:common:total', { lng: language })} + - {currencyBefore ? currency : null} - {' '} - {formatNumber(invoice.remaining, fraction, separator)} - {' '} + {currencyBefore ? currency : null}{' '} + {formatNumber(invoice.remaining, fraction, separator)}{' '} {currencyBefore ? null : currency}
); -} +}; Main.propTypes = { configs: PropTypes.object.isRequired, diff --git a/preview/templates/minimal/components/Main.jsx b/preview/templates/minimal/components/Main.jsx index 23ee6cf..ba2c51e 100644 --- a/preview/templates/minimal/components/Main.jsx +++ b/preview/templates/minimal/components/Main.jsx @@ -17,7 +17,7 @@ const Table = styled.table` margin-top: 50px; margin-bottom: 50px; width: 100%; - ${props => + ${(props) => props.alignItems && ` justify-content: ${props.alignItems}; @@ -113,7 +113,7 @@ function setAlignItems(configs) { } // Component -const Main = function({ invoice, configs, t }) { +const Main = function ({ invoice, configs, t }) { // Get currentLanguage const currentLanguage = configs.language; // Destructuring values @@ -125,26 +125,40 @@ const Main = function({ invoice, configs, t }) { const currency = configs.useSymbol ? currencies[code].symbol : code; // Render Items const itemComponents = invoice.rows.map((row, index) => ( - - - {padStart(index + 1, 2, 0)} - {'. '} - {row.description} ({row.quantity}) - - - {currencyBefore ? currency : null}{' '} - {formatNumber(row.subtotal, fraction, separator)}{' '} - {currencyBefore ? null : currency} - - + <> + + + {padStart(index + 1, 2, 0)} + {'. '} + {row.description} ({row.quantity}) + + + {currencyBefore ? currency : null}{' '} + {formatNumber(row.subtotal, fraction, separator)}{' '} + {currencyBefore ? null : currency} + + + {row.subitems && + row.subitems.length > 0 && + row.subitems.map((subitem) => ( + + + + {subitem.description} + + + ))} + )); return ( - - + + @@ -152,12 +166,10 @@ const Main = function({ invoice, configs, t }) { - + @@ -165,10 +177,11 @@ const Main = function({ invoice, configs, t }) { {tax && ( {tax.method === 'reverse' ? ( - + ) : ( +
{t('preview:common:itemDescription', {lng: currentLanguage})}{t('preview:common:price', {lng: currentLanguage})} + {t('preview:common:itemDescription', { lng: currentLanguage })} + {t('preview:common:price', { lng: currentLanguage })}
{t('preview:common:subtotal', {lng: currentLanguage})}{t('preview:common:subtotal', { lng: currentLanguage })} - {currencyBefore ? currency : null} - {' '} - {formatNumber(invoice.subtotal, fraction, separator)} - {' '} + {currencyBefore ? currency : null}{' '} + {formatNumber(invoice.subtotal, fraction, separator)}{' '} {currencyBefore ? null : currency} - {t('form:fields:tax:name', {lng: currentLanguage})} {tax.amount}% + {t('form:fields:tax:name', { lng: currentLanguage })} {tax.amount} + % {t('form:fields:tax:reverse', {lng: currentLanguage})}{t('form:fields:tax:reverse', { lng: currentLanguage })} {currencyBefore ? currency : null}{' '} @@ -186,7 +199,7 @@ const Main = function({ invoice, configs, t }) { {discount && ( - {t('form:fields:discount:name', {lng: currentLanguage})}{' '} + {t('form:fields:discount:name', { lng: currentLanguage })}{' '} {discount.type === 'percentage' && ( {discount.amount}% )} @@ -204,19 +217,17 @@ const Main = function({ invoice, configs, t }) { )} - {t('preview:common:total', {lng: currentLanguage})}{t('preview:common:total', { lng: currentLanguage })} - {currencyBefore ? currency : null} - {' '} - {formatNumber(invoice.grandTotal, fraction, separator)} - {' '} + {currencyBefore ? currency : null}{' '} + {formatNumber(invoice.grandTotal, fraction, separator)}{' '} {currencyBefore ? null : currency}
); -} +}; Main.propTypes = { configs: PropTypes.object.isRequired, From c3f02d6d652844ca20c9a9f19fbe78c6ad000d9c Mon Sep 17 00:00:00 2001 From: Andres Morelos Date: Sun, 19 Jun 2022 03:32:17 -0500 Subject: [PATCH 4/4] Increasing Version. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ee4bda3..4b085e9 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "invoncify", "homepage": "https://invoncify.andresmorelos.me", "productName": "Invoncify", - "version": "1.19.1", + "version": "1.21.0", "license": "GPL-3.0", "description": "Flexible invoicing desktop app with beautiful & customizable templates", "author": {