Compare commits

..

No commits in common. "main" and "685417b2599e69ddfb30baf685625304930a2c9a" have entirely different histories.

233 changed files with 33968 additions and 1 deletions

15
.env Normal file
View File

@ -0,0 +1,15 @@
REACT_APP_DOMAIN=192.168.0.68
REACT_APP_PATH=UniOilLoyaltyApp-BackEnd/public/index.php/api/cms
REACT_APP_DEV=http://$REACT_APP_DOMAIN/$REACT_APP_PATH
REACT_APP_QA=http://$REACT_APP_DOMAIN:4000/$REACT_APP_PATH
REACT_APP_IMG_URL=http://$REACT_APP_DOMAIN/UniOilLoyaltyApp-BackEnd/
REACT_APP_API=https://mobileapi.unioilapps.com/api/cms
REACT_APP_TOKEN=X_TOKEN
NODE_PATH=src/
GENERATE_SOURCEMAP=false
REACT_APP_PUBLIC=false
REACT_APP_NOTIF_API=http://unioilcms.taxikel.com:3000/api/
REACT_APP_STATION_API_TESTING=https://testing-api-station-locator.herokuapp.com/api/
REACT_APP_STATION_API_TESTINGX=https://station-locator-api.herokuapp.com/api/
REACT_APP_STATION_API=https://cors-anywhere.herokuapp.com/13.212.2.253/api/
REACT_APP_API_SYNC=https://mobileapi.unioilapps.com/api/mobile/

12
.flowconfig Normal file
View File

@ -0,0 +1,12 @@
[ignore]
./components
[include]
[libs]
.*/node_modules/*
[lints]
[options]
[strict]

50
.gitignore vendored Normal file
View File

@ -0,0 +1,50 @@
# These are some examples of commonly ignored file patterns.
# You should customize this list as applicable to your project.
# Learn more about .gitignore:
# https://www.atlassian.com/git/tutorials/saving-changes/gitignore
# Node artifact files
node_modules/
dist/
# Compiled Java class files
*.class
# Compiled Python bytecode
*.py[cod]
# Log files
*.log
# Package files
*.jar
# Maven
target/
dist/
# JetBrains IDE
.idea/
# Unit test reports
TEST*.xml
# Generated by MacOS
.DS_Store
# Generated by Windows
Thumbs.db
# Applications
*.app
*.exe
*.war
# Large media files
*.mp4
*.tiff
*.avi
*.flv
*.mov
*.wmv

40
.gitlab-ci.yml Normal file
View File

@ -0,0 +1,40 @@
image: node:latest
before_script:
- apt-get update -qy
- apt-get install -y ruby-dev
- gem install dpl
stages:
- testing
- staging
- production
testing:
type: deploy
stage: testing
image: ruby:latest
script:
- dpl --provider=heroku --app=$HEROKU_APP_TESTING --api-key=$HEROKU_API_KEY
only:
- testing
staging:
type: deploy
stage: staging
image: ruby:latest
script:
- dpl --provider=heroku --app=$HEROKU_APP_STAGING --api-key=$HEROKU_API_KEY
only:
- staging
# production:
# type: deploy
# stage: production
# image: ruby:latest
# script:
# - dpl --provider=heroku --app=$HEROKU_APP_PRODUCTION --api-key=$HEROKU_API_KEY
# only:
# - master

30
Dockerfile Normal file
View File

@ -0,0 +1,30 @@
# Stage 1 - the build process
FROM node:8.11.3 as builder
RUN mkdir /usr/src/app
WORKDIR /usr/src/app
ENV PATH /usr/src/app/node_modules/.bin:$PATH
ARG REACT_APP_PATH
ARG REACT_APP_DOMAIN
ARG REACT_APP_IMG_PATH
ARG REACT_APP_PUBLIC
ARG PUBLIC_URL
ARG GENERATE_SOURCEMAP
ENV REACT_APP_API $REACT_APP_DOMAIN/$REACT_APP_PATH
ENV REACT_APP_IMG_URL $REACT_APP_DOMAIN/$REACT_APP_IMG_PATH
RUN echo "DEBUG": $PUBLIC_URL
COPY package.json yarn.lock /usr/src/app/
RUN yarn install
COPY . /usr/src/app
RUN yarn build
# Stage 2 - the production environment
FROM nginx:1.13.9-alpine
RUN rm -rf /etc/nginx/conf.d
COPY conf /etc/nginx
RUN rm -rf /usr/share/nginx/html/*
COPY --from=builder /usr/src/app/build /usr/share/nginx/html
CMD ["nginx", "-g", "daemon off;"]

54
Makefile Normal file
View File

@ -0,0 +1,54 @@
dev-server:
docker-compose up -d --build dev-build
.PHONY: dev-server
dev-server-stop:
docker-compose stop -t 1 dev-build
.PHONY: dev-server-stop
test-server:
docker-compose up -d --build test-build
.PHONY: test-server
test-server-stop:
docker-compose stop -t 1 test-build
.PHONY: test-server-stop
prod-server:
docker-compose -f docker-compose-prod.yml up -d --build
.PHONY: prod-server
prod-server-stop:
docker-compose -f docker-compose-prod.yml down
.PHONY: prod-server-stop
dev-public-callback-server:
docker-compose up -d --build public-dev-build
.PHONY: dev-public-callback-server
dev-public-callback-server-stop:
docker-compose stop -t 1 public-dev-build
.PHONY: dev-public-callback-server-stop
test-public-callback-server:
docker-compose up -d --build public-test-build
.PHONY: test-public-callback-server
test-public-callback-server-stop:
docker-compose stop -t 1 public-test-build
.PHONY: test-public-callback-server-stop
prod-public-callback-server:
docker-compose -f docker-compose-public.yml up -d --build
.PHONY: prod-public-callback-server
prod-public-callback-server-stop:
docker-compose -f docker-compose-public.yml down
.PHONY: prod-public-callback-server-stop
stop:
docker-compose down
.PHONY: stop
clean:
docker system prune --volumes -f

View File

@ -1,2 +1,74 @@
# loyalty-cms
## Server Dependences
* [Docker/Linux](https://docs.docker.com/install/linux/docker-ce/ubuntu/)
* [Docker Compose/Linux](https://docs.docker.com/compose/install/#install-compose)
### `sudo apt-get update`
### `sudo apt-get install build-essential`
## CMS - Local Development Scripts
In the project directory, you can run:
### `npm start`
Runs the app in the development mode.<br>
Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
The page will reload if you make edits.<br>
You will also see any lint errors in the console.
## Development Server Scripts
Up the server for staging development run:
### `make dev-server`
Stop the server run:
### `make dev-server-stop`
## QA/Testing Server Scripts
Up the server for staging QA/Testing run:
### `make test-server`
Stop the server run:
### `make test-server-stop`
## Production Server Scripts
Up the server for production run:
### `make prod-server`
Stop the server run:
### `make prod-server-stop`
Clean image container run:
### `make clean`
## Public Development Server Scripts
Up the server for staging development run:
### `make dev-public-callback-server`
Stop the server run:
### `make dev-public-callback-server-stop`
## Public QA/Testing Server Scripts
Up the server for staging QA/Testing run:
### `make test-public-callback-server`
Stop the server run:
### `make test-public-callback-server-stop`
## Public Production Server Scripts
Up the server for staging QA/Testing run:
### `make prod-public-callback-server`
Stop the server run:
### `make prod-public-callback-server-stop`

133
conf/conf.d/default.conf Normal file
View File

@ -0,0 +1,133 @@
# read more here http://tautt.com/best-nginx-configuration-for-security/
# add_header Server "mystartup/1.0" always;
# config to don't allow the browser to render the page inside an frame or iframe
# and avoid clickjacking http://en.wikipedia.org/wiki/Clickjacking
# if you need to allow [i]frames, you can use SAMEORIGIN or even set an uri with ALLOW-FROM uri
# https://developer.mozilla.org/en-US/docs/HTTP/X-Frame-Options
add_header X-Frame-Options SAMEORIGIN;
# when serving user-supplied content, include a X-Content-Type-Options: nosniff header along with the Content-Type: header,
# to disable content-type sniffing on some browsers.
# https://www.owasp.org/index.php/List_of_useful_HTTP_headers
# currently suppoorted in IE > 8 http://blogs.msdn.com/b/ie/archive/2008/09/02/ie8-security-part-vi-beta-2-update.aspx
# http://msdn.microsoft.com/en-us/library/ie/gg622941(v=vs.85).aspx
# 'soon' on Firefox https://bugzilla.mozilla.org/show_bug.cgi?id=471020
add_header X-Content-Type-Options nosniff;
# This header enables the Cross-site scripting (XSS) filter built into most recent web browsers.
# It's usually enabled by default anyway, so the role of this header is to re-enable the filter for
# this particular website if it was disabled by the user.
# https://www.owasp.org/index.php/List_of_useful_HTTP_headers
add_header X-XSS-Protection "1; mode=block";
# with Content Security Policy (CSP) enabled(and a browser that supports it(http://caniuse.com/#feat=contentsecuritypolicy),
# you can tell the browser that it can only download content from the domains you explicitly allow
# http://www.html5rocks.com/en/tutorials/security/content-security-policy/
# https://www.owasp.org/index.php/Content_Security_Policy
# I need to change our application code so we can increase security by disabling 'unsafe-inline' 'unsafe-eval'
# directives for css and js(if you have inline css or js, you will need to keep it too).
# more: http://www.html5rocks.com/en/tutorials/security/content-security-policy/#inline-code-considered-harmful
# add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://ssl.google-analytics.com https://assets.zendesk.com https://connect.facebook.net; img-src 'self' https://ssl.google-analytics.com https://s-static.ak.facebook.com https://assets.zendesk.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com https://assets.zendesk.com; font-src 'self' https://themes.googleusercontent.com; frame-src https://assets.zendesk.com https://www.facebook.com https://s-static.ak.facebook.com https://tautt.zendesk.com; object-src 'none'";
# add_header Content-Security-Policy "default-src * data: 'unsafe-eval' 'unsafe-inline'" always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
server {
listen 80;
sendfile on;
default_type application/octet-stream;
# more_clear_headers Server;
# more_set_headers 'Server: Powered by Yondu';
# don't send the nginx version number in error pages and Server header
server_tokens off;
gzip on;
gzip_http_version 1.1;
gzip_disable "MSIE [1-6]\.";
gzip_min_length 256;
gzip_vary on;
gzip_proxied expired no-cache no-store private auth;
gzip_types
application/atom+xml
application/javascript
application/json
application/ld+json
application/manifest+json
application/rss+xml
application/vnd.geo+json
application/vnd.ms-fontobject
application/x-font-ttf
application/x-web-app-manifest+json
application/xhtml+xml
application/xml
font/opentype
image/bmp
image/svg+xml
image/x-icon
text/cache-manifest
text/css
text/plain
text/vcard
text/vnd.rim.location.xloc
text/vtt
text/x-component
text/x-cross-domain-policy;
# text/html is always compressed by gzip module
gzip_comp_level 9;
server_name *.example.org;
root /usr/share/nginx/html;
# . files
location ~ /\. {
deny all;
}
# cache.appcache, your document html and data
location ~* \.(?:manifest|appcache|html?|xml|json)$ {
expires -1;
# access_log logs/static.log; # I don't usually include a static log
}
# Media: images, icons, video, audio, HTC
location ~* \.(?:jpg|jpeg|gif|png|ico|cur|gz|svg|svgz|mp4|ogg|ogv|webm|htc)$ {
expires 1M;
access_log off;
}
# location ~* (service-worker\.js)$ {
# add_header Cache-Control no-store, no-cache;
# expires -1;
# proxy_no_cache 1;
# }
# CSS and Javascript
location ~* /static/.*\.(?:css|js)$ {
expires 1y;
access_log off;
}
# location /service-worker.js {
# expires -1;
# add_header Cache-Control no-store, no-cache;
# access_log off;
# }
location / {
try_files $uri $uri/ /index.html;
}
}

491
config-overrides.js Normal file
View File

@ -0,0 +1,491 @@
const { injectBabelPlugin } = require("react-app-rewired");
const rewireLess = require("react-app-rewire-less");
// const VariablesOutput = require('less-plugin-variables-output');
// const fs = require('fs');
// const path = require('path');
// const less = require('less');
module.exports = function override(config, env) {
config = injectBabelPlugin(['import', { libraryName: 'antd', style: true }], config); // change importing css to less
config = rewireLess.withLoaderOptions({
javascriptEnabled: true,
modifyVars: {
// "@primary-color": "#1DA57A" ,
'@primary-color' : '#1890ff',
'@info-color' : '#1890ff',
'@success-color ' : '#52c41a',
'@processing-color' : '#1890ff',
'@error-color' : '#f5222d',
'@highlight-color' : '#f5222d',
'@warning-color' : '#faad14',
'@normal-color' : '#d9d9d9',
// Color used by default to control hover and active backgrounds and for
// alert info backgrounds.
// @primary-1: color(~`colorPalette("@{primary-color}", 1)`); // replace tint(@primary-color, 90%)
// @primary-2: color(~`colorPalette("@{primary-color}", 2)`); // replace tint(@primary-color, 80%)
// @primary-3: color(~`colorPalette("@{primary-color}", 3)`); // unused
// @primary-4: color(~`colorPalette("@{primary-color}", 4)`); // unused
// @primary-5: color(~`colorPalette("@{primary-color}", 5)`); // color used to control the text color in many active and hover states, replace tint(@primary-color, 20%)
// @primary-6: @primary-color; // color used to control the text color of active buttons, don't use, use @primary-color
// @primary-7: color(~`colorPalette("@{primary-color}", 7)`); // replace shade(@primary-color, 5%)
// @primary-8: color(~`colorPalette("@{primary-color}", 8)`); // unused
// @primary-9: color(~`colorPalette("@{primary-color}", 9)`); // unused
// @primary-10: color(~`colorPalette("@{primary-color}", 10)`); // unused
// Base Scaffolding Variables
// ---
// Background color for `<body>`
// @body-background : #fff;
// Base background color for most components
// @component-background : #fff;
// @font-family-no-number : "Chinese Quote", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Helvetica Neue", Helvetica, Arial, sans-serif;
// @font-family : "Monospaced Number", @font-family-no-number;
// @code-family : Consolas, Menlo, Courier, monospace;
// @heading-color : fade(#000, 85%);
// @text-color : fade(#000, 65%);
// @text-color-secondary : fade(#000, 45%);
// @heading-color-dark : fade(#fff, 100%);
// @text-color-dark : fade(#fff, 85%);
// @text-color-secondary-dark: fade(#fff, 65%);
'@font-size-base' : '14px'
// @font-size-lg : @font-size-base + 2px;
// @font-size-sm : 12px;
// @line-height-base : 1.5;
// @border-radius-base : 4px;
// @border-radius-sm : 2px;
// vertical paddings
// @padding-lg : 24px; // containers
// @padding-md : 16px; // small containers and buttons
// @padding-sm : 12px; // Form controls and items
// @padding-xs : 8px; // small items
// vertical padding for all form controls
// @control-padding-horizontal: @padding-sm;
// @control-padding-horizontal-sm: @padding-xs;
// The background colors for active and hover states for things like
// list items or table cells.
// @item-active-bg : @primary-1;
// @item-hover-bg : @primary-1;
// ICONFONT
// @iconfont-css-prefix : anticon;
// @icon-url : "https://at.alicdn.com/t/font_148784_v4ggb6wrjmkotj4i";
// LINK
// @link-color : @primary-color;
// @link-hover-color : color(~`colorPalette("@{link-color}", 5)`);
// @link-active-color : color(~`colorPalette("@{link-color}", 7)`);
// @link-decoration : none;
// @link-hover-decoration : none;
// Animation
// @ease-out : cubic-bezier(0.215, 0.61, 0.355, 1);
// @ease-in : cubic-bezier(0.55, 0.055, 0.675, 0.19);
// @ease-in-out : cubic-bezier(0.645, 0.045, 0.355, 1);
// @ease-out-back : cubic-bezier(0.12, 0.4, 0.29, 1.46);
// @ease-in-back : cubic-bezier(0.71, -0.46, 0.88, 0.6);
// @ease-in-out-back : cubic-bezier(0.71, -0.46, 0.29, 1.46);
// @ease-out-circ : cubic-bezier(0.08, 0.82, 0.17, 1);
// @ease-in-circ : cubic-bezier(0.6, 0.04, 0.98, 0.34);
// @ease-in-out-circ : cubic-bezier(0.78, 0.14, 0.15, 0.86);
// @ease-out-quint : cubic-bezier(0.23, 1, 0.32, 1);
// @ease-in-quint : cubic-bezier(0.755, 0.05, 0.855, 0.06);
// @ease-in-out-quint : cubic-bezier(0.86, 0, 0.07, 1);
// Border color
// @border-color-base : hsv(0, 0, 85%); // base border outline a component
// @border-color-split : hsv(0, 0, 91%); // split border inside a component
// @border-width-base : 1px; // width of the border for a component
// @border-style-base : solid; // style of a components border
// Outline
// @outline-blur-size : 0;
// @outline-width : 2px;
// @outline-color : @primary-color;
// @background-color-light : hsv(0, 0, 98%); // background of header and selected item
// @background-color-base : hsv(0, 0, 96%); // Default grey background color
// Disabled states
// @disabled-color : fade(#000, 25%);
// @disabled-bg : @background-color-base;
// @disabled-color-dark : fade(#fff, 35%);
// Shadow
// @shadow-color : rgba(0, 0, 0, .15);
// @box-shadow-base : @shadow-1-down;
// @shadow-1-up : 0 2px 8px @shadow-color;
// @shadow-1-down : 0 2px 8px @shadow-color;
// @shadow-1-left : -2px 0 8px @shadow-color;
// @shadow-1-right : 2px 0 8px @shadow-color;
// @shadow-2 : 0 4px 12px @shadow-color;
// Buttons
// @btn-font-weight : 400;
// @btn-border-radius-base : @border-radius-base;
// @btn-border-radius-sm : @border-radius-base;
// @btn-primary-color : #fff;
// @btn-primary-bg : @primary-color;
// @btn-default-color : @text-color;
// @btn-default-bg : #fff;
// @btn-default-border : @border-color-base;
// @btn-danger-color : @error-color;
// @btn-danger-bg : @background-color-base;
// @btn-danger-border : @border-color-base;
// @btn-disable-color : @disabled-color;
// @btn-disable-bg : @disabled-bg;
// @btn-disable-border : @border-color-base;
// @btn-padding-base : 0 @padding-md - 1px;
// @btn-font-size-lg : @font-size-lg;
// @btn-font-size-sm : @font-size-base;
// @btn-padding-lg : @btn-padding-base;
// @btn-padding-sm : 0 @padding-xs - 1px;
// @btn-height-base : 32px;
// @btn-height-lg : 40px;
// @btn-height-sm : 24px;
// @btn-circle-size : @btn-height-base;
// @btn-circle-size-lg : @btn-height-lg;
// @btn-circle-size-sm : @btn-height-sm;
// @btn-group-border : @primary-5;
// Checkbox
// @checkbox-size : 16px;
// @checkbox-color : @primary-color;
// Radio
// @radio-size : 16px;
// @radio-dot-color : @primary-color;
// Radio buttons
// @radio-button-bg : @btn-default-bg;
// @radio-button-color : @btn-default-color;
// @radio-button-hover-color : @primary-5;
// @radio-button-active-color : @primary-7;
// Media queries breakpoints
// Extra small screen / phone
// @screen-xs : 480px;
// @screen-xs-min : @screen-xs;
// Small screen / tablet
// @screen-sm : 576px;
// @screen-sm-min : @screen-sm;
// Medium screen / desktop
// @screen-md : 768px;
// @screen-md-min : @screen-md;
// Large screen / wide desktop
// @screen-lg : 992px;
// @screen-lg-min : @screen-lg;
// Extra large screen / full hd
// @screen-xl : 1200px;
// @screen-xl-min : @screen-xl;
// Extra extra large screen / large descktop
// @screen-xxl : 1600px;
// @screen-xxl-min : @screen-xxl;
// provide a maximum
// @screen-xs-max : (@screen-sm-min - 1px);
// @screen-sm-max : (@screen-md-min - 1px);
// @screen-md-max : (@screen-lg-min - 1px);
// @screen-lg-max : (@screen-xl-min - 1px);
// @screen-xl-max : (@screen-xxl-min - 1px);
// Grid system
// @grid-columns : 24;
// @grid-gutter-width : 0;
// Layout
// @layout-body-background : #f0f2f5;
// @layout-header-background : #001529;
// @layout-footer-background : @layout-body-background;
// @layout-header-height : 64px;
// @layout-header-padding : 0 50px;
// @layout-footer-padding : 24px 50px;
// @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;
// Layout light theme
// @layout-sider-background-light : #fff;
// @layout-trigger-background-light: #fff;
// @layout-trigger-color-light : @text-color;
// z-index list
// @zindex-affix : 10;
// @zindex-back-top : 10;
// @zindex-modal-mask : 1000;
// @zindex-modal : 1000;
// @zindex-notification : 1010;
// @zindex-message : 1010;
// @zindex-popover : 1030;
// @zindex-picker : 1050;
// @zindex-dropdown : 1050;
// @zindex-tooltip : 1060;
// Animation
// @animation-duration-slow: .3s; // Modal
// @animation-duration-base: .2s;
// @animation-duration-fast: .1s; // Tooltip
// Form
// ---
// @label-required-color : @highlight-color;
// @label-color : @heading-color;
// @form-item-margin-bottom : 24px;
// @form-item-trailing-colon : true;
// @form-vertical-label-padding : 0 0 8px;
// @form-vertical-label-margin : 0;
// Input
// ---
// @input-height-base : 32px;
// @input-height-lg : 40px;
// @input-height-sm : 24px;
// @input-padding-horizontal : @control-padding-horizontal - 1px;
// @input-padding-horizontal-base: @input-padding-horizontal;
// @input-padding-horizontal-sm : @control-padding-horizontal-sm - 1px;
// @input-padding-horizontal-lg : @input-padding-horizontal;
// @input-padding-vertical-base : 4px;
// @input-padding-vertical-sm : 1px;
// @input-padding-vertical-lg : 6px;
// @input-placeholder-color : hsv(0, 0, 75%);
// @input-color : @text-color;
// @input-border-color : @border-color-base;
// @input-bg : #fff;
// @input-addon-bg : @background-color-light;
// @input-hover-border-color : @primary-color;
// @input-disabled-bg : @disabled-bg;
// Tooltip
// ---
//* Tooltip max width
// @tooltip-max-width: 250px;
//** Tooltip text color
// @tooltip-color: #fff;
//** Tooltip background color
// @tooltip-bg: rgba(0, 0, 0, .75);
//** Tooltip arrow width
// @tooltip-arrow-width: 5px;
//** Tooltip distance with trigger
// @tooltip-distance: @tooltip-arrow-width - 1px + 4px;
//** Tooltip arrow color
// @tooltip-arrow-color: @tooltip-bg;
// Popover
// ---
//** Popover body background color
// @popover-bg: #fff;
//** Popover text color
// @popover-color: @text-color;
//** Popover maximum width
// @popover-min-width: 177px;
//** Popover arrow width
// @popover-arrow-width: 6px;
//** Popover arrow color
// @popover-arrow-color: @popover-bg;
//** Popover outer arrow width
//** Popover outer arrow color
// @popover-arrow-outer-color: @popover-bg;
//** Popover distance with trigger
// @popover-distance: @popover-arrow-width + 4px;
// Modal
// --
// @modal-mask-bg: rgba(0, 0, 0, 0.65);
// Progress
// --
// @progress-default-color: @processing-color;
// @progress-remaining-color: @background-color-base;
// Menu
// ---
// @menu-inline-toplevel-item-height: 40px;
// @menu-item-height: 40px;
// @menu-collapsed-width: 80px;
// @menu-bg: @component-background;
// @menu-item-color: @text-color;
// @menu-highlight-color: @primary-color;
// @menu-item-active-bg: @item-active-bg;
// @menu-item-group-title-color: @text-color-secondary;
// dark theme
// @menu-dark-color: @text-color-secondary-dark;
// @menu-dark-bg: @layout-header-background;
// @menu-dark-arrow-color: #fff;
// @menu-dark-submenu-bg: #000c17;
// @menu-dark-highlight-color: #fff;
// @menu-dark-item-active-bg: @primary-color;
// Spin
// ---
// @spin-dot-size-sm: 14px;
// @spin-dot-size: 20px;
// @spin-dot-size-lg: 32px;
// Table
// --
// @table-header-bg: @background-color-light;
// @table-header-sort-bg: @background-color-base;
// @table-row-hover-bg: @primary-1;
// @table-selected-row-bg: #fafafa;
// @table-expanded-row-bg: #fbfbfb;
// @table-padding-vertical: 16px;
// @table-padding-horizontal: 16px;
// Tag
// --
// @tag-default-bg: @background-color-light;
// @tag-default-color: @text-color;
// @tag-font-size: @font-size-sm;
// TimePicker
// ---
// @time-picker-panel-column-width: 56px;
// @time-picker-panel-width: @time-picker-panel-column-width * 3;
// @time-picker-selected-bg: @background-color-base;
// Carousel
// ---
// @carousel-dot-width: 16px;
// @carousel-dot-height: 3px;
// @carousel-dot-active-width: 24px;
// Badge
// ---
// @badge-height: 20px;
// @badge-dot-size: 6px;
// @badge-font-size: @font-size-sm;
// @badge-font-weight: normal;
// @badge-status-size: 6px;
// Rate
// ---
// @rate-star-color: @yellow-6;
// @rate-star-bg: @border-color-split;
// Card
// ---
// @card-head-color: @heading-color;
// @card-head-background: @component-background;
// @card-head-padding: 16px;
// @card-inner-head-padding: 12px;
// @card-padding-base: 24px;
// @card-padding-wider: 32px;
// @card-actions-background: @background-color-light;
// @card-shadow: 0 2px 8px rgba(0, 0, 0, .09);
// Tabs
// ---
// '@tabs-card-head-background': '@background-color-light';
// '@tabs-card-height': '40px';
// '@tabs-card-active-color': '@primary-color';
// '@tabs-title-font-size': '@font-size-base';
// '@tabs-title-font-size-lg': '@font-size-lg';
// '@tabs-title-font-size-sm': '@font-size-base';
// '@tabs-ink-bar-color': '@primary-color';
// '@tabs-bar-margin': '0 0 16px 0';
// '@tabs-horizontal-margin': '0 32px 0 0';
// '@tabs-horizontal-padding': '12px 16px';
// '@tabs-vertical-padding': '8px 24px';
// '@tabs-vertical-margin': '0 0 16px 0';
// '@tabs-scrolling-size': '32px';
// '@tabs-highlight-color': '@primary-color';
// '@tabs-hover-color': '@primary-5';
// '@tabs-active-color': '@primary-7';
// BackTop
// ---
// '@back-top-color': '#fff';
// '@back-top-bg': '@text-color-secondary';
// '@back-top-hover-bg': '@text-color';
// Avatar
// ---
// '@avatar-size-base': '32px';
// '@avatar-size-lg': '40px';
// '@avatar-size-sm': '24px';
// '@avatar-font-size-base': '18px';
// '@avatar-font-size-lg': '24px';
// '@avatar-font-size-sm': '14px';
// '@avatar-bg': '#ccc';
// '@avatar-color': '#fff';
// '@avatar-border-radius': '@border-radius-base;'
// Switch
// ---
// '@switch-height': '22px';
// '@switch-sm-height': '16px';
// '@switch-sm-checked-margin-left': '-(@switch-sm-height - 3px)';
// '@switch-disabled-opacity': '0.4';
// '@switch-color': '@primary-color';
// Pagination
// ---
// '@pagination-item-size': '32px';
// '@pagination-item-size-sm': '24px';
// '@pagination-font-family': 'Arial';
// '@pagination-font-weight-active': '500';
// Breadcrumb
// ---
// '@breadcrumb-base-color': '@text-color-secondary';
// '@breadcrumb-last-item-color': '@text-color';
// '@breadcrumb-font-size': '@font-size-base';
// '@breadcrumb-icon-font-size': '@font-size-sm';
// '@breadcrumb-link-color': '@text-color-secondary';
// '@breadcrumb-link-color-hover': '@primary-5';
// '@breadcrumb-separator-color': '@text-color-secondary';
// '@breadcrumb-separator-margin': '0 @padding-xs';
// Slider
// ---
// '@slider-margin': '14px 6px 10px';
// '@slider-rail-background-color': '@background-color-base';
// '@slider-rail-background-color-hover': '#e1e1e1';
// '@slider-track-background-color': '@primary-3';
// '@slider-track-background-color-hover': '@primary-4';
// '@slider-handle-color': '@primary-3';
// '@slider-handle-color-hover': '@primary-4';
// '@slider-handle-color-focus': 'tint(@primary-color, 20%)';
// '@slider-handle-color-focus-shadow': 'tint(@primary-color, 50%)';
// '@slider-handle-color-tooltip-open': '@primary-color';
// '@slider-dot-border-color': '@border-color-split';
// '@slider-dot-border-color-active': 'tint(@primary-color, 50%)';
// '@slider-disabled-color': '@disabled-color';
// '@slider-disabled-background-color': '@component-background';
// Collapse
// ---
// '@collapse-header-padding': '12px 0 12px 40px';
// '@collapse-header-bg': '@background-color-light';
// '@collapse-content-padding': '@padding-md';
// '@collapse-content-bg': '@component-background';
},
})(config, env);
return config;
};

25
docker-compose-prod.yml Normal file
View File

@ -0,0 +1,25 @@
version: '3.5'
services:
prod-build:
container_name: prod-build
env_file:
- .env
environment:
- NODE_ENV=production
build:
context: .
dockerfile: Dockerfile
args:
# - REACT_APP_DOMAIN=http://mobilecorelb-1807532632.ap-southeast-1.elb.amazonaws.com/
# - REACT_APP_DOMAIN=https://mobilelink.unioil.com
- REACT_APP_DOMAIN=https://mobileapi.unioilapps.com
- REACT_APP_PATH=api/cms
- REACT_APP_IMG_PATH=unioilQA/
restart: always
ports:
- 8089:80
volumes:
- '.:/usr/src/app'
- '/usr/src/app/node_modules'

24
docker-compose-public.yml Normal file
View File

@ -0,0 +1,24 @@
version: '3.5'
services:
public-build:
container_name: public-build
env_file:
- .env
environment:
- NODE_ENV=production
build:
context: .
dockerfile: Dockerfile
args:
- REACT_APP_PUBLIC=true
# - PUBLIC_URL=/paypal
- REACT_APP_DOMAIN=https://mobileapid.unioil.com
- REACT_APP_PATH=api/cms
- REACT_APP_IMG_PATH=unioilQA
- GENERATE_SOURCEMAP=false
restart: always
ports:
- 8095:80
volumes:
- '.:/usr/src/app'
- '/usr/src/app/node_modules'

80
docker-compose.yml Normal file
View File

@ -0,0 +1,80 @@
version: '3.5'
services:
test-build:
container_name: test-build
env_file:
- .env
environment:
- NODE_ENV=production
build:
context: .
dockerfile: Dockerfile
args:
- REACT_APP_DOMAIN=http://192.168.0.68
- REACT_APP_PATH=unioilQA/public/index.php/api/cms
- REACT_APP_IMG_PATH=unioilQA/
restart: always
ports:
- 8089:80
volumes:
- '.:/usr/src/app'
- '/usr/src/app/node_modules'
dev-build:
container_name: dev-build
env_file:
- .env
environment:
- NODE_ENV=production
build:
context: .
dockerfile: Dockerfile
args:
- REACT_APP_DOMAIN=http://192.168.0.68
- REACT_APP_PATH=UniOilLoyaltyApp-BackEnd/public/index.php/api/cms
- REACT_APP_IMG_PATH=UniOilLoyaltyApp-BackEnd
restart: always
ports:
- 8081:80
volumes:
- '.:/usr/src/app'
- '/usr/src/app/node_modules'
public-dev-build:
container_name: public-dev-build
env_file:
- .env
environment:
- NODE_ENV=production
build:
context: .
dockerfile: Dockerfile
args:
- REACT_APP_PUBLIC=true
- REACT_APP_DOMAIN=http://192.168.0.68
- REACT_APP_PATH=UniOilLoyaltyApp-BackEnd/public/index.php/api/cms
- REACT_APP_IMG_PATH=UniOilLoyaltyApp-BackEnd/
restart: always
ports:
- 8093:80
volumes:
- '.:/usr/src/app'
- '/usr/src/app/node_modules'
public-test-build:
container_name: public-test-build
env_file:
- .env
environment:
- NODE_ENV=production
build:
context: .
dockerfile: Dockerfile
args:
- REACT_APP_PUBLIC=true
- REACT_APP_DOMAIN=http://192.168.0.68
- REACT_APP_PATH=unioilQA/public/index.php/api/cms
- REACT_APP_IMG_PATH=unioilQA
restart: always
ports:
- 8090:80
volumes:
- '.:/usr/src/app'
- '/usr/src/app/node_modules'

12
jsconfig.json Normal file
View File

@ -0,0 +1,12 @@
{
"compilerOptions": {
"target": "es2015",
"baseUrl": "src",
},
"exclude": [
"node_modules",
"build",
"public",
"__test__"
]
}

278
json-server/db.json Normal file
View File

@ -0,0 +1,278 @@
{
"posts": [
{
"id": 1441,
"title": "11-json-server",
"author": "11-server-deauthor"
},
{
"title": "14-Title",
"author": "14-Title Data",
"id": 4414
},
{
"title": "tes wafe",
"author": "weataewtaew",
"id": 1446
},
{
"title": "1-json-server-test",
"author": "werewrwe",
"id": 1447
},
{
"id": 447,
"title": "7-json-server",
"author": "7-server-deauthor"
},
{
"id": 449,
"title": "9-json-server",
"author": "9-tserver-deauthor"
},
{
"id": 14440,
"title": "10-json-server",
"author": "10-server-deauthor"
},
{
"id": 1441,
"title": "11-json-server",
"author": "11-server-deauthor"
},
{
"id": 12443,
"title": "12-json-server",
"author": "12-server-deauthor"
},
{
"title": "tes",
"author": "ewrwer",
"id": 1334444
},
{
"title": "14-Title",
"author": "14-Title Data",
"id": 143444
},
{
"title": "fytyiftyfy",
"author": "ftftcftc",
"id": 15344
},
{
"title": "tes wafe",
"author": "weataewtaew",
"id": 163444
},
{
"title": "1-json-server-test",
"author": "werewrwe",
"id": 17344
},
{
"id": 744,
"title": "7-json-server",
"author": "7-server-deauthor"
},
{
"id": 944,
"title": "9-json-server",
"author": "9-tserver-deauthor"
},
{
"id": 104,
"title": "10-json-server",
"author": "10-server-deauthor"
},
{
"id": 141,
"title": "11-json-server",
"author": "11-server-deauthor"
},
{
"id": 1244,
"title": "12-json-server",
"author": "12-server-deauthor"
},
{
"title": "tes",
"author": "ewrwer",
"id": 1344
},
{
"title": "14-Title",
"author": "14-Title Data",
"id": 1444
},
{
"title": "fytyiftyfy",
"author": "ftftcftc",
"id": 1544
},
{
"title": "tes wafe",
"author": "weataewtaew",
"id": 164
},
{
"title": "1-json-server-test",
"author": "werewrwe",
"id": 1744
},
{
"id": 744,
"title": "7-json-server",
"author": "7-server-deauthor"
},
{
"id": 944,
"title": "9-json-server",
"author": "9-tserver-deauthor"
},
{
"id": 1044,
"title": "10-json-server",
"author": "10-server-deauthor"
},
{
"id": 1144,
"title": "11-json-server",
"author": "11-server-deauthor"
},
{
"id": 12344,
"title": "12-json-server",
"author": "12-server-deauthor"
},
{
"title": "tes",
"author": "ewrwer",
"id": 13344
},
{
"title": "14-Title",
"author": "14-Title Data",
"id": 14344
},
{
"title": "fytyiftyfy",
"author": "ftftcftc",
"id": 15344
},
{
"title": "tes wafe",
"author": "weataewtaew",
"id": 16344
},
{
"title": "1-json-server-test",
"author": "werewrwe",
"id": 17344
},
{
"id": 744,
"title": "7-json-server",
"author": "7-server-deauthor"
},
{
"id": 944,
"title": "9-json-server",
"author": "9-tserver-deauthor"
},
{
"id": 1044,
"title": "10-json-server",
"author": "10-server-deauthor"
},
{
"id": 114444,
"title": "11-json-server",
"author": "11-server-deauthor"
},
{
"id": 1244,
"title": "12-json-server",
"author": "12-server-deauthor"
},
{
"title": "tes",
"author": "ewrwer",
"id": 1344
},
{
"title": "14-Title",
"author": "14-Title Data",
"id": 1444
},
{
"title": "fytyiftyfy",
"author": "ftftcftc",
"id": 1544
},
{
"title": "tes wafe",
"author": "weataewtaew",
"id": 1644
},
{
"title": "1-json-server-test",
"author": "werewrwe",
"id": 174
},
{
"id": 744,
"title": "7-json-server",
"author": "7-server-deauthor"
},
{
"id": 944,
"title": "9-json-server",
"author": "9-tserver-deauthor"
},
{
"id": 104,
"title": "10-json-server",
"author": "10-server-deauthor"
},
{
"id": 114,
"title": "11-json-server",
"author": "11-server-deauthor"
},
{
"id": 1234,
"title": "12-json-server",
"author": "12-server-deauthor"
},
{
"title": "tes",
"author": "ewrwer",
"id": 1334
},
{
"title": "14-Title",
"author": "14-Title Data",
"id": 1434
},
{
"title": "fytyiftyfy",
"author": "ftftcftc",
"id": 15344
},
{
"title": "tes wafe",
"author": "weataewtaew",
"id": 1634
},
{
"title": "1-json-server-test",
"author": "werewrwe",
"id": 1734
}
],
"comments": [],
"profile": {
"name": "typicode"
}
}

65
package.json Normal file
View File

@ -0,0 +1,65 @@
{
"name": "my-app",
"version": "0.1.0",
"private": true,
"dependencies": {
"@ant-design/icons": "4.0.0",
"@react-google-maps/api": "^1.9.7",
"antd": "^3.9.1",
"antd-input-password": "^0.3.0",
"axios": "^0.18.0",
"crypto-js": "^3.1.9-1",
"dsbridge": "^3.1.3",
"filesize": "^3.6.1",
"formik": "^1.2.0",
"history": "^4.7.2",
"js-file-download": "^0.4.4",
"libsodium-wrappers": "^0.7.3",
"moment": "^2.27.0",
"query-string": "^5.1.1",
"react": "^16.7.0",
"react-copy-to-clipboard": "^5.0.1",
"react-csv": "^2.0.3",
"react-csv-reader": "^3.1.0",
"react-device-detect": "^1.6.1",
"react-dom": "^16.5.0",
"react-geocode": "^0.2.1",
"react-google-autocomplete": "^1.2.6",
"react-google-map": "^3.1.1",
"react-google-maps": "^9.4.5",
"react-helmet": "^5.2.0",
"react-idle-timer": "^4.0.9",
"react-input-mask": "^2.0.4",
"react-loadable": "^5.5.0",
"react-papaparse": "^3.7.1",
"react-redux": "^5.0.7",
"react-router-dom": "^4.2.2",
"react-router-redux": "^4.0.8",
"react-scripts": "1.1.5",
"redux": "^4.0.0",
"redux-logger": "^3.0.6",
"redux-saga": "^0.16.0",
"styled-components": "^3.4.5",
"universal-cookie": "3.0.4",
"yup": "^0.26.3"
},
"scripts": {
"start": "react-app-rewired start",
"build": "react-app-rewired build",
"test": "react-app-rewired test --env=jsdom",
"eject": "react-app-rewired eject",
"dev": "docker-compose up -d --build",
"dev:stop": "docker-compose down",
"prod": "docker-compose -f docker-compose-prod.yml up -d --build",
"prod:stop": "docker-compose -f docker-compose-prod.yml down",
"flow": "flow"
},
"devDependencies": {
"babel-plugin-import": "^1.7.0",
"debug": "^3.1.0",
"flow-bin": "0.80.0",
"less-plugin-variables-output": "^1.2.0",
"react-app-rewire-less": "^2.1.3",
"react-app-rewired": "^1.6.2"
}
}

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

BIN
public/favicon_unioil.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

69
public/index.html Normal file
View File

@ -0,0 +1,69 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="theme-color" content="#000000">
<!--
manifest.json provides metadata used when your web app is added to the
homescreen on Android. See https://developers.google.com/web/fundamentals/engage-and-retain/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json">
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon_unioil.ico">
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>React App</title>
</head>
<body>
<noscript>
You need to enable JavaScript to run this app.
</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
<script type="text/javascript">
// For Adroid
//window.AndroidTopUpSuccess = AndroidTopUpFailed;
window.AndroidTopUpFailed = AndroidTopUpFailed;
// For iOS
window.iOStopUpSuccess = iOStopUpSuccess;
window.iOStopUpFailed = iOStopUpFailed;
function AndroidTopUpFailed() {
// For Adroid
AndroidInterface.returnTopUpPage();
}
function iOStopUpSuccess() {
//alert('top up success ' );
// For iOS
window.webkit.messageHandlers.backToHomePage.postMessage({'ButtonId':'clickMeButton'})
}
function iOStopUpFailed() {
//alert('back to top up' );
// For iOS
window.webkit.messageHandlers.backToTopUp.postMessage({'ButtonId':'clickMeButton'})
}
</script>
</body>
</html>

15
public/manifest.json Normal file
View File

@ -0,0 +1,15 @@
{
"short_name": "React App",
"name": "Create React App Sample",
"icons": [
{
"src": "favicon_unioil.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
}
],
"start_url": "./index.html",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}

215
src/App.js Normal file
View File

@ -0,0 +1,215 @@
import React, { Component, Fragment } from 'react';
import { BrowserRouter as Router, Route, Redirect, Switch, withRouter } from 'react-router-dom';
import Loadable from 'react-loadable';
import { connect } from 'react-redux';
import LoginLayoutRoute from './components/Login/Routes';
import DashboardRoute from './components/Dashboard/Routes';
import Loading from './components/Loading';
import { getCookie } from './utils/cookie';
import { customAction } from './actions';
import { API_UNI_OIL, API_POST } from 'utils/Api';
const AsyncLogin = Loadable({
loader: () => import('./containers/public/Login'),
loading: Loading,
});
const AsyncRegistration = Loadable({
loader: () => import('./containers/public/Registration'),
loading: Loading,
});
const AsyncChangePassword = Loadable({
loader: () => import('./containers/public/ChangePassword'),
loading: Loading,
});
const AsyncPublicTopSuccessPage = Loadable({
loader: () => import('./containers/public/PublicTopSuccessPage'),
loading: () => {
return null;
},
});
const AsyncPublicTopErrorPage = Loadable({
loader: () => import('./containers/public/PublicTopErrorPage'),
loading: () => {
return null;
},
});
const AsyncMyProfile = Loadable({
loader: () => import('./containers/private/MyProfile'),
loading: Loading,
});
const AsyncNotification = Loadable({
loader: () => import('./containers/private/Notifications'),
loading: Loading,
});
const AsyncUserManagement = Loadable({
loader: () => import('./containers/private/UserManagement'),
loading: Loading,
});
const AsyncMemberManagement = Loadable({
loader: () => import('./containers/private/MemberManagement'),
loading: Loading,
});
const AsyncPhotoSlider = Loadable({
loader: () => import('./containers/private/HomePage/PhotoSlider'),
loading: Loading,
});
const AsyncPromotions = Loadable({
loader: () => import('./containers/private/Promotions'),
loading: Loading,
});
const AsyncTopUp = Loadable({
loader: () => import('./containers/private/TopUp'),
loading: Loading,
});
const AsyncCardTypes = Loadable({
loader: () => import('./containers/private/AboutUs'),
loading: Loading,
});
const AsyncReports = Loadable({
loader: () => import('./containers/private/Reports'),
loading: Loading,
});
const AsyncSystemPreferences = Loadable({
loader: () => import('./containers/private/SystemPreferences'),
loading: Loading,
});
const AsyncPage404 = Loadable({
loader: () => import('./components/PageError/404'),
loading: Loading,
});
const AsyncLocator = Loadable({
loader: () => import('./containers/private/StationLocator/Location'),
loading: Loading,
});
const AsyncBranches = Loadable({
loader: () => import('./containers/private/StationLocator/Branches'),
loading: Loading,
})
const AsyncFuels = Loadable({
loader: () => import('./containers/private/StationLocator/Fuels'),
loading: Loading,
})
const CaptureRouteNotFound = withRouter(({ children, location }) => {
return location && location.state && location.state.pageNotFound ? <AsyncPage404 /> : children;
});
const publicRoutes = [
'/',
'/login',
'/registration',
'/forgot-password',
'/change-password',
'/topup-success-page',
'/topup-error-page',
];
class App extends Component {
state = {
accessAuth: false,
mounting: true,
};
componentDidMount = async () => {
if (getCookie('TOKEN')) {
let { history, customAction } = this.props;
let { replace, location } = history;
API_UNI_OIL.defaults.headers.common['Authorization'] = `Bearer ${getCookie('TOKEN').token}`;
//customAction({type: 'LOGIN_SUCCESS' });
try {
let response = await API_POST(`adminProfile`);
response.data.data['userInfo'] = { ...response.data.data };
customAction({
type: 'LOGIN_SUCCESS',
payload: { ...response.data.data },
});
} catch ({ response: error }) {
//notification.error({ message: "Error", description: "Something went wrong loading user data." , duration: 20, });
}
if (publicRoutes.includes(location.pathname)) replace('/user-management');
}
this.setState({ mounting: false });
};
render() {
console.log('====================================');
console.log(process.env.REACT_APP_API, process.env.REACT_APP_IMG_URL, process.env.REACT_APP_PUBLIC, 'API LIST!!!');
console.log('====================================');
if (this.state.mounting) return null;
if (process.env.REACT_APP_PUBLIC === 'false') {
return (
<Router>
<Switch>
<Redirect exact from='/' to='/login' />
<LoginLayoutRoute exact path='/login' component={AsyncLogin} />
<LoginLayoutRoute exact path='/registration' component={AsyncRegistration} />
<LoginLayoutRoute exact path='/change-password' component={AsyncChangePassword} />
{/* PRIVATE ROUTES */}
<DashboardRoute path='/user-management' component={AsyncUserManagement} />
<DashboardRoute path='/notifications' component={AsyncNotification} />
<DashboardRoute path='/member-management' component={AsyncMemberManagement} />
<DashboardRoute path='/home-page' component={AsyncPhotoSlider} />
<DashboardRoute path='/promotions' component={AsyncPromotions} />
<DashboardRoute path='/top-up' component={AsyncTopUp} />
<DashboardRoute path='/about-us' component={AsyncCardTypes} />
<DashboardRoute path='/reports' component={AsyncReports} />
<DashboardRoute path='/stations' component={AsyncLocator}/>
<DashboardRoute path='/branches' component={AsyncBranches}/>
<DashboardRoute path='/fuels' component={AsyncFuels} />
<DashboardRoute path='/system-parameters' component={AsyncSystemPreferences} />
<DashboardRoute path='/my-profile' component={AsyncMyProfile} />
<Route exact path='/topup-success-page' component={AsyncPublicTopSuccessPage} />
<Route exact path='/topup-error-page' component={AsyncPublicTopErrorPage} />
<Route exact path='/404' component={AsyncPage404} />
<DashboardRoute path='*' component={AsyncPage404} />
</Switch>
</Router>
);
} else {
return (
<Router>
<Switch>
<Redirect exact from='/' to='/topup-success-page' />
<Redirect exact from='/login' to='/topup-success-page' />
<Route exact path='/topup-success-page' component={AsyncPublicTopSuccessPage} />
<Route exact path='/topup-error-page' component={AsyncPublicTopErrorPage} />
<Route exact path='/404' component={AsyncPage404} />
<DashboardRoute path='*' component={AsyncPage404} />
</Switch>
</Router>
);
}
}
}
App = connect(
(state) => ({
isAuthenticated: state.auth.isAuthenticated,
}),
{ customAction }
)(App);
export default withRouter(App);

