├── github ├── dana.png ├── ovo.png └── paypal.png ├── .firebaserc ├── public ├── favicon.ico ├── logo192.png ├── logo512.png ├── robots.txt ├── assets │ └── fonts │ │ ├── materialdesignicons-webfont.eot │ │ ├── materialdesignicons-webfont.ttf │ │ ├── materialdesignicons-webfont.woff │ │ └── materialdesignicons-webfont.woff2 ├── .htaccess ├── manifest.json └── index.html ├── src ├── assets │ ├── img │ │ ├── logo.png │ │ └── default.png │ └── style.css ├── global.js ├── setupTests.js ├── App.test.js ├── components │ ├── FullScreen.js │ ├── Loading.js │ ├── FormatNumber.js │ ├── Table.js │ └── DefaultScreen.js ├── views │ ├── Errors │ │ ├── ErrorDevice.js │ │ ├── Error404.js │ │ └── Error.js │ ├── Role │ │ ├── ViewRole.js │ │ ├── AddRole.js │ │ └── EditRole.js │ ├── Product │ │ └── ViewProduct.js │ ├── User │ │ └── ViewUser.js │ ├── Expense │ │ └── ViewExpense.js │ ├── Login.js │ ├── Supplier │ │ ├── AddSupplier.js │ │ └── EditSupplier.js │ ├── Purchase │ │ └── ViewPurchase.js │ └── Sales │ │ └── ViewSales.js ├── store │ ├── reducer │ │ ├── checkUserReducer.js │ │ ├── permissionReducer.js │ │ ├── registerReducer.js │ │ ├── rootReducer.js │ │ ├── loginReducer.js │ │ ├── stockReducer.js │ │ ├── settingReducer.js │ │ ├── reportReducer.js │ │ ├── expenseReducer.js │ │ ├── roleReducer.js │ │ ├── unitReducer.js │ │ ├── userReducer.js │ │ ├── salesReducer.js │ │ ├── categoryReducer.js │ │ ├── customerReducer.js │ │ ├── supplierReducer.js │ │ ├── purchaseReducer.js │ │ ├── discountReducer.js │ │ └── productReducer.js │ └── actions │ │ ├── PermissionAction.js │ │ ├── StockActions.js │ │ ├── SettingActions.js │ │ ├── AuthActions.js │ │ ├── PurchaseActions.js │ │ └── ReportActions.js ├── index.js ├── App.js └── serviceWorker.js ├── firebase.json ├── .gitignore ├── package.json ├── .firebase ├── hosting.cHVibGlj.cache └── hosting.YnVpbGQ.cache └── README.md /github/dana.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kasirkita/Kasir-Kita/HEAD/github/dana.png -------------------------------------------------------------------------------- /github/ovo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kasirkita/Kasir-Kita/HEAD/github/ovo.png -------------------------------------------------------------------------------- /.firebaserc: -------------------------------------------------------------------------------- 1 | { 2 | "projects": { 3 | "default": "kasir-kita-1190d" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /github/paypal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kasirkita/Kasir-Kita/HEAD/github/paypal.png -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kasirkita/Kasir-Kita/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kasirkita/Kasir-Kita/HEAD/public/logo192.png -------------------------------------------------------------------------------- /public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kasirkita/Kasir-Kita/HEAD/public/logo512.png -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /src/assets/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kasirkita/Kasir-Kita/HEAD/src/assets/img/logo.png -------------------------------------------------------------------------------- /src/assets/img/default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kasirkita/Kasir-Kita/HEAD/src/assets/img/default.png -------------------------------------------------------------------------------- /public/assets/fonts/materialdesignicons-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kasirkita/Kasir-Kita/HEAD/public/assets/fonts/materialdesignicons-webfont.eot -------------------------------------------------------------------------------- /public/assets/fonts/materialdesignicons-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kasirkita/Kasir-Kita/HEAD/public/assets/fonts/materialdesignicons-webfont.ttf -------------------------------------------------------------------------------- /public/assets/fonts/materialdesignicons-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kasirkita/Kasir-Kita/HEAD/public/assets/fonts/materialdesignicons-webfont.woff -------------------------------------------------------------------------------- /public/assets/fonts/materialdesignicons-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kasirkita/Kasir-Kita/HEAD/public/assets/fonts/materialdesignicons-webfont.woff2 -------------------------------------------------------------------------------- /src/global.js: -------------------------------------------------------------------------------- 1 | let url 2 | 3 | if (!process.env.NODE_ENV || process.env.NODE_ENV === 'development') { 4 | url = 'http://localhost:8000/api' 5 | } else { 6 | url = 'https://kasir-kita.herokuapp.com/api' 7 | } 8 | 9 | export { url } -------------------------------------------------------------------------------- /src/setupTests.js: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom/extend-expect'; 6 | -------------------------------------------------------------------------------- /public/.htaccess: -------------------------------------------------------------------------------- 1 | 2 | 3 | RewriteEngine On 4 | RewriteBase / 5 | RewriteRule ^index\.html$ - [L] 6 | RewriteCond %{REQUEST_FILENAME} !-f 7 | RewriteCond %{REQUEST_FILENAME} !-d 8 | RewriteCond %{REQUEST_FILENAME} !-l 9 | RewriteRule . /index.html [L] 10 | 11 | -------------------------------------------------------------------------------- /firebase.json: -------------------------------------------------------------------------------- 1 | { 2 | "hosting": { 3 | "public": "build", 4 | "ignore": [ 5 | "firebase.json", 6 | "**/.*", 7 | "**/node_modules/**" 8 | ], 9 | "rewrites": [ 10 | { 11 | "source": "**", 12 | "destination": "/index.html" 13 | } 14 | ] 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/App.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from '@testing-library/react'; 3 | import App from './App'; 4 | 5 | test('renders learn react link', () => { 6 | const { getByText } = render(); 7 | const linkElement = getByText(/learn react/i); 8 | expect(linkElement).toBeInTheDocument(); 9 | }); 10 | -------------------------------------------------------------------------------- /src/components/FullScreen.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | 3 | class FullScreen extends Component { 4 | render() { 5 | return ( 6 |
7 | {this.props.children} 8 |
9 | ) 10 | } 11 | } 12 | 13 | export default FullScreen 14 | -------------------------------------------------------------------------------- /src/components/Loading.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | 3 | class Loading extends Component { 4 | render() { 5 | return ( 6 |
7 | 8 |
9 | ) 10 | } 11 | } 12 | 13 | export default Loading 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /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 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /src/views/Errors/ErrorDevice.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | function ErrorDevice() { 4 | return ( 5 |
6 |
7 |
8 |
9 |

Mohon maaf, aplikasi nya hanya bisa dilihat di desktop saja

10 |

11 | :'( 12 |

13 |
14 |
15 |
16 |
17 | ) 18 | } 19 | 20 | export default ErrorDevice 21 | -------------------------------------------------------------------------------- /src/views/Errors/Error404.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { Link } from 'react-router-dom' 3 | 4 | class Error404 extends Component { 5 | render() { 6 | return ( 7 |
8 |
9 |

404 - Halaman tidak di temukan

10 |

Halaman yang anda cari hilang, atau tidak ditemukan

11 | 12 | Kembali ke rumah 13 | 14 |
15 |
16 | ) 17 | } 18 | } 19 | 20 | export default Error404 21 | -------------------------------------------------------------------------------- /src/store/reducer/checkUserReducer.js: -------------------------------------------------------------------------------- 1 | const initState = { 2 | fetching: false, 3 | fetched: false, 4 | userExists: null, 5 | error: null 6 | } 7 | 8 | const checkUserReducer = (state = initState, action) => { 9 | switch (action.type) { 10 | case 'CHECK_USER_PENDING': 11 | return { 12 | ...state, 13 | fetching: true 14 | } 15 | case 'CHECK_USER_SUCCESS': 16 | return { 17 | ...state, 18 | fetching: false, 19 | fetched: true, 20 | userExists: action.userExists 21 | } 22 | case 'CHECK_USER_FAILED': 23 | return { 24 | ...state, 25 | fetching: false, 26 | fetched: true, 27 | error: action.error 28 | } 29 | default: 30 | return state 31 | } 32 | } 33 | 34 | export default checkUserReducer -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | import * as serviceWorker from './serviceWorker'; 5 | import { Provider } from 'react-redux'; 6 | import { createStore } from 'redux'; 7 | import { applyMiddleware } from 'redux'; 8 | import thunk from 'redux-thunk'; 9 | import rootReducer from './store/reducer/rootReducer'; 10 | import { ToastProvider } from 'react-toast-notifications' 11 | 12 | const store = createStore(rootReducer, applyMiddleware(thunk)) 13 | 14 | ReactDOM.render( 15 | 16 | 17 | 18 | 19 | , 20 | document.getElementById('root')); 21 | 22 | // If you want your app to work offline and load faster, you can change 23 | // unregister() to register() below. Note this comes with some pitfalls. 24 | // Learn more about service workers: https://bit.ly/CRA-PWA 25 | serviceWorker.unregister(); 26 | -------------------------------------------------------------------------------- /src/views/Errors/Error.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { Link } from 'react-router-dom' 3 | 4 | class Error extends Component { 5 | render() { 6 | return ( 7 |
8 |
9 |

{ this.props.code } - { this.props.title }

10 |

{this.props.message}

