├── img ├── login.png ├── sale.png ├── alerts.png ├── backup.png ├── budget.png ├── discount.png ├── payment.png ├── products.png ├── cashier-open.png ├── past-cashiers.png ├── cashier-closed.png ├── product-detail.png └── register-product.png ├── src ├── styles │ ├── index.js │ ├── fade.css │ ├── global.js │ └── colors.js ├── screens │ ├── cashier │ │ ├── components │ │ │ ├── past-cashier │ │ │ │ ├── config │ │ │ │ │ ├── index.js │ │ │ │ │ └── tabConfig.js │ │ │ │ └── components │ │ │ │ │ ├── Title.js │ │ │ │ │ └── DateFilter.js │ │ │ ├── current-cashier │ │ │ │ └── components │ │ │ │ │ ├── cashier-open │ │ │ │ │ ├── config │ │ │ │ │ │ ├── index.js │ │ │ │ │ │ └── tabConfig.js │ │ │ │ │ └── components │ │ │ │ │ │ └── top-buttons-values │ │ │ │ │ │ ├── button-config.js │ │ │ │ │ │ ├── CashierButton.js │ │ │ │ │ │ └── dialog-config.js │ │ │ │ │ └── cashier-closed │ │ │ │ │ ├── components │ │ │ │ │ └── CashierClosedAlert.js │ │ │ │ │ └── index.js │ │ │ └── bottom-valeus │ │ │ │ ├── BottomItem.js │ │ │ │ ├── item-config.js │ │ │ │ └── index.js │ │ └── cashier-utils.js │ ├── user │ │ └── config │ │ │ ├── index.js │ │ │ ├── tabConfig.js │ │ │ └── filterConfig.js │ ├── budget │ │ ├── config │ │ │ ├── index.js │ │ │ ├── tabConfig.js │ │ │ └── filterConfig.js │ │ └── components │ │ │ ├── PayBudgetButton.js │ │ │ ├── SelectLimitDate.js │ │ │ ├── BudgetExtraComponent.js │ │ │ └── BudgetStatus.js │ ├── customer │ │ ├── config │ │ │ ├── index.js │ │ │ ├── tabConfig.js │ │ │ └── filterConfig.js │ │ └── components │ │ │ └── TopContent.js │ ├── product │ │ └── config │ │ │ ├── index.js │ │ │ ├── tabConfig.js │ │ │ └── filterConfig.js │ ├── provider │ │ ├── config │ │ │ ├── index.js │ │ │ ├── tabConfig.js │ │ │ └── filterConfig.js │ │ └── index.js │ ├── sales │ │ ├── config │ │ │ ├── index.js │ │ │ ├── tabConfig.js │ │ │ └── filterConfig.js │ │ └── index.js │ └── stock │ │ ├── config │ │ ├── index.js │ │ ├── tabConfig.js │ │ └── filterConfig.js │ │ └── index.js ├── store │ ├── sagas │ │ ├── event-handlers-types │ │ │ ├── backup.js │ │ │ ├── brand.js │ │ │ ├── cashier.js │ │ │ ├── user.js │ │ │ ├── sale.js │ │ │ ├── budget.js │ │ │ ├── product.js │ │ │ ├── customer.js │ │ │ ├── provider.js │ │ │ └── stock.js │ │ ├── social.js │ │ ├── execRequest.js │ │ ├── entitiesTypes.js │ │ ├── eventHandler.js │ │ ├── brand.js │ │ ├── customerDebits.js │ │ ├── print.js │ │ ├── user.js │ │ ├── provider.js │ │ ├── alerts.js │ │ └── customer.js │ ├── ducks │ │ ├── social.js │ │ ├── index.js │ │ ├── auth.js │ │ ├── brand.js │ │ ├── print.js │ │ └── customerDebits.js │ └── index.js ├── index.js ├── utils │ └── filter │ │ ├── functionalFilter.js │ │ ├── textFilter.js │ │ ├── numericFilter.js │ │ ├── index.js │ │ └── dateFilter.js ├── config │ └── reactotron.js ├── components │ ├── login │ │ └── components │ │ │ ├── styles.js │ │ │ └── LoginForm.js │ ├── header │ │ ├── index.js │ │ └── components │ │ │ ├── toolbar │ │ │ ├── components │ │ │ │ ├── backup-component │ │ │ │ │ ├── buttons-config.js │ │ │ │ │ └── ButtonAction.js │ │ │ │ ├── about-me │ │ │ │ │ └── index.js │ │ │ │ └── UserInfo.js │ │ │ └── index.js │ │ │ └── navigation-menu │ │ │ ├── index.js │ │ │ └── navigation-items.js │ ├── common │ │ ├── sale-confirmation │ │ │ ├── errors.js │ │ │ └── components │ │ │ │ ├── CashierClosedAlert.js │ │ │ │ ├── form-payment │ │ │ │ ├── items-config.js │ │ │ │ └── FormPaymentItem.js │ │ │ │ └── FooterItems.js │ │ ├── product-sale-component │ │ │ ├── calculateValues.js │ │ │ └── components │ │ │ │ ├── select-product │ │ │ │ └── components │ │ │ │ │ ├── filter │ │ │ │ │ └── components │ │ │ │ │ │ └── select-filter │ │ │ │ │ │ └── FilterOptionItem.js │ │ │ │ │ └── SelectProductsValues.js │ │ │ │ ├── footer-values │ │ │ │ └── components │ │ │ │ │ ├── RemoveButton.js │ │ │ │ │ └── ObservationItem.js │ │ │ │ └── top-row │ │ │ │ ├── components │ │ │ │ ├── CustomerDebits.js │ │ │ │ └── select-customer │ │ │ │ │ └── components │ │ │ │ │ └── SelectUserDialog.js │ │ │ │ └── index.js │ │ ├── Dialog.js │ │ ├── sale-detail-dialog │ │ │ └── index.js │ │ ├── FullScreenDialog.js │ │ ├── ActionButton.js │ │ ├── ItemFiltered.js │ │ └── filter │ │ │ └── components │ │ │ └── DateFilterDialog.js │ └── Root.js ├── App.js └── Router.js ├── public ├── favicon.ico ├── back-end │ ├── events-handlers │ │ ├── backup │ │ │ ├── types.js │ │ │ └── index.js │ │ ├── brand │ │ │ ├── types.js │ │ │ └── index.js │ │ ├── cashier │ │ │ ├── types.js │ │ │ └── index.js │ │ ├── user │ │ │ ├── types.js │ │ │ └── index.js │ │ ├── sale │ │ │ ├── types.js │ │ │ └── index.js │ │ ├── budget │ │ │ ├── types.js │ │ │ └── index.js │ │ ├── product │ │ │ ├── types.js │ │ │ └── index.js │ │ ├── customer │ │ │ ├── types.js │ │ │ └── index.js │ │ ├── provider │ │ │ ├── types.js │ │ │ └── index.js │ │ ├── stock │ │ │ ├── types.js │ │ │ └── index.js │ │ └── index.js │ ├── config │ │ └── database.js │ ├── models │ │ ├── brand.js │ │ ├── stock.js │ │ ├── user.js │ │ ├── index.js │ │ ├── provider.js │ │ ├── product.js │ │ ├── customer.js │ │ ├── budget.js │ │ ├── cashier.js │ │ └── sale.js │ ├── entitiesTypes.js │ ├── controllers │ │ ├── brand │ │ │ └── index.js │ │ ├── cashier │ │ │ └── index.js │ │ ├── budget │ │ │ └── index.js │ │ ├── user │ │ │ └── index.js │ │ ├── customer │ │ │ └── index.js │ │ ├── provider │ │ │ └── index.js │ │ ├── sale │ │ │ └── index.js │ │ ├── product │ │ │ └── index.js │ │ ├── stock │ │ │ └── index.js │ │ └── backup │ │ │ └── index.js │ ├── electron-wait-react.js │ └── database │ │ └── migrations │ │ ├── 20181119031759-create-brands.js │ │ ├── 20181115043723-create-users.js │ │ ├── 20181120003008-create-stocks.js │ │ ├── 20181214011402-create-providers.js │ │ ├── 20181119222451-create-products.js │ │ ├── 20181214005947-create-customers.js │ │ ├── 20181218211159-create-budgets.js │ │ ├── 20181222225729-create-cashiers.js │ │ └── 20181208015057-create-sales.js ├── manifest.json ├── index.html └── electron.js ├── resources └── icon.png ├── .flowconfig ├── .editorconfig ├── .babelrc ├── .gitignore ├── .sequelizerc ├── docker-compose.yml ├── LICENSE ├── .eslintrc.json └── package.json /img/login.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steniowagner/store-system/HEAD/img/login.png -------------------------------------------------------------------------------- /img/sale.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steniowagner/store-system/HEAD/img/sale.png -------------------------------------------------------------------------------- /img/alerts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steniowagner/store-system/HEAD/img/alerts.png -------------------------------------------------------------------------------- /img/backup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steniowagner/store-system/HEAD/img/backup.png -------------------------------------------------------------------------------- /img/budget.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steniowagner/store-system/HEAD/img/budget.png -------------------------------------------------------------------------------- /img/discount.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steniowagner/store-system/HEAD/img/discount.png -------------------------------------------------------------------------------- /img/payment.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steniowagner/store-system/HEAD/img/payment.png -------------------------------------------------------------------------------- /img/products.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steniowagner/store-system/HEAD/img/products.png -------------------------------------------------------------------------------- /src/styles/index.js: -------------------------------------------------------------------------------- 1 | import colors from './colors'; 2 | 3 | export default { colors }; 4 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steniowagner/store-system/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /resources/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steniowagner/store-system/HEAD/resources/icon.png -------------------------------------------------------------------------------- /img/cashier-open.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steniowagner/store-system/HEAD/img/cashier-open.png -------------------------------------------------------------------------------- /img/past-cashiers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steniowagner/store-system/HEAD/img/past-cashiers.png -------------------------------------------------------------------------------- /img/cashier-closed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steniowagner/store-system/HEAD/img/cashier-closed.png -------------------------------------------------------------------------------- /img/product-detail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steniowagner/store-system/HEAD/img/product-detail.png -------------------------------------------------------------------------------- /img/register-product.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steniowagner/store-system/HEAD/img/register-product.png -------------------------------------------------------------------------------- /src/screens/cashier/components/past-cashier/config/index.js: -------------------------------------------------------------------------------- 1 | import tabConfig from './tabConfig'; 2 | 3 | export default { tabConfig }; 4 | -------------------------------------------------------------------------------- /src/screens/cashier/components/current-cashier/components/cashier-open/config/index.js: -------------------------------------------------------------------------------- 1 | import tabConfig from './tabConfig'; 2 | 3 | export default { tabConfig }; 4 | -------------------------------------------------------------------------------- /src/screens/user/config/index.js: -------------------------------------------------------------------------------- 1 | import filterConfig from './filterConfig'; 2 | import tabConfig from './tabConfig'; 3 | 4 | export default { filterConfig, tabConfig }; 5 | -------------------------------------------------------------------------------- /src/screens/budget/config/index.js: -------------------------------------------------------------------------------- 1 | import filterConfig from './filterConfig'; 2 | import tabConfig from './tabConfig'; 3 | 4 | export default { filterConfig, tabConfig }; 5 | -------------------------------------------------------------------------------- /src/screens/customer/config/index.js: -------------------------------------------------------------------------------- 1 | import filterConfig from './filterConfig'; 2 | import tabConfig from './tabConfig'; 3 | 4 | export default { filterConfig, tabConfig }; 5 | -------------------------------------------------------------------------------- /src/screens/product/config/index.js: -------------------------------------------------------------------------------- 1 | import filterConfig from './filterConfig'; 2 | import tabConfig from './tabConfig'; 3 | 4 | export default { filterConfig, tabConfig }; 5 | -------------------------------------------------------------------------------- /src/screens/provider/config/index.js: -------------------------------------------------------------------------------- 1 | import filterConfig from './filterConfig'; 2 | import tabConfig from './tabConfig'; 3 | 4 | export default { filterConfig, tabConfig }; 5 | -------------------------------------------------------------------------------- /src/screens/sales/config/index.js: -------------------------------------------------------------------------------- 1 | import filterConfig from './filterConfig'; 2 | import tabConfig from './tabConfig'; 3 | 4 | export default { filterConfig, tabConfig }; 5 | -------------------------------------------------------------------------------- /src/screens/stock/config/index.js: -------------------------------------------------------------------------------- 1 | import filterConfig from './filterConfig'; 2 | import tabConfig from './tabConfig'; 3 | 4 | export default { filterConfig, tabConfig }; 5 | -------------------------------------------------------------------------------- /.flowconfig: -------------------------------------------------------------------------------- 1 | [ignore] 2 | .*node_modules/.* 3 | .*src/back-end/.* 4 | 5 | [include] 6 | 7 | [libs] 8 | 9 | [lints] 10 | 11 | [options] 12 | all=true 13 | 14 | [strict] 15 | -------------------------------------------------------------------------------- /public/back-end/events-handlers/backup/types.js: -------------------------------------------------------------------------------- 1 | const backupEventTypes = { 2 | IMPORT_DATA: 'IMPORT_DATA', 3 | EXPORT_DATA: 'EXPORT_DATA', 4 | }; 5 | 6 | module.exports = backupEventTypes; 7 | -------------------------------------------------------------------------------- /public/back-end/events-handlers/brand/types.js: -------------------------------------------------------------------------------- 1 | const brandEventTypes = { 2 | CREATE_BRANDS: 'CREATE_BRANDS', 3 | READ_BRANDS: 'READ_BRANDS', 4 | }; 5 | 6 | module.exports = brandEventTypes; 7 | -------------------------------------------------------------------------------- /src/store/sagas/event-handlers-types/backup.js: -------------------------------------------------------------------------------- 1 | const backupEventTypes = { 2 | IMPORT_DATA: 'IMPORT_DATA', 3 | EXPORT_DATA: 'EXPORT_DATA', 4 | }; 5 | 6 | module.exports = backupEventTypes; 7 | -------------------------------------------------------------------------------- /src/store/sagas/event-handlers-types/brand.js: -------------------------------------------------------------------------------- 1 | const brandEventTypes = { 2 | CREATE_BRANDS: 'CREATE_BRANDS', 3 | READ_BRANDS: 'READ_BRANDS', 4 | }; 5 | 6 | module.exports = brandEventTypes; 7 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | import 'typeface-roboto'; 6 | 7 | ReactDOM.render(, document.getElementById('root')); 8 | -------------------------------------------------------------------------------- /public/back-end/config/database.js: -------------------------------------------------------------------------------- 1 | const database = { 2 | username: 'app', 3 | password: 'app', 4 | database: 'app', 5 | host: '127.0.0.1', 6 | dialect: 'mysql', 7 | }; 8 | 9 | module.exports = database; 10 | -------------------------------------------------------------------------------- /src/screens/user/config/tabConfig.js: -------------------------------------------------------------------------------- 1 | const tabConfig = [{ 2 | columnTitle: 'Name', 3 | dataField: 'name', 4 | }, { 5 | columnTitle: 'Username', 6 | dataField: 'username', 7 | }]; 8 | 9 | export default tabConfig; 10 | -------------------------------------------------------------------------------- /src/screens/provider/config/tabConfig.js: -------------------------------------------------------------------------------- 1 | const tabConfig = [{ 2 | columnTitle: 'Name', 3 | dataField: 'name', 4 | }, { 5 | columnTitle: 'Address', 6 | dataField: 'addressText', 7 | }]; 8 | 9 | export default tabConfig; 10 | -------------------------------------------------------------------------------- /src/store/ducks/social.js: -------------------------------------------------------------------------------- 1 | export const Types = { 2 | OPEN_URL: 'social/OPEN_URL', 3 | }; 4 | 5 | const Creators = { 6 | openURL: url => ({ 7 | type: Types.OPEN_URL, 8 | payload: { url }, 9 | }), 10 | }; 11 | 12 | export default Creators; 13 | -------------------------------------------------------------------------------- /src/store/sagas/social.js: -------------------------------------------------------------------------------- 1 | 2 | import { OPEN_URL } from './entitiesTypes'; 3 | 4 | const { ipcRenderer } = window.require('electron'); 5 | 6 | export const openURL = ({ payload }) => { 7 | const { url } = payload; 8 | 9 | ipcRenderer.send(OPEN_URL, url); 10 | }; 11 | -------------------------------------------------------------------------------- /public/back-end/events-handlers/cashier/types.js: -------------------------------------------------------------------------------- 1 | const cashierEventTypes = { 2 | IMPORT_CASHIERS: 'IMPORT_CASHIERS', 3 | CREATE_CASHIER: 'CREATE_CASHIER', 4 | READ_CASHIERS: 'READ_CASHIERS', 5 | UPDATE_CASHIER: 'UPDATE_CASHIER', 6 | }; 7 | 8 | module.exports = cashierEventTypes; 9 | -------------------------------------------------------------------------------- /src/store/sagas/event-handlers-types/cashier.js: -------------------------------------------------------------------------------- 1 | const cashierEventTypes = { 2 | IMPORT_CASHIERS: 'IMPORT_CASHIERS', 3 | CREATE_CASHIER: 'CREATE_CASHIER', 4 | READ_CASHIERS: 'READ_CASHIERS', 5 | UPDATE_CASHIER: 'UPDATE_CASHIER', 6 | }; 7 | 8 | module.exports = cashierEventTypes; 9 | -------------------------------------------------------------------------------- /src/screens/customer/config/tabConfig.js: -------------------------------------------------------------------------------- 1 | const tabConfig = [{ 2 | columnTitle: 'Name', 3 | dataField: 'name', 4 | }, { 5 | columnTitle: 'CPF', 6 | dataField: 'cpfText', 7 | }, { 8 | columnTitle: 'RG', 9 | dataField: 'rgText', 10 | }]; 11 | 12 | export default tabConfig; 13 | -------------------------------------------------------------------------------- /public/back-end/events-handlers/user/types.js: -------------------------------------------------------------------------------- 1 | const userEventTypes = { 2 | IMPORT_USERS: 'IMPORT_USERS', 3 | CREATE_USER: 'CREATE_USER', 4 | READ_USERS: 'READ_USERS', 5 | UPDATE_USER: 'UPDATE_USER', 6 | DELETE_USER: 'DELETE_USER', 7 | }; 8 | 9 | module.exports = userEventTypes; 10 | -------------------------------------------------------------------------------- /src/store/sagas/event-handlers-types/user.js: -------------------------------------------------------------------------------- 1 | const userEventTypes = { 2 | IMPORT_USERS: 'IMPORT_USERS', 3 | CREATE_USER: 'CREATE_USER', 4 | READ_USERS: 'READ_USERS', 5 | UPDATE_USER: 'UPDATE_USER', 6 | DELETE_USER: 'DELETE_USER', 7 | }; 8 | 9 | module.exports = userEventTypes; 10 | -------------------------------------------------------------------------------- /public/back-end/events-handlers/sale/types.js: -------------------------------------------------------------------------------- 1 | const saleEventTypes = { 2 | IMPORT_SALES: 'IMPORT_SALES', 3 | READ_SALE_BY_ID: 'READ_SALE_BY_ID', 4 | CREATE_SALE: 'CREATE_SALE', 5 | UPDATE_SALE: 'UPDATE_SALE', 6 | READ_SALES: 'READ_SALES', 7 | }; 8 | 9 | module.exports = saleEventTypes; 10 | -------------------------------------------------------------------------------- /src/store/sagas/event-handlers-types/sale.js: -------------------------------------------------------------------------------- 1 | const saleEventTypes = { 2 | IMPORT_SALES: 'IMPORT_SALES', 3 | READ_SALE_BY_ID: 'READ_SALE_BY_ID', 4 | CREATE_SALE: 'CREATE_SALE', 5 | UPDATE_SALE: 'UPDATE_SALE', 6 | READ_SALES: 'READ_SALES', 7 | }; 8 | 9 | module.exports = saleEventTypes; 10 | -------------------------------------------------------------------------------- /public/back-end/events-handlers/budget/types.js: -------------------------------------------------------------------------------- 1 | const budgetEventTypes = { 2 | IMPORT_BUDGET: 'IMPORT_BUDGET', 3 | CREATE_BUDGET: 'CREATE_BUDGET', 4 | READ_BUDGETS: 'READ_BUDGETS', 5 | UPDATE_BUDGET: 'UPDATE_BUDGET', 6 | DELETE_BUDGET: 'DELETE_BUDGET', 7 | }; 8 | 9 | module.exports = budgetEventTypes; 10 | -------------------------------------------------------------------------------- /src/store/sagas/event-handlers-types/budget.js: -------------------------------------------------------------------------------- 1 | const budgetEventTypes = { 2 | IMPORT_BUDGET: 'IMPORT_BUDGET', 3 | CREATE_BUDGET: 'CREATE_BUDGET', 4 | READ_BUDGETS: 'READ_BUDGETS', 5 | UPDATE_BUDGET: 'UPDATE_BUDGET', 6 | DELETE_BUDGET: 'DELETE_BUDGET', 7 | }; 8 | 9 | module.exports = budgetEventTypes; 10 | -------------------------------------------------------------------------------- /public/back-end/events-handlers/product/types.js: -------------------------------------------------------------------------------- 1 | const productEventTypes = { 2 | IMPORT_PRODUCTS: 'IMPORT_PRODUCTS', 3 | CREATE_PRODUCT: 'CREATE_PRODUCT', 4 | READ_PRODUCTS: 'READ_PRODUCTS', 5 | UPDATE_PRODUCT: 'UPDATE_PRODUCT', 6 | DELETE_PRODUCT: 'DELETE_PRODUCT', 7 | }; 8 | 9 | module.exports = productEventTypes; 10 | -------------------------------------------------------------------------------- /src/screens/stock/config/tabConfig.js: -------------------------------------------------------------------------------- 1 | const tabConfig = [{ 2 | columnTitle: 'Product', 3 | dataField: 'description', 4 | }, { 5 | columnTitle: 'Current Quantity', 6 | dataField: 'stockQuantity', 7 | }, { 8 | columnTitle: 'Min Quantity', 9 | dataField: 'minStockQuantity', 10 | }]; 11 | 12 | export default tabConfig; 13 | -------------------------------------------------------------------------------- /src/store/sagas/event-handlers-types/product.js: -------------------------------------------------------------------------------- 1 | const productEventTypes = { 2 | IMPORT_PRODUCTS: 'IMPORT_PRODUCTS', 3 | CREATE_PRODUCT: 'CREATE_PRODUCT', 4 | READ_PRODUCTS: 'READ_PRODUCTS', 5 | UPDATE_PRODUCT: 'UPDATE_PRODUCT', 6 | DELETE_PRODUCT: 'DELETE_PRODUCT', 7 | }; 8 | 9 | module.exports = productEventTypes; 10 | -------------------------------------------------------------------------------- /src/screens/provider/config/filterConfig.js: -------------------------------------------------------------------------------- 1 | import { FILTER_TYPES } from '../../../utils/filter'; 2 | 3 | const filterConfig = [{ 4 | placeholder: 'Enter the Name of the Provider you are looking for', 5 | type: FILTER_TYPES.TEXT, 6 | filterTitle: 'Name', 7 | dataField: 'name', 8 | }]; 9 | 10 | export default filterConfig; 11 | -------------------------------------------------------------------------------- /src/utils/filter/functionalFilter.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | type Config = { 4 | dataset: Array, 5 | behavior: Function, 6 | }; 7 | 8 | const functionalFilter = (config: Config): Array => { 9 | const { behavior, dataset } = config; 10 | 11 | return behavior(dataset); 12 | }; 13 | 14 | export default functionalFilter; 15 | -------------------------------------------------------------------------------- /public/back-end/events-handlers/customer/types.js: -------------------------------------------------------------------------------- 1 | const customerEventTypes = { 2 | IMPORT_CUSTOMERS: 'IMPORT_CUSTOMERS', 3 | CREATE_CUSTOMER: 'CREATE_CUSTOMER', 4 | READ_CUSTOMERS: 'READ_CUSTOMERS', 5 | UPDATE_CUSTOMER: 'UPDATE_CUSTOMER', 6 | DELETE_CUSTOMER: 'DELETE_CUSTOMER', 7 | }; 8 | 9 | module.exports = customerEventTypes; 10 | -------------------------------------------------------------------------------- /public/back-end/events-handlers/provider/types.js: -------------------------------------------------------------------------------- 1 | const providerEventTypes = { 2 | IMPORT_PROVIDERS: 'IMPORT_PROVIDERS', 3 | CREATE_PROVIDER: 'CREATE_PROVIDER', 4 | READ_PROVIDERS: 'READ_PROVIDERS', 5 | UPDATE_PROVIDER: 'UPDATE_PROVIDER', 6 | DELETE_PROVIDER: 'DELETE_PROVIDER', 7 | }; 8 | 9 | module.exports = providerEventTypes; 10 | -------------------------------------------------------------------------------- /src/store/sagas/event-handlers-types/customer.js: -------------------------------------------------------------------------------- 1 | const customerEventTypes = { 2 | IMPORT_CUSTOMERS: 'IMPORT_CUSTOMERS', 3 | CREATE_CUSTOMER: 'CREATE_CUSTOMER', 4 | READ_CUSTOMERS: 'READ_CUSTOMERS', 5 | UPDATE_CUSTOMER: 'UPDATE_CUSTOMER', 6 | DELETE_CUSTOMER: 'DELETE_CUSTOMER', 7 | }; 8 | 9 | module.exports = customerEventTypes; 10 | -------------------------------------------------------------------------------- /src/store/sagas/event-handlers-types/provider.js: -------------------------------------------------------------------------------- 1 | const providerEventTypes = { 2 | IMPORT_PROVIDERS: 'IMPORT_PROVIDERS', 3 | CREATE_PROVIDER: 'CREATE_PROVIDER', 4 | READ_PROVIDERS: 'READ_PROVIDERS', 5 | UPDATE_PROVIDER: 'UPDATE_PROVIDER', 6 | DELETE_PROVIDER: 'DELETE_PROVIDER', 7 | }; 8 | 9 | module.exports = providerEventTypes; 10 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "react" 4 | ], 5 | "plugins": [ 6 | [ 7 | "module-resolver", 8 | { 9 | "cwd": "babelrc", 10 | "root": [ 11 | "./src/front-end/components" 12 | ], 13 | "extensions": [ 14 | ".js", 15 | ".jsx" 16 | ] 17 | } 18 | ] 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /src/screens/product/config/tabConfig.js: -------------------------------------------------------------------------------- 1 | const tabConfig = [{ 2 | columnTitle: 'Code', 3 | dataField: 'barcode', 4 | }, { 5 | columnTitle: 'Description', 6 | dataField: 'description', 7 | }, { 8 | columnTitle: 'Brand', 9 | dataField: 'brandName', 10 | }, { 11 | columnTitle: 'Sale Price', 12 | dataField: 'salePriceText', 13 | }]; 14 | 15 | export default tabConfig; 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | /dist 12 | 13 | # misc 14 | .DS_Store 15 | .env.local 16 | .env.development.local 17 | .env.test.local 18 | .env.production.local 19 | 20 | npm-debug.log* 21 | yarn-debug.log* 22 | yarn-error.log* 23 | -------------------------------------------------------------------------------- /public/back-end/models/brand.js: -------------------------------------------------------------------------------- 1 | const BrandModel = (sequelize, DataTypes) => { 2 | const { STRING } = DataTypes; 3 | 4 | const Model = sequelize.define('Brand', { 5 | name: { 6 | validate: { 7 | notEmpty: true, 8 | }, 9 | allowNull: false, 10 | type: STRING, 11 | }, 12 | }); 13 | 14 | return Model; 15 | }; 16 | 17 | module.exports = BrandModel; 18 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /src/styles/fade.css: -------------------------------------------------------------------------------- 1 | .fade-appear, 2 | .fade-enter { 3 | opacity: 0; 4 | z-index: 1; 5 | } 6 | .fade-appear-active, 7 | .fade-enter.fade-enter-active { 8 | opacity: 1; 9 | transition: opacity 300ms linear 150ms; 10 | } 11 | 12 | .fade-exit { 13 | opacity: 1; 14 | } 15 | 16 | .fade-exit.fade-exit-active { 17 | opacity: 0; 18 | transition: opacity 150ms linear; 19 | } 20 | -------------------------------------------------------------------------------- /.sequelizerc: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | const pathsConfig = { 4 | 'config': path.resolve('src', 'back-end', 'config', 'database.js'), 5 | 'models-path': path.resolve('src', 'back-end', 'models'), 6 | 'seeders-path': path.resolve('src', 'back-end', 'database', 'seeders'), 7 | 'migrations-path': path.resolve('src', 'back-end', 'database', 'migrations'), 8 | }; 9 | 10 | module.exports = pathsConfig; 11 | -------------------------------------------------------------------------------- /src/config/reactotron.js: -------------------------------------------------------------------------------- 1 | import Reactotron from 'reactotron-react-js'; 2 | import { reactotronRedux } from 'reactotron-redux'; 3 | import sagaPlugin from 'reactotron-redux-saga'; 4 | 5 | if (process.env.NODE_ENV === 'development') { 6 | const tron = Reactotron.configure() 7 | .use(reactotronRedux()) 8 | .use(sagaPlugin()) 9 | .connect(); 10 | 11 | tron.clear(); 12 | 13 | console.tron = tron; 14 | } 15 | -------------------------------------------------------------------------------- /src/styles/global.js: -------------------------------------------------------------------------------- 1 | import { injectGlobal } from 'styled-components'; 2 | 3 | injectGlobal` 4 | * { 5 | box-sizing:border-box; 6 | padding:0; 7 | margin:0; 8 | } 9 | 10 | html { font-family: 'Roboto', sans-serif; } 11 | 12 | body > #root > div { 13 | -webkit-font-smoothing: antialiased !important; 14 | text-rendering: optimizeLegibility !important; 15 | display: block; 16 | height: 100vh; 17 | }, 18 | `; 19 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | 3 | volumes: 4 | store-system-data: 5 | driver: local 6 | 7 | services: 8 | mysql: 9 | image: ambientum/mysql:5.7 10 | container_name: store-system-app 11 | volumes: 12 | - store-system-data:/var/lib/mysql 13 | ports: 14 | - "3306:3306" 15 | environment: 16 | - MYSQL_ROOT_PASSWORD=app 17 | - MYSQL_DATABASE=app 18 | - MYSQL_USER=app 19 | - MYSQL_PASSWORD=app 20 | -------------------------------------------------------------------------------- /public/back-end/events-handlers/stock/types.js: -------------------------------------------------------------------------------- 1 | const stockEventTypes = { 2 | TAKE_AWAY_PRODUCTS_STOCK: 'TAKE_AWAY_PRODUCTS_STOCK', 3 | IMPORT_STOCK: 'IMPORT_STOCK', 4 | UPDATE_PRODUCTS_STOCK: 'UPDATE_PRODUCTS_STOCK', 5 | RETURN_PRODUTS_STOCK: 'RETURN_PRODUTS_STOCK', 6 | INSERT_PRODUCT_STOCK: 'INSERT_PRODUCT_STOCK', 7 | UPDATE_PRODUCT_STOCK: 'UPDATE_PRODUCT_STOCK', 8 | READ_STOCK: 'READ_STOCK', 9 | }; 10 | 11 | module.exports = stockEventTypes; 12 | -------------------------------------------------------------------------------- /src/store/sagas/event-handlers-types/stock.js: -------------------------------------------------------------------------------- 1 | const stockEventTypes = { 2 | TAKE_AWAY_PRODUCTS_STOCK: 'TAKE_AWAY_PRODUCTS_STOCK', 3 | IMPORT_STOCK: 'IMPORT_STOCK', 4 | UPDATE_PRODUCTS_STOCK: 'UPDATE_PRODUCTS_STOCK', 5 | RETURN_PRODUTS_STOCK: 'RETURN_PRODUTS_STOCK', 6 | INSERT_PRODUCT_STOCK: 'INSERT_PRODUCT_STOCK', 7 | UPDATE_PRODUCT_STOCK: 'UPDATE_PRODUCT_STOCK', 8 | READ_STOCK: 'READ_STOCK', 9 | }; 10 | 11 | module.exports = stockEventTypes; 12 | -------------------------------------------------------------------------------- /src/components/login/components/styles.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | export const Wrapper = styled.div` 4 | display: flex; 5 | flex-direction: column; 6 | align-items: center; 7 | justify-content: center; 8 | `; 9 | 10 | export const InputWrapper = styled.div` 11 | margin-bottom: 16px; 12 | `; 13 | 14 | export const ButtonWrapper = styled.div` 15 | width: 100%; 16 | display: flex; 17 | justify-content: flex-end; 18 | margin-top: 16px; 19 | `; 20 | -------------------------------------------------------------------------------- /src/screens/user/config/filterConfig.js: -------------------------------------------------------------------------------- 1 | import { FILTER_TYPES } from '../../../utils/filter'; 2 | 3 | const filterConfig = [{ 4 | placeholder: 'Enter the Name of the User you are looking for', 5 | type: FILTER_TYPES.TEXT, 6 | filterTitle: 'Name', 7 | dataField: 'name', 8 | }, { 9 | placeholder: 'Enter the Username of the User you are looking for', 10 | type: FILTER_TYPES.TEXT, 11 | filterTitle: 'Username', 12 | dataField: 'username', 13 | }]; 14 | 15 | export default filterConfig; 16 | -------------------------------------------------------------------------------- /src/screens/budget/components/PayBudgetButton.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React from 'react'; 4 | 5 | import ActionButton from '../../../components/common/ActionButton'; 6 | 7 | type Props = { 8 | onToggleSaleConfirmationDialog: Function, 9 | }; 10 | 11 | const PayBudgetButton = ({ onToggleSaleConfirmationDialog }: Props): Object => ( 12 | 16 | ); 17 | 18 | export default PayBudgetButton; 19 | -------------------------------------------------------------------------------- /src/components/header/index.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React from 'react'; 4 | 5 | import AppBar from '@material-ui/core/AppBar'; 6 | 7 | import NavigationMenu from './components/navigation-menu'; 8 | import Toolbar from './components/toolbar'; 9 | 10 | const Header = (): Object => ( 11 | 16 | 17 | 18 | 19 | ); 20 | 21 | export default Header; 22 | -------------------------------------------------------------------------------- /src/store/sagas/execRequest.js: -------------------------------------------------------------------------------- 1 | import { handleEventUnsubscription, handleEventSubscription } from './eventHandler'; 2 | import { OPERATION_REQUEST } from './entitiesTypes'; 3 | 4 | const { ipcRenderer } = window.require('electron'); 5 | 6 | export default function* execRequest(entity, action, tag, args) { 7 | ipcRenderer.send(OPERATION_REQUEST, entity, action, tag, args); 8 | const { result } = yield handleEventSubscription(tag); 9 | handleEventUnsubscription(tag); 10 | 11 | return result; 12 | } 13 | -------------------------------------------------------------------------------- /public/back-end/events-handlers/brand/index.js: -------------------------------------------------------------------------------- 1 | const brandController = require('../../controllers/brand'); 2 | const BRAND_OPERATIONS = require('./types'); 3 | 4 | const handleProviderEvent = (operation, args) => { 5 | switch (operation) { 6 | case BRAND_OPERATIONS.CREATE_BRANDS: return brandController.createBrands(args); 7 | 8 | case BRAND_OPERATIONS.READ_BRANDS: return brandController.getAllBrands(); 9 | 10 | default: return {}; 11 | } 12 | }; 13 | 14 | module.exports = handleProviderEvent; 15 | -------------------------------------------------------------------------------- /src/screens/sales/config/tabConfig.js: -------------------------------------------------------------------------------- 1 | const tabConfig = [{ 2 | columnTitle: 'Code', 3 | dataField: 'code', 4 | }, { 5 | columnTitle: 'Customer', 6 | dataField: 'customerName', 7 | }, { 8 | columnTitle: 'Date', 9 | dataField: 'dateToShow', 10 | }, { 11 | columnTitle: 'Salesman', 12 | dataField: 'salesman', 13 | }, { 14 | columnTitle: 'Sub-total', 15 | dataField: 'subtotalText', 16 | }, { 17 | columnTitle: 'Total', 18 | dataField: 'totalText', 19 | }]; 20 | 21 | export default tabConfig; 22 | -------------------------------------------------------------------------------- /public/back-end/entitiesTypes.js: -------------------------------------------------------------------------------- 1 | const entities = { 2 | OPERATION_RESPONSE: 'OPERATION_RESPONSE', 3 | CLOSE_PRINT_WINDOW: 'CLOSE_PRINT_WINDOW', 4 | OPEN_PRINT_WINDOW: 'OPEN_PRINT_WINDOW', 5 | OPERATION_REQUEST: 'OPERATION_REQUEST', 6 | PROVIDER: 'PROVIDER', 7 | CUSTOMER: 'CUSTOMER', 8 | CASHIER: 'CASHIER', 9 | PRODUCT: 'PRODUCT', 10 | BUDGET: 'BUDGET', 11 | BACKUP: 'BACKUP', 12 | STOCK: 'STOCK', 13 | BRAND: 'BRAND', 14 | USER: 'USER', 15 | SALE: 'SALE', 16 | }; 17 | 18 | module.exports = entities; 19 | -------------------------------------------------------------------------------- /public/back-end/events-handlers/backup/index.js: -------------------------------------------------------------------------------- 1 | const backupController = require('../../controllers/backup'); 2 | const BACKUP_OPERATIONS = require('./types'); 3 | 4 | const handleBackupEvent = (operation, args) => { 5 | switch (operation) { 6 | case BACKUP_OPERATIONS.IMPORT_DATA: return backupController.importFromBackupFile(args); 7 | 8 | case BACKUP_OPERATIONS.EXPORT_DATA: return backupController.exportToBackupFile(args); 9 | 10 | default: return {}; 11 | } 12 | }; 13 | 14 | module.exports = handleBackupEvent; 15 | -------------------------------------------------------------------------------- /src/screens/cashier/components/past-cashier/config/tabConfig.js: -------------------------------------------------------------------------------- 1 | const tabConfig = [{ 2 | columnTitle: 'Date', 3 | dataField: 'dateToShow', 4 | }, { 5 | columnTitle: 'Initial Money Quantity in Cashier', 6 | dataField: 'initialMoneyCashierText', 7 | }, { 8 | columnTitle: 'Total Inserted', 9 | dataField: 'totalIncomeText', 10 | }, { 11 | columnTitle: 'Total Withdrawn', 12 | dataField: 'totalOutcomeText', 13 | }, { 14 | columnTitle: 'Profit', 15 | dataField: 'totalProfitText', 16 | }]; 17 | 18 | export default tabConfig; 19 | -------------------------------------------------------------------------------- /src/store/sagas/entitiesTypes.js: -------------------------------------------------------------------------------- 1 | const entities = { 2 | OPERATION_RESPONSE: 'OPERATION_RESPONSE', 3 | CLOSE_PRINT_WINDOW: 'CLOSE_PRINT_WINDOW', 4 | OPEN_PRINT_WINDOW: 'OPEN_PRINT_WINDOW', 5 | OPERATION_REQUEST: 'OPERATION_REQUEST', 6 | OPEN_URL: 'OPEN_URL', 7 | PROVIDER: 'PROVIDER', 8 | CUSTOMER: 'CUSTOMER', 9 | CASHIER: 'CASHIER', 10 | PRODUCT: 'PRODUCT', 11 | BUDGET: 'BUDGET', 12 | BACKUP: 'BACKUP', 13 | STOCK: 'STOCK', 14 | BRAND: 'BRAND', 15 | USER: 'USER', 16 | SALE: 'SALE', 17 | }; 18 | 19 | module.exports = entities; 20 | -------------------------------------------------------------------------------- /src/styles/colors.js: -------------------------------------------------------------------------------- 1 | export default { 2 | white: '#FFF', 3 | tableOddColor: '#f2f2f2', 4 | containerColor: '#f4f4f4', 5 | darkText: '#rgba(0, 0, 0, 0.8)', 6 | headerText: '#6F6F6F', 7 | affirmative: '#5977e2', 8 | customInactiveButton: '#abbbf4', 9 | lightGray: '#f2f2f2', 10 | inputBorder: '#C9D2D7', 11 | mediumGray: '#494949', 12 | mediumGrayDisabled: '#a3a3a3', 13 | success: '#1DB954', 14 | successDisabled: '#62fc98', 15 | danger: '#FF5A60', 16 | dangerDisabled: '#f97c81', 17 | warning: '#ffc107', 18 | warningDisabled: '#ffdb72', 19 | }; 20 | -------------------------------------------------------------------------------- /src/screens/budget/config/tabConfig.js: -------------------------------------------------------------------------------- 1 | const tabConfig = [{ 2 | columnTitle: 'Code', 3 | dataField: 'code', 4 | }, { 5 | columnTitle: 'Request Date', 6 | dataField: 'dateToShow', 7 | }, { 8 | columnTitle: 'Validity', 9 | dataField: 'validityDate', 10 | }, { 11 | columnTitle: 'Customer', 12 | dataField: 'customerName', 13 | }, { 14 | columnTitle: 'Status', 15 | dataField: 'status', 16 | }, { 17 | columnTitle: 'Sub-total', 18 | dataField: 'subtotalText', 19 | }, { 20 | columnTitle: 'Total', 21 | dataField: 'totalText', 22 | }]; 23 | 24 | export default tabConfig; 25 | -------------------------------------------------------------------------------- /src/screens/product/config/filterConfig.js: -------------------------------------------------------------------------------- 1 | import { FILTER_TYPES } from '../../../utils/filter'; 2 | 3 | const filterConfig = [{ 4 | placeholder: 'Enter the Barcode', 5 | type: FILTER_TYPES.TEXT, 6 | filterTitle: 'Barcode', 7 | dataField: 'barcode', 8 | }, { 9 | placeholder: 'Enter the Description', 10 | type: FILTER_TYPES.TEXT, 11 | filterTitle: 'Description', 12 | dataField: 'description', 13 | }, { 14 | placeholder: 'Enter the Brand', 15 | type: FILTER_TYPES.TEXT, 16 | filterTitle: 'Brand', 17 | dataField: 'brandName', 18 | }]; 19 | 20 | export default filterConfig; 21 | -------------------------------------------------------------------------------- /public/back-end/events-handlers/cashier/index.js: -------------------------------------------------------------------------------- 1 | const cashierController = require('../../controllers/cashier'); 2 | const CASHIER_OPERATIONS = require('./types'); 3 | 4 | const handleBudgetEvent = (operation, args) => { 5 | switch (operation) { 6 | case CASHIER_OPERATIONS.CREATE_CASHIER: return cashierController.create(args); 7 | 8 | case CASHIER_OPERATIONS.READ_CASHIERS: return cashierController.readAll(); 9 | 10 | case CASHIER_OPERATIONS.UPDATE_CASHIER: return cashierController.update(args); 11 | 12 | default: return {}; 13 | } 14 | }; 15 | 16 | module.exports = handleBudgetEvent; 17 | -------------------------------------------------------------------------------- /public/back-end/controllers/brand/index.js: -------------------------------------------------------------------------------- 1 | const { Brand } = require('../../models'); 2 | 3 | exports.getAllBrands = async () => { 4 | try { 5 | return await Brand.findAll({ raw: true }); 6 | } catch (err) { 7 | return []; 8 | } 9 | }; 10 | 11 | exports.createBrands = async ({ brandsCreated, brandSelected }) => { 12 | try { 13 | await Promise.all(brandsCreated.map(async brand => Brand.create(brand))); 14 | 15 | const brandCreated = await Brand.findOne({ raw: true, where: { name: brandSelected } }); 16 | 17 | return brandCreated; 18 | } catch (err) { 19 | return err; 20 | } 21 | }; 22 | -------------------------------------------------------------------------------- /src/utils/filter/textFilter.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | type Config = { 4 | dataset: Array, 5 | filter: string, 6 | value: string, 7 | }; 8 | 9 | const textFilter = (filterConfig: Config): Array => { 10 | const { dataset, filter, value } = filterConfig; 11 | 12 | if (filter === 'all') return dataset; 13 | 14 | const stringToTest = value.replace(/\\/g, '\\\\'); 15 | const regexSubstring = new RegExp(stringToTest, 'i'); 16 | 17 | const datasetFiltered = dataset.filter(item => regexSubstring.test(item[filter])); 18 | 19 | return datasetFiltered; 20 | }; 21 | 22 | export default textFilter; 23 | -------------------------------------------------------------------------------- /public/back-end/models/stock.js: -------------------------------------------------------------------------------- 1 | const StockModel = (sequelize, DataTypes) => { 2 | const { INTEGER } = DataTypes; 3 | 4 | const Model = sequelize.define('Stock', { 5 | stockQuantity: { 6 | validate: { 7 | notEmpty: true, 8 | }, 9 | allowNull: false, 10 | type: INTEGER, 11 | }, 12 | 13 | minStockQuantity: { 14 | validate: { 15 | notEmpty: true, 16 | }, 17 | allowNull: false, 18 | type: INTEGER, 19 | }, 20 | }); 21 | 22 | Model.associate = ({ Product }) => Model.belongsTo(Product); 23 | 24 | return Model; 25 | }; 26 | 27 | module.exports = StockModel; 28 | -------------------------------------------------------------------------------- /src/components/common/sale-confirmation/errors.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | export const ERROR_TYPES = { 4 | BELOW_VALUE: 'BELOW_VALUE', 5 | ABOVE_VALUE: 'ABOVE_VALUE', 6 | }; 7 | 8 | const getErrorMessage = (type: string, value: number): Object => { 9 | const errors = { 10 | [ERROR_TYPES.BELOW_VALUE]: { 11 | message: `Faltam $ ${value.toFixed(2)}`, 12 | type: ERROR_TYPES.BELOW_VALUE, 13 | }, 14 | 15 | [ERROR_TYPES.ABOVE_VALUE]: { 16 | message: `Sobrando $ ${value.toFixed(2)}`, 17 | type: ERROR_TYPES.ABOVE_VALUE, 18 | }, 19 | }; 20 | 21 | return errors[type]; 22 | }; 23 | 24 | export default getErrorMessage; 25 | -------------------------------------------------------------------------------- /public/back-end/events-handlers/user/index.js: -------------------------------------------------------------------------------- 1 | const userController = require('../../controllers/user'); 2 | const USER_OPERATION_TYPES = require('./types'); 3 | 4 | const handleUserEvent = (operation, args) => { 5 | switch (operation) { 6 | case USER_OPERATION_TYPES.CREATE_USER: return userController.create(args); 7 | 8 | case USER_OPERATION_TYPES.READ_USERS: return userController.getAll(); 9 | 10 | case USER_OPERATION_TYPES.UPDATE_USER: return userController.edit(args); 11 | 12 | case USER_OPERATION_TYPES.DELETE_USER: return userController.remove(args); 13 | 14 | default: return {}; 15 | } 16 | }; 17 | 18 | module.exports = handleUserEvent; 19 | -------------------------------------------------------------------------------- /public/back-end/events-handlers/sale/index.js: -------------------------------------------------------------------------------- 1 | const saleController = require('../../controllers/sale'); 2 | const SALES_OPERATION_TYPES = require('./types'); 3 | 4 | const handleSaleEvent = (operation, args) => { 5 | switch (operation) { 6 | case SALES_OPERATION_TYPES.CREATE_SALE: return saleController.create(args); 7 | 8 | case SALES_OPERATION_TYPES.READ_SALES: return saleController.getAll(); 9 | 10 | case SALES_OPERATION_TYPES.READ_SALE_BY_ID: return saleController.getById(args); 11 | 12 | case SALES_OPERATION_TYPES.UPDATE_SALE: return saleController.edit(args); 13 | 14 | default: return {}; 15 | } 16 | }; 17 | 18 | module.exports = handleSaleEvent; 19 | -------------------------------------------------------------------------------- /src/store/sagas/eventHandler.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import { OPERATION_RESPONSE } from './entitiesTypes'; 4 | 5 | const { ipcRenderer } = window.require('electron'); 6 | 7 | export const handleEventSubscription = (eventTag: string): Object => { 8 | const handler = new Promise((resolve) => { 9 | const eventResponseId = `${OPERATION_RESPONSE}_${eventTag}`; 10 | 11 | ipcRenderer.on(eventResponseId, (_, result) => { 12 | resolve({ result }); 13 | }); 14 | }); 15 | 16 | return handler; 17 | }; 18 | 19 | export const handleEventUnsubscription = (eventTag: string): void => { 20 | ipcRenderer.removeAllListeners(`${OPERATION_RESPONSE}_${eventTag}`); 21 | }; 22 | -------------------------------------------------------------------------------- /public/back-end/events-handlers/budget/index.js: -------------------------------------------------------------------------------- 1 | const budgetController = require('../../controllers/budget'); 2 | const BUDGET_OPERATIONS = require('./types'); 3 | 4 | const handleBudgetEvent = (operation, args) => { 5 | switch (operation) { 6 | case BUDGET_OPERATIONS.CREATE_BUDGET: return budgetController.create(args); 7 | 8 | case BUDGET_OPERATIONS.READ_BUDGETS: return budgetController.readAll(); 9 | 10 | case BUDGET_OPERATIONS.UPDATE_BUDGET: return budgetController.update(args); 11 | 12 | case BUDGET_OPERATIONS.DELETE_BUDGET: return budgetController.remove(args); 13 | 14 | default: return {}; 15 | } 16 | }; 17 | 18 | module.exports = handleBudgetEvent; 19 | -------------------------------------------------------------------------------- /public/back-end/events-handlers/product/index.js: -------------------------------------------------------------------------------- 1 | const productController = require('../../controllers/product'); 2 | const PRODUCT_OPERATION = require('./types'); 3 | 4 | const handleProductEvent = (operation, args) => { 5 | switch (operation) { 6 | case PRODUCT_OPERATION.CREATE_PRODUCT: return productController.create(args); 7 | 8 | case PRODUCT_OPERATION.READ_PRODUCTS: return productController.getAll(); 9 | 10 | case PRODUCT_OPERATION.UPDATE_PRODUCT: return productController.edit(args); 11 | 12 | case PRODUCT_OPERATION.DELETE_PRODUCT: return productController.remove(args); 13 | 14 | default: return {}; 15 | } 16 | }; 17 | 18 | module.exports = handleProductEvent; 19 | -------------------------------------------------------------------------------- /public/back-end/controllers/cashier/index.js: -------------------------------------------------------------------------------- 1 | const { Cashier } = require('../../models'); 2 | 3 | exports.create = async (args) => { 4 | try { 5 | const { id } = await Cashier.create(args); 6 | 7 | return id; 8 | } catch (err) { 9 | return err; 10 | } 11 | }; 12 | 13 | exports.readAll = async () => { 14 | try { 15 | return await Cashier.findAll({ raw: true, order: [['updatedAt', 'DESC']] }); 16 | } catch (err) { 17 | return []; 18 | } 19 | }; 20 | 21 | exports.update = async (budgetUpdated) => { 22 | try { 23 | const { id } = budgetUpdated; 24 | 25 | return await Cashier.update({ ...budgetUpdated }, { where: { id } }); 26 | } catch (err) { 27 | return err; 28 | } 29 | }; 30 | -------------------------------------------------------------------------------- /public/back-end/events-handlers/stock/index.js: -------------------------------------------------------------------------------- 1 | const stockController = require('../../controllers/stock'); 2 | const STOCK_OPERATIONS_TYPES = require('./types'); 3 | 4 | const handleStockEvent = (operation, args) => { 5 | switch (operation) { 6 | case STOCK_OPERATIONS_TYPES.UPDATE_PRODUCTS_STOCK: return stockController.editInBatch(args); 7 | 8 | case STOCK_OPERATIONS_TYPES.INSERT_PRODUCT_STOCK: return stockController.insert(args); 9 | 10 | case STOCK_OPERATIONS_TYPES.UPDATE_PRODUCT_STOCK: return stockController.edit(args); 11 | 12 | case STOCK_OPERATIONS_TYPES.READ_STOCK: return stockController.getAll(); 13 | 14 | default: return {}; 15 | } 16 | }; 17 | 18 | module.exports = handleStockEvent; 19 | -------------------------------------------------------------------------------- /src/screens/cashier/components/past-cashier/components/Title.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React from 'react'; 4 | 5 | import styled from 'styled-components'; 6 | 7 | const TitleWrapper = styled.div` 8 | margin-bottom: 16px; 9 | `; 10 | 11 | type Props = { 12 | children: string, 13 | }; 14 | 15 | const Title = ({ children }: Props): Object => { 16 | const textWithDateText = `Registers of the day ${children}`; 17 | const defaultText = 'These are all Records'; 18 | 19 | const textToShow = (children ? textWithDateText : defaultText); 20 | 21 | return ( 22 | 23 |

24 | {textToShow} 25 |

26 |
27 | ); 28 | }; 29 | 30 | export default Title; 31 | -------------------------------------------------------------------------------- /public/back-end/models/user.js: -------------------------------------------------------------------------------- 1 | const UserModel = (sequelize, DataTypes) => { 2 | const { STRING } = DataTypes; 3 | 4 | const Model = sequelize.define('User', { 5 | username: { 6 | validate: { 7 | notEmpty: true, 8 | }, 9 | allowNull: false, 10 | type: STRING, 11 | unique: true, 12 | }, 13 | 14 | name: { 15 | validate: { 16 | notEmpty: true, 17 | }, 18 | allowNull: false, 19 | type: STRING, 20 | }, 21 | 22 | password: { 23 | validate: { 24 | notEmpty: true, 25 | }, 26 | allowNull: false, 27 | type: STRING, 28 | }, 29 | }); 30 | 31 | return Model; 32 | }; 33 | 34 | module.exports = UserModel; 35 | -------------------------------------------------------------------------------- /public/back-end/events-handlers/customer/index.js: -------------------------------------------------------------------------------- 1 | const customerController = require('../../controllers/customer'); 2 | const CUSTOMER_OPERATION_TYPES = require('./types'); 3 | 4 | const handleProviderEvent = (operation, args) => { 5 | switch (operation) { 6 | case CUSTOMER_OPERATION_TYPES.CREATE_CUSTOMER: return customerController.create(args); 7 | 8 | case CUSTOMER_OPERATION_TYPES.READ_CUSTOMERS: return customerController.getAll(); 9 | 10 | case CUSTOMER_OPERATION_TYPES.UPDATE_CUSTOMER: return customerController.edit(args); 11 | 12 | case CUSTOMER_OPERATION_TYPES.DELETE_CUSTOMER: return customerController.remove(args); 13 | 14 | default: return {}; 15 | } 16 | }; 17 | 18 | module.exports = handleProviderEvent; 19 | -------------------------------------------------------------------------------- /public/back-end/events-handlers/provider/index.js: -------------------------------------------------------------------------------- 1 | const providerController = require('../../controllers/provider'); 2 | const PROVIDER_OPERATION_TYPES = require('./types'); 3 | 4 | const handleProviderEvent = (operation, args) => { 5 | switch (operation) { 6 | case PROVIDER_OPERATION_TYPES.CREATE_PROVIDER: return providerController.create(args); 7 | 8 | case PROVIDER_OPERATION_TYPES.READ_PROVIDERS: return providerController.getAll(); 9 | 10 | case PROVIDER_OPERATION_TYPES.UPDATE_PROVIDER: return providerController.edit(args); 11 | 12 | case PROVIDER_OPERATION_TYPES.DELETE_PROVIDER: return providerController.remove(args); 13 | 14 | default: return {}; 15 | } 16 | }; 17 | 18 | module.exports = handleProviderEvent; 19 | -------------------------------------------------------------------------------- /src/store/index.js: -------------------------------------------------------------------------------- 1 | import { createStore, compose, applyMiddleware } from 'redux'; 2 | import createSagaMiddleware from 'redux-saga'; 3 | 4 | import sagas from './sagas'; 5 | import reducers from './ducks'; 6 | 7 | const middlewares = []; 8 | 9 | const sagaMonitor = process.env.NODE_ENV === 'development' ? console.tron.createSagaMonitor() : null; 10 | const sagaMiddleware = createSagaMiddleware({ sagaMonitor }); 11 | 12 | middlewares.push(sagaMiddleware); 13 | 14 | const createAppropriateStore = process.env.NODE_ENV === 'development' ? console.tron.createStore : createStore; 15 | 16 | const store = createAppropriateStore(reducers, compose(applyMiddleware(...middlewares))); 17 | 18 | sagaMiddleware.run(sagas); 19 | 20 | export default store; 21 | -------------------------------------------------------------------------------- /src/components/header/components/toolbar/components/backup-component/buttons-config.js: -------------------------------------------------------------------------------- 1 | import AttachFile from '@material-ui/icons/AttachFile'; 2 | import Save from '@material-ui/icons/Save'; 3 | 4 | import styled from 'styled-components'; 5 | 6 | const AttachFileIcon = styled(AttachFile)` 7 | margin-right: 16px; 8 | `; 9 | 10 | const SaveIcon = styled(Save)` 11 | margin-right: 16px; 12 | `; 13 | 14 | export const TYPES = { 15 | IMPORT: 'IMPORT', 16 | EXPORT: 'EXPORT', 17 | }; 18 | 19 | export const CONFIG = { 20 | [TYPES.IMPORT]: { 21 | message: 'Import Data from a Backup file', 22 | Icon: AttachFileIcon, 23 | }, 24 | 25 | [TYPES.EXPORT]: { 26 | message: 'Make the Backup of my current Data', 27 | Icon: SaveIcon, 28 | }, 29 | }; 30 | -------------------------------------------------------------------------------- /public/back-end/models/index.js: -------------------------------------------------------------------------------- 1 | const Sequelize = require('sequelize'); 2 | const path = require('path'); 3 | const fs = require('fs'); 4 | 5 | const config = require('../config/database.js'); 6 | 7 | const sequelize = new Sequelize(config); 8 | const db = {}; 9 | 10 | fs.readdirSync(__dirname) 11 | .filter(file => (file.indexOf('.') !== 0) && (file !== path.basename(__filename)) && (file.slice(-3) === '.js')) 12 | .forEach((file) => { 13 | const model = sequelize.import(path.join(__dirname, file)); 14 | db[model.name] = model; 15 | }); 16 | 17 | Object.keys(db).forEach((modelName) => { 18 | if (db[modelName].associate) { 19 | db[modelName].associate(db); 20 | } 21 | }); 22 | 23 | db.sequelize = sequelize; 24 | db.Sequelize = Sequelize; 25 | 26 | module.exports = db; 27 | -------------------------------------------------------------------------------- /public/back-end/electron-wait-react.js: -------------------------------------------------------------------------------- 1 | const net = require('net'); 2 | 3 | const port = process.env.PORT ? (process.env.PORT - 100) : 3000; 4 | 5 | process.env.ELECTRON_START_URL = `http://localhost:${port}`; 6 | process.env.NODE_ENV = 'development'; 7 | 8 | const client = new net.Socket(); 9 | 10 | let isElectronStarted = false; 11 | 12 | const tryConnectionWithReact = () => client.connect({ port }, () => { 13 | client.end(); 14 | if (!isElectronStarted) { 15 | console.log(`Electron is Running at ${process.env.ELECTRON_START_URL}!`); 16 | isElectronStarted = true; 17 | const { exec } = require('child_process'); 18 | exec('npm run electron'); 19 | } 20 | }); 21 | 22 | tryConnectionWithReact(); 23 | 24 | client.on('error', (_error) => { 25 | setTimeout(tryConnectionWithReact, 1000); 26 | }); 27 | -------------------------------------------------------------------------------- /public/back-end/models/provider.js: -------------------------------------------------------------------------------- 1 | const ProviderModel = (sequelize, DataTypes) => { 2 | const { STRING } = DataTypes; 3 | 4 | const Model = sequelize.define('Provider', { 5 | name: { 6 | validate: { 7 | notEmpty: true, 8 | }, 9 | allowNull: false, 10 | type: STRING, 11 | }, 12 | 13 | address: { 14 | type: STRING, 15 | }, 16 | 17 | neighborhood: { 18 | type: STRING, 19 | }, 20 | 21 | city: { 22 | type: STRING, 23 | }, 24 | 25 | state: { 26 | type: STRING, 27 | }, 28 | 29 | phone1: { 30 | type: STRING, 31 | }, 32 | 33 | phone2: { 34 | type: STRING, 35 | }, 36 | 37 | email: { 38 | type: STRING, 39 | }, 40 | }); 41 | 42 | return Model; 43 | }; 44 | 45 | module.exports = ProviderModel; 46 | -------------------------------------------------------------------------------- /src/screens/cashier/components/current-cashier/components/cashier-open/config/tabConfig.js: -------------------------------------------------------------------------------- 1 | const tabConfig = [{ 2 | columnTitle: 'Time', 3 | dataField: 'timestampText', 4 | }, { 5 | columnTitle: 'Operation', 6 | dataField: 'type', 7 | }, { 8 | columnTitle: 'Customer', 9 | dataField: 'customerName', 10 | }, { 11 | columnTitle: 'Code', 12 | dataField: 'code', 13 | }, { 14 | columnTitle: 'Salesman', 15 | dataField: 'salesman', 16 | }, { 17 | columnTitle: 'Value', 18 | dataField: 'valueText', 19 | }, { 20 | columnTitle: 'Discount', 21 | dataField: 'discountText', 22 | }, { 23 | columnTitle: 'Total', 24 | dataField: 'totalText', 25 | }, { 26 | columnTitle: 'Paid', 27 | dataField: 'valuePaid', 28 | }, { 29 | columnTitle: 'Debit', 30 | dataField: 'inDebitText', 31 | }]; 32 | 33 | export default tabConfig; 34 | -------------------------------------------------------------------------------- /src/store/ducks/index.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | 3 | import debits from './customerDebits'; 4 | import social from './social'; 5 | import customer from './customer'; 6 | import provider from './provider'; 7 | import cashier from './cashier'; 8 | import product from './product'; 9 | import alerts from './alerts'; 10 | import backup from './backup'; 11 | import budget from './budget'; 12 | import print from './print'; 13 | import stock from './stock'; 14 | import brand from './brand'; 15 | import auth from './auth'; 16 | import user from './user'; 17 | import sale from './sale'; 18 | 19 | export default combineReducers({ 20 | customer, 21 | provider, 22 | cashier, 23 | product, 24 | social, 25 | alerts, 26 | backup, 27 | budget, 28 | debits, 29 | brand, 30 | print, 31 | stock, 32 | auth, 33 | sale, 34 | user, 35 | }); 36 | -------------------------------------------------------------------------------- /src/store/ducks/auth.js: -------------------------------------------------------------------------------- 1 | import Immutable from 'seamless-immutable'; 2 | 3 | export const Types = { 4 | LOGOUT: 'auth/LOGOUT', 5 | LOGIN: 'auth/LOGIN', 6 | }; 7 | 8 | const INITIAL_STATE = Immutable({ 9 | isAuthenticated: false, 10 | user: null, 11 | }); 12 | 13 | export const Creators = { 14 | login: user => ({ 15 | type: Types.LOGIN, 16 | payload: { user }, 17 | }), 18 | 19 | logout: () => ({ 20 | type: Types.LOGOUT, 21 | }), 22 | }; 23 | 24 | const auth = (state = INITIAL_STATE, { payload, type }) => { 25 | switch (type) { 26 | case Types.LOGIN: 27 | return { 28 | isAuthenticated: true, 29 | user: payload.user, 30 | }; 31 | 32 | case Types.LOGOUT: 33 | return { 34 | ...INITIAL_STATE, 35 | }; 36 | 37 | default: 38 | return state; 39 | } 40 | }; 41 | 42 | export default auth; 43 | -------------------------------------------------------------------------------- /public/back-end/controllers/budget/index.js: -------------------------------------------------------------------------------- 1 | const { Budget } = require('../../models'); 2 | 3 | exports.create = async (args) => { 4 | try { 5 | const budget = await Budget.create(args); 6 | 7 | return budget.id; 8 | } catch (err) { 9 | return err; 10 | } 11 | }; 12 | 13 | exports.readAll = async () => { 14 | try { 15 | return await Budget.findAll({ raw: true, order: [['updatedAt', 'DESC']] }); 16 | } catch (err) { 17 | return []; 18 | } 19 | }; 20 | 21 | exports.update = async (budgetUpdated) => { 22 | const { id } = budgetUpdated; 23 | 24 | try { 25 | return await Budget.update({ ...budgetUpdated }, { where: { id } }); 26 | } catch (err) { 27 | return err; 28 | } 29 | }; 30 | 31 | exports.remove = async (id) => { 32 | try { 33 | return await Budget.destroy({ where: { id } }); 34 | } catch (err) { 35 | return err; 36 | } 37 | }; 38 | -------------------------------------------------------------------------------- /src/utils/filter/numericFilter.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | type Config = { 4 | dataset: Array, 5 | operator: string, 6 | filter: string, 7 | value: string, 8 | }; 9 | 10 | const numericFilter = (filterConfig: Config): Array => { 11 | const { 12 | operator, 13 | dataset, 14 | filter, 15 | value, 16 | } = filterConfig; 17 | 18 | const verifyOperatorValue = { 19 | '<': (_, currentValue) => value > currentValue, 20 | '>=': (_, currentValue) => value >= currentValue, 21 | '=': (_, currentValue) => value === currentValue, 22 | '>': (_, currentValue) => value < currentValue, 23 | '<=': (_, currentValue) => value <= currentValue, 24 | }; 25 | 26 | const datasetFiltered = dataset.filter(product => verifyOperatorValue[operator](value, product[filter])); 27 | 28 | return datasetFiltered; 29 | }; 30 | 31 | export default numericFilter; 32 | -------------------------------------------------------------------------------- /public/back-end/database/migrations/20181119031759-create-brands.js: -------------------------------------------------------------------------------- 1 | const onMigrate = (queryInterface, DataTypes) => { 2 | const { INTEGER, STRING, DATE } = DataTypes; 3 | 4 | queryInterface.createTable('Brands', { 5 | id: { 6 | allowNull: false, 7 | autoIncrement: true, 8 | primaryKey: true, 9 | type: INTEGER, 10 | }, 11 | 12 | name: { 13 | validate: { 14 | notEmpty: true, 15 | }, 16 | allowNull: false, 17 | type: STRING, 18 | unique: true, 19 | }, 20 | 21 | createdAt: { 22 | allowNull: false, 23 | type: DATE, 24 | }, 25 | 26 | updatedAt: { 27 | allowNull: false, 28 | type: DATE, 29 | }, 30 | }); 31 | }; 32 | 33 | const onRollback = queryInterface => queryInterface.dropTable('Brands'); 34 | 35 | module.exports = { 36 | up: onMigrate, 37 | 38 | down: onRollback, 39 | }; 40 | -------------------------------------------------------------------------------- /public/back-end/controllers/user/index.js: -------------------------------------------------------------------------------- 1 | const { User } = require('../../models'); 2 | 3 | exports.create = async (args) => { 4 | try { 5 | const user = await User.create(args); 6 | 7 | return user.id; 8 | } catch (err) { 9 | return err; 10 | } 11 | }; 12 | 13 | exports.getAll = async () => { 14 | try { 15 | return await User.findAll({ raw: true, order: [['updatedAt', 'DESC']] }); 16 | } catch (err) { 17 | return []; 18 | } 19 | }; 20 | 21 | exports.edit = async (userUpdated) => { 22 | try { 23 | return await User.update({ 24 | ...userUpdated, 25 | }, { 26 | where: { 27 | id: userUpdated.id, 28 | }, 29 | }); 30 | } catch (err) { 31 | return err; 32 | } 33 | }; 34 | 35 | exports.remove = async (id) => { 36 | try { 37 | return await User.destroy({ where: { id } }); 38 | } catch (err) { 39 | return err; 40 | } 41 | }; 42 | -------------------------------------------------------------------------------- /src/screens/sales/config/filterConfig.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import { FILTER_TYPES } from '../../../utils/filter'; 4 | 5 | const filterSalesWithDebit = (sales: Array): Array => sales.filter(sale => sale.inDebit > 0); 6 | 7 | const filterConfig = [{ 8 | placeholder: 'Enter the name of the Salesman', 9 | type: FILTER_TYPES.TEXT, 10 | filterTitle: 'Salesman', 11 | dataField: 'salesman', 12 | }, { 13 | placeholder: 'Enter the name of the Customer', 14 | type: FILTER_TYPES.TEXT, 15 | filterTitle: 'Customer', 16 | dataField: 'customerName', 17 | }, { 18 | placeholder: 'Enter the Sale\'s Code', 19 | type: FILTER_TYPES.TEXT, 20 | filterTitle: 'Code', 21 | dataField: 'code', 22 | }, { 23 | placeholder: 'Sales with Debit', 24 | type: FILTER_TYPES.FUNCTIONAL, 25 | filterTitle: 'In Debit', 26 | dataField: 'debit', 27 | behavior: filterSalesWithDebit, 28 | }]; 29 | 30 | export default filterConfig; 31 | -------------------------------------------------------------------------------- /src/components/Root.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React, { Fragment } from 'react'; 4 | 5 | import { BrowserRouter as ApplicationRouter } from 'react-router-dom'; 6 | 7 | import { connect } from 'react-redux'; 8 | 9 | import Router from '../Router'; 10 | import Header from './header'; 11 | import Login from './login'; 12 | 13 | const renderDashboard = (): Object => ( 14 | 15 | 16 |
17 | 18 | 19 | 20 | ); 21 | 22 | type Props = { 23 | auth: Object, 24 | }; 25 | 26 | const Root = ({ auth }: Props): Object => { 27 | const { isAuthenticated } = auth; 28 | 29 | return ( 30 | 31 | {isAuthenticated ? renderDashboard() : } 32 | 33 | ); 34 | }; 35 | 36 | const mapStateToProps = state => ({ 37 | auth: state.auth, 38 | }); 39 | 40 | export default connect(mapStateToProps)(Root); 41 | -------------------------------------------------------------------------------- /src/screens/customer/config/filterConfig.js: -------------------------------------------------------------------------------- 1 | import { FILTER_TYPES } from '../../../utils/filter'; 2 | 3 | const filterInDebit = (customers: Array): Array => customers.filter(customer => !!customer.isInDebit); 4 | 5 | const filterConfig = [{ 6 | placeholder: 'Enter the Name of the Customer you are looking for', 7 | type: FILTER_TYPES.TEXT, 8 | filterTitle: 'Name', 9 | dataField: 'name', 10 | }, { 11 | placeholder: 'Enter the CPF of the Customer you are looking for', 12 | type: FILTER_TYPES.TEXT, 13 | filterTitle: 'CPF', 14 | dataField: 'cpf', 15 | }, { 16 | placeholder: 'Enter the RG of the Customer you are looking for', 17 | type: FILTER_TYPES.TEXT, 18 | filterTitle: 'RG', 19 | dataField: 'rg', 20 | }, { 21 | placeholder: 'These are all Customers in Debit', 22 | type: FILTER_TYPES.FUNCTIONAL, 23 | filterTitle: 'In Debit', 24 | behavior: filterInDebit, 25 | }]; 26 | 27 | export default filterConfig; 28 | -------------------------------------------------------------------------------- /public/back-end/models/product.js: -------------------------------------------------------------------------------- 1 | const ProductModel = (sequelize, DataTypes) => { 2 | const { STRING, FLOAT } = DataTypes; 3 | 4 | const Model = sequelize.define('Product', { 5 | barcode: { 6 | validate: { 7 | notEmpty: true, 8 | }, 9 | allowNull: false, 10 | type: STRING, 11 | }, 12 | 13 | description: { 14 | validate: { 15 | notEmpty: true, 16 | }, 17 | allowNull: false, 18 | type: STRING, 19 | }, 20 | 21 | costPrice: { 22 | validate: { 23 | notEmpty: true, 24 | }, 25 | allowNull: false, 26 | type: FLOAT, 27 | }, 28 | 29 | salePrice: { 30 | validate: { 31 | notEmpty: true, 32 | }, 33 | allowNull: false, 34 | type: FLOAT, 35 | }, 36 | }); 37 | 38 | Model.associate = ({ Brand }) => Model.belongsTo(Brand); 39 | 40 | return Model; 41 | }; 42 | 43 | module.exports = ProductModel; 44 | -------------------------------------------------------------------------------- /src/components/header/components/toolbar/components/backup-component/ButtonAction.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React from 'react'; 4 | 5 | import Button from '@material-ui/core/Button'; 6 | import styled from 'styled-components'; 7 | 8 | const FilePathButtonWrapper = styled.div` 9 | display: flex; 10 | align-items: center; 11 | margin-top: 24px; 12 | margin-right: 16px; 13 | `; 14 | 15 | type Props = { 16 | isDisabled: boolean, 17 | action: Function, 18 | message: string, 19 | Icon: Object, 20 | }; 21 | 22 | const ButtonAction = ({ 23 | isDisabled, 24 | message, 25 | action, 26 | Icon, 27 | }: Props): Object => ( 28 | 29 | 38 | 39 | ); 40 | 41 | export default ButtonAction; 42 | -------------------------------------------------------------------------------- /public/back-end/controllers/customer/index.js: -------------------------------------------------------------------------------- 1 | const { Customer } = require('../../models'); 2 | 3 | exports.create = async (args) => { 4 | try { 5 | const customer = await Customer.create(args); 6 | 7 | return customer.id; 8 | } catch (err) { 9 | return err; 10 | } 11 | }; 12 | 13 | exports.getAll = async () => { 14 | try { 15 | return await Customer.findAll({ raw: true, order: [['updatedAt', 'DESC']] }); 16 | } catch (err) { 17 | return []; 18 | } 19 | }; 20 | 21 | exports.edit = async (customerUpdated) => { 22 | try { 23 | return await Customer.update({ 24 | ...customerUpdated, 25 | }, { 26 | where: { 27 | id: customerUpdated.id, 28 | }, 29 | }); 30 | } catch (err) { 31 | return err; 32 | } 33 | }; 34 | 35 | exports.remove = async (id) => { 36 | try { 37 | return await Customer.destroy({ where: { id } }); 38 | } catch (err) { 39 | return err; 40 | } 41 | }; 42 | -------------------------------------------------------------------------------- /public/back-end/controllers/provider/index.js: -------------------------------------------------------------------------------- 1 | const { Provider } = require('../../models'); 2 | 3 | exports.create = async (args) => { 4 | try { 5 | const provider = await Provider.create(args); 6 | 7 | return provider.id; 8 | } catch (err) { 9 | return err; 10 | } 11 | }; 12 | 13 | exports.getAll = async () => { 14 | try { 15 | return await Provider.findAll({ raw: true, order: [['updatedAt', 'DESC']] }); 16 | } catch (err) { 17 | return []; 18 | } 19 | }; 20 | 21 | exports.edit = async (providerUpdated) => { 22 | try { 23 | return await Provider.update({ 24 | ...providerUpdated, 25 | }, { 26 | where: { 27 | id: providerUpdated.id, 28 | }, 29 | }); 30 | } catch (err) { 31 | return err; 32 | } 33 | }; 34 | 35 | exports.remove = async (id) => { 36 | try { 37 | return await Provider.destroy({ where: { id } }); 38 | } catch (err) { 39 | return err; 40 | } 41 | }; 42 | -------------------------------------------------------------------------------- /public/back-end/database/migrations/20181115043723-create-users.js: -------------------------------------------------------------------------------- 1 | const onMigrate = (queryInterface, DataTypes) => { 2 | const { INTEGER, STRING, DATE } = DataTypes; 3 | 4 | queryInterface.createTable('Users', { 5 | id: { 6 | allowNull: false, 7 | autoIncrement: true, 8 | primaryKey: true, 9 | type: INTEGER, 10 | }, 11 | name: { 12 | allowNull: false, 13 | type: STRING, 14 | }, 15 | username: { 16 | allowNull: false, 17 | type: STRING, 18 | unique: true, 19 | }, 20 | password: { 21 | allowNull: false, 22 | type: STRING, 23 | }, 24 | createdAt: { 25 | allowNull: false, 26 | type: DATE, 27 | }, 28 | updatedAt: { 29 | allowNull: false, 30 | type: DATE, 31 | }, 32 | }); 33 | }; 34 | 35 | const onRollback = queryInterface => queryInterface.dropTable('Users'); 36 | 37 | module.exports = { 38 | up: onMigrate, 39 | 40 | down: onRollback, 41 | }; 42 | -------------------------------------------------------------------------------- /public/back-end/controllers/sale/index.js: -------------------------------------------------------------------------------- 1 | const { Sale } = require('../../models'); 2 | 3 | exports.create = async (args) => { 4 | try { 5 | const sale = await Sale.create(args); 6 | 7 | return sale.id; 8 | } catch (err) { 9 | return err; 10 | } 11 | }; 12 | 13 | exports.getAll = async () => { 14 | try { 15 | return await Sale.findAll({ raw: true, order: [['updatedAt', 'DESC']] }); 16 | } catch (err) { 17 | return []; 18 | } 19 | }; 20 | 21 | exports.getById = async (id) => { 22 | try { 23 | return await Sale.findOne({ 24 | raw: true, 25 | }, { 26 | where: { 27 | id, 28 | }, 29 | }); 30 | } catch (err) { 31 | return err; 32 | } 33 | }; 34 | 35 | exports.edit = async (saleUpdated) => { 36 | try { 37 | return await Sale.update({ 38 | ...saleUpdated, 39 | }, { 40 | where: { 41 | id: saleUpdated.id, 42 | }, 43 | }); 44 | } catch (err) { 45 | return err; 46 | } 47 | }; 48 | -------------------------------------------------------------------------------- /public/back-end/controllers/product/index.js: -------------------------------------------------------------------------------- 1 | const { Product, Brand } = require('../../models'); 2 | 3 | exports.create = async (args) => { 4 | try { 5 | const product = await Product.create(args); 6 | 7 | return product.id; 8 | } catch (err) { 9 | return err; 10 | } 11 | }; 12 | 13 | exports.getAll = async () => { 14 | try { 15 | return await Product.findAll({ 16 | order: [['updatedAt', 'DESC']], 17 | include: [Brand], 18 | raw: true, 19 | }); 20 | } catch (err) { 21 | return []; 22 | } 23 | }; 24 | 25 | exports.edit = async (productUpdated) => { 26 | try { 27 | return await Product.update({ 28 | ...productUpdated, 29 | }, { 30 | where: { 31 | id: productUpdated.id, 32 | }, 33 | }); 34 | } catch (err) { 35 | return err; 36 | } 37 | }; 38 | 39 | exports.remove = async (id) => { 40 | try { 41 | return await Product.destroy({ where: { id } }); 42 | } catch (err) { 43 | return err; 44 | } 45 | }; 46 | -------------------------------------------------------------------------------- /src/store/sagas/brand.js: -------------------------------------------------------------------------------- 1 | import { call, put } from 'redux-saga/effects'; 2 | 3 | import { Creators as BrandCreators } from '../ducks/brand'; 4 | 5 | import { READ_BRANDS, CREATE_BRANDS } from './event-handlers-types/brand'; 6 | import { BRAND } from './entitiesTypes'; 7 | import execRequest from './execRequest'; 8 | 9 | const EVENT_TAGS = { 10 | CREATE_BRANDS: 'BRANDS_CREATE', 11 | READ_ALL: 'BRANDS_READ_ALL', 12 | }; 13 | 14 | export function* getAllBrands() { 15 | try { 16 | const result = yield call(execRequest, BRAND, READ_BRANDS, EVENT_TAGS.READ_ALL); 17 | 18 | yield put(BrandCreators.getAllBrandsSuccess(result)); 19 | } catch (err) { 20 | yield put(BrandCreators.getAllBrandsFailure(err.message)); 21 | } 22 | } 23 | 24 | export function* createBrands(brandsCreated, brandSelected) { 25 | try { 26 | const params = { 27 | brandSelected, 28 | brandsCreated, 29 | }; 30 | 31 | const result = yield call(execRequest, BRAND, CREATE_BRANDS, EVENT_TAGS.CREATE_BRANDS, params); 32 | 33 | return result; 34 | } catch (err) { 35 | return err; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /public/back-end/database/migrations/20181120003008-create-stocks.js: -------------------------------------------------------------------------------- 1 | const onMigrate = (queryInterface, DataTypes) => { 2 | const { INTEGER, DATE } = DataTypes; 3 | 4 | queryInterface.createTable('Stocks', { 5 | id: { 6 | allowNull: false, 7 | autoIncrement: true, 8 | primaryKey: true, 9 | type: INTEGER, 10 | }, 11 | 12 | ProductId: { 13 | type: INTEGER, 14 | references: { model: 'Products', key: 'id' }, 15 | onUpdate: 'CASCADE', 16 | onDelete: 'CASCADE', 17 | allowNull: false, 18 | }, 19 | 20 | stockQuantity: { 21 | allowNull: false, 22 | type: INTEGER, 23 | }, 24 | 25 | minStockQuantity: { 26 | allowNull: false, 27 | type: INTEGER, 28 | }, 29 | 30 | createdAt: { 31 | allowNull: false, 32 | type: DATE, 33 | }, 34 | 35 | updatedAt: { 36 | allowNull: false, 37 | type: DATE, 38 | }, 39 | }); 40 | }; 41 | 42 | const onRollback = queryInterface => queryInterface.dropTable('Stocks'); 43 | 44 | module.exports = { 45 | up: onMigrate, 46 | 47 | down: onRollback, 48 | }; 49 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React from 'react'; 4 | 5 | import { MuiThemeProvider, createMuiTheme } from '@material-ui/core/styles'; 6 | import styled, { ThemeProvider } from 'styled-components'; 7 | import { Provider } from 'react-redux'; 8 | 9 | import './config/reactotron'; 10 | import './styles/global'; 11 | 12 | import Root from './components/Root'; 13 | 14 | import AppTheme from './styles'; 15 | import store from './store'; 16 | 17 | const Wrapper = styled.div` 18 | background-color: ${({ theme }) => theme.colors.containerColor}; 19 | `; 20 | 21 | const theme = createMuiTheme({ 22 | palette: { 23 | primary: { main: AppTheme.colors.affirmative }, 24 | secondary: { main: AppTheme.colors.white }, 25 | }, 26 | }); 27 | 28 | const App = (): Object => ( 29 | 32 | 35 | 36 | 39 | 40 | 41 | 42 | 43 | 44 | ); 45 | 46 | export default App; 47 | -------------------------------------------------------------------------------- /src/components/common/product-sale-component/calculateValues.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | export const getDiscountByMoney = (value: number): number => value; 4 | 5 | export const getDiscountByPercentage = (subTotal: number, value: number): number => { 6 | const percentage = (value / 100); 7 | const discountValue = (subTotal * percentage); 8 | 9 | return discountValue; 10 | }; 11 | 12 | export const calculateSubtotalValue = (products: Array): number => { 13 | const subTotal = products.reduce((current, product) => current + (product.salePrice * product.quantity), 0); 14 | 15 | return subTotal; 16 | }; 17 | 18 | export const calculateTotalValue = (discount: Object, subTotal: number): number => { 19 | const { value, type } = discount; 20 | 21 | const discountValue = (type === 'percentage' 22 | ? getDiscountByPercentage(subTotal, value) 23 | : getDiscountByMoney(value)); 24 | 25 | const discountEqualsSubtotal = ((subTotal - discountValue) === 0); 26 | 27 | if (discountEqualsSubtotal) { 28 | return 0; 29 | } 30 | 31 | const total = (subTotal - discountValue); 32 | 33 | return (total || subTotal); 34 | }; 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Stenio Wagner 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/components/common/product-sale-component/components/select-product/components/filter/components/select-filter/FilterOptionItem.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React from 'react'; 4 | 5 | import MenuItem from '@material-ui/core/MenuItem'; 6 | import MenuList from '@material-ui/core/MenuList'; 7 | import Paper from '@material-ui/core/Paper'; 8 | 9 | import styled from 'styled-components'; 10 | 11 | const Wrapper = styled(({ ...props }) => ( 12 | 15 | ))` 16 | margin-top: 4px; 17 | `; 18 | 19 | type Props = { 20 | onSelectOption: Function, 21 | optionSelected: Object, 22 | options: Array, 23 | }; 24 | 25 | const FilterOptionItem = ({ onSelectOption, optionSelected, options }: Props): Object => ( 26 | 27 | 28 | {options.map(option => ( 29 | onSelectOption(option)} 32 | key={option.field} 33 | > 34 | {option.title} 35 | 36 | ))} 37 | 38 | 39 | ); 40 | 41 | export default FilterOptionItem; 42 | -------------------------------------------------------------------------------- /src/screens/cashier/components/bottom-valeus/BottomItem.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React from 'react'; 4 | 5 | import styled from 'styled-components'; 6 | 7 | const ItemWrapper = styled.div` 8 | width: 100%; 9 | display: flex; 10 | align-items: center; 11 | margin: 16px 0; 12 | `; 13 | 14 | const IconWrapper = styled.div` 15 | width: 44px; 16 | height: 44px; 17 | display: flex; 18 | justify-content: center; 19 | align-items: center; 20 | margin-right: 16px; 21 | border-radius: 22px; 22 | background-color: ${({ theme, color }) => theme.colors[color]}; 23 | `; 24 | 25 | const Title = styled.span` 26 | font-size: 24px; 27 | `; 28 | 29 | type ItemConfig = { 30 | Icon: Function, 31 | message: string, 32 | value: string, 33 | color: string, 34 | }; 35 | 36 | const BottomItem = ({ 37 | message, 38 | value, 39 | Icon, 40 | color, 41 | }: ItemConfig): Object => ( 42 | 43 | 46 | 47 | 48 | 49 | {`${message} ${value}`} 50 | 51 | 52 | ); 53 | 54 | export default BottomItem; 55 | -------------------------------------------------------------------------------- /public/back-end/controllers/stock/index.js: -------------------------------------------------------------------------------- 1 | const { Product, Stock } = require('../../models'); 2 | 3 | exports.getAll = async () => { 4 | try { 5 | return await Stock.findAll({ 6 | order: [['updatedAt', 'DESC']], 7 | include: [Product], 8 | raw: true, 9 | }); 10 | } catch (err) { 11 | return []; 12 | } 13 | }; 14 | 15 | exports.insert = async (productInfo) => { 16 | try { 17 | return await Stock.create(productInfo); 18 | } catch (err) { 19 | return err; 20 | } 21 | }; 22 | 23 | exports.editInBatch = async (stockUpdated) => { 24 | try { 25 | stockUpdated.forEach(async (stockItem) => { 26 | await Stock.update({ 27 | ...stockItem, 28 | }, { 29 | where: { 30 | id: stockItem.id, 31 | }, 32 | }); 33 | }); 34 | 35 | return null; 36 | } catch (err) { 37 | return err; 38 | } 39 | }; 40 | 41 | exports.edit = async (productInfoUpdated) => { 42 | try { 43 | return await Stock.update({ 44 | ...productInfoUpdated, 45 | }, { 46 | where: { 47 | id: productInfoUpdated.id, 48 | }, 49 | }); 50 | } catch (err) { 51 | return err; 52 | } 53 | }; 54 | -------------------------------------------------------------------------------- /src/components/common/product-sale-component/components/footer-values/components/RemoveButton.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React from 'react'; 4 | 5 | import Button from '@material-ui/core/Button'; 6 | import styled from 'styled-components'; 7 | 8 | const ButtonWrapper = styled.div` 9 | width: 100%; 10 | display: flex; 11 | justify-content: flex-end; 12 | padding-top: 24px; 13 | `; 14 | 15 | type Props = { 16 | item: string | Object, 17 | onRemove: Function, 18 | entity: string, 19 | mode: string, 20 | }; 21 | 22 | const RemoveButton = ({ 23 | onRemove, 24 | entity, 25 | item, 26 | mode, 27 | }: Props): Object => { 28 | const hasItemSelected = (typeof item === 'object' ? !!item.type : !!item); 29 | const isOnEditionMode = (mode === 'edit'); 30 | 31 | const shouldShowRemoveButton = (isOnEditionMode || hasItemSelected); 32 | 33 | return shouldShowRemoveButton && ( 34 | 35 | 43 | 44 | ); 45 | }; 46 | 47 | export default RemoveButton; 48 | -------------------------------------------------------------------------------- /src/utils/filter/index.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import handleFunctionalFilter from './functionalFilter'; 4 | import handleNumericFilter from './numericFilter'; 5 | import handleTextFilter from './textFilter'; 6 | import handleDateFilter from './dateFilter'; 7 | 8 | export const FILTER_TYPES = { 9 | FUNCTIONAL: 'FUNCTIONAL', 10 | NUMERIC: 'NUMERIC', 11 | DATE: { 12 | ID: 'DATE', 13 | WHEN: { 14 | BEFORE: 'BEFORE', 15 | SAME: 'SAME', 16 | AFTER: 'AFTER', 17 | }, 18 | }, 19 | TEXT: 'TEXT', 20 | }; 21 | 22 | export const filterList = (filterConfig: Object): Array => { 23 | const { type } = filterConfig; 24 | 25 | let datasetFiltered = []; 26 | 27 | if (type === FILTER_TYPES.FUNCTIONAL) { 28 | datasetFiltered = handleFunctionalFilter(filterConfig); 29 | } 30 | 31 | if (type === FILTER_TYPES.NUMERIC) { 32 | datasetFiltered = handleNumericFilter(filterConfig); 33 | } 34 | 35 | if (type === FILTER_TYPES.TEXT) { 36 | datasetFiltered = handleTextFilter(filterConfig); 37 | } 38 | 39 | if (type === FILTER_TYPES.DATE.ID) { 40 | datasetFiltered = handleDateFilter(filterConfig, FILTER_TYPES.DATE.WHEN); 41 | } 42 | 43 | return datasetFiltered; 44 | }; 45 | -------------------------------------------------------------------------------- /src/components/common/sale-confirmation/components/CashierClosedAlert.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React, { Fragment } from 'react'; 4 | 5 | import DialogContentText from '@material-ui/core/DialogContentText'; 6 | import DialogActions from '@material-ui/core/DialogActions'; 7 | import DialogContent from '@material-ui/core/DialogContent'; 8 | import DialogTitle from '@material-ui/core/DialogTitle'; 9 | import Button from '@material-ui/core/Button'; 10 | 11 | type Props = { 12 | onCloseDialog: Function, 13 | }; 14 | 15 | const CashierClosedAlert = ({ onCloseDialog }: Props): Object => ( 16 | 17 | 20 | Cashier Closed 21 | 22 | 23 | 24 | The Cashier is Closed. By this reason, it's not possible to move forward with the desired action. 25 | To perform this operation, open the Cashier. 26 | 27 | 28 | 29 | 35 | 36 | 37 | ); 38 | 39 | export default CashierClosedAlert; 40 | -------------------------------------------------------------------------------- /src/store/ducks/brand.js: -------------------------------------------------------------------------------- 1 | import Immutable from 'seamless-immutable'; 2 | 3 | export const Types = { 4 | GET_ALL_REQUEST: 'brand/GET_ALL_REQUEST', 5 | GET_ALL_SUCCESS: 'brand/GET_ALL_SUCCESS', 6 | GET_ALL_FAILURE: 'brand/GET_ALL_FAILURE', 7 | }; 8 | 9 | const INITIAL_STATE = Immutable({ 10 | error: null, 11 | data: [], 12 | }); 13 | 14 | export const Creators = { 15 | getAllBrands: () => ({ 16 | type: Types.GET_ALL_REQUEST, 17 | }), 18 | 19 | getAllBrandsSuccess: brands => ({ 20 | type: Types.GET_ALL_SUCCESS, 21 | payload: { brands }, 22 | }), 23 | 24 | getAllBrandsFailure: error => ({ 25 | type: Types.GET_ALL_FAILURE, 26 | payload: { error }, 27 | }), 28 | }; 29 | 30 | const brand = (state = INITIAL_STATE, { payload, type }) => { 31 | switch (type) { 32 | case Types.GET_ALL_REQUEST: 33 | return { 34 | ...state, 35 | }; 36 | 37 | case Types.GET_ALL_SUCCESS: 38 | return { 39 | data: payload.brands, 40 | error: null, 41 | }; 42 | 43 | case Types.GET_ALL_FAILURE: 44 | return { 45 | ...state, 46 | error: payload.error, 47 | }; 48 | 49 | default: 50 | return state; 51 | } 52 | }; 53 | 54 | export default brand; 55 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "extends": ["plugin:flowtype/recommended", "airbnb"], 4 | "env": { 5 | "browser": true, 6 | "jest": true 7 | }, 8 | "plugins": ["react", "jsx-a11y", "import", "flowtype"], 9 | "rules": { 10 | "no-underscore-dangle": 0, 11 | "arrow-body-style": 0, 12 | "import/no-extraneous-dependencies": 0, 13 | "max-len": 0, 14 | "react/jsx-filename-extension": [ 15 | "error", 16 | { 17 | "extensions": [".js", ".jsx"] 18 | } 19 | ], 20 | "global-require": "off", 21 | "import/prefer-default-export": "off", 22 | "implicit-arrow-linebreak": ["error", "beside"], 23 | "brace-style": [ 24 | "error", 25 | "1tbs", 26 | { 27 | "allowSingleLine": true 28 | } 29 | ], 30 | "no-unused-expressions": ["error", { "allowTaggedTemplates": true }], 31 | "no-unused-vars": [ 32 | "error", 33 | { 34 | "argsIgnorePattern": "^_" 35 | } 36 | ], 37 | "flowtype/use-flow-type": 1, 38 | "flowtype/define-flow-type": 1, 39 | "flowtype/no-types-missing-file-annotation": 0 40 | }, 41 | "settings": { 42 | "flowtype": { 43 | "onlyFilesWithFlowAnnotation": false 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /public/back-end/models/customer.js: -------------------------------------------------------------------------------- 1 | const CustomerModel = (sequelize, DataTypes) => { 2 | const { STRING } = DataTypes; 3 | 4 | const Model = sequelize.define('Customer', { 5 | name: { 6 | validate: { 7 | notEmpty: true, 8 | }, 9 | allowNull: false, 10 | type: STRING, 11 | }, 12 | 13 | birthday: { 14 | type: STRING, 15 | }, 16 | 17 | address: { 18 | type: STRING, 19 | }, 20 | 21 | neighborhood: { 22 | type: STRING, 23 | }, 24 | 25 | obs: { 26 | type: STRING, 27 | }, 28 | 29 | city: { 30 | type: STRING, 31 | }, 32 | 33 | state: { 34 | type: STRING, 35 | }, 36 | 37 | motherName: { 38 | type: STRING, 39 | }, 40 | 41 | fatherName: { 42 | type: STRING, 43 | }, 44 | 45 | landline: { 46 | type: STRING, 47 | }, 48 | 49 | cellPhone: { 50 | type: STRING, 51 | }, 52 | 53 | cpf: { 54 | type: STRING, 55 | unique: true, 56 | }, 57 | 58 | rg: { 59 | type: STRING, 60 | unique: true, 61 | }, 62 | 63 | email: { 64 | type: STRING, 65 | unique: true, 66 | }, 67 | }); 68 | 69 | return Model; 70 | }; 71 | 72 | module.exports = CustomerModel; 73 | -------------------------------------------------------------------------------- /public/back-end/database/migrations/20181214011402-create-providers.js: -------------------------------------------------------------------------------- 1 | const onMigrate = (queryInterface, DataTypes) => { 2 | const { INTEGER, STRING, DATE } = DataTypes; 3 | 4 | queryInterface.createTable('Providers', { 5 | id: { 6 | allowNull: false, 7 | autoIncrement: true, 8 | primaryKey: true, 9 | type: INTEGER, 10 | }, 11 | 12 | name: { 13 | validate: { 14 | notEmpty: true, 15 | }, 16 | allowNull: false, 17 | type: STRING, 18 | }, 19 | 20 | address: { 21 | type: STRING, 22 | }, 23 | 24 | neighborhood: { 25 | type: STRING, 26 | }, 27 | 28 | city: { 29 | type: STRING, 30 | }, 31 | 32 | state: { 33 | type: STRING, 34 | }, 35 | 36 | phone1: { 37 | type: STRING, 38 | }, 39 | 40 | phone2: { 41 | type: STRING, 42 | }, 43 | 44 | email: { 45 | type: STRING, 46 | }, 47 | 48 | createdAt: { 49 | allowNull: false, 50 | type: DATE, 51 | }, 52 | 53 | updatedAt: { 54 | allowNull: false, 55 | type: DATE, 56 | }, 57 | }); 58 | }; 59 | 60 | const onRollback = queryInterface => queryInterface.dropTable('Providers'); 61 | 62 | module.exports = { 63 | up: onMigrate, 64 | 65 | down: onRollback, 66 | }; 67 | -------------------------------------------------------------------------------- /public/back-end/models/budget.js: -------------------------------------------------------------------------------- 1 | const BudgetModel = (sequelize, DataTypes) => { 2 | const { STRING, FLOAT, JSON } = DataTypes; 3 | 4 | const Model = sequelize.define('Budget', { 5 | customer: { 6 | type: JSON, 7 | }, 8 | 9 | code: { 10 | validate: { 11 | notEmpty: true, 12 | }, 13 | allowNull: false, 14 | type: STRING, 15 | }, 16 | 17 | dateToShow: { 18 | type: STRING, 19 | }, 20 | 21 | discount: { 22 | type: JSON, 23 | }, 24 | 25 | products: { 26 | allowNull: false, 27 | type: JSON, 28 | }, 29 | 30 | subtotal: { 31 | validate: { 32 | notEmpty: true, 33 | }, 34 | allowNull: false, 35 | type: FLOAT, 36 | }, 37 | 38 | total: { 39 | validate: { 40 | notEmpty: true, 41 | }, 42 | allowNull: false, 43 | type: FLOAT, 44 | }, 45 | 46 | observation: { 47 | type: STRING, 48 | }, 49 | 50 | validity: { 51 | type: STRING, 52 | }, 53 | 54 | status: { 55 | type: STRING, 56 | }, 57 | 58 | salesman: { 59 | validate: { 60 | notEmpty: true, 61 | }, 62 | allowNull: false, 63 | type: STRING, 64 | }, 65 | }); 66 | 67 | return Model; 68 | }; 69 | 70 | module.exports = BudgetModel; 71 | -------------------------------------------------------------------------------- /src/components/header/components/navigation-menu/index.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React, { Component } from 'react'; 4 | 5 | import Tabs from '@material-ui/core/Tabs'; 6 | import Tab from '@material-ui/core/Tab'; 7 | 8 | import { Link } from 'react-router-dom'; 9 | 10 | import items from './navigation-items'; 11 | 12 | class NavigationMenu extends Component { 13 | state = { 14 | currentTabIndex: 0, 15 | }; 16 | 17 | handleChangeTab = (_: any, currentTabIndex: number): void => { 18 | this.setState({ currentTabIndex }); 19 | }; 20 | 21 | render() { 22 | const { currentTabIndex } = this.state; 23 | 24 | return ( 25 | 34 | {items.map((item) => { 35 | const { Icon, title, route } = item; 36 | 37 | return ( 38 | } 41 | label={title} 42 | key={route} 43 | to={route} 44 | /> 45 | ); 46 | })} 47 | 48 | ); 49 | } 50 | } 51 | 52 | export default NavigationMenu; 53 | -------------------------------------------------------------------------------- /src/components/header/components/navigation-menu/navigation-items.js: -------------------------------------------------------------------------------- 1 | import AccountCircle from '@material-ui/icons/AccountCircle'; 2 | import LocalShipping from '@material-ui/icons/LocalShipping'; 3 | import ShoppingCart from '@material-ui/icons/ShoppingCart'; 4 | import Stock from '@material-ui/icons/PlaylistAddCheck'; 5 | import Assignment from '@material-ui/icons/Assignment'; 6 | import LocalOffer from '@material-ui/icons/LocalOffer'; 7 | import Contacts from '@material-ui/icons/Contacts'; 8 | import People from '@material-ui/icons/People'; 9 | 10 | const items = [{ 11 | Icon: Contacts, 12 | title: 'CASHIER', 13 | route: '/dashboard/cashier', 14 | }, { 15 | Icon: ShoppingCart, 16 | title: 'SALES', 17 | route: '/dashboard/sale', 18 | }, { 19 | Icon: LocalOffer, 20 | title: 'PRODUCTS', 21 | route: '/dashboard/product', 22 | }, { 23 | Icon: Stock, 24 | title: 'STOCK', 25 | route: '/dashboard/stock', 26 | }, { 27 | Icon: Assignment, 28 | title: 'BUDGETS', 29 | route: '/dashboard/budget', 30 | }, { 31 | Icon: People, 32 | title: 'CUSTOMERS', 33 | route: '/dashboard/customer', 34 | }, { 35 | Icon: LocalShipping, 36 | title: 'PROVIDERS', 37 | route: '/dashboard/provider', 38 | }, { 39 | Icon: AccountCircle, 40 | title: 'USERS', 41 | route: '/dashboard/user', 42 | }]; 43 | 44 | export default items; 45 | -------------------------------------------------------------------------------- /src/components/common/product-sale-component/components/footer-values/components/ObservationItem.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React, { Component, Fragment } from 'react'; 4 | 5 | import Input from '../../../../CustomInput'; 6 | 7 | type Props = { 8 | onSetValues: Function, 9 | item: string, 10 | }; 11 | 12 | type State = { 13 | observation: string, 14 | }; 15 | 16 | class ObservationItem extends Component { 17 | state = { 18 | observation: '', 19 | }; 20 | 21 | componentDidMount() { 22 | const { item } = this.props; 23 | 24 | this.setState({ 25 | observation: item, 26 | }); 27 | } 28 | 29 | onTypeObservation = (observation: string): void => { 30 | const { onSetValues } = this.props; 31 | 32 | this.setState({ 33 | observation, 34 | }, () => onSetValues('observation', observation)); 35 | }; 36 | 37 | render() { 38 | const { observation } = this.state; 39 | 40 | return ( 41 | 42 | this.onTypeObservation(event.target.value)} 44 | value={observation} 45 | onBlur={() => {}} 46 | type="textarea" 47 | autoFocus 48 | error="" 49 | label="" 50 | /> 51 | 52 | ); 53 | } 54 | } 55 | 56 | export default ObservationItem; 57 | -------------------------------------------------------------------------------- /public/back-end/database/migrations/20181119222451-create-products.js: -------------------------------------------------------------------------------- 1 | const onMigrate = (queryInterface, DataTypes) => { 2 | const { 3 | INTEGER, 4 | STRING, 5 | FLOAT, 6 | DATE, 7 | } = DataTypes; 8 | 9 | queryInterface.createTable('Products', { 10 | id: { 11 | allowNull: false, 12 | autoIncrement: true, 13 | primaryKey: true, 14 | type: INTEGER, 15 | }, 16 | 17 | BrandId: { 18 | type: INTEGER, 19 | references: { model: 'Brands', key: 'id' }, 20 | onUpdate: 'CASCADE', 21 | onDelete: 'CASCADE', 22 | allowNull: false, 23 | }, 24 | 25 | barcode: { 26 | allowNull: false, 27 | type: STRING, 28 | }, 29 | 30 | description: { 31 | allowNull: false, 32 | type: STRING, 33 | }, 34 | 35 | costPrice: { 36 | allowNull: false, 37 | type: FLOAT, 38 | }, 39 | 40 | salePrice: { 41 | allowNull: false, 42 | type: FLOAT, 43 | }, 44 | 45 | createdAt: { 46 | allowNull: false, 47 | type: DATE, 48 | }, 49 | 50 | updatedAt: { 51 | allowNull: false, 52 | type: DATE, 53 | }, 54 | }); 55 | }; 56 | 57 | const onRollback = queryInterface => queryInterface.dropTable('Products'); 58 | 59 | module.exports = { 60 | up: onMigrate, 61 | 62 | down: onRollback, 63 | }; 64 | -------------------------------------------------------------------------------- /src/screens/budget/components/SelectLimitDate.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React from 'react'; 4 | 5 | import moment from 'moment'; 6 | import 'moment/locale/pt-br'; 7 | 8 | import styled from 'styled-components'; 9 | import Input from '../../../components/common/CustomInput'; 10 | 11 | const Wrapper = styled.div` 12 | width: 35%; 13 | `; 14 | 15 | type Props = { 16 | setFieldValue: Function, 17 | errors: Object, 18 | values: Object, 19 | mode: string, 20 | }; 21 | 22 | const onChangeDateValue = (setFieldValue: Function, validity: string): void => { 23 | if (validity.length === 10) { 24 | setFieldValue('validity', validity); 25 | } 26 | }; 27 | 28 | const SelectLimitDate = ({ 29 | setFieldValue, 30 | errors, 31 | values, 32 | mode, 33 | }: Props): Object => { 34 | moment.locale('pt-br'); 35 | 36 | const today = moment().format('YYYY-MM-DD'); 37 | 38 | return ( 39 | 40 | onChangeDateValue(setFieldValue, event.target.value)} 42 | inputProps={{ min: today }} 43 | disabled={mode === 'detail'} 44 | value={values.validity} 45 | error={errors.validity} 46 | label="Validity" 47 | placeholder="" 48 | id="validity" 49 | type="date" 50 | /> 51 | 52 | ); 53 | }; 54 | 55 | export default SelectLimitDate; 56 | -------------------------------------------------------------------------------- /src/screens/budget/config/filterConfig.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import { BUDGET_STATUS } from '../components/BudgetStatus'; 4 | import { FILTER_TYPES } from '../../../utils/filter'; 5 | 6 | const filterPending = (budgets: Array): Array => budgets.filter(budget => budget.status === BUDGET_STATUS.PENDING); 7 | 8 | const filterApprovead = (budgets: Array): Array => budgets.filter(budget => budget.status === BUDGET_STATUS.APPROVED); 9 | 10 | const filterOutOfTime = (budgets: Array): Array => budgets.filter(budget => budget.status === BUDGET_STATUS.OUT_OF_TIME); 11 | 12 | const filterConfig = [{ 13 | placeholder: 'Enter the Code of the Budget', 14 | type: FILTER_TYPES.TEXT, 15 | filterTitle: 'Code', 16 | dataField: 'code', 17 | }, { 18 | placeholder: 'These are All Pending Budgets', 19 | type: FILTER_TYPES.FUNCTIONAL, 20 | filterTitle: 'Pending', 21 | dataField: 'pending', 22 | behavior: filterPending, 23 | }, { 24 | placeholder: 'These are All Approved Budgets', 25 | type: FILTER_TYPES.FUNCTIONAL, 26 | filterTitle: 'Approved', 27 | dataField: 'approvead', 28 | behavior: filterApprovead, 29 | }, { 30 | placeholder: 'These are All Out of Time Budgets', 31 | type: FILTER_TYPES.FUNCTIONAL, 32 | filterTitle: 'Out of Time', 33 | dataField: 'outOfTime', 34 | behavior: filterOutOfTime, 35 | }]; 36 | 37 | export default filterConfig; 38 | -------------------------------------------------------------------------------- /src/components/header/components/toolbar/index.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React from 'react'; 4 | 5 | import Toolbar from '@material-ui/core/Toolbar'; 6 | 7 | import styled from 'styled-components'; 8 | 9 | import Backup from './components/backup-component'; 10 | import BellAlert from './components/BellAlerts'; 11 | import UserInfo from './components/UserInfo'; 12 | import AboutMe from './components/about-me'; 13 | 14 | const Container = styled.div` 15 | width: 100%; 16 | display: flex; 17 | justify-content: space-between; 18 | align-items: center; 19 | `; 20 | 21 | const Title = styled.p` 22 | margin-left: 16px; 23 | font-size: 20px; 24 | font-weight: 500; 25 | color: ${({ theme }) => theme.colors.headerText}; 26 | `; 27 | 28 | const LeftSideContainer = styled.div` 29 | display: flex; 30 | align-items: center; 31 | `; 32 | 33 | const RightSideContainer = styled.div` 34 | display: flex; 35 | align-items: center; 36 | `; 37 | 38 | const HeaderBar = (): Object => ( 39 | 40 | 41 | 42 | 43 | 44 | MY STORE 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | ); 55 | 56 | export default HeaderBar; 57 | -------------------------------------------------------------------------------- /public/back-end/models/cashier.js: -------------------------------------------------------------------------------- 1 | const CashierModel = (sequelize, DataTypes) => { 2 | const { STRING, FLOAT, JSON } = DataTypes; 3 | 4 | const Model = sequelize.define('Cashier', { 5 | dateToShow: { 6 | type: STRING, 7 | }, 8 | 9 | timestampText: { 10 | type: STRING, 11 | }, 12 | 13 | openBy: { 14 | type: STRING, 15 | allowNull: false, 16 | validate: { 17 | notEmpty: true, 18 | }, 19 | }, 20 | 21 | closedBy: { 22 | type: STRING, 23 | allowNull: false, 24 | validate: { 25 | notEmpty: true, 26 | }, 27 | }, 28 | 29 | operations: { 30 | allowNull: false, 31 | type: JSON, 32 | }, 33 | 34 | initialMoneyCashier: { 35 | validate: { 36 | notEmpty: true, 37 | }, 38 | allowNull: false, 39 | type: FLOAT, 40 | }, 41 | 42 | totalIncome: { 43 | validate: { 44 | notEmpty: true, 45 | }, 46 | allowNull: false, 47 | type: FLOAT, 48 | }, 49 | 50 | totalOutcome: { 51 | validate: { 52 | notEmpty: true, 53 | }, 54 | allowNull: false, 55 | type: FLOAT, 56 | }, 57 | 58 | totalProfit: { 59 | validate: { 60 | notEmpty: true, 61 | }, 62 | allowNull: false, 63 | type: FLOAT, 64 | }, 65 | }); 66 | 67 | return Model; 68 | }; 69 | 70 | module.exports = CashierModel; 71 | -------------------------------------------------------------------------------- /public/back-end/models/sale.js: -------------------------------------------------------------------------------- 1 | const SaleModel = (sequelize, DataTypes) => { 2 | const { STRING, FLOAT, JSON } = DataTypes; 3 | 4 | const Model = sequelize.define('Sale', { 5 | paymentInfo: { 6 | validate: { 7 | notEmpty: true, 8 | }, 9 | allowNull: false, 10 | type: JSON, 11 | }, 12 | 13 | code: { 14 | validate: { 15 | notEmpty: true, 16 | }, 17 | allowNull: false, 18 | type: STRING, 19 | }, 20 | 21 | customer: { 22 | type: JSON, 23 | }, 24 | 25 | dateToShow: { 26 | type: STRING, 27 | }, 28 | 29 | discount: { 30 | type: JSON, 31 | }, 32 | 33 | products: { 34 | allowNull: false, 35 | type: JSON, 36 | }, 37 | 38 | subtotal: { 39 | validate: { 40 | notEmpty: true, 41 | }, 42 | allowNull: false, 43 | type: FLOAT, 44 | }, 45 | 46 | total: { 47 | validate: { 48 | notEmpty: true, 49 | }, 50 | allowNull: false, 51 | type: FLOAT, 52 | }, 53 | 54 | observation: { 55 | type: STRING, 56 | }, 57 | 58 | salesman: { 59 | validate: { 60 | notEmpty: true, 61 | }, 62 | allowNull: false, 63 | type: STRING, 64 | }, 65 | 66 | inDebit: { 67 | validate: { 68 | notEmpty: true, 69 | }, 70 | allowNull: false, 71 | type: FLOAT, 72 | }, 73 | }); 74 | 75 | return Model; 76 | }; 77 | 78 | module.exports = SaleModel; 79 | -------------------------------------------------------------------------------- /src/store/sagas/customerDebits.js: -------------------------------------------------------------------------------- 1 | 2 | import { call, put } from 'redux-saga/effects'; 3 | import { Creators as CustomerDebitsCreators } from '../ducks/customerDebits'; 4 | 5 | import { READ_SALES, UPDATE_SALE } from './event-handlers-types/sale'; 6 | import { getNumberCustomersInDebit } from './alerts'; 7 | import execRequest from './execRequest'; 8 | import { SALE } from './entitiesTypes'; 9 | 10 | const EVENT_TAGS = { 11 | GET_ALL_DEBITS: 'CUSTOMER_GET_ALL_DEBITS', 12 | REMOVE_DEBIT: 'CUSTOMER_REMOVE_DEBIT', 13 | }; 14 | 15 | export function* getCustomerDebits(action) { 16 | try { 17 | const { id } = action.payload; 18 | 19 | const result = yield call(execRequest, SALE, READ_SALES, EVENT_TAGS.GET_ALL_DEBITS); 20 | const customerSalesWithDebits = result.filter(sale => (sale.customer.id === id && sale.inDebit > 0)); 21 | 22 | yield put(CustomerDebitsCreators.getDebitsSuccess(customerSalesWithDebits)); 23 | } catch (err) { 24 | yield put(CustomerDebitsCreators.getDebitsFailure(err.message)); 25 | } 26 | } 27 | 28 | export function* removeDebit(action) { 29 | try { 30 | const { sale } = action.payload; 31 | 32 | const saleWithoutDebit = { 33 | ...sale, 34 | inDebit: 0, 35 | }; 36 | 37 | yield call(execRequest, SALE, UPDATE_SALE, EVENT_TAGS.REMOVE_DEBIT, saleWithoutDebit); 38 | yield put(CustomerDebitsCreators.removeDebitSuccess(sale.id)); 39 | yield call(getNumberCustomersInDebit); 40 | } catch (err) { 41 | yield put(CustomerDebitsCreators.removeDebitFailure(err.message)); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /public/back-end/events-handlers/index.js: -------------------------------------------------------------------------------- 1 | const ENTITIES = require('../entitiesTypes'); 2 | 3 | const handlerBackupEvents = require('./backup'); 4 | const handleCustomerEvents = require('./customer'); 5 | const handleProviderEvents = require('./provider'); 6 | const handleCashierEvents = require('./cashier'); 7 | const handleProductEvents = require('./product'); 8 | const handleBudgetEvents = require('./budget'); 9 | const handlerStockEvents = require('./stock'); 10 | const handleBrandEvents = require('./brand'); 11 | const handleUserEvents = require('./user'); 12 | const handleSaleEvents = require('./sale'); 13 | 14 | const eventHandler = (entitie, operation, args) => { 15 | switch (entitie) { 16 | case ENTITIES.CUSTOMER: return handleCustomerEvents(operation, args); 17 | 18 | case ENTITIES.PROVIDER: return handleProviderEvents(operation, args); 19 | 20 | case ENTITIES.CASHIER: return handleCashierEvents(operation, args); 21 | 22 | case ENTITIES.PRODUCT: return handleProductEvents(operation, args); 23 | 24 | case ENTITIES.BUDGET: return handleBudgetEvents(operation, args); 25 | 26 | case ENTITIES.BACKUP: return handlerBackupEvents(operation, args); 27 | 28 | case ENTITIES.STOCK: return handlerStockEvents(operation, args); 29 | 30 | case ENTITIES.BRAND: return handleBrandEvents(operation, args); 31 | 32 | case ENTITIES.USER: return handleUserEvents(operation, args); 33 | 34 | case ENTITIES.SALE: return handleSaleEvents(operation, args); 35 | 36 | default: return {}; 37 | } 38 | }; 39 | 40 | module.exports = eventHandler; 41 | -------------------------------------------------------------------------------- /src/screens/stock/config/filterConfig.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import { FILTER_TYPES } from '../../../utils/filter'; 4 | 5 | const filterByMinStockQuantityAboveLimit = (products: Array): Array => products.filter( 6 | product => product.stockQuantity > product.minStockQuantity, 7 | ); 8 | 9 | const filterByMinStockQuantityUnderLimit = (products: Array): Array => products.filter( 10 | product => product.stockQuantity < product.minStockQuantity, 11 | ); 12 | 13 | const filterByMinStockQuantityOnTheEdge = (products: Array): Array => products.filter( 14 | product => product.stockQuantity === product.minStockQuantity, 15 | ); 16 | 17 | const filterConfig = [{ 18 | placeholder: 'Enter the Description of the Product you are looking for', 19 | type: FILTER_TYPES.TEXT, 20 | dataField: 'description', 21 | filterTitle: 'Product', 22 | }, { 23 | placeholder: 'These are the Products that are above the min quantity', 24 | type: FILTER_TYPES.FUNCTIONAL, 25 | dataField: 'above', 26 | filterTitle: 'Above', 27 | behavior: filterByMinStockQuantityAboveLimit, 28 | }, { 29 | placeholder: 'These are the Products that are on the limit of quantity', 30 | type: FILTER_TYPES.FUNCTIONAL, 31 | dataField: 'limit', 32 | filterTitle: 'On the Limit', 33 | behavior: filterByMinStockQuantityOnTheEdge, 34 | }, { 35 | placeholder: 'These are the Products that are under the min quantity', 36 | type: FILTER_TYPES.FUNCTIONAL, 37 | dataField: 'under', 38 | filterTitle: 'Under', 39 | behavior: filterByMinStockQuantityUnderLimit, 40 | }]; 41 | 42 | export default filterConfig; 43 | -------------------------------------------------------------------------------- /src/screens/cashier/components/current-cashier/components/cashier-closed/components/CashierClosedAlert.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React from 'react'; 4 | 5 | import styled from 'styled-components'; 6 | 7 | import ActionButton from '../../../../../../../components/common/ActionButton'; 8 | 9 | const Wrapper = styled.div` 10 | width: 100%; 11 | height: 100%; 12 | display: flex; 13 | flex: 1; 14 | justify-content: center; 15 | align-items: center; 16 | `; 17 | 18 | const BigText = styled.p` 19 | margin-bottom: 16px; 20 | font-size: 32px; 21 | font-weight: 800; 22 | `; 23 | 24 | const SmallText = styled.p` 25 | margin-bottom: 32px; 26 | text-align: center; 27 | font-size: 24px; 28 | font-weight: 500; 29 | color: ${({ theme }) => theme.colors.mediumGrayDisabled}; 30 | `; 31 | 32 | const ContentWrapper = styled.div` 33 | width: 50%; 34 | display: flex; 35 | flex-direction: column; 36 | justify-content: center; 37 | align-items: center; 38 | `; 39 | 40 | type Props = { 41 | onToggleInitialMoneyDialog: Function, 42 | }; 43 | 44 | const CashierClosedAlert = ({ onToggleInitialMoneyDialog }: Props): Object => ( 45 | 46 | 47 | 48 | Cashier Closed 49 | 50 | 51 | The Cashier is Closed. To Open the Cashier and start the operations, click on the button below. 52 | 53 | 57 | 58 | 59 | ); 60 | 61 | export default CashierClosedAlert; 62 | -------------------------------------------------------------------------------- /src/store/sagas/print.js: -------------------------------------------------------------------------------- 1 | import { select, put } from 'redux-saga/effects'; 2 | 3 | import moment from 'moment'; 4 | import 'moment/locale/pt-br'; 5 | 6 | import { OPEN_PRINT_WINDOW, CLOSE_PRINT_WINDOW } from './entitiesTypes'; 7 | import { Creators as PrintCreators } from '../ducks/print'; 8 | 9 | const { ipcRenderer } = window.require('electron'); 10 | 11 | function* print(fileName) { 12 | ipcRenderer.send(OPEN_PRINT_WINDOW, fileName); 13 | 14 | yield new Promise((resolve) => { 15 | ipcRenderer.on(CLOSE_PRINT_WINDOW, () => { 16 | ipcRenderer.removeAllListeners(CLOSE_PRINT_WINDOW); 17 | ipcRenderer.removeAllListeners(OPEN_PRINT_WINDOW); 18 | resolve(); 19 | }); 20 | }); 21 | } 22 | 23 | const getFilaName = (data, currentPage) => { 24 | const customerName = ((data.customer.name && data.customer.name.toLowerCase()) || ''); 25 | const type = (data.status ? 'orçamento' : 'venda'); 26 | const date = moment().format('DD-MM-YYYY'); 27 | const page = (currentPage > 0 ? `-(${currentPage})` : ''); 28 | 29 | const fileName = `${type}-${customerName}-${date}${page}`; 30 | 31 | return fileName; 32 | }; 33 | 34 | export function* handlePrint() { 35 | let shouldPrint = true; 36 | 37 | while (shouldPrint) { 38 | const { numberOfPages, currentPage, data } = yield select(state => state.print); 39 | const fileName = getFilaName(data, currentPage); 40 | 41 | yield print(fileName); 42 | 43 | if ((numberOfPages - 1) === currentPage) { 44 | shouldPrint = false; 45 | } else { 46 | yield put(PrintCreators.rePrint()); 47 | } 48 | } 49 | 50 | yield put(PrintCreators.finishPrint()); 51 | } 52 | -------------------------------------------------------------------------------- /public/back-end/database/migrations/20181214005947-create-customers.js: -------------------------------------------------------------------------------- 1 | const onMigrate = (queryInterface, DataTypes) => { 2 | const { INTEGER, STRING, DATE } = DataTypes; 3 | 4 | queryInterface.createTable('Customers', { 5 | id: { 6 | allowNull: false, 7 | autoIncrement: true, 8 | primaryKey: true, 9 | type: INTEGER, 10 | }, 11 | 12 | name: { 13 | validate: { 14 | notEmpty: true, 15 | }, 16 | allowNull: false, 17 | type: STRING, 18 | }, 19 | 20 | birthday: { 21 | type: STRING, 22 | }, 23 | 24 | address: { 25 | type: STRING, 26 | }, 27 | 28 | neighborhood: { 29 | type: STRING, 30 | }, 31 | 32 | obs: { 33 | type: STRING, 34 | }, 35 | 36 | city: { 37 | type: STRING, 38 | }, 39 | 40 | state: { 41 | type: STRING, 42 | }, 43 | 44 | motherName: { 45 | type: STRING, 46 | }, 47 | 48 | fatherName: { 49 | type: STRING, 50 | }, 51 | 52 | landline: { 53 | type: STRING, 54 | }, 55 | 56 | cellPhone: { 57 | type: STRING, 58 | }, 59 | 60 | cpf: { 61 | type: STRING, 62 | }, 63 | 64 | rg: { 65 | type: STRING, 66 | }, 67 | 68 | email: { 69 | type: STRING, 70 | }, 71 | 72 | createdAt: { 73 | allowNull: false, 74 | type: DATE, 75 | }, 76 | 77 | updatedAt: { 78 | allowNull: false, 79 | type: DATE, 80 | }, 81 | }); 82 | }; 83 | 84 | const onRollback = queryInterface => queryInterface.dropTable('Customers'); 85 | 86 | module.exports = { 87 | up: onMigrate, 88 | 89 | down: onRollback, 90 | }; 91 | -------------------------------------------------------------------------------- /src/screens/budget/components/BudgetExtraComponent.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React from 'react'; 4 | 5 | import styled from 'styled-components'; 6 | 7 | import BudgetStatus, { BUDGET_STATUS } from './BudgetStatus'; 8 | import SelectLimitDate from './SelectLimitDate'; 9 | import PayBudgetButton from './PayBudgetButton'; 10 | 11 | const Wrapper = styled.div` 12 | margin: 24px 0; 13 | `; 14 | 15 | const LineWrapper = styled.div` 16 | width: 100%; 17 | display: flex; 18 | justify-content: space-between; 19 | align-items: center; 20 | margin-bottom: 16px; 21 | `; 22 | 23 | const StatusAndPaymentWrapper = styled.div` 24 | display: flex; 25 | flex-direction: row; 26 | align-items: flex-end; 27 | `; 28 | 29 | type Props = { 30 | onToggleSaleConfirmationDialog: Function, 31 | setFieldValue: Function, 32 | errors: Object, 33 | values: Object, 34 | mode: string, 35 | }; 36 | 37 | const BudgetExtraComponent = (props: Props): Object => { 38 | const { values, mode } = props; 39 | const { status } = values; 40 | 41 | const shouldRenderPayBudgetButton = (mode === 'detail' && status !== BUDGET_STATUS.APPROVED); 42 | 43 | return ( 44 | 45 | 46 | 49 | 50 | {shouldRenderPayBudgetButton && ( 51 | 54 | )} 55 | 58 | 59 | 60 | 61 | ); 62 | }; 63 | 64 | export default BudgetExtraComponent; 65 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 14 | 23 | 24 | 25 | 26 | 29 |
30 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /src/screens/cashier/components/current-cashier/components/cashier-open/components/top-buttons-values/button-config.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React from 'react'; 4 | 5 | import styled from 'styled-components'; 6 | 7 | import Remove from '@material-ui/icons/RemoveCircleOutline'; 8 | import Clear from '@material-ui/icons/HighlightOffOutlined'; 9 | import Add from '@material-ui/icons/AddCircleOutline'; 10 | 11 | const AddIcon = styled(({ ...props }) => ( 12 | 15 | ))` 16 | color: ${({ theme }) => theme.colors.white} 17 | `; 18 | 19 | const TakeMoneyOutIcon = styled(({ ...props }) => ( 20 | 23 | ))` 24 | color: ${({ theme }) => theme.colors.white} 25 | `; 26 | 27 | const CloseCashierIcon = styled(({ ...props }) => ( 28 | 31 | ))` 32 | color: ${({ theme }) => theme.colors.white} 33 | `; 34 | 35 | export const BUTTON_TYPES = { 36 | ADD_MONEY: 'ADD_MONEY', 37 | TAKE_MONEY_OUT: 'TAKE_MONEY_OUT', 38 | CLOSE_CASHIER: 'CLOSE_CASHIER', 39 | }; 40 | 41 | export const getButtonConfig = (type: string, action: Function): Object => { 42 | const CONFIGS = { 43 | [BUTTON_TYPES.ADD_MONEY]: { 44 | color: 'success', 45 | label: 'INSERT MONEY', 46 | Icon: AddIcon, 47 | action, 48 | }, 49 | 50 | [BUTTON_TYPES.TAKE_MONEY_OUT]: { 51 | color: 'danger', 52 | label: 'WITHDRAW MONEY', 53 | Icon: TakeMoneyOutIcon, 54 | action, 55 | }, 56 | 57 | [BUTTON_TYPES.CLOSE_CASHIER]: { 58 | color: 'warning', 59 | label: 'CLOSE CASHIER', 60 | Icon: CloseCashierIcon, 61 | action, 62 | }, 63 | }; 64 | 65 | return CONFIGS[type]; 66 | }; 67 | -------------------------------------------------------------------------------- /src/screens/cashier/components/past-cashier/components/DateFilter.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React from 'react'; 4 | 5 | import Calendar from '@material-ui/icons/CalendarToday'; 6 | import styled from 'styled-components'; 7 | 8 | import Input from '../../../../../components/common/CustomInput'; 9 | 10 | const Wrapper = styled.div` 11 | display: flex; 12 | justify-content: flex-end; 13 | align-items: flex-end; 14 | `; 15 | 16 | const InputWrapper = styled.div` 17 | width: 70%; 18 | `; 19 | 20 | const IconWrapper = styled.div` 21 | width: 44px; 22 | height: 44px; 23 | display: flex; 24 | justify-content: center; 25 | align-items: center; 26 | margin: 0 16px 10px 0; 27 | border-radius: 22px; 28 | background-color: ${({ theme }) => theme.colors.affirmative}; 29 | `; 30 | 31 | const CalendarIcon = styled(Calendar)` 32 | color: ${({ theme }) => theme.colors.white}; 33 | `; 34 | 35 | type Props = { 36 | onChooseDateToFilter: Function, 37 | dateFilterValue: string, 38 | }; 39 | 40 | const renderInput = (onChooseDateToFilter: Function, dateFilterValue: string): Object => ( 41 | 42 | {}} 46 | error="" 47 | value={dateFilterValue} 48 | id="date-filter" 49 | type="date" 50 | label="Filter by a Date" 51 | /> 52 | 53 | ); 54 | 55 | const DateFilter = ({ onChooseDateToFilter, dateFilterValue }: Props): Object => ( 56 | 57 | 58 | 59 | 60 | {renderInput(onChooseDateToFilter, dateFilterValue)} 61 | 62 | ); 63 | 64 | export default DateFilter; 65 | -------------------------------------------------------------------------------- /src/utils/filter/dateFilter.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import moment from 'moment'; 4 | 5 | type Config = { 6 | dataset: Array, 7 | filterSelected: string, 8 | value: string, 9 | } 10 | 11 | const getBeforeDateItems = (dataset: Array, value: Object): Array => dataset.filter((item) => { 12 | const itemDateParsed = moment(item.createdAt.substring(0, 10), 'YYYY-MM-DD').toDate(); 13 | return moment(itemDateParsed).isBefore(value); 14 | }); 15 | 16 | const getSameDateItems = (dataset: Array, value: Object): Array => dataset.filter((item) => { 17 | const itemDateParsed = moment(item.createdAt.substring(0, 10), 'YYYY-MM-DD').toDate(); 18 | return moment(itemDateParsed).isSame(value); 19 | }); 20 | 21 | const getAfterDateItems = (dataset: Array, value: Object): Array => dataset.filter((item) => { 22 | const itemDateParsed = moment(item.createdAt.substring(0, 10), 'YYYY-MM-DD').toDate(); 23 | return moment(itemDateParsed).isAfter(value); 24 | }); 25 | 26 | const filterWithDate = (config: Config, DATE_TYPES: Object): Array => { 27 | const { filterSelected, dataset, value } = config; 28 | 29 | const dateParsed = moment(value, 'L').toDate(); 30 | 31 | let datasetFilteredByDate = []; 32 | 33 | if (filterSelected === DATE_TYPES.BEFORE) { 34 | datasetFilteredByDate = getBeforeDateItems(dataset, dateParsed); 35 | } 36 | 37 | if (filterSelected === DATE_TYPES.SAME) { 38 | datasetFilteredByDate = getSameDateItems(dataset, dateParsed); 39 | } 40 | 41 | if (filterSelected === DATE_TYPES.AFTER) { 42 | datasetFilteredByDate = getAfterDateItems(dataset, dateParsed); 43 | } 44 | 45 | return datasetFilteredByDate; 46 | }; 47 | 48 | export default filterWithDate; 49 | -------------------------------------------------------------------------------- /public/back-end/database/migrations/20181218211159-create-budgets.js: -------------------------------------------------------------------------------- 1 | const onMigrate = (queryInterface, DataTypes) => { 2 | const { 3 | INTEGER, 4 | STRING, 5 | FLOAT, 6 | DATE, 7 | JSON, 8 | } = DataTypes; 9 | 10 | queryInterface.createTable('Budgets', { 11 | id: { 12 | allowNull: false, 13 | autoIncrement: true, 14 | primaryKey: true, 15 | type: INTEGER, 16 | }, 17 | 18 | code: { 19 | validate: { 20 | notEmpty: true, 21 | }, 22 | allowNull: false, 23 | type: STRING, 24 | }, 25 | 26 | customer: { 27 | type: JSON, 28 | }, 29 | 30 | discount: { 31 | type: JSON, 32 | }, 33 | 34 | products: { 35 | allowNull: false, 36 | type: JSON, 37 | }, 38 | 39 | subtotal: { 40 | validate: { 41 | notEmpty: true, 42 | }, 43 | allowNull: false, 44 | type: FLOAT, 45 | }, 46 | 47 | total: { 48 | validate: { 49 | notEmpty: true, 50 | }, 51 | allowNull: false, 52 | type: FLOAT, 53 | }, 54 | 55 | dateToShow: { 56 | type: STRING, 57 | }, 58 | 59 | observation: { 60 | type: STRING, 61 | }, 62 | 63 | validity: { 64 | type: STRING, 65 | }, 66 | 67 | status: { 68 | type: STRING, 69 | }, 70 | 71 | salesman: { 72 | validate: { 73 | notEmpty: true, 74 | }, 75 | allowNull: false, 76 | type: STRING, 77 | }, 78 | 79 | createdAt: { 80 | allowNull: false, 81 | type: DATE, 82 | }, 83 | 84 | updatedAt: { 85 | allowNull: false, 86 | type: DATE, 87 | }, 88 | }); 89 | }; 90 | 91 | const onRollback = queryInterface => queryInterface.dropTable('Budgets'); 92 | 93 | module.exports = { 94 | up: onMigrate, 95 | 96 | down: onRollback, 97 | }; 98 | -------------------------------------------------------------------------------- /src/screens/cashier/components/current-cashier/components/cashier-open/components/top-buttons-values/CashierButton.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React from 'react'; 4 | 5 | import ButtonBase from '@material-ui/core/ButtonBase'; 6 | import Money from '@material-ui/icons/Money'; 7 | import Badge from '@material-ui/core/Badge'; 8 | 9 | import styled from 'styled-components'; 10 | 11 | const ItemWrapper = styled.div` 12 | margin-right: 64px; 13 | `; 14 | 15 | const ButtonContainer = styled(ButtonBase)``; 16 | 17 | const ButtonWrapper = styled.div` 18 | display: flex; 19 | flex: 1; 20 | justify-content: space-between; 21 | align-items: center; 22 | padding: 14px 20px; 23 | border-radius: 6px; 24 | background-color: ${({ theme, color }) => theme.colors[color]}}; 25 | `; 26 | 27 | const ButtonTitle = styled.p` 28 | margin-right: 18px; 29 | font-size: 16px; 30 | font-weight: 700; 31 | color: ${({ theme }) => theme.colors.white}; 32 | `; 33 | 34 | const MoneyIcon = styled(({ ...props }) => ( 35 | 38 | ))` 39 | color: ${({ theme }) => theme.colors.white} 40 | `; 41 | 42 | type ButtonProps = { 43 | action: Function, 44 | Icon: Function, 45 | color: string, 46 | label: string, 47 | }; 48 | 49 | const CashierButton = ({ 50 | action, 51 | color, 52 | label, 53 | Icon, 54 | }: ButtonProps): Object => ( 55 | 56 | 59 | 62 | 63 | {label} 64 | 65 | } 67 | color="primary" 68 | > 69 | 70 | 71 | 72 | 73 | 74 | ); 75 | 76 | export default CashierButton; 77 | -------------------------------------------------------------------------------- /src/screens/cashier/components/current-cashier/components/cashier-open/components/top-buttons-values/dialog-config.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | export const CASHIER_OPERATIONS = { 4 | CONSOLIDATE_BUDGET_PAYMENT: 'Budget Payment', 5 | TAKE_AWAY_MONEY: 'Withdraw', 6 | ADD_MONEY: 'Insert', 7 | SALE: 'Sale', 8 | }; 9 | 10 | export const getDialogConfig = (type: string, action: Function, isDisabled = false): Object => { 11 | const CONFIGS = { 12 | [CASHIER_OPERATIONS.ADD_MONEY]: { 13 | type: CASHIER_OPERATIONS.ADD_MONEY, 14 | title: { 15 | create: 'Insert Money', 16 | edit: 'Edit Money Inserted', 17 | detail: 'Cashier Entry', 18 | }, 19 | valueTitle: { 20 | create: 'Report the quantity that will be insert', 21 | edit: 'Edit Quantity inserted', 22 | detail: 'Quantity inserted', 23 | }, 24 | reasonTitle: { 25 | create: 'Report the reason of why this quantity is been inserted', 26 | edit: 'Edit the reason of why the quantity was inserted', 27 | detail: 'Reason', 28 | }, 29 | isDisabled, 30 | action, 31 | }, 32 | 33 | [CASHIER_OPERATIONS.TAKE_AWAY_MONEY]: { 34 | type: CASHIER_OPERATIONS.TAKE_AWAY_MONEY, 35 | title: { 36 | create: 'Withdraw Money', 37 | edit: 'Edit Withdrawn Money', 38 | detail: 'Withdrawn Money', 39 | }, 40 | valueTitle: { 41 | create: 'Report the quantity that will be withdrawn', 42 | edit: 'Edit the quantity withdrawn', 43 | detail: 'Withdrawn Money', 44 | }, 45 | reasonTitle: { 46 | create: 'Report the reason of why this quantity was withdrawn', 47 | edit: 'Edit the reason of why this quantity was withdrawn', 48 | detail: 'Reason', 49 | }, 50 | isDisabled, 51 | action, 52 | }, 53 | }; 54 | 55 | return CONFIGS[type]; 56 | }; 57 | -------------------------------------------------------------------------------- /src/store/sagas/user.js: -------------------------------------------------------------------------------- 1 | import { call, put } from 'redux-saga/effects'; 2 | 3 | import { Creators as UserCreators } from '../ducks/user'; 4 | 5 | import { 6 | CREATE_USER, 7 | READ_USERS, 8 | UPDATE_USER, 9 | DELETE_USER, 10 | } from './event-handlers-types/user'; 11 | 12 | import execRequest from './execRequest'; 13 | import { USER } from './entitiesTypes'; 14 | 15 | const EVENT_TAGS = { 16 | GET_ALL_USERS: 'USERS_GET_ALL', 17 | CREATE_USER: 'USER_CREATE', 18 | REMOVE_USER: 'USER_REMOVE', 19 | EDIT_USER: 'USER_EDIT', 20 | }; 21 | 22 | export function* createUser(action) { 23 | try { 24 | const { args } = action; 25 | 26 | const result = yield call(execRequest, USER, CREATE_USER, EVENT_TAGS.CREATE_USER, args); 27 | 28 | const newUser = { 29 | ...args, 30 | id: result, 31 | }; 32 | 33 | yield put(UserCreators.createUserSuccess(newUser)); 34 | } catch (err) { 35 | yield put(UserCreators.createUserFailure(err.message)); 36 | } 37 | } 38 | 39 | export function* getAllUsers() { 40 | try { 41 | const result = yield call(execRequest, USER, READ_USERS, EVENT_TAGS.GET_ALL_USERS); 42 | yield put(UserCreators.getAllUsersSuccess(result)); 43 | } catch (err) { 44 | yield put(UserCreators.getAllUsersFailure(err)); 45 | } 46 | } 47 | 48 | export function* editUser(action) { 49 | try { 50 | const { user } = action.payload; 51 | 52 | yield call(execRequest, USER, UPDATE_USER, EVENT_TAGS.EDIT_USER, user); 53 | yield put(UserCreators.editUserSuccess(user)); 54 | } catch (err) { 55 | yield put(UserCreators.editUserFailure(err)); 56 | } 57 | } 58 | 59 | export function* removeUser(action) { 60 | try { 61 | const { id } = action.payload; 62 | 63 | yield call(execRequest, USER, DELETE_USER, EVENT_TAGS.REMOVE_USER, id); 64 | yield put(UserCreators.removeUserSuccess(id)); 65 | } catch (err) { 66 | yield put(UserCreators.removeUserFailure()); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /public/back-end/database/migrations/20181222225729-create-cashiers.js: -------------------------------------------------------------------------------- 1 | const onMigrate = (queryInterface, DataTypes) => { 2 | const { 3 | INTEGER, 4 | STRING, 5 | FLOAT, 6 | DATE, 7 | JSON, 8 | } = DataTypes; 9 | 10 | queryInterface.createTable('Cashiers', { 11 | id: { 12 | allowNull: false, 13 | autoIncrement: true, 14 | primaryKey: true, 15 | type: INTEGER, 16 | }, 17 | 18 | openBy: { 19 | validate: { 20 | notEmpty: true, 21 | }, 22 | allowNull: false, 23 | type: STRING, 24 | }, 25 | 26 | closedBy: { 27 | validate: { 28 | notEmpty: true, 29 | }, 30 | allowNull: false, 31 | type: STRING, 32 | }, 33 | 34 | dateToShow: { 35 | type: STRING, 36 | }, 37 | 38 | timestampText: { 39 | type: STRING, 40 | }, 41 | 42 | operations: { 43 | allowNull: false, 44 | type: JSON, 45 | }, 46 | 47 | initialMoneyCashier: { 48 | validate: { 49 | notEmpty: true, 50 | }, 51 | allowNull: false, 52 | type: FLOAT, 53 | }, 54 | 55 | totalIncome: { 56 | validate: { 57 | notEmpty: true, 58 | }, 59 | allowNull: false, 60 | type: FLOAT, 61 | }, 62 | 63 | totalOutcome: { 64 | validate: { 65 | notEmpty: true, 66 | }, 67 | allowNull: false, 68 | type: FLOAT, 69 | }, 70 | 71 | totalProfit: { 72 | validate: { 73 | notEmpty: true, 74 | }, 75 | allowNull: false, 76 | type: FLOAT, 77 | }, 78 | 79 | createdAt: { 80 | allowNull: false, 81 | type: DATE, 82 | }, 83 | 84 | updatedAt: { 85 | allowNull: false, 86 | type: DATE, 87 | }, 88 | }); 89 | }; 90 | 91 | const onRollback = queryInterface => queryInterface.dropTable('Cashiers'); 92 | 93 | module.exports = { 94 | up: onMigrate, 95 | 96 | down: onRollback, 97 | }; 98 | -------------------------------------------------------------------------------- /src/components/header/components/toolbar/components/about-me/index.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React, { Component, Fragment } from 'react'; 4 | 5 | import IconButton from '@material-ui/core/IconButton'; 6 | import Dialog from '@material-ui/core/Dialog'; 7 | import Slide from '@material-ui/core/Slide'; 8 | import Menu from '@material-ui/icons/Menu'; 9 | 10 | import styled from 'styled-components'; 11 | 12 | import Card from './components/Card'; 13 | 14 | const MenuIcon = styled(Menu)` 15 | color: ${({ theme }) => theme.colors.headerText}; 16 | `; 17 | 18 | type State = { 19 | isCardDialogOpen: boolean, 20 | }; 21 | 22 | class AboutMe extends Component { 23 | state = { 24 | isCardDialogOpen: false, 25 | }; 26 | 27 | onToggleCardDialog = (): void => { 28 | const { isCardDialogOpen } = this.state; 29 | 30 | this.setState({ 31 | isCardDialogOpen: !isCardDialogOpen, 32 | }); 33 | }; 34 | 35 | renderSlide = (props: Object): Object => ( 36 | 40 | ); 41 | 42 | renderDialog = (): Object => { 43 | const { isCardDialogOpen } = this.state; 44 | 45 | return ( 46 | 54 | 57 | 58 | ); 59 | }; 60 | 61 | render() { 62 | return ( 63 | 64 | 69 | 70 | 71 | {this.renderDialog()} 72 | 73 | ); 74 | } 75 | } 76 | 77 | export default AboutMe; 78 | -------------------------------------------------------------------------------- /src/components/common/sale-confirmation/components/form-payment/items-config.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React from 'react'; 4 | 5 | import CreditCard from '@material-ui/icons/CreditCard'; 6 | import DebitCard from '@material-ui/icons/CardMembership'; 7 | import Check from '@material-ui/icons/CalendarViewDay'; 8 | import Money from '@material-ui/icons/Money'; 9 | 10 | import styled from 'styled-components'; 11 | 12 | const MoneyIcon = styled(({ ...props }) => ( 13 | 16 | ))` 17 | color: ${({ theme }) => theme.colors.white} 18 | `; 19 | 20 | const CreditCardIcon = styled(({ ...props }) => ( 21 | 24 | ))` 25 | color: ${({ theme }) => theme.colors.white} 26 | `; 27 | 28 | const CheckIcon = styled(({ ...props }) => ( 29 | 32 | ))` 33 | color: ${({ theme }) => theme.colors.white} 34 | `; 35 | 36 | const DebitCardIcon = styled(({ ...props }) => ( 37 | 40 | ))` 41 | color: ${({ theme }) => theme.colors.white} 42 | `; 43 | 44 | export const ITEMS_TYPES = { 45 | CREDIT_CARD: 'CREDIT_CARD', 46 | DEBIT_CARD: 'DEBIT_CARD', 47 | CHECK: 'CHECK', 48 | MONEY: 'MONEY', 49 | }; 50 | 51 | const getItemConfig = (onType: Function, value: string, id: string): Object => { 52 | const configs = { 53 | [ITEMS_TYPES.MONEY]: { 54 | title: 'Money', 55 | Icon: MoneyIcon, 56 | onType, 57 | value, 58 | id, 59 | }, 60 | 61 | [ITEMS_TYPES.CREDIT_CARD]: { 62 | title: 'Credit Card', 63 | Icon: CreditCardIcon, 64 | onType, 65 | value, 66 | id, 67 | }, 68 | 69 | [ITEMS_TYPES.DEBIT_CARD]: { 70 | title: 'Debit Card', 71 | Icon: DebitCardIcon, 72 | onType, 73 | value, 74 | id, 75 | }, 76 | 77 | [ITEMS_TYPES.CHECK]: { 78 | title: 'Check', 79 | Icon: CheckIcon, 80 | onType, 81 | value, 82 | id, 83 | }, 84 | }; 85 | 86 | return configs[id]; 87 | }; 88 | 89 | export default getItemConfig; 90 | -------------------------------------------------------------------------------- /src/components/common/product-sale-component/components/top-row/components/CustomerDebits.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React, { Component, Fragment } from 'react'; 4 | 5 | import ErrorOutline from '@material-ui/icons/ErrorOutline'; 6 | import styled from 'styled-components'; 7 | 8 | import Debits from '../../../../../../screens/customer/components/Debits'; 9 | import FullScreenDialog from '../../../../FullScreenDialog'; 10 | import ActionButton from '../../../../ActionButton'; 11 | import appStyles from '../../../../../../styles'; 12 | 13 | const ButtonWrapper = styled.div` 14 | width: 100%; 15 | margin: 24px 0 32px 0; 16 | `; 17 | 18 | class CustomerDebits extends Component { 19 | state = { 20 | isUserDebitsDiaogOpen: false, 21 | }; 22 | 23 | onToggleUserDebitsDialog = (): void => { 24 | const { isUserDebitsDiaogOpen } = this.state; 25 | 26 | this.setState({ 27 | isUserDebitsDiaogOpen: !isUserDebitsDiaogOpen, 28 | }); 29 | }; 30 | 31 | renderCustomerDebitsTable = (): Object => { 32 | const { isUserDebitsDiaogOpen } = this.state; 33 | const { customerId } = this.props; 34 | 35 | return ( 36 | 41 | 44 | 45 | ); 46 | }; 47 | 48 | renderCustomerDebitsButton = (): Object => ( 49 | 50 | 59 | 60 | ); 61 | 62 | render() { 63 | return ( 64 | 65 | {this.renderCustomerDebitsButton()} 66 | {this.renderCustomerDebitsTable()} 67 | 68 | ); 69 | } 70 | } 71 | 72 | export default CustomerDebits; 73 | -------------------------------------------------------------------------------- /public/back-end/database/migrations/20181208015057-create-sales.js: -------------------------------------------------------------------------------- 1 | const onMigrate = (queryInterface, DataTypes) => { 2 | const { 3 | INTEGER, 4 | STRING, 5 | FLOAT, 6 | DATE, 7 | JSON, 8 | } = DataTypes; 9 | 10 | queryInterface.createTable('Sales', { 11 | id: { 12 | allowNull: false, 13 | autoIncrement: true, 14 | primaryKey: true, 15 | type: INTEGER, 16 | }, 17 | 18 | paymentInfo: { 19 | validate: { 20 | notEmpty: true, 21 | }, 22 | allowNull: false, 23 | type: JSON, 24 | }, 25 | 26 | code: { 27 | validate: { 28 | notEmpty: true, 29 | }, 30 | allowNull: false, 31 | type: STRING, 32 | }, 33 | 34 | customer: { 35 | type: JSON, 36 | }, 37 | 38 | discount: { 39 | type: JSON, 40 | }, 41 | 42 | products: { 43 | allowNull: false, 44 | type: JSON, 45 | }, 46 | 47 | subtotal: { 48 | validate: { 49 | notEmpty: true, 50 | }, 51 | allowNull: false, 52 | type: FLOAT, 53 | }, 54 | 55 | total: { 56 | validate: { 57 | notEmpty: true, 58 | }, 59 | allowNull: false, 60 | type: FLOAT, 61 | }, 62 | 63 | dateToShow: { 64 | type: STRING, 65 | }, 66 | 67 | observation: { 68 | type: STRING, 69 | }, 70 | 71 | salesman: { 72 | validate: { 73 | notEmpty: true, 74 | }, 75 | allowNull: false, 76 | type: STRING, 77 | }, 78 | 79 | inDebit: { 80 | validate: { 81 | notEmpty: true, 82 | }, 83 | allowNull: false, 84 | type: FLOAT, 85 | }, 86 | 87 | createdAt: { 88 | allowNull: false, 89 | type: DATE, 90 | }, 91 | 92 | updatedAt: { 93 | allowNull: false, 94 | type: DATE, 95 | }, 96 | }); 97 | }; 98 | 99 | const onRollback = queryInterface => queryInterface.dropTable('Sales'); 100 | 101 | module.exports = { 102 | up: onMigrate, 103 | 104 | down: onRollback, 105 | }; 106 | -------------------------------------------------------------------------------- /src/screens/customer/components/TopContent.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React from 'react'; 4 | 5 | import AssignmentInd from '@material-ui/icons/AssignmentInd'; 6 | import ArrowBack from '@material-ui/icons/ArrowBack'; 7 | import Button from '@material-ui/core/Button'; 8 | 9 | import styled from 'styled-components'; 10 | 11 | const Wrapper = styled.div` 12 | display: flex; 13 | justify-content: space-between; 14 | align-items: center; 15 | margin-bottom: 24px; 16 | margin-top: 8px; 17 | `; 18 | 19 | const SalerText = styled.span` 20 | font-size: 18px; 21 | margin-left: 12px; 22 | `; 23 | 24 | const SalerInfoWrapper = styled.div` 25 | display: flex; 26 | align-items: center; 27 | `; 28 | 29 | const UserIconWrapper = styled.div` 30 | display: flex; 31 | width: 48px; 32 | height: 48px; 33 | justify-content: center; 34 | align-items: center; 35 | border-radius: 24px; 36 | background-color: ${({ theme }) => theme.colors.affirmative}; 37 | `; 38 | 39 | const ArrowBackIcon = styled(ArrowBack)` 40 | color: ${({ theme }) => theme.colors.white}; 41 | `; 42 | 43 | const UserIcon = styled(({ ...props }) => ( 44 | 47 | ))` 48 | color: ${({ theme }) => theme.colors.white}; 49 | `; 50 | 51 | type Props = { 52 | onClickBackButton: Function, 53 | dateToShow: string, 54 | salesman: string, 55 | }; 56 | 57 | const renderBackButton = (onClickBackButton: Function): Object => ( 58 | 66 | ); 67 | 68 | const renderSalesmanInfo = (salesman: string): Object => ( 69 | 70 | 71 | 72 | 73 | 74 | {salesman} 75 | 76 | 77 | ); 78 | 79 | const TopContent = ({ onClickBackButton, dateToShow, salesman }: Props): Object => ( 80 | 81 | {renderBackButton(onClickBackButton)} 82 |

83 | {dateToShow} 84 |

85 | {renderSalesmanInfo(salesman)} 86 |
87 | ); 88 | 89 | export default TopContent; 90 | -------------------------------------------------------------------------------- /src/store/sagas/provider.js: -------------------------------------------------------------------------------- 1 | import { call, put } from 'redux-saga/effects'; 2 | 3 | import { Creators as ProviderCreators } from '../ducks/provider'; 4 | 5 | import { 6 | CREATE_PROVIDER, 7 | READ_PROVIDERS, 8 | UPDATE_PROVIDER, 9 | DELETE_PROVIDER, 10 | } from './event-handlers-types/provider'; 11 | 12 | import { PROVIDER } from './entitiesTypes'; 13 | import execRequest from './execRequest'; 14 | 15 | const EVENT_TAGS = { 16 | GET_ALL_PRODUCTS: 'PROVIDER_PRODUCTS_GET_ALL', 17 | PROVIDERS_GET_ALL: 'GET_ALL_PROVIDERS', 18 | REMOVE_PROVIDER: 'PROVIDER_REMOVE', 19 | PROVIDER_CREATE: 'CREATE_PROVIDER', 20 | EDIT_PROVIDER: 'PROVIDER_EDIT', 21 | }; 22 | 23 | export function* createProvider(action) { 24 | try { 25 | const { args } = action; 26 | 27 | const result = yield call(execRequest, PROVIDER, CREATE_PROVIDER, EVENT_TAGS.PROVIDER_CREATE, args); 28 | 29 | const newProvider = { 30 | ...args, 31 | id: result, 32 | }; 33 | 34 | yield put(ProviderCreators.createProviderSuccess(newProvider)); 35 | } catch (err) { 36 | yield put(ProviderCreators.createProviderFailure(err.message)); 37 | } 38 | } 39 | 40 | export function* getAllProviders() { 41 | try { 42 | const result = yield call(execRequest, PROVIDER, READ_PROVIDERS, EVENT_TAGS.PROVIDERS_GET_ALL); 43 | 44 | yield put(ProviderCreators.getAllProvidersSuccess(result)); 45 | } catch (err) { 46 | yield put(ProviderCreators.getAllProvidersFailure(err)); 47 | } 48 | } 49 | 50 | export function* editProvider(action) { 51 | try { 52 | const { provider } = action.payload; 53 | 54 | yield call(execRequest, PROVIDER, UPDATE_PROVIDER, EVENT_TAGS.EDIT_PROVIDER, provider); 55 | yield put(ProviderCreators.editProviderSuccess(provider)); 56 | } catch (err) { 57 | yield put(ProviderCreators.editProviderFailure(err)); 58 | } 59 | } 60 | 61 | export function* removeProvider(action) { 62 | try { 63 | const { id } = action.payload; 64 | 65 | yield call(execRequest, PROVIDER, DELETE_PROVIDER, EVENT_TAGS.REMOVE_PROVIDER, id); 66 | yield put(ProviderCreators.removeProviderSuccess(id)); 67 | } catch (err) { 68 | yield put(ProviderCreators.removeProviderFailure()); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/screens/cashier/components/bottom-valeus/item-config.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import ArrowForward from '@material-ui/icons/ArrowForward'; 4 | import InitialMoney from '@material-ui/icons/LocalAtm'; 5 | import ArrowBack from '@material-ui/icons/ArrowBack'; 6 | import Money from '@material-ui/icons/AttachMoney'; 7 | 8 | import styled from 'styled-components'; 9 | 10 | const ArrowBackIcon = styled(ArrowBack)` 11 | color: ${({ theme }) => theme.colors.white}; 12 | `; 13 | 14 | const ArrowForwardIcon = styled(ArrowForward)` 15 | color: ${({ theme }) => theme.colors.white}; 16 | `; 17 | 18 | const MoneyIcon = styled(Money)` 19 | color: ${({ theme }) => theme.colors.white}; 20 | `; 21 | 22 | const InitialMoneyIcon = styled(InitialMoney)` 23 | color: ${({ theme }) => theme.colors.white}; 24 | `; 25 | 26 | export const CONFIGS_TYPES = { 27 | TOTAL_PROFIT_FINISH_CASHIER: 'TOTAL_PROFIT_FINISH_CASHIER', 28 | TOTAL_INPUT: 'TOTAL_INPUT', 29 | TOTAL_OUTPUT: 'TOTAL_OUTPUT', 30 | TOTAL_PROFIT: 'TOTAL_PROFIT', 31 | INITAL_MONEY: 'INITAL_MONEY', 32 | }; 33 | 34 | export const getBottomValueItemConfig = (item: string, value: number): Object => { 35 | const configs = { 36 | [CONFIGS_TYPES.TOTAL_INPUT]: { 37 | message: 'Total Inserted in Cashier:', 38 | value: `$ ${Number(value).toFixed(2)}`, 39 | Icon: ArrowForwardIcon, 40 | color: 'success', 41 | }, 42 | 43 | [CONFIGS_TYPES.TOTAL_OUTPUT]: { 44 | message: 'Total Withdrawn in Cashier:', 45 | value: `$ ${Number(value).toFixed(2)}`, 46 | Icon: ArrowBackIcon, 47 | color: 'danger', 48 | }, 49 | 50 | [CONFIGS_TYPES.TOTAL_PROFIT]: { 51 | message: 'Profit:', 52 | value: `$ ${Number(value).toFixed(2)}`, 53 | Icon: MoneyIcon, 54 | color: 'affirmative', 55 | }, 56 | 57 | [CONFIGS_TYPES.TOTAL_PROFIT_FINISH_CASHIER]: { 58 | message: 'Total Profit:', 59 | value: `$ ${Number(value).toFixed(2)}`, 60 | Icon: MoneyIcon, 61 | color: 'affirmative', 62 | }, 63 | 64 | [CONFIGS_TYPES.INITAL_MONEY]: { 65 | message: 'Initial Money Quantity in Cashier:', 66 | value: `$ ${Number(value).toFixed(2)}`, 67 | Icon: InitialMoneyIcon, 68 | color: 'warning', 69 | }, 70 | }; 71 | 72 | return configs[item]; 73 | }; 74 | -------------------------------------------------------------------------------- /src/components/common/Dialog.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import DialogContentText from '@material-ui/core/DialogContentText'; 4 | import DialogActions from '@material-ui/core/DialogActions'; 5 | import DialogContent from '@material-ui/core/DialogContent'; 6 | import DialogTitle from '@material-ui/core/DialogTitle'; 7 | import Button from '@material-ui/core/Button'; 8 | import Dialog from '@material-ui/core/Dialog'; 9 | import Slide from '@material-ui/core/Slide'; 10 | 11 | type Props = { 12 | positiveAction: Function, 13 | negativeAction: Function, 14 | onCloseDialog: Function, 15 | positiveText: string, 16 | negativeText: string, 17 | description: string, 18 | isOpen: boolean, 19 | title: string, 20 | }; 21 | 22 | const onActionButtonClicked = (action: Function, onCloseDialog: Function): Object => { 23 | action(); 24 | onCloseDialog(); 25 | }; 26 | 27 | const renderSlide = (props: Object): Object => ( 28 | 32 | ); 33 | 34 | const CustomDialog = ({ 35 | positiveAction, 36 | negativeAction, 37 | onCloseDialog, 38 | positiveText, 39 | negativeText, 40 | description, 41 | title, 42 | isOpen, 43 | }: Props): Object => ( 44 | 53 | 56 | {title} 57 | 58 | 59 | 62 | {description} 63 | 64 | 65 | 66 | 72 | 78 | 79 | 80 | ); 81 | 82 | export default CustomDialog; 83 | -------------------------------------------------------------------------------- /src/components/common/sale-detail-dialog/index.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React, { Fragment } from 'react'; 4 | 5 | import DialogContent from '@material-ui/core/DialogContent'; 6 | import Dialog from '@material-ui/core/Dialog'; 7 | import Paper from '@material-ui/core/Paper'; 8 | 9 | import styled from 'styled-components'; 10 | 11 | import ProductList from '../product-sale-component/components/products-selected-list'; 12 | import TopContent from '../../../screens/customer/components/TopContent'; 13 | import BottomContent from './BottomContent'; 14 | 15 | const MainContent = styled(Paper)` 16 | width: 100%; 17 | height: 100%; 18 | border: 3px solid ${({ theme }) => theme.colors.lightGray}; 19 | border-radius: 4px; 20 | border-bottom-right-radius: 0px; 21 | border-bottom-left-radius: 0px; 22 | `; 23 | 24 | const renderContent = (onToggleSaleDetailDialog: Function, sale: Object): Object => { 25 | const { dateToShow, salesman, products } = sale; 26 | 27 | return ( 28 | 29 | 34 | 35 | {}} 37 | onRemoveProduct={() => {}} 38 | products={products} 39 | mode="detail" 40 | stock={[]} 41 | error="" 42 | /> 43 | 46 | 47 | 48 | ); 49 | }; 50 | 51 | type Props = { 52 | onToggleSaleDetailDialog: Function, 53 | isOpen: boolean, 54 | sale: Object, 55 | }; 56 | 57 | const SaleDetailDialog = ({ 58 | onToggleSaleDetailDialog, 59 | isOpen, 60 | sale, 61 | }: Props): Object => { 62 | return ( 63 | 72 | 73 | {sale && renderContent(onToggleSaleDetailDialog, sale)} 74 | 75 | 76 | ); 77 | }; 78 | 79 | export default SaleDetailDialog; 80 | -------------------------------------------------------------------------------- /src/screens/cashier/components/bottom-valeus/index.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React from 'react'; 4 | 5 | import Paper from '@material-ui/core/Paper'; 6 | import styled from 'styled-components'; 7 | 8 | import { CONFIGS_TYPES, getBottomValueItemConfig } from './item-config'; 9 | import BottomItem from './BottomItem'; 10 | 11 | const Container = styled.div` 12 | width: 100%; 13 | display: flex; 14 | justify-content: space-between; 15 | margin-top: 32px; 16 | border-radius: 4px; 17 | background-color: ${({ theme }) => theme.colors.lightGray} 18 | `; 19 | 20 | const SectionWrapper = styled.div` 21 | margin: 8px 16px; 22 | `; 23 | 24 | const renderTotalInputCashier = (totalInputCashier: number): Object => { 25 | const config = getBottomValueItemConfig(CONFIGS_TYPES.TOTAL_INPUT, totalInputCashier); 26 | 27 | return ( 28 | 31 | ); 32 | }; 33 | 34 | const renderTotalOutputCashier = (totalOutputCashier: number): Object => { 35 | const config = getBottomValueItemConfig(CONFIGS_TYPES.TOTAL_OUTPUT, totalOutputCashier); 36 | 37 | return ( 38 | 41 | ); 42 | }; 43 | 44 | const renderInitialMoneyInCashier = (initalMoney: number): Object => { 45 | const config = getBottomValueItemConfig(CONFIGS_TYPES.INITAL_MONEY, initalMoney); 46 | 47 | return ( 48 | 51 | ); 52 | }; 53 | 54 | const renderProfit = (totalProfit: number): Object => { 55 | const config = getBottomValueItemConfig(CONFIGS_TYPES.TOTAL_PROFIT, totalProfit); 56 | 57 | return ( 58 | 61 | ); 62 | }; 63 | 64 | type Props = { 65 | initialMoneyCashier: string, 66 | totalOutputCashier: number, 67 | totalInputCashier: number, 68 | totalProfit: number, 69 | }; 70 | 71 | const BottomValues = ({ 72 | initialMoneyCashier, 73 | totalOutputCashier, 74 | totalInputCashier, 75 | totalProfit, 76 | }: Props): Object => ( 77 | 78 | 79 | 80 | {renderTotalInputCashier(totalInputCashier)} 81 | {renderTotalOutputCashier(totalOutputCashier)} 82 | 83 | 84 | {renderInitialMoneyInCashier(initialMoneyCashier)} 85 | {renderProfit(totalProfit)} 86 | 87 | 88 | 89 | ); 90 | 91 | export default BottomValues; 92 | -------------------------------------------------------------------------------- /src/components/common/FullScreenDialog.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React from 'react'; 4 | 5 | import IconButton from '@material-ui/core/IconButton'; 6 | import Typography from '@material-ui/core/Typography'; 7 | import { withStyles } from '@material-ui/core/styles'; 8 | import CloseIcon from '@material-ui/icons/Close'; 9 | import Toolbar from '@material-ui/core/Toolbar'; 10 | import Dialog from '@material-ui/core/Dialog'; 11 | import AppBar from '@material-ui/core/AppBar'; 12 | import Paper from '@material-ui/core/Paper'; 13 | import Slide from '@material-ui/core/Slide'; 14 | 15 | import styled from 'styled-components'; 16 | 17 | const Wrapper = styled.div` 18 | width: 100%; 19 | height: 100%; 20 | background-color: ${({ theme }) => theme.colors.lightGray}; 21 | overflow-y: auto; 22 | `; 23 | 24 | const styles = theme => ({ 25 | container: { 26 | padding: `${theme.spacing.unit * 4}px ${theme.spacing.unit * 6}px`, 27 | }, 28 | content: { 29 | padding: `${theme.spacing.unit * 4}px ${theme.spacing.unit * 6}px`, 30 | }, 31 | toolbar: theme.mixins.toolbar, 32 | }); 33 | 34 | type Props = { 35 | children: Object, 36 | classes: Object, 37 | onClose: Function, 38 | isOpen: boolean, 39 | title: string, 40 | }; 41 | 42 | const TransitionComponent = (props: Object): Object => ( 43 | 47 | ); 48 | 49 | const FullScreenDialog = ({ 50 | children, 51 | classes, 52 | onClose, 53 | isOpen, 54 | title, 55 | }: Props): Object => ( 56 | 63 | 64 | 65 | 70 | 71 | 72 | 77 | {title} 78 | 79 | 80 | 81 |
84 | 87 | 90 | {children} 91 | 92 | 93 |
94 | ); 95 | 96 | export default withStyles(styles)(FullScreenDialog); 97 | -------------------------------------------------------------------------------- /public/back-end/controllers/backup/index.js: -------------------------------------------------------------------------------- 1 | const ApplicationModels = require('../../models'); 2 | 3 | const ENTITIES = { 4 | OPERATION_RESPONSE: 'OPERATION_RESPONSE', 5 | CLOSE_PRINT_WINDOW: 'CLOSE_PRINT_WINDOW', 6 | OPEN_PRINT_WINDOW: 'OPEN_PRINT_WINDOW', 7 | OPERATION_REQUEST: 'OPERATION_REQUEST', 8 | PROVIDER: 'PROVIDER', 9 | CUSTOMER: 'CUSTOMER', 10 | CASHIER: 'CASHIER', 11 | PRODUCT: 'PRODUCT', 12 | BUDGET: 'BUDGET', 13 | BACKUP: 'BACKUP', 14 | STOCK: 'STOCK', 15 | BRAND: 'BRAND', 16 | USER: 'USER', 17 | SALE: 'SALE', 18 | }; 19 | 20 | // OBS: THE ORDER MATTERS HERE! 21 | 22 | const MODELS = { 23 | [ENTITIES.BRAND]: { 24 | Model: ApplicationModels.Brand, 25 | field: 'brands', 26 | }, 27 | [ENTITIES.PRODUCT]: { 28 | Model: ApplicationModels.Product, 29 | field: 'products', 30 | }, 31 | [ENTITIES.STOCK]: { 32 | Model: ApplicationModels.Stock, 33 | field: 'stock', 34 | }, 35 | [ENTITIES.SALE]: { 36 | Model: ApplicationModels.Sale, 37 | field: 'sales', 38 | }, 39 | [ENTITIES.CASHIER]: { 40 | Model: ApplicationModels.Cashier, 41 | field: 'cashiers', 42 | }, 43 | [ENTITIES.BUDGET]: { 44 | Model: ApplicationModels.Budget, 45 | field: 'budgets', 46 | }, 47 | [ENTITIES.CUSTOMER]: { 48 | Model: ApplicationModels.Customer, 49 | field: 'customers', 50 | }, 51 | [ENTITIES.PROVIDER]: { 52 | Model: ApplicationModels.Provider, 53 | field: 'providers', 54 | }, 55 | [ENTITIES.USER]: { 56 | Model: ApplicationModels.User, 57 | field: 'users', 58 | }, 59 | }; 60 | 61 | exports.importFromBackupFile = async (backupFile) => { 62 | try { 63 | const modelKeys = Object.keys(MODELS); 64 | 65 | return await Promise.all(modelKeys.map(async (modelItem) => { 66 | const { Model, field } = MODELS[modelItem]; 67 | 68 | await Promise.all(backupFile[field].map(async item => Model.create(item))); 69 | })); 70 | } catch (err) { 71 | return err; 72 | } 73 | }; 74 | 75 | exports.exportToBackupFile = async () => { 76 | try { 77 | const backupFile = await Object.keys(MODELS).reduce(async (result, modelItem) => { 78 | const { Model, field } = MODELS[modelItem]; 79 | 80 | const data = await Model.findAll({ raw: true, order: [['updatedAt', 'DESC']] }); 81 | 82 | const currentResult = await result; 83 | 84 | return Object.assign({ ...currentResult }, { [field]: data }); 85 | }, {}); 86 | 87 | return backupFile; 88 | } catch (err) { 89 | return err; 90 | } 91 | }; 92 | -------------------------------------------------------------------------------- /src/screens/stock/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component, Fragment } from 'react'; 2 | 3 | import { bindActionCreators } from 'redux'; 4 | import { connect } from 'react-redux'; 5 | import { Creators as StockCreators } from '../../store/ducks/stock'; 6 | 7 | import config from './config'; 8 | import Form from './form'; 9 | 10 | import EntityComponent from '../../components/common/entity-component'; 11 | import Snackbar from '../../components/common/Snackbar'; 12 | 13 | type Props = { 14 | editStock: Function, 15 | getStock: Function, 16 | stock: Array, 17 | }; 18 | 19 | type State = { 20 | isSnackbarOpen: boolean, 21 | }; 22 | 23 | class Stock extends Component { 24 | state = { 25 | isSnackbarOpen: false, 26 | }; 27 | 28 | componentDidMount() { 29 | const { getStock } = this.props; 30 | 31 | getStock(); 32 | } 33 | 34 | componentWillReceiveProps(nextProps) { 35 | const { message, error } = nextProps.stock; 36 | 37 | if (message || error) { 38 | this.setState({ 39 | isSnackbarOpen: true, 40 | }); 41 | } 42 | } 43 | 44 | onEditStockItem = (itemToEdit: Object): void => { 45 | const { editStock } = this.props; 46 | 47 | editStock(itemToEdit); 48 | }; 49 | 50 | renderSnackbar = (stock: Object): Object => { 51 | const { isSnackbarOpen } = this.state; 52 | const { message, error } = stock; 53 | 54 | return ( 55 | this.setState({ isSnackbarOpen: false })} 57 | isOpen={isSnackbarOpen} 58 | message={message} 59 | error={error} 60 | /> 61 | ); 62 | }; 63 | 64 | render() { 65 | const { stock } = this.props; 66 | 67 | return ( 68 | 69 | ( 78 |
81 | )} 82 | /> 83 | {this.renderSnackbar(stock)} 84 | 85 | ); 86 | } 87 | } 88 | 89 | const mapDispatchToProps = dispatch => bindActionCreators(StockCreators, dispatch); 90 | 91 | const mapStateToProps = state => ({ 92 | stock: state.stock, 93 | }); 94 | 95 | export default connect(mapStateToProps, mapDispatchToProps)(Stock); 96 | -------------------------------------------------------------------------------- /src/screens/cashier/cashier-utils.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import moment from 'moment'; 4 | 5 | import { CASHIER_OPERATIONS } from './components/current-cashier/components/cashier-open/components/top-buttons-values/dialog-config'; 6 | 7 | export const getNewCashierOperationData = (value: string, reason: string, username: string, type: string): Object => ({ 8 | valueText: `$ ${parseFloat(value).toFixed(2)}`, 9 | timestampText: moment().calendar(), 10 | dateToShow: moment().format('lll'), 11 | value: parseFloat(value), 12 | salesman: username, 13 | id: Math.random(), 14 | customerName: '-', 15 | discountText: '-', 16 | inDebitText: '-', 17 | valuePaid: '-', 18 | totalText: '-', 19 | code: '-', 20 | reason, 21 | type, 22 | }); 23 | 24 | const getTotalPaymentValue = (paymentInfo: Object): number => { 25 | const valuePaid = Object.keys(paymentInfo).reduce((current, item) => current + Number(paymentInfo[item]), 0); 26 | 27 | return valuePaid; 28 | }; 29 | 30 | const getDiscountText = ({ type, value }) => { 31 | let discountText = '-'; 32 | 33 | if (type === 'percentage') { 34 | discountText = `${value}%`; 35 | } 36 | 37 | if (type === 'money') { 38 | discountText = `$ ${Number(value).toFixed(2)}`; 39 | } 40 | 41 | return discountText; 42 | }; 43 | 44 | export const parseSaleTableItem = (sale: Object): Object => { 45 | const { 46 | createdFromBudget, 47 | paymentInfo, 48 | customer, 49 | discount, 50 | subtotal, 51 | inDebit, 52 | total, 53 | } = sale; 54 | 55 | const discountText = getDiscountText(discount); 56 | const valuePaid = getTotalPaymentValue(paymentInfo); 57 | 58 | return { 59 | ...sale, 60 | valueText: `$ ${Number(subtotal).toFixed(2)}`, 61 | customerName: (customer ? customer.name : '-'), 62 | inDebitText: `$ ${Math.abs(inDebit).toFixed(2)}`, 63 | totalText: `$ ${Number(total).toFixed(2)}`, 64 | valuePaid: `$ ${valuePaid.toFixed(2)}`, 65 | timestampText: moment().calendar(), 66 | type: (createdFromBudget ? CASHIER_OPERATIONS.CONSOLIDATE_BUDGET_PAYMENT : CASHIER_OPERATIONS.SALE), 67 | discountText, 68 | }; 69 | }; 70 | 71 | export const calculateTotalProfit = (sales: Array): number => { 72 | const salesProducts = sales.map(sale => sale.products); 73 | 74 | const totalProfit = salesProducts.reduce((total, saleProducts) => { 75 | return total + saleProducts.reduce((current, product) => { 76 | const profit = product.salePrice - product.costPrice; 77 | return current + (profit * product.quantity); 78 | }, 0); 79 | }, 0); 80 | 81 | return totalProfit; 82 | }; 83 | -------------------------------------------------------------------------------- /src/store/ducks/print.js: -------------------------------------------------------------------------------- 1 | import Immutable from 'seamless-immutable'; 2 | 3 | export const Types = { 4 | FINISH_PRINT: 'print/FINISH_PRINT', 5 | START_PRINT: 'print/START_PRINT', 6 | RE_PRINT: 'print/RE_PRINT', 7 | }; 8 | 9 | const INITIAL_STATE = Immutable({ 10 | shouldRenderDocumentValues: true, 11 | shouldRenderDocumentInfo: true, 12 | numberOfPages: 1, 13 | currentPage: 0, 14 | open: false, 15 | data: {}, 16 | }); 17 | 18 | export const Creators = { 19 | startPrint: data => ({ 20 | type: Types.START_PRINT, 21 | payload: { data }, 22 | }), 23 | 24 | rePrint: () => ({ 25 | type: Types.RE_PRINT, 26 | }), 27 | 28 | finishPrint: () => ({ 29 | type: Types.FINISH_PRINT, 30 | }), 31 | }; 32 | 33 | // BASED ON A4 SHEET LAYOUT AND THE TABLE ROW HEIGHT 34 | const NUMBER_ITEMS_LAYOUT_SUPPORTS_WITHOUT_HEADER = 20; 35 | const NUMBER_ITEMS_LAYOUT_SUPPORTS_WITH_HEADER = 15; 36 | 37 | const getNumberOfPages = (dataset) => { 38 | let numberOfItems = dataset.length - NUMBER_ITEMS_LAYOUT_SUPPORTS_WITH_HEADER; 39 | let numberOfPages = 1; 40 | 41 | while (numberOfItems > 0) { 42 | numberOfItems -= NUMBER_ITEMS_LAYOUT_SUPPORTS_WITHOUT_HEADER; 43 | numberOfPages += 1; 44 | } 45 | 46 | return numberOfPages; 47 | }; 48 | 49 | const mapDatasetToPages = (dataset) => { 50 | const numberOfPages = getNumberOfPages(dataset); 51 | 52 | const datasetMappedIntoPages = Array(numberOfPages).fill({}).map((item, index) => { 53 | if (index === 0) { 54 | return dataset.slice(0, 15); 55 | } 56 | 57 | const startIndex = 15 + (20 * (index - 1)); 58 | const finalIndex = (startIndex + 20); 59 | 60 | return dataset.slice(startIndex, finalIndex); 61 | }); 62 | 63 | return datasetMappedIntoPages; 64 | }; 65 | 66 | const printContent = (state = INITIAL_STATE, { payload, type }) => { 67 | switch (type) { 68 | case Types.START_PRINT: 69 | return { 70 | ...state, 71 | shouldRenderDocumentValues: (getNumberOfPages(payload.data.products) === 1), 72 | datasetMappedIntoPages: mapDatasetToPages(payload.data.products), 73 | numberOfPages: getNumberOfPages(payload.data.products), 74 | data: payload.data, 75 | open: true, 76 | }; 77 | 78 | case Types.RE_PRINT: 79 | return { 80 | ...state, 81 | shouldRenderDocumentValues: ((state.numberOfPages - 1) === (state.currentPage + 1)), 82 | currentPage: state.currentPage + 1, 83 | shouldRenderDocumentInfo: false, 84 | }; 85 | 86 | case Types.FINISH_PRINT: 87 | return { 88 | ...state, 89 | open: false, 90 | }; 91 | 92 | default: 93 | return state; 94 | } 95 | }; 96 | 97 | export default printContent; 98 | -------------------------------------------------------------------------------- /public/electron.js: -------------------------------------------------------------------------------- 1 | const { 2 | BrowserWindow, 3 | ipcMain, 4 | shell, 5 | app, 6 | } = require('electron'); 7 | 8 | const path = require('path'); 9 | const url = require('url'); 10 | const fs = require('fs'); 11 | const os = require('os'); 12 | 13 | const OPERATIONS = { 14 | OPERATION_RESPONSE: 'OPERATION_RESPONSE', 15 | CLOSE_PRINT_WINDOW: 'CLOSE_PRINT_WINDOW', 16 | OPEN_PRINT_WINDOW: 'OPEN_PRINT_WINDOW', 17 | OPERATION_REQUEST: 'OPERATION_REQUEST', 18 | OPEN_URL: 'OPEN_URL', 19 | }; 20 | 21 | const handleEvent = require('./back-end/events-handlers'); 22 | 23 | let mainWindow; 24 | 25 | const createWindow = () => { 26 | mainWindow = new BrowserWindow(); 27 | mainWindow.maximize(); 28 | 29 | const startUrl = process.env.ELECTRON_START_URL || url.format({ 30 | pathname: path.join(__dirname, '/../build/index.html'), 31 | protocol: 'file:', 32 | slashes: true, 33 | }); 34 | 35 | if (process.env.NODE_ENV === 'development') { 36 | mainWindow.webContents.openDevTools(); 37 | } 38 | 39 | mainWindow.loadURL(startUrl); 40 | 41 | mainWindow.on('closed', () => { 42 | mainWindow = null; 43 | }); 44 | }; 45 | 46 | app.on('ready', () => { 47 | createWindow(); 48 | }); 49 | 50 | app.on('window-all-closed', () => { 51 | if (process.platform !== 'darwin') { 52 | app.quit(); 53 | } 54 | }); 55 | 56 | app.on('activate', () => { 57 | if (mainWindow === null) { 58 | createWindow(); 59 | } 60 | }); 61 | 62 | ipcMain.on(OPERATIONS.OPERATION_REQUEST, async (event, entitie, operation, tag, args) => { 63 | const eventResponseId = `${OPERATIONS.OPERATION_RESPONSE}_${tag}`; 64 | const result = await handleEvent(entitie, operation, args); 65 | 66 | event.sender.send(eventResponseId, result); 67 | }); 68 | 69 | ipcMain.on(OPERATIONS.OPEN_PRINT_WINDOW, (event, fileName) => { 70 | const pdfPath = path.join(os.tmpdir(), `${fileName}.pdf`); 71 | 72 | const pdfOptions = { 73 | printSelectionOnly: false, 74 | printBackground: false, 75 | landscape: false, 76 | marginsType: 1, 77 | pageSize: 'A4', 78 | }; 79 | 80 | const window = BrowserWindow.fromWebContents(event.sender); 81 | 82 | window.webContents.printToPDF(pdfOptions, (err, data) => { 83 | if (err) { 84 | throw err; 85 | } 86 | 87 | fs.writeFile(pdfPath, data, (writeError) => { 88 | if (writeError) { 89 | throw writeError; 90 | } 91 | 92 | shell.openExternal(`file://${pdfPath}`); 93 | 94 | event.sender.send(OPERATIONS.CLOSE_PRINT_WINDOW); 95 | }); 96 | }); 97 | }); 98 | 99 | ipcMain.on(OPERATIONS.OPEN_URL, (event, socialURL) => shell.openExternal(socialURL)); 100 | -------------------------------------------------------------------------------- /src/store/ducks/customerDebits.js: -------------------------------------------------------------------------------- 1 | import Immutable from 'seamless-immutable'; 2 | 3 | export const Types = { 4 | GET_DEBITS_REQUEST: 'debits/GET_DEBITS_REQUEST', 5 | GET_DEBITS_SUCCESS: 'debits/GET_DEBITS_SUCCESS', 6 | GET_DEBITS_FAILURE: 'debits/GET_DEBITS_FAILURE', 7 | 8 | REMOVE_DEBITS_REQUEST: 'debits/REMOVE_DEBITS_REQUEST', 9 | REMOVE_DEBITS_SUCCESS: 'debits/REMOVE_DEBITS_SUCCESS', 10 | REMOVE_DEBITS_FAILURE: 'debits/REMOVE_DEBITS_FAILURE', 11 | }; 12 | 13 | const INITIAL_STATE = Immutable({ 14 | data: [], 15 | message: null, 16 | error: null, 17 | }); 18 | 19 | export const Creators = { 20 | getDebits: id => ({ 21 | type: Types.GET_DEBITS_REQUEST, 22 | payload: { id }, 23 | }), 24 | 25 | getDebitsSuccess: sales => ({ 26 | type: Types.GET_DEBITS_SUCCESS, 27 | payload: { sales }, 28 | }), 29 | 30 | getDebitsFailure: error => ({ 31 | type: Types.GET_DEBITS_FAILURE, 32 | payload: { error }, 33 | }), 34 | 35 | removeDebit: sale => ({ 36 | type: Types.REMOVE_DEBITS_REQUEST, 37 | payload: { sale }, 38 | }), 39 | 40 | removeDebitSuccess: id => ({ 41 | type: Types.REMOVE_DEBITS_SUCCESS, 42 | payload: { id }, 43 | }), 44 | 45 | removeDebitFailure: error => ({ 46 | type: Types.REMOVE_DEBITS_FAILURE, 47 | payload: { error }, 48 | }), 49 | }; 50 | 51 | const customerDebit = (state = INITIAL_STATE, { payload, type }) => { 52 | switch (type) { 53 | case Types.GET_DEBITS_REQUEST: 54 | return { 55 | ...state, 56 | message: null, 57 | error: null, 58 | }; 59 | 60 | case Types.GET_DEBITS_SUCCESS: 61 | return { 62 | ...state, 63 | data: payload.sales.map(sale => ({ 64 | ...sale, 65 | paidValueText: `$ ${(sale.total - sale.inDebit).toFixed(2)}`, 66 | subtotalText: `$ ${sale.subtotal.toFixed(2)}`, 67 | inDebitText: `$ ${sale.inDebit.toFixed(2)}`, 68 | totalText: `$ ${sale.total.toFixed(2)}`, 69 | })), 70 | }; 71 | 72 | case Types.GET_DEBITS_FAILURE: 73 | return { 74 | ...state, 75 | error: payload.error, 76 | }; 77 | 78 | case Types.REMOVE_DEBITS_REQUEST: 79 | return { 80 | ...state, 81 | }; 82 | 83 | case Types.REMOVE_DEBITS_SUCCESS: 84 | return { 85 | ...state, 86 | data: state.data.filter(sale => sale.id !== payload.id), 87 | }; 88 | 89 | case Types.REMOVE_DEBITS_FAILURE: 90 | return { 91 | ...state, 92 | error: payload.error, 93 | }; 94 | 95 | default: 96 | return state; 97 | } 98 | }; 99 | 100 | export default customerDebit; 101 | -------------------------------------------------------------------------------- /src/store/sagas/alerts.js: -------------------------------------------------------------------------------- 1 | import { call, put } from 'redux-saga/effects'; 2 | 3 | import { Creators as AlertCreators } from '../ducks/alerts'; 4 | import { 5 | CUSTOMER, 6 | BUDGET, 7 | STOCK, 8 | SALE, 9 | } from './entitiesTypes'; 10 | 11 | import { BUDGET_STATUS } from '../../screens/budget/components/BudgetStatus'; 12 | import { READ_CUSTOMERS } from './event-handlers-types/customer'; 13 | import { READ_BUDGETS } from './event-handlers-types/budget'; 14 | import { READ_STOCK } from './event-handlers-types/stock'; 15 | import { READ_SALES } from './event-handlers-types/sale'; 16 | import execRequest from './execRequest'; 17 | 18 | const EVENT_TAGS = { 19 | READ_ALL_BUDGETS_OUTDATED: 'ALERTS_READ_ALL_BUDGETS_OUTDATED', 20 | READ_ALL_STOCK_UNDER_MIN: 'ALERTS_READ_ALL_STOCK_UNDER_MIN', 21 | READ_ALL_CUSTOMERS: 'ALERTS_READ_ALL_CUSTOMERS', 22 | READ_ALL_SALES: 'ALERTS_READ_ALL_SALES', 23 | }; 24 | 25 | export function* getNumberBudgetsOutOfDate() { 26 | try { 27 | const budgets = yield call(execRequest, BUDGET, READ_BUDGETS, EVENT_TAGS.READ_ALL_BUDGETS_OUTDATED); 28 | const numberBudgetsOutOfDate = budgets.reduce((total, { status }) => total + (status === BUDGET_STATUS.OUT_OF_TIME ? 1 : 0), 0); 29 | 30 | yield put(AlertCreators.getNumberBudgetsOutOfDateSuccess(numberBudgetsOutOfDate)); 31 | } catch (err) { 32 | yield put(AlertCreators.getNumberBudgetsOutOfDateFailure()); 33 | } 34 | } 35 | 36 | export function* getNumberCustomersInDebit() { 37 | try { 38 | const customers = yield call(execRequest, CUSTOMER, READ_CUSTOMERS, EVENT_TAGS.READ_ALL_CUSTOMERS); 39 | const sales = yield call(execRequest, SALE, READ_SALES, EVENT_TAGS.READ_ALL_SALES); 40 | 41 | let numberCustomersInDebit = 0; 42 | 43 | const isUserInDebit = (customerId, sale) => (sale.customer.id === customerId) && Boolean(sale.inDebit); 44 | customers.forEach((customer) => { 45 | const isUserWithAtLeastOneDebit = sales.some(sale => isUserInDebit(customer.id, sale)); 46 | numberCustomersInDebit += Number(isUserWithAtLeastOneDebit); 47 | }); 48 | 49 | yield put(AlertCreators.getNumberCustomersInDebitSuccess(numberCustomersInDebit)); 50 | } catch (err) { 51 | yield put(AlertCreators.getNumberCustomersInDebitFailure()); 52 | } 53 | } 54 | 55 | export function* getNumberStockUnderMin() { 56 | try { 57 | const productsInStock = yield call(execRequest, STOCK, READ_STOCK, EVENT_TAGS.READ_ALL_STOCK_UNDER_MIN); 58 | const numberStockUnderMin = productsInStock.reduce((total, { minStockQuantity, stockQuantity }) => total + (stockQuantity < minStockQuantity ? 1 : 0), 0); 59 | 60 | yield put(AlertCreators.getNumberStockUnderMinSuccess(numberStockUnderMin)); 61 | } catch (err) { 62 | yield put(AlertCreators.getNumberStockUnderMinFailure()); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/components/common/ActionButton.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React from 'react'; 4 | 5 | import AddCircleOutline from '@material-ui/icons/AddCircleOutline'; 6 | import ButtonBase from '@material-ui/core/ButtonBase'; 7 | 8 | import styled from 'styled-components'; 9 | 10 | const ButtonContainer = styled(ButtonBase)``; 11 | 12 | const ButtonWrapper = styled.div` 13 | height: 50px; 14 | display: flex; 15 | flex: 1; 16 | justify-content: space-between; 17 | align-items: center; 18 | padding-left: 20px; 19 | padding-right: 20px; 20 | border-radius: 6px; 21 | background-color: ${({ 22 | withCustomInactiveColor, 23 | withCustomColor, 24 | customColor, 25 | isDisabled, 26 | theme, 27 | }) => { 28 | let color = theme.colors.affirmative; 29 | 30 | const disabledColor = (withCustomInactiveColor ? theme.colors.customInactiveButton : theme.colors.lightGray); 31 | 32 | if (isDisabled) { 33 | color = disabledColor; 34 | } 35 | 36 | if (withCustomColor) { 37 | color = customColor; 38 | } 39 | 40 | return color; 41 | }}; 42 | `; 43 | 44 | const Title = styled.h2` 45 | margin-right: 18px, 46 | font-size: 28px; 47 | font-weight: 700; 48 | color: ${({ theme }) => theme.colors.white}; 49 | `; 50 | 51 | type Props = { 52 | withCustomInactiveColor: ?boolean, 53 | withCustomColor: ?boolean, 54 | withCustomIcon: ?boolean, 55 | customColor: ?string, 56 | CustomIcon: ?Object, 57 | disabled: boolean, 58 | withIcon: boolean, 59 | action: Function, 60 | title: string, 61 | }; 62 | 63 | const ActionButton = ({ 64 | withCustomInactiveColor, 65 | withCustomIcon, 66 | withCustomColor, 67 | customColor, 68 | CustomIcon, 69 | withIcon, 70 | disabled, 71 | action, 72 | title, 73 | }: Props): Obejct => ( 74 | action()} 76 | disabled={disabled} 77 | > 78 | 84 | {withIcon && ( 85 | 92 | )} 93 | {withCustomIcon && ( 94 | 101 | )} 102 | 103 | {title.toUpperCase()} 104 | 105 | 106 | 107 | ); 108 | 109 | export default ActionButton; 110 | -------------------------------------------------------------------------------- /src/store/sagas/customer.js: -------------------------------------------------------------------------------- 1 | import { call, put } from 'redux-saga/effects'; 2 | 3 | import { Creators as CustomerCreators } from '../ducks/customer'; 4 | import { Creators as AlertsCreators } from '../ducks/alerts'; 5 | 6 | import { 7 | CREATE_CUSTOMER, 8 | READ_CUSTOMERS, 9 | UPDATE_CUSTOMER, 10 | DELETE_CUSTOMER, 11 | } from './event-handlers-types/customer'; 12 | 13 | import { READ_SALES } from './event-handlers-types/sale'; 14 | import { CUSTOMER, SALE } from './entitiesTypes'; 15 | import execRequest from './execRequest'; 16 | 17 | const EVENT_TAGS = { 18 | GET_ALL_DEBITS: 'GET_ALL_DEBITS', 19 | READ_ALL: 'CUSTOMERS_READ_ALL', 20 | CREATE_CUSTOMER: 'CUSTOMER_CREATE', 21 | UPDATE_CUSTOMER: 'CUSTOMER_UPDATE', 22 | REMOVE_CUSTOMER: 'CUSTOMER_REMOVE', 23 | }; 24 | 25 | export function* createCustomer(action) { 26 | try { 27 | const { args } = action; 28 | 29 | const result = yield call(execRequest, CUSTOMER, CREATE_CUSTOMER, EVENT_TAGS.CREATE_CUSTOMER, args); 30 | 31 | const newCustomer = { 32 | ...args, 33 | id: result, 34 | }; 35 | 36 | yield put(CustomerCreators.createCustomerSuccess(newCustomer)); 37 | } catch (err) { 38 | yield put(CustomerCreators.createCustomerFailure()); 39 | } 40 | } 41 | 42 | const isCustomerInDebit = (customer, sales) => { 43 | const isCustomerWithDebit = sales.some(sale => (sale.customer.id === customer.id && sale.inDebit > 0)); 44 | 45 | return isCustomerWithDebit; 46 | }; 47 | 48 | export function* getAllCustomers() { 49 | try { 50 | const allCustomers = yield call(execRequest, CUSTOMER, READ_CUSTOMERS, EVENT_TAGS.READ_ALL); 51 | const sales = yield call(execRequest, SALE, READ_SALES, EVENT_TAGS.GET_ALL_DEBITS); 52 | 53 | const customers = allCustomers.map(customer => ({ ...customer, isInDebit: isCustomerInDebit(customer, sales) })); 54 | 55 | yield put(CustomerCreators.getAllCustomersSuccess(customers)); 56 | } catch (err) { 57 | yield put(CustomerCreators.getAllCustomersFailure()); 58 | } 59 | } 60 | 61 | export function* editCustomer(action) { 62 | try { 63 | const { customer } = action.payload; 64 | 65 | yield call(execRequest, CUSTOMER, UPDATE_CUSTOMER, EVENT_TAGS.UPDATE_CUSTOMER, customer); 66 | 67 | yield put(CustomerCreators.editCustomerSuccess(customer)); 68 | } catch (err) { 69 | yield put(CustomerCreators.editCustomerFailure()); 70 | } 71 | } 72 | 73 | export function* removeCustomer(action) { 74 | try { 75 | const { id } = action.payload; 76 | 77 | yield call(execRequest, CUSTOMER, DELETE_CUSTOMER, EVENT_TAGS.REMOVE_CUSTOMER, id); 78 | yield put(CustomerCreators.removeCustomerSuccess(id)); 79 | yield put(AlertsCreators.getNumberCustomersInDebit()); 80 | } catch (err) { 81 | yield put(CustomerCreators.removeCustomerFailure()); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/components/common/sale-confirmation/components/form-payment/FormPaymentItem.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React, { Component } from 'react'; 4 | 5 | import styled from 'styled-components'; 6 | 7 | import Input from '../../../CustomInput'; 8 | 9 | const Wrapper = styled.div` 10 | width: 100%; 11 | height: 64px; 12 | display: flex; 13 | align-items: center; 14 | justify-content: space-between; 15 | padding: 8px; 16 | &:hover { 17 | background-color: ${({ theme }) => theme.colors.lightGray}; 18 | } 19 | `; 20 | 21 | const InputWrapper = styled.div` 22 | height: 100%; 23 | display: flex; 24 | justify-content: center; 25 | align-items: center; 26 | `; 27 | 28 | const AboutItemWrapper = styled.div` 29 | display: flex; 30 | align-items: center; 31 | `; 32 | 33 | const IconWrapper = styled.div` 34 | width: 44px; 35 | height: 44px; 36 | display: flex; 37 | justify-content: center; 38 | align-items: center; 39 | border-radius: 22px; 40 | background-color: ${({ theme }) => theme.colors.inputBorder}; 41 | `; 42 | 43 | const Title = styled.span` 44 | margin-left: 16px; 45 | color: ${({ theme }) => theme.colors.darkText}; 46 | font-size: 18px; 47 | font-weight: 600; 48 | `; 49 | 50 | type Props = { 51 | lastInputFocused: string, 52 | onType: Function, 53 | title: string, 54 | value: string, 55 | Icon: Object, 56 | id: string, 57 | }; 58 | 59 | class FormPaymentItem extends Component { 60 | componentWillReceiveProps() { 61 | const { lastInputFocused, id } = this.props; 62 | 63 | if (lastInputFocused === id) { 64 | this._inputRef.focus(); 65 | } 66 | } 67 | 68 | renderInput = (): Object => { 69 | const { onType, value, id } = this.props; 70 | 71 | return ( 72 | { 74 | this._inputRef = input; 75 | }} 76 | value={value} 77 | onBlur={() => {}} 78 | onChange={onType} 79 | placeholder="" 80 | type="number" 81 | error="" 82 | label="" 83 | id={id} 84 | /> 85 | ); 86 | }; 87 | 88 | renderItemInfo = (): Object => { 89 | const { title, Icon } = this.props; 90 | 91 | return ( 92 | 93 | 94 | 95 | 96 | 97 | {title} 98 | 99 | 100 | ); 101 | }; 102 | 103 | render() { 104 | return ( 105 | 106 | {this.renderItemInfo()} 107 | 108 | {this.renderInput()} 109 | 110 | 111 | ); 112 | } 113 | } 114 | 115 | export default FormPaymentItem; 116 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "store-system", 3 | "version": "0.1.0", 4 | "description": "Store System", 5 | "author": "Stenio Wagner", 6 | "private": true, 7 | "main": "public/electron.js", 8 | "homepage": "./", 9 | "repository": "https://github.com/steniowagner/store-system", 10 | "keywords": [ 11 | "Electron", 12 | "store", 13 | "React", 14 | "Redux", 15 | "Redux-Saga", 16 | "MySQL", 17 | "Sequelize" 18 | ], 19 | "dependencies": { 20 | "@material-ui/core": "^3.0.3", 21 | "@material-ui/icons": "^3.0.1", 22 | "@material-ui/lab": "^3.0.0-alpha.18", 23 | "classnames": "^2.2.6", 24 | "formik": "^1.3.0", 25 | "moment": "^2.22.2", 26 | "mysql2": "^1.6.4", 27 | "react": "^16.4.1", 28 | "react-dom": "^16.4.1", 29 | "react-redux": "^5.1.1", 30 | "react-router-dom": "^4.3.1", 31 | "react-scripts": "1.1.4", 32 | "react-swipeable-views": "^0.13.0", 33 | "react-transition-group": "^2.5.2", 34 | "redux": "^4.0.1", 35 | "redux-saga": "^0.16.2", 36 | "seamless-immutable": "^7.1.4", 37 | "sequelize": "^4.41.2", 38 | "shorthash": "^0.0.2", 39 | "styled-components": "^3.3.3", 40 | "typeface-roboto": "^0.0.54", 41 | "yup": "^0.26.6" 42 | }, 43 | "scripts": { 44 | "dev": "concurrently \"npm run start\" \"wait-on http://localhost:3000 && npm run electron-dev .\"", 45 | "start": "BROWSER=none react-scripts start", 46 | "build": "react-scripts build", 47 | "dist": "electron-builder", 48 | "pack": "electron-builder --dir", 49 | "test": "react-scripts test --env=jsdom", 50 | "eject": "react-scripts eject", 51 | "electron-dev": "node public/back-end/electron-wait-react", 52 | "postinstall": "electron-builder install-app-deps", 53 | "electron": "electron .", 54 | "flow": "flow" 55 | }, 56 | "build": { 57 | "appId": "com.github.steniowagner", 58 | "directories": { 59 | "buildResources": "resources" 60 | }, 61 | "win": { 62 | "target": "nsis" 63 | }, 64 | "productName": "My Store" 65 | }, 66 | "devDependencies": { 67 | "babel-eslint": "^8.2.6", 68 | "babel-plugin-module-resolver": "^3.1.1", 69 | "concurrently": "^4.1.0", 70 | "electron": "^2.0.5", 71 | "electron-builder": "^20.38.4", 72 | "eslint": "^4.19.1", 73 | "eslint-config-airbnb": "^17.0.0", 74 | "eslint-import-resolver-babel-module": "^4.0.0", 75 | "eslint-plugin-flowtype": "^2.50.0", 76 | "eslint-plugin-import": "^2.13.0", 77 | "eslint-plugin-jsx-a11y": "^6.1.1", 78 | "eslint-plugin-react": "^7.10.0", 79 | "flow-bin": "^0.77.0", 80 | "prettier-eslint": "^8.8.2", 81 | "reactotron-react-js": "^2.1.1", 82 | "reactotron-redux": "^2.1.0", 83 | "reactotron-redux-saga": "^2.1.0", 84 | "sequelize-cli": "^5.3.0", 85 | "wait-on": "^3.2.0" 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/screens/cashier/components/current-cashier/components/cashier-closed/index.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React, { Component } from 'react'; 4 | 5 | import styled from 'styled-components'; 6 | 7 | import InitialMoneyCashDialog from './components/InitialMoneyCashDialog'; 8 | import CashierClosedAlert from './components/CashierClosedAlert'; 9 | 10 | const Container = styled.div` 11 | display: flex; 12 | width: 100%; 13 | height: 100%; 14 | justify-content: center; 15 | align-items: center; 16 | `; 17 | 18 | const Wrapper = styled.div``; 19 | 20 | type Props = { 21 | onSetInitialMoneyInCashier: Function, 22 | initialMoneyInCashier: string, 23 | }; 24 | 25 | type State = { 26 | isInitialMoneyDialogOpen: boolean, 27 | initialMoney: string, 28 | }; 29 | 30 | class CashierClosed extends Component { 31 | state = { 32 | isInitialMoneyDialogOpen: false, 33 | initialMoney: '', 34 | }; 35 | 36 | componentDidMount() { 37 | const { initialMoneyInCashier } = this.props; 38 | 39 | this.setState({ 40 | isInitialMoneyDialogOpen: !!initialMoneyInCashier, 41 | initialMoney: initialMoneyInCashier, 42 | }); 43 | } 44 | 45 | onSetInitialMoney = (): void => { 46 | const { onSetInitialMoneyInCashier } = this.props; 47 | const { initialMoney } = this.state; 48 | 49 | this.setState({ 50 | isInitialMoneyDialogOpen: false, 51 | }, () => onSetInitialMoneyInCashier(initialMoney)); 52 | }; 53 | 54 | onToggleInitialMoneyDialog = (): void => { 55 | const { isInitialMoneyDialogOpen } = this.state; 56 | 57 | this.setState({ 58 | isInitialMoneyDialogOpen: !isInitialMoneyDialogOpen, 59 | }); 60 | }; 61 | 62 | onTypeInitialMoney = (initialMoney: string): void => { 63 | this.setState({ 64 | initialMoney, 65 | }); 66 | }; 67 | 68 | renderCashierClosed = (): Object => ( 69 | 72 | ); 73 | 74 | renderInitialMoneyCashDialog = (): Object => { 75 | const { isInitialMoneyDialogOpen, initialMoney } = this.state; 76 | 77 | return ( 78 | 85 | ); 86 | } 87 | 88 | render() { 89 | return ( 90 | 91 | 92 | {this.renderCashierClosed()} 93 | {this.renderInitialMoneyCashDialog()} 94 | 95 | 96 | ); 97 | } 98 | } 99 | 100 | export default CashierClosed; 101 | -------------------------------------------------------------------------------- /src/components/common/ItemFiltered.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React, { Fragment } from 'react'; 4 | 5 | import ListItem from '@material-ui/core/ListItem'; 6 | import Divider from '@material-ui/core/Divider'; 7 | 8 | import styled from 'styled-components'; 9 | 10 | const Wrapper = styled.div` 11 | width: 100%; 12 | `; 13 | 14 | const PrimaryItemTextWrapper = styled.div` 15 | width: 100%; 16 | word-wrap: break-word; 17 | `; 18 | 19 | const PrimaryItemText = styled.p` 20 | font-size: 20px; 21 | font-weight: 700; 22 | color: rgba(0, 0, 0, 0.8); 23 | align-text: center; 24 | `; 25 | 26 | const SecondaryWrapper = styled.div` 27 | display: flex; 28 | justify-content: space-between; 29 | margin-top: 8px; 30 | `; 31 | 32 | const SecondaryItemWrapper = styled.div` 33 | margin-right: 16px; 34 | display: flex; 35 | justify-content: center; 36 | `; 37 | 38 | const SecondaryItemTitle = styled.p` 39 | font-size: 18px; 40 | font-weight: 500; 41 | color: rgba(0, 0, 0, 0.8); 42 | `; 43 | 44 | const SecondaryItemValue = styled.span` 45 | margin-left: 4px; 46 | font-size: 18px; 47 | color: rgba(0, 0, 0, 0.5); 48 | `; 49 | 50 | const ItemListWrapper = styled(({ ...props }) => ( 51 | 55 | ))` 56 | display: flex; 57 | flex-wrap: wrap; 58 | align-items: center;`; 59 | 60 | type Props = { 61 | secondariesItems: ?Array, 62 | onSelectItem: Function, 63 | primaryItem: Object, 64 | isLast: boolean 65 | }; 66 | 67 | const renderPrimaryItem = (primaryItem: string): Object => ( 68 | 69 | 70 | {primaryItem} 71 | 72 | 73 | ); 74 | 75 | const renderSecondariesItems = (secondariesItems: Array): Object => ( 76 | 77 | {secondariesItems.map(secondaryItem => ( 78 | 81 | 82 | {`${secondaryItem.title}: `} 83 | 84 | 85 | {secondaryItem.value} 86 | 87 | 88 | ))} 89 | 90 | ); 91 | 92 | const ItemFiltered = ({ 93 | secondariesItems, 94 | onSelectItem, 95 | primaryItem, 96 | isLast, 97 | }: Props): Object => ( 98 | 99 | onSelectItem()} 101 | > 102 | 103 | {renderPrimaryItem(primaryItem)} 104 | {!!secondariesItems && renderSecondariesItems(secondariesItems)} 105 | 106 | 107 | {!isLast && ( 108 | 111 | )} 112 | 113 | ); 114 | 115 | export default ItemFiltered; 116 | -------------------------------------------------------------------------------- /src/components/header/components/toolbar/components/UserInfo.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React, { Component, Fragment } from 'react'; 4 | 5 | import AssignmentInd from '@material-ui/icons/AssignmentInd'; 6 | import MenuItem from '@material-ui/core/MenuItem'; 7 | import Button from '@material-ui/core/Button'; 8 | import Menu from '@material-ui/core/Menu'; 9 | 10 | import styled from 'styled-components'; 11 | 12 | import { bindActionCreators } from 'redux'; 13 | import { connect } from 'react-redux'; 14 | import { Creators as CashierCreators } from '../../../../../store/ducks/cashier'; 15 | import { Creators as AuthCreators } from '../../../../../store/ducks/auth'; 16 | 17 | const UsernameText = styled.p` 18 | color: ${({ theme }) => theme.colors.headerText}; 19 | `; 20 | 21 | const UserIcon = styled(AssignmentInd)` 22 | margin-right: 8px; 23 | color: ${({ theme }) => theme.colors.headerText}; 24 | `; 25 | 26 | type Props = { 27 | resetMessages: Function, 28 | logout: Function, 29 | auth: Object, 30 | }; 31 | 32 | type State = { 33 | anchorElement: Object, 34 | }; 35 | 36 | class UserInfo extends Component { 37 | state = { 38 | anchorElement: null, 39 | }; 40 | 41 | onClickButton = (event) => { 42 | this.setState({ anchorElement: event.currentTarget }); 43 | }; 44 | 45 | onClickMenuItem = () => { 46 | const { resetMessages, logout } = this.props; 47 | 48 | this.setState({ 49 | anchorElement: null, 50 | }, () => { 51 | resetMessages(); 52 | logout(); 53 | }); 54 | } 55 | 56 | handleCloseMenu = () => { 57 | this.setState({ anchorElement: null }); 58 | }; 59 | 60 | render() { 61 | const { anchorElement } = this.state; 62 | const { auth } = this.props; 63 | const { user } = auth; 64 | 65 | return ( 66 | 67 | 77 | 83 | 86 | Change User 87 | 88 | 89 | 90 | ); 91 | } 92 | } 93 | 94 | const Creators = Object.assign({}, CashierCreators, AuthCreators); 95 | 96 | const mapDispatchToProps = dispatch => bindActionCreators(Creators, dispatch); 97 | 98 | const mapStateToProps = state => ({ 99 | auth: state.auth, 100 | }); 101 | 102 | export default connect(mapStateToProps, mapDispatchToProps)(UserInfo); 103 | -------------------------------------------------------------------------------- /src/components/common/product-sale-component/components/top-row/components/select-customer/components/SelectUserDialog.js: -------------------------------------------------------------------------------- 1 | 2 | import React, { Component } from 'react'; 3 | 4 | import { withStyles } from '@material-ui/core/styles'; 5 | 6 | import DialogActions from '@material-ui/core/DialogActions'; 7 | import DialogContent from '@material-ui/core/DialogContent'; 8 | import Button from '@material-ui/core/Button'; 9 | import Dialog from '@material-ui/core/Dialog'; 10 | import Slide from '@material-ui/core/Slide'; 11 | 12 | import CustomerFilter from './CustomerFilter'; 13 | 14 | const styles = { 15 | dialogPaper: { 16 | minHeight: '40vh', 17 | maxHeight: '80vh', 18 | }, 19 | }; 20 | 21 | type Props = { 22 | onSelectCustomer: Function, 23 | onToggle: Function, 24 | classes: Object, 25 | isOpen: boolean, 26 | }; 27 | 28 | type State = { 29 | customer: Object, 30 | }; 31 | 32 | class SelectCustomerDialog extends Component { 33 | state = { 34 | customer: {}, 35 | }; 36 | 37 | onChooseCustomer = (customer: Object) => { 38 | this.setState({ 39 | customer, 40 | }); 41 | }; 42 | 43 | onOkPressed = (): void => { 44 | const { onSelectCustomer, onToggle } = this.props; 45 | const { customer } = this.state; 46 | 47 | onSelectCustomer(customer); 48 | onToggle(); 49 | }; 50 | 51 | renderSlide = (props: Object): Object => ( 52 | 56 | ); 57 | 58 | renderDialog = (): Object => { 59 | const { 60 | customerSelected, 61 | customers, 62 | onToggle, 63 | classes, 64 | isOpen, 65 | } = this.props; 66 | 67 | return ( 68 | 79 | 80 | 85 | 86 | 87 | 93 | 99 | 100 | 101 | ); 102 | } 103 | 104 | render() { 105 | return this.renderDialog(); 106 | } 107 | } 108 | 109 | export default withStyles(styles)(SelectCustomerDialog); 110 | -------------------------------------------------------------------------------- /src/components/common/product-sale-component/components/top-row/index.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React, { Fragment } from 'react'; 4 | 5 | import Print from '@material-ui/icons/Print'; 6 | import styled from 'styled-components'; 7 | 8 | import { bindActionCreators } from 'redux'; 9 | import { connect } from 'react-redux'; 10 | import { Creators as PrintCreators } from '../../../../../store/ducks/print'; 11 | 12 | import SelectCustomer from './components/select-customer'; 13 | import CustomerDebits from './components/CustomerDebits'; 14 | import ActionButton from '../../../ActionButton'; 15 | 16 | const TopRowWrapper = styled.div` 17 | display: flex; 18 | justify-content: space-between; 19 | align-items: center; 20 | margin-bottom: 16px; 21 | `; 22 | 23 | const ButtonWrapper = styled.div` 24 | margin-bottom: ${({ hasError }) => (hasError ? 20 : 0)}px; 25 | `; 26 | 27 | type Props = { 28 | shouldRenderPrintReceiptButton: boolean, 29 | customerSelected: Object, 30 | setFieldValue: Function, 31 | debits: Array, 32 | startPrint: Function, 33 | values: Object, 34 | error: string, 35 | mode: string, 36 | }; 37 | 38 | const renderPrintReceptButton = (startPrint: Function, values: Object, error: Object, mode: string): Object => ( 39 | (mode === 'detail') && ( 40 | 43 | startPrint(values)} 45 | title="Print Voucher" 46 | CustomIcon={Print} 47 | withIcon={false} 48 | withCustomIcon 49 | /> 50 | 51 | ) 52 | ); 53 | 54 | const renderSelectCustomerContent = (customerSelected: Object, setFieldValue: Function, error: Object, mode: string): Object => ( 55 | 61 | ); 62 | 63 | const TopRow = ({ 64 | shouldRenderPrintReceiptButton, 65 | customerSelected, 66 | setFieldValue, 67 | startPrint, 68 | debits, 69 | values, 70 | error, 71 | mode, 72 | }: Props): Object => { 73 | const isUserWithDebits = (!!customerSelected && debits.length > 0); 74 | 75 | return ( 76 | 77 | 78 | Customer 79 | 80 | 81 | {renderSelectCustomerContent(customerSelected, setFieldValue, error, mode)} 82 | {shouldRenderPrintReceiptButton && renderPrintReceptButton(startPrint, values, error, mode)} 83 | 84 | {isUserWithDebits && ( 85 | 88 | )} 89 | 90 | ); 91 | }; 92 | 93 | const mapDispatchToProps = dispatch => bindActionCreators(PrintCreators, dispatch); 94 | 95 | const mapStateToProps = state => ({ 96 | debits: state.debits.data, 97 | }); 98 | 99 | export default connect(mapStateToProps, mapDispatchToProps)(TopRow); 100 | -------------------------------------------------------------------------------- /src/components/common/sale-confirmation/components/FooterItems.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React, { Fragment } from 'react'; 4 | 5 | import FormControlLabel from '@material-ui/core/FormControlLabel'; 6 | import Checkbox from '@material-ui/core/Checkbox'; 7 | import Divider from '@material-ui/core/Divider'; 8 | 9 | import styled from 'styled-components'; 10 | 11 | const Container = styled.div` 12 | width: 100%; 13 | background-color: ${({ theme }) => theme.colors.lightGray}; 14 | margin-top: 16px; 15 | border-radius: 4px; 16 | `; 17 | 18 | const Wrapper = styled.div` 19 | width: 100%; 20 | height: 48px; 21 | display: flex; 22 | padding: 0 8px; 23 | justify-content: space-between; 24 | align-items: center; 25 | `; 26 | 27 | const ErrorText = styled.span` 28 | font-size: 18px; 29 | color: ${({ theme }) => theme.colors.danger}; 30 | `; 31 | 32 | type Props = { 33 | onToggleShouldPrintReceiptCheckbox: Function, 34 | shouldRenderDebitCheckbox: boolean, 35 | onToggleInDebitCheckbox: Function, 36 | shouldPrintReceipt: boolean, 37 | isInDebit: boolean, 38 | error: string, 39 | mode: string, 40 | }; 41 | 42 | const renderDebitAndErrorRow = (onToggleInDebitCheckbox: Function, isInDebit: boolean, error: string): Object => ( 43 | 44 | 52 | )} 53 | label="Leave in Debit" 54 | disabled={!error} 55 | /> 56 | 57 | {error} 58 | 59 | 60 | ); 61 | 62 | const renderShouldPrintReceipt = (onToggleShouldPrintReceiptCheckbox: Function, shouldPrintReceipt: number): Object => ( 63 | 64 | 72 | )} 73 | label="Print Voucher" 74 | /> 75 | 76 | ); 77 | 78 | const FooterItems = ({ 79 | onToggleShouldPrintReceiptCheckbox, 80 | shouldRenderDebitCheckbox, 81 | onToggleInDebitCheckbox, 82 | shouldPrintReceipt, 83 | isInDebit, 84 | error, 85 | mode, 86 | }: Props): Object => { 87 | const isFormOnCreateMode = (mode === 'create'); 88 | 89 | return ( 90 | 91 | {shouldRenderDebitCheckbox && ( 92 | 93 | {renderDebitAndErrorRow(onToggleInDebitCheckbox, isInDebit, error)} 94 | 95 | )} 96 | {isFormOnCreateMode && ( 97 | 98 | 99 | {renderShouldPrintReceipt(onToggleShouldPrintReceiptCheckbox, shouldPrintReceipt)} 100 | 101 | )} 102 | 103 | ); 104 | }; 105 | 106 | export default FooterItems; 107 | -------------------------------------------------------------------------------- /src/screens/budget/components/BudgetStatus.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React from 'react'; 4 | 5 | import Approved from '@material-ui/icons/CheckCircleOutline'; 6 | import OutOfTime from '@material-ui/icons/TimerOff'; 7 | import Pending from '@material-ui/icons/History'; 8 | 9 | import styled from 'styled-components'; 10 | import appStyles from '../../../styles'; 11 | 12 | const Wrapper = styled.div` 13 | margin-left: 24px; 14 | `; 15 | 16 | const SelectorWrapper = styled.div` 17 | height: 50px; 18 | display: flex; 19 | justify-content: center; 20 | align-items: center; 21 | margin-top: 8px; 22 | border-radius: 6px; 23 | background-color: ${({ color }) => color}; 24 | `; 25 | 26 | const ItemSelectedText = styled.span` 27 | font-size: 18px; 28 | font-weight: 700; 29 | margin-right: 8px; 30 | color: ${({ theme }) => theme.colors.white}; 31 | `; 32 | 33 | const ItemWrapper = styled.div` 34 | width: 100%; 35 | height: 100%; 36 | display: flex; 37 | align-items: center; 38 | padding: 0 16px; 39 | `; 40 | 41 | const ApprovedIcon = styled(({ ...props }) => ( 42 | 45 | ))` 46 | padding-bottom: 2px; 47 | color: ${({ theme }) => theme.colors.white}; 48 | `; 49 | 50 | const OutOfTimeIcon = styled(({ ...props }) => ( 51 | 54 | ))` 55 | padding-bottom: 2px; 56 | color: ${({ theme }) => theme.colors.white}; 57 | `; 58 | 59 | const PendingIcon = styled(({ ...props }) => ( 60 | 63 | ))` 64 | padding-bottom: 2px; 65 | color: ${({ theme }) => theme.colors.white}; 66 | `; 67 | 68 | export const BUDGET_STATUS = { 69 | PENDING: 'PENDING', 70 | APPROVED: 'APPROVED', 71 | OUT_OF_TIME: 'OUT OF TIME', 72 | }; 73 | 74 | const STATUS_TYPES = [{ 75 | color: appStyles.colors.warning, 76 | statusText: BUDGET_STATUS.PENDING, 77 | Icon: PendingIcon, 78 | }, { 79 | color: appStyles.colors.success, 80 | statusText: BUDGET_STATUS.APPROVED, 81 | Icon: ApprovedIcon, 82 | }, { 83 | color: appStyles.colors.mediumGray, 84 | statusText: BUDGET_STATUS.OUT_OF_TIME, 85 | Icon: OutOfTimeIcon, 86 | }]; 87 | 88 | type Props = { 89 | status: string, 90 | }; 91 | 92 | const getStatusConfig = (status: string): Object => STATUS_TYPES.filter(statusType => statusType.statusText === status)[0]; 93 | 94 | const BudgetStatus = ({ status }: Props): Object => { 95 | const currentStatus = status || BUDGET_STATUS.PENDING; 96 | 97 | const { Icon, color, statusText } = getStatusConfig(currentStatus); 98 | 99 | return ( 100 | 101 | 102 | Status 103 | 104 | 107 | 108 | 109 | {statusText} 110 | 111 | 112 | 113 | 114 | 115 | ); 116 | }; 117 | 118 | export default BudgetStatus; 119 | -------------------------------------------------------------------------------- /src/Router.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React, { Component } from 'react'; 4 | 5 | import { CSSTransition, TransitionGroup } from 'react-transition-group'; 6 | import { withRouter, Switch, Route } from 'react-router-dom'; 7 | import styled from 'styled-components'; 8 | import './styles/fade.css'; 9 | 10 | import Customer from './screens/customer'; 11 | import Provider from './screens/provider'; 12 | import Product from './screens/product'; 13 | import Cashier from './screens/cashier'; 14 | import Budget from './screens/budget'; 15 | import Sales from './screens/sales'; 16 | import Stock from './screens/stock'; 17 | import User from './screens/user'; 18 | 19 | const Wrapper = styled.div` 20 | width: 100%; 21 | position: absolute; 22 | padding: 164px 28px 28px 28px; 23 | `; 24 | 25 | const Container = styled.div` 26 | height: 100%; 27 | position: relative; 28 | overflow-y: scroll; 29 | `; 30 | 31 | type Props = { 32 | history: Object, 33 | }; 34 | 35 | class ApplicationRouter extends Component { 36 | componentDidMount() { 37 | const { history } = this.props; 38 | 39 | history.push('/dashboard/cashier'); 40 | } 41 | 42 | render() { 43 | return ( 44 | 45 | ( 47 | 48 | 53 | 54 | 57 | 61 | 65 | 69 | 73 | 77 | 81 | 85 | 89 | 90 | 91 | 92 | 93 | )} 94 | /> 95 | 96 | ); 97 | } 98 | } 99 | 100 | export default withRouter(ApplicationRouter); 101 | -------------------------------------------------------------------------------- /src/components/common/filter/components/DateFilterDialog.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React, { Component } from 'react'; 4 | 5 | import DialogActions from '@material-ui/core/DialogActions'; 6 | import DialogContent from '@material-ui/core/DialogContent'; 7 | import DialogTitle from '@material-ui/core/DialogTitle'; 8 | import Button from '@material-ui/core/Button'; 9 | import Dialog from '@material-ui/core/Dialog'; 10 | import Slide from '@material-ui/core/Slide'; 11 | 12 | import moment from 'moment'; 13 | 14 | import Input from '../../CustomInput'; 15 | 16 | type Props = { 17 | isChooseDateDialogOpen: boolean, 18 | onToggleDateDialog: Function, 19 | }; 20 | 21 | type State = { 22 | dateChoosed: string, 23 | }; 24 | 25 | class DateFilterDialog extends Component { 26 | state = { 27 | dateChoosed: '', 28 | } 29 | 30 | onChooseDate = (dateChoosed: string): void => { 31 | this.setState({ 32 | dateChoosed, 33 | }); 34 | }; 35 | 36 | renderSlide = (props: Object): Object => ( 37 | 41 | ); 42 | 43 | renderTitle = (): Object => ( 44 | 47 | Escolha uma Data 48 | 49 | ); 50 | 51 | renderActionButtons = (): Object => { 52 | const { onToggleDateDialog, onChooseDate } = this.props; 53 | const { dateChoosed } = this.state; 54 | 55 | const date = moment(dateChoosed).format('DD/MM/YYYY'); 56 | 57 | return ( 58 | 59 | 65 | 72 | 73 | ); 74 | } 75 | 76 | renderDateInput = (): Object => { 77 | const { dateChoosed } = this.state; 78 | 79 | return ( 80 | this.onChooseDate(e.target.value)} 82 | value={dateChoosed} 83 | onBlur={() => {}} 84 | id="date-filter" 85 | placeholder="" 86 | type="date" 87 | error="" 88 | label="" 89 | /> 90 | ); 91 | }; 92 | 93 | render() { 94 | const { isChooseDateDialogOpen, onToggleDateDialog } = this.props; 95 | 96 | return ( 97 | 106 | {this.renderTitle()} 107 | 108 | {this.renderDateInput()} 109 | 110 | {this.renderActionButtons()} 111 | 112 | ); 113 | } 114 | } 115 | 116 | export default DateFilterDialog; 117 | -------------------------------------------------------------------------------- /src/components/login/components/LoginForm.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React, { Component } from 'react'; 4 | 5 | import Button from '@material-ui/core/Button'; 6 | 7 | import { bindActionCreators } from 'redux'; 8 | import { connect } from 'react-redux'; 9 | import { Creators as BudgetCreators } from '../../../store/ducks/budget'; 10 | import { Creators as AuthCreators } from '../../../store/ducks/auth'; 11 | 12 | import { Wrapper, InputWrapper, ButtonWrapper } from './styles'; 13 | import Input from '../../common/CustomInput'; 14 | 15 | type Props = { 16 | setOutdatedBudgets: Function, 17 | setSnackbarError: Function, 18 | users: Array, 19 | login: Function, 20 | }; 21 | 22 | type State = { 23 | username: string, 24 | password: string, 25 | } 26 | 27 | class LoginForm extends Component { 28 | state = { 29 | username: '', 30 | password: '', 31 | }; 32 | 33 | componentDidMount() { 34 | this._usernameInputRef.focus(); 35 | } 36 | 37 | onTypeInputValue = (stateRef: string, value: string) => { 38 | this.setState({ 39 | [stateRef]: value, 40 | }); 41 | }; 42 | 43 | onClickEnterButton = (): void => { 44 | const { setSnackbarError, users } = this.props; 45 | const { username, password } = this.state; 46 | 47 | const userSelected = users.filter(user => ((user.username === username) && (user.password === password)))[0]; 48 | 49 | return (userSelected ? this.handleLogin(userSelected) : setSnackbarError('User not Found')); 50 | }; 51 | 52 | handleLogin = (user: Object): void => { 53 | const { setOutdatedBudgets, login } = this.props; 54 | 55 | setOutdatedBudgets(); 56 | 57 | login(user); 58 | }; 59 | 60 | render() { 61 | const { username, password } = this.state; 62 | 63 | return ( 64 | 65 | 66 | { this._usernameInputRef = input; }} 68 | onChange={(event: Object): void => this.onTypeInputValue('username', event.target.value)} 69 | onBlur={() => {}} 70 | value={username} 71 | label="Username" 72 | placeholder="" 73 | id="username" 74 | type="text" 75 | error="" 76 | /> 77 | 78 | this.onTypeInputValue('password', event.target.value)} 80 | onBlur={() => {}} 81 | value={password} 82 | type="password" 83 | placeholder="" 84 | label="Password" 85 | id="password" 86 | error="" 87 | /> 88 | 89 | 97 | 98 | 99 | ); 100 | } 101 | } 102 | 103 | const Creators = Object.assign({}, BudgetCreators, AuthCreators); 104 | 105 | const mapDispatchToProps = dispatch => bindActionCreators(Creators, dispatch); 106 | 107 | export default connect(null, mapDispatchToProps)(LoginForm); 108 | -------------------------------------------------------------------------------- /src/components/common/product-sale-component/components/select-product/components/SelectProductsValues.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | 3 | import styled from 'styled-components'; 4 | 5 | import Input from '../../../../CustomInput'; 6 | 7 | const Container = styled.div` 8 | width: 30%; 9 | display: flex; 10 | align-items: flex-end; 11 | justify-content: flex-end; 12 | `; 13 | 14 | const InputWrapper = styled.div` 15 | width: 28%; 16 | margin: 0 8px; 17 | `; 18 | 19 | type Props = { 20 | onTypeQuantity: Function, 21 | salePrice: number, 22 | refFocus: string, 23 | quantity: string, 24 | mode: string, 25 | }; 26 | 27 | class SelectProductValues extends Component { 28 | componentWillReceiveProps(nextProps) { 29 | const { salePrice, refFocus } = nextProps; 30 | 31 | if (salePrice && (refFocus === 'quantity')) { 32 | this._inputQuantityRef.focus(); 33 | } 34 | } 35 | 36 | getInputConfig = (handleChange: Function, value: string, label: string, id: string, type: string): Object => ({ 37 | handleChange, 38 | value, 39 | label, 40 | type, 41 | id, 42 | }); 43 | 44 | renderInput = (config: Object): Object => { 45 | const { mode } = this.props; 46 | 47 | const { 48 | handleChange, 49 | value, 50 | label, 51 | type, 52 | id, 53 | } = config; 54 | 55 | const isQuantityField = (id === 'quantity'); 56 | const shouldDisabledField = (!isQuantityField || mode === 'detail'); 57 | 58 | return ( 59 | 60 | { 62 | if (isQuantityField) { 63 | this._inputQuantityRef = input; 64 | } 65 | }} 66 | disabled={shouldDisabledField} 67 | onChange={handleChange} 68 | onBlur={() => {}} 69 | placeholder="" 70 | label={label} 71 | value={value} 72 | type={type} 73 | error="" 74 | id={id} 75 | /> 76 | 77 | ); 78 | }; 79 | 80 | renderQuantityInput = (onTypeQuantity: Function, quantity: string): Object => { 81 | const inputConfig = this.getInputConfig(onTypeQuantity, quantity, 'Quantity', 'quantity', 'number'); 82 | 83 | return this.renderInput(inputConfig); 84 | }; 85 | 86 | renderPriceInput = (salePrice: number): Object => { 87 | const inputConfig = this.getInputConfig(() => {}, salePrice, 'Price', 'price', 'text'); 88 | 89 | return this.renderInput(inputConfig); 90 | }; 91 | 92 | renderTotalInput = (salePrice: number, quantity: string): Object => { 93 | const total = Math.abs(quantity) * salePrice; 94 | 95 | const inputConfig = this.getInputConfig(() => {}, total.toFixed(2), 'Total', 'total', 'text'); 96 | 97 | return this.renderInput(inputConfig); 98 | }; 99 | 100 | render() { 101 | const { onTypeQuantity, salePrice, quantity } = this.props; 102 | 103 | return ( 104 | 105 | {this.renderQuantityInput(onTypeQuantity, quantity)} 106 | {this.renderPriceInput(salePrice)} 107 | {this.renderTotalInput(salePrice, quantity)} 108 | 109 | ); 110 | } 111 | } 112 | 113 | export default SelectProductValues; 114 | -------------------------------------------------------------------------------- /src/screens/provider/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component, Fragment } from 'react'; 2 | 3 | import { bindActionCreators } from 'redux'; 4 | import { connect } from 'react-redux'; 5 | import { Creators as ProviderCreators } from '../../store/ducks/provider'; 6 | 7 | import config from './config'; 8 | import Form from './form'; 9 | 10 | import EntityComponent from '../../components/common/entity-component'; 11 | import Snackbar from '../../components/common/Snackbar'; 12 | 13 | type Props = { 14 | getAllProviders: Function, 15 | removeProvider: Function, 16 | createProvider: Function, 17 | providers: Arra, 18 | editProvider: Function, 19 | }; 20 | 21 | type State = { 22 | isSnackbarOpen: boolean, 23 | }; 24 | 25 | class Provider extends Component { 26 | state = { 27 | isSnackbarOpen: false, 28 | }; 29 | 30 | componentDidMount() { 31 | const { getAllProviders } = this.props; 32 | 33 | getAllProviders(); 34 | } 35 | 36 | componentWillReceiveProps(nextProps) { 37 | const { message, error } = nextProps.providers; 38 | 39 | if (message || error) { 40 | this.setState({ 41 | isSnackbarOpen: true, 42 | }); 43 | } 44 | } 45 | 46 | onCreateProvider = (provider: Object): void => { 47 | const { createProvider } = this.props; 48 | 49 | createProvider(provider); 50 | }; 51 | 52 | onEditProvider = (providerToEdit: Object): void => { 53 | const { editProvider } = this.props; 54 | 55 | editProvider(providerToEdit); 56 | }; 57 | 58 | onRemoveProvider = (providerId: number): void => { 59 | const { removeProvider } = this.props; 60 | 61 | removeProvider(providerId); 62 | }; 63 | 64 | renderSnackbar = (providers: Object): Object => { 65 | const { isSnackbarOpen } = this.state; 66 | const { message, error } = providers; 67 | 68 | return ( 69 | this.setState({ isSnackbarOpen: false })} 71 | isOpen={isSnackbarOpen} 72 | message={message} 73 | error={error} 74 | /> 75 | ); 76 | }; 77 | 78 | render() { 79 | const { providers } = this.props; 80 | 81 | const providersNames = providers.data.map(provider => provider.name); 82 | 83 | return ( 84 | 85 | ( 98 | 102 | )} 103 | /> 104 | {this.renderSnackbar(providers)} 105 | 106 | ); 107 | } 108 | } 109 | 110 | const mapDispatchToProps = dispatch => bindActionCreators(ProviderCreators, dispatch); 111 | 112 | const mapStateToProps = state => ({ 113 | providers: state.provider, 114 | }); 115 | 116 | export default connect(mapStateToProps, mapDispatchToProps)(Provider); 117 | -------------------------------------------------------------------------------- /src/screens/sales/index.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React, { Component, Fragment } from 'react'; 4 | 5 | import { bindActionCreators } from 'redux'; 6 | import { connect } from 'react-redux'; 7 | import { Creators as StockCreators } from '../../store/ducks/stock'; 8 | import { Creators as PrintCreators } from '../../store/ducks/print'; 9 | import { Creators as SaleCreators } from '../../store/ducks/sale'; 10 | 11 | import EntityComponent from '../../components/common/entity-component'; 12 | import Snackbar from '../../components/common/Snackbar'; 13 | 14 | import config from './config'; 15 | import Form from './form'; 16 | 17 | type Props = { 18 | getAllSales: Function, 19 | createSale: Function, 20 | startPrint: Function, 21 | stock: Array, 22 | sale: Array, 23 | getStock: Function, 24 | editSale: Function, 25 | }; 26 | 27 | type State = { 28 | isSnackbarOpen: boolean, 29 | }; 30 | 31 | class Sales extends Component { 32 | state = { 33 | isSnackbarOpen: false, 34 | }; 35 | 36 | componentDidMount() { 37 | const { getAllSales, getStock } = this.props; 38 | 39 | getAllSales(); 40 | getStock(); 41 | } 42 | 43 | componentWillReceiveProps(nextProps) { 44 | const { message, error } = nextProps.sale; 45 | 46 | if (message || error) { 47 | this.setState({ 48 | isSnackbarOpen: true, 49 | }); 50 | } 51 | } 52 | 53 | onCreateSale = (sale: Object): void => { 54 | const { shouldPrintReceipt } = sale; 55 | const { createSale, startPrint } = this.props; 56 | 57 | if (shouldPrintReceipt) { 58 | startPrint(sale); 59 | } 60 | 61 | createSale(sale); 62 | }; 63 | 64 | onEditSale = (saleEdited: Object): void => { 65 | const { editSale } = this.props; 66 | 67 | editSale(saleEdited); 68 | }; 69 | 70 | renderSnackbar = (sales: Object): Object => { 71 | const { isSnackbarOpen } = this.state; 72 | const { message, error } = sales; 73 | 74 | return ( 75 | this.setState({ isSnackbarOpen: false })} 77 | isOpen={isSnackbarOpen} 78 | message={message} 79 | error={error} 80 | /> 81 | ); 82 | }; 83 | 84 | render() { 85 | const { sale, stock } = this.props; 86 | 87 | return ( 88 | 89 | ( 100 | 104 | )} 105 | /> 106 | {this.renderSnackbar(sale)} 107 | 108 | ); 109 | } 110 | } 111 | 112 | const Creators = Object.assign({}, SaleCreators, StockCreators, PrintCreators); 113 | 114 | const mapDispatchToProps = dispatch => bindActionCreators(Creators, dispatch); 115 | 116 | const mapStateToProps = state => ({ 117 | stock: state.stock.data, 118 | sale: state.sale, 119 | }); 120 | 121 | export default connect(mapStateToProps, mapDispatchToProps)(Sales); 122 | --------------------------------------------------------------------------------