9
src/App.test.js Normal file
View File

@ -0,0 +1,9 @@
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
it('renders without crashing', () => {
const div = document.createElement('div');
ReactDOM.render(<App />, div);
ReactDOM.unmountComponentAtNode(div);
});

6
src/actions/index.js Normal file
View File

@ -0,0 +1,6 @@
export function customAction({type, payload}) {
return {
type: type,
payload
};
}

BIN
src/assets/img/bg_cms.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 224 KiB

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 57.834 57.955"><defs><style>.a{fill:#f4825c;stroke:#707070;}.b{fill:#e74610;}.c{stroke:none;}.d{fill:none;}</style></defs><g transform="translate(-0.498 0.199)"><g transform="translate(0 0)"><g class="a" transform="translate(1.009 0.934)"><circle class="c" cx="27.989" cy="27.989" r="27.989"/><circle class="d" cx="27.989" cy="27.989" r="27.489"/></g><g transform="translate(-26.502 -27.6)"><g transform="translate(27 27.4)"><path class="b" d="M55.917,85.355A28.978,28.978,0,1,1,84.834,56.317,29.012,29.012,0,0,1,55.917,85.355Zm0-54.31A25.272,25.272,0,1,0,81.189,56.317,25.253,25.253,0,0,0,55.917,31.045Z" transform="translate(-27 -27.4)"/><g transform="translate(18.529 18.65)"><path class="b" d="M59.638,63.344a1.827,1.827,0,0,1-1.337-.486l-8.5-8.5a1.762,1.762,0,0,1,0-2.551l8.5-8.5a1.8,1.8,0,0,1,2.551,2.551l-7.168,7.168,7.168,7.169a1.762,1.762,0,0,1,0,2.551A1.234,1.234,0,0,1,59.638,63.344Z" transform="translate(-40.745 -42.75)"/><path class="b" d="M44.133,63.323a1.827,1.827,0,0,1-1.336-.486,1.762,1.762,0,0,1,0-2.551l7.169-7.169L42.8,45.948A1.8,1.8,0,0,1,45.348,43.4l8.5,8.5a1.762,1.762,0,0,1,0,2.551l-8.5,8.5A1.814,1.814,0,0,1,44.133,63.323Z" transform="translate(-42.25 -42.729)"/></g></g></g></g></g></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 58.184 58.184"><defs><style>.a{fill:#56b68b;stroke:#707070;}.b{fill:#018952;fill-rule:evenodd;}.c{stroke:none;}.d{fill:none;}</style></defs><g transform="translate(-138.502 -248.613)"><g transform="translate(138.502 248.613)"><g transform="translate(0 0)"><g class="a" transform="translate(1.009 0.934)"><circle class="c" cx="27.989" cy="27.989" r="27.989"/><circle class="d" cx="27.989" cy="27.989" r="27.489"/></g><path class="b" d="M47.664,22.747l2.3,2.3L31.8,43.209l-2.3,2.3-2.3-2.3-8.978-8.978,2.3-2.3L29.5,40.913ZM34.092,5A29.092,29.092,0,1,1,5,34.092,29.079,29.079,0,0,1,34.092,5Zm0,3.967A25.16,25.16,0,1,1,8.967,34.092,25.173,25.173,0,0,1,34.092,8.967Z" transform="translate(-5 -5)"/></g></g></g></svg>

After

Width:  |  Height:  |  Size: 763 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

View File

@ -0,0 +1,683 @@
import React from "react";
import {
Table,
Input,
Icon,
Divider,
Tooltip,
Popconfirm,
message,
Button,
Pagination,
Row,
Col
} from "antd";
//import { callApi } from "utils/Api";
import { Link, withRouter } from "react-router-dom";
import querystring from "querystring";
import { encrypt, decrypt } from 'utils/encrypto'
// const querystring = require("querystring");
const { Column, ColumnGroup } = Table;
const Search = Input.Search;
class CustomTable extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
data: [],
loading: false,
filteredInfo: null,
sortedInfo: null,
pageName: '_page',
currentPage: 1,
sizeName: '_limit',
pageSize: 10,
totalData: 100,
searchValue: null,
headerColumns: props.columns.map(column => {
if (column.sorter) {
column.sorter = (a, b) => a[column.dataIndex] - b[column.dataIndex];
column.sortOrder = null;
}
return column;
})
};
}
componentDidMount() {
this.handleInitialLoad()
}
componentDidUpdate(prevProps, prevState, snapshot) {
const { currentPage, pageSize, pageName } = this.state;
if (prevProps.location.search !== this.props.location.search) {
if (this.props.location.search === "") {
this.handleclearAll();
}
}
if (prevState.sortedInfo !== this.state.sortedInfo) {
//Sort Columns Function
this.setState({
headerColumns: this.state.headerColumns.map(column => {
if (
this.state.sortedInfo &&
this.state.sortedInfo.columnKey === column.dataIndex
) {
column.sortOrder = this.state.sortedInfo.order;
}
return column;
})
});
}
}
handleInitialLoad = () => {
const { search } = this.props.location;
const { currentPage, pageSize, pageName, sizeName } = this.state;
if (search) {
let parsed = querystring.parse(search.substring(1));
console.log('====================================');
console.log(parsed,pageName, sizeName,parsed[pageName], parsed[sizeName], "&&&&&&SEARCH!!");
console.log('====================================');
if (parsed) {
if (parsed[pageName] && parsed[sizeName] && parsed.q && parsed._sort && parsed._order ) {
this.handleUpdateData({
page: parseInt(parsed[pageName]),
size: parseInt(parsed[sizeName]),
search: parsed.q
});
} else if (parsed[pageName] && parsed[sizeName] && parsed.q) {
alert("Search")
this.handleUpdateData({
page: parseInt(parsed[pageName]),
size: parseInt(parsed[sizeName]),
search: parsed.q
});
} else if (parsed[pageName] && parsed[sizeName]) {
this.handleUpdateData({
page: parseInt(parsed[pageName]),
size: parseInt(parsed[sizeName]),
});
}
// this.fetch({
// ...parsed
// });
}
} else {
this.handleUpdateData({
page: currentPage,
size: pageSize,
});
}
}
handleTableChange = (pagination, filters, sorter) => {
const { currentPage, pageSize, searchValue } = this.state;
console.log("====================================");
console.log(filters, sorter, "tesadasdas");
console.log("====================================");
// this.setState({
// filteredInfo: filters,
// sortedInfo: sorter
// });
if (Object.keys(sorter).length !== 0) {
// this.fetch({
// _page: currentPage,
// _limit: pageSize,
// _sort: sorter.field,
// _order: sorter.order === "ascend" ? "asc" : "desc",
// ...filters
// });
if (searchValue) {
this.handleUpdateData({
sort: sorter,
filter: filters,
search: searchValue
});
} else {
this.handleUpdateData({ sort: sorter, filter: filters });
}
} else {
if (searchValue) {
this.handleUpdateData({ filter: filters, search: searchValue });
} else {
this.handleUpdateData({ filter: filters });
}
// this.fetch({
// _page: currentPage,
// _limit: pageSize,
// ...filters
// });
}
};
fetch = async (params = {}) => {
let { url, history } = this.props;
const stringified = querystring.stringify(params);
console.log("GGGGG3333", url.default, stringified, params, window.location);
this.setState({ loading: true });
// history.push(`${url.default}?${stringified}`);
history.push({
pathname: url.default,
search: stringified
});
try {
// let response = await callApi({
// url: url.default,
// params: {
// _page: params._page,
// _limit: params._limit,
// ...params
// }
// });
// if (response.status === 200) {
// this.setState({
// loading: false,
// data: response.data,
// totalData: response.data.total ? response.data.total : 100
// });
// }
} catch (error) {}
};
handleUpdateData = ({ search, sort, page, size, filter }) => {
const {
currentPage,
pageSize,
filteredInfo,
sortedInfo,
searchValue
} = this.state;
console.log("====================================");
console.log(
search, sort, page, size, filter,
"WWOOOPS!!"
);
console.log("====================================");
if (search && sort && filter) {
this.setState({
filteredInfo: filter,
sortedInfo: sort,
searchValue: search,
})
this.fetch({
_page: currentPage,
_limit: pageSize,
q: search,
_sort: sort.field,
_order: sort.order === "ascend" ? "asc" : "desc",
...filter
});
//filteredInfo value
} else if (filter) {
if (sort) {
this.setState({
filteredInfo: filter,
sortedInfo: sort,
})
this.fetch({
_page: currentPage,
_limit: pageSize,
_sort: sort.field,
_order: sort.order === "ascend" ? "asc" : "desc",
...filter
});
} else if (search) {
this.setState({
filteredInfo: filter,
searchValue: search,
})
this.fetch({
_page: currentPage,
_limit: pageSize,
q: search,
...filter
});
} else {
this.setState({
filteredInfo: filter,
})
this.fetch({
_page: currentPage,
_limit: pageSize,
...filter
});
}
//sortedInfo value
} else if (sort) {
if (filter) {
this.setState({
filteredInfo: filter,
sortedInfo: sort,
})
this.fetch({
_page: currentPage,
_limit: pageSize,
_sort: sort.field,
_order: sort.order === "ascend" ? "asc" : "desc",
...filter
});
} else if (search) {
this.setState({
sortedInfo: sort,
searchValue: search,
})
this.fetch({
_page: currentPage,
_limit: pageSize,
q: search,
_sort: sort.field,
_order: sort.order === "ascend" ? "asc" : "desc"
});
} else {
this.setState({
sortedInfo: sort,
})
this.fetch({
_page: currentPage,
_limit: pageSize,
_sort: sort.field,
_order: sort.order === "ascend" ? "asc" : "desc"
});
}
//search Value
} else if (search) {
if (filter) {
this.setState({
filteredInfo: filter,
searchValue: search,
})
this.fetch({
_page: currentPage,
_limit: pageSize,
q: search,
...filter
});
} else if (sort) {
this.setState({
sortedInfo: sort,
searchValue: search,
})
this.fetch({
_page: currentPage,
_limit: pageSize,
q: search,
_sort: sort.field,
_order: sort.order === "ascend" ? "asc" : "desc"
});
} else if (page && size) {
alert(page, size, "OOOPS@")
this.setState({
currentPage: page,
pageSize: size,
searchValue: search,
})
this.fetch({
_page: page,
_limit: size,
q: search
});
} else if (page) {
this.setState({
currentPage: page,
searchValue: search,
})
this.fetch({
_page: page,
_limit: pageSize,
q: search
});
} else if (size) {
this.setState({
pageSize: size,
searchValue: search,
})
this.fetch({
_page: currentPage,
_limit: size,
q: search
});
} else {
this.setState({
searchValue: search,
})
this.fetch({
_page: currentPage,
_limit: pageSize,
q: search
});
}
} else {
if (page && size) {
this.setState({
currentPage: page,
pageSize: size,
})
this.fetch({
_page: page,
_limit: size
});
} else if (page) {
this.setState({
currentPage: page,
})
this.fetch({
_page: page,
_limit: pageSize
});
} else if (size) {
this.setState({
pageSize: size,
})
this.fetch({
_page: currentPage,
_limit: size
});
} else {
this.fetch({
_page: currentPage,
_limit: pageSize
});
}
}
};
handleSearch = e => {
const { currentPage, pageSize, filteredInfo, sortedInfo } = this.state;
console.log("====================================");
console.log(e);
console.log("====================================");
// this.setState({
// searchValue: e
// });
if (e) {
this.handleUpdateData({ search: e, page: 1 });
} else {
this.handleUpdateData({ search: null, page: 1 });
}
};
handleSearchChange = e => {
const { currentPage, pageSize, filteredInfo, sortedInfo } = this.state;
// this.setState({
// searchValue: e
// });
if (e) {
this.setState({
searchValue: e.target.value
})
} else {
this.setState({
searchValue: null
})
}
};
handleDeleteConfirmYes = e => {
console.log(e);
message.success("Click on Yes");
};
handleDeleteConfirmNo = e => {
console.log(e);
message.error("Click on No");
};
handleclearAll = () => {
console.log("====================================");
console.log("reset");
console.log("====================================");
this.setState({
filteredInfo: null,
sortedInfo: null,
searchValue: null,
// currentPage: 1,
// pageSize: 10
});
this.handleUpdateData({
page: 1,
size: 10,
});
// this.fetch({
// _page: 1,
// _limit: 10
// });
};
handlePagination = page => {
const { pageSize, searchValue } = this.state;
// this.fetch({
// _page: page,
// _limit: pageSize
// });
if (searchValue) {
this.handleUpdateData({ page, search: searchValue });
} else {
this.handleUpdateData({ page });
}
// this.setState({
// currentPage: page
// });
};
handleSizeChange = (current, pageSize) => {
console.log("TEST!", current, pageSize, searchValue);
const { searchValue } = this.state;
// this.fetch({
// _page: current,
// _limit: pageSize
// });
if (searchValue) {
this.handleUpdateData({
page: current,
size: pageSize,
search: searchValue
});
} else {
this.handleUpdateData({ page: current, size: pageSize });
}
// this.setState({
// currentPage: current,
// pageSize
// });
};
handleRenderActionButton = ({action, item}) => {
let {keyValue} = this.props
let idValue = item[keyValue].toString()
console.log('====================================');
console.log('====================================');
switch (action.type) {
case 'edit':
return <Tooltip key={action.key} placement="top" title={action.name}>
<Link to={`${action.path}/1`} style={{padding: '5px 8px'}}>
<Icon type='edit' />
</Link>
</Tooltip>
break;
case 'view':
return <Tooltip key={action.key} placement="top" title={action.name}>
<Link to={`${action.path}/2`} style={{padding: '5px 8px'}}>
<Icon type='right-circle-o' />
</Link>
</Tooltip>
break;
case 'delete':
return <Tooltip key={action.key} placement="top" title={action.name}>
<Popconfirm
title="Are you sure delete this item?"
onConfirm={() => this.handleDeleteConfirmYes(item)}
onCancel={() => this.handleDeleteConfirmNo(item)}
okText="Yes"
cancelText="No" >
<a href="javascript:;" style={{padding: '5px 8px'}}>
<Icon type='delete' />
</a>
</Popconfirm>
</Tooltip>
break;
default:
return null
break;
}
}
render() {
let { columns, keyValue, actions } = this.props;
let {
sortedInfo,
filteredInfo,
headerColumns,
currentPage,
pageSize,
totalData,
data,
searchValue
} = this.state;
// let headerColumns = columns.map(column => {
// if (column.sorter) {
// column.sorter = (a, b) => a[column.dataIndex] - b[column.dataIndex];
// column.sortOrder =
// sortedInfo &&
// sortedInfo.columnKey === column.dataIndex &&
// sortedInfo.order;
// }
// return column;
// });
console.log("====================================");
console.log(currentPage, pageSize, "TESTASDASD");
console.log("====================================");
return (
<div>
<div className="table-operations">
<Button onClick={this.handleclearAll}>
Clear filters
</Button>
</div>
<Search
value={searchValue}
placeholder="input search text"
onSearch={this.handleSearch}
onChange={this.handleSearchChange}
enterButton
/>
<Table
rowKey={keyValue}
dataSource={data}
loading={this.state.loading}
onChange={this.handleTableChange}
pagination={false}
>
{columns &&
columns.map(column => (
<Column
key={column.dataIndex ? column.dataIndex : column.key}
{...column}
/>
))}
{
actions && actions.length > 0 && <Column
title="Action"
align="center"
key="action"
width={130}
render={(text, record) => (
actions.map(item => {
return item.access === true && this.handleRenderActionButton({action: item, item: record})
})
)}
/>
}
</Table>
<Row style={{paddingTop: 20}}>
<Col span={8}>col-12</Col>
<Col span={16} style={{textAlign: 'right'}}>
<Pagination
// size="small"
current={currentPage}
pageSize={pageSize}
showSizeChanger
onChange={this.handlePagination}
onShowSizeChange={this.handleSizeChange}
// defaultCurrent={currentPage}
total={totalData}
/>
</Col>
</Row>
</div>
);
}
}
export default withRouter(CustomTable);