11 |
12 | { 13 | !this.props.connection && ( 14 | 15 | Kembali ke rumah 16 | 17 | ) 18 | } 19 |
20 |
21 | ) 22 | } 23 | } 24 | 25 | export default Error 26 | -------------------------------------------------------------------------------- /src/store/reducer/permissionReducer.js: -------------------------------------------------------------------------------- 1 | const initState = { 2 | permissions: [], 3 | fetching: false, 4 | fetched: false, 5 | error: null, 6 | type: null 7 | } 8 | 9 | const permissionReducer = (state = initState, action) => { 10 | switch (action.type) { 11 | case 'LIST_PERMISSION_PENDING': 12 | return { 13 | ...state, 14 | fetching: true, 15 | error: null 16 | } 17 | case 'LIST_PERMISSION_SUCCESS': 18 | 19 | return { 20 | ...state, 21 | fetching: false, 22 | fetched: true, 23 | permissions: action.data, 24 | success: true, 25 | type: 'list' 26 | } 27 | 28 | case 'LIST_PERMISSION_FAILED': 29 | return { 30 | ...state, 31 | fetching: false, 32 | fetched: true, 33 | error: action.error, 34 | message: action.error.data.message, 35 | success: false, 36 | type: 'list' 37 | } 38 | default: 39 | return state 40 | } 41 | } 42 | 43 | export default permissionReducer -------------------------------------------------------------------------------- /src/components/FormatNumber.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import NumberFormat from 'react-number-format' 3 | 4 | class FormatNumber extends React.Component { 5 | 6 | handleChangeNumber = (e) => { 7 | const { handleChangeNumber = false } = this.props 8 | 9 | if (handleChangeNumber) { 10 | handleChangeNumber(e) 11 | } 12 | } 13 | 14 | render() { 15 | const { value, validate, name, type = false } = this.props 16 | return this.handleChangeNumber(e)} value={value} placeholder={`${sessionStorage.getItem('currency') !== 'null' ? sessionStorage.getItem('currency') : '' } ${sessionStorage.getItem('decimal_separator') !== 'null' ? `0${sessionStorage.getItem('decimal_separator')}0` : '00' } `} /> 17 | } 18 | } 19 | export default FormatNumber 20 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { BrowserRouter, Route, Switch } from 'react-router-dom' 3 | import routes from './routes' 4 | import './assets/style.css' 5 | import Error404 from './views/Errors/Error404' 6 | import { isMobile } from 'react-device-detect' 7 | import ErrorDevice from './views/Errors/ErrorDevice' 8 | 9 | class App extends Component { 10 | render() { 11 | 12 | if (isMobile) 13 | return ( 14 | 15 | ) 16 | return ( 17 | 18 | 19 | { 20 | routes.map((route, index) => { 21 | return ( { 26 | return ( 27 | 28 | 29 | 30 | ); 31 | }} 32 | /> 33 | ) 34 | }) 35 | } 36 | 37 | 38 | 39 | ) 40 | } 41 | } 42 | 43 | export default App 44 | 45 | -------------------------------------------------------------------------------- /src/store/reducer/registerReducer.js: -------------------------------------------------------------------------------- 1 | const initState = { 2 | fetching: false, 3 | fetched: false, 4 | message: null, 5 | data: null, 6 | error: null, 7 | success: null 8 | } 9 | 10 | const registerReducer = (state = initState, action) => { 11 | switch (action.type) { 12 | case 'REGISTER_PENDING': 13 | return { 14 | ...state, 15 | fetching: true 16 | } 17 | case 'REGISTER_SUCCESS': 18 | sessionStorage.setItem('token', action.token) 19 | sessionStorage.setItem('name', action.data.name) 20 | sessionStorage.setItem('avatar', action.data.avatar) 21 | sessionStorage.setItem('email', action.data.email) 22 | return { 23 | ...state, 24 | fetching: false, 25 | fetched: true, 26 | data: action.data, 27 | message: action.message, 28 | success: true 29 | } 30 | case 'REGISTER_FAILED': 31 | return { 32 | ...state, 33 | fetching: false, 34 | fetched: true, 35 | error: action.error, 36 | message: action.error.data.message, 37 | success: false 38 | } 39 | default: 40 | return state 41 | } 42 | } 43 | 44 | export default registerReducer -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "schedio", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^4.2.4", 7 | "@testing-library/react": "^9.3.2", 8 | "@testing-library/user-event": "^7.1.2", 9 | "axios": "^0.19.2", 10 | "chart.js": "^2.9.3", 11 | "js-file-download": "^0.4.10", 12 | "moment": "^2.24.0", 13 | "react": "^16.12.0", 14 | "react-barcode": "^1.4.0", 15 | "react-bootstrap4-modal": "^1.7.4", 16 | "react-chartjs-2": "^2.9.0", 17 | "react-datepicker": "^2.12.0", 18 | "react-device-detect": "^1.11.14", 19 | "react-dom": "^16.12.0", 20 | "react-dropzone": "^10.2.1", 21 | "react-number-format": "^4.4.1", 22 | "react-redux": "^7.2.0", 23 | "react-router-dom": "^5.1.2", 24 | "react-scripts": "3.3.1", 25 | "react-select": "^3.0.8", 26 | "react-toast-notifications": "^2.4.0", 27 | "redux": "^4.0.5", 28 | "redux-thunk": "^2.3.0" 29 | }, 30 | "scripts": { 31 | "start": "react-scripts start", 32 | "build": "react-scripts build", 33 | "test": "react-scripts test", 34 | "eject": "react-scripts eject" 35 | }, 36 | "eslintConfig": { 37 | "extends": "react-app" 38 | }, 39 | "browserslist": { 40 | "production": [ 41 | ">0.2%", 42 | "not dead", 43 | "not op_mini all" 44 | ], 45 | "development": [ 46 | "last 1 chrome version", 47 | "last 1 firefox version", 48 | "last 1 safari version" 49 | ] 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/store/actions/PermissionAction.js: -------------------------------------------------------------------------------- 1 | import { url } from "../../global" 2 | import Axios from "axios" 3 | 4 | const getListPermission = () => { 5 | return (dispatch, getState) => { 6 | 7 | dispatch({ 8 | type: 'LIST_PERMISSION_PENDING', 9 | }) 10 | 11 | Axios.get(`${url}/permission/list`, { 12 | headers: { 13 | Authorization: `Bearer ${sessionStorage.getItem('token')}` 14 | } 15 | }).then(res => { 16 | dispatch({ 17 | type: 'LIST_PERMISSION_SUCCESS', 18 | data: res.data.data, 19 | success: true 20 | }) 21 | }).catch(error => { 22 | if (!error.response) { 23 | dispatch({ 24 | type: 'LIST_PERMISSION_FAILED', 25 | error: { 26 | status: null, 27 | connection: true, 28 | statusText: 'Koneksi Terputus', 29 | data: { 30 | message: 'Silahkan periksa koneksi backend, lihat tutorial di sini https://github.com/kasirkita/Kasir-Kita' 31 | } 32 | } 33 | }) 34 | 35 | } else { 36 | 37 | dispatch({ 38 | type: 'LIST_PERMISSION_FAILED', 39 | error: error.response, 40 | message: error.response.data.message 41 | }) 42 | } 43 | }) 44 | } 45 | } 46 | 47 | export { getListPermission } -------------------------------------------------------------------------------- /src/store/reducer/rootReducer.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from "redux" 2 | import checkUserReducer from "./checkUserReducer" 3 | import registerReducer from "./registerReducer" 4 | import loginReducer from "./loginReducer" 5 | import productReducer from "./productReducer" 6 | import categoryReducer from "./categoryReducer" 7 | import unitReducer from "./unitReducer" 8 | import roleReducer from "./roleReducer" 9 | import permissionReducer from "./permissionReducer" 10 | import userReducer from "./userReducer" 11 | import customerReducer from "./customerReducer" 12 | import supplierReducer from "./supplierReducer" 13 | import settingReducer from "./settingReducer" 14 | import salesReducer from "./salesReducer" 15 | import discountReducer from "./discountReducer" 16 | import purchaseReducer from "./purchaseReducer" 17 | import expenseReducer from "./expenseReducer" 18 | import stockReducer from "./stockReducer" 19 | import reportReducer from "./reportReducer" 20 | 21 | const rootReducer = combineReducers({ 22 | checkUser: checkUserReducer, 23 | register: registerReducer, 24 | login: loginReducer, 25 | product: productReducer, 26 | category: categoryReducer, 27 | unit: unitReducer, 28 | role: roleReducer, 29 | permission: permissionReducer, 30 | user: userReducer, 31 | customer: customerReducer, 32 | supplier: supplierReducer, 33 | setting: settingReducer, 34 | sales: salesReducer, 35 | discount: discountReducer, 36 | purchase: purchaseReducer, 37 | expense: expenseReducer, 38 | stock: stockReducer, 39 | report: reportReducer 40 | }) 41 | 42 | export default rootReducer -------------------------------------------------------------------------------- /src/store/reducer/loginReducer.js: -------------------------------------------------------------------------------- 1 | const initState = { 2 | fetching: false, 3 | fetched: false, 4 | message: null, 5 | data: null, 6 | error: null, 7 | success: null, 8 | redirect: '/' 9 | } 10 | 11 | const loginReducer = (state = initState, action) => { 12 | switch (action.type) { 13 | case 'LOGIN_PENDING': 14 | return { 15 | ...state, 16 | fetching: true, 17 | fetched: false 18 | } 19 | case 'LOGIN_SUCCESS': 20 | sessionStorage.setItem('token', action.token) 21 | sessionStorage.setItem('name', action.data.name) 22 | sessionStorage.setItem('avatar', action.data.avatar) 23 | sessionStorage.setItem('email', action.data.email) 24 | sessionStorage.setItem('permissions', JSON.stringify(action.permissions)) 25 | return { 26 | ...state, 27 | fetching: false, 28 | fetched: true, 29 | data: action.data, 30 | message: action.message, 31 | success: true, 32 | error: null, 33 | redirect: `/${action.redirect.type}` 34 | } 35 | case 'LOGIN_FAILED': 36 | return { 37 | ...state, 38 | fetching: false, 39 | fetched: true, 40 | error: action.error, 41 | message: action.error.data.message, 42 | success: false 43 | } 44 | default: 45 | return state 46 | } 47 | } 48 | 49 | export default loginReducer -------------------------------------------------------------------------------- /.firebase/hosting.cHVibGlj.cache: -------------------------------------------------------------------------------- 1 | favicon.ico,1581162026634,a08fa4488c3ecef62d9effd03b3a989929bdcbecf5e905941f9034a15bd3dba3 2 | logo192.png,1581162026635,caff018b7f1e8fd481eb1c50d75b0ef236bcd5078b1d15c8bb348453fee30293 3 | logo512.png,1581162026636,191fc21360b4ccfb1cda11a1efb97f489ed22672ca83f4064316802bbfdd750e 4 | manifest.json,1581162026636,341d52628782f8ac9290bbfc43298afccb47b7cbfcee146ae30cf0f46bc30900 5 | robots.txt,1581162026637,391d14b3c2f8c9143a27a28c7399585142228d4d1bdbe2c87ac946de411fa9a2 6 | assets/css/bootstrap.min.css,1581164928910,440f8e33b94eaccd2941aaa9cdd915350424b093984e8de8fd6fce2091cafb39 7 | assets/css/materialdesignicons.min.css,1580828340000,3affd4812b5caca1b35510c85751a3ddb0727e3b8174637fa0d448ea067e5bba 8 | assets/fonts/materialdesignicons-webfont.eot,1580828340000,4a87f1aa6d5439f247add95353dc64d888a3ea2d0a539d543d6e27989894205f 9 | assets/fonts/materialdesignicons-webfont.ttf,1580828340000,a42ef9e82643cad0a623e2fe4769b13d3f4dadf089b3ad6da55893b3a97e106f 10 | assets/fonts/materialdesignicons-webfont.woff,1580828340000,0bfe1621d69ffba8eed14ec77fc9bfabc68ef29cb7caf79b33560fcce13a878f 11 | assets/fonts/materialdesignicons-webfont.woff2,1580828340000,62984f6906f91fcb64e77b9a5235253375a4f106e6dec1ee501a0e59b032f2c1 12 | assets/js/bootstrap.min.js,1581164942970,745cfe3216675d98b2c01d26e1ce1a5838c09a9bf0075f7d3a4bbbde1f750309 13 | assets/js/jquery-3.4.1.slim.min.js,1581162228317,d9beae6bd087c5b78316cc58ac27ae9c1d0517b8335d4b29ff8a36e3c8f1ea11 14 | assets/js/popper.min.js,1581162248414,dd91bc46140a9a8d025ad3e9dfe640f11adf34d3844e8e4f31495ac5669b3791 15 | index.html,1583645083013,81a968975329521d7fcbbfb69c72cfb9bdeb0e882f19802b9948bba067399dec 16 | -------------------------------------------------------------------------------- /src/components/Table.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | class Table extends React.Component { 4 | render() { 5 | 6 | const {theads, ordering, handleSorting} = this.props; 7 | 8 | const tHead = theads.map((head, index) => { 9 | return ( 10 | 11 | {head.value} 12 | { 13 | head.sortable && ( 14 |
15 | { 16 | ordering.type === head.name ? 17 | () 18 | : 19 | () 20 | } 21 | 22 |
23 | ) 24 | } 25 | 26 | ) 27 | }) 28 | 29 | return ( 30 | 31 | 32 | 33 | {tHead} 34 | 35 | 36 | 37 | {this.props.children} 38 | 39 |
40 | ) 41 | } 42 | } 43 | 44 | export default Table; -------------------------------------------------------------------------------- /src/store/reducer/stockReducer.js: -------------------------------------------------------------------------------- 1 | const initState = { 2 | fetching: false, 3 | fetched: false, 4 | message: null, 5 | data: null, 6 | error: null, 7 | success: null, 8 | type: null, 9 | stock: null 10 | } 11 | 12 | const stockReducer = (state = initState, action) => { 13 | switch (action.type) { 14 | case 'FETCH_STOCK_PENDING': 15 | return { 16 | ...state, 17 | fetching: true, 18 | error: null 19 | } 20 | case 'FETCH_STOCK_SUCCESS': 21 | 22 | return { 23 | ...state, 24 | fetching: false, 25 | fetched: true, 26 | data: action.data, 27 | success: true, 28 | type: 'fetch' 29 | } 30 | 31 | case 'FETCH_STOCK_FAILED': 32 | return { 33 | ...state, 34 | fetching: false, 35 | fetched: true, 36 | error: action.error, 37 | message: action.error.data.message, 38 | success: false, 39 | type: 'fetch' 40 | } 41 | case 'SAVE_STOCK_PENDING': 42 | return { 43 | ...state, 44 | fetching: true, 45 | } 46 | case 'SAVE_STOCK_SUCCESS': 47 | 48 | return { 49 | ...state, 50 | fetching: false, 51 | fetched: true, 52 | message: action.message, 53 | carts: action.data, 54 | type: 'save', 55 | success: true 56 | } 57 | 58 | case 'SAVE_STOCK_FAILED': 59 | return { 60 | ...state, 61 | fetching: false, 62 | fetched: true, 63 | error: action.error, 64 | message: action.message, 65 | type: 'save', 66 | success: false 67 | } 68 | default: 69 | return state 70 | } 71 | } 72 | 73 | export default stockReducer -------------------------------------------------------------------------------- /src/store/reducer/settingReducer.js: -------------------------------------------------------------------------------- 1 | const initState = { 2 | fetching: false, 3 | fetched: false, 4 | message: null, 5 | data: null, 6 | error: null, 7 | success: null, 8 | type: null, 9 | setting: null, 10 | } 11 | 12 | const settingReducer = (state = initState, action) => { 13 | switch (action.type) { 14 | case 'SAVE_SETTING_PENDING': 15 | return { 16 | ...state, 17 | fetching: true, 18 | } 19 | case 'SAVE_SETTING_SUCCESS': 20 | 21 | return { 22 | ...state, 23 | fetching: false, 24 | fetched: true, 25 | message: action.message, 26 | type: 'save', 27 | success: true 28 | } 29 | 30 | case 'SAVE_SETTING_FAILED': 31 | return { 32 | ...state, 33 | fetching: false, 34 | fetched: true, 35 | error: action.error, 36 | message: action.message, 37 | type: 'save', 38 | success: false 39 | } 40 | case 'GET_SETTING_PENDING': 41 | return { 42 | ...state, 43 | fetching: true, 44 | error: null 45 | } 46 | case 'GET_SETTING_SUCCESS': 47 | 48 | return { 49 | ...state, 50 | fetching: false, 51 | fetched: true, 52 | type: 'get', 53 | setting: action.data, 54 | success: true 55 | } 56 | 57 | case 'GET_SETTING_FAILED': 58 | return { 59 | ...state, 60 | fetching: false, 61 | fetched: true, 62 | error: action.error, 63 | message: action.message, 64 | type: 'get', 65 | success: false 66 | } 67 | case 'UPDATE_SETTING_PENDING': 68 | return { 69 | ...state, 70 | fetching: true, 71 | } 72 | default: 73 | return state 74 | } 75 | } 76 | 77 | export default settingReducer -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Kasir Kita v.2.0 2 | Kasir Kita, aplikasi POS gratis dibuat menggunakan Reactjs 3 | 4 | ## Cara menginstall 5 | 6 | ### Step 1 Menginstall Package dan Mengatur URL 7 | Silahkan clone repository ini, atau download, setelah itu gunakan perintah `yarn install` atau `npm install`. Setelah itu silahkan buka file `global.js` yang ada pada directory `./src/global.js` lalu ubah url ke url aplikasi backend, untuk backend nya ada di repository ini `https://github.com/kasirkita/Kasir-Kita-BE` setelah itu gunakan perintah `yarn start` atau `npm start` untuk menyalakan servis dalam model development 8 | 9 | ### Step 2 Mengatur Node.js untuk servis printer 10 | Silahkan buka kembali file `global.js` yang ada pada directory `./src/global.js` lalu ubah url nya sesuai dengan url yang kalian punya, untuk mendownload servis node.js printer ada di repository `https://github.com/kasirkita/Kasir-Kita-Printer` 11 | 12 | ### Step 3 Build aplikasi 13 | Silahkan ketikan perinta `yarn build` atau `npm build` lalu akan otomatis hasil build ada di folder `./build` silahkan untuk melokasikan folder ke http servis yang kalian miliki 14 | 15 | ## Lisensi 16 | Aplikasi ini 100% Gratis, tetapi jika menginginkan penambahan fitur dan atau cara menjalakan aplikasi silahkan chat melalui whatsapp 089611081675 17 | Adapun ketentuan pada penggunaan aplikasi ini adalah 18 | 19 | - Tidak boleh dikomersilkan dalam bentuk apapun tanpa sepengetahuan author 20 | - Tidak boleh menyalin dan menyebarkan ulang kode tanpa sepengetahuan author 21 | - Tidak boleh menghapus credit pada aplikasi 22 | 23 | ## Dukungan 24 | Silahkan berkonstribusi atau mengirimkan issue untuk pengembangan, silahkan gunakan opsi fork dan lakukan PR untuk menjadi kontributor 25 | 26 | Jika merasa aplikasi ini berguna, silakan melakukan dukungan dengan cara berdonasi seikhlasnya dibawah ini 27 | 28 | 29 | 30 | 31 | 34 | 37 | 40 | 41 | 42 |
32 | Dana 33 | 35 | Ovo 36 | 38 | Paypal 39 |
43 | 44 | 45 | ## Kontributor 46 | 47 | - Razaq Hakim 48 | - Yulianto Saparudin 49 | - Rega Rivaldi 50 | - Muhammad Irfan 51 | -------------------------------------------------------------------------------- /src/views/Role/ViewRole.js: -------------------------------------------------------------------------------- 1 | import React, { Component, Fragment } from 'react' 2 | import { connect } from 'react-redux' 3 | import { Link } from 'react-router-dom' 4 | import { getRole } from '../../store/actions/RoleActions' 5 | import { getListPermission } from '../../store/actions/PermissionAction' 6 | 7 | class ViewRole extends Component { 8 | 9 | async componentDidMount() { 10 | await this.props.getListPermission() 11 | await this.props.getRole(this.props.match.params.id) 12 | } 13 | 14 | render() { 15 | const { role, permissions } = this.props 16 | return ( 17 | 18 |
19 |
20 |
21 |
22 |

{ role && role.name }

23 |
24 |
25 | Kembali 26 |
27 |
28 |
29 |
30 |
31 |

Izin

