feat: initial commit
This commit is contained in:
18
src/Main.js
Normal file
18
src/Main.js
Normal file
@@ -0,0 +1,18 @@
|
||||
import React from "react";
|
||||
import {BrowserRouter as Router} from "react-router-dom";
|
||||
import {StoreProvider} from "./utils/useStore";
|
||||
import {MainRoutes} from "./routes";
|
||||
import "./custom.less";
|
||||
// import "./Style.css";
|
||||
import ParticlesBg from "particles-bg";
|
||||
|
||||
export const Main = () => {
|
||||
return (
|
||||
<StoreProvider>
|
||||
<Router>
|
||||
<MainRoutes/>
|
||||
<ParticlesBg color={"#ababab"} num={20} type={"cobweb"} bg={true}/>
|
||||
</Router>
|
||||
</StoreProvider>
|
||||
);
|
||||
};
|
||||
19
src/component/BreadcumbComponent.js
Normal file
19
src/component/BreadcumbComponent.js
Normal file
@@ -0,0 +1,19 @@
|
||||
import React from "react";
|
||||
import {Breadcrumb} from "antd";
|
||||
import {Link} from "react-router-dom";
|
||||
|
||||
export const BreadcumbComponent = (props) => {
|
||||
return (
|
||||
<div>
|
||||
<Breadcrumb style={{marginBottom: 10}}>
|
||||
{props.data.map((e) => (
|
||||
<Breadcrumb.Item>
|
||||
<Link to={e.route}>
|
||||
<span>{e.name}</span>
|
||||
</Link>
|
||||
</Breadcrumb.Item>
|
||||
))}
|
||||
</Breadcrumb>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
18
src/component/PrivateRoute.js
Normal file
18
src/component/PrivateRoute.js
Normal file
@@ -0,0 +1,18 @@
|
||||
import React from 'react';
|
||||
import {Redirect, Route} from 'react-router-dom';
|
||||
import {useStore} from "../utils/useStore";
|
||||
// import { isLogin } from '../utils';
|
||||
|
||||
export const PrivateRoute = ({component: Component, ...rest}) => {
|
||||
const store = useStore();
|
||||
return (
|
||||
|
||||
// Show the component only when the user is logged in
|
||||
// Otherwise, redirect the user to /signin page
|
||||
<Route {...rest} render={props => (
|
||||
store.authentication.isLoggedIn ?
|
||||
<Component {...props} />
|
||||
: <Redirect to="/login"/>
|
||||
)}/>
|
||||
);
|
||||
};
|
||||
18
src/component/PublicRoute.js
Normal file
18
src/component/PublicRoute.js
Normal file
@@ -0,0 +1,18 @@
|
||||
import React from 'react';
|
||||
import {Redirect, Route} from 'react-router-dom';
|
||||
import {useStore} from "../utils/useStore";
|
||||
|
||||
export const PublicRoute = ({component: Component, restricted, ...rest}) => {
|
||||
const store = useStore();
|
||||
|
||||
return (
|
||||
// restricted = false meaning public route
|
||||
// restricted = true meaning restricted route
|
||||
<Route {...rest} render={props => (
|
||||
store.authentication.isLoggedIn && restricted ?
|
||||
<Redirect to="/app"/>
|
||||
: <Component {...props} />
|
||||
)}/>
|
||||
);
|
||||
};
|
||||
|
||||
5
src/config/app.js
Normal file
5
src/config/app.js
Normal file
@@ -0,0 +1,5 @@
|
||||
export const appConfig = {
|
||||
apiUrl: 'https://api-reconcile.movic.id'
|
||||
};
|
||||
|
||||
//export default appConfig;
|
||||
99
src/custom.less
Normal file
99
src/custom.less
Normal file
@@ -0,0 +1,99 @@
|
||||
@import '~antd/dist/antd.less';
|
||||
|
||||
@font-size-base: 12px; // major text font size
|
||||
//@primary-color: #ffd037; // primary color for all components
|
||||
@primary-color1: #2e9dfe; // primary color for all components
|
||||
@primary-color: #ed1f24; // primary color for all components
|
||||
//@link-color: #58585b; // link color
|
||||
@link-color1: #196bb1; // link color
|
||||
@link-color: #ed1f24; // link color
|
||||
@success-color: #52c41a; // success state color
|
||||
@warning-color: #faad14; // warning state color
|
||||
@error-color: #f5222d; // error state color
|
||||
@heading-color: rgba(0, 0, 0, 0.85); // heading text color
|
||||
@text-color: #1a1f36; // major text color
|
||||
@text-color-secondary: #4f566b; // secondary text color
|
||||
@disabled-color: #697386; // disable state color
|
||||
@border-radius-base: 6px; // major border radius
|
||||
@border-color-base: #d9d9d9; // major border color
|
||||
@box-shadow-base: @shadow-2; // major shadow for layers
|
||||
//@background-color-base: #e3e8ee;
|
||||
@background-color-base: #e3e8ee;
|
||||
@link-decoration: none;
|
||||
|
||||
body {
|
||||
font-family: 'Montserrat Alternates', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial,
|
||||
'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol',
|
||||
'Noto Color Emoji';
|
||||
-webkit-font-smoothing: antialiased;
|
||||
background-color: @background-color-base;
|
||||
font-weight: 400;
|
||||
color: #1a1f36;
|
||||
}
|
||||
|
||||
// Layout
|
||||
@layout-body-background: #e3e8ee;
|
||||
@layout-header-background: #001529;
|
||||
@layout-header-height: 64px;
|
||||
@layout-header-padding: 0 50px;
|
||||
@layout-header-color: @text-color;
|
||||
@layout-footer-padding: 24px 50px;
|
||||
@layout-footer-background: @layout-body-background;
|
||||
@layout-sider-background: @layout-header-background;
|
||||
@layout-trigger-height: 48px;
|
||||
@layout-trigger-background: #002140;
|
||||
@layout-trigger-color: #fff;
|
||||
@layout-zero-trigger-width: 36px;
|
||||
@layout-zero-trigger-height: 42px;
|
||||
|
||||
|
||||
// Statistic
|
||||
// ---
|
||||
|
||||
@statistic-content-font-size: 20px;
|
||||
@statistic-unit-font-size: 11px;
|
||||
|
||||
// Typography
|
||||
// ---
|
||||
@typography-title-font-weight: 100;
|
||||
|
||||
@menu-collapsed-width: 40px;
|
||||
@layout-header-padding: 0 24px;
|
||||
|
||||
|
||||
@card-shadow: 0 7px 14px 0 rgba(60, 66, 87, 0.12), 0 3px 6px 0 rgba(0, 0, 0, 0.12);
|
||||
|
||||
@select-item-selected-bg: transparent;
|
||||
@menu-item-active-bg: transparent;
|
||||
@layout-header-background: @layout-body-background;
|
||||
|
||||
@menu-item-active-border-width: 0;
|
||||
@select-selection-item-border-color: transparent;
|
||||
@menu-inline-toplevel-item-height: 20px;
|
||||
@menu-item-height: 20px;
|
||||
@shadow-2: 0 7px 14px 0 rgba(60, 66, 87, 0.5), 0 3px 6px 0 rgba(241, 241, 241, 0.5);
|
||||
|
||||
// Menu
|
||||
// ---
|
||||
|
||||
@menu-bg: transparent;
|
||||
|
||||
//Dropdown
|
||||
@dropdown-menu-bg: transparent;
|
||||
@btn-border-width: 0;
|
||||
@btn-shadow: rgba(0, 0, 0, 0) 0px 0px 0px 0px, rgba(0, 0, 0, 0) 0px 0px 0px 0px, rgba(0, 0, 0, 0.12) 0px 1px 1px 0px, rgba(60, 66, 87, 0.16) 0px 0px 0px 1px, rgba(0, 0, 0, 0) 0px 0px 0px 0px, rgba(0, 0, 0, 0) 0px 0px 0px 0px, rgba(60, 66, 87, 0.12) 0px 2px 5px 0px;
|
||||
|
||||
// Button
|
||||
@btn-height-base: 28px;
|
||||
|
||||
@btn-padding-horizontal-base: 8px;
|
||||
|
||||
@form-vertical-label-padding: 0 0 4px;
|
||||
@page-header-padding: 16px 20px;
|
||||
|
||||
@no-shadow: none;
|
||||
|
||||
|
||||
@table-header-bg: #f7fafc;
|
||||
|
||||
|
||||
48
src/index.css
Normal file
48
src/index.css
Normal file
@@ -0,0 +1,48 @@
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
||||
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
||||
sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
|
||||
monospace;
|
||||
}
|
||||
|
||||
.ant-tabs-tab {
|
||||
width: 150px !important;
|
||||
text-align: center !important;
|
||||
display: inline !important;
|
||||
color: black !important;
|
||||
}
|
||||
|
||||
.ant-tabs-tab.ant-tabs-tab-active .ant-tabs-tab-btn {
|
||||
color: #2D9CDB !important;
|
||||
border-color: #2D9CDB !important;
|
||||
}
|
||||
|
||||
.ant-card {
|
||||
margin-bottom: 30px !important;
|
||||
width: 1000px;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.ant-table-cell {
|
||||
text-align: center !important;
|
||||
}
|
||||
|
||||
.anticon anticon-down {
|
||||
text-align: right !important;
|
||||
}
|
||||
|
||||
.ant-btn > span + .anticon {
|
||||
margin-left: 770px !important;
|
||||
}
|
||||
|
||||
.ant-btn.ant-btn-primary {
|
||||
background: #2D9CDB !important;
|
||||
}
|
||||
|
||||
17
src/index.js
Normal file
17
src/index.js
Normal file
@@ -0,0 +1,17 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import './index.css';
|
||||
import reportWebVitals from './reportWebVitals';
|
||||
import {Main} from "./Main";
|
||||
|
||||
ReactDOM.render(
|
||||
<React.StrictMode>
|
||||
<Main/>
|
||||
</React.StrictMode>,
|
||||
document.getElementById('root')
|
||||
);
|
||||
|
||||
// If you want to start measuring performance in your app, pass a function
|
||||
// to log results (for example: reportWebVitals(console.log))
|
||||
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
|
||||
reportWebVitals();
|
||||
20
src/pages/About/About.js
Normal file
20
src/pages/About/About.js
Normal file
@@ -0,0 +1,20 @@
|
||||
import React from "react";
|
||||
import {PageHeader} from "antd";
|
||||
|
||||
export const About = () => {
|
||||
return <div>
|
||||
<PageHeader
|
||||
style={{
|
||||
padding: 0,
|
||||
margin: 0,
|
||||
height: 40,
|
||||
backgroundColor: "transparent",
|
||||
}}
|
||||
title={"About"}
|
||||
>
|
||||
</PageHeader>
|
||||
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Accusamus aut recusandae velit! Consequatur corporis,
|
||||
eum fuga, harum incidunt laboriosam minus necessitatibus neque non nostrum pariatur tempore. Dignissimos impedit
|
||||
rem tempora!
|
||||
</div>
|
||||
};
|
||||
24
src/pages/App/App.js
Normal file
24
src/pages/App/App.js
Normal file
@@ -0,0 +1,24 @@
|
||||
import React, {useEffect} from "react";
|
||||
import {DesktopLayout} from "./DesktopLayout";
|
||||
import {useMediaQuery} from 'react-responsive';
|
||||
import {useStore} from "../../utils/useStore";
|
||||
|
||||
|
||||
export const App = () => {
|
||||
// TODO: add mobile layout
|
||||
const store = useStore();
|
||||
const mediaQuery = {
|
||||
isDesktop: useMediaQuery({minWidth: 1024}),
|
||||
isTablet: useMediaQuery({minWidth: 768, maxWidth: 1023}),
|
||||
isMobile: useMediaQuery({maxWidth: 767}),
|
||||
isNotMobile: useMediaQuery({minWidth: 768}),
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
store.ui.setMediaQuery(mediaQuery);
|
||||
});
|
||||
// const isMobileDevice = useMediaQuery({
|
||||
// query: "(min-device-width: 480px)",
|
||||
// });
|
||||
return <DesktopLayout/>;
|
||||
};
|
||||
151
src/pages/App/DesktopLayout.js
Normal file
151
src/pages/App/DesktopLayout.js
Normal file
@@ -0,0 +1,151 @@
|
||||
import React, {useState} from "react";
|
||||
import {Button, Layout, Menu, Popover, Typography,} from "antd";
|
||||
import {MenuList} from "./MenuList";
|
||||
import {Link} from "react-router-dom";
|
||||
import {PlusSquareOutlined, UserOutlined,} from "@ant-design/icons";
|
||||
import {AppRoute} from "../../routes/app";
|
||||
|
||||
const {Text, Paragraph} = Typography;
|
||||
const {Header, Content, Sider} = Layout;
|
||||
|
||||
export const DesktopLayout = () => {
|
||||
const [clicked, setClicked] = useState(false);
|
||||
|
||||
return (
|
||||
<Layout
|
||||
theme={"light"}
|
||||
className={"transparent"}
|
||||
hasSider={true}
|
||||
style={{
|
||||
paddingLeft: 0,
|
||||
display: "flex",
|
||||
width: "100vw",
|
||||
height: "100vh",
|
||||
}}
|
||||
>
|
||||
<Sider
|
||||
className={"transparent"}
|
||||
width={240}
|
||||
style={{
|
||||
overflowX: "hidden",
|
||||
bottom: 0,
|
||||
justifyContent: "flex-start",
|
||||
paddingTop: 20,
|
||||
paddingLeft: 20,
|
||||
position: "fixed",
|
||||
top: 0,
|
||||
zIndex: 10,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
paddingLeft: 20,
|
||||
marginBottom: 40,
|
||||
}}
|
||||
>
|
||||
<Paragraph
|
||||
style={{
|
||||
margin: 0,
|
||||
padding: 0,
|
||||
fontSize: 20,
|
||||
marginLeft: 5,
|
||||
fontWeight: 600,
|
||||
color: "#828282",
|
||||
}}
|
||||
>
|
||||
PPOB
|
||||
</Paragraph>
|
||||
<Paragraph
|
||||
style={{
|
||||
margin: 0,
|
||||
padding: 0,
|
||||
fontSize: 11,
|
||||
marginLeft: 5,
|
||||
fontWeight: 600,
|
||||
color: "#413d3e",
|
||||
}}
|
||||
>
|
||||
Admin
|
||||
</Paragraph>
|
||||
</div>
|
||||
<MenuList closeLeftDrawer={() => {
|
||||
}}/>
|
||||
</Sider>
|
||||
|
||||
<Layout
|
||||
style={{
|
||||
paddingLeft: 240,
|
||||
}}
|
||||
>
|
||||
<Header
|
||||
theme={"light"}
|
||||
style={{
|
||||
height: 54,
|
||||
paddingLeft: 0,
|
||||
paddingRight: 0,
|
||||
backgroundColor: "transparent",
|
||||
maxWidth: 1000,
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "flex-end",
|
||||
}}
|
||||
>
|
||||
<Button style={{right: "94%"}}>
|
||||
<PlusSquareOutlined/>
|
||||
</Button>
|
||||
<Popover
|
||||
autoAdjustOverflow={true}
|
||||
placement="bottomRight"
|
||||
content={
|
||||
<Menu
|
||||
type={"line"}
|
||||
inlineIndent={0}
|
||||
theme="light"
|
||||
style={{
|
||||
backgroundColor: "transparent",
|
||||
borderRightWidth: 0,
|
||||
right: "30%",
|
||||
}}
|
||||
mode="inline"
|
||||
>
|
||||
<Menu.Item>
|
||||
<Link to="/app/profile">
|
||||
<span>Profile</span>
|
||||
</Link>
|
||||
</Menu.Item>
|
||||
<Menu.Item
|
||||
onClick={() => {
|
||||
// store.authentication.logout();
|
||||
// return history.push("/login");
|
||||
}}
|
||||
>
|
||||
<span>Sign out</span>
|
||||
</Menu.Item>
|
||||
</Menu>
|
||||
}
|
||||
title={
|
||||
<Text>
|
||||
info@bangun-kreatif.com
|
||||
{/*{store.userData.email}{" "}*/}
|
||||
<Paragraph style={{fontWeight: 400}} type={"secondary-dark"}>
|
||||
Administrator
|
||||
{/*{store.userData.role}*/}
|
||||
</Paragraph>
|
||||
</Text>
|
||||
}
|
||||
trigger="click"
|
||||
visible={clicked}
|
||||
onVisibleChange={() => setClicked(!clicked)}
|
||||
>
|
||||
<Button
|
||||
size={"default"}
|
||||
style={{}}
|
||||
icon={<UserOutlined style={{fontSize: "13px"}}/>}
|
||||
/>
|
||||
</Popover>
|
||||
</Header>
|
||||
<AppRoute/>
|
||||
</Layout>
|
||||
</Layout>
|
||||
);
|
||||
};
|
||||
69
src/pages/App/MenuList.js
Normal file
69
src/pages/App/MenuList.js
Normal file
@@ -0,0 +1,69 @@
|
||||
import React, {useEffect, useState} from "react";
|
||||
import {Menu} from "antd";
|
||||
import {Link} from "react-router-dom";
|
||||
import {CalendarOutlined, HomeOutlined,} from "@ant-design/icons";
|
||||
import {observer} from "mobx-react-lite";
|
||||
import {useStore} from "../../utils/useStore";
|
||||
|
||||
const {SubMenu} = Menu;
|
||||
|
||||
export const MenuList = observer((props) => {
|
||||
const store = useStore();
|
||||
useEffect(() => {
|
||||
}, []);
|
||||
|
||||
const [setKeys, setSetKeys] = useState(["dashboard"]);
|
||||
|
||||
return (
|
||||
<Menu
|
||||
defaultOpenKeys={["sub4"]}
|
||||
theme="light"
|
||||
style={{
|
||||
backgroundColor: "transparent",
|
||||
borderRightWidth: 0,
|
||||
fontWeight: 400,
|
||||
paddingLeft: 0,
|
||||
}}
|
||||
onClick={({keyPath, item}) => {
|
||||
props.closeLeftDrawer();
|
||||
}}
|
||||
mode="inline"
|
||||
selectedKeys={setKeys}
|
||||
onSelect={({setKeys, item, selectedKeys}) => setSetKeys(selectedKeys)}
|
||||
overflowedIndicator={0}
|
||||
forceSubMenuRender={true}
|
||||
>
|
||||
<Menu.Item key="home">
|
||||
<Link to={'/app/home'}>
|
||||
<HomeOutlined/>
|
||||
<span>Home</span>
|
||||
</Link>
|
||||
</Menu.Item>
|
||||
<Menu.Item key="membership">
|
||||
<Link to={'/app/membership'}>
|
||||
<HomeOutlined/>
|
||||
<span>Membership</span>
|
||||
</Link>
|
||||
</Menu.Item>
|
||||
<Menu.Item key="product">
|
||||
<Link to={'/app/product'}>
|
||||
<HomeOutlined/>
|
||||
<span>Product</span>
|
||||
</Link>
|
||||
</Menu.Item>
|
||||
<Menu.Item key="product">
|
||||
<Link to={'/app/transaction'}>
|
||||
<HomeOutlined/>
|
||||
<span>Transaction</span>
|
||||
</Link>
|
||||
</Menu.Item>
|
||||
<Menu.Item key="about">
|
||||
<Link to={'/app/about'}>
|
||||
<CalendarOutlined/>
|
||||
<span>About</span>
|
||||
</Link>
|
||||
</Menu.Item>
|
||||
<Menu.Divider style={{background: "transparent", paddingTop: 15}}/>
|
||||
</Menu>
|
||||
);
|
||||
});
|
||||
25
src/pages/Home/Home.js
Normal file
25
src/pages/Home/Home.js
Normal file
@@ -0,0 +1,25 @@
|
||||
import React from "react";
|
||||
import {Button, PageHeader} from "antd";
|
||||
import {store} from "../../utils/useStore";
|
||||
import {observer} from "mobx-react-lite";
|
||||
|
||||
export const Home = observer(() => {
|
||||
return <div>
|
||||
<PageHeader
|
||||
style={{
|
||||
padding: 0,
|
||||
margin: 0,
|
||||
height: 40,
|
||||
backgroundColor: "transparent",
|
||||
}}
|
||||
title={"Home"}
|
||||
>
|
||||
</PageHeader>
|
||||
<Button onClick={() => {
|
||||
store.ui.setTestValue();
|
||||
}}>{store.ui.testValue}</Button>
|
||||
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Accusamus aut recusandae velit! Consequatur corporis,
|
||||
eum fuga, harum incidunt laboriosam minus necessitatibus neque non nostrum pariatur tempore. Dignissimos impedit
|
||||
rem tempora!
|
||||
</div>
|
||||
});
|
||||
139
src/pages/Login/Login.js
Normal file
139
src/pages/Login/Login.js
Normal file
@@ -0,0 +1,139 @@
|
||||
import React, {useState} from "react";
|
||||
import {observer} from 'mobx-react-lite';
|
||||
import {useStore} from "../../utils/useStore";
|
||||
import {Button, Card, Checkbox, Col, Form, Input, Row, Typography} from 'antd';
|
||||
import {LockOutlined, UserOutlined} from '@ant-design/icons';
|
||||
import {useHistory} from "react-router-dom";
|
||||
|
||||
export const Login = observer(() => {
|
||||
const store = useStore();
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
let history = useHistory();
|
||||
|
||||
const onFinish = values => {
|
||||
console.log('Received values of form: ', values);
|
||||
enterLoading(values).then(res => {
|
||||
console.log(res, "awasaa");
|
||||
}).catch((error) => {
|
||||
console.log({error}, "awasaa error");
|
||||
});
|
||||
};
|
||||
|
||||
const enterLoading = async (props) => {
|
||||
// store.setInitialToken("ayayay", "clap");
|
||||
return history.push("/app/page_example_1");
|
||||
};
|
||||
|
||||
return <div style={{width: '100vw', display: 'flex', justifyContent: 'center'}}>
|
||||
<Row justify={'center'}>
|
||||
<Col>
|
||||
<div style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'flex-start',
|
||||
marginTop: '5vh',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
}}>
|
||||
<div style={{display: 'flex', flexDirection: 'column', alignItems: 'stretch'}}>
|
||||
<Typography.Paragraph
|
||||
style={{
|
||||
margin: 0,
|
||||
padding: 0,
|
||||
fontSize: 20,
|
||||
marginLeft: 5,
|
||||
fontWeight: 600,
|
||||
color: "#413d3e",
|
||||
}}
|
||||
>
|
||||
Boilerplate
|
||||
</Typography.Paragraph>
|
||||
</div>
|
||||
<Card
|
||||
style={{width: 320, textAlign: 'center'}}
|
||||
headStyle={{fontSize: 13, fontWeight: 200}}
|
||||
className={"shadow"}
|
||||
bordered={true}
|
||||
title={'Sign in to your account'}
|
||||
>
|
||||
<Form
|
||||
layout={'vertical'}
|
||||
name="normal_login"
|
||||
className="login-form"
|
||||
onFinish={onFinish}
|
||||
>
|
||||
<Form.Item
|
||||
label="Email"
|
||||
name="email"
|
||||
size={'large'}
|
||||
rules={[{required: false, message: 'Please input your Username!'}]}
|
||||
>
|
||||
<Input
|
||||
prefix={<UserOutlined className="site-form-item-icon"/>}
|
||||
type="text"
|
||||
placeholder="Email"/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
style={{
|
||||
marginBottom: 0,
|
||||
}}
|
||||
label="Password"
|
||||
name="password"
|
||||
size={'large'}
|
||||
rules={[{required: false, message: 'Please input your Password!'}]}
|
||||
>
|
||||
<Input.Password
|
||||
prefix={<LockOutlined className="site-form-item-icon"/>}
|
||||
type="password"
|
||||
placeholder="Password"
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
style={{
|
||||
marginTop: 0,
|
||||
marginBottom: 20,
|
||||
padding: 0
|
||||
}}
|
||||
// label="Password"
|
||||
name="forgot-password"
|
||||
size={'small'}
|
||||
rules={[{required: false, message: 'Please input your Password!'}]}
|
||||
>
|
||||
<a className="login-form-forgot" href="">
|
||||
Forgot password
|
||||
</a>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
style={{
|
||||
marginBottom: 5,
|
||||
textAlign: 'left'
|
||||
}}>
|
||||
<Form.Item name="remember" valuePropName="checked" noStyle>
|
||||
<Checkbox>Remember me</Checkbox>
|
||||
</Form.Item>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
style={{
|
||||
marginBottom: 0,
|
||||
}}>
|
||||
<Button type="primary"
|
||||
block
|
||||
loading={loading}
|
||||
htmlType="submit"
|
||||
size={'large'}
|
||||
onSubmit={enterLoading}
|
||||
className="login-form-button">
|
||||
Sign In
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Card>
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
</div>;
|
||||
});
|
||||
280
src/pages/Membership/Membership.js
Normal file
280
src/pages/Membership/Membership.js
Normal file
@@ -0,0 +1,280 @@
|
||||
import React, {useEffect, useState} from "react";
|
||||
import {Button, Card, Col, Input, List, message, Modal, PageHeader, Row, Space, Table, Tag} from "antd";
|
||||
import {useStore} from "../../utils/useStore";
|
||||
import {observer} from "mobx-react-lite";
|
||||
import {ExclamationCircleOutlined, FilterOutlined, PlusSquareOutlined,} from "@ant-design/icons";
|
||||
import {MembershipModal} from "./MembershipModal";
|
||||
import {BreadcumbComponent} from "../../component/BreadcumbComponent";
|
||||
|
||||
const {Search} = Input;
|
||||
|
||||
export const Membership = observer(() => {
|
||||
const store = useStore();
|
||||
const [visibleModal, setVisibleModal] = useState(false)
|
||||
const [initialData, setInitialData] = useState({})
|
||||
const [confirmLoading, setConfirmLoading] = useState(false);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const init = async () => {
|
||||
try {
|
||||
setIsLoading(true)
|
||||
await store.membership.getData();
|
||||
setIsLoading(false)
|
||||
} catch (e) {
|
||||
setIsLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
init()
|
||||
|
||||
}, []);
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: "Name",
|
||||
dataIndex: "name",
|
||||
key: "name",
|
||||
},
|
||||
{
|
||||
title: "Username",
|
||||
dataIndex: "username",
|
||||
key: "username",
|
||||
},
|
||||
{
|
||||
title: "Status",
|
||||
dataIndex: "status",
|
||||
key: "status",
|
||||
render: (text, record) => (
|
||||
<>
|
||||
<Tag color="#E3E8EE" style={{color: "#4F566B"}}>
|
||||
Inactive
|
||||
</Tag>
|
||||
<Tag color="processing">Active</Tag>
|
||||
</>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: "Action",
|
||||
key: "action",
|
||||
render: (text, record) => (
|
||||
<Space size="middle">
|
||||
<Button onClick={() => {
|
||||
setVisibleModal(true)
|
||||
setInitialData(record)
|
||||
}}>Edit</Button>
|
||||
<Button onClick={async () => {
|
||||
handleDelete(record.id)
|
||||
|
||||
}}
|
||||
>Delete</Button>
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
const routeData = [
|
||||
{
|
||||
route: "/app/home",
|
||||
name: "Home",
|
||||
},
|
||||
{
|
||||
route: "/app/membership",
|
||||
name: <span style={{fontWeight: "bold"}}>Membership</span>,
|
||||
},
|
||||
];
|
||||
|
||||
const onSubmit = async (data) => {
|
||||
if (initialData.id) {
|
||||
setInitialData({})
|
||||
setConfirmLoading(true);
|
||||
try {
|
||||
await store.membership.update(initialData.id, data)
|
||||
message.success("Success Update Data Member")
|
||||
} catch (e) {
|
||||
message.error("Failed Update Data Member")
|
||||
}
|
||||
setConfirmLoading(false);
|
||||
setVisibleModal(false);
|
||||
} else {
|
||||
setInitialData({})
|
||||
setConfirmLoading(true);
|
||||
try {
|
||||
await store.membership.create(data)
|
||||
message.success("Success Add New Member")
|
||||
} catch (e) {
|
||||
console.log(e, "apa errornya")
|
||||
message.error("Failed Add Member")
|
||||
}
|
||||
setConfirmLoading(false);
|
||||
setVisibleModal(false);
|
||||
}
|
||||
}
|
||||
|
||||
const handleDelete = (record) => {
|
||||
Modal.confirm({
|
||||
title: 'Are you sure reject this record?',
|
||||
icon: <ExclamationCircleOutlined/>,
|
||||
okText: 'Yes',
|
||||
okType: 'primary',
|
||||
cancelText: 'Cancel',
|
||||
onOk() {
|
||||
try {
|
||||
//TODO: minta apinya ke ka ilham ya, jangan di uncomment kalo pake api reconcile, nanti beneran ke apus datanya
|
||||
// await store.membership.delete(record.id)
|
||||
message.success('Success Delete Data')
|
||||
} catch (e) {
|
||||
message.error("Failed Delete Data")
|
||||
}
|
||||
},
|
||||
onCancel() {
|
||||
console.log('Cancel');
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<BreadcumbComponent data={routeData}/>
|
||||
<Card>
|
||||
{store.ui.mediaQuery.isDesktop && (
|
||||
<div>
|
||||
<Row style={{marginBottom: 20}}>
|
||||
<Col span={12}>
|
||||
<Button>
|
||||
<FilterOutlined/>
|
||||
Filter
|
||||
</Button>
|
||||
</Col>
|
||||
<Col span={12} style={{textAlign: "right"}}>
|
||||
<Search
|
||||
placeholder="input search text"
|
||||
style={{width: 200, marginRight: 10}}
|
||||
/>
|
||||
<Button onClick={() => setVisibleModal(true)}>
|
||||
<PlusSquareOutlined/> New
|
||||
</Button>
|
||||
</Col>
|
||||
</Row>
|
||||
<Table
|
||||
style={{textAlign: "center"}}
|
||||
columns={columns}
|
||||
dataSource={store.membership.data}
|
||||
bordered
|
||||
pagination={{
|
||||
total: store.membership.total_data,
|
||||
current: store.membership.page,
|
||||
pageSize: store.membership.pageSize,
|
||||
simple: true
|
||||
}} onChange={(page) => {
|
||||
store.membership.pageSize = page.pageSize;
|
||||
store.membership.page = page.current;
|
||||
store.membership.getData();
|
||||
}} current={store.membership.page}
|
||||
loading={store.membership.pageSize}/>
|
||||
</div>
|
||||
)}
|
||||
{store.ui.mediaQuery.isMobile && (
|
||||
<div>
|
||||
<Card bordered={false} bodyStyle={{padding: "0"}} style={{borderRadius: 0, width: '50%'}}>
|
||||
<PageHeader
|
||||
className={"card-page-header"}
|
||||
style={{
|
||||
padding: "6px 8px",
|
||||
}}
|
||||
title={
|
||||
<Button
|
||||
icon={<FilterOutlined/>}
|
||||
size={"small"}
|
||||
style={{margin: 3}}
|
||||
>
|
||||
Filter
|
||||
</Button>
|
||||
}
|
||||
subTitle=""
|
||||
/>
|
||||
</Card>
|
||||
|
||||
<List
|
||||
itemLayout="horizontal"
|
||||
position={"top"}
|
||||
dataSource={store.membership.data}
|
||||
style={{padding: 0}}
|
||||
renderItem={(item) => {
|
||||
console.log(item, "item ->");
|
||||
return (
|
||||
<div>
|
||||
<List.Item
|
||||
key={item.key}
|
||||
style={{
|
||||
backgroundColor: "#ffffff",
|
||||
paddingTop: 0,
|
||||
paddingBottom: 0,
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
width: "50%"
|
||||
}}
|
||||
>
|
||||
<List.Item.Meta
|
||||
className={["cariparkir-container"].join(" ")}
|
||||
title={<h3 style={{marginBottom: 0, color: "#5469d4"}}>{item.name}</h3>}
|
||||
description={
|
||||
<div style={{}}>
|
||||
<p>
|
||||
<small>Username : {item.username}</small> <br/>
|
||||
</p>
|
||||
<p>
|
||||
{item.status}
|
||||
</p>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
{/* <div style={{ marginRight: 16 }}>
|
||||
<Statistic
|
||||
title={
|
||||
<p
|
||||
style={{
|
||||
fontSize: 9,
|
||||
margin: 0,
|
||||
}}
|
||||
>
|
||||
{item.updated_at ? moment(item.updated_at).format("DD MMM YY, H:mm:ss") : "There is not top up yet"}
|
||||
</p>
|
||||
}
|
||||
prefix={"Rp"}
|
||||
precision={0}
|
||||
style={{ fontSize: 12, fontWeight: 300 }}
|
||||
valueStyle={{
|
||||
color: "#5469d4",
|
||||
fontSize: 12,
|
||||
fontWeight: 600,
|
||||
textAlign: "right",
|
||||
}}
|
||||
value={item.balances}
|
||||
/>
|
||||
|
||||
</div> */}
|
||||
</List.Item>
|
||||
{/* <Divider plain style={{ margin: 0 }} /> */}
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</Card>
|
||||
<MembershipModal visible={visibleModal}
|
||||
confirmLoading={confirmLoading}
|
||||
initialData={initialData}
|
||||
onCreate={async (data) => {
|
||||
onSubmit(data)
|
||||
}}
|
||||
onCancel={() => {
|
||||
setInitialData({})
|
||||
setVisibleModal(false);
|
||||
}}/>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
82
src/pages/Membership/MembershipModal.js
Normal file
82
src/pages/Membership/MembershipModal.js
Normal file
@@ -0,0 +1,82 @@
|
||||
import React from 'react';
|
||||
import {Form, Input, Modal, Select,} from 'antd';
|
||||
|
||||
export const MembershipModal = ({
|
||||
visible,
|
||||
onCreate,
|
||||
onCancel,
|
||||
initialData,
|
||||
}) => {
|
||||
const [form] = Form.useForm();
|
||||
const {Option} = Select;
|
||||
const dataStatus = ['Active', 'Inactive']
|
||||
|
||||
|
||||
return (
|
||||
<Modal
|
||||
visible={visible}
|
||||
title={initialData.id ? "Edit Member" : "Create a new Member"}
|
||||
okText={initialData.id ? "Edit" : "Create"}
|
||||
cancelText="Cancel"
|
||||
onCancel={() => {
|
||||
form.resetFields()
|
||||
onCancel()
|
||||
}}
|
||||
onOk={() => {
|
||||
form
|
||||
.validateFields()
|
||||
.then(values => {
|
||||
onCreate(values);
|
||||
form.resetFields()
|
||||
})
|
||||
.catch(info => {
|
||||
console.log('Validate Failed:', info);
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Form
|
||||
form={form}
|
||||
layout="vertical"
|
||||
name="form_in_modal"
|
||||
initialValues={initialData}
|
||||
>
|
||||
<Form.Item
|
||||
name="name"
|
||||
label="Name"
|
||||
rules={[{required: true, message: 'Please input Name!'}]}
|
||||
>
|
||||
<Input/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="username"
|
||||
label="Username"
|
||||
rules={[{required: true, message: 'Please input Username!'}]}
|
||||
>
|
||||
<Input/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="status"
|
||||
label="Status"
|
||||
|
||||
rules={[{required: true, message: 'Please select Status!'}]}
|
||||
>
|
||||
<Select
|
||||
showSearch
|
||||
placeholder="Select Status"
|
||||
optionFilterProp="children"
|
||||
filterOption={(input, option) =>
|
||||
option.children.toLowerCase().indexOf(input.toLowerCase()) >= 0
|
||||
}
|
||||
filterSort={(optionA, optionB) =>
|
||||
optionA.children.toLowerCase().localeCompare(optionB.children.toLowerCase())
|
||||
}
|
||||
>
|
||||
{dataStatus.map(it => {
|
||||
return <Option value={it}>{it}</Option>
|
||||
})}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
70
src/pages/Product/Product.js
Normal file
70
src/pages/Product/Product.js
Normal file
@@ -0,0 +1,70 @@
|
||||
import React from "react";
|
||||
import {Button, Card, Col, Input, Row, Tabs} from "antd";
|
||||
import {FilterOutlined, PlusSquareOutlined,} from "@ant-design/icons";
|
||||
import {BreadcumbComponent} from "../../component/BreadcumbComponent";
|
||||
import {Pulsa} from "./Pulsa";
|
||||
|
||||
const {TabPane} = Tabs;
|
||||
const {Search} = Input;
|
||||
|
||||
export const Product = () => {
|
||||
const callback = (key) => {
|
||||
console.log(key);
|
||||
};
|
||||
const routeData = [
|
||||
{
|
||||
route: "/app/home",
|
||||
name: "Home",
|
||||
},
|
||||
{
|
||||
route: "/app/product",
|
||||
name: <span style={{fontWeight: 'bold'}}>Product</span>,
|
||||
},
|
||||
];
|
||||
return (
|
||||
<div>
|
||||
<BreadcumbComponent data={routeData}/>
|
||||
<Card>
|
||||
<Row style={{marginBottom: 20}}>
|
||||
<Col span={12}>
|
||||
<Button>
|
||||
<FilterOutlined/>
|
||||
Filter
|
||||
</Button>
|
||||
</Col>
|
||||
<Col span={12} style={{textAlign: "right"}}>
|
||||
<Search
|
||||
placeholder="input search text"
|
||||
style={{width: 200, marginRight: 10}}
|
||||
/>
|
||||
<Button>
|
||||
<PlusSquareOutlined/> New
|
||||
</Button>
|
||||
</Col>
|
||||
</Row>
|
||||
<Tabs
|
||||
defaultActiveKey="1"
|
||||
onChange={callback}
|
||||
size="default"
|
||||
tabBarGutter="50"
|
||||
>
|
||||
<TabPane tab="Pulsa" key="1">
|
||||
<Pulsa/>
|
||||
</TabPane>
|
||||
<TabPane tab="Game Voucher" key="2">
|
||||
Game Voucher
|
||||
</TabPane>
|
||||
<TabPane tab="Product" key="3">
|
||||
Product
|
||||
</TabPane>
|
||||
<TabPane tab="Prduct" key="4">
|
||||
Prduct
|
||||
</TabPane>
|
||||
<TabPane tab="Prdct" key="5">
|
||||
Prdct
|
||||
</TabPane>
|
||||
</Tabs>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
100
src/pages/Product/Pulsa.js
Normal file
100
src/pages/Product/Pulsa.js
Normal file
@@ -0,0 +1,100 @@
|
||||
import React from "react";
|
||||
import {Button, Space, Table, Tag} from "antd";
|
||||
|
||||
export const Pulsa = () => {
|
||||
const columns = [
|
||||
{
|
||||
title: "Kode",
|
||||
dataIndex: "kode",
|
||||
key: "kode",
|
||||
},
|
||||
{
|
||||
title: "Produk",
|
||||
dataIndex: "produk",
|
||||
key: "produk",
|
||||
},
|
||||
{
|
||||
title: "Harga Beli",
|
||||
dataIndex: "harga_beli",
|
||||
key: "harga_beli",
|
||||
},
|
||||
,
|
||||
{
|
||||
title: "Harga Jual",
|
||||
dataIndex: "harga_jual",
|
||||
key: "harga_beli",
|
||||
},
|
||||
{
|
||||
title: "Gangguan",
|
||||
dataIndex: "gangguan",
|
||||
key: "gangguan",
|
||||
},
|
||||
{
|
||||
title: "Tersedia",
|
||||
dataIndex: "tersedia",
|
||||
key: "tersedia",
|
||||
},
|
||||
{
|
||||
title: "Action",
|
||||
key: "action",
|
||||
render: (text, record) => (
|
||||
<Space size="middle">
|
||||
<Button>Edit</Button>
|
||||
<Button>Delete</Button>
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
];
|
||||
const dataSource = [
|
||||
{
|
||||
key: "1",
|
||||
kode: "BROP2",
|
||||
produk: "DATA AXIS 2GB-60HR",
|
||||
harga_beli: "Rp.10.000",
|
||||
harga_beli: "Rp.10.000",
|
||||
harga_jual: "Rp.40.000",
|
||||
gangguan: <Tag color="processing">Active</Tag>,
|
||||
tersedia: <Tag color="processing">Ya</Tag>,
|
||||
},
|
||||
{
|
||||
key: "2",
|
||||
kode: "-",
|
||||
produk: "-",
|
||||
harga_beli: "-",
|
||||
harga_beli: "-",
|
||||
harga_jual: "-",
|
||||
gangguan: <Tag color="#E3E8EE" style={{color: '#4F566B'}}>Gangguan</Tag>,
|
||||
tersedia: <Tag color="#E3E8EE" style={{color: '#4F566B'}}>Tidak</Tag>,
|
||||
},
|
||||
{
|
||||
key: "3",
|
||||
kode: "-",
|
||||
produk: "-",
|
||||
harga_beli: "-",
|
||||
harga_beli: "-",
|
||||
harga_jual: "-",
|
||||
gangguan: <Tag color="processing">Active</Tag>,
|
||||
tersedia: <Tag color="processing">Ya</Tag>,
|
||||
},
|
||||
{
|
||||
key: "4",
|
||||
kode: "-",
|
||||
produk: "-",
|
||||
harga_beli: "-",
|
||||
harga_beli: "-",
|
||||
harga_jual: "-",
|
||||
gangguan: <Tag color="processing">Active</Tag>,
|
||||
tersedia: <Tag color="processing">Ya</Tag>,
|
||||
}
|
||||
];
|
||||
return (
|
||||
<div>
|
||||
<Table
|
||||
style={{textAlign: "center"}}
|
||||
columns={columns}
|
||||
dataSource={dataSource}
|
||||
bordered
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
127
src/pages/Transaction/Pulsa.js
Normal file
127
src/pages/Transaction/Pulsa.js
Normal file
@@ -0,0 +1,127 @@
|
||||
import React from "react";
|
||||
import {Button, Card, Col, Dropdown, Menu, message, Modal, Row, Space,} from "antd";
|
||||
import {DownOutlined, TabletOutlined, UserOutlined} from "@ant-design/icons";
|
||||
|
||||
//const style = { background: "#0092ff", padding: "8px 0" };
|
||||
const gridStyle = {
|
||||
width: "18%",
|
||||
textAlign: "center",
|
||||
marginRight: "8px",
|
||||
marginBottom: "20px",
|
||||
};
|
||||
|
||||
export const Pulsa = () => {
|
||||
|
||||
function handleMenuClick(e) {
|
||||
message.info("Click on menu item.");
|
||||
console.log("click", e);
|
||||
}
|
||||
|
||||
const menu = (
|
||||
<Menu onClick={handleMenuClick}>
|
||||
<Menu.Item key="1" icon={<UserOutlined/>}>
|
||||
1st menu item
|
||||
</Menu.Item>
|
||||
<Menu.Item key="2" icon={<UserOutlined/>}>
|
||||
2nd menu item
|
||||
</Menu.Item>
|
||||
<Menu.Item key="3" icon={<UserOutlined/>}>
|
||||
3rd menu item
|
||||
</Menu.Item>
|
||||
</Menu>
|
||||
);
|
||||
|
||||
function success() {
|
||||
Modal.success({
|
||||
content: 'some messages...some messages...',
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Row>
|
||||
<span style={{fontWeight: "bold", marginBottom: "10px"}}>
|
||||
Sub-Category
|
||||
</span>
|
||||
</Row>
|
||||
<Row>
|
||||
<Space wrap>
|
||||
<Dropdown overlay={menu}>
|
||||
<Button
|
||||
style={{
|
||||
marginBottom: "20px",
|
||||
color: "grey",
|
||||
}}
|
||||
>
|
||||
<TabletOutlined/>
|
||||
Select sub-Category
|
||||
<DownOutlined/>
|
||||
</Button>
|
||||
</Dropdown>
|
||||
</Space>
|
||||
</Row>
|
||||
<Row>
|
||||
<span style={{fontWeight: "bold", marginBottom: "10px"}}>
|
||||
Produk & Nominal
|
||||
</span>
|
||||
</Row>
|
||||
<Card>
|
||||
<Card.Grid style={gridStyle} onClick={success}>
|
||||
<span style={{color: "black"}}>DATA AXIS BRONET 2GB-60HR</span>
|
||||
<br/>
|
||||
<span style={{color: "grey", fontSize: 10}}>Harga : Rp.6.000</span>
|
||||
</Card.Grid>
|
||||
<Card.Grid style={gridStyle}>
|
||||
<span style={{color: "black"}}>DATA AXIS BRONET 2GB-60HR</span>
|
||||
<br/>
|
||||
<span style={{color: "grey", fontSize: 10}}>Harga : Rp.6.000</span>
|
||||
</Card.Grid>
|
||||
<Card.Grid style={gridStyle}>
|
||||
<span style={{color: "black"}}>DATA AXIS BRONET 2GB-60HR</span>
|
||||
<br/>
|
||||
<span style={{color: "grey", fontSize: 10}}>Harga : Rp.6.000</span>
|
||||
</Card.Grid>
|
||||
<Card.Grid style={gridStyle}>
|
||||
<span style={{color: "black"}}>DATA AXIS BRONET 2GB-60HR</span>
|
||||
<br/>
|
||||
<span style={{color: "grey", fontSize: 10}}>Harga : Rp.6.000</span>
|
||||
</Card.Grid>
|
||||
<Card.Grid style={gridStyle}>
|
||||
<span style={{color: "black"}}>DATA AXIS BRONET 2GB-60HR</span>
|
||||
<br/>
|
||||
<span style={{color: "grey", fontSize: 10}}>Harga : Rp.6.000</span>
|
||||
</Card.Grid>
|
||||
<Card.Grid style={gridStyle}>
|
||||
<span style={{color: "black"}}>DATA AXIS BRONET 2GB-60HR</span>
|
||||
<br/>
|
||||
<span style={{color: "grey", fontSize: 10}}>Harga : Rp.6.000</span>
|
||||
</Card.Grid>
|
||||
<Card.Grid style={gridStyle}>
|
||||
<span style={{color: "black"}}>DATA AXIS BRONET 2GB-60HR</span>
|
||||
<br/>
|
||||
<span style={{color: "grey", fontSize: 10}}>Harga : Rp.6.000</span>
|
||||
</Card.Grid>
|
||||
<Card.Grid style={gridStyle}>
|
||||
<span style={{color: "black"}}>DATA AXIS BRONET 2GB-60HR</span>
|
||||
<br/>
|
||||
<span style={{color: "grey", fontSize: 10}}>Harga : Rp.6.000</span>
|
||||
</Card.Grid>
|
||||
<Card.Grid style={gridStyle}>
|
||||
<span style={{color: "black"}}>DATA AXIS BRONET 2GB-60HR</span>
|
||||
<br/>
|
||||
<span style={{color: "grey", fontSize: 10}}>Harga : Rp.6.000</span>
|
||||
</Card.Grid>
|
||||
<Card.Grid style={gridStyle}>
|
||||
<span style={{color: "black"}}>DATA AXIS BRONET 2GB-60HR</span>
|
||||
<br/>
|
||||
<span style={{color: "grey", fontSize: 10}}>Harga : Rp.6.000</span>
|
||||
</Card.Grid>
|
||||
</Card>
|
||||
<Col style={{textAlign: "right"}}>
|
||||
<Button style={{backgroundColor: "#2D9CDB", color: "white"}}>
|
||||
Beli Sekarang
|
||||
</Button>
|
||||
</Col>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
67
src/pages/Transaction/Transaction.js
Normal file
67
src/pages/Transaction/Transaction.js
Normal file
@@ -0,0 +1,67 @@
|
||||
import React from "react";
|
||||
import {Button, Card, Col, Input, Row, Tabs} from "antd";
|
||||
import {FilterOutlined,} from "@ant-design/icons";
|
||||
import {BreadcumbComponent} from "../../component/BreadcumbComponent";
|
||||
import {Pulsa} from "./Pulsa";
|
||||
|
||||
const {TabPane} = Tabs;
|
||||
const {Search} = Input;
|
||||
|
||||
export const Transaction = () => {
|
||||
const callback = (key) => {
|
||||
console.log(key);
|
||||
};
|
||||
const routeData = [
|
||||
{
|
||||
route: "/app/home",
|
||||
name: "Home",
|
||||
},
|
||||
{
|
||||
route: "/app/transaction",
|
||||
name: <span style={{fontWeight: 'bold'}}>Transaction</span>,
|
||||
},
|
||||
];
|
||||
return (
|
||||
<div>
|
||||
<BreadcumbComponent data={routeData} text=""/>
|
||||
<Card>
|
||||
<Row style={{marginBottom: 20}}>
|
||||
<Col span={12}>
|
||||
<Button>
|
||||
<FilterOutlined/>
|
||||
Filter
|
||||
</Button>
|
||||
</Col>
|
||||
<Col span={12} style={{textAlign: "right"}}>
|
||||
<Search
|
||||
placeholder="input search text"
|
||||
style={{width: 200, marginRight: 10}}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<Tabs
|
||||
defaultActiveKey="1"
|
||||
onChange={callback}
|
||||
size="default"
|
||||
tabBarGutter="50"
|
||||
>
|
||||
<TabPane tab="Pulsa" key="1">
|
||||
<Pulsa/>
|
||||
</TabPane>
|
||||
<TabPane tab="Game Voucher" key="2">
|
||||
Game Voucher
|
||||
</TabPane>
|
||||
<TabPane tab="Product" key="3">
|
||||
Product
|
||||
</TabPane>
|
||||
<TabPane tab="Prduct" key="4">
|
||||
Prduct
|
||||
</TabPane>
|
||||
<TabPane tab="Prdct" key="5">
|
||||
Prdct
|
||||
</TabPane>
|
||||
</Tabs>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
13
src/reportWebVitals.js
Normal file
13
src/reportWebVitals.js
Normal file
@@ -0,0 +1,13 @@
|
||||
const reportWebVitals = onPerfEntry => {
|
||||
if (onPerfEntry && onPerfEntry instanceof Function) {
|
||||
import('web-vitals').then(({getCLS, getFID, getFCP, getLCP, getTTFB}) => {
|
||||
getCLS(onPerfEntry);
|
||||
getFID(onPerfEntry);
|
||||
getFCP(onPerfEntry);
|
||||
getLCP(onPerfEntry);
|
||||
getTTFB(onPerfEntry);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export default reportWebVitals;
|
||||
29
src/routes/app.js
Normal file
29
src/routes/app.js
Normal file
@@ -0,0 +1,29 @@
|
||||
import {Redirect, Route, Switch} from "react-router-dom";
|
||||
import {Home} from "../pages/Home/Home";
|
||||
import {About} from "../pages/About/About";
|
||||
import {Membership} from "../pages/Membership/Membership";
|
||||
import {Product} from "../pages/Product/Product";
|
||||
import {Transaction} from "../pages/Transaction/Transaction";
|
||||
|
||||
export const AppRoute = () => {
|
||||
return <Switch>
|
||||
<Route path={"/app/home"}>
|
||||
<Home/>
|
||||
</Route>
|
||||
<Route path={"/app/membership"}>
|
||||
<Membership/>
|
||||
</Route>
|
||||
<Route path={"/app/product"}>
|
||||
<Product/>
|
||||
</Route>
|
||||
<Route path={"/app/transaction"}>
|
||||
<Transaction/>
|
||||
</Route>
|
||||
<Route path={"/app/about"}>
|
||||
<About/>
|
||||
</Route>
|
||||
<Route path="/app" exact>
|
||||
<Redirect to={'/app/home'}/>
|
||||
</Route>
|
||||
</Switch>
|
||||
}
|
||||
16
src/routes/index.js
Normal file
16
src/routes/index.js
Normal file
@@ -0,0 +1,16 @@
|
||||
import {Redirect, Route, Switch} from "react-router-dom";
|
||||
import {Login} from "../pages/Login/Login";
|
||||
import {PublicRoute} from "../component/PublicRoute";
|
||||
import {App} from "../pages/App/App";
|
||||
|
||||
export const MainRoutes = (props) => {
|
||||
return (
|
||||
<Switch>
|
||||
<Route path="/" exact>
|
||||
<Redirect to={"/app/home"}/>
|
||||
</Route>
|
||||
<PublicRoute restricted={true} component={Login} path="/login" exact/>
|
||||
<PublicRoute component={App} path="/app"/>
|
||||
</Switch>
|
||||
);
|
||||
};
|
||||
5
src/setupTests.js
Normal file
5
src/setupTests.js
Normal file
@@ -0,0 +1,5 @@
|
||||
// jest-dom adds custom jest matchers for asserting on DOM nodes.
|
||||
// allows you to do things like:
|
||||
// expect(element).toHaveTextContent(/react/i)
|
||||
// learn more: https://github.com/testing-library/jest-dom
|
||||
import '@testing-library/jest-dom';
|
||||
27
src/store/authentication.js
Normal file
27
src/store/authentication.js
Normal file
@@ -0,0 +1,27 @@
|
||||
import {makeAutoObservable} from "mobx";
|
||||
|
||||
export class Authentication {
|
||||
ctx;
|
||||
|
||||
accessToken = '';
|
||||
|
||||
refreshToken = '';
|
||||
|
||||
constructor(ctx) {
|
||||
this.ctx = ctx;
|
||||
makeAutoObservable(this);
|
||||
}
|
||||
|
||||
get isLoggedIn() {
|
||||
return !!this.refreshToken;
|
||||
}
|
||||
|
||||
setInitialToken(accessToken, refreshToken) {
|
||||
this.setToken(accessToken, refreshToken);
|
||||
}
|
||||
|
||||
setToken(accessToken, refreshToken) {
|
||||
this.accessToken = accessToken;
|
||||
this.refreshToken = refreshToken;
|
||||
}
|
||||
}
|
||||
14
src/store/index.js
Normal file
14
src/store/index.js
Normal file
@@ -0,0 +1,14 @@
|
||||
import {UI} from "./ui";
|
||||
import {Authentication} from "./authentication";
|
||||
import {User} from "./user";
|
||||
import {Membership} from "./membership";
|
||||
|
||||
export class Store {
|
||||
ui = new UI(this);
|
||||
authentication = new Authentication(this);
|
||||
user = new User(this);
|
||||
membership = new Membership(this);
|
||||
|
||||
constructor() {
|
||||
}
|
||||
}
|
||||
37
src/store/membership.js
Normal file
37
src/store/membership.js
Normal file
@@ -0,0 +1,37 @@
|
||||
import {action, makeAutoObservable} from "mobx";
|
||||
import {http} from "../utils/http";
|
||||
|
||||
export class Membership {
|
||||
page = 1;
|
||||
pageSize = 10
|
||||
data = [];
|
||||
total_data = 0
|
||||
|
||||
constructor(ctx) {
|
||||
this.ctx = ctx;
|
||||
makeAutoObservable(this);
|
||||
}
|
||||
|
||||
@action
|
||||
async getData() {
|
||||
const response = await http.get(`/user?page=${this.page}&pageSize=${this.pageSize}`);
|
||||
this.data = response.body.data ?? []
|
||||
this.total_data = response.body.total_data ?? 0
|
||||
}
|
||||
|
||||
@action
|
||||
async create(data) {
|
||||
return await http.post('/user').send(data)
|
||||
}
|
||||
|
||||
@action
|
||||
async update(id, data) {
|
||||
return await http.put('/user/' + id).send(data);
|
||||
}
|
||||
|
||||
async delete(id) {
|
||||
return await http.del('/user/' + id);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
25
src/store/ui.js
Normal file
25
src/store/ui.js
Normal file
@@ -0,0 +1,25 @@
|
||||
import {makeAutoObservable} from "mobx";
|
||||
|
||||
export class UI {
|
||||
mediaQuery = {
|
||||
isMobile: false,
|
||||
isDesktop: true
|
||||
};
|
||||
|
||||
testValue = "Test Mobx";
|
||||
|
||||
constructor(ctx) {
|
||||
this.ctx = ctx;
|
||||
makeAutoObservable(this);
|
||||
}
|
||||
|
||||
setTestValue() {
|
||||
this.testValue = "yo dayo";
|
||||
}
|
||||
|
||||
setMediaQuery(data) {
|
||||
if (this.mediaQuery.isDesktop !== data.isDesktop || this.mediaQuery.isMobile !== data.isMobile) {
|
||||
this.mediaQuery = data;
|
||||
}
|
||||
};
|
||||
}
|
||||
13
src/store/user.js
Normal file
13
src/store/user.js
Normal file
@@ -0,0 +1,13 @@
|
||||
import {action, observable} from "mobx";
|
||||
import {http} from "../utils/http";
|
||||
|
||||
export class User {
|
||||
@observable data = [];
|
||||
|
||||
@action
|
||||
async getData() {
|
||||
this.data = (await http.get('/user')).body.data;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
169
src/utils/http.js
Normal file
169
src/utils/http.js
Normal file
@@ -0,0 +1,169 @@
|
||||
import superagent from "superagent";
|
||||
import {appConfig} from "../config/app";
|
||||
import {store} from "./useStore";
|
||||
|
||||
export const http = {
|
||||
get: (url, opts = {}) => {
|
||||
let req = superagent.get(appConfig.apiUrl + url);
|
||||
if (store.token) {
|
||||
req = req.set('Authorization', 'Bearer ' + store.token);
|
||||
}
|
||||
return req;
|
||||
},
|
||||
post: (url, opts) => {
|
||||
let req = superagent.post(appConfig.apiUrl + url);
|
||||
if (store.token) {
|
||||
req = req.set('Authorization', 'Bearer ' + store.token);
|
||||
}
|
||||
return req;
|
||||
},
|
||||
put: (url, opts) => {
|
||||
let req = superagent.put(appConfig.apiUrl + url);
|
||||
if (store.token) {
|
||||
req = req.set('Authorization', 'Bearer ' + store.token);
|
||||
}
|
||||
return req;
|
||||
},
|
||||
del: (url, opts) => {
|
||||
let req = superagent.del(appConfig.apiUrl + url);
|
||||
if (store.token) {
|
||||
req = req.set('Authorization', 'Bearer ' + store.token);
|
||||
}
|
||||
return req;
|
||||
},
|
||||
upload: (file) => {
|
||||
const request = superagent
|
||||
.post(appConfig.apiUrl + '/files')
|
||||
.attach('file', file);
|
||||
|
||||
return request;
|
||||
},
|
||||
uploadAntd: (args) => {
|
||||
const file = args.file;
|
||||
const request = http.upload(file);
|
||||
request
|
||||
.on('progress', event => {
|
||||
args.onProgress(event);
|
||||
})
|
||||
.then(it => {
|
||||
args.onSuccess(it);
|
||||
}).catch(err => {
|
||||
args.onError(err);
|
||||
});
|
||||
|
||||
return request;
|
||||
}
|
||||
};
|
||||
|
||||
// import superagent from "superagent";
|
||||
// import appConfig from "../Config/StaticVar";
|
||||
// import { TokenUtil } from './token'
|
||||
// import { navigationService } from './NavigationService'
|
||||
// import { Alert } from 'react-native'
|
||||
// import StaticVar from '../Config/StaticVar'
|
||||
// import AsyncStorage from '@react-native-async-storage/async-storage'
|
||||
// import FirebaseLib from '../Lib/FirebaseLib'
|
||||
|
||||
// let AuthIntercept = require('superagent-intercept')((err, res) => {
|
||||
// if (res?.status === 403) {
|
||||
// console.log("masuk sini ya")
|
||||
// Alert.alert("Sesi Anda Telah Habis", "Sesi Anda Telah Habis, Harap Login Kembali.", [
|
||||
// { text: "Oke", onPress: async () => {
|
||||
// navigationService.reset('LoginScreen');
|
||||
// await AsyncStorage.removeItem(StaticVar.DB_PROFILE)
|
||||
// await FirebaseLib.signOut()
|
||||
// }}
|
||||
// ]);
|
||||
// }
|
||||
// });
|
||||
|
||||
// export const http = {
|
||||
// get: (url, opts = {}) => {
|
||||
// try{
|
||||
// let req = superagent.get(appConfig.API_URL + url)
|
||||
// .use(AuthIntercept)
|
||||
// if (TokenUtil.accessToken) {
|
||||
// req = req.set('Authorization', 'Bearer ' + TokenUtil.accessToken);
|
||||
// }
|
||||
// return req;
|
||||
// }catch (e) {
|
||||
// console.log(e, "error get superagent")
|
||||
// throw e
|
||||
// }
|
||||
|
||||
// },
|
||||
// post: (url, opts) => {
|
||||
// try{
|
||||
// let req = superagent.post(appConfig.API_URL + url)
|
||||
// .use(AuthIntercept)
|
||||
// if (TokenUtil.accessToken) {
|
||||
// req = req.set('Authorization', 'Bearer ' + TokenUtil.accessToken);
|
||||
// }
|
||||
// return req;
|
||||
// }catch (e) {
|
||||
// console.log(e, "error post superagent")
|
||||
// throw e
|
||||
// }
|
||||
// },
|
||||
// put: (url, opts) => {
|
||||
// try{
|
||||
// let req = superagent.put(appConfig.API_URL + url)
|
||||
// .use(AuthIntercept)
|
||||
// if (TokenUtil.accessToken) {
|
||||
// req = req.set('Authorization', 'Bearer ' + TokenUtil.accessToken);
|
||||
// }
|
||||
// return req;
|
||||
// }catch (e) {
|
||||
// console.log(e, "error put superagent")
|
||||
// throw e
|
||||
// }
|
||||
// },
|
||||
// del: (url, opts) => {
|
||||
// try{
|
||||
// let req = superagent.del(appConfig.API_URL + url)
|
||||
// .use(AuthIntercept)
|
||||
// if (TokenUtil.accessToken) {
|
||||
// req = req.set('Authorization', 'Bearer ' + TokenUtil.accessToken);
|
||||
// }
|
||||
// return req;
|
||||
// }catch (e) {
|
||||
// console.log(e, "error del superagent")
|
||||
// throw e
|
||||
// }
|
||||
// },
|
||||
// upload: (file) => {
|
||||
// try{
|
||||
// const request = superagent
|
||||
// .post(appConfig.API_URL + '/v1/files')
|
||||
// .use(AuthIntercept)
|
||||
// .attach('file', file);
|
||||
|
||||
// return request;
|
||||
// }catch (e) {
|
||||
// console.log(e, "error upload superagent")
|
||||
// throw e
|
||||
// }
|
||||
// },
|
||||
// uploadAntd: (args) => {
|
||||
// try{
|
||||
// const file = args.file;
|
||||
// const request = http.upload(file)
|
||||
// .use(AuthIntercept)
|
||||
|
||||
// request
|
||||
// .on('progress', event => {
|
||||
// args.onProgress(event);
|
||||
// })
|
||||
// .then(it => {
|
||||
// args.onSuccess(it);
|
||||
// }).catch(err => {
|
||||
// args.onError(err);
|
||||
// });
|
||||
// return request;
|
||||
// }catch (e) {
|
||||
// console.log(e, "error uploadAntd superagent")
|
||||
// throw e
|
||||
// }
|
||||
// }
|
||||
|
||||
// };
|
||||
62
src/utils/permute.js
Normal file
62
src/utils/permute.js
Normal file
@@ -0,0 +1,62 @@
|
||||
function k_combinations(set, k) {
|
||||
var i, j, combs, head, tailcombs;
|
||||
|
||||
if (k > set.length || k <= 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (k == set.length) {
|
||||
return [set];
|
||||
}
|
||||
|
||||
if (k == 1) {
|
||||
combs = [];
|
||||
for (i = 0; i < set.length; i++) {
|
||||
combs.push([set[i]]);
|
||||
}
|
||||
return combs;
|
||||
}
|
||||
|
||||
combs = [];
|
||||
for (i = 0; i < set.length - k + 1; i++) {
|
||||
// head is a list that includes only our current element.
|
||||
head = set.slice(i, i + 1);
|
||||
// We take smaller combinations from the subsequent elements
|
||||
tailcombs = k_combinations(set.slice(i + 1), k - 1);
|
||||
// For each (k-1)-combination we join it with the current
|
||||
// and store it to the set of k-combinations.
|
||||
for (j = 0; j < tailcombs.length; j++) {
|
||||
combs.push(head.concat(tailcombs[j]));
|
||||
}
|
||||
}
|
||||
return combs;
|
||||
}
|
||||
|
||||
function stringPermutations(str) {
|
||||
let letters = str.split('')
|
||||
, results = [[letters.shift()]]
|
||||
while (letters.length) {
|
||||
const currLetter = letters.shift()
|
||||
let tmpResults = []
|
||||
results.forEach(result => {
|
||||
let rIdx = 0
|
||||
while (rIdx <= result.length) {
|
||||
const tmp = [...result]
|
||||
tmp.splice(rIdx, 0, currLetter)
|
||||
tmpResults.push(tmp)
|
||||
rIdx++
|
||||
}
|
||||
})
|
||||
results = tmpResults
|
||||
}
|
||||
return results
|
||||
.map(letterArray => letterArray.join(''))
|
||||
.filter((el, idx, self) => (self.indexOf(el) === idx))
|
||||
.sort()
|
||||
}
|
||||
|
||||
export function calculateCombination(text, n) {
|
||||
return k_combinations(text.split(''), n).map(it => stringPermutations(it.join(''))).flat().filter((value, index, self) => {
|
||||
return self.indexOf(value) === index;
|
||||
});
|
||||
}
|
||||
46
src/utils/token.js
Normal file
46
src/utils/token.js
Normal file
@@ -0,0 +1,46 @@
|
||||
export class TokenUtil {
|
||||
static accessToken = null;
|
||||
static refreshToken = null;
|
||||
|
||||
static loadToken() {
|
||||
const accessToken = localStorage.getItem('access_token');
|
||||
const refreshToken = localStorage.getItem('refresh_token');
|
||||
|
||||
if (accessToken) {
|
||||
TokenUtil.setAccessToken(accessToken);
|
||||
}
|
||||
|
||||
if (refreshToken) {
|
||||
TokenUtil.setRefreshToken(refreshToken);
|
||||
}
|
||||
}
|
||||
|
||||
static persistToken() {
|
||||
if (TokenUtil.accessToken != null) {
|
||||
localStorage.setItem('access_token', TokenUtil.accessToken);
|
||||
} else {
|
||||
localStorage.removeItem('access_token');
|
||||
}
|
||||
|
||||
if (TokenUtil.refreshToken != null) {
|
||||
localStorage.setItem('refresh_token', TokenUtil.refreshToken);
|
||||
} else {
|
||||
localStorage.removeItem('refresh_token');
|
||||
}
|
||||
}
|
||||
|
||||
static setAccessToken(accessToken) {
|
||||
TokenUtil.accessToken = accessToken;
|
||||
}
|
||||
|
||||
static setRefreshToken(refreshToken) {
|
||||
TokenUtil.refreshToken = refreshToken;
|
||||
}
|
||||
|
||||
static clearAccessToken() {
|
||||
TokenUtil.accessToken = null;
|
||||
}
|
||||
static clearRefreshToken() {
|
||||
TokenUtil.accessToken = null;
|
||||
}
|
||||
}
|
||||
23
src/utils/useStore.js
Normal file
23
src/utils/useStore.js
Normal file
@@ -0,0 +1,23 @@
|
||||
import React from 'react';
|
||||
import {Store} from "../store";
|
||||
import {useLocalObservable} from "mobx-react-lite";
|
||||
|
||||
const storeContext = React.createContext(null);
|
||||
|
||||
export const store = new Store();
|
||||
// store.authentication.loadToken();
|
||||
|
||||
export const StoreProvider = ({children}) => {
|
||||
const localStore = useLocalObservable(() => {
|
||||
return store;
|
||||
});
|
||||
return <storeContext.Provider value={localStore}>{children}</storeContext.Provider>
|
||||
};
|
||||
|
||||
export const useStore = () => {
|
||||
const store = React.useContext(storeContext);
|
||||
if (!store) {
|
||||
// throw new Error('useStore must be used within a StoreProvider.');
|
||||
}
|
||||
return store;
|
||||
};
|
||||
Reference in New Issue
Block a user