View File

@ -0,0 +1,90 @@
import React, { Component } from "react";
import { Icon, Avatar, Dropdown, Menu, notification } from "antd";
import { Link, withRouter } from "react-router-dom";
import Helmet from 'react-helmet';
import { connect } from "react-redux";
import { customAction } from 'actions'
import styled from 'styled-components';
import { API_UNI_OIL , API_POST } from 'utils/Api'
const HeaderButton = styled.a`
/* This renders the buttons above... Edit me! */
padding: 0 10px;
display: inline-block;
vertical-align: top;
cursor: pointer;
-webkit-transition: all .3s,padding 0s;
transition: all .3s,padding 0s;
&:hover {
background-color: rgb(243, 243, 243);
color: #fff
}
`
class HeaderDropdown extends Component {
state = {
}
handleLogout = () => {
this.props.customAction({type: 'LOGOUT'});
}
render() {
//const { userInfo } = this.state
const { history, userInfo } = this.props;
const menu = (
<Menu style={{width: 150 , margin: '0 0 0 auto'}} >
<Menu.Item key="0">
<a
onClick={()=> history.push("/my-profile")}
role="button"
rel="noopener noreferrer" >
<Icon type="user" /> My Profile
</a>
</Menu.Item>
<Menu.Divider />
<Menu.Item key="1">
<a
role="button"
onClick={this.handleLogout}>
<Icon type="logout" /> Logout
</a>
</Menu.Item>
</Menu>
);
return (
<div>
<Helmet title = "Dashboard" />
<Dropdown overlay={menu} placement="bottomRight">
<HeaderButton role="button" style={{
marginRight: 16, color: '#8E8E93', maxWidth: '256px',
whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' , position: 'relative'
}} >
<Avatar size="small"
style={{ background: '#B8BBC9', marginRight: 5 }} icon="user"
/> { userInfo && (`${userInfo.firstname} ${userInfo.lastname}`) } <Icon type="down" />
</HeaderButton>
</Dropdown>
</div>
);
}
}
HeaderDropdown = connect(
state => ({
//fetchData: state.fetchData
// put initial values from account reducer
}),
{ customAction }
)(HeaderDropdown)
export default withRouter(HeaderDropdown);

View File

@ -0,0 +1,75 @@
import React from "react";
import { Breadcrumb, Icon } from 'antd';
// import styled from 'styled-components';
import { Link, withRouter } from 'react-router-dom';
function MainBreadcrumbs(props) {
const {
pageRoutes,
// match,
location,
root
} = props;
const pathSnippets = location.pathname.split('/').filter(i => i);
const extraBreadcrumbItems = pathSnippets.map((route, index) => {
const url = `/${pathSnippets.slice(0, index + 1).join('/')}`;
const routeCompare = pageRoutes.find((myRoute) => myRoute.path === url)
const paramsId = pathSnippets[pathSnippets.length - 1]
if (routeCompare) {
if (routeCompare.params) {
return (
<Breadcrumb.Item key={index}>
<Link to={`${url}/${paramsId}`}>
{routeCompare.name}
</Link>
</Breadcrumb.Item>
);
} else {
return (
<Breadcrumb.Item key={index}>
<Link to={url}>
{routeCompare.name}
</Link>
</Breadcrumb.Item>
);
}
}
// return <Breadcrumb.Item key={index}></Breadcrumb.Item>;
})
if (root) {
return (
<Breadcrumb
separator={<Icon type="right" style={{fontSize: '10px', opacity: 0.6}} />}
routeComparestyle={{ padding: '11px 24px 9px', fontSize: '12px' }}
>
<Breadcrumb.Item>
<Link to='/my-profile'>
<Icon type="home" /> {` Home`}
</Link>
</Breadcrumb.Item>
</Breadcrumb>
);
} else {
return (
<Breadcrumb
separator={<Icon type="right" style={{fontSize: '10px', opacity: 0.6}} />}
style={{ padding: '11px 24px 9px', fontSize: '12px' }}
>
<Breadcrumb.Item>
<Link to='/my-profile'>
<Icon type="home" /> {` Home`}
</Link>
</Breadcrumb.Item>
{extraBreadcrumbItems}
</Breadcrumb>
);
}
}
export default withRouter(MainBreadcrumbs);

View File

@ -0,0 +1,36 @@
import React from "react";
import { Layout } from 'antd';
import { withRouter } from 'react-router-dom'
import MainBreadcrumbs from './MainBreadcrumbs'
const { Content } = Layout;
function MainContent(props) {
const {
children,
pageRoutes,
root
} = props;
return (
[
props.location && props.location.key && (
<div key={1} style={{
background: '#fff' ,
marginBottom: '75px',
position: 'fixed',
marginTop: '-110px',
width: '100%'}}
>
<MainBreadcrumbs root={root} pageRoutes={pageRoutes} />
</div>
)
,
<Content key={2} style={{ margin: '0 16px', padding: '0', background: '#fff', }}>
{children}
</Content>,
]
);
}
export default withRouter(MainContent);

View File

@ -0,0 +1,19 @@
import React from "react";
import { Layout } from "antd";
// import styled from 'styled-components';
// import { Link } from 'react-router-dom';
const { Footer } = Layout;
function MainFooter(props) {
// const {} = props;
return (
<Footer style={{ textAlign: "center", background: '#fcfcfc' }} >
{/* YONDU-SDG ©2018 Created by Front End Team! */}
</Footer>
);
}
export default MainFooter;

View File

@ -0,0 +1,83 @@
import React from "react";
import { Layout, Icon } from "antd";
import { connect } from "react-redux";
import styled from 'styled-components';
import { Link } from 'react-router-dom';
import HeaderDropdown from './HeaderDropdown';
const { Header } = Layout;
const HeaderLink = styled(Link)`
/* This renders the buttons above... Edit me! */
padding: 0 10px;
display: inline-block;
vertical-align: top;
cursor: pointer;
-webkit-transition: all .3s,padding 0s;
transition: all .3s,padding 0s;
&:hover {
background-color: #1890ff;
color: #fff
}
`
const RightHeader = styled.div`
/* This renders the buttons above... Edit me! */
float: right;
`
const IconTrigger = styled(Icon)`
/* This renders the buttons above... Edit me! */
font-size: 20px;
line-height: 69px;
cursor: pointer;
-webkit-transition: all .3s,padding 0s;
transition: all .3s,padding 0s;
padding: 0 24px;
&:hover {
color: #1890ff;
}
`
function MainHeader(props) {
const {
collapsed,
toggle,
userInfo,
} = props
return (
<Header style={{ background: '#fff', padding: 0, height: '66px', lineHeight: '69px', borderBottom: '1px solid rgb(230, 236, 245)', }}>
<IconTrigger
className="trigger"
type={collapsed ? 'menu-unfold' : 'menu-fold'}
onClick={toggle}
/>
<RightHeader>
<HeaderDropdown userInfo={userInfo}/>
</RightHeader>
</Header>
);
}
MainHeader = connect(
state => ({
// pull initial values from account reducer
}),
// { customAction }
)(MainHeader);
export default MainHeader;

View File

@ -0,0 +1,262 @@
import React from 'react';
import { Layout, Icon, Menu } from 'antd';
import styled from 'styled-components';
import { withRouter, Link } from 'react-router-dom';
const { SubMenu } = Menu;
const { Sider } = Layout;
const LogoPlaceholder = styled.div`
height: 32px;
margin: 16px;
background-repeat: no-repeat;
background-size: contain;
background-position: center;
`;
function MainSidebar(props) {
const { collapsed, match, location, userInfo, systemPreferences } = props;
const navigation = [
{
key: 0,
label: 'User Management',
path: '/user-management',
icon: 'team',
access: userInfo && userInfo.role == 1 ? true : false,
//access: userInfo && (userInfo.role == 1 || userInfo.role == 3) ? true : false,
},
{
key: 9,
label: 'Notifications',
path: '/notifications',
icon: 'notification',
access: true,
//access: userInfo && (userInfo.role == 1 || userInfo.role == 3) ? true : false,
},
{
key: 4,
label: 'Member Management',
path: '/member-management',
icon: 'credit-card',
access: userInfo && userInfo.role == 1 ? true : false,
child: [
{
key: 0.0,
label: 'Card Member',
path: '/member-management/card-member',
access: true,
},
{
key: 0.1,
label: 'Locked Accounts',
path: '/member-management/lock-account',
access: true,
},
],
},
{
key: 8,
label: 'Home Page ( Mobile ) ',
path: '/home-page',
icon: 'home',
access: true,
child: [
{
key: 0.0,
label: 'Photo Slider',
path: '/home-page/photo-slider',
access: true,
},
],
},
{
key: 3,
label: 'Promotions',
path: '/promotions',
icon: 'tags',
access: true,
},
{
key: 2,
label: 'Top-Up',
path: '/top-up',
icon: 'plus-circle',
access: userInfo && userInfo.role == 1 ? true : false,
},
{
key: 6,
label: 'About Us',
path: '/about-us',
icon: 'info-circle',
access: userInfo && userInfo.role == 1 ? true : false,
child: [
{
key: 0.6,
label: 'Card Types',
path: '/about-us/card-types',
access: true,
},
{
key: 0.5,
label: 'Terms & Privacy',
path: '/about-us/term-privacy',
access: true,
},
],
},
{
key: 7,
label: 'Reports',
path: '/reports',
icon: 'file-text',
access: true,
child: [
{
key: 0.7,
label: 'Registration Report',
path: '/reports/registration-report',
access: true,
},
{
key: 0.8,
label: 'Top-Up Usage Report',
path: '/reports/top-up',
access: true,
},
{
key: 0.9,
label: 'Mobile Usage Report',
path: '/reports/mobile-report',
access: true,
},
{
key: 0.1,
label: 'Station Rating Report',
path: '/reports/station-rating',
access: true,
},
],
},
{
key: 8,
label: 'System Parameters',
path: '/system-parameters',
icon: 'setting',
access: userInfo && userInfo.role == 1 ? true : false,
},
{
key: 12,
label: 'Station Locator',
path:'',
icon:'environment',
access: true,
child: [
{
key: 0.11,
label: 'Branches',
path:'/branches',
access: true
},
{
key: 0.12,
label: 'Stations',
path:'/stations',
access: true
},
{
key: 0.13,
label: 'Fuels',
path:'/fuels',
access: true
}
],
},
];
let newURL = location.pathname.split('/');
let appendedUrl = newURL[2];
if (appendedUrl == 'create' || appendedUrl == 'view' || appendedUrl == 'edit') appendedUrl = null;
let isSeondDaryPathExist = appendedUrl ? `/${appendedUrl}` : '';
let secondaryURL = `${match.path}${isSeondDaryPathExist}`;
return (
<Sider
trigger={null}
collapsible
width={295}
collapsed={collapsed}
style={{ background: '#fff', border: 'solid 1px #e6ecf5', zIndex: '999' }}
>
{!collapsed ? (
<div style={{ height: '65px', padding: '12px 0', textAlign: 'center', borderBottom: '1px solid #e6ecf5' }}>
{/* <img src={ require("assets/img/logo_unioil.png") } style={{ height: 40 }} /> */}
{userInfo && (
<img src={`${systemPreferences ? systemPreferences : userInfo.logo}`} style={{ height: '100%' }} />
)}
</div>
) : (
<LogoPlaceholder
className='logo'
style={{ backgroundImage: `url(${systemPreferences ? systemPreferences : userInfo.logo})` }}
/>
)}
<Menu
style={{ borderRight: !collapsed ? 0 : null, height: '90vh', overflow: 'auto', paddingTop: '17px' }}
//inlineIndent={10}
defaultOpenKeys={[ match.path ]}
selectedKeys={[ secondaryURL ]}
mode='inline'
>
{navigation.map((item) => {
if (item.access) {
if (item.child) {
return (
<SubMenu
key={item.path}
title={
<span>
<Icon type={item.icon} />
<span>{item.label}</span>
</span>
}
>
{item.child.map((subItem) => {
if (subItem.access) {
return (
<Menu.Item key={subItem.path}>
<Link to={subItem.path} style={{ paddingLeft: '15px' }}>
{subItem.icon && <Icon type={subItem.icon} />}
{subItem.label}
</Link>
</Menu.Item>
);
} else {
return null;
}
})}
</SubMenu>
);
} else {
return (
<Menu.Item key={item.path}>
<Link to={item.path}>
{item.icon && <Icon type={item.icon} />}
<span>{item.label}</span>
</Link>
</Menu.Item>
);
}
} else {
return null;
}
})}
</Menu>
</Sider>
);
}
export default withRouter(MainSidebar);

View File

@ -0,0 +1,107 @@
import React from 'react'
import { Layout } from 'antd';
import { notification } from "antd";
import { connect } from 'react-redux';
import IdleTimer from 'react-idle-timer'
import MainFooter from './components/MainFooter'
import MainHeader from './components/MainHeader'
import MainSidebar from './components/MainSidebar'
import { API_UNI_OIL , API_POST } from 'utils/Api'
import { customAction } from 'actions'
class DashboardLayout extends React.Component {
constructor(props) {
super(props)
this.idleTimer = null
this.onActive = this.handleActive.bind(this)
this.onIdle = this.handleIdle.bind(this)
this.state = {
collapsed: false,
userInfo: null,
updatedLogo: null
};
}
componentWillReceiveProps(nexProps, prevProps) {
if(nexProps && nexProps.systemPreferences) {
if(nexProps.systemPreferences.data && nexProps.systemPreferences.data) {
this.setState({
updatedLogo: nexProps.systemPreferences.data.logo
})
}
}
}
componentDidUpdate(nexProps) {
}
handleActive(e) {
// console.log('user is active', e)
}
handleIdle(e) {
// console.log('user is idle', e)
notification.error({
message: "Error",
description: <div>You are logout automatically for being idle more than 10 minutes.</div>,
duration: 0,
key: 'idle-notification-1'
});
this.props.customAction({type: 'LOGOUT'});
}
toggle = () => {
this.setState({
collapsed: !this.state.collapsed,
});
}
render() {
//const { userInfo } = this.state
const { children, userInfo } = this.props
return (
<Layout style={{ height: '100%' }}>
<MainSidebar collapsed={this.state.collapsed} userInfo={userInfo.data.userInfo} systemPreferences={this.state.updatedLogo}/>
<Layout style={{background: '#fcfcfc', paddingBottom: '10px'}}>
<MainHeader
collapsed={this.state.collapsed}
toggle={this.toggle}
userInfo={userInfo.data.userInfo}
/>
<div style={{ overflow: 'auto', marginTop: '94px', paddingTop: '16px', position: 'relative' }}>
<IdleTimer
ref={ref => { this.idleTimer = ref }}
element={document}
onActive={this.onActive}
onIdle={this.onIdle}
timeout={600000}
>
{children}
</IdleTimer>
</div>
</Layout>
</Layout>
);
}
}
DashboardLayout = connect(
state => ({
// pull initial values from account reducer
userInfo: state.login,
systemPreferences: state.systemPreferences
}),
{ customAction }
)(DashboardLayout);
export default DashboardLayout

View File

@ -0,0 +1,31 @@
import React from 'react';
import DashboardLayout from '../Layout'
import { Route, Redirect } from 'react-router-dom'
import { connect } from 'react-redux'
function DashboardRoute({ component: Component, isAuthenticated, ...rest }) {
return (
<Route {...rest} render={props => isAuthenticated ? (
<DashboardLayout>
<Component {...props} />
</DashboardLayout>
) : <Redirect
to={{
pathname: "/login",
state: { from: props.location, message: "You must log in to Enter this page" }
}}
/>
}
/>
)
};
export default DashboardRoute = connect(
state => ({
isAuthenticated: state.auth.isAuthenticated
}),
)(DashboardRoute);

View File

@ -0,0 +1,48 @@
import React, { Component, Fragment } from 'react';
import { Link } from 'react-router-dom';
import { List, Avatar } from 'antd';
import { fetchData } from 'utils/Api';
class ListDataDisplay extends Component {
state = {
data: []
};
async componentDidMount() {
// const { url } = this.props;
// const response = await fetchData(url);
// this.setState({
// data: response.data
// })
}
render() {
const { layout, avatar, viewPath, header, footer } = this.props;
return (
<Fragment>
<List
header={header}
footer={footer}
itemLayout={layout}
dataSource={this.state && this.state.data.data}
renderItem={item => (
<List.Item>
<List.Item.Meta
avatar={avatar && <Avatar
src={item.avatar}
/>}
title={<Link to={viewPath.replace(':id', item.id)}>{item.first_name}</Link>}
description={`${item.first_name.toLowerCase()}_${item.last_name.toLowerCase()}@gmail.com`}
/>
</List.Item>
)}
/>
</Fragment>
)
}
}
export default ListDataDisplay;

View File

@ -0,0 +1,5 @@
import List from './List';
export {
List,
}

View File

@ -0,0 +1,66 @@
import React, { PureComponent } from "react";
import { withRouter } from "react-router-dom";
import { Menu, Dropdown, Button, notification } from "antd";
import DownloadFile from "js-file-download";
import queryString from "query-string";
import moment from 'moment';
import { API_UNI_OIL } from "utils/Api";
class DropdownExport extends PureComponent {
state = {
loading: false
}
handleExportCSV = async() => {
this.setState({ loading: true });
let { location } = this.props;
let { search } = location;
let params = queryString.parse(search);
if(this.props.defaultFilter){
params = {
...params,
...this.props.defaultFilter
}
}
try {
let response = await API_UNI_OIL.get(this.props.url.path, {
params,
responseType: 'blob'
});
if (response.status === 200 || response.status === 201) {
//let dateNow = moment(new Date()).format('DD-MMM-YYYY')
let dateNow = moment(new Date()).format('MMDDYYYY')
DownloadFile(response.data, `${this.props.url.fileName}_${dateNow}.csv`);
this.setState({ loading: false });
}
} catch (error) {
this.setState({ loading: false });
}
}
render() {
const { loading } = this.state;
return(
<Button
loading={loading}
onClick={this.handleExportCSV}
style={{background: 'rgb(231, 70, 16)', borderColor: 'rgb(231, 70, 16)', color: '#fff'}}
>
Export CSV
</Button>
)
}
}
export default withRouter(DropdownExport);

View File

@ -0,0 +1 @@
export { default as DropdownExport } from "./DropdownExport";

View File

@ -0,0 +1,69 @@
import React, { Component } from 'react';
//import { Field, reduxForm } from 'redux-form';
import { Form, Row, Col, Button, Icon, Input,Select } from 'antd';
import { required } from 'constants/validation';
import {AInput,ASelect,ARangePicker} from './AntdForms'
const FormItem = Form.Item;
const { Option } = Select;
class AdvancedSearchForm extends Component {
render() {
const { handleSubmit,onSubmit, reset } = this.props;
return (
<Form
onSubmit={onSubmit}
className="login-form"
>
<Row gutter={24}>
<Col span={8}>
{/* <Field
name="filter_field"
label="Filter by Code"
component={ASelect}
validate={required}
defaultValue="1"
>
<Option value="ff0000">Red</Option>
<Option value="00ff00">Green</Option>
<Option value="0000ff">Blue</Option>
</Field> */}
</Col>
<Col span={8}>
{/* <Field
name="filter_value"
label="Filter by Name"
component={ARangePicker}
width={4}
placeholder={["From", "To"]}
hasFeedback
onFocus={e => e.preventDefault()}
onBlur={e => e.preventDefault()}
// validate={required}
/> */}
</Col>
</Row>
<Row>
<Col span={24} style={{ textAlign: 'right' }}>
<Button type="primary" htmlType="submit">Search</Button>
<Button style={{ marginLeft: 8 }} onClick={reset}>
Clear
</Button>
</Col>
</Row>
</Form>
);
}
}
// AdvancedSearchForm = reduxForm({
// form: "CreateEmployeeFieldsForm",
// })(AdvancedSearchForm);
export default AdvancedSearchForm;

View File

@ -0,0 +1,47 @@
import React from "react";
import { Form, Input, Radio, Select, Checkbox, Button, DatePicker } from "antd";
const FormItem = Form.Item;
const RadioGroup = Radio.Group;
const { Option } = Select;
const { TextArea } = Input;
const { RangePicker } = DatePicker;
const formItemLayout = {
// labelCol: {
// xs: { span: 24 },
// sm: { span: 7 }
// },
// wrapperCol: {
// xs: { span: 24 },
// sm: { span: 14 }
// }
};
const makeField = Component => ({ input, meta, children, hasFeedback, label, ...rest }) => {
const hasError = meta.touched && meta.invalid;
const inputs = {...input};
const rests = {...rest};
console.log(inputs,'inputss', rests , 'ressttss')
return (
<FormItem
{...formItemLayout}
label={label}
validateStatus={hasError ? "error" : "success"}
hasFeedback={hasFeedback && hasError}
help={hasError && meta.error}
>
<Component {...input} {...rest} children={children} />
</FormItem>
);
};
export const AInput = makeField(Input);
export const ARadioGroup = makeField(RadioGroup);
export const ASelect = makeField(Select);
export const ACheckbox = makeField(Checkbox);
export const ATextarea = makeField(TextArea);
export const ARangePicker = makeField(RangePicker);

View File

@ -0,0 +1,67 @@
import React, { Component } from 'react';
import { Form, Cascader } from 'antd';
import { fetchData } from 'utils/Api';
const FormItem = Form.Item;
const options = [{
value: 'zhejiang',
label: 'Zhejiang',
children: [{
value: 'hangzhou',
label: 'Hangzhou',
children: [{
value: 'xihu',
label: 'West Lake',
}],
}],
}, {
value: 'jiangsu',
label: 'Jiangsu',
children: [{
value: 'nanjing',
label: 'Nanjing',
children: [{
value: 'zhonghuamen',
label: 'Zhong Hua Men',
}],
}],
}];
class CascaderForm extends Component {
async componentDidMount() {
const { url } = this.props;
const response = await fetchData(url);
this.setState({
options: response.data.data
})
}
render() {
const {
field: { name, /* ...field */ },
form: { errors, setFieldValue, /* ...form */ },
layout,
label,
required,
...props
} = this.props;
return (
<FormItem
{...layout}
required={required}
label={label}
validateStatus={errors[name] && 'error'}
help={errors[name]}
>
<Cascader
{...props}
onChange={this.handleChange = (value) => { setFieldValue(name, value) }}
options={options}
/>
</FormItem>
);
}
}
export default CascaderForm;

View File

@ -0,0 +1,55 @@
import React from 'react';
import { Checkbox, Form } from 'antd';
const FormItem = Form.Item;
const CheckboxForm = ({
field: { name, ...field },
form: { touched, errors, handleChange, setFieldValue, ...form },
label,
inline,
layout,
required,
...props,
}) => {
if (inline) {
return (
<Checkbox
{...props}
{...field}
name={name}
type="checkbox"
checked={field.value}
onChange={this.handleChange = (value) => { setFieldValue(name, !field.value) }}
>
{label}
</Checkbox>
)
} else {
return (
<FormItem
{...layout}
required={required}
style={{marginBottom: '10px',marginLeft: '20.9%'}}
validateStatus={touched[name] && errors[name] && 'error'}
help={touched[name] && errors[name]}
>
<Checkbox
{...props}
{...field}
name={name}
type="checkbox"
checked={field.value}
onChange={this.handleChange = (value) => { setFieldValue(name, !field.value) }}
>
{label}
</Checkbox>
</FormItem>
);
}
};
export default CheckboxForm;

View File

@ -0,0 +1,160 @@
import React from 'react';
import { Form, DatePicker } from 'antd';
import moment from 'moment'
const FormItem = Form.Item;
const { RangePicker } = DatePicker;
const DatePickerForm = ({
field: { ...field },
form: { touched, errors, handleSubmit, setFieldValue, handlePanelChange, ...form },
type,
layout,
label,
format,
minDateToday,
required,
disabledDateStart,
dateStartEnd,
disabledDateStartEndPhotoSlider,
disabledDateStartEndPhotoSliderEndDate,
isEdit,
isAutoFill,
...props
}) => {
const onDateChange = (value, isDateRange = false) => {
value && setFieldValue(field.name, isDateRange
? [value[0].format(format), value[1].format(format)]
: value.format(format)
)
if(value == null) {
setFieldValue(field.name, isDateRange
? [value[0].format(format), value[1].format(format)]
: null
)
}
}
// Disable date less than `Today`
// use minDateToday props
// const disabledDate = (current) => {
// if (minDateToday) {
// var oneDay = (1 * 24 * 60 * 60 * 1000);
// return current && (current.valueOf() < (Date.now() - oneDay));
// }
// }
const disabledDate = (current) => {
// Can not select days before today and today
// for promotions
if(disabledDateStart && !disabledDateStartEndPhotoSlider) {
if(form.values.date_start) {
return current && current < moment(form.values.date_start);
} else {
//return current && moment(current).add(2,'days') < moment().endOf('day').add(2,'days');
}
}
// for photo slider Date Start
if(disabledDateStartEndPhotoSlider && !disabledDateStartEndPhotoSliderEndDate) {
if(dateStartEnd) {
if(current && current.format() < moment(dateStartEnd.date_start).format()) {
if(isEdit) {
//return current && current.format() < moment(dateStartEnd.date_start).subtract(1,'days').format()
}
return current && current.format() < moment(dateStartEnd.date_start).format()
} else {
if(isEdit) {
return current && current.format() > moment(dateStartEnd.date_end).format();
}
//return current && current.format() > moment(dateStartEnd.date_end).add(1,'days').format();
return current && current.format() > moment(dateStartEnd.date_end).format();
}
}
}
// for photo slider date End
if(disabledDateStartEndPhotoSliderEndDate) {
if(dateStartEnd) {
if(current && current.format() < form.values.date_start) {
// disabled previous date
return current && current < moment(form.values.date_start);
} else {
// diabled past date
if(isEdit) {
return current && current.format() > moment(dateStartEnd.date_end).format();
}
//return current && current.format() > moment(dateStartEnd.date_end).add(1,'days').format();
return current && current.format() > moment(dateStartEnd.date_end).format();
}
}
}
if(disabledDateStart) {
if(dateStartEnd) {
// return
} else {
if(form.values.date_start) {
return current && current < moment(form.values.date_start);
}
}
}
}
let _props = {...props}; let _field = {...field};
if(_field.value !== "") {
_props.value = _field.value && moment(_field.value,format)
}
return (
<FormItem
{...layout}
required={required}
label={label}
style={{marginBottom: '10px'}}
validateStatus={touched[field.name] && errors[field.name] && 'error'}
help={touched[field.name] && errors[field.name]}
>
{ type === 'date' &&
<DatePicker
{..._props}
onChange={(value) => onDateChange(value)}
format={format}
disabledDate={disabledDateStartEndPhotoSlider || disabledDateStart ? disabledDate : ()=> { return false } }
style={{width: '250px'}}
/>
}
{ type === 'date-time' &&
<DatePicker
showTime={true}
{..._props}
onChange={(value) => onDateChange(value)}
format={format}
style={{width: '250px'}}
/>
}
{ type === 'range' &&
<RangePicker
{...props}
onChange={(value) => onDateChange(value, true)}
format={format}
disabledDate={disabledDate}
/>
}
</FormItem>
);
};
export default DatePickerForm;

View File

@ -0,0 +1,145 @@
import React, { Component } from 'react';
import { Button,Popconfirm, Icon, Dropdown, Menu } from 'antd';
class HeaderForm extends Component {
confirm(action) {
action();
// message.success('Click on Yes');
}
cancel(e) {
// console.log(e);
// message.error('Click on No');
}
render() {
const { action, cancel, deleteAction , title , actionBtnName, cancelBtnName,
deleteBtnName, loading, withConfirm, styleBtn, isDropDown , actionPrivacy,
actionTerms, disabled, withCancelConfirm, isInsideForm } = this.props;
const menu = (
<Menu className="terms-management"
//onClick={handleMenuClick}
>
<Menu.Item key="1" onClick={actionPrivacy}>{`Terms & Condition`}</Menu.Item>
<Menu.Item key="2" onClick={actionTerms}>Privacy Policy</Menu.Item>
</Menu>
);
return (
<div style={{display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
borderBottom: '1px solid #E6ECF5',
background: '#fff',
position: 'fixed', width: '100%',
padding: '0px 24px 5px', zIndex: 99,
marginTop: isInsideForm == true ? '-154px' : '-73px' , marginLeft: '-17px' }}
>
<h1 style={{fontSize: '24px'}}>{title}</h1>
<div style={{display: 'flex', position: 'fixed', right: '24px'}}>
{
action &&
<div>
{
withConfirm
?
(
<Popconfirm
placement="bottom"
onConfirm={()=>this.confirm(action)}
onCancel={this.cancel} okText="Yes" cancelText="No"
title={withConfirm && withConfirm.message}
>
<Button
disabled={disabled}
loading={loading}
style={{ margin: '0 4px', width: '135px',
display: 'block', background: '#E74610', borderColor:'#E74610',
color: '#fff' ,
opacity: disabled ? 0.5 : 'initial'
}}
>
{actionBtnName}
</Button>
</Popconfirm>
) :
!isDropDown && (
<Button
loading={loading}
onClick={action}
disabled={disabled}
style={{
margin: '0 4px', width: '135px', display: 'block',
background: styleBtn ? styleBtn.background : '#E74610',
borderColor: styleBtn ? styleBtn.borderColor : '#E74610',
color: styleBtn ? styleBtn.color : '#fff',
opacity: disabled ? 0.5 : 'initial'
}}
>
{actionBtnName}
</Button>
)
}
</div>
}
{
cancel &&
<Popconfirm
placement="bottomRight"
onConfirm={cancel}
onCancel={null} okText="Yes" cancelText="No"
title={withCancelConfirm && withCancelConfirm.message}
>
<Button
loading={loading}
onClick={withCancelConfirm ? null : cancel}
style={{ margin: '0 4px', width: '135px', display: 'block', background: 'white', borderColor:'#b8bbc9', color: '#65697f' }}
>
{cancelBtnName}
</Button>
</Popconfirm>
}
{
deleteAction &&
(
<Popconfirm
placement="bottom" title={'Are you sure you want to delete this record?'}
onConfirm={deleteAction} okText="Yes" cancelText="No"
icon={ <Icon type="close-circle" /> }
>
<Button
disabled={disabled}
type="danger"
loading={loading}
style={{ margin: '0 4px', width: '135px', display: 'block', background: 'white', borderColor:'#b8bbc9', color: '#65697f' }}
>
{deleteBtnName}
</Button>
</Popconfirm>
)
}
{
isDropDown && (
<div style={{position: 'relative'}} className="terms-management-parent">
<Dropdown overlay={menu} className="terms-management">
<Button disabled={disabled}>
Add <Icon type="down" />
</Button>
</Dropdown>
</div>
)
}
</div>
</div>
);
}
}
export default HeaderForm;