32 |
33 | { 34 | permissions && permissions.map(permission => { 35 | return ( 36 |
37 | {permission.name} {role && role.perms[permission.slug] ? : } 38 |
39 | ) 40 | }) 41 | } 42 |
43 | 44 | 45 | 46 |
47 | 48 |
49 |
50 | ) 51 | } 52 | } 53 | 54 | const mapStateToProps = state => { 55 | return { 56 | ...state, 57 | role: state.role.role, 58 | permissions: state.permission.permissions 59 | } 60 | } 61 | 62 | const mapDispatchToProps = dispatch => { 63 | return { 64 | getRole: id => dispatch(getRole(id)), 65 | getListPermission: () => dispatch(getListPermission()) 66 | } 67 | } 68 | 69 | export default connect(mapStateToProps, mapDispatchToProps)(ViewRole) 70 | -------------------------------------------------------------------------------- /src/views/Product/ViewProduct.js: -------------------------------------------------------------------------------- 1 | import React, { Component, Fragment } from 'react' 2 | import { Link } from 'react-router-dom' 3 | import { getProduct } from '../../store/actions/ProductActions' 4 | import { connect } from 'react-redux' 5 | 6 | class ViewProduct extends Component { 7 | componentDidMount() { 8 | this.props.getProduct(this.props.match.params.id) 9 | } 10 | render() { 11 | const { product } = this.props 12 | return ( 13 | 14 |
15 |
16 |
17 |
18 |

{ product && product.name }

19 |
20 |
21 | Kembali 22 |
23 |
24 |
25 |
26 |
27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 |
Kode{product && product.code}Harga Jual{ product && product.price_formatted }
Harga Beli{ product && product.cost_formatted }Harga Grosir{ product && product.wholesale_formatted }
Kategori{ product && product.category && product.category.name }Stok{ product && product.qty && product.qty.amount }/{ product && product.unit && product.unit.name }
50 |
51 | 52 |
53 |
54 | ) 55 | } 56 | } 57 | 58 | const mapStateToProps = state => { 59 | return { 60 | ...state, 61 | product: state.product.product 62 | } 63 | } 64 | 65 | const mapDispatchToProps = dispatch => { 66 | return { 67 | getProduct: id => dispatch(getProduct(id)) 68 | } 69 | } 70 | 71 | export default connect(mapStateToProps, mapDispatchToProps)(ViewProduct) 72 | -------------------------------------------------------------------------------- /.firebase/hosting.YnVpbGQ.cache: -------------------------------------------------------------------------------- 1 | asset-manifest.json,1586165366288,032e7ad784a4609aa78fd6cc193bcfcbe1ed7877eef4282ffe16f5af14624ba7 2 | favicon.ico,1586165345474,34c008d977103c880e85aa3cf8dd3a5bb96ae9fdc07faf5444efd2695dfee8b3 3 | logo192.png,1586165345474,32f6e4a3a8ad267f2e9784f9b295d5ed2f3fb4194082444fa6a6cd15015c5608 4 | index.html,1586165366288,7a3fdc9f8c8c204f104a0dfb80fd9e2ab7e10b0da3426ad26c2e5cb2efafbd46 5 | manifest.json,1586165345475,341d52628782f8ac9290bbfc43298afccb47b7cbfcee146ae30cf0f46bc30900 6 | logo512.png,1586165345475,b085731885e2f2b7691bfc19ddb8ef3b0131e76df3f02cde7bc6511d1b682387 7 | robots.txt,1586165345476,391d14b3c2f8c9143a27a28c7399585142228d4d1bdbe2c87ac946de411fa9a2 8 | precache-manifest.18ea3eb43f45fb9a9c41a0b6957849c3.js,1586165366288,2b41cba26c1fc47c797c9ad386b7aefa29a2ecdaa9415b420c665fd4cdd4cd5b 9 | service-worker.js,1586165366288,11aca0ee129c80598ee52bbbeaae4a47b20e6b9055ceed4707cc664c9f70ed6f 10 | assets/js/bootstrap.min.js,1586165345471,6b5541588852d13957fd3757fa59079eff7af41e55b4002fadc37bda4a9099c2 11 | assets/js/popper.min.js,1586165345473,dd91bc46140a9a8d025ad3e9dfe640f11adf34d3844e8e4f31495ac5669b3791 12 | static/css/2.b90ce945.chunk.css,1586165366317,ebea2626a607164ac32d3d655e0a98eeba1f4170bf73d7206989a485ecd2ab01 13 | static/css/main.d3eb9bb3.chunk.css,1586165366290,add620f948e3797f158d1f4bf7a398e16da60603e3c730605c2376ccd9d449a9 14 | static/css/main.d3eb9bb3.chunk.css.map,1586165366316,f2a6791fd7a7a38678fb30362e450a1c72a74ddb99db5423003518dc21a12ba8 15 | static/css/2.b90ce945.chunk.css.map,1586165366316,7c47ca1122ec63c2c190263c439952407d4824945f1c7d5bf26f364149a0461b 16 | static/js/2.2a2f6937.chunk.js.LICENSE.txt,1586165366316,2c86e8128f3301795c0ae002bdcf998df9147aa14c080131acd8677ad76fe64d 17 | static/js/runtime-main.0a430e43.js,1586165366316,28283286d5f36a71b96dc826fc5dd068df005bea4b9684e1114694bcdc61eeec 18 | static/js/runtime-main.0a430e43.js.map,1586165366316,d8f5afbcfda42e1358a5c4df1be8a18712921da3027692f3608aa7d087d33317 19 | assets/js/jquery-3.4.1.slim.min.js,1586165345472,d9beae6bd087c5b78316cc58ac27ae9c1d0517b8335d4b29ff8a36e3c8f1ea11 20 | assets/css/bootstrap.min.css,1586165345431,440f8e33b94eaccd2941aaa9cdd915350424b093984e8de8fd6fce2091cafb39 21 | assets/css/materialdesignicons.min.css,1586165345436,3affd4812b5caca1b35510c85751a3ddb0727e3b8174637fa0d448ea067e5bba 22 | static/js/main.7e2a8d0e.chunk.js,1586165366291,d553b153e72823c10067e8b439f5a89d72f9bab4704ca3a75df7a123dd23b796 23 | assets/fonts/materialdesignicons-webfont.woff2,1586165345468,62984f6906f91fcb64e77b9a5235253375a4f106e6dec1ee501a0e59b032f2c1 24 | assets/fonts/materialdesignicons-webfont.woff,1586165345463,0bfe1621d69ffba8eed14ec77fc9bfabc68ef29cb7caf79b33560fcce13a878f 25 | static/js/2.2a2f6937.chunk.js,1586165366316,d5821b499b94cff303435425386da3d3b0e1d9d421eb5246a080a3d63a30db6f 26 | assets/fonts/materialdesignicons-webfont.eot,1586165345448,4a87f1aa6d5439f247add95353dc64d888a3ea2d0a539d543d6e27989894205f 27 | assets/fonts/materialdesignicons-webfont.ttf,1586165345456,a42ef9e82643cad0a623e2fe4769b13d3f4dadf089b3ad6da55893b3a97e106f 28 | static/js/main.7e2a8d0e.chunk.js.map,1586165366317,64992992a6b10a192878a60bd81ed01b204e2942dcb7f58eb015ce444ccd29e2 29 | static/js/2.2a2f6937.chunk.js.map,1586165366317,7b9e25abf4f5b715bb56de949a306e12a7e297c186878d41615a6d7577619e2e 30 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | 28 | 29 | 30 | 31 | 32 | Kasir Kita 33 | 34 | 35 | 36 |
37 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 55 | 56 | 57 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /src/views/User/ViewUser.js: -------------------------------------------------------------------------------- 1 | import React, { Component, Fragment } from 'react' 2 | import { connect } from 'react-redux' 3 | import { Link } from 'react-router-dom' 4 | import { getUser } from '../../store/actions/UserActions' 5 | 6 | class ViewUser extends Component { 7 | 8 | async componentDidMount() { 9 | await this.props.getUser(this.props.match.params.id) 10 | } 11 | 12 | render() { 13 | const { user } = this.props 14 | return ( 15 | 16 |
17 |
18 |
19 |
20 |

{ user && user.name }

21 |
22 |
23 | Kembali 24 |
25 |
26 |
27 |
28 |
29 |
30 | 31 |
32 | {user 33 |
34 |
35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 |
Nama:{user && user.name}
Email:{user && user.email}Nomor Telfon:{user && user.phone_number}
Peranan:{user && user.role && user.role.name }Tempat tanggal lahir:{user && user.place_of_birth}, {user && user.date_of_birth_formatted}
Alamat:{user && user.address}
59 |
60 |
61 |
62 | 63 |
64 |
65 | ) 66 | } 67 | } 68 | 69 | const mapStateToProps = state => { 70 | return { 71 | ...state, 72 | user: state.user.user, 73 | } 74 | } 75 | 76 | const mapDispatchToProps = dispatch => { 77 | return { 78 | getUser: id => dispatch(getUser(id)), 79 | } 80 | } 81 | 82 | export default connect(mapStateToProps, mapDispatchToProps)(ViewUser) 83 | -------------------------------------------------------------------------------- /src/store/reducer/reportReducer.js: -------------------------------------------------------------------------------- 1 | const initState = { 2 | fetching: false, 3 | fetched: false, 4 | message: null, 5 | data: null, 6 | error: null, 7 | success: null, 8 | type: null, 9 | } 10 | 11 | const reportReducer = (state = initState, action) => { 12 | switch (action.type) { 13 | case 'FETCH_SALES_REPORT_PENDING': 14 | return { 15 | ...state, 16 | fetching: true, 17 | error: null 18 | } 19 | case 'FETCH_SALES_REPORT_SUCCESS': 20 | 21 | return { 22 | ...state, 23 | fetching: false, 24 | fetched: true, 25 | data: action.data, 26 | success: true, 27 | type: 'fetch' 28 | } 29 | 30 | case 'FETCH_SALES_REPORT_FAILED': 31 | return { 32 | ...state, 33 | fetching: false, 34 | fetched: true, 35 | error: action.error, 36 | message: action.error.data.message, 37 | success: false, 38 | type: 'fetch' 39 | } 40 | case 'FETCH_PURCHASE_REPORT_PENDING': 41 | return { 42 | ...state, 43 | fetching: true, 44 | error: null 45 | } 46 | case 'FETCH_PURCHASE_REPORT_SUCCESS': 47 | 48 | return { 49 | ...state, 50 | fetching: false, 51 | fetched: true, 52 | data: action.data, 53 | success: true, 54 | type: 'fetch' 55 | } 56 | 57 | case 'FETCH_PURCHASE_REPORT_FAILED': 58 | return { 59 | ...state, 60 | fetching: false, 61 | fetched: true, 62 | error: action.error, 63 | message: action.error.data.message, 64 | success: false, 65 | type: 'fetch' 66 | } 67 | case 'FETCH_EXPENSE_REPORT_PENDING': 68 | return { 69 | ...state, 70 | fetching: true, 71 | error: null 72 | } 73 | case 'FETCH_EXPENSE_REPORT_SUCCESS': 74 | 75 | return { 76 | ...state, 77 | fetching: false, 78 | fetched: true, 79 | data: action.data, 80 | success: true, 81 | type: 'fetch' 82 | } 83 | 84 | case 'FETCH_EXPENSE_REPORT_FAILED': 85 | return { 86 | ...state, 87 | fetching: false, 88 | fetched: true, 89 | error: action.error, 90 | message: action.error.data.message, 91 | success: false, 92 | type: 'fetch' 93 | } 94 | 95 | case 'FETCH_STOCK_REPORT_PENDING': 96 | return { 97 | ...state, 98 | fetching: true, 99 | error: null 100 | } 101 | case 'FETCH_STOCK_REPORT_SUCCESS': 102 | 103 | return { 104 | ...state, 105 | fetching: false, 106 | fetched: true, 107 | data: action.data, 108 | success: true, 109 | type: 'fetch' 110 | } 111 | 112 | case 'FETCH_STOCK_REPORT_FAILED': 113 | return { 114 | ...state, 115 | fetching: false, 116 | fetched: true, 117 | error: action.error, 118 | message: action.error.data.message, 119 | success: false, 120 | type: 'fetch' 121 | } 122 | default: 123 | return state 124 | } 125 | } 126 | 127 | export default reportReducer -------------------------------------------------------------------------------- /src/store/actions/StockActions.js: -------------------------------------------------------------------------------- 1 | import Axios from 'axios' 2 | import { url } from '../../global' 3 | import moment from 'moment' 4 | 5 | const fetchStock = (params) => { 6 | return (dispatch, getState) => { 7 | 8 | const { 9 | page, 10 | perpage, 11 | keyword, 12 | ordering, 13 | product_id, 14 | start_date, 15 | end_date 16 | } = params 17 | 18 | dispatch({ 19 | type: 'FETCH_STOCK_PENDING' 20 | }) 21 | 22 | Axios.get(`${url}/stock`, { 23 | params: { 24 | page, 25 | perpage, 26 | keyword, 27 | ordering, 28 | product_id, 29 | start_date: moment(start_date).format('YYYY-MM-DD'), 30 | end_date: moment(end_date).format('YYYY-MM-DD') 31 | }, 32 | headers: { 33 | Authorization: `Bearer ${sessionStorage.getItem('token')}` 34 | } 35 | }).then(res => { 36 | 37 | dispatch({ 38 | type: 'FETCH_STOCK_SUCCESS', 39 | data: res.data.data, 40 | selected: res.data.selected 41 | }) 42 | 43 | }).catch(error => { 44 | 45 | if (!error.response) { 46 | dispatch({ 47 | type: 'FETCH_STOCK_FAILED', 48 | error: { 49 | status: null, 50 | connection: true, 51 | statusText: 'Koneksi Terputus', 52 | data: { 53 | message: 'Silahkan periksa koneksi backend, lihat tutorial di sini https://github.com/kasirkita/Kasir-Kita' 54 | } 55 | } 56 | }) 57 | 58 | } else { 59 | 60 | dispatch({ 61 | type: 'FETCH_STOCK_FAILED', 62 | error: error.response 63 | }) 64 | } 65 | 66 | }) 67 | } 68 | } 69 | 70 | 71 | const saveStock = (data) => { 72 | return (dispatch, getState) => { 73 | 74 | const { 75 | product_id, 76 | real_stock, 77 | description 78 | } = data 79 | 80 | dispatch({ 81 | type: 'SAVE_STOCK_PENDING' 82 | }) 83 | 84 | Axios.post(`${url}/stock`, { 85 | product_id, 86 | real_stock, 87 | description 88 | }, 89 | { 90 | headers: { 91 | Authorization: `Bearer ${sessionStorage.getItem('token')}` 92 | } 93 | }).then(res => { 94 | 95 | dispatch({ 96 | type: 'SAVE_STOCK_SUCCESS', 97 | data: res.data.data, 98 | message: res.data.message 99 | }) 100 | 101 | }).catch(error => { 102 | 103 | if (!error.response) { 104 | dispatch({ 105 | type: 'SAVE_STOCK_FAILED', 106 | error: { 107 | status: null, 108 | connection: true, 109 | statusText: 'Koneksi Terputus', 110 | data: { 111 | message: 'Silahkan periksa koneksi backend, lihat tutorial di sini https://github.com/kasirkita/Kasir-Kita' 112 | } 113 | } 114 | }) 115 | 116 | } else { 117 | 118 | dispatch({ 119 | type: 'SAVE_STOCK_FAILED', 120 | error: error.response, 121 | message: error.response.data.message 122 | }) 123 | } 124 | 125 | }) 126 | } 127 | } 128 | 129 | export { fetchStock, saveStock } -------------------------------------------------------------------------------- /src/store/reducer/expenseReducer.js: -------------------------------------------------------------------------------- 1 | const initState = { 2 | fetching: false, 3 | fetched: false, 4 | message: null, 5 | data: null, 6 | error: null, 7 | success: null, 8 | type: null, 9 | expense: null, 10 | } 11 | 12 | const expenseReducer = (state = initState, action) => { 13 | switch (action.type) { 14 | case 'FETCH_EXPENSE_PENDING': 15 | return { 16 | ...state, 17 | fetching: true, 18 | error: null 19 | } 20 | case 'FETCH_EXPENSE_SUCCESS': 21 | 22 | return { 23 | ...state, 24 | fetching: false, 25 | fetched: true, 26 | data: action.data, 27 | success: true, 28 | type: 'fetch' 29 | } 30 | 31 | case 'FETCH_EXPENSE_FAILED': 32 | return { 33 | ...state, 34 | fetching: false, 35 | fetched: true, 36 | error: action.error, 37 | message: action.error.data.message, 38 | success: false, 39 | type: 'fetch' 40 | } 41 | case 'SAVE_EXPENSE_PENDING': 42 | return { 43 | ...state, 44 | fetching: true, 45 | } 46 | case 'SAVE_EXPENSE_SUCCESS': 47 | 48 | return { 49 | ...state, 50 | fetching: false, 51 | fetched: true, 52 | message: action.message, 53 | type: 'save', 54 | success: true 55 | } 56 | 57 | case 'SAVE_EXPENSE_FAILED': 58 | return { 59 | ...state, 60 | fetching: false, 61 | fetched: true, 62 | error: action.error, 63 | message: action.message, 64 | type: 'save', 65 | success: false 66 | } 67 | case 'GET_EXPENSE_PENDING': 68 | return { 69 | ...state, 70 | fetching: true, 71 | } 72 | case 'GET_EXPENSE_SUCCESS': 73 | 74 | return { 75 | ...state, 76 | fetching: false, 77 | fetched: true, 78 | type: 'get', 79 | expense: action.data, 80 | success: true 81 | } 82 | 83 | case 'GET_EXPENSE_FAILED': 84 | return { 85 | ...state, 86 | fetching: false, 87 | fetched: true, 88 | error: action.error, 89 | message: action.message, 90 | type: 'get', 91 | success: false 92 | } 93 | case 'UPDATE_EXPENSE_PENDING': 94 | return { 95 | ...state, 96 | fetching: true, 97 | } 98 | case 'UPDATE_EXPENSE_SUCCESS': 99 | 100 | return { 101 | ...state, 102 | fetching: false, 103 | fetched: true, 104 | message: action.message, 105 | type: 'update', 106 | success: true 107 | } 108 | 109 | case 'UPDATE_EXPENSE_FAILED': 110 | return { 111 | ...state, 112 | fetching: false, 113 | fetched: true, 114 | error: action.error, 115 | message: action.message, 116 | type: 'update', 117 | success: false 118 | } 119 | case 'DELETE_EXPENSE_PENDING': 120 | return { 121 | ...state, 122 | fetching: true, 123 | } 124 | case 'DELETE_EXPENSE_SUCCESS': 125 | 126 | return { 127 | ...state, 128 | fetching: false, 129 | fetched: true, 130 | type: 'delete', 131 | message: action.message, 132 | success: true 133 | } 134 | 135 | case 'DELETE_EXPENSE_FAILED': 136 | return { 137 | ...state, 138 | fetching: false, 139 | fetched: true, 140 | error: action.error, 141 | message: action.message, 142 | type: 'delete', 143 | success: false 144 | } 145 | 146 | default: 147 | return state 148 | } 149 | } 150 | 151 | export default expenseReducer -------------------------------------------------------------------------------- /src/components/DefaultScreen.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { NavLink, Link } from 'react-router-dom' 3 | 4 | class DefaultScreen extends Component { 5 | 6 | handleLogout = () => { 7 | sessionStorage.removeItem('token') 8 | return this.props.history.push('/login') 9 | } 10 | 11 | render() { 12 | const path = this.props.location.pathname.split('/')[1] 13 | const permissions = sessionStorage.getItem('permissions') ? JSON.parse(sessionStorage.getItem('permissions')).filter(permission => permission !== null) : [] 14 | 15 | return ( 16 |
17 | 18 |
19 | 20 | Kasir Kita 2.0 21 | 22 |
    23 |
  • 24 | 27 | 28 |
    29 | 30 |
    31 |
  • 32 |
33 |
34 |
35 | 36 |
37 |
    38 | { 39 | permissions && permissions.length > 0 && permissions.map(permission => { 40 | return ( 41 | permission.children && permission.children.length > 0 ? ( 42 |
  • child.slug).includes(path) ? 'dropdown-active' : ''}`} key={permission._id}> 43 | 46 | 47 |
    48 | { 49 | permission.children.map(child => { 50 | return ( 51 | {child.name} 52 | ) 53 | }) 54 | } 55 |
    56 |
  • 57 | ) : ( 58 |
  • 59 | {permission.name} 60 |
  • 61 | ) 62 | ) 63 | }) 64 | } 65 |
66 |
67 |
68 | {this.props.children} 69 |
70 |
71 |
72 | 73 | 74 | ) 75 | } 76 | } 77 | 78 | export default DefaultScreen 79 | -------------------------------------------------------------------------------- /src/assets/style.css: -------------------------------------------------------------------------------- 1 | .autocomplete-wrapper { 2 | position: relative; 3 | } 4 | 5 | .autocomplete { 6 | position: absolute; 7 | z-index: 2; 8 | background-color: #fff; 9 | width: 100%; 10 | /* border: 1px solid #eee; */ 11 | } 12 | 13 | .autocomplete > ul { 14 | padding: 0px; 15 | list-style: none; 16 | } 17 | 18 | .autocomplete > ul > li { 19 | padding: 10px; 20 | border-bottom: 1px solid #eee; 21 | cursor: pointer; 22 | transition: all .2s ease-in-out; 23 | } 24 | .autocomplete > ul > li:hover { 25 | background-color: #007bff; 26 | border-color: #007bff; 27 | color: #fff; 28 | } 29 | 30 | .dropdown-active { 31 | background-color: #007bff; 32 | } 33 | 34 | .dropdown-active > a { 35 | color: #fff; 36 | } 37 | 38 | .table-custom > tbody p { 39 | margin-bottom: 0px; 40 | font-weight: bold; 41 | } 42 | 43 | .table-custom > tbody > tr > td { 44 | font-weight: normal !important; 45 | } 46 | 47 | .table-responsive { 48 | display: table; 49 | } 50 | 51 | .clickable-row { 52 | cursor: pointer; 53 | } 54 | 55 | .table-custom th { 56 | position: relative; 57 | cursor: pointer; 58 | padding-right: 35px !important; 59 | } 60 | 61 | .table-sorting { 62 | position: absolute; 63 | top: 10px; 64 | right: 10px; 65 | color: #ccc; 66 | } 67 | 68 | .table-sorting.active { 69 | color: #007bff; 70 | } 71 | 72 | .table-custom td p { 73 | margin-bottom: 0px !important; 74 | } 75 | 76 | .table-custom td div { 77 | margin-top: 10px; 78 | } 79 | 80 | .table-custom td div button:first-child { 81 | margin-right: 5px; 82 | } 83 | 84 | .table-scrollable tbody{ 85 | display:block; 86 | overflow:auto; 87 | height:200px; 88 | width:100%; 89 | } 90 | 91 | .dropzone-wrapper { 92 | border: 2px dashed #eee; 93 | } 94 | 95 | .table-scrollable thead tr, .table-scrollable tfoot tr { 96 | display:block; 97 | } 98 | 99 | .table-scrollable tr > th, .table-scrollable tr > td { 100 | width: 33.3%; 101 | } 102 | 103 | .table-scrollable tr > td { 104 | font-weight: normal; 105 | position: relative; 106 | } 107 | 108 | .table-scrollable tr > td:last-child { 109 | padding-right: 35px; 110 | } 111 | 112 | .table-scrollable tr > td > button.btn-remove { 113 | position: absolute; 114 | right: 0px; 115 | top: 5px; 116 | } 117 | 118 | .table-scrollable-two thead, .table-scrollable tbody tr { 119 | display: table; 120 | width: 100%; 121 | table-layout: fixed; 122 | } 123 | 124 | .table-scrollable-two tbody { 125 | display: block; 126 | overflow-y: scroll; 127 | overflow-x: hidden; 128 | max-height: 150px; 129 | } 130 | 131 | .table-scrollable-two { 132 | display: block; 133 | } 134 | 135 | .table-scrollable-two tfoot { 136 | display: block; 137 | } 138 | 139 | .btn-close-modal { 140 | position: absolute; 141 | right: 0; 142 | } 143 | 144 | .react-datepicker-wrapper { 145 | width: 100% !important; 146 | } 147 | 148 | .receipt { 149 | font-family: monospace; 150 | padding: 10px; 151 | border: 1px solid #aaa; 152 | } 153 | 154 | .term-and-condition { 155 | padding: 20px; 156 | border: 1px solid #e1e1e1; 157 | height: 200px; 158 | overflow-y: scroll; 159 | } 160 | 161 | .btn-disabled { 162 | cursor: not-allowed; 163 | } 164 | 165 | .loading { 166 | position: absolute; 167 | width: 100%; 168 | height: 100vh; 169 | background-color: #fff; 170 | top: 0; 171 | z-index: 2; 172 | color: #ccc; 173 | display: flex; 174 | justify-content: center; 175 | align-items: center; 176 | } 177 | 178 | .pointer { 179 | cursor: pointer; 180 | color: #007bff !important; 181 | } 182 | 183 | .pointer:focus, .pointer[aria-expanded="true"] { 184 | color: #fff !important; 185 | } 186 | 187 | .dropdown-active .pointer { 188 | color: #fff !important; 189 | } 190 | 191 | .uploading { 192 | opacity: .5; 193 | cursor: not-allowed; 194 | } 195 | 196 | .is-invalid-select .css-yk16xz-control { 197 | border-color: hsl(355, 70%, 54%) !important; 198 | } 199 | 200 | .img-avatar { 201 | height: 50px; 202 | object-fit: cover; 203 | } 204 | 205 | 206 | .table-scrollable tbody{ 207 | display:block; 208 | overflow:auto; 209 | max-height:200px; 210 | width:100%; 211 | } 212 | 213 | .table-scrollable thead tr, .table-scrollable tfoot tr { 214 | display:block; 215 | } 216 | 217 | .table-scrollable tr > th, .table-scrollable tr > td { 218 | width: 33.3%; 219 | } 220 | 221 | .table-scrollable tr > td { 222 | font-weight: normal; 223 | position: relative; 224 | } 225 | 226 | .table-scrollable tr > td:last-child { 227 | padding-right: 35px; 228 | } 229 | 230 | .table-scrollable tr > td > button.btn-remove { 231 | position: absolute; 232 | right: 0px; 233 | top: 5px; 234 | } 235 | 236 | .table-scrollable tfoot > tr > th { 237 | width: 100%; 238 | } -------------------------------------------------------------------------------- /src/views/Expense/ViewExpense.js: -------------------------------------------------------------------------------- 1 | import React, { Component, Fragment } from 'react' 2 | import 'react-datepicker/dist/react-datepicker.css' 3 | import { connect } from 'react-redux' 4 | import { withToastManager } from 'react-toast-notifications' 5 | import { Link } from 'react-router-dom' 6 | import { getExpense } from '../../store/actions/ExpenseActions' 7 | 8 | class ViewExpense extends Component { 9 | 10 | componentDidMount() { 11 | this.props.getExpense(this.props.match.params.id) 12 | } 13 | 14 | render() { 15 | const { expense } = this.props 16 | return ( 17 | 18 |
19 | 20 |
21 |
22 |
23 |

{ expense && expense.number }

24 |
25 |
26 | Kembali 27 |
28 |
29 |
30 |
31 | 32 |
33 |
34 |
35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 |
Nama Toko / Pemasok{ expense && expense.supplier_name }Penanggung Jawab{ expense && expense.in_charge ? expense.in_charge.name : '-' }
Tanggal Pembelian{ expense && expense.payment_date_formatted }Di buat oleh{ expense && expense.user ? expense.user.name : '-'}
Bukti{ expense && expense.evidence ? {expense.evidence} : '-' }Catatan{ expense && expense.notes }
57 |
58 |
59 |

Pembelian

60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 |
Nama BarangHarga BeliQtyTotal
{ expense && expense.product_name}{ expense && expense.price_formatted}{ expense && expense.qty}{ expense && expense.total_formatted}
78 |
79 |
80 |
81 |
82 |
83 | ) 84 | } 85 | } 86 | 87 | const mapStateToProps = (state) => { 88 | return { 89 | ...state, 90 | expense: state.expense.expense 91 | } 92 | } 93 | 94 | const mapDispatchToProps = (dispatch) => { 95 | return { 96 | getExpense: id => dispatch(getExpense(id)) 97 | } 98 | } 99 | 100 | 101 | export default connect(mapStateToProps, mapDispatchToProps)(withToastManager(ViewExpense)) 102 | -------------------------------------------------------------------------------- /src/store/actions/SettingActions.js: -------------------------------------------------------------------------------- 1 | import Axios from "axios" 2 | import { url } from "../../global" 3 | 4 | const getSetting = () => { 5 | return (dispatch, getState) => { 6 | dispatch({ 7 | type: 'GET_SETTING_PENDING', 8 | }) 9 | 10 | Axios.get(`${url}/setting`, { 11 | headers: { 12 | Authorization: `Bearer ${sessionStorage.getItem('token')}` 13 | } 14 | }).then(res => { 15 | dispatch({ 16 | type: 'GET_SETTING_SUCCESS', 17 | data: res.data.data, 18 | success: true 19 | }) 20 | }).catch(error => { 21 | if (!error.response) { 22 | dispatch({ 23 | type: 'GET_SETTING_FAILED', 24 | error: { 25 | status: null, 26 | connection: true, 27 | statusText: 'Koneksi Terputus', 28 | data: { 29 | message: 'Silahkan periksa koneksi backend, lihat tutorial di sini https://github.com/kasirkita/Kasir-Kita' 30 | } 31 | } 32 | }) 33 | 34 | } else { 35 | 36 | dispatch({ 37 | type: 'GET_SETTING_FAILED', 38 | error: error.response, 39 | message: error.response.data.message 40 | }) 41 | } 42 | }) 43 | } 44 | } 45 | 46 | const saveSetting = (data) => { 47 | return (dispatch, getState) => { 48 | 49 | const { 50 | name, 51 | address, 52 | logo, 53 | logo_remove, 54 | phone_number, 55 | divider, 56 | currency, 57 | thousand_separator, 58 | decimal_separator, 59 | tax, 60 | printer 61 | } = data 62 | 63 | dispatch({ 64 | type: 'SAVE_SETTING_PENDING' 65 | }) 66 | 67 | const fd = new FormData(); 68 | 69 | fd.set('name', name) 70 | fd.set('address', address) 71 | fd.append('logo', logo) 72 | fd.set('logo_remove', logo_remove) 73 | fd.set('phone_number', phone_number) 74 | fd.set('divider', divider) 75 | fd.set('currency', currency) 76 | fd.set('thousand_separator', thousand_separator) 77 | fd.set('decimal_separator', decimal_separator) 78 | fd.set('tax', tax) 79 | fd.set('printer', printer) 80 | 81 | Axios.post(`${url}/setting`, fd, 82 | { 83 | headers: { 84 | Authorization: `Bearer ${sessionStorage.getItem('token')}`, 85 | 'Content-Type': 'multipart/form-data' 86 | } 87 | }).then(res => { 88 | 89 | sessionStorage.setItem('decimal_separator', res.data.data.decimal_separator ? res.data.data.decimal_separator : '') 90 | sessionStorage.setItem('thousand_separator', res.data.data.thousand_separator ? res.data.data.thousand_separator : '') 91 | sessionStorage.setItem('currency', res.data.data.currency ? res.data.data.currency : '') 92 | sessionStorage.setItem('printer', res.data.data.printer ? res.data.data.printer : '') 93 | sessionStorage.setItem('tax', res.data.data.tax ? res.data.data.tax : '') 94 | sessionStorage.setItem('logo', res.data.data.logo_url) 95 | sessionStorage.setItem('logo_remove', res.data.data.logo_remove ? res.data.data.logo_remove : false) 96 | sessionStorage.setItem('shop_name', res.data.data.name ? res.data.data.name : '') 97 | sessionStorage.setItem('address', res.data.data.address ? res.data.data.address : '') 98 | sessionStorage.setItem('phone_number', res.data.data.phone_number ? res.data.data.phone_number : '') 99 | sessionStorage.setItem('divider', res.data.data.divider ? res.data.data.divider : '') 100 | 101 | 102 | dispatch({ 103 | type: 'SAVE_SETTING_SUCCESS', 104 | data: res.data.data, 105 | message: res.data.message 106 | }) 107 | 108 | }).catch(error => { 109 | 110 | if (!error.response) { 111 | dispatch({ 112 | type: 'SAVE_SETTING_FAILED', 113 | error: { 114 | status: null, 115 | connection: true, 116 | statusText: 'Koneksi Terputus', 117 | data: { 118 | message: 'Silahkan periksa koneksi backend, lihat tutorial di sini https://github.com/kasirkita/Kasir-Kita' 119 | } 120 | } 121 | }) 122 | 123 | } else { 124 | 125 | dispatch({ 126 | type: 'SAVE_SETTING_FAILED', 127 | error: error.response, 128 | message: error.response.data.message 129 | }) 130 | } 131 | 132 | }) 133 | } 134 | } 135 | 136 | 137 | export { getSetting, saveSetting } -------------------------------------------------------------------------------- /src/store/reducer/roleReducer.js: -------------------------------------------------------------------------------- 1 | const initState = { 2 | fetching: false, 3 | fetched: false, 4 | message: null, 5 | data: null, 6 | error: null, 7 | success: null, 8 | type: null, 9 | role: null, 10 | } 11 | 12 | const roleReducer = (state = initState, action) => { 13 | switch (action.type) { 14 | case 'FETCH_ROLE_PENDING': 15 | return { 16 | ...state, 17 | fetching: true, 18 | error: null 19 | } 20 | case 'FETCH_ROLE_SUCCESS': 21 | 22 | return { 23 | ...state, 24 | fetching: false, 25 | fetched: true, 26 | data: action.data, 27 | success: true, 28 | type: 'fetch' 29 | } 30 | 31 | case 'FETCH_ROLE_FAILED': 32 | return { 33 | ...state, 34 | fetching: false, 35 | fetched: true, 36 | error: action.error, 37 | message: action.error.data.message, 38 | success: false, 39 | type: 'fetch' 40 | } 41 | case 'SAVE_ROLE_PENDING': 42 | return { 43 | ...state, 44 | fetching: true, 45 | } 46 | case 'SAVE_ROLE_SUCCESS': 47 | 48 | return { 49 | ...state, 50 | fetching: false, 51 | fetched: true, 52 | message: action.message, 53 | type: 'save', 54 | success: true 55 | } 56 | 57 | case 'SAVE_ROLE_FAILED': 58 | return { 59 | ...state, 60 | fetching: false, 61 | fetched: true, 62 | error: action.error, 63 | message: action.message, 64 | type: 'save', 65 | success: false 66 | } 67 | case 'GET_ROLE_PENDING': 68 | return { 69 | ...state, 70 | fetching: true, 71 | } 72 | case 'GET_ROLE_SUCCESS': 73 | 74 | return { 75 | ...state, 76 | fetching: false, 77 | fetched: true, 78 | type: 'get', 79 | role: action.data, 80 | success: true 81 | } 82 | 83 | case 'GET_ROLE_FAILED': 84 | return { 85 | ...state, 86 | fetching: false, 87 | fetched: true, 88 | error: action.error, 89 | message: action.message, 90 | type: 'get', 91 | success: false 92 | } 93 | case 'UPDATE_ROLE_PENDING': 94 | return { 95 | ...state, 96 | fetching: true, 97 | } 98 | case 'UPDATE_ROLE_SUCCESS': 99 | 100 | return { 101 | ...state, 102 | fetching: false, 103 | fetched: true, 104 | message: action.message, 105 | type: 'update', 106 | success: true 107 | } 108 | 109 | case 'UPDATE_ROLE_FAILED': 110 | return { 111 | ...state, 112 | fetching: false, 113 | fetched: true, 114 | error: action.error, 115 | message: action.message, 116 | type: 'update', 117 | success: false 118 | } 119 | case 'TOGGLE_ROLE_PENDING': 120 | return { 121 | ...state, 122 | // fetching: true, 123 | } 124 | case 'TOGGLE_ROLE_SUCCESS': 125 | 126 | return { 127 | ...state, 128 | // fetching: false, 129 | // fetched: true, 130 | // message: action.message, 131 | // type: 'toggle', 132 | // success: true 133 | } 134 | 135 | case 'TOGGLE_ROLE_FAILED': 136 | return { 137 | ...state, 138 | // fetching: false, 139 | // fetched: true, 140 | error: action.error, 141 | // message: action.message, 142 | type: 'toggle', 143 | // success: false 144 | } 145 | case 'DELETE_ROLE_PENDING': 146 | return { 147 | ...state, 148 | fetching: true, 149 | } 150 | case 'DELETE_ROLE_SUCCESS': 151 | 152 | return { 153 | ...state, 154 | fetching: false, 155 | fetched: true, 156 | type: 'delete', 157 | message: action.message, 158 | success: true 159 | } 160 | 161 | case 'DELETE_ROLE_FAILED': 162 | return { 163 | ...state, 164 | fetching: false, 165 | fetched: true, 166 | error: action.error, 167 | message: action.message, 168 | type: 'delete', 169 | success: false 170 | } 171 | default: 172 | return state 173 | } 174 | } 175 | 176 | export default roleReducer -------------------------------------------------------------------------------- /src/store/reducer/unitReducer.js: -------------------------------------------------------------------------------- 1 | const initState = { 2 | fetching: false, 3 | fetched: false, 4 | message: null, 5 | data: null, 6 | error: null, 7 | success: null, 8 | type: null, 9 | unit: null, 10 | } 11 | 12 | const unitReducer = (state = initState, action) => { 13 | switch (action.type) { 14 | case 'FETCH_UNIT_PENDING': 15 | return { 16 | ...state, 17 | fetching: true, 18 | error: null 19 | } 20 | case 'FETCH_UNIT_SUCCESS': 21 | 22 | return { 23 | ...state, 24 | fetching: false, 25 | fetched: true, 26 | data: action.data, 27 | success: true, 28 | type: 'fetch' 29 | } 30 | 31 | case 'FETCH_UNIT_FAILED': 32 | return { 33 | ...state, 34 | fetching: false, 35 | fetched: true, 36 | error: action.error, 37 | message: action.error.data.message, 38 | success: false, 39 | type: 'fetch' 40 | } 41 | case 'SAVE_UNIT_PENDING': 42 | return { 43 | ...state, 44 | fetching: true, 45 | } 46 | case 'SAVE_UNIT_SUCCESS': 47 | 48 | return { 49 | ...state, 50 | fetching: false, 51 | fetched: true, 52 | message: action.message, 53 | type: 'save', 54 | success: true 55 | } 56 | 57 | case 'SAVE_UNIT_FAILED': 58 | return { 59 | ...state, 60 | fetching: false, 61 | fetched: true, 62 | error: action.error, 63 | message: action.message, 64 | type: 'save', 65 | success: false 66 | } 67 | case 'GET_UNIT_PENDING': 68 | return { 69 | ...state, 70 | fetching: true, 71 | } 72 | case 'GET_UNIT_SUCCESS': 73 | 74 | return { 75 | ...state, 76 | fetching: false, 77 | fetched: true, 78 | type: 'get', 79 | unit: action.data, 80 | success: true 81 | } 82 | 83 | case 'GET_UNIT_FAILED': 84 | return { 85 | ...state, 86 | fetching: false, 87 | fetched: true, 88 | error: action.error, 89 | message: action.message, 90 | type: 'get', 91 | success: false 92 | } 93 | case 'UPDATE_UNIT_PENDING': 94 | return { 95 | ...state, 96 | fetching: true, 97 | } 98 | case 'UPDATE_UNIT_SUCCESS': 99 | 100 | return { 101 | ...state, 102 | fetching: false, 103 | fetched: true, 104 | message: action.message, 105 | type: 'update', 106 | success: true 107 | } 108 | 109 | case 'UPDATE_UNIT_FAILED': 110 | return { 111 | ...state, 112 | fetching: false, 113 | fetched: true, 114 | error: action.error, 115 | message: action.message, 116 | type: 'update', 117 | success: false 118 | } 119 | case 'TOGGLE_UNIT_PENDING': 120 | return { 121 | ...state, 122 | // fetching: true, 123 | } 124 | case 'TOGGLE_UNIT_SUCCESS': 125 | 126 | return { 127 | ...state, 128 | // fetching: false, 129 | // fetched: true, 130 | // message: action.message, 131 | // type: 'toggle', 132 | // success: true 133 | } 134 | 135 | case 'TOGGLE_UNIT_FAILED': 136 | return { 137 | ...state, 138 | // fetching: false, 139 | // fetched: true, 140 | error: action.error, 141 | // message: action.message, 142 | type: 'toggle', 143 | // success: false 144 | } 145 | case 'DELETE_UNIT_PENDING': 146 | return { 147 | ...state, 148 | fetching: true, 149 | } 150 | case 'DELETE_UNIT_SUCCESS': 151 | 152 | return { 153 | ...state, 154 | fetching: false, 155 | fetched: true, 156 | type: 'delete', 157 | message: action.message, 158 | success: true 159 | } 160 | 161 | case 'DELETE_UNIT_FAILED': 162 | return { 163 | ...state, 164 | fetching: false, 165 | fetched: true, 166 | error: action.error, 167 | message: action.message, 168 | type: 'delete', 169 | success: false 170 | } 171 | default: 172 | return state 173 | } 174 | } 175 | 176 | export default unitReducer -------------------------------------------------------------------------------- /src/store/reducer/userReducer.js: -------------------------------------------------------------------------------- 1 | const initState = { 2 | fetching: false, 3 | fetched: false, 4 | message: null, 5 | data: null, 6 | error: null, 7 | success: null, 8 | type: null, 9 | user: null, 10 | } 11 | 12 | const userReducer = (state = initState, action) => { 13 | switch (action.type) { 14 | case 'FETCH_USER_PENDING': 15 | return { 16 | ...state, 17 | fetching: true, 18 | error: null 19 | } 20 | case 'FETCH_USER_SUCCESS': 21 | 22 | return { 23 | ...state, 24 | fetching: false, 25 | fetched: true, 26 | data: action.data, 27 | success: true, 28 | selected: action.selected, 29 | type: 'fetch' 30 | } 31 | 32 | case 'FETCH_USER_FAILED': 33 | return { 34 | ...state, 35 | fetching: false, 36 | fetched: true, 37 | error: action.error, 38 | message: action.error.data.message, 39 | success: false, 40 | type: 'fetch' 41 | } 42 | case 'SAVE_USER_PENDING': 43 | return { 44 | ...state, 45 | fetching: true, 46 | } 47 | case 'SAVE_USER_SUCCESS': 48 | 49 | return { 50 | ...state, 51 | fetching: false, 52 | fetched: true, 53 | message: action.message, 54 | type: 'save', 55 | success: true 56 | } 57 | 58 | case 'SAVE_USER_FAILED': 59 | return { 60 | ...state, 61 | fetching: false, 62 | fetched: true, 63 | error: action.error, 64 | message: action.message, 65 | type: 'save', 66 | success: false 67 | } 68 | case 'GET_USER_PENDING': 69 | return { 70 | ...state, 71 | fetching: true, 72 | } 73 | case 'GET_USER_SUCCESS': 74 | 75 | return { 76 | ...state, 77 | fetching: false, 78 | fetched: true, 79 | type: 'get', 80 | user: action.data, 81 | success: true 82 | } 83 | 84 | case 'GET_USER_FAILED': 85 | return { 86 | ...state, 87 | fetching: false, 88 | fetched: true, 89 | error: action.error, 90 | message: action.message, 91 | type: 'get', 92 | success: false 93 | } 94 | case 'UPDATE_USER_PENDING': 95 | return { 96 | ...state, 97 | fetching: true, 98 | } 99 | case 'UPDATE_USER_SUCCESS': 100 | 101 | return { 102 | ...state, 103 | fetching: false, 104 | fetched: true, 105 | message: action.message, 106 | type: 'update', 107 | success: true 108 | } 109 | 110 | case 'UPDATE_USER_FAILED': 111 | return { 112 | ...state, 113 | fetching: false, 114 | fetched: true, 115 | error: action.error, 116 | message: action.message, 117 | type: 'update', 118 | success: false 119 | } 120 | case 'TOGGLE_USER_PENDING': 121 | return { 122 | ...state, 123 | // fetching: true, 124 | } 125 | case 'TOGGLE_USER_SUCCESS': 126 | 127 | return { 128 | ...state, 129 | // fetching: false, 130 | // fetched: true, 131 | // message: action.message, 132 | // type: 'toggle', 133 | // success: true 134 | } 135 | 136 | case 'TOGGLE_USER_FAILED': 137 | return { 138 | ...state, 139 | // fetching: false, 140 | // fetched: true, 141 | error: action.error, 142 | // message: action.message, 143 | type: 'toggle', 144 | // success: false 145 | } 146 | case 'DELETE_USER_PENDING': 147 | return { 148 | ...state, 149 | fetching: true, 150 | } 151 | case 'DELETE_USER_SUCCESS': 152 | 153 | return { 154 | ...state, 155 | fetching: false, 156 | fetched: true, 157 | type: 'delete', 158 | message: action.message, 159 | success: true 160 | } 161 | 162 | case 'DELETE_USER_FAILED': 163 | return { 164 | ...state, 165 | fetching: false, 166 | fetched: true, 167 | error: action.error, 168 | message: action.message, 169 | type: 'delete', 170 | success: false 171 | } 172 | 173 | default: 174 | return state 175 | } 176 | } 177 | 178 | export default userReducer -------------------------------------------------------------------------------- /src/store/reducer/salesReducer.js: -------------------------------------------------------------------------------- 1 | const initState = { 2 | fetching: false, 3 | fetched: false, 4 | message: null, 5 | data: null, 6 | error: null, 7 | success: null, 8 | type: null, 9 | sales: null, 10 | carts: null 11 | } 12 | 13 | const salesReducer = (state = initState, action) => { 14 | switch (action.type) { 15 | case 'FETCH_SALES_PENDING': 16 | return { 17 | ...state, 18 | fetching: true, 19 | error: null 20 | } 21 | case 'FETCH_SALES_SUCCESS': 22 | 23 | return { 24 | ...state, 25 | fetching: false, 26 | fetched: true, 27 | data: action.data, 28 | success: true, 29 | type: 'fetch' 30 | } 31 | 32 | case 'FETCH_SALES_FAILED': 33 | return { 34 | ...state, 35 | fetching: false, 36 | fetched: true, 37 | error: action.error, 38 | message: action.error.data.message, 39 | success: false, 40 | type: 'fetch' 41 | } 42 | case 'SAVE_SALES_PENDING': 43 | return { 44 | ...state, 45 | fetching: true, 46 | } 47 | case 'SAVE_SALES_SUCCESS': 48 | 49 | return { 50 | ...state, 51 | fetching: false, 52 | fetched: true, 53 | message: action.message, 54 | carts: action.data, 55 | type: 'save', 56 | success: true 57 | } 58 | 59 | case 'SAVE_SALES_FAILED': 60 | return { 61 | ...state, 62 | fetching: false, 63 | fetched: true, 64 | error: action.error, 65 | message: action.message, 66 | type: 'save', 67 | success: false 68 | } 69 | case 'GET_SALES_PENDING': 70 | return { 71 | ...state, 72 | fetching: true, 73 | } 74 | case 'GET_SALES_SUCCESS': 75 | 76 | return { 77 | ...state, 78 | fetching: false, 79 | fetched: true, 80 | type: 'get', 81 | sales: action.data, 82 | success: true 83 | } 84 | 85 | case 'GET_SALES_FAILED': 86 | return { 87 | ...state, 88 | fetching: false, 89 | fetched: true, 90 | error: action.error, 91 | message: action.message, 92 | type: 'get', 93 | success: false 94 | } 95 | case 'UPDATE_SALES_PENDING': 96 | return { 97 | ...state, 98 | fetching: true, 99 | } 100 | case 'UPDATE_SALES_SUCCESS': 101 | 102 | return { 103 | ...state, 104 | fetching: false, 105 | fetched: true, 106 | message: action.message, 107 | type: 'update', 108 | success: true 109 | } 110 | 111 | case 'UPDATE_SALES_FAILED': 112 | return { 113 | ...state, 114 | fetching: false, 115 | fetched: true, 116 | error: action.error, 117 | message: action.message, 118 | type: 'update', 119 | success: false 120 | } 121 | case 'TOGGLE_SALES_PENDING': 122 | return { 123 | ...state, 124 | // fetching: true, 125 | } 126 | case 'TOGGLE_SALES_SUCCESS': 127 | 128 | return { 129 | ...state, 130 | // fetching: false, 131 | // fetched: true, 132 | // message: action.message, 133 | // type: 'toggle', 134 | // success: true 135 | } 136 | 137 | case 'TOGGLE_SALES_FAILED': 138 | return { 139 | ...state, 140 | // fetching: false, 141 | // fetched: true, 142 | error: action.error, 143 | // message: action.message, 144 | type: 'toggle', 145 | // success: false 146 | } 147 | case 'DELETE_SALES_PENDING': 148 | return { 149 | ...state, 150 | fetching: true, 151 | } 152 | case 'DELETE_SALES_SUCCESS': 153 | 154 | return { 155 | ...state, 156 | fetching: false, 157 | fetched: true, 158 | type: 'delete', 159 | message: action.message, 160 | success: true 161 | } 162 | 163 | case 'DELETE_SALES_FAILED': 164 | return { 165 | ...state, 166 | fetching: false, 167 | fetched: true, 168 | error: action.error, 169 | message: action.message, 170 | type: 'delete', 171 | success: false 172 | } 173 | default: 174 | return state 175 | } 176 | } 177 | 178 | export default salesReducer -------------------------------------------------------------------------------- /src/store/reducer/categoryReducer.js: -------------------------------------------------------------------------------- 1 | const initState = { 2 | fetching: false, 3 | fetched: false, 4 | message: null, 5 | data: null, 6 | error: null, 7 | success: null, 8 | type: null, 9 | category: null, 10 | } 11 | 12 | const categoryReducer = (state = initState, action) => { 13 | switch (action.type) { 14 | case 'FETCH_CATEGORY_PENDING': 15 | return { 16 | ...state, 17 | fetching: true, 18 | error: null 19 | } 20 | case 'FETCH_CATEGORY_SUCCESS': 21 | 22 | return { 23 | ...state, 24 | fetching: false, 25 | fetched: true, 26 | data: action.data, 27 | success: true, 28 | type: 'fetch' 29 | } 30 | 31 | case 'FETCH_CATEGORY_FAILED': 32 | return { 33 | ...state, 34 | fetching: false, 35 | fetched: true, 36 | error: action.error, 37 | message: action.error.data.message, 38 | success: false, 39 | type: 'fetch' 40 | } 41 | case 'SAVE_CATEGORY_PENDING': 42 | return { 43 | ...state, 44 | fetching: true, 45 | } 46 | case 'SAVE_CATEGORY_SUCCESS': 47 | 48 | return { 49 | ...state, 50 | fetching: false, 51 | fetched: true, 52 | message: action.message, 53 | type: 'save', 54 | success: true 55 | } 56 | 57 | case 'SAVE_CATEGORY_FAILED': 58 | return { 59 | ...state, 60 | fetching: false, 61 | fetched: true, 62 | error: action.error, 63 | message: action.message, 64 | type: 'save', 65 | success: false 66 | } 67 | case 'GET_CATEGORY_PENDING': 68 | return { 69 | ...state, 70 | fetching: true, 71 | } 72 | case 'GET_CATEGORY_SUCCESS': 73 | 74 | return { 75 | ...state, 76 | fetching: false, 77 | fetched: true, 78 | type: 'get', 79 | category: action.data, 80 | success: true 81 | } 82 | 83 | case 'GET_CATEGORY_FAILED': 84 | return { 85 | ...state, 86 | fetching: false, 87 | fetched: true, 88 | error: action.error, 89 | message: action.message, 90 | type: 'get', 91 | success: false 92 | } 93 | case 'UPDATE_CATEGORY_PENDING': 94 | return { 95 | ...state, 96 | fetching: true, 97 | } 98 | case 'UPDATE_CATEGORY_SUCCESS': 99 | 100 | return { 101 | ...state, 102 | fetching: false, 103 | fetched: true, 104 | message: action.message, 105 | type: 'update', 106 | success: true 107 | } 108 | 109 | case 'UPDATE_CATEGORY_FAILED': 110 | return { 111 | ...state, 112 | fetching: false, 113 | fetched: true, 114 | error: action.error, 115 | message: action.message, 116 | type: 'update', 117 | success: false 118 | } 119 | case 'TOGGLE_CATEGORY_PENDING': 120 | return { 121 | ...state, 122 | // fetching: true, 123 | } 124 | case 'TOGGLE_CATEGORY_SUCCESS': 125 | 126 | return { 127 | ...state, 128 | // fetching: false, 129 | // fetched: true, 130 | // message: action.message, 131 | // type: 'toggle', 132 | // success: true 133 | } 134 | 135 | case 'TOGGLE_CATEGORY_FAILED': 136 | return { 137 | ...state, 138 | // fetching: false, 139 | // fetched: true, 140 | error: action.error, 141 | // message: action.message, 142 | type: 'toggle', 143 | // success: false 144 | } 145 | case 'DELETE_CATEGORY_PENDING': 146 | return { 147 | ...state, 148 | fetching: true, 149 | } 150 | case 'DELETE_CATEGORY_SUCCESS': 151 | 152 | return { 153 | ...state, 154 | fetching: false, 155 | fetched: true, 156 | type: 'delete', 157 | message: action.message, 158 | success: true 159 | } 160 | 161 | case 'DELETE_CATEGORY_FAILED': 162 | return { 163 | ...state, 164 | fetching: false, 165 | fetched: true, 166 | error: action.error, 167 | message: action.message, 168 | type: 'delete', 169 | success: false 170 | } 171 | default: 172 | return state 173 | } 174 | } 175 | 176 | export default categoryReducer -------------------------------------------------------------------------------- /src/store/reducer/customerReducer.js: -------------------------------------------------------------------------------- 1 | const initState = { 2 | fetching: false, 3 | fetched: false, 4 | message: null, 5 | data: null, 6 | error: null, 7 | success: null, 8 | type: null, 9 | customer: null 10 | } 11 | 12 | const customerReducer = (state = initState, action) => { 13 | switch (action.type) { 14 | case 'FETCH_CUSTOMER_PENDING': 15 | return { 16 | ...state, 17 | fetching: true, 18 | error: null 19 | } 20 | case 'FETCH_CUSTOMER_SUCCESS': 21 | 22 | return { 23 | ...state, 24 | fetching: false, 25 | fetched: true, 26 | data: action.data, 27 | success: true, 28 | type: 'fetch' 29 | } 30 | 31 | case 'FETCH_CUSTOMER_FAILED': 32 | return { 33 | ...state, 34 | fetching: false, 35 | fetched: true, 36 | error: action.error, 37 | message: action.error.data.message, 38 | success: false, 39 | type: 'fetch' 40 | } 41 | case 'SAVE_CUSTOMER_PENDING': 42 | return { 43 | ...state, 44 | fetching: true, 45 | } 46 | case 'SAVE_CUSTOMER_SUCCESS': 47 | 48 | return { 49 | ...state, 50 | fetching: false, 51 | fetched: true, 52 | message: action.message, 53 | type: 'save', 54 | success: true 55 | } 56 | 57 | case 'SAVE_CUSTOMER_FAILED': 58 | return { 59 | ...state, 60 | fetching: false, 61 | fetched: true, 62 | error: action.error, 63 | message: action.message, 64 | type: 'save', 65 | success: false 66 | } 67 | case 'GET_CUSTOMER_PENDING': 68 | return { 69 | ...state, 70 | fetching: true, 71 | } 72 | case 'GET_CUSTOMER_SUCCESS': 73 | 74 | return { 75 | ...state, 76 | fetching: false, 77 | fetched: true, 78 | type: 'get', 79 | customer: action.data, 80 | success: true 81 | } 82 | 83 | case 'GET_CUSTOMER_FAILED': 84 | return { 85 | ...state, 86 | fetching: false, 87 | fetched: true, 88 | error: action.error, 89 | message: action.message, 90 | type: 'get', 91 | success: false 92 | } 93 | case 'UPDATE_CUSTOMER_PENDING': 94 | return { 95 | ...state, 96 | fetching: true, 97 | } 98 | case 'UPDATE_CUSTOMER_SUCCESS': 99 | 100 | return { 101 | ...state, 102 | fetching: false, 103 | fetched: true, 104 | message: action.message, 105 | type: 'update', 106 | success: true 107 | } 108 | 109 | case 'UPDATE_CUSTOMER_FAILED': 110 | return { 111 | ...state, 112 | fetching: false, 113 | fetched: true, 114 | error: action.error, 115 | message: action.message, 116 | type: 'update', 117 | success: false 118 | } 119 | case 'TOGGLE_CUSTOMER_PENDING': 120 | return { 121 | ...state, 122 | // fetching: true, 123 | } 124 | case 'TOGGLE_CUSTOMER_SUCCESS': 125 | 126 | return { 127 | ...state, 128 | // fetching: false, 129 | // fetched: true, 130 | // message: action.message, 131 | // type: 'toggle', 132 | // success: true 133 | } 134 | 135 | case 'TOGGLE_CUSTOMER_FAILED': 136 | return { 137 | ...state, 138 | // fetching: false, 139 | // fetched: true, 140 | error: action.error, 141 | // message: action.message, 142 | type: 'toggle', 143 | // success: false 144 | } 145 | case 'DELETE_CUSTOMER_PENDING': 146 | return { 147 | ...state, 148 | fetching: true, 149 | } 150 | case 'DELETE_CUSTOMER_SUCCESS': 151 | 152 | return { 153 | ...state, 154 | fetching: false, 155 | fetched: true, 156 | type: 'delete', 157 | message: action.message, 158 | success: true 159 | } 160 | 161 | case 'DELETE_CUSTOMER_FAILED': 162 | return { 163 | ...state, 164 | fetching: false, 165 | fetched: true, 166 | error: action.error, 167 | message: action.message, 168 | type: 'delete', 169 | success: false 170 | } 171 | default: 172 | return state 173 | } 174 | } 175 | 176 | export default customerReducer -------------------------------------------------------------------------------- /src/store/reducer/supplierReducer.js: -------------------------------------------------------------------------------- 1 | const initState = { 2 | fetching: false, 3 | fetched: false, 4 | message: null, 5 | data: null, 6 | error: null, 7 | success: null, 8 | type: null, 9 | supplier: null 10 | } 11 | 12 | const supplierReducer = (state = initState, action) => { 13 | switch (action.type) { 14 | case 'FETCH_SUPPLIER_PENDING': 15 | return { 16 | ...state, 17 | fetching: true, 18 | error: null 19 | } 20 | case 'FETCH_SUPPLIER_SUCCESS': 21 | 22 | return { 23 | ...state, 24 | fetching: false, 25 | fetched: true, 26 | data: action.data, 27 | success: true, 28 | type: 'fetch' 29 | } 30 | 31 | case 'FETCH_SUPPLIER_FAILED': 32 | return { 33 | ...state, 34 | fetching: false, 35 | fetched: true, 36 | error: action.error, 37 | message: action.error.data.message, 38 | success: false, 39 | type: 'fetch' 40 | } 41 | case 'SAVE_SUPPLIER_PENDING': 42 | return { 43 | ...state, 44 | fetching: true, 45 | } 46 | case 'SAVE_SUPPLIER_SUCCESS': 47 | 48 | return { 49 | ...state, 50 | fetching: false, 51 | fetched: true, 52 | message: action.message, 53 | type: 'save', 54 | success: true 55 | } 56 | 57 | case 'SAVE_SUPPLIER_FAILED': 58 | return { 59 | ...state, 60 | fetching: false, 61 | fetched: true, 62 | error: action.error, 63 | message: action.message, 64 | type: 'save', 65 | success: false 66 | } 67 | case 'GET_SUPPLIER_PENDING': 68 | return { 69 | ...state, 70 | fetching: true, 71 | } 72 | case 'GET_SUPPLIER_SUCCESS': 73 | 74 | return { 75 | ...state, 76 | fetching: false, 77 | fetched: true, 78 | type: 'get', 79 | supplier: action.data, 80 | success: true 81 | } 82 | 83 | case 'GET_SUPPLIER_FAILED': 84 | return { 85 | ...state, 86 | fetching: false, 87 | fetched: true, 88 | error: action.error, 89 | message: action.message, 90 | type: 'get', 91 | success: false 92 | } 93 | case 'UPDATE_SUPPLIER_PENDING': 94 | return { 95 | ...state, 96 | fetching: true, 97 | } 98 | case 'UPDATE_SUPPLIER_SUCCESS': 99 | 100 | return { 101 | ...state, 102 | fetching: false, 103 | fetched: true, 104 | message: action.message, 105 | type: 'update', 106 | success: true 107 | } 108 | 109 | case 'UPDATE_SUPPLIER_FAILED': 110 | return { 111 | ...state, 112 | fetching: false, 113 | fetched: true, 114 | error: action.error, 115 | message: action.message, 116 | type: 'update', 117 | success: false 118 | } 119 | case 'TOGGLE_SUPPLIER_PENDING': 120 | return { 121 | ...state, 122 | // fetching: true, 123 | } 124 | case 'TOGGLE_SUPPLIER_SUCCESS': 125 | 126 | return { 127 | ...state, 128 | // fetching: false, 129 | // fetched: true, 130 | // message: action.message, 131 | // type: 'toggle', 132 | // success: true 133 | } 134 | 135 | case 'TOGGLE_SUPPLIER_FAILED': 136 | return { 137 | ...state, 138 | // fetching: false, 139 | // fetched: true, 140 | error: action.error, 141 | // message: action.message, 142 | type: 'toggle', 143 | // success: false 144 | } 145 | case 'DELETE_SUPPLIER_PENDING': 146 | return { 147 | ...state, 148 | fetching: true, 149 | } 150 | case 'DELETE_SUPPLIER_SUCCESS': 151 | 152 | return { 153 | ...state, 154 | fetching: false, 155 | fetched: true, 156 | type: 'delete', 157 | message: action.message, 158 | success: true 159 | } 160 | 161 | case 'DELETE_SUPPLIER_FAILED': 162 | return { 163 | ...state, 164 | fetching: false, 165 | fetched: true, 166 | error: action.error, 167 | message: action.message, 168 | type: 'delete', 169 | success: false 170 | } 171 | default: 172 | return state 173 | } 174 | } 175 | 176 | export default supplierReducer -------------------------------------------------------------------------------- /src/store/reducer/purchaseReducer.js: -------------------------------------------------------------------------------- 1 | const initState = { 2 | fetching: false, 3 | fetched: false, 4 | message: null, 5 | data: null, 6 | error: null, 7 | success: null, 8 | type: null, 9 | purchase: null, 10 | } 11 | 12 | const purchaseReducer = (state = initState, action) => { 13 | switch (action.type) { 14 | case 'FETCH_PURCHASE_PENDING': 15 | return { 16 | ...state, 17 | fetching: true, 18 | error: null 19 | } 20 | case 'FETCH_PURCHASE_SUCCESS': 21 | 22 | return { 23 | ...state, 24 | fetching: false, 25 | fetched: true, 26 | data: action.data, 27 | success: true, 28 | selected: action.selected, 29 | type: 'fetch' 30 | } 31 | 32 | case 'FETCH_PURCHASE_FAILED': 33 | return { 34 | ...state, 35 | fetching: false, 36 | fetched: true, 37 | error: action.error, 38 | message: action.error.data.message, 39 | success: false, 40 | type: 'fetch' 41 | } 42 | case 'SAVE_PURCHASE_PENDING': 43 | return { 44 | ...state, 45 | fetching: true, 46 | } 47 | case 'SAVE_PURCHASE_SUCCESS': 48 | 49 | return { 50 | ...state, 51 | fetching: false, 52 | fetched: true, 53 | message: action.message, 54 | type: 'save', 55 | success: true 56 | } 57 | 58 | case 'SAVE_PURCHASE_FAILED': 59 | return { 60 | ...state, 61 | fetching: false, 62 | fetched: true, 63 | error: action.error, 64 | message: action.message, 65 | type: 'save', 66 | success: false 67 | } 68 | case 'GET_PURCHASE_PENDING': 69 | return { 70 | ...state, 71 | fetching: true, 72 | } 73 | case 'GET_PURCHASE_SUCCESS': 74 | 75 | return { 76 | ...state, 77 | fetching: false, 78 | fetched: true, 79 | type: 'get', 80 | purchase: action.data, 81 | success: true 82 | } 83 | 84 | case 'GET_PURCHASE_FAILED': 85 | return { 86 | ...state, 87 | fetching: false, 88 | fetched: true, 89 | error: action.error, 90 | message: action.message, 91 | type: 'get', 92 | success: false 93 | } 94 | case 'UPDATE_PURCHASE_PENDING': 95 | return { 96 | ...state, 97 | fetching: true, 98 | } 99 | case 'UPDATE_PURCHASE_SUCCESS': 100 | 101 | return { 102 | ...state, 103 | fetching: false, 104 | fetched: true, 105 | message: action.message, 106 | type: 'update', 107 | success: true 108 | } 109 | 110 | case 'UPDATE_PURCHASE_FAILED': 111 | return { 112 | ...state, 113 | fetching: false, 114 | fetched: true, 115 | error: action.error, 116 | message: action.message, 117 | type: 'update', 118 | success: false 119 | } 120 | case 'TOGGLE_PURCHASE_PENDING': 121 | return { 122 | ...state, 123 | // fetching: true, 124 | } 125 | case 'TOGGLE_PURCHASE_SUCCESS': 126 | 127 | return { 128 | ...state, 129 | // fetching: false, 130 | // fetched: true, 131 | // message: action.message, 132 | // type: 'toggle', 133 | // success: true 134 | } 135 | 136 | case 'TOGGLE_PURCHASE_FAILED': 137 | return { 138 | ...state, 139 | // fetching: false, 140 | // fetched: true, 141 | error: action.error, 142 | // message: action.message, 143 | type: 'toggle', 144 | // success: false 145 | } 146 | case 'DELETE_PURCHASE_PENDING': 147 | return { 148 | ...state, 149 | fetching: true, 150 | } 151 | case 'DELETE_PURCHASE_SUCCESS': 152 | 153 | return { 154 | ...state, 155 | fetching: false, 156 | fetched: true, 157 | type: 'delete', 158 | message: action.message, 159 | success: true 160 | } 161 | 162 | case 'DELETE_PURCHASE_FAILED': 163 | return { 164 | ...state, 165 | fetching: false, 166 | fetched: true, 167 | error: action.error, 168 | message: action.message, 169 | type: 'delete', 170 | success: false 171 | } 172 | 173 | default: 174 | return state 175 | } 176 | } 177 | 178 | export default purchaseReducer -------------------------------------------------------------------------------- /src/serviceWorker.js: -------------------------------------------------------------------------------- 1 | // This optional code is used to register a service worker. 2 | // register() is not called by default. 3 | 4 | // This lets the app load faster on subsequent visits in production, and gives 5 | // it offline capabilities. However, it also means that developers (and users) 6 | // will only see deployed updates on subsequent visits to a page, after all the 7 | // existing tabs open on the page have been closed, since previously cached 8 | // resources are updated in the background. 9 | 10 | // To learn more about the benefits of this model and instructions on how to 11 | // opt-in, read https://bit.ly/CRA-PWA 12 | 13 | const isLocalhost = Boolean( 14 | window.location.hostname === 'localhost' || 15 | // [::1] is the IPv6 localhost address. 16 | window.location.hostname === '[::1]' || 17 | // 127.0.0.0/8 are considered localhost for IPv4. 18 | window.location.hostname.match( 19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ 20 | ) 21 | ); 22 | 23 | export function register(config) { 24 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { 25 | // The URL constructor is available in all browsers that support SW. 26 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href); 27 | if (publicUrl.origin !== window.location.origin) { 28 | // Our service worker won't work if PUBLIC_URL is on a different origin 29 | // from what our page is served on. This might happen if a CDN is used to 30 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374 31 | return; 32 | } 33 | 34 | window.addEventListener('load', () => { 35 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; 36 | 37 | if (isLocalhost) { 38 | // This is running on localhost. Let's check if a service worker still exists or not. 39 | checkValidServiceWorker(swUrl, config); 40 | 41 | // Add some additional logging to localhost, pointing developers to the 42 | // service worker/PWA documentation. 43 | navigator.serviceWorker.ready.then(() => { 44 | console.log( 45 | 'This web app is being served cache-first by a service ' + 46 | 'worker. To learn more, visit https://bit.ly/CRA-PWA' 47 | ); 48 | }); 49 | } else { 50 | // Is not localhost. Just register service worker 51 | registerValidSW(swUrl, config); 52 | } 53 | }); 54 | } 55 | } 56 | 57 | function registerValidSW(swUrl, config) { 58 | navigator.serviceWorker 59 | .register(swUrl) 60 | .then(registration => { 61 | registration.onupdatefound = () => { 62 | const installingWorker = registration.installing; 63 | if (installingWorker == null) { 64 | return; 65 | } 66 | installingWorker.onstatechange = () => { 67 | if (installingWorker.state === 'installed') { 68 | if (navigator.serviceWorker.controller) { 69 | // At this point, the updated precached content has been fetched, 70 | // but the previous service worker will still serve the older 71 | // content until all client tabs are closed. 72 | console.log( 73 | 'New content is available and will be used when all ' + 74 | 'tabs for this page are closed. See https://bit.ly/CRA-PWA.' 75 | ); 76 | 77 | // Execute callback 78 | if (config && config.onUpdate) { 79 | config.onUpdate(registration); 80 | } 81 | } else { 82 | // At this point, everything has been precached. 83 | // It's the perfect time to display a 84 | // "Content is cached for offline use." message. 85 | console.log('Content is cached for offline use.'); 86 | 87 | // Execute callback 88 | if (config && config.onSuccess) { 89 | config.onSuccess(registration); 90 | } 91 | } 92 | } 93 | }; 94 | }; 95 | }) 96 | .catch(error => { 97 | console.error('Error during service worker registration:', error); 98 | }); 99 | } 100 | 101 | function checkValidServiceWorker(swUrl, config) { 102 | // Check if the service worker can be found. If it can't reload the page. 103 | fetch(swUrl, { 104 | headers: { 'Service-Worker': 'script' } 105 | }) 106 | .then(response => { 107 | // Ensure service worker exists, and that we really are getting a JS file. 108 | const contentType = response.headers.get('content-type'); 109 | if ( 110 | response.status === 404 || 111 | (contentType != null && contentType.indexOf('javascript') === -1) 112 | ) { 113 | // No service worker found. Probably a different app. Reload the page. 114 | navigator.serviceWorker.ready.then(registration => { 115 | registration.unregister().then(() => { 116 | window.location.reload(); 117 | }); 118 | }); 119 | } else { 120 | // Service worker found. Proceed as normal. 121 | registerValidSW(swUrl, config); 122 | } 123 | }) 124 | .catch(() => { 125 | console.log( 126 | 'No internet connection found. App is running in offline mode.' 127 | ); 128 | }); 129 | } 130 | 131 | export function unregister() { 132 | if ('serviceWorker' in navigator) { 133 | navigator.serviceWorker.ready.then(registration => { 134 | registration.unregister(); 135 | }); 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /src/store/reducer/discountReducer.js: -------------------------------------------------------------------------------- 1 | const initState = { 2 | fetching: false, 3 | fetched: false, 4 | message: null, 5 | data: null, 6 | error: null, 7 | success: null, 8 | type: null, 9 | discount: null, 10 | selected: null 11 | } 12 | 13 | const discountReducer = (state = initState, action) => { 14 | switch (action.type) { 15 | case 'FETCH_DISCOUNT_PENDING': 16 | return { 17 | ...state, 18 | fetching: true, 19 | error: null 20 | } 21 | case 'FETCH_DISCOUNT_SUCCESS': 22 | 23 | return { 24 | ...state, 25 | fetching: false, 26 | fetched: true, 27 | data: action.data, 28 | selected: action.selected, 29 | success: true, 30 | type: 'fetch' 31 | } 32 | 33 | case 'FETCH_DISCOUNT_FAILED': 34 | return { 35 | ...state, 36 | fetching: false, 37 | fetched: true, 38 | error: action.error, 39 | message: action.error.data.message, 40 | success: false, 41 | type: 'fetch' 42 | } 43 | case 'SAVE_DISCOUNT_PENDING': 44 | return { 45 | ...state, 46 | fetching: true, 47 | } 48 | case 'SAVE_DISCOUNT_SUCCESS': 49 | 50 | return { 51 | ...state, 52 | fetching: false, 53 | fetched: true, 54 | message: action.message, 55 | type: 'save', 56 | success: true 57 | } 58 | 59 | case 'SAVE_DISCOUNT_FAILED': 60 | return { 61 | ...state, 62 | fetching: false, 63 | fetched: true, 64 | error: action.error, 65 | message: action.message, 66 | type: 'save', 67 | success: false 68 | } 69 | case 'GET_DISCOUNT_PENDING': 70 | return { 71 | ...state, 72 | fetching: true, 73 | } 74 | case 'GET_DISCOUNT_SUCCESS': 75 | 76 | return { 77 | ...state, 78 | fetching: false, 79 | fetched: true, 80 | type: 'get', 81 | discount: action.data, 82 | success: true 83 | } 84 | 85 | case 'GET_DISCOUNT_FAILED': 86 | return { 87 | ...state, 88 | fetching: false, 89 | fetched: true, 90 | error: action.error, 91 | message: action.message, 92 | type: 'get', 93 | success: false 94 | } 95 | case 'UPDATE_DISCOUNT_PENDING': 96 | return { 97 | ...state, 98 | fetching: true, 99 | } 100 | case 'UPDATE_DISCOUNT_SUCCESS': 101 | 102 | return { 103 | ...state, 104 | fetching: false, 105 | fetched: true, 106 | message: action.message, 107 | type: 'update', 108 | success: true 109 | } 110 | 111 | case 'UPDATE_DISCOUNT_FAILED': 112 | return { 113 | ...state, 114 | fetching: false, 115 | fetched: true, 116 | error: action.error, 117 | message: action.message, 118 | type: 'update', 119 | success: false 120 | } 121 | case 'TOGGLE_DISCOUNT_PENDING': 122 | return { 123 | ...state, 124 | // fetching: true, 125 | } 126 | case 'TOGGLE_DISCOUNT_SUCCESS': 127 | 128 | return { 129 | ...state, 130 | // fetching: false, 131 | // fetched: true, 132 | // message: action.message, 133 | // type: 'toggle', 134 | // success: true 135 | } 136 | 137 | case 'TOGGLE_DISCOUNT_FAILED': 138 | return { 139 | ...state, 140 | // fetching: false, 141 | // fetched: true, 142 | error: action.error, 143 | // message: action.message, 144 | type: 'toggle', 145 | // success: false 146 | } 147 | case 'DELETE_DISCOUNT_PENDING': 148 | return { 149 | ...state, 150 | fetching: true, 151 | } 152 | case 'DELETE_DISCOUNT_SUCCESS': 153 | 154 | return { 155 | ...state, 156 | fetching: false, 157 | fetched: true, 158 | type: 'delete', 159 | message: action.message, 160 | success: true 161 | } 162 | 163 | case 'DELETE_DISCOUNT_FAILED': 164 | return { 165 | ...state, 166 | fetching: false, 167 | fetched: true, 168 | error: action.error, 169 | message: action.message, 170 | type: 'delete', 171 | success: false 172 | } 173 | case 'SELECT_DISCOUNT_PENDING': 174 | return { 175 | ...state, 176 | // fetching: true, 177 | } 178 | case 'SELECT_DISCOUNT_SUCCESS': 179 | 180 | return { 181 | ...state, 182 | selected: action.selected 183 | // fetching: false, 184 | // fetched: true, 185 | // message: action.message, 186 | // type: 'toggle', 187 | // success: true 188 | } 189 | 190 | case 'SELECT_DISCOUNT_FAILED': 191 | return { 192 | ...state, 193 | // fetching: false, 194 | // fetched: true, 195 | error: action.error, 196 | // message: action.message, 197 | type: 'toggle', 198 | // success: false 199 | } 200 | default: 201 | return state 202 | } 203 | } 204 | 205 | export default discountReducer -------------------------------------------------------------------------------- /src/views/Login.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { checkUser, login } from '../store/actions/AuthActions' 3 | import { connect } from 'react-redux' 4 | import Error from './Errors/Error' 5 | import Loading from '../components/Loading' 6 | import { withToastManager } from 'react-toast-notifications' 7 | 8 | class Login extends Component { 9 | 10 | state = { 11 | agree: false, 12 | email: 'aplikasikasirkita@gmail.com', 13 | password: 'secret' 14 | } 15 | 16 | componentDidMount = () => { 17 | this.props.checkUser() 18 | } 19 | 20 | componentDidUpdate = (prevProps) => { 21 | const { toastManager } = this.props; 22 | 23 | if (prevProps.checkUserExists !== this.props.checkUserExists) { 24 | if (!this.props.checkUserExists) 25 | return this.props.history.push('/welcome') 26 | } 27 | 28 | if (prevProps.loginFetching !== this.props.loginFetching ) { 29 | 30 | 31 | if (this.props.loginFetched) { 32 | 33 | if (this.props.loginSuccess) { 34 | 35 | toastManager.add(this.props.loginMessage, { 36 | appearance: 'success', 37 | autoDismiss: true 38 | }); 39 | 40 | return this.props.history.push(this.props.loginRedirect) 41 | 42 | } else { 43 | 44 | toastManager.add(this.props.loginMessage, { 45 | appearance: 'error', 46 | autoDismiss: true 47 | }); 48 | } 49 | 50 | 51 | } 52 | } 53 | } 54 | 55 | 56 | handleChange = (name) => (e) => { 57 | this.setState({ 58 | ...this.state, 59 | [name]: e.target.value 60 | }) 61 | } 62 | 63 | handleSubmit = (e) => { 64 | e.preventDefault() 65 | this.props.login(this.state) 66 | } 67 | 68 | handleReset = () => { 69 | this.setState({ 70 | agree: false, 71 | email: '', 72 | password: '' 73 | }) 74 | } 75 | 76 | render() { 77 | 78 | const { 79 | checkFetching, 80 | checkError, 81 | loginFetching, 82 | loginError 83 | 84 | } = this.props 85 | 86 | const { email, password } = this.state 87 | 88 | const error = loginError && loginError.data && loginError.data.errors 89 | 90 | if (checkError && checkError.status !== 422) 91 | return 92 | 93 | return ( 94 |
95 | { 96 | checkFetching && ( 97 | 98 | ) 99 | } 100 |
101 |

Masuk

102 |

Silahkan masukan username dan password

103 |
104 |
105 |
106 | 107 | 108 | { 109 | error && error.email && ( 110 |
{ error.email[0] }
111 | ) 112 | } 113 |
114 |
115 | 116 | 117 | { 118 | error && error.password && ( 119 |
{ error.password[0] }
120 | ) 121 | } 122 |
123 |
124 | { 125 | loginFetching ? ( 126 | 127 | ) : ( 128 | 129 | ) 130 | } 131 | 132 |
133 |
134 |
135 |
136 |
137 | ) 138 | } 139 | } 140 | 141 | const mapStateToProps = (state) => { 142 | return { 143 | checkFetching: state.checkUser.fetching, 144 | checkFetched: state.checkUser.fetched, 145 | checkUserExists: state.checkUser.userExists, 146 | checkError: state.checkUser.error, 147 | loginFetching: state.login.fetching, 148 | loginFetched: state.login.fetched, 149 | loginMessage: state.login.message, 150 | loginError: state.login.error, 151 | loginSuccess: state.login.success, 152 | loginRedirect: state.login.redirect 153 | } 154 | } 155 | 156 | const mapDispatchToProps = (dispatch) => { 157 | return { 158 | checkUser: () => dispatch(checkUser()), 159 | login: (data) => dispatch(login(data)) 160 | } 161 | } 162 | 163 | export default connect(mapStateToProps, mapDispatchToProps)(withToastManager(Login)) 164 | -------------------------------------------------------------------------------- /src/store/actions/AuthActions.js: -------------------------------------------------------------------------------- 1 | import Axios from 'axios' 2 | import { url } from '../../global' 3 | 4 | const checkUser = () => { 5 | return (dispatch, getState) => { 6 | 7 | dispatch({ 8 | type: 'CHECK_USER_PENDING' 9 | }) 10 | 11 | Axios.get(`${url}/check`).then(res => { 12 | dispatch({ 13 | type: 'CHECK_USER_SUCCESS', 14 | userExists: res.data.user_exists 15 | }) 16 | }).catch(error => { 17 | 18 | if (!error.response) { 19 | dispatch({ 20 | type: 'CHECK_USER_FAILED', 21 | error: { 22 | status: null, 23 | connection: true, 24 | statusText: 'Koneksi Terputus', 25 | data: { 26 | message: 'Silahkan periksa koneksi backend, lihat tutorial di sini https://github.com/kasirkita/Kasir-Kita' 27 | } 28 | } 29 | }) 30 | 31 | } else { 32 | 33 | dispatch({ 34 | type: 'CHECK_USER_FAILED', 35 | error: error.response 36 | }) 37 | } 38 | 39 | }) 40 | } 41 | } 42 | 43 | const register = (data) => { 44 | return async (dispatch, getState) => { 45 | 46 | const { 47 | name, 48 | email, 49 | password, 50 | password_confirmation 51 | } = data 52 | 53 | dispatch({ 54 | type: 'REGISTER_PENDING' 55 | }) 56 | 57 | await Axios.post(`${url}/register`, { 58 | 59 | name, 60 | email, 61 | password, 62 | password_confirmation 63 | 64 | }).then(res => { 65 | 66 | const setting = res.data.setting 67 | sessionStorage.setItem('decimal_separator', setting ? setting.decimal_separator : '.') 68 | sessionStorage.setItem('thousand_separator', setting ? setting.thousand_separator : '.' ) 69 | sessionStorage.setItem('currency', setting ? setting.currency : '') 70 | sessionStorage.setItem('printer', setting ? setting.printer : '') 71 | sessionStorage.setItem('tax', setting ? setting.tax : '') 72 | sessionStorage.setItem('logo', setting.logo_url) 73 | sessionStorage.setItem('logo_remove', setting.logo_remove ? setting.logo_remove : false) 74 | sessionStorage.setItem('shop_name', setting.name ? setting.name : '') 75 | sessionStorage.setItem('address', setting.address ? setting.address : '') 76 | sessionStorage.setItem('phone_number', setting.phone_number ? setting.phone_number : '') 77 | sessionStorage.setItem('divider', setting.divider ? setting.divider : '') 78 | 79 | 80 | dispatch({ 81 | type: 'REGISTER_SUCCESS', 82 | token: res.data.token, 83 | data: res.data.data, 84 | message: res.data.message 85 | }) 86 | }).catch(error => { 87 | if (!error.response) { 88 | dispatch({ 89 | type: 'REGISTER_FAILED', 90 | error: { 91 | status: null, 92 | connection: true, 93 | statusText: 'Koneksi Terputus', 94 | data: { 95 | message: 'Silahkan periksa koneksi backend, lihat tutorial di sini https://github.com/kasirkita/Kasir-Kita' 96 | } 97 | } 98 | }) 99 | 100 | } else { 101 | 102 | dispatch({ 103 | type: 'REGISTER_FAILED', 104 | error: error.response 105 | }) 106 | } 107 | }) 108 | } 109 | } 110 | 111 | 112 | const login = (data) => { 113 | return (dispatch, getState) => { 114 | 115 | const { 116 | email, 117 | password 118 | } = data 119 | 120 | dispatch({ 121 | type: 'LOGIN_PENDING' 122 | }) 123 | 124 | Axios.post(`${url}/login`, { 125 | email, 126 | password 127 | 128 | }).then(res => { 129 | 130 | const setting = res.data.setting 131 | sessionStorage.setItem('decimal_separator', setting ? setting.decimal_separator : '.') 132 | sessionStorage.setItem('thousand_separator', setting ? setting.thousand_separator : '.' ) 133 | sessionStorage.setItem('currency', setting ? setting.currency : '') 134 | sessionStorage.setItem('printer', setting ? setting.printer : '') 135 | sessionStorage.setItem('tax', setting ? setting.tax : '') 136 | sessionStorage.setItem('logo', setting.logo_url) 137 | sessionStorage.setItem('logo_remove', setting.logo_remove ? setting.logo_remove : false) 138 | sessionStorage.setItem('shop_name', setting.name ? setting.name : '') 139 | sessionStorage.setItem('address', setting.address ? setting.address : '') 140 | sessionStorage.setItem('phone_number', setting.phone_number ? setting.phone_number : '') 141 | sessionStorage.setItem('divider', setting.divider ? setting.divider : '') 142 | 143 | dispatch({ 144 | type: 'LOGIN_SUCCESS', 145 | token: res.data.token, 146 | data: res.data.data, 147 | message: res.data.message, 148 | permissions: res.data.permissions, 149 | redirect: res.data.redirect 150 | }) 151 | }).catch(error => { 152 | if (!error.response) { 153 | dispatch({ 154 | type: 'LOGIN_FAILED', 155 | error: { 156 | status: null, 157 | connection: true, 158 | statusText: 'Koneksi Terputus', 159 | data: { 160 | message: 'Silahkan periksa koneksi backend, lihat tutorial di sini https://github.com/kasirkita/Kasir-Kita' 161 | } 162 | } 163 | }) 164 | 165 | } else { 166 | 167 | dispatch({ 168 | type: 'LOGIN_FAILED', 169 | error: error.response 170 | }) 171 | } 172 | }) 173 | } 174 | } 175 | 176 | export { checkUser, register, login } -------------------------------------------------------------------------------- /src/views/Supplier/AddSupplier.js: -------------------------------------------------------------------------------- 1 | import React, { Component, Fragment } from 'react' 2 | import { Link } from 'react-router-dom' 3 | import { saveSupplier } from '../../store/actions/SupplierActions' 4 | import { connect } from 'react-redux' 5 | import { withToastManager } from 'react-toast-notifications' 6 | import Error from '../Errors/Error' 7 | 8 | class AddSupplier extends Component { 9 | 10 | state = { 11 | name: '', 12 | email: '', 13 | phone_number: '', 14 | address: '' 15 | } 16 | 17 | handleSave = () => { 18 | this.props.saveSupplier(this.state) 19 | } 20 | 21 | handleReset = () => { 22 | 23 | this.setState({ 24 | name: '', 25 | email: '', 26 | phone_number: '', 27 | address: '' 28 | }) 29 | } 30 | 31 | handleChange = (name) => (e) => { 32 | this.setState({ 33 | ...this.state, 34 | [name]: e.target.value 35 | }) 36 | } 37 | 38 | componentDidUpdate = (prevProps) => { 39 | 40 | const { toastManager } = this.props; 41 | 42 | if (prevProps.type !== this.props.type || prevProps.success !== this.props.success) { 43 | if (this.props.type === 'save') { 44 | 45 | if (this.props.success) { 46 | 47 | toastManager.add(this.props.message, { 48 | appearance: 'success', 49 | autoDismiss: true 50 | }); 51 | 52 | return this.props.history.push('/suppler') 53 | 54 | } else { 55 | 56 | toastManager.add(this.props.message, { 57 | appearance: 'error', 58 | autoDismiss: true 59 | }); 60 | } 61 | } 62 | } 63 | } 64 | 65 | 66 | render() { 67 | 68 | const { error, fetching } = this.props 69 | 70 | const { 71 | name, 72 | email, 73 | phone_number, 74 | address 75 | } = this.state 76 | 77 | const validate = error && error.data && error.data.errors 78 | 79 | if (error && error.status !== 422) 80 | return 81 | 82 | return ( 83 | 84 |
85 | 86 |
87 |
88 |
89 |

Tambah Pemasok

90 |
91 |
92 | Kembali 93 |
94 |
95 |
96 |
97 | 98 | 99 |
100 |
101 | 102 | 103 | { 104 | validate && validate.name && ( 105 |
{ validate.name[0] }
106 | ) 107 | } 108 |
109 | 110 | 111 |
112 | 113 | 114 | { 115 | validate && validate.email && ( 116 |
{ validate.email[0] }
117 | ) 118 | } 119 |
120 | 121 |
122 | 123 | 124 |
125 | 126 | 127 | 128 |
129 |
130 | 131 |
132 | 133 |