├── .env.example
├── .eslintignore
├── .eslintrc.json
├── .gitignore
├── README.md
├── package-lock.json
├── package.json
├── public
├── favicon.ico
├── index.html
├── logo.png
├── logo192.png
├── logo512.png
├── manifest.json
└── robots.txt
├── src
├── App.test.tsx
├── assets
│ └── icons
│ │ ├── ic_google.svg
│ │ ├── logo.png
│ │ └── logo.svg
├── components
│ ├── buttons
│ │ ├── ButtonIcon.tsx
│ │ └── ButtonPrimary.tsx
│ ├── cards
│ │ ├── Card.tsx
│ │ ├── CardSkelton.tsx
│ │ └── CardTransaction.tsx
│ ├── forms
│ │ └── Login.form.tsx
│ ├── index.tsx
│ ├── layouts
│ │ ├── Auth.container.tsx
│ │ ├── Login.container.tsx
│ │ └── ScreenContainer.container.tsx
│ ├── navbar
│ │ └── Navbar.tsx
│ ├── print
│ │ ├── ComponentToPrint.tsx
│ │ ├── PrintStruct.tsx
│ │ └── styles.css
│ ├── table
│ │ └── TableItems.tsx
│ └── tabs
│ │ └── BottomTabs.tsx
├── global.d.ts
├── hooks
│ └── index.ts
├── index.tsx
├── pages
│ ├── App.tsx
│ ├── index.tsx
│ ├── login
│ │ └── index.tsx
│ ├── sales
│ │ └── index.tsx
│ └── transaction
│ │ └── index.tsx
├── react-app-env.d.ts
├── redux
│ ├── auth
│ │ └── authSlice.ts
│ ├── cart
│ │ └── cartSlice.ts
│ └── store.ts
├── reportWebVitals.ts
├── routes
│ ├── PrivateRoute.tsx
│ └── index.ts
├── service
│ ├── api
│ │ └── index.ts
│ └── supabase
│ │ ├── connection.ts
│ │ └── transaction.ts
├── setupTests.ts
├── themes
│ └── theme.ts
├── types
│ ├── auth.type.ts
│ ├── cart.type.ts
│ └── transaction.type.ts
└── utils
│ ├── contstant.ts
│ └── index.ts
├── tsconfig.json
└── yarn.lock
/.env.example:
--------------------------------------------------------------------------------
1 | REACT_APP_GOOGLE_CLIENT_ID=
2 | REACT_APP_GOOGLE_CLIENT_SECRET=
3 | REACT_APP_SUPABASE_URL=
4 | REACT_APP_SUPABASE_KEY=
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | src/registerServiceWorker.js
2 | src/**/__tests__/**
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "parser": "@typescript-eslint/parser",
3 | "plugins": ["@typescript-eslint"],
4 | "extends": [
5 | "eslint:recommended",
6 | "plugin:@typescript-eslint/eslint-recommended",
7 | "plugin:@typescript-eslint/recommended",
8 | "plugin:react/recommended"
9 | ],
10 | "rules": {
11 | "@typescript-eslint/no-unused-vars": [
12 | 2,
13 | {
14 | "vars": "all",
15 | "args": "all",
16 | "varsIgnorePattern": "^jsx$",
17 | "argsIgnorePattern": "[Ii]gnored$"
18 | }
19 | ],
20 | "@typescript-eslint/no-use-before-define": "off",
21 | "@typescript-eslint/no-explicit-any": "off",
22 | "@typescript-eslint/explicit-module-boundary-types": "off"
23 | },
24 | "overrides": [
25 | {
26 | // enable the rule specifically for TypeScript files
27 | "files": ["*.ts", "*.tsx"],
28 | "rules": {
29 | "@typescript-eslint/explicit-module-boundary-types": ["error"]
30 | }
31 | }
32 | ]
33 | }
34 |
--------------------------------------------------------------------------------
/.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 | .env
14 |
15 | # misc
16 | .DS_Store
17 | .env.local
18 | .env.development.local
19 | .env.test.local
20 | .env.production.local
21 |
22 | npm-debug.log*
23 | yarn-debug.log*
24 | yarn-error.log*
25 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Simple POS Sesukamu
2 |
3 | A simple Point Of Sales application built using React & Typescript
4 |
5 | ## Getting Started
6 |
7 | To get started running the project locally, please follow the steps bello.
8 |
9 | First , clonse this repository
10 |
11 | ```bash
12 | git clone https://github.com/revell29/simple-pos-sesukamu.git
13 | ```
14 |
15 | Then, install dependencies and fetch data to your local machine. **Note that we use Yarn, not npm. Make sure you using Node version 14 up.**
16 |
17 | ```bash
18 | cd simple-pos-sesukamu
19 | yarn install
20 | ```
21 |
22 | Finally, run the development server.abs
23 |
24 | ```bash
25 | yarn start
26 | ```
27 |
28 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
29 |
30 | ### Frequently Used Tools
31 |
32 | - [Typescript](https://www.typescriptlang.org/)
33 | - [Create-React-App](https://create-react-app.dev/docs/adding-typescript/)
34 | - [Chakra-UI](https://chakra-ui.com/)
35 | - [Measure Web Performance](https://web.dev/measure)
36 | - [Supabase.io](https://supabase.io)
37 |
38 | ### Features
39 |
40 | - Create Transaction & Show Transaction ✅
41 | - Print Struct ✅
42 |
43 | ## Notes
44 |
45 | This is still work in progress and you can contribute in this project
46 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "pos-sesukamu",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@chakra-ui/icons": "^1.0.15",
7 | "@chakra-ui/react": "^1.6.7",
8 | "@emotion/react": "^11",
9 | "@emotion/styled": "^11",
10 | "@reduxjs/toolkit": "^1.6.1",
11 | "@supabase/gotrue-js": "^1.18.0",
12 | "@supabase/supabase-js": "^1.22.6",
13 | "@testing-library/jest-dom": "^5.11.4",
14 | "@testing-library/react": "^11.1.0",
15 | "@testing-library/user-event": "^12.1.10",
16 | "@types/jest": "^26.0.15",
17 | "@types/node": "^12.0.0",
18 | "@types/react-dom": "^17.0.0",
19 | "@types/react-router-dom": "^5.1.9",
20 | "axios": "^0.21.4",
21 | "framer-motion": "^4",
22 | "react": "^17.0.2",
23 | "react-dom": "^17.0.2",
24 | "react-redux": "^7.2.5",
25 | "react-router-dom": "^5.3.0",
26 | "react-scripts": "4.0.3",
27 | "react-social-login": "^3.4.14",
28 | "react-spring-bottom-sheet": "^3.4.0",
29 | "react-to-print": "^2.13.0",
30 | "reactjs-social-login": "^1.4.2",
31 | "typescript": "^4.1.2",
32 | "web-vitals": "^1.0.1"
33 | },
34 | "scripts": {
35 | "start": "react-scripts start",
36 | "build": "react-scripts build",
37 | "test": "react-scripts test",
38 | "eject": "react-scripts eject",
39 | "lint:fix": "eslint './src/**/*.{ts,tsx}'"
40 | },
41 | "eslintConfig": {
42 | "extends": [
43 | "react-app",
44 | "react-app/jest"
45 | ]
46 | },
47 | "browserslist": {
48 | "production": [
49 | ">0.2%",
50 | "not dead",
51 | "not op_mini all"
52 | ],
53 | "development": [
54 | "last 1 chrome version",
55 | "last 1 firefox version",
56 | "last 1 safari version"
57 | ]
58 | },
59 | "devDependencies": {
60 | "@types/react": "^17.0.21",
61 | "@typescript-eslint/eslint-plugin": "^4.31.1",
62 | "@typescript-eslint/parser": "^4.31.1",
63 | "eslint": "^7.32.0",
64 | "eslint-config-airbnb": "^18.2.1",
65 | "eslint-plugin-import": "^2.24.2",
66 | "eslint-plugin-jsx-a11y": "^6.4.1",
67 | "eslint-plugin-react": "^7.25.3",
68 | "eslint-plugin-react-hooks": "^4.2.0"
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/revell29/simple-pos-sesukamu/044b09d75bfb673dcbc09035e5356fa06b1b496a/public/favicon.ico
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
13 |
14 |
23 | Sesukamu T-Shirt Printing
24 |
25 |
26 |
27 |
28 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/public/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/revell29/simple-pos-sesukamu/044b09d75bfb673dcbc09035e5356fa06b1b496a/public/logo.png
--------------------------------------------------------------------------------
/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/revell29/simple-pos-sesukamu/044b09d75bfb673dcbc09035e5356fa06b1b496a/public/logo192.png
--------------------------------------------------------------------------------
/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/revell29/simple-pos-sesukamu/044b09d75bfb673dcbc09035e5356fa06b1b496a/public/logo512.png
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "Sesukamu POS",
3 | "name": "Point Of Sales Sesukamu",
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 |
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/src/App.test.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { render, screen } from "@testing-library/react";
3 | import App from "./pages/App";
4 |
5 | test("renders learn react link", () => {
6 | render();
7 | const linkElement = screen.getByText(/learn react/i);
8 | expect(linkElement).toBeInTheDocument();
9 | });
10 |
--------------------------------------------------------------------------------
/src/assets/icons/ic_google.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icons/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/revell29/simple-pos-sesukamu/044b09d75bfb673dcbc09035e5356fa06b1b496a/src/assets/icons/logo.png
--------------------------------------------------------------------------------
/src/assets/icons/logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/components/buttons/ButtonIcon.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Button, ButtonProps } from "@chakra-ui/react";
3 |
4 | interface IButtonIcon extends ButtonProps {
5 | children: React.ReactNode;
6 | }
7 |
8 | const ButtonIcon: React.FC = ({
9 | children,
10 | ...rest
11 | }: IButtonIcon) => {
12 | return (
13 |
16 | );
17 | };
18 |
19 | export default ButtonIcon;
20 |
--------------------------------------------------------------------------------
/src/components/buttons/ButtonPrimary.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Button, ButtonProps } from "@chakra-ui/react";
3 |
4 | interface IButtonPrimary extends ButtonProps {
5 | children: React.ReactNode;
6 | }
7 |
8 | const ButtonPrimary: React.FC = ({
9 | children,
10 | ...rest
11 | }: IButtonPrimary) => {
12 | return (
13 |
24 | );
25 | };
26 |
27 | export default ButtonPrimary;
28 |
--------------------------------------------------------------------------------
/src/components/cards/Card.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Box, BoxProps } from "@chakra-ui/react";
3 |
4 | interface ICardProps extends BoxProps {
5 | children: React.ReactNode;
6 | }
7 |
8 | const Card: React.FC = (props: ICardProps) => {
9 | const { children, ...rest } = props;
10 |
11 | return (
12 |
20 | {children}
21 |
22 | );
23 | };
24 |
25 | export default Card;
26 |
--------------------------------------------------------------------------------
/src/components/cards/CardSkelton.tsx:
--------------------------------------------------------------------------------
1 | import { Card } from "components";
2 | import React from "react";
3 | import { SkeletonCircle, Skeleton, Flex } from "@chakra-ui/react";
4 |
5 | const CardSkelton: React.FC = () => {
6 | return (
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | );
17 | };
18 |
19 | export default CardSkelton;
20 |
--------------------------------------------------------------------------------
/src/components/cards/CardTransaction.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Card } from "components";
3 | import { Avatar, Flex, Heading, Box, Text, Button } from "@chakra-ui/react";
4 | import { ITransaction } from "types/transaction.type";
5 | import { currencyFormat, formatDate } from "utils";
6 | import ReactToPrint from "react-to-print";
7 | import ComponentToPrint from "components/print/ComponentToPrint";
8 | import { useAppDispatch } from "hooks";
9 | import { clearItems, printTransaction } from "redux/cart/cartSlice";
10 |
11 | interface ICardTransactionProps {
12 | transaction: ITransaction;
13 | }
14 |
15 | const CardTransaction: React.FC = (
16 | props: ICardTransactionProps
17 | ) => {
18 | const { transaction } = props;
19 | const componentRef = React.useRef(null);
20 | const dispatch = useAppDispatch();
21 |
22 | const reactToPrintContent = React.useCallback(() => {
23 | return componentRef.current;
24 | }, [componentRef.current]);
25 |
26 | const printTrigger = React.useCallback(() => {
27 | return (
28 |
31 | );
32 | }, []);
33 |
34 | const handleOnBeforeGetContent = React.useCallback(() => {
35 | return new Promise((resolve) => {
36 | setTimeout(() => {
37 | dispatch(printTransaction(transaction));
38 | resolve();
39 | }, 2000);
40 | });
41 | }, []);
42 |
43 | const handleAfterPrint = React.useCallback(() => {
44 | dispatch(clearItems());
45 | }, [dispatch]);
46 | return (
47 |
54 |
59 |
60 |
61 |
65 |
66 |
67 | {transaction.trx_number}
68 |
69 | {formatDate(transaction.transaction_date)}
70 |
71 |
72 | {transaction.customer_name}
73 |
74 |
75 |
76 |
77 |
84 | {currencyFormat(transaction.grand_total)}
85 |
86 |
87 |
93 |
94 |
95 |
96 |
97 | );
98 | };
99 |
100 | export default CardTransaction;
101 |
--------------------------------------------------------------------------------
/src/components/forms/Login.form.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Image } from "@chakra-ui/react";
3 | import { Card } from "components";
4 | import { ButtonIcon } from "components";
5 | import GoogleIcon from "assets/icons/ic_google.svg";
6 | import { supabase } from "service/supabase/connection";
7 |
8 | const LoginForm: React.FC = () => {
9 | const handleLogin = async () => {
10 | await supabase.auth.signIn(
11 | {
12 | provider: "google",
13 | },
14 | {
15 | redirectTo:
16 | process.env.NODE_ENV === "development"
17 | ? "http://localhost:3000/sales"
18 | : "https://sesukamu-pos.vercel.app/sales",
19 | }
20 | );
21 | };
22 |
23 | return (
24 |
25 | }
30 | >
31 | Masuk Ke Aplikasi
32 |
33 |
34 | );
35 | };
36 |
37 | export default LoginForm;
38 |
--------------------------------------------------------------------------------
/src/components/index.tsx:
--------------------------------------------------------------------------------
1 | export { default as Card } from "./cards/Card";
2 | export { default as LoginContainer } from "./layouts/Login.container";
3 | export { default as LoginForm } from "./forms/Login.form";
4 | export { default as ButtonPrimary } from "./buttons/ButtonPrimary";
5 | export { default as ButtonIcon } from "./buttons/ButtonIcon";
6 | export { default as AuthContainer } from "./layouts/Auth.container";
7 | export { default as Navbar } from "./navbar/Navbar";
8 | export { default as ScreenContainer } from "./layouts/ScreenContainer.container";
9 | export { default as CardTransaction } from "./cards/CardTransaction";
10 | export { default as CardSkelton } from "./cards/CardSkelton";
11 | export { default as TableItem } from "./table/TableItems";
12 | export { default as BottomTabs } from "./tabs/BottomTabs";
13 | export { default as PrintStruct } from "./print/PrintStruct";
14 |
--------------------------------------------------------------------------------
/src/components/layouts/Auth.container.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Box } from "@chakra-ui/react";
3 | import ApiRequest from "service/api";
4 | import { useAppDispatch } from "hooks";
5 | import { saveUserInfo } from "redux/auth/authSlice";
6 | import { Navbar } from "components";
7 | import { supabase } from "service/supabase/connection";
8 |
9 | interface IAuthContainerProps {
10 | children: React.ReactNode;
11 | }
12 |
13 | const AuthContainer: React.FC = ({
14 | children,
15 | }: IAuthContainerProps) => {
16 | const dispatch = useAppDispatch();
17 |
18 | const getInfo = React.useCallback(
19 | async (token) => {
20 | await ApiRequest.getUserInfo(token)
21 | .then((res) => {
22 | dispatch(saveUserInfo(res));
23 | })
24 | .catch((err) => {
25 | throw err;
26 | });
27 | },
28 | [dispatch]
29 | );
30 |
31 | React.useEffect(() => {
32 | const session = supabase.auth.session();
33 | if (session) {
34 | getInfo(session?.provider_token);
35 | }
36 | }, []);
37 |
38 | return (
39 | <>
40 |
41 | {children}
42 | >
43 | );
44 | };
45 |
46 | export default AuthContainer;
47 |
--------------------------------------------------------------------------------
/src/components/layouts/Login.container.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Flex } from "@chakra-ui/react";
3 |
4 | type LoginContainerProps = {
5 | children: React.ReactNode;
6 | };
7 |
8 | const LoginContainer: React.FC = ({
9 | children,
10 | }: LoginContainerProps) => {
11 | return (
12 |
21 | {children}
22 |
23 | );
24 | };
25 |
26 | export default LoginContainer;
27 |
--------------------------------------------------------------------------------
/src/components/layouts/ScreenContainer.container.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Box, BoxProps } from "@chakra-ui/react";
3 |
4 | interface IScreenContainerProps extends BoxProps {
5 | children: React.ReactNode;
6 | }
7 |
8 | const ScreenContainer: React.FC = ({
9 | children,
10 | ...rest
11 | }: IScreenContainerProps) => {
12 | return (
13 |
19 | {children}
20 |
21 | );
22 | };
23 |
24 | export default ScreenContainer;
25 |
--------------------------------------------------------------------------------
/src/components/navbar/Navbar.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import {
3 | Flex,
4 | Heading,
5 | Spacer,
6 | Image,
7 | Menu,
8 | MenuButton,
9 | MenuItem,
10 | MenuList,
11 | Text,
12 | } from "@chakra-ui/react";
13 | import { useAppSelector } from "hooks";
14 | import { supabase } from "service/supabase/connection";
15 |
16 | const Navbar: React.FC = () => {
17 | const authInfo = useAppSelector((state) => state.authSlice);
18 | return (
19 |
24 |
25 |
26 | Sesukamu
27 |
28 |
29 |
30 |
31 |
32 | {authInfo.name}
33 |
34 |
51 |
52 |
53 | );
54 | };
55 |
56 | export default React.memo(Navbar);
57 |
--------------------------------------------------------------------------------
/src/components/print/ComponentToPrint.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { PrintStruct } from "components";
3 |
4 | class ComponentToPrint extends React.Component {
5 | render(): JSX.Element {
6 | return ;
7 | }
8 | }
9 |
10 | export default ComponentToPrint;
11 |
--------------------------------------------------------------------------------
/src/components/print/PrintStruct.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Box, Image, Text, Flex } from "@chakra-ui/react";
3 | import Logo from "assets/icons/logo.png";
4 | import "./styles.css";
5 | import {
6 | currencyFormat,
7 | formatDate,
8 | getGrandTotal,
9 | getTotalItems,
10 | } from "utils";
11 | import { useAppSelector } from "hooks";
12 |
13 | const Divider = () => ;
14 |
15 | const PrintStruct: React.FC = () => {
16 | const cartSlice = useAppSelector((state) => state.cartSlice);
17 |
18 | return (
19 |
20 |
21 |
29 | Jl Bulak Jawa RT 003 RW 003
30 | Kel. Jatisari, Kec. Jatiasih
31 | Kota Bekasi
32 | +62 858-1716-6676
33 |
34 |
35 | No: {cartSlice.trx_number}
36 | Kasir: Syapirin
37 | Tanggal: {formatDate(cartSlice.transaction_date)}
38 | Customer: {cartSlice.customer_name}
39 |
40 |
41 | {cartSlice.items.map((item, index) => (
42 | <>
43 |
44 | {item.item_name}
45 |
46 |
47 | {item.qty}x {currencyFormat(item.amount)}
48 |
49 |
50 | {currencyFormat(item.qty * item.amount)}
51 |
52 |
53 |
54 |
55 | >
56 | ))}
57 |
58 |
59 | Subtotal
60 |
61 | {currencyFormat(
62 | getGrandTotal(cartSlice.items) * getTotalItems(cartSlice.items)
63 | )}
64 |
65 |
66 |
67 |
68 | TOTAL ({getTotalItems(cartSlice.items)} Barang)
69 |
70 |
71 | {currencyFormat(
72 | getGrandTotal(cartSlice.items) * getTotalItems(cartSlice.items)
73 | )}
74 |
75 |
76 |
77 |
78 |
79 |
80 |
99 | @kaos_sesukamu
100 |
101 |
102 | Terimakasih telah berbelanja ditoko kami.
103 |
104 |
105 |
106 | );
107 | };
108 | export default PrintStruct;
109 |
--------------------------------------------------------------------------------
/src/components/print/styles.css:
--------------------------------------------------------------------------------
1 | @import url("https://fonts.googleapis.com/css2?family=Courier+Prime&display=swap");
2 |
3 | @page {
4 | margin: 0;
5 | }
6 |
7 | @media print {
8 | .print {
9 | width: 264px;
10 | font-family: Courier Prime, monospace;
11 | margin: auto;
12 | font-weight: 500;
13 | font-size: 0.9rem;
14 | text-size-adjust: none;
15 | -webkit-text-size-adjust: none;
16 | -moz-text-size-adjust: none;
17 | -ms-text-size-adjust: none;
18 | }
19 |
20 | .hidden-print,
21 | .hidden-print * {
22 | display: none !important;
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/components/table/TableItems.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import {
3 | Button,
4 | Box,
5 | Flex,
6 | Text,
7 | FormControl,
8 | Input,
9 | FormLabel,
10 | } from "@chakra-ui/react";
11 | import { useAppSelector, useAppDispatch } from "hooks";
12 | import { ItemCartType } from "types/cart.type";
13 | import { BottomSheet } from "react-spring-bottom-sheet";
14 | import "react-spring-bottom-sheet/dist/style.css";
15 | import { currencyFormat, generateNumber } from "utils";
16 | import { addItems, deleteItems } from "redux/cart/cartSlice";
17 | import { Card } from "components";
18 | import { CloseIcon } from "@chakra-ui/icons";
19 |
20 | const TableItem: React.FC = () => {
21 | const initialState = {
22 | item_id: generateNumber(),
23 | item_name: "",
24 | qty: 0,
25 | amount: 0,
26 | };
27 | const [open, setOpen] = React.useState(false);
28 | const dispatch = useAppDispatch();
29 | const cartSlice = useAppSelector((state) => state.cartSlice);
30 | const [items, setItems] = React.useState(initialState);
31 |
32 | const addItem = () => {
33 | if (items.item_name !== "") {
34 | setOpen(false);
35 | dispatch(addItems(items));
36 | setItems(initialState);
37 | }
38 | };
39 |
40 | return (
41 | <>
42 |
43 |
46 |
47 |
48 | {cartSlice.items.length > 0 &&
49 | cartSlice.items.map((item, index) => (
50 |
51 |
52 |
53 |
54 | {item.item_name}
55 |
56 |
57 | x{item.qty}
58 |
59 |
60 |
61 |
62 | {currencyFormat(item.amount)}
63 |
71 |
72 |
73 |
74 |
75 | ))}
76 |
77 | setOpen(false)}>
78 |
79 |
80 | Nama Item
81 |
83 | setItems({ ...items, item_name: e.target.value })
84 | }
85 | value={items.item_name}
86 | />
87 |
88 |
89 | Qty
90 |
93 | setItems({ ...items, qty: Number(e.target.value) })
94 | }
95 | value={items.qty}
96 | />
97 |
98 |
99 | Total
100 |
103 | setItems({ ...items, amount: Number(e.target.value) })
104 | }
105 | value={items.amount}
106 | />
107 |
108 |
111 |
112 |
113 | >
114 | );
115 | };
116 |
117 | export default TableItem;
118 |
--------------------------------------------------------------------------------
/src/components/tabs/BottomTabs.tsx:
--------------------------------------------------------------------------------
1 | import { Box, Flex, Text } from "@chakra-ui/react";
2 | import React from "react";
3 | import { currencyFormat } from "utils";
4 |
5 | interface BottomTabsProps {
6 | total: number;
7 | }
8 |
9 | const BottomTabs: React.FC = (props: BottomTabsProps) => {
10 | const { total } = props;
11 | return (
12 |
24 |
25 |
26 | Grand Total
27 |
28 |
29 | {currencyFormat(total)}
30 |
31 |
32 |
33 | );
34 | };
35 |
36 | export default BottomTabs;
37 |
--------------------------------------------------------------------------------
/src/global.d.ts:
--------------------------------------------------------------------------------
1 | declare module "reactjs-social-login";
2 |
--------------------------------------------------------------------------------
/src/hooks/index.ts:
--------------------------------------------------------------------------------
1 | import { TypedUseSelectorHook, useDispatch, useSelector } from "react-redux";
2 | import type { RootState, AppDispatch } from "redux/store";
3 |
4 | // Use throughout your app instead of plain `useDispatch` and `useSelector`
5 | export const useAppDispatch = (): any => useDispatch();
6 | export const useAppSelector: TypedUseSelectorHook = useSelector;
7 |
--------------------------------------------------------------------------------
/src/index.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom";
3 | import App from "pages/App";
4 | import reportWebVitals from "./reportWebVitals";
5 | import { ChakraProvider } from "@chakra-ui/react";
6 | import theme from "themes/theme";
7 | import { store } from "redux/store";
8 | import { Provider } from "react-redux";
9 |
10 | ReactDOM.render(
11 |
12 |
13 |
14 |
15 |
16 |
17 | ,
18 | document.getElementById("root")
19 | );
20 |
21 | // If you want to start measuring performance in your app, pass a function
22 | // to log results (for example: reportWebVitals(console.log))
23 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
24 | reportWebVitals();
25 |
--------------------------------------------------------------------------------
/src/pages/App.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { BrowserRouter as Router, Route, Switch } from "react-router-dom";
3 | import PrivateRoute from "routes/PrivateRoute";
4 | import { Login } from ".";
5 |
6 | const App: React.FC = () => {
7 | return (
8 |
9 |
10 |
11 |
12 |
13 |
14 | );
15 | };
16 |
17 | export default App;
18 |
--------------------------------------------------------------------------------
/src/pages/index.tsx:
--------------------------------------------------------------------------------
1 | export { default as Login } from "./login";
2 | export { default as SalesPage } from "./sales";
3 | export { default as TransactionPage } from "./transaction";
4 |
--------------------------------------------------------------------------------
/src/pages/login/index.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { LoginContainer, LoginForm } from "components";
3 | import { Box, Heading } from "@chakra-ui/react";
4 | import { useHistory } from "react-router";
5 | import { supabase } from "service/supabase/connection";
6 |
7 | const Login: React.FC = () => {
8 | const history = useHistory();
9 |
10 | const checkToken = React.useCallback(() => {
11 | const session = supabase.auth.session();
12 | if (session) {
13 | history.push("/sales");
14 | }
15 | }, [history]);
16 |
17 | React.useEffect(() => {
18 | checkToken();
19 | }, [checkToken]);
20 |
21 | return (
22 |
23 |
24 |
25 | Sesukamu Printing
26 |
27 |
28 |
29 |
30 | );
31 | };
32 |
33 | export default Login;
34 |
--------------------------------------------------------------------------------
/src/pages/sales/index.tsx:
--------------------------------------------------------------------------------
1 | import { CardTransaction, ScreenContainer, CardSkelton } from "components";
2 | import React from "react";
3 | import { RouteComponentProps } from "react-router-dom";
4 | import { Flex, Heading, Button } from "@chakra-ui/react";
5 | import { Link } from "react-router-dom";
6 | import { AddIcon } from "@chakra-ui/icons";
7 | import transaction from "service/supabase/transaction";
8 | import { ITransaction } from "types/transaction.type";
9 |
10 | const SalesPage: React.FC = () => {
11 | const [isLoading, setLoading] = React.useState(false);
12 | const [dataTransaction, setTransaction] = React.useState([]);
13 |
14 | const fetchTransaction = async () => {
15 | try {
16 | setLoading(true);
17 | const data: ITransaction[] = await transaction.fetchTransaction();
18 | setTransaction(data);
19 | } catch (err) {
20 | console.log(err);
21 | } finally {
22 | setLoading(false);
23 | }
24 | };
25 |
26 | React.useEffect(() => {
27 | fetchTransaction();
28 | }, []);
29 |
30 | return (
31 | <>
32 |
33 |
34 |
35 | Transaksi
36 |
37 | }
46 | >
47 | Tambah
48 |
49 |
50 | {!isLoading ? (
51 | dataTransaction &&
52 | dataTransaction.map(
53 | (
54 | transaction: ITransaction,
55 | index: React.Key | null | undefined
56 | ) =>
57 | )
58 | ) : (
59 |
60 | )}
61 |
62 | >
63 | );
64 | };
65 | export default SalesPage;
66 |
--------------------------------------------------------------------------------
/src/pages/transaction/index.tsx:
--------------------------------------------------------------------------------
1 | import { AddIcon, ChevronLeftIcon } from "@chakra-ui/icons";
2 | import {
3 | Link,
4 | FormControl,
5 | FormLabel,
6 | Input,
7 | Flex,
8 | Button,
9 | } from "@chakra-ui/react";
10 | import { BottomTabs, Card, ScreenContainer, TableItem } from "components";
11 | import { Link as ReactLink, RouteComponentProps } from "react-router-dom";
12 | import React from "react";
13 | import transaction from "service/supabase/transaction";
14 | import { dateCode, getGrandTotal, timeTransaction } from "utils";
15 | import { useAppDispatch, useAppSelector } from "hooks";
16 | import { useHistory } from "react-router-dom";
17 | import { clearItems } from "redux/cart/cartSlice";
18 |
19 | const TransactionPage: React.FC = () => {
20 | const [isLoading, setLoading] = React.useState(false);
21 | const [customer, setCustomer] = React.useState("");
22 | const cartSlice = useAppSelector((state) => state.cartSlice);
23 | const grandTotal = getGrandTotal(cartSlice.items);
24 | const history = useHistory();
25 | const dispatch = useAppDispatch();
26 |
27 | const saveTransaction = async () => {
28 | try {
29 | setLoading(true);
30 | const payload = {
31 | trx_number: `SESUKAMU/${dateCode()}/${timeTransaction()}`,
32 | customer_name: customer,
33 | grand_total: getGrandTotal(cartSlice.items),
34 | is_paid: true,
35 | transaction_details: cartSlice.items.map((item) => {
36 | return {
37 | item_name: item.item_name,
38 | qty: item.qty,
39 | amount: item.amount,
40 | };
41 | }),
42 | };
43 | const data = await transaction.saveTransaction(payload);
44 | if (data) {
45 | history.push("/sales");
46 | dispatch(clearItems());
47 | }
48 | } catch (err) {
49 | console.log(err);
50 | } finally {
51 | setLoading(false);
52 | }
53 | };
54 |
55 | return (
56 |
57 |
58 |
64 |
65 | Kembali
66 |
67 | }
74 | isLoading={isLoading}
75 | loadingText="Please wait."
76 | onClick={saveTransaction}
77 | disabled={cartSlice.items.length === 0 && customer === "" && true}
78 | >
79 | Simpan
80 |
81 |
82 |
83 |
84 |
85 | Nama Customer
86 |
87 | setCustomer(e.target.value)}
90 | value={customer}
91 | />
92 |
93 |
94 |
95 |
96 |
97 | );
98 | };
99 |
100 | export default TransactionPage;
101 |
--------------------------------------------------------------------------------
/src/react-app-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/src/redux/auth/authSlice.ts:
--------------------------------------------------------------------------------
1 | import { createSlice, PayloadAction } from "@reduxjs/toolkit";
2 | import { AuthSlice } from "types/auth.type";
3 |
4 | const initialState: AuthSlice = {
5 | id: "",
6 | email: "",
7 | verified_email: null,
8 | name: "",
9 | given_name: "",
10 | family_name: "",
11 | picture: "",
12 | locale: "",
13 | };
14 |
15 | export const authSlice = createSlice({
16 | name: "auth-slice",
17 | initialState,
18 | reducers: {
19 | saveUserInfo: (state, action: PayloadAction) => {
20 | state.id = action.payload.id;
21 | state.email = action.payload.email;
22 | state.verified_email = action.payload.verified_email;
23 | state.name = action.payload.name;
24 | state.given_name = action.payload.given_name;
25 | state.family_name = action.payload.family_name;
26 | state.picture = action.payload.picture;
27 | state.locale = action.payload.locale;
28 | },
29 | },
30 | });
31 |
32 | // Action creators are generated for each case reducer function
33 | export const { saveUserInfo } = authSlice.actions;
34 |
35 | export default authSlice.reducer;
36 |
--------------------------------------------------------------------------------
/src/redux/cart/cartSlice.ts:
--------------------------------------------------------------------------------
1 | import { createSlice, PayloadAction } from "@reduxjs/toolkit";
2 | import { CartSliceType, ItemCartType } from "types/cart.type";
3 | import { ITransaction } from "types/transaction.type";
4 |
5 | const initialState: CartSliceType = {
6 | customer_name: "",
7 | transaction_date: "",
8 | trx_number: "",
9 | grand_total: 0,
10 | is_paid: false,
11 | items: [],
12 | };
13 |
14 | export const cartSlice = createSlice({
15 | name: "cart-slsice",
16 | initialState,
17 | reducers: {
18 | addItems: (state, action: PayloadAction) => {
19 | const { payload } = action;
20 | const items = [...state.items, payload];
21 | state.items = items;
22 | },
23 |
24 | deleteItems: (state, action: PayloadAction) => {
25 | const { payload } = action;
26 | const items = state.items.filter((item) => item.item_id !== payload);
27 | state.items = items;
28 | },
29 | clearItems: (state) => {
30 | state.customer_name = "";
31 | state.transaction_date = "";
32 | state.grand_total = 0;
33 | state.items = [];
34 | },
35 | printTransaction: (state, action: PayloadAction) => {
36 | const { payload } = action;
37 | state.customer_name = payload.customer_name;
38 | state.transaction_date = payload.transaction_date;
39 | state.trx_number = payload.trx_number;
40 | state.grand_total = payload.grand_total;
41 | state.items = payload.transaction_details;
42 | },
43 | },
44 | });
45 |
46 | export const { addItems, deleteItems, clearItems, printTransaction } =
47 | cartSlice.actions;
48 | export default cartSlice.reducer;
49 |
--------------------------------------------------------------------------------
/src/redux/store.ts:
--------------------------------------------------------------------------------
1 | import { configureStore } from "@reduxjs/toolkit";
2 | import authSlice from "./auth/authSlice";
3 | import cartSlice from "./cart/cartSlice";
4 |
5 | export const store = configureStore({
6 | reducer: {
7 | authSlice,
8 | cartSlice,
9 | },
10 | });
11 |
12 | // Infer the `RootState` and `AppDispatch` types from the store itself
13 | export type RootState = ReturnType;
14 | // Inferred type: {posts: PostsState, comments: CommentsState, users: UsersState}
15 | export type AppDispatch = typeof store.dispatch;
16 |
--------------------------------------------------------------------------------
/src/reportWebVitals.ts:
--------------------------------------------------------------------------------
1 | import { ReportHandler } from "web-vitals";
2 |
3 | const reportWebVitals = (onPerfEntry?: ReportHandler): any => {
4 | if (onPerfEntry && onPerfEntry instanceof Function) {
5 | import("web-vitals").then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
6 | getCLS(onPerfEntry);
7 | getFID(onPerfEntry);
8 | getFCP(onPerfEntry);
9 | getLCP(onPerfEntry);
10 | getTTFB(onPerfEntry);
11 | });
12 | }
13 | };
14 |
15 | export default reportWebVitals;
16 |
--------------------------------------------------------------------------------
/src/routes/PrivateRoute.tsx:
--------------------------------------------------------------------------------
1 | import { AuthContainer } from "components";
2 | import * as React from "react";
3 | import { Switch, withRouter, Route } from "react-router-dom";
4 | // import { supabase } from "service/supabase/connection";
5 | import allRoutes from ".";
6 |
7 | const PrivateRoute = withRouter(() => {
8 | const AuthMenu = allRoutes.map((route, index) => (
9 | }
14 | />
15 | ));
16 |
17 | return (
18 |
19 | {AuthMenu}
20 |
21 | );
22 | });
23 |
24 | export default PrivateRoute;
25 |
--------------------------------------------------------------------------------
/src/routes/index.ts:
--------------------------------------------------------------------------------
1 | import { SalesPage, TransactionPage } from "pages";
2 |
3 | const allRoutes = [
4 | {
5 | path: "/sales",
6 | component: SalesPage,
7 | auth: true,
8 | },
9 | {
10 | path: "/sales/transaction",
11 | component: TransactionPage,
12 | auth: true,
13 | },
14 | ];
15 |
16 | export default allRoutes;
17 |
--------------------------------------------------------------------------------
/src/service/api/index.ts:
--------------------------------------------------------------------------------
1 | import axios from "axios";
2 | import { AuthSlice } from "types/auth.type";
3 |
4 | export const ApiRequest = {
5 | getUserInfo: async (token: string | any): Promise => {
6 | try {
7 | const response = await axios.get(
8 | `https://www.googleapis.com/oauth2/v1/userinfo?access_token=${token}`
9 | );
10 | return Promise.resolve(response.data);
11 | } catch (error) {
12 | return Promise.reject(error);
13 | }
14 | },
15 | };
16 |
17 | export default ApiRequest;
18 |
--------------------------------------------------------------------------------
/src/service/supabase/connection.ts:
--------------------------------------------------------------------------------
1 | import { createClient } from "@supabase/supabase-js";
2 | import {
3 | REACT_APP_SUPABASE_URL,
4 | REACT_APP_SUPABASE_KEY,
5 | } from "utils/contstant";
6 |
7 | export const supabase = createClient(
8 | `${REACT_APP_SUPABASE_URL}`,
9 | `${REACT_APP_SUPABASE_KEY}`
10 | );
11 |
--------------------------------------------------------------------------------
/src/service/supabase/transaction.ts:
--------------------------------------------------------------------------------
1 | import { ITransaction } from "types/transaction.type";
2 | import { supabase } from "./connection";
3 |
4 | const transaction = {
5 | fetchTransaction: async (): Promise => {
6 | try {
7 | const {
8 | data: listTransaction,
9 | error,
10 | status,
11 | } = await supabase
12 | .from("transaction")
13 | .select(
14 | "customer_name, grand_total, trx_number, transaction_date, transaction_details(item_name, qty, amount)"
15 | );
16 |
17 | if (error && status !== 406) {
18 | throw error;
19 | }
20 |
21 | return listTransaction;
22 | } catch (error: any) {
23 | return error;
24 | }
25 | },
26 | saveTransaction: async (payload: ITransaction): Promise => {
27 | try {
28 | const {
29 | data: transaction,
30 | error,
31 | status,
32 | } = await supabase.from("transaction").insert({
33 | trx_number: payload.trx_number,
34 | customer_name: payload.customer_name,
35 | is_paid: true,
36 | grand_total: payload.grand_total,
37 | });
38 |
39 | if (error && status !== 406) {
40 | throw error;
41 | }
42 |
43 | if (transaction) {
44 | payload.transaction_details = payload.transaction_details.map(
45 | (item) => {
46 | return {
47 | ...item,
48 | transaction_id: transaction[0].transaction_id,
49 | };
50 | }
51 | );
52 | await supabase
53 | .from("transaction_details")
54 | .insert(payload.transaction_details);
55 | }
56 |
57 | return transaction;
58 | } catch (error) {
59 | return error;
60 | }
61 | },
62 | };
63 |
64 | export default transaction;
65 |
--------------------------------------------------------------------------------
/src/setupTests.ts:
--------------------------------------------------------------------------------
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';
6 |
--------------------------------------------------------------------------------
/src/themes/theme.ts:
--------------------------------------------------------------------------------
1 | import { extendTheme, theme as defaultTheme } from "@chakra-ui/react";
2 |
3 | export default extendTheme({
4 | colors: {
5 | primary: {
6 | 50: "#2447F9",
7 | },
8 | },
9 | shadows: {
10 | md: "0 5px 10px rgba(17, 17, 17, 0.075)",
11 | },
12 | components: {
13 | Heading: {
14 | baseStyle: {
15 | fontWeight: 500,
16 | letterSpacing: "tighter",
17 | },
18 | },
19 | Button: {
20 | variants: {
21 | primary: {
22 | bg: "primary.50",
23 | color: "white",
24 | _hover: {
25 | boxShadow: "md",
26 | },
27 | },
28 | },
29 | },
30 | Link: {
31 | variants: {
32 | link: {
33 | color: "pink.400",
34 | },
35 | },
36 | },
37 | Modal: {
38 | baseStyle: {
39 | overlay: {
40 | "@supports (backdrop-filter: blur(2px))": {
41 | backdropFilter: "blur(2px)",
42 | },
43 | "@supports (-webkit-backdrop-filter: blur(2px))": {
44 | WebkitBackdropFilter: "blur(2px)",
45 | },
46 | },
47 | },
48 | },
49 | },
50 |
51 | fonts: {
52 | body: `'Archivo',${defaultTheme.fonts.body}`,
53 | heading: `'Manrope',${defaultTheme.fonts.heading}`,
54 | },
55 |
56 | styles: {
57 | global: {
58 | html: {
59 | scrollBehavior: "smooth",
60 | },
61 | "*:focus": {
62 | boxShadow: "none !important",
63 | },
64 | body: {
65 | cursor: "default",
66 | fontFamily: "body",
67 | lineHeight: "base",
68 | background: "#F3F5F8",
69 | MozOsxFontSmoothing: "grayscale",
70 | WebkitFontSmoothing: "antialiased",
71 | textRendering: "optimizeLegibility",
72 | },
73 | "*::placeholder": {
74 | color: "gray.200",
75 | },
76 | "*, *::before, &::after": {
77 | // borderColor: "whiteAlpha.300",
78 | wordWrap: "break-word",
79 | },
80 |
81 | "#nprogress": {
82 | pointerEvents: "none",
83 | },
84 | "#nprogress .bar": {
85 | bgGradient: "linear(to-r, whiteAlpha.400, yellow.200)",
86 | h: "2px",
87 | left: 0,
88 | pos: "fixed",
89 | top: 0,
90 | w: "full",
91 | zIndex: 2000,
92 | },
93 | ".nprogress-custom-parent": {
94 | overflow: "hidden",
95 | position: "absolute",
96 | },
97 | ".background-left": {
98 | position: "fixed",
99 | bottom: "0px",
100 | left: 0,
101 | zIndex: -10,
102 | },
103 | ".background-right": {
104 | position: "fixed",
105 | bottom: "0px",
106 | left: "2.5rem",
107 | zIndex: -10,
108 | },
109 | },
110 | },
111 | });
112 |
--------------------------------------------------------------------------------
/src/types/auth.type.ts:
--------------------------------------------------------------------------------
1 | export interface AuthSlice {
2 | id: string;
3 | email: string;
4 | verified_email: boolean | null;
5 | name: string;
6 | given_name: string;
7 | family_name: string;
8 | picture: string;
9 | locale: string;
10 | }
11 |
--------------------------------------------------------------------------------
/src/types/cart.type.ts:
--------------------------------------------------------------------------------
1 | export interface ItemCartType {
2 | item_id?: string;
3 | item_name: string;
4 | qty: number;
5 | amount: number;
6 | }
7 |
8 | export interface CartSliceType {
9 | customer_name: string;
10 | transaction_date: any;
11 | trx_number: string;
12 | grand_total: number;
13 | is_paid: boolean;
14 | items: ItemCartType[];
15 | }
16 |
--------------------------------------------------------------------------------
/src/types/transaction.type.ts:
--------------------------------------------------------------------------------
1 | import { ItemCartType } from "./cart.type";
2 |
3 | export interface ITransaction {
4 | customer_name: string;
5 | grand_total: number;
6 | is_paid: boolean;
7 | transaction_date?: any;
8 | transaction_id?: number;
9 | trx_number: string;
10 | transaction_details: Array;
11 | }
12 |
--------------------------------------------------------------------------------
/src/utils/contstant.ts:
--------------------------------------------------------------------------------
1 | export const REACT_APP_SUPABASE_URL = process.env.REACT_APP_SUPABASE_URL;
2 | export const REACT_APP_SUPABASE_KEY = process.env.REACT_APP_SUPABASE_KEY;
3 |
--------------------------------------------------------------------------------
/src/utils/index.ts:
--------------------------------------------------------------------------------
1 | import { ItemCartType } from "types/cart.type";
2 |
3 | export function currencyFormat(num = 0): string {
4 | const amount = Number(num);
5 | return "Rp " + amount.toFixed(0).replace(/(\d)(?=(\d{3})+(?!\d))/g, "$1,");
6 | }
7 |
8 | /**
9 | * get grand_total from cart
10 | *
11 | * @param cart
12 | * @returns
13 | */
14 | export function getGrandTotal(cart: Array): number {
15 | const result = cart.reduce(
16 | (prevValue, currentValue) => prevValue + currentValue.amount,
17 | 0
18 | );
19 | return result;
20 | }
21 |
22 | /**
23 | * get total items in cart
24 | * @param cart
25 | * @returns
26 | */
27 | export function getTotalItems(cart: Array): number {
28 | const result = cart.reduce(
29 | (prevValue, currentValue) => prevValue + currentValue.qty,
30 | 0
31 | );
32 | return result;
33 | }
34 |
35 | /**
36 | * generate number for transaction number
37 | * @returns
38 | */
39 | export function generateNumber(): string {
40 | const trxNumber = Date.now() + (Math.random() * 100).toFixed();
41 | return trxNumber;
42 | }
43 |
44 | /**
45 | * formating timestamptz
46 | *
47 | * @param UTC_Date
48 | * @returns
49 | */
50 | export const formatDate = (UTC_Date: string): string => {
51 | const localDate = new Date(UTC_Date);
52 | const objToday = localDate,
53 | dayOfMonth =
54 | objToday.getDate() < 10 ? "0" + objToday.getDate() : objToday.getDate(),
55 | curMonth =
56 | objToday.getMonth() + 1 < 10
57 | ? "0" + (objToday.getMonth() + 1)
58 | : objToday.getMonth() + 1,
59 | curYear = objToday.getFullYear(),
60 | curHour = objToday.getHours(),
61 | curMinute =
62 | objToday.getMinutes() < 10
63 | ? "0" + objToday.getMinutes()
64 | : objToday.getMinutes(),
65 | curMeridiem = objToday.getHours() > 12 ? "PM" : "AM";
66 | const today =
67 | curYear +
68 | "-" +
69 | curMonth +
70 | "-" +
71 | dayOfMonth +
72 | " " +
73 | curHour +
74 | ":" +
75 | curMinute +
76 | " " +
77 | curMeridiem;
78 | return today;
79 | };
80 |
81 | /**
82 | * generate date to transaction number
83 | * @returns
84 | */
85 | export function dateCode(): string {
86 | const dateObj = new Date();
87 | const monthDefault = dateObj.getUTCMonth() + 1; //months from 1-12
88 | const month = (monthDefault < 10 ? "0" : "") + monthDefault;
89 | const day = (dateObj.getUTCDate() < 10 ? "0" : "") + dateObj.getUTCDate();
90 | const year = dateObj.getUTCFullYear();
91 | const now = year + "" + month + "" + day;
92 | return now;
93 | }
94 |
95 | export function timeTransaction(): string {
96 | const dateObj = new Date();
97 | const hours = (dateObj.getHours() < 10 ? "0" : "") + dateObj.getHours();
98 | const minutes = (dateObj.getMinutes() < 10 ? "0" : "") + dateObj.getMinutes();
99 | const second = (dateObj.getSeconds() < 10 ? "0" : "") + dateObj.getSeconds();
100 | const time = hours + "" + minutes + "" + second;
101 | return time;
102 | }
103 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": ["dom", "dom.iterable", "esnext"],
5 | "allowJs": true,
6 | "skipLibCheck": true,
7 | "esModuleInterop": true,
8 | "allowSyntheticDefaultImports": true,
9 | "strict": true,
10 | "forceConsistentCasingInFileNames": true,
11 | "noFallthroughCasesInSwitch": true,
12 | "module": "esnext",
13 | "moduleResolution": "node",
14 | "resolveJsonModule": true,
15 | "isolatedModules": true,
16 | "noEmit": true,
17 | "jsx": "react-jsx",
18 | "baseUrl": "src"
19 | },
20 | "include": ["src"]
21 | }
22 |
--------------------------------------------------------------------------------