View File

@ -0,0 +1,37 @@
import * as React from 'react';
import { Form, Icon, Input, Row, Col, Button } from 'antd';
import ReactInputMask from 'react-input-mask';
const FormItem = Form.Item;
const InputNumberForm = ({
field: { ...field },
form: { touched, errors, ...form },
required,
icon,
layout,
withActionBtn,
action,
loading,
mask,
...props
}) => {
return (
<FormItem
{...layout}
required={required}
label={props.label}
style={{marginBottom: '10px'}}
validateStatus={touched[field.name] && errors[field.name] && 'error'}
help={touched[field.name] && errors[field.name]}
>
<ReactInputMask {...props}
className="ant-input"
{...field} mask={mask} maskChar=" "
/>
</FormItem>
);
};
export default InputNumberForm;

View File

@ -0,0 +1,71 @@
import React, { Component } from 'react';
import { Form, Icon, InputNumber } from 'antd';
const FormItem = Form.Item;
class InputNumberAntD extends Component {
handleChange = (value) => {
const { setFieldValue } = this.props.form;
const { name } = this.props.field;
//let valueNum = parseFloat(value).toFixed(2)
// Add custom action `onChange`
return setFieldValue(name, value);
}
render() {
const children = [];
const {
field: { ...field },
form: { touched, errors, ...form },
required,
icon,
layout,
withActionBtn,
action,
min,
max,
step,
...props
} = this.props;
const _props = {...props}
_props.value = field.value
return (
<FormItem
{...layout}
required={required}
label={props.label}
style={{marginBottom: '10px'}}
validateStatus={touched[field.name] && errors[field.name] && 'error'}
help={touched[field.name] && errors[field.name]}
>
<InputNumber
{..._props}
//defaultValue={0}
style={{width: '100%'}}
min={min && min}
max={max && max}
step={step && step}
//formatter={value => value && `${value}`}
//parser={value => value && value.replace('', '')}
// formatter={value => `${value}`.replace(/\B(?=(\d{3})+(?!\d))/g, ',')}
// parser={value => value.replace(/\$\s?|(,*)/g, '')}
onChange={this.handleChange}
/>
</FormItem>
);
}
}
export default InputNumberAntD;

View File

@ -0,0 +1,31 @@
import * as React from 'react';
import { Form, Icon, Input, Row, Col, Button } from 'antd';
import InputPassword from 'antd-input-password';
const FormItem = Form.Item;
const InputPasswords = ({
field: { ...field },
form: { touched, errors, ...form },
required,
icon,
layout,
withActionBtn,
action,
...props
}) => {
return (
<Form.Item
{...layout}
required={required}
label={props.label}
style={{marginBottom: '10px'}}
validateStatus={touched[field.name] && errors[field.name] && 'error'}
help={touched[field.name] && errors[field.name]}
>
<Input.Password {...props} {...field} />
</Form.Item>
);
};
export default InputPasswords;

View File

@ -0,0 +1,67 @@
import * as React from 'react';
import { Form, Icon, Input, Tooltip } from 'antd';
const FormItem = Form.Item;
const { TextArea } = Input;
const content = (
<span>
<div>This content will be used in the</div>
<div>"Enter ID Number" page as part of</div>
<div>the Apply for a Card process of</div>
<div>the Unioil Mobile App.</div>
</span>
);
const InputTextArea = ({
field: { ...field },
form: { touched, setFieldValue, errors, ...form },
required,
icon,
layout,
withActionBtn,
action,
onCountText,
charsperpage,
pagecount,
hasIcon,
...props
}) => {
<Icon type="question-circle" />
return (
<FormItem
{...layout}
required={required}
label={
<span>
{`${props.label} `}
{
hasIcon &&
<Tooltip placement="top" title={content}>
<Icon type="question-circle" />
</Tooltip>
}
</span>
}
style={{marginBottom: '10px'}}
validateStatus={touched[field.name] && errors[field.name] && 'error'}
help={touched[field.name] && errors[field.name]}
>
<TextArea
{...props}
{...field}
prefix={icon && <Icon type={icon} style={{ color: 'rgba(0,0,0,.25)' }} />}
/>
{onCountText && <div style={{position: 'relative'}}>
<div style={{position: 'absolute',right: '0%', top: '-18px'}}>
<span style={{color: field.value.length > charsperpage ? 'red' : 'rgba(0, 0, 0, 0.65)'}}>{field.value.length}</span>/{charsperpage && charsperpage}</div>
</div>
}
</FormItem>
);
};
export default InputTextArea;

View File

@ -0,0 +1,93 @@
import * as React from 'react';
import { Form, Icon, Input, Row, Col, Button, message } from 'antd';
import CopyToClipboard from 'react-copy-to-clipboard';
const FormItem = Form.Item;
const InputForm = ({
field: { ...field },
form: { touched, errors, ...form },
required,
icon,
layout,
withActionBtn,
action,
loading,
isCopyUsername,
...props
}) => {
return (
<FormItem
{...layout}
required={required}
label={props.label}
style={{marginBottom: '10px'}}
validateStatus={touched[field.name] && errors[field.name] && 'error'}
help={touched[field.name] && errors[field.name]}
>
{
!withActionBtn
?
<div style={{display: 'flex'}}>
<Input
{...props}
{...field}
prefix={icon && <Icon type={icon} style={{ color: 'rgba(0,0,0,.25)' }} />}
/>
{
isCopyUsername && (
<CopyToClipboard text={field.value}>
<Button
loading={loading}
disabled={
field.value.length > 0 ? false : true
}
style={{
padding: '0 30px',
opacity: field.value.length > 0 ? 'initial' : 0.8,
margin: '0 0 0 10px',background: '#E74610', borderColor:'#E74610', color: '#fff'
}}
onClick={()=> message.success('Username successfully copied.')}>
Copy
</Button>
</CopyToClipboard>
)
}
</div>
:
<Row gutter={8}>
<Col span={13}>
<Input
{...props}
{...field}
className="generated-input"
prefix={icon && <Icon type={icon} style={{ color: 'rgba(0,0,0,.25)' }} />}
/>
</Col>
<Col span={11}>
<Button
loading={loading}
disabled={withActionBtn.disabled}
style={{opacity: withActionBtn.disabled ? 0.8 : 'initial' ,background: '#E74610', borderColor:'#E74610', color: '#fff'}} onClick={withActionBtn.action}>{withActionBtn.name}
</Button>
<CopyToClipboard text={withActionBtn.password} onCopy={withActionBtn.copyAction ? withActionBtn.copyAction : ()=> {return null}}>
<Button
loading={loading}
disabled={withActionBtn.copyAction ? false : true}
style={{
padding: '0 30px',
opacity: withActionBtn.copyAction ? 'initial' : 0.8 , background: '#E74610', borderColor:'#E74610', color: '#fff', marginLeft: '5px'}}
// onClick={withActionBtn.copyAction ? withActionBtn.copyAction : ()=> {return null} }
>
Copy
</Button>
</CopyToClipboard>
</Col>
</Row>
}
</FormItem>
);
};
export default InputForm;

View File

@ -0,0 +1,86 @@
import React, { Component } from 'react';
import { Form, Select } from 'antd';
import { fetchData } from 'utils/Api';
const FormItem = Form.Item;
const Option = Select.Option;
class MultiSelectForm extends Component {
async componentDidMount() {
// const { url } = this.props;
// const response = await fetchData(url);
// this.setState({
// options: response.data.data
// })
}
handleChange = (value) => {
const { setFieldValue } = this.props.form;
const { name } = this.props.field;
if(this.props.handleGetDate) {
this.props.handleGetDate(value);
this.props.handleAutoFillDeatils(value,setFieldValue,this.props);
}
// Add custom action `onChange`
return setFieldValue(name, value);
}
render() {
const children = [];
if (this.state && this.state.options) {
this.state.options.map((item, key) => {
children.push(<Option value={item.id.toString()}>{item.first_name}</Option>)
return item;
});
}
const {
field: { ...field },
form: { touched, errors, ...form },
layout,
label,
required,
optionFilterProp,
mode,
optionsList,
branchesOptionsTwo,
placeholder,
...props
} = this.props;
// console.log(field)
return (
<FormItem
{...layout}
required={required}
label={label}
style={{marginBottom: '10px'}}
validateStatus={touched[field.name] && errors[field.name] && 'error'}
help={touched[field.name] && errors[field.name]}
>
<Select
{...props}
// filterOption={
// optionFilterProp ?
// (input, option) => option.props.children.toLowerCase().indexOf(input.toLowerCase()) >= 0 : ''
// }
placeholder={placeholder}
value={field.value}
mode={mode}
onChange={this.handleChange}
children={children}
>
{
optionsList && (optionsList.map((item,i) => {
return <Option value={item.id} key={`${i}-${field.name}`}>{item.name}</Option>
}))
}
</Select>
</FormItem>
);
}
}
export default MultiSelectForm;

View File

@ -0,0 +1,69 @@
import React, {Component} from 'react';
import { Form , Select } from 'antd';
import { Field } from 'formik';
import { Input } from 'components/Forms';
const formItemLayout = {
labelCol: {
xs: { span: 24 },
sm: { span: 4 },
},
wrapperCol: {
xs: { span: 24 },
sm: { span: 20 },
},
};
class MultiSelectOptions extends Component {
state= {
value: []
}
handleChange(event){
console.log(event)
this.setState({value: event})
}
render() {
const FormItem = Form.Item;
const Option = Select.Option;
const {
field: { ...field },
form: { touched, errors, ...form },
required,
icon,
layout,
withActionBtn,
action,
optionsList,
...props
} = this.props;
return (
<FormItem
{...layout}
required={required}
label={props.label}
style={{marginBottom: '10px'}}
validateStatus={touched[field.name] && errors[field.name] && 'error'}
help={touched[field.name] && errors[field.name]}
>
<Select
style={{ width: '100%' }}
placeholder="Select fuels."
defaultValue={[]}
onChange={(e)=>this.props.onChange (e)}
optionLabelProp="label"
mode="multiple"
>
{
optionsList && (optionsList.map((item,i) => {
return <Option value={item.id} key={`${i}-${field.name}`} label={item.name}>{item.name}</Option>
}))
}
</Select>
</FormItem>
)};
};
export default MultiSelectOptions;

View File

@ -0,0 +1,84 @@
import React, { Component } from 'react';
import { Form, Radio } from 'antd';
const FormItem = Form.Item;
const RadioGroup = Radio.Group;
let styles = {
':select': {
backgroundColor: 'yellow',
color: 'red'
}
};
class InputForm extends Component {
handleChange = (e) => {
const { setFieldValue } = this.props.form;
const { name } = this.props.field;
//Add custom action `onChange`
if(this.props.handleResetValue) {
this.props.handleResetValue(this.props.form);
}
if(this.props.handleScheduleStatus) {
this.props.handleScheduleStatus(e.target.value)
}
return setFieldValue(name, e.target.value);
}
render() {
const {
field: { ...field },
form: { touched, errors, ...form },
required,
icon,
layout,
optionsList,
isRadioButton,
...props,
} = this.props;
let _props = {...props};
let _field = {...field};
if(!_field.value) {
_field.value = _props.defaultValue
}
return (
<FormItem
{...layout}
required={required}
label={props.label}
style={{marginBottom: '10px'}}
validateStatus={touched[field.name] && errors[field.name] && 'error'}
help={touched[field.name] && errors[field.name]}
>
<Radio.Group {..._field} {..._props} onChange={this.handleChange}>
{
optionsList ? (
optionsList.map((item,i) => {
if(isRadioButton) {
return <Radio.Button value={item.value} disabled={item.isDisabled} key={i} style={styles}>
{item.label}
</Radio.Button>
}
return <Radio value={item.value} key={i} style={styles}>
{item.label}
</Radio>
})
) : null
}
</Radio.Group>
</FormItem>
);
}
}
export default InputForm;

View File

@ -0,0 +1,91 @@
import React, { Component } from 'react';
import { Form, Select } from 'antd';
import { fetchData } from 'utils/Api';
const FormItem = Form.Item;
const Option = Select.Option;
class SelectForm extends Component {
async componentDidMount() {
// const { url } = this.props;
// const response = await fetchData(url);
// this.setState({
// options: response.data.data
// })
}
handleChange = (value) => {
const { setFieldValue } = this.props.form;
const { name } = this.props.field;
if(this.props.handleGetDate) {
this.props.handleGetDate(value);
this.props.handleAutoFillDeatils(value,setFieldValue,this.props);
}
// Add custom action `onChange`
return setFieldValue(name, value);
}
render() {
const children = [];
if (this.state && this.state.options) {
this.state.options.map((item, key) => {
children.push(<Option value={item.id.toString()}>{item.first_name}</Option>)
return item;
});
}
const {
field: { ...field },
form: { touched, errors, ...form },
layout,
label,
required,
optionFilterProp,
mode,
optionsList,
branchesOptionsTwo,
...props
} = this.props;
return (
<FormItem
{...layout}
required={required}
label={label}
style={{marginBottom: '10px'}}
validateStatus={touched[field.name] && errors[field.name] && 'error'}
help={touched[field.name] && errors[field.name]}
>
<Select
{...props}
// filterOption={
// optionFilterProp ?
// (input, option) => option.props.children.toLowerCase().indexOf(input.toLowerCase()) >= 0 : ''
// }
mode={mode}
onChange={this.handleChange}
children={children}
>
{
branchesOptionsTwo
?
branchesOptionsTwo.map(item => (
<Select.Option key={item} value={item}>
{item}
</Select.Option>
))
:
optionsList && (optionsList.map((item,i) => {
return <Option value={item.value} key={`${i}-${field.name}`}>{item.label}</Option>
}))
}
</Select>
</FormItem>
);
}
}
export default SelectForm;

View File

@ -0,0 +1,185 @@
import React, { Component } from 'react';
import { Form, Icon, Input, Upload, message } from 'antd';
import filesize from 'filesize';
const FormItem = Form.Item;
class BackgroundUploadImage extends Component {
constructor(props) {
super(props);
this.state = {
fileUpload: null,
loading: false,
hasError: false,
imageUrl: props.imageUrl
};
}
normFile = (info) => {
const { handleFileUpload } = this.props;
const isJPG = info.file.type === 'image/jpeg' || info.file.type === 'image/png' || info.file.type === 'image/gif' ;
if(isJPG && info.file.originFileObj) {
this.getBase64(info.file.originFileObj, imageUrl => this.setState({
imageUrl,
loading: false,
}));
let imageUrl = this.state.imageUrl;
this.props.form.setFieldValue(this.props.field.name, imageUrl);
// this.props.form.setFieldValue("logo", 'imageValue');
handleFileUpload(info,this.props.form.setFieldValue)
}
// if (Array.isArray(e)) {
// return this.setState({fileUpload: e});
// }
// return e && this.setState({fileUpload: e.fileList});
}
getBase64 =(img, callback)=> {
const reader = new FileReader();
reader.readAsDataURL(img);
reader.addEventListener('load', () => {
callback(reader.result)
this.props.form.setFieldValue(this.props.field.name, reader.result);
});
}
beforeUpload =(file)=> {
const {notAcceptedImg} = this.props;
const isJPG = file.type === 'image/jpeg' || file.type === 'image/png' || file.type === 'image/gif' ;
if (!isJPG) {
message.error('You can only upload JPG, PNG or GIF file!');
}
if(notAcceptedImg && notAcceptedImg.length > 0) {
notAcceptedImg.map(item => {
if(file.type == item) return message.error('You can only upload JPG or PNG file!');
})
}
let fileSize; let isLt2M;
if(this.props.limit100kb) {
fileSize = filesize(file.size, {output: "array"} ) // 100kb
isLt2M = fileSize[0] < 104 && fileSize[1] == "KB"
} else {
isLt2M = file.size / 1024 / 1024 < 2; // 2MB
}
//const isLt2M = fileSize[0] < 104;
//const isLt2M = file.size / 1024 / 1024 < 2;
if (!isLt2M) {
if(this.props.limit100kb) {
message.error('Image must smaller than 100KB!');
} else {
message.error('Image must smaller than 2MB!');
}
}
return isJPG && isLt2M;
}
render() {
const {
field: { ...field },
form: { touched, errors, ...form },
required,
icon,
layout,
withActionBtn,
action,
fileList,
messageUpload,
multipleFileUpload,
imgWidth,
imgStyle,
isRatioMessage,
notAcceptedImg,
...props
} = this.props;
let _props = {...props};
let _field = {...field};
const { onChange, onBlur, ...restField } = field;
const { fileUpload } =this.state;
// let props_list_image = {
// action: '',
// listType: 'picture',
// defaultFileList: [...fileList],
// className: 'upload-list-inline',
// };
const uploadButton = (
<div>
<Icon type={this.state.loading ? 'loading' : 'plus'} />
<div className="ant-upload-text">Upload</div>
</div>
);
return (
<FormItem
{...layout}
required={required}
label={props.label}
style={{marginBottom: '10px'}}
validateStatus={touched[field.name] && errors[field.name] && 'error'}
help={touched[field.name] && errors[field.name]}
>
{
multipleFileUpload ?
(
<Upload.Dragger
{..._props} {..._field}
//{...props_list_image}
onChange={this.normFile}
>
<p className="ant-upload-drag-icon">
<Icon type="inbox" />
</p>
<p className="ant-upload-text">Click or drag file to this area to upload</p>
<p className="ant-upload-hint">Support for a single or bulk upload.</p>
</Upload.Dragger>
) :
(
<Upload
style={{padding: '13px 22px'}}
name="avatar"
listType="picture-card"
className="avatar-uploader"
accept=".jpg , .png , .gif"
showUploadList={false}
notAcceptedImg={notAcceptedImg}
beforeUpload={this.beforeUpload}
onChange={this.normFile}
className="upload-image"
>
{this.state.imageUrl ? <img src={this.state.imageUrl} alt="avatar" width={imgStyle ? imgStyle.width : "100%" } height={imgStyle ? imgStyle.height : "135"} /> : uploadButton}
<div style={{width: imgWidth ? imgWidth : 'initial', margin: '0 auto'}}>
<p className="ant-upload-text">Click or drag file to this area to upload.</p>
<p className="ant-upload-hint">Support for a single upload only.</p>
{ isRatioMessage && <p className="ant-upload-hint">{isRatioMessage.isRatioMessage && isRatioMessage.isRatioMessage}</p> }
</div>
</Upload>
)
}
</FormItem>
);
}
}
export default BackgroundUploadImage;

View File

@ -0,0 +1,71 @@
import React from 'react';
import { Form, DatePicker, TimePicker } from 'antd';
import moment from 'moment';
const FormItem = Form.Item;
const { RangePicker } = DatePicker;
const TimePickerForm = ({
field: { ...field },
form: { touched, errors, handleSubmit, setFieldValue, handlePanelChange, ...form },
type,
layout,
label,
format,
minDateToday,
required,
isAutoFill,
...props
}) => {
const onDateChange = (value, isDateRange = false) => {
value && setFieldValue(field.name, isDateRange
? [value[0].format(format), value[1].format(format)]
: value.format(format)
)
if(value == null) {
setFieldValue(field.name, isDateRange
? [value[0].format(format), value[1].format(format)]
: null
)
}
}
// Disable date less than `Today`
// use minDateToday props
const disabledDate = (current) => {
if (minDateToday) {
var oneDay = (1 * 24 * 60 * 60 * 1000);
return current && (current.valueOf() < (Date.now() - oneDay));
}
}
let _props = {...props}; let _field = {...field};
if(_field.value !== "") {
_props.value = _field.value && moment(_field.value,format)
}
return (
<FormItem
{...layout}
required={required}
label={label}
style={{marginBottom: '10px'}}
validateStatus={touched[field.name] && errors[field.name] && 'error'}
help={touched[field.name] && errors[field.name]}
>
<TimePicker
{..._props}
onChange={(value) => onDateChange(value)}
format={format}
//disabledDate={disabledDate}
style={{width: '250px'}}
/>
</FormItem>
);
};
export default TimePickerForm;

View File

@ -0,0 +1,201 @@
import React, { Component } from 'react';
import { Form, Icon, Input, Upload, message } from 'antd';
import filesize from 'filesize';
const FormItem = Form.Item;
class UploadImage extends Component {
constructor(props) {
super(props);
this.state = {
fileUpload: null,
loading: false,
hasError: false,
imageUrl: props.imageUrl
};
}
normFile = (info) => {
const { handleFileUpload } = this.props;
const isJPG = info.file.type === 'image/jpeg' || info.file.type === 'image/png' || info.file.type === 'image/gif' ;
if(isJPG && info.file.originFileObj) {
this.getBase64(info.file.originFileObj, imageUrl => this.setState({
imageUrl,
loading: false,
}));
// if(this.props.isDefault) {
// this.props.form.setFieldValue("image", 'imageValue');
// } else {
// let imageUrl = this.state.imageUrl ? this.state.imageUrl : this.props.imageUrl;
// this.props.form.setFieldValue("image", imageUrl);
// }
this.props.form.setFieldValue("logo", 'imageValue');
handleFileUpload(info,this.props.form.setFieldValue)
}
// if (Array.isArray(e)) {
// return this.setState({fileUpload: e});
// }
// return e && this.setState({fileUpload: e.fileList});
}
getBase64 =(img, callback)=> {
const reader = new FileReader();
reader.readAsDataURL(img);
//reader.addEventListener('load', () => callback(reader.result));
reader.addEventListener('load', () => {
callback(reader.result)
this.props.form.setFieldValue(this.props.field.name, reader.result);
});
}
beforeUpload =(file)=> {
const isJPG = file.type === 'image/jpeg' || file.type === 'image/png' || file.type === 'image/gif' ;
if (!isJPG) {
message.error('You can only upload JPG, PNG or GIF file!');
}
let fileSize; let isLt2M;
if(this.props.limit100kb) {
fileSize = filesize(file.size, {output: "array"} ) // 100kb
isLt2M = fileSize[0] < 104 && fileSize[1] == "KB"
} else {
isLt2M = file.size / 1024 / 1024 < 2; // 2MB
}
//const isLt2M = fileSize[0] < 104;
//const isLt2M = file.size / 1024 / 1024 < 2;
if (!isLt2M) {
if(this.props.limit100kb) {
message.error('Image must smaller than 100KB!');
} else {
message.error('Image must smaller than 2MB!');
}
}
return isJPG && isLt2M;
}
render() {
const {
field: { ...field },
form: { touched, errors, ...form },
required,
icon,
layout,
withActionBtn,
action,
fileList,
messageUpload,
multipleFileUpload,
imgWidth,
imgStyle,
isRatioMessage,
...props
} = this.props;
let _props = {...props};
let _field = {...field};
const { onChange, onBlur, ...restField } = field;
const { fileUpload } =this.state;
// let props_list_image = {
// action: '',
// listType: 'picture',
// defaultFileList: [...fileList],
// className: 'upload-list-inline',
// };
const uploadButton = (
<div>
<Icon
style={{fontSize: '30px'}}
type={this.state.loading ? 'loading' : 'plus'}
/>
{/* <div className="ant-upload-text">Upload</div> */}
</div>
);
let imageUrl;
if(this.props.isDefault) {
imageUrl = this.state.imageUrl
} else {
imageUrl = this.props.imageUrl
}
return (
<FormItem
{...layout}
required={required}
label={props.label}
style={{marginBottom: '10px'}}
validateStatus={touched[field.name] && errors[field.name] && 'error'}
help={touched[field.name] && errors[field.name]}
>
{
multipleFileUpload ?
(
<Upload.Dragger
{..._props} {..._field}
//{...props_list_image}
onChange={this.normFile}
>
<p className="ant-upload-drag-icon">
<Icon type="inbox" />
</p>
<p className="ant-upload-text">Click or drag file to this area to upload</p>
<p className="ant-upload-hint">Support for a single or bulk upload.</p>
</Upload.Dragger>
) :
(
<Upload
style={{padding: '13px 22px'}}
name="avatar"
listType="picture-card"
className="avatar-uploader"
accept=".jpg , .png , .gif"
showUploadList={false}
beforeUpload={this.beforeUpload}
onChange={this.normFile}
className="upload-image"
>
{imageUrl ? <img src={imageUrl} alt="avatar" width={imgStyle ? imgStyle.width : "100%" } height={imgStyle ? imgStyle.height : "135"} /> : uploadButton}
<div style={{width: imgWidth ? imgWidth : 'initial', margin: '0 auto'}}>
<p
style={{fontWeight: 'bold',marginTop:'3px'}}
className="ant-upload-text"
>Click or drag file to this area to upload</p>
<p
style={{fontSize: '13px'}}
className="ant-upload-hint"
>Support for a single upload only</p>
{ isRatioMessage &&
<div className="ant-upload-hint">
{isRatioMessage.message ? isRatioMessage.message : "Aspect Ratio 4:3 (ex. 1024 x 768)"}
</div>
}
</div>
</Upload>
)
}
</FormItem>
);
}
}
export default UploadImage;

View File

@ -0,0 +1,33 @@
import Checkbox from './Checkbox';
import Inputs from './Inputs';
import Select from './Select';
import Cascader from './Cascader';
import DatePicker from './DatePicker';
import Radio from './Radio';
import InputPassword from './InputPassword';
import InputTextArea from './InputTextArea';
import UploadImage from './UploadImage';
import MultiSelectOptions from './MultiSelectOptions';
import InputMaskNumber from './InputMaskNumber';
import TimePickerForm from './TimePicker'
import InputNumberAntD from './InputNumberAntD'
import SingleUploadImage from './SingleUploadImage'
import MultiSelect from './MultiSelect'
export {
Checkbox,
Inputs,
Select,
Cascader,
DatePicker,
Radio,
InputPassword,
InputTextArea,
UploadImage,
MultiSelectOptions,
MultiSelect,
InputMaskNumber,
TimePickerForm,
InputNumberAntD,
SingleUploadImage
}

View File

@ -0,0 +1,16 @@
import { Icon } from 'antd';
import React from 'react';
const Loading = () => {
return (
<div style={{padding: 20, display: 'flex' , justifyContent: 'center' , marginLeft: '-144px'}}>
<div>
<Icon type="sync" spin /> Loading Data Please wait...
</div>
</div>
);
};
export default Loading;

View File

@ -0,0 +1,23 @@
import React from 'react'
import { Layout } from 'antd';
const { Header, Footer, Content, Sider } = Layout;
function LoginLayout({children, ...rest}) {
return (
<Layout style={{height: "100%"}}>
<Sider width='50%' style={{ background: `url(${require("assets/img/bg_cms.png")}) center`, backgroundSize: 'cover' } }></Sider>
<Layout>
<Content style={{padding: 16}} >{children}</Content>
<Footer style={{textAlign: 'center', fontSize: '12px'}}>
<div style={{margin: '25px auto', padding: '17px 0', width: '325px', borderTop: '1px solid #e0e0e0', textAlign: 'left', color: '#8E8E93' }}>
By logging in you agree to Unioil's Terms of Service, <br/>Privacy Policy and Content Policies.
</div>
</Footer>
</Layout>
</Layout>
)
}
export default LoginLayout

View File

@ -0,0 +1,16 @@
import React from 'react';
import LoginLayout from '../Layout'
import { Route } from 'react-router-dom'
const LoginLayoutRoute = ({component: Component, ...rest}) => {
return (
<Route {...rest} render={matchProps => (
<LoginLayout>
<Component {...matchProps} />
</LoginLayout>
)} />
)
};
export default LoginLayoutRoute

View File

@ -0,0 +1,84 @@
import React, { Component } from "react";
import { Modal, Button } from "antd";
import { Link } from 'react-router-dom';
class ModalCancel extends React.Component {
state = { visible: false };
showModal = () => {
this.setState({
visible: true
});
};
handleOk = e => {
console.log(e);
this.setState({
visible: false
});
};
handleCancel = e => {
console.log(e);
this.setState({
visible: false
});
};
render() {
const {
path,
title,
message,
id,
dirty,
name,
loading
} = this.props
if (dirty) {
return [
<Button
key="button"
disabled={loading}
onClick={this.showModal}>
{name}
</Button>,
<Modal
getContainer={() => document.getElementById(id)}
key="modal"
width={300}
title={title ? title : "Your Title"}
visible={this.state.visible}
onOk={this.handleOk}
onCancel={this.handleCancel}
footer={[
<Button key={1} type="primary" onClick={this.handleCancel}>No</Button>,
<Link key={2} to={path} style={{marginLeft: 10}}>
<Button >
Yes
</Button>
</Link>
]}
>
{message ? message : "Your Message"}
</Modal>
];
} else {
return <Link to={path} disabled={loading}>
<Button key="back" disabled={loading}>
{name}
</Button>
</Link>
}
}
}
export default ModalCancel;

View File

@ -0,0 +1,429 @@
import React, { Component } from 'react';
import { withRouter } from 'react-router-dom';
import queryString from 'query-string';
import { getCookie } from '../../utils/cookie';
import _ from 'lodash';
import {
Table,
Button,
Row,
Col,
Input,
Icon,
Pagination,
Tooltip,
notification,
Popconfirm,
message,
DatePicker,
} from 'antd';
import { DropdownExport } from 'components/Dropdown/index';
import { fnQueryParams } from 'utils/helper';
import { API_UNI_OIL, API_GET, API_DELETE } from 'utils/Api';
import { API_GET_NOTIF } from 'utils/NotificationApi';
import '../Tables/index.css';
const { RangePicker } = DatePicker;
class Index extends Component {
constructor(props) {
super(props);
this.state = {
data: null,
total: null,
loading: false,
selectedRowKeys: [],
columns: [],
search_filter: '',
visible: false,
mounted: false,
test: true,
updating: false,
};
this.delayFetchRequest = _.debounce(this.fetch, 500);
this.handleSearchChangeDebounce = _.debounce(this.handleSearchStateChange, 1000);
}
componentDidMount() {
this.setState({ mounted: true });
this.handleFilterChange({});
}
componentDidUpdate(prevProps, prevState, snapshot) {
if (prevState.updating !== prevProps.updating) {
this.setState({ updating: prevProps.updating });
}
}
shouldComponentUpdate(nextProps, nextState) {
if (nextProps.updating !== nextState.updating) {
this.handleFilterChange({});
return true;
}
return true;
}
componentWillMount() {
this.delayFetchRequest.cancel();
this.handleSearchChangeDebounce.cancel();
}
handleTableChange = (pagination, filters, sorter) => {
let _sort_order;
if (sorter.order) _sort_order = sorter.order === 'ascend' ? 'asc' : 'desc';
if (sorter.column) {
if (sorter.column.sortByValue) sorter.field = sorter.column.sortByValue;
}
this.handleFilterChange({
...filters,
_sort_by: sorter.field,
_sort_order,
});
};
handleSearchChange = (e) => {
this.setState({ search_filter: e.target.value });
this.handleSearchChangeDebounce(e.target.value);
};
handleSearchStateChange = (search_filter) => {
this.setState({ search_filter });
this.handleFilterChange({ search: this.state.search_filter });
};
onPaginationChange = (page, page_size) => {
console.log("page")
console.log(page)
this.handleFilterChange({ page });
};
handleFilterChange = (props, isClearFilter) => {
console.log(props)
this.setState({ loading: true });
let { history, location } = this.props;
let { search, pathname } = location;
let urlParamsObject = isClearFilter ? props : queryString.parse(search);
urlParamsObject = props ? { ...urlParamsObject,page: 1, ...props } : {};
urlParamsObject = fnQueryParams(urlParamsObject);
urlParamsObject = queryString.parse(urlParamsObject);
history.push({ pathname, search: fnQueryParams(urlParamsObject) });
console.log({ pathname, search: fnQueryParams(urlParamsObject) })
this.delayFetchRequest(urlParamsObject);
};
clearFilters = () => {
let { history, location } = this.props;
let { search, pathname } = location;
let urlParamsObject = queryString.parse(search);
delete urlParamsObject['search'];
Object.keys(urlParamsObject).map((key, index) => {
if (this.props.filterValues.includes(key)) delete urlParamsObject[key];
});
history.push({ pathname, search: fnQueryParams(urlParamsObject) });
this.handleFilterChange(urlParamsObject, true);
};
clearAll = () => {
this.setState({ search_filter: '' });
this.handleFilterChange();
};
fetch = async (params = {}) => {
let defaulUrl;
if (this.props.defaultFilter) {
params = {
...params,
...this.props.defaultFilter,
};
}
if (this.props.url.defaultWithFilter) {
defaulUrl = this.props.url.defaultWithFilter;
} else {
defaulUrl = this.props.url.default;
}
try {
let response, data, total;
if(defaulUrl == 'notification'){
console.log(defaulUrl, params)
response = await API_GET_NOTIF('notification', params);
console.log(response.data, params, 'response');
console.log(getCookie('TOKEN').token);
data = response.data.data.length > 0 ? response.data.data : null;
total = response.data.total
}
console.table(data, 'data');
this.setState({ data, total, loading: false });
if (data == null && this.props.isEmptyMessagePopUp) {
message.info('No records found.');
}
if (this.props.dataResponse) {
this.props.dataResponse(data.length);
}
} catch (error) {
this.setState({ loading: false, total: 0 });
console.log('An error encountered: ' + error);
}
};
update = async (params = {}) => {
notification.success({
message: 'Success',
description: `Delete Successful.`,
});
};
remove = async (params = {}) => {
notification.error({
message: 'Error',
description: `Error message.`,
});
};
delete = async (uuid) => {
console.log(uuid)
let search = this.props.location;
console.log(search.pathname)
let api = process.env.REACT_APP_STATION_API
let path = search.pathname.substring(1)
try {
await API_UNI_OIL.delete(`${api}${path}/${uuid}`);
this.handleFilterChange({});
message.success('Record was successfully deleted.');
} catch ({ response: error }) {
this.handleFilterChange({});
notification.error({
message: 'Something went wrong deleting record!',
description: (
<div>
<h3>
{error && error.data && error.data.message}
</h3>
</div>
),
duration: 4,
});
}
};
handleBatchDelete = async () => {
const data = { [this.props.keyValue]: this.state.selectedRowKeys };
this.setState({ selectedRowKeys: [] });
try {
// await API_UNI_OIL.delete(this.props.url.apiDelete, { data });
// this.handleFilterChange({});
// message.success('Record was successfully deleted.');
console.log(this.props.url.apiDelete)
} catch ({ response: error }) {
this.handleFilterChange({});
notification.error({
message: 'Error',
description: (
<div>
<div>Something went wrong deleting records.</div>
- {error && error.data && error.data.message}
</div>
),
duration: 3,
});
}
};
onSelectChange = (selectedRowKeys) => {
this.setState({ selectedRowKeys });
};
handleDateRangePicker = async (date, dateString) => {
this.handleFilterChange({
date_start: dateString[0],
date_end: dateString[1],
});
};
render() {
if (!this.state.mounted) return null;
const { loading, selectedRowKeys } = this.state;
const rowSelection = {
selectedRowKeys,
onChange: this.onSelectChange,
getCheckboxProps: (record) => ({
disabled: record.editable == false, // Column configuration not to be checked
//name: record.name,
}),
};
const hasSelected = selectedRowKeys.length > 0;
let { history, keyValue, location, url: { apiDelete } } = this.props;
let { search } = this.props.location;
let urlParamsObject = queryString.parse(search);
let { _sort_order } = urlParamsObject;
if (_sort_order) _sort_order = _sort_order === 'asc' ? 'ascend' : 'descend';
const columns = this.props.columns.map((data) => {
if (data.dataIndex === 'action') {
return {
...data,
render: (text, record) =>
data.buttons.map((action) => {
let actionBtn;
if(action.key == 'location'){
actionBtn = () => this.props.locationData(record)
}
if(action.key == 'edit'){
actionBtn = () => history.push({ pathname: `${location.pathname}/view/${record.id}` });
}
if (action.key == 'delete') {
actionBtn = action.action;
if (record.editable == false) {
return;
} else {
return (
<Popconfirm
placement='bottomRight'
key={action.key}
title={'Are you sure you want to delete this record?'}
onConfirm={() => this.delete(record.id)}
okText='Yes'
cancelText='No'
icon={<Icon type='close-circle' />}
>
<Tooltip key={action.key} placement='top' title={action.title}>
<Icon
type={action.icon}
style={{
padding: '5px 14px 5px 0',
color: 'rgb(231, 70, 16)',
cursor: 'pointer',
}}
/>
</Tooltip>
</Popconfirm>
);
}
}
return (
<Tooltip key={action.key} placement='top' title={action.title}>
<Icon
type={action.icon}
style={{
padding: '5px 14px 5px 0',
color: 'rgb(231, 70, 16)',
cursor: 'pointer',
}}
onClick={actionBtn}
/>
</Tooltip>
);
}),
};
}
let filteredValue = null;
if (Array.isArray(urlParamsObject[data.dataIndex])) {
filteredValue = urlParamsObject[data.dataIndex];
} else if (urlParamsObject[data.dataIndex]) {
filteredValue = [ urlParamsObject[data.dataIndex] ];
}
return {
...data,
filteredValue,
sortOrder: data.sorter ? urlParamsObject._sort_by === data.dataIndex && _sort_order : null,
};
});
return (
<div style={{ margin: '0 24px', padding: '24px 0' }}>
<Row type='flex' justify='space-between' align='bottom' style={{ paddingBottom: 25 }}>
<Col>
{this.props.url.csv ? (
<RangePicker onChange={this.handleDateRangePicker} />
) : (
<Input
onChange={this.handleSearchChange}
style={{ width: 300 }}
value={this.state.search_filter}
prefix={<Icon type='search' style={{ color: 'rgba(0,0,0,.25)' }} />}
type='text'
placeholder='Search'
/>
)}
</Col>
<Col className='table-operations'>
{/* <Button onClick = {this.clearFilters}><b>Clear filters</b></Button>*/}
<Button onClick={this.clearAll}>Clear filters</Button>
{this.props.url.csv && <DropdownExport defaultFilter={this.props.defaultFilter} url={this.props.url.csv} />}
</Col>
</Row>
<Table
size='middle'
rowSelection={apiDelete && rowSelection}
columns={columns}
dataSource={this.state.data ? this.state.data : null}
pagination={false}
rowKey={(record) => record[this.props.keyValue]}
onChange={this.handleTableChange}
loading={loading}
/>
<Row type='flex' justify='space-between' style={{ marginTop: 20 }}>
<Col>
{apiDelete && (
<div>
<Popconfirm
placement='top'
title={'Are you sure you want to delete this record?'}
onConfirm={this.handleBatchDelete}
okText='Yes'
cancelText='No'
icon={<Icon type='close-circle' />}
>
<Button type='danger' disabled={!hasSelected} icon='delete' loading={loading}>
Delete
</Button>
<span style={{ marginLeft: 8 }}>
{hasSelected ? `Selected ${selectedRowKeys.length} item(s)` : ''}
</span>
</Popconfirm>
</div>
)}
</Col>
<Col>
{this.state.total > 0 ? (
<Pagination
style={{ float: 'right' }}
showSizeChanger
defaultCurrent={parseInt(urlParamsObject.page, 10) || 1}
defaultPageSize={parseInt(urlParamsObject.page_size, 10) || 10}
pageSizeOptions={[ '10','20']}
total={this.state.total}
showTotal={(total, range) =>
`Showing ${this.state.total > 0 ? range[0] : 0}-${this.state.total > 0 ? range[1] : 0} of ${this.state
.total > 0
? total
: 0}`}
onChange={this.onPaginationChange}
onShowSizeChange={this.onPaginationChange}
/>
) : null}
</Col>
</Row>
</div>
);
}
}
export default withRouter(Index);

View File

@ -0,0 +1,17 @@
import React, { Fragment } from 'react';
import { withRouter} from 'react-router-dom'
function Page404(){
const error = "Page not found";
const errorMessage = "Sorry, but the page you are looking for doesn't exist";
return <Fragment>
<div align="center" style={{ marginTop: "10%" }}>
<h1 style={{ fontSize: 150, fontWeight: 'bold', margin: 0 }}>404</h1>
<p style={{ fontSize: 30, fontWeight: 'bold', margin: 0 }}>{error}</p>
<p>{errorMessage}</p>
</div>
</Fragment>
}
export default withRouter(Page404);

View File

@ -0,0 +1,18 @@
import React, { Fragment } from 'react';
import { Link } from "react-router-dom";
function PageError(){
const error = "Forbidden";
const errorMessage = "Access to this resources on the server is denied!"
return <Fragment>
<div align="center" style={{ marginTop: "10%" }}>
<h1 style={{ fontSize: 150, fontWeight: 'bold', margin: 0 }}>403</h1>
<p style={{ fontSize: 30, fontWeight: 'bold', margin: 0 }}>{error}</p>
<p>{errorMessage}</p>
<Link to={'/locations'}>Go Back</Link>
</div>
</Fragment>
}
export default PageError;

View File

@ -0,0 +1,17 @@
import React, { Fragment } from 'react';
import { withRouter} from 'react-router-dom'
function Page404(){
const error = "Page not found";
const errorMessage = "Sorry, but the page you are looking for doesn't exist";
return <Fragment>
<div align="center">
<h1 style={{ fontSize: 150, fontWeight: 'bold', margin: 0 }}>404</h1>
<p style={{ fontSize: 30, fontWeight: 'bold', margin: 0 }}>{error}</p>
<p>{errorMessage}</p>
</div>
</Fragment>
}
export default withRouter(Page404);

View File

@ -0,0 +1,2 @@
export { default as PAGE403 } from "./403";
export { default as PAGE404 } from "./404";

View File

@ -0,0 +1,445 @@
import React, { Component } from 'react';
import { withRouter } from 'react-router-dom';
import queryString from 'query-string';
import { getCookie } from '../../utils/cookie';
import _ from 'lodash';
import {
Table,
Button,
Row,
Col,
Input,
Icon,
Pagination,
Tooltip,
notification,
Popconfirm,
message,
DatePicker,
} from 'antd';
import { DropdownExport } from 'components/Dropdown/index';
import { fnQueryParams } from 'utils/helper';
import { API_UNI_OIL, API_GET, API_DELETE } from 'utils/Api';
import { API_GET_BRANCH, API_GET_FUELS } from 'utils/StationApi';
import '../Tables/index.css';
const { RangePicker } = DatePicker;
class Index extends Component {
constructor(props) {
super(props);
this.state = {
data: null,
total: null,
loading: false,
selectedRowKeys: [],
columns: [],
search_filter: '',
visible: false,
mounted: false,
test: true,
updating: false,
};
this.delayFetchRequest = _.debounce(this.fetch, 500);
this.handleSearchChangeDebounce = _.debounce(this.handleSearchStateChange, 1000);
}
componentDidMount() {
this.setState({ mounted: true });
this.handleFilterChange({});
}
componentDidUpdate(prevProps, prevState, snapshot) {
if (prevState.updating !== prevProps.updating) {
this.setState({ updating: prevProps.updating });
}
}
shouldComponentUpdate(nextProps, nextState) {
if (nextProps.updating !== nextState.updating) {
this.handleFilterChange({});
return true;
}
return true;
}
componentWillMount() {
this.delayFetchRequest.cancel();
this.handleSearchChangeDebounce.cancel();
}
handleTableChange = (pagination, filters, sorter) => {
let _sort_order;
if (sorter.order) _sort_order = sorter.order === 'ascend' ? 'asc' : 'desc';
if (sorter.column) {
if (sorter.column.sortByValue) sorter.field = sorter.column.sortByValue;
}
this.handleFilterChange({
...filters,
_sort_by: sorter.field,
_sort_order,
});
};
handleSearchChange = (e) => {
this.setState({ search_filter: e.target.value });
this.handleSearchChangeDebounce(e.target.value);
};
handleSearchStateChange = (search_filter) => {
this.setState({ search_filter });
this.handleFilterChange({ search: this.state.search_filter });
};
onPaginationChange = (page, page_size) => {
console.log(page)
this.handleFilterChange({ page });
};
handleFilterChange = (props, isClearFilter) => {
console.log(props)
this.setState({ loading: true });
let { history, location } = this.props;
let { search, pathname } = location;
let urlParamsObject = isClearFilter ? props : queryString.parse(search);
urlParamsObject = props ? { ...urlParamsObject,page: 1, ...props } : {};
urlParamsObject = fnQueryParams(urlParamsObject);
urlParamsObject = queryString.parse(urlParamsObject);
history.push({ pathname, search: fnQueryParams(urlParamsObject) });
console.log({ pathname, search: fnQueryParams(urlParamsObject) })
this.delayFetchRequest(urlParamsObject);
};
clearFilters = () => {
let { history, location } = this.props;
let { search, pathname } = location;
let urlParamsObject = queryString.parse(search);
delete urlParamsObject['search'];
Object.keys(urlParamsObject).map((key, index) => {
if (this.props.filterValues.includes(key)) delete urlParamsObject[key];
});
history.push({ pathname, search: fnQueryParams(urlParamsObject) });
this.handleFilterChange(urlParamsObject, true);
};
clearAll = () => {
this.setState({ search_filter: '' });
this.handleFilterChange();
};
fetch = async (params = {}) => {
let defaulUrl;
if (this.props.defaultFilter) {
params = {
...params,
...this.props.defaultFilter,
};
}
if (this.props.url.defaultWithFilter) {
defaulUrl = this.props.url.defaultWithFilter;
} else {
defaulUrl = this.props.url.default;
}
try {
let response, data, total;
if(defaulUrl == 'branches'){
console.log(defaulUrl, params)
response = await API_GET_BRANCH('branches', params);
console.log(response.data, params, 'response ng branches');
console.log(getCookie('TOKEN').token);
data = response.data.data.length > 0 ? response.data.data : null;
total = response.data.total
}
if(defaulUrl == 'fuels'){
console.log(defaulUrl, params)
response = await API_GET_FUELS('fuels', params)
console.log(response, 'fuels');
console.log(getCookie('TOKEN').token);
data = response.data.data.length > 0 ? response.data.data : null;
total = response.data.total
console.log(data, 'fuel data')
}
if(defaulUrl == 'location'){
console.log(defaulUrl, params)
response = await API_GET_BRANCH('stations', params);
console.log(response);
console.log(getCookie('TOKEN').token);
data = response.data.data.length > 0 ? response.data.data : null;
total = response.data.total
}
console.table(data, 'data');
this.setState({ data, total, loading: false });
if (data == null && this.props.isEmptyMessagePopUp) {
message.info('No records found.');
}
if (this.props.dataResponse) {
this.props.dataResponse(data.length);
}
} catch (error) {
this.setState({ loading: false, total: 0 });
console.log('An error encountered: ' + error);
}
};
update = async (params = {}) => {
notification.success({
message: 'Success',
description: `Delete Successful.`,
});
};
remove = async (params = {}) => {
notification.error({
message: 'Error',
description: `Error message.`,
});
};
delete = async (uuid) => {
console.log(uuid)
let search = this.props.location;
console.log(search.pathname)
let api = process.env.REACT_APP_STATION_API
let path = search.pathname.substring(1)
try {
await API_UNI_OIL.delete(`${api}${path}/${uuid}`);
this.handleFilterChange({});
message.success('Record was successfully deleted.');
} catch ({ response: error }) {
this.handleFilterChange({});
notification.error({
message: 'Something went wrong deleting record!',
description: (
<div>
<h3>
{error && error.data && error.data.message}
</h3>
</div>
),
duration: 4,
});
}
};
handleBatchDelete = async () => {
const data = { [this.props.keyValue]: this.state.selectedRowKeys };
this.setState({ selectedRowKeys: [] });
try {
// await API_UNI_OIL.delete(this.props.url.apiDelete, { data });
// this.handleFilterChange({});
// message.success('Record was successfully deleted.');
console.log(this.props.url.apiDelete)
} catch ({ response: error }) {
this.handleFilterChange({});
notification.error({
message: 'Error',
description: (
<div>
<div>Something went wrong deleting records.</div>
- {error && error.data && error.data.message}
</div>
),
duration: 3,
});
}
};
onSelectChange = (selectedRowKeys) => {
this.setState({ selectedRowKeys });
};
handleDateRangePicker = async (date, dateString) => {
this.handleFilterChange({
date_start: dateString[0],
date_end: dateString[1],
});
};
render() {
if (!this.state.mounted) return null;
const { loading, selectedRowKeys } = this.state;
const rowSelection = {
selectedRowKeys,
onChange: this.onSelectChange,
getCheckboxProps: (record) => ({
disabled: record.editable == false, // Column configuration not to be checked
//name: record.name,
}),
};
const hasSelected = selectedRowKeys.length > 0;
let { history, keyValue, location, url: { apiDelete } } = this.props;
let { search } = this.props.location;
let urlParamsObject = queryString.parse(search);
let { _sort_order } = urlParamsObject;
if (_sort_order) _sort_order = _sort_order === 'asc' ? 'ascend' : 'descend';
const columns = this.props.columns.map((data) => {
if (data.dataIndex === 'action') {
return {
...data,
render: (text, record) =>
data.buttons.map((action) => {
let actionBtn;
if(action.key == 'location'){
actionBtn = () => this.props.locationData(record)
}
if(action.key == 'edit'){
actionBtn = () => history.push({ pathname: `${location.pathname}/view/${record.id}` });
}
if (action.key == 'delete') {
actionBtn = action.action;
if (record.editable == false) {
return;
} else {
return (
<Popconfirm
placement='bottomRight'
key={action.key}
title={'Are you sure you want to delete this record?'}
onConfirm={() => this.delete(record.id)}
okText='Yes'
cancelText='No'
icon={<Icon type='close-circle' />}
>
<Tooltip key={action.key} placement='top' title={action.title}>
<Icon
type={action.icon}
style={{
padding: '5px 14px 5px 0',
color: 'rgb(231, 70, 16)',
cursor: 'pointer',
}}
/>
</Tooltip>
</Popconfirm>
);
}
}
return (
<Tooltip key={action.key} placement='top' title={action.title}>
<Icon
type={action.icon}
style={{
padding: '5px 14px 5px 0',
color: 'rgb(231, 70, 16)',
cursor: 'pointer',
}}
onClick={actionBtn}
/>
</Tooltip>
);
}),
};
}
let filteredValue = null;
if (Array.isArray(urlParamsObject[data.dataIndex])) {
filteredValue = urlParamsObject[data.dataIndex];
} else if (urlParamsObject[data.dataIndex]) {
filteredValue = [ urlParamsObject[data.dataIndex] ];
}
return {
...data,
filteredValue,
sortOrder: data.sorter ? urlParamsObject._sort_by === data.dataIndex && _sort_order : null,
};
});
return (
<div style={{ margin: '0 24px', padding: '24px 0' }}>
<Row type='flex' justify='space-between' align='bottom' style={{ paddingBottom: 25 }}>
<Col>
{this.props.url.csv ? (
<RangePicker onChange={this.handleDateRangePicker} />
) : (
<Input
onChange={this.handleSearchChange}
style={{ width: 300 }}
value={this.state.search_filter}
prefix={<Icon type='search' style={{ color: 'rgba(0,0,0,.25)' }} />}
type='text'
placeholder='Search'
/>
)}
</Col>
<Col className='table-operations'>
{/* <Button onClick = {this.clearFilters}><b>Clear filters</b></Button>*/}
<Button onClick={this.clearAll}>Clear filters</Button>
{this.props.url.csv && <DropdownExport defaultFilter={this.props.defaultFilter} url={this.props.url.csv} />}
</Col>
</Row>
<Table
size='middle'
rowSelection={apiDelete && rowSelection}
columns={columns}
dataSource={this.state.data ? this.state.data : null}
pagination={false}
rowKey={(record) => record[this.props.keyValue]}
onChange={this.handleTableChange}
loading={loading}
/>
<Row type='flex' justify='space-between' style={{ marginTop: 20 }}>
<Col>
{apiDelete && (
<div>
<Popconfirm
placement='top'
title={'Are you sure you want to delete this record?'}
onConfirm={this.handleBatchDelete}
okText='Yes'
cancelText='No'
icon={<Icon type='close-circle' />}
>
<Button type='danger' disabled={!hasSelected} icon='delete' loading={loading}>
Delete
</Button>
<span style={{ marginLeft: 8 }}>
{hasSelected ? `Selected ${selectedRowKeys.length} item(s)` : ''}
</span>
</Popconfirm>
</div>
)}
</Col>
<Col>
{this.state.total > 0 ? (
<Pagination
style={{ float: 'right' }}
showSizeChanger
defaultCurrent={parseInt(urlParamsObject.page, 10) || 1}
defaultPageSize={parseInt(urlParamsObject.page_size, 10) || 10}
pageSizeOptions={[ '10']}
total={this.state.total}
showTotal={(total, range) =>
`Showing ${this.state.total > 0 ? range[0] : 0}-${this.state.total > 0 ? range[1] : 0} of ${this.state
.total > 0
? total
: 0}`}
onChange={this.onPaginationChange}
onShowSizeChange={this.onPaginationChange}
/>
) : null}
</Col>
</Row>
</div>
);
}
}
export default withRouter(Index);

View File

@ -0,0 +1,324 @@
import React, { Component, Fragment } from 'react';
import moment from 'moment';
import { Table, Button, Popconfirm, Popover, message,
Pagination, Input ,Row, Col , Select, Form, Icon,
DatePicker, Collapse, notification } from 'antd';
import { withRouter } from "react-router-dom";
import queryString from 'query-string';
//import { fetchAllItem , jsonServerApi } from 'utils/Api';
import { fnQueryParams } from "utils/helper";
const Search = Input.Search;
const RangePicker = DatePicker.RangePicker;
const Panel = Collapse.Panel;
class TableLayout extends Component {
state = {
data: [],
total: 0,
loading: false,
columns: null,
selectedRowKeys: []
};
componentDidMount() {
this.renderColumns();
this.fetch();
}
onPaginationChange = (page, page_size) => {
let _page = page, _limit = page_size;
this.fnFilterChange({_page, _limit});
this.fetch({_page, _limit});
}
fnFilterChange = (props) => {
let { location, history } = this.props;
let { pathname, search } = location;
let values = queryString.parse(search);
let params = { ...values, ...props };
history.push({ pathname, search:fnQueryParams(params) });
}
fetch = async (params = {}) => {
let values = queryString.parse(this.props.location.search); //get parameters
if(params) {
if(params._page) values = params;
}
const { api } = this.props;
this.setState({ loading: true });
let url = api.default;
// let asysnData = await fetchAllItem(url, {
// ...values
// });
// if(asysnData) {
// this.setState({
// loading: false,
// data: asysnData.data,
// total: asysnData.headers['x-total-count'],
// search: values.search
// });
// }
}
remove = async (params={}) => {
let { path, uuid } = params
try {
// const response = await jsonServerApi.delete(`${path}/${uuid}`);
// if(response) {
// this.fetch()
// notification.success({
// message: 'Success',
// description: `Delete Successfull.`,
// })
// }
} catch (error) {
if(error) {
notification.error({
message: 'Error',
description: `Error response message here.`,
})
}
}
}
actionHandler = props => {
let { path, uuid, action, data } = props;
let { history, location } = this.props;
let { search, pathname } = location;
if(action === 'update') {
history.push({
pathname: `${path}/${uuid}`,
state: {
initialValues: data
}
});
}
if(action === 'delete') {
//this.remove({path, uuid});
}
if(action === 'view') {
history.push({
pathname: `${path}/${uuid}`,
state: {
prevPath:`${pathname}${search}`,
initialValues: data
}
});
}
}
confirm(items,id) {
this.remove({path: items.path, uuid: id });
}
cancel(e) {
message.error('You Click Cancel.');
}
onChange = (dates, dateStrings) => {
console.log('From: ', dates[0], ', to: ', dates[1]);
console.log('From: ', dateStrings[0], ', to: ', dateStrings[1]);
}
renderColumns = () => {
const { headers,actions,keyValue } = this.props;
let columns = headers;
headers.map((item, index) => {
if(item.renderActions) {
item['render'] = (text, record) => {
return item.renderActions.map((itemAction,key) => {
if(itemAction.action_name) {
return (
<span key={key} style={{marginRight: '10px'}}>
{
(itemAction.action || itemAction.action_name == "delete")
?
<Popconfirm
title="Are you sure you want to delete this record?"
content={itemAction.message}
onConfirm={
()=> this.confirm(itemAction, record[keyValue])
}
onCancel={()=> this.cancel()} okText="Yes" cancelText="Cancel"
>
<Button
icon={itemAction.icon}
type="danger"
>
</Button>
</Popconfirm>
:
<Popover
title={itemAction.action_name}
content={itemAction.action_message}
trigger="hover"
>
<Button
onClick={
()=> this.actionHandler({
path: itemAction.path,
uuid: record[keyValue],
action: itemAction.action_name,
data: record
})
}
icon={itemAction.icon}
type="primary"
/>
</Popover>
}
</span>
)
}
})
}
}
})
return this.setState({ columns: columns })
}
onDeleteHandler = () => {
this.setState({ loading: true });
// ajax request after empty completing
setTimeout(() => {
this.setState((prevState, props) => ({
selectedRowKeys: [],
loading: false,
}));
this.fetch();
}, 1000);
}
onSelectChange = (selectedRowKeys) => {
this.setState({ selectedRowKeys });
}
onFilterSearch = (val) => {
console.log('Received values of form: ', val);
this.fnFilterChange(val);
}
render() {
const { keyValue,scrollbale, advanceFilter:AdvancedSearchForm } = this.props;
const { loading,selectedRowKeys } = this.state;
const rowSelection = { selectedRowKeys, onChange: this.onSelectChange };
const hasSelected = selectedRowKeys.length > 0;
// VALUES FROM PARAMS URL
let values = queryString.parse(this.props.location.search);
return (
<Fragment>
<Collapse style={{marginBottom: 16}}>
<Panel header="Advance Filter" key="1">
<Collapse defaultActiveKey="1" style={{padding: 16}}>
<AdvancedSearchForm onSubmit={this.onFilterSearch}/>
</Collapse>
</Panel>
</Collapse>
<Row gutter={16}>
<Col span={8} >
<h5>Date Range :</h5>
<RangePicker
ranges={{ Today: [moment(), moment()], 'This Month': [moment(), moment().endOf('month')] }}
onChange={this.onChange}
/>
</Col>
<Col span={8} >
<h5>Date</h5>
<DatePicker onChange={this.onChange} />
</Col>
<Col span={8} offset={16} style={{marginBottom: 16}}>
<span>Search :</span>
<Search
defaultValue={values.search}
onSearch={(val)=> this.fnFilterChange({search: val})}
placeholder="Input search text"
enterButton="Search"
label={'Search'}
/>
</Col>
</Row>
<div style={{ marginBottom: 16, marginTop: 16 }}>
<Button
type="danger"
onClick={this.onDeleteHandler}
disabled={!hasSelected}
icon="delete"
loading={loading}
>
Delete All
</Button>
<span style={{ marginLeft: 8 }}>
{hasSelected ? `Selected ${selectedRowKeys.length} items` : ''}
</span>
</div>
<Table
columns={this.state.columns}
dataSource={this.state.data}
rowKey={record => record[keyValue]}
pagination={false}
loading={this.state.loading}
rowSelection={rowSelection}
scroll={scrollbale ? scrollbale : {x: 0, y: 0}}
/>
{
this.state.total > 0
?
<Pagination
style={{float: 'right' , marginTop: 16}}
showSizeChanger
showQuickJumper
defaultCurrent={parseInt(values._page) || 1}
defaultPageSize={parseInt(values._limit) || 10}
pageSizeOptions={['5','10','15','20']}
total={parseInt(this.state.total)}
showTotal={(total, range) => `${range[0]}-${range[1]} of ${total} items`}
onChange={this.onPaginationChange}
onShowSizeChange={this.onPaginationChange}
/>
: null
}
</Fragment>
);
}
}
export default withRouter(TableLayout);

View File

@ -0,0 +1,427 @@
import React, { Component } from 'react';
import { withRouter } from 'react-router-dom';
import queryString from 'query-string';
import { getCookie } from '../../utils/cookie';
import _ from 'lodash';
import {
Table,
Button,
Row,
Col,
Input,
Icon,
Pagination,
Tooltip,
notification,
Popconfirm,
message,
DatePicker,
} from 'antd';
import { DropdownExport } from 'components/Dropdown/index';
import { fnQueryParams } from 'utils/helper';
import { API_UNI_OIL, API_GET, API_DELETE } from 'utils/Api';
import { API_GET_NOTIF, API_CREATE_NOTIF } from 'utils/NotificationApi';
import './index.css';
const { RangePicker } = DatePicker;
class AdvanceTable extends Component {
constructor(props) {
super(props);
this.state = {
data: null,
total: null,
loading: false,
selectedRowKeys: [],
columns: [],
search_filter: '',
mounted: false,
test: true,
updating: false,
};
this.delayFetchRequest = _.debounce(this.fetch, 500);
this.handleSearchChangeDebounce = _.debounce(this.handleSearchStateChange, 1000);
}
componentDidMount() {
this.setState({ mounted: true });
this.handleFilterChange({});
}
componentDidUpdate(prevProps, prevState, snapshot) {
if (prevState.updating !== prevProps.updating) {
this.setState({ updating: prevProps.updating });
}
}
shouldComponentUpdate(nextProps, nextState) {
if (nextProps.updating !== nextState.updating) {
this.handleFilterChange({});
return true;
}
return true;
}
componentWillMount() {
this.delayFetchRequest.cancel();
this.handleSearchChangeDebounce.cancel();
}
handleTableChange = (pagination, filters, sorter) => {
let _sort_order;
if (sorter.order) _sort_order = sorter.order === 'ascend' ? 'asc' : 'desc';
if (sorter.column) {
if (sorter.column.sortByValue) sorter.field = sorter.column.sortByValue;
}
this.handleFilterChange({
...filters,
_sort_by: sorter.field,
_sort_order,
});
};
handleSearchChange = (e) => {
this.setState({ search_filter: e.target.value });
this.handleSearchChangeDebounce(e.target.value);
};
handleSearchStateChange = (search_filter) => {
this.setState({ search_filter });
this.handleFilterChange({ _search: this.state.search_filter });
};
onPaginationChange = (page, page_size) => {
this.handleFilterChange({ page, page_size });
};
handleFilterChange = (props, isClearFilter) => {
this.setState({ loading: true });
console.log(props)
let { history, location } = this.props;
let { search, pathname } = location;
let urlParamsObject = isClearFilter ? props : queryString.parse(search);
urlParamsObject = props ? { ...urlParamsObject, page: 1, ...props } : {};
urlParamsObject = fnQueryParams(urlParamsObject);
urlParamsObject = queryString.parse(urlParamsObject);
history.push({ pathname, search: fnQueryParams(urlParamsObject) });
this.delayFetchRequest(urlParamsObject);
};
clearFilters = () => {
let { history, location } = this.props;
let { search, pathname } = location;
let urlParamsObject = queryString.parse(search);
delete urlParamsObject['_search'];
Object.keys(urlParamsObject).map((key, index) => {
if (this.props.filterValues.includes(key)) delete urlParamsObject[key];
});
history.push({ pathname, search: fnQueryParams(urlParamsObject) });
this.handleFilterChange(urlParamsObject, true);
};
clearAll = () => {
this.setState({ search_filter: '' });
this.handleFilterChange();
};
fetch = async (params = {}) => {
let defaulUrl;
if (this.props.defaultFilter) {
params = {
...params,
...this.props.defaultFilter,
};
}
if (this.props.url.defaultWithFilter) {
defaulUrl = this.props.url.defaultWithFilter;
} else {
defaulUrl = this.props.url.default;
}
try {
let response, data, total;
if(defaulUrl == 'notifications'){
response = await API_GET_NOTIF(defaulUrl, params);
console.log(response, 'response');
console.log(getCookie('TOKEN').token);
data = response.data.length > 0 ? response.data : null;
total = response.data.length > 0 ? response.data.total : 0;
}
else {
response = await API_GET(defaulUrl, params);
console.log(response, 'response');
console.log(getCookie('TOKEN').token);
data = response.data.data.length > 0 ? response.data.data : null;
total = response.data.data.length > 0 ? response.data.data.total : 0;
}
console.table(data, 'data');
// console.table(total, 'total');
this.setState({ data, total, loading: false });
if (data == null && this.props.isEmptyMessagePopUp) {
message.info('No records found.');
}
if (this.props.dataResponse) {
this.props.dataResponse(data.length);
}
} catch (error) {
this.setState({ loading: false, total: 0 });
console.log('An error encountered: ' + error);
}
};
update = async (params = {}) => {
notification.success({
message: 'Success',
description: `Delete Successful.`,
});
};
remove = async (params = {}) => {
notification.error({
message: 'Error',
description: `Error message.`,
});
};
delete = async (uuid) => {
try {
await API_UNI_OIL.delete(`${this.props.url.default}/${uuid}`);
this.handleFilterChange({});
message.success('Record was successfully deleted.');
} catch ({ response: error }) {
this.handleFilterChange({});
notification.error({
message: 'Error',
description: (
<div>
<div>Something went wrong deleting record.</div>
- {error && error.data && error.data.message}
</div>
),
duration: 3,
});
}
};
handleBatchDelete = async () => {
const data = { [this.props.keyValue]: this.state.selectedRowKeys };
this.setState({ selectedRowKeys: [] });
try {
await API_UNI_OIL.delete(this.props.url.apiDelete, { data });
this.handleFilterChange({});
message.success('Record was successfully deleted.');
} catch ({ response: error }) {
this.handleFilterChange({});
notification.error({
message: 'Error',
description: (
<div>
<div>Something went wrong deleting records.</div>
- {error && error.data && error.data.message}
</div>
),
duration: 3,
});
}
};
onSelectChange = (selectedRowKeys) => {
this.setState({ selectedRowKeys });
};
handleDateRangePicker = async (date, dateString) => {
this.handleFilterChange({
date_start: dateString[0],
date_end: dateString[1],
});
};
render() {
if (!this.state.mounted) return null;
const { loading, selectedRowKeys } = this.state;
const rowSelection = {
selectedRowKeys,
onChange: this.onSelectChange,
getCheckboxProps: (record) => ({
disabled: record.editable == false, // Column configuration not to be checked
//name: record.name,
}),
};
const hasSelected = selectedRowKeys.length > 0;
let { history, keyValue, location, url: { apiDelete } } = this.props;
let { search } = this.props.location;
let urlParamsObject = queryString.parse(search);
let { _sort_order } = urlParamsObject;
if (_sort_order) _sort_order = _sort_order === 'asc' ? 'ascend' : 'descend';
const columns = this.props.columns.map((data) => {
if (data.dataIndex === 'action') {
return {
...data,
render: (text, record) =>
data.buttons.map((action) => {
let actionBtn;
if (action.key == 'edit')
actionBtn = () => history.push({ pathname: `${location.pathname}/edit/${record[keyValue]}` });
if (action.key == 'view')
actionBtn = () => history.push({ pathname: `${location.pathname}/view/${record[keyValue]}` });
if (action.key == 'delete') {
actionBtn = action.action;
if (record.editable == false) {
return;
} else {
return (
<Popconfirm
placement='bottomRight'
key={action.key}
title={'Are you sure you want to delete this record?'}
onConfirm={() => this.delete(record[keyValue])}
okText='Yes'
cancelText='No'
icon={<Icon type='close-circle' />}
>
<Tooltip key={action.key} placement='top' title={action.title}>
<Icon
type={action.icon}
style={{
padding: '5px 14px 5px 0',
color: 'rgb(231, 70, 16)',
cursor: 'pointer',
}}
/>
</Tooltip>
</Popconfirm>
);
}
}
return (
<Tooltip key={action.key} placement='top' title={action.title}>
<Icon
type={action.icon}
style={{
padding: '5px 14px 5px 0',
color: 'rgb(231, 70, 16)',
cursor: 'pointer',
}}
//onClick={ this.update }
onClick={actionBtn}
/>
</Tooltip>
);
}),
};
}
let filteredValue = null;
if (Array.isArray(urlParamsObject[data.dataIndex])) {
filteredValue = urlParamsObject[data.dataIndex];
} else if (urlParamsObject[data.dataIndex]) {
filteredValue = [ urlParamsObject[data.dataIndex] ];
}
return {
...data,
filteredValue,
sortOrder: data.sorter ? urlParamsObject._sort_by === data.dataIndex && _sort_order : null,
};
});
return (
<div style={{ margin: '0 24px', padding: '24px 0' }}>
<Row type='flex' justify='space-between' align='bottom' style={{ paddingBottom: 25 }}>
<Col>
{this.props.url.csv ? (
<RangePicker onChange={this.handleDateRangePicker} />
) : (
<Input
onChange={this.handleSearchChange}
style={{ width: 300 }}
value={this.state.search_filter}
prefix={<Icon type='search' style={{ color: 'rgba(0,0,0,.25)' }} />}
type='text'
placeholder='Search'
/>
)}
</Col>
<Col className='table-operations'>
{/* <Button onClick = {this.clearFilters}><b>Clear filters</b></Button>*/}
<Button onClick={this.clearAll}>Clear filters</Button>
{this.props.url.csv && <DropdownExport defaultFilter={this.props.defaultFilter} url={this.props.url.csv} />}
</Col>
</Row>
<Table
size='middle'
rowSelection={apiDelete && rowSelection}
columns={columns}
dataSource={this.state.data ? this.state.data : null}
pagination={false}
rowKey={(record) => record[this.props.keyValue]}
onChange={this.handleTableChange}
loading={loading}
/>
<Row type='flex' justify='space-between' style={{ marginTop: 20 }}>
<Col>
{apiDelete && (
<div>
<Popconfirm
placement='top'
title={'Are you sure you want to delete this record?'}
onConfirm={this.handleBatchDelete}
okText='Yes'
cancelText='No'
icon={<Icon type='close-circle' />}
>
<Button type='danger' disabled={!hasSelected} icon='delete' loading={loading}>
Delete
</Button>
<span style={{ marginLeft: 8 }}>
{hasSelected ? `Selected ${selectedRowKeys.length} item(s)` : ''}
</span>
</Popconfirm>
</div>
)}
</Col>
<Col>
{this.state.total > 0 ? (
<Pagination
style={{ float: 'right' }}
showSizeChanger
defaultCurrent={parseInt(urlParamsObject.page, 10) || 1}
defaultPageSize={parseInt(urlParamsObject.page_size, 10) || 10}
pageSizeOptions={[ '10', '50', '100' ]}
total={this.state.total}
showTotal={(total, range) =>
`Showing ${this.state.total > 0 ? range[0] : 0}-${this.state.total > 0 ? range[1] : 0} of ${this.state
.total > 0
? total
: 0}`}
onChange={this.onPaginationChange}
onShowSizeChange={this.onPaginationChange}
/>
) : null}
</Col>
</Row>
</div>
);
}
}
export default withRouter(AdvanceTable);

View File

@ -0,0 +1,4 @@
.table-operations > button {
margin-right: 8px;
}

67
src/constants/Response.js Normal file
View File

@ -0,0 +1,67 @@
const RESPONSE = {
HTTP_CONTINUE: 100,
HTTP_SWITCHING_PROTOCOLS: 101,
HTTP_PROCESSING: 102,
HTTP_EARLY_HINTS: 103,
HTTP_OK: 200,
HTTP_CREATED: 201,
HTTP_ACCEPTED: 202,
HTTP_NON_AUTHORITATIVE_INFORMATION: 203,
HTTP_NO_CONTENT: 204,
HTTP_RESET_CONTENT: 205,
HTTP_PARTIAL_CONTENT: 206,
HTTP_MULTI_STATUS: 207,
HTTP_ALREADY_REPORTED: 208,
HTTP_IM_USED: 226,
HTTP_MULTIPLE_CHOICES: 300,
HTTP_MOVED_PERMANENTLY: 301,
HTTP_FOUND: 302,
HTTP_SEE_OTHER: 303,
HTTP_NOT_MODIFIED: 304,
HTTP_USE_PROXY: 305,
HTTP_RESERVED: 306,
HTTP_TEMPORARY_REDIRECT: 307,
HTTP_PERMANENTLY_REDIRECT: 308,
HTTP_BAD_REQUEST: 400,
HTTP_UNAUTHORIZED: 401,
HTTP_PAYMENT_REQUIRED: 402,
HTTP_FORBIDDEN: 403,
HTTP_NOT_FOUND: 404,
HTTP_METHOD_NOT_ALLOWED: 405,
HTTP_NOT_ACCEPTABLE: 406,
HTTP_PROXY_AUTHENTICATION_REQUIRED: 407,
HTTP_REQUEST_TIMEOUT: 408,
HTTP_CONFLICT: 409,
HTTP_GONE: 410,
HTTP_LENGTH_REQUIRED: 411,
HTTP_PRECONDITION_FAILED: 412,
HTTP_REQUEST_ENTITY_TOO_LARGE: 413,
HTTP_REQUEST_URI_TOO_LONG: 414,
HTTP_UNSUPPORTED_MEDIA_TYPE: 415,
HTTP_REQUESTED_RANGE_NOT_SATISFIABLE: 416,
HTTP_EXPECTATION_FAILED: 417,
HTTP_I_AM_A_TEAPOT: 418,
HTTP_MISDIRECTED_REQUEST: 421,
HTTP_UNPROCESSABLE_ENTITY: 422,
HTTP_LOCKED: 423,
HTTP_FAILED_DEPENDENCY: 424,
HTTP_RESERVED_FOR_WEBDAV_ADVANCED_COLLECTIONS_EXPIRED_PROPOSAL: 425,
HTTP_UPGRADE_REQUIRED: 426,
HTTP_PRECONDITION_REQUIRED: 428,
HTTP_TOO_MANY_REQUESTS: 429,
HTTP_REQUEST_HEADER_FIELDS_TOO_LARGE: 431,
HTTP_UNAVAILABLE_FOR_LEGAL_REASONS: 451,
HTTP_INTERNAL_SERVER_ERROR: 500,
HTTP_NOT_IMPLEMENTED: 501,
HTTP_BAD_GATEWAY: 502,
HTTP_SERVICE_UNAVAILABLE: 503,
HTTP_GATEWAY_TIMEOUT: 504,
HTTP_VERSION_NOT_SUPPORTED: 505,
HTTP_VARIANT_ALSO_NEGOTIATES_EXPERIMENTAL: 506,
HTTP_INSUFFICIENT_STORAGE: 507,
HTTP_LOOP_DETECTED: 508,
HTTP_NOT_EXTENDED: 510,
HTTP_NETWORK_AUTHENTICATION_REQUIRED: 511,
}
export default RESPONSE;

8
src/constants/Types.js Normal file
View File

@ -0,0 +1,8 @@
export const LOGOUT = 'LOGOUT'
export const LOGOUT_SUCCESS = 'LOGOUT_SUCCESS'
export const LOGOUT_RESET = 'LOGOUT_RESET'
export const FETCH_DATA = 'FETCH_DATA'
export const FETCH_DATA_SUCCESS = 'FETCH_DATA_SUCCESS'
export const FETCH_DATA_ERROR = 'FETCH_DATA_ERROR'
export const FETCH_DATA_RESET = 'FETCH_DATA_RESET'

5
src/constants/global.js Normal file
View File

@ -0,0 +1,5 @@
const global = {
loadingTime: 3000
}
export default global;

View File

@ -0,0 +1,71 @@
export const required = value => (value ? undefined : 'This is a required field.');
export const dropdownRequired = value =>
value === null || value === undefined
? 'This is a required field.'
: undefined;
export const requiredAtleastOne = value => (value ? undefined : 'At least one must be selected.');
export const arrayChecker = value => (value && value.length > 0 ? undefined : 'This is a required field.');
export const email = value =>
value && !/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i.test(value)
? 'Please provide a valid email address.'
: undefined;
export const alphaNumeric = value =>
value && /[^a-zA-Z0-9 ]/i.test(value)
? 'Only alphanumeric characters'
: undefined;
const length = size => value =>
value && value.length !== size
? `Must be ${size} characters.`
: undefined;
export const length4 = length(4);
export const length10 = length(10);
export const length11 = length(11);
export const length12 = length(12);
export const length13 = length(13);
const minLength = min => value =>
value && value.length < min
? `Must be ${min} characters or more`
: undefined;
export const minLength2 = minLength(2)
const maxLength = max => value =>
value && value.length > max
? `Must be ${max} characters or less.`
: undefined;
export const maxLength5 = maxLength(5);
export const maxLength10 = maxLength(10);
export const maxLength15 = maxLength(15);
export const numbers = value =>
value && !/^[0-9]*$/i.test(value)
? 'Must be whole numbers only.'
: undefined;
export const numbersWithDecimals = value =>
value && !/^\d+(\.\d{1,2})?$/i.test(value)
? 'Must be a valid number with two (2) decimals only.'
: undefined;
const minValue = min => value =>
value && parseFloat(value.toString().replace(/,/g, '')) <= min
? `Must be greater than ${min}.`
: undefined;
export const minValue0 = minValue(0);
const minAllowableValue = min => value =>
value && parseFloat(value.toString().replace(/,/g, '')) < min
? `Must be greater than ${min}.`
: undefined;
export const minAllowableValue0 = minAllowableValue(0)
const maxValue = max => value =>
value && parseFloat(value.toString().replace(/,/g, '')) >= max
? `Must be less than ${max}.`
: undefined;
export const maxValue1M = maxValue(1000000)
export const maxValue100 = maxValue(100)

View File

@ -0,0 +1,205 @@
// LIBRARIES
import React from 'react';
import { Row, Button, Col } from 'antd';
import { Form, Field } from 'formik';
import { connect } from 'react-redux';
// COMPONENTS
import HeaderForm from "components/Forms/HeaderForm"
import { Inputs, Checkbox, Radio, UploadImage, InputTextArea, SingleUploadImage } from 'components/Forms';
// HELPER FUNCTIONS
const formItemLayout = {
labelCol: {
xs: { span: 24 },
sm: { span: 6 },
},
wrapperCol: {
xs: { span: 24 },
sm: { span: 10 },
},
};
function AddCardForm(props) {
const {
isSubmitting,
handleSubmit,
loading,
handleFileUpload,
handleFileUploadBackground,
history
} = props;
return (
<Form noValidate>
<HeaderForm
isInsideForm
loading={loading}
disabled={props.isValid == false ? true : false}
title="Card Types"
action={handleSubmit}
actionBtnName="Submit"
withCancelConfirm={{ message: 'Are you sure you want to discard changes?'}}
cancel={()=> {history.push("/about-us/card-types")}}
cancelBtnName="Cancel"
/>
<Field
name="code"
type="text"
//icon="user"
layout={formItemLayout}
label="Card Code"
placeholder="Card Code"
component={Inputs}
/>
<Field
name="type"
type="text"
//icon="user"
layout={formItemLayout}
label="Card Type Description"
placeholder="Card Type Description"
component={Inputs}
/>
<Field
onCountText
charsperpage={140}
maxLength="140"
style={{marginBottom: '10px'}}
name="description"
type="text"
icon=""
layout={formItemLayout}
label="Card Type Short Description"
placeholder="Card Type Short Description"
rows={4}
component={InputTextArea}
/>
<Field
limit100kb
name="image"
type="file"
accept=".jpg , .png, .gif"
notAcceptedImg={["image/gif"]}
multiple={false}
imageUrl={props.values.image && `${props.values.image}`}
className="upload-list-inline"
icon="user"
layout={formItemLayout}
label="Upload Card Type Image"
placeholder="Upload Card Type Image"
component={SingleUploadImage}
imgWidth="294px"
handleFileUpload={handleFileUpload}
/>
<Field
name="virtual_card_font_color"
icon=""
layout={formItemLayout}
optionsList={[
{
label: "White",
value: 1
},
{
label: "Black",
value: 2,
}
]}
label="Virtual Card Font Color"
component={Radio}
/>
<Field
limit100kb
name="bg_image"
type="file"
multiple={false}
notAcceptedImg={["image/gif"]}
isRatioMessage={{isRatioMessage: 'Image Size (ex. 375 x 260).'}}
imageUrl={props.values.image && `${props.values.bg_image}`}
className="upload-list-inline"
icon="user"
layout={formItemLayout}
label="Upload Card Type Cover Image"
placeholder="Upload Card Type Cover Image"
component={SingleUploadImage}
imgWidth="294px"
handleFileUpload={handleFileUploadBackground}
/>
<Field
name="id_number"
icon=""
layout={formItemLayout}
optionsList={[
{
label: "Yes",
value: 1
},
{
label: "No",
value: 2,
}
]}
label="ID Number Required?"
component={Radio}
/>
<Field
onCountText
charsperpage={40}
maxLength="40"
style={{marginBottom: '10px'}}
disabled={props.values.id_number && props.values.id_number == 2 ? true: false}
name="id_number_description"
type="text"
icon=""
layout={formItemLayout}
label="ID Number Description"
placeholder="ID Number Description"
rows={2}
component={InputTextArea}
/>
<h4 style={{marginLeft: '190px'}}>DATA PRIVACY</h4>
<Field
name="terms_and_conditions"
type="text"
icon=""
layout={formItemLayout}
label={`Terms & Conditions`}
placeholder={`Terms & Conditions`}
rows={10}
component={InputTextArea}
/>
<Field
name="faqs"
type="text"
icon=""
layout={formItemLayout}
label="FAQs"
placeholder="FAQs"
rows={10}
component={InputTextArea}
/>
</Form>
);
};
AddCardForm = connect(
state => ({
}),
)(AddCardForm);
export default AddCardForm;

View File

@ -0,0 +1,191 @@
// LIBRARIES
import React, { Component } from 'react'
import { connect } from "react-redux"
import { Formik } from 'formik'
import { message,notification } from 'antd';
// COMPONENTS
import HeaderForm from "components/Forms/HeaderForm"
import AddCardForm from './components/AddCardForm'
// HELPER FUNCTIONS
import { userDetailsSchema } from './validationSchema'
import { customAction } from "actions";
import { API_GET, API_POST, API_UNI_OIL } from "utils/Api";
import { apiFormValidation } from "utils/helper";
class CardTypeCreate extends Component {
state = {
generated_password: null,
userInfo: null,
loading: false,
fileUpload: null,
fileUploadBackground: null
}
componentDidMount() {
}
handleSubmit = async (values, actions) => {
const { fileUpload, fileUploadBackground } = this.state;
const { history } = this.props;
const { setErrors } = actions;
this.setState({loading: true})
try {
const headers = {
'ContentType': 'multipart/form-data',
};
const formData = new FormData();
if(fileUpload) {
fileUpload.forEach((t, i) => {
formData.append( `image`, t.originFileObj);
});
}
if(fileUploadBackground) {
fileUploadBackground.forEach((t, i) => {
formData.append( `bg_image`, t.originFileObj);
});
}
if(values.id_number == 1) {
values.id_number = 1;
if(!values.id_number_description) {
setErrors({id_number_description: "ID Number Description is required!"})
notification.error({
message: 'Error',
description: <div>
Something went wrong creating new record.
<div>- ID Number Description is required!</div>
</div>
});
return this.setState({loading: false})
}
} else {
values.id_number = 0;
}
if(values.virtual_card_font_color == 2) {
values.virtual_card_font_color = 1
} else {
values.virtual_card_font_color = 0
}
values.code && (formData.append('code', values.code));
values.type && (formData.append('type', values.type));
values.description && (formData.append('description', values.description));
values.terms_and_conditions && (formData.append('terms_and_conditions', values.terms_and_conditions));
values.faqs && (formData.append('faqs', values.faqs));
formData.append('id_number', values.id_number);
formData.append('id_number_description', values.id_number_description);
formData.append('virtual_card_font_color', values.virtual_card_font_color);
let response = await API_UNI_OIL.post('cardType', formData , headers)
if(response) {
message.success('New record added.');
this.setState({loading: false})
history.push({ pathname: "/about-us/card-types" })
}
} catch ({response: error}) {
if (error.status === 422) {
apiFormValidation({ data: error.data.data, setErrors });
}
notification.error({
message: 'Error',
description: <div>
Something went wrong creating new record.
{ error && error.data && error.data.data && error.data.data.image
&& (<div>- {error.data.data.image[0]} </div>) }
</div>
});
this.setState({loading: false})
}
}
handleAddCardTypes =()=> {
this.form.submitForm()
}
handleFileUpload =(e)=> {
if (Array.isArray(e)) {
return this.setState({fileUpload: e});
}
return e && this.setState({fileUpload: e.fileList});
}
handleFileUploadBackground =(e)=> {
if (Array.isArray(e)) {
return this.setState({fileUploadBackground: e});
}
return e && this.setState({fileUploadBackground: e.fileList});
}
render() {
const { userManagement } = this.props
const { loading, isGenerated } = this.state;
return (
<div style={{ border:'1px solid #E6ECF5' , paddingBottom: '10px'}}>
{/* <HeaderForm
loading={loading}
title="Card Types"
action={this.handleAddCardTypes}
actionBtnName="Submit"
withCancelConfirm={{ message: 'Are you sure you want to discard changes?'}}
cancel={()=> {this.props.history.push("/about-us/card-types")}}
cancelBtnName="Cancel"
/> */}
<div>
<h2 style={{margin: '25px 35px'}}>Card Type Details</h2>
<Formik
initialValues={{
code: '',
type: '',
description: '',
image: '',
terms_and_conditions: '',
faqs: '',
virtual_card_font_color: 1,
bg_image: '',
id_number: 2,
id_number_description: '',
}}
ref={node => (this.form = node)}
enableReinitialize={true}
validationSchema={userDetailsSchema}
onSubmit={this.handleSubmit }
render = {(props)=>
<AddCardForm
{...props}
loading={loading}
history={this.props.history}
handleFileUpload={this.handleFileUpload}
handleFileUploadBackground={this.handleFileUploadBackground}
/>
}
/>
</div>
</div>
)
}
}
CardTypeCreate = connect(
state => ({
//userInfo: state
userManagement: state.userManagement,
}),
{ customAction }
)(CardTypeCreate);
export default CardTypeCreate;

View File

@ -0,0 +1,39 @@
import * as Yup from 'yup'
export const userDetailsSchema = Yup.object().shape({
code: Yup.string()
.trim()
.max(128, "Maximum character is 128.")
//.max(12,"Card Code must be 20 characters only. "),
.required('Card Code is required!'),
type: Yup.string()
.trim()
.max(128, "Maximum character is 128.")
.required('Card Type Description is required!'),
description: Yup.string()
.trim()
.max(140, "Maximum character is 140.")
.required('Card Type Short Description is required!'),
image: Yup.string()
.required('Upload Card Type Image is required!'),
virtual_card_font_color: Yup.string()
.required('Virtual Card Font Color is required!'),
bg_image: Yup.string()
.required('Upload Card Type Cover Image is required!'),
terms_and_conditions: Yup.string()
.trim()
.max(32000, "Maximum character is 32,000.")
.required('Terms and Condition is required!'),
faqs: Yup.string()
.trim()
.max(32000, "Maximum character is 32,000.")
.required('FAQs is required!'),
id_number: Yup.string()
.required('ID Number is required!'),
id_number_description: Yup.string()
.trim()
.max(40, "Maximum character is 40."),
//.required('ID Number Description is required!'),
})

View File

@ -0,0 +1,204 @@
// LIBRARIES
import React from 'react';
import { Row, Button, Col } from 'antd';
import { Form, Field } from 'formik';
import { connect } from 'react-redux';
// COMPONENTS
import HeaderForm from "components/Forms/HeaderForm"
import { Inputs, Radio, InputTextArea, UploadImage,SingleUploadImage } from 'components/Forms';
// HELPER FUNCTIONS
const formItemLayout = {
labelCol: {
xs: { span: 24 },
sm: { span: 6 },
},
wrapperCol: {
xs: { span: 24 },
sm: { span: 10 },
},
};
function EditCardForm(props) {
const {
isSubmitting,
loading,
handleSubmit,
handleFileUpload,
history,
handleFileUploadBackground
} = props;
return (
<Form noValidate>
<HeaderForm
isInsideForm
loading={loading}
disabled={props.isValid == false ? true : false}
title="Update Card Type"
action={handleSubmit}
actionBtnName="Submit"
withConfirm={{message: "Save changes to this record?"}}
withCancelConfirm={{ message: 'Are you sure you want to discard changes?'}}
cancel={()=> { history.push("/about-us/card-types")}}
cancelBtnName="Cancel"
/>
<Field
name="code"
type="text"
//icon="user"
layout={formItemLayout}
label="Card Code"
placeholder="Card Code"
component={Inputs}
/>
<Field
name="type"
type="text"
//icon="user"
layout={formItemLayout}
label="Card Type Description"
placeholder="Card Type Description"
component={Inputs}
/>
<Field
onCountText
charsperpage={140}
maxLength="140"
style={{marginBottom: '10px'}}
name="description"
type="text"
icon=""
layout={formItemLayout}
label="Card Type Short Description"
placeholder="Card Type Short Description"
rows={4}
component={InputTextArea}
/>
<Field
limit100kb
name="image"
type="file"
accept=".jpg , .png, .gif"
notAcceptedImg={["image/gif"]}
multiple={false}
imageUrl={props.values.image && `${props.values.image}`}
className="upload-list-inline"
icon="user"
layout={formItemLayout}
label="Upload Card Type Image"
placeholder="Upload Card Type Image"
component={SingleUploadImage}
imgWidth="294px"
handleFileUpload={handleFileUpload}
/>
<Field
name="virtual_card_font_color"
icon=""
layout={formItemLayout}
optionsList={[
{
label: "White",
value: 1
},
{
label: "Black",
value: 2,
}
]}
label="Virtual Card Font Color"
component={Radio}
/>
<Field
limit100kb
name="bg_image"
type="file"
multiple={false}
notAcceptedImg={["image/gif"]}
isRatioMessage={{isRatioMessage: 'Image Size (ex. 375 x 260).'}}
imageUrl={props.values.image && `${props.values.bg_image}`}
className="upload-list-inline"
icon="user"
layout={formItemLayout}
label="Upload Card Type Cover Image"
placeholder="Upload Card Type Cover Image"
component={SingleUploadImage}
imgWidth="294px"
handleFileUpload={handleFileUploadBackground}
/>
<Field
name="id_number"
icon=""
layout={formItemLayout}
optionsList={[
{
label: "Yes",
value: 1
},
{
label: "No",
value: 2,
}
]}
label="ID Number Required?"
component={Radio}
/>
<Field
onCountText
charsperpage={40}
maxLength="40"
style={{marginBottom: '10px'}}
disabled={props.values.id_number && props.values.id_number == 2 ? true: false}
name="id_number_description"
type="text"
icon=""
layout={formItemLayout}
label="ID Number Description"
placeholder="ID Number Description"
rows={2}
component={InputTextArea}
/>
<h4 style={{marginLeft: '190px'}}>DATA PRIVACY</h4>
<Field
name="terms_and_conditions"
type="text"
icon=""
layout={formItemLayout}
label={`Terms & Conditions`}
placeholder={`Terms & Conditions`}
rows={10}
component={InputTextArea}
/>
<Field
name="faqs"
type="text"
icon=""
layout={formItemLayout}
label="FAQs"
placeholder="FAQs"
rows={10}
component={InputTextArea}
/>
</Form>
);
};
EditCardForm = connect(
state => ({
}),
)(EditCardForm);
export default EditCardForm;

View File

@ -0,0 +1,196 @@
// LIBRARIES
import React, { Component } from 'react'
import { Formik } from 'formik'
import { withRouter } from "react-router-dom"
import { notification, message } from "antd"
// COMPONENTS
import HeaderForm from "components/Forms/HeaderForm"
import EditCardForm from './components/EditCardForm'
// HELPER FUNCTIONS
import { userDetailsSchema } from './validationSchema'
import { API_GET, API_PUT, API_POST } from "utils/Api";
import { API_UNI_OIL } from "utils/Api";
import { apiFormValidation } from "utils/helper";
class CardTypeEdit extends Component {
state = {
loading: false,
userInfo: null,
mounted: false,
}
async componentDidMount() {
const { match } = this.props;
try {
let response = await API_UNI_OIL.get(`cardType/${match.params.id}`);
this.setState({
userInfo: {...response.data.data},
mounted: true
})
} catch ({response: error}) {
notification.error({
message: "Error",
description: <div>
<div>Something went wrong loading data.</div>
- {error && error.data && error.data.message}
</div> ,
duration: 3,
});
if(error.status == 404) {
if(this.props.location.pathname)
this.props.history.push(`${this.props.location.pathname}/404`); return
}
this.setState({ mounted: false })
}
}
handleSubmit = async (values, actions) => {
const { fileUpload, userInfo, fileUploadBackground } = this.state;
const { history } = this.props;
const { setErrors } = actions;
this.setState({loading: true})
try {
const headers = {
'ContentType': 'multipart/form-data',
};
const formData = new FormData();
if(fileUpload) {
fileUpload.forEach((t, i) => {
formData.append( `image`, t.originFileObj);
});
}
if(fileUploadBackground) {
fileUploadBackground.forEach((t, i) => {
formData.append( `bg_image`, t.originFileObj);
});
}
if(values.id_number == 1) {
values.id_number = 1;
if(!values.id_number_description) {
setErrors({id_number_description: "ID Number Description is required!"})
notification.error({
message: 'Error',
description: <div>
Something went wrong.
<div>- ID Number Description is required!</div>
</div>
});
return this.setState({loading: false})
}
} else {
values.id_number = 0;
}
if(values.virtual_card_font_color == 2) {
values.virtual_card_font_color = 1
} else {
values.virtual_card_font_color = 0
}
values.code && (formData.append('code', values.code));
values.type && (formData.append('type', values.type));
values.description && (formData.append('description', values.description));
values.terms_and_conditions && (formData.append('terms_and_conditions', values.terms_and_conditions));
values.faqs && (formData.append('faqs', values.faqs));
formData.append('id_number', values.id_number);
formData.append('id_number_description', values.id_number_description);
formData.append('virtual_card_font_color', values.virtual_card_font_color);
let response = await API_UNI_OIL.post(`cardTypeUpdate/${userInfo.cardtype_uuid}`, formData , headers)
if(response) {
message.success('Record was successfully update.');
this.setState({loading: false})
history.push({ pathname: "/about-us/card-types" })
}
} catch ({response: error}) {
if (error.status === 422) {
apiFormValidation({ data: error.data.data, setErrors });
}
notification.error({
message: 'Error',
description: <div>
Something went wrong creating new record.
{ error && error.data && error.data.data && error.data.data.image
&& (<div>- {error.data.data.image[0]} </div>) }
</div>
});
this.setState({loading: false})
}
}
handleEditCardTypes =()=> {
this.form.submitForm()
}
handleFileUpload =(e)=> {
if (Array.isArray(e)) {
return this.setState({fileUpload: e});
}
return e && this.setState({fileUpload: e.fileList});
}
handleFileUploadBackground =(e)=> {
if (Array.isArray(e)) {
return this.setState({fileUploadBackground: e});
}
return e && this.setState({fileUploadBackground: e.fileList});
}
render() {
if(!this.state.mounted) return null;
const { loading, userInfo } = this.state
return (
<div style={{ border:'1px solid #E6ECF5' , paddingBottom: '10px'}}>
<div>
<h2 style={{margin: '25px 35px'}}>Card Types Details</h2>
<Formik
initialValues={{
code: userInfo.code || '',
type: userInfo.name || '',
description: userInfo.description || '',
image: userInfo.image || '',
terms_and_conditions: userInfo.terms_and_conditions || '',
faqs: userInfo.faqs || '',
id_number: userInfo.id_number && userInfo.id_number == 1 ? userInfo.id_number : 2 || '',
id_number_description: userInfo.id_number_description || '',
virtual_card_font_color: userInfo.virtual_card_font_color && userInfo.virtual_card_font_color == 1 ? 2 : 1 || 1,
bg_image: userInfo.bg_image || '',
id_number_description: userInfo.id_number_description || '',
}}
ref={node => (this.form = node)}
enableReinitialize={true}
validationSchema={userDetailsSchema}
onSubmit={this.handleSubmit }
render = {(props)=>
<EditCardForm
{...props}
loading={loading}
history={this.props.history}
handleFileUpload={this.handleFileUpload}
handleFileUploadBackground={this.handleFileUploadBackground}
/>
}
/>
</div>
</div>
)
}
}
export default withRouter(CardTypeEdit);

View File

@ -0,0 +1,40 @@
import * as Yup from 'yup'
export const userDetailsSchema = Yup.object().shape({
code: Yup.string()
.trim()
.max(128, "Maximum character is 128.")
//.max(12,"Card Code must be 12 characters only. "),
.required('Card Code is required!'),
type: Yup.string()
.trim()
.max(128, "Maximum character is 128.")
.required('Card Type Description is required!'),
description: Yup.string()
.trim()
.max(140, "Maximum character is 140.")
.required('Card Type Short Description is required!'),
image: Yup.string()
.required('Upload Card Type Image is required!'),
virtual_card_font_color: Yup.string()
.required('Virtual Card Font Color is required!'),
bg_image: Yup.string()
.required('Upload Card Type Cover Image is required!'),
terms_and_conditions: Yup.string()
.trim()
.max(32000, "Maximum character is 32,000.")
.required('Terms and Condition is required!'),
faqs: Yup.string()
.trim()
.max(32000, "Maximum character is 32,000.")
.required('FAQs is required!'),
id_number: Yup.string()
.required('ID Number is required!'),
id_number_description: Yup.string()
.trim()
.max(40, "Maximum character is 40."),
})

View File

@ -0,0 +1,109 @@
// LIBRARIES
import React, { Component } from 'react';
import { Menu, Dropdown, notification, Icon, message } from "antd"
import { connect } from "react-redux";
import { Link } from 'react-router-dom'
// COMPONENTS
import AdvanceTable from "components/Tables/AdvanceTable";
import HeaderForm from "components/Forms/HeaderForm";
// HELPER FUNCTIONS
import { API_UNI_OIL } from "utils/Api";
import { customAction } from 'actions';
class CardTypeList extends Component {
state= {
updating: false,
}
delete =(admin_uuid)=> {
}
updateDropDown = async(e) => {
}
render() {
const { match, history } = this.props;
return (
<div style={{border:'1px solid #E6ECF5'}}>
<HeaderForm
title="Card Types"
action={()=> history.push({ pathname: `${match.url}/create` })}
actionBtnName="Add Card"
/>
<AdvanceTable
updating = { this.state.updating }
keyValue="cardtype_uuid"
url={{
//default: 'admin?page=1&page_size=10&_sort_by=create_dt&_sort_order=desc'
apiDelete: 'cardTypeBatchDelete',
default: 'cardType',
filter: '?page=1&page_size=10&_sort_by=create_dt&_sort_order=desc'
}}
filterValues ={["role", "status"]}
columns={
[
{
title: 'Card Type Code',
dataIndex: 'code',
key: 'code',
sorter: true,
filters: [],
width: "17%",
},
{
title: 'Card Type Description',
dataIndex: 'name',
key: 'name',
sorter: true,
filters:[],
},
{
title: 'Action',
dataIndex: 'action',
key: 'action',
width: 150,
buttons: [
{
key: 'edit',
title: "Edit",
icon: 'edit',
url: 'about-us/card-types/edit'
},
{
key: 'delete',
title: "Delete",
icon: 'delete',
url: '',
action: this.delete
},
{
key: 'view',
title: "View",
icon: 'right-circle-o',
url: 'about-us/card-types/view'
}
]
},
]
}
/>
</div>
);
}
}
CardTypeList = connect(
state => ({
//user: state.viewUser.data,
//status: state.viewUser.code,
//responseMsg: state.viewUser.messages
}),
{ customAction }
)(CardTypeList);
export default CardTypeList;

View File

@ -0,0 +1,103 @@
// LIBRARIES
import React from 'react'
import { connect } from 'react-redux'
import { Icon, Avatar, Row , Col } from 'antd'
// COMPONENTS
import HeaderForm from "components/Forms/HeaderForm"
// HELPER FUNCTIONS
function ViewCardForm(props) {
const {
isSubmitting,
userInfo
} = props;
return (
<div>
<div style={{padding: '15px 30px 0px', borderTop: 0}}>
<div>
<h2 style={{margin: '0 0 20px'}}>Details</h2>
{/*Account Details */}
<h2 style={{fontWeight: 'bold', fontSize: '16px'}}>CARD DETAILS</h2>
<Row style={styles.marginTop}>
<Col span={18} push={5}>{userInfo && userInfo.code}</Col>
<Col span={5} pull={18}><span style={{fontWeight: '600'}}>Card Code:</span></Col>
</Row>
<Row style={styles.marginTop}>
<Col span={18} push={5}>{userInfo && userInfo.name}</Col>
<Col span={5} pull={18}><span style={{fontWeight: '600'}}>Card Type Description:</span></Col>
</Row>
<Row style={styles.marginTop}>
<Col span={18} push={5}>{userInfo && userInfo.description}</Col>
<Col span={5} pull={18}><span style={{fontWeight: '600'}}>Card Type Short Description:</span></Col>
</Row>
<Row style={styles.marginTop}>
<Col span={18} push={5}>
<img
src={userInfo && userInfo.image && `${userInfo.image}`}
alt="avatar"
width="300"
style={{maxHeight: '250px'}}
/>
</Col>
<Col span={5} pull={18}><span style={{fontWeight: '600'}}>Card Type Image:</span></Col>
</Row>
<Row style={styles.marginTop}>
<Col span={18} push={5}>{userInfo && userInfo.virtual_card_font_color && userInfo.virtual_card_font_color ? "Black": "White"}</Col>
<Col span={5} pull={18}><span style={{fontWeight: '600'}}>Virtual Card Font Color:</span></Col>
</Row>
<Row style={styles.marginTop}>
<Col span={18} push={5}>
<img
src={userInfo && userInfo.image && `${userInfo.bg_image}`}
alt="avatar"
width="300"
style={{maxHeight: '250px'}}
/>
</Col>
<Col span={5} pull={18}><span style={{fontWeight: '600'}}>Card Type Cover Image:</span></Col>
</Row>
<Row style={styles.marginTop}>
<Col span={18} push={5}>{userInfo && userInfo.id_number && userInfo.id_number ? "Yes": "No"}</Col>
<Col span={5} pull={18}><span style={{fontWeight: '600'}}>ID Number Required:</span></Col>
</Row>
<Row style={styles.marginTop}>
<Col span={18} push={5}>{userInfo && userInfo.id_number_description}</Col>
<Col span={5} pull={18}><span style={{fontWeight: '600'}}>ID Number Description:</span></Col>
</Row>
</div>
{/*Account Details */}
<div style={{margin: '15px 0 10px'}}>
<h2 style={{fontWeight: 'bold', fontSize: '16px'}}>DATA PRIVACY DETAILS</h2>
<Row style={styles.marginTop}>
<Col span={18} push={5}>{userInfo && userInfo.terms_and_conditions}</Col>
<Col span={5} pull={18}><span style={{fontWeight: '600'}}>{`Terms and Conditions:`}</span></Col>
</Row>
<Row style={styles.marginTop}>
<Col span={18} push={5}>{userInfo && userInfo.faqs}</Col>
<Col span={5} pull={18}><span style={{fontWeight: '600'}}>FAQs:</span></Col>
</Row>
</div>
</div>
</div>
);
};
ViewCardForm = connect(
state => ({
}),
)(ViewCardForm);
export default ViewCardForm;
const styles = {
marginTop: {
marginTop: '5px'
}
}

View File

@ -0,0 +1,95 @@
// LIBRARIES
import React, { Component } from 'react'
import { withRouter } from "react-router-dom"
import { notification, message } from "antd"
// COMPONENTS
import HeaderForm from 'components/Forms/HeaderForm'
import ViewCardForm from './components/ViewCardForm'
// HELPER FUNCTIONS
import { API_UNI_OIL } from "utils/Api";
class CardTypeView extends Component {
state = {
mounted: false,
userInfo: null
}
async componentDidMount() {
const { match } = this.props;
try {
let response = await API_UNI_OIL.get(`cardType/${match.params.id}`)
this.setState({
userInfo: {...response.data.data},
mounted: true
})
} catch ({response: error}) {
notification.error({
message: "Error",
description: <div>
<div>Something went wrong loading data.</div>
- { error && error.data && error.data.message }
</div> ,
duration: 3,
});
if(error.status == 404) {
if(this.props.location.pathname)
this.props.history.push(`${this.props.location.pathname}/404`); return
}
this.setState({ mounted: false })
}
}
delete = async (uuid) => {
const { userInfo } = this.state;
const { match } = this.props;
try {
await API_UNI_OIL.delete(`cardType/${userInfo.cardtype_uuid}`);
message.success('Record was successfully deleted.');
this.props.history.push("/about-us/card-types");
} catch ({response:error}) {
notification.error({
message: "Error",
description: <div>
<div>Something went wrong deleting record.</div>
- { error && error.data && error.data.message }
</div> ,
duration: 3,
});
}
}
render() {
if(!this.state.mounted) return null;
const { userInfo } = this.state
const { history, match } = this.props
return (
<div style={{ border:'1px solid #E6ECF5' , paddingBottom: '10px'}}>
<HeaderForm
title="Card Type Details"
action={()=> {this.props.history.push(`/about-us/card-types/edit/${match.params.id}`)}}
actionBtnName="Update"
styleBtn={{background: 'white', borderColor: 'rgb(184, 187, 201)',color: 'rgb(101, 105, 127)'}}
deleteAction={this.delete}
deleteBtnName="Delete"
/>
<div>
<ViewCardForm userInfo={userInfo} />
</div>
</div>
)
}
}
export default withRouter(CardTypeView);

View File

@ -0,0 +1,83 @@
// LIBRARIES
import React from 'react';
import { Row, Button, Col } from 'antd';
import { Form, Field } from 'formik';
import { connect } from 'react-redux';
// COMPONENTS
import HeaderForm from "components/Forms/HeaderForm"
import { Inputs, InputTextArea } from 'components/Forms';
// HELPER FUNCTIONS
const formItemLayout = {
labelCol: {
xs: { span: 24 },
sm: { span: 4 },
},
wrapperCol: {
xs: { span: 24 },
sm: { span: 16 },
},
};
function AddUserManagementForm(props) {
const {
isSubmitting,
handleSubmit,
generatePassword,
loading,
isGenerated,
match,
history
} = props;
return (
<Form noValidate>
<HeaderForm
isInsideForm
loading={loading}
disabled={props.isValid == false ? true : false}
title={match.params.id == "1" ? "Terms" : "Privacy Policy"}
action={handleSubmit}
actionBtnName="Submit"
withCancelConfirm={{ message: 'Are you sure you want to discard changes?'}}
cancel={()=> { history.push("/about-us/term-privacy")}}
cancelBtnName="Cancel"
/>
<Field
name="title"
type="text"
icon=""
layout={formItemLayout}
label="Title"
placeholder="Title"
component={Inputs}
/>
<Field
name="details"
type="text"
icon="user"
layout={formItemLayout}
label="Details"
placeholder="Details"
rows={10}
component={InputTextArea}
/>
</Form>
);
};
AddUserManagementForm = connect(
state => ({
}),
)(AddUserManagementForm);
export default AddUserManagementForm;

View File

@ -0,0 +1,117 @@
// LIBRARIES
import React, { Component } from 'react'
import { connect } from "react-redux"
import { Formik } from 'formik'
import { message,notification } from 'antd';
// COMPONENTS
import HeaderForm from "components/Forms/HeaderForm"
import AddUserManagementForm from './components/AddUserManagementForm'
// HELPER FUNCTIONS
import { userDetailsSchema } from './validationSchema'
import { customAction } from "actions";
import { API_GET, API_POST, API_UNI_OIL } from "utils/Api";
import { apiFormValidation } from "utils/helper";
class TermAndPrivacyCreate extends Component {
state = {
generated_password: null,
userInfo: null,
loading: false,
isGenerated: false
}
componentDidMount() {
}
handleSubmit = async (values, actions) => {
const { setSubmitting, setErrors } = actions;
let { history, match } = this.props;
let params = { ...values, type: match.params.id }
this.setState({ loading: true });
try {
const response = await API_UNI_OIL.post('TermsAndPrivacy', params);
if(response) {
message.success('New record added.');
this.setState({ loading: false });
history.push({ pathname: '/about-us/term-privacy' });
}
} catch ({response:error}) {
if (error.status === 422) {
apiFormValidation({ data: error.data.data, setErrors });
}
notification.error({
message: "Error",
description: <div>
<div>Something went wrong creating new record.</div>
- { error && error.data && error.data.message }
</div> ,
duration: 3,
});
setSubmitting(false);
this.setState({ loading: false });
}
}
handleCreateTermPrivacy =()=> {
this.form.submitForm()
}
render() {
const { userManagement, match } = this.props
const { loading, isGenerated } = this.state;
return (
<div style={{ border:'1px solid #E6ECF5' , paddingBottom: '30px'}}>
{/* <HeaderForm
loading={loading}
title={match.params.id == "1" ? "Terms" : "Privacy Policy"}
action={this.handleCreateTermPrivacy}
actionBtnName="Submit"
withCancelConfirm={{ message: 'Are you sure you want to discard changes?'}}
cancel={()=> { this.props.history.push("/about-us/term-privacy")}}
cancelBtnName="Cancel"
/> */}
<div>
<h2 style={{margin: '25px 35px'}}>{match.params.id == "1" ? "Terms" : "Privacy Policy"} Details</h2>
<Formik
initialValues={{
title: '',
details: '',
}}
ref={node => (this.form = node)}
enableReinitialize={true}
validationSchema={userDetailsSchema}
onSubmit={this.handleSubmit }
render = {(props)=>
<AddUserManagementForm
{...props}
loading={userManagement.createRequestPending || loading}
match={match}
history={this.props.history}
isGenerated={isGenerated}
/>
}
/>
</div>
</div>
)
}
}
TermAndPrivacyCreate = connect(
state => ({
//userInfo: state
userManagement: state.userManagement,
}),
{ customAction }
)(TermAndPrivacyCreate);
export default TermAndPrivacyCreate;

View File

@ -0,0 +1,18 @@
import * as Yup from 'yup'
export const userDetailsSchema = Yup.object().shape({
title: Yup.string()
.trim()
.max(128, "Maximum character is 128.")
.required('Title is required!'),
details: Yup.string()
.trim()
.max(32000, "Maximum character is 32,000.")
.required('Details is required!'),
// type: Yup.string()
// .required('Type is required!')
})

View File

@ -0,0 +1,82 @@
// LIBRARIES
import React from 'react';
import { Row, Button, Col } from 'antd';
import { Form, Field } from 'formik';
import { connect } from 'react-redux';
// COMPONENTS
import HeaderForm from "components/Forms/HeaderForm"
import { Inputs, InputTextArea } from 'components/Forms';
// HELPER FUNCTIONS
const formItemLayout = {
labelCol: {
xs: { span: 24 },
sm: { span: 4 },
},
wrapperCol: {
xs: { span: 24 },
sm: { span: 16 },
},
};
function EditUserManagementForm(props) {
const {
isSubmitting,
generatePassword,
loading,
handleSubmit,
isGenerated,
history
} = props;
return (
<Form noValidate>
<HeaderForm
isInsideForm
loading={loading}
disabled={props.isValid == false ? true : false}
title="Update Terms"
action={handleSubmit}
actionBtnName="Submit"
withConfirm={{message: "Save changes to this record?"}}
withCancelConfirm={{ message: 'Are you sure you want to discard changes?'}}
cancel={()=> { history.push("/about-us/term-privacy")}}
cancelBtnName="Cancel"
/>
<Field
name="title"
type="text"
icon=""
layout={formItemLayout}
label="Title"
placeholder="Title"
component={Inputs}
/>
<Field
name="details"
type="text"
icon="user"
layout={formItemLayout}
label="Details"
placeholder="Details"
rows={10}
component={InputTextArea}
/>
</Form>
);
};
EditUserManagementForm = connect(
state => ({
}),
)(EditUserManagementForm);
export default EditUserManagementForm;

View File

@ -0,0 +1,135 @@
// LIBRARIES
import React, { Component } from 'react'
import { Formik } from 'formik'
import { withRouter } from "react-router-dom"
import { notification, message } from "antd"
// COMPONENTS
import HeaderForm from "components/Forms/HeaderForm"
import EditUserManagementForm from './components/EditUserManagementForm'
// HELPER FUNCTIONS
import { userDetailsSchema } from './validationSchema'
import { API_GET, API_PUT, API_POST } from "utils/Api";
import { API_UNI_OIL } from "utils/Api";
import { apiFormValidation } from "utils/helper";
class TermAndPrivacyEdit extends Component {
state = {
loading: false,
userInfo: null,
mounted: false,
timerCount: 20,
isGenerated: false
}
async componentDidMount() {
const { match } = this.props;
try {
let response = await API_UNI_OIL.get(`TermsAndPrivacy/${match.params.id}`);
this.setState({
userInfo: {...response.data.data},
mounted: true
})
} catch ({response: error}) {
notification.error({
message: "Error",
description: <div>
<div>Something went wrong loading data.</div>
- {error && error.data && error.data.message}
</div> ,
duration: 3,
});
if(error.status == 404) {
if(this.props.location.pathname)
this.props.history.push(`${this.props.location.pathname}/404`); return
}
this.setState({ mounted: false })
}
}
handleEditUserManagement =()=> {
this.form.submitForm()
}
handleSubmit = async (values, actions) => {
const { setErrors, setSubmitting } = actions;
const { userInfo } = this.state;
let { history } = this.props;
const params = {
...values,
type : userInfo.type
}
this.setState({loading: true})
try {
const response = await API_PUT(`TermsAndPrivacy/${userInfo.tp_uuid}`, params);
if(response.status === 422){
if (response.status === 422) {
apiFormValidation({ data: response.data.data, setErrors });
}
notification.error({ message: "Success", description: "Something went wrong updating record" });
setSubmitting(false)
this.setState({loading: false})
}else {
message.success('Record was successfully update.');
this.setState({loading: false})
this.props.history.push("/about-us/term-privacy");
}
} catch (error) {
setSubmitting(false)
this.setState({loading: false})
}
}
render() {
if(!this.state.mounted) return null;
const { loading, userInfo } = this.state
return (
<div style={{ border:'1px solid #E6ECF5' , paddingBottom: '30px'}}>
{/* <HeaderForm
loading={loading}
title="Update Terms"
action={this.handleEditUserManagement}
actionBtnName="Submit"
withConfirm={{message: "Save changes to this record?"}}
withCancelConfirm={{ message: 'Are you sure you want to discard changes?'}}
cancel={()=> { this.props.history.push("/about-us/term-privacy")}}
cancelBtnName="Cancel"
/> */}
<div>
<h2 style={{margin: '25px 35px'}}>Details</h2>
<Formik
initialValues={{
title: userInfo.title || '',
details: userInfo.details || '',
}}
ref={node => (this.form = node)}
enableReinitialize={true}
validationSchema={userDetailsSchema}
onSubmit={this.handleSubmit }
render = {(props)=>
<EditUserManagementForm
{...props}
loading={loading}
history={this.props.history}
/>
}
/>
</div>
</div>
)
}
}
export default withRouter(TermAndPrivacyEdit);

View File

@ -0,0 +1,16 @@
import * as Yup from 'yup'
export const userDetailsSchema = Yup.object().shape({
title: Yup.string()
.trim()
.max(128, "Maximum character is 128.")
.required('Title is required!'),
details: Yup.string()
.trim()
.max(32000, "Maximum character is 32,000.")
.required('Details is required!'),
})

View File

@ -0,0 +1,130 @@
// LIBRARIES
import React, { Component } from 'react';
import { Menu, Dropdown, notification, Icon, message } from "antd"
import { connect } from "react-redux";
// COMPONENTS
import AdvanceTable from "components/Tables/AdvanceTable";
import HeaderForm from "components/Forms/HeaderForm";
// HELPER FUNCTIONS
import { API_UNI_OIL } from "utils/Api";
import { customAction } from 'actions';
class TermAndPrivacyList extends Component {
state= {
updating: false,
}
delete =(admin_uuid)=> {
}
updateDropDown = async(e) => {
}
render() {
const { match, history } = this.props;
return (
<div style={{border:'1px solid #E6ECF5'}}>
<HeaderForm
isDropDown
title={`Terms & Privacy`}
actionPrivacy={()=> history.push({ pathname: `${match.url}/create/1` }) }
actionTerms={()=> history.push({ pathname: `${match.url}/create/2` })}
actionBtnName="Add User"
/>
<AdvanceTable
updating = { this.state.updating }
keyValue="tp_uuid"
url={{
//default: 'admin?page=1&page_size=10&_sort_by=create_dt&_sort_order=desc'
apiDelete: 'TermsAndPrivacyBatchDelete',
default: 'TermsAndPrivacy',
filter: '?page=1&page_size=10&_sort_by=create_dt&_sort_order=desc'
}}
filterValues ={["role", "status"]}
columns={
[
{
title: 'Title',
dataIndex: 'title',
key: 'title',
sorter: true,
filters: [],
width: "20%",
},
{
title: 'Details',
dataIndex: 'details',
key: 'details',
sorter: true,
filters:[],
render: (text, record) => (
<div style={{ width: '490px', whiteSpace: 'noWrap',
overflow: 'hidden', textOverflow: 'ellipsis'
}}>
{record && record.details}
</div>
)
},
{
title: 'Type',
dataIndex: 'type',
key: 'type',
width: "8%",
sorter: true,
filters:[],
render: (text, record) => (
<span >
{record && record.type == 1 ? "Terms": "Privacy"}
</span>
)
},
{
title: 'Action',
dataIndex: 'action',
key: 'action',
width: 150,
buttons: [
{
key: 'edit',
title: "Edit",
icon: 'edit',
url: '/user-management/edit'
},
{
key: 'delete',
title: "Delete",
icon: 'delete',
url: '',
action: this.delete
},
{
key: 'view',
title: "View",
icon: 'right-circle-o',
url: '/user-management/view'
}
]
},
]
}
/>
</div>
);
}
}
TermAndPrivacyList = connect(
state => ({
//user: state.viewUser.data,
//status: state.viewUser.code,
//responseMsg: state.viewUser.messages
}),
{ customAction }
)(TermAndPrivacyList);
export default TermAndPrivacyList;

View File

@ -0,0 +1,55 @@
// LIBRARIES
import React from 'react'
import { connect } from 'react-redux'
import { Icon, Avatar, Row , Col } from 'antd'
// COMPONENTS
import HeaderForm from "components/Forms/HeaderForm"
// HELPER FUNCTIONS
function ViewUserManagementForm(props) {
const {
isSubmitting,
userInfo
} = props;
return (
<div>
<div style={{padding: '15px 30px 30px', borderTop: 0}}>
<div>
<h2 style={{margin: '0 0 20px'}}>Details</h2>
{/*Account Details */}
<Row style={{marginBottom: '5px'}}>
<Col span={18} push={3}>{userInfo && userInfo.title}</Col>
<Col span={3} pull={18}><span style={{fontWeight: '600'}}>Title:</span></Col>
</Row>
<Row>
<Col span={18} push={3}>{userInfo && <pre style={{
overflow: 'auto' ,
overflowWrap: 'break-word',
whiteSpace: 'pre-wrap',
wordWrap: 'break-word',}}>{userInfo.details}</pre>}
</Col>
<Col span={3} pull={18}><span style={{fontWeight: '600'}}>Details:</span></Col>
</Row>
{/* <Row>
<Col span={18} push={3}>{userInfo && userInfo.type == "1" ? "Terms" : "Privacy"}</Col>
<Col span={3} pull={18}><span style={{fontWeight: '600'}}>Type:</span></Col>
</Row> */}
</div>
</div>
</div>
);
};
ViewUserManagementForm = connect(
state => ({
}),
)(ViewUserManagementForm);
export default ViewUserManagementForm;

View File

@ -0,0 +1,94 @@
// LIBRARIES
import React, { Component } from 'react'
import { withRouter } from "react-router-dom"
import { notification, message } from "antd"
// COMPONENTS
import HeaderForm from 'components/Forms/HeaderForm'
import ViewUserManagementForm from './components/ViewUserManagementForm'
// HELPER FUNCTIONS
import { API_UNI_OIL } from "utils/Api";
class TermAndPrivacyView extends Component {
state = {
mounted: false,
userInfo: null
}
async componentDidMount() {
const { match } = this.props;
try {
let response = await API_UNI_OIL.get(`TermsAndPrivacy/${match.params.id}`)
this.setState({
userInfo: {...response.data.data},
mounted: true
})
} catch ({response: error}) {
notification.error({
message: "Error",
description: <div>
<div>Something went wrong loading data.</div>
- { error && error.data && error.data.message }
</div> ,
duration: 3,
});
if(error.status == 404) {
if(this.props.location.pathname)
this.props.history.push(`${this.props.location.pathname}/404`); return
}
this.setState({ mounted: false })
}
}
delete = async (uuid) => {
const { userInfo } = this.state
const { match } = this.props;
try {
await API_UNI_OIL.delete(`TermsAndPrivacy/${userInfo.tp_uuid}`);
message.success('Record was successfully deleted.');
this.props.history.push("/about-us/term-privacy");
} catch ({response:error}) {
// this.props.history.push("/404");
notification.error({
message: "Error",
description: <div>
<div>Something went wrong deleting record.</div>
{/* - { error && error.data && error.data.message } */}
</div> ,
duration: 3,
});
}
}
render() {
const { userInfo } = this.state
const { history, match } = this.props
return (
<div style={{ border:'1px solid #E6ECF5' , paddingBottom: '10px'}}>
<HeaderForm
title={"Terms & Privacy Details"}
action={()=> {this.props.history.push(`/about-us/term-privacy/edit/${match.params.id}`)}}
actionBtnName="Update"
styleBtn={{background: 'white', borderColor: 'rgb(184, 187, 201)',color: 'rgb(101, 105, 127)'}}
deleteAction={this.delete}
deleteBtnName="Delete"
/>
<div>
<ViewUserManagementForm userInfo={userInfo} />
</div>
</div>
)
}
}
export default withRouter(TermAndPrivacyView);

View File

@ -0,0 +1,137 @@
// LIBRARIES
import React, { Component, Fragment } from 'react';
import { Switch, Route, Redirect } from 'react-router-dom';
import { connect } from 'react-redux'
// COMPONENTS
import CardTypeList from './CardTypes/List';
import CardTypeCreate from './CardTypes/Create';
import CardTypeEdit from './CardTypes/Edit';
import CardTypeView from './CardTypes/View';
import TermAndPrivacyList from './TermAndPrivacy/List';
import TermAndPrivacyCreate from './TermAndPrivacy/Create';
import TermAndPrivacyEdit from './TermAndPrivacy/Edit';
import TermAndPrivacyView from './TermAndPrivacy/View';
// import TermAndPrivacyList from './TermAndPrivacy/List';
import { PAGE404 } from "components/PageError/index"
import MainContent from 'components/Dashboard/Layout/components/MainContent';
// HELPER FUNCTIONS
class AboutUs extends Component {
state = {
pageRoutes: [
{
path: `${this.props.match.url}/card-types`,
name: "Card Types",
component: CardTypeList,
},
{
path: `${this.props.match.url}/card-types/create`,
name: "Create Card Types",
component: CardTypeCreate,
},
{
path: `${this.props.match.url}/card-types/edit`,
params: ':id',
name: "Update Card Types",
component: CardTypeEdit,
},
{
path: `${this.props.match.url}/card-types/view`,
params: ':id',
name: "View Card Types",
component: CardTypeView,
},
{
path: `${this.props.match.url}/term-privacy`,
name: "Terms & Privacy",
component: TermAndPrivacyList,
},
{
path: `${this.props.match.url}/term-privacy/create`,
params: ':id',
name: "Terms",
component: TermAndPrivacyCreate,
},
{
path: `${this.props.match.url}/term-privacy/edit`,
params: ':id',
name: "Terms & Privacy",
component:TermAndPrivacyEdit,
},
{
path: `${this.props.match.url}/term-privacy/view`,
params: ':id',
name: "Terms & Privacy Details",
component: TermAndPrivacyView,
}
// {
// path: `${this.props.match.url}/lock-account`,
// name: "Locked Accounts",
// component: CardMemberList,
// },
// {
// path: `${this.props.match.url}/lock-account/view`,
// params: ':id',
// name: "View Locked Account",
// component: CardMemberView,
// }
],
}
render() {
const { userInfo } = this.props
const { pageRoutes } = this.state;
return (
<div style={{position: 'relative'}}>
<MainContent pageRoutes={userInfo.data.userInfo.role == 1 ? pageRoutes : []}>
{
userInfo.data.userInfo.role == 1
?
<Switch>
<Redirect exact from="/about-us" to="/about-us/card-types"/>
<Route exact path = "/about-us/card-types" component = { CardTypeList } />
<Route exact path = "/about-us/card-types/create" component = { CardTypeCreate } />
<Route exact path = "/about-us/card-types/view/:id" component = { CardTypeView } />
<Route exact path = "/about-us/card-types/edit/:id" component = { CardTypeEdit } />
<Route exact path = "/about-us/term-privacy" component = { TermAndPrivacyList } />
<Route exact path = "/about-us/term-privacy/create/:id" component = { TermAndPrivacyCreate } />
<Route exact path = "/about-us/term-privacy/view/:id" component = { TermAndPrivacyView } />
<Route exact path = "/about-us/term-privacy/edit/:id" component = { TermAndPrivacyEdit } />
<PAGE404 />
</Switch>
:
<Switch>
<PAGE404 />
</Switch>
}
</MainContent>
</div>
);
}
}
AboutUs = connect(
state => ({
userInfo: state.login
}),
)(AboutUs);
export default AboutUs;

View File

@ -0,0 +1,254 @@
// LIBRARIES
import React from 'react';
import { Row, Button, Col } from 'antd';
import { Form, Field } from 'formik';
import { connect } from 'react-redux';
import moment from 'moment'
// COMPONENTS
import HeaderForm from "components/Forms/HeaderForm"
import { Inputs, Select, DatePicker , InputTextArea, UploadImage, TimePickerForm } from 'components/Forms';
// HELPER FUNCTIONS
const formItemLayout = {
labelCol: {
xs: { span: 24 },
sm: { span: 5 },
},
wrapperCol: {
xs: { span: 24 },
sm: { span: 10 },
},
};
function AddPhotoSliderForm(props) {
const {
isSubmitting,
handleSubmit,
loading,
promotionsOptions,
handleFileUpload,
handleGetDate,
photoSliderLimit,
dateStartEnd,
history,
handleAutoFillDeatils
} = props;
return (
<Form noValidate>
<HeaderForm
isInsideForm
loading={loading}
disabled={props.isValid == false ? true : false}
title="Photo Slider"
action={handleSubmit}
actionBtnName="Submit"
withCancelConfirm={{ message: 'Are you sure you want to discard changes?'}}
cancel={() => { history.push("/home-page/photo-slider") }}
cancelBtnName="Cancel"
/>
<Field
allowClear
name="promotion_uuid"
type="select"
icon=""
disabled={photoSliderLimit}
layout={formItemLayout}
label="Promotion Name"
placeholder="Promotion Name"
mode="single"
optionsList={promotionsOptions}
handleGetDate={handleGetDate}
component={Select}
handleAutoFillDeatils={handleAutoFillDeatils}
/>
<Field
name="title"
type="text"
icon=""
disabled={photoSliderLimit}
layout={formItemLayout}
label="Title"
placeholder="Title"
component={Inputs}
/>
<Field
name="description"
type="text"
icon=""
disabled={photoSliderLimit}
layout={formItemLayout}
label="Description"
placeholder="Description"
rows={6}
component={InputTextArea}
/>
<Field
isRatioMessage={{
message: <div style={{fontSize: '13px'}} >
<div>Image Size: 1020 x 621</div>
<div>Maximum File Size: 100KB</div>
</div>,
// dimensions: [1021,621]
}}
limit100kb
name="image"
type="file"
disabled={photoSliderLimit}
accept=".jpg , .png, .gif"
multiple={false}
imageUrl={props.values.image && `${props.values.image}`}
className="upload-list-inline"
icon="user"
layout={formItemLayout}
label="Upload Image"
placeholder="Upload Image"
component={UploadImage}
imgWidth="294px"
imgStyle={{width:"100%", height:"170"}}
handleFileUpload={handleFileUpload}
/>
<Field
name="date_start"
type="date"
icon=""
disabledDateStartEndPhotoSlider
dateStartEnd={props.values.promotion_uuid ? dateStartEnd : null}
disabled={photoSliderLimit}
layout={formItemLayout}
label="Start Appearance Date"
placeholder="Start Appearance Date"
component={DatePicker}
isAutoFill
/>
<Field
name="date_end"
type="date"
icon=""
disabledDateStart
disabledDateStartEndPhotoSlider
disabledDateStartEndPhotoSliderEndDate
dateStartEnd={props.values.promotion_uuid ? dateStartEnd : null}
disabled={photoSliderLimit}
layout={formItemLayout}
label="End Appearance Date"
placeholder="End Appearance Date"
component={DatePicker}
isAutoFill
/>
<Field
name="start_time"
type="date"
disabledHours={()=> {
if(props.values.promotion_uuid) {
let time = dateStartEnd && dateStartEnd.date_start && moment(dateStartEnd.date_start, 'YYYY-MM-DDTHH:mm:ss').format('HH:mm:ss').replace(/[^0-9]/g,'').substring(0, 2)
let disabledTime = [];
let timeLimit = time;
// if(time) {
// while(timeLimit > 0) {
// timeLimit--;
// disabledTime.push(timeLimit)
// }
// }
// return disabledTime
let date_start = moment(props.values.date_start).format('YYYY-MM-DD');
let promotion_date_start = moment(dateStartEnd.date_start).format('YYYY-MM-DD');
if(date_start != promotion_date_start) {
return []
} else {
if(time) {
while(timeLimit > 0) {
timeLimit--;
disabledTime.push(timeLimit)
}
}
return disabledTime
}
} else {
return []
}
}}
defaultOpenValue={moment('00:00', 'HH:mm')}
format={'HH:mm'}
icon=""
disabled={photoSliderLimit}
layout={formItemLayout}
label="Start Time"
placeholder="Start Time"
component={TimePickerForm}
/>
<Field
name="end_time"
type="date"
disabledHours={() => {
if(props.values.promotion_uuid) {
let time = dateStartEnd && dateStartEnd.date_end && moment(dateStartEnd.date_end, 'YYYY-MM-DDTHH:mm:ss').format('HH:mm:ss').replace(/[^0-9]/g,'').substring(0, 2)
let disabledEndTime = [];
let timeLimit = time;
// if(time) {
// while(timeLimit < 23) {
// timeLimit++;
// disabledEndTime.push(timeLimit)
// }
// }
// return disabledEndTime
let date_end = moment(props.values.date_end).format('YYYY-MM-DD');
let promotion_date_end = moment(dateStartEnd.date_end).format('YYYY-MM-DD');
if(date_end != promotion_date_end) {
return []
} else {
if(time) {
while(timeLimit < 23) {
timeLimit++;
disabledEndTime.push(timeLimit)
}
}
return disabledEndTime
}
} else {
return []
}
}}
defaultOpenValue={moment('00:00', 'HH:mm')}
format={'HH:mm'}
icon=""
disabled={photoSliderLimit}
layout={formItemLayout}
label="End Time"
placeholder="End Time"
component={TimePickerForm}
/>
</Form>
);
};
AddPhotoSliderForm = connect(
state => ({
}),
)(AddPhotoSliderForm);
export default AddPhotoSliderForm;

View File

@ -0,0 +1,266 @@
// LIBRARIES
import React, { Component } from 'react'
import { Formik } from 'formik'
import { message, notification } from 'antd'
import moment from 'moment'
// COMPONENTS
import HeaderForm from "components/Forms/HeaderForm"
import AddPhotoSliderForm from './components/AddPhotoSliderForm'
// HELPER FUNCTIONS
import { userDetailsSchema } from './validationSchema'
import { API_GET, API_POST, API_UNI_OIL } from "utils/Api"
import { apiFormValidation } from "utils/helper"
class CreatePhotoSlider extends Component {
state = {
loading: false,
promotionsOptions: null,
mounted: false,
photoSliderLimit: false,
dateStartEnd: null
}
async componentDidMount() {
try {
let photoSlider = await API_UNI_OIL('photoSliderCount');
if(photoSlider)
this.setState({photoSliderLimit: false})
} catch ({response:error}) {
this.setState({photoSliderLimit: true})
}
try {
let promotionsList = await API_GET('getPromotions');
if (promotionsList) {
let promotionsOptions = []
await promotionsList.data.data.map(item => {
promotionsOptions.push({
label: item.title,
value: item.promotion_uuid,
date: { dateStart: item.date_start , dateEnd: item.date_end }
})
})
this.setState({
promotionsOptions: promotionsOptions,
mounted: true
})
}
} catch ({ response: error }) {
notification.error({
message: "Error",
description: <div>
<div>Something went wrong loading data.</div>
- {error && error.data && error.data.message}
</div>,
duration: 3,
});
this.setState({ mounted: false })
}
}
handleSubmit = async (values, actions) => {
const { fileUpload } = this.state;
const { history } = this.props;
const { setErrors } = actions;
this.setState({ loading: true })
try {
const headers = {
'ContentType': 'multipart/form-data',
};
const formData = new FormData();
if (fileUpload) {
fileUpload.forEach((t, i) => {
formData.append(`image`, t.originFileObj);
});
} else {
if(values.image) {
let imageUrlPath = values.image
formData.append(`image`, imageUrlPath);
}
}
let date_start = moment(values.date_start).format('YYYY-MM-DD');
let start_time = moment(values.start_time).format('HH:mm:ss');
if(start_time == 'Invalid date') {
start_time = values.start_time
} else {
start_time = moment(values.start_time).format('HH:mm:ss');
}
let date_end = moment(values.date_end).format('YYYY-MM-DD');
let end_time = moment(values.end_time).format('HH:mm:ss');
if(end_time == 'Invalid date') {
end_time = values.end_time
} else {
end_time = moment(values.end_time).format('HH:mm:ss');
}
let startDateTime = moment(date_start + ' ' + start_time, 'YYYY-MM-DDTHH:mm:ss');
let endDateTime = moment(date_end + ' ' + end_time, 'YYYY-MM-DDTHH:mm:ss');
values.promotion_uuid && (formData.append('promotion_uuid', values.promotion_uuid));
values.title && (formData.append('title', values.title));
values.description && (formData.append('description', values.description));
values.date_start && (formData.append('date_start', startDateTime.format('YYYY-MM-DDTHH:mm:ss')));
values.date_end && (formData.append('date_end', endDateTime.format('YYYY-MM-DDTHH:mm:ss')));
let response = await API_UNI_OIL.post('photoSlider', formData, headers)
if (response) {
message.success('New record added.');
this.setState({ loading: false })
history.push({ pathname: "/home-page/photo-slider" })
}
} catch ({ response: error }) {
if (error.status === 422) {
apiFormValidation({ data: error.data.data, setErrors })
}
notification.error({
message: 'Error',
description: <div>
{error && error.data && error.data.data && error.data.data.image
&& (<div>{error.data.data.image[0] ? `- ${error.data.data.image[0]}` : "Something went wrong creating new record."} </div>) }
</div>
});
this.setState({ loading: false })
}
}
handleAddPhotoSlider = () => {
this.form.submitForm()
}
handleFileUpload = (e) => {
if (Array.isArray(e)) {
return this.setState({ fileUpload: e });
}
return e && this.setState({ fileUpload: e.fileList });
}
handleGetDate = async (id) => {
const {promotionsOptions} = this.state;
if(promotionsOptions) {
await promotionsOptions.map(item=> {
if(item.value == id) {
this.setState({
dateStartEnd: {
date_start: item.date.dateStart,
date_end: item.date.dateEnd
}
})
}
})
}
}
handleAutoFillDeatils = async (id,setFieldValue) => {
if(id) {
try {
let promotionsList = await API_GET(`promotion/${id}`);
let autofillData = {
...promotionsList.data.data
}
setFieldValue('title', autofillData.title);
setFieldValue('description', autofillData.description);
setFieldValue('image', `${autofillData.image}`);
setFieldValue('date_start', moment(autofillData.date_start, 'YYYY-MM-DDTHH:mm'));
setFieldValue('date_end', moment(autofillData.date_end, 'YYYY-MM-DDTHH:mm'));
setFieldValue('start_time', moment(autofillData.date_start, 'YYYY-MM-DDTHH:mm').format('HH:mm') );
setFieldValue('end_time', moment(autofillData.date_end, 'YYYY-MM-DDTHH:mm').format('HH:mm') );
this.setState({
isAutofill: true
})
} catch ({response: error}) {
notification.error({
message: 'Error',
description: <div>
Something went wrong autofill records.
- {error && error.data && error.data.message}
</div>
});
}
}
}
render() {
if (!this.state.mounted) return null;
const { loading, promotionsOptions, photoSliderLimit, dateStartEnd } = this.state
return (
<div style={{ border: '1px solid #E6ECF5', paddingBottom: '10px' }}>
{/* <HeaderForm
loading={loading}
title="Photo Slider"
action={this.handleAddPhotoSlider}
disabled={photoSliderLimit}
actionBtnName="Submit"
withCancelConfirm={{ message: 'Are you sure you want to discard changes?'}}
cancel={() => { this.props.history.push("/home-page/photo-slider") }}
cancelBtnName="Cancel"
/> */}
<div>
<h2 style={{ margin: '25px 35px' }}>Photo Slider Content Details</h2>
<Formik
initialValues={{
promotion_uuid: '',
title: '',
description: '',
image: '',
date_start: '',
date_end: '',
start_time: '',
end_time: '',
}}
ref={node => (this.form = node)}
enableReinitialize={true}
validationSchema={userDetailsSchema}
onSubmit={this.handleSubmit}
render={(props) =>
<AddPhotoSliderForm
{...props}
loading={loading}
history={this.props.history}
photoSliderLimit={photoSliderLimit}
promotionsOptions={promotionsOptions}
handleGetDate={this.handleGetDate}
dateStartEnd={dateStartEnd}
handleFileUpload={this.handleFileUpload}
handleAutoFillDeatils={this.handleAutoFillDeatils}
/>
}
/>
</div>
</div>
)
}
}
export default CreatePhotoSlider

View File

@ -0,0 +1,28 @@
import * as Yup from 'yup'
export const userDetailsSchema = Yup.object().shape({
promotion_uuid: Yup.string(),
//.required('Branches is required!'),
title: Yup.string()
.trim()
.max(128, "Maximum character is 128.")
.required('Title is required!'),
description: Yup.string()
.trim()
.max(32000, "Maximum character is 32,000."),
//.required('Description is required!'),
image: Yup.string()
.required('Upload Image is required!'),
date_start: Yup.string()
.required('Start Appearance Date is required!'),
date_end: Yup.string()
.required('End Appearance Date is required!'),
start_time: Yup.string()
.required('Start Time is required!'),
end_time: Yup.string()
.required('End Time is required!'),
})

View File

@ -0,0 +1,236 @@
// LIBRARIES
import React from 'react'
import { Row, Button, Col } from 'antd'
import { Form, Field } from 'formik'
import { connect } from 'react-redux'
import moment from 'moment'
// COMPONENTS
import HeaderForm from "components/Forms/HeaderForm"
import { Inputs, Select, DatePicker , InputTextArea, UploadImage ,TimePickerForm } from 'components/Forms'
// HELPER FUNCTIONS
const formItemLayout = {
labelCol: {
xs: { span: 24 },
sm: { span: 5 },
},
wrapperCol: {
xs: { span: 24 },
sm: { span: 10 },
},
};
function EditUserManagementForm(props) {
const {
isSubmitting,
loading,
handleSubmit,
promotionsDefaultValue,
promotionsOptions,
handleFileUpload,
dateStartEnd,
handleGetDate,
history,
handleAutoFillDeatils,
} = props;
return (
<Form noValidate>
<HeaderForm
isInsideForm
loading={loading}
disabled={props.isValid == false ? true : false}
title="Update Photo Slider"
action={handleSubmit}
actionBtnName="Submit"
withConfirm={{message: "Save changes to this record?"}}
withCancelConfirm={{ message: 'Are you sure you want to discard changes?'}}
cancel={()=> history.push("/home-page/photo-slider")}
cancelBtnName="Cancel"
/>
<Field
allowClear
name="promotion_uuid"
type="select"
icon=""
layout={formItemLayout}
label="Promotion Name"
placeholder="Promotion Name"
mode="single"
defaultValue={promotionsDefaultValue && (promotionsDefaultValue)}
optionsList={promotionsOptions}
handleGetDate={handleGetDate}
component={Select}
handleAutoFillDeatils={handleAutoFillDeatils}
/>
<Field
name="title"
type="text"
icon=""
layout={formItemLayout}
label="Title"
placeholder="Title"
component={Inputs}
/>
<Field
name="description"
type="text"
icon=""
layout={formItemLayout}
label="Description"
placeholder="Description"
rows={6}
component={InputTextArea}
/>
<Field
isRatioMessage={{
message: <div style={{fontSize: '13px'}} >
<div>Image Size: 1020 x 621</div>
<div>Maximum File Size: 100KB</div>
</div>,
// dimensions: [1021,621]
}}
limit100kb
name="image"
type="file"
accept=".jpg , .png, .gif"
multiple={false}
imageUrl={props.values.image && `${props.values.image}`}
className="upload-list-inline"
icon="user"
layout={formItemLayout}
label="Upload Image"
placeholder="Upload Image"
component={UploadImage}
imgWidth="294px"
imgStyle={{width:"100%", height:"170"}}
handleFileUpload={handleFileUpload}
/>
<Field
name="date_start"
type="date"
icon=""
isEdit
disabledDateStartEndPhotoSlider
dateStartEnd={props.values.promotion_uuid ? dateStartEnd : null }
defaultValue={ moment(props.values.date_start, 'YYYY-MM-DD') }
layout={formItemLayout}
label="Start Appearance Date"
placeholder="Start Appearance Date"
component={DatePicker}
isAutoFill
/>
<Field
name="date_end"
type="date"
icon=""
isEdit
disabledDateStart
disabledDateStartEndPhotoSlider
disabledDateStartEndPhotoSliderEndDate
dateStartEnd={props.values.promotion_uuid ? dateStartEnd : null }
defaultValue={ moment(props.values.date_end, 'YYYY-MM-DD') }
layout={formItemLayout}
label="End Appearance Date"
placeholder="End Appearance Date"
component={DatePicker}
isAutoFill
/>
<Field
name="start_time"
type="date"
icon=""
disabledHours={()=> {
if(props.values.promotion_uuid) {
let time = dateStartEnd && dateStartEnd.date_start && moment(dateStartEnd.date_start, 'YYYY-MM-DDTHH:mm').format('HH:mm').replace(/[^0-9]/g,'').substring(0, 2)
let disabledTime = [];
let timeLimit = time;
let date_start = moment(props.values.date_start).format('YYYY-MM-DD');
let promotion_date_start = moment(dateStartEnd.date_start).format('YYYY-MM-DD');
if(date_start != promotion_date_start) {
return []
} else {
if(time) {
while(timeLimit > 0) {
timeLimit--;
disabledTime.push(timeLimit)
}
}
return disabledTime
}
} else {
return []
}
}}
defaultValue={moment(props.values.start_time, 'HH:mm')}
format={'HH:mm'}
layout={formItemLayout}
label="Start Time"
placeholder="Start Time"
component={TimePickerForm}
/>
<Field
name="end_time"
type="date"
icon=""
disabledHours={() => {
if(props.values.promotion_uuid) {
let time = dateStartEnd && dateStartEnd.date_end && moment(dateStartEnd.date_end, 'YYYY-MM-DDTHH:mm').format('HH:mm').replace(/[^0-9]/g,'').substring(0, 2)
let disabledEndTime = [];
let timeLimit = time;
let date_end = moment(props.values.date_end).format('YYYY-MM-DD');
let promotion_date_end = moment(dateStartEnd.date_end).format('YYYY-MM-DD');
if(date_end != promotion_date_end) {
return []
} else {
if(time) {
while(timeLimit < 23) {
timeLimit++;
disabledEndTime.push(timeLimit)
}
}
return disabledEndTime
}
} else {
return []
}
}}
defaultValue={moment(props.values.end_time, 'HH:mm')}
format={'HH:mm'}
layout={formItemLayout}
label="End Time"
placeholder="End Time"
component={TimePickerForm}
/>
</Form>
);
};
EditUserManagementForm = connect(
state => ({
}),
)(EditUserManagementForm);
export default EditUserManagementForm;

View File

@ -0,0 +1,331 @@
// LIBRARIES
import React, { Component } from 'react'
import { Formik } from 'formik'
import moment from 'moment'
import { notification, message } from "antd"
// COMPONENTS
import HeaderForm from "components/Forms/HeaderForm"
import EditPhotoSliderForm from './components/EditPhotoSliderForm'
// HELPER FUNCTIONS
import { userDetailsSchema } from './validationSchema'
import { API_GET, API_PUT, API_POST } from "utils/Api";
import { API_UNI_OIL } from "utils/Api";
import { apiFormValidation } from "utils/helper";
class EditPhotoSlider extends Component {
state = {
loading: false,
userInfo: null,
mounted: false,
promotionsDefaultValue: null,
promotionsDefaultKeyValue: null,
promotionsOptions: null,
dateStartEnd: null
}
async componentDidMount() {
const { match } = this.props;
let promotionsDefaultValue = []
let promotionsDefaultKeyValue = []
try {
let response = await API_UNI_OIL.get(`photoSlider/${match.params.id}`);
// default options branch
let promotions = []
if(response.data.data.promotion) {
promotions.push({...response.data.data.promotion})
await promotions.map(item => {
promotionsDefaultValue.push(
item.title
)
promotionsDefaultKeyValue.push(
item.promotion_uuid
)
})
}
// default options promotype
let dateStartEnd = promotions && promotions[0] && {
date_start: promotions[0].date_start,
date_end: promotions[0].date_end
}
this.setState({
userInfo: {...response.data.data},
mounted: true,
promotionsDefaultValue,
promotionsDefaultKeyValue,
dateStartEnd: dateStartEnd
})
} catch ({response: error}) {
notification.error({
message: "Error",
description: <div>
<div>Something went wrong loading data.</div>
- { error && error.data && error.data.message }
</div> ,
duration: 3,
});
if(error.status == 404) {
if(this.props.location.pathname) {
this.props.history.push(`${this.props.location.pathname}/404`);
return;
}
} else {
this.setState({ mounted: false })
}
}
// options
try {
let params = {
selected_promotion: promotionsDefaultKeyValue.length > 0 && promotionsDefaultKeyValue[0]
}
let promotionsOptions = []; let promoTypeOptions = []
let promotionsList = await API_GET('getPromotions', params);
if(promotionsList) {
await promotionsList.data.data.map(item => {
promotionsOptions.push({
label: item.title,
value: item.promotion_uuid,
date: { dateStart: item.date_start , dateEnd: item.date_end }
})
})
}
this.setState({
promotionsOptions: promotionsOptions,
mounted: true
})
} catch ({response: error}) {
this.setState({ mounted: false })
}
}
handleSubmit = async (values, actions) => {
const { fileUpload, branchesOptions, userInfo } = this.state;
const { history } = this.props;
const { setErrors } = actions;
console.log(values, 'valuesvaluesvalues')
this.setState({loading: true})
try {
const headers = {
'ContentType': 'multipart/form-data',
};
const formData = new FormData();
if(fileUpload) {
fileUpload.forEach((t, i) => {
formData.append( `image`, t.originFileObj);
});
} else {
if(values.image) {
let imageUrlPath = values.image
formData.append(`image`, imageUrlPath);
}
}
let date_start = moment(values.date_start).format('YYYY-MM-DD');
let start_time = moment(values.start_time).format('HH:mm:ss');
if(start_time == 'Invalid date') {
start_time = values.start_time
} else {
start_time = moment(values.start_time).format('HH:mm:ss');
}
let date_end = moment(values.date_end).format('YYYY-MM-DD');
let end_time = moment(values.end_time).format('HH:mm:ss');
if(end_time == 'Invalid date') {
end_time = values.end_time
} else {
end_time = moment(values.end_time).format('HH:mm:ss');
}
let startDateTime = moment(date_start + ' ' + start_time, 'YYYY-MM-DDTHH:mm:ss');
let endDateTime = moment(date_end + ' ' + end_time, 'YYYY-MM-DDTHH:mm:ss');
// let branchesList = []
// values.promotion.map(item => {
// branchesOptions.map(userInfo => {
// if(userInfo.label == item || userInfo.value == item) {
// branchesList.push(userInfo.value);
// }
// })
// })
values.promotion_uuid && (formData.append('promotion_uuid', values.promotion_uuid ));
values.title && (formData.append('title', values.title));
values.description && (formData.append('description', values.description));
values.date_start && (formData.append('date_start', startDateTime.format('YYYY-MM-DDTHH:mm:ss') ) );
values.date_end && (formData.append('date_end', endDateTime.format('YYYY-MM-DDTHH:mm:ss') ) );
// log formdata
// for (var pair of formData.entries()) {
// console.log(pair[0]+ ', ' + pair[1]);
// }
let response = await API_UNI_OIL.post(`updatePhotoSlider/${userInfo.photoslider_uuid}`, formData , headers)
if(response) {
message.success('Record was successfully update.');
this.setState({loading: false})
history.push({ pathname: "/home-page/photo-slider" })
}
} catch ({response: error}) {
if (error.status === 422) {
apiFormValidation({ data: error.data.data, setErrors })
}
notification.error({
message: 'Error',
description: <div>
{ error && error.data && error.data.data && error.data.data.image
&& (<div>{error.data.data.image[0] ? `- ${error.data.data.image[0]}` : "Something went wrong updating record."} </div>) }
</div>
});
this.setState({loading: false})
}
}
handleEditPhotoSlider =()=> {
this.form.submitForm()
}
handleFileUpload =(e)=> {
if (Array.isArray(e)) {
return this.setState({fileUpload: e});
}
return e && this.setState({fileUpload: e.fileList});
}
handleGetDate = async (id) => {
const {promotionsOptions} = this.state;
if(promotionsOptions) {
await promotionsOptions.map(item=> {
if(item.value == id) {
this.setState({
dateStartEnd: {
date_start: item.date.dateStart,
date_end: item.date.dateEnd
}
})
}
})
}
}
handleAutoFillDeatils = async (id,setFieldValue) => {
if(id) {
try {
let promotionsList = await API_GET(`promotion/${id}`);
let autofillData = {
...promotionsList.data.data
}
setFieldValue('title', autofillData.title);
setFieldValue('description', autofillData.description);
setFieldValue('image', `${autofillData.image}`);
setFieldValue('date_start', moment(autofillData.date_start, 'YYYY-MM-DDTHH:mm'));
setFieldValue('date_end', moment(autofillData.date_end, 'YYYY-MM-DDTHH:mm'));
setFieldValue('start_time', moment(autofillData.date_start, 'YYYY-MM-DDTHH:mm').format('HH:mm') );
setFieldValue('end_time', moment(autofillData.date_end, 'YYYY-MM-DDTHH:mm').format('HH:mm') );
this.setState({
isAutofill: true
})
} catch ({response: error}) {
notification.error({
message: 'Error',
description: <div>
Something went wrong autofill records.
- {error && error.data && error.data.message}
</div>
});
}
}
}
render() {
if(!this.state.mounted) return null;
const { loading, userInfo, promotionsOptions, promotionsDefaultValue , promotionsDefaultKeyValue, dateStartEnd } = this.state
return (
<div style={{ border:'1px solid #E6ECF5' , paddingBottom: '10px'}}>
{/* <HeaderForm
loading={loading}
title="Update Photo Slider"
action={this.handleEditPhotoSlider}
actionBtnName="Submit"
withConfirm={{message: "Save changes to this record?"}}
withCancelConfirm={{ message: 'Are you sure you want to discard changes?'}}
cancel={()=> this.props.history.push("/home-page/photo-slider")}
cancelBtnName="Cancel"
/> */}
<div>
<h2 style={{margin: '25px 35px'}}>Photo Slider Content Details</h2>
<Formik
initialValues={{
promotion_uuid: promotionsDefaultKeyValue[0] || '',
title: userInfo.title || '',
description: userInfo.description || '',
image: userInfo.image || '',
date_start: moment(userInfo.date_start, 'YYYY-MM-DDTHH:mm').format('YYYY-MM-DD') || '',
date_end: moment( userInfo.date_end, 'YYYY-MM-DDTHH:mm').format('YYYY-MM-DD') || '',
start_time: moment(userInfo.date_start, 'YYYY-MM-DDTHH:mm').format('HH:mm') || '',
end_time: moment(userInfo.date_end, 'YYYY-MM-DDTHH:mm' ).format('HH:mm') || '',
}}
ref={node => (this.form = node)}
enableReinitialize={true}
validationSchema={userDetailsSchema}
onSubmit={this.handleSubmit }
render = {(props)=>
<EditPhotoSliderForm
{...props}
loading={loading}
history={this.props.history}
dateStartEnd={dateStartEnd}
promotionsOptions={promotionsOptions}
promotionsDefaultValue={promotionsDefaultValue}
handleFileUpload={this.handleFileUpload}
handleGetDate={this.handleGetDate}
handleAutoFillDeatils={this.handleAutoFillDeatils}
/>
}
/>
</div>
</div>
)
}
}
export default EditPhotoSlider

View File

@ -0,0 +1,28 @@
import * as Yup from 'yup'
export const userDetailsSchema = Yup.object().shape({
promotion_uuid: Yup.string(),
//.required('Promotion Name is required!'),
title: Yup.string()
.trim()
.max(128, "Maximum character is 128.")
.required('Title is required!'),
description: Yup.string()
.trim()
.max(32000, "Maximum character is 32,000."),
// .required('Description is required!'),
image: Yup.string()
.required('Upload Image is required!'),
date_start: Yup.string()
.required('Start Appearance Date is required!'),
date_end: Yup.string()
.required('End Appearance Date is required!'),
start_time: Yup.string()
.required('Start Time is required!'),
end_time: Yup.string()
.required('End Time is required!'),
})

Some files were not shown because too many files have changed in this diff Show More