diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 0000000..ea47730 --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,677 @@ +version: 2.1 + +orbs: + node: circleci/node@4.1.0 + +aliases: + - &install-node + name: Install Node with NPM using NVM + command: | + echo 'export NVM_DIR="/opt/circleci/.nvm"' >> $BASH_ENV + echo ' [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"' >> $BASH_ENV + nvm install v14 + nvm alias default v14 + source $BASH_ENV + node --version + + - &install-gradle + name: Install Gradle + command: | + wget https://services.gradle.org/distributions/gradle-4.0.2-bin.zip -P /tmp + sudo unzip -d /opt/gradle /tmp/gradle-*.zip + echo 'export GRADLE_HOME=/opt/gradle/gradle-4.0.2' >> $BASH_ENV + echo 'export PATH=$PATH:/opt/gradle/gradle-4.0.2/bin' >> $BASH_ENV + source $BASH_ENV + + - &install-deps + name: Install Global Dependencies + command: | + sudo apt-get update + sudo apt install npm + sudo apt install build-essential + sudo npm install --quiet node-gyp -g + sudo npm install cordova -g + sudo npm install ionic -g + sudo npm config set python /usr/bin/python + + - &install-chrome + name: Install Chrome + command: | + sudo apt install -y libappindicator3-1 + curl -L -o google-chrome.deb https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb + sudo dpkg -i google-chrome.deb + sudo sed -i 's|HERE/chrome\"|HERE/chrome\" --disable-setuid-sandbox|g' /opt/google/chrome/google-chrome + rm google-chrome.deb + + - &install-yarn + name: Install Latest Yarn + command: | + # remove default yarn + sudo rm -rf $(dirname $(which yarn))/yarn* + # download latest + rm -rf ~/.yarn + curl -o- -L https://yarnpkg.com/install.sh | bash + echo 'export PATH="${PATH}:${HOME}/.yarn/bin:${HOME}/.config/yarn/global/node_modules/.bin"' >> $BASH_ENV + source $BASH_ENV + + - &install-android + name: Install Android SDK + command: | + sudo apt-get update + sudo apt-get remove openjdk* + sudo apt-get clean + sudo add-apt-repository -y ppa:openjdk-r/ppa + sudo apt-get update + sudo apt-get install -y openjdk-8-jdk + sudo update-java-alternatives --set java-1.8.0-openjdk-amd64 + which java + java -version + sudo update-alternatives --install /usr/bin/java java /usr/lib/jvm/java-8-openjdk-amd64/jre/bin/java 1 --force + sudo update-alternatives --set java /usr/lib/jvm/java-8-openjdk-amd64/jre/bin/java + echo 'export JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64' >> $BASH_ENV + echo 'export PATH=$PATH:$JAVA_HOME/bin' >> $BASH_ENV + source $BASH_ENV + sudo apt install android-sdk + echo 'export ANDROID_SDK_ROOT="/usr/lib/android-sdk"' >> $BASH_ENV + echo 'export ANDROID_HOME="/usr/lib/android-sdk"' >> $BASH_ENV + source $BASH_ENV + wget https://dl.google.com/android/repository/commandlinetools-linux-6609375_latest.zip + unzip commandlinetools-linux-6609375_latest.zip -d cmdline-tools + sudo mv cmdline-tools $ANDROID_HOME/ + echo 'export PATH=$ANDROID_HOME/cmdline-tools/tools/bin:$ANDROID_HOME/tools:$ANDROID_HOME/tools/bin:$ANDROID_HOME/platform-tools:$PATH' >> $BASH_ENV + mkdir ~/.android && echo '### User Sources for Android SDK Manager' > ~/.android/repositories.cfg + sudo chown -R $(whoami) $ANDROID_HOME + echo "Installing SDK tools" + yes | sdkmanager "tools" --sdk_root=${ANDROID_HOME} || true + echo "Installing SDK platform-tools" + yes | sdkmanager "platform-tools" --sdk_root=${ANDROID_HOME} || true + echo "Installing SDK platforms;android-28" + yes | sdkmanager "platforms;android-28" --sdk_root=${ANDROID_HOME} || true + echo "Installing SDK platforms;android-29" + yes | sdkmanager "platforms;android-29" --sdk_root=${ANDROID_HOME} || true + echo "Installing SDK build-tools;28.0.3" + yes | sdkmanager "build-tools;28.0.3" --sdk_root=${ANDROID_HOME} || true + echo "Installing SDK build-tools;29.0.3" + yes | sdkmanager "build-tools;29.0.3" --sdk_root=${ANDROID_HOME} || true + echo "Accepting SDK Licenses" + yes | sdkmanager --licenses --sdk_root=${ANDROID_HOME} || true + echo "Updating SDK and accepting licenses" + yes | sudo /usr/lib/android-sdk/cmdline-tools/tools/bin/sdkmanager --update --sdk_root=${ANDROID_HOME} || true + source $BASH_ENV + echo $JAVA_HOME + echo $PATH + echo $ANDROID_HOME + echo $ANDROID_SDK_ROOT + which java + java -version + +defaults: &defaults + # put here anything which is common between all jobs + # we define default work dir, however almost every job redefine it + working_directory: /tmp/workspace + environment: + JVM_OPTS: -Xmx4096m + JAVA_HOME: /usr/lib/jvm/java-8-openjdk-amd64 + ANDROID_HOME: /usr/lib/android-sdk + +jobs: + build-monorepo-root: + <<: *defaults + working_directory: /tmp/workspace/monorepo-root + machine: + image: ubuntu-2004:202010-01 + steps: + - checkout + - run: *install-node + - run: *install-deps + - run: *install-yarn + - run: + name: 'Pull Submodules' + command: | + git submodule init + git submodule update --remote + - restore_cache: + name: Restore Yarn Package Cache + keys: + - yarn-packages-monorepo-root-{{ checksum "yarn.lock" }} + - run: + name: Run Bootstrap + no_output_timeout: 60m + command: yarn bootstrap + no_output_timeout: 60m + - run: + name: Run Build All + # note: it should be possible also to run: yarn build, + # but for now we don't want parallel builds in Circle + command: yarn build:all + no_output_timeout: 60m + - save_cache: + name: Save Yarn Package Cache + key: yarn-packages-monorepo-root-{{ checksum "yarn.lock" }} + paths: + - ~/.cache/yarn + - persist_to_workspace: + root: /tmp/workspace/monorepo-root + paths: + - '*' + build-backend-api: + <<: *defaults + working_directory: /tmp/workspace/backend/api + machine: + image: ubuntu-2004:202010-01 + steps: + - checkout + - run: *install-node + - run: *install-deps + - run: *install-yarn + - run: + name: 'Pull Submodules' + command: | + git submodule init + git submodule update --remote + - restore_cache: + name: Restore Yarn Package Cache + keys: + - yarn-packages-backend-api-{{ checksum "yarn.lock" }} + - run: + name: Run Bootstrap + no_output_timeout: 60m + command: yarn bootstrap + no_output_timeout: 60m + - run: + name: Run Build Common Packages + command: yarn build:common + no_output_timeout: 60m + - run: + name: Run Build + command: yarn build:server + no_output_timeout: 60m + - save_cache: + name: Save Yarn Package Cache + key: yarn-packages-backend-api-{{ checksum "yarn.lock" }} + paths: + - ~/.cache/yarn + - persist_to_workspace: + root: /tmp/workspace/backend/api + paths: + - '*' + build-admin-website-angular: + <<: *defaults + working_directory: /tmp/workspace/admin/website-angular + machine: + image: ubuntu-2004:202010-01 + steps: + - checkout + - run: *install-node + - run: *install-deps + - run: *install-yarn + - run: + name: 'Pull Submodules' + command: | + git submodule init + git submodule update --remote + - restore_cache: + name: Restore Yarn Package Cache + keys: + - yarn-packages-admin-website-angular-{{ checksum "yarn.lock" }} + - run: + name: Run Bootstrap + no_output_timeout: 60m + command: yarn bootstrap + no_output_timeout: 60m + - run: + name: Run Build Common Packages + command: yarn build:common + no_output_timeout: 60m + - run: + name: Run Build + command: yarn build:admin + no_output_timeout: 60m + - save_cache: + name: Save Yarn Package Cache + key: yarn-packages-admin-website-angular-{{ checksum "yarn.lock" }} + paths: + - ~/.cache/yarn + - persist_to_workspace: + root: /tmp/workspace/admin/website-angular + paths: + - '*' + build-carrier-mobile-ionic: + <<: *defaults + working_directory: /tmp/workspace/carrier/mobile-ionic + machine: + image: ubuntu-2004:202010-01 + steps: + - checkout + - run: *install-node + - run: *install-deps + - run: *install-yarn + - run: *install-android + - run: *install-gradle + - run: + name: 'Pull Submodules' + command: | + git submodule init + git submodule update --remote + - restore_cache: + name: Restore Yarn Package Cache + keys: + - yarn-packages-carrier-mobile-ionic-{{ checksum "yarn.lock" }} + - run: + name: Run Bootstrap + no_output_timeout: 60m + command: yarn bootstrap + no_output_timeout: 60m + - run: + name: Run Build Common Packages + command: yarn build:common + no_output_timeout: 60m + - run: + name: Run Build + command: yarn build:carrier + no_output_timeout: 60m + - run: + name: Run Ionic Build (Production) + command: cd packages/carrier-mobile-ionic && yarn ionic:build:prod + no_output_timeout: 60m + - run: + name: Add new android platform + command: | + cd packages/carrier-mobile-ionic + yarn ionic cordova platform add android@8.0.0 --noresources + ionic config set -g telemetry true + # echo y | android update sdk --no-ui --all --filter tools,platform-tools,extra-google-m2repository,extra-google-google_play_services,extra-android-support,extra-android-m2repository,android-25 + # echo y | android update sdk --no-ui --all --filter build-tools-25.0.0 + no_output_timeout: 60m + - run: + name: Run Cordova Build for Android (Debug) + command: | + cd packages/carrier-mobile-ionic + yarn cordova:build + mkdir -p /tmp/apk + cp -r platforms/android/app/build/outputs/apk/debug/app-debug.apk /tmp/apk/carrier.apk + no_output_timeout: 60m + - save_cache: + name: Save Yarn Package Cache + key: yarn-packages-carrier-mobile-ionic-{{ checksum "yarn.lock" }} + paths: + - ~/.cache/yarn + - store_artifacts: + path: /tmp/apk + destination: apks + - persist_to_workspace: + root: /tmp/workspace/carrier/mobile-ionic + paths: + - '*' + build-shop-mobile-ionic: + <<: *defaults + working_directory: /tmp/workspace/shop/mobile-ionic + machine: + image: ubuntu-2004:202010-01 + steps: + - checkout + - run: *install-node + - run: *install-deps + - run: *install-yarn + - run: *install-android + - run: *install-gradle + - run: + name: 'Pull Submodules' + command: | + git submodule init + git submodule update --remote + - restore_cache: + name: Restore Yarn Package Cache + keys: + - yarn-packages-shop-mobile-ionic-{{ checksum "yarn.lock" }} + - run: + name: Run Bootstrap + no_output_timeout: 60m + command: yarn bootstrap + no_output_timeout: 60m + - run: + name: Run Build Common Packages + command: yarn build:common + no_output_timeout: 60m + - run: + name: Run Build + command: yarn build:shopmobile + no_output_timeout: 60m + - run: + name: Run Ionic Build (Production) + command: cd packages/shop-mobile-ionic && yarn ionic:build:prod + no_output_timeout: 60m + - run: + name: Add new android platform + command: | + cd packages/shop-mobile-ionic + yarn ionic cordova platform add android@8.0.0 --noresources + ionic config set -g telemetry true + # echo y | android update sdk --no-ui --all --filter tools,platform-tools,extra-google-m2repository,extra-google-google_play_services,extra-android-support,extra-android-m2repository,android-25 + # echo y | android update sdk --no-ui --all --filter build-tools-25.0.0 + no_output_timeout: 60m + - run: + name: Run Cordova Build for Android (Debug) + command: | + cd packages/shop-mobile-ionic + yarn cordova:build + mkdir -p /tmp/apk + cp -r platforms/android/app/build/outputs/apk/debug/app-debug.apk /tmp/apk/shop.apk + no_output_timeout: 60m + - save_cache: + name: Save Yarn Package Cache + key: yarn-packages-shop-mobile-ionic-{{ checksum "yarn.lock" }} + paths: + - ~/.cache/yarn + - store_artifacts: + path: /tmp/apk + destination: apks + - persist_to_workspace: + root: /tmp/workspace/shop/mobile-ionic + paths: + - '*' + build-shop-website-angular: + <<: *defaults + working_directory: /tmp/workspace/shop/website-angular + machine: + image: ubuntu-2004:202010-01 + steps: + - checkout + - run: *install-node + - run: *install-deps + - run: *install-yarn + - run: + name: 'Pull Submodules' + command: | + git submodule init + git submodule update --remote + - restore_cache: + name: Restore Yarn Package Cache + keys: + - yarn-packages-shop-website-angular-{{ checksum "yarn.lock" }} + - run: + name: Run Bootstrap + no_output_timeout: 60m + command: yarn bootstrap + no_output_timeout: 60m + - run: + name: Run Build Common Packages + command: yarn build:common + no_output_timeout: 60m + - run: + name: Run Build + command: yarn build:shopweb + no_output_timeout: 60m + - save_cache: + name: Save Yarn Package Cache + key: yarn-packages-shop-website-angular-{{ checksum "yarn.lock" }} + paths: + - ~/.cache/yarn + - persist_to_workspace: + root: /tmp/workspace/shop/website-angular + paths: + - '*' + build-merchant-tablet-ionic: + <<: *defaults + working_directory: /tmp/workspace/merchant/tablet-ionic + machine: + image: ubuntu-2004:202010-01 + steps: + - checkout + - run: *install-node + - run: *install-deps + - run: *install-yarn + - run: *install-android + - run: *install-gradle + - run: + name: 'Pull Submodules' + command: | + git submodule init + git submodule update --remote + - restore_cache: + name: Restore Yarn Package Cache + keys: + - yarn-packages-merchant-tablet-ionic-{{ checksum "yarn.lock" }} + - run: + name: Run Bootstrap + no_output_timeout: 60m + command: yarn bootstrap + no_output_timeout: 60m + - run: + name: Run Build Common Packages + command: yarn build:common + no_output_timeout: 60m + - run: + name: Run Build + command: yarn build:merchant + no_output_timeout: 60m + - run: + name: Run Ionic Build (Production) + command: cd packages/merchant-tablet-ionic && yarn ionic:build:prod + no_output_timeout: 60m + - run: + name: Add new android platform + command: | + cd packages/merchant-tablet-ionic + yarn ionic cordova platform add android@8.0.0 --noresources + ionic config set -g telemetry true + # echo y | android update sdk --no-ui --all --filter tools,platform-tools,extra-google-m2repository,extra-google-google_play_services,extra-android-support,extra-android-m2repository,android-25 + # echo y | android update sdk --no-ui --all --filter build-tools-25.0.0 + no_output_timeout: 60m + - run: + name: Run Cordova Build for Android (Debug) + command: | + cd packages/merchant-tablet-ionic + yarn cordova:build + mkdir -p /tmp/apk + cp -r platforms/android/app/build/outputs/apk/debug/app-debug.apk /tmp/apk/merchant.apk + no_output_timeout: 60m + - save_cache: + name: Save Yarn Package Cache + key: yarn-packages-merchant-tablet-ionic-{{ checksum "yarn.lock" }} + paths: + - ~/.cache/yarn + - store_artifacts: + path: /tmp/apk + destination: apks + - persist_to_workspace: + root: /tmp/workspace/merchant/tablet-ionic + paths: + - '*' + test-carrier-mobile-ionic: + <<: *defaults + working_directory: /tmp/workspace/carrier/mobile-ionic + machine: + image: ubuntu-2004:202010-01 + steps: + - run: *install-node + - run: *install-deps + - run: *install-yarn + - run: *install-chrome + - attach_workspace: + at: /tmp/workspace/carrier/mobile-ionic + - run: + name: 'Pull Submodules' + command: | + git submodule init + git submodule update --remote + - restore_cache: + name: Restore Yarn Package Cache + keys: + - yarn-packages-carrier-mobile-ionic-{{ checksum "yarn.lock" }} + - run: + name: Install Dependencies + command: cd carrier/mobile-ionic && yarn install + no_output_timeout: 60m + - run: + name: test + command: cd carrier/mobile-ionic && yarn test + no_output_timeout: 60m + + test-shop-mobile-ionic: + <<: *defaults + working_directory: /tmp/workspace/shop/mobile-ionic + machine: + image: ubuntu-2004:202010-01 + steps: + - run: *install-node + - run: *install-deps + - run: *install-yarn + - run: *install-chrome + - attach_workspace: + at: /tmp/workspace/shop/mobile-ionic + - run: + name: 'Pull Submodules' + command: | + git submodule init + git submodule update --remote + - restore_cache: + name: Restore Yarn Package Cache + keys: + - yarn-packages-shop-mobile-ionic-{{ checksum "yarn.lock" }} + - run: + name: Install Dependencies + command: cd shop/mobile-ionic && yarn install + no_output_timeout: 60m + - run: + name: test + command: cd shop/mobile-ionic && yarn test + no_output_timeout: 60m + + test-shop-website-angular: + <<: *defaults + working_directory: /tmp/workspace/shop/website-angular + machine: + image: ubuntu-2004:202010-01 + steps: + - run: *install-node + - run: *install-deps + - run: *install-yarn + - run: *install-chrome + - attach_workspace: + at: /tmp/workspace/shop/website-angular + - run: + name: 'Pull Submodules' + command: | + git submodule init + git submodule update --remote + - restore_cache: + name: Restore Yarn Package Cache + keys: + - yarn-packages-shop-website-angular-{{ checksum "yarn.lock" }} + - run: + name: Install Dependencies + command: cd shop/website-angular && yarn install + no_output_timeout: 60m + - run: + name: test + command: cd shop/website-angular && yarn test + no_output_timeout: 60m + + test-admin-website-angular: + <<: *defaults + working_directory: /tmp/workspace/admin/website-angular + machine: + image: ubuntu-2004:202010-01 + steps: + - run: *install-node + - run: *install-deps + - run: *install-yarn + - run: *install-chrome + - attach_workspace: + at: /tmp/workspace/admin/website-angular + - run: + name: 'Pull Submodules' + command: | + git submodule init + git submodule update --remote + - restore_cache: + name: Restore Yarn Package Cache + keys: + - yarn-packages-admin-website-angular-{{ checksum "yarn.lock" }} + - run: + name: Install Dependencies + command: cd admin/website-angular && yarn install + no_output_timeout: 60m + - run: + name: test + command: cd admin/website-angular && yarn test + no_output_timeout: 60m + + test-backend-api: + <<: *defaults + working_directory: /tmp/workspace/backend/api + machine: + image: ubuntu-2004:202010-01 + steps: + - run: *install-node + - run: *install-deps + - run: *install-yarn + - run: *install-chrome + - attach_workspace: + at: /tmp/workspace/backend/api + - run: + name: 'Pull Submodules' + command: | + git submodule init + git submodule update --remote + - restore_cache: + name: Restore Yarn Package Cache + keys: + - yarn-packages-backend-api-{{ checksum "yarn.lock" }} + - run: + name: Install Dependencies + command: cd backend/api && yarn install + no_output_timeout: 60m + - run: + name: test + command: cd backend/api && yarn test + no_output_timeout: 60m + +workflows: + version: 2 + build_and_test: + jobs: + - build-monorepo-root + - build-backend-api + - build-admin-website-angular + - build-merchant-tablet-ionic + - build-shop-mobile-ionic + - build-shop-website-angular + - build-carrier-mobile-ionic + # - test-admin-website-angular: + # requires: + # - build-admin-website-angular + # filters: + # branches: + # only: master + # - test-backend-api: + # requires: + # - build-backend-api + # filters: + # branches: + # only: master + # - test-merchant-tablet-ionic: + # requires: + # - build-merchant-tablet-ionic + # filters: + # branches: + # only: master + # - test-carrier-mobile-ionic: + # requires: + # - build-carrier-mobile-ionic + # filters: + # branches: + # only: master + # - test-shop-mobile-ionic: + # requires: + # - build-shop-mobile-ionic + # filters: + # branches: + # only: master + # - test-shop-website-angular: + # requires: + # - build-shop-website-angular + # filters: + # branches: + # only: master diff --git a/.deploy/admin-web-angular/Dockerfile b/.deploy/admin-web-angular/Dockerfile new file mode 100644 index 0000000..bbce832 --- /dev/null +++ b/.deploy/admin-web-angular/Dockerfile @@ -0,0 +1,155 @@ +# Ever Demand Platform Admin UI (Angular) + +ARG API_BASE_URL +ARG API_HOST +ARG API_PORT +ARG CLIENT_BASE_URL +ARG DEMO +ARG WEB_HOST +ARG WEB_PORT +ARG HTTPS_SERVICES_ENDPOINT +ARG SERVICES_ENDPOINT +ARG GQL_ENDPOINT +ARG GQL_SUBSCRIPTIONS_ENDPOINT +ARG SENTRY_DSN +ARG CHATWOOT_SDK_TOKEN +ARG CLOUDINARY_CLOUD_NAME +ARG CLOUDINARY_API_KEY +ARG GOOGLE_MAPS_API_KEY +ARG GOOGLE_PLACE_AUTOCOMPLETE +ARG DEFAULT_LATITUDE +ARG DEFAULT_LONGITUDE +ARG DEFAULT_CURRENCY +ARG DEFAULT_LANGUAGE +ARG CURRENCY_SYMBOL +ARG NO_INTERNET_LOGO +ARG MAP_MERCHANT_ICON_LINK +ARG MAP_USER_ICON_LINK +ARG MAP_CARRIER_ICON_LINK +ARG API_FILE_UPLOAD_URL +ARG COMPANY_NAME +ARG COMPANY_SITE_LINK +ARG COMPANY_GITHUB_LINK +ARG COMPANY_FACEBOOK_LINK +ARG COMPANY_TWITTER_LINK +ARG COMPANY_LINKEDIN_LINK +ARG GENERATE_PASSWORD_CHARSET +ARG SETTINGS_APP_TYPE +ARG SETTINGS_MAINTENANCE_API_URL + +FROM node:16-alpine3.14 AS dependencies + +LABEL maintainer="ever@ever.co" +LABEL org.opencontainers.image.source https://github.com/ever-co/ever-demand + +ENV CI=true + +RUN apk --update add bash \ + && apk add libexecinfo libexecinfo-dev \ + && npm i -g npm \ + && apk add --no-cache --virtual build-dependencies build-base \ + snappy dos2unix g++ snappy-dev gcc libgcc libstdc++ linux-headers autoconf automake make nasm python2 py2-setuptools vips-dev git \ + && npm install --quiet node-gyp -g \ + && npm config set python /usr/bin/python \ + && npm install yarn -g --force \ + && mkdir /srv/ever && chown -R node:node /srv/ever + +COPY wait .deploy/admin-web-angular/entrypoint.compose.sh .deploy/admin-web-angular/entrypoint.prod.sh / + +RUN chmod +x /wait /entrypoint.compose.sh /entrypoint.prod.sh && dos2unix /entrypoint.compose.sh && dos2unix /entrypoint.prod.sh + +USER node:node + +WORKDIR /srv/ever + +COPY --chown=node:node package.json yarn.lock lerna.json package.workspaces.json tsconfig.base.json ./ +COPY --chown=node:node packages/admin-web-angular/package.json ./packages/admin-web-angular/package.json +COPY --chown=node:node .snyk ./.snyk +COPY --chown=node:node packages/common/package.json ./packages/common/package.json +COPY --chown=node:node packages/common-angular/package.json ./packages/common-angular/package.json + +RUN yarn bootstrap && yarn cache clean + +FROM node:16-alpine3.14 AS development + +USER node:node + +WORKDIR /srv/ever + +COPY --chown=node:node --from=dependencies /wait /entrypoint.compose.sh /entrypoint.prod.sh / +COPY --chown=node:node --from=dependencies /srv/ever . +COPY . . + +FROM node:16-alpine3.14 AS build + +WORKDIR /srv/ever + +RUN mkdir dist + +COPY --chown=node:node --from=development /srv/ever . + +ENV NODE_OPTIONS=${NODE_OPTIONS:-"--max-old-space-size=2048"} +ENV NODE_ENV=${NODE_ENV:-production} + +ENV IS_DOCKER=true + +RUN yarn build:admin + +FROM nginx:alpine AS production + +# USER nginx:nginx + +WORKDIR /srv/ever + +COPY --chown=nginx:nginx --from=dependencies /wait /entrypoint.prod.sh /entrypoint.compose.sh ./ +COPY --chown=nginx:nginx .deploy/admin-web-angular/nginx.compose.conf /etc/nginx/conf.d/compose.conf.template +COPY --chown=nginx:nginx .deploy/admin-web-angular/nginx.prod.conf /etc/nginx/conf.d/prod.conf.template +COPY --chown=nginx:nginx --from=build /srv/ever/packages/admin-web-angular/build . + +RUN chmod +x wait entrypoint.compose.sh entrypoint.prod.sh && \ + chmod a+rw /etc/nginx/conf.d/compose.conf.template /etc/nginx/conf.d/prod.conf.template + +ENV API_HOST=${API_HOST:-api} +ENV API_PORT=${API_PORT:-3000} + +ENV API_BASE_URL=${API_BASE_URL:-http://localhost:3000} +ENV CLIENT_BASE_URL=${CLIENT_BASE_URL:-http://localhost:4200} +ENV WEB_HOST=${WEB_HOST:-0.0.0.0} +ENV WEB_PORT=${WEB_PORT:-4200} +ENV DEMO=${DEMO:-false} + +ENV HTTPS_SERVICES_ENDPOINT=${HTTPS_SERVICES_ENDPOINT} +ENV SERVICES_ENDPOINT=${SERVICES_ENDPOINT} +ENV GQL_ENDPOINT=${GQL_ENDPOINT} +ENV GQL_SUBSCRIPTIONS_ENDPOINT=${GQL_SUBSCRIPTIONS_ENDPOINT} +ENV SENTRY_DSN=${SENTRY_DSN} +ENV CHATWOOT_SDK_TOKEN=${CHATWOOT_SDK_TOKEN} +ENV CLOUDINARY_CLOUD_NAME=${CLOUDINARY_CLOUD_NAME} +ENV CLOUDINARY_API_KEY=${CLOUDINARY_API_KEY} +ENV GOOGLE_MAPS_API_KEY=${GOOGLE_MAPS_API_KEY} +ENV GOOGLE_PLACE_AUTOCOMPLETE=${GOOGLE_PLACE_AUTOCOMPLETE:-false} +ENV DEFAULT_LATITUDE=${DEFAULT_LATITUDE:-42.6459136} +ENV DEFAULT_LONGITUDE=${DEFAULT_LONGITUDE:-23.3332736} +ENV DEFAULT_CURRENCY=${DEFAULT_CURRENCY:-USD}_DSN} +ENV DEFAULT_LANGUAGE=${DEFAULT_LANGUAGE} +ENV CURRENCY_SYMBOL=${CURRENCY_SYMBOL} +ENV NO_INTERNET_LOGO=${NO_INTERNET_LOGO} +ENV MAP_MERCHANT_ICON_LINK=${MAP_MERCHANT_ICON_LINK} +ENV MAP_USER_ICON_LINK=${MAP_USER_ICON_LINK} +ENV MAP_CARRIER_ICON_LINK=${MAP_CARRIER_ICON_LINK} +ENV API_FILE_UPLOAD_URL=${API_FILE_UPLOAD_URL} +ENV COMPANY_NAME=${COMPANY_NAME} +ENV COMPANY_SITE_LINK=${COMPANY_SITE_LINK} +ENV COMPANY_GITHUB_LINK=${COMPANY_GITHUB_LINK} +ENV COMPANY_FACEBOOK_LINK=${COMPANY_FACEBOOK_LINK} +ENV COMPANY_TWITTER_LINK=${COMPANY_TWITTER_LINK} +ENV COMPANY_LINKEDIN_LINK=${COMPANY_LINKEDIN_LINK} +ENV GENERATE_PASSWORD_CHARSET=${GENERATE_PASSWORD_CHARSET} +ENV SETTINGS_APP_TYPE=${SETTINGS_APP_TYPE} +ENV SETTINGS_MAINTENANCE_API_URL=${SETTINGS_MAINTENANCE_API_URL} + +EXPOSE ${WEB_PORT} + +ENTRYPOINT [ "./entrypoint.prod.sh" ] + +CMD [ "nginx", "-g", "daemon off;" ] diff --git a/.deploy/admin-web-angular/entrypoint.compose.sh b/.deploy/admin-web-angular/entrypoint.compose.sh new file mode 100644 index 0000000..cb8de1e --- /dev/null +++ b/.deploy/admin-web-angular/entrypoint.compose.sh @@ -0,0 +1,51 @@ +#!/bin/sh +set -ex + +# This Entrypoint used inside Docker Compose only + +export WAIT_HOSTS=$API_HOST:$API_PORT + +# In production we should replace some values in generated JS code +sed -i "s#DOCKER_API_BASE_URL#$API_BASE_URL#g" *.js +sed -i "s#DOCKER_CLIENT_BASE_URL#$CLIENT_BASE_URL#g" *.js +sed -i "s#DOCKER_API_HOST#$API_HOST#g" *.js +sed -i "s#DOCKER_API_PORT#$API_PORT#g" *.js +sed -i "s#DOCKER_WEB_HOST#$WEB_HOST#g" *.js +sed -i "s#DOCKER_WEB_PORT#$WEB_PORT#g" *.js +sed -i "s#DOCKER_HTTPS_SERVICES_ENDPOINT#$HTTPS_SERVICES_ENDPOINT#g" *.js +sed -i "s#DOCKER_SERVICES_ENDPOINT#$SERVICES_ENDPOINT#g" *.js +sed -i "s#DOCKER_GQL_ENDPOINT#$GQL_ENDPOINT#g" *.js +sed -i "s#DOCKER_GQL_SUBSCRIPTIONS_ENDPOINT#$GQL_SUBSCRIPTIONS_ENDPOINT#g" *.js +sed -i "s#DOCKER_SENTRY_DSN#$SENTRY_DSN#g" *.js +sed -i "s#DOCKER_CHATWOOT_SDK_TOKEN#$CHATWOOT_SDK_TOKEN#g" *.js +sed -i "s#DOCKER_CLOUDINARY_CLOUD_NAME#$CLOUDINARY_CLOUD_NAME#g" *.js +sed -i "s#DOCKER_CLOUDINARY_API_KEY#$CLOUDINARY_API_KEY#g" *.js +sed -i "s#DOCKER_GOOGLE_MAPS_API_KEY#$GOOGLE_MAPS_API_KEY#g" *.js +sed -i "s#DOCKER_GOOGLE_PLACE_AUTOCOMPLETE#$GOOGLE_PLACE_AUTOCOMPLETE#g" *.js +sed -i "s#DOCKER_DEFAULT_LATITUDE#$DEFAULT_LATITUDE#g" *.js +sed -i "s#DOCKER_DEFAULT_LONGITUDE#$DEFAULT_LONGITUDE#g" *.js +sed -i "s#DOCKER_DEFAULT_CURRENCY#$DEFAULT_CURRENCY#g" *.js +sed -i "s#DOCKER_DEFAULT_LANGUAGE#$DEFAULT_LANGUAGE#g" *.js +sed -i "s#DOCKER_CURRENCY_SYMBOL#$CURRENCY_SYMBOL#g" *.js +sed -i "s#DOCKER_NO_INTERNET_LOGO#$NO_INTERNET_LOGO#g" *.js +sed -i "s#DOCKER_MAP_MERCHANT_ICON_LINK#$MAP_MERCHANT_ICON_LINK#g" *.js +sed -i "s#DOCKER_MAP_USER_ICON_LINK#$MAP_USER_ICON_LINK#g" *.js +sed -i "s#DOCKER_MAP_CARRIER_ICON_LINK#$MAP_CARRIER_ICON_LINK#g" *.js +sed -i "s#DOCKER_API_FILE_UPLOAD_URL#$API_FILE_UPLOAD_URL#g" *.js +sed -i "s#DOCKER_COMPANY_NAME#$COMPANY_NAME#g" *.js +sed -i "s#DOCKER_COMPANY_SITE_LINK#$COMPANY_SITE_LINK#g" *.js +sed -i "s#DOCKER_COMPANY_GITHUB_LINK#$COMPANY_GITHUB_LINK#g" *.js +sed -i "s#DOCKER_COMPANY_FACEBOOK_LINK#$COMPANY_FACEBOOK_LINK#g" *.js +sed -i "s#DOCKER_COMPANY_TWITTER_LINK#$COMPANY_TWITTER_LINK#g" *.js +sed -i "s#DOCKER_COMPANY_LINKEDIN_LINK#$COMPANY_LINKEDIN_LINK#g" *.js +sed -i "s#DOCKER_GENERATE_PASSWORD_CHARSET#$GENERATE_PASSWORD_CHARSET#g" *.js +sed -i "s#DOCKER_SETTINGS_APP_TYPE#$SETTINGS_APP_TYPE#g" *.js +sed -i "s#DOCKER_SETTINGS_MAINTENANCE_API_URL#$SETTINGS_MAINTENANCE_API_URL#g" *.js +sed -i "s#DOCKER_DEMO#$DEMO#g" *.js + +envsubst '${API_HOST} ${API_PORT}' < /etc/nginx/conf.d/compose.conf.template > /etc/nginx/nginx.conf + +# in Docker Compose we should wait other services start +./wait + +exec "$@" \ No newline at end of file diff --git a/.deploy/admin-web-angular/entrypoint.prod.sh b/.deploy/admin-web-angular/entrypoint.prod.sh new file mode 100644 index 0000000..db995f1 --- /dev/null +++ b/.deploy/admin-web-angular/entrypoint.prod.sh @@ -0,0 +1,48 @@ +#!/bin/sh +set -ex + +# This Entrypoint used when we run Docker container outside of Docker Compose (e.g. in k8s) + +# In production we should replace some values in generated JS code +sed -i "s#DOCKER_API_BASE_URL#$API_BASE_URL#g" *.js +sed -i "s#DOCKER_CLIENT_BASE_URL#$CLIENT_BASE_URL#g" *.js +sed -i "s#DOCKER_API_HOST#$API_HOST#g" *.js +sed -i "s#DOCKER_API_PORT#$API_PORT#g" *.js +sed -i "s#DOCKER_WEB_HOST#$WEB_HOST#g" *.js +sed -i "s#DOCKER_WEB_PORT#$WEB_PORT#g" *.js +sed -i "s#DOCKER_HTTPS_SERVICES_ENDPOINT#$HTTPS_SERVICES_ENDPOINT#g" *.js +sed -i "s#DOCKER_SERVICES_ENDPOINT#$SERVICES_ENDPOINT#g" *.js +sed -i "s#DOCKER_GQL_ENDPOINT#$GQL_ENDPOINT#g" *.js +sed -i "s#DOCKER_GQL_SUBSCRIPTIONS_ENDPOINT#$GQL_SUBSCRIPTIONS_ENDPOINT#g" *.js +sed -i "s#DOCKER_SENTRY_DSN#$SENTRY_DSN#g" *.js +sed -i "s#DOCKER_CHATWOOT_SDK_TOKEN#$CHATWOOT_SDK_TOKEN#g" *.js +sed -i "s#DOCKER_CLOUDINARY_CLOUD_NAME#$CLOUDINARY_CLOUD_NAME#g" *.js +sed -i "s#DOCKER_CLOUDINARY_API_KEY#$CLOUDINARY_API_KEY#g" *.js +sed -i "s#DOCKER_GOOGLE_MAPS_API_KEY#$GOOGLE_MAPS_API_KEY#g" *.js +sed -i "s#DOCKER_GOOGLE_PLACE_AUTOCOMPLETE#$GOOGLE_PLACE_AUTOCOMPLETE#g" *.js +sed -i "s#DOCKER_DEFAULT_LATITUDE#$DEFAULT_LATITUDE#g" *.js +sed -i "s#DOCKER_DEFAULT_LONGITUDE#$DEFAULT_LONGITUDE#g" *.js +sed -i "s#DOCKER_DEFAULT_CURRENCY#$DEFAULT_CURRENCY#g" *.js +sed -i "s#DOCKER_DEFAULT_LANGUAGE#$DEFAULT_LANGUAGE#g" *.js +sed -i "s#DOCKER_CURRENCY_SYMBOL#$CURRENCY_SYMBOL#g" *.js +sed -i "s#DOCKER_NO_INTERNET_LOGO#$NO_INTERNET_LOGO#g" *.js +sed -i "s#DOCKER_MAP_MERCHANT_ICON_LINK#$MAP_MERCHANT_ICON_LINK#g" *.js +sed -i "s#DOCKER_MAP_USER_ICON_LINK#$MAP_USER_ICON_LINK#g" *.js +sed -i "s#DOCKER_MAP_CARRIER_ICON_LINK#$MAP_CARRIER_ICON_LINK#g" *.js +sed -i "s#DOCKER_API_FILE_UPLOAD_URL#$API_FILE_UPLOAD_URL#g" *.js +sed -i "s#DOCKER_COMPANY_NAME#$COMPANY_NAME#g" *.js +sed -i "s#DOCKER_COMPANY_SITE_LINK#$COMPANY_SITE_LINK#g" *.js +sed -i "s#DOCKER_COMPANY_GITHUB_LINK#$COMPANY_GITHUB_LINK#g" *.js +sed -i "s#DOCKER_COMPANY_FACEBOOK_LINK#$COMPANY_FACEBOOK_LINK#g" *.js +sed -i "s#DOCKER_COMPANY_TWITTER_LINK#$COMPANY_TWITTER_LINK#g" *.js +sed -i "s#DOCKER_COMPANY_LINKEDIN_LINK#$COMPANY_LINKEDIN_LINK#g" *.js +sed -i "s#DOCKER_GENERATE_PASSWORD_CHARSET#$GENERATE_PASSWORD_CHARSET#g" *.js +sed -i "s#DOCKER_SETTINGS_APP_TYPE#$SETTINGS_APP_TYPE#g" *.js +sed -i "s#DOCKER_SETTINGS_MAINTENANCE_API_URL#$SETTINGS_MAINTENANCE_API_URL#g" *.js +sed -i "s#DOCKER_DEMO#$DEMO#g" *.js + +# We may not need to use that env vars now in nginx.config, but we may want later. +# Also we just need to copy nginx.conf to correct place anyway... +envsubst '' < /etc/nginx/conf.d/prod.conf.template > /etc/nginx/nginx.conf + +exec "$@" diff --git a/.deploy/admin-web-angular/nginx.compose.conf b/.deploy/admin-web-angular/nginx.compose.conf new file mode 100644 index 0000000..959ab02 --- /dev/null +++ b/.deploy/admin-web-angular/nginx.compose.conf @@ -0,0 +1,37 @@ +user nginx; +error_log /var/log/nginx/error.log warn; +pid /var/run/nginx.pid; + +events { + worker_connections 1024; +} + +http { + include /etc/nginx/mime.types; + + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"'; + + access_log /var/log/nginx/access.log main; + + #gzip on; + + upstream api { + server ${API_HOST}:${API_PORT}; + } + + server { + listen 4200; + + location / { + root /srv/ever; + try_files $uri $uri/ /index.html; + } + + location /api/ { + proxy_pass http://api; + proxy_set_header Host $http_host; + } + } +} diff --git a/.deploy/admin-web-angular/nginx.prod.conf b/.deploy/admin-web-angular/nginx.prod.conf new file mode 100644 index 0000000..37c3f1b --- /dev/null +++ b/.deploy/admin-web-angular/nginx.prod.conf @@ -0,0 +1,28 @@ +user nginx; +error_log /var/log/nginx/error.log warn; +pid /var/run/nginx.pid; + +events { + worker_connections 1024; +} + +http { + include /etc/nginx/mime.types; + + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"'; + + access_log /var/log/nginx/access.log main; + + #gzip on; + + server { + listen 4200; + + location / { + root /srv/ever; + try_files $uri $uri/ /index.html; + } + } +} diff --git a/.deploy/api/Dockerfile b/.deploy/api/Dockerfile new file mode 100644 index 0000000..797bf2a --- /dev/null +++ b/.deploy/api/Dockerfile @@ -0,0 +1,219 @@ +# Ever Demand Platform API (Core) + +ARG NODE_OPTIONS +ARG NODE_ENV +ARG API_BASE_URL +ARG API_HOST +ARG API_PORT +ARG HTTPPORT +ARG HTTPSPORT +ARG GQLPORT +ARG GQLPORT_SUBSCRIPTIONS +ARG STRIPE_SECRET_KEY +ARG URBAN_AIRSHIP_KEY +ARG URBAN_AIRSHIP_SECRET +ARG KEYMETRICS_MACHINE_NAME +ARG KEYMETRICS_SECRET_KEY +ARG KEYMETRICS_PUBLIC_KEY +ARG GOOGLE_APP_ID +ARG GOOGLE_APP_SECRET +ARG FACEBOOK_APP_ID +ARG FACEBOOK_APP_SECRET +ARG JWT_SECRET +ARG EXPRESS_SESSION_SECRET +ARG SETTING_INVITES_ENABLED +ARG SETTINGS_REGISTRATIONS_REQUIRED_ON_START +ARG ADMIN_PASSWORD_RESET +ARG FAKE_DATA_GENERATOR +ARG ARCGIS_CLIENT_ID +ARG ARCGIS_CLIENT_SECRET +ARG IP_STACK_API_KEY +ARG ENGINE_API_KEY +ARG SENTRY_DSN +ARG DB_URI +ARG DB_HOST +ARG DB_NAME +ARG DB_PORT +ARG DB_USER +ARG DB_PASS +ARG DB_TYPE +ARG DB_SSL_MODE +ARG DB_CA_CERT +ARG DEMO +ARG HOST +ARG PORT +ARG AWS_ACCESS_KEY_ID +ARG AWS_SECRET_ACCESS_KEY +ARG AWS_REGION +ARG AWS_S3_BUCKET +ARG CLOUDINARY_API_KEY +ARG CLOUDINARY_API_SECRET +ARG CLOUDINARY_CLOUD_NAME +ARG MAIL_FROM_ADDRESS +ARG MAIL_HOST +ARG MAIL_PORT +ARG MAIL_USERNAME +ARG MAIL_PASSWORD +ARG ALLOW_SUPER_ADMIN_ROLE +ARG GOOGLE_CALLBACK_URL +ARG FACEBOOK_GRAPH_VERSION +ARG FACEBOOK_CALLBACK_URL +ARG UNLEASH_APP_NAME +ARG UNLEASH_API_URL +ARG UNLEASH_INSTANCE_ID +ARG UNLEASH_REFRESH_INTERVAL +ARG UNLEASH_METRICS_INTERVAL + +FROM node:16-alpine3.14 AS dependencies + +LABEL maintainer="ever@ever.co" +LABEL org.opencontainers.image.source https://github.com/ever-co/ever-demand + +ENV CI=true + +RUN apk --update add bash \ + && apk add libexecinfo libexecinfo-dev \ + && npm i -g npm \ + && apk add --no-cache --virtual build-dependencies build-base \ + snappy dos2unix g++ snappy-dev gcc libgcc libstdc++ linux-headers autoconf automake make nasm python2 py2-setuptools vips-dev git \ + && npm install --quiet node-gyp -g \ + && npm config set python /usr/bin/python \ + && npm install yarn -g --force \ + && mkdir /srv/ever && chown -R node:node /srv/ever + +COPY wait .deploy/api/entrypoint.compose.sh .deploy/api/entrypoint.prod.sh / +RUN chmod +x /wait /entrypoint.compose.sh /entrypoint.prod.sh && dos2unix /entrypoint.compose.sh && dos2unix /entrypoint.prod.sh + +USER node:node + +WORKDIR /srv/ever + +COPY --chown=node:node package.json yarn.lock lerna.json package.workspaces.json tsconfig.base.json ./ +COPY --chown=node:node packages/core/package.json ./packages/core/package.json +COPY --chown=node:node packages/common/package.json ./packages/common/package.json +COPY --chown=node:node .snyk ./.snyk +COPY --chown=node:node packages/core/.snyk ./packages/core/.snyk + +RUN yarn bootstrap && yarn cache clean + +FROM node:16-alpine3.14 AS development + +USER node:node + +WORKDIR /srv/ever + +COPY --chown=node:node --from=dependencies /wait /entrypoint.compose.sh /entrypoint.prod.sh / +COPY --chown=node:node --from=dependencies /srv/ever . +COPY . . + +FROM node:16-alpine3.14 AS build + +WORKDIR /srv/ever + +RUN mkdir dist + +COPY --chown=node:node --from=development /srv/ever . + +ENV NODE_OPTIONS=${NODE_OPTIONS:-"--max-old-space-size=2048"} +ENV NODE_ENV=${NODE_ENV:-production} +ENV DEMO=${DEMO:-false} + +ENV IS_DOCKER=true + +RUN yarn build:server + +FROM node:16-alpine3.14 AS production + +WORKDIR /srv/ever + +COPY --chown=node:node --from=dependencies /wait /entrypoint.prod.sh /entrypoint.compose.sh ./ +COPY --chown=node:node --from=dependencies /srv/ever/node_modules ./node_modules +COPY --chown=node:node --from=build /srv/ever/packages/common/ ./packages/common/ +COPY --chown=node:node --from=build /srv/ever/packages/core/ ./packages/core/ + +RUN chmod +x wait entrypoint.compose.sh entrypoint.prod.sh + +RUN npm install cross-env -g \ + && npm install pm2 -g --unsafe-perm \ + && touch ormlogs.log && chown node:node ormlogs.log \ + && chown node:node wait && chmod +x wait + +RUN mkdir tmp && cd tmp && mkdir logs && cd /srv/ever && chown -R node:node tmp/logs + +USER node:node + +ENV NODE_OPTIONS=${NODE_OPTIONS:-"--max-old-space-size=2048"} +ENV NODE_ENV=${NODE_ENV:-production} +ENV API_HOST=${API_HOST:-api} +ENV API_PORT=${API_PORT:-5500} +ENV API_BASE_URL=${API_BASE_URL:-http://localhost:5500} +ENV HTTPPORT=${HTTPPORT:-5500} +ENV HTTPSPORT=${HTTPSPORT:-2087} +ENV GQLPORT=${GQLPORT:-8443} +ENV GQLPORT_SUBSCRIPTIONS=${GQLPORT_SUBSCRIPTIONS:-2086} +ENV STRIPE_SECRET_KEY=${STRIPE_SECRET_KEY} +ENV URBAN_AIRSHIP_KEY=${URBAN_AIRSHIP_KEY} +ENV URBAN_AIRSHIP_SECRET=${URBAN_AIRSHIP_SECRET} +ENV KEYMETRICS_MACHINE_NAME=${KEYMETRICS_MACHINE_NAME} +ENV KEYMETRICS_SECRET_KEY=${KEYMETRICS_SECRET_KEY} +ENV KEYMETRICS_PUBLIC_KEY=${KEYMETRICS_PUBLIC_KEY} +ENV GOOGLE_APP_ID=${GOOGLE_APP_ID} +ENV GOOGLE_APP_SECRET=${GOOGLE_APP_SECRET} +ENV FACEBOOK_APP_ID=${FACEBOOK_APP_ID} +ENV FACEBOOK_APP_SECRET=${FACEBOOK_APP_SECRET} +ENV JWT_SECRET=${JWT_SECRET:-secretKey} +ENV EXPRESS_SESSION_SECRET=${EXPRESS_SESSION_SECRET:-demand} +ENV SETTING_INVITES_ENABLED=${SETTING_INVITES_ENABLED:-false} +ENV SETTINGS_REGISTRATIONS_REQUIRED_ON_START=${SETTINGS_REGISTRATIONS_REQUIRED_ON_START:-false} +ENV ADMIN_PASSWORD_RESET=${ADMIN_PASSWORD_RESET} +ENV FAKE_DATA_GENERATOR=${FAKE_DATA_GENERATOR} +ENV ARCGIS_CLIENT_ID=${ARCGIS_CLIENT_ID} +ENV ARCGIS_CLIENT_SECRET=${ARCGIS_CLIENT_SECRET} +ENV IP_STACK_API_KEY=${IP_STACK_API_KEY} +ENV ENGINE_API_KEY=${ENGINE_API_KEY} +ENV SENTRY_DSN=${SENTRY_DSN} +ENV DB_URI=${DB_URI:-mongodb://localhost/ever_development} +ENV DB_HOST=${DB_HOST:-localhost} +ENV DB_NAME=${DB_NAME:-ever_development} +ENV DB_PORT=${DB_PORT:-27017} +ENV DB_USER=${DB_USER} +ENV DB_PASS=${DB_PASS} +ENV DB_TYPE=${DB_TYPE:-mongodb} +ENV DB_SSL_MODE=${DB_SSL_MODE} +ENV DB_CA_CERT=${DB_CA_CERT} +ENV HOST=${HOST:-0.0.0.0} +ENV PORT=${PORT:-5500} +ENV DEMO=${DEMO:-false} +ENV AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID} +ENV AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY} +ENV AWS_REGION=${AWS_REGION} +ENV AWS_S3_BUCKET=${AWS_S3_BUCKET} +ENV CLOUDINARY_API_KEY=${CLOUDINARY_API_KEY} +ENV CLOUDINARY_API_SECRET=${CLOUDINARY_API_SECRET} +ENV CLOUDINARY_CLOUD_NAME=${CLOUDINARY_CLOUD_NAME} +ENV MAIL_FROM_ADDRESS=${MAIL_FROM_ADDRESS} +ENV MAIL_HOST=${MAIL_HOST} +ENV MAIL_PORT=${MAIL_PORT} +ENV MAIL_USERNAME=${MAIL_USERNAME} +ENV MAIL_PASSWORD=${MAIL_PASSWORD} +ENV ALLOW_SUPER_ADMIN_ROLE=${ALLOW_SUPER_ADMIN_ROLE} +ENV GOOGLE_CALLBACK_URL=${GOOGLE_CALLBACK_URL} +ENV FACEBOOK_GRAPH_VERSION=${FACEBOOK_GRAPH_VERSION} +ENV FACEBOOK_CALLBACK_URL=${FACEBOOK_CALLBACK_URL} +ENV UNLEASH_APP_NAME=${UNLEASH_APP_NAME} +ENV UNLEASH_API_URL=${UNLEASH_API_URL} +ENV UNLEASH_INSTANCE_ID=${UNLEASH_INSTANCE_ID} +ENV UNLEASH_REFRESH_INTERVAL=${UNLEASH_REFRESH_INTERVAL} +ENV UNLEASH_METRICS_INTERVAL=${UNLEASH_METRICS_INTERVAL} + +# 5500 for HTTP +# 2087 for HTTPS +# 8443 for GraphQL +# 2086 for GraphQL Subscriptions (WebSockets) + +EXPOSE ${HTTPPORT} ${HTTPSPORT} ${GQLPORT} ${GQLPORT_SUBSCRIPTIONS} + +ENTRYPOINT [ "./entrypoint.prod.sh" ] + +CMD [ "pm2-runtime", "packages/core/build/main.js" ] +# CMD [ "node", "packages/core/build/main.js" ] diff --git a/.deploy/api/entrypoint.compose.sh b/.deploy/api/entrypoint.compose.sh new file mode 100644 index 0000000..0632dfa --- /dev/null +++ b/.deploy/api/entrypoint.compose.sh @@ -0,0 +1,11 @@ +#!/bin/sh +set -ex + +# This Entrypoint used inside Docker Compose only + +export WAIT_HOSTS=$DB_HOST:$DB_PORT + +# in Docker Compose we should wait other services start +./wait + +exec "$@" diff --git a/.deploy/api/entrypoint.prod.sh b/.deploy/api/entrypoint.prod.sh new file mode 100644 index 0000000..6bfb10b --- /dev/null +++ b/.deploy/api/entrypoint.prod.sh @@ -0,0 +1,6 @@ +#!/bin/sh +set -ex + +# This Entrypoint used when we run Docker container outside of Docker Compose (e.g. in k8s) + +exec "$@" diff --git a/.deploy/carrier-mobile-ionic/Dockerfile b/.deploy/carrier-mobile-ionic/Dockerfile new file mode 100644 index 0000000..7cc70be --- /dev/null +++ b/.deploy/carrier-mobile-ionic/Dockerfile @@ -0,0 +1,157 @@ +# Ever Demand Platform Carrier Mobile App (Ionic) + +ARG API_BASE_URL +ARG API_HOST +ARG API_PORT +ARG CLIENT_BASE_URL +ARG DEMO +ARG WEB_HOST +ARG WEB_PORT +ARG HTTPS_SERVICES_ENDPOINT +ARG SERVICES_ENDPOINT +ARG GQL_ENDPOINT +ARG GQL_SUBSCRIPTIONS_ENDPOINT +ARG SENTRY_DSN +ARG CHATWOOT_SDK_TOKEN +ARG CLOUDINARY_CLOUD_NAME +ARG CLOUDINARY_API_KEY +ARG GOOGLE_MAPS_API_KEY +ARG GOOGLE_ANALYTICS_API_KEY +ARG GOOGLE_PLACE_AUTOCOMPLETE +ARG DEFAULT_LATITUDE +ARG DEFAULT_LONGITUDE +ARG DEFAULT_CURRENCY +ARG DEFAULT_LANGUAGE +ARG DEFAULT_CUSTOMER_LOGO +ARG LOGIN_LOGO +ARG NO_INTERNET_LOGO +ARG COMPANY_NAME +ARG APP_NAME +ARG MIXPANEL_API_KEY +ARG DEFAULT_LOGIN_USERNAME +ARG DEFAULT_LOGIN_PASSWORD +ARG SETTINGS_APP_TYPE +ARG SETTINGS_MAINTENANCE_API_URL + +FROM node:16-alpine3.14 AS dependencies + +LABEL maintainer="ever@ever.co" +LABEL org.opencontainers.image.source https://github.com/ever-co/ever-demand + +ENV CI=true + +RUN apk --update add bash \ + && apk add libexecinfo libexecinfo-dev \ + && npm i -g npm \ + && apk add --no-cache --virtual build-dependencies build-base \ + snappy dos2unix g++ snappy-dev gcc libgcc libstdc++ linux-headers autoconf automake make nasm python2 py2-setuptools vips-dev git \ + && npm install --quiet node-gyp -g \ + && npm config set python /usr/bin/python \ + && npm install yarn -g --force \ + && mkdir /srv/ever && chown -R node:node /srv/ever + +COPY wait .deploy/carrier-mobile-ionic/entrypoint.compose.sh .deploy/carrier-mobile-ionic/entrypoint.prod.sh / + +RUN chmod +x /wait /entrypoint.compose.sh /entrypoint.prod.sh && dos2unix /entrypoint.compose.sh && dos2unix /entrypoint.prod.sh + +USER node:node + +WORKDIR /srv/ever + +COPY --chown=node:node package.json yarn.lock lerna.json package.workspaces.json tsconfig.base.json ./ +COPY --chown=node:node packages/carrier-mobile-ionic/package.json ./packages/carrier-mobile-ionic/package.json +COPY --chown=node:node .snyk ./.snyk +COPY --chown=node:node packages/common/package.json ./packages/common/package.json +COPY --chown=node:node packages/common-angular/package.json ./packages/common-angular/package.json + +RUN yarn bootstrap && yarn cache clean + +FROM node:16-alpine3.14 AS development + +USER node:node + +WORKDIR /srv/ever + +COPY --chown=node:node --from=dependencies /wait /entrypoint.compose.sh /entrypoint.prod.sh / +COPY --chown=node:node --from=dependencies /srv/ever . +COPY . . + +FROM node:16-alpine3.14 AS build + +WORKDIR /srv/ever + +RUN mkdir www + +COPY --chown=node:node --from=development /srv/ever . + +ENV NODE_OPTIONS=${NODE_OPTIONS:-"--max-old-space-size=2048"} +ENV NODE_ENV=${NODE_ENV:-production} + +ENV IS_DOCKER=true + +RUN yarn build:carrier + +FROM nginx:alpine AS production + +# USER nginx:nginx + +WORKDIR /srv/ever + +COPY --chown=nginx:nginx --from=dependencies /wait /entrypoint.prod.sh /entrypoint.compose.sh ./ +COPY --chown=nginx:nginx .deploy/carrier-mobile-ionic/nginx.compose.conf /etc/nginx/conf.d/compose.conf.template +COPY --chown=nginx:nginx .deploy/carrier-mobile-ionic/nginx.prod.conf /etc/nginx/conf.d/prod.conf.template +COPY --chown=nginx:nginx --from=build /srv/ever/packages/carrier-mobile-ionic/www . + +RUN chmod +x wait entrypoint.compose.sh entrypoint.prod.sh && \ + chmod a+rw /etc/nginx/conf.d/compose.conf.template /etc/nginx/conf.d/prod.conf.template + +ENV API_HOST=${API_HOST:-api} +ENV API_PORT=${API_PORT:-3000} + +ENV API_BASE_URL=${API_BASE_URL:-http://localhost:3000} +ENV CLIENT_BASE_URL=${CLIENT_BASE_URL:-http://localhost:4203} +ENV WEB_HOST=${WEB_HOST:-0.0.0.0} +ENV WEB_PORT=${WEB_PORT:-4200} +ENV DEMO=${DEMO:-false} + +ENV HTTPS_SERVICES_ENDPOINT=${HTTPS_SERVICES_ENDPOINT} +ENV SERVICES_ENDPOINT=${SERVICES_ENDPOINT} +ENV GQL_ENDPOINT=${GQL_ENDPOINT} +ENV GQL_SUBSCRIPTIONS_ENDPOINT=${GQL_SUBSCRIPTIONS_ENDPOINT} +ENV SENTRY_DSN=${SENTRY_DSN} +ENV CHATWOOT_SDK_TOKEN=${CHATWOOT_SDK_TOKEN} +ENV CLOUDINARY_CLOUD_NAME=${CLOUDINARY_CLOUD_NAME} +ENV CLOUDINARY_API_KEY=${CLOUDINARY_API_KEY} +ENV GOOGLE_MAPS_API_KEY=${GOOGLE_MAPS_API_KEY} +ENV GOOGLE_PLACE_AUTOCOMPLETE=${GOOGLE_PLACE_AUTOCOMPLETE:-false} +ENV DEFAULT_LATITUDE=${DEFAULT_LATITUDE:-42.6459136} +ENV DEFAULT_LONGITUDE=${DEFAULT_LONGITUDE:-23.3332736} +ENV DEFAULT_CURRENCY=${DEFAULT_CURRENCY:-USD}_DSN} +ENV DEFAULT_LANGUAGE=${DEFAULT_LANGUAGE} +ENV DEFAULT_LOGIN_USERNAME=${DEFAULT_LOGIN_USERNAME} +ENV DEFAULT_LOGIN_PASSWORD=${DEFAULT_LOGIN_PASSWORD} +ENV DEFAULT_CUSTOMER_LOGO=${DEFAULT_CUSTOMER_LOGO} +ENV LOGIN_LOGO=${LOGIN_LOGO} +ENV MIXPANEL_API_KEY=${MIXPANEL_API_KEY} +ENV CURRENCY_SYMBOL=${CURRENCY_SYMBOL} +ENV NO_INTERNET_LOGO=${NO_INTERNET_LOGO} +ENV MAP_MERCHANT_ICON_LINK=${MAP_MERCHANT_ICON_LINK} +ENV MAP_USER_ICON_LINK=${MAP_USER_ICON_LINK} +ENV MAP_CARRIER_ICON_LINK=${MAP_CARRIER_ICON_LINK} +ENV API_FILE_UPLOAD_URL=${API_FILE_UPLOAD_URL} +ENV APP_NAME=${APP_NAME} +ENV COMPANY_NAME=${COMPANY_NAME} +ENV COMPANY_SITE_LINK=${COMPANY_SITE_LINK} +ENV COMPANY_GITHUB_LINK=${COMPANY_GITHUB_LINK} +ENV COMPANY_FACEBOOK_LINK=${COMPANY_FACEBOOK_LINK} +ENV COMPANY_TWITTER_LINK=${COMPANY_TWITTER_LINK} +ENV COMPANY_LINKEDIN_LINK=${COMPANY_LINKEDIN_LINK} +ENV GENERATE_PASSWORD_CHARSET=${GENERATE_PASSWORD_CHARSET} +ENV SETTINGS_APP_TYPE=${SETTINGS_APP_TYPE} +ENV SETTINGS_MAINTENANCE_API_URL=${SETTINGS_MAINTENANCE_API_URL} + +EXPOSE ${WEB_PORT} + +ENTRYPOINT [ "./entrypoint.prod.sh" ] + +CMD [ "nginx", "-g", "daemon off;" ] diff --git a/.deploy/carrier-mobile-ionic/entrypoint.compose.sh b/.deploy/carrier-mobile-ionic/entrypoint.compose.sh new file mode 100644 index 0000000..0fc9680 --- /dev/null +++ b/.deploy/carrier-mobile-ionic/entrypoint.compose.sh @@ -0,0 +1,27 @@ +#!/bin/sh +set -ex + +# This Entrypoint used inside Docker Compose only + +export WAIT_HOSTS=$API_HOST:$API_PORT + +# In production we should replace some values in generated JS code +sed -i "s#DOCKER_API_BASE_URL#$API_BASE_URL#g" *.js +sed -i "s#DOCKER_CLIENT_BASE_URL#$CLIENT_BASE_URL#g" *.js +sed -i "s#DOCKER_SENTRY_DSN#$SENTRY_DSN#g" *.js +sed -i "s#DOCKER_CLOUDINARY_CLOUD_NAME#$CLOUDINARY_CLOUD_NAME#g" *.js +sed -i "s#DOCKER_CLOUDINARY_API_KEY#$CLOUDINARY_API_KEY#g" *.js +sed -i "s#DOCKER_GOOGLE_MAPS_API_KEY#$GOOGLE_MAPS_API_KEY#g" *.js +sed -i "s#DOCKER_GOOGLE_PLACE_AUTOCOMPLETE#$GOOGLE_PLACE_AUTOCOMPLETE#g" *.js +sed -i "s#DOCKER_DEFAULT_LATITUDE#$DEFAULT_LATITUDE#g" *.js +sed -i "s#DOCKER_DEFAULT_LONGITUDE#$DEFAULT_LONGITUDE#g" *.js +sed -i "s#DOCKER_DEFAULT_CURRENCY#$DEFAULT_CURRENCY#g" *.js +sed -i "s#DOCKER_CHATWOOT_SDK_TOKEN#$CHATWOOT_SDK_TOKEN#g" *.js +sed -i "s#DOCKER_DEMO#$DEMO#g" *.js + +envsubst '${API_HOST} ${API_PORT}' < /etc/nginx/conf.d/compose.conf.template > /etc/nginx/nginx.conf + +# in Docker Compose we should wait other services start +./wait + +exec "$@" \ No newline at end of file diff --git a/.deploy/carrier-mobile-ionic/entrypoint.prod.sh b/.deploy/carrier-mobile-ionic/entrypoint.prod.sh new file mode 100644 index 0000000..6fb2f30 --- /dev/null +++ b/.deploy/carrier-mobile-ionic/entrypoint.prod.sh @@ -0,0 +1,24 @@ +#!/bin/sh +set -ex + +# This Entrypoint used when we run Docker container outside of Docker Compose (e.g. in k8s) + +# In production we should replace some values in generated JS code +sed -i "s#DOCKER_API_BASE_URL#$API_BASE_URL#g" *.js +sed -i "s#DOCKER_CLIENT_BASE_URL#$CLIENT_BASE_URL#g" *.js +sed -i "s#DOCKER_SENTRY_DSN#$SENTRY_DSN#g" *.js +sed -i "s#DOCKER_CLOUDINARY_CLOUD_NAME#$CLOUDINARY_CLOUD_NAME#g" *.js +sed -i "s#DOCKER_CLOUDINARY_API_KEY#$CLOUDINARY_API_KEY#g" *.js +sed -i "s#DOCKER_GOOGLE_MAPS_API_KEY#$GOOGLE_MAPS_API_KEY#g" *.js +sed -i "s#DOCKER_GOOGLE_PLACE_AUTOCOMPLETE#$GOOGLE_PLACE_AUTOCOMPLETE#g" *.js +sed -i "s#DOCKER_DEFAULT_LATITUDE#$DEFAULT_LATITUDE#g" *.js +sed -i "s#DOCKER_DEFAULT_LONGITUDE#$DEFAULT_LONGITUDE#g" *.js +sed -i "s#DOCKER_DEFAULT_CURRENCY#$DEFAULT_CURRENCY#g" *.js +sed -i "s#DOCKER_CHATWOOT_SDK_TOKEN#$CHATWOOT_SDK_TOKEN#g" *.js +sed -i "s#DOCKER_DEMO#$DEMO#g" *.js + +# We may not need to use that env vars now in nginx.config, but we may want later. +# Also we just need to copy nginx.conf to correct place anyway... +envsubst '' < /etc/nginx/conf.d/prod.conf.template > /etc/nginx/nginx.conf + +exec "$@" diff --git a/.deploy/carrier-mobile-ionic/nginx.compose.conf b/.deploy/carrier-mobile-ionic/nginx.compose.conf new file mode 100644 index 0000000..4a68e4e --- /dev/null +++ b/.deploy/carrier-mobile-ionic/nginx.compose.conf @@ -0,0 +1,37 @@ +user nginx; +error_log /var/log/nginx/error.log warn; +pid /var/run/nginx.pid; + +events { + worker_connections 1024; +} + +http { + include /etc/nginx/mime.types; + + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"'; + + access_log /var/log/nginx/access.log main; + + #gzip on; + + upstream api { + server ${API_HOST}:${API_PORT}; + } + + server { + listen 4203; + + location / { + root /srv/ever; + try_files $uri $uri/ /index.html; + } + + location /api/ { + proxy_pass http://api; + proxy_set_header Host $http_host; + } + } +} diff --git a/.deploy/carrier-mobile-ionic/nginx.prod.conf b/.deploy/carrier-mobile-ionic/nginx.prod.conf new file mode 100644 index 0000000..155ee94 --- /dev/null +++ b/.deploy/carrier-mobile-ionic/nginx.prod.conf @@ -0,0 +1,28 @@ +user nginx; +error_log /var/log/nginx/error.log warn; +pid /var/run/nginx.pid; + +events { + worker_connections 1024; +} + +http { + include /etc/nginx/mime.types; + + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"'; + + access_log /var/log/nginx/access.log main; + + #gzip on; + + server { + listen 4203; + + location / { + root /srv/ever; + try_files $uri $uri/ /index.html; + } + } +} diff --git a/.deploy/k8s/README.md b/.deploy/k8s/README.md new file mode 100644 index 0000000..67e11f9 --- /dev/null +++ b/.deploy/k8s/README.md @@ -0,0 +1,50 @@ +# Kubernetes + +Let's assume current k8s kube config file saved as `k8s-ever-kubeconfig.yaml` + +## Verify connectivity + +kubectl --kubeconfig="k8s-ever-kubeconfig.yaml" get nodes + +## Deploy + +- To Deploy + +`kubectl --kubeconfig="k8s-ever-kubeconfig.yaml" --context do-sfo2-k8s-ever apply -f k8s-manifest.yaml` + +Note: it assume we have context called `do-sfo2-k8s-ever` defined already + +- To Describe Deployment + +`kubectl describe deployment --kubeconfig="k8s-ever-kubeconfig.yaml"` + +- To Redeploy (if use latest docker images versions) + +`kubectl --kubeconfig="k8s-ever-kubeconfig.yaml" --context do-sfo2-k8s-ever rollout restart -f k8s-manifest.yaml` + +## Monitoring + +See + +`kubectl --kubeconfig="k8s-ever-kubeconfig.yaml" port-forward svc/kube-prometheus-stack-grafana 8090:80 -n kube-prometheus-stack` + +Your Grafana instance will now be available at http://localhost:8090. + +Default credentials: + +Username: admin +Password: prom-operator + +Note: change password after login! + +`kubectl --kubeconfig="k8s-ever-kubeconfig.yaml" port-forward svc/kube-prometheus-stack-prometheus 9090 -n kube-prometheus-stack` + +Your Prometheus instance will now be available at http://localhost:9090. + +## Links + +- [DO, WebSockets, Load Balancers](https://medium.com/swlh/how-to-use-web-sockets-socket-io-with-digital-ocean-load-balancers-and-kubernetes-dok8s-with-e4dd5531c67e) +- [Load Balancers Configs in DO](https://www.digitalocean.com/docs/kubernetes/how-to/configure-load-balancers) +- [K8s Ingress](https://kubernetes.io/docs/concepts/services-networking/ingress/#ingress-controllers) +- [K8s Ingress with Nginx + SSL LetsEncrypt](https://www.digitalocean.com/community/tutorials/how-to-set-up-an-nginx-ingress-with-cert-manager-on-digitalocean-kubernetes) +- [Github Actions with DO](https://github.com/do-community/example-doctl-action/blob/master/.github/workflows/workflow.yaml) diff --git a/.deploy/k8s/k8s-manifest.demo.yaml b/.deploy/k8s/k8s-manifest.demo.yaml new file mode 100644 index 0000000..6ee433b --- /dev/null +++ b/.deploy/k8s/k8s-manifest.demo.yaml @@ -0,0 +1,569 @@ +--- +kind: Service +apiVersion: v1 +metadata: + name: ever-demo-admin-lb + annotations: + service.beta.kubernetes.io/do-loadbalancer-name: 'admindemo.ever.co' + service.beta.kubernetes.io/do-loadbalancer-protocol: 'http2' + service.beta.kubernetes.io/do-loadbalancer-http2-ports: '443' + # Replace with your Certificate Id. You can get a list of Ids with 'doctl compute certificate list' + service.beta.kubernetes.io/do-loadbalancer-certificate-id: 'a93346c1-d63b-4c33-84c5-4589787428ca' + service.beta.kubernetes.io/do-loadbalancer-size-slug: 'lb-small' + service.beta.kubernetes.io/do-loadbalancer-hostname: 'admindemo.ever.co' +spec: + type: LoadBalancer + selector: + app: ever-demo-admin + ports: + - name: http + protocol: TCP + port: 443 + targetPort: 4200 + +--- +kind: Service +apiVersion: v1 +metadata: + name: ever-demo-shop-lb + annotations: + service.beta.kubernetes.io/do-loadbalancer-name: 'shopdemo.ever.co' + service.beta.kubernetes.io/do-loadbalancer-protocol: 'http2' + service.beta.kubernetes.io/do-loadbalancer-http2-ports: '443' + # Replace with your Certificate Id. You can get a list of Ids with 'doctl compute certificate list' + service.beta.kubernetes.io/do-loadbalancer-certificate-id: 'a93346c1-d63b-4c33-84c5-4589787428ca' + service.beta.kubernetes.io/do-loadbalancer-size-slug: 'lb-small' + service.beta.kubernetes.io/do-loadbalancer-hostname: 'shopdemo.ever.co' +spec: + type: LoadBalancer + selector: + app: ever-demo-shop-web-angular + ports: + - name: http + protocol: TCP + port: 443 + targetPort: 4200 + +--- +kind: Service +apiVersion: v1 +metadata: + name: ever-demo-mobile-shop-lb + annotations: + service.beta.kubernetes.io/do-loadbalancer-name: 'mobileshopdemo.ever.co' + service.beta.kubernetes.io/do-loadbalancer-protocol: 'http2' + service.beta.kubernetes.io/do-loadbalancer-http2-ports: '443' + # Replace with your Certificate Id. You can get a list of Ids with 'doctl compute certificate list' + service.beta.kubernetes.io/do-loadbalancer-certificate-id: 'a93346c1-d63b-4c33-84c5-4589787428ca' + service.beta.kubernetes.io/do-loadbalancer-size-slug: 'lb-small' + service.beta.kubernetes.io/do-loadbalancer-hostname: 'mobileshopdemo.ever.co' +spec: + type: LoadBalancer + selector: + app: ever-demo-shop-mobile-ionic + ports: + - name: http + protocol: TCP + port: 443 + targetPort: 4201 + +--- +kind: Service +apiVersion: v1 +metadata: + name: ever-demo-carrier-mobile-ionic-lb + annotations: + service.beta.kubernetes.io/do-loadbalancer-name: 'mobilecarrierdemo.ever.co' + service.beta.kubernetes.io/do-loadbalancer-protocol: 'http2' + service.beta.kubernetes.io/do-loadbalancer-http2-ports: '443' + # Replace with your Certificate Id. You can get a list of Ids with 'doctl compute certificate list' + service.beta.kubernetes.io/do-loadbalancer-certificate-id: 'a93346c1-d63b-4c33-84c5-4589787428ca' + service.beta.kubernetes.io/do-loadbalancer-size-slug: 'lb-small' + service.beta.kubernetes.io/do-loadbalancer-hostname: 'mobilecarrierdemo.ever.co' +spec: + type: LoadBalancer + selector: + app: ever-demo-carrier-mobile-ionic + ports: + - name: http + protocol: TCP + port: 443 + targetPort: 4203 + +--- +kind: Service +apiVersion: v1 +metadata: + name: ever-demo-merchant-tablet-ionic-lb + annotations: + service.beta.kubernetes.io/do-loadbalancer-name: 'merchantdemo.ever.co' + service.beta.kubernetes.io/do-loadbalancer-protocol: 'http2' + service.beta.kubernetes.io/do-loadbalancer-http2-ports: '443' + # Replace with your Certificate Id. You can get a list of Ids with 'doctl compute certificate list' + service.beta.kubernetes.io/do-loadbalancer-certificate-id: 'a93346c1-d63b-4c33-84c5-4589787428ca' + service.beta.kubernetes.io/do-loadbalancer-size-slug: 'lb-small' + service.beta.kubernetes.io/do-loadbalancer-hostname: 'merchantdemo.ever.co' +spec: + type: LoadBalancer + selector: + app: ever-demo-merchant-tablet-ionic + ports: + - name: http + protocol: TCP + port: 443 + targetPort: 4202 + +--- +kind: Service +apiVersion: v1 +metadata: + name: ever-demo-api-lb + annotations: + service.beta.kubernetes.io/do-loadbalancer-name: 'apidemo.ever.co' + service.beta.kubernetes.io/do-loadbalancer-protocol: 'http2' + + # GraphQL Subscriptions use WebSockets on Port 2086 + service.beta.kubernetes.io/do-loadbalancer-http-ports: '2086' + + # Rest API works on 443, GraphQL API works on 8443 + service.beta.kubernetes.io/do-loadbalancer-http2-ports: '443,8443' + + # Replace with your Certificate Id. You can get a list of Ids with 'doctl compute certificate list' + service.beta.kubernetes.io/do-loadbalancer-certificate-id: 'a93346c1-d63b-4c33-84c5-4589787428ca' + service.beta.kubernetes.io/do-loadbalancer-size-slug: 'lb-small' + service.beta.kubernetes.io/do-loadbalancer-hostname: 'apidemo.ever.co' + + service.beta.kubernetes.io/do-loadbalancer-sticky-sessions-type: 'cookies' + service.beta.kubernetes.io/do-loadbalancer-sticky-sessions-cookie-name: 'route' + service.beta.kubernetes.io/do-loadbalancer-sticky-sessions-cookie-ttl: '34650' +spec: + type: LoadBalancer + selector: + app: ever-demo-api + ports: + - name: rest-api-http + protocol: TCP + port: 443 + targetPort: 5500 + - name: gql-api-http + protocol: TCP + port: 8443 + targetPort: 8443 + - name: gql-subscriptions-ws + protocol: TCP + port: 2086 + targetPort: 2086 + +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: ever-demo-api +spec: + replicas: 1 + selector: + matchLabels: + app: ever-demo-api + template: + metadata: + labels: + app: ever-demo-api + spec: + containers: + - name: ever-demo-api + image: ghcr.io/ever-co/ever-api:latest + env: + - name: HOST + value: 0.0.0.0 + - name: DEMO + value: 'true' + - name: NODE_ENV + value: 'production' + - name: API_HOST + value: '0.0.0.0' + - name: HTTPPORT + value: '5500' + # We use LB, so no need to expose HTTPS, only HTTPS required + # - name: HTTPSPORT + # value: '2087' + - name: GQLPORT + value: '8443' + # WebSockets (WS) used for GraphQL Subscriptions + - name: GQLPORT_SUBSCRIPTIONS + value: '2086' + - name: ADMIN_PASSWORD_RESET + value: 'true' + - name: FAKE_DATA_GENERATOR + value: 'true' + - name: SETTING_INVITES_ENABLED + value: 'true' + - name: SETTINGS_REGISTRATIONS_REQUIRED_ON_START + value: 'false' + - name: LOG_LEVEL + value: 'info' + - name: SENTRY_DSN + value: '$SENTRY_DSN' + - name: DB_URI + value: '$DB_URI' + - name: DB_HOST + value: '$DB_HOST' + - name: DB_SSL_MODE + value: '$DB_SSL_MODE' + - name: DB_CA_CERT + value: '$DB_CA_CERT' + - name: DB_USER + value: '$DB_USER' + - name: DB_PASS + value: '$DB_PASS' + - name: DB_TYPE + value: 'mongodb' + - name: DB_NAME + value: '$DB_NAME' + - name: DB_PORT + value: '$DB_PORT' + - name: AWS_ACCESS_KEY_ID + value: '$AWS_ACCESS_KEY_ID' + - name: AWS_SECRET_ACCESS_KEY + value: '$AWS_SECRET_ACCESS_KEY' + - name: AWS_REGION + value: '$AWS_REGION' + - name: AWS_S3_BUCKET + value: '$AWS_S3_BUCKET' + - name: EXPRESS_SESSION_SECRET + value: '$EXPRESS_SESSION_SECRET' + - name: JWT_SECRET + value: '$JWT_SECRET' + - name: CLOUDINARY_API_KEY + value: '$CLOUDINARY_API_KEY' + - name: CLOUDINARY_API_SECRET + value: '$CLOUDINARY_API_SECRET' + - name: CLOUDINARY_CLOUD_NAME + value: '$CLOUDINARY_CLOUD_NAME' + - name: MAIL_FROM_ADDRESS + value: '$MAIL_FROM_ADDRESS' + - name: MAIL_HOST + value: '$MAIL_HOST' + - name: MAIL_PORT + value: '$MAIL_PORT' + - name: MAIL_USERNAME + value: '$MAIL_USERNAME' + - name: MAIL_PASSWORD + value: '$MAIL_PASSWORD' + - name: ALLOW_SUPER_ADMIN_ROLE + value: '$ALLOW_SUPER_ADMIN_ROLE' + - name: GOOGLE_APP_ID + value: '$GOOGLE_APP_ID' + - name: GOOGLE_APP_SECRET + value: '$GOOGLE_APP_SECRET' + - name: GOOGLE_CALLBACK_URL + value: '$GOOGLE_CALLBACK_URL' + - name: FACEBOOK_APP_ID + value: '$FACEBOOK_APP_ID' + - name: FACEBOOK_APP_SECRET + value: '$FACEBOOK_APP_SECRET' + - name: FACEBOOK_GRAPH_VERSION + value: '$FACEBOOK_GRAPH_VERSION' + - name: FACEBOOK_CALLBACK_URL + value: '$FACEBOOK_CALLBACK_URL' + - name: UNLEASH_APP_NAME + value: '$UNLEASH_APP_NAME' + - name: UNLEASH_API_URL + value: '$UNLEASH_API_URL' + - name: UNLEASH_INSTANCE_ID + value: '$UNLEASH_INSTANCE_ID' + - name: UNLEASH_REFRESH_INTERVAL + value: '$UNLEASH_REFRESH_INTERVAL' + - name: UNLEASH_METRICS_INTERVAL + value: '$UNLEASH_METRICS_INTERVAL' + - name: STRIPE_SECRET_KEY + value: '$STRIPE_SECRET_KEY' + - name: URBAN_AIRSHIP_KEY + value: '$URBAN_AIRSHIP_KEY' + - name: URBAN_AIRSHIP_SECRET + value: '$URBAN_AIRSHIP_SECRET' + - name: KEYMETRICS_MACHINE_NAME + value: '$KEYMETRICS_MACHINE_NAME' + - name: KEYMETRICS_SECRET_KEY + value: '$KEYMETRICS_SECRET_KEY' + - name: KEYMETRICS_PUBLIC_KEY + value: '$KEYMETRICS_PUBLIC_KEY' + - name: ARCGIS_CLIENT_ID + value: '$ARCGIS_CLIENT_ID' + - name: ARCGIS_CLIENT_SECRET + value: '$ARCGIS_CLIENT_SECRET' + - name: ENGINE_API_KEY + value: '$ENGINE_API_KEY' + ports: + - containerPort: 5500 + protocol: TCP + # We use LB, so no need to expose HTTPS, only HTTP required + # - containerPort: 2087 + # protocol: TCP + - containerPort: 8443 + protocol: TCP + - containerPort: 2086 + protocol: TCP +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: ever-demo-admin +spec: + replicas: 1 + selector: + matchLabels: + app: ever-demo-admin + template: + metadata: + labels: + app: ever-demo-admin + spec: + containers: + - name: ever-demo-admin + image: ghcr.io/ever-co/ever-admin-angular:latest + env: + - name: DEMO + value: 'true' + - name: NODE_ENV + value: 'production' + - name: HTTPS_SERVICES_ENDPOINT + value: 'https://apidemo.ever.co' + # We connect via HTTPS, so no need HTTP endpoint address + # - name: SERVICES_ENDPOINT + # value: '' + - name: GQL_ENDPOINT + value: 'https://apidemo.ever.co:8443/graphql' + - name: GQL_SUBSCRIPTIONS_ENDPOINT + value: 'ws://apidemo.ever.co:2086/subscriptions' + - name: SENTRY_DSN + value: 'https://7cd381188b6f446ca0e69185227b9031@o51327.ingest.sentry.io/4397292' + - name: CHATWOOT_SDK_TOKEN + value: 'jFoSXEjGmqhUhqU3zfgkFfMt' + - name: GOOGLE_MAPS_API_KEY + value: '' + - name: GOOGLE_PLACE_AUTOCOMPLETE + value: 'false' + - name: DEFAULT_LATITUDE + value: '42.6459136' + - name: DEFAULT_LONGITUDE + value: '23.3332736' + - name: CURRENCY_SYMBOL + value: '$' + - name: DEFAULT_LANGUAGE + value: 'en-US' + + ports: + - containerPort: 4200 + protocol: TCP + +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: ever-demo-carrier-mobile-ionic +spec: + replicas: 1 + selector: + matchLabels: + app: ever-demo-carrier-mobile-ionic + template: + metadata: + labels: + app: ever-demo-carrier-mobile-ionic + spec: + containers: + - name: ever-demo-carrier-mobile-ionic + image: ghcr.io/ever-co/ever-carrier-ionic:latest + env: + - name: DEMO + value: 'true' + - name: NODE_ENV + value: 'production' + - name: HTTPS_SERVICES_ENDPOINT + value: 'https://apidemo.ever.co' + # We connect via HTTPS, so no need HTTP endpoint address + # - name: SERVICES_ENDPOINT + # value: '' + - name: GQL_ENDPOINT + value: 'http://apidemo.ever.co:8443/graphql' + - name: GQL_SUBSCRIPTIONS_ENDPOINT + value: 'ws://apidemo.ever.co:2086/subscriptions' + - name: SENTRY_DSN + value: 'https://7cd381188b6f446ca0e69185227b9031@o51327.ingest.sentry.io/4397292' + - name: CHATWOOT_SDK_TOKEN + value: 'jFoSXEjGmqhUhqU3zfgkFfMt' + - name: GOOGLE_MAPS_API_KEY + value: '' + - name: GOOGLE_PLACE_AUTOCOMPLETE + value: 'false' + - name: DEFAULT_LATITUDE + value: '42.6459136' + - name: DEFAULT_LONGITUDE + value: '23.3332736' + - name: CURRENCY_SYMBOL + value: '$' + - name: DEFAULT_LANGUAGE + value: 'en-US' + + ports: + - containerPort: 4203 + protocol: TCP + +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: ever-demo-shop-mobile-ionic +spec: + replicas: 1 + selector: + matchLabels: + app: ever-demo-shop-mobile-ionic + template: + metadata: + labels: + app: ever-demo-shop-mobile-ionic + spec: + containers: + - name: ever-demo-shop-mobile-ionic + image: ghcr.io/ever-co/ever-shop-ionic:latest + env: + - name: DEMO + value: 'true' + - name: NODE_ENV + value: 'production' + - name: HTTPS_SERVICES_ENDPOINT + value: 'https://apidemo.ever.co' + # We connect via HTTPS, so no need HTTP endpoint address + # - name: SERVICES_ENDPOINT + # value: '' + - name: GQL_ENDPOINT + value: 'http://apidemo.ever.co:8443/graphql' + - name: GQL_SUBSCRIPTIONS_ENDPOINT + value: 'ws://apidemo.ever.co:2086/subscriptions' + - name: SENTRY_DSN + value: 'https://7cd381188b6f446ca0e69185227b9031@o51327.ingest.sentry.io/4397292' + - name: CHATWOOT_SDK_TOKEN + value: 'jFoSXEjGmqhUhqU3zfgkFfMt' + - name: GOOGLE_MAPS_API_KEY + value: '' + - name: GOOGLE_PLACE_AUTOCOMPLETE + value: 'false' + - name: DEFAULT_LATITUDE + value: '42.6459136' + - name: DEFAULT_LONGITUDE + value: '23.3332736' + - name: CURRENCY_SYMBOL + value: '$' + - name: DEFAULT_LANGUAGE + value: 'en-US' + + ports: + - containerPort: 4201 + protocol: TCP + +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: ever-demo-merchant-tablet-ionic +spec: + replicas: 1 + selector: + matchLabels: + app: ever-demo-merchant-tablet-ionic + template: + metadata: + labels: + app: ever-demo-merchant-tablet-ionic + spec: + containers: + - name: ever-demo-merchant-tablet-ionic + image: ghcr.io/ever-co/ever-merchant-ionic:latest + env: + - name: DEMO + value: 'true' + - name: NODE_ENV + value: 'production' + - name: HTTPS_SERVICES_ENDPOINT + value: 'https://apidemo.ever.co' + # We connect via HTTPS, so no need HTTP endpoint address + # - name: SERVICES_ENDPOINT + # value: '' + - name: GQL_ENDPOINT + value: 'http://apidemo.ever.co:8443/graphql' + - name: GQL_SUBSCRIPTIONS_ENDPOINT + value: 'ws://apidemo.ever.co:2086/subscriptions' + - name: SENTRY_DSN + value: 'https://7cd381188b6f446ca0e69185227b9031@o51327.ingest.sentry.io/4397292' + - name: CHATWOOT_SDK_TOKEN + value: 'jFoSXEjGmqhUhqU3zfgkFfMt' + - name: GOOGLE_MAPS_API_KEY + value: '' + - name: GOOGLE_PLACE_AUTOCOMPLETE + value: 'false' + - name: DEFAULT_LATITUDE + value: '42.6459136' + - name: DEFAULT_LONGITUDE + value: '23.3332736' + - name: CURRENCY_SYMBOL + value: '$' + - name: DEFAULT_LANGUAGE + value: 'en-US' + + ports: + - containerPort: 4202 + protocol: TCP + +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: ever-demo-shop-web-angular +spec: + replicas: 1 + selector: + matchLabels: + app: ever-demo-shop-web-angular + template: + metadata: + labels: + app: ever-demo-shop-web-angular + spec: + containers: + - name: ever-shop-web-angular + image: ghcr.io/ever-co/ever-shop-angular:latest + env: + - name: DEMO + value: 'true' + - name: NODE_ENV + value: 'production' + - name: HTTPS_SERVICES_ENDPOINT + value: 'https://apidemo.ever.co' + # We connect via HTTPS, so no need HTTP endpoint address + # - name: SERVICES_ENDPOINT + # value: '' + - name: GQL_ENDPOINT + value: 'http://apidemo.ever.co:8443/graphql' + - name: GQL_SUBSCRIPTIONS_ENDPOINT + value: 'ws://apidemo.ever.co:2086/subscriptions' + - name: SENTRY_DSN + value: 'https://7cd381188b6f446ca0e69185227b9031@o51327.ingest.sentry.io/4397292' + - name: CHATWOOT_SDK_TOKEN + value: 'jFoSXEjGmqhUhqU3zfgkFfMt' + - name: GOOGLE_MAPS_API_KEY + value: '' + - name: GOOGLE_PLACE_AUTOCOMPLETE + value: 'false' + - name: DEFAULT_LATITUDE + value: '42.6459136' + - name: DEFAULT_LONGITUDE + value: '23.3332736' + - name: CURRENCY_SYMBOL + value: '$' + - name: DEFAULT_LANGUAGE + value: 'en-US' + + ports: + - containerPort: 4200 + protocol: TCP diff --git a/.deploy/merchant-tablet-ionic/Dockerfile b/.deploy/merchant-tablet-ionic/Dockerfile new file mode 100644 index 0000000..b74587a --- /dev/null +++ b/.deploy/merchant-tablet-ionic/Dockerfile @@ -0,0 +1,147 @@ +# Ever Demand Platform Merchant Tablet App (Ionic) + +ARG API_BASE_URL +ARG API_HOST +ARG API_PORT +ARG CLIENT_BASE_URL +ARG DEMO +ARG WEB_HOST +ARG WEB_PORT +ARG HTTPS_SERVICES_ENDPOINT +ARG SERVICES_ENDPOINT +ARG GQL_ENDPOINT +ARG GQL_SUBSCRIPTIONS_ENDPOINT +ARG SENTRY_DSN +ARG CHATWOOT_SDK_TOKEN +ARG CLOUDINARY_CLOUD_NAME +ARG CLOUDINARY_API_KEY +ARG GOOGLE_MAPS_API_KEY +ARG GOOGLE_ANALYTICS_API_KEY +ARG GOOGLE_PLACE_AUTOCOMPLETE +ARG DEFAULT_LATITUDE +ARG DEFAULT_LONGITUDE +ARG DEFAULT_CURRENCY +ARG DEFAULT_LANGUAGE +ARG DEFAULT_CUSTOMER_LOGO +ARG LOGIN_LOGO +ARG NO_INTERNET_LOGO +ARG COMPANY_NAME +ARG APP_NAME +ARG MIXPANEL_API_KEY +ARG DEFAULT_LOGIN_USERNAME +ARG DEFAULT_LOGIN_PASSWORD +ARG SETTINGS_APP_TYPE +ARG SETTINGS_MAINTENANCE_API_URL + +FROM node:16-alpine3.14 AS dependencies + +LABEL maintainer="ever@ever.co" +LABEL org.opencontainers.image.source https://github.com/ever-co/ever-demand + +ENV CI=true + +RUN apk --update add bash \ + && apk add libexecinfo libexecinfo-dev \ + && npm i -g npm \ + && apk add --no-cache --virtual build-dependencies build-base \ + snappy dos2unix g++ snappy-dev gcc libgcc libstdc++ linux-headers autoconf automake make nasm python2 py2-setuptools vips-dev git \ + && npm install --quiet node-gyp -g \ + && npm config set python /usr/bin/python \ + && npm install yarn -g --force \ + && mkdir /srv/ever && chown -R node:node /srv/ever + +COPY wait .deploy/merchant-tablet-ionic/entrypoint.compose.sh .deploy/merchant-tablet-ionic/entrypoint.prod.sh / + +RUN chmod +x /wait /entrypoint.compose.sh /entrypoint.prod.sh && dos2unix /entrypoint.compose.sh && dos2unix /entrypoint.prod.sh + +USER node:node + +WORKDIR /srv/ever + +COPY --chown=node:node package.json yarn.lock lerna.json package.workspaces.json tsconfig.base.json ./ +COPY --chown=node:node packages/merchant-tablet-ionic/package.json ./packages/merchant-tablet-ionic/package.json +COPY --chown=node:node .snyk ./.snyk +COPY --chown=node:node packages/common/package.json ./packages/common/package.json +COPY --chown=node:node packages/common-angular/package.json ./packages/common-angular/package.json + +RUN yarn bootstrap && yarn cache clean + +FROM node:16-alpine3.14 AS development + +USER node:node + +WORKDIR /srv/ever + +COPY --chown=node:node --from=dependencies /wait /entrypoint.compose.sh /entrypoint.prod.sh / +COPY --chown=node:node --from=dependencies /srv/ever . +COPY . . + +FROM node:16-alpine3.14 AS build + +WORKDIR /srv/ever + +RUN mkdir www + +COPY --chown=node:node --from=development /srv/ever . + +ENV NODE_OPTIONS=${NODE_OPTIONS:-"--max-old-space-size=2048"} +ENV NODE_ENV=${NODE_ENV:-production} + +ENV IS_DOCKER=true + +RUN yarn build:merchant + +FROM nginx:alpine AS production + +# USER nginx:nginx + +WORKDIR /srv/ever + +COPY --chown=nginx:nginx --from=dependencies /wait /entrypoint.prod.sh /entrypoint.compose.sh ./ +COPY --chown=nginx:nginx .deploy/merchant-tablet-ionic/nginx.compose.conf /etc/nginx/conf.d/compose.conf.template +COPY --chown=nginx:nginx .deploy/merchant-tablet-ionic/nginx.prod.conf /etc/nginx/conf.d/prod.conf.template +COPY --chown=nginx:nginx --from=build /srv/ever/packages/merchant-tablet-ionic/www . + +RUN chmod +x wait entrypoint.compose.sh entrypoint.prod.sh && \ + chmod a+rw /etc/nginx/conf.d/compose.conf.template /etc/nginx/conf.d/prod.conf.template + +ENV API_HOST=${API_HOST:-api} +ENV API_PORT=${API_PORT:-3000} + +ENV API_BASE_URL=${API_BASE_URL:-http://localhost:3000} +ENV CLIENT_BASE_URL=${CLIENT_BASE_URL:-http://localhost:4202} +ENV WEB_HOST=${WEB_HOST:-0.0.0.0} +ENV WEB_PORT=${WEB_PORT:-4202} +ENV DEMO=${DEMO:-false} + +ENV HTTPS_SERVICES_ENDPOINT=${HTTPS_SERVICES_ENDPOINT} +ENV SERVICES_ENDPOINT=${SERVICES_ENDPOINT} +ENV GQL_ENDPOINT=${GQL_ENDPOINT} +ENV GQL_SUBSCRIPTIONS_ENDPOINT=${GQL_SUBSCRIPTIONS_ENDPOINT} +ENV SENTRY_DSN=${SENTRY_DSN} +ENV CHATWOOT_SDK_TOKEN=${CHATWOOT_SDK_TOKEN} +ENV CLOUDINARY_CLOUD_NAME=${CLOUDINARY_CLOUD_NAME} +ENV CLOUDINARY_API_KEY=${CLOUDINARY_API_KEY} +ENV GOOGLE_MAPS_API_KEY=${GOOGLE_MAPS_API_KEY} +ENV GOOGLE_ANALYTICS_API_KEY=${GOOGLE_ANALYTICS_API_KEY} +ENV GOOGLE_PLACE_AUTOCOMPLETE=${GOOGLE_PLACE_AUTOCOMPLETE:-false} +ENV DEFAULT_LATITUDE=${DEFAULT_LATITUDE:-42.6459136} +ENV DEFAULT_LONGITUDE=${DEFAULT_LONGITUDE:-23.3332736} +ENV DEFAULT_CURRENCY=${DEFAULT_CURRENCY:-USD}_DSN} +ENV DEFAULT_LANGUAGE=${DEFAULT_LANGUAGE:-en} +ENV DEFAULT_CUSTOMER_LOGO=${DEFAULT_CUSTOMER_LOGO} +ENV LOGIN_LOGO=${LOGIN_LOGO} +ENV NO_INTERNET_LOGO=${NO_INTERNET_LOGO} +ENV COMPANY_NAME=${COMPANY_NAME} +ENV APP_NAME=${APP_NAME} +ENV MIXPANEL_API_KEY=${MIXPANEL_API_KEY} +ENV DEFAULT_LOGIN_USERNAME=${DEFAULT_LOGIN_USERNAME} +ENV DEFAULT_LOGIN_PASSWORD=${DEFAULT_LOGIN_PASSWORD} +ENV SETTINGS_APP_TYPE=${SETTINGS_APP_TYPE} +ENV SETTINGS_MAINTENANCE_API_URL=${SETTINGS_MAINTENANCE_API_URL} + +EXPOSE ${WEB_PORT} + +ENTRYPOINT [ "./entrypoint.prod.sh" ] + +CMD [ "nginx", "-g", "daemon off;" ] diff --git a/.deploy/merchant-tablet-ionic/entrypoint.compose.sh b/.deploy/merchant-tablet-ionic/entrypoint.compose.sh new file mode 100644 index 0000000..0fc9680 --- /dev/null +++ b/.deploy/merchant-tablet-ionic/entrypoint.compose.sh @@ -0,0 +1,27 @@ +#!/bin/sh +set -ex + +# This Entrypoint used inside Docker Compose only + +export WAIT_HOSTS=$API_HOST:$API_PORT + +# In production we should replace some values in generated JS code +sed -i "s#DOCKER_API_BASE_URL#$API_BASE_URL#g" *.js +sed -i "s#DOCKER_CLIENT_BASE_URL#$CLIENT_BASE_URL#g" *.js +sed -i "s#DOCKER_SENTRY_DSN#$SENTRY_DSN#g" *.js +sed -i "s#DOCKER_CLOUDINARY_CLOUD_NAME#$CLOUDINARY_CLOUD_NAME#g" *.js +sed -i "s#DOCKER_CLOUDINARY_API_KEY#$CLOUDINARY_API_KEY#g" *.js +sed -i "s#DOCKER_GOOGLE_MAPS_API_KEY#$GOOGLE_MAPS_API_KEY#g" *.js +sed -i "s#DOCKER_GOOGLE_PLACE_AUTOCOMPLETE#$GOOGLE_PLACE_AUTOCOMPLETE#g" *.js +sed -i "s#DOCKER_DEFAULT_LATITUDE#$DEFAULT_LATITUDE#g" *.js +sed -i "s#DOCKER_DEFAULT_LONGITUDE#$DEFAULT_LONGITUDE#g" *.js +sed -i "s#DOCKER_DEFAULT_CURRENCY#$DEFAULT_CURRENCY#g" *.js +sed -i "s#DOCKER_CHATWOOT_SDK_TOKEN#$CHATWOOT_SDK_TOKEN#g" *.js +sed -i "s#DOCKER_DEMO#$DEMO#g" *.js + +envsubst '${API_HOST} ${API_PORT}' < /etc/nginx/conf.d/compose.conf.template > /etc/nginx/nginx.conf + +# in Docker Compose we should wait other services start +./wait + +exec "$@" \ No newline at end of file diff --git a/.deploy/merchant-tablet-ionic/entrypoint.prod.sh b/.deploy/merchant-tablet-ionic/entrypoint.prod.sh new file mode 100644 index 0000000..6fb2f30 --- /dev/null +++ b/.deploy/merchant-tablet-ionic/entrypoint.prod.sh @@ -0,0 +1,24 @@ +#!/bin/sh +set -ex + +# This Entrypoint used when we run Docker container outside of Docker Compose (e.g. in k8s) + +# In production we should replace some values in generated JS code +sed -i "s#DOCKER_API_BASE_URL#$API_BASE_URL#g" *.js +sed -i "s#DOCKER_CLIENT_BASE_URL#$CLIENT_BASE_URL#g" *.js +sed -i "s#DOCKER_SENTRY_DSN#$SENTRY_DSN#g" *.js +sed -i "s#DOCKER_CLOUDINARY_CLOUD_NAME#$CLOUDINARY_CLOUD_NAME#g" *.js +sed -i "s#DOCKER_CLOUDINARY_API_KEY#$CLOUDINARY_API_KEY#g" *.js +sed -i "s#DOCKER_GOOGLE_MAPS_API_KEY#$GOOGLE_MAPS_API_KEY#g" *.js +sed -i "s#DOCKER_GOOGLE_PLACE_AUTOCOMPLETE#$GOOGLE_PLACE_AUTOCOMPLETE#g" *.js +sed -i "s#DOCKER_DEFAULT_LATITUDE#$DEFAULT_LATITUDE#g" *.js +sed -i "s#DOCKER_DEFAULT_LONGITUDE#$DEFAULT_LONGITUDE#g" *.js +sed -i "s#DOCKER_DEFAULT_CURRENCY#$DEFAULT_CURRENCY#g" *.js +sed -i "s#DOCKER_CHATWOOT_SDK_TOKEN#$CHATWOOT_SDK_TOKEN#g" *.js +sed -i "s#DOCKER_DEMO#$DEMO#g" *.js + +# We may not need to use that env vars now in nginx.config, but we may want later. +# Also we just need to copy nginx.conf to correct place anyway... +envsubst '' < /etc/nginx/conf.d/prod.conf.template > /etc/nginx/nginx.conf + +exec "$@" diff --git a/.deploy/merchant-tablet-ionic/nginx.compose.conf b/.deploy/merchant-tablet-ionic/nginx.compose.conf new file mode 100644 index 0000000..fee3fef --- /dev/null +++ b/.deploy/merchant-tablet-ionic/nginx.compose.conf @@ -0,0 +1,37 @@ +user nginx; +error_log /var/log/nginx/error.log warn; +pid /var/run/nginx.pid; + +events { + worker_connections 1024; +} + +http { + include /etc/nginx/mime.types; + + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"'; + + access_log /var/log/nginx/access.log main; + + #gzip on; + + upstream api { + server ${API_HOST}:${API_PORT}; + } + + server { + listen 4202; + + location / { + root /srv/ever; + try_files $uri $uri/ /index.html; + } + + location /api/ { + proxy_pass http://api; + proxy_set_header Host $http_host; + } + } +} diff --git a/.deploy/merchant-tablet-ionic/nginx.prod.conf b/.deploy/merchant-tablet-ionic/nginx.prod.conf new file mode 100644 index 0000000..b2c4138 --- /dev/null +++ b/.deploy/merchant-tablet-ionic/nginx.prod.conf @@ -0,0 +1,28 @@ +user nginx; +error_log /var/log/nginx/error.log warn; +pid /var/run/nginx.pid; + +events { + worker_connections 1024; +} + +http { + include /etc/nginx/mime.types; + + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"'; + + access_log /var/log/nginx/access.log main; + + #gzip on; + + server { + listen 4202; + + location / { + root /srv/ever; + try_files $uri $uri/ /index.html; + } + } +} diff --git a/.deploy/shop-mobile-ionic/Dockerfile b/.deploy/shop-mobile-ionic/Dockerfile new file mode 100644 index 0000000..a4183b5 --- /dev/null +++ b/.deploy/shop-mobile-ionic/Dockerfile @@ -0,0 +1,147 @@ +# Ever Demand Platform Shop Mobile App (Ionic) + +ARG API_BASE_URL +ARG API_HOST +ARG API_PORT +ARG CLIENT_BASE_URL +ARG DEMO +ARG WEB_HOST +ARG WEB_PORT +ARG HTTPS_SERVICES_ENDPOINT +ARG SERVICES_ENDPOINT +ARG GQL_ENDPOINT +ARG GQL_SUBSCRIPTIONS_ENDPOINT +ARG SENTRY_DSN +ARG CHATWOOT_SDK_TOKEN +ARG CLOUDINARY_CLOUD_NAME +ARG CLOUDINARY_API_KEY +ARG GOOGLE_MAPS_API_KEY +ARG GOOGLE_ANALYTICS_API_KEY +ARG GOOGLE_PLACE_AUTOCOMPLETE +ARG DEFAULT_LATITUDE +ARG DEFAULT_LONGITUDE +ARG DEFAULT_CURRENCY +ARG DEFAULT_LANGUAGE +ARG DEFAULT_CUSTOMER_LOGO +ARG LOGIN_LOGO +ARG NO_INTERNET_LOGO +ARG COMPANY_NAME +ARG APP_NAME +ARG MIXPANEL_API_KEY +ARG DEFAULT_LOGIN_USERNAME +ARG DEFAULT_LOGIN_PASSWORD +ARG SETTINGS_APP_TYPE +ARG SETTINGS_MAINTENANCE_API_URL + +FROM node:16-alpine3.14 AS dependencies + +LABEL maintainer="ever@ever.co" +LABEL org.opencontainers.image.source https://github.com/ever-co/ever-demand + +ENV CI=true + +RUN apk --update add bash \ + && apk add libexecinfo libexecinfo-dev \ + && npm i -g npm \ + && apk add --no-cache --virtual build-dependencies build-base \ + snappy dos2unix g++ snappy-dev gcc libgcc libstdc++ linux-headers autoconf automake make nasm python2 py2-setuptools vips-dev git \ + && npm install --quiet node-gyp -g \ + && npm config set python /usr/bin/python \ + && npm install yarn -g --force \ + && mkdir /srv/ever && chown -R node:node /srv/ever + +COPY wait .deploy/shop-mobile-ionic/entrypoint.compose.sh .deploy/shop-mobile-ionic/entrypoint.prod.sh / + +RUN chmod +x /wait /entrypoint.compose.sh /entrypoint.prod.sh && dos2unix /entrypoint.compose.sh && dos2unix /entrypoint.prod.sh + +USER node:node + +WORKDIR /srv/ever + +COPY --chown=node:node package.json yarn.lock lerna.json package.workspaces.json tsconfig.base.json ./ +COPY --chown=node:node packages/shop-mobile-ionic/package.json ./packages/shop-mobile-ionic/package.json +COPY --chown=node:node .snyk ./.snyk +COPY --chown=node:node packages/common/package.json ./packages/common/package.json +COPY --chown=node:node packages/common-angular/package.json ./packages/common-angular/package.json + +RUN yarn bootstrap && yarn cache clean + +FROM node:16-alpine3.14 AS development + +USER node:node + +WORKDIR /srv/ever + +COPY --chown=node:node --from=dependencies /wait /entrypoint.compose.sh /entrypoint.prod.sh / +COPY --chown=node:node --from=dependencies /srv/ever . +COPY . . + +FROM node:16-alpine3.14 AS build + +WORKDIR /srv/ever + +RUN mkdir www + +COPY --chown=node:node --from=development /srv/ever . + +ENV NODE_OPTIONS=${NODE_OPTIONS:-"--max-old-space-size=2048"} +ENV NODE_ENV=${NODE_ENV:-production} + +ENV IS_DOCKER=true + +RUN yarn build:shopmobile + +FROM nginx:alpine AS production + +# USER nginx:nginx + +WORKDIR /srv/ever + +COPY --chown=nginx:nginx --from=dependencies /wait /entrypoint.prod.sh /entrypoint.compose.sh ./ +COPY --chown=nginx:nginx .deploy/shop-mobile-ionic/nginx.compose.conf /etc/nginx/conf.d/compose.conf.template +COPY --chown=nginx:nginx .deploy/shop-mobile-ionic/nginx.prod.conf /etc/nginx/conf.d/prod.conf.template +COPY --chown=nginx:nginx --from=build /srv/ever/packages/shop-mobile-ionic/www . + +RUN chmod +x wait entrypoint.compose.sh entrypoint.prod.sh && \ + chmod a+rw /etc/nginx/conf.d/compose.conf.template /etc/nginx/conf.d/prod.conf.template + +ENV API_HOST=${API_HOST:-api} +ENV API_PORT=${API_PORT:-3000} + +ENV API_BASE_URL=${API_BASE_URL:-http://localhost:3000} +ENV CLIENT_BASE_URL=${CLIENT_BASE_URL:-http://localhost:4201} +ENV WEB_HOST=${WEB_HOST:-0.0.0.0} +ENV WEB_PORT=${WEB_PORT:-4201} +ENV DEMO=${DEMO:-false} + +ENV HTTPS_SERVICES_ENDPOINT=${HTTPS_SERVICES_ENDPOINT} +ENV SERVICES_ENDPOINT=${SERVICES_ENDPOINT} +ENV GQL_ENDPOINT=${GQL_ENDPOINT} +ENV GQL_SUBSCRIPTIONS_ENDPOINT=${GQL_SUBSCRIPTIONS_ENDPOINT} +ENV SENTRY_DSN=${SENTRY_DSN} +ENV CHATWOOT_SDK_TOKEN=${CHATWOOT_SDK_TOKEN} +ENV CLOUDINARY_CLOUD_NAME=${CLOUDINARY_CLOUD_NAME} +ENV CLOUDINARY_API_KEY=${CLOUDINARY_API_KEY} +ENV GOOGLE_MAPS_API_KEY=${GOOGLE_MAPS_API_KEY} +ENV GOOGLE_ANALYTICS_API_KEY=${GOOGLE_ANALYTICS_API_KEY} +ENV GOOGLE_PLACE_AUTOCOMPLETE=${GOOGLE_PLACE_AUTOCOMPLETE:-false} +ENV DEFAULT_LATITUDE=${DEFAULT_LATITUDE:-42.6459136} +ENV DEFAULT_LONGITUDE=${DEFAULT_LONGITUDE:-23.3332736} +ENV DEFAULT_CURRENCY=${DEFAULT_CURRENCY:-USD}_DSN} +ENV DEFAULT_LANGUAGE=${DEFAULT_LANGUAGE:-en} +ENV DEFAULT_CUSTOMER_LOGO=${DEFAULT_CUSTOMER_LOGO} +ENV LOGIN_LOGO=${LOGIN_LOGO} +ENV NO_INTERNET_LOGO=${NO_INTERNET_LOGO} +ENV COMPANY_NAME=${COMPANY_NAME} +ENV APP_NAME=${APP_NAME} +ENV MIXPANEL_API_KEY=${MIXPANEL_API_KEY} +ENV DEFAULT_LOGIN_USERNAME=${DEFAULT_LOGIN_USERNAME} +ENV DEFAULT_LOGIN_PASSWORD=${DEFAULT_LOGIN_PASSWORD} +ENV SETTINGS_APP_TYPE=${SETTINGS_APP_TYPE} +ENV SETTINGS_MAINTENANCE_API_URL=${SETTINGS_MAINTENANCE_API_URL} + +EXPOSE ${WEB_PORT} + +ENTRYPOINT [ "./entrypoint.prod.sh" ] + +CMD [ "nginx", "-g", "daemon off;" ] diff --git a/.deploy/shop-mobile-ionic/entrypoint.compose.sh b/.deploy/shop-mobile-ionic/entrypoint.compose.sh new file mode 100644 index 0000000..0fc9680 --- /dev/null +++ b/.deploy/shop-mobile-ionic/entrypoint.compose.sh @@ -0,0 +1,27 @@ +#!/bin/sh +set -ex + +# This Entrypoint used inside Docker Compose only + +export WAIT_HOSTS=$API_HOST:$API_PORT + +# In production we should replace some values in generated JS code +sed -i "s#DOCKER_API_BASE_URL#$API_BASE_URL#g" *.js +sed -i "s#DOCKER_CLIENT_BASE_URL#$CLIENT_BASE_URL#g" *.js +sed -i "s#DOCKER_SENTRY_DSN#$SENTRY_DSN#g" *.js +sed -i "s#DOCKER_CLOUDINARY_CLOUD_NAME#$CLOUDINARY_CLOUD_NAME#g" *.js +sed -i "s#DOCKER_CLOUDINARY_API_KEY#$CLOUDINARY_API_KEY#g" *.js +sed -i "s#DOCKER_GOOGLE_MAPS_API_KEY#$GOOGLE_MAPS_API_KEY#g" *.js +sed -i "s#DOCKER_GOOGLE_PLACE_AUTOCOMPLETE#$GOOGLE_PLACE_AUTOCOMPLETE#g" *.js +sed -i "s#DOCKER_DEFAULT_LATITUDE#$DEFAULT_LATITUDE#g" *.js +sed -i "s#DOCKER_DEFAULT_LONGITUDE#$DEFAULT_LONGITUDE#g" *.js +sed -i "s#DOCKER_DEFAULT_CURRENCY#$DEFAULT_CURRENCY#g" *.js +sed -i "s#DOCKER_CHATWOOT_SDK_TOKEN#$CHATWOOT_SDK_TOKEN#g" *.js +sed -i "s#DOCKER_DEMO#$DEMO#g" *.js + +envsubst '${API_HOST} ${API_PORT}' < /etc/nginx/conf.d/compose.conf.template > /etc/nginx/nginx.conf + +# in Docker Compose we should wait other services start +./wait + +exec "$@" \ No newline at end of file diff --git a/.deploy/shop-mobile-ionic/entrypoint.prod.sh b/.deploy/shop-mobile-ionic/entrypoint.prod.sh new file mode 100644 index 0000000..6fb2f30 --- /dev/null +++ b/.deploy/shop-mobile-ionic/entrypoint.prod.sh @@ -0,0 +1,24 @@ +#!/bin/sh +set -ex + +# This Entrypoint used when we run Docker container outside of Docker Compose (e.g. in k8s) + +# In production we should replace some values in generated JS code +sed -i "s#DOCKER_API_BASE_URL#$API_BASE_URL#g" *.js +sed -i "s#DOCKER_CLIENT_BASE_URL#$CLIENT_BASE_URL#g" *.js +sed -i "s#DOCKER_SENTRY_DSN#$SENTRY_DSN#g" *.js +sed -i "s#DOCKER_CLOUDINARY_CLOUD_NAME#$CLOUDINARY_CLOUD_NAME#g" *.js +sed -i "s#DOCKER_CLOUDINARY_API_KEY#$CLOUDINARY_API_KEY#g" *.js +sed -i "s#DOCKER_GOOGLE_MAPS_API_KEY#$GOOGLE_MAPS_API_KEY#g" *.js +sed -i "s#DOCKER_GOOGLE_PLACE_AUTOCOMPLETE#$GOOGLE_PLACE_AUTOCOMPLETE#g" *.js +sed -i "s#DOCKER_DEFAULT_LATITUDE#$DEFAULT_LATITUDE#g" *.js +sed -i "s#DOCKER_DEFAULT_LONGITUDE#$DEFAULT_LONGITUDE#g" *.js +sed -i "s#DOCKER_DEFAULT_CURRENCY#$DEFAULT_CURRENCY#g" *.js +sed -i "s#DOCKER_CHATWOOT_SDK_TOKEN#$CHATWOOT_SDK_TOKEN#g" *.js +sed -i "s#DOCKER_DEMO#$DEMO#g" *.js + +# We may not need to use that env vars now in nginx.config, but we may want later. +# Also we just need to copy nginx.conf to correct place anyway... +envsubst '' < /etc/nginx/conf.d/prod.conf.template > /etc/nginx/nginx.conf + +exec "$@" diff --git a/.deploy/shop-mobile-ionic/nginx.compose.conf b/.deploy/shop-mobile-ionic/nginx.compose.conf new file mode 100644 index 0000000..9910e4f --- /dev/null +++ b/.deploy/shop-mobile-ionic/nginx.compose.conf @@ -0,0 +1,37 @@ +user nginx; +error_log /var/log/nginx/error.log warn; +pid /var/run/nginx.pid; + +events { + worker_connections 1024; +} + +http { + include /etc/nginx/mime.types; + + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"'; + + access_log /var/log/nginx/access.log main; + + #gzip on; + + upstream api { + server ${API_HOST}:${API_PORT}; + } + + server { + listen 4201; + + location / { + root /srv/ever; + try_files $uri $uri/ /index.html; + } + + location /api/ { + proxy_pass http://api; + proxy_set_header Host $http_host; + } + } +} diff --git a/.deploy/shop-mobile-ionic/nginx.prod.conf b/.deploy/shop-mobile-ionic/nginx.prod.conf new file mode 100644 index 0000000..ae8e123 --- /dev/null +++ b/.deploy/shop-mobile-ionic/nginx.prod.conf @@ -0,0 +1,28 @@ +user nginx; +error_log /var/log/nginx/error.log warn; +pid /var/run/nginx.pid; + +events { + worker_connections 1024; +} + +http { + include /etc/nginx/mime.types; + + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"'; + + access_log /var/log/nginx/access.log main; + + #gzip on; + + server { + listen 4201; + + location / { + root /srv/ever; + try_files $uri $uri/ /index.html; + } + } +} diff --git a/.deploy/shop-web-angular/Dockerfile b/.deploy/shop-web-angular/Dockerfile new file mode 100644 index 0000000..7efaf22 --- /dev/null +++ b/.deploy/shop-web-angular/Dockerfile @@ -0,0 +1,135 @@ +# Ever Demand Platform Shop Web (Angular) + +ARG API_BASE_URL +ARG API_HOST +ARG API_PORT +ARG CLIENT_BASE_URL +ARG DEMO +ARG WEB_HOST +ARG WEB_PORT +ARG HTTPS_SERVICES_ENDPOINT +ARG SERVICES_ENDPOINT +ARG GQL_ENDPOINT +ARG GQL_SUBSCRIPTIONS_ENDPOINT +ARG SENTRY_DSN +ARG CHATWOOT_SDK_TOKEN +ARG CLOUDINARY_CLOUD_NAME +ARG CLOUDINARY_API_KEY +ARG GOOGLE_MAPS_API_KEY +ARG GOOGLE_PLACE_AUTOCOMPLETE +ARG DEFAULT_LATITUDE +ARG DEFAULT_LONGITUDE +ARG DEFAULT_CURRENCY +ARG AUTH_LOGO +ARG NO_INTERNET_LOGO +ARG DELIVERY_TIME_MIN +ARG DELIVERY_TIME_MAX +ARG SETTINGS_APP_TYPE +ARG SETTINGS_MAINTENANCE_API_URL + +FROM node:16-alpine3.14 AS dependencies + +LABEL maintainer="ever@ever.co" +LABEL org.opencontainers.image.source https://github.com/ever-co/ever-demand + +ENV CI=true + +RUN apk --update add bash \ + && apk add libexecinfo libexecinfo-dev \ + && npm i -g npm \ + && apk add --no-cache --virtual build-dependencies build-base \ + snappy dos2unix g++ snappy-dev gcc libgcc libstdc++ linux-headers autoconf automake make nasm python2 py2-setuptools vips-dev git \ + && npm install --quiet node-gyp -g \ + && npm config set python /usr/bin/python \ + && npm install yarn -g --force \ + && mkdir /srv/ever && chown -R node:node /srv/ever + +COPY wait .deploy/shop-web-angular/entrypoint.compose.sh .deploy/shop-web-angular/entrypoint.prod.sh / + +RUN chmod +x /wait /entrypoint.compose.sh /entrypoint.prod.sh && dos2unix /entrypoint.compose.sh && dos2unix /entrypoint.prod.sh + +USER node:node + +WORKDIR /srv/ever + +COPY --chown=node:node package.json yarn.lock lerna.json package.workspaces.json tsconfig.base.json ./ +COPY --chown=node:node packages/shop-web-angular/package.json ./packages/shop-web-angular/package.json +COPY --chown=node:node .snyk ./.snyk +COPY --chown=node:node packages/common/package.json ./packages/common/package.json +COPY --chown=node:node packages/common-angular/package.json ./packages/common-angular/package.json + +RUN yarn bootstrap && yarn cache clean + +FROM node:16-alpine3.14 AS development + +USER node:node + +WORKDIR /srv/ever + +COPY --chown=node:node --from=dependencies /wait /entrypoint.compose.sh /entrypoint.prod.sh / +COPY --chown=node:node --from=dependencies /srv/ever . +COPY . . + +FROM node:16-alpine3.14 AS build + +WORKDIR /srv/ever + +RUN mkdir dist + +COPY --chown=node:node --from=development /srv/ever . + +ENV NODE_OPTIONS=${NODE_OPTIONS:-"--max-old-space-size=2048"} +ENV NODE_ENV=${NODE_ENV:-production} + +ENV IS_DOCKER=true + +RUN yarn build:shopweb + +FROM nginx:alpine AS production + +# USER nginx:nginx + +WORKDIR /srv/ever + +COPY --chown=nginx:nginx --from=dependencies /wait /entrypoint.prod.sh /entrypoint.compose.sh ./ +COPY --chown=nginx:nginx .deploy/shop-web-angular/nginx.compose.conf /etc/nginx/conf.d/compose.conf.template +COPY --chown=nginx:nginx .deploy/shop-web-angular/nginx.prod.conf /etc/nginx/conf.d/prod.conf.template +COPY --chown=nginx:nginx --from=build /srv/ever/packages/shop-web-angular/build . + +RUN chmod +x wait entrypoint.compose.sh entrypoint.prod.sh && \ + chmod a+rw /etc/nginx/conf.d/compose.conf.template /etc/nginx/conf.d/prod.conf.template + +ENV API_HOST=${API_HOST:-api} +ENV API_PORT=${API_PORT:-3000} + +ENV API_BASE_URL=${API_BASE_URL:-http://localhost:3000} +ENV CLIENT_BASE_URL=${CLIENT_BASE_URL:-http://localhost:4200} +ENV WEB_HOST=${WEB_HOST:-0.0.0.0} +ENV WEB_PORT=${WEB_PORT:-4200} +ENV DEMO=${DEMO:-false} + +ENV HTTPS_SERVICES_ENDPOINT=${HTTPS_SERVICES_ENDPOINT} +ENV SERVICES_ENDPOINT=${SERVICES_ENDPOINT} +ENV GQL_ENDPOINT=${GQL_ENDPOINT} +ENV GQL_SUBSCRIPTIONS_ENDPOINT=${GQL_SUBSCRIPTIONS_ENDPOINT} +ENV SENTRY_DSN=${SENTRY_DSN} +ENV CHATWOOT_SDK_TOKEN=${CHATWOOT_SDK_TOKEN} +ENV CLOUDINARY_CLOUD_NAME=${CLOUDINARY_CLOUD_NAME} +ENV CLOUDINARY_API_KEY=${CLOUDINARY_API_KEY} +ENV GOOGLE_MAPS_API_KEY=${GOOGLE_MAPS_API_KEY} +ENV GOOGLE_PLACE_AUTOCOMPLETE=${GOOGLE_PLACE_AUTOCOMPLETE:-false} +ENV DEFAULT_LATITUDE=${DEFAULT_LATITUDE:-42.6459136} +ENV DEFAULT_LONGITUDE=${DEFAULT_LONGITUDE:-23.3332736} +ENV DEFAULT_CURRENCY=${DEFAULT_CURRENCY:-USD}_DSN} +ENV AUTH_LOGO=${AUTH_LOGO} +ENV NO_INTERNET_LOGO=${NO_INTERNET_LOGO} +ENV DELIVERY_TIME_MIN=${DELIVERY_TIME_MIN} +ENV DELIVERY_TIME_MAX=${DELIVERY_TIME_MAX} +ENV SETTINGS_APP_TYPE=${SETTINGS_APP_TYPE} +ENV SETTINGS_MAINTENANCE_API_URL=${SETTINGS_MAINTENANCE_API_URL} + +EXPOSE ${WEB_PORT} + +ENTRYPOINT [ "./entrypoint.prod.sh" ] + +CMD [ "nginx", "-g", "daemon off;" ] diff --git a/.deploy/shop-web-angular/entrypoint.compose.sh b/.deploy/shop-web-angular/entrypoint.compose.sh new file mode 100644 index 0000000..0fc9680 --- /dev/null +++ b/.deploy/shop-web-angular/entrypoint.compose.sh @@ -0,0 +1,27 @@ +#!/bin/sh +set -ex + +# This Entrypoint used inside Docker Compose only + +export WAIT_HOSTS=$API_HOST:$API_PORT + +# In production we should replace some values in generated JS code +sed -i "s#DOCKER_API_BASE_URL#$API_BASE_URL#g" *.js +sed -i "s#DOCKER_CLIENT_BASE_URL#$CLIENT_BASE_URL#g" *.js +sed -i "s#DOCKER_SENTRY_DSN#$SENTRY_DSN#g" *.js +sed -i "s#DOCKER_CLOUDINARY_CLOUD_NAME#$CLOUDINARY_CLOUD_NAME#g" *.js +sed -i "s#DOCKER_CLOUDINARY_API_KEY#$CLOUDINARY_API_KEY#g" *.js +sed -i "s#DOCKER_GOOGLE_MAPS_API_KEY#$GOOGLE_MAPS_API_KEY#g" *.js +sed -i "s#DOCKER_GOOGLE_PLACE_AUTOCOMPLETE#$GOOGLE_PLACE_AUTOCOMPLETE#g" *.js +sed -i "s#DOCKER_DEFAULT_LATITUDE#$DEFAULT_LATITUDE#g" *.js +sed -i "s#DOCKER_DEFAULT_LONGITUDE#$DEFAULT_LONGITUDE#g" *.js +sed -i "s#DOCKER_DEFAULT_CURRENCY#$DEFAULT_CURRENCY#g" *.js +sed -i "s#DOCKER_CHATWOOT_SDK_TOKEN#$CHATWOOT_SDK_TOKEN#g" *.js +sed -i "s#DOCKER_DEMO#$DEMO#g" *.js + +envsubst '${API_HOST} ${API_PORT}' < /etc/nginx/conf.d/compose.conf.template > /etc/nginx/nginx.conf + +# in Docker Compose we should wait other services start +./wait + +exec "$@" \ No newline at end of file diff --git a/.deploy/shop-web-angular/entrypoint.prod.sh b/.deploy/shop-web-angular/entrypoint.prod.sh new file mode 100644 index 0000000..6fb2f30 --- /dev/null +++ b/.deploy/shop-web-angular/entrypoint.prod.sh @@ -0,0 +1,24 @@ +#!/bin/sh +set -ex + +# This Entrypoint used when we run Docker container outside of Docker Compose (e.g. in k8s) + +# In production we should replace some values in generated JS code +sed -i "s#DOCKER_API_BASE_URL#$API_BASE_URL#g" *.js +sed -i "s#DOCKER_CLIENT_BASE_URL#$CLIENT_BASE_URL#g" *.js +sed -i "s#DOCKER_SENTRY_DSN#$SENTRY_DSN#g" *.js +sed -i "s#DOCKER_CLOUDINARY_CLOUD_NAME#$CLOUDINARY_CLOUD_NAME#g" *.js +sed -i "s#DOCKER_CLOUDINARY_API_KEY#$CLOUDINARY_API_KEY#g" *.js +sed -i "s#DOCKER_GOOGLE_MAPS_API_KEY#$GOOGLE_MAPS_API_KEY#g" *.js +sed -i "s#DOCKER_GOOGLE_PLACE_AUTOCOMPLETE#$GOOGLE_PLACE_AUTOCOMPLETE#g" *.js +sed -i "s#DOCKER_DEFAULT_LATITUDE#$DEFAULT_LATITUDE#g" *.js +sed -i "s#DOCKER_DEFAULT_LONGITUDE#$DEFAULT_LONGITUDE#g" *.js +sed -i "s#DOCKER_DEFAULT_CURRENCY#$DEFAULT_CURRENCY#g" *.js +sed -i "s#DOCKER_CHATWOOT_SDK_TOKEN#$CHATWOOT_SDK_TOKEN#g" *.js +sed -i "s#DOCKER_DEMO#$DEMO#g" *.js + +# We may not need to use that env vars now in nginx.config, but we may want later. +# Also we just need to copy nginx.conf to correct place anyway... +envsubst '' < /etc/nginx/conf.d/prod.conf.template > /etc/nginx/nginx.conf + +exec "$@" diff --git a/.deploy/shop-web-angular/nginx.compose.conf b/.deploy/shop-web-angular/nginx.compose.conf new file mode 100644 index 0000000..959ab02 --- /dev/null +++ b/.deploy/shop-web-angular/nginx.compose.conf @@ -0,0 +1,37 @@ +user nginx; +error_log /var/log/nginx/error.log warn; +pid /var/run/nginx.pid; + +events { + worker_connections 1024; +} + +http { + include /etc/nginx/mime.types; + + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"'; + + access_log /var/log/nginx/access.log main; + + #gzip on; + + upstream api { + server ${API_HOST}:${API_PORT}; + } + + server { + listen 4200; + + location / { + root /srv/ever; + try_files $uri $uri/ /index.html; + } + + location /api/ { + proxy_pass http://api; + proxy_set_header Host $http_host; + } + } +} diff --git a/.deploy/shop-web-angular/nginx.prod.conf b/.deploy/shop-web-angular/nginx.prod.conf new file mode 100644 index 0000000..37c3f1b --- /dev/null +++ b/.deploy/shop-web-angular/nginx.prod.conf @@ -0,0 +1,28 @@ +user nginx; +error_log /var/log/nginx/error.log warn; +pid /var/run/nginx.pid; + +events { + worker_connections 1024; +} + +http { + include /etc/nginx/mime.types; + + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"'; + + access_log /var/log/nginx/access.log main; + + #gzip on; + + server { + listen 4200; + + location / { + root /srv/ever; + try_files $uri $uri/ /index.html; + } + } +} diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..89f2a60 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,27 @@ +# http://editorconfig.org +root = true + +[*] +charset = utf-8 +indent_style = tab +indent_size = 4 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true + +[*.md] +max_line_length = off +insert_final_newline = false +trim_trailing_whitespace = false + +[*.yml] +indent_style = space +indent_size = 4 + +[*.json] +indent_style = space +indent_size = 4 + +[*.graphql] +indent_style = space +indent_size = 4 diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..dfe0770 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Auto detect text files and perform LF normalization +* text=auto diff --git a/.github/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..1ce1b37 --- /dev/null +++ b/.github/CODE_OF_CONDUCT.md @@ -0,0 +1 @@ +# Contributor Code of Conduct diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md new file mode 100644 index 0000000..682c5a7 --- /dev/null +++ b/.github/CONTRIBUTING.md @@ -0,0 +1,42 @@ +## Contributing to Ever Demand Platform + +We would love for you to contribute to Ever Demand Platform! + +## Got a Question + +If you have a questions about how to use Ever Platform it's best to contact us on Slack, Discord or other community chat, see [Contact Us Details](https://github.com/ever-co/ever-demand#contact-us) for more information. + +## Submitting Pull Requests + +If you're changing the structure of the repository please create an issue first. + +By default, when you submitting contributions with Pull Requests, we require electronic submission of individual [Contributor Assignment Agreement (CAA)](https://gist.github.com/evereq/95f74ae09510766ffa9379006715ccfd). In some cases (when contributions are very small, at our discretion) instead of CAA we may accept submission of individual [Contributor License Agreement (CLA)](https://gist.github.com/evereq/53ddec283243481344fb61df1706ec40). + +If you submitting contribution on behalf of some legal entity, you need to submit Entity Contributor Assignment Agreement (CAA) or Entity Contributor License Agreement (CLA), which you can request by sending us an email to legal@ever.co. + +We are using open-source [CLA assistant](https://github.com/cla-assistant/cla-assistant) project to collect your signatures on CAA. +The templates for our CAA/CLA documents generated by http://www.harmonyagreements.org. +You can also download CAA/CLA documents ([Contributor Assignment Agreement (CAA)](https://gist.github.com/evereq/95f74ae09510766ffa9379006715ccfd) or [Contributor License Agreement (CLA)](https://gist.github.com/evereq/53ddec283243481344fb61df1706ec40)), sign it, scan and email to legal@ever.co. + +Note: you only need to do this signing procedure once, either for you or your company! + +## Branching + +For branching naming / usage see [Branching Strategy and CI Wiki page](https://github.com/ever-co/ever-demand/wiki/Branching-strategy-and-CI) + +## Submitting bug reports + +Make sure you are on latest changes. +If you can, please provide more information about your environment such as browser, operating system, node version, and yarn version. + +## Feature requests + +You are more than welcome to submit future requests here https://github.com/ever-co/feature-requests/issues + +## Legal + +This is an open source project. +Contributions you make to this public Ever Platform repository are completely voluntary. +When you submit an issue, bug report, question, enhancement, pull request, etc., you are offering your contribution without expectation of payment, you expressly waive any future pay claims against the Ever Co. LTD related to your contribution, and you acknowledge that this does not create an obligation on the part of the Ever Co. LTD of any kind. Furthermore, your contributing to this project does not create an employer-employee relationship between the Ever Co. LTD and the contributor. + +See also "Submitting Pull Requests" section above for more information on CAA/CLA, required for any contributions. diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..81fcc86 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,45 @@ +--- +name: Bug +about: Create a report to help us improve Ever Demand Platform +title: '' +labels: '' +assignees: '' +--- +# Bug Report + + + +### 🔎 Search Terms + + + +### 🕗 Version & Regression Information + + +- This is a crash +- This changed between versions ______ and _______ +- This is the behavior in every version I tried, and I reviewed the FAQ for entries about _________ +- I was unable to test this on prior versions because _______ + +### 💻 Code + + +```ts +// Sample of code +``` + +### 🙁 Actual behavior + + + +### 🙂 Expected behavior + + \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..412d1e9 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,7 @@ +--- +blank_issues_enabled: true +contact_links: + - + about: "Please check the Wiki before filing new issues" + name: "Wiki" + url: "https://github.com/ever-co/ever-demand/wiki" diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..3b02b84 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,6 @@ +- [ ] Have you followed the [contributing guidelines](https://github.com/ever-co/ever/blob/master/.github/CONTRIBUTING.md)? +- [ ] Have you explained what your changes do, and why they add value? + +**Please note: we will close your PR without comment if you do not check the boxes above and provide ALL requested information.** + +--- diff --git a/.github/SUPPORT.md b/.github/SUPPORT.md new file mode 100644 index 0000000..515ea7a --- /dev/null +++ b/.github/SUPPORT.md @@ -0,0 +1 @@ +# Support \ No newline at end of file diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml new file mode 100644 index 0000000..3d7844e --- /dev/null +++ b/.github/workflows/codeql-analysis.yml @@ -0,0 +1,39 @@ +name: "CodeQL" + +on: + push: + branches: [ develop, master ] + pull_request: + # The branches below must be a subset of the branches above + branches: [ develop ] + schedule: + - cron: '30 15 * * 6' + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + language: [ 'javascript' ] + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + - name: Initialize CodeQL + uses: github/codeql-action/init@v1 + with: + languages: ${{ matrix.language }} + + - name: Autobuild + uses: github/codeql-action/autobuild@v1 + + #- run: | + # make bootstrap + # make release + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v1 diff --git a/.github/workflows/deploy-do.yml b/.github/workflows/deploy-do.yml new file mode 100644 index 0000000..6b85833 --- /dev/null +++ b/.github/workflows/deploy-do.yml @@ -0,0 +1,93 @@ +name: Deploy to DigitalOcean + +on: + workflow_run: + workflows: ['Build and Publish Docker Images'] + branches: [ develop, master ] + types: + - completed + +jobs: + deploy-do: + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Install doctl + uses: digitalocean/action-doctl@v2 + with: + token: ${{ secrets.DIGITALOCEAN_ACCESS_TOKEN }} + + - name: Log in to DigitalOcean Container Registry with short-lived credentials + run: doctl registry login --expiry-seconds 600 + + - name: Save DigitalOcean kubeconfig with short-lived credentials + run: doctl kubernetes cluster kubeconfig save --expiry-seconds 600 k8s-ever + + - name: Write MongoDB Certificate file + run: | + echo "$DB_CA_CERT" | base64 --decode > ${HOME}/ca-certificate.crt + env: + DB_CA_CERT: '${{ secrets.DB_CA_CERT }}' + + - name: Apply k8s manifests changes in DigitalOcean k8s cluster (if any) + run: | + envsubst < $GITHUB_WORKSPACE/.deploy/k8s/k8s-manifest.demo.yaml | kubectl --context do-sfo2-k8s-ever apply -f - + env: + DB_URI: '${{ secrets.DB_URI }}' + DB_HOST: '${{ secrets.DB_HOST }}' + DB_USER: '${{ secrets.DB_USER }}' + DB_PASS: '${{ secrets.DB_PASS }}' + DB_NAME: 'ever_demo' + DB_CA_CERT: '${{ secrets.DB_CA_CERT }}' + DB_SSL_MODE: 'true' + SENTRY_DSN: '${{ secrets.SENTRY_DSN }}' + AWS_ACCESS_KEY_ID: '${{ secrets.AWS_ACCESS_KEY_ID }}' + AWS_SECRET_ACCESS_KEY: '${{ secrets.AWS_SECRET_ACCESS_KEY }}' + AWS_REGION: '${{ secrets.AWS_REGION }}' + AWS_S3_BUCKET: '${{ secrets.AWS_S3_BUCKET }}' + EXPRESS_SESSION_SECRET: '${{ secrets.EXPRESS_SESSION_SECRET }}' + JWT_SECRET: '${{ secrets.JWT_SECRET }}' + CLOUDINARY_API_KEY: '${{ secrets.CLOUDINARY_API_KEY }}' + CLOUDINARY_API_SECRET: '${{ secrets.CLOUDINARY_API_SECRET }}' + CLOUDINARY_CLOUD_NAME: '${{ secrets.CLOUDINARY_CLOUD_NAME }}' + MAIL_FROM_ADDRESS: '${{ secrets.MAIL_FROM_ADDRESS }}' + MAIL_HOST: '${{ secrets.MAIL_HOST }}' + MAIL_PORT: '${{ secrets.MAIL_PORT }}' + MAIL_USERNAME: '${{ secrets.MAIL_USERNAME }}' + MAIL_PASSWORD: '${{ secrets.MAIL_PASSWORD }}' + ALLOW_SUPER_ADMIN_ROLE: '${{ secrets.ALLOW_SUPER_ADMIN_ROLE }}' + GOOGLE_APP_ID: '${{ secrets.GOOGLE_APP_ID }}' + GOOGLE_APP_SECRET: '${{ secrets.GOOGLE_APP_SECRET }}' + GOOGLE_CALLBACK_URL: '${{ secrets.GOOGLE_CALLBACK_URL }}' + FACEBOOK_APP_ID: '${{ secrets.FACEBOOK_APP_ID }}' + FACEBOOK_APP_SECRET: '${{ secrets.FACEBOOK_APP_SECRET }}' + FACEBOOK_GRAPH_VERSION: '${{ secrets.FACEBOOK_GRAPH_VERSION }}' + FACEBOOK_CALLBACK_URL: '${{ secrets.FACEBOOK_CALLBACK_URL }}' + UNLEASH_APP_NAME: '${{ secrets.UNLEASH_APP_NAME }}' + UNLEASH_API_URL: '${{ secrets.UNLEASH_API_URL }}' + UNLEASH_INSTANCE_ID: '${{ secrets.UNLEASH_INSTANCE_ID }}' + UNLEASH_REFRESH_INTERVAL: '${{ secrets.UNLEASH_REFRESH_INTERVAL }}' + UNLEASH_METRICS_INTERVAL: '${{ secrets.UNLEASH_METRICS_INTERVAL }}' + STRIPE_SECRET_KEY: '${{ secrets.STRIPE_SECRET_KEY }}' + URBAN_AIRSHIP_KEY: '${{ secrets.URBAN_AIRSHIP_KEY }}' + URBAN_AIRSHIP_SECRET: '${{ secrets.URBAN_AIRSHIP_SECRET }}' + KEYMETRICS_MACHINE_NAME: '${{ secrets.KEYMETRICS_MACHINE_NAME }}' + KEYMETRICS_SECRET_KEY: '${{ secrets.KEYMETRICS_SECRET_KEY }}' + KEYMETRICS_PUBLIC_KEY: '${{ secrets.KEYMETRICS_PUBLIC_KEY }}' + ARCGIS_CLIENT_ID: '${{ secrets.ARCGIS_CLIENT_ID }}' + ARCGIS_CLIENT_SECRET: '${{ secrets.ARCGIS_CLIENT_SECRET }}' + ENGINE_API_KEY: '${{ secrets.ENGINE_API_KEY }}' + + # we need this step because for now we just use :latest tag + # note: for production we will use different strategy later + - name: Restart Pods to pick up :latest tag version + run: | + kubectl --context do-sfo2-k8s-ever rollout restart deployment/ever-demo-api + kubectl --context do-sfo2-k8s-ever rollout restart deployment/ever-demo-admin + kubectl --context do-sfo2-k8s-ever rollout restart deployment/ever-demo-carrier-mobile-ionic + kubectl --context do-sfo2-k8s-ever rollout restart deployment/ever-demo-shop-mobile-ionic + kubectl --context do-sfo2-k8s-ever rollout restart deployment/ever-demo-merchant-tablet-ionic + kubectl --context do-sfo2-k8s-ever rollout restart deployment/ever-demo-shop-web-angular diff --git a/.github/workflows/docker-build-publish.yml b/.github/workflows/docker-build-publish.yml new file mode 100644 index 0000000..583a0a9 --- /dev/null +++ b/.github/workflows/docker-build-publish.yml @@ -0,0 +1,309 @@ +name: Build and Publish Docker Images + +on: + push: + branches: + - develop + - master + +jobs: + ever-api: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Set up QEMU + uses: docker/setup-qemu-action@v1 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + - name: Login to DockerHub + uses: docker/login-action@v1 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + - name: Login to GitHub Container Registry + uses: docker/login-action@v1 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GH_TOKEN }} + - name: Install doctl + uses: digitalocean/action-doctl@v2 + with: + token: ${{ secrets.DIGITALOCEAN_ACCESS_TOKEN }} + + - name: Log in to DigitalOcean Container Registry with short-lived credentials + run: doctl registry login --expiry-seconds 3600 + + - name: Build + uses: docker/build-push-action@v2 + with: + context: . + file: ./.deploy/api/Dockerfile + load: true + tags: | + ghcr.io/ever-co/ever-api:latest + everco/ever-api:latest + registry.digitalocean.com/ever/ever-api:latest + + - name: Push to DigitalOcean Registry + run: | + docker push registry.digitalocean.com/ever/ever-api:latest + + - name: Push to Github Registry + run: | + docker push ghcr.io/ever-co/ever-api:latest + + - name: Push to Docker Hub Registry + run: | + docker push everco/ever-api:latest + + ever-admin-angular: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Set up QEMU + uses: docker/setup-qemu-action@v1 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + - name: Login to DockerHub + uses: docker/login-action@v1 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + - name: Login to GitHub Container Registry + uses: docker/login-action@v1 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GH_TOKEN }} + - name: Install doctl + uses: digitalocean/action-doctl@v2 + with: + token: ${{ secrets.DIGITALOCEAN_ACCESS_TOKEN }} + - name: Log in to DigitalOcean Container Registry with short-lived credentials + run: doctl registry login --expiry-seconds 3600 + + - name: Build + uses: docker/build-push-action@v2 + with: + context: . + file: ./.deploy/admin-web-angular/Dockerfile + load: true + tags: | + ghcr.io/ever-co/ever-admin-angular:latest + everco/ever-admin-angular:latest + registry.digitalocean.com/ever/ever-admin-angular:latest + + - name: Push to DigitalOcean Registry + run: | + docker push registry.digitalocean.com/ever/ever-admin-angular:latest + + - name: Push to Github Registry + run: | + docker push ghcr.io/ever-co/ever-admin-angular:latest + + - name: Push to Docker Hub Registry + run: | + docker push everco/ever-admin-angular:latest + + ever-carrier-ionic: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Set up QEMU + uses: docker/setup-qemu-action@v1 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + - name: Login to DockerHub + uses: docker/login-action@v1 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + - name: Login to GitHub Container Registry + uses: docker/login-action@v1 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GH_TOKEN }} + - name: Install doctl + uses: digitalocean/action-doctl@v2 + with: + token: ${{ secrets.DIGITALOCEAN_ACCESS_TOKEN }} + - name: Log in to DigitalOcean Container Registry with short-lived credentials + run: doctl registry login --expiry-seconds 3600 + + - name: Build + uses: docker/build-push-action@v2 + with: + context: . + file: ./.deploy/carrier-mobile-ionic/Dockerfile + load: true + tags: | + ghcr.io/ever-co/ever-carrier-ionic:latest + everco/ever-carrier-ionic:latest + registry.digitalocean.com/ever/ever-carrier-ionic:latest + + - name: Push to DigitalOcean Registry + run: | + docker push registry.digitalocean.com/ever/ever-carrier-ionic:latest + + - name: Push to Github Registry + run: | + docker push ghcr.io/ever-co/ever-carrier-ionic:latest + + - name: Push to Docker Hub Registry + run: | + docker push everco/ever-carrier-ionic:latest + + ever-merchant-ionic: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Set up QEMU + uses: docker/setup-qemu-action@v1 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + - name: Login to DockerHub + uses: docker/login-action@v1 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + - name: Login to GitHub Container Registry + uses: docker/login-action@v1 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GH_TOKEN }} + - name: Install doctl + uses: digitalocean/action-doctl@v2 + with: + token: ${{ secrets.DIGITALOCEAN_ACCESS_TOKEN }} + - name: Log in to DigitalOcean Container Registry with short-lived credentials + run: doctl registry login --expiry-seconds 3600 + + - name: Build + uses: docker/build-push-action@v2 + with: + context: . + file: ./.deploy/merchant-tablet-ionic/Dockerfile + load: true + tags: | + ghcr.io/ever-co/ever-merchant-ionic:latest + everco/ever-merchant-ionic:latest + registry.digitalocean.com/ever/ever-merchant-ionic:latest + + - name: Push to DigitalOcean Registry + run: | + docker push registry.digitalocean.com/ever/ever-merchant-ionic:latest + + - name: Push to Github Registry + run: | + docker push ghcr.io/ever-co/ever-merchant-ionic:latest + + - name: Push to Docker Hub Registry + run: | + docker push everco/ever-merchant-ionic:latest + + ever-shop-ionic: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Set up QEMU + uses: docker/setup-qemu-action@v1 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + - name: Login to DockerHub + uses: docker/login-action@v1 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + - name: Login to GitHub Container Registry + uses: docker/login-action@v1 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GH_TOKEN }} + - name: Install doctl + uses: digitalocean/action-doctl@v2 + with: + token: ${{ secrets.DIGITALOCEAN_ACCESS_TOKEN }} + - name: Log in to DigitalOcean Container Registry with short-lived credentials + run: doctl registry login --expiry-seconds 3600 + + - name: Build + uses: docker/build-push-action@v2 + with: + context: . + file: ./.deploy/shop-mobile-ionic/Dockerfile + load: true + tags: | + ghcr.io/ever-co/ever-shop-ionic:latest + everco/ever-shop-ionic:latest + registry.digitalocean.com/ever/ever-shop-ionic:latest + + - name: Push to DigitalOcean Registry + run: | + docker push registry.digitalocean.com/ever/ever-shop-ionic:latest + + - name: Push to Github Registry + run: | + docker push ghcr.io/ever-co/ever-shop-ionic:latest + + - name: Push to Docker Hub Registry + run: | + docker push everco/ever-shop-ionic:latest + + ever-shop-angular: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Set up QEMU + uses: docker/setup-qemu-action@v1 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + - name: Login to DockerHub + uses: docker/login-action@v1 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + - name: Login to GitHub Container Registry + uses: docker/login-action@v1 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GH_TOKEN }} + - name: Install doctl + uses: digitalocean/action-doctl@v2 + with: + token: ${{ secrets.DIGITALOCEAN_ACCESS_TOKEN }} + - name: Log in to DigitalOcean Container Registry with short-lived credentials + run: doctl registry login --expiry-seconds 3600 + + - name: Build + uses: docker/build-push-action@v2 + with: + context: . + file: ./.deploy/shop-web-angular/Dockerfile + load: true + tags: | + ghcr.io/ever-co/ever-shop-angular:latest + everco/ever-shop-angular:latest + registry.digitalocean.com/ever/ever-shop-angular:latest + + - name: Push to DigitalOcean Registry + run: | + docker push registry.digitalocean.com/ever/ever-shop-angular:latest + + - name: Push to Github Registry + run: | + docker push ghcr.io/ever-co/ever-shop-angular:latest + + - name: Push to Docker Hub Registry + run: | + docker push everco/ever-shop-angular:latest diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..af59ce8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,54 @@ +# compiled output +/dist +/tmp +/out-tsc +/build + +# dependencies +/node_modules + +# IDEs and editors +/.idea +.project +.classpath +.c9/ +*.launch +.settings/ +*.sublime-workspace + +# IDE - VSCode +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json + +# misc +/.sass-cache +/connect.lock +/coverage +/libpeerconnection.log +npm-debug.log +yarn-debug.log +yarn-error.log +testem.log +/typings + +# e2e +/e2e/*.js +/e2e/*.map + +# System Files +.DS_Store +Thumbs.db +/admin/website-angular/yarn-error.log + +.env +*.lerna_backup + +/packages/core/tsconfig.build.tsbuildinfo +/packages/common-angular/*.tsbuildinfo +/packages/admin-web-angular/*.tsbuildinfo +/packages/common/*.tsbuildinfo +/packages/core/*.tsbuildinfo +/packages/shop-mobile-ionic/package-lock.json diff --git a/.gitpod.yml b/.gitpod.yml new file mode 100644 index 0000000..c010da9 --- /dev/null +++ b/.gitpod.yml @@ -0,0 +1,3 @@ +tasks: + - init: yarn install && yarn run build + command: yarn run watch diff --git a/.husky/commit-msg b/.husky/commit-msg new file mode 100644 index 0000000..e8511ea --- /dev/null +++ b/.husky/commit-msg @@ -0,0 +1,4 @@ +#!/bin/sh +. "$(dirname "$0")/_/husky.sh" + +npx --no-install commitlint --edit $1 diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100644 index 0000000..6700f51 --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1,2 @@ +#!/bin/sh +. "$(dirname "$0")/_/husky.sh" diff --git a/.ncurc.json b/.ncurc.json new file mode 100644 index 0000000..1790b65 --- /dev/null +++ b/.ncurc.json @@ -0,0 +1,3 @@ +{ + "upgrade": false +} diff --git a/.prettierrc.yaml b/.prettierrc.yaml new file mode 100644 index 0000000..bd2d7cb --- /dev/null +++ b/.prettierrc.yaml @@ -0,0 +1,5 @@ +semi: true +useTabs: true +tabWidth: 4 +singleQuote: true +arrowParens: 'always' diff --git a/.snyk b/.snyk new file mode 100644 index 0000000..c261936 --- /dev/null +++ b/.snyk @@ -0,0 +1,5 @@ +# Snyk (https://snyk.io) policy file, patches or ignores known vulnerabilities. +version: v1.13.5 +# ignores vulnerabilities until expiry date; change duration by modifying expiry date +ignore: {} +patch: {} diff --git a/.stylelintrc.json b/.stylelintrc.json new file mode 100644 index 0000000..38f78c8 --- /dev/null +++ b/.stylelintrc.json @@ -0,0 +1,90 @@ +{ + "rules": { + "color-hex-case": "lower", + "color-no-invalid-hex": true, + + "function-calc-no-unspaced-operator": true, + "function-comma-space-after": "always-single-line", + "function-comma-space-before": "never", + "function-name-case": "lower", + "function-url-quotes": "always", + "function-whitespace-after": "always", + + "number-leading-zero": "always", + "number-no-trailing-zeros": true, + "length-zero-no-unit": true, + + "string-no-newline": true, + "string-quotes": "single", + + "unit-case": "lower", + "unit-no-unknown": true, + "unit-whitelist": [ + "px", + "%", + "deg", + "ms", + "em", + "vh", + "vw", + "s", + "rem" + ], + + "value-list-comma-space-after": "always-single-line", + "value-list-comma-space-before": "never", + + "shorthand-property-no-redundant-values": true, + + "property-case": "lower", + + "declaration-block-no-duplicate-properties": [ + true, + { + "ignore": ["consecutive-duplicates-with-different-values"] + } + ], + "declaration-block-trailing-semicolon": "always", + "declaration-block-single-line-max-declarations": 1, + "declaration-block-semicolon-space-before": "never", + "declaration-block-semicolon-space-after": "always-single-line", + "declaration-block-semicolon-newline-before": "never-multi-line", + "declaration-block-semicolon-newline-after": "always-multi-line", + "declaration-property-value-blacklist": [ + { "/.*/": ["initial"] }, + { "message": "The `initial` value is not supported in IE." } + ], + + "block-closing-brace-newline-after": [ + "always", + { + "ignoreAtRules": ["if", "else"] + } + ], + "block-closing-brace-newline-before": "always-multi-line", + "block-opening-brace-newline-after": "always-multi-line", + "block-opening-brace-space-before": "always-multi-line", + + "selector-attribute-brackets-space-inside": "never", + "selector-attribute-operator-space-after": "never", + "selector-attribute-operator-space-before": "never", + "selector-combinator-space-after": "always", + "selector-combinator-space-before": "always", + "selector-pseudo-class-case": "lower", + "selector-pseudo-class-parentheses-space-inside": "never", + "selector-pseudo-element-case": "lower", + "selector-pseudo-element-colon-notation": "double", + "selector-pseudo-element-no-unknown": [ + true, + { + "ignorePseudoElements": ["ng-deep"] + } + ], + "selector-type-case": "lower", + "selector-max-id": 0, + + "no-missing-end-of-source-newline": true, + + "max-line-length": 120 + } +} diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..6d16e85 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,54 @@ +language: node_js + +sudo: false +dist: xenial + +git: + depth: 3 + +node_js: + - '14' + +cache: + directories: + - node_modules + - packages/core/node_modules + - packages/admin-web-angular/node_modules + - packages/carrier-mobile-ionic/node_modules + - packages/shop-mobile-ionic/node_modules + - packages/shop-web-angular/node_modules + - packages/merchant-tablet-ionic/node_modules + +addons: + apt: + sources: + - ubuntu-toolchain-r-test + packages: + - g++-4.8 + +before_install: + - npm i -g npm@latest + - npm i -g yarn@latest + - npm i -g lerna@latest + +install: + - git clone -c core.symlinks=true --recursive --depth=3 --branch=$TRAVIS_BRANCH https://github.com/$TRAVIS_REPO_SLUG.git $TRAVIS_REPO_SLUG + - cd $TRAVIS_REPO_SLUG + - git checkout -qf $TRAVIS_COMMIT + - yarn install + - yarn bootstrap + +matrix: + include: + - env: PROJECT=packages/core + - env: PROJECT=packages/admin-web-angular + - env: PROJECT=packages/carrier-mobile-ionic + - env: PROJECT=packages/shop-mobile-ionic + - env: PROJECT=packages/shop-web-angular + - env: PROJECT=packages/merchant-tablet-ionic + +before_script: + - cd $PROJECT + +script: + - yarn build diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..e913111 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,4 @@ +{ + "version": "0.2.0", + "configurations": [] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..06a40f5 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,100 @@ +{ + "git.confirmSync": false, + "explorer.confirmDelete": false, + "editor.tabSize": 4, + "editor.insertSpaces": false, + "editor.detectIndentation": false, + "files.trimTrailingWhitespace": true, + "typescript.tsdk": "./node_modules/typescript/lib", + "tslint.enable": true, + "typescript.preferences.quoteStyle": "single", + "files.exclude": { + "**/.vscode": true, + ".git": true, + "**/.git": true, + ".build": true, + "**/.DS_Store": true, + "**/*.js": true, + "**/*.js.map": true, + "build/**/*.js": { + "when": "$(basename).ts" + }, + "**/node_modules": true, + "**/dist": true, + "**/build": true, + "**/.build": true, + "**/.sourcemaps": true, + "**/www": true, + "**/yarn.lock": true + }, + "search.exclude": { + "**/.vscode": true, + ".git": true, + ".build": true, + "**/*.js": true, + "**/*.js.map": true, + "**/node_modules": true, + "**/bower_components": true, + "**/dist": true, + "**/.build": true, + "**/build": true, + "out/**": true, + "out-build/**": true, + "out-vscode/**": true, + "i18n/**": true, + "extensions/**/out/**": true, + "test/smoke/out/**": true, + "**/.sourcemaps": true, + "**/www": true, + "**/yarn.lock": true + }, + "recommendations": [ + "eg2.tslint", + "mikael.angular-beastcode", + "aaron-bond.better-comments", + "mikestead.dotenv", + "sidneys1.gitconfig", + "codezombiech.gitignore", + "eamodio.gitlens", + "kumar-harsh.graphql-for-vscode", + "abusaidm.html-snippets", + "xabikos.javascriptsnippets", + "davidanson.vscode-markdownlint", + "leizongmin.node-module-intellisense" + ], + "git.ignoreLimitWarning": true, + "vsicons.presets.nestjs": true, + "cSpell.words": [ + "AGPL", + "ARCGIS", + "changeme", + "CLOUDINARY", + "Codegen", + "CQRS", + "devkit", + "echarts", + "entrypoint", + "envalid", + "esnext", + "fontawesome", + "fortawesome", + "GQLPORT", + "highlightjs", + "inversify", + "KEYMETRICS", + "LINKEDIN", + "logpath", + "microservices", + "MIXPANEL", + "mongod", + "mongodb", + "Progressbar", + "pyro", + "snyk", + "typedoc", + "typeorm", + "Whitespaces" + ], + "deepscan.enable": true, + "angular.enable-strict-mode-prompt": false +} diff --git a/.whitesource b/.whitesource new file mode 100644 index 0000000..6b5c4ea --- /dev/null +++ b/.whitesource @@ -0,0 +1,8 @@ +########################################################## +#### WhiteSource Integration configuration file #### +########################################################## + +# Configuration # +#---------------# +ws.repo.scan=true +vulnerable.check.run.conclusion.level=success \ No newline at end of file diff --git a/CREDITS.md b/CREDITS.md new file mode 100644 index 0000000..ab6670a --- /dev/null +++ b/CREDITS.md @@ -0,0 +1,25 @@ +# CREDITS + +This application uses Open Source components and 3rd party libraries, which are licensed under their own respective Open-Source licenses. +You can find the links to source code of their open source projects along with license information below. +We acknowledge and are grateful to these developers for their contributions to open source. + +- Project: Angular Starter https://github.com/gdi2290/angular-starter + Copyright (c) 2015-2018 PatrickJS, Tipe Inc + License (MIT) https://github.com/gdi2290/angular-starter/blob/master/LICENSE + +- Project: ngx-admin https://github.com/akveo/ngx-admin + Copyright (c) 2017 akveo.com + License (MIT) https://github.com/akveo/ngx-admin/blob/master/LICENSE + +- Project: Nest https://github.com/nestjs/nest + Copyright (c) 2017 Kamil Myśliwiec + License (MIT) https://github.com/nestjs/nest/blob/master/LICENSE + +- Project: Ionic https://github.com/ionic-team/ionic + Copyright 2015-present Drifty Co. + License (MIT) https://github.com/ionic-team/ionic/blob/master/LICENSE + +- Project: docker-compose-wait https://github.com/ufoscout/docker-compose-wait + A small command-line utility to wait for other docker images to be started while using docker-compose. + License (Apache-2.0 License) https://github.com/ufoscout/docker-compose-wait/blob/master/LICENSE diff --git a/Jenkinsfile b/Jenkinsfile new file mode 100644 index 0000000..c837518 --- /dev/null +++ b/Jenkinsfile @@ -0,0 +1,180 @@ +pipeline { + agent { + label 'docker' + } + + environment { + DOCKER_BUILDKIT = 1 // Experimental faster build system + REPO_NAME = "ever" + IMAGE_CORE_API = "ever-core" + IMAGE_ADMIN_WEB_ANGULAR = "ever-admin-web-angular" + GITHUB_DOCKER_USERNAME = credentials('github-docker-username') + GITHUB_DOCKER_PASSWORD = credentials('github-docker-password') + GITHUB_DOCKER_REPO = "docker.pkg.github.com/ever-co/ever" + GITHUB_DISPATCH_TOKEN = credentials('github-dispatch-token') + GITHUB_TOKEN = credentials('github-token') + CI_URL = "ci.ever.co" + AWS_ACCESS_KEY_ID = credentials('aws-access-key') + AWS_SECRET_ACCESS_KEY = credentials('aws-secret-key') + AWS_DEFAULT_REGION = "us-east-1" + AWS_ECR_REPO = """${sh( + script: "aws ecr get-login --no-include-email --region ${env.AWS_DEFAULT_REGION} | grep -o 'https://.*' | sed -e 's|https://||g' | tr -d '\n'", + returnStdout: true + )}""" + AWS_ECR_PASSWORD = """${sh( + script: "aws ecr get-login-password --region ${env.AWS_DEFAULT_REGION}", + returnStdout: true + )}""" + WORKFLOW_ID = """${sh( + script: "curl --silent -X GET -H 'Accept: application/vnd.github.v3+json' https://api.github.com/repos/ever-co/$REPO_NAME-pulumi/actions/workflows/pulumi.yml | tr -s ' ' | tr -d '\r' | tr -d '\n' | grep -Eo '[0-9]{5,9}' | uniq", + returnStdout: true + )}""" + } + + stages { + stage("Clone") { + steps{ + git branch: 'develop', + url: 'https://github.com/ever-co/ever.git' + + sh """ + curl 'https://api.github.com/repos/ever-co/${REPO_NAME}/statuses/$GIT_COMMIT' -H 'Authorization: token ${GITHUB_TOKEN}' -H 'Content-Type: application/json' -X POST -d '{"state": "pending", "context": "Jenkins", "description": "Jenkins pipeline is running", "target_url": "https://$CI_URL/job/${JOB_NAME}/$BUILD_NUMBER/console"}' + """ + } + post { + success { + echo "Cloning successful..." + } + failure { + echo "Cloning failed! See log for details. Terminating..." + } + } + } + stage("Docker Image Build") { + parallel { + stage("Core API Image") { + steps { + sh "docker build -t ${env.IMAGE_CORE_API} -f .deploy/core/Dockerfile ." + } + post{ + success { + echo "Image for Core API built!" + } + failure { + echo "Core API Image build failed..." + } + } + } + stage("Admin Web Angular Image") { + steps { + sh "docker build -t ${env.IMAGE_ADMIN_WEB_ANGULAR} -f .deploy/admin-web-angular/Dockerfile ." + } + post { + success { + echo "Image for Admin Web Angular built!" + } + failure { + echo "Admin Web Angular image build failed..." + } + } + } + } + } + stage("Login to repositories") { + steps { + sh "docker login docker.pkg.github.com -u ${GITHUB_DOCKER_USERNAME} -p ${GITHUB_DOCKER_PASSWORD}" + sh "docker login ${AWS_ECR_REPO} -u AWS -p ${AWS_ECR_PASSWORD}" // Login to AWS ECR + } + } + stage ("Push To AWS ECR") { + parallel { + stage ("Core API Image") { + steps { + // Tag and push to ECR + sh "docker tag ${IMAGE_CORE_API} ${AWS_ECR_REPO}/${IMAGE_CORE_API}" + sh "docker push ${AWS_ECR_REPO}/${IMAGE_CORE_API}" + sh "docker rmi ${AWS_ECR_REPO}/${IMAGE_CORE_API}" // Cleans tag + } + post { + success { + echo "Successfuly pushed to ECR on build ${env.BUILD_ID}!" + } + failure { + echo "Push to ECR failed! See log for details..." + } + } + } + stage ("Admin Web Angular Image") { + steps { + sh "docker tag ${IMAGE_ADMIN_WEB_ANGULAR} ${AWS_ECR_REPO}/${IMAGE_ADMIN_WEB_ANGULAR}" + sh "docker push ${AWS_ECR_REPO}/${IMAGE_ADMIN_WEB_ANGULAR}" + sh "docker rmi ${AWS_ECR_REPO}/${IMAGE_ADMIN_WEB_ANGULAR}" // Cleans tag + } + post { + success { + echo "Successfully pushed to ECR on build ${env.BUILD_ID}!" + } + failure { + echo "Push to ECR failed! See log for details..." + } + } + } + } + } + stage ("Push to GitHub") { + parallel { + stage ("Core API Image") { + steps { + sh "docker tag ${IMAGE_CORE_API} ${GITHUB_DOCKER_REPO}/${IMAGE_CORE_API}" + sh "docker push ${GITHUB_DOCKER_REPO}/${IMAGE_CORE_API}" + sh "docker rmi ${GITHUB_DOCKER_REPO}/${IMAGE_CORE_API}" + } + post { + success { + echo "Successfully pushed to GitHub on build ${env.BUILD_ID}!" + } + failure { + echo "Push to GitHub failed! See log for details..." + } + } + } + stage ("Admin Web Angular Image") { + steps { + sh "docker tag ${IMAGE_CORE_API} ${GITHUB_DOCKER_REPO}/${IMAGE_CORE_API}" + sh "docker push ${GITHUB_DOCKER_REPO}/${IMAGE_CORE_API}" + sh "docker rmi ${GITHUB_DOCKER_REPO}/${IMAGE_CORE_API}" + } + post { + success { + echo "Successfully pushed to GitHub on build ${env.BUILD_ID}!" + } + failure { + echo "Push to GitHub failed! See log for details..." + } + } + } + } + } + stage ("Pulumi Update") { + steps { + sh """ + curl -sX POST 'https://api.github.com/repos/ever-co/${REPO_NAME}-pulumi/actions/workflows/2319005/dispatches' -H 'Accept: application/vnd.github.v3+json' -H 'Authorization: token ${GITHUB_DISPATCH_TOKEN}' -d '{"ref": "master"}' + """ + } + } + } + post { + success { + echo "Ever CI/CD pipeline executed successfully!" + sh """ + curl 'https://api.github.com/repos/ever-co/${REPO_NAME}/statuses/$GIT_COMMIT' -H 'Authorization: token ${GITHUB_TOKEN}' -H 'Content-Type: application/json' -X POST -d '{"state": "success", "context": "Jenkins", "description": "Jenkins pipeline succeeded", "target_url": "https://$CI_URL/job/${JOB_NAME}/$BUILD_NUMBER/console"}' + """ + } + failure { + echo "Ever CI/CD pipeline failed..." + sh """ + curl 'https://api.github.com/repos/ever-co/${REPO_NAME}/statuses/$GIT_COMMIT' -H 'Authorization: token ${GITHUB_TOKEN}' -H 'Content-Type: application/json' -X POST -d '{"state": "failure", "context": "Jenkins", "description": "Jenkins pipeline failed", "target_url": "https://$CI_URL/job/${JOB_NAME}/$BUILD_NUMBER/console"}' + """ + } + } +} \ No newline at end of file diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..9670d57 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,73 @@ +# License + +Copyright © 2016-present, Ever Co. LTD. All rights reserved. + +This document represent official information about our licensing, make sure you read and understand it before start using software and source code. + +- Check more detailed information about licensing in our [Wiki](https://github.com/ever-co/ever-demand/wiki/Licensing). +- You can also ask any questions in the [Issue](https://github.com/ever-co/ever-demand/issues/1428) or [Contact Us](https://github.com/ever-co/ever-demand#contact-us). + +This software is available under an Open Source Licenses ("Community Edition"). It is suitable if your business can comply with the requirements of corresponding open-source licenses, see more information below (e.g. requirements to release your modifications under the same open-source licenses for the benefits of our community). + +Alternatively, commercial versions of the software must be used in accordance with the terms and conditions of separate written agreement between you and Ever Co. LTD. With commercial license, your source code (including your changes) is kept proprietary. You can purchase a commercial licenses at https://ever.co/pricing. + +In addition, Ever Co. LTD holds copyright and/or sufficient licenses to all components of the Ever Platform, and therefore can grant, at its sole discretion, the ability for companies, individuals, or organizations to create proprietary modules which may be dynamically linked at runtime with the portions of Ever Platform which fall under our copyright/license umbrella. + +We support the open-source community. If you're building awesome non-profit/open-source projects, we're happy to help and will provide (subject to [acceptance criteria](https://github.com/ever-co/ever-demand/wiki/Free-license-and-hosting-for-Non-profit-and-Open-Source-projects)) Ever Demand Enterprise edition license and free hosting option! Feel free to contact us at to make a request. More details about licensing for non-profit/open-source projects explained in our [Wiki](https://github.com/ever-co/ever-demand/wiki/Free-license-and-hosting-for-Non-profit-and-Open-Source-projects). + +**The default Ever Platform license, without a valid Ever Platform Small Business or Ever Platform Enterprise License agreement, is the Ever Platform Community Edition License.** + +### _Ever Platform Community Edition_ License + +Different parts of the Platform are made available under the terms of the separate Open-Source licenses: + +- Shopping Mobile App and Carrier Mobile App - [GNU General Public License v3.0](https://www.gnu.org/licenses/gpl-3.0.txt) +- Shopping Website - [GNU Affero General Public License v3.0](https://www.gnu.org/licenses/agpl-3.0.txt) +- Merchant Tablet App and Merchant Website - [GNU Affero General Public License v3.0](https://www.gnu.org/licenses/agpl-3.0.txt) +- Admin Website - [GNU Affero General Public License v3.0](https://www.gnu.org/licenses/agpl-3.0.txt) +- Backend Api (Server) - [GNU Affero General Public License v3.0](https://www.gnu.org/licenses/agpl-3.0.txt) +- Pyro shared modules - [MIT License](https://opensource.org/licenses/MIT) +- Other shared modules - [GNU General Public License v3.0](https://www.gnu.org/licenses/gpl-3.0.txt) + +If you decide to choose the Ever® Platform Community™ Edition License, you must comply with the following terms: + +This program is free software: you can redistribute it and/or modify it under the terms of the corresponding licenses described in the LICENSE.md files located in software sub-folders and under the terms of licenses described in individual files. + +This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +You should have received a copy of the relevant GNU Licenses along with this program. If not, see . + +We suggest to check a great overview about different open-source licenses at . +For example, for AGPL v3 (strongest copyleft license we use) conditions can be summarized as following: +- making available complete source code of licensed works and modifications, which include larger works using a licensed work, under the same license. +- Copyright and license notices must be preserved +- Contributors provide an express grant of patent rights. +- When a modified version is used to provide a service over a network, the complete source code of the modified version must be made available. + +Feel free to [Contact Us](https://github.com/ever-co/ever-demand#contact-us) for an additional information about used open-source licenses! + +### _Ever Platform Small Business_ License + +Ever® Platform Small Business™ License can be purchased by businesses with annual revenues do not exceed $1 million. +For more information, please see https://ever.co/pricing or contact us at . + +### _Ever Platform Enterprise_ License + +Ever® Platform Enterprise™ License can be purchased by businesses with more than $1 million in annual revenue. +For more information, please see https://ever.co/pricing or contact us at . + +## Credits + +Please see [CREDITS.md](CREDITS.md) files for a list of libraries and software included in this program and information about licenses. + +## Trademarks + +**Ever**® is a registered trademark of [Ever Co. LTD](https://ever.co). +**Ever® Platform™**, **Ever® Platform Community™**, **Ever® Platform Small Business™** and **Ever® Platform Enterprise™** are all trademarks of [Ever Co. LTD](https://ever.co). + +The trademarks and logos may only be used with the written permission of Ever Co. LTD. and may not be used to promote or otherwise market competitive products or services. +If you wish to use these trademarks and logos you should contact our licensing department at to determine the necessary steps you must take. + +All other brand and product names are trademarks, registered trademarks or service marks of their respective holders. + +If you have any questions regarding our licensing policy, please contact us: (via email) diff --git a/README.md b/README.md new file mode 100644 index 0000000..c62e528 --- /dev/null +++ b/README.md @@ -0,0 +1,278 @@ +[![Gitpod Ready-to-Code](https://img.shields.io/badge/Gitpod-Ready--to--Code-blue?logo=gitpod)](https://gitpod.io/#https://github.com/ever-co/ever-demand) +[![Join the community on Spectrum](https://withspectrum.github.io/badge/badge.svg)](https://spectrum.chat/ever) +[![Gitter](https://badges.gitter.im/JoinChat.svg)](https://gitter.im/ever-co/ever?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +[![Get help on Codementor](https://cdn.codementor.io/badges/get_help_github.svg)](https://www.codementor.io/evereq?utm_source=github&utm_medium=button&utm_term=evereq&utm_campaign=github) + +# Open-Source Commerce Platform for On-Demand Economy and Digital Marketplaces + +[Ever® Demand™](https://ever.co) is an Open-Source, Real-Time, Reactive, **On-Demand** Commerce Platform built with [TypeScript](http://www.typescriptlang.org). + +You are welcome to check more information about the platform at our official website - ****. + +![overview](https://docs.ever.co/docs/assets/overview_small.png) + +## Demos + +**Demos are not available at the moment as we are moving to Kubernetes and showdown old demo server!** + + + +## Video Intros + +- Introduction of both Ever Demand and Ever Gauzy platforms - [view video](https://www.loom.com/share/ff9a9b1fa3a849cca5cf68a6d502443b) (~30 min) or [download](https://media.githubusercontent.com/media/ever-co/ever-demand-docs/master/docs/assets/videos/EverDemandAndGauzyIntro.mp4) +- Introduction to Ever Demand Mobile Shop customer experience (UX) - [view video](https://www.loom.com/share/488f774e6b6d4ee88107443ce4522f1f) (~30 min) or [download](https://media.githubusercontent.com/media/ever-co/ever-demand-docs/master/docs/assets/videos/EverDemandMobileShopIntro.mp4) + +## Features + +- Modern & Open Platform for **On-Demand Economy** and **Digital Marketplaces** +- Supports Single-Store and Multi-Store / Multi-Vendor / Peer-to-Peer Marketplaces +- Everything Reactive, Real-Time and Blazing Fast! +- Headless Commerce framework, which allows different implementations of store-fronts, Admin UIs and client apps. It exposes rich GraphQL, REST and WS APIs. +- Mobile ordering App for customers to make On-Demand orders (Hybrid / PWA, iOS and Android using Ionic / Ionic Native) +- Carrier (Driver) Mobile App for deliveries by carriers, drivers or service providers (iOS and Android using Ionic / Ionic Native) +- Customizing Shopping e-commerce Website for customers to make in-browser On-Demand purchases of food, goods or services +- Merchant Tablet App for Stores/Merchants/Warehouses to manage & track orders, organize deliveries, etc. +- Admin Website used to manage all platform features and settings in the single Web-based interface +- Multi-language and culture settings across Platform (i18N) +- Products Catalogs (global and per Merchant) with Multiple Product Images +- Inventory/Stock Management and Real-time Order Management/Processing across the Platform +- Deliveries/Shipping management and processing across Platform (shipping with real-time location tracking for On-Demand orders) +- Real-Time discounts, promotions and products/services availability updates +- Customers registration, Guest Checkouts, Invitations (optional) +- Gateway and Payment Processing (currently supported Payments Gateway - [Stripe](https://stripe.com)) +- Plugins / Extensions / Custom Fields (WIP) + +## Planned Features + +- Tax Calculations +- Third-party Shipping providers integrations +- Users Roles / Permissions across Platform +- Large products catalogs with products variants, facets and full-text search + +You can also track feature requests from the community in the [separate repo](https://github.com/ever-co/feature-requests/issues). + +### Disclaimer + +_A word of caution_: We are in α (alpha), i.e. Ever® Platform™ is very much under development (work in progress, WIP). +Expect _lots_ of changes and some :bug: and please be nice! :stuck_out_tongue_winking_eye: + +## Technology Stack and Requirements + +- Full-stack [TypeScript](https://www.typescriptlang.org) - frontends and [NodeJs](https://nodejs.org)/[Nest](https://github.com/nestjs/nest) backend. +- Headless Commerce framework (Backend APIs/Server) developed using [Nest](https://github.com/nestjs/nest). Supports GraphQL, REST and WS Real-Time APIs (WebSockets using [Socket.io](https://socket.io) library). +- [Ionic](https://ionicframework.com) (version 5) for Carrier Mobile App and Merchant Tablet App. +- Shopping Mobile App built with [Ionic](https://ionicframework.com) (version 5). +- Shopping Mobile App built with [React Native](https://github.com/facebook/react-native) using [Expo](https://github.com/expo/expo) (WIP). +- Shopping Mobile App built with [Flutter](https://github.com/flutter/flutter) / Dart (WIP). +- Shopping Website developed with [Angular](https://angular.io) (version 9.1) using [Angular Starter](https://github.com/gdi2290/angular-starter). +- Admin Website developed with [Angular](https://angular.io) (version 9.1) using [ngx-admin](https://github.com/akveo/ngx-admin). +- [RxJS](http://reactivex.io/rxjs) library used heavy in every part of the Platform. +- [InversifyJS](http://inversify.io) used for Inversion Control / Dependency Injection in most parts of the Platform. On the Backend/API we also use DI provided by [Nest](https://github.com/nestjs/nest). +- [MongoDB](https://www.mongodb.com) Database used with [Mongoose](https://mongoosejs.com) ORM (supported MongoDB version >= 3.2; we recommend version >=4). +- We have ongoing effort (WIP) to add support for other databases using [TypeORM](https://github.com/typeorm/typeorm) and [Prisma](https://github.com/prisma/prisma). Following additional DBs will be fully supported: MySQL, PostgreSQL, MariaDB, SQLite, MS SQL Server and Oracle. +- For production, we integrate and recommend to use [PM2](https://github.com/Unitech/pm2). + +#### See also README.md and CREDITS.md files in relevant folders for lists of libraries and software included in the Platform, information about licenses and other details. + +## Documentation + +Please refer to our official [Platform Documentation](https://docs.ever.co) and to our [Wiki](https://github.com/ever-co/ever-demand/wiki) (WIP). + +For a quick overview of each project in the Platform (Server, Admin, Shops, etc.), you can search for README.md file in the root of the projects folders. For example, see [./backend/api/README.md](backend/api/README.md) for Server (Backend) related overview. + +## Getting Started + +We follow [Gitflow Workflow](https://nvie.com/posts/a-successful-git-branching-model), so the [`develop` branch](https://github.com/ever-co/ever-demand/tree/develop) may be in an unstable or even broken state during development. Please use [releases](https://github.com/ever-co/ever/releases) or [`master` branch](https://github.com/ever-co/ever-demand/tree/master) instead of the `develop` branch in order to get more stable code. + +### Clone Repo + +Clone the Ever Platform Git repo: + +``` +git clone https://github.com/ever-co/ever-demand.git +``` + +**IMPORTANT NOTE:** + +- if you want to use develop branch (unstable, but latest development) clone using `--branch develop` (our default branch). +- if you want to use release branch (more stable) clone using `--branch master`. + +### Yarn + +Currently, we are using `Yarn` (instead of `npm`), so make sure you have latest Yarn version installed before running Ever Platform: + +``` +npm install -g yarn@latest +``` + +### Quick installation + +After git repo cloned, just run following command to install/bootstrap all dependencies: + +``` +yarn bootstrap +``` + +Above command install required packages in all Platform projects using Lerna. + +Note: if above command fails for any reason, you can try to install required packages manually by running `yarn` inside every sub-folder of `packages` folder with 'package.json' file + +### Build + +You can build all projects in Ever Platform using single command below: + +``` +yarn build:all +``` + +Note: the parallel build available using `yarn build` command + +### Lerna (manual installation) + +We are using [Lerna](https://github.com/lerna/lerna) for mono-repo management. +You need to run the following command from the working folder where you cloned Ever git repo, which install Lerna together with other packages: + +``` +yarn +``` + +You may instead install Lerna globally: + +``` +npm install lerna@latest -g +``` + +Now, after Lerna installed (locally or globally), you need to Bootstrap all dependencies manually: + +``` +yarn lerna bootstrap +``` + +The command above install all required packages for every sub-project of the Ever Platform. + +Note: if above command fails for any reason, you can try to install required packages manually by running `yarn` inside every sub-folder with 'package.json' file. + +After Lerna bootstrap everything you need to run build for all projects as described above in the "Build" section. + +### MongoDB + +Ever platform configured to use MongoDB by default and assume you have MongoDB service running and accepting connections on the default `localhost:27017`. Please see relevant section in our [documentation](https://github.com/ever-co/ever-demand/wiki/MongoDB). + +### Platform Configuration + +See relevant section in our [documentation](https://github.com/ever-co/ever-demand/wiki/Ever-Platform-Configuration). + +### Run Platform Projects + +After you build everything (`yarn build:all`, described above), each project from Ever Platform could be started by single command from this list: + +- Run API server `yarn run:server` +- Run Admin Website `yarn run:admin` and open http://localhost:4200 +- Run Shopping Mobile App `yarn run:shopmobile` and open http://localhost:4201 +- Run Merchant Ionic Tablet App `yarn run:merchant` and open http://localhost:4202 +- Run Carrier Mobile app `yarn run:carrier` and open http://localhost:4203 +- Run Shopping Website `yarn run:shopweb` and open http://localhost:3000 + +Note 1: it is important to build shared / common platform modules (`yarn build:common` or `yarn build:all`) before running the Platform Core (API) or Apps + +Note 2: during development you can run server with `yarn run:server:dev` to enable watch on TS files changes + +Note 3: on the first run, API Server (Backend) creates MongoDB local database `ever_development` with the following (default) Admin user + +- email: `admin@ever.co` +- password: `admin` + +You can use credentials above to login into Platform Admin App. + +Note 3: in order to be able to run every project, you need to make sure everything builds, see section "Build" above. + +## Metrics + +According to [cloc](https://github.com/AlDanial/cloc) project, Ever Platform today has more than 120K lines of TypeScript, GraphQL, HTML / CSS and other code files. You can get more details in the relevant section of our [documentation](https://github.com/ever-co/ever-demand/wiki/Metrics). + +## Contribute + +- Please give us :star: on Github, it **helps**! +- You are more than welcome to submit feature requests in the [separate repo](https://github.com/ever-co/feature-requests/issues) +- Pull requests are always welcome! Please base pull requests against the _develop_ branch and follow the [contributing guide](.github/CONTRIBUTING.md). + +## Contributors + +View full list of our [contributors](https://github.com/ever-co/ever-demand/graphs/contributors). + +## Contact Us + +- [Ever.co Website Contact Us page](https://ever.co/contacts) +- [Discord Chat](https://discord.gg/msqRJ4w) +- [Slack Community](https://join.slack.com/t/everplatform/shared_invite/enQtNzc2NzI1OTgwMjQwLTBkODI3OTU2ZDI1YTQwNWE3OGExYWUwYjE5NThkMjRiYjA0NmFiNzZhYWUzNWViNWI4Nzg2YTc3MzY2MjY0YzU) +- [Spectrum Community](https://spectrum.chat/ever) +- [Gitter Chat](https://gitter.im/ever-co/ever) +- [CodeMentor](https://www.codementor.io/evereq) +- [Telegram](https://t.me/everplatform) +- For business inquiries: +- Please report security vulnerabilities to +- [Ever Platform @ Twitter](https://twitter.com/everplatform) +- [Ever Platform @ Facebook](https://www.facebook.com/everplatform) + +## Security + +Ever Platform follows good security practices, but 100% security cannot be guaranteed in any software! +Ever Platform is provided AS IS without any warranty. Use at your own risk! +See more details in the [LICENSE.md](LICENSE.md). + +In a production setup, all client-side to server-side (backend, APIs) communications should be encrypted using HTTPS/WSS/SSL (REST APIs, GraphQL endpoint, Socket.io WebSockets, etc.). + +If you discover any issue regarding security, please disclose the information responsibly by sending an email to or on [![huntr](https://cdn.huntr.dev/huntr_security_badge_mono.svg)](https://huntr.dev) and not by creating a GitHub issue. + +## License + +We support the open-source community. If you're building awesome non-profit/open-source projects, we're happy to help and will provide (subject to [acceptance criteria](https://github.com/ever-co/ever-demand/wiki/Free-license-and-hosting-for-Non-profit-and-Open-Source-projects)) Ever Demand Enterprise edition license and free hosting option! Feel free to contact us at to make a request. More details explained in our [Wiki](https://github.com/ever-co/ever-demand/wiki/Free-license-and-hosting-for-Non-profit-and-Open-Source-projects). + +This software is available under following licenses: + +- [Ever® Demand™ Platform Community Edition](https://github.com/ever-co/ever-demand/blob/master/LICENSE.md#ever-platform-community-edition-license) +- [Ever® Demand™ Platform Small Business](https://github.com/ever-co/ever-demand/blob/master/LICENSE.md#ever-platform-small-business-license) +- [Ever® Demand™ Platform Enterprise](https://github.com/ever-co/ever-demand/blob/master/LICENSE.md#ever-platform-enterprise-license) + +#### The default Ever® Demand™ Platform license, without a valid Ever® Demand™ Platform Enterprise or Ever® Demand™ Platform Small Business License agreement, is the Ever® Demand™ Platform Community Edition License. + +#### Please see [LICENSE.md](LICENSE.md) for more information on licenses. You can also [compare our offering](https://ever.co/compare-ever/#compare). + +[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fever-co%2Fever.svg?type=large)](https://app.fossa.io/projects/git%2Bgithub.com%2Fever-co%2Fever?ref=badge_large) + +## Trademarks + +**Ever**® is a registered trademark of [Ever Co. LTD](https://ever.co). +**Ever® Demand™**, **Ever® Gauzy™** and **Ever® OpenSaaS™** are all trademarks of [Ever Co. LTD](https://ever.co). + +The trademarks may only be used with the written permission of Ever Co. LTD. and may not be used to promote or otherwise market competitive products or services. + +All other brand and product names are trademarks, registered trademarks or service marks of their respective holders. + +#### Copyright © 2016-present, Ever Co. LTD. All rights reserved. + +![visitors](https://visitor-badge.laobi.icu/badge?page_id=ever-co.ever-platform) +[![huntr](https://cdn.huntr.dev/huntr_security_badge_mono.svg)](https://huntr.dev) +[![Circle CI](https://circleci.com/gh/ever-co/ever-demand.svg?style=svg)](https://circleci.com/gh/ever-co/ever-demand) +[![codecov](https://codecov.io/gh/ever-co/ever-demand/branch/master/graph/badge.svg)](https://codecov.io/gh/ever-co/ever-demand) +[![Codacy Badge](https://app.codacy.com/project/badge/Grade/ec4b3c9e71ff42919563f1809de4e601)](https://www.codacy.com/gh/ever-co/ever-demand/dashboard?utm_source=github.com&utm_medium=referral&utm_content=ever-co/ever-demand&utm_campaign=Badge_Grade) +[![DeepScan grade](https://deepscan.io/api/teams/3293/projects/4849/branches/38566/badge/grade.svg)](https://deepscan.io/dashboard#view=project&tid=3293&pid=4849&bid=38566) +[![Known Vulnerabilities](https://snyk.io/test/github/ever-co/ever-demand/badge.svg)](https://snyk.io/test/github/ever-co/ever-demand) +[![Total alerts](https://img.shields.io/lgtm/alerts/g/ever-co/ever-demand.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/ever-co/ever-demand/alerts/) +[![Language grade: JavaScript](https://img.shields.io/lgtm/grade/javascript/g/ever-co/ever-demand.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/ever-co/ever-demand/context:javascript) +[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fever-co%2Fever-demand.svg?type=shield)](https://app.fossa.io/projects/git%2Bgithub.com%2Fever-co%2Fever-demand?ref=badge_shield) +[![lerna](https://img.shields.io/badge/maintained%20with-lerna-cc00ff.svg)](https://lernajs.io) +[![Crowdin](https://badges.crowdin.net/e/581540ddcc7c1cf42a50d0e0a6a3d7f7/localized.svg)](https://ever.crowdin.com/ever) + +## P.S. + +- If you are running any business or doing freelance, check our new project [Ever Gauzy](https://github.com/ever-co/ever-gauzy) - Open Business Management Platform (ERP/CRM/HRM) +- [We are Hiring: remote TypeScript / NodeJS / NestJS / Angular & React developers](https://github.com/ever-co/jobs#available-positions) diff --git a/commitlint.config.js b/commitlint.config.js new file mode 100644 index 0000000..a81959b --- /dev/null +++ b/commitlint.config.js @@ -0,0 +1,6 @@ +module.exports = { + extends: [ + '@commitlint/config-conventional', + '@commitlint/config-lerna-scopes' + ] +}; diff --git a/crowdin.yml b/crowdin.yml new file mode 100644 index 0000000..217efdd --- /dev/null +++ b/crowdin.yml @@ -0,0 +1,3 @@ +files: + - source: /packages/admin-web-angular/src/assets/i18n/en.json + translation: /packages/admin-web-angular/src/assets/i18n/%two_letters_code%.json diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..60af1a5 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,148 @@ +version: '3.7' + +services: + db: + image: mongo + container_name: db + restart: always + environment: + - MONGO_DATA_DIR=/data/db + - MONGO_LOG_DIR=/dev/null + volumes: + - mongo_data:/data/db + ports: + - '27017:27017' + command: mongod --logpath=/dev/null # --quiet + networks: + - overlay + + admin-web: + container_name: admin-web + image: ever-admin-web:latest + command: yarn run:admin + build: + context: . + dockerfile: .deploy/admin-web-angular/Dockerfile + environment: + NODE_ENV: production + restart: on-failure + depends_on: + - api + ports: + - 4200:4200 + networks: + - overlay + + shop-web: + container_name: shop-web + image: ever-shop-web:latest + command: yarn run:shopweb + build: + context: . + dockerfile: .deploy/shop-web-angular/Dockerfile + environment: + NODE_ENV: production + restart: on-failure + depends_on: + - api + ports: + - 3000:3000 + networks: + - overlay + + carrier-mobile: + container_name: carrier-mobile + image: ever-carrier-mobile:latest + command: yarn run:carrier + build: + context: . + dockerfile: .deploy/carrier-mobile-ionic/Dockerfile + environment: + NODE_ENV: production + restart: on-failure + depends_on: + - api + ports: + - 4203:4200 + - 4204:4203 + networks: + - overlay + + shop-mobile: + container_name: shop-mobile + image: ever-shop-mobile:latest + command: yarn run:shopmobile + build: + context: . + dockerfile: .deploy/shop-mobile-ionic/Dockerfile + environment: + NODE_ENV: production + restart: on-failure + depends_on: + - api + ports: + - 4201:4201 + networks: + - overlay + + merchant-mobile: + container_name: merchant-mobile + image: ever-merchant-mobile:latest + command: yarn run:merchant + build: + context: . + dockerfile: .deploy/merchant-tablet-ionic/Dockerfile + environment: + NODE_ENV: production + restart: on-failure + depends_on: + - api + ports: + - 4202:4202 + networks: + - overlay + + api: + container_name: api + image: ever-api:latest + build: + context: . + dockerfile: .deploy/api/Dockerfile + args: + NODE_ENV: ${NODE_ENV:-development} + environment: + NODE_ENV: ${NODE_ENV:-development} + WAIT_HOSTS: db:27017 + DB_HOST: db + DB_URI: mongodb://db/ever_development + TESTING_DB_URI: mongodb://db/ever_testing + env_file: + - .env + entrypoint: './entrypoint.compose.sh' + command: ['node', 'packages/core/build/src/main.js'] + restart: on-failure + depends_on: + - db + links: + - db + # volumes: + # - .:/srv/ever + # - root_node_modules:/srv/ever/node_modules + # - core_node_modules:/srv/ever/packages/core/node_modules + ports: + - 5500:5500 + - 2087:2087 + - 2086:2086 + - 8443:8443 + networks: + - overlay + +volumes: + root_node_modules: + core_node_modules: + certificates: + mongo_data: + +networks: + overlay: + driver: bridge diff --git a/ever.code-workspace b/ever.code-workspace new file mode 100644 index 0000000..1618259 --- /dev/null +++ b/ever.code-workspace @@ -0,0 +1,22 @@ +{ + "folders": [ + { + "path": "apps" + }, + { + "path": "libs" + } + ], + "settings": { + "typescript.tsdk": "./node_modules/typescript/lib", + "debug.node.autoAttach": "off", + "search.usePCRE2": true, + "git.ignoreLimitWarning": true, + "checkpoints.showActiveFileOnly": true, + "deepscan.enable": true, + "cSpell.words": [ + "toastr" + ], + "vsicons.presets.nestjs": true + } +} diff --git a/greenkeeper.json b/greenkeeper.json new file mode 100644 index 0000000..cd188c6 --- /dev/null +++ b/greenkeeper.json @@ -0,0 +1,15 @@ +{ + "groups": { + "default": { + "packages": [ + "packages/admin-web-angular/package.json", + "packages/core/package.json", + "packages/carrier-mobile-ionic/package.json", + "packages/merchant-tablet-ionic/package.json", + "package.json", + "packages/shop-mobile-ionic/package.json", + "packages/shop-web-angular/package.json" + ] + } + } +} diff --git a/lerna.json b/lerna.json new file mode 100644 index 0000000..265c688 --- /dev/null +++ b/lerna.json @@ -0,0 +1,29 @@ +{ + "lerna": "3.13.0", + "npmClient": "yarn", + "version": "0.3.0", + "concurrency": 8, + "changelog": { + "repo": "ever-co/ever", + "cacheDir": ".changelog", + "labels": {} + }, + "packages": ["packages/*"], + "useWorkspaces": true, + "command": { + "bootstrap": {}, + "publish": { + "conventionalCommits": true, + "ignoreChanges": ["*.md", "test/**"], + "message": "chore(release): %s" + }, + "version": { + "push": false + }, + "clean": { + "yes": true + } + }, + "npmClientArgs": ["--no-package-lock"], + "stream": true +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..65d9e96 --- /dev/null +++ b/package.json @@ -0,0 +1,288 @@ +{ + "name": "ever-demand", + "version": "0.4.3", + "description": "Ever Demand - Open-Source Commerce Platform for On-Demand Economy and Digital Marketplaces", + "license": "AGPL-3.0", + "homepage": "https://ever.co", + "repository": { + "type": "git", + "url": "https://github.com/ever-co/ever-demand.git" + }, + "bugs": { + "url": "https://github.com/ever-co/ever-demand/issues" + }, + "private": true, + "author": { + "name": "Ever Co. LTD", + "email": "ever@ever.co", + "url": "https://ever.co" + }, + "scripts": { + "prepare:husky": "npx husky install .husky", + "bootstrap": "yarn install --frozen-lockfile && yarn lerna bootstrap", + "build": "yarn run build:common && yarn run build:common:angular && yarn lerna run --parallel build", + "watch": "yarn lerna run --parallel watch", + "clean": "yarn lerna run clean", + "pub": "yarn lerna publish", + "typesync": "yarn lerna exec -- typesync && yarn lerna bootstrap", + "fix": "yarn lerna run fix", + "ncu": "yarn lerna exec -- ncu", + "ncu:u": "yarn lerna exec -- ncu -u && yarn lerna bootstrap", + "lint": "yarn tslint --fix", + "start:api": "yarn run:server", + "run:ncu": "yarn ncu --configFileName=.ncurc.json --configFilePath=./", + "run:server": "yarn run build:server && yarn --cwd ./packages/core run start", + "run:server:dev": "yarn --cwd ./packages/core start:dev", + "run:admin": "yarn --cwd ./packages/admin-web-angular start", + "run:merchant": "yarn --cwd ./packages/merchant-tablet-ionic start", + "run:shopmobile": "yarn --cwd ./packages/shop-mobile-ionic start", + "run:shopexpo": "yarn --cwd ./packages/shop-mobile-expo start", + "run:shopweb": "yarn --cwd ./packages/shop-web-angular start", + "run:carrier": "yarn --cwd ./packages/carrier-mobile-ionic start", + "build:server": "yarn run build:common && yarn lerna run build --scope @ever-platform/core", + "build:common": "yarn lerna run build --scope @ever-platform/common", + "build:common:angular": "yarn lerna run build --scope @ever-platform/common-angular", + "build:admin": "yarn run build:common && yarn run build:common:angular && yarn lerna run build --scope @ever-platform/admin-web-angular", + "build:merchant": "yarn run build:common && yarn run build:common:angular && yarn lerna run build --scope @ever-platform/merchant-tablet-ionic", + "build:shopmobile": "yarn run build:common && yarn run build:common:angular && yarn lerna run build --scope @ever-platform/shop-mobile-ionic", + "build:shopweb": "yarn run build:common && yarn run build:common:angular && yarn lerna run build --scope @ever-platform/shop-web-angular", + "build:carrier": "yarn run build:common && yarn run build:common:angular && yarn lerna run build --scope @ever-platform/carrier-mobile-ionic", + "build:all": "yarn run build:common && yarn run build:common:angular && yarn run build:server && yarn run build:admin && yarn run build:merchant && yarn run build:shopmobile && yarn run build:shopweb && yarn run build:carrier", + "tslint-check": "tslint-config-prettier-check ./tslint.json", + "count": "cross-env yarn cloc packages/core/src packages/shop-web-angular/src packages/carrier-mobile-ionic/src packages/shop-web-angular/src packages/shop-mobile-ionic/src packages/merchant-tablet-ionic/src packages/common-angular packages/common --exclude-dir='node_modules,modules'", + "snyk-protect": "snyk protect" + }, + "resolutions": { + "@angular-devkit/build-angular": "~13.1.0", + "@angular-devkit/architect": "^0.1301.0", + "@angular-devkit/core": "^13.1.0", + "rxjs": "^7.4.0" + }, + "dependencies": { + "@angular/animations": "^13.1.0", + "@angular/common": "^13.1.0", + "@angular/compiler": "^13.1.0", + "@angular/core": "^13.1.0", + "@angular/forms": "^13.1.0", + "@angular/language-service": "~13.1.0", + "@angular/platform-browser": "^13.1.0", + "@angular/platform-browser-dynamic": "^13.1.0", + "@angular/router": "^13.1.0", + "@angular/service-worker": "^13.1.0", + "rxjs": "^7.4.0", + "ng2-smart-table": "^1.7.2", + "ng2-completer": "^9.0.1", + "unleash": "^2.0.2" + }, + "devDependencies": { + "@angular-devkit/architect": "^0.1301.0", + "@angular-devkit/build-angular": "~13.1.0", + "@angular-devkit/core": "^13.1.0", + "@angular-devkit/schematics": "^13.1.0", + "@angular-eslint/eslint-plugin": "~1.0.0", + "@angular-eslint/eslint-plugin-template": "~1.0.0", + "@angular/cli": "^13.1.0", + "@angular/compiler-cli": "^13.1.0", + "@commitlint/cli": "^13.2.1", + "@commitlint/config-conventional": "^13.2.0", + "@commitlint/config-lerna-scopes": "^13.2.0", + "@commitlint/travis-cli": "^13.2.1", + "@compodoc/compodoc": "^1.1.15", + "@nrwl/angular": "^12.9.0", + "@nrwl/cli": "12.9.0", + "@nrwl/cypress": "^12.9.0", + "@nrwl/eslint-plugin-nx": "12.9.0", + "@nrwl/jest": "^12.9.0", + "@nrwl/linter": "12.9.0", + "@nrwl/nest": "^12.9.0", + "@nrwl/node": "^12.9.0", + "@nrwl/tao": "12.9.0", + "@nrwl/workspace": "^12.9.0", + "@nstudio/angular": "12.7.0", + "@nstudio/electron": "12.7.0", + "@nstudio/electron-angular": "12.7.0", + "@nstudio/web": "12.7.0", + "@nstudio/web-angular": "12.7.0", + "@nstudio/xplat": "^12.7.0", + "@semantic-release/changelog": "^5.0.1", + "@semantic-release/git": "^9.0.0", + "@semantic-release/github": "^7.2.1", + "@semantic-release/npm": "^7.1.1", + "@types/d3-color": "^3.0.2", + "@types/googlemaps": "^3.43.3", + "@types/hammerjs": "^2.0.40", + "@types/jasmine": "^3.9.1", + "@types/jasminewd2": "^2.0.10", + "@types/leaflet": "^1.7.5", + "@types/node": "^16.11.0", + "@types/socket.io": "^3.0.2", + "@types/socket.io-client": "^3.0.0", + "@types/source-map": "^0.5.7", + "@types/swiper": "^5.4.3", + "@types/uglify-js": "^3.13.1", + "@types/uuid": "^8.3.1", + "@types/webpack": "^5.28.0", + "@types/ws": "^8.2.0", + "@types/jest": "~25.1.4", + "@types/yargs": "^17.0.4", + "@webcomponents/webcomponentsjs": "^2.6.0", + "add-asset-html-webpack-plugin": "^3.2.0", + "angular-router-loader": "^0.8.5", + "angular2-template-loader": "^0.6.2", + "assets-webpack-plugin": "^7.1.1", + "autoprefixer": "^10.3.7", + "awesome-typescript-loader": "^5.2.1", + "clean-webpack-plugin": "^4.0.0", + "cypress": "8.3.1", + "@cypress/browserify-preprocessor": "^3.0.1", + "cypress-cucumber-preprocessor": "^4.2.0", + "cypress-file-upload": "^5.0.8", + "cloc": "^2.8.0", + "codelyzer": "^6.0.2", + "commitizen": "^4.2.1", + "concurrently": "^6.3.0", + "conventional-changelog": "^3.1.24", + "conventional-changelog-cli": "^2.1.1", + "conventional-changelog-core": "^4.2.4", + "coveralls": "^3.1.1", + "cpr": "^3.0.1", + "cross-env": "^7.0.3", + "cryptiles": "^4.1.3", + "css-loader": "^6.4.0", + "cz-conventional-changelog": "^3.3.0", + "dotenv": "^10.0.0", + "envalid": "^7.2.1", + "exports-loader": "^3.0.0", + "expose-loader": "^3.0.0", + "extract-text-webpack-plugin": "^3.0.2", + "file-loader": "^6.2.0", + "find-root": "^1.1.0", + "gh-pages": "^3.2.3", + "html-loader": "^2.1.2", + "html-webpack-plugin": "^5.4.0", + "husky": "^7.0.2", + "imports-loader": "^3.0.0", + "istanbul-instrumenter-loader": "^3.0.1", + "jasmine-core": "^3.10.0", + "jasmine-spec-reporter": "^7.0.0", + "jest": "^27.2.5", + "json-loader": "^0.5.7", + "lerna": "^4.0.0", + "lerna-changelog": "^2.2.0", + "lerna-update-wizard": "^1.1.0", + "lerna-wizard": "^1.1.1", + "lint-staged": "^11.2.3", + "mini-css-extract-plugin": "^2.4.2", + "ng-router-loader": "^2.1.0", + "ngc-webpack": "~4.1.2", + "nodemon": "^2.0.13", + "npm-check-updates": "^11.8.5", + "npm-run-all": "^4.1.5", + "nyc": "^15.1.0", + "optimize-js-plugin": "^0.0.4", + "parse5": "^6.0.1", + "preload-webpack-plugin": "^2.3.0", + "prettier": "^2.4.1", + "prettier-tslint": "^0.4.2", + "pretty-quick": "^3.1.1", + "protractor": "^7.0.0", + "require-directory": "^2.1.1", + "rimraf": "^3.0.2", + "script-ext-html-webpack-plugin": "^2.1.5", + "snyk": "^1.737.0", + "source-map-loader": "^3.0.0", + "standard-version": "^9.3.1", + "string-replace-loader": "^3.0.3", + "style-loader": "^3.3.0", + "terser": "^5.9.0", + "terser-webpack-plugin": "^5.2.4", + "to-string-loader": "^1.1.6", + "ts-jest": "^27.0.6", + "ts-loader": "^9.2.6", + "ts-node": "^10.3.0", + "tslint": "^6.1.3", + "tslint-config-prettier": "^1.18.0", + "tslint-loader": "^3.6.0", + "typedoc": "^0.22.5", + "typemoq": "^2.1.0", + "typescript": "~4.5.3", + "yargs": "^17.2.1", + "typescript-tslint-plugin": "^1.0.1", + "uglifyjs-webpack-plugin": "^2.2.0", + "url-loader": "^4.1.1", + "wait-on": "^6.0.0", + "webpack": "^5.58.2", + "webpack-cli": "^4.9.0", + "webpack-merge": "^5.8.0", + "copy-webpack-plugin": "^9.0.1", + "webpack-node-externals": "^3.0.0", + "webpack-dev-middleware": "^5.2.1", + "webpack-dev-server": "^4.3.1", + "webpack-graphql-loader": "^1.0.2", + "webpack-inline-manifest-plugin": "^4.0.1", + "webpack-bundle-analyzer": "^4.5.0" + }, + "config": { + "commitizen": { + "path": "cz-conventional-changelog" + } + }, + "commitlint": { + "extends": [ + "@commitlint/config-conventional" + ], + "rules": {} + }, + "husky": { + "hooks": { + "commit-msg": "commitlint -E HUSKY_GIT_PARAMS", + "pre-commit": "pretty-quick --no-verify --staged" + } + }, + "lint-staged": {}, + "release": { + "verifyConditions": [ + "@semantic-release/changelog", + "@semantic-release/npm", + "@semantic-release/git", + "@semantic-release/github" + ], + "prepare": [ + "@semantic-release/changelog", + "@semantic-release/npm", + "@semantic-release/git" + ], + "publish": [ + "@semantic-release/github" + ], + "generateNotes": { + "preset": "angular" + }, + "npmPublish": false + }, + "workspaces": { + "packages": [ + "libs/*", + "apps/*", + "tools", + "packages/*", + "packages/plugins/*" + ], + "nohoist": [ + "**/@types/jasmine", + "**/@types/jasminewd2", + "**/@nebular", + "**/@angular*/**", + "**/@ngtools/**" + ] + }, + "engines": { + "node": ">=14.4.0", + "yarn": ">=1.13.0" + }, + "snyk": true, + "xplat": { + "prefix": "ever", + "framework": "angular" + } +} diff --git a/package.workspaces.json b/package.workspaces.json new file mode 100644 index 0000000..c1ad8c3 --- /dev/null +++ b/package.workspaces.json @@ -0,0 +1,14 @@ +{ + "name": "ever", + "version": "0.4.3", + "repository": "https://github.com/ever-co/ever.git", + "private": true, + "resolutions": {}, + "workspaces": [ + "libs/*", + "apps/*", + "tools", + "packages/*", + "packages/plugins/*" + ] +} diff --git a/packages/admin-web-angular/.dockerignore b/packages/admin-web-angular/.dockerignore new file mode 100644 index 0000000..3438721 --- /dev/null +++ b/packages/admin-web-angular/.dockerignore @@ -0,0 +1,10 @@ +.git +.gitignore +.gitmodules +README.md +docker +node_modules +tmp +build +dist +.env diff --git a/packages/admin-web-angular/.ebextensions/logging.config b/packages/admin-web-angular/.ebextensions/logging.config new file mode 100644 index 0000000..65f465d --- /dev/null +++ b/packages/admin-web-angular/.ebextensions/logging.config @@ -0,0 +1,8 @@ +files: + "/opt/elasticbeanstalk/tasks/bundlelogs.d/admin.conf": + content: | + /tmp/logs* + + "/opt/elasticbeanstalk/tasks/taillogs.d/admin.conf": + content: | + /tmp/logs/*.log diff --git a/packages/admin-web-angular/.ebextensions/nodecommand.config b/packages/admin-web-angular/.ebextensions/nodecommand.config new file mode 100644 index 0000000..293c8e2 --- /dev/null +++ b/packages/admin-web-angular/.ebextensions/nodecommand.config @@ -0,0 +1,4 @@ +option_settings: + - namespace: aws:elasticbeanstalk:container:nodejs + option_name: NodeCommand + value: "npm start" diff --git a/packages/admin-web-angular/.ebignore b/packages/admin-web-angular/.ebignore new file mode 100644 index 0000000..c2658d7 --- /dev/null +++ b/packages/admin-web-angular/.ebignore @@ -0,0 +1 @@ +node_modules/ diff --git a/packages/admin-web-angular/.elasticbeanstalk/config.yml b/packages/admin-web-angular/.elasticbeanstalk/config.yml new file mode 100644 index 0000000..4e7e249 --- /dev/null +++ b/packages/admin-web-angular/.elasticbeanstalk/config.yml @@ -0,0 +1,14 @@ +branch-defaults: + master: + environment: ever-admin-env +environment-defaults: + ever-api-env: + branch: null + repository: null +global: + application_name: ever-admin + default_ec2_keyname: ever + default_platform: 64bit Amazon Linux 2016.03 v2.1.3 running Node.js + default_region: us-east-1 + profile: null + sc: git diff --git a/packages/admin-web-angular/.env.template b/packages/admin-web-angular/.env.template new file mode 100644 index 0000000..9b5ce58 --- /dev/null +++ b/packages/admin-web-angular/.env.template @@ -0,0 +1,44 @@ +# NOTE: do NOT ever put here any secure settings! (e.g. Secret Keys) +# We are using dotenv (.env) for consistency with other Platform projects +# This is Angular app and all settings will be loaded into the client browser! + +# Don't forget to update scripts/*.ts and src/environments/*.ts on changes! + +HTTPS_SERVICES_ENDPOINT=https://localhost:2087 +SERVICES_ENDPOINT=http://localhost:5500 +GQL_ENDPOINT=http://localhost:8443/graphql +GQL_SUBSCRIPTIONS_ENDPOINT=ws://localhost:2086/subscriptions + +# Insert below Google Maps API Key and make sure you restrict access to it +GOOGLE_MAPS_API_KEY= + +DEFAULT_LATITUDE=42.6459136 +DEFAULT_LONGITUDE=23.3332736 + +NO_INTERNET_LOGO=assets/images/ever-logo.svg + +MAP_MERCHANT_ICON_LINK=http://maps.google.com/mapfiles/kml/pal3/icon21.png +MAP_USER_ICON_LINK=http://maps.google.com/mapfiles/kml/pal3/icon48.png +MAP_CARRIER_ICON_LINK=http://maps.google.com/mapfiles/kml/pal4/icon54.png +API_FILE_UPLOAD_URL=https://api.cloudinary.com/v1_1/evereq/upload + +COMPANY_NAME=Ever Co. LTD +COMPANY_SITE_LINK=https://ever.co/ +COMPANY_GITHUB_LINK=https://github.com/ever-co +COMPANY_FACEBOOK_LINK=https://www.facebook.com/evercoapp +COMPANY_TWITTER_LINK=https://twitter.com/evercoapp +COMPANY_LINKEDIN_LINK=https://www.linkedin.com/company/ever-co. + +GENERATE_PASSWORD_CHARSET=abcdefghijklnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789$_ + +CURRENCY_SYMBOL=$ + +DEFAULT_LANGUAGE=en-US + +# For maintenance micro service. Ever maintenance API URL: https://maintenance.ever.co/status +SETTINGS_APP_TYPE=admin +SETTINGS_MAINTENANCE_API_URL= + +PORT=4200 +WEB_MEMORY=4096 +WEB_CONCURRENCY=1 diff --git a/packages/admin-web-angular/.gitignore b/packages/admin-web-angular/.gitignore new file mode 100644 index 0000000..c9453d2 --- /dev/null +++ b/packages/admin-web-angular/.gitignore @@ -0,0 +1,71 @@ +# compiled output +/dist +/build +/tmp +/out-tsc + + +**/*.d.ts +**/*.d.ts.map +**/*.js +**/*.js.map + +# dependencies +/node_modules + +# IDEs and editors +/.idea +.project +.classpath +.c9/ +*.launch +.settings/ +*.sublime-workspace + +# IDE - VSCode +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json + +# misc +/.angular/cache +/.sass-cache +/connect.lock +/coverage +/libpeerconnection.log +npm-debug.log +testem.log +/typings + +# Do not store autogenerated docs in repo +/docs + +# e2e +/e2e/*.js +/e2e/*.map + +# System Files +.DS_Store +Thumbs.db + +# Elastic Beanstalk Files + +.elasticbeanstalk/* + +!.elasticbeanstalk/config.yml +!.elasticbeanstalk/*.cfg.yml +!.elasticbeanstalk/*.global.yml + +/package-lock.json + +# environment files +.env +.env.prod +/src/environments/environment.ts +/src/environments/environment.prod.ts + +# storybook builds + +/storybook-static diff --git a/packages/admin-web-angular/.storybook/tsconfig.json b/packages/admin-web-angular/.storybook/tsconfig.json new file mode 100644 index 0000000..042a8b7 --- /dev/null +++ b/packages/admin-web-angular/.storybook/tsconfig.json @@ -0,0 +1,12 @@ +{ + "extends": "../src/tsconfig.app.json", + "compilerOptions": { + "types": ["node", "googlemaps"] + }, + "exclude": [ + "../src/test.ts", + "../src/**/*.spec.ts", + "../projects/**/*.spec.ts" + ], + "include": ["../src/**/*", "../projects/**/*"] +} diff --git a/packages/admin-web-angular/.stylelintrc.json b/packages/admin-web-angular/.stylelintrc.json new file mode 100644 index 0000000..38f78c8 --- /dev/null +++ b/packages/admin-web-angular/.stylelintrc.json @@ -0,0 +1,90 @@ +{ + "rules": { + "color-hex-case": "lower", + "color-no-invalid-hex": true, + + "function-calc-no-unspaced-operator": true, + "function-comma-space-after": "always-single-line", + "function-comma-space-before": "never", + "function-name-case": "lower", + "function-url-quotes": "always", + "function-whitespace-after": "always", + + "number-leading-zero": "always", + "number-no-trailing-zeros": true, + "length-zero-no-unit": true, + + "string-no-newline": true, + "string-quotes": "single", + + "unit-case": "lower", + "unit-no-unknown": true, + "unit-whitelist": [ + "px", + "%", + "deg", + "ms", + "em", + "vh", + "vw", + "s", + "rem" + ], + + "value-list-comma-space-after": "always-single-line", + "value-list-comma-space-before": "never", + + "shorthand-property-no-redundant-values": true, + + "property-case": "lower", + + "declaration-block-no-duplicate-properties": [ + true, + { + "ignore": ["consecutive-duplicates-with-different-values"] + } + ], + "declaration-block-trailing-semicolon": "always", + "declaration-block-single-line-max-declarations": 1, + "declaration-block-semicolon-space-before": "never", + "declaration-block-semicolon-space-after": "always-single-line", + "declaration-block-semicolon-newline-before": "never-multi-line", + "declaration-block-semicolon-newline-after": "always-multi-line", + "declaration-property-value-blacklist": [ + { "/.*/": ["initial"] }, + { "message": "The `initial` value is not supported in IE." } + ], + + "block-closing-brace-newline-after": [ + "always", + { + "ignoreAtRules": ["if", "else"] + } + ], + "block-closing-brace-newline-before": "always-multi-line", + "block-opening-brace-newline-after": "always-multi-line", + "block-opening-brace-space-before": "always-multi-line", + + "selector-attribute-brackets-space-inside": "never", + "selector-attribute-operator-space-after": "never", + "selector-attribute-operator-space-before": "never", + "selector-combinator-space-after": "always", + "selector-combinator-space-before": "always", + "selector-pseudo-class-case": "lower", + "selector-pseudo-class-parentheses-space-inside": "never", + "selector-pseudo-element-case": "lower", + "selector-pseudo-element-colon-notation": "double", + "selector-pseudo-element-no-unknown": [ + true, + { + "ignorePseudoElements": ["ng-deep"] + } + ], + "selector-type-case": "lower", + "selector-max-id": 0, + + "no-missing-end-of-source-newline": true, + + "max-line-length": 120 + } +} diff --git a/packages/admin-web-angular/.travis.yml b/packages/admin-web-angular/.travis.yml new file mode 100644 index 0000000..8345a90 --- /dev/null +++ b/packages/admin-web-angular/.travis.yml @@ -0,0 +1,41 @@ +language: node_js + +sudo: false +dist: trusty + +node_js: + - '8' + - '9' + - '10' + +cache: + directories: + - node_modules + +branches: + only: + - master + - starter-kit + - demo + +addons: + apt: + sources: + - ubuntu-toolchain-r-test + packages: + - g++-4.8 + +before_install: + - npm i -g npm@latest + +install: + - travis_retry npm i + +before_script: + +script: + - npm run lint:ci + - npm run build:prod + +git: + depth: 1 diff --git a/packages/admin-web-angular/CREDITS.md b/packages/admin-web-angular/CREDITS.md new file mode 100644 index 0000000..c8a77e4 --- /dev/null +++ b/packages/admin-web-angular/CREDITS.md @@ -0,0 +1,9 @@ +# CREDITS + +This application uses Open Source components and 3rd party libraries, which are licensed under their own respective Open-Source licenses. +You can find the links to source code of their open source projects along with license information below. +We acknowledge and are grateful to these developers for their contributions to open source. + +- Project: ngx-admin https://github.com/akveo/ngx-admin + Copyright (c) 2017 akveo.com + License (MIT) https://github.com/akveo/ngx-admin/blob/master/LICENSE diff --git a/packages/admin-web-angular/GNU-AGPL-3.0.txt b/packages/admin-web-angular/GNU-AGPL-3.0.txt new file mode 100644 index 0000000..dba13ed --- /dev/null +++ b/packages/admin-web-angular/GNU-AGPL-3.0.txt @@ -0,0 +1,661 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU Affero General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +. diff --git a/packages/admin-web-angular/LICENSE.md b/packages/admin-web-angular/LICENSE.md new file mode 100644 index 0000000..4be4403 --- /dev/null +++ b/packages/admin-web-angular/LICENSE.md @@ -0,0 +1,41 @@ +# License + +Copyright © 2016-present, Ever Co. LTD. All rights reserved. + +This software is available under different licenses + +### _Ever Platform Community Edition_ License for for Admin Website + +If you decide to choose the Ever Platform Community Edition License for for Admin Website, you must comply with the following terms: + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License, version 3, +as published by the Free Software Foundation. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . + +[GNU Affero General Public License v3.0](https://www.gnu.org/licenses/agpl-3.0.txt) + +### _Ever Platform Enterprise_ License + +Alternatively, commercial versions of the software must be used in accordance with the terms and conditions of separate written agreement between you and Ever Co. LTD. + +For more information about Ever Platform Enterprise License please contact . + +#### The default Ever Platform license, without a valid Ever Platform Enterprise License agreement, is the Ever Platform Community Edition License. + +## Credits + +Please see [CREDITS.md](CREDITS.md) file for a list of libraries and software included in this program and information about licenses. + +## Trademarks + +**Ever**® is a registered trademark of [Ever Co. LTD](https://ever.co). +The trademark may only be used with the written permission of Ever Co. LTD. and may not be used to promote or otherwise market competitive products or services. +All other brand and product names are trademarks, registered trademarks or service marks of their respective holders. diff --git a/packages/admin-web-angular/README.md b/packages/admin-web-angular/README.md new file mode 100644 index 0000000..5dee286 --- /dev/null +++ b/packages/admin-web-angular/README.md @@ -0,0 +1 @@ +# Ever Demand Admin Web App (Angular) diff --git a/packages/admin-web-angular/angular.json b/packages/admin-web-angular/angular.json new file mode 100644 index 0000000..27dcd4d --- /dev/null +++ b/packages/admin-web-angular/angular.json @@ -0,0 +1,193 @@ +{ + "$schema": "../../node_modules/@angular/cli/lib/config/schema.json", + "version": 1, + "newProjectRoot": "projects", + "projects": { + "ever-admin": { + "root": "", + "sourceRoot": "src", + "projectType": "application", + "prefix": "app", + "architect": { + "build": { + "builder": "@angular-devkit/build-angular:browser", + "options": { + "preserveSymlinks": true, + "outputPath": "build", + "index": "src/index.html", + "main": "src/main.ts", + "tsConfig": "tsconfig.json", + "polyfills": "src/polyfills.ts", + "assets": [ + "src/favicon.ico", + "src/favicon.png", + { + "glob": "**/*", + "input": "src/assets/", + "ignore": ["**/*.css"], + "output": "/assets/" + }, + { + "glob": "**/*", + "input": "../../node_modules/leaflet/dist/images", + "output": "/assets/img/markers" + }, + { + "glob": "**/*.svg", + "input": "../../node_modules/ionicons/dist/ionicons/svg", + "output": "/assets/svg" + } + ], + "allowedCommonJsDependencies": [ + "highlight.js" + ], + "styles": [ + "../../node_modules/bootstrap/dist/css/bootstrap.css", + "../../node_modules/typeface-exo/index.css", + "../../node_modules/roboto-fontface/css/roboto/roboto-fontface.css", + "../../node_modules/@fortawesome/fontawesome-free/css/all.css", + "../../node_modules/socicon/css/socicon.css", + "../../node_modules/angular-tree-component/dist/angular-tree-component.css", + "../../node_modules/pace-js/templates/pace-theme-flash.tmpl.css", + "../../node_modules/leaflet/dist/leaflet.css", + "src/app/@theme/styles/styles.scss", + "../../node_modules/swiper/swiper-bundle.css", + "../../node_modules/highlight.js/styles/github.css" + ], + "stylePreprocessorOptions": { + "includePaths": ["src/app", "src/assets"] + }, + "scripts": [ + "../../node_modules/pace-js/pace.min.js", + "../../node_modules/tinymce/tinymce.min.js", + "../../node_modules/tinymce/themes/mobile/theme.min.js", + "../../node_modules/tinymce/themes/silver/theme.min.js", + "../../node_modules/tinymce/plugins/link/plugin.min.js", + "../../node_modules/tinymce/plugins/paste/plugin.min.js", + "../../node_modules/tinymce/plugins/table/plugin.min.js", + "../../node_modules/echarts/dist/echarts.min.js", + "../../node_modules/echarts/dist/extension/bmap.min.js", + "../../node_modules/chart.js/dist/chart.min.js" + ] + }, + "configurations": { + "production": { + "budgets": [ + { + "type": "anyComponentStyle", + "maximumWarning": "6kb" + } + ], + "optimization": true, + "outputHashing": "all", + "sourceMap": false, + "namedChunks": false, + "aot": true, + "extractLicenses": true, + "vendorChunk": false, + "buildOptimizer": true, + "fileReplacements": [ + { + "replace": "src/environments/environment.ts", + "with": "src/environments/environment.prod.ts" + } + ] + } + } + }, + "serve": { + "builder": "@angular-devkit/build-angular:dev-server", + "options": { + "browserTarget": "ever-admin:build" + }, + "configurations": { + "production": { + "browserTarget": "ever-admin:build:production" + } + } + }, + "extract-i18n": { + "builder": "@angular-devkit/build-angular:extract-i18n", + "options": { + "browserTarget": "ever-admin:build" + } + }, + "test": { + "builder": "@angular-devkit/build-angular:karma", + "options": { + "main": "src/test.ts", + "karmaConfig": "./karma.conf.js", + "polyfills": "src/polyfills.ts", + "tsConfig": "tsconfig.spec.json", + "scripts": [ + "../../node_modules/pace-js/pace.min.js", + "../../node_modules/tinymce/tinymce.min.js", + "../../node_modules/tinymce/themes/mobile/theme.min.js", + "../../node_modules/tinymce/themes/silver/theme.min.js", + "../../node_modules/tinymce/plugins/link/plugin.min.js", + "../../node_modules/tinymce/plugins/paste/plugin.min.js", + "../../node_modules/tinymce/plugins/table/plugin.min.js", + "../../node_modules/echarts/dist/echarts.min.js", + "../../node_modules/echarts/dist/extension/bmap.min.js", + "../../node_modules/chart.js/dist/chart.min.js" + ], + "styles": [ + "../../node_modules/bootstrap/dist/css/bootstrap.css", + "../../node_modules/typeface-exo/index.css", + "../../node_modules/roboto-fontface/css/roboto/roboto-fontface.css", + "../../node_modules/@fortawesome/fontawesome-free/css/all.css", + "../../node_modules/socicon/css/socicon.css", + "../../node_modules/pace-js/templates/pace-theme-flash.tmpl.css", + "../../node_modules/leaflet/dist/leaflet.css", + "src/app/@theme/styles/styles.scss" + ], + "assets": [ + "src/favicon.ico", + "src/favicon.png", + { + "glob": "**/*", + "input": "src/assets/", + "ignore": ["**/*.css"], + "output": "/assets/" + }, + { + "glob": "**/*", + "input": "../../node_modules/leaflet/dist/images", + "output": "/assets/img/markers" + }, + { + "glob": "**/*.svg", + "input": "../../node_modules/ionicons/dist/ionicons/svg", + "output": "/assets/svg" + } + ] + } + } + } + }, + "ever-admin-e2e": { + "root": "", + "sourceRoot": "", + "projectType": "application", + "architect": { + "e2e": { + "builder": "@angular-devkit/build-angular:protractor", + "options": { + "protractorConfig": "./protractor.conf.js", + "devServerTarget": "ever-admin:serve" + } + } + } + } + }, + "defaultProject": "ever-admin", + "schematics": { + "@schematics/angular:component": { + "prefix": "ea", + "style": "scss" + }, + "@schematics/angular:directive": { + "prefix": "ea" + } + } +} diff --git a/packages/admin-web-angular/e2e/tsconfig.e2e.json b/packages/admin-web-angular/e2e/tsconfig.e2e.json new file mode 100644 index 0000000..9fae5c4 --- /dev/null +++ b/packages/admin-web-angular/e2e/tsconfig.e2e.json @@ -0,0 +1,16 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "outDir": "../out-tsc/e2e", + "baseUrl": "./", + "module": "commonjs", + "target": "es5", + "types": [ + "jasmine", + "jasminewd2", + "node", + "reflect-metadata", + "googlemaps" + ] + } +} diff --git a/packages/admin-web-angular/graphql.config.json b/packages/admin-web-angular/graphql.config.json new file mode 100644 index 0000000..50ba5a6 --- /dev/null +++ b/packages/admin-web-angular/graphql.config.json @@ -0,0 +1,31 @@ +{ + "README_schema": "Specifies how to load the GraphQL schema that completion, error highlighting, and documentation is based on in the IDE", + "schema": { + "README_request": "To request the schema from a url instead, remove the 'file' JSON property above (and optionally delete the default graphql.schema.json file).", + "request": { + "url": "http://localhost:8443/graphql", + "method": "POST", + "README_postIntrospectionQuery": "Whether to POST an introspectionQuery to the url. If the url always returns the schema JSON, set to false and consider using GET", + "postIntrospectionQuery": true, + "README_options": "See the 'Options' section at https://github.com/then/then-request", + "options": { + "headers": { + "user-agent": "JS GraphQL" + } + } + } + }, + + "README_endpoints": "A list of GraphQL endpoints that can be queried from '.graphql' files in the IDE", + "endpoints": [ + { + "name": "Default (http://localhost:8443/graphql)", + "url": "http://localhost:8443/graphql", + "options": { + "headers": { + "user-agent": "JS GraphQL" + } + } + } + ] +} diff --git a/packages/admin-web-angular/ng-package.json b/packages/admin-web-angular/ng-package.json new file mode 100644 index 0000000..1371a03 --- /dev/null +++ b/packages/admin-web-angular/ng-package.json @@ -0,0 +1,10 @@ +{ + "$schema": "../../node_modules/ng-packagr/ng-package.schema.json", + "dest": "dist", + "lib": { + "entryFile": "src/main.ts", + "styleIncludePaths": ["src/app"] + }, + "deleteDestPath": false, + "whitelistedNonPeerDependencies": ["."] +} diff --git a/packages/admin-web-angular/package.json b/packages/admin-web-angular/package.json new file mode 100644 index 0000000..45d5a6f --- /dev/null +++ b/packages/admin-web-angular/package.json @@ -0,0 +1,192 @@ +{ + "name": "@ever-platform/admin-web-angular", + "version": "0.4.3", + "description": "Ever Admin", + "license": "AGPL-3.0", + "homepage": "https://ever.co", + "repository": { + "type": "git", + "url": "https://github.com/ever-co/ever-demand.git" + }, + "bugs": { + "url": "https://github.com/ever-co/ever-demand/issues" + }, + "private": false, + "author": { + "name": "Ever Co. LTD", + "email": "ever@ever.co", + "url": "https://ever.co" + }, + "browserslist": [ + "> 1%", + "last 2 versions", + "IE 11", + "last 1 Chrome version", + "last 1 Firefox version", + "last 2 Edge major versions", + "last 2 Safari major versions", + "last 2 iOS major versions", + "Firefox ESR", + "not IE 9-10", + "not ios_saf 15.2-15.3", + "not safari 15.2-15.3" + ], + "scripts": { + "config": "cross-env NODE_ENV=development NODE_OPTIONS=--max_old_space_size=4096 yarn ts-node -P tsconfig.commonjs.json ./scripts/configure.ts", + "ng:dev": "cross-env NODE_ENV=development NODE_OPTIONS=--max_old_space_size=4096 yarn ng", + "ng:prod": "cross-env NODE_ENV=production NODE_OPTIONS=--max_old_space_size=4096 yarn ng", + "conventional-changelog": "cross-env NODE_ENV=development NODE_OPTIONS=--max_old_space_size=4096 conventional-changelog", + "config:dev": "cross-env NODE_ENV=development NODE_OPTIONS=--max_old_space_size=4096 yarn run config -- --environment=dev", + "config:prod": "cross-env NODE_ENV=production NODE_OPTIONS=--max_old_space_size=4096 yarn run config -- --environment=prod", + "start": "cross-env NODE_ENV=development NODE_OPTIONS=--max_old_space_size=4096 yarn run config:dev && ng serve", + "start:server:prod": "cross-env NODE_ENV=development NODE_OPTIONS=--max_old_space_size=4096 yarn run build:prod && node --harmony ./dist/out-tsc/app.js", + "start:server:pm2": "cross-env NODE_ENV=production NODE_OPTIONS=--max_old_space_size=4096 yarn run build:prod && node --harmony ./dist/out-tsc/packages/admin-web-angular/src/pm2bootstrap.js", + "build": "cross-env NODE_ENV=development NODE_OPTIONS=--max_old_space_size=4096 yarn run config:dev && yarn ng:dev build && yarn tsc", + "build:prod": "cross-env NODE_ENV=production NODE_OPTIONS=--max_old_space_size=4096 yarn run config:prod && yarn ng:prod build -- --prod --aot=false --build-optimizer=false && yarn tsc", + "test": "cross-env NODE_ENV=development NODE_OPTIONS=--max_old_space_size=4096 yarn run config:dev && ng:dev test", + "test:coverage": "cross-env NODE_ENV=development NODE_OPTIONS=--max_old_space_size=4096 yarn run config:dev && rimraf coverage && yarn run test -- --code-coverage", + "lint": "cross-env NODE_ENV=development NODE_OPTIONS=--max_old_space_size=4096 yarn run config:dev && ng:dev lint", + "lint:fix": "cross-env NODE_ENV=development NODE_OPTIONS=--max_old_space_size=4096 yarn run config:dev && ng:dev lint ever-admin --fix", + "lint:styles": "cross-env NODE_ENV=development NODE_OPTIONS=--max_old_space_size=4096 yarn run config:dev && stylelint ./src/**/*.scss", + "lint:ci": "cross-env NODE_ENV=development NODE_OPTIONS=--max_old_space_size=4096 yarn run config:dev && yarn run lint && yarn run lint:styles", + "pree2e": "cross-env NODE_ENV=development NODE_OPTIONS=--max_old_space_size=4096 yarn run config:dev && webdriver-manager update --standalone false --gecko false", + "e2e": "cross-env NODE_ENV=development NODE_OPTIONS=--max_old_space_size=4096 yarn run config:dev && ng e2e", + "docs": "cross-env NODE_ENV=development NODE_OPTIONS=--max_old_space_size=4096 yarn run config:dev && compodoc -p src/tsconfig.app.json -d docs", + "docs:serve": "cross-env NODE_ENV=development NODE_OPTIONS=--max_old_space_size=4096 yarn run config:dev && compodoc -p src/tsconfig.app.json -d docs -s", + "release:changelog": "cross-env NODE_ENV=development NODE_OPTIONS=--max_old_space_size=4096 yarn run config:dev && yarn run conventional-changelog -- -p angular -i CHANGELOG.md -s", + "storybook": "cross-env NODE_ENV=development NODE_OPTIONS=--max_old_space_size=4096 yarn run config:dev && start-storybook -p 9001 -c .storybook", + "build-storybook": "cross-env NODE_ENV=development NODE_OPTIONS=--max_old_space_size=4096 yarn run config:dev && build-storybook -c .storybook" + }, + "dependencies": { + "@agm/core": "^1.1.0", + "@angular/animations": "^13.1.0", + "@angular/cdk": "^13.1.0", + "@angular/common": "^13.1.0", + "@angular/compiler": "^13.1.0", + "@angular/core": "^13.1.0", + "@angular/forms": "^13.1.0", + "@angular/language-service": "^13.1.0", + "@angular/localize": "^13.1.0", + "@angular/platform-browser": "^13.1.0", + "@angular/platform-browser-dynamic": "^13.1.0", + "@angular/router": "^13.1.0", + "@apollo/client": "^3.5.5", + "@asymmetrik/ngx-leaflet": "^8.1.0", + "@ever-co/angular2-wizard": "^0.6.2", + "@ever-platform/common": "^0.4.3", + "@ever-platform/common-angular": "^0.4.3", + "@fortawesome/fontawesome-free": "^5.15.4", + "@juggle/resize-observer": "^3.3.1", + "@nebular/auth": "^8.0.0", + "@nebular/bootstrap": "^8.0.0", + "@nebular/eva-icons": "^8.0.0", + "@nebular/security": "^8.0.0", + "@nebular/theme": "^8.0.0", + "@ng-bootstrap/ng-bootstrap": "^10.0.0", + "@ng-select/ng-select": "^7.3.0", + "@ngx-translate/core": "^13.0.0", + "@ngx-translate/http-loader": "^6.0.0", + "@swimlane/ngx-charts": "^19.1.0", + "angular-2-dropdown-multiselect": "^1.9.0", + "angular-tree-component": "^8.3.0", + "angular2-chartjs": "^0.5.1", + "angular2-toaster": "^11.0.1", + "angular2-uuid": "^1.1.1", + "apollo-angular": "^2.6.0", + "apollo-upload-client": "^16.0.0", + "bootstrap": "^4.6.0", + "buffer": "^6.0.3", + "chart.js": "^3.6.0", + "ckeditor": "^4.11.4", + "core-js": "^3.18.3", + "cryptiles": "^4.1.3", + "echarts": "^5.2.1", + "eva-icons": "^1.1.3", + "faker": "^5.5.3", + "fingerprintjs2": "^2.0.6", + "global": "^4.4.0", + "graphql": "15.7.2", + "graphql-tag": "^2.12.6", + "highlight.js": "^11.2.0", + "intl": "^1.2.5", + "ionicons": "^5.5.3", + "is-url": "^1.2.4", + "jquery": "^3.6.0", + "jsbarcode": "^3.11.5", + "leaflet": "^1.7.1", + "lodash.template": "^4.5.0", + "lodash.templatesettings": "^4.2.0", + "moment": "^2.29.1", + "ng-simple-slideshow": "^1.2.9", + "ng2-ckeditor": "^1.3.4", + "ng2-completer": "^9.0.1", + "ng2-file-upload": "^1.4.0", + "ng2-simple-timer": "^11.2.9", + "ng2-smart-table": "^1.7.2", + "ng2-tree": "^2.0.0-rc.11", + "ngx-echarts": "^7.0.2", + "ngx-highlightjs": "^5.0.0", + "ngx-moment": "^5.0.0", + "ngx-swiper-wrapper": "^10.0.0", + "ngx-translate-multi-http-loader": "^3.0.0", + "normalize.css": "^8.0.1", + "pace-js": "1.2.4", + "pm2": "^5.1.2", + "popper.js": "^1.15.0", + "qrcode": "^1.4.4", + "reflect-metadata": "^0.1.13", + "roboto-fontface": "^0.10.0", + "rxjs": "^7.4.0", + "rxjs-compat": "^6.6.7", + "socicon": "^3.0.5", + "socket.io-client": "^4.3.0", + "stripe": "^8.183.0", + "subscriptions-transport-ws": "^0.11.0", + "tinymce": "^5.10.0", + "tslib": "^2.3.1", + "typeface-exo": "^1.1.13", + "underscore": "^1.13.1", + "underscore.string": "^3.3.5", + "uuid": "^8.3.2", + "zen-observable": "^0.8.15", + "zone.js": "~0.11.4" + }, + "devDependencies": { + "@angular-builders/custom-webpack": "^13.0.0", + "@angular-devkit/architect": "^0.1301.0", + "@angular-devkit/build-angular": "^13.1.0", + "@angular-devkit/build-webpack": "^0.1301.0", + "@angular-devkit/core": "^13.1.0", + "@angular-devkit/schematics": "^13.1.0", + "@angular/cli": "^13.1.0", + "@angular/compiler-cli": "~13.1.0", + "@storybook/addon-actions": "^6.3.12", + "@storybook/addon-knobs": "^6.3.1", + "@storybook/addon-links": "^6.3.12", + "@storybook/addon-notes": "^5.3.21", + "@storybook/addons": "^6.3.12", + "@storybook/angular": "^6.3.12", + "@types/chart.js": "^2.9.34", + "@types/jasmine": "~3.9.1", + "@types/jasminewd2": "~2.0.10", + "@types/node": "^16.11.0", + "codelyzer": "^6.0.2", + "jasmine-core": "~3.10.0", + "jasmine-spec-reporter": "~7.0.0", + "karma": "~6.3.4", + "karma-chrome-launcher": "~3.1.0", + "karma-coverage-istanbul-reporter": "~3.0.3", + "karma-jasmine": "~4.0.1", + "karma-jasmine-html-reporter": "^1.7.0", + "protractor": "~7.0.0", + "swiper": "6.4.15", + "ts-node": "~10.3.0", + "tslint": "~5.20.1", + "typescript": "~4.5.3" + }, + "engines": { + "node": ">=14.4.0", + "yarn": ">=1.13.0" + }, + "snyk": false +} diff --git a/packages/admin-web-angular/scripts/configure.ts b/packages/admin-web-angular/scripts/configure.ts new file mode 100644 index 0000000..0454473 --- /dev/null +++ b/packages/admin-web-angular/scripts/configure.ts @@ -0,0 +1,163 @@ +// NOTE: do NOT ever put here any secure settings! (e.g. Secret Keys) +// We are using dotenv (.env) for consistency with other Platform projects +// This is Angular app and all settings will be loaded into the client browser! + +import { env } from './env'; +import { writeFile, unlinkSync } from 'fs'; +import { argv } from 'yargs'; + +const environment = argv["environment"]; +const isProd = environment === 'prod'; + +if (!env.GOOGLE_MAPS_API_KEY) { + console.warn( + 'WARNING: No Google Maps API Key defined in the .env. Google Maps may not be visible!' + ); +} + +let envFileContent = `// NOTE: Auto-generated file +// The file contents for the current environment will overwrite these during build. +// The build system defaults to the dev environment which uses 'environment.ts', but if you do +// 'ng build --env=prod' then 'environment.prod.ts' will be used instead. +// The list of which env maps to which file can be found in '.angular-cli.json'. + +import { Environment } from './model'; + +`; + + +if (!env.IS_DOCKER) { + envFileContent += ` + + export const environment: Environment = { + production: ${isProd}, + + SERVICES_ENDPOINT: '${env.SERVICES_ENDPOINT}', + HTTPS_SERVICES_ENDPOINT: '${env.HTTPS_SERVICES_ENDPOINT}', + GQL_ENDPOINT: '${env.GQL_ENDPOINT}', + GQL_SUBSCRIPTIONS_ENDPOINT: '${env.GQL_SUBSCRIPTIONS_ENDPOINT}', + + GOOGLE_MAPS_API_KEY: '${env.GOOGLE_MAPS_API_KEY}', + + DEFAULT_LATITUDE: ${env.DEFAULT_LATITUDE}, + DEFAULT_LONGITUDE: ${env.DEFAULT_LONGITUDE}, + + NO_INTERNET_LOGO: '${env.NO_INTERNET_LOGO}', + + MAP_MERCHANT_ICON_LINK: '${env.MAP_MERCHANT_ICON_LINK}', + + MAP_USER_ICON_LINK: '${env.MAP_USER_ICON_LINK}', + + MAP_CARRIER_ICON_LINK: '${env.MAP_CARRIER_ICON_LINK}', + + API_FILE_UPLOAD_URL: '${env.API_FILE_UPLOAD_URL}', + + COMPANY_NAME: '${env.COMPANY_NAME}', + COMPANY_SITE_LINK: '${env.COMPANY_SITE_LINK}', + COMPANY_GITHUB_LINK: '${env.COMPANY_GITHUB_LINK}', + COMPANY_FACEBOOK_LINK: '${env.COMPANY_FACEBOOK_LINK}', + COMPANY_TWITTER_LINK: '${env.COMPANY_TWITTER_LINK}', + COMPANY_LINKEDIN_LINK: '${env.COMPANY_LINKEDIN_LINK}', + + GENERATE_PASSWORD_CHARSET: '${env.GENERATE_PASSWORD_CHARSET}', + + CURRENCY_SYMBOL: '${env.CURRENCY_SYMBOL}', + + DEFAULT_LANGUAGE: '${env.DEFAULT_LANGUAGE}', + + // For maintenance micro service. Ever maintenance API URL: https://maintenance.ever.co/status + SETTINGS_APP_TYPE: '${env.SETTINGS_APP_TYPE}', + SETTINGS_MAINTENANCE_API_URL: '${env.SETTINGS_MAINTENANCE_API_URL}' + }; +`; +} else { + envFileContent += ` + + export const environment: Environment = { + production: ${isProd}, + + SERVICES_ENDPOINT: 'DOCKER_SERVICES_ENDPOINT', + HTTPS_SERVICES_ENDPOINT: 'DOCKER_HTTPS_SERVICES_ENDPOINT', + GQL_ENDPOINT: 'DOCKER_GQL_ENDPOINT', + GQL_SUBSCRIPTIONS_ENDPOINT: 'DOCKER_GQL_SUBSCRIPTIONS_ENDPOINT', + + GOOGLE_MAPS_API_KEY: 'DOCKER_GOOGLE_MAPS_API_KEY', + + DEFAULT_LATITUDE: ${env.DEFAULT_LATITUDE}, + DEFAULT_LONGITUDE: ${env.DEFAULT_LONGITUDE}, + + NO_INTERNET_LOGO: 'DOCKER_NO_INTERNET_LOGO', + + MAP_MERCHANT_ICON_LINK: 'DOCKER_MAP_MERCHANT_ICON_LINK', + + MAP_USER_ICON_LINK: 'DOCKER_MAP_USER_ICON_LINK', + + MAP_CARRIER_ICON_LINK: 'DOCKER_MAP_CARRIER_ICON_LINK', + + API_FILE_UPLOAD_URL: 'DOCKER_API_FILE_UPLOAD_URL', + + COMPANY_NAME: 'DOCKER_COMPANY_NAME', + COMPANY_SITE_LINK: 'DOCKER_COMPANY_SITE_LINK', + COMPANY_GITHUB_LINK: 'DOCKER_COMPANY_GITHUB_LINK', + COMPANY_FACEBOOK_LINK: 'DOCKER_COMPANY_FACEBOOK_LINK', + COMPANY_TWITTER_LINK: 'DOCKER_COMPANY_TWITTER_LINK', + COMPANY_LINKEDIN_LINK: 'DOCKER_COMPANY_LINKEDIN_LINK', + + GENERATE_PASSWORD_CHARSET: 'DOCKER_GENERATE_PASSWORD_CHARSET', + + CURRENCY_SYMBOL: 'DOCKER_CURRENCY_SYMBOL', + + DEFAULT_LANGUAGE: 'DOCKER_DEFAULT_LANGUAGE', + + // For maintenance micro service. Ever maintenance API URL: https://maintenance.ever.co/status + SETTINGS_APP_TYPE: 'DOCKER_SETTINGS_APP_TYPE', + SETTINGS_MAINTENANCE_API_URL: 'DOCKER_SETTINGS_MAINTENANCE_API_URL' + }; + + `; +} + +if (!isProd) { + envFileContent += ` + + // For easier debugging in development mode, you can import the following file + // to ignore zone related error stack frames such as 'zone.run', 'zoneDelegate.invokeTask'. + import 'zone.js'; // Included with Angular CLI. + + `; +} + +// we always want first to remove old generated files (one of them is not needed for current build) +try { + unlinkSync(`./src/environments/environment.ts`); +} catch {} +try { + unlinkSync(`./src/environments/environment.prod.ts`); +} catch {} + +const envFileDest: string = isProd ? 'environment.prod.ts' : 'environment.ts'; +const envFileDestOther: string = !isProd + ? 'environment.prod.ts' + : 'environment.ts'; + +writeFile( + `./src/environments/${envFileDest}`, + envFileContent, + function (err) { + if (err) { + console.log(err); + } else { + console.log(`Generated Angular environment file: ${envFileDest}`); + } +}); + +writeFile( + `./src/environments/${envFileDestOther}`, + '', + function (err) { + if (err) { + console.log(err); + } else { + console.log(`Generated Second Empty Angular environment file: ${envFileDestOther}`); + } +}); diff --git a/packages/admin-web-angular/scripts/env.ts b/packages/admin-web-angular/scripts/env.ts new file mode 100644 index 0000000..7535fa4 --- /dev/null +++ b/packages/admin-web-angular/scripts/env.ts @@ -0,0 +1,126 @@ +// NOTE: do NOT ever put here any secure settings! (e.g. Secret Keys) +// We are using dotenv (.env) for consistency with other Platform projects +// This is Angular app and all settings will be loaded into the client browser! + +require('dotenv').config(); + +import { cleanEnv, num, str, bool, CleanOptions } from 'envalid'; + +export type Env = Readonly<{ + production: boolean; + + // Set to true if build / runs in Docker + IS_DOCKER: boolean; + + SERVICES_ENDPOINT: string; + HTTPS_SERVICES_ENDPOINT: string; + GQL_ENDPOINT: string; + GQL_SUBSCRIPTIONS_ENDPOINT: string; + + GOOGLE_MAPS_API_KEY: string; + + DEFAULT_LATITUDE: number; + DEFAULT_LONGITUDE: number; + + NO_INTERNET_LOGO: string; + + MAP_MERCHANT_ICON_LINK: string; + + MAP_USER_ICON_LINK: string; + + MAP_CARRIER_ICON_LINK: string; + + API_FILE_UPLOAD_URL: string; + + COMPANY_NAME: string; + COMPANY_SITE_LINK: string; + COMPANY_GITHUB_LINK: string; + COMPANY_FACEBOOK_LINK: string; + COMPANY_TWITTER_LINK: string; + COMPANY_LINKEDIN_LINK: string; + + GENERATE_PASSWORD_CHARSET: string; + + CURRENCY_SYMBOL: string; + + SETTINGS_APP_TYPE?: string; + SETTINGS_MAINTENANCE_API_URL?: string; + + DEFAULT_LANGUAGE: string; + + WEB_CONCURRENCY: number; + WEB_MEMORY: number; + PORT: number; +}>; + +const opt: CleanOptions = { +}; + +export const env: Env = cleanEnv( + process.env, + { + production: bool({ default: false }), + + IS_DOCKER: bool({ default: false }), + + SERVICES_ENDPOINT: str({ default: 'http://localhost:5500' }), + HTTPS_SERVICES_ENDPOINT: str({ default: 'https://localhost:2087' }), + GQL_ENDPOINT: str({ default: 'http://localhost:8443/graphql' }), + GQL_SUBSCRIPTIONS_ENDPOINT: str({ + default: 'ws://localhost:2086/subscriptions', + }), + + GOOGLE_MAPS_API_KEY: str({ default: '' }), + + DEFAULT_LATITUDE: num({ default: 42.6459136 }), + DEFAULT_LONGITUDE: num({ default: 23.3332736 }), + + NO_INTERNET_LOGO: str({ default: 'assets/images/ever-logo.svg' }), + + MAP_MERCHANT_ICON_LINK: str({ + default: 'http://maps.google.com/mapfiles/kml/pal3/icon21.png', + }), + + MAP_USER_ICON_LINK: str({ + default: 'http://maps.google.com/mapfiles/kml/pal3/icon48.png', + }), + + MAP_CARRIER_ICON_LINK: str({ + default: 'http://maps.google.com/mapfiles/kml/pal4/icon54.png', + }), + + API_FILE_UPLOAD_URL: str({ + default: 'https://api.cloudinary.com/v1_1/evereq/upload', + }), + + COMPANY_NAME: str({ default: 'Ever Co. LTD' }), + COMPANY_SITE_LINK: str({ default: 'https://ever.co/' }), + COMPANY_GITHUB_LINK: str({ default: 'https://github.com/ever-co' }), + COMPANY_FACEBOOK_LINK: str({ + default: 'https://www.facebook.com/evercoapp', + }), + COMPANY_TWITTER_LINK: str({ default: 'https://twitter.com/evercoapp' }), + COMPANY_LINKEDIN_LINK: str({ + default: 'https://www.linkedin.com/company/ever-co.', + }), + + GENERATE_PASSWORD_CHARSET: str({ + default: + 'abcdefghijklnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789$_', + }), + + CURRENCY_SYMBOL: str({ default: '$' }), + + // For maintenance micro service. Ever maintenance API URL: https://maintenance.ever.co/status + SETTINGS_APP_TYPE: str({ default: 'admin' }), + SETTINGS_MAINTENANCE_API_URL: str({ + default: '', + }), + + DEFAULT_LANGUAGE: str({ default: 'en-US' }), + + WEB_CONCURRENCY: num({ default: 1 }), + WEB_MEMORY: num({ default: 2048 }), + PORT: num({ default: 4200 }), + }, opt +); diff --git a/packages/admin-web-angular/src/app.ts b/packages/admin-web-angular/src/app.ts new file mode 100644 index 0000000..2b3528e --- /dev/null +++ b/packages/admin-web-angular/src/app.ts @@ -0,0 +1,12 @@ +import connect from 'connect'; +import path from 'path'; +import serveStatic from 'serve-static'; +import { env } from '../scripts/env'; + +const port: number = env.PORT; + +connect() + .use(serveStatic(path.join(__dirname, '../../../../../build'))) + .listen(port); + +console.log(`listening on ${port}`); diff --git a/packages/admin-web-angular/src/app/@core/auth/admin-auth-strategy.service.ts b/packages/admin-web-angular/src/app/@core/auth/admin-auth-strategy.service.ts new file mode 100644 index 0000000..bd7172e --- /dev/null +++ b/packages/admin-web-angular/src/app/@core/auth/admin-auth-strategy.service.ts @@ -0,0 +1,309 @@ +import { Observable, from, of as observableOf } from 'rxjs'; +import { NbAuthResult, NbAuthStrategy } from '@nebular/auth'; +import { ActivatedRoute } from '@angular/router'; +import { Apollo, gql } from 'apollo-angular'; +import { catchError, map } from 'rxjs/operators'; +import { Injectable } from '@angular/core'; +import { Store } from '../data/store.service'; +import Admin from '@modules/server.common/entities/Admin'; +import { NbAuthStrategyClass } from '@nebular/auth/auth.options'; +import { IAdminLoginResponse } from '@modules/server.common/routers/IAdminRouter'; +import { getDummyImage } from '@modules/server.common/utils'; + +@Injectable() +export class AdminAuthStrategy extends NbAuthStrategy { + private static config = { + login: { + redirect: { + success: '/', + failure: null, + }, + defaultErrors: [ + 'Login/Email combination is not correct, please try again.', + ], + defaultMessages: ['You have been successfully logged in.'], + }, + register: { + redirect: { + success: '/', + failure: null, + }, + defaultErrors: ['Something went wrong, please try again.'], + defaultMessages: ['You have been successfully registered.'], + }, + logout: { + redirect: { + success: '/', + failure: null, + }, + defaultErrors: ['Something went wrong, please try again.'], + defaultMessages: ['You have been successfully logged out.'], + }, + requestPass: { + redirect: { + success: '/', + failure: null, + }, + defaultErrors: ['Something went wrong, please try again.'], + defaultMessages: [ + 'Reset password instructions have been sent to your email.', + ], + }, + resetPass: { + redirect: { + success: '/', + failure: null, + }, + resetPasswordTokenKey: 'reset_password_token', + defaultErrors: ['Something went wrong, please try again.'], + defaultMessages: ['Your password has been successfully changed.'], + }, + }; + + constructor( + private apollo: Apollo, + private route: ActivatedRoute, + private store: Store + ) { + super(); + } + + static setup(options: { name: string }): [NbAuthStrategyClass, any] { + return [AdminAuthStrategy, options]; + } + + getByEmail(email: string) { + return this.apollo + .query({ + query: gql` + query GetAdminByEmail($email: String!) { + adminByEmail(email: $email) { + _id + } + } + `, + variables: { email }, + }) + .pipe(map((res) => res.data['adminByEmail'])); + } + + authenticate(args: { + email: string; + password: string; + rememberMe?: boolean | null; + }): Observable { + const { email, password } = args; + + // TODO implement remember me feature + const rememberMe = !!args.rememberMe; + + const Login = gql` + mutation Login($email: String!, $password: String!) { + adminLogin(email: $email, password: $password) { + token + admin { + _id + id + email + name + pictureUrl + } + } + } + `; + + return this.apollo + .mutate({ + mutation: Login, + variables: { + email, + password, + }, + errorPolicy: 'all', + }) + .pipe( + map( + /* + Instead of res: any, it should be: + res: { + data: { adminLogin: IAdminLoginResponse }; + errors; + } + */ + (res: any) => { + const { data, errors } = res; + const isSuccessful = !!data.adminLogin; + + if (errors) { + return new NbAuthResult( + false, + res, + AdminAuthStrategy.config.login.redirect.failure, + errors.map((err) => JSON.stringify(err)) + ); + } + + if (!isSuccessful) { + return new NbAuthResult( + false, + res, + AdminAuthStrategy.config.login.redirect.failure, + AdminAuthStrategy.config.login.defaultErrors + ); + } + + this.store.adminId = data.adminLogin.admin.id; + this.store.token = data.adminLogin.token; + + return new NbAuthResult( + isSuccessful, + res, + AdminAuthStrategy.config.login.redirect.success, + [], + AdminAuthStrategy.config.logout.defaultMessages + ); + } + ), + catchError((err) => { + console.error(err); + + return observableOf( + new NbAuthResult( + false, + err, + AdminAuthStrategy.config.login.defaultErrors, + [AdminAuthStrategy.config.logout.defaultErrors] + ) + ); + }) + ); + } + + register(args: { + email: string; + fullName: string; + password: string; + confirmPassword: string; + terms: boolean; + }): Observable { + const { email, fullName, password, confirmPassword, terms } = args; + + if (password !== confirmPassword) { + return observableOf( + new NbAuthResult(false, null, null, [ + "The passwords don't match.", + ]) + ); + } + + const letter = fullName.charAt(0).toUpperCase(); + const pictureUrl = getDummyImage(300, 300, letter); + + const mutation = gql` + mutation Register( + $email: String! + $fullName: String! + $pictureUrl: String! + $password: String! + ) { + registerAdmin( + registerInput: { + admin: { + email: $email + name: $fullName + pictureUrl: $pictureUrl + } + password: $password + } + ) { + _id + id + email + pictureUrl + } + } + `; + + return this.apollo + .mutate({ + mutation, + variables: { + email, + fullName, + password, + pictureUrl, + }, + errorPolicy: 'all', + }) + .pipe( + /* + res: { data: { registerAdmin: Admin }; errors } + */ + map((res: any) => { + const { data, errors } = res; + const admin = data.registerAdmin; + + if (errors) { + return new NbAuthResult( + false, + res, + AdminAuthStrategy.config.register.redirect.failure, + errors.map((err) => JSON.stringify(err)) + ); + } + + return new NbAuthResult( + true, + res, + AdminAuthStrategy.config.register.redirect.success, + [], + AdminAuthStrategy.config.register.defaultMessages + ); + }), + catchError((err) => { + console.error(err); + + return observableOf( + new NbAuthResult( + false, + err, + AdminAuthStrategy.config.register.defaultErrors, + [AdminAuthStrategy.config.logout.defaultErrors] + ) + ); + }) + ); + } + + logout(): Observable { + return from(this._logout()); + } + + requestPassword(data?: any): Observable { + throw new Error('Not implemented yet'); + } + + resetPassword(data: any = {}): Observable { + throw new Error('Not implemented yet'); + } + + refreshToken(data?: any): Observable { + throw new Error('Not implemented yet'); + } + + private async _logout(): Promise { + this.store.clear(); + + this.store.serverConnection = '200'; + + await this.apollo.getClient().resetStore(); + + return new NbAuthResult( + true, + null, + AdminAuthStrategy.config.logout.redirect.success, + [], + AdminAuthStrategy.config.logout.defaultMessages + ); + } +} diff --git a/packages/admin-web-angular/src/app/@core/auth/admin-auth.guard.ts b/packages/admin-web-angular/src/app/@core/auth/admin-auth.guard.ts new file mode 100644 index 0000000..da1f27c --- /dev/null +++ b/packages/admin-web-angular/src/app/@core/auth/admin-auth.guard.ts @@ -0,0 +1,48 @@ +import { Injectable } from '@angular/core'; +import { + ActivatedRouteSnapshot, + CanActivate, + Router, + RouterStateSnapshot, +} from '@angular/router'; +import { Apollo, gql } from 'apollo-angular'; + +@Injectable() +export class AdminAuthGuard implements CanActivate { + constructor( + private readonly router: Router, + private readonly apollo: Apollo + ) {} + + async isAuthenticated() { + const res = await this.apollo + .query<{ adminAuthenticated: boolean }>({ + query: gql` + query IsAdminAuthenticated { + adminAuthenticated + } + `, + fetchPolicy: 'network-only', + }) + .toPromise(); + + return res.data.adminAuthenticated; + } + + async canActivate( + route: ActivatedRouteSnapshot, + state: RouterStateSnapshot + ) { + if (await this.isAuthenticated()) { + // logged in so return true + return true; + } + + // not logged in so redirect to login page with the return url + this.router.navigate(['/auth/login'], { + queryParams: { returnUrl: state.url }, + }); + + return false; + } +} diff --git a/packages/admin-web-angular/src/app/@core/auth/auth.module.ts b/packages/admin-web-angular/src/app/@core/auth/auth.module.ts new file mode 100644 index 0000000..96247d9 --- /dev/null +++ b/packages/admin-web-angular/src/app/@core/auth/auth.module.ts @@ -0,0 +1,40 @@ +import { NgModule } from '@angular/core'; +import { AdminAuthGuard } from './admin-auth.guard'; +import { AdminAuthStrategy } from './admin-auth-strategy.service'; +import { NbAuthModule } from '@nebular/auth'; +import { CommonModule } from '@angular/common'; +import { environment } from 'environments/environment'; + +const socialLinks = [ + { + url: environment.COMPANY_GITHUB_LINK, + icon: 'github-outline', + }, + { + url: environment.COMPANY_FACEBOOK_LINK, + target: '_blank', + icon: 'facebook-outline', + }, + { + url: environment.COMPANY_TWITTER_LINK, + target: '_blank', + icon: 'twitter-outline', + }, +]; + +@NgModule({ + imports: [CommonModule, NbAuthModule], + providers: [ + ...NbAuthModule.forRoot({ + strategies: [AdminAuthStrategy.setup({ name: 'email' })], + forms: { + login: { socialLinks }, + register: { socialLinks }, + }, + }).providers, + + AdminAuthGuard, + AdminAuthStrategy, + ], +}) +export class AuthModule {} diff --git a/packages/admin-web-angular/src/app/@core/core.module.ts b/packages/admin-web-angular/src/app/@core/core.module.ts new file mode 100644 index 0000000..71a0814 --- /dev/null +++ b/packages/admin-web-angular/src/app/@core/core.module.ts @@ -0,0 +1,59 @@ +import { + ModuleWithProviders, + NgModule, + Optional, + SkipSelf, +} from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { NbRoleProvider, NbSecurityModule } from '@nebular/security'; +import { throwIfAlreadyLoaded } from './module-import-guard'; +import { DataModule } from './data/data.module'; +import { AnalyticsService } from './utils/analytics.service'; +import { AuthModule } from './auth/auth.module'; +import { NbEverRoleProvider } from './roleProvider'; + +export const NB_CORE_PROVIDERS = [ + ...DataModule.forRoot().providers, + + NbSecurityModule.forRoot({ + accessControl: { + guest: { + view: '*', + }, + user: { + parent: 'guest', + create: '*', + edit: '*', + remove: '*', + }, + }, + }).providers, + { + provide: NbRoleProvider, + useClass: NbEverRoleProvider, + }, + AnalyticsService, +]; + +@NgModule({ + imports: [AuthModule, CommonModule], + declarations: [], +}) +export class CoreModule { + constructor( + @Optional() + @SkipSelf() + parentModule: CoreModule + ) { + throwIfAlreadyLoaded(parentModule, 'CoreModule'); + } + + static forRoot(): ModuleWithProviders { + const providers: ModuleWithProviders = { + ngModule: CoreModule, + providers: [...NB_CORE_PROVIDERS], + }; + + return providers; + } +} diff --git a/packages/admin-web-angular/src/app/@core/data/admins.service.ts b/packages/admin-web-angular/src/app/@core/data/admins.service.ts new file mode 100644 index 0000000..e2acfc2 --- /dev/null +++ b/packages/admin-web-angular/src/app/@core/data/admins.service.ts @@ -0,0 +1,81 @@ +import { Injectable } from '@angular/core'; +import { Apollo, gql } from 'apollo-angular'; +import Admin from '@modules/server.common/entities/Admin'; +import { map, share } from 'rxjs/operators'; +import { Observable } from 'rxjs'; +import { IAdminUpdateObject } from '@modules/server.common/interfaces/IAdmin'; + +@Injectable() +export class AdminsService { + constructor(private readonly _apollo: Apollo) {} + + getAdmin(id: string): Observable { + return this._apollo + .watchQuery<{ admin: Admin }>({ + query: gql` + query admin($id: String!) { + admin(id: $id) { + id + name + email + pictureUrl + firstName + lastName + } + } + `, + variables: { id }, + pollInterval: 2000, + }) + .valueChanges.pipe( + map((res) => res.data['admin']), + share() + ); + } + + updatePassword( + id: string, + password: { new: string; current: string } + ): Observable { + return this._apollo.mutate({ + mutation: gql` + mutation UpdateAdminPassword( + $id: String! + $password: AdminPasswordUpdateInput! + ) { + updateAdminPassword(id: $id, password: $password) + } + `, + variables: { id, password }, + }); + } + + updateById(id: string, updateInput: IAdminUpdateObject): Observable { + return this._apollo + .mutate<{ id: string; updateInput: IAdminUpdateObject }>({ + mutation: gql` + mutation UpdateAdmin( + $id: String! + $updateInput: AdminUpdateInput! + ) { + updateAdmin(id: $id, updateInput: $updateInput) { + id + name + email + pictureUrl + firstName + lastName + } + } + `, + variables: { + id, + updateInput, + }, + }) + .pipe( + map((result: any) => result.data.updateAdmin), + share() + ); + } +} diff --git a/packages/admin-web-angular/src/app/@core/data/carriers-orders.service.ts b/packages/admin-web-angular/src/app/@core/data/carriers-orders.service.ts new file mode 100644 index 0000000..850268d --- /dev/null +++ b/packages/admin-web-angular/src/app/@core/data/carriers-orders.service.ts @@ -0,0 +1,87 @@ +import { Injectable } from '@angular/core'; +import { Apollo, gql } from 'apollo-angular'; +import Order from '@modules/server.common/entities/Order'; +import { map, share } from 'rxjs/operators'; +import { Observable } from 'rxjs'; + +@Injectable() +export class CarriersOrdersService { + constructor(private readonly apollo: Apollo) {} + + getCarrierOrdersHistory( + carrierId: string, + options: { sort?: string; skip?: number; limit?: number } = { + sort: 'asc', + } + ): Observable { + return this.apollo + .watchQuery<{ getCarrierOrdersHistory: Order[] }>({ + query: gql` + query GetCarrierOrdersHistory( + $carrierId: String! + $options: GeoLocationOrdersOptions + ) { + getCarrierOrdersHistory( + carrierId: $carrierId + options: $options + ) { + id + carrierStatus + carrierStatusText + warehouseStatusText + createdAt + startDeliveryTime + status + deliveryTime + finishedProcessingTime + user { + id + firstName + lastName + image + geoLocation { + streetAddress + house + postcode + countryName + city + } + } + warehouse { + id + name + logo + geoLocation { + house + postcode + countryName + city + } + } + } + } + `, + variables: { carrierId, options }, + pollInterval: 2000, + }) + .valueChanges.pipe( + map((res) => res.data.getCarrierOrdersHistory), + share() + ); + } + + async getCountOfCarrierOrdersHistory(carrierId: string) { + const res = await this.apollo + .query({ + query: gql` + query GetCountOfCarrierOrdersHistory($carrierId: String!) { + getCountOfCarrierOrdersHistory(carrierId: $carrierId) + } + `, + variables: { carrierId }, + }) + .toPromise(); + + return res.data['getCountOfCarrierOrdersHistory']; + } +} diff --git a/packages/admin-web-angular/src/app/@core/data/carriers.service.ts b/packages/admin-web-angular/src/app/@core/data/carriers.service.ts new file mode 100644 index 0000000..f19c581 --- /dev/null +++ b/packages/admin-web-angular/src/app/@core/data/carriers.service.ts @@ -0,0 +1,252 @@ +import { Injectable } from '@angular/core'; +import { Apollo, gql } from 'apollo-angular'; +import Carrier from '@modules/server.common/entities/Carrier'; +import { map, share } from 'rxjs/operators'; +import { Observable } from 'rxjs'; +import ICarrier from '@modules/server.common/interfaces/ICarrier'; +import IPagingOptions from '@modules/server.common/interfaces/IPagingOptions'; + +@Injectable() +export class CarriersService { + constructor(private readonly _apollo: Apollo) {} + + private carriers$: Observable = this._apollo + .watchQuery<{ getCarriers: ICarrier[] }>({ + query: gql` + query getCarriers { + getCarriers { + _id + firstName + lastName + phone + logo + isDeleted + numberOfDeliveries + skippedOrderIds + status + geoLocation { + city + streetAddress + house + loc { + type + coordinates + } + } + } + } + `, + pollInterval: 1000, + }) + .valueChanges.pipe( + map((res) => res.data.getCarriers), + map((carriers) => carriers.map((c) => this._carrierFactory(c))), + share() + ); + + getAllCarriers(): Observable { + return this.carriers$; + } + + getCarriers( + pagingOptions?: IPagingOptions, + carriersFindInput?: any + ): Observable { + return this._apollo + .watchQuery<{ getCarriers: ICarrier[] }>({ + query: gql` + query GetCarriers( + $pagingOptions: PagingOptionsInput + $carriersFindInput: CarriersFindInput + ) { + getCarriers( + pagingOptions: $pagingOptions + carriersFindInput: $carriersFindInput + ) { + _id + firstName + lastName + phone + logo + isDeleted + numberOfDeliveries + skippedOrderIds + status + isActive + username + isSharedCarrier + geoLocation { + city + streetAddress + house + loc { + type + coordinates + } + } + } + } + `, + variables: { pagingOptions, carriersFindInput }, + pollInterval: 2000, + }) + .valueChanges.pipe( + map((res) => res.data.getCarriers), + map((carriers) => carriers.map((c) => this._carrierFactory(c))), + share() + ); + } + + removeByIds(ids: string[]): Observable { + return this._apollo.mutate({ + mutation: gql` + mutation RemoveCarriersByIds($ids: [String!]!) { + removeCarriersByIds(ids: $ids) + } + `, + variables: { ids }, + }); + } + + getCarrierByUsername(username: string): Observable { + return this._apollo + .query({ + query: gql` + query GetCarrierByUsername($username: String!) { + getCarrierByUsername(username: $username) { + username + } + } + `, + variables: { username }, + }) + .pipe( + map((res) => res.data['getCarrierByUsername']), + share() + ); + } + + getCarrierById(id: string): Observable { + return this._apollo + .query({ + query: gql` + query GetCarrierById($id: String!) { + getCarrier(id: $id) { + id + firstName + lastName + phone + logo + isDeleted + numberOfDeliveries + skippedOrderIds + status + isActive + username + isSharedCarrier + geoLocation { + city + streetAddress + house + loc { + type + coordinates + } + } + } + } + `, + variables: { id }, + }) + .pipe( + map((res) => res.data['getCarrier']), + share() + ); + } + + async getCarrierCurrentOrder(carrierId: string): Promise { + const res = await this._apollo + .query({ + query: gql` + query GetCarrierCurrentOrder($carrierId: String!) { + getCarrierCurrentOrder(carrierId: $carrierId) { + id + carrierStatus + carrierStatusText + warehouseStatusText + createdAt + startDeliveryTime + status + deliveryTime + finishedProcessingTime + user { + id + phone + email + apartment + firstName + lastName + image + geoLocation { + house + postcode + countryName + city + streetAddress + loc { + coordinates + type + } + } + } + warehouse { + id + name + logo + contactEmail + contactPhone + geoLocation { + house + postcode + countryName + city + streetAddress + loc { + coordinates + type + } + } + } + } + } + `, + variables: { carrierId }, + }) + .toPromise(); + + return res.data['getCarrierCurrentOrder']; + } + + async getCountOfCarriers(carriersFindInput?: any): Promise { + const res = await this._apollo + .query({ + query: gql` + query GetCountOfCarriers( + $carriersFindInput: CarriersFindInput + ) { + getCountOfCarriers( + carriersFindInput: $carriersFindInput + ) + } + `, + variables: { carriersFindInput }, + }) + .toPromise(); + + return res.data['getCountOfCarriers']; + } + + protected _carrierFactory(carrier: ICarrier) { + return carrier == null ? null : new Carrier(carrier); + } +} diff --git a/packages/admin-web-angular/src/app/@core/data/currencies.service.ts b/packages/admin-web-angular/src/app/@core/data/currencies.service.ts new file mode 100644 index 0000000..ec7a21c --- /dev/null +++ b/packages/admin-web-angular/src/app/@core/data/currencies.service.ts @@ -0,0 +1,64 @@ +import { Injectable } from '@angular/core'; +import { Apollo, gql } from 'apollo-angular'; +import { Observable } from 'rxjs'; +import Currency from '@modules/server.common/entities/Currency'; +import { map, share } from 'rxjs/operators'; + +export interface CurrencyMutationRespone { + success: boolean; + message?: string; + data?: Currency; +} + +@Injectable() +export class CurrenciesService { + constructor(private readonly apollo: Apollo) {} + + private currencies$: Observable = this.apollo + .watchQuery<{ currencies: Currency[] }>({ + query: gql` + query allCurrencies { + currencies { + currencyCode + } + } + `, + pollInterval: 2000, + }) + .valueChanges.pipe( + map((result) => result.data.currencies), + share() + ); + + getCurrencies(): Observable { + return this.currencies$; + } + + create(createInput: { + currencyCode: string; + }): Observable { + return this.apollo + .mutate<{ createCurrency: CurrencyMutationRespone }>({ + mutation: gql` + mutation CreateCurrency( + $createInput: CurrencyCreateInput! + ) { + createCurrency(createInput: $createInput) { + success + message + data { + currencyCode + } + } + } + `, + variables: { + createInput, + }, + }) + .pipe( + map((result) => result.data.createCurrency), + share() + ); + } +} diff --git a/packages/admin-web-angular/src/app/@core/data/data.module.ts b/packages/admin-web-angular/src/app/@core/data/data.module.ts new file mode 100644 index 0000000..092422d --- /dev/null +++ b/packages/admin-web-angular/src/app/@core/data/data.module.ts @@ -0,0 +1,44 @@ +import { ModuleWithProviders, NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { UsersService } from './users.service'; +import { StateService } from './state.service'; +import { SmartTableService } from './smart-table.service'; +import { CarriersService } from './carriers.service'; +import { DeviceService } from './device.service'; +import { OrdersService } from './orders.service'; +import { ProductsService } from './products.service'; +import { ProductsCategoryService } from './productsCategory.service'; +import { WarehousesService } from './warehouses.service'; +import { WarehouseOrdersService } from './warehouseOrders.service'; +import { Store } from './store.service'; +import { DataService } from './data.service'; + +const SERVICES = [ + DataService, + CarriersService, + DeviceService, + OrdersService, + UsersService, + ProductsService, + ProductsCategoryService, + WarehousesService, + WarehouseOrdersService, + Store, + StateService, + SmartTableService, +]; + +@NgModule({ + imports: [CommonModule], + providers: [...SERVICES], +}) +export class DataModule { + static forRoot(): ModuleWithProviders { + const providers: ModuleWithProviders = { + ngModule: DataModule, + providers: [...SERVICES], + }; + + return providers; + } +} diff --git a/packages/admin-web-angular/src/app/@core/data/data.service.ts b/packages/admin-web-angular/src/app/@core/data/data.service.ts new file mode 100644 index 0000000..0e6faa0 --- /dev/null +++ b/packages/admin-web-angular/src/app/@core/data/data.service.ts @@ -0,0 +1,21 @@ +import { Injectable } from '@angular/core'; +import { Apollo, gql } from 'apollo-angular'; +import { map } from 'rxjs/operators'; + +@Injectable() +export class DataService { + constructor(private readonly _apollo: Apollo) {} + + async clearAll(): Promise { + return this._apollo + .query({ + query: gql` + query ClearAll { + clearAll + } + `, + }) + .pipe(map((res) => res.data['clearAll'])) + .toPromise(); + } +} diff --git a/packages/admin-web-angular/src/app/@core/data/device.service.ts b/packages/admin-web-angular/src/app/@core/data/device.service.ts new file mode 100644 index 0000000..776521e --- /dev/null +++ b/packages/admin-web-angular/src/app/@core/data/device.service.ts @@ -0,0 +1,159 @@ +import { Injectable } from '@angular/core'; +import { Apollo, gql } from 'apollo-angular'; +import { Observable } from 'rxjs'; +import { map, share, first } from 'rxjs/operators'; +import Device from '@modules/server.common/entities/Device'; +import { IDeviceRawObject } from '@modules/server.common/interfaces/IDevice'; + +export interface DeviceInfo { + language: string; + type: string; + uuid: string; +} + +export interface DeviceFindInput { + channelId?: string; + language?: string; + type?: string; + uuid?: string; +} + +interface RemovedObject { + n: number; + ok: number; +} + +@Injectable() +export class DeviceService { + constructor(private readonly apollo: Apollo) {} + + private devices$: Observable = this.apollo + .watchQuery<{ devices: IDeviceRawObject[] }>({ + query: gql` + query allDevices { + devices { + _id + language + type + uuid + } + } + `, + pollInterval: 2000, + }) + .valueChanges.pipe( + map((result) => result.data.devices), + map((devices) => devices.map((d) => this._deviceFactory(d))), + share() + ); + + getByFindInput(findInput: DeviceFindInput): Observable { + return this.apollo + .query({ + query: gql` + query GetByUuid($findInput: DeviceFindInput) { + devices(findInput: $findInput) { + id + } + } + `, + variables: { findInput }, + }) + .pipe( + map((res) => res.data['devices']), + share() + ); + } + + async getDeviceByUuid(uuid: string) { + return this.getByFindInput({ uuid }).pipe(first()).toPromise(); + } + + getWithWebsocket() { + const COMMENT_QUERY = gql` + query _allDevices { + devices { + _id + language + type + uuid + } + } + `; + + return this.apollo.watchQuery({ + query: COMMENT_QUERY, + }); + } + + getDevices(): Observable { + return this.devices$; + } + + update(deviceId: string, updateInput: DeviceInfo): Observable { + return this.apollo + .mutate<{ updateDevice: IDeviceRawObject }>({ + mutation: gql` + mutation UpdateDevice( + $deviceId: String! + $updateInput: DeviceUpdateInput! + ) { + updateDevice(id: $deviceId, updateInput: $updateInput) { + id + } + } + `, + variables: { + deviceId, + updateInput, + }, + }) + .pipe( + map((result: any) => result.data.updateDevice.update), + map((d) => this._deviceFactory(d)), + share() + ); + } + + removeByIds(ids: string[]): Observable { + return this.apollo + .mutate({ + mutation: gql` + mutation RemoveDeviceByIds($ids: [String!]!) { + removeDeviceByIds(ids: $ids) { + n + } + } + `, + variables: { ids }, + }) + .pipe( + map((result: any) => result.data.removeDeviceByIds), + share() + ); + } + + create(createInput: DeviceInfo): Observable { + return this.apollo + .mutate<{ createDevice: IDeviceRawObject }>({ + mutation: gql` + mutation CreateDevice($createInput: DeviceCreateInput!) { + createDevice(createInput: $createInput) { + id + } + } + `, + variables: { + createInput, + }, + }) + .pipe( + map((result: any) => result.data.createDevice), + share() + ); + } + + protected _deviceFactory(device: IDeviceRawObject) { + return device == null ? null : new Device(device); + } +} diff --git a/packages/admin-web-angular/src/app/@core/data/fakeDataServices/carriers.ts b/packages/admin-web-angular/src/app/@core/data/fakeDataServices/carriers.ts new file mode 100644 index 0000000..fd85024 --- /dev/null +++ b/packages/admin-web-angular/src/app/@core/data/fakeDataServices/carriers.ts @@ -0,0 +1,181 @@ +import { Injectable } from '@angular/core'; +import CarrierStatus from '@modules/server.common/enums/CarrierStatus'; +import { Country } from '@modules/server.common/entities/GeoLocation'; +import * as faker from 'faker'; +import { ICarrierRegistrationInput } from '@modules/server.common/routers/ICarrierRouter'; +import { environment } from 'environments/environment'; + +const lng = environment['DEFAULT_LONGITUDE']; +const lat = environment['DEFAULT_LATITUDE']; + +@Injectable() +export default class FakeDataCarriers { + readonly registrationInputs: Readonly<{ + generate(): ICarrierRegistrationInput; + mike: ICarrierRegistrationInput; + tom: ICarrierRegistrationInput; + josh: ICarrierRegistrationInput; + }> = + lng && lat + ? { + generate: () => { + const firstName = faker.name.firstName(); + + return { + carrier: { + isDeleted: false, + firstName, + lastName: faker.name.lastName(), + status: faker.datatype.number({ + min: 0, + max: 2, + }) as CarrierStatus, + phone: faker.phone.phoneNumber(), + username: faker.internet.userName(), + logo: faker.image.avatar(), + numberOfDeliveries: faker.datatype.number({ + min: 200, + max: 20000, + }), + deliveriesCountToday: faker.datatype.number({ + min: 0, + max: 60, + }), + isSharedCarrier: true, + geoLocation: { + city: faker.address.city(), + postcode: faker.address.zipCode(), + streetAddress: faker.address.streetAddress(), + house: faker.datatype.number(199).toString(), + countryId: faker.datatype.number( + 220 + ) as Country, + loc: { + type: 'Point', + coordinates: [ + this.getCloseCoordinate(lng), + this.getCloseCoordinate(lat), + ], + }, + }, + }, + password: '123456', + }; + }, + + mike: { + carrier: { + isDeleted: false, + firstName: 'Mike', + lastName: 'Carr', + status: CarrierStatus.Online, + phone: '052-315-2346', + username: 'mike', + logo: faker.image.avatar(), + numberOfDeliveries: faker.datatype.number({ + min: 200, + max: 20000, + }), + deliveriesCountToday: faker.datatype.number({ + min: 0, + max: 60, + }), + isSharedCarrier: true, + geoLocation: { + city: 'Ashdod', + postcode: '77452', + streetAddress: 'HaAtsmaut', + house: '28', + countryId: Country.IL, + loc: { + type: 'Point', + coordinates: [ + this.getCloseCoordinate(lng), + this.getCloseCoordinate(lat), + ], + }, + }, + }, + password: '123456', + }, + + tom: { + carrier: { + isDeleted: false, + firstName: 'Tom', + lastName: 'Bisic', + status: CarrierStatus.Online, + phone: '052-311-5711', + username: 'tom', + logo: faker.image.avatar(), + numberOfDeliveries: faker.datatype.number({ + min: 200, + max: 20000, + }), + deliveriesCountToday: faker.datatype.number({ + min: 0, + max: 60, + }), + isSharedCarrier: true, + geoLocation: { + city: 'Ashdod', + postcode: '77452', + streetAddress: 'HaAtsmaut', + house: '88', + countryId: Country.IL, + loc: { + type: 'Point', + coordinates: [ + this.getCloseCoordinate(lng), + this.getCloseCoordinate(lat), + ], + }, + }, + }, + password: '123456', + }, + + josh: { + carrier: { + firstName: 'Josh', + lastName: 'Lenon', + status: CarrierStatus.Online, + phone: '052-311-5711', + username: 'josh', + logo: faker.image.avatar(), + numberOfDeliveries: faker.datatype.number({ + min: 200, + max: 20000, + }), + deliveriesCountToday: faker.datatype.number({ + min: 0, + max: 60, + }), + isSharedCarrier: true, + geoLocation: { + city: 'Ashdod', + postcode: '77452', + streetAddress: 'HaAtsmaut', + house: '88', + countryId: Country.IL, + loc: { + type: 'Point', + coordinates: [ + this.getCloseCoordinate(lng), + this.getCloseCoordinate(lat), + ], + }, + }, + }, + password: '123456', + }, + } + : null; + + private getCloseCoordinate(coord) { + const num = Math.floor(Math.random() * (9 - 0 + 1)) + 0; + const num2 = Math.floor(Math.random() * (9 - 0 + 1)) + 0; + + return coord + num * 0.01 + num2 * 0.001; + } +} diff --git a/packages/admin-web-angular/src/app/@core/data/fakeDataServices/invites.ts b/packages/admin-web-angular/src/app/@core/data/fakeDataServices/invites.ts new file mode 100644 index 0000000..3553923 --- /dev/null +++ b/packages/admin-web-angular/src/app/@core/data/fakeDataServices/invites.ts @@ -0,0 +1,105 @@ +import { Injectable } from '@angular/core'; +import { IInviteCreateObject } from '@modules/server.common/interfaces/IInvite'; +import { Country } from '@modules/server.common/entities/GeoLocation'; +import * as faker from 'faker'; +import { environment } from 'environments/environment'; + +const NEED_DEFAULT_SETTINGS_MESSAGE = + "Can't generate fake data without DEFAULT_LONGITUDE and DEFAULT_LATITUDE"; +const lng = environment['DEFAULT_LONGITUDE']; +const lat = environment['DEFAULT_LATITUDE']; + +@Injectable() +export default class FakeDataInvites { + get getHardcodedCreateObject() { + if (lng && lat) { + const a: IInviteCreateObject = { + geoLocation: { + city: 'אשדוד', + postcode: '77452', + streetAddress: 'העצמאות', + house: '38', + countryId: 1, + loc: { + type: 'Point', + coordinates: [lng + 0.05, lat - 0.01], + }, + }, + apartment: '3', + }; + + const b: IInviteCreateObject = { + geoLocation: { + city: 'אשדוד', + postcode: '77452', + streetAddress: 'העצמאות', + house: '450', + countryId: 1, + loc: { + type: 'Point', + coordinates: [lng + 0.09, lat - 0.1], + }, + }, + apartment: '2', + }; + + const c: IInviteCreateObject = { + geoLocation: { + city: 'Rishon LeTsiyon', + postcode: '77452', + streetAddress: 'Jabotinsky', + house: '180', + countryId: 1, + loc: { + type: 'Point', + coordinates: [lng, lat - 0.01], + }, + }, + apartment: '6', + }; + + const d: IInviteCreateObject = { + geoLocation: { + city: 'Rishon LeTsiyon', + postcode: '77452', + streetAddress: 'Jabotinsky', + house: '190', + countryId: 1, + loc: { + type: 'Point', + coordinates: [lng + 0.06, lat], + }, + }, + apartment: '6', + }; + + return { a, b, c, d }; + } else { + console.warn(NEED_DEFAULT_SETTINGS_MESSAGE); + return; + } + } + + getCreateObject(): IInviteCreateObject { + if (lng && lat) { + return { + geoLocation: { + city: faker.address.city(), + postcode: faker.address.zipCode(), + notes: faker.lorem.text(1), + streetAddress: faker.address.streetAddress(), + house: faker.datatype.number(199).toString(), + countryId: faker.datatype.number(1) as Country, + loc: { + type: 'Point', + coordinates: [lng, lat], + }, + }, + apartment: faker.datatype.number(199).toString(), + }; + } else { + console.warn(NEED_DEFAULT_SETTINGS_MESSAGE); + return; + } + } +} diff --git a/packages/admin-web-angular/src/app/@core/data/fakeDataServices/products.ts b/packages/admin-web-angular/src/app/@core/data/fakeDataServices/products.ts new file mode 100644 index 0000000..f424645 --- /dev/null +++ b/packages/admin-web-angular/src/app/@core/data/fakeDataServices/products.ts @@ -0,0 +1,455 @@ +import { Injectable } from '@angular/core'; +import { IProductCreateObject } from '@modules/server.common/interfaces/IProduct'; +import * as faker from 'faker'; +import { random } from 'underscore'; +import { IProductsCategory } from '@modules/server.common/interfaces/IProductsCategory'; +import { images } from '@modules/server.common/data/image-urls'; +import { productNames } from '@modules/server.common/data/food-product-names'; +import { TranslateService } from '@ngx-translate/core'; + +@Injectable() +export default class FakeDataProducts { + private locales = []; + constructor(private translateService: TranslateService) { + this.locales = this.translateService.getLangs(); + } + + async getRandomProduct( + inputCategories: IProductsCategory[] = [] + ): Promise { + const images = await this._getRandomImages(); + + return { + title: [ + { + locale: 'en-US', + value: this._getRandomProductName(), + }, + { + locale: 'he-IL', + value: 'רק איזה סוג של מוצר', + }, + ], + description: [ + { + locale: 'en-US', + value: faker.lorem.sentence(24), + }, + ], + details: [ + { + locale: 'en-US', + value: faker.lorem.sentence(), + }, + ], + images, + categories: inputCategories, + }; + } + + async getPeperoniAndMushroomPizzaCreateObject( + inputCategories: IProductsCategory[] + ): Promise { + const imageElementUS = await this._getImageMeta( + 'https://res.cloudinary.com/evereq/image/upload/v1538675155/everbie-products-images/pizza_1_jwsppj.jpg' + ); + + const imageUS = { + url: imageElementUS.src, + orientation: this._getImageOrientation(imageElementUS), + width: imageElementUS.width, + height: imageElementUS.height, + }; + + const randomImages = await this._getRandomImages(['en-US']); + + const images = randomImages.concat([ + { + locale: 'en-US', + ...imageUS, + }, + ]); + + return { + title: [ + { + locale: 'en-US', + value: 'Pepperoni and mushrooms', + }, + { + locale: 'he-IL', + value: 'פפרוני ופטריות', + }, + ], + description: [ + { + locale: 'en-US', + value: 'Peperoni & Mushroom', + }, + ], + details: [ + { + locale: 'en-US', + value: '100% tasty peperoni pizza', + }, + ], + images, + categories: inputCategories, + }; + } + + async getSushiAndCaviarMixCreateObject( + inputCategories: IProductsCategory[] + ): Promise { + const imageElementUS = await this._getImageMeta( + 'https://res.cloudinary.com/evereq/image/upload/v1538675517/sushi_3_1000x1600_yfdxvp_f25npp.jpg' + ); + + const imageUS = { + url: imageElementUS.src, + orientation: this._getImageOrientation(imageElementUS), + width: imageElementUS.width, + height: imageElementUS.height, + }; + + const randomImages = await this._getRandomImages(['en-US']); + + const images = randomImages.concat([ + { + locale: 'en-US', + ...imageUS, + }, + ]); + + return { + title: [ + { + locale: 'en-US', + value: 'Sushi and caviar mix', + }, + { + locale: 'he-IL', + value: 'מיקס סושי וקוויאר', + }, + ], + description: [ + { + locale: 'en-US', + value: 'Sushi & Caviar mix', + }, + ], + details: [ + { + locale: 'en-US', + value: 'Mix Caviar and sushi', + }, + ], + images, + categories: inputCategories, + }; + } + + async getSushiMixCreateObject( + inputCategories: IProductsCategory[] + ): Promise { + const imageElementUS = await this._getImageMeta( + 'https://res.cloudinary.com/evereq/image/upload/v1538675517/sushi_o8gcsm_fxgdij.jpg' + ); + + const imageUS = { + url: imageElementUS.src, + orientation: this._getImageOrientation(imageElementUS), + width: imageElementUS.width, + height: imageElementUS.height, + }; + + const randomImages = await this._getRandomImages(['en-US']); + + const images = randomImages.concat([ + { + locale: 'en-US', + ...imageUS, + }, + ]); + + return { + title: [ + { + locale: 'en-US', + value: 'Mix of 23 sushi', + }, + { + locale: 'he-IL', + value: 'מיקס של 23 סושי', + }, + ], + description: [ + { + locale: 'en-US', + value: '23 Sushi Mix', + }, + ], + details: [ + { + locale: 'en-US', + value: '23 tasty mix from sushi', + }, + ], + images, + categories: inputCategories, + }; + } + + async getPastaCreateObject( + inputCategories: IProductsCategory[] + ): Promise { + const imageElementUS = await this._getImageMeta( + 'https://images.pexels.com/photos/1279330/pexels-photo-1279330.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=750&w=1260' + ); + + const imageElementIL = await this._getImageMeta( + 'https://images.pexels.com/photos/1373915/pexels-photo-1373915.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=750&w=1260' + ); + + const imageElementBG = await this._getImageMeta( + 'https://images.pexels.com/photos/983587/pexels-photo-983587.jpeg?auto=compress&cs=tinysrgb&h=350' + ); + + const imageElementRU = await this._getImageMeta( + 'https://images.pexels.com/photos/769969/pexels-photo-769969.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=750&w=1260' + ); + + const imageUS = { + url: imageElementUS.src, + orientation: this._getImageOrientation(imageElementUS), + width: imageElementUS.width, + height: imageElementUS.height, + }; + + const imageIL = { + url: imageElementIL.src, + orientation: this._getImageOrientation(imageElementIL), + width: imageElementIL.width, + height: imageElementIL.height, + }; + + const imageBG = { + url: imageElementBG.src, + orientation: this._getImageOrientation(imageElementBG), + width: imageElementBG.width, + height: imageElementBG.height, + }; + + const imageRU = { + url: imageElementRU.src, + orientation: this._getImageOrientation(imageElementRU), + width: imageElementRU.width, + height: imageElementRU.height, + }; + + const randomImages = await this._getRandomImages([ + 'en-US', + 'he-IL', + 'ru-RU', + 'bg-BG', + ]); + + const images = randomImages.concat([ + { + locale: 'en-US', + ...imageUS, + }, + { + locale: 'he-IL', + ...imageIL, + }, + { + locale: 'ru-RU', + ...imageRU, + }, + { + locale: 'bg-BG', + ...imageBG, + }, + ]); + + return { + title: [ + { + locale: 'en-US', + value: 'Spiced pasta', + }, + { + locale: 'he-IL', + value: 'פסטה מתובלת', + }, + ], + description: [ + { + locale: 'en-US', + value: 'Seasoned Pasta', + }, + ], + details: [ + { + locale: 'en-US', + value: 'Great seasoned pasta', + }, + ], + images, + categories: inputCategories, + }; + } + + async getSushiBoxCreateObject( + inputCategories: IProductsCategory[] + ): Promise { + const imageElementUS = await this._getImageMeta( + 'https://res.cloudinary.com/evereq/image/upload/v1538675411/everbie-products-images/sushi_1_vdbljq.jpg' + ); + + const imageUS = { + url: imageElementUS.src, + orientation: this._getImageOrientation(imageElementUS), + width: imageElementUS.width, + height: imageElementUS.height, + }; + + const randomImages = await this._getRandomImages(['en-US']); + + const images = randomImages.concat([ + { + locale: 'en-US', + ...imageUS, + }, + ]); + + return { + title: [ + { + locale: 'en-US', + value: 'Sushi box', + }, + { + locale: 'he-IL', + value: 'קופסת סושי', + }, + ], + description: [ + { + locale: 'en-US', + value: 'Sushi box', + }, + ], + details: [ + { + locale: 'en-US', + value: 'Sushi box', + }, + ], + images, + categories: inputCategories, + }; + } + + async getPeperoniAndTomatoPizzaCreateObject( + inputCategories: IProductsCategory[] + ): Promise { + const imageElementUS = await this._getImageMeta( + 'https://res.cloudinary.com/evereq/image/upload/v1538675342/pizza_2_duoq0f_zahy7o.jpg' + ); + + const imageUS = { + url: imageElementUS.src, + orientation: this._getImageOrientation(imageElementUS), + width: imageElementUS.width, + height: imageElementUS.height, + }; + + const randomImages = await this._getRandomImages(['en-US']); + + const images = randomImages.concat([ + { + locale: 'en-US', + ...imageUS, + }, + ]); + + return { + title: [ + { + locale: 'en-US', + value: 'Pepperoni and tomatoes', + }, + { + locale: 'he-IL', + value: 'פפרוני ועגבניות', + }, + ], + description: [ + { + locale: 'en-US', + value: 'Peperoni & Tomato', + }, + ], + details: [ + { + locale: 'en-US', + value: + '100% muzzarella with tomato and pepperoni served with tomato souce by side.', + }, + ], + images, + categories: inputCategories, + }; + } + + private async _getRandomImages(skipLocales = []) { + const image = await this._getImage(); + return this.locales + .filter((locale) => !skipLocales.includes(locale)) + .map((locale) => { + return { locale, ...image }; + }); + } + + private async _getImage() { + try { + const url = images.food[random(0, images.food.length - 1)]; + + const img: HTMLImageElement = await this._getImageMeta(url); + + const imgOrientation = this._getImageOrientation(img); + + return { + url: img.src, + orientation: imgOrientation, + width: img.width, + height: img.height, + }; + } catch (error) { + return error; + } + } + + private _getImageOrientation(image: HTMLImageElement) { + return image.width === image.height + ? 0 + : image.width < image.height + ? 1 + : 2; + } + + private _getImageMeta(url: string): Promise { + return new Promise((resolve, reject) => { + const img = new Image(); + img.onload = () => resolve(img); + img.onerror = reject; + img.src = url; + }); + } + + private _getRandomProductName(): string { + return productNames[random(0, productNames.length - 1)]; + } +} diff --git a/packages/admin-web-angular/src/app/@core/data/fakeDataServices/productsCategories.ts b/packages/admin-web-angular/src/app/@core/data/fakeDataServices/productsCategories.ts new file mode 100644 index 0000000..df22065 --- /dev/null +++ b/packages/admin-web-angular/src/app/@core/data/fakeDataServices/productsCategories.ts @@ -0,0 +1,269 @@ +import { Injectable } from '@angular/core'; +import { images } from '@modules/server.common/data/image-urls'; + +@Injectable() +export default class FakeDataProductsCategories { + private static readonly locales = { + en: 'en-US', + bg: 'bg-BG', + he: 'he-IL', + ru: 'ru-RU', + }; + + getDifferentKindsOfCategories() { + return { + salads: { + name: [ + { + locale: FakeDataProductsCategories.locales.en, + value: 'Salads', + }, + { + locale: FakeDataProductsCategories.locales.bg, + value: 'Салати', + }, + { + locale: FakeDataProductsCategories.locales.he, + value: 'סלטים', + }, + { + locale: FakeDataProductsCategories.locales.ru, + value: 'Салаты', + }, + ], + image: images.productCategories.salads, + }, + dessert: { + name: [ + { + locale: FakeDataProductsCategories.locales.en, + value: 'Desserts', + }, + { + locale: FakeDataProductsCategories.locales.bg, + value: 'Десерти', + }, + { + locale: FakeDataProductsCategories.locales.he, + value: 'קינוחים', + }, + { + locale: FakeDataProductsCategories.locales.ru, + value: 'Десерты', + }, + ], + image: images.productCategories.dessert, + }, + drinks: { + name: [ + { + locale: FakeDataProductsCategories.locales.en, + value: 'Drinks', + }, + { + locale: FakeDataProductsCategories.locales.bg, + value: 'Напитки', + }, + { + locale: FakeDataProductsCategories.locales.he, + value: 'מַשׁקָאוֹת', + }, + { + locale: FakeDataProductsCategories.locales.ru, + value: 'Напитки', + }, + ], + image: images.productCategories.drinks, + }, + meatDishes: { + name: [ + { + locale: FakeDataProductsCategories.locales.en, + value: 'Meat Dishes', + }, + { + locale: FakeDataProductsCategories.locales.bg, + value: 'Месни Ястия', + }, + { + locale: FakeDataProductsCategories.locales.he, + value: 'כלי אוכל', + }, + { + locale: FakeDataProductsCategories.locales.ru, + value: 'Мясные Блюда', + }, + ], + image: images.productCategories.meat, + }, + soups: { + name: [ + { + locale: FakeDataProductsCategories.locales.en, + value: 'Soups', + }, + { + locale: FakeDataProductsCategories.locales.bg, + value: 'Супи', + }, + { + locale: FakeDataProductsCategories.locales.he, + value: 'מרקים', + }, + { + locale: FakeDataProductsCategories.locales.ru, + value: 'Супы', + }, + ], + image: images.productCategories.soups, + }, + alcohol: { + name: [ + { + locale: FakeDataProductsCategories.locales.en, + value: 'Alcohol', + }, + { + locale: FakeDataProductsCategories.locales.bg, + value: 'Алкохол', + }, + { + locale: FakeDataProductsCategories.locales.he, + value: 'כּוֹהֶל', + }, + { + locale: FakeDataProductsCategories.locales.ru, + value: 'Алкоголь', + }, + ], + image: images.productCategories.alcohol, + }, + pizza: { + name: [ + { + locale: FakeDataProductsCategories.locales.en, + value: 'Pizza', + }, + { + locale: FakeDataProductsCategories.locales.bg, + value: 'Пица', + }, + { + locale: FakeDataProductsCategories.locales.he, + value: 'פיצה', + }, + { + locale: FakeDataProductsCategories.locales.ru, + value: 'Пицца', + }, + ], + image: images.productCategories.pizza, + }, + fastFood: { + name: [ + { + locale: FakeDataProductsCategories.locales.en, + value: 'Fast Food', + }, + { + locale: FakeDataProductsCategories.locales.bg, + value: 'Бърза Храна', + }, + { + locale: FakeDataProductsCategories.locales.he, + value: 'אוכל מהיר', + }, + { + locale: FakeDataProductsCategories.locales.ru, + value: 'Быстрое Питание', + }, + ], + image: images.productCategories.fastFood, + }, + burger: { + name: [ + { + locale: FakeDataProductsCategories.locales.en, + value: 'Burger', + }, + { + locale: FakeDataProductsCategories.locales.bg, + value: 'Бургер', + }, + { + locale: FakeDataProductsCategories.locales.he, + value: 'בורגר', + }, + { + locale: FakeDataProductsCategories.locales.ru, + value: 'Бутерброд', + }, + ], + image: images.productCategories.burger, + }, + sushi: { + name: [ + { + locale: FakeDataProductsCategories.locales.en, + value: 'Sushi', + }, + { + locale: FakeDataProductsCategories.locales.bg, + value: 'Суши', + }, + { + locale: FakeDataProductsCategories.locales.he, + value: 'סושי', + }, + { + locale: FakeDataProductsCategories.locales.ru, + value: 'Суши', + }, + ], + image: images.productCategories.sushi, + }, + pasta: { + name: [ + { + locale: FakeDataProductsCategories.locales.en, + value: 'Pasta', + }, + { + locale: FakeDataProductsCategories.locales.bg, + value: 'Тестени Изделия', + }, + { + locale: FakeDataProductsCategories.locales.he, + value: 'פיצה קטגוריות', + }, + { + locale: FakeDataProductsCategories.locales.ru, + value: 'Макаронные Изделия', + }, + ], + image: images.productCategories.pasta, + }, + vegetarian: { + name: [ + { + locale: FakeDataProductsCategories.locales.en, + value: 'Vegetarian', + }, + { + locale: FakeDataProductsCategories.locales.bg, + value: 'Вегетариански', + }, + { + locale: FakeDataProductsCategories.locales.he, + value: 'צמחוני', + }, + { + locale: FakeDataProductsCategories.locales.ru, + value: 'Вегетарианец', + }, + ], + image: images.productCategories.vegetarian, + }, + }; + } +} diff --git a/packages/admin-web-angular/src/app/@core/data/fakeDataServices/storageService.ts b/packages/admin-web-angular/src/app/@core/data/fakeDataServices/storageService.ts new file mode 100644 index 0000000..b19b9ea --- /dev/null +++ b/packages/admin-web-angular/src/app/@core/data/fakeDataServices/storageService.ts @@ -0,0 +1,37 @@ +import { StorageService } from '@modules/server.common/StorageService'; +import Order from '@modules/server.common/entities/Order'; +import GeoLocation from '@modules/server.common/entities/GeoLocation'; +import User from '@modules/server.common/entities/User'; +import { generateObjectIdString } from '@modules/server.common/utils'; +import { environment } from 'environments/environment'; +import { Injectable } from '@angular/core'; + +const lng = environment['DEFAULT_LONGITUDE']; +const lat = environment['DEFAULT_LATITUDE']; + +@Injectable() +export class AdminStorageService extends StorageService { + isConnected: boolean = false; + + order: Order | null = null; + + user: User | null = null; + + customerGeoLocation: GeoLocation = + lng && lat + ? new GeoLocation({ + _id: generateObjectIdString(), + _createdAt: new Date().toString(), + _updatedAt: new Date().toString(), + city: 'אשדוד', + postcode: '77452', + streetAddress: 'העצמאות', + house: '38', + countryId: 1, + loc: { + type: 'Point', + coordinates: [lng, lat], + }, + }) + : null; +} diff --git a/packages/admin-web-angular/src/app/@core/data/fakeDataServices/users.ts b/packages/admin-web-angular/src/app/@core/data/fakeDataServices/users.ts new file mode 100644 index 0000000..9eb00fd --- /dev/null +++ b/packages/admin-web-angular/src/app/@core/data/fakeDataServices/users.ts @@ -0,0 +1,49 @@ +import { Injectable } from '@angular/core'; +import Invite from '@modules/server.common/entities/Invite'; +import IEnterByCode from '@modules/server.common/interfaces/IEnterByCode'; +import { Country } from '@modules/server.common/entities/GeoLocation'; +import * as faker from 'faker'; +import { IUserRegistrationInput } from '@modules/server.common/routers/IUserAuthRouter'; +import { environment } from '../../../../environments/environment'; + +@Injectable() +export default class FakeDataUsers { + getUserRegistrationInput(): IUserRegistrationInput { + return { + user: { + firstName: faker.name.firstName(), + lastName: faker.name.lastName(), + email: faker.internet.email(), + phone: faker.phone.phoneNumber(), + image: faker.image.avatar(), + apartment: faker.datatype.number(199).toString(), + geoLocation: { + countryId: faker.datatype.number(200) as Country, + city: faker.address.city(), + postcode: faker.address.zipCode(), + notes: faker.lorem.text(1), + streetAddress: faker.address.streetAddress(), + house: faker.datatype.number(199).toString(), + loc: { + type: 'Point', + coordinates: [ + environment.DEFAULT_LONGITUDE, + environment.DEFAULT_LATITUDE, + ], + }, + }, + isBanned: Math.random() < 0.01, + }, + password: '123456', + }; + } + + getEnterByCodeToken1(invite: Invite): IEnterByCode { + return { + location: invite.geoLocation.loc, + inviteCode: invite.code, + firstName: faker.name.firstName(), + lastName: faker.name.lastName(), + }; + } +} diff --git a/packages/admin-web-angular/src/app/@core/data/fakeDataServices/warehouses.ts b/packages/admin-web-angular/src/app/@core/data/fakeDataServices/warehouses.ts new file mode 100644 index 0000000..26a91ba --- /dev/null +++ b/packages/admin-web-angular/src/app/@core/data/fakeDataServices/warehouses.ts @@ -0,0 +1,268 @@ +import { Injectable } from '@angular/core'; +import { IGeoLocationCreateObject } from '@modules/server.common/interfaces/IGeoLocation'; +import { Country } from '@modules/server.common/entities/GeoLocation'; +import ForwardOrdersMethod from '@modules/server.common/enums/ForwardOrdersMethod'; +import { IWarehouseRegistrationInput } from '@modules/server.common/routers/IWarehouseRouter'; +import { getFakeImg } from '@modules/server.common/utils'; +import { environment } from 'environments/environment'; +import * as faker from 'faker'; +import { random } from 'underscore'; +// import { _appIdRandomProviderFactory } from '@angular/core/src/application_tokens'; + +const NEED_DEFAULT_SETTINGS_MESSAGE = + "Can't generate fake data without DEFAULT_LONGITUDE and DEFAULT_LATITUDE"; +const lng = environment['DEFAULT_LONGITUDE']; +const lat = environment['DEFAULT_LATITUDE']; + +@Injectable() +export default class FakeDataWarehouses { + readonly registrationInputs: Readonly<{ + generate(): IWarehouseRegistrationInput; + pizzaRestaurant: IWarehouseRegistrationInput; + pizzaHit: IWarehouseRegistrationInput; + pizzaTroya: IWarehouseRegistrationInput; + dominexPizza: IWarehouseRegistrationInput; + }> = + lng && lat + ? { + generate: () => { + const warehouseName = faker.company.companyName(); + + return { + warehouse: { + name: `Restaurant ${warehouseName}`, + isActive: true, + username: faker.internet.userName(), + logo: getFakeImg(200, 200, 75, warehouseName), + contactEmail: faker.internet.email(), + contactPhone: faker.phone.phoneNumber(), + ordersEmail: null, + ordersPhone: null, + forwardOrdersUsing: [ + ForwardOrdersMethod.Unselected, + ], + isManufacturing: true, + isCarrierRequired: true, + usedCarriersIds: [], + geoLocation: { + city: faker.address.city(), + postcode: faker.address.zipCode(), + streetAddress: faker.address.streetAddress(), + house: faker.datatype.number(199).toString(), + countryId: faker.datatype.number( + 1 + ) as Country, + loc: { + type: 'Point', + coordinates: [lng, lat], + }, + }, + _createdAt: this._getRandomDateRange(), + }, + password: '123456', + }; + }, + + pizzaRestaurant: { + warehouse: { + name: 'Pizza Dan', + isActive: true, + username: 'restaurant_pizza', + logo: getFakeImg(200, 200, 75, 'Pizza Dan'), + contactEmail: faker.internet.email(), + contactPhone: faker.phone.phoneNumber(), + forwardOrdersUsing: [ + ForwardOrdersMethod.Unselected, + ], + ordersEmail: null, + ordersPhone: null, + isManufacturing: true, + isCarrierRequired: true, + hasRestrictedCarriers: false, + usedCarriersIds: [], + products: [], + geoLocation: { + city: 'Ashdod', + postcode: '77452', + streetAddress: 'HaAtsmaut', + house: '125', + countryId: Country.IL, + loc: { + type: 'Point', + coordinates: [lng + 0.05, lat + 0.09], + }, + }, + _createdAt: this._getRandomDateRange(), + }, + password: '123456', + } as any, + + pizzaHit: { + warehouse: { + name: 'Pizza Hit', + isActive: true, + username: 'hut_pizza', + logo: getFakeImg(200, 200, 75, 'Pizza Hit'), + contactEmail: faker.internet.email(), + contactPhone: faker.phone.phoneNumber(), + forwardOrdersUsing: [ + ForwardOrdersMethod.Unselected, + ], + ordersEmail: null, + ordersPhone: null, + isManufacturing: true, + isCarrierRequired: true, + hasRestrictedCarriers: false, + usedCarriersIds: [], + products: [], + geoLocation: { + city: 'Ashdod', + postcode: '77452', + streetAddress: 'HaAtsmaut', + house: '125', + countryId: Country.IL, + loc: { + type: 'Point', + coordinates: [lng - 0.05, lat - 0.09], + }, + }, + _createdAt: this._getRandomDateRange(), + }, + password: '123456', + } as any, + + pizzaTroya: { + warehouse: { + name: 'Pizza Troya', + isActive: true, + username: 'trova_pizza', + logo: getFakeImg(200, 200, 75, 'Pizza Troya'), + contactEmail: faker.internet.email(), + contactPhone: faker.phone.phoneNumber(), + forwardOrdersUsing: [ + ForwardOrdersMethod.Unselected, + ], + ordersEmail: null, + ordersPhone: null, + isManufacturing: false, + isCarrierRequired: false, + hasRestrictedCarriers: false, + usedCarriersIds: [], + products: [], + geoLocation: { + city: 'Ashdod', + postcode: '77452', + streetAddress: 'HaAtsmaut', + house: '128', + countryId: Country.IL, + loc: { + type: 'Point', + coordinates: [lng + 0.08, lat + 0.07], + }, + }, + _createdAt: this._getRandomDateRange(), + } as any, + password: '123456', + }, + + dominexPizza: { + warehouse: { + name: 'Dominex Pizza', + isActive: true, + username: 'dominex_pizza', + logo: getFakeImg(200, 200, 75, 'Pizza Pizza'), + contactEmail: faker.internet.email(), + contactPhone: faker.phone.phoneNumber(), + forwardOrdersUsing: [ + ForwardOrdersMethod.Unselected, + ], + ordersEmail: null, + ordersPhone: null, + isManufacturing: true, + isCarrierRequired: true, + hasRestrictedCarriers: false, + usedCarriersIds: [], + products: [], + geoLocation: { + city: 'Ashdod', + postcode: '77452', + streetAddress: 'HaAtsmaut', + house: '125', + countryId: Country.IL, + loc: { + type: 'Point', + coordinates: [lng - 0.08, lat - 0.07], + }, + }, + _createdAt: this._getRandomDateRange(), + } as any, + password: '123456', + }, + } + : null; + + getHardcodedGeoLocation(): IGeoLocationCreateObject { + if (lng && lat) { + return { + city: 'Ashdod', + postcode: '77452', + streetAddress: 'HaAtsmaut', + house: '38', + countryId: Country.IL, + loc: { + type: 'Point', + coordinates: [lng, lat], + }, + }; + } else { + console.warn(NEED_DEFAULT_SETTINGS_MESSAGE); + return; + } + } + + getNewGeoLocation1(): IGeoLocationCreateObject { + if (lng && lat) { + return { + city: faker.address.city(), + postcode: faker.address.zipCode(), + streetAddress: faker.address.streetAddress(), + house: faker.datatype.number(199).toString(), + countryId: faker.datatype.number(1) as Country, + loc: { + type: 'Point', + coordinates: [lng + 0.05, lat - 0.08], + }, + }; + } else { + console.warn(NEED_DEFAULT_SETTINGS_MESSAGE); + return; + } + } + + private _getRandomDateRange(yearsRange: number = 6) { + const now = new Date(); + const currentYear = now.getFullYear(); + const startYear = currentYear - yearsRange; + + const storeYear = random(startYear, currentYear); + const storeMonth = random(11); + const storeDate = random(31); + const storeHours = random(23); + const storeMinutes = random(59); + + const storeCreatedAt = new Date( + storeYear, + storeMonth, + storeDate, + storeHours, + storeMinutes + ); + + if (storeCreatedAt > now) { + const diff = storeCreatedAt.getTime() - now.getTime(); + storeCreatedAt.setTime(now.getTime() - random(diff)); + } + + return storeCreatedAt; + } +} diff --git a/packages/admin-web-angular/src/app/@core/data/fakeDataServices/warehousesProducts.ts b/packages/admin-web-angular/src/app/@core/data/fakeDataServices/warehousesProducts.ts new file mode 100644 index 0000000..b154e3c --- /dev/null +++ b/packages/admin-web-angular/src/app/@core/data/fakeDataServices/warehousesProducts.ts @@ -0,0 +1,106 @@ +import { Injectable } from '@angular/core'; +import { IWarehouseProductCreateObject } from '@modules/server.common/interfaces/IWarehouseProduct'; +import * as faker from 'faker'; +import { random } from 'underscore'; + +enum IsDeliveryTakeawayStatus { + Takeaway = 0, + IsDeliveryRequired = 1, + Both = 2, +} + +interface IIsDeliveryTakeawayStates { + 0: IIsDeliveryTakeaway; + 1: IIsDeliveryTakeaway; + 2: IIsDeliveryTakeaway; +} + +interface IIsDeliveryTakeaway { + isDeliveryRequired: boolean; + isTakeAway: boolean; +} + +@Injectable() +export default class FakeDataWarehousesProducts { + private _currentTakeawayDeliveryStatus: IsDeliveryTakeawayStatus = + IsDeliveryTakeawayStatus.Takeaway; + + private _takeawayDeliveryOrBoth: IIsDeliveryTakeawayStates = { + 0: { isDeliveryRequired: false, isTakeAway: true }, + 1: { isDeliveryRequired: true, isTakeAway: false }, + 2: { isDeliveryRequired: true, isTakeAway: true }, + }; + + getCreateObject(productId: string): IWarehouseProductCreateObject { + const currentTakeawayDeliveryState: IIsDeliveryTakeaway = this._getCurrentWarehouseProductIsDeliveryTakeawayState(); + const price = this.getRandomPrice; + + return { + product: productId, + initialPrice: price, + price, + isCarrierRequired: true, + isManufacturing: true, + count: faker.datatype.number({ min: 1, max: 10 }), + isDeliveryRequired: currentTakeawayDeliveryState.isDeliveryRequired, + isTakeaway: currentTakeawayDeliveryState.isTakeAway, + }; + } + + getHardcodedCreateObject(productIds: string[]) { + const productCreateObjects: IWarehouseProductCreateObject[] = productIds.map( + (id) => { + const currentTakeawayDeliveryState: IIsDeliveryTakeaway = this._getCurrentWarehouseProductIsDeliveryTakeawayState(); + const price = this.getRandomPrice; + + return { + product: id, + initialPrice: price + random(20), + price, + isCarrierRequired: true, + isManufacturing: true, + count: 5, + isDeliveryRequired: + currentTakeawayDeliveryState.isDeliveryRequired, + isTakeaway: currentTakeawayDeliveryState.isTakeAway, + }; + } + ); + + return productCreateObjects; + } + + private get getRandomPrice(): number { + return 5 + faker.datatype.number(150); + } + + private _setNextWarehouseProductIsDeliveryTakeawayStatus() { + let result: IsDeliveryTakeawayStatus; + + switch (this._currentTakeawayDeliveryStatus) { + case IsDeliveryTakeawayStatus.Takeaway: + result = IsDeliveryTakeawayStatus.IsDeliveryRequired; + break; + + case IsDeliveryTakeawayStatus.IsDeliveryRequired: + result = IsDeliveryTakeawayStatus.Both; + break; + + case IsDeliveryTakeawayStatus.Both: + result = IsDeliveryTakeawayStatus.Takeaway; + break; + } + + this._currentTakeawayDeliveryStatus = result; + } + + private _getCurrentWarehouseProductIsDeliveryTakeawayState(): IIsDeliveryTakeaway { + const currentTakeawayDeliveryState = this._takeawayDeliveryOrBoth[ + this._currentTakeawayDeliveryStatus + ]; + + this._setNextWarehouseProductIsDeliveryTakeawayStatus(); + + return currentTakeawayDeliveryState; + } +} diff --git a/packages/admin-web-angular/src/app/@core/data/geo-location-orders.service.ts b/packages/admin-web-angular/src/app/@core/data/geo-location-orders.service.ts new file mode 100644 index 0000000..466a767 --- /dev/null +++ b/packages/admin-web-angular/src/app/@core/data/geo-location-orders.service.ts @@ -0,0 +1,102 @@ +import { Injectable } from '@angular/core'; +import { Apollo, gql } from 'apollo-angular'; +import Order from '@modules/server.common/entities/Order'; +import { map, share } from 'rxjs/operators'; +import IGeoLocation from '@modules/server.common/interfaces/IGeoLocation'; +import { Observable } from 'rxjs'; + +@Injectable() +export class GeoLocationOrdersService { + constructor(private readonly apollo: Apollo) {} + + getOrdersForWork( + geoLocation: IGeoLocation, + skippedOrderIds: string[] = [], + options: { sort?: string; skip?: number; limit?: number } = { + sort: 'asc', + }, + searchObj?: { byRegex: Array<{ key: string; value: string }> } + ): Observable { + return this.apollo + .watchQuery<{ getOrdersForWork: Order[] }>({ + query: gql` + query GetOrdersForWork( + $geoLocation: GeoLocationFindInput! + $skippedOrderIds: [String!]! + $options: GeoLocationOrdersOptions + $searchObj: SearchOrdersForWork + ) { + getOrdersForWork( + geoLocation: $geoLocation + skippedOrderIds: $skippedOrderIds + options: $options + searchObj: $searchObj + ) { + id + carrierStatus + carrierStatusText + warehouseStatusText + createdAt + user { + id + firstName + lastName + image + geoLocation { + streetAddress + house + postcode + countryName + city + } + } + warehouse { + id + name + logo + geoLocation { + house + postcode + countryName + city + } + } + } + } + `, + variables: { geoLocation, skippedOrderIds, options, searchObj }, + pollInterval: 1000, + }) + .valueChanges.pipe( + map((res) => res.data.getOrdersForWork), + share() + ); + } + + async getCountOfOrdersForWork( + geoLocation: IGeoLocation, + skippedOrderIds: string[] = [], + searchObj?: { byRegex: Array<{ key: string; value: string }> } + ) { + const res = await this.apollo + .query({ + query: gql` + query GetCountOfOrdersForWork( + $geoLocation: GeoLocationFindInput! + $skippedOrderIds: [String!]! + $searchObj: SearchOrdersForWork + ) { + getCountOfOrdersForWork( + geoLocation: $geoLocation + skippedOrderIds: $skippedOrderIds + searchObj: $searchObj + ) + } + `, + variables: { geoLocation, skippedOrderIds, searchObj }, + }) + .toPromise(); + + return res.data['getCountOfOrdersForWork']; + } +} diff --git a/packages/admin-web-angular/src/app/@core/data/geo-location.service.ts b/packages/admin-web-angular/src/app/@core/data/geo-location.service.ts new file mode 100644 index 0000000..8037328 --- /dev/null +++ b/packages/admin-web-angular/src/app/@core/data/geo-location.service.ts @@ -0,0 +1,82 @@ +import { Injectable } from '@angular/core'; +import GeoLocation from '@modules/server.common/entities/GeoLocation'; +import { Apollo, gql } from 'apollo-angular'; +import ProductInfo from '@modules/server.common/entities/ProductInfo'; +import { Observable } from 'rxjs'; +import { map, share } from 'rxjs/operators'; + +@Injectable() +export class GeoLocationService { + constructor(private readonly apollo: Apollo) {} + + getGeoLocationProducts( + geoLocation: GeoLocation + ): Observable { + return this.apollo + .watchQuery<{ geoLocationProducts: ProductInfo[] }>({ + query: gql` + query geoLocationProducts( + $geoLocation: GeoLocationFindInput! + ) { + geoLocationProducts(geoLocation: $geoLocation) { + distance + warehouseId + warehouseLogo + warehouseProduct { + price + initialPrice + count + isManufacturing + isCarrierRequired + isDeliveryRequired + isProductAvailable + deliveryTimeMin + deliveryTimeMax + product { + id + title { + locale + value + } + description { + locale + value + } + details { + locale + value + } + images { + locale + url + width + height + orientation + } + } + } + } + } + `, + variables: { + geoLocation: { + countryId: geoLocation.countryId, + city: geoLocation.city, + postcode: geoLocation.postcode, + streetAddress: geoLocation.streetAddress, + house: geoLocation.house, + loc: geoLocation.loc, + }, + }, + pollInterval: 2000, + }) + .valueChanges.pipe( + map((res) => + res.data.geoLocationProducts.filter(Boolean).filter( + (p) => p.warehouseProduct.isProductAvailable === true + ) + ), + share() + ); + } +} diff --git a/packages/admin-web-angular/src/app/@core/data/invites-requests.service.ts b/packages/admin-web-angular/src/app/@core/data/invites-requests.service.ts new file mode 100644 index 0000000..77f1d9c --- /dev/null +++ b/packages/admin-web-angular/src/app/@core/data/invites-requests.service.ts @@ -0,0 +1,264 @@ +import { Injectable } from '@angular/core'; +import { Observable } from 'rxjs'; +import InviteRequest from '@modules/server.common/entities/InviteRequest'; +import { Apollo, gql } from 'apollo-angular'; +import { map, share } from 'rxjs/operators'; +import { + IInviteRequestCreateObject, + IInviteRequestUpdateObject, +} from '@modules/server.common/interfaces/IInviteRequest'; +import { getCountryName } from '@modules/server.common/entities/GeoLocation'; +import { IGeoLocationCreateObject } from '@modules/server.common/interfaces/IGeoLocation'; +import { InviteRequestViewModel } from '../../pages/+customers/+invites/+invites-requests/invites-requests.component'; +import { countries } from '@modules/server.common/data/abbreviation-to-country'; +import IPagingOptions from '@modules/server.common/interfaces/IPagingOptions'; + +interface RemovedObject { + n: number; + ok: number; +} + +@Injectable() +export class InvitesRequestsService { + constructor(private readonly _apollo: Apollo) {} + + private invitesRequests$: Observable = this._apollo + .watchQuery<{ invitesRequests: InviteRequest[] }>({ + query: gql` + query allInvitesRequests { + invitesRequests { + id + geoLocation { + city + streetAddress + house + countryId + loc { + coordinates + type + } + } + isInvited + invitedDate + apartment + } + } + `, + pollInterval: 2000, + }) + .valueChanges.pipe( + map((res) => res.data.invitesRequests), + share() + ); + + getAllInvitesRequests(): Observable { + return this.invitesRequests$; + } + + getInvitesRequests( + pagingOptions?: IPagingOptions, + invited?: boolean + ): Observable { + return this._apollo + .watchQuery<{ invitesRequests: InviteRequest[] }>({ + query: gql` + query AllInvitesRequests( + $pagingOptions: PagingOptionsInput + $invited: Boolean + ) { + invitesRequests( + pagingOptions: $pagingOptions + invited: $invited + ) { + id + geoLocation { + city + streetAddress + house + countryId + loc { + coordinates + type + } + } + isInvited + invitedDate + apartment + } + } + `, + variables: { pagingOptions, invited }, + pollInterval: 2000, + }) + .valueChanges.pipe( + map((res) => res.data.invitesRequests), + share() + ); + } + + createInviteRequest( + createInput: IInviteRequestCreateObject + ): Observable { + return this._apollo + .mutate<{ createInput: IInviteRequestCreateObject }>({ + mutation: gql` + mutation CreateInviteRequest( + $createInput: InviteRequestCreateInput! + ) { + createInviteRequest(createInput: $createInput) { + id + } + } + `, + variables: { + createInput, + }, + }) + .pipe( + map((result: any) => result.data.createInviteRequest), + share() + ); + } + + removeByIds(ids: string[]): Observable { + return this._apollo + .mutate({ + mutation: gql` + mutation RemoveInvitesRequestsByIds($ids: [String!]!) { + removeInvitesRequestsByIds(ids: $ids) { + n + } + } + `, + variables: { ids }, + }) + .pipe( + map((result: any) => result.data.removeInvitesRequestsByIds), + share() + ); + } + + updateInviteRequest( + id: string, + updateInput: IInviteRequestUpdateObject + ): Observable { + return this._apollo + .mutate<{ id: string; updateInput: IInviteRequestUpdateObject }>({ + mutation: gql` + mutation UpdateInviteRequest( + $id: String! + $updateInput: InviteRequestUpdateInput! + ) { + updateInviteRequest( + id: $id + updateInput: $updateInput + ) { + id + } + } + `, + variables: { + id, + updateInput, + }, + }) + .pipe( + map((result: any) => result.data.updateInviteRequest), + share() + ); + } + + async getCreateInviteRequestObject(data: InviteRequestViewModel) { + const res = await this._tryFindNewAddress( + data.house, + data.address, + data.city, + Object.values(countries).indexOf(data.country) + ); + + const lat = Number(res['lat']).toFixed(7); + const lng = Number(res['lng']).toFixed(7); + + const geoLocation: IGeoLocationCreateObject = { + countryId: Object.values(countries).indexOf(data.country), + city: data.city, + streetAddress: data.address, + house: data.house, + loc: { + coordinates: [Number(lng), Number(lat)], + type: 'Point', + }, + }; + + const inviteRequest: IInviteRequestCreateObject = { + apartment: data.apartment, + isManual: true, + geoLocation, + }; + + return inviteRequest; + } + + generate1000InviteRequests(defaultLng: number, defaultLat: number): any { + return this._apollo.query({ + query: gql` + query Generate1000InviteRequests( + $defaultLng: Float! + $defaultLat: Float! + ) { + generate1000InviteRequests( + defaultLng: $defaultLng + defaultLat: $defaultLat + ) + } + `, + variables: { defaultLng, defaultLat }, + }); + } + + async getCountOfInvitesRequests(invited?: boolean) { + const res = await this._apollo + .query({ + query: gql` + query GetCountOfInvitesRequests($invited: Boolean) { + getCountOfInvitesRequests(invited: $invited) + } + `, + variables: { invited }, + }) + .toPromise(); + + return res.data['getCountOfInvitesRequests']; + } + + private _tryFindNewAddress( + house: string, + streetAddress: string, + city: string, + countryId: number + ) { + const countryName = getCountryName(countryId); + + const geocoder = new google.maps.Geocoder(); + + return new Promise((resolve, reject) => { + geocoder.geocode( + { + address: `${streetAddress} ${house}, ${city}`, + componentRestrictions: { + country: countryName, + }, + }, + (results, status) => { + if (status === google.maps.GeocoderStatus.OK) { + const place: google.maps.GeocoderResult = results[0]; + + resolve(place.geometry.location.toJSON()); + } else { + resolve({ lat: 0, lng: 0 }); + } + } + ); + }); + } +} diff --git a/packages/admin-web-angular/src/app/@core/data/invites.service.ts b/packages/admin-web-angular/src/app/@core/data/invites.service.ts new file mode 100644 index 0000000..b08729b --- /dev/null +++ b/packages/admin-web-angular/src/app/@core/data/invites.service.ts @@ -0,0 +1,248 @@ +import { Apollo, gql } from 'apollo-angular'; +import { Injectable } from '@angular/core'; +import { Observable } from 'rxjs'; +import Invite from '@modules/server.common/entities/Invite'; +import { map, share } from 'rxjs/operators'; +import { + IInviteUpdateObject, + IInviteCreateObject, +} from '@modules/server.common/interfaces/IInvite'; +import { InviteViewModel } from '../../pages/+customers/+invites/invites.component'; +import { getCountryName } from '@modules/server.common/entities/GeoLocation'; +import { IGeoLocationCreateObject } from '@modules/server.common/interfaces/IGeoLocation'; +import { countries } from '@modules/server.common/data/abbreviation-to-country'; +import IPagingOptions from '@modules/server.common/interfaces/IPagingOptions'; + +interface RemovedObject { + n: number; + ok: number; +} + +@Injectable() +export class InvitesService { + constructor(private readonly apollo: Apollo) {} + + private invites$: Observable = this.apollo + .watchQuery<{ invites: Invite[] }>({ + query: gql` + query allInvites { + invites { + id + code + geoLocation { + city + streetAddress + house + countryId + loc { + coordinates + type + } + } + apartment + } + } + `, + pollInterval: 2000, + }) + .valueChanges.pipe( + map((res) => res.data.invites), + share() + ); + + getAllInvitesRequests(): Observable { + return this.invites$; + } + + getInvites(pagingOptions?: IPagingOptions): Observable { + return this.apollo + .watchQuery<{ invites: Invite[] }>({ + query: gql` + query AllInvites($pagingOptions: PagingOptionsInput) { + invites(pagingOptions: $pagingOptions) { + id + code + geoLocation { + city + streetAddress + house + countryId + loc { + coordinates + type + } + } + apartment + } + } + `, + variables: { pagingOptions }, + pollInterval: 2000, + }) + .valueChanges.pipe( + map((res) => res.data.invites), + share() + ); + } + + createInvite(createInput: IInviteCreateObject): Observable { + return this.apollo + .mutate<{ createInput: IInviteCreateObject }>({ + mutation: gql` + mutation CreateInvite($createInput: InviteCreateInput!) { + createInvite(createInput: $createInput) { + id + } + } + `, + variables: { + createInput, + }, + }) + .pipe( + map((result: any) => result.data.createInvite), + share() + ); + } + + removeByIds(ids: string[]): Observable { + return this.apollo + .mutate({ + mutation: gql` + mutation RemoveInvitesByIds($ids: [String!]!) { + removeInvitesByIds(ids: $ids) { + n + } + } + `, + variables: { ids }, + }) + .pipe( + map((result: any) => result.data.removeInvitesByIds), + share() + ); + } + + updateInvite( + id: string, + updateInput: IInviteUpdateObject + ): Observable { + return this.apollo + .mutate<{ id: string; updateInput: IInviteUpdateObject }>({ + mutation: gql` + mutation UpdateInvite( + $id: String! + $updateInput: InviteUpdateInput! + ) { + updateInvite(id: $id, updateInput: $updateInput) { + id + } + } + `, + variables: { + id, + updateInput, + }, + }) + .pipe( + map((result: any) => result.data.updateInvite), + share() + ); + } + + async getCreateInviteObject(data: InviteViewModel) { + const res = await this._tryFindNewAddress( + data.house, + data.address, + data.city, + Object.values(countries).indexOf(data.country) + ); + + const lat = Number(res['lat']).toFixed(7); + const lng = Number(res['lng']).toFixed(7); + + const geoLocation: IGeoLocationCreateObject = { + countryId: Object.values(countries).indexOf(data.country), + city: data.city, + streetAddress: data.address, + house: data.house, + loc: { + coordinates: [Number(lng), Number(lat)], + type: 'Point', + }, + }; + + const invite: IInviteCreateObject = { + code: data.invite, + apartment: data.apartment, + geoLocation, + }; + + return invite; + } + + async getCountOfInvites() { + const res = await this.apollo + .query({ + query: gql` + query GetCountOfInvites { + getCountOfInvites + } + `, + }) + .toPromise(); + + return res.data['getCountOfInvites']; + } + + generate1000InvitesConnectedToInviteRequests( + defaultLng: number, + defaultLat: number + ) { + return this.apollo.query({ + query: gql` + query Generate1000InvitesConnectedToInviteRequests( + $defaultLng: Float! + $defaultLat: Float! + ) { + generate1000InvitesConnectedToInviteRequests( + defaultLng: $defaultLng + defaultLat: $defaultLat + ) + } + `, + variables: { defaultLng, defaultLat }, + }); + } + + private _tryFindNewAddress( + house: string, + streetAddress: string, + city: string, + countryId: number + ) { + const countryName = getCountryName(countryId); + + const geocoder = new google.maps.Geocoder(); + + return new Promise((resolve, reject) => { + geocoder.geocode( + { + address: `${streetAddress} ${house}, ${city}`, + componentRestrictions: { + country: countryName, + }, + }, + (results, status) => { + if (status === google.maps.GeocoderStatus.OK) { + const place: google.maps.GeocoderResult = results[0]; + + resolve(place.geometry.location.toJSON()); + } else { + resolve({ lat: 0, lng: 0 }); + } + } + ); + }); + } +} diff --git a/packages/admin-web-angular/src/app/@core/data/orders.service.ts b/packages/admin-web-angular/src/app/@core/data/orders.service.ts new file mode 100644 index 0000000..1f47752 --- /dev/null +++ b/packages/admin-web-angular/src/app/@core/data/orders.service.ts @@ -0,0 +1,290 @@ +import { Injectable } from '@angular/core'; +import { Apollo, gql } from 'apollo-angular'; +import Order from '@modules/server.common/entities/Order'; +import { map, share } from 'rxjs/operators'; +import { Observable } from 'rxjs'; + +@Injectable() +export class OrdersService { + constructor(private readonly _apollo: Apollo) {} + + generatePastOrdersPerCarrier() { + return this._apollo.query({ + query: gql` + query GeneratePastOrdersPerCarrier { + generatePastOrdersPerCarrier + } + `, + }); + } + + generateActiveAndAvailableOrdersPerCarrier() { + return this._apollo.query({ + query: gql` + query GenerateActiveAndAvailableOrdersPerCarrier { + generateActiveAndAvailableOrdersPerCarrier + } + `, + }); + } + + addOrdersToTake(): Observable { + return this._apollo.query({ + query: gql` + query AddOrdersToTake { + addOrdersToTake + } + `, + }); + } + + addTakenOrders(carrierIds: string[]): Observable { + return this._apollo.query({ + query: gql` + query AddTakenOrders($carrierIds: [String!]!) { + addTakenOrders(carrierIds: $carrierIds) + } + `, + variables: { carrierIds }, + }); + } + + generateRandomOrdersCurrentStore( + storeId: string, + storeCreatedAt: Date, + ordersLimit: number + ): Observable<{ error: boolean; message: string }> { + return this._apollo + .query<{ + generateRandomOrdersCurrentStore: { + error: boolean; + message: string; + }; + }>({ + query: gql` + query GenerateRandomOrdersCurrentStore( + $storeId: String! + $storeCreatedAt: Date! + $ordersLimit: Int! + ) { + generateRandomOrdersCurrentStore( + storeId: $storeId + storeCreatedAt: $storeCreatedAt + ordersLimit: $ordersLimit + ) { + error + message + } + } + `, + variables: { storeId, storeCreatedAt, ordersLimit }, + }) + .pipe(map((res) => res.data.generateRandomOrdersCurrentStore)); + } + + getOrdersChartTotalOrders(): Observable { + return this._apollo + .watchQuery<{ getOrdersChartTotalOrders: Order[] }>({ + // no needed + // isCompleted + query: gql` + query GetOrdersChartTotalOrders { + getOrdersChartTotalOrders { + isCancelled + _createdAt + totalPrice + } + } + `, + }) + .valueChanges.pipe( + map((res) => res.data.getOrdersChartTotalOrders), + share() + ); + } + + async getOrdersChartTotalOrdersNew() { + const res = await this._apollo + .query<{ + getOrdersChartTotalOrders: Order[]; + }>({ + query: gql` + query GetOrdersChartTotalOrders { + getOrdersChartTotalOrders { + isCancelled + _createdAt + totalPrice + isCompleted + } + } + `, + }) + .toPromise(); + return res.data.getOrdersChartTotalOrders; + } + + getDashboardCompletedOrders(): Observable { + return this._apollo + .watchQuery<{ getDashboardCompletedOrders: Order[] }>({ + query: gql` + query GetDashboardCompletedOrders { + getDashboardCompletedOrders { + warehouseId + totalPrice + } + } + `, + }) + .valueChanges.pipe( + map((res) => res.data.getDashboardCompletedOrders), + share() + ); + } + + async getComplatedOrdersInfo(storeId?: string) { + const res = await this._apollo + .query({ + query: gql` + query GetComplatedOrdersInfo($storeId: String) { + getCompletedOrdersInfo(storeId: $storeId) { + totalOrders + totalRevenue + } + } + `, + variables: { storeId }, + }) + .toPromise(); + + return res.data['getCompletedOrdersInfo']; + } + + getDashboardCompletedOrdersToday(): Observable { + return this._apollo + .watchQuery<{ getDashboardCompletedOrdersToday: Order[] }>({ + query: gql` + query GetDashboardCompletedOrdersToday { + getDashboardCompletedOrdersToday { + user { + _id + } + warehouseId + totalPrice + isCompleted + } + } + `, + }) + .valueChanges.pipe( + map((res) => res.data.getDashboardCompletedOrdersToday), + share() + ); + } + + getOrders(): Observable { + return this._apollo + .watchQuery<{ orders: Order[] }>({ + query: gql` + query Orders { + orders { + user { + _id + } + warehouseId + totalPrice + isCompleted + } + } + `, + pollInterval: 4000, + }) + .valueChanges.pipe( + map((res) => res.data.orders), + share() + ); + } + + getOrderById(id: string) { + return this._apollo + .watchQuery({ + query: gql` + query GetOrderById($id: String!) { + getOrder(id: $id) { + id + warehouseId + carrierId + createdAt + orderNumber + } + } + `, + pollInterval: 4000, + variables: { id }, + }) + .valueChanges.pipe( + map((res) => res.data['getOrder']), + share() + ); + } + + async getUsersOrdersCountInfo(usersIds?: string[]) { + const res = await this._apollo + .query({ + query: gql` + query GetUsersOrdersCountInfo($usersIds: [String!]) { + getUsersOrdersCountInfo(usersIds: $usersIds) { + id + ordersCount + } + } + `, + variables: { usersIds }, + }) + .toPromise(); + + return res.data['getUsersOrdersCountInfo']; + } + + async getMerchantsOrdersCountInfo(merchantsIds?: string[]) { + const res = await this._apollo + .query({ + query: gql` + query GetMerchantsOrdersCountInfo( + $merchantsIds: [String!] + ) { + getMerchantsOrdersCountInfo( + merchantsIds: $merchantsIds + ) { + id + ordersCount + } + } + `, + variables: { merchantsIds }, + }) + .toPromise(); + + return res.data['getMerchantsOrdersCountInfo']; + } + + async getMerchantsOrders() { + const res = await this._apollo + .query({ + query: gql` + query getMerchantsOrders { + getMerchantsOrders { + _id + ordersCount + } + } + `, + }) + .toPromise(); + + return res.data['getMerchantsOrders']; + } + + protected _orderFactory(order: Order) { + return order == null ? null : new Order(order); + } +} diff --git a/packages/admin-web-angular/src/app/@core/data/products.service.ts b/packages/admin-web-angular/src/app/@core/data/products.service.ts new file mode 100644 index 0000000..5d6e427 --- /dev/null +++ b/packages/admin-web-angular/src/app/@core/data/products.service.ts @@ -0,0 +1,197 @@ +import { Injectable } from '@angular/core'; +import { Apollo, gql } from 'apollo-angular'; +import Product from '@modules/server.common/entities/Product'; +import { IProductCreateObject } from '@modules/server.common/interfaces/IProduct'; +import { Observable } from 'rxjs'; +import { map, share } from 'rxjs/operators'; +import IPagingOptions from '@modules/server.common/interfaces/IPagingOptions'; + +interface RemovedObject { + n: number; + ok: number; +} + +@Injectable() +export class ProductsService { + constructor(private readonly apollo: Apollo) {} + + getProducts( + pagingOptions?: IPagingOptions, + existedProductsIds: string[] = [] + ): Observable { + return this.apollo + .watchQuery<{ products: Product[] }>({ + query: gql` + query AllProducts( + $pagingOptions: PagingOptionsInput + $existedProductsIds: [String] + ) { + products( + pagingOptions: $pagingOptions + existedProductsIds: $existedProductsIds + ) { + id + title { + locale + value + } + description { + locale + value + } + details { + locale + value + } + images { + locale + url + width + height + orientation + } + categories + } + } + `, + variables: { pagingOptions, existedProductsIds }, + pollInterval: 2000, + }) + .valueChanges.pipe( + map((res) => res.data.products), + share() + ); + } + + create(product: IProductCreateObject): Observable { + return this.apollo + .mutate<{ product: IProductCreateObject }>({ + mutation: gql` + mutation SaveProduct($product: ProductCreateInput!) { + createProduct(product: $product) { + id + title { + locale + value + } + description { + locale + value + } + details { + locale + value + } + images { + locale + url + width + height + orientation + } + } + } + `, + variables: { + product, + }, + }) + .pipe( + map((result: any) => result.data.createProduct), + share() + ); + } + + save(product: Product) { + return this.apollo + .mutate<{ product: Product }>({ + mutation: gql` + mutation SaveProduct($product: ProductSaveInput!) { + saveProduct(product: $product) { + id + } + } + `, + variables: { + product, + }, + }) + .pipe( + map((result: any) => result.data.saveProduct), + share() + ); + } + + removeByIds(ids: string[]): Observable { + return this.apollo + .mutate({ + mutation: gql` + mutation RemoveProductsByIds($ids: [String!]!) { + removeProductsByIds(ids: $ids) { + n + } + } + `, + variables: { ids }, + }) + .pipe( + map((result: any) => result.data.removeProductsByIds), + share() + ); + } + + getProductById(id: string) { + return this.apollo + .query({ + query: gql` + query GetProductById($id: String!) { + product(id: $id) { + id + title { + locale + value + } + description { + locale + value + } + details { + locale + value + } + images { + locale + url + width + height + orientation + } + categories + } + } + `, + variables: { id }, + }) + .pipe( + map((res) => res.data['product']), + share() + ); + } + + async getCountOfProducts(existedProductsIds: string[] = []) { + const res = await this.apollo + .query({ + query: gql` + query GetCountOfProducts($existedProductsIds: [String]) { + getCountOfProducts( + existedProductsIds: $existedProductsIds + ) + } + `, + variables: { existedProductsIds }, + }) + .toPromise(); + + return res.data['getCountOfProducts']; + } +} diff --git a/packages/admin-web-angular/src/app/@core/data/productsCategory.service.ts b/packages/admin-web-angular/src/app/@core/data/productsCategory.service.ts new file mode 100644 index 0000000..05adb41 --- /dev/null +++ b/packages/admin-web-angular/src/app/@core/data/productsCategory.service.ts @@ -0,0 +1,135 @@ +import { Injectable } from '@angular/core'; +import { Apollo, gql } from 'apollo-angular'; +import { Observable } from 'rxjs'; +import { map, share } from 'rxjs/operators'; +import { IProductsCategoryCreateObject } from '@modules/server.common/interfaces/IProductsCategory'; +import ProductsCategory from '@modules/server.common/entities/ProductsCategory'; +import { getDummyImage } from '@modules/server.common/utils'; +import { ProductLocalesService } from '@modules/client.common.angular2/locale/product-locales.service'; + +@Injectable() +export class ProductsCategoryService { + constructor( + private readonly apollo: Apollo, + private readonly productLocalesService: ProductLocalesService + ) {} + + getCategories(): Observable { + return this.apollo + .watchQuery<{ productsCategories: ProductsCategory[] }>({ + query: gql` + query allCategories { + productsCategories { + id + image + name { + locale + value + } + } + } + `, + pollInterval: 1000, + }) + .valueChanges.pipe( + map((res) => res.data.productsCategories), + share() + ); + } + + create( + productsCategory: IProductsCategoryCreateObject + ): Observable { + this.getDefaultImage(productsCategory); + return this.apollo + .mutate<{ productsCategory: IProductsCategoryCreateObject }>({ + mutation: gql` + mutation SaveProductsCategory( + $productsCategory: ProductsCategoriesCreateInput! + ) { + createProductsCategory(createInput: $productsCategory) { + id + image + name { + locale + value + } + } + } + `, + variables: { + productsCategory, + }, + }) + .pipe( + map((result: any) => result.data.createProductsCategory), + share() + ); + } + + update( + id: string, + productsCategory: IProductsCategoryCreateObject + ): Observable { + this.getDefaultImage(productsCategory); + return this.apollo + .mutate<{ + id: string; + productsCategory: IProductsCategoryCreateObject; + }>({ + mutation: gql` + mutation UpdateProductsCategory( + $id: String! + $productsCategory: ProductsCategoriesCreateInput! + ) { + updateProductsCategory( + id: $id + updateInput: $productsCategory + ) { + id + image + name { + locale + value + } + } + } + `, + variables: { + id, + productsCategory, + }, + }) + .pipe( + map((result: any) => result.data.updateProductsCategory), + share() + ); + } + + removeByIds(ids: string[]) { + return this.apollo.mutate({ + mutation: gql` + mutation removeProductsCategoriesByIds($ids: [String!]!) { + removeProductsCategoriesByIds(ids: $ids) { + ok + n + } + } + `, + variables: { ids }, + }); + } + + private getDefaultImage(data: IProductsCategoryCreateObject) { + if (!data.image) { + data.image = getDummyImage( + 300, + 300, + this.productLocalesService + .getTranslate(data.name) + .charAt(0) + .toUpperCase() + ); + } + } +} diff --git a/packages/admin-web-angular/src/app/@core/data/smart-table.service.ts b/packages/admin-web-angular/src/app/@core/data/smart-table.service.ts new file mode 100644 index 0000000..27d9ac2 --- /dev/null +++ b/packages/admin-web-angular/src/app/@core/data/smart-table.service.ts @@ -0,0 +1,10 @@ +import { Injectable } from '@angular/core'; + +@Injectable() +export class SmartTableService { + data = []; + + getData() { + return this.data; + } +} diff --git a/packages/admin-web-angular/src/app/@core/data/state.service.ts b/packages/admin-web-angular/src/app/@core/data/state.service.ts new file mode 100644 index 0000000..6c06dcd --- /dev/null +++ b/packages/admin-web-angular/src/app/@core/data/state.service.ts @@ -0,0 +1,95 @@ +import { Injectable, OnDestroy } from '@angular/core'; +import { Observable, BehaviorSubject, of as observableOf } from 'rxjs'; +import { takeWhile } from 'rxjs/operators'; +import { NbLayoutDirection, NbLayoutDirectionService } from '@nebular/theme'; + +@Injectable() +export class StateService implements OnDestroy { + constructor(directionService: NbLayoutDirectionService) { + directionService + .onDirectionChange() + .pipe(takeWhile(() => this.alive)) + .subscribe((direction) => this.updateSidebarIcons(direction)); + + this.updateSidebarIcons(directionService.getDirection()); + } + + protected layouts: any = [ + { + name: 'One Column', + icon: 'nb-layout-default', + id: 'one-column', + selected: true, + }, + { + name: 'Two Column', + icon: 'nb-layout-two-column', + id: 'two-column', + }, + { + name: 'Center Column', + icon: 'nb-layout-centre', + id: 'center-column', + }, + ]; + + protected sidebars: any = [ + { + name: 'Sidebar at layout start', + icon: 'nb-layout-sidebar-left', + id: 'start', + selected: true, + }, + { + name: 'Sidebar at layout end', + icon: 'nb-layout-sidebar-right', + id: 'end', + }, + ]; + + protected layoutState$ = new BehaviorSubject(this.layouts[0]); + protected sidebarState$ = new BehaviorSubject(this.sidebars[0]); + + alive = true; + + ngOnDestroy() { + this.alive = false; + } + + setLayoutState(state: any): any { + this.layoutState$.next(state); + } + + getLayoutStates(): Observable { + return observableOf(this.layouts); + } + + onLayoutState(): Observable { + return this.layoutState$.asObservable(); + } + + setSidebarState(state: any): any { + this.sidebarState$.next(state); + } + + getSidebarStates(): Observable { + return observableOf(this.sidebars); + } + + onSidebarState(): Observable { + return this.sidebarState$.asObservable(); + } + + private updateSidebarIcons(direction: NbLayoutDirection) { + const [startSidebar, endSidebar] = this.sidebars; + const isLtr = direction === NbLayoutDirection.LTR; + const startIconClass = isLtr + ? 'nb-layout-sidebar-left' + : 'nb-layout-sidebar-right'; + const endIconClass = isLtr + ? 'nb-layout-sidebar-right' + : 'nb-layout-sidebar-left'; + startSidebar.icon = startIconClass; + endSidebar.icon = endIconClass; + } +} diff --git a/packages/admin-web-angular/src/app/@core/data/store.service.ts b/packages/admin-web-angular/src/app/@core/data/store.service.ts new file mode 100644 index 0000000..1082bed --- /dev/null +++ b/packages/admin-web-angular/src/app/@core/data/store.service.ts @@ -0,0 +1,78 @@ +import Admin from '@modules/server.common/entities/Admin'; +import User from '@modules/server.common/entities/User'; +import { Injectable } from '@angular/core'; + +@Injectable() +export class Store { + get token(): string | null { + return localStorage.getItem('token') || null; + } + + set token(token: string) { + if (token == null) { + localStorage.removeItem('token'); + } else { + localStorage.setItem('token', token); + } + } + + get adminId(): Admin['id'] | null { + return localStorage.getItem('_adminId') || null; + } + + set adminId(id: Admin['id'] | null) { + if (id == null) { + localStorage.removeItem('_adminId'); + } else { + localStorage.setItem('_adminId', id); + } + } + + get userId(): User['id'] | null { + return localStorage.getItem('_userId') || null; + } + + set userId(id: User['id'] | null) { + if (id == null) { + localStorage.removeItem('_userId'); + } else { + localStorage.setItem('_userId', id); + } + } + + get maintenanceMode(): string | null { + return localStorage.getItem('maintenanceMode') || null; + } + + get serverConnection() { + return localStorage.getItem('serverConnection'); + } + + set serverConnection(val: string) { + localStorage.setItem('serverConnection', val); + } + + get adminPasswordReset() { + return localStorage.getItem('adminPasswordReset'); + } + + set adminPasswordReset(val: string) { + localStorage.setItem('adminPasswordReset', val); + } + + get fakeDataGenerator() { + return localStorage.getItem('fakeDataGenerator'); + } + + set fakeDataGenerator(val: string) { + localStorage.setItem('fakeDataGenerator', val); + } + + clearMaintenanceMode() { + localStorage.removeItem('maintenanceMode'); + } + + clear() { + localStorage.clear(); + } +} diff --git a/packages/admin-web-angular/src/app/@core/data/users.service.ts b/packages/admin-web-angular/src/app/@core/data/users.service.ts new file mode 100644 index 0000000..4c5a79a --- /dev/null +++ b/packages/admin-web-angular/src/app/@core/data/users.service.ts @@ -0,0 +1,256 @@ +import { Injectable } from '@angular/core'; +import { Apollo, gql } from 'apollo-angular'; +import User from '@modules/server.common/entities/User'; +import { map, share } from 'rxjs/operators'; +import { Observable } from 'rxjs'; +import IUser, { + IResponseGenerate1000Customers, +} from '@modules/server.common/interfaces/IUser'; +import { IUserRegistrationInput } from '@modules/server.common/routers/IUserAuthRouter'; +import IPagingOptions from '@modules/server.common/interfaces/IPagingOptions'; + +@Injectable() +export class UsersService { + constructor(private readonly _apollo: Apollo) {} + + isUserEmailExists(email: string): Promise { + return this._apollo + .query<{ isUserEmailExists: boolean }>({ + query: gql` + query IsUserEmailExists($email: String!) { + isUserEmailExists(email: $email) + } + `, + variables: { email }, + }) + .pipe(map((res) => res.data.isUserEmailExists)) + .toPromise(); + } + + isUserExists(conditions: { + exceptCustomerId: string; + memberKey: string; + memberValue: string; + }): Observable { + return this._apollo + .query({ + query: gql` + query IsUserExists($conditions: UserMemberInput!) { + isUserExists(conditions: $conditions) + } + `, + variables: { conditions }, + }) + .pipe(map((res) => res.data['isUserExists'])); + } + + getUsers(pagingOptions?: IPagingOptions): Observable { + return this._apollo + .watchQuery<{ users: IUser[] }>({ + query: gql` + query AllUsers($pagingOptions: PagingOptionsInput) { + users(pagingOptions: $pagingOptions) { + _id + firstName + lastName + image + email + apartment + phone + isBanned + geoLocation { + countryId + city + house + streetAddress + loc { + type + coordinates + } + } + } + } + `, + variables: { pagingOptions }, + pollInterval: 5000, + }) + .valueChanges.pipe( + map((res) => res.data.users), + map((users) => users.map((user) => this._userFactory(user))), + share() + ); + } + + getUserById(id: string) { + return this._apollo + .query({ + query: gql` + query GetUserById($id: String!) { + user(id: $id) { + _id + firstName + lastName + image + email + apartment + phone + isBanned + geoLocation { + streetAddress + city + house + notes + loc { + type + coordinates + } + } + } + } + `, + variables: { id }, + }) + .pipe( + map((res) => res.data['user']), + map((user) => this._userFactory(user)), + share() + ); + } + + removeByIds(ids: string[]) { + return this._apollo.mutate({ + mutation: gql` + mutation RemoveUsersByIds($ids: [String!]!) { + removeUsersByIds(ids: $ids) + } + `, + variables: { ids }, + }); + } + + async registerUser(registerInput: IUserRegistrationInput) { + const res = await this._apollo + .mutate({ + mutation: gql` + mutation RegisterUser($registerInput: UserRegisterInput!) { + registerUser(registerInput: $registerInput) { + id + firstName + lastName + } + } + `, + variables: { registerInput }, + }) + .toPromise(); + + return res.data['registerUser']; + } + + async banUser(id: string) { + return this._apollo + .mutate({ + mutation: gql` + mutation BanUser($id: String!) { + banUser(id: $id) { + id + firstName + lastName + } + } + `, + variables: { id }, + }) + .toPromise(); + } + + async unbanUser(id: string) { + return this._apollo + .mutate({ + mutation: gql` + mutation UnbanUser($id: String!) { + unbanUser(id: $id) { + id + firstName + lastName + } + } + `, + variables: { id }, + }) + .toPromise(); + } + + async getCountOfUsers() { + const res = await this._apollo + .query({ + query: gql` + query GetCountOfUsers { + getCountOfUsers + } + `, + }) + .toPromise(); + + return res.data['getCountOfUsers']; + } + + async getCustomerMetrics( + id: string + ): Promise<{ + totalOrders: number; + canceledOrders: number; + completedOrdersTotalSum: number; + }> { + const res = await this._apollo + .query({ + query: gql` + query GetCustomerMetrics($id: String!) { + getCustomerMetrics(id: $id) { + totalOrders + canceledOrders + completedOrdersTotalSum + } + } + `, + variables: { id }, + }) + .toPromise(); + + return res.data['getCustomerMetrics']; + } + + // TODO: rename and add parameter Qty of fake Customers to generate + generate1000Customers( + defaultLng: number, + defaultLat: number + ): Observable { + return this._apollo + .query<{ generate1000Customers: IResponseGenerate1000Customers }>({ + query: gql` + query Generate1000Customers( + $defaultLng: Float! + $defaultLat: Float! + ) { + generate1000Customers( + defaultLng: $defaultLng + defaultLat: $defaultLat + ) { + success + message + } + } + `, + variables: { defaultLng, defaultLat }, + }) + .pipe( + map((res) => { + return res.data.generate1000Customers; + }) + ); + } + + protected _userFactory(user: IUser) { + return user == null ? null : new User(user); + } +} diff --git a/packages/admin-web-angular/src/app/@core/data/warehouseOrders.service.ts b/packages/admin-web-angular/src/app/@core/data/warehouseOrders.service.ts new file mode 100644 index 0000000..177b677 --- /dev/null +++ b/packages/admin-web-angular/src/app/@core/data/warehouseOrders.service.ts @@ -0,0 +1,196 @@ +import { Injectable } from '@angular/core'; +import { Apollo, gql } from 'apollo-angular'; +import { Observable } from 'rxjs'; +import { IOrderCreateInput } from '@modules/server.common/routers/IWarehouseOrdersRouter'; +import Order from '@modules/server.common/entities/Order'; +import { map } from 'rxjs/operators'; +import IPagingOptions from '@modules/server.common/interfaces/IPagingOptions'; + +@Injectable() +export class WarehouseOrdersService { + constructor(private readonly _apollo: Apollo) {} + + createOrder(createInput: IOrderCreateInput): Observable { + return this._apollo + .mutate({ + mutation: gql` + mutation MakeOrder($createInput: OrderCreateInput!) { + createOrder(createInput: $createInput) { + _id + _createdAt + _updatedAt + carrierStatus + isConfirmed + warehouseId + warehouseStatus + user { + _id + } + carrier { + _id + } + } + } + `, + variables: { createInput }, + }) + .pipe(map((result: any) => result.data.createOrder)); + } + + getDashboardOrdersChartOrders(storeId: string): Observable { + return this._apollo + .watchQuery<{ getDashboardOrdersChartOrders: Order[] }>({ + query: gql` + query GetDashboardOrdersChartOrders($storeId: String!) { + getDashboardOrdersChartOrders(storeId: $storeId) { + isCompleted + isCancelled + _createdAt + totalPrice + } + } + `, + variables: { storeId }, + }) + .valueChanges.pipe( + map((res) => res.data.getDashboardOrdersChartOrders) + ); + } + + getStoreOrders(storeId: string): Observable { + return this._apollo + .watchQuery<{ getStoreOrders: Order[] }>({ + query: gql` + query GetStoreOrders($storeId: String!) { + getStoreOrders(storeId: $storeId) { + id + isCompleted + products { + count + price + } + } + } + `, + pollInterval: 5000, + variables: { storeId }, + }) + .valueChanges.pipe(map((res) => res.data.getStoreOrders)); + } + + getStoreOrdersTableData( + storeId: string, + pagingOptions?: IPagingOptions, + status?: string + ): Observable<{ orders: Order[] }> { + return this._apollo + .watchQuery({ + query: gql` + query GetStoreOrdersTableData( + $storeId: String! + $pagingOptions: PagingOptionsInput + $status: String + ) { + getStoreOrdersTableData( + storeId: $storeId + pagingOptions: $pagingOptions + status: $status + ) { + orders { + id + carrierStatus + carrierStatusText + warehouseStatusText + createdAt + warehouseStatus + deliveryTime + status + isConfirmed + finishedProcessingTime + isCancelled + isPaid + orderType + orderNumber + _createdAt + user { + id + firstName + lastName + geoLocation { + streetAddress + house + postcode + countryName + city + } + } + warehouse { + id + name + geoLocation { + house + postcode + countryName + city + } + } + + products { + count + price + product { + id + title { + locale + value + } + description { + locale + value + } + details { + locale + value + } + images { + locale + url + width + height + orientation + } + } + } + } + } + } + `, + pollInterval: 5000, + variables: { storeId, pagingOptions, status }, + }) + .valueChanges.pipe( + map((res) => res.data['getStoreOrdersTableData']) + ); + } + + async getCountOfStoreOrders(storeId: string, status?: string) { + const res = await this._apollo + .query({ + query: gql` + query getCountOfStoreOrders( + $storeId: String! + $status: String! + ) { + getCountOfStoreOrders( + storeId: $storeId + status: $status + ) + } + `, + variables: { storeId, status }, + }) + .toPromise(); + + return res.data['getCountOfStoreOrders']; + } +} diff --git a/packages/admin-web-angular/src/app/@core/data/warehouses.service.ts b/packages/admin-web-angular/src/app/@core/data/warehouses.service.ts new file mode 100644 index 0000000..9a9e5bf --- /dev/null +++ b/packages/admin-web-angular/src/app/@core/data/warehouses.service.ts @@ -0,0 +1,277 @@ +import { Injectable } from '@angular/core'; +import { Apollo, gql } from 'apollo-angular'; +import Warehouse from '@modules/server.common/entities/Warehouse'; +import { map, share } from 'rxjs/operators'; +import { Observable } from 'rxjs'; +import IWarehouse from '@modules/server.common/interfaces/IWarehouse'; +import IWarehouseProductCreateObject from '@modules/server.common/interfaces/IWarehouseProduct'; +import GeoLocation from '@modules/server.common/entities/GeoLocation'; +import IPagingOptions from '@modules/server.common/interfaces/IPagingOptions'; + +@Injectable() +export class WarehousesService { + constructor(private readonly _apollo: Apollo) {} + + hasExistingStores(): Observable { + return this._apollo + .query<{ hasExistingStores: boolean }>({ + query: gql` + query HasExistingStores { + hasExistingStores + } + `, + }) + .pipe(map((res) => res.data.hasExistingStores)); + } + + getCountExistingCustomers() { + return this._apollo + .watchQuery<{ + getCountExistingCustomers: { total; perStore }; + }>({ + query: gql` + query GetCountExistingCustomers { + getCountExistingCustomers { + total + perStore { + storeId + customersCount + } + } + } + `, + }) + .valueChanges.pipe( + map((res) => res.data.getCountExistingCustomers) + ); + } + + getCountExistingCustomersToday() { + return this._apollo + .watchQuery<{ + getCountExistingCustomersToday: { total; perStore }; + }>({ + query: gql` + query GetCountExistingCustomersToday { + getCountExistingCustomersToday { + total + perStore { + storeId + customersCount + } + } + } + `, + }) + .valueChanges.pipe( + map((res) => res.data.getCountExistingCustomersToday) + ); + } + + getAllStores() { + return this._apollo + .query<{ getAllStores: Warehouse[] }>({ + query: gql` + query GetAllStores { + getAllStores { + id + _createdAt + geoLocation { + loc { + coordinates + } + } + } + } + `, + }) + .pipe(map((res) => res.data.getAllStores)); + } + + getStoreLivePosition() { + return this._apollo + .watchQuery<{ getAllStores: Warehouse[] }>({ + query: gql` + query GetAllStores { + getAllStores { + id + _createdAt + name + logo + geoLocation { + loc { + coordinates + } + city + countryId + } + } + } + `, + pollInterval: 5000, + }) + .valueChanges.pipe(map((res) => res.data.getAllStores)); + } + + getStores(pagingOptions?: IPagingOptions): Observable { + return this._apollo + .watchQuery<{ warehouses: IWarehouse[] }>({ + query: gql` + query AllWarehouses($pagingOptions: PagingOptionsInput) { + warehouses(pagingOptions: $pagingOptions) { + _id + _createdAt + name + contactEmail + contactPhone + logo + username + usedCarriersIds + carriersIds + geoLocation { + city + streetAddress + house + } + } + } + `, + variables: { pagingOptions }, + pollInterval: 5000, + }) + .valueChanges.pipe( + map((res) => res.data.warehouses), + map((ws) => ws.map((w) => this._warehouseFactory(w))), + share() + ); + } + + getNearbyStores(geoLocation: GeoLocation): Observable { + return this._apollo + .watchQuery<{ nearbyStores: IWarehouse[] }>({ + query: gql` + query GetNearbyStores($geoLocation: GeoLocationFindInput!) { + nearbyStores(geoLocation: $geoLocation) { + _id + name + contactEmail + contactPhone + logo + geoLocation { + city + streetAddress + house + } + } + } + `, + pollInterval: 5000, + variables: { geoLocation }, + }) + .valueChanges.pipe( + map((res) => res.data.nearbyStores), + map((ws) => ws.map((w) => this._warehouseFactory(w))), + share() + ); + } + + removeByIds(ids: string[]) { + return this._apollo.mutate({ + mutation: gql` + mutation RemoveByIds($ids: [String!]!) { + removeWarehousesByIds(ids: $ids) + } + `, + variables: { ids }, + }); + } + + addProducts( + warehouseId: string, + products: IWarehouseProductCreateObject[] + ) { + return this._apollo + .mutate<{ + warehouseId: string; + products: IWarehouseProductCreateObject[]; + }>({ + mutation: gql` + mutation AddProducts( + $warehouseId: String! + $products: [WarehouseProductInput!]! + ) { + addWarehouseProducts( + warehouseId: $warehouseId + products: $products + ) { + product { + id + } + } + } + `, + variables: { + warehouseId, + products, + }, + }) + .pipe( + map((result: any) => result.data.warehouseAddProducts), + share() + ); + } + + removeProductsById(warehouseId: string, productsIds: string[]) { + return this._apollo.mutate({ + mutation: gql` + mutation RemoveProductsByIds( + $warehouseId: String! + $productsIds: [String!]! + ) { + removeWarehouseProducts( + warehouseId: $warehouseId + productsIds: $productsIds + ) + } + `, + variables: { warehouseId, productsIds }, + }); + } + + getStoreById(id: string) { + return this._apollo + .query({ + query: gql` + query GetStoreById($id: String!) { + warehouse(id: $id) { + id + name + logo + } + } + `, + variables: { id }, + }) + .pipe( + map((res) => res.data['warehouse']), + share() + ); + } + + async getCountOfMerchants() { + const res = await this._apollo + .query({ + query: gql` + query GetCountOfMerchants { + getCountOfMerchants + } + `, + }) + .toPromise(); + + return res.data['getCountOfMerchants']; + } + protected _warehouseFactory(warehouse: IWarehouse) { + return warehouse == null ? null : new Warehouse(warehouse); + } +} diff --git a/packages/admin-web-angular/src/app/@core/module-import-guard.ts b/packages/admin-web-angular/src/app/@core/module-import-guard.ts new file mode 100644 index 0000000..5284dff --- /dev/null +++ b/packages/admin-web-angular/src/app/@core/module-import-guard.ts @@ -0,0 +1,7 @@ +export function throwIfAlreadyLoaded(parentModule: any, moduleName: string) { + if (parentModule) { + throw new Error( + `${moduleName} has already been loaded. Import Core modules in the AppModule only.` + ); + } +} diff --git a/packages/admin-web-angular/src/app/@core/roleProvider.ts b/packages/admin-web-angular/src/app/@core/roleProvider.ts new file mode 100644 index 0000000..f840ef4 --- /dev/null +++ b/packages/admin-web-angular/src/app/@core/roleProvider.ts @@ -0,0 +1,9 @@ +import { NbRoleProvider } from '@nebular/security'; +import { of as observableOf } from 'rxjs'; + +export class NbEverRoleProvider extends NbRoleProvider { + getRole() { + // here you could provide any role based on any auth flow + return observableOf('guest'); + } +} diff --git a/packages/admin-web-angular/src/app/@core/services/dashboard/layout.service.ts b/packages/admin-web-angular/src/app/@core/services/dashboard/layout.service.ts new file mode 100644 index 0000000..f91c395 --- /dev/null +++ b/packages/admin-web-angular/src/app/@core/services/dashboard/layout.service.ts @@ -0,0 +1,16 @@ +import { Injectable } from '@angular/core'; +import { Observable, Subject } from 'rxjs'; +import { delay, share } from 'rxjs/operators'; + +@Injectable() +export class LayoutService { + protected layoutSize$ = new Subject(); + + changeLayoutSize() { + this.layoutSize$.next(true); + } + + onChangeLayoutSize(): Observable { + return this.layoutSize$.pipe(share(), delay(1)); + } +} diff --git a/packages/admin-web-angular/src/app/@core/services/dashboard/orders-chart.service.ts b/packages/admin-web-angular/src/app/@core/services/dashboard/orders-chart.service.ts new file mode 100644 index 0000000..41357a3 --- /dev/null +++ b/packages/admin-web-angular/src/app/@core/services/dashboard/orders-chart.service.ts @@ -0,0 +1,486 @@ +import { Injectable } from '@angular/core'; +import { PeriodsService } from './periods.service'; + +export class OrdersChart { + chartLabel: string[]; + linesData: number[][]; +} + +// tslint:disable-next-line:max-classes-per-file +@Injectable() +export class OrdersChartService { + // TODO: replace with dynamic range + private _years = ['2012', '2013', '2014', '2015', '2016', '2017', '2018']; + + private data = {}; + + constructor(private period: PeriodsService) { + this.data = { + today: this.getDataForDayPeriod(), + lastWeek: this.getDataForWeekPeriod(), + currentYear: this.getDataForMonthPeriod(), + years: this.getDataForYearPeriod(), + }; + } + + getDataLabels(nPoints: number, labelsArray: string[]): string[] { + const labelsArrayLength = labelsArray.length; + const step = Math.round(nPoints / labelsArrayLength); + + return Array.from(Array(nPoints)).map((item, index) => { + const dataIndex = Math.round(index / step); + + return index % step === 0 ? labelsArray[dataIndex] : ''; + }); + } + + getOrdersChartData(period: string): OrdersChart { + return this.data[period]; + } + + private getDataForDayPeriod(): OrdersChart { + return { + chartLabel: this.getDataLabels(24, this.period.getHours()), + linesData: [[0, 2, 4, 2, 3, 4, 7, 5, 3, 1, 1, 2]], + }; + } + + private getDataForWeekPeriod(): OrdersChart { + return { + chartLabel: this.getDataLabels(42, this.period.getWeekDays()), + linesData: [ + [ + 184, + 267, + 326, + 366, + 389, + 399, + 392, + 371, + 340, + 304, + 265, + 227, + 191, + 158, + 130, + 108, + 95, + 91, + 97, + 109, + 125, + 144, + 166, + 189, + 212, + 236, + 259, + 280, + 300, + 316, + 329, + 338, + 342, + 339, + 329, + 312, + 288, + 258, + 221, + 178, + 128, + 71, + ], + [ + 158, + 178, + 193, + 205, + 212, + 213, + 204, + 190, + 180, + 173, + 168, + 164, + 162, + 160, + 159, + 158, + 159, + 166, + 179, + 195, + 215, + 236, + 257, + 276, + 292, + 301, + 304, + 303, + 300, + 293, + 284, + 273, + 262, + 251, + 241, + 234, + 232, + 232, + 232, + 232, + 232, + 232, + ], + [ + 58, + 137, + 202, + 251, + 288, + 312, + 323, + 324, + 311, + 288, + 257, + 222, + 187, + 154, + 124, + 100, + 81, + 68, + 61, + 58, + 61, + 69, + 80, + 96, + 115, + 137, + 161, + 186, + 210, + 233, + 254, + 271, + 284, + 293, + 297, + 297, + 297, + 297, + 297, + 297, + 297, + 297, + 297, + ], + ], + }; + } + + private getDataForMonthPeriod(): OrdersChart { + return { + chartLabel: this.getDataLabels(47, this.period.getMonths()), + linesData: [ + [ + 5, + 63, + 113, + 156, + 194, + 225, + 250, + 270, + 283, + 289, + 290, + 286, + 277, + 264, + 244, + 220, + 194, + 171, + 157, + 151, + 150, + 152, + 155, + 160, + 166, + 170, + 167, + 153, + 135, + 115, + 97, + 82, + 71, + 64, + 63, + 62, + 61, + 62, + 65, + 73, + 84, + 102, + 127, + 159, + 203, + 259, + 333, + ], + [ + 6, + 83, + 148, + 200, + 240, + 265, + 273, + 259, + 211, + 122, + 55, + 30, + 28, + 36, + 50, + 68, + 88, + 109, + 129, + 146, + 158, + 163, + 165, + 173, + 187, + 208, + 236, + 271, + 310, + 346, + 375, + 393, + 400, + 398, + 387, + 368, + 341, + 309, + 275, + 243, + 220, + 206, + 202, + 207, + 222, + 247, + 286, + 348, + ], + [ + 398, + 348, + 315, + 292, + 274, + 261, + 251, + 243, + 237, + 231, + 222, + 209, + 192, + 172, + 152, + 132, + 116, + 102, + 90, + 80, + 71, + 64, + 58, + 53, + 49, + 48, + 54, + 66, + 84, + 104, + 125, + 142, + 156, + 166, + 172, + 174, + 172, + 167, + 159, + 149, + 136, + 121, + 105, + 86, + 67, + 45, + 22, + ], + ], + }; + } + + private getDataForYearPeriod(): OrdersChart { + return { + chartLabel: this.getDataLabels(42, this._years), + linesData: [ + [ + 190, + 269, + 327, + 366, + 389, + 398, + 396, + 387, + 375, + 359, + 343, + 327, + 312, + 298, + 286, + 276, + 270, + 268, + 265, + 258, + 247, + 234, + 220, + 204, + 188, + 172, + 157, + 142, + 128, + 116, + 106, + 99, + 95, + 94, + 92, + 89, + 84, + 77, + 69, + 60, + 49, + 36, + 22, + ], + [ + 265, + 307, + 337, + 359, + 375, + 386, + 393, + 397, + 399, + 397, + 390, + 379, + 365, + 347, + 326, + 305, + 282, + 261, + 241, + 223, + 208, + 197, + 190, + 187, + 185, + 181, + 172, + 160, + 145, + 126, + 105, + 82, + 60, + 40, + 26, + 19, + 22, + 43, + 82, + 141, + 220, + 321, + ], + [ + 9, + 165, + 236, + 258, + 244, + 206, + 186, + 189, + 209, + 239, + 273, + 307, + 339, + 365, + 385, + 396, + 398, + 385, + 351, + 300, + 255, + 221, + 197, + 181, + 170, + 164, + 162, + 161, + 159, + 154, + 146, + 135, + 122, + 108, + 96, + 87, + 83, + 82, + 82, + 82, + 82, + 82, + 82, + ], + ], + }; + } +} diff --git a/packages/admin-web-angular/src/app/@core/services/dashboard/orders-profit-chart.service.ts b/packages/admin-web-angular/src/app/@core/services/dashboard/orders-profit-chart.service.ts new file mode 100644 index 0000000..3809a80 --- /dev/null +++ b/packages/admin-web-angular/src/app/@core/services/dashboard/orders-profit-chart.service.ts @@ -0,0 +1,62 @@ +import { of as observableOf, Observable } from 'rxjs'; +import { Injectable } from '@angular/core'; +import { OrdersChart, OrdersChartService } from './orders-chart.service'; +import { ProfitChart, ProfitChartService } from './profit-chart.service'; + +export class OrderProfitChartSummary { + isPrice?: boolean; + values: { + total: { + title: string; + value: number; + }; + completed: { + title: string; + value: number; + }; + cancelled: { + title: string; + value: number; + }; + }; +} + +// tslint:disable-next-line:max-classes-per-file +@Injectable() +export class OrdersProfitChartService { + private summaryNew: OrderProfitChartSummary[] = [ + { + values: { + total: { + title: 'Dummy title', + value: 9999, + }, + completed: { + title: 'Dummy title completed', + value: 9999, + }, + cancelled: { + title: 'Dummy title cancelled', + value: 8888, + }, + }, + }, + ]; + + constructor( + private ordersChartService: OrdersChartService, + private profitChartService: ProfitChartService + ) {} + + getOrderProfitChartSummary(): Observable { + return observableOf(this.summaryNew); + } + + getOrdersChartData(period: string): Observable { + return observableOf(this.ordersChartService.getOrdersChartData(period)); + } + + getProfitChartData(period: string): Observable { + return observableOf(this.profitChartService.getProfitChartData(period)); + } +} diff --git a/packages/admin-web-angular/src/app/@core/services/dashboard/periods.service.ts b/packages/admin-web-angular/src/app/@core/services/dashboard/periods.service.ts new file mode 100644 index 0000000..aa2b600 --- /dev/null +++ b/packages/admin-web-angular/src/app/@core/services/dashboard/periods.service.ts @@ -0,0 +1,366 @@ +import { Injectable } from '@angular/core'; +import { TranslateService } from '@ngx-translate/core'; + +@Injectable() +export class PeriodsService { + constructor(private readonly _translateService: TranslateService) {} + + getYearsByRange(dateRange: { from: Date; to: Date }) { + const from = dateRange.from; + const to = dateRange.to; + + const yearLabels = []; + const currentDate = new Date(from.getTime()); + + while (true) { + yearLabels.push(currentDate.getFullYear()); + + if (currentDate.getFullYear() === to.getFullYear()) { + break; + } + + currentDate.setFullYear(currentDate.getFullYear() + 1); + } + + return yearLabels; + } + + getDatesLastMonth() { + const today = new Date(); + const current = new Date(today.getTime()); + current.setDate(1); + + const months = this.getMonths(); + const monthDateLabels = []; + const todayDate = today.getDate(); + + while (true) { + const currentDate = current.getDate(); + let currentMonth = months[current.getMonth()]; + + monthDateLabels.push(`${currentDate} ${currentMonth}`); + + if (currentDate === todayDate) { + if (currentDate === 1) { + current.setDate(currentDate + 1); + currentMonth = months[current.getMonth()]; + + monthDateLabels.push(`${currentDate} ${currentMonth}`); + } + + break; + } + + current.setDate(currentDate + 1); + } + + return monthDateLabels; + } + + getMonthLabelsKeys(dateRange: { from: Date; to: Date }) { + const keys: string[] = []; + const labels: string[] = []; + + const from = dateRange.from; + const to = dateRange.to; + const current = new Date(from); + current.setDate(1); + + while (true) { + keys.push(this.getMonthLabelKey(current)); + labels.push(this.getMonthLabel(current)); + + if ( + current.getFullYear() === to.getFullYear() && + current.getMonth() === to.getMonth() + ) { + break; + } + + current.setMonth(current.getMonth() + 1); + } + + return { keys, labels }; + } + + getMonthLabelKey(orderDate: Date): string { + return `${orderDate.getMonth()} ${orderDate.getFullYear()}`; + } + + getMonthLabel(orderDate: Date): string { + const months = this.getMonths(); + return `${months[orderDate.getMonth()]} ${orderDate.getFullYear()}`; + } + + getWeekLabelKey( + orderDate: Date, + getDateWeekNumber: (date: Date) => number + ) { + const orderWeek = getDateWeekNumber(orderDate); + let orderWeekLabel = ''; + + if (orderWeek === 1) { + const datePrev = new Date(orderDate); + let dateNext: Date; + + if (orderDate.getMonth() === 0) { + datePrev.setFullYear(datePrev.getFullYear() - 1); + } + + datePrev.setDate(31); + + dateNext = new Date(datePrev); + dateNext.setDate(dateNext.getDate() + 1); + + const weekPrev = getDateWeekNumber(datePrev); + const weekNext = getDateWeekNumber(dateNext); + + if (orderWeek === weekPrev && weekPrev === weekNext) { + const yearPrevAbbr = datePrev + .getFullYear() + .toString() + .substring(2); + const yearNextAbbr = dateNext + .getFullYear() + .toString() + .substring(2); + + orderWeekLabel = `${orderWeek}\n${yearPrevAbbr}-${yearNextAbbr}`; + } else { + orderWeekLabel = `${orderWeek}\n${orderDate.getFullYear()}`; + } + } else { + orderWeekLabel = `${orderWeek}\n${orderDate.getFullYear()}`; + } + + return orderWeekLabel; + } + + getWeekLabelsKeys( + dateRanges: { from: Date; to: Date }, + getWeekFunction: (date: Date) => number + ) { + const from = dateRanges.from; + const to = dateRanges.to; + + let isFirstWeekIntercepted = false; + + if (from.getFullYear() < to.getFullYear()) { + const fromEnd = new Date(from); + fromEnd.setMonth(11); + fromEnd.setDate(31); + + const toBegin = new Date(fromEnd); + toBegin.setDate(toBegin.getDate() + 1); + + const fromWeek = getWeekFunction(fromEnd); + const toWeek = getWeekFunction(toBegin); + + if (fromWeek === toWeek) { + isFirstWeekIntercepted = true; + } + } + + const currentDate = new Date(from); + const keys: string[] = []; + const labels: string[] = []; + + const labelPrefix = this._translate( + 'DASHBOARD_VIEW.CHARTS.LABELS.WEEK' + ); + + while (true) { + const currentWeek = getWeekFunction(currentDate); + let weekYear: string; + + if (currentWeek === 1 && isFirstWeekIntercepted) { + const fromYearAbbr = from.getFullYear().toString().substring(2); + + const toYearAbbr = to.getFullYear().toString().substring(2); + + weekYear = `${fromYearAbbr}-${toYearAbbr}`; + } else { + weekYear = currentDate.getFullYear().toString(); + } + + keys.push(`${currentWeek}\n${weekYear}`); + + labels.push(`${labelPrefix}-${currentWeek}\n${weekYear}`); + + currentDate.setDate(currentDate.getDate() + 7); + + if (currentDate.getTime() > dateRanges.to.getTime()) { + break; + } + } + + return { keys, labels }; + } + + getDateLabelKey(orderDate: Date) { + const date = new Date(orderDate.getTime()); + + const day = date.getDate(); + const month = date.getMonth(); + const year = date.getFullYear(); + + return this._getFormattedDate(day, month, year); + } + + getHours() { + return [ + '1:00', + '2:00', + '3:00', + '4:00', + '5:00', + '6:00', + '7:00', + '8:00', + '9:00', + '10:00', + '11:00', + '12:00', + '13:00', + '14:00', + '15:00', + '16:00', + '17:00', + '18:00', + '19:00', + '20:00', + '21:00', + '22:00', + '23:00', + '00:00', + ]; + } + + getMonths() { + const translationMonthsPrefix = 'DASHBOARD_VIEW.CHARTS.LABELS.MONTHS'; + + const labels = [ + this._translate(`${translationMonthsPrefix}.JAN`), + this._translate(`${translationMonthsPrefix}.FEB`), + this._translate(`${translationMonthsPrefix}.MAR`), + this._translate(`${translationMonthsPrefix}.APR`), + this._translate(`${translationMonthsPrefix}.MAY`), + this._translate(`${translationMonthsPrefix}.JUN`), + this._translate(`${translationMonthsPrefix}.JUL`), + this._translate(`${translationMonthsPrefix}.AUG`), + this._translate(`${translationMonthsPrefix}.SEP`), + this._translate(`${translationMonthsPrefix}.OCT`), + this._translate(`${translationMonthsPrefix}.NOV`), + this._translate(`${translationMonthsPrefix}.DEC`), + ]; + + return labels; + } + + getWeekDays() { + const translationWeekDaysPrefix = + 'DASHBOARD_VIEW.CHARTS.LABELS.WEEKDAYS'; + + const labels = [ + this._translate(`${translationWeekDaysPrefix}.MON`), + this._translate(`${translationWeekDaysPrefix}.TUE`), + this._translate(`${translationWeekDaysPrefix}.WED`), + this._translate(`${translationWeekDaysPrefix}.THU`), + this._translate(`${translationWeekDaysPrefix}.FRI`), + this._translate(`${translationWeekDaysPrefix}.SAT`), + this._translate(`${translationWeekDaysPrefix}.SUN`), + ]; + + return labels; + } + + getDatesLabelsKeys(dateRanges: { from: Date; to: Date }) { + const keys: string[] = []; + const labels: string[] = []; + + const currentDate = new Date(dateRanges.from); + const months = this.getMonths(); + + const endDay = dateRanges.to.getDate(); + const endMonth = dateRanges.to.getMonth(); + const endYear = dateRanges.to.getFullYear(); + + while (true) { + const currentDay = currentDate.getDate(); + const currentMonth = currentDate.getMonth(); + const currentMonthLabel = months[currentDate.getMonth()]; + const currentYear = currentDate.getFullYear(); + + keys.push( + this._getFormattedDate(currentDay, currentMonth, currentYear) + ); + labels.push( + this._getFormattedDate( + currentDay, + currentMonthLabel, + currentYear + ) + ); + + if ( + currentDay === endDay && + currentMonth === endMonth && + currentYear === endYear + ) { + break; + } + currentDate.setDate(currentDate.getDate() + 1); + } + + return { keys, labels }; + } + + getYearLabels(yearsRange: { from: number; to: number }): string[] { + let { from, to } = yearsRange; + + if (from === Number.MAX_SAFE_INTEGER) { + from = to; + } + + let labels: string[] = this._generateYearLabels(from, to); + + if (labels.length === 1) { + labels.unshift(`${from - 1}`); + labels.push(`${to + 1}`); + } else if (labels.length === 2) { + labels.push(`${to + 1}`); + } + + return labels; + } + + private _generateYearLabels(yearFrom: number, yearTo: number): string[] { + const labels = + yearFrom === yearTo + ? [`${yearFrom}`] + : [ + `${yearFrom}`, + ...this._generateYearLabels(yearFrom + 1, yearTo), + ]; + + return labels; + } + + private _getFormattedDate( + day: number, + month: string | number, + year: number + ) { + return `${day} ${month} ${`${year}`.substr(2, 2)}`; + } + + private _translate(key: string) { + let translationResult = ''; + + this._translateService.get(key).subscribe((res) => { + translationResult = res; + }); + + return translationResult; + } +} diff --git a/packages/admin-web-angular/src/app/@core/services/dashboard/profit-chart.service.ts b/packages/admin-web-angular/src/app/@core/services/dashboard/profit-chart.service.ts new file mode 100644 index 0000000..0ca978d --- /dev/null +++ b/packages/admin-web-angular/src/app/@core/services/dashboard/profit-chart.service.ts @@ -0,0 +1,87 @@ +import { Injectable } from '@angular/core'; +import { PeriodsService } from './periods.service'; + +export class ProfitChart { + chartLabel: string[]; + data: number[][]; +} + +// tslint:disable-next-line:max-classes-per-file +@Injectable() +export class ProfitChartService { + // TODO: replace with dynamic range + private _years = ['2012', '2013', '2014', '2015', '2016', '2017', '2018']; + + private data = {}; + + constructor(private period: PeriodsService) { + this.data = { + today: this.getDataForDayPeriod(), + lastWeek: this.getDataForWeekPeriod(), + currentYear: this.getDataForMonthPeriod(), + years: this.getDataForYearPeriod(), + }; + } + + getProfitChartData(period: string): ProfitChart { + return this.data[period]; + } + + private getDataForDayPeriod(): ProfitChart { + const nPoint = this.period.getHours().length; + + return { + chartLabel: this.period.getHours(), + data: [ + this.getRandomData(nPoint), + this.getRandomData(nPoint), + this.getRandomData(nPoint), + ], + }; + } + + private getDataForWeekPeriod(): ProfitChart { + const nPoint = this.period.getWeekDays().length; + + return { + chartLabel: this.period.getWeekDays(), + data: [ + this.getRandomData(nPoint), + this.getRandomData(nPoint), + this.getRandomData(nPoint), + ], + }; + } + + private getDataForMonthPeriod(): ProfitChart { + const nPoint = this.period.getMonths().length; + + return { + chartLabel: this.period.getMonths(), + data: [ + this.getRandomData(nPoint), + this.getRandomData(nPoint), + this.getRandomData(nPoint), + ], + }; + } + + private getDataForYearPeriod(): ProfitChart { + const nPoint = this._years.length; + + return { + chartLabel: this._years, + data: [ + this.getRandomData(nPoint), + this.getRandomData(nPoint), + this.getRandomData(nPoint), + ], + }; + } + + private getRandomData(nPoints: number): number[] { + return Array.from(Array(nPoints)).map(() => { + return 0; + }); + } +} diff --git a/packages/admin-web-angular/src/app/@core/services/notify/notify.service.ts b/packages/admin-web-angular/src/app/@core/services/notify/notify.service.ts new file mode 100644 index 0000000..c4448c4 --- /dev/null +++ b/packages/admin-web-angular/src/app/@core/services/notify/notify.service.ts @@ -0,0 +1,32 @@ +import { ToasterService, Toast, ToastType } from 'angular2-toaster'; +import { Injectable } from '@angular/core'; + +@Injectable() +export class NotifyService { + private _toast: Toast; + + constructor(private readonly _toasterService: ToasterService) {} + + success(title?: string, body?: string) { + this._setupToast('success', title, body); + this._notify(); + } + + warn(title?: string, body?: string) { + this._setupToast('warning', title, body); + this._notify(); + } + + error(title?: string, body?: string) { + this._setupToast('error', title, body); + this._notify(); + } + + private _setupToast(type: ToastType, title: string, body: string) { + this._toast = { type, title, body }; + } + + private _notify() { + this._toasterService.pop(this._toast); + } +} diff --git a/packages/admin-web-angular/src/app/@core/services/server-settings.service.ts b/packages/admin-web-angular/src/app/@core/services/server-settings.service.ts new file mode 100644 index 0000000..57e06de --- /dev/null +++ b/packages/admin-web-angular/src/app/@core/services/server-settings.service.ts @@ -0,0 +1,47 @@ +import { Injectable } from '@angular/core'; +import { Store } from '../data/store.service'; +import { Apollo, gql } from 'apollo-angular'; +import { IAdminAppSettings } from '@modules/server.common/interfaces/IAppsSettings'; +import { take, map } from 'rxjs/operators'; + +@Injectable({ + providedIn: 'root', +}) +export class ServerSettingsService { + constructor( + private readonly _apollo: Apollo, + private readonly store: Store + ) {} + + async load() { + return new Promise(async (resolve, reject) => { + const res = await this.getAdminAppSettings(); + + if (res) { + this.store.adminPasswordReset = res.adminPasswordReset; + this.store.fakeDataGenerator = res.fakeDataGenerator; + } + + resolve(true); + }); + } + + getAdminAppSettings() { + return this._apollo + .query<{ settings: IAdminAppSettings }>({ + query: gql` + query adminAppSettings { + adminAppSettings { + adminPasswordReset + fakeDataGenerator + } + } + `, + }) + .pipe( + take(1), + map((res) => res.data['adminAppSettings']) + ) + .toPromise(); + } +} diff --git a/packages/admin-web-angular/src/app/@core/utils/analytics.service.ts b/packages/admin-web-angular/src/app/@core/utils/analytics.service.ts new file mode 100644 index 0000000..64a8060 --- /dev/null +++ b/packages/admin-web-angular/src/app/@core/utils/analytics.service.ts @@ -0,0 +1,34 @@ +import { Injectable } from '@angular/core'; +import { NavigationEnd, Router } from '@angular/router'; +import { Location } from '@angular/common'; +import { filter } from 'rxjs/operators'; + +declare const ga: any; + +@Injectable() +export class AnalyticsService { + private enabled: boolean; + + constructor(private location: Location, private router: Router) { + this.enabled = false; + } + + trackPageViews() { + if (this.enabled) { + this.router.events + .pipe(filter((event) => event instanceof NavigationEnd)) + .subscribe(() => { + ga('send', { + hitType: 'pageview', + page: this.location.path(), + }); + }); + } + } + + trackEvent(eventName: string) { + if (this.enabled) { + ga('send', 'event', eventName); + } + } +} diff --git a/packages/admin-web-angular/src/app/@core/utils/getDifferenceFromTimes .ts b/packages/admin-web-angular/src/app/@core/utils/getDifferenceFromTimes .ts new file mode 100644 index 0000000..0db7da8 --- /dev/null +++ b/packages/admin-web-angular/src/app/@core/utils/getDifferenceFromTimes .ts @@ -0,0 +1,32 @@ +export function getDifferenceFromTimes(time1: Date, time2: Date): string { + let delta = Math.abs(time1.getTime() - time2.getTime()) / 1000; + + const days = Math.floor(delta / 86400); + delta -= days * 86400; + + const hours = Math.floor(delta / 3600) % 24; + delta -= hours * 3600; + + const minutes = Math.floor(delta / 60) % 60; + delta -= minutes * 60; + + let seconds = delta % 60; + seconds = Math.ceil(seconds); + + let h = '0' + hours; + h = h.substr(-2); + + let min = '0' + minutes; + min = min.substr(-2); + + let sec = '0' + seconds; + sec = sec.substr(-2); + + if (days !== 0) { + return `${days + ' days'}`; + } else { + return `${hours !== 0 ? hours + 'h ' : ''}${ + minutes !== 0 ? minutes + 'm ' : '' + }${seconds !== 0 ? seconds + 's' : ''}`; + } +} diff --git a/packages/admin-web-angular/src/app/@core/utils/i18n.module.ts b/packages/admin-web-angular/src/app/@core/utils/i18n.module.ts new file mode 100644 index 0000000..37ee06b --- /dev/null +++ b/packages/admin-web-angular/src/app/@core/utils/i18n.module.ts @@ -0,0 +1,34 @@ +import { NgModule } from '@angular/core'; +import { HttpClientModule, HttpClient } from '@angular/common/http'; +import { + TranslateModule, + TranslateLoader, + TranslateService, +} from '@ngx-translate/core'; +import { TranslateHttpLoader } from '@ngx-translate/http-loader'; + +// AoT requires an exported function for factories +export function HttpLoaderFactory(http: HttpClient) { + return new TranslateHttpLoader(http); +} + +@NgModule({ + declarations: [], + imports: [ + HttpClientModule, + TranslateModule.forRoot({ + loader: { + provide: TranslateLoader, + useFactory: HttpLoaderFactory, + deps: [HttpClient], + }, + }), + ], + exports: [TranslateModule], +}) +export class I18nModule { + constructor(translate: TranslateService) { + translate.setDefaultLang('en-US'); + translate.use('en-US'); + } +} diff --git a/packages/admin-web-angular/src/app/@core/utils/passwords.service.ts b/packages/admin-web-angular/src/app/@core/utils/passwords.service.ts new file mode 100644 index 0000000..757f589 --- /dev/null +++ b/packages/admin-web-angular/src/app/@core/utils/passwords.service.ts @@ -0,0 +1,22 @@ +import { Injectable } from '@angular/core'; +import { environment } from 'environments/environment'; + +@Injectable() +export class PasswordsService { + static generatePassword() { + const min = 7; + const max = 10; + + const length = Math.floor(Math.random() * (max - min + 1)) + min; + + const charset = environment.GENERATE_PASSWORD_CHARSET; + + let password = ''; + + for (let i = 0, n = charset.length; i < length; ++i) { + password += charset.charAt(Math.floor(Math.random() * n)); + } + + return password; + } +} diff --git a/packages/admin-web-angular/src/app/@shared/carrier/carrier-mutation/carrier-mutation.component.html b/packages/admin-web-angular/src/app/@shared/carrier/carrier-mutation/carrier-mutation.component.html new file mode 100644 index 0000000..1328482 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/carrier/carrier-mutation/carrier-mutation.component.html @@ -0,0 +1,51 @@ + + + + + +
+ +
+ +
+ + + +
+
+
diff --git a/packages/admin-web-angular/src/app/@shared/carrier/carrier-mutation/carrier-mutation.component.scss b/packages/admin-web-angular/src/app/@shared/carrier/carrier-mutation/carrier-mutation.component.scss new file mode 100644 index 0000000..bab7aed --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/carrier/carrier-mutation/carrier-mutation.component.scss @@ -0,0 +1,19 @@ +.ng-valid[required], +.ng-valid.required { + border-left: 5px solid #42a948; /* green */ +} + +:host ::ng-deep .card-footer button { + background-color: #111111 !important; + border: none !important; + color: white !important; + cursor: pointer !important; +} +:host ::ng-deep .card-footer button:hover { + background-color: #bebebe !important; + color: #111111 !important; +} + +nb-card { + margin: 0px; +} diff --git a/packages/admin-web-angular/src/app/@shared/carrier/carrier-mutation/carrier-mutation.component.ts b/packages/admin-web-angular/src/app/@shared/carrier/carrier-mutation/carrier-mutation.component.ts new file mode 100644 index 0000000..c7c5d4d --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/carrier/carrier-mutation/carrier-mutation.component.ts @@ -0,0 +1,120 @@ +import { + Component, + ViewChild, + EventEmitter, + AfterViewInit, +} from '@angular/core'; +import { FormBuilder, FormControl, FormGroup } from '@angular/forms'; +import { ToasterService } from 'angular2-toaster'; + +import { CarrierRouter } from '@modules/client.common.angular2/routers/carrier-router.service'; +import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; + +import { BasicInfoFormComponent } from '../forms'; +import { LocationFormComponent } from '../../forms/location'; + +import { getDummyImage } from '@modules/server.common/utils'; + +@Component({ + selector: 'ea-carrier-mutation', + templateUrl: './carrier-mutation.component.html', + styleUrls: ['./carrier-mutation.component.scss'], +}) +export class CarrierMutationComponent implements AfterViewInit { + @ViewChild('basicInfoForm') + basicInfoForm: BasicInfoFormComponent; + + @ViewChild('locationForm') + locationForm: LocationFormComponent; + + readonly form: FormGroup = this.formBuilder.group({ + basicInfo: BasicInfoFormComponent.buildForm(this.formBuilder), + location: LocationFormComponent.buildForm(this.formBuilder), + password: BasicInfoFormComponent.buildPasswordForm(this.formBuilder), + }); + + readonly basicInfo = this.form.get('basicInfo') as FormControl; + readonly location = this.form.get('location') as FormControl; + readonly password = this.form.get('password') as FormControl; + + public loading: boolean; + + mapCoordEmitter = new EventEmitter< + google.maps.LatLng | google.maps.LatLngLiteral + >(); + mapGeometryEmitter = new EventEmitter< + google.maps.places.PlaceGeometry | google.maps.GeocoderGeometry + >(); + + constructor( + private toasterService: ToasterService, + private readonly activeModal: NgbActiveModal, + private readonly formBuilder: FormBuilder, + protected carrierRouter: CarrierRouter + ) {} + + ngAfterViewInit(): void { + if (this.locationForm) { + this.locationForm.setDefaultCoords(); + } + } + + onGeometrySend( + geometry: + | google.maps.places.PlaceGeometry + | google.maps.GeocoderGeometry + ) { + this.mapGeometryEmitter.emit(geometry); + } + + onCoordinatesChanges( + location: google.maps.LatLng | google.maps.LatLngLiteral + ) { + this.mapCoordEmitter.emit(location); + } + + async createCarrier() { + try { + // GeoJSON use reversed order for coordinates from our implementation. + // we use lat => lng but GeoJSON use lng => lat. + const geoLocationInput = this.locationForm.getValue(); + geoLocationInput.loc.coordinates.reverse(); + + this.loading = true; + const carrierCreateObj = { + ...this.basicInfoForm.getValue(), + geoLocation: geoLocationInput, + }; + + if (!carrierCreateObj.logo) { + const letter = carrierCreateObj.firstName + .charAt(0) + .toUpperCase(); + carrierCreateObj.logo = getDummyImage(300, 300, letter); + } + + const carrier = await this.carrierRouter.register({ + carrier: carrierCreateObj, + password: this.basicInfoForm.getPassword(), + }); + this.loading = false; + this.toasterService.pop( + 'success', + `Carrier ${carrier.firstName} was created` + ); + this.activeModal.close(carrier); + } catch (err) { + this.loading = false; + + this.toasterService.pop( + 'error', + `Error in creating carrier: "${err.message}"` + ); + this.activeModal.dismiss('canceled'); + } + } + + cancel() { + this.activeModal.dismiss('canceled'); + } +} diff --git a/packages/admin-web-angular/src/app/@shared/carrier/carrier-mutation/carrier-mutation.module.ts b/packages/admin-web-angular/src/app/@shared/carrier/carrier-mutation/carrier-mutation.module.ts new file mode 100644 index 0000000..e3b1651 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/carrier/carrier-mutation/carrier-mutation.module.ts @@ -0,0 +1,24 @@ +import { NgModule } from '@angular/core'; +import { CarrierMutationComponent } from './carrier-mutation.component'; +import { ThemeModule } from '../../../@theme/theme.module'; +import { TranslateModule } from '@ngx-translate/core'; +import { FormWizardModule } from '@ever-co/angular2-wizard'; +import { LocationFormModule } from '../../forms/location'; +import { CarrierFormsModule } from '../forms'; +import { GoogleMapModule } from '../../forms/google-map/google-map.module'; +import { NbSpinnerModule } from '@nebular/theme'; + +@NgModule({ + imports: [ + ThemeModule, + FormWizardModule, + TranslateModule.forChild(), + CarrierFormsModule, + LocationFormModule, + GoogleMapModule, + NbSpinnerModule, + ], + exports: [CarrierMutationComponent], + declarations: [CarrierMutationComponent] +}) +export class CarrierMutationModule {} diff --git a/packages/admin-web-angular/src/app/@shared/carrier/carrier-mutation/index.ts b/packages/admin-web-angular/src/app/@shared/carrier/carrier-mutation/index.ts new file mode 100644 index 0000000..0157be0 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/carrier/carrier-mutation/index.ts @@ -0,0 +1,2 @@ +export * from './carrier-mutation.component'; +export * from './carrier-mutation.module'; diff --git a/packages/admin-web-angular/src/app/@shared/carrier/carriers-table/carriers-table.component.html b/packages/admin-web-angular/src/app/@shared/carrier/carriers-table/carriers-table.component.html new file mode 100644 index 0000000..dc0dd00 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/carrier/carriers-table/carriers-table.component.html @@ -0,0 +1,8 @@ + + diff --git a/packages/admin-web-angular/src/app/@shared/carrier/carriers-table/carriers-table.component.scss b/packages/admin-web-angular/src/app/@shared/carrier/carriers-table/carriers-table.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/packages/admin-web-angular/src/app/@shared/carrier/carriers-table/carriers-table.component.ts b/packages/admin-web-angular/src/app/@shared/carrier/carriers-table/carriers-table.component.ts new file mode 100644 index 0000000..c6da2d2 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/carrier/carriers-table/carriers-table.component.ts @@ -0,0 +1,207 @@ +import { + Component, + OnDestroy, + AfterViewInit, + Input, + OnInit, + EventEmitter, + Output, +} from '@angular/core'; +import { LocalDataSource } from 'ng2-smart-table'; +import { TranslateService } from '@ngx-translate/core'; +import { CarrierImageComponent } from '@app/@shared/render-component/carriers-table/carrier-image/carrier-image.component'; +import { RedirectNameComponent } from '@app/@shared/render-component/name-redirect/name-redirect.component'; +import { CarrierPhoneComponent } from '@app/@shared/render-component/carriers-table/carrier-phone/carrier-phone.component'; +import { Observable, forkJoin, Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; +import Carrier from '@modules/server.common/entities/Carrier'; +import { CarrierActionsComponent } from '@app/@shared/render-component/carriers-table/carrier-actions/carrier-actions.component'; +import CarrierStatus from '@modules/server.common/enums/CarrierStatus'; + +export interface CarrierSmartTableObject { + id: string; + image: string; + name: string; + phone: string; + status: string; + address: string; + deliveries: number; +} + +@Component({ + selector: 'ea-carriers-smart-table', + templateUrl: 'carriers-table.component.html', + styleUrls: ['carriers-table.component.scss'], +}) +export class CarriersSmartTableComponent + implements OnDestroy, OnInit, AfterViewInit { + static noInfoSign = ''; + + @Input() + perPage: number; + @Input() + selectMode = 'multi'; + @Input() + actions: string = ''; + @Input() + redirectPage = 'carriers'; + @Input() + loadWholeData: CarrierSmartTableObject[]; + + @Output() + editRow = new EventEmitter(); + @Output() + deleteRow = new EventEmitter(); + + pageChange: EventEmitter = new EventEmitter(); + + settingsSmartTable: any; + sourceSmartTable = new LocalDataSource(); + selectedCarriers: Carrier[] = []; + + private ngDestroy$ = new Subject(); + + constructor(private readonly _translateService: TranslateService) {} + + get hasSelectedCarriers(): boolean { + return this.selectedCarriers.length > 0; + } + + ngOnInit(): void { + this.loadSettingsSmartTable(this.perPage); + } + + ngAfterViewInit() { + if (this.loadWholeData) { + this.loadData(this.loadWholeData); + } else { + this.smartTableChange(); + } + } + + ngOnDestroy() { + this.ngDestroy$.next(); + this.ngDestroy$.complete(); + } + + selectCarrierTmp(ev) { + this.selectedCarriers = ev.selected; + } + + async loadData(carriersData: CarrierSmartTableObject[]) { + await this.sourceSmartTable.load(carriersData); + } + + loadSettingsSmartTable(perPage) { + const columnTitlePrefix = 'CARRIERS_VIEW.SMART_TABLE_COLUMNS.'; + const getTranslate = (name: string): Observable => + this._translateService.get(columnTitlePrefix + name); + + forkJoin( + this._translateService.get('Id'), + getTranslate('IMAGE'), + getTranslate('NAME'), + getTranslate('PHONE'), + getTranslate('STATUS'), + getTranslate('ADDRESS'), + getTranslate('DELIVERIES') + ) + .pipe(takeUntil(this.ngDestroy$)) + .subscribe( + ([id, image, name, phone, status, address, deliveries]) => { + this.settingsSmartTable = { + selectMode: this.selectMode, + mode: 'external', + actions: this.actions === 'show' && { + add: false, + }, + edit: { + editButtonContent: '', + }, + delete: { + deleteButtonContent: '', + confirmDelete: true, + }, + + columns: { + images: { + title: image, + class: 'carrier-image', + type: 'custom', + renderComponent: CarrierImageComponent, + onComponentInitFunction: (instance) => { + instance.redirectPage = this.redirectPage; + }, + filter: false, + }, + name: { + title: name, + type: 'custom', + renderComponent: RedirectNameComponent, + onComponentInitFunction: (instance) => { + instance.redirectPage = this.redirectPage; + }, + }, + phone: { + title: phone, + type: 'custom', + renderComponent: CarrierPhoneComponent, + }, + status: { title: status }, + address: { title: address }, + deliveries: { title: deliveries, filter: false }, + actions: { + title: 'Actions', + filter: false, + type: 'custom', + renderComponent: CarrierActionsComponent, + }, + }, + pager: { + display: true, + perPage, + }, + }; + } + ); + } + + private async smartTableChange() { + this.sourceSmartTable + .onChanged() + .pipe(takeUntil(this.ngDestroy$)) + .subscribe(async (event) => { + if (event.action === 'page') { + const page = event.paging.page; + + this.pageChange.emit(page); + } + }); + } + + static getCarrierSmartTableObject(c: Carrier) { + return { + id: c.id, + image: c.logo || CarriersSmartTableComponent.noInfoSign, + name: `${c.firstName || CarriersSmartTableComponent.noInfoSign} ${ + c.lastName || CarriersSmartTableComponent.noInfoSign + }`, + phone: c.phone || CarriersSmartTableComponent.noInfoSign, + status: { + [CarrierStatus.Offline]: 'Offline', + [CarrierStatus.Online]: 'Online', + [CarrierStatus.Blocked]: 'Blocked', + }[c.status], + address: `${ + c.geoLocation.city || CarriersSmartTableComponent.noInfoSign + } st. ${ + c.geoLocation.streetAddress || + CarriersSmartTableComponent.noInfoSign + }, hse. № ${ + c.geoLocation.house || CarriersSmartTableComponent.noInfoSign + }`, + deliveries: c.numberOfDeliveries, + carrier: c, + }; + } +} diff --git a/packages/admin-web-angular/src/app/@shared/carrier/carriers-table/carriers-table.module.ts b/packages/admin-web-angular/src/app/@shared/carrier/carriers-table/carriers-table.module.ts new file mode 100644 index 0000000..fcc2931 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/carrier/carriers-table/carriers-table.module.ts @@ -0,0 +1,18 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { Ng2SmartTableModule } from 'ng2-smart-table'; +import { CarriersTableModule } from '@app/@shared/render-component/carriers-table/carriers-table.module'; +import { CarriersSmartTableComponent } from './carriers-table.component'; +import { TranslateModule } from '@ngx-translate/core'; + +@NgModule({ + imports: [ + CommonModule, + Ng2SmartTableModule, + CarriersTableModule, + TranslateModule.forChild(), + ], + declarations: [CarriersSmartTableComponent], + exports: [CarriersSmartTableComponent], +}) +export class CarriersSmartTableModule {} diff --git a/packages/admin-web-angular/src/app/@shared/carrier/forms/basic-info/basic-info-form.component.html b/packages/admin-web-angular/src/app/@shared/carrier/forms/basic-info/basic-info-form.component.html new file mode 100644 index 0000000..09f3697 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/carrier/forms/basic-info/basic-info-form.component.html @@ -0,0 +1,241 @@ + +
+
+ +
+
+ + +
+ + +
+ {{ + 'SHARED.FORMS.ERRORS.FIRST_NAME_REQUIRED' + | translate + }}! +
+
+ {{ + 'SHARED.FORMS.ERRORS.MUST_CONTAIN_ONLY_LETTERS' + | translate + }}! +
+
+
+ +
+ + +
+ {{ + 'SHARED.FORMS.ERRORS.LAST_NAME_REQUIRED' + | translate + }}! +
+
+ {{ + 'SHARED.FORMS.ERRORS.MUST_CONTAIN_ONLY_LETTERS' + | translate + }}! +
+
+
+
+
+ +
+
+ +
+ + +
+ {{ + 'SHARED.FORMS.ERRORS.USERNAME_REQUIRED' | translate + }}! +
+
+
+ +
+ +
+ + +
+ {{ + 'SHARED.FORMS.ERRORS.PASSWORD_REQUIRED' | translate + }}! +
+
+
+
+ +
+
+ +
+ + +
+
+ {{ + 'SHARED.FORMS.ERRORS.PHONE_MUST_CONTAINS_ONLY(specail_signs)AND_DIGIT_CHARACTER' + | translate + }} +
+
+ {{ + 'SHARED.FORMS.ERRORS.PHONE_REQUIRED' + | translate + }}! +
+
+
+
+ +
+ + +
+ + +
+ {{ + 'SHARED.FORMS.ERRORS.LOGO_URL_REQUIRED' | translate + }}! +
+
+
+ +
+ +
+
+
+ Invalid image + + +
+
+
+
+ +
+ + + {{ + 'CARRIERS_VIEW.CARRIER_PAGE.CARRIER_CAN_BE_SHARED' + | translate + }} + + + {{ 'CARRIERS_VIEW.CARRIER_PAGE.ACTIVE' | translate }} + +
+
+
+
diff --git a/packages/admin-web-angular/src/app/@shared/carrier/forms/basic-info/basic-info-form.component.scss b/packages/admin-web-angular/src/app/@shared/carrier/forms/basic-info/basic-info-form.component.scss new file mode 100644 index 0000000..6c14a68 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/carrier/forms/basic-info/basic-info-form.component.scss @@ -0,0 +1,18 @@ +.preview-img { + padding-left: 14px; + padding-right: 16px; +} + +.img-rounded { + max-height: 70px; +} + +.remove-icon { + cursor: pointer; + + span { + padding-right: 7px; + position: absolute; + font-size: 1.1em; + } +} diff --git a/packages/admin-web-angular/src/app/@shared/carrier/forms/basic-info/basic-info-form.component.ts b/packages/admin-web-angular/src/app/@shared/carrier/forms/basic-info/basic-info-form.component.ts new file mode 100644 index 0000000..58e72ac --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/carrier/forms/basic-info/basic-info-form.component.ts @@ -0,0 +1,196 @@ +import { + Component, + Input, + ViewChild, + ElementRef, + AfterViewInit, + OnInit, +} from '@angular/core'; +import { + AbstractControl, + FormBuilder, + FormControl, + FormGroup, + Validators, +} from '@angular/forms'; +import { ICarrierCreateObject } from '@modules/server.common/interfaces/ICarrier'; +import { isEmpty, pick } from 'underscore'; +import { FormHelpers } from '../../../forms/helpers'; +import * as isUrl from 'is-url'; +import { TranslateService } from '@ngx-translate/core'; +import { first } from 'rxjs/operators'; + +export type CarrierBasicInfo = Pick< + ICarrierCreateObject, + | 'isDeleted' + | 'username' + | 'phone' + | 'firstName' + | 'lastName' + | 'logo' + | 'apartment' + | 'isSharedCarrier' +>; + +@Component({ + selector: 'ea-carrier-basic-info-form', + templateUrl: 'basic-info-form.component.html', + styleUrls: ['basic-info-form.component.scss'], +}) +export class BasicInfoFormComponent implements OnInit, AfterViewInit { + @ViewChild('logoImagePreview') + logoImagePreview: ElementRef; + + uploaderPlaceholder: string; + + @Input() + readonly form: FormGroup; + + @Input() + readonly password?: AbstractControl; + + @Input() + boxShadow; + + constructor(private translateService: TranslateService) {} + + get isActive() { + return this.form.get('isActive'); + } + + get isSharedCarrier() { + return this.form.get('isSharedCarrier'); + } + + get username() { + return this.form.get('username'); + } + + get phone() { + return this.form.get('phone'); + } + + get firstName() { + return this.form.get('firstName'); + } + + get lastName() { + return this.form.get('lastName'); + } + + get logo() { + return this.form.get('logo'); + } + + get showLogoMeta() { + return this.logo && this.logo.value !== ''; + } + + static buildForm(formBuilder: FormBuilder): FormGroup { + // would be used in the parent component and injected into this.form + return formBuilder.group({ + firstName: [ + '', + [ + Validators.required, + Validators.pattern(new RegExp(`^[a-z ,.'-]+$`, 'i')), + ], + ], + lastName: [ + '', + [ + Validators.required, + Validators.pattern(new RegExp(`^[a-z ,.'-]+$`, 'i')), + ], + ], + isActive: [true, Validators.required], + isSharedCarrier: [false], + phone: [ + '', + [ + Validators.required, + Validators.pattern(BasicInfoFormComponent.phoneNumberRegex), + ], + ], + username: ['', Validators.required], + logo: [ + '', + [ + (control: AbstractControl) => { + const imageUrl = control.value; + + if (!isUrl(imageUrl) && !isEmpty(imageUrl)) { + return { invalidImageUrl: true }; + } + + return null; + }, + ], + ], + }); + } + + static buildPasswordForm(formBuilder: FormBuilder): AbstractControl { + return new FormControl('', [Validators.required]); + } + + ngOnInit(): void { + this.getuploaderPlaceholderText(); + } + + ngAfterViewInit() { + this._setupCarrierLogoUrlValidation(); + } + + getValue(): CarrierBasicInfo { + return this.form.getRawValue() as CarrierBasicInfo; + } + + setValue(basicInfo: T) { + FormHelpers.deepMark(this.form, 'dirty'); + + this.form.setValue(pick(basicInfo, Object.keys(this.getValue()))); + } + + getPassword(): string { + // password is not part of carrier + if (!this.password) { + throw new Error("Form doesn't contain password"); + } + return this.password.value as string; + } + + setPassword(value: string) { + this.password.setValue(value); + } + + deleteImg() { + this.logo.setValue(''); + } + + async getuploaderPlaceholderText() { + this.uploaderPlaceholder = await this.translateService + .get('CARRIERS_VIEW.EDIT.PHOTO_URL') + .pipe(first()) + .toPromise(); + + // TODO add translate + this.uploaderPlaceholder += ' (optional)'; + } + + private static phoneNumberRegex = /^[+]*[(]{0,1}[0-9]{1,4}[)]{0,1}[-\s\./0-9x]*$/; + + private _setupCarrierLogoUrlValidation() { + this.logoImagePreview.nativeElement.onload = () => { + if (this.showLogoMeta) { + this.logo.setErrors(null); + } + }; + + this.logoImagePreview.nativeElement.onerror = () => { + if (this.showLogoMeta) { + this.logo.setErrors({ invalidUrl: true }); + } + }; + } +} diff --git a/packages/admin-web-angular/src/app/@shared/carrier/forms/basic-info/index.ts b/packages/admin-web-angular/src/app/@shared/carrier/forms/basic-info/index.ts new file mode 100644 index 0000000..91afc05 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/carrier/forms/basic-info/index.ts @@ -0,0 +1 @@ +export * from './basic-info-form.component'; diff --git a/packages/admin-web-angular/src/app/@shared/carrier/forms/carrier-forms.module.ts b/packages/admin-web-angular/src/app/@shared/carrier/forms/carrier-forms.module.ts new file mode 100644 index 0000000..375fbe4 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/carrier/forms/carrier-forms.module.ts @@ -0,0 +1,18 @@ +import { NgModule } from '@angular/core'; +import { TranslateModule } from '@ngx-translate/core'; +import { FormWizardModule } from '@ever-co/angular2-wizard'; +import { BasicInfoFormComponent } from './basic-info'; +import { ThemeModule } from '@app/@theme'; +import { FileUploaderModule } from '@app/@shared/file-uploader/file-uploader.module'; + +@NgModule({ + imports: [ + ThemeModule, + FormWizardModule, + TranslateModule.forChild(), + FileUploaderModule, + ], + exports: [BasicInfoFormComponent], + declarations: [BasicInfoFormComponent], +}) +export class CarrierFormsModule {} diff --git a/packages/admin-web-angular/src/app/@shared/carrier/forms/index.ts b/packages/admin-web-angular/src/app/@shared/carrier/forms/index.ts new file mode 100644 index 0000000..d77f9d2 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/carrier/forms/index.ts @@ -0,0 +1,2 @@ +export * from './basic-info'; +export * from './carrier-forms.module'; diff --git a/packages/admin-web-angular/src/app/@shared/confirmation-modal/confirmation-modal.component.html b/packages/admin-web-angular/src/app/@shared/confirmation-modal/confirmation-modal.component.html new file mode 100644 index 0000000..8ce7f09 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/confirmation-modal/confirmation-modal.component.html @@ -0,0 +1,15 @@ + + + diff --git a/packages/admin-web-angular/src/app/@shared/confirmation-modal/confirmation-modal.component.scss b/packages/admin-web-angular/src/app/@shared/confirmation-modal/confirmation-modal.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/packages/admin-web-angular/src/app/@shared/confirmation-modal/confirmation-modal.component.ts b/packages/admin-web-angular/src/app/@shared/confirmation-modal/confirmation-modal.component.ts new file mode 100644 index 0000000..080df20 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/confirmation-modal/confirmation-modal.component.ts @@ -0,0 +1,70 @@ +import { + Component, + OnInit, + Output, + EventEmitter, + OnDestroy, +} from '@angular/core'; +import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; +import { Subject } from 'rxjs'; +import { TranslateService } from '@ngx-translate/core'; + +@Component({ + styleUrls: ['./confirmation-modal.component.scss'], + templateUrl: './confirmation-modal.component.html', +}) +export class ConfirmationModalComponent implements OnInit, OnDestroy { + public ngDestroy$ = new Subject(); + + public prefix: string = 'CONFIRM_MODAL.'; + public confirmButton: string = 'YES'; + public cancelButton: string = 'NO'; + public mainText: string; + + @Output() + confirmEvent = new EventEmitter(); + + constructor( + private readonly activeModal: NgbActiveModal, + private translateService: TranslateService + ) { + if (!this.mainText) { + this.mainText = 'ARE_YOU_SURE'; + } + } + + ngOnInit(): void {} + + mainTextTr() { + const forTranslate = this.prefix + this.mainText; + return this._translate(forTranslate); + } + confirmButtonTr() { + const forTranslate = this.prefix + this.confirmButton; + return this._translate(forTranslate); + } + + cancelButtonTr() { + const forTranslate = this.prefix + this.cancelButton; + return this._translate(forTranslate); + } + + cancel() { + this.activeModal.dismiss('canceled'); + } + + private _translate(key: string): string { + let translationResult = ''; + + this.translateService.get(key).subscribe((res) => { + translationResult = res; + }); + + return translationResult; + } + + ngOnDestroy() { + this.ngDestroy$.next(); + this.ngDestroy$.complete(); + } +} diff --git a/packages/admin-web-angular/src/app/@shared/confirmation-modal/confirmation-modal.module.ts b/packages/admin-web-angular/src/app/@shared/confirmation-modal/confirmation-modal.module.ts new file mode 100644 index 0000000..663d733 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/confirmation-modal/confirmation-modal.module.ts @@ -0,0 +1,23 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { ThemeModule } from '../../@theme'; +import { TranslateModule } from '@ngx-translate/core'; +import { ConfirmationModalComponent } from './confirmation-modal.component'; +import { ToasterModule } from 'angular2-toaster'; +import { NbSpinnerModule, NbButtonModule } from '@nebular/theme'; + +const COMPONENTS = [ConfirmationModalComponent]; + +@NgModule({ + imports: [ + CommonModule, + ThemeModule, + TranslateModule.forChild(), + ToasterModule.forRoot(), + NbSpinnerModule, + NbButtonModule, + ], + declarations: COMPONENTS, + exports: COMPONENTS +}) +export class ConfirmationModalModule {} diff --git a/packages/admin-web-angular/src/app/@shared/elapsed-time/elapsed-time.component.html b/packages/admin-web-angular/src/app/@shared/elapsed-time/elapsed-time.component.html new file mode 100644 index 0000000..5a99089 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/elapsed-time/elapsed-time.component.html @@ -0,0 +1,9 @@ +
+

+ {{ 'ELAPSED_TIME.TITLE' | translate }} +

+ +
+ {{ timePasssed.timePassed }} +
+
diff --git a/packages/admin-web-angular/src/app/@shared/elapsed-time/elapsed-time.component.scss b/packages/admin-web-angular/src/app/@shared/elapsed-time/elapsed-time.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/packages/admin-web-angular/src/app/@shared/elapsed-time/elapsed-time.component.spec.ts b/packages/admin-web-angular/src/app/@shared/elapsed-time/elapsed-time.component.spec.ts new file mode 100644 index 0000000..e134dca --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/elapsed-time/elapsed-time.component.spec.ts @@ -0,0 +1,24 @@ +import 'jasmine'; +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { ElapsedTimeComponent } from './elapsed-time.component'; + +describe('ElapsedTimeComponent', () => { + let component: ElapsedTimeComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ElapsedTimeComponent], + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(ElapsedTimeComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/packages/admin-web-angular/src/app/@shared/elapsed-time/elapsed-time.component.ts b/packages/admin-web-angular/src/app/@shared/elapsed-time/elapsed-time.component.ts new file mode 100644 index 0000000..4afc563 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/elapsed-time/elapsed-time.component.ts @@ -0,0 +1,68 @@ +import { Component, OnInit } from '@angular/core'; + +@Component({ + selector: 'ea-elapsed-time', + templateUrl: './elapsed-time.component.html', + styleUrls: ['./elapsed-time.component.scss'], +}) +export class ElapsedTimeComponent implements OnInit { + public timePasssed: any = { timePassed: '00 : 00' }; + public timer: any; + + constructor() {} + + ngOnInit() { + if (this._getEndTime) { + this.timePasssed = JSON.parse(this._getEndTime); + } else { + this.timer = setInterval(this.updateTime, 1000, [this.timePasssed]); + } + } + + updateTime(comp: any) { + const currDate = new Date(); + + const startDate = new Date( + JSON.parse(localStorage.getItem('simulationStartDate')) + ); + + if (startDate) { + let diff = (currDate.getTime() - startDate.getTime()) / 1000; + + const hoursPassed = Math.floor(diff / 3600); + + diff = diff - hoursPassed * 3600; + + const minutesPassed = Math.floor(diff / 60); + + const secondsPassed = Number( + (diff - minutesPassed * 60).toFixed(0) + ); + + let minutesPassedStr = ''; + let secondsPassedStr = ''; + let hoursPassedStr = ''; + + if (hoursPassed >= 1) { + hoursPassedStr = hoursPassed.toString() + ' : '; + } + + minutesPassedStr = + minutesPassed < 10 + ? '0' + minutesPassed + : minutesPassed.toString(); + + secondsPassedStr = + secondsPassed < 10 + ? '0' + secondsPassed + : secondsPassed.toString(); + + comp[0].timePassed = + hoursPassedStr + minutesPassedStr + ' : ' + secondsPassedStr; + } + } + + private get _getEndTime() { + return localStorage.getItem('simulationEndTime'); + } +} diff --git a/packages/admin-web-angular/src/app/@shared/elapsed-time/elapsed-time.module.ts b/packages/admin-web-angular/src/app/@shared/elapsed-time/elapsed-time.module.ts new file mode 100644 index 0000000..cd742b5 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/elapsed-time/elapsed-time.module.ts @@ -0,0 +1,12 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { FormsModule } from '@angular/forms'; +import { TranslateModule } from '@ngx-translate/core'; +import { ElapsedTimeComponent } from './elapsed-time.component'; + +@NgModule({ + imports: [CommonModule, FormsModule, TranslateModule.forChild()], + declarations: [ElapsedTimeComponent], + exports: [ElapsedTimeComponent] +}) +export class ElapsedTimeModule {} diff --git a/packages/admin-web-angular/src/app/@shared/file-uploader/file-uploader.component.html b/packages/admin-web-angular/src/app/@shared/file-uploader/file-uploader.component.html new file mode 100644 index 0000000..5ef6b4a --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/file-uploader/file-uploader.component.html @@ -0,0 +1,24 @@ +
+ + + + +
diff --git a/packages/admin-web-angular/src/app/@shared/file-uploader/file-uploader.component.scss b/packages/admin-web-angular/src/app/@shared/file-uploader/file-uploader.component.scss new file mode 100644 index 0000000..003df9e --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/file-uploader/file-uploader.component.scss @@ -0,0 +1,18 @@ +.row.file-uploader-container { + padding-left: 14px; + padding-right: 16px; + + button { + padding: 0 !important; + border-radius: 0px; + border-top-right-radius: 0.375rem; + border-bottom-right-radius: 0.375rem; + margin: 0; + } + + input { + border-radius: 0px !important; + border-top-left-radius: 0.375rem !important; + border-bottom-left-radius: 0.375rem !important; + } +} diff --git a/packages/admin-web-angular/src/app/@shared/file-uploader/file-uploader.component.ts b/packages/admin-web-angular/src/app/@shared/file-uploader/file-uploader.component.ts new file mode 100644 index 0000000..777abe1 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/file-uploader/file-uploader.component.ts @@ -0,0 +1,161 @@ +import { + Component, + Input, + Output, + EventEmitter, + ViewChild, +} from '@angular/core'; +import { FileUploader, FileUploaderOptions } from 'ng2-file-upload'; +import { environment } from 'environments/environment'; +import { ProductLocalesService } from '@modules/client.common.angular2/locale/product-locales.service'; +import { IProductImage } from '@modules/server.common/interfaces/IProduct'; +import { NgModel } from '@angular/forms'; + +@Component({ + selector: 'e-cu-file-uploader', + templateUrl: './file-uploader.component.html', + styleUrls: ['./file-uploader.component.scss'], +}) +export class FileUploaderComponent { + @ViewChild('shownInput', { static: true }) + shownInput: NgModel; + + @Input() + placeholder: string; + @Input() + name: string; + @Input() + fileUrl: string; + @Input() + customClass: string; + @Input() + locale: string; + + @Output() + uploadedImgUrl: EventEmitter = new EventEmitter(); + @Output() + uploadedImgObj: EventEmitter = new EventEmitter< + IProductImage + >(); + + uploader: FileUploader; + + private oldValue: string; + + constructor( + public readonly localeTranslateService: ProductLocalesService + ) {} + + ngOnInit(): void { + this._uploaderConfig(); + } + + async imageUrlChanged() { + const newValue = + this.fileUrl && + this.fileUrl.replace(this.oldValue || '', '').trim(); + + if (this.uploader.queue.length > 0) { + this.uploader.queue[this.uploader.queue.length - 1].upload(); + } else { + const image = await this._setupImage(newValue); + + this.uploadedImgUrl.emit(this.fileUrl); + this.uploadedImgObj.emit(image); + this.oldValue = this.fileUrl; + } + + this.uploader.onSuccessItem = ( + item: any, + response: string, + status: number + ) => { + const data = JSON.parse(response); + this.fileUrl = data.url; + const locale = this.locale; + const width = data.width; + const height = data.height; + const orientation = width !== height ? (width > height ? 2 : 1) : 0; + const url = data.url; + + const newImage = { + locale, + url, + width, + height, + orientation, + }; + + this.uploadedImgUrl.emit(data.url); + this.uploadedImgObj.emit(newImage); + this.oldValue = this.fileUrl; + }; + } + + private _uploaderConfig() { + const uploaderOptions: FileUploaderOptions = { + url: environment.API_FILE_UPLOAD_URL, + + isHTML5: true, + removeAfterUpload: true, + headers: [ + { + name: 'X-Requested-With', + value: 'XMLHttpRequest', + }, + ], + }; + this.uploader = new FileUploader(uploaderOptions); + + this.uploader.onBuildItemForm = ( + fileItem: any, + form: FormData + ): any => { + form.append('upload_preset', 'everbie-products-images'); + + let tags = 'myphotoalbum'; + + if (this.name) { + form.append('context', `photo=${this.name}`); + tags = `myphotoalbum,${this.name}`; + } + + form.append('folder', 'angular_sample'); + form.append('tags', tags); + form.append('file', fileItem); + + fileItem.withCredentials = false; + + return { fileItem, form }; + }; + } + + private async _setupImage(imgUrl) { + try { + const img = await this._getImageMeta(imgUrl); + const width = img['width']; + const height = img['height']; + const orientation = width !== height ? (width > height ? 2 : 1) : 0; + const locale = this.locale; + const url = imgUrl; + return { + locale, + url, + width, + height, + orientation, + }; + } catch (error) { + return error; + } + } + + private async _getImageMeta(url) { + return new Promise((resolve, reject) => { + const img = new Image(); + img.onload = () => resolve(img); + img.onerror = (err) => reject(false); + img.src = url; + }); + } +} diff --git a/packages/admin-web-angular/src/app/@shared/file-uploader/file-uploader.module.ts b/packages/admin-web-angular/src/app/@shared/file-uploader/file-uploader.module.ts new file mode 100644 index 0000000..d778118 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/file-uploader/file-uploader.module.ts @@ -0,0 +1,20 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { TranslateModule } from '@ngx-translate/core'; +import { FileUploadModule } from 'ng2-file-upload'; +import { FileUploaderComponent } from './file-uploader.component'; +import { FormsModule } from '@angular/forms'; +import { NbButtonModule } from '@nebular/theme'; + +@NgModule({ + imports: [ + CommonModule, + FormsModule, + TranslateModule.forChild(), + FileUploadModule, + NbButtonModule, + ], + exports: [FileUploaderComponent], + declarations: [FileUploaderComponent], +}) +export class FileUploaderModule {} diff --git a/packages/admin-web-angular/src/app/@shared/forms/google-map/google-map.component.scss b/packages/admin-web-angular/src/app/@shared/forms/google-map/google-map.component.scss new file mode 100644 index 0000000..fd6fcd3 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/forms/google-map/google-map.component.scss @@ -0,0 +1,4 @@ +.g-map { + height: 240px; + width: 100%; +} diff --git a/packages/admin-web-angular/src/app/@shared/forms/google-map/google-map.component.ts b/packages/admin-web-angular/src/app/@shared/forms/google-map/google-map.component.ts new file mode 100644 index 0000000..a11196c --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/forms/google-map/google-map.component.ts @@ -0,0 +1,111 @@ +import { ViewChild, Component, Input, OnInit, OnDestroy } from '@angular/core'; +import { Observable, Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; + +@Component({ + selector: 'google-map', + styleUrls: ['./google-map.component.scss'], + template: `
`, +}) +export class GoogleMapComponent implements OnInit, OnDestroy { + @ViewChild('gmap', { static: true }) + mapElement: any; + + @Input() + mapTypeEvent: Observable; + + @Input() + mapCoordEvent: Observable; + + @Input() + mapGeometryEvent: Observable< + google.maps.places.PlaceGeometry | google.maps.GeocoderGeometry + >; + + map: google.maps.Map; + + private _mapMarker: google.maps.Marker; + + private _ngDestroy$ = new Subject(); + + ngOnInit() { + this._setupGoogleMap(); + this._listenForMapType(); + this._listenForMapCoordinates(); + this._listenForMapGeometry(); + } + + private _navigateTo( + location: google.maps.LatLng | google.maps.LatLngLiteral + ) { + this.map.setCenter(location); + } + + private _listenForMapGeometry() { + this.mapGeometryEvent + .pipe(takeUntil(this._ngDestroy$)) + .subscribe((geometry) => { + if (geometry.viewport) { + this.map.fitBounds(geometry.viewport); + } else { + this.map.setCenter(geometry.location); + this.map.setZoom(17); + } + }); + } + + private _listenForMapType() { + if (this.mapTypeEvent) { + this.mapTypeEvent + .pipe(takeUntil(this._ngDestroy$)) + .subscribe((mapType: string) => { + this.map.setMapTypeId(mapType); + }); + } + } + + private _listenForMapCoordinates() { + this.mapCoordEvent + .pipe(takeUntil(this._ngDestroy$)) + .subscribe((location) => { + this._navigateTo(location); + this._addMapMarker(location); + }); + } + + private _setupGoogleMap() { + const optionsMap = { + center: new google.maps.LatLng(0, 0), + zoom: 14, + mapTypeId: google.maps.MapTypeId.ROADMAP, + }; + + this.map = new google.maps.Map( + this.mapElement.nativeElement, + optionsMap + ); + } + + private _addMapMarker( + location: google.maps.LatLng | google.maps.LatLngLiteral + ) { + this._clearMarker(); + + this._mapMarker = new google.maps.Marker({ + map: this.map, + position: location, + // icon: 'assets/images/google-map-customer-marker.png' + }); + } + + private _clearMarker() { + if (this._mapMarker) { + this._mapMarker.setMap(null); + } + } + + ngOnDestroy() { + this._ngDestroy$.next(); + this._ngDestroy$.complete(); + } +} diff --git a/packages/admin-web-angular/src/app/@shared/forms/google-map/google-map.module.ts b/packages/admin-web-angular/src/app/@shared/forms/google-map/google-map.module.ts new file mode 100644 index 0000000..2dbc19c --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/forms/google-map/google-map.module.ts @@ -0,0 +1,16 @@ +import { NgModule } from '@angular/core'; +import { GoogleMapComponent } from './google-map.component'; +import { AgmCoreModule } from '@agm/core'; +import { environment } from 'environments/environment'; + +@NgModule({ + imports: [ + AgmCoreModule.forRoot({ + apiKey: environment.GOOGLE_MAPS_API_KEY, + libraries: ['drawing'], + }), + ], + declarations: [GoogleMapComponent], + exports: [GoogleMapComponent], +}) +export class GoogleMapModule {} diff --git a/packages/admin-web-angular/src/app/@shared/forms/helpers.ts b/packages/admin-web-angular/src/app/@shared/forms/helpers.ts new file mode 100644 index 0000000..f15ecd3 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/forms/helpers.ts @@ -0,0 +1,26 @@ +import { FormArray, FormGroup } from '@angular/forms'; +import * as _ from 'underscore.string'; + +export class FormHelpers { + /** + * Loop and mark all it has + * + * @param {FormGroup} formGroup + * @param markAs + * @param opts + * + */ + static deepMark( + formGroup: FormGroup | FormArray, + markAs: 'touched' | 'untouched' | 'dirty' | 'pristine' | 'pending', + opts = { onlySelf: false } + ): void { + Object.values(formGroup.controls).forEach((c) => { + if (c instanceof FormGroup || c instanceof FormArray) { + FormHelpers.deepMark(c, markAs, opts); + } else { + c[`markAs${_.capitalize(markAs)}`](opts); + } + }); + } +} diff --git a/packages/admin-web-angular/src/app/@shared/forms/location/index.ts b/packages/admin-web-angular/src/app/@shared/forms/location/index.ts new file mode 100644 index 0000000..62baa8d --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/forms/location/index.ts @@ -0,0 +1,2 @@ +export * from './location-form.component'; +export * from './location-form.module'; diff --git a/packages/admin-web-angular/src/app/@shared/forms/location/location-form.component.html b/packages/admin-web-angular/src/app/@shared/forms/location/location-form.component.html new file mode 100644 index 0000000..8336857 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/forms/location/location-form.component.html @@ -0,0 +1,178 @@ +
+
+
+ + +
+
+ +
+ +
+
+ +
+ + +
+ + +
+ {{ 'SHARED.FORMS.ERRORS.COUNTRY_REQUIRED' | translate }}! +
+
+ +
+ + +
+ {{ 'SHARED.FORMS.ERRORS.CITY_REQUIRED' | translate }}! +
+
+ +
+ +
+
+ +
+ + +
+ + +
+ {{ + 'SHARED.FORMS.ERRORS.STREET_ADDRESS_REQUIRED' + | translate + }}! +
+
+
+ +
+ + +
+
+
+ + +
+ {{ + 'SHARED.FORMS.ERRORS.HOUSE_REQUIRED' + | translate + }}! +
+
+ +
+ +
+
+
+
+ +
+ +
+ +
+
+ +
+ +
+
+
+ +
+ +
+ +
+
+
+
+
+
diff --git a/packages/admin-web-angular/src/app/@shared/forms/location/location-form.component.scss b/packages/admin-web-angular/src/app/@shared/forms/location/location-form.component.scss new file mode 100644 index 0000000..f04c74b --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/forms/location/location-form.component.scss @@ -0,0 +1,29 @@ +.search-addon-wrapper { + position: relative; + width: 46px; + + i { + font-size: 0.8em; + color: black; + opacity: 0.6; + width: 46px; + position: absolute; + top: 50%; + transform: translateY(-50%); + } +} + +.input-group-prepend { + .input-group-text { + background-color: #f7f9fc !important; + border-color: #edf1f7 !important; + } +} + +ul { + padding-left: 0 !important !important; +} + +.control-label { + cursor: pointer; +} diff --git a/packages/admin-web-angular/src/app/@shared/forms/location/location-form.component.ts b/packages/admin-web-angular/src/app/@shared/forms/location/location-form.component.ts new file mode 100644 index 0000000..932df5c --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/forms/location/location-form.component.ts @@ -0,0 +1,461 @@ +import { + Component, + Input, + EventEmitter, + Output, + ViewChild, + ElementRef, + AfterViewInit, +} from '@angular/core'; +import { + AbstractControl, + FormArray, + FormBuilder, + FormGroup, + Validators, +} from '@angular/forms'; + +import { IGeoLocationCreateObject } from '@modules/server.common/interfaces/IGeoLocation'; +import { + Country, + CountryName, + getCountryName, + countriesIdsToNamesArray, +} from '@modules/server.common/entities/GeoLocation'; +import { FormHelpers } from '../helpers'; + +import { pick, isEmpty } from 'underscore'; +import { ToasterService } from 'angular2-toaster'; + +import { countries } from '@modules/server.common/data/abbreviation-to-country'; +import { environment } from 'environments/environment'; + +@Component({ + selector: 'ea-location-form', + styleUrls: ['./location-form.component.scss'], + templateUrl: 'location-form.component.html', +}) +export class LocationFormComponent implements AfterViewInit { + @Input() + readonly form: FormGroup; + @Input() + readonly apartment?: AbstractControl; + @Input() + showAutocompleteSearch: boolean = false; + + @Output() + mapCoordinatesEmitter = new EventEmitter< + google.maps.LatLng | google.maps.LatLngLiteral + >(); + + @Output() + mapGeometryEmitter = new EventEmitter< + google.maps.places.PlaceGeometry | google.maps.GeocoderGeometry + >(); + + @ViewChild('autocomplete') + searchElement: ElementRef; + + public showCoordinates: boolean = false; + + static COUNTRIES: Array<{ + id: Country; + name: CountryName; + }> = countriesIdsToNamesArray; + + private _lastUsedAddressText: string; + private _lat: number; + private _lng: number; + + constructor(private readonly toasterService: ToasterService) {} + + ngAfterViewInit() { + this._initGoogleAutocompleteApi(); + } + + get countries() { + return LocationFormComponent.COUNTRIES; + } + + get isCountryValid(): boolean { + return ( + this.countryId.errors && + (this.countryId.dirty || this.countryId.touched) + ); + } + + get isCityValid(): boolean { + return this.city.errors && (this.city.dirty || this.city.touched); + } + + get isStreetAddressValid(): boolean { + return ( + this.streetAddress.errors && + (this.streetAddress.dirty || this.streetAddress.touched) + ); + } + + get isHouseValid(): boolean { + return this.house.errors && (this.house.dirty || this.house.touched); + } + + get isLocationValid(): boolean { + return ( + this.coordinates.errors && + (this.coordinates.dirty || this.coordinates.touched) + ); + } + + get countryId() { + return this.form.get('countryId'); + } + + get city() { + return this.form.get('city'); + } + + get streetAddress() { + return this.form.get('streetAddress'); + } + + get house() { + return this.form.get('house'); + } + + get postcode() { + return this.form.get('postcode'); + } + + get coordinates() { + return this.form.get('loc').get('coordinates') as FormArray; + } + + static buildForm(formBuilder: FormBuilder): FormGroup { + const form = formBuilder.group({ + countryId: [ + Country.US, + [ + (ctrl) => + LocationFormComponent.COUNTRIES.map( + (c) => c.id + ).includes(ctrl.value), + ], + ], + city: ['', [Validators.required]], + streetAddress: ['', [Validators.required]], + house: ['', [Validators.required]], + postcode: [''], + + loc: formBuilder.group({ + type: ['Point'], + coordinates: formBuilder.array([null, null]), + }), + }); + + return form; + } + + static buildApartmentForm(formBuilder: FormBuilder): AbstractControl { + return formBuilder.control(''); + } + + onAddressChanges() { + if (this.showAutocompleteSearch) { + this._tryFindNewAddress(); + } + } + + onCoordinatesChanged() { + if (this.showAutocompleteSearch) { + this._tryFindNewCoordinates(); + } + } + + getValue(): IGeoLocationCreateObject { + const location = this.form.getRawValue() as IGeoLocationCreateObject; + if (!location.postcode) { + delete location.postcode; + } + return location; + } + + getApartment(): string { + // apartment is not part of geo location + if (!this.apartment) { + throw new Error("Form doesn't contain apartment"); + } + return this.apartment.value as string; + } + + setValue(geoLocation: T) { + FormHelpers.deepMark(this.form, 'dirty'); + + this.form.setValue({ + postcode: geoLocation.postcode || '', + ...(pick(geoLocation, Object.keys(this.getValue())) as any), + }); + + // This setup the form and map with new received values. + this._tryFindNewCoordinates(); + } + + setApartment(apartment: string) { + this.apartment.setValue(apartment); + } + + toggleShowCoordinates() { + this.showCoordinates = !this.showCoordinates; + } + + setDefaultCoords() { + const lat = environment.DEFAULT_LATITUDE; + const lng = environment.DEFAULT_LONGITUDE; + + if (lat && lng) { + this.coordinates.setValue([lat, lng]); + this.onCoordinatesChanged(); + } + } + + private _applyFormattedAddress(address: string) { + if (this.searchElement) { + this.searchElement.nativeElement.value = address; + } + } + + private _tryFindNewAddress() { + const house = this.house.value; + const streetAddress = this.streetAddress.value; + const city = this.city.value; + const countryName = getCountryName(+this.countryId.value); + + if ( + isEmpty(streetAddress) || + isEmpty(house) || + isEmpty(city) || + isEmpty(countryName) + ) { + return; + } + + const newAddress = `${house}${streetAddress}${city}${countryName}`; + + if (newAddress !== this._lastUsedAddressText) { + this._lastUsedAddressText = newAddress; + + const geocoder = new google.maps.Geocoder(); + + geocoder.geocode( + { + address: `${streetAddress} ${house}, ${city}`, + componentRestrictions: { + country: countryName, + }, + }, + (results, status) => { + if (status === google.maps.GeocoderStatus.OK) { + const formattedAddress = results[0].formatted_address; + const place: google.maps.GeocoderResult = results[0]; + + this._applyNewPlaceOnTheMap(place); + this._applyFormattedAddress(formattedAddress); + } + } + ); + } + } + + private _tryFindNewCoordinates() { + const formCoordinates = this.coordinates.value; + this._lat = formCoordinates[0]; + this._lng = formCoordinates[1]; + + const geocoder = new google.maps.Geocoder(); + geocoder.geocode( + { + location: { lng: this._lng, lat: this._lat }, + }, + (results, status) => { + if (status === google.maps.GeocoderStatus.OK) { + const formattedAddress = results[0].formatted_address; + const place = results[0]; + + const useGeometryLatLng = false; + this._applyNewPlaceOnTheMap(place, useGeometryLatLng); + this._applyFormattedAddress(formattedAddress); + } + } + ); + } + + private _emitCoordinates( + location: google.maps.LatLng | google.maps.LatLngLiteral + ) { + this.mapCoordinatesEmitter.emit(location); + } + + private _emitGeometry( + geometry: + | google.maps.places.PlaceGeometry + | google.maps.GeocoderGeometry + ) { + this.mapGeometryEmitter.emit(geometry); + } + + private _popInvalidAddressMessage() { + this.toasterService.pop( + 'warning', + 'Invalid address, please try again!' + ); + } + + private _setupGoogleAutocompleteOptions( + autocomplete: google.maps.places.Autocomplete + ) { + autocomplete['setFields'](['address_components', 'geometry']); + } + + private _applyNewPlaceOnTheMap( + place: google.maps.places.PlaceResult | google.maps.GeocoderResult, + useGeometryLatLng: boolean = true + ) { + if (place.geometry === undefined || place.geometry === null) { + this._popInvalidAddressMessage(); + return; + } + + if (useGeometryLatLng) { + const loc = place.geometry.location; + this._lat = loc.lat(); + this._lng = loc.lng(); + } + + // If the place has a geometry, then present it on a map. + this._emitGeometry(place.geometry); + + this._emitCoordinates(new google.maps.LatLng(this._lat, this._lng)); + + this._gatherAddressInformation(place); + } + + private _listenForGoogleAutocompleteAddressChanges( + autocomplete: google.maps.places.Autocomplete + ) { + autocomplete.addListener('place_changed', (_) => { + const place: google.maps.places.PlaceResult = autocomplete.getPlace(); + this._applyNewPlaceOnTheMap(place); + }); + } + + private _gatherAddressInformation( + locationResult: + | google.maps.GeocoderResult + | google.maps.places.PlaceResult + ) { + const longName = 'long_name'; + const shortName = 'short_name'; + + const neededAddressTypes = { + country: shortName, + locality: longName, + // 'neighborhood' is not need for now + // neighborhood: longName, + route: longName, + intersection: longName, + street_number: longName, + postal_code: longName, + administrative_area_level_1: shortName, + administrative_area_level_2: shortName, + administrative_area_level_3: shortName, + administrative_area_level_4: shortName, + administrative_area_level_5: shortName, + }; + + let streetName = ''; + let streetNumber = ''; // is house number also + let countryId = ''; + let postcode = ''; + let city = ''; + + locationResult.address_components.forEach((address) => { + const addressType = address.types[0]; + const addressTypeKey = neededAddressTypes[addressType]; + + const val = address[addressTypeKey]; + + switch (addressType) { + case 'country': + countryId = val; + break; + case 'locality': + case 'administrative_area_level_1': + case 'administrative_area_level_2': + case 'administrative_area_level_3': + case 'administrative_area_level_4': + case 'administrative_area_level_5': + if (city === '') { + city = val; + } + break; + case 'route': + case 'intersection': + if (streetName === '') { + streetName = val; + } + break; + case 'street_number': + streetNumber = val; + break; + case 'postal_code': + postcode = val; + break; + } + }); + + this._setFormLocationValues( + countryId, + city, + streetName, + streetNumber, + postcode + ); + } + + private _initGoogleAutocompleteApi() { + if (this.searchElement) { + const autocomplete = new google.maps.places.Autocomplete( + this.searchElement.nativeElement + ); + + this._setupGoogleAutocompleteOptions(autocomplete); + + this._listenForGoogleAutocompleteAddressChanges(autocomplete); + } + } + + private _setFormLocationValues( + countryId, + city, + streetName, + streetNumber, + postcode + ) { + if (!isEmpty(countryId)) { + this.countryId.setValue(Country[countryId]); + } + if (!isEmpty(city)) { + this.city.setValue(city); + } + if (!isEmpty(postcode)) { + this.postcode.setValue(postcode); + } + if (!isEmpty(streetName)) { + this.streetAddress.setValue(streetName); + } + if (!isEmpty(streetNumber)) { + this.house.setValue(streetNumber); + } + + this.coordinates.setValue([this._lat, this._lng]); + } +} diff --git a/packages/admin-web-angular/src/app/@shared/forms/location/location-form.module.ts b/packages/admin-web-angular/src/app/@shared/forms/location/location-form.module.ts new file mode 100644 index 0000000..0cff1e1 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/forms/location/location-form.module.ts @@ -0,0 +1,18 @@ +import { NgModule } from '@angular/core'; +import { LocationFormComponent } from './location-form.component'; +import { TranslateModule } from '@ngx-translate/core'; +import { FormWizardModule } from '@ever-co/angular2-wizard'; +import { ThemeModule } from '../../../@theme'; +import { AgmCoreModule } from '@agm/core'; + +@NgModule({ + imports: [ + ThemeModule, + FormWizardModule, + TranslateModule.forChild(), + AgmCoreModule, + ], + exports: [LocationFormComponent], + declarations: [LocationFormComponent], +}) +export class LocationFormModule {} diff --git a/packages/admin-web-angular/src/app/@shared/forms/password/update/index.ts b/packages/admin-web-angular/src/app/@shared/forms/password/update/index.ts new file mode 100644 index 0000000..5b5df97 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/forms/password/update/index.ts @@ -0,0 +1,2 @@ +export * from './password-update-form.component'; +export * from './password-update-form.module'; diff --git a/packages/admin-web-angular/src/app/@shared/forms/password/update/password-update-form.component.html b/packages/admin-web-angular/src/app/@shared/forms/password/update/password-update-form.component.html new file mode 100644 index 0000000..e69de29 diff --git a/packages/admin-web-angular/src/app/@shared/forms/password/update/password-update-form.component.ts b/packages/admin-web-angular/src/app/@shared/forms/password/update/password-update-form.component.ts new file mode 100644 index 0000000..74f1acc --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/forms/password/update/password-update-form.component.ts @@ -0,0 +1,11 @@ +import { Component, OnInit } from '@angular/core'; + +@Component({ + selector: 'ea-password-update-form', + templateUrl: 'password-update-form.component.html', +}) +export class PasswordUpdateFormComponent implements OnInit { + constructor() {} + + ngOnInit() {} +} diff --git a/packages/admin-web-angular/src/app/@shared/forms/password/update/password-update-form.module.ts b/packages/admin-web-angular/src/app/@shared/forms/password/update/password-update-form.module.ts new file mode 100644 index 0000000..a48bcc4 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/forms/password/update/password-update-form.module.ts @@ -0,0 +1,12 @@ +import { NgModule } from '@angular/core'; +import { TranslateModule } from '@ngx-translate/core'; +import { FormWizardModule } from '@ever-co/angular2-wizard'; +import { ThemeModule } from '../../../../@theme'; +import { PasswordUpdateFormComponent } from './password-update-form.component'; + +@NgModule({ + imports: [ThemeModule, FormWizardModule, TranslateModule.forChild()], + exports: [PasswordUpdateFormComponent], + declarations: [PasswordUpdateFormComponent], +}) +export class LocationFormModule {} diff --git a/packages/admin-web-angular/src/app/@shared/invite/by-code/by-code-modal.component.html b/packages/admin-web-angular/src/app/@shared/invite/by-code/by-code-modal.component.html new file mode 100644 index 0000000..bb5502d --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/invite/by-code/by-code-modal.component.html @@ -0,0 +1,29 @@ + + + diff --git a/packages/admin-web-angular/src/app/@shared/invite/by-code/by-code-modal.component.ts b/packages/admin-web-angular/src/app/@shared/invite/by-code/by-code-modal.component.ts new file mode 100644 index 0000000..a170d99 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/invite/by-code/by-code-modal.component.ts @@ -0,0 +1,64 @@ +import { Component } from '@angular/core'; +import { ILocation } from '@modules/server.common/interfaces/IGeoLocation'; +import { InviteRouter } from '@modules/client.common.angular2/routers/invite-router.service'; +import { UserAuthRouter } from '@modules/client.common.angular2/routers/user-auth-router.service'; +import { ToasterService } from 'angular2-toaster'; +import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; +import { first } from 'rxjs/operators'; + +@Component({ + templateUrl: './by-code-modal.component.html', +}) +export class ByCodeModalComponent { + public code: number; + public location: ILocation; + + constructor( + private readonly inviteRouter: InviteRouter, + private readonly userAuthRouter: UserAuthRouter, + private readonly toasterService: ToasterService, + private readonly activeModal: NgbActiveModal + ) {} + + closeModal() { + this.activeModal.close(); + } + + async login() { + if (this.code > 999 && this.code < 10000 && this.location) { + try { + const invite = await this.inviteRouter + .getByCode({ + location: this.location, + inviteCode: this.code.toString(), + }) + .pipe(first()) + .toPromise(); + + if (invite) { + const user = await this.userAuthRouter.register({ + user: { + apartment: invite.apartment, + geoLocation: invite.geoLocation, + }, + }); + this.toasterService.pop( + 'success', + `Successful logen with code` + ); + this.activeModal.close(user); + } else { + this.invalidCodeToaster(); + } + } catch (error) { + this.toasterService.pop('error', `Error: "${error.message}"`); + } + } else { + this.invalidCodeToaster(); + } + } + + private invalidCodeToaster() { + this.toasterService.pop('error', `Invalid code.`); + } +} diff --git a/packages/admin-web-angular/src/app/@shared/invite/by-code/by-code-modal.module.ts b/packages/admin-web-angular/src/app/@shared/invite/by-code/by-code-modal.module.ts new file mode 100644 index 0000000..09d7b25 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/invite/by-code/by-code-modal.module.ts @@ -0,0 +1,12 @@ +import { NgModule } from '@angular/core'; +import { TranslateModule } from '@ngx-translate/core'; +import { ThemeModule } from '../../../@theme/theme.module'; +import { ByCodeModalComponent } from './by-code-modal.component'; +import { NbButtonModule } from '@nebular/theme'; + +@NgModule({ + imports: [ThemeModule, TranslateModule.forChild(), NbButtonModule], + exports: [ByCodeModalComponent], + declarations: [ByCodeModalComponent] +}) +export class ByCodeModalModule {} diff --git a/packages/admin-web-angular/src/app/@shared/invite/invite-request/invite-request-modal.component.html b/packages/admin-web-angular/src/app/@shared/invite/invite-request/invite-request-modal.component.html new file mode 100644 index 0000000..0ea81fb --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/invite/invite-request/invite-request-modal.component.html @@ -0,0 +1,36 @@ +
+ + + +
diff --git a/packages/admin-web-angular/src/app/@shared/invite/invite-request/invite-request-modal.component.scss b/packages/admin-web-angular/src/app/@shared/invite/invite-request/invite-request-modal.component.scss new file mode 100644 index 0000000..5006488 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/invite/invite-request/invite-request-modal.component.scss @@ -0,0 +1,7 @@ +.invite-request-modal { + margin: 0 !important; + .modal-body { + padding-left: 0; + padding-right: 0; + } +} diff --git a/packages/admin-web-angular/src/app/@shared/invite/invite-request/invite-request-modal.component.ts b/packages/admin-web-angular/src/app/@shared/invite/invite-request/invite-request-modal.component.ts new file mode 100644 index 0000000..883f7c0 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/invite/invite-request/invite-request-modal.component.ts @@ -0,0 +1,81 @@ +import { Component, ViewChild, EventEmitter } from '@angular/core'; +import { LocationFormComponent } from '@app/@shared/forms/location'; +import { FormGroup, FormControl, FormBuilder } from '@angular/forms'; +import { ToasterService } from 'angular2-toaster'; +import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; +import { IInviteRequestCreateObject } from '@modules/server.common/interfaces/IInviteRequest'; +import { InviteRequestRouter } from '@modules/client.common.angular2/routers/invite-request-router.service'; + +@Component({ + selector: 'ea-invite-request-modal', + templateUrl: './invite-request-modal.component.html', + styleUrls: ['/invite-request-modal.component.scss'], +}) +export class InviteRequestModalComponent { + @ViewChild('locationForm', { static: true }) + locationForm: LocationFormComponent; + + mapTypeEmitter = new EventEmitter(); + mapCoordEmitter = new EventEmitter(); + mapGeometryEmitter = new EventEmitter(); + + readonly form: FormGroup = this.formBuilder.group({ + location: LocationFormComponent.buildForm(this.formBuilder), + apartment: LocationFormComponent.buildApartmentForm(this.formBuilder), + }); + + readonly location = this.form.get('location') as FormControl; + readonly apartment = this.form.get('apartment') as FormControl; + + constructor( + private readonly toasterService: ToasterService, + private readonly activeModal: NgbActiveModal, + private readonly formBuilder: FormBuilder, + private readonly inviteRequestRouter: InviteRequestRouter + ) {} + + onCoordinatesChanges(coords: number[]) { + this.mapCoordEmitter.emit(coords); + } + + onGeometrySend(geometry: any) { + this.mapGeometryEmitter.emit(geometry); + } + + emitMapType(mapType: string) { + this.mapTypeEmitter.emit(mapType); + } + + async create() { + try { + const inciteRequest = await this.inviteRequestRouter.create( + this.getInviteRequestCreateObj() + ); + this.toasterService.pop( + 'success', + `Successful create invite request` + ); + this.activeModal.close(inciteRequest); + } catch (err) { + this.toasterService.pop( + 'error', + `Error in creating invite request: "${err.message}"` + ); + } + } + + closeModal() { + this.activeModal.close(); + } + + private getInviteRequestCreateObj(): IInviteRequestCreateObject { + // GeoJSON use reversed order for coordinates from our implementation. + // we use lat => lng but GeoJSON use lng => lat. + const geoLocationInput = this.locationForm.getValue(); + geoLocationInput.loc.coordinates.reverse(); + return { + geoLocation: geoLocationInput, + apartment: this.locationForm.getApartment() || ' ', + }; + } +} diff --git a/packages/admin-web-angular/src/app/@shared/invite/invite-request/invite-request-modal.module.ts b/packages/admin-web-angular/src/app/@shared/invite/invite-request/invite-request-modal.module.ts new file mode 100644 index 0000000..f68052e --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/invite/invite-request/invite-request-modal.module.ts @@ -0,0 +1,22 @@ +import { NgModule } from '@angular/core'; +import { TranslateModule } from '@ngx-translate/core'; +import { FormWizardModule } from '@ever-co/angular2-wizard'; +import { ThemeModule } from '../../../@theme/theme.module'; +import { LocationFormModule } from '../../forms/location'; +import { GoogleMapModule } from '../../forms/google-map/google-map.module'; +import { InviteRequestModalComponent } from './invite-request-modal.component'; +import { NbButtonModule } from '@nebular/theme'; + +@NgModule({ + imports: [ + ThemeModule, + FormWizardModule, + TranslateModule.forChild(), + LocationFormModule, + GoogleMapModule, + NbButtonModule, + ], + exports: [InviteRequestModalComponent], + declarations: [InviteRequestModalComponent] +}) +export class InviteRequestModalModule {} diff --git a/packages/admin-web-angular/src/app/@shared/json-modal/json-modal.component.html b/packages/admin-web-angular/src/app/@shared/json-modal/json-modal.component.html new file mode 100644 index 0000000..a742783 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/json-modal/json-modal.component.html @@ -0,0 +1,11 @@ + +
+ {{ title | translate }} + "{{ subTitle }}" + {{ 'Details' | translate }} +
+
+ + +
+
diff --git a/packages/admin-web-angular/src/app/@shared/json-modal/json-modal.component.scss b/packages/admin-web-angular/src/app/@shared/json-modal/json-modal.component.scss new file mode 100644 index 0000000..b3ff4c8 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/json-modal/json-modal.component.scss @@ -0,0 +1,18 @@ +.iconInfo { +} +.header { + padding: 0px; + margin: 0px; +} +.body { + padding: 0px; + margin: 0px; +} +.product-title { + color: #009100; +} +.title { + padding: 0px; + margin: 0px; + text-align: center; +} diff --git a/packages/admin-web-angular/src/app/@shared/json-modal/json-modal.component.ts b/packages/admin-web-angular/src/app/@shared/json-modal/json-modal.component.ts new file mode 100644 index 0000000..96e00b7 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/json-modal/json-modal.component.ts @@ -0,0 +1,20 @@ +import { Component, OnInit } from '@angular/core'; +import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; + +@Component({ + styleUrls: ['./json-modal.component.scss'], + templateUrl: './json-modal.component.html', +}) +export class JsonModalComponent implements OnInit { + public obj: {}; + public title: string; + public subTitle: string; + + constructor(private readonly activeModal: NgbActiveModal) {} + + cancel() { + this.activeModal.dismiss('canceled'); + } + + ngOnInit(): void {} +} diff --git a/packages/admin-web-angular/src/app/@shared/json-modal/json-modal.module.ts b/packages/admin-web-angular/src/app/@shared/json-modal/json-modal.module.ts new file mode 100644 index 0000000..0ed5994 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/json-modal/json-modal.module.ts @@ -0,0 +1,20 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { ThemeModule } from '../../@theme'; +import { JsonModalComponent } from './json-modal.component'; +import { TranslateModule } from '@ngx-translate/core'; +import { HighlightModule } from 'ngx-highlightjs'; + +const COMPONENTS = [JsonModalComponent]; + +@NgModule({ + imports: [ + CommonModule, + ThemeModule, + TranslateModule.forChild(), + HighlightModule, + ], + declarations: COMPONENTS, + exports: COMPONENTS +}) +export class JsonModalModule {} diff --git a/packages/admin-web-angular/src/app/@shared/order/order-map/carreir-location/carreir-location.ts b/packages/admin-web-angular/src/app/@shared/order/order-map/carreir-location/carreir-location.ts new file mode 100644 index 0000000..097a44e --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/order/order-map/carreir-location/carreir-location.ts @@ -0,0 +1,76 @@ +import { + Component, + OnInit, + ViewChild, + ElementRef, + Input, + OnDestroy, +} from '@angular/core'; +import Order from '@modules/server.common/entities/Order'; +import { environment } from 'environments/environment'; + +@Component({ + selector: 'ea-carrier-location', + template: `
`, +}) +export class CarrierLocationComponent implements OnInit, OnDestroy { + @ViewChild('gmap', { static: true }) + gmapElement: ElementRef; + public map: google.maps.Map; + private directionsDisplay = new google.maps.DirectionsRenderer(); + private directionsService = new google.maps.DirectionsService(); + + @Input() + order: Order; + + ngOnInit(): void { + this.loadMap(); + + this.loadRoot(); + } + + loadMap() { + const mapProp = { + center: new google.maps.LatLng( + environment.DEFAULT_LATITUDE, + environment.DEFAULT_LONGITUDE + ), + zoom: 15, + mapTypeId: google.maps.MapTypeId.ROADMAP, + mapTypeControl: false, + streetViewControl: false, + }; + this.map = new google.maps.Map(this.gmapElement.nativeElement, mapProp); + } + + loadRoot() { + if (this.order) { + const [carrierLng, carrierLat] = this.order.carrier[ + 'geoLocation' + ].loc.coordinates; + const [userLng, userLat] = this.order.user[ + 'geoLocation' + ].loc.coordinates; + const origin = new google.maps.LatLng(carrierLat, carrierLng); + const destination = new google.maps.LatLng(userLat, userLng); + + const request = { + origin, + destination, + travelMode: google.maps.TravelMode['DRIVING'], + }; + + this.directionsService.route(request, (res, stat: any) => { + if (stat === 'OK') { + this.directionsDisplay.setDirections(res); + } + }); + + this.directionsDisplay.setMap(this.map); + } + } + + ngOnDestroy(): void { + this.directionsDisplay.setMap(null); + } +} diff --git a/packages/admin-web-angular/src/app/@shared/order/order-map/order-map.module.ts b/packages/admin-web-angular/src/app/@shared/order/order-map/order-map.module.ts new file mode 100644 index 0000000..cb56a56 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/order/order-map/order-map.module.ts @@ -0,0 +1,24 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { ToasterModule } from 'angular2-toaster'; +import { TranslateModule } from '@ngx-translate/core'; +import { ThemeModule } from '@app/@theme'; +import { UserWarehouseLocationComponent } from './user-warehouse-map/user-warehouse-map'; +import { CarrierLocationComponent } from './carreir-location/carreir-location'; + +const ORDER_MAP_COMPONENTS = [ + UserWarehouseLocationComponent, + CarrierLocationComponent, +]; +@NgModule({ + imports: [ + CommonModule, + ThemeModule, + ToasterModule.forRoot(), + TranslateModule.forChild(), + ], + declarations: [...ORDER_MAP_COMPONENTS], + exports: [...ORDER_MAP_COMPONENTS], + providers: [] +}) +export class OrderMapModule {} diff --git a/packages/admin-web-angular/src/app/@shared/order/order-map/user-warehouse-map/user-warehouse-map.ts b/packages/admin-web-angular/src/app/@shared/order/order-map/user-warehouse-map/user-warehouse-map.ts new file mode 100644 index 0000000..fe86877 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/order/order-map/user-warehouse-map/user-warehouse-map.ts @@ -0,0 +1,108 @@ +import { + Component, + ViewChild, + ElementRef, + Input, + OnDestroy, + OnChanges, + AfterViewInit, +} from '@angular/core'; +import Order from '@modules/server.common/entities/Order'; +import IGeoLocation from '@modules/server.common/interfaces/IGeoLocation'; +import { environment } from 'environments/environment'; + +const warehouseIcon = environment.MAP_MERCHANT_ICON_LINK; +const userIcon = environment.MAP_USER_ICON_LINK; + +@Component({ + selector: 'ea-user-warehouse-location', + template: `
`, +}) +export class UserWarehouseLocationComponent + implements AfterViewInit, OnDestroy, OnChanges { + @ViewChild('gmap', { static: true }) + gmapElement: ElementRef; + + public map: google.maps.Map; + + public userMarker: any; + + public warehouseMarker: any; + + @Input() + order: Order; + + public isLoaded: boolean; + + ngAfterViewInit(): void { + this.loadMap(); + } + + ngOnChanges(): void { + this.loadMarkers(); + } + + loadMap() { + const mapProp = { + center: new google.maps.LatLng( + environment.DEFAULT_LATITUDE, + environment.DEFAULT_LONGITUDE + ), + zoom: 15, + mapTypeId: google.maps.MapTypeId.ROADMAP, + mapTypeControl: false, + streetViewControl: false, + }; + this.map = new google.maps.Map(this.gmapElement.nativeElement, mapProp); + this.loadMarkers(); + } + + loadMarkers() { + if (this.order && !this.isLoaded && this.map) { + this.isLoaded = true; + const user = this.order.user; + const warehouse = this.order.warehouse; + + this.userMarker = this.addMarker( + user['geoLocation'], + this.map, + userIcon + ); + + this.warehouseMarker = this.addMarker( + warehouse['geoLocation'], + this.map, + warehouseIcon + ); + + const bounds = new google.maps.LatLngBounds(); + bounds.extend(this.warehouseMarker.getPosition()); + bounds.extend(this.userMarker.getPosition()); + + this.map.fitBounds(bounds); + } + } + + addMarker(geoLocation: IGeoLocation, map, icon) { + const [lng, lat] = geoLocation.loc.coordinates; + const position = new google.maps.LatLng(lat, lng); + return new google.maps.Marker({ + position, + map, + icon, + }); + } + + removeMarkers() { + if (this.userMarker) { + this.userMarker.setMap(null); + } + if (this.warehouseMarker) { + this.warehouseMarker.setMap(null); + } + } + + ngOnDestroy(): void { + this.removeMarkers(); + } +} diff --git a/packages/admin-web-angular/src/app/@shared/payment-gateways/payPal-gateway/payPal-gateway.component.html b/packages/admin-web-angular/src/app/@shared/payment-gateways/payPal-gateway/payPal-gateway.component.html new file mode 100644 index 0000000..a283028 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/payment-gateways/payPal-gateway/payPal-gateway.component.html @@ -0,0 +1,212 @@ +
+
+ + + {{ name }} + +
+
+ +
+
+
+ + +
+ +
+ + + + + {{ + 'FAKE_DATA.SETUP_MERCHANTS.PAYMENTS.PAYPAL.CURRENCY_TEXT_IS_REQUIRED' + | translate + }} + + +
+
+ +
+
+ + + + + {{ + 'FAKE_DATA.SETUP_MERCHANTS.PAYMENTS.PAYPAL.PUBLISHABLE_KEY_IS_REQUIRED' + | translate + }} + + +
+
+ +
+
+ + + + + {{ + 'FAKE_DATA.SETUP_MERCHANTS.PAYMENTS.PAYPAL.SECRET_KEY_IS_REQUIRED' + | translate + }} + + +
+
+ +
+
+ + + + + {{ + 'FAKE_DATA.SETUP_MERCHANTS.PAYMENTS.PAYPAL.PAYMENT_DESCRIPTION_IS_REQUIRED' + | translate + }} + + +
+
+
diff --git a/packages/admin-web-angular/src/app/@shared/payment-gateways/payPal-gateway/payPal-gateway.component.ts b/packages/admin-web-angular/src/app/@shared/payment-gateways/payPal-gateway/payPal-gateway.component.ts new file mode 100644 index 0000000..0daf830 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/payment-gateways/payPal-gateway/payPal-gateway.component.ts @@ -0,0 +1,69 @@ +import { Component, Input, ViewChild } from '@angular/core'; +import PaymentGateways, { + paymentGatewaysToString, + paymentGatewaysLogo, +} from '@modules/server.common/enums/PaymentGateways'; +import { Country } from '@modules/server.common/entities'; +import { NgForm } from '@angular/forms'; +import IPaymentGatewayCreateObject from '@modules/server.common/interfaces/IPaymentGateway'; + +@Component({ + selector: 'ea-payPal-gateway', + templateUrl: './payPal-gateway.component.html', +}) +export class PayPalGatewayComponent { + @ViewChild('payPalConfigForm', { static: true }) + payPalConfigForm: NgForm; + + isPayPalEnabled: boolean; + name = paymentGatewaysToString(PaymentGateways.PayPal); + logo = paymentGatewaysLogo(PaymentGateways.PayPal); + + @Input() + currenciesCodes: string[] = []; + @Input() + warehouseCountry: Country; + + configModel = { + currency: '', + mode: '', + publishableKey: '', + secretKey: '', + description: '', + }; + + payPalTypes = ['sandbox', 'live']; + + get isFormValid(): boolean { + let isValid = false; + + if (this.payPalConfigForm) { + isValid = + (this.payPalConfigForm.touched || + this.payPalConfigForm.dirty) && + this.payPalConfigForm.valid; + } + + return isValid; + } + + get createObject(): IPaymentGatewayCreateObject | null { + if (!this.isFormValid || !this.isPayPalEnabled) { + return null; + } + + return { + paymentGateway: PaymentGateways.PayPal, + configureObject: this.configModel, + }; + } + + setValue(data) { + this.isPayPalEnabled = true; + this.configModel.currency = data['currency'] || ''; + this.configModel.mode = data['mode'] || ''; + this.configModel.publishableKey = data['publishableKey'] || ''; + this.configModel.secretKey = data['secretKey'] || ''; + this.configModel.description = data['description'] || ''; + } +} diff --git a/packages/admin-web-angular/src/app/@shared/payment-gateways/payment-gateways.component.html b/packages/admin-web-angular/src/app/@shared/payment-gateways/payment-gateways.component.html new file mode 100644 index 0000000..003b229 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/payment-gateways/payment-gateways.component.html @@ -0,0 +1,9 @@ + + diff --git a/packages/admin-web-angular/src/app/@shared/payment-gateways/payment-gateways.component.ts b/packages/admin-web-angular/src/app/@shared/payment-gateways/payment-gateways.component.ts new file mode 100644 index 0000000..d8af5cd --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/payment-gateways/payment-gateways.component.ts @@ -0,0 +1,132 @@ +import { Component, Input, ViewChild, OnChanges } from '@angular/core'; +import { Country } from '@modules/server.common/entities'; +import { StripeGatewayComponent } from './stripe-gateway/stripe-gateway.component'; +import { PayPalGatewayComponent } from './payPal-gateway/payPal-gateway.component'; +import IPaymentGatewayCreateObject from '@modules/server.common/interfaces/IPaymentGateway'; +import { CurrenciesService } from '@app/@core/data/currencies.service'; +import { first } from 'rxjs/operators'; +import Warehouse from '@modules/server.common/entities/Warehouse'; +import PaymentGateways from '@modules/server.common/enums/PaymentGateways'; +import { countriesDefaultCurrencies } from '@modules/server.common/entities/Currency'; + +@Component({ + selector: 'ea-payment-gateways', + templateUrl: './payment-gateways.component.html', +}) +export class PaymentGatewaysComponent implements OnChanges { + @ViewChild('stripeGateway') + stripeGateway: StripeGatewayComponent; + + @ViewChild('payPalGateway') + payPalGateway: PayPalGatewayComponent; + + @Input() + warehouseLogo: string; + @Input() + warehouseCountry: Country; + @Input() + isEdit: boolean; + + currenciesCodes: string[] = []; + + constructor(private currenciesService: CurrenciesService) { + this.loadCurrenciesCodes(); + } + + get isValid(): boolean { + let valid = false; + if ( + this.stripeGateway.isStripeEnabled || + this.payPalGateway.isPayPalEnabled + ) { + if (this.stripeGateway.isStripeEnabled) { + valid = this.stripeGateway.isFormValid; + + if (!valid) { + return; + } + } + + if (this.payPalGateway.isPayPalEnabled) { + valid = this.payPalGateway.isFormValid; + + if (!valid) { + return; + } + } + } + + return valid; + } + + get paymentsGateways(): IPaymentGatewayCreateObject[] { + const paymentsGateways = []; + + const stripeGatewayCreateObject = this.stripeGateway.createObject; + const payPalGatewayCreateObject = this.payPalGateway.createObject; + + if (stripeGatewayCreateObject) { + paymentsGateways.push(stripeGatewayCreateObject); + } + + if (payPalGatewayCreateObject) { + paymentsGateways.push(payPalGatewayCreateObject); + } + + return paymentsGateways; + } + + ngOnChanges(): void { + const merchantCountry = Country[this.warehouseCountry]; + + if (merchantCountry) { + const defaultCurrency = + countriesDefaultCurrencies[merchantCountry.toString()] || ''; + + if ( + this.stripeGateway && + (!this.isEdit || !this.stripeGateway.configModel.currency) + ) { + this.stripeGateway.configModel.currency = defaultCurrency; + } + + if ( + this.payPalGateway && + (!this.isEdit || !this.payPalGateway.configModel.currency) + ) { + this.payPalGateway.configModel.currency = defaultCurrency; + } + } + } + + private async loadCurrenciesCodes() { + const res = await this.currenciesService + .getCurrencies() + .pipe(first()) + .toPromise(); + + if (res) { + this.currenciesCodes = res.map((r) => r.currencyCode); + } + } + + setValue(merchant: Warehouse) { + if (merchant.paymentGateways) { + const stripeConfigObj = merchant.paymentGateways.find( + (g) => g.paymentGateway === PaymentGateways.Stripe + ); + + if (stripeConfigObj) { + this.stripeGateway.setValue(stripeConfigObj.configureObject); + } + + const payPalConfigObj = merchant.paymentGateways.find( + (g) => g.paymentGateway === PaymentGateways.PayPal + ); + + if (payPalConfigObj) { + this.payPalGateway.setValue(payPalConfigObj.configureObject); + } + } + } +} diff --git a/packages/admin-web-angular/src/app/@shared/payment-gateways/payment-gateways.module.ts b/packages/admin-web-angular/src/app/@shared/payment-gateways/payment-gateways.module.ts new file mode 100644 index 0000000..bf1abfc --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/payment-gateways/payment-gateways.module.ts @@ -0,0 +1,26 @@ +import { NgModule } from '@angular/core'; +import { ThemeModule } from '@app/@theme'; +import { CommonModule } from '@angular/common'; +import { TranslateModule } from '@ngx-translate/core'; +import { PaymentGatewaysComponent } from './payment-gateways.component'; +import { StripeGatewayComponent } from './stripe-gateway/stripe-gateway.component'; +import { PayPalGatewayComponent } from './payPal-gateway/payPal-gateway.component'; +import { FileUploaderModule } from '@app/@shared/file-uploader/file-uploader.module'; +import { CurrenciesService } from '@app/@core/data/currencies.service'; + +@NgModule({ + imports: [ + CommonModule, + ThemeModule, + TranslateModule.forChild(), + FileUploaderModule, + ], + declarations: [ + PaymentGatewaysComponent, + StripeGatewayComponent, + PayPalGatewayComponent, + ], + exports: [PaymentGatewaysComponent], + providers: [CurrenciesService], +}) +export class PaymentGatewaysModule {} diff --git a/packages/admin-web-angular/src/app/@shared/payment-gateways/stripe-gateway/stripe-gateway.component.html b/packages/admin-web-angular/src/app/@shared/payment-gateways/stripe-gateway/stripe-gateway.component.html new file mode 100644 index 0000000..d398d09 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/payment-gateways/stripe-gateway/stripe-gateway.component.html @@ -0,0 +1,231 @@ +
+
+ + + {{ name }} + +
+
+ +
+
+
+ + + + + {{ + 'FAKE_DATA.SETUP_MERCHANTS.PAYMENTS.STRIPE.PAY_BUTTON_TEXT_IS_REQUIRED' + | translate + }} + + +
+ +
+ + + + + {{ + 'FAKE_DATA.SETUP_MERCHANTS.PAYMENTS.STRIPE.CURRENCY_TEXT_IS_REQUIRED' + | translate + }} + + +
+
+ +
+
+ + + + + + {{ + 'FAKE_DATA.SETUP_MERCHANTS.PAYMENTS.STRIPE.COMPANY_BRAND_LOGO_IS_REQUIRED' + | translate + }} + + + + {{ + 'FAKE_DATA.SETUP_MERCHANTS.PAYMENTS.STRIPE.INVALID_LOGO_URL' + | translate + }} + + +
+ +
+
+
+ Invalid logo +
+ +
+
+
+
+
+ +
+
+ + + + + {{ + 'FAKE_DATA.SETUP_MERCHANTS.PAYMENTS.STRIPE.PUBLISHABLE_KEY_IS_REQUIRED' + | translate + }} + + +
+
+ +
+
+ + {{ + 'FAKE_DATA.SETUP_MERCHANTS.PAYMENTS.STRIPE.ALLOW_REMEMBER_ME' + | translate + }} + +
+
+
diff --git a/packages/admin-web-angular/src/app/@shared/payment-gateways/stripe-gateway/stripe-gateway.component.ts b/packages/admin-web-angular/src/app/@shared/payment-gateways/stripe-gateway/stripe-gateway.component.ts new file mode 100644 index 0000000..3d7d24b --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/payment-gateways/stripe-gateway/stripe-gateway.component.ts @@ -0,0 +1,105 @@ +import { Component, Input, OnChanges, ViewChild } from '@angular/core'; +import PaymentGateways, { + paymentGatewaysToString, + paymentGatewaysLogo, +} from '@modules/server.common/enums/PaymentGateways'; +import { Country } from '@modules/server.common/entities'; +import { NgForm } from '@angular/forms'; +import IPaymentGatewayCreateObject from '@modules/server.common/interfaces/IPaymentGateway'; +import { TranslateService } from '@ngx-translate/core'; +import { takeUntil } from 'rxjs/operators'; +import { Subject } from 'rxjs'; + +@Component({ + selector: 'ea-stripe-gateway', + templateUrl: './stripe-gateway.component.html', +}) +export class StripeGatewayComponent { + @ViewChild('stripeConfigForm', { static: true }) + stripeConfigForm: NgForm; + + isStripeEnabled: boolean; + name = paymentGatewaysToString(PaymentGateways.Stripe); + logo = paymentGatewaysLogo(PaymentGateways.Stripe); + invalidUrl: boolean; + + private _ngDestroy$ = new Subject(); + + COMPANY_BRAND_LOGO = + 'FAKE_DATA.SETUP_MERCHANTS.PAYMENTS.STRIPE.COMPANY_BRAND_LOGO'; + + @Input() + currenciesCodes: string[] = []; + @Input() + warehouseCountry: Country; + @Input() + set companyBrandLogo(logo: string) { + if (!this.configModel.companyBrandLogo) { + this.configModel.companyBrandLogo = logo; + } + } + + constructor(private translateService: TranslateService) { + // https://github.com/ngx-translate/core/issues/835 + // see how to translate words in the component(.ts) file + + translateService + .stream(this.COMPANY_BRAND_LOGO) + .pipe(takeUntil(this._ngDestroy$)) + .subscribe((text: string) => { + this.COMPANY_BRAND_LOGO = text; + }); + } + + configModel = { + payButtontext: '', + currency: '', + companyBrandLogo: '', + publishableKey: '', + allowRememberMe: true, + }; + + get isFormValid(): boolean { + let isValid = false; + + if (this.stripeConfigForm) { + isValid = + (this.stripeConfigForm.touched || + this.stripeConfigForm.dirty) && + this.stripeConfigForm.valid && + !this.invalidUrl && + this.configModel.companyBrandLogo !== ''; + } + + return isValid; + } + + get createObject(): IPaymentGatewayCreateObject | null { + if (!this.isFormValid || !this.isStripeEnabled) { + return null; + } + + return { + paymentGateway: PaymentGateways.Stripe, + configureObject: this.configModel, + }; + } + + deleteImg() { + this.configModel.companyBrandLogo = ''; + } + + setValue(data) { + this.isStripeEnabled = true; + this.configModel.payButtontext = data['payButtontext'] || ''; + this.configModel.currency = data['currency'] || ''; + this.configModel.companyBrandLogo = data['companyBrandLogo'] || ''; + this.configModel.publishableKey = data['publishableKey'] || ''; + this.configModel.allowRememberMe = data['allowRememberMe']; + } + + ngOnDestroy() { + this._ngDestroy$.next(); + this._ngDestroy$.complete(); + } +} diff --git a/packages/admin-web-angular/src/app/@shared/product/categories/categories-table/categories-table.component.html b/packages/admin-web-angular/src/app/@shared/product/categories/categories-table/categories-table.component.html new file mode 100644 index 0000000..5abf1c2 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/product/categories/categories-table/categories-table.component.html @@ -0,0 +1,10 @@ + + diff --git a/packages/admin-web-angular/src/app/@shared/product/categories/categories-table/categories-table.component.scss b/packages/admin-web-angular/src/app/@shared/product/categories/categories-table/categories-table.component.scss new file mode 100644 index 0000000..35f7e1b --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/product/categories/categories-table/categories-table.component.scss @@ -0,0 +1,11 @@ +:host ::ng-deep ng2-smart-table table { + th.image { + text-align: center; + } + th.ng2-smart-actions { + text-align: center; + } + tr > td:nth-child(3) { + text-align: center; + } +} diff --git a/packages/admin-web-angular/src/app/@shared/product/categories/categories-table/categories-table.component.ts b/packages/admin-web-angular/src/app/@shared/product/categories/categories-table/categories-table.component.ts new file mode 100644 index 0000000..88e2a4f --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/product/categories/categories-table/categories-table.component.ts @@ -0,0 +1,211 @@ +import { + Component, + OnDestroy, + Input, + OnInit, + Output, + EventEmitter, +} from '@angular/core'; +import { LocalDataSource } from 'ng2-smart-table'; +import ProductsCategory from '@modules/server.common/entities/ProductsCategory'; +import { ProductsCategoryService } from '@app/@core/data/productsCategory.service'; +import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; +import { CategoryEditComponent } from '../category-edit'; +import { TranslateService } from '@ngx-translate/core'; +import { forkJoin, Subject, Observable, Subscription } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; +import { ProductLocalesService } from '@modules/client.common.angular2/locale/product-locales.service'; +import { ILocaleMember } from '@modules/server.common/interfaces/ILocale'; +import { NotifyService } from '@app/@core/services/notify/notify.service'; +import { ConfirmationModalComponent } from '@app/@shared/confirmation-modal/confirmation-modal.component'; +import { CategoryImageComponent } from './category-image.component'; + +interface ProductViewModel { + id: string; + title: string; + description: string; + details: string; + images: string[]; +} + +@Component({ + selector: 'ea-categories-table', + styleUrls: ['./categories-table.component.scss'], + templateUrl: './categories-table.component.html', +}) +export class CategoriesTableComponent implements OnInit, OnDestroy { + @Input() + selectMode = 'multi'; + @Input() + showPerPage = 7; + @Input() + editWithModal = true; + + @Output() + editRow = new EventEmitter(); + @Output() + deleteRow = new EventEmitter(); + + productsCategories: ProductsCategory[] = []; + loading: boolean; + + confirmSub$: Subscription; + + settingsSmartTable: object; + sourceSmartTable = new LocalDataSource(); + + private static noInfoSign = ''; + private _selectedCategories: ProductViewModel[] = []; + private ngDestroy$ = new Subject(); + + constructor( + private readonly _translateService: TranslateService, + private readonly _productsCategoryService: ProductsCategoryService, + private readonly _productLocalesService: ProductLocalesService, + private readonly _modalService: NgbModal, + private readonly _notifyService: NotifyService, + private readonly modalService: NgbModal + ) {} + + get hasSelectedCategories(): boolean { + return this._selectedCategories.length > 0; + } + + get selectedCategories() { + return [...this._selectedCategories]; + } + + ngOnInit(): void { + this._loadSettingsSmartTable(); + this._applyTranslationOnSmartTable(); + } + edit(ev) { + if (this.editWithModal) { + const activeModal = this._modalService.open(CategoryEditComponent, { + size: 'lg', + container: 'nb-layout', + backdrop: 'static', + }); + const modalComponent: CategoryEditComponent = + activeModal.componentInstance; + modalComponent.currentCategory = ev.data; + } else { + this.editRow.emit(ev.data); + } + } + + async deleteCategory(e) { + const activeModal = this.modalService.open(ConfirmationModalComponent, { + size: 'sm', + container: 'nb-layout', + backdrop: 'static', + }); + const modalComponent: ConfirmationModalComponent = + activeModal.componentInstance; + + this.confirmSub$ = await modalComponent.confirmEvent + .pipe(takeUntil(modalComponent.ngDestroy$)) + .subscribe((dataEvent) => { + const idsArray: any = []; + idsArray.push(e.data.id); + + try { + this.loading = true; + + this._productsCategoryService + .removeByIds(idsArray) + .pipe() + .toPromise(); + + this.loading = false; + + const message = `Category '${e.data.title}' deleted`; + this._notifyService.success(message); + + this.deleteRow.emit(e.data); + } catch (error) { + this.loading = false; + const message = `Something went wrong!`; + this._notifyService.error(message); + } + + modalComponent.cancel(); + }); + } + + selectCategoryTmp(ev) { + this._selectedCategories = ev.selected; + } + + async loadDataSmartTable(categories: ProductsCategory[]) { + this.productsCategories = categories; + const categoriesVM = categories.map((category) => { + return { + id: category.id, + title: + this.localeTranslate(category.name) || + CategoriesTableComponent.noInfoSign, + image: category.image, + _nameLocaleValues: category.name, + }; + }); + + this.sourceSmartTable.load(categoriesVM); + } + + localeTranslate(member: ILocaleMember[]) { + return this._productLocalesService.getTranslate(member); + } + + private _applyTranslationOnSmartTable() { + this._translateService.onLangChange.subscribe(() => { + this._loadSettingsSmartTable(); + this.loadDataSmartTable(this.productsCategories); + }); + } + + private _loadSettingsSmartTable() { + const columnTitlePrefix = 'CATEGORY_VIEW.'; + const getTranslate = (name: string): Observable => + this._translateService.get(columnTitlePrefix + name); + + forkJoin(getTranslate('IMAGE'), getTranslate('TITLE')) + .pipe(takeUntil(this.ngDestroy$)) + .subscribe(([image, titleTr]) => { + this.settingsSmartTable = { + selectMode: this.selectMode, + mode: 'external', + actions: { + add: false, + position: 'left', + }, + edit: { + editButtonContent: '', + }, + delete: { + deleteButtonContent: '', + confirmDelete: true, + }, + columns: { + image: { + title: image, + type: 'custom', + filter: false, + renderComponent: CategoryImageComponent, + width: '5%', + }, + title: { title: titleTr }, + }, + pager: { + display: true, + perPage: this.showPerPage, + }, + }; + }); + } + + ngOnDestroy() { + this.ngDestroy$.next(); + this.ngDestroy$.complete(); + } +} diff --git a/packages/admin-web-angular/src/app/@shared/product/categories/categories-table/categories-table.stories.ts b/packages/admin-web-angular/src/app/@shared/product/categories/categories-table/categories-table.stories.ts new file mode 100644 index 0000000..37dbb25 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/product/categories/categories-table/categories-table.stories.ts @@ -0,0 +1,87 @@ +import { storiesOf, moduleMetadata } from '@storybook/angular'; +import { of } from 'rxjs'; +import { withKnobs, boolean } from '@storybook/addon-knobs'; +import { ThemeModule } from '@app/@theme'; +import { CommonModule } from '@angular/common'; +import { Ng2SmartTableModule } from 'ng2-smart-table'; +import { NbSpinnerModule } from '@nebular/theme'; +import { ConfirmationModalModule } from '@app/@shared/confirmation-modal/confirmation-modal.module'; +import { ToasterModule } from 'angular2-toaster'; +import { TranslateLoader } from '@ngx-translate/core'; +import { RouterModule } from '@angular/router'; +import { routes, NbAuthModule } from '@nebular/auth'; +import { NotifyService } from '@app/@core/services/notify/notify.service'; +import { DeviceService } from '@app/@core/data/device.service'; +import { APOLLO_OPTIONS } from 'apollo-angular'; +import { HttpLink } from 'apollo-angular/http'; +import { InMemoryCache } from '@apollo/client/core'; +import { PipesModule } from '@modules/client.common.angular2/pipes/pipes.module'; +import { HttpClientModule } from '@angular/common/http'; +import { FileUploaderModule } from '@app/@shared/file-uploader/file-uploader.module'; +import { FormsModule } from '@angular/forms'; +import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; +import { LocaleModule } from '@modules/client.common.angular2/locale/locale.module'; +import { ProductsCategoryService } from '@app/@core/data/productsCategory.service'; +import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; +import { CategoriesTableComponent } from './categories-table.component'; +import { I18nModule } from '@app/@core/utils/i18n.module'; + +const stories = storiesOf('Categories Table Component', module); + +export function createApollo(httpLink: HttpLink) { + return { + link: httpLink.create({ uri: 'https://api.example.com/graphql' }), + cache: new InMemoryCache(), + }; +} + +export const staticTranslateLoader: TranslateLoader = { + getTranslation(lang: string) { + return of(require('../../../../../assets/i18n/en.json')); + }, +}; + +stories.addDecorator(withKnobs); + +stories.addDecorator( + moduleMetadata({ + declarations: [], + imports: [ + CommonModule, + ThemeModule, + Ng2SmartTableModule, + NbSpinnerModule, + ConfirmationModalModule, + BrowserAnimationsModule, + ToasterModule.forRoot(), + RouterModule.forChild(routes), + NbAuthModule, + PipesModule, + FileUploaderModule, + Ng2SmartTableModule, + NbSpinnerModule, + FormsModule, + LocaleModule, + HttpClientModule, + I18nModule, + ], + providers: [ + DeviceService, + { + provide: APOLLO_OPTIONS, + useFactory: createApollo, + deps: [HttpLink], + }, + NotifyService, + NgbActiveModal, + ProductsCategoryService, + ], + }) +); + +stories.add('Category Table', () => ({ + component: CategoriesTableComponent, + props: { + storybookVersion: boolean('Storybook-Version', true), + }, +})); diff --git a/packages/admin-web-angular/src/app/@shared/product/categories/categories-table/category-image.component.scss b/packages/admin-web-angular/src/app/@shared/product/categories/categories-table/category-image.component.scss new file mode 100644 index 0000000..3c88b01 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/product/categories/categories-table/category-image.component.scss @@ -0,0 +1,13 @@ +#category-image { + background: #000000; + border-radius: 4px; + width: 80px; + height: 80px; +} + +img { + opacity: 0.85; + width: 80px; + height: 80px; + border-radius: 4px; +} diff --git a/packages/admin-web-angular/src/app/@shared/product/categories/categories-table/category-image.component.ts b/packages/admin-web-angular/src/app/@shared/product/categories/categories-table/category-image.component.ts new file mode 100644 index 0000000..1d0b956 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/product/categories/categories-table/category-image.component.ts @@ -0,0 +1,21 @@ +import { Component, OnInit, ViewChild, ElementRef } from '@angular/core'; +import { ViewCell } from 'ng2-smart-table'; + +@Component({ + selector: 'category-image', + styleUrls: ['./category-image.component.scss'], + template: ` +
+ `, +}) +export class CategoryImageComponent implements ViewCell, OnInit { + value: string; + rowData: any; + + @ViewChild('image', { static: true }) + image: ElementRef; + + ngOnInit() { + this.image.nativeElement.src = this.value; + } +} diff --git a/packages/admin-web-angular/src/app/@shared/product/categories/categories-table/index.ts b/packages/admin-web-angular/src/app/@shared/product/categories/categories-table/index.ts new file mode 100644 index 0000000..336dbba --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/product/categories/categories-table/index.ts @@ -0,0 +1 @@ +export * from './categories-table.component'; diff --git a/packages/admin-web-angular/src/app/@shared/product/categories/category-create/category-create.component.html b/packages/admin-web-angular/src/app/@shared/product/categories/category-create/category-create.component.html new file mode 100644 index 0000000..c3403e7 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/product/categories/category-create/category-create.component.html @@ -0,0 +1,30 @@ + + + + + + + diff --git a/packages/admin-web-angular/src/app/@shared/product/categories/category-create/category-create.component.scss b/packages/admin-web-angular/src/app/@shared/product/categories/category-create/category-create.component.scss new file mode 100644 index 0000000..9b90e67 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/product/categories/category-create/category-create.component.scss @@ -0,0 +1,9 @@ +div.modal-footer { + padding-top: 7px; + margin-left: 23px; + border: none; +} + +label { + margin: auto 0; +} diff --git a/packages/admin-web-angular/src/app/@shared/product/categories/category-create/category-create.component.ts b/packages/admin-web-angular/src/app/@shared/product/categories/category-create/category-create.component.ts new file mode 100644 index 0000000..23a3a1d --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/product/categories/category-create/category-create.component.ts @@ -0,0 +1,73 @@ +import { Component, OnDestroy } from '@angular/core'; +import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; +import { Subject } from 'rxjs'; + +import { ProductLocalesService } from '@modules/client.common.angular2/locale/product-locales.service'; +import { ILocaleMember } from '@modules/server.common/interfaces/ILocale'; +import { TranslateService } from '@ngx-translate/core'; +import { NotifyService } from '@app/@core/services/notify/notify.service'; +import { first } from 'rxjs/operators'; +import { ProductsCategoryService } from '@app/@core/data/productsCategory.service'; + +@Component({ + selector: 'ea-category-create', + templateUrl: './category-create.component.html', + styleUrls: ['./category-create.component.scss'], +}) +export class CategoryCreateComponent implements OnDestroy { + productId: any; + userId: any; + loading: boolean; + storybookVersion: boolean; + + private _ngDestroy$ = new Subject(); + + constructor( + private readonly activeModal: NgbActiveModal, + private readonly _productLocalesService: ProductLocalesService, + private readonly _translateService: TranslateService, + private readonly _notifyService: NotifyService, + private readonly _productsCategoryService: ProductsCategoryService + ) {} + + ngOnDestroy() { + this._ngDestroy$.next(); + this._ngDestroy$.complete(); + } + + cancel() { + this.activeModal.dismiss('canceled'); + } + + async createCategory(createObject) { + if (this.storybookVersion) { + this._notifyService.success( + 'Category ' + + this.localeTranslate(createObject.name) + + ' is added' + ); + return true; + } + try { + this.loading = true; + await this._productsCategoryService + .create(createObject) + .pipe(first()) + .toPromise(); + this.loading = false; + const message = `Category ${this.localeTranslate( + createObject.name + )} is added!`; + this._notifyService.success(message); + this.cancel(); + } catch (err) { + this.loading = false; + const message = `Something went wrong!`; + this._notifyService.error(message); + } + } + + localeTranslate(member: ILocaleMember[]) { + return this._productLocalesService.getTranslate(member); + } +} diff --git a/packages/admin-web-angular/src/app/@shared/product/categories/category-create/category-create.stories.ts b/packages/admin-web-angular/src/app/@shared/product/categories/category-create/category-create.stories.ts new file mode 100644 index 0000000..1a181f0 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/product/categories/category-create/category-create.stories.ts @@ -0,0 +1,99 @@ +import { storiesOf, moduleMetadata } from '@storybook/angular'; +import { of } from 'rxjs'; +import { withKnobs, boolean } from '@storybook/addon-knobs'; +import { ThemeModule } from '@app/@theme'; +import { CommonModule } from '@angular/common'; +import { Ng2SmartTableModule } from 'ng2-smart-table'; +import { NbSpinnerModule } from '@nebular/theme'; +import { ConfirmationModalModule } from '@app/@shared/confirmation-modal/confirmation-modal.module'; +import { ToasterModule } from 'angular2-toaster'; +import { + TranslateStore, + TranslateLoader, + TranslateService, + TranslatePipe, +} from '@ngx-translate/core'; +import { RouterModule } from '@angular/router'; +import { routes, NbAuthModule } from '@nebular/auth'; +import { NotifyService } from '@app/@core/services/notify/notify.service'; +import { DeviceService } from '@app/@core/data/device.service'; +import { APOLLO_OPTIONS } from 'apollo-angular'; +import { HttpLink } from 'apollo-angular/http'; +import { InMemoryCache } from '@apollo/client/core'; +import { PipesModule } from '@modules/client.common.angular2/pipes/pipes.module'; +import { HttpClientModule } from '@angular/common/http'; +import { CategoryCreateComponent } from './category-create.component'; +import { FileUploaderModule } from '@app/@shared/file-uploader/file-uploader.module'; +import { FormsModule } from '@angular/forms'; +import { ProductCategoriesFormsModule } from '../forms/product-categories-forms.module'; +import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; +import { ProductLocalesService } from '@modules/client.common.angular2/locale/product-locales.service'; +import { LocaleModule } from '@modules/client.common.angular2/locale/locale.module'; +import { ProductsCategoryService } from '@app/@core/data/productsCategory.service'; +import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; +import { I18nModule } from '@app/@core/utils/i18n.module'; + +const stories = storiesOf('Categories Create Component', module); + +export function createApollo(httpLink: HttpLink) { + return { + link: httpLink.create({ uri: 'https://api.example.com/graphql' }), + cache: new InMemoryCache(), + }; +} + +export const staticTranslateLoader: TranslateLoader = { + getTranslation(lang: string) { + return of(require('../../../../../assets/i18n/en.json')); + }, +}; + +stories.addDecorator(withKnobs); + +stories.addDecorator( + moduleMetadata({ + declarations: [CategoryCreateComponent], + imports: [ + CommonModule, + ThemeModule, + Ng2SmartTableModule, + NbSpinnerModule, + ConfirmationModalModule, + BrowserAnimationsModule, + ToasterModule.forRoot(), + RouterModule.forChild(routes), + NbAuthModule, + PipesModule, + FileUploaderModule, + Ng2SmartTableModule, + NbSpinnerModule, + FormsModule, + ProductCategoriesFormsModule, + LocaleModule, + HttpClientModule, + I18nModule, + ], + providers: [ + DeviceService, + { + provide: APOLLO_OPTIONS, + useFactory: createApollo, + deps: [HttpLink], + }, + TranslateStore, + TranslateService, + NotifyService, + NgbActiveModal, + ProductLocalesService, + ProductsCategoryService, + TranslatePipe, + ], + }) +); + +stories.add('Category Create', () => ({ + component: CategoryCreateComponent, + props: { + storybookVersion: boolean('Storybook-Version', true), + }, +})); diff --git a/packages/admin-web-angular/src/app/@shared/product/categories/category-create/index.ts b/packages/admin-web-angular/src/app/@shared/product/categories/category-create/index.ts new file mode 100644 index 0000000..67672ce --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/product/categories/category-create/index.ts @@ -0,0 +1 @@ +export * from './category-create.component'; diff --git a/packages/admin-web-angular/src/app/@shared/product/categories/category-edit/category-edit.component.html b/packages/admin-web-angular/src/app/@shared/product/categories/category-edit/category-edit.component.html new file mode 100644 index 0000000..74b4669 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/product/categories/category-edit/category-edit.component.html @@ -0,0 +1,29 @@ + + + + + diff --git a/packages/admin-web-angular/src/app/@shared/product/categories/category-edit/category-edit.component.scss b/packages/admin-web-angular/src/app/@shared/product/categories/category-edit/category-edit.component.scss new file mode 100644 index 0000000..58836b2 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/product/categories/category-edit/category-edit.component.scss @@ -0,0 +1,25 @@ +.preview-img { + padding-left: 14px; + padding-right: 16px; +} + +.img-rounded { + max-height: 70px; +} + +.remove-icon { + cursor: pointer; + padding-left: 7px; + padding-right: 7px; + padding-top: 2px; +} + +div.modal-footer { + padding-top: 7px; + margin-right: 67px; + border: none; +} + +label { + margin: auto 0; +} diff --git a/packages/admin-web-angular/src/app/@shared/product/categories/category-edit/category-edit.component.ts b/packages/admin-web-angular/src/app/@shared/product/categories/category-edit/category-edit.component.ts new file mode 100644 index 0000000..bec2904 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/product/categories/category-edit/category-edit.component.ts @@ -0,0 +1,65 @@ +import { Component, OnDestroy } from '@angular/core'; +import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; +import { Subject } from 'rxjs'; +import { first } from 'rxjs/operators'; +import { ProductsCategoryService } from '../../../../@core/data/productsCategory.service'; +import { IProductsCategoryName } from '@modules/server.common/interfaces/IProductsCategory'; +import { ProductLocalesService } from '@modules/client.common.angular2/locale/product-locales.service'; +import { ILocaleMember } from '@modules/server.common/interfaces/ILocale'; +import { NotifyService } from '@app/@core/services/notify/notify.service'; + +@Component({ + selector: 'ea-category-edit', + templateUrl: './category-edit.component.html', + styleUrls: ['./category-edit.component.scss'], +}) +export class CategoryEditComponent implements OnDestroy { + currentCategory: { + id: string; + image: string; + title: string; + _nameLocaleValues: IProductsCategoryName[]; + }; + + userId: any; + + private _ngDestroy$ = new Subject(); + + constructor( + private readonly _activeModal: NgbActiveModal, + private readonly _productsCategoryService: ProductsCategoryService, + private readonly _productLocalesService: ProductLocalesService, + private readonly _notifyService: NotifyService + ) {} + + ngOnDestroy() { + this._ngDestroy$.next(); + this._ngDestroy$.complete(); + } + + cancel() { + this._activeModal.dismiss('canceled'); + } + + async editCategory(categoryRaw) { + try { + await this._productsCategoryService + .update(this.currentCategory.id, categoryRaw) + .pipe(first()) + .toPromise(); + + const message = `Category ${this.localeTranslate( + categoryRaw.name + )} is edited`; + this._notifyService.success(message); + this.cancel(); + } catch (err) { + const message = `Something went wrong!`; + this._notifyService.error(message); + } + } + + localeTranslate(member: ILocaleMember[]) { + return this._productLocalesService.getTranslate(member); + } +} diff --git a/packages/admin-web-angular/src/app/@shared/product/categories/category-edit/index.ts b/packages/admin-web-angular/src/app/@shared/product/categories/category-edit/index.ts new file mode 100644 index 0000000..b362c2b --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/product/categories/category-edit/index.ts @@ -0,0 +1 @@ +export * from './category-edit.component'; diff --git a/packages/admin-web-angular/src/app/@shared/product/categories/forms/basic-info/basic-info-form.component.html b/packages/admin-web-angular/src/app/@shared/product/categories/forms/basic-info/basic-info-form.component.html new file mode 100644 index 0000000..235d277 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/product/categories/forms/basic-info/basic-info-form.component.html @@ -0,0 +1,70 @@ +
+
+ +
+ + +
+ {{ + 'CATEGORY_VIEW.CREATE.ENTER_THE_CATEGORY_NAME' | translate + }}! +
+
+
+ +
+ + +
+ + +
+ {{ 'CATEGORY_VIEW.CREATE.INVALID_URL' | translate }}! +
+
+
+ +
+ +
+
+
+ Invalid image +
+
+
+
+
+
+
+
diff --git a/packages/admin-web-angular/src/app/@shared/product/categories/forms/basic-info/basic-info-form.component.scss b/packages/admin-web-angular/src/app/@shared/product/categories/forms/basic-info/basic-info-form.component.scss new file mode 100644 index 0000000..7593d2c --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/product/categories/forms/basic-info/basic-info-form.component.scss @@ -0,0 +1,18 @@ +.preview-img { + padding-left: 14px; + padding-right: 16px; +} + +.img-rounded { + max-height: 70px; +} + +.remove-icon { + div { + cursor: pointer; + } + + padding-left: 7px; + padding-right: 7px; + padding-top: 2px; +} diff --git a/packages/admin-web-angular/src/app/@shared/product/categories/forms/basic-info/basic-info-form.component.ts b/packages/admin-web-angular/src/app/@shared/product/categories/forms/basic-info/basic-info-form.component.ts new file mode 100644 index 0000000..f94f57b --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/product/categories/forms/basic-info/basic-info-form.component.ts @@ -0,0 +1,159 @@ +import { + Component, + ViewChild, + ElementRef, + OnInit, + AfterViewInit, + Input, +} from '@angular/core'; +import { + FormGroup, + Validators, + AbstractControl, + FormBuilder, +} from '@angular/forms'; +import * as isUrl from 'is-url'; +import * as _ from 'underscore'; +import { TranslateService } from '@ngx-translate/core'; +import { IProductsCategoryCreateObject } from '@modules/server.common/interfaces/IProductsCategory'; + +@Component({ + selector: 'ea-product-category-basic-info-form', + templateUrl: 'basic-info-form.component.html', + styleUrls: ['basic-info-form.component.scss'], +}) +export class BasicInfoFormComponent implements OnInit, AfterViewInit { + @ViewChild('imagePreview', { static: true }) + imagePreviewElement: ElementRef; + + @Input() + category: { title: string; image: string }; + + readonly form: FormGroup = this.fb.group({ + name: ['', Validators.required], + image: [ + '', + [ + (control: AbstractControl) => { + const imageUrl = control.value; + if (!isUrl(imageUrl) && !_.isEmpty(imageUrl)) { + return { invalidUrl: true }; + } + + return null; + }, + ], + ], + }); + + constructor( + private readonly fb: FormBuilder, + private readonly _langTranslateService: TranslateService + ) {} + + get image() { + return this.form.get('image'); + } + + get name() { + return this.form.get('name'); + } + + get isFormModelValid(): boolean { + return this.form.valid; + } + + get showImageMeta() { + return this.image && this.image.value !== ''; + } + + get usedLanguage() { + const usedLanguage = this._langTranslateService.currentLang; + switch (usedLanguage) { + case 'en-US': + return 'en-US'; + + case 'bg-BG': + return 'bg-BG'; + + case 'he-IL': + return 'he-IL'; + + case 'ru-RU': + return 'ru-RU'; + + case 'es-ES': + return 'es-ES'; + + default: + return 'en-US'; + } + } + + get createObject() { + const usedLanguage = this.usedLanguage; + + const categoryObject: IProductsCategoryCreateObject = { + name: [{ locale: usedLanguage, value: this.name.value }], + }; + if (this.showImageMeta) { + categoryObject.image = this.image.value; + } + + return categoryObject; + } + + getEditObject(currentCategory) { + const usedLanguage = this.usedLanguage; + const newCategoryNames = currentCategory._nameLocaleValues.map( + ({ locale, value }) => { + return locale === usedLanguage + ? { + locale: usedLanguage, + value: this.name.value, + } + : { locale, value }; + } + ); + if (!newCategoryNames.some((c) => c.locale === usedLanguage)) { + newCategoryNames.push({ + locale: usedLanguage, + value: this.name.value, + }); + } + + const categoryRaw: IProductsCategoryCreateObject = { + name: newCategoryNames, + image: this.image.value, + }; + + return categoryRaw; + } + + ngOnInit() { + if (this.category) { + this.name.setValue(this.category.title); + this.image.setValue(this.category.image); + } + } + + deleteImg() { + this.image.setValue(''); + } + + ngAfterViewInit() { + this._setupLogoUrlValidation(); + } + + private _setupLogoUrlValidation() { + this.imagePreviewElement.nativeElement.onload = () => { + this.image.setErrors(null); + }; + + this.imagePreviewElement.nativeElement.onerror = () => { + if (this.showImageMeta) { + this.image.setErrors({ invalidUrl: true }); + } + }; + } +} diff --git a/packages/admin-web-angular/src/app/@shared/product/categories/forms/basic-info/index.ts b/packages/admin-web-angular/src/app/@shared/product/categories/forms/basic-info/index.ts new file mode 100644 index 0000000..91afc05 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/product/categories/forms/basic-info/index.ts @@ -0,0 +1 @@ +export * from './basic-info-form.component'; diff --git a/packages/admin-web-angular/src/app/@shared/product/categories/forms/product-categories-forms.module.ts b/packages/admin-web-angular/src/app/@shared/product/categories/forms/product-categories-forms.module.ts new file mode 100644 index 0000000..4cc94e2 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/product/categories/forms/product-categories-forms.module.ts @@ -0,0 +1,32 @@ +import { NgModule } from '@angular/core'; +import { TranslateModule } from '@ngx-translate/core'; +import { BasicInfoFormComponent } from './basic-info'; +import { FileUploaderModule } from '@app/@shared/file-uploader/file-uploader.module'; +import { ThemeModule } from '@app/@theme'; +import { CommonModule } from '@angular/common'; +import { FormsModule } from '@angular/forms'; +import { CategoriesTableComponent } from '../categories-table'; +import { Ng2SmartTableModule } from 'ng2-smart-table'; +import { NbSpinnerModule } from '@nebular/theme'; +import { CategoryImageComponent } from '../categories-table/category-image.component'; +import { ConfirmationModalModule } from '@app/@shared/confirmation-modal/confirmation-modal.module'; + +@NgModule({ + imports: [ + CommonModule, + ThemeModule, + TranslateModule.forChild(), + FileUploaderModule, + Ng2SmartTableModule, + NbSpinnerModule, + FormsModule, + ConfirmationModalModule, + ], + declarations: [ + BasicInfoFormComponent, + CategoriesTableComponent, + CategoryImageComponent, + ], + exports: [BasicInfoFormComponent, CategoriesTableComponent] +}) +export class ProductCategoriesFormsModule {} diff --git a/packages/admin-web-angular/src/app/@shared/product/forms/basic-info/basic-info-form.component.html b/packages/admin-web-angular/src/app/@shared/product/forms/basic-info/basic-info-form.component.html new file mode 100644 index 0000000..ade1e30 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/product/forms/basic-info/basic-info-form.component.html @@ -0,0 +1,194 @@ +
+
+
+ +
+ + + + + {{ + 'PRODUCT_TYPE_VIEW.WIZARD_FORM.VALIDATION_MESSAGES.TITLE' + | translate + }}! + + + + {{ + 'PRODUCT_TYPE_VIEW.WIZARD_FORM.VALIDATION_MESSAGES.THE_LENGHT_OF_THE_TITLE' + | translate + }} + + +
+
+
+ +
+ + + + {{ + 'PRODUCT_TYPE_VIEW.WIZARD_FORM.VALIDATION_MESSAGES.IMAGE' + | translate + }}! + +
+
+ +
+ +
+
+
+ Invalid image +
+ +
+
+
+
+
+
+ +
+ +
+
+ +
+ + + + {{ + 'PRODUCT_TYPE_VIEW.WIZARD_FORM.VALIDATION_MESSAGES.DESCRIPTION' + | translate + }}! + + + + {{ + 'PRODUCT_TYPE_VIEW.WIZARD_FORM.VALIDATION_MESSAGES.THE_LENGHT_OF_THE_DESCRIPTION' + | translate + }} + + +
+
+ +
+ +
+ +
+
+ +
+ +
+ + +
+
+ +
+ +
+ +
+
+
+
diff --git a/packages/admin-web-angular/src/app/@shared/product/forms/basic-info/basic-info-form.component.scss b/packages/admin-web-angular/src/app/@shared/product/forms/basic-info/basic-info-form.component.scss new file mode 100644 index 0000000..050298c --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/product/forms/basic-info/basic-info-form.component.scss @@ -0,0 +1,66 @@ +.product-basic-info { + ::ng-deep .categoryOptions-dropdown { + .ng-star-inserted { + margin-top: 0 !important; + } + + .dropdown, + .dropdown-menu { + border: 2px solid #dadfe6; + border-radius: 0.375rem; + background-color: white !important; + width: 100%; + color: #111111 !important; + } + + .dropdown-menu.ng-star-inserted { + border: 2px solid #dadfe6 !important; + } + + button { + color: #111111 !important; + margin: 0 !important; + // background-color: white !important; + } + div { + a { + &:hover { + background-color: #f0f0f0 !important; + color: #111111 !important; + font-weight: bold; + font-size: 1.05em !important; + } + } + } + } + + .preview-img { + padding-left: 14px; + padding-right: 16px; + } + + .ion-md-remove-circle { + font-size: 1rem; + } + + .img-rounded { + max-height: 70px; + } + + .removeIcon div { + cursor: pointer; + } + + .images-content { + margin-left: 16px !important; + } + label.control-label { + font-size: 1rem; + } + + span { + font-size: 1rem; + display: block; + margin: 0; + } +} diff --git a/packages/admin-web-angular/src/app/@shared/product/forms/basic-info/basic-info-form.component.ts b/packages/admin-web-angular/src/app/@shared/product/forms/basic-info/basic-info-form.component.ts new file mode 100644 index 0000000..50b673c --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/product/forms/basic-info/basic-info-form.component.ts @@ -0,0 +1,430 @@ +import { + Component, + Input, + OnDestroy, + OnInit, + ViewChild, + ElementRef, +} from '@angular/core'; +import { + FormBuilder, + FormGroup, + Validators, + AbstractControl, +} from '@angular/forms'; +import { + IProductImage, + IProductCreateObject, + IProductTitle, + IProductDescription, + IProductDetails, +} from '@modules/server.common/interfaces/IProduct'; +import Product from '@modules/server.common/entities/Product'; +import { Subject } from 'rxjs'; +import { ProductLocalesService } from '@modules/client.common.angular2/locale/product-locales.service'; +import { IMultiSelectOption } from 'angular-2-dropdown-multiselect'; +import { FormHelpers } from '../../../forms/helpers'; +import { pick } from 'underscore'; +import * as isUrl from 'is-url'; +import { takeUntil, first } from 'rxjs/operators'; +import { TranslateService } from '@ngx-translate/core'; +import { + LanguageCodesEnum, + LanguagesEnum, +} from '@modules/server.common/interfaces/ILanguage'; + +@Component({ + selector: 'ea-product-basic-info-form', + styleUrls: ['./basic-info-form.component.scss'], + templateUrl: 'basic-info-form.component.html', +}) +export class BasicInfoFormComponent implements OnDestroy, OnInit { + @ViewChild('fileInput') + fileInput: any; + @ViewChild('productImagePreview') + productImagePreview: ElementRef; + + @Input() + readonly form: FormGroup; + @Input() + productCategories: any; + @Input() + currentProduct: Product; + + uploaderPlaceholder: string; + product: any; + uploaderChanged: boolean; + + title: AbstractControl; + description: AbstractControl; + details: AbstractControl; + image: AbstractControl; + locale: AbstractControl; + category: AbstractControl; + selectedProductCategories: AbstractControl; + + private _selectedProductCategories: string[]; + private actualCategories: any = []; + private _category: string; + private _title: string; + private _description: string; + private _details: string; + private _image: string; + private _locale: string; + + private _ngDestroy$ = new Subject(); + public categoryOptions: IMultiSelectOption[]; + private onLocaleChanges: any; + private images: IProductImage[] = []; + + public languages = Object.keys(LanguageCodesEnum); + + static hasValidImage(images) { + if (images) { + let imageUrls = images.toString().split(/\s+/); + imageUrls = imageUrls.filter((a) => a.toString().trim() !== ''); + + if (imageUrls.length > 0) { + for (const imageUrl of imageUrls) { + if (isUrl(imageUrl)) { + return true; + } + } + } + } + + return false; + } + + static buildForm(formBuilder: FormBuilder): FormGroup { + // would be used in the parent component and injected into this.form + return formBuilder.group({ + title: ['', [Validators.required, Validators.maxLength(255)]], + description: ['', [Validators.required, Validators.maxLength(255)]], + details: [''], + locale: ['', Validators.required], + selectedProductCategories: [[]], + image: [ + '', + [ + Validators.required, + (control: AbstractControl) => { + const value = control.value; + const hasImage = + BasicInfoFormComponent.hasValidImage(value); + if (hasImage) { + return null; + } else { + return { invalidImageUrl: true }; + } + }, + ], + ], + }); + } + + constructor( + private _productLocalesService: ProductLocalesService, + private _translateService: TranslateService + ) { + this.getUploaderPlaceholderText(); + } + + get imageControl() { + return this.form.get('image'); + } + + get imagesUrls() { + return this.images ? this.images.map((i) => i.url).join(' ') : ''; + } + + get imagesArr() { + if (this.imagesUrls) { + const imagesStr = this.imagesUrls; + + let imageUrls = imagesStr.split(/\s+/); + imageUrls = imageUrls.filter((a) => a.trim() !== ''); + + return imageUrls; + } + return null; + } + + ngOnInit() { + if (this.productCategories) { + this.categoryOptions = this.productCategories.map((category) => { + return { + id: category.id, + name: category.name[0].value, + }; + }); + } + + this._bindFormControls(); + this._setDefaultLocaleValue(); + + this.onLocaleChanges = this.locale.valueChanges + .pipe(takeUntil(this._ngDestroy$)) + .subscribe((v) => { + if (v !== this._productLocalesService.currentLocale) { + this._productLocalesService.currentLocale = v; + + if (!this.product) { + this.images.forEach((i) => (i.locale = v)); + } + + this.setValue(this.product); + } + }); + + this.laodData(); + } + + ngOnDestroy() { + this.onLocaleChanges.unsubscribe(); + this.form.reset(); + this._ngDestroy$.next(true); + this._ngDestroy$.complete(); + } + + getLanguageCode(language: LanguagesEnum) { + return LanguageCodesEnum[language]; + } + + deleteImg(image) { + this.images = this.images.filter((i) => i.url !== image); + + this.image.setValue(this.imagesUrls); + } + + addImageObj(imgData: IProductImage) { + this.uploaderChanged = true; + if (imgData) { + const existData = this.images.find((i) => i.url === imgData.url); + if (!existData) { + this.images.push(imgData); + + this.image.setValue(imgData.url); + } + } + } + + getValue(): IProductCreateObject { + return this.form.getRawValue() as IProductCreateObject; + } + + setValue(product: Product) { + if (this.productCategories) { + this.categoryOptions = this.productCategories.map((category) => { + return { + id: category.id, + name: category.name[0].value, + }; + }); + } + + FormHelpers.deepMark(this.form, 'dirty'); + + if (!this.product) { + this.product = product; + } + if (this.product) { + let image = ''; + const imgs = product.images.filter( + (i) => i.locale === this.locale.value + ); + if (imgs) { + image = imgs.map((i) => i.url).join(' '); + } + + this.images = imgs; + + const product1 = { + title: this._productLocalesService.getMemberValue( + product.title + ), + description: this._productLocalesService.getMemberValue( + product.description + ), + details: this._productLocalesService.getMemberValue( + product.details + ), + image, + locale: this.locale.value, + selectedProductCategories: [...this.product.categories], + }; + + this.form.setValue(pick(product1, Object.keys(this.getValue()))); + } + } + + async setupProductCreateObject(): Promise { + this._bindModelProperties(); + + const productLocale = this._locale; + + const productImages: IProductImage[] = this.images.filter( + (i) => i.locale === productLocale && isUrl(i.url) + ); + + const productTitle: IProductTitle = { + locale: productLocale, + value: this._title, + }; + + const productDescription: IProductDescription = { + locale: productLocale, + value: this._description, + }; + + const productDetails: IProductDetails = { + locale: productLocale, + value: this._details || '', + }; + + let productCreateObject: IProductCreateObject = { + title: [productTitle], + description: [productDescription], + details: [productDetails], + categories: this.actualCategories.map((c) => { + return { + _id: c.id, + name: c.name, + }; + }), + images: productImages, + }; + + if (this.product) { + productCreateObject = { + title: [ + ...this.product.title + .filter((t) => t.locale !== this._locale) + .map((t) => ({ locale: t.locale, value: t.value })), + productTitle, + ], + description: [ + ...this.product.description + .filter((d) => d.locale !== this._locale) + .map((d) => ({ locale: d.locale, value: d.value })), + productDescription, + ], + details: [ + ...this.product.details + .filter((d) => d.locale !== this._locale) + .map((d) => ({ locale: d.locale, value: d.value })), + productDetails, + ], + categories: this.actualCategories.map((c) => { + return { + _id: c.id, + name: c.name, + }; + }), + images: [ + ...this.product.images + .filter((i) => i.locale !== this._locale) + .map((i) => this.getProductImage(i)), + ...productImages.map((i) => this.getProductImage(i)), + ], + }; + } + + return productCreateObject; + } + + imgOnLoad() { + this.imageControl.setErrors(null); + } + imgOnError() { + if (this.imageControl.value !== '') { + const hasImage = BasicInfoFormComponent.hasValidImage( + this.image.value + ); + + if (hasImage) { + this.imageControl.setErrors(null); + } else { + this.imageControl.setErrors({ invalidUrl: true }); + } + } + } + + private _setDefaultLocaleValue() { + this.locale.setValue(this._translateService.currentLang || 'en-US'); + } + + private _bindFormControls() { + this.title = this._getFormControlByName('title'); + this.description = this._getFormControlByName('description'); + this.details = this._getFormControlByName('details'); + this.image = this._getFormControlByName('image'); + this.category = this._getFormControlByName('category'); + this.locale = this._getFormControlByName('locale'); + this.selectedProductCategories = this._getFormControlByName( + 'selectedProductCategories' + ); + } + + private _getFormControlByName(controlName: string): AbstractControl { + return this.form.get(controlName); + } + + private _bindModelProperties() { + const getInputVal = (name: string) => + this._getFormControlByName(name).value; + + this._selectedProductCategories = getInputVal( + 'selectedProductCategories' + ); + this.actualCategories = []; + + if (this._selectedProductCategories) { + for (const val of this._selectedProductCategories) { + for (const val1 of this.productCategories) { + if (val === val1.id) { + const newObj: any = {}; + newObj.name = []; + newObj.id = val1.id; + for (let i = 0; i < val1.name.length; i++) { + newObj.name[i] = {}; + newObj.name[i].locale = val1.name[i].locale; + newObj.name[i].value = val1.name[i].value; + } + this.actualCategories.push(newObj); + } + } + } + } + + // stores the IDs of categories we've selected + this._title = getInputVal('title'); + this._description = getInputVal('description'); + this._details = getInputVal('details'); + this._image = getInputVal('image'); + this._locale = getInputVal('locale'); + } + + private async getUploaderPlaceholderText() { + this.uploaderPlaceholder = await this._translateService + .get('WAREHOUSE_VIEW.PLACEHOLDER.IMAGE_URL') + .pipe(first()) + .toPromise(); + } + + private getProductImage(data: IProductImage): IProductImage { + return { + locale: data.locale, + url: data.url, + orientation: data.orientation, + width: data.width, + height: data.height, + }; + } + + private laodData() { + if (this.currentProduct) { + this.setValue(this.currentProduct); + } + } +} diff --git a/packages/admin-web-angular/src/app/@shared/product/forms/basic-info/index.ts b/packages/admin-web-angular/src/app/@shared/product/forms/basic-info/index.ts new file mode 100644 index 0000000..91afc05 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/product/forms/basic-info/index.ts @@ -0,0 +1 @@ +export * from './basic-info-form.component'; diff --git a/packages/admin-web-angular/src/app/@shared/product/forms/index.ts b/packages/admin-web-angular/src/app/@shared/product/forms/index.ts new file mode 100644 index 0000000..675093f --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/product/forms/index.ts @@ -0,0 +1,2 @@ +export * from './basic-info'; +export * from './product-forms.module'; diff --git a/packages/admin-web-angular/src/app/@shared/product/forms/product-forms.module.ts b/packages/admin-web-angular/src/app/@shared/product/forms/product-forms.module.ts new file mode 100644 index 0000000..9e1c3ed --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/product/forms/product-forms.module.ts @@ -0,0 +1,33 @@ +import { NgModule } from '@angular/core'; +import { TranslateModule } from '@ngx-translate/core'; +import { FormWizardModule } from '@ever-co/angular2-wizard'; +import { ThemeModule } from '../../../@theme/theme.module'; +import { BasicInfoFormComponent } from './basic-info'; +import { ProductsTableComponent } from './products-table'; +import { Ng2SmartTableModule } from 'ng2-smart-table'; +import { MultiselectDropdownModule } from 'angular-2-dropdown-multiselect'; +import { ProductCategoriesModule } from '../../render-component/product-categories/product-categories.module'; +import { CommonModule } from '@angular/common'; +import { NbSpinnerModule } from '@nebular/theme'; +import { ConfirmationModalModule } from '../../confirmation-modal/confirmation-modal.module'; +import { RenderComponentsModule } from '@app/@shared/render-component/render-components.module'; +import { FileUploaderModule } from '@app/@shared/file-uploader/file-uploader.module'; + +@NgModule({ + imports: [ + ThemeModule, + CommonModule, + FormWizardModule, + TranslateModule.forChild(), + MultiselectDropdownModule, + Ng2SmartTableModule, + FileUploaderModule, + ProductCategoriesModule, + NbSpinnerModule, + ConfirmationModalModule, + RenderComponentsModule, + ], + exports: [BasicInfoFormComponent, ProductsTableComponent], + declarations: [BasicInfoFormComponent, ProductsTableComponent], +}) +export class ProductFormsModule {} diff --git a/packages/admin-web-angular/src/app/@shared/product/forms/products-table/index.ts b/packages/admin-web-angular/src/app/@shared/product/forms/products-table/index.ts new file mode 100644 index 0000000..3e9a92d --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/product/forms/products-table/index.ts @@ -0,0 +1 @@ +export * from './products-table.component'; diff --git a/packages/admin-web-angular/src/app/@shared/product/forms/products-table/products-table.component.html b/packages/admin-web-angular/src/app/@shared/product/forms/products-table/products-table.component.html new file mode 100644 index 0000000..683c063 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/product/forms/products-table/products-table.component.html @@ -0,0 +1,14 @@ + + + + diff --git a/packages/admin-web-angular/src/app/@shared/product/forms/products-table/products-table.component.scss b/packages/admin-web-angular/src/app/@shared/product/forms/products-table/products-table.component.scss new file mode 100644 index 0000000..b29be2c --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/product/forms/products-table/products-table.component.scss @@ -0,0 +1,3 @@ +nb-card { + margin-bottom: 0; +} diff --git a/packages/admin-web-angular/src/app/@shared/product/forms/products-table/products-table.component.ts b/packages/admin-web-angular/src/app/@shared/product/forms/products-table/products-table.component.ts new file mode 100644 index 0000000..47f172b --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/product/forms/products-table/products-table.component.ts @@ -0,0 +1,327 @@ +import { + Component, + Input, + OnDestroy, + OnInit, + EventEmitter, +} from '@angular/core'; +import { DomSanitizer } from '@angular/platform-browser'; +import { LocalDataSource } from 'ng2-smart-table'; +import Product from '@modules/server.common/entities/Product'; +import { ProductsService } from '../../../../@core/data/products.service'; +import { Router } from '@angular/router'; +import { first, takeUntil } from 'rxjs/operators'; +import { TranslateService } from '@ngx-translate/core'; +import { forkJoin, Observable, Subject, Subscription } from 'rxjs'; +import { ILocaleMember } from '@modules/server.common/interfaces/ILocale'; +import { ProductLocalesService } from '@modules/client.common.angular2/locale/product-locales.service'; +import { ProductCategoriesComponent } from '../../../render-component/product-categories/product-categories'; +import { ProductsCategoryService } from '../../../../@core/data/productsCategory.service'; +import { NotifyService } from '@app/@core/services/notify/notify.service'; +import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; +import { ConfirmationModalComponent } from '../../../confirmation-modal/confirmation-modal.component'; +import { ProductCheckboxComponent } from '@app/@shared/render-component/product-checkbox/product-checkbox'; +import { ProductTitleComponent } from '@app/@shared/render-component/product-title/product-title.component'; +import { ProductImageComponent } from '@app/@shared/render-component/product-image/product-image.component'; + +interface ProductViewModel { + id: string; + title: string; + description: string; + details: string; + categories: string[]; + images: string[]; +} + +@Component({ + selector: 'ea-products-table', + styleUrls: ['./products-table.component.scss'], + templateUrl: 'products-table.component.html', +}) +export class ProductsTableComponent implements OnInit, OnDestroy { + confirmSub$: Subscription; + settingsSmartTable: object; + sourceSmartTable = new LocalDataSource(); + loading: boolean; + selectProducts$: EventEmitter = new EventEmitter(); + $subSlectProducts: Subscription; + pagesChanges$: EventEmitter = new EventEmitter(); + + @Input() + perPage: number = 0; + @Input() + hiddenTableActions: boolean; + @Input() + boxShadow: string; + + private static noInfoSign = ''; + private _selectedProducts: ProductViewModel[] = []; + private products: Product[]; + private categoriesInfo: any; + private ngDestroy$ = new Subject(); + private dataCount: number; + + constructor( + private readonly _sanitizer: DomSanitizer, + private readonly _productsService: ProductsService, + private readonly _router: Router, + private readonly _translateService: TranslateService, + private readonly _productLocalesService: ProductLocalesService, + private readonly _productsCategoryService: ProductsCategoryService, + private readonly _notifyService: NotifyService, + private readonly modalService: NgbModal + ) {} + + ngOnInit(): void { + this.getCategories(); + this._loadSettingsSmartTable(); + this._applyTranslationOnSmartTable(); + this.smartTableChange(); + } + + edit(event) { + this._router.navigate(['/products/list/' + event.data.id + '/edit']); + } + + async deleteProduct(event) { + const activeModal = this.modalService.open(ConfirmationModalComponent, { + size: 'sm', + container: 'nb-layout', + backdrop: 'static', + }); + const modalComponent: ConfirmationModalComponent = + activeModal.componentInstance; + + this.confirmSub$ = await modalComponent.confirmEvent + .pipe(takeUntil(modalComponent.ngDestroy$)) + .subscribe((dataEvent) => { + try { + this.loading = true; + const productTitle = + event.data.title || ProductsTableComponent.noInfoSign; + this._productsService + .removeByIds([event.data.id]) + .pipe(first()) + .toPromise(); + this.loading = false; + const message = `Product ${productTitle} is deleted`; + this._notifyService.success(message); + } catch (error) { + let message = `Something went wrong`; + if (error.message === 'Validation error') { + message = error.message; + } + this.loading = false; + this._notifyService.error(message); + } + modalComponent.cancel(); + }); + } + + public get hasSelectedProducts(): boolean { + return this._selectedProducts.length > 0; + } + + public get selectedProducts() { + return [...this._selectedProducts]; + } + + public set selectedProducts(products) { + this._selectedProducts = products; + } + + getCategories() { + this._productsCategoryService + .getCategories() + .subscribe((categories) => { + this.categoriesInfo = categories; + this.loadDataSmartTable( + this.products || [], + this.dataCount || 0 + ); + }); + } + + selectProductTmp(ev) { + if (ev.data) { + this.selectProducts$.emit({ + current: ev.data, + allData: ev.source.data, + }); + } + } + + async loadDataSmartTable( + products: Product[], + dataCount: number, + page: number = 1 + ) { + this.dataCount = dataCount; + this.products = products; + let productsVM = products.map((product) => { + return { + checked: this.selectedProducts.find( + (d) => d.id === product['id'] + ), + title: + this.localeTranslate(product.title) || + ProductsTableComponent.noInfoSign, + description: + this.localeTranslate(product.description) || + ProductsTableComponent.noInfoSign, + details: product.details[0] + ? this.localeTranslate(product.details) || + ProductsTableComponent.noInfoSign + : ProductsTableComponent.noInfoSign, + categories: { + ids: product.categories, + search: + this.categoriesInfo && + this.categoriesInfo + .filter((c) => product.categories.includes(c.id)) + .map((c) => + this._productLocalesService.getTranslate(c.name) + ) + .toString(), + }, + image: + this.localeTranslate(product.images) || + ProductsTableComponent.noInfoSign, + id: product.id, + allCategories: this.categoriesInfo, + }; + }); + productsVM = productsVM.filter((p) => p); + if (this.$subSlectProducts) { + this.$subSlectProducts.unsubscribe(); + } + this.$subSlectProducts = this.selectProducts$ + .pipe(takeUntil(this.ngDestroy$)) + .subscribe(({ current, allData }) => { + allData.find((d) => d && d.id === current['id'])['checked'] = + !current.checked; + + if (current.checked) { + this._selectedProducts.push(current); + } else { + this._selectedProducts = this._selectedProducts.filter( + (p) => p.id !== current.id + ); + } + this.sourceSmartTable.load(allData); + }); + + const productsData = new Array(dataCount); + productsData.splice( + this.perPage * (page - 1), + this.perPage, + ...productsVM + ); + this.sourceSmartTable.load(productsData); + } + + protected localeTranslate(member: ILocaleMember[]) { + return this._productLocalesService.getTranslate(member); + } + + private async smartTableChange() { + this.sourceSmartTable + .onChanged() + .pipe(takeUntil(this.ngDestroy$)) + .subscribe(async (event) => { + if (event.action === 'page') { + const page = event.paging.page; + this.pagesChanges$.emit(page); + } + }); + } + + private _loadSettingsSmartTable() { + const columnTitlePrefix = 'WAREHOUSE_VIEW.SELECT_PRODUCTS.'; + const getTranslate = (name: string): Observable => + this._translateService.get(columnTitlePrefix + name); + + forkJoin( + this._translateService.get('Id'), + getTranslate('TITLE'), + getTranslate('DESCRIPTION'), + getTranslate('DETAILS'), + getTranslate('IMAGES'), + getTranslate('CATEGORY') + ) + .pipe(takeUntil(this.ngDestroy$)) + .subscribe(([id, name, description, details, images, category]) => { + this.settingsSmartTable = { + actions: !this.hiddenTableActions && { + add: false, + position: 'left', + }, + edit: { + editButtonContent: '', + }, + delete: { + deleteButtonContent: + '', + confirmDelete: true, + }, + mode: 'external', + columns: { + checkbox: { + title: '', + filter: false, + type: 'custom', + renderComponent: ProductCheckboxComponent, + }, + title: { + title: name, + type: 'custom', + renderComponent: ProductTitleComponent, + }, + description: { title: description }, + details: { title: details }, + categories: { + title: category, + type: 'custom', + renderComponent: ProductCategoriesComponent, + filterFunction( + cell?: any, + search?: string + ): boolean { + if (cell.search.includes(search)) { + return true; + } else { + return false; + } + }, + }, + images: { + title: images, + type: 'custom', + renderComponent: ProductImageComponent, + }, + }, + pager: { + display: true, + perPage: this.perPage, + }, + }; + }); + } + + private _applyTranslationOnSmartTable() { + this._translateService.onLangChange + .pipe(takeUntil(this.ngDestroy$)) + .subscribe((res) => { + this._loadSettingsSmartTable(); + this.loadDataSmartTable( + this.products || [], + this.dataCount || 0 + ); + }); + } + + ngOnDestroy() { + this.ngDestroy$.next(); + this.ngDestroy$.complete(); + } +} diff --git a/packages/admin-web-angular/src/app/@shared/product/product-create/index.ts b/packages/admin-web-angular/src/app/@shared/product/product-create/index.ts new file mode 100644 index 0000000..95173c1 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/product/product-create/index.ts @@ -0,0 +1,2 @@ +export * from './product-create.component'; +export * from './product-create.module'; diff --git a/packages/admin-web-angular/src/app/@shared/product/product-create/product-create.component.html b/packages/admin-web-angular/src/app/@shared/product/product-create/product-create.component.html new file mode 100644 index 0000000..e0570f3 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/product/product-create/product-create.component.html @@ -0,0 +1,29 @@ + +
+ + + + + +
\ No newline at end of file diff --git a/packages/admin-web-angular/src/app/@shared/product/product-create/product-create.component.scss b/packages/admin-web-angular/src/app/@shared/product/product-create/product-create.component.scss new file mode 100644 index 0000000..17bcf0b --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/product/product-create/product-create.component.scss @@ -0,0 +1,15 @@ +:host ::ng-deep .card-footer button { + background-color: #111111 !important; + border: none !important; + color: white !important; + cursor: pointer !important; +} + +:host ::ng-deep .card-footer button:hover { + background-color: #bebebe !important; + color: #111111 !important; +} + +:host ::ng-deep nb-card { + background-color: white !important; +} diff --git a/packages/admin-web-angular/src/app/@shared/product/product-create/product-create.component.ts b/packages/admin-web-angular/src/app/@shared/product/product-create/product-create.component.ts new file mode 100644 index 0000000..cf96cdc --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/product/product-create/product-create.component.ts @@ -0,0 +1,68 @@ +import { Component, OnInit, ViewChild } from '@angular/core'; +import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; +import { FormBuilder, FormControl, FormGroup } from '@angular/forms'; +import { BasicInfoFormComponent } from '../forms'; +import { IProductCreateObject } from '@modules/server.common/interfaces/IProduct'; +import { ProductsService } from '../../../@core/data/products.service'; +import { firstValueFrom } from 'rxjs'; +import { first } from 'rxjs/operators'; +import { TranslateService } from '@ngx-translate/core'; +import { NotifyService } from '@app/@core/services/notify/notify.service'; + +@Component({ + selector: 'ea-product-create', + templateUrl: './product-create.component.html', + styleUrls: ['./product-create.component.scss'], +}) +export class ProductCreateComponent implements OnInit { + @ViewChild('basicInfoForm', { static: true }) + basicInfoForm: BasicInfoFormComponent; + + public loading: boolean; + public productsCategories: any; + + readonly form: FormGroup = this._formBuilder.group({ + basicInfo: BasicInfoFormComponent.buildForm(this._formBuilder), + }); + + readonly basicInfo = this.form.get('basicInfo') as FormControl; + + constructor( + private readonly _activeModal: NgbActiveModal, + private readonly _formBuilder: FormBuilder, + private readonly _productsService: ProductsService, + private readonly _translateService: TranslateService, + private readonly _notifyService: NotifyService + ) {} + + ngOnInit() { + this.basicInfoForm.productCategories = this.productsCategories; + } + + async createProduct() { + if (this.basicInfo.valid) { + const productCreateObject: IProductCreateObject = await this.basicInfoForm.setupProductCreateObject(); + try { + this.loading = true; + await firstValueFrom( + this._productsService.create(productCreateObject).pipe(first()) + ).finally(() => { + this.loading = false; + }) + + const message = `Product ${productCreateObject.title[0].value} is created`; + this._notifyService.success(message); + this.cancelModal(); + } catch (error) { + const message = `Something went wrong!`; + this.loading = false; + this._notifyService.error(message); + this.cancelModal(); + } + } + } + + public cancelModal() { + this._activeModal.dismiss('canceled'); + } +} diff --git a/packages/admin-web-angular/src/app/@shared/product/product-create/product-create.module.ts b/packages/admin-web-angular/src/app/@shared/product/product-create/product-create.module.ts new file mode 100644 index 0000000..2a94470 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/product/product-create/product-create.module.ts @@ -0,0 +1,20 @@ +import { NgModule } from '@angular/core'; +import { ThemeModule } from '../../../@theme/theme.module'; +import { FormWizardModule } from '@ever-co/angular2-wizard'; +import { ProductCreateComponent } from './product-create.component'; +import { ProductFormsModule } from '../forms'; +import { NbSpinnerModule } from '@nebular/theme'; +import { TranslateModule } from '@app/@shared/translate/translate.module'; + +@NgModule({ + imports: [ + ThemeModule, + FormWizardModule, + TranslateModule, + ProductFormsModule, + NbSpinnerModule, + ], + exports: [ProductCreateComponent], + declarations: [ProductCreateComponent] +}) +export class ProductCreateModule {} diff --git a/packages/admin-web-angular/src/app/@shared/render-component/carrier-orders-table/carrier-orders-table.module.ts b/packages/admin-web-angular/src/app/@shared/render-component/carrier-orders-table/carrier-orders-table.module.ts new file mode 100644 index 0000000..9e9e70f --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/render-component/carrier-orders-table/carrier-orders-table.module.ts @@ -0,0 +1,15 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { ThemeModule } from '../../../@theme'; +import { TranslateModule } from '@ngx-translate/core'; +import { StoreOrderComponent } from './store-order.component'; +import { UserOrderComponent } from './user-order-component'; + +const COMPONENTS = [StoreOrderComponent, UserOrderComponent]; + +@NgModule({ + imports: [CommonModule, ThemeModule, TranslateModule.forChild()], + declarations: COMPONENTS, + exports: COMPONENTS +}) +export class CarrierOrdersTableModule {} diff --git a/packages/admin-web-angular/src/app/@shared/render-component/carrier-orders-table/store-order.component.html b/packages/admin-web-angular/src/app/@shared/render-component/carrier-orders-table/store-order.component.html new file mode 100644 index 0000000..5776be4 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/render-component/carrier-orders-table/store-order.component.html @@ -0,0 +1,16 @@ +
+ + + {{ rowData?.warehouse?.name }} + + + {{ rowData?.warehouse?.geoLocation?.city }}, + {{ rowData?.warehouse?.geoLocation?.house }}, + {{ rowData?.warehouse?.geoLocation?.postcode }}, + {{ rowData?.warehouse?.geoLocation?.countryName }} + +
diff --git a/packages/admin-web-angular/src/app/@shared/render-component/carrier-orders-table/store-order.component.ts b/packages/admin-web-angular/src/app/@shared/render-component/carrier-orders-table/store-order.component.ts new file mode 100644 index 0000000..bb6adc1 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/render-component/carrier-orders-table/store-order.component.ts @@ -0,0 +1,33 @@ +import { Component, Input, OnInit } from '@angular/core'; +import { ViewCell } from 'ng2-smart-table'; +import { Router } from '@angular/router'; + +@Component({ + templateUrl: './store-order.component.html', + styles: [ + ` + .warehouse-image { + width: 30px; + height: 30px; + margin-right: 5px !important; + margin-bottom: 3px !important; + } + `, + ], +}) +export class StoreOrderComponent implements ViewCell, OnInit { + value: string | number; + + @Input() + rowData: any; + + constructor(private readonly router: Router) {} + + ngOnInit() {} + + redirect() { + if (this.rowData.warehouse.id) { + this.router.navigate([`stores/${this.rowData.warehouse.id}`]); + } + } +} diff --git a/packages/admin-web-angular/src/app/@shared/render-component/carrier-orders-table/user-order-component.html b/packages/admin-web-angular/src/app/@shared/render-component/carrier-orders-table/user-order-component.html new file mode 100644 index 0000000..420724d --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/render-component/carrier-orders-table/user-order-component.html @@ -0,0 +1,18 @@ +
+ + + {{ rowData?.user?.firstName }} + {{ rowData?.user?.lastName }} + + + {{ rowData?.user?.geoLocation?.city }}, {{ + rowData?.user?.geoLocation?.house }}, {{ + rowData?.user?.geoLocation?.postcode }}, {{ + rowData?.user?.geoLocation?.countryName }} + +
diff --git a/packages/admin-web-angular/src/app/@shared/render-component/carrier-orders-table/user-order-component.ts b/packages/admin-web-angular/src/app/@shared/render-component/carrier-orders-table/user-order-component.ts new file mode 100644 index 0000000..dc9ed1c --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/render-component/carrier-orders-table/user-order-component.ts @@ -0,0 +1,33 @@ +import { Component, Input, OnInit } from '@angular/core'; +import { ViewCell } from 'ng2-smart-table'; +import { Router } from '@angular/router'; + +@Component({ + templateUrl: './user-order-component.html', + styles: [ + ` + .user-image { + width: 30px; + height: 30px; + margin-right: 5px !important; + margin-bottom: 3px !important; + } + `, + ], +}) +export class UserOrderComponent implements ViewCell, OnInit { + value: string | number; + + @Input() + rowData: any; + + constructor(private readonly router: Router) {} + + ngOnInit() {} + + redirect() { + if (this.rowData.user.id) { + this.router.navigate([`customers/list/${this.rowData.user.id}`]); + } + } +} diff --git a/packages/admin-web-angular/src/app/@shared/render-component/carriers-table/carrier-actions.component.ts b/packages/admin-web-angular/src/app/@shared/render-component/carriers-table/carrier-actions.component.ts new file mode 100644 index 0000000..2fb4cbd --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/render-component/carriers-table/carrier-actions.component.ts @@ -0,0 +1,45 @@ +import { Component, Input, OnInit } from '@angular/core'; +import { ViewCell } from 'ng2-smart-table'; +import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; +import { CarrierTableInfoComponent } from '@app/pages/+carriers/+carrier/carrier-info.component'; +import { CarrierLocationComponent } from '@app/pages/+carriers/+carrier/location/carrier-location.component'; + +@Component({ + template: ` +
+

+ +

+

+
+ `, +}) +export class CarrierActionsComponent implements ViewCell, OnInit { + value: string | number; + baseUrl: string; + + @Input() + rowData: any; + + constructor(private readonly modalService: NgbModal) {} + + ngOnInit() {} + openInfo() { + const activeModal = this.modalService.open(CarrierTableInfoComponent, { + size: 'sm', + container: 'nb-layout', + }); + const modalComponent: CarrierTableInfoComponent = + activeModal.componentInstance; + modalComponent.carrierId = this.rowData.id; + } + openMap() { + const activeModal = this.modalService.open(CarrierLocationComponent, { + size: 'sm', + windowClass: 'map-modal', + }); + const modalComponent: CarrierLocationComponent = + activeModal.componentInstance; + modalComponent.carrierId = this.rowData.id; + } +} diff --git a/packages/admin-web-angular/src/app/@shared/render-component/carriers-table/carrier-actions/carrier-actions.component.html b/packages/admin-web-angular/src/app/@shared/render-component/carriers-table/carrier-actions/carrier-actions.component.html new file mode 100644 index 0000000..5825f6c --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/render-component/carriers-table/carrier-actions/carrier-actions.component.html @@ -0,0 +1,6 @@ +
+
+ +
+
+
diff --git a/packages/admin-web-angular/src/app/@shared/render-component/carriers-table/carrier-actions/carrier-actions.component.ts b/packages/admin-web-angular/src/app/@shared/render-component/carriers-table/carrier-actions/carrier-actions.component.ts new file mode 100644 index 0000000..3f25420 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/render-component/carriers-table/carrier-actions/carrier-actions.component.ts @@ -0,0 +1,39 @@ +import { Component, Input, OnInit } from '@angular/core'; +import { ViewCell } from 'ng2-smart-table'; +import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; +import { CarrierLocationComponent } from '@app/pages/+carriers/+carrier/location/carrier-location.component'; +import { CarrierTableInfoComponent } from '@app/pages/+carriers/+carrier/carrier-info.component'; + +@Component({ + templateUrl: './carrier-actions.component.html', + styles: [' .iconsCont {cursor: pointer}'], +}) +export class CarrierActionsComponent implements ViewCell, OnInit { + value: string | number; + baseUrl: string; + + @Input() + rowData: any; + + constructor(private readonly modalService: NgbModal) {} + + ngOnInit() {} + openInfo() { + const activeModal = this.modalService.open(CarrierTableInfoComponent, { + size: 'lg', + container: 'nb-layout', + }); + const modalComponent: CarrierTableInfoComponent = + activeModal.componentInstance; + modalComponent.carrierId = this.rowData.id; + } + openMap() { + const activeModal = this.modalService.open(CarrierLocationComponent, { + size: 'sm', + windowClass: 'map-modal', + }); + const modalComponent: CarrierLocationComponent = + activeModal.componentInstance; + modalComponent.carrierId = this.rowData.id; + } +} diff --git a/packages/admin-web-angular/src/app/@shared/render-component/carriers-table/carrier-image/carrier-image.component.html b/packages/admin-web-angular/src/app/@shared/render-component/carriers-table/carrier-image/carrier-image.component.html new file mode 100644 index 0000000..251330f --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/render-component/carriers-table/carrier-image/carrier-image.component.html @@ -0,0 +1,6 @@ + diff --git a/packages/admin-web-angular/src/app/@shared/render-component/carriers-table/carrier-image/carrier-image.component.scss b/packages/admin-web-angular/src/app/@shared/render-component/carriers-table/carrier-image/carrier-image.component.scss new file mode 100644 index 0000000..45158e4 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/render-component/carriers-table/carrier-image/carrier-image.component.scss @@ -0,0 +1,8 @@ +img { + width: 64px; + height: 64px; +} + +.redirect-image { + cursor: pointer; +} diff --git a/packages/admin-web-angular/src/app/@shared/render-component/carriers-table/carrier-image/carrier-image.component.ts b/packages/admin-web-angular/src/app/@shared/render-component/carriers-table/carrier-image/carrier-image.component.ts new file mode 100644 index 0000000..d40a572 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/render-component/carriers-table/carrier-image/carrier-image.component.ts @@ -0,0 +1,26 @@ +import { OnInit, Component } from '@angular/core'; +import { Router } from '@angular/router'; +import { ViewCell } from 'ng2-smart-table'; + +@Component({ + styleUrls: ['carrier-image.component.scss'], + templateUrl: 'carrier-image.component.html', +}) +export class CarrierImageComponent implements ViewCell, OnInit { + value: any; + rowData: any; + imageUrl: string; + redirectPage: string; + + constructor(private readonly router: Router) {} + + ngOnInit() { + this.imageUrl = this.rowData.image; + } + + redirect() { + if (this.redirectPage) { + this.router.navigate([`${this.redirectPage}/${this.rowData.id}`]); + } + } +} diff --git a/packages/admin-web-angular/src/app/@shared/render-component/carriers-table/carrier-phone/carrier-phone.component.html b/packages/admin-web-angular/src/app/@shared/render-component/carriers-table/carrier-phone/carrier-phone.component.html new file mode 100644 index 0000000..24433ee --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/render-component/carriers-table/carrier-phone/carrier-phone.component.html @@ -0,0 +1,5 @@ + diff --git a/packages/admin-web-angular/src/app/@shared/render-component/carriers-table/carrier-phone/carrier-phone.component.scss b/packages/admin-web-angular/src/app/@shared/render-component/carriers-table/carrier-phone/carrier-phone.component.scss new file mode 100644 index 0000000..c942bb6 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/render-component/carriers-table/carrier-phone/carrier-phone.component.scss @@ -0,0 +1,12 @@ +div a { + i { + display: flex; + align-items: center; + justify-content: center; + width: 18px; + height: 18px; + } + align-items: center; + display: flex; + text-decoration: none; +} diff --git a/packages/admin-web-angular/src/app/@shared/render-component/carriers-table/carrier-phone/carrier-phone.component.ts b/packages/admin-web-angular/src/app/@shared/render-component/carriers-table/carrier-phone/carrier-phone.component.ts new file mode 100644 index 0000000..59e5dbf --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/render-component/carriers-table/carrier-phone/carrier-phone.component.ts @@ -0,0 +1,12 @@ +import { Component, Input } from '@angular/core'; + +@Component({ + styleUrls: ['./carrier-phone.component.scss'], + templateUrl: './carrier-phone.component.html', +}) +export class CarrierPhoneComponent { + @Input() + rowData: any; + + constructor() {} +} diff --git a/packages/admin-web-angular/src/app/@shared/render-component/carriers-table/carriers-table.module.ts b/packages/admin-web-angular/src/app/@shared/render-component/carriers-table/carriers-table.module.ts new file mode 100644 index 0000000..c7cd0e8 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/render-component/carriers-table/carriers-table.module.ts @@ -0,0 +1,28 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { ThemeModule } from '../../../@theme'; +import { CarrierTableInfoComponent } from '../../../pages/+carriers/+carrier/carrier-info.component'; +import { CarrierImageComponent } from './carrier-image/carrier-image.component'; +import { TranslateModule } from '@ngx-translate/core'; +import { CarrierPhoneComponent } from './carrier-phone/carrier-phone.component'; +import { CarrierActionsComponent } from './carrier-actions/carrier-actions.component'; +import { HighlightModule } from 'ngx-highlightjs'; + +const COMPONENTS = [ + CarrierActionsComponent, + CarrierTableInfoComponent, + CarrierImageComponent, + CarrierPhoneComponent, +]; + +@NgModule({ + imports: [ + CommonModule, + ThemeModule, + HighlightModule, + TranslateModule.forChild(), + ], + declarations: COMPONENTS, + exports: COMPONENTS +}) +export class CarriersTableModule {} diff --git a/packages/admin-web-angular/src/app/@shared/render-component/created/created.component.html b/packages/admin-web-angular/src/app/@shared/render-component/created/created.component.html new file mode 100644 index 0000000..6a0ba3e --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/render-component/created/created.component.html @@ -0,0 +1,6 @@ +Created: + {{ rowData?.createdAt | amLocal | amDateFormat: 'DD.MM.YYYY HH:mm' }} + +Elapsed: + diff --git a/packages/admin-web-angular/src/app/@shared/render-component/created/created.component.ts b/packages/admin-web-angular/src/app/@shared/render-component/created/created.component.ts new file mode 100644 index 0000000..dfe5a0d --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/render-component/created/created.component.ts @@ -0,0 +1,81 @@ +import { + Component, + Input, + OnInit, + OnDestroy, + ElementRef, + ViewChild, +} from '@angular/core'; +import { ViewCell } from 'ng2-smart-table'; +import { timer, Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; +import { getDifferenceFromTimes } from '../../../@core/utils/getDifferenceFromTimes '; +import OrderStatus from '@modules/server.common/enums/OrderStatus'; + +@Component({ + templateUrl: './created.component.html', +}) +export class CreatedComponent implements ViewCell, OnInit, OnDestroy { + @ViewChild('createdTimer', { static: true }) + createdTimer: ElementRef; + + private ngDestroy$ = new Subject(); + + @Input() + value: string | number; + @Input() + rowData: any; + + createdAt: string; + + get notProcessing() { + return this.rowData && this.rowData.status >= OrderStatus.Delivered; + } + + ngOnInit() { + this.createdAt = new Date( + this.rowData.createdAt + ).toLocaleDateString(); + if (!this.notProcessing) { + if (this.rowData.startDeliveryTime) { + this.updateTimer(); + } + } else { + if ( + this.rowData && + this.rowData.startDeliveryTime && + (this.rowData.deliveryTime || + this.rowData.finishedProcessingTime) + ) { + const start = new Date( + this.rowData.deliveryTime || + this.rowData.finishedProcessingTime + ); + const end = new Date(this.rowData.startDeliveryTime); + + this.showTime(start, end); + } + } + } + + updateTimer() { + timer(1, 1000) + .pipe(takeUntil(this.ngDestroy$)) + .subscribe(() => { + const start = new Date(); + const end = new Date(this.rowData.startDeliveryTime); + + this.showTime(start, end); + }); + } + private showTime(start, end) { + const time = getDifferenceFromTimes(start, end); + + this.createdTimer.nativeElement.innerText = time; + } + + ngOnDestroy() { + this.ngDestroy$.next(); + this.ngDestroy$.complete(); + } +} diff --git a/packages/admin-web-angular/src/app/@shared/render-component/customer-email/customer-email.component.html b/packages/admin-web-angular/src/app/@shared/render-component/customer-email/customer-email.component.html new file mode 100644 index 0000000..31a83d2 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/render-component/customer-email/customer-email.component.html @@ -0,0 +1,5 @@ + diff --git a/packages/admin-web-angular/src/app/@shared/render-component/customer-email/customer-email.component.scss b/packages/admin-web-angular/src/app/@shared/render-component/customer-email/customer-email.component.scss new file mode 100644 index 0000000..c942bb6 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/render-component/customer-email/customer-email.component.scss @@ -0,0 +1,12 @@ +div a { + i { + display: flex; + align-items: center; + justify-content: center; + width: 18px; + height: 18px; + } + align-items: center; + display: flex; + text-decoration: none; +} diff --git a/packages/admin-web-angular/src/app/@shared/render-component/customer-email/customer-email.component.ts b/packages/admin-web-angular/src/app/@shared/render-component/customer-email/customer-email.component.ts new file mode 100644 index 0000000..cec9110 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/render-component/customer-email/customer-email.component.ts @@ -0,0 +1,12 @@ +import { Component, Input } from '@angular/core'; + +@Component({ + styleUrls: ['./customer-email.component.scss'], + templateUrl: './customer-email.component.html', +}) +export class CustomerEmailComponent { + @Input() + rowData: any; + + constructor() {} +} diff --git a/packages/admin-web-angular/src/app/@shared/render-component/customer-orders-table/checkbox/checkbox.component.html b/packages/admin-web-angular/src/app/@shared/render-component/customer-orders-table/checkbox/checkbox.component.html new file mode 100644 index 0000000..46b5ea4 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/render-component/customer-orders-table/checkbox/checkbox.component.html @@ -0,0 +1,3 @@ +
+ +
diff --git a/packages/admin-web-angular/src/app/@shared/render-component/customer-orders-table/checkbox/checkbox.component.ts b/packages/admin-web-angular/src/app/@shared/render-component/customer-orders-table/checkbox/checkbox.component.ts new file mode 100644 index 0000000..7e02f3b --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/render-component/customer-orders-table/checkbox/checkbox.component.ts @@ -0,0 +1,41 @@ +import { Component, Input, EventEmitter, OnInit } from '@angular/core'; +import { ViewCell } from 'ng2-smart-table'; + +@Component({ + templateUrl: './checkbox.component.html', +}) +export class CheckboxComponent implements ViewCell, OnInit { + @Input() + value: string | number; + + @Input() + rowData: any; + + checked: boolean; + type: 'takeaway' | 'delivery'; + + takeProductDelivery: boolean; + takeProductTakeaway: boolean; + + newValue: EventEmitter = new EventEmitter(); + id: EventEmitter = new EventEmitter(); + + constructor() {} + + onChange() { + this.newValue.emit({ + type: this.type, + checked: this.checked, + id: this.rowData.id, + }); + } + + ngOnInit() { + this.id.emit(this.rowData.id); + + this.checked = + this.type === 'takeaway' + ? this.rowData.takeProductTakeaway + : this.rowData.takeProductDelivery; + } +} diff --git a/packages/admin-web-angular/src/app/@shared/render-component/customer-orders-table/customer-order-actions.component.ts b/packages/admin-web-angular/src/app/@shared/render-component/customer-orders-table/customer-order-actions.component.ts new file mode 100644 index 0000000..8d52ebc --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/render-component/customer-orders-table/customer-order-actions.component.ts @@ -0,0 +1,52 @@ +import { Component, Input, OnInit } from '@angular/core'; +import { ViewCell } from 'ng2-smart-table'; +import { OrderInfoComponent } from '../../../pages/+customers/+customer/ea-customer-orders/order-info/order-info.component'; +import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; +import { OrderCancelComponent } from '../../../pages/+customers/+customer/ea-customer-orders/order-cancel/order-cancel.component'; + +@Component({ + template: ` +
+

+ +

+

+ +

+
+ `, +}) +export class CustomerOrderActionsComponent implements ViewCell, OnInit { + value: string | number; + baseUrl: string; + + @Input() + rowData: any; + + constructor(private readonly modalService: NgbModal) {} + + ngOnInit() {} + + openInfo() { + const activeModal = this.modalService.open(OrderInfoComponent, { + size: 'lg', + container: 'nb-layout', + }); + const modalComponent: OrderInfoComponent = + activeModal.componentInstance; + modalComponent.selectedOrder = this.rowData; + modalComponent.orderId = this.rowData.id; + modalComponent.storeId = this.rowData.warehouseId; + modalComponent.carrierId = this.rowData.carrierId; + } + + openCancel() { + const activeModal = this.modalService.open(OrderCancelComponent, { + size: 'sm', + container: 'nb-layout', + }); + const modalComponent: OrderCancelComponent = + activeModal.componentInstance; + modalComponent.orderId = this.rowData.id; + } +} diff --git a/packages/admin-web-angular/src/app/@shared/render-component/customer-orders-table/customer-order-actions/customer-order-actions.component.html b/packages/admin-web-angular/src/app/@shared/render-component/customer-orders-table/customer-order-actions/customer-order-actions.component.html new file mode 100644 index 0000000..9f90078 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/render-component/customer-orders-table/customer-order-actions/customer-order-actions.component.html @@ -0,0 +1,8 @@ +
+
+ +
+
+ +
+
diff --git a/packages/admin-web-angular/src/app/@shared/render-component/customer-orders-table/customer-order-actions/customer-order-actions.component.ts b/packages/admin-web-angular/src/app/@shared/render-component/customer-orders-table/customer-order-actions/customer-order-actions.component.ts new file mode 100644 index 0000000..bd422a6 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/render-component/customer-orders-table/customer-order-actions/customer-order-actions.component.ts @@ -0,0 +1,43 @@ +import { Component, Input, OnInit } from '@angular/core'; +import { ViewCell } from 'ng2-smart-table'; +import { OrderInfoComponent } from '../../../../pages/+customers/+customer/ea-customer-orders/order-info/order-info.component'; +import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; +import { OrderCancelComponent } from '../../../../pages/+customers/+customer/ea-customer-orders/order-cancel/order-cancel.component'; + +@Component({ + templateUrl: './customer-order-actions.component.html', +}) +export class CustomerOrderActionsComponent implements ViewCell, OnInit { + value: string | number; + baseUrl: string; + + @Input() + rowData: any; + + constructor(private readonly modalService: NgbModal) {} + + ngOnInit() {} + + openInfo() { + const activeModal = this.modalService.open(OrderInfoComponent, { + size: 'lg', + container: 'nb-layout', + }); + const modalComponent: OrderInfoComponent = + activeModal.componentInstance; + modalComponent.selectedOrder = this.rowData; + modalComponent.orderId = this.rowData.id; + modalComponent.storeId = this.rowData.warehouseId; + modalComponent.carrierId = this.rowData.carrierId; + } + + openCancel() { + const activeModal = this.modalService.open(OrderCancelComponent, { + size: 'sm', + container: 'nb-layout', + }); + const modalComponent: OrderCancelComponent = + activeModal.componentInstance; + modalComponent.orderId = this.rowData.id; + } +} diff --git a/packages/admin-web-angular/src/app/@shared/render-component/customer-orders-table/customer-orders-table.module.ts b/packages/admin-web-angular/src/app/@shared/render-component/customer-orders-table/customer-orders-table.module.ts new file mode 100644 index 0000000..ce1d1cd --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/render-component/customer-orders-table/customer-orders-table.module.ts @@ -0,0 +1,30 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { ThemeModule } from '../../../@theme'; +import { RedirectStoreComponent } from './redirect-store/redirect-store.component'; +import { RedirectCarrierComponent } from './redirect-carrier/redirect-carrier.component'; +import { RedirectOrderComponent } from './redirect-order.component'; +import { RedirectProductComponent } from './redirect-product/redirect-product.component'; +import { TranslateModule } from '@ngx-translate/core'; +import { CustomerOrderActionsComponent } from './customer-order-actions/customer-order-actions.component'; +import { NbButtonModule } from '@nebular/theme'; + +const COMPONENTS = [ + RedirectStoreComponent, + RedirectCarrierComponent, + RedirectOrderComponent, + RedirectProductComponent, + CustomerOrderActionsComponent, +]; + +@NgModule({ + imports: [ + CommonModule, + ThemeModule, + TranslateModule.forChild(), + NbButtonModule, + ], + declarations: COMPONENTS, + exports: COMPONENTS +}) +export class CustomerOrdersTableModule {} diff --git a/packages/admin-web-angular/src/app/@shared/render-component/customer-orders-table/redirect-carrier/redirect-carrier.component.html b/packages/admin-web-angular/src/app/@shared/render-component/customer-orders-table/redirect-carrier/redirect-carrier.component.html new file mode 100644 index 0000000..d7b8d2a --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/render-component/customer-orders-table/redirect-carrier/redirect-carrier.component.html @@ -0,0 +1,7 @@ +
+ + +
{{ carrier.firstName }}
+
+

{{ carrierStatusText | translate }}

+
diff --git a/packages/admin-web-angular/src/app/@shared/render-component/customer-orders-table/redirect-carrier/redirect-carrier.component.scss b/packages/admin-web-angular/src/app/@shared/render-component/customer-orders-table/redirect-carrier/redirect-carrier.component.scss new file mode 100644 index 0000000..8e43452 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/render-component/customer-orders-table/redirect-carrier/redirect-carrier.component.scss @@ -0,0 +1,9 @@ +.carrier-name { + padding-top: 5px; + font-weight: bold; +} + +.carrier-image { + width: 30px; + height: 30px; +} diff --git a/packages/admin-web-angular/src/app/@shared/render-component/customer-orders-table/redirect-carrier/redirect-carrier.component.ts b/packages/admin-web-angular/src/app/@shared/render-component/customer-orders-table/redirect-carrier/redirect-carrier.component.ts new file mode 100644 index 0000000..03f04a0 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/render-component/customer-orders-table/redirect-carrier/redirect-carrier.component.ts @@ -0,0 +1,42 @@ +import { Component, Input, OnInit } from '@angular/core'; +import { ViewCell } from 'ng2-smart-table'; +import { Router } from '@angular/router'; +import { Observable } from 'rxjs'; +import Carrier from '@modules/server.common/entities/Carrier'; +import { CarriersService } from '../../../../@core/data/carriers.service'; +import { TranslateService } from '@ngx-translate/core'; + +@Component({ + styleUrls: ['./redirect-carrier.component.scss'], + templateUrl: './redirect-carrier.component.html', +}) +export class RedirectCarrierComponent implements ViewCell, OnInit { + value: string | number; + + @Input() + rowData: any; + carrier$: Observable; + + public carrierStatusText: string; + + constructor( + private readonly router: Router, + private readonly carriersService: CarriersService, + private translate: TranslateService + ) {} + + ngOnInit() { + if (this.rowData.carrierId) { + this.carrier$ = this.carriersService.getCarrierById( + this.rowData.carrierId + ); + } + this.carrierStatusText = + 'STATUS_TEXT.' + this.rowData.carrierStatusText; + } + redirect() { + if (this.rowData.carrierId) { + this.router.navigate([`carriers/${this.rowData.carrierId}`]); + } + } +} diff --git a/packages/admin-web-angular/src/app/@shared/render-component/customer-orders-table/redirect-order.component.ts b/packages/admin-web-angular/src/app/@shared/render-component/customer-orders-table/redirect-order.component.ts new file mode 100644 index 0000000..e0534df --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/render-component/customer-orders-table/redirect-order.component.ts @@ -0,0 +1,31 @@ +import { Component, Input, OnInit, EventEmitter } from '@angular/core'; +import { ViewCell } from 'ng2-smart-table'; +import { Router } from '@angular/router'; +import { getIdFromTheDate } from '@modules/server.common/utils'; + +@Component({ + template: ` + + `, +}) +export class RedirectOrderComponent implements ViewCell, OnInit { + value: string | number; + orderId: string; + + @Input() + rowData: any; + + constructor(private readonly router: Router) {} + + ngOnInit() { + this.orderId = getIdFromTheDate(this.rowData); + } + + redirect() { + if (this.rowData.id) { + this.router.navigate([`orders/${this.rowData.id}`]); + } + } +} diff --git a/packages/admin-web-angular/src/app/@shared/render-component/customer-orders-table/redirect-product.component.ts b/packages/admin-web-angular/src/app/@shared/render-component/customer-orders-table/redirect-product.component.ts new file mode 100644 index 0000000..57b6d63 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/render-component/customer-orders-table/redirect-product.component.ts @@ -0,0 +1,55 @@ +import { Component, Input, OnInit } from '@angular/core'; +import { ViewCell } from 'ng2-smart-table'; +import { Router } from '@angular/router'; +import OrderProduct from '@modules/server.common/entities/OrderProduct'; +import { ProductLocalesService } from '@modules/client.common.angular2/locale/product-locales.service'; +import { ILocaleMember } from '@modules/server.common/interfaces/ILocale'; + +@Component({ + template: ` +
+
+ {{ productTitle }} + {{ product.count }} +
+
+ `, +}) +export class RedirectProductComponent implements ViewCell, OnInit { + value: string | number; + product: OrderProduct; + public productTitle: string; + + @Input() + rowData: any; + + constructor( + private readonly router: Router, + private _productLocalesService: ProductLocalesService + ) {} + + ngOnInit(): void { + if (this.rowData.products.length) { + this.product = this.rowData.products[0]; + this.productTitle = this.localeTranslate( + this.rowData.products[0].product.title + ); + } + } + + protected localeTranslate(member: ILocaleMember[]) { + return this._productLocalesService.getTranslate(member); + } + + redirect() { + if (this.product) { + this.router.navigate([ + `products/list/${this.product['product'].id}`, + ]); + } + } +} diff --git a/packages/admin-web-angular/src/app/@shared/render-component/customer-orders-table/redirect-product/redirect-product.component.html b/packages/admin-web-angular/src/app/@shared/render-component/customer-orders-table/redirect-product/redirect-product.component.html new file mode 100644 index 0000000..3f3df4a --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/render-component/customer-orders-table/redirect-product/redirect-product.component.html @@ -0,0 +1,6 @@ +
+

+ {{ productTitle }} + {{ product.count }} +

+
diff --git a/packages/admin-web-angular/src/app/@shared/render-component/customer-orders-table/redirect-product/redirect-product.component.ts b/packages/admin-web-angular/src/app/@shared/render-component/customer-orders-table/redirect-product/redirect-product.component.ts new file mode 100644 index 0000000..bf8fab2 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/render-component/customer-orders-table/redirect-product/redirect-product.component.ts @@ -0,0 +1,44 @@ +import { Component, Input, OnInit } from '@angular/core'; +import { ViewCell } from 'ng2-smart-table'; +import { Router } from '@angular/router'; +import OrderProduct from '@modules/server.common/entities/OrderProduct'; +import { ProductLocalesService } from '@modules/client.common.angular2/locale/product-locales.service'; +import { ILocaleMember } from '@modules/server.common/interfaces/ILocale'; + +@Component({ + templateUrl: './redirect-product.component.html', +}) +export class RedirectProductComponent implements ViewCell, OnInit { + value: string | number; + product: OrderProduct; + public productTitle: string; + + @Input() + rowData: any; + + constructor( + private readonly router: Router, + private _productLocalesService: ProductLocalesService + ) {} + + ngOnInit(): void { + if (this.rowData.products.length) { + this.product = this.rowData.products[0]; + this.productTitle = this.localeTranslate( + this.rowData.products[0].product.title + ); + } + } + + protected localeTranslate(member: ILocaleMember[]) { + return this._productLocalesService.getTranslate(member); + } + + redirect() { + if (this.product) { + this.router.navigate([ + `products/list/${this.product['product'].id}`, + ]); + } + } +} diff --git a/packages/admin-web-angular/src/app/@shared/render-component/customer-orders-table/redirect-store/redirect-store.component.html b/packages/admin-web-angular/src/app/@shared/render-component/customer-orders-table/redirect-store/redirect-store.component.html new file mode 100644 index 0000000..552bcfd --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/render-component/customer-orders-table/redirect-store/redirect-store.component.html @@ -0,0 +1,7 @@ +
+ + +
{{ store.name }}
+
+

{{ warehouseStatusText | translate }}

+
diff --git a/packages/admin-web-angular/src/app/@shared/render-component/customer-orders-table/redirect-store/redirect-store.component.scss b/packages/admin-web-angular/src/app/@shared/render-component/customer-orders-table/redirect-store/redirect-store.component.scss new file mode 100644 index 0000000..0223ff2 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/render-component/customer-orders-table/redirect-store/redirect-store.component.scss @@ -0,0 +1,9 @@ +.warehouse-name { + padding-top: 5px; + font-weight: bold; +} + +.warehouse-image { + width: 30px; + height: 30px; +} diff --git a/packages/admin-web-angular/src/app/@shared/render-component/customer-orders-table/redirect-store/redirect-store.component.ts b/packages/admin-web-angular/src/app/@shared/render-component/customer-orders-table/redirect-store/redirect-store.component.ts new file mode 100644 index 0000000..c679989 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/render-component/customer-orders-table/redirect-store/redirect-store.component.ts @@ -0,0 +1,39 @@ +import { Component, Input, OnInit } from '@angular/core'; +import { ViewCell } from 'ng2-smart-table'; +import { Router } from '@angular/router'; +import { WarehousesService } from '../../../../@core/data/warehouses.service'; +import Warehouse from '@modules/server.common/entities/Warehouse'; +import { Observable } from 'rxjs'; + +@Component({ + styleUrls: ['redirect-store.component.scss'], + templateUrl: './redirect-store.component.html', +}) +export class RedirectStoreComponent implements ViewCell, OnInit { + value: string | number; + + @Input() + rowData: any; + store$: Observable; + + public warehouseStatusText: string; + + constructor( + private readonly router: Router, + private readonly warehousesService: WarehousesService + ) {} + + ngOnInit() { + this.store$ = this.warehousesService.getStoreById( + this.rowData.warehouseId + ); + this.warehouseStatusText = + 'STATUS_TEXT.' + this.rowData.warehouseStatusText; + } + + redirect() { + if (this.rowData.warehouseId) { + this.router.navigate([`stores/${this.rowData.warehouseId}`]); + } + } +} diff --git a/packages/admin-web-angular/src/app/@shared/render-component/customer-phone/customer-phone.component.html b/packages/admin-web-angular/src/app/@shared/render-component/customer-phone/customer-phone.component.html new file mode 100644 index 0000000..24433ee --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/render-component/customer-phone/customer-phone.component.html @@ -0,0 +1,5 @@ + diff --git a/packages/admin-web-angular/src/app/@shared/render-component/customer-phone/customer-phone.component.scss b/packages/admin-web-angular/src/app/@shared/render-component/customer-phone/customer-phone.component.scss new file mode 100644 index 0000000..c942bb6 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/render-component/customer-phone/customer-phone.component.scss @@ -0,0 +1,12 @@ +div a { + i { + display: flex; + align-items: center; + justify-content: center; + width: 18px; + height: 18px; + } + align-items: center; + display: flex; + text-decoration: none; +} diff --git a/packages/admin-web-angular/src/app/@shared/render-component/customer-phone/customer-phone.component.ts b/packages/admin-web-angular/src/app/@shared/render-component/customer-phone/customer-phone.component.ts new file mode 100644 index 0000000..a09f110 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/render-component/customer-phone/customer-phone.component.ts @@ -0,0 +1,12 @@ +import { Component, Input } from '@angular/core'; + +@Component({ + styleUrls: ['./customer-phone.component.scss'], + templateUrl: './customer-phone.component.html', +}) +export class CustomerPhoneComponent { + @Input() + rowData: any; + + constructor() {} +} diff --git a/packages/admin-web-angular/src/app/@shared/render-component/customer-products-table/customer-products-table.module.ts b/packages/admin-web-angular/src/app/@shared/render-component/customer-products-table/customer-products-table.module.ts new file mode 100644 index 0000000..fd06415 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/render-component/customer-products-table/customer-products-table.module.ts @@ -0,0 +1,23 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { ThemeModule } from '../../../@theme'; +import { ProductOrderProductsComponent } from './product-order-products/product-order-products.component'; +import { StoreOrderProductsComponent } from './store-order-products/store-order-products.component'; +import { OrderBtnOrderProductsComponent } from './order-btn-order-products/order-btn-order-products.component'; +// duplicate OrderBtnOrderProductsComponent +import { OrderBtnOrderProductsComponent as RootrderBtnOrderProductsComponent } from './order-btn-order-products.component'; +import { TranslateModule } from '@ngx-translate/core'; + +const COMPONENTS = [ + ProductOrderProductsComponent, + StoreOrderProductsComponent, + OrderBtnOrderProductsComponent, + RootrderBtnOrderProductsComponent +]; + +@NgModule({ + imports: [CommonModule, ThemeModule, TranslateModule.forChild()], + declarations: COMPONENTS, + exports: COMPONENTS +}) +export class CustomerProductsTableModule {} diff --git a/packages/admin-web-angular/src/app/@shared/render-component/customer-products-table/order-btn-order-products.component.ts b/packages/admin-web-angular/src/app/@shared/render-component/customer-products-table/order-btn-order-products.component.ts new file mode 100644 index 0000000..b26998d --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/render-component/customer-products-table/order-btn-order-products.component.ts @@ -0,0 +1,55 @@ +import { Component, Input, OnInit } from '@angular/core'; +import { ViewCell } from 'ng2-smart-table'; +import { CustomOrderComponent } from '../../../pages/+customers/+customer/ea-customer-products/custom-order'; +import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; +import Product from '@modules/server.common/entities/Product'; + +@Component({ + template: ` +
+
+ {{ 'CUSTOMERS_VIEW.ORDER' | translate }} +
+
+ `, +}) +export class OrderBtnOrderProductsComponent implements ViewCell, OnInit { + value: string | number; + + @Input() + rowData: any; + + @Input() + availableProducts: Product[]; + + @Input() + userId: string; + + private productId: string; + + constructor(private readonly modalService: NgbModal) {} + + ngOnInit(): void { + this.productId = this.rowData.warehouseProduct.product.id; + } + + openModal() { + const productsArray: any = this.availableProducts; + if (productsArray) { + localStorage.setItem('ever_customOrderProductId', this.productId); + const currProduct = productsArray.find((x) => { + return x.warehouseProduct.product.id === this.productId; + }); + const activeModal = this.modalService.open(CustomOrderComponent, { + size: 'lg', + container: 'nb-layout', + }); + + const modalComponent: CustomOrderComponent = + activeModal.componentInstance; + modalComponent.warehouseId = currProduct.warehouseId; + modalComponent.userId = this.userId; + modalComponent.currentProduct = currProduct; + } + } +} diff --git a/packages/admin-web-angular/src/app/@shared/render-component/customer-products-table/order-btn-order-products/order-btn-order-products.component.html b/packages/admin-web-angular/src/app/@shared/render-component/customer-products-table/order-btn-order-products/order-btn-order-products.component.html new file mode 100644 index 0000000..92811e4 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/render-component/customer-products-table/order-btn-order-products/order-btn-order-products.component.html @@ -0,0 +1,5 @@ +
+
+ {{ 'CUSTOMERS_VIEW.ORDER' | translate }} +
+
diff --git a/packages/admin-web-angular/src/app/@shared/render-component/customer-products-table/order-btn-order-products/order-btn-order-products.component.ts b/packages/admin-web-angular/src/app/@shared/render-component/customer-products-table/order-btn-order-products/order-btn-order-products.component.ts new file mode 100644 index 0000000..99a5e1f --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/render-component/customer-products-table/order-btn-order-products/order-btn-order-products.component.ts @@ -0,0 +1,49 @@ +import { Component, Input, OnInit } from '@angular/core'; +import { ViewCell } from 'ng2-smart-table'; +import { CustomOrderComponent } from '../../../../pages/+customers/+customer/ea-customer-products/custom-order'; +import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; +import Product from '@modules/server.common/entities/Product'; + +@Component({ + templateUrl: './order-btn-order-products.component.html', +}) +export class OrderBtnOrderProductsComponent implements ViewCell, OnInit { + value: string | number; + + @Input() + rowData: any; + + @Input() + availableProducts: Product[]; + + @Input() + userId: string; + + private productId: string; + + constructor(private readonly modalService: NgbModal) {} + + ngOnInit(): void { + this.productId = this.rowData.warehouseProduct.product.id; + } + + openModal() { + const productsArray: any = this.availableProducts; + if (productsArray) { + localStorage.setItem('ever_customOrderProductId', this.productId); + const currProduct = productsArray.find((x) => { + return x.warehouseProduct.product.id === this.productId; + }); + const activeModal = this.modalService.open(CustomOrderComponent, { + size: 'lg', + container: 'nb-layout', + }); + + const modalComponent: CustomOrderComponent = + activeModal.componentInstance; + modalComponent.warehouseId = currProduct.warehouseId; + modalComponent.userId = this.userId; + modalComponent.currentProduct = currProduct; + } + } +} diff --git a/packages/admin-web-angular/src/app/@shared/render-component/customer-products-table/product-order-products.component.ts b/packages/admin-web-angular/src/app/@shared/render-component/customer-products-table/product-order-products.component.ts new file mode 100644 index 0000000..f050665 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/render-component/customer-products-table/product-order-products.component.ts @@ -0,0 +1,56 @@ +import { Component, Input, OnInit } from '@angular/core'; +import { ViewCell } from 'ng2-smart-table'; +import { Router } from '@angular/router'; +import { ILocaleMember } from '@modules/server.common/interfaces/ILocale'; +import { ProductLocalesService } from '@modules/client.common.angular2/locale/product-locales.service'; + +@Component({ + template: ` +
+

+ + + {{ productTitle }} + +

+
+ `, +}) +export class ProductOrderProductsComponent implements ViewCell, OnInit { + value: string | number; + productInfo: any; + public productTitle: string; + + @Input() + rowData: any; + + constructor( + private readonly router: Router, + private _productLocalesService: ProductLocalesService + ) {} + + ngOnInit(): void { + this.productInfo = this.rowData; + + this.productTitle = this.localeTranslate( + this.rowData.warehouseProduct.product.title + ); + } + + protected localeTranslate(member: ILocaleMember[]) { + return this._productLocalesService.getTranslate(member); + } + + redirect() { + if (this.productInfo) { + this.router.navigate([ + `products/list/${this.productInfo.warehouseProduct['product'].id}`, + ]); + } + } +} diff --git a/packages/admin-web-angular/src/app/@shared/render-component/customer-products-table/product-order-products/product-order-products.component.html b/packages/admin-web-angular/src/app/@shared/render-component/customer-products-table/product-order-products/product-order-products.component.html new file mode 100644 index 0000000..b14d219 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/render-component/customer-products-table/product-order-products/product-order-products.component.html @@ -0,0 +1,11 @@ +
+

+ + + {{ productTitle }} + +

+
diff --git a/packages/admin-web-angular/src/app/@shared/render-component/customer-products-table/product-order-products/product-order-products.component.ts b/packages/admin-web-angular/src/app/@shared/render-component/customer-products-table/product-order-products/product-order-products.component.ts new file mode 100644 index 0000000..5b919b2 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/render-component/customer-products-table/product-order-products/product-order-products.component.ts @@ -0,0 +1,42 @@ +import { Component, Input, OnInit } from '@angular/core'; +import { ViewCell } from 'ng2-smart-table'; +import { Router } from '@angular/router'; +import { ILocaleMember } from '@modules/server.common/interfaces/ILocale'; +import { ProductLocalesService } from '@modules/client.common.angular2/locale/product-locales.service'; + +@Component({ + templateUrl: './product-order-products.component.html', +}) +export class ProductOrderProductsComponent implements ViewCell, OnInit { + value: string | number; + productInfo: any; + public productTitle: string; + + @Input() + rowData: any; + + constructor( + private readonly router: Router, + private _productLocalesService: ProductLocalesService + ) {} + + ngOnInit(): void { + this.productInfo = this.rowData; + + this.productTitle = this.localeTranslate( + this.rowData.warehouseProduct.product.title + ); + } + + protected localeTranslate(member: ILocaleMember[]) { + return this._productLocalesService.getTranslate(member); + } + + redirect() { + if (this.productInfo) { + this.router.navigate([ + `products/list/${this.productInfo.warehouseProduct['product'].id}`, + ]); + } + } +} diff --git a/packages/admin-web-angular/src/app/@shared/render-component/customer-products-table/store-order-products.component.ts b/packages/admin-web-angular/src/app/@shared/render-component/customer-products-table/store-order-products.component.ts new file mode 100644 index 0000000..394efda --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/render-component/customer-products-table/store-order-products.component.ts @@ -0,0 +1,42 @@ +import { Component, Input, OnInit } from '@angular/core'; +import { ViewCell } from 'ng2-smart-table'; +import { Router } from '@angular/router'; +import { Observable } from 'rxjs'; +import Warehouse from '@modules/server.common/entities/Warehouse'; +import { WarehousesService } from '../../../@core/data/warehouses.service'; + +@Component({ + template: ` +
+
+ + {{ store.name }} + +
+
+ `, +}) +export class StoreOrderProductsComponent implements ViewCell, OnInit { + value: string | number; + store$: Observable; + + @Input() + rowData: any; + + constructor( + private readonly router: Router, + private readonly warehousesService: WarehousesService + ) {} + + ngOnInit(): void { + this.store$ = this.warehousesService.getStoreById( + this.rowData.warehouseId + ); + } + + redirect() { + if (this.rowData) { + this.router.navigate([`stores/${this.rowData.warehouseId}`]); + } + } +} diff --git a/packages/admin-web-angular/src/app/@shared/render-component/customer-products-table/store-order-products/store-order-products.component.html b/packages/admin-web-angular/src/app/@shared/render-component/customer-products-table/store-order-products/store-order-products.component.html new file mode 100644 index 0000000..0c9a50e --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/render-component/customer-products-table/store-order-products/store-order-products.component.html @@ -0,0 +1,7 @@ +
+

+ + {{ store.name }} + +

+
diff --git a/packages/admin-web-angular/src/app/@shared/render-component/customer-products-table/store-order-products/store-order-products.component.ts b/packages/admin-web-angular/src/app/@shared/render-component/customer-products-table/store-order-products/store-order-products.component.ts new file mode 100644 index 0000000..34ae2cb --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/render-component/customer-products-table/store-order-products/store-order-products.component.ts @@ -0,0 +1,34 @@ +import { Component, Input, OnInit } from '@angular/core'; +import { ViewCell } from 'ng2-smart-table'; +import { Router } from '@angular/router'; +import { Observable } from 'rxjs'; +import Warehouse from '@modules/server.common/entities/Warehouse'; +import { WarehousesService } from '../../../../@core/data/warehouses.service'; + +@Component({ + templateUrl: './store-order-products.component.html', +}) +export class StoreOrderProductsComponent implements ViewCell, OnInit { + value: string | number; + store$: Observable; + + @Input() + rowData: any; + + constructor( + private readonly router: Router, + private readonly warehousesService: WarehousesService + ) {} + + ngOnInit(): void { + this.store$ = this.warehousesService.getStoreById( + this.rowData.warehouseId + ); + } + + redirect() { + if (this.rowData) { + this.router.navigate([`stores/${this.rowData.warehouseId}`]); + } + } +} diff --git a/packages/admin-web-angular/src/app/@shared/render-component/customer-table/customer-orders-number/customer-orders-number.component.html b/packages/admin-web-angular/src/app/@shared/render-component/customer-table/customer-orders-number/customer-orders-number.component.html new file mode 100644 index 0000000..05bdb6f --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/render-component/customer-table/customer-orders-number/customer-orders-number.component.html @@ -0,0 +1,5 @@ +
+
+ {{ numberQTY }} +
+
diff --git a/packages/admin-web-angular/src/app/@shared/render-component/customer-table/customer-orders-number/customer-orders-number.component.scss b/packages/admin-web-angular/src/app/@shared/render-component/customer-table/customer-orders-number/customer-orders-number.component.scss new file mode 100644 index 0000000..1a9e06e --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/render-component/customer-table/customer-orders-number/customer-orders-number.component.scss @@ -0,0 +1,3 @@ +.number-qty { + cursor: pointer; +} diff --git a/packages/admin-web-angular/src/app/@shared/render-component/customer-table/customer-orders-number/customer-orders-number.component.ts b/packages/admin-web-angular/src/app/@shared/render-component/customer-table/customer-orders-number/customer-orders-number.component.ts new file mode 100644 index 0000000..6c8c667 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/render-component/customer-table/customer-orders-number/customer-orders-number.component.ts @@ -0,0 +1,19 @@ +import { OnInit, Component } from '@angular/core'; +import { Router } from '@angular/router'; +import { ViewCell } from 'ng2-smart-table'; + +@Component({ + styleUrls: ['customer-orders-number.component.scss'], + templateUrl: 'customer-orders-number.component.html', +}) +export class CustomerOrdersNumberComponent implements ViewCell, OnInit { + value: any; + rowData: any; + numberQTY: number; + + constructor(private readonly _router: Router) {} + + ngOnInit() { + this.numberQTY = this.rowData.ordersQty; + } +} diff --git a/packages/admin-web-angular/src/app/@shared/render-component/customer-table/customer-table.module.ts b/packages/admin-web-angular/src/app/@shared/render-component/customer-table/customer-table.module.ts new file mode 100644 index 0000000..e0fe458 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/render-component/customer-table/customer-table.module.ts @@ -0,0 +1,21 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { ThemeModule } from '../../../@theme'; +import { HighlightModule } from 'ngx-highlightjs'; +import { TranslateModule } from '@ngx-translate/core'; +import { CustomerImageComponent } from './customer-table/customer-image.component'; +import { CustomerOrdersNumberComponent } from './customer-orders-number/customer-orders-number.component'; + +const COMPONENTS = [CustomerImageComponent, CustomerOrdersNumberComponent]; + +@NgModule({ + imports: [ + CommonModule, + ThemeModule, + HighlightModule, + TranslateModule.forChild(), + ], + declarations: COMPONENTS, + exports: COMPONENTS +}) +export class CustomerTableModule {} diff --git a/packages/admin-web-angular/src/app/@shared/render-component/customer-table/customer-table/customer-image.component.html b/packages/admin-web-angular/src/app/@shared/render-component/customer-table/customer-table/customer-image.component.html new file mode 100644 index 0000000..0aee9d2 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/render-component/customer-table/customer-table/customer-image.component.html @@ -0,0 +1,6 @@ + diff --git a/packages/admin-web-angular/src/app/@shared/render-component/customer-table/customer-table/customer-image.component.scss b/packages/admin-web-angular/src/app/@shared/render-component/customer-table/customer-table/customer-image.component.scss new file mode 100644 index 0000000..45158e4 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/render-component/customer-table/customer-table/customer-image.component.scss @@ -0,0 +1,8 @@ +img { + width: 64px; + height: 64px; +} + +.redirect-image { + cursor: pointer; +} diff --git a/packages/admin-web-angular/src/app/@shared/render-component/customer-table/customer-table/customer-image.component.ts b/packages/admin-web-angular/src/app/@shared/render-component/customer-table/customer-table/customer-image.component.ts new file mode 100644 index 0000000..f8d5bf2 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/render-component/customer-table/customer-table/customer-image.component.ts @@ -0,0 +1,26 @@ +import { OnInit, Component } from '@angular/core'; +import { Router } from '@angular/router'; +import { ViewCell } from 'ng2-smart-table'; + +@Component({ + styleUrls: ['customer-image.component.scss'], + templateUrl: 'customer-image.component.html', +}) +export class CustomerImageComponent implements ViewCell, OnInit { + value: any; + rowData: any; + imageUrl: string; + redirectPage: string; + + constructor(private readonly router: Router) {} + + ngOnInit() { + this.imageUrl = this.rowData.image; + } + + redirect() { + if (this.redirectPage) { + this.router.navigate([`${this.redirectPage}/${this.rowData.id}`]); + } + } +} diff --git a/packages/admin-web-angular/src/app/@shared/render-component/image-slider/image-slider.component.html b/packages/admin-web-angular/src/app/@shared/render-component/image-slider/image-slider.component.html new file mode 100644 index 0000000..a686285 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/render-component/image-slider/image-slider.component.html @@ -0,0 +1,11 @@ + +
+
+ +
{{ image.title }}
+
+
+
+
+
+
diff --git a/packages/admin-web-angular/src/app/@shared/render-component/image-slider/image-slider.component.scss b/packages/admin-web-angular/src/app/@shared/render-component/image-slider/image-slider.component.scss new file mode 100644 index 0000000..e99ea35 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/render-component/image-slider/image-slider.component.scss @@ -0,0 +1,11 @@ +.slide-title { + background-color: rgba(0, 0, 0, 0.5); + color: rgba(255, 255, 255, 0.8); + position: absolute; + bottom: 0; + width: 100%; + padding: 1rem 0; +} +.swiper-pagination { + top: 0; +} diff --git a/packages/admin-web-angular/src/app/@shared/render-component/image-slider/image-slider.module.ts b/packages/admin-web-angular/src/app/@shared/render-component/image-slider/image-slider.module.ts new file mode 100644 index 0000000..043cfc3 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/render-component/image-slider/image-slider.module.ts @@ -0,0 +1,11 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { SwiperModule } from 'ngx-swiper-wrapper'; +import { ImageSliderComponent } from './image.slider.component'; + +@NgModule({ + imports: [SwiperModule, CommonModule], + declarations: [ImageSliderComponent], + exports: [ImageSliderComponent], +}) +export class ImageSliderModule {} diff --git a/packages/admin-web-angular/src/app/@shared/render-component/image-slider/image.slider.component.ts b/packages/admin-web-angular/src/app/@shared/render-component/image-slider/image.slider.component.ts new file mode 100644 index 0000000..6c8c271 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/render-component/image-slider/image.slider.component.ts @@ -0,0 +1,26 @@ +import { Component, Input } from '@angular/core'; +import { SwiperOptions } from 'swiper'; + +@Component({ + selector: 'image-slider', + styleUrls: ['./image-slider.component.scss'], + templateUrl: './image-slider.component.html', +}) +export class ImageSliderComponent { + @Input() slideImages: any; + + config: SwiperOptions = { + pagination: { el: '.swiper-pagination', clickable: true }, + navigation: { + nextEl: '.swiper-button-next', + prevEl: '.swiper-button-prev', + }, + autoplay: { + delay: 5000, + }, + speed: 800, + loop: true, + }; + + public colors = [1, 2, 3, 4]; +} diff --git a/packages/admin-web-angular/src/app/@shared/render-component/invites-requests/invited-date.component.ts b/packages/admin-web-angular/src/app/@shared/render-component/invites-requests/invited-date.component.ts new file mode 100644 index 0000000..12a68b3 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/render-component/invites-requests/invited-date.component.ts @@ -0,0 +1,26 @@ +import { Component, Input, OnInit } from '@angular/core'; +import { ViewCell } from 'ng2-smart-table'; +import InviteRequest from '@modules/server.common/entities/InviteRequest'; + +@Component({ + styles: [ + '.invited-icon { color: green; margin-right: 3px;} .actions-invites-requests{width: 5rem;}', + ], + template: ` +
+ {{ inviteRequest?.invitedDate | date: 'short' }} +
+
+ `, +}) +export class InvitedDateComponent implements ViewCell, OnInit { + value: string | number; + @Input() + rowData: any; + inviteRequest: InviteRequest; + constructor() {} + + ngOnInit() { + this.inviteRequest = this.rowData.inviteRequest; + } +} diff --git a/packages/admin-web-angular/src/app/@shared/render-component/invites-requests/invites-requests.module.ts b/packages/admin-web-angular/src/app/@shared/render-component/invites-requests/invites-requests.module.ts new file mode 100644 index 0000000..1810544 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/render-component/invites-requests/invites-requests.module.ts @@ -0,0 +1,14 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { ThemeModule } from '../../../@theme'; +import { StatusComponent } from './status/status.component'; +import { InvitedDateComponent } from './invited-date.component'; + +const COMPONENTS = [StatusComponent, InvitedDateComponent]; + +@NgModule({ + imports: [CommonModule, ThemeModule], + declarations: COMPONENTS, + exports: COMPONENTS +}) +export class InvitesRequestsTableModule {} diff --git a/packages/admin-web-angular/src/app/@shared/render-component/invites-requests/status.component.ts b/packages/admin-web-angular/src/app/@shared/render-component/invites-requests/status.component.ts new file mode 100644 index 0000000..150a048 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/render-component/invites-requests/status.component.ts @@ -0,0 +1,26 @@ +import { Component, Input, OnInit } from '@angular/core'; +import { ViewCell } from 'ng2-smart-table'; + +@Component({ + styles: [ + '.invited-icon { color: green; margin-right: 3px;} .actions-invites-requests{width: 5rem;}', + ], + template: ` +
+ + {{ isInvited ? 'Invited' : 'Not Invited' }} +
+
+ `, +}) +export class StatusComponent implements ViewCell, OnInit { + value: string | number; + @Input() + rowData: any; + isInvited: boolean; + constructor() {} + + ngOnInit() { + this.isInvited = this.rowData.isInvited; + } +} diff --git a/packages/admin-web-angular/src/app/@shared/render-component/invites-requests/status/status.component.html b/packages/admin-web-angular/src/app/@shared/render-component/invites-requests/status/status.component.html new file mode 100644 index 0000000..e3006e2 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/render-component/invites-requests/status/status.component.html @@ -0,0 +1,5 @@ +
+ + {{ isInvited ? 'Invited' : 'Not Invited' }} +
+
diff --git a/packages/admin-web-angular/src/app/@shared/render-component/invites-requests/status/status.component.ts b/packages/admin-web-angular/src/app/@shared/render-component/invites-requests/status/status.component.ts new file mode 100644 index 0000000..c64cc01 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/render-component/invites-requests/status/status.component.ts @@ -0,0 +1,20 @@ +import { Component, Input, OnInit } from '@angular/core'; +import { ViewCell } from 'ng2-smart-table'; + +@Component({ + styles: [ + '.invited-icon { color: green; margin-right: 3px;} .actions-invites-requests{width: 5rem;}', + ], + templateUrl: './status.component.html', +}) +export class StatusComponent implements ViewCell, OnInit { + value: string | number; + @Input() + rowData: any; + isInvited: boolean; + constructor() {} + + ngOnInit() { + this.isInvited = this.rowData.isInvited; + } +} diff --git a/packages/admin-web-angular/src/app/@shared/render-component/name-redirect.component.scss b/packages/admin-web-angular/src/app/@shared/render-component/name-redirect.component.scss new file mode 100644 index 0000000..cffa090 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/render-component/name-redirect.component.scss @@ -0,0 +1,12 @@ +.redirectBtn { + h6 { + margin-bottom: 0; + text-align: center !important; + } + cursor: pointer; +} + +.warehouse-name-smt { + text-align: left !important; + width: auto; +} diff --git a/packages/admin-web-angular/src/app/@shared/render-component/name-redirect.component.ts b/packages/admin-web-angular/src/app/@shared/render-component/name-redirect.component.ts new file mode 100644 index 0000000..5254715 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/render-component/name-redirect.component.ts @@ -0,0 +1,29 @@ +import { Component, Input, OnInit } from '@angular/core'; +import { ViewCell } from 'ng2-smart-table'; +import { Router } from '@angular/router'; + +@Component({ + styleUrls: ['name-redirect.component.scss'], + template: ` +
+
+ {{ rowData.name }} +
+
+ `, +}) +export class RedirectNameComponent implements ViewCell, OnInit { + value: string | number; + redirectPage: string; + @Input() + rowData: any; + + constructor(private readonly router: Router) {} + + ngOnInit() {} + redirect() { + if (this.redirectPage) { + this.router.navigate([`${this.redirectPage}/${this.rowData.id}`]); + } + } +} diff --git a/packages/admin-web-angular/src/app/@shared/render-component/name-redirect/name-redirect.component.html b/packages/admin-web-angular/src/app/@shared/render-component/name-redirect/name-redirect.component.html new file mode 100644 index 0000000..ef48030 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/render-component/name-redirect/name-redirect.component.html @@ -0,0 +1,10 @@ +
+

+ {{ rowData.name + }}ban +

+
diff --git a/packages/admin-web-angular/src/app/@shared/render-component/name-redirect/name-redirect.component.scss b/packages/admin-web-angular/src/app/@shared/render-component/name-redirect/name-redirect.component.scss new file mode 100644 index 0000000..a30e0c1 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/render-component/name-redirect/name-redirect.component.scss @@ -0,0 +1,14 @@ +.redirectBtn { + p { + margin-bottom: 0; + text-align: center !important; + } + cursor: pointer; +} + +.warehouse-name-smt { + text-align: left !important; + width: auto; + font-size: 0.9rem; + line-height: 1; +} diff --git a/packages/admin-web-angular/src/app/@shared/render-component/name-redirect/name-redirect.component.ts b/packages/admin-web-angular/src/app/@shared/render-component/name-redirect/name-redirect.component.ts new file mode 100644 index 0000000..50452ad --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/render-component/name-redirect/name-redirect.component.ts @@ -0,0 +1,23 @@ +import { Component, Input, OnInit } from '@angular/core'; +import { ViewCell } from 'ng2-smart-table'; +import { Router } from '@angular/router'; + +@Component({ + styleUrls: ['name-redirect.component.scss'], + templateUrl: './name-redirect.component.html', +}) +export class RedirectNameComponent implements ViewCell, OnInit { + value: string | number; + redirectPage: string; + @Input() + rowData: any; + + constructor(private readonly router: Router) {} + + ngOnInit() {} + redirect() { + if (this.redirectPage) { + this.router.navigate([`${this.redirectPage}/${this.rowData.id}`]); + } + } +} diff --git a/packages/admin-web-angular/src/app/@shared/render-component/price-countInput/price-countInput.component.html b/packages/admin-web-angular/src/app/@shared/render-component/price-countInput/price-countInput.component.html new file mode 100644 index 0000000..f0726a3 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/render-component/price-countInput/price-countInput.component.html @@ -0,0 +1,9 @@ + diff --git a/packages/admin-web-angular/src/app/@shared/render-component/price-countInput/price-countInput.component.ts b/packages/admin-web-angular/src/app/@shared/render-component/price-countInput/price-countInput.component.ts new file mode 100644 index 0000000..22ce714 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/render-component/price-countInput/price-countInput.component.ts @@ -0,0 +1,27 @@ +import { Component, EventEmitter, Input, OnInit } from '@angular/core'; +import { ViewCell } from 'ng2-smart-table'; + +@Component({ + templateUrl: './price-countInput.component.html', +}) +export class PriceCountInputComponent implements ViewCell, OnInit { + value: string | number; + + newValue: EventEmitter = new EventEmitter(); + id: EventEmitter = new EventEmitter(); + placeholder: string; + + @Input() + rowData: any; + + ngOnInit() { + this.id.emit(this.rowData.id); + if (this.placeholder === 'Count') { + this.value = 1; + } + } + + valueChange(e) { + this.newValue.emit(this.value); + } +} diff --git a/packages/admin-web-angular/src/app/@shared/render-component/product-categories/product-categories.html b/packages/admin-web-angular/src/app/@shared/render-component/product-categories/product-categories.html new file mode 100644 index 0000000..fe22531 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/render-component/product-categories/product-categories.html @@ -0,0 +1,4 @@ +
+ Category Image + {{ getTranslates(category.name) }} +
diff --git a/packages/admin-web-angular/src/app/@shared/render-component/product-categories/product-categories.module.ts b/packages/admin-web-angular/src/app/@shared/render-component/product-categories/product-categories.module.ts new file mode 100644 index 0000000..726b060 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/render-component/product-categories/product-categories.module.ts @@ -0,0 +1,13 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { ThemeModule } from '../../../@theme'; +import { ProductCategoriesComponent } from './product-categories'; + +const COMPONENTS = [ProductCategoriesComponent]; + +@NgModule({ + imports: [CommonModule, ThemeModule], + declarations: COMPONENTS, + exports: COMPONENTS +}) +export class ProductCategoriesModule {} diff --git a/packages/admin-web-angular/src/app/@shared/render-component/product-categories/product-categories.ts b/packages/admin-web-angular/src/app/@shared/render-component/product-categories/product-categories.ts new file mode 100644 index 0000000..e4f250c --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/render-component/product-categories/product-categories.ts @@ -0,0 +1,43 @@ +import { Component, OnInit } from '@angular/core'; +import { ViewCell } from 'ng2-smart-table'; +import { ProductLocalesService } from '@modules/client.common.angular2/locale/product-locales.service'; + +@Component({ + styles: [ + ` + div { + padding-bottom: 4px; + } + + div img { + width: 35px; + height: 35px; + border-radius: 50%; + } + `, + ], + templateUrl: './product-categories.html', +}) +export class ProductCategoriesComponent implements ViewCell, OnInit { + value: any; + rowData: any; + categoriesArr = []; + + constructor(private readonly _localeTranslate: ProductLocalesService) {} + + ngOnInit() { + this.getCategories(); + } + + getCategories() { + if (this.rowData.allCategories) { + this.categoriesArr = this.rowData.allCategories.filter((category) => + this.rowData.categories.ids.includes(category.id) + ); + } + } + + getTranslates(categoryName) { + return this._localeTranslate.getTranslate(categoryName); + } +} diff --git a/packages/admin-web-angular/src/app/@shared/render-component/product-checkbox/product-checkbox.ts b/packages/admin-web-angular/src/app/@shared/render-component/product-checkbox/product-checkbox.ts new file mode 100644 index 0000000..f892c31 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/render-component/product-checkbox/product-checkbox.ts @@ -0,0 +1,21 @@ +import { Component, Input, OnInit } from '@angular/core'; +import { ViewCell } from 'ng2-smart-table'; + +@Component({ + template: ` +
+ +
+ `, +}) +export class ProductCheckboxComponent implements ViewCell, OnInit { + @Input() + value: string | number; + + @Input() + rowData: any; + + constructor() {} + + ngOnInit() {} +} diff --git a/packages/admin-web-angular/src/app/@shared/render-component/product-image-redirect/product-image-redirect.component.ts b/packages/admin-web-angular/src/app/@shared/render-component/product-image-redirect/product-image-redirect.component.ts new file mode 100644 index 0000000..3fe8e8f --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/render-component/product-image-redirect/product-image-redirect.component.ts @@ -0,0 +1,31 @@ +import { OnInit, Component } from '@angular/core'; +import { Router } from '@angular/router'; +import { ViewCell } from 'ng2-smart-table'; +import { ProductLocalesService } from '@modules/client.common.angular2/locale/product-locales.service'; + +@Component({ + styles: ['img { cursor: pointer }'], + template: ` `, +}) +export class ProductImageRedirectComponent implements ViewCell, OnInit { + value: any; + rowData: any; + + imageUrl: string; + + constructor( + private readonly _router: Router, + private readonly _localeTranslate: ProductLocalesService + ) {} + + ngOnInit() { + this.imageUrl = this._localeTranslate.getTranslate( + this.rowData.product.images + ); + } + + redirect() { + const productId = this.rowData.id; + this._router.navigate(['products/list/' + productId]); + } +} diff --git a/packages/admin-web-angular/src/app/@shared/render-component/product-image/product-image.component.html b/packages/admin-web-angular/src/app/@shared/render-component/product-image/product-image.component.html new file mode 100644 index 0000000..23706e1 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/render-component/product-image/product-image.component.html @@ -0,0 +1,7 @@ +
+ Product Image +
+
diff --git a/packages/admin-web-angular/src/app/@shared/render-component/product-image/product-image.component.scss b/packages/admin-web-angular/src/app/@shared/render-component/product-image/product-image.component.scss new file mode 100644 index 0000000..ed2f4b2 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/render-component/product-image/product-image.component.scss @@ -0,0 +1,5 @@ +div img { + max-width: 100px; + max-height: 100px; + cursor: pointer; +} diff --git a/packages/admin-web-angular/src/app/@shared/render-component/product-image/product-image.component.ts b/packages/admin-web-angular/src/app/@shared/render-component/product-image/product-image.component.ts new file mode 100644 index 0000000..73e283f --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/render-component/product-image/product-image.component.ts @@ -0,0 +1,17 @@ +import { Component, Input, OnInit } from '@angular/core'; +import { Router } from '@angular/router'; + +@Component({ + styleUrls: ['./product-image.component.scss'], + templateUrl: './product-image.component.html', +}) +export class ProductImageComponent { + @Input() + rowData: any; + + constructor(private router: Router) {} + + redirect() { + this.router.navigate([`products/list/${this.rowData.id}/edit`]); + } +} diff --git a/packages/admin-web-angular/src/app/@shared/render-component/product-takeaway-delivery/product-takeaway-delivery.component.ts b/packages/admin-web-angular/src/app/@shared/render-component/product-takeaway-delivery/product-takeaway-delivery.component.ts new file mode 100644 index 0000000..bddcb22 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/render-component/product-takeaway-delivery/product-takeaway-delivery.component.ts @@ -0,0 +1,84 @@ +import { Component, Input, OnInit } from '@angular/core'; +import { WarehouseProductsRouter } from '@modules/client.common.angular2/routers/warehouse-products-router.service'; +import WarehouseProduct from '@modules/server.common/entities/WarehouseProduct'; + +@Component({ + styles: [ + ` + div { + white-space: nowrap; + padding-bottom: 4px; + } + `, + ], + template: ` +
+
+ {{ + 'WAREHOUSE_VIEW.PRODUCTS_TAB.DELIVERY' | translate + }} +
+
+ {{ + 'WAREHOUSE_VIEW.PRODUCTS_TAB.TAKEAWAY' | translate + }} +
+
+ `, +}) +export class ProductTakeawayDeliveryComponent implements OnInit { + @Input() + rowData: any; + isDelivery: boolean; + isTakeaway: boolean; + wareHouseId: string; + productId: string; + + constructor(private warehouseProductRouter: WarehouseProductsRouter) {} + + ngOnInit() { + this.isDelivery = this.rowData.isDeliveryRequired; + this.isTakeaway = this.rowData.isTakeaway; + this.wareHouseId = this.rowData.storeId; + this.productId = this.rowData.product.id; + } + + async isDeliveryChange() { + this.isDelivery = !this.isDelivery; + + if (!this.isDelivery && !this.isTakeaway) { + this.isDelivery = true; + alert('Atleast one type should be selected!'); + return; + } + this.rowData.isDeliveryRequired = this.isDelivery; + await this.warehouseProductRouter.changeProductDelivery( + this.wareHouseId, + this.productId, + this.rowData.isDeliveryRequired + ); + } + + async isTakeawayChange() { + this.isTakeaway = !this.isTakeaway; + if (!this.isDelivery && !this.isTakeaway) { + this.isTakeaway = true; + alert('Atleast one type should be selected!'); + return; + } + this.rowData.isTakeaway = this.isTakeaway; + await this.warehouseProductRouter.changeProductTakeaway( + this.wareHouseId, + this.productId, + this.rowData.isTakeaway + ); + } +} diff --git a/packages/admin-web-angular/src/app/@shared/render-component/product-title-redirect/product-title-redirect.component.ts b/packages/admin-web-angular/src/app/@shared/render-component/product-title-redirect/product-title-redirect.component.ts new file mode 100644 index 0000000..397750d --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/render-component/product-title-redirect/product-title-redirect.component.ts @@ -0,0 +1,33 @@ +import { Component, OnInit } from '@angular/core'; +import { Router } from '@angular/router'; +import { ViewCell } from 'ng2-smart-table'; +import { ProductLocalesService } from '@modules/client.common.angular2/locale/product-locales.service'; +import { TranslateService } from '@ngx-translate/core'; + +@Component({ + styles: ['a { float: left; }', 'a:hover { color: #4caf508f !important; }'], + template: ` {{ title }} `, +}) +export class ProductTitleRedirectComponent implements ViewCell, OnInit { + value: any; + rowData: any; + + title: string; + + constructor( + private readonly _router: Router, + private readonly _localeTranslate: ProductLocalesService, + private translate: TranslateService + ) {} + + ngOnInit() { + this.title = this._localeTranslate.getTranslate( + this.rowData.product.title + ); + } + + redirect() { + const productId = this.rowData.id; + this._router.navigate(['products/list/' + productId]); + } +} diff --git a/packages/admin-web-angular/src/app/@shared/render-component/product-title/product-title.component.html b/packages/admin-web-angular/src/app/@shared/render-component/product-title/product-title.component.html new file mode 100644 index 0000000..c0768b9 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/render-component/product-title/product-title.component.html @@ -0,0 +1,8 @@ +
+

+ {{ rowData.title }} +

+
diff --git a/packages/admin-web-angular/src/app/@shared/render-component/product-title/product-title.component.scss b/packages/admin-web-angular/src/app/@shared/render-component/product-title/product-title.component.scss new file mode 100644 index 0000000..28186f6 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/render-component/product-title/product-title.component.scss @@ -0,0 +1,5 @@ +div p { + margin-bottom: 0 !important; + text-align: center !important; + width: 100%; +} diff --git a/packages/admin-web-angular/src/app/@shared/render-component/product-title/product-title.component.ts b/packages/admin-web-angular/src/app/@shared/render-component/product-title/product-title.component.ts new file mode 100644 index 0000000..ae44c3e --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/render-component/product-title/product-title.component.ts @@ -0,0 +1,17 @@ +import { Component, Input } from '@angular/core'; +import { Router } from '@angular/router'; + +@Component({ + styleUrls: ['./product-title.component.scss'], + templateUrl: './product-title.component.html', +}) +export class ProductTitleComponent { + @Input() + rowData: any; + + constructor(private router: Router) {} + + redirect() { + this.router.navigate([`products/list/${this.rowData.id}/edit`]); + } +} diff --git a/packages/admin-web-angular/src/app/@shared/render-component/redirect-id.ts b/packages/admin-web-angular/src/app/@shared/render-component/redirect-id.ts new file mode 100644 index 0000000..e0ed987 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/render-component/redirect-id.ts @@ -0,0 +1,29 @@ +import { Component, Input } from '@angular/core'; +import { ViewCell } from 'ng2-smart-table'; +import { Router } from '@angular/router'; + +@Component({ + template: ` + + `, +}) +export class RedirectIdComponent implements ViewCell { + @Input() + value: string | number; + + @Input() + baseUrl: string; + + @Input() + rowData: { id: string | number }; + + constructor(private readonly router: Router) {} + + redirect() { + if (this.baseUrl && this.rowData.id) { + this.router.navigate([`${this.baseUrl}/${this.rowData.id}`]); + } + } +} diff --git a/packages/admin-web-angular/src/app/@shared/render-component/render-components.module.ts b/packages/admin-web-angular/src/app/@shared/render-component/render-components.module.ts new file mode 100644 index 0000000..ad99720 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/render-component/render-components.module.ts @@ -0,0 +1,49 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { FormsModule } from '@angular/forms'; +import { PriceCountInputComponent } from './price-countInput/price-countInput.component'; +import { RedirectIdComponent } from './redirect-id'; +import { ThemeModule } from '../../@theme'; +import { RedirectNameComponent } from './name-redirect/name-redirect.component'; +import { CreatedComponent } from './created/created.component'; +import { ProductTitleRedirectComponent } from './product-title-redirect/product-title-redirect.component'; +import { ProductImageRedirectComponent } from './product-image-redirect/product-image-redirect.component'; +import { ProductCheckboxComponent } from './product-checkbox/product-checkbox'; +import { MomentModule } from 'ngx-moment'; +import { ProductTitleComponent } from './product-title/product-title.component'; +import { ProductImageComponent } from './product-image/product-image.component'; +import { CustomerEmailComponent } from './customer-email/customer-email.component'; +import { CustomerPhoneComponent } from './customer-phone/customer-phone.component'; +import { CheckboxComponent } from './customer-orders-table/checkbox/checkbox.component'; +import { IsAvailableCheckBox } from './store-product-is-available-checkbox/is-available-checkbox.component'; +import { ProductTakeawayDeliveryComponent } from './product-takeaway-delivery/product-takeaway-delivery.component'; +import { TranslateModule } from '@ngx-translate/core'; + +const COMPONENTS = [ + PriceCountInputComponent, + RedirectIdComponent, + RedirectNameComponent, + CreatedComponent, + ProductTitleRedirectComponent, + ProductImageRedirectComponent, + ProductCheckboxComponent, + CheckboxComponent, + ProductTitleComponent, + ProductImageComponent, + CustomerEmailComponent, + CustomerPhoneComponent, + IsAvailableCheckBox, + ProductTakeawayDeliveryComponent, +]; + +@NgModule({ + imports: [ + CommonModule, + FormsModule, + ThemeModule, + MomentModule, + TranslateModule.forChild(), + ], + declarations: COMPONENTS +}) +export class RenderComponentsModule {} diff --git a/packages/admin-web-angular/src/app/@shared/render-component/simulation-table/simulation-table.module.ts b/packages/admin-web-angular/src/app/@shared/render-component/simulation-table/simulation-table.module.ts new file mode 100644 index 0000000..ddae8a5 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/render-component/simulation-table/simulation-table.module.ts @@ -0,0 +1,20 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { ThemeModule } from '../../../@theme'; +import { SimulationJsonComponent } from './sumulation-json.component'; +import { JsonModalModule } from '../../json-modal/json-modal.module'; +import { TranslateModule } from '@ngx-translate/core'; + +const COMPONENTS = [SimulationJsonComponent]; + +@NgModule({ + imports: [ + CommonModule, + ThemeModule, + JsonModalModule, + TranslateModule.forChild(), + ], + declarations: COMPONENTS, + exports: COMPONENTS +}) +export class SimulationTableModule {} diff --git a/packages/admin-web-angular/src/app/@shared/render-component/simulation-table/sumulation-json.component.html b/packages/admin-web-angular/src/app/@shared/render-component/simulation-table/sumulation-json.component.html new file mode 100644 index 0000000..5a525d0 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/render-component/simulation-table/sumulation-json.component.html @@ -0,0 +1,5 @@ +
+
+ +
+
diff --git a/packages/admin-web-angular/src/app/@shared/render-component/simulation-table/sumulation-json.component.scss b/packages/admin-web-angular/src/app/@shared/render-component/simulation-table/sumulation-json.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/packages/admin-web-angular/src/app/@shared/render-component/simulation-table/sumulation-json.component.ts b/packages/admin-web-angular/src/app/@shared/render-component/simulation-table/sumulation-json.component.ts new file mode 100644 index 0000000..72950bf --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/render-component/simulation-table/sumulation-json.component.ts @@ -0,0 +1,52 @@ +import { Component, Input, OnInit } from '@angular/core'; +import { ViewCell } from 'ng2-smart-table'; +import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; +import { JsonModalComponent } from '../../json-modal/json-modal.component'; +import { ILocaleMember } from '@modules/server.common/interfaces/ILocale'; +import { ProductLocalesService } from '@modules/client.common.angular2/locale/product-locales.service'; + +@Component({ + templateUrl: './sumulation-json.component.html', +}) +export class SimulationJsonComponent implements ViewCell, OnInit { + @Input() + value: string | number; + + @Input() + rowData: any; + + hideBtn: boolean; + + constructor( + private readonly modalService: NgbModal, + private _productLocalesService: ProductLocalesService + ) {} + + ngOnInit() {} + + openInfo() { + const activeModal = this.modalService.open(JsonModalComponent, { + size: 'lg', + container: 'nb-layout', + windowClass: 'simJSON', + }); + + const modalComponent: JsonModalComponent = + activeModal.componentInstance; + modalComponent.obj = this.rowData; + modalComponent.title = 'Product'; + modalComponent.subTitle = this.localeTranslate( + this.rowData.product['title'] + ); + } + + protected localeTranslate(member: ILocaleMember[]) { + return this._productLocalesService.getTranslate(member); + } + + openCancel() { + // const activeModal = this.modalService.open(OrderCancelComponent, { size: 'sm', container: 'nb-layout' }); + // const modalComponent: OrderCancelComponent = activeModal.componentInstance; + // modalComponent.orderId = this.rowData.id + } +} diff --git a/packages/admin-web-angular/src/app/@shared/render-component/store-product-is-available-checkbox/is-available-checkbox.component.ts b/packages/admin-web-angular/src/app/@shared/render-component/store-product-is-available-checkbox/is-available-checkbox.component.ts new file mode 100644 index 0000000..b40f89f --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/render-component/store-product-is-available-checkbox/is-available-checkbox.component.ts @@ -0,0 +1,49 @@ +import { Component, Input, OnInit } from '@angular/core'; +import { ViewCell } from 'ng2-smart-table'; +import { WarehouseProductsRouter } from '@modules/client.common.angular2/routers/warehouse-products-router.service'; +@Component({ + template: ` +
+ +
+ `, + styles: [ + ` + .checkbox-container { + display: flex; + justify-content: center; + align-items: center; + } + ​ nb-checkbox { + width: 1rem; + height: 1rem; + } + `, + ], +}) +export class IsAvailableCheckBox implements ViewCell, OnInit { + @Input() rowData: any; + @Input() value: string; + isChecked: boolean; + wareHouseId: string; + productId: string; + constructor(private warehouseProductRouter: WarehouseProductsRouter) {} + ngOnInit() { + this.isChecked = this.rowData.isProductAvailable; + this.wareHouseId = this.rowData.storeId; + this.productId = this.rowData.product.id; + } + + async clickHandler() { + this.isChecked = !this.isChecked; + this.rowData.isProductAvailable = this.isChecked; + await this.warehouseProductRouter.changeProductAvailability( + this.wareHouseId, + this.productId, + this.rowData.isProductAvailable + ); + } +} diff --git a/packages/admin-web-angular/src/app/@shared/render-component/store-products-table/store-order-product-amount/store-order-product-amount.component.html b/packages/admin-web-angular/src/app/@shared/render-component/store-products-table/store-order-product-amount/store-order-product-amount.component.html new file mode 100644 index 0000000..adb0d89 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/render-component/store-products-table/store-order-product-amount/store-order-product-amount.component.html @@ -0,0 +1,19 @@ +
+ +
+ {{ productAmount }} +
+ + +
diff --git a/packages/admin-web-angular/src/app/@shared/render-component/store-products-table/store-order-product-amount/store-order-product-amount.component.scss b/packages/admin-web-angular/src/app/@shared/render-component/store-products-table/store-order-product-amount/store-order-product-amount.component.scss new file mode 100644 index 0000000..65786da --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/render-component/store-products-table/store-order-product-amount/store-order-product-amount.component.scss @@ -0,0 +1,12 @@ +.btn-increase { + line-height: 0.5 !important; +} + +.btn-decrease { + line-height: 0.5 !important; + background-color: orange; +} + +.badge-dark { + font-size: 11px; +} diff --git a/packages/admin-web-angular/src/app/@shared/render-component/store-products-table/store-order-product-amount/store-order-product-amount.component.ts b/packages/admin-web-angular/src/app/@shared/render-component/store-products-table/store-order-product-amount/store-order-product-amount.component.ts new file mode 100644 index 0000000..f2f981e --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/render-component/store-products-table/store-order-product-amount/store-order-product-amount.component.ts @@ -0,0 +1,181 @@ +import { + Component, + OnInit, + OnDestroy, +} from '@angular/core'; +import { ViewCell } from 'ng2-smart-table'; +import { WarehouseProductsRouter } from '@modules/client.common.angular2/routers/warehouse-products-router.service'; +import { ToasterService } from 'angular2-toaster'; +import { ProductLocalesService } from '@modules/client.common.angular2/locale/product-locales.service'; +import { ILocaleMember } from '@modules/server.common/interfaces/ILocale'; +import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; +import { ConfirmationModalComponent } from '@app/@shared/confirmation-modal/confirmation-modal.component'; +import { OrderRouter } from '@modules/client.common.angular2/routers/order-router.service'; +import { takeUntil } from 'rxjs/operators'; +import { Subject } from 'rxjs'; + +@Component({ + styleUrls: ['store-order-product-amount.component.scss'], + templateUrl: 'store-order-product-amount.component.html', +}) +export class StoreOrderProductAmountComponent + implements ViewCell, OnInit, OnDestroy { + value: any; + rowData: any; + + private ngDestroy$ = new Subject(); + + public storeID: string; + public productID: string; + public productTitle: string; + public productAmount: number; + + public productObj: any; + + public orderId: string; + public orderWarehouseId: string; + + public availableProducts: number; + + public loading: boolean; + + public modalTitle: string; + + constructor( + private productLocalesService: ProductLocalesService, + private toasterService: ToasterService, + private warehouseProductsRouter: WarehouseProductsRouter, + private readonly modalService: NgbModal, + private readonly orderRouter: OrderRouter + ) {} + + ngOnInit() { + this.productID = this.rowData.product.id; + this.storeID = this.rowData.storeId; + this.productAmount = this.value; + this.productTitle = this.localeTranslate(this.rowData.product.title); + this.orderId = this.rowData.orderId; + this.orderWarehouseId = this.rowData.orderWarehouseId; + + this.availableProducts = this.rowData.warehouseProducts + .filter((d) => d.product === this.productID) + .map((d) => d.count)[0]; + + this.productObj = [ + { + productId: this.productID, + count: 1, + }, + ]; + } + + protected localeTranslate(member: ILocaleMember[]) { + return this.productLocalesService.getTranslate(member); + } + async addProduct() { + if (this.availableProducts > 0) { + const activeModal = this.modalService.open( + ConfirmationModalComponent, + { + size: 'sm', + container: 'nb-layout', + backdrop: 'static', + } + ); + const modalComponent: ConfirmationModalComponent = + activeModal.componentInstance; + + modalComponent.mainText = 'ARE_YOU_SURE_YOU_WANT_TO_INCREASE'; + + this.loading = true; + + await modalComponent.confirmEvent + .pipe(takeUntil(modalComponent.ngDestroy$)) + .subscribe((dataEvent) => { + // if (this.availableProducts > 0) { + this.orderRouter + .addProducts( + this.orderId, + this.productObj, + this.orderWarehouseId + ) + .then((r) => { + this.toasterService.pop( + `info`, + `Increased amount of the order's product!` + ); + }) + .catch((err) => { + this.toasterService.pop(`error`, `Error!`); + }); + // } else { + // this.toasterService.pop( + // `error`, + // `There are no more available products!` + // ); + // } + modalComponent.cancel(); + }); + } else { + this.toasterService.pop( + `error`, + `There are no more available products!` + ); + } + this.loading = false; + } + + async removeProduct() { + if (this.productAmount >= 1) { + const activeModal = this.modalService.open( + ConfirmationModalComponent, + { + size: 'sm', + container: 'nb-layout', + backdrop: 'static', + } + ); + const modalComponent: ConfirmationModalComponent = + activeModal.componentInstance; + + modalComponent.mainText = 'ARE_YOU_SURE_YOU_WANT_TO_DECREASE'; + this.loading = true; + + await modalComponent.confirmEvent + .pipe(takeUntil(modalComponent.ngDestroy$)) + .subscribe((dataEvent) => { + this.orderRouter + .decreaseOrderProducts( + this.orderId, + this.productObj, + this.orderWarehouseId + ) + .then((r) => { + this.toasterService.pop( + `info`, + `Decreased amount of the order's product!` + ); + }) + .catch((err) => { + this.toasterService.pop( + `error`, + `You can not decrease the qty + of the product to 0, but you can remove selected product!` + ); + }); + modalComponent.cancel(); + }); + } else { + this.toasterService.pop( + `error`, + `There are no products for remove!` + ); + } + this.loading = false; + } + + ngOnDestroy(): void { + this.ngDestroy$.next(); + this.ngDestroy$.complete(); + } +} diff --git a/packages/admin-web-angular/src/app/@shared/render-component/store-products-table/store-product-amount/store-product-amount.component.html b/packages/admin-web-angular/src/app/@shared/render-component/store-products-table/store-product-amount/store-product-amount.component.html new file mode 100644 index 0000000..dd2fbf6 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/render-component/store-products-table/store-product-amount/store-product-amount.component.html @@ -0,0 +1,19 @@ +
+ +
+ {{ productAmount }} +
+ + +
diff --git a/packages/admin-web-angular/src/app/@shared/render-component/store-products-table/store-product-amount/store-product-amount.component.scss b/packages/admin-web-angular/src/app/@shared/render-component/store-products-table/store-product-amount/store-product-amount.component.scss new file mode 100644 index 0000000..65786da --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/render-component/store-products-table/store-product-amount/store-product-amount.component.scss @@ -0,0 +1,12 @@ +.btn-increase { + line-height: 0.5 !important; +} + +.btn-decrease { + line-height: 0.5 !important; + background-color: orange; +} + +.badge-dark { + font-size: 11px; +} diff --git a/packages/admin-web-angular/src/app/@shared/render-component/store-products-table/store-product-amount/store-product-amount.component.ts b/packages/admin-web-angular/src/app/@shared/render-component/store-products-table/store-product-amount/store-product-amount.component.ts new file mode 100644 index 0000000..71aa16c --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/render-component/store-products-table/store-product-amount/store-product-amount.component.ts @@ -0,0 +1,69 @@ +import { Component, Output, EventEmitter, OnInit } from '@angular/core'; +import { ViewCell } from 'ng2-smart-table'; +import { WarehouseProductsRouter } from '@modules/client.common.angular2/routers/warehouse-products-router.service'; +import { ToasterService } from 'angular2-toaster'; +import { ProductLocalesService } from '@modules/client.common.angular2/locale/product-locales.service'; +import { ILocaleMember } from '@modules/server.common/interfaces/ILocale'; + +@Component({ + styleUrls: ['store-product-amount.component.scss'], + templateUrl: 'store-product-amount.component.html', +}) +export class StoreProductAmountComponent implements ViewCell, OnInit { + value: any; + rowData: any; + + public storeID: string; + public productID: string; + public productTitle: string; + public productAmount: number; + + public loading: boolean; + + constructor( + private productLocalesService: ProductLocalesService, + private toasterService: ToasterService, + private warehouseProductsRouter: WarehouseProductsRouter + ) {} + + ngOnInit() { + this.productID = this.rowData.product.id; + this.storeID = this.rowData.storeId; + this.storeID = this.rowData.storeId; + this.productAmount = this.value; + this.productTitle = this.localeTranslate(this.rowData.product.title); + } + + protected localeTranslate(member: ILocaleMember[]) { + return this.productLocalesService.getTranslate(member); + } + + addProduct() { + this.loading = true; + this.warehouseProductsRouter.increaseCount( + this.storeID, + this.productID, + 1 + ); + // this.loading = false; + this.toasterService.pop( + 'info', + `${this.productTitle} product amound increased!` + ); + } + + removeProduct() { + this.loading = true; + this.warehouseProductsRouter.decreaseCount( + this.storeID, + this.productID, + 1 + ); + // this.loading = false; + + this.toasterService.pop( + 'info', + `${this.productTitle} product amound decreased!` + ); + } +} diff --git a/packages/admin-web-angular/src/app/@shared/render-component/store-products-table/store-product-image/store-product-image.component.html b/packages/admin-web-angular/src/app/@shared/render-component/store-products-table/store-product-image/store-product-image.component.html new file mode 100644 index 0000000..29587ed --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/render-component/store-products-table/store-product-image/store-product-image.component.html @@ -0,0 +1,9 @@ +
+ +
diff --git a/packages/admin-web-angular/src/app/@shared/render-component/store-products-table/store-product-image/store-product-image.component.ts b/packages/admin-web-angular/src/app/@shared/render-component/store-products-table/store-product-image/store-product-image.component.ts new file mode 100644 index 0000000..97b8ec5 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/render-component/store-products-table/store-product-image/store-product-image.component.ts @@ -0,0 +1,24 @@ +import { Component } from '@angular/core'; +import { ViewCell } from 'ng2-smart-table'; +import { WarehouseProductsRouter } from '@modules/client.common.angular2/routers/warehouse-products-router.service'; + +@Component({ + templateUrl: './store-product-image.component.html', +}) +export class StoreProductImageComponent implements ViewCell { + value: any; + rowData: any; + + constructor(private warehouseProductsRouter: WarehouseProductsRouter) {} + + addProduct() { + const storeId = this.rowData.storeId; + const productId = this.rowData.id; + const disableImg = this.rowData.disableImg; + if (storeId && productId && !disableImg) { + this.warehouseProductsRouter.increaseCount(storeId, productId, 1); + } else { + console.warn("Can't add product."); + } + } +} diff --git a/packages/admin-web-angular/src/app/@shared/render-component/store-products-table/store-product-price.component.ts b/packages/admin-web-angular/src/app/@shared/render-component/store-products-table/store-product-price.component.ts new file mode 100644 index 0000000..343d2fe --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/render-component/store-products-table/store-product-price.component.ts @@ -0,0 +1,16 @@ +import { Component } from '@angular/core'; +import { ViewCell } from 'ng2-smart-table'; +import { environment } from 'environments/environment'; + +@Component({ + template: ` {{ currencySymbol }}{{ value }} `, +}) +export class StoreProductPriceComponent implements ViewCell { + value: any; + + rowData: any; + + currencySymbol: string = environment.CURRENCY_SYMBOL; + + constructor() {} +} diff --git a/packages/admin-web-angular/src/app/@shared/render-component/store-products-table/store-product-qty.component.ts b/packages/admin-web-angular/src/app/@shared/render-component/store-products-table/store-product-qty.component.ts new file mode 100644 index 0000000..d33efe0 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/render-component/store-products-table/store-product-qty.component.ts @@ -0,0 +1,16 @@ +import { Component } from '@angular/core'; +import { ViewCell } from 'ng2-smart-table'; + +@Component({ + template: ` +
+ {{ value }} +
+ `, +}) +export class StoreProductQtyComponent implements ViewCell { + value: any; + rowData: any; + + constructor() {} +} diff --git a/packages/admin-web-angular/src/app/@shared/render-component/store-products-table/store-products-table.module.ts b/packages/admin-web-angular/src/app/@shared/render-component/store-products-table/store-products-table.module.ts new file mode 100644 index 0000000..02fb9e2 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/render-component/store-products-table/store-products-table.module.ts @@ -0,0 +1,31 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { ThemeModule } from '../../../@theme'; +import { StoreProductImageComponent } from './store-product-image/store-product-image.component'; +import { StoreProductPriceComponent } from './store-product-price.component'; +import { StoreProductQtyComponent } from './store-product-qty.component'; +import { NbSpinnerModule, NbButtonModule } from '@nebular/theme'; +import { StoreProductAmountComponent } from './store-product-amount/store-product-amount.component'; +import { StoreOrderProductAmountComponent } from './store-order-product-amount/store-order-product-amount.component'; +import { ConfirmationModalModule } from '@app/@shared/confirmation-modal/confirmation-modal.module'; + +const COMPONENTS = [ + StoreProductImageComponent, + StoreProductPriceComponent, + StoreProductQtyComponent, + StoreProductAmountComponent, + StoreOrderProductAmountComponent, +]; + +@NgModule({ + imports: [ + CommonModule, + ThemeModule, + NbSpinnerModule, + ConfirmationModalModule, + NbButtonModule, + ], + declarations: COMPONENTS, + exports: COMPONENTS +}) +export class StoreProductsTableModule {} diff --git a/packages/admin-web-angular/src/app/@shared/render-component/warehouse-table/elapsed.component.ts b/packages/admin-web-angular/src/app/@shared/render-component/warehouse-table/elapsed.component.ts new file mode 100644 index 0000000..06db91e --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/render-component/warehouse-table/elapsed.component.ts @@ -0,0 +1,84 @@ +import { + Component, + Input, + OnInit, + OnDestroy, + ElementRef, + ViewChild, +} from '@angular/core'; +import { ViewCell } from 'ng2-smart-table'; +import { timer, Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; +import { getDifferenceFromTimes } from '../../../@core/utils/getDifferenceFromTimes '; +import OrderStatus from '@modules/server.common/enums/OrderStatus'; +import OrderCarrierStatus from '@modules/server.common/enums/OrderCarrierStatus'; +import OrderWarehouseStatus from '@modules/server.common/enums/OrderWarehouseStatus'; + +@Component({ + template: ` `, +}) +export class ElapsedComponent implements ViewCell, OnInit, OnDestroy { + @ViewChild('elapsedTime', { static: true }) + elapsedTime: ElementRef; + + private ngDestroy$ = new Subject(); + + @Input() + value: string | number; + + @Input() + rowData: any; + + get notProcessing() { + return ( + this.rowData && + this.rowData.order && + (this.rowData.order.status >= OrderStatus.Delivered || + this.rowData.carrierStatus >= + OrderCarrierStatus.DeliveryCompleted || + this.rowData.warehouseStatus === + OrderWarehouseStatus.GivenToCustomer) + ); + } + + ngOnInit() { + if (!this.notProcessing) { + this.updateTimer(); + } else { + if ( + this.rowData && + this.rowData.order && + (this.rowData.order.deliveryTime || + this.rowData.order.finishedProcessingTime) + ) { + const start = new Date( + this.rowData.order.deliveryTime || + this.rowData.order.finishedProcessingTime + ); + const end = new Date(this.rowData.createdAt); + + this.showTime(start, end); + } + } + } + + updateTimer() { + timer(1, 1000) + .pipe(takeUntil(this.ngDestroy$)) + .subscribe(() => { + const start = new Date(); + const end = new Date(this.rowData.createdAt); + this.showTime(start, end); + }); + } + + private showTime(start, end) { + const time = getDifferenceFromTimes(start, end); + this.elapsedTime.nativeElement.innerText = time; + } + + ngOnDestroy() { + this.ngDestroy$.next(); + this.ngDestroy$.complete(); + } +} diff --git a/packages/admin-web-angular/src/app/@shared/render-component/warehouse-table/elapsed/elapsed.component.ts b/packages/admin-web-angular/src/app/@shared/render-component/warehouse-table/elapsed/elapsed.component.ts new file mode 100644 index 0000000..b8b71f7 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/render-component/warehouse-table/elapsed/elapsed.component.ts @@ -0,0 +1,84 @@ +import { + Component, + Input, + OnInit, + OnDestroy, + ElementRef, + ViewChild, +} from '@angular/core'; +import { ViewCell } from 'ng2-smart-table'; +import { timer, Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; +import { getDifferenceFromTimes } from '../../../../@core/utils/getDifferenceFromTimes '; +import OrderStatus from '@modules/server.common/enums/OrderStatus'; +import OrderCarrierStatus from '@modules/server.common/enums/OrderCarrierStatus'; +import OrderWarehouseStatus from '@modules/server.common/enums/OrderWarehouseStatus'; + +@Component({ + template: ` `, +}) +export class ElapsedComponent implements ViewCell, OnInit, OnDestroy { + @ViewChild('elapsedTime', { static: true }) + elapsedTime: ElementRef; + + private ngDestroy$ = new Subject(); + + @Input() + value: string | number; + + @Input() + rowData: any; + + get notProcessing() { + return ( + this.rowData && + this.rowData.order && + (this.rowData.order.status >= OrderStatus.Delivered || + this.rowData.carrierStatus >= + OrderCarrierStatus.DeliveryCompleted || + this.rowData.warehouseStatus === + OrderWarehouseStatus.GivenToCustomer) + ); + } + + ngOnInit() { + if (!this.notProcessing) { + this.updateTimer(); + } else { + if ( + this.rowData && + this.rowData.order && + (this.rowData.order.deliveryTime || + this.rowData.order.finishedProcessingTime) + ) { + const start = new Date( + this.rowData.order.deliveryTime || + this.rowData.order.finishedProcessingTime + ); + const end = new Date(this.rowData.createdAt); + + this.showTime(start, end); + } + } + } + + updateTimer() { + timer(1, 1000) + .pipe(takeUntil(this.ngDestroy$)) + .subscribe(() => { + const start = new Date(); + const end = new Date(this.rowData.createdAt); + this.showTime(start, end); + }); + } + + private showTime(start, end) { + const time = getDifferenceFromTimes(start, end); + this.elapsedTime.nativeElement.innerText = time; + } + + ngOnDestroy() { + this.ngDestroy$.next(); + this.ngDestroy$.complete(); + } +} diff --git a/packages/admin-web-angular/src/app/@shared/render-component/warehouse-table/status.component.ts b/packages/admin-web-angular/src/app/@shared/render-component/warehouse-table/status.component.ts new file mode 100644 index 0000000..0c2c29d --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/render-component/warehouse-table/status.component.ts @@ -0,0 +1,50 @@ +import { Component, Input, OnInit } from '@angular/core'; +import { ViewCell } from 'ng2-smart-table'; + +@Component({ + styles: [ + ` + .paid-icon { + color: green; + margin-right: 3px; + } + .closed-icon { + color: red; + margin-right: 3px; + } + .actions-invites-requests { + width: 6rem; + } + `, + ], + template: ` +
+ + {{ text }} +
+ `, +}) +export class StatusComponent implements ViewCell, OnInit { + value: string | number; + @Input() + rowData: any; + paid: boolean; + closed: boolean; + + text: string; + + checkOrderField: string; + + constructor() {} + + ngOnInit() { + if (this.checkOrderField === 'isPaid') { + this.paid = this.rowData.isPaid; + } else { + this.closed = this.rowData.isCancelled; + } + } +} diff --git a/packages/admin-web-angular/src/app/@shared/render-component/warehouse-table/status/status.component.html b/packages/admin-web-angular/src/app/@shared/render-component/warehouse-table/status/status.component.html new file mode 100644 index 0000000..121e1b5 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/render-component/warehouse-table/status/status.component.html @@ -0,0 +1,4 @@ +
+ + {{ text }} +
diff --git a/packages/admin-web-angular/src/app/@shared/render-component/warehouse-table/status/status.component.ts b/packages/admin-web-angular/src/app/@shared/render-component/warehouse-table/status/status.component.ts new file mode 100644 index 0000000..220a3a3 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/render-component/warehouse-table/status/status.component.ts @@ -0,0 +1,42 @@ +import { Component, Input, OnInit } from '@angular/core'; +import { ViewCell } from 'ng2-smart-table'; + +@Component({ + styles: [ + ` + .paid-icon { + color: green; + margin-right: 3px; + } + .closed-icon { + color: red; + margin-right: 3px; + } + .actions-invites-requests { + width: 6rem; + } + `, + ], + templateUrl: './status.component.html', +}) +export class StatusComponent implements ViewCell, OnInit { + value: string | number; + @Input() + rowData: any; + paid: boolean; + closed: boolean; + + text: string; + + checkOrderField: string; + + constructor() {} + + ngOnInit() { + if (this.checkOrderField === 'isPaid') { + this.paid = this.rowData.isPaid; + } else { + this.closed = this.rowData.isCancelled; + } + } +} diff --git a/packages/admin-web-angular/src/app/@shared/render-component/warehouse-table/warehouse-actions.component.scss b/packages/admin-web-angular/src/app/@shared/render-component/warehouse-table/warehouse-actions.component.scss new file mode 100644 index 0000000..bf9ce8f --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/render-component/warehouse-table/warehouse-actions.component.scss @@ -0,0 +1,4 @@ +.iconsCont { + cursor: pointer; + margin-top: 11px; +} diff --git a/packages/admin-web-angular/src/app/@shared/render-component/warehouse-table/warehouse-actions.component.ts b/packages/admin-web-angular/src/app/@shared/render-component/warehouse-table/warehouse-actions.component.ts new file mode 100644 index 0000000..ade6dbb --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/render-component/warehouse-table/warehouse-actions.component.ts @@ -0,0 +1,35 @@ +import { Component, Input, OnInit } from '@angular/core'; +import { ViewCell } from 'ng2-smart-table'; +import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; +import { WarehouseTableInfoComponent } from '../../../pages/+warehouses/+warehouse/warehouse-info/warehouse-info.component'; + +@Component({ + styleUrls: ['warehouse-actions.component.scss'], + template: ` +
+

+ +

+
+ `, +}) +export class WarehouseActionsComponent implements ViewCell, OnInit { + value: string | number; + baseUrl: string; + + @Input() + rowData: any; + + constructor(private readonly modalService: NgbModal) {} + + ngOnInit() {} + openInfo() { + const activeModal = this.modalService.open( + WarehouseTableInfoComponent, + { size: 'lg', container: 'nb-layout' } + ); + const modalComponent: WarehouseTableInfoComponent = + activeModal.componentInstance; + modalComponent.warehouse = this.rowData.warehouseInfo; + } +} diff --git a/packages/admin-web-angular/src/app/@shared/render-component/warehouse-table/warehouse-actions/warehouse-actions.component.html b/packages/admin-web-angular/src/app/@shared/render-component/warehouse-table/warehouse-actions/warehouse-actions.component.html new file mode 100644 index 0000000..2f3cfce --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/render-component/warehouse-table/warehouse-actions/warehouse-actions.component.html @@ -0,0 +1,5 @@ +
+

+ +

+
diff --git a/packages/admin-web-angular/src/app/@shared/render-component/warehouse-table/warehouse-actions/warehouse-actions.component.scss b/packages/admin-web-angular/src/app/@shared/render-component/warehouse-table/warehouse-actions/warehouse-actions.component.scss new file mode 100644 index 0000000..8ffae2c --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/render-component/warehouse-table/warehouse-actions/warehouse-actions.component.scss @@ -0,0 +1,7 @@ +.iconsCont { + cursor: pointer; + margin-top: 11px; + i { + font-size: 1.4rem; + } +} diff --git a/packages/admin-web-angular/src/app/@shared/render-component/warehouse-table/warehouse-actions/warehouse-actions.component.ts b/packages/admin-web-angular/src/app/@shared/render-component/warehouse-table/warehouse-actions/warehouse-actions.component.ts new file mode 100644 index 0000000..9b0f086 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/render-component/warehouse-table/warehouse-actions/warehouse-actions.component.ts @@ -0,0 +1,29 @@ +import { Component, Input, OnInit } from '@angular/core'; +import { ViewCell } from 'ng2-smart-table'; +import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; +import { WarehouseTableInfoComponent } from '../../../../pages/+warehouses/+warehouse/warehouse-info/warehouse-info.component'; + +@Component({ + styleUrls: ['warehouse-actions.component.scss'], + templateUrl: './warehouse-actions.component.html', +}) +export class WarehouseActionsComponent implements ViewCell, OnInit { + value: string | number; + baseUrl: string; + + @Input() + rowData: any; + + constructor(private readonly modalService: NgbModal) {} + + ngOnInit() {} + openInfo() { + const activeModal = this.modalService.open( + WarehouseTableInfoComponent, + { size: 'lg', container: 'nb-layout' } + ); + const modalComponent: WarehouseTableInfoComponent = + activeModal.componentInstance; + modalComponent.warehouse = this.rowData.warehouseInfo; + } +} diff --git a/packages/admin-web-angular/src/app/@shared/render-component/warehouse-table/warehouse-email/warehouse-email.component.html b/packages/admin-web-angular/src/app/@shared/render-component/warehouse-table/warehouse-email/warehouse-email.component.html new file mode 100644 index 0000000..31a83d2 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/render-component/warehouse-table/warehouse-email/warehouse-email.component.html @@ -0,0 +1,5 @@ + diff --git a/packages/admin-web-angular/src/app/@shared/render-component/warehouse-table/warehouse-email/warehouse-email.component.scss b/packages/admin-web-angular/src/app/@shared/render-component/warehouse-table/warehouse-email/warehouse-email.component.scss new file mode 100644 index 0000000..c942bb6 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/render-component/warehouse-table/warehouse-email/warehouse-email.component.scss @@ -0,0 +1,12 @@ +div a { + i { + display: flex; + align-items: center; + justify-content: center; + width: 18px; + height: 18px; + } + align-items: center; + display: flex; + text-decoration: none; +} diff --git a/packages/admin-web-angular/src/app/@shared/render-component/warehouse-table/warehouse-email/warehouse-email.component.ts b/packages/admin-web-angular/src/app/@shared/render-component/warehouse-table/warehouse-email/warehouse-email.component.ts new file mode 100644 index 0000000..7e2513f --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/render-component/warehouse-table/warehouse-email/warehouse-email.component.ts @@ -0,0 +1,12 @@ +import { Component, Input } from '@angular/core'; + +@Component({ + styleUrls: ['./warehouse-email.component.scss'], + templateUrl: './warehouse-email.component.html', +}) +export class WarehouseEmailComponent { + @Input() + rowData: any; + + constructor() {} +} diff --git a/packages/admin-web-angular/src/app/@shared/render-component/warehouse-table/warehouse-image/warehouse-image.component.html b/packages/admin-web-angular/src/app/@shared/render-component/warehouse-table/warehouse-image/warehouse-image.component.html new file mode 100644 index 0000000..9698c2a --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/render-component/warehouse-table/warehouse-image/warehouse-image.component.html @@ -0,0 +1,6 @@ + diff --git a/packages/admin-web-angular/src/app/@shared/render-component/warehouse-table/warehouse-image/warehouse-image.component.scss b/packages/admin-web-angular/src/app/@shared/render-component/warehouse-table/warehouse-image/warehouse-image.component.scss new file mode 100644 index 0000000..45158e4 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/render-component/warehouse-table/warehouse-image/warehouse-image.component.scss @@ -0,0 +1,8 @@ +img { + width: 64px; + height: 64px; +} + +.redirect-image { + cursor: pointer; +} diff --git a/packages/admin-web-angular/src/app/@shared/render-component/warehouse-table/warehouse-image/warehouse-image.component.ts b/packages/admin-web-angular/src/app/@shared/render-component/warehouse-table/warehouse-image/warehouse-image.component.ts new file mode 100644 index 0000000..a6d7c3f --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/render-component/warehouse-table/warehouse-image/warehouse-image.component.ts @@ -0,0 +1,26 @@ +import { OnInit, Component } from '@angular/core'; +import { Router } from '@angular/router'; +import { ViewCell } from 'ng2-smart-table'; + +@Component({ + styleUrls: ['warehouse-image.component.scss'], + templateUrl: 'warehouse-image.component.html', +}) +export class WarehouseImageComponent implements ViewCell, OnInit { + value: any; + rowData: any; + imageUrl: string; + redirectPage: string; + + constructor(private readonly router: Router) {} + + ngOnInit() { + this.imageUrl = this.rowData.image; + } + + redirect() { + if (this.redirectPage) { + this.router.navigate([`${this.redirectPage}/${this.rowData.id}`]); + } + } +} diff --git a/packages/admin-web-angular/src/app/@shared/render-component/warehouse-table/warehouse-orders-number/warehouse-orders-number.component.html b/packages/admin-web-angular/src/app/@shared/render-component/warehouse-table/warehouse-orders-number/warehouse-orders-number.component.html new file mode 100644 index 0000000..9abd8dc --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/render-component/warehouse-table/warehouse-orders-number/warehouse-orders-number.component.html @@ -0,0 +1,8 @@ +
+
+ {{ numberQTY }} +
+
diff --git a/packages/admin-web-angular/src/app/@shared/render-component/warehouse-table/warehouse-orders-number/warehouse-orders-number.component.scss b/packages/admin-web-angular/src/app/@shared/render-component/warehouse-table/warehouse-orders-number/warehouse-orders-number.component.scss new file mode 100644 index 0000000..1a9e06e --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/render-component/warehouse-table/warehouse-orders-number/warehouse-orders-number.component.scss @@ -0,0 +1,3 @@ +.number-qty { + cursor: pointer; +} diff --git a/packages/admin-web-angular/src/app/@shared/render-component/warehouse-table/warehouse-orders-number/warehouse-orders-number.component.ts b/packages/admin-web-angular/src/app/@shared/render-component/warehouse-table/warehouse-orders-number/warehouse-orders-number.component.ts new file mode 100644 index 0000000..197b9bf --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/render-component/warehouse-table/warehouse-orders-number/warehouse-orders-number.component.ts @@ -0,0 +1,26 @@ +import { OnInit, Component } from '@angular/core'; +import { Router } from '@angular/router'; +import { ViewCell } from 'ng2-smart-table'; + +@Component({ + styleUrls: ['warehouse-orders-number.component.scss'], + templateUrl: 'warehouse-orders-number.component.html', +}) +export class WarehouseOrdersNumberComponent implements ViewCell, OnInit { + value: any; + rowData: any; + numberQTY: number; + redirectPage: string; + + constructor(private readonly router: Router) {} + + ngOnInit() { + this.numberQTY = this.rowData.ordersQty; + } + + redirect() { + if (this.redirectPage) { + this.router.navigate([`${this.redirectPage}/${this.rowData.id}`]); + } + } +} diff --git a/packages/admin-web-angular/src/app/@shared/render-component/warehouse-table/warehouse-phone/warehouse-phone.component.html b/packages/admin-web-angular/src/app/@shared/render-component/warehouse-table/warehouse-phone/warehouse-phone.component.html new file mode 100644 index 0000000..24433ee --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/render-component/warehouse-table/warehouse-phone/warehouse-phone.component.html @@ -0,0 +1,5 @@ + diff --git a/packages/admin-web-angular/src/app/@shared/render-component/warehouse-table/warehouse-phone/warehouse-phone.component.scss b/packages/admin-web-angular/src/app/@shared/render-component/warehouse-table/warehouse-phone/warehouse-phone.component.scss new file mode 100644 index 0000000..c942bb6 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/render-component/warehouse-table/warehouse-phone/warehouse-phone.component.scss @@ -0,0 +1,12 @@ +div a { + i { + display: flex; + align-items: center; + justify-content: center; + width: 18px; + height: 18px; + } + align-items: center; + display: flex; + text-decoration: none; +} diff --git a/packages/admin-web-angular/src/app/@shared/render-component/warehouse-table/warehouse-phone/warehouse-phone.component.ts b/packages/admin-web-angular/src/app/@shared/render-component/warehouse-table/warehouse-phone/warehouse-phone.component.ts new file mode 100644 index 0000000..e8ec1f8 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/render-component/warehouse-table/warehouse-phone/warehouse-phone.component.ts @@ -0,0 +1,12 @@ +import { Component, Input } from '@angular/core'; + +@Component({ + styleUrls: ['./warehouse-phone.component.scss'], + templateUrl: './warehouse-phone.component.html', +}) +export class WarehousePhoneComponent { + @Input() + rowData: any; + + constructor() {} +} diff --git a/packages/admin-web-angular/src/app/@shared/render-component/warehouse-table/warehouse-table.module.ts b/packages/admin-web-angular/src/app/@shared/render-component/warehouse-table/warehouse-table.module.ts new file mode 100644 index 0000000..2d01d60 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/render-component/warehouse-table/warehouse-table.module.ts @@ -0,0 +1,38 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { ThemeModule } from '../../../@theme'; +import { ElapsedComponent } from './elapsed/elapsed.component'; +import { WarehouseActionsComponent } from './warehouse-actions/warehouse-actions.component'; +import { HighlightModule } from 'ngx-highlightjs'; +import { TranslateModule } from '@ngx-translate/core'; +import { WarehouseTableInfoComponent } from '../../../pages/+warehouses/+warehouse/warehouse-info/warehouse-info.component'; +import { WarehouseImageComponent } from './warehouse-image/warehouse-image.component'; +import { RouterModule } from '@angular/router'; +import { WarehouseOrdersNumberComponent } from './warehouse-orders-number/warehouse-orders-number.component'; +import { StatusComponent } from './status/status.component'; +import { WarehouseEmailComponent } from './warehouse-email/warehouse-email.component'; +import { WarehousePhoneComponent } from './warehouse-phone/warehouse-phone.component'; + +const COMPONENTS = [ + ElapsedComponent, + WarehouseActionsComponent, + WarehouseTableInfoComponent, + WarehouseImageComponent, + WarehouseOrdersNumberComponent, + WarehouseEmailComponent, + WarehousePhoneComponent, + StatusComponent, +]; + +@NgModule({ + imports: [ + CommonModule, + ThemeModule, + RouterModule, + HighlightModule, + TranslateModule.forChild(), + ], + declarations: COMPONENTS, + exports: COMPONENTS +}) +export class WarehouseTableModule {} diff --git a/packages/admin-web-angular/src/app/@shared/styles/control-icon.shared.scss b/packages/admin-web-angular/src/app/@shared/styles/control-icon.shared.scss new file mode 100644 index 0000000..8d2821a --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/styles/control-icon.shared.scss @@ -0,0 +1,42 @@ +@import '../../@theme/styles/themes'; +@import '~@nebular/theme/components/card/card.component.theme'; + +@mixin optionally-clickable { + &[routerLink], + &[href], + &.clickable { + cursor: pointer; + } +} + +@mixin control-icon { + .control-icon { + width: 1.4em; + height: 1.4em; + outline: none; + + // path { + // fill: nb-theme(color-fg); + // } + + // &:hover path { + // fill: nb-theme(color-fg-text); + // } + + // &:active path { + // fill: nb-theme(color-fg-highlight); + // } + + &.control-icon-left { + float: left !important; + margin-right: 1.25rem; + } + + &.control-icon-right { + float: right !important; + margin-left: 1.25rem; + } + + @include optionally-clickable; + } +} diff --git a/packages/admin-web-angular/src/app/@shared/translate/translate.module.ts b/packages/admin-web-angular/src/app/@shared/translate/translate.module.ts new file mode 100644 index 0000000..e6ee171 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/translate/translate.module.ts @@ -0,0 +1,26 @@ +import { NgModule } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { TranslateHttpLoader } from '@ngx-translate/http-loader'; +import { + TranslateLoader, + TranslateModule as NgxTranslateModule +} from '@ngx-translate/core'; + +export function HttpLoaderFactory(http: HttpClient) { + return new TranslateHttpLoader(http, './assets/i18n/', '.json'); +} + +@NgModule({ + declarations: [], + exports: [NgxTranslateModule], + imports: [ + NgxTranslateModule.forChild({ + loader: { + provide: TranslateLoader, + useFactory: HttpLoaderFactory, + deps: [HttpClient] + } + }) + ] +}) +export class TranslateModule {} diff --git a/packages/admin-web-angular/src/app/@shared/user/ban-confirm/ban-confirm.component.html b/packages/admin-web-angular/src/app/@shared/user/ban-confirm/ban-confirm.component.html new file mode 100644 index 0000000..d01f8f8 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/user/ban-confirm/ban-confirm.component.html @@ -0,0 +1,37 @@ + + + diff --git a/packages/admin-web-angular/src/app/@shared/user/ban-confirm/ban-confirm.component.scss b/packages/admin-web-angular/src/app/@shared/user/ban-confirm/ban-confirm.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/packages/admin-web-angular/src/app/@shared/user/ban-confirm/ban-confirm.component.ts b/packages/admin-web-angular/src/app/@shared/user/ban-confirm/ban-confirm.component.ts new file mode 100644 index 0000000..256577e --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/user/ban-confirm/ban-confirm.component.ts @@ -0,0 +1,15 @@ +import { Component, OnInit, Input } from '@angular/core'; +import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; +import { CustomerViewModel } from '@app/pages/+customers/customers.component'; + +@Component({ + selector: 'ea-ban-confirm', + templateUrl: './ban-confirm.component.html', + styleUrls: ['./ban-confirm.component.scss'], +}) +export class BanConfirmComponent implements OnInit { + @Input() user: CustomerViewModel; + constructor(public readonly modal: NgbActiveModal) {} + + ngOnInit() {} +} diff --git a/packages/admin-web-angular/src/app/@shared/user/ban-confirm/ban-confirm.module.ts b/packages/admin-web-angular/src/app/@shared/user/ban-confirm/ban-confirm.module.ts new file mode 100644 index 0000000..18c2f21 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/user/ban-confirm/ban-confirm.module.ts @@ -0,0 +1,10 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { BanConfirmComponent } from './ban-confirm.component'; + +@NgModule({ + declarations: [BanConfirmComponent], + exports: [BanConfirmComponent], + imports: [CommonModule] +}) +export class BanConfirmModule {} diff --git a/packages/admin-web-angular/src/app/@shared/user/ban-confirm/index.ts b/packages/admin-web-angular/src/app/@shared/user/ban-confirm/index.ts new file mode 100644 index 0000000..707c02d --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/user/ban-confirm/index.ts @@ -0,0 +1,2 @@ +export * from './ban-confirm.module'; +export * from './ban-confirm.component'; diff --git a/packages/admin-web-angular/src/app/@shared/user/forms/basic-info/basic-info-form.component.html b/packages/admin-web-angular/src/app/@shared/user/forms/basic-info/basic-info-form.component.html new file mode 100644 index 0000000..e846d70 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/user/forms/basic-info/basic-info-form.component.html @@ -0,0 +1,116 @@ +
+
+ +
+
+ + +
+ +
+ +
+ +
+
+ +
+ + +
+ + +
+
+ {{ + 'SHARED.USER.FORMS.BASIC_INFO.ERRORS.INVALID_EMAIL' + | translate + }}! +
+
+ {{ + 'SHARED.USER.FORMS.BASIC_INFO.ERRORS.EMAIL_IS_ALREADY_IN_USE' + | translate + }}! +
+
+
+
+ +
+ + +
+ +
+ {{ 'SHARED.FORMS.ERRORS.LOGO_URL_REQUIRED' | translate }}! +
+
+
+ +
+ +
+
+
+ Invalid image + + +
+
+
+
+
+
diff --git a/packages/admin-web-angular/src/app/@shared/user/forms/basic-info/basic-info-form.component.scss b/packages/admin-web-angular/src/app/@shared/user/forms/basic-info/basic-info-form.component.scss new file mode 100644 index 0000000..45832e3 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/user/forms/basic-info/basic-info-form.component.scss @@ -0,0 +1,17 @@ +.preview-img { + padding-left: 14px; + padding-right: 16px; +} + +.img-rounded { + max-height: 70px; +} + +.remove-icon { + cursor: pointer; + + span { + position: absolute; + font-size: 1.1em; + } +} diff --git a/packages/admin-web-angular/src/app/@shared/user/forms/basic-info/basic-info-form.component.ts b/packages/admin-web-angular/src/app/@shared/user/forms/basic-info/basic-info-form.component.ts new file mode 100644 index 0000000..bcab18f --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/user/forms/basic-info/basic-info-form.component.ts @@ -0,0 +1,201 @@ +import { + Component, + Input, + OnDestroy, + ViewChild, + ElementRef, + AfterViewInit, + OnInit, +} from '@angular/core'; +import { + FormBuilder, + FormGroup, + Validators, + FormControl, + AbstractControl, +} from '@angular/forms'; +import { Subject } from 'rxjs'; +import { IUserCreateObject } from '@modules/server.common/interfaces/IUser'; +import { UsersService } from '../../../../@core/data/users.service'; +import { FormHelpers } from '../../../forms/helpers'; +import { ActivatedRoute } from '@angular/router'; +import { TranslateService } from '@ngx-translate/core'; +import { first, debounceTime } from 'rxjs/operators'; + +export type CustomerBasicInfo = Pick< + IUserCreateObject, + 'firstName' | 'lastName' | 'email' | 'image' +>; + +@Component({ + selector: 'ea-user-basic-info-form', + styleUrls: ['./basic-info-form.component.scss'], + templateUrl: 'basic-info-form.component.html', +}) +export class BasicInfoFormComponent + implements OnInit, OnDestroy, AfterViewInit { + @ViewChild('logoImagePreview') + logoImagePreview: ElementRef; + + @Input() + readonly form: FormGroup; + @Input() + showBasicInfoLabel: boolean = false; + + uploaderPlaceholder: string; + + private _ngDestroy$ = new Subject(); + private static _usersService: UsersService; + private static _customerId: string; + + constructor( + private translateService: TranslateService, + private readonly _usersService: UsersService, + private readonly _route: ActivatedRoute + ) { + const customerId = this._route.snapshot.paramMap.get('id'); + BasicInfoFormComponent.initialize(this._usersService, customerId); + } + + ngOnInit(): void { + this.getUploaderPlaceholderText(); + } + + ngAfterViewInit() { + this._setupUserLogoUrlValidation(); + } + + get firstName() { + return this.form.get('firstName'); + } + + get lastName() { + return this.form.get('lastName'); + } + + get image() { + return this.form.get('image'); + } + get showLogoMeta() { + return this.image && this.image.value !== ''; + } + + get email() { + return this.form.get('email'); + } + + static initialize(usersService: UsersService, customerId: string) { + this._usersService = usersService; + this._customerId = customerId; + } + + static destroy() { + BasicInfoFormComponent._usersService = null; + BasicInfoFormComponent._customerId = null; + } + + static buildForm(formBuilder: FormBuilder): FormGroup { + const emailSearch$ = new Subject(); + let isSearchRdy = false; + + return formBuilder.group({ + firstName: [''], + lastName: [''], + image: [''], + email: [ + '', + [ + (control: AbstractControl) => + control.value ? Validators.email(control) : null, + ], + async (ctrlEmail: FormControl) => { + if (!isSearchRdy) { + emailSearch$ + .pipe(debounceTime(500)) + .subscribe(async () => { + const hasExistedEmail = await this._usersService + .isUserExists({ + exceptCustomerId: this._customerId, + memberKey: 'email', + memberValue: ctrlEmail.value, + }) + .toPromise(); + + if (hasExistedEmail) { + ctrlEmail.setErrors({ emailTaken: true }); + } + }); + + isSearchRdy = true; + } + + if ( + isSearchRdy && + ctrlEmail.value && + ctrlEmail.value.length > 0 + ) { + emailSearch$.next(true); + } + }, + ], + }); + } + + getValue(): CustomerBasicInfo { + const basicInfo = this.form.getRawValue() as { + firstName: string; + lastName: string; + image: string; + email: string; + }; + + return { + ...(basicInfo.firstName ? { firstName: basicInfo.firstName } : {}), + ...(basicInfo.lastName ? { lastName: basicInfo.lastName } : {}), + ...(basicInfo.image ? { image: basicInfo.image } : {}), + ...(basicInfo.email ? { email: basicInfo.email } : {}), + }; + } + + setValue(basicInfo: T) { + FormHelpers.deepMark(this.form, 'dirty'); + + this.form.setValue({ + firstName: basicInfo.firstName ? basicInfo.firstName : '', + lastName: basicInfo.lastName ? basicInfo.lastName : '', + image: basicInfo.image ? basicInfo.image : '', + email: basicInfo.email ? basicInfo.email : '', + }); + } + + deleteImg() { + this.image.setValue(''); + } + + ngOnDestroy() { + this._ngDestroy$.next(); + this._ngDestroy$.complete(); + BasicInfoFormComponent.destroy(); + } + + private _setupUserLogoUrlValidation() { + this.logoImagePreview.nativeElement.onload = () => { + if (this.showLogoMeta) { + this.image.setErrors(null); + } + }; + + this.logoImagePreview.nativeElement.onerror = () => { + if (this.showLogoMeta) { + this.image.setErrors({ invalidUrl: true }); + } + }; + } + + private async getUploaderPlaceholderText() { + this.uploaderPlaceholder = await this.translateService + .get('SHARED.USER.FORMS.BASIC_INFO.PICTURE_URL') + .pipe(first()) + .toPromise(); + } +} diff --git a/packages/admin-web-angular/src/app/@shared/user/forms/basic-info/index.ts b/packages/admin-web-angular/src/app/@shared/user/forms/basic-info/index.ts new file mode 100644 index 0000000..91afc05 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/user/forms/basic-info/index.ts @@ -0,0 +1 @@ +export * from './basic-info-form.component'; diff --git a/packages/admin-web-angular/src/app/@shared/user/forms/index.ts b/packages/admin-web-angular/src/app/@shared/user/forms/index.ts new file mode 100644 index 0000000..0a4bcc8 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/user/forms/index.ts @@ -0,0 +1,2 @@ +export * from './basic-info'; +export * from './user-forms.module'; diff --git a/packages/admin-web-angular/src/app/@shared/user/forms/user-forms.module.ts b/packages/admin-web-angular/src/app/@shared/user/forms/user-forms.module.ts new file mode 100644 index 0000000..ef83216 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/user/forms/user-forms.module.ts @@ -0,0 +1,19 @@ +import { NgModule } from '@angular/core'; +import { TranslateModule } from '@ngx-translate/core'; +import { FormWizardModule } from '@ever-co/angular2-wizard'; +import { ThemeModule } from '../../../@theme/theme.module'; +import { BasicInfoFormComponent } from './basic-info'; +import { FileUploaderModule } from '@app/@shared/file-uploader/file-uploader.module'; + +@NgModule({ + imports: [ + ThemeModule, + FormWizardModule, + FileUploaderModule, + TranslateModule.forChild(), + ], + exports: [BasicInfoFormComponent], + declarations: [BasicInfoFormComponent], + providers: [], +}) +export class UserFormsModule {} diff --git a/packages/admin-web-angular/src/app/@shared/user/user-mutation/index.ts b/packages/admin-web-angular/src/app/@shared/user/user-mutation/index.ts new file mode 100644 index 0000000..3f096e5 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/user/user-mutation/index.ts @@ -0,0 +1,2 @@ +export * from './user-mutation.component'; +export * from './user-mutation.module'; diff --git a/packages/admin-web-angular/src/app/@shared/user/user-mutation/user-mutation.component.html b/packages/admin-web-angular/src/app/@shared/user/user-mutation/user-mutation.component.html new file mode 100644 index 0000000..0683c3f --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/user/user-mutation/user-mutation.component.html @@ -0,0 +1,61 @@ + + + + + +
+ +
+ + +
+ + + + + +
+
+
diff --git a/packages/admin-web-angular/src/app/@shared/user/user-mutation/user-mutation.component.scss b/packages/admin-web-angular/src/app/@shared/user/user-mutation/user-mutation.component.scss new file mode 100644 index 0000000..ff71bd9 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/user/user-mutation/user-mutation.component.scss @@ -0,0 +1,23 @@ +:host ::ng-deep .card-footer button { + background-color: #111111 !important; + border: none !important; + color: white !important; + cursor: pointer !important; +} + +:host ::ng-deep .card-footer button:hover { + background-color: #bebebe !important; + color: #111111 !important; +} + +nb-card-header ul { + padding-left: 0; +} + +google-map { + margin-top: 25px; +} + +.user-modal { + margin: 0px; +} diff --git a/packages/admin-web-angular/src/app/@shared/user/user-mutation/user-mutation.component.ts b/packages/admin-web-angular/src/app/@shared/user/user-mutation/user-mutation.component.ts new file mode 100644 index 0000000..8f02f6c --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/user/user-mutation/user-mutation.component.ts @@ -0,0 +1,89 @@ +import { Component, ViewChild, EventEmitter } from '@angular/core'; +import { FormBuilder, FormControl, FormGroup } from '@angular/forms'; +import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; +import { ToasterService } from 'angular2-toaster'; +import { BasicInfoFormComponent } from '../forms/basic-info'; +import { LocationFormComponent } from '../../forms/location'; +import { UserAuthRouter } from '@modules/client.common.angular2/routers/user-auth-router.service'; + +@Component({ + selector: 'ea-user-mutation', + templateUrl: './user-mutation.component.html', + styleUrls: ['./user-mutation.component.scss'], +}) +export class UserMutationComponent { + @ViewChild('basicInfoForm') + basicInfoForm: BasicInfoFormComponent; + + @ViewChild('locationForm') + locationForm: LocationFormComponent; + + mapTypeEmitter = new EventEmitter(); + mapCoordEmitter = new EventEmitter(); + mapGeometryEmitter = new EventEmitter(); + + readonly form: FormGroup = this.formBuilder.group({ + basicInfo: BasicInfoFormComponent.buildForm(this.formBuilder), + apartment: LocationFormComponent.buildApartmentForm(this.formBuilder), + location: LocationFormComponent.buildForm(this.formBuilder), + }); + + readonly basicInfo = this.form.get('basicInfo') as FormControl; + readonly apartment = this.form.get('apartment') as FormControl; + readonly location = this.form.get('location') as FormControl; + + public loading: boolean; + + constructor( + protected readonly userAuthRouter: UserAuthRouter, + private readonly toasterService: ToasterService, + private readonly activeModal: NgbActiveModal, + private readonly formBuilder: FormBuilder + ) {} + + onCoordinatesChanges(coords: number[]) { + this.mapCoordEmitter.emit(coords); + } + + onGeometrySend(geometry: any) { + this.mapGeometryEmitter.emit(geometry); + } + + emitMapType(mapType: string) { + this.mapTypeEmitter.emit(mapType); + } + + async create() { + try { + this.loading = true; + + // GeoJSON in MongoDB save coordinates lng-lat, but locationForm return lat-lng for that we reverse them + const location = this.locationForm.getValue(); + location.loc.coordinates.reverse(); + + const user = await this.userAuthRouter.register({ + user: { + ...this.basicInfoForm.getValue(), + geoLocation: location, + apartment: this.locationForm.getApartment(), + }, + }); + this.loading = false; + this.toasterService.pop( + 'success', + `Customer with '${user.id}' was added` + ); + this.activeModal.close(user); + } catch (err) { + this.loading = false; + this.toasterService.pop( + 'error', + `Error in creating customer: "${err.message}"` + ); + } + } + + cancel() { + this.activeModal.dismiss('canceled'); + } +} diff --git a/packages/admin-web-angular/src/app/@shared/user/user-mutation/user-mutation.module.ts b/packages/admin-web-angular/src/app/@shared/user/user-mutation/user-mutation.module.ts new file mode 100644 index 0000000..c07efad --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/user/user-mutation/user-mutation.module.ts @@ -0,0 +1,24 @@ +import { NgModule } from '@angular/core'; +import { UserMutationComponent } from './user-mutation.component'; +import { TranslateModule } from '@ngx-translate/core'; +import { FormWizardModule } from '@ever-co/angular2-wizard'; +import { ThemeModule } from '../../../@theme/theme.module'; +import { UserFormsModule } from '../forms'; +import { LocationFormModule } from '../../forms/location'; +import { GoogleMapModule } from '../../forms/google-map/google-map.module'; +import { NbSpinnerModule } from '@nebular/theme'; + +@NgModule({ + imports: [ + ThemeModule, + FormWizardModule, + TranslateModule.forChild(), + UserFormsModule, + LocationFormModule, + GoogleMapModule, + NbSpinnerModule, + ], + exports: [UserMutationComponent], + declarations: [UserMutationComponent] +}) +export class UserMutationModule {} diff --git a/packages/admin-web-angular/src/app/@shared/warehouse-product/forms/add-warehouse-products-table/add-warehouse-products-table.component.html b/packages/admin-web-angular/src/app/@shared/warehouse-product/forms/add-warehouse-products-table/add-warehouse-products-table.component.html new file mode 100644 index 0000000..fed1c95 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/warehouse-product/forms/add-warehouse-products-table/add-warehouse-products-table.component.html @@ -0,0 +1,7 @@ + + + + diff --git a/packages/admin-web-angular/src/app/@shared/warehouse-product/forms/add-warehouse-products-table/add-warehouse-products-table.component.scss b/packages/admin-web-angular/src/app/@shared/warehouse-product/forms/add-warehouse-products-table/add-warehouse-products-table.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/packages/admin-web-angular/src/app/@shared/warehouse-product/forms/add-warehouse-products-table/add-warehouse-products-table.component.ts b/packages/admin-web-angular/src/app/@shared/warehouse-product/forms/add-warehouse-products-table/add-warehouse-products-table.component.ts new file mode 100644 index 0000000..bb9ea2d --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/warehouse-product/forms/add-warehouse-products-table/add-warehouse-products-table.component.ts @@ -0,0 +1,224 @@ +import { Component, OnInit, OnDestroy, Input } from '@angular/core'; +import { LocalDataSource } from 'ng2-smart-table'; +import Product from '@modules/server.common/entities/Product'; +import { first, takeUntil } from 'rxjs/operators'; +import { Subject, Observable, forkJoin } from 'rxjs'; +import { PriceCountInputComponent } from '../../../render-component/price-countInput/price-countInput.component'; +import { TranslateService } from '@ngx-translate/core'; +import Warehouse from '@modules/server.common/entities/Warehouse'; +import { WarehouseRouter } from '@modules/client.common.angular2/routers/warehouse-router.service'; +import { CheckboxComponent } from '@app/@shared/render-component/customer-orders-table/checkbox/checkbox.component'; + +@Component({ + selector: 'ea-add-warehouse-products-table', + templateUrl: './add-warehouse-products-table.component.html', +}) +export class AddWarehouseProductsComponent implements OnInit, OnDestroy { + @Input() + boxShadow: string; + @Input() + perPage = 5; + + settingsSmartTable: object; + sourceSmartTable = new LocalDataSource(); + + private ngDestroy$ = new Subject(); + private warehouseProducts: any[]; + private warehouse: Warehouse; + + constructor( + private _translateService: TranslateService, + private warehouseRouter: WarehouseRouter + ) {} + + ngOnInit(): void { + this._loadSettingsSmartTable(); + } + + get allWarehouseProducts() { + return [...this.warehouseProducts]; + } + + productsIsValid() { + if (this.warehouseProducts) { + const notRedy = this.warehouseProducts.filter( + (p) => + !p.count || + !p.price || + (!p['isTakeaway'] && !p['isDeliveryRequired']) + )[0]; + + return notRedy ? false : true; + } + } + + async loadDataSmartTable(products: Product[], warehouseId?: string) { + this.warehouseProducts = products.map((p) => { + return { product: p.id }; + }); + + if (warehouseId) { + this.warehouse = await this.warehouseRouter + .get(warehouseId) + .pipe(first()) + .toPromise(); + if (this.warehouseProducts) { + this.warehouseProducts.map((p) => { + p['isTakeaway'] = this.warehouse.productsTakeaway; + p['isDeliveryRequired'] = this.warehouse.productsDelivery; + if (!p['isTakeaway'] && !p['isDeliveryRequired']) { + p['isDeliveryRequired'] = true; + p['isTakeaway'] = true; + } + }); + } + } + + const productsVM = products.map((product) => { + const resObj = { + name: product.title, + id: product.id, + takeProductDelivery: this.warehouse.productsDelivery, + takeProductTakeaway: this.warehouse.productsTakeaway, + }; + if (!resObj.takeProductDelivery && !resObj.takeProductTakeaway) { + resObj.takeProductDelivery = true; + resObj.takeProductTakeaway = true; + } + + return resObj; + }); + + this.sourceSmartTable.load(productsVM); + } + + private _loadSettingsSmartTable() { + const columnTitlePrefix = 'WAREHOUSE_VIEW.SAVE.'; + const getTranslate = (name: string): Observable => + this._translateService.get(columnTitlePrefix + name); + + forkJoin( + getTranslate('PRODUCT_NAME'), + getTranslate('PRICE'), + getTranslate('COUNT'), + getTranslate('DELIVERY'), + getTranslate('TAKEAWAY') + ) + .pipe(takeUntil(this.ngDestroy$)) + .subscribe(([name, price, count, delivery, takeaway]) => { + this.settingsSmartTable = { + actions: false, + hideSubHeader: true, + // selectMode: 'multi', + columns: { + name: { + title: name, + filter: false, + }, + price: { + title: price, + type: 'custom', + filter: false, + renderComponent: PriceCountInputComponent, + onComponentInitFunction: async (instance) => { + instance.placeholder = price; + + const id = await instance.id + .pipe(first()) + .toPromise(); + const warehouseProd = this.warehouseProducts.filter( + (p) => p.product === id + )[0]; + + instance.newValue + .pipe(takeUntil(this.ngDestroy$)) + .subscribe((v) => { + warehouseProd['initialPrice'] = v; + warehouseProd['price'] = v; + }); + }, + }, + count: { + title: count, + type: 'custom', + filter: false, + renderComponent: PriceCountInputComponent, + onComponentInitFunction: async (instance) => { + instance.placeholder = count; + const id = await instance.id + .pipe(first()) + .toPromise(); + const warehouseProd = this.warehouseProducts.filter( + (p) => p.product === id + )[0]; + warehouseProd['count'] = 1; + instance.newValue + .pipe(takeUntil(this.ngDestroy$)) + .subscribe((v) => { + warehouseProd['count'] = v; + }); + }, + }, + delivery: { + title: delivery, + type: 'custom', + filter: false, + renderComponent: CheckboxComponent, + onComponentInitFunction: async (instance) => { + instance.type = 'delivery'; + const id = await instance.id + .pipe(first()) + .toPromise(); + const warehouseProd = this.warehouseProducts.filter( + (p) => p.product === id + )[0]; + instance.newValue + .pipe(takeUntil(this.ngDestroy$)) + .subscribe((res) => { + if (res.type === 'delivery') { + warehouseProd[ + 'isDeliveryRequired' + ] = res.checked; + } + }); + }, + }, + takeaway: { + title: takeaway, + type: 'custom', + filter: false, + renderComponent: CheckboxComponent, + onComponentInitFunction: async ( + instance: CheckboxComponent + ) => { + instance.type = 'takeaway'; + const id = await instance.id + .pipe(first()) + .toPromise(); + const warehouseProd = this.warehouseProducts.filter( + (p) => p.product === id + )[0]; + instance.newValue + .pipe(takeUntil(this.ngDestroy$)) + .subscribe((res) => { + if (res.type === 'takeaway') { + warehouseProd['isTakeaway'] = + res.checked; + } + }); + }, + }, + }, + pager: { + display: true, + perPage: this.perPage, + }, + }; + }); + } + + ngOnDestroy() { + this.ngDestroy$.next(); + this.ngDestroy$.complete(); + } +} diff --git a/packages/admin-web-angular/src/app/@shared/warehouse-product/forms/add-warehouse-products-table/index.ts b/packages/admin-web-angular/src/app/@shared/warehouse-product/forms/add-warehouse-products-table/index.ts new file mode 100644 index 0000000..3c3446a --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/warehouse-product/forms/add-warehouse-products-table/index.ts @@ -0,0 +1 @@ +export * from './add-warehouse-products-table.component'; diff --git a/packages/admin-web-angular/src/app/@shared/warehouse-product/forms/index.ts b/packages/admin-web-angular/src/app/@shared/warehouse-product/forms/index.ts new file mode 100644 index 0000000..579b27a --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/warehouse-product/forms/index.ts @@ -0,0 +1,2 @@ +export * from './warehouse-product-forms.module'; +export * from './warehouse-add-choice'; diff --git a/packages/admin-web-angular/src/app/@shared/warehouse-product/forms/warehouse-add-choice/index.ts b/packages/admin-web-angular/src/app/@shared/warehouse-product/forms/warehouse-add-choice/index.ts new file mode 100644 index 0000000..7237dcf --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/warehouse-product/forms/warehouse-add-choice/index.ts @@ -0,0 +1 @@ +export * from './warehouse-add-choice.component'; diff --git a/packages/admin-web-angular/src/app/@shared/warehouse-product/forms/warehouse-add-choice/warehouse-add-choice.component.html b/packages/admin-web-angular/src/app/@shared/warehouse-product/forms/warehouse-add-choice/warehouse-add-choice.component.html new file mode 100644 index 0000000..34d0919 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/warehouse-product/forms/warehouse-add-choice/warehouse-add-choice.component.html @@ -0,0 +1,29 @@ + +
+
+ +
+
+ +
+
+
diff --git a/packages/admin-web-angular/src/app/@shared/warehouse-product/forms/warehouse-add-choice/warehouse-add-choice.component.scss b/packages/admin-web-angular/src/app/@shared/warehouse-product/forms/warehouse-add-choice/warehouse-add-choice.component.scss new file mode 100644 index 0000000..12ed2ea --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/warehouse-product/forms/warehouse-add-choice/warehouse-add-choice.component.scss @@ -0,0 +1,42 @@ +.option { + width: 200px; + height: 150px; + border: 2px solid #dadfe6; + margin: 0 auto; + position: relative; + text-align: center; + color: #2a2a2a; + text-transform: uppercase; + letter-spacing: 0.4px; + font-weight: 500; + font-family: Exo; + -webkit-transition: none; + transition: none; + cursor: default; + padding: 0.75rem 1.5rem; + font-size: 1rem; + line-height: 1.25; + border-radius: 0.375rem; + padding-top: 63px; +} + +.option:hover { + background: #dadfe6; +} + +.selected { + background: #dadfe6; +} + +.add-products-modal { + font-size: 0.95rem; + + button { + text-transform: uppercase; + margin: 10%; + margin-top: 11%; + margin: auto !important !important; + width: 244px; + white-space: normal; + } +} diff --git a/packages/admin-web-angular/src/app/@shared/warehouse-product/forms/warehouse-add-choice/warehouse-add-choice.component.ts b/packages/admin-web-angular/src/app/@shared/warehouse-product/forms/warehouse-add-choice/warehouse-add-choice.component.ts new file mode 100644 index 0000000..26b02cc --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/warehouse-product/forms/warehouse-add-choice/warehouse-add-choice.component.ts @@ -0,0 +1,19 @@ +import { Component, EventEmitter } from '@angular/core'; + +@Component({ + selector: 'ea-warehouse-add-choice', + styleUrls: ['./warehouse-add-choice.component.scss'], + templateUrl: './warehouse-add-choice.component.html', +}) +export class WarehouseAddChoiceComponent { + public choice: EventEmitter = new EventEmitter(); + + choiceType: number; + + constructor() {} + + changeChoice(choiceType) { + this.choiceType = choiceType === 'new' ? 2 : 1; + this.choice.emit(choiceType); + } +} diff --git a/packages/admin-web-angular/src/app/@shared/warehouse-product/forms/warehouse-product-forms.module.ts b/packages/admin-web-angular/src/app/@shared/warehouse-product/forms/warehouse-product-forms.module.ts new file mode 100644 index 0000000..8d41181 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/warehouse-product/forms/warehouse-product-forms.module.ts @@ -0,0 +1,32 @@ +import { NgModule } from '@angular/core'; +import { TranslateModule } from '@ngx-translate/core'; +import { FormWizardModule } from '@ever-co/angular2-wizard'; +import { ThemeModule } from '../../../@theme'; +import { WarehouseAddChoiceComponent } from './warehouse-add-choice'; +import { AddWarehouseProductsComponent } from './add-warehouse-products-table'; +import { Ng2SmartTableModule } from 'ng2-smart-table'; +import { RenderComponentsModule } from '../../render-component/render-components.module'; +import { WarehouseProductsComponent } from './warehouse-products-table'; +import { StoreProductsTableModule } from '@app/@shared/render-component/store-products-table/store-products-table.module'; +import { ProductCategoriesModule } from '@app/@shared/render-component/product-categories/product-categories.module'; + +const COMPONENTS = [ + WarehouseAddChoiceComponent, + AddWarehouseProductsComponent, + WarehouseProductsComponent, +]; + +@NgModule({ + imports: [ + ThemeModule, + FormWizardModule, + TranslateModule.forChild(), + Ng2SmartTableModule, + RenderComponentsModule, + StoreProductsTableModule, + ProductCategoriesModule, + ], + exports: COMPONENTS, + declarations: COMPONENTS, +}) +export class WarehouseProductFormsModule {} diff --git a/packages/admin-web-angular/src/app/@shared/warehouse-product/forms/warehouse-products-table/index.ts b/packages/admin-web-angular/src/app/@shared/warehouse-product/forms/warehouse-products-table/index.ts new file mode 100644 index 0000000..90845a5 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/warehouse-product/forms/warehouse-products-table/index.ts @@ -0,0 +1 @@ +export * from './warehouse-products-table.component'; diff --git a/packages/admin-web-angular/src/app/@shared/warehouse-product/forms/warehouse-products-table/warehouse-products-table.component.html b/packages/admin-web-angular/src/app/@shared/warehouse-product/forms/warehouse-products-table/warehouse-products-table.component.html new file mode 100644 index 0000000..e42ca24 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/warehouse-product/forms/warehouse-products-table/warehouse-products-table.component.html @@ -0,0 +1,8 @@ + + diff --git a/packages/admin-web-angular/src/app/@shared/warehouse-product/forms/warehouse-products-table/warehouse-products-table.component.ts b/packages/admin-web-angular/src/app/@shared/warehouse-product/forms/warehouse-products-table/warehouse-products-table.component.ts new file mode 100644 index 0000000..b751d35 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/warehouse-product/forms/warehouse-products-table/warehouse-products-table.component.ts @@ -0,0 +1,251 @@ +import { + Component, + OnDestroy, + OnInit, + Output, + EventEmitter, + Input, +} from '@angular/core'; +import { LocalDataSource } from 'ng2-smart-table'; +import { StoreProductPriceComponent } from '@app/@shared/render-component/store-products-table/store-product-price.component'; +import { StoreProductAmountComponent } from '@app/@shared/render-component/store-products-table/store-product-amount/store-product-amount.component'; +import { ProductCategoriesComponent } from '@app/@shared/render-component/product-categories/product-categories'; +import { ProductTitleRedirectComponent } from '@app/@shared/render-component/product-title-redirect/product-title-redirect.component'; +import { Observable, forkJoin, Subject } from 'rxjs'; +import { TranslateService } from '@ngx-translate/core'; +import { takeUntil } from 'rxjs/operators'; +import WarehouseProduct from '@modules/server.common/entities/WarehouseProduct'; +import { ProductLocalesService } from '@modules/client.common.angular2/locale/product-locales.service'; +import { ProductsCategoryService } from '@app/@core/data/productsCategory.service'; +import Product from '@modules/server.common/entities/Product'; +import { StoreProductImageComponent } from '@app/@shared/render-component/store-products-table/store-product-image/store-product-image.component'; +import { CheckboxComponent } from '@app/@shared/render-component/customer-orders-table/checkbox/checkbox.component'; +import { IsAvailableCheckBox } from '@app/@shared/render-component/store-product-is-available-checkbox/is-available-checkbox.component'; +import { ProductTakeawayDeliveryComponent } from '@app/@shared/render-component/product-takeaway-delivery/product-takeaway-delivery.component'; + +export interface WarehouseProductViewModel { + id: string; + image: string; + title: string; + description: string; + details: string; + categories: { ids: string[]; search: string }; + price: number; + qty: number; + storeId: string; + product: Product; + allCategories: any[]; + isProductAvailable: boolean; +} + +@Component({ + selector: 'ea-warehouse-products-table', + templateUrl: './warehouse-products-table.component.html', +}) +export class WarehouseProductsComponent implements OnInit, OnDestroy { + @Output() + onEdit = new EventEmitter(); + @Output() + onDelete = new EventEmitter(); + + @Input() + perPage: number = 5; + @Input() + selectMode = 'multi'; + + settingsSmartTable: object; + sourceSmartTable = new LocalDataSource(); + selectedProducts: WarehouseProductViewModel[] = []; + + private ngDestroy$ = new Subject(); + private categoriesInfo: any = []; + + constructor( + private readonly _translateService: TranslateService, + private readonly _productLocalesService: ProductLocalesService, + private readonly _productsCategoryService: ProductsCategoryService + ) {} + + get hasSelectedProducts(): boolean { + return this.selectedProducts.length > 0; + } + + ngOnInit(): void { + this._getCategories(); + this._loadSettingsSmartTable(); + this._applyTranslationOnSmartTable(); + } + + ngOnDestroy(): void { + this.ngDestroy$.next(); + this.ngDestroy$.complete(); + } + + async loadDataSmartTable(products: WarehouseProduct[], storeId: string) { + const productsVM = products.map((product: WarehouseProduct) => { + return { + id: product.productId, + image: this._productLocalesService.getTranslate( + product.product['images'] + ), + title: this._productLocalesService.getTranslate( + product.product['title'] + ), + description: this._productLocalesService.getTranslate( + product.product['description'] + ), + details: this._productLocalesService.getTranslate( + product.product['details'] + ), + categories: { + ids: product.product['categories'], + search: + this.categoriesInfo && + this.categoriesInfo + .filter((c) => + product.product['categories'].includes(c.id) + ) + .map((c) => + this._productLocalesService.getTranslate(c.name) + ) + .toString(), + }, + price: product.price, + qty: product.count, + type: product, + storeId, + product: product.product, + allCategories: this.categoriesInfo, + isProductAvailable: product.isProductAvailable, + isTakeaway: product.isTakeaway, + isDeliveryRequired: product.isDeliveryRequired, + }; + }); + + this.sourceSmartTable.load(productsVM); + } + + selectProductTmp(ev) { + this.selectedProducts = ev.selected; + } + + private _getCategories() { + this._productsCategoryService + .getCategories() + .subscribe((categories) => { + this.categoriesInfo = categories; + }); + } + + private _loadSettingsSmartTable() { + let columnTitlePrefix = 'WAREHOUSE_VIEW.PRODUCTS_TAB.'; + const getTranslate = (name: string): Observable => + this._translateService.get(columnTitlePrefix + name); + + forkJoin( + this._translateService.get('Id'), + getTranslate('IMAGE'), + getTranslate('TITLE'), + getTranslate('DESCRIPTION'), + getTranslate('DETAILS'), + getTranslate('CATEGORY'), + getTranslate('PRICE'), + getTranslate('QUANTITY'), + getTranslate('AVAILABILITY'), + getTranslate('TYPE') + ) + .pipe(takeUntil(this.ngDestroy$)) + .subscribe( + ([ + id, + image, + titleTr, + description, + details, + category, + price, + quantity, + availability, + type, + ]) => { + this.settingsSmartTable = { + mode: 'external', + actions: { + add: false, + position: 'left', + }, + edit: { + editButtonContent: '', + }, + delete: { + deleteButtonContent: '', + confirmDelete: true, + }, + selectMode: this.selectMode, + columns: { + image: { + title: image, + type: 'custom', + class: 'text-center', + renderComponent: StoreProductImageComponent, + filter: false, + }, + title: { + title: titleTr, + type: 'custom', + renderComponent: ProductTitleRedirectComponent, + }, + description: { title: description }, + details: { title: details }, + categories: { + title: category, + type: 'custom', + renderComponent: ProductCategoriesComponent, + filterFunction( + cell?: any, + search?: string + ): boolean { + if (cell.search.includes(search)) { + return true; + } else { + return false; + } + }, + }, + price: { + title: price, + type: 'custom', + renderComponent: StoreProductPriceComponent, + }, + qty: { + title: quantity, + class: 'text-center', + type: 'custom', + renderComponent: StoreProductAmountComponent, + }, + isAvailable: { + title: availability, + type: 'custom', + renderComponent: IsAvailableCheckBox, + }, + type: { + title: type, + type: 'custom', + renderComponent: ProductTakeawayDeliveryComponent, + }, + }, + pager: { + display: true, + perPage: this.perPage, + }, + }; + } + ); + } + + private _applyTranslationOnSmartTable() { + this._translateService.onLangChange.subscribe(() => { + this._loadSettingsSmartTable(); + }); + } +} diff --git a/packages/admin-web-angular/src/app/@shared/warehouse-product/warehouse-product-create/index.ts b/packages/admin-web-angular/src/app/@shared/warehouse-product/warehouse-product-create/index.ts new file mode 100644 index 0000000..0d63d2f --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/warehouse-product/warehouse-product-create/index.ts @@ -0,0 +1,2 @@ +export * from './warehouse-product-create.component'; +export * from './warehouse-product-create.module'; diff --git a/packages/admin-web-angular/src/app/@shared/warehouse-product/warehouse-product-create/warehouse-product-create.component.html b/packages/admin-web-angular/src/app/@shared/warehouse-product/warehouse-product-create/warehouse-product-create.component.html new file mode 100644 index 0000000..9ec0ce4 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/warehouse-product/warehouse-product-create/warehouse-product-create.component.html @@ -0,0 +1,72 @@ + + + + +
+ +
+ + +
+ + + + + +
+ + +
+ +
+
+
diff --git a/packages/admin-web-angular/src/app/@shared/warehouse-product/warehouse-product-create/warehouse-product-create.component.scss b/packages/admin-web-angular/src/app/@shared/warehouse-product/warehouse-product-create/warehouse-product-create.component.scss new file mode 100644 index 0000000..92ab477 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/warehouse-product/warehouse-product-create/warehouse-product-create.component.scss @@ -0,0 +1,28 @@ +.ng-valid[required], +.ng-valid.required { + border-left: 5px solid #42a948; +} + +:host ::ng-deep .card-footer button { + background-color: #111111 !important; + border: none !important; + color: white !important; + cursor: pointer !important; +} + +:host ::ng-deep .card-footer button:hover { + background-color: #bebebe !important; + color: #111111 !important; +} + +body > div.pac-container.pac-logo { + background: green !important !important !important !important !important !important; +} + +.add-products-to-store-modal { + margin: 0px; +} + +hr { + border-top: none !important; +} diff --git a/packages/admin-web-angular/src/app/@shared/warehouse-product/warehouse-product-create/warehouse-product-create.component.ts b/packages/admin-web-angular/src/app/@shared/warehouse-product/warehouse-product-create/warehouse-product-create.component.ts new file mode 100644 index 0000000..81baf4a --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/warehouse-product/warehouse-product-create/warehouse-product-create.component.ts @@ -0,0 +1,265 @@ +import { Component, ViewChild, OnInit, OnDestroy } from '@angular/core'; +import { FormBuilder, FormGroup, FormControl } from '@angular/forms'; +import { WarehouseAddChoiceComponent } from '../forms'; +import { takeUntil, first } from 'rxjs/operators'; +import { firstValueFrom, Subject } from 'rxjs'; +import { BasicInfoFormComponent } from '../../product/forms'; +import { ProductsTableComponent } from '../../product/forms/products-table'; +import { ProductsService } from '../../../@core/data/products.service'; +import Product from '@modules/server.common/entities/Product'; +import { IProductCreateObject } from '@modules/server.common/interfaces/IProduct'; +import { AddWarehouseProductsComponent } from '../forms/add-warehouse-products-table'; +import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; +import { WarehousesService } from '../../../@core/data/warehouses.service'; +import { WizardComponent } from '@ever-co/angular2-wizard'; +import { TranslateService } from '@ngx-translate/core'; +import { NbThemeService } from '@nebular/theme'; +import ProductsCategory from '@modules/server.common/entities/ProductsCategory'; +import { ProductsCategoryService } from '@app/@core/data/productsCategory.service'; +import Warehouse from '@modules/server.common/entities/Warehouse'; +import WarehouseProduct from '@modules/server.common/entities/WarehouseProduct'; +import { NotifyService } from '@app/@core/services/notify/notify.service'; + +const perPage = 3; + +@Component({ + selector: 'ea-warehouse-product-create', + templateUrl: './warehouse-product-create.component.html', + styleUrls: ['./warehouse-product-create.component.scss'], +}) +export class WarehouseProductCreateComponent implements OnInit, OnDestroy { + loading: boolean; + + currentThemeCosmic: boolean = false; + + warehouseId: string; + productsCategories: ProductsCategory[]; + selectedWarehouse: Warehouse; + perPage: number; + choiced: string; + + @ViewChild('warehouseAddChoice', { static: true }) + warehouseAddChoice: WarehouseAddChoiceComponent; + + @ViewChild('basicInfoForm') + basicInfoForm: BasicInfoFormComponent; + + @ViewChild('productsTable') + productsTable: ProductsTableComponent; + + @ViewChild('addWarehouseProductsTable') + addWarehouseProductsTable: AddWarehouseProductsComponent; + + @ViewChild('wizzardFrom') + wizzardFrom: WizardComponent; + + @ViewChild('wizzardFromStep1', { static: true }) + wizzardFromStep1: any; + + hasSelectedProducts = () => false; + validAllProducts = () => false; + + readonly form: FormGroup = this._formBuilder.group({ + basicInfo: BasicInfoFormComponent.buildForm(this._formBuilder), + }); + + readonly basicInfo = this.form.get('basicInfo') as FormControl; + $productsTablePagesChanges: any; + + private ngDestroy$ = new Subject(); + private createdProducts: Product[] = []; + private selectedProducts: any[] = []; + isSetp2: boolean; + + constructor( + private readonly _formBuilder: FormBuilder, + private readonly _productsService: ProductsService, + private readonly _warehousesService: WarehousesService, + private readonly _activeModal: NgbActiveModal, + private readonly _translateService: TranslateService, + private readonly _themeService: NbThemeService, + private readonly _productsCategoryService: ProductsCategoryService, + private readonly _notifyService: NotifyService + ) { + this.perPage = perPage; + this.loadProductCategories(); + this.checkCurrentTheme(); + } + + ngOnInit(): void { + this.wizzardFromStep1.showNext = false; + this.warehouseAddChoice.choice + .pipe(takeUntil(this.ngDestroy$)) + .subscribe(async (res) => { + this.choiced = res; + }); + } + + checkCurrentTheme() { + if (this._themeService.currentTheme === 'cosmic') { + this.currentThemeCosmic = true; + } + } + + get hasCoiced() { + return this.choiced; + } + + get isValidBasicInfoForm() { + return this.basicInfo && this.basicInfo.valid && this.isSetp2; + } + + async createProduct() { + // TODO: implement (we have same method in other component already) + } + + async addProducts() { + this.loading = true; + try { + const productsForAdd = this.addWarehouseProductsTable + .allWarehouseProducts; + const res = await this._warehousesService + .addProducts(this.warehouseId, productsForAdd) + .pipe(first()) + .toPromise(); + this.loading = false; + const message = `${productsForAdd.length} products was added`; + this._notifyService.success(message); + this.cancel(); + } catch (error) { + let message = `Something went wrong`; + if (error.message === 'Validation error') { + message = error.message; + } + this.loading = false; + this._notifyService.error(message); + this.cancel(); + } + } + + async onStep1Next() { + this.isSetp2 = true; + if (this.choiced === 'existing') { + this.hasSelectedProducts = () => { + if (this.productsTable) { + return this.productsTable.hasSelectedProducts; + } + return false; + }; + if (this.$productsTablePagesChanges) { + this.$productsTablePagesChanges.unsubscribe(); + } + + const loadDataSmartTable = async (page = 1) => { + let existedProductsIds = this.selectedWarehouse.products.map( + (product: WarehouseProduct) => product.productId + ); + + if (this.createdProducts) { + for (const product of this.createdProducts) { + existedProductsIds.push(product.id); + } + } + + let products = await this._productsService + .getProducts( + { + skip: perPage * (page - 1), + limit: perPage, + }, + existedProductsIds + ) + .pipe(first()) + .toPromise(); + + const dataCount = await this.getDataCount(existedProductsIds); + + this.productsTable.loadDataSmartTable( + products, + dataCount, + page + ); + }; + + if (this.productsTable) { + this.$productsTablePagesChanges = this.productsTable.pagesChanges$ + .pipe(takeUntil(this.ngDestroy$)) + .subscribe((page: number) => { + loadDataSmartTable(page); + }); + } + + await loadDataSmartTable(); + } + } + + selectedChoice() { + if (this.choiced) { + this.onStep1Next(); + this.wizzardFrom.next(); + } + } + + async onStep2Next() { + this.isSetp2 = false; + if (this.choiced === 'new') { + if (this.basicInfo.valid) { + const productCreateObject: IProductCreateObject = await this.basicInfoForm.setupProductCreateObject(); + const product = await this._productsService + .create(productCreateObject) + .pipe(first()) + .toPromise(); + this.createdProducts.push(product); + + const message = `Product ${productCreateObject.title[0].value} is created`; + this._notifyService.success(message); + } + } else { + this.selectedProducts = this.productsTable.selectedProducts; + } + const newCreatedProducts = this.createdProducts.map((p) => { + return { + id: p.id, + title: p.title[0].value, + }; + }); + this.addWarehouseProductsTable.loadDataSmartTable( + [...newCreatedProducts, ...this.selectedProducts], + this.warehouseId + ); + + this.validAllProducts = () => + this.addWarehouseProductsTable.productsIsValid(); + } + + onStep2Prev() { + if (this.choiced === 'existing') { + this.selectedProducts = []; + this.hasSelectedProducts = () => true; + } + this.choiced = null; + } + + onStep3Prev() { + this.isSetp2 = true; + } + + async loadProductCategories() { + this.productsCategories = await firstValueFrom( + this._productsCategoryService.getCategories() + ); + } + + cancel() { + this._activeModal.dismiss('canceled'); + } + + private async getDataCount(existedProductsIds: string[]) { + return this._productsService.getCountOfProducts(existedProductsIds); + } + + ngOnDestroy() { + this.ngDestroy$.next(); + this.ngDestroy$.complete(); + } +} diff --git a/packages/admin-web-angular/src/app/@shared/warehouse-product/warehouse-product-create/warehouse-product-create.module.ts b/packages/admin-web-angular/src/app/@shared/warehouse-product/warehouse-product-create/warehouse-product-create.module.ts new file mode 100644 index 0000000..22c94a4 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/warehouse-product/warehouse-product-create/warehouse-product-create.module.ts @@ -0,0 +1,24 @@ +import { NgModule } from '@angular/core'; +import { ThemeModule } from '../../../@theme/theme.module'; +import { TranslateModule } from '@ngx-translate/core'; +import { FormWizardModule } from '@ever-co/angular2-wizard'; +import { WarehouseProductCreateComponent } from './warehouse-product-create.component'; +import { WarehouseProductFormsModule } from '../forms'; +import { ProductFormsModule } from '../../product/forms'; +import { NbSpinnerModule } from '@nebular/theme'; +import { NotifyService } from '@app/@core/services/notify/notify.service'; + +@NgModule({ + imports: [ + ThemeModule, + FormWizardModule, + TranslateModule.forChild(), + WarehouseProductFormsModule, + ProductFormsModule, + NbSpinnerModule, + ], + providers: [NotifyService], + exports: [WarehouseProductCreateComponent], + declarations: [WarehouseProductCreateComponent] +}) +export class WarehouseProductCreateModule {} diff --git a/packages/admin-web-angular/src/app/@shared/warehouse/+warehouse-order-modal/make-order-comment.component.ts b/packages/admin-web-angular/src/app/@shared/warehouse/+warehouse-order-modal/make-order-comment.component.ts new file mode 100644 index 0000000..6d6cced --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/warehouse/+warehouse-order-modal/make-order-comment.component.ts @@ -0,0 +1,34 @@ +import { Component, EventEmitter, Input, Output } from '@angular/core'; +import { ViewCell } from 'ng2-smart-table'; + +@Component({ + styles: [ + ` + .order-comment-wrapper textarea { + width: 100%; + } + `, + ], + template: ` +
+ +
+ `, +}) +export class MakeOrderCommentComponent implements ViewCell { + @Input() + value; + @Input() + rowData: any; + + @Output() + comment = new EventEmitter(); + + get productId(): string { + return this.value.productId; + } + + setComment(e) { + this.comment.emit(e.target.value); + } +} diff --git a/packages/admin-web-angular/src/app/@shared/warehouse/+warehouse-order-modal/warehouse-order-input.component.html b/packages/admin-web-angular/src/app/@shared/warehouse/+warehouse-order-modal/warehouse-order-input.component.html new file mode 100644 index 0000000..192ddeb --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/warehouse/+warehouse-order-modal/warehouse-order-input.component.html @@ -0,0 +1,19 @@ +
+ + + {{ productAmount }} + + +
diff --git a/packages/admin-web-angular/src/app/@shared/warehouse/+warehouse-order-modal/warehouse-order-input.component.ts b/packages/admin-web-angular/src/app/@shared/warehouse/+warehouse-order-modal/warehouse-order-input.component.ts new file mode 100644 index 0000000..1ef4259 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/warehouse/+warehouse-order-modal/warehouse-order-input.component.ts @@ -0,0 +1,43 @@ +import { Component, EventEmitter, Input, Output } from '@angular/core'; +import { ViewCell } from 'ng2-smart-table'; + +@Component({ + styles: ['.order-input-wrapper { text-align: center; }'], + templateUrl: './warehouse-order-input.component.html', +}) +export class WarehouseOrderInputComponent implements ViewCell { + @Input() + value; + + @Input() + rowData: any; + + @Output() + amount = new EventEmitter(); + + private _productAmount: number = 0; + + get warehouseAvailableProducts(): number { + if (this.value) { + return +this.value.available; + } + return 0; + } + + get productId(): string { + return this.value.productId; + } + + get productAmount(): number { + return this._productAmount; + } + + set productAmount(amount: number) { + this._productAmount = amount; + this.amount.emit(amount); + } + + get warehouseHasAvailable(): boolean { + return this._productAmount < this.warehouseAvailableProducts; + } +} diff --git a/packages/admin-web-angular/src/app/@shared/warehouse/+warehouse-order-modal/warehouse-order-modal.component.html b/packages/admin-web-angular/src/app/@shared/warehouse/+warehouse-order-modal/warehouse-order-modal.component.html new file mode 100644 index 0000000..fd124d7 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/warehouse/+warehouse-order-modal/warehouse-order-modal.component.html @@ -0,0 +1,39 @@ + + + + {{ TRANSLATE_PREFIXES.MAKE_ORDER | translate }} + + {{ modalTitle }} + + +
+ +
+
+ + + + + + + + +
diff --git a/packages/admin-web-angular/src/app/@shared/warehouse/+warehouse-order-modal/warehouse-order-modal.component.scss b/packages/admin-web-angular/src/app/@shared/warehouse/+warehouse-order-modal/warehouse-order-modal.component.scss new file mode 100644 index 0000000..4c08963 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/warehouse/+warehouse-order-modal/warehouse-order-modal.component.scss @@ -0,0 +1,34 @@ +nb-card { + margin: 0; + + nb-card-header { + border: none; + div.checkbox label { + cursor: pointer; + display: flex; + align-items: flex-start; + margin-bottom: 0; + width: 40%; + + input[type='checkbox'] { + zoom: 1.5; + margin-right: 2.5%; + } + } + } + + nb-card-body { + padding: 0; + + ng2-smart-table { + text-align: center; + } + } + + nb-card-footer { + button { + float: right; + margin-right: 2%; + } + } +} diff --git a/packages/admin-web-angular/src/app/@shared/warehouse/+warehouse-order-modal/warehouse-order-modal.component.ts b/packages/admin-web-angular/src/app/@shared/warehouse/+warehouse-order-modal/warehouse-order-modal.component.ts new file mode 100644 index 0000000..d79681f --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/warehouse/+warehouse-order-modal/warehouse-order-modal.component.ts @@ -0,0 +1,310 @@ +import { + Component, + EventEmitter, + Input, + OnDestroy, + OnInit, + Output, +} from '@angular/core'; +import { LocalDataSource } from 'ng2-smart-table'; +import { Subject } from 'rxjs'; +import { WarehouseProductsRouter } from '@modules/client.common.angular2/routers/warehouse-products-router.service'; +import WarehouseProduct from '@modules/server.common/entities/WarehouseProduct'; +import { WarehouseOrderInputComponent } from './warehouse-order-input.component'; +import { IOrderCreateInputProduct } from '@modules/server.common/routers/IWarehouseOrdersRouter'; +import { TranslateService } from '@ngx-translate/core'; +import { ProductLocalesService } from '@modules/client.common.angular2/locale/product-locales.service'; +import { ILocaleMember } from '@modules/server.common/interfaces/ILocale'; +import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; +import { takeUntil } from 'rxjs/operators'; +import { MakeOrderCommentComponent } from './make-order-comment.component'; + +@Component({ + selector: 'ea-warehouse-order-modal', + styleUrls: ['./warehouse-order-modal.component.scss'], + templateUrl: './warehouse-order-modal.component.html', +}) +export class WarehouseOrderModalComponent implements OnInit, OnDestroy { + @Input() + warehouseId: string; + + @Input() + loading: boolean; + + @Input() + showOrderAction: boolean = true; + + @Input() + modalTitle: string; + + @Input() + actionBtnText: string; + + @Output() + makeOrderEmitter = new EventEmitter(); + + @Output() + isOrderAllowedEmitter = new EventEmitter(); + + settingsSmartTable: object; + sourceSmartTable = new LocalDataSource(); + + private _warehouseProducts: WarehouseProduct[] = []; + + private _orderProducts: IOrderCreateInputProduct[] = []; + + private _clearAvailableProductsFilter: boolean = false; + + private _ngDestroy$ = new Subject(); + + constructor( + private readonly _translateService: TranslateService, + private readonly activeModal: NgbActiveModal, + private readonly _productLocaleService: ProductLocalesService, + private readonly _warehouseProductsRouter: WarehouseProductsRouter + ) {} + + get TRANSLATE_PREFIXES() { + const basePrefix = 'SHARED.WAREHOUSE.ORDER_MODAL'; + const smartTableTitlesPrefix = 'SMART_TABLE.TITLES'; + + return { + MAKE_ORDER: `${basePrefix}.MAKE_ORDER`, + ONLY_AVAILABLE: `${basePrefix}.ONLY_AVAILABLE`, + ORDER: `${basePrefix}.ORDER`, + SMART_TABLE: { + TITLES: { + IMG: `${basePrefix}.${smartTableTitlesPrefix}.IMG`, + PRODUCT: `${basePrefix}.${smartTableTitlesPrefix}.PRODUCT`, + PRICE: `${basePrefix}.${smartTableTitlesPrefix}.PRICE`, + AVAILABLE: `${basePrefix}.${smartTableTitlesPrefix}.AVAILABLE`, + AMOUNT: `${basePrefix}.${smartTableTitlesPrefix}.AMOUNT`, + COMMENT: `${basePrefix}.${smartTableTitlesPrefix}.COMMENT`, + }, + }, + }; + } + + get canOrder(): boolean { + return this._orderProducts.some((product) => product.count > 0); + } + + ngOnInit() { + this._loadSettingsSmartTable(); + this._loadWarehouseProducts(); + } + + makeOrder() { + this.loading = true; + this.makeOrderEmitter.emit( + this._orderProducts.filter(({ count }) => count > 0) + ); + } + + cancel() { + this.activeModal.dismiss('canceled'); + } + + toggleAvalableProducts() { + if (this._clearAvailableProductsFilter) { + this.sourceSmartTable.setFilter([]); + } else { + this.sourceSmartTable.setFilter([ + { + field: 'available', + search: '0', + filter: (element, valueToCompare) => { + const regex = /
([0-9]+)<\/div>/gm; + const productCount = +regex.exec(element)[1]; + + return productCount > +valueToCompare; + }, + }, + ]); + } + + this._clearAvailableProductsFilter = !this + ._clearAvailableProductsFilter; + } + + private _loadWarehouseProducts() { + this._warehouseProductsRouter + .getAvailable(this.warehouseId) + .pipe(takeUntil(this._ngDestroy$)) + .subscribe((p) => { + Object.assign(this._warehouseProducts, p); + this._loadDataSmartTable(); + }); + } + + private _loadDataSmartTable() { + this._orderProducts = this._warehouseProducts.map( + (wp: WarehouseProduct) => { + return { + productId: wp.productId, + count: 0, + comment: '', + }; + } + ); + + const productsData = this._warehouseProducts.map( + (wp: WarehouseProduct) => { + return { + img: ` + + `, + product: ` + ${this._getTranslate(wp.product['title'])} + `, + price: `${wp.price}$`, + available: ` +
${wp.count}
+ `, + amount: { productId: wp.productId, available: wp.count }, + comment: { productId: wp.productId }, + }; + } + ); + + this.sourceSmartTable.setSort([ + { + field: 'available', + direction: 'desc', + compare: this._compareByAvailableProducts, + }, + ]); + this.sourceSmartTable.load(productsData); + } + + private _getTranslate(members: ILocaleMember[]): string { + return this._productLocaleService.getTranslate(members); + } + + private _compareByAvailableProducts(_, first, second) { + const regex = /
([0-9]+)<\/div>/gm; + + const matchFirst = +regex.exec(first)[1]; + regex.lastIndex = 0; // to reset the regex + const matchSecond = +regex.exec(second)[1]; + + return _ > 0 ? matchFirst - matchSecond : matchSecond - matchFirst; + } + + private _translate(key: string): string { + let translationResult = ''; + + this._translateService.get(key).subscribe((res) => { + translationResult = res; + }); + + return translationResult; + } + + private _loadSettingsSmartTable() { + const img = this._translate( + this.TRANSLATE_PREFIXES.SMART_TABLE.TITLES.IMG + ); + const product = this._translate( + this.TRANSLATE_PREFIXES.SMART_TABLE.TITLES.PRODUCT + ); + const price = this._translate( + this.TRANSLATE_PREFIXES.SMART_TABLE.TITLES.PRICE + ); + const available = this._translate( + this.TRANSLATE_PREFIXES.SMART_TABLE.TITLES.AVAILABLE + ); + const amount = this._translate( + this.TRANSLATE_PREFIXES.SMART_TABLE.TITLES.AMOUNT + ); + + const comment = this._translate( + this.TRANSLATE_PREFIXES.SMART_TABLE.TITLES.COMMENT + ); + + this.settingsSmartTable = { + actions: false, + pager: { perPage: 5 }, + columns: { + img: { + title: img, + filter: false, + type: 'html', + width: '50px', + }, + product: { + title: product, + type: 'html', + }, + price: { + title: price, + filter: false, + compareFunction: (_, first, second) => { + const matchFirst = +first.replace('$', ''); + const matchSecond = +second.replace('$', ''); + return _ > 0 + ? matchFirst - matchSecond + : matchSecond - matchFirst; + }, + }, + available: { + title: available, + type: 'html', + filter: false, + compareFunction: this._compareByAvailableProducts, + }, + amount: { + title: amount, + filter: false, + type: 'custom', + renderComponent: WarehouseOrderInputComponent, + onComponentInitFunction: ( + childInstance: WarehouseOrderInputComponent + ) => { + childInstance.amount + .pipe(takeUntil(this._ngDestroy$)) + .subscribe((count) => { + const wProduct = this._orderProducts.find( + ({ productId }) => + productId === childInstance.productId + ); + wProduct.count = count; + + if (!this.showOrderAction) { + this.isOrderAllowedEmitter.emit( + this.canOrder + ); + } + }); + }, + }, + + comment: { + title: comment, + filter: false, + type: 'custom', + renderComponent: MakeOrderCommentComponent, + onComponentInitFunction: ( + childInstance: MakeOrderCommentComponent + ) => { + childInstance.comment + .pipe(takeUntil(this._ngDestroy$)) + .subscribe((comment) => { + const wProduct = this._orderProducts.find( + ({ productId }) => + productId === childInstance.productId + ); + + wProduct.comment = comment; + }); + }, + }, + }, + }; + } + + ngOnDestroy() { + this._ngDestroy$.next(); + this._ngDestroy$.complete(); + } +} diff --git a/packages/admin-web-angular/src/app/@shared/warehouse/+warehouse-order-modal/warehouse-order-modal.module.ts b/packages/admin-web-angular/src/app/@shared/warehouse/+warehouse-order-modal/warehouse-order-modal.module.ts new file mode 100644 index 0000000..0fcdc9a --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/warehouse/+warehouse-order-modal/warehouse-order-modal.module.ts @@ -0,0 +1,34 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { Ng2SmartTableModule } from 'ng2-smart-table'; +import { ThemeModule } from '../../../@theme'; +import { WarehouseOrderModalComponent } from './warehouse-order-modal.component'; +import { WarehouseOrderInputComponent } from './warehouse-order-input.component'; +import { TranslateModule } from '@ngx-translate/core'; +import { NbSpinnerModule, NbButtonModule } from '@nebular/theme'; +import { MakeOrderCommentComponent } from './make-order-comment.component'; + +const COMPONENTS = [ + WarehouseOrderModalComponent, + WarehouseOrderInputComponent, + MakeOrderCommentComponent, +]; + +@NgModule({ + imports: [ + CommonModule, + ThemeModule, + Ng2SmartTableModule, + NbSpinnerModule, + NbSpinnerModule, + TranslateModule.forChild(), + NbButtonModule, + ], + declarations: [ + WarehouseOrderModalComponent, + WarehouseOrderInputComponent, + MakeOrderCommentComponent, + ], + exports: COMPONENTS +}) +export class WarehouseOrderModalModule {} diff --git a/packages/admin-web-angular/src/app/@shared/warehouse/customer-warehouses-table/customer-warehouses-table.component.html b/packages/admin-web-angular/src/app/@shared/warehouse/customer-warehouses-table/customer-warehouses-table.component.html new file mode 100644 index 0000000..7bcfb1e --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/warehouse/customer-warehouses-table/customer-warehouses-table.component.html @@ -0,0 +1,6 @@ + + diff --git a/packages/admin-web-angular/src/app/@shared/warehouse/customer-warehouses-table/customer-warehouses-table.component.scss b/packages/admin-web-angular/src/app/@shared/warehouse/customer-warehouses-table/customer-warehouses-table.component.scss new file mode 100644 index 0000000..0ee9007 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/warehouse/customer-warehouses-table/customer-warehouses-table.component.scss @@ -0,0 +1,34 @@ +:host ::ng-deep ng2-smart-table { + tr { + th { + padding: 18px !important; + } + td { + padding: 17px !important; + } + } + + text-align: center; + + td.ng2-smart-actions.ng2-smart-action-multiple-select { + text-align: center; + cursor: pointer; + } + + tr.ng2-smart-filters th { + text-align: center; + } + + // tr td, th { + // &:first-of-type { + // border-left: none; + // } + // &:last-of-type { + // border-right: none; + // } + // } + + tr.ng2-smart-filters > th:nth-child(1) { + text-align: center !important; + } +} diff --git a/packages/admin-web-angular/src/app/@shared/warehouse/customer-warehouses-table/customer-warehouses-table.component.ts b/packages/admin-web-angular/src/app/@shared/warehouse/customer-warehouses-table/customer-warehouses-table.component.ts new file mode 100644 index 0000000..e1afe73 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/warehouse/customer-warehouses-table/customer-warehouses-table.component.ts @@ -0,0 +1,143 @@ +import { + AfterViewInit, + Component, + EventEmitter, + Input, + OnInit, + OnDestroy, +} from '@angular/core'; +import { LocalDataSource } from 'ng2-smart-table'; +import { WarehouseViewModel } from '../../../models/WarehouseViewModel'; +import { WarehouseOrderComponent } from '../../../pages/+warehouses/+warehouse-order/warehouse-order.component'; +import { Subject, forkJoin, Observable } from 'rxjs'; +import { RedirectNameComponent } from '../../render-component/name-redirect/name-redirect.component'; +import { TranslateService } from '@ngx-translate/core'; +import { takeUntil } from 'rxjs/operators'; + +@Component({ + selector: 'ea-customer-warehouses-table', + styleUrls: ['./customer-warehouses-table.component.scss'], + templateUrl: './customer-warehouses-table.component.html', +}) +export class CustomerWarehousesTableComponent + implements OnInit, AfterViewInit, OnDestroy { + private ngDestroy$ = new Subject(); + + @Input() + sourceEvent: EventEmitter; + + @Input() + selectWarehouseTmp: (ev) => void; + + public settingsSmartTable: any; + public sourceSmartTable = new LocalDataSource(); + + private _ngDestroy$ = new Subject(); + + constructor(private _translateService: TranslateService) {} + ngOnInit() { + this._loadSettingsSmartTable(); + this._loadDataSmartTable(); + this._applyTranslationOnSmartTable(); + } + + ngAfterViewInit(): void { + this._addCustomHTMLElements(); + } + + ngOnDestroy() { + this._ngDestroy$.next(); + this._ngDestroy$.complete(); + } + + // This is just workaround to show some search icon on smart table, in the future maybe we must find better solution. + private _addCustomHTMLElements(): any { + const target = document.querySelector( + '#nearby-stores ng2-smart-table .ng2-smart-filters > th:nth-child(1)' + ); + if (target) { + target.innerHTML = + ''; + } + } + + private _applyTranslationOnSmartTable() { + this._translateService.onLangChange.subscribe(() => { + this._loadSettingsSmartTable(); + }); + } + + private _loadSettingsSmartTable() { + const columnTitlePrefix = 'CUSTOMERS_VIEW.SMART_TABLE_COLUMNS.'; + const getTranslate = (name: string): Observable => + this._translateService.get(columnTitlePrefix + name); + + forkJoin( + this._translateService.get('Id'), + getTranslate('NAME'), + getTranslate('EMAIL'), + getTranslate('PHONE'), + getTranslate('CITY'), + getTranslate('ADDRESS'), + getTranslate('ORDERS_QTY'), + getTranslate('ACTIONS') + ) + .pipe(takeUntil(this.ngDestroy$)) + .subscribe( + ([ + id, + name, + email, + phone, + city, + address, + orderQTY, + actions, + ]) => { + this.settingsSmartTable = { + actions: false, + selectMode: 'multi', + columns: { + name: { + title: name, + type: 'custom', + renderComponent: RedirectNameComponent, + onComponentInitFunction: (instance) => { + instance.redirectPage = 'stores'; + }, + }, + email: { title: email }, + phone: { title: phone }, + city: { title: city }, + address: { title: address }, + ordersQty: { + title: orderQTY, + type: 'html', + filter: false, + valuePrepareFunction: (_, vm) => + `${vm.ordersQty}`, + }, + actions: { + title: actions, + filter: false, + type: 'custom', + renderComponent: WarehouseOrderComponent, + }, + }, + pager: { + display: true, + perPage: 3, + }, + }; + } + ); + } + + private _loadDataSmartTable() { + this.sourceEvent + .pipe(takeUntil(this._ngDestroy$)) + .subscribe((rawSource) => { + this.sourceSmartTable.load(rawSource); + }); + } +} diff --git a/packages/admin-web-angular/src/app/@shared/warehouse/customer-warehouses-table/customer-warehouses-table.module.ts b/packages/admin-web-angular/src/app/@shared/warehouse/customer-warehouses-table/customer-warehouses-table.module.ts new file mode 100644 index 0000000..da53994 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/warehouse/customer-warehouses-table/customer-warehouses-table.module.ts @@ -0,0 +1,32 @@ +import { TranslateModule } from '@ngx-translate/core'; +import { NgModule } from '@angular/core'; +import { ThemeModule } from '../../../@theme'; +import { ToasterModule } from 'angular2-toaster'; +import { Ng2SmartTableModule } from 'ng2-smart-table'; +import { CustomerWarehousesTableComponent } from './customer-warehouses-table.component'; +import { WarehouseOrderComponent } from '../../../pages/+warehouses/+warehouse-order/warehouse-order.component'; +import { SelectWarehouseComponent } from '../select-warehouse.component/select-warehouse.component'; +import { WarehouseTableModule } from '../../render-component/warehouse-table/warehouse-table.module'; +import { WarehouseInfoComponent } from '@app/pages/+warehouses/+warehouse-order/warehouse-info/warehouse-info.component'; +import { HighlightModule } from 'ngx-highlightjs'; + +const COMPONENTS = [ + CustomerWarehousesTableComponent, + WarehouseOrderComponent, // TODO REMOVE! + SelectWarehouseComponent, // TODO REMOVE! + WarehouseInfoComponent, // TODO REMOVE! +]; + +@NgModule({ + imports: [ + ThemeModule, + ToasterModule, + TranslateModule.forChild(), + HighlightModule, + Ng2SmartTableModule, + WarehouseTableModule, + ], + declarations: COMPONENTS, + exports: COMPONENTS +}) +export class CustomerWarehousesTableModule {} diff --git a/packages/admin-web-angular/src/app/@shared/warehouse/forms/basic-info/basic-info-form.component.html b/packages/admin-web-angular/src/app/@shared/warehouse/forms/basic-info/basic-info-form.component.html new file mode 100644 index 0000000..546eef7 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/warehouse/forms/basic-info/basic-info-form.component.html @@ -0,0 +1,222 @@ +
+
+
+ + +
+ + +
+
+ {{ + 'WAREHOUSE_VIEW.MUTATION.ERRORS.NAME_IS_REQUIRED' + | translate + }}! +
+
+ {{ + 'WAREHOUSE_VIEW.MUTATION.ERRORS.NAME_ATLEAST_3_CHARS' + | translate + }}! +
+
+ {{ + 'WAREHOUSE_VIEW.MUTATION.ERRORS.NAME_MORE_THAN_30_CHARS' + | translate + }}! +
+
+
+
+ +
+ + +
+ + +
+ {{ + 'WAREHOUSE_VIEW.MUTATION.ERRORS.INVALID_URL' + | translate + }}! +
+
+
+ +
+ +
+
+
+ Invalid image +
+ +
+
+
+
+
+
+ +
+ + +
+
+ + ({{ 'WAREHOUSE_VIEW.MUTATION.RIGHT_NOW' | translate }}) +
+
+
+ +
+ + +
+ + +
+
+ {{ + 'WAREHOUSE_VIEW.MUTATION.ERRORS.USERNAME_IS_REQUIRED' + | translate + }}! +
+
+
+
+ +
+ + +
+ + +
+
+ {{ + 'WAREHOUSE_VIEW.MUTATION.ERRORS.PASSWORD_IS_REQUIRED' + | translate + }}! +
+
+
+
+ +
+ + +
+
+ + {{ + 'WAREHOUSE_VIEW.MUTATION.USE_ONLY_SPECIFIC_CARRIERS' + | translate + }} +
+ +
+ + +
+
+
+ + +
+ + +
+ + + Use All Carriers + + + Use Only Store Carriers + + + Prefer Store Carriers + + +
+
+
+
diff --git a/packages/admin-web-angular/src/app/@shared/warehouse/forms/basic-info/basic-info-form.component.scss b/packages/admin-web-angular/src/app/@shared/warehouse/forms/basic-info/basic-info-form.component.scss new file mode 100644 index 0000000..f675fda --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/warehouse/forms/basic-info/basic-info-form.component.scss @@ -0,0 +1,19 @@ +::ng-deep .carriers-dropdown { + .dropdown { + width: 100%; + } +} +.preview-img { + padding-left: 14px; + padding-right: 16px; +} + +.img-rounded { + // width: 70px; + max-height: 70px; +} + +.removeIcon { + padding-left: 4px; + padding-right: 4px; +} diff --git a/packages/admin-web-angular/src/app/@shared/warehouse/forms/basic-info/basic-info-form.component.ts b/packages/admin-web-angular/src/app/@shared/warehouse/forms/basic-info/basic-info-form.component.ts new file mode 100644 index 0000000..c5ea004 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/warehouse/forms/basic-info/basic-info-form.component.ts @@ -0,0 +1,284 @@ +import { Component, Input, ViewChild, OnInit } from '@angular/core'; +import { + AbstractControl, + FormBuilder, + FormControl, + FormGroup, + Validators, +} from '@angular/forms'; +import { IWarehouseCreateObject } from '@modules/server.common/interfaces/IWarehouse'; +import { first } from 'rxjs/operators'; +import { CarrierRouter } from '@modules/client.common.angular2/routers/carrier-router.service'; +import { IMultiSelectOption } from 'angular-2-dropdown-multiselect'; +import { pick } from 'underscore'; +import { FormHelpers } from '../../../forms/helpers'; +import { getDummyImage } from '@modules/server.common/utils'; +import { TranslateService } from '@ngx-translate/core'; + +export type WarehouseBasicInfo = Pick< + IWarehouseCreateObject, + | 'name' + | 'logo' + | 'isActive' + | 'username' + | 'hasRestrictedCarriers' + | 'carriersIds' + | 'useOnlyRestrictedCarriersForDelivery' + | 'preferRestrictedCarriersForDelivery' + | 'ordersShortProcess' + | 'orderCancelation' +>; + +@Component({ + selector: 'ea-warehouse-basic-info-form', + styleUrls: ['basic-info-form.component.scss'], + templateUrl: 'basic-info-form.component.html', +}) +export class BasicInfoFormComponent implements OnInit { + @ViewChild('fileInput', { static: true }) + fileInput: any; + + @Input() + readonly form: FormGroup; + @Input() + readonly password?: AbstractControl; + + uploaderPlaceholder: string; + + carriersOptions: IMultiSelectOption[]; + + private _delivery: 'all' | 'onlyStore' | 'preferStore' = 'all'; + + static buildForm(formBuilder: FormBuilder): FormGroup { + // would be used in the parent component and injected into this.form + return formBuilder.group({ + name: [ + '', + [ + Validators.required, + Validators.minLength(3), + Validators.maxLength(255), + ], + ], + logo: [ + '', + [ + (control: AbstractControl) => { + const isEmpty = control.value === ''; + if (!isEmpty) { + if ( + !control.value.startsWith('http') || + control.value.match( + /s?:?(\/\/[^"']*\.(?:png|jpg|jpeg|gif|png|svg))/ + ) === null + ) { + return { validUrl: true }; + } + } + return null; + }, + ], + ], + isActive: [true, [Validators.required]], + username: ['', [Validators.required]], + + hasRestrictedCarriers: [false, [Validators.required]], + useOnlyRestrictedCarriersForDelivery: [false], + preferRestrictedCarriersForDelivery: [false], + ordersShortProcess: [false], + carriersIds: [[]], + }); + } + + static buildPasswordForm(formBuilder: FormBuilder): AbstractControl { + return new FormControl('', [Validators.required]); + } + + getValue(): WarehouseBasicInfo { + const basicInfo = this.form.getRawValue() as { + name: string; + logo: string; + isActive: boolean; + username: string; + + hasRestrictedCarriers: boolean; + carriersIds: string[]; + useOnlyRestrictedCarriersForDelivery: boolean; + preferRestrictedCarriersForDelivery: boolean; + ordersShortProcess: boolean; + }; + + if (!basicInfo.logo) { + const letter = basicInfo.name.charAt(0).toUpperCase(); + basicInfo.logo = getDummyImage(300, 300, letter); + } + + return { + isActive: basicInfo.isActive, + name: basicInfo.name, + username: basicInfo.username, + logo: basicInfo.logo, + ...(basicInfo.hasRestrictedCarriers + ? { + hasRestrictedCarriers: basicInfo.hasRestrictedCarriers, + carriersIds: basicInfo.carriersIds, + } + : {}), + ...(basicInfo.hasRestrictedCarriers && + basicInfo.carriersIds && + basicInfo.carriersIds.length + ? { + useOnlyRestrictedCarriersForDelivery: + basicInfo.useOnlyRestrictedCarriersForDelivery, + preferRestrictedCarriersForDelivery: + basicInfo.preferRestrictedCarriersForDelivery, + } + : { + useOnlyRestrictedCarriersForDelivery: false, + preferRestrictedCarriersForDelivery: false, + }), + ordersShortProcess: basicInfo.ordersShortProcess, + orderCancelation: { enabled: false, onState: 0 }, + }; + } + + setValue(basicInfo: T) { + FormHelpers.deepMark(this.form, 'dirty'); + + basicInfo = Object.assign( + { + useOnlyRestrictedCarriersForDelivery: false, + preferRestrictedCarriersForDelivery: false, + ordersShortProcess: false, + }, + basicInfo + ); + + this.form.setValue( + pick(basicInfo, [ + ...Object.keys(this.getValue()), + 'hasRestrictedCarriers', + 'carriersIds', + 'useOnlyRestrictedCarriersForDelivery', + 'preferRestrictedCarriersForDelivery', + ]) + ); + + const onlyStore = basicInfo.useOnlyRestrictedCarriersForDelivery; + const preferStore = basicInfo.preferRestrictedCarriersForDelivery; + + if (onlyStore) { + this.delivery = 'onlyStore'; + } else if (preferStore) { + this.delivery = 'preferStore'; + } else { + this.delivery = 'all'; + } + } + + getPassword(): string { + // password is not part of warehouse + if (!this.password) { + throw new Error("Form doesn't contain password"); + } + return this.password.value as string; + } + + setPassword(value: string) { + this.password.setValue(value); + } + + constructor( + private readonly carrierRouter: CarrierRouter, + private readonly translateService: TranslateService + ) {} + + get name() { + return this.form.get('name'); + } + + get logo() { + return this.form.get('logo'); + } + + get isActive() { + return this.form.get('isActive'); + } + + get username() { + return this.form.get('username'); + } + + get hasRestrictedCarriers() { + return this.form.get('hasRestrictedCarriers'); + } + + get carriersIds() { + return this.form.get('carriersIds'); + } + + get useOnlyRestrictedCarriersForDelivery() { + return this.form.get('useOnlyRestrictedCarriersForDelivery'); + } + + get preferRestrictedCarriersForDelivery() { + return this.form.get('preferRestrictedCarriersForDelivery'); + } + + get delivery() { + return this._delivery; + } + + set delivery(value) { + this._delivery = value; + this.useOnlyRestrictedCarriersForDelivery.setValue(false); + this.preferRestrictedCarriersForDelivery.setValue(false); + + switch (value) { + case 'onlyStore': + this.useOnlyRestrictedCarriersForDelivery.setValue(true); + break; + case 'preferStore': + this.preferRestrictedCarriersForDelivery.setValue(true); + break; + } + } + + get showLogoMeta() { + return this.logo && this.logo.value !== ''; + } + + ngOnInit(): void { + this.loadCarriersOptions(); + this.getUploaderPlaceholderText(); + } + + deleteImg() { + this.logo.setValue(''); + } + + private async getUploaderPlaceholderText() { + const res = await this.translateService + .get(['WAREHOUSE_VIEW.MUTATION.PHOTO', 'OPTIONAL']) + .pipe(first()) + .toPromise(); + + this.uploaderPlaceholder = `${res['WAREHOUSE_VIEW.MUTATION.PHOTO']} (${res['OPTIONAL']})`; + } + + private async loadCarriersOptions() { + let carriers = await this.carrierRouter + .getAllActive() + .pipe(first()) + .toPromise(); + + carriers = carriers.filter((c) => c.isSharedCarrier); + + this.carriersOptions = carriers.map((c) => { + return { + id: c.id, + name: `${c.firstName} ${c.lastName}`, + }; + }); + } +} diff --git a/packages/admin-web-angular/src/app/@shared/warehouse/forms/basic-info/index.ts b/packages/admin-web-angular/src/app/@shared/warehouse/forms/basic-info/index.ts new file mode 100644 index 0000000..91afc05 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/warehouse/forms/basic-info/index.ts @@ -0,0 +1 @@ +export * from './basic-info-form.component'; diff --git a/packages/admin-web-angular/src/app/@shared/warehouse/forms/contact-info/contact-info-form.component.html b/packages/admin-web-angular/src/app/@shared/warehouse/forms/contact-info/contact-info-form.component.html new file mode 100644 index 0000000..d9f1c25 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/warehouse/forms/contact-info/contact-info-form.component.html @@ -0,0 +1,155 @@ +
+
+
+
+ + + + +
+
+ {{ + 'WAREHOUSE_VIEW.MUTATION.ERRORS.INVALID_EMAIL' + | translate + }} +
+
+
+
+ + + + +
+
+ + {{ + 'WAREHOUSE_VIEW.MUTATION.ERRORS.PHONE_CONTAINS_ONLY_DIGIT' + | translate + }} +
+
+
+
+ + +
+
+ + {{ + 'WAREHOUSE_VIEW.MUTATION.CONTACT_INFO_TAB.ORDER_FORWARDING_EMAIL' + | translate + }} + + +
+
+ {{ + 'WAREHOUSE_VIEW.MUTATION.ERRORS.INVALID_EMAIL' + | translate + }} +
+
+ {{ + 'WAREHOUSE_VIEW.MUTATION.ERRORS.ORDERS_EMAIL_IS_REQUIRED' + | translate + }} +
+
+
+
+ + {{ + 'WAREHOUSE_VIEW.MUTATION.CONTACT_INFO_TAB.ORDER_FORWARDING_PHONE' + | translate + }} + + + +
+
+ + {{ + 'WAREHOUSE_VIEW.MUTATION.ERRORS.PHONE_CONTAINS_ONLY_DIGIT' + | translate + }} +
+
+ {{ + 'WAREHOUSE_VIEW.MUTATION.ERRORS.ORDERS_PHONE_IS_REQUIRED' + | translate + }} +
+
+
+
+
+
diff --git a/packages/admin-web-angular/src/app/@shared/warehouse/forms/contact-info/contact-info-form.component.ts b/packages/admin-web-angular/src/app/@shared/warehouse/forms/contact-info/contact-info-form.component.ts new file mode 100644 index 0000000..1930858 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/warehouse/forms/contact-info/contact-info-form.component.ts @@ -0,0 +1,174 @@ +import { Component, Input } from '@angular/core'; +import { FormBuilder, FormGroup, Validators } from '@angular/forms'; +import ForwardOrdersMethod from '@modules/server.common/enums/ForwardOrdersMethod'; +import { IWarehouseCreateObject } from '@modules/server.common/interfaces/IWarehouse'; +import { FormHelpers } from '../../../forms/helpers'; + +export type WarehouseContactInfo = Pick< + IWarehouseCreateObject, + | 'contactEmail' + | 'contactPhone' + | 'forwardOrdersUsing' + | 'ordersEmail' + | 'ordersPhone' +>; + +const phoneNumberRegex = /^[+]*[(]{0,1}[0-9]{1,4}[)]{0,1}[-\s\./0-9x]*$/; + +@Component({ + selector: 'ea-warehouse-contact-info-form', + templateUrl: 'contact-info-form.component.html', +}) +export class ContactInfoFormComponent { + @Input() + readonly form: FormGroup; + + forwardingEmail: boolean; + forwardingPhone: boolean; + + static buildForm(formBuilder: FormBuilder): FormGroup { + // would be used in the parent component and injected into this.form + + return formBuilder.group({ + contactPhone: ['', [Validators.pattern(phoneNumberRegex)]], + contactEmail: ['', [Validators.email]], + + forwardOrdersUsing: [], + + ordersPhone: ['', [Validators.pattern(phoneNumberRegex)]], + ordersEmail: ['', [Validators.email]], + }); + } + + getValue() { + const contactInfo = this.form.getRawValue() as { + contactEmail: string; + contactPhone: string; + forwardOrdersUsing: ForwardOrdersMethod[]; + ordersEmail: string; + ordersPhone: string; + }; + + return { + ...(contactInfo.contactEmail + ? { contactEmail: contactInfo.contactEmail } + : { contactEmail: null }), + ...(contactInfo.contactPhone + ? { contactPhone: contactInfo.contactPhone } + : { contactPhone: null }), + forwardOrdersUsing: this.getForwardOrdersUsing(), + ...(contactInfo.ordersEmail + ? { ordersEmail: contactInfo.ordersEmail } + : { ordersEmail: null }), + ...(contactInfo.ordersPhone + ? { ordersPhone: contactInfo.ordersPhone } + : { ordersPhone: null }), + }; + } + + setValue(contactInfo: T) { + FormHelpers.deepMark(this.form, 'dirty'); + + this.form.setValue({ + contactEmail: contactInfo.contactEmail + ? contactInfo.contactEmail + : '', + contactPhone: contactInfo.contactPhone + ? contactInfo.contactPhone + : '', + forwardOrdersUsing: contactInfo.forwardOrdersUsing, + ordersEmail: contactInfo.ordersEmail ? contactInfo.ordersEmail : '', + ordersPhone: contactInfo.ordersPhone ? contactInfo.ordersPhone : '', + }); + + if (contactInfo.forwardOrdersUsing) { + this.forwardingEmail = contactInfo.forwardOrdersUsing.includes( + ForwardOrdersMethod.Email + ); + this.forwardingPhone = contactInfo.forwardOrdersUsing.includes( + ForwardOrdersMethod.Phone + ); + } + } + + get contactEmail() { + return this.form.get('contactEmail'); + } + + get contactPhone() { + return this.form.get('contactPhone'); + } + + get forwardOrdersUsing() { + return this.form.get('forwardOrdersUsing'); + } + + get ordersEmail() { + return this.form.get('ordersEmail'); + } + + get ordersPhone() { + return this.form.get('ordersPhone'); + } + + get validForm() { + return ( + this.form && + this.form.valid && + (this.forwardingEmail ? this.ordersEmail.value !== '' : true) && + (this.forwardingPhone ? this.ordersPhone.value !== '' : true) + ); + } + + forwardingPhoneChange() { + this.forwardingPhone = !this.forwardingPhone; + let forwardOrdersUsingArr = this.forwardOrdersUsing.value || []; + + forwardOrdersUsingArr = forwardOrdersUsingArr.filter( + (v) => v !== ForwardOrdersMethod.Phone + ); + + if (this.forwardingPhone) { + forwardOrdersUsingArr.push(ForwardOrdersMethod.Phone); + } else { + this.ordersPhone.setValue(''); + } + + this.forwardOrdersUsing.setValue(forwardOrdersUsingArr); + } + + forwardingEmailChange() { + this.forwardingEmail = !this.forwardingEmail; + let forwardOrdersUsingArr = this.forwardOrdersUsing.value || []; + + forwardOrdersUsingArr = forwardOrdersUsingArr.filter( + (v) => v !== ForwardOrdersMethod.Email + ); + + if (this.forwardingEmail) { + forwardOrdersUsingArr.push(ForwardOrdersMethod.Email); + } else { + this.ordersEmail.setValue(''); + } + + this.forwardOrdersUsing.setValue(forwardOrdersUsingArr); + } + + private getForwardOrdersUsing() { + const forwardOrdersUsing = []; + + if (this.forwardingEmail) { + forwardOrdersUsing.push(ForwardOrdersMethod.Email); + } + + if (this.forwardingPhone) { + forwardOrdersUsing.push(ForwardOrdersMethod.Phone); + } + + if (!this.forwardingEmail && !this.forwardingPhone) { + forwardOrdersUsing.push(ForwardOrdersMethod.Unselected); + } + + return forwardOrdersUsing; + } +} diff --git a/packages/admin-web-angular/src/app/@shared/warehouse/forms/contact-info/index.ts b/packages/admin-web-angular/src/app/@shared/warehouse/forms/contact-info/index.ts new file mode 100644 index 0000000..a7007c7 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/warehouse/forms/contact-info/index.ts @@ -0,0 +1 @@ +export * from './contact-info-form.component'; diff --git a/packages/admin-web-angular/src/app/@shared/warehouse/forms/index.ts b/packages/admin-web-angular/src/app/@shared/warehouse/forms/index.ts new file mode 100644 index 0000000..40b139d --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/warehouse/forms/index.ts @@ -0,0 +1,3 @@ +export * from './basic-info'; +export * from './contact-info'; +export * from './warehouse-forms.module'; diff --git a/packages/admin-web-angular/src/app/@shared/warehouse/forms/payments-settings/payments-settings-form.component.html b/packages/admin-web-angular/src/app/@shared/warehouse/forms/payments-settings/payments-settings-form.component.html new file mode 100644 index 0000000..3de3035 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/warehouse/forms/payments-settings/payments-settings-form.component.html @@ -0,0 +1,30 @@ +
+
+
+ + {{ + 'FAKE_DATA.SETUP_MERCHANTS.PAYMENTS.ALLOW_ONLINE_PAYMENT' + | translate + }} + +
+
+ + {{ + 'FAKE_DATA.SETUP_MERCHANTS.PAYMENTS.ALLOW_CASH_PAYMENT' + | translate + }} + +
+
+
+
+ +
+
+
diff --git a/packages/admin-web-angular/src/app/@shared/warehouse/forms/payments-settings/payments-settings-form.component.ts b/packages/admin-web-angular/src/app/@shared/warehouse/forms/payments-settings/payments-settings-form.component.ts new file mode 100644 index 0000000..c521fc2 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/warehouse/forms/payments-settings/payments-settings-form.component.ts @@ -0,0 +1,41 @@ +import { Component, ViewChild, Input } from '@angular/core'; +import { PaymentGatewaysComponent } from '@app/@shared/payment-gateways/payment-gateways.component'; +import IPaymentGatewayCreateObject from '@modules/server.common/interfaces/IPaymentGateway'; +import Warehouse from '@modules/server.common/entities/Warehouse'; + +@Component({ + selector: 'ea-warehouse-payments-settings-form', + templateUrl: './payments-settings-form.component.html', +}) +export class PaymentsSettingsFormComponent { + @ViewChild('paymentGateways', { static: true }) + paymentGateways: PaymentGatewaysComponent; + + @Input() + warehouseLogo: string; + @Input() + warehouseCountry: string; + @Input() + isEdit: boolean; + + isPaymentEnabled: boolean = false; + isCashPaymentEnabled: boolean = true; + + get isPaymentValid() { + return !this.isPaymentEnabled || this.paymentGateways.isValid; + } + + get isCashPaymentValid() { + return !this.isCashPaymentEnabled || this.paymentGateways.isValid; + } + + get paymentsGateways(): IPaymentGatewayCreateObject[] { + return this.paymentGateways.paymentsGateways; + } + + setValue(merchant: Warehouse) { + this.isPaymentEnabled = merchant.isPaymentEnabled; + this.isCashPaymentEnabled = merchant.isCashPaymentEnabled; + this.paymentGateways.setValue(merchant); + } +} diff --git a/packages/admin-web-angular/src/app/@shared/warehouse/forms/warehouse-forms.module.ts b/packages/admin-web-angular/src/app/@shared/warehouse/forms/warehouse-forms.module.ts new file mode 100644 index 0000000..5295cfc --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/warehouse/forms/warehouse-forms.module.ts @@ -0,0 +1,50 @@ +import { NgModule } from '@angular/core'; +import { TranslateModule } from '@ngx-translate/core'; +import { FormWizardModule } from '@ever-co/angular2-wizard'; +import { ThemeModule } from '../../../@theme/theme.module'; +import { BasicInfoFormComponent } from './basic-info'; +import { ContactInfoFormComponent } from './contact-info'; +import { MultiselectDropdownModule } from 'angular-2-dropdown-multiselect'; +import { WarehouseManageTabsComponent } from './warehouse-manage-tabs/warehouse-manage-tabs.component'; +import { GoogleMapModule } from '../../forms/google-map/google-map.module'; +import { LocationFormModule } from '../../forms/location'; +import { WarehouseManageTabsDetailsComponent } from './warehouse-manage-tabs/details/warehouse-manage-tabs-details.component'; +import { WarehouseManageTabsAccountComponent } from './warehouse-manage-tabs/account/warehouse-manage-tabs-account.component'; +import { WarehouseManageTabsDeliveryAreasComponent } from './warehouse-manage-tabs/delivery-areas/warehouse-manage-tabs-delivery-areas.component'; +import { FileUploaderModule } from '@app/@shared/file-uploader/file-uploader.module'; +import { NbButtonModule } from '@nebular/theme'; +import { PaymentsSettingsFormComponent } from './payments-settings/payments-settings-form.component'; +import { PaymentGatewaysModule } from '@app/@shared/payment-gateways/payment-gateways.module'; + +@NgModule({ + imports: [ + ThemeModule, + FormWizardModule, + TranslateModule.forChild(), + MultiselectDropdownModule, + FileUploaderModule, + GoogleMapModule, + LocationFormModule, + NbButtonModule, + PaymentGatewaysModule, + ], + exports: [ + WarehouseManageTabsComponent, + WarehouseManageTabsDetailsComponent, + WarehouseManageTabsAccountComponent, + BasicInfoFormComponent, + ContactInfoFormComponent, + WarehouseManageTabsDeliveryAreasComponent, + PaymentsSettingsFormComponent, + ], + declarations: [ + WarehouseManageTabsComponent, + WarehouseManageTabsDetailsComponent, + WarehouseManageTabsAccountComponent, + BasicInfoFormComponent, + ContactInfoFormComponent, + WarehouseManageTabsDeliveryAreasComponent, + PaymentsSettingsFormComponent, + ], +}) +export class WarehouseFormsModule {} diff --git a/packages/admin-web-angular/src/app/@shared/warehouse/forms/warehouse-manage-tabs/account/warehouse-manage-tabs-account.component.html b/packages/admin-web-angular/src/app/@shared/warehouse/forms/warehouse-manage-tabs/account/warehouse-manage-tabs-account.component.html new file mode 100644 index 0000000..79bbeff --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/warehouse/forms/warehouse-manage-tabs/account/warehouse-manage-tabs-account.component.html @@ -0,0 +1,100 @@ +
+
+
+ + +
+ + +
+
+ {{ + 'WAREHOUSE_VIEW.MUTATION.ERRORS.USERNAME_IS_REQUIRED' + | translate + }}! +
+
+
+
+ +
+ + +
+ +
+
+ +
+ + +
+ +
+
+ +
+ + +
+ + +
+
+ {{ + 'WAREHOUSE_VIEW.MUTATION.PASSWORDS_DO_NOT_MATCH' + | translate + }}! +
+
+
+
+
+
diff --git a/packages/admin-web-angular/src/app/@shared/warehouse/forms/warehouse-manage-tabs/account/warehouse-manage-tabs-account.component.scss b/packages/admin-web-angular/src/app/@shared/warehouse/forms/warehouse-manage-tabs/account/warehouse-manage-tabs-account.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/packages/admin-web-angular/src/app/@shared/warehouse/forms/warehouse-manage-tabs/account/warehouse-manage-tabs-account.component.ts b/packages/admin-web-angular/src/app/@shared/warehouse/forms/warehouse-manage-tabs/account/warehouse-manage-tabs-account.component.ts new file mode 100644 index 0000000..6e74a60 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/warehouse/forms/warehouse-manage-tabs/account/warehouse-manage-tabs-account.component.ts @@ -0,0 +1,88 @@ +import { Component, Input, OnInit, OnDestroy } from '@angular/core'; +import { + FormGroup, + FormBuilder, + Validators, + AbstractControl, +} from '@angular/forms'; + +@Component({ + selector: 'ea-warehouse-manage-tabs-account', + styleUrls: ['./warehouse-manage-tabs-account.component.scss'], + templateUrl: './warehouse-manage-tabs-account.component.html', +}) +export class WarehouseManageTabsAccountComponent implements OnInit, OnDestroy { + static password: AbstractControl; + + static initialize(passwordControl: AbstractControl) { + this.password = passwordControl; + } + + static clean() { + WarehouseManageTabsAccountComponent.password = null; + } + + static buildForm(formBuilder: FormBuilder) { + return formBuilder.group({ + username: ['', [Validators.required]], + password: formBuilder.group({ + current: [''], + new: [''], + confirm: [ + '', + [ + (control: AbstractControl) => { + if (this.password) { + return control.value.length > 0 && + control.value !== this.password.value + ? { notMatch: true } + : null; + } + }, + ], + ], + }), + }); + } + + @Input() + readonly form: FormGroup; + + get username() { + return this.form.get('username'); + } + get password() { + return this.form.get('password'); + } + get passwordNew() { + return this.password.get('new'); + } + get passwordConfirm() { + return this.password.get('confirm'); + } + + ngOnInit() { + WarehouseManageTabsAccountComponent.initialize(this.passwordNew); + } + + ngOnDestroy() { + WarehouseManageTabsAccountComponent.clean(); + } + + getValue() { + const accountInfo = this.form.getRawValue() as { + username: string; + password: { + current: string; + new: string; + confirm: string; + }; + }; + + return accountInfo; + } + + setValue(username: string) { + this.username.setValue(username); + } +} diff --git a/packages/admin-web-angular/src/app/@shared/warehouse/forms/warehouse-manage-tabs/delivery-areas/warehouse-manage-tabs-delivery-areas.component.html b/packages/admin-web-angular/src/app/@shared/warehouse/forms/warehouse-manage-tabs/delivery-areas/warehouse-manage-tabs-delivery-areas.component.html new file mode 100644 index 0000000..e532933 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/warehouse/forms/warehouse-manage-tabs/delivery-areas/warehouse-manage-tabs-delivery-areas.component.html @@ -0,0 +1,216 @@ +
+
+
+
+
+
+
+
+ {{ + 'WAREHOUSE_VIEW.MUTATION.SELECT_SHAPE_TO_ADD_ZONE' + | translate + }} +
+ + +
+
+ + {{ + 'WAREHOUSE_VIEW.MUTATION.DRAW_SHAPE_ON_MAP' | translate + }} + +
+
+ + +
+ +
+
+ +
+ + +
+ +
+
+ +
+ + +
+ +
+
+ +
+
+ + + +
+ +
+ + +
+
+
+
+
+
+
    +
  • +
    + + {{ zone.properties.name }} + +
    + + +
    +
    +
  • +
+
+
+
+
+
+ Maximum Delivery Radius (meters): + + +
+
+ Show in map +
+
diff --git a/packages/admin-web-angular/src/app/@shared/warehouse/forms/warehouse-manage-tabs/delivery-areas/warehouse-manage-tabs-delivery-areas.component.scss b/packages/admin-web-angular/src/app/@shared/warehouse/forms/warehouse-manage-tabs/delivery-areas/warehouse-manage-tabs-delivery-areas.component.scss new file mode 100644 index 0000000..5ef294e --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/warehouse/forms/warehouse-manage-tabs/delivery-areas/warehouse-manage-tabs-delivery-areas.component.scss @@ -0,0 +1,116 @@ +.delivery-container { + display: flex; + + .map-container { + width: 70%; + height: 60vh; + margin-left: 14px; + + .g-map { + height: 100%; + width: 100%; + } + } + + .zones { + height: 100%; + width: 30%; + + .btn-container { + display: flex; + justify-content: space-evenly; + width: 60px; + } + + .zones-form { + .centered { + align-items: center; + flex-direction: column; + } + + span { + margin-bottom: 8px; + font-family: Exo; + font-weight: bold; + display: inline-block; + width: 100%; + text-align: center; + font-size: 1.1em; + } + } + + .zones-list { + color: white; + + ul { + display: flex; + flex-direction: column; + align-items: center; + padding-left: 0 !important; + + li { + display: flex; + justify-content: space-between; + width: 80%; + align-items: center; + background-color: #47d26f; + border-radius: 5px; + padding: 6px; + margin-bottom: 5px; + padding-left: 10px; + border: 2px solid transparent; + + div { + display: flex; + width: 100%; + justify-content: space-between; + align-items: center; + height: 100%; + + span { + font-size: 1.2em; + font-family: Exo; + user-select: none; + } + + div { + width: 35px; + + i { + font-size: 1em; + cursor: pointer; + } + } + } + } + + li:hover { + opacity: 0.9; + border: 2px solid mediumseagreen; + } + + i:hover { + color: dimgrey; + } + } + } + } +} + +.max-distance { + margin-bottom: 1rem; + display: flex; + width: 70%; + align-items: center; + justify-content: center; + padding: 1rem; + .number-input { + margin-right: 2rem; + display: flex; + align-items: center; + input { + margin-left: 1rem; + max-width: 8rem; + } + } +} diff --git a/packages/admin-web-angular/src/app/@shared/warehouse/forms/warehouse-manage-tabs/delivery-areas/warehouse-manage-tabs-delivery-areas.component.ts b/packages/admin-web-angular/src/app/@shared/warehouse/forms/warehouse-manage-tabs/delivery-areas/warehouse-manage-tabs-delivery-areas.component.ts new file mode 100644 index 0000000..b5660ff --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/warehouse/forms/warehouse-manage-tabs/delivery-areas/warehouse-manage-tabs-delivery-areas.component.ts @@ -0,0 +1,475 @@ +import { Component, OnInit, ViewChild, Input, OnDestroy } from '@angular/core'; +import { Observable, Subject } from 'rxjs'; +import { takeUntil, first } from 'rxjs/operators'; +import { FormGroup, FormBuilder } from '@angular/forms'; + +@Component({ + selector: 'ea-warehouse-manage-tabs-delivery-areas', + styleUrls: ['./warehouse-manage-tabs-delivery-areas.component.scss'], + templateUrl: './warehouse-manage-tabs-delivery-areas.component.html', +}) +export class WarehouseManageTabsDeliveryAreasComponent + implements OnInit, OnDestroy { + @ViewChild('gmap', { static: true }) + mapElement: any; + + @Input() + mapCoordEvent: Observable; + + @Input() + form: FormGroup; + + deliverForm: FormGroup; + selectedShapeType: string; + + // containing all shape objects with all properties + zonesObjects: any[] = []; + + // Used to disable Add button, form and show the text to draw + shapeReady: boolean = false; + + // Used to know when we're editing a zone + isEditing: boolean = false; + + private _zoneNumber = 0; + + private _map: google.maps.Map; + private _drawingManager: google.maps.drawing.DrawingManager; + private _polyOptions: any = { + strokeWeight: 0.5, + fillOpacity: 0.45, + editable: true, + fillColor: '#1E90FF', + }; + private _selectedZone: any; + private _mapMarker: google.maps.Marker; + + private _ngDestroy$ = new Subject(); + + private _storeLocation; + private _maxDistanceZone; + + constructor(private fb: FormBuilder) {} + + ngOnInit() { + this._setupGoogleMap(); + this._listenForMapCoordinates(); + this._initiliazeForm(); + } + + static buildForm(formBuilder: FormBuilder): FormGroup { + // would be used in the parent component and injected into this.form + return formBuilder.group({ + deliveryAreas: '', + maxDistance: 0, + showOnMap: false, + }); + } + + get maxDistance() { + return this.form.get('maxDistance').value; + } + set maxDistance(distance) { + this.form.get('maxDistance').setValue(distance); + } + get deliveryZones() { + return this.form.get('deliveryAreas').value; + } + + set deliveryZones(zonesData) { + this.form.get('deliveryAreas').setValue(zonesData); + } + + getValue(): any { + // add type + const geoJSON = { + type: 'FeatureCollection', + features: [], + maxDistance: this.maxDistance, + }; + + const features = []; + + this.zonesObjects.forEach((zoneObject) => { + if (zoneObject.type === 'circle') { + const tempObj = {}; + + const coordsArr = zoneObject + .getCenter() + .toUrlValue(7) + .split(','); + + tempObj['properties'] = zoneObject.properties; + tempObj['properties']['radius'] = zoneObject.radius; + tempObj['type'] = 'Feature'; + tempObj['geometry'] = { + type: 'Point', + coordinates: [+coordsArr[1], +coordsArr[0]], + }; + + features.push(tempObj); + } else { + const tempObj = {}; + + const coordinates = [[]]; + + zoneObject.getPath().forEach((point) => { + const mappedCoordinates = point + .toUrlValue(7) + .split(',') + .map((p) => +p); + + coordinates[0].push(mappedCoordinates); + }); + + tempObj['properties'] = zoneObject.properties; + tempObj['type'] = 'Feature'; + tempObj['geometry'] = { + type: 'Polygon', + coordinates, + }; + + features.push(tempObj); + } + }); + + geoJSON.features = features; + + return geoJSON; + } + + setValue(data) { + if (data && data.maxDistance) { + this.maxDistance = data.maxDistance; + this.mapCoordEvent.pipe(first()).subscribe((location) => { + if (location && this.form.get('showOnMap').value) { + this._maxDistanceZone = this.createMaxDistanceCircle( + location, + data.maxDistance + ); + } + }); + } + // add type + if (data && data.features.length > 0) { + data.features.forEach((feature) => { + if (feature.geometry.type === 'Point') { + // Point = Circle + + const circle = new google.maps.Circle({ + center: { + // Point coodinates are reversed: lng => lat + lng: +feature.geometry.coordinates[0], + lat: +feature.geometry.coordinates[1], + }, + radius: feature.properties['radius'], + strokeWeight: 0.5, + fillOpacity: 0.45, + fillColor: '#1E90FF', + map: this._map, + }); + + circle['type'] = 'circle'; + + circle['properties'] = feature.properties; + + this._addZoneEventListeners(circle); + + this.zonesObjects.push(circle); + } else if (feature.geometry.type === 'Polygon') { + const polygon = new google.maps.Polygon({ + strokeWeight: 0.5, + fillOpacity: 0.45, + fillColor: '#1E90FF', + paths: this._mapCoordinates( + feature.geometry.coordinates + ), + }); + + polygon['properties'] = feature.properties; + + polygon['type'] = 'polygon'; + + polygon.setMap(this._map); + + this._addZoneEventListeners(polygon); + + this.zonesObjects.push(polygon); + } + }); + + this._zoneNumber = data.features.length; + this.deliveryZones = data; + } + } + + addZone() { + if (this.shapeReady && this.deliverForm.status === 'VALID') { + if ( + this._selectedZone.type === + google.maps.drawing.OverlayType.POLYGON + ) { + const coordinates = [[]]; + + this._selectedZone.getPath().forEach((point) => { + const mappedCoordinates = point + .toUrlValue(7) + .split(',') + .map((p) => +p); + + coordinates[0].push(mappedCoordinates); + }); + + this._selectedZone.properties = { + name: this.deliverForm.get('name').value, + minimumAmount: this.deliverForm.get('amount').value, + fee: this.deliverForm.get('fee').value, + }; + + this.zonesObjects.push(this._selectedZone); + } else { + /* Since GeoJSON doesn't support circle shape types, we will add it as a point and later use that as the center + of the circle + We will save the radius as a property and will have our full circle */ + + this._selectedZone.properties = { + name: this.deliverForm.get('name').value, + minimumAmount: this.deliverForm.get('amount').value, + fee: this.deliverForm.get('fee').value, + }; + + this.zonesObjects.push(this._selectedZone); + } + + this._clearSelection(); + this._zoneNumber++; + this.selectedShapeType = null; + this._selectedZone = null; + this.shapeReady = false; + + this.deliverForm + .get('name') + .setValue('Zone ' + this._zoneNumber || 0); + this.deliverForm.get('fee').setValue(''); + this.deliverForm.get('amount').setValue(''); + } + } + + startDrawing() { + this._clearSelection(); + + this._drawingManager = new google.maps.drawing.DrawingManager({ + drawingMode: this._getShape(), + drawingControl: false, + circleOptions: this._polyOptions, + polygonOptions: this._polyOptions, + map: this._map, + }); + + google.maps.event.addListener( + this._drawingManager, + 'overlaycomplete', + (zoneObject) => { + // Switch back to non-drawing mode after drawing a shape. + this._drawingManager.setDrawingMode(null); + // Set state to ready so the Add button is enabled + this.shapeReady = true; + + // Add an event listener that selects the newly-drawn shape when the user + // mouses down on it. + const newZone = zoneObject.overlay; + newZone.type = zoneObject.type; + + this._addZoneEventListeners(newZone); + + this.setSelection(newZone); + } + ); + + google.maps.event.addListener( + this._drawingManager, + 'drawingmode_changed', + this._clearSelection + ); + } + + cancelAdd() { + this.deleteSelectedShape(); + this.deliverForm.get('fee').setValue(''); + this.deliverForm.get('amount').setValue(''); + this.deliverForm.get('name').setValue('Zone ' + this._zoneNumber); + } + + closeEdit() { + this.isEditing = false; + this._clearSelection(); + this.deliverForm.get('fee').setValue(''); + this.deliverForm.get('amount').setValue(''); + this.deliverForm.get('name').setValue('Zone ' + this._zoneNumber); + } + + deleteSelectedShape() { + if (this._selectedZone) { + this._selectedZone.setMap(null); + this.shapeReady = false; + this.selectedShapeType = null; + } + } + + setSelection(zone) { + this._clearSelection(); + this._selectedZone = zone; + zone.setEditable(true); + this.highlightZone(zone); + + if (!this.isEditing && !this.selectedShapeType) { + this._populateForm(zone); + } + } + + editZone() { + this._selectedZone.properties = { + name: this.deliverForm.get('name').value, + fee: this.deliverForm.get('fee').value, + minimumAmount: this.deliverForm.get('amount').value, + }; + + this.closeEdit(); + } + + highlightZone(zone) { + zone.set('fillColor', '#FF8C00'); + } + + removeHighlight(zone) { + zone.set('fillColor', '#1E90FF'); + } + + deleteZone(zone) { + zone.setMap(null); + const index = this.zonesObjects.indexOf(zone); + this.zonesObjects.splice(index, 1); + this._zoneNumber = this.zonesObjects.length; + } + + private _mapCoordinates(coordinates) { + const tempArr = []; + + coordinates[0].forEach((c) => { + tempArr.push({ + lat: c[0], + lng: c[1], + }); + }); + + return tempArr; + } + + private _addZoneEventListeners(zone) { + google.maps.event.addListener(zone, 'click', () => { + if (!this.selectedShapeType) { + // if selectedShapeType we're adding a zone, so we shoudn't be able to select other zone + this.setSelection(zone); + } + }); + + google.maps.event.addListener(zone, 'mouseover', () => { + this.highlightZone(zone); + }); + + google.maps.event.addListener(zone, 'mouseout', () => { + this.removeHighlight(zone); + }); + } + + private _initiliazeForm() { + this.deliverForm = this.fb.group({ + name: ['Zone ' + this._zoneNumber || 0], + amount: '', + fee: '', + }); + } + + private _getShape() { + if (this.selectedShapeType === 'circle') { + return google.maps.drawing.OverlayType.CIRCLE; + } else if (this.selectedShapeType === 'shape') { + return google.maps.drawing.OverlayType.POLYGON; + } + } + + private _populateForm(zone) { + this.isEditing = true; + this.deliverForm.get('name').setValue(zone.properties.name); + this.deliverForm.get('fee').setValue(zone.properties.fee); + this.deliverForm.get('amount').setValue(zone.properties.minimumAmount); + } + + private _clearSelection() { + if (this._selectedZone) { + this.isEditing = false; + this._selectedZone.set('fillColor', '#1E90FF'); + this._selectedZone.setEditable(false); + this._selectedZone = null; + } + } + + private _listenForMapCoordinates() { + this.mapCoordEvent + .pipe(takeUntil(this._ngDestroy$)) + .subscribe((location) => { + this._map.setCenter(location); + + this._mapMarker = new google.maps.Marker({ + map: this._map, + position: location, + }); + this._storeLocation = location; + }); + } + + private _setupGoogleMap() { + const optionsMap = { + center: new google.maps.LatLng(0, 0), + zoom: 13, + mapTypeId: google.maps.MapTypeId.ROADMAP, + disableDefaultUI: true, + }; + + this._map = new google.maps.Map( + this.mapElement.nativeElement, + optionsMap + ); + + google.maps.event.addListener(this._map, 'click', this._clearSelection); + } + + onMaxDistanceChange() { + if (this._maxDistanceZone) { + this.deleteZone(this._maxDistanceZone); + } + if (this._storeLocation && this.form.get('showOnMap').value) { + this._maxDistanceZone = this.createMaxDistanceCircle( + this._storeLocation, + this.maxDistance + ); + } + } + + createMaxDistanceCircle(center, maxDistance) { + const zone = new google.maps.Circle({ + center: center, + radius: maxDistance, + strokeWeight: 0.9, + fillOpacity: 0.05, + fillColor: '#000000', + map: this._map, + }); + return zone; + } + + ngOnDestroy() { + this._ngDestroy$.next(); + this._ngDestroy$.complete(); + } +} diff --git a/packages/admin-web-angular/src/app/@shared/warehouse/forms/warehouse-manage-tabs/details/warehouse-manage-tabs-details.component.html b/packages/admin-web-angular/src/app/@shared/warehouse/forms/warehouse-manage-tabs/details/warehouse-manage-tabs-details.component.html new file mode 100644 index 0000000..a308ae2 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/warehouse/forms/warehouse-manage-tabs/details/warehouse-manage-tabs-details.component.html @@ -0,0 +1,258 @@ +
+
+
+ + +
+ + +
+
+ {{ + 'WAREHOUSE_VIEW.MUTATION.ERRORS.NAME_IS_REQUIRED' + | translate + }}! +
+
+ {{ + 'WAREHOUSE_VIEW.MUTATION.ERRORS.NAME_ATLEAST_3_CHARS' + | translate + }}! +
+
+ {{ + 'WAREHOUSE_VIEW.MUTATION.ERRORS.NAME_MORE_THAN_30_CHARS' + | translate + }}! +
+
+
+
+ +
+ + +
+ + +
+ {{ + 'WAREHOUSE_VIEW.MUTATION.ERRORS.INVALID_URL' + | translate + }}! +
+
+
+ +
+ +
+
+
+ Invalid image +
+
+
+
+
+
+
+ +
+
+
+ {{ + 'WAREHOUSE_VIEW.MUTATION.IS_ACTIVE' | translate + }} +
+
+
+ +
+
+
+ + {{ + 'WAREHOUSE_VIEW.MUTATION.ORDERS_SHORT_PROCESS' + | translate + }} + +
+
+
+ +
+
+
+ {{ + 'WAREHOUSE_VIEW.MUTATION.PRODUCTS_MANUFACTURING' + | translate + }} +
+
+
+ +
+
+
+ {{ + 'WAREHOUSE_VIEW.MUTATION.CARRIER_REQUIRED' + | translate + }} +
+
+
+ +
+
+
+ {{ + 'WAREHOUSE_VIEW.MUTATION.UNALLOWED_ORDER_CANCELATION' + | translate + }} +
+
+
+ +
+
+
+
+
+ + {{ + 'WAREHOUSE_VIEW.MUTATION.IN_STORE_MODE' | translate + }} + +
+
+
+ +
+
+
+ + {{ + 'WAREHOUSE_VIEW.MUTATION.CARRIER_WORK_COMPETITION' + | translate + }} + +
+
+
+ +
+ + +
+
+ {{ + 'WAREHOUSE_VIEW.MUTATION.USE_ONLY_SPECIFIC_CARRIERS' + | translate + }} +
+ +
+ + +
+
+
+ + +
+ + +
+ + + Use All Carriers + + + Use Only Store Carriers + + + Prefer Store Carriers + + +
+
+
+
diff --git a/packages/admin-web-angular/src/app/@shared/warehouse/forms/warehouse-manage-tabs/details/warehouse-manage-tabs-details.component.scss b/packages/admin-web-angular/src/app/@shared/warehouse/forms/warehouse-manage-tabs/details/warehouse-manage-tabs-details.component.scss new file mode 100644 index 0000000..9f14100 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/warehouse/forms/warehouse-manage-tabs/details/warehouse-manage-tabs-details.component.scss @@ -0,0 +1,20 @@ +::ng-deep .carriers-dropdown { + .dropdown { + width: 100%; + } +} + +.preview-img { + padding-left: 14px; + padding-right: 16px; +} + +.img-rounded { + // width: 70px; + max-height: 70px; +} + +.removeIcon { + padding-left: 4px; + padding-right: 4px; +} diff --git a/packages/admin-web-angular/src/app/@shared/warehouse/forms/warehouse-manage-tabs/details/warehouse-manage-tabs-details.component.ts b/packages/admin-web-angular/src/app/@shared/warehouse/forms/warehouse-manage-tabs/details/warehouse-manage-tabs-details.component.ts new file mode 100644 index 0000000..702b0c3 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/warehouse/forms/warehouse-manage-tabs/details/warehouse-manage-tabs-details.component.ts @@ -0,0 +1,346 @@ +import { + Component, + Input, + ViewChild, + ElementRef, + AfterViewInit, + OnInit, +} from '@angular/core'; +import { + AbstractControl, + FormBuilder, + FormGroup, + Validators, +} from '@angular/forms'; +import { IWarehouseCreateObject } from '@modules/server.common/interfaces/IWarehouse'; +import { first } from 'rxjs/operators'; +import { CarrierRouter } from '@modules/client.common.angular2/routers/carrier-router.service'; +import { IMultiSelectOption } from 'angular-2-dropdown-multiselect'; +import { FormHelpers } from '../../../../forms/helpers'; +import * as _ from 'lodash'; +import * as isUrl from 'is-url'; +import { TranslateService } from '@ngx-translate/core'; + +export type WarehouseManageTabsDetails = Pick< + IWarehouseCreateObject, + | 'name' + | 'logo' + | 'isActive' + | 'hasRestrictedCarriers' + | 'carriersIds' + | 'isManufacturing' + | 'isCarrierRequired' + | 'useOnlyRestrictedCarriersForDelivery' + | 'preferRestrictedCarriersForDelivery' + | 'ordersShortProcess' + | 'orderCancelation' + | 'inStoreMode' + | 'carrierCompetition' +>; + +@Component({ + selector: 'ea-warehouse-manage-tabs-details', + styleUrls: ['./warehouse-manage-tabs-details.component.scss'], + templateUrl: './warehouse-manage-tabs-details.component.html', +}) +export class WarehouseManageTabsDetailsComponent + implements OnInit, AfterViewInit { + @ViewChild('fileInput') + fileInput: ElementRef; + + @ViewChild('logoPreview') + logoPreviewElement: ElementRef; + + @Input() + readonly form: FormGroup; + + uploaderPlaceholder: string; + + carriersOptions: IMultiSelectOption[]; + + private _delivery: 'all' | 'onlyStore' | 'preferStore' = 'all'; + + // orderCancelationOptions can be moved to a separate file + orderCancelationOptions = [ + { text: 'ORDERING', value: 1 }, + { text: 'START_PROCESSING', value: 2 }, + { text: 'START_ALLOCATION', value: 3 }, + { text: 'ALLOCATED', value: 4 }, + { text: 'START_PACKAGING', value: 5 }, + { text: 'PACKAGED', value: 6 }, + { text: 'CARRIER_TAKE_WORK', value: 7 }, + { text: 'CARRIER_GOT_IT', value: 8 }, + { text: 'CARRIER_START_DELIVERY', value: 9 }, + { text: 'DELIVERED', value: 11 }, + ]; + + constructor( + private readonly _carrierRouter: CarrierRouter, + private readonly _translateService: TranslateService + ) {} + + get inStoreMode() { + return this.form.get('inStoreMode'); + } + + get name() { + return this.form.get('name'); + } + + get logo() { + return this.form.get('logo'); + } + + get isActive() { + return this.form.get('isActive'); + } + + get hasRestrictedCarriers() { + return this.form.get('hasRestrictedCarriers'); + } + + get carriersIds() { + return this.form.get('carriersIds'); + } + + get showLogoMeta() { + return this.logo && this.logo.value !== ''; + } + + get isManufacturing() { + return this.form.get('isManufacturing'); + } + + get isCarrierRequired() { + return this.form.get('isCarrierRequired'); + } + + get useOnlyRestrictedCarriersForDelivery() { + return this.form.get('useOnlyRestrictedCarriersForDelivery'); + } + + get preferRestrictedCarriersForDelivery() { + return this.form.get('preferRestrictedCarriersForDelivery'); + } + get ordersShortProcess() { + return this.form.get('ordersShortProcess'); + } + + get enabledOrderCancelation() { + return this.form.get('enabledOrderCancelation'); + } + + get delivery() { + return this._delivery; + } + + set delivery(value) { + this._delivery = value; + this.useOnlyRestrictedCarriersForDelivery.setValue(false); + this.preferRestrictedCarriersForDelivery.setValue(false); + + switch (value) { + case 'onlyStore': + this.useOnlyRestrictedCarriersForDelivery.setValue(true); + break; + case 'preferStore': + this.preferRestrictedCarriersForDelivery.setValue(true); + break; + } + } + + static buildForm(formBuilder: FormBuilder): FormGroup { + // would be used in the parent component and injected into this.form + return formBuilder.group({ + name: [ + '', + [ + Validators.required, + Validators.minLength(3), + Validators.maxLength(255), + ], + ], + logo: [ + '', + [ + (control: AbstractControl) => { + const imageUrl = control.value; + + if (!isUrl(imageUrl) && !_.isEmpty(imageUrl)) { + return { invalidUrl: true }; + } + + return null; + }, + ], + ], + isActive: [true, [Validators.required]], + isManufacturing: [true, [Validators.required]], + isCarrierRequired: [true, [Validators.required]], + hasRestrictedCarriers: [false, [Validators.required]], + useOnlyRestrictedCarriersForDelivery: [false], + preferRestrictedCarriersForDelivery: [false], + carriersIds: [[]], + ordersShortProcess: [false], + inStoreMode: [false], + carrierCompetition: [false], + enabledOrderCancelation: [false], + stateOrderCancelation: [0], + }); + } + + ngOnInit(): void { + this.getUploaderPlaceholderText(); + this.loadCarriersOptions(); + } + + ngAfterViewInit() { + this._setupLogoUrlValidation(); + } + + getValue(): WarehouseManageTabsDetails { + const basicInfo = this.form.getRawValue() as { + name: string; + logo: string; + isActive: boolean; + isManufacturing: boolean; + isCarrierRequired: boolean; + hasRestrictedCarriers: boolean; + carriersIds: string[]; + useOnlyRestrictedCarriersForDelivery: boolean; + preferRestrictedCarriersForDelivery: boolean; + ordersShortProcess: boolean; + inStoreMode: boolean; + carrierCompetition: boolean; + enabledOrderCancelation: boolean; + stateOrderCancelation: number; + }; + + return { + ordersShortProcess: basicInfo.ordersShortProcess, + isActive: basicInfo.isActive, + inStoreMode: basicInfo.inStoreMode, + carrierCompetition: basicInfo.carrierCompetition, + isManufacturing: basicInfo.isManufacturing, + isCarrierRequired: basicInfo.isCarrierRequired, + name: basicInfo.name, + logo: basicInfo.logo, + ...(basicInfo.hasRestrictedCarriers + ? { + hasRestrictedCarriers: basicInfo.hasRestrictedCarriers, + carriersIds: basicInfo.carriersIds, + } + : { + hasRestrictedCarriers: false, + carriersIds: basicInfo.carriersIds, + }), + ...(basicInfo.hasRestrictedCarriers && + basicInfo.carriersIds && + basicInfo.carriersIds.length + ? { + useOnlyRestrictedCarriersForDelivery: + basicInfo.useOnlyRestrictedCarriersForDelivery, + preferRestrictedCarriersForDelivery: + basicInfo.preferRestrictedCarriersForDelivery, + } + : { + useOnlyRestrictedCarriersForDelivery: false, + preferRestrictedCarriersForDelivery: false, + }), + orderCancelation: { + enabled: basicInfo.enabledOrderCancelation, + onState: Number(basicInfo.stateOrderCancelation), + }, + }; + } + + setValue(basicInfo: T) { + FormHelpers.deepMark(this.form, 'dirty'); + + basicInfo = Object.assign( + { + useOnlyRestrictedCarriersForDelivery: false, + preferRestrictedCarriersForDelivery: false, + ordersShortProcess: false, + inStoreMode: false, + carrierCompetition: false, + enabledOrderCancelation: basicInfo.orderCancelation + ? basicInfo.orderCancelation.enabled + : false, + stateOrderCancelation: basicInfo.orderCancelation + ? basicInfo.orderCancelation.onState + : 0, + }, + basicInfo + ); + + //Remove orderCancelation from the list becouse its not actually form control + //can be improved + const filteredValues = Object.keys(this.getValue()); + _.remove(filteredValues, (e) => e === 'orderCancelation'); + + this.form.setValue( + _.pick(basicInfo, [ + ...filteredValues, + 'hasRestrictedCarriers', + 'carriersIds', + 'useOnlyRestrictedCarriersForDelivery', + 'preferRestrictedCarriersForDelivery', + 'enabledOrderCancelation', + 'stateOrderCancelation', + ]) + ); + + const onlyStore = basicInfo.useOnlyRestrictedCarriersForDelivery; + const preferStore = basicInfo.preferRestrictedCarriersForDelivery; + + if (onlyStore) { + this.delivery = 'onlyStore'; + } else if (preferStore) { + this.delivery = 'preferStore'; + } else { + this.delivery = 'all'; + } + } + + deleteImg() { + this.logo.setValue(''); + } + + private _setupLogoUrlValidation() { + this.logoPreviewElement.nativeElement.onload = () => { + this.logo.setErrors(null); + }; + + this.logoPreviewElement.nativeElement.onerror = () => { + if (this.showLogoMeta) { + this.logo.setErrors({ invalidUrl: true }); + } + }; + } + + private async getUploaderPlaceholderText() { + const res = await this._translateService + .get(['WAREHOUSE_VIEW.MUTATION.PHOTO', 'OPTIONAL']) + .pipe(first()) + .toPromise(); + + this.uploaderPlaceholder = `${res['WAREHOUSE_VIEW.MUTATION.PHOTO']} (${res['OPTIONAL']})`; + } + + private async loadCarriersOptions() { + let carriers = await this._carrierRouter + .getAllActive() + .pipe(first()) + .toPromise(); + + carriers = carriers.filter((c) => c.isSharedCarrier); + + this.carriersOptions = carriers.map((c) => { + return { + id: c.id, + name: `${c.firstName} ${c.lastName}`, + }; + }); + } +} diff --git a/packages/admin-web-angular/src/app/@shared/warehouse/forms/warehouse-manage-tabs/warehouse-manage-tabs.component.html b/packages/admin-web-angular/src/app/@shared/warehouse/forms/warehouse-manage-tabs/warehouse-manage-tabs.component.html new file mode 100644 index 0000000..4bf4166 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/warehouse/forms/warehouse-manage-tabs/warehouse-manage-tabs.component.html @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + + + +
+ + +
+ + + + + + + + + +
diff --git a/packages/admin-web-angular/src/app/@shared/warehouse/forms/warehouse-manage-tabs/warehouse-manage-tabs.component.scss b/packages/admin-web-angular/src/app/@shared/warehouse/forms/warehouse-manage-tabs/warehouse-manage-tabs.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/packages/admin-web-angular/src/app/@shared/warehouse/forms/warehouse-manage-tabs/warehouse-manage-tabs.component.ts b/packages/admin-web-angular/src/app/@shared/warehouse/forms/warehouse-manage-tabs/warehouse-manage-tabs.component.ts new file mode 100644 index 0000000..9ae89a2 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/warehouse/forms/warehouse-manage-tabs/warehouse-manage-tabs.component.ts @@ -0,0 +1,168 @@ +import { Input, Component, ViewChild, EventEmitter } from '@angular/core'; +import { FormGroup, FormBuilder } from '@angular/forms'; +import { IWarehouseCreateObject } from '@modules/server.common/interfaces/IWarehouse'; +import { WarehouseManageTabsDetailsComponent } from './details/warehouse-manage-tabs-details.component'; +import { WarehouseManageTabsAccountComponent } from './account/warehouse-manage-tabs-account.component'; +import { ContactInfoFormComponent } from '../contact-info'; +import Warehouse from '@modules/server.common/entities/Warehouse'; +import ForwardOrdersMethod from '@modules/server.common/enums/ForwardOrdersMethod'; +import { LocationFormComponent } from '../../../forms/location'; +import IGeoLocation from '@modules/server.common/interfaces/IGeoLocation'; +import { WarehouseManageTabsDeliveryAreasComponent } from './delivery-areas/warehouse-manage-tabs-delivery-areas.component'; +import { PaymentsSettingsFormComponent } from '../payments-settings/payments-settings-form.component'; + +export type WarehouseManageTabs = Pick< + IWarehouseCreateObject, + | 'name' + | 'logo' + | 'isActive' + | 'username' + | 'hasRestrictedCarriers' + | 'carriersIds' + | 'isManufacturing' + | 'isCarrierRequired' +>; + +@Component({ + selector: 'ea-warehouse-manage-tabs', + styleUrls: ['./warehouse-manage-tabs.component.scss'], + templateUrl: './warehouse-manage-tabs.component.html', +}) +export class WarehouseManageTabsComponent { + static buildForm(formBuilder: FormBuilder): FormGroup { + return formBuilder.group({ + details: WarehouseManageTabsDetailsComponent.buildForm(formBuilder), + account: WarehouseManageTabsAccountComponent.buildForm(formBuilder), + contactInfo: ContactInfoFormComponent.buildForm(formBuilder), + location: LocationFormComponent.buildForm(formBuilder), + deliverAreas: WarehouseManageTabsDeliveryAreasComponent.buildForm( + formBuilder + ), + }); + } + + @Input() + readonly form: FormGroup; + + @ViewChild('detailsComponent') + readonly detailsComponent: WarehouseManageTabsDetailsComponent; + + @ViewChild('accountComponent') + readonly accountComponent: WarehouseManageTabsAccountComponent; + + @ViewChild('contactInfoForm') + readonly contactInfoForm: ContactInfoFormComponent; + + @ViewChild('locationForm') + readonly locationForm: LocationFormComponent; + + @ViewChild('paymentsSettingsForm') + readonly paymentsSettingsForm: PaymentsSettingsFormComponent; + + @ViewChild('deliveryAreasForm') + readonly deliveryAreasForm: WarehouseManageTabsDeliveryAreasComponent; + + @ViewChild('tabSet') + readonly tabSet; + + mapCoordEmitter = new EventEmitter(); + mapGeometryEmitter = new EventEmitter(); + + get details() { + return this.form.get('details'); + } + + get account() { + return this.form.get('account'); + } + + get contactInfo() { + return this.form.get('contactInfo'); + } + + get location() { + return this.form.get('location'); + } + + get validForm() { + return this.form.valid && this.paymentsSettingsForm.isPaymentValid; + } + + get deliveryAreas() { + return this.form.get('deliverAreas'); + } + + onCoordinatesChanges(coords: number[]) { + this.mapCoordEmitter.emit(coords); + } + + onGeometrySend(geometry: any) { + this.mapGeometryEmitter.emit(geometry); + } + + getValue() { + // GeoJSON use reversed order for coordinates from our implementation. + // we use lat => lng but GeoJSON use lng => lat. + const geoLocationInput = this.locationForm.getValue(); + geoLocationInput.loc.coordinates.reverse(); + + const detailsRaw = this.detailsComponent.getValue(); + const accountRaw = this.accountComponent.getValue(); + const contactRaw = this.contactInfoForm.getValue(); + const locationRaw = geoLocationInput; + const deliveryAreasRaw = this.deliveryAreasForm.getValue(); + + const inputResult: { + basicInfo: WarehouseManageTabs; + password: { + current: string; + new: string; + }; + contactInfo: { + contactEmail: string; + contactPhone: string; + forwardOrdersUsing: ForwardOrdersMethod[]; + ordersEmail: string; + ordersPhone: string; + }; + location: IGeoLocation; + deliveryAreas: any; // add type + isPaymentEnabled: boolean; + paymentsGateways: object[]; + isCashPaymentEnabled: boolean; + } = { + basicInfo: { ...detailsRaw, username: accountRaw.username }, + password: accountRaw.password, + contactInfo: contactRaw, + location: locationRaw as IGeoLocation, + deliveryAreas: deliveryAreasRaw, + isPaymentEnabled: this.paymentsSettingsForm.isPaymentEnabled, + paymentsGateways: this.paymentsSettingsForm.paymentsGateways, + isCashPaymentEnabled: this.paymentsSettingsForm + .isCashPaymentEnabled, + }; + return inputResult; + } + + setValue(warehouse: Warehouse) { + // GeoJSON use reversed order of lat => lng + const geoLocationInput = warehouse.geoLocation; + geoLocationInput.loc.coordinates.reverse(); + + this.detailsComponent.setValue(warehouse); + this.accountComponent.setValue(warehouse.username); + this.contactInfoForm.setValue(warehouse); + this.locationForm.setValue(geoLocationInput); + this.deliveryAreasForm.setValue(warehouse.deliveryAreas); + this.paymentsSettingsForm.setValue(warehouse); + } + + warehouseUpdateFinish() { + this.tabSet.tabs._results[0].activeValue = true; + this.tabSet.tabs._results[1].activeValue = false; + this.tabSet.tabs._results[2].activeValue = false; + this.tabSet.tabs._results[3].activeValue = false; + this.tabSet.tabs._results[4].activeValue = false; + this.tabSet.tabs._results[5].activeValue = false; + } +} diff --git a/packages/admin-web-angular/src/app/@shared/warehouse/select-warehouse.component/select-warehouse.component.ts b/packages/admin-web-angular/src/app/@shared/warehouse/select-warehouse.component/select-warehouse.component.ts new file mode 100644 index 0000000..bedc46d --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/warehouse/select-warehouse.component/select-warehouse.component.ts @@ -0,0 +1,33 @@ +import { Router } from '@angular/router'; +import { Component, OnInit, Input } from '@angular/core'; +import { ViewCell } from 'ng2-smart-table'; + +@Component({ + template: ` + + `, +}) +export class SelectWarehouseComponent implements ViewCell, OnInit { + @Input() + public value: string | number; + + @Input() + public rowData: any; + + public renderValue: string; + + constructor(private readonly _router: Router) {} + + ngOnInit() { + this.renderValue = this.value.toString(); + } + + public selectWarehouse(warehouseId: string) { + this._router.navigate(['/stores/' + warehouseId]); + } +} diff --git a/packages/admin-web-angular/src/app/@shared/warehouse/warehouse-mutation/index.ts b/packages/admin-web-angular/src/app/@shared/warehouse/warehouse-mutation/index.ts new file mode 100644 index 0000000..32dabe3 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/warehouse/warehouse-mutation/index.ts @@ -0,0 +1,2 @@ +export * from './warehouse-mutation.component'; +export * from './warehouse-mutation.module'; diff --git a/packages/admin-web-angular/src/app/@shared/warehouse/warehouse-mutation/warehouse-mutation.component.html b/packages/admin-web-angular/src/app/@shared/warehouse/warehouse-mutation/warehouse-mutation.component.html new file mode 100644 index 0000000..1769a3c --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/warehouse/warehouse-mutation/warehouse-mutation.component.html @@ -0,0 +1,82 @@ + + + + +
+ +
+ + +
+ +
+ + +
+ + + + +
+ + +
+
+ + + +
+ + +
+
diff --git a/packages/admin-web-angular/src/app/@shared/warehouse/warehouse-mutation/warehouse-mutation.component.scss b/packages/admin-web-angular/src/app/@shared/warehouse/warehouse-mutation/warehouse-mutation.component.scss new file mode 100644 index 0000000..bf0a4dc --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/warehouse/warehouse-mutation/warehouse-mutation.component.scss @@ -0,0 +1,8 @@ +.ng-valid[required], +.ng-valid.required { + border-left: 5px solid #42a948; +} + +:host ::ng-deep .card-footer button { + cursor: pointer !important; +} diff --git a/packages/admin-web-angular/src/app/@shared/warehouse/warehouse-mutation/warehouse-mutation.component.ts b/packages/admin-web-angular/src/app/@shared/warehouse/warehouse-mutation/warehouse-mutation.component.ts new file mode 100644 index 0000000..d03c4c5 --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/warehouse/warehouse-mutation/warehouse-mutation.component.ts @@ -0,0 +1,129 @@ +import { + Component, + ViewChild, + EventEmitter, + AfterViewInit, +} from '@angular/core'; +import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; +import { WarehouseRouter } from '@modules/client.common.angular2/routers/warehouse-router.service'; +import { ToasterService } from 'angular2-toaster'; +import { FormBuilder, FormControl, FormGroup } from '@angular/forms'; +import { BasicInfoFormComponent, ContactInfoFormComponent } from '../forms'; +import { LocationFormComponent } from '../../forms/location'; +import { PaymentsSettingsFormComponent } from '../forms/payments-settings/payments-settings-form.component'; + +@Component({ + selector: 'ea-warehouse-mutation', + templateUrl: './warehouse-mutation.component.html', + styleUrls: ['./warehouse-mutation.component.scss'], +}) +export class WarehouseMutationComponent implements AfterViewInit { + loading: boolean; + + @ViewChild('basicInfoForm') + basicInfoForm: BasicInfoFormComponent; + + @ViewChild('contactInfoForm', { static: true }) + contactInfoForm: ContactInfoFormComponent; + + @ViewChild('locationForm') + locationForm: LocationFormComponent; + + @ViewChild('paymentsSettingsForm') + paymentsSettingsForm: PaymentsSettingsFormComponent; + + mapCoordEmitter = new EventEmitter(); + mapGeometryEmitter = new EventEmitter(); + + readonly form: FormGroup = this.formBuilder.group({ + basicInfo: BasicInfoFormComponent.buildForm(this.formBuilder), + password: BasicInfoFormComponent.buildPasswordForm(this.formBuilder), + contactInfo: ContactInfoFormComponent.buildForm(this.formBuilder), + location: LocationFormComponent.buildForm(this.formBuilder), + }); + + readonly basicInfo = this.form.get('basicInfo') as FormControl; + readonly contactInfo = this.form.get('contactInfo') as FormControl; + readonly location = this.form.get('location') as FormControl; + readonly password = this.form.get('password') as FormControl; + + constructor( + private readonly activeModal: NgbActiveModal, + private readonly formBuilder: FormBuilder, + private readonly toasterService: ToasterService, + private readonly warehouseRouter: WarehouseRouter + ) {} + + get isValidContactInfo() { + return this.contactInfoForm.validForm !== undefined + ? this.contactInfoForm.validForm + : true; + } + + ngAfterViewInit() { + // This hack is need because the styles of 'ng-bootstrap' modal and google autocomplete api + // collide and autocomplete field just doesn't show without larger z-index. + setTimeout(() => { + const elementRef = document.querySelector( + 'body > div.pac-container.pac-logo' + ); + + if (elementRef) { + elementRef['style']['zIndex'] = 10000; + } + }, 2000); + + if (this.locationForm) { + this.locationForm.setDefaultCoords(); + } + } + + onCoordinatesChanges(coords: number[]) { + this.mapCoordEmitter.emit(coords); + } + + onGeometrySend(geometry: any) { + this.mapGeometryEmitter.emit(geometry); + } + + async createWarehouse() { + try { + // GeoJSON use reversed order for coordinates from our implementation. + // we use lat => lng but GeoJSON use lng => lat. + const geoLocationInput = this.locationForm.getValue(); + geoLocationInput.loc.coordinates.reverse(); + + this.loading = true; + const w = await this.warehouseRouter.register({ + warehouse: { + ...this.basicInfoForm.getValue(), + ...this.contactInfoForm.getValue(), + geoLocation: geoLocationInput, + isPaymentEnabled: this.paymentsSettingsForm + .isPaymentEnabled, + paymentGateways: this.paymentsSettingsForm.paymentsGateways, + isCashPaymentEnabled: this.paymentsSettingsForm + .isCashPaymentEnabled, + }, + password: this.basicInfoForm.getPassword(), + }); + this.loading = false; + this.toasterService.pop( + 'success', + `Warehouse ${w.name} was created!` + ); + + this.activeModal.close(); + } catch (err) { + this.loading = false; + this.toasterService.pop( + 'error', + `Error in creating warehouse: "${err.message}"` + ); + } + } + + cancel() { + this.activeModal.dismiss('canceled'); + } +} diff --git a/packages/admin-web-angular/src/app/@shared/warehouse/warehouse-mutation/warehouse-mutation.module.ts b/packages/admin-web-angular/src/app/@shared/warehouse/warehouse-mutation/warehouse-mutation.module.ts new file mode 100644 index 0000000..21b64ac --- /dev/null +++ b/packages/admin-web-angular/src/app/@shared/warehouse/warehouse-mutation/warehouse-mutation.module.ts @@ -0,0 +1,24 @@ +import { NgModule } from '@angular/core'; +import { WarehouseMutationComponent } from './warehouse-mutation.component'; +import { TranslateModule } from '@ngx-translate/core'; +import { FormWizardModule } from '@ever-co/angular2-wizard'; +import { ThemeModule } from '../../../@theme'; +import { WarehouseFormsModule } from '../forms'; +import { LocationFormModule } from '../../forms/location'; +import { GoogleMapModule } from '../../forms/google-map/google-map.module'; +import { NbSpinnerModule } from '@nebular/theme'; + +@NgModule({ + imports: [ + ThemeModule, + FormWizardModule, + TranslateModule.forChild(), + WarehouseFormsModule, + LocationFormModule, + GoogleMapModule, + NbSpinnerModule, + ], + exports: [WarehouseMutationComponent], + declarations: [WarehouseMutationComponent] +}) +export class WarehouseMutationModule {} diff --git a/packages/admin-web-angular/src/app/@theme/components/footer/footer.component.html b/packages/admin-web-angular/src/app/@theme/components/footer/footer.component.html new file mode 100644 index 0000000..bf03cc4 --- /dev/null +++ b/packages/admin-web-angular/src/app/@theme/components/footer/footer.component.html @@ -0,0 +1,30 @@ +{{ 'FOOTER_VIEW.COPY_RIGHT' | translate }}, + {{ + companyName + }}. {{ 'FOOTER_VIEW.ALL_RIGHTS_RESERVED' | translate }}. +
+ + + + +
diff --git a/packages/admin-web-angular/src/app/@theme/components/footer/footer.component.scss b/packages/admin-web-angular/src/app/@theme/components/footer/footer.component.scss new file mode 100644 index 0000000..fc196de --- /dev/null +++ b/packages/admin-web-angular/src/app/@theme/components/footer/footer.component.scss @@ -0,0 +1,30 @@ +@import '../../styles/themes'; +@import '~@nebular/theme/styles/global/breakpoints'; +@import '~bootstrap/scss/mixins/breakpoints'; + +@include nb-install-component() { + width: 100%; + display: flex; + justify-content: space-between; + align-items: center; + + .socials { + font-size: 2rem; + + a { + padding: 0.4rem; + color: #a4abb3; + transition: color ease-out 0.1s; + + &:hover { + color: #2a2a2a; + } + } + } + + @include media-breakpoint-down(is) { + .socials { + font-size: 1.5rem; + } + } +} diff --git a/packages/admin-web-angular/src/app/@theme/components/footer/footer.component.ts b/packages/admin-web-angular/src/app/@theme/components/footer/footer.component.ts new file mode 100644 index 0000000..a27628f --- /dev/null +++ b/packages/admin-web-angular/src/app/@theme/components/footer/footer.component.ts @@ -0,0 +1,25 @@ +import { Component } from '@angular/core'; +import { environment } from 'environments/environment'; + +@Component({ + selector: 'ngx-footer', + styleUrls: ['./footer.component.scss'], + templateUrl: './footer.component.html', +}) +export class FooterComponent { + companyName: string; + companySiteLink: string; + companyGithubLink: string; + companyFacebookLink: string; + companyTwitterLink: string; + companyLinkedinLink: string; + + constructor() { + this.companyName = environment.COMPANY_NAME; + this.companySiteLink = environment.COMPANY_SITE_LINK; + this.companyGithubLink = environment.COMPANY_GITHUB_LINK; + this.companyFacebookLink = environment.COMPANY_FACEBOOK_LINK; + this.companyTwitterLink = environment.COMPANY_TWITTER_LINK; + this.companyLinkedinLink = environment.COMPANY_LINKEDIN_LINK; + } +} diff --git a/packages/admin-web-angular/src/app/@theme/components/header/header.component.html b/packages/admin-web-angular/src/app/@theme/components/header/header.component.html new file mode 100644 index 0000000..86a1220 --- /dev/null +++ b/packages/admin-web-angular/src/app/@theme/components/header/header.component.html @@ -0,0 +1,44 @@ +
+
+ + + + + +
+
+ +
+ + + + + + + +
diff --git a/packages/admin-web-angular/src/app/@theme/components/header/header.component.scss b/packages/admin-web-angular/src/app/@theme/components/header/header.component.scss new file mode 100644 index 0000000..0d2c2ad --- /dev/null +++ b/packages/admin-web-angular/src/app/@theme/components/header/header.component.scss @@ -0,0 +1,230 @@ +@import '../../styles/themes'; +// @import '~bootstrap/scss/mixins/breakpoints'; +// @import '~@nebular/theme/styles/global/breakpoints'; + +@include nb-install-component() { + display: flex; + justify-content: space-between; + width: 100%; + + .left { + display: flex; + width: 100%; + order: 0; + flex-direction: row; + } + .right { + order: 1; + flex-direction: row-reverse; + } + + .logo-container { + display: flex; + align-items: center; + // width: calc(#{nb-theme(sidebar-width)} - #{nb-theme(header-padding)}); + } + + nb-action { + height: auto; + display: flex; + align-content: center; + } + + nb-user { + cursor: pointer; + } + + ::ng-deep nb-search button { + padding: 0 !important; + } + + .header-container { + display: flex; + align-items: center; + width: auto; + + .navigation { + @include nb-ltr(padding-right, 1.25rem); + @include nb-rtl(padding-left, 1.25rem); + font-size: 2.5rem; + text-decoration: none; + + nb-icon { + font-size: 2rem; + } + } + + .logo { + padding: 0 1.25rem; + font-size: 1.75rem; + @include nb-ltr(border-left, 1px solid nb-theme(divider-color)); + @include nb-rtl(border-right, 1px solid nb-theme(divider-color)); + white-space: nowrap; + + display: flex; + align-items: flex-start; + justify-content: center; + + .ever-simbol { + font-size: 1rem; + margin-right: 5px; + margin-left: 1px; + margin-top: 1px; + font-weight: bold; + } + } + } + + // @include nb-for-theme(corporate) { + // $menu-action-separator-color: #3f4550; + + // nb-action { + // @include nb-ltr(border-left-color, $menu-action-separator-color); + // @include nb-rtl(border-right-color, $menu-action-separator-color); + // } + + // .header-container .logo { + // @include nb-ltr(border, none); + // @include nb-rtl(border, none); + // } + + // // .header-container ::ng-deep ngx-theme-switcher .dropdown-toggle { + // // color: nb-theme(color-white); + // // background: transparent; + // // } + // } + + // ngx-layout-direction-switcher { + // margin: 0 1.5rem; + // } + + // ngx-theme-switcher { + // // margin: nb-theme(layout-padding); + // margin-top: 0; + // margin-bottom: 0; + // } + + // @include media-breakpoint-down(xl) { + // ngx-layout-direction-switcher { + // display: none; + // } + // } + + // .toggle-layout ::ng-deep a { + // display: block; + // text-decoration: none; + // line-height: 1; + + // i { + // // color: nb-theme(color-fg-highlight); + // font-size: 2.25rem; + // border-radius: 50%; + // position: relative; + // animation-name: pulse-light; + + // &::after { + // content: ' '; + // // hack to be able to set border-radius + // background-image: url('data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7'); + // border-radius: 50%; + // pointer-events: none; + + // position: absolute; + // top: 52.3%; + // left: 50%; + // transform: translate(-50%, -50%); + // width: 13%; + // height: 13%; + + // animation: 3s linear infinite pulse; + + // @include nb-for-theme(default) { + // animation-name: pulse-light; + // } + // } + // } + // } + + // TODO style + // @include keyframes(pulse) { + // 0% { + // box-shadow: 0 0 1px 0 rgba(nb-theme(color-fg-highlight), 0); + // } + // 20% { + // box-shadow: 0 0 3px 10px rgba(nb-theme(color-fg-highlight), 0.4); + // } + // 100% { + // box-shadow: 0 0 5px 20px rgba(nb-theme(color-fg-highlight), 0); + // } + // } + + // @include keyframes(pulse-light) { + // 0% { + // box-shadow: 0 0 1px 0 rgba(115, 255, 208, 0); + // } + // 20% { + // box-shadow: 0 0 3px 10px rgba(115, 255, 208, 0.4); + // } + // 100% { + // box-shadow: 0 0 5px 20px rgba(115, 255, 208, 0); + // } + // } + + // @include media-breakpoint-down(md) { + // nb-action:not(.toggle-layout) { + // border: none; + // } + + // .control-item { + // display: none; + // } + + // .toggle-layout { + // padding: 0; + // } + + // ngx-layout-direction-switcher { + // display: none; + // } + + // ngx-theme-switcher { + // margin: 0 0.5rem; + // } + // } + + // @include media-breakpoint-down(sm) { + // nb-user ::ng-deep .user-name { + // display: none; + // } + // } + + // @include media-breakpoint-down(is) { + // .header-container { + // .logo { + // font-size: 1.25rem; + // } + // } + + // .toggle-layout { + // display: none; + // } + + // ngx-theme-switcher { + // display: none; + // } + + // nb-action:not(.toggle-layout) { + // padding: 0; + // } + // } + + // @include media-breakpoint-down(xs) { + // .right ::ng-deep { + // display: none; + // } + // } + + .admin-menu { + cursor: pointer; + } +} diff --git a/packages/admin-web-angular/src/app/@theme/components/header/header.component.ts b/packages/admin-web-angular/src/app/@theme/components/header/header.component.ts new file mode 100644 index 0000000..b9cd1cb --- /dev/null +++ b/packages/admin-web-angular/src/app/@theme/components/header/header.component.ts @@ -0,0 +1,87 @@ +import { Component, Input, OnDestroy, OnInit } from '@angular/core'; +import { NbMenuService, NbSidebarService, NbMenuItem } from '@nebular/theme'; +import { Subject, Observable } from 'rxjs'; +import { AdminsService } from '../../../@core/data/admins.service'; +import { Store } from '../../../@core/data/store.service'; +import Admin from '@modules/server.common/entities/Admin'; +import { TranslateService } from '@ngx-translate/core'; + +@Component({ + selector: 'ngx-header', + styleUrls: ['./header.component.scss'], + templateUrl: './header.component.html', +}) +export class HeaderComponent implements OnInit, OnDestroy { + @Input() + position = 'normal'; + + admin$: Observable; + + adminMenu: NbMenuItem[]; + + private ngDestroy$ = new Subject(); + + constructor( + private sidebarService: NbSidebarService, + private menuService: NbMenuService, + private adminsService: AdminsService, + private store: Store, + private translateService: TranslateService + ) { + this.initialize(); + this._applyTranslationOnSmartTable(); + this.admin$ = this.adminsService.getAdmin(this.store.adminId); + } + + ngOnInit() {} + + initialize() { + this.adminMenu = [ + { + title: this.getTranslation('HEADER_VIEW.PROFILE'), + link: '/profile', + }, + { + title: this.getTranslation('HEADER_VIEW.LOG_OUT'), + link: '/auth/logout', + }, + ]; + } + + getTranslation(prefix: string) { + let result = ''; + this.translateService.get(prefix).subscribe((res) => { + result = res; + }); + return result; + } + + private _applyTranslationOnSmartTable() { + this.translateService.onLangChange.subscribe(() => { + this.initialize(); + }); + } + + toggleSidebar(): boolean { + this.sidebarService.toggle(true, 'menu-sidebar'); + return false; + } + + toggleSettings(): boolean { + this.sidebarService.toggle(false, 'settings-sidebar'); + return false; + } + + navigateHome() { + this.menuService.navigateHome(); + } + + startSearch() { + return false; + } + + ngOnDestroy() { + this.ngDestroy$.next(); + this.ngDestroy$.complete(); + } +} diff --git a/packages/admin-web-angular/src/app/@theme/components/index.ts b/packages/admin-web-angular/src/app/@theme/components/index.ts new file mode 100644 index 0000000..3cf247d --- /dev/null +++ b/packages/admin-web-angular/src/app/@theme/components/index.ts @@ -0,0 +1,11 @@ +export * from './footer/footer.component'; +export * from './search-input/search-input.component'; +export * from './tiny-mce/tiny-mce.component'; + +// TODO remvoe +export * from './header/header.component'; +export * from './theme-settings/theme-settings.component'; +export * from './theme-switcher/theme-switcher.component'; +export * from './switcher/switcher.component'; +export * from './layout-direction-switcher/layout-direction-switcher.component'; +export * from './theme-switcher/themes-switcher-list/themes-switcher-list.component'; diff --git a/packages/admin-web-angular/src/app/@theme/components/layout-direction-switcher/layout-direction-switcher.component.html b/packages/admin-web-angular/src/app/@theme/components/layout-direction-switcher/layout-direction-switcher.component.html new file mode 100644 index 0000000..f429209 --- /dev/null +++ b/packages/admin-web-angular/src/app/@theme/components/layout-direction-switcher/layout-direction-switcher.component.html @@ -0,0 +1,16 @@ +
+

Directions:

+ + + LTR + + RTL + + +
diff --git a/packages/admin-web-angular/src/app/@theme/components/layout-direction-switcher/layout-direction-switcher.component.ts b/packages/admin-web-angular/src/app/@theme/components/layout-direction-switcher/layout-direction-switcher.component.ts new file mode 100644 index 0000000..aed6ba8 --- /dev/null +++ b/packages/admin-web-angular/src/app/@theme/components/layout-direction-switcher/layout-direction-switcher.component.ts @@ -0,0 +1,34 @@ +import { Component, Input, OnDestroy } from '@angular/core'; +import { NbLayoutDirection, NbLayoutDirectionService } from '@nebular/theme'; +import { takeWhile } from 'rxjs/operators'; + +@Component({ + selector: 'ngx-layout-direction-switcher', + templateUrl: './layout-direction-switcher.component.html', +}) +export class LayoutDirectionSwitcherComponent implements OnDestroy { + directions = NbLayoutDirection; + currentDirection: NbLayoutDirection; + alive = true; + + @Input() vertical: boolean = false; + + constructor(private directionService: NbLayoutDirectionService) { + this.currentDirection = this.directionService.getDirection(); + + this.directionService + .onDirectionChange() + .pipe(takeWhile(() => this.alive)) + .subscribe( + (newDirection) => (this.currentDirection = newDirection) + ); + } + + toggleDirection(newDirection) { + this.directionService.setDirection(newDirection); + } + + ngOnDestroy() { + this.alive = false; + } +} diff --git a/packages/admin-web-angular/src/app/@theme/components/search-input/search-input.component.scss b/packages/admin-web-angular/src/app/@theme/components/search-input/search-input.component.scss new file mode 100644 index 0000000..8d9f016 --- /dev/null +++ b/packages/admin-web-angular/src/app/@theme/components/search-input/search-input.component.scss @@ -0,0 +1,33 @@ +:host { + display: flex; + align-items: center; + + i.control-icon { + &::before { + font-size: 2.3rem; + } + + &:hover { + cursor: pointer; + } + } + + input { + border: none; + outline: none; + margin-left: 1rem; + width: 15rem; + transition: width 0.2s ease; + + &.hidden { + width: 0; + margin: 0; + } + } + + ::ng-deep search-input { + input { + background: transparent; + } + } +} diff --git a/packages/admin-web-angular/src/app/@theme/components/search-input/search-input.component.ts b/packages/admin-web-angular/src/app/@theme/components/search-input/search-input.component.ts new file mode 100644 index 0000000..71bf951 --- /dev/null +++ b/packages/admin-web-angular/src/app/@theme/components/search-input/search-input.component.ts @@ -0,0 +1,42 @@ +import { + Component, + ElementRef, + EventEmitter, + Output, + ViewChild, +} from '@angular/core'; + +@Component({ + selector: 'ngx-search-input', + styleUrls: ['./search-input.component.scss'], + template: ` + + + `, +}) +export class SearchInputComponent { + @ViewChild('input', { static: true }) input: ElementRef; + + @Output() search: EventEmitter = new EventEmitter(); + + isInputShown = false; + + showInput() { + this.isInputShown = true; + this.input.nativeElement.focus(); + } + + hideInput() { + this.isInputShown = false; + } + + onInput(val: string) { + this.search.emit(val); + } +} diff --git a/packages/admin-web-angular/src/app/@theme/components/switcher/switcher.component.html b/packages/admin-web-angular/src/app/@theme/components/switcher/switcher.component.html new file mode 100644 index 0000000..4b984cf --- /dev/null +++ b/packages/admin-web-angular/src/app/@theme/components/switcher/switcher.component.html @@ -0,0 +1,18 @@ + diff --git a/packages/admin-web-angular/src/app/@theme/components/switcher/switcher.component.scss b/packages/admin-web-angular/src/app/@theme/components/switcher/switcher.component.scss new file mode 100644 index 0000000..7978def --- /dev/null +++ b/packages/admin-web-angular/src/app/@theme/components/switcher/switcher.component.scss @@ -0,0 +1,114 @@ +// TODO style +// @import '../../styles/themes'; +// @import '~@nebular/bootstrap/styles/hero-buttons'; +// @import '~bootstrap/scss/mixins/breakpoints'; +// @import '~@nebular/theme/styles/global/breakpoints'; + +// @include nb-install-component() { +// .switch-label { +// display: flex; +// justify-content: space-between; +// align-items: center; +// cursor: pointer; +// margin: 0; + +// &.vertical { +// flex-direction: column; +// align-items: flex-start; + +// .first, +// .second { +// padding: 0; +// } + +// .switch { +// margin-top: 0.5em; +// } +// } + +// & > span { +// font-size: 1.125rem; +// font-weight: 500; +// transition: opacity 0.3s ease; +// color: nb-theme(color-fg); + +// &.first { +// @include nb-ltr(padding-right, 10px); +// @include nb-rtl(padding-left, 10px); +// } + +// &.second { +// @include nb-ltr(padding-left, 10px); +// @include nb-rtl(padding-right, 10px); +// } + +// &.active { +// color: nb-theme(color-fg-text); +// } + +// @include nb-for-theme(cosmic) { +// color: nb-theme(color-fg); + +// &.active { +// color: nb-theme(color-white); +// } +// } + +// &:active { +// opacity: 0.78; +// } +// } +// } + +// .switch { +// position: relative; +// display: inline-block; +// width: 3rem; +// height: 1.5rem; +// margin: 0; + +// input { +// display: none; + +// &:checked + .slider::before { +// @include nb-ltr(transform, translateX(1.5rem)); +// @include nb-rtl(transform, translateX(-1.5rem)); +// } +// } + +// .slider { +// position: absolute; +// top: 0; +// left: 0; +// right: 0; +// bottom: 0; +// border-radius: 1.75rem; +// background-color: nb-theme(layout-bg); +// } + +// .slider::before { +// position: absolute; +// content: ''; +// height: 1.5rem; +// width: 1.5rem; +// border-radius: 50%; +// background-color: nb-theme(color-success); +// transition: 0.2s; + +// // TODO style +// // box-shadow: 0 0 0.25rem 0 rgba(nb-theme(color-fg), 0.4); + +// // @include nb-for-theme(cosmic) { +// // @include btn-hero-primary-gradient(); +// // } + +// @include nb-for-theme(corporate) { +// background-color: nb-theme(color-primary); +// } +// } +// } + +// @include media-breakpoint-down(xs) { +// align-items: flex-end; +// } +// } diff --git a/packages/admin-web-angular/src/app/@theme/components/switcher/switcher.component.ts b/packages/admin-web-angular/src/app/@theme/components/switcher/switcher.component.ts new file mode 100644 index 0000000..adeac94 --- /dev/null +++ b/packages/admin-web-angular/src/app/@theme/components/switcher/switcher.component.ts @@ -0,0 +1,46 @@ +import { Component, EventEmitter, Input, Output } from '@angular/core'; + +@Component({ + selector: 'ngx-switcher', + styleUrls: ['./switcher.component.scss'], + templateUrl: './switcher.component.html', +}) +export class SwitcherComponent { + @Input() + firstValue: any; + @Input() + secondValue: any; + + @Input() + firstValueLabel: string; + @Input() + secondValueLabel: string; + + @Input() + vertical: boolean; + + @Input() + value: any; + @Output() + valueChange = new EventEmitter(); + + isFirstValue() { + return this.value === this.firstValue; + } + + isSecondValue() { + return this.value === this.secondValue; + } + + currentValueLabel() { + return this.isFirstValue() + ? this.firstValueLabel + : this.secondValueLabel; + } + + changeValue() { + this.value = this.isFirstValue() ? this.secondValue : this.firstValue; + + this.valueChange.emit(this.value); + } +} diff --git a/packages/admin-web-angular/src/app/@theme/components/theme-settings/theme-settings.component.html b/packages/admin-web-angular/src/app/@theme/components/theme-settings/theme-settings.component.html new file mode 100644 index 0000000..f408b0a --- /dev/null +++ b/packages/admin-web-angular/src/app/@theme/components/theme-settings/theme-settings.component.html @@ -0,0 +1,61 @@ +
+
LAYOUTS
+
+ + + +
+
SIDEBAR
+
+ + + +
+ +
THEMES
+
+ + +
+ +
LANGUAGE
+
+ +
+ +
+ +
+
diff --git a/packages/admin-web-angular/src/app/@theme/components/theme-settings/theme-settings.component.scss b/packages/admin-web-angular/src/app/@theme/components/theme-settings/theme-settings.component.scss new file mode 100644 index 0000000..945a787 --- /dev/null +++ b/packages/admin-web-angular/src/app/@theme/components/theme-settings/theme-settings.component.scss @@ -0,0 +1,79 @@ +@import '../../styles/themes'; + +@include nb-install-component() { + h6 { + margin-bottom: 0.5rem; + font-weight: 500; + font-size: 1rem; + } + + .settings-row { + display: flex; + flex-direction: row; + align-items: center; + flex-wrap: wrap; + + width: 90%; + margin: 0 0 1rem; + + a { + text-decoration: none; + font-size: 2.25rem; + + color: #a4abb3; + + &.selected { + color: #40dc7e; + } + + @include nb-for-theme(cosmic) { + &.selected { + color: #3dcc6d; + } + } + } + } + + .settings { + margin-bottom: 1em; + } + + .switcher { + margin-bottom: 1rem; + + ::ng-deep ngx-switcher { + .switch-label span { + font-size: 1em; + font-weight: normal; + } + + .switch { + height: 1.5em; + width: 3em; + + .slider::before { + height: 1.5em; + width: 1.5em; + } + + input:checked + .slider::before { + @include nb-ltr(transform, translateX(1.5rem) !important); + @include nb-rtl(transform, translateX(-1.5rem) !important); + } + } + + @include nb-for-theme(cosmic) { + // .switch .slider { + // background-color: nb-theme(color-bg); + // } + } + } + } + + .settings-row select { + max-height: 40px; + padding: 0; + padding-left: 10px; + font-size: 0.9rem; + } +} diff --git a/packages/admin-web-angular/src/app/@theme/components/theme-settings/theme-settings.component.ts b/packages/admin-web-angular/src/app/@theme/components/theme-settings/theme-settings.component.ts new file mode 100644 index 0000000..be8ab55 --- /dev/null +++ b/packages/admin-web-angular/src/app/@theme/components/theme-settings/theme-settings.component.ts @@ -0,0 +1,134 @@ +import { Component } from '@angular/core'; +import { StateService } from '../../../@core/data/state.service'; +import { TranslateService } from '@ngx-translate/core'; +import { NbThemeService } from '@nebular/theme'; +import { environment } from 'environments/environment'; + +@Component({ + selector: 'ngx-theme-settings', + styleUrls: ['./theme-settings.component.scss'], + templateUrl: './theme-settings.component.html', +}) +export class ThemeSettingsComponent { + layouts = []; + sidebars = []; + + languages = [ + { + value: 'en-US', + name: 'English', + }, + { + value: 'bg-BG', + name: 'Bulgarian', + }, + { + value: 'he-IL', + name: 'Hebrew', + }, + { + value: 'ru-RU', + name: 'Russian', + }, + { + value: 'es-ES', + name: 'Spanish', + }, + ]; + + themes = [ + { + value: 'everlight', + name: 'Ever Light', + }, + { + value: 'everdark', + name: 'Ever Dark', + }, + { + value: 'default', + name: 'White', + }, + { + value: 'cosmic', + name: 'Cosmic', + }, + { + value: 'corporate', + name: 'Corporate', + }, + { + value: 'dark', + name: 'Dark', + }, + ]; + + currentTheme = 'everlight'; + defaultLanguage = ''; + + constructor( + protected stateService: StateService, + public translate: TranslateService, + private themeService: NbThemeService + ) { + this.defaultLanguage = environment['DEFAULT_LANGUAGE']; + + translate.addLangs(['en-US', 'bg-BG', 'he-IL', 'ru-RU', 'es-ES']); + translate.setDefaultLang('en-US'); + + const browserLang = translate.getBrowserLang(); + if (this.defaultLanguage) { + translate.use(this.defaultLanguage); + } else { + translate.use( + browserLang.match(/en-US|bg-BG|he-IL|ru-RU|es-ES/) + ? browserLang + : 'en-US' + ); + } + + this.stateService + .getLayoutStates() + .subscribe((layouts: any[]) => (this.layouts = layouts)); + + this.stateService + .getSidebarStates() + .subscribe((sidebars: any[]) => (this.sidebars = sidebars)); + } + + toggleTheme() { + this.themeService.changeTheme(this.currentTheme); + } + + switchLanguage(language: string) { + if (this.defaultLanguage === 'he-IL') { + this.stateService.setSidebarState(this.sidebars[1]); + } else { + this.stateService.setSidebarState(this.sidebars[0]); + } + + this.translate.use(this.defaultLanguage); + } + + layoutSelect(layout: any): boolean { + this.layouts = this.layouts.map((l: any) => { + l.selected = false; + return l; + }); + + layout.selected = true; + this.stateService.setLayoutState(layout); + return false; + } + + sidebarSelect(sidebars: any): boolean { + this.sidebars = this.sidebars.map((s: any) => { + s.selected = false; + return s; + }); + + sidebars.selected = true; + this.stateService.setSidebarState(sidebars); + return false; + } +} diff --git a/packages/admin-web-angular/src/app/@theme/components/theme-switcher/theme-switcher.component.html b/packages/admin-web-angular/src/app/@theme/components/theme-switcher/theme-switcher.component.html new file mode 100644 index 0000000..cf0f6ed --- /dev/null +++ b/packages/admin-web-angular/src/app/@theme/components/theme-switcher/theme-switcher.component.html @@ -0,0 +1,13 @@ +
+ + Dark + Light + White + Cosmic + Corporate +
diff --git a/packages/admin-web-angular/src/app/@theme/components/theme-switcher/theme-switcher.component.scss b/packages/admin-web-angular/src/app/@theme/components/theme-switcher/theme-switcher.component.scss new file mode 100644 index 0000000..e47262d --- /dev/null +++ b/packages/admin-web-angular/src/app/@theme/components/theme-switcher/theme-switcher.component.scss @@ -0,0 +1,48 @@ +@import '../../styles/themes'; +@import '~@nebular/theme/styles/core/mixins'; +@import '~@nebular/theme/styles/core/functions'; + +@include nb-install-component() { + .themes-switcher { + display: flex; + font-size: 0.9rem; + padding: 0.8rem 0.8rem; + align-items: center; + cursor: pointer; + // background-color: nb-theme(switcher-background); + // border-radius: nb-theme(radius); + + // &:hover { + // $color: nb-theme(switcher-background); + // $percentage: nb-theme(switcher-background-percentage); + + // background-color: tint($color, $percentage); + // } + + span { + margin: 0 0.7rem; + } + + i { + // color: nb-theme(color-primary); + font-size: 1.1rem; + border-radius: 50%; + position: relative; + + @include nb-for-theme(default) { + // color: nb-theme(color-success); + } + + @include nb-for-theme(corporate) { + // color: nb-theme(color-fg-highlight); + } + + &::before { + // Hack for IE11, IE11 should not set background + // background: nb-theme(drops-icon-line-gadient); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + } + } + } +} diff --git a/packages/admin-web-angular/src/app/@theme/components/theme-switcher/theme-switcher.component.ts b/packages/admin-web-angular/src/app/@theme/components/theme-switcher/theme-switcher.component.ts new file mode 100644 index 0000000..93db25c --- /dev/null +++ b/packages/admin-web-angular/src/app/@theme/components/theme-switcher/theme-switcher.component.ts @@ -0,0 +1,33 @@ +import { Component, Input, ViewChild } from '@angular/core'; +import { NbPopoverDirective, NbThemeService } from '@nebular/theme'; +import { NbJSThemeOptions } from '@nebular/theme/services/js-themes/theme.options'; +import { ThemeSwitcherListComponent } from './themes-switcher-list/themes-switcher-list.component'; +import { AnalyticsService } from '@app/@core/utils/analytics.service'; + +@Component({ + selector: 'ngx-theme-switcher', + templateUrl: './theme-switcher.component.html', + styleUrls: ['./theme-switcher.component.scss'], +}) +export class ThemeSwitcherComponent { + @ViewChild(NbPopoverDirective, { static: true }) + popover: NbPopoverDirective; + + @Input() + showTitle: boolean = true; + + switcherListComponent = ThemeSwitcherListComponent; + theme: NbJSThemeOptions; + + constructor( + private themeService: NbThemeService, + private analyticsService: AnalyticsService + ) { + this.themeService.onThemeChange().subscribe((theme: any) => { + this.activeTheme = theme.name; + }); + + this.activeTheme = themeService.currentTheme; + } + activeTheme: string; +} diff --git a/packages/admin-web-angular/src/app/@theme/components/theme-switcher/themes-switcher-list/theme-switcher-list.component.html b/packages/admin-web-angular/src/app/@theme/components/theme-switcher/themes-switcher-list/theme-switcher-list.component.html new file mode 100644 index 0000000..08ddf27 --- /dev/null +++ b/packages/admin-web-angular/src/app/@theme/components/theme-switcher/themes-switcher-list/theme-switcher-list.component.html @@ -0,0 +1,10 @@ +
    +
  • + + {{ theme.title }} +
  • +
diff --git a/packages/admin-web-angular/src/app/@theme/components/theme-switcher/themes-switcher-list/theme-switcher-list.component.scss b/packages/admin-web-angular/src/app/@theme/components/theme-switcher/themes-switcher-list/theme-switcher-list.component.scss new file mode 100644 index 0000000..0bb6aab --- /dev/null +++ b/packages/admin-web-angular/src/app/@theme/components/theme-switcher/themes-switcher-list/theme-switcher-list.component.scss @@ -0,0 +1,110 @@ +@import '../../../styles/themes'; +@import '~@nebular/theme/styles/core/mixins'; +@import '~bootstrap/scss/mixins/breakpoints'; +@import '~@nebular/theme/styles/global/breakpoints'; + +$icon-color-default: #0bbb79; +$icon-color-cosmic: #7958fa; +$icon-color-corporate: #a7a2be; +$icon-color-everlight: white; +$icon-color-everdark: gray; + +$icon-top-color-default: #01dbb5; +$icon-top-color-cosmic: #a258fe; +$icon-top-color-corporate: #e9e8eb; +$icon-top-color-everlight: gray; +$icon-top-color-everdark: black; + +@include nb-install-component() { + ::ng-deep .themes-switcher-list { + padding: 1rem 2rem 1.25rem 0.5rem; + margin: 0; + + @include nb-ltr(text-align, start); + @include nb-rtl(text-align, end); + + .themes-switcher-item { + list-style: none; + cursor: pointer; + + &:hover span { + opacity: 0.5; + } + + i { + font-size: 2rem; + + &.drop-icon-default { + color: $icon-color-default; + + // Hack for IE11, IE11 should not set background + background: -webkit-linear-gradient( + $icon-top-color-default, + $icon-color-default + ); + } + + &.drop-icon-cosmic { + color: $icon-color-cosmic; + + // Hack for IE11, IE11 should not set background + background: -webkit-linear-gradient( + $icon-top-color-cosmic, + $icon-color-cosmic + ); + } + + &.drop-icon-corporate { + color: $icon-color-corporate; + + // Hack for IE11, IE11 should not set background + background: -webkit-linear-gradient( + $icon-top-color-corporate, + $icon-color-corporate + ); + } + &.drop-icon-everlight { + color: $icon-color-corporate; + + // Hack for IE11, IE11 should not set background + background: -webkit-linear-gradient( + $icon-top-color-everlight, + $icon-color-everlight + ); + } + + &.drop-icon-everdark { + color: $icon-color-corporate; + + // Hack for IE11, IE11 should not set background + background: -webkit-linear-gradient( + $icon-top-color-everdark, + $icon-color-everdark + ); + } + + &.drop-icon-default, + &.drop-icon-cosmic, + &.drop-icon-corporate, + &.drop-icon-everlight, + &.drop-icon-everdark { + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + } + } + + span { + font-weight: 300; + vertical-align: super; + padding-left: 1rem; + // color: #2a2a2a; + } + } + } + + @include media-breakpoint-down(is) { + ::ng-deep .themes-switcher-list { + display: none; + } + } +} diff --git a/packages/admin-web-angular/src/app/@theme/components/theme-switcher/themes-switcher-list/themes-switcher-list.component.ts b/packages/admin-web-angular/src/app/@theme/components/theme-switcher/themes-switcher-list/themes-switcher-list.component.ts new file mode 100644 index 0000000..c9ae46a --- /dev/null +++ b/packages/admin-web-angular/src/app/@theme/components/theme-switcher/themes-switcher-list/themes-switcher-list.component.ts @@ -0,0 +1,48 @@ +import { Component, Input } from '@angular/core'; +import { NbPopoverDirective, NbThemeService } from '@nebular/theme'; +import { AnalyticsService } from '../../../../@core/utils/analytics.service'; +import { NbJSThemeOptions } from '@nebular/theme/services/js-themes/theme.options'; + +@Component({ + selector: 'ngx-theme-switcher-list', + templateUrl: './theme-switcher-list.component.html', + styleUrls: ['./theme-switcher-list.component.scss'], +}) +export class ThemeSwitcherListComponent { + @Input() popover: NbPopoverDirective; + + theme: NbJSThemeOptions; + + themes = [ + { + title: 'Light', + key: 'everlight', + }, + { + title: 'Dark', + key: 'everdark', + }, + { + title: 'White', + key: 'default', + }, + { + title: 'Cosmic', + key: 'cosmic', + }, + { + title: 'Corporate', + key: 'corporate', + }, + ]; + + constructor( + private themeService: NbThemeService, + private analyticsService: AnalyticsService + ) {} + onToggleTheme(themeKey: string) { + this.themeService.changeTheme(themeKey); + this.analyticsService.trackEvent('switchTheme'); + this.popover.hide(); + } +} diff --git a/packages/admin-web-angular/src/app/@theme/components/tiny-mce/tiny-mce.component.ts b/packages/admin-web-angular/src/app/@theme/components/tiny-mce/tiny-mce.component.ts new file mode 100644 index 0000000..de1dae6 --- /dev/null +++ b/packages/admin-web-angular/src/app/@theme/components/tiny-mce/tiny-mce.component.ts @@ -0,0 +1,40 @@ +import { + AfterViewInit, + Component, + ElementRef, + EventEmitter, + OnDestroy, + Output, +} from '@angular/core'; + +@Component({ + selector: 'ngx-tiny-mce', + template: '', +}) +export class TinyMCEComponent implements OnDestroy, AfterViewInit { + @Output() + editorKeyup = new EventEmitter(); + + editor: any; + + constructor(private host: ElementRef) {} + + ngAfterViewInit() { + tinymce.init({ + target: this.host.nativeElement, + plugins: ['link', 'paste', 'table'], + skin_url: 'assets/skins/lightgray', + setup: (editor) => { + this.editor = editor; + editor.on('keyup', () => { + this.editorKeyup.emit(editor.getContent()); + }); + }, + height: '320', + }); + } + + ngOnDestroy() { + tinymce.remove(this.editor); + } +} diff --git a/packages/admin-web-angular/src/app/@theme/directives/.gitkeep b/packages/admin-web-angular/src/app/@theme/directives/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/packages/admin-web-angular/src/app/@theme/index.ts b/packages/admin-web-angular/src/app/@theme/index.ts new file mode 100644 index 0000000..c2bf3e6 --- /dev/null +++ b/packages/admin-web-angular/src/app/@theme/index.ts @@ -0,0 +1 @@ +export * from './theme.module'; diff --git a/packages/admin-web-angular/src/app/@theme/layouts/index.ts b/packages/admin-web-angular/src/app/@theme/layouts/index.ts new file mode 100644 index 0000000..6d28d15 --- /dev/null +++ b/packages/admin-web-angular/src/app/@theme/layouts/index.ts @@ -0,0 +1,3 @@ +export * from './one-column/one-column.layout'; +export * from './two-columns/two-columns.layout'; +export * from './three-columns/three-columns.layout'; diff --git a/packages/admin-web-angular/src/app/@theme/layouts/one-column/one-column.layout.html b/packages/admin-web-angular/src/app/@theme/layouts/one-column/one-column.layout.html new file mode 100644 index 0000000..a72f87f --- /dev/null +++ b/packages/admin-web-angular/src/app/@theme/layouts/one-column/one-column.layout.html @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/packages/admin-web-angular/src/app/@theme/layouts/one-column/one-column.layout.scss b/packages/admin-web-angular/src/app/@theme/layouts/one-column/one-column.layout.scss new file mode 100644 index 0000000..ad0ad57 --- /dev/null +++ b/packages/admin-web-angular/src/app/@theme/layouts/one-column/one-column.layout.scss @@ -0,0 +1,159 @@ +@import '../../styles/themes'; +@import '~bootstrap/scss/mixins/breakpoints'; +@import '~@nebular/theme/styles/global/breakpoints'; + +@include nb-install-component() { + nb-layout-column.small { + flex: 0.15 !important; + } + + nb-sidebar.settings-sidebar { + $sidebar-width: 7.5rem; + + transition: width 0.3s ease; + width: $sidebar-width; + overflow: hidden; + + &.collapsed { + width: 0; + + ::ng-deep .main-container { + width: 0; + + .scrollable { + width: $sidebar-width; + padding: 1.25rem; + } + } + } + + ::ng-deep .main-container { + width: $sidebar-width; + // background: nb-theme(color-bg); + transition: width 0.3s ease; + overflow: hidden; + + .scrollable { + width: $sidebar-width; + } + + // @include nb-for-theme(cosmic) { + // background: nb-theme(layout-bg); + // } + } + } + + // nb-sidebar.menu-sidebar { + // // margin-top: nb-theme(sidebar-header-gap); + + // @include nb-for-theme(corporate) { + // margin-top: 0; + // } + + // // ::ng-deep .main-container { + // // height: calc( + // // #{nb-theme(sidebar-height)} - #{nb-theme(header-height)} - #{nb-theme( + // // sidebar-header-gap + // // )} + // // ) !important; + // // @include nb-ltr(border-top-right-radius, nb-theme(radius)); + // // @include nb-rtl(border-top-left-radius, nb-theme(radius)); + + // // @include nb-for-theme(corporate) { + // // border: 1px solid nb-theme(separator); + // // height: calc( + // // #{nb-theme(sidebar-height)} - #{nb-theme(header-height)} + // // ) !important; + // // } + // // } + + // // ::ng-deep .scrollable { + // // @include nb-for-theme(corporate) { + // // padding-top: 0; + + // // .menu-item:first-child { + // // border-top: none; + // // } + // // } + // // } + + // ::ng-deep nb-sidebar-header { + // padding-bottom: 0.5rem; + // text-align: center; + // } + + // background: transparent; + + // .main-btn { + // padding: 0.75rem 2.5rem; + // margin-top: -2rem; + // font-weight: bold; + // transition: padding 0.3s cubic-bezier(0.18, 0.89, 0.32, 1.48); + + // // @include nb-for-theme(corporate) { + // // border-radius: nb-theme(radius); + // // } + + // i { + // font-size: 2rem; + // text-shadow: 0 1px 1px rgba(0, 0, 0, 0.2); + // } + // span { + // @include nb-ltr(padding-left, 0.25rem); + // @include nb-rtl(padding-right, 0.25rem); + // } + + // i, + // span { + // vertical-align: middle; + // } + // } + + // &.compacted { + // ::ng-deep nb-sidebar-header { + // padding-left: 0; + // padding-right: 0; + // } + + // .main-btn { + // width: 46px; + // height: 44px; + // padding: 0.375rem; + // border-radius: 5px; + // transition: none; + + // span { + // display: none; + // } + // } + // } + // } + + // @include media-breakpoint-down(xs) { + // .main-content { + // padding: 0.75rem !important; + // } + // } + + // @include media-breakpoint-down(sm) { + // nb-sidebar.menu-sidebar { + // margin-top: 0; + + // ::ng-deep .main-container { + // // height: calc( + // // #{nb-theme(sidebar-height)} - #{nb-theme(header-height)} + // // ) !important; + // @include nb-ltr(border-top-right-radius, 0); + // @include nb-rtl(border-top-left-radius, 0); + + // .scrollable { + // padding-top: 0; + // } + // } + // } + + // .main-btn { + // display: none; + // } + // } +} diff --git a/packages/admin-web-angular/src/app/@theme/layouts/one-column/one-column.layout.ts b/packages/admin-web-angular/src/app/@theme/layouts/one-column/one-column.layout.ts new file mode 100644 index 0000000..c32e7aa --- /dev/null +++ b/packages/admin-web-angular/src/app/@theme/layouts/one-column/one-column.layout.ts @@ -0,0 +1,28 @@ +import { Component, OnDestroy } from '@angular/core'; +import { NbThemeService } from '@nebular/theme'; +import { takeWhile } from 'rxjs/operators'; + +// TODO: move layouts into the framework +@Component({ + selector: 'ngx-one-column-layout', + styleUrls: ['./one-column.layout.scss'], + templateUrl: './one-column.layout.html', +}) +export class OneColumnLayoutComponent implements OnDestroy { + private alive = true; + + currentTheme: string; + + constructor(protected themeService: NbThemeService) { + this.themeService + .getJsTheme() + .pipe(takeWhile(() => this.alive)) + .subscribe((theme) => { + this.currentTheme = theme.name; + }); + } + + ngOnDestroy() { + this.alive = false; + } +} diff --git a/packages/admin-web-angular/src/app/@theme/layouts/sample/sample.layout.html b/packages/admin-web-angular/src/app/@theme/layouts/sample/sample.layout.html new file mode 100644 index 0000000..afe19c1 --- /dev/null +++ b/packages/admin-web-angular/src/app/@theme/layouts/sample/sample.layout.html @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/admin-web-angular/src/app/@theme/layouts/sample/sample.layout.scss b/packages/admin-web-angular/src/app/@theme/layouts/sample/sample.layout.scss new file mode 100644 index 0000000..bbf9fe5 --- /dev/null +++ b/packages/admin-web-angular/src/app/@theme/layouts/sample/sample.layout.scss @@ -0,0 +1,159 @@ +@import '../../styles/themes'; +@import '~bootstrap/scss/mixins/breakpoints'; +@import '~@nebular/theme/styles/global/breakpoints'; + +@include nb-install-component() { + nb-layout-column.small { + flex: 0.15 !important; + } + + nb-sidebar.settings-sidebar { + $sidebar-width: 10rem; + + transition: width 0.3s ease; + width: $sidebar-width; + overflow: hidden; + + &.collapsed { + width: 0; + + ::ng-deep .main-container { + width: 0; + + .scrollable { + width: $sidebar-width; + padding: 1.25rem; + } + } + } + + ::ng-deep .main-container { + width: $sidebar-width; + // background: nb-theme(color-bg); + transition: width 0.3s ease; + overflow: hidden; + + .scrollable { + width: $sidebar-width; + } + + // @include nb-for-theme(cosmic) { + // background: nb-theme(layout-bg); + // } + } + } + + nb-sidebar.menu-sidebar { + // margin-top: nb-theme(sidebar-header-gap); + + @include nb-for-theme(corporate) { + margin-top: 0; + } + + // ::ng-deep .main-container { + // height: calc( + // #{nb-theme(sidebar-height)} - #{nb-theme(header-height)} - #{nb-theme( + // sidebar-header-gap + // )} + // ) !important; + // @include nb-ltr(border-top-right-radius, nb-theme(radius)); + // @include nb-rtl(border-top-left-radius, nb-theme(radius)); + + // @include nb-for-theme(corporate) { + // border: 1px solid nb-theme(separator); + // height: calc( + // #{nb-theme(sidebar-height)} - #{nb-theme(header-height)} + // ) !important; + // } + // } + + ::ng-deep .scrollable { + @include nb-for-theme(corporate) { + padding-top: 0; + + .menu-item:first-child { + border-top: none; + } + } + } + + ::ng-deep nb-sidebar-header { + padding-bottom: 0.5rem; + text-align: center; + } + + // background: transparent; + + .main-btn { + padding: 0.75rem 2.5rem; + margin-top: -2rem; + font-weight: bold; + transition: padding 0.3s cubic-bezier(0.18, 0.89, 0.32, 1.48); + + // @include nb-for-theme(corporate) { + // border-radius: nb-theme(radius); + // } + + i { + font-size: 2rem; + text-shadow: 0 1px 1px rgba(0, 0, 0, 0.2); + } + span { + @include nb-ltr(padding-left, 0.25rem); + @include nb-rtl(padding-right, 0.25rem); + } + + i, + span { + vertical-align: middle; + } + } + + &.compacted { + ::ng-deep nb-sidebar-header { + padding-left: 0; + padding-right: 0; + } + + .main-btn { + width: 46px; + height: 44px; + padding: 0.375rem; + border-radius: 5px; + transition: none; + + span { + display: none; + } + } + } + } + + @include media-breakpoint-down(xs) { + .main-content { + padding: 0.75rem !important; + } + } + + @include media-breakpoint-down(sm) { + nb-sidebar.menu-sidebar { + margin-top: 0; + + ::ng-deep .main-container { + // height: calc( + // #{nb-theme(sidebar-height)} - #{nb-theme(header-height)} + // ) !important; + @include nb-ltr(border-top-right-radius, 0); + @include nb-rtl(border-top-left-radius, 0); + + .scrollable { + padding-top: 0; + } + } + } + + .main-btn { + display: none; + } + } +} diff --git a/packages/admin-web-angular/src/app/@theme/layouts/sample/sample.layout.ts b/packages/admin-web-angular/src/app/@theme/layouts/sample/sample.layout.ts new file mode 100644 index 0000000..052b716 --- /dev/null +++ b/packages/admin-web-angular/src/app/@theme/layouts/sample/sample.layout.ts @@ -0,0 +1,118 @@ +import { Component, OnDestroy } from '@angular/core'; +import { delay, takeWhile, withLatestFrom } from 'rxjs/operators'; +import { + NbMediaBreakpoint, + NbMediaBreakpointsService, + NbMenuItem, + NbMenuService, + NbSidebarService, + NbThemeService, +} from '@nebular/theme'; + +import { StateService } from '../../../@core/data/state.service'; + +// TODO: move layouts into the framework +@Component({ + selector: 'ngx-sample-layout', + styleUrls: ['./sample.layout.scss'], + templateUrl: 'sample.layout.html', +}) +export class SampleLayoutComponent implements OnDestroy { + subMenu: NbMenuItem[] = [ + { + title: 'PAGE LEVEL MENU', + group: true, + }, + { + title: 'Buttons', + icon: 'ion ion-android-radio-button-off', + link: '/ui-features/buttons', + }, + { + title: 'Grid', + icon: 'ion ion-android-radio-button-off', + link: '/ui-features/grid', + }, + { + title: 'Icons', + icon: 'ion ion-android-radio-button-off', + link: '/ui-features/icons', + }, + { + title: 'Modals', + icon: 'ion ion-android-radio-button-off', + link: '/ui-features/modals', + }, + { + title: 'Typography', + icon: 'ion ion-android-radio-button-off', + link: '/ui-features/typography', + }, + { + title: 'Animated Searches', + icon: 'ion ion-android-radio-button-off', + link: '/ui-features/search-fields', + }, + { + title: 'Tabs', + icon: 'ion ion-android-radio-button-off', + link: '/ui-features/tabs', + }, + ]; + layout: any = {}; + sidebar: any = {}; + + private alive = true; + + currentTheme: string; + + constructor( + protected stateService: StateService, + protected menuService: NbMenuService, + protected themeService: NbThemeService, + protected bpService: NbMediaBreakpointsService, + protected sidebarService: NbSidebarService + ) { + this.stateService + .onLayoutState() + .pipe(takeWhile(() => this.alive)) + .subscribe((layout: string) => (this.layout = layout)); + + this.stateService + .onSidebarState() + .pipe(takeWhile(() => this.alive)) + .subscribe((sidebar: string) => { + this.sidebar = sidebar; + }); + + const isBp = this.bpService.getByName('is'); + this.menuService + .onItemSelect() + .pipe( + takeWhile(() => this.alive), + withLatestFrom(this.themeService.onMediaQueryChange()), + delay(20) + ) + .subscribe( + ([item, [bpFrom, bpTo]]: [ + any, + [NbMediaBreakpoint, NbMediaBreakpoint] + ]) => { + if (bpTo.width <= isBp.width) { + this.sidebarService.collapse('menu-sidebar'); + } + } + ); + + this.themeService + .getJsTheme() + .pipe(takeWhile(() => this.alive)) + .subscribe((theme) => { + this.currentTheme = theme.name; + }); + } + + ngOnDestroy() { + this.alive = false; + } +} diff --git a/packages/admin-web-angular/src/app/@theme/layouts/three-columns/three-columns.layout.html b/packages/admin-web-angular/src/app/@theme/layouts/three-columns/three-columns.layout.html new file mode 100644 index 0000000..4076b97 --- /dev/null +++ b/packages/admin-web-angular/src/app/@theme/layouts/three-columns/three-columns.layout.html @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + diff --git a/packages/admin-web-angular/src/app/@theme/layouts/three-columns/three-columns.layout.scss b/packages/admin-web-angular/src/app/@theme/layouts/three-columns/three-columns.layout.scss new file mode 100644 index 0000000..cc88031 --- /dev/null +++ b/packages/admin-web-angular/src/app/@theme/layouts/three-columns/three-columns.layout.scss @@ -0,0 +1,157 @@ +@import '../../styles/themes'; +@import '~bootstrap/scss/mixins/breakpoints'; +@import '~@nebular/theme/styles/global/breakpoints'; + +@include nb-install-component() { + nb-layout-column.small { + flex: 0.15 !important; + } + + nb-sidebar.settings-sidebar { + $sidebar-width: 7.5rem; + + transition: width 0.3s ease; + width: $sidebar-width; + overflow: hidden; + + &.collapsed { + width: 0; + + ::ng-deep .main-container { + width: 0; + + .scrollable { + width: $sidebar-width; + padding: 1.25rem; + } + } + } + + ::ng-deep .main-container { + width: $sidebar-width; + // background: nb-theme(color-bg); + transition: width 0.3s ease; + overflow: hidden; + + .scrollable { + width: $sidebar-width; + } + + // @include nb-for-theme(cosmic) { + // background: nb-theme(layout-bg); + // } + } + } + + nb-sidebar.menu-sidebar { + // margin-top: nb-theme(sidebar-header-gap); + + @include nb-for-theme(corporate) { + margin-top: 0; + } + + // ::ng-deep .main-container { + // height: calc( + // #{nb-theme(sidebar-height)} - #{nb-theme(header-height)} - #{nb-theme( + // sidebar-header-gap + // )} + // ) !important; + // @include nb-ltr(border-top-right-radius, nb-theme(radius)); + // @include nb-rtl(border-top-left-radius, nb-theme(radius)); + + // @include nb-for-theme(corporate) { + // border: 1px solid nb-theme(separator); + // height: calc( + // #{nb-theme(sidebar-height)} - #{nb-theme(header-height)} + // ) !important; + // } + // } + + ::ng-deep .scrollable { + @include nb-for-theme(corporate) { + padding-top: 0; + + .menu-item:first-child { + border-top: none; + } + } + } + + ::ng-deep nb-sidebar-header { + padding-bottom: 0.5rem; + text-align: center; + } + + .main-btn { + padding: 0.75rem 2.5rem; + margin-top: -2rem; + font-weight: bold; + transition: padding 0.3s cubic-bezier(0.18, 0.89, 0.32, 1.48); + + // @include nb-for-theme(corporate) { + // border-radius: nb-theme(radius); + // } + + i { + font-size: 2rem; + text-shadow: 0 1px 1px rgba(0, 0, 0, 0.2); + } + span { + @include nb-ltr(padding-left, 0.25rem); + @include nb-rtl(padding-right, 0.25rem); + } + + i, + span { + vertical-align: middle; + } + } + + &.compacted { + ::ng-deep nb-sidebar-header { + padding-left: 0; + padding-right: 0; + } + + .main-btn { + width: 46px; + height: 44px; + padding: 0.375rem; + border-radius: 5px; + transition: none; + + span { + display: none; + } + } + } + } + + @include media-breakpoint-down(xs) { + .main-content { + padding: 0.75rem !important; + } + } + + @include media-breakpoint-down(sm) { + nb-sidebar.menu-sidebar { + margin-top: 0; + + ::ng-deep .main-container { + // height: calc( + // #{nb-theme(sidebar-height)} - #{nb-theme(header-height)} + // ) !important; + @include nb-ltr(border-top-right-radius, 0); + @include nb-rtl(border-top-left-radius, 0); + + .scrollable { + padding-top: 0; + } + } + } + + .main-btn { + display: none; + } + } +} diff --git a/packages/admin-web-angular/src/app/@theme/layouts/three-columns/three-columns.layout.ts b/packages/admin-web-angular/src/app/@theme/layouts/three-columns/three-columns.layout.ts new file mode 100644 index 0000000..7a9e812 --- /dev/null +++ b/packages/admin-web-angular/src/app/@theme/layouts/three-columns/three-columns.layout.ts @@ -0,0 +1,28 @@ +import { Component, OnDestroy } from '@angular/core'; +import { NbThemeService } from '@nebular/theme'; +import { takeWhile } from 'rxjs/operators'; + +// TODO: move layouts into the framework +@Component({ + selector: 'ngx-three-columns-layout', + styleUrls: ['./three-columns.layout.scss'], + templateUrl: './three-columns.layout.html', +}) +export class ThreeColumnsLayoutComponent implements OnDestroy { + private alive = true; + + currentTheme: string; + + constructor(protected themeService: NbThemeService) { + this.themeService + .getJsTheme() + .pipe(takeWhile(() => this.alive)) + .subscribe((theme) => { + this.currentTheme = theme.name; + }); + } + + ngOnDestroy() { + this.alive = false; + } +} diff --git a/packages/admin-web-angular/src/app/@theme/layouts/two-columns/two-columns.layout.html b/packages/admin-web-angular/src/app/@theme/layouts/two-columns/two-columns.layout.html new file mode 100644 index 0000000..0f2974f --- /dev/null +++ b/packages/admin-web-angular/src/app/@theme/layouts/two-columns/two-columns.layout.html @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + diff --git a/packages/admin-web-angular/src/app/@theme/layouts/two-columns/two-columns.layout.scss b/packages/admin-web-angular/src/app/@theme/layouts/two-columns/two-columns.layout.scss new file mode 100644 index 0000000..9d70351 --- /dev/null +++ b/packages/admin-web-angular/src/app/@theme/layouts/two-columns/two-columns.layout.scss @@ -0,0 +1,157 @@ +@import '../../styles/themes'; +@import '~bootstrap/scss/mixins/breakpoints'; +@import '~@nebular/theme/styles/global/breakpoints'; + +@include nb-install-component() { + nb-layout-column.small { + flex: 0.15 !important; + } + + nb-sidebar.settings-sidebar { + $sidebar-width: 7.5rem; + + transition: width 0.3s ease; + width: $sidebar-width; + overflow: hidden; + + &.collapsed { + width: 0; + + ::ng-deep .main-container { + width: 0; + + .scrollable { + width: $sidebar-width; + padding: 1.25rem; + } + } + } + + ::ng-deep .main-container { + width: $sidebar-width; + // background: nb-theme(color-bg); + transition: width 0.3s ease; + overflow: hidden; + + .scrollable { + width: $sidebar-width; + } + + @include nb-for-theme(cosmic) { + // background: nb-theme(layout-bg); + } + } + } + + nb-sidebar.menu-sidebar { + // margin-top: nb-theme(sidebar-header-gap); + + @include nb-for-theme(corporate) { + margin-top: 0; + } + + // ::ng-deep .main-container { + // height: calc( + // #{nb-theme(sidebar-height)} - #{nb-theme(header-height)} - #{nb-theme( + // sidebar-header-gap + // )} + // ) !important; + // @include nb-ltr(border-top-right-radius, nb-theme(radius)); + // @include nb-rtl(border-top-left-radius, nb-theme(radius)); + + // @include nb-for-theme(corporate) { + // border: 1px solid nb-theme(separator); + // height: calc( + // #{nb-theme(sidebar-height)} - #{nb-theme(header-height)} + // ) !important; + // } + // } + + ::ng-deep .scrollable { + @include nb-for-theme(corporate) { + padding-top: 0; + + .menu-item:first-child { + border-top: none; + } + } + } + + ::ng-deep nb-sidebar-header { + padding-bottom: 0.5rem; + text-align: center; + } + + .main-btn { + padding: 0.75rem 2.5rem; + margin-top: -2rem; + font-weight: bold; + transition: padding 0.3s cubic-bezier(0.18, 0.89, 0.32, 1.48); + + // @include nb-for-theme(corporate) { + // border-radius: nb-theme(radius); + // } + + i { + font-size: 2rem; + text-shadow: 0 1px 1px rgba(0, 0, 0, 0.2); + } + span { + @include nb-ltr(padding-left, 0.25rem); + @include nb-rtl(padding-right, 0.25rem); + } + + i, + span { + vertical-align: middle; + } + } + + &.compacted { + ::ng-deep nb-sidebar-header { + padding-left: 0; + padding-right: 0; + } + + .main-btn { + width: 46px; + height: 44px; + padding: 0.375rem; + border-radius: 5px; + transition: none; + + span { + display: none; + } + } + } + } + + @include media-breakpoint-down(xs) { + .main-content { + padding: 0.75rem !important; + } + } + + @include media-breakpoint-down(sm) { + nb-sidebar.menu-sidebar { + margin-top: 0; + + ::ng-deep .main-container { + // height: calc( + // #{nb-theme(sidebar-height)} - #{nb-theme(header-height)} + // ) !important; + @include nb-ltr(border-top-right-radius, 0); + @include nb-rtl(border-top-left-radius, 0); + + .scrollable { + padding-top: 0; + } + } + } + + .main-btn { + display: none; + } + } +} diff --git a/packages/admin-web-angular/src/app/@theme/layouts/two-columns/two-columns.layout.ts b/packages/admin-web-angular/src/app/@theme/layouts/two-columns/two-columns.layout.ts new file mode 100644 index 0000000..a74452f --- /dev/null +++ b/packages/admin-web-angular/src/app/@theme/layouts/two-columns/two-columns.layout.ts @@ -0,0 +1,28 @@ +import { Component, OnDestroy } from '@angular/core'; +import { NbThemeService } from '@nebular/theme'; +import { takeWhile } from 'rxjs/operators'; + +// TODO: move layouts into the framework +@Component({ + selector: 'ngx-two-columns-layout', + styleUrls: ['./two-columns.layout.scss'], + templateUrl: './two-columns.layout.html', +}) +export class TwoColumnsLayoutComponent implements OnDestroy { + private alive = true; + + currentTheme: string; + + constructor(protected themeService: NbThemeService) { + this.themeService + .getJsTheme() + .pipe(takeWhile(() => this.alive)) + .subscribe((theme) => { + this.currentTheme = theme.name; + }); + } + + ngOnDestroy() { + this.alive = false; + } +} diff --git a/packages/admin-web-angular/src/app/@theme/styles/angular2-wizard.scss b/packages/admin-web-angular/src/app/@theme/styles/angular2-wizard.scss new file mode 100644 index 0000000..b3472f6 --- /dev/null +++ b/packages/admin-web-angular/src/app/@theme/styles/angular2-wizard.scss @@ -0,0 +1,3 @@ +form-wizard > div > div.card-header > ul { + padding-left: 0 !important; +} diff --git a/packages/admin-web-angular/src/app/@theme/styles/bootstrap-rtl.scss b/packages/admin-web-angular/src/app/@theme/styles/bootstrap-rtl.scss new file mode 100644 index 0000000..a2637b8 --- /dev/null +++ b/packages/admin-web-angular/src/app/@theme/styles/bootstrap-rtl.scss @@ -0,0 +1,195 @@ +@import './themes'; +.nb-theme-cosmic .whiteDiv { + color: white !important; +} +@mixin bootstrap-rtl() { + .btn-group:not(.btn-divided-group) > .btn:not(.dropdown-toggle) { + &:first-child { + @include nb-ltr() { + // border-top-left-radius: nb-theme(btn-border-radius); + // border-bottom-left-radius: nb-theme(btn-border-radius); + border-top-right-radius: 0; + border-bottom-right-radius: 0; + } + @include nb-rtl() { + border-top-left-radius: 0; + border-bottom-left-radius: 0; + // border-top-right-radius: nb-theme(btn-border-radius); + // border-bottom-right-radius: nb-theme(btn-border-radius); + } + } + &:last-child { + @include nb-ltr() { + border-top-left-radius: 0; + border-bottom-left-radius: 0; + // border-top-right-radius: nb-theme(btn-border-radius); + // border-bottom-right-radius: nb-theme(btn-border-radius); + } + @include nb-rtl() { + // border-top-left-radius: nb-theme(btn-border-radius); + // border-bottom-left-radius: nb-theme(btn-border-radius); + border-top-right-radius: 0; + border-bottom-right-radius: 0; + } + } + } + + .btn-group.dropdown { + & > .btn:first-of-type.dropdown-toggle { + @include nb-ltr() { + // border-top-left-radius: nb-theme(btn-border-radius); + border-top-right-radius: 0; + } + @include nb-rtl() { + border-top-left-radius: 0; + // border-top-right-radius: nb-theme(btn-border-radius); + } + } + & > .btn:last-of-type.dropdown-toggle { + @include nb-ltr() { + border-top-left-radius: 0; + // border-top-right-radius: nb-theme(btn-border-radius); + } + @include nb-rtl() { + // border-top-left-radius: nb-theme(btn-border-radius); + border-top-right-radius: 0; + } + } + + &:not(.show) { + & > .btn:first-of-type.dropdown-toggle { + @include nb-ltr() { + // border-bottom-left-radius: nb-theme(btn-border-radius); + border-bottom-right-radius: 0; + } + @include nb-rtl() { + border-bottom-left-radius: 0; + // border-bottom-right-radius: nb-theme(btn-border-radius); + } + } + & > .btn:last-of-type.dropdown-toggle { + @include nb-ltr() { + border-bottom-left-radius: 0; + // border-bottom-right-radius: nb-theme(btn-border-radius); + } + @include nb-rtl() { + // border-bottom-left-radius: nb-theme(btn-border-radius); + border-bottom-right-radius: 0; + } + } + } + } + + .btn-group.dropup { + & > .btn:first-of-type.dropdown-toggle { + @include nb-ltr() { + // border-bottom-left-radius: nb-theme(btn-border-radius); + border-bottom-right-radius: 0; + } + @include nb-rtl() { + border-bottom-left-radius: 0; + // border-bottom-right-radius: nb-theme(btn-border-radius); + } + } + & > .btn:last-of-type.dropdown-toggle { + @include nb-ltr() { + border-bottom-left-radius: 0; + // border-bottom-right-radius: nb-theme(btn-border-radius); + } + @include nb-rtl() { + // border-bottom-left-radius: nb-theme(btn-border-radius); + border-bottom-right-radius: 0; + } + } + + &:not(.show) { + & > .btn:first-of-type.dropdown-toggle { + @include nb-ltr() { + // border-top-left-radius: nb-theme(btn-border-radius); + border-top-right-radius: 0; + } + @include nb-rtl() { + border-top-left-radius: 0; + // border-top-right-radius: nb-theme(btn-border-radius); + } + } + & > .btn:last-of-type.dropdown-toggle { + @include nb-ltr() { + border-top-left-radius: 0; + // border-top-right-radius: nb-theme(btn-border-radius); + } + @include nb-rtl() { + // border-top-left-radius: nb-theme(btn-border-radius); + border-top-right-radius: 0; + } + } + } + } + + .btn-divided-group { + .btn:not(:first-child) { + @include nb-ltr(margin-left, 0.5rem); + @include nb-rtl(margin-right, 0.5rem); + // border-radius: nb-theme(btn-border-radius); + } + } + + .input-group-addon, + .input-group-icon { + @include nb-ltr() { + // border-left: nb-theme(form-control-border); + border-right: none; + } + @include nb-rtl() { + border-left: none; + // border-right: nb-theme(form-control-border); + } + } + + .input-group { + .form-control:first-child:not(:only-child), + .input-group-addon:first-child, + .input-group-prepend .btn:first-child, + .input-group-btn .btn:first-child { + @include nb-ltr() { + // border-top-left-radius: nb-theme(form-control-border-radius); + // border-bottom-left-radius: nb-theme(form-control-border-radius); + border-top-right-radius: 0; + border-bottom-right-radius: 0; + } + @include nb-rtl() { + border-top-left-radius: 0; + border-bottom-left-radius: 0; + // border-top-right-radius: nb-theme(form-control-border-radius); + // border-bottom-right-radius: nb-theme(form-control-border-radius); + } + } + .form-control:last-child:not(:only-child), + .input-group-addon:last-child, + .input-group-append .btn:last-child, + .input-group-btn .btn:last-child { + @include nb-ltr() { + border-top-left-radius: 0; + border-bottom-left-radius: 0; + // border-top-right-radius: nb-theme(form-control-border-radius); + // border-bottom-right-radius: nb-theme(form-control-border-radius); + } + @include nb-rtl() { + // border-top-left-radius: nb-theme(form-control-border-radius); + // border-bottom-left-radius: nb-theme(form-control-border-radius); + border-top-right-radius: 0; + border-bottom-right-radius: 0; + } + } + + .dropdown.show .btn.dropdown-toggle { + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; + } + + .dropup.show .btn.dropdown-toggle { + border-top-left-radius: 0; + border-top-right-radius: 0; + } + } +} diff --git a/packages/admin-web-angular/src/app/@theme/styles/checkbox-custom.scss b/packages/admin-web-angular/src/app/@theme/styles/checkbox-custom.scss new file mode 100644 index 0000000..e69de29 diff --git a/packages/admin-web-angular/src/app/@theme/styles/everdark.theme.scss b/packages/admin-web-angular/src/app/@theme/styles/everdark.theme.scss new file mode 100644 index 0000000..00869c5 --- /dev/null +++ b/packages/admin-web-angular/src/app/@theme/styles/everdark.theme.scss @@ -0,0 +1,133 @@ +$gray: #888; +$primary: #3366ff; +$danger: #ce4843; +$success: #47d26f; +$dark: hsl(234, 26%, 25%); +$nb-themes: nb-register-theme( + ( + color-primary-100: $primary, + color-primary-200: $primary, + color-primary-300: $primary, + color-primary-400: $primary, + color-primary-500: $primary, + color-primary-600: $primary, + color-primary-700: $primary, + color-primary-800: $primary, + color-primary-900: $primary, + color-info-100: $gray, + color-info-200: $gray, + color-info-300: $gray, + color-info-400: $gray, + color-info-500: $gray, + color-info-600: $gray, + color-info-700: $gray, + color-info-800: $gray, + color-info-900: $gray, + // backgrounds + header-background-color: #1f212a, + layout-background-color: #2a2c39, + layout-window-mode-background-color: #2a2c39, + footer-background-color: #1f212a, + input-background-color: #1f212a, + // TODO modal-bg: #1f212a, + chat-background-color: #1f212a, + actions-background-color: #1f212a, + search-background-color: #1f212a, + alert-background-color: #1f212a, + select-outline-background-color: #1f212a, + select-filled-background-color: #1f212a, + select-hero-background-color: #1f212a, + color-bg: #1f212a, + drops-icon-line-gadient: -webkit-linear-gradient(gray, black), + // cards + // TODO card-border-color: rgba(0, 0, 0, 0.125), + // card-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), + card-background-color: #1f212a !important, + card-header-primary-background-color: $gray, + // card-fg-text: #888, + // buttons + // btn-secondary-border: $gray, + button-outline-filled-primary-background-color: $gray, + button-outline-filled-primary-hover-background-color: $gray, + button-outline-filled-primary-active-background-color: $gray, + button-outline-primary-border-color: $gray, + button-outline-primary-hover-border-color: $gray, + button-outline-primary-active-border-color: $gray, + button-outline-primary-text-color: white, + button-outline-primary-hover-text-color: white, + button-outline-primary-active-text-color: white, + button-filled-success-background-color: $success, + button-filled-warning-background-color: #fee140, + button-filled-danger-background-color: $danger, + linear-gradient: linear-gradient(to right, red, green), + // menu + sidebar-background-color: #1f212a, + menu-background-color: transparent, + menu-item-divider-color: transparent, + menu-text-color: #8b8b8b, + menu-submenu-background-color: #2a2c39, + menu-submenu-text-color: white, + menu-item-active-text-color: white, + menu-item-active-background-color: #2a2c39, + menu-item-icon-active-color: white, + menu-item-icon-color: #8b8b8b, + // table + smart-table-bg-even: transparent, + smart-table-bg-active: rgba(0, 0, 0, 0.1), + smart-table-separator: #333, + smart-table-paging-bg-active: rgba(100, 100, 100, 1), + smart-table-header-bg: #1f212a, + smart-table-paging-border-color: #8b8b8b, + // progress bar + // progress-bar-default-bg: red, + progress-bar-info-background-color: red, + progress-bar-primary-background-color: + linear-gradient(45deg, red 0%, crimson 100%), + progress-bar-success-background-color: + linear-gradient(45deg, red 0%, crimson 100%), + progress-bar-warning-background-color: + linear-gradient(45deg, red 0%, crimson 100%), + progress-bar-danger-background-color: + linear-gradient(45deg, red 0%, crimson 100%), + // progress-bar-background: red, + // progress-bar-bg-color: red, + // text + // TODO color-fg-text: #8b8b8b, + // color-fg: #8b8b8b, + // color-fg-highlight: white, + text-basic-color: #8b8b8b, + card-header-text-color: #8b8b8b, + footer-text-color: #8b8b8b, + link-color: white !important, + // separator + tabset-divider-color: $gray, + route-tabset-divider-color: $gray, + card-divider-color: transparent, + footer-divider-color: transparent, + actions-divider-color: $gray, + // btn-group-separator: $gray, + // form + input-focus-border-color: #fff, + input-border-color: $gray, + // chart + chart-panel-summary-border-color: $gray, + // misc + switcher-background: rgba(255, 255, 255, 0.2), + scrollbar-fg: #fff, + scrollbar-bg: #1f212a, + spinner-background-color: transparent, + spinner-circle-filled-color: $danger, + // tabset-tab-underline-color: $gray, + select-primary-background-color: $gray, + // tabs-selected-second-color: $gray, + popover-background-color: #1f212a !important, + popover-border-color: white, + user-picture-box-border-color: $gray, + badge-success-background-color: $gray, + tooltip-success-background-color: $gray, + shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2), + tabset-tab-text-color: white !important, + ), + everdark, + cosmic +); diff --git a/packages/admin-web-angular/src/app/@theme/styles/everlight.theme.scss b/packages/admin-web-angular/src/app/@theme/styles/everlight.theme.scss new file mode 100644 index 0000000..2bc220f --- /dev/null +++ b/packages/admin-web-angular/src/app/@theme/styles/everlight.theme.scss @@ -0,0 +1,68 @@ +$gray: #888; +$danger: #ce4843; +$primary: #3366ff; +$success: #47d26f; +$dark: hsl(234, 26%, 25%); +$nb-themes: nb-register-theme( + ( + color-success-100: #47d26f, + color-success-200: #47d26f, + color-success-300: #47d26f, + color-success-400: #47d26f, + color-success-500: #47d26f, + color-success-600: #47d26f, + color-success-700: #47d26f, + color-success-800: #47d26f, + color-success-900: #47d26f, + color-text-color-highlight-500: #47d26f, + color-primary-100: #3366ff, + color-primary-200: #3366ff, + color-primary-300: #3366ff, + color-primary-400: #3366ff, + color-primary-500: #3366ff, + color-primary-600: #3366ff, + color-primary-700: #3366ff, + color-primary-800: #3366ff, + color-primary-900: #3366ff, + color-danger-100: #ce4843, + color-danger-200: #ce4843, + color-danger-300: #ce4843, + color-danger-400: #ce4843, + color-danger-500: #ce4843, + color-danger-600: #ce4843, + color-danger-700: #ce4843, + color-danger-800: #ce4843, + color-danger-900: #ce4843, + color-info-100: #888, + color-info-200: #888, + color-info-300: #888, + color-info-400: #888, + color-info-500: #888, + color-info-600: #888, + color-info-700: #888, + color-info-800: #888, + color-info-900: #888, + layout-background-color: rgb(235, 235, 235), + sidebar-background-color: #1f212a, + menu-background-color: transparent, + menu-item-divider-color: transparent, + drops-icon-line-gadient: -webkit-linear-gradient(white, gray), + menu-text-color: #8b8b8b, + menu-submenu-background-color: #2a2c39, + menu-submenu-text-color: white, + menu-item-active-text-color: white, + menu-item-active-background-color: #2a2c39, + menu-item-icon-active-color: white, + menu-item-icon-color: #8b8b8b, + header-background-color: #1f212a, + header-text-color: #fff, + user-initials-text-color: red, + menu-submenu-item-active-background-color: #2a2c39, + menu-submenu-item-active-text-color: #8b8b8b, + user-name-text-color: white, + text-basic-color: #8b8b8b, + spinner-background-color: transparent, + ), + everlight, + default +); diff --git a/packages/admin-web-angular/src/app/@theme/styles/global-btn.scss b/packages/admin-web-angular/src/app/@theme/styles/global-btn.scss new file mode 100644 index 0000000..ce07f0d --- /dev/null +++ b/packages/admin-web-angular/src/app/@theme/styles/global-btn.scss @@ -0,0 +1,4 @@ +a:hover, +button:hover { + cursor: pointer !important; +} diff --git a/packages/admin-web-angular/src/app/@theme/styles/google-map.scss b/packages/admin-web-angular/src/app/@theme/styles/google-map.scss new file mode 100644 index 0000000..01582ec --- /dev/null +++ b/packages/admin-web-angular/src/app/@theme/styles/google-map.scss @@ -0,0 +1,3 @@ +body > div.pac-container.pac-logo:last-child { + z-index: 999999999999999; +} diff --git a/packages/admin-web-angular/src/app/@theme/styles/menu.scss b/packages/admin-web-angular/src/app/@theme/styles/menu.scss new file mode 100644 index 0000000..a9cf940 --- /dev/null +++ b/packages/admin-web-angular/src/app/@theme/styles/menu.scss @@ -0,0 +1,3 @@ +nb-menu .menu-item i.menu-icon { + font-size: 1.8rem !important; +} diff --git a/packages/admin-web-angular/src/app/@theme/styles/ng-bootstrap.modal.scss b/packages/admin-web-angular/src/app/@theme/styles/ng-bootstrap.modal.scss new file mode 100644 index 0000000..f8dc477 --- /dev/null +++ b/packages/admin-web-angular/src/app/@theme/styles/ng-bootstrap.modal.scss @@ -0,0 +1,36 @@ +.ng-custom { + div.modal-content { + width: 80%; + margin: auto; + + .card { + border: none; + .card-block { + wizard-step div { + ea-location-form, + ea-user-basic-info-form, + ea-carrier-basic-info-form { + div { + padding-right: 3%; + } + } + } + } + + .card-footer { + padding: 3% 6.7%; + } + + ea-product-create .card-footer { + padding: 1% 11.1% 4% 11.1% !important; + } + + label.control-label { + padding-left: 3.3%; + padding-right: 0; + margin: auto 0; + text-align: left; + } + } + } +} diff --git a/packages/admin-web-angular/src/app/@theme/styles/pace.theme.scss b/packages/admin-web-angular/src/app/@theme/styles/pace.theme.scss new file mode 100644 index 0000000..cd5ed95 --- /dev/null +++ b/packages/admin-web-angular/src/app/@theme/styles/pace.theme.scss @@ -0,0 +1,22 @@ +/** + * @license + * Copyright Akveo. All Rights Reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ + +@mixin ngx-pace-theme() { + .pace .pace-progress { + background: nb-theme(color-primary-default); + } + + .pace .pace-progress-inner { + box-shadow: 0 0 10px nb-theme(color-primary-default), + 0 0 5px nb-theme(color-primary-default); + } + + .pace .pace-activity { + display: none; + // border-top-color: nb-theme(color-fg-highlight); + // border-left-color: nb-theme(color-fg-highlight); + } +} diff --git a/packages/admin-web-angular/src/app/@theme/styles/smart-table-global.scss b/packages/admin-web-angular/src/app/@theme/styles/smart-table-global.scss new file mode 100644 index 0000000..5df27ba --- /dev/null +++ b/packages/admin-web-angular/src/app/@theme/styles/smart-table-global.scss @@ -0,0 +1,16 @@ +ng2-smart-table thead tr.ng2-smart-filters { + th { + border: none !important; + margin: auto !important; + padding: 1.5% 1% !important; + } +} + +.ng2-smart-actions.ng2-smart-action-multiple-select.ng-star-inserted { + text-align: center; + input.form-control { + display: inline-block; + width: 0.9rem; + margin: 0 auto; + } +} diff --git a/packages/admin-web-angular/src/app/@theme/styles/styles.scss b/packages/admin-web-angular/src/app/@theme/styles/styles.scss new file mode 100644 index 0000000..2dbb908 --- /dev/null +++ b/packages/admin-web-angular/src/app/@theme/styles/styles.scss @@ -0,0 +1,288 @@ +// themes - our custom or/and out of the box themes +@import 'themes'; + +// framework component themes (styles tied to theme variables) +@import '~@nebular/theme/styles/globals'; +@import '~@nebular/auth/styles/globals'; + +@import '~@nebular/bootstrap/styles/globals'; + +@import '~angular2-toaster/toaster'; + +// toaster styles copied from 'angular2-toaster' package +// @import './toaster'; + +// custom global ng-bootstrap modal +@import './ng-bootstrap.modal'; +// loading progress bar theme +@import './pace.theme'; +// custom btn styles +@import './global-btn'; +@import './bootstrap-rtl'; +@import './menu.scss'; +// Smart table custom global styles +@import './smart-table-global'; +// Angular2 wizard custom globals. +@import './angular2-wizard'; +@import './google-map'; + +@import '~@ng-select/ng-select/themes/default.theme.css'; + +// In this versions of ngx-highlight we need to import theme globally +@import '~highlight.js/styles/github.css'; + +// install the framework and custom global styles +@include nb-install() { + // framework global styles + @include nb-bootstrap-global(); + @include nb-auth-global(); + @include nb-theme-global(); + + // loading progress bar + @include ngx-pace-theme(); + + // fixed in rc.9 and can be removed after upgrade + .custom-control .custom-control-indicator { + border-radius: 50%; // TODO: quickfix for https://github.com/akveo/nebular/issues/275 + } + + @include bootstrap-rtl(); + + .nb-theme-cosmic .whiteDiv { + color: white !important; + } + + pre { + text-align: left; + } + + hr { + margin: 3% 0; + } + + div.alert.alert-danger { + margin-top: 1%; + } + + input[type='checkbox'] { + cursor: pointer; + } + + select, + input:not([type='checkbox']) { + height: 50px !important; + } + + ng2-smart-table { + input[type='checkbox'] { + transform: scale(1.4) !important; + } + + th[ng2-st-checkbox-select-all] { + cursor: pointer !important; + + input[type='checkbox'] { + margin-top: 4px; + } + } + } +} + +// Smart table styles +.products-table td.ng2-smart-actions.ng-star-inserted { + padding: 0 !important; +} + +.category-table td.ng2-smart-actions.ng-star-inserted { + padding: 0 !important; +} + +ea-warehouse-product-create { + max-height: 800px; + // overflow: scroll; +} + +// ss-multiselect-dropdown style +.dropdown.dropdown-inline { + width: 100%; +} + +nb-card.no-margin { + margin: 0; +} + +.no-padding { + padding: 0 !important; +} + +.no-margin { + margin: 0 !important; +} + +.no-text-transform { + text-transform: none !important; +} + +// .specific-header-color { +// color: rgb(24, 22, 22); +// background-color: rgb(241, 239, 239); +// } +.content-center { + display: flex !important; + align-content: center !important; + align-items: center !important; +} + +.button-icon mr-1 { + margin-right: 3px; +} + +.clickable { + cursor: pointer; +} + +.badge-dark { + background: black; + color: white; + padding: 5px 8px; + border-radius: 50%; +} + +ea-product-create { + li.nav-item.active.enabled.ng-star-inserted { + display: none; + } + + .ng-star-inserted { + margin-top: 30px; + } +} + +.maintenance-message-container { + color: white !important; + padding: 20px; + display: flex; + align-items: center; + justify-content: center; + text-align: center; + height: 100%; + background: #2a2c39; + + h2, + h3, + h4, + h5, + h6 { + color: white !important; + } +} + +.nb-theme-default nb-spinner { + background-color: transparent !important; +} + +ea-product-create .card-footer { + padding: 1% 11.1% 4% 11.1% !important; +} + +.pull-right { + float: right !important; +} + +.card, +.card-footer, +.card-header { + background-color: transparent !important; +} + +.nb-theme-everdark { + .popover { + background-color: #1f212a !important; + border: 2px solid white; + } +} + +.ng-select .ng-select-container .ng-value-container .ng-input > input { + height: auto !important; +} + +.setup-merchants-page { + nb-stepper { + .step-content { + width: 100%; + text-align: center; + + button { + margin: 8px; + border: 0; + } + } + } +} + +nb-card.no-box-shadow { + box-shadow: none; + margin-bottom: 0; +} + +.preview-img-container { + .preview-img { + padding-right: 16px; + } + + .img-rounded { + margin-top: 3px; + max-height: 70px !important; + } + + .remove-icon { + cursor: pointer; + + span { + padding-right: 7px; + position: absolute; + font-size: 1.1em; + } + } +} + +h1, +h2, +h3, +h4, +h5, +h6 { + font-family: Exo !important; + font-weight: 500 !important; +} + +.ng2-smart-action.ng-star-inserted i { + font-size: 1.5rem; +} + +span.space { + margin-right: 5px; +} + +div.redirectBtn { + border: 2px solid #dadfe6; + color: #2a2a2a; + background-color: transparent; + padding: 0.525rem 0.6rem 0.525rem 0.6rem; + border-radius: 0.375rem; + position: relative; + cursor: pointer; + + strong { + font-weight: 500; + } +} + +div.redirectBtn:hover { + background: #dadfe6; +} + +button, +.btn { + font-weight: 500 !important; +} diff --git a/packages/admin-web-angular/src/app/@theme/styles/theme.corporate.ts b/packages/admin-web-angular/src/app/@theme/styles/theme.corporate.ts new file mode 100644 index 0000000..2cc11dd --- /dev/null +++ b/packages/admin-web-angular/src/app/@theme/styles/theme.corporate.ts @@ -0,0 +1,298 @@ +export const CORPORATE_THEME = { + name: 'corporate', + base: 'default', + variables: { + temperature: ['#ffa36b', '#ffa36b', '#ff9e7a', '#ff9888', '#ff8ea0'], + + solar: { + gradientLeft: '#ff8ea0', + gradientRight: '#ffa36b', + shadowColor: 'rgba(0, 0, 0, 0)', + radius: ['80%', '90%'], + }, + + traffic: { + colorBlack: '#ffffff', + tooltipBg: '#eef2f5', + tooltipBorderColor: '#eef2f5', + tooltipExtraCss: 'border-radius: 10px; padding: 4px 16px;', + tooltipTextColor: '#2a2a2a;', + tooltipFontWeight: '400', + + lineBg: '#cae6f3', + lineShadowBlur: '0', + itemColor: '#bcc3cc', + itemBorderColor: '#bcc3cc', + itemEmphasisBorderColor: '#74a2ff', + shadowLineDarkBg: 'rgba(0, 0, 0, 0)', + shadowLineShadow: 'rgba(0, 0, 0, 0)', + gradFrom: '#ffffff', + gradTo: '#ffffff', + }, + + electricity: { + tooltipBg: '#edf0f4', + tooltipLineColor: '#bdc4cd', + tooltipLineWidth: '0', + tooltipBorderColor: '#ebeef2', + tooltipExtraCss: 'border-radius: 10px; padding: 8px 24px;', + tooltipTextColor: '#2a2a2a;', + tooltipFontWeight: 'bolder', + + axisLineColor: 'rgba(0, 0, 0, 0)', + xAxisTextColor: '#2a2a2a;', + yAxisSplitLine: '#ebeef2', + + itemBorderColor: '#73a1ff', + lineStyle: 'solid', + lineWidth: '4', + lineGradFrom: '#bdc4cd', + lineGradTo: '#c0c8d1', + lineShadow: 'rgba(0, 0, 0, 0)', + + areaGradFrom: 'rgba(255, 255, 255, 0)', + areaGradTo: 'rgba(255, 255, 255, 0)', + shadowLineDarkBg: 'rgba(255, 255, 255, 0)', + }, + + bubbleMap: { + titleColor: '#484848', + areaColor: '#dddddd', + areaHoverColor: '#cccccc', + areaBorderColor: '#ebeef2', + }, + + profitBarAnimationEchart: { + textColor: '#b2bac2', + + firstAnimationBarColor: '#719efc', + secondAnimationBarColor: '#5dcfe3', + + splitLineStyleOpacity: '0.06', + splitLineStyleWidth: '1', + splitLineStyleColor: '#000000', + + tooltipTextColor: '#2a2a2a;', + tooltipFontWeight: '400', + tooltipFontSize: '16', + tooltipBg: '#eef2f5', + tooltipBorderColor: '#eef2f5', + tooltipBorderWidth: '3', + tooltipExtraCss: 'border-radius: 10px; padding: 4px 16px;', + }, + + trafficBarEchart: { + gradientFrom: '#ff8ea0', + gradientTo: '#ffa36b', + shadow: 'rgba(0, 0, 0, 0)', + shadowBlur: '0', + + axisTextColor: '#b2bac2', + axisFontSize: '12', + + tooltipBg: '#edf0f4', + tooltipBorderColor: '#ebeef2', + tooltipExtraCss: 'border-radius: 10px; padding: 8px 24px;', + tooltipTextColor: '#2a2a2a;', + tooltipFontWeight: 'bolder', + }, + + countryOrders: { + countryBorderColor: 'rgba(255, 255, 255, 1)', + countryFillColor: 'rgba(236, 242, 245, 1)', + countryBorderWidth: '1', + hoveredCountryBorderColor: 'rgba(113, 158, 252, 1)', + hoveredCountryFillColor: 'rgba(199, 216, 247, 1)', + hoveredCountryBorderWidth: '3', + + chartAxisLineColor: 'rgba(0, 0, 0, 0)', + chartAxisTextColor: '#b2bac2', + chartAxisFontSize: '16', + chartGradientTo: 'rgba(113, 158, 252, 1)', + chartGradientFrom: 'rgba(113, 158, 252, 1)', + chartAxisSplitLine: '#ebeef2', + chartShadowLineColor: '#2f296b', + + chartLineBottomShadowColor: 'rgba(113, 158, 252, 1)', + + chartInnerLineColor: '#eceff4', + }, + + echarts: { + bg: '#ffffff', + textColor: '#484848', + axisLineColor: '#bbbbbb', + splitLineColor: '#ebeef2', + itemHoverShadowColor: 'rgba(0, 0, 0, 0.5)', + tooltipBackgroundColor: '#6a7985', + areaOpacity: '0.7', + }, + + chartjs: { + axisLineColor: '#cccccc', + textColor: '#484848', + }, + + orders: { + tooltipBg: '#ffffff', + tooltipLineColor: 'rgba(0, 0, 0, 0)', + tooltipLineWidth: '0', + tooltipBorderColor: '#ebeef2', + tooltipExtraCss: 'border-radius: 10px; padding: 8px 24px;', + tooltipTextColor: '#2a2a2a;', + tooltipFontWeight: 'bolder', + tooltipFontSize: '20', + + axisLineColor: 'rgba(161, 161 ,229, 0.3)', + axisFontSize: '16', + axisTextColor: '#b2bac2', + yAxisSplitLine: 'rgba(161, 161 ,229, 0.2)', + + itemBorderColor: '#73a1ff', + lineStyle: 'solid', + lineWidth: '4', + + // first line + firstAreaGradFrom: 'rgba(227, 236, 254, 0.7)', + firstAreaGradTo: 'rgba(227, 236, 254, 0.7)', + firstShadowLineDarkBg: 'rgba(0, 0, 0, 0)', + + // second line + secondLineGradFrom: 'rgba(93, 207, 227, 1)', + secondLineGradTo: 'rgba(93, 207, 227, 1)', + + secondAreaGradFrom: 'rgba(0, 0, 0, 0)', + secondAreaGradTo: 'rgba(0, 0, 0, 0)', + secondShadowLineDarkBg: 'rgba(0, 0, 0, 0)', + + // third line + thirdLineGradFrom: 'rgba(113, 158, 252, 1)', + thirdLineGradTo: 'rgba(113, 158, 252, 1)', + + thirdAreaGradFrom: 'rgba(0, 0, 0, 0)', + thirdAreaGradTo: 'rgba(0, 0, 0, 0)', + thirdShadowLineDarkBg: 'rgba(0, 0, 0, 0)', + }, + + profit: { + bg: '#ffffff', + textColor: '#ffffff', + axisLineColor: 'rgba(161, 161 ,229, 0.3)', + splitLineColor: 'rgba(161, 161 ,229, 0.2)', + areaOpacity: '1', + + axisFontSize: '16', + axisTextColor: '#b2bac2', + + // first bar + firstLineGradFrom: '#719efc', + firstLineGradTo: '#719efc', + firstLineShadow: 'rgba(14, 16, 48, 0.4)', + + // second bar + secondLineGradFrom: '#5dcfe3', + secondLineGradTo: '#5dcfe3', + secondLineShadow: 'rgba(14, 16, 48, 0.4)', + + // third bar + thirdLineGradFrom: '#e3ecfe', + thirdLineGradTo: '#e3ecfe', + thirdLineShadow: 'rgba(14, 16, 48, 0.4)', + }, + + orderProfitLegend: { + firstItem: '#719efc', + secondItem: '#5dcfe3', + thirdItem: '#e3ecfe', + }, + + visitors: { + tooltipBg: '#ffffff', + tooltipLineColor: 'rgba(0, 0, 0, 0)', + tooltipLineWidth: '0', + tooltipBorderColor: '#ebeef2', + tooltipExtraCss: 'border-radius: 10px; padding: 8px 24px;', + tooltipTextColor: '#2a2a2a;', + tooltipFontWeight: 'bolder', + tooltipFontSize: '20', + + axisLineColor: 'rgba(161, 161 ,229, 0.3)', + axisFontSize: '16', + axisTextColor: '#b2bac2', + yAxisSplitLine: 'rgba(161, 161 ,229, 0.2)', + + itemBorderColor: '#73a1ff', + lineStyle: 'dotted', + lineWidth: '6', + lineGradFrom: '#73a1ff', + lineGradTo: '#73a1ff', + lineShadow: 'rgba(0, 0, 0, 0)', + + areaGradFrom: 'rgba(146, 181, 252, 1)', + areaGradTo: 'rgba(146, 181, 252, 1)', + shadowLineDarkBg: '#a695ff', + + innerLineStyle: 'solid', + innerLineWidth: '1', + + innerAreaGradFrom: 'rgba(227, 236, 254, 1)', + innerAreaGradTo: 'rgba(227, 236, 254, 1)', + }, + + visitorsLegend: { + firstIcon: '#e3ecfe', + secondIcon: '#719efc', + }, + + visitorsPie: { + firstPieGradientLeft: '#94e2ed', + firstPieGradientRight: '#94e2ed', + firstPieShadowColor: 'rgba(0, 0, 0, 0)', + firstPieRadius: ['65%', '90%'], + + secondPieGradientLeft: '#719efc', + secondPieGradientRight: '#719efc', + secondPieShadowColor: '#b2cafb', + secondPieRadius: ['63%', '92%'], + shadowOffsetX: '-4', + shadowOffsetY: '-4', + }, + + visitorsPieLegend: { + firstSection: '#719efc', + secondSection: '#99e5ee', + }, + + earningPie: { + radius: ['65%', '100%'], + center: ['50%', '50%'], + + fontSize: '22', + + firstPieGradientLeft: '#719efc', + firstPieGradientRight: '#719efc', + firstPieShadowColor: 'rgba(0, 0, 0, 0)', + + secondPieGradientLeft: '#ff9f6f', + secondPieGradientRight: '#ff9f6f', + secondPieShadowColor: 'rgba(0, 0, 0, 0)', + + thirdPieGradientLeft: '#ff5e83', + thirdPieGradientRight: '#ff5e83', + thirdPieShadowColor: 'rgba(0, 0, 0, 0)', + }, + + earningLine: { + gradFrom: '#e3ecfe', + gradTo: '#e3ecfe', + + tooltipTextColor: '#2a2a2a;', + tooltipFontWeight: '400', + tooltipFontSize: '16', + tooltipBg: '#eef2f5', + tooltipBorderColor: '#eef2f5', + tooltipBorderWidth: '3', + tooltipExtraCss: 'border-radius: 10px; padding: 4px 16px;', + }, + }, +}; diff --git a/packages/admin-web-angular/src/app/@theme/styles/theme.cosmic.ts b/packages/admin-web-angular/src/app/@theme/styles/theme.cosmic.ts new file mode 100644 index 0000000..888b186 --- /dev/null +++ b/packages/admin-web-angular/src/app/@theme/styles/theme.cosmic.ts @@ -0,0 +1,304 @@ +export const COSMIC_THEME = { + name: 'cosmic', + base: 'default', + variables: { + temperature: ['#2ec7fe', '#31ffad', '#7bff24', '#fff024', '#f7bd59'], + + solar: { + gradientLeft: '#7bff24', + gradientRight: '#2ec7fe', + shadowColor: '#19977E', + radius: ['70%', '90%'], + }, + + traffic: { + colorBlack: '#000000', + tooltipBg: 'rgba(0, 255, 170, 0.35)', + tooltipBorderColor: '#00d977', + tooltipExtraCss: + 'box-shadow: 0px 2px 46px 0 rgba(0, 255, 170, 0.35); border-radius: 10px; padding: 4px 16px;', + tooltipTextColor: '#ffffff', + tooltipFontWeight: 'normal', + + lineBg: '#d1d1ff', + lineShadowBlur: '14', + itemColor: '#BEBBFF', + itemBorderColor: '#ffffff', + itemEmphasisBorderColor: '#ffffff', + shadowLineDarkBg: '#655ABD', + shadowLineShadow: 'rgba(33, 7, 77, 0.5)', + gradFrom: 'rgba(118, 89, 255, 0.4)', + gradTo: 'rgba(164, 84, 255, 0.5)', + }, + + electricity: { + tooltipBg: 'rgba(0, 255, 170, 0.35)', + tooltipLineColor: 'rgba(255, 255, 255, 0.1)', + tooltipLineWidth: '1', + tooltipBorderColor: '#00d977', + tooltipExtraCss: + 'box-shadow: 0px 2px 46px 0 rgba(0, 255, 170, 0.35); border-radius: 10px; padding: 8px 24px;', + tooltipTextColor: '#ffffff', + tooltipFontWeight: 'normal', + + axisLineColor: 'rgba(161, 161 ,229, 0.3)', + xAxisTextColor: '#a1a1e5', + yAxisSplitLine: 'rgba(161, 161 ,229, 0.2)', + + itemBorderColor: '#ffffff', + lineStyle: 'dotted', + lineWidth: '6', + lineGradFrom: '#00ffaa', + lineGradTo: '#fff835', + lineShadow: 'rgba(14, 16, 48, 0.4)', + + areaGradFrom: 'rgba(188, 92, 255, 0.5)', + areaGradTo: 'rgba(188, 92, 255, 0)', + shadowLineDarkBg: '#a695ff', + }, + + bubbleMap: { + titleColor: '#ffffff', + areaColor: '#2c2961', + areaHoverColor: '#a1a1e5', + areaBorderColor: '#654ddb', + }, + + profitBarAnimationEchart: { + textColor: '#ffffff', + + firstAnimationBarColor: '#0088ff', + secondAnimationBarColor: '#7659ff', + + splitLineStyleOpacity: '0.06', + splitLineStyleWidth: '1', + splitLineStyleColor: '#000000', + + tooltipTextColor: '#ffffff', + tooltipFontWeight: 'normal', + tooltipFontSize: '16', + tooltipBg: 'rgba(0, 255, 170, 0.35)', + tooltipBorderColor: '#00d977', + tooltipBorderWidth: '3', + tooltipExtraCss: + 'box-shadow: 0px 2px 46px 0 rgba(0, 255, 170, 0.35); border-radius: 10px; padding: 4px 16px;', + }, + + trafficBarEchart: { + gradientFrom: '#fc0', + gradientTo: '#ffa100', + shadow: '#ffb600', + shadowBlur: '5', + + axisTextColor: '#a1a1e5', + axisFontSize: '12', + + tooltipBg: 'rgba(0, 255, 170, 0.35)', + tooltipBorderColor: '#00d977', + tooltipExtraCss: + 'box-shadow: 0px 2px 46px 0 rgba(0, 255, 170, 0.35); border-radius: 10px; padding: 4px 16px;', + tooltipTextColor: '#ffffff', + tooltipFontWeight: 'normal', + }, + + countryOrders: { + countryBorderColor: '#525dbd', + countryFillColor: '#4f41a6', + countryBorderWidth: '2', + hoveredCountryBorderColor: '#00f9a6', + hoveredCountryFillColor: '#377aa7', + hoveredCountryBorderWidth: '3', + + chartAxisLineColor: 'rgba(161, 161 ,229, 0.3)', + chartAxisTextColor: '#a1a1e5', + chartAxisFontSize: '16', + chartGradientTo: '#00c7c7', + chartGradientFrom: '#00d977', + chartAxisSplitLine: 'rgba(161, 161 ,229, 0.2)', + chartShadowBarColor: '#2f296b', + + chartLineBottomShadowColor: '#00977e', + + chartInnerLineColor: '#2f296b', + }, + + echarts: { + bg: '#3d3780', + textColor: '#ffffff', + axisLineColor: '#a1a1e5', + splitLineColor: '#342e73', + itemHoverShadowColor: 'rgba(0, 0, 0, 0.5)', + tooltipBackgroundColor: '#6a7985', + areaOpacity: '1', + }, + + chartjs: { + axisLineColor: '#a1a1e5', + textColor: '#ffffff', + }, + + orders: { + tooltipBg: 'rgba(0, 255, 170, 0.35)', + tooltipLineColor: 'rgba(255, 255, 255, 0.1)', + tooltipLineWidth: '1', + tooltipBorderColor: '#00d977', + tooltipExtraCss: + 'box-shadow: 0px 2px 46px 0 rgba(0, 255, 170, 0.35); border-radius: 10px; padding: 8px 24px;', + tooltipTextColor: '#ffffff', + tooltipFontWeight: 'normal', + tooltipFontSize: '20', + + axisLineColor: 'rgba(161, 161 ,229, 0.3)', + axisFontSize: '16', + axisTextColor: '#a1a1e5', + yAxisSplitLine: 'rgba(161, 161 ,229, 0.2)', + + itemBorderColor: '#ffffff', + lineStyle: 'solid', + lineWidth: '4', + + // first line + firstAreaGradFrom: 'rgba(78, 64, 164, 1)', + firstAreaGradTo: 'rgba(78, 64, 164, 1)', + firstShadowLineDarkBg: '#018dff', + + // second line + secondLineGradFrom: '#00bece', + secondLineGradTo: '#00da78', + + secondAreaGradFrom: 'rgba(38, 139, 145, 0.8)', + secondAreaGradTo: 'rgba(38, 139, 145, 0.5)', + secondShadowLineDarkBg: '#2c5a85', + + // third line + thirdLineGradFrom: '#8069ff', + thirdLineGradTo: '#8357ff', + + thirdAreaGradFrom: 'rgba(118, 73, 208, 0.7)', + thirdAreaGradTo: 'rgba(188, 92, 255, 0.4)', + thirdShadowLineDarkBg: '#a695ff', + }, + + profit: { + bg: '#3d3780', + textColor: '#ffffff', + axisLineColor: '#a1a1e5', + splitLineColor: '#342e73', + areaOpacity: '1', + + axisFontSize: '16', + axisTextColor: '#a1a1e5', + + // first bar + firstLineGradFrom: '#00bece', + firstLineGradTo: '#00da78', + firstLineShadow: 'rgba(14, 16, 48, 0.4)', + + // second bar + secondLineGradFrom: '#8069ff', + secondLineGradTo: '#8357ff', + secondLineShadow: 'rgba(14, 16, 48, 0.4)', + + // third bar + thirdLineGradFrom: '#4e40a4', + thirdLineGradTo: '#4e40a4', + thirdLineShadow: 'rgba(14, 16, 48, 0.4)', + }, + + orderProfitLegend: { + firstItem: 'linear-gradient(90deg, #00c7c7 0%, #00d977 100%)', + secondItem: 'linear-gradient(90deg, #a454ff 0%, #7659ff 100%)', + thirdItem: '#4e40a4', + }, + + visitors: { + tooltipBg: 'rgba(0, 255, 170, 0.35)', + tooltipLineColor: 'rgba(255, 255, 255, 0.1)', + tooltipLineWidth: '1', + tooltipBorderColor: '#00d977', + tooltipExtraCss: + 'box-shadow: 0px 2px 46px 0 rgba(0, 255, 170, 0.35); border-radius: 10px; padding: 8px 24px;', + tooltipTextColor: '#ffffff', + tooltipFontWeight: 'normal', + + axisLineColor: 'rgba(161, 161 ,229, 0.3)', + axisFontSize: '16', + axisTextColor: '#a1a1e5', + yAxisSplitLine: 'rgba(161, 161 ,229, 0.2)', + + itemBorderColor: '#ffffff', + lineStyle: 'dotted', + lineWidth: '6', + lineGradFrom: '#ffffff', + lineGradTo: '#ffffff', + lineShadow: 'rgba(14, 16, 48, 0.4)', + + areaGradFrom: 'rgba(188, 92, 255, 1)', + areaGradTo: 'rgba(188, 92, 255, 0.5)', + shadowLineDarkBg: '#a695ff', + + innerLineStyle: 'solid', + innerLineWidth: '1', + + innerAreaGradFrom: 'rgba(59, 165, 243, 1)', + innerAreaGradTo: 'rgba(4, 133, 243 , 1)', + }, + + visitorsLegend: { + firstIcon: 'linear-gradient(90deg, #0088ff 0%, #3dafff 100%)', + secondIcon: 'linear-gradient(90deg, #a454ff 0%, #7659ff 100%)', + }, + + visitorsPie: { + firstPieGradientLeft: '#7bff24', + firstPieGradientRight: '#2ec7fe', + firstPieShadowColor: '#19977E', + firstPieRadius: ['70%', '90%'], + + secondPieGradientLeft: '#ff894a', + secondPieGradientRight: '#ffcc10', + secondPieShadowColor: '#cf7c1c', + secondPieRadius: ['60%', '95%'], + shadowOffsetX: '0', + shadowOffsetY: '3', + }, + + visitorsPieLegend: { + firstSection: 'linear-gradient(90deg, #ffcb17 0%, #ff874c 100%)', + secondSection: 'linear-gradient(90deg, #00c7c7 0%, #00d977 100%)', + }, + + earningPie: { + radius: ['65%', '100%'], + center: ['50%', '50%'], + + fontSize: '22', + + firstPieGradientLeft: '#00d77f', + firstPieGradientRight: '#00d77f', + firstPieShadowColor: 'rgba(0, 0, 0, 0)', + + secondPieGradientLeft: '#7756f7', + secondPieGradientRight: '#7756f7', + secondPieShadowColor: 'rgba(0, 0, 0, 0)', + + thirdPieGradientLeft: '#ffca00', + thirdPieGradientRight: '#ffca00', + thirdPieShadowColor: 'rgba(0, 0, 0, 0)', + }, + + earningLine: { + gradFrom: 'rgba(118, 89, 255, 0.4)', + gradTo: 'rgba(164, 84, 255, 0.5)', + + tooltipTextColor: '#ffffff', + tooltipFontWeight: 'normal', + tooltipFontSize: '16', + tooltipBg: 'rgba(0, 255, 170, 0.35)', + tooltipBorderColor: '#00d977', + tooltipBorderWidth: '3', + tooltipExtraCss: + 'box-shadow: 0px 2px 46px 0 rgba(0, 255, 170, 0.35); border-radius: 10px; padding: 4px 16px;', + }, + }, +}; diff --git a/packages/admin-web-angular/src/app/@theme/styles/theme.dark.ts b/packages/admin-web-angular/src/app/@theme/styles/theme.dark.ts new file mode 100644 index 0000000..8763ce6 --- /dev/null +++ b/packages/admin-web-angular/src/app/@theme/styles/theme.dark.ts @@ -0,0 +1,4 @@ +export const DARK_THEME = { + name: 'dark', + base: 'default', +}; diff --git a/packages/admin-web-angular/src/app/@theme/styles/theme.default.ts b/packages/admin-web-angular/src/app/@theme/styles/theme.default.ts new file mode 100644 index 0000000..2acd2f5 --- /dev/null +++ b/packages/admin-web-angular/src/app/@theme/styles/theme.default.ts @@ -0,0 +1,300 @@ +export const DEFAULT_THEME = { + name: 'default', + base: null, + variables: { + // Safari fix + temperature: ['#42db7d', '#42db7d', '#42db7d', '#42db7d', '#42db7d'], + + solar: { + gradientLeft: '#42db7d', + gradientRight: '#42db7d', + shadowColor: 'rgba(0, 0, 0, 0)', + radius: ['80%', '90%'], + }, + + traffic: { + colorBlack: '#000000', + tooltipBg: '#ffffff', + tooltipBorderColor: '#c0c8d1', + tooltipExtraCss: 'border-radius: 10px; padding: 4px 16px;', + tooltipTextColor: '#2a2a2a;', + tooltipFontWeight: 'bolder', + + lineBg: '#c0c8d1', + lineShadowBlur: '1', + itemColor: '#bcc3cc', + itemBorderColor: '#bcc3cc', + itemEmphasisBorderColor: '#42db7d', + shadowLineDarkBg: 'rgba(0, 0, 0, 0)', + shadowLineShadow: 'rgba(0, 0, 0, 0)', + gradFrom: '#ebeef2', + gradTo: '#ebeef2', + }, + + electricity: { + tooltipBg: '#ffffff', + tooltipLineColor: 'rgba(0, 0, 0, 0)', + tooltipLineWidth: '0', + tooltipBorderColor: '#ebeef2', + tooltipExtraCss: 'border-radius: 10px; padding: 8px 24px;', + tooltipTextColor: '#2a2a2a;', + tooltipFontWeight: 'bolder', + + axisLineColor: 'rgba(0, 0, 0, 0)', + xAxisTextColor: '#2a2a2a;', + yAxisSplitLine: '#ebeef2', + + itemBorderColor: '#42db7d', + lineStyle: 'solid', + lineWidth: '4', + lineGradFrom: '#42db7d', + lineGradTo: '#42db7d', + lineShadow: 'rgba(0, 0, 0, 0)', + + areaGradFrom: 'rgba(235, 238, 242, 0.5)', + areaGradTo: 'rgba(235, 238, 242, 0.5)', + shadowLineDarkBg: 'rgba(0, 0, 0, 0)', + }, + + bubbleMap: { + titleColor: '#484848', + areaColor: '#dddddd', + areaHoverColor: '#cccccc', + areaBorderColor: '#ebeef2', + }, + + profitBarAnimationEchart: { + textColor: '#484848', + + firstAnimationBarColor: '#3edd81', + secondAnimationBarColor: '#8d7fff', + + splitLineStyleOpacity: '0.06', + splitLineStyleWidth: '1', + splitLineStyleColor: '#000000', + + tooltipTextColor: '#2a2a2a;', + tooltipFontWeight: 'bolder', + tooltipFontSize: '16', + tooltipBg: '#ffffff', + tooltipBorderColor: '#c0c8d1', + tooltipBorderWidth: '3', + tooltipExtraCss: 'border-radius: 10px; padding: 4px 16px;', + }, + + trafficBarEchart: { + gradientFrom: '#fc0', + gradientTo: '#ffa100', + shadow: '#ffb600', + shadowBlur: '0', + + axisTextColor: '#b2bac2', + axisFontSize: '12', + + tooltipBg: '#ffffff', + tooltipBorderColor: '#c0c8d1', + tooltipExtraCss: 'border-radius: 10px; padding: 4px 16px;', + tooltipTextColor: '#2a2a2a;', + tooltipFontWeight: 'bolder', + }, + + countryOrders: { + countryBorderColor: 'rgba(255, 255, 255, 1)', + countryFillColor: 'rgba(236, 242, 245, 1)', + countryBorderWidth: '1', + hoveredCountryBorderColor: '#40dc7e', + hoveredCountryFillColor: '#c7f4d9', + hoveredCountryBorderWidth: '3', + + chartAxisLineColor: 'rgba(0, 0, 0, 0)', + chartAxisTextColor: '#b2bac2', + chartAxisFontSize: '16', + chartGradientTo: '#3edd81', + chartGradientFrom: '#3bddaf', + chartAxisSplitLine: '#ebeef2', + chartShadowLineColor: '#2f296b', + + chartLineBottomShadowColor: '#eceff4', + + chartInnerLineColor: '#eceff4', + }, + + echarts: { + bg: '#ffffff', + textColor: '#484848', + axisLineColor: '#bbbbbb', + splitLineColor: '#ebeef2', + itemHoverShadowColor: 'rgba(0, 0, 0, 0.5)', + tooltipBackgroundColor: '#6a7985', + areaOpacity: '0.7', + }, + + chartjs: { + axisLineColor: '#cccccc', + textColor: '#484848', + }, + + orders: { + tooltipBg: '#ffffff', + tooltipLineColor: 'rgba(0, 0, 0, 0)', + tooltipLineWidth: '0', + tooltipBorderColor: '#ebeef2', + tooltipExtraCss: 'border-radius: 10px; padding: 8px 24px;', + tooltipTextColor: '#2a2a2a;', + tooltipFontWeight: 'bolder', + tooltipFontSize: '20', + + axisLineColor: 'rgba(161, 161 ,229, 0.3)', + axisFontSize: '16', + axisTextColor: '#b2bac2', + yAxisSplitLine: 'rgba(161, 161 ,229, 0.2)', + + itemBorderColor: '#42db7d', + lineStyle: 'solid', + lineWidth: '4', + + // first line + firstAreaGradFrom: 'rgba(236, 242, 245, 0.8)', + firstAreaGradTo: 'rgba(236, 242, 245, 0.8)', + firstShadowLineDarkBg: 'rgba(0, 0, 0, 0)', + + // second line + secondLineGradFrom: 'rgba(164, 123, 255, 1)', + secondLineGradTo: 'rgba(164, 123, 255, 1)', + + secondAreaGradFrom: 'rgba(188, 92, 255, 0.2)', + secondAreaGradTo: 'rgba(188, 92, 255, 0)', + secondShadowLineDarkBg: 'rgba(0, 0, 0, 0)', + + // third line + thirdLineGradFrom: 'rgba(55, 220, 136, 1)', + thirdLineGradTo: 'rgba(55, 220, 136, 1)', + + thirdAreaGradFrom: 'rgba(31 ,106, 124, 0.2)', + thirdAreaGradTo: 'rgba(4, 126, 230, 0)', + thirdShadowLineDarkBg: 'rgba(0, 0, 0, 0)', + }, + + // TODO: need design for default theme + profit: { + bg: '#ffffff', + textColor: '#ffffff', + axisLineColor: 'rgba(161, 161 ,229, 0.3)', + splitLineColor: 'rgba(161, 161 ,229, 0.2)', + areaOpacity: '1', + + axisFontSize: '16', + axisTextColor: '#b2bac2', + + // first bar + firstLineGradFrom: '#00bece', + firstLineGradTo: '#00da78', + firstLineShadow: 'rgba(14, 16, 48, 0.4)', + + // second bar + secondLineGradFrom: '#8069ff', + secondLineGradTo: '#8357ff', + secondLineShadow: 'rgba(14, 16, 48, 0.4)', + + // third bar + thirdLineGradFrom: 'rgba(236, 242, 245, 0.8)', + thirdLineGradTo: 'rgba(236, 242, 245, 0.8)', + thirdLineShadow: 'rgba(14, 16, 48, 0.4)', + }, + + orderProfitLegend: { + firstItem: 'linear-gradient(90deg, #3edd81 0%, #3bddad 100%)', + secondItem: 'linear-gradient(90deg, #8d7fff 0%, #b17fff 100%)', + thirdItem: 'rgba(236, 242, 245, 0.8)', + }, + + visitors: { + tooltipBg: '#ffffff', + tooltipLineColor: 'rgba(0, 0, 0, 0)', + tooltipLineWidth: '0', + tooltipBorderColor: '#ebeef2', + tooltipExtraCss: 'border-radius: 10px; padding: 8px 24px;', + tooltipTextColor: '#2a2a2a;', + tooltipFontWeight: 'bolder', + tooltipFontSize: '20', + + axisLineColor: 'rgba(161, 161 ,229, 0.3)', + axisFontSize: '16', + axisTextColor: '#b2bac2', + yAxisSplitLine: 'rgba(161, 161 ,229, 0.2)', + + itemBorderColor: '#42db7d', + lineStyle: 'dotted', + lineWidth: '6', + lineGradFrom: '#ffffff', + lineGradTo: '#ffffff', + lineShadow: 'rgba(14, 16, 48, 0)', + + areaGradFrom: 'rgba(188, 92, 255, 1)', + areaGradTo: 'rgba(188, 92, 255, 0.5)', + shadowLineDarkBg: '#a695ff', + + innerLineStyle: 'solid', + innerLineWidth: '1', + + innerAreaGradFrom: 'rgba(60, 221, 156, 1)', + innerAreaGradTo: 'rgba(60, 221, 156, 1)', + }, + + visitorsLegend: { + firstIcon: 'linear-gradient(90deg, #3edd81 0%, #3bddad 100%)', + secondIcon: 'linear-gradient(90deg, #8d7fff 0%, #b17fff 100%)', + }, + + visitorsPie: { + firstPieGradientLeft: '#8defbb', + firstPieGradientRight: '#8defbb', + firstPieShadowColor: 'rgba(0, 0, 0, 0)', + firstPieRadius: ['70%', '90%'], + + secondPieGradientLeft: '#ff894a', + secondPieGradientRight: '#ffcc10', + secondPieShadowColor: 'rgba(0, 0, 0, 0)', + secondPieRadius: ['60%', '97%'], + shadowOffsetX: '0', + shadowOffsetY: '0', + }, + + visitorsPieLegend: { + firstSection: 'linear-gradient(90deg, #ffcb17 0%, #ff874c 100%)', + secondSection: '#8defbb', + }, + + earningPie: { + radius: ['65%', '100%'], + center: ['50%', '50%'], + + fontSize: '22', + + firstPieGradientLeft: '#00d77f', + firstPieGradientRight: '#00d77f', + firstPieShadowColor: 'rgba(0, 0, 0, 0)', + + secondPieGradientLeft: '#7756f7', + secondPieGradientRight: '#7756f7', + secondPieShadowColor: 'rgba(0, 0, 0, 0)', + + thirdPieGradientLeft: '#ffca00', + thirdPieGradientRight: '#ffca00', + thirdPieShadowColor: 'rgba(0, 0, 0, 0)', + }, + + earningLine: { + gradFrom: 'rgba(188, 92, 255, 0.5)', + gradTo: 'rgba(188, 92, 255, 0.5)', + + tooltipTextColor: '#2a2a2a;', + tooltipFontWeight: 'bolder', + tooltipFontSize: '16', + tooltipBg: '#ffffff', + tooltipBorderColor: '#c0c8d1', + tooltipBorderWidth: '3', + tooltipExtraCss: 'border-radius: 10px; padding: 4px 16px;', + }, + }, +}; diff --git a/packages/admin-web-angular/src/app/@theme/styles/theme.everdark.ts b/packages/admin-web-angular/src/app/@theme/styles/theme.everdark.ts new file mode 100644 index 0000000..49b04f6 --- /dev/null +++ b/packages/admin-web-angular/src/app/@theme/styles/theme.everdark.ts @@ -0,0 +1,305 @@ +export const EVERDARK_THEME = { + name: 'everdark', + base: 'default', + variables: { + temperature: ['#2ec7fe', '#31ffad', '#7bff24', '#fff024', '#f7bd59'], + + solar: { + gradientLeft: '#7bff24', + gradientRight: '#2ec7fe', + shadowColor: '#19977E', + radius: ['70%', '90%'], + }, + + traffic: { + colorBlack: '#000000', + tooltipBg: 'rgba(0, 255, 170, 0.35)', + tooltipBorderColor: '#00d977', + tooltipExtraCss: + 'box-shadow: 0px 2px 46px 0 rgba(0, 255, 170, 0.35); border-radius: 10px; padding: 4px 16px;', + tooltipTextColor: '#ffffff', + tooltipFontWeight: 'normal', + + lineBg: '#d1d1ff', + lineShadowBlur: '14', + itemColor: '#BEBBFF', + itemBorderColor: '#ffffff', + itemEmphasisBorderColor: '#ffffff', + shadowLineDarkBg: '#655ABD', + shadowLineShadow: 'rgba(33, 7, 77, 0.5)', + gradFrom: 'rgba(118, 89, 255, 0.4)', + gradTo: 'rgba(164, 84, 255, 0.5)', + }, + + electricity: { + tooltipBg: 'rgba(0, 255, 170, 0.35)', + tooltipLineColor: 'rgba(255, 255, 255, 0.1)', + tooltipLineWidth: '1', + tooltipBorderColor: '#00d977', + tooltipExtraCss: + 'box-shadow: 0px 2px 46px 0 rgba(0, 255, 170, 0.35); border-radius: 10px; padding: 8px 24px;', + tooltipTextColor: '#ffffff', + tooltipFontWeight: 'normal', + + axisLineColor: 'rgba(161, 161 ,229, 0.3)', + xAxisTextColor: '#a1a1e5', + yAxisSplitLine: 'rgba(161, 161 ,229, 0.2)', + + itemBorderColor: '#ffffff', + lineStyle: 'dotted', + lineWidth: '6', + lineGradFrom: '#00ffaa', + lineGradTo: '#fff835', + lineShadow: 'rgba(14, 16, 48, 0.4)', + + areaGradFrom: 'rgba(188, 92, 255, 0.5)', + areaGradTo: 'rgba(188, 92, 255, 0)', + shadowLineDarkBg: '#a695ff', + }, + + bubbleMap: { + titleColor: '#ffffff', + areaColor: '#2c2961', + areaHoverColor: '#a1a1e5', + areaBorderColor: '#654ddb', + }, + + profitBarAnimationEchart: { + textColor: '#ffffff', + + firstAnimationBarColor: '#0088ff', + secondAnimationBarColor: '#7659ff', + + splitLineStyleOpacity: '0.06', + splitLineStyleWidth: '1', + splitLineStyleColor: '#000000', + + tooltipTextColor: '#ffffff', + tooltipFontWeight: 'normal', + tooltipFontSize: '16', + tooltipBg: 'rgba(0, 255, 170, 0.35)', + tooltipBorderColor: '#00d977', + tooltipBorderWidth: '3', + tooltipExtraCss: + 'box-shadow: 0px 2px 46px 0 rgba(0, 255, 170, 0.35); border-radius: 10px; padding: 4px 16px;', + }, + + trafficBarEchart: { + gradientFrom: '#fc0', + gradientTo: '#ffa100', + shadow: '#ffb600', + shadowBlur: '5', + + axisTextColor: 'white', + axisFontSize: '12', + + tooltipBg: 'rgba(0, 255, 170, 0.35)', + tooltipBorderColor: '#00d977', + tooltipExtraCss: + 'box-shadow: 0px 2px 46px 0 rgba(0, 255, 170, 0.35); border-radius: 10px; padding: 4px 16px;', + tooltipTextColor: '#ffffff', + tooltipFontWeight: 'normal', + }, + + countryOrders: { + countryBorderColor: '#525dbd', + countryFillColor: '#4f41a6', + countryBorderWidth: '2', + hoveredCountryBorderColor: '#00f9a6', + hoveredCountryFillColor: '#377aa7', + hoveredCountryBorderWidth: '3', + + chartAxisLineColor: 'rgba(161, 161 ,229, 0.3)', + chartAxisTextColor: '#a1a1e5', + chartAxisFontSize: '16', + chartGradientTo: '#00c7c7', + chartGradientFrom: '#00d977', + chartAxisSplitLine: 'rgba(161, 161 ,229, 0.2)', + chartShadowBarColor: '#2f296b', + + chartLineBottomShadowColor: '#00977e', + + chartInnerLineColor: '#2f296b', + }, + + echarts: { + bg: '#3d3780', + textColor: '#ffffff', + axisLineColor: 'white', + splitLineColor: 'white', + itemHoverShadowColor: 'rgba(0, 0, 0, 0.5)', + tooltipBackgroundColor: '#6a7985', + areaOpacity: '1', + }, + + chartjs: { + axisLineColor: '#a1a1e5', + textColor: '#ffffff', + }, + + orders: { + tooltipBg: 'rgba(0, 255, 170, 0.35)', + tooltipLineColor: 'rgba(255, 255, 255, 0.1)', + tooltipLineWidth: '1', + tooltipBorderColor: '#00d977', + tooltipExtraCss: + 'box-shadow: 0px 2px 46px 0 rgba(0, 255, 170, 0.35); border-radius: 10px; padding: 8px 24px;', + tooltipTextColor: '#ffffff', + tooltipFontWeight: 'normal', + tooltipFontSize: '20', + + axisLineColor: 'rgba(161, 161 ,229, 0.3)', + axisFontSize: '16', + axisTextColor: 'white', + yAxisSplitLine: 'rgba(161, 161 ,229, 0.2)', + + itemBorderColor: '#ffffff', + lineStyle: 'solid', + lineWidth: '4', + + // first line + firstAreaGradFrom: 'transparent', + firstAreaGradTo: 'transparent', + firstShadowLineDarkBg: '#018dff', + + // second line + secondLineGradFrom: 'orange', + secondLineGradTo: 'red', + + secondAreaGradFrom: '#ce4843', + secondAreaGradTo: 'orange', + secondShadowLineDarkBg: '#2c5a85', + + // third line + thirdLineGradFrom: '#47d26f', + thirdLineGradTo: 'springgreen', + + thirdAreaGradFrom: 'rgba(131 ,126, 124, 0.7)', + thirdAreaGradTo: 'rgba(230, 226, 230, 0.5)', + thirdShadowLineDarkBg: '#a695ff', + }, + + profit: { + bg: 'transparent', + textColor: '#ffffff', + axisLineColor: '#fff', + splitLineColor: '#fff', + areaOpacity: '1', + + axisFontSize: '16', + axisTextColor: '#fff', + + // first bar + firstLineGradFrom: '#555', + firstLineGradTo: '#888', + firstLineShadow: 'rgba(14, 16, 48, 0.4)', + + // second bar + secondLineGradFrom: '#8069ff', + secondLineGradTo: '#8357ff', + secondLineShadow: 'rgba(14, 16, 48, 0.4)', + + // third bar + thirdLineGradFrom: '#4e40a4', + thirdLineGradTo: '#4e40a4', + thirdLineShadow: 'rgba(14, 16, 48, 0.4)', + }, + + orderProfitLegend: { + firstItem: + 'linear-gradient(90deg, #47d26f 0%, mediumspringgreen 100%)', + secondItem: 'linear-gradient(90deg, #ce4843 0%, orange 100%)', + thirdItem: 'linear-gradient(90deg, gray 0%, lightgray 100%)', + }, + + visitors: { + tooltipBg: 'rgba(0, 255, 170, 0.35)', + tooltipLineColor: 'rgba(255, 255, 255, 0.1)', + tooltipLineWidth: '1', + tooltipBorderColor: '#00d977', + tooltipExtraCss: + 'box-shadow: 0px 2px 46px 0 rgba(0, 255, 170, 0.35); border-radius: 10px; padding: 8px 24px;', + tooltipTextColor: '#ffffff', + tooltipFontWeight: 'normal', + + axisLineColor: 'rgba(161, 161 ,229, 0.3)', + axisFontSize: '16', + axisTextColor: '#a1a1e5', + yAxisSplitLine: 'rgba(161, 161 ,229, 0.2)', + + itemBorderColor: '#ffffff', + lineStyle: 'dotted', + lineWidth: '6', + lineGradFrom: '#ffffff', + lineGradTo: '#ffffff', + lineShadow: 'rgba(14, 16, 48, 0.4)', + + areaGradFrom: 'rgba(188, 92, 255, 1)', + areaGradTo: 'rgba(188, 92, 255, 0.5)', + shadowLineDarkBg: '#a695ff', + + innerLineStyle: 'solid', + innerLineWidth: '1', + + innerAreaGradFrom: 'rgba(59, 165, 243, 1)', + innerAreaGradTo: 'rgba(4, 133, 243 , 1)', + }, + + visitorsLegend: { + firstIcon: 'linear-gradient(90deg, #0088ff 0%, #3dafff 100%)', + secondIcon: 'linear-gradient(90deg, #a454ff 0%, #7659ff 100%)', + }, + + visitorsPie: { + firstPieGradientLeft: '#7bff24', + firstPieGradientRight: '#2ec7fe', + firstPieShadowColor: '#19977E', + firstPieRadius: ['70%', '90%'], + + secondPieGradientLeft: '#ff894a', + secondPieGradientRight: '#ffcc10', + secondPieShadowColor: '#cf7c1c', + secondPieRadius: ['60%', '95%'], + shadowOffsetX: '0', + shadowOffsetY: '3', + }, + + visitorsPieLegend: { + firstSection: 'linear-gradient(90deg, #ffcb17 0%, #ff874c 100%)', + secondSection: 'linear-gradient(90deg, #00c7c7 0%, #00d977 100%)', + }, + + earningPie: { + radius: ['65%', '100%'], + center: ['50%', '50%'], + + fontSize: '22', + + firstPieGradientLeft: '#00d77f', + firstPieGradientRight: '#00d77f', + firstPieShadowColor: 'rgba(0, 0, 0, 0)', + + secondPieGradientLeft: '#7756f7', + secondPieGradientRight: '#7756f7', + secondPieShadowColor: 'rgba(0, 0, 0, 0)', + + thirdPieGradientLeft: '#ffca00', + thirdPieGradientRight: '#ffca00', + thirdPieShadowColor: 'rgba(0, 0, 0, 0)', + }, + + earningLine: { + gradFrom: 'rgba(118, 89, 255, 0.4)', + gradTo: 'rgba(164, 84, 255, 0.5)', + + tooltipTextColor: '#ffffff', + tooltipFontWeight: 'normal', + tooltipFontSize: '16', + tooltipBg: 'rgba(0, 255, 170, 0.35)', + tooltipBorderColor: '#00d977', + tooltipBorderWidth: '3', + tooltipExtraCss: + 'box-shadow: 0px 2px 46px 0 rgba(0, 255, 170, 0.35); border-radius: 10px; padding: 4px 16px;', + }, + }, +}; diff --git a/packages/admin-web-angular/src/app/@theme/styles/theme.everlight.ts b/packages/admin-web-angular/src/app/@theme/styles/theme.everlight.ts new file mode 100644 index 0000000..599971a --- /dev/null +++ b/packages/admin-web-angular/src/app/@theme/styles/theme.everlight.ts @@ -0,0 +1,4 @@ +export const EVERLIGHT_THEME = { + name: 'everlight', + base: 'default', +}; diff --git a/packages/admin-web-angular/src/app/@theme/styles/themes.scss b/packages/admin-web-angular/src/app/@theme/styles/themes.scss new file mode 100644 index 0000000..0355322 --- /dev/null +++ b/packages/admin-web-angular/src/app/@theme/styles/themes.scss @@ -0,0 +1,150 @@ +// @nebular theming framework +@import '~@nebular/theme/styles/theming'; +// @nebular out of the box themes +@import '~@nebular/theme/styles/themes'; + +// which themes you what to enable (empty to enable all) +$nb-enabled-themes: (default, cosmic, corporate, everlight, everdark, dark); + +$nb-themes: nb-register-theme( + ( + // app wise variables for each theme + sidebar-header-gap: 2rem, + sidebar-header-height: initial, + layout-content-width: 1400px, + header-height: 5.45rem, + font-family-primary: Roboto, + font-family-secondary: Exo, + card-height-tiny: 13.5rem, + select-min-width: 6rem, + // + //switcher-background: #ebeff5, + //switcher-background-percentage: 50%, + //drops-icon-line-gadient: -webkit-linear-gradient(#01dbb5, #0bbb79), + // + //header-button-background: #f5f7fa, + //header-button-background-hover: #ebeff5, + //header-button-color: #2a2a2a, + //header-button-outline: #42db7d, + // + //list-item-border-width: 1px, + // + slide-out-background: linear-gradient(270deg, #edf1f7 0%, #e4e9f2 100%), + slide-out-shadow-color: 0 4px 14px 0 #8f9bb3, + slide-out-shadow-color-rtl: 0 4px 14px 0 #8f9bb3, + // + //chart-panel-summary-box-shadow: none, + //chart-panel-summary-background-color: #ecf2f5, + //chart-panel-summary-border-color: #ebeff1, + //chart-panel-summary-border-width: 1px, + // + //ecommerce-card-border-width: 1px, + // + //progress-bar-background: linear-gradient(90deg, #3edd81 0%, #3bddaf 100%), + ), + default, + default +); + +$nb-themes: nb-register-theme( + ( + card-height-tiny: 13.5rem, + select-min-width: 6rem, + // app wise variables for each theme + //sidebar-header-gap: 2rem, + //sidebar-header-height: initial, + //layout-content-width: 1400px, + //header-height: 5.45rem, + // + //font-main: Roboto, + //font-secondary: Exo, + // + //switcher-background: #4e41a5, + //switcher-background-percentage: 14%, + //drops-icon-line-gadient: -webkit-linear-gradient(#a258fe, #7958fa), + // + //header-button-background: #4e41a5, + //header-button-background-hover: #675cb2, + //header-button-color: #ffffff, + //header-button-outline: #7659ff, + // + //list-item-border-width: 1px, + // + slide-out-background: radial-gradient(circle, #5a37b8 0%, #7b51db 100%), + slide-out-shadow-color: 2px 0 3px #29157a, + slide-out-shadow-color-rtl: -2px 0 3px #29157a, + smart-table-bg-even: transparent, + smart-table-bg-active: rgba(0, 0, 0, 0.1), + // + //chart-panel-summary-box-shadow: none, + //chart-panel-summary-background-color: rgba(0, 0, 0, 0.1), + //chart-panel-summary-border-color: #332e73, + //chart-panel-summary-border-width: 1px, + // + //ecommerce-card-border-width: 1px, + // + //progress-bar-background: linear-gradient(90deg, #00c7c7 0%, #00d977 100%),,,, + ), + cosmic, + cosmic +); + +$nb-themes: nb-register-theme( + ( + card-height-tiny: 13.5rem, + select-min-width: 6rem, + // app wise variables for each theme + //sidebar-header-gap: 2rem, + //sidebar-header-height: initial, + //layout-content-width: 1400px, + //header-height: 5.45rem, + // + //font-main: Roboto, + //font-secondary: Exo, + // + //switcher-background: #2b2d34, + //switcher-background-percentage: 14%, + //drops-icon-line-gadient: -webkit-linear-gradient(#e9e8eb, #a7a2be), + // + //header-button-background: #2b2d34, + //header-button-background-hover: #494a50, + //header-button-color: #ffffff, + //header-button-outline: #a4abb3, + // + //list-item-border-width: 1px, + // + slide-out-background: linear-gradient(270deg, #edf1f7 0%, #e4e9f2 100%), + slide-out-shadow-color: 0 4px 14px 0 #8f9bb3, + slide-out-shadow-color-rtl: 0 4px 14px 0 #8f9bb3, + // + //chart-panel-summary-box-shadow: none, + //chart-panel-summary-background-color: #f7fafb, + //chart-panel-summary-border-color: #ebeff1, + //chart-panel-summary-border-width: 1px, + // + //ecommerce-card-border-width: 1px, + // + //progress-bar-background: linear-gradient(90deg, #ff9f6f 0%, #ff8b97 100%),,,, + ), + corporate, + corporate +); + +$nb-themes: nb-register-theme( + ( + card-height-tiny: 13.5rem, + select-min-width: 6rem, + slide-out-background: linear-gradient(270deg, #edf1f7 0%, #e4e9f2 100%), + slide-out-shadow-color: 0 4px 14px 0 #8f9bb3, + slide-out-shadow-color-rtl: 0 4px 14px 0 #8f9bb3, + smart-table-bg-even: transparent, + smart-table-bg-active: rgba(0, 0, 0, 0.1), + text-basic-color: #8b8b8b, + ), + dark, + dark +); + +// Ever themes +@import './everdark.theme.scss'; +@import './everlight.theme.scss'; diff --git a/packages/admin-web-angular/src/app/@theme/styles/toaster.css b/packages/admin-web-angular/src/app/@theme/styles/toaster.css new file mode 100644 index 0000000..6c68a01 --- /dev/null +++ b/packages/admin-web-angular/src/app/@theme/styles/toaster.css @@ -0,0 +1,287 @@ +.toaster-icon { + position: absolute; + left: 0em; + top: 0em; + font-weight: normal; + color: #ffffff; +} + +.toast-title { + font-weight: bold; +} + +.toast-message { + -ms-word-wrap: break-word; + word-wrap: break-word; +} + +.toast-message a, +.toast-message label { + color: #ffffff; +} + +.toast-message a:hover { + color: #cccccc; + text-decoration: none; +} + +.toast-close-button { + position: relative; + right: -0.3em; + top: -0.3em; + float: right; + font-size: 20px; + font-weight: bold; + color: #ffffff; + -webkit-text-shadow: 0 1px 0 #ffffff; + text-shadow: 0 1px 0 #ffffff; + opacity: 0.8; + -ms-filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=80); + filter: alpha(opacity=80); + z-index: 999; +} + +.toast-close-button:hover, +.toast-close-button:focus { + color: #000000; + text-decoration: none; + cursor: pointer; + opacity: 0.4; + -ms-filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=40); + filter: alpha(opacity=40); +} + +/*Additional properties for button version + iOS requires the button element instead of an anchor tag. + If you want the anchor version, it requires `href="#"`.*/ +button.toast-close-button { + padding: 0; + cursor: pointer; + background: transparent; + border: 0; + -webkit-appearance: none; +} + +.toast-content { + display: inline-block; + width: 95%; +} + +.toast-top-full-width { + top: 0; + right: 0; + width: 100%; +} + +.toast-bottom-full-width { + bottom: 0; + right: 0; + width: 100%; +} + +.toast-top-left { + top: 12px; + left: 12px; +} + +.toast-top-center { + top: 12px; +} + +.toast-top-right { + top: 12px; + right: 12px; +} + +.toast-bottom-right { + right: 12px; + bottom: 12px; +} + +.toast-bottom-center { + bottom: 12px; +} + +.toast-bottom-left { + bottom: 12px; + left: 12px; +} + +.toast-center { + top: 45%; +} + +#toast-container { + position: fixed; + z-index: 999999; + /*overrides*/ + pointer-events: auto; +} + +#toast-container.toast-center, +#toast-container.toast-top-center, +#toast-container.toast-bottom-center { + width: 100%; + pointer-events: none; + left: 0; + right: 0; +} + +#toast-container.toast-center > div, +#toast-container.toast-top-center > div, +#toast-container.toast-bottom-center > div { + margin: 6px auto; + pointer-events: auto; +} + +#toast-container.toast-center > button, +#toast-container.toast-top-center > button, +#toast-container.toast-bottom-center > button { + pointer-events: auto; +} + +#toast-container * { + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + box-sizing: border-box; +} + +#toast-container > div { + margin: 0 0 6px; + padding: 15px 15px 15px 50px; + width: 300px; + -moz-border-radius: 3px 3px 3px 3px; + -webkit-border-radius: 3px 3px 3px 3px; + border-radius: 3px 3px 3px 3px; + background-position: 15px center; + background-repeat: no-repeat; + -moz-box-shadow: 0 0 12px #999999; + -webkit-box-shadow: 0 0 12px #999999; + box-shadow: 0 0 12px #999999; + color: #ffffff; + opacity: 0.8; + -ms-filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=80); + filter: alpha(opacity=80); +} + +#toast-container > :hover { + -moz-box-shadow: 0 0 12px #000000; + -webkit-box-shadow: 0 0 12px #000000; + box-shadow: 0 0 12px #000000; + opacity: 1; + -ms-filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=100); + filter: alpha(opacity=100); + cursor: pointer; +} + +.icon-success { + width: 35px; + height: 100%; + display: inline-block; + background-repeat: no-repeat; + background-position: 100% 50%; + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAADsSURBVEhLY2AYBfQMgf///3P8+/evAIgvA/FsIF+BavYDDWMBGroaSMMBiE8VC7AZDrIFaMFnii3AZTjUgsUUWUDA8OdAH6iQbQEhw4HyGsPEcKBXBIC4ARhex4G4BsjmweU1soIFaGg/WtoFZRIZdEvIMhxkCCjXIVsATV6gFGACs4Rsw0EGgIIH3QJYJgHSARQZDrWAB+jawzgs+Q2UO49D7jnRSRGoEFRILcdmEMWGI0cm0JJ2QpYA1RDvcmzJEWhABhD/pqrL0S0CWuABKgnRki9lLseS7g2AlqwHWQSKH4oKLrILpRGhEQCw2LiRUIa4lwAAAABJRU5ErkJggg==') !important; +} + +.icon-error { + width: 35px; + height: 100%; + display: inline-block; + background-repeat: no-repeat; + background-position: 100% 50%; + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAHOSURBVEhLrZa/SgNBEMZzh0WKCClSCKaIYOED+AAKeQQLG8HWztLCImBrYadgIdY+gIKNYkBFSwu7CAoqCgkkoGBI/E28PdbLZmeDLgzZzcx83/zZ2SSXC1j9fr+I1Hq93g2yxH4iwM1vkoBWAdxCmpzTxfkN2RcyZNaHFIkSo10+8kgxkXIURV5HGxTmFuc75B2RfQkpxHG8aAgaAFa0tAHqYFfQ7Iwe2yhODk8+J4C7yAoRTWI3w/4klGRgR4lO7Rpn9+gvMyWp+uxFh8+H+ARlgN1nJuJuQAYvNkEnwGFck18Er4q3egEc/oO+mhLdKgRyhdNFiacC0rlOCbhNVz4H9FnAYgDBvU3QIioZlJFLJtsoHYRDfiZoUyIxqCtRpVlANq0EU4dApjrtgezPFad5S19Wgjkc0hNVnuF4HjVA6C7QrSIbylB+oZe3aHgBsqlNqKYH48jXyJKMuAbiyVJ8KzaB3eRc0pg9VwQ4niFryI68qiOi3AbjwdsfnAtk0bCjTLJKr6mrD9g8iq/S/B81hguOMlQTnVyG40wAcjnmgsCNESDrjme7wfftP4P7SP4N3CJZdvzoNyGq2c/HWOXJGsvVg+RA/k2MC/wN6I2YA2Pt8GkAAAAASUVORK5CYII=') !important; +} + +.icon-info { + width: 35px; + height: 100%; + display: inline-block; + background-repeat: no-repeat; + background-position: 100% 50%; + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAGwSURBVEhLtZa9SgNBEMc9sUxxRcoUKSzSWIhXpFMhhYWFhaBg4yPYiWCXZxBLERsLRS3EQkEfwCKdjWJAwSKCgoKCcudv4O5YLrt7EzgXhiU3/4+b2ckmwVjJSpKkQ6wAi4gwhT+z3wRBcEz0yjSseUTrcRyfsHsXmD0AmbHOC9Ii8VImnuXBPglHpQ5wwSVM7sNnTG7Za4JwDdCjxyAiH3nyA2mtaTJufiDZ5dCaqlItILh1NHatfN5skvjx9Z38m69CgzuXmZgVrPIGE763Jx9qKsRozWYw6xOHdER+nn2KkO+Bb+UV5CBN6WC6QtBgbRVozrahAbmm6HtUsgtPC19tFdxXZYBOfkbmFJ1VaHA1VAHjd0pp70oTZzvR+EVrx2Ygfdsq6eu55BHYR8hlcki+n+kERUFG8BrA0BwjeAv2M8WLQBtcy+SD6fNsmnB3AlBLrgTtVW1c2QN4bVWLATaIS60J2Du5y1TiJgjSBvFVZgTmwCU+dAZFoPxGEEs8nyHC9Bwe2GvEJv2WXZb0vjdyFT4Cxk3e/kIqlOGoVLwwPevpYHT+00T+hWwXDf4AJAOUqWcDhbwAAAAASUVORK5CYII=') !important; +} + +.icon-wait { + width: 35px; + height: 100%; + display: inline-block; + background-repeat: no-repeat; + background-position: 100% 50%; + background-image: url('data:image/gif;base64,R0lGODlhIAAgAIQAAAQCBISGhMzKzERCROTm5CQiJKyurHx+fPz+/ExOTOzu7Dw+PIyOjCwqLFRWVAwKDIyKjMzOzOzq7CQmJLy6vFRSVPTy9AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH/C05FVFNDQVBFMi4wAwEAAAAh+QQJCQAXACwAAAAAIAAgAAAF3eAljmRpnmh6VRSVqLDpIDTixOdUlFSNUDhSQUAT7ES9GnD0SFQAKWItMqr4bqKHVPDI+WiTkaOFFVlrFe83rDrT0qeIjwrT0iLdU0GOiBxhAA4VeSk6QYeIOAsQEAuJKgw+EI8nA18IA48JBAQvFxCXDI8SNAQikV+iiaQIpheWX5mJmxKeF6g0qpQmA4yOu8C7EwYWCgZswRcTFj4KyMAGlwYxDwcHhCXMXxYxBzQHKNo+3DDeCOAn0V/TddbYJA0K48gAEAFQicMWFsfwNA3JSgAIAAFfwIMIL4QAACH5BAkJABoALAAAAAAgACAAhAQCBIyKjERCRMzOzCQiJPTy9DQyNGRmZMTCxOTm5CwqLHx+fBQWFJyenNTW1Pz6/Dw6PGxubAwKDIyOjNTS1CQmJCwuLPz+/Dw+PHRydAAAAAAAAAAAAAAAAAAAAAAAAAXboCaOZGmeaKoxWcSosMkk15W8cZ7VdZaXkcEgQtrxfD9RhHchima1GwlCGUBSFCaFxMrgRtnLFhWujWHhs2nJc8KoVlWGQnEn7/i8XgOwWAB7JwoONQ4KgSQAZRcOgHgSCwsSIhZMNRZ5CzULIgaWF5h4mhecfIQ8jXmQkiODhYeIiRYGjrG2PxgBARi3IhNMAbcCnwI5BAQpAZ8TIwK6vCQVDwUVKL+WzAANTA210g/VJ8OWxQefByQE4dZMzBoInwh4zrtgn2p725YNthUFTNRuGYB3AYGBHCEAACH5BAkJAB0ALAAAAAAgACAAhAQCBISChFRWVMzKzCQiJOTm5GxqbCwuLJSWlPz6/NTW1AwODJSSlGRmZCwqLOzu7HR2dDQ2NAQGBISGhFxaXNTS1CQmJOzq7GxubDQyNKSmpPz+/Nza3AAAAAAAAAAAAAXfYCeOZGmeaKqurHBdAiuP17Zdc0lMAVHWt9yI8LA9fCPB4xEjARoNSWpis01kBpshFahurqzsZosiGpErScMAUO0maKF8Tq/bTQCIQgFp30cQXhB1BHEcXhx0FgkJFiOHVYlzi42AgoRxeRx8fn+en3UABwedKgsBAwMBCygOCjYKDisLFV4VrCUAtVUKpSZdXl8mB8EbByQWcQPFAyYZxccdB7sV0cvBzbmvvG0LBV4FrFTBYCWuNhyyHRTFFB20trh4BxmdYl4YIqepq0IRxRE+IfDCAFQHARo0NGERAgAh+QQJCQAgACwAAAAAIAAgAIUEAgSEgoRMTkzMyswcHhzk5uR0cnQUFhRcXlwsKiz09vQMCgyMiozU1tQkJiR8fnxkZmT8/vwEBgSEhoRcWlzU0tQkIiT08vR0dnQcGhxkYmQ0MjT8+vwMDgyMjozc2twAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAG+UCQcEgsGo/IpHLJXDweC6Z0+IhEHlOjRGIMWLHZoUZx0RQlAajxkFFKFFYFl5m5KNpIySU+X2bIBEoQZBBZGQdMElFhjI2Oj5AgHQEDAw8dQxYeDBaNHRVWVhWYCXsRFwmMXqFWEyAerB6MA6xWA6+xs7URt6VWqIwTu64gDh4eDp6goaORQ5OVAZjO1EgEGhB4RwAYDQ0YAEwIcBEKFEgYrBhLBORxgUYfrB9LELuF8fNDAAaVBuEg7NXCVyRdqHVCGLBiIIQAB1Yc4BXh9uEbwAXuyi2iQI7DuSwHdiFqCEGDtizLRFUDsaGAlQIbVoJYIEDAIiZBAAAh+QQJCQAbACwAAAAAIAAgAIQEAgSMioxcWlz08vQcHhysqqwMDgx8enwsKiykoqRkZmT8+vzEwsQMCgyUlpQkJiS0srQEBgSMjoxcXlz09vQkIiSsrqwUEhQ0MjRsamz8/vwAAAAAAAAAAAAAAAAAAAAF7+AmjmRpnmiqruz2PG0sIssCj4CQJAIgj4/abRNJaI6agu9kCAQaphdJgEQKUIFjgGWsahJYLdf7RTWfLKr3+jsBClVlG5Xb9eb4fImgUBBKDVB4ExRHFGwbGRQLGXMEhUgUfw2QC4IyCmSNDQtHlm2ZXgoiGQsUjW0EnUgLfyKBeYSeiHojfH61uS0GBisVEgEVLRcWRxAXKAgDRwMILMVIECgSVRIrBmS9JtRI1iMVBweuGxerSNolyszOIhjLGs0jEFXSKA8SEkMbcEgWIxfzNBxrw6AKgxIGkM05UOWALhERHJhysOThBgAVWYQAACH5BAkJABkALAAAAAAgACAAhAQGBIyKjERCRMzOzCwuLGRiZPz6/OTm5AwODLSytFRSVNTW1Dw6PHx6fAwKDJSSlERGRNTS1DQyNGxqbPz+/BQSFLy6vFRWVNza3AAAAAAAAAAAAAAAAAAAAAAAAAAAAAXqYCaO5FgFwxBUZeu61ULNFMa+eBvQdJD/owFvFhkBBAwHsBQZUooZyWF2YOQkBNJu6ANMaQeli0AxSEwymi0DcUJeEgPlbEJFAghRe/h+Eeg/Dl9UYks5DF9VhksOAgKFi5GSSwh5kzgVCXIJNxknD5aSCTwJIw8zD5MITpanFKmSCHI8NxUPoJejNKWXLZkznL0vCJ3CxsckDpA/ChYJFzkTBgYTSxc80C4OswbLLhY8Fi/bMwYAJVgl4DTiL9LUJADrFuci1zTZLwD1IwU8BSQuWLCQb1EDHg2QiSDALYvCDAISJLDy8FIIACH5BAkJAB4ALAAAAAAgACAAhAQGBISGhFRSVNTW1CQiJKyqrGRmZOzu7CwuLIyOjGxubPz6/BQSFGRiZOTi5CwqLLy6vDQ2NIyKjFRWVCQmJKyurGxqbPT29DQyNJSSlHRydPz+/BQWFOzq7AAAAAAAAAXhoCeOJElYClGubOs117YtjWuvxCLLi3qbhc6h4FPsdorfiNI5dige43GT9AAkHUcCwCpMNxVP7tgTJY4J1uF7EBl0M8Ooueuo2SOCIkVa11kVX2E2EmgsFH4yBz4uAAkdHVstBAUHQ4xKmZqbnJ2bAhAQAiURGJ4eE0cTIxgzpp0QRxCsrp6xO7MjpaepO6unKxOhv8DFxsfIJBwaChw2DAkZDEocDjIOzi0ZMhlKUjIaLtsb3T8aR+EtDBkJ0yQUBQVQI9XX2ZsDMgMlyxr3mzE2XEgmotCGAARFIHiQ0FMIACH5BAkJABgALAAAAAAgACAAhAQCBISGhDw+POTi5CwuLLS2tPTy9BQSFJyenGRiZDQ2NIyOjLy+vPz6/BweHIyKjFRSVOzq7DQyNLy6vBQWFHRydDw6PPz+/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXXICaOZHkcZaquIjVd10SxtFrAcFGrVhBYIwoON9uNAsOA6DCEFTEKBEKxEjQvAtELNxkpGrAGNfW4Plpb2QgxRKjKzfPoVGLj3CnLNUv7hscpSDhKOxJSgDwPP0ZGAACMjAQFDQYFBJA0BAZDBpeYGBQVFUU3TV2YFAMwAzNgTQ2PkBVDFRiuQ7CYszi1pUOnkKmrM5qcnqiiTwQTDQ2Wn9DR0tPUfRKQEBEREDQSFw3XRhEwEd3f4TvjF+XWKgJ8JNnb0QkwCdUlCzAL+CQODAwc9BtIMAQAOw==') !important; +} + +.icon-warning { + width: 35px; + height: 100%; + display: inline-block; + background-repeat: no-repeat; + background-position: 100% 50%; + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAGYSURBVEhL5ZSvTsNQFMbXZGICMYGYmJhAQIJAICYQPAACiSDB8AiICQQJT4CqQEwgJvYASAQCiZiYmJhAIBATCARJy+9rTsldd8sKu1M0+dLb057v6/lbq/2rK0mS/TRNj9cWNAKPYIJII7gIxCcQ51cvqID+GIEX8ASG4B1bK5gIZFeQfoJdEXOfgX4QAQg7kH2A65yQ87lyxb27sggkAzAuFhbbg1K2kgCkB1bVwyIR9m2L7PRPIhDUIXgGtyKw575yz3lTNs6X4JXnjV+LKM/m3MydnTbtOKIjtz6VhCBq4vSm3ncdrD2lk0VgUXSVKjVDJXJzijW1RQdsU7F77He8u68koNZTz8Oz5yGa6J3H3lZ0xYgXBK2QymlWWA+RWnYhskLBv2vmE+hBMCtbA7KX5drWyRT/2JsqZ2IvfB9Y4bWDNMFbJRFmC9E74SoS0CqulwjkC0+5bpcV1CZ8NMej4pjy0U+doDQsGyo1hzVJttIjhQ7GnBtRFN1UarUlH8F3xict+HY07rEzoUGPlWcjRFRr4/gChZgc3ZL2d8oAAAAASUVORK5CYII=') !important; +} + +#toast-container.toast-top-full-width > div, +#toast-container.toast-bottom-full-width > div { + width: 96%; + margin: auto; +} + +.toast { + position: relative; + background-color: #030303; +} + +.toast-success { + background-color: #51a351; +} + +.toast-error { + background-color: #bd362f; +} + +.toast-info { + background-color: #2f96b4; +} + +.toast-wait { + background-color: #2f96b4; +} + +.toast-warning { + background-color: #f89406; +} + +/*Responsive Design*/ +@media all and (max-width: 240px) { + #toast-container > div { + padding: 8px 8px 8px 50px; + width: 11em; + } + #toast-container .toast-close-button { + right: -0.1em; + top: -0.2em; + } + .toast-content { + width: 94%; + } +} + +@media all and (min-width: 241px) and (max-width: 480px) { + #toast-container > div { + padding: 8px 8px 8px 50px; + width: 18em; + } + #toast-container .toast-close-button { + right: -0.1em; + top: -0.2em; + } + .toast-content { + width: 94%; + } +} + +@media all and (min-width: 481px) and (max-width: 768px) { + #toast-container > div { + padding: 15px 15px 15px 50px; + width: 25em; + } +} diff --git a/packages/admin-web-angular/src/app/@theme/styles/toaster.min.css b/packages/admin-web-angular/src/app/@theme/styles/toaster.min.css new file mode 100644 index 0000000..0925691 --- /dev/null +++ b/packages/admin-web-angular/src/app/@theme/styles/toaster.min.css @@ -0,0 +1,243 @@ +.toaster-icon { + position: absolute; + left: 0; + top: 0; + font-weight: normal; + color: #fff; +} +.toast-title { + font-weight: bold; +} +.toast-message { + -ms-word-wrap: break-word; + word-wrap: break-word; +} +.toast-message a, +.toast-message label { + color: #fff; +} +.toast-message a:hover { + color: #ccc; + text-decoration: none; +} +.toast-close-button { + position: relative; + right: -0.3em; + top: -0.3em; + float: right; + font-size: 20px; + font-weight: bold; + color: #fff; + -webkit-text-shadow: 0 1px 0 #fff; + text-shadow: 0 1px 0 #fff; + opacity: 0.8; + -ms-filter: alpha(opacity=80); + filter: alpha(opacity=80); + z-index: 999; +} +.toast-close-button:hover, +.toast-close-button:focus { + color: #000; + text-decoration: none; + cursor: pointer; + opacity: 0.4; + -ms-filter: alpha(opacity=40); + filter: alpha(opacity=40); +} +button.toast-close-button { + padding: 0; + cursor: pointer; + background: transparent; + border: 0; + -webkit-appearance: none; +} +.toast-content { + display: inline-block; + width: 95%; +} +.toast-top-full-width { + top: 0; + right: 0; + width: 100%; +} +.toast-bottom-full-width { + bottom: 0; + right: 0; + width: 100%; +} +.toast-top-left { + top: 12px; + left: 12px; +} +.toast-top-center { + top: 12px; +} +.toast-top-right { + top: 12px; + right: 12px; +} +.toast-bottom-right { + right: 12px; + bottom: 12px; +} +.toast-bottom-center { + bottom: 12px; +} +.toast-bottom-left { + bottom: 12px; + left: 12px; +} +.toast-center { + top: 45%; +} +#toast-container { + position: fixed; + z-index: 999999; + pointer-events: auto; +} +#toast-container.toast-center, +#toast-container.toast-top-center, +#toast-container.toast-bottom-center { + width: 100%; + pointer-events: none; + left: 0; + right: 0; +} +#toast-container.toast-center > div, +#toast-container.toast-top-center > div, +#toast-container.toast-bottom-center > div { + margin: 6px auto; + pointer-events: auto; +} +#toast-container.toast-center > button, +#toast-container.toast-top-center > button, +#toast-container.toast-bottom-center > button { + pointer-events: auto; +} +#toast-container * { + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + box-sizing: border-box; +} +#toast-container > div { + margin: 0 0 6px; + padding: 15px 15px 15px 50px; + width: 300px; + -moz-border-radius: 3px 3px 3px 3px; + -webkit-border-radius: 3px 3px 3px 3px; + border-radius: 3px 3px 3px 3px; + background-position: 15px center; + background-repeat: no-repeat; + -moz-box-shadow: 0 0 12px #999; + -webkit-box-shadow: 0 0 12px #999; + box-shadow: 0 0 12px #999; + color: #fff; + opacity: 0.8; + -ms-filter: alpha(opacity=80); + filter: alpha(opacity=80); +} +#toast-container > :hover { + -moz-box-shadow: 0 0 12px #000; + -webkit-box-shadow: 0 0 12px #000; + box-shadow: 0 0 12px #000; + opacity: 1; + -ms-filter: alpha(opacity=100); + filter: alpha(opacity=100); + cursor: pointer; +} +.icon-success { + width: 35px; + height: 100%; + display: inline-block; + background-repeat: no-repeat; + background-position: 100% 50%; + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAADsSURBVEhLY2AYBfQMgf///3P8+/evAIgvA/FsIF+BavYDDWMBGroaSMMBiE8VC7AZDrIFaMFnii3AZTjUgsUUWUDA8OdAH6iQbQEhw4HyGsPEcKBXBIC4ARhex4G4BsjmweU1soIFaGg/WtoFZRIZdEvIMhxkCCjXIVsATV6gFGACs4Rsw0EGgIIH3QJYJgHSARQZDrWAB+jawzgs+Q2UO49D7jnRSRGoEFRILcdmEMWGI0cm0JJ2QpYA1RDvcmzJEWhABhD/pqrL0S0CWuABKgnRki9lLseS7g2AlqwHWQSKH4oKLrILpRGhEQCw2LiRUIa4lwAAAABJRU5ErkJggg==') !important; +} +.icon-error { + width: 35px; + height: 100%; + display: inline-block; + background-repeat: no-repeat; + background-position: 100% 50%; + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAHOSURBVEhLrZa/SgNBEMZzh0WKCClSCKaIYOED+AAKeQQLG8HWztLCImBrYadgIdY+gIKNYkBFSwu7CAoqCgkkoGBI/E28PdbLZmeDLgzZzcx83/zZ2SSXC1j9fr+I1Hq93g2yxH4iwM1vkoBWAdxCmpzTxfkN2RcyZNaHFIkSo10+8kgxkXIURV5HGxTmFuc75B2RfQkpxHG8aAgaAFa0tAHqYFfQ7Iwe2yhODk8+J4C7yAoRTWI3w/4klGRgR4lO7Rpn9+gvMyWp+uxFh8+H+ARlgN1nJuJuQAYvNkEnwGFck18Er4q3egEc/oO+mhLdKgRyhdNFiacC0rlOCbhNVz4H9FnAYgDBvU3QIioZlJFLJtsoHYRDfiZoUyIxqCtRpVlANq0EU4dApjrtgezPFad5S19Wgjkc0hNVnuF4HjVA6C7QrSIbylB+oZe3aHgBsqlNqKYH48jXyJKMuAbiyVJ8KzaB3eRc0pg9VwQ4niFryI68qiOi3AbjwdsfnAtk0bCjTLJKr6mrD9g8iq/S/B81hguOMlQTnVyG40wAcjnmgsCNESDrjme7wfftP4P7SP4N3CJZdvzoNyGq2c/HWOXJGsvVg+RA/k2MC/wN6I2YA2Pt8GkAAAAASUVORK5CYII=') !important; +} +.icon-info { + width: 35px; + height: 100%; + display: inline-block; + background-repeat: no-repeat; + background-position: 100% 50%; + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAGwSURBVEhLtZa9SgNBEMc9sUxxRcoUKSzSWIhXpFMhhYWFhaBg4yPYiWCXZxBLERsLRS3EQkEfwCKdjWJAwSKCgoKCcudv4O5YLrt7EzgXhiU3/4+b2ckmwVjJSpKkQ6wAi4gwhT+z3wRBcEz0yjSseUTrcRyfsHsXmD0AmbHOC9Ii8VImnuXBPglHpQ5wwSVM7sNnTG7Za4JwDdCjxyAiH3nyA2mtaTJufiDZ5dCaqlItILh1NHatfN5skvjx9Z38m69CgzuXmZgVrPIGE763Jx9qKsRozWYw6xOHdER+nn2KkO+Bb+UV5CBN6WC6QtBgbRVozrahAbmm6HtUsgtPC19tFdxXZYBOfkbmFJ1VaHA1VAHjd0pp70oTZzvR+EVrx2Ygfdsq6eu55BHYR8hlcki+n+kERUFG8BrA0BwjeAv2M8WLQBtcy+SD6fNsmnB3AlBLrgTtVW1c2QN4bVWLATaIS60J2Du5y1TiJgjSBvFVZgTmwCU+dAZFoPxGEEs8nyHC9Bwe2GvEJv2WXZb0vjdyFT4Cxk3e/kIqlOGoVLwwPevpYHT+00T+hWwXDf4AJAOUqWcDhbwAAAAASUVORK5CYII=') !important; +} +.icon-wait { + width: 35px; + height: 100%; + display: inline-block; + background-repeat: no-repeat; + background-position: 100% 50%; + background-image: url('data:image/gif;base64,R0lGODlhIAAgAIQAAAQCBISGhMzKzERCROTm5CQiJKyurHx+fPz+/ExOTOzu7Dw+PIyOjCwqLFRWVAwKDIyKjMzOzOzq7CQmJLy6vFRSVPTy9AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH/C05FVFNDQVBFMi4wAwEAAAAh+QQJCQAXACwAAAAAIAAgAAAF3eAljmRpnmh6VRSVqLDpIDTixOdUlFSNUDhSQUAT7ES9GnD0SFQAKWItMqr4bqKHVPDI+WiTkaOFFVlrFe83rDrT0qeIjwrT0iLdU0GOiBxhAA4VeSk6QYeIOAsQEAuJKgw+EI8nA18IA48JBAQvFxCXDI8SNAQikV+iiaQIpheWX5mJmxKeF6g0qpQmA4yOu8C7EwYWCgZswRcTFj4KyMAGlwYxDwcHhCXMXxYxBzQHKNo+3DDeCOAn0V/TddbYJA0K48gAEAFQicMWFsfwNA3JSgAIAAFfwIMIL4QAACH5BAkJABoALAAAAAAgACAAhAQCBIyKjERCRMzOzCQiJPTy9DQyNGRmZMTCxOTm5CwqLHx+fBQWFJyenNTW1Pz6/Dw6PGxubAwKDIyOjNTS1CQmJCwuLPz+/Dw+PHRydAAAAAAAAAAAAAAAAAAAAAAAAAXboCaOZGmeaKoxWcSosMkk15W8cZ7VdZaXkcEgQtrxfD9RhHchima1GwlCGUBSFCaFxMrgRtnLFhWujWHhs2nJc8KoVlWGQnEn7/i8XgOwWAB7JwoONQ4KgSQAZRcOgHgSCwsSIhZMNRZ5CzULIgaWF5h4mhecfIQ8jXmQkiODhYeIiRYGjrG2PxgBARi3IhNMAbcCnwI5BAQpAZ8TIwK6vCQVDwUVKL+WzAANTA210g/VJ8OWxQefByQE4dZMzBoInwh4zrtgn2p725YNthUFTNRuGYB3AYGBHCEAACH5BAkJAB0ALAAAAAAgACAAhAQCBISChFRWVMzKzCQiJOTm5GxqbCwuLJSWlPz6/NTW1AwODJSSlGRmZCwqLOzu7HR2dDQ2NAQGBISGhFxaXNTS1CQmJOzq7GxubDQyNKSmpPz+/Nza3AAAAAAAAAAAAAXfYCeOZGmeaKqurHBdAiuP17Zdc0lMAVHWt9yI8LA9fCPB4xEjARoNSWpis01kBpshFahurqzsZosiGpErScMAUO0maKF8Tq/bTQCIQgFp30cQXhB1BHEcXhx0FgkJFiOHVYlzi42AgoRxeRx8fn+en3UABwedKgsBAwMBCygOCjYKDisLFV4VrCUAtVUKpSZdXl8mB8EbByQWcQPFAyYZxccdB7sV0cvBzbmvvG0LBV4FrFTBYCWuNhyyHRTFFB20trh4BxmdYl4YIqepq0IRxRE+IfDCAFQHARo0NGERAgAh+QQJCQAgACwAAAAAIAAgAIUEAgSEgoRMTkzMyswcHhzk5uR0cnQUFhRcXlwsKiz09vQMCgyMiozU1tQkJiR8fnxkZmT8/vwEBgSEhoRcWlzU0tQkIiT08vR0dnQcGhxkYmQ0MjT8+vwMDgyMjozc2twAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAG+UCQcEgsGo/IpHLJXDweC6Z0+IhEHlOjRGIMWLHZoUZx0RQlAajxkFFKFFYFl5m5KNpIySU+X2bIBEoQZBBZGQdMElFhjI2Oj5AgHQEDAw8dQxYeDBaNHRVWVhWYCXsRFwmMXqFWEyAerB6MA6xWA6+xs7URt6VWqIwTu64gDh4eDp6goaORQ5OVAZjO1EgEGhB4RwAYDQ0YAEwIcBEKFEgYrBhLBORxgUYfrB9LELuF8fNDAAaVBuEg7NXCVyRdqHVCGLBiIIQAB1Yc4BXh9uEbwAXuyi2iQI7DuSwHdiFqCEGDtizLRFUDsaGAlQIbVoJYIEDAIiZBAAAh+QQJCQAbACwAAAAAIAAgAIQEAgSMioxcWlz08vQcHhysqqwMDgx8enwsKiykoqRkZmT8+vzEwsQMCgyUlpQkJiS0srQEBgSMjoxcXlz09vQkIiSsrqwUEhQ0MjRsamz8/vwAAAAAAAAAAAAAAAAAAAAF7+AmjmRpnmiqruz2PG0sIssCj4CQJAIgj4/abRNJaI6agu9kCAQaphdJgEQKUIFjgGWsahJYLdf7RTWfLKr3+jsBClVlG5Xb9eb4fImgUBBKDVB4ExRHFGwbGRQLGXMEhUgUfw2QC4IyCmSNDQtHlm2ZXgoiGQsUjW0EnUgLfyKBeYSeiHojfH61uS0GBisVEgEVLRcWRxAXKAgDRwMILMVIECgSVRIrBmS9JtRI1iMVBweuGxerSNolyszOIhjLGs0jEFXSKA8SEkMbcEgWIxfzNBxrw6AKgxIGkM05UOWALhERHJhysOThBgAVWYQAACH5BAkJABkALAAAAAAgACAAhAQGBIyKjERCRMzOzCwuLGRiZPz6/OTm5AwODLSytFRSVNTW1Dw6PHx6fAwKDJSSlERGRNTS1DQyNGxqbPz+/BQSFLy6vFRWVNza3AAAAAAAAAAAAAAAAAAAAAAAAAAAAAXqYCaO5FgFwxBUZeu61ULNFMa+eBvQdJD/owFvFhkBBAwHsBQZUooZyWF2YOQkBNJu6ANMaQeli0AxSEwymi0DcUJeEgPlbEJFAghRe/h+Eeg/Dl9UYks5DF9VhksOAgKFi5GSSwh5kzgVCXIJNxknD5aSCTwJIw8zD5MITpanFKmSCHI8NxUPoJejNKWXLZkznL0vCJ3CxsckDpA/ChYJFzkTBgYTSxc80C4OswbLLhY8Fi/bMwYAJVgl4DTiL9LUJADrFuci1zTZLwD1IwU8BSQuWLCQb1EDHg2QiSDALYvCDAISJLDy8FIIACH5BAkJAB4ALAAAAAAgACAAhAQGBISGhFRSVNTW1CQiJKyqrGRmZOzu7CwuLIyOjGxubPz6/BQSFGRiZOTi5CwqLLy6vDQ2NIyKjFRWVCQmJKyurGxqbPT29DQyNJSSlHRydPz+/BQWFOzq7AAAAAAAAAXhoCeOJElYClGubOs117YtjWuvxCLLi3qbhc6h4FPsdorfiNI5dige43GT9AAkHUcCwCpMNxVP7tgTJY4J1uF7EBl0M8Ooueuo2SOCIkVa11kVX2E2EmgsFH4yBz4uAAkdHVstBAUHQ4xKmZqbnJ2bAhAQAiURGJ4eE0cTIxgzpp0QRxCsrp6xO7MjpaepO6unKxOhv8DFxsfIJBwaChw2DAkZDEocDjIOzi0ZMhlKUjIaLtsb3T8aR+EtDBkJ0yQUBQVQI9XX2ZsDMgMlyxr3mzE2XEgmotCGAARFIHiQ0FMIACH5BAkJABgALAAAAAAgACAAhAQCBISGhDw+POTi5CwuLLS2tPTy9BQSFJyenGRiZDQ2NIyOjLy+vPz6/BweHIyKjFRSVOzq7DQyNLy6vBQWFHRydDw6PPz+/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXXICaOZHkcZaquIjVd10SxtFrAcFGrVhBYIwoON9uNAsOA6DCEFTEKBEKxEjQvAtELNxkpGrAGNfW4Plpb2QgxRKjKzfPoVGLj3CnLNUv7hscpSDhKOxJSgDwPP0ZGAACMjAQFDQYFBJA0BAZDBpeYGBQVFUU3TV2YFAMwAzNgTQ2PkBVDFRiuQ7CYszi1pUOnkKmrM5qcnqiiTwQTDQ2Wn9DR0tPUfRKQEBEREDQSFw3XRhEwEd3f4TvjF+XWKgJ8JNnb0QkwCdUlCzAL+CQODAwc9BtIMAQAOw==') !important; +} +.icon-warning { + width: 35px; + height: 100%; + display: inline-block; + background-repeat: no-repeat; + background-position: 100% 50%; + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAGYSURBVEhL5ZSvTsNQFMbXZGICMYGYmJhAQIJAICYQPAACiSDB8AiICQQJT4CqQEwgJvYASAQCiZiYmJhAIBATCARJy+9rTsldd8sKu1M0+dLb057v6/lbq/2rK0mS/TRNj9cWNAKPYIJII7gIxCcQ51cvqID+GIEX8ASG4B1bK5gIZFeQfoJdEXOfgX4QAQg7kH2A65yQ87lyxb27sggkAzAuFhbbg1K2kgCkB1bVwyIR9m2L7PRPIhDUIXgGtyKw575yz3lTNs6X4JXnjV+LKM/m3MydnTbtOKIjtz6VhCBq4vSm3ncdrD2lk0VgUXSVKjVDJXJzijW1RQdsU7F77He8u68koNZTz8Oz5yGa6J3H3lZ0xYgXBK2QymlWWA+RWnYhskLBv2vmE+hBMCtbA7KX5drWyRT/2JsqZ2IvfB9Y4bWDNMFbJRFmC9E74SoS0CqulwjkC0+5bpcV1CZ8NMej4pjy0U+doDQsGyo1hzVJttIjhQ7GnBtRFN1UarUlH8F3xict+HY07rEzoUGPlWcjRFRr4/gChZgc3ZL2d8oAAAAASUVORK5CYII=') !important; +} +#toast-container.toast-top-full-width > div, +#toast-container.toast-bottom-full-width > div { + width: 96%; + margin: auto; +} +.toast { + position: relative; + background-color: #030303; +} +.toast-success { + background-color: #51a351; +} +.toast-error { + background-color: #bd362f; +} +.toast-info { + background-color: #2f96b4; +} +.toast-wait { + background-color: #2f96b4; +} +.toast-warning { + background-color: #f89406; +} +@media all and (max-width: 240px) { + #toast-container > div { + padding: 8px 8px 8px 50px; + width: 11em; + } + #toast-container .toast-close-button { + right: -0.1em; + top: -0.2em; + } + .toast-content { + width: 94%; + } +} +@media all and (min-width: 241px) and (max-width: 480px) { + #toast-container > div { + padding: 8px 8px 8px 50px; + width: 18em; + } + #toast-container .toast-close-button { + right: -0.1em; + top: -0.2em; + } + .toast-content { + width: 94%; + } +} +@media all and (min-width: 481px) and (max-width: 768px) { + #toast-container > div { + padding: 15px 15px 15px 50px; + width: 25em; + } +} diff --git a/packages/admin-web-angular/src/app/@theme/theme.module.ts b/packages/admin-web-angular/src/app/@theme/theme.module.ts new file mode 100644 index 0000000..5b2fa42 --- /dev/null +++ b/packages/admin-web-angular/src/app/@theme/theme.module.ts @@ -0,0 +1,152 @@ +import { ModuleWithProviders, NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; + +import { + NbActionsModule, + NbCardModule, + NbLayoutModule, + NbMenuModule, + NbRouteTabsetModule, + NbSearchModule, + NbSidebarModule, + NbTabsetModule, + NbThemeModule, + NbUserModule, + NbCheckboxModule, + NbPopoverModule, + NbContextMenuModule, + NbProgressBarModule, + NbIconModule, + NbSelectModule, + NbRadioModule, +} from '@nebular/theme'; + +import { NbEvaIconsModule } from '@nebular/eva-icons'; +import { NbSecurityModule } from '@nebular/security'; + +import { + FooterComponent, + HeaderComponent, + LayoutDirectionSwitcherComponent, + SearchInputComponent, + SwitcherComponent, + ThemeSettingsComponent, + ThemeSwitcherComponent, + ThemeSwitcherListComponent, + TinyMCEComponent, +} from './components'; + +import { + OneColumnLayoutComponent, + ThreeColumnsLayoutComponent, + TwoColumnsLayoutComponent, +} from './layouts'; + +import { DEFAULT_THEME } from './styles/theme.default'; +import { COSMIC_THEME } from './styles/theme.cosmic'; +import { CORPORATE_THEME } from './styles/theme.corporate'; +import { EVERDARK_THEME } from './styles/theme.everdark'; +import { EVERLIGHT_THEME } from './styles/theme.everlight'; + +import { HttpClient, HttpClientModule } from '@angular/common/http'; +import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; +import { TranslateHttpLoader } from '@ngx-translate/http-loader'; +import { AdminsService } from '../@core/data/admins.service'; +import { PipesModule } from '@modules/client.common.angular2/pipes/pipes.module'; +import { DARK_THEME } from './styles/theme.dark'; +import { SampleLayoutComponent } from './layouts/sample/sample.layout'; + +export function HttpLoaderFactory(http: HttpClient) { + return new TranslateHttpLoader(http, './assets/i18n/', '.json'); +} + +const BASE_MODULES = [CommonModule, FormsModule, ReactiveFormsModule]; + +const NB_MODULES = [ + NbCardModule, + NbLayoutModule, + NbTabsetModule, + NbRouteTabsetModule, + NbMenuModule, + NbUserModule, + NbActionsModule, + NbSearchModule, + NbSidebarModule, + NbCheckboxModule, + NbPopoverModule, + NbContextMenuModule, + NgbModule, + NbSecurityModule, // *nbIsGranted directive + NbProgressBarModule, + NbIconModule, + NbEvaIconsModule, + NbSelectModule, + NbRadioModule, +]; + +const COMPONENTS = [ + SwitcherComponent, + LayoutDirectionSwitcherComponent, + ThemeSwitcherComponent, + ThemeSwitcherListComponent, + HeaderComponent, + FooterComponent, + SearchInputComponent, + ThemeSettingsComponent, + TinyMCEComponent, + OneColumnLayoutComponent, + SampleLayoutComponent, + ThreeColumnsLayoutComponent, + TwoColumnsLayoutComponent, +]; + +const ENTRY_COMPONENTS = [ThemeSwitcherListComponent]; + +const NB_THEME_PROVIDERS = [ + ...NbThemeModule.forRoot( + { + name: 'everlight', + }, + [ + DEFAULT_THEME, + COSMIC_THEME, + CORPORATE_THEME, + EVERDARK_THEME, + EVERLIGHT_THEME, + DARK_THEME, + ] + ).providers, + ...NbSidebarModule.forRoot().providers, + ...NbMenuModule.forRoot().providers, + AdminsService, +]; + +@NgModule({ + imports: [ + ...BASE_MODULES, + ...NB_MODULES, + HttpClientModule, + TranslateModule.forChild({ + loader: { + provide: TranslateLoader, + useFactory: HttpLoaderFactory, + deps: [HttpClient], + }, + }), + PipesModule, + ], + exports: [...BASE_MODULES, ...NB_MODULES, ...COMPONENTS], + declarations: [...COMPONENTS] +}) +export class ThemeModule { + static forRoot(): ModuleWithProviders { + const providers: ModuleWithProviders = { + ngModule: ThemeModule, + providers: [...NB_THEME_PROVIDERS], + }; + + return providers; + } +} diff --git a/packages/admin-web-angular/src/app/app-routing.module.ts b/packages/admin-web-angular/src/app/app-routing.module.ts new file mode 100644 index 0000000..1d953fe --- /dev/null +++ b/packages/admin-web-angular/src/app/app-routing.module.ts @@ -0,0 +1,80 @@ +import { ExtraOptions, RouterModule, Routes } from '@angular/router'; +import { NgModule } from '@angular/core'; +import { + NbAuthComponent, + NbLoginComponent, + NbLogoutComponent, + NbRegisterComponent, + NbRequestPasswordComponent, + NbResetPasswordComponent, +} from '@nebular/auth'; +import { AdminAuthGuard } from './@core/auth/admin-auth.guard'; +import { AppModuleGuard } from './app.module.guard'; +import { MaintenanceModuleGuard } from './pages/+maintenance-info/maintenance-info.module.guard'; + +const routes: Routes = [ + { + path: '', + loadChildren: () => + import('app/pages/pages.module').then((m) => m.PagesModule), + canActivate: [AppModuleGuard, AdminAuthGuard], + }, + { + path: 'auth', + component: NbAuthComponent, + canActivate: [AppModuleGuard], + children: [ + { + path: '', + component: NbLoginComponent, + }, + { + path: 'login', + component: NbLoginComponent, + }, + { + path: 'register', + component: NbRegisterComponent, + }, + { + path: 'logout', + component: NbLogoutComponent, + }, + { + path: 'request-password', + component: NbRequestPasswordComponent, + }, + { + path: 'reset-password', + component: NbResetPasswordComponent, + }, + ], + }, + { + path: 'maintenance-info', + loadChildren: () => + import('app/pages/+maintenance-info/maintenance-info.module').then( + (m) => m.MaintenanceInfoModule + ), + canActivate: [MaintenanceModuleGuard], + }, + { + path: 'server-down', + loadChildren: () => + import('app/pages/+server-down/server-down.module').then( + (m) => m.ServerDownModule + ), + }, + { path: '**', redirectTo: '' }, +]; + +const config: ExtraOptions = { + useHash: true, + enableTracing: true, +}; + +@NgModule({ + imports: [RouterModule.forRoot(routes, config)], + exports: [RouterModule], +}) +export class AppRoutingModule {} diff --git a/packages/admin-web-angular/src/app/app.component.ts b/packages/admin-web-angular/src/app/app.component.ts new file mode 100644 index 0000000..988c29f --- /dev/null +++ b/packages/admin-web-angular/src/app/app.component.ts @@ -0,0 +1,19 @@ +/** + * @license + * Copyright Akveo. All Rights Reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ +import { Component, OnInit } from '@angular/core'; +import { AnalyticsService } from './@core/utils/analytics.service'; + +@Component({ + selector: 'ea-app', + template: '', +}) +export class AppComponent implements OnInit { + constructor(private analytics: AnalyticsService) {} + + ngOnInit(): void { + this.analytics.trackPageViews(); + } +} diff --git a/packages/admin-web-angular/src/app/app.module.guard.ts b/packages/admin-web-angular/src/app/app.module.guard.ts new file mode 100644 index 0000000..2d05e1d --- /dev/null +++ b/packages/admin-web-angular/src/app/app.module.guard.ts @@ -0,0 +1,34 @@ +import { Injectable } from '@angular/core'; +import { + Router, + CanActivate, + ActivatedRouteSnapshot, + RouterStateSnapshot, +} from '@angular/router'; +import { Store } from './@core/data/store.service'; + +@Injectable() +export class AppModuleGuard implements CanActivate { + constructor(private readonly router: Router, private store: Store) {} + + canActivate( + route: ActivatedRouteSnapshot, + state: RouterStateSnapshot + ): boolean { + const maintenanceMode = this.store.maintenanceMode; + + if (maintenanceMode) { + this.router.navigate(['maintenance-info']); + return false; + } + + const serverConnection = Number(this.store.serverConnection); + + if (serverConnection === 0) { + this.router.navigate(['server-down']); + return false; + } + + return true; + } +} diff --git a/packages/admin-web-angular/src/app/app.module.ts b/packages/admin-web-angular/src/app/app.module.ts new file mode 100644 index 0000000..d5b92cf --- /dev/null +++ b/packages/admin-web-angular/src/app/app.module.ts @@ -0,0 +1,203 @@ +import { NgModule, APP_INITIALIZER } from '@angular/core'; +import { APP_BASE_HREF } from '@angular/common'; +import { BrowserModule } from '@angular/platform-browser'; +import { FormsModule } from '@angular/forms'; +import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; +import { HttpClient, HttpClientModule } from '@angular/common/http'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { FormWizardModule } from '@ever-co/angular2-wizard'; +import { SimpleTimer } from 'ng2-simple-timer'; +import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; +import { ToasterModule } from 'angular2-toaster'; +import { Apollo } from 'apollo-angular'; +import { HttpLink, HttpLinkHandler } from 'apollo-angular/http'; +import { ApolloLink, InMemoryCache } from '@apollo/client/core'; +import { WebSocketLink } from '@apollo/client/link/ws'; +import { setContext } from '@apollo/client/link/context'; +import { NgxEchartsModule } from 'ngx-echarts'; +import { getOperationAST } from 'graphql/utilities/getOperationAST'; +import { CoreModule } from './@core/core.module'; +import { ThemeModule } from './@theme/theme.module'; +import { CommonModule } from '@modules/client.common.angular2/common.module'; +import { AppComponent } from './app.component'; +import { AppRoutingModule } from './app-routing.module'; +import { environment } from 'environments/environment'; +import { Store } from './@core/data/store.service'; +import { GoogleMapsLoader } from '@modules/client.common.angular2/services/googleMapsLoader'; +import { MaintenanceService } from '@modules/client.common.angular2/services/maintenance.service'; +import { AppModuleGuard } from './app.module.guard'; +import { MaintenanceModuleGuard } from './pages/+maintenance-info/maintenance-info.module.guard'; +import { ServerConnectionService } from '@modules/client.common.angular2/services/server-connection.service'; +import { ServerSettingsService } from './@core/services/server-settings.service'; +import { NbEvaIconsModule } from '@nebular/eva-icons'; +import { HttpLoaderFactory } from './@shared/translate/translate.module'; +import { HighlightModule, HIGHLIGHT_OPTIONS } from 'ngx-highlightjs'; + +// It's more 'standard' way to use Font-Awesome module and special package, +// but for some reason ngx-admin works without it. So we leave next line commented for now. +// import { FontAwesomeModule } from '@fortawesome/angular-fontawesome'; +@NgModule({ + imports: [ + BrowserModule, + FormsModule, + FormWizardModule, + // See comment above about Font-Awesome in ngx-admin + // FontAwesomeModule, + ToasterModule.forRoot(), + BrowserAnimationsModule, + HttpClientModule, + AppRoutingModule, + TranslateModule.forRoot({ + loader: { + provide: TranslateLoader, + useFactory: HttpLoaderFactory, + deps: [HttpClient], + }, + }), + CommonModule.forRoot({ apiUrl: environment.SERVICES_ENDPOINT }), + NgbModule, + NbEvaIconsModule, + ThemeModule.forRoot(), + CoreModule.forRoot(), + NgxEchartsModule.forRoot({ + /** + * This will import all modules from echarts. + * If you only need custom modules, + * please refer to [Custom Build] section. + */ + echarts: () => import('echarts') + }), + HighlightModule + ], + declarations: [AppComponent], + bootstrap: [AppComponent], + providers: [ + ServerConnectionService, + { + provide: APP_INITIALIZER, + useFactory: serverConnectionFactory, + deps: [ServerConnectionService, Store], + multi: true, + }, + GoogleMapsLoader, + { + provide: APP_INITIALIZER, + useFactory: googleMapsLoaderFactory, + deps: [GoogleMapsLoader], + multi: true, + }, + MaintenanceService, + { + provide: APP_INITIALIZER, + useFactory: maintenanceFactory, + deps: [MaintenanceService], + multi: true, + }, + ServerSettingsService, + { + provide: APP_INITIALIZER, + useFactory: serverSettingsFactory, + deps: [ServerSettingsService], + multi: true, + }, + { provide: APP_BASE_HREF, useValue: '/' }, + SimpleTimer, + AppModuleGuard, + MaintenanceModuleGuard, + { + provide: HIGHLIGHT_OPTIONS, + useValue: { + fullLibraryLoader: () => import('highlight.js') + } + } + ], +}) +export class AppModule { + constructor( + private apollo: Apollo, + private httpLink: HttpLink, + private store: Store + ) { + // Create an http link: + const http: HttpLinkHandler = httpLink.create({ + uri: environment.GQL_ENDPOINT, + }); + + // Create a WebSocket link: + const ws: WebSocketLink = new WebSocketLink({ + uri: environment.GQL_SUBSCRIPTIONS_ENDPOINT, + options: { + reconnect: true, + lazy: true, + }, + }); + + const authLink: ApolloLink = setContext((_, { headers }) => { + // get the authentication token from local storage if it exists + const token = store.token; + // return the headers to the context so httpLink can read them + return { + headers: { + ...headers, + authorization: token ? `Bearer ${token}` : '', + }, + }; + }); + + apollo.create({ + link: authLink.concat( + ApolloLink.split( + (operation) => { + const operationAST = getOperationAST( + operation.query, + operation.operationName + ); + return ( + !!operationAST && + operationAST.operation === 'subscription' + ); + }, + ws, + http + ) + ), + defaultOptions: { + watchQuery: { + fetchPolicy: 'network-only', + errorPolicy: 'ignore', + }, + query: { + fetchPolicy: 'network-only', + errorPolicy: 'all', + }, + mutate: { + errorPolicy: 'all', + }, + }, + cache: new InMemoryCache(), + }); + } +} + +export function googleMapsLoaderFactory(provider: GoogleMapsLoader) { + return () => provider.load(environment.GOOGLE_MAPS_API_KEY); +} + +export function serverConnectionFactory( + provider: ServerConnectionService, + store: Store +) { + return () => provider.load(environment.SERVICES_ENDPOINT, store); +} + +export function maintenanceFactory(provider: MaintenanceService) { + return () => + provider.load( + environment['SETTINGS_APP_TYPE'], + environment['SETTINGS_MAINTENANCE_API_URL'] + ); +} + +export function serverSettingsFactory(provider: ServerSettingsService) { + return () => provider.load(); +} diff --git a/packages/admin-web-angular/src/app/models/DashboardInfoViewModel.ts b/packages/admin-web-angular/src/app/models/DashboardInfoViewModel.ts new file mode 100644 index 0000000..9cb8bc8 --- /dev/null +++ b/packages/admin-web-angular/src/app/models/DashboardInfoViewModel.ts @@ -0,0 +1,10 @@ +export class DashboardInfoViewModel { + constructor() { + this.customers = 0; + this.orders = 0; + this.revenue = 0; + } + customers: number; + orders: number; + revenue: number; +} diff --git a/packages/admin-web-angular/src/app/models/DashboardLoadingIndicatorState.ts b/packages/admin-web-angular/src/app/models/DashboardLoadingIndicatorState.ts new file mode 100644 index 0000000..c350716 --- /dev/null +++ b/packages/admin-web-angular/src/app/models/DashboardLoadingIndicatorState.ts @@ -0,0 +1,22 @@ +export class DashboardLoadingIndicatorState { + constructor( + public totalInfo: IStoreInfoState = { + customers: true, + orders: true, + revenue: true, + }, + public todayInfo: IStoreInfoState = { + customers: true, + orders: true, + revenue: true, + }, + public chartPanelSummary: boolean = true, + public chart: boolean = true + ) {} +} + +interface IStoreInfoState { + customers: boolean; + orders: boolean; + revenue: boolean; +} diff --git a/packages/admin-web-angular/src/app/models/FakeDataBtnState.ts b/packages/admin-web-angular/src/app/models/FakeDataBtnState.ts new file mode 100644 index 0000000..0c7ecfd --- /dev/null +++ b/packages/admin-web-angular/src/app/models/FakeDataBtnState.ts @@ -0,0 +1,36 @@ +export class FakeDataBtnState { + constructor( + public all: boolean = false, + public hardcoded: boolean = false, + public clearAll: boolean = false, + public invite1: boolean = false, + public invite2: boolean = false, + public invite3: boolean = false, + public invite4: boolean = false, + public user: boolean = false, + public users100: boolean = false, + public carrier1: boolean = false, + public carrier2: boolean = false, + public carrier3: boolean = false, + public carriers100: boolean = false, + public product1: boolean = false, + public product2: boolean = false, + public product3: boolean = false, + public product4: boolean = false, + public product5: boolean = false, + public product6: boolean = false, + public warehouse1: boolean = false, + public warehouse2: boolean = false, + public warehouse3: boolean = false, + public warehouse100: boolean = false, + public warehouseProduct1: boolean = false, + public warehouseProduct2: boolean = false, + public warehouseProduct3: boolean = false, + public warehouseGeoLocation: boolean = false, + public createOrder1: boolean = false, + public createOrder2: boolean = false, + public create1000Orders: boolean = false, + public confirmOrder1: boolean = false, + public confirmOrder2: boolean = false + ) {} +} diff --git a/packages/admin-web-angular/src/app/models/IExistingCustomersViewModel.ts b/packages/admin-web-angular/src/app/models/IExistingCustomersViewModel.ts new file mode 100644 index 0000000..bacca3d --- /dev/null +++ b/packages/admin-web-angular/src/app/models/IExistingCustomersViewModel.ts @@ -0,0 +1,4 @@ +export interface IExistingCustomersViewModel { + total: number; + perStore: Array<{ storeId: string; customersCount: number }>; +} diff --git a/packages/admin-web-angular/src/app/models/WarehouseViewModel.ts b/packages/admin-web-angular/src/app/models/WarehouseViewModel.ts new file mode 100644 index 0000000..62ae606 --- /dev/null +++ b/packages/admin-web-angular/src/app/models/WarehouseViewModel.ts @@ -0,0 +1,10 @@ +export interface WarehouseViewModel { + id: string; + name: string; + image: string; + email: string; + phone: string; + city: string; + address: string; + ordersQty: number; +} diff --git a/packages/admin-web-angular/src/app/pages/+carriers/+carrier/+carrier-edit/carrier-edit.component.html b/packages/admin-web-angular/src/app/pages/+carriers/+carrier/+carrier-edit/carrier-edit.component.html new file mode 100644 index 0000000..5b20ec3 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+carriers/+carrier/+carrier-edit/carrier-edit.component.html @@ -0,0 +1,118 @@ + + +
+ + + +
+ + + + +

{{ 'CARRIERS_VIEW.EDIT.EDIT_CARRIER' | translate }}

+
+ +
+
+ +
+
+ + + + +
+ {{ + 'CARRIERS_VIEW.EDIT.BASIC_INFO' | translate + }} +
+
+ + + + +
+
+ + + + +
+ {{ 'CARRIERS_VIEW.EDIT.LOCATION' | translate }} +
+ + + + +
+ + + + + + + + + + +
+
+
+
+
+
diff --git a/packages/admin-web-angular/src/app/pages/+carriers/+carrier/+carrier-edit/carrier-edit.component.scss b/packages/admin-web-angular/src/app/pages/+carriers/+carrier/+carrier-edit/carrier-edit.component.scss new file mode 100644 index 0000000..4cc9eef --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+carriers/+carrier/+carrier-edit/carrier-edit.component.scss @@ -0,0 +1,21 @@ +@import '../../../../@theme/styles/themes'; +@import '../../../../@shared/styles/control-icon.shared'; + +@include nb-install-component() { + @include control-icon; +} + +nb-card-header { + min-height: 71px; + position: relative; + + .text { + position: absolute; + top: 50%; + transform: translateY(-50%); + } + + #map-type { + float: right; + } +} diff --git a/packages/admin-web-angular/src/app/pages/+carriers/+carrier/+carrier-edit/carrier-edit.component.ts b/packages/admin-web-angular/src/app/pages/+carriers/+carrier/+carrier-edit/carrier-edit.component.ts new file mode 100644 index 0000000..97bce14 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+carriers/+carrier/+carrier-edit/carrier-edit.component.ts @@ -0,0 +1,126 @@ +import { Component, OnInit, ViewChild, EventEmitter } from '@angular/core'; +import { FormBuilder, FormControl, FormGroup } from '@angular/forms'; +import { ActivatedRoute, Router } from '@angular/router'; +import { BasicInfoFormComponent } from '../../../../@shared/carrier/forms'; +import { LocationFormComponent } from '../../../../@shared/forms/location'; +import { CarrierRouter } from '@modules/client.common.angular2/routers/carrier-router.service'; +import Carrier from '@modules/server.common/entities/Carrier'; +import IGeoLocation from '@modules/server.common/interfaces/IGeoLocation'; +import { ToasterService } from 'angular2-toaster'; +import { map, first } from 'rxjs/operators'; + +@Component({ + selector: 'ea-carrier-edit', + templateUrl: './carrier-edit.component.html', + styleUrls: ['./carrier-edit.component.scss'], +}) +export class CarrierEditComponent implements OnInit { + @ViewChild('basicInfoForm') + basicInfoForm: BasicInfoFormComponent; + + @ViewChild('locationForm') + locationForm: LocationFormComponent; + + mapTypeEmitter = new EventEmitter(); + mapCoordEmitter = new EventEmitter(); + mapGeometryEmitter = new EventEmitter(); + + public loading: boolean; + + readonly form: FormGroup = this.formBuilder.group({ + basicInfo: BasicInfoFormComponent.buildForm(this.formBuilder), + location: LocationFormComponent.buildForm(this.formBuilder), + apartment: LocationFormComponent.buildApartmentForm(this.formBuilder), + }); + + readonly basicInfo = this.form.get('basicInfo') as FormControl; + readonly location = this.form.get('location') as FormControl; + readonly apartment = this.form.get('apartment') as FormControl; + + readonly carrierId$ = this.activatedRoute.params.pipe(map((p) => p['id'])); + + public currentCarrier: Carrier; + + constructor( + private readonly toasterService: ToasterService, + private readonly activatedRoute: ActivatedRoute, + private readonly formBuilder: FormBuilder, + private readonly carrierRouter: CarrierRouter, + private readonly router: Router + ) {} + + get isCarrierValid() { + return this.basicInfo.valid && this.location.valid; + } + + ngOnInit() { + const id = this.activatedRoute.snapshot.params.id; + + this.carrierRouter + .get(id) + .pipe(first()) + .subscribe((carrier) => { + if (!carrier) { + this.toasterService.pop( + 'error', + `Carrier with id ${id} doesn't exist!` + ); + } + + this.currentCarrier = carrier; + + // GeoJSON use reversed order for coordinates from our locationForm. + // we use lat => lng but GeoJSON use lng => lat. + const geoLocationInput = carrier.geoLocation; + geoLocationInput.loc.coordinates.reverse(); + + this.basicInfoForm.setValue(carrier); + this.locationForm.setValue(geoLocationInput); + this.locationForm.setApartment(carrier.apartment); + }); + } + + onCoordinatesChanges(coords: number[]) { + this.mapCoordEmitter.emit(coords); + } + + onGeometrySend(geometry: any) { + this.mapGeometryEmitter.emit(geometry); + } + + emitMapType(mapType: string) { + this.mapTypeEmitter.emit(mapType); + } + + async updateCarrier() { + try { + const basicInfo = this.basicInfoForm.getValue(); + basicInfo['apartment'] = this.apartment.value; + + // GeoJSON use reversed order for coordinates from our implementation. + // we use lat => lng but GeoJSON use lng => lat. + const geoLocationInput = this.locationForm.getValue(); + geoLocationInput.loc.coordinates.reverse(); + + this.loading = true; + const carrier = await this.carrierRouter.updateById( + this.currentCarrier.id, + { + ...basicInfo, + geoLocation: geoLocationInput as IGeoLocation, + } + ); + this.loading = false; + this.toasterService.pop( + 'success', + `Carrier ${carrier.firstName} was updated` + ); + } catch (err) { + this.loading = false; + this.toasterService.pop( + 'error', + `Error in updating carrier: "${err.message}"` + ); + } + } +} diff --git a/packages/admin-web-angular/src/app/pages/+carriers/+carrier/+carrier-edit/carrier-edit.module.ts b/packages/admin-web-angular/src/app/pages/+carriers/+carrier/+carrier-edit/carrier-edit.module.ts new file mode 100644 index 0000000..b1833d8 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+carriers/+carrier/+carrier-edit/carrier-edit.module.ts @@ -0,0 +1,28 @@ +import { NgModule } from '@angular/core'; +import { CarrierEditComponent } from './carrier-edit.component'; +import { ThemeModule } from '../../../../@theme/theme.module'; +import { TranslateModule } from '@ngx-translate/core'; +import { LocationFormModule } from '../../../../@shared/forms/location'; +import { CarrierFormsModule } from '../../../../@shared/carrier/forms'; +import { GoogleMapModule } from '../../../../@shared/forms/google-map/google-map.module'; +import { RouterModule } from '@angular/router'; +import { routes } from './carrier-edit.routes'; +import { ToasterModule } from 'angular2-toaster'; +import { NbSpinnerModule, NbButtonModule } from '@nebular/theme'; + +@NgModule({ + imports: [ + ThemeModule, + TranslateModule.forChild(), + ToasterModule.forRoot(), + RouterModule.forChild(routes), + CarrierFormsModule, + LocationFormModule, + GoogleMapModule, + NbSpinnerModule, + NbButtonModule, + ], + exports: [CarrierEditComponent], + declarations: [CarrierEditComponent], +}) +export class CarrierEditModule {} diff --git a/packages/admin-web-angular/src/app/pages/+carriers/+carrier/+carrier-edit/carrier-edit.routes.ts b/packages/admin-web-angular/src/app/pages/+carriers/+carrier/+carrier-edit/carrier-edit.routes.ts new file mode 100644 index 0000000..8c2a810 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+carriers/+carrier/+carrier-edit/carrier-edit.routes.ts @@ -0,0 +1,9 @@ +import { Routes } from '@angular/router'; +import { CarrierEditComponent } from './carrier-edit.component'; + +export const routes: Routes = [ + { + path: '', + component: CarrierEditComponent, + }, +]; diff --git a/packages/admin-web-angular/src/app/pages/+carriers/+carrier/+carrier-edit/index.ts b/packages/admin-web-angular/src/app/pages/+carriers/+carrier/+carrier-edit/index.ts new file mode 100644 index 0000000..2462742 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+carriers/+carrier/+carrier-edit/index.ts @@ -0,0 +1,2 @@ +export * from './carrier-edit.component'; +export * from './carrier-edit.module'; diff --git a/packages/admin-web-angular/src/app/pages/+carriers/+carrier/carrier-info.component.html b/packages/admin-web-angular/src/app/pages/+carriers/+carrier/carrier-info.component.html new file mode 100644 index 0000000..0017f71 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+carriers/+carrier/carrier-info.component.html @@ -0,0 +1,36 @@ + + + +
+
+		
+	
+
diff --git a/packages/admin-web-angular/src/app/pages/+carriers/+carrier/carrier-info.component.ts b/packages/admin-web-angular/src/app/pages/+carriers/+carrier/carrier-info.component.ts new file mode 100644 index 0000000..a98150c --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+carriers/+carrier/carrier-info.component.ts @@ -0,0 +1,39 @@ +import { Component, OnInit } from '@angular/core'; +import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; +import { CarriersService } from '@app/@core/data/carriers.service'; +import Carrier from '@modules/server.common/entities/Carrier'; +import { first } from 'rxjs/operators'; +import { Router } from '@angular/router'; + +@Component({ + templateUrl: './carrier-info.component.html', + styles: ['.carrier-redirect { cursor: pointer; margin-left: 5px}'], +}) +export class CarrierTableInfoComponent implements OnInit { + public carrierId: string; + public carrierData: Carrier | any = {}; + + constructor( + private readonly activeModal: NgbActiveModal, + private readonly carrierService: CarriersService, + private readonly router: Router + ) {} + + cancel() { + this.activeModal.dismiss('canceled'); + } + + ngOnInit(): void { + this.carrierService + .getCarrierById(this.carrierId) + .pipe(first()) + .subscribe((data) => { + this.carrierData = data; + }); + } + + redirectToCarrierPage() { + this.router.navigate([`/carriers/${this.carrierId}`]); + this.cancel(); + } +} diff --git a/packages/admin-web-angular/src/app/pages/+carriers/+carrier/carrier-info/carrier-info.component.html b/packages/admin-web-angular/src/app/pages/+carriers/+carrier/carrier-info/carrier-info.component.html new file mode 100644 index 0000000..fa86188 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+carriers/+carrier/carrier-info/carrier-info.component.html @@ -0,0 +1,72 @@ + + + {{ carrier.firstName }} {{ carrier.firstName && carrier.lastName }} + {{ carrier.firstName ? '' : '# ' + carrier.id }} + + {{ + 'CARRIERS_VIEW.CARRIER_PAGE.ACTIVE' | translate + }} + {{ 'CARRIERS_VIEW.CARRIER_PAGE.NOT_ACTIVE' | translate }} + + + + + + + + +
+
+ + {{ carrier.firstName }} {{ carrier.lastName }} + +
+
+ {{ carrier.geoLocation.streetAddress }}, + {{ carrier.geoLocation.house }} +
+
+ {{ carrier.geoLocation.city }} + {{ carrier.geoLocation.postcode }}, + {{ carrier.geoLocation.countryName }} +
+
+ + +
+ +
+
+
diff --git a/packages/admin-web-angular/src/app/pages/+carriers/+carrier/carrier-info/carrier-info.component.scss b/packages/admin-web-angular/src/app/pages/+carriers/+carrier/carrier-info/carrier-info.component.scss new file mode 100644 index 0000000..18e9605 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+carriers/+carrier/carrier-info/carrier-info.component.scss @@ -0,0 +1,34 @@ +nb-card-footer { + padding: 0; +} + +.carrierStatus { + margin-top: 10px; + width: 100% !important; +} + +.isActive { + display: inline-block; + border-radius: 5px; + + .label-is-active { + font-size: 75%; + font-weight: bold; + line-height: 1; + color: rgb(255, 255, 255); + text-align: center; + white-space: nowrap; + vertical-align: baseline; + padding: 0.2em 0.6em 0.3em; + border-radius: 0.25em; + border-right: 5px; + } + + .label-warning { + background: #ffa100; + } + + .label-success { + background: #40dc7e; + } +} diff --git a/packages/admin-web-angular/src/app/pages/+carriers/+carrier/carrier-info/carrier-info.component.ts b/packages/admin-web-angular/src/app/pages/+carriers/+carrier/carrier-info/carrier-info.component.ts new file mode 100644 index 0000000..5eb98fc --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+carriers/+carrier/carrier-info/carrier-info.component.ts @@ -0,0 +1,32 @@ +import { Component, Input, Output, EventEmitter } from '@angular/core'; +import Carrier from '@modules/server.common/entities/Carrier'; +import CarrierStatus from '@modules/server.common/enums/CarrierStatus'; +import { CarrierRouter } from '@modules/client.common.angular2/routers/carrier-router.service'; + +@Component({ + selector: 'ea-carrier-info', + styleUrls: ['carrier-info.component.scss'], + templateUrl: 'carrier-info.component.html', +}) +export class CarrierInfoComponent { + @Input() + carrier: Carrier; + + @Output() + getChangeCarrier = new EventEmitter(); + + public showCode: boolean = false; + public loading: boolean; + constructor(private carrierRouter: CarrierRouter) {} + + async toogleStatus() { + this.loading = true; + const isOnline = this.carrier.status === CarrierStatus.Online; + const carrier = await this.carrierRouter.updateStatus( + this.carrier.id, + isOnline ? CarrierStatus.Offline : CarrierStatus.Online + ); + this.getChangeCarrier.emit(carrier); + this.loading = false; + } +} diff --git a/packages/admin-web-angular/src/app/pages/+carriers/+carrier/carrier-map/carrier-map.component.html b/packages/admin-web-angular/src/app/pages/+carriers/+carrier/carrier-map/carrier-map.component.html new file mode 100644 index 0000000..195ae15 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+carriers/+carrier/carrier-map/carrier-map.component.html @@ -0,0 +1,6 @@ + + {{ carrierId }} + +
+
+
diff --git a/packages/admin-web-angular/src/app/pages/+carriers/+carrier/carrier-map/carrier-map.component.scss b/packages/admin-web-angular/src/app/pages/+carriers/+carrier/carrier-map/carrier-map.component.scss new file mode 100644 index 0000000..7b5bf52 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+carriers/+carrier/carrier-map/carrier-map.component.scss @@ -0,0 +1,11 @@ +.googleMap { + width: 100%; + height: 300px; +} + +nb-card { + width: 60vw; + position: absolute; + top: 10%; + left: calc(50% - 30vw); +} diff --git a/packages/admin-web-angular/src/app/pages/+carriers/+carrier/carrier-map/carrier-map.component.ts b/packages/admin-web-angular/src/app/pages/+carriers/+carrier/carrier-map/carrier-map.component.ts new file mode 100644 index 0000000..27451e9 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+carriers/+carrier/carrier-map/carrier-map.component.ts @@ -0,0 +1,183 @@ +import { + Component, + Input, + Output, + EventEmitter, + ViewChild, +} from '@angular/core'; +import { CarrierRouter } from '@modules/client.common.angular2/routers/carrier-router.service'; +import { Subject } from 'rxjs'; +import { ActivatedRoute } from '@angular/router'; +import { CarrierOrdersRouter } from '@modules/client.common.angular2/routers/carrier-orders-router.service'; +import { CarriersService } from '@app/@core/data/carriers.service'; +import { environment } from 'environments/environment'; +import GeoLocation from '@modules/server.common/entities/GeoLocation'; + +declare var google: any; +const directionsDisplay = new google.maps.DirectionsRenderer(); +const directionsService = new google.maps.DirectionsService(); + +@Component({ + selector: 'ea-carrier-map', + styleUrls: ['carrier-map.component.scss'], + templateUrl: 'carrier-map.component.html', +}) +export class CarrierMapComponent { + private ngDestroy$ = new Subject(); + public carrierId: string; + + @ViewChild('gmap', { static: true }) + gmapElement: any; + map: google.maps.Map; + carrierSub$: any; + marker: any; + userMarker: any; + warehouseMarker: any; + interval: any; + isReverted: boolean = true; + params$: any; + + constructor( + private route: ActivatedRoute, + private carrierRouter: CarrierRouter, + private carrierOrdersRouter: CarrierOrdersRouter, + private carriersService: CarriersService + ) {} + + ngOnInit(): void { + this.showMap(); + this._subscribeCarrier(); + } + + async _subscribeCarrier() { + this.params$ = this.route.params.subscribe((res) => { + const carrierId = this.carrierId; + + this.carrierSub$ = this.carrierRouter + .get(carrierId) + .subscribe(async (carrier) => { + if (this.interval) { + clearInterval(this.interval); + } + const newCoordinates = new google.maps.LatLng( + carrier.geoLocation.coordinates.lat, + carrier.geoLocation.coordinates.lng + ); + if (this.marker) { + this.marker.setMap(null); + } + + let isWorking = false; + + this.interval = setInterval(async () => { + const order = await this.carriersService.getCarrierCurrentOrder( + carrierId + ); + if (order) { + if (!isWorking) { + const user = order.user; + const warehouse = order.warehouse; + const warehouseIcon = + environment.MAP_MERCHANT_ICON_LINK; + const userIcon = environment.MAP_USER_ICON_LINK; + user.geoLocation = new GeoLocation( + user.geoLocation + ); + this.userMarker = this.addMarker( + new google.maps.LatLng( + user.geoLocation.coordinates.lat, + user.geoLocation.coordinates.lng + ), + this.map, + userIcon + ); + warehouse.geoLocation = new GeoLocation( + warehouse.geoLocation + ); + this.warehouseMarker = this.addMarker( + new google.maps.LatLng( + warehouse[ + 'geoLocation' + ].coordinates.lat, + warehouse['geoLocation'].coordinates.lng + ), + this.map, + warehouseIcon + ); + + const bounds = new google.maps.LatLngBounds(); + bounds.extend(this.marker.getPosition()); + bounds.extend( + this.warehouseMarker.getPosition() + ); + bounds.extend(this.userMarker.getPosition()); + this.map.fitBounds(bounds); + + isWorking = true; + this.isReverted = false; + } + } else { + if (isWorking) { + this.revertMap(); + isWorking = false; + } + + if (!this.isReverted) { + this.revertMap(); + } + } + }, 1500); + + this.map.setCenter(newCoordinates); + const carierIcon = environment.MAP_CARRIER_ICON_LINK; + + this.marker = this.addMarker( + newCoordinates, + this.map, + carierIcon + ); + }); + }); + } + + revertMap() { + this.map.setZoom(15); + this.warehouseMarker.setMap(null); + this.userMarker.setMap(null); + this.isReverted = true; + } + + showMap() { + const mapProp = { + center: new google.maps.LatLng(42.642941, 23.334149), + zoom: 15, + mapTypeId: google.maps.MapTypeId.ROADMAP, + }; + this.map = new google.maps.Map(this.gmapElement.nativeElement, mapProp); + } + + addMarker(position, map, icon) { + return new google.maps.Marker({ + position, + map, + icon, + }); + } + + ngOnDestroy() { + this.ngDestroy$.next(true); + this.ngDestroy$.complete(); + + if (this.interval) { + clearInterval(this.interval); + } + + if (this.carrierSub$) { + this.carrierSub$.unsubscribe(); + } + + if (this.params$) { + this.params$.unsubscribe(); + } + } +} diff --git a/packages/admin-web-angular/src/app/pages/+carriers/+carrier/carrier-map/carrier-map.module.ts b/packages/admin-web-angular/src/app/pages/+carriers/+carrier/carrier-map/carrier-map.module.ts new file mode 100644 index 0000000..2509264 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+carriers/+carrier/carrier-map/carrier-map.module.ts @@ -0,0 +1,22 @@ +import { NgModule } from '@angular/core'; +import { FormWizardModule } from '@ever-co/angular2-wizard'; +import { Ng2SmartTableModule } from 'ng2-smart-table'; +import { TranslateModule } from '@ngx-translate/core'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { FileUploadModule } from 'ng2-file-upload'; +import { CarrierMapComponent } from './carrier-map.component'; +import { ThemeModule } from '@app/@theme'; + +@NgModule({ + declarations: [CarrierMapComponent], + imports: [ + FormWizardModule, + Ng2SmartTableModule, + FileUploadModule, + FormsModule, + ReactiveFormsModule, + TranslateModule.forChild(), + ThemeModule, + ] +}) +export class CarrierMapModule {} diff --git a/packages/admin-web-angular/src/app/pages/+carriers/+carrier/carrier-orders-history/carrier-orders-history.component.html b/packages/admin-web-angular/src/app/pages/+carriers/+carrier/carrier-orders-history/carrier-orders-history.component.html new file mode 100644 index 0000000..d4aa04c --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+carriers/+carrier/carrier-orders-history/carrier-orders-history.component.html @@ -0,0 +1,10 @@ + + + + + + diff --git a/packages/admin-web-angular/src/app/pages/+carriers/+carrier/carrier-orders-history/carrier-orders-history.component.scss b/packages/admin-web-angular/src/app/pages/+carriers/+carrier/carrier-orders-history/carrier-orders-history.component.scss new file mode 100644 index 0000000..d4339bb --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+carriers/+carrier/carrier-orders-history/carrier-orders-history.component.scss @@ -0,0 +1,48 @@ +@import '../../../../@theme/styles/themes'; + +@include nb-install-component() { + /*@include nb-for-theme(default) { + .header-color { + color: rgb(24, 22, 22); + background-color: rgb(241, 239, 239); + } + }*/ + + .padding { + padding: 2%; + } + + .padding-bottom { + padding-bottom: 2%; + } + .margin { + margin: 1%; + } + + nb-card-footer { + padding: 0; + } + + ::ng-deep ng2-smart-table { + th { + border-top: 0; + } + + th, + td { + &:first-of-type { + border-left: none; + } + &:last-of-type { + border-right: none; + } + } + + tr:last-of-type { + th, + td { + border-bottom: none; + } + } + } +} diff --git a/packages/admin-web-angular/src/app/pages/+carriers/+carrier/carrier-orders-history/carrier-orders-history.component.ts b/packages/admin-web-angular/src/app/pages/+carriers/+carrier/carrier-orders-history/carrier-orders-history.component.ts new file mode 100644 index 0000000..c900407 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+carriers/+carrier/carrier-orders-history/carrier-orders-history.component.ts @@ -0,0 +1,248 @@ +import { + Component, + EventEmitter, + Input, + OnChanges, + OnDestroy, + Output, + AfterViewInit, +} from '@angular/core'; +import Order from '@modules/server.common/entities/Order'; +import OrderCarrierStatus from '@modules/server.common/enums/OrderCarrierStatus'; +import { ICarrierOrdersRouterGetOptions } from '@modules/server.common/routers/ICarrierOrdersRouter'; +import Carrier from '@modules/server.common/entities/Carrier'; +import { CarrierOrdersRouter } from '@modules/client.common.angular2/routers/carrier-orders-router.service'; +import { forkJoin, Observable, Subscription } from 'rxjs'; +import { LocalDataSource } from 'ng2-smart-table'; +import { Subject } from 'rxjs'; +import { TranslateService } from '@ngx-translate/core'; +import { CreatedComponent } from '../../../../@shared/render-component/created/created.component'; +import { CarriersOrdersService } from '@app/@core/data/carriers-orders.service'; +import { StoreOrderComponent } from '@app/@shared/render-component/carrier-orders-table/store-order.component'; +import { UserOrderComponent } from '@app/@shared/render-component/carrier-orders-table/user-order-component'; +import { takeUntil } from 'rxjs/operators'; + +const perPage = 3; + +@Component({ + selector: 'ea-carrier-orders-history', + templateUrl: './carrier-orders-history.component.html', + styleUrls: ['./carrier-orders-history.component.scss'], +}) +export class CarrierOrdersHistoryComponent + implements OnDestroy, OnChanges, AfterViewInit { + private ngDestroy$ = new Subject(); + + @Input() + protected carrierOrderOptions: ICarrierOrdersRouterGetOptions; + + @Input() + protected selectedCarrier: Carrier; + + @Output() + protected selectedOrderEvent = new EventEmitter(); + + public selectedOrder: Order; + public currentOrders: Order[] = []; + public settingsSmartTable: object; + public sourceSmartTable: LocalDataSource = new LocalDataSource(); + public enumOrderCarrierStatus: typeof OrderCarrierStatus = OrderCarrierStatus; + + private dataCount: number; + $ordersHistory: Subscription; + + constructor( + private carrierOrdersRouter: CarrierOrdersRouter, + private _translateService: TranslateService, + private carriersOrdersService: CarriersOrdersService + ) { + this._setupSmartTable(); + } + + ngAfterViewInit() { + this.loadSmartTableTranslates(); + this.smartTableChange(); + } + + get isCarrierPickupOrder(): boolean { + return ( + this.selectedOrder !== undefined && + this.selectedOrder.carrierStatus === + this.enumOrderCarrierStatus.CarrierPickedUpOrder + ); + } + + get isCarrierArrivedToCustomer(): boolean { + return ( + this.selectedOrder !== undefined && + this.selectedOrder.carrierStatus === + this.enumOrderCarrierStatus.CarrierArrivedToCustomer + ); + } + + ngOnChanges() { + this.getAllAvailableOrders(); + this.loadDataCount(); + } + + selectOrder(ev) { + console.log(ev); + } + + loadSmartTableTranslates() { + this._translateService.onLangChange.subscribe((d) => { + this._setupSmartTable(); + }); + } + + async getAllAvailableOrders() { + this.loadSmartTableData(); + + // Old code for load all order + // this.carrierOrdersRouter + // .getAvailable(this.selectedCarrier.id, this.carrierOrderOptions) + // .pipe(takeUntil(this.ngDestroy$)) + // .subscribe((orders: Order[]) => { + // console.log('ORDERS LOAD2:'); + // console.log(orders); + + // this.currentOrders = orders.filter( + // (order: Order) => + // order.carrier !== undefined && + // order.carrier === this.selectedCarrier.id + // ); + + // console.log('ORDERS FINAL2:'); + // console.log(this.currentOrders); + + // this.sourceSmartTable.load(this.currentOrders); + // }); + } + + private _setupSmartTable() { + const columnTitlePrefix = 'CARRIERS_VIEW.CARRIER_PAGE.'; + const getTranslate = (name: string): Observable => + this._translateService.get(columnTitlePrefix + name); + + forkJoin( + this._translateService.get('Id'), + getTranslate('WAREHOUSE'), + getTranslate('CUSTOMER'), + getTranslate('WAREHOUSE_STATUS'), + getTranslate('CARRIER_STATUS'), + getTranslate('TIME') + ) + .pipe(takeUntil(this.ngDestroy$)) + .subscribe( + ([ + id, + merchant, + customer, + warehouseStatus, + carrierStatus, + created, + ]) => { + this.settingsSmartTable = { + actions: false, + columns: { + Warehouse: { + title: merchant, + type: 'custom', + renderComponent: StoreOrderComponent, + width: '20%', + }, + Customer: { + title: customer, + type: 'custom', + renderComponent: UserOrderComponent, + width: '20%', + }, + WarehouseStatus: { + title: warehouseStatus, + type: 'string', + valuePrepareFunction: (_, order: Order) => { + let warehouseStat = 'BAD_STATUS'; + getTranslate(order.warehouseStatusText) + .pipe(takeUntil(this.ngDestroy$)) + .subscribe((y) => { + warehouseStat = y; + }); + + return warehouseStat; + }, + }, + CarrierStatus: { + title: carrierStatus, + type: 'string', + valuePrepareFunction: (_, order: Order) => { + let carrierStat = 'No Status'; + getTranslate(order.carrierStatusText) + .pipe(takeUntil(this.ngDestroy$)) + .subscribe((y) => { + carrierStat = y; + }); + + return carrierStat; + }, + }, + Created: { + title: created, + type: 'custom', + renderComponent: CreatedComponent, + }, + }, + pager: { + display: true, + perPage: 3, + }, + }; + } + ); + } + + private async smartTableChange() { + this.sourceSmartTable + .onChanged() + .pipe(takeUntil(this.ngDestroy$)) + .subscribe(async (event) => { + if (event.action === 'page') { + const page = event.paging.page; + this.loadSmartTableData(page); + } + }); + } + + private async loadDataCount() { + this.dataCount = await this.carriersOrdersService.getCountOfCarrierOrdersHistory( + this.selectedCarrier.id + ); + } + + private async loadSmartTableData(page = 1) { + if (this.$ordersHistory) { + this.$ordersHistory.unsubscribe(); + } + + this.$ordersHistory = await this.carriersOrdersService + .getCarrierOrdersHistory(this.selectedCarrier.id, { + sort: 'desc', + skip: perPage * (page - 1), + limit: perPage, + }) + .pipe(takeUntil(this.ngDestroy$)) + .subscribe((orders: Order[]) => { + this.currentOrders = new Array(this.dataCount); + this.currentOrders.splice( + perPage * (page - 1), + perPage, + ...orders + ); + this.sourceSmartTable.load(this.currentOrders); + }); + } + + ngOnDestroy() { + this.ngDestroy$.next(); + this.ngDestroy$.complete(); + } +} diff --git a/packages/admin-web-angular/src/app/pages/+carriers/+carrier/carrier-orders-status/carrier-orders-status.component.html b/packages/admin-web-angular/src/app/pages/+carriers/+carrier/carrier-orders-status/carrier-orders-status.component.html new file mode 100644 index 0000000..0d52201 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+carriers/+carrier/carrier-orders-status/carrier-orders-status.component.html @@ -0,0 +1,78 @@ + + + {{ 'CARRIERS_VIEW.CARRIER_PAGE.CARRIER_ORDERS_STATUS' | translate }} +
+
+
+ +
+ +
+ + + +
+
+
+
+
diff --git a/packages/admin-web-angular/src/app/pages/+carriers/+carrier/carrier-orders-status/carrier-orders-status.component.scss b/packages/admin-web-angular/src/app/pages/+carriers/+carrier/carrier-orders-status/carrier-orders-status.component.scss new file mode 100644 index 0000000..49ecb61 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+carriers/+carrier/carrier-orders-status/carrier-orders-status.component.scss @@ -0,0 +1,15 @@ +.btn-startProcessing { + color: #333; + background-color: #fff; + border-color: #ccc; + margin-bottom: 5px; + font-weight: 500; + padding: 0.75rem 1.5rem; +} + +.btn-startProcessing:hover { + color: rgb(24, 22, 22); + background-color: rgb(241, 239, 239); + border-color: rgb(182, 176, 176); + cursor: pointer; +} diff --git a/packages/admin-web-angular/src/app/pages/+carriers/+carrier/carrier-orders-status/carrier-orders-status.component.ts b/packages/admin-web-angular/src/app/pages/+carriers/+carrier/carrier-orders-status/carrier-orders-status.component.ts new file mode 100644 index 0000000..992201a --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+carriers/+carrier/carrier-orders-status/carrier-orders-status.component.ts @@ -0,0 +1,109 @@ +import { + Component, + Input, + OnDestroy, + Output, + EventEmitter, +} from '@angular/core'; +import Order from '@modules/server.common/entities/Order'; +import Carrier from '@modules/server.common/entities/Carrier'; +import { CarrierOrdersRouter } from '@modules/client.common.angular2/routers/carrier-orders-router.service'; +import OrderCarrierStatus from '@modules/server.common/enums/OrderCarrierStatus'; +import { map } from 'underscore'; +import { OrderRouter } from '@modules/client.common.angular2/routers/order-router.service'; + +@Component({ + selector: 'ea-carrier-orders-status', + styleUrls: ['./carrier-orders-status.component.scss'], + templateUrl: 'carrier-orders-status.component.html', +}) +export class CarrierOrdersStatusComponent implements OnDestroy { + @Input() + selectedOrder: Order; + @Input() + selectedCarrier: Carrier; + + @Output() + getChangeOrder = new EventEmitter(); + + enumOrderCarrierStatus: typeof OrderCarrierStatus; + canControlNEw: boolean; + + public orders: Order[] = []; + public loading: boolean; + + protected pageBtnStates: any = { + isSelectOrdersForDeliveryAvailable: true, + isCarrierPickupOrder: true, + }; + + constructor( + private carrierOrdersRouter: CarrierOrdersRouter, + private orderRouter: OrderRouter + ) { + this.enumOrderCarrierStatus = OrderCarrierStatus; + } + + public get ordersIds(): string[] { + this.orders.push(this.selectedOrder); + return map(this.orders, (order) => order.id); + } + + public get ordersCarrierStatus() { + return this.selectedOrder ? this.selectedOrder.carrierStatus : null; + } + + async selectOrdersForDelivery() { + this.pageBtnStates.isSelectOrdersForDeliveryAvailable = false; + + this.selectedOrder.carrierStatus = this.enumOrderCarrierStatus.CarrierSelectedOrder; + + this.loading = true; + + await this.carrierOrdersRouter.selectedForDelivery( + this.selectedCarrier.id, + this.ordersIds + ); + + this.loading = false; + + this.pageBtnStates.isSelectOrdersForDeliveryAvailable = true; + } + + async updateCarrierOrdersStatus(status: OrderCarrierStatus) { + this.pageBtnStates.isCarrierPickupOrder = false; + + this.loading = true; + + this.selectedOrder.carrierStatus = status; + + this.selectedOrder = await this.orderRouter.updateCarrierStatus( + this.selectedOrder.id, + status + ); + + this.getChangeOrder.emit(this.selectedOrder); + + this.loading = false; + + this.pageBtnStates.isCarrierPickupOrder = true; + } + + async cancelDelivery() { + this.pageBtnStates.isCarrierPickupOrder = false; + + this.loading = true; + + await this.carrierOrdersRouter.cancelDelivery( + this.selectedCarrier.id, + this.ordersIds + ); + + this.selectedOrder = null; + this.getChangeOrder.emit(null); + this.loading = false; + this.pageBtnStates.isCarrierPickupOrder = true; + } + + ngOnDestroy() {} +} diff --git a/packages/admin-web-angular/src/app/pages/+carriers/+carrier/carrier-orders/carrier-orders.component.html b/packages/admin-web-angular/src/app/pages/+carriers/+carrier/carrier-orders/carrier-orders.component.html new file mode 100644 index 0000000..4d740a8 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+carriers/+carrier/carrier-orders/carrier-orders.component.html @@ -0,0 +1,81 @@ + + +
+ {{ 'CARRIERS_VIEW.CARRIER_PAGE.AVAIBLE_ORDER_TO_PICK_UP' | translate }} +
+ + + + + + +
diff --git a/packages/admin-web-angular/src/app/pages/+carriers/+carrier/carrier-orders/carrier-orders.component.scss b/packages/admin-web-angular/src/app/pages/+carriers/+carrier/carrier-orders/carrier-orders.component.scss new file mode 100644 index 0000000..f9bf7fd --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+carriers/+carrier/carrier-orders/carrier-orders.component.scss @@ -0,0 +1,53 @@ +@import '../../../../@theme/styles/themes'; + +@include nb-install-component() { + /*@include nb-for-theme(default) { + .header-color { + color: rgb(24, 22, 22); + background-color: rgb(241, 239, 239); + } + }*/ + + .padding { + padding: 2%; + } + + .padding-bottom { + padding-bottom: 2%; + } + .margin { + margin: 1%; + } + + nb-card-footer { + padding: 0; + } + + ::ng-deep ng2-smart-table { + th { + border-top: 0; + } + + th, + td { + &:first-of-type { + border-left: none; + } + &:last-of-type { + border-right: none; + } + } + + tr:last-of-type { + th, + td { + border-bottom: none; + } + } + } + + .carrier-buttons { + width: 100%; + margin: 0; + } +} diff --git a/packages/admin-web-angular/src/app/pages/+carriers/+carrier/carrier-orders/carrier-orders.component.ts b/packages/admin-web-angular/src/app/pages/+carriers/+carrier/carrier-orders/carrier-orders.component.ts new file mode 100644 index 0000000..5e4c9ca --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+carriers/+carrier/carrier-orders/carrier-orders.component.ts @@ -0,0 +1,386 @@ +import { + Component, + EventEmitter, + Input, + OnChanges, + OnDestroy, + Output, + AfterViewInit, +} from '@angular/core'; +import Order from '@modules/server.common/entities/Order'; +import OrderCarrierStatus from '@modules/server.common/enums/OrderCarrierStatus'; +import { ICarrierOrdersRouterGetOptions } from '@modules/server.common/routers/ICarrierOrdersRouter'; +import Carrier from '@modules/server.common/entities/Carrier'; +import { CarrierOrdersRouter } from '@modules/client.common.angular2/routers/carrier-orders-router.service'; +import { OrderRouter } from '@modules/client.common.angular2/routers/order-router.service'; +import CarrierStatus from '@modules/server.common/enums/CarrierStatus'; +import { some } from 'underscore'; +import { LocalDataSource } from 'ng2-smart-table'; +import { TranslateService } from '@ngx-translate/core'; +import { forkJoin, Observable, Subject, Subscription } from 'rxjs'; +import { CreatedComponent } from '../../../../@shared/render-component/created/created.component'; +import { CarriersService } from '@app/@core/data/carriers.service'; +import { GeoLocationOrdersService } from '@app/@core/data/geo-location-orders.service'; +import GeoLocation from '@modules/server.common/entities/GeoLocation'; +import { StoreOrderComponent } from '@app/@shared/render-component/carrier-orders-table/store-order.component'; +import { UserOrderComponent } from '@app/@shared/render-component/carrier-orders-table/user-order-component'; + +const perPage = 3; +let searchCustomer: boolean; +let oldSearch = ''; + +@Component({ + selector: 'ea-carrier-orders', + templateUrl: './carrier-orders.component.html', + styleUrls: ['./carrier-orders.component.scss'], +}) +export class CarrierOrdersComponent + implements OnDestroy, OnChanges, AfterViewInit { + private ngDestroy$ = new Subject(); + + @Input() + protected carrierOrderOptions: ICarrierOrdersRouterGetOptions; + + @Input() + public selectedCarrier: Carrier; + + @Output() + protected selectedOrderEvent = new EventEmitter(); + + public selectedOrder: Order; + protected currentOrders: Order[]; + public settingsSmartTable: object; + public sourceSmartTable: LocalDataSource = new LocalDataSource(); + public enumOrderCarrierStatus: typeof OrderCarrierStatus = OrderCarrierStatus; + + private _isWork: boolean; + private dataCount: number; + private $locationOrders: Subscription; + subscription: Subscription; + + public carrierOnlineStatus = CarrierStatus.Online; + + static $customerSearch = new EventEmitter(); + + constructor( + private carrierOrdersRouter: CarrierOrdersRouter, + private orderRouter: OrderRouter, + private _translateService: TranslateService, + private carriersService: CarriersService, + private geoLocationOrdersService: GeoLocationOrdersService + ) { + this._setupSmartTable(); + } + + get isCarrierPickupOrder(): boolean { + return ( + this.selectedOrder && + this.selectedOrder !== undefined && + this.selectedOrder.carrierStatus === + this.enumOrderCarrierStatus.CarrierPickedUpOrder + ); + } + + get isCarrierArrivedToCustomer(): boolean { + return ( + this.selectedOrder && + this.selectedOrder !== undefined && + this.selectedOrder.carrierStatus === + this.enumOrderCarrierStatus.CarrierArrivedToCustomer + ); + } + + ngAfterViewInit() { + this.loadSmartTableTranslates(); + this.smartTableChange(); + + this.subscription = CarrierOrdersComponent.$customerSearch.subscribe( + async (searchText: string) => { + await this.loadDataCount({ + byRegex: [{ key: 'user.firstName', value: searchText }], + }); + await this.loadSmartTableData(1, { + byRegex: [{ key: 'user.firstName', value: searchText }], + }); + } + ); + } + + ngOnChanges() { + this.loadDataCount(); + this.getAllAvailableOrders(); + this._isWork = true; + } + + async getAllAvailableOrders() { + this.loadSmartTableData(); + + // Old code for load all available order + // this.carrierOrdersRouter + // .getAvailable(this.selectedCarrier.id, this.carrierOrderOptions) + // .pipe(takeUntil(this.ngDestroy$)) + // .subscribe((orders: Order[]) => { + // console.log('ORDERS LOAD:'); + // console.log(orders); + + // this._isWork = false; + // this.currentOrders = orders; + + // orders.every((o: Order) => { + // if ( + // o.carrierId === this.selectedCarrier.id && + // o.isPaid === false && + // o.carrierStatus !== + // OrderCarrierStatus.IssuesDuringDelivery && + // o.carrierStatus !== + // OrderCarrierStatus.ClientRefuseTakingOrder + // ) { + // this.currentOrders = [o]; + // this._isWork = true; + // } + + // return !this._isWork; + // }); + + // if (!this._isWork) { + // let filter = (order: Order): boolean => + // order.carrierStatus !== + // OrderCarrierStatus.IssuesDuringDelivery && + // order.carrierStatus !== + // OrderCarrierStatus.ClientRefuseTakingOrder && + // this.selectedCarrier.status === + // CarrierStatus.Online && + // OrderWarehouseStatus.PackagingFinished === + // order.warehouseStatus && + // OrderCarrierStatus.DeliveryCompleted !== + // order.carrierStatus && + // (order.carrier === undefined || + // order.carrier === null || + // (order.carrierId === this.selectedCarrier.id && + // order.isPaid === false)); + + // this.currentOrders = orders.filter(filter); + // } + // console.log('ORDERS FINAL:'); + // console.log(this.currentOrders); + + // this.sourceSmartTable.load(this.currentOrders); + // }); + } + + async smartTableChange() { + this.subscription = this.sourceSmartTable + .onChanged() + .subscribe(async (event) => { + if (event.action === 'page') { + const page = event.paging.page; + this.loadSmartTableData(page); + } + }); + } + + selectOrder(ev) { + const order = ev.data as Order; + + if (this.selectedOrder && order.id === this.selectedOrder.id) { + this.selectedOrderEvent.emit(null); + this.selectedOrder = null; + this._isWork = false; + } else { + this.selectedOrderEvent.emit(order); + this.selectedOrder = order; + this._isWork = true; + } + } + + loadSmartTableTranslates() { + this._translateService.onLangChange.subscribe(() => { + this._setupSmartTable(); + }); + } + + public get canControl(): boolean { + return some(this.currentOrders, (order) => + order + ? OrderCarrierStatus.CarrierPickedUpOrder <= + order.carrierStatus && + OrderCarrierStatus.DeliveryCompleted >= order.carrierStatus + : false + ); + } + + public async updateOrderCarrierStatus(status: OrderCarrierStatus) { + this.selectedOrder = await this.orderRouter.updateCarrierStatus( + this.selectedOrder.id, + status + ); + + await this.loadSmartTableData(); + } + + private _setupSmartTable() { + const columnTitlePrefix = 'CARRIERS_VIEW.CARRIER_PAGE.'; + const getTranslate = (name: string): Observable => + this._translateService.get(columnTitlePrefix + name); + + this.subscription = forkJoin( + this._translateService.get('Id'), + getTranslate('WAREHOUSE'), + getTranslate('CUSTOMER'), + getTranslate('WAREHOUSE_STATUS'), + getTranslate('CARRIER_STATUS'), + getTranslate('CREATED') + ).subscribe( + ([ + id, + warehouse, + customer, + warehouseStatus, + carrierStatus, + created, + ]) => { + this.settingsSmartTable = { + actions: false, + columns: { + Warehouse: { + title: warehouse, + type: 'custom', + renderComponent: StoreOrderComponent, + width: '20%', + }, + Customer: { + title: customer, + type: 'custom', + renderComponent: UserOrderComponent, + width: '20%', + filterFunction( + cell?: string, + search?: string + ): boolean { + if (!searchCustomer && oldSearch !== search) { + oldSearch = search; + + searchCustomer = true; + setTimeout(() => { + searchCustomer = false; + + CarrierOrdersComponent.$customerSearch.emit( + search + ); + }, 1000); + } + + return true; + }, + }, + WarehouseStatus: { + title: warehouseStatus, + type: 'string', + valuePrepareFunction: (_, order: Order) => { + let warehouseStat = 'BAD_STATUS'; + getTranslate( + order.warehouseStatusText + ).subscribe((y) => { + warehouseStat = y; + }); + + return warehouseStat; + }, + }, + CarrierStatus: { + title: carrierStatus, + type: 'string', + valuePrepareFunction: (_, order: Order) => { + let carrierStat = 'No Status'; + getTranslate(order.carrierStatusText).subscribe( + (y) => { + carrierStat = y; + } + ); + + return carrierStat; + }, + }, + Created: { + title: created, + type: 'custom', + renderComponent: CreatedComponent, + }, + }, + pager: { + display: true, + perPage, + }, + }; + } + ); + } + + private async loadDataCount(searchObj?: { + byRegex: Array<{ key: string; value: string }>; + }) { + const getOrdersGeoObj = { + loc: { + type: 'Point', + coordinates: this.selectedCarrier.geoLocation.loc.coordinates, + }, + }; + this.dataCount = await this.geoLocationOrdersService.getCountOfOrdersForWork( + getOrdersGeoObj as GeoLocation, + this.selectedCarrier.skippedOrderIds, + searchObj + ); + } + + private async loadSmartTableData( + page = 1, + searchObj?: { byRegex: Array<{ key: string; value: string }> } + ) { + const getOrdersGeoObj = { + loc: { + type: 'Point', + coordinates: this.selectedCarrier.geoLocation.loc.coordinates, + }, + }; + + if (this.$locationOrders) { + await this.$locationOrders.unsubscribe(); + } + + this.$locationOrders = this.geoLocationOrdersService + .getOrdersForWork( + getOrdersGeoObj as GeoLocation, + this.selectedCarrier.skippedOrderIds, + { + skip: perPage * (page - 1), + limit: perPage, + }, + searchObj + ) + .subscribe(async (ordersForWork: Order[]) => { + const currentOrder = await this.carriersService.getCarrierCurrentOrder( + this.selectedCarrier.id + ); + + if (currentOrder) { + this.currentOrders = [currentOrder]; + } else { + this.currentOrders = new Array(this.dataCount); + this.currentOrders.splice( + perPage * (page - 1), + perPage, + ...ordersForWork + ); + } + + await this.sourceSmartTable.load(this.currentOrders); + }); + } + + ngOnDestroy() { + this.ngDestroy$.next(); + this.ngDestroy$.complete(); + this.subscription.unsubscribe(); + this.$locationOrders.unsubscribe(); + } +} diff --git a/packages/admin-web-angular/src/app/pages/+carriers/+carrier/carrier.component.html b/packages/admin-web-angular/src/app/pages/+carriers/+carrier/carrier.component.html new file mode 100644 index 0000000..5183f14 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+carriers/+carrier/carrier.component.html @@ -0,0 +1,139 @@ + + +
+
+ + + + + + + +
+

+ {{ 'Carrier' | translate }} +

+ +

{{ 'Manage carrier and deliveries' | translate }}

+
+
+ + + +
+ + + + + + + + + + + + + +
+ +
+ + + {{ 'Carrier' | translate }} + + + + + + + {{ item.firstName }} + {{ item.firstName && item.lastName }} + {{ item.firstName ? '' : '# ' + item.id }} + + +

+ + {{ item.firstName }} + {{ item.firstName && item.lastName }} + {{ item.firstName ? '' : '# ' + item.id }} +

+
+
+
+
+ + + + + {{ 'CARRIERS_VIEW.CARRIER_PAGE.CARRIER_ORDERS_STATUS' | translate }} + +
+
diff --git a/packages/admin-web-angular/src/app/pages/+carriers/+carrier/carrier.component.scss b/packages/admin-web-angular/src/app/pages/+carriers/+carrier/carrier.component.scss new file mode 100644 index 0000000..b820950 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+carriers/+carrier/carrier.component.scss @@ -0,0 +1,30 @@ +@import '../../../@theme/styles/themes'; +@import '../../../@shared/styles/control-icon.shared'; + +@include nb-install-component() { + @include control-icon; + + .btn-color { + background: #4caf50; + cursor: pointer; + } + .btn-color:hover { + background: #4caf50; + cursor: pointer; + } + + .btn-color.btn-outline-success { + background: #4caf50; + cursor: pointer; + } + .btn-color.btn-outline-success:hover { + background: #4caf50; + cursor: pointer; + } +} + +nb-card#carrier-select-carriers { + nb-card-body { + overflow: inherit !important; + } +} diff --git a/packages/admin-web-angular/src/app/pages/+carriers/+carrier/carrier.component.ts b/packages/admin-web-angular/src/app/pages/+carriers/+carrier/carrier.component.ts new file mode 100644 index 0000000..e7e877d --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+carriers/+carrier/carrier.component.ts @@ -0,0 +1,170 @@ +import { find } from 'underscore'; +import { Component, OnDestroy, OnInit, ViewChild } from '@angular/core'; +import { Subject } from 'rxjs'; +import { ICarrierCreateObject } from '@modules/server.common/interfaces/ICarrier'; +import { ICarrierOrdersRouterGetOptions } from '@modules/server.common/routers/ICarrierOrdersRouter'; +import Order from '@modules/server.common/entities/Order'; +import OrderCarrierStatus from '@modules/server.common/enums/OrderCarrierStatus'; +import Carrier from '@modules/server.common/entities/Carrier'; +import { ActivatedRoute, Router } from '@angular/router'; +import { ToasterService } from 'angular2-toaster'; +import { first } from 'rxjs/operators'; +import { CarriersService } from '../../../@core/data/carriers.service'; +import CarrierStatus from '@modules/server.common/enums/CarrierStatus'; +import { CarrierOrdersComponent } from './carrier-orders/carrier-orders.component'; + +@Component({ + selector: 'ea-carrier', + templateUrl: './carrier.component.html', + styleUrls: ['./carrier.component.scss'], +}) +export class CarrierComponent implements OnInit, OnDestroy { + @ViewChild('carrierOrders', { static: false }) + public carrierOrders: CarrierOrdersComponent; + + private ngDestroy$ = new Subject(); + + public carriers: ICarrierCreateObject[]; + protected selectedOrder: Order; + + carrierOrderOptions: ICarrierOrdersRouterGetOptions; + + public selectedOrdersId: string[] = []; + + private inDeliveryOrders: Order[] = []; + private closeOrders: Order[] = []; + + public selectedCarrier: ICarrierCreateObject; + private carriers$: any; + private currentTab: string; + + get showOrderStatus() { + return ( + this.selectedOrder && + this.selectedOrder.carrierStatus < + OrderCarrierStatus.CarrierPickedUpOrder && + this.selectedCarrier && + this.selectedCarrier.status === CarrierStatus.Online + ); + } + + constructor( + private readonly _route: ActivatedRoute, + private readonly _router: Router, + private readonly _toasterService: ToasterService, + private carriersService: CarriersService + ) {} + + ngOnInit() { + this.getAllCarriers(); + } + + public get isCarrierDelivering() { + return this.inDeliveryOrders.length > 0; + } + + public get currentOrders() { + if (this.isCarrierDelivering) { + return this.inDeliveryOrders; + } else { + return this.closeOrders; + } + } + + public get shouldShowOrdersStatusesControl() { + return ( + this.selectedOrdersId.length > 0 && + find( + this.currentOrders, + (order) => order.id === this.selectedOrdersId[0] + )!.carrierStatus <= OrderCarrierStatus.CarrierSelectedOrder + ); + } + + getChangeOrder(order: Order) { + this.selectedOrder = order; + } + + getChangeCarrier(carrier: Carrier) { + if (carrier.status === CarrierStatus.Offline) { + this.carrierOrders.selectedOrder = null; + this.selectedOrder = null; + } + } + + carrierSelect(newCarrier: Carrier) { + this._router.navigate([`/carriers/${newCarrier.id}`]); + + this.selectedOrder = null; + + const objToCompare: ICarrierOrdersRouterGetOptions = { + populateWarehouse: true, + completion: 'not-completed', + }; + + this.carrierOrderOptions = + this.carrierOrderOptions === objToCompare ? null : objToCompare; + + this.selectedCarrier = + this.selectedCarrier === newCarrier ? null : newCarrier; + } + + getAllCarriers() { + this.carriers$ = this.carriersService.getCarriers().subscribe((c) => { + this.carriers = c; + this._selectCarrierIfIdExists(); + }); + } + + orderStatusShow(warehouseOrderProducts) { + if (warehouseOrderProducts) { + // TODO: next line is most probably a bug, because selectedOrdersId is array, + // but warehouseOrderProducts.id is single value! + this.selectedOrdersId = warehouseOrderProducts.id; + this.selectedOrder = warehouseOrderProducts; + } else { + this.selectedOrder = null; + } + } + + selectTab(ev) { + if (this.currentTab !== ev.tabTitle) { + this.currentTab = ev.tabTitle; + if (this.carrierOrders) { + this.carrierOrders.selectedOrder = null; + } + this.selectedOrder = null; + } + } + + selectedOrdersChange(selectedOrdersIds: string[]) { + this.selectedOrdersId = selectedOrdersIds; + } + + private async _selectCarrierIfIdExists() { + const p = await this._route.params.pipe(first()).toPromise(); + + const carrierId = p.id; + if (carrierId !== undefined) { + const carrier = this.carriers.find( + (c) => c._id.toString() === carrierId + ); + if (carrier !== undefined) { + this.carrierSelect(carrier as Carrier); + } else { + this._toasterService.pop( + `warning`, + `Carrier with id '${carrierId}' is not active!` + ); + } + } + } + + ngOnDestroy() { + this.ngDestroy$.next(true); + this.ngDestroy$.complete(); + if (this.carriers$) { + this.carriers$.unsubscribe(); + } + } +} diff --git a/packages/admin-web-angular/src/app/pages/+carriers/+carrier/carrier.module.ts b/packages/admin-web-angular/src/app/pages/+carriers/+carrier/carrier.module.ts new file mode 100644 index 0000000..e8bcb4b --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+carriers/+carrier/carrier.module.ts @@ -0,0 +1,61 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { CarrierComponent } from './carrier.component'; +import { ThemeModule } from '../../../@theme'; +import { RouterModule } from '@angular/router'; +import { routes } from './carrier.routes'; +import { CarrierLocationModule } from './location/carrier-location.module'; +import { CarrierInfoComponent } from './carrier-info/carrier-info.component'; +import { CarrierOrdersStatusComponent } from './carrier-orders-status/carrier-orders-status.component'; +import { CarrierOrdersComponent } from './carrier-orders/carrier-orders.component'; +import { FormWizardModule } from '@ever-co/angular2-wizard'; +import { ToasterModule } from 'angular2-toaster'; +import { TranslateModule } from '@ngx-translate/core'; +import { CarrierMutationModule } from '../../../@shared/carrier/carrier-mutation'; +import { HighlightModule } from 'ngx-highlightjs'; +import { NbSpinnerModule, NbButtonModule } from '@nebular/theme'; +import { CarriersTableModule } from '../../../@shared/render-component/carriers-table/carriers-table.module'; +import { CarrierOrdersHistoryComponent } from './carrier-orders-history/carrier-orders-history.component'; +import { RenderComponentsModule } from '../../../@shared/render-component/render-components.module'; +import { CarriersService } from '../../../@core/data/carriers.service'; +import { GeoLocationOrdersService } from '../../../@core/data/geo-location-orders.service'; +import { CarriersOrdersService } from '../../../@core/data/carriers-orders.service'; +import { CarrierOrdersTableModule } from '../../../@shared/render-component/carrier-orders-table/carrier-orders-table.module'; +import { NgSelectModule } from '@ng-select/ng-select'; +import { FormsModule } from '@angular/forms'; +import { Ng2SmartTableModule } from 'ng2-smart-table'; + +@NgModule({ + imports: [ + CommonModule, + ThemeModule, + FormWizardModule, + Ng2SmartTableModule, + CarrierLocationModule, + CarrierMutationModule, + CarriersTableModule, + RenderComponentsModule, + ToasterModule.forRoot(), + TranslateModule.forChild(), + RouterModule.forChild(routes), + HighlightModule, + NbSpinnerModule, + CarrierOrdersTableModule, + NgSelectModule, + FormsModule, + NbButtonModule, + ], + declarations: [ + CarrierComponent, + CarrierInfoComponent, + CarrierOrdersStatusComponent, + CarrierOrdersComponent, + CarrierOrdersHistoryComponent, + ], + providers: [ + CarriersService, + GeoLocationOrdersService, + CarriersOrdersService, + ] +}) +export class CarrierModule {} diff --git a/packages/admin-web-angular/src/app/pages/+carriers/+carrier/carrier.routes.ts b/packages/admin-web-angular/src/app/pages/+carriers/+carrier/carrier.routes.ts new file mode 100644 index 0000000..e5e4753 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+carriers/+carrier/carrier.routes.ts @@ -0,0 +1,16 @@ +import { Routes } from '@angular/router'; +import { CarrierComponent } from './carrier.component'; + +export const routes: Routes = [ + { + path: '', + component: CarrierComponent, + }, + { + path: 'edit', + loadChildren: () => + import('./+carrier-edit/carrier-edit.module').then( + (m) => m.CarrierEditModule + ), + }, +]; diff --git a/packages/admin-web-angular/src/app/pages/+carriers/+carrier/index.ts b/packages/admin-web-angular/src/app/pages/+carriers/+carrier/index.ts new file mode 100644 index 0000000..5dbdb0b --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+carriers/+carrier/index.ts @@ -0,0 +1 @@ +export * from './carrier.module'; diff --git a/packages/admin-web-angular/src/app/pages/+carriers/+carrier/location/carrier-location.component.html b/packages/admin-web-angular/src/app/pages/+carriers/+carrier/location/carrier-location.component.html new file mode 100644 index 0000000..60e55e4 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+carriers/+carrier/location/carrier-location.component.html @@ -0,0 +1,6 @@ + + {{ + 'CARRIERS_VIEW.CARRIER_PAGE.LOCATION' | translate + }} +
+
diff --git a/packages/admin-web-angular/src/app/pages/+carriers/+carrier/location/carrier-location.component.scss b/packages/admin-web-angular/src/app/pages/+carriers/+carrier/location/carrier-location.component.scss new file mode 100644 index 0000000..f32c4ef --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+carriers/+carrier/location/carrier-location.component.scss @@ -0,0 +1,37 @@ +@import '../../../../@theme/styles/themes'; + +@include nb-install-component() { + nb-card-body { + padding: 0; + } + + ::ng-deep .leaflet-container { + width: 100%; + // height: nb-theme(card-height-large); + } +} + +.header-color { + color: rgb(24, 22, 22); + background-color: rgb(241, 239, 239); +} + +.googleMap { + width: 100%; + height: 200px !important; +} + +.modal-style { + background: white; + width: 60vw; + position: absolute; + top: 10%; + left: calc(50% - 30vw); + nb-card-header { + background: white; + } + .googleMap { + width: 100%; + height: 400px !important; + } +} diff --git a/packages/admin-web-angular/src/app/pages/+carriers/+carrier/location/carrier-location.component.ts b/packages/admin-web-angular/src/app/pages/+carriers/+carrier/location/carrier-location.component.ts new file mode 100644 index 0000000..6c84ff6 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+carriers/+carrier/location/carrier-location.component.ts @@ -0,0 +1,283 @@ +import { Component, OnDestroy, OnInit, ViewChild } from '@angular/core'; +import { Subject } from 'rxjs'; +// TODO: import 'style-loader!leaflet/dist/leaflet.css'; +import { ActivatedRoute } from '@angular/router'; +import { CarrierRouter } from '@modules/client.common.angular2/routers/carrier-router.service'; +import { first } from 'rxjs/operators'; +import { CarrierOrdersRouter } from '@modules/client.common.angular2/routers/carrier-orders-router.service'; +import { ICarrierOrdersRouterGetOptions } from '@modules/server.common/routers/ICarrierOrdersRouter'; +import { environment } from 'environments/environment'; +import { CarriersService } from '@app/@core/data/carriers.service'; +import GeoLocation from '@modules/server.common/entities/GeoLocation'; + +declare var google: any; +const directionsDisplay = new google.maps.DirectionsRenderer(); +const directionsService = new google.maps.DirectionsService(); + +@Component({ + selector: 'ea-carrier-location', + styleUrls: ['./carrier-location.component.scss'], + templateUrl: './carrier-location.component.html', +}) +export class CarrierLocationComponent implements OnDestroy, OnInit { + private ngDestroy$ = new Subject(); + + @ViewChild('gmap', { static: true }) + gmapElement: any; + map: google.maps.Map; + carrierSub$: any; + marker: any; + userMarker: any; + warehouseMarker: any; + interval: any; + isReverted: boolean = true; + params$: any; + carrierId: string; + + constructor( + private route: ActivatedRoute, + private carrierRouter: CarrierRouter, + private carrierOrdersRouter: CarrierOrdersRouter, + private carriersService: CarriersService + ) {} + + ngOnInit(): void { + this.showMap(); + this._subscribeCarrier(); + } + + async _subscribeCarrier() { + this.params$ = this.route.params.subscribe((res) => { + const carrierId = res.id || this.carrierId; + + this.carrierSub$ = this.carrierRouter + .get(carrierId) + .subscribe(async (carrier) => { + if (this.interval) { + clearInterval(this.interval); + } + const newCoordinates = new google.maps.LatLng( + carrier.geoLocation.coordinates.lat, + carrier.geoLocation.coordinates.lng + ); + if (this.marker) { + this.marker.setMap(null); + } + let isWorking = false; + + this.interval = setInterval(async () => { + const order = await this.carriersService.getCarrierCurrentOrder( + carrierId + ); + + if (order) { + if (!isWorking) { + const user = order.user; + const warehouse = order.warehouse; + const warehouseIcon = + environment.MAP_MERCHANT_ICON_LINK; + const userIcon = environment.MAP_USER_ICON_LINK; + user.geoLocation = new GeoLocation( + user.geoLocation + ); + this.userMarker = this.addMarker( + new google.maps.LatLng( + user.geoLocation.coordinates.lat, + user.geoLocation.coordinates.lng + ), + this.map, + userIcon + ); + warehouse.geoLocation = new GeoLocation( + warehouse.geoLocation + ); + this.warehouseMarker = this.addMarker( + new google.maps.LatLng( + warehouse[ + 'geoLocation' + ].coordinates.lat, + warehouse['geoLocation'].coordinates.lng + ), + this.map, + warehouseIcon + ); + const start = new google.maps.LatLng( + user.geoLocation.coordinates.lat, + user.geoLocation.coordinates.lng + ); + const end = new google.maps.LatLng( + warehouse['geoLocation'].coordinates.lat, + warehouse['geoLocation'].coordinates.lng + ); + const request = { + origin: start, + destination: end, + travelMode: 'DRIVING', + }; + + directionsService.route(request, function ( + res, + stat + ) { + if (stat === 'OK') { + directionsDisplay.setDirections(res); + } + }); + directionsDisplay.setOptions({ + suppressMarkers: true, + }); + directionsDisplay.setMap(this.map); + + const bounds = new google.maps.LatLngBounds(); + bounds.extend(this.marker.getPosition()); + bounds.extend( + this.warehouseMarker.getPosition() + ); + bounds.extend(this.userMarker.getPosition()); + this.map.fitBounds(bounds); + + isWorking = true; + this.isReverted = false; + + const userInfoContent = ` +

${order.user.firstName + ' ' + order.user.lastName}

+
    +
  • ${ + order.user.email + }
  • +
  • ${ + order.user.phone + }
  • +
  • ${ + order.user.geoLocation.streetAddress + }
  • +
+ `; + + const userInfoWindow = new google.maps.InfoWindow( + { + content: userInfoContent, + } + ); + + this.userMarker.addListener('click', () => { + userInfoWindow.open( + this.map, + this.userMarker + ); + }); + const warehouseInfoContent = ` +

${order.warehouse.name}

+
    +
  • + + ${order.warehouse.contactEmail} +
  • +
  • + + ${order.warehouse.contactPhone} +
  • +
  • + + ${order.warehouse.geoLocation.streetAddress} +
  • +
+ `; + + const warehouseInfoWindow = new google.maps.InfoWindow( + { + content: warehouseInfoContent, + } + ); + + this.warehouseMarker.addListener( + 'click', + () => { + warehouseInfoWindow.open( + this.map, + this.warehouseMarker + ); + } + ); + } + } else { + if (isWorking) { + this.revertMap(); + isWorking = false; + } + + if (!this.isReverted) { + this.revertMap(); + } + } + }, 1500); + + this.map.setCenter(newCoordinates); + const carierIcon = environment.MAP_CARRIER_ICON_LINK; + + this.marker = this.addMarker( + newCoordinates, + this.map, + carierIcon + ); + const carrierInfoContent = ` +

${carrier.fullName}

+
    +
  • ${carrier.username}
  • +
  • ${carrier.phone}
  • +
  • ${carrier.geoLocation.streetAddress}
  • +
+ `; + + const carrierInfoWindow = new google.maps.InfoWindow({ + content: carrierInfoContent, + }); + + this.marker.addListener('click', () => { + carrierInfoWindow.open(this.map, this.marker); + }); + }); + }); + } + + revertMap() { + this.map.setZoom(15); + this.warehouseMarker.setMap(null); + this.userMarker.setMap(null); + this.isReverted = true; + } + + showMap() { + const mapProp = { + center: new google.maps.LatLng(42.642941, 23.334149), + zoom: 15, + mapTypeId: google.maps.MapTypeId.ROADMAP, + }; + this.map = new google.maps.Map(this.gmapElement.nativeElement, mapProp); + } + + addMarker(position, map, icon) { + return new google.maps.Marker({ + position, + map, + icon, + }); + } + + ngOnDestroy() { + this.ngDestroy$.next(true); + this.ngDestroy$.complete(); + + if (this.interval) { + clearInterval(this.interval); + } + + if (this.carrierSub$) { + this.carrierSub$.unsubscribe(); + } + + if (this.params$) { + this.params$.unsubscribe(); + } + } +} diff --git a/packages/admin-web-angular/src/app/pages/+carriers/+carrier/location/carrier-location.module.ts b/packages/admin-web-angular/src/app/pages/+carriers/+carrier/location/carrier-location.module.ts new file mode 100644 index 0000000..fab2880 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+carriers/+carrier/location/carrier-location.module.ts @@ -0,0 +1,26 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { ThemeModule } from '../../../../@theme/theme.module'; +import { AgmCoreModule } from '@agm/core'; +import { LeafletModule } from '@asymmetrik/ngx-leaflet'; +import { NgxEchartsModule } from 'ngx-echarts'; +import { CarrierLocationComponent } from './carrier-location.component'; +import { TranslateModule } from '@ngx-translate/core'; +import { environment } from 'environments/environment'; + +@NgModule({ + imports: [ + CommonModule, + ThemeModule, + AgmCoreModule.forRoot({ + apiKey: environment.GOOGLE_MAPS_API_KEY, + libraries: ['places'], + }), + LeafletModule, + TranslateModule.forChild(), + NgxEchartsModule, + ], + declarations: [CarrierLocationComponent], + exports: [CarrierLocationComponent] +}) +export class CarrierLocationModule {} diff --git a/packages/admin-web-angular/src/app/pages/+carriers/carriers-routing.module.ts b/packages/admin-web-angular/src/app/pages/+carriers/carriers-routing.module.ts new file mode 100644 index 0000000..233f46e --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+carriers/carriers-routing.module.ts @@ -0,0 +1,36 @@ +import { RouterModule, Routes } from '@angular/router'; +import { NgModule } from '@angular/core'; +import { CarriersComponent } from './carriers.component'; +import { TrackComponent } from './track/track.component'; + +const routes: Routes = [ + { + path: '', + component: CarriersComponent, + }, + { + path: 'track', + component: TrackComponent, + }, + { + path: 'track/:storeId', + component: TrackComponent, + }, + { + path: 'track/:storeId/:carrierId', + component: TrackComponent, + }, + { + path: ':id', + loadChildren: () => + import('./+carrier/carrier.module').then((m) => m.CarrierModule), + }, +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule], +}) +export class CarriersRoutingModule { + public static routes = routes; +} diff --git a/packages/admin-web-angular/src/app/pages/+carriers/carriers.component.html b/packages/admin-web-angular/src/app/pages/+carriers/carriers.component.html new file mode 100644 index 0000000..f56966b --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+carriers/carriers.component.html @@ -0,0 +1,40 @@ + + + + + + + + + + + + diff --git a/packages/admin-web-angular/src/app/pages/+carriers/carriers.component.scss b/packages/admin-web-angular/src/app/pages/+carriers/carriers.component.scss new file mode 100644 index 0000000..52c4e4c --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+carriers/carriers.component.scss @@ -0,0 +1,56 @@ +nb-card-header { + border-bottom: 0; +} + +nb-card-body { + padding: 0; + border-bottom-left-radius: 0.5rem; + border-bottom-right-radius: 0.5rem; +} + +:host ::ng-deep ng2-smart-table { + tr { + th { + padding: 18px !important; + } + td { + padding: 17px !important; + } + } + + tr.ng2-smart-titles > th:nth-child(1) { + text-align: center; + cursor: pointer; + + input { + cursor: pointer; + } + } + + td.ng2-smart-actions.ng2-smart-action-multiple-select { + text-align: center; + cursor: pointer; + } + + tr.ng2-smart-filters th { + text-align: center; + } + + tr td, + th { + &:first-of-type { + border-left: none; + } + &:last-of-type { + border-right: none; + } + } + + input[type='checkbox'] { + cursor: pointer; + } +} + +:host ::ng-deep ng2-smart-table .carrier-image { + width: 74px; +} diff --git a/packages/admin-web-angular/src/app/pages/+carriers/carriers.component.ts b/packages/admin-web-angular/src/app/pages/+carriers/carriers.component.ts new file mode 100644 index 0000000..11116f4 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+carriers/carriers.component.ts @@ -0,0 +1,141 @@ +import { Component, OnDestroy, ViewChild, AfterViewInit } from '@angular/core'; +import { CarriersService } from '../../@core/data/carriers.service'; +import { CarrierMutationComponent } from '../../@shared/carrier/carrier-mutation'; +import { Subject } from 'rxjs'; +import { ToasterService } from 'angular2-toaster'; +import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; +import * as _ from 'underscore'; +import { CarriersSmartTableComponent } from '@app/@shared/carrier/carriers-table/carriers-table.component'; +import { takeUntil, first } from 'rxjs/operators'; +import Carrier from '@modules/server.common/entities/Carrier'; +import CarrierStatus from '@modules/server.common/enums/CarrierStatus'; +import { TranslateService } from '@ngx-translate/core'; + +const perPage = 5; + +@Component({ + selector: 'ea-carriers', + templateUrl: 'carriers.component.html', + styleUrls: ['carriers.component.scss'], +}) +export class CarriersComponent implements OnDestroy, AfterViewInit { + @ViewChild('carriersTable', { static: true }) + carriersTable: CarriersSmartTableComponent; + + loading: boolean; + perPage = perPage; + + private dataCount: number; + private $carriers; + private ngDestroy$ = new Subject(); + + constructor( + private readonly _carriersService: CarriersService, + private readonly _toasterService: ToasterService, + private readonly modalService: NgbModal, + private readonly _translateService: TranslateService + ) { + this._applyTranslationOnSmartTable(); + } + + openWizardNewCarrier() { + this.modalService.open(CarrierMutationComponent, { + size: 'lg', + container: 'nb-layout', + windowClass: 'ng-custom', + backdrop: 'static', + }); + } + + async deleteSelectedCarriers() { + const idsForDelete: string[] = this.carriersTable.selectedCarriers.map( + (c) => c.id + ); + this.loading = true; + + try { + await this._carriersService + .removeByIds(idsForDelete) + .pipe(first()) + .toPromise(); + + this.carriersTable.selectedCarriers.forEach((carrier) => + this._toasterService.pop( + `success`, + `Carrier ${carrier['name']} DELETED` + ) + ); + + this.carriersTable.selectedCarriers = []; + this.loading = false; + } catch (error) { + this.loading = false; + this._toasterService.pop(`error`, `${error.message}`); + } + } + + ngAfterViewInit(): void { + this._loadDataSmartTable(); + this.smartTablePageChange(); + } + + ngOnDestroy() { + this.ngDestroy$.next(); + this.ngDestroy$.complete(); + } + + private async _loadDataSmartTable(page = 1) { + if (this.$carriers) { + await this.$carriers.unsubscribe(); + } + + this.$carriers = this._carriersService + .getCarriers({ + skip: perPage * (page - 1), + limit: perPage, + }) + .pipe(takeUntil(this.ngDestroy$)) + .subscribe(async (data: Carrier[]) => { + const carriersVm = data.map( + CarriersSmartTableComponent.getCarrierSmartTableObject + ); + + await this.loadDataCount(); + + const carriersData = new Array(this.dataCount); + + carriersData.splice( + perPage * (page - 1), + perPage, + ...carriersVm + ); + + await this.carriersTable.loadData(carriersData); + }); + } + + private _applyTranslationOnSmartTable() { + this._translateService.onLangChange + .pipe(takeUntil(this.ngDestroy$)) + .subscribe(() => { + if (this.carriersTable) { + this.carriersTable.loadSettingsSmartTable(this.perPage); + this._loadDataSmartTable(); + } + }); + } + + private async loadDataCount() { + this.dataCount = await this._carriersService.getCountOfCarriers(); + } + + private async smartTablePageChange() { + if (this.carriersTable) { + this.carriersTable.pageChange + .pipe(takeUntil(this.ngDestroy$)) + .subscribe((page) => { + this._loadDataSmartTable(page); + }); + } + } +} diff --git a/packages/admin-web-angular/src/app/pages/+carriers/carriers.module.ts b/packages/admin-web-angular/src/app/pages/+carriers/carriers.module.ts new file mode 100644 index 0000000..d8c290b --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+carriers/carriers.module.ts @@ -0,0 +1,34 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { ToasterModule } from 'angular2-toaster'; +import { TranslateModule } from '@ngx-translate/core'; +import { CarriersComponent } from './carriers.component'; +import { ThemeModule } from '../../@theme'; +import { FormWizardModule } from '@ever-co/angular2-wizard'; +import { CarriersRoutingModule } from './carriers-routing.module'; +import { RenderComponentsModule } from '../../@shared/render-component/render-components.module'; +import { NbSpinnerModule, NbButtonModule } from '@nebular/theme'; +import { TrackModule } from './track/track.module'; +import { CarrierLocationModule } from './+carrier/location/carrier-location.module'; +import { CarriersSmartTableModule } from '@app/@shared/carrier/carriers-table/carriers-table.module'; +import { CarrierMutationModule } from '@app/@shared/carrier/carrier-mutation'; + +@NgModule({ + imports: [ + CommonModule, + ThemeModule, + FormWizardModule, + CarriersRoutingModule, + ToasterModule.forChild(), + TranslateModule.forChild(), + NbSpinnerModule, + CarrierLocationModule, + TrackModule, + CarriersSmartTableModule, + RenderComponentsModule, + CarrierMutationModule, + NbButtonModule, + ], + declarations: [CarriersComponent], +}) +export class CarriersModule {} diff --git a/packages/admin-web-angular/src/app/pages/+carriers/track/carrier-tracking/carrier-tracking.component.html b/packages/admin-web-angular/src/app/pages/+carriers/track/carrier-tracking/carrier-tracking.component.html new file mode 100644 index 0000000..fb91483 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+carriers/track/carrier-tracking/carrier-tracking.component.html @@ -0,0 +1,179 @@ +
+
+
+
+ + + {{ + 'CARRIERS_VIEW.TRACK_PAGE.TRACK_ALL_WORKING_CARRIERS' + | translate + }} + + +
+
+
+
+ +
+ + + {{ + 'CARRIERS_VIEW.TRACK_PAGE.FILTER_CARRIERS' + | translate + }} + + +
+ + + + {{ item.name }} + + +

+ + {{ item.name }} +

+
+
+ + + + {{ item.fullName }} + + +

+ + {{ item.fullName }} +

+
+
+
+ +
+
+
+ {{ selectedStore.name }} +
+
    +
  • + {{ + 'CARRIERS_VIEW.TRACK_PAGE.PHONE' + | translate + }}: + {{ selectedStore.contactPhone }} +
  • +
  • + {{ + 'CARRIERS_VIEW.TRACK_PAGE.EMAIL' + | translate + }}: + {{ selectedStore.contactEmail }} +
  • +
  • + {{ + 'CARRIERS_VIEW.TRACK_PAGE.ADDRESS' + | translate + }}: + {{ + selectedStore.geoLocation + .streetAddress + }} +
  • +
+
+
+
+ {{ selectedCarrier.fullName }} +
+
    +
  • + {{ + 'CARRIERS_VIEW.TRACK_PAGE.DELIVERY_COUNT' + | translate + }}: + {{ + selectedCarrier.numberOfDeliveries + }} +
  • +
  • + {{ + 'CARRIERS_VIEW.TRACK_PAGE.PHONE' + | translate + }}: {{ selectedCarrier.phone }} +
  • +
  • + {{ + 'CARRIERS_VIEW.TRACK_PAGE.ADDRESS' + | translate + }}: + {{ + selectedCarrier.geoLocation + .streetAddress + }} +
  • +
+
+
+
+
+
+
+
+
+
diff --git a/packages/admin-web-angular/src/app/pages/+carriers/track/carrier-tracking/carrier-tracking.component.scss b/packages/admin-web-angular/src/app/pages/+carriers/track/carrier-tracking/carrier-tracking.component.scss new file mode 100644 index 0000000..9d2e362 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+carriers/track/carrier-tracking/carrier-tracking.component.scss @@ -0,0 +1,4 @@ +.googleMap { + width: 100%; + height: 60vh; +} diff --git a/packages/admin-web-angular/src/app/pages/+carriers/track/carrier-tracking/carrier-tracking.component.ts b/packages/admin-web-angular/src/app/pages/+carriers/track/carrier-tracking/carrier-tracking.component.ts new file mode 100644 index 0000000..3504018 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+carriers/track/carrier-tracking/carrier-tracking.component.ts @@ -0,0 +1,203 @@ +import { + Component, + Input, + Output, + EventEmitter, + ViewChild, + OnInit, + OnDestroy, +} from '@angular/core'; +import { CarrierRouter } from '@modules/client.common.angular2/routers/carrier-router.service'; +import { Subject, Subscription } from 'rxjs'; +import { ActivatedRoute, Router } from '@angular/router'; +import { CarrierOrdersRouter } from '@modules/client.common.angular2/routers/carrier-orders-router.service'; +import { CarriersService } from '@app/@core/data/carriers.service'; +import { environment } from 'environments/environment'; +import GeoLocation from '@modules/server.common/entities/GeoLocation'; +import Warehouse from '@modules/server.common/entities/Warehouse'; +import { NgSelectModule } from '@ng-select/ng-select'; +import { FormsModule } from '@angular/forms'; +import { WarehousesService } from '@app/@core/data/warehouses.service'; +import Carrier from '@modules/server.common/entities/Carrier'; +import { ICarrierCreateObject } from '@modules/server.common/interfaces/ICarrier'; +import { takeUntil } from 'rxjs/operators'; + +declare var google: any; +const directionsDisplay = new google.maps.DirectionsRenderer(); +const directionsService = new google.maps.DirectionsService(); + +@Component({ + selector: 'ea-carrier-tracking', + styleUrls: ['carrier-tracking.component.scss'], + templateUrl: 'carrier-tracking.component.html', +}) +export class CarrierTrackingComponent implements OnInit, OnDestroy { + private ngDestroy$ = new Subject(); + public carrierId: string; + + @ViewChild('gmap', { static: true }) + gmapElement: any; + map: google.maps.Map; + carrierSub$: any; + marker: any; + userMarker: any; + warehouseMarkers = []; + interval: NodeJS.Timer; + isReverted: boolean = true; + params$: Subscription; + selectedCarrier: Carrier; + carriers: Carrier[] = []; + selectedStore: Warehouse; + filteredCarriersList: Carrier[] = []; + + constructor( + private route: ActivatedRoute, + private router: Router, + private carrierRouter: CarrierRouter, + private carrierOrdersRouter: CarrierOrdersRouter, + private carriersService: CarriersService, + private readonly _storesService: WarehousesService + ) {} + + ngOnInit(): void { + this.showMap(); + this.getCarriers(); + } + + getCarriers() { + this._storesService + .getStores() + .pipe(takeUntil(this.ngDestroy$)) + .subscribe((stores) => { + this.stores = stores; + this.carriersService + .getAllCarriers() + .pipe(takeUntil(this.ngDestroy$)) + .subscribe((carriers) => { + this.carriers = carriers.filter( + (carrier) => carrier.status === 0 + ); + this.filteredCarriersList = this.carriers; + this.loadDataFromUrl(); + }); + }); + } + + public stores: Warehouse[] = []; + @Output() + selectedStoreEmitter = new EventEmitter(); + + selectNewStore(id) { + this.selectedStore = this.stores.find((s) => s.id === id); + } + + storeListener(e) { + this.router.navigate([`carriers/track/${this.selectedStore.id}`]); + } + + carrierListener(e) { + if (this.selectedStore) { + this.router.navigate([ + `carriers/track/${this.selectedStore.id}/${this.selectedCarrier.id}`, + ]); + } else { + this.router.navigate([ + `carriers/track/1/${this.selectedCarrier.id}`, + ]); + } + } + + loadDataFromUrl() { + this.params$ = this.route.params.subscribe((res) => { + if (!res.carrierId && res.storeId) { + this.selectNewStore(res.storeId); + this.filteredCarriersList = this.carriers.filter((x) => + this.selectedStore.usedCarriersIds.includes(x.id) + ); + this.selectedCarrier = undefined; + this.revertMap(); + this._subscribeCarrier(this.filteredCarriersList); + } else if (res.carrierId) { + this.selectNewStore(res.storeId); + const filteredList = this.filteredCarriersList.filter( + (carrier) => carrier._id === res.carrierId + ); + this.selectedCarrier = filteredList[0]; + this.revertMap(); + this._subscribeCarrier(filteredList); + } else if (!res.carrierId && !res.storeId) { + this._subscribeCarrier(this.filteredCarriersList); + } + }); + } + + async _subscribeCarrier(carrierList: Carrier[]) { + const idArray = carrierList.map((carrier) => carrier._id); + idArray.forEach((c) => { + const carrierId = c.toString(); + this.carrierSub$ = this.carrierRouter + .get(carrierId) + .subscribe(async (carrier) => { + if (this.interval) { + clearInterval(this.interval); + } + const newCoordinates = new google.maps.LatLng( + carrier.geoLocation.coordinates.lat, + carrier.geoLocation.coordinates.lng + ); + this.map.setCenter(newCoordinates); + const carierIcon = environment.MAP_CARRIER_ICON_LINK; + + const marker = this.addMarker( + newCoordinates, + this.map, + carierIcon + ); + this.warehouseMarkers.push(marker); + }); + }); + } + + revertMap() { + if (this.warehouseMarkers.length > 0) { + this.warehouseMarkers.forEach((x) => { + x.setMap(null); + }); + this.warehouseMarkers = []; + } + } + + showMap() { + const mapProp = { + center: new google.maps.LatLng(42.642941, 23.334149), + zoom: 15, + mapTypeId: google.maps.MapTypeId.ROADMAP, + }; + this.map = new google.maps.Map(this.gmapElement.nativeElement, mapProp); + } + + addMarker(position, map, icon) { + return new google.maps.Marker({ + position, + map, + icon, + }); + } + + ngOnDestroy() { + this.ngDestroy$.next(true); + this.ngDestroy$.complete(); + this.carriers = []; + if (this.interval) { + clearInterval(this.interval); + } + + if (this.carrierSub$) { + this.carrierSub$.unsubscribe(); + } + + if (this.params$) { + this.params$.unsubscribe(); + } + } +} diff --git a/packages/admin-web-angular/src/app/pages/+carriers/track/track.component.html b/packages/admin-web-angular/src/app/pages/+carriers/track/track.component.html new file mode 100644 index 0000000..8929415 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+carriers/track/track.component.html @@ -0,0 +1 @@ + diff --git a/packages/admin-web-angular/src/app/pages/+carriers/track/track.component.scss b/packages/admin-web-angular/src/app/pages/+carriers/track/track.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/packages/admin-web-angular/src/app/pages/+carriers/track/track.component.ts b/packages/admin-web-angular/src/app/pages/+carriers/track/track.component.ts new file mode 100644 index 0000000..30995a6 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+carriers/track/track.component.ts @@ -0,0 +1,18 @@ +import { Component, OnInit } from '@angular/core'; +import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; + +@Component({ + templateUrl: './track.component.html', + styleUrls: ['./track.component.scss'], +}) +export class TrackComponent implements OnInit { + public carrierId: string; + + constructor(private readonly activeModal: NgbActiveModal) {} + + cancel() { + this.activeModal.dismiss('canceled'); + } + + ngOnInit(): void {} +} diff --git a/packages/admin-web-angular/src/app/pages/+carriers/track/track.module.ts b/packages/admin-web-angular/src/app/pages/+carriers/track/track.module.ts new file mode 100644 index 0000000..26dad1f --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+carriers/track/track.module.ts @@ -0,0 +1,27 @@ +import { NgModule } from '@angular/core'; +import { CarrierTrackingComponent } from './carrier-tracking/carrier-tracking.component'; +import { TrackComponent } from './track.component'; +import { FormWizardModule } from '@ever-co/angular2-wizard'; +import { Ng2SmartTableModule } from 'ng2-smart-table'; +import { FileUploadModule } from 'ng2-file-upload'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { TranslateModule } from '@ngx-translate/core'; +import { ThemeModule } from '@app/@theme'; +import { NgSelectModule } from '@ng-select/ng-select'; +import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; + +@NgModule({ + imports: [ + FormWizardModule, + Ng2SmartTableModule, + FileUploadModule, + FormsModule, + ReactiveFormsModule, + TranslateModule.forChild(), + ThemeModule, + NgSelectModule, + ], + declarations: [TrackComponent, CarrierTrackingComponent], + providers: [NgbActiveModal] +}) +export class TrackModule {} diff --git a/packages/admin-web-angular/src/app/pages/+customers/+customer/+customer-edit/customer-edit.component.html b/packages/admin-web-angular/src/app/pages/+customers/+customer/+customer-edit/customer-edit.component.html new file mode 100644 index 0000000..e4a752a --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+customers/+customer/+customer-edit/customer-edit.component.html @@ -0,0 +1,102 @@ + + +
+ + +
+ + + + +

{{ 'CUSTOMERS_VIEW.EDIT.EDIT_CUSTOMER' | translate }}

+
+ + +
+
+ +
+
+ + +
+ {{ 'CUSTOMERS_VIEW.EDIT.BASIC_INFO' | translate }} +
+
+ + + + +
+
+ +
+ + +
{{ 'LOCATION' | translate }}
+ + + + +
+ + + + + + + + + + +
+
+
+
diff --git a/packages/admin-web-angular/src/app/pages/+customers/+customer/+customer-edit/customer-edit.component.scss b/packages/admin-web-angular/src/app/pages/+customers/+customer/+customer-edit/customer-edit.component.scss new file mode 100644 index 0000000..518a940 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+customers/+customer/+customer-edit/customer-edit.component.scss @@ -0,0 +1,25 @@ +@import '../../../../@theme/styles/themes'; +@import '../../../../@shared/styles/control-icon.shared'; + +@include nb-install-component() { + @include control-icon; +} + +nb-card-header { + min-height: 71px; + position: relative; + + .text { + position: absolute; + top: 50%; + transform: translateY(-50%); + } + + #map-type { + float: right; + } + + .control-icon-left { + cursor: pointer; + } +} diff --git a/packages/admin-web-angular/src/app/pages/+customers/+customer/+customer-edit/customer-edit.component.ts b/packages/admin-web-angular/src/app/pages/+customers/+customer/+customer-edit/customer-edit.component.ts new file mode 100644 index 0000000..05d6a92 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+customers/+customer/+customer-edit/customer-edit.component.ts @@ -0,0 +1,120 @@ +import { Component, ViewChild, EventEmitter, OnInit } from '@angular/core'; +import { FormBuilder, FormControl, FormGroup } from '@angular/forms'; +import { ActivatedRoute, Router } from '@angular/router'; +import { first } from 'rxjs/operators'; +import { ToasterService } from 'angular2-toaster'; +import { BasicInfoFormComponent } from '../../../../@shared/user/forms'; +import { LocationFormComponent } from '../../../../@shared/forms/location'; +import { UserRouter } from '@modules/client.common.angular2/routers/user-router.service'; +import IGeoLocation from '@modules/server.common/interfaces/IGeoLocation'; +import User from '@modules/server.common/entities/User'; + +@Component({ + templateUrl: './customer-edit.component.html', + styleUrls: ['./customer-edit.component.scss'], +}) +export class CustomerEditComponent implements OnInit { + @ViewChild('basicInfoForm') + basicInfoForm: BasicInfoFormComponent; + + @ViewChild('locationForm') + locationForm: LocationFormComponent; + + mapTypeEmitter = new EventEmitter(); + mapCoordEmitter = new EventEmitter(); + mapGeometryEmitter = new EventEmitter(); + + public loading: boolean; + + readonly form: FormGroup = this._formBuilder.group({ + basicInfo: BasicInfoFormComponent.buildForm(this._formBuilder), + location: LocationFormComponent.buildForm(this._formBuilder), + }); + + readonly basicInfo = this.form.get('basicInfo') as FormControl; + readonly location = this.form.get('location') as FormControl; + + public _currentCustomer: User; + + constructor( + private readonly _activatedRoute: ActivatedRoute, + private readonly _router: Router, + private readonly _formBuilder: FormBuilder, + private readonly _customerRouter: UserRouter, + private readonly _toasterService: ToasterService + ) {} + + ngOnInit() { + const id = this._activatedRoute.snapshot.params.id; + + this._customerRouter + .get(id) + .pipe(first()) + .subscribe((customer) => { + if (!customer) { + this._toasterService.pop( + 'error', + `Customer with id ${id} doesn't exist!` + ); + } + + this._currentCustomer = customer; + + // GeoJSON use reversed order of lat => lng + const geoLocationInput = customer.geoLocation; + geoLocationInput.loc.coordinates.reverse(); + + this.basicInfoForm.setValue(customer); + this.locationForm.setValue(geoLocationInput); + this._emitMapCoordinates([ + customer.geoLocation.coordinates.lat, + customer.geoLocation.coordinates.lng, + ]); + }); + } + + onCoordinatesChanges(coords: number[]) { + this.mapCoordEmitter.emit(coords); + } + + onGeometrySend(geometry: any) { + this.mapGeometryEmitter.emit(geometry); + } + + emitMapType(mapType: string) { + this.mapTypeEmitter.emit(mapType); + } + + private _emitMapCoordinates(coords: number[]) { + this.mapCoordEmitter.emit(coords); + } + + protected async updateCustomer() { + const geoLocationInput = this.locationForm.getValue(); + geoLocationInput.loc.coordinates.reverse(); + try { + this.loading = true; + const customer = await this._customerRouter.updateUser( + this._currentCustomer.id, + { + ...this.basicInfoForm.getValue(), + geoLocation: geoLocationInput as IGeoLocation, + } + ); + this.loading = false; + this._toasterService.pop( + 'success', + `Customer ${customer.firstName} was updated` + ); + await this._router.navigate([`/customers/list/${customer.id}`], { + relativeTo: this._activatedRoute, + }); + } catch (err) { + this.loading = false; + this._toasterService.pop( + 'error', + `Error in updating customer: "${err.message}"` + ); + } + } +} diff --git a/packages/admin-web-angular/src/app/pages/+customers/+customer/+customer-edit/customer-edit.module.ts b/packages/admin-web-angular/src/app/pages/+customers/+customer/+customer-edit/customer-edit.module.ts new file mode 100644 index 0000000..3b2b40e --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+customers/+customer/+customer-edit/customer-edit.module.ts @@ -0,0 +1,34 @@ +import { RouterModule, Routes } from '@angular/router'; +import { TranslateModule } from '@ngx-translate/core'; +import { NgModule } from '@angular/core'; +import { ThemeModule } from '../../../../@theme'; +import { UserFormsModule } from '../../../../@shared/user/forms'; +import { LocationFormModule } from '../../../../@shared/forms/location'; +import { GoogleMapModule } from '../../../../@shared/forms/google-map/google-map.module'; +import { CustomerEditComponent } from './customer-edit.component'; +import { ToasterModule } from 'angular2-toaster'; +import { NbSpinnerModule, NbButtonModule } from '@nebular/theme'; + +const routes: Routes = [ + { + path: '', + component: CustomerEditComponent, + }, +]; + +@NgModule({ + imports: [ + ThemeModule, + ToasterModule, + TranslateModule.forChild(), + RouterModule.forChild(routes), + + UserFormsModule, + LocationFormModule, + GoogleMapModule, + NbSpinnerModule, + NbButtonModule, + ], + declarations: [CustomerEditComponent], +}) +export class CustomerEditModule {} diff --git a/packages/admin-web-angular/src/app/pages/+customers/+customer/customer.component.html b/packages/admin-web-angular/src/app/pages/+customers/+customer/customer.component.html new file mode 100644 index 0000000..ef83833 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+customers/+customer/customer.component.html @@ -0,0 +1,131 @@ + + +
+
+ + + + + + + +
+

+ {{ + 'CUSTOMERS_VIEW.CUSTOMER_VIEW.CUSTOMER' | translate + }} +

+

+ {{ + 'CUSTOMERS_VIEW.CUSTOMER_VIEW.MANAGE_CUSTOMER' + | translate + }} +

+
+
+ +
+
+ + + + + + + + + + + + + + +
+
+ +
+ + + {{ 'CUSTOMERS_VIEW.CUSTOMER_VIEW.CUSTOMER' | translate }} + + + + + + + {{ item.firstName }} + {{ item.firstName && item.lastName }} + {{ item.firstName ? '' : '# ' + item.id }} + + +

+ + {{ item.firstName }} + {{ item.firstName && item.lastName }} + {{ item.firstName ? '' : '# ' + item.id }} +

+
+
+
+
+ + + + + + +
+
diff --git a/packages/admin-web-angular/src/app/pages/+customers/+customer/customer.component.scss b/packages/admin-web-angular/src/app/pages/+customers/+customer/customer.component.scss new file mode 100644 index 0000000..63f2d19 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+customers/+customer/customer.component.scss @@ -0,0 +1,40 @@ +@import '../../../@theme/styles/themes'; +@import '../../../@shared/styles/control-icon.shared'; + +@include nb-install-component() { + @include control-icon; + + .btn-color { + background: #4caf50; + cursor: pointer; + } + .btn-color:hover { + background: #4caf50; + cursor: pointer; + } + + .btn-color.btn-outline-success { + background: #4caf50; + cursor: pointer; + } + .btn-color.btn-outline-success:hover { + background: #4caf50; + cursor: pointer; + } + + .edit-wrapper { + button { + float: right; + } + } + + .control-icon-left { + margin: 0 !important; + } +} + +nb-card#user-select-users { + nb-card-body { + overflow: inherit !important; + } +} diff --git a/packages/admin-web-angular/src/app/pages/+customers/+customer/customer.component.ts b/packages/admin-web-angular/src/app/pages/+customers/+customer/customer.component.ts new file mode 100644 index 0000000..f0b4ecf --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+customers/+customer/customer.component.ts @@ -0,0 +1,64 @@ +import { Component, OnInit, OnDestroy } from '@angular/core'; +import { ActivatedRoute, Router } from '@angular/router'; +import { UsersService } from '../../../@core/data/users.service'; +import User from '@modules/server.common/entities/User'; +import { Observable, Subject } from 'rxjs'; +import { first, takeUntil, switchMap } from 'rxjs/operators'; + +@Component({ + selector: 'ea-customer', + templateUrl: './customer.component.html', + styleUrls: ['./customer.component.scss'], +}) +export class CustomerComponent implements OnInit, OnDestroy { + user$: Observable; + users: User[] = []; + selectedUser: User; + + private _ngDestroy$ = new Subject(); + + constructor( + private readonly _userService: UsersService, + private readonly _router: ActivatedRoute, + private readonly _route: Router + ) {} + + ngOnInit() { + this.user$ = this._router.params.pipe( + switchMap((p) => { + return this._userService.getUserById(p.id); + }) + ); + + (async () => { + return this.loadUsers(); + })(); + } + + ngOnDestroy() { + this._ngDestroy$.next(); + this._ngDestroy$.complete(); + } + + async loadUsers() { + this._userService + .getUsers() + .pipe(takeUntil(this._ngDestroy$)) + .subscribe((users) => { + this.users = users; + this._selectCurrentUser(); + }); + } + + async customerSelect(e) { + this._route.navigate([`/customers/list/${e.id}`]); + await this.loadUsers(); + } + + private async _selectCurrentUser() { + const routeParams = await this._router.params.pipe(first()).toPromise(); + this.selectedUser = this.users.filter( + (u) => u.id === routeParams.id + )[0]; + } +} diff --git a/packages/admin-web-angular/src/app/pages/+customers/+customer/customer.module.ts b/packages/admin-web-angular/src/app/pages/+customers/+customer/customer.module.ts new file mode 100644 index 0000000..0d05cf5 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+customers/+customer/customer.module.ts @@ -0,0 +1,69 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { RouterModule } from '@angular/router'; +import { ToasterModule } from 'angular2-toaster'; +import { TranslateModule } from '@ngx-translate/core'; +import { Ng2SmartTableModule } from 'ng2-smart-table'; +import { HighlightModule } from 'ngx-highlightjs'; +import { ThemeModule } from '../../../@theme'; +import { CustomerComponent } from './customer.component'; +import { CustomerInfoComponent } from './ea-customer-info/ea-customer-info.component'; +import { CustomerProductsComponent } from './ea-customer-products/ea-customer-products.component'; +import { CustomerLocationComponent } from './ea-customer-location/ea-customer-location.component'; +import { CustomerStoresComponent } from './ea-customer-stores/ea-customer-stores.component'; +import { CustomerOrdersTableModule } from '../../../@shared/render-component/customer-orders-table/customer-orders-table.module'; +import { CustomerProductsTableModule } from '../../../@shared/render-component/customer-products-table/customer-products-table.module'; +import { RenderComponentsModule } from '../../../@shared/render-component/render-components.module'; +import { CustomerWarehousesTableModule } from '../../../@shared/warehouse/customer-warehouses-table/customer-warehouses-table.module'; +import { WarehouseMutationModule } from '../../../@shared/warehouse/warehouse-mutation'; +import { WarehouseOrderModalModule } from '../../../@shared/warehouse/+warehouse-order-modal/warehouse-order-modal.module'; +import { CustomerOrdersModule } from './ea-customer-orders/ea-customer-orders.module'; +import { CustomerMetricsComponent } from './ea-customer-metrics/ea-customer-metrics.component'; +import { NgSelectModule } from '@ng-select/ng-select'; +import { FormsModule } from '@angular/forms'; +import { NbButtonModule } from '@nebular/theme'; + +const routes = [ + { + path: '', + component: CustomerComponent, + }, + { + path: 'edit', + loadChildren: () => + import('./+customer-edit/customer-edit.module').then( + (m) => m.CustomerEditModule + ), + }, +]; + +@NgModule({ + imports: [ + CommonModule, + Ng2SmartTableModule, + ThemeModule, + ToasterModule.forRoot(), + RouterModule.forChild(routes), + TranslateModule.forChild(), + HighlightModule, + RenderComponentsModule, + WarehouseMutationModule, + CustomerOrdersTableModule, + CustomerProductsTableModule, + CustomerWarehousesTableModule, + WarehouseOrderModalModule, + CustomerOrdersModule, + NgSelectModule, + FormsModule, + NbButtonModule, + ], + declarations: [ + CustomerComponent, + CustomerLocationComponent, + CustomerInfoComponent, + CustomerProductsComponent, + CustomerStoresComponent, + CustomerMetricsComponent + ] +}) +export class CustomerModule {} diff --git a/packages/admin-web-angular/src/app/pages/+customers/+customer/ea-customer-info/ea-customer-info.component.html b/packages/admin-web-angular/src/app/pages/+customers/+customer/ea-customer-info/ea-customer-info.component.html new file mode 100644 index 0000000..56675f6 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+customers/+customer/ea-customer-info/ea-customer-info.component.html @@ -0,0 +1,49 @@ + + + {{ 'CUSTOMERS_VIEW.CUSTOMER' | translate }} #{{ user.id }} + + +
+
+
+ {{ user.firstName }} {{ user.lastName }} +
+
{{ user.email }}
+
{{ user.phone }}
+
+ +
+
+ {{ user.geoLocation.streetAddress }}, + {{ user.geoLocation.house }} + {{ user.apartment && ', Ap.' + user.apartment }} +
+
+ {{ user.geoLocation.city }} + {{ + user.geoLocation.countryName && + ', ' + user.geoLocation.countryName + }} +
+
Notes: {{ user.geoLocation.notes }}
+
+
+ + +
+ + +
+
+
diff --git a/packages/admin-web-angular/src/app/pages/+customers/+customer/ea-customer-info/ea-customer-info.component.scss b/packages/admin-web-angular/src/app/pages/+customers/+customer/ea-customer-info/ea-customer-info.component.scss new file mode 100644 index 0000000..1dbcba7 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+customers/+customer/ea-customer-info/ea-customer-info.component.scss @@ -0,0 +1,3 @@ +.info { + margin-bottom: 1em; +} diff --git a/packages/admin-web-angular/src/app/pages/+customers/+customer/ea-customer-info/ea-customer-info.component.ts b/packages/admin-web-angular/src/app/pages/+customers/+customer/ea-customer-info/ea-customer-info.component.ts new file mode 100644 index 0000000..ea6958d --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+customers/+customer/ea-customer-info/ea-customer-info.component.ts @@ -0,0 +1,34 @@ +import { Component, OnDestroy } from '@angular/core'; +import User from '@modules/server.common/entities/User'; +import { UsersService } from '../../../../@core/data/users.service'; +import { ActivatedRoute } from '@angular/router'; +import { first } from 'rxjs/operators'; + +@Component({ + selector: 'ea-customer-info', + styleUrls: ['ea-customer-info.component.scss'], + templateUrl: './ea-customer-info.component.html', +}) +export class CustomerInfoComponent implements OnDestroy { + showCode: boolean = false; + params$: any; + user: User; + + constructor( + private readonly _userService: UsersService, + private readonly _router: ActivatedRoute + ) { + this.params$ = this._router.params.subscribe(async (r) => { + this.user = await this._userService + .getUserById(r.id) + .pipe(first()) + .toPromise(); + }); + } + + ngOnDestroy(): void { + if (this.params$) { + this.params$.unsubscribe(); + } + } +} diff --git a/packages/admin-web-angular/src/app/pages/+customers/+customer/ea-customer-info/ea-customer-info.stories.ts b/packages/admin-web-angular/src/app/pages/+customers/+customer/ea-customer-info/ea-customer-info.stories.ts new file mode 100644 index 0000000..46cf883 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+customers/+customer/ea-customer-info/ea-customer-info.stories.ts @@ -0,0 +1,139 @@ +var user = { + _id: '5cc0925e8979b91ee93c86a1', + firstName: 'Maxwell', + lastName: 'Mante', + image: 'https://s3.amazonaws.com/uifaces/faces/twitter/jm_denis/128.jpg', + email: 'Mason.Kutch33@hotmail.com', + apartment: '138', + phone: '1-566-610-8055 x749', + geoLocation: { + streetAddress: '139 Lebsack Parks', + city: 'Angelineland', + house: '150', + loc: { + type: 'Point', + coordinates: [23.3332736, 42.6459136], + __typename: 'Loc', + }, + __typename: 'GeoLocation', + }, + __typename: 'User', +}; + +import { storiesOf, moduleMetadata } from '@storybook/angular'; + +import { withKnobs, object } from '@storybook/addon-knobs'; +import { ThemeModule } from '@app/@theme'; +import { CommonModule, APP_BASE_HREF } from '@angular/common'; +import { Ng2SmartTableModule } from 'ng2-smart-table'; +import { NbSpinnerModule } from '@nebular/theme'; +import { ConfirmationModalModule } from '@app/@shared/confirmation-modal/confirmation-modal.module'; +import { ToasterModule } from 'angular2-toaster'; +import { + TranslateModule, + TranslateStore, + TranslateService, +} from '@ngx-translate/core'; +import { RouterModule } from '@angular/router'; +import { NbAuthModule } from '@nebular/auth'; +import { NotifyService } from '@app/@core/services/notify/notify.service'; +import { APOLLO_OPTIONS } from 'apollo-angular'; +import { HttpLink } from 'apollo-angular/http'; +import { InMemoryCache } from '@apollo/client/core'; +import { PipesModule } from '@modules/client.common.angular2/pipes/pipes.module'; +import { HttpClient, HttpClientModule } from '@angular/common/http'; +import { TranslateHttpLoader } from '@ngx-translate/http-loader'; +import { NgSelectModule } from '@ng-select/ng-select'; +import { I18nModule } from '@app/@core/utils/i18n.module'; +import { CustomerInfoComponent } from './ea-customer-info.component'; +import { HighlightModule } from 'ngx-highlightjs'; +import { RenderComponentsModule } from '@app/@shared/render-component/render-components.module'; +import { WarehouseMutationModule } from '@app/@shared/warehouse/warehouse-mutation'; +import { CustomerOrdersTableModule } from '@app/@shared/render-component/customer-orders-table/customer-orders-table.module'; +import { CustomerProductsTableModule } from '@app/@shared/render-component/customer-products-table/customer-products-table.module'; +import { CustomerWarehousesTableModule } from '@app/@shared/warehouse/customer-warehouses-table/customer-warehouses-table.module'; +import { WarehouseOrderModalModule } from '@app/@shared/warehouse/+warehouse-order-modal/warehouse-order-modal.module'; +import { CustomerOrdersModule } from '../ea-customer-orders/ea-customer-orders.module'; +import { FormsModule } from '@angular/forms'; +import { CustomerComponent } from '../customer.component'; +import { CustomerLocationComponent } from '../ea-customer-location/ea-customer-location.component'; +import { CustomerProductsComponent } from '../ea-customer-products/ea-customer-products/ea-customer-products.component'; +import { CustomerStoresComponent } from '../ea-customer-stores/ea-customer-stores.component'; +import { CustomerMetricsComponent } from '../ea-customer-metrics/ea-customer-metrics.component'; +import { UsersService } from '@app/@core/data/users.service'; + +const stories = storiesOf('Customer Info', module); + +export function createApollo(httpLink: HttpLink) { + return { + link: httpLink.create({ uri: 'https://api.example.com/graphql' }), + cache: new InMemoryCache(), + }; +} + +export function createTranslateLoader(http: HttpClient) { + return new TranslateHttpLoader(http, '/i18n/', '.json'); +} + +stories.addDecorator(withKnobs); +stories.addDecorator( + moduleMetadata({ + declarations: [ + CustomerComponent, + CustomerLocationComponent, + CustomerInfoComponent, + CustomerProductsComponent, + CustomerStoresComponent, + CustomerMetricsComponent, + ], + imports: [ + NgSelectModule, + CommonModule, + ThemeModule, + Ng2SmartTableModule, + NbSpinnerModule, + ConfirmationModalModule, + ToasterModule.forRoot(), + HttpClientModule, + I18nModule, + NbAuthModule, + PipesModule, + CommonModule, + Ng2SmartTableModule, + ThemeModule, + ToasterModule.forRoot(), + RouterModule.forRoot([]), + TranslateModule.forChild(), + HighlightModule, + RenderComponentsModule, + WarehouseMutationModule, + CustomerOrdersTableModule, + CustomerProductsTableModule, + CustomerWarehousesTableModule, + WarehouseOrderModalModule, + CustomerOrdersModule, + NgSelectModule, + FormsModule, + ], + providers: [ + { + provide: APOLLO_OPTIONS, + useFactory: createApollo, + deps: [HttpLink], + }, + TranslateStore, + NotifyService, + TranslateService, + HttpLink, + UsersService, + { provide: APP_BASE_HREF, useValue: '/' }, + ], + }) +); + +stories.add('Customer Info', () => ({ + component: CustomerInfoComponent, + props: { + user: object('User', user), + }, +})); diff --git a/packages/admin-web-angular/src/app/pages/+customers/+customer/ea-customer-location/ea-customer-location.component.html b/packages/admin-web-angular/src/app/pages/+customers/+customer/ea-customer-location/ea-customer-location.component.html new file mode 100644 index 0000000..a2e4b30 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+customers/+customer/ea-customer-location/ea-customer-location.component.html @@ -0,0 +1,4 @@ + + Location +
+
diff --git a/packages/admin-web-angular/src/app/pages/+customers/+customer/ea-customer-location/ea-customer-location.component.scss b/packages/admin-web-angular/src/app/pages/+customers/+customer/ea-customer-location/ea-customer-location.component.scss new file mode 100644 index 0000000..bbbf69b --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+customers/+customer/ea-customer-location/ea-customer-location.component.scss @@ -0,0 +1,22 @@ +@import '../../../../@theme/styles/themes'; + +@include nb-install-component() { + nb-card-body { + padding: 0; + } + + ::ng-deep .leaflet-container { + width: 100%; + // height: nb-theme(card-height-large); + } +} + +.header-color { + color: rgb(24, 22, 22); + background-color: rgb(241, 239, 239); +} + +.googleMap { + width: 100%; + height: 200px !important; +} diff --git a/packages/admin-web-angular/src/app/pages/+customers/+customer/ea-customer-location/ea-customer-location.component.ts b/packages/admin-web-angular/src/app/pages/+customers/+customer/ea-customer-location/ea-customer-location.component.ts new file mode 100644 index 0000000..6fe8ad2 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+customers/+customer/ea-customer-location/ea-customer-location.component.ts @@ -0,0 +1,61 @@ +import { Component, OnDestroy, OnInit, ViewChild, Input } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; +import { UsersService } from '../../../../@core/data/users.service'; +import { first } from 'rxjs/operators'; + +declare var google: any; +@Component({ + selector: 'ea-customer-location', + styleUrls: ['./ea-customer-location.component.scss'], + templateUrl: './ea-customer-location.component.html', +}) +export class CustomerLocationComponent implements OnDestroy, OnInit { + @ViewChild('gmap', { static: true }) + gmapElement: any; + map: google.maps.Map; + marker: any; + params$: any; + + constructor( + private readonly _userService: UsersService, + private readonly _router: ActivatedRoute + ) {} + + ngOnInit(): void { + this.params$ = this._router.params.subscribe(async (r) => { + const user = await this._userService + .getUserById(r.id) + .pipe(first()) + .toPromise(); + const coordinates = new google.maps.LatLng( + user['geoLocation'].coordinates.lat, + user['geoLocation'].coordinates.lng + ); + this.showMap(coordinates); + this.marker = this.addMarker(coordinates, this.map); + }); + } + + showMap(coordinates) { + const mapProp = { + center: coordinates, + zoom: 17, + mapTypeId: google.maps.MapTypeId.ROADMAP, + }; + this.map = new google.maps.Map(this.gmapElement.nativeElement, mapProp); + } + + addMarker(coordinates, map) { + return new google.maps.Marker({ + position: coordinates, + map, + }); + } + ngOnDestroy(): void { + this.marker.setMap(null); + + if (this.params$) { + this.params$.unsubscribe(); + } + } +} diff --git a/packages/admin-web-angular/src/app/pages/+customers/+customer/ea-customer-metrics/ea-customer-metrics.component.html b/packages/admin-web-angular/src/app/pages/+customers/+customer/ea-customer-metrics/ea-customer-metrics.component.html new file mode 100644 index 0000000..f0428b5 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+customers/+customer/ea-customer-metrics/ea-customer-metrics.component.html @@ -0,0 +1,21 @@ + + + {{ 'CUSTOMERS_VIEW.ORDERS_STATISTICS' | translate }} + + +
+

+ {{ 'CUSTOMERS_VIEW.NUMBER_OF_ORDERS' | translate }}: + {{ userMetrics.totalOrders }} +

+

+ {{ 'CUSTOMERS_VIEW.CANCELED_ORDERS' | translate }}: + {{ userMetrics.canceledOrders }} +

+

+ {{ 'CUSTOMERS_VIEW.COMPLETED_ORDERS_TOTAL' | translate }}: + ${{ userMetrics.completedOrdersTotalSum }} +

+
+
+
diff --git a/packages/admin-web-angular/src/app/pages/+customers/+customer/ea-customer-metrics/ea-customer-metrics.component.ts b/packages/admin-web-angular/src/app/pages/+customers/+customer/ea-customer-metrics/ea-customer-metrics.component.ts new file mode 100644 index 0000000..beee173 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+customers/+customer/ea-customer-metrics/ea-customer-metrics.component.ts @@ -0,0 +1,31 @@ +import { Component, OnDestroy } from '@angular/core'; +import User from '@modules/server.common/entities/User'; +import { ActivatedRoute } from '@angular/router'; +import { Subscription } from 'rxjs'; +import { UsersService } from '@app/@core/data/users.service'; + +@Component({ + selector: 'ea-customer-metrics', + templateUrl: './ea-customer-metrics.component.html', +}) +export class CustomerMetricsComponent implements OnDestroy { + showCode: boolean = false; + params$: Subscription; + user: User; + userMetrics: any; + + constructor( + private readonly _router: ActivatedRoute, + private userService: UsersService + ) { + this.params$ = this._router.params.subscribe(async (r) => { + this.userMetrics = await this.userService.getCustomerMetrics(r.id); + }); + } + + ngOnDestroy(): void { + if (this.params$) { + this.params$.unsubscribe(); + } + } +} diff --git a/packages/admin-web-angular/src/app/pages/+customers/+customer/ea-customer-orders/ea-customer-orders.component.html b/packages/admin-web-angular/src/app/pages/+customers/+customer/ea-customer-orders/ea-customer-orders.component.html new file mode 100644 index 0000000..cbda39f --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+customers/+customer/ea-customer-orders/ea-customer-orders.component.html @@ -0,0 +1,8 @@ + + + + + diff --git a/packages/admin-web-angular/src/app/pages/+customers/+customer/ea-customer-orders/ea-customer-orders.component.scss b/packages/admin-web-angular/src/app/pages/+customers/+customer/ea-customer-orders/ea-customer-orders.component.scss new file mode 100644 index 0000000..3ef0b79 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+customers/+customer/ea-customer-orders/ea-customer-orders.component.scss @@ -0,0 +1,60 @@ +@import '../../../../@theme/styles/themes'; + +@include nb-install-component() { + .padding { + padding: 2%; + } + + .padding-bottom { + padding-bottom: 2%; + } + .margin { + margin: 1%; + } + + nb-card-footer { + padding: 0; + } + + ::ng-deep ng2-smart-table { + .redirectBtn { + font-size: 0.9em !important; + text-align: left !important; + width: 100% !important; + cursor: pointer; + } + .iconBtns { + display: inline-block !important; + margin-right: 5px; + } + .iconsCont { + width: 80%; + margin: 0 auto; + text-align: center; + } + .warhouseBtn { + img { + display: inline-block; + margin: 5 auto; + } + } + .warnCancelled { + border: none; + color: #ff6780; + text-align: center; + display: inline-block; + width: 100%; + font-weight: bold; + text-transform: uppercase; + font-size: 0.9em; + } + } + + nb-card-body { + padding: 0; + } + + nb-card { + margin: 0; + } +} diff --git a/packages/admin-web-angular/src/app/pages/+customers/+customer/ea-customer-orders/ea-customer-orders.component.ts b/packages/admin-web-angular/src/app/pages/+customers/+customer/ea-customer-orders/ea-customer-orders.component.ts new file mode 100644 index 0000000..5848ed2 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+customers/+customer/ea-customer-orders/ea-customer-orders.component.ts @@ -0,0 +1,200 @@ +import { Component, OnDestroy, OnInit, Input, OnChanges } from '@angular/core'; +import { UserOrdersRouter } from '@modules/client.common.angular2/routers/user-orders-router.service'; +import { ActivatedRoute } from '@angular/router'; +import { LocalDataSource } from 'ng2-smart-table'; +import Order from '@modules/server.common/entities/Order'; +import { DatePipe } from '@angular/common'; +import { takeUntil } from 'rxjs/operators'; +import { RedirectStoreComponent } from '../../../../@shared/render-component/customer-orders-table/redirect-store/redirect-store.component'; +import { RedirectCarrierComponent } from '../../../../@shared/render-component/customer-orders-table/redirect-carrier/redirect-carrier.component'; +import { RedirectOrderComponent } from '../../../../@shared/render-component/customer-orders-table/redirect-order.component'; +import { RedirectProductComponent } from '../../../../@shared/render-component/customer-orders-table/redirect-product/redirect-product.component'; +import { TranslateService } from '@ngx-translate/core'; +import { forkJoin, Subject, Observable } from 'rxjs'; +import { CustomerOrderActionsComponent } from '@app/@shared/render-component/customer-orders-table/customer-order-actions/customer-order-actions.component'; + +@Component({ + selector: 'ea-customer-orders', + styleUrls: ['./ea-customer-orders.component.scss'], + templateUrl: './ea-customer-orders.component.html', +}) +export class CustomerOrdersComponent implements OnDestroy, OnInit, OnChanges { + private ngDestroy$ = new Subject(); + + @Input() + userId: string; + + settingsSmartTable: object; + sourceSmartTable: LocalDataSource = new LocalDataSource(); + + params$: any; + orderedProductsSubscription$: any; + + constructor( + private userOrdersRouter: UserOrdersRouter, + private readonly _router: ActivatedRoute, + private _translateService: TranslateService + ) { + this.params$ = this._router.params.subscribe(async (res) => { + const userId = res.id; + + this.orderedProductsSubscription$ = this.userOrdersRouter + .get(userId) + .subscribe((orders) => { + this.sourceSmartTable.load(orders); + }); + }); + this._applyTranslationOnSmartTable(); + } + + ngOnInit(): void { + this._setupSmartTable(); + } + + ngOnChanges() { + if (this.userId) { + this.orderedProductsSubscription$ = this.userOrdersRouter + .get(this.userId) + .subscribe((orders) => { + this.sourceSmartTable.load(orders); + }); + } + } + + private _applyTranslationOnSmartTable() { + this._translateService.onLangChange.subscribe(() => { + this._setupSmartTable(); + }); + } + + private _setupSmartTable() { + const columnTitlePrefix = 'CUSTOMERS_VIEW.SMART_TABLE_COLUMNS.'; + const getTranslate = (name: string): Observable => + this._translateService.get(columnTitlePrefix + name); + + forkJoin( + this._translateService.get('Id'), + getTranslate('ORDER_NUMBER'), + getTranslate('WAREHOUSE'), + getTranslate('CARRIER'), + getTranslate('PRODUCT_LIST'), + getTranslate('STATS'), + getTranslate('DELIVERY_TIME'), + getTranslate('CREATED'), + getTranslate('ACTIONS'), + getTranslate('PAID'), + getTranslate('COMPLETED'), + getTranslate('CANCELLED'), + getTranslate('NOT_DELIVERED') + ) + .pipe(takeUntil(this.ngDestroy$)) + .subscribe( + ([ + id, + orderNumber, + warehouse, + carrier, + productList, + stats, + deliveryTime, + created, + actions, + paid, + completed, + cancelled, + notDelivered, + ]) => { + this.settingsSmartTable = { + actions: false, + columns: { + orderNumber: { + title: orderNumber, + type: 'custom', + renderComponent: RedirectOrderComponent, + }, + Warehouse: { + title: warehouse, + type: 'custom', + renderComponent: RedirectStoreComponent, + }, + Carrier: { + title: carrier, + type: 'custom', + renderComponent: RedirectCarrierComponent, + }, + ProductsList: { + title: productList, + type: 'custom', + renderComponent: RedirectProductComponent, + }, + Stats: { + title: stats, + type: 'html', + valuePrepareFunction: (_, order: Order) => { + if (order.isCancelled) { + return ` + ${cancelled} + `; + } else { + return ` + ${completed}${ + order.isCompleted ? '✔' : '✘' + } + ${paid}${order.isPaid ? '✔' : '✘'} + `; + } + }, + }, + DeliveryTime: { + title: deliveryTime, + type: 'html', + valuePrepareFunction: (_, order: Order) => { + const raw: Date = new Date( + order.deliveryTime + ); + const formatted: string = order.deliveryTime + ? new DatePipe('en-EN').transform( + raw, + 'short' + ) + : `${notDelivered}`; + return `

${formatted}

`; + }, + }, + Created: { + title: created, + type: 'html', + valuePrepareFunction: (_, order: Order) => { + const raw: Date = new Date( + order._createdAt.toString() + ); + const formatted: string = new DatePipe( + 'en-EN' + ).transform(raw, 'short'); + return `

${formatted}

`; + }, + }, + actions: { + title: actions, + type: 'custom', + renderComponent: CustomerOrderActionsComponent, + }, + }, + pager: { + display: true, + perPage: 3, + }, + }; + } + ); + } + + ngOnDestroy(): void { + if (this.params$) { + this.params$.unsubscribe(); + } + if (this.orderedProductsSubscription$) { + this.orderedProductsSubscription$.unsubscribe(); + } + } +} diff --git a/packages/admin-web-angular/src/app/pages/+customers/+customer/ea-customer-orders/ea-customer-orders.module.ts b/packages/admin-web-angular/src/app/pages/+customers/+customer/ea-customer-orders/ea-customer-orders.module.ts new file mode 100644 index 0000000..db7140b --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+customers/+customer/ea-customer-orders/ea-customer-orders.module.ts @@ -0,0 +1,34 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { ToasterModule } from 'angular2-toaster'; +import { TranslateModule } from '@ngx-translate/core'; +import { Ng2SmartTableModule } from 'ng2-smart-table'; +import { HighlightModule } from 'ngx-highlightjs'; +import { ThemeModule } from '@app/@theme'; +import { RenderComponentsModule } from '@app/@shared/render-component/render-components.module'; +import { OrderInfoComponent } from './order-info/order-info.component'; +import { OrderCancelComponent } from './order-cancel/order-cancel.component'; +import { CustomOrderComponent } from '../ea-customer-products/custom-order'; +import { CustomerOrdersTableModule } from '@app/@shared/render-component/customer-orders-table/customer-orders-table.module'; +import { CustomerOrdersComponent } from './ea-customer-orders.component'; + +@NgModule({ + imports: [ + CommonModule, + Ng2SmartTableModule, + ThemeModule, + ToasterModule.forRoot(), + TranslateModule.forChild(), + HighlightModule, + RenderComponentsModule, + CustomerOrdersTableModule, + ], + declarations: [ + OrderInfoComponent, + OrderCancelComponent, + CustomerOrdersComponent, + CustomOrderComponent, + ], + exports: [CustomerOrdersComponent] +}) +export class CustomerOrdersModule {} diff --git a/packages/admin-web-angular/src/app/pages/+customers/+customer/ea-customer-orders/order-cancel/order-cancel.component.html b/packages/admin-web-angular/src/app/pages/+customers/+customer/ea-customer-orders/order-cancel/order-cancel.component.html new file mode 100644 index 0000000..131214d --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+customers/+customer/ea-customer-orders/order-cancel/order-cancel.component.html @@ -0,0 +1,11 @@ + + diff --git a/packages/admin-web-angular/src/app/pages/+customers/+customer/ea-customer-orders/order-cancel/order-cancel.component.scss b/packages/admin-web-angular/src/app/pages/+customers/+customer/ea-customer-orders/order-cancel/order-cancel.component.scss new file mode 100644 index 0000000..f0a9ccb --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+customers/+customer/ea-customer-orders/order-cancel/order-cancel.component.scss @@ -0,0 +1,61 @@ +.cancelBtn { + color: #111111; + display: inline-block; + text-transform: uppercase; + width: 100%; + cursor: pointer; + font-weight: bold; + text-align: center; + margin: 0 auto; + -moz-box-shadow: inset 0px 1px 0px 0px #ffffff; + -webkit-box-shadow: inset 0px 1px 0px 0px #ffffff; + box-shadow: inset 0px 1px 0px 0px #ffffff; + background: -webkit-gradient( + linear, + left top, + left bottom, + color-stop(0.05, #ffffff), + color-stop(1, #f6f6f6) + ); + background: -moz-linear-gradient(top, #ffffff 5%, #f6f6f6 100%); + background: -webkit-linear-gradient(top, #ffffff 5%, #f6f6f6 100%); + background: -o-linear-gradient(top, #ffffff 5%, #f6f6f6 100%); + background: -ms-linear-gradient(top, #ffffff 5%, #f6f6f6 100%); + background: linear-gradient(to bottom, #ffffff 5%, #f6f6f6 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#f6f6f6', GradientType=0); + background-color: #ffffff; + -moz-border-radius: 6px; + -webkit-border-radius: 6px; + border-radius: 6px; + border: 1px solid #dcdcdc; + display: inline-block; + cursor: pointer; + font-family: Arial; + font-size: 15px; + font-weight: bold; + padding: 6px 24px; + text-decoration: none; + text-shadow: 0px 1px 0px #ffffff; + + &:hover { + background: -webkit-gradient( + linear, + left top, + left bottom, + color-stop(0.05, #f6f6f6), + color-stop(1, #ffffff) + ); + background: -moz-linear-gradient(top, #f6f6f6 5%, #ffffff 100%); + background: -webkit-linear-gradient(top, #f6f6f6 5%, #ffffff 100%); + background: -o-linear-gradient(top, #f6f6f6 5%, #ffffff 100%); + background: -ms-linear-gradient(top, #f6f6f6 5%, #ffffff 100%); + background: linear-gradient(to bottom, #f6f6f6 5%, #ffffff 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#f6f6f6', endColorstr='#ffffff', GradientType=0); + background-color: #f6f6f6; + } + + &:active { + position: relative; + top: 1px; + } +} diff --git a/packages/admin-web-angular/src/app/pages/+customers/+customer/ea-customer-orders/order-cancel/order-cancel.component.ts b/packages/admin-web-angular/src/app/pages/+customers/+customer/ea-customer-orders/order-cancel/order-cancel.component.ts new file mode 100644 index 0000000..2c7506b --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+customers/+customer/ea-customer-orders/order-cancel/order-cancel.component.ts @@ -0,0 +1,31 @@ +import { Component, OnInit } from '@angular/core'; +import Order from '@modules/server.common/entities/Order'; +import { OrdersService } from '../../../../../@core/data/orders.service'; +import { Observable } from 'rxjs'; +import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; +import { WarehouseOrdersRouter } from '@modules/client.common.angular2/routers/warehouse-orders-router.service'; + +@Component({ + styleUrls: ['./order-cancel.component.scss'], + templateUrl: './order-cancel.component.html', +}) +export class OrderCancelComponent implements OnInit { + public orderId: string; + public order$: Observable; + + constructor( + private readonly _orderService: OrdersService, + private readonly activeModal: NgbActiveModal, + private warehouseOrderdersRouter: WarehouseOrdersRouter + ) {} + + cancel() { + this.activeModal.dismiss('canceled'); + } + async cancelOrder() { + const cncld = await this.warehouseOrderdersRouter.cancel(this.orderId); + this.cancel(); + } + + ngOnInit(): void {} +} diff --git a/packages/admin-web-angular/src/app/pages/+customers/+customer/ea-customer-orders/order-info/order-info.component.html b/packages/admin-web-angular/src/app/pages/+customers/+customer/ea-customer-orders/order-info/order-info.component.html new file mode 100644 index 0000000..9b01af7 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+customers/+customer/ea-customer-orders/order-info/order-info.component.html @@ -0,0 +1,49 @@ + + diff --git a/packages/admin-web-angular/src/app/pages/+customers/+customer/ea-customer-orders/order-info/order-info.component.scss b/packages/admin-web-angular/src/app/pages/+customers/+customer/ea-customer-orders/order-info/order-info.component.scss new file mode 100644 index 0000000..783d0a2 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+customers/+customer/ea-customer-orders/order-info/order-info.component.scss @@ -0,0 +1,18 @@ +.info-content { + overflow: hidden; + h6 { + span { + color: #009100; + cursor: pointer; + transition: opacity 0.1s ease-in; + &:hover { + opacity: 0.7; + } + } + } + .json-viewer { + button { + margin-bottom: 15px; + } + } +} diff --git a/packages/admin-web-angular/src/app/pages/+customers/+customer/ea-customer-orders/order-info/order-info.component.ts b/packages/admin-web-angular/src/app/pages/+customers/+customer/ea-customer-orders/order-info/order-info.component.ts new file mode 100644 index 0000000..df04a27 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+customers/+customer/ea-customer-orders/order-info/order-info.component.ts @@ -0,0 +1,49 @@ +import { Component, OnInit } from '@angular/core'; +import Order from '@modules/server.common/entities/Order'; +import { OrdersService } from '../../../../../@core/data/orders.service'; +import { Observable } from 'rxjs'; +import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; +import { Router } from '@angular/router'; + +@Component({ + styleUrls: ['./order-info.component.scss'], + templateUrl: './order-info.component.html', +}) +export class OrderInfoComponent implements OnInit { + public orderId: string; + public storeId: string; + public carrierId: string; + public order$: Observable; + + public selectedOrder: any; + public showCode: boolean; + + constructor( + private readonly _orderService: OrdersService, + private readonly activeModal: NgbActiveModal, + private router: Router + ) {} + + cancel() { + this.activeModal.dismiss('canceled'); + } + + ngOnInit(): void { + this.order$ = this._orderService.getOrderById(this.orderId); + } + + redirectToOrderPage() { + this.router.navigate([`/orders/${this.orderId}`]); + this.activeModal.dismiss('canceled'); + } + + redirectToStorePage() { + this.router.navigate([`/stores/${this.storeId}`]); + this.activeModal.dismiss('canceled'); + } + + redirectToCarrierPage() { + this.router.navigate([`/carriers/${this.carrierId}`]); + this.activeModal.dismiss('canceled'); + } +} diff --git a/packages/admin-web-angular/src/app/pages/+customers/+customer/ea-customer-products/custom-order/custom-order.component.html b/packages/admin-web-angular/src/app/pages/+customers/+customer/ea-customer-products/custom-order/custom-order.component.html new file mode 100644 index 0000000..5294141 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+customers/+customer/ea-customer-products/custom-order/custom-order.component.html @@ -0,0 +1,75 @@ + + + + + + + diff --git a/packages/admin-web-angular/src/app/pages/+customers/+customer/ea-customer-products/custom-order/custom-order.component.scss b/packages/admin-web-angular/src/app/pages/+customers/+customer/ea-customer-products/custom-order/custom-order.component.scss new file mode 100644 index 0000000..81d7efa --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+customers/+customer/ea-customer-products/custom-order/custom-order.component.scss @@ -0,0 +1,42 @@ +:host ::ng-deep .modal-footer .btn-primary { + background-color: #111111 !important; + border: none !important; + color: white !important; + cursor: pointer !important; +} + +:host ::ng-deep .modal-footer .btn-primary:hover { + background-color: #bebebe !important; + color: #111111 !important; +} +:host ::ng-deep .modal-footer .btn-neutral { + background-color: #111111; + color: #ffffff; +} + +.seePass { + border: 0px; +} + +#showPass { + font-size: 25px; + padding: 0 10px; +} +input[type='number'] { + height: 30px; + line-height: 30px; + font-size: 16px; + padding: 0 8px; +} +input[type='number']::-webkit-inner-spin-button, +input[type='number']::-webkit-outer-spin-button { + opacity: 1; + cursor: pointer; + width: 14px; + height: 14px; + padding: 4px; + margin-bottom: 3px; + position: relative; + right: 4px; + border-radius: 28px; +} diff --git a/packages/admin-web-angular/src/app/pages/+customers/+customer/ea-customer-products/custom-order/custom-order.component.ts b/packages/admin-web-angular/src/app/pages/+customers/+customer/ea-customer-products/custom-order/custom-order.component.ts new file mode 100644 index 0000000..92828e3 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+customers/+customer/ea-customer-products/custom-order/custom-order.component.ts @@ -0,0 +1,117 @@ +import { Component, Input, OnInit, OnDestroy } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; +import { + AbstractControl, + FormBuilder, + FormControl, + FormGroup, + Validators, +} from '@angular/forms'; +import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; +import Warehouse from '@modules/server.common/entities/Warehouse'; +import { WarehouseOrdersRouter } from '@modules/client.common.angular2/routers/warehouse-orders-router.service'; +import User from '@modules/server.common/entities/User'; +import { WarehousesService } from '../../../../../@core/data/warehouses.service'; +import { ToasterService } from 'angular2-toaster'; +import { WarehouseRouter } from '@modules/client.common.angular2/routers/warehouse-router.service'; +import { Subject } from 'rxjs'; +import { getIdFromTheDate } from '@modules/server.common/utils'; +import Order from '@modules/server.common/entities/Order'; +import { IOrderCreateInput } from '@modules/server.common/routers/IWarehouseOrdersRouter'; + +@Component({ + selector: 'ea-custom-order', + templateUrl: './custom-order.component.html', + styleUrls: ['./custom-order.component.scss'], +}) +export class CustomOrderComponent implements OnInit, OnDestroy { + private readonly ngDestroy$ = new Subject(); + + @Input() + warehouseId: Warehouse['id']; + + @Input() + currentProduct: any; + + @Input() + userId: User['id']; + + readonly form: FormGroup = this.fb.group({ + count: [ + 0, + [ + Validators.required, + Validators.min(1), + (control: FormControl) => { + if ( + this.currentProduct != null && + control.value > + this.currentProduct.warehouseProduct.count + ) { + return { notEnoughAvailable: true }; + } + + return null; + }, + ], + ], + }); + + get count(): AbstractControl { + return this.form.get('count'); + } + + constructor( + private readonly warehouseRouter: WarehouseRouter, + private readonly activatedRoute: ActivatedRoute, + private readonly activeModal: NgbActiveModal, + private readonly toasterService: ToasterService, + private readonly warehousesService: WarehousesService, + private readonly fb: FormBuilder, + private readonly warehouseOrdersRouter: WarehouseOrdersRouter + ) {} + + ngOnInit() {} + + cancel() { + this.activeModal.dismiss('canceled'); + } + + async createOrder() { + try { + const orderCreateInput: IOrderCreateInput = { + userId: this.userId, + warehouseId: this.warehouseId, + products: [ + { + count: this.count.value, + productId: this.currentProduct.warehouseProduct.product[ + 'id' + ], + }, + ], + }; + + const order: Order = await this.warehouseOrdersRouter.create( + orderCreateInput + ); + + this.toasterService.pop( + 'success', + `Order #${getIdFromTheDate(order)} was created` + ); + + this.activeModal.close(order); + } catch (err) { + this.toasterService.pop( + 'error', + `Error in creating order: "${err.message}"` + ); + } + } + + ngOnDestroy() { + this.ngDestroy$.next(); + this.ngDestroy$.complete(); + } +} diff --git a/packages/admin-web-angular/src/app/pages/+customers/+customer/ea-customer-products/custom-order/index.ts b/packages/admin-web-angular/src/app/pages/+customers/+customer/ea-customer-products/custom-order/index.ts new file mode 100644 index 0000000..5d7937e --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+customers/+customer/ea-customer-products/custom-order/index.ts @@ -0,0 +1 @@ +export * from './custom-order.component'; diff --git a/packages/admin-web-angular/src/app/pages/+customers/+customer/ea-customer-products/ea-customer-products.component.html b/packages/admin-web-angular/src/app/pages/+customers/+customer/ea-customer-products/ea-customer-products.component.html new file mode 100644 index 0000000..cbda39f --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+customers/+customer/ea-customer-products/ea-customer-products.component.html @@ -0,0 +1,8 @@ + + + + + diff --git a/packages/admin-web-angular/src/app/pages/+customers/+customer/ea-customer-products/ea-customer-products.component.ts b/packages/admin-web-angular/src/app/pages/+customers/+customer/ea-customer-products/ea-customer-products.component.ts new file mode 100644 index 0000000..08278c2 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+customers/+customer/ea-customer-products/ea-customer-products.component.ts @@ -0,0 +1,163 @@ +import { Component, OnDestroy, OnInit } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; +import { LocalDataSource } from 'ng2-smart-table'; +import Warehouse from '@modules/server.common/entities/Warehouse'; +import { UserRouter } from '@modules/client.common.angular2/routers/user-router.service'; +import { takeUntil } from 'rxjs/operators'; +import { DomSanitizer } from '@angular/platform-browser'; +import { WarehousesService } from '../../../../@core/data/warehouses.service'; +import ProductInfo from '@modules/server.common/entities/ProductInfo'; +import { ProductOrderProductsComponent } from '../../../../@shared/render-component/customer-products-table/product-order-products.component'; +import { StoreOrderProductsComponent } from '../../../../@shared/render-component/customer-products-table/store-order-products.component'; +import { OrderBtnOrderProductsComponent } from '../../../../@shared/render-component/customer-products-table/order-btn-order-products/order-btn-order-products.component'; +import { GeoLocationService } from '../../../../@core/data/geo-location.service'; +import { TranslateService } from '@ngx-translate/core'; +import { firstValueFrom, forkJoin, Observable, Subject } from 'rxjs'; + +@Component({ + selector: 'ea-customer-products', + templateUrl: './ea-customer-products.component.html', + styleUrls: ['./ea-customer-products.scss'] +}) +export class CustomerProductsComponent implements OnDestroy, OnInit { + private ngDestroy$ = new Subject(); + + settingsSmartTable: object; + sourceSmartTable: LocalDataSource = new LocalDataSource(); + + params$: any; + availableProductsSubscription$: any; + + warehouses: Warehouse[]; + availableProducts: ProductInfo[]; + userId: string; + + constructor( + private readonly geoLocationProductService: GeoLocationService, + private userRouter: UserRouter, + private readonly _router: ActivatedRoute, + private readonly _sanitizer: DomSanitizer, + private readonly _warehousesService: WarehousesService, + private _translateService: TranslateService + ) { + this.params$ = this._router.params.subscribe(async (res) => { + this.userId = res.id; + const user = await firstValueFrom( + this.userRouter.get(this.userId) + ); + if (user == null) { + throw new Error(`User can't be found (id: ${this.userId})`); + } + this.availableProductsSubscription$ = this.geoLocationProductService + .getGeoLocationProducts(user.geoLocation) + .subscribe((products) => { + this.availableProducts = products; + this.sourceSmartTable.load(products); + }); + }); + + this._applyTranslationOnSmartTable(); + } + + ngOnInit(): void { + this._setupSmartTable(); + this._loadWarehouses(); + } + + private _applyTranslationOnSmartTable() { + this._translateService.onLangChange.subscribe(() => { + this._setupSmartTable(); + }); + } + + private _setupSmartTable() { + const columnTitlePrefix = 'CUSTOMERS_VIEW.SMART_TABLE_COLUMNS.'; + const getTranslate = (name: string): Observable => + this._translateService.get(columnTitlePrefix + name); + + forkJoin( + this._translateService.get('Id'), + getTranslate('PRODUCT'), + getTranslate('PRICE'), + getTranslate('STORE'), + getTranslate('AVAILABLE_COUNT'), + getTranslate('ORDER') + ) + .pipe(takeUntil(this.ngDestroy$)) + .subscribe(([id, prod, price, store, availableCount, order]) => { + this.settingsSmartTable = { + actions: false, + columns: { + Product: { + title: prod, + type: 'custom', + renderComponent: ProductOrderProductsComponent, + }, + Price: { + title: price, + type: 'html', + valuePrepareFunction: ( + val, + product: ProductInfo + ) => { + return this._sanitizer.bypassSecurityTrustHtml( + `
+

${product.warehouseProduct.price}

+
+
` + ); + }, + }, + Store: { + title: store, + type: 'custom', + renderComponent: StoreOrderProductsComponent, + }, + AvailableCount: { + title: availableCount, + type: 'html', + valuePrepareFunction: ( + val, + product: ProductInfo + ) => { + return this._sanitizer.bypassSecurityTrustHtml( + `
+
${product.warehouseProduct.count}
+
+
` + ); + }, + }, + Order: { + title: order, + type: 'custom', + renderComponent: OrderBtnOrderProductsComponent, + onComponentInitFunction: async (instance) => { + instance.userId = this.userId; + instance.availableProducts = this.availableProducts; + }, + }, + }, + pager: { + display: true, + perPage: 3, + }, + }; + }); + } + + private async _loadWarehouses() { + this.warehouses = await firstValueFrom( + this._warehousesService.getStores() + ); + } + + ngOnDestroy(): void { + if (this.params$) { + this.params$.unsubscribe(); + } + if (this.availableProductsSubscription$) { + this.availableProductsSubscription$.unsubscribe(); + } + } +} diff --git a/packages/admin-web-angular/src/app/pages/+customers/+customer/ea-customer-products/ea-customer-products.scss b/packages/admin-web-angular/src/app/pages/+customers/+customer/ea-customer-products/ea-customer-products.scss new file mode 100644 index 0000000..26d9d80 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+customers/+customer/ea-customer-products/ea-customer-products.scss @@ -0,0 +1,114 @@ +@import '../../../../@theme/styles/themes'; + +@include nb-install-component() { + .padding { + padding: 2%; + } + + .padding-bottom { + padding-bottom: 2%; + } + + .margin { + margin: 1%; + } + + nb-card-footer { + padding: 0; + } + + ::ng-deep ng2-smart-table { + .redirectBtn { + font-size: 0.9em !important; + text-align: left !important; + width: 100% !important; + } + } + + ::ng-deep .productBtn { + cursor: pointer; + + &:hover { + color: #0074d9 !important; + text-decoration: none; + } + + img { + width: 10%; + display: inline-block; + float: left; + } + } + + ::ng-deep .warhouseBtn { + cursor: pointer; + + &:hover { + color: #0074d9 !important; + text-decoration: none; + } + } + + ::ng-deep .btn { + -moz-box-shadow: inset 0px 1px 0px 0px #ffffff; + -webkit-box-shadow: inset 0px 1px 0px 0px #ffffff; + box-shadow: inset 0px 1px 0px 0px #ffffff; + background: -webkit-gradient( + linear, + left top, + left bottom, + color-stop(0.05, #ffffff), + color-stop(1, #f6f6f6) + ); + background: -moz-linear-gradient(top, #ffffff 5%, #f6f6f6 100%); + background: -webkit-linear-gradient(top, #ffffff 5%, #f6f6f6 100%); + background: -o-linear-gradient(top, #ffffff 5%, #f6f6f6 100%); + background: -ms-linear-gradient(top, #ffffff 5%, #f6f6f6 100%); + background: linear-gradient(to bottom, #ffffff 5%, #f6f6f6 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#f6f6f6', GradientType=0); + background-color: #ffffff; + -moz-border-radius: 6px; + -webkit-border-radius: 6px; + border-radius: 6px; + border: 1px solid #dcdcdc; + display: inline-block; + cursor: pointer !important; + color: #666666; + font-family: Arial; + font-size: 15px; + font-weight: bold; + padding: 6px 24px; + text-decoration: none; + text-shadow: 0px 1px 0px #ffffff; + + &:hover { + background: -webkit-gradient( + linear, + left top, + left bottom, + color-stop(0.05, #f6f6f6), + color-stop(1, #ffffff) + ); + background: -moz-linear-gradient(top, #f6f6f6 5%, #ffffff 100%); + background: -webkit-linear-gradient(top, #f6f6f6 5%, #ffffff 100%); + background: -o-linear-gradient(top, #f6f6f6 5%, #ffffff 100%); + background: -ms-linear-gradient(top, #f6f6f6 5%, #ffffff 100%); + background: linear-gradient(to bottom, #f6f6f6 5%, #ffffff 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#f6f6f6', endColorstr='#ffffff', GradientType=0); + background-color: #f6f6f6; + } + + &:active { + position: relative; + top: 1px; + } + } + + nb-card-body { + padding: 0; + } + + nb-card { + margin: 0; + } +} diff --git a/packages/admin-web-angular/src/app/pages/+customers/+customer/ea-customer-stores/ea-customer-stores.component.html b/packages/admin-web-angular/src/app/pages/+customers/+customer/ea-customer-stores/ea-customer-stores.component.html new file mode 100644 index 0000000..9395bd0 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+customers/+customer/ea-customer-stores/ea-customer-stores.component.html @@ -0,0 +1,21 @@ + + + + + + + diff --git a/packages/admin-web-angular/src/app/pages/+customers/+customer/ea-customer-stores/ea-customer-stores.component.scss b/packages/admin-web-angular/src/app/pages/+customers/+customer/ea-customer-stores/ea-customer-stores.component.scss new file mode 100644 index 0000000..c38e81f --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+customers/+customer/ea-customer-stores/ea-customer-stores.component.scss @@ -0,0 +1,18 @@ +nb-card-body { + padding: 0; +} + +nb-card-header { + border: none; + position: relative; + + .title { + position: absolute; + top: 50%; + transform: translateY(-50%); + } +} + +nb-card { + margin: 0; +} diff --git a/packages/admin-web-angular/src/app/pages/+customers/+customer/ea-customer-stores/ea-customer-stores.component.ts b/packages/admin-web-angular/src/app/pages/+customers/+customer/ea-customer-stores/ea-customer-stores.component.ts new file mode 100644 index 0000000..056d4f8 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+customers/+customer/ea-customer-stores/ea-customer-stores.component.ts @@ -0,0 +1,169 @@ +import { + Component, + EventEmitter, + Input, + OnDestroy, + OnInit, +} from '@angular/core'; +import { WarehouseViewModel } from '../../../../models/WarehouseViewModel'; +import { WarehousesService } from '../../../../@core/data/warehouses.service'; +import { OrdersService } from '../../../../@core/data/orders.service'; +import { WarehouseMutationComponent } from '../../../../@shared/warehouse/warehouse-mutation'; +import User from '@modules/server.common/entities/User'; +import GeoLocation from '@modules/server.common/entities/GeoLocation'; +import Warehouse from '@modules/server.common/entities/Warehouse'; +import { ToasterService } from 'angular2-toaster'; +import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; +import { combineLatest, Subject } from 'rxjs'; +import { first, takeUntil } from 'rxjs/operators'; +import { ActivatedRoute } from '@angular/router'; +import { UserRouter } from '@modules/client.common.angular2/routers/user-router.service'; + +@Component({ + selector: 'ea-customer-stores', + styleUrls: ['./ea-customer-stores.component.scss'], + templateUrl: './ea-customer-stores.component.html', +}) +export class CustomerStoresComponent implements OnInit, OnDestroy { + @Input() + currentUser: User; + + params$: any; + + public sourceEventEmitter = new EventEmitter(); + + private _selectedCustomerDestroy$ = new Subject(); + private _ngDestroy$ = new Subject(); + + private _selectedWarehouses: WarehouseViewModel[] = []; + + constructor( + private readonly _toasterService: ToasterService, + private readonly _modalService: NgbModal, + private readonly _warehousesService: WarehousesService, + private readonly _ordersService: OrdersService, + private readonly _router: ActivatedRoute, + private userRouter: UserRouter + ) { + this.params$ = this._router.params.subscribe(async (res) => { + const user = await this.userRouter + .get(res.id) + .pipe(first()) + .toPromise(); + this._destroyExceptSelectedCustomerSubscriber(); + this.currentUser = user; + if (this.currentUser) { + this._loadNearbyWarehouses(); + } + }); + } + + get hasSelectedWarehouses(): boolean { + return this._selectedWarehouses.length > 0; + } + + ngOnInit() { + if (this.currentUser) { + this._loadNearbyWarehouses(); + } + } + + createWarehouseModel() { + this._modalService.open(WarehouseMutationComponent, { + size: 'lg', + container: 'nb-layout', + }); + } + + deleteSelectedRows() { + const idsForDelete: string[] = this._selectedWarehouses.map( + (w) => w.id + ); + + this._warehousesService.removeByIds(idsForDelete).subscribe(() => { + this._selectedWarehouses.forEach((warehouse) => + this._toasterService.pop( + `success`, + `Warehouse '${warehouse.name}' DELETED` + ) + ); + this._selectedWarehouses = []; + }); + } + + selectWarehouseTmp(ev) { + this._selectedWarehouses = ev.selected; + } + + private _destroyExceptSelectedCustomerSubscriber() { + this._ngDestroy$.next(); + this._ngDestroy$.complete(); + this._ngDestroy$ = new Subject(); + } + + private async _setupStoresData(warehouses) { + const merchantsOrders = await this._ordersService.getMerchantsOrdersCountInfo( + warehouses.map((w) => w.id) + ); + + const noInfoSign = ''; + + const sourceResult = warehouses.map((warehouse) => { + const merchantOrders = merchantsOrders.find( + (res) => res['id'] === warehouse.id + ); + return { + id: warehouse.id, + name: warehouse.name || noInfoSign, + email: warehouse.contactEmail || noInfoSign, + phone: warehouse.contactPhone || noInfoSign, + city: warehouse.geoLocation.city || noInfoSign, + address: ` + St. ${warehouse.geoLocation.streetAddress || noInfoSign}, House № ${ + warehouse.geoLocation.house || noInfoSign + } + `, + ordersQty: merchantOrders ? merchantOrders.ordersCount : 0, + actions: { + actionName: 'Order', + actionOwnerId: this.currentUser.id, + }, + }; + }); + + return sourceResult; + } + + private _loadNearbyWarehouses() { + const emitSource = async (stores) => { + const sourceResult = await this._setupStoresData(stores); + this.sourceEventEmitter.emit(sourceResult); + }; + + const { + loc: { type, coordinates }, + } = this.currentUser.geoLocation; + const geoInput = { loc: { type, coordinates } } as GeoLocation; + + const stores$ = this._warehousesService.getNearbyStores(geoInput); + + combineLatest(stores$) + .pipe(takeUntil(this._ngDestroy$)) + .subscribe((res) => { + const stores: Warehouse[] = res[0]; + emitSource(stores); + }); + } + + ngOnDestroy() { + this._selectedCustomerDestroy$.next(); + this._selectedCustomerDestroy$.complete(); + + this._ngDestroy$.next(); + this._ngDestroy$.complete(); + + if (this.params$) { + this.params$.unsubscribe(); + } + } +} diff --git a/packages/admin-web-angular/src/app/pages/+customers/+invites/+invites-requests/invites-requests.component.html b/packages/admin-web-angular/src/app/pages/+customers/+invites/+invites-requests/invites-requests.component.html new file mode 100644 index 0000000..7c42593 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+customers/+invites/+invites-requests/invites-requests.component.html @@ -0,0 +1,50 @@ + + + + + +
+ + {{ 'CUSTOMERS_VIEW.NOT_INVITED_ONLY' | translate }} + +
+
+ + + + + +
diff --git a/packages/admin-web-angular/src/app/pages/+customers/+invites/+invites-requests/invites-requests.component.scss b/packages/admin-web-angular/src/app/pages/+customers/+invites/+invites-requests/invites-requests.component.scss new file mode 100644 index 0000000..52ee747 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+customers/+invites/+invites-requests/invites-requests.component.scss @@ -0,0 +1,7 @@ +.right-actions { + display: flex !important; + align-content: center !important; + align-items: center !important; + + height: 42px; +} diff --git a/packages/admin-web-angular/src/app/pages/+customers/+invites/+invites-requests/invites-requests.component.ts b/packages/admin-web-angular/src/app/pages/+customers/+invites/+invites-requests/invites-requests.component.ts new file mode 100644 index 0000000..9f8e919 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+customers/+invites/+invites-requests/invites-requests.component.ts @@ -0,0 +1,529 @@ +import { + Component, + OnDestroy, + Renderer2, + ElementRef, + AfterViewChecked, +} from '@angular/core'; + +import { Subject, forkJoin, Observable } from 'rxjs'; +import { LocalDataSource } from 'ng2-smart-table'; +import InviteRequest from '@modules/server.common/entities/InviteRequest'; +import { InvitesRequestsService } from '../../../../@core/data/invites-requests.service'; +import { getCountryName } from '@modules/server.common/entities/GeoLocation'; +import { takeUntil, first } from 'rxjs/operators'; +import { CountryRenderComponent } from '../country-render/country-render.component'; +import { InvitesService } from '../../../../@core/data/invites.service'; +import { IGeoLocationCreateObject } from '@modules/server.common/interfaces/IGeoLocation'; +import { IInviteCreateObject } from '@modules/server.common/interfaces/IInvite'; +import { TranslateService } from '@ngx-translate/core'; +import { countries } from '@modules/server.common/data/abbreviation-to-country'; +import { StatusComponent } from '../../../../@shared/render-component/invites-requests/status/status.component'; +import { InvitedDateComponent } from '../../../../@shared/render-component/invites-requests/invited-date.component'; +import { NotifyService } from '@app/@core/services/notify/notify.service'; +import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; +import { ConfirmationModalComponent } from '../../../../@shared/confirmation-modal/confirmation-modal.component'; +import { InviteViewModel } from '../invites.component'; + +// import { ToasterService } from 'angular2-toaster'; +// import fingerprint2 from 'fingerprintjs2'; + +export interface InviteRequestViewModel { + id: string; + country: string; + city: string; + address: string; + house: string; + apartment: string; +} + +const perPage = 10; + +@Component({ + selector: 'ea-invites-requests', + styleUrls: ['invites-requests.component.scss'], + templateUrl: 'invites-requests.component.html', +}) +export class InvitesRequestsComponent implements OnDestroy, AfterViewChecked { + loading: boolean; + + settingsSmartTable: object; + sourceSmartTable = new LocalDataSource(); + + private ngDestroy$ = new Subject(); + private noInfoSign = ''; + private selectedInvitesRequests: any[] = []; + private addClick: boolean; + public notInvitedOnly: boolean = true; + private dataCount: number; + private $invitesRequests: any; + private currentPage: number = 1; + + constructor( + private readonly _invitesRequestsService: InvitesRequestsService, + private readonly _invitesService: InvitesService, + private readonly _elRef: ElementRef, + private readonly _renderer: Renderer2, + private readonly _translateService: TranslateService, + private readonly _notifyService: NotifyService, + private readonly modalService: NgbModal + ) { + this._loadSettingsSmartTable(); + this._loadDataSmartTable(); + this.smartTableChange(); + } + + ngAfterViewChecked(): void { + this.loadSmartTableTranslates(); + + if ( + this._elRef.nativeElement.querySelector( + '.ng2-smart-action-add-create' + ) + ) { + const firstRow = 2; + const columnOffset = 0; + + const td = this._renderer.createElement('td'); + + const tr = this._elRef.nativeElement.getElementsByTagName('tr')[ + firstRow + ]; + + const refChild = tr.childNodes[columnOffset]; + + if (!this.addClick) { + this._renderer.insertBefore(tr, td, refChild); + this.addClick = true; + } + } else { + this.addClick = false; + } + } + + get hasSelectedInvitesRequests(): boolean { + return this.selectedInvitesRequests.length > 0; + } + + get hasSelectedInvitesRequestsForInvite(): boolean { + return ( + this.selectedInvitesRequests.filter( + (i: InviteRequest) => !i.isInvited + ).length > 0 + ); + } + + selectInvitesRequestsTmp(ev: { selected: any }) { + this.selectedInvitesRequests = ev.selected; + } + + notInvitedOnlyChanged() { + this.$invitesRequests.unsubscribe(); + this.notInvitedOnly = !this.notInvitedOnly; + this._loadDataSmartTable(this.currentPage); + } + + loadSmartTableTranslates() { + this._translateService.onLangChange.subscribe((d) => { + this._loadSettingsSmartTable(); + }); + } + + async createConfirm(e): Promise { + try { + this.loading = true; + + const createDataObject = this.inviteRequestCreateObject(e.newData); + + const createInput = await this._invitesRequestsService.getCreateInviteRequestObject( + createDataObject + ); + + await this._invitesRequestsService + .createInviteRequest(createInput) + .pipe(first()) + .toPromise(); + + e.confirm.resolve(); + + this.loading = false; + + const message = `Invite request was created`; + + this._notifyService.success(message); + } catch (error) { + this.loading = false; + + let message = `Something went wrong`; + + if (error.message === 'Validation error') { + message = error.message; + } + + this._notifyService.error(message); + } + + // fingerprint2().get(async (uuid, c) => { + // let deviceId; + // const device = await this.deviceService.getByFindInput({ uuid }).pipe(first()).toPromise() + // if (device.length >= 1) { + // deviceId = device[0].id + // } else { + // console.warn(navigator.language); + + // } + // console.warn(deviceId); + + // }) + } + + async deleteConfirm(e): Promise { + const activeModal = this.modalService.open(ConfirmationModalComponent, { + size: 'sm', + container: 'nb-layout', + backdrop: 'static', + }); + + const modalComponent: ConfirmationModalComponent = + activeModal.componentInstance; + + await modalComponent.confirmEvent + .pipe(takeUntil(modalComponent.ngDestroy$)) + .subscribe((dataEvent) => { + try { + this.loading = true; + + this._invitesRequestsService + .removeByIds([e.data.id]) + .pipe(first()) + .toPromise(); + + this.loading = false; + + const message = `Invite request was deleted`; + + this._notifyService.success(message); + + e.confirm.resolve(); + } catch (error) { + this.loading = false; + + const message = `Something went wrong!`; + + this._notifyService.error(message); + } + + modalComponent.cancel(); + }); + } + + async editConfirm(e): Promise { + try { + this.loading = true; + const createDataObject: InviteViewModel = this.inviteRequestCreateObject( + e.newData + ); + const updateInput = await this._invitesService.getCreateInviteObject( + createDataObject + ); + + const res = await this._invitesRequestsService + .updateInviteRequest(e.data.id, updateInput) + .pipe(first()) + .toPromise(); + e.confirm.resolve(res); + this.loading = false; + const message = `Invite request was updated`; + this._notifyService.success(message); + } catch (error) { + this.loading = false; + const message = `Something went wrong`; + this._notifyService.error(message); + console.warn(error); + } + } + + async inviteSelectedRows(): Promise { + let succesInvite = 0; + const invitesRequests = this.selectedInvitesRequests.filter( + (i: InviteRequest) => !i.isInvited + ); + for (const inviteRequest of invitesRequests) { + try { + this.loading = true; + + await this._invitesService + .createInvite(this.getInviteCreateObject(inviteRequest)) + .pipe(first()) + .toPromise(); + + await this._invitesRequestsService + .updateInviteRequest(inviteRequest.id, { + isInvited: true, + invitedDate: new Date(), + }) + .pipe(first()) + .toPromise(); + + this.loading = false; + + succesInvite++; + } catch (error) { + this.loading = false; + + const errorMessage = `Something went wrong!`; + + this._notifyService.error(errorMessage); + } + } + + const message = `${succesInvite} success invites`; + + this._notifyService.success(message); + + this.sourceSmartTable.refresh(); + + this.selectInvitesRequestsTmp({ selected: [] }); + } + + async deleteSelectedRows(): Promise { + const idsForDelete: string[] = this.selectedInvitesRequests.map( + (c) => c.id + ); + + try { + this.loading = true; + + await this._invitesRequestsService + .removeByIds(idsForDelete) + .pipe(first()) + .toPromise(); + + this.loading = false; + + const message = `${idsForDelete.length} invites requests was deleted`; + + this._notifyService.success(message); + + this.selectedInvitesRequests = []; + } catch (error) { + this.loading = false; + const message = `Something went wrong!`; + this._notifyService.error(message); + } + } + + private _loadSettingsSmartTable() { + const columnTitlePrefix = 'CUSTOMERS_VIEW.SMART_TABLE_COLUMNS.'; + + const getTranslate = (name: string): Observable => + this._translateService.get(columnTitlePrefix + name); + + forkJoin( + getTranslate('COUNTRY'), + getTranslate('CITY'), + getTranslate('STREET_ADDRESS'), + getTranslate('HOUSE'), + getTranslate('APARTMENT'), + getTranslate('INVITE_CODE'), + getTranslate('INVITED_DATE'), + getTranslate('STATUS') + ) + .pipe(takeUntil(this.ngDestroy$)) + .subscribe( + ([ + country, + city, + streetAddress, + house, + apartment, + inviteCode, + invitedDate, + status, + ]) => { + this.settingsSmartTable = { + selectMode: 'multi', + add: { + addButtonContent: '', + createButtonContent: + '', + cancelButtonContent: '', + confirmCreate: true, + }, + edit: { + editButtonContent: '', + saveButtonContent: + '', + cancelButtonContent: '', + confirmSave: true, + }, + delete: { + deleteButtonContent: '', + confirmDelete: true, + }, + columns: { + country: { + title: country, + editor: { + type: 'custom', + component: CountryRenderComponent, + }, + }, + city: { title: city }, + address: { title: streetAddress }, + house: { title: house }, + apartment: { title: apartment }, + invitedDate: { + title: invitedDate, + editable: false, + addable: false, + type: 'custom', + renderComponent: InvitedDateComponent, + }, + status: { + title: status, + class: 'text-center', + filter: false, + editable: false, + addable: false, + type: 'custom', + renderComponent: StatusComponent, + }, + }, + pager: { + display: true, + perPage, + }, + }; + } + ); + } + + private async _loadDataSmartTable(page = 1) { + let invitesRequests: InviteRequest[] = []; + + if (this.$invitesRequests) { + await this.$invitesRequests.unsubscribe(); + } + + const loadData = async () => { + const invitesRequestsVM = invitesRequests.map((inviteRequest) => { + return { + country: + getCountryName(inviteRequest.geoLocation.countryId) || + this.noInfoSign, + city: inviteRequest.geoLocation.city || this.noInfoSign, + address: + inviteRequest.geoLocation.streetAddress || + this.noInfoSign, + house: inviteRequest.geoLocation.house || this.noInfoSign, + apartment: + inviteRequest.apartment.trim() || this.noInfoSign, + id: inviteRequest.id, + geoLocation: inviteRequest.geoLocation, + invitedDate: + inviteRequest.invitedDate && + new Date( + inviteRequest.invitedDate + ).toLocaleDateString() + + '\n' + + new Date( + inviteRequest.invitedDate + ).toLocaleTimeString(), + isInvited: inviteRequest.isInvited, + inviteRequest, + }; + }); + + await this.loadDataCount(); + + const invitesRequestsData = new Array(this.dataCount); + + invitesRequestsData.splice( + perPage * (page - 1), + perPage, + ...invitesRequestsVM + ); + + await this.sourceSmartTable.load(invitesRequestsData); + }; + + this.loading = true; + + // We call two times 'loadData' + // This is need because in every change on one of them the server emit and we want to receive it + this.$invitesRequests = this._invitesRequestsService + .getInvitesRequests( + { + skip: perPage * (page - 1), + limit: perPage, + }, + !this.notInvitedOnly + ) + .pipe(takeUntil(this.ngDestroy$)) + .subscribe(async (i: InviteRequest[]) => { + invitesRequests = i; + await loadData(); + this.loading = false; + }); + } + + private inviteRequestCreateObject(data) { + this.inviteRequestValidation(data); + data.apartment = data.apartment || ' '; + return data; + } + + private inviteRequestValidation(data) { + if (!data.address || !data.city || !data.country || !data.house) { + throw new Error('Validation error'); + } + } + + private getInviteCreateObject(data): IInviteCreateObject { + data.apartment = + data.apartment !== this.noInfoSign ? data.apartment : ' '; + + const geoLocation: IGeoLocationCreateObject = { + countryId: Object.values(countries).indexOf(data.country), + city: data.city, + streetAddress: data.address, + house: data.house, + loc: { + coordinates: data.geoLocation.loc.coordinates, + type: 'Point', + }, + }; + + const invite: IInviteCreateObject = { + apartment: data.apartment, + geoLocation, + }; + + return invite; + } + + private async smartTableChange() { + this.sourceSmartTable + .onChanged() + .pipe(takeUntil(this.ngDestroy$)) + .subscribe(async (event) => { + if (event.action === 'page') { + const page = event.paging.page; + this.currentPage = page; + this._loadDataSmartTable(page); + } + }); + } + + private async loadDataCount() { + this.dataCount = await this._invitesRequestsService.getCountOfInvitesRequests( + !this.notInvitedOnly + ); + } + + ngOnDestroy(): void { + this.ngDestroy$.next(); + this.ngDestroy$.complete(); + } +} diff --git a/packages/admin-web-angular/src/app/pages/+customers/+invites/+invites-requests/invites-requests.module.ts b/packages/admin-web-angular/src/app/pages/+customers/+invites/+invites-requests/invites-requests.module.ts new file mode 100644 index 0000000..1dda00f --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+customers/+invites/+invites-requests/invites-requests.module.ts @@ -0,0 +1,39 @@ +import { NgModule } from '@angular/core'; +import { CommonModule, JsonPipe } from '@angular/common'; +import { Ng2SmartTableModule } from 'ng2-smart-table'; +import { ThemeModule } from '../../../../@theme'; +import { ToasterModule } from 'angular2-toaster'; +import { HighlightModule } from 'ngx-highlightjs'; +import { InvitesRequestsComponent } from './invites-requests.component'; +import { InvitesRequestsService } from '../../../../@core/data/invites-requests.service'; +import { CountryRenderComponent } from '../country-render/country-render.component'; +import { InvitesService } from '../../../../@core/data/invites.service'; +import { DeviceService } from '../../../../@core/data/device.service'; +import { TranslateModule } from '@ngx-translate/core'; +import { InvitesRequestsTableModule } from '@app/@shared/render-component/invites-requests/invites-requests.module'; +import { NotifyService } from '@app/@core/services/notify/notify.service'; +import { ConfirmationModalModule } from '../../../../@shared/confirmation-modal/confirmation-modal.module'; +import { NbButtonModule } from '@nebular/theme'; +@NgModule({ + imports: [ + CommonModule, + Ng2SmartTableModule, + ThemeModule, + ToasterModule.forRoot(), + TranslateModule.forChild(), + HighlightModule, + InvitesRequestsTableModule, + ConfirmationModalModule, + NbButtonModule, + ], + declarations: [InvitesRequestsComponent, CountryRenderComponent], + providers: [ + JsonPipe, + InvitesRequestsService, + InvitesService, + DeviceService, + NotifyService, + ], + exports: [CountryRenderComponent, InvitesRequestsComponent], +}) +export class InvitesRequestsModule {} diff --git a/packages/admin-web-angular/src/app/pages/+customers/+invites/country-render/country-render.component.html b/packages/admin-web-angular/src/app/pages/+customers/+invites/country-render/country-render.component.html new file mode 100644 index 0000000..4ee684c --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+customers/+invites/country-render/country-render.component.html @@ -0,0 +1,9 @@ + diff --git a/packages/admin-web-angular/src/app/pages/+customers/+invites/country-render/country-render.component.ts b/packages/admin-web-angular/src/app/pages/+customers/+invites/country-render/country-render.component.ts new file mode 100644 index 0000000..ae82a2b --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+customers/+invites/country-render/country-render.component.ts @@ -0,0 +1,31 @@ +import { Component, AfterViewInit, Input } from '@angular/core'; +import { DefaultEditor, Cell } from 'ng2-smart-table'; + +import { + Country, + CountryName, + countriesIdsToNamesArray, +} from '@modules/server.common/entities/GeoLocation'; + +@Component({ + templateUrl: './country-render.component.html', +}) +export class CountryRenderComponent extends DefaultEditor + implements AfterViewInit { + @Input() + cell: Cell; + + country: string; + + constructor() { + super(); + } + + get countries(): Array<{ id: Country; name: CountryName }> { + return countriesIdsToNamesArray; + } + + ngAfterViewInit() {} + + onChanged(e) {} +} diff --git a/packages/admin-web-angular/src/app/pages/+customers/+invites/invites.component.html b/packages/admin-web-angular/src/app/pages/+customers/+invites/invites.component.html new file mode 100644 index 0000000..190b6f8 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+customers/+invites/invites.component.html @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/packages/admin-web-angular/src/app/pages/+customers/+invites/invites.component.scss b/packages/admin-web-angular/src/app/pages/+customers/+invites/invites.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/packages/admin-web-angular/src/app/pages/+customers/+invites/invites.component.ts b/packages/admin-web-angular/src/app/pages/+customers/+invites/invites.component.ts new file mode 100644 index 0000000..28e59ad --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+customers/+invites/invites.component.ts @@ -0,0 +1,386 @@ +import { + Component, + OnDestroy, + ElementRef, + Renderer2, + AfterViewChecked, + OnInit, +} from '@angular/core'; + +import { Subject, Observable, forkJoin } from 'rxjs'; +import { LocalDataSource } from 'ng2-smart-table'; +import { InvitesService } from '../../../@core/data/invites.service'; +import Invite from '@modules/server.common/entities/Invite'; +import { takeUntil, first } from 'rxjs/operators'; +import { ToasterService } from 'angular2-toaster'; +import { IInviteUpdateObject } from '@modules/server.common/interfaces/IInvite'; +import { IGeolocationUpdateObject } from '@modules/server.common/interfaces/IGeoLocation'; +import { + Country, + getCountryName, +} from '@modules/server.common/entities/GeoLocation'; +import { CountryRenderComponent } from './country-render/country-render.component'; +import { TranslateService } from '@ngx-translate/core'; +import { NotifyService } from '@app/@core/services/notify/notify.service'; +import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; +import { ConfirmationModalComponent } from '../../../@shared/confirmation-modal/confirmation-modal.component'; + +export interface InviteViewModel { + id: string; + country: string; + city: string; + address: string; + house: string; + apartment: string; + invite: string; +} + +const perPage = 10; + +@Component({ + selector: 'ea-invites', + templateUrl: './invites.component.html', + styleUrls: ['/invites.component.scss'], +}) +export class InvitesComponent implements OnInit, OnDestroy, AfterViewChecked { + settingsSmartTable: object; + sourceSmartTable = new LocalDataSource(); + loading: boolean; + + private ngDestroy$ = new Subject(); + + private noInfoSign = ''; + private selectedInvites: InviteViewModel[] = []; + + private addClick2: boolean; + + private dataCount: number; + private $invites; + + constructor( + private readonly _invitesService: InvitesService, + private readonly _elRef: ElementRef, + private readonly _renderer: Renderer2, + private readonly _translateService: TranslateService, + private readonly _notifyService: NotifyService, + private readonly modalService: NgbModal + ) {} + + ngOnInit() { + this._loadSettingsSmartTable(); + this._loadDataSmartTable(); + this._applyTranslationOnSmartTable(); + this.smartTableChange(); + } + + ngAfterViewChecked(): void { + if ( + this._elRef.nativeElement.querySelector( + '.ng2-smart-action-add-create' + ) + ) { + const firstRow = 2; + const columnOffset = 0; + + const td = this._renderer.createElement('td'); + const tr = this._elRef.nativeElement.getElementsByTagName('tr')[ + firstRow + ]; + const refChild = tr.childNodes[columnOffset]; + if (!this.addClick2 && tr.className !== 'ng-star-inserted') { + this._renderer.insertBefore(tr, td, refChild); + this.addClick2 = true; + } + } else { + this.addClick2 = false; + } + } + + get hasSelectedInvites(): boolean { + return this.selectedInvites.length > 0; + } + + selectInviteTmp(ev) { + this.selectedInvites = ev.selected; + } + + async createConfirm(e) { + try { + this.loading = true; + const createDataObject = this.inviteCreateObject(e.newData); + const createInput = await this._invitesService.getCreateInviteObject( + createDataObject + ); + await this._invitesService + .createInvite(createInput) + .pipe(first()) + .toPromise(); + e.confirm.resolve(); + this.loading = false; + const message = `Invite was created`; + this._notifyService.success(message); + } catch (error) { + let message = `Something went wrong`; + if (error.message === 'Validation error') { + message = error.message; + } + this.loading = false; + this._notifyService.error(message); + } + } + + async deleteConfirm(e) { + const activeModal = this.modalService.open(ConfirmationModalComponent, { + size: 'sm', + container: 'nb-layout', + backdrop: 'static', + }); + const modalComponent: ConfirmationModalComponent = + activeModal.componentInstance; + + await modalComponent.confirmEvent + .pipe(takeUntil(modalComponent.ngDestroy$)) + .subscribe((dataEvent) => { + try { + this.loading = true; + this._invitesService + .removeByIds([e.data.id]) + .pipe(first()) + .toPromise(); + + this.loading = false; + const message = `Invite was deleted`; + this._notifyService.success(message); + e.confirm.resolve(); + } catch (error) { + this.loading = false; + const message = `Something went wrong!`; + this._notifyService.error(message); + } + + modalComponent.cancel(); + }); + } + + async editConfirm(e) { + try { + this.loading = true; + const createDataObject = this.inviteCreateObject(e.newData); + const createInput = await this._invitesService.getCreateInviteObject( + createDataObject + ); + + const res = await this._invitesService + .updateInvite(e.data.id, createInput) + .pipe(first()) + .toPromise(); + e.confirm.resolve(res); + this.loading = false; + const message = `Invite was updated`; + this._notifyService.success(message); + } catch (error) { + this.loading = false; + let message = `Something went wrong`; + if (error.message === 'Validation error') { + message = error.message; + } + this._notifyService.error(message); + } + } + + async deleteSelectedRows() { + const idsForDelete: string[] = this.selectedInvites.map((c) => c.id); + try { + this.loading = true; + await this._invitesService + .removeByIds(idsForDelete) + .pipe(first()) + .toPromise(); + + this.loading = false; + const message = `${idsForDelete.length} invites was deleted`; + this._notifyService.success(message); + this.selectedInvites = []; + } catch (error) { + this.loading = false; + const message = `Something went wrong!`; + this._notifyService.error(message); + } + } + + private _applyTranslationOnSmartTable() { + this._translateService.onLangChange.subscribe(() => { + this._loadSettingsSmartTable(); + }); + } + + private _loadSettingsSmartTable() { + const columnTitlePrefix = 'CUSTOMERS_VIEW.SMART_TABLE_COLUMNS.'; + const getTranslate = (name: string): Observable => + this._translateService.get(columnTitlePrefix + name); + + forkJoin( + this._translateService.get('Id'), + getTranslate('CITY'), + getTranslate('COUNTRY'), + getTranslate('STREET_ADDRESS'), + getTranslate('HOUSE'), + getTranslate('APARTMENT'), + getTranslate('INVITE_CODE') + ) + .pipe(takeUntil(this.ngDestroy$)) + .subscribe( + ([ + id, + city, + country, + streetAddress, + apartment, + house, + inviteCode, + ]) => { + this.settingsSmartTable = { + selectMode: 'multi', + add: { + addButtonContent: '', + createButtonContent: + '', + cancelButtonContent: '', + confirmCreate: true, + }, + edit: { + editButtonContent: '', + saveButtonContent: + '', + cancelButtonContent: '', + confirmSave: true, + }, + delete: { + deleteButtonContent: '', + confirmDelete: true, + }, + columns: { + country: { + title: country, + editor: { + type: 'custom', + component: CountryRenderComponent, + }, + }, + city: { title: city }, + address: { title: streetAddress }, + house: { title: house }, + apartment: { title: apartment }, + invite: { title: inviteCode }, + }, + pager: { + display: true, + perPage, + }, + }; + } + ); + } + + private async _loadDataSmartTable(page = 1) { + if (this.$invites) { + await this.$invites.unsubscribe(); + } + let invites: Invite[] = []; + + const loadData = async () => { + const invitesVM = invites.map((invite) => { + this.loading = false; + + return { + country: + getCountryName(invite.geoLocation.countryId).trim() || + this.noInfoSign, + city: invite.geoLocation.city.trim() || this.noInfoSign, + address: + invite.geoLocation.streetAddress.trim() || + this.noInfoSign, + house: invite.geoLocation.house.trim() || this.noInfoSign, + apartment: invite.apartment.trim() || this.noInfoSign, + invite: invite.code.trim() || this.noInfoSign, + id: invite.id, + geoLocation: invite.geoLocation, + }; + }); + + await this.loadDataCount(); + + const invitesData = new Array(this.dataCount); + + invitesData.splice(perPage * (page - 1), perPage, ...invitesVM); + + await this.sourceSmartTable.load(invitesData); + }; + + // We call two times 'loadData' + // This is need because in every change on one of them the server emit and we want to receive it + this.loading = true; + this.$invites = this._invitesService + .getInvites({ + skip: perPage * (page - 1), + limit: perPage, + }) + .pipe(takeUntil(this.ngDestroy$)) + .subscribe(async (i: Invite[]) => { + this.loading = true; + invites = i; + await loadData(); + this.loading = false; + }); + } + + private getUpdateInviteObject(data: InviteViewModel): IInviteUpdateObject { + const geoLocation: IGeolocationUpdateObject = { + countryId: Country[data.country], + city: data.city, + streetAddress: data.address, + house: data.house, + }; + + const invite: IInviteUpdateObject = { + code: data.invite, + apartment: data.apartment, + geoLocation, + }; + + return invite; + } + + private inviteCreateObject(data) { + this.inviteValidation(data); + data.apartment = data.apartment || ' '; + return data; + } + + private inviteValidation(data) { + if (!data.address || !data.city || !data.country || !data.house) { + throw new Error('Validation error'); + } + } + + private async smartTableChange() { + this.sourceSmartTable + .onChanged() + .pipe(takeUntil(this.ngDestroy$)) + .subscribe(async (event) => { + if (event.action === 'page') { + const page = event.paging.page; + this._loadDataSmartTable(page); + } + }); + } + + private async loadDataCount() { + this.dataCount = await this._invitesService.getCountOfInvites(); + } + + ngOnDestroy(): void { + this.ngDestroy$.next(); + this.ngDestroy$.complete(); + } +} diff --git a/packages/admin-web-angular/src/app/pages/+customers/+invites/invites.module.ts b/packages/admin-web-angular/src/app/pages/+customers/+invites/invites.module.ts new file mode 100644 index 0000000..70a2075 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+customers/+invites/invites.module.ts @@ -0,0 +1,43 @@ +import { NgModule } from '@angular/core'; +import { CommonModule, JsonPipe } from '@angular/common'; +import { RouterModule, Routes } from '@angular/router'; +import { FormsModule } from '@angular/forms'; +import { Ng2SmartTableModule } from 'ng2-smart-table'; +import { ToasterModule } from 'angular2-toaster'; +import { TranslateModule } from '@ngx-translate/core'; +import { HighlightModule } from 'ngx-highlightjs'; +import { ThemeModule } from '../../../@theme'; +import { InvitesComponent } from './invites.component'; +import { InvitesService } from '../../../@core/data/invites.service'; +import { InvitesRequestsModule } from './+invites-requests/invites-requests.module'; +import { NbSpinnerModule, NbButtonModule } from '@nebular/theme'; +import { ConfirmationModalModule } from '../../../@shared/confirmation-modal/confirmation-modal.module'; + +const routes: Routes = [ + { + path: '', + component: InvitesComponent, + }, +]; + +@NgModule({ + imports: [ + CommonModule, + FormsModule, + Ng2SmartTableModule, + ThemeModule, + ToasterModule.forRoot(), + RouterModule.forChild(routes), + TranslateModule.forChild(), + HighlightModule, + InvitesRequestsModule, + NbSpinnerModule, + ConfirmationModalModule, + NbButtonModule, + ], + declarations: [InvitesComponent], + providers: [JsonPipe, InvitesService], +}) +export class InvitesModule { + public static routes = routes; +} diff --git a/packages/admin-web-angular/src/app/pages/+customers/customers.component.html b/packages/admin-web-angular/src/app/pages/+customers/customers.component.html new file mode 100644 index 0000000..4c9e95b --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+customers/customers.component.html @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/packages/admin-web-angular/src/app/pages/+customers/customers.component.scss b/packages/admin-web-angular/src/app/pages/+customers/customers.component.scss new file mode 100644 index 0000000..6232ac6 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+customers/customers.component.scss @@ -0,0 +1,57 @@ +nb-card-header { + border-bottom: 0; +} + +nb-card-body { + padding: 0; + border-bottom-left-radius: 0.5rem; + border-bottom-right-radius: 0.5rem; +} + +:host ::ng-deep ng2-smart-table { + tr { + th { + padding: 18px !important; + } + td { + padding: 17px !important; + } + } + + tr.ng2-smart-titles > th:nth-child(1) { + text-align: center; + cursor: pointer; + + input { + margin-left: 1px !important; + cursor: pointer; + } + } + + td.ng2-smart-actions.ng2-smart-action-multiple-select { + text-align: center; + cursor: pointer; + } + + tr.ng2-smart-filters th { + text-align: center; + } + + tr td, + th { + &:first-of-type { + border-left: none; + } + &:last-of-type { + border-right: none; + } + } + + input[type='checkbox'] { + cursor: pointer; + } +} + +:host ::ng-deep ng2-smart-table .customer-image { + width: 74px; +} diff --git a/packages/admin-web-angular/src/app/pages/+customers/customers.component.ts b/packages/admin-web-angular/src/app/pages/+customers/customers.component.ts new file mode 100644 index 0000000..e3f7dec --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+customers/customers.component.ts @@ -0,0 +1,382 @@ +import { AfterViewInit, Component, OnDestroy } from '@angular/core'; +import { Router } from '@angular/router'; +import { LocalDataSource } from 'ng2-smart-table'; +import { UsersService } from '../../@core/data/users.service'; +import { OrdersService } from '../../@core/data/orders.service'; +import User from '@modules/server.common/entities/User'; +import Order from '@modules/server.common/entities/Order'; +import { UserMutationComponent } from '../../@shared/user/user-mutation'; +import { TranslateService } from '@ngx-translate/core'; +import { Observable, forkJoin } from 'rxjs'; +import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; +import { Subject } from 'rxjs'; +import { takeUntil, first } from 'rxjs/operators'; +import { getCountryName } from '@modules/server.common/entities/GeoLocation'; +import { CountryRenderComponent } from './+invites/country-render/country-render.component'; +import { RedirectNameComponent } from '../../@shared/render-component/name-redirect/name-redirect.component'; +import { CustomerImageComponent } from '../../@shared/render-component/customer-table/customer-table/customer-image.component'; +import { NotifyService } from '../../@core/services/notify/notify.service'; +import { CustomerOrdersNumberComponent } from '../../@shared/render-component/customer-table/customer-orders-number/customer-orders-number.component'; +import { CustomerEmailComponent } from '../../@shared/render-component/customer-email/customer-email.component'; +import { CustomerPhoneComponent } from '../../@shared/render-component/customer-phone/customer-phone.component'; +import { BanConfirmComponent } from '@app/@shared/user/ban-confirm'; + +export interface CustomerViewModel { + id: string; + name: string; + image: string; + email: string; + phone: string; + country: string; + city: string; + address: string; + ordersQty: number; + isBanned: boolean; +} + +const perPage = 7; + +@Component({ + selector: 'ea-customers', + templateUrl: './customers.component.html', + styleUrls: ['./customers.component.scss'], +}) +export class CustomersComponent implements AfterViewInit, OnDestroy { + private ngDestroy$ = new Subject(); + + static noInfoSign = ''; + public loading: boolean; + public showBanLoading = false; + + protected customers: User[] = []; + protected orders: Order[] = []; + + public settingsSmartTable: object; + public sourceSmartTable = new LocalDataSource(); + + private _selectedCustomers: CustomerViewModel[] = []; + private dataCount: number; + private $users; + + public _showOnlyBanned: boolean; + + constructor( + private readonly _router: Router, + private readonly _ordersService: OrdersService, + private readonly _usersService: UsersService, + private readonly _modalService: NgbModal, + private readonly _translateService: TranslateService, + private readonly _notifyService: NotifyService + ) { + this._loadSettingsSmartTable(); + } + + public get hasSelectedCustomers(): boolean { + return this._selectedCustomers.length > 0; + } + + ngAfterViewInit() { + this._addCustomHTMLElements(); + this._applyTranslationOnSmartTable(); + this.smartTableChange(); + this._loadDataSmartTable(); + } + + protected selectUser(ev) { + const userId: string = ev.data.id; + this._router.navigate(['/customers/list' + userId]); + } + + public showCreateUserModal() { + this._modalService.open(UserMutationComponent, { + size: 'lg', + container: 'nb-layout', + windowClass: 'ng-custom', + backdrop: 'static', + }); + } + + public selectCustomerTmp(ev) { + this._selectedCustomers = ev.selected; + } + + public deleteSelectedRows() { + const idsForDelete: string[] = this._selectedCustomers.map((w) => w.id); + + try { + this.loading = true; + this._usersService + .removeByIds(idsForDelete) + .pipe(first()) + .toPromise(); + this._selectedCustomers = []; + + this.loading = false; + const message = `Users was removed`; + this._notifyService.success(message); + } catch (error) { + let message = `Something went wrong`; + if (error.message === 'Validation error') { + message = error.message; + } + this.loading = false; + this._notifyService.error(message); + } + } + + public banSelectedRows() { + if (this.isUserBanned) { + this.showUnbanPopup(); + } else { + this.showBanPopup(); + } + } + + private showUnbanPopup() { + const modal = this._modalService.open(BanConfirmComponent, { + size: 'lg', + container: 'nb-layout', + windowClass: 'ng-custom', + backdrop: 'static', + }); + modal.componentInstance.user = this._selectedCustomers[0]; + modal.result + .then(async (user) => { + this.showBanLoading = true; + await this._usersService.unbanUser(user.id); + this._loadDataSmartTable(); + this.showBanLoading = false; + this._notifyService.success(`${user.name} is unbanned!`); + }) + .catch((_) => {}); + } + + private showBanPopup() { + const modal = this._modalService.open(BanConfirmComponent, { + size: 'lg', + container: 'nb-layout', + windowClass: 'ng-custom', + backdrop: 'static', + }); + modal.componentInstance.user = this._selectedCustomers[0]; + modal.result + .then(async (user) => { + this.showBanLoading = true; + await this._usersService.banUser(user.id); + this._loadDataSmartTable(); + this.showBanLoading = false; + this._notifyService.success(`${user.name} is banned!`); + }) + .catch((_) => {}); + } + + private _loadSettingsSmartTable() { + const columnTitlePrefix = 'CUSTOMERS_VIEW.SMART_TABLE_COLUMNS.'; + const getTranslate = (name: string): Observable => + this._translateService.get(columnTitlePrefix + name); + + forkJoin( + this._translateService.get('Id'), + getTranslate('IMAGE'), + getTranslate('NAME'), + getTranslate('EMAIL'), + getTranslate('PHONE'), + getTranslate('COUNTRY'), + getTranslate('CITY'), + getTranslate('ADDRESS'), + getTranslate('ORDERS_QTY') + ).subscribe( + ([ + id, + image, + name, + email, + phone, + country, + city, + address, + ordersQty, + ]) => { + this.settingsSmartTable = { + actions: false, + selectMode: 'multi', + columns: { + images: { + title: image, + class: 'customer-image', + type: 'custom', + renderComponent: CustomerImageComponent, + onComponentInitFunction: (instance) => { + instance.redirectPage = 'customers/list'; + }, + filter: false, + }, + name: { + title: name, + type: 'custom', + renderComponent: RedirectNameComponent, + onComponentInitFunction: (instance) => { + instance.redirectPage = 'customers/list'; + }, + }, + email: { + title: email, + type: 'custom', + renderComponent: CustomerEmailComponent, + }, + phone: { + title: phone, + type: 'custom', + renderComponent: CustomerPhoneComponent, + }, + country: { + title: country, + editor: { + type: 'custom', + component: CountryRenderComponent, + }, + }, + city: { title: city }, + address: { title: address }, + ordersQty: { + title: ordersQty, + type: 'custom', + renderComponent: CustomerOrdersNumberComponent, + filter: false, + }, + }, + pager: { + display: true, + perPage, + }, + }; + } + ); + } + + private async _loadDataSmartTable(page = 1) { + if (this.$users) { + await this.$users.unsubscribe(); + } + let users: User[] = []; + + const loadData = async () => { + const usersOrders = await this._ordersService.getUsersOrdersCountInfo( + users.map((u) => u.id) + ); + + let usersVM = users.map((user) => { + const userOrders = usersOrders.find( + (res) => res['id'] === user.id + ); + + return { + id: user.id, + image: user.image || CustomersComponent.noInfoSign, + name: + user.firstName && user.lastName + ? `${user.firstName} ${user.lastName}` + : user.firstName + ? user.firstName + : user.lastName + ? user.lastName + : user.id, + email: user.email || CustomersComponent.noInfoSign, + phone: user.phone || CustomersComponent.noInfoSign, + country: + getCountryName(user.geoLocation.countryId).trim() || + CustomersComponent.noInfoSign, + city: + user.geoLocation.city || CustomersComponent.noInfoSign, + address: `st. ${ + user.geoLocation.streetAddress || + CustomersComponent.noInfoSign + }, hse. № ${ + user.geoLocation.house || CustomersComponent.noInfoSign + }`, + ordersQty: userOrders ? userOrders.ordersCount : 0, + isBanned: user.isBanned, + }; + }); + + await this.loadDataCount(); + + if (this.showOnlyBanned) { + usersVM = usersVM.filter((user) => user.isBanned); + } + + const usersData = new Array(this.dataCount); + + usersData.splice(perPage * (page - 1), perPage, ...usersVM); + await this.sourceSmartTable.load(usersData); + }; + + // We call two times 'loadData' + // This is need because in every change on one of them the server emit and we want to receive it + this.$users = this._usersService + .getUsers({ + skip: perPage * (page - 1), + limit: perPage, + }) + .pipe(takeUntil(this.ngDestroy$)) + .subscribe(async (u: User[]) => { + users = u; + await loadData(); + }); + } + + private _applyTranslationOnSmartTable() { + this._translateService.onLangChange + .pipe(takeUntil(this.ngDestroy$)) + .subscribe(() => { + this._loadSettingsSmartTable(); + }); + } + + // This is just workaround to show some search icon on smart table, in the future maybe we must find better solution. + private _addCustomHTMLElements(): any { + document.querySelector( + 'tr.ng2-smart-filters > th:nth-child(1)' + ).innerHTML = ''; + } + + private async smartTableChange() { + this.sourceSmartTable + .onChanged() + .pipe(takeUntil(this.ngDestroy$)) + .subscribe(async (event) => { + if (event.action === 'page') { + const page = event.paging.page; + this._loadDataSmartTable(page); + } + }); + } + + private async loadDataCount() { + this.dataCount = await this._usersService.getCountOfUsers(); + } + + public get isOnlyOneCustomerSelected(): boolean { + return this._selectedCustomers.length === 1; + } + + public get isUserBanned() { + return ( + this._selectedCustomers[0] && this._selectedCustomers[0].isBanned + ); + } + + public set showOnlyBanned(v: boolean) { + this._showOnlyBanned = v; + this._loadDataSmartTable(); + } + + public get showOnlyBanned(): boolean { + return this._showOnlyBanned; + } + + ngOnDestroy() { + this.ngDestroy$.next(); + this.ngDestroy$.complete(); + } +} diff --git a/packages/admin-web-angular/src/app/pages/+customers/customers.module.ts b/packages/admin-web-angular/src/app/pages/+customers/customers.module.ts new file mode 100644 index 0000000..0a0b913 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+customers/customers.module.ts @@ -0,0 +1,56 @@ +import { NgModule } from '@angular/core'; +import { CommonModule, JsonPipe } from '@angular/common'; +import { RouterModule, Routes } from '@angular/router'; +import { Ng2SmartTableModule } from 'ng2-smart-table'; +import { ToasterModule } from 'angular2-toaster'; +import { TranslateModule } from '@ngx-translate/core'; +import { CustomersComponent } from './customers.component'; +import { ThemeModule } from '../../@theme'; +import { HighlightModule } from 'ngx-highlightjs'; +import { RenderComponentsModule } from '../../@shared/render-component/render-components.module'; +import { GeoLocationService } from '../../@core/data/geo-location.service'; +import { UserMutationModule } from '../../@shared/user/user-mutation'; +import { CustomerTableModule } from '../../@shared/render-component/customer-table/customer-table.module'; +import { NbSpinnerModule, NbButtonModule } from '@nebular/theme'; +import { NotifyService } from '@app/@core/services/notify/notify.service'; +import { BanConfirmModule } from '@app/@shared/user/ban-confirm'; + +const routes: Routes = [ + { + path: 'list', + component: CustomersComponent, + }, + { + path: 'invites', + loadChildren: () => + import('./+invites/invites.module').then((m) => m.InvitesModule), + }, + { + path: 'list/:id', + loadChildren: () => + import('./+customer/customer.module').then((m) => m.CustomerModule), + }, +]; + +@NgModule({ + imports: [ + CommonModule, + Ng2SmartTableModule, + ThemeModule, + ToasterModule.forRoot(), + RouterModule.forChild(routes), + TranslateModule.forChild(), + HighlightModule, + RenderComponentsModule, + UserMutationModule, + CustomerTableModule, + NbSpinnerModule, + BanConfirmModule, + NbButtonModule, + ], + declarations: [CustomersComponent], + providers: [JsonPipe, GeoLocationService, NotifyService], +}) +export class CustomersModule { + public static routes = routes; +} diff --git a/packages/admin-web-angular/src/app/pages/+dashboard/charts-panel/chart-panel-header/chart-panel-header.component.html b/packages/admin-web-angular/src/app/pages/+dashboard/charts-panel/chart-panel-header/chart-panel-header.component.html new file mode 100644 index 0000000..246c666 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+dashboard/charts-panel/chart-panel-header/chart-panel-header.component.html @@ -0,0 +1,76 @@ +
+ + + +
+
+ {{ 'DASHBOARD_VIEW.CHARTS.FROM' | translate }}: + +
+
+ {{ 'DASHBOARD_VIEW.CHARTS.TO' | translate }}: + +
+ +
+
+ +
+ + + x + {{ 'DASHBOARD_VIEW.CHARTS.FROM' | translate }}: {{ fromDate | myReplacePipe: '-':'.' }}
+ {{ 'DASHBOARD_VIEW.CHARTS.TO' | translate }}: {{ toDate | myReplacePipe: '-':'.' }} +
+
+ + +
diff --git a/packages/admin-web-angular/src/app/pages/+dashboard/charts-panel/chart-panel-header/chart-panel-header.component.scss b/packages/admin-web-angular/src/app/pages/+dashboard/charts-panel/chart-panel-header/chart-panel-header.component.scss new file mode 100644 index 0000000..a3448db --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+dashboard/charts-panel/chart-panel-header/chart-panel-header.component.scss @@ -0,0 +1,77 @@ +@import '../../../../@theme/styles/themes'; +@import '~bootstrap/scss/mixins/breakpoints'; +@import '~@nebular/theme/styles/global/breakpoints'; + +@include nb-install-component() { + a.close { + color: red; + opacity: 0.3; + padding-left: 3px; + font-size: 16px; + } + a.close:hover { + opacity: 1; + } + + .chart-header { + display: flex; + justify-content: space-between; + margin-bottom: 2.125rem; + align-items: center; + padding-right: 5%; + } + + .dropdown { + min-width: 8.125rem; + } + + div.custom-range { + input { + display: block !important; + height: auto !important; + margin-bottom: 4px !important; + } + div { + padding-top: 7px; + padding-bottom: 4px; + text-align: center; + } + } + + @include media-breakpoint-down(is) { + .chart-header { + flex-direction: column-reverse; + } + + ea-legend-chart { + align-self: flex-start; + + ::ng-deep .legends { + padding-left: 0; + font-size: 0.875rem; + } + + ::ng-deep .legend { + margin-left: 1rem; + } + } + + .dropdown { + margin-top: 1.5rem; + margin-bottom: 1.5rem; + align-self: flex-end; + } + } + + .range-period-container { + display: flex; + width: 100%; + padding-right: 2%; + padding-left: 7%; + button, + small { + white-space: nowrap; + margin-left: auto; + } + } +} diff --git a/packages/admin-web-angular/src/app/pages/+dashboard/charts-panel/chart-panel-header/chart-panel-header.component.ts b/packages/admin-web-angular/src/app/pages/+dashboard/charts-panel/chart-panel-header/chart-panel-header.component.ts new file mode 100644 index 0000000..860921a --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+dashboard/charts-panel/chart-panel-header/chart-panel-header.component.ts @@ -0,0 +1,303 @@ +import { + Component, + EventEmitter, + Input, + OnDestroy, + Output, + ViewChild, + ElementRef, + OnInit, +} from '@angular/core'; +import { + NbMediaBreakpoint, + NbMediaBreakpointsService, + NbThemeService, +} from '@nebular/theme'; +import { takeWhile } from 'rxjs/operators'; +import { TranslateService } from '@ngx-translate/core'; +import { ToasterService } from 'angular2-toaster'; +import { ChartsPanelComponent } from '../charts-panel.component'; +import { Observable, Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; + +@Component({ + selector: 'ea-chart-panel-header', + styleUrls: ['./chart-panel-header.component.scss'], + templateUrl: './chart-panel-header.component.html', +}) +export class ChartPanelHeaderComponent implements OnInit, OnDestroy { + @Input() + type: string; + + @Input() + preservedRangeEvent: Observable<{ from: Date; to: Date }>; + + @Input() + clearRangeFromParent: Observable; + + @Output() + periodChange = new EventEmitter(); + + @Output() + clearSelectedRange = new EventEmitter(); + + @Output() + customRangeDaysDiff = new EventEmitter<{ + fromDate: Date; + toDate: Date; + daysDiff: number; + }>(); + + @ViewChild('customDateRangeButton', { static: false }) + customDateRangeButton: ElementRef; + + isDateRangeSelected = false; + types: Array<{ key: string; value: string }> = []; + + chartLegend: Array<{ iconColor: string; title: string }> = []; + breakpoint: NbMediaBreakpoint = { name: '', width: 0 }; + breakpoints: any; + currentTheme: string; + + fromDate: string; + toDate: string; + + private _alive = true; + private _ngDestroy$ = new Subject(); + + constructor( + private readonly _themeService: NbThemeService, + private readonly _breakpointService: NbMediaBreakpointsService, + private readonly _translateService: TranslateService, + private readonly _toasterService: ToasterService + ) { + this._themeService + .getJsTheme() + .pipe(takeWhile(() => this._alive)) + .subscribe((theme) => { + this.orderProfitLegend = theme.variables.orderProfitLegend; + this.currentTheme = theme.name; + setTimeout(() => { + this._setLegendItems(); + this._setTypes(); + }, 1500); + }); + + this.breakpoints = this._breakpointService.getBreakpointsMap(); + this._themeService + .onMediaQueryChange() + .pipe(takeWhile(() => this._alive)) + .subscribe(([oldValue, newValue]) => { + this.breakpoint = newValue; + }); + } + + ngOnInit() { + this._listenLanguageChange(); + this._listenForPreservedRanges(); + this._listenClearRangeFromParent(); + } + + get periodText() { + switch (this.type) { + case ChartsPanelComponent._PERIODS.today: + return this._translations.today; + case ChartsPanelComponent._PERIODS.lastWeek: + return this._translations.lastWeek; + case ChartsPanelComponent._PERIODS.lastMonth: + return this._translations.lastMonth; + case ChartsPanelComponent._PERIODS.currentYear: + return this._translations.currentYear; + case ChartsPanelComponent._PERIODS.years: + return this._translations.years; + default: + return this._translations.selectPeriod; + } + } + + changePeriod(period: string): void { + this.type = period; + this.periodChange.emit(period); + this.clearRange(); + } + + selectDateRange() { + const from = new Date(this.fromDate); + const to = new Date(this.toDate); + + const isDateRangeValid = this._validateDateRange(from, to); + + if (isDateRangeValid) { + this.isDateRangeSelected = true; + const timeDiff = Math.abs(to.getTime() - from.getTime()); + const daysDiff = Math.ceil(timeDiff / (24 * 1000 * 3600)); + + this.customRangeDaysDiff.emit({ + fromDate: from, + toDate: to, + daysDiff, + }); + + this._closeDateRangeDropdown(); + } + } + + clearRange() { + this.isDateRangeSelected = false; + } + + clearRangeAndSendEvent() { + this.clearRange(); + this.clearSelectedRange.emit(); + } + + private _listenClearRangeFromParent() { + this.clearRangeFromParent + .pipe(takeUntil(this._ngDestroy$)) + .subscribe(() => { + this.isDateRangeSelected = false; + }); + } + + private _setTypes() { + this.types = [ + { + key: ChartsPanelComponent._PERIODS.today, + value: this._translations.today, + }, + { + key: ChartsPanelComponent._PERIODS.lastWeek, + value: this._translations.lastWeek, + }, + { + key: ChartsPanelComponent._PERIODS.lastMonth, + value: this._translations.lastMonth, + }, + { + key: ChartsPanelComponent._PERIODS.currentYear, + value: this._translations.currentYear, + }, + { + key: ChartsPanelComponent._PERIODS.years, + value: this._translations.years, + }, + ]; + } + + private _listenForPreservedRanges() { + this.preservedRangeEvent + .pipe(takeUntil(this._ngDestroy$)) + .subscribe(({ from, to }: { from: Date; to: Date }) => { + const fromYear = from.getFullYear().toString(); + let fromMonth = (from.getMonth() + 1).toString(); + let fromDate = from.getDate().toString(); + + if (fromMonth.length === 1) { + fromMonth = `0${fromMonth}`; + } + if (fromDate.length === 1) { + fromDate = `0${fromDate}`; + } + + const toYear = to.getFullYear().toString(); + let toMonth = (to.getMonth() + 1).toString(); + let toDate = to.getDate().toString(); + + if (toMonth.length === 1) { + toMonth = `0${toMonth}`; + } + if (toDate.length === 1) { + toDate = `0${toDate}`; + } + + this.fromDate = `${fromYear}-${fromMonth}-${fromDate}`; + this.toDate = `${toYear}-${toMonth}-${toDate}`; + + this.isDateRangeSelected = true; + }); + } + + orderProfitLegend: any; + private _setLegendItems() { + this.chartLegend = [ + { + iconColor: this.orderProfitLegend.firstItem, + title: this._translations.completed, + }, + { + iconColor: this.orderProfitLegend.secondItem, + title: this._translations.canceled, + }, + { + iconColor: this.orderProfitLegend.thirdItem, + title: this._translations.totalOrders, + }, + ]; + } + + private _listenLanguageChange() { + this._translateService.onLangChange.subscribe(() => { + this._setLegendItems(); + this._setTypes(); + }); + } + + private _translate(key: string) { + let translationResult = ''; + + this._translateService.get(key).subscribe((res) => { + translationResult = res; + }); + + return translationResult; + } + + private _validateDateRange(from: Date, to: Date): boolean { + let isDateRangeValid = true; + + if (isNaN(from.valueOf()) || isNaN(to.valueOf())) { + this._toasterService.pop( + 'error', + 'Invalid date format', + 'Date should be in format "mm/dd/yyyy"' + ); + + isDateRangeValid = false; + } else if (from > to) { + this._toasterService.pop( + 'error', + 'Wrong date ranges', + '"From date" must be before or equal to "to date"!' + ); + + isDateRangeValid = false; + } + + return isDateRangeValid; + } + + private _closeDateRangeDropdown() { + this.customDateRangeButton.nativeElement.click(); + } + + private get _translations() { + const prefix = 'DASHBOARD_VIEW.CHARTS'; + return { + today: this._translate(`${prefix}.TODAY`), + lastWeek: this._translate(`${prefix}.LAST_WEEK`), + lastMonth: this._translate(`${prefix}.LAST_MONTH`), + currentYear: this._translate(`${prefix}.CURRENT_YEAR`), + years: this._translate(`${prefix}.YEARS`), + totalOrders: this._translate(`${prefix}.ALL_ORDERS`), + canceled: this._translate(`${prefix}.CANCELED`), + completed: this._translate(`${prefix}.PAYMENT`), + selectPeriod: this._translate(`${prefix}.SELECT_PERIOD`), + }; + } + + ngOnDestroy() { + this._alive = false; + this._ngDestroy$.next(); + this._ngDestroy$.complete(); + } +} diff --git a/packages/admin-web-angular/src/app/pages/+dashboard/charts-panel/chart-panel-summary/chart-panel-summary.component.html b/packages/admin-web-angular/src/app/pages/+dashboard/charts-panel/chart-panel-summary/chart-panel-summary.component.html new file mode 100644 index 0000000..fe4bff4 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+dashboard/charts-panel/chart-panel-summary/chart-panel-summary.component.html @@ -0,0 +1,51 @@ +
+
+
+ +
+

+ {{ item.values.completed.title }} ${{ + item.values.completed.value | myNumberWithCommas + }} +

+ +

+ {{ item.values.total.title }} ${{ + item.values.total.value | myNumberWithCommas + }} +

+ +

+ {{ item.values.cancelled.title }} ${{ + item.values.cancelled.value | myNumberWithCommas + }} +

+
+ +
+

+ {{ item.values.completed.title }} + {{ item.values.completed.value }} +

+ +

+ {{ item.values.total.title }} + {{ item.values.total.value }} +

+ +

+ {{ item.values.cancelled.title }} + {{ item.values.cancelled.value }} +

+
+
+ + + +
+
+
diff --git a/packages/admin-web-angular/src/app/pages/+dashboard/charts-panel/chart-panel-summary/chart-panel-summary.component.scss b/packages/admin-web-angular/src/app/pages/+dashboard/charts-panel/chart-panel-summary/chart-panel-summary.component.scss new file mode 100644 index 0000000..6f72328 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+dashboard/charts-panel/chart-panel-summary/chart-panel-summary.component.scss @@ -0,0 +1,74 @@ +@import '../../../../@theme/styles/themes'; +@import '~bootstrap/scss/mixins/breakpoints'; +@import '~@nebular/theme/styles/global/breakpoints'; + +@include nb-install-component() { + .summary-container { + display: flex; + flex: 1; + background-color: nb-theme(chart-panel-summary-background-color); + box-shadow: nb-theme(chart-panel-summary-box-shadow); + justify-content: space-between; + padding: 1.8rem 4rem 1.2rem; + margin-bottom: 1rem; + border: nb-theme(chart-panel-summary-border-width) solid + nb-theme(chart-panel-summary-border-color); + border-left: none; + border-right: none; + + .summary { + width: 100%; + } + + small { + margin-bottom: 0.5rem; + } + + .col-4 { + line-height: 1.3rem !important; + } + } + + .value { + font-size: 2rem; + color: #2a2a2a; + span div small { + font-size: 0.5em; + display: block; + } + } + + @include media-breakpoint-down(sm) { + .value, + .title { + font-weight: 500; + } + + .title { + font-size: 0.875rem; + } + + .value { + font-size: 1.25rem; + } + } + + @include media-breakpoint-down(is) { + .summary-container { + padding-left: 1.25rem; + padding-right: 1.25rem; + } + + .value { + margin-top: 0.5rem; + } + } + + .waiting { + opacity: 0.2; + } + + .loading-placeholder { + padding: 13px 61px; + } +} diff --git a/packages/admin-web-angular/src/app/pages/+dashboard/charts-panel/chart-panel-summary/chart-panel-summary.component.ts b/packages/admin-web-angular/src/app/pages/+dashboard/charts-panel/chart-panel-summary/chart-panel-summary.component.ts new file mode 100644 index 0000000..6e17f93 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+dashboard/charts-panel/chart-panel-summary/chart-panel-summary.component.ts @@ -0,0 +1,21 @@ +import { Component, Input } from '@angular/core'; + +@Component({ + selector: 'ea-chart-panel-summary', + styleUrls: ['./chart-panel-summary.component.scss'], + templateUrl: './chart-panel-summary.component.html', +}) +export class ChartPanelSummaryComponent { + @Input() + summary: Array<{ + values: { + total: { title: string; value: number }; + completed: { title: string; value: number }; + cancelled: { title: string; value: number }; + }; + isPrice: boolean; + }>; + + @Input() + isLoading: boolean; +} diff --git a/packages/admin-web-angular/src/app/pages/+dashboard/charts-panel/charts-panel.component.html b/packages/admin-web-angular/src/app/pages/+dashboard/charts-panel/charts-panel.component.html new file mode 100644 index 0000000..8bcebce --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+dashboard/charts-panel/charts-panel.component.html @@ -0,0 +1,60 @@ + + + + + +
+ + + + + + +
+
+ + +
+ + + + + + +
+
+
+
diff --git a/packages/admin-web-angular/src/app/pages/+dashboard/charts-panel/charts-panel.component.scss b/packages/admin-web-angular/src/app/pages/+dashboard/charts-panel/charts-panel.component.scss new file mode 100644 index 0000000..34a7de3 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+dashboard/charts-panel/charts-panel.component.scss @@ -0,0 +1,37 @@ +@import '../../../@theme/styles/themes'; + +$legend-all-orders-color: #00977e; +$legend-payment-color: #6935ca; +$legend-canceled-color: #3f4fda; + +@include nb-install-component() { + ::ng-deep nb-tabset { + display: flex; + flex-direction: column; + flex: 1; + + ul { + border-bottom: none; + } + } + + nb-tab { + flex: 1; + padding: 0; + padding-bottom: 1.25rem; + } + + .chart-container { + flex: 1; + height: 100%; + display: flex; + flex-direction: column; + overflow: hidden; + } + + ea-chart-panel-header, + ea-profit-chart, + ea-orders-chart { + padding: 0 1.25rem; + } +} diff --git a/packages/admin-web-angular/src/app/pages/+dashboard/charts-panel/charts-panel.component.ts b/packages/admin-web-angular/src/app/pages/+dashboard/charts-panel/charts-panel.component.ts new file mode 100644 index 0000000..d7d34ac --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+dashboard/charts-panel/charts-panel.component.ts @@ -0,0 +1,2514 @@ +import { Component, OnDestroy, ViewChild, OnInit, Input } from '@angular/core'; +import { OrdersChartComponent } from './charts/orders-chart/orders-chart.component'; +import { ProfitChartComponent } from './charts/profit-chart/profit-chart.component'; +import { TranslateService } from '@ngx-translate/core'; +import { + OrdersChart, + OrdersChartService, +} from '@app/@core/services/dashboard/orders-chart.service'; +import { ProfitChart } from '@app/@core/services/dashboard/profit-chart.service'; +import { Subject } from 'rxjs'; +import { takeWhile } from 'rxjs/operators'; +import { + OrdersProfitChartService, + OrderProfitChartSummary, +} from '@app/@core/services/dashboard/orders-profit-chart.service'; +import Order from '@modules/server.common/entities/Order'; +import { PeriodsService } from '@app/@core/services/dashboard/periods.service'; +import { DashboardLoadingIndicatorState } from '@app/models/DashboardLoadingIndicatorState'; +import { toDate } from '@modules/server.common/utils'; +import { takeUntil } from 'rxjs/operators'; + +interface IOrdersChartModel { + total: any; + cancelled: any; + completed: any; +} + +@Component({ + selector: 'ea-ecommerce-charts', + styleUrls: ['./charts-panel.component.scss'], + templateUrl: './charts-panel.component.html', +}) +export class ChartsPanelComponent implements OnInit, OnDestroy { + preservedRanges$ = new Subject<{ from: Date; to: Date }>(); + clearRange$ = new Subject(); + + loading = new DashboardLoadingIndicatorState(); + + period: string = ChartsPanelComponent._PERIODS.today; + chartPanelSummary: OrderProfitChartSummary[] = []; + ordersChartData: OrdersChart; + profitChartData: ProfitChart; + + @ViewChild('ordersChart') + ordersChart: OrdersChartComponent; + @ViewChild('profitChart') + profitChart: ProfitChartComponent; + @ViewChild('ordersProfitTab') + ordersProfitTab: any; + + private _ordersToday: IOrdersChartModel; + private _ordersLastWeek: IOrdersChartModel; + private _ordersLastMonth: IOrdersChartModel; + private _ordersCurrentYear: IOrdersChartModel; + private _ordersYears: IOrdersChartModel; + private _ordersDateRange: IOrdersChartModel; + private _ordersWeeksRange: IOrdersChartModel; + private _ordersMonthsRange: IOrdersChartModel; + private _ordersYearsRange: IOrdersChartModel; + + private _yearsLabelRange = { + from: null as number, + to: null as number, + }; + private _dateLabelRange = { + from: null as Date, + to: null as Date, + }; + + private _orders: Order[] = []; + + private _chartPanelSummaryTotal: number = 0; + private _chartPanelSummaryCompleted: number = 0; + private _chartPanelSummaryCancelled: number = 0; + + private _isOrderChartSelected: boolean = true; + private _isDateRangeSelected: boolean = false; + + private _alive = true; + private _ngDestroy$ = new Subject(); + + constructor( + private readonly _translateService: TranslateService, + private readonly _ordersProfitChartService: OrdersProfitChartService, + private readonly _periodService: PeriodsService, + private readonly _ordersChartService: OrdersChartService + ) { + this._ordersProfitChartService + .getOrderProfitChartSummary() + .pipe(takeWhile(() => this._alive)) + .subscribe((summary) => { + this.chartPanelSummary = summary; + }); + + this.getOrdersChartData(this.period); + this.getProfitChartData(this.period); + } + + ngOnInit() { + this._resetChartData(); + this._listenLangChange(); + } + + @Input() + set orders(orders: Order[]) { + this._orders = orders; + this._setupAndDisplayChartsData(); + } + + get orders(): Order[] { + return this._orders; + } + + @Input() + set isOrdersLoad(isLoading: boolean) { + this._toggleLoading.chartPanelSummary(isLoading); + this._toggleLoading.chart(isLoading); + } + + static get _PERIODS() { + return { + today: 'today', // hours in last day + lastWeek: 'lastWeek', // weekdays in last week + lastMonth: 'lastMonth', // days in last month + currentYear: 'currentYear', // months in last year + years: 'years', // years from first order to last + rangeDay: 'range day', // 0 days difference + rangeDays: 'range days', // 1 - 27 days + rangeWeeks: 'range weeks', // 28 - 60 days + rangeMonths: 'range months', // 60 - 365 days + rangeYears: 'range years', // more than 365 days + }; + } + + private get _translations() { + const rootPrefix = 'DASHBOARD_VIEW.CHARTS'; + + return { + TOTAL_ORDERS_OVER_PERIOD: this._translate( + `${rootPrefix}.TOTAL_ORDERS` + ), + TOTAL_COMPLETED_ORDERS_OVER_PERIOD: this._translate( + `${rootPrefix}.TOTAL_COMPLETED_ORDERS` + ), + TOTAL_CANCELLED_ORDERS_OVER_PERIOD: this._translate( + `${rootPrefix}.TOTAL_CANCELLED_ORDERS` + ), + + TOTAL_REVENUE_OVER_PERIOD: this._translate( + `${rootPrefix}.TOTAL_REVENUE_ALL_ORDERS` + ), + TOTAL_REVENUE_COMPLETED_ORDERS_OVER_PERIOD: this._translate( + `${rootPrefix}.TOTAL_REVENUE_COMPLETED_ORDERS` + ), + TOTAL_REVENUE_CANCELLED_ORDERS_OVER_PERIOD: this._translate( + `${rootPrefix}.TOTAL_LOST_REVENUE_CANCELLED_ORDERS` + ), + }; + } + + private get _toggleLoading() { + return { + chartPanelSummary: (isLoading) => + (this.loading.chartPanelSummary = isLoading), + chart: (isLoading) => (this.loading.chart = isLoading), + }; + } + + ngOnDestroy() { + this._alive = false; + this._ngDestroy$.next(); + this._ngDestroy$.complete(); + } + + clearRange() { + this.setPeriodAndGetChartData(ChartsPanelComponent._PERIODS.today); + } + + setPeriodAndGetChartData(value: string): void { + if (this.period !== value) { + this.period = value; + } + this._isDateRangeSelected = false; + this._setupAndDisplayChartsData(); + } + + selectDateRangeOrderCharts({ + fromDate, + toDate, + daysDiff, + }: { + fromDate: Date; + toDate: Date; + daysDiff: number; + }) { + this._dateLabelRange.from = fromDate; + this._dateLabelRange.to = toDate; + this.period = this._calculateCustomPeriod(daysDiff); + this._isDateRangeSelected = true; + this._setupAndDisplayChartsData(); + } + + changeTab(selectedTab) { + const isOrdersTabActive = this.ordersProfitTab.tabs.first.active; + if (!isOrdersTabActive) { + this.profitChart.resizeChart(); + this._isOrderChartSelected = false; + } else { + this._isOrderChartSelected = true; + this.ordersChart.resizeChart(); + } + + if (this._isDateRangeSelected) { + this._sendRangeIfSelected(); + } else { + this._clearRangeFromHeader(); + } + this._setupAndDisplayChartsData(); + } + + getOrdersChartData(period: string) { + this._ordersProfitChartService + .getOrdersChartData(period) + .pipe(takeWhile(() => this._alive)) + .subscribe((ordersChartData) => { + this.ordersChartData = ordersChartData; + }); + } + + getProfitChartData(period: string) { + this._ordersProfitChartService + .getProfitChartData(period) + .pipe(takeWhile(() => this._alive)) + .subscribe((profitChartData) => { + this.profitChartData = profitChartData; + }); + } + + private _setChartsSummary() { + this.chartPanelSummary = []; + + if (this._isOrderChartSelected) { + this._setOrdersChartSummary(); + } else { + this._setProfitChartSummary(); + } + } + + private _setOrdersChartSummary() { + this.chartPanelSummary.push({ + values: { + total: { + title: this._translations.TOTAL_ORDERS_OVER_PERIOD, + value: this._chartPanelSummaryTotal, + }, + completed: { + title: this._translations + .TOTAL_COMPLETED_ORDERS_OVER_PERIOD, + value: this._chartPanelSummaryCompleted, + }, + cancelled: { + title: this._translations + .TOTAL_CANCELLED_ORDERS_OVER_PERIOD, + value: this._chartPanelSummaryCancelled, + }, + }, + isPrice: false, + }); + } + + private _setProfitChartSummary() { + this.chartPanelSummary.push({ + values: { + total: { + title: this._translations.TOTAL_REVENUE_OVER_PERIOD, + value: this._chartPanelSummaryTotal, + }, + completed: { + title: this._translations + .TOTAL_REVENUE_COMPLETED_ORDERS_OVER_PERIOD, + value: this._chartPanelSummaryCompleted, + }, + cancelled: { + title: this._translations + .TOTAL_REVENUE_CANCELLED_ORDERS_OVER_PERIOD, + value: this._chartPanelSummaryCancelled, + }, + }, + isPrice: true, + }); + } + + private _setupLabelsYearsRange(order: Order) { + const orderYear: number = toDate(order._createdAt).getFullYear(); + + if (orderYear < this._yearsLabelRange.from) { + this._yearsLabelRange.from = orderYear; + } + } + + private _resetYearsLabelRange() { + this._yearsLabelRange = { + from: Number.MAX_SAFE_INTEGER, + to: new Date().getFullYear(), + }; + } + + private _setupAndDisplayChartsData() { + this._resetChartData(); + this._resetYearsLabelRange(); + this._resetChartPanelSummaryValues(); + + this._orders + .filter((order) => { + switch (this.period) { + case ChartsPanelComponent._PERIODS.today: + return this._isOrderTodayPeriodMatch(order); + case ChartsPanelComponent._PERIODS.lastWeek: + return this._isOrderLastWeekPeriodMatch(order); + case ChartsPanelComponent._PERIODS.lastMonth: + return this._isOrderLastMonthPeriodMatch(order); + case ChartsPanelComponent._PERIODS.currentYear: + return this._isOrderCurrentYearPeriodMatch(order); + case ChartsPanelComponent._PERIODS.years: + this._setupLabelsYearsRange(order); + return true; + case ChartsPanelComponent._PERIODS.rangeDay: + return this._isOrderCustomDayPeriodMatch(order); + case ChartsPanelComponent._PERIODS.rangeDays: + case ChartsPanelComponent._PERIODS.rangeWeeks: + case ChartsPanelComponent._PERIODS.rangeMonths: + case ChartsPanelComponent._PERIODS.rangeYears: + return this._isOrderRangePeriodMatch(order); + } + }) + .forEach((order) => { + const orderDateCreated = new Date(order._createdAt); + + const orderHour = orderDateCreated.getHours(); + const orderWeekDay = orderDateCreated.getDay(); + const orderDate = orderDateCreated.getDate(); + const orderMonth = orderDateCreated.getMonth(); + const orderYear = orderDateCreated.getFullYear(); + + const orderDateRange = this._periodService.getDateLabelKey( + orderDateCreated + ); + const orderWeekRange = this._periodService.getWeekLabelKey( + orderDateCreated, + this._getDateWeekNumber + ); + const orderMonthRange = this._periodService.getMonthLabelKey( + orderDateCreated + ); + + if (this._isOrderChartSelected) { + this._incrementOrdersAmountSummary(order); + + this._setupOrdersChartData( + orderHour, + orderWeekDay, + orderDate, + orderMonth, + orderYear, + orderDateRange, + orderWeekRange, + orderMonthRange, + order.isCancelled + ); + } else { + const orderTotalPrice = order.totalPrice; + this._incrementOrdersProfitSummary(order, orderTotalPrice); + + this._setupProfitChartData( + orderHour, + orderWeekDay, + orderDate, + orderMonth, + orderYear, + orderDateRange, + orderWeekRange, + orderMonthRange, + orderTotalPrice, + order.isCancelled + ); + } + }); + + if (this._isOrderChartSelected) { + this._displayOrdersChart(); + } else { + this._displayProfitChart(); + } + + this._setChartsSummary(); + } + + private _incrementOrdersAmountSummary(order: Order) { + this._chartPanelSummaryTotal += 1; + order.isCancelled + ? (this._chartPanelSummaryCancelled += 1) + : (this._chartPanelSummaryCompleted += 1); + } + + private _incrementOrdersProfitSummary( + order: Order, + orderTotalPrice: number + ) { + this._chartPanelSummaryTotal += orderTotalPrice; + order.isCancelled + ? (this._chartPanelSummaryCancelled += orderTotalPrice) + : (this._chartPanelSummaryCompleted += orderTotalPrice); + } + + private _setupOrdersChartData( + orderHour: number, + orderWeekDay: number, + orderDate: number, + orderMonth: number, + orderYear: number, + orderDateRange: string, + orderWeekRange: string, + orderMonthRange: string, + isCancelled: boolean + ) { + this._setupOrdersChartTotal( + orderHour, + orderWeekDay, + orderDate, + orderMonth, + orderYear, + orderDateRange, + orderWeekRange, + orderMonthRange + ); + + if (isCancelled) { + this._setupOrdersChartCanceled( + orderHour, + orderWeekDay, + orderDate, + orderMonth, + orderYear, + orderDateRange, + orderWeekRange, + orderMonthRange + ); + } else { + this._setupOrdersChartCompleted( + orderHour, + orderWeekDay, + orderDate, + orderMonth, + orderYear, + orderDateRange, + orderWeekRange, + orderMonthRange + ); + } + } + + private _setupProfitChartData( + orderHour: number, + orderWeekDay: number, + orderDate: number, + orderMonth: number, + orderYear: number, + orderDateRange: string, + orderWeekRange: string, + orderMonthRange: string, + orderTotalPrice: number, + isCancelled: boolean + ) { + this._setupProfitChartTotal( + orderHour, + orderWeekDay, + orderDate, + orderMonth, + orderYear, + orderDateRange, + orderWeekRange, + orderMonthRange, + orderTotalPrice + ); + + if (isCancelled) { + this._setupProfitChartCanceled( + orderHour, + orderWeekDay, + orderDate, + orderMonth, + orderYear, + orderDateRange, + orderWeekRange, + orderMonthRange, + orderTotalPrice + ); + } else { + this._setupProfitChartCompleted( + orderHour, + orderWeekDay, + orderDate, + orderMonth, + orderYear, + orderDateRange, + orderWeekRange, + orderMonthRange, + orderTotalPrice + ); + } + } + + private _setupOrdersChartTotal( + orderHour: number, + orderWeekDay: number, + orderDate: number, + orderMonth: number, + orderYear: number, + orderDateRange: string, + orderWeekRange: string, + orderMonthRange: string + ) { + switch (this.period) { + case ChartsPanelComponent._PERIODS.today: + this._incrementTotalOrdersAmount(this._ordersToday, orderHour); + break; + case ChartsPanelComponent._PERIODS.lastWeek: + this._incrementTotalOrdersAmount( + this._ordersLastWeek, + orderWeekDay + ); + break; + case ChartsPanelComponent._PERIODS.lastMonth: + this._incrementTotalOrdersAmount( + this._ordersLastMonth, + orderDate + ); + break; + case ChartsPanelComponent._PERIODS.currentYear: + this._incrementTotalOrdersAmount( + this._ordersCurrentYear, + orderMonth + ); + break; + case ChartsPanelComponent._PERIODS.years: + this._incrementTotalOrdersAmount(this._ordersYears, orderYear); + break; + case ChartsPanelComponent._PERIODS.rangeDay: + this._incrementTotalOrdersAmount(this._ordersToday, orderHour); + break; + case ChartsPanelComponent._PERIODS.rangeDays: + this._incrementTotalOrdersAmount( + this._ordersDateRange, + orderDateRange + ); + break; + case ChartsPanelComponent._PERIODS.rangeWeeks: + this._incrementTotalOrdersAmount( + this._ordersWeeksRange, + orderWeekRange + ); + break; + case ChartsPanelComponent._PERIODS.rangeMonths: + this._incrementTotalOrdersAmount( + this._ordersMonthsRange, + orderMonthRange + ); + break; + case ChartsPanelComponent._PERIODS.rangeYears: + this._incrementTotalOrdersAmount( + this._ordersYearsRange, + orderYear + ); + break; + } + } + + /** This method can substitute method above */ + // private _setupOrdersChartTotal( + // orderHour: number, + // orderWeekDay: number, + // orderDate: number, + // orderMonth: number, + // orderYear: number, + // orderDateRange: string, + // orderWeekRange: string, + // orderMonthRange: string + // ) { + // this._setupOrdersChartTotalCommon(orderHour); + + // if (!this._isDateRangeSelected) { + // this._setupOrdersChartTotalOptionSelected( + // orderWeekDay, + // orderDate, + // orderMonth, + // orderYear + // ); + // } else { + // this._setupOrdersChartTotalRangeSelected( + // orderDateRange, + // orderWeekRange, + // orderMonthRange, + // orderYear + // ); + // } + // } + + private _setupOrdersChartCanceled( + orderHour: number, + orderWeekDay: number, + orderDate: number, + orderMonth: number, + orderYear: number, + orderDateRange: string, + orderWeekRange: string, + orderMonthRange: string + ) { + switch (this.period) { + case ChartsPanelComponent._PERIODS.today: + this._incrementCanceledOrdersAmount( + this._ordersToday, + orderHour + ); + break; + case ChartsPanelComponent._PERIODS.lastWeek: + this._incrementCanceledOrdersAmount( + this._ordersLastWeek, + orderWeekDay + ); + break; + case ChartsPanelComponent._PERIODS.lastMonth: + this._incrementCanceledOrdersAmount( + this._ordersLastMonth, + orderDate + ); + break; + case ChartsPanelComponent._PERIODS.currentYear: + this._incrementCanceledOrdersAmount( + this._ordersCurrentYear, + orderMonth + ); + break; + case ChartsPanelComponent._PERIODS.years: + this._incrementCanceledOrdersAmount( + this._ordersYears, + orderYear + ); + break; + case ChartsPanelComponent._PERIODS.rangeDay: + this._incrementCanceledOrdersAmount( + this._ordersToday, + orderHour + ); + break; + case ChartsPanelComponent._PERIODS.rangeDays: + this._incrementCanceledOrdersAmount( + this._ordersDateRange, + orderDateRange + ); + break; + case ChartsPanelComponent._PERIODS.rangeWeeks: + this._incrementCanceledOrdersAmount( + this._ordersWeeksRange, + orderWeekRange + ); + break; + case ChartsPanelComponent._PERIODS.rangeMonths: + this._incrementCanceledOrdersAmount( + this._ordersMonthsRange, + orderMonthRange + ); + break; + case ChartsPanelComponent._PERIODS.rangeYears: + this._incrementCanceledOrdersAmount( + this._ordersYearsRange, + orderYear + ); + break; + } + } + + /** This method can substitute method above */ + // private _setupOrdersChartCancelled( + // orderHour: number, + // orderWeekDay: number, + // orderDate: number, + // orderMonth: number, + // orderYear: number, + // orderDateRange: string, + // orderWeekRange: string, + // orderMonthRange: string + // ) { + // this._setupOrdersChartCancelledCommon(orderHour); + + // if (!this._isDateRangeSelected) { + // this._setupOrdersChartCancelledOptionSelected( + // orderWeekDay, + // orderDate, + // orderMonth, + // orderYear + // ); + // } else { + // this._setupOrdersChartCancelledRangeSelected( + // orderDateRange, + // orderWeekRange, + // orderMonthRange, + // orderYear + // ); + // } + // } + + private _setupOrdersChartCompleted( + orderHour: number, + orderWeekDay: number, + orderDate: number, + orderMonth: number, + orderYear: number, + orderDateRange: string, + orderWeekRange: string, + orderMonthRange: string + ) { + switch (this.period) { + case ChartsPanelComponent._PERIODS.today: + this._incrementCompletedOrdersAmount( + this._ordersToday, + orderHour + ); + break; + case ChartsPanelComponent._PERIODS.lastWeek: + this._incrementCompletedOrdersAmount( + this._ordersLastWeek, + orderWeekDay + ); + break; + case ChartsPanelComponent._PERIODS.lastMonth: + this._incrementCompletedOrdersAmount( + this._ordersLastMonth, + orderDate + ); + break; + case ChartsPanelComponent._PERIODS.currentYear: + this._incrementCompletedOrdersAmount( + this._ordersCurrentYear, + orderMonth + ); + break; + case ChartsPanelComponent._PERIODS.years: + this._incrementCompletedOrdersAmount( + this._ordersYears, + orderYear + ); + break; + case ChartsPanelComponent._PERIODS.rangeDay: + this._incrementCompletedOrdersAmount( + this._ordersToday, + orderHour + ); + break; + case ChartsPanelComponent._PERIODS.rangeDays: + this._incrementCompletedOrdersAmount( + this._ordersDateRange, + orderDateRange + ); + break; + case ChartsPanelComponent._PERIODS.rangeWeeks: + this._incrementCompletedOrdersAmount( + this._ordersWeeksRange, + orderWeekRange + ); + break; + case ChartsPanelComponent._PERIODS.rangeMonths: + this._incrementCompletedOrdersAmount( + this._ordersMonthsRange, + orderMonthRange + ); + break; + case ChartsPanelComponent._PERIODS.rangeYears: + this._incrementCompletedOrdersAmount( + this._ordersYearsRange, + orderYear + ); + break; + } + } + + /** This method can substitute method above */ + // private _setupOrdersChartCompleted( + // orderHour: number, + // orderWeekDay: number, + // orderDate: number, + // orderMonth: number, + // orderYear: number, + // orderDateRange: string, + // orderWeekRange: string, + // orderMonthRange: string + // ) { + // this._setupOrdersChartCompletedCommon(orderHour); + + // if (!this._isDateRangeSelected) { + // this._setupOrdersChartCompletedOptionSelected( + // orderWeekDay, + // orderDate, + // orderMonth, + // orderYear + // ); + // } else { + // this._setupOrdersChartCompletedRangeSelected( + // orderDateRange, + // orderWeekRange, + // orderMonthRange, + // orderYear + // ); + // } + // } + + private _setupProfitChartTotal( + orderHour: number, + orderWeekDay: number, + orderDate: number, + orderMonth: number, + orderYear: number, + orderDateRange: string, + orderWeekRange: string, + orderMonthRange: string, + orderTotalPrice: number + ) { + switch (this.period) { + case ChartsPanelComponent._PERIODS.today: + this._incrementTotalOrdersProfit( + this._ordersToday, + orderHour, + orderTotalPrice + ); + break; + case ChartsPanelComponent._PERIODS.lastWeek: + this._incrementTotalOrdersProfit( + this._ordersLastWeek, + orderWeekDay, + orderTotalPrice + ); + + break; + case ChartsPanelComponent._PERIODS.lastMonth: + this._incrementTotalOrdersProfit( + this._ordersLastMonth, + orderDate, + orderTotalPrice + ); + + break; + case ChartsPanelComponent._PERIODS.currentYear: + this._incrementTotalOrdersProfit( + this._ordersCurrentYear, + orderMonth, + orderTotalPrice + ); + + break; + case ChartsPanelComponent._PERIODS.years: + this._incrementTotalOrdersProfit( + this._ordersYears, + orderYear, + orderTotalPrice + ); + break; + case ChartsPanelComponent._PERIODS.rangeDay: + this._incrementTotalOrdersProfit( + this._ordersToday, + orderHour, + orderTotalPrice + ); + break; + case ChartsPanelComponent._PERIODS.rangeDays: + this._incrementTotalOrdersProfit( + this._ordersDateRange, + orderDateRange, + orderTotalPrice + ); + + break; + case ChartsPanelComponent._PERIODS.rangeWeeks: + this._incrementTotalOrdersProfit( + this._ordersWeeksRange, + orderWeekRange, + orderTotalPrice + ); + + break; + case ChartsPanelComponent._PERIODS.rangeMonths: + this._incrementTotalOrdersProfit( + this._ordersMonthsRange, + orderMonthRange, + orderTotalPrice + ); + + break; + case ChartsPanelComponent._PERIODS.rangeYears: + this._incrementTotalOrdersProfit( + this._ordersYearsRange, + orderYear, + orderTotalPrice + ); + break; + } + } + + /** This method can substitute method above */ + // private _setupProfitChartTotal( + // orderHour: number, + // orderWeekDay: number, + // orderDate: number, + // orderMonth: number, + // orderYear: number, + // orderDateRange: string, + // orderWeekRange: string, + // orderMonthRange: string, + // orderTotalPrice: number + // ) { + // this._setupProfitChartTotalCommon(orderHour, orderTotalPrice); + + // if (!this._isDateRangeSelected) { + // this._setupProfitChartTotalOptionSelected( + // orderWeekDay, + // orderDate, + // orderMonth, + // orderYear, + // orderTotalPrice + // ); + // } else { + // this._setupProfitChartTotalRangeSelected( + // orderDateRange, + // orderWeekRange, + // orderMonthRange, + // orderYear, + // orderTotalPrice + // ); + // } + // } + + private _setupProfitChartCanceled( + orderHour: number, + orderWeekDay: number, + orderDate: number, + orderMonth: number, + orderYear: number, + orderDateRange: string, + orderWeekRange: string, + orderMonthRange: string, + orderTotalPrice: number + ) { + switch (this.period) { + case ChartsPanelComponent._PERIODS.today: + this._incrementCanceledOrdersProfit( + this._ordersToday, + orderHour, + orderTotalPrice + ); + break; + case ChartsPanelComponent._PERIODS.lastWeek: + this._incrementCanceledOrdersProfit( + this._ordersLastWeek, + orderWeekDay, + orderTotalPrice + ); + + break; + case ChartsPanelComponent._PERIODS.lastMonth: + this._incrementCanceledOrdersProfit( + this._ordersLastMonth, + orderDate, + orderTotalPrice + ); + + break; + case ChartsPanelComponent._PERIODS.currentYear: + this._incrementCanceledOrdersProfit( + this._ordersCurrentYear, + orderMonth, + orderTotalPrice + ); + + break; + case ChartsPanelComponent._PERIODS.years: + this._incrementCanceledOrdersProfit( + this._ordersYears, + orderYear, + orderTotalPrice + ); + break; + case ChartsPanelComponent._PERIODS.rangeDay: + this._incrementCanceledOrdersProfit( + this._ordersToday, + orderHour, + orderTotalPrice + ); + break; + case ChartsPanelComponent._PERIODS.rangeDays: + this._incrementCanceledOrdersProfit( + this._ordersDateRange, + orderDateRange, + orderTotalPrice + ); + + break; + case ChartsPanelComponent._PERIODS.rangeWeeks: + this._incrementCanceledOrdersProfit( + this._ordersWeeksRange, + orderWeekRange, + orderTotalPrice + ); + + break; + case ChartsPanelComponent._PERIODS.rangeMonths: + this._incrementCanceledOrdersProfit( + this._ordersMonthsRange, + orderMonthRange, + orderTotalPrice + ); + + break; + case ChartsPanelComponent._PERIODS.rangeYears: + this._incrementCanceledOrdersProfit( + this._ordersYearsRange, + orderYear, + orderTotalPrice + ); + break; + } + } + + /** This method can substitute method above */ + // private _setupProfitChartCanceled( + // orderHour: number, + // orderWeekDay: number, + // orderDate: number, + // orderMonth: number, + // orderYear: number, + // orderDateRange: string, + // orderWeekRange: string, + // orderMonthRange: string, + // orderTotalPrice: number + // ) { + // this._setupProfitChartCancelledCommon(orderHour, orderTotalPrice); + + // if (!this._isDateRangeSelected) { + // this._setupProfitChartCancelledOptionSelected( + // orderWeekDay, + // orderDate, + // orderMonth, + // orderYear, + // orderTotalPrice + // ); + // } else { + // this._setupProfitChartCancelledRangeSelected( + // orderDateRange, + // orderTotalPrice, + // orderWeekRange, + // orderMonthRange, + // orderYear + // ); + // } + // } + + private _setupProfitChartCompleted( + orderHour: number, + orderWeekDay: number, + orderDate: number, + orderMonth: number, + orderYear: number, + orderDateRange: string, + orderWeekRange: string, + orderMonthRange: string, + orderTotalPrice: number + ) { + switch (this.period) { + case ChartsPanelComponent._PERIODS.today: + this._incrementCompletedOrdersProfit( + this._ordersToday, + orderHour, + orderTotalPrice + ); + break; + case ChartsPanelComponent._PERIODS.lastWeek: + this._incrementCompletedOrdersProfit( + this._ordersLastWeek, + orderWeekDay, + orderTotalPrice + ); + + break; + case ChartsPanelComponent._PERIODS.lastMonth: + this._incrementCompletedOrdersProfit( + this._ordersLastMonth, + orderDate, + orderTotalPrice + ); + + break; + case ChartsPanelComponent._PERIODS.currentYear: + this._incrementCompletedOrdersProfit( + this._ordersCurrentYear, + orderMonth, + orderTotalPrice + ); + + break; + case ChartsPanelComponent._PERIODS.years: + this._incrementCompletedOrdersProfit( + this._ordersYears, + orderYear, + orderTotalPrice + ); + break; + case ChartsPanelComponent._PERIODS.rangeDay: + this._incrementCompletedOrdersProfit( + this._ordersToday, + orderHour, + orderTotalPrice + ); + break; + case ChartsPanelComponent._PERIODS.rangeDays: + this._incrementCompletedOrdersProfit( + this._ordersDateRange, + orderDateRange, + orderTotalPrice + ); + + break; + case ChartsPanelComponent._PERIODS.rangeWeeks: + this._incrementCompletedOrdersProfit( + this._ordersWeeksRange, + orderWeekRange, + orderTotalPrice + ); + + break; + case ChartsPanelComponent._PERIODS.rangeMonths: + this._incrementCompletedOrdersProfit( + this._ordersMonthsRange, + orderMonthRange, + orderTotalPrice + ); + + break; + case ChartsPanelComponent._PERIODS.rangeYears: + this._incrementCompletedOrdersProfit( + this._ordersYearsRange, + orderYear, + orderTotalPrice + ); + break; + } + } + + /** This method can substitute method above */ + // private _setupProfitChartCompleted( + // orderHour: number, + // orderWeekDay: number, + // orderDate: number, + // orderMonth: number, + // orderYear: number, + // orderDateRange: string, + // orderWeekRange: string, + // orderMonthRange: string, + // orderTotalPrice: number + // ) { + // this._setupProfitChartCompletedCommon(orderHour, orderTotalPrice); + + // if (!this._isDateRangeSelected) { + // this._setupProfitChartCompletedOptionSelected( + // orderWeekDay, + // orderDate, + // orderMonth, + // orderYear, + // orderTotalPrice + // ); + // } else { + // this._setupProfitChartCompletedRangeSelected( + // orderDateRange, + // orderTotalPrice, + // orderWeekRange, + // orderMonthRange, + // orderYear + // ); + // } + // } + + private _setupOrdersChartTotalCommon(orderHour: number) { + this._incrementTotalOrdersAmount(this._ordersToday, orderHour); + } + + private _setupOrdersChartTotalOptionSelected( + orderWeekDay: number, + orderDate: number, + orderMonth: number, + orderYear: number + ) { + this._incrementTotalOrdersAmount(this._ordersLastWeek, orderWeekDay); + + this._incrementTotalOrdersAmount(this._ordersLastMonth, orderDate); + + this._incrementTotalOrdersAmount(this._ordersCurrentYear, orderMonth); + + this._incrementTotalOrdersAmount(this._ordersYears, orderYear); + } + + private _setupOrdersChartTotalRangeSelected( + orderDateRange: string, + orderWeekRange: string, + orderMonthRange: string, + orderYear: number + ) { + this._incrementTotalOrdersAmount(this._ordersDateRange, orderDateRange); + + this._incrementTotalOrdersAmount( + this._ordersWeeksRange, + orderWeekRange + ); + + this._incrementTotalOrdersAmount( + this._ordersMonthsRange, + orderMonthRange + ); + + this._incrementTotalOrdersAmount(this._ordersYearsRange, orderYear); + } + + private _setupOrdersChartCancelledCommon(orderHour: number) { + this._incrementCanceledOrdersAmount(this._ordersToday, orderHour); + } + + private _setupOrdersChartCancelledOptionSelected( + orderWeekDay: number, + orderDate: number, + orderMonth: number, + orderYear: number + ) { + this._incrementCanceledOrdersAmount(this._ordersLastWeek, orderWeekDay); + + this._incrementCanceledOrdersAmount(this._ordersLastMonth, orderDate); + + this._incrementCanceledOrdersAmount( + this._ordersCurrentYear, + orderMonth + ); + + this._incrementCanceledOrdersAmount(this._ordersYears, orderYear); + } + + private _setupOrdersChartCancelledRangeSelected( + orderDateRange: string, + orderWeekRange: string, + orderMonthRange: string, + orderYear: number + ) { + this._incrementCanceledOrdersAmount( + this._ordersDateRange, + orderDateRange + ); + + this._incrementCanceledOrdersAmount( + this._ordersWeeksRange, + orderWeekRange + ); + + this._incrementCanceledOrdersAmount( + this._ordersMonthsRange, + orderMonthRange + ); + + this._incrementCanceledOrdersAmount(this._ordersYearsRange, orderYear); + } + + private _setupOrdersChartCompletedCommon(orderHour: number) { + this._incrementCompletedOrdersAmount(this._ordersToday, orderHour); + } + + private _setupOrdersChartCompletedOptionSelected( + orderWeekDay: number, + orderDate: number, + orderMonth: number, + orderYear: number + ) { + this._incrementCompletedOrdersAmount( + this._ordersLastWeek, + orderWeekDay + ); + + this._incrementCompletedOrdersAmount(this._ordersLastMonth, orderDate); + + this._incrementCompletedOrdersAmount( + this._ordersCurrentYear, + orderMonth + ); + + this._incrementCompletedOrdersAmount(this._ordersYears, orderYear); + } + + private _setupOrdersChartCompletedRangeSelected( + orderDateRange: string, + orderWeekRange: string, + orderMonthRange: string, + orderYear: number + ) { + this._incrementCompletedOrdersAmount( + this._ordersDateRange, + orderDateRange + ); + + this._incrementCompletedOrdersAmount( + this._ordersWeeksRange, + orderWeekRange + ); + + this._incrementCompletedOrdersAmount( + this._ordersMonthsRange, + orderMonthRange + ); + + this._incrementCompletedOrdersAmount(this._ordersYearsRange, orderYear); + } + + private _setupProfitChartTotalCommon( + orderHour: number, + orderTotalPrice: number + ) { + this._incrementTotalOrdersProfit( + this._ordersToday, + orderHour, + orderTotalPrice + ); + } + + private _setupProfitChartTotalOptionSelected( + orderWeekDay: number, + orderDate: number, + orderMonth: number, + orderYear: number, + orderTotalPrice: number + ) { + this._incrementTotalOrdersProfit( + this._ordersLastWeek, + orderWeekDay, + orderTotalPrice + ); + + this._incrementTotalOrdersProfit( + this._ordersLastMonth, + orderDate, + orderTotalPrice + ); + + this._incrementTotalOrdersProfit( + this._ordersCurrentYear, + orderMonth, + orderTotalPrice + ); + + this._incrementTotalOrdersProfit( + this._ordersYears, + orderYear, + orderTotalPrice + ); + } + + private _setupProfitChartTotalRangeSelected( + orderDateRange: string, + orderWeekRange: string, + orderMonthRange: string, + orderYear: number, + orderTotalPrice: number + ) { + this._incrementTotalOrdersProfit( + this._ordersDateRange, + orderDateRange, + orderTotalPrice + ); + + this._incrementTotalOrdersProfit( + this._ordersWeeksRange, + orderWeekRange, + orderTotalPrice + ); + + this._incrementTotalOrdersProfit( + this._ordersMonthsRange, + orderMonthRange, + orderTotalPrice + ); + + this._incrementTotalOrdersProfit( + this._ordersYearsRange, + orderYear, + orderTotalPrice + ); + } + + private _setupProfitChartCancelledCommon( + orderHour: number, + orderTotalPrice: number + ) { + this._incrementCanceledOrdersProfit( + this._ordersToday, + orderHour, + orderTotalPrice + ); + } + + private _setupProfitChartCancelledOptionSelected( + orderWeekDay: number, + orderDate: number, + orderMonth: number, + orderYear: number, + orderTotalPrice: number + ) { + this._incrementCanceledOrdersProfit( + this._ordersLastWeek, + orderWeekDay, + orderTotalPrice + ); + + this._incrementCanceledOrdersProfit( + this._ordersLastMonth, + orderDate, + orderTotalPrice + ); + + this._incrementCanceledOrdersProfit( + this._ordersCurrentYear, + orderMonth, + orderTotalPrice + ); + + this._incrementCanceledOrdersProfit( + this._ordersYears, + orderYear, + orderTotalPrice + ); + } + + private _setupProfitChartCancelledRangeSelected( + orderDateRange: string, + orderTotalPrice: number, + orderWeekRange: string, + orderMonthRange: string, + orderYear: number + ) { + this._incrementCanceledOrdersProfit( + this._ordersDateRange, + orderDateRange, + orderTotalPrice + ); + + this._incrementCanceledOrdersProfit( + this._ordersWeeksRange, + orderWeekRange, + orderTotalPrice + ); + + this._incrementCanceledOrdersProfit( + this._ordersMonthsRange, + orderMonthRange, + orderTotalPrice + ); + + this._incrementCanceledOrdersProfit( + this._ordersYearsRange, + orderYear, + orderTotalPrice + ); + } + + private _setupProfitChartCompletedCommon( + orderHour: number, + orderTotalPrice: number + ) { + this._incrementCompletedOrdersProfit( + this._ordersToday, + orderHour, + orderTotalPrice + ); + } + + private _setupProfitChartCompletedOptionSelected( + orderWeekDay: number, + orderDate: number, + orderMonth: number, + orderYear: number, + orderTotalPrice: number + ) { + this._incrementCompletedOrdersProfit( + this._ordersLastWeek, + orderWeekDay, + orderTotalPrice + ); + + this._incrementCompletedOrdersProfit( + this._ordersLastMonth, + orderDate, + orderTotalPrice + ); + + this._incrementCompletedOrdersProfit( + this._ordersCurrentYear, + orderMonth, + orderTotalPrice + ); + + this._incrementCompletedOrdersProfit( + this._ordersYears, + orderYear, + orderTotalPrice + ); + } + + private _setupProfitChartCompletedRangeSelected( + orderDateRange: string, + orderTotalPrice: number, + orderWeekRange: string, + orderMonthRange: string, + orderYear: number + ) { + this._incrementCompletedOrdersProfit( + this._ordersDateRange, + orderDateRange, + orderTotalPrice + ); + + this._incrementCompletedOrdersProfit( + this._ordersWeeksRange, + orderWeekRange, + orderTotalPrice + ); + + this._incrementCompletedOrdersProfit( + this._ordersMonthsRange, + orderMonthRange, + orderTotalPrice + ); + + this._incrementCompletedOrdersProfit( + this._ordersYearsRange, + orderYear, + orderTotalPrice + ); + } + + private _incrementTotalOrdersAmount( + varToStore: IOrdersChartModel, + key: number | string + ) { + if (!varToStore.total[key]) { + varToStore.total[key] = 0; + } + varToStore.total[key] += 1; + } + + private _incrementCanceledOrdersAmount( + varToStore: IOrdersChartModel, + key: number | string + ) { + if (!varToStore.cancelled[key]) { + varToStore.cancelled[key] = 0; + } + varToStore.cancelled[key] += 1; + } + + private _incrementCompletedOrdersAmount( + varToStore: IOrdersChartModel, + key: number | string + ) { + if (!varToStore.completed[key]) { + varToStore.completed[key] = 0; + } + varToStore.completed[key] += 1; + } + + private _incrementTotalOrdersProfit( + varToStore: IOrdersChartModel, + key: number | string, + value: number + ) { + if (!varToStore.total[key]) { + varToStore.total[key] = 0; + } + varToStore.total[key] += value; + } + + private _incrementCanceledOrdersProfit( + varToStore: IOrdersChartModel, + key: number | string, + value: number + ) { + if (!varToStore.cancelled[key]) { + varToStore.cancelled[key] = 0; + } + varToStore.cancelled[key] += value; + } + + private _incrementCompletedOrdersProfit( + varToStore: IOrdersChartModel, + key: number | string, + value: number + ) { + if (!varToStore.completed[key]) { + varToStore.completed[key] = 0; + } + varToStore.completed[key] += value; + } + + private _displayOrdersChart() { + if (this.period === ChartsPanelComponent._PERIODS.today) { + this._setupOrdersChartForToday(); + } else if (this.period === ChartsPanelComponent._PERIODS.lastWeek) { + this._setupOrdersChartForLastWeek(); + } else if (this.period === ChartsPanelComponent._PERIODS.lastMonth) { + this._setupOrdersChartForLastMonth(); + } else if (this.period === ChartsPanelComponent._PERIODS.currentYear) { + this._setupOrdersChartForCurrentYear(); + } else if (this.period === ChartsPanelComponent._PERIODS.years) { + this._setupOrdersChartForYears(); + } else if (this.period === ChartsPanelComponent._PERIODS.rangeDay) { + this._setupOrdersChartForToday(); + } else if (this.period === ChartsPanelComponent._PERIODS.rangeDays) { + this._setupOrdersChartForDaysRange(); + } else if (this.period === ChartsPanelComponent._PERIODS.rangeWeeks) { + this._setupOrdersChartForWeeksRange(); + } else if (this.period === ChartsPanelComponent._PERIODS.rangeMonths) { + this._setupOrdersChartForMonthsRange(); + } else if (this.period === ChartsPanelComponent._PERIODS.rangeYears) { + this._setupOrdersChartForYearsRange(); + } + } + + private _displayProfitChart() { + if (this.period === ChartsPanelComponent._PERIODS.today) { + this._setupProfitChartForToday(); + } else if (this.period === ChartsPanelComponent._PERIODS.lastWeek) { + this._setupProfitChartForLastWeek(); + } else if (this.period === ChartsPanelComponent._PERIODS.lastMonth) { + this._setupProfitChartForLastMonth(); + } else if (this.period === ChartsPanelComponent._PERIODS.currentYear) { + this._setupProfitChartForCurrentYear(); + } else if (this.period === ChartsPanelComponent._PERIODS.years) { + this._setupProfitChartForYears(); + } else if (this.period === ChartsPanelComponent._PERIODS.rangeDay) { + this._setupProfitChartForToday(); + } else if (this.period === ChartsPanelComponent._PERIODS.rangeDays) { + this._setupProfitChartForDaysRange(); + } else if (this.period === ChartsPanelComponent._PERIODS.rangeWeeks) { + this._setupProfitChartForWeeksRange(); + } else if (this.period === ChartsPanelComponent._PERIODS.rangeMonths) { + this._setupProfitChartForMonthsRange(); + } else if (this.period === ChartsPanelComponent._PERIODS.rangeYears) { + this._setupProfitChartForYearsRange(); + } + } + + private _setupOrdersChartForToday() { + const hours = this._periodService.getHours(); + const initialLinesData = this._getInitialChartData(hours.length); + + this.ordersChartData = { + chartLabel: this._ordersChartService.getDataLabels( + hours.length, + hours + ), + linesData: initialLinesData, + }; + + Object.keys(this._ordersToday.total).forEach((key) => { + const val = this._ordersToday.total[key]; + const indexKey = this._getIndexKey(key, hours.length - 1); + + this.ordersChartData.linesData[0][indexKey] = val; + }); + Object.keys(this._ordersToday.cancelled).forEach((key) => { + const val = this._ordersToday.cancelled[key]; + const indexKey = this._getIndexKey(key, hours.length - 1); + + this.ordersChartData.linesData[1][indexKey] = val; + }); + Object.keys(this._ordersToday.completed).forEach((key) => { + const val = this._ordersToday.completed[key]; + const indexKey = this._getIndexKey(key, hours.length - 1); + + this.ordersChartData.linesData[2][indexKey] = val; + }); + } + + private _setupOrdersChartForLastWeek() { + const weeksDays = this._periodService.getWeekDays(); + const initialLinesData = this._getInitialChartData(weeksDays.length); + + this.ordersChartData = { + chartLabel: this._ordersChartService.getDataLabels( + weeksDays.length, + weeksDays + ), + linesData: initialLinesData, + }; + + Object.keys(this._ordersLastWeek.total).forEach((key) => { + const val = this._ordersLastWeek.total[key]; + const indexKey = this._getIndexKey(key, weeksDays.length - 1); + + this.ordersChartData.linesData[0][indexKey] = val; + }); + Object.keys(this._ordersLastWeek.cancelled).forEach((key) => { + const val = this._ordersLastWeek.cancelled[key]; + const indexKey = this._getIndexKey(key, weeksDays.length - 1); + + this.ordersChartData.linesData[1][indexKey] = val; + }); + Object.keys(this._ordersLastWeek.completed).forEach((key) => { + const val = this._ordersLastWeek.completed[key]; + const indexKey = this._getIndexKey(key, weeksDays.length - 1); + + this.ordersChartData.linesData[2][indexKey] = val; + }); + } + + private _setupOrdersChartForLastMonth() { + const dates = this._periodService.getDatesLastMonth(); + const initialLinesData = this._getInitialChartData(dates.length); + + this.ordersChartData = { + chartLabel: this._ordersChartService.getDataLabels( + dates.length, + dates + ), + linesData: initialLinesData, + }; + + // Because the dates strat from 1 but array indexes start from 0 and we use dates for indexing. + const indexFromDate = (key) => +key - 1; + + Object.keys(this._ordersLastMonth.total).forEach((key) => { + const val = this._ordersLastMonth.total[key]; + + const indexKey = indexFromDate(key); + this.ordersChartData.linesData[0][indexKey] = val; + }); + Object.keys(this._ordersLastMonth.cancelled).forEach((key) => { + const val = this._ordersLastMonth.cancelled[key]; + + const indexKey = indexFromDate(key); + this.ordersChartData.linesData[1][indexKey] = val; + }); + Object.keys(this._ordersLastMonth.completed).forEach((key) => { + const val = this._ordersLastMonth.completed[key]; + + const indexKey = indexFromDate(key); + this.ordersChartData.linesData[2][indexKey] = val; + }); + + console.log(this.ordersChartData.linesData); + } + + private _setupOrdersChartForCurrentYear() { + const months = this._periodService.getMonths(); + const initialLinesData = this._getInitialChartData(months.length); + + this.ordersChartData = { + chartLabel: this._ordersChartService.getDataLabels( + months.length, + months + ), + linesData: initialLinesData, + }; + + Object.keys(this._ordersCurrentYear.total).forEach((key) => { + const val = this._ordersCurrentYear.total[key]; + this.ordersChartData.linesData[0][key] = val; + }); + Object.keys(this._ordersCurrentYear.cancelled).forEach((key) => { + const val = this._ordersCurrentYear.cancelled[key]; + this.ordersChartData.linesData[1][key] = val; + }); + Object.keys(this._ordersCurrentYear.completed).forEach((key) => { + const val = this._ordersCurrentYear.completed[key]; + this.ordersChartData.linesData[2][key] = val; + }); + } + + private _setupOrdersChartForYears() { + const years = this._periodService.getYearLabels(this._yearsLabelRange); + + if (years.length === 1) { + years.push(`${this._yearsLabelRange.to}`); + } + + const initialLinesData = this._getInitialChartData(years.length); + + this.ordersChartData = { + chartLabel: this._ordersChartService.getDataLabels( + years.length, + years + ), + linesData: initialLinesData, + }; + + const indexByKey = this._generageIndexesByKeys(years); + + Object.keys(this._ordersYears.total).forEach((key) => { + const val = this._ordersYears.total[key]; + this.ordersChartData.linesData[0][indexByKey[key]] = val; + }); + Object.keys(this._ordersYears.cancelled).forEach((key) => { + const val = this._ordersYears.cancelled[key]; + this.ordersChartData.linesData[1][indexByKey[key]] = val; + }); + Object.keys(this._ordersYears.completed).forEach((key) => { + const val = this._ordersYears.completed[key]; + this.ordersChartData.linesData[2][indexByKey[key]] = val; + }); + } + + private _setupOrdersChartForDaysRange() { + const { keys, labels } = this._periodService.getDatesLabelsKeys( + this._dateLabelRange + ); + + const initialLinesData = this._getInitialChartData(labels.length); + + this.ordersChartData = { + chartLabel: this._ordersChartService.getDataLabels( + labels.length, + labels + ), + linesData: initialLinesData, + }; + + const indexByKey = this._generageIndexesByKeys(keys); + + Object.keys(this._ordersDateRange.total).forEach((key) => { + const val = this._ordersDateRange.total[key]; + this.ordersChartData.linesData[0][indexByKey[key]] = val; + }); + Object.keys(this._ordersDateRange.cancelled).forEach((key) => { + const val = this._ordersDateRange.cancelled[key]; + this.ordersChartData.linesData[1][indexByKey[key]] = val; + }); + Object.keys(this._ordersDateRange.completed).forEach((key) => { + const val = this._ordersDateRange.completed[key]; + this.ordersChartData.linesData[2][indexByKey[key]] = val; + }); + } + + private _setupOrdersChartForWeeksRange() { + const { keys, labels } = this._periodService.getWeekLabelsKeys( + this._dateLabelRange, + this._getDateWeekNumber + ); + + const initialLinesData = this._getInitialChartData(labels.length); + + this.ordersChartData = { + chartLabel: this._ordersChartService.getDataLabels( + labels.length, + labels + ), + linesData: initialLinesData, + }; + + const indexByKey = this._generageIndexesByKeys(keys); + + Object.keys(this._ordersWeeksRange.total).forEach((key) => { + const val = this._ordersWeeksRange.total[key]; + this.ordersChartData.linesData[0][indexByKey[key]] = val; + }); + Object.keys(this._ordersWeeksRange.cancelled).forEach((key) => { + const val = this._ordersWeeksRange.cancelled[key]; + this.ordersChartData.linesData[1][indexByKey[key]] = val; + }); + Object.keys(this._ordersWeeksRange.completed).forEach((key) => { + const val = this._ordersWeeksRange.completed[key]; + this.ordersChartData.linesData[2][indexByKey[key]] = val; + }); + } + + private _setupOrdersChartForMonthsRange() { + const { keys, labels } = this._periodService.getMonthLabelsKeys( + this._dateLabelRange + ); + + const initialLinesData = this._getInitialChartData(labels.length); + + this.ordersChartData = { + chartLabel: this._ordersChartService.getDataLabels( + labels.length, + labels + ), + linesData: initialLinesData, + }; + + const indexByKey = this._generageIndexesByKeys(keys); + + Object.keys(this._ordersMonthsRange.total).forEach((key) => { + const val = this._ordersMonthsRange.total[key]; + this.ordersChartData.linesData[0][indexByKey[key]] = val; + }); + Object.keys(this._ordersMonthsRange.cancelled).forEach((key) => { + const val = this._ordersMonthsRange.cancelled[key]; + this.ordersChartData.linesData[1][indexByKey[key]] = val; + }); + Object.keys(this._ordersMonthsRange.completed).forEach((key) => { + const val = this._ordersMonthsRange.completed[key]; + this.ordersChartData.linesData[2][indexByKey[key]] = val; + }); + } + + private _setupOrdersChartForYearsRange() { + const years = this._periodService.getYearsByRange(this._dateLabelRange); + const initialLinesData = this._getInitialChartData(years.length); + + this.ordersChartData = { + chartLabel: this._ordersChartService.getDataLabels( + years.length, + years + ), + linesData: initialLinesData, + }; + + const indexByKey = this._generageIndexesByKeys(years); + + Object.keys(this._ordersYearsRange.total).forEach((key) => { + const val = this._ordersYearsRange.total[key]; + this.ordersChartData.linesData[0][indexByKey[key]] = val; + }); + Object.keys(this._ordersYearsRange.cancelled).forEach((key) => { + const val = this._ordersYearsRange.cancelled[key]; + this.ordersChartData.linesData[1][indexByKey[key]] = val; + }); + Object.keys(this._ordersYearsRange.completed).forEach((key) => { + const val = this._ordersYearsRange.completed[key]; + this.ordersChartData.linesData[2][indexByKey[key]] = val; + }); + } + + private _setupProfitChartForToday() { + const hours = this._periodService.getHours(); + const initialLinesData = this._getInitialChartData(hours.length); + + this.profitChartData = { + chartLabel: this._ordersChartService.getDataLabels( + hours.length, + hours + ), + data: initialLinesData, + }; + + Object.keys(this._ordersToday.total).forEach((key) => { + const val = this._ordersToday.total[key]; + const indexKey = this._getIndexKey(key, hours.length - 1); + + this.profitChartData.data[2][indexKey] = val; + }); + Object.keys(this._ordersToday.cancelled).forEach((key) => { + const val = this._ordersToday.cancelled[key]; + const indexKey = this._getIndexKey(key, hours.length - 1); + + this.profitChartData.data[1][indexKey] = val; + }); + Object.keys(this._ordersToday.completed).forEach((key) => { + const val = this._ordersToday.completed[key]; + const indexKey = this._getIndexKey(key, hours.length - 1); + + this.profitChartData.data[0][indexKey] = val; + }); + } + + private _setupProfitChartForLastWeek() { + const weeks = this._periodService.getWeekDays(); + const initialLinesData = this._getInitialChartData(weeks.length); + + this.profitChartData = { + chartLabel: this._ordersChartService.getDataLabels( + weeks.length, + weeks + ), + data: initialLinesData, + }; + + Object.keys(this._ordersLastWeek.total).forEach((key) => { + const val = this._ordersLastWeek.total[key]; + const indexKey = this._getIndexKey(key, weeks.length - 1); + + this.profitChartData.data[2][indexKey] = val; + }); + Object.keys(this._ordersLastWeek.cancelled).forEach((key) => { + const val = this._ordersLastWeek.cancelled[key]; + const indexKey = this._getIndexKey(key, weeks.length - 1); + + this.profitChartData.data[1][indexKey] = val; + }); + Object.keys(this._ordersLastWeek.completed).forEach((key) => { + const val = this._ordersLastWeek.completed[key]; + const indexKey = this._getIndexKey(key, weeks.length - 1); + + this.profitChartData.data[0][indexKey] = val; + }); + } + + private _setupProfitChartForLastMonth() { + const dates = this._periodService.getDatesLastMonth(); + const initialLinesData = this._getInitialChartData(dates.length); + + this.profitChartData = { + chartLabel: this._ordersChartService.getDataLabels( + dates.length, + dates + ), + data: initialLinesData, + }; + + // Because the dates strat from 1 but array indexes start from 0 and we use dates for indexing. + const indexFromDate = (key) => +key - 1; + + Object.keys(this._ordersLastMonth.total).forEach((key) => { + const val = this._ordersLastMonth.total[key]; + const indexKey = indexFromDate(key); + + this.profitChartData.data[2][indexKey] = val; + }); + Object.keys(this._ordersLastMonth.cancelled).forEach((key) => { + const val = this._ordersLastMonth.cancelled[key]; + const indexKey = indexFromDate(key); + + this.profitChartData.data[1][indexKey] = val; + }); + Object.keys(this._ordersLastMonth.completed).forEach((key) => { + const val = this._ordersLastMonth.completed[key]; + const indexKey = indexFromDate(key); + + this.profitChartData.data[0][indexKey] = val; + }); + } + + private _setupProfitChartForCurrentYear() { + const months = this._periodService.getMonths(); + const initialLinesData = this._getInitialChartData(months.length); + + this.profitChartData = { + chartLabel: this._ordersChartService.getDataLabels( + months.length, + months + ), + data: initialLinesData, + }; + + Object.keys(this._ordersCurrentYear.total).forEach((key) => { + const val = this._ordersCurrentYear.total[key]; + this.profitChartData.data[2][key] = val; + }); + Object.keys(this._ordersCurrentYear.cancelled).forEach((key) => { + const val = this._ordersCurrentYear.cancelled[key]; + this.profitChartData.data[1][key] = val; + }); + Object.keys(this._ordersCurrentYear.completed).forEach((key) => { + const val = this._ordersCurrentYear.completed[key]; + this.profitChartData.data[0][key] = val; + }); + } + + private _setupProfitChartForYears() { + const years = this._periodService.getYearLabels(this._yearsLabelRange); + + const initialLinesData = this._getInitialChartData(years.length); + + this.profitChartData = { + chartLabel: this._ordersChartService.getDataLabels( + years.length, + years + ), + data: initialLinesData, + }; + + const indexByKey = this._generageIndexesByKeys(years); + + Object.keys(this._ordersYears.total).forEach((key) => { + const val = this._ordersYears.total[key]; + this.profitChartData.data[2][indexByKey[key]] = val; + }); + Object.keys(this._ordersYears.cancelled).forEach((key) => { + const val = this._ordersYears.cancelled[key]; + this.profitChartData.data[1][indexByKey[key]] = val; + }); + Object.keys(this._ordersYears.completed).forEach((key) => { + const val = this._ordersYears.completed[key]; + this.profitChartData.data[0][indexByKey[key]] = val; + }); + } + + private _setupProfitChartForDaysRange() { + const { keys, labels } = this._periodService.getDatesLabelsKeys( + this._dateLabelRange + ); + + const initialLinesData = this._getInitialChartData(labels.length); + + this.profitChartData = { + chartLabel: this._ordersChartService.getDataLabels( + labels.length, + labels + ), + data: initialLinesData, + }; + + const indexByKey = this._generageIndexesByKeys(keys); + + Object.keys(this._ordersDateRange.total).forEach((key) => { + const val = this._ordersDateRange.total[key]; + this.profitChartData.data[2][indexByKey[key]] = val; + }); + Object.keys(this._ordersDateRange.cancelled).forEach((key) => { + const val = this._ordersDateRange.cancelled[key]; + this.profitChartData.data[1][indexByKey[key]] = val; + }); + Object.keys(this._ordersDateRange.completed).forEach((key) => { + const val = this._ordersDateRange.completed[key]; + this.profitChartData.data[0][indexByKey[key]] = val; + }); + } + + private _setupProfitChartForWeeksRange() { + const { keys, labels } = this._periodService.getWeekLabelsKeys( + this._dateLabelRange, + this._getDateWeekNumber + ); + + const initialLinesData = this._getInitialChartData(labels.length); + + this.profitChartData = { + chartLabel: this._ordersChartService.getDataLabels( + labels.length, + labels + ), + data: initialLinesData, + }; + + const indexByKey = this._generageIndexesByKeys(keys); + + Object.keys(this._ordersWeeksRange.total).forEach((key) => { + const val = this._ordersWeeksRange.total[key]; + this.profitChartData.data[2][indexByKey[key]] = val; + }); + Object.keys(this._ordersWeeksRange.cancelled).forEach((key) => { + const val = this._ordersWeeksRange.cancelled[key]; + this.profitChartData.data[1][indexByKey[key]] = val; + }); + Object.keys(this._ordersWeeksRange.completed).forEach((key) => { + const val = this._ordersWeeksRange.completed[key]; + this.profitChartData.data[0][indexByKey[key]] = val; + }); + } + + private _setupProfitChartForMonthsRange() { + const { keys, labels } = this._periodService.getMonthLabelsKeys( + this._dateLabelRange + ); + + const initialLinesData = this._getInitialChartData(labels.length); + + this.profitChartData = { + chartLabel: this._ordersChartService.getDataLabels( + labels.length, + labels + ), + data: initialLinesData, + }; + + const indexByKey = this._generageIndexesByKeys(keys); + + Object.keys(this._ordersMonthsRange.total).forEach((key) => { + const val = this._ordersMonthsRange.total[key]; + this.profitChartData.data[2][indexByKey[key]] = val; + }); + Object.keys(this._ordersMonthsRange.cancelled).forEach((key) => { + const val = this._ordersMonthsRange.cancelled[key]; + this.profitChartData.data[1][indexByKey[key]] = val; + }); + Object.keys(this._ordersMonthsRange.completed).forEach((key) => { + const val = this._ordersMonthsRange.completed[key]; + this.profitChartData.data[0][indexByKey[key]] = val; + }); + } + + private _setupProfitChartForYearsRange() { + const years = this._periodService.getYearsByRange(this._dateLabelRange); + const initialLinesData = this._getInitialChartData(years.length); + + this.profitChartData = { + chartLabel: this._ordersChartService.getDataLabels( + years.length, + years + ), + data: initialLinesData, + }; + + const indexByKey = this._generageIndexesByKeys(years); + + Object.keys(this._ordersYearsRange.total).forEach((key) => { + const val = this._ordersYearsRange.total[key]; + this.profitChartData.data[2][indexByKey[key]] = val; + }); + Object.keys(this._ordersYearsRange.cancelled).forEach((key) => { + const val = this._ordersYearsRange.cancelled[key]; + this.profitChartData.data[1][indexByKey[key]] = val; + }); + Object.keys(this._ordersYearsRange.completed).forEach((key) => { + const val = this._ordersYearsRange.completed[key]; + this.profitChartData.data[0][indexByKey[key]] = val; + }); + } + + private _isOrderTodayPeriodMatch(order: Order): boolean { + const dateToCompare = new Date(); + const orderDate = new Date(order._createdAt); + + // If we want to show yesterday orders. + // dateToCompare.setDate(dateToCompare.getDate() - 1); + + const dateToCompareDay = dateToCompare.getDate(); + const dateToCompareWeek = this._getDateWeekNumber(dateToCompare); + const dateToCompareMonth = dateToCompare.getMonth(); + const dateToCompareYear = dateToCompare.getFullYear(); + + const orderDay = orderDate.getDate(); + const orderWeek = this._getDateWeekNumber(orderDate); + const orderMonth = orderDate.getMonth(); + const orderYear = orderDate.getFullYear(); + + return ( + orderDay === dateToCompareDay && + orderWeek === dateToCompareWeek && + orderMonth === dateToCompareMonth && + orderYear === dateToCompareYear + ); + } + + private _isOrderLastWeekPeriodMatch(order: Order): boolean { + const dateToCompare = new Date(); + const orderDate = new Date(order._createdAt); + + dateToCompare.setDate(dateToCompare.getDate() - 7); + + const dateToCompareWeek = this._getDateWeekNumber(dateToCompare); + const dateToCompareYear = dateToCompare.getFullYear(); + + const orderWeek = this._getDateWeekNumber(orderDate); + const orderYear = orderDate.getFullYear(); + + // TODO + // See correctness of these conditions + return ( + (orderWeek === dateToCompareWeek && + orderYear === dateToCompareYear) || + ((orderWeek === 1 || orderWeek === 52) && + orderWeek === dateToCompareWeek && + Math.abs(orderYear - dateToCompareYear) === 1) + ); + } + + private _isOrderLastMonthPeriodMatch(order: Order): boolean { + const orderDate = new Date(order._createdAt); + const today = new Date(); + + return ( + orderDate.getFullYear() === today.getFullYear() && + orderDate.getMonth() === today.getMonth() + ); + } + + private _isOrderCurrentYearPeriodMatch(order: Order): boolean { + const dateToCompare = new Date(); + const orderDate = new Date(order._createdAt); + + return orderDate.getFullYear() === dateToCompare.getFullYear(); + } + + private _isOrderCustomDayPeriodMatch(order: Order): boolean { + const dateToCompareDay = this._dateLabelRange.from.getDate(); + const dateToCompareWeek = this._getDateWeekNumber( + this._dateLabelRange.from + ); + const dateToCompareMonth = this._dateLabelRange.from.getMonth(); + const dateToCompareYear = this._dateLabelRange.from.getFullYear(); + + const orderDate = new Date(order._createdAt); + const orderDay = orderDate.getDate(); + const orderWeek = this._getDateWeekNumber(orderDate); + const orderMonth = orderDate.getMonth(); + const orderYear = orderDate.getFullYear(); + + return ( + orderDay === dateToCompareDay && + orderWeek === dateToCompareWeek && + orderMonth === dateToCompareMonth && + orderYear === dateToCompareYear + ); + } + + private _isOrderRangePeriodMatch(order: Order): boolean { + const from = this._dateLabelRange.from; + const to = this._dateLabelRange.to; + const orderDate = new Date(order._createdAt); + + return ( + orderDate.getTime() >= from.getTime() && + orderDate.getTime() <= to.getTime() + ); + } + + private _resetChartData() { + this._ordersToday = { + total: {}, + cancelled: {}, + completed: {}, + }; + this._ordersLastWeek = { + total: {}, + cancelled: {}, + completed: {}, + }; + this._ordersLastMonth = { + total: {}, + cancelled: {}, + completed: {}, + }; + this._ordersCurrentYear = { + total: {}, + cancelled: {}, + completed: {}, + }; + this._ordersYears = { + total: {}, + cancelled: {}, + completed: {}, + }; + this._ordersDateRange = { + total: {}, + cancelled: {}, + completed: {}, + }; + this._ordersWeeksRange = { + total: {}, + cancelled: {}, + completed: {}, + }; + this._ordersMonthsRange = { + total: {}, + cancelled: {}, + completed: {}, + }; + this._ordersYearsRange = { + total: {}, + cancelled: {}, + completed: {}, + }; + } + + private _resetChartPanelSummaryValues() { + this._chartPanelSummaryTotal = 0; + this._chartPanelSummaryCompleted = 0; + this._chartPanelSummaryCancelled = 0; + } + + private _sendRangeIfSelected() { + this.preservedRanges$.next(this._dateLabelRange); + } + + private _clearRangeFromHeader() { + this.clearRange$.next(); + } + + private _translate(key: string) { + let translationResult = ''; + + this._translateService.get(key).subscribe((res) => { + translationResult = res; + }); + + return translationResult; + } + + private _getIndexKey(key: string, maxIndexValue: number) { + let indexKey = +key; + + indexKey = indexKey === 0 ? maxIndexValue : (indexKey -= 1); + + return indexKey; + } + + private _calculateCustomPeriod(daysDiff: number) { + switch (true) { + case daysDiff === 0: + return ChartsPanelComponent._PERIODS.rangeDay; + case daysDiff > 0 && daysDiff <= 27: + return ChartsPanelComponent._PERIODS.rangeDays; + case daysDiff > 27 && daysDiff <= 60: + return ChartsPanelComponent._PERIODS.rangeWeeks; + case daysDiff > 60 && daysDiff <= 365: + return ChartsPanelComponent._PERIODS.rangeMonths; + case daysDiff > 365: + return ChartsPanelComponent._PERIODS.rangeYears; + } + } + + private _getInitialChartData(dataLength: number): number[][] { + const dataRow = Array.from('0'.repeat(dataLength)).map((x) => +x); + + return [ + JSON.parse(JSON.stringify(dataRow)), + JSON.parse(JSON.stringify(dataRow)), + JSON.parse(JSON.stringify(dataRow)), + ]; + } + + private _generageIndexesByKeys(keys: string[]) { + const indexByKey = {}; + keys.forEach((key, index) => (indexByKey[key] = index)); + + return indexByKey; + } + + private _getDateWeekNumber(date) { + const target = new Date(date.valueOf()); + const dayNumber = (date.getUTCDay() + 6) % 7; + let firstThursday; + + target.setUTCDate(target.getUTCDate() - dayNumber + 3); + firstThursday = target.valueOf(); + target.setUTCMonth(0, 1); + + if (target.getUTCDay() !== 4) { + target.setUTCMonth(0, 1 + ((4 - target.getUTCDay() + 7) % 7)); + } + + return ( + Math.ceil( + (firstThursday - (target as any)) / (7 * 24 * 3600 * 1000) + ) + 1 + ); + } + + private _listenLangChange() { + this._translateService.onLangChange + .pipe(takeUntil(this._ngDestroy$)) + .subscribe(() => { + this._refreshChartData(); + this._setChartsSummary(); + }); + } + + private _refreshChartData() { + switch (this.period) { + case ChartsPanelComponent._PERIODS.lastWeek: + this._refreshLastWeekChartData(); + break; + case ChartsPanelComponent._PERIODS.lastMonth: + this._refreshLastMonthChartData(); + break; + case ChartsPanelComponent._PERIODS.currentYear: + this._refreshCurrentYearChartData(); + break; + case ChartsPanelComponent._PERIODS.rangeDays: + this._refreshDaysRangeChartData(); + break; + case ChartsPanelComponent._PERIODS.rangeWeeks: + this._refreshWeeksRangeChartData(); + break; + case ChartsPanelComponent._PERIODS.rangeMonths: + this._refreshMonthsRangeChartData(); + break; + } + } + + private _refreshLastWeekChartData() { + this._isOrderChartSelected + ? this._setupOrdersChartForLastWeek() + : this._setupProfitChartForLastWeek(); + } + + private _refreshLastMonthChartData() { + this._isOrderChartSelected + ? this._setupOrdersChartForLastMonth() + : this._setupProfitChartForLastMonth(); + } + + private _refreshCurrentYearChartData() { + this._isOrderChartSelected + ? this._setupOrdersChartForCurrentYear() + : this._setupProfitChartForCurrentYear(); + } + + private _refreshDaysRangeChartData() { + this._isOrderChartSelected + ? this._setupOrdersChartForDaysRange() + : this._setupProfitChartForDaysRange(); + } + + private _refreshWeeksRangeChartData() { + this._isOrderChartSelected + ? this._setupOrdersChartForWeeksRange() + : this._setupProfitChartForWeeksRange(); + } + + private _refreshMonthsRangeChartData() { + this._isOrderChartSelected + ? this._setupOrdersChartForMonthsRange() + : this._setupProfitChartForMonthsRange(); + } +} diff --git a/packages/admin-web-angular/src/app/pages/+dashboard/charts-panel/charts/charts-common.component.scss b/packages/admin-web-angular/src/app/pages/+dashboard/charts-panel/charts/charts-common.component.scss new file mode 100644 index 0000000..d238d76 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+dashboard/charts-panel/charts/charts-common.component.scss @@ -0,0 +1,13 @@ +@import '../../../../@theme/styles/themes'; + +@include nb-install-component() { + display: block; + height: 100%; + width: 100%; + + .echart { + display: block; + height: 100%; + width: 100%; + } +} diff --git a/packages/admin-web-angular/src/app/pages/+dashboard/charts-panel/charts/orders-chart.component.ts b/packages/admin-web-angular/src/app/pages/+dashboard/charts-panel/charts/orders-chart.component.ts new file mode 100644 index 0000000..901975b --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+dashboard/charts-panel/charts/orders-chart.component.ts @@ -0,0 +1,338 @@ +import { + Component, + Input, + ViewChild, + OnChanges, + AfterViewInit, + OnDestroy, +} from '@angular/core'; +import { NbThemeService } from '@nebular/theme'; +import { OrdersChart } from '@app/@core/services/dashboard/orders-chart.service'; +import { LayoutService } from '@app/@core/services/dashboard/layout.service'; +import { takeWhile, delay } from 'rxjs/operators'; + +@Component({ + selector: 'ea-orders-chart', + styleUrls: ['./charts-common.component.scss'], + template: ` +
+ `, +}) +export class OrdersChartComponent + implements OnChanges, AfterViewInit, OnDestroy { + @Input() + ordersChartData: OrdersChart; + + private alive = true; + + echartsIntance: any; + option: any; + + ngOnChanges(): void { + if (this.option) { + this.updateOrdersChartOptions(this.ordersChartData); + } + } + + constructor( + private readonly _theme: NbThemeService, + private readonly _layoutService: LayoutService + ) { + this._layoutService + .onChangeLayoutSize() + .pipe(takeWhile(() => this.alive)) + .subscribe(() => this.resizeChart()); + } + + ngAfterViewInit(): void { + this._theme + .getJsTheme() + .pipe( + takeWhile(() => this.alive), + delay(1) + ) + .subscribe((config) => { + const eTheme: any = config.variables.orders; + eTheme.axisFontSize = 14; + + this.setOptions(eTheme); + this.updateOrdersChartOptions(this.ordersChartData); + }); + } + + setOptions(eTheme) { + this.option = { + grid: { + left: 40, + top: 20, + right: 0, + bottom: 40, + }, + tooltip: { + trigger: 'item', + axisPointer: { + type: 'line', + lineStyle: { + color: eTheme.tooltipLineColor, + width: eTheme.tooltipLineWidth, + }, + }, + textStyle: { + color: eTheme.tooltipTextColor, + fontSize: eTheme.tooltipFontSize, + fontWeight: eTheme.tooltipFontWeight, + }, + position: 'top', + backgroundColor: eTheme.tooltipBg, + borderColor: eTheme.tooltipBorderColor, + borderWidth: 3, + formatter: (params) => { + return Math.round(parseInt(params.value, 10)); + }, + extraCssText: eTheme.tooltipExtraCss, + }, + xAxis: { + type: 'category', + boundaryGap: false, + offset: 5, + data: [], + axisTick: { + show: false, + }, + axisLabel: { + color: eTheme.axisTextColor, + fontSize: eTheme.axisFontSize, + }, + axisLine: { + lineStyle: { + color: eTheme.axisLineColor, + width: '2', + }, + }, + }, + yAxis: { + type: 'value', + boundaryGap: false, + axisLine: { + lineStyle: { + color: eTheme.axisLineColor, + width: '1', + }, + }, + axisLabel: { + color: eTheme.axisTextColor, + fontSize: eTheme.axisFontSize, + }, + axisTick: { + show: false, + }, + splitLine: { + lineStyle: { + color: eTheme.yAxisSplitLine, + width: '1', + }, + }, + }, + series: [ + this.getFirstLine(eTheme), + this.getSecondLine(eTheme), + this.getThirdLine(eTheme), + ], + }; + } + + getFirstLine(eTheme) { + return { + type: 'line', + smooth: true, + symbolSize: 20, + itemStyle: { + normal: { + opacity: 0, + }, + emphasis: { + opacity: 0, + }, + }, + lineStyle: { + normal: { + width: 0, + }, + }, + areaStyle: { + normal: { + color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ + { + offset: 0, + color: eTheme.firstAreaGradFrom, + }, + { + offset: 1, + color: eTheme.firstAreaGradTo, + }, + ]), + opacity: 1, + }, + }, + data: [], + }; + } + + getSecondLine(eTheme) { + return { + type: 'line', + smooth: true, + symbolSize: 20, + itemStyle: { + normal: { + opacity: 0, + }, + emphasis: { + color: '#ffffff', + borderColor: eTheme.itemBorderColor, + borderWidth: 2, + opacity: 1, + }, + }, + lineStyle: { + normal: { + width: eTheme.lineWidth, + type: eTheme.lineStyle, + color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ + { + offset: 0, + color: eTheme.secondLineGradFrom, + }, + { + offset: 1, + color: eTheme.secondLineGradTo, + }, + ]), + }, + }, + areaStyle: { + normal: { + color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ + { + offset: 0, + color: eTheme.secondAreaGradFrom, + }, + { + offset: 1, + color: eTheme.secondAreaGradTo, + }, + ]), + }, + }, + data: [], + }; + } + + getThirdLine(eTheme) { + return { + type: 'line', + smooth: true, + symbolSize: 20, + itemStyle: { + normal: { + opacity: 0, + }, + emphasis: { + color: '#ffffff', + borderColor: eTheme.itemBorderColor, + borderWidth: 2, + opacity: 1, + }, + }, + lineStyle: { + normal: { + width: eTheme.lineWidth, + type: eTheme.lineStyle, + color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ + { + offset: 0, + color: eTheme.thirdLineGradFrom, + }, + { + offset: 1, + color: eTheme.thirdLineGradTo, + }, + ]), + }, + }, + areaStyle: { + normal: { + color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ + { + offset: 0, + color: eTheme.thirdAreaGradFrom, + }, + { + offset: 1, + color: eTheme.thirdAreaGradTo, + }, + ]), + }, + }, + data: [], + }; + } + + updateOrdersChartOptions(ordersChartData: OrdersChart) { + const options = this.option; + + const series = this.getNewSeries( + options.series, + ordersChartData.linesData + ); + const xAxis = this.getNewXAxis( + options.xAxis, + ordersChartData.chartLabel + ); + + this.option = { + ...options, + xAxis, + series, + }; + } + + getNewSeries(series, linesData: number[][]) { + return series.map((line, index) => { + return { + ...line, + data: linesData[index], + }; + }); + } + + getNewXAxis(xAxis, chartLabel: string[]) { + return { + ...xAxis, + data: chartLabel, + }; + } + + onChartInit(echarts) { + this.echartsIntance = echarts; + } + + resizeChart() { + if (this.echartsIntance) { + // Fix recalculation chart size + // TODO: investigate more deeply + setTimeout(() => { + this.echartsIntance.resize(); + }, 0); + } + } + + ngOnDestroy() { + this.alive = false; + } +} diff --git a/packages/admin-web-angular/src/app/pages/+dashboard/charts-panel/charts/orders-chart/orders-chart.component.html b/packages/admin-web-angular/src/app/pages/+dashboard/charts-panel/charts/orders-chart/orders-chart.component.html new file mode 100644 index 0000000..a73682a --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+dashboard/charts-panel/charts/orders-chart/orders-chart.component.html @@ -0,0 +1,6 @@ +
diff --git a/packages/admin-web-angular/src/app/pages/+dashboard/charts-panel/charts/orders-chart/orders-chart.component.scss b/packages/admin-web-angular/src/app/pages/+dashboard/charts-panel/charts/orders-chart/orders-chart.component.scss new file mode 100644 index 0000000..b013fef --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+dashboard/charts-panel/charts/orders-chart/orders-chart.component.scss @@ -0,0 +1,13 @@ +@import '../../../../../@theme/styles/themes'; + +@include nb-install-component() { + display: block; + height: 100%; + width: 100%; + + .echart { + display: block; + height: 100%; + width: 100%; + } +} diff --git a/packages/admin-web-angular/src/app/pages/+dashboard/charts-panel/charts/orders-chart/orders-chart.component.ts b/packages/admin-web-angular/src/app/pages/+dashboard/charts-panel/charts/orders-chart/orders-chart.component.ts new file mode 100644 index 0000000..ca7f3b5 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+dashboard/charts-panel/charts/orders-chart/orders-chart.component.ts @@ -0,0 +1,331 @@ +import { + Component, + Input, + ViewChild, + OnChanges, + AfterViewInit, + OnDestroy, +} from '@angular/core'; +import { NbThemeService } from '@nebular/theme'; +import { OrdersChart } from '@app/@core/services/dashboard/orders-chart.service'; +import { LayoutService } from '@app/@core/services/dashboard/layout.service'; +import { takeWhile, delay } from 'rxjs/operators'; + +@Component({ + selector: 'ea-orders-chart', + styleUrls: ['./orders-chart.component.scss'], + templateUrl: './orders-chart.component.html', +}) +export class OrdersChartComponent + implements OnChanges, AfterViewInit, OnDestroy { + @Input() + ordersChartData: OrdersChart; + + private alive = true; + + echartsIntance: any; + option: any; + + ngOnChanges(): void { + if (this.option) { + this.updateOrdersChartOptions(this.ordersChartData); + } + } + + constructor( + private readonly _theme: NbThemeService, + private readonly _layoutService: LayoutService + ) { + this._layoutService + .onChangeLayoutSize() + .pipe(takeWhile(() => this.alive)) + .subscribe(() => this.resizeChart()); + } + + ngAfterViewInit(): void { + this._theme + .getJsTheme() + .pipe( + takeWhile(() => this.alive), + delay(1) + ) + .subscribe((config) => { + const eTheme: any = config.variables.orders; + eTheme.axisFontSize = 14; + + this.setOptions(eTheme); + this.updateOrdersChartOptions(this.ordersChartData); + }); + } + + setOptions(eTheme) { + this.option = { + grid: { + left: 40, + top: 20, + right: 0, + bottom: 40, + }, + tooltip: { + trigger: 'item', + axisPointer: { + type: 'line', + lineStyle: { + color: eTheme.tooltipLineColor, + width: eTheme.tooltipLineWidth, + }, + }, + textStyle: { + color: eTheme.tooltipTextColor, + fontSize: eTheme.tooltipFontSize, + fontWeight: eTheme.tooltipFontWeight, + }, + position: 'top', + backgroundColor: eTheme.tooltipBg, + borderColor: eTheme.tooltipBorderColor, + borderWidth: 3, + formatter: (params) => { + return Math.round(parseInt(params.value, 10)); + }, + extraCssText: eTheme.tooltipExtraCss, + }, + xAxis: { + type: 'category', + boundaryGap: false, + offset: 5, + data: [], + axisTick: { + show: false, + }, + axisLabel: { + color: eTheme.axisTextColor, + fontSize: eTheme.axisFontSize, + }, + axisLine: { + lineStyle: { + color: eTheme.axisLineColor, + width: '2', + }, + }, + }, + yAxis: { + type: 'value', + boundaryGap: false, + axisLine: { + lineStyle: { + color: eTheme.axisLineColor, + width: '1', + }, + }, + axisLabel: { + color: eTheme.axisTextColor, + fontSize: eTheme.axisFontSize, + }, + axisTick: { + show: false, + }, + splitLine: { + lineStyle: { + color: eTheme.yAxisSplitLine, + width: '1', + }, + }, + }, + series: [ + this.getFirstLine(eTheme), + this.getSecondLine(eTheme), + this.getThirdLine(eTheme), + ], + }; + } + + getFirstLine(eTheme) { + return { + type: 'line', + smooth: true, + symbolSize: 20, + itemStyle: { + normal: { + opacity: 0, + }, + emphasis: { + opacity: 0, + }, + }, + lineStyle: { + normal: { + width: 0, + }, + }, + areaStyle: { + normal: { + color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ + { + offset: 0, + color: eTheme.firstAreaGradFrom, + }, + { + offset: 1, + color: eTheme.firstAreaGradTo, + }, + ]), + opacity: 1, + }, + }, + data: [], + }; + } + + getSecondLine(eTheme) { + return { + type: 'line', + smooth: true, + symbolSize: 20, + itemStyle: { + normal: { + opacity: 0, + }, + emphasis: { + color: '#ffffff', + borderColor: eTheme.itemBorderColor, + borderWidth: 2, + opacity: 1, + }, + }, + lineStyle: { + normal: { + width: eTheme.lineWidth, + type: eTheme.lineStyle, + color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ + { + offset: 0, + color: eTheme.secondLineGradFrom, + }, + { + offset: 1, + color: eTheme.secondLineGradTo, + }, + ]), + }, + }, + areaStyle: { + normal: { + color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ + { + offset: 0, + color: eTheme.secondAreaGradFrom, + }, + { + offset: 1, + color: eTheme.secondAreaGradTo, + }, + ]), + }, + }, + data: [], + }; + } + + getThirdLine(eTheme) { + return { + type: 'line', + smooth: true, + symbolSize: 20, + itemStyle: { + normal: { + opacity: 0, + }, + emphasis: { + color: '#ffffff', + borderColor: eTheme.itemBorderColor, + borderWidth: 2, + opacity: 1, + }, + }, + lineStyle: { + normal: { + width: eTheme.lineWidth, + type: eTheme.lineStyle, + color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ + { + offset: 0, + color: eTheme.thirdLineGradFrom, + }, + { + offset: 1, + color: eTheme.thirdLineGradTo, + }, + ]), + }, + }, + areaStyle: { + normal: { + color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ + { + offset: 0, + color: eTheme.thirdAreaGradFrom, + }, + { + offset: 1, + color: eTheme.thirdAreaGradTo, + }, + ]), + }, + }, + data: [], + }; + } + + updateOrdersChartOptions(ordersChartData: OrdersChart) { + const options = this.option; + + const series = this.getNewSeries( + options.series, + ordersChartData.linesData + ); + const xAxis = this.getNewXAxis( + options.xAxis, + ordersChartData.chartLabel + ); + + this.option = { + ...options, + xAxis, + series, + }; + } + + getNewSeries(series, linesData: number[][]) { + return series.map((line, index) => { + return { + ...line, + data: linesData[index], + }; + }); + } + + getNewXAxis(xAxis, chartLabel: string[]) { + return { + ...xAxis, + data: chartLabel, + }; + } + + onChartInit(echarts) { + this.echartsIntance = echarts; + } + + resizeChart() { + if (this.echartsIntance) { + // Fix recalculation chart size + // TODO: investigate more deeply + setTimeout(() => { + this.echartsIntance.resize(); + }, 0); + } + } + + ngOnDestroy() { + this.alive = false; + } +} diff --git a/packages/admin-web-angular/src/app/pages/+dashboard/charts-panel/charts/profit-chart.component.ts b/packages/admin-web-angular/src/app/pages/+dashboard/charts-panel/charts/profit-chart.component.ts new file mode 100644 index 0000000..d16bdf5 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+dashboard/charts-panel/charts/profit-chart.component.ts @@ -0,0 +1,240 @@ +import { + Component, + Input, + OnChanges, + AfterViewInit, + OnDestroy, +} from '@angular/core'; +import { NbThemeService } from '@nebular/theme'; +import { takeWhile } from 'rxjs/operators'; +import { ProfitChart } from '@app/@core/services/dashboard/profit-chart.service'; +import { LayoutService } from '@app/@core/services/dashboard/layout.service'; + +@Component({ + selector: 'ea-profit-chart', + styleUrls: ['./charts-common.component.scss'], + template: ` +
+ `, +}) +export class ProfitChartComponent + implements OnChanges, AfterViewInit, OnDestroy { + @Input() + profitChartData: ProfitChart; + + private alive = true; + + echartsIntance: any; + options: any = {}; + + constructor( + private theme: NbThemeService, + private layoutService: LayoutService + ) { + this.layoutService + .onChangeLayoutSize() + .pipe(takeWhile(() => this.alive)) + .subscribe(() => this.resizeChart()); + } + + ngOnChanges(): void { + if (this.echartsIntance) { + this.updateProfitChartOptions(this.profitChartData); + } + } + + ngAfterViewInit() { + this.theme + .getJsTheme() + .pipe(takeWhile(() => this.alive)) + .subscribe((config) => { + const eTheme: any = config.variables.profit; + eTheme.axisFontSize = 14; + + this.setOptions(eTheme); + }); + } + + setOptions(eTheme) { + this.options = { + backgroundColor: eTheme.bg, + tooltip: { + trigger: 'axis', + axisPointer: { + type: 'shadow', + shadowStyle: { + color: 'rgba(0, 0, 0, 0.3)', + }, + }, + }, + grid: { + left: '3%', + right: '4%', + bottom: '3%', + containLabel: true, + }, + xAxis: [ + { + type: 'category', + data: this.profitChartData.chartLabel, + axisTick: { + alignWithLabel: true, + }, + axisLine: { + lineStyle: { + color: eTheme.axisLineColor, + }, + }, + axisLabel: { + color: eTheme.axisTextColor, + fontSize: eTheme.axisFontSize, + }, + }, + ], + yAxis: [ + { + type: 'value', + axisLine: { + lineStyle: { + color: eTheme.axisLineColor, + }, + }, + splitLine: { + lineStyle: { + color: eTheme.splitLineColor, + }, + }, + axisLabel: { + color: eTheme.axisTextColor, + fontSize: eTheme.axisFontSize, + }, + }, + ], + series: [ + { + name: 'Completed', + type: 'bar', + barGap: 0, + barWidth: '20%', + itemStyle: { + normal: { + color: new echarts.graphic.LinearGradient( + 0, + 0, + 0, + 1, + [ + { + offset: 0, + color: eTheme.firstLineGradFrom, + }, + { + offset: 1, + color: eTheme.firstLineGradTo, + }, + ] + ), + }, + }, + data: this.profitChartData.data[0], + }, + { + name: 'Canceled', + type: 'bar', + barWidth: '20%', + itemStyle: { + normal: { + color: new echarts.graphic.LinearGradient( + 0, + 0, + 0, + 1, + [ + { + offset: 0, + color: eTheme.secondLineGradFrom, + }, + { + offset: 1, + color: eTheme.secondLineGradTo, + }, + ] + ), + }, + }, + data: this.profitChartData.data[1], + }, + { + name: 'Total orders', + type: 'bar', + barWidth: '20%', + itemStyle: { + normal: { + color: new echarts.graphic.LinearGradient( + 0, + 0, + 0, + 1, + [ + { + offset: 0, + color: eTheme.thirdLineGradFrom, + }, + { + offset: 1, + color: eTheme.thirdLineGradTo, + }, + ] + ), + }, + }, + data: this.profitChartData.data[2], + }, + ], + }; + } + + updateProfitChartOptions(profitChartData: ProfitChart) { + const options = this.options; + const series = this.getNewSeries(options.series, profitChartData.data); + + this.echartsIntance.setOption({ + series, + xAxis: { + data: this.profitChartData.chartLabel, + }, + }); + } + + getNewSeries(series, data: number[][]) { + return series.map((line, index) => { + return { + ...line, + data: data[index], + }; + }); + } + + onChartInit(echarts) { + this.echartsIntance = echarts; + } + + resizeChart() { + if (this.echartsIntance) { + // Fix recalculation chart size + // TODO: investigate more deeply + setTimeout(() => { + this.echartsIntance.resize(); + }, 0); + } + } + + ngOnDestroy(): void { + this.alive = false; + } +} diff --git a/packages/admin-web-angular/src/app/pages/+dashboard/charts-panel/charts/profit-chart/profit-chart.component.html b/packages/admin-web-angular/src/app/pages/+dashboard/charts-panel/charts/profit-chart/profit-chart.component.html new file mode 100644 index 0000000..d8e9883 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+dashboard/charts-panel/charts/profit-chart/profit-chart.component.html @@ -0,0 +1,6 @@ +
diff --git a/packages/admin-web-angular/src/app/pages/+dashboard/charts-panel/charts/profit-chart/profit-chart.component.scss b/packages/admin-web-angular/src/app/pages/+dashboard/charts-panel/charts/profit-chart/profit-chart.component.scss new file mode 100644 index 0000000..b013fef --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+dashboard/charts-panel/charts/profit-chart/profit-chart.component.scss @@ -0,0 +1,13 @@ +@import '../../../../../@theme/styles/themes'; + +@include nb-install-component() { + display: block; + height: 100%; + width: 100%; + + .echart { + display: block; + height: 100%; + width: 100%; + } +} diff --git a/packages/admin-web-angular/src/app/pages/+dashboard/charts-panel/charts/profit-chart/profit-chart.component.ts b/packages/admin-web-angular/src/app/pages/+dashboard/charts-panel/charts/profit-chart/profit-chart.component.ts new file mode 100644 index 0000000..b14ee0a --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+dashboard/charts-panel/charts/profit-chart/profit-chart.component.ts @@ -0,0 +1,233 @@ +import { + Component, + Input, + OnChanges, + AfterViewInit, + OnDestroy, +} from '@angular/core'; +import { NbThemeService } from '@nebular/theme'; +import { takeWhile } from 'rxjs/operators'; +import { ProfitChart } from '@app/@core/services/dashboard/profit-chart.service'; +import { LayoutService } from '@app/@core/services/dashboard/layout.service'; + +@Component({ + selector: 'ea-profit-chart', + styleUrls: ['./profit-chart.component.scss'], + templateUrl: './profit-chart.component.html', +}) +export class ProfitChartComponent + implements OnChanges, AfterViewInit, OnDestroy { + @Input() + profitChartData: ProfitChart; + + private alive = true; + + echartsIntance: any; + options: any = {}; + + constructor( + private theme: NbThemeService, + private layoutService: LayoutService + ) { + this.layoutService + .onChangeLayoutSize() + .pipe(takeWhile(() => this.alive)) + .subscribe(() => this.resizeChart()); + } + + ngOnChanges(): void { + if (this.echartsIntance) { + this.updateProfitChartOptions(this.profitChartData); + } + } + + ngAfterViewInit() { + this.theme + .getJsTheme() + .pipe(takeWhile(() => this.alive)) + .subscribe((config) => { + const eTheme: any = config.variables.profit; + eTheme.axisFontSize = 14; + + this.setOptions(eTheme); + }); + } + + setOptions(eTheme) { + this.options = { + backgroundColor: eTheme.bg, + tooltip: { + trigger: 'axis', + axisPointer: { + type: 'shadow', + shadowStyle: { + color: 'rgba(0, 0, 0, 0.3)', + }, + }, + }, + grid: { + left: '3%', + right: '4%', + bottom: '3%', + containLabel: true, + }, + xAxis: [ + { + type: 'category', + data: this.profitChartData.chartLabel, + axisTick: { + alignWithLabel: true, + }, + axisLine: { + lineStyle: { + color: eTheme.axisLineColor, + }, + }, + axisLabel: { + color: eTheme.axisTextColor, + fontSize: eTheme.axisFontSize, + }, + }, + ], + yAxis: [ + { + type: 'value', + axisLine: { + lineStyle: { + color: eTheme.axisLineColor, + }, + }, + splitLine: { + lineStyle: { + color: eTheme.splitLineColor, + }, + }, + axisLabel: { + color: eTheme.axisTextColor, + fontSize: eTheme.axisFontSize, + }, + }, + ], + series: [ + { + name: 'Completed', + type: 'bar', + barGap: 0, + barWidth: '20%', + itemStyle: { + normal: { + color: new echarts.graphic.LinearGradient( + 0, + 0, + 0, + 1, + [ + { + offset: 0, + color: eTheme.firstLineGradFrom, + }, + { + offset: 1, + color: eTheme.firstLineGradTo, + }, + ] + ), + }, + }, + data: this.profitChartData.data[0], + }, + { + name: 'Canceled', + type: 'bar', + barWidth: '20%', + itemStyle: { + normal: { + color: new echarts.graphic.LinearGradient( + 0, + 0, + 0, + 1, + [ + { + offset: 0, + color: eTheme.secondLineGradFrom, + }, + { + offset: 1, + color: eTheme.secondLineGradTo, + }, + ] + ), + }, + }, + data: this.profitChartData.data[1], + }, + { + name: 'Total orders', + type: 'bar', + barWidth: '20%', + itemStyle: { + normal: { + color: new echarts.graphic.LinearGradient( + 0, + 0, + 0, + 1, + [ + { + offset: 0, + color: eTheme.thirdLineGradFrom, + }, + { + offset: 1, + color: eTheme.thirdLineGradTo, + }, + ] + ), + }, + }, + data: this.profitChartData.data[2], + }, + ], + }; + } + + updateProfitChartOptions(profitChartData: ProfitChart) { + const options = this.options; + const series = this.getNewSeries(options.series, profitChartData.data); + + this.echartsIntance.setOption({ + series, + xAxis: { + data: this.profitChartData.chartLabel, + }, + }); + } + + getNewSeries(series, data: number[][]) { + return series.map((line, index) => { + return { + ...line, + data: data[index], + }; + }); + } + + onChartInit(echarts) { + this.echartsIntance = echarts; + } + + resizeChart() { + if (this.echartsIntance) { + // Fix recalculation chart size + // TODO: investigate more deeply + setTimeout(() => { + this.echartsIntance.resize(); + }, 0); + } + } + + ngOnDestroy(): void { + this.alive = false; + } +} diff --git a/packages/admin-web-angular/src/app/pages/+dashboard/charts-panel/legend-chart/enum.legend-item-color.ts b/packages/admin-web-angular/src/app/pages/+dashboard/charts-panel/legend-chart/enum.legend-item-color.ts new file mode 100644 index 0000000..bcb83b0 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+dashboard/charts-panel/legend-chart/enum.legend-item-color.ts @@ -0,0 +1,7 @@ +export enum NgxLegendItemColor { + GREEN = 'green', + PURPLE = 'purple', + LIGHT_PURPLE = 'light-purple', + BLUE = 'blue', + YELLOW = 'yellow', +} diff --git a/packages/admin-web-angular/src/app/pages/+dashboard/charts-panel/legend-chart/legend-chart.component.html b/packages/admin-web-angular/src/app/pages/+dashboard/charts-panel/legend-chart/legend-chart.component.html new file mode 100644 index 0000000..59ba383 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+dashboard/charts-panel/legend-chart/legend-chart.component.html @@ -0,0 +1,9 @@ +
+
+
+
{{ legend.title }}
+
+
diff --git a/packages/admin-web-angular/src/app/pages/+dashboard/charts-panel/legend-chart/legend-chart.component.scss b/packages/admin-web-angular/src/app/pages/+dashboard/charts-panel/legend-chart/legend-chart.component.scss new file mode 100644 index 0000000..e002976 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+dashboard/charts-panel/legend-chart/legend-chart.component.scss @@ -0,0 +1,40 @@ +@import '../../../../@theme/styles/themes'; +@import '~bootstrap/scss/mixins/breakpoints'; +@import '~@nebular/theme/styles/global/breakpoints'; + +@include nb-install-component() { + .legends { + display: flex; + @include nb-rtl(flex-direction, row-reverse); + color: #a4abb3; + padding: 0 0 0 2.85rem; + } + + .legend { + display: flex; + justify-content: space-between; + align-items: baseline; + margin-left: 4rem; + + &:first-child { + margin-left: 0; + } + } + + .legend-item-color { + min-width: 15px; + min-height: 15px; + border-radius: 0.2rem; + } + + .legend-title { + padding: 0 0.75rem; + white-space: nowrap; + } + + @include media-breakpoint-down(md) { + .legend { + margin-left: 1.5rem; + } + } +} diff --git a/packages/admin-web-angular/src/app/pages/+dashboard/charts-panel/legend-chart/legend-chart.component.ts b/packages/admin-web-angular/src/app/pages/+dashboard/charts-panel/legend-chart/legend-chart.component.ts new file mode 100644 index 0000000..1588ba9 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+dashboard/charts-panel/legend-chart/legend-chart.component.ts @@ -0,0 +1,18 @@ +import { Component, Input } from '@angular/core'; + +import { NgxLegendItemColor } from './enum.legend-item-color'; + +@Component({ + selector: 'ea-legend-chart', + styleUrls: ['./legend-chart.component.scss'], + templateUrl: './legend-chart.component.html', +}) +export class LegendChartComponent { + /** + * Take an array of legend items + * Available iconColor: 'green', 'purple', 'light-purple', 'blue', 'yellow' + * @type {{iconColor: string; title: string}[]} + */ + @Input() + legendItems: Array<{ iconColor: NgxLegendItemColor; title: string }> = []; +} diff --git a/packages/admin-web-angular/src/app/pages/+dashboard/dashboard-select-store/dashboard-select-store.component.html b/packages/admin-web-angular/src/app/pages/+dashboard/dashboard-select-store/dashboard-select-store.component.html new file mode 100644 index 0000000..38fd71f --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+dashboard/dashboard-select-store/dashboard-select-store.component.html @@ -0,0 +1,87 @@ + + +
{{ 'DASHBOARD_VIEW.SELECT_COMPONENT.STORES' | translate }}
+
+ + + + + + {{ item.name }} + + +

+ + + {{ item.name }} +

+
+
+ +
+
+
+ {{ + 'DASHBOARD_VIEW.SELECT_COMPONENT.CONTACT_DETAILS' + | translate + }} +
+
    +
  • + {{ + 'DASHBOARD_VIEW.SELECT_COMPONENT.PHONE' | translate + }}: {{ selectedStore.contactPhone }} +
  • +
  • + {{ + 'DASHBOARD_VIEW.SELECT_COMPONENT.EMAIL' | translate + }}: {{ selectedStore.contactEmail }} +
  • +
  • + {{ + 'DASHBOARD_VIEW.SELECT_COMPONENT.ORDERS_FORWARDING_WITH' + | translate + }} + + + {{ + 'DASHBOARD_VIEW.SELECT_COMPONENT.PHONE' + | translate + }} + + + {{ + 'DASHBOARD_VIEW.SELECT_COMPONENT.EMAIL' + | translate + }} + +
  • +
+
+ +
+ +
+
+
+
diff --git a/packages/admin-web-angular/src/app/pages/+dashboard/dashboard-select-store/dashboard-select-store.component.scss b/packages/admin-web-angular/src/app/pages/+dashboard/dashboard-select-store/dashboard-select-store.component.scss new file mode 100644 index 0000000..f70513a --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+dashboard/dashboard-select-store/dashboard-select-store.component.scss @@ -0,0 +1,5 @@ +nb-card#dashboard-select-stores { + nb-card-body { + overflow: inherit !important; + } +} diff --git a/packages/admin-web-angular/src/app/pages/+dashboard/dashboard-select-store/dashboard-select-store.component.ts b/packages/admin-web-angular/src/app/pages/+dashboard/dashboard-select-store/dashboard-select-store.component.ts new file mode 100644 index 0000000..a07b4f6 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+dashboard/dashboard-select-store/dashboard-select-store.component.ts @@ -0,0 +1,31 @@ +import { Component, Input, EventEmitter, Output } from '@angular/core'; +import Warehouse from '@modules/server.common/entities/Warehouse'; + +@Component({ + selector: 'ea-dashboard-select-store', + templateUrl: './dashboard-select-store.component.html', + styleUrls: ['./dashboard-select-store.component.scss'], +}) +export class DashboardSelectStoreComponent { + @Input() + stores: Warehouse[]; + + @Output() + selectedStoreEmitter = new EventEmitter(); + + selectedStore: Warehouse; + + constructor() {} + + selectNewStore(ev) { + let storeId; + if (ev) { + storeId = ev.id; + this.selectedStore = this.stores.find((s) => s.id === storeId); + } else { + this.selectedStore = null; + } + + this.selectedStoreEmitter.emit(storeId); + } +} diff --git a/packages/admin-web-angular/src/app/pages/+dashboard/dashboard-select-store/dashboard-select-store.stories.ts b/packages/admin-web-angular/src/app/pages/+dashboard/dashboard-select-store/dashboard-select-store.stories.ts new file mode 100644 index 0000000..3b9a744 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+dashboard/dashboard-select-store/dashboard-select-store.stories.ts @@ -0,0 +1,624 @@ +import { storiesOf, moduleMetadata } from '@storybook/angular'; +import { text, withKnobs, object } from '@storybook/addon-knobs'; +import { action } from '@storybook/addon-actions'; +import { ThemeModule } from '@app/@theme'; +import { CommonModule } from '@angular/common'; +import { Ng2SmartTableModule } from 'ng2-smart-table'; +import { NbSpinnerModule } from '@nebular/theme'; +import { ConfirmationModalModule } from '@app/@shared/confirmation-modal/confirmation-modal.module'; +import { ToasterModule } from 'angular2-toaster'; +import { TranslateStore, TranslateService } from '@ngx-translate/core'; +import { RouterModule } from '@angular/router'; +import { routes, NbAuthModule } from '@nebular/auth'; +import { NotifyService } from '@app/@core/services/notify/notify.service'; +import { APOLLO_OPTIONS } from 'apollo-angular'; +import { HttpLink } from 'apollo-angular/http'; +import { InMemoryCache } from '@apollo/client/core'; +import { PipesModule } from '@modules/client.common.angular2/pipes/pipes.module'; +import { HttpClient, HttpClientModule } from '@angular/common/http'; +import { TranslateHttpLoader } from '@ngx-translate/http-loader'; +import { DashboardSelectStoreComponent } from './dashboard-select-store.component'; +import { NgSelectModule } from '@ng-select/ng-select'; +import { I18nModule } from '@app/@core/utils/i18n.module'; + +const stories = storiesOf('Dashboard', module); + +export function createApollo(httpLink: HttpLink) { + return { + link: httpLink.create({ uri: 'https://api.example.com/graphql' }), + cache: new InMemoryCache(), + }; +} + +export function createTranslateLoader(http: HttpClient) { + return new TranslateHttpLoader(http, '/i18n/', '.json'); +} + +stories.addDecorator(withKnobs); + +stories.addDecorator( + moduleMetadata({ + declarations: [DashboardSelectStoreComponent], + imports: [ + NgSelectModule, + CommonModule, + ThemeModule, + Ng2SmartTableModule, + NbSpinnerModule, + ConfirmationModalModule, + ToasterModule.forRoot(), + HttpClientModule, + I18nModule, + RouterModule.forChild(routes), + NbAuthModule, + PipesModule, + ], + providers: [ + { + provide: APOLLO_OPTIONS, + useFactory: createApollo, + deps: [HttpLink], + }, + TranslateStore, + TranslateService, + NotifyService, + HttpLink, + ], + }) +); + +stories.add('Dashboard Select Store', () => ({ + component: DashboardSelectStoreComponent, + props: { + stores: object('Stores', stores), + text: text('Hello', ''), + selectedStoreEmitter: action('Selected Data'), + }, +})); + +const stores = [ + { + id: '5cb1494208d4970a8444d97d', + _createdAt: '2018-06-28T08:05:00.000Z', + name: 'Pizza Dan', + contactEmail: 'Daija_Bernier8@gmail.com', + contactPhone: '(667) 017-1190 x99049', + logo: + 'https://fakeimg.pl/200x200/FFD890%2C128/000/?text=Pizza Dan&font_size=75', + username: 'restaurant_pizza', + usedCarriersIds: [ + '5cb1492408d4970a8444d8b5', + '5cb1492408d4970a8444d8b7', + '5cb1492408d4970a8444d8b9', + '5cb1492508d4970a8444d8bb', + '5cb1492508d4970a8444d8bd', + '5cb1492508d4970a8444d8bf', + '5cb1492508d4970a8444d8c1', + '5cb1492608d4970a8444d8c3', + '5cb1492608d4970a8444d8c5', + '5cb1492608d4970a8444d8c7', + '5cb1492708d4970a8444d8c9', + '5cb1492708d4970a8444d8cb', + '5cb1492708d4970a8444d8cd', + '5cb1492708d4970a8444d8cf', + '5cb1492808d4970a8444d8d1', + '5cb1492808d4970a8444d8d3', + '5cb1492808d4970a8444d8d5', + '5cb1492808d4970a8444d8d7', + '5cb1492908d4970a8444d8d9', + '5cb1492908d4970a8444d8db', + '5cb1492908d4970a8444d8dd', + '5cb1492a08d4970a8444d8df', + '5cb1492a08d4970a8444d8e1', + '5cb1492a08d4970a8444d8e3', + '5cb1492a08d4970a8444d8e5', + '5cb1492b08d4970a8444d8e7', + '5cb1492b08d4970a8444d8e9', + '5cb1492b08d4970a8444d8eb', + '5cb1492b08d4970a8444d8ed', + '5cb1492c08d4970a8444d8ef', + '5cb1492c08d4970a8444d8f1', + '5cb1492c08d4970a8444d8f3', + '5cb1492d08d4970a8444d8f5', + '5cb1492d08d4970a8444d8f7', + '5cb1492d08d4970a8444d8f9', + '5cb1492d08d4970a8444d8fb', + '5cb1492e08d4970a8444d8fd', + '5cb1492e08d4970a8444d8ff', + '5cb1492e08d4970a8444d901', + '5cb1492f08d4970a8444d903', + '5cb1492f08d4970a8444d905', + '5cb1492f08d4970a8444d907', + '5cb1492f08d4970a8444d909', + '5cb1493008d4970a8444d90b', + '5cb1493008d4970a8444d90d', + '5cb1493008d4970a8444d90f', + '5cb1493008d4970a8444d911', + '5cb1493108d4970a8444d913', + '5cb1493108d4970a8444d915', + '5cb1493108d4970a8444d917', + '5cb1493208d4970a8444d919', + '5cb1493208d4970a8444d91b', + '5cb1493208d4970a8444d91d', + '5cb1493208d4970a8444d91f', + '5cb1493308d4970a8444d921', + '5cb1493308d4970a8444d923', + '5cb1493308d4970a8444d925', + '5cb1493308d4970a8444d927', + '5cb1493408d4970a8444d929', + '5cb1493408d4970a8444d92b', + '5cb1493408d4970a8444d92d', + '5cb1493508d4970a8444d92f', + '5cb1493508d4970a8444d931', + '5cb1493508d4970a8444d933', + '5cb1493508d4970a8444d935', + '5cb1493608d4970a8444d937', + '5cb1493608d4970a8444d939', + '5cb1493608d4970a8444d93b', + '5cb1493708d4970a8444d93d', + '5cb1493708d4970a8444d93f', + '5cb1493708d4970a8444d941', + '5cb1493708d4970a8444d943', + '5cb1493808d4970a8444d945', + '5cb1493808d4970a8444d947', + '5cb1493808d4970a8444d949', + '5cb1493808d4970a8444d94b', + '5cb1493908d4970a8444d94d', + '5cb1493908d4970a8444d94f', + '5cb1493908d4970a8444d951', + '5cb1493a08d4970a8444d953', + '5cb1493a08d4970a8444d955', + '5cb1493a08d4970a8444d957', + '5cb1493a08d4970a8444d959', + '5cb1493b08d4970a8444d95b', + '5cb1493b08d4970a8444d95d', + '5cb1493b08d4970a8444d95f', + '5cb1493c08d4970a8444d961', + '5cb1493c08d4970a8444d963', + '5cb1493c08d4970a8444d965', + '5cb1493c08d4970a8444d967', + '5cb1493d08d4970a8444d969', + '5cb1493d08d4970a8444d96b', + '5cb1493d08d4970a8444d96d', + '5cb1493e08d4970a8444d96f', + '5cb1493f08d4970a8444d971', + '5cb1493f08d4970a8444d973', + '5cb1494008d4970a8444d975', + '5cb1494108d4970a8444d977', + '5cb1494108d4970a8444d979', + '5cb1494208d4970a8444d97b', + ], + carriersIds: [], + geoLocation: { + city: 'Ashdod', + streetAddress: 'HaAtsmaut', + house: '125', + __typename: 'GeoLocation', + }, + __typename: 'Warehouse', + barcodeData: '5cb1494208d4970a8444d97d', + }, + { + id: '5cb1494408d4970a8444d983', + _createdAt: '2018-05-13T19:26:00.000Z', + name: 'Dominex Pizza', + contactEmail: 'Lori_Morar@yahoo.com', + contactPhone: '990.782.8808', + logo: + 'https://fakeimg.pl/200x200/FFD890%2C128/000/?text=Pizza Pizza&font_size=75', + username: 'dominex_pizza', + usedCarriersIds: [ + '5cb1492408d4970a8444d8b5', + '5cb1492408d4970a8444d8b7', + '5cb1492408d4970a8444d8b9', + '5cb1492508d4970a8444d8bb', + '5cb1492508d4970a8444d8bd', + '5cb1492508d4970a8444d8bf', + '5cb1492508d4970a8444d8c1', + '5cb1492608d4970a8444d8c3', + '5cb1492608d4970a8444d8c5', + '5cb1492608d4970a8444d8c7', + '5cb1492708d4970a8444d8c9', + '5cb1492708d4970a8444d8cb', + '5cb1492708d4970a8444d8cd', + '5cb1492708d4970a8444d8cf', + '5cb1492808d4970a8444d8d1', + '5cb1492808d4970a8444d8d3', + '5cb1492808d4970a8444d8d5', + '5cb1492808d4970a8444d8d7', + '5cb1492908d4970a8444d8d9', + '5cb1492908d4970a8444d8db', + '5cb1492908d4970a8444d8dd', + '5cb1492a08d4970a8444d8df', + '5cb1492a08d4970a8444d8e1', + '5cb1492a08d4970a8444d8e3', + '5cb1492a08d4970a8444d8e5', + '5cb1492b08d4970a8444d8e7', + '5cb1492b08d4970a8444d8e9', + '5cb1492b08d4970a8444d8eb', + '5cb1492b08d4970a8444d8ed', + '5cb1492c08d4970a8444d8ef', + '5cb1492c08d4970a8444d8f1', + '5cb1492c08d4970a8444d8f3', + '5cb1492d08d4970a8444d8f5', + '5cb1492d08d4970a8444d8f7', + '5cb1492d08d4970a8444d8f9', + '5cb1492d08d4970a8444d8fb', + '5cb1492e08d4970a8444d8fd', + '5cb1492e08d4970a8444d8ff', + '5cb1492e08d4970a8444d901', + '5cb1492f08d4970a8444d903', + '5cb1492f08d4970a8444d905', + '5cb1492f08d4970a8444d907', + '5cb1492f08d4970a8444d909', + '5cb1493008d4970a8444d90b', + '5cb1493008d4970a8444d90d', + '5cb1493008d4970a8444d90f', + '5cb1493008d4970a8444d911', + '5cb1493108d4970a8444d913', + '5cb1493108d4970a8444d915', + '5cb1493108d4970a8444d917', + '5cb1493208d4970a8444d919', + '5cb1493208d4970a8444d91b', + '5cb1493208d4970a8444d91d', + '5cb1493208d4970a8444d91f', + '5cb1493308d4970a8444d921', + '5cb1493308d4970a8444d923', + '5cb1493308d4970a8444d925', + '5cb1493308d4970a8444d927', + '5cb1493408d4970a8444d929', + '5cb1493408d4970a8444d92b', + '5cb1493408d4970a8444d92d', + '5cb1493508d4970a8444d92f', + '5cb1493508d4970a8444d931', + '5cb1493508d4970a8444d933', + '5cb1493508d4970a8444d935', + '5cb1493608d4970a8444d937', + '5cb1493608d4970a8444d939', + '5cb1493608d4970a8444d93b', + '5cb1493708d4970a8444d93d', + '5cb1493708d4970a8444d93f', + '5cb1493708d4970a8444d941', + '5cb1493708d4970a8444d943', + '5cb1493808d4970a8444d945', + '5cb1493808d4970a8444d947', + '5cb1493808d4970a8444d949', + '5cb1493808d4970a8444d94b', + '5cb1493908d4970a8444d94d', + '5cb1493908d4970a8444d94f', + '5cb1493908d4970a8444d951', + '5cb1493a08d4970a8444d953', + '5cb1493a08d4970a8444d955', + '5cb1493a08d4970a8444d957', + '5cb1493a08d4970a8444d959', + '5cb1493b08d4970a8444d95b', + '5cb1493b08d4970a8444d95d', + '5cb1493b08d4970a8444d95f', + '5cb1493c08d4970a8444d961', + '5cb1493c08d4970a8444d963', + '5cb1493c08d4970a8444d965', + '5cb1493c08d4970a8444d967', + '5cb1493d08d4970a8444d969', + '5cb1493d08d4970a8444d96b', + '5cb1493d08d4970a8444d96d', + '5cb1493e08d4970a8444d96f', + '5cb1493f08d4970a8444d971', + '5cb1493f08d4970a8444d973', + '5cb1494008d4970a8444d975', + '5cb1494108d4970a8444d977', + '5cb1494108d4970a8444d979', + '5cb1494208d4970a8444d97b', + ], + carriersIds: [], + geoLocation: { + city: 'Ashdod', + streetAddress: 'HaAtsmaut', + house: '125', + __typename: 'GeoLocation', + }, + __typename: 'Warehouse', + barcodeData: '5cb1494408d4970a8444d983', + }, + { + id: '5cb1494308d4970a8444d97f', + _createdAt: '2017-04-01T04:12:00.000Z', + name: 'Pizza Hit', + contactEmail: 'Wilburn_Konopelski80@yahoo.com', + contactPhone: '641.266.1153 x733', + logo: + 'https://fakeimg.pl/200x200/FFD890%2C128/000/?text=Pizza Hit&font_size=75', + username: 'hut_pizza', + usedCarriersIds: [ + '5cb1492408d4970a8444d8b5', + '5cb1492408d4970a8444d8b7', + '5cb1492408d4970a8444d8b9', + '5cb1492508d4970a8444d8bb', + '5cb1492508d4970a8444d8bd', + '5cb1492508d4970a8444d8bf', + '5cb1492508d4970a8444d8c1', + '5cb1492608d4970a8444d8c3', + '5cb1492608d4970a8444d8c5', + '5cb1492608d4970a8444d8c7', + '5cb1492708d4970a8444d8c9', + '5cb1492708d4970a8444d8cb', + '5cb1492708d4970a8444d8cd', + '5cb1492708d4970a8444d8cf', + '5cb1492808d4970a8444d8d1', + '5cb1492808d4970a8444d8d3', + '5cb1492808d4970a8444d8d5', + '5cb1492808d4970a8444d8d7', + '5cb1492908d4970a8444d8d9', + '5cb1492908d4970a8444d8db', + '5cb1492908d4970a8444d8dd', + '5cb1492a08d4970a8444d8df', + '5cb1492a08d4970a8444d8e1', + '5cb1492a08d4970a8444d8e3', + '5cb1492a08d4970a8444d8e5', + '5cb1492b08d4970a8444d8e7', + '5cb1492b08d4970a8444d8e9', + '5cb1492b08d4970a8444d8eb', + '5cb1492b08d4970a8444d8ed', + '5cb1492c08d4970a8444d8ef', + '5cb1492c08d4970a8444d8f1', + '5cb1492c08d4970a8444d8f3', + '5cb1492d08d4970a8444d8f5', + '5cb1492d08d4970a8444d8f7', + '5cb1492d08d4970a8444d8f9', + '5cb1492d08d4970a8444d8fb', + '5cb1492e08d4970a8444d8fd', + '5cb1492e08d4970a8444d8ff', + '5cb1492e08d4970a8444d901', + '5cb1492f08d4970a8444d903', + '5cb1492f08d4970a8444d905', + '5cb1492f08d4970a8444d907', + '5cb1492f08d4970a8444d909', + '5cb1493008d4970a8444d90b', + '5cb1493008d4970a8444d90d', + '5cb1493008d4970a8444d90f', + '5cb1493008d4970a8444d911', + '5cb1493108d4970a8444d913', + '5cb1493108d4970a8444d915', + '5cb1493108d4970a8444d917', + '5cb1493208d4970a8444d919', + '5cb1493208d4970a8444d91b', + '5cb1493208d4970a8444d91d', + '5cb1493208d4970a8444d91f', + '5cb1493308d4970a8444d921', + '5cb1493308d4970a8444d923', + '5cb1493308d4970a8444d925', + '5cb1493308d4970a8444d927', + '5cb1493408d4970a8444d929', + '5cb1493408d4970a8444d92b', + '5cb1493408d4970a8444d92d', + '5cb1493508d4970a8444d92f', + '5cb1493508d4970a8444d931', + '5cb1493508d4970a8444d933', + '5cb1493508d4970a8444d935', + '5cb1493608d4970a8444d937', + '5cb1493608d4970a8444d939', + '5cb1493608d4970a8444d93b', + '5cb1493708d4970a8444d93d', + '5cb1493708d4970a8444d93f', + '5cb1493708d4970a8444d941', + '5cb1493708d4970a8444d943', + '5cb1493808d4970a8444d945', + '5cb1493808d4970a8444d947', + '5cb1493808d4970a8444d949', + '5cb1493808d4970a8444d94b', + '5cb1493908d4970a8444d94d', + '5cb1493908d4970a8444d94f', + '5cb1493908d4970a8444d951', + '5cb1493a08d4970a8444d953', + '5cb1493a08d4970a8444d955', + '5cb1493a08d4970a8444d957', + '5cb1493a08d4970a8444d959', + '5cb1493b08d4970a8444d95b', + '5cb1493b08d4970a8444d95d', + '5cb1493b08d4970a8444d95f', + '5cb1493c08d4970a8444d961', + '5cb1493c08d4970a8444d963', + '5cb1493c08d4970a8444d965', + '5cb1493c08d4970a8444d967', + '5cb1493d08d4970a8444d969', + '5cb1493d08d4970a8444d96b', + '5cb1493d08d4970a8444d96d', + '5cb1493e08d4970a8444d96f', + '5cb1493f08d4970a8444d971', + '5cb1493f08d4970a8444d973', + '5cb1494008d4970a8444d975', + '5cb1494108d4970a8444d977', + '5cb1494108d4970a8444d979', + '5cb1494208d4970a8444d97b', + ], + carriersIds: [], + geoLocation: { + city: 'Ashdod', + streetAddress: 'HaAtsmaut', + house: '125', + __typename: 'GeoLocation', + }, + __typename: 'Warehouse', + barcodeData: '5cb1494308d4970a8444d97f', + }, + { + id: '5cb1492108d4970a8444b566', + _createdAt: '2016-12-23T00:21:00.000Z', + name: 'Restaurant Leffler, Gislason and Williamson', + contactEmail: 'Elinor.Abernathy69@gmail.com', + contactPhone: '1-547-018-6580 x973', + logo: + 'https://fakeimg.pl/200x200/FFD890%2C128/000/?text=Leffler, Gislason and Williamson&font_size=75', + username: 'Wava_Kub', + usedCarriersIds: ['5cb1492108d4970a8444b564'], + carriersIds: [], + geoLocation: { + city: 'Violafurt', + streetAddress: '4642 Rempel Valleys', + house: '43', + __typename: 'GeoLocation', + }, + __typename: 'Warehouse', + barcodeData: '5cb1492108d4970a8444b566', + }, + { + id: '5cb1492108d4970a8444b56a', + _createdAt: '2014-08-16T20:08:00.000Z', + name: 'Restaurant Lynch, Mann and Roberts', + contactEmail: 'Gisselle91@yahoo.com', + contactPhone: '384.622.9808 x399', + logo: + 'https://fakeimg.pl/200x200/FFD890%2C128/000/?text=Lynch, Mann and Roberts&font_size=75', + username: 'Emmett80', + usedCarriersIds: ['5cb1492108d4970a8444b568'], + carriersIds: [], + geoLocation: { + city: 'Deckowmouth', + streetAddress: '0687 Salma Inlet', + house: '55', + __typename: 'GeoLocation', + }, + __typename: 'Warehouse', + barcodeData: '5cb1492108d4970a8444b56a', + }, + { + id: '5cb1492008d4970a8444b562', + _createdAt: '2013-06-30T10:02:00.000Z', + name: 'Restaurant Goyette, Ankunding and Hansen', + contactEmail: 'Demetrius_Barrows16@hotmail.com', + contactPhone: '(407) 305-1598 x4226', + logo: + 'https://fakeimg.pl/200x200/FFD890%2C128/000/?text=Goyette, Ankunding and Hansen&font_size=75', + username: 'Brisa.Runolfsdottir', + usedCarriersIds: ['5cb1492008d4970a8444b560'], + carriersIds: [], + geoLocation: { + city: 'Ritchieside', + streetAddress: '668 Bridie Islands', + house: '197', + __typename: 'GeoLocation', + }, + __typename: 'Warehouse', + barcodeData: '5cb1492008d4970a8444b562', + }, + { + id: '5cb1494408d4970a8444d981', + _createdAt: '2013-05-14T12:06:00.000Z', + name: 'Pizza Troya', + contactEmail: 'Corine11@hotmail.com', + contactPhone: '(769) 437-3162 x6846', + logo: + 'https://fakeimg.pl/200x200/FFD890%2C128/000/?text=Pizza Troya&font_size=75', + username: 'trova_pizza', + usedCarriersIds: [ + '5cb1492408d4970a8444d8b5', + '5cb1492408d4970a8444d8b7', + '5cb1492408d4970a8444d8b9', + '5cb1492508d4970a8444d8bb', + '5cb1492508d4970a8444d8bd', + '5cb1492508d4970a8444d8bf', + '5cb1492508d4970a8444d8c1', + '5cb1492608d4970a8444d8c3', + '5cb1492608d4970a8444d8c5', + '5cb1492608d4970a8444d8c7', + '5cb1492708d4970a8444d8c9', + '5cb1492708d4970a8444d8cb', + '5cb1492708d4970a8444d8cd', + '5cb1492708d4970a8444d8cf', + '5cb1492808d4970a8444d8d1', + '5cb1492808d4970a8444d8d3', + '5cb1492808d4970a8444d8d5', + '5cb1492808d4970a8444d8d7', + '5cb1492908d4970a8444d8d9', + '5cb1492908d4970a8444d8db', + '5cb1492908d4970a8444d8dd', + '5cb1492a08d4970a8444d8df', + '5cb1492a08d4970a8444d8e1', + '5cb1492a08d4970a8444d8e3', + '5cb1492a08d4970a8444d8e5', + '5cb1492b08d4970a8444d8e7', + '5cb1492b08d4970a8444d8e9', + '5cb1492b08d4970a8444d8eb', + '5cb1492b08d4970a8444d8ed', + '5cb1492c08d4970a8444d8ef', + '5cb1492c08d4970a8444d8f1', + '5cb1492c08d4970a8444d8f3', + '5cb1492d08d4970a8444d8f5', + '5cb1492d08d4970a8444d8f7', + '5cb1492d08d4970a8444d8f9', + '5cb1492d08d4970a8444d8fb', + '5cb1492e08d4970a8444d8fd', + '5cb1492e08d4970a8444d8ff', + '5cb1492e08d4970a8444d901', + '5cb1492f08d4970a8444d903', + '5cb1492f08d4970a8444d905', + '5cb1492f08d4970a8444d907', + '5cb1492f08d4970a8444d909', + '5cb1493008d4970a8444d90b', + '5cb1493008d4970a8444d90d', + '5cb1493008d4970a8444d90f', + '5cb1493008d4970a8444d911', + '5cb1493108d4970a8444d913', + '5cb1493108d4970a8444d915', + '5cb1493108d4970a8444d917', + '5cb1493208d4970a8444d919', + '5cb1493208d4970a8444d91b', + '5cb1493208d4970a8444d91d', + '5cb1493208d4970a8444d91f', + '5cb1493308d4970a8444d921', + '5cb1493308d4970a8444d923', + '5cb1493308d4970a8444d925', + '5cb1493308d4970a8444d927', + '5cb1493408d4970a8444d929', + '5cb1493408d4970a8444d92b', + '5cb1493408d4970a8444d92d', + '5cb1493508d4970a8444d92f', + '5cb1493508d4970a8444d931', + '5cb1493508d4970a8444d933', + '5cb1493508d4970a8444d935', + '5cb1493608d4970a8444d937', + '5cb1493608d4970a8444d939', + '5cb1493608d4970a8444d93b', + '5cb1493708d4970a8444d93d', + '5cb1493708d4970a8444d93f', + '5cb1493708d4970a8444d941', + '5cb1493708d4970a8444d943', + '5cb1493808d4970a8444d945', + '5cb1493808d4970a8444d947', + '5cb1493808d4970a8444d949', + '5cb1493808d4970a8444d94b', + '5cb1493908d4970a8444d94d', + '5cb1493908d4970a8444d94f', + '5cb1493908d4970a8444d951', + '5cb1493a08d4970a8444d953', + '5cb1493a08d4970a8444d955', + '5cb1493a08d4970a8444d957', + '5cb1493a08d4970a8444d959', + '5cb1493b08d4970a8444d95b', + '5cb1493b08d4970a8444d95d', + '5cb1493b08d4970a8444d95f', + '5cb1493c08d4970a8444d961', + '5cb1493c08d4970a8444d963', + '5cb1493c08d4970a8444d965', + '5cb1493c08d4970a8444d967', + '5cb1493d08d4970a8444d969', + '5cb1493d08d4970a8444d96b', + '5cb1493d08d4970a8444d96d', + '5cb1493e08d4970a8444d96f', + '5cb1493f08d4970a8444d971', + '5cb1493f08d4970a8444d973', + '5cb1494008d4970a8444d975', + '5cb1494108d4970a8444d977', + '5cb1494108d4970a8444d979', + '5cb1494208d4970a8444d97b', + ], + carriersIds: [], + geoLocation: { + city: 'Ashdod', + streetAddress: 'HaAtsmaut', + house: '128', + __typename: 'GeoLocation', + }, + __typename: 'Warehouse', + barcodeData: '5cb1494408d4970a8444d981', + }, +]; diff --git a/packages/admin-web-angular/src/app/pages/+dashboard/dashboard.component.html b/packages/admin-web-angular/src/app/pages/+dashboard/dashboard.component.html new file mode 100644 index 0000000..4a614c8 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+dashboard/dashboard.component.html @@ -0,0 +1,273 @@ +
+
+
+
+ + +
+
+ {{ + 'DASHBOARD_VIEW.TOTAL_CUSTOMER' | translate + }} +
+

+ + {{ + totalInfo.customers | myNumberWithCommas + }} + + + +

+ +
+ {{ + 'DASHBOARD_VIEW.TOTAL_QUANTITY_OF_EXISTING_CUSTOMERS' + | translate + }} +
+
+ +
+
+ {{ + 'DASHBOARD_VIEW.TOTAL_COMPLETED_ORDERS' + | translate + }} +
+

+ + {{ totalInfo.orders | myNumberWithCommas }} + + + +

+ +
+ {{ + 'DASHBOARD_VIEW.TOTAL_QUANTITY_OF_COMPLETED_ORDERS' + | translate + }} +
+
+ +
+
+ {{ 'DASHBOARD_VIEW.TOTAL_REVENUE' | translate }} +
+

+ + ${{ + totalInfo.revenue | myNumberWithCommas + }} + + + +

+ +
+ {{ + 'DASHBOARD_VIEW.TOTAL_SUM_SUM_OF_ALL_COMPLETED_ORDERS' + | translate + }} +
+
+
+
+
+ +
+ + +
+
+ {{ + 'DASHBOARD_VIEW.TODAYs_CUSTOMERS' + | translate + }} +
+

+ + {{ + todayInfo.customers | myNumberWithCommas + }} + + + +

+ + + +
+ + + {{ + 'DASHBOARD_VIEW.TODAYs_QUANTITY_OF_REGISTERED_CUSTOMERS' + | translate + }} + + {{ labelAvgPercent.customers }} ({{ + averageRateCustomersToday.value.toFixed( + 2 + ) + }}%) + + + +
+
+ +
+
+ {{ + 'DASHBOARD_VIEW.TODAYs_COMPLETED_ORDERS' + | translate + }} +
+

+ + {{ todayInfo.orders | myNumberWithCommas }} + + + +

+ + + +
+ + + {{ + 'DASHBOARD_VIEW.TODAYs_QUANTITY_OF_NEW_COMPLETED_ORDERS' + | translate + }} + + {{ labelAvgPercent.orders }} ({{ + averageRateOrdersToday.value.toFixed( + 2 + ) + }}%) + + +
+
+ +
+
+ {{ + 'DASHBOARD_VIEW.TODAYs_REVENUE' | translate + }} +
+

+ + ${{ + todayInfo.revenue | myNumberWithCommas + }} + + + +

+ + + +
+ + + {{ + 'DASHBOARD_VIEW.TODAYs_SUM_OF_COMPLETED_ORDERS' + | translate + }} + + {{ labelAvgPercent.revenue }} ({{ + averageRateRevenueToday.value.toFixed( + 2 + ) + }}%) + + +
+
+
+
+
+
+ +
+
+ +
+
+
+ +
+ +
+
diff --git a/packages/admin-web-angular/src/app/pages/+dashboard/dashboard.component.scss b/packages/admin-web-angular/src/app/pages/+dashboard/dashboard.component.scss new file mode 100644 index 0000000..4556398 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+dashboard/dashboard.component.scss @@ -0,0 +1,60 @@ +@import '../../@theme/styles/themes'; + +$shadow-green: #00977e; + +@include nb-install-component() { + .progress-info { + color: #2a2a2a; + margin-top: 2.5rem; + + &:first-child { + margin-top: 0; + } + } + + .title { + font-family: font-main !important; + // font-size: 1.125rem !important; + font-weight: 600 !important; + } + + .value { + // font-size: 2.5rem !important; + font-weight: 300 !important; + line-height: 2rem !important; + } + + .description { + color: #a4abb3; + } + + ::ng-deep nb-progress-bar { + margin-top: 0.2rem; + + .progress-container { + height: 0.8rem; + border-radius: 0; + } + + .progress-value { + height: 0.6rem; + background: nb-theme(progress-bar-background); + + @include nb-for-theme(cosmic) { + box-shadow: 0 0.2rem $shadow-green; + } + + @include nb-for-theme(corporate) { + height: 100%; + } + } + } + + .waiting { + opacity: 0.2; + } + + .loading-placeholder { + padding: 13px 61px; + } +} diff --git a/packages/admin-web-angular/src/app/pages/+dashboard/dashboard.component.ts b/packages/admin-web-angular/src/app/pages/+dashboard/dashboard.component.ts new file mode 100644 index 0000000..462a802 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+dashboard/dashboard.component.ts @@ -0,0 +1,486 @@ +import { Component, OnInit, OnDestroy } from '@angular/core'; +import Warehouse from '@modules/server.common/entities/Warehouse'; +import { Subject, Subscription } from 'rxjs'; +import { WarehousesService } from '../../@core/data/warehouses.service'; +import Order from '@modules/server.common/entities/Order'; +import { OrdersService } from '../../@core/data/orders.service'; +import { WarehouseOrdersService } from '@app/@core/data/warehouseOrders.service'; +import { DashboardLoadingIndicatorState } from '@app/models/DashboardLoadingIndicatorState'; +import { TranslateService } from '@ngx-translate/core'; +import { DashboardInfoViewModel } from '@app/models/DashboardInfoViewModel'; +import { IExistingCustomersViewModel } from '@app/models/IExistingCustomersViewModel'; +import { takeUntil } from 'rxjs/operators'; + +@Component({ + selector: 'ea-dashboard', + styleUrls: ['./dashboard.component.scss'], + templateUrl: './dashboard.component.html', +}) +export class DashboardComponent implements OnInit, OnDestroy { + stores: Warehouse[] = []; + + loading = new DashboardLoadingIndicatorState(); + + totalInfo = new DashboardInfoViewModel(); + todayInfo = new DashboardInfoViewModel(); + + existingCustomers: IExistingCustomersViewModel; + existingCustomersToday: IExistingCustomersViewModel; + + completedOrders: Order[] = []; + completedOrdersToday: Order[] = []; + chartPanelOrders: Order[] = []; + + hasSelectedStore: boolean = false; + selectedStoreId: string; + isChartPanelOrdersLoad: boolean = true; + + averageRateCustomersToday = { + value: 0, + allStores: 0, + perStore: {}, // => perStore[storeId] + }; + averageRateOrdersToday = { + value: 0, + allStores: 0, + perStore: {}, + }; + averageRateRevenueToday = { + value: 0, + allStores: 0, + perStore: {}, + }; + + private _ngDestroy$ = new Subject(); + + constructor( + private readonly _storesService: WarehousesService, + private readonly _storeOrdersService: WarehouseOrdersService, + private readonly _ordersService: OrdersService, + private readonly _translateService: TranslateService + ) {} + + get labelAvgPercent() { + const maxPercentRate = 100; + return { + customers: + this.averageRateCustomersToday.value > maxPercentRate + ? this._translations.labelBetterThanAverage + : this._translations.labelTillAverage, + + orders: + this.averageRateOrdersToday.value > maxPercentRate + ? this._translations.labelBetterThanAverage + : this._translations.labelTillAverage, + + revenue: + this.averageRateRevenueToday.value > maxPercentRate + ? this._translations.labelBetterThanAverage + : this._translations.labelTillAverage, + }; + } + + private get _translations(): { + labelTillAverage: string; + labelBetterThanAverage: string; + } { + const translationPrefix = 'DASHBOARD_VIEW'; + + return { + labelTillAverage: this._translate( + `${translationPrefix}.TILL_AVERAGE` + ), + labelBetterThanAverage: this._translate( + `${translationPrefix}.BETTER_THAN_AVERAGE` + ), + }; + } + + private get _toggleLoading() { + return { + totalCustomers: (isLoading) => + (this.loading.totalInfo.customers = isLoading), + totalOrders: (isLoading) => + (this.loading.totalInfo.orders = isLoading), + totalRevenue: (isLoading) => + (this.loading.totalInfo.revenue = isLoading), + + todayCustomers: (isLoading) => + (this.loading.todayInfo.customers = isLoading), + todayOrders: (isLoading) => + (this.loading.todayInfo.orders = isLoading), + todayRevenue: (isLoading) => + (this.loading.todayInfo.revenue = isLoading), + }; + } + + ngOnInit() { + this.loadAllStoresData(); + } + + async onSelectStore(storeId: string) { + if (storeId) { + this.hasSelectedStore = true; + this.selectedStoreId = storeId; + + this._toggleLoadingDashboardMetrics(true); + + this._displayTotalCustomers(); + this._displayTotalCustomersToday(); + + await this._calculatePerMerchantMetrics(); + this._calculatePerMerchantMetricsToday(); + this._calculateAveragePercentagesToday(); + + this._toggleLoadingDashboardMetrics(false); + + this._listenChartPanelPerStoreOrders(); + } else { + this.hasSelectedStore = false; + this.loadAllStoresData(); + } + } + + private loadAllStoresData() { + this._listenChartPanelTotalOrders(); + + this._listenTotalStores(); + + this._listenTotalCustomers(); + this._listenTotalCustomersToday(); + + this._listenTotalOrders(); + this._listenTotalOrdersToday(); + } + + private _listenTotalStores() { + this._storesService + .getStores() + .pipe(takeUntil(this._ngDestroy$)) + .subscribe((stores) => { + this.stores = stores; + }); + } + + private _toggleLoadingDashboardMetrics(isLoading: boolean) { + this._toggleLoading.totalCustomers(isLoading); + this._toggleLoading.totalOrders(isLoading); + this._toggleLoading.totalRevenue(isLoading); + this._toggleLoading.todayCustomers(isLoading); + this._toggleLoading.todayOrders(isLoading); + this._toggleLoading.todayRevenue(isLoading); + } + + private async _listenChartPanelTotalOrders() { + this.isChartPanelOrdersLoad = true; + this.chartPanelOrders = await this._ordersService.getOrdersChartTotalOrdersNew(); + this.isChartPanelOrdersLoad = false; + // .pipe(takeUntil(this._ngDestroy$)) + // .subscribe((orders) => { + // if (!this.hasSelectedStore) { + // this.chartPanelOrders = orders; + // } + // }); + } + + private _dashboardOrdersChartOrdersSubscription: Subscription; + private _listenChartPanelPerStoreOrders() { + // Every time when new store is selected, we have to unsubscribe previous emissions + if (this._dashboardOrdersChartOrdersSubscription) { + this._dashboardOrdersChartOrdersSubscription.unsubscribe(); + this._dashboardOrdersChartOrdersSubscription = null; + } + + this._dashboardOrdersChartOrdersSubscription = this._storeOrdersService + .getDashboardOrdersChartOrders(this.selectedStoreId) + .subscribe((orders) => { + this.chartPanelOrders = orders; + }); + } + + private async _listenTotalOrders() { + // Old logic for orders info + // this._ordersService + // .getDashboardCompletedOrders() + // .pipe(takeUntil(this._ngDestroy$)) + // .subscribe((orders) => { + // this.completedOrders = orders; + // if (this.hasSelectedStore) { + // this._calculatePerMerchantMetrics(); + // } else { + // this._calculateGlobalMetrics(); + // } + // this._calculateAveragePercentagesToday(); + // }); + + this._toggleLoading.totalOrders(true); + this._toggleLoading.totalRevenue(true); + + if (this.hasSelectedStore) { + await this._calculatePerMerchantMetrics(); + } else { + await this._calculateGlobalMetrics(); + } + + this._toggleLoading.totalOrders(false); + this._toggleLoading.totalRevenue(false); + + this._calculateAveragePercentagesToday(); + } + + private _listenTotalOrdersToday() { + this._toggleLoading.todayOrders(true); + this._toggleLoading.todayRevenue(true); + + this._ordersService + .getDashboardCompletedOrdersToday() + .pipe(takeUntil(this._ngDestroy$)) + .subscribe((orders) => { + this.completedOrdersToday = orders; + + if (this.hasSelectedStore) { + this._calculatePerMerchantMetricsToday(); + } else { + this._calculateGlobalMetricsToday(); + } + this._calculateAveragePercentagesToday(); + + this._toggleLoading.todayOrders(false); + this._toggleLoading.todayRevenue(false); + }); + } + + private _listenTotalCustomers() { + this._toggleLoading.totalCustomers(true); + + this._storesService + .getCountExistingCustomers() + .pipe(takeUntil(this._ngDestroy$)) + .subscribe((existingCustomers) => { + this.existingCustomers = existingCustomers; + + this._displayTotalCustomers(); + this._toggleLoading.totalCustomers(false); + + this._calculateAveragePercentagesToday(); + }); + } + + private _listenTotalCustomersToday() { + this._toggleLoading.todayCustomers(true); + + this._storesService + .getCountExistingCustomersToday() + .pipe(takeUntil(this._ngDestroy$)) + .subscribe((existingCustomersToday) => { + this.existingCustomersToday = existingCustomersToday; + + this._displayTotalCustomersToday(); + this._toggleLoading.todayCustomers(false); + this._calculateAveragePercentagesToday(); + }); + } + + private _calculateStoreDaysExistence(store: Warehouse) { + const day = 24 * 60 * 60 * 1000; + + const storeCreatedAt = new Date(store._createdAt); + const difference = Math.abs(Date.now() - storeCreatedAt.getTime()); + const storeDaysExistence = difference / day; + + return storeDaysExistence; + } + + private _calculateAveragePercentagesToday() { + let storesDaysTotal = 0; + + this.stores.forEach((store) => { + let storeDaysExistence = this._calculateStoreDaysExistence(store); + storesDaysTotal += storeDaysExistence; + + if (storeDaysExistence < 1) { + storeDaysExistence = 1; + } + this._calculateAverageCustomersPercentCurrentStore( + storeDaysExistence, + store.id + ); + this._calculateAverageOrdersPercentCurrentStore( + storeDaysExistence, + store.id + ); + this._calculateAverageRevenuePercentCurrentStore( + storeDaysExistence, + store.id + ); + }); + + const daysTotalAverage = storesDaysTotal / this.stores.length || 1; + // if (daysTotalAverage < 1) { + // daysTotalAverage = 1; + // } + + this._calculateAverageCustomersPercentGlobal(daysTotalAverage); + this._calculateAverageOrdersPercentGlobal(daysTotalAverage); + this._calculateAverageRevenuePercentGlobal(daysTotalAverage); + + this._displayCustomersAveragePercent(); + this._displayOrdersAveragePercent(); + this._displayRevenueAveragePercent(); + } + + private _calculateAverageCustomersPercentGlobal(daysTotalAverage: number) { + const averageCustomersPerDay = + this.totalInfo.customers / daysTotalAverage || 0; + + this.averageRateCustomersToday.allStores = + (this.todayInfo.customers / averageCustomersPerDay || 0) * 100; + } + + private _calculateAverageOrdersPercentGlobal(daysTotalAverage: number) { + const averageOrdersPerDay = + this.totalInfo.orders / daysTotalAverage || 0; + + this.averageRateOrdersToday.allStores = + (this.todayInfo.orders / averageOrdersPerDay || 0) * 100; + } + + private _calculateAverageRevenuePercentGlobal(daysTotalAverage: number) { + const averageRevenuePerDay = + this.totalInfo.revenue / daysTotalAverage || 0; + + this.averageRateRevenueToday.allStores = + (this.todayInfo.revenue / averageRevenuePerDay || 0) * 100; + } + + private _calculateAverageCustomersPercentCurrentStore( + storeDaysExistence: number, + storeId: string + ) { + const averageCustomers = + this.totalInfo.customers / storeDaysExistence || 0; + + this.averageRateCustomersToday.perStore[storeId] = + (this.todayInfo.customers / averageCustomers || 0) * 100; + } + + private _calculateAverageOrdersPercentCurrentStore( + storeDaysExistence: number, + storeId: string + ) { + const averageOrders = this.totalInfo.orders / storeDaysExistence || 0; + + this.averageRateOrdersToday.perStore[storeId] = + (this.todayInfo.orders / averageOrders || 0) * 100; + } + + private _calculateAverageRevenuePercentCurrentStore( + storeDaysExistence: number, + storeId: string + ) { + const averageRevenue = this.totalInfo.revenue / storeDaysExistence || 0; + + this.averageRateRevenueToday.perStore[storeId] = + (this.todayInfo.revenue / averageRevenue || 0) * 100; + } + + private async _calculateGlobalMetrics() { + const orderInfo = await this._ordersService.getComplatedOrdersInfo(); + + this.totalInfo.orders = orderInfo['totalOrders']; + this.totalInfo.revenue = orderInfo['totalRevenue']; + } + + private _calculateGlobalMetricsToday() { + this.todayInfo.orders = this.completedOrdersToday.length; + + this.todayInfo.revenue = this.completedOrdersToday + .map((order) => order.totalPrice) + .reduce((prevPrice, nextPrice) => prevPrice + nextPrice, 0); + } + + private async _calculatePerMerchantMetrics() { + const orderInfo = await this._ordersService.getComplatedOrdersInfo( + this.selectedStoreId + ); + + this.totalInfo.orders = orderInfo['totalOrders']; + this.totalInfo.revenue = orderInfo['totalRevenue']; + } + + private _calculatePerMerchantMetricsToday() { + const storeCompletedOrdersToday = this.completedOrdersToday.filter( + (o) => o.warehouseId === this.selectedStoreId + ); + + this.todayInfo.orders = storeCompletedOrdersToday.length; + this.todayInfo.revenue = storeCompletedOrdersToday + .map((order) => order.totalPrice) + .reduce((prevPrice, nextPrice) => prevPrice + nextPrice, 0); + } + + private _displayCustomersAveragePercent() { + this.hasSelectedStore + ? (this.averageRateCustomersToday.value = this.averageRateCustomersToday.perStore[ + this.selectedStoreId + ]) + : (this.averageRateCustomersToday.value = this.averageRateCustomersToday.allStores); + } + + private _displayOrdersAveragePercent() { + this.hasSelectedStore + ? (this.averageRateOrdersToday.value = this.averageRateOrdersToday.perStore[ + this.selectedStoreId + ]) + : (this.averageRateOrdersToday.value = this.averageRateOrdersToday.allStores); + } + + private _displayRevenueAveragePercent() { + this.hasSelectedStore + ? (this.averageRateRevenueToday.value = this.averageRateRevenueToday.perStore[ + this.selectedStoreId + ]) + : (this.averageRateRevenueToday.value = this.averageRateRevenueToday.allStores); + } + + private _displayTotalCustomers() { + if (this.hasSelectedStore) { + const store = this.existingCustomers.perStore.find( + (s) => s.storeId === this.selectedStoreId + ); + this.totalInfo.customers = store ? store.customersCount : 0; + } else { + this.totalInfo.customers = this.existingCustomers.total; + } + } + + private _displayTotalCustomersToday() { + if (this.hasSelectedStore) { + const store = this.existingCustomersToday.perStore.find( + (s) => s.storeId === this.selectedStoreId + ); + this.todayInfo.customers = store ? store.customersCount : 0; + } else { + this.todayInfo.customers = this.existingCustomersToday.total; + } + } + + private _translate(key: string): string { + let translationResult = ''; + + this._translateService.get(key).subscribe((res) => { + translationResult = res; + }); + + return translationResult; + } + + ngOnDestroy() { + this._ngDestroy$.next(); + this._ngDestroy$.complete(); + + if (this._dashboardOrdersChartOrdersSubscription) { + this._dashboardOrdersChartOrdersSubscription.unsubscribe(); + } + } +} diff --git a/packages/admin-web-angular/src/app/pages/+dashboard/dashboard.module.ts b/packages/admin-web-angular/src/app/pages/+dashboard/dashboard.module.ts new file mode 100644 index 0000000..1123a41 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+dashboard/dashboard.module.ts @@ -0,0 +1,72 @@ +import { NgModule } from '@angular/core'; +import { DashboardComponent } from './dashboard.component'; +import { Routes, RouterModule } from '@angular/router'; +import { CommonModule } from '@angular/common'; +import { TranslateModule } from '@ngx-translate/core'; +import { ThemeModule } from '../../@theme'; +import { ChartsPanelComponent } from './charts-panel/charts-panel.component'; +import { OrdersChartComponent } from './charts-panel/charts/orders-chart/orders-chart.component'; +// duplicate OrdersChartComponent +import { OrdersChartComponent as RootOrdersChartComponent } from './charts-panel/charts/orders-chart.component'; +import { ProfitChartComponent } from './charts-panel/charts/profit-chart/profit-chart.component'; +// duplicate ProfitChartComponent +import { ProfitChartComponent as RootProfitChartComponent } from './charts-panel/charts/profit-chart.component'; +import { ChartPanelHeaderComponent } from './charts-panel/chart-panel-header/chart-panel-header.component'; +import { ChartPanelSummaryComponent } from './charts-panel/chart-panel-summary/chart-panel-summary.component'; +import { NgxEchartsModule } from 'ngx-echarts'; +import { LegendChartComponent } from './charts-panel/legend-chart/legend-chart.component'; +import { DashboardSelectStoreComponent } from './dashboard-select-store/dashboard-select-store.component'; +import { LayoutService } from '@app/@core/services/dashboard/layout.service'; +import { OrdersProfitChartService } from '@app/@core/services/dashboard/orders-profit-chart.service'; +import { OrdersChartService } from '@app/@core/services/dashboard/orders-chart.service'; +import { PeriodsService } from '@app/@core/services/dashboard/periods.service'; +import { ProfitChartService } from '@app/@core/services/dashboard/profit-chart.service'; +import { ToasterModule } from 'angular2-toaster'; +import { NbSpinnerModule } from '@nebular/theme'; +import { PipesModule } from '@modules/client.common.angular2/pipes/pipes.module'; +import { NgSelectModule } from '@ng-select/ng-select'; +import { FormsModule } from '@angular/forms'; + +export const routes: Routes = [ + { + path: '', + component: DashboardComponent, + }, +]; + +@NgModule({ + imports: [ + CommonModule, + ThemeModule, + NgxEchartsModule.forRoot({ + echarts: () => import('echarts') + }), + ToasterModule, + TranslateModule.forChild(), + RouterModule.forChild(routes), + NbSpinnerModule, + PipesModule, + NgSelectModule, + FormsModule, + ], + declarations: [ + DashboardComponent, + ChartsPanelComponent, + ChartPanelHeaderComponent, + ChartPanelSummaryComponent, + OrdersChartComponent, + RootOrdersChartComponent, + ProfitChartComponent, + RootProfitChartComponent, + LegendChartComponent, + DashboardSelectStoreComponent, + ], + providers: [ + LayoutService, + OrdersProfitChartService, + OrdersChartService, + ProfitChartService, + PeriodsService, + ] +}) +export class DashboardModule {} diff --git a/packages/admin-web-angular/src/app/pages/+dashboard/dashboard.stories.ts b/packages/admin-web-angular/src/app/pages/+dashboard/dashboard.stories.ts new file mode 100644 index 0000000..0175f9e --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+dashboard/dashboard.stories.ts @@ -0,0 +1,742 @@ +import { storiesOf, moduleMetadata } from '@storybook/angular'; +import { text, withKnobs, object } from '@storybook/addon-knobs'; +import { action } from '@storybook/addon-actions'; +import { ThemeModule } from '@app/@theme'; +import { CommonModule, APP_BASE_HREF, DOCUMENT } from '@angular/common'; +import { Ng2SmartTableModule } from 'ng2-smart-table'; +import { + NbSpinnerModule, + NbThemeService, + NbThemeModule, + NB_THEME_OPTIONS, + NbMediaBreakpointsService, + NB_MEDIA_BREAKPOINTS, + NB_BUILT_IN_JS_THEMES, + NB_JS_THEMES, + DEFAULT_MEDIA_BREAKPOINTS, + NB_WINDOW, + windowFactory, + NB_DOCUMENT, + NbSpinnerService, + NB_LAYOUT_DIRECTION, + NbLayoutDirection, + NbLayoutDirectionService, + NbLayoutScrollService, + NbLayoutRulerService, + NbOverlayModule, +} from '@nebular/theme'; +import { ConfirmationModalModule } from '@app/@shared/confirmation-modal/confirmation-modal.module'; +import { ToasterModule } from 'angular2-toaster'; +import { TranslateStore, TranslateService } from '@ngx-translate/core'; +import { RouterModule } from '@angular/router'; +import { NbAuthModule, NbAuthService } from '@nebular/auth'; +import { NotifyService } from '@app/@core/services/notify/notify.service'; +import { APOLLO_OPTIONS } from 'apollo-angular'; +import { HttpLink } from 'apollo-angular/http'; +import { InMemoryCache } from '@apollo/client/core'; +import { PipesModule } from '@modules/client.common.angular2/pipes/pipes.module'; +import { HttpClient, HttpClientModule } from '@angular/common/http'; +import { TranslateHttpLoader } from '@ngx-translate/http-loader'; +import { NgSelectModule } from '@ng-select/ng-select'; +import { I18nModule } from '@app/@core/utils/i18n.module'; +import { DashboardComponent } from './dashboard.component'; +import { NgxEchartsModule } from 'ngx-echarts'; +import { ChartsPanelComponent } from './charts-panel/charts-panel.component'; +import { ChartPanelHeaderComponent } from './charts-panel/chart-panel-header/chart-panel-header.component'; +import { ChartPanelSummaryComponent } from './charts-panel/chart-panel-summary/chart-panel-summary.component'; +import { OrdersChartComponent } from './charts-panel/charts/orders-chart/orders-chart.component'; +import { ProfitChartComponent } from './charts-panel/charts/profit-chart/profit-chart.component'; +import { LegendChartComponent } from './charts-panel/legend-chart/legend-chart.component'; +import { DashboardSelectStoreComponent } from './dashboard-select-store/dashboard-select-store.component'; +import { WarehousesService } from '@app/@core/data/warehouses.service'; +import { WarehouseModule } from '../+warehouses/+warehouse'; +import { WarehouseOrdersService } from '@app/@core/data/warehouseOrders.service'; +import { WarehouseProductCreateModule } from '@app/@shared/warehouse-product/warehouse-product-create'; +import { WarehouseOrderModule } from '../+warehouses/+warehouse/+warehouse-order/warehouse-order.module'; +import { WarehouseProductsViewModule } from '../+warehouses/+warehouse/+warehouse-products-view/warehouse-products-view.module'; +import { WarehouseMainInfoViewModule } from '../+warehouses/+warehouse/warehouse-main-info/warehouse-main-info.module'; +import { WarehouseSelectViewModule } from '../+warehouses/+warehouse/warehouse-select-view/warehouse-select-view.module'; +import { WarehouseOrderViewModule } from '../+warehouses/+warehouse/warehouse-order-view/warehouse-order-view.module'; +import { WarehouseOrdersTableModule } from '../+warehouses/+warehouse/warehouse-orders-table/warehouse-orders-table.module'; +import { OrdersService } from '@app/@core/data/orders.service'; +import { OrdersProfitChartService } from '@app/@core/services/dashboard/orders-profit-chart.service'; +import { OrdersChartService } from '@app/@core/services/dashboard/orders-chart.service'; +import { PeriodsService } from '@app/@core/services/dashboard/periods.service'; +import { ProfitChartService } from '@app/@core/services/dashboard/profit-chart.service'; +import { LayoutService } from '@app/@core/services/dashboard/layout.service'; +import { + BUILT_IN_THEMES, + NbJSThemesRegistry, +} from '@nebular/theme/services/js-themes-registry.service'; + +const stories = storiesOf('Dashboard', module); + +export function createApollo(httpLink: HttpLink) { + return { + link: httpLink.create({ uri: 'https://api.example.com/graphql' }), + cache: new InMemoryCache(), + }; +} + +export function createTranslateLoader(http: HttpClient) { + return new TranslateHttpLoader(http, '/i18n/', '.json'); +} + +stories.addDecorator(withKnobs); + +stories.addDecorator( + moduleMetadata({ + declarations: [ + DashboardComponent, + ChartsPanelComponent, + ChartPanelHeaderComponent, + ChartPanelSummaryComponent, + OrdersChartComponent, + ProfitChartComponent, + LegendChartComponent, + DashboardSelectStoreComponent, + ], + imports: [ + NgSelectModule, + CommonModule, + ThemeModule, + Ng2SmartTableModule, + NbSpinnerModule, + ConfirmationModalModule, + ToasterModule.forRoot(), + HttpClientModule, + I18nModule, + RouterModule.forRoot([]), + NbAuthModule, + PipesModule, + NgxEchartsModule, + WarehouseModule, + WarehouseProductCreateModule, + WarehouseOrderModule, + WarehouseProductsViewModule, + WarehouseMainInfoViewModule, + WarehouseSelectViewModule, + WarehouseOrderViewModule, + WarehouseOrdersTableModule, + NbThemeModule, + ], + entryComponents: [ + ChartsPanelComponent, + ChartPanelHeaderComponent, + ChartPanelSummaryComponent, + OrdersChartComponent, + ProfitChartComponent, + LegendChartComponent, + ], + providers: [ + { + provide: APOLLO_OPTIONS, + useFactory: createApollo, + deps: [HttpLink], + }, + TranslateStore, + TranslateService, + NotifyService, + HttpLink, + WarehousesService, + WarehouseOrdersService, + OrdersService, + OrdersProfitChartService, + OrdersChartService, + PeriodsService, + ProfitChartService, + { provide: APP_BASE_HREF, useValue: '/' }, + { provide: NB_THEME_OPTIONS, useValue: {} }, + { + provide: NB_MEDIA_BREAKPOINTS, + useValue: { + name: 'sm', + width: 576, + }, + }, + LayoutService, + OrdersProfitChartService, + OrdersChartService, + ProfitChartService, + PeriodsService, + NbThemeModule, + NbThemeService, + NbAuthService, + NbMediaBreakpointsService, + { provide: NB_THEME_OPTIONS, useValue: {} }, + { provide: NB_BUILT_IN_JS_THEMES, useValue: BUILT_IN_THEMES }, + { provide: NB_JS_THEMES, useValue: [] }, + { + provide: NB_MEDIA_BREAKPOINTS, + useValue: DEFAULT_MEDIA_BREAKPOINTS, + }, + { provide: NB_WINDOW, useFactory: windowFactory }, + { provide: NB_DOCUMENT, useExisting: DOCUMENT }, + NbJSThemesRegistry, + NbThemeService, + NbMediaBreakpointsService, + NbSpinnerService, + { provide: NB_LAYOUT_DIRECTION, useValue: NbLayoutDirection.LTR }, + NbLayoutDirectionService, + NbLayoutScrollService, + NbLayoutRulerService, + ...NbOverlayModule.forRoot().providers, + ], + }) +); + +stories.add('Dashboard', () => ({ + component: DashboardComponent, + props: { + stores: object('Stores', stores), + text: text('Hello', ''), + selectedStoreEmitter: action('Selected Data'), + }, +})); + +const stores = [ + { + id: '5cb1494208d4970a8444d97d', + _createdAt: '2018-06-28T08:05:00.000Z', + name: 'Pizza Dan', + contactEmail: 'Daija_Bernier8@gmail.com', + contactPhone: '(667) 017-1190 x99049', + logo: + 'https://fakeimg.pl/200x200/FFD890%2C128/000/?text=Pizza Dan&font_size=75', + username: 'restaurant_pizza', + usedCarriersIds: [ + '5cb1492408d4970a8444d8b5', + '5cb1492408d4970a8444d8b7', + '5cb1492408d4970a8444d8b9', + '5cb1492508d4970a8444d8bb', + '5cb1492508d4970a8444d8bd', + '5cb1492508d4970a8444d8bf', + '5cb1492508d4970a8444d8c1', + '5cb1492608d4970a8444d8c3', + '5cb1492608d4970a8444d8c5', + '5cb1492608d4970a8444d8c7', + '5cb1492708d4970a8444d8c9', + '5cb1492708d4970a8444d8cb', + '5cb1492708d4970a8444d8cd', + '5cb1492708d4970a8444d8cf', + '5cb1492808d4970a8444d8d1', + '5cb1492808d4970a8444d8d3', + '5cb1492808d4970a8444d8d5', + '5cb1492808d4970a8444d8d7', + '5cb1492908d4970a8444d8d9', + '5cb1492908d4970a8444d8db', + '5cb1492908d4970a8444d8dd', + '5cb1492a08d4970a8444d8df', + '5cb1492a08d4970a8444d8e1', + '5cb1492a08d4970a8444d8e3', + '5cb1492a08d4970a8444d8e5', + '5cb1492b08d4970a8444d8e7', + '5cb1492b08d4970a8444d8e9', + '5cb1492b08d4970a8444d8eb', + '5cb1492b08d4970a8444d8ed', + '5cb1492c08d4970a8444d8ef', + '5cb1492c08d4970a8444d8f1', + '5cb1492c08d4970a8444d8f3', + '5cb1492d08d4970a8444d8f5', + '5cb1492d08d4970a8444d8f7', + '5cb1492d08d4970a8444d8f9', + '5cb1492d08d4970a8444d8fb', + '5cb1492e08d4970a8444d8fd', + '5cb1492e08d4970a8444d8ff', + '5cb1492e08d4970a8444d901', + '5cb1492f08d4970a8444d903', + '5cb1492f08d4970a8444d905', + '5cb1492f08d4970a8444d907', + '5cb1492f08d4970a8444d909', + '5cb1493008d4970a8444d90b', + '5cb1493008d4970a8444d90d', + '5cb1493008d4970a8444d90f', + '5cb1493008d4970a8444d911', + '5cb1493108d4970a8444d913', + '5cb1493108d4970a8444d915', + '5cb1493108d4970a8444d917', + '5cb1493208d4970a8444d919', + '5cb1493208d4970a8444d91b', + '5cb1493208d4970a8444d91d', + '5cb1493208d4970a8444d91f', + '5cb1493308d4970a8444d921', + '5cb1493308d4970a8444d923', + '5cb1493308d4970a8444d925', + '5cb1493308d4970a8444d927', + '5cb1493408d4970a8444d929', + '5cb1493408d4970a8444d92b', + '5cb1493408d4970a8444d92d', + '5cb1493508d4970a8444d92f', + '5cb1493508d4970a8444d931', + '5cb1493508d4970a8444d933', + '5cb1493508d4970a8444d935', + '5cb1493608d4970a8444d937', + '5cb1493608d4970a8444d939', + '5cb1493608d4970a8444d93b', + '5cb1493708d4970a8444d93d', + '5cb1493708d4970a8444d93f', + '5cb1493708d4970a8444d941', + '5cb1493708d4970a8444d943', + '5cb1493808d4970a8444d945', + '5cb1493808d4970a8444d947', + '5cb1493808d4970a8444d949', + '5cb1493808d4970a8444d94b', + '5cb1493908d4970a8444d94d', + '5cb1493908d4970a8444d94f', + '5cb1493908d4970a8444d951', + '5cb1493a08d4970a8444d953', + '5cb1493a08d4970a8444d955', + '5cb1493a08d4970a8444d957', + '5cb1493a08d4970a8444d959', + '5cb1493b08d4970a8444d95b', + '5cb1493b08d4970a8444d95d', + '5cb1493b08d4970a8444d95f', + '5cb1493c08d4970a8444d961', + '5cb1493c08d4970a8444d963', + '5cb1493c08d4970a8444d965', + '5cb1493c08d4970a8444d967', + '5cb1493d08d4970a8444d969', + '5cb1493d08d4970a8444d96b', + '5cb1493d08d4970a8444d96d', + '5cb1493e08d4970a8444d96f', + '5cb1493f08d4970a8444d971', + '5cb1493f08d4970a8444d973', + '5cb1494008d4970a8444d975', + '5cb1494108d4970a8444d977', + '5cb1494108d4970a8444d979', + '5cb1494208d4970a8444d97b', + ], + carriersIds: [], + geoLocation: { + city: 'Ashdod', + streetAddress: 'HaAtsmaut', + house: '125', + __typename: 'GeoLocation', + }, + __typename: 'Warehouse', + barcodeData: '5cb1494208d4970a8444d97d', + }, + { + id: '5cb1494408d4970a8444d983', + _createdAt: '2018-05-13T19:26:00.000Z', + name: 'Dominex Pizza', + contactEmail: 'Lori_Morar@yahoo.com', + contactPhone: '990.782.8808', + logo: + 'https://fakeimg.pl/200x200/FFD890%2C128/000/?text=Pizza Pizza&font_size=75', + username: 'dominex_pizza', + usedCarriersIds: [ + '5cb1492408d4970a8444d8b5', + '5cb1492408d4970a8444d8b7', + '5cb1492408d4970a8444d8b9', + '5cb1492508d4970a8444d8bb', + '5cb1492508d4970a8444d8bd', + '5cb1492508d4970a8444d8bf', + '5cb1492508d4970a8444d8c1', + '5cb1492608d4970a8444d8c3', + '5cb1492608d4970a8444d8c5', + '5cb1492608d4970a8444d8c7', + '5cb1492708d4970a8444d8c9', + '5cb1492708d4970a8444d8cb', + '5cb1492708d4970a8444d8cd', + '5cb1492708d4970a8444d8cf', + '5cb1492808d4970a8444d8d1', + '5cb1492808d4970a8444d8d3', + '5cb1492808d4970a8444d8d5', + '5cb1492808d4970a8444d8d7', + '5cb1492908d4970a8444d8d9', + '5cb1492908d4970a8444d8db', + '5cb1492908d4970a8444d8dd', + '5cb1492a08d4970a8444d8df', + '5cb1492a08d4970a8444d8e1', + '5cb1492a08d4970a8444d8e3', + '5cb1492a08d4970a8444d8e5', + '5cb1492b08d4970a8444d8e7', + '5cb1492b08d4970a8444d8e9', + '5cb1492b08d4970a8444d8eb', + '5cb1492b08d4970a8444d8ed', + '5cb1492c08d4970a8444d8ef', + '5cb1492c08d4970a8444d8f1', + '5cb1492c08d4970a8444d8f3', + '5cb1492d08d4970a8444d8f5', + '5cb1492d08d4970a8444d8f7', + '5cb1492d08d4970a8444d8f9', + '5cb1492d08d4970a8444d8fb', + '5cb1492e08d4970a8444d8fd', + '5cb1492e08d4970a8444d8ff', + '5cb1492e08d4970a8444d901', + '5cb1492f08d4970a8444d903', + '5cb1492f08d4970a8444d905', + '5cb1492f08d4970a8444d907', + '5cb1492f08d4970a8444d909', + '5cb1493008d4970a8444d90b', + '5cb1493008d4970a8444d90d', + '5cb1493008d4970a8444d90f', + '5cb1493008d4970a8444d911', + '5cb1493108d4970a8444d913', + '5cb1493108d4970a8444d915', + '5cb1493108d4970a8444d917', + '5cb1493208d4970a8444d919', + '5cb1493208d4970a8444d91b', + '5cb1493208d4970a8444d91d', + '5cb1493208d4970a8444d91f', + '5cb1493308d4970a8444d921', + '5cb1493308d4970a8444d923', + '5cb1493308d4970a8444d925', + '5cb1493308d4970a8444d927', + '5cb1493408d4970a8444d929', + '5cb1493408d4970a8444d92b', + '5cb1493408d4970a8444d92d', + '5cb1493508d4970a8444d92f', + '5cb1493508d4970a8444d931', + '5cb1493508d4970a8444d933', + '5cb1493508d4970a8444d935', + '5cb1493608d4970a8444d937', + '5cb1493608d4970a8444d939', + '5cb1493608d4970a8444d93b', + '5cb1493708d4970a8444d93d', + '5cb1493708d4970a8444d93f', + '5cb1493708d4970a8444d941', + '5cb1493708d4970a8444d943', + '5cb1493808d4970a8444d945', + '5cb1493808d4970a8444d947', + '5cb1493808d4970a8444d949', + '5cb1493808d4970a8444d94b', + '5cb1493908d4970a8444d94d', + '5cb1493908d4970a8444d94f', + '5cb1493908d4970a8444d951', + '5cb1493a08d4970a8444d953', + '5cb1493a08d4970a8444d955', + '5cb1493a08d4970a8444d957', + '5cb1493a08d4970a8444d959', + '5cb1493b08d4970a8444d95b', + '5cb1493b08d4970a8444d95d', + '5cb1493b08d4970a8444d95f', + '5cb1493c08d4970a8444d961', + '5cb1493c08d4970a8444d963', + '5cb1493c08d4970a8444d965', + '5cb1493c08d4970a8444d967', + '5cb1493d08d4970a8444d969', + '5cb1493d08d4970a8444d96b', + '5cb1493d08d4970a8444d96d', + '5cb1493e08d4970a8444d96f', + '5cb1493f08d4970a8444d971', + '5cb1493f08d4970a8444d973', + '5cb1494008d4970a8444d975', + '5cb1494108d4970a8444d977', + '5cb1494108d4970a8444d979', + '5cb1494208d4970a8444d97b', + ], + carriersIds: [], + geoLocation: { + city: 'Ashdod', + streetAddress: 'HaAtsmaut', + house: '125', + __typename: 'GeoLocation', + }, + __typename: 'Warehouse', + barcodeData: '5cb1494408d4970a8444d983', + }, + { + id: '5cb1494308d4970a8444d97f', + _createdAt: '2017-04-01T04:12:00.000Z', + name: 'Pizza Hit', + contactEmail: 'Wilburn_Konopelski80@yahoo.com', + contactPhone: '641.266.1153 x733', + logo: + 'https://fakeimg.pl/200x200/FFD890%2C128/000/?text=Pizza Hit&font_size=75', + username: 'hut_pizza', + usedCarriersIds: [ + '5cb1492408d4970a8444d8b5', + '5cb1492408d4970a8444d8b7', + '5cb1492408d4970a8444d8b9', + '5cb1492508d4970a8444d8bb', + '5cb1492508d4970a8444d8bd', + '5cb1492508d4970a8444d8bf', + '5cb1492508d4970a8444d8c1', + '5cb1492608d4970a8444d8c3', + '5cb1492608d4970a8444d8c5', + '5cb1492608d4970a8444d8c7', + '5cb1492708d4970a8444d8c9', + '5cb1492708d4970a8444d8cb', + '5cb1492708d4970a8444d8cd', + '5cb1492708d4970a8444d8cf', + '5cb1492808d4970a8444d8d1', + '5cb1492808d4970a8444d8d3', + '5cb1492808d4970a8444d8d5', + '5cb1492808d4970a8444d8d7', + '5cb1492908d4970a8444d8d9', + '5cb1492908d4970a8444d8db', + '5cb1492908d4970a8444d8dd', + '5cb1492a08d4970a8444d8df', + '5cb1492a08d4970a8444d8e1', + '5cb1492a08d4970a8444d8e3', + '5cb1492a08d4970a8444d8e5', + '5cb1492b08d4970a8444d8e7', + '5cb1492b08d4970a8444d8e9', + '5cb1492b08d4970a8444d8eb', + '5cb1492b08d4970a8444d8ed', + '5cb1492c08d4970a8444d8ef', + '5cb1492c08d4970a8444d8f1', + '5cb1492c08d4970a8444d8f3', + '5cb1492d08d4970a8444d8f5', + '5cb1492d08d4970a8444d8f7', + '5cb1492d08d4970a8444d8f9', + '5cb1492d08d4970a8444d8fb', + '5cb1492e08d4970a8444d8fd', + '5cb1492e08d4970a8444d8ff', + '5cb1492e08d4970a8444d901', + '5cb1492f08d4970a8444d903', + '5cb1492f08d4970a8444d905', + '5cb1492f08d4970a8444d907', + '5cb1492f08d4970a8444d909', + '5cb1493008d4970a8444d90b', + '5cb1493008d4970a8444d90d', + '5cb1493008d4970a8444d90f', + '5cb1493008d4970a8444d911', + '5cb1493108d4970a8444d913', + '5cb1493108d4970a8444d915', + '5cb1493108d4970a8444d917', + '5cb1493208d4970a8444d919', + '5cb1493208d4970a8444d91b', + '5cb1493208d4970a8444d91d', + '5cb1493208d4970a8444d91f', + '5cb1493308d4970a8444d921', + '5cb1493308d4970a8444d923', + '5cb1493308d4970a8444d925', + '5cb1493308d4970a8444d927', + '5cb1493408d4970a8444d929', + '5cb1493408d4970a8444d92b', + '5cb1493408d4970a8444d92d', + '5cb1493508d4970a8444d92f', + '5cb1493508d4970a8444d931', + '5cb1493508d4970a8444d933', + '5cb1493508d4970a8444d935', + '5cb1493608d4970a8444d937', + '5cb1493608d4970a8444d939', + '5cb1493608d4970a8444d93b', + '5cb1493708d4970a8444d93d', + '5cb1493708d4970a8444d93f', + '5cb1493708d4970a8444d941', + '5cb1493708d4970a8444d943', + '5cb1493808d4970a8444d945', + '5cb1493808d4970a8444d947', + '5cb1493808d4970a8444d949', + '5cb1493808d4970a8444d94b', + '5cb1493908d4970a8444d94d', + '5cb1493908d4970a8444d94f', + '5cb1493908d4970a8444d951', + '5cb1493a08d4970a8444d953', + '5cb1493a08d4970a8444d955', + '5cb1493a08d4970a8444d957', + '5cb1493a08d4970a8444d959', + '5cb1493b08d4970a8444d95b', + '5cb1493b08d4970a8444d95d', + '5cb1493b08d4970a8444d95f', + '5cb1493c08d4970a8444d961', + '5cb1493c08d4970a8444d963', + '5cb1493c08d4970a8444d965', + '5cb1493c08d4970a8444d967', + '5cb1493d08d4970a8444d969', + '5cb1493d08d4970a8444d96b', + '5cb1493d08d4970a8444d96d', + '5cb1493e08d4970a8444d96f', + '5cb1493f08d4970a8444d971', + '5cb1493f08d4970a8444d973', + '5cb1494008d4970a8444d975', + '5cb1494108d4970a8444d977', + '5cb1494108d4970a8444d979', + '5cb1494208d4970a8444d97b', + ], + carriersIds: [], + geoLocation: { + city: 'Ashdod', + streetAddress: 'HaAtsmaut', + house: '125', + __typename: 'GeoLocation', + }, + __typename: 'Warehouse', + barcodeData: '5cb1494308d4970a8444d97f', + }, + { + id: '5cb1492108d4970a8444b566', + _createdAt: '2016-12-23T00:21:00.000Z', + name: 'Restaurant Leffler, Gislason and Williamson', + contactEmail: 'Elinor.Abernathy69@gmail.com', + contactPhone: '1-547-018-6580 x973', + logo: + 'https://fakeimg.pl/200x200/FFD890%2C128/000/?text=Leffler, Gislason and Williamson&font_size=75', + username: 'Wava_Kub', + usedCarriersIds: ['5cb1492108d4970a8444b564'], + carriersIds: [], + geoLocation: { + city: 'Violafurt', + streetAddress: '4642 Rempel Valleys', + house: '43', + __typename: 'GeoLocation', + }, + __typename: 'Warehouse', + barcodeData: '5cb1492108d4970a8444b566', + }, + { + id: '5cb1492108d4970a8444b56a', + _createdAt: '2014-08-16T20:08:00.000Z', + name: 'Restaurant Lynch, Mann and Roberts', + contactEmail: 'Gisselle91@yahoo.com', + contactPhone: '384.622.9808 x399', + logo: + 'https://fakeimg.pl/200x200/FFD890%2C128/000/?text=Lynch, Mann and Roberts&font_size=75', + username: 'Emmett80', + usedCarriersIds: ['5cb1492108d4970a8444b568'], + carriersIds: [], + geoLocation: { + city: 'Deckowmouth', + streetAddress: '0687 Salma Inlet', + house: '55', + __typename: 'GeoLocation', + }, + __typename: 'Warehouse', + barcodeData: '5cb1492108d4970a8444b56a', + }, + { + id: '5cb1492008d4970a8444b562', + _createdAt: '2013-06-30T10:02:00.000Z', + name: 'Restaurant Goyette, Ankunding and Hansen', + contactEmail: 'Demetrius_Barrows16@hotmail.com', + contactPhone: '(407) 305-1598 x4226', + logo: + 'https://fakeimg.pl/200x200/FFD890%2C128/000/?text=Goyette, Ankunding and Hansen&font_size=75', + username: 'Brisa.Runolfsdottir', + usedCarriersIds: ['5cb1492008d4970a8444b560'], + carriersIds: [], + geoLocation: { + city: 'Ritchieside', + streetAddress: '668 Bridie Islands', + house: '197', + __typename: 'GeoLocation', + }, + __typename: 'Warehouse', + barcodeData: '5cb1492008d4970a8444b562', + }, + { + id: '5cb1494408d4970a8444d981', + _createdAt: '2013-05-14T12:06:00.000Z', + name: 'Pizza Troya', + contactEmail: 'Corine11@hotmail.com', + contactPhone: '(769) 437-3162 x6846', + logo: + 'https://fakeimg.pl/200x200/FFD890%2C128/000/?text=Pizza Troya&font_size=75', + username: 'trova_pizza', + usedCarriersIds: [ + '5cb1492408d4970a8444d8b5', + '5cb1492408d4970a8444d8b7', + '5cb1492408d4970a8444d8b9', + '5cb1492508d4970a8444d8bb', + '5cb1492508d4970a8444d8bd', + '5cb1492508d4970a8444d8bf', + '5cb1492508d4970a8444d8c1', + '5cb1492608d4970a8444d8c3', + '5cb1492608d4970a8444d8c5', + '5cb1492608d4970a8444d8c7', + '5cb1492708d4970a8444d8c9', + '5cb1492708d4970a8444d8cb', + '5cb1492708d4970a8444d8cd', + '5cb1492708d4970a8444d8cf', + '5cb1492808d4970a8444d8d1', + '5cb1492808d4970a8444d8d3', + '5cb1492808d4970a8444d8d5', + '5cb1492808d4970a8444d8d7', + '5cb1492908d4970a8444d8d9', + '5cb1492908d4970a8444d8db', + '5cb1492908d4970a8444d8dd', + '5cb1492a08d4970a8444d8df', + '5cb1492a08d4970a8444d8e1', + '5cb1492a08d4970a8444d8e3', + '5cb1492a08d4970a8444d8e5', + '5cb1492b08d4970a8444d8e7', + '5cb1492b08d4970a8444d8e9', + '5cb1492b08d4970a8444d8eb', + '5cb1492b08d4970a8444d8ed', + '5cb1492c08d4970a8444d8ef', + '5cb1492c08d4970a8444d8f1', + '5cb1492c08d4970a8444d8f3', + '5cb1492d08d4970a8444d8f5', + '5cb1492d08d4970a8444d8f7', + '5cb1492d08d4970a8444d8f9', + '5cb1492d08d4970a8444d8fb', + '5cb1492e08d4970a8444d8fd', + '5cb1492e08d4970a8444d8ff', + '5cb1492e08d4970a8444d901', + '5cb1492f08d4970a8444d903', + '5cb1492f08d4970a8444d905', + '5cb1492f08d4970a8444d907', + '5cb1492f08d4970a8444d909', + '5cb1493008d4970a8444d90b', + '5cb1493008d4970a8444d90d', + '5cb1493008d4970a8444d90f', + '5cb1493008d4970a8444d911', + '5cb1493108d4970a8444d913', + '5cb1493108d4970a8444d915', + '5cb1493108d4970a8444d917', + '5cb1493208d4970a8444d919', + '5cb1493208d4970a8444d91b', + '5cb1493208d4970a8444d91d', + '5cb1493208d4970a8444d91f', + '5cb1493308d4970a8444d921', + '5cb1493308d4970a8444d923', + '5cb1493308d4970a8444d925', + '5cb1493308d4970a8444d927', + '5cb1493408d4970a8444d929', + '5cb1493408d4970a8444d92b', + '5cb1493408d4970a8444d92d', + '5cb1493508d4970a8444d92f', + '5cb1493508d4970a8444d931', + '5cb1493508d4970a8444d933', + '5cb1493508d4970a8444d935', + '5cb1493608d4970a8444d937', + '5cb1493608d4970a8444d939', + '5cb1493608d4970a8444d93b', + '5cb1493708d4970a8444d93d', + '5cb1493708d4970a8444d93f', + '5cb1493708d4970a8444d941', + '5cb1493708d4970a8444d943', + '5cb1493808d4970a8444d945', + '5cb1493808d4970a8444d947', + '5cb1493808d4970a8444d949', + '5cb1493808d4970a8444d94b', + '5cb1493908d4970a8444d94d', + '5cb1493908d4970a8444d94f', + '5cb1493908d4970a8444d951', + '5cb1493a08d4970a8444d953', + '5cb1493a08d4970a8444d955', + '5cb1493a08d4970a8444d957', + '5cb1493a08d4970a8444d959', + '5cb1493b08d4970a8444d95b', + '5cb1493b08d4970a8444d95d', + '5cb1493b08d4970a8444d95f', + '5cb1493c08d4970a8444d961', + '5cb1493c08d4970a8444d963', + '5cb1493c08d4970a8444d965', + '5cb1493c08d4970a8444d967', + '5cb1493d08d4970a8444d969', + '5cb1493d08d4970a8444d96b', + '5cb1493d08d4970a8444d96d', + '5cb1493e08d4970a8444d96f', + '5cb1493f08d4970a8444d971', + '5cb1493f08d4970a8444d973', + '5cb1494008d4970a8444d975', + '5cb1494108d4970a8444d977', + '5cb1494108d4970a8444d979', + '5cb1494208d4970a8444d97b', + ], + carriersIds: [], + geoLocation: { + city: 'Ashdod', + streetAddress: 'HaAtsmaut', + house: '128', + __typename: 'GeoLocation', + }, + __typename: 'Warehouse', + barcodeData: '5cb1494408d4970a8444d981', + }, +]; diff --git a/packages/admin-web-angular/src/app/pages/+device/device-mutation/device-mutation.component.html b/packages/admin-web-angular/src/app/pages/+device/device-mutation/device-mutation.component.html new file mode 100644 index 0000000..c834f43 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+device/device-mutation/device-mutation.component.html @@ -0,0 +1,72 @@ + + diff --git a/packages/admin-web-angular/src/app/pages/+device/device-mutation/device-mutation.component.ts b/packages/admin-web-angular/src/app/pages/+device/device-mutation/device-mutation.component.ts new file mode 100644 index 0000000..47faeaf --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+device/device-mutation/device-mutation.component.ts @@ -0,0 +1,79 @@ +import { Component, OnDestroy, OnInit } from '@angular/core'; +import Device from '@modules/server.common/entities/Device'; +import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; +import { FormBuilder, Validators } from '@angular/forms'; +import { DeviceService } from '../../../@core/data/device.service'; +import { first } from 'rxjs/operators'; +import { Subject } from 'rxjs'; +import { NotifyService } from '../../../@core/services/notify/notify.service'; + +@Component({ + selector: 'ea-device-mutation', + templateUrl: './device-mutation.component.html', +}) +export class DeviceMutationComponent implements OnDestroy, OnInit { + private ngDestroy$ = new Subject(); + public device: Device; + public loading: boolean; + + readonly form = this.fb.group({ + id: ['', Validators.required], + language: ['', Validators.required], + type: ['', Validators.required], + uuid: ['', Validators.required], + }); + + public id = this.form.get('id'); + public language = this.form.get('language'); + public type = this.form.get('type'); + public uuid = this.form.get('uuid'); + + constructor( + private readonly activeModal: NgbActiveModal, + private readonly fb: FormBuilder, + private readonly deviceService: DeviceService, + private readonly _notifyService: NotifyService + ) {} + + cancel() { + this.activeModal.dismiss('canceled'); + } + + async update() { + try { + this.loading = true; + const dev = await this.deviceService + .update(this.id.value, { + language: this.language.value, + type: this.type.value, + uuid: this.uuid.value, + }) + .pipe(first()) + .toPromise(); + this.loading = false; + const message = `Device was updated! BLQKS`; + this._notifyService.success(message); + } catch (error) { + let message = `Something went wrong`; + if (error.message === 'Validation error') { + message = error.message; + } + this.loading = false; + this._notifyService.error(message); + } + + this.cancel(); + } + + ngOnInit(): void { + this.id.setValue(this.device.id); + this.language.setValue(this.device.language); + this.type.setValue(this.device.type); + this.uuid.setValue(this.device.uuid); + } + + ngOnDestroy(): void { + this.ngDestroy$.next(); + this.ngDestroy$.complete(); + } +} diff --git a/packages/admin-web-angular/src/app/pages/+device/device.component.html b/packages/admin-web-angular/src/app/pages/+device/device.component.html new file mode 100644 index 0000000..2601284 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+device/device.component.html @@ -0,0 +1,37 @@ + + + +

{{ 'CUSTOMERS_VIEW.DEVICE.CUSTOMERS_DEVICES' | translate }}

+
+
+ + + +
+
+ +
+
+
+ + + + + +
diff --git a/packages/admin-web-angular/src/app/pages/+device/device.component.scss b/packages/admin-web-angular/src/app/pages/+device/device.component.scss new file mode 100644 index 0000000..4155bed --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+device/device.component.scss @@ -0,0 +1,46 @@ +nb-card-header { + border-bottom: 0; +} +.title { + text-align: left; +} +nb-card-body { + padding: 0; + border-bottom-left-radius: 0.5rem; + border-bottom-right-radius: 0.5rem; +} + +:host ::ng-deep ng2-smart-table { + tr.ng2-smart-titles > th:nth-child(1) { + text-align: center; + cursor: pointer; + + input { + margin-left: 5px !important; + cursor: pointer; + } + } + + td.ng2-smart-actions.ng2-smart-action-multiple-select { + text-align: center; + cursor: pointer; + } + + tr.ng2-smart-filters th { + text-align: center; + } + + tr td, + th { + &:first-of-type { + border-left: none; + } + &:last-of-type { + border-right: none; + } + } + + input[type='checkbox'] { + cursor: pointer; + } +} diff --git a/packages/admin-web-angular/src/app/pages/+device/device.component.ts b/packages/admin-web-angular/src/app/pages/+device/device.component.ts new file mode 100644 index 0000000..031f2df --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+device/device.component.ts @@ -0,0 +1,229 @@ +import { Component, OnDestroy, OnInit } from '@angular/core'; +import { DeviceService } from '../../@core/data/device.service'; +import Device from '@modules/server.common/entities/Device'; +import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; +import { DeviceMutationComponent } from './device-mutation/device-mutation.component'; +import { LocalDataSource } from 'ng2-smart-table'; +import { TranslateService } from '@ngx-translate/core'; +import { Observable, forkJoin, Subject } from 'rxjs'; +import { takeUntil, first } from 'rxjs/operators'; +import { NotifyService } from '../../@core/services/notify/notify.service'; +import { ConfirmationModalComponent } from '../../@shared/confirmation-modal/confirmation-modal.component'; + +@Component({ + selector: 'ea-device', + styleUrls: ['./device.component.scss'], + templateUrl: './device.component.html', +}) +export class DeviceComponent implements OnDestroy, OnInit { + private ngDestroy$ = new Subject(); + + public devices: Device[]; + private device$: any; + public _selectedDevices: Device[] = []; + + public loading: boolean; + + static noInfoSign = ''; + + public sourceSmartTable: LocalDataSource = new LocalDataSource(); + public settingsSmartTable: object; + + constructor( + private readonly _deviceService: DeviceService, + private readonly _modalService: NgbModal, + private readonly _translateService: TranslateService, + private readonly _notifyService: NotifyService + ) { + this._loadSmartTableSettings(); + this._setupDataForSmartTable(); + this._listenForEntityLocaleTranslate(); + this._applyTranslationOnSmartTable(); + } + + // Maybe "updateDevice" this function is not in use?! + updateDevice(device: Device) { + const activeModal = this._modalService.open(DeviceMutationComponent, { + size: 'lg', + container: 'nb-layout', + backdrop: 'static', + }); + const modalComponent: DeviceMutationComponent = + activeModal.componentInstance; + modalComponent.device = device; + } + + ngOnInit(): void {} + + edit(event) { + const activeModal = this._modalService.open(DeviceMutationComponent, { + size: 'lg', + container: 'nb-layout', + backdrop: 'static', + }); + const modalComponent: DeviceMutationComponent = + activeModal.componentInstance; + modalComponent.device = event.data; + modalComponent.loading = this.loading; + } + + async deleteSelectedDevices() { + const idsForDelete: string[] = this._selectedDevices.map((c) => c.id); + + try { + this.loading = true; + await this._deviceService + .removeByIds(idsForDelete) + .pipe(first()) + .toPromise(); + + this.loading = false; + const message = `${idsForDelete.length} devices was deleted`; + this._notifyService.success(message); + } catch (error) { + this.loading = false; + const message = `Something went wrong!`; + this._notifyService.error(message); + } + } + + get hasSelectedDevices(): boolean { + return this._selectedDevices.length > 0; + } + + selectProductTmp(ev) { + this._selectedDevices = ev.selected; + } + + get selectedProducts() { + return [...this._selectedDevices]; + } + + async deleteDevice(e) { + const activeModal = this._modalService.open(ConfirmationModalComponent, { + size: 'sm', + container: 'nb-layout', + backdrop: 'static', + }); + const modalComponent: ConfirmationModalComponent = + activeModal.componentInstance; + + await modalComponent.confirmEvent + .pipe(takeUntil(modalComponent.ngDestroy$)) + .subscribe((dataEvent) => { + const idsArray: any = []; + idsArray.push(e.data.id); + try { + this.loading = true; + this._deviceService + .removeByIds(idsArray) + .pipe() + .toPromise(); + this.loading = false; + const message = `${idsArray[0]} device was deleted`; + this._notifyService.success(message); + } catch (error) { + this.loading = false; + const message = `Something went wrong!`; + this._notifyService.error(message); + } + modalComponent.cancel(); + }); + } + + private _listenForEntityLocaleTranslate() { + this._translateService.onLangChange + .pipe(takeUntil(this.ngDestroy$)) + .subscribe(() => { + this._setupDataForSmartTable(); + this._loadSmartTableSettings(); + }); + } + + private _applyTranslationOnSmartTable() { + this._translateService.onLangChange.subscribe(() => { + this._loadSmartTableSettings(); + }); + } + + private _loadSmartTableSettings() { + const columnTitlePrefix = 'CUSTOMERS_VIEW.DEVICE.'; + const getTranslate = (name: string): Observable => + this._translateService.get(columnTitlePrefix + name); + + forkJoin( + getTranslate('ID'), + getTranslate('LANGUAGE'), + getTranslate('TYPE'), + getTranslate('UUID') + ) + .pipe(takeUntil(this.ngDestroy$)) + .subscribe(([id, language, type, uuid]) => { + this.settingsSmartTable = { + selectMode: 'multi', + actions: { + add: false, + position: 'left', + }, + edit: { + editButtonContent: '', + }, + delete: { + deleteButtonContent: '', + confirmDelete: true, + }, + mode: 'external', + hideSubHeader: true, + columns: { + id: { + title: id, + filter: false, + }, + language: { + title: language, + filter: false, + }, + type: { + title: type, + filter: false, + }, + uuid: { + title: uuid, + filter: false, + }, + }, + pager: { + display: true, + perPage: 5, + }, + }; + }); + } + + private _setupDataForSmartTable() { + this._deviceService + .getDevices() + .pipe(takeUntil(this.ngDestroy$)) + .subscribe((data: Device[]) => { + const devicesVm = data.map((c) => { + return { + id: c.id, + language: c.language || DeviceComponent.noInfoSign, + type: c.type || DeviceComponent.noInfoSign, + uuid: c.uuid || DeviceComponent.noInfoSign, + }; + }); + + this.sourceSmartTable.load(devicesVm); + }); + } + + ngOnDestroy(): void { + this.ngDestroy$.next(); + this.ngDestroy$.complete(); + + if (this.device$) { + this.device$.unsubscribe(); + } + } +} diff --git a/packages/admin-web-angular/src/app/pages/+device/device.module.ts b/packages/admin-web-angular/src/app/pages/+device/device.module.ts new file mode 100644 index 0000000..792550f --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+device/device.module.ts @@ -0,0 +1,41 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { ThemeModule } from '../../@theme/theme.module'; +import { RouterModule, Routes } from '@angular/router'; +import { DeviceComponent } from './device.component'; +import { DeviceMutationComponent } from './device-mutation/device-mutation.component'; +import { TranslateModule } from '@ngx-translate/core'; +import { Ng2SmartTableModule } from 'ng2-smart-table'; +import { ToasterModule } from 'angular2-toaster'; +import { NbSpinnerModule, NbButtonModule } from '@nebular/theme'; +import { NotifyService } from '@app/@core/services/notify/notify.service'; +import { ConfirmationModalModule } from '../../@shared/confirmation-modal/confirmation-modal.module'; + +const routes: Routes = [ + { + path: '', + component: DeviceComponent, + }, +]; + +@NgModule({ + imports: [ + CommonModule, + ThemeModule, + Ng2SmartTableModule, + NbSpinnerModule, + ConfirmationModalModule, + ToasterModule.forRoot(), + TranslateModule.forChild(), + RouterModule.forChild(routes), + NbButtonModule, + ], + providers: [NotifyService], + declarations: [DeviceComponent, DeviceMutationComponent], + exports: [] +}) +export class DeviceModule { + public static routes = routes; + + constructor() {} +} diff --git a/packages/admin-web-angular/src/app/pages/+device/device.stories.ts b/packages/admin-web-angular/src/app/pages/+device/device.stories.ts new file mode 100644 index 0000000..268e3f3 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+device/device.stories.ts @@ -0,0 +1,72 @@ +import { storiesOf, moduleMetadata } from '@storybook/angular'; +import { withKnobs } from '@storybook/addon-knobs'; +import { DeviceComponent } from './device.component'; +import { ThemeModule } from '@app/@theme'; +import { CommonModule } from '@angular/common'; +import { Ng2SmartTableModule } from 'ng2-smart-table'; +import { NbSpinnerModule } from '@nebular/theme'; +import { ConfirmationModalModule } from '@app/@shared/confirmation-modal/confirmation-modal.module'; +import { ToasterModule } from 'angular2-toaster'; +import { TranslateStore, TranslateService } from '@ngx-translate/core'; +import { RouterModule } from '@angular/router'; +import { routes, NbAuthModule } from '@nebular/auth'; +import { NotifyService } from '@app/@core/services/notify/notify.service'; +import { DeviceMutationComponent } from './device-mutation/device-mutation.component'; +import { DeviceService } from '@app/@core/data/device.service'; +import { APOLLO_OPTIONS } from 'apollo-angular'; +import { HttpLink } from 'apollo-angular/http'; +import { InMemoryCache } from '@apollo/client/core'; +import { PipesModule } from '@modules/client.common.angular2/pipes/pipes.module'; +import { HttpClient, HttpClientModule } from '@angular/common/http'; +import { TranslateHttpLoader } from '@ngx-translate/http-loader'; +import { I18nModule } from '@app/@core/utils/i18n.module'; + +const stories = storiesOf('Device Component', module); + +export function createApollo(httpLink: HttpLink) { + return { + link: httpLink.create({ uri: 'https://api.example.com/graphql' }), + cache: new InMemoryCache(), + }; +} + +export function createTranslateLoader(http: HttpClient) { + return new TranslateHttpLoader(http, '/i18n/', '.json'); +} + +stories.addDecorator(withKnobs); +stories.addDecorator( + moduleMetadata({ + declarations: [DeviceComponent, DeviceMutationComponent], + imports: [ + CommonModule, + ThemeModule, + Ng2SmartTableModule, + NbSpinnerModule, + ConfirmationModalModule, + ToasterModule.forRoot(), + HttpClientModule, + I18nModule, + RouterModule.forChild(routes), + NbAuthModule, + PipesModule, + ], + providers: [ + DeviceService, + { + provide: APOLLO_OPTIONS, + useFactory: createApollo, + deps: [HttpLink], + }, + TranslateStore, + TranslateService, + NotifyService, + HttpLink, + ], + }) +); + +stories.add('Device', () => ({ + component: DeviceComponent, + props: {}, +})); diff --git a/packages/admin-web-angular/src/app/pages/+fakeData/fakeData.component.html b/packages/admin-web-angular/src/app/pages/+fakeData/fakeData.component.html new file mode 100644 index 0000000..2b1a0b7 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+fakeData/fakeData.component.html @@ -0,0 +1,569 @@ + + + + +

{{ 'FAKE_DATA.FAKE_DATA_GENERATOR' | translate }}

+
+ + +
+
+
+ + + +
+
+ +
+
+ +
+
+
+
+
+ + + {{ + 'FAKE_DATA.HARDCODED_DATA' | translate + }} + + +
+
+
+ +
+
+
+
+
+ + + {{ + 'FAKE_DATA.CREATE_INVITE' | translate + }} + + +
+
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+
+
+ + + + {{ 'FAKE_DATA.CREATE_USER' | translate }} + + +
+
+
+ +
+
+ +
+
+ +
+
+
+
+
+ + + + {{ 'FAKE_DATA.CREATE_CARRIER' | translate }} + + +
+
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+
+
+ + + {{ + 'FAKE_DATA.CREATE_PRODUCT' | translate + }} + + +
+
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+
+
+ + + + {{ 'FAKE_DATA.CREATE_WAREHOUSE' | translate }} + + + +
+
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+
+
+ + + {{ 'FAKE_DATA.CREATE_WAREHOUSE_PRODUCT' | translate }} + + + +
+
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+
+
+ + + {{ 'FAKE_DATA.UPDATE_WAREHOUSE_GEO_LOCATION' | translate }} + + + +
+
+
+ +
+
+
+
+
+ + + {{ 'FAKE_DATA.CREATE_ORDER' | translate }} + + + +
+
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+
+
+ + + + {{ 'FAKE_DATA.CONFIRM_ORDER' | translate }} + + + +
+
+
+ +
+
+ +
+
+ +
+
+
+
+
diff --git a/packages/admin-web-angular/src/app/pages/+fakeData/fakeData.component.scss b/packages/admin-web-angular/src/app/pages/+fakeData/fakeData.component.scss new file mode 100644 index 0000000..8556940 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+fakeData/fakeData.component.scss @@ -0,0 +1,24 @@ +.btn-startProcessing { + color: #333; + background-color: #fff; + border-color: #ccc; +} + +.btn-startProcessing:hover { + color: rgb(24, 22, 22); + background-color: rgb(241, 239, 239); + border-color: rgb(182, 176, 176); + cursor: pointer; +} + +.fakeData { + padding: 10px; +} + +input[type='checkbox'] { + zoom: 1.3; +} + +.btn { + padding: 0.625rem 1.125rem !important; +} diff --git a/packages/admin-web-angular/src/app/pages/+fakeData/fakeData.component.ts b/packages/admin-web-angular/src/app/pages/+fakeData/fakeData.component.ts new file mode 100644 index 0000000..ecc9adf --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+fakeData/fakeData.component.ts @@ -0,0 +1,1176 @@ +import { Component, OnDestroy, OnInit } from '@angular/core'; +import Invite from '@modules/server.common/entities/Invite'; +import { IWarehouseProductCreateObject } from '@modules/server.common/interfaces/IWarehouseProduct'; +import { InviteRouter } from '@modules/client.common.angular2/routers/invite-router.service'; +import { CarrierRouter } from '@modules/client.common.angular2/routers/carrier-router.service'; +import { UserRouter } from '@modules/client.common.angular2/routers/user-router.service'; +import { ProductRouter } from '@modules/client.common.angular2/routers/product-router.service'; +import { WarehouseRouter } from '@modules/client.common.angular2/routers/warehouse-router.service'; +import Product from '@modules/server.common/entities/Product'; +import { WarehouseProductsRouter } from '@modules/client.common.angular2/routers/warehouse-products-router.service'; +import Warehouse from '@modules/server.common/entities/Warehouse'; +import User from '@modules/server.common/entities/User'; +import { WarehouseOrdersRouter } from '@modules/client.common.angular2/routers/warehouse-orders-router.service'; +import { OrderRouter } from '@modules/client.common.angular2/routers/order-router.service'; +import Order from '@modules/server.common/entities/Order'; +import Carrier from '@modules/server.common/entities/Carrier'; +import { FakeDataBtnState } from '../../models/FakeDataBtnState'; +import FakeDataCarriers from '../../@core/data/fakeDataServices/carriers'; +import FakeDataInvites from '../../@core/data/fakeDataServices/invites'; +import FakeDataProducts from '../../@core/data/fakeDataServices/products'; +import FakeDataWarehouses from '../../@core/data/fakeDataServices/warehouses'; +import FakeDataWarehousesProducts from '../../@core/data/fakeDataServices/warehousesProducts'; +import FakeDataUsers from '../../@core/data/fakeDataServices/users'; +import { UserAuthRouter } from '@modules/client.common.angular2/routers/user-auth-router.service'; +import { CarriersService } from '../../@core/data/carriers.service'; +import { Subject } from 'rxjs'; +import { ToasterService } from 'angular2-toaster'; +import { first, takeUntil } from 'rxjs/operators'; +import { WarehousesService } from '../../@core/data/warehouses.service'; +import { DataService } from '../../@core/data/data.service'; +import ProductsCategory from '@modules/server.common/entities/ProductsCategory'; +import { ProductsCategoryService } from '../../@core/data/productsCategory.service'; +import FakeDataProductsCategories from '../../@core/data/fakeDataServices/productsCategories'; +import { + IProductsCategoryName, + IProductsCategory, +} from '@modules/server.common/interfaces/IProductsCategory'; +import { NotifyService } from '@app/@core/services/notify/notify.service'; +import { OrdersService } from '@app/@core/data/orders.service'; +import { ICarrierRegistrationInput } from '@modules/server.common/routers/ICarrierRouter'; +import { InvitesService } from '@app/@core/data/invites.service'; +import { InvitesRequestsService } from '@app/@core/data/invites-requests.service'; +import { UsersService } from '@app/@core/data/users.service'; +import { environment } from 'environments/environment'; +import { random }from 'underscore'; +import { CurrenciesService } from '@app/@core/data/currencies.service'; + +const NEED_DEFAULT_SETTINGS_MESSAGE = + "Can't generate fake data without DEFAULT_LONGITUDE and DEFAULT_LATITUDE"; +const lng = environment['DEFAULT_LONGITUDE']; +const lat = environment['DEFAULT_LATITUDE']; + +@Component({ + selector: 'ea-fake-data', + templateUrl: './fakeData.component.html', + styleUrls: ['./fakeData.component.scss'], +}) +export class FakeDataComponent implements OnInit, OnDestroy { + invite: Invite | null = null; + peperoniAndMushroomPizzaProduct: Product; + sushiAndCaviarMixProduct: Product; + sushiMixProduct: Product; + pastaProduct: Product; + sushiBoxProduct: Product; + peperoniAndTomatoPizzaProduct: Product; + warehouse1: Warehouse; + warehouse2: Warehouse; + warehouse3: Warehouse; + user: User; + order1: Order; + order2: Order; + includeHardcodedData: boolean = true; + + public loading: FakeDataBtnState; + public isBtnDisabled: FakeDataBtnState; + + private _existingWarehouses: Warehouse[] = []; + private _ngDestroy$ = new Subject(); + + constructor( + protected fakeDataWarehousesProducts: FakeDataWarehousesProducts, + protected fakeDataWarehouses: FakeDataWarehouses, + protected fakeDataInvites: FakeDataInvites, + protected fakeDataUsers: FakeDataUsers, + protected fakeDataCarriers: FakeDataCarriers, + private readonly _fakeDataProductsCategories: FakeDataProductsCategories, + protected carrierRouter: CarrierRouter, + protected userRouter: UserRouter, + protected userAuthRouter: UserAuthRouter, + protected fakeDataProducts: FakeDataProducts, + protected productRouter: ProductRouter, + protected warehouseRouter: WarehouseRouter, + protected toasterService: ToasterService, + protected inviteRouter: InviteRouter, + protected warehouseProductsRouter: WarehouseProductsRouter, + protected warehouseOrdersRouter: WarehouseOrdersRouter, + protected orderRouter: OrderRouter, + private readonly _carriersService: CarriersService, + private readonly _warehousesService: WarehousesService, + private readonly _dataService: DataService, + private readonly _productsCategoriesService: ProductsCategoryService, + private readonly _ordersService: OrdersService, + private readonly _invitesService: InvitesService, + private readonly _inviteRequestsService: InvitesRequestsService, + private readonly _notifyService: NotifyService, + private readonly _usersService: UsersService, + private readonly _currenciesService: CurrenciesService + ) { + this._setupButtonStatuses(); + this._setupButtonLoading(); + } + + ngOnInit() { + this._listenForExistingWarehouses(); + } + + private get _hasProducts() { + return ( + this.peperoniAndMushroomPizzaProduct && + this.sushiAndCaviarMixProduct && + this.sushiMixProduct && + this.pastaProduct && + this.sushiBoxProduct && + this.peperoniAndTomatoPizzaProduct + ); + } + + private get _notify() { + const showMessage = (message: string) => + this._notifyService.success(message); + const errror = (message: string) => this._notifyService.error(message); + + return { + invite: (id) => + showMessage(`Invite with id "${id}" created successfully`), + user: (id) => + showMessage(`User with id "${id}" created successfully`), + carrier: (id) => + showMessage(`Carrier with id "${id}" created successfully`), + product: (id) => + showMessage(`Product with id "${id}" created successfully`), + warehouse: (id) => + showMessage(`Warehouse with id "${id}" created successfully`), + warehouseAddProducts: (id) => + showMessage( + `Warehouse with id "${id}" added products successfully` + ), + geoLocation: (id) => + showMessage(`Warehouse with id "${id}" update geo location`), + order: (id) => + showMessage(`Order with id "${id}" created successfully`), + confirmOrder: (id) => + showMessage(`Order with id "${id}" confirmed`), + clearAll: () => showMessage('All data was removed from database'), + generateRandomOrdersPerStore: (storeId, ordersCount) => + showMessage( + `Store with id "${storeId}" has new ${ordersCount} orders.` + ), + errorGenerate: (msg) => errror(msg), + }; + } + + async callAll() { + this.isBtnDisabled.all = true; + this.loading.all = true; + + if (!this.includeHardcodedData) { + await this._generateCurrencies(); + } + + await this.createInvite1(); + await this.createInvite2(); + await this.createInvite3(); + await this.createInvite4(); + await this._generate1000InvitesConnectedToInviteRequests(); + await this._generate1000InviteRequests(); + await this.createUser(); + await this.createCarrier1(); + await this.createCarrier2(); + await this.createCarrier3(); + await this._generateProducts(); + await this.createWarehouse1(); + await this.createWarehouse2(); + await this.createWarehouse3(); + await this.createWarehouse1Products(); + await this.createWarehouse2Products(); + await this.createWarehouse3Products(); + await this.updateWarehouse1GeoLocation(); + await this.createOrder1(); + await this.createOrder2(); + await this.confirmOrder1(); + await this.confirmOrder2(); + await this._generate1000Customers(); + + if (this.includeHardcodedData === true) { + await this.generateHardcoded(); + } + + await this.generate100CustomersStoresCarriers(); + await this._generageRangeCustomers(300); + await this.generateRandomOrdersPerStore(); + + await this._generate1000Customers(); + + if (this.includeHardcodedData === false) { + await this._generateRandomOrdersPerCarrier(); + } + + this.isBtnDisabled.all = false; + this.loading.all = false; + } + + async generate100CustomersStoresCarriers() { + await this._generateCustomerEntities(); + } + + async generateHardcoded() { + this.loading.hardcoded = true; + this.isBtnDisabled.hardcoded = true; + + await this._generateCurrencies(); + + await this._createHardcodedInvites(); + await this._createHardcodedWarehouses(); + await this._generateProducts(); + await this._createWarehouseProductsForHardcodedWarehouses(); + await this._createHardcodedCarriers(); + + await this._generateRandomOrdersPerCarrier(); + + this.loading.hardcoded = false; + this.isBtnDisabled.hardcoded = false; + } + + async clearAll() { + if (window.confirm('Are you sure?')) { + this.isBtnDisabled.clearAll = true; + this.loading.clearAll = true; + await this._dataService.clearAll(); + this.isBtnDisabled.clearAll = false; + this.loading.clearAll = false; + + this._notify.clearAll(); + } + } + + async createInvite1() { + this.loading.invite1 = true; + this.isBtnDisabled.invite1 = true; + await this._createInvite(); + this.loading.invite1 = false; + this.isBtnDisabled.invite1 = false; + } + + async createInvite2() { + this.loading.invite2 = true; + this.isBtnDisabled.invite2 = true; + await this._createInvite(); + this.loading.invite2 = false; + this.isBtnDisabled.invite2 = false; + } + + async createInvite3() { + this.loading.invite3 = true; + this.isBtnDisabled.invite3 = true; + await this._createInvite(); + this.loading.invite3 = false; + this.isBtnDisabled.invite3 = false; + } + + async createInvite4() { + this.loading.invite4 = true; + this.isBtnDisabled.invite4 = true; + await this._createInvite(); + this.loading.invite4 = false; + this.isBtnDisabled.invite4 = false; + } + + async generate100Customers() { + this.loading.users100 = true; + this.isBtnDisabled.users100 = true; + + let userNumber = 1; + + const create = async () => { + const userRegisterInput = this.fakeDataUsers.getUserRegistrationInput(); + const user: User = await this.userAuthRouter.register( + userRegisterInput + ); + this._notify.user(user.id); + + if (userNumber <= 100) { + userNumber += 1; + await create(); + } + }; + + await create(); + this.loading.users100 = false; + this.isBtnDisabled.users100 = false; + } + + async createUser() { + this.isBtnDisabled.user = true; + this.loading.user = true; + + if (this.invite === null) { + await this._createInvite(); + } + + this.user = await this.userAuthRouter.register({ + user: { + geoLocation: this.invite.geoLocation, + apartment: this.invite.apartment, + }, + }); + + this.isBtnDisabled.user = false; + this.loading.user = false; + this._notify.user(this.user.id); + } + + private _hardcodedCarrierIds: string[] = []; + + async createCarrier1() { + this.isBtnDisabled.carrier1 = true; + this.loading.carrier1 = true; + + await this._createCarrier(); + this.loading.carrier1 = false; + + this.isBtnDisabled.carrier1 = false; + } + + async createCarrier2() { + this.loading.carrier2 = true; + this.isBtnDisabled.carrier2 = true; + + await this._createCarrier(); + this.loading.carrier2 = false; + this.isBtnDisabled.carrier2 = false; + } + + async createCarrier3() { + this.loading.carrier3 = true; + this.isBtnDisabled.carrier3 = true; + + await this._createCarrier(); + this.loading.carrier3 = false; + this.isBtnDisabled.carrier3 = false; + } + + async generate100Carriers() { + this.loading.carriers100 = true; + this.isBtnDisabled.carriers100 = true; + + await this._generate100Carriers(); + + this.loading.carriers100 = false; + this.isBtnDisabled.carriers100 = false; + } + + // PRODUCT CATEGORIES + private _productCategories: Array<{ + _id: string; + name: Array<{ locale: string; value: string }>; + _createdAt: string | Date; + _updatedAt: string | Date; + }> = []; + + async createPeperoniAndMushroomPizzaProduct( + categories?: IProductsCategory[] + ) { + this.isBtnDisabled.product1 = true; + this.loading.product1 = true; + + const productCreateObject = await this.fakeDataProducts.getPeperoniAndMushroomPizzaCreateObject( + categories + ); + + this.peperoniAndMushroomPizzaProduct = await this.productRouter.create( + productCreateObject + ); + + this.isBtnDisabled.product1 = false; + this.loading.product1 = false; + + this._notify.product(this.peperoniAndMushroomPizzaProduct.id); + } + + async createSushiAndCaviarMixProduct(categories?: IProductsCategory[]) { + this.isBtnDisabled.product2 = true; + this.loading.product2 = true; + + const productCreateObject = await this.fakeDataProducts.getSushiAndCaviarMixCreateObject( + categories + ); + this.sushiAndCaviarMixProduct = await this.productRouter.create( + productCreateObject + ); + + this.isBtnDisabled.product2 = false; + this.loading.product2 = false; + this._notify.product(this.sushiAndCaviarMixProduct.id); + } + + async createSushiMixProduct(categories?: IProductsCategory[]) { + this.isBtnDisabled.product3 = true; + this.loading.product3 = true; + + const productCreateObject = await this.fakeDataProducts.getSushiMixCreateObject( + categories + ); + this.sushiMixProduct = await this.productRouter.create( + productCreateObject + ); + + this.isBtnDisabled.product3 = false; + this.loading.product3 = false; + this._notify.product(this.sushiMixProduct.id); + } + + async createPastaProduct(categories?: IProductsCategory[]) { + this.isBtnDisabled.product4 = true; + this.loading.product4 = true; + + const productCreateObject = await this.fakeDataProducts.getPastaCreateObject( + categories + ); + this.pastaProduct = await this.productRouter.create( + productCreateObject + ); + + this.isBtnDisabled.product4 = false; + this.loading.product4 = false; + this._notify.product(this.pastaProduct.id); + } + + async createSushiBoxProduct(categories?: IProductsCategory[]) { + this.isBtnDisabled.product5 = true; + this.loading.product5 = true; + + const productCreateObject = await this.fakeDataProducts.getSushiBoxCreateObject( + categories + ); + this.sushiBoxProduct = await this.productRouter.create( + productCreateObject + ); + + this.isBtnDisabled.product5 = false; + this.loading.product5 = false; + this._notify.product(this.sushiBoxProduct.id); + } + + async createPeperoniAndTomatoPizzaProduct(categories?: IProductsCategory[]) { + this.isBtnDisabled.product6 = true; + this.loading.product6 = true; + + const productCreateObject = await this.fakeDataProducts.getPeperoniAndTomatoPizzaCreateObject( + categories + ); + this.peperoniAndTomatoPizzaProduct = await this.productRouter.create( + productCreateObject + ); + + this.isBtnDisabled.product6 = false; + this.loading.product6 = false; + this._notify.product(this.peperoniAndTomatoPizzaProduct.id); + } + + async generate100Warehouses() { + this.isBtnDisabled.warehouse100 = true; + this.loading.warehouse100 = true; + + let warehouseCount = 0; + + const create = async () => { + const warehouseRegisterInput = this.fakeDataWarehouses.registrationInputs.generate(); + const warehouse: Warehouse = await this.warehouseRouter.register( + warehouseRegisterInput + ); + + this._notify.warehouse(warehouse.id); + if (warehouseCount <= 100) { + warehouseCount += 1; + await create(); + } + }; + + await create(); + + this.isBtnDisabled.warehouse100 = false; + this.loading.warehouse100 = false; + } + + async createWarehouse1() { + this.isBtnDisabled.warehouse1 = true; + this.loading.warehouse1 = true; + this.warehouse1 = await this._createWarehouseWithCarrier(); + this.isBtnDisabled.warehouse1 = false; + this.loading.warehouse1 = false; + } + + async createWarehouse2() { + this.isBtnDisabled.warehouse2 = true; + this.loading.warehouse2 = true; + this.warehouse2 = await this._createWarehouseWithCarrier(); + this.isBtnDisabled.warehouse2 = false; + this.loading.warehouse2 = false; + } + + async createWarehouse3() { + this.isBtnDisabled.warehouse3 = true; + this.loading.warehouse3 = true; + this.warehouse3 = await this._createWarehouseWithCarrier(); + this.isBtnDisabled.warehouse3 = false; + this.loading.warehouse3 = false; + } + + private _hardcodedWarehouses: Warehouse[] = []; + + async createWarehouse1Products() { + this.isBtnDisabled.warehouseProduct1 = true; + this.loading.warehouseProduct1 = true; + + const products: Product[] = [ + this.peperoniAndMushroomPizzaProduct, + this.sushiAndCaviarMixProduct, + this.sushiMixProduct, + this.pastaProduct, + this.sushiBoxProduct, + this.peperoniAndTomatoPizzaProduct, + ]; + await this._createWarehouseProducts( + this.warehouse1.id, + products.map((p) => p.id) + ); + + this.isBtnDisabled.warehouseProduct1 = false; + this.loading.warehouseProduct1 = false; + } + + async createWarehouse2Products() { + this.isBtnDisabled.warehouseProduct2 = true; + this.loading.warehouseProduct2 = true; + + const products: Product[] = [ + this.peperoniAndMushroomPizzaProduct, + this.sushiAndCaviarMixProduct, + this.sushiMixProduct, + ]; + await this._createWarehouseProducts( + this.warehouse2.id, + products.map((p) => p.id) + ); + + this.isBtnDisabled.warehouseProduct2 = false; + this.loading.warehouseProduct2 = false; + } + + async createWarehouse3Products() { + this.isBtnDisabled.warehouseProduct3 = true; + this.loading.warehouseProduct3 = true; + + this._createWarehouseProducts(this.warehouse3.id, [ + this.sushiBoxProduct.id, + ]); + + this.isBtnDisabled.warehouseProduct3 = false; + this.loading.warehouseProduct3 = false; + } + + async updateWarehouse1GeoLocation() { + this.isBtnDisabled.warehouseGeoLocation = true; + this.loading.warehouseGeoLocation = true; + + const newGeoLocation = this.fakeDataWarehouses.getNewGeoLocation1(); + await this.warehouseRouter.updateGeoLocation( + this.warehouse1.id, + newGeoLocation + ); + + this.isBtnDisabled.warehouseGeoLocation = false; + this.loading.warehouseGeoLocation = false; + this._notify.geoLocation(this.warehouse1.id); + } + + async createOrder1() { + this.isBtnDisabled.createOrder1 = true; + this.loading.createOrder1 = true; + + this.order1 = await this._createOrder( + this.user.id, + this.warehouse1.id, + this.peperoniAndMushroomPizzaProduct.id + ); + + this.isBtnDisabled.createOrder1 = false; + this.loading.createOrder1 = false; + } + + async confirmOrder1() { + this.isBtnDisabled.confirmOrder1 = true; + this.loading.confirmOrder1 = true; + + await this.orderRouter.confirm(this.order1.id); + + this.isBtnDisabled.confirmOrder1 = false; + this.loading.confirmOrder1 = false; + this._notify.confirmOrder(this.order1.id); + } + + async createOrder2() { + this.isBtnDisabled.createOrder2 = true; + this.loading.createOrder2 = true; + + this.order2 = await this._createOrder( + this.user.id, + this.warehouse1.id, + this.sushiBoxProduct.id + ); + + this.isBtnDisabled.createOrder2 = false; + this.loading.createOrder2 = false; + } + + async confirmOrder2() { + this.isBtnDisabled.confirmOrder2 = true; + this.loading.confirmOrder2 = true; + + await this.orderRouter.confirm(this.order2.id); + + this.isBtnDisabled.confirmOrder2 = false; + this.loading.confirmOrder2 = false; + this._notify.confirmOrder(this.order2.id); + } + + async generateRandomOrdersPerStore(ordersLimit: number = 1000) { + this.isBtnDisabled.create1000Orders = true; + this.loading.create1000Orders = true; + + let stores: Warehouse[] = await this._warehousesService + .getAllStores() + .toPromise(); + + for (let store of stores) { + let storeId = store.id; + let storeCreatedAt = new Date(store._createdAt); + + const response = await this._ordersService + .generateRandomOrdersCurrentStore( + storeId, + storeCreatedAt, + ordersLimit + ) + .toPromise(); + + if (response.error) { + this._notify.errorGenerate(response.message); + } else { + this._notify.generateRandomOrdersPerStore(storeId, ordersLimit); + } + } + + this.isBtnDisabled.create1000Orders = false; + this.loading.create1000Orders = false; + } + + private async _createOrder( + userId: string, + warehouseId: string, + productId: string + ): Promise { + const order: Order = await this.warehouseOrdersRouter.createByProductType( + userId, + warehouseId, + productId + ); + + this._notify.order(order.id); + + return order; + } + + private _setupButtonLoading() { + this.isBtnDisabled = new FakeDataBtnState(); + } + + private _setupButtonStatuses() { + this.loading = new FakeDataBtnState(); + } + + private async _generate1000Customers() { + if (lng && lat) { + const response = await this._usersService // maybe _usersService? + .generate1000Customers(lng, lat) + .toPromise(); + + if (!response.success) { + this._notify.errorGenerate(response.message); + } + } else { + console.warn(NEED_DEFAULT_SETTINGS_MESSAGE); + } + } + + private async _generate1000InviteRequests() { + if (lng && lat) { + await this._inviteRequestsService + .generate1000InviteRequests(lng, lat) + .toPromise(); + } else { + console.warn(NEED_DEFAULT_SETTINGS_MESSAGE); + } + } + + private async _generate1000InvitesConnectedToInviteRequests() { + if (lng && lat) { + await this._invitesService + .generate1000InvitesConnectedToInviteRequests(lng, lat) + .toPromise(); + } else { + console.warn(NEED_DEFAULT_SETTINGS_MESSAGE); + } + } + + private async _generateRandomOrdersPerCarrier() { + await this._generateActiveAndAvailableOrdersPerCarrier(); + await this._generatePastOrdersPerCarrier(); + } + + private async _generateActiveAndAvailableOrdersPerCarrier() { + await this._ordersService + .generateActiveAndAvailableOrdersPerCarrier() + .toPromise(); + } + + private async _generatePastOrdersPerCarrier() { + await this._ordersService.generatePastOrdersPerCarrier().toPromise(); + } + + private async _generageRangeCustomers(count: number) { + for (let i = 0; i < count; i += 1) { + await this._createRandomUser(); + } + } + + private async _createHardcodedInvites() { + for (const objKey of ['a', 'b', 'c', 'd']) { + const objToCreate = this.fakeDataInvites.getHardcodedCreateObject[ + objKey + ]; + + const createdObject = await this.inviteRouter.create(objToCreate); + + this._notify.invite(createdObject.id); + } + } + + private async _createInvite() { + const inviteCreateObject = this.fakeDataInvites.getCreateObject(); + + this.invite = await this.inviteRouter.create(inviteCreateObject); + + this._notify.invite(this.invite.id); + } + + private async _isUserEmailExists(email: string): Promise { + return this._usersService.isUserEmailExists(email); + } + + private async _createHardcodedCarriers() { + for (const objKey of ['josh', 'tom', 'mike']) { + const objToRegister = this.fakeDataCarriers.registrationInputs[ + objKey + ]; + + if ( + await this._isCarrierUsernameExists( + objToRegister.carrier.username + ) + ) { + return; + } + + const createdObject = await this.carrierRouter.register( + objToRegister + ); + + const carrierId = createdObject.id; + + this._hardcodedCarrierIds.push(carrierId); + this._notify.carrier(carrierId); + } + + await this._generageRangeCustomers(15); + await this._addOrdersToTake(); + await this._addTakenOrders(); + } + + private async _addOrdersToTake() { + if (this._hardcodedCarrierIds.length > 0) { + await this._ordersService.addOrdersToTake().toPromise(); + } + } + + private async _addTakenOrders() { + if (this._hardcodedCarrierIds.length > 0) { + await this._ordersService + .addTakenOrders(this._hardcodedCarrierIds) + .toPromise(); + } + } + + private async _createCarrier(): Promise { + const carrierRegisterInput = this.fakeDataCarriers.registrationInputs.generate(); + + // If our fake library generate twice the same username we try again + if ( + await this._isCarrierUsernameExists( + carrierRegisterInput.carrier.username + ) + ) { + return this._createCarrier(); + } + + const carrier: Carrier = await this.carrierRouter.register( + carrierRegisterInput + ); + + this._notify.carrier(carrier.id); + return carrier; + } + + private async _generate100Carriers(): Promise { + const rawCarriers: ICarrierRegistrationInput[] = []; + const carriersUsernames = []; + + for (let i = 1; i <= 100; i += 1) { + const carrierRegisterInput = this.fakeDataCarriers.registrationInputs.generate(); + const carrierUsername = carrierRegisterInput.carrier.username; + + if (!carriersUsernames.includes(carrierUsername)) { + carriersUsernames.push(carrierUsername); + rawCarriers.push(carrierRegisterInput); + } + } + + const carriers: Carrier[] = []; + + for (const raw of rawCarriers) { + const carrier: Carrier = await this.carrierRouter.register(raw); + carriers.push(carrier); + this._notify.carrier(carrier.id); + } + + return carriers; + } + + private async _generateCustomerEntities() { + let userNumber = 1; + + const create = async () => { + await this._createRandomUserWithOrder(); + + if (userNumber <= 90) { + userNumber += 1; + await create(); + } + }; + + await create(); + } + + private async _isCarrierUsernameExists(username: string) { + const carrierUsername = await this._carriersService + .getCarrierByUsername(username) + .pipe(first()) + .toPromise(); + return carrierUsername !== null; + } + + private async _createHardcodedWarehouses() { + const carriers: Carrier[] = await this._generate100Carriers(); + const carrierIds = carriers.map((c) => c.id); + + for (const objKey of [ + 'pizzaRestaurant', + 'pizzaHit', + 'pizzaTroya', + 'dominexPizza', + ]) { + const objToRegister = this.fakeDataWarehouses.registrationInputs[ + objKey + ]; + + // We don't want warehouses with the same usernames. + if ( + this._existingWarehouses.some( + (w: Warehouse) => + w.username === objToRegister.warehouse.username + ) + ) { + return; + } + + carrierIds.forEach((carrierId) => { + objToRegister.warehouse.usedCarriersIds.push(carrierId); + }); + + const createdObject = await this.warehouseRouter.register( + objToRegister + ); + + this._hardcodedWarehouses.push(createdObject); + + this._notify.warehouse(createdObject.id); + } + } + + private async _createWarehouseWithCarrier(): Promise { + const warehouseRegisterInput = this.fakeDataWarehouses.registrationInputs.generate(); + + const carrier: Carrier = await this._createCarrier(); + warehouseRegisterInput.warehouse.usedCarriersIds.push(carrier.id); + + const warehouse: Warehouse = await this.warehouseRouter.register( + warehouseRegisterInput + ); + + this._notify.warehouse(warehouse.id); + return warehouse; + } + + private async _createRandomUser() { + const userRegisterInput = this.fakeDataUsers.getUserRegistrationInput(); + const isUserEmailExists = await this._isUserEmailExists( + userRegisterInput.user.email + ); + + if (isUserEmailExists) { + return this._createRandomUser(); + } else { + const user: User = await this.userAuthRouter.register( + userRegisterInput + ); + return user; + } + } + + private async _createRandomUserWithOrder() { + const user: User = await this._createRandomUser(); + const warehouse: Warehouse = await this._createWarehouseWithCarrier(); + + const p1: Product = await this._createProduct(); + const p2: Product = await this._createProduct(); + + await this._createWarehouseProducts(warehouse.id, [p1.id, p2.id]); + + await this._createOrder(user.id, warehouse.id, p1.id); + await this._createOrder(user.id, warehouse.id, p2.id); + + this._notify.user(user.id); + } + + private async _generateProductCategories() { + if (this._productCategories.length === 0) { + this._productCategories = (await this.generateCategories()).map( + (c) => { + return { + _id: c.id, + name: c.name.map((n) => { + return { + locale: n.locale, + value: n.value, + }; + }), + _createdAt: c._createdAt, + _updatedAt: c._updatedAt, + }; + } + ); + } + } + + private async _createProduct(): Promise { + await this._generateProductCategories(); + + const getRandomCategory = () => { + return this._productCategories[ + random(this._productCategories.length - 1) + ]; + }; + const productCreateObject = await this.fakeDataProducts.getRandomProduct( + [getRandomCategory()] + ); + + const product: Product = await this.productRouter.create( + productCreateObject + ); + + this._notify.product(product.id); + + return product; + } + + private async _generateProducts() { + await this._generateProductCategories(); + + const filter = (predicate: (val: IProductsCategoryName) => boolean) => { + return this._productCategories.filter((c) => + c.name.some(predicate) + ); + }; + + const categoriesPizza = filter( + (cName) => cName.value.toLowerCase() === 'pizza' + ); + const categoriesSushi = filter( + (cName) => cName.value.toLowerCase() === 'sushi' + ); + const categoriesPasta = filter( + (cName) => cName.value.toLowerCase() === 'pasta' + ); + + await this.createPeperoniAndMushroomPizzaProduct(categoriesPizza); + await this.createSushiAndCaviarMixProduct(categoriesSushi); + await this.createSushiMixProduct(categoriesSushi); + await this.createPastaProduct(categoriesPasta); + await this.createSushiBoxProduct(categoriesSushi); + await this.createPeperoniAndTomatoPizzaProduct(categoriesPizza); + } + + private async generateCategories(): Promise { + const create: (inputObject: any) => Promise = async ( + inputObject + ) => { + return this._productsCategoriesService + .create(inputObject) + .toPromise(); + }; + + let resultCategories = await this._productsCategoriesService + .getCategories() + .pipe(first()) + .toPromise(); + + if (resultCategories.length === 0) { + const rawCategories = this._fakeDataProductsCategories.getDifferentKindsOfCategories(); + + const categoryPizza = await create(rawCategories.pizza); + + const categorySushi = await create(rawCategories.sushi); + + const categoryBurger = await create(rawCategories.burger); + + const categoryVegetarian = await create(rawCategories.vegetarian); + + const categorySalads = await create(rawCategories.salads); + + const categoryDessert = await create(rawCategories.dessert); + + const categoryDrinks = await create(rawCategories.drinks); + + const categoryMeatDishes = await create(rawCategories.meatDishes); + + const categorySoups = await create(rawCategories.soups); + + const categoryAlcohol = await create(rawCategories.alcohol); + + const categoryFastFood = await create(rawCategories.fastFood); + + const categoryPasta = await create(rawCategories.pasta); + + resultCategories = [ + categoryPizza, + categorySushi, + categoryBurger, + categoryVegetarian, + categorySalads, + categoryDessert, + categoryDrinks, + categoryMeatDishes, + categorySoups, + categoryAlcohol, + categoryFastFood, + categoryPasta, + ]; + } + + return resultCategories; + } + + private _listenForExistingWarehouses() { + this._warehousesService + .getStores() + .pipe(takeUntil(this._ngDestroy$)) + .subscribe((existingWarehouses) => { + this._existingWarehouses = existingWarehouses; + }); + } + + private async _createWarehouseProducts( + warehouseId: string, + productIds: string[] + ) { + const createObjects: IWarehouseProductCreateObject[] = productIds.map( + (id: string) => this.fakeDataWarehousesProducts.getCreateObject(id) + ); + + await this.warehouseProductsRouter.add(warehouseId, createObjects); + + this._notify.warehouseAddProducts(warehouseId); + } + + private async _createWarehouseProductsForHardcodedWarehouses() { + const warehouseProductCreateObjects: IWarehouseProductCreateObject[] = this.fakeDataWarehousesProducts.getHardcodedCreateObject( + [ + this.peperoniAndMushroomPizzaProduct.id, + this.peperoniAndTomatoPizzaProduct.id, + this.sushiAndCaviarMixProduct.id, + this.sushiMixProduct.id, + this.pastaProduct.id, + this.sushiBoxProduct.id, + ] + ); + + const moreWarehouseProducts = await this._generateProductsForWarehouseProducts(); + + for (const w of this._hardcodedWarehouses) { + await this.warehouseProductsRouter.add( + w.id, + warehouseProductCreateObjects.concat(moreWarehouseProducts) + ); + } + } + + private async _generateProductsForWarehouseProducts( + productsLimit = 150 + ): Promise { + const warehouseProductCreateObjects: IWarehouseProductCreateObject[] = []; + + for (let i = 1; i <= productsLimit; i += 1) { + const product = await this._createProduct(); + + const warehouseProductCreateObject = this.fakeDataWarehousesProducts.getCreateObject( + product.id + ); + + warehouseProductCreateObjects.push(warehouseProductCreateObject); + } + + return warehouseProductCreateObjects; + } + + private async _generateCurrencies() { + const currenciesCodes = ['USD', 'ILS', 'EUR', 'BGN', 'RUB', 'UYU']; + + for (const currencyCode of currenciesCodes) { + const res = await this._currenciesService + .create({ currencyCode }) + .pipe(first()) + .toPromise(); + + this.toasterService.pop( + res.success ? 'success' : 'warning', + res.message + ); + } + } + + ngOnDestroy() { + this._ngDestroy$.next(); + this._ngDestroy$.complete(); + } +} diff --git a/packages/admin-web-angular/src/app/pages/+fakeData/fakeData.module.guard.ts b/packages/admin-web-angular/src/app/pages/+fakeData/fakeData.module.guard.ts new file mode 100644 index 0000000..a537ae6 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+fakeData/fakeData.module.guard.ts @@ -0,0 +1,30 @@ +import { Injectable } from '@angular/core'; +import { + CanActivate, + ActivatedRouteSnapshot, + Router, + RouterStateSnapshot, +} from '@angular/router'; +import { Store } from '@app/@core/data/store.service'; + +@Injectable() +export class FakeDataModuleGuard implements CanActivate { + constructor( + private readonly router: Router, + private readonly store: Store + ) {} + + canActivate( + route: ActivatedRouteSnapshot, + state: RouterStateSnapshot + ): boolean { + const fakeDataGenerator = !!+this.store.fakeDataGenerator; + + if (!fakeDataGenerator) { + this.router.navigate(['/']); + return false; + } + + return true; + } +} diff --git a/packages/admin-web-angular/src/app/pages/+fakeData/fakeData.module.ts b/packages/admin-web-angular/src/app/pages/+fakeData/fakeData.module.ts new file mode 100644 index 0000000..90abd28 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+fakeData/fakeData.module.ts @@ -0,0 +1,67 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { ThemeModule } from '../../@theme/theme.module'; +import { RouterModule } from '@angular/router'; +import { routes } from './fakeData.routes'; +import { FakeDataComponent } from './fakeData.component'; +import FakeDataInvites from '../../@core/data/fakeDataServices/invites'; +import FakeDataCarriers from '../../@core/data/fakeDataServices/carriers'; +import FakeDataProducts from '../../@core/data/fakeDataServices/products'; +import FakeDataWarehouses from '../../@core/data/fakeDataServices/warehouses'; +import FakeDataWarehousesProducts from '../../@core/data/fakeDataServices/warehousesProducts'; +import FakeDataUsers from '../../@core/data/fakeDataServices/users'; +import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; +import { HttpClient } from '@angular/common/http'; +import { TranslateHttpLoader } from '@ngx-translate/http-loader'; +import { ToasterModule } from 'angular2-toaster'; +import FakeDataProductsCategories from '../../@core/data/fakeDataServices/productsCategories'; +import { NbSpinnerModule, NbButtonModule } from '@nebular/theme'; +import { NotifyService } from '@app/@core/services/notify/notify.service'; +import { InvitesService } from '@app/@core/data/invites.service'; +import { InvitesRequestsService } from '@app/@core/data/invites-requests.service'; +import { UsersService } from '@app/@core/data/users.service'; +import { CurrenciesService } from '@app/@core/data/currencies.service'; + +export function HttpLoaderFactory(http: HttpClient) { + return new TranslateHttpLoader(http, './assets/i18n/', '.json'); +} + +@NgModule({ + imports: [ + CommonModule, + ThemeModule, + ToasterModule.forRoot(), + TranslateModule.forChild({ + loader: { + provide: TranslateLoader, + useFactory: HttpLoaderFactory, + deps: [HttpClient], + }, + }), + RouterModule.forChild(routes), + NbSpinnerModule, + NbButtonModule, + ], + declarations: [FakeDataComponent], + providers: [ + FakeDataInvites, + FakeDataCarriers, + FakeDataProducts, + FakeDataWarehouses, + FakeDataWarehousesProducts, + FakeDataUsers, + FakeDataProductsCategories, + InvitesService, + InvitesRequestsService, + UsersService, + NotifyService, + CurrenciesService, + ], +}) +export class FakeDataModule { + public static routes = routes; + + constructor() { + console.log('`FakeData` module initialized'); + } +} diff --git a/packages/admin-web-angular/src/app/pages/+fakeData/fakeData.routes.ts b/packages/admin-web-angular/src/app/pages/+fakeData/fakeData.routes.ts new file mode 100644 index 0000000..e857d30 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+fakeData/fakeData.routes.ts @@ -0,0 +1,9 @@ +import { Routes } from '@angular/router'; +import { FakeDataComponent } from './fakeData.component'; + +export const routes: Routes = [ + { + path: '', + component: FakeDataComponent, + }, +]; diff --git a/packages/admin-web-angular/src/app/pages/+fakeData/index.ts b/packages/admin-web-angular/src/app/pages/+fakeData/index.ts new file mode 100644 index 0000000..be56c2d --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+fakeData/index.ts @@ -0,0 +1 @@ +export * from './fakeData.module'; diff --git a/packages/admin-web-angular/src/app/pages/+maintenance-info/maintenance-info.component.html b/packages/admin-web-angular/src/app/pages/+maintenance-info/maintenance-info.component.html new file mode 100644 index 0000000..b2dab78 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+maintenance-info/maintenance-info.component.html @@ -0,0 +1,8 @@ + + + + diff --git a/packages/admin-web-angular/src/app/pages/+maintenance-info/maintenance-info.component.ts b/packages/admin-web-angular/src/app/pages/+maintenance-info/maintenance-info.component.ts new file mode 100644 index 0000000..3d87886 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+maintenance-info/maintenance-info.component.ts @@ -0,0 +1,46 @@ +import { Component, OnDestroy } from '@angular/core'; +import { MaintenanceService } from '@modules/client.common.angular2/services/maintenance.service'; +import { environment } from 'environments/environment'; +import { Router } from '@angular/router'; +import { Store } from '@app/@core/data/store.service'; + +@Component({ + templateUrl: './maintenance-info.component.html', +}) +export class MaintenanceInfoComponent { + public message: string; + private maintenanceMode: string; + + constructor( + private maintenanceService: MaintenanceService, + private router: Router, + private store: Store + ) { + this.maintenanceMode = this.store.maintenanceMode; + this.getMessage(); + this.getStatus(); + } + + async getMessage() { + this.message = await this.maintenanceService.getMessage( + this.maintenanceMode, + environment['SETTINGS_MAINTENANCE_API_URL'] + ); + } + + private async getStatus() { + const interval = setInterval(async () => { + const status = await this.maintenanceService.getStatus( + environment['SETTINGS_APP_TYPE'], + environment['SETTINGS_MAINTENANCE_API_URL'] + ); + console.warn(`Maintenance on '${this.maintenanceMode}': ${status}`); + + if (!status) { + clearInterval(interval); + this.store.clearMaintenanceMode(); + this.router.navigate(['']); + } + }, 5000); + } +} diff --git a/packages/admin-web-angular/src/app/pages/+maintenance-info/maintenance-info.module.guard.ts b/packages/admin-web-angular/src/app/pages/+maintenance-info/maintenance-info.module.guard.ts new file mode 100644 index 0000000..bcaf92a --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+maintenance-info/maintenance-info.module.guard.ts @@ -0,0 +1,26 @@ +import { Injectable } from '@angular/core'; +import { + Router, + CanActivate, + ActivatedRouteSnapshot, + RouterStateSnapshot, +} from '@angular/router'; +import { Store } from '@app/@core/data/store.service'; + +@Injectable() +export class MaintenanceModuleGuard implements CanActivate { + constructor(private readonly router: Router, private store: Store) {} + + canActivate( + route: ActivatedRouteSnapshot, + state: RouterStateSnapshot + ): boolean { + const maintenanceMode = this.store.maintenanceMode; + + if (!maintenanceMode) { + this.router.navigate(['']); + return false; + } + return true; + } +} diff --git a/packages/admin-web-angular/src/app/pages/+maintenance-info/maintenance-info.module.ts b/packages/admin-web-angular/src/app/pages/+maintenance-info/maintenance-info.module.ts new file mode 100644 index 0000000..aa588f3 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+maintenance-info/maintenance-info.module.ts @@ -0,0 +1,27 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { Routes, RouterModule } from '@angular/router'; + +import { MaintenanceService } from '@modules/client.common.angular2/services/maintenance.service'; +import { MaintenanceInfoComponent } from './maintenance-info.component'; +import { PipesModule } from '@modules/client.common.angular2/pipes/pipes.module'; +import { ThemeModule } from '@app/@theme'; + +const routes: Routes = [ + { + path: '', + component: MaintenanceInfoComponent, + }, +]; + +@NgModule({ + imports: [ + CommonModule, + RouterModule.forChild(routes), + PipesModule, + ThemeModule, + ], + providers: [MaintenanceService], + declarations: [MaintenanceInfoComponent], +}) +export class MaintenanceInfoModule {} diff --git a/packages/admin-web-angular/src/app/pages/+orders/+order/order-products/order-products.component.html b/packages/admin-web-angular/src/app/pages/+orders/+order/order-products/order-products.component.html new file mode 100644 index 0000000..16ad5ab --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+orders/+order/order-products/order-products.component.html @@ -0,0 +1,106 @@ +
+ + + +
+

+ {{ + 'ORDER_VIEW.ORDER_PRODUCT_INFO.ORDER_PRODUCTS' + | translate + }} +

+
+
+
+
+ + +
+
+ +
+
+ +

+ {{ + 'ORDER_VIEW.ORDER_PRODUCT_INFO.THE_ORDER_IS_CANCELED' + | translate + }} +

+

+ {{ + 'ORDER_VIEW.ORDER_PRODUCT_INFO.THE_ORDER_IS_GIVEN_TO_CARRIER' + | translate + }} +

+

+ {{ + 'ORDER_VIEW.ORDER_PRODUCT_INFO.THE_ORDER_IS_DELIVERED' + | translate + }} +

+
+ + + + + +
+
diff --git a/packages/admin-web-angular/src/app/pages/+orders/+order/order-products/order-products.component.scss b/packages/admin-web-angular/src/app/pages/+orders/+order/order-products/order-products.component.scss new file mode 100644 index 0000000..e11e9c8 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+orders/+order/order-products/order-products.component.scss @@ -0,0 +1,17 @@ +:host ::ng-deep .order-product ng2-smart-table { + .ng2-smart-th.qty input { + text-align: center; + } + + .ng2-smart-titles.ng-star-inserted:first-child { + text-align: center; + } +} + +.order-product { + .break-line { + border-bottom: 1px solid #ebeef2; + margin-top: 1.2rem; + margin-bottom: 1.25rem; + } +} diff --git a/packages/admin-web-angular/src/app/pages/+orders/+order/order-products/order-products.component.ts b/packages/admin-web-angular/src/app/pages/+orders/+order/order-products/order-products.component.ts new file mode 100644 index 0000000..26b64ed --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+orders/+order/order-products/order-products.component.ts @@ -0,0 +1,303 @@ +import { Component, Input, OnInit, OnChanges, OnDestroy } from '@angular/core'; +import { LocalDataSource } from 'ng2-smart-table'; +import { Subject, forkJoin, Observable } from 'rxjs'; +import Order from '@modules/server.common/entities/Order'; +import OrderProduct from '@modules/server.common/entities/OrderProduct'; +import { ProductLocalesService } from '@modules/client.common.angular2/locale/product-locales.service'; +import Product from '@modules/server.common/entities/Product'; +import { WarehouseOrdersRouter } from '@modules/client.common.angular2/routers/warehouse-orders-router.service'; +import OrderWarehouseStatus from '@modules/server.common/enums/OrderWarehouseStatus'; +import OrderCarrierStatus from '@modules/server.common/enums/OrderCarrierStatus'; +import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; +import { first, takeUntil } from 'rxjs/operators'; +import { OrderRouter } from '@modules/client.common.angular2/routers/order-router.service'; +import { Router } from '@angular/router'; +import { TranslateService } from '@ngx-translate/core'; +import { ToasterService } from 'angular2-toaster'; +import { ConfirmationModalComponent } from '../../../../@shared/confirmation-modal/confirmation-modal.component'; +import { StoreOrderProductAmountComponent } from '../../../../@shared/render-component/store-products-table/store-order-product-amount/store-order-product-amount.component'; +import { ProductTitleRedirectComponent } from '../../../../@shared/render-component/product-title-redirect/product-title-redirect.component'; +import { StoreProductPriceComponent } from '../../../../@shared/render-component/store-products-table/store-product-price.component'; +import { StoreProductImageComponent } from '../../../../@shared/render-component/store-products-table/store-product-image/store-product-image.component'; +import { WarehouseOrderModalComponent } from '../../../../@shared/warehouse/+warehouse-order-modal/warehouse-order-modal.component'; + +interface OrderProductsViewModel { + price: string; + qty: string; + product: Product; + image: string; +} + +@Component({ + selector: 'ea-order-products', + templateUrl: './order-products.component.html', + styleUrls: ['./order-products.component.scss'], +}) +export class OrderProductsComponent implements OnInit, OnChanges, OnDestroy { + public selectedProducts: OrderProductsViewModel[] = []; + + public sourceSmartTable: LocalDataSource = new LocalDataSource(); + public settingsSmartTable: object; + private _ngDestroy$ = new Subject(); + + public modalTitle: string; + public actionBtnText: string; + public toastSuccMsg: string; + public toastErrMsg: string; + + public loading: boolean; + + @Input() + public order: Order; + + constructor( + private readonly _productLocalesService: ProductLocalesService, + private readonly warehouseOrdersRouter: WarehouseOrdersRouter, + private readonly modalService: NgbModal, + private readonly orderRouter: OrderRouter, + private readonly _translateService: TranslateService, + private readonly router: Router, + private readonly _toasterService: ToasterService + ) { + this._loadSmartTableSettings(); + } + + public get givenToCarrier() { + return OrderWarehouseStatus.GivenToCarrier; + } + + public get deliveryCompleted() { + return OrderCarrierStatus.DeliveryCompleted; + } + + ngOnInit(): void { + this.loadDataSmartTable(); + this._applyTranslationOnSmartTable(); + } + + ngOnChanges(): void { + this.loadDataSmartTable(); + } + + async loadDataSmartTable() { + const loadData = () => { + if (this.order) { + console.error(); + + const productsVM = this.order.products.map( + (product: OrderProduct) => { + return { + id: product.product.id, + price: product.price, + qty: product.count, + product: product.product, + comment: product.comment, + image: this._productLocalesService.getTranslate( + product.product['images'] + ), + disableImg: true, + orderId: this.order.id, + orderWarehouseId: this.order.warehouseId, + warehouseProducts: this.order.warehouse['products'], + }; + } + ); + + this.sourceSmartTable.load(productsVM); + } + }; + + loadData(); + } + + selectProductTmp(ev) { + this.selectedProducts = ev.selected; + } + + addProductsModalTranslates() { + const columnTitlePrefix = 'ORDER_VIEW.ORDER_PRODUCT_INFO.'; + const getTranslatedWords = (name: string): Observable => + this._translateService.get(columnTitlePrefix + name); + + forkJoin( + getTranslatedWords('ADD_PRODUCTS_MODAL'), + getTranslatedWords('ADD'), + getTranslatedWords('SUCCESS_TOAST'), + getTranslatedWords('ERROR_TOAST') + ) + .pipe(takeUntil(this._ngDestroy$)) + .subscribe(([addProduct, add, successToast, errorToast]) => { + this.actionBtnText = add; + this.modalTitle = addProduct; + this.toastSuccMsg = successToast; + this.toastErrMsg = errorToast; + }); + } + + async addProducts() { + if (this.order) { + const componentRef = this.modalService.open( + WarehouseOrderModalComponent, + { + size: 'lg', + container: 'nb-layout', + windowClass: 'ng-custom', + backdrop: 'static', + } + ); + const instance: WarehouseOrderModalComponent = + componentRef.componentInstance; + + instance.warehouseId = this.order.warehouseId; + instance.modalTitle = this.modalTitle; + instance.actionBtnText = this.actionBtnText; + + const products = await instance.makeOrderEmitter + .pipe(first()) + .toPromise(); + + this.orderRouter + .addProducts(this.order.id, products, this.order.warehouseId) + .then((r) => { + this._toasterService.pop( + `success`, + `${this.toastSuccMsg}!` + ); + }) + .catch((err) => { + this._toasterService.pop(`error`, `${this.toastErrMsg}!`); + }); + + componentRef.close(); + } + } + + async removeSelectedProducts() { + const productsIds = this.selectedProducts.map((res) => res['id']); + if (this.order && productsIds.length > 0) { + try { + this.loading = true; + const order = await this.orderRouter.removeProducts( + this.order.id, + productsIds + ); + + this.selectedProducts = []; + this.loading = false; + this._toasterService.pop( + `success`, + `Selected products are deleted!` + ); + } catch (error) { + this.loading = false; + this._toasterService.pop('error', `Error: "${error.message}"`); + } + } + } + + async cancelOrder() { + const activeModal = this.modalService.open(ConfirmationModalComponent, { + size: 'sm', + container: 'nb-layout', + backdrop: 'static', + }); + const modalComponent: ConfirmationModalComponent = + activeModal.componentInstance; + + await modalComponent.confirmEvent + .pipe(takeUntil(modalComponent.ngDestroy$)) + .subscribe((dataEvent) => { + if (this.order) { + try { + this.loading = true; + const res = this.warehouseOrdersRouter.cancel( + this.order.id + ); + this.loading = false; + this._toasterService.pop( + `success`, + `Order is canceled!` + ); + } catch (error) { + this.loading = false; + this._toasterService.pop( + 'error', + `Error: "${error.message}"` + ); + } + // const res = await this.warehouseOrdersRouter.cancel(this.order.id); + } + modalComponent.cancel(); + }); + } + + private _loadSmartTableSettings() { + const columnTitlePrefix = 'ORDER_VIEW.ORDER_PRODUCT_INFO.SMART_TABLE.'; + const getTranslate = (name: string): Observable => + this._translateService.get(columnTitlePrefix + name); + + forkJoin( + this._translateService.get('Id'), + getTranslate('IMAGE'), + getTranslate('NAME'), + getTranslate('QTY'), + getTranslate('PRICE'), + getTranslate('COMMENT') + ) + .pipe(takeUntil(this._ngDestroy$)) + .subscribe(([id, image, name, qty, price, comment]) => { + this.settingsSmartTable = { + actions: false, + selectMode: 'multi', + columns: { + name: { + title: name, + renderComponent: ProductTitleRedirectComponent, + type: 'custom', + }, + comment: { + title: comment, + width: '15%', + }, + qty: { + title: qty, + class: 'text-center', + type: 'custom', + width: '15%', + renderComponent: StoreOrderProductAmountComponent, + }, + price: { + title: price, + type: 'custom', + width: '20%', + renderComponent: StoreProductPriceComponent, + }, + image: { + title: image, + type: 'custom', + class: 'text-center', + renderComponent: StoreProductImageComponent, + filter: false, + }, + }, + pager: { + display: true, + perPage: 5, + }, + }; + }); + } + + private _applyTranslationOnSmartTable() { + this.addProductsModalTranslates(); + this._translateService.onLangChange.subscribe(() => { + this._loadSmartTableSettings(); + this.addProductsModalTranslates(); + }); + } + + ngOnDestroy() { + this._ngDestroy$.next(); + this._ngDestroy$.complete(); + } +} diff --git a/packages/admin-web-angular/src/app/pages/+orders/+order/order-products/order-products.module.ts b/packages/admin-web-angular/src/app/pages/+orders/+order/order-products/order-products.module.ts new file mode 100644 index 0000000..620fd29 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+orders/+order/order-products/order-products.module.ts @@ -0,0 +1,31 @@ +import { NgModule } from '@angular/core'; +import { ThemeModule } from '@app/@theme'; +import { ToasterModule } from 'angular2-toaster'; +import { CommonModule } from '@angular/common'; +import { OrderProductsComponent } from './order-products.component'; +import { Ng2SmartTableModule } from 'ng2-smart-table'; +import { RenderComponentsModule } from '@app/@shared/render-component/render-components.module'; +import { StoreProductsTableModule } from '@app/@shared/render-component/store-products-table/store-products-table.module'; +import { WarehouseOrderModalModule } from '@app/@shared/warehouse/+warehouse-order-modal/warehouse-order-modal.module'; +import { TranslateModule } from '@ngx-translate/core'; +import { NbSpinnerModule, NbButtonModule } from '@nebular/theme'; +import { ConfirmationModalModule } from '@app/@shared/confirmation-modal/confirmation-modal.module'; + +@NgModule({ + imports: [ + CommonModule, + ThemeModule, + ToasterModule.forRoot(), + Ng2SmartTableModule, + TranslateModule.forChild(), + RenderComponentsModule, + StoreProductsTableModule, + WarehouseOrderModalModule, + NbSpinnerModule, + ConfirmationModalModule, + NbButtonModule, + ], + declarations: [OrderProductsComponent], + exports: [OrderProductsComponent], +}) +export class OrderProductsModule {} diff --git a/packages/admin-web-angular/src/app/pages/+orders/+order/order.component.html b/packages/admin-web-angular/src/app/pages/+orders/+order/order.component.html new file mode 100644 index 0000000..4f6d695 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+orders/+order/order.component.html @@ -0,0 +1,99 @@ +
+ + +
+ + +

+ {{ 'ORDER_VIEW.ORDER_HEADER_INFO.ORDER' | translate }} + {{ getOrderName(order) }} + {{ + order.createdAt | date: 'MM-dd-yy' + }} +

+
+ + +

+ {{ + 'ORDER_VIEW.ORDER_HEADER_INFO.MANAGE_ORDER' | translate + }} +

+

+ {{ 'ORDER_VIEW.ORDER_HEADER_INFO.TOTAL' | translate }} ${{ + getTotalPrice(order) + }} +

+
+
+ + + + +
+
+ + + + + + + + + +

{{ 'ORDER_VIEW.LOCATION_INFO.MAP' | translate }}

+
+ + +
+
+ {{ + 'ORDER_VIEW.LOCATION_INFO.DELIVERY_DISTANCE' + | translate + }} + {{ + getDistance( + order?.user?.geoLocation, + order?.warehouse?.geoLocation + ) + }} +
+
+
+ + +
+
+
+
+
+
diff --git a/packages/admin-web-angular/src/app/pages/+orders/+order/order.component.scss b/packages/admin-web-angular/src/app/pages/+orders/+order/order.component.scss new file mode 100644 index 0000000..04b27a1 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+orders/+order/order.component.scss @@ -0,0 +1,19 @@ +.order-page { + .order-map { + height: 25rem; + .sub-title { + height: 10%; + + :first-child { + width: 100%; + text-align: right; + margin: 0; + padding: 0 1.25rem; + } + } + + .map { + height: 90%; + } + } +} diff --git a/packages/admin-web-angular/src/app/pages/+orders/+order/order.component.ts b/packages/admin-web-angular/src/app/pages/+orders/+order/order.component.ts new file mode 100644 index 0000000..1e7e266 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+orders/+order/order.component.ts @@ -0,0 +1,196 @@ +import { Component, OnDestroy } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; +import { takeUntil } from 'rxjs/operators'; +import Order from '@modules/server.common/entities/Order'; +import { Subject } from 'rxjs'; +import Warehouse from '@modules/server.common/entities/Warehouse'; +import User from '@modules/server.common/entities/User'; +import { OrderRouter } from '@modules/client.common.angular2/routers/order-router.service'; +import Carrier from '@modules/server.common/entities/Carrier'; +import OrderCarrierStatus from '@modules/server.common/enums/OrderCarrierStatus'; +import OrderProduct from '@modules/server.common/entities/OrderProduct'; +import GeoLocation from '@modules/server.common/entities/GeoLocation'; +import { TranslateService } from '@ngx-translate/core'; +import { getIdFromTheDate } from '@modules/server.common/utils'; + +const service = new google.maps.DistanceMatrixService(); + +@Component({ + selector: 'ea-order', + templateUrl: './order.component.html', + styleUrls: ['./order.component.scss'], +}) +export class OrderComponent implements OnDestroy { + public orderId: string; + public order$; + public distance: string; + + public PREFIX: string = 'ORDER_VIEW.ORDER_SIDEBAR.'; + public WAREHOUSE_TITLE: string = 'WAREHOUSE'; + public CUSTOMER_TITLE: string = 'CUSTOMER'; + public CARRIER_TITLE: string = 'CARRIER'; + + private ngDestroy$ = new Subject(); + + constructor( + private readonly _router: ActivatedRoute, + private readonly orderRouter: OrderRouter, + private readonly translateService: TranslateService + ) { + const id = this._router.snapshot.params.id; + + this.order$ = this.orderRouter + .get(id, { populateWarehouse: true, populateCarrier: true }) + .pipe(takeUntil(this.ngDestroy$)); + } + + get titleWarehouse() { + const titleForTr = this.PREFIX + this.WAREHOUSE_TITLE; + return this._translate(titleForTr); + } + + get titleCustomer() { + const titleForTr = this.PREFIX + this.CUSTOMER_TITLE; + return this._translate(titleForTr); + } + + get titleCarrier() { + const titleForTr = this.PREFIX + this.CARRIER_TITLE; + return this._translate(titleForTr); + } + + getTotalPrice(order) { + if (order && order.products.length > 0) { + return order.products + .map((p: OrderProduct) => p.price * p.count) + .reduce((a, b) => a + b, 0); + } else { + return 0; + } + } + + getWarehouseContactDetails(warehouse: Warehouse) { + const details = []; + if (warehouse) { + details.push(warehouse.name); + details.push(warehouse.contactPhone); + details.push(warehouse.contactEmail); + } + if (warehouse && warehouse.geoLocation) { + details.push(this.getFullAddress(warehouse.geoLocation)); + } + return details.filter((d) => d); + } + + getCustomerContactDetails(user: User) { + const details = []; + if (user) { + let name = ''; + if (user.firstName) { + name += user.firstName; + } + if (user.lastName) { + name += ' ' + user.lastName; + } + details.push(name); + details.push(user.phone); + details.push(user.email); + } + if (user && user.geoLocation) { + details.push(user.fullAddress); + + user.geoLocation.notes = + user.geoLocation.notes === undefined + ? '' + : user.geoLocation.notes; + + details.push(`Notes: ${user.geoLocation.notes}`); + } + return details.filter((d) => d); + } + + getCarrierContactDetails(carrier: Carrier) { + const details = []; + if (carrier) { + details.push( + carrier.firstName + ? carrier.firstName + ' ' + carrier.lastName + : carrier.username + ); + details.push(carrier.phone); + } + if (carrier && carrier.geoLocation) { + details.push(this.getFullAddress(carrier.geoLocation)); + } + return details.filter((d) => d); + } + + getOrderName(order: Order) { + if (order) { + return getIdFromTheDate(order); + } + } + + isCarrierCurrent(order) { + return ( + order.carrierStatus >= OrderCarrierStatus.CarrierPickedUpOrder && + !order['isCompleted'] + ); + } + + getDistance(geoLocation1: GeoLocation, geoLocation2: GeoLocation) { + if (!this.distance && geoLocation1 && geoLocation2) { + this.distance = '0'; + service.getDistanceMatrix( + { + origins: [ + new google.maps.LatLng( + geoLocation1.coordinates.lat, + geoLocation1.coordinates.lng + ), + ], + destinations: [ + new google.maps.LatLng( + geoLocation2.coordinates.lat, + geoLocation2.coordinates.lng + ), + ], + travelMode: google.maps.TravelMode.DRIVING, + unitSystem: google.maps.UnitSystem.METRIC, + avoidHighways: false, + avoidTolls: false, + }, + (response, status) => { + if (status === google.maps.DistanceMatrixStatus.OK) { + this.distance = + response.rows[0].elements[0].distance['text']; + } + } + ); + } + + return this.distance; + } + + private getFullAddress(geoLocation: GeoLocation) { + return ( + `${geoLocation.city}, ${geoLocation.streetAddress} ` + + `${geoLocation.house}` + ); + } + + private _translate(key: string): string { + let translationResult = ''; + + this.translateService.get(key).subscribe((res) => { + translationResult = res; + }); + + return translationResult; + } + + ngOnDestroy() { + this.ngDestroy$.next(true); + this.ngDestroy$.complete(); + } +} diff --git a/packages/admin-web-angular/src/app/pages/+orders/+order/order.module.ts b/packages/admin-web-angular/src/app/pages/+orders/+order/order.module.ts new file mode 100644 index 0000000..84477b3 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+orders/+order/order.module.ts @@ -0,0 +1,25 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { ThemeModule } from '../../../@theme'; +import { ToasterModule } from 'angular2-toaster'; +import { TranslateModule } from '@ngx-translate/core'; +import { OrderComponent } from './order.component'; +import { SidebarInfoBoxModule } from './sidebar-info-box/sidebar-info-box.module'; +import { WarehouseOrderViewModule } from '@app/pages/+warehouses/+warehouse/warehouse-order-view/warehouse-order-view.module'; +import { OrderMapModule } from '@app/@shared/order/order-map/order-map.module'; +import { OrderProductsModule } from './order-products/order-products.module'; + +@NgModule({ + imports: [ + CommonModule, + ThemeModule, + ToasterModule.forRoot(), + TranslateModule.forChild(), + SidebarInfoBoxModule, + WarehouseOrderViewModule, + OrderMapModule, + OrderProductsModule, + ], + declarations: [OrderComponent], +}) +export class OrderModule {} diff --git a/packages/admin-web-angular/src/app/pages/+orders/+order/sidebar-info-box/sidebar-info-box.component.html b/packages/admin-web-angular/src/app/pages/+orders/+order/sidebar-info-box/sidebar-info-box.component.html new file mode 100644 index 0000000..8a8bf07 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+orders/+order/sidebar-info-box/sidebar-info-box.component.html @@ -0,0 +1,29 @@ + + +

{{ title }}

+
+ + +
+ +
+
+
+ {{ 'ORDER_VIEW.ORDER_SIDEBAR.CONTACT_DETAILS' | translate }} +
+
    +
  • + {{ detail }} +
  • +
+
+
+
diff --git a/packages/admin-web-angular/src/app/pages/+orders/+order/sidebar-info-box/sidebar-info-box.component.scss b/packages/admin-web-angular/src/app/pages/+orders/+order/sidebar-info-box/sidebar-info-box.component.scss new file mode 100644 index 0000000..894f036 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+orders/+order/sidebar-info-box/sidebar-info-box.component.scss @@ -0,0 +1,11 @@ +.order-sidebar-info-box { + position: relative !important; + overflow-x: hidden !important; + .img-container { + height: 80px; + img { + max-height: 100%; + max-width: 100%; + } + } +} diff --git a/packages/admin-web-angular/src/app/pages/+orders/+order/sidebar-info-box/sidebar-info-box.component.ts b/packages/admin-web-angular/src/app/pages/+orders/+order/sidebar-info-box/sidebar-info-box.component.ts new file mode 100644 index 0000000..c36b067 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+orders/+order/sidebar-info-box/sidebar-info-box.component.ts @@ -0,0 +1,25 @@ +import { Component, Input } from '@angular/core'; +import { Router } from '@angular/router'; + +@Component({ + selector: 'ea-order-sidebar-info-box', + templateUrl: './sidebar-info-box.component.html', + styleUrls: ['./sidebar-info-box.component.scss'], +}) +export class SidebarInfoBoxComponent { + @Input() + public title: string; + @Input() + public imageUrl: string; + @Input() + public contactDetails: string[]; + @Input() + public redirectUrl: string; + constructor(private readonly router: Router) {} + + redirect() { + if (this.redirectUrl) { + this.router.navigate([this.redirectUrl]); + } + } +} diff --git a/packages/admin-web-angular/src/app/pages/+orders/+order/sidebar-info-box/sidebar-info-box.module.ts b/packages/admin-web-angular/src/app/pages/+orders/+order/sidebar-info-box/sidebar-info-box.module.ts new file mode 100644 index 0000000..d95ec65 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+orders/+order/sidebar-info-box/sidebar-info-box.module.ts @@ -0,0 +1,18 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { ToasterModule } from 'angular2-toaster'; +import { TranslateModule } from '@ngx-translate/core'; +import { ThemeModule } from '@app/@theme'; +import { SidebarInfoBoxComponent } from './sidebar-info-box.component'; + +@NgModule({ + imports: [ + CommonModule, + ThemeModule, + ToasterModule.forRoot(), + TranslateModule.forChild(), + ], + declarations: [SidebarInfoBoxComponent], + exports: [SidebarInfoBoxComponent], +}) +export class SidebarInfoBoxModule {} diff --git a/packages/admin-web-angular/src/app/pages/+orders/orders.component.html b/packages/admin-web-angular/src/app/pages/+orders/orders.component.html new file mode 100644 index 0000000..af27616 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+orders/orders.component.html @@ -0,0 +1 @@ +

Orders Component TODO...

diff --git a/packages/admin-web-angular/src/app/pages/+orders/orders.component.scss b/packages/admin-web-angular/src/app/pages/+orders/orders.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/packages/admin-web-angular/src/app/pages/+orders/orders.component.ts b/packages/admin-web-angular/src/app/pages/+orders/orders.component.ts new file mode 100644 index 0000000..4df01a7 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+orders/orders.component.ts @@ -0,0 +1,8 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'ea-orders', + templateUrl: './orders.component.html', + styleUrls: ['./orders.component.scss'], +}) +export class OrdersComponent {} diff --git a/packages/admin-web-angular/src/app/pages/+orders/orders.module.ts b/packages/admin-web-angular/src/app/pages/+orders/orders.module.ts new file mode 100644 index 0000000..f838640 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+orders/orders.module.ts @@ -0,0 +1,40 @@ +import { Routes, RouterModule } from '@angular/router'; +import { NgModule } from '@angular/core'; +import { CommonModule, JsonPipe } from '@angular/common'; +import { Ng2SmartTableModule } from 'ng2-smart-table'; +import { ThemeModule } from '../../@theme'; +import { ToasterModule } from 'angular2-toaster'; +import { TranslateModule } from '@ngx-translate/core'; +import { HighlightModule } from 'ngx-highlightjs'; +import { OrdersComponent } from './orders.component'; +import { OrderComponent } from './+order/order.component'; +import { OrderModule } from './+order/order.module'; + +const routes: Routes = [ + { + path: '', + component: OrdersComponent, + }, + { + path: ':id', + component: OrderComponent, + }, +]; + +@NgModule({ + imports: [ + CommonModule, + Ng2SmartTableModule, + ThemeModule, + ToasterModule.forRoot(), + RouterModule.forChild(routes), + TranslateModule.forChild(), + HighlightModule, + OrderModule, + ], + declarations: [OrdersComponent], + providers: [JsonPipe], +}) +export class OrdersModule { + public static routes = routes; +} diff --git a/packages/admin-web-angular/src/app/pages/+products/+categories/categories.component.html b/packages/admin-web-angular/src/app/pages/+products/+categories/categories.component.html new file mode 100644 index 0000000..c8a36bb --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+products/+categories/categories.component.html @@ -0,0 +1,35 @@ + + + + + + + + + + + + diff --git a/packages/admin-web-angular/src/app/pages/+products/+categories/categories.component.scss b/packages/admin-web-angular/src/app/pages/+products/+categories/categories.component.scss new file mode 100644 index 0000000..cfff344 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+products/+categories/categories.component.scss @@ -0,0 +1,61 @@ +nb-card-header { + border-bottom: 0; +} + +nb-card-body { + padding: 0; + border-bottom-left-radius: 0.5rem; + border-bottom-right-radius: 0.5rem; +} + +:host ::ng-deep ng2-smart-table { + tr { + td:nth-child(1), + th:nth-child(1) { + width: 30px; + } + td:nth-child(2), + th:nth-child(2) { + width: 30px; + } + th { + padding: 18px !important; + } + td { + padding: 17px !important; + } + } + + tr.ng2-smart-titles > th:nth-child(1) { + text-align: center; + cursor: pointer; + + input { + margin-left: 1px !important; + cursor: pointer; + } + } + + td.ng2-smart-actions.ng2-smart-action-multiple-select { + text-align: center; + cursor: pointer; + } + + tr.ng2-smart-filters th { + text-align: center; + } + + tr td, + th { + &:first-of-type { + border-left: none; + } + &:last-of-type { + border-right: none; + } + } + + input[type='checkbox'] { + cursor: pointer; + } +} diff --git a/packages/admin-web-angular/src/app/pages/+products/+categories/categories.component.ts b/packages/admin-web-angular/src/app/pages/+products/+categories/categories.component.ts new file mode 100644 index 0000000..67d8f06 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+products/+categories/categories.component.ts @@ -0,0 +1,84 @@ +import { Component, OnDestroy, ViewChild } from '@angular/core'; +import { takeUntil } from 'rxjs/operators'; +import { LocalDataSource } from 'ng2-smart-table'; +import { ProductsCategoryService } from '../../../@core/data/productsCategory.service'; +import ProductsCategory from '@modules/server.common/entities/ProductsCategory'; +import { CategoryCreateComponent } from '../../../@shared/product/categories/category-create/category-create.component'; +import { firstValueFrom, Subject } from 'rxjs'; +import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; +import { CategoriesTableComponent } from '../../../@shared/product/categories/categories-table/categories-table.component'; +import { NotifyService } from '@app/@core/services/notify/notify.service'; + +@Component({ + selector: 'ea-categories', + templateUrl: './categories.component.html', + styleUrls: ['./categories.component.scss'], +}) +export class CategoriesComponent implements OnDestroy { + @ViewChild('categoriesTable', { static: true }) + categoriesTable: CategoriesTableComponent; + + protected settingsSmartTable: object; + protected sourceSmartTable = new LocalDataSource(); + + private ngDestroy$ = new Subject(); + public loading: boolean; + + constructor( + private readonly _productsCategoryService: ProductsCategoryService, + private readonly _modalService: NgbModal, + private readonly _notifyService: NotifyService + ) { + this._loadDataSmartTable(); + } + + public get hasSelectedCategories(): boolean { + return this.categoriesTable.hasSelectedCategories; + } + + openWizardNewCategory() { + this._modalService.open(CategoryCreateComponent, { + size: 'lg', + container: 'nb-layout', + backdrop: 'static', + }); + } + + async deleteSelectedRows() { + const categories = this.categoriesTable.selectedCategories; + const idsArray: any = []; + for (const val of categories) { + idsArray.push(val.id); + } + + try { + this.loading = true; + await firstValueFrom( + this._productsCategoryService.removeByIds(idsArray).pipe() + ); + this.loading = false; + const message = `Selected are removed!`; + this._notifyService.success(message); + } catch (error) { + this.loading = false; + const message = `Something went wrong!`; + this._notifyService.error(message); + } + } + + private async _loadDataSmartTable() { + let categories: ProductsCategory[] = []; + this._productsCategoryService + .getCategories() + .pipe(takeUntil(this.ngDestroy$)) + .subscribe((c: ProductsCategory[]) => { + categories = c; + this.categoriesTable.loadDataSmartTable(categories); + }); + } + + ngOnDestroy() { + this.ngDestroy$.next(); + this.ngDestroy$.complete(); + } +} diff --git a/packages/admin-web-angular/src/app/pages/+products/+product/index.ts b/packages/admin-web-angular/src/app/pages/+products/+product/index.ts new file mode 100644 index 0000000..8b3162c --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+products/+product/index.ts @@ -0,0 +1 @@ +export * from './product.module'; diff --git a/packages/admin-web-angular/src/app/pages/+products/+product/product-edit/product-edit.component.html b/packages/admin-web-angular/src/app/pages/+products/+product/product-edit/product-edit.component.html new file mode 100644 index 0000000..7d33b1a --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+products/+product/product-edit/product-edit.component.html @@ -0,0 +1,41 @@ + + + +
+ + + + +

{{ 'PRODUCTS_VIEW.EDIT_VIEW.EDIT_PRODUCT' | translate }}

+
+ + +
+ + +

+ {{ 'PRODUCTS_VIEW.EDIT_VIEW.BASIC_INFO' | translate }} +

+
+ +
+
diff --git a/packages/admin-web-angular/src/app/pages/+products/+product/product-edit/product-edit.component.scss b/packages/admin-web-angular/src/app/pages/+products/+product/product-edit/product-edit.component.scss new file mode 100644 index 0000000..dad6bf1 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+products/+product/product-edit/product-edit.component.scss @@ -0,0 +1,27 @@ +@import '../../../../@theme/styles/themes'; +@import '../../../../@shared/styles/control-icon.shared'; + +@include nb-install-component() { + @include control-icon; + + .basic-info { + height: 100%; + margin: 0; + padding: 0; + } + + .edit-product { + position: relative; + height: 100%; + margin: 0; + padding: 0; + } + + .body-header { + text-align: center; + } + + .control-icon-left { + cursor: pointer; + } +} diff --git a/packages/admin-web-angular/src/app/pages/+products/+product/product-edit/product-edit.component.ts b/packages/admin-web-angular/src/app/pages/+products/+product/product-edit/product-edit.component.ts new file mode 100644 index 0000000..cd4c8d0 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+products/+product/product-edit/product-edit.component.ts @@ -0,0 +1,128 @@ +import { Component, ViewChild, OnInit, OnDestroy } from '@angular/core'; +import { BasicInfoFormComponent } from '../../../../@shared/product/forms/basic-info'; +import { FormGroup, FormBuilder, FormControl } from '@angular/forms'; +import Product from '@modules/server.common/entities/Product'; +import { Subject } from 'rxjs'; +import { ActivatedRoute, Router } from '@angular/router'; +import { ProductsService } from '../../../../@core/data/products.service'; +import { takeUntil, first, switchMap } from 'rxjs/operators'; +import { ToasterService } from 'angular2-toaster'; +import { ProductsCategoryService } from '../../../../@core/data/productsCategory.service'; +import { Location } from '@angular/common'; + +@Component({ + styleUrls: ['./product-edit.component.scss'], + templateUrl: './product-edit.component.html', +}) +export class ProductEditComponent implements OnInit, OnDestroy { + private ngDestroy$ = new Subject(); + + @ViewChild('basicInfoForm', { static: true }) + public basicInfoForm: BasicInfoFormComponent; + + public readonly form: FormGroup = this.formBuilder.group({ + basicInfo: BasicInfoFormComponent.buildForm(this.formBuilder), + }); + + storeId: string; + + public readonly basicInfo = this.form.get('basicInfo') as FormControl; + + protected product$: any; + protected status; + product: any; + + public productsCategories: any; + public loading: boolean; + + constructor( + private readonly formBuilder: FormBuilder, + private readonly activatedRoute: ActivatedRoute, + private readonly productsService: ProductsService, + private readonly toasterService: ToasterService, + private readonly router: Router, + private readonly productsCategoryService: ProductsCategoryService, + private location: Location + ) { + this.loadProductCategories(); + + this.product$ = this.activatedRoute.params.pipe( + switchMap((p) => { + return this.productsService.getProductById(p.id); + }) + ); + } + + public get isProductValid() { + return this.basicInfo.valid && this.status === 'changes'; + } + + public async updateProduct() { + try { + const res = await this.basicInfoForm.setupProductCreateObject(); + this.loading = true; + const updatedProd = await this.productsService + .save({ + _id: this.product.id, + title: res.title, + description: res.description, + details: res.details, + images: res.images, + categories: res.categories, + } as Product) + .pipe(first()) + .toPromise(); + + this.toasterService.pop( + 'success', + `Product ${updatedProd.title} was updated!` + ); + this.loading = false; + await this.router.navigate([`/products/list/${updatedProd.id}`], { + relativeTo: this.activatedRoute, + }); + } catch (err) { + this.loading = false; + this.toasterService.pop( + 'error', + `Error in updating carrier: "${err.message}"` + ); + } + } + + back() { + this.location.back(); + } + + ngOnInit(): void { + this.basicInfoForm.productCategories = this.productsCategories; + + this.product$.pipe(takeUntil(this.ngDestroy$)).subscribe((product) => { + this.basicInfoForm.productCategories = this.productsCategories; + this.basicInfoForm.setValue(product); + this.product = product; + this.changes(); + }); + } + + async changes() { + this.basicInfo.valueChanges + .pipe(first()) + .toPromise() + .then(() => { + this.status = 'changes'; + }); + } + + async loadProductCategories() { + this.productsCategories = await this.productsCategoryService + .getCategories() + .pipe(first()) + .toPromise(); + } + + ngOnDestroy() { + this.ngDestroy$.next(); + this.ngDestroy$.complete(); + } +} diff --git a/packages/admin-web-angular/src/app/pages/+products/+product/product-edit/product-edit.module.ts b/packages/admin-web-angular/src/app/pages/+products/+product/product-edit/product-edit.module.ts new file mode 100644 index 0000000..b9e0ede --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+products/+product/product-edit/product-edit.module.ts @@ -0,0 +1,34 @@ +import { NgModule } from '@angular/core'; +import { ThemeModule } from '../../../../@theme/theme.module'; +import { TranslateModule } from '@ngx-translate/core'; +import { RouterModule, Routes } from '@angular/router'; +import { ToasterModule } from 'angular2-toaster'; +import { ProductEditComponent } from './product-edit.component'; +import { ProductFormsModule } from '../../../../@shared/product/forms'; +import { FormWizardModule } from '@ever-co/angular2-wizard'; +import { ProductsCategoryService } from '../../../../@core/data/productsCategory.service'; +import { NbSpinnerModule, NbButtonModule } from '@nebular/theme'; + +export const routes: Routes = [ + { + path: '', + component: ProductEditComponent, + }, +]; + +@NgModule({ + imports: [ + ThemeModule, + TranslateModule.forChild(), + ToasterModule.forRoot(), + RouterModule.forChild(routes), + ProductFormsModule, + FormWizardModule, + NbSpinnerModule, + NbButtonModule, + ], + exports: [], + declarations: [ProductEditComponent], + providers: [ProductsCategoryService], +}) +export class ProductEditModule {} diff --git a/packages/admin-web-angular/src/app/pages/+products/+product/product.component.html b/packages/admin-web-angular/src/app/pages/+products/+product/product.component.html new file mode 100644 index 0000000..d18c2f7 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+products/+product/product.component.html @@ -0,0 +1,75 @@ + + + + + + + +

{{ getTranslatedValue(product?.title) }}

+
+
+ +
+ {{ getTranslatedValue(product?.title) }} + +
+

+ {{ 'CUSTOMERS_VIEW.DESCRIPTION' | translate }}: + {{ getTranslatedValue(product?.description) }} +

+ +

+ {{ 'CUSTOMERS_VIEW.DETAILS' | translate }}: + {{ getTranslatedValue(product?.details) }} +

+ +
+
+ {{ 'CUSTOMERS_VIEW.CATEGORY' | translate }}: + + {{ getTranslatedValue(category.name) + }}{{ isLast ? '' : ', ' }} + +
+
+ +
+ +
+
+ +
+ +
+
+
diff --git a/packages/admin-web-angular/src/app/pages/+products/+product/product.component.scss b/packages/admin-web-angular/src/app/pages/+products/+product/product.component.scss new file mode 100644 index 0000000..39e96a2 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+products/+product/product.component.scss @@ -0,0 +1,41 @@ +div { + font-size: 16px; +} + +p { + margin-bottom: 20px; +} + +strong { + color: black; + opacity: 0.66; +} + +div.input-group { + div label { + cursor: pointer; + } + + select { + cursor: pointer; + } +} + +img { + min-height: 200px; +} + +nb-card-header { + min-height: 71px; + position: relative; + + .control-icon-left { + cursor: pointer; + overflow: hidden; + vertical-align: middle; + width: 1.4em; + height: 1.4em; + float: left !important; + margin-right: 1.25rem; + } +} diff --git a/packages/admin-web-angular/src/app/pages/+products/+product/product.component.ts b/packages/admin-web-angular/src/app/pages/+products/+product/product.component.ts new file mode 100644 index 0000000..463084f --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+products/+product/product.component.ts @@ -0,0 +1,126 @@ +import { Component, OnInit, OnDestroy } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; +import { ProductsService } from '../../../@core/data/products.service'; +import { ProductLocalesService } from '@modules/client.common.angular2/locale/product-locales.service'; +import { IProductsCategory } from '@modules/server.common/interfaces/IProductsCategory'; +import { ILocaleMember } from '@modules/server.common/interfaces/ILocale'; +import Product from '@modules/server.common/entities/Product'; +import { Subject } from 'rxjs'; +import { first, takeUntil } from 'rxjs/operators'; +import { TranslateService } from '@ngx-translate/core'; +import { ProductsCategoryService } from '../../../@core/data/productsCategory.service'; +import { Location } from '@angular/common'; + +@Component({ + selector: 'ea-product', + templateUrl: './product.component.html', + styleUrls: ['./product.component.scss'], +}) +export class ProductComponent implements OnInit, OnDestroy { + product: Product; + productLangToShow: string; + + private _productId: string; + private _ngDestroy$ = new Subject(); + public productCategoriesArr = []; + public allCategories: any; + + constructor( + public readonly translateService: TranslateService, + private readonly _location: Location, + private readonly _router: ActivatedRoute, + private readonly _productsService: ProductsService, + private readonly _productLocalesService: ProductLocalesService, + private productsCategoryService: ProductsCategoryService + ) { + this._router.params + .pipe(first()) + .toPromise() + .then((res) => { + this._productId = res.id; + }); + + this.getAllCategories(); + } + + get showCategories(): boolean { + return this.product && this.product.categories.length > 0; + } + + ngOnInit() { + this._loadProduct(); + this._setProductLanguage(); + this.productCategoriesArr = this.allCategories; + } + + ngOnDestroy() { + this._ngDestroy$.next(); + this._ngDestroy$.complete(); + } + + getLanguageFullName(langAbbreviation: string) { + switch (langAbbreviation) { + case 'en-US': + return 'English'; + case 'bg-BG': + return 'Български'; + case 'he-IL': + return 'עברית'; + case 'ru-RU': + return 'Русский'; + case 'es-ES': + return 'Spanish'; + } + } + + getTranslatedValue(member: ILocaleMember[]): string { + return this._productLocalesService.getTranslate( + member, + this.productLangToShow + ); + } + + getCategories(categories: IProductsCategory[]) { + return categories + .map((c) => this.getTranslatedValue(c.name)) + .join(', '); + } + async getAllCategories() { + this.allCategories = await this.productsCategoryService + .getCategories() + .pipe(first()) + .toPromise(); + } + + getTranslates(categoryName) { + return this._productLocalesService.getTranslate(categoryName); + } + + onProductLangChange(selectedLanguage: string) { + this.productLangToShow = selectedLanguage; + } + + getProductCategories(categoriesID) { + this.productCategoriesArr = this.allCategories.filter((c) => + categoriesID.includes(c.id) + ); + } + + back() { + this._location.back(); + } + + private _loadProduct() { + this._productsService + .getProductById(this._productId) + .pipe(takeUntil(this._ngDestroy$)) + .subscribe((p) => { + this.product = p; + this.getProductCategories(p.categories); + }); + } + + private _setProductLanguage() { + this.productLangToShow = this.translateService.currentLang; + } +} diff --git a/packages/admin-web-angular/src/app/pages/+products/+product/product.module.ts b/packages/admin-web-angular/src/app/pages/+products/+product/product.module.ts new file mode 100644 index 0000000..9749342 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+products/+product/product.module.ts @@ -0,0 +1,26 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { ThemeModule } from '../../../@theme'; +import { RouterModule } from '@angular/router'; +import { routes } from './product.routes'; +import { FormWizardModule } from '@ever-co/angular2-wizard'; +import { ToasterModule } from 'angular2-toaster'; +import { TranslateModule } from '@ngx-translate/core'; +import { HighlightModule } from 'ngx-highlightjs'; +import { ProductComponent } from './product.component'; +import { Ng2SmartTableModule } from 'ng2-smart-table'; + +@NgModule({ + imports: [ + CommonModule, + ThemeModule, + FormWizardModule, + Ng2SmartTableModule, + ToasterModule.forRoot(), + TranslateModule.forChild(), + RouterModule.forChild(routes), + HighlightModule, + ], + declarations: [ProductComponent], +}) +export class ProductModule {} diff --git a/packages/admin-web-angular/src/app/pages/+products/+product/product.routes.ts b/packages/admin-web-angular/src/app/pages/+products/+product/product.routes.ts new file mode 100644 index 0000000..7fc71eb --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+products/+product/product.routes.ts @@ -0,0 +1,16 @@ +import { Routes } from '@angular/router'; +import { ProductComponent } from './product.component'; + +export const routes: Routes = [ + { + path: '', + component: ProductComponent, + }, + { + path: 'edit', + loadChildren: () => + import('./product-edit/product-edit.module').then( + (m) => m.ProductEditModule + ), + }, +]; diff --git a/packages/admin-web-angular/src/app/pages/+products/products.component.html b/packages/admin-web-angular/src/app/pages/+products/products.component.html new file mode 100644 index 0000000..5de4edc --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+products/products.component.html @@ -0,0 +1,36 @@ + + + + + + + + + + + + diff --git a/packages/admin-web-angular/src/app/pages/+products/products.component.scss b/packages/admin-web-angular/src/app/pages/+products/products.component.scss new file mode 100644 index 0000000..91361b5 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+products/products.component.scss @@ -0,0 +1,53 @@ +nb-card-header { + border-bottom: 0; +} + +nb-card-body { + padding: 0; + border-bottom-left-radius: 0.5rem; + border-bottom-right-radius: 0.5rem; +} + +:host ::ng-deep ng2-smart-table { + tr { + th { + padding: 18px !important; + } + td { + padding: 17px !important; + } + } + + tr.ng2-smart-titles > th:nth-child(1) { + text-align: center; + cursor: pointer; + + input { + margin-left: 1px !important; + cursor: pointer; + } + } + + td.ng2-smart-actions.ng2-smart-action-multiple-select { + text-align: center; + cursor: pointer; + } + + tr.ng2-smart-filters th { + text-align: center; + } + + tr td, + th { + &:first-of-type { + border-left: none; + } + &:last-of-type { + border-right: none; + } + } + + input[type='checkbox'] { + cursor: pointer; + } +} diff --git a/packages/admin-web-angular/src/app/pages/+products/products.component.ts b/packages/admin-web-angular/src/app/pages/+products/products.component.ts new file mode 100644 index 0000000..81f819d --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+products/products.component.ts @@ -0,0 +1,137 @@ +import { Component, OnDestroy, AfterViewInit, ViewChild } from '@angular/core'; +import { takeUntil, first } from 'rxjs/operators'; +import { LocalDataSource } from 'ng2-smart-table'; +import { ProductsService } from '../../@core/data/products.service'; +import Product from '@modules/server.common/entities/Product'; +import { firstValueFrom, Subject } from 'rxjs'; +import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; +import { ProductCreateComponent } from '../../@shared/product/product-create'; +import { ProductsTableComponent } from '../../@shared/product/forms/products-table'; +import { ProductsCategoryService } from '../../@core/data/productsCategory.service'; +import { NotifyService } from '@app/@core/services/notify/notify.service'; + +const perPage = 5; + +@Component({ + selector: 'ea-products', + templateUrl: './products.component.html', + styleUrls: ['./products.component.scss'], +}) +export class ProductsComponent implements OnDestroy, AfterViewInit { + @ViewChild('productsTable', { static: true }) + protected productsTable: ProductsTableComponent; + + public productsCategories: any; + + public loading: boolean; + public perPage: number; + + protected settingsSmartTable: object; + protected sourceSmartTable = new LocalDataSource(); + + private ngDestroy$ = new Subject(); + private $products; + + constructor( + private readonly _productsService: ProductsService, + private readonly modalService: NgbModal, + private readonly productsCategoryService: ProductsCategoryService, + private readonly _notifyService: NotifyService + ) { + this.perPage = perPage; + this.getCategories(); + } + + ngAfterViewInit(): void { + this._loadDataSmartTable(); + if (this.productsTable) { + this.productsTable.pagesChanges$ + .pipe(takeUntil(this.ngDestroy$)) + .subscribe((page: number) => { + this._loadDataSmartTable(page); + }); + } + } + + openWizardNewProduct() { + const activeModal = this.modalService.open(ProductCreateComponent, { + size: 'lg', + container: 'nb-layout', + backdrop: 'static', + }); + + const modalComponent: ProductCreateComponent = + activeModal.componentInstance; + + modalComponent.productsCategories = this.productsCategories; + } + + async deleteSelectedRows() { + const idsForDelete: string[] = this.productsTable.selectedProducts.map( + (c) => c.id + ); + + try { + this.loading = true; + await firstValueFrom( + this._productsService.removeByIds(idsForDelete) + ); + + this.loading = false; + + const message = `${idsForDelete.length} products was deleted!`; + + this._notifyService.success(message); + + this.productsTable.selectedProducts = []; + } catch (error) { + this.loading = false; + const message = `Something went wrong!`; + this._notifyService.error(message); + this.productsTable.selectedProducts = []; + } + } + + public get hasSelectedProducts(): boolean { + return this.productsTable.hasSelectedProducts; + } + + private async getCategories() { + this.productsCategories = await firstValueFrom( + this.productsCategoryService.getCategories() + ); + } + + private async _loadDataSmartTable(page: number = 1) { + if (this.$products) { + await this.$products.unsubscribe(); + } + this.$products = this._productsService + .getProducts({ + skip: perPage * (page - 1), + limit: perPage, + }) + .pipe(takeUntil(this.ngDestroy$)) + .subscribe(async (p: Product[]) => { + this.productsTable.perPage = perPage; + + const dataCount = await this.getDataCount(); + let products: Product[] = p; + + this.productsTable.loadDataSmartTable( + products || [], + dataCount, + page + ); + }); + } + + private async getDataCount() { + return this._productsService.getCountOfProducts(); + } + + ngOnDestroy() { + this.ngDestroy$.next(); + this.ngDestroy$.complete(); + } +} diff --git a/packages/admin-web-angular/src/app/pages/+products/products.module.ts b/packages/admin-web-angular/src/app/pages/+products/products.module.ts new file mode 100644 index 0000000..0a717b6 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+products/products.module.ts @@ -0,0 +1,62 @@ +import { Routes, RouterModule } from '@angular/router'; +import { NgModule } from '@angular/core'; +import { CommonModule, JsonPipe } from '@angular/common'; +import { ThemeModule } from '../../@theme'; +import { ToasterModule } from 'angular2-toaster'; +import { TranslateModule } from '@ngx-translate/core'; +import { HighlightModule } from 'ngx-highlightjs'; +import { ProductsComponent } from './products.component'; +import { CategoriesComponent } from './+categories/categories.component'; +import { ProductCreateModule } from '../../@shared/product/product-create'; +import { ProductFormsModule } from '../../@shared/product/forms'; +import { CategoryCreateComponent } from '../../@shared/product/categories/category-create'; +import { CategoryEditComponent } from '../../@shared/product/categories/category-edit/category-edit.component'; +import { NbSpinnerModule, NbButtonModule } from '@nebular/theme'; +import { NotifyService } from '@app/@core/services/notify/notify.service'; +import { ConfirmationModalModule } from '../../@shared/confirmation-modal/confirmation-modal.module'; +import { FileUploaderModule } from '@app/@shared/file-uploader/file-uploader.module'; +import { ProductCategoriesFormsModule } from '@app/@shared/product/categories/forms/product-categories-forms.module'; + +const routes: Routes = [ + { + path: 'list', + component: ProductsComponent, + }, + { + path: 'categories', + component: CategoriesComponent, + }, + { + path: 'list/:id', + loadChildren: () => + import('./+product/product.module').then((m) => m.ProductModule), + }, +]; + +@NgModule({ + imports: [ + CommonModule, + ThemeModule, + ToasterModule.forRoot(), + RouterModule.forChild(routes), + TranslateModule.forChild(), + HighlightModule, + ProductCreateModule, + ProductFormsModule, + NbSpinnerModule, + ConfirmationModalModule, + FileUploaderModule, + ProductCategoriesFormsModule, + NbButtonModule, + ], + declarations: [ + ProductsComponent, + CategoryCreateComponent, + CategoryEditComponent, + CategoriesComponent, + ], + providers: [JsonPipe, NotifyService], +}) +export class ProductsModule { + public static routes = routes; +} diff --git a/packages/admin-web-angular/src/app/pages/+profile/edit/account/account.component.html b/packages/admin-web-angular/src/app/pages/+profile/edit/account/account.component.html new file mode 100644 index 0000000..d7bc7da --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+profile/edit/account/account.component.html @@ -0,0 +1,100 @@ + +
+
+
+ + + + + {{ 'PROFILE_VIEW.ERROR' | translate }}: + {{ oldPasswordErrorMsg }} +
+
+ + + + + {{ 'PROFILE_VIEW.ERROR' | translate }}: + {{ passwordErrorMsg }} +
+
+ + + + + {{ 'PROFILE_VIEW.ERROR' | translate }}: + {{ repeatPasswordErrorMsg }} +
+ +
+
+
diff --git a/packages/admin-web-angular/src/app/pages/+profile/edit/account/account.component.scss b/packages/admin-web-angular/src/app/pages/+profile/edit/account/account.component.scss new file mode 100644 index 0000000..f85898c --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+profile/edit/account/account.component.scss @@ -0,0 +1,8 @@ +.container { + padding: 1.25rem; + padding-bottom: 0; +} + +.save-button-profile { + margin-bottom: 30px; +} diff --git a/packages/admin-web-angular/src/app/pages/+profile/edit/account/account.component.ts b/packages/admin-web-angular/src/app/pages/+profile/edit/account/account.component.ts new file mode 100644 index 0000000..a4a7dce --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+profile/edit/account/account.component.ts @@ -0,0 +1,189 @@ +import { Component, Input, OnInit, OnDestroy } from '@angular/core'; +import { + FormGroup, + AbstractControl, + FormBuilder, + Validators, +} from '@angular/forms'; +import Admin from '@modules/server.common/entities/Admin'; +import { AdminsService } from '../../../../@core/data/admins.service'; +import { first, takeUntil, debounceTime } from 'rxjs/operators'; +import { ToasterService } from 'angular2-toaster'; +import { Router } from '@angular/router'; +import { Subject } from 'rxjs'; +import { TranslateService } from '@ngx-translate/core'; + +@Component({ + selector: 'ea-account', + styleUrls: ['/account.component.scss'], + templateUrl: './account.component.html', +}) +export class AccountComponent implements OnInit, OnDestroy { + private ngDestroy$ = new Subject(); + + public accountForm: FormGroup; + public oldPassword: AbstractControl; + public password: AbstractControl; + public repeatPassword: AbstractControl; + + public oldPasswordErrorMsg: string; + public passwordErrorMsg: string; + public repeatPasswordErrorMsg: string; + + public PASSWORDS_DO_NOT_MATCH: string = 'PASSWORDS_DO_NOT_MATCH'; + public SUCCESSFULLY_CHANGE_PASSWORD: string = + 'SUCCESSFULLY_CHANGE_PASSWORD'; + public PREFIX: string = 'PROFILE_VIEW.'; + + public loading: boolean; + + @Input() + private admin: Admin; + $password: any; + + constructor( + private formBuilder: FormBuilder, + private adminsService: AdminsService, + private toasterService: ToasterService, + private router: Router, + private _translateService: TranslateService + ) { + this.buildForm(); + this.bindFormControls(); + + this.loadControls(); + } + + ngOnInit(): void { + this.$password = this.password.valueChanges.subscribe((res) => { + this.repeatPassword.setValue(''); + }); + } + + passwordDoNotMuch() { + return this._translate(this.PREFIX + this.PASSWORDS_DO_NOT_MATCH); + } + + successfullyChangePassword() { + return this._translate(this.PREFIX + this.SUCCESSFULLY_CHANGE_PASSWORD); + } + + async saveChanges() { + try { + this.loading = true; + const res = await this.adminsService + .updatePassword(this.admin.id, { + new: this.password.value, + current: this.oldPassword.value, + }) + .pipe(first()) + .toPromise(); + if (res.errors) { + this.toasterService.pop('error', res.errors[0].message); + } else { + this.toasterService.pop( + 'success', + this.successfullyChangePassword() + ); + this.router.navigate(['/auth/logout']); + } + this.loading = false; + } catch (error) { + this.loading = true; + this.toasterService.pop('error', error); + this.loading = false; + } + } + + buildForm() { + this.accountForm = this.formBuilder.group({ + oldPassword: ['', Validators.required], + password: ['', [Validators.required, Validators.minLength(4)]], + repeatPassword: [ + '', + [ + Validators.required, + (control: AbstractControl) => { + if (this.password) { + return control.value === this.password.value + ? null + : { validUrl: true }; + } else { + return null; + } + }, + ], + ], + }); + } + + bindFormControls() { + this.oldPassword = this.accountForm.get('oldPassword'); + this.password = this.accountForm.get('password'); + this.repeatPassword = this.accountForm.get('repeatPassword'); + } + + loadControls() { + this.validations.oldPasswordControl(); + this.validations.passwordControl(); + this.validations.repeatPasswordControl(); + } + + // Use "errors[0]" because to show messages one by one till all are fixed + private validations = { + oldPasswordControl: () => { + this.oldPassword.valueChanges + .pipe(debounceTime(500), takeUntil(this.ngDestroy$)) + .subscribe((value) => { + this.oldPasswordErrorMsg = + (this.oldPassword.touched || this.oldPassword.dirty) && + this.oldPassword.errors + ? Object.keys(this.oldPassword.errors)[0] + : ''; + }); + }, + passwordControl: () => { + this.password.valueChanges + .pipe(debounceTime(500), takeUntil(this.ngDestroy$)) + .subscribe((value) => { + this.passwordErrorMsg = + (this.password.touched || this.password.dirty) && + this.password.errors + ? Object.keys(this.password.errors)[0] + : ''; + }); + }, + repeatPasswordControl: () => { + this.repeatPassword.valueChanges + .pipe(debounceTime(500), takeUntil(this.ngDestroy$)) + .subscribe((value) => { + this.repeatPasswordErrorMsg = + (this.repeatPassword.touched || + this.repeatPassword.dirty) && + this.repeatPassword.errors + ? this.repeatPassword.errors.validUrl + ? this.passwordDoNotMuch() + : Object.keys(this.repeatPassword.errors)[0] + : ''; + }); + }, + }; + + private _translate(key: string): string { + let translationResult = ''; + + this._translateService.get(key).subscribe((res) => { + translationResult = res; + }); + + return translationResult; + } + + ngOnDestroy(): void { + if (this.$password) { + this.$password.unsubscribe(); + } + this.ngDestroy$.next(); + this.ngDestroy$.complete(); + } +} diff --git a/packages/admin-web-angular/src/app/pages/+profile/edit/basic-info/basic-info.component.html b/packages/admin-web-angular/src/app/pages/+profile/edit/basic-info/basic-info.component.html new file mode 100644 index 0000000..921444b --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+profile/edit/basic-info/basic-info.component.html @@ -0,0 +1,174 @@ + +
+
+
+
+ + + + {{ 'PROFILE_VIEW.USERNAME' | translate }}: + {{ usernameErrorMsg }} +
+ +
+ + + + + {{ 'PROFILE_VIEW.ERROR' | translate }}: + {{ emailErrorMsg }} +
+
+ +
+
+ + + + + {{ 'PROFILE_VIEW.ERROR' | translate }}: + {{ firstNameErrorMsg }} +
+ +
+ + + + + {{ 'PROFILE_VIEW.ERROR' | translate }}: + {{ lastNameErrorMsg }} +
+
+ +
+
+ +
+
+ + + {{ 'PROFILE_VIEW.ERROR' | translate }} : + {{ pictureUrlErrorMsg }} +
+
+
+
+ Invalid image +
+ +
+
+
+ + +
+
+
diff --git a/packages/admin-web-angular/src/app/pages/+profile/edit/basic-info/basic-info.component.scss b/packages/admin-web-angular/src/app/pages/+profile/edit/basic-info/basic-info.component.scss new file mode 100644 index 0000000..587481f --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+profile/edit/basic-info/basic-info.component.scss @@ -0,0 +1,26 @@ +.container { + padding: 1.25rem; + padding-bottom: 0; +} + +.preview-img { + padding-left: 14px; + padding-right: 16px; +} + +.img-rounded { + max-height: 70px; +} + +.remove-icon { + cursor: pointer; + + span { + position: absolute; + font-size: 1.1em; + } +} + +.save-button-profile { + margin-bottom: 30px; +} diff --git a/packages/admin-web-angular/src/app/pages/+profile/edit/basic-info/basic-info.component.ts b/packages/admin-web-angular/src/app/pages/+profile/edit/basic-info/basic-info.component.ts new file mode 100644 index 0000000..fef98be --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+profile/edit/basic-info/basic-info.component.ts @@ -0,0 +1,243 @@ +import { Component, Input, OnChanges, OnDestroy } from '@angular/core'; +import { + FormGroup, + AbstractControl, + FormBuilder, + Validators, +} from '@angular/forms'; +import Admin from '@modules/server.common/entities/Admin'; +import { takeUntil, first, debounceTime } from 'rxjs/operators'; +import { Subject } from 'rxjs'; +import { IAdminUpdateObject } from '@modules/server.common/interfaces/IAdmin'; +import { AdminsService } from '../../../../@core/data/admins.service'; +import { getDummyImage } from '@modules/server.common/utils'; +import { ToasterService } from 'angular2-toaster'; +import { TranslateService } from '@ngx-translate/core'; + +@Component({ + selector: 'ea-basic-info', + styleUrls: ['/basic-info.component.scss'], + templateUrl: './basic-info.component.html', +}) +export class BasicInfoComponent implements OnChanges, OnDestroy { + @Input() + admin: Admin; + + uploaderPlaceholder: string; + basicInfoForm: FormGroup; + username: AbstractControl; + email: AbstractControl; + picture: AbstractControl; + firstName: AbstractControl; + lastName: AbstractControl; + + usernameErrorMsg: string; + emailErrorMsg: string; + firstNameErrorMsg: string; + lastNameErrorMsg: string; + INVALID_EMAIL_ADDRESS: string = 'INVALID_EMAIL_ADDRESS'; + INVALID_URL: string = 'INVALID_URL'; + NAME_MUST_CONTAIN_ONLY_LETTERS: string = 'NAME_MUST_CONTAIN_ONLY_LETTERS'; + PREFIX: string = 'PROFILE_VIEW.'; + loading: boolean; + + private ngDestroy$ = new Subject(); + + constructor( + private formBuilder: FormBuilder, + private adminsService: AdminsService, + private toasterService: ToasterService, + private _translateService: TranslateService + ) { + this.getUploaderPlaceholderText(); + this.buildForm(); + this.bindFormControls(); + this._applyTranslationOnSmartTable(); + + this.loadControls(); + } + + get pictureUrlErrorMsg() { + return this.picture.errors.pattern + ? this.invalidURL() + : Object.keys(this.picture.errors)[0]; + } + + ngOnChanges(): void { + this._applyTranslationOnSmartTable(); + if (this.admin) { + this.username.setValue(this.admin.name); + this.email.setValue(this.admin.email); + this.picture.setValue(this.admin.pictureUrl); + this.firstName.setValue(this.admin.firstName); + this.lastName.setValue(this.admin.lastName); + } + } + + ngOnDestroy() { + this.ngDestroy$.next(); + this.ngDestroy$.complete(); + } + + invalidEmailAddress() { + return this._translate(this.PREFIX + this.INVALID_EMAIL_ADDRESS); + } + + invalidURL() { + return this._translate(this.PREFIX + this.INVALID_URL); + } + + nameMustContainOnlyLetters() { + return this._translate( + this.PREFIX + this.NAME_MUST_CONTAIN_ONLY_LETTERS + ); + } + + async saveChanges() { + try { + this.loading = true; + const res = await this.adminsService + .updateById(this.admin.id, this.getAdminCreateObj()) + .pipe(first()) + .toPromise(); + this.loading = false; + this.basicInfoForm.markAsPristine(); + this.toasterService.pop('success', 'Successfully updated data'); + } catch (error) { + this.loading = false; + this.toasterService.pop('error', error); + } + } + + buildForm() { + const imgUrlRegex: RegExp = new RegExp( + `(http(s?):)s?:?(\/\/[^"']*\.(?:png|jpg|jpeg|gif|png|svg))` + ); + const nameRegex: RegExp = new RegExp(`^[a-z ,.'-]+$`, 'i'); + + this.basicInfoForm = this.formBuilder.group({ + username: ['', Validators.required], + email: ['', [Validators.required, Validators.email]], + picture: ['', [Validators.pattern(imgUrlRegex)]], + firstName: ['', Validators.pattern(nameRegex)], + lastName: ['', Validators.pattern(nameRegex)], + }); + } + + bindFormControls() { + this.username = this.basicInfoForm.get('username'); + this.email = this.basicInfoForm.get('email'); + this.picture = this.basicInfoForm.get('picture'); + this.firstName = this.basicInfoForm.get('firstName'); + this.lastName = this.basicInfoForm.get('lastName'); + } + + loadControls() { + this.validations.usernameControl(); + this.validations.emailControl(); + this.validations.firstNameControl(); + this.validations.lastNameControl(); + this.validations.pictureControl(); + } + + deleteImg() { + this.picture.setValue(''); + this.basicInfoForm.markAsDirty(); + } + + private _applyTranslationOnSmartTable() { + this._translateService.onLangChange.subscribe(() => { + this.loadControls(); + }); + } + + // Use "errors[0]" because to show messages one by one till all are fixed + private validations = { + usernameControl: () => { + this.username.valueChanges + .pipe(debounceTime(500), takeUntil(this.ngDestroy$)) + .subscribe((value) => { + this.usernameErrorMsg = this.hasError(this.username) + ? Object.keys(this.username.errors)[0] + : ''; + }); + }, + emailControl: () => { + this.email.valueChanges + .pipe(debounceTime(500), takeUntil(this.ngDestroy$)) + .subscribe((value) => { + this.emailErrorMsg = this.hasError(this.email) + ? this.email.errors.email + ? this.invalidEmailAddress() + : Object.keys(this.email.errors)[0] + : ''; + }); + }, + firstNameControl: () => { + this.firstName.valueChanges + .pipe(debounceTime(500), takeUntil(this.ngDestroy$)) + .subscribe((value) => { + this.firstNameErrorMsg = this.hasError(this.firstName) + ? this.firstName.errors.pattern + ? this.nameMustContainOnlyLetters() + : Object.keys(this.firstName.errors)[0] + : ''; + }); + }, + lastNameControl: () => { + this.lastName.valueChanges + .pipe(debounceTime(500), takeUntil(this.ngDestroy$)) + .subscribe((value) => { + this.lastNameErrorMsg = this.hasError(this.lastName) + ? this.lastName.errors.pattern + ? this.nameMustContainOnlyLetters() + : Object.keys(this.lastName.errors)[0] + : ''; + }); + }, + pictureControl: () => { + this.picture.valueChanges + .pipe(debounceTime(500), takeUntil(this.ngDestroy$)) + .subscribe((value) => { + value !== this.admin.pictureUrl && !this.picture.invalid + ? this.picture.markAsDirty() + : this.picture.markAsPristine(); + }); + }, + }; + + private hasError(control: AbstractControl) { + return (control.touched || control.dirty) && control.errors; + } + + private getAdminCreateObj(): IAdminUpdateObject { + if (!this.picture.value) { + const letter = this.username.value.charAt(0).toUpperCase(); + this.picture.setValue(getDummyImage(300, 300, letter)); + } + return { + name: this.username.value, + email: this.email.value, + firstName: this.firstName.value, + lastName: this.lastName.value, + pictureUrl: this.picture.value, + }; + } + + private _translate(key: string): string { + let translationResult = ''; + + this._translateService.get(key).subscribe((res) => { + translationResult = res; + }); + + return translationResult; + } + + private async getUploaderPlaceholderText() { + this.uploaderPlaceholder = await this._translateService + .get('PROFILE_VIEW.PICTURE_URL') + .pipe(first()) + .toPromise(); + } +} diff --git a/packages/admin-web-angular/src/app/pages/+profile/edit/edit.module.ts b/packages/admin-web-angular/src/app/pages/+profile/edit/edit.module.ts new file mode 100644 index 0000000..fe74780 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+profile/edit/edit.module.ts @@ -0,0 +1,27 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { ToasterModule } from 'angular2-toaster'; +import { TranslateModule } from '@ngx-translate/core'; +import { ThemeModule } from '../../../@theme'; +import { AccountComponent } from './account/account.component'; +import { BasicInfoComponent } from './basic-info/basic-info.component'; +import { NbSpinnerModule, NbButtonModule } from '@nebular/theme'; +import { FileUploaderModule } from '@app/@shared/file-uploader/file-uploader.module'; + +const EDIT_PROFILE_COMPONENTS = [AccountComponent, BasicInfoComponent]; + +@NgModule({ + imports: [ + CommonModule, + ThemeModule, + ToasterModule.forRoot(), + TranslateModule.forChild(), + FileUploaderModule, + NbSpinnerModule, + NbButtonModule, + ], + declarations: [...EDIT_PROFILE_COMPONENTS], + exports: [...EDIT_PROFILE_COMPONENTS], + providers: [] +}) +export class EditProfileModule {} diff --git a/packages/admin-web-angular/src/app/pages/+profile/profile.component.html b/packages/admin-web-angular/src/app/pages/+profile/profile.component.html new file mode 100644 index 0000000..747d53e --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+profile/profile.component.html @@ -0,0 +1,36 @@ + + + + +
+

+ {{ 'PROFILE_VIEW.PROFILE_PAGE' | translate }} +

+
+ + +
+
+
+ Admin image +
+
+
+
+

{{ admin.name }}

+

{{ admin.email }}

+
+ +
+ + + + + + + + + + + +
diff --git a/packages/admin-web-angular/src/app/pages/+profile/profile.component.scss b/packages/admin-web-angular/src/app/pages/+profile/profile.component.scss new file mode 100644 index 0000000..37377d0 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+profile/profile.component.scss @@ -0,0 +1,22 @@ +nb-card-header { + header { + h1 { + padding-bottom: 1.25rem; + } + } + body { + background-color: transparent; + } + body.row { + padding: 1.25rem; + padding-bottom: 0; + } + + .profile-image { + img { + max-width: 100%; + max-height: 100%; + border-radius: 5px; + } + } +} diff --git a/packages/admin-web-angular/src/app/pages/+profile/profile.component.ts b/packages/admin-web-angular/src/app/pages/+profile/profile.component.ts new file mode 100644 index 0000000..daa4b5d --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+profile/profile.component.ts @@ -0,0 +1,18 @@ +import { Component } from '@angular/core'; +import Admin from '@modules/server.common/entities/Admin'; +import { AdminsService } from '@app/@core/data/admins.service'; +import { Store } from '@app/@core/data/store.service'; +import { Observable } from 'rxjs'; + +@Component({ + selector: 'ea-profile', + styleUrls: ['./profile.component.scss'], + templateUrl: './profile.component.html', +}) +export class ProfileComponent { + public admin$: Observable; + + constructor(private adminsService: AdminsService, private store: Store) { + this.admin$ = this.adminsService.getAdmin(this.store.adminId); + } +} diff --git a/packages/admin-web-angular/src/app/pages/+profile/profile.module.ts b/packages/admin-web-angular/src/app/pages/+profile/profile.module.ts new file mode 100644 index 0000000..bba5444 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+profile/profile.module.ts @@ -0,0 +1,32 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { RouterModule, Routes } from '@angular/router'; +import { ToasterModule } from 'angular2-toaster'; +import { TranslateModule } from '@ngx-translate/core'; +import { ThemeModule } from '../../@theme'; +import { ProfileComponent } from './profile.component'; +import { AdminsService } from '../../@core/data/admins.service'; +import { EditProfileModule } from './edit/edit.module'; + +export const routes: Routes = [ + { + path: '', + component: ProfileComponent, + }, +]; + +const PROFILE_COMPONENTS = [ProfileComponent]; + +@NgModule({ + imports: [ + CommonModule, + ThemeModule, + ToasterModule.forRoot(), + TranslateModule.forChild(), + RouterModule.forChild(routes), + EditProfileModule, + ], + declarations: [...PROFILE_COMPONENTS], + providers: [AdminsService] +}) +export class ProfileModule {} diff --git a/packages/admin-web-angular/src/app/pages/+server-down/server-down.module.ts b/packages/admin-web-angular/src/app/pages/+server-down/server-down.module.ts new file mode 100644 index 0000000..776096d --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+server-down/server-down.module.ts @@ -0,0 +1,38 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { FormsModule } from '@angular/forms'; +import { Routes, RouterModule } from '@angular/router'; +import { ServerDownPage } from './server-down.page'; +import { TranslateModule, TranslateLoader } from '@ngx-translate/core'; +import { HttpClient } from '@angular/common/http'; +import { TranslateHttpLoader } from '@ngx-translate/http-loader'; +import { ThemeModule } from '@app/@theme'; + +export function HttpLoaderFactory(http: HttpClient) { + return new TranslateHttpLoader(http, '../../../assets/i18n/', '.json'); +} + +const routes: Routes = [ + { + path: '', + component: ServerDownPage, + }, +]; + +@NgModule({ + imports: [ + CommonModule, + FormsModule, + ThemeModule, + RouterModule.forChild(routes), + TranslateModule.forChild({ + loader: { + provide: TranslateLoader, + useFactory: HttpLoaderFactory, + deps: [HttpClient], + }, + }), + ], + declarations: [ServerDownPage], +}) +export class ServerDownModule {} diff --git a/packages/admin-web-angular/src/app/pages/+server-down/server-down.page.html b/packages/admin-web-angular/src/app/pages/+server-down/server-down.page.html new file mode 100644 index 0000000..f168d58 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+server-down/server-down.page.html @@ -0,0 +1,15 @@ + + + diff --git a/packages/admin-web-angular/src/app/pages/+server-down/server-down.page.scss b/packages/admin-web-angular/src/app/pages/+server-down/server-down.page.scss new file mode 100644 index 0000000..11db159 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+server-down/server-down.page.scss @@ -0,0 +1,25 @@ +// @import '@modules/client.common.angular2/scss/everbie.common'; +// @import "~@modules/client.common.angular2/scss/everbie.common.scss'; +// @import "~@ever-platform/common-angular/scss/everbie.common.scss'; + +.info-page { + padding: 0 !important; + + .server-down-content { + color: white; + + // background: $brand; + + display: flex; + align-items: center; + justify-content: center; + text-align: center; + height: 100%; + flex-direction: column; + } + + .info-massage h3 { + color: red; + width: 100%; + } +} diff --git a/packages/admin-web-angular/src/app/pages/+server-down/server-down.page.ts b/packages/admin-web-angular/src/app/pages/+server-down/server-down.page.ts new file mode 100644 index 0000000..bc22553 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+server-down/server-down.page.ts @@ -0,0 +1,48 @@ +import { Component, OnDestroy } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { Location } from '@angular/common'; +import { Store } from '@app/@core/data/store.service'; +import { environment } from 'environments/environment'; +import { TranslateService } from '@ngx-translate/core'; +import { ServerConnectionService } from '@modules/client.common.angular2/services/server-connection.service'; + +@Component({ + styleUrls: ['./server-down.page.scss'], + templateUrl: 'server-down.page.html', +}) +export class ServerDownPage implements OnDestroy { + noInternetLogo: string; + interval; + + constructor( + private store: Store, + private readonly http: HttpClient, + private location: Location, + private translate: TranslateService, + private serverConnectionService: ServerConnectionService + ) { + const browserLang = translate.getBrowserLang(); + translate.use(browserLang.match(/en|bg|he|ru/) ? browserLang : 'en-US'); + + this.noInternetLogo = environment['NO_INTERNET_LOGO']; + this.testConnection(); + } + + private async testConnection() { + this.interval = setInterval(async () => { + await this.serverConnectionService.checkServerConnection( + environment.SERVICES_ENDPOINT, + this.store + ); + + if (Number(this.store.serverConnection) !== 0) { + clearInterval(this.interval); + this.location.back(); + } + }, 5000); + } + + ngOnDestroy(): void { + clearInterval(this.interval); + } +} diff --git a/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/account/account.component.html b/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/account/account.component.html new file mode 100644 index 0000000..29da794 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/account/account.component.html @@ -0,0 +1,227 @@ + + + + + + + + + + diff --git a/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/account/account.component.scss b/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/account/account.component.scss new file mode 100644 index 0000000..e36c874 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/account/account.component.scss @@ -0,0 +1,5 @@ +.setup-merchant-account { + nb-card-body { + text-align: left; + } +} diff --git a/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/account/account.component.ts b/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/account/account.component.ts new file mode 100644 index 0000000..319869c --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/account/account.component.ts @@ -0,0 +1,47 @@ +import { Component, ViewChild, Output, EventEmitter, Input } from '@angular/core'; +import { NgForm } from '@angular/forms'; + +@Component({ + selector: 'ea-merchants-setup-account', + templateUrl: './account.component.html', + styleUrls: ['./account.component.scss'], +}) +export class SetupMerchantAccountComponent { + @ViewChild('accountForm', { static: true }) + accountForm: NgForm; + + @Output() + previousStep: EventEmitter = new EventEmitter(); + + @Output() + nextStep: EventEmitter = new EventEmitter(); + + @Input() showNext: boolean = true; + @Input() showPrev: boolean = true; + + accountModel = { + email: '', + username: '', + password: '', + repeatPassword: '', + }; + + get formValid() { + return ( + this.accountForm.valid && + this.accountModel.password === this.accountModel.repeatPassword + ); + } + + emailChange() { + let targetIndex = this.accountModel.email.indexOf('@'); + if (targetIndex > 0 && this.accountModel.username === '') { + let defaultUsername = this.accountModel.email.substring( + 0, + targetIndex + ); + + this.accountModel.username = defaultUsername; + } + } +} diff --git a/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/basic-info/basic-info.component.html b/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/basic-info/basic-info.component.html new file mode 100644 index 0000000..f49ae13 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/basic-info/basic-info.component.html @@ -0,0 +1,179 @@ + + + {{ 'FAKE_DATA.SETUP_MERCHANTS.BASIC_INFO.BASIC_INFO' | translate }} + + +
+
+ + + + + {{ + 'FAKE_DATA.SETUP_MERCHANTS.BASIC_INFO.NAME_IS_REQUIRED' + | translate + }} + + + {{ + 'FAKE_DATA.SETUP_MERCHANTS.BASIC_INFO.NAME_MUST_BE_AT_LEAST_4_CHARACTERS' + | translate + }} + + +
+
+ + + + + + {{ + 'FAKE_DATA.SETUP_MERCHANTS.BASIC_INFO.ENTER_A_VALID_LOGO_URL_OR_BROWSE_FROM_A_DEVICE' + | translate + }} + + +
+
+
+
+ Invalid image +
+ +
+
+
+
+ +
+
+ + + + + {{ + 'FAKE_DATA.SETUP_MERCHANTS.BASIC_INFO.BARCODE_DATA_IS_REQUIRED' + | translate + }} + + +
+
+
+ + Invalid image + +
+
+
+
+
+
+ + + diff --git a/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/basic-info/basic-info.component.scss b/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/basic-info/basic-info.component.scss new file mode 100644 index 0000000..8429223 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/basic-info/basic-info.component.scss @@ -0,0 +1,34 @@ +.setup-merchant-basic-info { + nb-card-body { + text-align: left; + } + + .preview-img { + padding-right: 16px; + } + + .preview-barcode { + padding: 8px; + background: #dedede; + display: inline-block; + margin-top: 3px; + img { + margin: 0; + } + } + + .img-rounded { + margin-top: 3px; + max-height: 70px; + } + + .remove-icon { + cursor: pointer; + + span { + padding-right: 7px; + position: absolute; + font-size: 1.1em; + } + } +} diff --git a/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/basic-info/basic-info.component.ts b/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/basic-info/basic-info.component.ts new file mode 100644 index 0000000..3e1eef7 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/basic-info/basic-info.component.ts @@ -0,0 +1,103 @@ +import { + Component, + ViewChild, + OnInit, + OnDestroy, + Output, + EventEmitter, +} from '@angular/core'; +import { NgForm, NgModel } from '@angular/forms'; +import * as QRCode from 'qrcode'; +import { TranslateService } from '@ngx-translate/core'; +import { takeUntil } from 'rxjs/operators'; +import { Subject } from 'rxjs'; +import { getDummyImage } from '@modules/server.common/utils'; + +@Component({ + selector: 'ea-merchants-setup-basic-info', + templateUrl: './basic-info.component.html', + styleUrls: ['./basic-info.component.scss'], +}) +export class SetupMerchantBasicInfoComponent implements OnInit, OnDestroy { + @ViewChild('basicInfoForm', { static: true }) + basicInfoForm: NgForm; + + @ViewChild('name') + name: NgModel; + + @Output() + previousStep: EventEmitter = new EventEmitter(); + @Output() + nextStep: EventEmitter = new EventEmitter(); + + private _ngDestroy$ = new Subject(); + + // TODO add translate + uploaderPlaceholder: string = 'Photo (optional)'; + barcodeDataUrl: string; + invalidUrl: boolean; + basicInfoModel = { + name: '', + logo: '', + barcodeData: '', + }; + + constructor(private translateService: TranslateService) {} + + ngOnInit(): void { + this.getUploaderPlaceholderText(); + } + + get basicInfoCreateObj() { + const model = { ...this.basicInfoModel }; + if (!model.logo && model.name) { + const letter = model.name.charAt(0).toUpperCase(); + const pictureUrl = getDummyImage(300, 300, letter); + model.logo = pictureUrl; + } + return model; + } + + get formValid() { + return ( + this.basicInfoForm.valid && + (this.basicInfoModel.logo === '' || !this.invalidUrl) + ); + } + + deleteImg() { + this.basicInfoModel.logo = ''; + } + + nameChange() { + if (this.name.valid && this.basicInfoModel.barcodeData === '') { + this.basicInfoModel.barcodeData = this.name.value; + + this.barcodeDataChange(); + } + } + + async barcodeDataChange() { + if (this.basicInfoModel.barcodeData) { + this.barcodeDataUrl = await QRCode.toDataURL( + this.basicInfoModel.barcodeData + ); + } else { + this.barcodeDataUrl = null; + } + } + + getUploaderPlaceholderText() { + this.translateService + .stream('FAKE_DATA.SETUP_MERCHANTS.BASIC_INFO.PHOTO_OPTIONAL') + .pipe(takeUntil(this._ngDestroy$)) + .subscribe((text) => { + this.uploaderPlaceholder = text; + }); + } + + ngOnDestroy() { + this._ngDestroy$.next(); + this._ngDestroy$.complete(); + } +} diff --git a/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/components.module.ts b/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/components.module.ts new file mode 100644 index 0000000..87b1cb2 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/components.module.ts @@ -0,0 +1,45 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { ThemeModule } from '@app/@theme'; +import { TranslateModule } from '@ngx-translate/core'; +import { FormsModule } from '@angular/forms'; +import { FileUploaderModule } from '@app/@shared/file-uploader/file-uploader.module'; +import { MerchantsSetupInstructionsComponent } from './instructions/instructions.component'; +import { SetupMerchantAccountComponent } from './account/account.component'; +import { SetupMerchantBasicInfoComponent } from './basic-info/basic-info.component'; +import { SetupMerchantContactInfoComponent } from './contact-info/contact-info.component'; +import { SetupMerchantManufacturingComponent } from './manufacturing/manufacturing.component'; +import { SetupMerchantOrdersSettingsComponent } from './settings/orders/orders.component'; +import { NbRadioModule, NbButtonModule } from '@nebular/theme'; +import { SetupMerchantProductCategoriesComponent } from './product-categories/product-categories.component'; +import { ProductCategoriesFormsModule } from '@app/@shared/product/categories/forms/product-categories-forms.module'; +import { NotifyService } from '@app/@core/services/notify/notify.service'; +import { NgxBarcodeModule } from '@modules/client.common.angular2/components/ngx-barcode/ngx-barcode.module'; + +const COMPONENTS = [ + SetupMerchantAccountComponent, + SetupMerchantBasicInfoComponent, + SetupMerchantContactInfoComponent, + MerchantsSetupInstructionsComponent, + SetupMerchantManufacturingComponent, + SetupMerchantOrdersSettingsComponent, + SetupMerchantProductCategoriesComponent, +]; + +@NgModule({ + imports: [ + CommonModule, + ThemeModule, + FormsModule, + NbRadioModule, + TranslateModule.forChild(), + FileUploaderModule, + ProductCategoriesFormsModule, + NgxBarcodeModule, + NbButtonModule, + ], + declarations: COMPONENTS, + exports: COMPONENTS, + providers: [NotifyService], +}) +export class SetupMerchantsComponentsModule {} diff --git a/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/contact-info/contact-info.component.html b/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/contact-info/contact-info.component.html new file mode 100644 index 0000000..dbee991 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/contact-info/contact-info.component.html @@ -0,0 +1,206 @@ + + + {{ 'FAKE_DATA.SETUP_MERCHANTS.CONTACT_INFO.CONTACT_INFO' | translate }} + + +
+
+ + + + + {{ + 'FAKE_DATA.SETUP_MERCHANTS.CONTACT_INFO.INVALID_PHONE_NUMBER_FORMAT' + | translate + }} + + +
+ +
+ + {{ + 'FAKE_DATA.SETUP_MERCHANTS.CONTACT_INFO.ORDER_FORWARDING_EMAIL' + | translate + }} + +
+
+ + {{ + 'FAKE_DATA.SETUP_MERCHANTS.CONTACT_INFO.ORDER_FORWARDING_PHONE' + | translate + }} + +
+ +
+ + + + + {{ + 'FAKE_DATA.SETUP_MERCHANTS.CONTACT_INFO.ORDERS_EMAIL_IS_REQUIRED' + | translate + }} + + + {{ + 'FAKE_DATA.SETUP_MERCHANTS.CONTACT_INFO.INVALID_EMAIL_FORMAT' + | translate + }} + + +
+ +
+ + + + + {{ + 'FAKE_DATA.SETUP_MERCHANTS.CONTACT_INFO.ORDERS_PHONE_IS_REQUIRED' + | translate + }} + + + {{ + 'FAKE_DATA.SETUP_MERCHANTS.CONTACT_INFO.INVALID_PHONE_NUMBER_FORMAT' + | translate + }} + + +
+
+
+ + + diff --git a/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/contact-info/contact-info.component.scss b/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/contact-info/contact-info.component.scss new file mode 100644 index 0000000..fc1d729 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/contact-info/contact-info.component.scss @@ -0,0 +1,5 @@ +.setup-merchant-contact-info { + nb-card-body { + text-align: left; + } +} diff --git a/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/contact-info/contact-info.component.ts b/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/contact-info/contact-info.component.ts new file mode 100644 index 0000000..82833c8 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/contact-info/contact-info.component.ts @@ -0,0 +1,26 @@ +import { Component, ViewChild, Output, EventEmitter } from '@angular/core'; +import { NgForm } from '@angular/forms'; + +@Component({ + selector: 'ea-merchants-setup-contact-info', + templateUrl: './contact-info.component.html', + styleUrls: ['./contact-info.component.scss'], +}) +export class SetupMerchantContactInfoComponent { + @ViewChild('contactInfoForm', { static: true }) + contactInfoForm: NgForm; + + @Output() + previousStep: EventEmitter = new EventEmitter(); + @Output() + nextStep: EventEmitter = new EventEmitter(); + + forwardingEmail: boolean; + forwardingPhone: boolean; + contactInfoModel = { + contactPhone: '', + forwardOrdersUsing: [], + ordersEmail: '', + ordersPhone: '', + }; +} diff --git a/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/instructions/instructions.component.html b/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/instructions/instructions.component.html new file mode 100644 index 0000000..29dc854 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/instructions/instructions.component.html @@ -0,0 +1,43 @@ + + +
+ {{ + 'FAKE_DATA.SETUP_MERCHANTS.HOW_TO_SET_UP.HOW_TO_SET_UP' + | translate + }} + {{ stepper.selected.label }}? +
+
+ +
+ TODO add instructions for Account +
+
+ TODO add instructions for Basic info +
+
+ TODO add instructions for Contact info +
+
+ TODO add instructions for Location +
+
+ TODO add instructions for Payments +
+
+ TODO add instructions for Manufacturing +
+
+ TODO add instructions for Delivery & Takeaway Settings +
+
+ TODO add instructions for Orders Settings +
+
+ TODO add instructions for Product categories +
+
+ TODO add instructions for Products +
+
+
diff --git a/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/instructions/instructions.component.ts b/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/instructions/instructions.component.ts new file mode 100644 index 0000000..a1b1d8a --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/instructions/instructions.component.ts @@ -0,0 +1,13 @@ +import { Component, Input } from '@angular/core'; +import { NbStepperComponent } from '@nebular/theme'; + +@Component({ + selector: 'ea-merchants-setup-instructions', + templateUrl: './instructions.component.html', +}) +export class MerchantsSetupInstructionsComponent { + @Input() + stepper: NbStepperComponent; + + constructor() {} +} diff --git a/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/location/location.component.html b/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/location/location.component.html new file mode 100644 index 0000000..4dfc2db --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/location/location.component.html @@ -0,0 +1,27 @@ + + + {{ 'FAKE_DATA.SETUP_MERCHANTS.LOCATION.LOCATION' | translate }} + + + + + + + + + + + diff --git a/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/location/location.component.scss b/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/location/location.component.scss new file mode 100644 index 0000000..17d2250 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/location/location.component.scss @@ -0,0 +1,5 @@ +.setup-merchant-location { + nb-card-body { + text-align: left; + } +} diff --git a/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/location/location.component.ts b/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/location/location.component.ts new file mode 100644 index 0000000..a9e1472 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/location/location.component.ts @@ -0,0 +1,25 @@ +import { Component, EventEmitter, ViewChild, Output } from '@angular/core'; +import { LocationFormComponent } from '@app/@shared/forms/location'; +import { FormBuilder } from '@angular/forms'; + +@Component({ + selector: 'ea-merchants-setup-location', + templateUrl: './location.component.html', + styleUrls: ['./location.component.scss'], +}) +export class SetupMerchantLocationComponent { + @ViewChild('locationForm', { static: true }) + locationForm: LocationFormComponent; + + @Output() + previousStep: EventEmitter = new EventEmitter(); + @Output() + nextStep: EventEmitter = new EventEmitter(); + + location = LocationFormComponent.buildForm(this.formBuilder); + + mapCoordEmitter = new EventEmitter(); + mapGeometryEmitter = new EventEmitter(); + + constructor(private readonly formBuilder: FormBuilder) {} +} diff --git a/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/location/location.module.ts b/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/location/location.module.ts new file mode 100644 index 0000000..8b13c18 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/location/location.module.ts @@ -0,0 +1,26 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { ThemeModule } from '@app/@theme'; +import { TranslateModule } from '@ngx-translate/core'; +import { FormsModule } from '@angular/forms'; +import { SetupMerchantLocationComponent } from './location.component'; +import { LocationFormModule } from '@app/@shared/forms/location'; +import { GoogleMapModule } from '@app/@shared/forms/google-map/google-map.module'; +import { NbButtonModule } from '@nebular/theme'; + +const COMPONENTS = [SetupMerchantLocationComponent]; + +@NgModule({ + imports: [ + CommonModule, + ThemeModule, + FormsModule, + TranslateModule.forChild(), + LocationFormModule, + GoogleMapModule, + NbButtonModule, + ], + declarations: COMPONENTS, + exports: COMPONENTS, +}) +export class SetupMerchantsLocationModule {} diff --git a/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/manufacturing/manufacturing.component.html b/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/manufacturing/manufacturing.component.html new file mode 100644 index 0000000..6abfb80 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/manufacturing/manufacturing.component.html @@ -0,0 +1,26 @@ + + + {{ + 'FAKE_DATA.SETUP_MERCHANTS.MANUFACTURING.MANUFACTURING' | translate + }} + + +
+
+ + {{ + 'FAKE_DATA.SETUP_MERCHANTS.MANUFACTURING.PRODUCTS_MANUFACTURING' + | translate + }} + +
+
+
+
+ + + diff --git a/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/manufacturing/manufacturing.component.scss b/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/manufacturing/manufacturing.component.scss new file mode 100644 index 0000000..d3fcaf1 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/manufacturing/manufacturing.component.scss @@ -0,0 +1,5 @@ +.setup-merchant-manufacturing { + nb-card-body { + text-align: left; + } +} diff --git a/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/manufacturing/manufacturing.component.ts b/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/manufacturing/manufacturing.component.ts new file mode 100644 index 0000000..d770bab --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/manufacturing/manufacturing.component.ts @@ -0,0 +1,15 @@ +import { Component, Output, EventEmitter } from '@angular/core'; + +@Component({ + selector: 'ea-merchants-setup-manufacturing', + templateUrl: './manufacturing.component.html', + styleUrls: ['./manufacturing.component.scss'], +}) +export class SetupMerchantManufacturingComponent { + @Output() + previousStep: EventEmitter = new EventEmitter(); + @Output() + nextStep: EventEmitter = new EventEmitter(); + + isManufacturing: boolean; +} diff --git a/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/payments/payments.component.html b/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/payments/payments.component.html new file mode 100644 index 0000000..814da1d --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/payments/payments.component.html @@ -0,0 +1,47 @@ + + + {{ 'FAKE_DATA.SETUP_MERCHANTS.PAYMENTS.PAYMENTS' | translate }} + + +
+
+ + {{ + 'FAKE_DATA.SETUP_MERCHANTS.PAYMENTS.ALLOW_ONLINE_PAYMENT' + | translate + }} + +
+
+ + {{ + 'FAKE_DATA.SETUP_MERCHANTS.PAYMENTS.ALLOW_CASH_PAYMENT' + | translate + }} + +
+
+
+
+ +
+
+
+
+ + + diff --git a/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/payments/payments.component.scss b/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/payments/payments.component.scss new file mode 100644 index 0000000..cfc3388 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/payments/payments.component.scss @@ -0,0 +1,5 @@ +.setup-merchant-payments { + nb-card-body { + text-align: left; + } +} diff --git a/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/payments/payments.component.ts b/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/payments/payments.component.ts new file mode 100644 index 0000000..fe849a8 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/payments/payments.component.ts @@ -0,0 +1,53 @@ +import { + Component, + EventEmitter, + Output, + Input, + ViewChild, + OnInit, +} from '@angular/core'; +import { LocationFormComponent } from '@app/@shared/forms/location'; +import IPaymentGatewayCreateObject from '@modules/server.common/interfaces/IPaymentGateway'; +import { PaymentGatewaysComponent } from '@app/@shared/payment-gateways/payment-gateways.component'; + +@Component({ + selector: 'ea-merchants-setup-payments', + templateUrl: './payments.component.html', + styleUrls: ['./payments.component.scss'], +}) +export class SetupMerchantPaymentsComponent implements OnInit { + @ViewChild('paymentGateways', { static: true }) + paymentGateways: PaymentGatewaysComponent; + @Output() + previousStep: EventEmitter = new EventEmitter(); + @Output() + nextStep: EventEmitter = new EventEmitter(); + + @Input() + warehouseLogo: string; + @Input() + locationForm: LocationFormComponent; + + isPaymentEnabled = false; + isCashPaymentEnabled = true; + + ngOnInit() { + console.warn(this.isCashPaymentEnabled); + } + + get isPaymentValid() { + return !this.isPaymentEnabled || this.paymentGateways.isValid; + } + + get paymentsGateways(): IPaymentGatewayCreateObject[] { + return this.paymentGateways.paymentsGateways; + } + + isCashAllowed(ev) { + this.isCashPaymentEnabled = ev; + } + + isOnlinePaymentAllowed(ev) { + this.isPaymentEnabled = ev; + } +} diff --git a/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/payments/payments.module.ts b/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/payments/payments.module.ts new file mode 100644 index 0000000..848105b --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/payments/payments.module.ts @@ -0,0 +1,22 @@ +import { CommonModule } from '@angular/common'; +import { ThemeModule } from '@app/@theme'; +import { FormsModule } from '@angular/forms'; +import { TranslateModule } from '@ngx-translate/core'; +import { SetupMerchantPaymentsComponent } from './payments.component'; +import { NgModule } from '@angular/core'; +import { PaymentGatewaysModule } from '@app/@shared/payment-gateways/payment-gateways.module'; +import { NbButtonModule } from '@nebular/theme'; + +@NgModule({ + imports: [ + CommonModule, + ThemeModule, + FormsModule, + TranslateModule.forChild(), + PaymentGatewaysModule, + NbButtonModule, + ], + declarations: [SetupMerchantPaymentsComponent], + exports: [SetupMerchantPaymentsComponent], +}) +export class SetupMerchantsPaymentsModule {} diff --git a/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/product-categories/product-categories.component.html b/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/product-categories/product-categories.component.html new file mode 100644 index 0000000..54a38d2 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/product-categories/product-categories.component.html @@ -0,0 +1,82 @@ + + + {{ + 'FAKE_DATA.SETUP_MERCHANTS.PRODUCT_CATEGORIES.PRODUCT_CATEGORIES' + | translate + }} + + +
+
+ +
+ +
+ +
+ +
+ +
+
+
+
+ +
+ + + +
+ +
+ + + + + +
diff --git a/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/product-categories/product-categories.component.scss b/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/product-categories/product-categories.component.scss new file mode 100644 index 0000000..8376a4c --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/product-categories/product-categories.component.scss @@ -0,0 +1,5 @@ +.setup-merchant-product-categories { + nb-card-body { + text-align: left; + } +} diff --git a/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/product-categories/product-categories.component.ts b/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/product-categories/product-categories.component.ts new file mode 100644 index 0000000..6385300 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/product-categories/product-categories.component.ts @@ -0,0 +1,145 @@ +import { Component, ViewChild, Output, EventEmitter } from '@angular/core'; +import { first } from 'rxjs/operators'; +import { ProductsCategoryService } from '@app/@core/data/productsCategory.service'; +import { ILocaleMember } from '@modules/server.common/interfaces/ILocale'; +import { ProductLocalesService } from '@modules/client.common.angular2/locale/product-locales.service'; +import { BasicInfoFormComponent } from '@app/@shared/product/categories/forms/basic-info'; +import { NotifyService } from '@app/@core/services/notify/notify.service'; +import ProductsCategory from '@modules/server.common/entities/ProductsCategory'; +import { CategoriesTableComponent } from '@app/@shared/product/categories/categories-table'; + +@Component({ + selector: 'ea-merchants-setup-product-categories', + templateUrl: './product-categories.component.html', + styleUrls: ['./product-categories.component.scss'], +}) +export class SetupMerchantProductCategoriesComponent { + @ViewChild('basicInfo') + basicInfo: BasicInfoFormComponent; + + @ViewChild('categoriesTable') + categoriesTable: CategoriesTableComponent; + + @Output() + previousStep: EventEmitter = new EventEmitter(); + @Output() + nextStep: EventEmitter = new EventEmitter(); + + currentCategory: ProductsCategory; + productCategories: ProductsCategory[] = []; + showPerPage = 3; + + mutationType: 'add' | 'edit' = 'add'; + private _showMutationForm: boolean = false; + + constructor( + private productsCategoryService: ProductsCategoryService, + private readonly notifyService: NotifyService, + private readonly productLocalesService: ProductLocalesService + ) {} + + get isValidForm() { + let res = false; + if (this.basicInfo) { + res = this.basicInfo.form.valid; + } + + return res; + } + + get isInvalidValidForm() { + let res = false; + if (this.basicInfo) { + res = this.basicInfo.form.invalid; + } + + return res; + } + + get showMutationForm() { + return this._showMutationForm; + } + + set showMutationForm(isShow: boolean) { + this._showMutationForm = isShow; + this.currentCategory = null; + this.mutationType = 'add'; + if (!isShow) { + this.loadCategories(); + } + } + + async add() { + try { + const category = await this.productsCategoryService + .create(this.basicInfo.createObject) + .pipe(first()) + .toPromise(); + this.productCategories.unshift(category); + + const message = `Category ${this.localeTranslate( + this.basicInfo.createObject.name + )} is added!`; + this.notifyService.success(message); + + this.showMutationForm = false; + } catch (err) { + const message = `Something went wrong!`; + const body = err.message + ? '\n' + `Error message: ${err.message}` + : ''; + this.notifyService.error(message, body); + } + } + + async edit() { + try { + const editObj = this.basicInfo.getEditObject(this.currentCategory); + const category = await this.productsCategoryService + .update(this.currentCategory.id, editObj) + .pipe(first()) + .toPromise(); + this.productCategories = this.productCategories.filter( + (c) => c.id !== category.id + ); + this.productCategories.unshift(category); + + const message = `Category ${this.localeTranslate( + this.basicInfo.createObject.name + )} is edited`; + this.notifyService.success(message); + + this.showMutationForm = false; + } catch (err) { + const message = `Something went wrong!`; + const body = err.message + ? '\n' + `Error message: ${err.message}` + : ''; + this.notifyService.error(message, body); + } + } + + loadCategories() { + if (this.productCategories.length > 0) { + this.categoriesTable.loadDataSmartTable(this.productCategories); + } + } + + removeCategory(category) { + this.productCategories = this.productCategories.filter( + (c) => c.id !== category.id + ); + + this.loadCategories(); + } + + editCategory(category) { + this.showMutationForm = true; + this.mutationType = 'edit'; + this.currentCategory = category; + } + + localeTranslate(member: ILocaleMember[]) { + return this.productLocalesService.getTranslate(member); + } +} diff --git a/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/products/add-products/add-products.component.html b/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/products/add-products/add-products.component.html new file mode 100644 index 0000000..a9c1d44 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/products/add-products/add-products.component.html @@ -0,0 +1,4 @@ + diff --git a/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/products/add-products/add-products.component.ts b/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/products/add-products/add-products.component.ts new file mode 100644 index 0000000..a886a40 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/products/add-products/add-products.component.ts @@ -0,0 +1,67 @@ +import { + Component, + ViewChild, + OnInit, + Input, + Output, + EventEmitter, +} from '@angular/core'; +import { AddWarehouseProductsComponent } from '@app/@shared/warehouse-product/forms/add-warehouse-products-table'; +import Product from '@modules/server.common/entities/Product'; +import { WarehousesService } from '@app/@core/data/warehouses.service'; +import { first } from 'rxjs/operators'; +import { NotifyService } from '@app/@core/services/notify/notify.service'; + +@Component({ + selector: 'ea-merchants-setup-add-products', + templateUrl: './add-products.component.html', +}) +export class SetupMerchantAddProductsComponent implements OnInit { + @ViewChild('addWarehouseProductsTable', { static: true }) + addWarehouseProductsTable: AddWarehouseProductsComponent; + + @Input() + products: Product[]; + @Input() + storeId: string; + + @Output() + successAdd: EventEmitter = new EventEmitter(); + + constructor( + private warehousesService: WarehousesService, + private notifyService: NotifyService + ) {} + + ngOnInit(): void { + this.addWarehouseProductsTable.loadDataSmartTable( + this.products || [], + this.storeId + ); + } + + async add() { + try { + const productsForAdd = this.addWarehouseProductsTable + .allWarehouseProducts; + + await this.warehousesService + .addProducts(this.storeId, productsForAdd) + .pipe(first()) + .toPromise(); + + this.successAdd.emit(true); + + const message = `${productsForAdd.length} products was added`; + this.notifyService.success(message); + } catch (error) { + let message = `Something went wrong`; + + if (error.message === 'Validation error') { + message = error.message; + } + + this.notifyService.error(message); + } + } +} diff --git a/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/products/product-mutation/product-mutation.component.html b/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/products/product-mutation/product-mutation.component.html new file mode 100644 index 0000000..f763482 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/products/product-mutation/product-mutation.component.html @@ -0,0 +1,7 @@ + diff --git a/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/products/product-mutation/product-mutation.component.ts b/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/products/product-mutation/product-mutation.component.ts new file mode 100644 index 0000000..d361761 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/products/product-mutation/product-mutation.component.ts @@ -0,0 +1,106 @@ +import { + Component, + EventEmitter, + Output, + ViewChild, + Input, +} from '@angular/core'; +import { FormGroup, FormControl, FormBuilder } from '@angular/forms'; +import { BasicInfoFormComponent } from '@app/@shared/product/forms/basic-info/basic-info-form.component'; +import ProductsCategory from '@modules/server.common/entities/ProductsCategory'; +import { ProductsCategoryService } from '@app/@core/data/productsCategory.service'; +import { first } from 'rxjs/operators'; +import { IProductCreateObject } from '@modules/server.common/interfaces/IProduct'; +import { ProductsService } from '@app/@core/data/products.service'; +import { ProductViewModel } from '@app/pages/+simulation/products/products.component'; +import { ProductLocalesService } from '@modules/client.common.angular2/locale/product-locales.service'; +import Product from '@modules/server.common/entities/Product'; + +@Component({ + selector: 'ea-merchants-setup-product-mutation', + templateUrl: './product-mutation.component.html', +}) +export class SetupMerchantProductMutationComponent { + @ViewChild('basicInfoForm', { static: true }) + basicInfoForm: BasicInfoFormComponent; + + @Output() + onCreate: EventEmitter = new EventEmitter(); + @Output() + onEdit: EventEmitter = new EventEmitter(); + + @Input() + product: Product; + + productsCategories: ProductsCategory[]; + areCategoriesLoaded: boolean = false; + + readonly form: FormGroup = this._formBuilder.group({ + basicInfo: BasicInfoFormComponent.buildForm(this._formBuilder), + }); + + readonly basicInfo = this.form.get('basicInfo') as FormControl; + + constructor( + private readonly _formBuilder: FormBuilder, + private readonly _productsService: ProductsService, + private readonly _productLocalesService: ProductLocalesService, + private readonly _productsCategoryService: ProductsCategoryService + ) { + this.loadProductCategories(); + } + + async loadProductCategories() { + try { + this.productsCategories = await this._productsCategoryService + .getCategories() + .pipe(first()) + .toPromise(); + } catch (error) { + console.warn( + `Error during load product categories. message: ${error.message}` + ); + } + + this.areCategoriesLoaded = true; + } + + async create() { + const productCreateObject: IProductCreateObject = await this.basicInfoForm.setupProductCreateObject(); + try { + const product = await this._productsService + .create(productCreateObject) + .pipe(first()) + .toPromise(); + + this.onCreate.emit({ + id: product.id, + title: this._productLocalesService.getMemberValue( + product.title + ), + image: this._productLocalesService.getMemberValue( + product.images + ), + }); + } catch (error) { + console.error(error.message); + } + } + + async save() { + try { + const res = await this.basicInfoForm.setupProductCreateObject(); + await this._productsService + .save({ + _id: this.product._id, + ...res, + } as Product) + .pipe(first()) + .toPromise(); + + this.onEdit.emit(true); + } catch (error) { + console.error(error.message); + } + } +} diff --git a/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/products/products-catalog/products-catalog.component.html b/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/products/products-catalog/products-catalog.component.html new file mode 100644 index 0000000..c399dc1 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/products/products-catalog/products-catalog.component.html @@ -0,0 +1,7 @@ + + diff --git a/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/products/products-catalog/products-catalog.component.ts b/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/products/products-catalog/products-catalog.component.ts new file mode 100644 index 0000000..4e9d0ba --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/products/products-catalog/products-catalog.component.ts @@ -0,0 +1,64 @@ +import { Component, ViewChild, Input, OnInit, OnDestroy } from '@angular/core'; +import { ProductsTableComponent } from '@app/@shared/product/forms/products-table'; +import { first, takeUntil } from 'rxjs/operators'; +import { ProductsService } from '@app/@core/data/products.service'; +import { Subject } from 'rxjs'; + +@Component({ + selector: 'ea-merchants-setup-products-catalog', + templateUrl: './products-catalog.component.html', +}) +export class SetupMerchantProductsCatalogComponent + implements OnInit, OnDestroy { + @ViewChild('productsTable', { static: true }) + productsTable: ProductsTableComponent; + + @Input() + existedProductsIds: string[]; + perPage = 3; + + private ngDestroy$ = new Subject(); + + constructor(private readonly productsService: ProductsService) {} + + ngOnInit(): void { + this.loadData(); + this.smartTablePageChange(); + } + + ngOnDestroy() { + this.ngDestroy$.next(); + this.ngDestroy$.complete(); + } + + private async loadData(page = 1) { + let products = await this.productsService + .getProducts( + { + skip: this.perPage * (page - 1), + limit: this.perPage, + }, + this.existedProductsIds + ) + .pipe(first()) + .toPromise(); + + const dataCount = await this.getDataCount(this.existedProductsIds); + + this.productsTable.loadDataSmartTable(products, dataCount, page); + } + + private async smartTablePageChange() { + if (this.productsTable) { + this.productsTable.pagesChanges$ + .pipe(takeUntil(this.ngDestroy$)) + .subscribe((page) => { + this.loadData(page); + }); + } + } + + private async getDataCount(existedProductsIds: string[]) { + return this.productsService.getCountOfProducts(existedProductsIds); + } +} diff --git a/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/products/products.component.html b/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/products/products.component.html new file mode 100644 index 0000000..e819d86 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/products/products.component.html @@ -0,0 +1,151 @@ + + + + {{ 'FAKE_DATA.SETUP_MERCHANTS.PRODUCTS.PRODUCTS' | translate }} + + + {{ + 'FAKE_DATA.SETUP_MERCHANTS.PRODUCTS.SELECT_FROM_PRODUCTS_CATALOG' + | translate + }} + + + {{ + 'FAKE_DATA.SETUP_MERCHANTS.PRODUCTS.CREATE_PRODUCT' | translate + }} + + + {{ 'FAKE_DATA.SETUP_MERCHANTS.PRODUCTS.EDIT_PRODUCT' | translate }} + + + {{ 'FAKE_DATA.SETUP_MERCHANTS.PRODUCTS.ADD_PRODUCT' | translate }} + + + +
+
+
+ +
+ +
+ +
+ +
+ +
+
+
+ +
+ +
+ +
+
+ +
+
+ +
+ +
+ +
+ + +
+
+ + + + + + + + +
diff --git a/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/products/products.component.scss b/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/products/products.component.scss new file mode 100644 index 0000000..b95f9a6 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/products/products.component.scss @@ -0,0 +1,20 @@ +.setup-merchant-products { + nb-card-body { + text-align: left; + } + + .actions { + button { + width: 100%; + margin: 0; + } + } + + ea-merchants-setup-add-products { + width: 100%; + } + + .btn { + height: 100%; + } +} diff --git a/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/products/products.component.ts b/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/products/products.component.ts new file mode 100644 index 0000000..046cfff --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/products/products.component.ts @@ -0,0 +1,184 @@ +import { + Component, + ViewChild, + Output, + EventEmitter, + OnDestroy, + Input, + OnChanges, +} from '@angular/core'; +import { SetupMerchantProductsCatalogComponent } from './products-catalog/products-catalog.component'; +import { SetupMerchantAddProductsComponent } from './add-products/add-products.component'; +import WarehouseProduct from '@modules/server.common/entities/WarehouseProduct'; +import { WarehouseProductsRouter } from '@modules/client.common.angular2/routers/warehouse-products-router.service'; +import { Subject, Subscription } from 'rxjs'; +import { takeUntil, first } from 'rxjs/operators'; +import { WarehouseProductsComponent } from '@app/@shared/warehouse-product/forms/warehouse-products-table'; +import { SetupMerchantProductMutationComponent } from './product-mutation/product-mutation.component'; +import { WarehouseRouter } from '@modules/client.common.angular2/routers/warehouse-router.service'; +import { NotifyService } from '@app/@core/services/notify/notify.service'; +import Product from '@modules/server.common/entities/Product'; +import { Router } from '@angular/router'; + +@Component({ + selector: 'ea-merchants-setup-products', + templateUrl: './products.component.html', + styleUrls: ['./products.component.scss'], +}) +export class SetupMerchantProductsComponent implements OnChanges, OnDestroy { + @ViewChild('productsCatalog') + productsCatalog: SetupMerchantProductsCatalogComponent; + @ViewChild('addProducts') + addProducts: SetupMerchantAddProductsComponent; + @ViewChild('productsTable') + productsTable: WarehouseProductsComponent; + @ViewChild('productMutation') + productMutation: SetupMerchantProductMutationComponent; + + @Output() + previousStep: EventEmitter = new EventEmitter(); + @Output() + nextStep: EventEmitter = new EventEmitter(); + + @Input() + storeId: string; + + componentViews = { + main: 'main', + productsTable: 'productsTable', + createProduct: 'createProduct', + editProduct: 'editProduct', + addProducts: 'addProducts', + }; + productsPerPage = 3; + showProductsTable: boolean = false; + currentProduct: Product; + + productsForAdd = []; + storeProducts: WarehouseProduct[]; + + private _currentView = this.componentViews.main; + private _prevView = this.componentViews.main; + private _ngDestroy$ = new Subject(); + private products$: Subscription; + + constructor( + private warehouseProductsRouter: WarehouseProductsRouter, + private warehouseRouter: WarehouseRouter, + private notifyService: NotifyService, + private router: Router + ) {} + + get haveProductsForAdd() { + let hasSelectedCarriers = false; + + if (this.productsCatalog) { + hasSelectedCarriers = this.productsCatalog.productsTable + .hasSelectedProducts; + } + + return hasSelectedCarriers; + } + + get currentView() { + return this._currentView; + } + + set currentView(view: string) { + this._prevView = this.currentView; + this._currentView = view; + } + + get existedProductsIds() { + let ids = []; + if (this.storeProducts) { + ids = this.storeProducts.map((p) => p.productId); + } + + return ids; + } + + select(products) { + this.productsForAdd = products; + this.currentView = this.componentViews.addProducts; + } + + back() { + if (this.currentView === this.componentViews.addProducts) { + this.currentView = this._prevView; + return; + } + + this.currentProduct = null; + this.currentView = this.componentViews.main; + } + + editProduct({ data }) { + this.currentProduct = data.product; + this.currentView = this.componentViews.editProduct; + } + + async removeProduct({ data }) { + try { + if (this.storeId) { + const store = await this.warehouseRouter + .get(this.storeId, true) + .pipe(first()) + .toPromise(); + + store.products = store.products.filter( + (p: WarehouseProduct) => p.productId !== data.id + ); + + await this.warehouseRouter.save(store); + } else { + throw new Error("Store id don't exist"); + } + } catch (error) { + const message = `Can't remove products error: ${error.message}`; + this.notifyService.error(message); + } + } + + async loadProducts() { + if (this.storeId) { + if (this.products$) { + await this.products$.unsubscribe(); + } + + this.products$ = this.warehouseProductsRouter + .get(this.storeId) + .pipe(takeUntil(this._ngDestroy$)) + .subscribe((products) => { + this.showProductsTable = products.length > 0; + + this.productsTable.loadDataSmartTable( + products, + this.storeId + ); + + this.storeProducts = products; + }); + } + } + + updateMain() { + this.currentProduct = null; + this.currentView = this.componentViews.main; + this.loadProducts(); + } + + finish() { + this.nextStep.emit(); + this.router.navigateByUrl('/setup'); + } + + ngOnChanges(): void { + this.loadProducts(); + } + + ngOnDestroy(): void { + this._ngDestroy$.next(); + this._ngDestroy$.complete(); + } +} diff --git a/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/products/products.module.ts b/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/products/products.module.ts new file mode 100644 index 0000000..46c5605 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/products/products.module.ts @@ -0,0 +1,34 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { ThemeModule } from '@app/@theme'; +import { FormsModule } from '@angular/forms'; +import { TranslateModule } from '@ngx-translate/core'; +import { SetupMerchantProductsComponent } from './products.component'; +import { SetupMerchantProductsCatalogComponent } from './products-catalog/products-catalog.component'; +import { ProductFormsModule } from '@app/@shared/product/forms'; +import { SetupMerchantAddProductsComponent } from './add-products/add-products.component'; +import { WarehouseProductFormsModule } from '@app/@shared/warehouse-product/forms'; +import { SetupMerchantProductMutationComponent } from './product-mutation/product-mutation.component'; +import { NbButtonModule } from '@nebular/theme'; + +const COMPONENTS = [ + SetupMerchantProductsComponent, + SetupMerchantProductsCatalogComponent, + SetupMerchantAddProductsComponent, + SetupMerchantProductMutationComponent, +]; + +@NgModule({ + imports: [ + CommonModule, + ThemeModule, + FormsModule, + TranslateModule.forChild(), + ProductFormsModule, + WarehouseProductFormsModule, + NbButtonModule, + ], + declarations: COMPONENTS, + exports: COMPONENTS, +}) +export class SetupMerchantsProductsModule {} diff --git a/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/settings/delivery-takeaway/add-new-carrier/add-new-carrier.component.html b/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/settings/delivery-takeaway/add-new-carrier/add-new-carrier.component.html new file mode 100644 index 0000000..8cfe951 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/settings/delivery-takeaway/add-new-carrier/add-new-carrier.component.html @@ -0,0 +1,6 @@ + diff --git a/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/settings/delivery-takeaway/add-new-carrier/add-new-carrier.component.ts b/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/settings/delivery-takeaway/add-new-carrier/add-new-carrier.component.ts new file mode 100644 index 0000000..ee987da --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/settings/delivery-takeaway/add-new-carrier/add-new-carrier.component.ts @@ -0,0 +1,58 @@ +import { + Component, + ViewChild, + Input, + AfterViewInit, + OnInit, +} from '@angular/core'; +import { BasicInfoFormComponent } from '@app/@shared/carrier/forms/basic-info/basic-info-form.component'; +import { FormGroup, FormControl, FormBuilder } from '@angular/forms'; +import { CarrierRouter } from '@modules/client.common.angular2/routers/carrier-router.service'; +import { first } from 'rxjs/operators'; + +@Component({ + selector: 'ea-merchants-setup-add-new-carrier', + templateUrl: './add-new-carrier.component.html', +}) +export class SetupMerchantAddNewCarrierComponent + implements AfterViewInit, OnInit { + @ViewChild('basicInfoForm', { static: true }) + basicInfoForm: BasicInfoFormComponent; + + @Input() + carrierId: string; + + form: FormGroup = this.formBuilder.group({ + basicInfo: BasicInfoFormComponent.buildForm(this.formBuilder), + password: BasicInfoFormComponent.buildPasswordForm(this.formBuilder), + }); + + readonly basicInfo = this.form.get('basicInfo') as FormControl; + password: FormControl; + + constructor( + private readonly formBuilder: FormBuilder, + private carrierRouter: CarrierRouter + ) {} + + ngOnInit(): void { + if (!this.carrierId) { + this.password = this.form.get('password') as FormControl; + } + } + + ngAfterViewInit(): void { + this.laodData(); + } + + private async laodData() { + if (this.carrierId) { + const carrier = await this.carrierRouter + .get(this.carrierId) + .pipe(first()) + .toPromise(); + + this.basicInfoForm.setValue(carrier); + } + } +} diff --git a/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/settings/delivery-takeaway/delivery-takeaway.component.html b/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/settings/delivery-takeaway/delivery-takeaway.component.html new file mode 100644 index 0000000..685d8b4 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/settings/delivery-takeaway/delivery-takeaway.component.html @@ -0,0 +1,157 @@ + + + {{ + 'FAKE_DATA.SETUP_MERCHANTS.DELIVERY_AND_TAKEAWAY_SETTINGS.DELIVERY_AND_TAKEAWAY_SETTINGS' + | translate + }} + + + {{ + 'FAKE_DATA.SETUP_MERCHANTS.DELIVERY_AND_TAKEAWAY_SETTINGS.SELECT_FROM_SHARED_CARRIERS' + | translate + }} + + + {{ + 'FAKE_DATA.SETUP_MERCHANTS.DELIVERY_AND_TAKEAWAY_SETTINGS.ADD_YOUR_CARRIER' + | translate + }} + + + {{ + 'FAKE_DATA.SETUP_MERCHANTS.DELIVERY_AND_TAKEAWAY_SETTINGS.EDIT_CARRIER' + | translate + }} + + + +
+
+
+ + {{ + 'FAKE_DATA.SETUP_MERCHANTS.DELIVERY_AND_TAKEAWAY_SETTINGS.CARRIER_REQUIRED' + | translate + }} + +
+
+
+
+ + {{ + 'FAKE_DATA.SETUP_MERCHANTS.DELIVERY_AND_TAKEAWAY_SETTINGS.PRODUCTS_DELIVERY_BY_DEFAULT' + | translate + }} + +
+
+ + {{ + 'FAKE_DATA.SETUP_MERCHANTS.DELIVERY_AND_TAKEAWAY_SETTINGS.PRODUCTS_TAKEAWAY_BY_DEFAULT' + | translate + }} + +
+
+
+
+ +
+ +
+ +
+ +
+ +
+
+
+ +
+ +
+ +
+ +
+
+
+ +
+ + +
+
+ + + + +
diff --git a/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/settings/delivery-takeaway/delivery-takeaway.component.scss b/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/settings/delivery-takeaway/delivery-takeaway.component.scss new file mode 100644 index 0000000..d6cc720 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/settings/delivery-takeaway/delivery-takeaway.component.scss @@ -0,0 +1,20 @@ +.setup-merchant-delivery-takeaway { + nb-card-body { + text-align: left; + } + + .actions { + button { + width: 100%; + margin: 0; + } + } + + ea-merchants-setup-add-new-carrier { + margin: auto; + } + + .btn { + height: 100%; + } +} diff --git a/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/settings/delivery-takeaway/delivery-takeaway.component.ts b/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/settings/delivery-takeaway/delivery-takeaway.component.ts new file mode 100644 index 0000000..a261ff4 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/settings/delivery-takeaway/delivery-takeaway.component.ts @@ -0,0 +1,182 @@ +import { + Component, + ViewChild, + OnDestroy, + AfterViewInit, + Input, + Output, + EventEmitter, +} from '@angular/core'; +import { SetupMerchantSharedCarriersComponent } from './shared-carriers/shared-carriers.component'; +import { + CarriersSmartTableComponent, + CarrierSmartTableObject, +} from '@app/@shared/carrier/carriers-table/carriers-table.component'; +import { Subject } from 'rxjs'; +import { SetupMerchantAddNewCarrierComponent } from './add-new-carrier/add-new-carrier.component'; +import { getDummyImage } from '@modules/server.common/utils'; +import { CarrierRouter } from '@modules/client.common.angular2/routers/carrier-router.service'; + +@Component({ + selector: 'ea-merchants-setup-delivery-takeaway', + templateUrl: './delivery-takeaway.component.html', + styleUrls: ['./delivery-takeaway.component.scss'], +}) +export class SetupMerchantDeliveryAndTakeawayComponent + implements AfterViewInit, OnDestroy { + @ViewChild('newCarrier') + newCarrier: SetupMerchantAddNewCarrierComponent; + @ViewChild('sharedCarriers') + sharedCarriers: SetupMerchantSharedCarriersComponent; + @ViewChild('carriersTable') + carriersTable: CarriersSmartTableComponent; + + @Output() + previousStep: EventEmitter = new EventEmitter(); + @Output() + nextStep: EventEmitter = new EventEmitter(); + + @Input() + locationForm: any; + + componentViews = { + main: 'main', + carriersTable: 'carriersTable', + addNewCarrier: 'addNewCarrier', + editCarrier: 'editCarrier', + }; + currentView = this.componentViews.main; + carriersPerPage = 3; + carrierId: string; + + isCarrierRequired: boolean; + productsDelivery: boolean; + productsTakeaway: boolean; + + restrictedCarriers: CarrierSmartTableObject[] = []; + + private ngDestroy$ = new Subject(); + + constructor(private carrierRouter: CarrierRouter) {} + + get haveCarriersForAdd() { + let hasSelectedCarriers = false; + + if (this.sharedCarriers) { + hasSelectedCarriers = this.sharedCarriers.carriersTable + .hasSelectedCarriers; + } + + if (this.newCarrier) { + hasSelectedCarriers = + this.newCarrier.form.valid && + this.locationForm && + this.locationForm.form.valid; + } + return hasSelectedCarriers; + } + + get isBasicInfoValid() { + let res = false; + + if (this.newCarrier) { + res = this.newCarrier.basicInfoForm.form.valid; + } + + return res; + } + + get restrictedCarriersIds() { + let ids = []; + if (this.restrictedCarriers) { + ids = this.restrictedCarriers.map((c) => c.id); + } + + return ids; + } + + ngAfterViewInit(): void {} + + async add() { + if (this.currentView === this.componentViews.carriersTable) { + const carriers = this.sharedCarriers.carriersTable.selectedCarriers + .map((data) => data['carrier']) + .map(CarriersSmartTableComponent.getCarrierSmartTableObject); + + this.restrictedCarriers.unshift(...carriers); + } else if (this.currentView === this.componentViews.addNewCarrier) { + const geoLocationInput = this.locationForm.getValue(); + geoLocationInput.loc.coordinates.reverse(); + + const carrierCreateObj = { + ...this.newCarrier.basicInfoForm.getValue(), + geoLocation: geoLocationInput, + }; + + if (!carrierCreateObj.logo) { + const letter = carrierCreateObj.firstName + .charAt(0) + .toUpperCase(); + carrierCreateObj.logo = getDummyImage(300, 300, letter); + } + + let carrier = await this.carrierRouter.register({ + carrier: carrierCreateObj, + password: this.newCarrier.basicInfoForm.getPassword(), + }); + + this.restrictedCarriers.unshift( + CarriersSmartTableComponent.getCarrierSmartTableObject(carrier) + ); + } + this.currentView = this.componentViews.main; + } + + async save() { + const basicInfo = this.newCarrier.basicInfoForm.getValue(); + + const carrier = await this.carrierRouter.updateById(this.carrierId, { + ...basicInfo, + }); + + this.restrictedCarriers = this.restrictedCarriers.filter( + (c) => c.id !== this.carrierId + ); + this.restrictedCarriers.unshift( + CarriersSmartTableComponent.getCarrierSmartTableObject(carrier) + ); + this.carrierId = null; + + this.currentView = this.componentViews.main; + } + + back() { + this.currentView = this.componentViews.main; + } + + productsDeliveryChange() { + if (!this.productsDelivery) { + this.restrictedCarriers = []; + } + } + + removeCarrier(e) { + if (this.restrictedCarriers) { + this.restrictedCarriers = this.restrictedCarriers.filter( + (c) => c.id !== e.data.id + ); + } + + this.carriersTable.loadData(this.restrictedCarriers); + } + + editCarrier(e) { + this.carrierId = e.data.id; + this.currentView = this.componentViews.editCarrier; + } + + ngOnDestroy() { + this.ngDestroy$.next(); + this.ngDestroy$.complete(); + } +} diff --git a/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/settings/delivery-takeaway/delivery-takeaway.module.ts b/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/settings/delivery-takeaway/delivery-takeaway.module.ts new file mode 100644 index 0000000..b6b6213 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/settings/delivery-takeaway/delivery-takeaway.module.ts @@ -0,0 +1,40 @@ +import { SetupMerchantDeliveryAndTakeawayComponent } from './delivery-takeaway.component'; +import { CommonModule } from '@angular/common'; +import { NgModule } from '@angular/core'; +import { ThemeModule } from '@app/@theme'; +import { FormsModule } from '@angular/forms'; +import { TranslateModule } from '@ngx-translate/core'; +import { CarriersSmartTableModule } from '@app/@shared/carrier/carriers-table/carriers-table.module'; +import { SetupMerchantSharedCarriersComponent } from './shared-carriers/shared-carriers.component'; +import { CarrierLocationModule } from '@app/pages/+carriers/+carrier/location/carrier-location.module'; +import { TrackModule } from '@app/pages/+carriers/track/track.module'; +import { RenderComponentsModule } from '@app/@shared/render-component/render-components.module'; +import { CarrierMutationModule } from '@app/@shared/carrier/carrier-mutation'; +import { SetupMerchantAddNewCarrierComponent } from './add-new-carrier/add-new-carrier.component'; +import { CarrierFormsModule } from '@app/@shared/carrier/forms'; +import { NbButtonModule } from '@nebular/theme'; + +const COMPONENTS = [ + SetupMerchantDeliveryAndTakeawayComponent, + SetupMerchantSharedCarriersComponent, + SetupMerchantAddNewCarrierComponent, +]; + +@NgModule({ + imports: [ + CommonModule, + ThemeModule, + FormsModule, + TranslateModule.forChild(), + CarriersSmartTableModule, + CarrierLocationModule, + TrackModule, + RenderComponentsModule, + CarrierMutationModule, + CarrierFormsModule, + NbButtonModule, + ], + declarations: COMPONENTS, + exports: COMPONENTS, +}) +export class SetupMerchantsDeliveryAndTakeawayModule {} diff --git a/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/settings/delivery-takeaway/shared-carriers/shared-carriers.component.html b/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/settings/delivery-takeaway/shared-carriers/shared-carriers.component.html new file mode 100644 index 0000000..edb3c0c --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/settings/delivery-takeaway/shared-carriers/shared-carriers.component.html @@ -0,0 +1,5 @@ + diff --git a/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/settings/delivery-takeaway/shared-carriers/shared-carriers.component.ts b/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/settings/delivery-takeaway/shared-carriers/shared-carriers.component.ts new file mode 100644 index 0000000..25cf94b --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/settings/delivery-takeaway/shared-carriers/shared-carriers.component.ts @@ -0,0 +1,120 @@ +import { + Component, + ViewChild, + OnDestroy, + AfterViewInit, + Input, +} from '@angular/core'; +import Carrier from '@modules/server.common/entities/Carrier'; +import { CarriersSmartTableComponent } from '@app/@shared/carrier/carriers-table/carriers-table.component'; +import { Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; +import { TranslateService } from '@ngx-translate/core'; +import { CarriersService } from '@app/@core/data/carriers.service'; +import CarrierStatus from '@modules/server.common/enums/CarrierStatus'; + +const perPage = 5; + +@Component({ + selector: 'ea-merchants-setup-shared-carriers', + templateUrl: './shared-carriers.component.html', +}) +export class SetupMerchantSharedCarriersComponent + implements OnDestroy, AfterViewInit { + @ViewChild('carriersTable', { static: true }) + carriersTable: CarriersSmartTableComponent; + + @Input() + existedCarriersIds: string[] = []; + + perPage = perPage; + + private dataCount: number; + private $carriers; + private ngDestroy$ = new Subject(); + + constructor( + private readonly _translateService: TranslateService, + private readonly _carriersService: CarriersService + ) { + this._applyTranslationOnSmartTable(); + } + + ngAfterViewInit(): void { + this._loadDataSmartTable(); + this.smartTablePageChange(); + } + + ngOnDestroy() { + this.ngDestroy$.next(); + this.ngDestroy$.complete(); + } + + private async _loadDataSmartTable(page = 1) { + if (this.$carriers) { + await this.$carriers.unsubscribe(); + } + + this.$carriers = this._carriersService + .getCarriers( + { + skip: perPage * (page - 1), + limit: perPage, + }, + { + isSharedCarrier: true, + _id: { + $nin: this.existedCarriersIds, + }, + } + ) + .pipe(takeUntil(this.ngDestroy$)) + .subscribe(async (data: Carrier[]) => { + const carriersVm = data.map( + CarriersSmartTableComponent.getCarrierSmartTableObject + ); + + await this.loadDataCount(); + + const carriersData = new Array(this.dataCount); + + carriersData.splice( + perPage * (page - 1), + perPage, + ...carriersVm + ); + + await this.carriersTable.loadData(carriersData); + }); + } + + private _applyTranslationOnSmartTable() { + this._translateService.onLangChange + .pipe(takeUntil(this.ngDestroy$)) + .subscribe(() => { + if (this.carriersTable) { + this.carriersTable.loadSettingsSmartTable(this.perPage); + this._loadDataSmartTable(); + } + }); + } + + private async smartTablePageChange() { + if (this.carriersTable) { + this.carriersTable.pageChange + .pipe(takeUntil(this.ngDestroy$)) + .subscribe((page) => { + this._loadDataSmartTable(page); + }); + } + } + + private async loadDataCount() { + this.dataCount = await this._carriersService.getCountOfCarriers({ + isSharedCarrier: true, + _id: { + $nin: this.existedCarriersIds, + }, + }); + } +} diff --git a/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/settings/orders/orders.component.html b/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/settings/orders/orders.component.html new file mode 100644 index 0000000..71287ed --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/settings/orders/orders.component.html @@ -0,0 +1,55 @@ + + + {{ + 'FAKE_DATA.SETUP_MERCHANTS.ORDERS_SETTINGS.ORDERS_SETTINGS' + | translate + }} + + +
+
+ + + + {{ type.label }} + + +
+
+
+ Invalid image + +
+
+
+
+
+ + + diff --git a/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/settings/orders/orders.component.scss b/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/settings/orders/orders.component.scss new file mode 100644 index 0000000..d990fd6 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/settings/orders/orders.component.scss @@ -0,0 +1,16 @@ +.setup-merchant-orders-settings { + nb-card-body { + text-align: left; + + .preview-barcode { + padding: 8px; + background: #dedede; + display: inline-block; + + img { + margin: 0; + max-height: 5rem; + } + } + } +} diff --git a/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/settings/orders/orders.component.ts b/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/settings/orders/orders.component.ts new file mode 100644 index 0000000..5e3599d --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+setup/+merchants/components/settings/orders/orders.component.ts @@ -0,0 +1,65 @@ +import { Component, Output, EventEmitter, Input } from '@angular/core'; +import OrderBarcodeTypes, { + orderBarcodeTypesToString, +} from '@modules/server.common/enums/OrderBarcodeTypes'; +import * as QRCode from 'qrcode'; + +@Component({ + selector: 'ea-merchants-setup-orders-settings', + templateUrl: './orders.component.html', + styleUrls: ['./orders.component.scss'], +}) +export class SetupMerchantOrdersSettingsComponent { + + @Output() + previousStep: EventEmitter = new EventEmitter(); + + @Output() + nextStep: EventEmitter = new EventEmitter(); + + @Input() + canCreateMerchant: boolean = false; + + iOrderBarcodeType: OrderBarcodeTypes = OrderBarcodeTypes.QR; + barcodeData: string; + barcodeDataUrl: string; + isQRCode: boolean = true; + ngxBarcodeFormat: string; + img: string; + + orderBarcodeTypes = [ + { + label: orderBarcodeTypesToString(OrderBarcodeTypes.QR), + value: OrderBarcodeTypes.QR, + }, + { + label: orderBarcodeTypesToString(OrderBarcodeTypes.CODE128), + value: OrderBarcodeTypes.CODE128, + }, + { + label: orderBarcodeTypesToString(OrderBarcodeTypes.CODE39), + value: OrderBarcodeTypes.CODE39, + }, + { + label: orderBarcodeTypesToString(OrderBarcodeTypes.MSI), + value: OrderBarcodeTypes.MSI, + }, + ]; + + constructor() { + this.loadBarcodeDataUrl(); + } + + async loadBarcodeDataUrl() { + const dummyId = Date.now().toString(); + this.barcodeDataUrl = await QRCode.toDataURL(dummyId); + this.barcodeData = dummyId; + } + + typeChange(type) { + this.isQRCode = type === OrderBarcodeTypes.QR; + if (!this.isQRCode) { + this.ngxBarcodeFormat = orderBarcodeTypesToString(type); + } + } +} diff --git a/packages/admin-web-angular/src/app/pages/+setup/+merchants/merchants.component.html b/packages/admin-web-angular/src/app/pages/+setup/+merchants/merchants.component.html new file mode 100644 index 0000000..6fc6cc3 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+setup/+merchants/merchants.component.html @@ -0,0 +1,146 @@ + + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
diff --git a/packages/admin-web-angular/src/app/pages/+setup/+merchants/merchants.component.scss b/packages/admin-web-angular/src/app/pages/+setup/+merchants/merchants.component.scss new file mode 100644 index 0000000..5930d7d --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+setup/+merchants/merchants.component.scss @@ -0,0 +1,2 @@ +.setup-merchants-page { +} diff --git a/packages/admin-web-angular/src/app/pages/+setup/+merchants/merchants.component.ts b/packages/admin-web-angular/src/app/pages/+setup/+merchants/merchants.component.ts new file mode 100644 index 0000000..d787573 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+setup/+merchants/merchants.component.ts @@ -0,0 +1,117 @@ +import { Component, ViewChild } from '@angular/core'; +import { SetupMerchantAccountComponent } from './components/account/account.component'; +import { SetupMerchantBasicInfoComponent } from './components/basic-info/basic-info.component'; +import { SetupMerchantContactInfoComponent } from './components/contact-info/contact-info.component'; +import { SetupMerchantLocationComponent } from './components/location/location.component'; +import { SetupMerchantPaymentsComponent } from './components/payments/payments.component'; +import { SetupMerchantManufacturingComponent } from './components/manufacturing/manufacturing.component'; +import { SetupMerchantDeliveryAndTakeawayComponent } from './components/settings/delivery-takeaway/delivery-takeaway.component'; +import { SetupMerchantOrdersSettingsComponent } from './components/settings/orders/orders.component'; +import { IWarehouseCreateObject } from '@modules/server.common/interfaces/IWarehouse'; +import GeoLocation from '@modules/server.common/entities/GeoLocation'; +import { WarehouseRouter } from '@modules/client.common.angular2/routers/warehouse-router.service'; +import { ToasterService } from 'angular2-toaster'; +import { NbStepperComponent } from '@nebular/theme'; +import Warehouse from '@modules/server.common/entities/Warehouse'; + +@Component({ + styleUrls: ['./merchants.component.scss'], + templateUrl: './merchants.component.html', +}) +export class SetupMerchantsComponent { + @ViewChild('nbStepper') + nbStepper: NbStepperComponent; + + @ViewChild('account', { static: true }) + stepAccount: SetupMerchantAccountComponent; + @ViewChild('basicInfo', { static: true }) + stepBasicInfo: SetupMerchantBasicInfoComponent; + @ViewChild('contactInfo', { static: true }) + stepContactInfo: SetupMerchantContactInfoComponent; + @ViewChild('location', { static: true }) + stepLocation: SetupMerchantLocationComponent; + @ViewChild('payments') + stepPayments: SetupMerchantPaymentsComponent; + @ViewChild('manufacturing') + stepManufacturing: SetupMerchantManufacturingComponent; + @ViewChild('deliveryAndTakeaway') + stepDeliveryAndTakeaway: SetupMerchantDeliveryAndTakeawayComponent; + @ViewChild('ordersSettings') + stepOrdersSettings: SetupMerchantOrdersSettingsComponent; + + currentStore: Warehouse; + + get canCreateMerchant() { + return ( + this.stepAccount.formValid && + this.stepBasicInfo.formValid && + this.stepContactInfo.contactInfoForm.valid && + this.stepLocation.location.valid + ); + } + + constructor( + private warehouseRouter: WarehouseRouter, + private readonly toasterService: ToasterService + ) {} + + async createMerchant() { + try { + this.currentStore = await this.warehouseRouter.register( + this.getMerchantCreateObj() + ); + + this.toasterService.pop( + 'success', + `Warehouse ${this.currentStore.name} was created!` + ); + + this.nbStepper.next(); + } catch (error) { + this.toasterService.pop({ + type: 'error', + title: `Error in creating warehouse: "${error.message}"`, + timeout: 0, + }); + } + } + + private getMerchantCreateObj(): { + warehouse: IWarehouseCreateObject; + password: string; + } { + let warehouse: IWarehouseCreateObject; + let password: string; + + if (this.canCreateMerchant) { + const geoLocationInput = this.stepLocation.locationForm.getValue(); + geoLocationInput.loc.coordinates.reverse(); + + const accountModel = this.stepAccount.accountModel; + + warehouse = { + contactEmail: accountModel.email, + username: accountModel.username, + ...this.stepBasicInfo.basicInfoCreateObj, + ...this.stepContactInfo.contactInfoModel, + geoLocation: geoLocationInput as GeoLocation, + isPaymentEnabled: this.stepPayments.isPaymentEnabled, + isManufacturing: this.stepManufacturing.isManufacturing, + isCarrierRequired: this.stepDeliveryAndTakeaway + .isCarrierRequired, + productsDelivery: this.stepDeliveryAndTakeaway.productsDelivery, + productsTakeaway: this.stepDeliveryAndTakeaway.productsTakeaway, + hasRestrictedCarriers: + this.stepDeliveryAndTakeaway.restrictedCarriersIds.length > + 0, + carriersIds: this.stepDeliveryAndTakeaway.restrictedCarriersIds, + orderBarcodeType: this.stepOrdersSettings.iOrderBarcodeType, + isActive: true, + paymentGateways: this.stepPayments.paymentsGateways, + isCashPaymentEnabled: this.stepPayments.isCashPaymentEnabled, + }; + password = accountModel.password; + } + return { warehouse, password }; + } +} diff --git a/packages/admin-web-angular/src/app/pages/+setup/+merchants/merchants.module.ts b/packages/admin-web-angular/src/app/pages/+setup/+merchants/merchants.module.ts new file mode 100644 index 0000000..39c8bd2 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+setup/+merchants/merchants.module.ts @@ -0,0 +1,38 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { ThemeModule } from '@app/@theme'; +import { RouterModule, Routes } from '@angular/router'; +import { TranslateModule } from '@ngx-translate/core'; +import { SetupMerchantsComponent } from './merchants.component'; +import { NbStepperModule } from '@nebular/theme'; +import { SetupMerchantsComponentsModule } from './components/components.module'; +import { SetupMerchantsLocationModule } from './components/location/location.module'; +import { SetupMerchantsDeliveryAndTakeawayModule } from './components/settings/delivery-takeaway/delivery-takeaway.module'; +import { ToasterModule } from 'angular2-toaster'; +import { SetupMerchantsProductsModule } from './components/products/products.module'; +import { SetupMerchantsPaymentsModule } from './components/payments/payments.module'; + +const routes: Routes = [ + { + path: '', + component: SetupMerchantsComponent, + }, +]; + +@NgModule({ + imports: [ + CommonModule, + ThemeModule, + RouterModule.forChild(routes), + TranslateModule.forChild(), + NbStepperModule, + SetupMerchantsComponentsModule, + SetupMerchantsLocationModule, + SetupMerchantsDeliveryAndTakeawayModule, + SetupMerchantsProductsModule, + SetupMerchantsPaymentsModule, + ToasterModule.forRoot(), + ], + declarations: [SetupMerchantsComponent], +}) +export class SetupMerchantsModule {} diff --git a/packages/admin-web-angular/src/app/pages/+setup/setup.component.html b/packages/admin-web-angular/src/app/pages/+setup/setup.component.html new file mode 100644 index 0000000..b75536d --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+setup/setup.component.html @@ -0,0 +1,24 @@ + + +

{{ 'FAKE_DATA.SETUP' | translate }}

+
+ + + + + +
diff --git a/packages/admin-web-angular/src/app/pages/+setup/setup.component.scss b/packages/admin-web-angular/src/app/pages/+setup/setup.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/packages/admin-web-angular/src/app/pages/+setup/setup.component.ts b/packages/admin-web-angular/src/app/pages/+setup/setup.component.ts new file mode 100644 index 0000000..6dac93d --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+setup/setup.component.ts @@ -0,0 +1,33 @@ +import { Component, OnInit } from '@angular/core'; +import { Router } from '@angular/router'; +import { environment } from 'environments/environment'; +import { Store } from '@app/@core/data/store.service'; + +@Component({ + styleUrls: ['./setup.component.scss'], + templateUrl: './setup.component.html', +}) +export class SetupComponent implements OnInit { + public loading: boolean; + public fakeDataGenerator: boolean; + + constructor( + private readonly _router: Router, + private readonly _store: Store + ) {} + ngOnInit(): void { + this.fakeDataGenerator = !!+this._store.fakeDataGenerator; + } + + navigateToFakeDataPage() { + this.loading = true; + this._router.navigate(['/generate-initial-data']); + this.loading = false; + } + + navigateToSetupMerchantsPage() { + this.loading = true; + this._router.navigate(['/setup/merchants']); + this.loading = false; + } +} diff --git a/packages/admin-web-angular/src/app/pages/+setup/setup.module.ts b/packages/admin-web-angular/src/app/pages/+setup/setup.module.ts new file mode 100644 index 0000000..880d488 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+setup/setup.module.ts @@ -0,0 +1,34 @@ +import { RouterModule, Routes } from '@angular/router'; +import { NgModule } from '@angular/core'; +import { SetupComponent } from './setup.component'; +import { CommonModule } from '@angular/common'; +import { ThemeModule } from '../../@theme'; +import { TranslateModule } from '@ngx-translate/core'; +import { NbSpinnerModule, NbButtonModule } from '@nebular/theme'; + +const routes: Routes = [ + { + path: '', + component: SetupComponent, + }, + { + path: 'merchants', + loadChildren: () => + import('./+merchants/merchants.module').then( + (m) => m.SetupMerchantsModule + ), + }, +]; + +@NgModule({ + imports: [ + CommonModule, + ThemeModule, + RouterModule.forChild(routes), + TranslateModule.forChild(), + NbSpinnerModule, + NbButtonModule, + ], + declarations: [SetupComponent], +}) +export class SetupModule {} diff --git a/packages/admin-web-angular/src/app/pages/+sign-in-redirect/sign-in-redirect.component.ts b/packages/admin-web-angular/src/app/pages/+sign-in-redirect/sign-in-redirect.component.ts new file mode 100644 index 0000000..7757b98 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+sign-in-redirect/sign-in-redirect.component.ts @@ -0,0 +1,28 @@ +import { Component, OnInit } from '@angular/core'; +import { WarehousesService } from '@app/@core/data/warehouses.service'; +import { Router } from '@angular/router'; +import { first } from 'rxjs/operators'; + +@Component({ + template: `

Please wait a second...

`, +}) +export class SignInRedirectComponent { + constructor( + private readonly _storesService: WarehousesService, + private readonly _router: Router + ) { + this._redirectToProperPage(); + } + + private _redirectToProperPage() { + this._storesService + .hasExistingStores() + .toPromise() + .then((hasExistingStores) => { + const routeToRedirect = hasExistingStores + ? 'dashboard' + : 'setup'; + this._router.navigate([routeToRedirect]); + }); + } +} diff --git a/packages/admin-web-angular/src/app/pages/+sign-in-redirect/sign-in-redirect.module.ts b/packages/admin-web-angular/src/app/pages/+sign-in-redirect/sign-in-redirect.module.ts new file mode 100644 index 0000000..cb9b03a --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+sign-in-redirect/sign-in-redirect.module.ts @@ -0,0 +1,15 @@ +import { NgModule } from '@angular/core'; +import { RouterModule } from '@angular/router'; +import { SignInRedirectComponent } from './sign-in-redirect.component'; +import { WarehousesService } from '@app/@core/data/warehouses.service'; + +@NgModule({ + imports: [ + RouterModule.forChild([ + { path: '', component: SignInRedirectComponent }, + ]), + ], + declarations: [SignInRedirectComponent], + providers: [WarehousesService], +}) +export class SignInRedirectModule {} diff --git a/packages/admin-web-angular/src/app/pages/+simulation/index.ts b/packages/admin-web-angular/src/app/pages/+simulation/index.ts new file mode 100644 index 0000000..e226666 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+simulation/index.ts @@ -0,0 +1 @@ +export * from './simulation.module'; diff --git a/packages/admin-web-angular/src/app/pages/+simulation/instructions/instructions.component.html b/packages/admin-web-angular/src/app/pages/+simulation/instructions/instructions.component.html new file mode 100644 index 0000000..ce8dc77 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+simulation/instructions/instructions.component.html @@ -0,0 +1,232 @@ + + + {{ 'SIMULATION_VIEW.INSTRUCTIONS_STEPS.INSTRUTIONS' | translate }} + + + +
+
+

+ {{ + 'SIMULATION_VIEW.INSTRUCTIONS_STEPS.CREATE_USER_STEP.CREATE_USER' + | translate + }} +

+ + {{ + 'SIMULATION_VIEW.INSTRUCTIONS_STEPS.CREATE_USER_STEP.STEP_1' + | translate + }} + +
+
+

+ + {{ + 'SIMULATION_VIEW.INSTRUCTIONS_STEPS.CREATE_USER_STEP.ORDER' + | translate + }} + +

+ + {{ + 'SIMULATION_VIEW.INSTRUCTIONS_STEPS.ORDER_STEP.STEP_2' + | translate + }} + +
+
+

+ + {{ + 'SIMULATION_VIEW.INSTRUCTIONS_STEPS.CONFIRM_OR_CANCEL_STEP.CONFIRM_CANCEL_ORDER' + | translate + }} +

+ + {{ + 'SIMULATION_VIEW.INSTRUCTIONS_STEPS.CONFIRM_OR_CANCEL_STEP.STEP_3' + | translate + }} +
+
+ +
+
    +
    + {{ + 'SIMULATION_VIEW.INSTRUCTIONS_STEPS.INVITE_STEP.TO_CONTINUE_ITs_REQUIRED_TO_BE_INVITED_IN_THE_SYSTEM' + | translate + }} +
    +
  • + {{ + 'SIMULATION_VIEW.INSTRUCTIONS_STEPS.INVITE_STEP.SEND_INVITE_REQUEST' + | translate + }} +
  • +
  • + {{ + 'SIMULATION_VIEW.INSTRUCTIONS_STEPS.INVITE_STEP.ALL_INVITE_REQUESTS_ARE_REVIEWED' + | translate + }} +
  • +
  • + {{ + 'SIMULATION_VIEW.INSTRUCTIONS_STEPS.INVITE_STEP.AFTER_YOU_GET_INVITED_BEFORE' + | translate + }} + {{ inviteCode ? inviteCode : '...' }} + {{ + 'SIMULATION_VIEW.INSTRUCTIONS_STEPS.INVITE_STEP.AFTER_YOU_GET_INVITED_AFTER' + | translate + }} +
  • +
+
    +
    + {{ + 'SIMULATION_VIEW.INSTRUCTIONS_STEPS.CREATE_USER_STEP.TO_CONTINUE_IS_REQUIRED_TO_REGISTER_IN_THE_SYSTEM' + | translate + }} +
    +
  • + {{ + 'SIMULATION_VIEW.INSTRUCTIONS_STEPS.CREATE_USER_STEP.CLICK_ON_BUTTON_CREATE_USER' + | translate + }} +
  • +
  • + {{ + 'SIMULATION_VIEW.INSTRUCTIONS_STEPS.CREATE_USER_STEP.FILL_THE_FORM_FOR_ADDITIONAL_INFO' + | translate + }} +
  • +
  • + {{ + 'SIMULATION_VIEW.INSTRUCTIONS_STEPS.CREATE_USER_STEP.FILL_THE_FORM_FOR_LOCATION_AND_PRESS_DONE_BUTTON' + | translate + }} +
  • +
+
+ +
+
    +
    + {{ + 'SIMULATION_VIEW.INSTRUCTIONS_STEPS.ORDER_STEP.CREATE_ORDER' + | translate + }} +
    +
  • + {{ + 'SIMULATION_VIEW.INSTRUCTIONS_STEPS.ORDER_STEP.CHOICE_SOME_PRODUCTS_FROM_THE_TABLE' + | translate + }} +
  • +
  • + {{ + 'SIMULATION_VIEW.INSTRUCTIONS_STEPS.ORDER_STEP.SELECT_PRODUCT' + | translate + }} +
  • +
  • + {{ + 'SIMULATION_VIEW.INSTRUCTIONS_STEPS.ORDER_STEP.SELECT_BUTTON_ORDER_TO_CREATE_ORDER' + | translate + }} +
  • +
+
    +
    + {{ + 'SIMULATION_VIEW.INSTRUCTIONS_STEPS.ORDER_STEP.REVIEW_ORDER_HISTORY' + | translate + }} +
    +
  • + {{ + 'SIMULATION_VIEW.INSTRUCTIONS_STEPS.ORDER_STEP.ON_PRESS_ORDER_HISTORY_TAB' + | translate + }} +
  • +
  • + {{ + 'SIMULATION_VIEW.INSTRUCTIONS_STEPS.ORDER_STEP.HERE_YOU_CAN_SEE_DETAILS_ABOUT_EACH_ORDER' + | translate + }} +
  • +
  • + {{ + 'SIMULATION_VIEW.INSTRUCTIONS_STEPS.ORDER_STEP.PRESS_ON_CARRIER_ORDER_OR_PRODUCT_NAME' + | translate + }} +
  • +
+
+ +
+
    +
    + {{ + 'SIMULATION_VIEW.INSTRUCTIONS_STEPS.CONFIRM_OR_CANCEL_STEP.REAL_TIME' + | translate + }} +
    +
  • + {{ + 'SIMULATION_VIEW.INSTRUCTIONS_STEPS.CONFIRM_OR_CANCEL_STEP.TRACK_STATUS_ON_YOUR_ORDER' + | translate + }} +
  • +
  • + {{ + 'SIMULATION_VIEW.INSTRUCTIONS_STEPS.CONFIRM_OR_CANCEL_STEP.ELAPSED_TIME_FROM_CREATE_TO_DELIVERED' + | translate + }} +
  • +
  • + {{ + 'SIMULATION_VIEW.INSTRUCTIONS_STEPS.CONFIRM_OR_CANCEL_STEP.SHOWS_MERCHANT_LOCATION' + | translate + }} +
  • +
  • + {{ + 'SIMULATION_VIEW.INSTRUCTIONS_STEPS.CONFIRM_OR_CANCEL_STEP.SHOWS_CARRIER_LOCATION' + | translate + }} +
  • +
+
    +
    + {{ + 'SIMULATION_VIEW.INSTRUCTIONS_STEPS.CONFIRM_OR_CANCEL_STEP.POSSIBILITIES' + | translate + }} +
    +
  • + {{ + 'SIMULATION_VIEW.INSTRUCTIONS_STEPS.CONFIRM_OR_CANCEL_STEP.SLIDER_REVIEW_OF_THE_ALL_PRODUCTS' + | translate + }} +
  • +
  • + {{ + 'SIMULATION_VIEW.INSTRUCTIONS_STEPS.CONFIRM_OR_CANCEL_STEP.CAN_CANCEL_IT_WITH_ORDER_CANCEL_BUTTON' + | translate + }} +
  • +
  • + {{ + 'SIMULATION_VIEW.INSTRUCTIONS_STEPS.CONFIRM_OR_CANCEL_STEP.AFTER_THE_ORDER_IS_DELIVER_USER_CAN_CLICK_BUTTON_ORDER_CONFIRM_TO_CONTINUE' + | translate + }} +
  • +
+
+
+
diff --git a/packages/admin-web-angular/src/app/pages/+simulation/instructions/instructions.component.scss b/packages/admin-web-angular/src/app/pages/+simulation/instructions/instructions.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/packages/admin-web-angular/src/app/pages/+simulation/instructions/instructions.component.ts b/packages/admin-web-angular/src/app/pages/+simulation/instructions/instructions.component.ts new file mode 100644 index 0000000..948020f --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+simulation/instructions/instructions.component.ts @@ -0,0 +1,37 @@ +import { Component, OnDestroy, Input } from '@angular/core'; +import { Subject } from 'rxjs'; + +export enum Step { + One, + Two, + Three, +} + +@Component({ + selector: 'ea-simulation-instructions', + templateUrl: './instructions.component.html', + styleUrls: ['./instructions.component.scss'], +}) +export class SimulationInstructionsComponent implements OnDestroy { + @Input() + public inviteSystem: boolean; + + public step: Step; + + public stepTypes = { + one: Step.One, + two: Step.Two, + three: Step.Three, + }; + + public inviteCode: string; + + private _ngDestroy$ = new Subject(); + + constructor() {} + + ngOnDestroy() { + this._ngDestroy$.next(); + this._ngDestroy$.complete(); + } +} diff --git a/packages/admin-web-angular/src/app/pages/+simulation/order/order.component.html b/packages/admin-web-angular/src/app/pages/+simulation/order/order.component.html new file mode 100644 index 0000000..cb4482c --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+simulation/order/order.component.html @@ -0,0 +1,105 @@ +
+
+
+

+ {{ orderInfoStatuses.TITLE }} +

+ +
+ {{ orderInfoStatuses.DETAILS }} +
+ +
+ {{ orderInfoStatuses.NOT_PAID_NOTE }} +
+
+
+
+ +
+
+
+
+
+ {{ + 'SIMULATION_VIEW.ORDER_INFO.DELIVERY_STATUS.WE' + | translate + }} +
+ +
+ .. + +
+
+ {{ + 'SIMULATION_VIEW.ORDER_INFO.DELIVERY_STATUS.CARRIER' + | translate + }} +
+ +
+ .. + +
+
+ {{ + 'SIMULATION_VIEW.ORDER_INFO.DELIVERY_STATUS.YOU' + | translate + }} +
+ +
+
+ +
+
+ +
+
+
+
+ + +
+
diff --git a/packages/admin-web-angular/src/app/pages/+simulation/order/order.component.scss b/packages/admin-web-angular/src/app/pages/+simulation/order/order.component.scss new file mode 100644 index 0000000..8cc275b --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+simulation/order/order.component.scss @@ -0,0 +1,69 @@ +@keyframes blinking { + 0% { + color: black; + } + 50% { + color: #9a9a9a; + } +} +.order-info { + text-align: center; + + background: #ebeef2; + .row { + margin-top: 1rem; + } + .col { + padding: 0 !important; + } + margin-top: 1.25rem; + .delivery-status { + margin-top: 20px; + margin-bottom: 20px; + text-align: center; + font-size: 36px; + display: flex; + .status { + display: inline-block; + } + + .align-end { + align-self: flex-end; + } + + * { + color: #9a9a9a !important; + } + + .activated { + &, + * { + color: black !important; + } + } + + .current { + &, + * { + animation: blinking 1s ease-in-out infinite !important; + } + } + } + + .order-img { + padding-right: 0; + img { + max-height: 350px; + max-width: 100%; + } + } + + .order-map { + min-height: 300px; + padding: 0; + } + + .no-padding { + padding: 0 !important; + } +} diff --git a/packages/admin-web-angular/src/app/pages/+simulation/order/order.component.ts b/packages/admin-web-angular/src/app/pages/+simulation/order/order.component.ts new file mode 100644 index 0000000..65e0e7f --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+simulation/order/order.component.ts @@ -0,0 +1,220 @@ +import { Component, OnDestroy, Input, ViewChild, OnInit } from '@angular/core'; +import { TranslateService } from '@ngx-translate/core'; +import { Subject } from 'rxjs'; +import Order from '@modules/server.common/entities/Order'; +import { ProductLocalesService } from '@modules/client.common.angular2/locale/product-locales.service'; +import OrderCarrierStatus from '@modules/server.common/enums/OrderCarrierStatus'; +import { takeUntil } from 'rxjs/operators'; +import { ElapsedTimeComponent } from '@app/@shared/elapsed-time/elapsed-time.component'; +import { IImage } from 'ng-simple-slideshow'; +import Product from '@modules/server.common/entities/Product'; + +export enum DeliveryStatus { + Warehouse, + Carrier, + Customer, + Completed, +} + +@Component({ + selector: 'ea-simulation-order', + templateUrl: './order.component.html', + styleUrls: ['./order.component.scss'], +}) +export class SimulationOrderComponent implements OnDestroy, OnInit { + @ViewChild('elapsedTime') + elapsedTime: ElapsedTimeComponent; + + @Input() + public order: Order; + public delivered: boolean; + + private _ngDestroy$ = new Subject(); + slideImages: any; + + constructor( + private readonly _translateService: TranslateService, + private readonly _localeTranslate: ProductLocalesService + ) {} + + ngOnInit() { + if (!this._getStartDate) { + this._setStartDate = new Date(); + } + this.getSlideImage(this.order); + } + + getSlideImage(order: Order) { + const images = order.products.map((p) => { + return { + url: this._localeTranslate.getTranslate(p.product.images), + caption: this._localeTranslate.getTranslate(p.product.title), + title: this._localeTranslate.getTranslate( + p.product.description + ), + style: { + height: '100%', + }, + }; + }); + + this.slideImages = images; + } + + private get _getStartDate() { + return localStorage.getItem('simulationStartDate'); + } + + private set _setStartDate(val: Date) { + localStorage.setItem('simulationStartDate', JSON.stringify(val)); + } + private set _setEndTime(val: any) { + localStorage.setItem('simulationEndTime', JSON.stringify(val)); + } + + public get isCustomerActive() { + return this.currentStatus >= DeliveryStatus.Customer; + } + + public get isCustomerCurrent() { + const isCurrent = this.currentStatus === DeliveryStatus.Customer; + if ( + isCurrent !== this.delivered && + !isCurrent && + this.isCustomerActive + ) { + if (this.elapsedTime) { + this._setEndTime = this.elapsedTime.timePasssed; + this.clearTimer(); + } + } + this.delivered = isCurrent; + return isCurrent; + } + + public get isWarehouseActive() { + return this.currentStatus >= DeliveryStatus.Warehouse; + } + + public get isWarehouseCurrent() { + return this.currentStatus === DeliveryStatus.Warehouse; + } + + public get isCarrierActive() { + return this.currentStatus >= DeliveryStatus.Carrier; + } + + public get isCarrierCurrent() { + return this.currentStatus === DeliveryStatus.Carrier; + } + + public get currentStatus(): DeliveryStatus { + if (this.order == null) { + return DeliveryStatus.Warehouse; + } + + if (this.order['isCompleted']) { + return DeliveryStatus.Completed; + } else if ( + this.order.carrierStatus >= + OrderCarrierStatus.CarrierArrivedToCustomer + ) { + return DeliveryStatus.Customer; + } else if ( + this.order.carrierStatus >= OrderCarrierStatus.CarrierPickedUpOrder + ) { + return DeliveryStatus.Carrier; + } else { + return DeliveryStatus.Warehouse; + } + } + + public get deliveryTimeRange(): string { + if (this.order == null) { + return ''; + } + + let deliveryTimeMin = 0; + let deliveryTimeMax = 0; + + this.order.products.forEach((product) => { + if ( + product.deliveryTimeMin && + product.deliveryTimeMin > deliveryTimeMin + ) { + deliveryTimeMin = product.deliveryTimeMin; + } + if ( + product.deliveryTimeMax && + product.deliveryTimeMax > deliveryTimeMax + ) { + deliveryTimeMax = product.deliveryTimeMax; + } + }); + if (deliveryTimeMin !== 0 && deliveryTimeMax !== 0) { + return deliveryTimeMin + '-' + deliveryTimeMax; + } + if (deliveryTimeMin !== 0) { + return deliveryTimeMin.toString(); + } + if (deliveryTimeMax !== 0) { + return deliveryTimeMax.toString(); + } + return 30 + '-' + 60; + } + + public get orderInfoStatuses() { + const popupStatuses = + 'SIMULATION_VIEW.ORDER_INFO.STATUSES.' + this.currentStatus; + let result: string = ''; + + const getTitle = () => { + this._translateService + .get(popupStatuses + '.TITLE') + .pipe(takeUntil(this._ngDestroy$)) + .subscribe((t) => (result = t)); + return result; + }; + + const getDetails = () => { + this._translateService + .get(popupStatuses + '.DETAILS') + .pipe(takeUntil(this._ngDestroy$)) + .subscribe((d) => (result = d)); + return result.replace('%t', this.deliveryTimeRange); + }; + + const getPaidNote = () => { + this._translateService + .get(popupStatuses + '.NOT_PAID_NOTE') + .pipe(takeUntil(this._ngDestroy$)) + .subscribe((n) => (result = n)); + return result.replace( + '%s', + `${ + this.order + ? (this.order).totalPrice.toFixed(2) + : '00.00' + }$` + ); + }; + + return { + TITLE: getTitle(), + DETAILS: getDetails(), + NOT_PAID_NOTE: getPaidNote(), + }; + } + + clearTimer() { + clearInterval(this.elapsedTime.timer); + } + + ngOnDestroy() { + this.clearTimer(); + localStorage.removeItem('simulationStartDate'); + localStorage.removeItem('simulationEndTime'); + this._ngDestroy$.next(); + this._ngDestroy$.complete(); + } +} diff --git a/packages/admin-web-angular/src/app/pages/+simulation/order/order.module.ts b/packages/admin-web-angular/src/app/pages/+simulation/order/order.module.ts new file mode 100644 index 0000000..fe37e90 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+simulation/order/order.module.ts @@ -0,0 +1,27 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { ThemeModule } from '@app/@theme'; +import { ToasterModule } from 'angular2-toaster'; +import { TranslateModule } from '@ngx-translate/core'; +import { SimulationOrderComponent } from './order.component'; +import { ElapsedTimeModule } from '@app/@shared/elapsed-time/elapsed-time.module'; +import { ImageSliderModule } from '../../../@shared/render-component/image-slider/image-slider.module'; +import { OrderMapModule } from '@app/@shared/order/order-map/order-map.module'; + +const SIMULATION_ORDER_COMPONENTS = [SimulationOrderComponent]; + +@NgModule({ + imports: [ + CommonModule, + ThemeModule, + ToasterModule.forRoot(), + TranslateModule.forChild(), + ElapsedTimeModule, + OrderMapModule, + ImageSliderModule, + ], + declarations: [...SIMULATION_ORDER_COMPONENTS], + exports: [...SIMULATION_ORDER_COMPONENTS], + providers: [] +}) +export class SimulationOrderModule {} diff --git a/packages/admin-web-angular/src/app/pages/+simulation/products/products.component.html b/packages/admin-web-angular/src/app/pages/+simulation/products/products.component.html new file mode 100644 index 0000000..1974ca7 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+simulation/products/products.component.html @@ -0,0 +1,7 @@ + + diff --git a/packages/admin-web-angular/src/app/pages/+simulation/products/products.component.ts b/packages/admin-web-angular/src/app/pages/+simulation/products/products.component.ts new file mode 100644 index 0000000..12b5a94 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+simulation/products/products.component.ts @@ -0,0 +1,147 @@ +import { Component, OnDestroy, EventEmitter } from '@angular/core'; +import { LocalDataSource } from 'ng2-smart-table'; +import { ProductImageRedirectComponent } from '@app/@shared/render-component/product-image-redirect/product-image-redirect.component'; +import { forkJoin, Observable, Subject } from 'rxjs'; +import { ProductTitleRedirectComponent } from '@app/@shared/render-component/product-title-redirect/product-title-redirect.component'; +import { SimulationJsonComponent } from '@app/@shared/render-component/simulation-table/sumulation-json.component'; +import { TranslateService } from '@ngx-translate/core'; +import { takeUntil } from 'rxjs/operators'; +import ProductInfo from '@modules/server.common/entities/ProductInfo'; +import { ProductCheckboxComponent } from '@app/@shared/render-component/product-checkbox/product-checkbox'; + +export interface ProductViewModel { + id: string; + title: string; + image: string; +} + +@Component({ + selector: 'ea-simulation-products', + templateUrl: './products.component.html', +}) +export class SimulationProductsComponent implements OnDestroy { + private _ngDestroy$ = new Subject(); + + public sourceSmartTable: LocalDataSource = new LocalDataSource(); + public settingsSmartTable: object; + public selectedProducts: ProductViewModel[] = []; + public selectProducts$: EventEmitter = new EventEmitter(); + public selectProductsChange$: EventEmitter = new EventEmitter(); + + public inFilter: boolean; + + constructor(private readonly _translateService: TranslateService) { + this._loadSmartTableSettings(); + this._applyTranslationOnSmartTable(); + } + + setupDataForSmartTable(products: ProductInfo[]) { + if (products.length > 0) { + const data = products.map((pInfo: ProductInfo) => { + const product = pInfo.warehouseProduct.product; + return { + id: product['id'], + product, + warehouseId: pInfo.warehouseId, + }; + }); + + this.selectProducts$ + .pipe(takeUntil(this._ngDestroy$)) + .subscribe(({ current, allData }) => { + allData.find((d) => d.id === current['id'])[ + 'checked' + ] = !current.checked; + this.selectedProducts = allData.filter((r) => r.checked); + if (this.selectedProducts.length !== 0) { + const newData = data.filter( + (d) => d['warehouseId'] === current['warehouseId'] + ); + this.sourceSmartTable.load(newData); + this.selectProductsChange$.emit(); + } else { + this.sourceSmartTable.load(data); + this.selectProductsChange$.emit(); + } + }); + this.sourceSmartTable.load(data); + } + } + + async selectProductTmp(ev) { + this.selectProducts$.emit({ + current: ev.data, + allData: ev.source.data, + }); + } + + private _applyTranslationOnSmartTable() { + this._translateService.onLangChange.subscribe(() => { + this._loadSmartTableSettings(); + }); + } + + private _loadSmartTableSettings() { + const columnTitlePrefix = 'SIMULATION_VIEW.SMART_TABLE.'; + const getTranslate = (name: string): Observable => + this._translateService.get(columnTitlePrefix + name); + + forkJoin( + getTranslate('ID'), + getTranslate('TITLE'), + getTranslate('IMAGE') + ) + .pipe(takeUntil(this._ngDestroy$)) + .subscribe(([id, titleTr, image]) => { + this.settingsSmartTable = { + actions: false, + hideSubHeader: true, + // selectMode: 'multi', + mode: 'inline', + columns: { + checkbox: { + title: '', + filter: false, + type: 'custom', + renderComponent: ProductCheckboxComponent, + }, + images: { + title: image, + filter: false, + renderComponent: ProductImageRedirectComponent, + type: 'custom', + width: '6%', + }, + title: { + title: titleTr, + filter: false, + class: 'text-left', + renderComponent: ProductTitleRedirectComponent, + type: 'custom', + }, + id: { + class: 'text-left', + title: id, + filter: false, + }, + json: { + title: 'Actions', + width: '6%', + filter: false, + type: 'custom', + renderComponent: SimulationJsonComponent, + }, + }, + pager: { + display: true, + perPage: 5, + }, + }; + }); + } + + ngOnDestroy() { + this._ngDestroy$.next(); + this._ngDestroy$.complete(); + } +} diff --git a/packages/admin-web-angular/src/app/pages/+simulation/simulation.component.html b/packages/admin-web-angular/src/app/pages/+simulation/simulation.component.html new file mode 100644 index 0000000..b39e167 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+simulation/simulation.component.html @@ -0,0 +1,143 @@ + +
+
+ + +
+

{{ 'SIMULATION_VIEW.SIMULATION' | translate }}

+
+

+ {{ 'SIMULATION_VIEW.PURCHASE_PRODUCTS' | translate }} +

+
+
+ + +
+
+ + +
+ + +
+ {{ 'SIMULATION_VIEW.PRODUCTS' | translate }}: +
+ {{ productsCount }} +
+ {{ 'SIMULATION_VIEW.STORE' | translate }}: +
+ {{ warehouse?.name }} +
+
+ +
+ + +
+
+ + + + + + + + + + + +
+
+
+
+ +
+
diff --git a/packages/admin-web-angular/src/app/pages/+simulation/simulation.component.scss b/packages/admin-web-angular/src/app/pages/+simulation/simulation.component.scss new file mode 100644 index 0000000..012f259 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+simulation/simulation.component.scss @@ -0,0 +1,78 @@ +:host ::ng-deep .ng2-smart-titles:first-child { + text-align: center !important; +} + +.simulation-products-table { + text-align: center !important; +} + +:host ::ng-deep ng2-smart-table .tableImg { + height: 100px; + width: 100px; +} + +nb-card { + transform: translate3d(0, 0, 0); +} + +.tableImg { + height: 100px; + width: 100px; +} + +.tableImg:hover { + cursor: pointer; +} + +.ion-clipboard { + padding-left: 20px; +} + +.ion-clipboard:hover { + cursor: pointer; +} + +pre { + background-color: #f4f4f4; + padding: 1%; +} + +a { + background-color: yellow; + font-size: 120px; +} + +.hid { + visibility: hidden; +} + +.body-title { + margin-top: 1.25rem; + margin-bottom: 10px; +} + +#simulations-buttons { + text-align: left !important; + + div { + text-align: left !important; + } +} + +.order-info { + display: inline-block; + margin-left: 15px; + + h6 { + color: white !important; + margin-bottom: 0 !important; + } + + padding: 0.5rem; + border-bottom-left-radius: 0.375rem; + border-bottom-right-radius: 0.375rem; +} + +.fix-btn-height { + height: 2.5rem !important; +} diff --git a/packages/admin-web-angular/src/app/pages/+simulation/simulation.component.ts b/packages/admin-web-angular/src/app/pages/+simulation/simulation.component.ts new file mode 100644 index 0000000..0c09d2f --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+simulation/simulation.component.ts @@ -0,0 +1,319 @@ +import { Component, OnDestroy, ViewChild, OnInit } from '@angular/core'; +import { ProductLocalesService } from '@modules/client.common.angular2/locale/product-locales.service'; +import { ILocaleMember } from '@modules/server.common/interfaces/ILocale'; +import ProductInfo from '@modules/server.common/entities/ProductInfo'; +import User from '@modules/server.common/entities/User'; +import { UserMutationComponent } from '../../@shared/user/user-mutation'; +import { GeoLocationService } from '../../@core/data/geo-location.service'; +import { takeUntil, first } from 'rxjs/operators'; +import { Observable, Subject, forkJoin } from 'rxjs'; +import { TranslateService } from '@ngx-translate/core'; +import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; +import { UserRouter } from '@modules/client.common.angular2/routers/user-router.service'; +import { Store } from '@app/@core/data/store.service'; +import { SimulationProductsComponent } from './products/products.component'; +import { WarehouseOrdersRouter } from '@modules/client.common.angular2/routers/warehouse-orders-router.service'; +import Order from '@modules/server.common/entities/Order'; +import { OrderRouter } from '@modules/client.common.angular2/routers/order-router.service'; +import { InviteRouter } from '@modules/client.common.angular2/routers/invite-router.service'; +import { InviteRequestModalComponent } from '@app/@shared/invite/invite-request/invite-request-modal.component'; +import InviteRequest from '@modules/server.common/entities/InviteRequest'; +import { IInviteCreateObject } from '@modules/server.common/interfaces/IInvite'; +import Invite from '@modules/server.common/entities/Invite'; +import { ToasterService } from 'angular2-toaster'; +import { ByCodeModalComponent } from '@app/@shared/invite/by-code/by-code-modal.component'; +import { + SimulationInstructionsComponent, + Step, +} from './instructions/instructions.component'; +import Warehouse from '@modules/server.common/entities/Warehouse'; +import { WarehouseRouter } from '@modules/client.common.angular2/routers/warehouse-router.service'; +import { IOrderCreateInput } from '@modules/server.common/routers/IWarehouseOrdersRouter'; + +@Component({ + selector: 'ea-simulation', + templateUrl: './simulation.component.html', + styleUrls: ['/simulation.component.scss'], +}) +export class SimulationComponent implements OnDestroy, OnInit { + @ViewChild('productsTable', { static: true }) + productsTable: SimulationProductsComponent; + + @ViewChild('instructions') + instructions: SimulationInstructionsComponent; + + public hasProductsForOrder: boolean; + public inviteSystem: boolean; + public user: User; + public order: Order; + public inviteRequest: InviteRequest; + public invite: Invite; + public loadButtons: boolean; + public loading: boolean; + public productsCount: number; + public storeName: string; + public warehouse: Warehouse; + + private _ngDestroy$ = new Subject(); + private _productsInfoData: ProductInfo[] = []; + + constructor( + private readonly _modalService: NgbModal, + private translate: TranslateService, + private _productLocalesService: ProductLocalesService, + private geoLocationService: GeoLocationService, + private store: Store, + private readonly userRouter: UserRouter, + private readonly warehouseOrdersRouter: WarehouseOrdersRouter, + private readonly orderRouter: OrderRouter, + private readonly inviteRouter: InviteRouter, + private readonly toasterService: ToasterService, + private readonly warehouseRouter: WarehouseRouter + ) { + this.loadPage(); + } + + async loadPage() { + const invitesSettings = await this.inviteRouter.getInvitesSettings(); + this.inviteSystem = invitesSettings.isEnabled; + this.loadButtons = true; + this._listenForEntityLocaleTranslate(); + this._startTracking(); + } + + ngOnInit(): void { + this.selectProductsChange(); + localStorage.removeItem('simulationStartDate'); + localStorage.removeItem('simulationEndTime'); + } + + localeTranslate(member: ILocaleMember[]) { + return this._productLocalesService.getTranslate(member); + } + + async showInviteRequestModal(): Promise { + try { + this.inviteRequest = await this._modalService.open( + InviteRequestModalComponent, + { + size: 'lg', + container: 'nb-layout', + backdrop: 'static', + } + ).result; + } catch (error) { + this.inviteRequest = null; + } + } + + selectProductsChange() { + this.productsTable.selectProductsChange$ + .pipe(takeUntil(this._ngDestroy$)) + .subscribe(async (res) => { + this.productsCount = this.productsTable.selectedProducts.length; + + if (this.productsCount > 0) { + // We are sure that all products use same warehouse for that user [0] + const warehouseId = this.productsTable.selectedProducts[0][ + 'warehouseId' + ]; + + if ( + !this.warehouse || + (warehouseId && this.warehouse.id !== warehouseId) + ) { + this.warehouse = await this.warehouseRouter + .get(warehouseId) + .pipe(first()) + .toPromise(); + } + } + + this.hasProductsForOrder = + this.productsTable.selectedProducts.length > 0; + }); + } + + async orderCreate() { + const products = this.productsTable.selectedProducts; + + this.loading = true; + + if (products.length > 0) { + this.hasProductsForOrder = false; + + const orderProducts = products.map((p) => ({ + count: 1, + productId: p.id, + })); + + // We are sure that all products use same warehouse for that user [0] + const warehouseId = products[0]['warehouseId']; + + const orderRouterOptions = { + populateWarehouse: true, + populateCarrier: true, + }; + + const orderCreateInput: IOrderCreateInput = { + userId: this.user.id, + warehouseId, + products: orderProducts, + }; + + const order: Order = await this.warehouseOrdersRouter.create( + orderCreateInput + ); + + this.orderRouter + .get(order.id, orderRouterOptions) + .pipe(takeUntil(this._ngDestroy$)) + .subscribe((o: Order) => { + this.order = o; + }); + + this.loading = false; + + this.instructions.step = Step.Three; + } + } + + orderConfirm() { + this.loading = true; + this.order = null; + this.instructions.step = Step.Two; + this.productsTable.setupDataForSmartTable(this._productsInfoData); + // this.loading = false; + } + + async inviteUser(): Promise { + try { + this.invite = await this.inviteRouter.create( + this.getInviteCreateObj() + ); + + this.inviteRequest = null; + + this.instructions.inviteCode = this.invite.code; + + this.toasterService.pop( + 'success', + `Successful invited user, your code is ${this.invite.code}` + ); + } catch (err) { + this.toasterService.pop( + 'error', + `Error in invite user: "${err.message}"` + ); + } + } + + async createUser(): Promise { + if (this.inviteSystem) { + return this.showByCodeModal(); + } else { + return this.showCreateUserModal(); + } + } + + async orderCancel(): Promise { + this.loading = true; + await this.warehouseOrdersRouter.cancel(this.order.id); + this.order = null; + this.instructions.step = Step.Two; + this.loading = false; + } + + private getInviteCreateObj(): IInviteCreateObject { + if (this.inviteRequest) { + return { + geoLocation: this.inviteRequest.geoLocation, + apartment: this.inviteRequest.apartment, + }; + } + return null; + } + + private async showCreateUserModal(): Promise { + try { + this.user = await this._modalService.open(UserMutationComponent, { + size: 'lg', + container: 'nb-layout', + backdrop: 'static', + }).result; + + this.store.userId = this.user.id; + this._startTracking(); + } catch (error) { + this.user = null; + } + } + + private async showByCodeModal(): Promise { + try { + const activeModal = this._modalService.open(ByCodeModalComponent, { + size: 'sm', + container: 'nb-layout', + }); + + const modalComponent: ByCodeModalComponent = + activeModal.componentInstance; + + if (this.invite) { + modalComponent.location = this.invite.geoLocation.loc; + } + + this.user = await activeModal.result; + + this.store.userId = this.user.id; + + this.invite = null; + + this._startTracking(); + } catch (error) { + this.user = null; + } + } + + private async _startTracking(): Promise { + if (this.store.userId) { + this.user = await this.userRouter + .get(this.store.userId) + .pipe(first()) + .toPromise(); + + const productsInfo: Observable< + ProductInfo[] + > = this.geoLocationService.getGeoLocationProducts( + this.user.geoLocation + ); + + productsInfo + .pipe(takeUntil(this._ngDestroy$)) + .subscribe((pInfo: ProductInfo[]) => { + pInfo = pInfo || []; + this._productsInfoData = pInfo; + this.productsTable.setupDataForSmartTable(pInfo); + }); + + this.instructions.step = Step.Two; + } else { + this.instructions.step = Step.One; + } + } + + private _listenForEntityLocaleTranslate() { + this.translate.onLangChange + .pipe(takeUntil(this._ngDestroy$)) + .subscribe(() => { + this.productsTable.setupDataForSmartTable( + this._productsInfoData + ); + }); + } + + ngOnDestroy() { + this._ngDestroy$.next(); + this._ngDestroy$.complete(); + } +} diff --git a/packages/admin-web-angular/src/app/pages/+simulation/simulation.module.ts b/packages/admin-web-angular/src/app/pages/+simulation/simulation.module.ts new file mode 100644 index 0000000..a3f7904 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+simulation/simulation.module.ts @@ -0,0 +1,56 @@ +import { NgModule } from '@angular/core'; +import { CommonModule, JsonPipe } from '@angular/common'; +import { RouterModule } from '@angular/router'; +import { FormWizardModule } from '@ever-co/angular2-wizard'; +import { Ng2SmartTableModule } from 'ng2-smart-table'; +import { ToasterModule } from 'angular2-toaster'; +import { TranslateModule } from '@ngx-translate/core'; +import { ThemeModule } from '../../@theme'; +import { AdminStorageService } from '../../@core/data/fakeDataServices/storageService'; +import { UserMutationModule } from '../../@shared/user/user-mutation'; +import { WarehouseMutationModule } from '../../@shared/warehouse/warehouse-mutation'; +import { SimulationComponent } from './simulation.component'; +import { routes } from './simulation.routes'; +import { SimulationTableModule } from '../../@shared/render-component/simulation-table/simulation-table.module'; +import { GeoLocationService } from '../../@core/data/geo-location.service'; +import { HighlightModule } from 'ngx-highlightjs'; +import { SimulationProductsComponent } from './products/products.component'; +import { CustomerOrdersModule } from '../+customers/+customer/ea-customer-orders/ea-customer-orders.module'; +import { SimulationInstructionsComponent } from './instructions/instructions.component'; +import { InviteRequestModalModule } from '@app/@shared/invite/invite-request/invite-request-modal.module'; +import { ByCodeModalModule } from '@app/@shared/invite/by-code/by-code-modal.module'; +import { SimulationOrderModule } from './order/order.module'; +import { RenderComponentsModule } from '@app/@shared/render-component/render-components.module'; +import { NbSpinnerModule, NbButtonModule } from '@nebular/theme'; + +@NgModule({ + imports: [ + CommonModule, + ThemeModule, + FormWizardModule, + Ng2SmartTableModule, + ToasterModule.forRoot(), + TranslateModule.forChild(), + RouterModule.forChild(routes), + HighlightModule, + UserMutationModule, + InviteRequestModalModule, + ByCodeModalModule, + WarehouseMutationModule, + SimulationTableModule, + CustomerOrdersModule, + SimulationOrderModule, + RenderComponentsModule, + NbSpinnerModule, + NbButtonModule, + ], + declarations: [ + SimulationComponent, + SimulationProductsComponent, + SimulationInstructionsComponent, + ], + providers: [AdminStorageService, JsonPipe, GeoLocationService] +}) +export class SimulationModule { + public static routes = routes; +} diff --git a/packages/admin-web-angular/src/app/pages/+simulation/simulation.routes.ts b/packages/admin-web-angular/src/app/pages/+simulation/simulation.routes.ts new file mode 100644 index 0000000..b203a6a --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+simulation/simulation.routes.ts @@ -0,0 +1,9 @@ +import { Routes } from '@angular/router'; +import { SimulationComponent } from './simulation.component'; + +export const routes: Routes = [ + { + path: '', + component: SimulationComponent, + }, +]; diff --git a/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse-order/warehouse-info/warehouse-info.component.html b/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse-order/warehouse-info/warehouse-info.component.html new file mode 100644 index 0000000..a599011 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse-order/warehouse-info/warehouse-info.component.html @@ -0,0 +1,47 @@ + + diff --git a/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse-order/warehouse-info/warehouse-info.component.scss b/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse-order/warehouse-info/warehouse-info.component.scss new file mode 100644 index 0000000..a24d859 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse-order/warehouse-info/warehouse-info.component.scss @@ -0,0 +1,12 @@ +.warehouse-redirect { + cursor: pointer; + color: #009100; +} + +.json-info { + padding-top: 12px; +} + +.customer-warehouse-info { + padding-bottom: 0px; +} diff --git a/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse-order/warehouse-info/warehouse-info.component.ts b/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse-order/warehouse-info/warehouse-info.component.ts new file mode 100644 index 0000000..e006502 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse-order/warehouse-info/warehouse-info.component.ts @@ -0,0 +1,31 @@ +import { Component, OnInit } from '@angular/core'; +import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; +import { Router } from '@angular/router'; + +@Component({ + styleUrls: ['warehouse-info.component.scss'], + templateUrl: 'warehouse-info.component.html', +}) +export class WarehouseInfoComponent implements OnInit { + public warehouseId: string; + showCode: boolean = false; + + public selectedWarehouse: any; + + constructor( + private readonly activeModal: NgbActiveModal, + private router: Router + ) {} + + cancel() { + this.activeModal.dismiss('canceled'); + } + + ngOnInit(): void { + console.log(this.selectedWarehouse); + } + redirectToWarehousePage() { + this.router.navigate([`/stores/${this.warehouseId}`]); + this.activeModal.dismiss('canceled'); + } +} diff --git a/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse-order/warehouse-order.component.html b/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse-order/warehouse-order.component.html new file mode 100644 index 0000000..95156db --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse-order/warehouse-order.component.html @@ -0,0 +1,11 @@ + +
+

+ +

+
diff --git a/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse-order/warehouse-order.component.scss b/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse-order/warehouse-order.component.scss new file mode 100644 index 0000000..d8fc361 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse-order/warehouse-order.component.scss @@ -0,0 +1,4 @@ +.iconsCont { + margin-top: 10px; + cursor: pointer; +} diff --git a/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse-order/warehouse-order.component.ts b/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse-order/warehouse-order.component.ts new file mode 100644 index 0000000..3cf2855 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse-order/warehouse-order.component.ts @@ -0,0 +1,70 @@ +import { Component, Input, OnInit } from '@angular/core'; +import { ViewCell } from 'ng2-smart-table'; +import { ToasterService } from 'angular2-toaster'; +import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; +import { WarehouseOrdersService } from '../../../@core/data/warehouseOrders.service'; +import { WarehouseOrderModalComponent } from '../../../@shared/warehouse/+warehouse-order-modal/warehouse-order-modal.component'; +import { WarehouseInfoComponent } from './warehouse-info/warehouse-info.component'; + +@Component({ + styleUrls: ['./warehouse-order.component.scss'], + templateUrl: './warehouse-order.component.html', +}) +export class WarehouseOrderComponent implements ViewCell, OnInit { + @Input() + value: any; + @Input() + rowData: any; + + get renderValue(): string { + const orderAction = + 'CUSTOMERS_VIEW.' + this.value.actionName.toString(); + return orderAction; + } + + constructor( + private readonly _modalService: NgbModal, + private readonly _warehouseOrdersService: WarehouseOrdersService, + private readonly _toasterService: ToasterService, + private readonly modalService: NgbModal + ) {} + + ngOnInit() {} + + openWarehouseOrderModal() { + const componentRef = this._modalService.open( + WarehouseOrderModalComponent, + { size: 'lg' } + ); + const instance: WarehouseOrderModalComponent = + componentRef.componentInstance; + + instance.warehouseId = this.rowData.id; + + instance.makeOrderEmitter.subscribe((data) => { + const userId = this.value.actionOwnerId.toString(); + const warehouseId = instance.warehouseId; + + this._warehouseOrdersService + .createOrder({ userId, warehouseId, products: data }) + .subscribe(() => { + this._toasterService.pop( + `success`, + `The order is finished!` + ); + componentRef.close(); + }); + }); + } + + openInfo() { + const activeModal = this.modalService.open(WarehouseInfoComponent, { + size: 'sm', + container: 'nb-layout', + }); + const modalComponent: WarehouseInfoComponent = + activeModal.componentInstance; + modalComponent.warehouseId = this.rowData.id; + modalComponent.selectedWarehouse = this.rowData; + } +} diff --git a/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse-track/warehouse-track.component.html b/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse-track/warehouse-track.component.html new file mode 100644 index 0000000..3fbac45 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse-track/warehouse-track.component.html @@ -0,0 +1,146 @@ +
+
+
+
+ + +
+ + +
+ + {{ + 'WAREHOUSES_VIEW.MERCHANTS.TRACK_ALL_MERCHANTS' + | translate + }} +
+ +
+
+
+
+
+ + + {{ + 'WAREHOUSES_VIEW.MERCHANTS.FILTER_MERCHANTS' + | translate + }} + + +
+ + + + {{ item }} + + + + +

+ + {{ item }} +

+
+
+ + +

+ + {{ item }} +

+
+
+
+ +
+
+
+ {{ selectedStore.name }} +
+
    +
  • Phone: {{ selectedStore?.contactPhone }}
  • +
  • Email: {{ selectedStore?.contactEmail }}
  • +
  • Address: {{ selectedStore?.geoLocation?.streetAddress }}
  • +
+
+
+
+ {{ selectedCarrier.fullName }} +
+
    +
  • Delivery Count: {{ selectedCarrier.numberOfDeliveries }}
  • +
  • Phone: {{ selectedCarrier.phone }}
  • +
  • Address: {{ selectedStore?.geoLocation?.streetAddress }}
  • +
+
+
+
+
+
+
+
+
+
diff --git a/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse-track/warehouse-track.component.scss b/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse-track/warehouse-track.component.scss new file mode 100644 index 0000000..64404a4 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse-track/warehouse-track.component.scss @@ -0,0 +1,7 @@ +.googleMap { + height: 700px; +} + +.go-back { + cursor: pointer; +} diff --git a/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse-track/warehouse-track.component.ts b/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse-track/warehouse-track.component.ts new file mode 100644 index 0000000..b12e13d --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse-track/warehouse-track.component.ts @@ -0,0 +1,208 @@ +import { Component, ViewChild, OnInit } from '@angular/core'; +import { WarehousesService } from '@app/@core/data/warehouses.service'; +import { environment } from 'environments/environment'; +import { Marker } from '@agm/core/services/google-maps-types'; +import Warehouse from '@modules/server.common/entities/Warehouse'; +import { + getCountryName, + CountryName, +} from '@modules/server.common/entities/GeoLocation'; +import { Location } from '@angular/common'; +@Component({ + templateUrl: './warehouse-track.component.html', + styleUrls: ['./warehouse-track.component.scss'], +}) +export class WarehouseTrackComponent implements OnInit { + @ViewChild('gmap', { static: true }) + gmapElement: any; + map: google.maps.Map; + + merchantMarkers: any[] = []; + merchants: Warehouse[] = []; + listOfCities: string[] = []; + listOfCountries: CountryName[] = []; + listOfMerchants: string[]; + merchantCity: string; + merchantName: string; + merchantCountry: CountryName; + + constructor( + private warehouseService: WarehousesService, + private location: Location + ) {} + + ngOnInit(): void { + this.showMap(); + + const warehouseService = this.warehouseService + .getStoreLivePosition() + .subscribe((store) => { + this.merchants = store; + + if (this.merchantMarkers.length === 0) { + this.listOfCities = Array.from( + new Set(store.map((mer) => mer.geoLocation.city)) + ).sort(); + this.listOfCountries = Array.from( + new Set( + store.map((mer) => + getCountryName(mer.geoLocation.countryId) + ) + ) + ).sort(); + this.listOfMerchants = this.setSort( + store.map((mer) => mer.name) + ); + this.populateMarkers(store, this.merchantMarkers); + } else if (store.length === this.merchantMarkers.length) { + this.updateMarkers(store); + } else { + this.updateMarkers(store); + } + }); + } + + setSort(arr: any): any[] { + return Array.from(new Set(arr)).sort(); + } + + showMap() { + const lat = environment.DEFAULT_LATITUDE; + const lng = environment.DEFAULT_LONGITUDE; + + const mapProp = { + center: new google.maps.LatLng(lat, lng), + zoom: 15, + mapTypeId: google.maps.MapTypeId.ROADMAP, + }; + this.map = new google.maps.Map(this.gmapElement.nativeElement, mapProp); + } + + filterByName(event) { + if (event) { + this.merchantName = event; + + this.deleteMarkerStorage(); + const filteredMerchants = this.merchants.filter( + (mer) => mer.name === this.merchantName + ); + this.populateMarkers(filteredMerchants, this.merchantMarkers); + } else { + this.deleteMarkerStorage(); + this.populateMarkers(this.merchants, this.merchantMarkers); + } + } + + filterByCity(event) { + if (event) { + this.merchantCity = event; + this.merchantName = undefined; + this.deleteMarkerStorage(); + const filteredMerchants = this.merchants.filter( + (mer) => mer.geoLocation.city === this.merchantCity + ); + this.populateMarkers(filteredMerchants, this.merchantMarkers); + this.listOfMerchants = this.setSort( + filteredMerchants.map((mer) => mer.name) + ); + } else { + this.deleteMarkerStorage(); + this.populateMarkers(this.merchants, this.merchantMarkers); + } + } + + filterByCountry(event) { + if (event) { + this.merchantCountry = event; + this.merchantCity = undefined; + this.merchantName = undefined; + this.deleteMarkerStorage(); + const filteredMerchants = this.merchants.filter( + (mer) => + getCountryName(mer.geoLocation.countryId) === + this.merchantCountry + ); + this.listOfCities = this.setSort( + filteredMerchants.map((mer) => mer.geoLocation.city) + ); + this.listOfMerchants = this.setSort( + filteredMerchants.map((mer) => mer.name) + ); + this.populateMarkers(filteredMerchants, this.merchantMarkers); + } else { + this.deleteMarkerStorage(); + this.populateMarkers(this.merchants, this.merchantMarkers); + } + } + + populateMarkers(merchantArray, markerStorage) { + const latlngbounds = new google.maps.LatLngBounds(); + + merchantArray.forEach((mer) => { + const coords = new google.maps.LatLng( + mer.geoLocation.loc.coordinates[1], + mer.geoLocation.loc.coordinates[0] + ); + const storeIcon = environment.MAP_MERCHANT_ICON_LINK; + const marker = this.addMarker(coords, this.map, storeIcon); + markerStorage.push({ marker, id: mer.id }); + latlngbounds.extend(coords); + }); + + this.map.fitBounds(latlngbounds); + } + + updateMarkers(merchantArray: Warehouse[]) { + merchantArray.forEach((mer, index) => { + const newCoords = new google.maps.LatLng( + mer.geoLocation.loc.coordinates[1], + mer.geoLocation.loc.coordinates[0] + ); + let markerIndex; + const marker = this.merchantMarkers.find((markerItem, i) => { + if (markerItem.id === mer.id) { + markerIndex = i; + return true; + } else { + return false; + } + }); + if (marker) { + if (!newCoords.equals(marker.marker.getPosition())) { + this.merchantMarkers[markerIndex].marker.setPosition( + newCoords + ); + } + } + }); + } + + deleteMarkerStorage() { + this.merchantMarkers.forEach((mar) => { + mar.marker.setMap(null); + }); + this.merchantMarkers = []; + } + + addMarker(position, map, icon) { + return new google.maps.Marker({ + position, + map, + icon, + }); + } + + public selectedCarrier(): boolean { + // TODO: implement + return true; + } + + public selectedStore(): boolean { + // TODO: implement + return true; + } + + goBack() { + this.location.back(); + } +} diff --git a/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse-track/warehouse-track.module.ts b/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse-track/warehouse-track.module.ts new file mode 100644 index 0000000..c2b6008 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse-track/warehouse-track.module.ts @@ -0,0 +1,26 @@ +import { NgModule } from '@angular/core'; +import { FormWizardModule } from '@ever-co/angular2-wizard'; +import { Ng2SmartTableModule } from 'ng2-smart-table'; +import { FileUploadModule } from 'ng2-file-upload'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { TranslateModule } from '@ngx-translate/core'; +import { ThemeModule } from '@app/@theme'; +import { NgSelectModule } from '@ng-select/ng-select'; +import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; +import { WarehouseTrackComponent } from './warehouse-track.component'; + +@NgModule({ + imports: [ + FormWizardModule, + Ng2SmartTableModule, + FileUploadModule, + FormsModule, + ReactiveFormsModule, + TranslateModule.forChild(), + ThemeModule, + NgSelectModule, + ], + declarations: [WarehouseTrackComponent], + providers: [NgbActiveModal] +}) +export class WarehouseTrackModule {} diff --git a/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/+warehouse-manage/index.ts b/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/+warehouse-manage/index.ts new file mode 100644 index 0000000..f954be5 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/+warehouse-manage/index.ts @@ -0,0 +1,2 @@ +export * from './warehouse-manage.component'; +export * from './warehouse-manage.module'; diff --git a/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/+warehouse-manage/warehouse-manage.component.html b/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/+warehouse-manage/warehouse-manage.component.html new file mode 100644 index 0000000..8fb6821 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/+warehouse-manage/warehouse-manage.component.html @@ -0,0 +1,47 @@ + + +
+ + +
+ + + + +

{{ 'WAREHOUSE_MANAGE.MANAGE_STORE' | translate }}

+
+ + +
+
+ +
+
+ + + + + +
+
+
diff --git a/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/+warehouse-manage/warehouse-manage.component.scss b/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/+warehouse-manage/warehouse-manage.component.scss new file mode 100644 index 0000000..4cc9eef --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/+warehouse-manage/warehouse-manage.component.scss @@ -0,0 +1,21 @@ +@import '../../../../@theme/styles/themes'; +@import '../../../../@shared/styles/control-icon.shared'; + +@include nb-install-component() { + @include control-icon; +} + +nb-card-header { + min-height: 71px; + position: relative; + + .text { + position: absolute; + top: 50%; + transform: translateY(-50%); + } + + #map-type { + float: right; + } +} diff --git a/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/+warehouse-manage/warehouse-manage.component.ts b/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/+warehouse-manage/warehouse-manage.component.ts new file mode 100644 index 0000000..d12c604 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/+warehouse-manage/warehouse-manage.component.ts @@ -0,0 +1,128 @@ +import { Component, OnInit, ViewChild } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; +import { FormBuilder, FormControl, FormGroup } from '@angular/forms'; +import { ToasterService } from 'angular2-toaster'; +import { first, map, switchMap } from 'rxjs/operators'; +import { ContactInfoFormComponent } from '../../../../@shared/warehouse/forms'; +import { LocationFormComponent } from '../../../../@shared/forms/location'; +import { WarehouseRouter } from '@modules/client.common.angular2/routers/warehouse-router.service'; +import Warehouse from '@modules/server.common/entities/Warehouse'; +import { WarehouseManageTabsComponent } from '../../../../@shared/warehouse/forms/warehouse-manage-tabs/warehouse-manage-tabs.component'; + +@Component({ + selector: 'ea-warehouse-manage', + templateUrl: './warehouse-manage.component.html', + styleUrls: ['./warehouse-manage.component.scss'], +}) +export class WarehouseManageComponent implements OnInit { + loading: boolean; + + @ViewChild('warehouseManageTabs') + warehouseManageTabs: WarehouseManageTabsComponent; + + @ViewChild('contactInfoForm') + contactInfoForm: ContactInfoFormComponent; + + @ViewChild('locationForm') + locationForm: LocationFormComponent; + + readonly form: FormGroup = this._formBuilder.group({ + tabsForm: WarehouseManageTabsComponent.buildForm(this._formBuilder), + }); + + readonly tabsForm = this.form.get('tabsForm') as FormControl; + + readonly warehouseId$ = this.activatedRoute.params.pipe( + map((p) => p['id']) + ); + + _currentWarehouse: Warehouse; + + constructor( + private readonly _formBuilder: FormBuilder, + private readonly activatedRoute: ActivatedRoute, + private readonly toasterService: ToasterService, + private readonly warehouseRouter: WarehouseRouter + ) {} + + ngOnInit() { + this.loadWarehouse(); + } + + async loadWarehouse() { + const warehouseId = await this.warehouseId$.pipe(first()).toPromise(); + const warehouse = await this.warehouseRouter + .get(warehouseId) + .pipe(first()) + .toPromise(); + this._currentWarehouse = warehouse; + this.warehouseManageTabs.setValue(warehouse); + } + + get validForm() { + return this.warehouseManageTabs.validForm; + } + + async updateWarehouse() { + try { + this.loading = true; + const tabsInfoRaw = this.warehouseManageTabs.getValue(); + + const warehouseRaw = { + ...this._currentWarehouse, + ...tabsInfoRaw.basicInfo, + ...tabsInfoRaw.contactInfo, + geoLocation: tabsInfoRaw.location, + deliveryAreas: tabsInfoRaw.deliveryAreas, + isPaymentEnabled: tabsInfoRaw.isPaymentEnabled, + paymentGateways: tabsInfoRaw.paymentsGateways, + isCashPaymentEnabled: tabsInfoRaw.isCashPaymentEnabled, + }; + + const username = this.tabsForm.value.account['username']; + const passwordOld = tabsInfoRaw.password.current; + const passwordNew = tabsInfoRaw.password.new; + + if (passwordNew.length > 0) { + await this.warehouseRouter.updatePassword( + this._currentWarehouse.id, + { + current: passwordOld, + new: passwordNew, + } + ); + } + + const warehouse = await this.warehouseRouter.save( + warehouseRaw as Warehouse + ); + + this.loading = false; + + this._showWarehouseUpdateSuccessMessage(warehouse); + + this.warehouseManageTabs.warehouseUpdateFinish(); + this.warehouseManageTabs.accountComponent.form.setValue({ + username: username, + password: { + confirm: '', + current: '', + new: '', + }, + }); + } catch (err) { + this.loading = false; + this.toasterService.pop( + 'error', + `Error in updating customer: "${err.message}"` + ); + } + } + + private _showWarehouseUpdateSuccessMessage(warehouse) { + this.toasterService.pop( + 'success', + `Store ${warehouse.name} was updated` + ); + } +} diff --git a/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/+warehouse-manage/warehouse-manage.module.ts b/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/+warehouse-manage/warehouse-manage.module.ts new file mode 100644 index 0000000..abc6b65 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/+warehouse-manage/warehouse-manage.module.ts @@ -0,0 +1,33 @@ +import { RouterModule, Routes } from '@angular/router'; +import { TranslateModule } from '@ngx-translate/core'; +import { NgModule } from '@angular/core'; +import { LocationFormModule } from '../../../../@shared/forms/location'; +import { WarehouseFormsModule } from '../../../../@shared/warehouse/forms'; +import { GoogleMapModule } from '../../../../@shared/forms/google-map/google-map.module'; +import { ThemeModule } from '../../../../@theme'; +import { WarehouseManageComponent } from './warehouse-manage.component'; +import { ToasterModule } from 'angular2-toaster'; +import { NbSpinnerModule, NbButtonModule } from '@nebular/theme'; + +const routes: Routes = [ + { + path: '', + component: WarehouseManageComponent, + }, +]; + +@NgModule({ + imports: [ + ThemeModule, + ToasterModule, + TranslateModule.forChild(), + RouterModule.forChild(routes), + WarehouseFormsModule, + LocationFormModule, + GoogleMapModule, + NbSpinnerModule, + NbButtonModule, + ], + declarations: [WarehouseManageComponent], +}) +export class WarehouseManageModule {} diff --git a/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/+warehouse-order/create-user/warehouse-order-create-user.component.html b/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/+warehouse-order/create-user/warehouse-order-create-user.component.html new file mode 100644 index 0000000..63a71f5 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/+warehouse-order/create-user/warehouse-order-create-user.component.html @@ -0,0 +1,27 @@ + + + + +
+ + + + +
+ + +
diff --git a/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/+warehouse-order/create-user/warehouse-order-create-user.component.scss b/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/+warehouse-order/create-user/warehouse-order-create-user.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/+warehouse-order/create-user/warehouse-order-create-user.component.ts b/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/+warehouse-order/create-user/warehouse-order-create-user.component.ts new file mode 100644 index 0000000..34e6f53 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/+warehouse-order/create-user/warehouse-order-create-user.component.ts @@ -0,0 +1,118 @@ +import { FormGroup, FormBuilder, FormControl } from '@angular/forms'; + +import { + Component, + EventEmitter, + Input, + OnDestroy, + Output, + AfterViewInit, +} from '@angular/core'; + +import { BasicInfoFormComponent } from '../../../../../@shared/user/forms'; +import { LocationFormComponent } from '../../../../../@shared/forms/location'; +import { UsersService } from '../../../../../@core/data/users.service'; +import { Observable, Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; +import { ToasterService } from 'angular2-toaster'; + +@Component({ + selector: 'ea-warehouse-order-create-user', + styleUrls: ['./warehouse-order-create-user.component.scss'], + templateUrl: './warehouse-order-create-user.component.html', +}) +export class WarehouseOrderCreateUserComponent + implements OnDestroy, AfterViewInit { + readonly form: FormGroup = this._formBuilder.group({ + basicInfo: BasicInfoFormComponent.buildForm(this._formBuilder), + apartment: LocationFormComponent.buildApartmentForm(this._formBuilder), + location: LocationFormComponent.buildForm(this._formBuilder), + }); + + readonly basicInfo = this.form.get('basicInfo') as FormControl; + readonly apartment = this.form.get('apartment') as FormControl; + readonly location = this.form.get('location') as FormControl; + + @Input() + createUserEvent: Observable; + + mapCoordEmitter = new EventEmitter< + google.maps.LatLng | google.maps.LatLngLiteral + >(); + + mapGeometryEmitter = new EventEmitter< + google.maps.places.PlaceGeometry | google.maps.GeocoderGeometry + >(); + + @Output() + newUserEmitter = new EventEmitter(); + + private readonly _ngDestroy$ = new Subject(); + + constructor( + private readonly _formBuilder: FormBuilder, + private readonly _usersService: UsersService, + private readonly _toasterService: ToasterService + ) {} + + ngAfterViewInit() { + this._listenForNewUser(); + } + + onCoordinatesChanges( + location: google.maps.LatLng | google.maps.LatLngLiteral + ) { + this.mapCoordEmitter.emit(location); + } + + onGeometrySend( + geometry: + | google.maps.places.PlaceGeometry + | google.maps.GeocoderGeometry + ) { + this.mapGeometryEmitter.emit(geometry); + } + + private _listenForNewUser() { + this.createUserEvent.pipe(takeUntil(this._ngDestroy$)).subscribe(() => { + if (this.form.valid) { + this._registerNewUser(); + } + }); + } + + private async _registerNewUser() { + const rawData = { + ...this.basicInfo.value, + apartment: this.apartment.value, + geoLocation: this.location.value, + }; + + // GeoJSON use reversed order for coordinates from our implementation. + // we use lat => lng but GeoJSON use lng => lat. + this.location.value.loc.coordinates.reverse(); + + try { + const user = await this._usersService.registerUser({ + user: rawData, + }); + const userFirstname = user.firstName; + this.newUserEmitter.emit(user); + this.form.reset(); + this._toasterService.pop( + 'success', + `User "${userFirstname}" was added successfully` + ); + } catch (error) { + this._toasterService.pop( + 'error', + `Error in creating customer: "${error.message}"` + ); + } + } + + ngOnDestroy() { + this._ngDestroy$.next(); + this._ngDestroy$.complete(); + } +} diff --git a/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/+warehouse-order/warehouse-order.component.html b/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/+warehouse-order/warehouse-order.component.html new file mode 100644 index 0000000..d55691c --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/+warehouse-order/warehouse-order.component.html @@ -0,0 +1,84 @@ + + + + + +
+ + + +
+
+
+ + + +
+ +
+
+
+ +
+
+ + + + + +
diff --git a/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/+warehouse-order/warehouse-order.component.scss b/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/+warehouse-order/warehouse-order.component.scss new file mode 100644 index 0000000..0aec0f8 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/+warehouse-order/warehouse-order.component.scss @@ -0,0 +1,53 @@ +form-wizard { + wizard-step.step-1 { + text-align: center; + } + + wizard-step { + button { + margin: 6%; + margin-top: 8%; + margin: auto !important !important; + } + } +} + +// Align only last child (address column) because text is too long. +:host ::ng-deep ng2-smart-table { + thead tr { + text-align: center; + input { + text-align: center; + } + } + + tbody tr { + cursor: pointer; + text-align: center; + + td:last-child { + text-align: left; + } + } +} + +.ng-valid[required], +.ng-valid.required { + border-left: 5px solid #42a948; +} + +:host ::ng-deep .card-footer button { + background-color: #111111 !important; + border: none !important; + color: white !important; + cursor: pointer !important; +} + +:host ::ng-deep .card-footer button:hover { + background-color: #bebebe !important; + color: #111111 !important; +} + +body > div.pac-container.pac-logo { + background: green !important !important !important !important !important !important; +} diff --git a/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/+warehouse-order/warehouse-order.component.ts b/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/+warehouse-order/warehouse-order.component.ts new file mode 100644 index 0000000..384f027 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/+warehouse-order/warehouse-order.component.ts @@ -0,0 +1,359 @@ +import { + Component, + ViewChild, + OnInit, + OnDestroy, + EventEmitter, +} from '@angular/core'; + +import { ActivatedRoute } from '@angular/router'; +import { ToasterService } from 'angular2-toaster'; +import { WizardComponent } from '@ever-co/angular2-wizard'; +import { LocalDataSource } from 'ng2-smart-table'; +import { Subject } from 'rxjs'; +import { takeUntil, finalize } from 'rxjs/operators'; +import { UsersService } from '../../../../@core/data/users.service'; +import { WarehouseOrdersService } from '../../../../@core/data/warehouseOrders.service'; +import User from '@modules/server.common/entities/User'; +import { IOrderCreateInputProduct } from '@modules/server.common/routers/IWarehouseOrdersRouter'; +import { WarehouseOrderCreateUserComponent } from './create-user/warehouse-order-create-user.component'; +import { WarehouseOrderModalComponent } from '../../../../@shared/warehouse/+warehouse-order-modal/warehouse-order-modal.component'; +import { TranslateService } from '@ngx-translate/core'; +import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; + +const perPage = 5; + +@Component({ + styleUrls: ['./warehouse-order.component.scss'], + templateUrl: './warehouse-order.component.html', +}) +export class WarehouseOrderComponent implements OnInit, OnDestroy { + loading: boolean; + + warehouseId: string; + titleSelectAdd: string = this._translate( + this.TRANSLATE_PREFIXES.STEP2.TITLE.SELECT_ADD + ); + isSelectedFromExisting: boolean; + showNextButton: boolean = false; + isOrderAllowed: boolean = false; + + settingsSmartTable: object; + sourceSmartTable = new LocalDataSource(); + + createUserEmitter = new EventEmitter(); + orderFinishedEmitter = new EventEmitter(); + + @ViewChild('wizardForm', { static: true }) + private _wizardForm: WizardComponent; + + @ViewChild('wizardFormStepTwo') + public wizardFormStepTwo: WizardComponent; + + @ViewChild('warehouseOrderCreateUser') + private _warehouseOrderCreateUser: WarehouseOrderCreateUserComponent; + + @ViewChild('warehouseOrderModal') + private _warehouseOrderModal: WarehouseOrderModalComponent; + + private _userIdToOrder: string; + private userSelected: any; + private _dataIsLoaded: boolean = false; + + private _noInfoSign = ''; + private _ngDestroy$ = new Subject(); + + private dataCount: number; + private $users; + + constructor( + private readonly activeModal: NgbActiveModal, + private readonly _activatedRoute: ActivatedRoute, + private readonly _toasterService: ToasterService, + private readonly _translateService: TranslateService, + private readonly _usersService: UsersService, + private readonly _warehouseOrdersService: WarehouseOrdersService + ) {} + + get TRANSLATE_PREFIXES() { + const wizardFormPrefix = + 'WAREHOUSE_VIEW.CREATE_ORDER_MODAL.WIZARD_FORM'; + const step1Prefix = 'STEP1'; + const step2Prefix = 'STEP2'; + const step3Prefix = 'STEP3'; + + return { + BUTTON_NEXT: `${wizardFormPrefix}.BUTTON_NEXT`, + BUTTON_PREV: `${wizardFormPrefix}.BUTTON_PREV`, + BUTTON_DONE: `${wizardFormPrefix}.BUTTON_DONE`, + STEP1: { + TITLE: `${wizardFormPrefix}.${step1Prefix}.TITLE`, + SELECT_FROM_EXISTING: `${wizardFormPrefix}.${step1Prefix}.SELECT_FROM_EXISTING`, + ADD_NEW_CUSTOMER: `${wizardFormPrefix}.${step1Prefix}.ADD_NEW_CUSTOMER`, + }, + STEP2: { + TITLE: { + SELECT_ADD: `${wizardFormPrefix}.${step2Prefix}.TITLE.SELECT_ADD`, + SELECT_CUSTOMER: `${wizardFormPrefix}.${step2Prefix}.TITLE.SELECT_CUSTOMER`, + ADD_NEW: `${wizardFormPrefix}.${step2Prefix}.TITLE.ADD_NEW`, + }, + SMART_TABLE: { + TITLES: { + FULL_NAME: `${wizardFormPrefix}.${step2Prefix}.SMART_TABLE.TITLES.FULL_NAME`, + EMAIL: `${wizardFormPrefix}.${step2Prefix}.SMART_TABLE.TITLES.EMAIL`, + PHONE: `${wizardFormPrefix}.${step2Prefix}.SMART_TABLE.TITLES.PHONE`, + ADDRESS: `${wizardFormPrefix}.${step2Prefix}.SMART_TABLE.TITLES.ADDRESS`, + }, + }, + }, + STEP3: { + TITLE: `${wizardFormPrefix}.${step3Prefix}.TITLE`, + }, + }; + } + + get isCreateOrderWizardAllowed() { + return ( + this.isSelectedFromExisting || + (this._warehouseOrderCreateUser !== undefined && + this._warehouseOrderCreateUser.form.valid) + ); + } + + get buttonDone() { + return this._translate(this.TRANSLATE_PREFIXES.BUTTON_DONE); + } + + get buttonNext() { + return this._translate(this.TRANSLATE_PREFIXES.BUTTON_NEXT); + } + + get buttonPrevious() { + return this._translate(this.TRANSLATE_PREFIXES.BUTTON_PREV); + } + + private get _isAllowedToCreateCustomer(): boolean { + return !this.isSelectedFromExisting; + } + + ngOnInit() { + this._listenStepWizard(); + this._getWarehouseIdFromRoute(); + this.smartTableChange(); + } + + onIsOrderAllowed(orderAllowed: boolean) { + this.isOrderAllowed = orderAllowed; + } + + onMakeOrder(orderProducts: IOrderCreateInputProduct[]) { + this.loading = true; + this._warehouseOrdersService + .createOrder({ + userId: this._userIdToOrder, + warehouseId: this.warehouseId, + products: orderProducts, + }) + .pipe(takeUntil(this._ngDestroy$)) + .pipe(finalize(() => this.orderFinishedEmitter.emit())) + .subscribe( + () => { + this.loading = false; + this._toasterService.pop( + `success`, + `User ${this.userSelected.name} finish the order` + ); + }, + () => { + this.loading = false; + this._toasterService.pop( + 'error', + 'Something is wrong, unable to place order' + ); + } + ); + } + + onNewUser(userData) { + userData.name = + (userData.firstName ? userData.firstName : '') + + ' ' + + (userData.lastName ? userData.lastName : ''); + this.userSelected = userData; + + this._userIdToOrder = userData.id; + } + + onNextStep() { + this._wizardForm.next(); + } + + selectExistingCustomer() { + this.titleSelectAdd = this._translate( + this.TRANSLATE_PREFIXES.STEP2.TITLE.SELECT_CUSTOMER + ); + + this.isSelectedFromExisting = true; + this.showNextButton = false; + + this._loadAndSetupData(); + this.onNextStep(); + } + + addNewCustomer() { + this.titleSelectAdd = this._translate( + this.TRANSLATE_PREFIXES.STEP2.TITLE.ADD_NEW + ); + + this.isSelectedFromExisting = false; + this.showNextButton = true; + + this.onNextStep(); + } + + selectFromExisting(ev) { + this.userSelected = ev.data; + this._userIdToOrder = ev.data.id; + this._wizardForm.next(); + } + + completeOrder() { + this._warehouseOrderModal.makeOrder(); + } + + cancel() { + this.activeModal.dismiss('canceled'); + } + + private _translate(key: string): string { + let translationResult = ''; + + this._translateService.get(key).subscribe((res) => { + translationResult = res; + }); + + return translationResult; + } + + private _loadAndSetupData() { + if (!this._dataIsLoaded) { + this._setupSmartTableSettings(); + this._loadDataSmartTable(); + this._dataIsLoaded = true; + } + } + + private _listenStepWizard() { + this._wizardForm.onStepChanged + .pipe(takeUntil(this._ngDestroy$)) + .subscribe((wizardStep) => { + const currentStepIndex = this._wizardForm.activeStepIndex; + + if (currentStepIndex === 0) { + this.titleSelectAdd = this._translate( + this.TRANSLATE_PREFIXES.STEP2.TITLE.SELECT_ADD + ); + } + if (currentStepIndex === 2 && this._isAllowedToCreateCustomer) { + this.createUserEmitter.emit(); + } + }); + } + + private _setupSmartTableSettings() { + const fullName = this._translate( + this.TRANSLATE_PREFIXES.STEP2.SMART_TABLE.TITLES.FULL_NAME + ); + const email = this._translate( + this.TRANSLATE_PREFIXES.STEP2.SMART_TABLE.TITLES.EMAIL + ); + const phone = this._translate( + this.TRANSLATE_PREFIXES.STEP2.SMART_TABLE.TITLES.PHONE + ); + const address = this._translate( + this.TRANSLATE_PREFIXES.STEP2.SMART_TABLE.TITLES.ADDRESS + ); + + this.settingsSmartTable = { + actions: false, + columns: { + name: { title: fullName }, + email: { title: email }, + phone: { title: phone }, + address: { title: address }, + }, + pager: { + display: true, + perPage, + }, + }; + } + + private async _loadDataSmartTable(page = 1) { + if (this.$users) { + await this.$users.unsubscribe(); + } + this.$users = this._usersService + .getUsers({ + skip: perPage * (page - 1), + limit: perPage, + }) + .pipe(takeUntil(this._ngDestroy$)) + .subscribe(async (users: User[]) => { + await this.loadDataCount(); + const formattedData = this._setupDataForSmartTable(users); + const usersData = new Array(this.dataCount); + + usersData.splice( + perPage * (page - 1), + perPage, + ...formattedData + ); + + await this.sourceSmartTable.load(usersData); + }); + } + + private _setupDataForSmartTable(users: User[]) { + return users.map((user: User) => { + return { + id: user.id, + name: ` + ${user.firstName || this._noInfoSign} ${user.lastName || this._noInfoSign} + `, + email: user.email || this._noInfoSign, + phone: user.phone || this._noInfoSign, + address: user.fullAddress || this._noInfoSign, + }; + }); + } + + private _getWarehouseIdFromRoute() { + this._activatedRoute.children[0].children[0].children[0].children[0].params + .pipe(takeUntil(this._ngDestroy$)) + .subscribe((params) => { + this.warehouseId = params.id; + }); + } + + private async smartTableChange() { + this.sourceSmartTable + .onChanged() + .pipe(takeUntil(this._ngDestroy$)) + .subscribe(async (event) => { + if (event.action === 'page') { + const page = event.paging.page; + this._loadDataSmartTable(page); + } + }); + } + + private async loadDataCount() { + this.dataCount = await this._usersService.getCountOfUsers(); + } + + ngOnDestroy() { + this._ngDestroy$.next(); + this._ngDestroy$.complete(); + } +} diff --git a/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/+warehouse-order/warehouse-order.module.ts b/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/+warehouse-order/warehouse-order.module.ts new file mode 100644 index 0000000..1788b9b --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/+warehouse-order/warehouse-order.module.ts @@ -0,0 +1,30 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { FormWizardModule } from '@ever-co/angular2-wizard'; +import { Ng2SmartTableModule } from 'ng2-smart-table'; +import { UserFormsModule } from '../../../../@shared/user/forms'; +import { LocationFormModule } from '../../../../@shared/forms/location'; +import { GoogleMapModule } from '../../../../@shared/forms/google-map/google-map.module'; +import { WarehouseOrderComponent } from './warehouse-order.component'; +import { WarehouseOrderCreateUserComponent } from './create-user/warehouse-order-create-user.component'; +import { WarehouseOrderModalModule } from '../../../../@shared/warehouse/+warehouse-order-modal/warehouse-order-modal.module'; +import { TranslateModule } from '@ngx-translate/core'; +import { NbSpinnerModule } from '@nebular/theme'; +import { ThemeModule } from '../../../../@theme'; + +@NgModule({ + imports: [ + CommonModule, + ThemeModule, + FormWizardModule, + Ng2SmartTableModule, + TranslateModule.forChild(), + UserFormsModule, + LocationFormModule, + GoogleMapModule, + WarehouseOrderModalModule, + NbSpinnerModule, + ], + declarations: [WarehouseOrderComponent, WarehouseOrderCreateUserComponent] +}) +export class WarehouseOrderModule {} diff --git a/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/+warehouse-products-view/warehouse-products-view.component.html b/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/+warehouse-products-view/warehouse-products-view.component.html new file mode 100644 index 0000000..2e21162 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/+warehouse-products-view/warehouse-products-view.component.html @@ -0,0 +1,89 @@ + + + {{ 'WAREHOUSE_VIEW.TOP_PRODUCTS' | translate }} + + +
    +
  • +
    +
    + + + {{ warehouseProduct.soldCount }} + + + + + + + + + + + + +
    +
    + +
    + +
    +
    + {{ + localeTranslate( + warehouseProduct?.product?.title + ) + }} +
    + {{ warehouseProduct?.count }} +
    +
    +
  • +
+
+
diff --git a/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/+warehouse-products-view/warehouse-products-view.component.scss b/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/+warehouse-products-view/warehouse-products-view.component.scss new file mode 100644 index 0000000..efdcb40 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/+warehouse-products-view/warehouse-products-view.component.scss @@ -0,0 +1,144 @@ +@import '../../../../@theme/styles/themes'; +@import '~bootstrap/scss/mixins/breakpoints'; +@import '~@nebular/theme/styles/global/breakpoints'; +$brand: #2a2c39; +$brand-lighted: #353748; +$brand-darken: darken($brand, 5%); + +nb-card { + transform: translate3d(0, 0, 0); +} + +.example-container:not(:last-child) { + margin-bottom: 1.5rem; +} + +:host ::ng-deep .productsContainer a { + border: none; + background-color: rgba(182, 176, 176, 0.05) !important; + color: #111111 !important; + + .warehouse-product-image { + text-align: center; + span { + text-align: left !important; + padding: 0 !important; + } + span.badge { + padding: 5px !important; + } + } +} + +:host ::ng-deep ng2-smart-table { + .tableImg { + height: 100px; + width: 100px; + } +} + +.nb-theme-cosmic .whiteDiv { + color: white !important; +} + +.products { + list-style-type: none; + height: inherit !important; + max-width: 80%; + margin: 0 auto; + .product-card-container { + border: 0; + padding-bottom: 0; + margin-bottom: 1.25rem; + .product-card { + margin: 0; + padding: 0; + text-align: center; + .product-image-container { + cursor: pointer; + } + .product-image-container:active { + background: #000; + .product-image { + opacity: 0.8; + } + } + .product-buttons { + position: absolute; + top: 20px; + right: 20px; + .add-button { + padding: 0; + font-size: 30px; + color: #fff; + } + } + .product-image { + width: 100%; + } + .product-actions-info-bar { + background-color: $brand-darken; + color: white; + margin-bottom: 0; + padding-top: 5px; + padding-bottom: 5px; + margin-top: -6px; + .actions { + a { + margin-top: -2px; + border-radius: 3px; + color: $brand-lighted; + float: right; + border-color: #ccc; + color: white; + margin-right: 10px; + padding: 4px; + font-size: 18px; + line-height: 0; + } + } + .info { + span { + margin-top: -2px; + border-radius: 3px; + color: $brand-lighted; + float: left; + border-color: #ccc; + color: rgb(161, 161, 161); + margin-left: 10px; + padding: 4px; + font-size: 18px; + line-height: 0; + } + } + } + .product-mini-bar { + font-size: 17px; + background-color: $brand-lighted; + color: white; + text-align: center; + margin-bottom: 0; + padding-top: 11px; + padding-bottom: 11px; + margin-top: -6px; + .count { + background: #fff; + margin-top: -2px; + padding: 2px; + border-radius: 3px; + color: $brand-lighted; + float: right; + margin-right: 10px; + } + } + } + + .name { + width: 75%; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + display: inline-block; + } + } +} diff --git a/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/+warehouse-products-view/warehouse-products-view.component.ts b/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/+warehouse-products-view/warehouse-products-view.component.ts new file mode 100644 index 0000000..ae74bb3 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/+warehouse-products-view/warehouse-products-view.component.ts @@ -0,0 +1,107 @@ +import { + Component, + ViewChild, + OnInit, + OnDestroy, + EventEmitter, + Input, + OnChanges, +} from '@angular/core'; +import { ActivatedRoute, Router } from '@angular/router'; +import { ToasterService } from 'angular2-toaster'; +import { WizardComponent } from '@ever-co/angular2-wizard'; +import { LocalDataSource } from 'ng2-smart-table'; +import { Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; +import { TranslateService } from '@ngx-translate/core'; +import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; +import WarehouseProduct from '@modules/server.common/entities/WarehouseProduct'; +import { WarehouseProductsRouter } from '@modules/client.common.angular2/routers/warehouse-products-router.service'; +import Warehouse from '@modules/server.common/entities/Warehouse'; +import { ILocaleMember } from '@modules/server.common/interfaces/ILocale'; +import { ProductLocalesService } from '@modules/client.common.angular2/locale/product-locales.service'; +import { WarehouseRouter } from '@modules/client.common.angular2/routers/warehouse-router.service'; + +const SHOW_TOP_PRODUCTS_QUANTITY: number = 10; + +@Component({ + selector: 'ea-warehouse-products-view', + styleUrls: ['./warehouse-products-view.component.scss'], + templateUrl: './warehouse-products-view.component.html', +}) +export class WarehouseProductsViewComponent + implements OnInit, OnDestroy, OnChanges { + private _ngDestroy$ = new Subject(); + + public warehouseID: string; + public topWarehouseProducts: WarehouseProduct[]; + + public productTitle: string; + + @Input() + public warehouse: Warehouse; + + constructor( + private warehouseProductsRouter: WarehouseProductsRouter, + private _productLocalesService: ProductLocalesService, + private warehouseRouter: WarehouseRouter, + private readonly _router: Router, + private toasterService: ToasterService + ) {} + + ngOnChanges() { + this._getTopWarehouseProducts(); + } + ngOnInit() { + this._getTopWarehouseProducts(); + } + + removeProduct(warehouseProduct: WarehouseProduct) { + this.warehouse.products = this.warehouse.products.filter( + (p: WarehouseProduct) => p.id !== warehouseProduct.id + ); + this.warehouseRouter.save(this.warehouse); + } + + editProduct(warehouseProduct: WarehouseProduct) { + this._router.navigate([ + '/products/list/' + warehouseProduct.product['id'] + '/edit', + ]); + } + + protected addProduct(warehouseProduct: WarehouseProduct) { + this.warehouseProductsRouter.increaseCount( + this.warehouse.id, + warehouseProduct.productId, + 1 + ); + this.productTitle = this.localeTranslate( + warehouseProduct.product['title'] + ); + + this.toasterService.pop( + 'info', + `${this.productTitle} product amound increased!` + ); + } + + protected localeTranslate(member: ILocaleMember[]) { + return this._productLocalesService.getTranslate(member); + } + + private _getTopWarehouseProducts() { + const id = this.warehouse.id; + const quantity = SHOW_TOP_PRODUCTS_QUANTITY; + this.warehouseID = id; + + this.warehouseProductsRouter + .getTopProducts(this.warehouseID, quantity) + .pipe(takeUntil(this._ngDestroy$)) + .subscribe((products) => (this.topWarehouseProducts = products)); + } + + ngOnDestroy() { + this._ngDestroy$.next(); + this._ngDestroy$.complete(); + } +} diff --git a/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/+warehouse-products-view/warehouse-products-view.module.ts b/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/+warehouse-products-view/warehouse-products-view.module.ts new file mode 100644 index 0000000..15ed07c --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/+warehouse-products-view/warehouse-products-view.module.ts @@ -0,0 +1,22 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { FormWizardModule } from '@ever-co/angular2-wizard'; +import { Ng2SmartTableModule } from 'ng2-smart-table'; +import { TranslateModule } from '@ngx-translate/core'; +import { NbSpinnerModule } from '@nebular/theme'; +import { ThemeModule } from '../../../../@theme'; +import { WarehouseProductsViewComponent } from './warehouse-products-view.component'; + +@NgModule({ + imports: [ + CommonModule, + ThemeModule, + FormWizardModule, + Ng2SmartTableModule, + TranslateModule.forChild(), + NbSpinnerModule, + ], + declarations: [WarehouseProductsViewComponent], + exports: [WarehouseProductsViewComponent], +}) +export class WarehouseProductsViewModule {} diff --git a/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/index.ts b/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/index.ts new file mode 100644 index 0000000..1e0c270 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/index.ts @@ -0,0 +1 @@ +export * from './warehouse.module'; diff --git a/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/products-table/products-table.component.html b/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/products-table/products-table.component.html new file mode 100644 index 0000000..863ac0c --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/products-table/products-table.component.html @@ -0,0 +1,35 @@ + + + + + {{ + 'WAREHOUSE_VIEW.PRODUCTS_TAB.CLICK_ON_PRODUCT_IMAGE_TO_INCREASE_AVAILABLE_QUANTITY' + | translate + }} + + + + + + diff --git a/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/products-table/products-table.component.scss b/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/products-table/products-table.component.scss new file mode 100644 index 0000000..98d7253 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/products-table/products-table.component.scss @@ -0,0 +1,5 @@ +:host ::ng-deep ng2-smart-table { + .ng2-smart-th.qty input { + text-align: center; + } +} diff --git a/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/products-table/products-table.component.ts b/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/products-table/products-table.component.ts new file mode 100644 index 0000000..9f6a0e8 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/products-table/products-table.component.ts @@ -0,0 +1,121 @@ +import { + OnDestroy, + Component, + Output, + EventEmitter, + Input, + ViewChild, +} from '@angular/core'; +import { Subject, Subscription } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; +import WarehouseProduct from '@modules/server.common/entities/WarehouseProduct'; +import { WarehouseProductsRouter } from '@modules/client.common.angular2/routers/warehouse-products-router.service'; +import { ProductLocalesService } from '@modules/client.common.angular2/locale/product-locales.service'; +import { ILocaleMember } from '@modules/server.common/interfaces/ILocale'; +import Warehouse from '@modules/server.common/entities/Warehouse'; +import { WarehouseRouter } from '@modules/client.common.angular2/routers/warehouse-router.service'; +import { Router } from '@angular/router'; +import { NotifyService } from '../../../../@core/services/notify/notify.service'; +import { ConfirmationModalComponent } from '../../../../@shared/confirmation-modal/confirmation-modal.component'; +import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; +import { + WarehouseProductViewModel, + WarehouseProductsComponent, +} from '@app/@shared/warehouse-product/forms/warehouse-products-table'; + +@Component({ + selector: 'ea-store-products-table', + templateUrl: './products-table.component.html', + styleUrls: ['./products-table.component.scss'], +}) +export class ProductsTableComponent implements OnDestroy { + @ViewChild('productsTable', { static: true }) + productsTable: WarehouseProductsComponent; + + @Output() + addProducts: EventEmitter = new EventEmitter(); + + @Input() + warehouse: Warehouse; + + loading: boolean; + + private ngDestroy$ = new Subject(); + private warehouseProducts$: Subscription; + + constructor( + private readonly _warehouseProductsRouter: WarehouseProductsRouter, + private readonly _productLocalesService: ProductLocalesService, + private readonly warehouseRouter: WarehouseRouter, + private readonly _router: Router, + private readonly _notifyService: NotifyService, + private readonly modalService: NgbModal + ) {} + + editProduct(ev) { + this._router.navigate(['/products/list/' + ev.data.id + '/edit']); + } + + removeProduct(ev) { + const activeModal = this.modalService.open(ConfirmationModalComponent, { + size: 'sm', + container: 'nb-layout', + backdrop: 'static', + }); + const modalComponent: ConfirmationModalComponent = + activeModal.componentInstance; + + modalComponent.confirmEvent + .pipe(takeUntil(modalComponent.ngDestroy$)) + .subscribe((dataEvent) => { + this.removeProducts([ev.data]); + modalComponent.cancel(); + }); + } + + deleteSelectedRows() { + this.removeProducts(this.productsTable.selectedProducts); + } + + localeTranslate(member: ILocaleMember[]) { + return this._productLocalesService.getTranslate(member); + } + + async loadDataSmartTable(storeId) { + if (this.warehouseProducts$) { + this.warehouseProducts$.unsubscribe(); + this.productsTable.selectedProducts = []; + } + + this.warehouseProducts$ = this._warehouseProductsRouter + .get(storeId) + .pipe(takeUntil(this.ngDestroy$)) + .subscribe((p: WarehouseProduct[]) => { + this.productsTable.loadDataSmartTable(p, storeId); + }); + } + + removeProducts(warehouseProducts: WarehouseProductViewModel[]) { + if (this.warehouse) { + this.loading = true; + this.warehouse.products = this.warehouse.products.filter( + (p: WarehouseProduct) => + !warehouseProducts + .map((product) => product.id) + .includes(p.productId) + ); + this.warehouseRouter.save(this.warehouse); + this.loading = false; + const message = 'Selected products are deleted!'; + this._notifyService.success(message); + } else { + const message = `Can't remove products`; + this.loading = false; + this._notifyService.error(message); + } + } + + ngOnDestroy(): void { + console.warn('ProductsTableComponent destroyed'); + } +} diff --git a/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/products-table/products-table.module.ts b/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/products-table/products-table.module.ts new file mode 100644 index 0000000..b0f8ab3 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/products-table/products-table.module.ts @@ -0,0 +1,27 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { ThemeModule } from '@app/@theme'; +import { TranslateModule } from '@app/@shared/translate/translate.module'; +import { ProductsTableComponent } from './products-table.component'; +import { ToasterModule } from 'angular2-toaster'; +import { NbSpinnerModule, NbButtonModule } from '@nebular/theme'; +import { ConfirmationModalModule } from '../../../../@shared/confirmation-modal/confirmation-modal.module'; +import { WarehouseProductFormsModule } from '@app/@shared/warehouse-product/forms'; + +const COMPONENTS = [ProductsTableComponent]; + +@NgModule({ + imports: [ + CommonModule, + ThemeModule, + ToasterModule.forChild(), + TranslateModule, + NbSpinnerModule, + ConfirmationModalModule, + WarehouseProductFormsModule, + NbButtonModule, + ], + declarations: [...COMPONENTS], + exports: [...COMPONENTS], +}) +export class ProductsTableModule {} diff --git a/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/warehouse-info/warehouse-info.component.html b/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/warehouse-info/warehouse-info.component.html new file mode 100644 index 0000000..ab9f4e3 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/warehouse-info/warehouse-info.component.html @@ -0,0 +1,32 @@ + + +
+
+
+
+
diff --git a/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/warehouse-info/warehouse-info.component.scss b/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/warehouse-info/warehouse-info.component.scss new file mode 100644 index 0000000..f0da69b --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/warehouse-info/warehouse-info.component.scss @@ -0,0 +1,23 @@ +.iconInfo { +} +.header { + padding: 0px; + margin: 0px; +} +.body { + padding: 0px; + margin: 0px; +} +.product-title { + color: #009100; +} +.title { + padding: 0px; + margin: 0px; + text-align: center; +} + +.warehouse-redirect { + cursor: pointer; + color: #009100; +} diff --git a/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/warehouse-info/warehouse-info.component.ts b/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/warehouse-info/warehouse-info.component.ts new file mode 100644 index 0000000..04b8a94 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/warehouse-info/warehouse-info.component.ts @@ -0,0 +1,28 @@ +import { Component, OnInit } from '@angular/core'; +import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; +import Warehouse from '@modules/server.common/entities/Warehouse'; +import { Router } from '@angular/router'; + +@Component({ + templateUrl: 'warehouse-info.component.html', + styleUrls: ['warehouse-info.component.scss'], +}) +export class WarehouseTableInfoComponent implements OnInit { + public warehouse: Warehouse; + + constructor( + private readonly activeModal: NgbActiveModal, + private router: Router + ) {} + + cancel() { + this.activeModal.dismiss('canceled'); + } + + ngOnInit(): void {} + + redirectToWarehousePage() { + this.router.navigate([`/stores/${this.warehouse.id}`]); + this.activeModal.dismiss('canceled'); + } +} diff --git a/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/warehouse-main-info/warehouse-main-info.component.html b/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/warehouse-main-info/warehouse-main-info.component.html new file mode 100644 index 0000000..425f848 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/warehouse-main-info/warehouse-main-info.component.html @@ -0,0 +1,41 @@ + + +
+ +
+ +

{{ selectedWarehouse?.name }}

+
+
+
+ +
+ +
+ + {{ + 'WAREHOUSE_VIEW.PRODUCTS_MANUFACTURING' | translate + }} + +
+
+
+
+ +
+ +
+ {{ + 'WAREHOUSE_VIEW.CARRIER_REQUIRED' | translate + }} +
+
+
+
+ + +

+ {{ 'WAREHOUSE_VIEW.MANAGE_STORE_PRODUCTS_&_ORDERS' | translate }} +

+
+
diff --git a/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/warehouse-main-info/warehouse-main-info.component.scss b/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/warehouse-main-info/warehouse-main-info.component.scss new file mode 100644 index 0000000..1dcdbc6 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/warehouse-main-info/warehouse-main-info.component.scss @@ -0,0 +1,89 @@ +@import '../../../../@theme/styles/themes'; +@import '~bootstrap/scss/mixins/breakpoints'; +@import '~@nebular/theme/styles/global/breakpoints'; +$brand: #2a2c39; +$brand-lighted: #353748; +$brand-darken: darken($brand, 5%); + +.example-container:not(:last-child) { + margin-bottom: 1.5rem; +} + +.list-group-item:hover { + cursor: pointer; +} + +nb-card { + transform: translate3d(0, 0, 0); +} + +.warehouseImg { + padding-right: 0; + + img { + width: 100%; + max-height: 130px; + } +} + +:host ::ng-deep .productsContainer a { + border: none; + background-color: rgba(182, 176, 176, 0.05) !important; + color: #111111 !important; + + .warehouse-product-image { + text-align: center; + span { + text-align: left !important; + padding: 0 !important; + } + span.badge { + padding: 5px !important; + } + } +} + +:host ::ng-deep ng2-smart-table { + .tableImg { + height: 100px; + width: 100px; + } +} + +.nb-theme-cosmic .whiteDiv { + color: white !important; +} + +.product-info { + padding-top: 10px; +} + +nb-card { + transform: translate3d(0, 0, 0); +} + +button:disabled { + cursor: not-allowed; + opacity: 0.5; +} + +nb-card-footer { + border: none; +} + +nb-card-header { + .store-info { + margin-left: 0; + margin-bottom: 5px; + } + + .store-info-img { + padding: 3px; + font-size: 18px; + color: black; + } +} + +.store-details { + padding-bottom: 0; +} diff --git a/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/warehouse-main-info/warehouse-main-info.component.ts b/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/warehouse-main-info/warehouse-main-info.component.ts new file mode 100644 index 0000000..d8b6b95 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/warehouse-main-info/warehouse-main-info.component.ts @@ -0,0 +1,34 @@ +import { Component, OnInit, OnDestroy, Input, OnChanges } from '@angular/core'; +import { Subject } from 'rxjs'; +import Warehouse from '@modules/server.common/entities/Warehouse'; + +@Component({ + selector: 'ea-warehouse-main-info', + styleUrls: ['./warehouse-main-info.component.scss'], + templateUrl: './warehouse-main-info.component.html', +}) +export class WarehouseMainInfoViewComponent + implements OnInit, OnDestroy, OnChanges { + private _ngDestroy$ = new Subject(); + + public isManufacturing: boolean; + public isCarrierRequired: boolean; + + @Input() + public selectedWarehouse: Warehouse; + + constructor() {} + + ngOnChanges() { + if (this.selectedWarehouse) { + this.isManufacturing = this.selectedWarehouse.isManufacturing; + this.isCarrierRequired = this.selectedWarehouse.isCarrierRequired; + } + } + ngOnInit() {} + + ngOnDestroy() { + this._ngDestroy$.next(); + this._ngDestroy$.complete(); + } +} diff --git a/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/warehouse-main-info/warehouse-main-info.module.ts b/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/warehouse-main-info/warehouse-main-info.module.ts new file mode 100644 index 0000000..7918405 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/warehouse-main-info/warehouse-main-info.module.ts @@ -0,0 +1,22 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { FormWizardModule } from '@ever-co/angular2-wizard'; +import { Ng2SmartTableModule } from 'ng2-smart-table'; +import { TranslateModule } from '@ngx-translate/core'; +import { NbSpinnerModule } from '@nebular/theme'; +import { ThemeModule } from '../../../../@theme'; +import { WarehouseMainInfoViewComponent } from './warehouse-main-info.component'; + +@NgModule({ + imports: [ + CommonModule, + ThemeModule, + FormWizardModule, + Ng2SmartTableModule, + TranslateModule.forChild(), + NbSpinnerModule, + ], + declarations: [WarehouseMainInfoViewComponent], + exports: [WarehouseMainInfoViewComponent], +}) +export class WarehouseMainInfoViewModule {} diff --git a/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/warehouse-order-view/order-header-info/order-header-info.component.html b/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/warehouse-order-view/order-header-info/order-header-info.component.html new file mode 100644 index 0000000..75142a6 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/warehouse-order-view/order-header-info/order-header-info.component.html @@ -0,0 +1,20 @@ +
+ {{ 'WAREHOUSE_VIEW.ORDER.ORDER' | translate }} #{{ + selectedOrder.orderNumber + }} + {{ + getStatus(selectedOrder.warehouseStatusText) + }} + {{ + getStatus(selectedOrder.carrierStatusText) + }} +
+ +
+ {{ selectedOrder.createdAt | date: 'dd-MM-yyyy' }} +
+ {{ timers[selectedOrder.products[0].id] }} +
+
diff --git a/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/warehouse-order-view/order-header-info/order-header-info.component.scss b/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/warehouse-order-view/order-header-info/order-header-info.component.scss new file mode 100644 index 0000000..a83c659 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/warehouse-order-view/order-header-info/order-header-info.component.scss @@ -0,0 +1,6 @@ +@import '../../../../../@theme/styles/themes'; +@import '~bootstrap/scss/mixins/breakpoints'; +@import '~@nebular/theme/styles/global/breakpoints'; +$brand: #2a2c39; +$brand-lighted: #353748; +$brand-darken: darken($brand, 5%); diff --git a/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/warehouse-order-view/order-header-info/order-header-info.component.ts b/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/warehouse-order-view/order-header-info/order-header-info.component.ts new file mode 100644 index 0000000..2f86fdc --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/warehouse-order-view/order-header-info/order-header-info.component.ts @@ -0,0 +1,53 @@ +import { + Component, + ViewChild, + OnInit, + OnDestroy, + EventEmitter, + Input, + OnChanges, +} from '@angular/core'; + +import { Subject } from 'rxjs'; +import Order from '@modules/server.common/entities/Order'; +import { TranslateService } from '@ngx-translate/core'; + +@Component({ + selector: 'ea-order-header-info', + styleUrls: ['./order-header-info.component.scss'], + templateUrl: './order-header-info.component.html', +}) +export class OrderHeaderInfoComponent implements OnInit, OnDestroy, OnChanges { + private _ngDestroy$ = new Subject(); + + public timers: string[] = []; + + @Input() + selectedOrder: Order; + + constructor(private _translateService: TranslateService) {} + + ngOnChanges() {} + ngOnInit() {} + + getStatus(status) { + const columnTitlePrefix = 'STATUS_TEXT.'; + const forTranslate = columnTitlePrefix + status; + return this._translate(forTranslate); + } + + private _translate(key: string): string { + let translationResult = ''; + + this._translateService.get(key).subscribe((res) => { + translationResult = res; + }); + + return translationResult; + } + + ngOnDestroy() { + this._ngDestroy$.next(); + this._ngDestroy$.complete(); + } +} diff --git a/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/warehouse-order-view/order-header-info/order-header-info.module.ts b/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/warehouse-order-view/order-header-info/order-header-info.module.ts new file mode 100644 index 0000000..ffdcf19 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/warehouse-order-view/order-header-info/order-header-info.module.ts @@ -0,0 +1,22 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { FormWizardModule } from '@ever-co/angular2-wizard'; +import { Ng2SmartTableModule } from 'ng2-smart-table'; +import { TranslateModule } from '@ngx-translate/core'; +import { NbSpinnerModule } from '@nebular/theme'; +import { OrderHeaderInfoComponent } from './order-header-info.component'; +import { ThemeModule } from '../../../../../@theme'; + +@NgModule({ + imports: [ + CommonModule, + ThemeModule, + FormWizardModule, + Ng2SmartTableModule, + TranslateModule.forChild(), + NbSpinnerModule, + ], + declarations: [OrderHeaderInfoComponent], + exports: [OrderHeaderInfoComponent], +}) +export class OrderHeaderInfoModule {} diff --git a/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/warehouse-order-view/warehouse-order-view.component.html b/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/warehouse-order-view/warehouse-order-view.component.html new file mode 100644 index 0000000..456fba0 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/warehouse-order-view/warehouse-order-view.component.html @@ -0,0 +1,189 @@ + + + + + + +
+
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+
+ +
+
+ +

+ {{ + 'WAREHOUSE_VIEW.ORDER.CANT_PROCESSING_WITHOUT_PRODUCTS' + | translate + }} +

+ +
+ +
+
+
diff --git a/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/warehouse-order-view/warehouse-order-view.component.scss b/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/warehouse-order-view/warehouse-order-view.component.scss new file mode 100644 index 0000000..3cd05a1 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/warehouse-order-view/warehouse-order-view.component.scss @@ -0,0 +1,290 @@ +@import '../../../../@theme/styles/themes'; +@import '~bootstrap/scss/mixins/breakpoints'; +@import '~@nebular/theme/styles/global/breakpoints'; +$brand: #2a2c39; +$brand-lighted: #353748; +$brand-darken: darken($brand, 5%); +.btnManage { + padding-top: 1.25rem; + padding-bottom: 1.25rem; +} + +.btn { + cursor: pointer; + font-weight: 500 !important; + padding: 0.625rem 1.125rem !important; +} + +.btn:hover { + cursor: pointer; +} + +.btn-startProcessing { + color: #333; + background-color: #fff; + border-color: #ccc; + margin-bottom: 3px; +} + +.order-btn-margin-bottom { + margin-bottom: 3px; +} + +.btn-startProcessing:hover { + color: rgb(24, 22, 22); + background-color: rgb(241, 239, 239); + border-color: rgb(182, 176, 176); + cursor: pointer; +} + +.toggle-types { + .btn-toggle-radio-group { + margin-bottom: 1rem; + } +} + +.subheader { + margin-bottom: 0.75rem; + font-size: 0.875rem; + // TODO style + // font-weight: nb-theme(font-weight-bolder); + // color: #2a2a2a; +} + +.example-container:not(:last-child) { + margin-bottom: 1.5rem; +} + +.warehouse-product-image { + max-width: 100%; + max-height: 300px; +} + +.list-group-item:hover { + cursor: pointer; +} + +nb-card { + transform: translate3d(0, 0, 0); +} + +.btn-danger { + color: #fff; + background-color: #d9534f; + border-color: #d43f3a; +} + +.btnOrder { + padding: 3px; + padding-left: 0px !important; +} + +.warehouseImg { + padding-right: 0; + + img { + width: 100%; + max-height: 130px; + } +} + +:host ::ng-deep .productsContainer a { + border: none; + background-color: rgba(182, 176, 176, 0.05) !important; + color: #111111 !important; + + .warehouse-product-image { + text-align: center; + span { + text-align: left !important; + padding: 0 !important; + } + span.badge { + padding: 5px !important; + } + } +} + +:host ::ng-deep ng2-smart-table { + .tableImg { + height: 100px; + width: 100px; + } +} + +.nb-theme-cosmic .whiteDiv { + color: white !important; +} + +.product-info { + padding-top: 10px; +} + +nb-card { + transform: translate3d(0, 0, 0); +} + +button:disabled { + cursor: not-allowed; + opacity: 0.5; +} + +nb-card-footer { + border: none; +} + +nb-card-header { + .store-info { + margin-left: 0; + margin-bottom: 5px; + } + + .store-info-img { + padding: 3px; + font-size: 18px; + color: rgb(161, 161, 161); + } +} + +.page-header { + padding: 1.25rem !important; +} + +.products { + list-style-type: none; + height: inherit !important; + max-width: 80%; + margin: 0 auto; + .product-card-container { + border: 0; + padding-bottom: 0; + margin-bottom: 1.25rem; + .product-card { + margin: 0; + padding: 0; + text-align: center; + .product-image-container { + cursor: pointer; + } + .product-image-container:active { + background: #000; + .product-image { + opacity: 0.8; + } + } + .product-buttons { + position: absolute; + top: 20px; + right: 20px; + .add-button { + padding: 0; + font-size: 30px; + color: #fff; + } + } + .product-image { + width: 100%; + } + .product-actions-info-bar { + background-color: $brand-darken; + color: white; + // text-align: center; + margin-bottom: 0; + padding-top: 5px; + padding-bottom: 5px; + margin-top: -6px; + .actions { + a { + margin-top: -2px; + border-radius: 3px; + color: $brand-lighted; + float: right; + border-color: #ccc; + color: white; + margin-right: 10px; + padding: 4px; + font-size: 18px; + line-height: 0; + } + } + .info { + span { + margin-top: -2px; + border-radius: 3px; + color: $brand-lighted; + float: left; + border-color: #ccc; + color: rgb(161, 161, 161); + margin-left: 10px; + padding: 4px; + font-size: 18px; + line-height: 0; + } + } + } + .product-mini-bar { + font-size: 17px; + background-color: $brand-lighted; + color: white; + text-align: center; + margin-bottom: 0; + padding-top: 11px; + padding-bottom: 11px; + margin-top: -6px; + .count { + background: #fff; + margin-top: -2px; + padding: 2px; + border-radius: 3px; + color: $brand-lighted; + float: right; + margin-right: 10px; + } + } + } + + .name { + width: 75%; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + display: inline-block; + } + } +} + +.progress-bar-danger { + background: #d9534f !important; + color: white !important; +} + +.progress-bar-warning { + background: #f0ad4e !important; + color: white !important; +} + +// added for the Cosmic theme + +.list-group-item { + background-color: transparent; +} + +.store-details { + padding-bottom: 0; +} + +.order-view { + padding: 0; + padding-top: 1.25rem; + .actions { + padding-bottom: 1.25rem; + padding-left: 1.25rem; + border-bottom: 1px solid #ebeef2; + width: 100%; + } + + .slider { + text-align: center; + background: #a4abb3; + } +} diff --git a/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/warehouse-order-view/warehouse-order-view.component.ts b/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/warehouse-order-view/warehouse-order-view.component.ts new file mode 100644 index 0000000..9c047cf --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/warehouse-order-view/warehouse-order-view.component.ts @@ -0,0 +1,129 @@ +import { + Component, + ViewChild, + OnInit, + OnDestroy, + EventEmitter, + Input, + OnChanges, +} from '@angular/core'; + +import { Subject, Observable } from 'rxjs'; +import Order from '@modules/server.common/entities/Order'; +import OrderWarehouseStatus from '@modules/server.common/enums/OrderWarehouseStatus'; +import { OrderRouter } from '@modules/client.common.angular2/routers/order-router.service'; +import { ILocaleMember } from '@modules/server.common/interfaces/ILocale'; +import { ProductLocalesService } from '@modules/client.common.angular2/locale/product-locales.service'; +import { environment } from 'environments/environment'; +import { TranslateService } from '@ngx-translate/core'; +import { takeUntil } from 'rxjs/operators'; +import DeliveryType from '@modules/server.common/enums/DeliveryType'; + +@Component({ + selector: 'ea-warehouse-order-view', + styleUrls: ['./warehouse-order-view.component.scss'], + templateUrl: './warehouse-order-view.component.html', +}) +export class WarehouseOrderViewComponent + implements OnInit, OnDestroy, OnChanges { + @Input() + selectedOrder: Order; + @Input() + hideHeader: boolean; + @ViewChild('slideshow', { static: true }) + slideshow: any; + + isSelectedOrderActionsAvailable: boolean = true; + loading: boolean; + QTY: string; + slideImages: any; + orderTypeDelivery: DeliveryType = DeliveryType.Delivery; + orderTypeTakeaway: DeliveryType = DeliveryType.Takeaway; + + private _ngDestroy$ = new Subject(); + + constructor( + private orderRouter: OrderRouter, + private _productLocalesService: ProductLocalesService, + private readonly translateService: TranslateService + ) { + this._applyTranslationOnSmartTable(); + } + + ngOnChanges() { + this.selectedOrder.products.map((p) => + p.product.images.sort((i1, i2) => i2.orientation - i1.orientation) + ); + + if (this.slideshow && this.selectedOrder.products.length === 1) { + this.slideshow.goToSlide(0); + } + this.getSlideImage(); + } + + ngOnInit() {} + private _applyTranslationOnSmartTable() { + this.translateService.onLangChange.subscribe(() => { + this.takeSlidebarTranslates(); + }); + } + + takeSlidebarTranslates() { + const columnTitlePrefix = 'ORDER_VIEW.ORDER_SIDEBAR.'; + const getTranslatedWord = (name: string): Observable => + this.translateService.get(columnTitlePrefix + name); + getTranslatedWord('QTY') + .pipe(takeUntil(this._ngDestroy$)) + .subscribe((tr) => { + this.QTY = tr; + }); + } + + getSlideImage() { + const images = this.selectedOrder.products.map((p) => { + const productTitle = this._productLocalesService.getTranslate( + p.product.title + ); + const productCount = p.count; + const productPrice = p.price; + + this.takeSlidebarTranslates(); + return { + url: this._productLocalesService.getTranslate(p.product.images), + caption: `${productTitle} (${ + environment.CURRENCY_SYMBOL + productPrice + }, ${this.QTY}: ${productCount})`, + title: this._productLocalesService.getTranslate( + p.product.description + ), + style: { + height: '100%', + }, + }; + }); + + this.slideImages = images; + } + + async updateOrderWarehouseStatus(status: OrderWarehouseStatus) { + this.isSelectedOrderActionsAvailable = false; + this.loading = true; + await this.orderRouter.updateWarehouseStatus( + this.selectedOrder.id, + status + ); + this.selectedOrder.warehouseStatus = status; + + this.isSelectedOrderActionsAvailable = true; + this.loading = false; + } + + protected localeTranslate(member: ILocaleMember[]) { + return this._productLocalesService.getTranslate(member); + } + + ngOnDestroy() { + this._ngDestroy$.next(); + this._ngDestroy$.complete(); + } +} diff --git a/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/warehouse-order-view/warehouse-order-view.module.ts b/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/warehouse-order-view/warehouse-order-view.module.ts new file mode 100644 index 0000000..25a5117 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/warehouse-order-view/warehouse-order-view.module.ts @@ -0,0 +1,26 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { FormWizardModule } from '@ever-co/angular2-wizard'; +import { Ng2SmartTableModule } from 'ng2-smart-table'; +import { TranslateModule } from '@ngx-translate/core'; +import { ThemeModule } from '../../../../@theme'; +import { NbSpinnerModule } from '@nebular/theme'; +import { WarehouseOrderViewComponent } from './warehouse-order-view.component'; +import { OrderHeaderInfoModule } from './order-header-info/order-header-info.module'; +import { ImageSliderModule } from '../../../../@shared/render-component/image-slider/image-slider.module'; + +@NgModule({ + imports: [ + CommonModule, + ThemeModule, + FormWizardModule, + Ng2SmartTableModule, + TranslateModule.forChild(), + NbSpinnerModule, + OrderHeaderInfoModule, + ImageSliderModule, + ], + declarations: [WarehouseOrderViewComponent], + exports: [WarehouseOrderViewComponent], +}) +export class WarehouseOrderViewModule {} diff --git a/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/warehouse-orders-table/warehouse-orders-table.component.html b/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/warehouse-orders-table/warehouse-orders-table.component.html new file mode 100644 index 0000000..4514d34 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/warehouse-orders-table/warehouse-orders-table.component.html @@ -0,0 +1,100 @@ +
Orders table view
+ + + +
+
+ +
+
+
+ + +
+
+
+ + + + + + + + + +
+
+
+
+ + + + + +
diff --git a/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/warehouse-orders-table/warehouse-orders-table.component.scss b/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/warehouse-orders-table/warehouse-orders-table.component.scss new file mode 100644 index 0000000..efdcb40 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/warehouse-orders-table/warehouse-orders-table.component.scss @@ -0,0 +1,144 @@ +@import '../../../../@theme/styles/themes'; +@import '~bootstrap/scss/mixins/breakpoints'; +@import '~@nebular/theme/styles/global/breakpoints'; +$brand: #2a2c39; +$brand-lighted: #353748; +$brand-darken: darken($brand, 5%); + +nb-card { + transform: translate3d(0, 0, 0); +} + +.example-container:not(:last-child) { + margin-bottom: 1.5rem; +} + +:host ::ng-deep .productsContainer a { + border: none; + background-color: rgba(182, 176, 176, 0.05) !important; + color: #111111 !important; + + .warehouse-product-image { + text-align: center; + span { + text-align: left !important; + padding: 0 !important; + } + span.badge { + padding: 5px !important; + } + } +} + +:host ::ng-deep ng2-smart-table { + .tableImg { + height: 100px; + width: 100px; + } +} + +.nb-theme-cosmic .whiteDiv { + color: white !important; +} + +.products { + list-style-type: none; + height: inherit !important; + max-width: 80%; + margin: 0 auto; + .product-card-container { + border: 0; + padding-bottom: 0; + margin-bottom: 1.25rem; + .product-card { + margin: 0; + padding: 0; + text-align: center; + .product-image-container { + cursor: pointer; + } + .product-image-container:active { + background: #000; + .product-image { + opacity: 0.8; + } + } + .product-buttons { + position: absolute; + top: 20px; + right: 20px; + .add-button { + padding: 0; + font-size: 30px; + color: #fff; + } + } + .product-image { + width: 100%; + } + .product-actions-info-bar { + background-color: $brand-darken; + color: white; + margin-bottom: 0; + padding-top: 5px; + padding-bottom: 5px; + margin-top: -6px; + .actions { + a { + margin-top: -2px; + border-radius: 3px; + color: $brand-lighted; + float: right; + border-color: #ccc; + color: white; + margin-right: 10px; + padding: 4px; + font-size: 18px; + line-height: 0; + } + } + .info { + span { + margin-top: -2px; + border-radius: 3px; + color: $brand-lighted; + float: left; + border-color: #ccc; + color: rgb(161, 161, 161); + margin-left: 10px; + padding: 4px; + font-size: 18px; + line-height: 0; + } + } + } + .product-mini-bar { + font-size: 17px; + background-color: $brand-lighted; + color: white; + text-align: center; + margin-bottom: 0; + padding-top: 11px; + padding-bottom: 11px; + margin-top: -6px; + .count { + background: #fff; + margin-top: -2px; + padding: 2px; + border-radius: 3px; + color: $brand-lighted; + float: right; + margin-right: 10px; + } + } + } + + .name { + width: 75%; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + display: inline-block; + } + } +} diff --git a/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/warehouse-orders-table/warehouse-orders-table.component.ts b/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/warehouse-orders-table/warehouse-orders-table.component.ts new file mode 100644 index 0000000..eb157fa --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/warehouse-orders-table/warehouse-orders-table.component.ts @@ -0,0 +1,256 @@ +import { + Component, + OnInit, + OnDestroy, + OnChanges, + AfterViewInit, +} from '@angular/core'; +import { LocalDataSource } from 'ng2-smart-table'; +import { Subject, Observable, forkJoin } from 'rxjs'; +import { TranslateService } from '@ngx-translate/core'; +import { NgbModalRef, NgbModal } from '@ng-bootstrap/ng-bootstrap'; +import { ILocaleMember } from '@modules/server.common/interfaces/ILocale'; +import { ProductLocalesService } from '@modules/client.common.angular2/locale/product-locales.service'; +import Order from '@modules/server.common/entities/Order'; +import { + ordersFilter, + OrdersFilterModes, +} from '../../../ordersFilter/ordersFilter'; +import { WarehouseOrdersRouter } from '@modules/client.common.angular2/routers/warehouse-orders-router.service'; +import { RedirectOrderComponent } from '../../../../@shared/render-component/customer-orders-table/redirect-order.component'; +import { ElapsedComponent } from '../../../../@shared/render-component/warehouse-table/elapsed/elapsed.component'; +import { DatePipe } from '@angular/common'; +import { WarehouseOrderComponent } from '../+warehouse-order/warehouse-order.component'; +import { takeUntil } from 'rxjs/operators'; + +@Component({ + selector: 'ea-warehouse-orders-table', + styleUrls: ['./warehouse-orders-table.component.scss'], + templateUrl: './warehouse-orders-table.component.html', +}) +export class WarehouseOrdersTableComponent + implements OnInit, OnDestroy, OnChanges, AfterViewInit { + private _ngDestroy$ = new Subject(); + + public orders: Order[] = []; + public allOrders: Order[] = []; + public selectedOrder: Order; + + public filterMode: OrdersFilterModes = 'ready'; + public warehouseID: string; + + public settingsSmartTable: object; + public sourceSmartTable: LocalDataSource = new LocalDataSource(); + + constructor( + private _translateService: TranslateService, + private readonly modalService: NgbModal, + private _productLocalesService: ProductLocalesService, + private warehouseOrdersRouter: WarehouseOrdersRouter + ) { + this._loadTableSettings(); + this._listenForEntityLocaleTranslate(); + } + + ngOnChanges() {} + ngOnInit() {} + + openWarehouseOrderCreateModal() { + const modalRef: NgbModalRef = this.modalService.open( + WarehouseOrderComponent, + { + size: 'lg', + container: 'nb-layout', + windowClass: 'ng-custom', + backdrop: 'static', + } + ); + + (modalRef.componentInstance as WarehouseOrderComponent).orderFinishedEmitter + .pipe(takeUntil(this._ngDestroy$)) + .subscribe(() => { + modalRef.close(); + }); + } + + ngAfterViewInit() { + this.loadSmartTableTranslates(); + } + + switchLanguage(language: string) { + this._translateService.use(language); + } + + filterOrders(mode) { + this.selectedOrder = null; + this._getWarehouseOrders(this.warehouseID); + this.filterMode = mode; + } + + getOrders() { + this.orders = ordersFilter(this.allOrders, this.filterMode); + return this.orders; + } + + loadSmartTableTranslates() { + this._translateService.onLangChange.subscribe((d) => { + this._loadTableSettings(); + }); + } + + public selectOrder(warehouseOrderProducts) { + this.selectedOrder = + this.selectedOrder === warehouseOrderProducts.data + ? null + : warehouseOrderProducts.data; + } + + private _setupDataForSmartTable(orders: Order[]) { + const data = []; + + orders.forEach((order) => { + const raw: Date = new Date(order._createdAt); + const formatted: string = new DatePipe('en-EN').transform( + raw, + 'dd-MMM-yyyy hh:mm:ss' + ); + + const columnTitlePrefix = 'STATUS_TEXT.'; + const getTranslate = (name: string): Observable => + this._translateService.get(columnTitlePrefix + name); + + forkJoin( + this._translateService.get('Id'), + getTranslate(order.warehouseStatusText), + getTranslate(order.carrierStatusText), + getTranslate('CARRIER'), + getTranslate('CREATED'), + getTranslate('ELAPSED') + ) + .pipe(takeUntil(this._ngDestroy$)) + .subscribe( + ([ + id, + warehouseStatusText, + carrierStatusText, + carrier, + created, + elapsed, + ]) => { + data.push({ + id: order.id, + products: order.products, + orderNumber: order.orderNumber, + warehouseStatusText: order.warehouseStatusText, + carrierStatusText: order.carrierStatusText, + createdAt: order.createdAt, + warehouseStatus: order.warehouseStatus, + carrier: order.carrier, + carrierStatus: order.carrierStatus, + product: order.products.length + ? ` +
+ +
+ ${this.localeTranslate(order.products[0].product.title)} + ${order.products[0].count} +
+
+ ` + : '', + status: warehouseStatusText, + carrierStatusHtml: `
${carrierStatusText}
`, + created: formatted, + }); + } + ); + }); + return data; + } + + protected localeTranslate(member: ILocaleMember[]) { + return this._productLocalesService.getTranslate(member); + } + + private _listenForEntityLocaleTranslate() { + this._translateService.onLangChange.subscribe(() => { + const data = this._setupDataForSmartTable(this.getOrders()); + this.sourceSmartTable.load(data); + }); + } + + private _getWarehouseOrders(id: string) { + this.warehouseOrdersRouter + .get(id) + .pipe(takeUntil(this._ngDestroy$)) + .subscribe((orders) => { + this.allOrders = orders; + const data = this._setupDataForSmartTable(this.getOrders()); + this.sourceSmartTable.load(data); + }); + } + + private _loadTableSettings() { + const columnTitlePrefix = 'WAREHOUSE_VIEW.'; + const getTranslate = (name: string): Observable => + this._translateService.get(columnTitlePrefix + name); + + forkJoin( + this._translateService.get('Id'), + getTranslate('ORDER_NUMBER'), + getTranslate('PRODUCTS'), + getTranslate('STATUS'), + getTranslate('CARRIER'), + getTranslate('CREATED'), + getTranslate('ELAPSED') + ) + .pipe(takeUntil(this._ngDestroy$)) + .subscribe( + ([ + id, + orderNumber, + product, + status, + carrier, + created, + elapsed, + ]) => { + this.settingsSmartTable = { + actions: false, + columns: { + orderNumber: { + title: orderNumber, + type: 'custom', + renderComponent: RedirectOrderComponent, + }, + product: { title: product, type: 'html' }, + status: { + title: status, + type: 'string', + width: '30px', + }, + carrierStatusHtml: { title: carrier, type: 'html' }, + created: { title: created, type: 'string' }, + elapsed: { + title: elapsed, + filter: false, + type: 'custom', + renderComponent: ElapsedComponent, + }, + }, + pager: { + display: true, + perPage: 3, + }, + }; + } + ); + } + + ngOnDestroy() { + this._ngDestroy$.next(); + this._ngDestroy$.complete(); + } +} diff --git a/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/warehouse-orders-table/warehouse-orders-table.module.ts b/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/warehouse-orders-table/warehouse-orders-table.module.ts new file mode 100644 index 0000000..27ee181 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/warehouse-orders-table/warehouse-orders-table.module.ts @@ -0,0 +1,23 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { FormWizardModule } from '@ever-co/angular2-wizard'; +import { Ng2SmartTableModule } from 'ng2-smart-table'; +import { TranslateModule } from '@ngx-translate/core'; +import { NbSpinnerModule, NbButtonModule } from '@nebular/theme'; +import { ThemeModule } from '../../../../@theme'; +import { WarehouseOrdersTableComponent } from './warehouse-orders-table.component'; + +@NgModule({ + imports: [ + CommonModule, + ThemeModule, + FormWizardModule, + Ng2SmartTableModule, + TranslateModule.forChild(), + NbSpinnerModule, + NbButtonModule, + ], + declarations: [WarehouseOrdersTableComponent], + exports: [WarehouseOrdersTableComponent], +}) +export class WarehouseOrdersTableModule {} diff --git a/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/warehouse-select-view/warehouse-select-view.component.html b/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/warehouse-select-view/warehouse-select-view.component.html new file mode 100644 index 0000000..354873c --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/warehouse-select-view/warehouse-select-view.component.html @@ -0,0 +1,134 @@ + + + {{ 'WAREHOUSE_VIEW.WAREHOUSE' | translate }} + + + +
+
+ + + + {{ item.name }} + + +

+ + {{ item.name }} +

+
+
+ +
+ + + +
+
+
+ +
+
+
{{ 'WAREHOUSE_VIEW.CONTACT_DETAILS' | translate }}
+
    +
  • + {{ 'WAREHOUSE_VIEW.PHONE' | translate }}: + {{ warehouse?.contactPhone }} +
  • +
  • + {{ 'WAREHOUSE_VIEW.EMAIL' | translate }}: + {{ warehouse?.contactEmail }} +
  • +
  • + {{ + 'WAREHOUSE_VIEW.ORDERS_FORWARDING_WITH' | translate + }} + + {{ 'WAREHOUSE_VIEW.PHONE' | translate }} + + + {{ 'WAREHOUSE_VIEW.EMAIL' | translate }} + +
  • +
+
+ +
+
+ {{ 'WAREHOUSE_VIEW.ORDERS_FORWARDING_DETAILS' | translate }} +
+
    +
  • + {{ 'WAREHOUSE_VIEW.PHONE' | translate }}: + {{ warehouse?.ordersPhone }} +
  • +
  • + {{ 'WAREHOUSE_VIEW.EMAIL' | translate }}: + {{ warehouse?.ordersEmail }} +
  • +
+
+
+
+
diff --git a/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/warehouse-select-view/warehouse-select-view.component.scss b/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/warehouse-select-view/warehouse-select-view.component.scss new file mode 100644 index 0000000..8a645fb --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/warehouse-select-view/warehouse-select-view.component.scss @@ -0,0 +1,10 @@ +.btnManage { + padding-top: 1.25rem; + padding-bottom: 1.25rem; +} + +nb-card#store-select-stores { + nb-card-body { + overflow: inherit !important; + } +} diff --git a/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/warehouse-select-view/warehouse-select-view.component.ts b/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/warehouse-select-view/warehouse-select-view.component.ts new file mode 100644 index 0000000..5fbc047 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/warehouse-select-view/warehouse-select-view.component.ts @@ -0,0 +1,85 @@ +import { + Component, + ViewChild, + OnInit, + OnDestroy, + EventEmitter, + Input, + OnChanges, + Output, +} from '@angular/core'; + +import { ActivatedRoute, Router } from '@angular/router'; +import { ToasterService } from 'angular2-toaster'; +import { WizardComponent } from '@ever-co/angular2-wizard'; +import { LocalDataSource } from 'ng2-smart-table'; +import { Subject } from 'rxjs'; +import { takeUntil, first } from 'rxjs/operators'; +import { TranslateService } from '@ngx-translate/core'; +import { NgbActiveModal, NgbModal } from '@ng-bootstrap/ng-bootstrap'; +import Warehouse from '@modules/server.common/entities/Warehouse'; +import { JsonModalComponent } from '../../../../@shared/json-modal/json-modal.component'; +import { WarehouseRouter } from '@modules/client.common.angular2/routers/warehouse-router.service'; +import Order from '@modules/server.common/entities/Order'; + +@Component({ + selector: 'ea-warehouse-select-view', + styleUrls: ['./warehouse-select-view.component.scss'], + templateUrl: './warehouse-select-view.component.html', +}) +export class WarehouseSelectViewComponent + implements OnInit, OnDestroy, OnChanges { + private _ngDestroy$ = new Subject(); + + @Output() + selectWarehouseEvent = new EventEmitter(); + + @Input() + public warehouses: Warehouse[]; + + @Input() + public selectedWarehouse: Warehouse; + + public warehouse: Warehouse; + + constructor( + private readonly _router: Router, + private readonly modalService: NgbModal, + private warehouseRouter: WarehouseRouter, + private readonly _route: ActivatedRoute, + private readonly _toasterService: ToasterService + ) {} + + ngOnChanges() {} + ngOnInit() {} + + public selectWarehouse(warehouse: Warehouse) { + this.warehouse = warehouse; + this.selectWarehouseEvent.emit(warehouse); + console.log(this.selectedWarehouse); + } + + openInfo() { + const activeModal = this.modalService.open(JsonModalComponent, { + size: 'lg', + container: 'nb-layout', + windowClass: 'simJSON', + }); + + const modalComponent: JsonModalComponent = + activeModal.componentInstance; + if (this.warehouse !== undefined) { + modalComponent.obj = this.warehouse; + modalComponent.title = 'Warehouse'; + modalComponent.subTitle = this.warehouse.name; + } + modalComponent.obj = this.selectedWarehouse; + modalComponent.title = 'Warehouse'; + modalComponent.subTitle = this.selectedWarehouse.name; + } + + ngOnDestroy() { + this._ngDestroy$.next(); + this._ngDestroy$.complete(); + } +} diff --git a/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/warehouse-select-view/warehouse-select-view.module.ts b/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/warehouse-select-view/warehouse-select-view.module.ts new file mode 100644 index 0000000..7d10c94 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/warehouse-select-view/warehouse-select-view.module.ts @@ -0,0 +1,31 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { FormWizardModule } from '@ever-co/angular2-wizard'; +import { Ng2SmartTableModule } from 'ng2-smart-table'; +import { TranslateModule } from '@ngx-translate/core'; +import { NbSpinnerModule, NbButtonModule } from '@nebular/theme'; +import { ThemeModule } from '../../../../@theme'; +import { WarehouseSelectViewComponent } from './warehouse-select-view.component'; +import { RouterModule } from '@angular/router'; +import { JsonModalModule } from '../../../../@shared/json-modal/json-modal.module'; +import { NgSelectModule } from '@ng-select/ng-select'; +import { FormsModule } from '@angular/forms'; + +@NgModule({ + imports: [ + CommonModule, + ThemeModule, + FormWizardModule, + Ng2SmartTableModule, + TranslateModule.forChild(), + RouterModule, + NbSpinnerModule, + JsonModalModule, + NgSelectModule, + FormsModule, + NbButtonModule, + ], + declarations: [WarehouseSelectViewComponent], + exports: [WarehouseSelectViewComponent], +}) +export class WarehouseSelectViewModule {} diff --git a/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/warehouse.component.html b/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/warehouse.component.html new file mode 100644 index 0000000..b7b38e9 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/warehouse.component.html @@ -0,0 +1,170 @@ +
+ +
+ + +
+
+ +
+ + + + + +
+
+ +
+
+
+ + +
+ + + + + + + + + + + +
+
+ + + + + + + +
+
+ + + +
+
+
+ +
+ + +
+
+
+ +
+
+
+
+
diff --git a/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/warehouse.component.scss b/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/warehouse.component.scss new file mode 100644 index 0000000..139b2db --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/warehouse.component.scss @@ -0,0 +1,280 @@ +@import '../../../@theme/styles/themes'; +@import '~bootstrap/scss/mixins/breakpoints'; +@import '~@nebular/theme/styles/global/breakpoints'; +$brand: #2a2c39; +$brand-lighted: #353748; +$brand-darken: darken($brand, 5%); +.btnManage { + padding-top: 1.25rem; + padding-bottom: 1.25rem; +} + +.btnOrders { + padding: 10px; +} + +.btn { + cursor: pointer; +} + +.btn:hover { + cursor: pointer; +} + +.btn-startProcessing { + color: #333; + background-color: #fff; + border-color: #ccc; + margin-bottom: 5px; +} + +.btn-startProcessing:hover { + color: rgb(24, 22, 22); + background-color: rgb(241, 239, 239); + border-color: rgb(182, 176, 176); + cursor: pointer; +} + +.toggle-types { + .btn-toggle-radio-group { + margin-bottom: 1rem; + } +} + +.subheader { + margin-bottom: 0.75rem; + font-size: 0.875rem; + // TODO style + // font-weight: nb-theme(font-weight-bolder); + // color: #2a2a2a; +} + +.example-container:not(:last-child) { + margin-bottom: 1.5rem; +} + +.warehouse-product-image { + max-width: 100%; + max-height: 300px; +} + +.list-group-item:hover { + cursor: pointer; +} + +nb-card { + transform: translate3d(0, 0, 0); +} + +.btn-danger { + color: #fff; + background-color: #d9534f; + border-color: #d43f3a; +} + +.btnOrder { + padding: 3px; +} + +.warehouseImg { + padding-right: 0; + + img { + width: 100%; + max-height: 130px; + } +} + +:host ::ng-deep .productsContainer a { + border: none; + background-color: rgba(182, 176, 176, 0.05) !important; + color: #111111 !important; + + .warehouse-product-image { + text-align: center; + span { + text-align: left !important; + padding: 0 !important; + } + span.badge { + padding: 5px !important; + } + } +} + +:host ::ng-deep ng2-smart-table { + .tableImg { + height: 100px; + width: 100px; + } +} + +.nb-theme-cosmic .whiteDiv { + color: white !important; +} + +.product-info { + padding-top: 10px; +} + +nb-card { + transform: translate3d(0, 0, 0); +} + +button:disabled { + cursor: not-allowed; + opacity: 0.5; +} + +nb-card-footer { + border: none; +} + +nb-card-header { + .store-info { + margin-left: 0; + margin-bottom: 5px; + } + + .store-info-img { + padding: 3px; + font-size: 18px; + color: rgb(161, 161, 161); + } +} + +.page-header { + padding: 1.25rem !important; +} + +.products { + list-style-type: none; + height: inherit !important; + max-width: 80%; + margin: 0 auto; + .product-card-container { + border: 0; + padding-bottom: 0; + margin-bottom: 1.25rem; + .product-card { + margin: 0; + padding: 0; + text-align: center; + .product-image-container { + cursor: pointer; + } + .product-image-container:active { + background: #000; + .product-image { + opacity: 0.8; + } + } + .product-buttons { + position: absolute; + top: 20px; + right: 20px; + .add-button { + padding: 0; + font-size: 30px; + color: #fff; + } + } + .product-image { + width: 100%; + } + .product-actions-info-bar { + background-color: $brand-darken; + color: white; + margin-bottom: 0; + padding-top: 5px; + padding-bottom: 5px; + margin-top: -6px; + .actions { + a { + margin-top: -2px; + border-radius: 3px; + color: $brand-lighted; + float: right; + border-color: #ccc; + color: white; + margin-right: 10px; + padding: 4px; + font-size: 18px; + line-height: 0; + } + } + .info { + span { + margin-top: -2px; + border-radius: 3px; + color: $brand-lighted; + float: left; + border-color: #ccc; + color: rgb(161, 161, 161); + margin-left: 10px; + padding: 4px; + font-size: 18px; + line-height: 0; + } + } + } + .product-mini-bar { + font-size: 17px; + background-color: $brand-lighted; + color: white; + text-align: center; + margin-bottom: 0; + padding-top: 11px; + padding-bottom: 11px; + margin-top: -6px; + .count { + background: #fff; + margin-top: -2px; + padding: 2px; + border-radius: 3px; + color: $brand-lighted; + float: right; + margin-right: 10px; + } + } + } + + .name { + width: 75%; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + display: inline-block; + } + } +} + +.progress-bar-danger { + background: #d9534f !important; + color: white !important; +} + +.progress-bar-warning { + background: #f0ad4e !important; + color: white !important; +} + +// added for the Cosmic theme + +.list-group-item { + background-color: transparent; +} + +.store-details { + padding-bottom: 0; +} + +.orders-filters { + padding: 0 1.25rem; +} + +.process-orders { + label:hover { + color: #000; + } +} diff --git a/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/warehouse.component.ts b/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/warehouse.component.ts new file mode 100644 index 0000000..d912e1b --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/warehouse.component.ts @@ -0,0 +1,436 @@ +import { + Component, + OnDestroy, + AfterViewInit, + ViewChild, + OnChanges, +} from '@angular/core'; +import { DatePipe } from '@angular/common'; +import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap'; +import { ActivatedRoute, Router } from '@angular/router'; +import { ToasterService } from 'angular2-toaster'; +import { LocalDataSource } from 'ng2-smart-table'; +import { TranslateService } from '@ngx-translate/core'; +import Order from '@modules/server.common/entities/Order'; +import { WarehouseRouter } from '@modules/client.common.angular2/routers/warehouse-router.service'; +import Warehouse from '@modules/server.common/entities/Warehouse'; +import { ILocaleMember } from '@modules/server.common/interfaces/ILocale'; +import WarehouseProduct from '@modules/server.common/entities/WarehouseProduct'; +import { ProductLocalesService } from '@modules/client.common.angular2/locale/product-locales.service'; +import { + ordersFilter, + OrdersFilterModes, +} from '../../ordersFilter/ordersFilter'; +import { WarehouseProductCreateComponent } from '../../../@shared/warehouse-product/warehouse-product-create'; +import { ElapsedComponent } from '../../../@shared/render-component/warehouse-table/elapsed/elapsed.component'; +import { WarehouseOrderComponent } from './+warehouse-order/warehouse-order.component'; +import { RedirectOrderComponent } from '@app/@shared/render-component/customer-orders-table/redirect-order.component'; +import { ProductsTableComponent } from './products-table/products-table.component'; +import { WarehouseSelectViewComponent } from './warehouse-select-view/warehouse-select-view.component'; +import { WarehouseOrdersService } from '@app/@core/data/warehouseOrders.service'; +import { StatusComponent } from '@app/@shared/render-component/warehouse-table/status/status.component'; +import { Subject, forkJoin, Observable } from 'rxjs'; +import { takeUntil, first } from 'rxjs/operators'; + +const perPage = 3; + +@Component({ + selector: 'ea-warehouse', + templateUrl: './warehouse.component.html', + styleUrls: ['./warehouse.component.scss'], +}) +export class WarehouseComponent implements OnDestroy, AfterViewInit, OnChanges { + private ngDestroy$ = new Subject(); + + @ViewChild('productsTable') + public productsTable: ProductsTableComponent; + + @ViewChild('warehouseSelectView') + public warehouseSelectView: WarehouseSelectViewComponent; + + public filterMode: OrdersFilterModes = 'all'; + public orders: Order[] = []; + public allOrders: Order[] = []; + public loading: boolean; + + public warehouses: Warehouse[]; + public topWarehouseProducts: WarehouseProduct[]; + public warehouse: Warehouse; + public selectedWarehouse: Warehouse; + public warehouseID: string; + public selectedOrder: Order; + public settingsSmartTable: object; + public sourceSmartTable: LocalDataSource = new LocalDataSource(); + public isSelectedOrderActionsAvailable: boolean = true; + + protected timers: string[] = []; + + private dataCount: number; + private $storeOrders; + private page: number = 1; + + constructor( + private readonly modalService: NgbModal, + private warehouseRouter: WarehouseRouter, + private translate: TranslateService, + private readonly _route: ActivatedRoute, + private readonly _toasterService: ToasterService, + private _productLocalesService: ProductLocalesService, + private readonly _router: Router, + private _translateService: TranslateService, + private warehouseOrdersService: WarehouseOrdersService + ) { + this._loadTableSettings(); + this._loadWarehouses(); + this._listenForEntityLocaleTranslate(); + } + + ngAfterViewInit() { + this.loadSmartTableTranslates(); + this.smartTableChange(); + } + + ngOnChanges() {} + + openWarehouseOrderCreateModal() { + const modalRef: NgbModalRef = this.modalService.open( + WarehouseOrderComponent, + { + size: 'xl', + container: 'nb-layout', + windowClass: 'ng-custom', + backdrop: 'static', + } + ); + + (modalRef.componentInstance as WarehouseOrderComponent).orderFinishedEmitter + .pipe(takeUntil(this.ngDestroy$)) + .subscribe(() => { + modalRef.close(); + }); + } + + switchLanguage(language: string) { + this.translate.use(language); + } + + async filterOrders(mode) { + this.selectedOrder = null; + this.page = 1; + await this._getWarehouseOrders(this.warehouseID, this.page, mode); + this.sourceSmartTable.setPage(1); + this.filterMode = mode; + } + + getOrders() { + this.orders = ordersFilter(this.allOrders, this.filterMode); + return this.orders; + } + + public selectOrder(warehouseOrderProducts) { + this.selectedOrder = + this.selectedOrder === warehouseOrderProducts.data + ? null + : warehouseOrderProducts.data; + } + + selectWarehouseEvent(warehouse) { + this._router.navigate([`/stores/${warehouse.id}`]); + this.selectedOrder = null; + this.warehouse = warehouse; + this.warehouseID = warehouse.id; + + this._getWarehouseOrders(warehouse.id, this.page, this.filterMode); + this._loadTableSettings(); + + this.selectedWarehouse = warehouse; + + if (this.productsTable) { + this.productsTable.loadDataSmartTable(warehouse.id); + } + } + + loadSmartTableTranslates() { + this._translateService.onLangChange.subscribe((d) => { + this._loadTableSettings(); + }); + } + + public openAddProductTypeModel() { + const activeModal = this.modalService.open( + WarehouseProductCreateComponent, + { + size: 'lg', + container: 'nb-layout', + backdrop: 'static', + windowClass: 'ng-custom', + } + ); + const modalComponent: WarehouseProductCreateComponent = + activeModal.componentInstance; + modalComponent.warehouseId = this.warehouseID; + modalComponent.selectedWarehouse = this.selectedWarehouse; + } + + private _loadWarehouses() { + this.warehouseRouter + .getAll() + .pipe(takeUntil(this.ngDestroy$)) + .subscribe((w: Warehouse[]) => { + this.warehouses = w; + this._selectWarehouseIfIdExist(); + }); + } + + private async _getWarehouseOrders( + id: string, + page = 1, + status = this.filterMode + ) { + if (this.$storeOrders) { + await this.$storeOrders.unsubscribe(); + } + this.$storeOrders = this.warehouseOrdersService + .getStoreOrdersTableData( + id, + { + skip: perPage * (page - 1), + limit: perPage, + }, + status + ) + .pipe(takeUntil(this.ngDestroy$)) + .subscribe(async (res) => { + const orders = res.orders; + await this.loadDataCount(id, status); + + this.allOrders = orders; + const data = this._setupDataForSmartTable(orders); + const ordersData = new Array(this.dataCount); + + ordersData.splice(perPage * (page - 1), perPage, ...data); + + await this.sourceSmartTable.load(ordersData); + }); + } + + private _loadTableSettings() { + const columnTitlePrefix = 'WAREHOUSE_VIEW.'; + const getTranslate = (name: string): Observable => + this._translateService.get(columnTitlePrefix + name); + + forkJoin( + this._translateService.get('Id'), + getTranslate('ORDER_NUMBER'), + getTranslate('PRODUCTS'), + getTranslate('WAREHOUSE_STATUS'), + getTranslate('CARRIER_STATUS'), + getTranslate('PAID'), + getTranslate('CANCELLED'), + getTranslate('CREATED'), + getTranslate('ELAPSED') + ) + .pipe(takeUntil(this.ngDestroy$)) + .subscribe( + ([ + id, + orderNumber, + product, + status, + carrier, + paid, + cancelled, + created, + elapsed, + ]) => { + this.settingsSmartTable = { + actions: false, + columns: { + orderNumber: { + title: orderNumber, + type: 'custom', + renderComponent: RedirectOrderComponent, + width: '100px', + }, + product: { title: product, type: 'html' }, + status: { + title: status, + type: 'html', + width: '100px', + }, + carrierStatusHtml: { + title: carrier, + type: 'html', + width: '100px', + }, + paid: { + title: paid, + type: 'custom', + renderComponent: StatusComponent, + onComponentInitFunction: async ( + instance: StatusComponent + ) => { + instance.text = paid; + instance.checkOrderField = 'isPaid'; + }, + width: '100px', + }, + cancelled: { + title: cancelled, + type: 'custom', + renderComponent: StatusComponent, + onComponentInitFunction: async ( + instance: StatusComponent + ) => { + instance.text = cancelled; + instance.checkOrderField = 'isCancelled'; + }, + width: '100px', + }, + created: { title: created, type: 'string' }, + elapsed: { + title: elapsed, + filter: false, + type: 'custom', + renderComponent: ElapsedComponent, + }, + }, + pager: { + display: true, + perPage, + }, + }; + } + ); + } + + private _setupDataForSmartTable(orders: Order[]) { + const data = []; + + orders.forEach((order) => { + const raw: Date = new Date(order._createdAt); + const formatted: string = new DatePipe('en-EN').transform( + raw, + 'dd-MMM-yyyy hh:mm:ss' + ); + + const columnTitlePrefix = 'STATUS_TEXT.'; + const getTranslate = (name: string): Observable => + this._translateService.get(columnTitlePrefix + name); + + forkJoin( + this._translateService.get('Id'), + getTranslate(order.warehouseStatusText), + getTranslate(order.carrierStatusText), + getTranslate('CARRIER'), + getTranslate('PAID'), + getTranslate('CANCELLED'), + getTranslate('CREATED'), + getTranslate('ELAPSED') + ) + .pipe(takeUntil(this.ngDestroy$)) + .subscribe( + ([ + id, + warehouseStatusText, + carrierStatusText, + carrier, + paid, + cancelled, + created, + elapsed, + ]) => { + data.push({ + id: order.id, + products: order.products, + orderNumber: order.orderNumber, + warehouseStatusText: order.warehouseStatusText, + carrierStatusText: order.carrierStatusText, + createdAt: order.createdAt, + warehouseStatus: order.warehouseStatus, + carrier: order.carrier, + carrierStatus: order.carrierStatus, + isPaid: order.isPaid, + isCancelled: order.isCancelled, + product: order.products.length + ? ` +
+ +

+ ${this.localeTranslate(order.products[0].product.title)} + ${order.products[0].count} +

+
+ ` + : '', + status: `
${warehouseStatusText}
`, + carrierStatusHtml: `
${carrierStatusText}
`, + created: formatted, + orderType: order.orderType, + order, + }); + } + ); + }); + + return data; + } + + protected localeTranslate(member: ILocaleMember[]) { + return this._productLocalesService.getTranslate(member); + } + + private _listenForEntityLocaleTranslate() { + this.translate.onLangChange.subscribe(() => { + const data = this._setupDataForSmartTable(this.getOrders()); + this.sourceSmartTable.load(data); + }); + } + + private async _selectWarehouseIfIdExist() { + const p = await this._route.params.pipe(first()).toPromise(); + const warehouseId = p.id; + if (warehouseId !== undefined) { + const warehouse = this.warehouses.find((w) => w.id === p.id); + if (warehouse !== undefined) { + this.selectWarehouseEvent(warehouse as Warehouse); + } else { + this._toasterService.pop( + `warning`, + `Warehouse with id '${warehouseId}' is not active now!` + ); + } + } + } + + private async smartTableChange() { + this.sourceSmartTable + .onChanged() + .pipe(takeUntil(this.ngDestroy$)) + .subscribe(async (event) => { + if (event.action === 'page') { + const page = event.paging.page; + this.page = page; + this._getWarehouseOrders( + this.warehouseID, + page, + (status = this.filterMode) + ); + } + }); + } + + private async loadDataCount(id, status) { + this.dataCount = await this.warehouseOrdersService.getCountOfStoreOrders( + id, + status + ); + } + + ngOnDestroy() { + this.ngDestroy$.next(); + this.ngDestroy$.complete(); + } +} diff --git a/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/warehouse.module.ts b/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/warehouse.module.ts new file mode 100644 index 0000000..d712fba --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+warehouses/+warehouse/warehouse.module.ts @@ -0,0 +1,62 @@ +import { NgModule } from '@angular/core'; +import { RouterModule, Routes } from '@angular/router'; +import { CommonModule } from '@angular/common'; +import { Ng2SmartTableModule } from 'ng2-smart-table'; +import { ToasterModule } from 'angular2-toaster'; +import { ThemeModule } from '../../../@theme'; +import { WarehouseComponent } from './warehouse.component'; +import { WarehouseProductCreateModule } from '../../../@shared/warehouse-product/warehouse-product-create'; +import { WarehouseTableModule } from '../../../@shared/render-component/warehouse-table/warehouse-table.module'; +import { WarehouseOrderModule } from './+warehouse-order/warehouse-order.module'; +import { CustomerOrdersTableModule } from '../../../@shared/render-component/customer-orders-table/customer-orders-table.module'; +import { JsonModalModule } from '../../../@shared/json-modal/json-modal.module'; +import { NbSpinnerModule, NbButtonModule } from '@nebular/theme'; +import { ProductsTableModule } from './products-table/products-table.module'; +import { WarehouseProductsViewModule } from './+warehouse-products-view/warehouse-products-view.module'; +import { WarehouseMainInfoViewModule } from './warehouse-main-info/warehouse-main-info.module'; +import { WarehouseSelectViewModule } from './warehouse-select-view/warehouse-select-view.module'; +import { WarehouseOrderViewModule } from './warehouse-order-view/warehouse-order-view.module'; +import { WarehouseOrdersTableModule } from './warehouse-orders-table/warehouse-orders-table.module'; +import { TranslateModule } from '@app/@shared/translate/translate.module'; + +const routes: Routes = [ + { + path: '', + component: WarehouseComponent, + }, + { + path: 'manage', + loadChildren: () => + import('./+warehouse-manage/warehouse-manage.module').then( + (m) => m.WarehouseManageModule + ), + }, +]; + +@NgModule({ + imports: [ + CommonModule, + ToasterModule.forRoot(), + ThemeModule, + Ng2SmartTableModule, + WarehouseTableModule, + TranslateModule, + RouterModule.forChild(routes), + WarehouseProductCreateModule, + WarehouseOrderModule, + CustomerOrdersTableModule, + JsonModalModule, + NbSpinnerModule, + ProductsTableModule, + WarehouseProductsViewModule, + WarehouseMainInfoViewModule, + WarehouseSelectViewModule, + WarehouseOrderViewModule, + WarehouseOrdersTableModule, + NbButtonModule, + ], + declarations: [WarehouseComponent], +}) +export class WarehouseModule { + public static routes = routes; +} diff --git a/packages/admin-web-angular/src/app/pages/+warehouses/warehouses-routing.module.ts b/packages/admin-web-angular/src/app/pages/+warehouses/warehouses-routing.module.ts new file mode 100644 index 0000000..d76497b --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+warehouses/warehouses-routing.module.ts @@ -0,0 +1,30 @@ +import { RouterModule, Routes } from '@angular/router'; +import { WarehousesComponent } from './warehouses.component'; +import { NgModule } from '@angular/core'; +import { WarehouseTrackComponent } from './+warehouse-track/warehouse-track.component'; + +const routes: Routes = [ + { + path: '', + component: WarehousesComponent, + }, + { + path: 'track', + component: WarehouseTrackComponent, + }, + { + path: ':id', + loadChildren: () => + import('./+warehouse/warehouse.module').then( + (m) => m.WarehouseModule + ), + }, +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule], +}) +export class WarehousesRoutingModule { + public static routes = routes; +} diff --git a/packages/admin-web-angular/src/app/pages/+warehouses/warehouses.component.html b/packages/admin-web-angular/src/app/pages/+warehouses/warehouses.component.html new file mode 100644 index 0000000..5d998a8 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+warehouses/warehouses.component.html @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + diff --git a/packages/admin-web-angular/src/app/pages/+warehouses/warehouses.component.scss b/packages/admin-web-angular/src/app/pages/+warehouses/warehouses.component.scss new file mode 100644 index 0000000..99d20cb --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+warehouses/warehouses.component.scss @@ -0,0 +1,83 @@ +nb-card-header { + border-bottom: 0; +} + +nb-card-body { + padding: 0; + border-bottom-left-radius: 0.5rem; + border-bottom-right-radius: 0.5rem; +} + +:host ::ng-deep ng2-smart-table { + tr { + th { + padding: 18px !important; + } + td { + padding: 17px !important; + } + } + + tr.ng2-smart-titles > th:nth-child(1) { + text-align: center; + cursor: pointer; + + input { + margin-left: 1px !important; + cursor: pointer; + } + } + + td.ng2-smart-actions.ng2-smart-action-multiple-select { + text-align: center; + cursor: pointer; + } + + tr.ng2-smart-filters th { + text-align: center; + } + + tr td, + th { + &:first-of-type { + border-left: none; + } + &:last-of-type { + border-right: none; + } + } + + input[type='checkbox'] { + cursor: pointer; + } + + .warehouse-name { + width: 17%; + } + .warehouse-email { + width: 17%; + } + .warehouse-phone { + width: 17%; + } + .warehouse-city { + width: 17%; + } + .warehouse-address { + width: 32%; + } + // .warehouse-qty { + // width: 5%; + // } + // .warehouse-actions { + // width: 5%; + // } +} + +:host ::ng-deep ng2-smart-table .warehouse-image { + width: 74px; +} + +.ng2-smart-th.qty { + text-align: center; +} diff --git a/packages/admin-web-angular/src/app/pages/+warehouses/warehouses.component.ts b/packages/admin-web-angular/src/app/pages/+warehouses/warehouses.component.ts new file mode 100644 index 0000000..a57c374 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+warehouses/warehouses.component.ts @@ -0,0 +1,316 @@ +import { AfterViewInit, Component, OnDestroy } from '@angular/core'; +import { Router } from '@angular/router'; +import { ToasterService } from 'angular2-toaster'; +import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; +import { LocalDataSource } from 'ng2-smart-table'; +import { TranslateService } from '@ngx-translate/core'; +import { WarehousesService } from '../../@core/data/warehouses.service'; +import { OrdersService } from '../../@core/data/orders.service'; +import Warehouse from '@modules/server.common/entities/Warehouse'; +import { Observable, forkJoin, Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; +import { DomSanitizer } from '@angular/platform-browser'; +import { WarehouseViewModel } from '../../models/WarehouseViewModel'; +import { WarehouseMutationComponent } from '../../@shared/warehouse/warehouse-mutation'; +import { RedirectNameComponent } from '../../@shared/render-component/name-redirect/name-redirect.component'; +import { WarehouseActionsComponent } from '../../@shared/render-component/warehouse-table/warehouse-actions/warehouse-actions.component'; +import { WarehouseImageComponent } from '../../@shared/render-component/warehouse-table/warehouse-image/warehouse-image.component'; +import { WarehouseOrdersNumberComponent } from '../../@shared/render-component/warehouse-table/warehouse-orders-number/warehouse-orders-number.component'; +import { ConfirmationModalComponent } from '../../@shared/confirmation-modal/confirmation-modal.component'; +import { WarehouseEmailComponent } from '../../@shared/render-component/warehouse-table/warehouse-email/warehouse-email.component'; +import { WarehousePhoneComponent } from '../../@shared/render-component/warehouse-table/warehouse-phone/warehouse-phone.component'; + +const perPage = 5; + +@Component({ + selector: 'ea-warehouses', + templateUrl: './warehouses.component.html', + styleUrls: ['./warehouses.component.scss'], +}) +export class WarehousesComponent implements AfterViewInit, OnDestroy { + private static noInfoSign = ''; + + private ngDestroy$ = new Subject(); + + public settingsSmartTable: object; + public sourceSmartTable = new LocalDataSource(); + + private _selectedWarehouses: WarehouseViewModel[] = []; + + public loading: boolean; + + private dataCount: number; + private $merchants; + + constructor( + private readonly _translateService: TranslateService, + private readonly _router: Router, + private readonly _modalService: NgbModal, + private readonly _warehousesService: WarehousesService, + private readonly _ordersService: OrdersService, + private readonly _toasterService: ToasterService, + private readonly _sanitizer: DomSanitizer, + private readonly modalService: NgbModal + ) { + this._loadSettingsSmartTable(); + } + + get hasSelectedWarehouses(): boolean { + return this._selectedWarehouses.length > 0; + } + + ngAfterViewInit() { + this._addCustomHTMLElements(); + this._applyTranslationOnSmartTable(); + this.smartTableChange(); + this._loadDataSmartTable(); + } + + createWarehouseModel() { + this._modalService.open(WarehouseMutationComponent, { + size: 'lg', + container: 'nb-layout', + backdrop: 'static', + }); + } + + selectWarehouseTmp(ev) { + this._selectedWarehouses = ev.selected; + } + + async deleteSelectedRows() { + const activeModal = this.modalService.open(ConfirmationModalComponent, { + size: 'sm', + container: 'nb-layout', + backdrop: 'static', + }); + const modalComponent: ConfirmationModalComponent = + activeModal.componentInstance; + + await modalComponent.confirmEvent + .pipe(takeUntil(modalComponent.ngDestroy$)) + .subscribe((dataEvent) => { + const idsForDelete: string[] = this._selectedWarehouses.map( + (w) => w.id + ); + + try { + this.loading = true; + this._warehousesService + .removeByIds(idsForDelete) + .subscribe(() => { + this.loading = false; + this._toasterService.pop( + `success`, + `Selected warehouse are deleted!` + ); + this._selectedWarehouses = []; + }); + } catch (error) { + this.loading = false; + + this._toasterService.pop( + 'error', + `Error: "${error.message}"` + ); + } + + modalComponent.cancel(); + }); + } + + // This is just workaround to show some search icon on smart table, in the future maybe we must find better solution. + private _addCustomHTMLElements(): any { + document.querySelector( + 'tr.ng2-smart-filters > th:nth-child(1)' + ).innerHTML = ''; + } + + private _selectWarehouse(warehouseId: string) { + this._router.navigate(['/stores/' + warehouseId]); + } + + private _applyTranslationOnSmartTable() { + this._translateService.onLangChange + .pipe(takeUntil(this.ngDestroy$)) + .subscribe(() => { + this._loadSettingsSmartTable(); + }); + } + + private async _loadDataSmartTable(page = 1) { + if (this.$merchants) { + await this.$merchants.unsubscribe(); + } + + let warehouses: Warehouse[] = []; + + this.$merchants = this._warehousesService + .getStores({ + skip: perPage * (page - 1), + limit: perPage, + }) + .pipe(takeUntil(this.ngDestroy$)) + .subscribe((w: Warehouse[]) => { + warehouses = w; + loadData(); + }); + + const loadData = async () => { + const merchantsOrders = await this._ordersService.getMerchantsOrdersCountInfo( + warehouses.map((w) => w.id) + ); + + const warehousesVM = warehouses.map((warehouse) => { + const merchantOrders = merchantsOrders.find( + (res) => res['id'] === warehouse.id + ); + + return { + id: warehouse.id, + image: warehouse.logo || WarehousesComponent.noInfoSign, + name: warehouse.name || WarehousesComponent.noInfoSign, + email: + warehouse.contactEmail || + WarehousesComponent.noInfoSign, + phone: + warehouse.contactPhone || + WarehousesComponent.noInfoSign, + city: + warehouse.geoLocation.city || + WarehousesComponent.noInfoSign, + address: `st. ${ + warehouse.geoLocation.streetAddress || + WarehousesComponent.noInfoSign + }, hse. № ${ + warehouse.geoLocation.house || + WarehousesComponent.noInfoSign + }`, + ordersQty: merchantOrders ? merchantOrders.ordersCount : 0, + warehouseInfo: warehouse, + }; + }); + + await this.loadDataCount(); + + const merchantsData = new Array(this.dataCount); + + merchantsData.splice( + perPage * (page - 1), + perPage, + ...warehousesVM + ); + + await this.sourceSmartTable.load(merchantsData); + }; + } + + private _loadSettingsSmartTable() { + const columnTitlePrefix = 'WAREHOUSES_VIEW.SMART_TABLE_COLUMNS.'; + const getTranslate = (name: string): Observable => + this._translateService.get(columnTitlePrefix + name); + + forkJoin( + this._translateService.get('Id'), + getTranslate('IMAGE'), + getTranslate('NAME'), + getTranslate('EMAIL'), + getTranslate('PHONE'), + getTranslate('CITY'), + getTranslate('ADDRESS'), + getTranslate('ORDERS') + ) + .pipe(takeUntil(this.ngDestroy$)) + .subscribe( + ([id, image, name, email, phone, city, address, orders]) => { + this.settingsSmartTable = { + actions: false, + selectMode: 'multi', + columns: { + images: { + title: image, + class: 'warehouse-image', + type: 'custom', + renderComponent: WarehouseImageComponent, + onComponentInitFunction: (instance) => { + instance.redirectPage = 'stores'; + }, + filter: false, + }, + name: { + title: name, + type: 'custom', + class: 'warehouse-name', + renderComponent: RedirectNameComponent, + onComponentInitFunction: (instance) => { + instance.redirectPage = 'stores'; + }, + }, + email: { + title: email, + type: 'custom', + renderComponent: WarehouseEmailComponent, + class: 'warehouse-email', + }, + phone: { + title: phone, + type: 'custom', + renderComponent: WarehousePhoneComponent, + class: 'warehouse-phone', + }, + city: { + title: city, + class: 'warehouse-city', + }, + address: { + title: address, + class: 'warehouse-address', + }, + ordersQty: { + title: orders, + type: 'custom', + filter: false, + class: 'warehouse-qty', + renderComponent: WarehouseOrdersNumberComponent, + onComponentInitFunction: (instance) => { + instance.redirectPage = 'stores'; + }, + }, + actions: { + title: 'Actions', + filter: false, + type: 'custom', + class: 'warehouse-actions', + renderComponent: WarehouseActionsComponent, + }, + }, + pager: { + display: true, + perPage, + }, + }; + } + ); + } + + private async smartTableChange() { + this.sourceSmartTable + .onChanged() + .pipe(takeUntil(this.ngDestroy$)) + .subscribe(async (event) => { + if (event.action === 'page') { + const page = event.paging.page; + this._loadDataSmartTable(page); + } + }); + } + + private async loadDataCount() { + this.dataCount = await this._warehousesService.getCountOfMerchants(); + } + + ngOnDestroy() { + this.ngDestroy$.next(); + this.ngDestroy$.complete(); + } +} diff --git a/packages/admin-web-angular/src/app/pages/+warehouses/warehouses.module.ts b/packages/admin-web-angular/src/app/pages/+warehouses/warehouses.module.ts new file mode 100644 index 0000000..c368a45 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/+warehouses/warehouses.module.ts @@ -0,0 +1,39 @@ +import { NgModule } from '@angular/core'; +import { CommonModule, JsonPipe } from '@angular/common'; +import { FormWizardModule } from '@ever-co/angular2-wizard'; +import { ToasterModule } from 'angular2-toaster'; +import { Ng2SmartTableModule } from 'ng2-smart-table'; +import { ThemeModule } from '../../@theme'; +import { WarehousesComponent } from './warehouses.component'; +import { WarehousesRoutingModule } from './warehouses-routing.module'; +import { WarehouseMutationModule } from '../../@shared/warehouse/warehouse-mutation'; +import { HighlightModule } from 'ngx-highlightjs'; +import { RenderComponentsModule } from '../../@shared/render-component/render-components.module'; +import { WarehouseTableModule } from '../../@shared/render-component/warehouse-table/warehouse-table.module'; +import { NbSpinnerModule, NbButtonModule } from '@nebular/theme'; +import { ConfirmationModalModule } from '@app/@shared/confirmation-modal/confirmation-modal.module'; +import { WarehouseTrackModule } from './+warehouse-track/warehouse-track.module'; +import { TranslateModule } from '@app/@shared/translate/translate.module'; + +@NgModule({ + imports: [ + ThemeModule, + CommonModule, + WarehouseMutationModule, + WarehousesRoutingModule, + Ng2SmartTableModule, + FormWizardModule, + ConfirmationModalModule, + WarehouseTrackModule, + ToasterModule.forRoot(), + TranslateModule, + HighlightModule, + RenderComponentsModule, + WarehouseTableModule, + NbSpinnerModule, + NbButtonModule, + ], + declarations: [WarehousesComponent], + providers: [JsonPipe], +}) +export class WarehousesModule {} diff --git a/packages/admin-web-angular/src/app/pages/charts/chartjs/chartjs-bar-horizontal.component.ts b/packages/admin-web-angular/src/app/pages/charts/chartjs/chartjs-bar-horizontal.component.ts new file mode 100644 index 0000000..82aa258 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/charts/chartjs/chartjs-bar-horizontal.component.ts @@ -0,0 +1,107 @@ +import { Component, OnDestroy } from '@angular/core'; +import { NbThemeService } from '@nebular/theme'; + +@Component({ + selector: 'ngx-chartjs-bar-horizontal', + template: ` + + `, +}) +export class ChartjsBarHorizontalComponent implements OnDestroy { + data: any; + options: any; + themeSubscription: any; + + constructor(private theme: NbThemeService) { + this.themeSubscription = this.theme.getJsTheme().subscribe((config) => { + const colors: any = config.variables; + const chartjs: any = config.variables.chartjs; + + this.data = { + labels: [ + 'January', + 'February', + 'March', + 'April', + 'May', + 'June', + ], + datasets: [ + { + label: 'Dataset 1', + backgroundColor: colors.infoLight, + borderWidth: 1, + data: [ + this.random(), + this.random(), + this.random(), + this.random(), + this.random(), + this.random(), + ], + }, + { + label: 'Dataset 2', + backgroundColor: colors.successLight, + data: [ + this.random(), + this.random(), + this.random(), + this.random(), + this.random(), + this.random(), + ], + }, + ], + }; + + this.options = { + responsive: true, + maintainAspectRatio: false, + elements: { + rectangle: { + borderWidth: 2, + }, + }, + scales: { + xAxes: [ + { + gridLines: { + display: true, + color: chartjs.axisLineColor, + }, + ticks: { + fontColor: chartjs.textColor, + }, + }, + ], + yAxes: [ + { + gridLines: { + display: false, + color: chartjs.axisLineColor, + }, + ticks: { + fontColor: chartjs.textColor, + }, + }, + ], + }, + legend: { + position: 'right', + labels: { + fontColor: chartjs.textColor, + }, + }, + }; + }); + } + + private random() { + return Math.round(Math.random() * 100); + } + + ngOnDestroy(): void { + this.themeSubscription.unsubscribe(); + } +} diff --git a/packages/admin-web-angular/src/app/pages/charts/chartjs/chartjs-bar.component.ts b/packages/admin-web-angular/src/app/pages/charts/chartjs/chartjs-bar.component.ts new file mode 100644 index 0000000..e5067c5 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/charts/chartjs/chartjs-bar.component.ts @@ -0,0 +1,87 @@ +import { Component, OnDestroy } from '@angular/core'; +import { NbColorHelper, NbThemeService } from '@nebular/theme'; + +@Component({ + selector: 'ngx-chartjs-bar', + template: ` `, +}) +export class ChartjsBarComponent implements OnDestroy { + data: any; + options: any; + themeSubscription: any; + + constructor(private theme: NbThemeService) { + this.themeSubscription = this.theme.getJsTheme().subscribe((config) => { + const colors: any = config.variables; + const chartjs: any = config.variables.chartjs; + + this.data = { + labels: [ + '2006', + '2007', + '2008', + '2009', + '2010', + '2011', + '2012', + ], + datasets: [ + { + data: [65, 59, 80, 81, 56, 55, 40], + label: 'Series A', + backgroundColor: NbColorHelper.hexToRgbA( + colors.primaryLight, + 0.8 + ), + }, + { + data: [28, 48, 40, 19, 86, 27, 90], + label: 'Series B', + backgroundColor: NbColorHelper.hexToRgbA( + colors.infoLight, + 0.8 + ), + }, + ], + }; + + this.options = { + maintainAspectRatio: false, + responsive: true, + legend: { + labels: { + fontColor: chartjs.textColor, + }, + }, + scales: { + xAxes: [ + { + gridLines: { + display: false, + color: chartjs.axisLineColor, + }, + ticks: { + fontColor: chartjs.textColor, + }, + }, + ], + yAxes: [ + { + gridLines: { + display: true, + color: chartjs.axisLineColor, + }, + ticks: { + fontColor: chartjs.textColor, + }, + }, + ], + }, + }; + }); + } + + ngOnDestroy(): void { + this.themeSubscription.unsubscribe(); + } +} diff --git a/packages/admin-web-angular/src/app/pages/charts/chartjs/chartjs-line.component.ts b/packages/admin-web-angular/src/app/pages/charts/chartjs/chartjs-line.component.ts new file mode 100644 index 0000000..98314fc --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/charts/chartjs/chartjs-line.component.ts @@ -0,0 +1,98 @@ +import { Component, OnDestroy } from '@angular/core'; +import { NbColorHelper, NbThemeService } from '@nebular/theme'; + +@Component({ + selector: 'ngx-chartjs-line', + template: ` `, +}) +export class ChartjsLineComponent implements OnDestroy { + data: any; + options: any; + themeSubscription: any; + + constructor(private theme: NbThemeService) { + this.themeSubscription = this.theme.getJsTheme().subscribe((config) => { + const colors: any = config.variables; + const chartjs: any = config.variables.chartjs; + + this.data = { + labels: [ + 'January', + 'February', + 'March', + 'April', + 'May', + 'June', + 'July', + ], + datasets: [ + { + data: [65, 59, 80, 81, 56, 55, 40], + label: 'Series A', + backgroundColor: NbColorHelper.hexToRgbA( + colors.primary, + 0.3 + ), + borderColor: colors.primary, + }, + { + data: [28, 48, 40, 19, 86, 27, 90], + label: 'Series B', + backgroundColor: NbColorHelper.hexToRgbA( + colors.danger, + 0.3 + ), + borderColor: colors.danger, + }, + { + data: [18, 48, 77, 9, 100, 27, 40], + label: 'Series C', + backgroundColor: NbColorHelper.hexToRgbA( + colors.info, + 0.3 + ), + borderColor: colors.info, + }, + ], + }; + + this.options = { + responsive: true, + maintainAspectRatio: false, + scales: { + xAxes: [ + { + gridLines: { + display: true, + color: chartjs.axisLineColor, + }, + ticks: { + fontColor: chartjs.textColor, + }, + }, + ], + yAxes: [ + { + gridLines: { + display: true, + color: chartjs.axisLineColor, + }, + ticks: { + fontColor: chartjs.textColor, + }, + }, + ], + }, + legend: { + labels: { + fontColor: chartjs.textColor, + }, + }, + }; + }); + } + + ngOnDestroy(): void { + this.themeSubscription.unsubscribe(); + } +} diff --git a/packages/admin-web-angular/src/app/pages/charts/chartjs/chartjs-multiple-xaxis.component.ts b/packages/admin-web-angular/src/app/pages/charts/chartjs/chartjs-multiple-xaxis.component.ts new file mode 100644 index 0000000..6a73433 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/charts/chartjs/chartjs-multiple-xaxis.component.ts @@ -0,0 +1,154 @@ +import { Component, OnDestroy } from '@angular/core'; +import { NbThemeService } from '@nebular/theme'; + +@Component({ + selector: 'ngx-chartjs-multiple-xaxis', + template: ` `, +}) +export class ChartjsMultipleXaxisComponent implements OnDestroy { + data: {}; + options: any; + themeSubscription: any; + + constructor(private theme: NbThemeService) { + this.themeSubscription = this.theme.getJsTheme().subscribe((config) => { + const colors: any = config.variables; + const chartjs: any = config.variables.chartjs; + + this.data = { + labels: [ + 'January', + 'February', + 'March', + 'April', + 'May', + 'June', + ], + datasets: [ + { + label: 'dataset - big points', + data: [ + this.random(), + this.random(), + this.random(), + this.random(), + this.random(), + this.random(), + ], + borderColor: colors.primary, + backgroundColor: colors.primary, + fill: false, + borderDash: [5, 5], + pointRadius: 8, + pointHoverRadius: 10, + }, + { + label: 'dataset - individual point sizes', + data: [ + this.random(), + this.random(), + this.random(), + this.random(), + this.random(), + this.random(), + ], + borderColor: colors.dangerLight, + backgroundColor: colors.dangerLight, + fill: false, + borderDash: [5, 5], + pointRadius: 8, + pointHoverRadius: 10, + }, + { + label: 'dataset - large pointHoverRadius', + data: [ + this.random(), + this.random(), + this.random(), + this.random(), + this.random(), + this.random(), + ], + borderColor: colors.info, + backgroundColor: colors.info, + fill: false, + pointRadius: 8, + pointHoverRadius: 10, + }, + { + label: 'dataset - large pointHitRadius', + data: [ + this.random(), + this.random(), + this.random(), + this.random(), + this.random(), + this.random(), + ], + borderColor: colors.success, + backgroundColor: colors.success, + fill: false, + pointRadius: 8, + pointHoverRadius: 10, + }, + ], + }; + + this.options = { + responsive: true, + maintainAspectRatio: false, + legend: { + position: 'bottom', + labels: { + fontColor: chartjs.textColor, + }, + }, + hover: { + mode: 'index', + }, + scales: { + xAxes: [ + { + display: true, + scaleLabel: { + display: true, + labelString: 'Month', + }, + gridLines: { + display: true, + color: chartjs.axisLineColor, + }, + ticks: { + fontColor: chartjs.textColor, + }, + }, + ], + yAxes: [ + { + display: true, + scaleLabel: { + display: true, + labelString: 'Value', + }, + gridLines: { + display: true, + color: chartjs.axisLineColor, + }, + ticks: { + fontColor: chartjs.textColor, + }, + }, + ], + }, + }; + }); + } + + private random() { + return Math.round(Math.random() * 100); + } + + ngOnDestroy(): void { + this.themeSubscription.unsubscribe(); + } +} diff --git a/packages/admin-web-angular/src/app/pages/charts/chartjs/chartjs-pie.component.ts b/packages/admin-web-angular/src/app/pages/charts/chartjs/chartjs-pie.component.ts new file mode 100644 index 0000000..b05648e --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/charts/chartjs/chartjs-pie.component.ts @@ -0,0 +1,59 @@ +import { Component, OnDestroy } from '@angular/core'; +import { NbThemeService } from '@nebular/theme'; + +@Component({ + selector: 'ngx-chartjs-pie', + template: ` `, +}) +export class ChartjsPieComponent implements OnDestroy { + data: any; + options: any; + themeSubscription: any; + + constructor(private theme: NbThemeService) { + this.themeSubscription = this.theme.getJsTheme().subscribe((config) => { + const colors: any = config.variables; + const chartjs: any = config.variables.chartjs; + + this.data = { + labels: ['Download Sales', 'In-Store Sales', 'Mail Sales'], + datasets: [ + { + data: [300, 500, 100], + backgroundColor: [ + colors.primaryLight, + colors.infoLight, + colors.successLight, + ], + }, + ], + }; + + this.options = { + maintainAspectRatio: false, + responsive: true, + scales: { + xAxes: [ + { + display: false, + }, + ], + yAxes: [ + { + display: false, + }, + ], + }, + legend: { + labels: { + fontColor: chartjs.textColor, + }, + }, + }; + }); + } + + ngOnDestroy(): void { + this.themeSubscription.unsubscribe(); + } +} diff --git a/packages/admin-web-angular/src/app/pages/charts/chartjs/chartjs-radar.component.ts b/packages/admin-web-angular/src/app/pages/charts/chartjs/chartjs-radar.component.ts new file mode 100644 index 0000000..bb7d0f9 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/charts/chartjs/chartjs-radar.component.ts @@ -0,0 +1,80 @@ +import { Component, OnDestroy } from '@angular/core'; +import { NbColorHelper, NbThemeService } from '@nebular/theme'; + +@Component({ + selector: 'ngx-chartjs-radar', + template: ` + + `, +}) +export class ChartjsRadarComponent implements OnDestroy { + options: any; + data: {}; + themeSubscription: any; + + constructor(private theme: NbThemeService) { + this.themeSubscription = this.theme.getJsTheme().subscribe((config) => { + const colors: any = config.variables; + const chartjs: any = config.variables.chartjs; + + this.data = { + labels: [ + 'Eating', + 'Drinking', + 'Sleeping', + 'Designing', + 'Coding', + 'Cycling', + 'Running', + ], + datasets: [ + { + data: [65, 59, 90, 81, 56, 55, 40], + label: 'Series A', + borderColor: colors.danger, + backgroundColor: NbColorHelper.hexToRgbA( + colors.dangerLight, + 0.5 + ), + }, + { + data: [28, 48, 40, 19, 96, 27, 100], + label: 'Series B', + borderColor: colors.warning, + backgroundColor: NbColorHelper.hexToRgbA( + colors.warningLight, + 0.5 + ), + }, + ], + }; + + this.options = { + responsive: true, + maintainAspectRatio: false, + scaleFontColor: 'white', + legend: { + labels: { + fontColor: chartjs.textColor, + }, + }, + scale: { + pointLabels: { + fontSize: 14, + fontColor: chartjs.textColor, + }, + gridLines: { + color: chartjs.axisLineColor, + }, + angleLines: { + color: chartjs.axisLineColor, + }, + }, + }; + }); + } + + ngOnDestroy(): void { + this.themeSubscription.unsubscribe(); + } +} diff --git a/packages/admin-web-angular/src/app/pages/charts/chartjs/chartjs.component.html b/packages/admin-web-angular/src/app/pages/charts/chartjs/chartjs.component.html new file mode 100644 index 0000000..17a84e5 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/charts/chartjs/chartjs.component.html @@ -0,0 +1,50 @@ +
+
+ + Pie + + + + +
+
+ + Bar + + + + +
+
+ + Line + + + + +
+
+ + Multiple x-axis + + + + +
+
+ + Bar Horizontal + + + + +
+
+ + Radar + + + + +
+
diff --git a/packages/admin-web-angular/src/app/pages/charts/chartjs/chartjs.component.scss b/packages/admin-web-angular/src/app/pages/charts/chartjs/chartjs.component.scss new file mode 100644 index 0000000..a177782 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/charts/chartjs/chartjs.component.scss @@ -0,0 +1,20 @@ +@import '../../../@theme/styles/themes'; + +@include nb-install-component() { + ngx-chartjs-pie, + ngx-chartjs-bar, + ngx-chartjs-line, + ngx-chartjs-multiple-xaxis, + ngx-chartjs-bar-horizontal, + ngx-chartjs-radar { + display: block; + // height: nb-theme(card-height-medium); + width: 100%; + + ::ng-deep chart { + display: block; + height: 100%; + width: 100%; + } + } +} diff --git a/packages/admin-web-angular/src/app/pages/charts/chartjs/chartjs.component.ts b/packages/admin-web-angular/src/app/pages/charts/chartjs/chartjs.component.ts new file mode 100644 index 0000000..e93c7b0 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/charts/chartjs/chartjs.component.ts @@ -0,0 +1,8 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'ngx-chartjs', + styleUrls: ['./chartjs.component.scss'], + templateUrl: './chartjs.component.html', +}) +export class ChartjsComponent {} diff --git a/packages/admin-web-angular/src/app/pages/charts/charts-routing.module.ts b/packages/admin-web-angular/src/app/pages/charts/charts-routing.module.ts new file mode 100644 index 0000000..a67d768 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/charts/charts-routing.module.ts @@ -0,0 +1,40 @@ +import { NgModule } from '@angular/core'; +import { RouterModule, Routes } from '@angular/router'; +import { ChartsComponent } from './charts.component'; +import { EchartsComponent } from './echarts/echarts.component'; +import { D3Component } from './d3/d3.component'; +import { ChartjsComponent } from './chartjs/chartjs.component'; + +const routes: Routes = [ + { + path: '', + component: ChartsComponent, + children: [ + { + path: 'echarts', + component: EchartsComponent, + }, + { + path: 'd3', + component: D3Component, + }, + { + path: 'chartjs', + component: ChartjsComponent, + }, + ], + }, +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule], +}) +export class ChartsRoutingModule {} + +export const routedComponents = [ + ChartsComponent, + EchartsComponent, + D3Component, + ChartjsComponent, +]; diff --git a/packages/admin-web-angular/src/app/pages/charts/charts.component.ts b/packages/admin-web-angular/src/app/pages/charts/charts.component.ts new file mode 100644 index 0000000..60a7ea7 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/charts/charts.component.ts @@ -0,0 +1,7 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'ngx-charts', + template: ` `, +}) +export class ChartsComponent {} diff --git a/packages/admin-web-angular/src/app/pages/charts/charts.module.ts b/packages/admin-web-angular/src/app/pages/charts/charts.module.ts new file mode 100644 index 0000000..dd22abd --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/charts/charts.module.ts @@ -0,0 +1,59 @@ +import { NgModule } from '@angular/core'; +import { NgxEchartsModule } from 'ngx-echarts'; +import { NgxChartsModule } from '@swimlane/ngx-charts'; +import { ChartModule } from 'angular2-chartjs'; +import { ThemeModule } from '../../@theme/theme.module'; +import { ChartsRoutingModule, routedComponents } from './charts-routing.module'; +import { ChartjsBarComponent } from './chartjs/chartjs-bar.component'; +import { ChartjsLineComponent } from './chartjs/chartjs-line.component'; +import { ChartjsPieComponent } from './chartjs/chartjs-pie.component'; +import { ChartjsMultipleXaxisComponent } from './chartjs/chartjs-multiple-xaxis.component'; +import { ChartjsBarHorizontalComponent } from './chartjs/chartjs-bar-horizontal.component'; +import { ChartjsRadarComponent } from './chartjs/chartjs-radar.component'; +import { D3BarComponent } from './d3/d3-bar.component'; +import { D3LineComponent } from './d3/d3-line.component'; +import { D3PieComponent } from './d3/d3-pie.component'; +import { D3AreaStackComponent } from './d3/d3-area-stack.component'; +import { D3PolarComponent } from './d3/d3-polar.component'; +import { D3AdvancedPieComponent } from './d3/d3-advanced-pie.component'; +import { EchartsLineComponent } from './echarts/echarts-line.component'; +import { EchartsPieComponent } from './echarts/echarts-pie.component'; +import { EchartsBarComponent } from './echarts/echarts-bar.component'; +import { EchartsMultipleXaxisComponent } from './echarts/echarts-multiple-xaxis.component'; +import { EchartsAreaStackComponent } from './echarts/echarts-area-stack.component'; +import { EchartsBarAnimationComponent } from './echarts/echarts-bar-animation.component'; +import { EchartsRadarComponent } from './echarts/echarts-radar.component'; + +const components = [ + ChartjsBarComponent, + ChartjsLineComponent, + ChartjsPieComponent, + ChartjsMultipleXaxisComponent, + ChartjsBarHorizontalComponent, + ChartjsRadarComponent, + D3BarComponent, + D3LineComponent, + D3PieComponent, + D3AreaStackComponent, + D3PolarComponent, + D3AdvancedPieComponent, + EchartsLineComponent, + EchartsPieComponent, + EchartsBarComponent, + EchartsMultipleXaxisComponent, + EchartsAreaStackComponent, + EchartsBarAnimationComponent, + EchartsRadarComponent, +]; + +@NgModule({ + imports: [ + ThemeModule, + ChartsRoutingModule, + NgxEchartsModule, + NgxChartsModule, + ChartModule, + ], + declarations: [...routedComponents, ...components], +}) +export class ChartsModule {} diff --git a/packages/admin-web-angular/src/app/pages/charts/d3/d3-advanced-pie.component.ts b/packages/admin-web-angular/src/app/pages/charts/d3/d3-advanced-pie.component.ts new file mode 100644 index 0000000..111facd --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/charts/d3/d3-advanced-pie.component.ts @@ -0,0 +1,50 @@ +import { Component, OnDestroy } from '@angular/core'; +import { NbThemeService } from '@nebular/theme'; + +@Component({ + selector: 'ngx-d3-advanced-pie', + template: ` + + + `, +}) +export class D3AdvancedPieComponent implements OnDestroy { + single = [ + { + name: 'Germany', + value: 8940000, + }, + { + name: 'USA', + value: 5000000, + }, + { + name: 'France', + value: 7200000, + }, + ]; + colorScheme: any; + themeSubscription: any; + + constructor(private theme: NbThemeService) { + this.themeSubscription = this.theme.getJsTheme().subscribe((config) => { + const colors: any = config.variables; + this.colorScheme = { + domain: [ + colors.primaryLight, + colors.infoLight, + colors.successLight, + colors.warningLight, + colors.dangerLight, + ], + }; + }); + } + + ngOnDestroy(): void { + this.themeSubscription.unsubscribe(); + } +} diff --git a/packages/admin-web-angular/src/app/pages/charts/d3/d3-area-stack.component.ts b/packages/admin-web-angular/src/app/pages/charts/d3/d3-area-stack.component.ts new file mode 100644 index 0000000..9b764ff --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/charts/d3/d3-area-stack.component.ts @@ -0,0 +1,93 @@ +import { Component, OnDestroy } from '@angular/core'; +import { NbThemeService } from '@nebular/theme'; + +@Component({ + selector: 'ngx-d3-area-stack', + template: ` + + + `, +}) +export class D3AreaStackComponent implements OnDestroy { + multi = [ + { + name: 'Germany', + series: [ + { + name: '2010', + value: 7300000, + }, + { + name: '2011', + value: 8940000, + }, + ], + }, + { + name: 'USA', + series: [ + { + name: '2010', + value: 7870000, + }, + { + name: '2011', + value: 8270000, + }, + ], + }, + { + name: 'France', + series: [ + { + name: '2010', + value: 5000002, + }, + { + name: '2011', + value: 5800000, + }, + ], + }, + ]; + showLegend = true; + autoScale = true; + showXAxis = true; + showYAxis = true; + showXAxisLabel = true; + showYAxisLabel = true; + xAxisLabel = 'Country'; + yAxisLabel = 'Population'; + colorScheme: any; + themeSubscription: any; + + constructor(private theme: NbThemeService) { + this.themeSubscription = this.theme.getJsTheme().subscribe((config) => { + const colors: any = config.variables; + this.colorScheme = { + domain: [ + colors.primaryLight, + colors.infoLight, + colors.successLight, + colors.warningLight, + colors.dangerLight, + ], + }; + }); + } + + ngOnDestroy(): void { + this.themeSubscription.unsubscribe(); + } +} diff --git a/packages/admin-web-angular/src/app/pages/charts/d3/d3-bar.component.ts b/packages/admin-web-angular/src/app/pages/charts/d3/d3-bar.component.ts new file mode 100644 index 0000000..f21d599 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/charts/d3/d3-bar.component.ts @@ -0,0 +1,51 @@ +import { Component, OnDestroy } from '@angular/core'; +import { NbThemeService } from '@nebular/theme'; + +@Component({ + selector: 'ngx-d3-bar', + template: ` + + + `, +}) +export class D3BarComponent implements OnDestroy { + results = [ + { name: 'Germany', value: 8940 }, + { name: 'USA', value: 5000 }, + { name: 'France', value: 7200 }, + ]; + showLegend = true; + showXAxis = true; + showYAxis = true; + xAxisLabel = 'Country'; + yAxisLabel = 'Population'; + colorScheme: any; + themeSubscription: any; + + constructor(private theme: NbThemeService) { + this.themeSubscription = this.theme.getJsTheme().subscribe((config) => { + const colors: any = config.variables; + this.colorScheme = { + domain: [ + colors.primaryLight, + colors.infoLight, + colors.successLight, + colors.warningLight, + colors.dangerLight, + ], + }; + }); + } + + ngOnDestroy(): void { + this.themeSubscription.unsubscribe(); + } +} diff --git a/packages/admin-web-angular/src/app/pages/charts/d3/d3-line.component.ts b/packages/admin-web-angular/src/app/pages/charts/d3/d3-line.component.ts new file mode 100644 index 0000000..9d6be7a --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/charts/d3/d3-line.component.ts @@ -0,0 +1,91 @@ +import { Component, OnDestroy } from '@angular/core'; +import { NbThemeService } from '@nebular/theme'; + +@Component({ + selector: 'ngx-d3-line', + template: ` + + + `, +}) +export class D3LineComponent implements OnDestroy { + multi = [ + { + name: 'Germany', + series: [ + { + name: '2010', + value: 7300, + }, + { + name: '2011', + value: 8940, + }, + ], + }, + { + name: 'USA', + series: [ + { + name: '2010', + value: 7870, + }, + { + name: '2011', + value: 8270, + }, + ], + }, + { + name: 'France', + series: [ + { + name: '2010', + value: 5002, + }, + { + name: '2011', + value: 5800, + }, + ], + }, + ]; + showLegend = true; + showXAxis = true; + showYAxis = true; + showXAxisLabel = true; + xAxisLabel = 'Country'; + showYAxisLabel = true; + yAxisLabel = 'Population'; + colorScheme: any; + themeSubscription: any; + + constructor(private theme: NbThemeService) { + this.themeSubscription = this.theme.getJsTheme().subscribe((config) => { + const colors: any = config.variables; + this.colorScheme = { + domain: [ + colors.primaryLight, + colors.infoLight, + colors.successLight, + colors.warningLight, + colors.dangerLight, + ], + }; + }); + } + + ngOnDestroy(): void { + this.themeSubscription.unsubscribe(); + } +} diff --git a/packages/admin-web-angular/src/app/pages/charts/d3/d3-pie.component.ts b/packages/admin-web-angular/src/app/pages/charts/d3/d3-pie.component.ts new file mode 100644 index 0000000..df96ce4 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/charts/d3/d3-pie.component.ts @@ -0,0 +1,45 @@ +import { Component, OnDestroy } from '@angular/core'; +import { NbThemeService } from '@nebular/theme'; + +@Component({ + selector: 'ngx-d3-pie', + template: ` + + + `, +}) +export class D3PieComponent implements OnDestroy { + results = [ + { name: 'Germany', value: 8940 }, + { name: 'USA', value: 5000 }, + { name: 'France', value: 7200 }, + ]; + showLegend = true; + showLabels = true; + colorScheme: any; + themeSubscription: any; + + constructor(private theme: NbThemeService) { + this.themeSubscription = this.theme.getJsTheme().subscribe((config) => { + const colors: any = config.variables; + this.colorScheme = { + domain: [ + colors.primaryLight, + colors.infoLight, + colors.successLight, + colors.warningLight, + colors.dangerLight, + ], + }; + }); + } + + ngOnDestroy(): void { + this.themeSubscription.unsubscribe(); + } +} diff --git a/packages/admin-web-angular/src/app/pages/charts/d3/d3-polar.component.ts b/packages/admin-web-angular/src/app/pages/charts/d3/d3-polar.component.ts new file mode 100644 index 0000000..7ccf1be --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/charts/d3/d3-polar.component.ts @@ -0,0 +1,105 @@ +import { Component, OnDestroy } from '@angular/core'; +import { NbThemeService } from '@nebular/theme'; + +@Component({ + selector: 'ngx-d3-polar', + template: ` + + + `, +}) +export class D3PolarComponent implements OnDestroy { + multi = [ + { + name: 'Germany', + series: [ + { + name: '1990', + value: 31476, + }, + { + name: '2000', + value: 36953, + }, + { + name: '2010', + value: 40632, + }, + ], + }, + { + name: 'USA', + series: [ + { + name: '1990', + value: 37060, + }, + { + name: '2000', + value: 45986, + }, + { + name: '2010', + value: 49737, + }, + ], + }, + { + name: 'France', + series: [ + { + name: '1990', + value: 29476, + }, + { + name: '2000', + value: 34774, + }, + { + name: '2010', + value: 36240, + }, + ], + }, + ]; + showLegend = true; + autoScale = true; + showXAxis = true; + showYAxis = true; + showXAxisLabel = true; + showYAxisLabel = true; + xAxisLabel = 'Country'; + yAxisLabel = 'Population'; + colorScheme: any; + themeSubscription: any; + + constructor(private theme: NbThemeService) { + this.themeSubscription = this.theme.getJsTheme().subscribe((config) => { + const colors: any = config.variables; + this.colorScheme = { + domain: [ + colors.primaryLight, + colors.infoLight, + colors.successLight, + colors.warningLight, + colors.dangerLight, + ], + }; + }); + } + + ngOnDestroy(): void { + this.themeSubscription.unsubscribe(); + } +} diff --git a/packages/admin-web-angular/src/app/pages/charts/d3/d3.component.html b/packages/admin-web-angular/src/app/pages/charts/d3/d3.component.html new file mode 100644 index 0000000..f0a885b --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/charts/d3/d3.component.html @@ -0,0 +1,42 @@ +
+
+ + Pie + + + + +
+
+ + Bar + + + + +
+
+ + Line + + + + +
+
+ + Advanced Pie + + + + +
+
+ + Area Chart + + + + +
+
diff --git a/packages/admin-web-angular/src/app/pages/charts/d3/d3.component.scss b/packages/admin-web-angular/src/app/pages/charts/d3/d3.component.scss new file mode 100644 index 0000000..bf7ff8a --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/charts/d3/d3.component.scss @@ -0,0 +1,40 @@ +// @import '../../../@theme/styles/themes'; + +// @include nb-install-component() { +// ngx-d3-bar, +// ngx-d3-pie, +// ngx-d3-advanced-pie, +// ngx-d3-area-stack, +// ngx-d3-line, +// ngx-d3-polar { +// display: block; +// width: 100%; +// height: nb-theme(card-height-medium); + +// ::ng-deep { +// .pie-label { +// fill: #2a2a2a; +// } + +// .grid-line-path { +// stroke: nb-theme(separator); +// } + +// text { +// fill: #2a2a2a; +// } + +// .chart-legend { +// .legend-labels { +// background: nb-theme(color-bg); +// } +// .legend-label { +// color: #2a2a2a; +// .active .legend-label-text { +// color: #2a2a2a; +// } +// } +// } +// } +// } +// } diff --git a/packages/admin-web-angular/src/app/pages/charts/d3/d3.component.ts b/packages/admin-web-angular/src/app/pages/charts/d3/d3.component.ts new file mode 100644 index 0000000..3ef32f7 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/charts/d3/d3.component.ts @@ -0,0 +1,8 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'ngx-d3', + styleUrls: ['./d3.component.scss'], + templateUrl: './d3.component.html', +}) +export class D3Component {} diff --git a/packages/admin-web-angular/src/app/pages/charts/echarts/echarts-area-stack.component.ts b/packages/admin-web-angular/src/app/pages/charts/echarts/echarts-area-stack.component.ts new file mode 100644 index 0000000..6ba31d5 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/charts/echarts/echarts-area-stack.component.ts @@ -0,0 +1,156 @@ +import { AfterViewInit, Component, OnDestroy } from '@angular/core'; +import { NbThemeService } from '@nebular/theme'; + +@Component({ + selector: 'ngx-echarts-area-stack', + template: `
`, +}) +export class EchartsAreaStackComponent implements AfterViewInit, OnDestroy { + options: any = {}; + themeSubscription: any; + + constructor(private theme: NbThemeService) {} + + ngAfterViewInit() { + this.themeSubscription = this.theme.getJsTheme().subscribe((config) => { + const colors: any = config.variables; + const echarts: any = config.variables.echarts; + + this.options = { + backgroundColor: echarts.bg, + color: [ + colors.warningLight, + colors.infoLight, + colors.dangerLight, + colors.successLight, + colors.primaryLight, + ], + tooltip: { + trigger: 'axis', + axisPointer: { + type: 'cross', + label: { + backgroundColor: echarts.tooltipBackgroundColor, + }, + }, + }, + legend: { + data: [ + 'Mail marketing', + 'Affiliate advertising', + 'Video ad', + 'Direct interview', + 'Search engine', + ], + textStyle: { + color: echarts.textColor, + }, + }, + grid: { + left: '3%', + right: '4%', + bottom: '3%', + containLabel: true, + }, + xAxis: [ + { + type: 'category', + boundaryGap: false, + data: [ + 'Monday', + 'Tuesday', + 'Wednesday', + 'Thursday', + 'Friday', + 'Saturday', + 'Sunday', + ], + axisTick: { + alignWithLabel: true, + }, + axisLine: { + lineStyle: { + color: echarts.axisLineColor, + }, + }, + axisLabel: { + textStyle: { + color: echarts.textColor, + }, + }, + }, + ], + yAxis: [ + { + type: 'value', + axisLine: { + lineStyle: { + color: echarts.axisLineColor, + }, + }, + splitLine: { + lineStyle: { + color: echarts.splitLineColor, + }, + }, + axisLabel: { + textStyle: { + color: echarts.textColor, + }, + }, + }, + ], + series: [ + { + name: 'Mail marketing', + type: 'line', + stack: 'Total amount', + areaStyle: { normal: { opacity: echarts.areaOpacity } }, + data: [120, 132, 101, 134, 90, 230, 210], + }, + { + name: 'Affiliate advertising', + type: 'line', + stack: 'Total amount', + areaStyle: { normal: { opacity: echarts.areaOpacity } }, + data: [220, 182, 191, 234, 290, 330, 310], + }, + { + name: 'Video ad', + type: 'line', + stack: 'Total amount', + areaStyle: { normal: { opacity: echarts.areaOpacity } }, + data: [150, 232, 201, 154, 190, 330, 410], + }, + { + name: 'Direct interview', + type: 'line', + stack: 'Total amount', + areaStyle: { normal: { opacity: echarts.areaOpacity } }, + data: [320, 332, 301, 334, 390, 330, 320], + }, + { + name: 'Search engine', + type: 'line', + stack: 'Total amount', + label: { + normal: { + show: true, + position: 'top', + textStyle: { + color: echarts.textColor, + }, + }, + }, + areaStyle: { normal: { opacity: echarts.areaOpacity } }, + data: [820, 932, 901, 934, 1290, 1330, 1320], + }, + ], + }; + }); + } + + ngOnDestroy(): void { + this.themeSubscription.unsubscribe(); + } +} diff --git a/packages/admin-web-angular/src/app/pages/charts/echarts/echarts-bar-animation.component.ts b/packages/admin-web-angular/src/app/pages/charts/echarts/echarts-bar-animation.component.ts new file mode 100644 index 0000000..aceeb55 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/charts/echarts/echarts-bar-animation.component.ts @@ -0,0 +1,106 @@ +import { AfterViewInit, Component, OnDestroy } from '@angular/core'; +import { NbThemeService } from '@nebular/theme'; + +@Component({ + selector: 'ngx-echarts-bar-animation', + template: `
`, +}) +export class EchartsBarAnimationComponent implements AfterViewInit, OnDestroy { + options: any = {}; + themeSubscription: any; + + constructor(private theme: NbThemeService) {} + + ngAfterViewInit() { + this.themeSubscription = this.theme.getJsTheme().subscribe((config) => { + const xAxisData = []; + const data1 = []; + const data2 = []; + + const colors: any = config.variables; + const echarts: any = config.variables.echarts; + + this.options = { + backgroundColor: echarts.bg, + color: [colors.primaryLight, colors.infoLight], + legend: { + data: ['bar', 'bar2'], + align: 'left', + textStyle: { + color: echarts.textColor, + }, + }, + xAxis: [ + { + data: xAxisData, + silent: false, + axisTick: { + alignWithLabel: true, + }, + axisLine: { + lineStyle: { + color: echarts.axisLineColor, + }, + }, + axisLabel: { + textStyle: { + color: echarts.textColor, + }, + }, + }, + ], + yAxis: [ + { + axisLine: { + lineStyle: { + color: echarts.axisLineColor, + }, + }, + splitLine: { + lineStyle: { + color: echarts.splitLineColor, + }, + }, + axisLabel: { + textStyle: { + color: echarts.textColor, + }, + }, + }, + ], + series: [ + { + name: 'bar', + type: 'bar', + data: data1, + animationDelay: (idx) => { + return idx * 10; + }, + }, + { + name: 'bar2', + type: 'bar', + data: data2, + animationDelay: (idx) => { + return idx * 10 + 100; + }, + }, + ], + animationEasing: 'elasticOut', + animationDelayUpdate: (idx) => { + return idx * 5; + }, + }; + + for (let i = 0; i < 100; i++) { + xAxisData.push('Category ' + i); + data1.push((Math.sin(i / 5) * (i / 5 - 10) + i / 6) * 5); + data2.push((Math.cos(i / 5) * (i / 5 - 10) + i / 6) * 5); + } + }); + } + + ngOnDestroy(): void { + this.themeSubscription.unsubscribe(); + } +} diff --git a/packages/admin-web-angular/src/app/pages/charts/echarts/echarts-bar.component.ts b/packages/admin-web-angular/src/app/pages/charts/echarts/echarts-bar.component.ts new file mode 100644 index 0000000..0b5b453 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/charts/echarts/echarts-bar.component.ts @@ -0,0 +1,88 @@ +import { AfterViewInit, Component, OnDestroy } from '@angular/core'; +import { NbThemeService } from '@nebular/theme'; + +@Component({ + selector: 'ngx-echarts-bar', + template: `
`, +}) +export class EchartsBarComponent implements AfterViewInit, OnDestroy { + options: any = {}; + themeSubscription: any; + + constructor(private theme: NbThemeService) {} + + ngAfterViewInit() { + this.themeSubscription = this.theme.getJsTheme().subscribe((config) => { + const colors: any = config.variables; + const echarts: any = config.variables.echarts; + + this.options = { + backgroundColor: echarts.bg, + color: [colors.primaryLight], + tooltip: { + trigger: 'axis', + axisPointer: { + type: 'shadow', + }, + }, + grid: { + left: '3%', + right: '4%', + bottom: '3%', + containLabel: true, + }, + xAxis: [ + { + type: 'category', + data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'], + axisTick: { + alignWithLabel: true, + }, + axisLine: { + lineStyle: { + color: echarts.axisLineColor, + }, + }, + axisLabel: { + textStyle: { + color: echarts.textColor, + }, + }, + }, + ], + yAxis: [ + { + type: 'value', + axisLine: { + lineStyle: { + color: echarts.axisLineColor, + }, + }, + splitLine: { + lineStyle: { + color: echarts.splitLineColor, + }, + }, + axisLabel: { + textStyle: { + color: echarts.textColor, + }, + }, + }, + ], + series: [ + { + name: 'Score', + type: 'bar', + barWidth: '60%', + data: [10, 52, 200, 334, 390, 330, 220], + }, + ], + }; + }); + } + + ngOnDestroy(): void { + this.themeSubscription.unsubscribe(); + } +} diff --git a/packages/admin-web-angular/src/app/pages/charts/echarts/echarts-line.component.ts b/packages/admin-web-angular/src/app/pages/charts/echarts/echarts-line.component.ts new file mode 100644 index 0000000..169ad72 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/charts/echarts/echarts-line.component.ts @@ -0,0 +1,112 @@ +import { AfterViewInit, Component, OnDestroy } from '@angular/core'; +import { NbThemeService } from '@nebular/theme'; + +@Component({ + selector: 'ngx-echarts-line', + template: `
`, +}) +export class EchartsLineComponent implements AfterViewInit, OnDestroy { + options: any = {}; + themeSubscription: any; + + constructor(private theme: NbThemeService) {} + + ngAfterViewInit() { + this.themeSubscription = this.theme.getJsTheme().subscribe((config) => { + const colors: any = config.variables; + const echarts: any = config.variables.echarts; + + this.options = { + backgroundColor: echarts.bg, + color: [colors.danger, colors.primary, colors.info], + tooltip: { + trigger: 'item', + formatter: '{a}
{b} : {c}', + }, + legend: { + left: 'left', + data: ['Line 1', 'Line 2', 'Line 3'], + textStyle: { + color: echarts.textColor, + }, + }, + xAxis: [ + { + type: 'category', + data: ['1', '2', '3', '4', '5', '6', '7', '8', '9'], + axisTick: { + alignWithLabel: true, + }, + axisLine: { + lineStyle: { + color: echarts.axisLineColor, + }, + }, + axisLabel: { + textStyle: { + color: echarts.textColor, + }, + }, + }, + ], + yAxis: [ + { + type: 'log', + axisLine: { + lineStyle: { + color: echarts.axisLineColor, + }, + }, + splitLine: { + lineStyle: { + color: echarts.splitLineColor, + }, + }, + axisLabel: { + textStyle: { + color: echarts.textColor, + }, + }, + }, + ], + grid: { + left: '3%', + right: '4%', + bottom: '3%', + containLabel: true, + }, + series: [ + { + name: 'Line 1', + type: 'line', + data: [1, 3, 9, 27, 81, 247, 741, 2223, 6669], + }, + { + name: 'Line 2', + type: 'line', + data: [1, 2, 4, 8, 16, 32, 64, 128, 256], + }, + { + name: 'Line 3', + type: 'line', + data: [ + 1 / 2, + 1 / 4, + 1 / 8, + 1 / 16, + 1 / 32, + 1 / 64, + 1 / 128, + 1 / 256, + 1 / 512, + ], + }, + ], + }; + }); + } + + ngOnDestroy(): void { + this.themeSubscription.unsubscribe(); + } +} diff --git a/packages/admin-web-angular/src/app/pages/charts/echarts/echarts-multiple-xaxis.component.ts b/packages/admin-web-angular/src/app/pages/charts/echarts/echarts-multiple-xaxis.component.ts new file mode 100644 index 0000000..52d7acc --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/charts/echarts/echarts-multiple-xaxis.component.ts @@ -0,0 +1,196 @@ +import { AfterViewInit, Component, OnDestroy } from '@angular/core'; +import { NbThemeService } from '@nebular/theme'; + +@Component({ + selector: 'ngx-echarts-multiple-xaxis', + template: `
`, +}) +export class EchartsMultipleXaxisComponent implements AfterViewInit, OnDestroy { + options: any = {}; + themeSubscription: any; + + constructor(private theme: NbThemeService) {} + + ngAfterViewInit() { + this.themeSubscription = this.theme.getJsTheme().subscribe((config) => { + const colors: any = config.variables; + const echarts: any = config.variables.echarts; + + this.options = { + backgroundColor: echarts.bg, + color: [colors.success, colors.info], + tooltip: { + trigger: 'none', + axisPointer: { + type: 'cross', + }, + }, + legend: { + data: ['2015 Precipitation', '2016 Precipitation'], + textStyle: { + color: echarts.textColor, + }, + }, + grid: { + top: 70, + bottom: 50, + }, + xAxis: [ + { + type: 'category', + axisTick: { + alignWithLabel: true, + }, + axisLine: { + onZero: false, + lineStyle: { + color: colors.info, + }, + }, + axisLabel: { + textStyle: { + color: echarts.textColor, + }, + }, + axisPointer: { + label: { + formatter: (params) => { + return ( + 'Precipitation ' + + params.value + + (params.seriesData.length + ? ':' + params.seriesData[0].data + : '') + ); + }, + }, + }, + data: [ + '2016-1', + '2016-2', + '2016-3', + '2016-4', + '2016-5', + '2016-6', + '2016-7', + '2016-8', + '2016-9', + '2016-10', + '2016-11', + '2016-12', + ], + }, + { + type: 'category', + axisTick: { + alignWithLabel: true, + }, + axisLine: { + onZero: false, + lineStyle: { + color: colors.success, + }, + }, + axisLabel: { + textStyle: { + color: echarts.textColor, + }, + }, + axisPointer: { + label: { + formatter: (params) => { + return ( + 'Precipitation ' + + params.value + + (params.seriesData.length + ? ':' + params.seriesData[0].data + : '') + ); + }, + }, + }, + data: [ + '2015-1', + '2015-2', + '2015-3', + '2015-4', + '2015-5', + '2015-6', + '2015-7', + '2015-8', + '2015-9', + '2015-10', + '2015-11', + '2015-12', + ], + }, + ], + yAxis: [ + { + type: 'value', + axisLine: { + lineStyle: { + color: echarts.axisLineColor, + }, + }, + splitLine: { + lineStyle: { + color: echarts.splitLineColor, + }, + }, + axisLabel: { + textStyle: { + color: echarts.textColor, + }, + }, + }, + ], + series: [ + { + name: '2015 Precipitation', + type: 'line', + xAxisIndex: 1, + smooth: true, + data: [ + 2.6, + 5.9, + 9.0, + 26.4, + 28.7, + 70.7, + 175.6, + 182.2, + 48.7, + 18.8, + 6.0, + 2.3, + ], + }, + { + name: '2016 Precipitation', + type: 'line', + smooth: true, + data: [ + 3.9, + 5.9, + 11.1, + 18.7, + 48.3, + 69.2, + 231.6, + 46.6, + 55.4, + 18.4, + 10.3, + 0.7, + ], + }, + ], + }; + }); + } + + ngOnDestroy(): void { + this.themeSubscription.unsubscribe(); + } +} diff --git a/packages/admin-web-angular/src/app/pages/charts/echarts/echarts-pie.component.ts b/packages/admin-web-angular/src/app/pages/charts/echarts/echarts-pie.component.ts new file mode 100644 index 0000000..c9f5d69 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/charts/echarts/echarts-pie.component.ts @@ -0,0 +1,83 @@ +import { AfterViewInit, Component, OnDestroy } from '@angular/core'; +import { NbThemeService } from '@nebular/theme'; + +@Component({ + selector: 'ngx-echarts-pie', + template: `
`, +}) +export class EchartsPieComponent implements AfterViewInit, OnDestroy { + options: any = {}; + themeSubscription: any; + + constructor(private theme: NbThemeService) {} + + ngAfterViewInit() { + this.themeSubscription = this.theme.getJsTheme().subscribe((config) => { + const colors = config.variables; + const echarts: any = config.variables.echarts; + + this.options = { + backgroundColor: echarts.bg, + color: [ + colors.warningLight, + colors.infoLight, + colors.dangerLight, + colors.successLight, + colors.primaryLight, + ], + tooltip: { + trigger: 'item', + formatter: '{a}
{b} : {c} ({d}%)', + }, + legend: { + orient: 'vertical', + left: 'left', + data: ['USA', 'Germany', 'France', 'Canada', 'Russia'], + textStyle: { + color: echarts.textColor, + }, + }, + series: [ + { + name: 'Countries', + type: 'pie', + radius: '80%', + center: ['50%', '50%'], + data: [ + { value: 335, name: 'Germany' }, + { value: 310, name: 'France' }, + { value: 234, name: 'Canada' }, + { value: 135, name: 'Russia' }, + { value: 1548, name: 'USA' }, + ], + itemStyle: { + emphasis: { + shadowBlur: 10, + shadowOffsetX: 0, + shadowColor: echarts.itemHoverShadowColor, + }, + }, + label: { + normal: { + textStyle: { + color: echarts.textColor, + }, + }, + }, + labelLine: { + normal: { + lineStyle: { + color: echarts.axisLineColor, + }, + }, + }, + }, + ], + }; + }); + } + + ngOnDestroy(): void { + this.themeSubscription.unsubscribe(); + } +} diff --git a/packages/admin-web-angular/src/app/pages/charts/echarts/echarts-radar.component.ts b/packages/admin-web-angular/src/app/pages/charts/echarts/echarts-radar.component.ts new file mode 100644 index 0000000..40dbf43 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/charts/echarts/echarts-radar.component.ts @@ -0,0 +1,86 @@ +import { AfterViewInit, Component, OnDestroy } from '@angular/core'; +import { NbThemeService } from '@nebular/theme'; + +@Component({ + selector: 'ngx-echarts-radar', + template: `
`, +}) +export class EchartsRadarComponent implements AfterViewInit, OnDestroy { + options: any = {}; + themeSubscription: any; + + constructor(private theme: NbThemeService) {} + + ngAfterViewInit() { + this.themeSubscription = this.theme.getJsTheme().subscribe((config) => { + const colors: any = config.variables; + const echarts: any = config.variables.echarts; + + this.options = { + backgroundColor: echarts.bg, + color: [colors.danger, colors.warning], + tooltip: {}, + legend: { + data: ['Allocated Budget', 'Actual Spending'], + textStyle: { + color: echarts.textColor, + }, + }, + radar: { + name: { + textStyle: { + color: echarts.textColor, + }, + }, + indicator: [ + { name: 'Sales', max: 6500 }, + { name: 'Administration', max: 16000 }, + { name: 'Information Techology', max: 30000 }, + { name: 'Customer Support', max: 38000 }, + { name: 'Development', max: 52000 }, + { name: 'Marketing', max: 25000 }, + ], + splitArea: { + areaStyle: { + color: 'transparent', + }, + }, + }, + series: [ + { + name: 'Budget vs Spending', + type: 'radar', + data: [ + { + value: [ + 4300, + 10000, + 28000, + 35000, + 50000, + 19000, + ], + name: 'Allocated Budget', + }, + { + value: [ + 5000, + 14000, + 28000, + 31000, + 42000, + 21000, + ], + name: 'Actual Spending', + }, + ], + }, + ], + }; + }); + } + + ngOnDestroy(): void { + this.themeSubscription.unsubscribe(); + } +} diff --git a/packages/admin-web-angular/src/app/pages/charts/echarts/echarts.component.html b/packages/admin-web-angular/src/app/pages/charts/echarts/echarts.component.html new file mode 100644 index 0000000..493c8d6 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/charts/echarts/echarts.component.html @@ -0,0 +1,58 @@ +
+
+ + Pie + + + + +
+
+ + Bar + + + + +
+
+ + Line + + + + +
+
+ + Multiple x-axis + + + + +
+
+ + Area Stack + + + + +
+
+ + Bar Animation + + + + +
+
+ + Radar + + + + +
+
diff --git a/packages/admin-web-angular/src/app/pages/charts/echarts/echarts.component.scss b/packages/admin-web-angular/src/app/pages/charts/echarts/echarts.component.scss new file mode 100644 index 0000000..ba6f096 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/charts/echarts/echarts.component.scss @@ -0,0 +1,20 @@ +@import '../../../@theme/styles/themes'; + +@include nb-install-component() { + ngx-echarts-pie, + ngx-echarts-bar, + ngx-echarts-line, + ngx-echarts-multiple-xaxis, + ngx-echarts-area-stack, + ngx-echarts-bar-animation, + ngx-echarts-radar { + display: block; + // height: nb-theme(card-height-medium); + width: 100%; + } + + ::ng-deep .echart { + height: 100%; + width: 100%; + } +} diff --git a/packages/admin-web-angular/src/app/pages/charts/echarts/echarts.component.ts b/packages/admin-web-angular/src/app/pages/charts/echarts/echarts.component.ts new file mode 100644 index 0000000..e1a7f09 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/charts/echarts/echarts.component.ts @@ -0,0 +1,8 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'ngx-echarts', + styleUrls: ['./echarts.component.scss'], + templateUrl: './echarts.component.html', +}) +export class EchartsComponent {} diff --git a/packages/admin-web-angular/src/app/pages/components/components-routing.module.ts b/packages/admin-web-angular/src/app/pages/components/components-routing.module.ts new file mode 100644 index 0000000..af92efd --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/components/components-routing.module.ts @@ -0,0 +1,35 @@ +import { NgModule } from '@angular/core'; +import { RouterModule, Routes } from '@angular/router'; + +import { ComponentsComponent } from './components.component'; +import { TreeComponent } from './tree/tree.component'; +import { NotificationsComponent } from './notifications/notifications.component'; + +const routes: Routes = [ + { + path: '', + component: ComponentsComponent, + children: [ + { + path: 'tree', + component: TreeComponent, + }, + { + path: 'notifications', + component: NotificationsComponent, + }, + ], + }, +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule], +}) +export class ComponentsRoutingModule {} + +export const routedComponents = [ + ComponentsComponent, + TreeComponent, + NotificationsComponent, +]; diff --git a/packages/admin-web-angular/src/app/pages/components/components.component.ts b/packages/admin-web-angular/src/app/pages/components/components.component.ts new file mode 100644 index 0000000..d0a3775 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/components/components.component.ts @@ -0,0 +1,7 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'ngx-components', + template: ` `, +}) +export class ComponentsComponent {} diff --git a/packages/admin-web-angular/src/app/pages/components/components.module.ts b/packages/admin-web-angular/src/app/pages/components/components.module.ts new file mode 100644 index 0000000..ba1027d --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/components/components.module.ts @@ -0,0 +1,21 @@ +import { NgModule } from '@angular/core'; + +import { TreeModule } from 'angular-tree-component'; +import { ToasterModule } from 'angular2-toaster'; + +import { ThemeModule } from '../../@theme/theme.module'; +import { + ComponentsRoutingModule, + routedComponents, +} from './components-routing.module'; + +@NgModule({ + imports: [ + ThemeModule, + ComponentsRoutingModule, + TreeModule, + ToasterModule.forRoot(), + ], + declarations: [...routedComponents], +}) +export class ComponentsModule {} diff --git a/packages/admin-web-angular/src/app/pages/components/notifications/notifications.component.html b/packages/admin-web-angular/src/app/pages/components/notifications/notifications.component.html new file mode 100644 index 0000000..238d056 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/components/notifications/notifications.component.html @@ -0,0 +1,154 @@ + + + Toaster configuration + + + + +
+
+
+ + +
+
+ + +
+
+ + +
+
+
+ + +
+
+
+ + +
+
+ +
+
+ + +
+
+ + +
+ +
+ Newest on top +
+
+ Hide on click +
+
+ Prevent arising of duplicate toast +
+
+ Close button +
+
+
+
+ + + + + + +
diff --git a/packages/admin-web-angular/src/app/pages/components/notifications/notifications.component.scss b/packages/admin-web-angular/src/app/pages/components/notifications/notifications.component.scss new file mode 100644 index 0000000..f8117b0 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/components/notifications/notifications.component.scss @@ -0,0 +1,28 @@ +@import '../../../@theme/styles/themes'; +@import '~bootstrap/scss/mixins/breakpoints'; +@import '~@nebular/theme/styles/global/breakpoints'; + +@include nb-install-component() { + nb-card-footer { + padding-bottom: 0.25rem; + + button { + @include nb-ltr(margin, 0 1rem 1rem 0); + @include nb-rtl(margin, 0 0 1rem 1rem); + } + } + + /* stylelint-disable */ + toaster-container ::ng-deep { + #toast-container .toast-close-button { + right: 0; + } + } + /* stylelint-enable */ + + @include media-breakpoint-down(xs) { + .dropdown-toggle { + font-size: 0.75rem; + } + } +} diff --git a/packages/admin-web-angular/src/app/pages/components/notifications/notifications.component.ts b/packages/admin-web-angular/src/app/pages/components/notifications/notifications.component.ts new file mode 100644 index 0000000..089d71e --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/components/notifications/notifications.component.ts @@ -0,0 +1,101 @@ +import { Component } from '@angular/core'; +import { + BodyOutputType, + Toast, + ToasterConfig, + ToasterService, +} from 'angular2-toaster'; + +import 'style-loader!angular2-toaster/toaster.css'; + +@Component({ + selector: 'ngx-notifications', + styleUrls: ['./notifications.component.scss'], + templateUrl: './notifications.component.html', +}) +export class NotificationsComponent { + constructor(private toasterService: ToasterService) {} + + config: ToasterConfig; + + position = 'toast-top-right'; + animationType = 'fade'; + title = 'HI there!'; + content = `I'm cool toaster!`; + timeout = 5000; + toastsLimit = 5; + type = 'default'; + + isNewestOnTop = true; + isHideOnClick = true; + isDuplicatesPrevented = false; + isCloseButton = true; + + types: string[] = ['default', 'info', 'success', 'warning', 'error']; + animations: string[] = [ + 'fade', + 'flyLeft', + 'flyRight', + 'slideDown', + 'slideUp', + ]; + positions: string[] = [ + 'toast-top-full-width', + 'toast-bottom-full-width', + 'toast-top-left', + 'toast-top-center', + 'toast-top-right', + 'toast-bottom-right', + 'toast-bottom-center', + 'toast-bottom-left', + 'toast-center', + ]; + + quotes = [ + { title: null, body: 'We rock at Angular' }, + { title: null, body: 'Titles are not always needed' }, + { title: null, body: 'Toastr rock!' }, + { + title: 'What about nice html?', + body: 'Sure you can!', + }, + ]; + + makeToast() { + this.showToast(this.type, this.title, this.content); + } + + openRandomToast() { + const typeIndex = Math.floor(Math.random() * this.types.length); + const quoteIndex = Math.floor(Math.random() * this.quotes.length); + const type = this.types[typeIndex]; + const quote = this.quotes[quoteIndex]; + + this.showToast(type, quote.title, quote.body); + } + + clearToasts() { + this.toasterService.clear(); + } + + private showToast(type: any, title: string, body: string) { + this.config = new ToasterConfig({ + positionClass: this.position, + timeout: this.timeout, + newestOnTop: this.isNewestOnTop, + tapToDismiss: this.isHideOnClick, + preventDuplicates: this.isDuplicatesPrevented, + animation: this.animationType, + limit: this.toastsLimit, + }); + const toast: Toast = { + type, + title, + body, + timeout: this.timeout, + showCloseButton: this.isCloseButton, + bodyOutputType: BodyOutputType.TrustedHtml, + }; + this.toasterService.popAsync(toast); + } +} diff --git a/packages/admin-web-angular/src/app/pages/components/tree/tree.component.html b/packages/admin-web-angular/src/app/pages/components/tree/tree.component.html new file mode 100644 index 0000000..d839261 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/components/tree/tree.component.html @@ -0,0 +1,10 @@ +
+
+ + Tree + + + + +
+
diff --git a/packages/admin-web-angular/src/app/pages/components/tree/tree.component.scss b/packages/admin-web-angular/src/app/pages/components/tree/tree.component.scss new file mode 100644 index 0000000..be79121 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/components/tree/tree.component.scss @@ -0,0 +1,19 @@ +@import '../../../@theme/styles/themes'; + +@include nb-install-component() { + ::ng-deep .angular-tree-component { + cursor: default; + + .node-wrapper { + .node-content-wrapper { + background: none; + box-shadow: none; + cursor: default; + } + + .toggle-children-wrapper { + cursor: pointer; + } + } + } +} diff --git a/packages/admin-web-angular/src/app/pages/components/tree/tree.component.ts b/packages/admin-web-angular/src/app/pages/components/tree/tree.component.ts new file mode 100644 index 0000000..0af6a9c --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/components/tree/tree.component.ts @@ -0,0 +1,44 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'ngx-tree', + templateUrl: './tree.component.html', + styleUrls: ['./tree.component.scss'], +}) +export class TreeComponent { + nodes = [ + { + name: 'Programming languages by programming paradigm', + children: [ + { + name: 'Object-oriented programming', + children: [ + { + name: 'Java', + }, + { + name: 'C++', + }, + { + name: 'C#', + }, + ], + }, + { + name: 'Prototype-based programming', + children: [ + { + name: 'JavaScript', + }, + { + name: 'CoffeeScript', + }, + { + name: 'Lua', + }, + ], + }, + ], + }, + ]; +} diff --git a/packages/admin-web-angular/src/app/pages/dashboard/dashboard.component.html b/packages/admin-web-angular/src/app/pages/dashboard/dashboard.component.html new file mode 100644 index 0000000..e69de29 diff --git a/packages/admin-web-angular/src/app/pages/dashboard/dashboard.component.scss b/packages/admin-web-angular/src/app/pages/dashboard/dashboard.component.scss new file mode 100644 index 0000000..9e7c99d --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/dashboard/dashboard.component.scss @@ -0,0 +1,22 @@ +@import '../../@theme/styles/themes'; +@import '~bootstrap/scss/mixins/breakpoints'; +@import '~@nebular/theme/styles/global/breakpoints'; + +@include nb-install-component() { + .solar-card nb-card-header { + border: none; + padding-bottom: 0; + } + + @include media-breakpoint-down(sm) { + ngx-traffic { + display: none; + } + } + + // @include media-breakpoint-down(is) { + // ::ng-deep nb-card.large-card { + // height: nb-theme(card-height-medium); + // } + // } +} diff --git a/packages/admin-web-angular/src/app/pages/dashboard/dashboard.component.ts b/packages/admin-web-angular/src/app/pages/dashboard/dashboard.component.ts new file mode 100644 index 0000000..e8ed57f --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/dashboard/dashboard.component.ts @@ -0,0 +1,88 @@ +import { Component, OnDestroy } from '@angular/core'; +import { NbThemeService } from '@nebular/theme'; +import { takeWhile } from 'rxjs/operators'; + +interface CardSettings { + title: string; + iconClass: string; + type: string; +} + +@Component({ + selector: 'ngx-dashboard', + styleUrls: ['./dashboard.component.scss'], + templateUrl: './dashboard.component.html', +}) +export class DashboardComponent implements OnDestroy { + private alive = true; + + lightCard: CardSettings = { + title: 'Light', + iconClass: 'nb-lightbulb', + type: 'primary', + }; + rollerShadesCard: CardSettings = { + title: 'Roller Shades', + iconClass: 'nb-roller-shades', + type: 'success', + }; + wirelessAudioCard: CardSettings = { + title: 'Wireless Audio', + iconClass: 'nb-audio', + type: 'info', + }; + coffeeMakerCard: CardSettings = { + title: 'Coffee Maker', + iconClass: 'nb-coffee-maker', + type: 'warning', + }; + + statusCards: string; + + commonStatusCardsSet: CardSettings[] = [ + this.lightCard, + this.rollerShadesCard, + this.wirelessAudioCard, + this.coffeeMakerCard, + ]; + + statusCardsByThemes: { + default: CardSettings[]; + cosmic: CardSettings[]; + corporate: CardSettings[]; + } = { + default: this.commonStatusCardsSet, + cosmic: this.commonStatusCardsSet, + corporate: [ + { + ...this.lightCard, + type: 'warning', + }, + { + ...this.rollerShadesCard, + type: 'primary', + }, + { + ...this.wirelessAudioCard, + type: 'danger', + }, + { + ...this.coffeeMakerCard, + type: 'secondary', + }, + ], + }; + + constructor(private themeService: NbThemeService) { + this.themeService + .getJsTheme() + .pipe(takeWhile(() => this.alive)) + .subscribe((theme) => { + this.statusCards = this.statusCardsByThemes[theme.name]; + }); + } + + ngOnDestroy() { + this.alive = false; + } +} diff --git a/packages/admin-web-angular/src/app/pages/dashboard/dashboard.module.ts b/packages/admin-web-angular/src/app/pages/dashboard/dashboard.module.ts new file mode 100644 index 0000000..f5104bb --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/dashboard/dashboard.module.ts @@ -0,0 +1,10 @@ +import { NgModule } from '@angular/core'; +import { NgxEchartsModule } from 'ngx-echarts'; +import { ThemeModule } from '../../@theme/theme.module'; +import { DashboardComponent } from './dashboard.component'; + +@NgModule({ + imports: [ThemeModule, NgxEchartsModule], + declarations: [DashboardComponent], +}) +export class DashboardModule {} diff --git a/packages/admin-web-angular/src/app/pages/editors/ckeditor/ckeditor.component.ts b/packages/admin-web-angular/src/app/pages/editors/ckeditor/ckeditor.component.ts new file mode 100644 index 0000000..4550a42 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/editors/ckeditor/ckeditor.component.ts @@ -0,0 +1,21 @@ +import { Component } from '@angular/core'; + +import './ckeditor.loader'; +import 'ckeditor'; + +@Component({ + selector: 'ngx-ckeditor', + template: ` + + + CKEditor + + + + + + `, +}) +export class CKEditorComponent {} diff --git a/packages/admin-web-angular/src/app/pages/editors/ckeditor/ckeditor.loader.ts b/packages/admin-web-angular/src/app/pages/editors/ckeditor/ckeditor.loader.ts new file mode 100644 index 0000000..8232269 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/editors/ckeditor/ckeditor.loader.ts @@ -0,0 +1 @@ +window['CKEDITOR_BASEPATH'] = '//cdn.ckeditor.com/4.6.2/full-all/'; diff --git a/packages/admin-web-angular/src/app/pages/editors/editors-routing.module.ts b/packages/admin-web-angular/src/app/pages/editors/editors-routing.module.ts new file mode 100644 index 0000000..30c5a4b --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/editors/editors-routing.module.ts @@ -0,0 +1,35 @@ +import { NgModule } from '@angular/core'; +import { RouterModule, Routes } from '@angular/router'; + +import { EditorsComponent } from './editors.component'; +import { TinyMCEComponent } from './tiny-mce/tiny-mce.component'; +import { CKEditorComponent } from './ckeditor/ckeditor.component'; + +const routes: Routes = [ + { + path: '', + component: EditorsComponent, + children: [ + { + path: 'tinymce', + component: TinyMCEComponent, + }, + { + path: 'ckeditor', + component: CKEditorComponent, + }, + ], + }, +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule], +}) +export class EditorsRoutingModule {} + +export const routedComponents = [ + EditorsComponent, + TinyMCEComponent, + CKEditorComponent, +]; diff --git a/packages/admin-web-angular/src/app/pages/editors/editors.component.ts b/packages/admin-web-angular/src/app/pages/editors/editors.component.ts new file mode 100644 index 0000000..75d1837 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/editors/editors.component.ts @@ -0,0 +1,7 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'ngx-editors', + template: ` `, +}) +export class EditorsComponent {} diff --git a/packages/admin-web-angular/src/app/pages/editors/editors.module.ts b/packages/admin-web-angular/src/app/pages/editors/editors.module.ts new file mode 100644 index 0000000..37bf910 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/editors/editors.module.ts @@ -0,0 +1,15 @@ +import { NgModule } from '@angular/core'; +import { CKEditorModule } from 'ng2-ckeditor'; + +import { ThemeModule } from '../../@theme/theme.module'; + +import { + EditorsRoutingModule, + routedComponents, +} from './editors-routing.module'; + +@NgModule({ + imports: [ThemeModule, EditorsRoutingModule, CKEditorModule], + declarations: [...routedComponents], +}) +export class EditorsModule {} diff --git a/packages/admin-web-angular/src/app/pages/editors/tiny-mce/tiny-mce.component.ts b/packages/admin-web-angular/src/app/pages/editors/tiny-mce/tiny-mce.component.ts new file mode 100644 index 0000000..230afd0 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/editors/tiny-mce/tiny-mce.component.ts @@ -0,0 +1,16 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'ngx-tiny-mce-page', + template: ` + + + Tiny MCE + + + + + + `, +}) +export class TinyMCEComponent {} diff --git a/packages/admin-web-angular/src/app/pages/forms/form-inputs/form-inputs.component.html b/packages/admin-web-angular/src/app/pages/forms/form-inputs/form-inputs.component.html new file mode 100644 index 0000000..3146d81 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/forms/form-inputs/form-inputs.component.html @@ -0,0 +1,397 @@ +
+
+ + Default Inputs + +
+ +
+
+
+ +
+
+ +
+
+
+ +
+
+ +
+
+ +
+
+ + A block of help text that breaks into a new line and + may extend beyond one line. + +
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+ + + Input Groups + +
+ @ + +
+ +
+ + + + + + + +
+
+ + + + +
+
+ + +
+
+
+ + + Selects + +
+ + +
+
+ + +
+
+
+
+ +
+ + Input Styles + +
+ +
+
+ +
+
+ +
+
+
+ + + Validation States + +
+ +
+
+ +
+
+ +
+
+ Checkbox with Success + Checkbox with Warning + Checkbox with Danger +
+
+ +
+
+ +
+
+ +
+
+
+ + + Checkboxes & Radios + +
+
+ Checkbox 1 + Checkbox 2 + +
+ + +
+
+
+ + + +
+
+ Disabled Checkbox + +
+
+
+
+ + + + Rating +
+ + + + + + + + + {{ starRate }} +
+
+ + + + + + + + + {{ heartRate }} +
+
+
+
+
diff --git a/packages/admin-web-angular/src/app/pages/forms/form-inputs/form-inputs.component.scss b/packages/admin-web-angular/src/app/pages/forms/form-inputs/form-inputs.component.scss new file mode 100644 index 0000000..d55805f --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/forms/form-inputs/form-inputs.component.scss @@ -0,0 +1,128 @@ +// @import '../../../@theme/styles/themes'; +// @import '~bootstrap/scss/mixins/breakpoints'; +// @import '~@nebular/theme/styles/global/breakpoints'; + +// @include nb-install-component() { +// nb-card-body { +// overflow: visible; +// } + +// .input-group { +// margin-bottom: 1rem; +// } + +// .validation-checkboxes { +// display: flex; +// justify-content: space-between; + +// .custom-control { +// margin-left: 1rem; +// } +// } + +// .demo-checkboxes { +// display: flex; +// flex-direction: column; +// margin-bottom: 1rem; +// } + +// .demo-radio { +// display: flex; +// flex-direction: column; +// margin-bottom: 1rem; +// } + +// .demo-disabled-checkbox-radio { +// display: flex; +// flex-direction: column; +// margin-bottom: 1rem; +// } + +// .demo-checkboxes-radio { +// display: flex; +// justify-content: space-between; +// } + +// .demo-rating { +// display: flex; +// justify-content: space-between; +// flex-wrap: wrap; +// } + +// .star { +// font-size: 1.5rem; +// color: nb-theme(color-fg); +// } + +// .filled { +// color: nb-theme(color-fg); +// } + +// // TODO: Replace with the card header styles mixin +// .rating-header { +// line-height: 2rem; +// font-size: 1.25rem; +// font-family: nb-theme(font-secondary); +// font-weight: nb-theme(font-weight-bolder); +// color: #2a2a2a; +// } + +// .current-rate { +// font-size: 1.5rem; +// @include nb-ltr(padding-left, 1rem); +// @include nb-rtl(padding-right, 1rem); +// color: #2a2a2a; +// } + +// .full-name-inputs { +// display: flex; +// } + +// .input-group.has-person-icon { +// position: relative; + +// .form-control { +// padding-left: 3rem; +// } + +// &::before { +// content: '\F47D'; +// font-family: 'Ionicons'; +// font-size: 2rem; +// position: absolute; +// z-index: 100; +// left: 1rem; +// top: 0.25rem; +// } +// } + +// .dropdown { +// min-width: 7rem; +// } + +// .dropdown-menu { +// width: auto; +// } + +// .form-group label { +// padding: 0 0 0.75rem; +// } + +// ngb-rating { +// outline: none; +// } + +// ngb-rating i { +// color: nb-theme(color-success); +// @include nb-for-theme(cosmic) { +// color: nb-theme(color-primary); +// } +// } + +// @include media-breakpoint-down(xs) { +// button:not(.btn-icon) { +// padding: 0.75rem 1rem; +// font-size: 0.75rem; +// } +// } +// } diff --git a/packages/admin-web-angular/src/app/pages/forms/form-inputs/form-inputs.component.ts b/packages/admin-web-angular/src/app/pages/forms/form-inputs/form-inputs.component.ts new file mode 100644 index 0000000..e2f8adb --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/forms/form-inputs/form-inputs.component.ts @@ -0,0 +1,11 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'ngx-form-inputs', + styleUrls: ['./form-inputs.component.scss'], + templateUrl: './form-inputs.component.html', +}) +export class FormInputsComponent { + starRate = 2; + heartRate = 4; +} diff --git a/packages/admin-web-angular/src/app/pages/forms/form-layouts/form-layouts.component.html b/packages/admin-web-angular/src/app/pages/forms/form-layouts/form-layouts.component.html new file mode 100644 index 0000000..22faefa --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/forms/form-layouts/form-layouts.component.html @@ -0,0 +1,302 @@ +
+
+ + Inline form + +
+ +
+
@
+ +
+ Remember me + +
+
+
+
+
+ +
+
+ + Using the Grid + +
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+
+
+ +
+
+
+
+
+ + + Form without labels + +
+
+ +
+
+ +
+
+ +
+ +
+
+
+
+ +
+ + Basic form + +
+
+ + +
+
+ + +
+
+ Check me out +
+ +
+
+
+ + + Block form + +
+
+
+ + +
+
+
+
+ + +
+
+
+
+
+
+ + +
+
+
+
+ + +
+
+
+ +
+
+
+
+ +
+
+ + Horizontal form + +
+
+ +
+ +
+
+
+ +
+ +
+
+
+
+
+ Remember me +
+
+
+
+
+ +
+
+
+
+
+
+
diff --git a/packages/admin-web-angular/src/app/pages/forms/form-layouts/form-layouts.component.scss b/packages/admin-web-angular/src/app/pages/forms/form-layouts/form-layouts.component.scss new file mode 100644 index 0000000..f6a61ba --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/forms/form-layouts/form-layouts.component.scss @@ -0,0 +1,21 @@ +@import '../../../@theme/styles/themes'; + +@include nb-install-component() { + .full-width { + flex: 1; + min-width: 220px; + } + + nb-checkbox { + margin-bottom: 1rem; + } + + .form-inline > * { + @include nb-ltr(margin, 0 1.5rem 1.5rem 0); + @include nb-rtl(margin, 0 0 1.5rem 1.5rem); + } + + nb-card.inline-form-card nb-card-body { + padding-bottom: 0; + } +} diff --git a/packages/admin-web-angular/src/app/pages/forms/form-layouts/form-layouts.component.ts b/packages/admin-web-angular/src/app/pages/forms/form-layouts/form-layouts.component.ts new file mode 100644 index 0000000..bfe33b4 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/forms/form-layouts/form-layouts.component.ts @@ -0,0 +1,8 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'ngx-form-layouts', + styleUrls: ['./form-layouts.component.scss'], + templateUrl: './form-layouts.component.html', +}) +export class FormLayoutsComponent {} diff --git a/packages/admin-web-angular/src/app/pages/forms/forms-routing.module.ts b/packages/admin-web-angular/src/app/pages/forms/forms-routing.module.ts new file mode 100644 index 0000000..07fb218 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/forms/forms-routing.module.ts @@ -0,0 +1,35 @@ +import { NgModule } from '@angular/core'; +import { RouterModule, Routes } from '@angular/router'; + +import { FormsComponent } from './forms.component'; +import { FormInputsComponent } from './form-inputs/form-inputs.component'; +import { FormLayoutsComponent } from './form-layouts/form-layouts.component'; + +const routes: Routes = [ + { + path: '', + component: FormsComponent, + children: [ + { + path: 'inputs', + component: FormInputsComponent, + }, + { + path: 'layouts', + component: FormLayoutsComponent, + }, + ], + }, +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule], +}) +export class FormsRoutingModule {} + +export const routedComponents = [ + FormsComponent, + FormInputsComponent, + FormLayoutsComponent, +]; diff --git a/packages/admin-web-angular/src/app/pages/forms/forms.component.ts b/packages/admin-web-angular/src/app/pages/forms/forms.component.ts new file mode 100644 index 0000000..2c9c97f --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/forms/forms.component.ts @@ -0,0 +1,7 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'ngx-form-elements', + template: ` `, +}) +export class FormsComponent {} diff --git a/packages/admin-web-angular/src/app/pages/forms/forms.module.ts b/packages/admin-web-angular/src/app/pages/forms/forms.module.ts new file mode 100644 index 0000000..45982ba --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/forms/forms.module.ts @@ -0,0 +1,10 @@ +import { NgModule } from '@angular/core'; + +import { ThemeModule } from '../../@theme/theme.module'; +import { FormsRoutingModule, routedComponents } from './forms-routing.module'; + +@NgModule({ + imports: [ThemeModule, FormsRoutingModule], + declarations: [...routedComponents], +}) +export class FormsModule {} diff --git a/packages/admin-web-angular/src/app/pages/maps/bubble/bubble-map.component.scss b/packages/admin-web-angular/src/app/pages/maps/bubble/bubble-map.component.scss new file mode 100644 index 0000000..2d05f1d --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/maps/bubble/bubble-map.component.scss @@ -0,0 +1,13 @@ +// @import '../../../@theme/styles/themes'; + +// @include nb-install-component() { + +// nb-card-body { +// padding: nb-theme(card-padding) 0 0 0; +// } + +// .echarts { +// width: 100%; +// height: nb-theme(card-height-large); +// } +// } diff --git a/packages/admin-web-angular/src/app/pages/maps/bubble/bubble-map.component.ts b/packages/admin-web-angular/src/app/pages/maps/bubble/bubble-map.component.ts new file mode 100644 index 0000000..da050f7 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/maps/bubble/bubble-map.component.ts @@ -0,0 +1,1397 @@ +import { Component, OnDestroy } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { combineLatest } from 'rxjs'; +import { takeWhile } from 'rxjs/operators'; +import { NbThemeService } from '@nebular/theme'; +import { registerMap } from 'echarts'; + +@Component({ + selector: 'ngx-bubble-map', + styleUrls: ['./bubble-map.component.scss'], + template: ` + + Bubble Maps + +
+
+
+ `, +}) +export class BubbleMapComponent implements OnDestroy { + latlong: any = {}; + mapData: any[]; + max = -Infinity; + min = Infinity; + options: any; + + bubbleTheme: any; + geoColors: any[]; + + private alive = true; + + constructor(private theme: NbThemeService, private http: HttpClient) { + combineLatest([ + this.http.get('assets/map/world.json'), + this.theme.getJsTheme(), + ]) + .pipe(takeWhile(() => this.alive)) + .subscribe(([map, config]: [any, any]) => { + registerMap('world', map); + + const colors = config.variables; + this.bubbleTheme = config.variables.bubbleMap; + this.geoColors = [ + colors.primary, + colors.info, + colors.success, + colors.warning, + colors.danger, + ]; + + this.latlong = { + AD: { latitude: 42.5, longitude: 1.5 }, + AE: { latitude: 24, longitude: 54 }, + AF: { latitude: 33, longitude: 65 }, + AG: { latitude: 17.05, longitude: -61.8 }, + AI: { latitude: 18.25, longitude: -63.1667 }, + AL: { latitude: 41, longitude: 20 }, + AM: { latitude: 40, longitude: 45 }, + AN: { latitude: 12.25, longitude: -68.75 }, + AO: { latitude: -12.5, longitude: 18.5 }, + AP: { latitude: 35, longitude: 105 }, + AQ: { latitude: -90, longitude: 0 }, + AR: { latitude: -34, longitude: -64 }, + AS: { latitude: -14.3333, longitude: -170 }, + AT: { latitude: 47.3333, longitude: 13.3333 }, + AU: { latitude: -27, longitude: 133 }, + AW: { latitude: 12.5, longitude: -69.9667 }, + AZ: { latitude: 40.5, longitude: 47.5 }, + BA: { latitude: 44, longitude: 18 }, + BB: { latitude: 13.1667, longitude: -59.5333 }, + BD: { latitude: 24, longitude: 90 }, + BE: { latitude: 50.8333, longitude: 4 }, + BF: { latitude: 13, longitude: -2 }, + BG: { latitude: 43, longitude: 25 }, + BH: { latitude: 26, longitude: 50.55 }, + BI: { latitude: -3.5, longitude: 30 }, + BJ: { latitude: 9.5, longitude: 2.25 }, + BM: { latitude: 32.3333, longitude: -64.75 }, + BN: { latitude: 4.5, longitude: 114.6667 }, + BO: { latitude: -17, longitude: -65 }, + BR: { latitude: -10, longitude: -55 }, + BS: { latitude: 24.25, longitude: -76 }, + BT: { latitude: 27.5, longitude: 90.5 }, + BV: { latitude: -54.4333, longitude: 3.4 }, + BW: { latitude: -22, longitude: 24 }, + BY: { latitude: 53, longitude: 28 }, + BZ: { latitude: 17.25, longitude: -88.75 }, + CA: { latitude: 54, longitude: -100 }, + CC: { latitude: -12.5, longitude: 96.8333 }, + CD: { latitude: 0, longitude: 25 }, + CF: { latitude: 7, longitude: 21 }, + CG: { latitude: -1, longitude: 15 }, + CH: { latitude: 47, longitude: 8 }, + CI: { latitude: 8, longitude: -5 }, + CK: { latitude: -21.2333, longitude: -159.7667 }, + CL: { latitude: -30, longitude: -71 }, + CM: { latitude: 6, longitude: 12 }, + CN: { latitude: 35, longitude: 105 }, + CO: { latitude: 4, longitude: -72 }, + CR: { latitude: 10, longitude: -84 }, + CU: { latitude: 21.5, longitude: -80 }, + CV: { latitude: 16, longitude: -24 }, + CX: { latitude: -10.5, longitude: 105.6667 }, + CY: { latitude: 35, longitude: 33 }, + CZ: { latitude: 49.75, longitude: 15.5 }, + DE: { latitude: 51, longitude: 9 }, + DJ: { latitude: 11.5, longitude: 43 }, + DK: { latitude: 56, longitude: 10 }, + DM: { latitude: 15.4167, longitude: -61.3333 }, + DO: { latitude: 19, longitude: -70.6667 }, + DZ: { latitude: 28, longitude: 3 }, + EC: { latitude: -2, longitude: -77.5 }, + EE: { latitude: 59, longitude: 26 }, + EG: { latitude: 27, longitude: 30 }, + EH: { latitude: 24.5, longitude: -13 }, + ER: { latitude: 15, longitude: 39 }, + ES: { latitude: 40, longitude: -4 }, + ET: { latitude: 8, longitude: 38 }, + EU: { latitude: 47, longitude: 8 }, + FI: { latitude: 62, longitude: 26 }, + FJ: { latitude: -18, longitude: 175 }, + FK: { latitude: -51.75, longitude: -59 }, + FM: { latitude: 6.9167, longitude: 158.25 }, + FO: { latitude: 62, longitude: -7 }, + FR: { latitude: 46, longitude: 2 }, + GA: { latitude: -1, longitude: 11.75 }, + GB: { latitude: 54, longitude: -2 }, + GD: { latitude: 12.1167, longitude: -61.6667 }, + GE: { latitude: 42, longitude: 43.5 }, + GF: { latitude: 4, longitude: -53 }, + GH: { latitude: 8, longitude: -2 }, + GI: { latitude: 36.1833, longitude: -5.3667 }, + GL: { latitude: 72, longitude: -40 }, + GM: { latitude: 13.4667, longitude: -16.5667 }, + GN: { latitude: 11, longitude: -10 }, + GP: { latitude: 16.25, longitude: -61.5833 }, + GQ: { latitude: 2, longitude: 10 }, + GR: { latitude: 39, longitude: 22 }, + GS: { latitude: -54.5, longitude: -37 }, + GT: { latitude: 15.5, longitude: -90.25 }, + GU: { latitude: 13.4667, longitude: 144.7833 }, + GW: { latitude: 12, longitude: -15 }, + GY: { latitude: 5, longitude: -59 }, + HK: { latitude: 22.25, longitude: 114.1667 }, + HM: { latitude: -53.1, longitude: 72.5167 }, + HN: { latitude: 15, longitude: -86.5 }, + HR: { latitude: 45.1667, longitude: 15.5 }, + HT: { latitude: 19, longitude: -72.4167 }, + HU: { latitude: 47, longitude: 20 }, + ID: { latitude: -5, longitude: 120 }, + IE: { latitude: 53, longitude: -8 }, + IL: { latitude: 31.5, longitude: 34.75 }, + IN: { latitude: 20, longitude: 77 }, + IO: { latitude: -6, longitude: 71.5 }, + IQ: { latitude: 33, longitude: 44 }, + IR: { latitude: 32, longitude: 53 }, + IS: { latitude: 65, longitude: -18 }, + IT: { latitude: 42.8333, longitude: 12.8333 }, + JM: { latitude: 18.25, longitude: -77.5 }, + JO: { latitude: 31, longitude: 36 }, + JP: { latitude: 36, longitude: 138 }, + KE: { latitude: 1, longitude: 38 }, + KG: { latitude: 41, longitude: 75 }, + KH: { latitude: 13, longitude: 105 }, + KI: { latitude: 1.4167, longitude: 173 }, + KM: { latitude: -12.1667, longitude: 44.25 }, + KN: { latitude: 17.3333, longitude: -62.75 }, + KP: { latitude: 40, longitude: 127 }, + KR: { latitude: 37, longitude: 127.5 }, + KW: { latitude: 29.3375, longitude: 47.6581 }, + KY: { latitude: 19.5, longitude: -80.5 }, + KZ: { latitude: 48, longitude: 68 }, + LA: { latitude: 18, longitude: 105 }, + LB: { latitude: 33.8333, longitude: 35.8333 }, + LC: { latitude: 13.8833, longitude: -61.1333 }, + LI: { latitude: 47.1667, longitude: 9.5333 }, + LK: { latitude: 7, longitude: 81 }, + LR: { latitude: 6.5, longitude: -9.5 }, + LS: { latitude: -29.5, longitude: 28.5 }, + LT: { latitude: 55, longitude: 24 }, + LU: { latitude: 49.75, longitude: 6 }, + LV: { latitude: 57, longitude: 25 }, + LY: { latitude: 25, longitude: 17 }, + MA: { latitude: 32, longitude: -5 }, + MC: { latitude: 43.7333, longitude: 7.4 }, + MD: { latitude: 47, longitude: 29 }, + ME: { latitude: 42.5, longitude: 19.4 }, + MG: { latitude: -20, longitude: 47 }, + MH: { latitude: 9, longitude: 168 }, + MK: { latitude: 41.8333, longitude: 22 }, + ML: { latitude: 17, longitude: -4 }, + MM: { latitude: 22, longitude: 98 }, + MN: { latitude: 46, longitude: 105 }, + MO: { latitude: 22.1667, longitude: 113.55 }, + MP: { latitude: 15.2, longitude: 145.75 }, + MQ: { latitude: 14.6667, longitude: -61 }, + MR: { latitude: 20, longitude: -12 }, + MS: { latitude: 16.75, longitude: -62.2 }, + MT: { latitude: 35.8333, longitude: 14.5833 }, + MU: { latitude: -20.2833, longitude: 57.55 }, + MV: { latitude: 3.25, longitude: 73 }, + MW: { latitude: -13.5, longitude: 34 }, + MX: { latitude: 23, longitude: -102 }, + MY: { latitude: 2.5, longitude: 112.5 }, + MZ: { latitude: -18.25, longitude: 35 }, + NA: { latitude: -22, longitude: 17 }, + NC: { latitude: -21.5, longitude: 165.5 }, + NE: { latitude: 16, longitude: 8 }, + NF: { latitude: -29.0333, longitude: 167.95 }, + NG: { latitude: 10, longitude: 8 }, + NI: { latitude: 13, longitude: -85 }, + NL: { latitude: 52.5, longitude: 5.75 }, + NO: { latitude: 62, longitude: 10 }, + NP: { latitude: 28, longitude: 84 }, + NR: { latitude: -0.5333, longitude: 166.9167 }, + NU: { latitude: -19.0333, longitude: -169.8667 }, + NZ: { latitude: -41, longitude: 174 }, + OM: { latitude: 21, longitude: 57 }, + PA: { latitude: 9, longitude: -80 }, + PE: { latitude: -10, longitude: -76 }, + PF: { latitude: -15, longitude: -140 }, + PG: { latitude: -6, longitude: 147 }, + PH: { latitude: 13, longitude: 122 }, + PK: { latitude: 30, longitude: 70 }, + PL: { latitude: 52, longitude: 20 }, + PM: { latitude: 46.8333, longitude: -56.3333 }, + PR: { latitude: 18.25, longitude: -66.5 }, + PS: { latitude: 32, longitude: 35.25 }, + PT: { latitude: 39.5, longitude: -8 }, + PW: { latitude: 7.5, longitude: 134.5 }, + PY: { latitude: -23, longitude: -58 }, + QA: { latitude: 25.5, longitude: 51.25 }, + RE: { latitude: -21.1, longitude: 55.6 }, + RO: { latitude: 46, longitude: 25 }, + RS: { latitude: 44, longitude: 21 }, + RU: { latitude: 60, longitude: 100 }, + RW: { latitude: -2, longitude: 30 }, + SA: { latitude: 25, longitude: 45 }, + SB: { latitude: -8, longitude: 159 }, + SC: { latitude: -4.5833, longitude: 55.6667 }, + SD: { latitude: 15, longitude: 30 }, + SE: { latitude: 62, longitude: 15 }, + SG: { latitude: 1.3667, longitude: 103.8 }, + SH: { latitude: -15.9333, longitude: -5.7 }, + SI: { latitude: 46, longitude: 15 }, + SJ: { latitude: 78, longitude: 20 }, + SK: { latitude: 48.6667, longitude: 19.5 }, + SL: { latitude: 8.5, longitude: -11.5 }, + SM: { latitude: 43.7667, longitude: 12.4167 }, + SN: { latitude: 14, longitude: -14 }, + SO: { latitude: 10, longitude: 49 }, + SR: { latitude: 4, longitude: -56 }, + ST: { latitude: 1, longitude: 7 }, + SV: { latitude: 13.8333, longitude: -88.9167 }, + SY: { latitude: 35, longitude: 38 }, + SZ: { latitude: -26.5, longitude: 31.5 }, + TC: { latitude: 21.75, longitude: -71.5833 }, + TD: { latitude: 15, longitude: 19 }, + TF: { latitude: -43, longitude: 67 }, + TG: { latitude: 8, longitude: 1.1667 }, + TH: { latitude: 15, longitude: 100 }, + TJ: { latitude: 39, longitude: 71 }, + TK: { latitude: -9, longitude: -172 }, + TM: { latitude: 40, longitude: 60 }, + TN: { latitude: 34, longitude: 9 }, + TO: { latitude: -20, longitude: -175 }, + TR: { latitude: 39, longitude: 35 }, + TT: { latitude: 11, longitude: -61 }, + TV: { latitude: -8, longitude: 178 }, + TW: { latitude: 23.5, longitude: 121 }, + TZ: { latitude: -6, longitude: 35 }, + UA: { latitude: 49, longitude: 32 }, + UG: { latitude: 1, longitude: 32 }, + UM: { latitude: 19.2833, longitude: 166.6 }, + US: { latitude: 38, longitude: -97 }, + UY: { latitude: -33, longitude: -56 }, + UZ: { latitude: 41, longitude: 64 }, + VA: { latitude: 41.9, longitude: 12.45 }, + VC: { latitude: 13.25, longitude: -61.2 }, + VE: { latitude: 8, longitude: -66 }, + VG: { latitude: 18.5, longitude: -64.5 }, + VI: { latitude: 18.3333, longitude: -64.8333 }, + VN: { latitude: 16, longitude: 106 }, + VU: { latitude: -16, longitude: 167 }, + WF: { latitude: -13.3, longitude: -176.2 }, + WS: { latitude: -13.5833, longitude: -172.3333 }, + YE: { latitude: 15, longitude: 48 }, + YT: { latitude: -12.8333, longitude: 45.1667 }, + ZA: { latitude: -29, longitude: 24 }, + ZM: { latitude: -15, longitude: 30 }, + ZW: { latitude: -20, longitude: 30 }, + }; + + this.mapData = [ + { + code: 'AF', + name: 'Afghanistan', + value: 32358260, + color: this.getRandomGeoColor(), + }, + { + code: 'AL', + name: 'Albania', + value: 3215988, + color: this.getRandomGeoColor(), + }, + { + code: 'DZ', + name: 'Algeria', + value: 35980193, + color: this.getRandomGeoColor(), + }, + { + code: 'AO', + name: 'Angola', + value: 19618432, + color: this.getRandomGeoColor(), + }, + { + code: 'AR', + name: 'Argentina', + value: 40764561, + color: this.getRandomGeoColor(), + }, + { + code: 'AM', + name: 'Armenia', + value: 3100236, + color: this.getRandomGeoColor(), + }, + { + code: 'AU', + name: 'Australia', + value: 22605732, + color: this.getRandomGeoColor(), + }, + { + code: 'AT', + name: 'Austria', + value: 8413429, + color: this.getRandomGeoColor(), + }, + { + code: 'AZ', + name: 'Azerbaijan', + value: 9306023, + color: this.getRandomGeoColor(), + }, + { + code: 'BH', + name: 'Bahrain', + value: 1323535, + color: this.getRandomGeoColor(), + }, + { + code: 'BD', + name: 'Bangladesh', + value: 150493658, + color: this.getRandomGeoColor(), + }, + { + code: 'BY', + name: 'Belarus', + value: 9559441, + color: this.getRandomGeoColor(), + }, + { + code: 'BE', + name: 'Belgium', + value: 10754056, + color: this.getRandomGeoColor(), + }, + { + code: 'BJ', + name: 'Benin', + value: 9099922, + color: this.getRandomGeoColor(), + }, + { + code: 'BT', + name: 'Bhutan', + value: 738267, + color: this.getRandomGeoColor(), + }, + { + code: 'BO', + name: 'Bolivia', + value: 10088108, + color: this.getRandomGeoColor(), + }, + { + code: 'BA', + name: 'Bosnia and Herzegovina', + value: 3752228, + color: this.getRandomGeoColor(), + }, + { + code: 'BW', + name: 'Botswana', + value: 2030738, + color: this.getRandomGeoColor(), + }, + { + code: 'BR', + name: 'Brazil', + value: 196655014, + color: this.getRandomGeoColor(), + }, + { + code: 'BN', + name: 'Brunei', + value: 405938, + color: this.getRandomGeoColor(), + }, + { + code: 'BG', + name: 'Bulgaria', + value: 7446135, + color: this.getRandomGeoColor(), + }, + { + code: 'BF', + name: 'Burkina Faso', + value: 16967845, + color: this.getRandomGeoColor(), + }, + { + code: 'BI', + name: 'Burundi', + value: 8575172, + color: this.getRandomGeoColor(), + }, + { + code: 'KH', + name: 'Cambodia', + value: 14305183, + color: this.getRandomGeoColor(), + }, + { + code: 'CM', + name: 'Cameroon', + value: 20030362, + color: this.getRandomGeoColor(), + }, + { + code: 'CA', + name: 'Canada', + value: 34349561, + color: this.getRandomGeoColor(), + }, + { + code: 'CV', + name: 'Cape Verde', + value: 500585, + color: this.getRandomGeoColor(), + }, + { + code: 'CF', + name: 'Central African Rep.', + value: 4486837, + color: this.getRandomGeoColor(), + }, + { + code: 'TD', + name: 'Chad', + value: 11525496, + color: this.getRandomGeoColor(), + }, + { + code: 'CL', + name: 'Chile', + value: 17269525, + color: this.getRandomGeoColor(), + }, + { + code: 'CN', + name: 'China', + value: 1347565324, + color: this.getRandomGeoColor(), + }, + { + code: 'CO', + name: 'Colombia', + value: 46927125, + color: this.getRandomGeoColor(), + }, + { + code: 'KM', + name: 'Comoros', + value: 753943, + color: this.getRandomGeoColor(), + }, + { + code: 'CD', + name: 'Congo, Dem. Rep.', + value: 67757577, + color: this.getRandomGeoColor(), + }, + { + code: 'CG', + name: 'Congo, Rep.', + value: 4139748, + color: this.getRandomGeoColor(), + }, + { + code: 'CR', + name: 'Costa Rica', + value: 4726575, + color: this.getRandomGeoColor(), + }, + { + code: 'CI', + name: "Cote d'Ivoire", + value: 20152894, + color: this.getRandomGeoColor(), + }, + { + code: 'HR', + name: 'Croatia', + value: 4395560, + color: this.getRandomGeoColor(), + }, + { + code: 'CU', + name: 'Cuba', + value: 11253665, + color: this.getRandomGeoColor(), + }, + { + code: 'CY', + name: 'Cyprus', + value: 1116564, + color: this.getRandomGeoColor(), + }, + { + code: 'CZ', + name: 'Czech Rep.', + value: 10534293, + color: this.getRandomGeoColor(), + }, + { + code: 'DK', + name: 'Denmark', + value: 5572594, + color: this.getRandomGeoColor(), + }, + { + code: 'DJ', + name: 'Djibouti', + value: 905564, + color: this.getRandomGeoColor(), + }, + { + code: 'DO', + name: 'Dominican Rep.', + value: 10056181, + color: this.getRandomGeoColor(), + }, + { + code: 'EC', + name: 'Ecuador', + value: 14666055, + color: this.getRandomGeoColor(), + }, + { + code: 'EG', + name: 'Egypt', + value: 82536770, + color: this.getRandomGeoColor(), + }, + { + code: 'SV', + name: 'El Salvador', + value: 6227491, + color: this.getRandomGeoColor(), + }, + { + code: 'GQ', + name: 'Equatorial Guinea', + value: 720213, + color: this.getRandomGeoColor(), + }, + { + code: 'ER', + name: 'Eritrea', + value: 5415280, + color: this.getRandomGeoColor(), + }, + { + code: 'EE', + name: 'Estonia', + value: 1340537, + color: this.getRandomGeoColor(), + }, + { + code: 'ET', + name: 'Ethiopia', + value: 84734262, + color: this.getRandomGeoColor(), + }, + { + code: 'FJ', + name: 'Fiji', + value: 868406, + color: this.getRandomGeoColor(), + }, + { + code: 'FI', + name: 'Finland', + value: 5384770, + color: this.getRandomGeoColor(), + }, + { + code: 'FR', + name: 'France', + value: 63125894, + color: this.getRandomGeoColor(), + }, + { + code: 'GA', + name: 'Gabon', + value: 1534262, + color: this.getRandomGeoColor(), + }, + { + code: 'GM', + name: 'Gambia', + value: 1776103, + color: this.getRandomGeoColor(), + }, + { + code: 'GE', + name: 'Georgia', + value: 4329026, + color: this.getRandomGeoColor(), + }, + { + code: 'DE', + name: 'Germany', + value: 82162512, + color: this.getRandomGeoColor(), + }, + { + code: 'GH', + name: 'Ghana', + value: 24965816, + color: this.getRandomGeoColor(), + }, + { + code: 'GR', + name: 'Greece', + value: 11390031, + color: this.getRandomGeoColor(), + }, + { + code: 'GT', + name: 'Guatemala', + value: 14757316, + color: this.getRandomGeoColor(), + }, + { + code: 'GN', + name: 'Guinea', + value: 10221808, + color: this.getRandomGeoColor(), + }, + { + code: 'GW', + name: 'Guinea-Bissau', + value: 1547061, + color: this.getRandomGeoColor(), + }, + { + code: 'GY', + name: 'Guyana', + value: 756040, + color: this.getRandomGeoColor(), + }, + { + code: 'HT', + name: 'Haiti', + value: 10123787, + color: this.getRandomGeoColor(), + }, + { + code: 'HN', + name: 'Honduras', + value: 7754687, + color: this.getRandomGeoColor(), + }, + { + code: 'HK', + name: 'Hong Kong, China', + value: 7122187, + color: this.getRandomGeoColor(), + }, + { + code: 'HU', + name: 'Hungary', + value: 9966116, + color: this.getRandomGeoColor(), + }, + { + code: 'IS', + name: 'Iceland', + value: 324366, + color: this.getRandomGeoColor(), + }, + { + code: 'IN', + name: 'India', + value: 1241491960, + color: this.getRandomGeoColor(), + }, + { + code: 'ID', + name: 'Indonesia', + value: 242325638, + color: this.getRandomGeoColor(), + }, + { + code: 'IR', + name: 'Iran', + value: 74798599, + color: this.getRandomGeoColor(), + }, + { + code: 'IQ', + name: 'Iraq', + value: 32664942, + color: this.getRandomGeoColor(), + }, + { + code: 'IE', + name: 'Ireland', + value: 4525802, + color: this.getRandomGeoColor(), + }, + { + code: 'IL', + name: 'Israel', + value: 7562194, + color: this.getRandomGeoColor(), + }, + { + code: 'IT', + name: 'Italy', + value: 60788694, + color: this.getRandomGeoColor(), + }, + { + code: 'JM', + name: 'Jamaica', + value: 2751273, + color: this.getRandomGeoColor(), + }, + { + code: 'JP', + name: 'Japan', + value: 126497241, + color: this.getRandomGeoColor(), + }, + { + code: 'JO', + name: 'Jordan', + value: 6330169, + color: this.getRandomGeoColor(), + }, + { + code: 'KZ', + name: 'Kazakhstan', + value: 16206750, + color: this.getRandomGeoColor(), + }, + { + code: 'KE', + name: 'Kenya', + value: 41609728, + color: this.getRandomGeoColor(), + }, + { + code: 'KP', + name: 'Korea, Dem. Rep.', + value: 24451285, + color: this.getRandomGeoColor(), + }, + { + code: 'KR', + name: 'Korea, Rep.', + value: 48391343, + color: this.getRandomGeoColor(), + }, + { + code: 'KW', + name: 'Kuwait', + value: 2818042, + color: this.getRandomGeoColor(), + }, + { + code: 'KG', + name: 'Kyrgyzstan', + value: 5392580, + color: this.getRandomGeoColor(), + }, + { + code: 'LA', + name: 'Laos', + value: 6288037, + color: this.getRandomGeoColor(), + }, + { + code: 'LV', + name: 'Latvia', + value: 2243142, + color: this.getRandomGeoColor(), + }, + { + code: 'LB', + name: 'Lebanon', + value: 4259405, + color: this.getRandomGeoColor(), + }, + { + code: 'LS', + name: 'Lesotho', + value: 2193843, + color: this.getRandomGeoColor(), + }, + { + code: 'LR', + name: 'Liberia', + value: 4128572, + color: this.getRandomGeoColor(), + }, + { + code: 'LY', + name: 'Libya', + value: 6422772, + color: this.getRandomGeoColor(), + }, + { + code: 'LT', + name: 'Lithuania', + value: 3307481, + color: this.getRandomGeoColor(), + }, + { + code: 'LU', + name: 'Luxembourg', + value: 515941, + color: this.getRandomGeoColor(), + }, + { + code: 'MK', + name: 'Macedonia, FYR', + value: 2063893, + color: this.getRandomGeoColor(), + }, + { + code: 'MG', + name: 'Madagascar', + value: 21315135, + color: this.getRandomGeoColor(), + }, + { + code: 'MW', + name: 'Malawi', + value: 15380888, + color: this.getRandomGeoColor(), + }, + { + code: 'MY', + name: 'Malaysia', + value: 28859154, + color: this.getRandomGeoColor(), + }, + { + code: 'ML', + name: 'Mali', + value: 15839538, + color: this.getRandomGeoColor(), + }, + { + code: 'MR', + name: 'Mauritania', + value: 3541540, + color: this.getRandomGeoColor(), + }, + { + code: 'MU', + name: 'Mauritius', + value: 1306593, + color: this.getRandomGeoColor(), + }, + { + code: 'MX', + name: 'Mexico', + value: 114793341, + color: this.getRandomGeoColor(), + }, + { + code: 'MD', + name: 'Moldova', + value: 3544864, + color: this.getRandomGeoColor(), + }, + { + code: 'MN', + name: 'Mongolia', + value: 2800114, + color: this.getRandomGeoColor(), + }, + { + code: 'ME', + name: 'Montenegro', + value: 632261, + color: this.getRandomGeoColor(), + }, + { + code: 'MA', + name: 'Morocco', + value: 32272974, + color: this.getRandomGeoColor(), + }, + { + code: 'MZ', + name: 'Mozambique', + value: 23929708, + color: this.getRandomGeoColor(), + }, + { + code: 'MM', + name: 'Myanmar', + value: 48336763, + color: this.getRandomGeoColor(), + }, + { + code: 'NA', + name: 'Namibia', + value: 2324004, + color: this.getRandomGeoColor(), + }, + { + code: 'NP', + name: 'Nepal', + value: 30485798, + color: this.getRandomGeoColor(), + }, + { + code: 'NL', + name: 'Netherlands', + value: 16664746, + color: this.getRandomGeoColor(), + }, + { + code: 'NZ', + name: 'New Zealand', + value: 4414509, + color: this.getRandomGeoColor(), + }, + { + code: 'NI', + name: 'Nicaragua', + value: 5869859, + color: this.getRandomGeoColor(), + }, + { + code: 'NE', + name: 'Niger', + value: 16068994, + color: this.getRandomGeoColor(), + }, + { + code: 'NG', + name: 'Nigeria', + value: 162470737, + color: this.getRandomGeoColor(), + }, + { + code: 'NO', + name: 'Norway', + value: 4924848, + color: this.getRandomGeoColor(), + }, + { + code: 'OM', + name: 'Oman', + value: 2846145, + color: this.getRandomGeoColor(), + }, + { + code: 'PK', + name: 'Pakistan', + value: 176745364, + color: this.getRandomGeoColor(), + }, + { + code: 'PA', + name: 'Panama', + value: 3571185, + color: this.getRandomGeoColor(), + }, + { + code: 'PG', + name: 'Papua New Guinea', + value: 7013829, + color: this.getRandomGeoColor(), + }, + { + code: 'PY', + name: 'Paraguay', + value: 6568290, + color: this.getRandomGeoColor(), + }, + { + code: 'PE', + name: 'Peru', + value: 29399817, + color: this.getRandomGeoColor(), + }, + { + code: 'PH', + name: 'Philippines', + value: 94852030, + color: this.getRandomGeoColor(), + }, + { + code: 'PL', + name: 'Poland', + value: 38298949, + color: this.getRandomGeoColor(), + }, + { + code: 'PT', + name: 'Portugal', + value: 10689663, + color: this.getRandomGeoColor(), + }, + { + code: 'PR', + name: 'Puerto Rico', + value: 3745526, + color: this.getRandomGeoColor(), + }, + { + code: 'QA', + name: 'Qatar', + value: 1870041, + color: this.getRandomGeoColor(), + }, + { + code: 'RO', + name: 'Romania', + value: 21436495, + color: this.getRandomGeoColor(), + }, + { + code: 'RU', + name: 'Russia', + value: 142835555, + color: this.getRandomGeoColor(), + }, + { + code: 'RW', + name: 'Rwanda', + value: 10942950, + color: this.getRandomGeoColor(), + }, + { + code: 'SA', + name: 'Saudi Arabia', + value: 28082541, + color: this.getRandomGeoColor(), + }, + { + code: 'SN', + name: 'Senegal', + value: 12767556, + color: this.getRandomGeoColor(), + }, + { + code: 'RS', + name: 'Serbia', + value: 9853969, + color: this.getRandomGeoColor(), + }, + { + code: 'SL', + name: 'Sierra Leone', + value: 5997486, + color: this.getRandomGeoColor(), + }, + { + code: 'SG', + name: 'Singapore', + value: 5187933, + color: this.getRandomGeoColor(), + }, + { + code: 'SK', + name: 'Slovak Republic', + value: 5471502, + color: this.getRandomGeoColor(), + }, + { + code: 'SI', + name: 'Slovenia', + value: 2035012, + color: this.getRandomGeoColor(), + }, + { + code: 'SB', + name: 'Solomon Islands', + value: 552267, + color: this.getRandomGeoColor(), + }, + { + code: 'SO', + name: 'Somalia', + value: 9556873, + color: this.getRandomGeoColor(), + }, + { + code: 'ZA', + name: 'South Africa', + value: 50459978, + color: this.getRandomGeoColor(), + }, + { + code: 'ES', + name: 'Spain', + value: 46454895, + color: this.getRandomGeoColor(), + }, + { + code: 'LK', + name: 'Sri Lanka', + value: 21045394, + color: this.getRandomGeoColor(), + }, + { + code: 'SD', + name: 'Sudan', + value: 34735288, + color: this.getRandomGeoColor(), + }, + { + code: 'SR', + name: 'Suriname', + value: 529419, + color: this.getRandomGeoColor(), + }, + { + code: 'SZ', + name: 'Swaziland', + value: 1203330, + color: this.getRandomGeoColor(), + }, + { + code: 'SE', + name: 'Sweden', + value: 9440747, + color: this.getRandomGeoColor(), + }, + { + code: 'CH', + name: 'Switzerland', + value: 7701690, + color: this.getRandomGeoColor(), + }, + { + code: 'SY', + name: 'Syria', + value: 20766037, + color: this.getRandomGeoColor(), + }, + { + code: 'TW', + name: 'Taiwan', + value: 23072000, + color: this.getRandomGeoColor(), + }, + { + code: 'TJ', + name: 'Tajikistan', + value: 6976958, + color: this.getRandomGeoColor(), + }, + { + code: 'TZ', + name: 'Tanzania', + value: 46218486, + color: this.getRandomGeoColor(), + }, + { + code: 'TH', + name: 'Thailand', + value: 69518555, + color: this.getRandomGeoColor(), + }, + { + code: 'TG', + name: 'Togo', + value: 6154813, + color: this.getRandomGeoColor(), + }, + { + code: 'TT', + name: 'Trinidad and Tobago', + value: 1346350, + color: this.getRandomGeoColor(), + }, + { + code: 'TN', + name: 'Tunisia', + value: 10594057, + color: this.getRandomGeoColor(), + }, + { + code: 'TR', + name: 'Turkey', + value: 73639596, + color: this.getRandomGeoColor(), + }, + { + code: 'TM', + name: 'Turkmenistan', + value: 5105301, + color: this.getRandomGeoColor(), + }, + { + code: 'UG', + name: 'Uganda', + value: 34509205, + color: this.getRandomGeoColor(), + }, + { + code: 'UA', + name: 'Ukraine', + value: 45190180, + color: this.getRandomGeoColor(), + }, + { + code: 'AE', + name: 'United Arab Emirates', + value: 7890924, + color: this.getRandomGeoColor(), + }, + { + code: 'GB', + name: 'United Kingdom', + value: 62417431, + color: this.getRandomGeoColor(), + }, + { + code: 'US', + name: 'United States', + value: 313085380, + color: this.getRandomGeoColor(), + }, + { + code: 'UY', + name: 'Uruguay', + value: 3380008, + color: this.getRandomGeoColor(), + }, + { + code: 'UZ', + name: 'Uzbekistan', + value: 27760267, + color: this.getRandomGeoColor(), + }, + { + code: 'VE', + name: 'Venezuela', + value: 29436891, + color: this.getRandomGeoColor(), + }, + { + code: 'PS', + name: 'West Bank and Gaza', + value: 4152369, + color: this.getRandomGeoColor(), + }, + { + code: 'VN', + name: 'Vietnam', + value: 88791996, + color: this.getRandomGeoColor(), + }, + { + code: 'YE', + name: 'Yemen, Rep.', + value: 24799880, + color: this.getRandomGeoColor(), + }, + { + code: 'ZM', + name: 'Zambia', + value: 13474959, + color: this.getRandomGeoColor(), + }, + { + code: 'ZW', + name: 'Zimbabwe', + value: 12754378, + color: this.getRandomGeoColor(), + }, + ]; + + this.mapData.forEach((itemOpt) => { + if (itemOpt.value > this.max) { + this.max = itemOpt.value; + } + if (itemOpt.value < this.min) { + this.min = itemOpt.value; + } + }); + + this.options = { + title: { + text: 'World Population (2011)', + left: 'center', + top: 'top', + textStyle: { + color: this.bubbleTheme.titleColor, + }, + }, + tooltip: { + trigger: 'item', + formatter: (params) => { + return `${params.name}: ${params.value[2]}`; + }, + }, + visualMap: { + show: false, + min: 0, + max: this.max, + inRange: { + symbolSize: [6, 60], + }, + }, + geo: { + name: 'World Population (2010)', + type: 'map', + map: 'world', + roam: true, + label: { + emphasis: { + show: false, + }, + }, + itemStyle: { + normal: { + areaColor: this.bubbleTheme.areaColor, + borderColor: this.bubbleTheme.areaBorderColor, + }, + emphasis: { + areaColor: this.bubbleTheme.areaHoverColor, + }, + }, + zoom: 1.1, + }, + series: [ + { + type: 'scatter', + coordinateSystem: 'geo', + data: this.mapData.map((itemOpt) => { + return { + name: itemOpt.name, + value: [ + this.latlong[itemOpt.code].longitude, + this.latlong[itemOpt.code].latitude, + itemOpt.value, + ], + itemStyle: { + normal: { + color: itemOpt.color, + }, + }, + }; + }), + }, + ], + }; + }); + } + + private getRandomGeoColor() { + const index = Math.round(Math.random() * this.geoColors.length); + return this.geoColors[index]; + } + + ngOnDestroy() { + this.alive = false; + } +} diff --git a/packages/admin-web-angular/src/app/pages/maps/gmaps/gmaps.component.scss b/packages/admin-web-angular/src/app/pages/maps/gmaps/gmaps.component.scss new file mode 100644 index 0000000..c10bacd --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/maps/gmaps/gmaps.component.scss @@ -0,0 +1,12 @@ +@import '../../../@theme/styles/themes'; + +@include nb-install-component() { + nb-card-body { + padding: 0; + } + + ::ng-deep agm-map { + width: 100%; + // height: nb-theme(card-height-large); + } +} diff --git a/packages/admin-web-angular/src/app/pages/maps/gmaps/gmaps.component.ts b/packages/admin-web-angular/src/app/pages/maps/gmaps/gmaps.component.ts new file mode 100644 index 0000000..f6cdef4 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/maps/gmaps/gmaps.component.ts @@ -0,0 +1,20 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'ngx-gmaps', + styleUrls: ['./gmaps.component.scss'], + template: ` + + Google Maps + + + + + + + `, +}) +export class GmapsComponent { + lat = 51.678418; + lng = 7.809007; +} diff --git a/packages/admin-web-angular/src/app/pages/maps/leaflet/leaflet.component.scss b/packages/admin-web-angular/src/app/pages/maps/leaflet/leaflet.component.scss new file mode 100644 index 0000000..1ec52f8 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/maps/leaflet/leaflet.component.scss @@ -0,0 +1,12 @@ +@import '../../../@theme/styles/themes'; + +@include nb-install-component() { + nb-card-body { + padding: 0; + } + + ::ng-deep .leaflet-container { + width: 100%; + // height: nb-theme(card-height-large); + } +} diff --git a/packages/admin-web-angular/src/app/pages/maps/leaflet/leaflet.component.ts b/packages/admin-web-angular/src/app/pages/maps/leaflet/leaflet.component.ts new file mode 100644 index 0000000..ab5451d --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/maps/leaflet/leaflet.component.ts @@ -0,0 +1,29 @@ +import { Component } from '@angular/core'; + +import L from 'leaflet'; +import 'style-loader!leaflet/dist/leaflet.css'; + +@Component({ + selector: 'ngx-leaflet', + styleUrls: ['./leaflet.component.scss'], + template: ` + + Leaflet Maps + +
+
+
+ `, +}) +export class LeafletComponent { + options = { + layers: [ + L.tileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { + maxZoom: 18, + attribution: '...', + }), + ], + zoom: 5, + center: L.latLng({ lat: 38.991709, lng: -76.886109 }), + }; +} diff --git a/packages/admin-web-angular/src/app/pages/maps/maps-routing.module.ts b/packages/admin-web-angular/src/app/pages/maps/maps-routing.module.ts new file mode 100644 index 0000000..e6e70ee --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/maps/maps-routing.module.ts @@ -0,0 +1,51 @@ +import { NgModule } from '@angular/core'; +import { RouterModule, Routes } from '@angular/router'; + +import { MapsComponent } from './maps.component'; +import { GmapsComponent } from './gmaps/gmaps.component'; +import { LeafletComponent } from './leaflet/leaflet.component'; +import { BubbleMapComponent } from './bubble/bubble-map.component'; +import { SearchMapComponent } from './search-map/search-map.component'; +import { MapComponent } from './search-map/map/map.component'; +import { SearchComponent } from './search-map/search/search.component'; + +const routes: Routes = [ + { + path: '', + component: MapsComponent, + children: [ + { + path: 'gmaps', + component: GmapsComponent, + }, + { + path: 'leaflet', + component: LeafletComponent, + }, + { + path: 'bubble', + component: BubbleMapComponent, + }, + { + path: 'searchmap', + component: SearchMapComponent, + }, + ], + }, +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule], +}) +export class MapsRoutingModule {} + +export const routedComponents = [ + MapsComponent, + GmapsComponent, + LeafletComponent, + BubbleMapComponent, + SearchMapComponent, + MapComponent, + SearchComponent, +]; diff --git a/packages/admin-web-angular/src/app/pages/maps/maps.component.ts b/packages/admin-web-angular/src/app/pages/maps/maps.component.ts new file mode 100644 index 0000000..fa7a1c1 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/maps/maps.component.ts @@ -0,0 +1,7 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'ngx-maps', + template: ` `, +}) +export class MapsComponent {} diff --git a/packages/admin-web-angular/src/app/pages/maps/maps.module.ts b/packages/admin-web-angular/src/app/pages/maps/maps.module.ts new file mode 100644 index 0000000..a1cea2f --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/maps/maps.module.ts @@ -0,0 +1,23 @@ +import { NgModule } from '@angular/core'; +import { AgmCoreModule } from '@agm/core'; +import { LeafletModule } from '@asymmetrik/ngx-leaflet'; +import { NgxEchartsModule } from 'ngx-echarts'; +import { ThemeModule } from '../../@theme/theme.module'; +import { MapsRoutingModule, routedComponents } from './maps-routing.module'; +import { environment } from 'environments/environment'; + +@NgModule({ + imports: [ + ThemeModule, + AgmCoreModule.forRoot({ + apiKey: environment.GOOGLE_MAPS_API_KEY, + libraries: ['places'], + }), + LeafletModule, + MapsRoutingModule, + NgxEchartsModule, + ], + exports: [], + declarations: [...routedComponents], +}) +export class MapsModule {} diff --git a/packages/admin-web-angular/src/app/pages/maps/search-map/entity/Location.ts b/packages/admin-web-angular/src/app/pages/maps/search-map/entity/Location.ts new file mode 100644 index 0000000..40a9403 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/maps/search-map/entity/Location.ts @@ -0,0 +1,6 @@ +export class Location { + constructor( + public latitude: number = 53.9, + public longitude: number = 27.5667 + ) {} +} diff --git a/packages/admin-web-angular/src/app/pages/maps/search-map/map/map.component.html b/packages/admin-web-angular/src/app/pages/maps/search-map/map/map.component.html new file mode 100644 index 0000000..b5233d4 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/maps/search-map/map/map.component.html @@ -0,0 +1,8 @@ + + + diff --git a/packages/admin-web-angular/src/app/pages/maps/search-map/map/map.component.scss b/packages/admin-web-angular/src/app/pages/maps/search-map/map/map.component.scss new file mode 100644 index 0000000..fb9c0d1 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/maps/search-map/map/map.component.scss @@ -0,0 +1,12 @@ +@import '../../../../@theme/styles/themes'; + +@include nb-install-component() { + nb-card-body { + padding: 0; + } + + ::ng-deep agm-map { + width: 100%; + // height: nb-theme(card-height-large); + } +} diff --git a/packages/admin-web-angular/src/app/pages/maps/search-map/map/map.component.ts b/packages/admin-web-angular/src/app/pages/maps/search-map/map/map.component.ts new file mode 100644 index 0000000..d22e28c --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/maps/search-map/map/map.component.ts @@ -0,0 +1,32 @@ +import { Component, Input, OnInit } from '@angular/core'; +import { Location } from '../entity/Location'; + +@Component({ + selector: 'ngx-map', + templateUrl: './map.component.html', + styleUrls: ['./map.component.scss'], +}) +export class MapComponent implements OnInit { + latitude: number; + longitude: number; + zoom: number; + + @Input() + public set searchedLocation(searchedLocation: Location) { + this.latitude = searchedLocation.latitude; + this.longitude = searchedLocation.longitude; + this.zoom = 12; + } + + ngOnInit(): void { + // set up current location + if ('geolocation' in navigator) { + navigator.geolocation.getCurrentPosition((position) => { + this.searchedLocation = new Location( + position.coords.latitude, + position.coords.longitude + ); + }); + } + } +} diff --git a/packages/admin-web-angular/src/app/pages/maps/search-map/search-map.component.html b/packages/admin-web-angular/src/app/pages/maps/search-map/search-map.component.html new file mode 100644 index 0000000..dc7866d --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/maps/search-map/search-map.component.html @@ -0,0 +1,7 @@ + + Google Maps with search + + + + + diff --git a/packages/admin-web-angular/src/app/pages/maps/search-map/search-map.component.ts b/packages/admin-web-angular/src/app/pages/maps/search-map/search-map.component.ts new file mode 100644 index 0000000..960ddb7 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/maps/search-map/search-map.component.ts @@ -0,0 +1,14 @@ +import { Component } from '@angular/core'; +import { Location } from './entity/Location'; + +@Component({ + selector: 'ngx-search-map', + templateUrl: './search-map.component.html', +}) +export class SearchMapComponent { + searchedLocation: Location = new Location(); + + updateLocation(event: Location) { + this.searchedLocation = new Location(event.latitude, event.longitude); + } +} diff --git a/packages/admin-web-angular/src/app/pages/maps/search-map/search/search.component.html b/packages/admin-web-angular/src/app/pages/maps/search-map/search/search.component.html new file mode 100644 index 0000000..ce4895b --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/maps/search-map/search/search.component.html @@ -0,0 +1,11 @@ +
+ +
diff --git a/packages/admin-web-angular/src/app/pages/maps/search-map/search/search.component.ts b/packages/admin-web-angular/src/app/pages/maps/search-map/search/search.component.ts new file mode 100644 index 0000000..248bd53 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/maps/search-map/search/search.component.ts @@ -0,0 +1,57 @@ +import { + Component, + ElementRef, + EventEmitter, + NgZone, + OnInit, + Output, + ViewChild, +} from '@angular/core'; +import { MapsAPILoader } from '@agm/core'; +import { Location } from '../entity/Location'; + +@Component({ + selector: 'ngx-search', + templateUrl: './search.component.html', +}) +export class SearchComponent implements OnInit { + @Output() positionChanged = new EventEmitter(); + + @ViewChild('search', { static: true }) + public searchElementRef: ElementRef; + + constructor(private mapsAPILoader: MapsAPILoader, private ngZone: NgZone) {} + + ngOnInit() { + // load Places Autocomplete + this.mapsAPILoader.load().then(() => { + const autocomplete = new google.maps.places.Autocomplete( + this.searchElementRef.nativeElement, + { + types: ['address'], + } + ); + autocomplete.addListener('place_changed', () => { + this.ngZone.run(() => { + // get the place result + const place: google.maps.places.PlaceResult = autocomplete.getPlace(); + + // verify result + if ( + place.geometry === undefined || + place.geometry === null + ) { + return; + } + + this.positionChanged.emit( + new Location( + place.geometry.location.lat(), + place.geometry.location.lng() + ) + ); + }); + }); + }); + } +} diff --git a/packages/admin-web-angular/src/app/pages/miscellaneous/miscellaneous-routing.module.ts b/packages/admin-web-angular/src/app/pages/miscellaneous/miscellaneous-routing.module.ts new file mode 100644 index 0000000..a39860c --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/miscellaneous/miscellaneous-routing.module.ts @@ -0,0 +1,26 @@ +import { NgModule } from '@angular/core'; +import { RouterModule, Routes } from '@angular/router'; + +import { MiscellaneousComponent } from './miscellaneous.component'; +import { NotFoundComponent } from './not-found/not-found.component'; + +const routes: Routes = [ + { + path: '', + component: MiscellaneousComponent, + children: [ + { + path: '404', + component: NotFoundComponent, + }, + ], + }, +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule], +}) +export class MiscellaneousRoutingModule {} + +export const routedComponents = [MiscellaneousComponent, NotFoundComponent]; diff --git a/packages/admin-web-angular/src/app/pages/miscellaneous/miscellaneous.component.ts b/packages/admin-web-angular/src/app/pages/miscellaneous/miscellaneous.component.ts new file mode 100644 index 0000000..76f4f54 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/miscellaneous/miscellaneous.component.ts @@ -0,0 +1,7 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'ngx-miscellaneous', + template: ` `, +}) +export class MiscellaneousComponent {} diff --git a/packages/admin-web-angular/src/app/pages/miscellaneous/miscellaneous.module.ts b/packages/admin-web-angular/src/app/pages/miscellaneous/miscellaneous.module.ts new file mode 100644 index 0000000..70ec259 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/miscellaneous/miscellaneous.module.ts @@ -0,0 +1,12 @@ +import { NgModule } from '@angular/core'; +import { ThemeModule } from '../../@theme/theme.module'; +import { + MiscellaneousRoutingModule, + routedComponents, +} from './miscellaneous-routing.module'; + +@NgModule({ + imports: [ThemeModule, MiscellaneousRoutingModule], + declarations: [...routedComponents], +}) +export class MiscellaneousModule {} diff --git a/packages/admin-web-angular/src/app/pages/miscellaneous/not-found/not-found.component.html b/packages/admin-web-angular/src/app/pages/miscellaneous/not-found/not-found.component.html new file mode 100644 index 0000000..6bd302c --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/miscellaneous/not-found/not-found.component.html @@ -0,0 +1,21 @@ +
+
+ + +
+

404 Page Not Found

+ The page you were looking for doesn't exist + +
+
+
+
+
diff --git a/packages/admin-web-angular/src/app/pages/miscellaneous/not-found/not-found.component.scss b/packages/admin-web-angular/src/app/pages/miscellaneous/not-found/not-found.component.scss new file mode 100644 index 0000000..60dbf6e --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/miscellaneous/not-found/not-found.component.scss @@ -0,0 +1,20 @@ +.flex-centered { + margin: auto; +} +nb-card-body { + display: flex; +} + +.title { + text-align: center; +} + +.sub-title { + text-align: center; + display: block; + margin-bottom: 3rem; +} + +.btn { + margin-bottom: 2rem; +} diff --git a/packages/admin-web-angular/src/app/pages/miscellaneous/not-found/not-found.component.ts b/packages/admin-web-angular/src/app/pages/miscellaneous/not-found/not-found.component.ts new file mode 100644 index 0000000..b577f34 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/miscellaneous/not-found/not-found.component.ts @@ -0,0 +1,15 @@ +import { NbMenuService } from '@nebular/theme'; +import { Component } from '@angular/core'; + +@Component({ + selector: 'ngx-not-found', + styleUrls: ['./not-found.component.scss'], + templateUrl: './not-found.component.html', +}) +export class NotFoundComponent { + constructor(private menuService: NbMenuService) {} + + goToHome() { + this.menuService.navigateHome(); + } +} diff --git a/packages/admin-web-angular/src/app/pages/ordersFilter/ordersFilter.ts b/packages/admin-web-angular/src/app/pages/ordersFilter/ordersFilter.ts new file mode 100644 index 0000000..0ef45c0 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/ordersFilter/ordersFilter.ts @@ -0,0 +1,53 @@ +import { filter } from 'underscore'; +import Order from '@modules/server.common/entities/Order'; +import OrderStatus from '@modules/server.common/enums/OrderStatus'; +import OrderWarehouseStatus from '@modules/server.common/enums/OrderWarehouseStatus'; + +export type OrdersFilterModes = + | 'ready' + | 'in_delivery' + | 'not_confirmed' + | 'cancelled' + | 'all'; +export type OrdersFilter = ( + orders: Order[], + mode: OrdersFilterModes +) => Order[]; + +export const ordersFilter: OrdersFilter = ( + orders: Order[], + mode: OrdersFilterModes +) => { + return filter(orders, (order) => { + switch (mode) { + // orders which are ready to be ship to the customer + case 'ready': + return ( + order.status === OrderStatus.WarehousePreparation || + (order.status === OrderStatus.InDelivery && + order.warehouseStatus === + OrderWarehouseStatus.PackagingFinished) + ); + + // orders in delivery stage + case 'in_delivery': + return order.status === OrderStatus.InDelivery; + + // orders which are not completed yet (not confirmed yet by client) + case 'not_confirmed': + return ( + !order.isConfirmed && + !order.isCancelled && + !order.isCompleted + ); + + case 'cancelled': + return order.isCancelled; + + case 'all': + + default: + return true; + } + }); +}; diff --git a/packages/admin-web-angular/src/app/pages/pages-routing.module.ts b/packages/admin-web-angular/src/app/pages/pages-routing.module.ts new file mode 100644 index 0000000..08ed960 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/pages-routing.module.ts @@ -0,0 +1,114 @@ +import { RouterModule, Routes } from '@angular/router'; +import { NgModule } from '@angular/core'; +import { PagesComponent } from './pages.component'; +import { FakeDataModuleGuard } from './+fakeData/fakeData.module.guard'; + +const routes: Routes = [ + { + path: '', + component: PagesComponent, + children: [ + { + path: '', + redirectTo: 'sign-in-redirect', + pathMatch: 'full', + }, + // { + // path: '', + // redirectTo: 'dashboard', + // pathMatch: 'full' + // }, + { + path: 'sign-in-redirect', + loadChildren: () => + import( + 'app/pages/+sign-in-redirect/sign-in-redirect.module' + ).then((m) => m.SignInRedirectModule), + }, + { + path: 'dashboard', + loadChildren: () => + import('app/pages/+dashboard/dashboard.module').then( + (m) => m.DashboardModule + ), + }, + { + path: 'simulation', + loadChildren: () => + import('app/pages/+simulation/simulation.module').then( + (m) => m.SimulationModule + ), + }, + { + path: 'stores', + loadChildren: () => + import('app/pages/+warehouses/warehouses.module').then( + (m) => m.WarehousesModule + ), + }, + { + path: 'carriers', + loadChildren: () => + import('app/pages/+carriers/carriers.module').then( + (m) => m.CarriersModule + ), + }, + { + path: 'setup', + loadChildren: () => + import('app/pages/+setup/setup.module').then( + (m) => m.SetupModule + ), + }, + { + path: 'generate-initial-data', + loadChildren: () => + import('app/pages/+fakeData/fakeData.module').then( + (m) => m.FakeDataModule + ), + canActivate: [FakeDataModuleGuard], + }, + { + path: 'devices', + loadChildren: () => + import('app/pages/+device/device.module').then( + (m) => m.DeviceModule + ), + }, + { + path: 'customers', + loadChildren: () => + import('app/pages/+customers/customers.module').then( + (m) => m.CustomersModule + ), + }, + { + path: 'orders', + loadChildren: () => + import('app/pages/+orders/orders.module').then( + (m) => m.OrdersModule + ), + }, + { + path: 'products', + loadChildren: () => + import('app/pages/+products/products.module').then( + (m) => m.ProductsModule + ), + }, + { + path: 'profile', + loadChildren: () => + import('app/pages/+profile/profile.module').then( + (m) => m.ProfileModule + ), + }, + ], + }, +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule], +}) +export class PagesRoutingModule {} diff --git a/packages/admin-web-angular/src/app/pages/pages.component.html b/packages/admin-web-angular/src/app/pages/pages.component.html new file mode 100644 index 0000000..abeeb6e --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/pages.component.html @@ -0,0 +1,4 @@ + + + + diff --git a/packages/admin-web-angular/src/app/pages/pages.component.ts b/packages/admin-web-angular/src/app/pages/pages.component.ts new file mode 100644 index 0000000..addbd96 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/pages.component.ts @@ -0,0 +1,107 @@ +import { Component } from '@angular/core'; + +import { TranslateService } from '@ngx-translate/core'; +import { NbMenuItem } from '@nebular/theme'; + +@Component({ + selector: 'ea-pages', + templateUrl: './pages.component.html', +}) +export class PagesComponent { + menu: NbMenuItem[]; + + constructor(private translate: TranslateService) { + this.initialize(); + this._applyTranslationOnSmartTable(); + } + + initialize() { + this.menu = [ + { + title: this.getTranslation('MENU_VIEW.DASHBOARD'), + icon: 'pie-chart-outline', + link: '/dashboard', + pathMatch: 'prefix', + }, + { + title: this.getTranslation('MENU_VIEW.STORES'), + icon: 'home-outline', + link: '/stores', + pathMatch: 'prefix', + }, + { + title: this.getTranslation('MENU_VIEW.PRODUCTS.PRODUCTS'), + icon: 'shopping-cart-outline', + link: '/products', + children: [ + { + title: this.getTranslation( + 'MENU_VIEW.PRODUCTS.MANAGEMENT' + ), + link: '/products/list', + }, + { + title: this.getTranslation( + 'MENU_VIEW.PRODUCTS.CATEGORIES' + ), + link: '/products/categories', + pathMatch: 'prefix', + }, + ], + }, + { + title: this.getTranslation('MENU_VIEW.CUSTOMERS.CUSTOMERS'), + icon: 'person-outline', + link: '/customers', + children: [ + { + title: this.getTranslation( + 'MENU_VIEW.CUSTOMERS.MANAGEMENT' + ), + link: '/customers/list', + }, + { + title: this.getTranslation( + 'MENU_VIEW.CUSTOMERS.INVITES' + ), + link: '/customers/invites', + pathMatch: 'prefix', + }, + ], + }, + { + title: this.getTranslation('MENU_VIEW.CARRIERS'), + icon: 'car-outline', + link: '/carriers', + pathMatch: 'prefix', + }, + { + title: this.getTranslation('MENU_VIEW.SIMULATION'), + icon: 'star-outline', + link: '/simulation', + pathMatch: 'prefix', + home: true, + }, + { + title: this.getTranslation('MENU_VIEW.SETUP'), + icon: 'settings-2-outline', + link: '/setup', + pathMatch: 'prefix', + }, + ]; + } + + getTranslation(prefix: string) { + let result = ''; + this.translate.get(prefix).subscribe((res) => { + result = res; + }); + return result; + } + + private _applyTranslationOnSmartTable() { + this.translate.onLangChange.subscribe(() => { + this.initialize(); + }); + } +} diff --git a/packages/admin-web-angular/src/app/pages/pages.module.ts b/packages/admin-web-angular/src/app/pages/pages.module.ts new file mode 100644 index 0000000..4ebe0a2 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/pages.module.ts @@ -0,0 +1,23 @@ +import { NgModule } from '@angular/core'; +import { PagesComponent } from './pages.component'; +import { DashboardModule } from './dashboard/dashboard.module'; +import { PagesRoutingModule } from './pages-routing.module'; +import { ThemeModule } from '../@theme/theme.module'; +import { MiscellaneousModule } from './miscellaneous/miscellaneous.module'; +import { SignInRedirectModule } from './+sign-in-redirect/sign-in-redirect.module'; +import { FakeDataModuleGuard } from './+fakeData/fakeData.module.guard'; + +const PAGES_COMPONENTS = [PagesComponent]; + +@NgModule({ + imports: [ + PagesRoutingModule, + ThemeModule, + DashboardModule, + SignInRedirectModule, + MiscellaneousModule, + ], + providers: [FakeDataModuleGuard], + declarations: [...PAGES_COMPONENTS], +}) +export class PagesModule {} diff --git a/packages/admin-web-angular/src/app/pages/tables/smart-table/smart-table.component.html b/packages/admin-web-angular/src/app/pages/tables/smart-table/smart-table.component.html new file mode 100644 index 0000000..fede225 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/tables/smart-table/smart-table.component.html @@ -0,0 +1,14 @@ + + + Smart Table + + + + + + + diff --git a/packages/admin-web-angular/src/app/pages/tables/smart-table/smart-table.component.ts b/packages/admin-web-angular/src/app/pages/tables/smart-table/smart-table.component.ts new file mode 100644 index 0000000..1393785 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/tables/smart-table/smart-table.component.ts @@ -0,0 +1,75 @@ +import { Component } from '@angular/core'; +import { LocalDataSource } from 'ng2-smart-table'; + +import { SmartTableService } from '../../../@core/data/smart-table.service'; + +@Component({ + selector: 'ngx-smart-table', + templateUrl: './smart-table.component.html', + styles: [ + ` + nb-card { + transform: translate3d(0, 0, 0); + } + `, + ], +}) +export class SmartTableComponent { + settings = { + add: { + addButtonContent: '', + createButtonContent: '', + cancelButtonContent: '', + }, + edit: { + editButtonContent: '', + saveButtonContent: '', + cancelButtonContent: '', + }, + delete: { + deleteButtonContent: '', + confirmDelete: true, + }, + columns: { + id: { + title: 'ID', + type: 'number', + }, + firstName: { + title: 'First Name', + type: 'string', + }, + lastName: { + title: 'Last Name', + type: 'string', + }, + username: { + title: 'Username', + type: 'string', + }, + email: { + title: 'E-mail', + type: 'string', + }, + age: { + title: 'Age', + type: 'number', + }, + }, + }; + + source: LocalDataSource = new LocalDataSource(); + + constructor(private service: SmartTableService) { + const data = this.service.getData(); + this.source.load(data); + } + + onDeleteConfirm(event): void { + if (window.confirm('Are you sure you want to delete?')) { + event.confirm.resolve(); + } else { + event.confirm.reject(); + } + } +} diff --git a/packages/admin-web-angular/src/app/pages/tables/tables-routing.module.ts b/packages/admin-web-angular/src/app/pages/tables/tables-routing.module.ts new file mode 100644 index 0000000..3580c5c --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/tables/tables-routing.module.ts @@ -0,0 +1,26 @@ +import { NgModule } from '@angular/core'; +import { RouterModule, Routes } from '@angular/router'; + +import { TablesComponent } from './tables.component'; +import { SmartTableComponent } from './smart-table/smart-table.component'; + +const routes: Routes = [ + { + path: '', + component: TablesComponent, + children: [ + { + path: 'smart-table', + component: SmartTableComponent, + }, + ], + }, +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule], +}) +export class TablesRoutingModule {} + +export const routedComponents = [TablesComponent, SmartTableComponent]; diff --git a/packages/admin-web-angular/src/app/pages/tables/tables.component.ts b/packages/admin-web-angular/src/app/pages/tables/tables.component.ts new file mode 100644 index 0000000..4fae211 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/tables/tables.component.ts @@ -0,0 +1,7 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'ngx-tables', + template: ` `, +}) +export class TablesComponent {} diff --git a/packages/admin-web-angular/src/app/pages/tables/tables.module.ts b/packages/admin-web-angular/src/app/pages/tables/tables.module.ts new file mode 100644 index 0000000..f330fef --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/tables/tables.module.ts @@ -0,0 +1,13 @@ +import { NgModule } from '@angular/core'; +import { Ng2SmartTableModule } from 'ng2-smart-table'; + +import { ThemeModule } from '../../@theme/theme.module'; +import { routedComponents, TablesRoutingModule } from './tables-routing.module'; +import { SmartTableService } from '../../@core/data/smart-table.service'; + +@NgModule({ + imports: [ThemeModule, TablesRoutingModule, Ng2SmartTableModule], + declarations: [...routedComponents], + providers: [SmartTableService], +}) +export class TablesModule {} diff --git a/packages/admin-web-angular/src/app/pages/ui-features/buttons/action-groups/action-groups.component.html b/packages/admin-web-angular/src/app/pages/ui-features/buttons/action-groups/action-groups.component.html new file mode 100644 index 0000000..1e375a3 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/ui-features/buttons/action-groups/action-groups.component.html @@ -0,0 +1,17 @@ + + +
+ Action Groups +
+ + + + + + + + + + +
+
diff --git a/packages/admin-web-angular/src/app/pages/ui-features/buttons/action-groups/action-groups.component.scss b/packages/admin-web-angular/src/app/pages/ui-features/buttons/action-groups/action-groups.component.scss new file mode 100644 index 0000000..359e5cc --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/ui-features/buttons/action-groups/action-groups.component.scss @@ -0,0 +1,75 @@ +// @import '../../../../@theme/styles/themes'; +// @import '~@nebular/theme/components/card/card.component.theme'; +// @import '~bootstrap/scss/mixins/breakpoints'; +// @import '~@nebular/theme/styles/global/breakpoints'; + +// @include nb-install-component() { +// nb-card-body { +// display: flex; +// align-items: center; +// } + +// .action-groups-header { +// flex-basis: 20%; + +// color: nb-theme(card-header-fg-heading); +// font-family: nb-theme(card-header-font-family); +// font-size: nb-theme(card-header-font-size); +// font-weight: nb-theme(card-header-font-weight); +// } + +// .nb-actions { +// flex-basis: 80%; +// } + +// @include media-breakpoint-down(sm) { +// nb-card-body { +// flex-direction: column; +// align-items: flex-start; +// padding: 0.75rem; + +// .action-groups-header { +// @include nb-ltr(margin, 0 0 0.5rem 0.25rem); +// @include nb-rtl(margin, 0 0.25rem 0.5rem 0); +// } +// } + +// nb-action { +// padding: 0 0.5rem; +// ::ng-deep .control-icon { +// font-size: 1.75rem; +// } +// } +// nb-user ::ng-deep { +// .user-container { +// font-size: 0.875rem; +// } +// .user-picture { +// height: 1.75rem; +// width: 1.75rem; +// } +// } +// } + +// @include media-breakpoint-down(xs) { +// nb-action { +// padding: 0 0.375rem; +// ::ng-deep .control-icon { +// font-size: 1.5rem; +// } +// } +// nb-user ::ng-deep { +// .user-container { +// font-size: 0.75rem; +// } +// .user-picture { +// height: 1.5rem; +// width: 1.5rem; +// } +// } +// nb-card-body { +// padding-left: 0; +// padding-right: 0; +// } +// } +// } diff --git a/packages/admin-web-angular/src/app/pages/ui-features/buttons/action-groups/action-groups.component.ts b/packages/admin-web-angular/src/app/pages/ui-features/buttons/action-groups/action-groups.component.ts new file mode 100644 index 0000000..6c2327c --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/ui-features/buttons/action-groups/action-groups.component.ts @@ -0,0 +1,8 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'ngx-action-groups', + styleUrls: ['./action-groups.component.scss'], + templateUrl: './action-groups.component.html', +}) +export class ActionGroupsComponent {} diff --git a/packages/admin-web-angular/src/app/pages/ui-features/buttons/block-level-buttons/block-level-buttons.component.html b/packages/admin-web-angular/src/app/pages/ui-features/buttons/block-level-buttons/block-level-buttons.component.html new file mode 100644 index 0000000..1f4f004 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/ui-features/buttons/block-level-buttons/block-level-buttons.component.html @@ -0,0 +1,23 @@ + + Block Level Buttons + +
+
+
+ + +
+ +
+
+
+
diff --git a/packages/admin-web-angular/src/app/pages/ui-features/buttons/block-level-buttons/block-level-buttons.component.ts b/packages/admin-web-angular/src/app/pages/ui-features/buttons/block-level-buttons/block-level-buttons.component.ts new file mode 100644 index 0000000..3b6a0ea --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/ui-features/buttons/block-level-buttons/block-level-buttons.component.ts @@ -0,0 +1,7 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'ngx-block-level-buttons', + templateUrl: './block-level-buttons.component.html', +}) +export class BlockLevelButtonsComponent {} diff --git a/packages/admin-web-angular/src/app/pages/ui-features/buttons/button-groups/button-groups.component.html b/packages/admin-web-angular/src/app/pages/ui-features/buttons/button-groups/button-groups.component.html new file mode 100644 index 0000000..1f6bac8 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/ui-features/buttons/button-groups/button-groups.component.html @@ -0,0 +1,338 @@ + + Button Groups + +
+
+
+
Toggle Types
+
+ + + +
+
+ + + +
+
+
+
+
Pagination
+
+ + + + + +
+
+ +
+
Icon Toolbar
+
+ + + + + +
+
+
+
+
+ Divided Button Group +
+
+ + + + + + +
+
+
+
Divided Button Group
+
+
+ + + +
+
+
+ + + +
+
+
+
+
+
diff --git a/packages/admin-web-angular/src/app/pages/ui-features/buttons/button-groups/button-groups.component.scss b/packages/admin-web-angular/src/app/pages/ui-features/buttons/button-groups/button-groups.component.scss new file mode 100644 index 0000000..b3705cd --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/ui-features/buttons/button-groups/button-groups.component.scss @@ -0,0 +1,66 @@ +@import '../../../../@theme/styles/themes'; +@import '~bootstrap/scss/mixins/breakpoints'; +@import '~@nebular/theme/styles/global/breakpoints'; + +@include nb-install-component() { + .toolbars-container { + display: flex; + flex-direction: column; + } + + .pagination-container { + @include nb-ltr(margin-right, 1rem); + @include nb-rtl(margin-left, 1rem); + + .btn-group > .btn { + padding-left: 1.125rem; + padding-right: 1.125rem; + } + } + + .icon-toolbar-container { + .btn-group > .btn { + padding-left: 1.125rem; + padding-right: 1.125rem; + } + } + + .toggle-types { + .btn-toggle-radio-group { + margin-bottom: 1rem; + } + } + + .divided-button-group { + .btn-divided-checkbox-group { + margin-bottom: 1rem; + flex-wrap: wrap; + } + } + + .example-container:not(:last-child) { + margin-bottom: 1.5rem; + } + + .example-container > div { + &:not(:last-child) { + margin-bottom: 1rem; + } + } + + .example-container > .btn-divided-checkbox-group { + flex-wrap: wrap; + + > label { + flex-basis: 10%; + @include nb-ltr(margin, 0 0.25rem 0.5rem 0); + @include nb-rtl(margin, 0 0 0.5rem 0.25rem); + } + } + + @include media-breakpoint-down(sm) { + .btn { + padding: 0.75rem 0.7rem; + } + } +} diff --git a/packages/admin-web-angular/src/app/pages/ui-features/buttons/button-groups/button-groups.component.ts b/packages/admin-web-angular/src/app/pages/ui-features/buttons/button-groups/button-groups.component.ts new file mode 100644 index 0000000..41516a3 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/ui-features/buttons/button-groups/button-groups.component.ts @@ -0,0 +1,43 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'ngx-button-groups', + styleUrls: ['./button-groups.component.scss'], + templateUrl: './button-groups.component.html', +}) +export class ButtonGroupsComponent { + radioModel = 'left'; + + checkboxModel = { + left: false, + middle: false, + right: false, + }; + + dividedCheckboxModel = { + monday: true, + tuesday: true, + wednesday: false, + thursday: false, + friday: false, + saturday: false, + }; + + paginationModel = 1; + + iconToolbarModel = { + one: false, + two: false, + three: true, + four: false, + five: false, + }; + + dividedButtonGroupOne = 'left'; + + dividedButtonGroupTwo = { + left: false, + middle: false, + right: false, + }; +} diff --git a/packages/admin-web-angular/src/app/pages/ui-features/buttons/buttons.component.html b/packages/admin-web-angular/src/app/pages/ui-features/buttons/buttons.component.html new file mode 100644 index 0000000..3567837 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/ui-features/buttons/buttons.component.html @@ -0,0 +1,25 @@ +
+
+ +
+
+ + + + +
+
+ + +
+
+ +
+
+
+
+ + + +
+
diff --git a/packages/admin-web-angular/src/app/pages/ui-features/buttons/buttons.component.scss b/packages/admin-web-angular/src/app/pages/ui-features/buttons/buttons.component.scss new file mode 100644 index 0000000..61d16fc --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/ui-features/buttons/buttons.component.scss @@ -0,0 +1,102 @@ +// @import '../../../@theme/styles/themes'; +// @import '~@nebular/theme/styles/global/bootstrap/buttons'; +// @import '~bootstrap/scss/mixins/breakpoints'; +// @import '~@nebular/theme/styles/global/breakpoints'; + +// @include nb-install-component() { +// ::ng-deep { +// $button-size: 50px; + +// .container-title { +// color: nb-theme(color-fg); +// font-family: nb-theme(font-secondary); +// margin-bottom: 0.5rem; +// } + +// .header { +// color: nb-theme(color-fg-header); +// font-size: 0.875rem; +// } + +// .subheader { +// font-size: 0.75rem; +// font-weight: nb-theme(font-weight-light); +// color: nb-theme(color-fg); +// } + +// .btn-demo { +// width: 180px; +// } + +// .state-container { +// display: flex; + +// &:not(:last-child) { +// margin-bottom: 1rem; +// } + +// .state-value { +// width: $button-size; +// height: $button-size; +// border-radius: nb-theme(btn-border-radius); + +// @include nb-for-theme(corporate) { +// border-radius: nb-theme(btn-semi-round-border-radius); +// } +// } + +// .state-details { +// display: flex; +// flex-direction: column; +// justify-content: center; +// margin-left: 1rem; +// margin-right: 1rem; +// height: $button-size; +// } +// } + +// .example-container { +// @include nb-ltr(padding-right, 0); +// @include nb-rtl(padding-left, 0); +// } + +// .example-container .container-btn { +// margin-bottom: 1.5rem; +// } + +// .block-level-buttons .btn-group { +// margin-bottom: 1rem; +// } +// } + +// @include media-breakpoint-down(is) { +// ngx-default-buttons ::ng-deep nb-card-header { +// flex-direction: column; +// align-items: left; + +// span { +// margin-bottom: 1rem; +// } +// } +// } + +// @include media-breakpoint-down(xs) { +// ::ng-deep.icon-buttons .icon-button-examples { +// button { +// @include nb-ltr(margin-right, 1rem); +// @include nb-rtl(margin-left, 1rem); +// } +// } + +// ngx-default-buttons ::ng-deep nb-card-header { +// flex-direction: column; +// margin-bottom: 0.5rem; +// } + +// ngx-block-level-buttons ::ng-deep { +// .btn-primary { +// padding: 0.75rem 1rem; +// } +// } +// } +// } diff --git a/packages/admin-web-angular/src/app/pages/ui-features/buttons/buttons.component.ts b/packages/admin-web-angular/src/app/pages/ui-features/buttons/buttons.component.ts new file mode 100644 index 0000000..1dcdc13 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/ui-features/buttons/buttons.component.ts @@ -0,0 +1,8 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'ngx-buttons', + styleUrls: ['./buttons.component.scss'], + templateUrl: './buttons.component.html', +}) +export class ButtonsComponent {} diff --git a/packages/admin-web-angular/src/app/pages/ui-features/buttons/buttons.module.ts b/packages/admin-web-angular/src/app/pages/ui-features/buttons/buttons.module.ts new file mode 100644 index 0000000..b0e3aad --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/ui-features/buttons/buttons.module.ts @@ -0,0 +1,36 @@ +import { NgModule } from '@angular/core'; + +import { ThemeModule } from '../../../@theme/theme.module'; +import { DefaultButtonsComponent } from './default-buttons/default-buttons.component'; +import { HeroButtonComponent } from './hero-buttons/hero-buttons.component'; +import { ShapeButtonsComponent } from './shape-buttons/shape-buttons.component'; +import { SizeButtonsComponent } from './size-buttons/size-buttons.component'; +import { ButtonsComponent } from './buttons.component'; +import { ActionGroupsComponent } from './action-groups/action-groups.component'; +import { DropdownButtonsComponent } from './dropdown-buttons/dropdown-button.component'; +import { BlockLevelButtonsComponent } from './block-level-buttons/block-level-buttons.component'; +import { ButtonGroupsComponent } from './button-groups/button-groups.component'; +import { IconButtonsComponent } from './icon-buttons/icon-buttons.component'; +import { LabeledActionsGroupComponent } from './labeled-actions-group/labeled-actions-group.component'; + +const components = [ + ButtonsComponent, + DefaultButtonsComponent, + HeroButtonComponent, + ShapeButtonsComponent, + SizeButtonsComponent, + ActionGroupsComponent, + DropdownButtonsComponent, + BlockLevelButtonsComponent, + ButtonGroupsComponent, + IconButtonsComponent, + LabeledActionsGroupComponent, +]; + +@NgModule({ + imports: [ThemeModule], + exports: [...components], + declarations: [...components], + providers: [], +}) +export class ButtonsModule {} diff --git a/packages/admin-web-angular/src/app/pages/ui-features/buttons/default-buttons/default-buttons.component.html b/packages/admin-web-angular/src/app/pages/ui-features/buttons/default-buttons/default-buttons.component.html new file mode 100644 index 0000000..694a7c7 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/ui-features/buttons/default-buttons/default-buttons.component.html @@ -0,0 +1,59 @@ + + + Default Buttons + + + +
+
+
+ {{ b.containerTitle }} +
+
+ +
+
+
+
+
+ Default + {{ b.default }} +
+
+
+
+
+ Hover + 14% white +
+
+
+
+
+ Active + 14% black +
+
+
+
+
+
+
diff --git a/packages/admin-web-angular/src/app/pages/ui-features/buttons/default-buttons/default-buttons.component.scss b/packages/admin-web-angular/src/app/pages/ui-features/buttons/default-buttons/default-buttons.component.scss new file mode 100644 index 0000000..3ca5b51 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/ui-features/buttons/default-buttons/default-buttons.component.scss @@ -0,0 +1,126 @@ +// @import '../../../../@theme/styles/themes'; +// @import '~@nebular/theme/styles/global/bootstrap/buttons'; + +// @include nb-install-component() { + +// nb-card-header { +// display: flex; +// align-items: center; +// justify-content: space-between; + +// .dropdown { +// flex-basis: 30%; +// min-width: 220px; +// } +// } + +// nb-card-body { +// padding-bottom: 0; +// } + +// .example-container { +// margin-bottom: 1.5rem; +// } + +// .primary-container { +// .original { +// background-color: nb-theme(btn-primary-bg); +// } +// .hover { +// @include btn-primary-hover(); +// } +// .active { +// @include btn-primary-active(); +// } +// } + +// .primary-container.outline .original { +// background-color: transparent; +// border: 2px solid nb-theme(btn-primary-bg); +// } + +// .success-container { +// .original { +// background-color: nb-theme(btn-success-bg); +// } +// .hover { +// @include btn-success-hover(); +// } +// .active { +// @include btn-success-active(); +// } +// } + +// .success-container.outline .original { +// background-color: transparent; +// border: 2px solid nb-theme(btn-success-bg); +// } + +// .warning-container { +// .original { +// background-color: nb-theme(btn-warning-bg); +// } +// .hover { +// @include btn-warning-hover(); +// } +// .active { +// @include btn-warning-active(); +// } +// } + +// .warning-container.outline .original { +// background-color: transparent; +// border: 2px solid nb-theme(btn-warning-bg); +// } + +// .info-container { +// .original { +// background-color: nb-theme(btn-info-bg); +// } +// .hover { +// @include btn-info-hover(); +// } +// .active { +// @include btn-info-active(); +// } +// } + +// .info-container.outline .original { +// background-color: transparent; +// border: 2px solid nb-theme(btn-info-bg); +// } + +// .danger-container { +// .original { +// background-color: nb-theme(btn-danger-bg); +// } +// .hover { +// @include btn-danger-hover(); +// } +// .active { +// @include btn-danger-active(); +// } +// } + +// .danger-container.outline .original { +// background-color: transparent; +// border: 2px solid nb-theme(btn-danger-bg); +// } + +// .secondary-container { +// .original { +// border: 2px solid nb-theme(btn-secondary-border); +// } +// .hover { +// @include btn-secondary-hover(); +// } +// .active { +// @include btn-secondary-active(); +// } +// } + +// .secondary-container.outline .original { +// background-color: transparent; +// border: 2px solid nb-theme(btn-secondary-border); +// } +// } diff --git a/packages/admin-web-angular/src/app/pages/ui-features/buttons/default-buttons/default-buttons.component.ts b/packages/admin-web-angular/src/app/pages/ui-features/buttons/default-buttons/default-buttons.component.ts new file mode 100644 index 0000000..cbe2b05 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/ui-features/buttons/default-buttons/default-buttons.component.ts @@ -0,0 +1,113 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'ngx-default-buttons', + styleUrls: ['./default-buttons.component.scss'], + templateUrl: './default-buttons.component.html', +}) +export class DefaultButtonsComponent { + buttonsViews = [ + { + title: 'Default Buttons', + key: 'default', + }, + { + title: 'Outline Buttons', + key: 'outline', + }, + ]; + + selectedView = this.buttonsViews[0]; + + buttons = { + default: [ + { + class: 'btn-primary', + container: 'primary-container', + containerTitle: 'Primary Button', + title: 'Primary', + default: '#7659ff', + }, + { + class: 'btn-warning', + container: 'warning-container', + containerTitle: 'Warning Button', + title: 'Warning', + default: '#ffcb17', + }, + { + class: 'btn-success', + container: 'success-container', + containerTitle: 'Success Button', + title: 'Success', + default: '#00d977', + }, + { + class: 'btn-info', + container: 'info-container', + containerTitle: 'Info Button', + title: 'Info', + default: '#0088ff', + }, + { + class: 'btn-danger', + container: 'danger-container', + containerTitle: 'Danger Button', + title: 'Danger', + default: '#ff386a', + }, + { + class: 'btn-secondary', + container: 'secondary-container', + containerTitle: 'Default Button', + title: 'Default', + default: '#bdbaff', + }, + ], + + outline: [ + { + class: 'btn-outline-primary', + container: 'primary-container outline', + containerTitle: 'Primary Button', + title: 'Primary', + default: '#7659ff', + }, + { + class: 'btn-outline-warning', + container: 'warning-container outline', + containerTitle: 'Warning Button', + title: 'Warning', + default: '#ffcb17', + }, + { + class: 'btn-outline-success', + container: 'success-container outline', + containerTitle: 'Success Button', + title: 'Success', + default: '#00d977', + }, + { + class: 'btn-outline-info', + container: 'info-container', + containerTitle: 'Info Button', + title: 'Info', + default: '#0088ff', + }, + { + class: 'btn-outline-danger', + container: 'danger-container outline', + containerTitle: 'Danger Button', + title: 'Danger', + default: '#ff386a', + }, + { + class: 'btn-outline-secondary', + container: 'secondary-container outline', + containerTitle: 'Default Button', + title: 'Default', + default: '#bdbaff', + }, + ], + }; +} diff --git a/packages/admin-web-angular/src/app/pages/ui-features/buttons/dropdown-buttons/dropdown-button.component.html b/packages/admin-web-angular/src/app/pages/ui-features/buttons/dropdown-buttons/dropdown-button.component.html new file mode 100644 index 0000000..c17de29 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/ui-features/buttons/dropdown-buttons/dropdown-button.component.html @@ -0,0 +1,52 @@ + + Button Dropdowns + + + + + + + diff --git a/packages/admin-web-angular/src/app/pages/ui-features/buttons/dropdown-buttons/dropdown-button.component.scss b/packages/admin-web-angular/src/app/pages/ui-features/buttons/dropdown-buttons/dropdown-button.component.scss new file mode 100644 index 0000000..df0e25e --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/ui-features/buttons/dropdown-buttons/dropdown-button.component.scss @@ -0,0 +1,15 @@ +@import '../../../../@theme/styles/themes'; + +@include nb-install-component() { + text-align: center; + + .dropdown, + .dropup, + .btn-group { + margin-bottom: 1rem; + } + + nb-card-body { + overflow: visible; + } +} diff --git a/packages/admin-web-angular/src/app/pages/ui-features/buttons/dropdown-buttons/dropdown-button.component.ts b/packages/admin-web-angular/src/app/pages/ui-features/buttons/dropdown-buttons/dropdown-button.component.ts new file mode 100644 index 0000000..3df898d --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/ui-features/buttons/dropdown-buttons/dropdown-button.component.ts @@ -0,0 +1,8 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'ngx-dropdown-buttons', + styleUrls: ['./dropdown-button.component.scss'], + templateUrl: './dropdown-button.component.html', +}) +export class DropdownButtonsComponent {} diff --git a/packages/admin-web-angular/src/app/pages/ui-features/buttons/hero-buttons/hero-buttons.component.html b/packages/admin-web-angular/src/app/pages/ui-features/buttons/hero-buttons/hero-buttons.component.html new file mode 100644 index 0000000..05c7d42 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/ui-features/buttons/hero-buttons/hero-buttons.component.html @@ -0,0 +1,108 @@ + + Hero Buttons + +
+
+
+ {{ hb.title }} +
+
+ +
+
+
+
+
+ Border + {{ + hb[themeName].border + }} +
+
+
+
+
+ Color + {{ + hb[themeName].color + }} +
+
+
+
+
+ Linear Gradient + {{ + hb[themeName].gradientLeft + }} + {{ + hb[themeName].gradientRight + }} +
+
+
+
+
+ Bevel + 0 3px 0 0 + {{ + hb[themeName].bevel + }} +
+
+ No Bevel +
+
+
+
+
+ Shadow + 0 4px 10px 0 + {{ + hb[themeName].shadow + }} +
+
+ No Shadow +
+
+
+
+
+ Glow + {{ + hb[themeName].glow.params + }} + {{ + hb[themeName].glow.color + }} +
+
+ No Glow +
+
+
+
+
+
+
diff --git a/packages/admin-web-angular/src/app/pages/ui-features/buttons/hero-buttons/hero-buttons.component.scss b/packages/admin-web-angular/src/app/pages/ui-features/buttons/hero-buttons/hero-buttons.component.scss new file mode 100644 index 0000000..4a658d3 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/ui-features/buttons/hero-buttons/hero-buttons.component.scss @@ -0,0 +1,135 @@ +// @import '../../../../@theme/styles/themes'; +// @import '~@nebular/theme/styles/global/bootstrap/buttons'; + +// @include nb-install-component() { + +// nb-card-body { +// padding-bottom: 0; +// } + +// .none { +// position: relative; +// transform: rotate(45deg); + +// &::before, &::after { +// position: absolute; +// content: ''; +// background: nb-theme(form-control-border-color); +// } + +// &::before { +// left: 50%; +// top: 10%; +// transform: translateX(-50%); +// width: 1px; +// height: 80%; +// } + +// &::after { +// top: 50%; +// left: 10%; +// transform: translateY(-50%); +// height: 1px; +// width: 80%; +// } +// } + +// .shadow { +// box-shadow: nb-theme(btn-hero-shadow); +// } + +// .primary-container { +// .color { +// background-color: nb-theme(color-primary); +// } +// .gradient { +// @include btn-hero-primary-gradient(); +// } +// .glow { +// box-shadow: btn-hero-primary-glow(); +// } +// .bevel { +// box-shadow: btn-hero-primary-bevel(); +// } +// } + +// .warning-container { +// .color { +// background-color: nb-theme(color-warning); +// } +// .gradient { +// @include btn-hero-warning-gradient(); +// } +// .glow { +// box-shadow: btn-hero-warning-glow(); +// } +// .bevel { +// box-shadow: btn-hero-warning-bevel(); +// } +// } + +// .success-container { +// .color { +// background-color: nb-theme(color-success); +// } +// .gradient { +// @include btn-hero-success-gradient(); +// } +// .glow { +// box-shadow: btn-hero-success-glow(); +// } +// .bevel { +// box-shadow: btn-hero-success-bevel(); +// } +// } + +// .info-container { +// .color { +// background-color: nb-theme(color-info); +// } +// .gradient { +// @include btn-hero-info-gradient(); +// } +// .glow { +// box-shadow: btn-hero-info-glow(); +// } +// .bevel { +// box-shadow: btn-hero-info-bevel(); +// } +// } + +// .danger-container { +// .color { +// background-color: nb-theme(color-danger); +// } +// .gradient { +// @include btn-hero-danger-gradient(); +// } +// .glow { +// box-shadow: btn-hero-danger-glow(); +// } +// .bevel { +// box-shadow: btn-hero-danger-bevel(); +// } +// } + +// .secondary-container { +// .color { +// background-color: nb-theme(btn-secondary-bg); +// } +// .border { +// color: nb-theme(btn-secondary-color); +// border: nb-theme(btn-secondary-border-width) solid nb-theme(btn-secondary-border); +// } +// .glow { +// box-shadow: btn-hero-secondary-glow(); +// } +// .bevel { +// box-shadow: btn-hero-secondary-bevel(); +// } +// } + +// .example-container { +// margin-bottom: 1.5rem; +// } +// } diff --git a/packages/admin-web-angular/src/app/pages/ui-features/buttons/hero-buttons/hero-buttons.component.ts b/packages/admin-web-angular/src/app/pages/ui-features/buttons/hero-buttons/hero-buttons.component.ts new file mode 100644 index 0000000..b904c1b --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/ui-features/buttons/hero-buttons/hero-buttons.component.ts @@ -0,0 +1,188 @@ +import { Component, OnDestroy } from '@angular/core'; +import { NbThemeService } from '@nebular/theme'; +import { Subscription } from 'rxjs'; + +@Component({ + selector: 'ngx-hero-buttons', + styleUrls: ['./hero-buttons.component.scss'], + templateUrl: './hero-buttons.component.html', +}) +export class HeroButtonComponent implements OnDestroy { + themeName = 'default'; + settings: any[]; + themeSubscription: Subscription; + + constructor(private themeService: NbThemeService) { + this.themeSubscription = this.themeService + .getJsTheme() + .subscribe((theme) => { + this.themeName = theme.name; + this.init(theme.variables); + }); + } + + init(colors: any) { + this.settings = [ + { + class: 'btn-hero-primary', + container: 'primary-container', + title: 'Primary Button', + buttonTitle: 'Primary', + default: { + gradientLeft: `adjust-hue(${colors.primary}, 20deg)`, + gradientRight: colors.primary, + }, + corporate: { + color: colors.primary, + glow: { + params: '0 0 20px 0', + color: 'rgba (115, 161, 255, 0.5)', + }, + }, + cosmic: { + gradientLeft: `adjust-hue(${colors.primary}, 20deg)`, + gradientRight: colors.primary, + bevel: `shade(${colors.primary}, 14%)`, + shadow: 'rgba (6, 7, 64, 0.5)', + glow: { + params: '0 2px 12px 0', + color: `adjust-hue(${colors.primary}, 10deg)`, + }, + }, + }, + { + class: 'btn-hero-warning', + container: 'warning-container', + title: 'Warning Button', + buttonTitle: 'Warning', + default: { + gradientLeft: `adjust-hue(${colors.warning}, 10deg)`, + gradientRight: colors.warning, + }, + corporate: { + color: colors.warning, + glow: { + params: '0 0 20px 0', + color: 'rgba (256, 163, 107, 0.5)', + }, + }, + cosmic: { + gradientLeft: `adjust-hue(${colors.warning}, 10deg)`, + gradientRight: colors.warning, + bevel: `shade(${colors.warning}, 14%)`, + shadow: 'rgba (33, 7, 77, 0.5)', + glow: { + params: '0 2px 12px 0', + color: `adjust-hue(${colors.warning}, 5deg)`, + }, + }, + }, + { + class: 'btn-hero-success', + container: 'success-container', + title: 'Success Button', + buttonTitle: 'Success', + default: { + gradientLeft: `adjust-hue(${colors.success}, 20deg)`, + gradientRight: colors.success, + }, + corporate: { + color: colors.success, + glow: { + params: '0 0 20px 0', + color: 'rgba (93, 207, 227, 0.5)', + }, + }, + cosmic: { + gradientLeft: `adjust-hue(${colors.success}, 20deg)`, + gradientRight: colors.success, + bevel: `shade(${colors.success}, 14%)`, + shadow: 'rgba (33, 7, 77, 0.5)', + glow: { + params: '0 2px 12px 0', + color: `adjust-hue(${colors.success}, 10deg)`, + }, + }, + }, + { + class: 'btn-hero-info', + container: 'info-container', + title: 'Info Button', + buttonTitle: 'Info', + default: { + gradientLeft: `adjust-hue(${colors.info}, -10deg)`, + gradientRight: colors.info, + }, + corporate: { + color: colors.info, + glow: { + params: '0 0 20px 0', + color: 'rgba (186, 127, 236, 0.5)', + }, + }, + cosmic: { + gradientLeft: `adjust-hue(${colors.info}, -10deg)`, + gradientRight: colors.info, + bevel: `shade(${colors.info}, 14%)`, + shadow: 'rgba (33, 7, 77, 0.5)', + glow: { + params: '0 2px 12px 0', + color: `adjust-hue(${colors.info}, -5deg)`, + }, + }, + }, + { + class: 'btn-hero-danger', + container: 'danger-container', + title: 'Danger Button', + buttonTitle: 'Danger', + default: { + gradientLeft: `adjust-hue(${colors.danger}, -20deg)`, + gradientRight: colors.danger, + }, + corporate: { + color: colors.danger, + glow: { + params: '0 0 20px 0', + color: 'rgba (255, 107, 131, 0.5)', + }, + }, + cosmic: { + gradientLeft: `adjust-hue(${colors.danger}, -20deg)`, + gradientRight: colors.danger, + bevel: `shade(${colors.danger}, 14%)`, + shadow: 'rgba (33, 7, 77, 0.5)', + glow: { + params: '0 2px 12px 0', + color: `adjust-hue(${colors.danger}, -10deg)`, + }, + }, + }, + { + class: 'btn-hero-secondary', + container: 'secondary-container', + title: 'Ghost Button', + buttonTitle: 'Ghost', + default: { + border: '#dadfe6', + }, + corporate: { + color: '#edf2f5', + }, + cosmic: { + border: colors.primary, + bevel: '#665ebd', + shadow: 'rgba (33, 7, 77, 0.5)', + glow: { + params: '0 2px 12px 0', + color: 'rgba (146, 141, 255, 1)', + }, + }, + }, + ]; + } + + ngOnDestroy() { + this.themeSubscription.unsubscribe(); + } +} diff --git a/packages/admin-web-angular/src/app/pages/ui-features/buttons/icon-buttons/icon-buttons.component.html b/packages/admin-web-angular/src/app/pages/ui-features/buttons/icon-buttons/icon-buttons.component.html new file mode 100644 index 0000000..a391987 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/ui-features/buttons/icon-buttons/icon-buttons.component.html @@ -0,0 +1,57 @@ + + Icon buttons + +
+
+
+ + +
+
+ +
+
+ + + +
+
+ + + +
+
+
+
+
diff --git a/packages/admin-web-angular/src/app/pages/ui-features/buttons/icon-buttons/icon-buttons.component.scss b/packages/admin-web-angular/src/app/pages/ui-features/buttons/icon-buttons/icon-buttons.component.scss new file mode 100644 index 0000000..444b4af --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/ui-features/buttons/icon-buttons/icon-buttons.component.scss @@ -0,0 +1,30 @@ +nb-card-body { + div:not(:last-child) { + margin-bottom: 1rem; + } +} + +.btn-with-icon-example { + width: 100%; + + .btn { + width: 100%; + } +} + +.btn-group:not(:last-child) { + margin-bottom: 1rem; +} + +.icon-button-examples { + display: flex; + justify-content: space-between; + + button { + min-width: 4rem; + } +} + +.icon-button-examples:not(:last-child) { + margin-bottom: 1rem; +} diff --git a/packages/admin-web-angular/src/app/pages/ui-features/buttons/icon-buttons/icon-buttons.component.ts b/packages/admin-web-angular/src/app/pages/ui-features/buttons/icon-buttons/icon-buttons.component.ts new file mode 100644 index 0000000..509e0cb --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/ui-features/buttons/icon-buttons/icon-buttons.component.ts @@ -0,0 +1,8 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'ngx-icon-buttons', + styleUrls: ['./icon-buttons.component.scss'], + templateUrl: './icon-buttons.component.html', +}) +export class IconButtonsComponent {} diff --git a/packages/admin-web-angular/src/app/pages/ui-features/buttons/labeled-actions-group/labeled-actions-group.component.html b/packages/admin-web-angular/src/app/pages/ui-features/buttons/labeled-actions-group/labeled-actions-group.component.html new file mode 100644 index 0000000..98310fc --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/ui-features/buttons/labeled-actions-group/labeled-actions-group.component.html @@ -0,0 +1,14 @@ + + + + + Pause + + Logs + + Search + + Setup + + + diff --git a/packages/admin-web-angular/src/app/pages/ui-features/buttons/labeled-actions-group/labeled-actions-group.component.scss b/packages/admin-web-angular/src/app/pages/ui-features/buttons/labeled-actions-group/labeled-actions-group.component.scss new file mode 100644 index 0000000..e83b924 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/ui-features/buttons/labeled-actions-group/labeled-actions-group.component.scss @@ -0,0 +1,59 @@ +// @import '../../../../@theme/styles/themes'; +// @import '~bootstrap/scss/mixins/breakpoints'; +// @import '~@nebular/theme/styles/global/breakpoints'; + +// @include nb-install-component() { +// nb-actions > nb-action { +// padding: 0; +// } + +// nb-action { +// i { +// color: nb-theme(color-fg); +// font-size: 2.5rem; +// @include nb-ltr(margin-right, 1rem); +// @include nb-rtl(margin-left, 1rem); + +// @include nb-for-theme(corporate) { +// color: nb-theme(actions-fg); +// } +// } + +// span { +// font-family: nb-theme(font-secondary); +// font-weight: 500; +// color: #2a2a2a; +// text-transform: uppercase; +// } +// } + +// @include media-breakpoint-down(md) { +// nb-actions nb-action { +// padding: 0 0.75rem; +// } +// } +// @include media-breakpoint-down(sm) { +// nb-card-body { +// padding: 1rem; +// } + +// nb-action { +// font-size: 0.75rem; +// i { +// font-size: 2rem; +// @include nb-ltr(margin-right, 0.5rem); +// @include nb-rtl(margin-left, 0.5rem); +// } +// } +// } + +// @include media-breakpoint-down(is) { +// nb-action i { +// font-size: 1.75rem; +// margin: 0; +// } +// span { +// display: none; +// } +// } +// } diff --git a/packages/admin-web-angular/src/app/pages/ui-features/buttons/labeled-actions-group/labeled-actions-group.component.ts b/packages/admin-web-angular/src/app/pages/ui-features/buttons/labeled-actions-group/labeled-actions-group.component.ts new file mode 100644 index 0000000..1bf8561 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/ui-features/buttons/labeled-actions-group/labeled-actions-group.component.ts @@ -0,0 +1,8 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'ngx-labeled-actions-group', + styleUrls: ['./labeled-actions-group.component.scss'], + templateUrl: './labeled-actions-group.component.html', +}) +export class LabeledActionsGroupComponent {} diff --git a/packages/admin-web-angular/src/app/pages/ui-features/buttons/shape-buttons/shape-buttons.component.html b/packages/admin-web-angular/src/app/pages/ui-features/buttons/shape-buttons/shape-buttons.component.html new file mode 100644 index 0000000..68044ca --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/ui-features/buttons/shape-buttons/shape-buttons.component.html @@ -0,0 +1,49 @@ + + Button Shapes + +
+
+ Rectangle Button +
+
+ Border radius: + 4px +
+
+ +
+
+ +
+
+ Semi-round Button +
+
+ Border radius: + 12px +
+
+ +
+
+ +
+
+ Rounded Button +
+
+ Border radius: + round +
+
+ +
+
+
+
diff --git a/packages/admin-web-angular/src/app/pages/ui-features/buttons/shape-buttons/shape-buttons.component.scss b/packages/admin-web-angular/src/app/pages/ui-features/buttons/shape-buttons/shape-buttons.component.scss new file mode 100644 index 0000000..150f032 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/ui-features/buttons/shape-buttons/shape-buttons.component.scss @@ -0,0 +1,30 @@ +// @import '../../../../@theme/styles/themes'; +// @import '~@nebular/theme/styles/global/bootstrap/buttons'; + +// @include nb-install-component() { + +// nb-card-body { +// padding: 0 0 29px; +// display: flex; +// flex-wrap: wrap; +// justify-content: space-between; +// } + +// .shape-container { +// margin: 1.25rem 1.25rem 0; +// } + +// .container-title { +// margin-bottom: 0.25rem; +// } + +// .subheader { +// margin-bottom: 1rem; +// font-size: 0.875rem; + +// span:nth-child(2) { +// color: #2a2a2a; +// font-weight: 500; +// } +// } +// } diff --git a/packages/admin-web-angular/src/app/pages/ui-features/buttons/shape-buttons/shape-buttons.component.ts b/packages/admin-web-angular/src/app/pages/ui-features/buttons/shape-buttons/shape-buttons.component.ts new file mode 100644 index 0000000..feddee5 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/ui-features/buttons/shape-buttons/shape-buttons.component.ts @@ -0,0 +1,8 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'ngx-shape-buttons', + styleUrls: ['./shape-buttons.component.scss'], + templateUrl: './shape-buttons.component.html', +}) +export class ShapeButtonsComponent {} diff --git a/packages/admin-web-angular/src/app/pages/ui-features/buttons/size-buttons/size-buttons.component.html b/packages/admin-web-angular/src/app/pages/ui-features/buttons/size-buttons/size-buttons.component.html new file mode 100644 index 0000000..b3e61dc --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/ui-features/buttons/size-buttons/size-buttons.component.html @@ -0,0 +1,50 @@ + + Button Sizes + + +
+
+ Large Button +
+
+ 0.875rem 1.75rem +
+
+ +
+
+
+
+ Medium Button +
+
+ 0.75rem 1.5rem +
+
+ +
+
+
+
+ Small Button +
+
+ 0.675rem 1.5rem +
+
+ +
+
+
+
+ Tiny Button +
+
+ 0.5rem 1.25rem +
+
+ +
+
+
+
diff --git a/packages/admin-web-angular/src/app/pages/ui-features/buttons/size-buttons/size-buttons.component.scss b/packages/admin-web-angular/src/app/pages/ui-features/buttons/size-buttons/size-buttons.component.scss new file mode 100644 index 0000000..bd67478 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/ui-features/buttons/size-buttons/size-buttons.component.scss @@ -0,0 +1,26 @@ +// @import '../../../../@theme/styles/themes'; +// @import '~@nebular/theme/styles/global/bootstrap/buttons'; + +// @include nb-install-component() { + +// nb-card-body { +// padding: 0 1.25rem 1.25rem 0; +// display: flex; +// flex-wrap: wrap; +// } + +// .container-title { +// margin-bottom: 0.25rem; +// } + +// .size-container { +// margin: 1.25rem 0 0 1.25rem; +// } + +// .subheader { +// margin-bottom: 0.75rem; +// font-size: 0.875rem; +// font-weight: nb-theme(font-weight-bolder); +// color: #2a2a2a; +// } +// } diff --git a/packages/admin-web-angular/src/app/pages/ui-features/buttons/size-buttons/size-buttons.component.ts b/packages/admin-web-angular/src/app/pages/ui-features/buttons/size-buttons/size-buttons.component.ts new file mode 100644 index 0000000..23099df --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/ui-features/buttons/size-buttons/size-buttons.component.ts @@ -0,0 +1,8 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'ngx-size-buttons', + styleUrls: ['./size-buttons.component.scss'], + templateUrl: './size-buttons.component.html', +}) +export class SizeButtonsComponent {} diff --git a/packages/admin-web-angular/src/app/pages/ui-features/grid/grid.component.html b/packages/admin-web-angular/src/app/pages/ui-features/grid/grid.component.html new file mode 100644 index 0000000..c18ddf6 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/ui-features/grid/grid.component.html @@ -0,0 +1,286 @@ +
+
+ + + Grid System + + +
Stacked to horizontal
+
+
+
.col-md-1
+
+
+
.col-md-1
+
+
+
.col-md-1
+
+
+
.col-md-1
+
+
+
.col-md-1
+
+
+
.col-md-1
+
+
+
.col-md-1
+
+
+
.col-md-1
+
+
+
.col-md-1
+
+
+
.col-md-1
+
+
+
.col-md-1
+
+
+
.col-md-1
+
+
+
+
+
.col-md-8
+
+
+
.col-md-4
+
+
+
+
+
.col-md-4
+
+
+
.col-md-4
+
+
+
.col-md-4
+
+
+
+
+
.col-md-6
+
+
+
.col-md-6
+
+
+ +
Mobile and desktop
+
+
+
.col-12 .col-md-8
+
+
+
.col-6 .col-md-4
+
+
+
+
+
.col-6 .col-md-4
+
+
+
col-6 .col-md-4
+
+
+
.col-6 .col-md-4
+
+
+
+
+
.col-6
+
+
+
.col-6
+
+
+ +
Mobile, tablet, desktop
+
+
+
.col-12 .col-sm-6 .col-md-8
+
+
+
.col-6 .col-md-4
+
+
+
+
+
.col-6 .col-sm-4
+
+
+
.col-6 .col-sm-4
+
+
+
.col-6 .col-sm-4
+
+
+ +
Column wrapping
+
+
+
.col-9
+
+
+
+ .col-4
Since 9 + 4 = 13 > 12, this + 4-column-wide div gets wrapped onto a new line as + one contiguous unit. +
+
+
+
+ .col-6
Subsequent columns continue along the + new line. +
+
+
+ +
Responsive column resets
+
+
+
+ .col-6 .col-sm-3 +

+ Resize your viewport or check it out on your + phone for an example. +

+
+
+
+
.col-6 .col-sm-3
+
+
+
.col-6 .col-sm-3
+
+
+
.col-6 .col-sm-3
+
+
+ +
Offsetting columns
+
+
+
.col-md-4
+
+
+
.col-md-4 .offset-md-4
+
+
+
+
+
.col-md-3 .offset-md-3
+
+
+
.col-md-3 .offset-md-3
+
+
+
+
+
.col-md-6 .offset-md-3
+
+
+ +
Grid options
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Extra small devices + Phones (<576px) + + Small devices + Tablets (≥576px) + + Medium devices + Desktops (≥768px) + + Large devices + Desktops (≥992px) + + Large devices + Desktops (≥1200px) +
+ Grid behavior + Horizontal at all times + Collapsed to start, horizontal above + breakpoints +
+ Container width + None (auto)540px720px960px1140px
+ Class prefix + .col-.col-sm-.col-md-.col-lg-.col-xl-
+ # of columns + 12
+ Gutter width + + 1.875rem / 30px (15px on each side of a + column) +
+ Nestable + Yes
OffsetsYes
+ Column ordering + Yes
+
+
+
+
+
diff --git a/packages/admin-web-angular/src/app/pages/ui-features/grid/grid.component.scss b/packages/admin-web-angular/src/app/pages/ui-features/grid/grid.component.scss new file mode 100644 index 0000000..9efb383 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/ui-features/grid/grid.component.scss @@ -0,0 +1,33 @@ +// @import '../../../@theme/styles/themes'; + +// @include nb-install-component() { +// .show-grid { +// .row { +// margin: -0.5rem; +// } + +// div[class^=col-] { +// padding: 0.5rem; +// box-sizing: border-box; + +// div { +// text-align: center; +// background-color: nb-theme(color-bg-active); +// padding: 0.75rem 0.25rem; +// border-radius: 0.25rem; +// } +// } +// } + +// .grid-h { +// margin-top: 1.5rem; + +// &:first-child { +// margin-top: 0; +// } +// } + +// .table-responsive { +// margin-top: 1rem; +// } +// } diff --git a/packages/admin-web-angular/src/app/pages/ui-features/grid/grid.component.ts b/packages/admin-web-angular/src/app/pages/ui-features/grid/grid.component.ts new file mode 100644 index 0000000..d4acdd9 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/ui-features/grid/grid.component.ts @@ -0,0 +1,8 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'ngx-grid', + styleUrls: ['./grid.component.scss'], + templateUrl: './grid.component.html', +}) +export class GridComponent {} diff --git a/packages/admin-web-angular/src/app/pages/ui-features/icons/icons.component.html b/packages/admin-web-angular/src/app/pages/ui-features/icons/icons.component.html new file mode 100644 index 0000000..0077beb --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/ui-features/icons/icons.component.html @@ -0,0 +1,78 @@ +
+
+ + + Eva Icons + + +
+ + + +
+
+ + See all eva-icons + +
+
+ +
+ + + Nebular + + +
+ +
+
+
+ + + + Font awesome icons + + +
+ +
+
+ + + See all Font Awesome icons + + +
+ + + + Ionicons + + +
+ +
+
+ + See all ionicons + +
+
+
diff --git a/packages/admin-web-angular/src/app/pages/ui-features/icons/icons.component.scss b/packages/admin-web-angular/src/app/pages/ui-features/icons/icons.component.scss new file mode 100644 index 0000000..1bbd7d6 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/ui-features/icons/icons.component.scss @@ -0,0 +1,25 @@ +.icon { + display: inline-block; + width: 4rem; + padding: 1.25rem 0; + text-align: center; + font-size: 1.25rem; + + i:hover { + opacity: 0.8; + cursor: pointer; + } +} + +.nb-icons .icon { + padding: 0.75rem 0; + font-size: 1.75rem; +} + +nb-card-body { + padding: 0; +} + +nb-card-footer { + text-align: right; +} diff --git a/packages/admin-web-angular/src/app/pages/ui-features/icons/icons.component.ts b/packages/admin-web-angular/src/app/pages/ui-features/icons/icons.component.ts new file mode 100644 index 0000000..99b42cc --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/ui-features/icons/icons.component.ts @@ -0,0 +1,231 @@ +import { ChangeDetectionStrategy, Component } from '@angular/core'; +import { icons } from 'eva-icons'; + +@Component({ + selector: 'ngx-icons', + styleUrls: ['./icons.component.scss'], + templateUrl: './icons.component.html', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class IconsComponent { + evaIcons = []; + + constructor() { + this.evaIcons = Object.keys(icons).filter( + (icon) => icon.indexOf('outline') === -1 + ); + } + + icons = { + nebular: [ + 'nb-alert', + 'nb-angle-double-left', + 'nb-angle-double-right', + 'nb-arrow-down', + 'nb-arrow-dropdown', + 'nb-arrow-dropleft', + 'nb-arrow-dropright', + 'nb-arrow-dropup', + 'nb-arrow-left', + 'nb-arrow-retweet', + 'nb-arrow-right', + 'nb-arrow-thin-down', + 'nb-arrow-thin-left', + 'nb-arrow-thin-right', + 'nb-arrow-thin-up', + 'nb-arrow-up', + 'nb-audio', + 'nb-bar-chart', + 'nb-checkmark', + 'nb-chevron-down', + 'nb-chevron-down-outline', + 'nb-chevron-left', + 'nb-chevron-left-outline', + 'nb-chevron-right', + 'nb-chevron-right-outline', + 'nb-chevron-up', + 'nb-chevron-up-outline', + 'nb-close', + 'nb-close-circled', + 'nb-cloudy', + 'nb-coffee-maker', + 'nb-compose', + 'nb-edit', + 'nb-email', + 'nb-flame-circled', + 'nb-gear', + 'nb-grid-a', + 'nb-grid-a-outline', + 'nb-grid-b', + 'nb-grid-b-outline', + 'nb-heart', + 'nb-home', + 'nb-keypad', + 'nb-layout-centre', + 'nb-layout-default', + 'nb-layout-one-column', + 'nb-layout-sidebar-left', + 'nb-layout-sidebar-right', + 'nb-layout-two-column', + 'nb-lightbulb', + 'nb-list', + 'nb-location', + 'nb-locked', + 'nb-loop', + 'nb-loop-circled', + 'nb-menu', + 'nb-notifications', + 'nb-paper-plane', + 'nb-partlysunny', + 'nb-pause', + 'nb-pause-outline', + 'nb-person', + 'nb-phone', + 'nb-play', + 'nb-play-outline', + 'nb-plus', + 'nb-plus-circled', + 'nb-power', + 'nb-power-circled', + 'nb-rainy', + 'nb-roller-shades', + 'nb-search', + 'nb-shuffle', + 'nb-skip-backward', + 'nb-skip-backward-outline', + 'nb-skip-forward', + 'nb-skip-forward-outline', + 'nb-snowy-circled', + 'nb-square', + 'nb-square-outline', + 'nb-star', + 'nb-sunny', + 'nb-sunny-circled', + 'nb-tables', + 'nb-title', + 'nb-trash', + 'nb-volume-high', + 'nb-volume-mute', + 'nb-drop', + 'nb-drops', + 'nb-info', + 'nb-expand', + 'nb-collapse', + 'nb-e-commerce', + 'nb-danger', + 'nb-checkmark-circle', + 'nb-help', + ], + + ionicons: [ + 'ion-ionic', + 'ion-arrow-right-b', + 'ion-arrow-down-b', + 'ion-arrow-left-b', + 'ion-arrow-up-c', + 'ion-arrow-right-c', + 'ion-arrow-down-c', + 'ion-arrow-left-c', + 'ion-arrow-return-right', + 'ion-arrow-return-left', + 'ion-arrow-swap', + 'ion-arrow-shrink', + 'ion-arrow-expand', + 'ion-arrow-move', + 'ion-arrow-resize', + 'ion-chevron-up', + 'ion-chevron-right', + 'ion-chevron-down', + 'ion-chevron-left', + 'ion-navicon-round', + 'ion-navicon', + 'ion-drag', + 'ion-log-in', + 'ion-log-out', + 'ion-checkmark-round', + 'ion-checkmark', + 'ion-checkmark-circled', + 'ion-close-round', + 'ion-plus-round', + 'ion-minus-round', + 'ion-information', + 'ion-help', + 'ion-backspace-outline', + 'ion-help-buoy', + 'ion-asterisk', + 'ion-alert', + 'ion-alert-circled', + 'ion-refresh', + 'ion-loop', + 'ion-shuffle', + 'ion-home', + 'ion-search', + 'ion-flag', + 'ion-star', + 'ion-heart', + 'ion-heart-broken', + 'ion-gear-a', + 'ion-gear-b', + 'ion-toggle-filled', + 'ion-toggle', + 'ion-settings', + 'ion-wrench', + 'ion-hammer', + 'ion-edit', + 'ion-trash-a', + 'ion-trash-b', + 'ion-document', + 'ion-document-text', + 'ion-clipboard', + 'ion-scissors', + 'ion-funnel', + 'ion-bookmark', + 'ion-email', + 'ion-email-unread', + 'ion-folder', + 'ion-filing', + 'ion-archive', + 'ion-reply', + 'ion-reply-all', + 'ion-forward', + ], + + fontAwesome: [ + 'fa fa-adjust', + 'fa fa-anchor', + 'fa fa-archive', + 'fa fa-chart-area', + 'fa fa-arrows-alt', + 'fa fa-arrows-alt-h', + 'fa fa-arrows-alt-v', + 'fa fa-asterisk', + 'fa fa-at', + 'fa fa-car', + 'fa fa-ban', + 'fa fa-university', + 'fa fa-chart-bar', + 'far fa-chart-bar', + 'fa fa-barcode', + 'fa fa-bars', + 'fa fa-bed', + 'fa fa-beer', + 'fa fa-bell', + 'far fa-bell', + 'fa fa-bell-slash', + 'far fa-bell-slash', + 'fa fa-bicycle', + 'fa fa-binoculars', + 'fa fa-birthday-cake', + 'fa fa-bolt', + 'fa fa-bomb', + 'fa fa-book', + 'fa fa-bookmark', + 'far fa-bookmark', + 'fa fa-briefcase', + 'fa fa-bug', + 'fa fa-building', + 'far fa-building', + 'fa fa-bullhorn', + ], + }; +} diff --git a/packages/admin-web-angular/src/app/pages/ui-features/modals/modal/modal.component.html b/packages/admin-web-angular/src/app/pages/ui-features/modals/modal/modal.component.html new file mode 100644 index 0000000..2e976dc --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/ui-features/modals/modal/modal.component.html @@ -0,0 +1,12 @@ + + + diff --git a/packages/admin-web-angular/src/app/pages/ui-features/modals/modal/modal.component.ts b/packages/admin-web-angular/src/app/pages/ui-features/modals/modal/modal.component.ts new file mode 100644 index 0000000..deeaed9 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/ui-features/modals/modal/modal.component.ts @@ -0,0 +1,19 @@ +import { Component } from '@angular/core'; +import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; + +@Component({ + selector: 'ngx-modal', + templateUrl: './modal.component.html', +}) +export class ModalComponent { + modalHeader: string; + modalContent = `Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy + nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis + nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat.`; + + constructor(private activeModal: NgbActiveModal) {} + + closeModal() { + this.activeModal.close(); + } +} diff --git a/packages/admin-web-angular/src/app/pages/ui-features/modals/modals.component.html b/packages/admin-web-angular/src/app/pages/ui-features/modals/modals.component.html new file mode 100644 index 0000000..f2cf1e5 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/ui-features/modals/modals.component.html @@ -0,0 +1,18 @@ +
+
+ + Modals + + + + + + +
+
diff --git a/packages/admin-web-angular/src/app/pages/ui-features/modals/modals.component.scss b/packages/admin-web-angular/src/app/pages/ui-features/modals/modals.component.scss new file mode 100644 index 0000000..904c24b --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/ui-features/modals/modals.component.scss @@ -0,0 +1,8 @@ +@import '~@nebular/theme/styles/core/mixins'; + +:host { + button { + @include nb-ltr(margin, 0 0.75rem 2rem 0); + @include nb-rtl(margin, 0 0 2rem 0.75rem); + } +} diff --git a/packages/admin-web-angular/src/app/pages/ui-features/modals/modals.component.ts b/packages/admin-web-angular/src/app/pages/ui-features/modals/modals.component.ts new file mode 100644 index 0000000..f597abb --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/ui-features/modals/modals.component.ts @@ -0,0 +1,43 @@ +import { Component } from '@angular/core'; +import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; + +import { ModalComponent } from './modal/modal.component'; + +@Component({ + selector: 'ngx-modals', + styleUrls: ['./modals.component.scss'], + templateUrl: './modals.component.html', +}) +export class ModalsComponent { + constructor(private modalService: NgbModal) {} + + showLargeModal() { + const activeModal = this.modalService.open(ModalComponent, { + size: 'lg', + container: 'nb-layout', + }); + + activeModal.componentInstance.modalHeader = 'Large Modal'; + } + + showSmallModal() { + const activeModal = this.modalService.open(ModalComponent, { + size: 'sm', + container: 'nb-layout', + }); + + activeModal.componentInstance.modalHeader = 'Small Modal'; + } + + showStaticModal() { + const activeModal = this.modalService.open(ModalComponent, { + size: 'sm', + backdrop: 'static', + container: 'nb-layout', + }); + + activeModal.componentInstance.modalHeader = 'Static modal'; + activeModal.componentInstance.modalContent = `This is static modal, backdrop click + will not close it. Click × or confirmation button to close modal.`; + } +} diff --git a/packages/admin-web-angular/src/app/pages/ui-features/popovers/popover-examples.component.ts b/packages/admin-web-angular/src/app/pages/ui-features/popovers/popover-examples.component.ts new file mode 100644 index 0000000..4311b21 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/ui-features/popovers/popover-examples.component.ts @@ -0,0 +1,72 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'ngx-popover-tabs', + template: ` + + +
+ Such a wonderful day! +
+
+ +
+ Indeed! +
+
+
+ `, +}) +export class NgxPopoverTabsComponent {} + +@Component({ + selector: 'ngx-popover-form', + template: ` +
+
+
+ +
+
+ +
+
+ +
+ +
+
+ `, +}) +export class NgxPopoverFormComponent {} + +@Component({ + selector: 'ngx-popover-card', + template: ` + + + Hello! + + + Far far away, behind the word mountains, far from the countries + Vokalia and Consonantia, there live the blind texts. Separated + they live in Bookmarksgrove right at the coast of the Semantics, + a large language ocean. + + + `, +}) +export class NgxPopoverCardComponent {} diff --git a/packages/admin-web-angular/src/app/pages/ui-features/popovers/popovers.component.html b/packages/admin-web-angular/src/app/pages/ui-features/popovers/popovers.component.html new file mode 100644 index 0000000..bd391a2 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/ui-features/popovers/popovers.component.html @@ -0,0 +1,314 @@ +
+
+ + Popover Position + +

+ When popover has not enough space based on the configured + placement, it will adjust accordingly trying to fit the + screen. +

+ + + + +
+
+
+ +
+ + Simple Popovers + +

+ In a simples form popover can take a string of text to + render. +

+ + + +
+
+
+
+ +
+
+ + + +
+ Such a wonderful day! +
+
+ +
+ Indeed! +
+
+
+
+ + +
+
+
+ +
+
+ +
+
+ +
+ +
+
+
+ + + + + Hello! + + + Far far away, behind the word mountains, far from the + countries Vokalia and Consonantia, there live the blind + texts. Separated they live in Bookmarksgrove right at the + coast of the Semantics, a large language ocean. + + + + + + Template Popovers + +

+ You can pass a refference to `ng-template` to be rendered. +

+ + + +
+
+
+ +
+ + Component Popovers + +

Same way popover can render any angular compnoent.

+ + + +
+
+
+
+ +
+
+ + Event Debouncing + +

+ Quickly move mouse cursor over the buttons, only the last + popover will be created. It allows us to avoid excess white + improving page performance. +

+ + + + + + + + + + + + + + + + + +
+
+
+
diff --git a/packages/admin-web-angular/src/app/pages/ui-features/popovers/popovers.component.scss b/packages/admin-web-angular/src/app/pages/ui-features/popovers/popovers.component.scss new file mode 100644 index 0000000..e0b59a6 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/ui-features/popovers/popovers.component.scss @@ -0,0 +1,19 @@ +@import '~@nebular/theme/styles/core/mixins'; + +::ng-deep nb-card.popover-card { + margin-bottom: 0; + width: 300px; + box-shadow: none; +} + +:host { + button.with-margins { + @include nb-ltr(margin, 0 0.75rem 2rem 0); + @include nb-rtl(margin, 0 0 2rem 0.75rem); + } + + ::ng-deep .btn-outline-secondary { + @include nb-ltr(margin, 0 0.5rem 0.5rem 0); + @include nb-rtl(margin, 0 0 0.5rem 0.5rem); + } +} diff --git a/packages/admin-web-angular/src/app/pages/ui-features/popovers/popovers.component.ts b/packages/admin-web-angular/src/app/pages/ui-features/popovers/popovers.component.ts new file mode 100644 index 0000000..02f0d43 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/ui-features/popovers/popovers.component.ts @@ -0,0 +1,17 @@ +import { Component } from '@angular/core'; +import { + NgxPopoverCardComponent, + NgxPopoverFormComponent, + NgxPopoverTabsComponent, +} from './popover-examples.component'; + +@Component({ + selector: 'ngx-popovers', + styleUrls: ['./popovers.component.scss'], + templateUrl: './popovers.component.html', +}) +export class PopoversComponent { + tabsComponent = NgxPopoverTabsComponent; + cardComponent = NgxPopoverCardComponent; + formComponent = NgxPopoverFormComponent; +} diff --git a/packages/admin-web-angular/src/app/pages/ui-features/search-fields/search-fields.component.html b/packages/admin-web-angular/src/app/pages/ui-features/search-fields/search-fields.component.html new file mode 100644 index 0000000..216eb59 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/ui-features/search-fields/search-fields.component.html @@ -0,0 +1,75 @@ +
+
+ + + Layout Rotate Search + + + + + +
+
+ + + Modal Zoomin Search + + + + + +
+
+ + + Modal Move Search + + + + + +
+
+ + + Modal Drop Search + + + + + +
+
+ + + Modal Half Search + + + + + +
+
+ + + Curtain Search + + + + + +
+
+ + + Column Curtain Search + + + + + +
+
diff --git a/packages/admin-web-angular/src/app/pages/ui-features/search-fields/search-fields.component.ts b/packages/admin-web-angular/src/app/pages/ui-features/search-fields/search-fields.component.ts new file mode 100644 index 0000000..501e5bb --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/ui-features/search-fields/search-fields.component.ts @@ -0,0 +1,7 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'ngx-search-fields', + templateUrl: 'search-fields.component.html', +}) +export class SearchComponent {} diff --git a/packages/admin-web-angular/src/app/pages/ui-features/tabs/tabs.component.html b/packages/admin-web-angular/src/app/pages/ui-features/tabs/tabs.component.html new file mode 100644 index 0000000..f6f12f2 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/ui-features/tabs/tabs.component.html @@ -0,0 +1,244 @@ +
+
+ + + +

+ In 1975, the first general purpose home automation + network technology, + X10, was developed. It is a communication protocol for + electronic devices. It primarily uses + electric power transmission + wiring for signalling and control, where the signals + involve brief + radio frequency + bursts of + digital data, and remains the most widely available.[8] + By 1978, X10 products included a 16 channel command + console, a lamp module, and an appliance module. Soon + after came the wall switch module and the first X10 + timer. +

+
+ + Content #2 + + + Content #3 + +
+
+
+ +
+ + + +

+ Home automation or smart home[1] + (also known as domotics[2]) is + building automation + for the home. It involves the control and automation of + lighting, heating (such as + smart thermostats), ventilation, air conditioning (HVAC), and security, as well as + home appliances + such as washer/dryers, ovens or refrigerators/freezers. + Wi-Fi + is often used for remote monitoring and control. Home + devices, when remotely monitored and controlled via the + Internet, are an important constituent of the + Internet of Things. Modern systems generally consist of switches and + sensors connected to a central hub sometimes called a + "gateway" from which the system is controlled with a + user interface + that is interacted either with a wall-mounted terminal, + mobile phone software, + tablet computer + or a web interface, often but not always via Internet + cloud services. +

+

+ While there are many competing vendors, there are very + few worldwide accepted industry standards and the smart + home space is heavily fragmented.[3] + Popular + communications protocol + for products include + X10, + Ethernet, + RS-485, + 6LoWPAN, + Bluetooth LE (BLE), + ZigBee + and + Z-Wave, or other proprietary protocols all of which are + incompatible with each other.[4] + Manufacturers often prevent independent implementations + by withholding documentation and by litigation.[5] +

+
+ + Content #2 + + + Content #3 + +
+
+
+ +
+ + + +
+
diff --git a/packages/admin-web-angular/src/app/pages/ui-features/tabs/tabs.component.scss b/packages/admin-web-angular/src/app/pages/ui-features/tabs/tabs.component.scss new file mode 100644 index 0000000..964fa99 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/ui-features/tabs/tabs.component.scss @@ -0,0 +1,28 @@ +// @import '../../../@theme/styles/themes'; +// @import '~bootstrap/scss/mixins/breakpoints'; +// @import '~@nebular/theme/styles/global/breakpoints'; + +// @include nb-install-component() { +// nb-tabset { +// height: 100%; +// display: flex; +// flex-direction: column; +// } + +// nb-tab { +// padding: 1.25rem; +// } + +// ::ng-deep ngx-tab1, +// ::ng-deep ngx-tab2 { +// display: block; +// padding: 1.25rem; +// } + +// @include media-breakpoint-down(xs) { +// nb-tabset ::ng-deepul { +// font-size: 1rem; +// padding: 0 0.25rem; +// } +// } +// } diff --git a/packages/admin-web-angular/src/app/pages/ui-features/tabs/tabs.component.ts b/packages/admin-web-angular/src/app/pages/ui-features/tabs/tabs.component.ts new file mode 100644 index 0000000..da7b5d9 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/ui-features/tabs/tabs.component.ts @@ -0,0 +1,50 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'ngx-tab1', + template: ` +

+ Early home automation began with labor-saving machines. + Self-contained electric or gas powered + home appliances + became viable in the 1900s with the introduction of + electric power distribution + + and led to the introduction of washing machines (1904), water + heaters (1889), refrigerators, sewing machines, dishwashers, and + clothes dryers. +

+ `, +}) +export class Tab1Component {} + +@Component({ + selector: 'ngx-tab2', + template: `

Tab 2 works!

`, +}) +export class Tab2Component {} + +@Component({ + selector: 'ngx-tabs', + styleUrls: ['./tabs.component.scss'], + templateUrl: './tabs.component.html', +}) +export class TabsComponent { + tabs: any[] = [ + { + title: 'Route tab #1', + route: '/ui-features/tabs/tab1', + }, + { + title: 'Route tab #2', + route: '/ui-features/tabs/tab2', + }, + ]; +} diff --git a/packages/admin-web-angular/src/app/pages/ui-features/typography/typography.component.html b/packages/admin-web-angular/src/app/pages/ui-features/typography/typography.component.html new file mode 100644 index 0000000..74bf273 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/ui-features/typography/typography.component.html @@ -0,0 +1,351 @@ +
+
+
+
+ + + Used Fonts + + +
+
+
Exo
+ +
+ Bold + Regular + Light +
+
+

+ Far far away, behind the word mountains, far + from the countries Vokalia and Consonantia, + there live the blind texts. Separated they live + in Bookmarksgrove right at the coast of the + Semantics, a large language ocean. +

+
+ +
+
+
Roboto
+ +
+ Bold + Regular + Light +
+
+

+ Far far away, behind the word mountains, far + from the countries Vokalia and Consonantia, + there live the blind texts. Separated they live + in Bookmarksgrove right at the coast of the + Semantics, a large language ocean. +

+
+
+
+ + + + Article Example + + +

So what's About the grammar?

+

+ Far far away, behind the word mountains, far from + the countries Vokalia and + Consonantia, there live the blind + texts. They live in Bookmarksgrove. +

+

+ A small river named Duden flows by + their place and supplies it with the necessary + regelialia. It is a paradisematic country, in which + roasted parts of sentences fly into your mouth. Even + the all-powerful Pointing has no control about the + blind texts it is an almost unorthographic life One + day however a small line of blind text by the name + of + Lorem Ipsum + decided to leave for the far + World of Grammar. +

+
+
+
+
+ + + Headings + + +
+
+

H1. Heading

+
+ +
+ Demibold 2.5rem (40px) +
+
+
+
+

H2. Heading

+
+ +
+ Demibold 2rem (32px) +
+
+
+
+

H3. Heading

+
+ +
+ Demibold 1.75rem (28px) +
+
+
+
+

H4. Heading

+
+ +
+ Demibold 1.5rem (24px) +
+
+
+
+
H5. Heading
+
+ +
+ Demibold 1.25rem (20px) +
+
+
+
+
H6. Heading
+
+ +
+ Demibold 1rem (16px) +
+
+
+
+ + + + Blockquotes + + +
+

+ Far far away, behind the word mountains, far + from the countries Vokalia and Consonantia. +

+
+ +
+

+ Far far away, behind the word mountains, far + from the countries. +

+
+ Vladimir Lugovsky +
+
+
+

+ Far far away, behind the word mountains. +

+
+ Vladimir Lugovsky +
+
+
+
+
+
+
+ +
+ + + Font Colors + + +
+
+
+
+
Heading Text
+ Far far away, behind the your awesomeness. +
+
+
+
+
+
Body Text
+ Far far away, behind the your awesomeness. +
+
+
+
+
+
Hint Text
+ Far far away, behind the your awesomeness. +
+
+ +
+
+
+
Primary Text
+ Far far away, behind the your awesomeness. +
+
+
+
+
+
Success Text
+ Far far away, behind the your awesomeness. +
+
+
+
+
+
Info Text
+ Far far away, behind the your awesomeness. +
+
+
+
+
+
Warning Text
+ Far far away, behind the your awesomeness. +
+
+
+
+
+
Danger Text
+ Far far away, behind the your awesomeness. +
+
+
+
+
+
+ +
+ + + Alerts + + + + + + + + + + +
+ +
+ + + Text Types + + +

Highlighted text

+

+ Far far away, behind the word mountains, far from the + countries Vokalia and + Consonantia, there live the blind texts. +

+

Bold Text

+

+ Far far away, behind the word mountains, far from the + countries Vokalia and + Consonantia, there live the blind texts. +

+

Link Text

+

+ Far far away, behind the word mountains, far from the + countries Vokalia and + Consonantia, there live the blind texts. +

+
+
+
+ +
+ + + Lists + + +

Ordered List

+
    +
  1. Far far away, behind the word mountains
  2. +
  3. Far from the countries Vokalia and Consonantia
  4. +
  5. There live the blind texts.
  6. +
  7. Right at the coast of the Semantics.
  8. +
  9. A small river named Duden flows
  10. +
+ +

Unordered List

+
    +
  • Far far away, behind the word mountains
  • +
  • Far from the countries Vokalia and Consonantia
  • +
  • There live the blind texts.
  • +
  • Right at the coast of the Semantics.
  • +
  • A small river named Duden flows
  • +
+
+
+
+
diff --git a/packages/admin-web-angular/src/app/pages/ui-features/typography/typography.component.scss b/packages/admin-web-angular/src/app/pages/ui-features/typography/typography.component.scss new file mode 100644 index 0000000..ab3b0a7 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/ui-features/typography/typography.component.scss @@ -0,0 +1,128 @@ +// @import '../../../@theme/styles/themes'; + +// @include nb-install-component() { + +// .font-secondary .font-header .name { +// font-size: 4.5rem; +// line-height: 4rem; +// font-weight: 500; +// } + +// .font-main .font-header .name { +// font-size: 3rem; +// font-weight: 500; +// } + +// .font-row { +// &:first-child { +// margin-bottom: 2rem; +// } + +// .header { +// align-items: baseline; +// } + +// p { +// margin: 0; +// } +// } + +// .headings-card { +// nb-card-body { +// padding: 1rem 1.25rem; +// } +// .header { +// padding-bottom: 0.675rem; +// margin-bottom: 0.675rem; + +// &:last-child { +// padding-bottom: 0; +// margin-bottom: 0; +// } +// } +// } + +// .header { +// display: flex; +// flex-wrap: wrap; +// align-items: center; +// padding-bottom: 1rem; +// margin-bottom: 1rem; +// border-bottom: 1px solid nb-theme(separator); +// color: #2a2a2a; + +// &:last-child { +// border-bottom: none; +// padding: 0; +// margin: 0; +// } + +// div:first-child { +// flex: 2; +// -ms-flex: 2 1 auto; +// line-height: 1; +// align-items: flex-end; + +// h1, h2, h3, h4, h5, h6 { +// margin-bottom: 0; +// } +// } + +// .variants { +// flex: 1; +// -ms-flex: 1 1 auto; +// display: flex; +// justify-content: space-between; +// align-items: flex-end; + +// span { +// padding-right: 1rem; +// padding-left: 1rem; +// font-size: 1.5rem; +// } +// } + +// .detail { +// flex: 1; +// display: flex; +// justify-content: flex-end; +// align-items: flex-end; +// color: nb-theme(color-fg); +// } +// } + +// .colors { +// display: flex; +// flex-direction: column; + +// .item { +// display: flex; +// align-items: center; +// margin-bottom: 1.25rem; +// &:last-child { +// margin-bottom: 0; +// } +// } + +// .color { +// width: 86px; +// height: 60px; +// border-top-right-radius: 1rem; +// border-bottom-left-radius: 1rem; +// @include nb-ltr(margin-right, 1rem); +// @include nb-rtl(margin-left, 1rem); +// } + +// h1, h2, h3, h4, h5, h6 { +// margin-bottom: 0.25rem; +// } +// } + +// .text-link { +// color: nb-theme(link-color); +// } + +// .bg-link { +// background: nb-theme(link-color); +// } +// } diff --git a/packages/admin-web-angular/src/app/pages/ui-features/typography/typography.component.ts b/packages/admin-web-angular/src/app/pages/ui-features/typography/typography.component.ts new file mode 100644 index 0000000..ddb24c9 --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/ui-features/typography/typography.component.ts @@ -0,0 +1,33 @@ +import { Component, OnDestroy } from '@angular/core'; +import { + NbMediaBreakpoint, + NbMediaBreakpointsService, + NbThemeService, +} from '@nebular/theme'; + +@Component({ + selector: 'ngx-typography', + styleUrls: ['./typography.component.scss'], + templateUrl: './typography.component.html', +}) +export class TypographyComponent implements OnDestroy { + breakpoint: NbMediaBreakpoint; + breakpoints: any; + themeSubscription: any; + + constructor( + private themeService: NbThemeService, + private breakpointService: NbMediaBreakpointsService + ) { + this.breakpoints = this.breakpointService.getBreakpointsMap(); + this.themeSubscription = this.themeService + .onMediaQueryChange() + .subscribe(([oldValue, newValue]) => { + this.breakpoint = newValue; + }); + } + + ngOnDestroy() { + this.themeSubscription.unsubscribe(); + } +} diff --git a/packages/admin-web-angular/src/app/pages/ui-features/ui-features-routing.module.ts b/packages/admin-web-angular/src/app/pages/ui-features/ui-features-routing.module.ts new file mode 100644 index 0000000..baa2d1e --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/ui-features/ui-features-routing.module.ts @@ -0,0 +1,78 @@ +import { NgModule } from '@angular/core'; +import { RouterModule, Routes } from '@angular/router'; + +import { UiFeaturesComponent } from './ui-features.component'; +import { ButtonsComponent } from './buttons/buttons.component'; +import { GridComponent } from './grid/grid.component'; +import { IconsComponent } from './icons/icons.component'; +import { ModalsComponent } from './modals/modals.component'; +import { TypographyComponent } from './typography/typography.component'; +import { + Tab1Component, + Tab2Component, + TabsComponent, +} from './tabs/tabs.component'; +import { SearchComponent } from './search-fields/search-fields.component'; +import { PopoversComponent } from './popovers/popovers.component'; + +const routes: Routes = [ + { + path: '', + component: UiFeaturesComponent, + children: [ + { + path: 'buttons', + component: ButtonsComponent, + }, + { + path: 'grid', + component: GridComponent, + }, + { + path: 'icons', + component: IconsComponent, + }, + { + path: 'modals', + component: ModalsComponent, + }, + { + path: 'popovers', + component: PopoversComponent, + }, + { + path: 'typography', + component: TypographyComponent, + }, + { + path: 'search-fields', + component: SearchComponent, + }, + { + path: 'tabs', + component: TabsComponent, + children: [ + { + path: '', + redirectTo: 'tab1', + pathMatch: 'full', + }, + { + path: 'tab1', + component: Tab1Component, + }, + { + path: 'tab2', + component: Tab2Component, + }, + ], + }, + ], + }, +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule], +}) +export class UiFeaturesRoutingModule {} diff --git a/packages/admin-web-angular/src/app/pages/ui-features/ui-features.component.ts b/packages/admin-web-angular/src/app/pages/ui-features/ui-features.component.ts new file mode 100644 index 0000000..93b456f --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/ui-features/ui-features.component.ts @@ -0,0 +1,7 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'ngx-ui-features', + template: ` `, +}) +export class UiFeaturesComponent {} diff --git a/packages/admin-web-angular/src/app/pages/ui-features/ui-features.module.ts b/packages/admin-web-angular/src/app/pages/ui-features/ui-features.module.ts new file mode 100644 index 0000000..dff622b --- /dev/null +++ b/packages/admin-web-angular/src/app/pages/ui-features/ui-features.module.ts @@ -0,0 +1,46 @@ +import { NgModule } from '@angular/core'; + +import { ThemeModule } from '../../@theme/theme.module'; +import { ButtonsModule } from './buttons/buttons.module'; +import { UiFeaturesRoutingModule } from './ui-features-routing.module'; +import { UiFeaturesComponent } from './ui-features.component'; +import { GridComponent } from './grid/grid.component'; +import { ModalsComponent } from './modals/modals.component'; +import { IconsComponent } from './icons/icons.component'; +import { ModalComponent } from './modals/modal/modal.component'; +import { TypographyComponent } from './typography/typography.component'; +import { + Tab1Component, + Tab2Component, + TabsComponent, +} from './tabs/tabs.component'; +import { SearchComponent } from './search-fields/search-fields.component'; +import { PopoversComponent } from './popovers/popovers.component'; +import { + NgxPopoverCardComponent, + NgxPopoverFormComponent, + NgxPopoverTabsComponent, +} from './popovers/popover-examples.component'; + +const components = [ + UiFeaturesComponent, + GridComponent, + ModalsComponent, + IconsComponent, + ModalComponent, + TypographyComponent, + TabsComponent, + Tab1Component, + Tab2Component, + SearchComponent, + PopoversComponent, + NgxPopoverCardComponent, + NgxPopoverFormComponent, + NgxPopoverTabsComponent, +]; + +@NgModule({ + imports: [ThemeModule, UiFeaturesRoutingModule, ButtonsModule], + declarations: [...components] +}) +export class UiFeaturesModule {} diff --git a/packages/admin-web-angular/src/assets/i18n/bg-BG.json b/packages/admin-web-angular/src/assets/i18n/bg-BG.json new file mode 100644 index 0000000..90df574 --- /dev/null +++ b/packages/admin-web-angular/src/assets/i18n/bg-BG.json @@ -0,0 +1,1155 @@ +{ + "COMMON": { + "SAVE": "Запази", + "CANCEL": "Отказ", + "USA": "САЩ", + "ISRAEL": "Израел", + "BULGARIA": "България" + }, + "ORDER_VIEW": { + "ORDER_HEADER_INFO": { + "ORDER": "Поръчка", + "MANAGE_ORDER": "Управление на поръчката", + "TOTAL": "Обща сума" + }, + "ORDER_PRODUCT_INFO": { + "ORDER_PRODUCTS": "Поръчайте продукти", + "ADD_PRODUCTS": "Добавете продукти", + "REMOVE_PRODUCTS": "Премахване на продуктите", + "CANCEL_ORDER": "Отмяна на поръчката", + "THE_ORDER_IS_CANCELED": "Поръчката е отменена", + "THE_ORDER_IS_GIVEN_TO_CARRIER": "Поръчката се дава на превозвача.", + "THE_ORDER_IS_DELIVERED": "Поръчката е доставена.", + "ADD_PRODUCTS_MODAL": "Добавяне на продукти", + "ADD": "Добавяне", + "SUCCESS_TOAST": "Продуктите бяха прибавени към поръчката", + "ERROR_TOAST": "Грешка, нещо се обърка", + "SMART_TABLE": { + "NAME": "Име", + "QTY": "КОЛ", + "PRICE": "Цена", + "IMAGE": "Изображение", + "COMMENT": "Коментар" + } + }, + "ORDER_SIDEBAR": { + "CONTACT_DETAILS": "Данни за контакт", + "WAREHOUSE": "Склад", + "CUSTOMER": "Клиент", + "CARRIER": "Носител", + "QTY": "кол" + }, + "LOCATION_INFO": { + "MAP": "Карта", + "DELIVERY_DISTANCE": "Разстояние за доставка:" + } + }, + "DASHBOARD_VIEW": { + "TOTAL_CUSTOMER": "Общо клиенти", + "TOTAL_QUANTITY_OF_EXISTING_CUSTOMERS": "Общо количество съществуващи клиенти", + "TOTAL_COMPLETED_ORDERS": "Общо изпълнени поръчки", + "TOTAL_QUANTITY_OF_COMPLETED_ORDERS": "Общо количество изпълнени поръчки", + "TOTAL_REVENUE": "Общо приходи", + "TOTAL_SUM_SUM_OF_ALL_COMPLETED_ORDERS": "Обща сума на всички изпълнени поръчки", + "TODAYs_CUSTOMERS": "Днешните клиенти", + "TODAYs_QUANTITY_OF_REGISTERED_CUSTOMERS": "Днешното количество на регистрираните клиенти", + "TODAYs_COMPLETED_ORDERS": "Днешни завършени поръчки", + "TODAYs_QUANTITY_OF_NEW_COMPLETED_ORDERS": "Днешното количество нови попълнени поръчки", + "TODAYs_REVENUE": "Днешните приходи", + "TODAYs_SUM_OF_COMPLETED_ORDERS": "Днешната сума от изпълнени поръчки", + "TILL_AVERAGE": "до средно", + "BETTER_THAN_AVERAGE": "над средното", + "SELECT_COMPONENT": { + "STORES": "Магазини", + "CONTACT_DETAILS": "Данни за контакт", + "PHONE": "телефон", + "EMAIL": "електронна поща", + "ORDERS_FORWARDING_WITH": "Поръчки Препращане с", + "SELECT_STORE": "Изберете магазин" + }, + "CHARTS": { + "ORDERS": "Поръчки", + "PROFIT": "Печалба", + "TOTAL_ORDERS": "Общо поръчки", + "TOTAL_COMPLETED_ORDERS": "Общо изпълнени поръчки", + "TOTAL_CANCELLED_ORDERS": "Total canceled orders", + "TOTAL_REVENUE_ALL_ORDERS": "Общо приходи от всички поръчки", + "TOTAL_REVENUE_COMPLETED_ORDERS": "Общо приходи от изпълнени поръчки", + "TOTAL_LOST_REVENUE_CANCELLED_ORDERS": "Total lost revenue from canceled orders", + "PAYMENT": "Плащане", + "CANCELED": "Отменен", + "ALL_ORDERS": "Всички поръчки", + "TODAY": "Днес", + "LAST_WEEK": "Предишна Седмица", + "LAST_MONTH": "Изминал месец", + "CURRENT_YEAR": "Текуща Година", + "YEARS": "По година", + "CUSTOM_PERIOD": "Потребителски Период", + "SELECT_PERIOD": "Избери Период", + "FROM": "От", + "TO": "До", + "SELECT": "Избери", + "LABELS": { + "WEEK": "Седм", + "WEEKDAYS": { + "MON": "Пн", + "TUE": "Вт", + "WED": "Ср", + "THU": "Чт", + "FRI": "Пт", + "SAT": "Сб", + "SUN": "Нд" + }, + "MONTHS": { + "JAN": "Ян", + "FEB": "Февр", + "MAR": "Март", + "APR": "Април", + "MAY": "Май", + "JUN": "Юни", + "JUL": "Юли", + "AUG": "Авг", + "SEP": "Септ", + "OCT": "Окт", + "NOV": "Ноем", + "DEC": "Дек" + } + } + } + }, + "HEADER_VIEW": { + "ADMIN": "Админ", + "EVER": "Евер", + "PROFILE": "Профил", + "LOG_OUT": "Излез от профила си" + }, + "FOOTER_VIEW": { + "COPY_RIGHT": "Copyright © 2016-до момента", + "ALL_RIGHTS_RESERVED": "Всички права запазени" + }, + "PROFILE_VIEW": { + "PROFILE_PAGE": "Профилна страница", + "BASIC_INFO": "Основна информация", + "ACCOUNT": "Сметка", + "USERNAME": "Потребител", + "ERROR": "грешка", + "EMAIL": "електронна поща", + "FIRST_NAME": "Първо Име", + "FIRST_NAME_OPTIONAL": "Първо Име (незадължително)", + "LAST_NAME": "Фамилно Име (незадължително)", + "PICTURE_URL": "URL адрес на картината (по избор)", + "BROWSE": "паса", + "REMOVE": "Премахване", + "SAVE": "Запази", + "OLD_PASSWORD": "Стара парола", + "NEW_PASSWORD": "Нова парола", + "REPEAT_NEW_PASSWORD": "Повтори новата парола", + "INVALID_EMAIL_ADDRESS": "Невалиден имейл адрес", + "INVALID_URL": "невалиден адрес", + "NAME_MUST_CONTAIN_ONLY_LETTERS": "Името трябва да съдържа само букви", + "PASSWORDS_DO_NOT_MATCH": "Паролите не съвпадат", + "SUCCESSFULLY_CHANGE_PASSWORD": "Успешно промяна на паролата" + }, + "PRODUCTS_VIEW": { + "DELETE": "Изтрий", + "CREATE": "създавам", + "EDIT_VIEW": { + "EDIT_PRODUCT": "Редактиране на продукта", + "BASIC_INFO": "Основна информация", + "SAVE": "Запази" + } + }, + "FAKE_DATA": { + "FAKE_DATA_GENERATOR": "Генератор на фалшиви данни", + "GENERATE_ALL": "Генерирай Всичко", + "CREATE_100_USERS": "Създай 100 клиента", + "CREATE_100_CARRIERS": "Създай 100 доставчика", + "CREATE_100_WAREHOUSES": "Създайте 100 склада", + "SETUP": "НАСТРОЙВАНЕ", + "GENERATE_INITIAL_DATA": "Генериране на първоначални данни", + "CREATE_INVITE": "СЪЗДАЙТЕ ПОКАНИ", + "HARDCODED_DATA": "Фиксирани данни", + "CLEAR_ALL": "ИЗЧИСТИ ВСИЧКО", + "GENERATE_HARDCODED_ONLY": "Генерирайте само фиксирани", + "INCLUDED_HARDCODED_DATA": "Включете твърди кодирани данни", + "CREATE_1st_INVITE": "Създайте първата покана", + "CREATE_2st_INVITE": "Създайте 2-ра покана", + "CREATE_3st_INVITE": "Създайте 3-та покана", + "CREATE_4st_INVITE": "Създайте 4-та покана", + "CREATE_CUSTOMER": "Създаване на клиент", + "CREATE_USER": "СЪЗДАЙ ПОТРЕБИТЕЛ", + "CREATE_1st_USER": "Създайте първи потребител (с 1-ва покана)", + "CREATE_CARRIER": "СЪЗДАЙТЕ КУРИЕР", + "CREATE_1st_CARRIER": "Създайте първия куриер", + "CREATE_2nd_CARRIER": "Създайте втория куриер", + "CREATE_3rd_CARRIER": "Създайте третия куриер", + "CREATE_PRODUCT": "СЪЗДАЙТЕ ПРОДУКТ", + "CREATE_PEPERONI&MUSHROOM_PIZZA_PRODUCT": "Създаване на продукт за пица Peperoni & Mushroom", + "CREATE_SUSHI&CAVIAR_PRODUCT": "Създайте суши & хайвер продукт", + "CREATE_SUSHI_MIX_PRODUCT": "Създайте продукт за смесване на суши", + "CREATE_PASTA_PRODUCT": "Създайте продукт от макаронени изделия", + "CREATE_SUSHI_BOX_PRODUCT": "Създайте продукт за суши кутия", + "CREATE_PEPERONI&TOMATO_PIZZA_PRODUCT": "Създайте пица продукт Peperoni & Tomato", + "CREATE_WAREHOUSE": "СЪЗДАЙТЕ СКЛАД", + "CREATE_1st_WAREHOUSE": "Създайте първия склад", + "CREATE_2nd_WAREHOUSE": "Създайте 2-ри склад", + "CREATE_3rd_WAREHOUSE": "Създайте 3-ти склад", + "CREATE_WAREHOUSE_PRODUCT": "СЪЗДАЙТЕ СКЛАДОВ ПРОДУКТ", + "CREATE_1st_WAREHOUSE_PRODUCTS(using_product_number_1_2_3_4_5_and_6)": "Създаване на първите складови продукти (с използване на номера на продуктите 1, 2, 3, 4, 5 и 6)", + "CREATE_3rd_WAREHOUSE_PRODUCTS(using_1st_product)": "Създайте 2-ри складови продукти (използвайки продукти 1, 2 и 3", + "CREATE_2nd_WAREHOUSE_PRODUCTS(using_product_number_1_2_and_3": "Създайте 3-ти складов продукт (с 1-вия продукт)", + "UPDATE_WAREHOUSE_GEO_LOCATION": "МЕСТОПОЛОЖЕНИЕ НА СКЛАДОВАТА СГРАДА", + "UPDATE_1ST_WAREHOUSE_GEO_LOCATION": "Обновление местоположения 1-го хранилища", + "CREATE_1RD_ORDER(using_1st_warehouse_1st_user_and_1st_product)": "Создайте 1-й заказ (используя 1-й склад, 1-й пользователь и 1-й продукт)", + "CREATE_2ND_ORDER(using_1st_warehouse_1st_user_and_2nd_product)": "Создайте 2-й заказ (используя 1-й склад, 1-й пользователь и 2-й продукт)", + "CONFIRM_ORDER": "ПОТВЪРДИ ПОРЪЧКАТА", + "CREATE_ORDER": "СЪЗДАВАНЕ НА ПОРЪЧКА", + "CONFIMR_1st_ORDER": "Потвърдете 1-та поръчка", + "CONFIMR_2nd_ORDER": "Потвърдете 2-ра поръчка", + "SETUP_MERCHANTS": { + "SETUP_MERCHANTS": "Настройки на ресторантите", + "PREV": "НАЗАД", + "NEXT": "СЛЕДВАЩИЯ", + "ADD": "Добави", + "BACK": "Обратно", + "SAVE": "Запази", + "SELECT": "Изберете", + "CREATE": "Създай Клиент", + "HOW_TO_SET_UP": { + "HOW_TO_SET_UP": "Как да настроите" + }, + "STEPPER": { + "ACCOUNT": "Акаунт", + "BASIC_INFO": "Основна информация", + "CONTACT_INFO": "Информация за контакт", + "LOCATION": "Местоположение", + "PAYMENTS": "Плащания", + "MANUFACTURING": "Производство", + "DELIVERY_AND_TAKEAWAY_SETTINGS": "Настройки за доставка и за вкъщи", + "ORDERS_SETTINGS": "Настройки за поръчки", + "PRODUCT_CATEGORIES": "Продуктови категории", + "PRODUCTS": "Продукти" + }, + "ACCOUNT": { + "ACCOUNT": "Сметка", + "EMAIL_ADDRESS": "Имейл адрес", + "EMAIL": "Електронна поща", + "PASSWORD": "Парола", + "REPEAT_PASSWORD": "Повтори паролата", + "EMAIL_IS_REQUIRED": "Изисква се имейл", + "INVALID_EMAIL_FORMAT": "Невалиден имейл формат", + "USERNAME": "Потребител", + "USERNAME_IS_REQUIRED": "Изисква се потребителско име", + "USERNAME_MUST_BE_AT_LEAST_3_CHARACTERS": "Потребителското име трябва да съдържа поне 3 знака", + "PASSWORD_IS_REQUIRED": "Изисква се парола", + "PASSWORD_MUST_BE_AT_LEAST_4_CHARACTERS": "Паролата трябва да съдържа поне 4 знака", + "REPEAT_PASSWORD_IS_REQUIRED": "Изисква се повторна парола", + "PASSWORDS_DO_NOT_MATCH": "Паролите не съвпадат" + }, + "BASIC_INFO": { + "BASIC_INFO": "Основна информация", + "NAME": "Име", + "NAME_IS_REQUIRED": "Изисква се име", + "NAME_MUST_BE_AT_LEAST_4_CHARACTERS": "Името трябва да е най-малко 4 знака", + "PHOTO": "Снимка", + "ENTER_A_VALID_LOGO_URL_OR_BROWSE_FROM_A_DEVICE": "Въведете валиден URL адрес на лого или прегледайте от устройството", + "REMOVE": "Премахване", + "PHOTO_OPTIONAL": "Снимка (по избор)", + "BARCODE_DATA": "Данни за баркод", + "BARCODE_DATA_IS_REQUIRED": "Необходими са данни за баркода" + }, + "CONTACT_INFO": { + "CONTACT_INFO": "Информация за контакт", + "CONTACT_PHONE": "Телефон за връзка", + "INVALID_PHONE_NUMBER_FORMAT": "Невалиден формат на телефонния номер", + "ORDER_FORWARDING_EMAIL": "Избери изпращане на имейл", + "ORDER_FORWARDING_PHONE": "Избери изпращане телефон", + "ORDERS_EMAIL": "Имейл за поръчки", + "ORDERS_EMAIL_IS_REQUIRED": "Изисква се имейл за поръчки", + "INVALID_EMAIL_FORMAT": "Невалиден имейл формат", + "ORDERS_PHONE": "Телефон за поръчки", + "ORDERS_PHONE_IS_REQUIRED": "Изисква се телефонна поръчка" + }, + "LOCATION": { + "LOCATION": "Место нахождения" + }, + "PAYMENTS": { + "PAYMENTS": "Плащания", + "ALLOW_ONLINE_PAYMENT": "Разрешаване на онлайн плащания?", + "ALLOW_CASH_PAYMENT": "Разрешаване на плащане в брой?", + "STRIPE": { + "PAY_BUTTON_TEXT": "Текст на бутона за плащане", + "CURRENCY": "Валута", + "PAY_BUTTON_TEXT_IS_REQUIRED": "Изисква се текст на бутон за плащане", + "CHOOSE_CURRENCY_CODE": "Изберете кода на валутата", + "CURRENCY_TEXT_IS_REQUIRED": "Изберете кода на валутата", + "COMPANY_BRAND_LOGO": "Лого на фирмената марка", + "COMPANY_BRAND_LOGO_IS_REQUIRED": "Изисква се лого на фирмената марка", + "INVALID_LOGO_URL": "Невалиден URL адрес на логото", + "INVALID_LOGO": "Невалидно лого", + "PUBLISHABLE_KEY": "Публикуван ключ", + "PUBLISHABLE_KEY_IS_REQUIRED": "Изисква се издателски ключ", + "ALLOW_REMEMBER_ME": "Нека ме запамети?", + "REMOVE": "Премахни" + }, + "PAYPAL": { + "MODE": "Метод", + "CHOOSE_PAYPAL_MODE": "Избери PayPal метод", + "TYPE": "тип", + "CURRENCY": "Валута", + "CHOOSE_CURRENCY_CODE": "Изберете кода на валутата", + "CURRENCY_TEXT_IS_REQUIRED": "Необходим е текст на валутата", + "PUBLISHABLE_KEY": "Публикуван ключ", + "PUBLISHABLE_KEY_IS_REQUIRED": "Изисква се публичен ключ", + "SECRET_KEY": "Тайният ключ", + "SECRET_KEY_IS_REQUIRED": "Изисква се секретен ключ", + "PAYMENT_DESCRIPTION": "Описание на плащането", + "PAYMENT_DESCRIPTION_IS_REQUIRED": "Изисква се описание на плащането" + } + }, + "MANUFACTURING": { + "MANUFACTURING": "Производство", + "PRODUCTS_MANUFACTURING": "Продукти Производство" + }, + "DELIVERY_AND_TAKEAWAY_SETTINGS": { + "DELIVERY_AND_TAKEAWAY_SETTINGS": "Настройки за доставка и за вкъщи", + "SELECT_FROM_SHARED_CARRIERS": "Изберете от споделени превозвачи", + "ADD_YOUR_CARRIER": "Добавете своя оператор", + "EDIT_CARRIER": "Редактиране на носителя", + "CARRIER_REQUIRED": "Необходим е превозвач", + "PRODUCTS_DELIVERY_BY_DEFAULT": "Доставка на продукти (по подразбиране)", + "PRODUCTS_TAKEAWAY_BY_DEFAULT": "Продукти за вкъщи (по подразбиране)", + "USE_SELECTED_SHARED_CARRIERS": "Използвайте избрани споделени оператори", + "ADD_YOUR_CARRIERS": "Добавете операторите си" + }, + "ORDERS_SETTINGS": { + "ORDERS_SETTINGS": "Настройки за поръчки", + "ORDER_BARCODE_QR_CODE_TYPES": "Заяви типът баркод/qr код" + }, + "PRODUCT_CATEGORIES": { + "PRODUCT_CATEGORIES": "Продуктови категории", + "ADD_OWN_PRODUCT_CATEGORY": "Добавете собствена категория продукти" + }, + "PRODUCTS": { + "PRODUCTS": "Продукти", + "SELECT_FROM_PRODUCTS_CATALOG": "Изберете от каталога с продукти", + "CREATE_PRODUCT": "Създайте продукт", + "EDIT_PRODUCT": "Редактиране на продукт", + "ADD_PRODUCT": "Добавете продукт", + "CREATE_NEW_PRODUCT": "Създайте нов продукт" + } + } + }, + "WAREHOUSE_MANAGE": { + "TITLE": "Управление на склада", + "MANAGE_STORE": "Управление на магазина", + "SAVE": "Запази", + "NAME": "Име", + "USERNAME": "Потребител", + "PASSWORD": "Парола", + "COUNTRY": "Държава", + "CITY": "Град", + "POSTCODE": "Пощенски код", + "IS_ACTIVE": "Активен ли е?", + "PRODUCTS_MANUFACTURING": "Производство на изделия", + "CARRIER_REQUIRED": "Необходим е превозвач", + "RIGHT_NOW": "(точно сега)", + "CARRIERS": "Превозвачи", + "ADDRESS": "Адрес", + "CARRIERS_SPECIFIC": "Използвайте само конкретни превозвачи", + "VALIDATION": { + "NAME": "Името е задължително", + "USERNAME": "Изисква се потребителско име", + "PASSWORD": "Изисква се парола", + "COUNTRY": "Необходима е държава", + "CITY": "Градът е задължителен", + "STREET": "Улицата е задължителна", + "HOUSE": "Необходим е номер на къщата", + "POSTCODE": "Пощенски код е задължителен" + }, + "WIZARD_TITLES": { + "DETAILS": "Детайли", + "ACCOUNT": "Сметка", + "CONTACT_INFO": "Информация за контакт", + "LOCATION": "Местоположение", + "PAYMENT": "Плащане", + "DELIVERY_ZONES": "Зони за доставка" + } + }, + "WAREHOUSES_VIEW": { + "TITLE": "Складове", + "DELETE_WAREHOUSES": "Изтрий Селектирани", + "DELETE": "Изтрий", + "CREATE": "създавам", + "SHOW_ON_MAP": "Покажи на карта", + "SMART_TABLE_COLUMNS": { + "IMAGE": "Изображение", + "NAME": "Име", + "EMAIL": "Eлектронна Поща", + "PHONE": "Телефон", + "CITY": "Град", + "ADDRESS": "Адрес", + "ORDERS_QTY": "Поръчки количество", + "ORDERS": "Поръчки" + }, + "INFO": { + "STORE_INFO": "Информация за магазина", + "STORE_ID": "Идент. № на магазина", + "STORE_NAME": "Име на Магазина" + }, + "MERCHANTS": { + "TRACK_ALL_MERCHANTS": "Проследи всички търговци", + "FILTER_MERCHANTS": "Филтрирай търговци", + "FILTER_BY_NAME": "Филтрирай по име", + "FILTER_BY_CITY": "Филтрирай по град", + "FILTER_BY_COUNTRY": "Филтрирай по държава" + } + }, + "WAREHOUSE_VIEW": { + "ORDER": { + "CONFIRM": "Потвърди", + "START_PROCESSING": "Стартирайте обработката", + "START_ALLOCATION": "Започнете разпределението", + "ALLOCATED": "Разпределени", + "ALLOCATION_FAILS": "Разпределението не е успешно", + "START_PACKAGING": "Стартирайте опаковането", + "PACKAGED": "Опакован", + "PACKAGING_FAILS": "Опаковането не успешно", + "GIVEN_TO_CARRIER": "Дадено на Куриера", + "GIVEN_TO_CUSTOMER": "Дадено на клиента", + "ORDER": "Поръчка", + "CANT_PROCESSING_WITHOUT_PRODUCTS": "Не може да се обработва поръчката без продукти." + }, + "MUTATION": { + "TITLE": "Създай нов склад", + "NAME": "Име", + "LOGO": "Лого", + "PHOTO": "Снимка", + "IS_ACTIVE": "Активен ли е?", + "PRODUCTS_MANUFACTURING": "Производство на изделия", + "CARRIER_REQUIRED": "Необходим е превозвач", + "RIGHT_NOW": "Точно сега", + "ORDERS_PHONE": "Телефон за поръчки", + "CONTACT_PHONE": "Телефон за връзка", + "ORDERS_EMAIL": "Имейл за поръчки", + "CONTACT_EMAIL": "Емейл за връзка", + "FORWARD_ORDERS_WITH": "Препращайте поръчките с", + "USERNAME": "Потребител", + "OLD_PASSWORD": "Стара парола", + "NEW_PASSWORD": "Нова Парола", + "CONFIRM_PASSWORD": "Потвърди Парола", + "PASSWORDS_DO_NOT_MATCH": "Паролите не съвпадат", + "PASSWORD": "Парола", + "COUNTRY": "Държава", + "USA": "САЩ", + "ISRAEL": "Израел", + "BULGARIA": "България", + "CITY": "Град", + "ADDRESS": "Адрес", + "POSTCODE": "Пощенски код", + "COORDINATES": "Координати", + "AUTO_DETECT_COORDINATES": "Автоматично откриване на координати", + "CARRIERS": "Превозвачите", + "USE_ONLY_SPECIFIC_CARRIERS": "Използвайте само конкретни доставчици", + "SELECT_SHAPE_TO_ADD_ZONE": "Изберете форма, за да добавите нова зона", + "CIRCLE": "Кръг", + "SHAPE": "Форма", + "DRAW_SHAPE_ON_MAP": "Начертайте форма на картата", + "MINIMUM_AMOUNT": "Минимална сума", + "DELIVERY_FEE": "Такса за доставка", + "CANCEL": "Отказ", + "ADD": "Добави", + "EDIT": "Редактирай", + "ZONE_NAME": "Име на зона", + "IN_STORE_MODE": "В режим на съхраняване", + "ERRORS": { + "NAME_IS_REQUIRED": "Името на склада е задължително", + "NAME_ATLEAST_3_CHARS": "Името трябва да е с дължина най-малко 3 знака", + "NAME_MORE_THAN_255_CHARS": "Името не може да има повече от 255 знака", + "LOGO_IS_REQUIRED": "Логото е задължително", + "INVALID_URL": "Въведете валиден URL адрес на логото или качете от устройство", + "PHONE_CONTAINS_ONLY_DIGIT": "Телефонният номер може да започва с '+' и трябва да съдържа само само: '-,., (Интервал), #' и цифри", + "INVALID_EMAIL": "Грешен имейл", + "ORDERS_PHONE_IS_REQUIRED": "Телефон за поръчките се изисква", + "CONTACT_PHONE_IS_REQUIRED": "Необходим е телефон за връзка", + "ORDERS_EMAIL_IS_REQUIRED": "Необходим е имейл за поръчките за връзка", + "CONTACT_EMAIL_IS_REQUIRED": "Необходим е имейл за връзка", + "USERNAME_IS_REQUIRED": "Изисква се потребителско име", + "PASSWORD_IS_REQUIRED": "Изисква се парола", + "COORDINATES_ARE_REQUIRED": "Необходими са координати", + "COUNTRY_IS_REQUIRED": "Необходима е държава" + }, + "WIZARD_TITLES": { + "BASIC_INFO": "Основна информация", + "CONTACT_INFO": "Информация за контакт", + "LOCATION": "Местоположение" + }, + "CONTACT_INFO_TAB": { + "ORDER_FORWARDING_EMAIL": "Поръчка за изпращане на имейл", + "ORDER_FORWARDING_PHONE": "телефон за обратна връзка" + } + }, + "NEW_PRODUCT": { + "SELECT_FROM_PRODUCTS_CATALOG": "Изберете от Каталога с продукти", + "CREATE_NEW_PRODUCT": "Създайте нов продукт", + "HOW_TO_ADD": "Как да добавите", + "ADD": "Добави", + "SAVE": "Запази", + "ADD_PRODUCTS_TO_STORE": "Добавяне на продукти за съхранение", + "NOTHING_FOUND": "Нищо не е намерено..." + }, + "SELECT_PRODUCTS": { + "TITLE": "Заглавие", + "DESCRIPTION": "Описание", + "DETAILS": "Детайли", + "IMAGES": "Снимки", + "CATEGORY": "Категория" + }, + "SAVE": { + "PRODUCT_NAME": "Име на продукта", + "PRICE": "Цена", + "COUNT": "Броя", + "DELIVERY": "Доставка", + "TAKEAWAY": "Вземам" + }, + "PLACEHOLDER": { + "EXAMPLE": "Пример: Domino's Pizza", + "IMAGE_URL": "URL адрес на изображението", + "HERE_GOES_A_SHORT_DESCRIPTION": "Кратко описание", + "HERE_GOES_A_DETAILS_ABOUT_PRODUCT_(OPTION)": "Подробности за продукта (опция)", + "REMOVE_IMAGE": "Премахване на изображението" + }, + "CREATE_ORDER_MODAL": { + "TITLE": { + "CREATE_ORDER": "Създаване на поръчка" + }, + "WIZARD_FORM": { + "BUTTON_NEXT": "Продължи", + "BUTTON_PREV": "Обратно", + "BUTTON_DONE": "Завърши Поръчката", + "STEP1": { + "TITLE": "Изберете Опция", + "SELECT_FROM_EXISTING": "Избери Клиент", + "ADD_NEW_CUSTOMER": "Добави Нов" + }, + "STEP2": { + "TITLE": { + "SELECT_CUSTOMER": "Изберете Клиент", + "SELECT_ADD": "Избери/Добави", + "ADD_NEW": "Добави Нов" + }, + "SMART_TABLE": { + "TITLES": { + "FULL_NAME": "Име", + "EMAIL": "Имейл", + "PHONE": "Телефон", + "ADDRESS": "Адрес" + } + } + }, + "STEP3": { + "TITLE": "Създаване Поръчка" + } + } + }, + "PRODUCTS_TAB": { + "PRODUCTS": "Продукти", + "ADD_PRODUCTS": "Добавяне на продукти", + "DELETE": "Изтрий", + "IMAGE": "Изображение", + "TITLE": "Заглавие", + "DESCRIPTION": "описание", + "DETAILS": "детайли", + "CATEGORY": "категория", + "PRICE": "Цена", + "QUANTITY": "количество", + "CLICK_ON_PRODUCT_IMAGE_TO_INCREASE_AVAILABLE_QUANTITY": "Кликнете върху Изображение на продукта, за да увеличите наличното количество", + "AVAILABILITY": "Наличност", + "TYPE": "Тип", + "DELIVERY": "Доставка", + "TAKEAWAY": "За вкъщи" + }, + "NEW_PRODUCT_TYPE": "Нов тип продукт", + "ADD_PRODUCTS": "Добавяне на продукти", + "CREATE_ORDER": "Създаване на поръчка", + "STATUS": "Статус", + "ORDERS": "Поръчки", + "WAREHOUSE": "Склад", + "PRODUCT": "Продукт", + "PRODUCTS": "Продукти", + "ORDER_NUMBER": "Номер на поръчка", + "CANCELLED": "Отменен", + "WAREHOUSE_STATUS": "Състояние на склад", + "CARRIER_STATUS": "Статус на превозвача", + "PAID": "Платен", + "CARRIER": "Носител", + "CREATED": "Създаден", + "ELAPSED": "Изминало", + "CONTACT_DETAILS": "Данни за контакт", + "EMAIL": "Имейл", + "PHONE": "Телефон", + "ORDERS_FORWARDING_DETAILS": "Поръчки за изпращане на подробности", + "ORDERS_FORWARDING_WITH": "Поръчки Препращане с", + "MANAGE_STORE": "Управление на магазина", + "TOP_PRODUCTS": "Топ продукти", + "PRODUCTS_MANUFACTURING": "Производство на изделия", + "CARRIER_REQUIRED": "Необходим е превозвач", + "MANAGE_WAREHOUSE": "Управление на склада", + "MANAGE_STORE_PRODUCTS_&_ORDERS": "Управление на складови продукти и поръчки" + }, + "SIMULATION_VIEW": { + "SIMULATION": "Симулация", + "PURCHASE_PRODUCTS": "Купете продукти", + "CREATE_INVITE_REQUEST": "Създаване на заявка за покана", + "SEND": "Изпращам", + "CREATE_USER": "Създаване на потребител", + "ORDER_CONFIRM": "Потвърдете поръчката", + "ORDER_CANCEL": "Отмяна на поръчката", + "PRODUCTS": "Продукти", + "STORE": "Магазин", + "ORDER": "Поръчка", + "INVITE_REQUEST": "Искане за покана", + "INVITE_USER": "Поканете потребител", + "TAB_BUTTONS": { + "PRODUCTS": "Продукти", + "ORDER_HISTORY": "История на поръчките" + }, + "USER_MUTATION": { + "TITLE": "Създай потребител", + "NAME": "Име", + "EMAIL": "Имейл", + "COUNTRY": "Държава", + "CITY": "Град", + "USA": "САЩ", + "ISRAEL": "Израел", + "BULGARIA": "България", + "ADDRESS": "Адрес", + "POSTCODE": "Пощенски код", + "COORDINATES": "Координати", + "AUTO_DETECT_COORDINATES": "Автоматично откриване на координати", + "ERRORS": { + "FIRST_NAME_IS_REQUIRED": "Първо име е задължително", + "LAST_NAME_IS_REQUIRED": "Фамилно име е задължително", + "INVALID_EMAIL": "Невалиден Имейл", + "EMAIL_IS_REQUIRED": "Имейл е задължителен", + "COORDINATES_ARE_REQUIRED": "Координати са задължителни" + }, + "WIZARD_TITLES": { + "ADDITIONAL_INFO": "Допълнителна информация", + "LOCATION": "Местоположение" + } + }, + "SMART_TABLE": { + "TITLE": "Заглавие", + "ID": "Идент. номер", + "IMAGE": "Изображение" + }, + "ORDER_INFO": { + "STATUSES": [ + { + "TITLE": "Подготвяме поръчката!", + "DETAILS": "Ще я получите между %t минути.", + "NOT_PAID_NOTE": "Подгответе портфейла си (%s в брой)." + }, + { + "TITLE": "Доставчика е в движение!", + "DETAILS": "Ще получите реда в %t min.", + "NOT_PAID_NOTE": "Подгответе портфейла си (%s в брой)." + }, + { + "TITLE": "Проверете си вратата!", + "DETAILS": "Ще получите реда в секунди.", + "NOT_PAID_NOTE": "Подгответе портфейла си (%s в брой)." + }, + { + "TITLE": "Поръчка завършена!", + "DETAILS": "Благодарим Ви, че използвате Ever", + "NOT_PAID_NOTE": "" + } + ], + "DELIVERY_STATUS": { + "WE": "Ние", + "CARRIER": "Доставчик", + "YOU": "Вие" + }, + "DELIVERY_WRONG": { + "DELIVERY_WRONG": "Доставката не беше правилно!", + "PROCESSING_WRONG": "Обработката не беше правилно!", + "TRY_AGAIN": "Моля, опитайте отново.", + "CALL_FOR_DETAILS": "Обадете се за подробности" + } + }, + "INSTRUCTIONS_STEPS": { + "INSTRUTIONS": "инструкции", + "CREATE_USER_STEP": { + "CREATE_USER": "Създаване на потребител", + "STEP_1": "Етап 1", + "ORDER": "Поръчка", + "TO_CONTINUE_IS_REQUIRED_TO_REGISTER_IN_THE_SYSTEM": "Стъпка За да продължите, трябва да се регистрирате в системата", + "CLICK_ON_BUTTON_CREATE_USER": "Кликнете върху бутона 'Създаване на потребител'", + "FILL_THE_FORM_FOR_ADDITIONAL_INFO": "Попълнете формуляра за допълнителна информация (по избор)", + "FILL_THE_FORM_FOR_LOCATION_AND_PRESS_DONE_BUTTON": "Попълнете формуляра за местоположение и натиснете бутона DONE" + }, + "ORDER_STEP": { + "ORDER": "Поръчка", + "STEP_2": "Стъпка 2.", + "CREATE_ORDER": "Създаване на поръчка", + "CHOICE_SOME_PRODUCTS_FROM_THE_TABLE": "Избор на някои продукти от таблицата (можете да видите повече подробности за това кога натиснете името и изображението му)", + "SELECT_PRODUCT": "За да бъде избран, един продукт трябва да натисне реда си.", + "SELECT_BUTTON_ORDER_TO_CREATE_ORDER": "Натиснете бутона 'Поръчка', за да създадете поръчка с избрания продукт.", + "REVIEW_ORDER_HISTORY": "Преглед на историята на поръчките:", + "ON_PRESS_ORDER_HISTORY_TAB": "Натиснете 'История на поръчките', за да видите всички поръчки.", + "HERE_YOU_CAN_SEE_DETAILS_ABOUT_EACH_ORDER": "Тук можете да видите подробности за всяка поръчка", + "PRESS_ON_CARRIER_ORDER_OR_PRODUCT_NAME": "Натиснете върху носителя, поръчката или името на продукта за повече информация." + }, + "CONFIRM_OR_CANCEL_STEP": { + "STEP_3": "Стъпка 3", + "CONFIRM_CANCEL_ORDER": "Потвърдете / отменете поръчката", + "REAL_TIME": "Реално време", + "TRACK_STATUS_ON_YOUR_ORDER": "Проследяване на състоянието на поръчката ви.", + "ELAPSED_TIME_FROM_CREATE_TO_DELIVERED": "Проследяване на състоянието на поръчката ви.", + "SHOWS_MERCHANT_LOCATION": "Показва местоположението на търговеца.", + "SHOWS_CARRIER_LOCATION": "Показване на местоположението на оператора.", + "POSSIBILITIES": "Възможности:", + "SLIDER_REVIEW_OF_THE_ALL_PRODUCTS": "Преглед на плъзгачите на всички продукти.", + "CAN_CANCEL_IT_WITH_ORDER_CANCEL_BUTTON": "Докато поръчката не бъде доставена, потребителят я анулира с бутона 'Откажи поръчката'.", + "AFTER_THE_ORDER_IS_DELIVER_USER_CAN_CLICK_BUTTON_ORDER_CONFIRM_TO_CONTINUE": "След подаването на поръчката потребителят може да кликне върху бутона 'Потвърждаване на поръчката', за да продължи." + }, + "INVITE_STEP": { + "TO_CONTINUE_ITs_REQUIRED_TO_BE_INVITED_IN_THE_SYSTEM": "За да продължите, трябва да бъдете поканени в системата:", + "SEND_INVITE_REQUEST": "Изпратете 'Искане за покана' до системата от формуляра, който ще бъде отворен след натискане на бутона Покана за заявка.", + "ALL_INVITE_REQUESTS_ARE_REVIEWED": "Всички заявки за покани се преглеждат от администратор и те могат да бъдат поканени, ако системата е налице близо до местоположението ви (за тестване можете веднага да направите бутона Покани потребител).", + "AFTER_YOU_GET_INVITED_BEFORE": "След като се поканите, можете лесно да влезете в системата, просто трябва да въведете вашия поканен код, който ще бъде предоставен от системата (тук ще видите", + "AFTER_YOU_GET_INVITED_AFTER": "кода след натискане на бутона Покани, и можете да го въведете, когато натиснете бутона Създаване на потребител)." + } + } + }, + "CUSTOMERS_VIEW": { + "TITLE": "Клиенти", + "DELETE_CUSTOMERS": "Изтрий Селектирани", + "CREATE_CUSTOMER": "Създай Клиент", + "DELETE": "Изтрий", + "CREATE": "създавам", + "BAN": "BAN", + "UNBAN": "UNBAN", + "MANAGE_CUSTOMER": "Управление на клиента", + "CUSTOMER": "Клиент", + "CUSTOMERS_DEVICES": "Клиентски устройства", + "INVITE": "Покана", + "NOT_INVITED_ONLY": "Не са поканени само", + "ORDER": "Поръчка", + "ORDERS_STATISTICS": "Статистика на поръчките", + "NUMBER_OF_ORDERS": "Общо поръчки", + "CANCELED_ORDERS": "Отказани поръчки", + "COMPLETED_ORDERS_TOTAL": "Цена общо поръчки", + "Order": "Поръчка", + "CANCEL_ORDER": "Отмяна на поръчката", + "CATEGORY": "Категория", + "ORDERS_HISTORY": "История на поръчките", + "AVAILABLE_PRODUCTS": "Налични продукти", + "NEARBY_STORES": "Магазини наблизо", + "INVITES_REQUESTS_MANAGEMENT": "Покана за управление на заявки", + "INVITES_MANAGEMENT": "Управлявай поканите", + "DESCRIPTION": "Описание", + "DETAILS": "Детайли", + "MAKE_A_CUSTOM_ORDER": "Направете персонална заявка", + "PRODUCT_COUNT": "Продукт брой", + "QUANTITY_CAN'T_BE_EMPTY": "Количеството не може да бъде празно", + "QUANTITY_CAN'T_BE_0": "Количеството не може да бъде 0", + "NOT_ENOUGH_PRODUCTS_AVAILABLE": "Няма достатъчно продукти", + "ORDER_INFO": "Информация за поръчката", + "ORDER_ID": "Id на поръчката", + "STORE_ID": "Идент. № на магазина", + "CARRIER_ID": "Идентификационен номер на куриера", + "NO_CARRIER": "Няма превозвач", + "SMART_TABLE_COLUMNS": { + "IMAGE": "Изображение", + "NAME": "Име", + "EMAIL": "Eлектронна Поща", + "PHONE": "Телефон", + "ADDRESS": "Адрес", + "ORDERS_QTY": "Поръчки количество", + "COUNTRY": "Държава", + "CITY": "Град", + "STREET_ADDRESS": "Адрес на улица", + "HOUSE": "Къща", + "APARTMENT": "Апартамент", + "INVITE_CODE": "Код за покана", + "INVITED_DATE": "Поканена дата", + "ORDER_NUMBER": "Номер на поръчка", + "WAREHOUSE": "Склад", + "CARRIER": "Носител", + "PRODUCT_LIST": "Списък на продуктите", + "STATS": "Статистика", + "DELIVERY_TIME": "Време за доставка", + "CREATED": "Създаден", + "ACTIONS": "Мерки", + "PAID": "Платен", + "COMPLETED": "Завършен", + "CANCELLED": "Отменен", + "NOT_DELIVERED": "Не е доставено", + "PRODUCT": "Продукт", + "PRICE": "Цена", + "STORE": "Магазин", + "AVAILABLE_COUNT": "Наличен брой", + "ORDER": "Поръчка", + "STATUS": "Статус" + }, + "EDIT": { + "EDIT_CUSTOMER": "Редактиране на клиент", + "BASIC_INFO": "Основна информация", + "SAVE": "Запази" + }, + "DEVICE": { + "ALL_DEVICE": "Всички устройства", + "DEVICE_ID": "ID на устройството", + "ID": "Идентификационен Номер", + "UPDATE": "Актуализация", + "LANGUAGE": "език", + "TYPE": "тип", + "TYPEU": "Тип", + "UUID": "Универсален уникален идентификатор", + "DEVICE_UUID": "Универсален уникален идентификатор", + "UPDATE_DEVICE": "Актуализиране на устройството", + "CUSTOMERS_DEVICES": "Клиентски устройства", + "DELETE": "Изтрий", + "CREATE": "създавам" + }, + "WAREHOUSE": { + "WAREHOUSE_INFO": "Информация за складовете", + "WAREHOUSE_ID": "Идент. № на склада", + "WAREHOUSE_NAME": "Име на склада" + }, + "CUSTOMER_VIEW": { + "MANAGE_CUSTOMER": "Управление на клиента", + "EDIT": "Редактирай", + "CUSTOMER": "Клиент" + }, + "INVITES_VIEW": { + "DELETE": "Изтрий", + "INVITE": "Покана" + } + }, + "CARRIERS_VIEW": { + "TITLE": "Доставчици", + "DELETE_CARRIERS": "Изтрий селектирани", + "CREATE_CARRIER": "Създай Доставчик", + "DELETE": "Изтрий", + "CREATE_BUTTON": "създавам", + "ACTIVE_AND_AVAILABLE_ORDERS": "Активни и налични поръчки", + "ORDERS_HISTORY": "История на поръчките", + "TRACK": "Проследи", + "SMART_TABLE_COLUMNS": { + "IMAGE": "Изображение", + "NAME": "Име", + "PHONE": "Телефон", + "STATUS": "Статус", + "ADDRESS": "Адрес", + "DELIVERIES": "Доставки" + }, + "CARRIER_PAGE": { + "WAREHOUSE": "Склад", + "CUSTOMER": "Клиент", + "SAVE": "Запази", + "EDIT": "Редактиране", + "CARRIER_INFO": "Информация за превозвача", + "CARRIER_ID": "Идентификационен номер на оператора", + "REGISTER_NEW_CARRIER": "Регистрирайте нов превозвач", + "WAREHOUSE_STATUS": "Статус на склада", + "CARRIER_STATUS": "Статус на превозвача", + "CREATED": "Създаден", + "ARRIVED_TO_CUSTOMER": "Пристигна до клиента", + "FAILED": "Провалена", + "DELIVERED": "Доставени", + "CLIENT_REFUSE_ORDER": "Поръчка отказана от клиента", + "AVAIBLE_ORDER_TO_PICK_UP": "Налични поръчки за набиране (всеки превозвач може да вземе няколко поръчки)", + "ACTIVE": "Активен", + "CARRIER_CAN_BE_SHARED": "Превозвачът може да бъде споделен?", + "NOT_ACTIVE": "Неактивен", + "WORKING": "Работещ", + "NOT_WORKING": "Не работещ", + "SELECT_CARRIER": "Изберете оператор", + "CARRIER_ORDERS_STATUS": "Статус на Поръчките на Куриера", + "Start": "Започни", + "PICKED_UP_ORDER": "Получена поръчка", + "CANCEL": "Отказ", + "Arrived To Client": "Пристигнал до клиента", + "No Carrier": "Няма превозвач", + "Order Selected For Delivery": "Поръчката е избрана за доставка", + "Order Picked Up": "Поръчката е взета", + "Order In Delivery": "Поръчка се доставя", + "Delivered": "Доставени", + "Delivery Issues": "Проблеми при доставката", + "Client Refuse to Take Order": "Клиентът отказва да приеме поръчка", + "BAD_STATUS": "BAD_STATUS", + "Created": "Създаден", + "Confirmed": "Потвърдено", + "Processing": "Обработване", + "Allocation Started": "Разпределението е започнало", + "Allocation Finished": "Разпределението е завършено", + "Packaging Started": "Опаковането започна", + "Packaged": "Опакован", + "Given to Carrier": "Дадено на Carrier", + "Allocation Failed": "Разпределението не бе успешно", + "Packaging Failed": "Опаковането не бе успешно", + "LOCATION": "Местоположение", + "TIME": "Време", + "NAME": "Име" + }, + "EDIT": { + "EDIT_CARRIER": "Редактиране на превозвача", + "BASIC_INFO": "Основна информация", + "LOCATION": "Местоположение", + "PHOTO_URL": "URL адрес на снимката", + "CONTACT_PHONE": "Свържете се с телефона", + "FIRST_NAME": "Първо име", + "LAST_NAME": "Фамилия" + }, + "CREATE": { + "BASIC_INFO": "Основна информация", + "LOCATION": "Местоположение" + }, + "TRACK_PAGE": { + "TRACK_ALL_WORKING_CARRIERS": "Проследете всички работещи превозвачи", + "FILTER_CARRIERS": "Филтрирай превозвачите", + "PHONE": "Телефон", + "EMAIL": "Эл. адрес", + "ADDRESS": "Адрес", + "DELIVERY_COUNT": "Счетчик доставки" + } + }, + "PRODUCT_TYPE_VIEW": { + "TITLE": "Нов продукт", + "WIZARD_FORM": { + "VALIDATION_MESSAGES": { + "TITLE": "Заглавието е задължително", + "THE_LENGHT_OF_THE_TITLE": "Дължината на заглавието трябва да е максимум 255 знака!", + "IMAGE": "Снимката е задължителна", + "DESCRIPTION": "Описанието е задължително", + "LANGUAGE": "Език е задължителен", + "THE_LENGHT_OF_THE_DESCRIPTION": "Дължината на описанието трябва да бъде максимум 255 знака!", + "PRICE": "Цената е задължителна", + "COUNT": "Брой продукти са задължителни" + } + } + }, + "SHARED": { + "FORMS": { + "ERRORS": { + "FIRST_NAME_REQUIRED": "Името е задължително", + "LAST_NAME_REQUIRED": "Изисква се фамилно име", + "USERNAME_REQUIRED": "Изисква се потребителско име", + "PASSWORD_REQUIRED": "Изисква се парола", + "PHONE_REQUIRED": "Необходим е телефон", + "LOGO_URL_REQUIRED": "URL адресът на логото трябва да започва с 'https'", + "IS_ACTIVE": "Необходимо е полето за активност", + "COUNTRY_REQUIRED": "Необходима е държава", + "CITY_REQUIRED": "Необходима е държава", + "STREET_ADDRESS_REQUIRED": "Изисква се уличен адрес", + "HOUSE_REQUIRED": "Необходим е номер на къщата", + "COORDINATES_REQUIRED": "Необходими са координати", + "MUST_CONTAIN_ONLY_LETTERS": "Трябва да съдържа само букви", + "PHONE_MUST_CONTAINS_ONLY(specail_signs)AND_DIGIT_CHARACTER": "Телефонният номер може да започва с '+' и трябва да съдържа само само: '-,., (Интервал), #' и цифри" + } + }, + "USER": { + "FORMS": { + "BASIC_INFO": { + "TITLE": "Основно Инфо.", + "FIRST_NAME": "Първо Име", + "FIRST_NAME_OPTIONAL": "Име (незадължително)", + "LAST_NAME_OPTIONAL": "Фамилно име (по избор)", + "PHOTO_URL": "URL адрес на снимката", + "PICTURE_URL": "URL адрес на картината (по избор)", + "EMAIL": "Електронна Поща", + "EMAIL_OPTIONAL": "Имейл (по избор)", + "ERRORS": { + "INVALID_EMAIL": "Невалиден Имейл", + "EMAIL_IS_ALREADY_IN_USE": "Имейлът вече е в употреба" + } + } + } + }, + "WAREHOUSE": { + "ORDER_MODAL": { + "MAKE_ORDER": "Направи Поръчка", + "ONLY_AVAILABLE": "Покажи наличните продукти", + "ORDER": "Поръчай", + "SMART_TABLE": { + "TITLES": { + "IMG": "Снимка", + "PRODUCT": "Продукт", + "PRICE": "Цена", + "AVAILABLE": "Налично", + "AMOUNT": "Количество", + "COMMENT": "Коментар" + } + } + } + } + }, + "MENU_VIEW": { + "DASHBOARD": "Табло", + "STORES": "Магазини", + "PRODUCTS": { + "PRODUCTS": "Продукти", + "MANAGEMENT": "Управление", + "CATEGORIES": "Категории" + }, + "CUSTOMERS": { + "CUSTOMERS": "Клиенти", + "MANAGEMENT": "Управление", + "INVITES": "Покани" + }, + "CARRIERS": "Превозвачи", + "SIMULATION": "Симулация", + "SETUP": "Настройки" + }, + "CATEGORY_VIEW": { + "TITLE": "Заглавие", + "IMAGE": "Изображение", + "CREATE_BUTTON": "Създай", + "DELETE": "Изтрий", + "EDIT": { + "EDIT_CATEGORY": "Обнови Категория", + "CATEGORY_NAME": "Име на категория", + "ENTER_THE_CATEGORY_NAME": "Въведете името на категорията", + "DONE": "Свършен" + }, + "CREATE": { + "CREATE_CATEGORY": "Създайте категория", + "CATEGORY_NAME": "Име на категория", + "ENTER_THE_CATEGORY_NAME": "Въведете името на категорията", + "PHOTO": "Снимка", + "BROWSE": "Разгледай", + "INVALID_URL": "Невалиден адрес", + "REMOVE_IMAGE": "Премахване на изображението", + "PHOTO_OPTIONAL": "Снимка (по избор)", + "DONE": "Свършен" + } + }, + "PLACEHOLDER": { + "EXAMPLE": "Пример: Domino's Pizza", + "IMAGE_URL": "URL адрес на изображението", + "HERE_GOES_A_SHORT_DESCRIPTION": "Кратко описание", + "HERE_GOES_A_DETAILS_ABOUT_PRODUCT_(OPTION)": "Подробности за продукта (опция)", + "REMOVE_IMAGE": "Премахване на изображението", + "PASSWORD": "Парола", + "LATITUDE": "Географска ширина", + "LONGITUDE": "Географска Дължина", + "APARTMENT": "Апартамент", + "HOUSE": "Къща", + "STREET": "Улица", + "ZIP": "Пощенски код", + "CITY": "Град", + "FIND_ADDRESS": "Намерете адрес" + }, + "STATUS_TEXT": { + "Created": "Създаден", + "Confirmed": "Потвърдено", + "Processing": "Обработване", + "Allocation Started": "Разпределението е започнало", + "Allocation Finished": "Разпределението е завършено", + "Packaging Started": "Опаковането започна", + "Packaged": "Опакован", + "Given to Carrier": "Дадено на Carrier", + "Allocation Failed": "Разпределението не бе успешно", + "Packaging Failed": "Опаковането не бе успешно", + "No Carrier": "Няма превозвач", + "Order Selected For Delivery": "Поръчката е избрана за доставка", + "Order Picked Up": "Поръчката е взета", + "Order In Delivery": "Поръчка се доставя", + "Arrived To Client": "Пристигнал до клиента", + "Delivered": "Доставени", + "Delivery Issues": "Проблеми при доставката", + "Client Refuse to Take Order": "Клиентът отказва да приеме поръчка", + "Given to Customer": "Дадено на клиента", + "BAD_STATUS": "BAD_STATUS" + }, + "ELAPSED_TIME": { + "TITLE": "Изминалото време" + }, + "CONFIRM_MODAL": { + "ARE_YOU_SURE": "Сигурен ли си", + "ARE_YOU_SURE_YOU_WANT_TO_INCREASE": "Наистина ли искате да увеличите количеството продукти?", + "ARE_YOU_SURE_YOU_WANT_TO_DECREASE": "Наистина ли искате да намалите количеството продукти??", + "YES": "Да", + "NO": "Не" + }, + "NO_SERVER_VIEW": { + "NO_SERVER": "Няма връзка със сървъра" + }, + "BUTTON_NEXT": "Продължи", + "BUTTON_PREV": "Обратно", + "BUTTON_DONE": "Завърши Поръчката", + "TERRAIN": "Терен", + "SATELLITE": "Спътник", + "LOCATION": "Местоположение", + "ROAD_MAP": "Пътна карта", + "Manage warehouse": "Управление на складови продукти и поръчки", + "Warehouse": "Склад", + "Create Warehouse": "Създаване на склад", + "SIMULATION": "Симулатции", + "Purchase products": "Купете продукти", + "Manage": "Управлявай", + "Orders": "Поръчки", + "Confirmed": "Потвърдено", + "In Delivery": "В процес на доставка", + "Not Confirmed": "Непотвърдено", + "Not paid": "Неплатен", + "Cancelled": "Отменен", + "All": "Всички", + "CANCEL": "Отказ", + "Default Settings": "Настройките по подразбиране", + "Products Manufacturing": "Производство на изделия", + "Carrier required before sale": "Превозвачът е необходим преди продажбата", + "New Product Type": "Превозвачът е необходим преди продажбата", + "Products": "Продукти", + "Product": "Продукт", + "Title": "Заглавие", + "Picture Url": "Линк към снимката", + "Description": "Описание", + "Details": "Детайли", + "Price": "Цена", + "CATEGORY": "Категория", + "LANGUAGE": "Език", + "BROWSE": "РАЗГЛЕДАЙ", + "ENGLISH": "Английски", + "HEBREW": "Иврит", + "RUSSIAN": "Руски", + "BULGARIAN": "Български", + "SPANISH": "испански", + "FRENCH": "Френски", + "SELECT": "Изберете", + "Name": "Име", + "Id": "Идент. номер", + "Warehouse name is required": "Името на склада е задължително", + "Name must be at least 1 characters long": "Името трябва да е с дължина поне 1 знак", + "Title cannot be more than 255 characters long": "Заглавието не може да има повече от 255 знака", + "Logo": "Лого", + "Warehouse logo is required": "Необходимо е лого на склад", + "is Active": "Активен ли е", + "right now": "точно сега", + "Unselected": "Unselected", + "Phone": "Телефон", + "Email": "Електронна поща", + "Username": "Потребител", + "Username is required": "Изисква се потребителско име", + "Password": "Парола", + "Country": "Държава", + "USA": "САЩ", + "Israel": "Израел", + "Bulgaria": "България", + "City": "Град", + "Address": "Адрес", + "Postcode": "Пощенски код", + "Coordinates": "Координати", + "Auto detect coordinates": "Автоматично откриване на координати", + "Carriers": "Превозвачите", + "Carrier": "Превозвач", + "Use only specific carriers": "Използвайте само конкретни носители", + "Manage carrier and deliveries": "Управлявайте превозвача и доставките", + "Register New Carrier": "Регистрирайте нов превозвач", + "Create User": "Създаване на потребител", + "OPTIONAL": "по избор" +} diff --git a/packages/admin-web-angular/src/assets/i18n/bg.json b/packages/admin-web-angular/src/assets/i18n/bg.json new file mode 100644 index 0000000..8af1d05 --- /dev/null +++ b/packages/admin-web-angular/src/assets/i18n/bg.json @@ -0,0 +1,1169 @@ +{ + "COMMON": { + "SAVE": "Save", + "CANCEL": "Cancel", + "USA": "USA", + "ISRAEL": "Israel", + "BULGARIA": "Bulgaria" + }, + "ORDER_VIEW": { + "ORDER_HEADER_INFO": { + "ORDER": "Order", + "MANAGE_ORDER": "Manage Order", + "TOTAL": "Total" + }, + "ORDER_PRODUCT_INFO": { + "ORDER_PRODUCTS": "Order Products", + "ADD_PRODUCTS": "Add products", + "REMOVE_PRODUCTS": "Remove Products", + "CANCEL_ORDER": "Cancel Order", + "THE_ORDER_IS_CANCELED": "The order is canceled", + "THE_ORDER_IS_GIVEN_TO_CARRIER": "The order is given to carrier.", + "THE_ORDER_IS_DELIVERED": "The order is delivered.", + "ADD_PRODUCTS_MODAL": "Add Products", + "ADD": "Add", + "SUCCESS_TOAST": "Products were added to the order", + "ERROR_TOAST": "Error, something went wrong", + "SMART_TABLE": { + "NAME": "Name", + "QTY": "QTY", + "PRICE": "Price", + "IMAGE": "Image", + "COMMENT": "Comment" + } + }, + "ORDER_SIDEBAR": { + "CONTACT_DETAILS": "Contact Details", + "WAREHOUSE": "Warehouse", + "CUSTOMER": "Customer", + "CARRIER": "Carrier", + "QTY": "qty" + }, + "LOCATION_INFO": { + "MAP": "Map", + "DELIVERY_DISTANCE": "Delivery Distance:" + } + }, + "DASHBOARD_VIEW": { + "TOTAL_CUSTOMER": "Total Customers", + "TOTAL_QUANTITY_OF_EXISTING_CUSTOMERS": "Total quantity of existing customers", + "TOTAL_COMPLETED_ORDERS": "Total completed orders", + "TOTAL_QUANTITY_OF_COMPLETED_ORDERS": "Total quantity of completed orders", + "TOTAL_REVENUE": "Total Revenue", + "TOTAL_SUM_SUM_OF_ALL_COMPLETED_ORDERS": "Total sum of all completed orders", + "TODAYs_CUSTOMERS": "Today's Customers", + "TODAYs_QUANTITY_OF_REGISTERED_CUSTOMERS": "Today's quantity of registered customers", + "TODAYs_COMPLETED_ORDERS": "Today's completed orders", + "TODAYs_QUANTITY_OF_NEW_COMPLETED_ORDERS": "Today's quantity of new completed orders", + "TODAYs_REVENUE": "Today's Revenue", + "TODAYs_SUM_OF_COMPLETED_ORDERS": "Today's sum of completed orders", + "TILL_AVERAGE": "till average", + "BETTER_THAN_AVERAGE": "better than average", + "SELECT_COMPONENT": { + "STORES": "Stores", + "CONTACT_DETAILS": "Contact Details", + "PHONE": "Phone", + "EMAIL": "Email", + "ORDERS_FORWARDING_WITH": "Orders Forwarding With", + "SELECT_STORE": "Select store" + }, + "CHARTS": { + "ORDERS": "Orders", + "PROFIT": "Profit", + "TOTAL_ORDERS": "Total orders", + "TOTAL_COMPLETED_ORDERS": "Total completed orders", + "TOTAL_CANCELLED_ORDERS": "Total canceled orders", + "TOTAL_REVENUE_ALL_ORDERS": "Total revenue from all orders", + "TOTAL_REVENUE_COMPLETED_ORDERS": "Total revenue from completed orders", + "TOTAL_LOST_REVENUE_CANCELLED_ORDERS": "Total lost revenue from canceled orders", + "PAYMENT": "Payment", + "CANCELED": "Canceled", + "ALL_ORDERS": "All Orders", + "TODAY": "Today", + "LAST_WEEK": "Last Week", + "LAST_MONTH": "Last Month", + "CURRENT_YEAR": "Current Year", + "YEARS": "By Year", + "CUSTOM_PERIOD": "Custom Period", + "SELECT_PERIOD": "Select Period", + "FROM": "From", + "TO": "To", + "SELECT": "Select", + "LABELS": { + "WEEK": "Week", + "WEEKDAYS": { + "MON": "Mon", + "TUE": "Tue", + "WED": "Wed", + "THU": "Thu", + "FRI": "Fri", + "SAT": "Sat", + "SUN": "Sun" + }, + "MONTHS": { + "JAN": "Jan", + "FEB": "Feb", + "MAR": "Mar", + "APR": "Apr", + "MAY": "May", + "JUN": "Jun", + "JUL": "Jul", + "AUG": "Aug", + "SEP": "Sep", + "OCT": "Oct", + "NOV": "Nov", + "DEC": "Dec" + } + } + } + }, + "HEADER_VIEW": { + "ADMIN": "Admin", + "EVER": "Ever", + "PROFILE": "Profile", + "LOG_OUT": "Log out" + }, + "FOOTER_VIEW": { + "COPY_RIGHT": "Copyright © 2016-present", + "ALL_RIGHTS_RESERVED": "All rights reserved" + }, + "PROFILE_VIEW": { + "PROFILE_PAGE": "Profile page", + "BASIC_INFO": "Basic Info", + "ACCOUNT": "Account", + "USERNAME": "Username", + "ERROR": "Error", + "EMAIL": "Email", + "FIRST_NAME": "First Name", + "FIRST_NAME_OPTIONAL": "First Name (optional)", + "LAST_NAME": "Last Name (optional)", + "PICTURE_URL": "Picture url (optional)", + "BROWSE": "Browse", + "REMOVE": "Remove", + "SAVE": "Save", + "OLD_PASSWORD": "Old password", + "NEW_PASSWORD": "New password", + "REPEAT_NEW_PASSWORD": "Repeat new password", + "INVALID_EMAIL_ADDRESS": "Invalid email address", + "INVALID_URL": "Invalid URL", + "NAME_MUST_CONTAIN_ONLY_LETTERS": "Name must contain only letters", + "PASSWORDS_DO_NOT_MATCH": "Passwords do not match", + "SUCCESSFULLY_CHANGE_PASSWORD": "Successfully change password" + }, + "PRODUCTS_VIEW": { + "DELETE": "DELETE", + "CREATE": "CREATE", + "EDIT_VIEW": { + "EDIT_PRODUCT": "Edit Product", + "BASIC_INFO": "Basic Info", + "SAVE": "Save" + } + }, + "FAKE_DATA": { + "FAKE_DATA_GENERATOR": "Fake Data Generator", + "GENERATE_ALL": "Generate All", + "CREATE_100_USERS": "Create 100 customers", + "CREATE_100_CARRIERS": "Create 100 carriers", + "CREATE_100_WAREHOUSES": "Create 100 warehouses", + "SETUP": "SETUP", + "GENERATE_INITIAL_DATA": "Generate Initial Data", + "CREATE_INVITE": "CREATE INVITE", + "HARDCODED_DATA": "Hardcoded data", + "CLEAR_ALL": "CLEAR ALL", + "GENERATE_HARDCODED_ONLY": "Generate Hardcoded Only", + "INCLUDED_HARDCODED_DATA": "Include hardcoded data", + "CREATE_1st_INVITE": "Create 1st invite", + "CREATE_2st_INVITE": "Create 2nd invite", + "CREATE_3st_INVITE": "Create 3rd invite", + "CREATE_4st_INVITE": "Create 4rd invite", + "CREATE_CUSTOMER": "Create Customer", + "CREATE_USER": "CREATE USER", + "CREATE_1st_USER": "Create 1st user (using 1st invite)", + "CREATE_CARRIER": "CREATE CARRIER", + "CREATE_1st_CARRIER": "Create 1st carrier", + "CREATE_2nd_CARRIER": "Create 2nd carrier", + "CREATE_3rd_CARRIER": "Create 3rd carrier", + "CREATE_PRODUCT": "CREATE PRODUCT", + "CREATE_PEPERONI&MUSHROOM_PIZZA_PRODUCT": "Create Peperoni & Mushroom pizza product", + "CREATE_SUSHI&CAVIAR_PRODUCT": "Create Sushi & Caviar product", + "CREATE_SUSHI_MIX_PRODUCT": "Create Sushi mix product", + "CREATE_PASTA_PRODUCT": "Create Pasta product", + "CREATE_SUSHI_BOX_PRODUCT": "Create Sushi box product", + "CREATE_PEPERONI&TOMATO_PIZZA_PRODUCT": "Create Peperoni & Tomato pizza product", + "CREATE_WAREHOUSE": "CREATE WAREHOUSE", + "CREATE_1st_WAREHOUSE": "Create 1st warehouse", + "CREATE_2nd_WAREHOUSE": "Create 2nd warehouse", + "CREATE_3rd_WAREHOUSE": "Create 3rd warehouse", + "CREATE_WAREHOUSE_PRODUCT": "CREATE WAREHOUSE PRODUCT", + "CREATE_1st_WAREHOUSE_PRODUCTS(using_product_number_1_2_3_4_5_and_6)": "Create 1st warehouse products (using product number 1, 2, 3, 4, 5 and 6)", + "CREATE_3rd_WAREHOUSE_PRODUCTS(using_1st_product)": "Create 3rd warehouse products (using 1st product)", + "CREATE_2nd_WAREHOUSE_PRODUCTS(using_product_number_1_2_and_3": "Create 2nd warehouse products (using product number 1, 2, and 3", + "UPDATE_WAREHOUSE_GEO_LOCATION": "UPDATE WAREHOUSE GEO LOCATION", + "UPDATE_1ST_WAREHOUSE_GEO_LOCATION": "Update 1st warehouse geo location", + "CREATE_1RD_ORDER(using_1st_warehouse_1st_user_and_1st_product)": "Create 1rd order (using 1st warehouse, 1st user and 1st product)", + "CREATE_2ND_ORDER(using_1st_warehouse_1st_user_and_2nd_product)": "Create 2nd order (using 1st warehouse, 1st user and 2nd product)", + "CONFIRM_ORDER": "CONFIRM ORDER", + "CREATE_ORDER": "CREATE ORDER", + "CONFIMR_1st_ORDER": "Confirm 1st order", + "CONFIMR_2nd_ORDER": "Confirm 2nd order", + "SETUP_MERCHANTS": { + "SETUP_MERCHANTS": "Setup merchants", + "PREV": "PREV", + "NEXT": "NEXT", + "ADD": "Add", + "BACK": "Back", + "SAVE": "Save", + "SELECT": "Select", + "CREATE": "Create", + "HOW_TO_SET_UP": { + "HOW_TO_SET_UP": "How to setup" + }, + "STEPPER": { + "ACCOUNT": "Account", + "BASIC_INFO": "Basic info", + "CONTACT_INFO": "Contact info", + "LOCATION": "Location", + "PAYMENTS": "Payments", + "MANUFACTURING": "Manufacturing", + "DELIVERY_AND_TAKEAWAY_SETTINGS": "Delivery & Takeaway Settings", + "ORDERS_SETTINGS": "Orders Settings", + "PRODUCT_CATEGORIES": "Product categories", + "PRODUCTS": "Products" + }, + "ACCOUNT": { + "ACCOUNT": "Account", + "EMAIL_ADDRESS": "Email address", + "EMAIL": "Email", + "PASSWORD": "Password", + "REPEAT_PASSWORD": "Repeat password", + "EMAIL_IS_REQUIRED": "Email is required", + "INVALID_EMAIL_FORMAT": "Invalid email format", + "USERNAME": "Username", + "USERNAME_IS_REQUIRED": "Username is required", + "USERNAME_MUST_BE_AT_LEAST_3_CHARACTERS": "Username must be at least 3 characters", + "PASSWORD_IS_REQUIRED": "Password is required", + "PASSWORD_MUST_BE_AT_LEAST_4_CHARACTERS": "Password must be at least 4 characters", + "REPEAT_PASSWORD_IS_REQUIRED": "Repeat password is required", + "PASSWORDS_DO_NOT_MATCH": "Passwords do not match" + }, + "BASIC_INFO": { + "BASIC_INFO": "Basic info", + "NAME": "Name", + "NAME_IS_REQUIRED": "Name is required", + "NAME_MUST_BE_AT_LEAST_4_CHARACTERS": "Name must be at least 4 characters", + "PHOTO": "Photo", + "ENTER_A_VALID_LOGO_URL_OR_BROWSE_FROM_A_DEVICE": "Enter a valid logo URL or browse from a device", + "REMOVE": "Remove", + "PHOTO_OPTIONAL": "Photo (optional)", + "BARCODE_DATA": "Barcode Data", + "BARCODE_DATA_IS_REQUIRED": "Barcode Data is required" + }, + "CONTACT_INFO": { + "CONTACT_INFO": "Contact info", + "CONTACT_PHONE": "Contact Phone", + "INVALID_PHONE_NUMBER_FORMAT": "Invalid phone number format", + "ORDER_FORWARDING_EMAIL": "Order Forwarding Email", + "ORDER_FORWARDING_PHONE": "Order Forwarding Phone", + "ORDERS_EMAIL": "Orders Email", + "ORDERS_EMAIL_IS_REQUIRED": "Orders email is required", + "INVALID_EMAIL_FORMAT": "Invalid email format", + "ORDERS_PHONE": "Orders Phone", + "ORDERS_PHONE_IS_REQUIRED": "Orders phone is required" + }, + "LOCATION": { + "LOCATION": "Location" + }, + "PAYMENTS": { + "PAYMENTS": "Payments", + "ALLOW_ONLINE_PAYMENT": "Allow online payments?", + "ALLOW_CASH_PAYMENT": "Allow cash payments?", + "STRIPE": { + "PAY_BUTTON_TEXT": "Pay Button text", + "CURRENCY": "Currency", + "PAY_BUTTON_TEXT_IS_REQUIRED": "Pay Button text is required", + "CHOOSE_CURRENCY_CODE": "Choose currency code", + "CURRENCY_TEXT_IS_REQUIRED": "Currency text is required", + "COMPANY_BRAND_LOGO": "Company brand logo", + "COMPANY_BRAND_LOGO_IS_REQUIRED": "Company brand logo is required", + "INVALID_LOGO_URL": "Invalid logo url", + "INVALID_LOGO": "Invalid logo", + "PUBLISHABLE_KEY": "Publishable key", + "PUBLISHABLE_KEY_IS_REQUIRED": "Publishable key is required", + "ALLOW_REMEMBER_ME": "Allow remember me?", + "REMOVE": "Remove" + }, + "PAYPAL": { + "MODE": "Mode", + "CHOOSE_PAYPAL_MODE": "Choose PayPal mode", + "TYPE": "type", + "CURRENCY": "Currency", + "CHOOSE_CURRENCY_CODE": "Choose currency code", + "CURRENCY_TEXT_IS_REQUIRED": "Currency text is required", + "PUBLISHABLE_KEY": "Publishable key", + "PUBLISHABLE_KEY_IS_REQUIRED": "Publishable key is required", + "SECRET_KEY": "Secret key", + "SECRET_KEY_IS_REQUIRED": "Secret key is required", + "PAYMENT_DESCRIPTION": "Payment description", + "PAYMENT_DESCRIPTION_IS_REQUIRED": "Payment description is required" + } + }, + "MANUFACTURING": { + "MANUFACTURING": "Manufacturing", + "PRODUCTS_MANUFACTURING": "Products Manufacturing" + }, + "DELIVERY_AND_TAKEAWAY_SETTINGS": { + "DELIVERY_AND_TAKEAWAY_SETTINGS": "Delivery & Takeaway Settings", + "SELECT_FROM_SHARED_CARRIERS": "Select from shared carriers", + "ADD_YOUR_CARRIER": "Add your carrier", + "EDIT_CARRIER": "Edit carrier", + "CARRIER_REQUIRED": "Carrier required", + "PRODUCTS_DELIVERY_BY_DEFAULT": "Products Delivery (by default)", + "PRODUCTS_TAKEAWAY_BY_DEFAULT": "Products Takeaway (by default)", + "USE_SELECTED_SHARED_CARRIERS": "Use selected shared carriers", + "ADD_YOUR_CARRIERS": "Add your carriers" + }, + "ORDERS_SETTINGS": { + "ORDERS_SETTINGS": "Orders Settings", + "ORDER_BARCODE_QR_CODE_TYPES": "Order Barcode/QR code types" + }, + "PRODUCT_CATEGORIES": { + "PRODUCT_CATEGORIES": "Product categories", + "ADD_OWN_PRODUCT_CATEGORY": "Add own product category" + }, + "PRODUCTS": { + "PRODUCTS": "Products", + "SELECT_FROM_PRODUCTS_CATALOG": "Select from products catalog", + "CREATE_PRODUCT": "Create product", + "EDIT_PRODUCT": "Edit product", + "ADD_PRODUCT": "Add product", + "CREATE_NEW_PRODUCT": "Create new product" + } + } + }, + "WAREHOUSE_MANAGE": { + "TITLE": "Manage Warehouse", + "MANAGE_STORE": "Manage Store", + "SAVE": "SAVE", + "NAME": "Name", + "USERNAME": "Username", + "PASSWORD": "Password", + "COUNTRY": "Country", + "CITY": "City", + "POSTCODE": "Postcode", + "IS_ACTIVE": "Is Active", + "PRODUCTS_MANUFACTURING": "Products Manufacturing", + "CARRIER_REQUIRED": "Carrier required", + "RIGHT_NOW": "(right now)", + "CARRIERS": "Carriers", + "ADDRESS": "Address", + "CARRIERS_SPECIFIC": "Use only specific carriers", + "VALIDATION": { + "NAME": "Name is required", + "USERNAME": "Username is required", + "PASSWORD": "Password is required", + "COUNTRY": "Country is required", + "CITY": "City is required", + "STREET": "Street is required", + "HOUSE": "House number is required", + "POSTCODE": "Postcode is required" + }, + "WIZARD_TITLES": { + "DETAILS": "Details", + "ACCOUNT": "Account", + "CONTACT_INFO": "Contact Info", + "LOCATION": "Location", + "PAYMENT": "Payment", + "DELIVERY_ZONES": "Delivery zones" + } + }, + "WAREHOUSES_VIEW": { + "TITLE": "Stores", + "DELETE_WAREHOUSES": "Delete Selected", + "DELETE": "DELETE", + "CREATE": "CREATE", + "SHOW_ON_MAP": "Show on map", + "SMART_TABLE_COLUMNS": { + "IMAGE": "Image", + "NAME": "Name", + "EMAIL": "Email", + "PHONE": "Phone", + "CITY": "City", + "ADDRESS": "Address", + "ORDERS_QTY": "Orders QTY", + "ORDERS": "Orders" + }, + "INFO": { + "STORE_INFO": "Store Info", + "STORE_ID": "Store ID", + "STORE_NAME": "Store Name" + }, + "MERCHANTS": { + "TRACK_ALL_MERCHANTS": "Track all merchants", + "FILTER_MERCHANTS": "Filter merchants", + "FILTER_BY_NAME": "Filter by name", + "FILTER_BY_CITY": "Filter by city", + "FILTER_BY_COUNTRY": "Filter by country" + } + }, + "WAREHOUSE_VIEW": { + "ORDER": { + "CONFIRM": "Confirm", + "START_PROCESSING": "Start Processing", + "START_ALLOCATION": " Start Allocation", + "ALLOCATED": "Allocated", + "ALLOCATION_FAILS": "Allocation Fails", + "START_PACKAGING": "Start Packaging", + "PACKAGED": "Packaged", + "PACKAGING_FAILS": "Packaging Fails", + "GIVEN_TO_CARRIER": "Given to Carrier", + "GIVEN_TO_CUSTOMER": "Given to Customer", + "ORDER": "Order", + "CANT_PROCESSING_WITHOUT_PRODUCTS": "Can't processing the order without products." + }, + "MUTATION": { + "TITLE": "Register New Store", + "NAME": "Name", + "LOGO": "Logo", + "PHOTO": "Photo", + "IS_ACTIVE": "Is Active", + "ORDERS_SHORT_PROCESS": "Orders Short Process", + "PRODUCTS_MANUFACTURING": "Products Manufacturing", + "CARRIER_REQUIRED": "Carrier required", + "RIGHT_NOW": "Right now", + "ORDERS_PHONE": "Orders Phone", + "CONTACT_PHONE": "Contact Phone", + "ORDERS_EMAIL": "Orders Email", + "CONTACT_EMAIL": "Contact Email", + "FORWARD_ORDERS_WITH": "Forward Orders With", + "USERNAME": "Username", + "OLD_PASSWORD": "Old Password", + "NEW_PASSWORD": "New Password", + "CONFIRM_PASSWORD": "Confirm Password", + "PASSWORDS_DO_NOT_MATCH": "Passwords do not match", + "PASSWORD": "Password", + "COUNTRY": "Country", + "USA": "USA", + "ISRAEL": "Israel", + "BULGARIA": "Bulgaria", + "CITY": "City", + "ADDRESS": "Address", + "POSTCODE": "Postcode", + "COORDINATES": "Coordinates", + "AUTO_DETECT_COORDINATES": "Auto detect coordinates", + "CARRIERS": "Carriers", + "USE_ONLY_SPECIFIC_CARRIERS": "Use only specific carriers", + "SELECT_SHAPE_TO_ADD_ZONE": "Select shape to add a new zone", + "CIRCLE": "Circle", + "SHAPE": "Shape", + "DRAW_SHAPE_ON_MAP": "Draw a shape on the map", + "MINIMUM_AMOUNT": "Minimum amount", + "DELIVERY_FEE": "Delivery fee", + "CANCEL": "Cancel", + "ADD": "Add", + "EDIT": "Edit", + "ZONE_NAME": "Zone name", + "UNALLOWED_ORDER_CANCELATION": "Unallowed Order Cancelation", + "IN_STORE_MODE": "In-store mode", + "ORDER_CANCELATION_OPTIONS": { + "ORDERING": "After Ordering", + "START_PROCESSING": "After Start Processing", + "START_ALLOCATION": "After Start Allocation", + "ALLOCATED": "After Allocated", + "START_PACKAGING": "After Start Packaging", + "PACKAGED": "After Packaged", + "CARRIER_TAKE_WORK": "After Carrier Take Work", + "CARRIER_GOT_IT": "After Carrier Got It", + "CARRIER_START_DELIVERY": "After Carrier Start Delivery", + "DELIVERED": "After Delivered" + }, + "ERRORS": { + "NAME_IS_REQUIRED": "Warehouse name is required", + "NAME_ATLEAST_3_CHARS": "Name must be at least 3 characters long", + "NAME_MORE_THAN_255_CHARS": "Name cannot be more than 255 characters long", + "LOGO_IS_REQUIRED": "Warehouse logo is required", + "INVALID_URL": "Enter a valid logo URL or upload from a device", + "PHONE_CONTAINS_ONLY_DIGIT": "The phone number must only contain digits", + "INVALID_EMAIL": "Invalid Email", + "ORDERS_PHONE_IS_REQUIRED": "Orders Phone is required", + "CONTACT_PHONE_IS_REQUIRED": "Contact Phone is required", + "ORDERS_EMAIL_IS_REQUIRED": "Orders Email is required", + "CONTACT_EMAIL_IS_REQUIRED": "Contact Email is required", + "USERNAME_IS_REQUIRED": "Username is required", + "PASSWORD_IS_REQUIRED": "Password is required", + "COORDINATES_ARE_REQUIRED": "Coordinates are required", + "COUNTRY_IS_REQUIRED": "Country is required" + }, + "WIZARD_TITLES": { + "BASIC_INFO": "Basic Info", + "CONTACT_INFO": "Contact Info", + "LOCATION": "Location" + }, + "CONTACT_INFO_TAB": { + "ORDER_FORWARDING_EMAIL": "Order Forwarding Email", + "ORDER_FORWARDING_PHONE": "Order Forwarding Phone" + } + }, + "NEW_PRODUCT": { + "SELECT_FROM_PRODUCTS_CATALOG": "Select from Products Catalog", + "CREATE_NEW_PRODUCT": "Create new Product", + "HOW_TO_ADD": "How to add", + "ADD": "Add", + "SAVE": "Save", + "ADD_PRODUCTS_TO_STORE": "Add Products to Store", + "NOTHING_FOUND": "Nothing found..." + }, + "SELECT_PRODUCTS": { + "TITLE": "Title", + "DESCRIPTION": "Description", + "DETAILS": "Details", + "IMAGES": "Images", + "CATEGORY": "Category" + }, + "SAVE": { + "PRODUCT_NAME": "Product Name", + "PRICE": "Price", + "COUNT": "Count", + "DELIVERY": "Delivery", + "TAKEAWAY": "Takeaway" + }, + "PLACEHOLDER": { + "EXAMPLE": "Example: Domino's Pizza", + "IMAGE_URL": "Image url", + "HERE_GOES_A_SHORT_DESCRIPTION": "Here goes a short description", + "HERE_GOES_A_DETAILS_ABOUT_PRODUCT_(OPTION)": "Here goes a details about product (option)", + "REMOVE_IMAGE": "Remove image" + }, + "CREATE_ORDER_MODAL": { + "TITLE": { + "CREATE_ORDER": "Create Order" + }, + "WIZARD_FORM": { + "BUTTON_NEXT": "Next", + "BUTTON_PREV": "Back", + "BUTTON_DONE": "Finish Order", + "STEP1": { + "TITLE": "Choose Option", + "SELECT_FROM_EXISTING": "Select From Existing", + "ADD_NEW_CUSTOMER": "Add New Customer" + }, + "STEP2": { + "TITLE": { + "SELECT_CUSTOMER": "Select Customer", + "SELECT_ADD": "Select/Add", + "ADD_NEW": "Add New" + }, + "SMART_TABLE": { + "TITLES": { + "FULL_NAME": "Full Name", + "EMAIL": "Email", + "PHONE": "Phone", + "ADDRESS": "Address" + } + } + }, + "STEP3": { + "TITLE": "Create Order" + } + } + }, + "PRODUCTS_TAB": { + "PRODUCTS": "Products", + "ADD_PRODUCTS": "Add Products", + "DELETE": "Delete", + "IMAGE": "Image", + "TITLE": "Title", + "DESCRIPTION": "Description", + "DETAILS": "Details", + "CATEGORY": "Category", + "PRICE": "Price", + "QUANTITY": "Quantity", + "CLICK_ON_PRODUCT_IMAGE_TO_INCREASE_AVAILABLE_QUANTITY": "Click on Product Image to increase available quantity", + "AVAILABILITY": "Availability", + "TYPE": "Type", + "DELIVERY": "Delivery", + "TAKEAWAY": "Takeaway" + }, + "NEW_PRODUCT_TYPE": "New Type Product", + "ADD_PRODUCTS": "Add Products", + "CREATE_ORDER": "Create Order", + "STATUS": "Status", + "ORDERS": "Orders", + "WAREHOUSE": "Warehouse", + "PRODUCT": "Product", + "PRODUCTS": "Products", + "ORDER_NUMBER": "Order Number", + "CANCELLED": "Cancelled", + "WAREHOUSE_STATUS": "Warehouse Status", + "CARRIER_STATUS": "Carrier Status", + "PAID": "Paid", + "CARRIER": "Carrier", + "CREATED": "Created", + "ELAPSED": "Elapsed", + "CONTACT_DETAILS": "Contact Details", + "EMAIL": "Email", + "PHONE": "Phone", + "ORDERS_FORWARDING_DETAILS": "Orders Forwarding Details", + "ORDERS_FORWARDING_WITH": "Orders Forwarding With", + "MANAGE_STORE": "Manage Store", + "TOP_PRODUCTS": "Top Products", + "PRODUCTS_MANUFACTURING": "Products Manufacturing", + "CARRIER_REQUIRED": "Carrier required", + "MANAGE_WAREHOUSE": "Manage warehouse", + "MANAGE_STORE_PRODUCTS_&_ORDERS": "Manage store products & orders" + }, + "SIMULATION_VIEW": { + "SIMULATION": "Simulation", + "PURCHASE_PRODUCTS": "Purchase products", + "CREATE_INVITE_REQUEST": "Create Invite Request", + "SEND": "Send", + "CREATE_USER": "Create User", + "ORDER_CONFIRM": "Order Confirm", + "ORDER_CANCEL": "Order Cancel", + "PRODUCTS": "Products", + "STORE": "Store", + "ORDER": "Order", + "INVITE_REQUEST": "Invite-Request", + "INVITE_USER": "Invite User", + "TAB_BUTTONS": { + "PRODUCTS": "Products", + "ORDER_HISTORY": "Order history" + }, + "USER_MUTATION": { + "TITLE": "Create Customer", + "NAME": "Name", + "EMAIL": "Email", + "COUNTRY": "Country", + "CITY": "City", + "USA": "USA", + "ISRAEL": "Israel", + "BULGARIA": "Bulgaria", + "ADDRESS": "Address", + "POSTCODE": "Postcode", + "COORDINATES": "Coordinates", + "AUTO_DETECT_COORDINATES": "Auto detect coordinates", + "ERRORS": { + "FIRST_NAME_IS_REQUIRED": "First name is required", + "LAST_NAME_IS_REQUIRED": "Last name is required", + "INVALID_EMAIL": "Invalid Email", + "EMAIL_IS_REQUIRED": "Email is required", + "COORDINATES_ARE_REQUIRED": "Coordinates are required" + }, + "WIZARD_TITLES": { + "ADDITIONAL_INFO": "Additional Info", + "LOCATION": "Location" + } + }, + "SMART_TABLE": { + "TITLE": "Title", + "ID": "Id", + "IMAGE": "Image" + }, + "ORDER_INFO": { + "STATUSES": [ + { + "TITLE": "We're preparing the order!", + "DETAILS": "You will get it in %t minutes.", + "NOT_PAID_NOTE": "Prepare your wallet (%s in cash)." + }, + { + "TITLE": "Carrier on the way!", + "DETAILS": "You will get the order in %t min.", + "NOT_PAID_NOTE": "Prepare your wallet (%s in cash)." + }, + { + "TITLE": "Check your door!", + "DETAILS": "You will get the order in seconds.", + "NOT_PAID_NOTE": "Prepare your wallet (%s in cash)." + }, + { + "TITLE": "Order Completed!", + "DETAILS": "Thanks for using Ever", + "NOT_PAID_NOTE": "" + } + ], + "DELIVERY_STATUS": { + "WE": "We", + "CARRIER": "Carrier", + "YOU": "You" + }, + "DELIVERY_WRONG": { + "DELIVERY_WRONG": "The Delivery Went Wrong!", + "PROCESSING_WRONG": "Processing Went Wrong!", + "TRY_AGAIN": "Please try again.", + "CALL_FOR_DETAILS": "Call for details" + } + }, + "INSTRUCTIONS_STEPS": { + "INSTRUTIONS": "Instructions", + "CREATE_USER_STEP": { + "CREATE_USER": "Create User", + "STEP_1": "Step 1.", + "ORDER": "Order", + "TO_CONTINUE_IS_REQUIRED_TO_REGISTER_IN_THE_SYSTEM": "To continue is required to register in the system", + "CLICK_ON_BUTTON_CREATE_USER": "Click on button 'Create user'", + "FILL_THE_FORM_FOR_ADDITIONAL_INFO": "Fill the form for additional info (optional)", + "FILL_THE_FORM_FOR_LOCATION_AND_PRESS_DONE_BUTTON": "Fill the form for location and press DONE button" + }, + "ORDER_STEP": { + "ORDER": "Order", + "STEP_2": "Step 2.", + "CREATE_ORDER": "Create order", + "CHOICE_SOME_PRODUCTS_FROM_THE_TABLE": "Choice some products from the table (you can see more details about when press on his name or image)", + "SELECT_PRODUCT": "To be selected one product have to press on his row.", + "SELECT_BUTTON_ORDER_TO_CREATE_ORDER": "Press button 'Order' to create order with the selected product.", + "REVIEW_ORDER_HISTORY": "Review order history:", + "ON_PRESS_ORDER_HISTORY_TAB": "On press 'Order history' shows all your orders..", + "HERE_YOU_CAN_SEE_DETAILS_ABOUT_EACH_ORDER": "Here you can see details about each order", + "PRESS_ON_CARRIER_ORDER_OR_PRODUCT_NAME": "Press on carrier, order or product name for more information." + }, + "CONFIRM_OR_CANCEL_STEP": { + "STEP_3": "Step 3", + "CONFIRM_CANCEL_ORDER": "Confirm/Cancel order", + "REAL_TIME": "Real time", + "TRACK_STATUS_ON_YOUR_ORDER": "Track status on your order.", + "ELAPSED_TIME_FROM_CREATE_TO_DELIVERED": "Elapsed time from created to delivered.", + "SHOWS_MERCHANT_LOCATION": "Shows merchant location.", + "SHOWS_CARRIER_LOCATION": "Show carrier location.", + "POSSIBILITIES": "Possibilities:", + "SLIDER_REVIEW_OF_THE_ALL_PRODUCTS": "Slider review of the all products.", + "CAN_CANCEL_IT_WITH_ORDER_CANCEL_BUTTON": "Untill the order is not delivered you user cancel it with 'Order Cancel' button.", + "AFTER_THE_ORDER_IS_DELIVER_USER_CAN_CLICK_BUTTON_ORDER_CONFIRM_TO_CONTINUE": "After the order is delivered the user can click button 'Order Confirm' to continue.." + }, + "INVITE_STEP": { + "TO_CONTINUE_ITs_REQUIRED_TO_BE_INVITED_IN_THE_SYSTEM": "To continue it's required to be invited in the system:", + "SEND_INVITE_REQUEST": "Send 'Invite Request' to the system from the form which will be opened after pressing Invite Request button.", + "ALL_INVITE_REQUESTS_ARE_REVIEWED": "All invite requests are reviewed by admin and they can be invited if the system is available near your location (for test you can do immediately form Invite User button).", + "AFTER_YOU_GET_INVITED_BEFORE": "After you get invite you can easy login the system, just have to enter your invite code, which will be provided from the system (you will see here", + "AFTER_YOU_GET_INVITED_AFTER": "code after press Invite button, and can enter it when press Create User button)." + } + } + }, + "CUSTOMERS_VIEW": { + "TITLE": "Customers", + "DELETE_CUSTOMERS": "Delete", + "CREATE_CUSTOMER": "Create", + "DELETE": "DELETE", + "CREATE": "CREATE", + "BAN": "BAN", + "UNBAN": "UNBAN", + "MANAGE_CUSTOMER": "Manage customer", + "CUSTOMER": "Customer", + "CUSTOMERS_DEVICES": "Devices", + "INVITE": "Invite", + "NOT_INVITED_ONLY": "Not invited only", + "ORDER": "Order", + "ORDERS_STATISTICS": "Orders statistics", + "NUMBER_OF_ORDERS": "Number of Orders", + "CANCELED_ORDERS": "Canceled Orders", + "COMPLETED_ORDERS_TOTAL": "Completed Orders Total", + "Order": "Order", + "CANCEL_ORDER": "Cancel Order", + "CATEGORY": "Category", + "ORDERS_HISTORY": "Orders History", + "AVAILABLE_PRODUCTS": "Available products", + "NEARBY_STORES": "Nearby Stores", + "INVITES_REQUESTS_MANAGEMENT": "Invites requests management", + "INVITES_MANAGEMENT": "Invites management", + "DESCRIPTION": "Description", + "DETAILS": "Details", + "MAKE_A_CUSTOM_ORDER": "Make a custom order", + "PRODUCT_COUNT": "Product Count", + "QUANTITY_CAN'T_BE_EMPTY": "Quantity can't be empty", + "QUANTITY_CAN'T_BE_0": "Quantity can't be 0", + "NOT_ENOUGH_PRODUCTS_AVAILABLE": "Not enough products available", + "ORDER_INFO": "Order Info", + "ORDER_ID": "Order Id", + "STORE_ID": "Store Id", + "CARRIER_ID": "Carrier Id", + "NO_CARRIER": "No Carrier", + "SMART_TABLE_COLUMNS": { + "IMAGE": "Image", + "NAME": "Name", + "EMAIL": "Email", + "PHONE": "Phone", + "ADDRESS": "Address", + "ORDERS_QTY": "Orders QTY", + "COUNTRY": "Country", + "CITY": "City", + "STREET_ADDRESS": "Street Address", + "HOUSE": "House", + "APARTMENT": "Apartment", + "INVITE_CODE": "Invite Code", + "INVITED_DATE": "Invited date", + "ORDER_NUMBER": "Order Number", + "WAREHOUSE": "Warehouse", + "CARRIER": "Carrier", + "PRODUCT_LIST": "Product list", + "STATS": "Stats", + "DELIVERY_TIME": "Delivery Time", + "CREATED": "Created", + "ACTIONS": "Actions", + "PAID": "Paid", + "COMPLETED": "Completed", + "CANCELLED": "Cancelled", + "NOT_DELIVERED": "Not delivered", + "PRODUCT": "Product", + "PRICE": "Price", + "STORE": "Store", + "AVAILABLE_COUNT": "Available Count", + "ORDER": "Order", + "STATUS": "Status" + }, + "EDIT": { + "EDIT_CUSTOMER": "Edit Customer", + "BASIC_INFO": "Basic Info", + "SAVE": "Save" + }, + "DEVICE": { + "ALL_DEVICE": "All Device", + "DEVICE_ID": "Device ID", + "ID": "id", + "UPDATE": "Update", + "LANGUAGE": "language", + "TYPE": "type", + "TYPEU": "Type", + "UUID": "uuid", + "DEVICE_UUID": "Device UUID", + "UPDATE_DEVICE": "Update Device", + "CUSTOMERS_DEVICES": "Customers Devices", + "DELETE": "DELETE", + "CREATE": "CREATE" + }, + "WAREHOUSE": { + "WAREHOUSE_INFO": "Warehouse Info", + "WAREHOUSE_ID": "Warehouse Id", + "WAREHOUSE_NAME": "Warehouse Name" + }, + "CUSTOMER_VIEW": { + "MANAGE_CUSTOMER": "Manage customer", + "EDIT": "EDIT", + "CUSTOMER": "Customer" + }, + "INVITES_VIEW": { + "DELETE": "Delete", + "INVITE": "Invite" + } + }, + "CARRIERS_VIEW": { + "TITLE": "Carriers", + "DELETE_CARRIERS": "Delete Selected", + "CREATE_CARRIER": "Create Carrier", + "DELETE": "DELETE", + "CREATE_BUTTON": "CREATE", + "ACTIVE_AND_AVAILABLE_ORDERS": "Active and Available Orders", + "ORDERS_HISTORY": "Orders History", + "TRACK": "Track", + "SMART_TABLE_COLUMNS": { + "IMAGE": "Image", + "NAME": "Name", + "PHONE": "Phone", + "STATUS": "Status", + "ADDRESS": "Address", + "DELIVERIES": "Deliveries" + }, + "CARRIER_PAGE": { + "WAREHOUSE": "Warehouse", + "CUSTOMER": "Customer", + "SAVE": "Save", + "EDIT": "Edit", + "CARRIER_INFO": "Carrier Info", + "CARRIER_ID": "Carrier Id", + "REGISTER_NEW_CARRIER": "Register New Carrier", + "WAREHOUSE_STATUS": "Warehouse Status", + "CARRIER_STATUS": "Carrier Status", + "CREATED": "Created", + "ARRIVED_TO_CUSTOMER": "Arrived to Customer", + "FAILED": "Failed", + "DELIVERED": "Delivered", + "CLIENT_REFUSE_ORDER": "Client Refuse Order", + "AVAIBLE_ORDER_TO_PICK_UP": "Available Orders to pick up (each carrier can pick up multiple orders)", + "ACTIVE": "Active", + "CARRIER_CAN_BE_SHARED": "Carrier can be shared?", + "NOT_ACTIVE": "Not Active", + "WORKING": "Working", + "NOT_WORKING": "Not Working", + "SELECT_CARRIER": "Select carrier", + "CARRIER_ORDERS_STATUS": "Carrier orders status", + "Start": "Start", + "PICKED_UP_ORDER": "Picked Up Order", + "CANCEL": "Cancel", + "Arrived To Client": "Arrived To Client", + "No Carrier": "No Carrier", + "Order Selected For Delivery": "Order Selected For Delivery", + "Order Picked Up": "Order Picked Up", + "Order In Delivery": "Order In Delivery", + "Delivered": "Delivered", + "Delivery Issues": "Delivery Issues", + "Client Refuse to Take Order": "Client Refuse to Take Order", + "BAD_STATUS": "BAD_STATUS", + "Created": "Created", + "Confirmed": "Confirmed", + "Processing": "Processing", + "Allocation Started": "Allocation Started", + "Allocation Finished": "Allocation Finished", + "Packaging Started": "Packaging Started", + "Packaged": "Packaged", + "Given to Carrier": "Given to Carrier", + "Allocation Failed": "Allocation Failed", + "Packaging Failed": "Packaging Failed", + "LOCATION": "Location", + "TIME": "Time", + "NAME": "Name" + }, + "EDIT": { + "EDIT_CARRIER": "Edit Carrier", + "BASIC_INFO": "Basic Info", + "LOCATION": "Location", + "PHOTO_URL": "Photo Url", + "CONTACT_PHONE": "Contact Phone", + "FIRST_NAME": "First Name", + "LAST_NAME": "Last Name" + }, + "CREATE": { + "BASIC_INFO": "Basic Info", + "LOCATION": "Location" + }, + "TRACK_PAGE": { + "TRACK_ALL_WORKING_CARRIERS": "Track all working carriers", + "FILTER_CARRIERS": "Filter Carriers", + "PHONE": "Phone", + "EMAIL": "Email", + "ADDRESS": "Address", + "DELIVERY_COUNT": "Delivery Count" + } + }, + "PRODUCT_TYPE_VIEW": { + "TITLE": "New Product", + "WIZARD_FORM": { + "VALIDATION_MESSAGES": { + "TITLE": "Title is required", + "THE_LENGHT_OF_THE_TITLE": "The length of title should be max 255 characters long!", + "IMAGE": "Picture is required", + "DESCRIPTION": "Description is required", + "LANGUAGE": "Language is required", + "THE_LENGHT_OF_THE_DESCRIPTION": "The length of description should be max 255 characters long!", + "PRICE": "Price is required", + "COUNT": "Product count is required" + } + } + }, + "SHARED": { + "FORMS": { + "ERRORS": { + "FIRST_NAME_REQUIRED": "First name is required", + "LAST_NAME_REQUIRED": "Last name is required", + "USERNAME_REQUIRED": "Username is required", + "PASSWORD_REQUIRED": "Password is required", + "PHONE_REQUIRED": "Phone is required", + "LOGO_URL_REQUIRED": "Enter valid image URL or browse for a file", + "IS_ACTIVE": "Is active field is required", + "COUNTRY_REQUIRED": "Country is required", + "CITY_REQUIRED": "City is required", + "STREET_ADDRESS_REQUIRED": "Street address is required", + "HOUSE_REQUIRED": "House number is required", + "COORDINATES_REQUIRED": "Coordinates are required", + "MUST_CONTAIN_ONLY_LETTERS": "Must contain only letters", + "PHONE_MUST_CONTAINS_ONLY(specail_signs)AND_DIGIT_CHARACTER": "Phone number can start with '+' or '(some numbers)' and must contains only only: '-, ., (space), #'' and digit characters" + } + }, + "USER": { + "FORMS": { + "BASIC_INFO": { + "TITLE": "Basic Info", + "FIRST_NAME": "First Name", + "FIRST_NAME_OPTIONAL": "First Name (optional)", + "LAST_NAME_OPTIONAL": "Last Name (optional)", + "PHOTO_URL": "Photo Url", + "PICTURE_URL": "Picture url (optional)", + "EMAIL": "Email", + "EMAIL_OPTIONAL": "Email (optional)", + "ERRORS": { + "INVALID_EMAIL": "Invalid Email", + "EMAIL_IS_ALREADY_IN_USE": "Email is already in use" + } + } + } + }, + "WAREHOUSE": { + "ORDER_MODAL": { + "MAKE_ORDER": "Make Order", + "ONLY_AVAILABLE": "Show only available products", + "ORDER": "Order", + "SMART_TABLE": { + "TITLES": { + "IMG": "Img", + "PRODUCT": "Product", + "PRICE": "Price", + "AVAILABLE": "Available", + "AMOUNT": "Amount", + "COMMENT": "Comment" + } + } + } + } + }, + "MENU_VIEW": { + "DASHBOARD": "Dashboard", + "STORES": "Stores", + "PRODUCTS": { + "PRODUCTS": "Products", + "MANAGEMENT": "Management", + "CATEGORIES": "Categories" + }, + "CUSTOMERS": { + "CUSTOMERS": "Customers", + "MANAGEMENT": "Management", + "INVITES": "Invites" + }, + "CARRIERS": "Carriers", + "SIMULATION": "Simulation", + "SETUP": "Setup" + }, + "CATEGORY_VIEW": { + "TITLE": "Title", + "IMAGE": "Image", + "CREATE_BUTTON": "CREATE", + "DELETE": "DELETE", + "EDIT": { + "EDIT_CATEGORY": "Edit Category", + "CATEGORY_NAME": "Category Name", + "ENTER_THE_CATEGORY_NAME": "Enter the category name", + "DONE": "Done" + }, + "CREATE": { + "CREATE_CATEGORY": "Create Category", + "CATEGORY_NAME": "Category Name", + "ENTER_THE_CATEGORY_NAME": "Enter the category name", + "PHOTO": "Photo", + "BROWSE": "Browse", + "INVALID_URL": "Invalid Url", + "REMOVE_IMAGE": "Remove image", + "PHOTO_OPTIONAL": "Photo (optional)", + "DONE": "Done" + } + }, + "PLACEHOLDER": { + "EXAMPLE": "Example: Domino's Pizza", + "IMAGE_URL": "Image url", + "HERE_GOES_A_SHORT_DESCRIPTION": "Here goes a short description", + "HERE_GOES_A_DETAILS_ABOUT_PRODUCT_(OPTION)": "Here goes a details about product (option)", + "REMOVE_IMAGE": "Remove image", + "PASSWORD": "Password", + "LATITUDE": "Latitude", + "LONGITUDE": "Longitude", + "APARTMENT": "Apartment", + "HOUSE": "House", + "STREET": "Street", + "ZIP": "ZIP", + "CITY": "City", + "FIND_ADDRESS": "Find Address" + }, + "STATUS_TEXT": { + "Created": "Created", + "Confirmed": "Confirmed", + "Processing": "Processing", + "Allocation Started": "Allocation Started", + "Allocation Finished": "Allocation Finished", + "Packaging Started": "Packaging Started", + "Packaged": "Packaged", + "Given to Carrier": "Given to Carrier", + "Allocation Failed": "Allocation Failed", + "Packaging Failed": "Packaging Failed", + "No Carrier": "No Carrier", + "Order Selected For Delivery": "Order Selected For Delivery", + "Order Picked Up": "Order Picked Up", + "Order In Delivery": "Order In Delivery", + "Arrived To Client": "Arrived To Client", + "Delivered": "Delivered", + "Delivery Issues": "Delivery Issues", + "Client Refuse to Take Order": "Client Refuse to Take Order", + "Given to Customer": "Given to Customer", + "BAD_STATUS": "BAD_STATUS" + }, + "ELAPSED_TIME": { + "TITLE": "Elapsed time" + }, + "CONFIRM_MODAL": { + "ARE_YOU_SURE": "Are you sure?", + "ARE_YOU_SURE_YOU_WANT_TO_INCREASE": "Are you sure you want to increase the qty of products?", + "ARE_YOU_SURE_YOU_WANT_TO_DECREASE": "Are you sure you want to decrease the qty of products?", + "YES": "Yes", + "NO": "No" + }, + "NO_SERVER_VIEW": { + "NO_SERVER": "Server connection is lost" + }, + "BUTTON_NEXT": "Next", + "BUTTON_PREV": "Back", + "BUTTON_DONE": "Done", + "TERRAIN": "Terrain", + "SATELLITE": "Satellite", + "LOCATION": "Location", + "ROAD_MAP": "Road Map", + "Manage warehouse": "Manage store products & orders", + "Warehouse": "Warehouse", + "Create Warehouse": "Create Warehouse", + "SIMULATION": "Simulation", + "Purchase products": "Purchase products", + "Manage": "Manage", + "Orders": "Orders", + "Confirmed": "Confirmed", + "In Delivery": "In Delivery", + "Not Confirmed": "Not Confirmed", + "Not paid": "Not paid", + "Cancelled": "Cancelled", + "All": "All", + "CANCEL": "Cancel", + "Default Settings": "Default Settings", + "Products Manufacturing": "Products Manufacturing", + "Carrier required before sale": "Carrier required before sale", + "New Product Type": "New Product Type", + "Products": "Products", + "Product": "Product", + "Title": "Title", + "Picture Url": "Picture Url", + "Description": "Description", + "Details": "Details", + "Price": "Price", + "CATEGORY": "Category", + "LANGUAGE": "Language", + "BROWSE": "Browse", + "ENGLISH": "English", + "HEBREW": "Hebrew", + "RUSSIAN": "Russian", + "BULGARIAN": "Bulgarian", + "SPANISH": "испански", + "FRENCH": "Френски", + "SELECT": "Select", + "Name": "Name", + "Id": "Id", + "Warehouse name is required": "Warehouse name is required", + "Name must be at least 1 characters long": "Name must be at least 1 characters long", + "Title cannot be more than 255 characters long": "Title cannot be more than 255 characters long", + "Logo": "Logo", + "Warehouse logo is required": "Warehouse logo is required", + "is Active": "is Active", + "right now": "right now", + "Unselected": "Unselected", + "Phone": "Phone", + "Email": "Email", + "Username": "Username", + "Username is required": "Username is required", + "Password": "Password", + "Country": "Country", + "USA": "USA", + "Israel": "Israel", + "Bulgaria": "Bulgaria", + "City": "City", + "Address": "Address", + "Postcode": "Postcode", + "Coordinates": "Coordinates", + "Auto detect coordinates": "Auto detect coordinates", + "Carriers": "Carriers", + "Carrier": "Carrier", + "Use only specific carriers": "Use only specific carriers", + "Manage carrier and deliveries": "Manage carrier and deliveries", + "Register New Carrier": "Register New Carrier", + "Create User": "Create User", + "OPTIONAL": "optional" +} diff --git a/packages/admin-web-angular/src/assets/i18n/en-US.json b/packages/admin-web-angular/src/assets/i18n/en-US.json new file mode 100644 index 0000000..2997a24 --- /dev/null +++ b/packages/admin-web-angular/src/assets/i18n/en-US.json @@ -0,0 +1,1170 @@ +{ + "COMMON": { + "SAVE": "Save", + "CANCEL": "Cancel", + "USA": "USA", + "ISRAEL": "Israel", + "BULGARIA": "Bulgaria" + }, + "ORDER_VIEW": { + "ORDER_HEADER_INFO": { + "ORDER": "Order", + "MANAGE_ORDER": "Manage Order", + "TOTAL": "Total" + }, + "ORDER_PRODUCT_INFO": { + "ORDER_PRODUCTS": "Order Products", + "ADD_PRODUCTS": "Add products", + "REMOVE_PRODUCTS": "Remove Products", + "CANCEL_ORDER": "Cancel Order", + "THE_ORDER_IS_CANCELED": "The order is canceled", + "THE_ORDER_IS_GIVEN_TO_CARRIER": "The order is given to carrier.", + "THE_ORDER_IS_DELIVERED": "The order is delivered.", + "ADD_PRODUCTS_MODAL": "Add Products", + "ADD": "Add", + "SUCCESS_TOAST": "Products were added to the order", + "ERROR_TOAST": "Error, something went wrong", + "SMART_TABLE": { + "NAME": "Name", + "QTY": "QTY", + "PRICE": "Price", + "IMAGE": "Image", + "COMMENT": "Comment" + } + }, + "ORDER_SIDEBAR": { + "CONTACT_DETAILS": "Contact Details", + "WAREHOUSE": "Warehouse", + "CUSTOMER": "Customer", + "CARRIER": "Carrier", + "QTY": "qty" + }, + "LOCATION_INFO": { + "MAP": "Map", + "DELIVERY_DISTANCE": "Delivery Distance:" + } + }, + "DASHBOARD_VIEW": { + "TOTAL_CUSTOMER": "Total Customers", + "TOTAL_QUANTITY_OF_EXISTING_CUSTOMERS": "Total quantity of existing customers", + "TOTAL_COMPLETED_ORDERS": "Total completed orders", + "TOTAL_QUANTITY_OF_COMPLETED_ORDERS": "Total quantity of completed orders", + "TOTAL_REVENUE": "Total Revenue", + "TOTAL_SUM_SUM_OF_ALL_COMPLETED_ORDERS": "Total sum of all completed orders", + "TODAYs_CUSTOMERS": "Today's Customers", + "TODAYs_QUANTITY_OF_REGISTERED_CUSTOMERS": "Today's quantity of registered customers", + "TODAYs_COMPLETED_ORDERS": "Today's completed orders", + "TODAYs_QUANTITY_OF_NEW_COMPLETED_ORDERS": "Today's quantity of new completed orders", + "TODAYs_REVENUE": "Today's Revenue", + "TODAYs_SUM_OF_COMPLETED_ORDERS": "Today's sum of completed orders", + "TILL_AVERAGE": "till average", + "BETTER_THAN_AVERAGE": "better than average", + "SELECT_COMPONENT": { + "STORES": "Stores", + "CONTACT_DETAILS": "Contact Details", + "PHONE": "Phone", + "EMAIL": "Email", + "ORDERS_FORWARDING_WITH": "Orders Forwarding With", + "SELECT_STORE": "Select store" + }, + "CHARTS": { + "ORDERS": "Orders", + "PROFIT": "Profit", + "TOTAL_ORDERS": "Total orders", + "TOTAL_COMPLETED_ORDERS": "Total completed orders", + "TOTAL_CANCELLED_ORDERS": "Total canceled orders", + "TOTAL_REVENUE_ALL_ORDERS": "Total revenue from all orders", + "TOTAL_REVENUE_COMPLETED_ORDERS": "Total revenue from completed orders", + "TOTAL_LOST_REVENUE_CANCELLED_ORDERS": "Total lost revenue from canceled orders", + "PAYMENT": "Payment", + "CANCELED": "Canceled", + "ALL_ORDERS": "All Orders", + "TODAY": "Today", + "LAST_WEEK": "Last Week", + "LAST_MONTH": "Last Month", + "CURRENT_YEAR": "Current Year", + "YEARS": "By Year", + "CUSTOM_PERIOD": "Custom Period", + "SELECT_PERIOD": "Select Period", + "FROM": "From", + "TO": "To", + "SELECT": "Select", + "LABELS": { + "WEEK": "Week", + "WEEKDAYS": { + "MON": "Mon", + "TUE": "Tue", + "WED": "Wed", + "THU": "Thu", + "FRI": "Fri", + "SAT": "Sat", + "SUN": "Sun" + }, + "MONTHS": { + "JAN": "Jan", + "FEB": "Feb", + "MAR": "Mar", + "APR": "Apr", + "MAY": "May", + "JUN": "Jun", + "JUL": "Jul", + "AUG": "Aug", + "SEP": "Sep", + "OCT": "Oct", + "NOV": "Nov", + "DEC": "Dec" + } + } + } + }, + "HEADER_VIEW": { + "ADMIN": "Admin", + "EVER": "Ever", + "PROFILE": "Profile", + "LOG_OUT": "Log out" + }, + "FOOTER_VIEW": { + "COPY_RIGHT": "Copyright © 2016-present", + "ALL_RIGHTS_RESERVED": "All rights reserved" + }, + "PROFILE_VIEW": { + "PROFILE_PAGE": "Profile page", + "BASIC_INFO": "Basic Info", + "ACCOUNT": "Account", + "USERNAME": "Username", + "ERROR": "Error", + "EMAIL": "Email", + "FIRST_NAME": "First Name", + "FIRST_NAME_OPTIONAL": "First Name (optional)", + "LAST_NAME": "Last Name (optional)", + "PICTURE_URL": "Picture url (optional)", + "BROWSE": "Browse", + "REMOVE": "Remove", + "SAVE": "Save", + "OLD_PASSWORD": "Old password", + "NEW_PASSWORD": "New password", + "REPEAT_NEW_PASSWORD": "Repeat new password", + "INVALID_EMAIL_ADDRESS": "Invalid email address", + "INVALID_URL": "Invalid URL", + "NAME_MUST_CONTAIN_ONLY_LETTERS": "Name must contain only letters", + "PASSWORDS_DO_NOT_MATCH": "Passwords do not match", + "SUCCESSFULLY_CHANGE_PASSWORD": "Successfully change password" + }, + "PRODUCTS_VIEW": { + "DELETE": "DELETE", + "CREATE": "CREATE", + "EDIT_VIEW": { + "EDIT_PRODUCT": "Edit Product", + "BASIC_INFO": "Basic Info", + "SAVE": "Save" + } + }, + "FAKE_DATA": { + "FAKE_DATA_GENERATOR": "Fake Data Generator", + "GENERATE_ALL": "Generate All", + "CREATE_100_USERS": "Create 100 customers", + "CREATE_100_CARRIERS": "Create 100 carriers", + "CREATE_100_WAREHOUSES": "Create 100 warehouses", + "SETUP": "SETUP", + "GENERATE_INITIAL_DATA": "Generate Initial Data", + "CREATE_INVITE": "CREATE INVITE", + "HARDCODED_DATA": "Hardcoded data", + "CLEAR_ALL": "CLEAR ALL", + "GENERATE_HARDCODED_ONLY": "Generate Hardcoded Only", + "INCLUDED_HARDCODED_DATA": "Include hardcoded data", + "CREATE_1st_INVITE": "Create 1st invite", + "CREATE_2st_INVITE": "Create 2nd invite", + "CREATE_3st_INVITE": "Create 3rd invite", + "CREATE_4st_INVITE": "Create 4rd invite", + "CREATE_CUSTOMER": "Create Customer", + "CREATE_USER": "CREATE USER", + "CREATE_1st_USER": "Create 1st user (using 1st invite)", + "CREATE_CARRIER": "CREATE CARRIER", + "CREATE_1st_CARRIER": "Create 1st carrier", + "CREATE_2nd_CARRIER": "Create 2nd carrier", + "CREATE_3rd_CARRIER": "Create 3rd carrier", + "CREATE_PRODUCT": "CREATE PRODUCT", + "CREATE_PEPERONI&MUSHROOM_PIZZA_PRODUCT": "Create Peperoni & Mushroom pizza product", + "CREATE_SUSHI&CAVIAR_PRODUCT": "Create Sushi & Caviar product", + "CREATE_SUSHI_MIX_PRODUCT": "Create Sushi mix product", + "CREATE_PASTA_PRODUCT": "Create Pasta product", + "CREATE_SUSHI_BOX_PRODUCT": "Create Sushi box product", + "CREATE_PEPERONI&TOMATO_PIZZA_PRODUCT": "Create Peperoni & Tomato pizza product", + "CREATE_WAREHOUSE": "CREATE WAREHOUSE", + "CREATE_1st_WAREHOUSE": "Create 1st warehouse", + "CREATE_2nd_WAREHOUSE": "Create 2nd warehouse", + "CREATE_3rd_WAREHOUSE": "Create 3rd warehouse", + "CREATE_WAREHOUSE_PRODUCT": "CREATE WAREHOUSE PRODUCT", + "CREATE_1st_WAREHOUSE_PRODUCTS(using_product_number_1_2_3_4_5_and_6)": "Create 1st warehouse products (using product number 1, 2, 3, 4, 5 and 6)", + "CREATE_3rd_WAREHOUSE_PRODUCTS(using_1st_product)": "Create 3rd warehouse products (using 1st product)", + "CREATE_2nd_WAREHOUSE_PRODUCTS(using_product_number_1_2_and_3": "Create 2nd warehouse products (using product number 1, 2, and 3", + "UPDATE_WAREHOUSE_GEO_LOCATION": "UPDATE WAREHOUSE GEO LOCATION", + "UPDATE_1ST_WAREHOUSE_GEO_LOCATION": "Update 1st warehouse geo location", + "CREATE_1RD_ORDER(using_1st_warehouse_1st_user_and_1st_product)": "Create 1rd order (using 1st warehouse, 1st user and 1st product)", + "CREATE_2ND_ORDER(using_1st_warehouse_1st_user_and_2nd_product)": "Create 2nd order (using 1st warehouse, 1st user and 2nd product)", + "CONFIRM_ORDER": "CONFIRM ORDER", + "CREATE_ORDER": "CREATE ORDER", + "CONFIMR_1st_ORDER": "Confirm 1st order", + "CONFIMR_2nd_ORDER": "Confirm 2nd order", + "SETUP_MERCHANTS": { + "SETUP_MERCHANTS": "Setup merchants", + "PREV": "PREV", + "NEXT": "NEXT", + "ADD": "Add", + "BACK": "Back", + "SAVE": "Save", + "SELECT": "Select", + "CREATE": "Create", + "HOW_TO_SET_UP": { + "HOW_TO_SET_UP": "How to setup" + }, + "STEPPER": { + "ACCOUNT": "Account", + "BASIC_INFO": "Basic info", + "CONTACT_INFO": "Contact info", + "LOCATION": "Location", + "PAYMENTS": "Payments", + "MANUFACTURING": "Manufacturing", + "DELIVERY_AND_TAKEAWAY_SETTINGS": "Delivery & Takeaway Settings", + "ORDERS_SETTINGS": "Orders Settings", + "PRODUCT_CATEGORIES": "Product categories", + "PRODUCTS": "Products" + }, + "ACCOUNT": { + "ACCOUNT": "Account", + "EMAIL_ADDRESS": "Email address", + "EMAIL": "Email", + "PASSWORD": "Password", + "REPEAT_PASSWORD": "Repeat password", + "EMAIL_IS_REQUIRED": "Email is required", + "INVALID_EMAIL_FORMAT": "Invalid email format", + "USERNAME": "Username", + "USERNAME_IS_REQUIRED": "Username is required", + "USERNAME_MUST_BE_AT_LEAST_3_CHARACTERS": "Username must be at least 3 characters", + "PASSWORD_IS_REQUIRED": "Password is required", + "PASSWORD_MUST_BE_AT_LEAST_4_CHARACTERS": "Password must be at least 4 characters", + "REPEAT_PASSWORD_IS_REQUIRED": "Repeat password is required", + "PASSWORDS_DO_NOT_MATCH": "Passwords do not match" + }, + "BASIC_INFO": { + "BASIC_INFO": "Basic info", + "NAME": "Name", + "NAME_IS_REQUIRED": "Name is required", + "NAME_MUST_BE_AT_LEAST_4_CHARACTERS": "Name must be at least 4 characters", + "PHOTO": "Photo", + "ENTER_A_VALID_LOGO_URL_OR_BROWSE_FROM_A_DEVICE": "Enter a valid logo URL or browse from a device", + "REMOVE": "Remove", + "PHOTO_OPTIONAL": "Photo (optional)", + "BARCODE_DATA": "Barcode Data", + "BARCODE_DATA_IS_REQUIRED": "Barcode Data is required" + }, + "CONTACT_INFO": { + "CONTACT_INFO": "Contact info", + "CONTACT_PHONE": "Contact Phone", + "INVALID_PHONE_NUMBER_FORMAT": "Invalid phone number format", + "ORDER_FORWARDING_EMAIL": "Order Forwarding Email", + "ORDER_FORWARDING_PHONE": "Order Forwarding Phone", + "ORDERS_EMAIL": "Orders Email", + "ORDERS_EMAIL_IS_REQUIRED": "Orders email is required", + "INVALID_EMAIL_FORMAT": "Invalid email format", + "ORDERS_PHONE": "Orders Phone", + "ORDERS_PHONE_IS_REQUIRED": "Orders phone is required" + }, + "LOCATION": { + "LOCATION": "Location" + }, + "PAYMENTS": { + "PAYMENTS": "Payments", + "ALLOW_ONLINE_PAYMENT": "Allow online payments?", + "ALLOW_CASH_PAYMENT": "Allow cash payments?", + "STRIPE": { + "PAY_BUTTON_TEXT": "Pay Button text", + "CURRENCY": "Currency", + "PAY_BUTTON_TEXT_IS_REQUIRED": "Pay Button text is required", + "CHOOSE_CURRENCY_CODE": "Choose currency code", + "CURRENCY_TEXT_IS_REQUIRED": "Currency text is required", + "COMPANY_BRAND_LOGO": "Company brand logo", + "COMPANY_BRAND_LOGO_IS_REQUIRED": "Company brand logo is required", + "INVALID_LOGO_URL": "Invalid logo url", + "INVALID_LOGO": "Invalid logo", + "PUBLISHABLE_KEY": "Publishable key", + "PUBLISHABLE_KEY_IS_REQUIRED": "Publishable key is required", + "ALLOW_REMEMBER_ME": "Allow remember me?", + "REMOVE": "Remove" + }, + "PAYPAL": { + "MODE": "Mode", + "CHOOSE_PAYPAL_MODE": "Choose PayPal mode", + "TYPE": "type", + "CURRENCY": "Currency", + "CHOOSE_CURRENCY_CODE": "Choose currency code", + "CURRENCY_TEXT_IS_REQUIRED": "Currency text is required", + "PUBLISHABLE_KEY": "Publishable key", + "PUBLISHABLE_KEY_IS_REQUIRED": "Publishable key is required", + "SECRET_KEY": "Secret key", + "SECRET_KEY_IS_REQUIRED": "Secret key is required", + "PAYMENT_DESCRIPTION": "Payment description", + "PAYMENT_DESCRIPTION_IS_REQUIRED": "Payment description is required" + } + }, + "MANUFACTURING": { + "MANUFACTURING": "Manufacturing", + "PRODUCTS_MANUFACTURING": "Products Manufacturing" + }, + "DELIVERY_AND_TAKEAWAY_SETTINGS": { + "DELIVERY_AND_TAKEAWAY_SETTINGS": "Delivery & Takeaway Settings", + "SELECT_FROM_SHARED_CARRIERS": "Select from shared carriers", + "ADD_YOUR_CARRIER": "Add your carrier", + "EDIT_CARRIER": "Edit carrier", + "CARRIER_REQUIRED": "Carrier required", + "PRODUCTS_DELIVERY_BY_DEFAULT": "Products Delivery (by default)", + "PRODUCTS_TAKEAWAY_BY_DEFAULT": "Products Takeaway (by default)", + "USE_SELECTED_SHARED_CARRIERS": "Use selected shared carriers", + "ADD_YOUR_CARRIERS": "Add your carriers" + }, + "ORDERS_SETTINGS": { + "ORDERS_SETTINGS": "Orders Settings", + "ORDER_BARCODE_QR_CODE_TYPES": "Order Barcode/QR code types" + }, + "PRODUCT_CATEGORIES": { + "PRODUCT_CATEGORIES": "Product categories", + "ADD_OWN_PRODUCT_CATEGORY": "Add own product category" + }, + "PRODUCTS": { + "PRODUCTS": "Products", + "SELECT_FROM_PRODUCTS_CATALOG": "Select from products catalog", + "CREATE_PRODUCT": "Create product", + "EDIT_PRODUCT": "Edit product", + "ADD_PRODUCT": "Add product", + "CREATE_NEW_PRODUCT": "Create new product" + } + } + }, + "WAREHOUSE_MANAGE": { + "TITLE": "Manage Warehouse", + "MANAGE_STORE": "Manage Store", + "SAVE": "SAVE", + "NAME": "Name", + "USERNAME": "Username", + "PASSWORD": "Password", + "COUNTRY": "Country", + "CITY": "City", + "POSTCODE": "Postcode", + "IS_ACTIVE": "Is Active", + "PRODUCTS_MANUFACTURING": "Products Manufacturing", + "CARRIER_REQUIRED": "Carrier required", + "RIGHT_NOW": "(right now)", + "CARRIERS": "Carriers", + "ADDRESS": "Address", + "CARRIERS_SPECIFIC": "Use only specific carriers", + "VALIDATION": { + "NAME": "Name is required", + "USERNAME": "Username is required", + "PASSWORD": "Password is required", + "COUNTRY": "Country is required", + "CITY": "City is required", + "STREET": "Street is required", + "HOUSE": "House number is required", + "POSTCODE": "Postcode is required" + }, + "WIZARD_TITLES": { + "DETAILS": "Details", + "ACCOUNT": "Account", + "CONTACT_INFO": "Contact Info", + "LOCATION": "Location", + "PAYMENT": "Payment", + "DELIVERY_ZONES": "Delivery zones" + } + }, + "WAREHOUSES_VIEW": { + "TITLE": "Stores", + "DELETE_WAREHOUSES": "Delete Selected", + "DELETE": "DELETE", + "CREATE": "CREATE", + "SHOW_ON_MAP": "Show on map", + "SMART_TABLE_COLUMNS": { + "IMAGE": "Image", + "NAME": "Name", + "EMAIL": "Email", + "PHONE": "Phone", + "CITY": "City", + "ADDRESS": "Address", + "ORDERS_QTY": "Orders QTY", + "ORDERS": "Orders" + }, + "INFO": { + "STORE_INFO": "Store Info", + "STORE_ID": "Store ID", + "STORE_NAME": "Store Name" + }, + "MERCHANTS": { + "TRACK_ALL_MERCHANTS": "Track all merchants", + "FILTER_MERCHANTS": "Filter merchants", + "FILTER_BY_NAME": "Filter by name", + "FILTER_BY_CITY": "Filter by city", + "FILTER_BY_COUNTRY": "Filter by country" + } + }, + "WAREHOUSE_VIEW": { + "ORDER": { + "CONFIRM": "Confirm", + "START_PROCESSING": "Start Processing", + "START_ALLOCATION": " Start Allocation", + "ALLOCATED": "Allocated", + "ALLOCATION_FAILS": "Allocation Fails", + "START_PACKAGING": "Start Packaging", + "PACKAGED": "Packaged", + "PACKAGING_FAILS": "Packaging Fails", + "GIVEN_TO_CARRIER": "Given to Carrier", + "GIVEN_TO_CUSTOMER": "Given to Customer", + "ORDER": "Order", + "CANT_PROCESSING_WITHOUT_PRODUCTS": "Can't processing the order without products." + }, + "MUTATION": { + "TITLE": "Register New Store", + "NAME": "Name", + "LOGO": "Logo", + "PHOTO": "Photo", + "IS_ACTIVE": "Is Active", + "ORDERS_SHORT_PROCESS": "Orders Short Process", + "PRODUCTS_MANUFACTURING": "Products Manufacturing", + "CARRIER_REQUIRED": "Carrier required", + "RIGHT_NOW": "Right now", + "ORDERS_PHONE": "Orders Phone", + "CONTACT_PHONE": "Contact Phone", + "ORDERS_EMAIL": "Orders Email", + "CONTACT_EMAIL": "Contact Email", + "FORWARD_ORDERS_WITH": "Forward Orders With", + "USERNAME": "Username", + "OLD_PASSWORD": "Old Password", + "NEW_PASSWORD": "New Password", + "CONFIRM_PASSWORD": "Confirm Password", + "PASSWORDS_DO_NOT_MATCH": "Passwords do not match", + "PASSWORD": "Password", + "COUNTRY": "Country", + "USA": "USA", + "ISRAEL": "Israel", + "BULGARIA": "Bulgaria", + "CITY": "City", + "ADDRESS": "Address", + "POSTCODE": "Postcode", + "COORDINATES": "Coordinates", + "AUTO_DETECT_COORDINATES": "Auto detect coordinates", + "CARRIERS": "Carriers", + "USE_ONLY_SPECIFIC_CARRIERS": "Use only specific carriers", + "SELECT_SHAPE_TO_ADD_ZONE": "Select shape to add a new zone", + "CIRCLE": "Circle", + "SHAPE": "Shape", + "DRAW_SHAPE_ON_MAP": "Draw a shape on the map", + "MINIMUM_AMOUNT": "Minimum amount", + "DELIVERY_FEE": "Delivery fee", + "CANCEL": "Cancel", + "ADD": "Add", + "EDIT": "Edit", + "ZONE_NAME": "Zone name", + "UNALLOWED_ORDER_CANCELATION": "Unallowed Order Cancelation", + "IN_STORE_MODE": "In-store mode", + "CARRIER_WORK_COMPETITION": "Carrier Work Competition", + "ORDER_CANCELATION_OPTIONS": { + "ORDERING": "After Ordering", + "START_PROCESSING": "After Start Processing", + "START_ALLOCATION": "After Start Allocation", + "ALLOCATED": "After Allocated", + "START_PACKAGING": "After Start Packaging", + "PACKAGED": "After Packaged", + "CARRIER_TAKE_WORK": "After Carrier Take Work", + "CARRIER_GOT_IT": "After Carrier Got It", + "CARRIER_START_DELIVERY": "After Carrier Start Delivery", + "DELIVERED": "After Delivered" + }, + "ERRORS": { + "NAME_IS_REQUIRED": "Warehouse name is required", + "NAME_ATLEAST_3_CHARS": "Name must be at least 3 characters long", + "NAME_MORE_THAN_255_CHARS": "Name cannot be more than 255 characters long", + "LOGO_IS_REQUIRED": "Warehouse logo is required", + "INVALID_URL": "Enter a valid logo URL or upload from a device", + "PHONE_CONTAINS_ONLY_DIGIT": "The phone number must only contain digits", + "INVALID_EMAIL": "Invalid Email", + "ORDERS_PHONE_IS_REQUIRED": "Orders Phone is required", + "CONTACT_PHONE_IS_REQUIRED": "Contact Phone is required", + "ORDERS_EMAIL_IS_REQUIRED": "Orders Email is required", + "CONTACT_EMAIL_IS_REQUIRED": "Contact Email is required", + "USERNAME_IS_REQUIRED": "Username is required", + "PASSWORD_IS_REQUIRED": "Password is required", + "COORDINATES_ARE_REQUIRED": "Coordinates are required", + "COUNTRY_IS_REQUIRED": "Country is required" + }, + "WIZARD_TITLES": { + "BASIC_INFO": "Basic Info", + "CONTACT_INFO": "Contact Info", + "LOCATION": "Location" + }, + "CONTACT_INFO_TAB": { + "ORDER_FORWARDING_EMAIL": "Order Forwarding Email", + "ORDER_FORWARDING_PHONE": "Order Forwarding Phone" + } + }, + "NEW_PRODUCT": { + "SELECT_FROM_PRODUCTS_CATALOG": "Select from Products Catalog", + "CREATE_NEW_PRODUCT": "Create new Product", + "HOW_TO_ADD": "How to add", + "ADD": "Add", + "SAVE": "Save", + "ADD_PRODUCTS_TO_STORE": "Add Products to Store", + "NOTHING_FOUND": "Nothing found..." + }, + "SELECT_PRODUCTS": { + "TITLE": "Title", + "DESCRIPTION": "Description", + "DETAILS": "Details", + "IMAGES": "Images", + "CATEGORY": "Category" + }, + "SAVE": { + "PRODUCT_NAME": "Product Name", + "PRICE": "Price", + "COUNT": "Count", + "DELIVERY": "Delivery", + "TAKEAWAY": "Takeaway" + }, + "PLACEHOLDER": { + "EXAMPLE": "Example: Domino's Pizza", + "IMAGE_URL": "Image url", + "HERE_GOES_A_SHORT_DESCRIPTION": "Here goes a short description", + "HERE_GOES_A_DETAILS_ABOUT_PRODUCT_(OPTION)": "Here goes a details about product (option)", + "REMOVE_IMAGE": "Remove image" + }, + "CREATE_ORDER_MODAL": { + "TITLE": { + "CREATE_ORDER": "Create Order" + }, + "WIZARD_FORM": { + "BUTTON_NEXT": "Next", + "BUTTON_PREV": "Back", + "BUTTON_DONE": "Finish Order", + "STEP1": { + "TITLE": "Choose Option", + "SELECT_FROM_EXISTING": "Select From Existing", + "ADD_NEW_CUSTOMER": "Add New Customer" + }, + "STEP2": { + "TITLE": { + "SELECT_CUSTOMER": "Select Customer", + "SELECT_ADD": "Select/Add", + "ADD_NEW": "Add New" + }, + "SMART_TABLE": { + "TITLES": { + "FULL_NAME": "Full Name", + "EMAIL": "Email", + "PHONE": "Phone", + "ADDRESS": "Address" + } + } + }, + "STEP3": { + "TITLE": "Create Order" + } + } + }, + "PRODUCTS_TAB": { + "PRODUCTS": "Products", + "ADD_PRODUCTS": "Add Products", + "DELETE": "Delete", + "IMAGE": "Image", + "TITLE": "Title", + "DESCRIPTION": "Description", + "DETAILS": "Details", + "CATEGORY": "Category", + "PRICE": "Price", + "QUANTITY": "Quantity", + "CLICK_ON_PRODUCT_IMAGE_TO_INCREASE_AVAILABLE_QUANTITY": "Click on Product Image to increase available quantity", + "AVAILABILITY": "Availability", + "TYPE": "Type", + "DELIVERY": "Delivery", + "TAKEAWAY": "Takeaway" + }, + "NEW_PRODUCT_TYPE": "New Type Product", + "ADD_PRODUCTS": "Add Products", + "CREATE_ORDER": "Create Order", + "STATUS": "Status", + "ORDERS": "Orders", + "WAREHOUSE": "Warehouse", + "PRODUCT": "Product", + "PRODUCTS": "Products", + "ORDER_NUMBER": "Order Number", + "CANCELLED": "Cancelled", + "WAREHOUSE_STATUS": "Warehouse Status", + "CARRIER_STATUS": "Carrier Status", + "PAID": "Paid", + "CARRIER": "Carrier", + "CREATED": "Created", + "ELAPSED": "Elapsed", + "CONTACT_DETAILS": "Contact Details", + "EMAIL": "Email", + "PHONE": "Phone", + "ORDERS_FORWARDING_DETAILS": "Orders Forwarding Details", + "ORDERS_FORWARDING_WITH": "Orders Forwarding With", + "MANAGE_STORE": "Manage Store", + "TOP_PRODUCTS": "Top Products", + "PRODUCTS_MANUFACTURING": "Products Manufacturing", + "CARRIER_REQUIRED": "Carrier required", + "MANAGE_WAREHOUSE": "Manage warehouse", + "MANAGE_STORE_PRODUCTS_&_ORDERS": "Manage store products & orders" + }, + "SIMULATION_VIEW": { + "SIMULATION": "Simulation", + "PURCHASE_PRODUCTS": "Purchase products", + "CREATE_INVITE_REQUEST": "Create Invite Request", + "SEND": "Send", + "CREATE_USER": "Create User", + "ORDER_CONFIRM": "Order Confirm", + "ORDER_CANCEL": "Order Cancel", + "PRODUCTS": "Products", + "STORE": "Store", + "ORDER": "Order", + "INVITE_REQUEST": "Invite-Request", + "INVITE_USER": "Invite User", + "TAB_BUTTONS": { + "PRODUCTS": "Products", + "ORDER_HISTORY": "Order history" + }, + "USER_MUTATION": { + "TITLE": "Create Customer", + "NAME": "Name", + "EMAIL": "Email", + "COUNTRY": "Country", + "CITY": "City", + "USA": "USA", + "ISRAEL": "Israel", + "BULGARIA": "Bulgaria", + "ADDRESS": "Address", + "POSTCODE": "Postcode", + "COORDINATES": "Coordinates", + "AUTO_DETECT_COORDINATES": "Auto detect coordinates", + "ERRORS": { + "FIRST_NAME_IS_REQUIRED": "First name is required", + "LAST_NAME_IS_REQUIRED": "Last name is required", + "INVALID_EMAIL": "Invalid Email", + "EMAIL_IS_REQUIRED": "Email is required", + "COORDINATES_ARE_REQUIRED": "Coordinates are required" + }, + "WIZARD_TITLES": { + "ADDITIONAL_INFO": "Additional Info", + "LOCATION": "Location" + } + }, + "SMART_TABLE": { + "TITLE": "Title", + "ID": "Id", + "IMAGE": "Image" + }, + "ORDER_INFO": { + "STATUSES": [ + { + "TITLE": "We're preparing the order!", + "DETAILS": "You will get it in %t minutes.", + "NOT_PAID_NOTE": "Prepare your wallet (%s in cash)." + }, + { + "TITLE": "Carrier on the way!", + "DETAILS": "You will get the order in %t min.", + "NOT_PAID_NOTE": "Prepare your wallet (%s in cash)." + }, + { + "TITLE": "Check your door!", + "DETAILS": "You will get the order in seconds.", + "NOT_PAID_NOTE": "Prepare your wallet (%s in cash)." + }, + { + "TITLE": "Order Completed!", + "DETAILS": "Thanks for using Ever", + "NOT_PAID_NOTE": "" + } + ], + "DELIVERY_STATUS": { + "WE": "We", + "CARRIER": "Carrier", + "YOU": "You" + }, + "DELIVERY_WRONG": { + "DELIVERY_WRONG": "The Delivery Went Wrong!", + "PROCESSING_WRONG": "Processing Went Wrong!", + "TRY_AGAIN": "Please try again.", + "CALL_FOR_DETAILS": "Call for details" + } + }, + "INSTRUCTIONS_STEPS": { + "INSTRUTIONS": "Instructions", + "CREATE_USER_STEP": { + "CREATE_USER": "Create User", + "STEP_1": "Step 1.", + "ORDER": "Order", + "TO_CONTINUE_IS_REQUIRED_TO_REGISTER_IN_THE_SYSTEM": "To continue is required to register in the system", + "CLICK_ON_BUTTON_CREATE_USER": "Click on button 'Create user'", + "FILL_THE_FORM_FOR_ADDITIONAL_INFO": "Fill the form for additional info (optional)", + "FILL_THE_FORM_FOR_LOCATION_AND_PRESS_DONE_BUTTON": "Fill the form for location and press DONE button" + }, + "ORDER_STEP": { + "ORDER": "Order", + "STEP_2": "Step 2.", + "CREATE_ORDER": "Create order", + "CHOICE_SOME_PRODUCTS_FROM_THE_TABLE": "Choice some products from the table (you can see more details about when press on his name or image)", + "SELECT_PRODUCT": "To be selected one product have to press on his row.", + "SELECT_BUTTON_ORDER_TO_CREATE_ORDER": "Press button 'Order' to create order with the selected product.", + "REVIEW_ORDER_HISTORY": "Review order history:", + "ON_PRESS_ORDER_HISTORY_TAB": "On press 'Order history' shows all your orders..", + "HERE_YOU_CAN_SEE_DETAILS_ABOUT_EACH_ORDER": "Here you can see details about each order", + "PRESS_ON_CARRIER_ORDER_OR_PRODUCT_NAME": "Press on carrier, order or product name for more information." + }, + "CONFIRM_OR_CANCEL_STEP": { + "STEP_3": "Step 3", + "CONFIRM_CANCEL_ORDER": "Confirm/Cancel order", + "REAL_TIME": "Real time", + "TRACK_STATUS_ON_YOUR_ORDER": "Track status on your order.", + "ELAPSED_TIME_FROM_CREATE_TO_DELIVERED": "Elapsed time from created to delivered.", + "SHOWS_MERCHANT_LOCATION": "Shows merchant location.", + "SHOWS_CARRIER_LOCATION": "Show carrier location.", + "POSSIBILITIES": "Possibilities:", + "SLIDER_REVIEW_OF_THE_ALL_PRODUCTS": "Slider review of the all products.", + "CAN_CANCEL_IT_WITH_ORDER_CANCEL_BUTTON": "Untill the order is not delivered you user cancel it with 'Order Cancel' button.", + "AFTER_THE_ORDER_IS_DELIVER_USER_CAN_CLICK_BUTTON_ORDER_CONFIRM_TO_CONTINUE": "After the order is delivered the user can click button 'Order Confirm' to continue.." + }, + "INVITE_STEP": { + "TO_CONTINUE_ITs_REQUIRED_TO_BE_INVITED_IN_THE_SYSTEM": "To continue it's required to be invited in the system:", + "SEND_INVITE_REQUEST": "Send 'Invite Request' to the system from the form which will be opened after pressing Invite Request button.", + "ALL_INVITE_REQUESTS_ARE_REVIEWED": "All invite requests are reviewed by admin and they can be invited if the system is available near your location (for test you can do immediately form Invite User button).", + "AFTER_YOU_GET_INVITED_BEFORE": "After you get invite you can easy login the system, just have to enter your invite code, which will be provided from the system (you will see here", + "AFTER_YOU_GET_INVITED_AFTER": "code after press Invite button, and can enter it when press Create User button)." + } + } + }, + "CUSTOMERS_VIEW": { + "TITLE": "Customers", + "DELETE_CUSTOMERS": "Delete", + "CREATE_CUSTOMER": "Create", + "DELETE": "DELETE", + "CREATE": "CREATE", + "BAN": "BAN", + "UNBAN": "UNBAN", + "MANAGE_CUSTOMER": "Manage customer", + "CUSTOMER": "Customer", + "CUSTOMERS_DEVICES": "Devices", + "INVITE": "Invite", + "NOT_INVITED_ONLY": "Not invited only", + "ORDER": "Order", + "ORDERS_STATISTICS": "Orders statistics", + "NUMBER_OF_ORDERS": "Number of Orders", + "CANCELED_ORDERS": "Canceled Orders", + "COMPLETED_ORDERS_TOTAL": "Completed Orders Total", + "Order": "Order", + "CANCEL_ORDER": "Cancel Order", + "CATEGORY": "Category", + "ORDERS_HISTORY": "Orders History", + "AVAILABLE_PRODUCTS": "Available products", + "NEARBY_STORES": "Nearby Stores", + "INVITES_REQUESTS_MANAGEMENT": "Invites requests management", + "INVITES_MANAGEMENT": "Invites management", + "DESCRIPTION": "Description", + "DETAILS": "Details", + "MAKE_A_CUSTOM_ORDER": "Make a custom order", + "PRODUCT_COUNT": "Product Count", + "QUANTITY_CAN'T_BE_EMPTY": "Quantity can't be empty", + "QUANTITY_CAN'T_BE_0": "Quantity can't be 0", + "NOT_ENOUGH_PRODUCTS_AVAILABLE": "Not enough products available", + "ORDER_INFO": "Order Info", + "ORDER_ID": "Order Id", + "STORE_ID": "Store Id", + "CARRIER_ID": "Carrier Id", + "NO_CARRIER": "No Carrier", + "SMART_TABLE_COLUMNS": { + "IMAGE": "Image", + "NAME": "Name", + "EMAIL": "Email", + "PHONE": "Phone", + "ADDRESS": "Address", + "ORDERS_QTY": "Orders QTY", + "COUNTRY": "Country", + "CITY": "City", + "STREET_ADDRESS": "Street Address", + "HOUSE": "House", + "APARTMENT": "Apartment", + "INVITE_CODE": "Invite Code", + "INVITED_DATE": "Invited date", + "ORDER_NUMBER": "Order Number", + "WAREHOUSE": "Warehouse", + "CARRIER": "Carrier", + "PRODUCT_LIST": "Product list", + "STATS": "Stats", + "DELIVERY_TIME": "Delivery Time", + "CREATED": "Created", + "ACTIONS": "Actions", + "PAID": "Paid", + "COMPLETED": "Completed", + "CANCELLED": "Cancelled", + "NOT_DELIVERED": "Not delivered", + "PRODUCT": "Product", + "PRICE": "Price", + "STORE": "Store", + "AVAILABLE_COUNT": "Available Count", + "ORDER": "Order", + "STATUS": "Status" + }, + "EDIT": { + "EDIT_CUSTOMER": "Edit Customer", + "BASIC_INFO": "Basic Info", + "SAVE": "Save" + }, + "DEVICE": { + "ALL_DEVICE": "All Device", + "DEVICE_ID": "Device ID", + "ID": "id", + "UPDATE": "Update", + "LANGUAGE": "language", + "TYPE": "type", + "TYPEU": "Type", + "UUID": "uuid", + "DEVICE_UUID": "Device UUID", + "UPDATE_DEVICE": "Update Device", + "CUSTOMERS_DEVICES": "Customers Devices", + "DELETE": "DELETE", + "CREATE": "CREATE" + }, + "WAREHOUSE": { + "WAREHOUSE_INFO": "Warehouse Info", + "WAREHOUSE_ID": "Warehouse Id", + "WAREHOUSE_NAME": "Warehouse Name" + }, + "CUSTOMER_VIEW": { + "MANAGE_CUSTOMER": "Manage customer", + "EDIT": "EDIT", + "CUSTOMER": "Customer" + }, + "INVITES_VIEW": { + "DELETE": "Delete", + "INVITE": "Invite" + } + }, + "CARRIERS_VIEW": { + "TITLE": "Carriers", + "DELETE_CARRIERS": "Delete Selected", + "CREATE_CARRIER": "Create Carrier", + "DELETE": "DELETE", + "CREATE_BUTTON": "CREATE", + "ACTIVE_AND_AVAILABLE_ORDERS": "Active and Available Orders", + "ORDERS_HISTORY": "Orders History", + "TRACK": "Track", + "SMART_TABLE_COLUMNS": { + "IMAGE": "Image", + "NAME": "Name", + "PHONE": "Phone", + "STATUS": "Status", + "ADDRESS": "Address", + "DELIVERIES": "Deliveries" + }, + "CARRIER_PAGE": { + "WAREHOUSE": "Warehouse", + "CUSTOMER": "Customer", + "SAVE": "Save", + "EDIT": "Edit", + "CARRIER_INFO": "Carrier Info", + "CARRIER_ID": "Carrier Id", + "REGISTER_NEW_CARRIER": "Register New Carrier", + "WAREHOUSE_STATUS": "Warehouse Status", + "CARRIER_STATUS": "Carrier Status", + "CREATED": "Created", + "ARRIVED_TO_CUSTOMER": "Arrived to Customer", + "FAILED": "Failed", + "DELIVERED": "Delivered", + "CLIENT_REFUSE_ORDER": "Client Refuse Order", + "AVAIBLE_ORDER_TO_PICK_UP": "Available Orders to pick up (each carrier can pick up multiple orders)", + "ACTIVE": "Active", + "CARRIER_CAN_BE_SHARED": "Carrier can be shared?", + "NOT_ACTIVE": "Not Active", + "WORKING": "Working", + "NOT_WORKING": "Not Working", + "SELECT_CARRIER": "Select carrier", + "CARRIER_ORDERS_STATUS": "Carrier orders status", + "Start": "Start", + "PICKED_UP_ORDER": "Picked Up Order", + "CANCEL": "Cancel", + "Arrived To Client": "Arrived To Client", + "No Carrier": "No Carrier", + "Order Selected For Delivery": "Order Selected For Delivery", + "Order Picked Up": "Order Picked Up", + "Order In Delivery": "Order In Delivery", + "Delivered": "Delivered", + "Delivery Issues": "Delivery Issues", + "Client Refuse to Take Order": "Client Refuse to Take Order", + "BAD_STATUS": "BAD_STATUS", + "Created": "Created", + "Confirmed": "Confirmed", + "Processing": "Processing", + "Allocation Started": "Allocation Started", + "Allocation Finished": "Allocation Finished", + "Packaging Started": "Packaging Started", + "Packaged": "Packaged", + "Given to Carrier": "Given to Carrier", + "Allocation Failed": "Allocation Failed", + "Packaging Failed": "Packaging Failed", + "LOCATION": "Location", + "TIME": "Time", + "NAME": "Name" + }, + "EDIT": { + "EDIT_CARRIER": "Edit Carrier", + "BASIC_INFO": "Basic Info", + "LOCATION": "Location", + "PHOTO_URL": "Photo Url", + "CONTACT_PHONE": "Contact Phone", + "FIRST_NAME": "First Name", + "LAST_NAME": "Last Name" + }, + "CREATE": { + "BASIC_INFO": "Basic Info", + "LOCATION": "Location" + }, + "TRACK_PAGE": { + "TRACK_ALL_WORKING_CARRIERS": "Track all working carriers", + "FILTER_CARRIERS": "Filter Carriers", + "PHONE": "Phone", + "EMAIL": "Email", + "ADDRESS": "Address", + "DELIVERY_COUNT": "Delivery Count" + } + }, + "PRODUCT_TYPE_VIEW": { + "TITLE": "New Product", + "WIZARD_FORM": { + "VALIDATION_MESSAGES": { + "TITLE": "Title is required", + "THE_LENGHT_OF_THE_TITLE": "The length of title should be max 255 characters long!", + "IMAGE": "Picture is required", + "DESCRIPTION": "Description is required", + "LANGUAGE": "Language is required", + "THE_LENGHT_OF_THE_DESCRIPTION": "The length of description should be max 255 characters long!", + "PRICE": "Price is required", + "COUNT": "Product count is required" + } + } + }, + "SHARED": { + "FORMS": { + "ERRORS": { + "FIRST_NAME_REQUIRED": "First name is required", + "LAST_NAME_REQUIRED": "Last name is required", + "USERNAME_REQUIRED": "Username is required", + "PASSWORD_REQUIRED": "Password is required", + "PHONE_REQUIRED": "Phone is required", + "LOGO_URL_REQUIRED": "Enter valid image URL or browse for a file", + "IS_ACTIVE": "Is active field is required", + "COUNTRY_REQUIRED": "Country is required", + "CITY_REQUIRED": "City is required", + "STREET_ADDRESS_REQUIRED": "Street address is required", + "HOUSE_REQUIRED": "House number is required", + "COORDINATES_REQUIRED": "Coordinates are required", + "MUST_CONTAIN_ONLY_LETTERS": "Must contain only letters", + "PHONE_MUST_CONTAINS_ONLY(specail_signs)AND_DIGIT_CHARACTER": "Phone number can start with '+' or '(some numbers)' and must contains only only: '-, ., (space), #'' and digit characters" + } + }, + "USER": { + "FORMS": { + "BASIC_INFO": { + "TITLE": "Basic Info", + "FIRST_NAME": "First Name", + "FIRST_NAME_OPTIONAL": "First Name (optional)", + "LAST_NAME_OPTIONAL": "Last Name (optional)", + "PHOTO_URL": "Photo Url", + "PICTURE_URL": "Picture url (optional)", + "EMAIL": "Email", + "EMAIL_OPTIONAL": "Email (optional)", + "ERRORS": { + "INVALID_EMAIL": "Invalid Email", + "EMAIL_IS_ALREADY_IN_USE": "Email is already in use" + } + } + } + }, + "WAREHOUSE": { + "ORDER_MODAL": { + "MAKE_ORDER": "Make Order", + "ONLY_AVAILABLE": "Show only available products", + "ORDER": "Order", + "SMART_TABLE": { + "TITLES": { + "IMG": "Img", + "PRODUCT": "Product", + "PRICE": "Price", + "AVAILABLE": "Available", + "AMOUNT": "Amount", + "COMMENT": "Comment" + } + } + } + } + }, + "MENU_VIEW": { + "DASHBOARD": "Dashboard", + "STORES": "Stores", + "PRODUCTS": { + "PRODUCTS": "Products", + "MANAGEMENT": "Management", + "CATEGORIES": "Categories" + }, + "CUSTOMERS": { + "CUSTOMERS": "Customers", + "MANAGEMENT": "Management", + "INVITES": "Invites" + }, + "CARRIERS": "Carriers", + "SIMULATION": "Simulation", + "SETUP": "Setup" + }, + "CATEGORY_VIEW": { + "TITLE": "Title", + "IMAGE": "Image", + "CREATE_BUTTON": "CREATE", + "DELETE": "DELETE", + "EDIT": { + "EDIT_CATEGORY": "Edit Category", + "CATEGORY_NAME": "Category Name", + "ENTER_THE_CATEGORY_NAME": "Enter the category name", + "DONE": "Done" + }, + "CREATE": { + "CREATE_CATEGORY": "Create Category", + "CATEGORY_NAME": "Category Name", + "ENTER_THE_CATEGORY_NAME": "Enter the category name", + "PHOTO": "Photo", + "BROWSE": "Browse", + "INVALID_URL": "Invalid Url", + "REMOVE_IMAGE": "Remove image", + "PHOTO_OPTIONAL": "Photo (optional)", + "DONE": "Done" + } + }, + "PLACEHOLDER": { + "EXAMPLE": "Example: Domino's Pizza", + "IMAGE_URL": "Image url", + "HERE_GOES_A_SHORT_DESCRIPTION": "Here goes a short description", + "HERE_GOES_A_DETAILS_ABOUT_PRODUCT_(OPTION)": "Here goes a details about product (option)", + "REMOVE_IMAGE": "Remove image", + "PASSWORD": "Password", + "LATITUDE": "Latitude", + "LONGITUDE": "Longitude", + "APARTMENT": "Apartment", + "HOUSE": "House", + "STREET": "Street", + "ZIP": "ZIP", + "CITY": "City", + "FIND_ADDRESS": "Find Address" + }, + "STATUS_TEXT": { + "Created": "Created", + "Confirmed": "Confirmed", + "Processing": "Processing", + "Allocation Started": "Allocation Started", + "Allocation Finished": "Allocation Finished", + "Packaging Started": "Packaging Started", + "Packaged": "Packaged", + "Given to Carrier": "Given to Carrier", + "Allocation Failed": "Allocation Failed", + "Packaging Failed": "Packaging Failed", + "No Carrier": "No Carrier", + "Order Selected For Delivery": "Order Selected For Delivery", + "Order Picked Up": "Order Picked Up", + "Order In Delivery": "Order In Delivery", + "Arrived To Client": "Arrived To Client", + "Delivered": "Delivered", + "Delivery Issues": "Delivery Issues", + "Client Refuse to Take Order": "Client Refuse to Take Order", + "Given to Customer": "Given to Customer", + "BAD_STATUS": "BAD_STATUS" + }, + "ELAPSED_TIME": { + "TITLE": "Elapsed time" + }, + "CONFIRM_MODAL": { + "ARE_YOU_SURE": "Are you sure?", + "ARE_YOU_SURE_YOU_WANT_TO_INCREASE": "Are you sure you want to increase the qty of products?", + "ARE_YOU_SURE_YOU_WANT_TO_DECREASE": "Are you sure you want to decrease the qty of products?", + "YES": "Yes", + "NO": "No" + }, + "NO_SERVER_VIEW": { + "NO_SERVER": "Server connection is lost" + }, + "BUTTON_NEXT": "Next", + "BUTTON_PREV": "Back", + "BUTTON_DONE": "Done", + "TERRAIN": "Terrain", + "SATELLITE": "Satellite", + "LOCATION": "Location", + "ROAD_MAP": "Road Map", + "Manage warehouse": "Manage store products & orders", + "Warehouse": "Warehouse", + "Create Warehouse": "Create Warehouse", + "SIMULATION": "Simulation", + "Purchase products": "Purchase products", + "Manage": "Manage", + "Orders": "Orders", + "Confirmed": "Confirmed", + "In Delivery": "In Delivery", + "Not Confirmed": "Not Confirmed", + "Not paid": "Not paid", + "Cancelled": "Cancelled", + "All": "All", + "CANCEL": "Cancel", + "Default Settings": "Default Settings", + "Products Manufacturing": "Products Manufacturing", + "Carrier required before sale": "Carrier required before sale", + "New Product Type": "New Product Type", + "Products": "Products", + "Product": "Product", + "Title": "Title", + "Picture Url": "Picture Url", + "Description": "Description", + "Details": "Details", + "Price": "Price", + "CATEGORY": "Category", + "LANGUAGE": "Language", + "BROWSE": "Browse", + "ENGLISH": "English", + "HEBREW": "Hebrew", + "RUSSIAN": "Russian", + "BULGARIAN": "Bulgarian", + "SPANISH": "Spanish", + "FRENCH": "French", + "SELECT": "Select", + "Name": "Name", + "Id": "Id", + "Warehouse name is required": "Warehouse name is required", + "Name must be at least 1 characters long": "Name must be at least 1 characters long", + "Title cannot be more than 255 characters long": "Title cannot be more than 255 characters long", + "Logo": "Logo", + "Warehouse logo is required": "Warehouse logo is required", + "is Active": "is Active", + "right now": "right now", + "Unselected": "Unselected", + "Phone": "Phone", + "Email": "Email", + "Username": "Username", + "Username is required": "Username is required", + "Password": "Password", + "Country": "Country", + "USA": "USA", + "Israel": "Israel", + "Bulgaria": "Bulgaria", + "City": "City", + "Address": "Address", + "Postcode": "Postcode", + "Coordinates": "Coordinates", + "Auto detect coordinates": "Auto detect coordinates", + "Carriers": "Carriers", + "Carrier": "Carrier", + "Use only specific carriers": "Use only specific carriers", + "Manage carrier and deliveries": "Manage carrier and deliveries", + "Register New Carrier": "Register New Carrier", + "Create User": "Create User", + "OPTIONAL": "optional" +} diff --git a/packages/admin-web-angular/src/assets/i18n/en.json b/packages/admin-web-angular/src/assets/i18n/en.json new file mode 100644 index 0000000..c26b63c --- /dev/null +++ b/packages/admin-web-angular/src/assets/i18n/en.json @@ -0,0 +1,1169 @@ +{ + "COMMON": { + "SAVE": "Save", + "CANCEL": "Cancel", + "USA": "USA", + "ISRAEL": "Israel", + "BULGARIA": "Bulgaria" + }, + "ORDER_VIEW": { + "ORDER_HEADER_INFO": { + "ORDER": "Order", + "MANAGE_ORDER": "Manage Order", + "TOTAL": "Total" + }, + "ORDER_PRODUCT_INFO": { + "ORDER_PRODUCTS": "Order Products", + "ADD_PRODUCTS": "Add products", + "REMOVE_PRODUCTS": "Remove Products", + "CANCEL_ORDER": "Cancel Order", + "THE_ORDER_IS_CANCELED": "The order is canceled", + "THE_ORDER_IS_GIVEN_TO_CARRIER": "The order is given to carrier.", + "THE_ORDER_IS_DELIVERED": "The order is delivered.", + "ADD_PRODUCTS_MODAL": "Add Products", + "ADD": "Add", + "SUCCESS_TOAST": "Products were added to the order", + "ERROR_TOAST": "Error, something went wrong", + "SMART_TABLE": { + "NAME": "Name", + "QTY": "QTY", + "PRICE": "Price", + "IMAGE": "Image", + "COMMENT": "Comment" + } + }, + "ORDER_SIDEBAR": { + "CONTACT_DETAILS": "Contact Details", + "WAREHOUSE": "Warehouse", + "CUSTOMER": "Customer", + "CARRIER": "Carrier", + "QTY": "qty" + }, + "LOCATION_INFO": { + "MAP": "Map", + "DELIVERY_DISTANCE": "Delivery Distance:" + } + }, + "DASHBOARD_VIEW": { + "TOTAL_CUSTOMER": "Total Customers", + "TOTAL_QUANTITY_OF_EXISTING_CUSTOMERS": "Total quantity of existing customers", + "TOTAL_COMPLETED_ORDERS": "Total completed orders", + "TOTAL_QUANTITY_OF_COMPLETED_ORDERS": "Total quantity of completed orders", + "TOTAL_REVENUE": "Total Revenue", + "TOTAL_SUM_SUM_OF_ALL_COMPLETED_ORDERS": "Total sum of all completed orders", + "TODAYs_CUSTOMERS": "Today's Customers", + "TODAYs_QUANTITY_OF_REGISTERED_CUSTOMERS": "Today's quantity of registered customers", + "TODAYs_COMPLETED_ORDERS": "Today's completed orders", + "TODAYs_QUANTITY_OF_NEW_COMPLETED_ORDERS": "Today's quantity of new completed orders", + "TODAYs_REVENUE": "Today's Revenue", + "TODAYs_SUM_OF_COMPLETED_ORDERS": "Today's sum of completed orders", + "TILL_AVERAGE": "till average", + "BETTER_THAN_AVERAGE": "better than average", + "SELECT_COMPONENT": { + "STORES": "Stores", + "CONTACT_DETAILS": "Contact Details", + "PHONE": "Phone", + "EMAIL": "Email", + "ORDERS_FORWARDING_WITH": "Orders Forwarding With", + "SELECT_STORE": "Select store" + }, + "CHARTS": { + "ORDERS": "Orders", + "PROFIT": "Profit", + "TOTAL_ORDERS": "Total orders", + "TOTAL_COMPLETED_ORDERS": "Total completed orders", + "TOTAL_CANCELLED_ORDERS": "Total canceled orders", + "TOTAL_REVENUE_ALL_ORDERS": "Total revenue from all orders", + "TOTAL_REVENUE_COMPLETED_ORDERS": "Total revenue from completed orders", + "TOTAL_LOST_REVENUE_CANCELLED_ORDERS": "Total lost revenue from canceled orders", + "PAYMENT": "Payment", + "CANCELED": "Canceled", + "ALL_ORDERS": "All Orders", + "TODAY": "Today", + "LAST_WEEK": "Last Week", + "LAST_MONTH": "Last Month", + "CURRENT_YEAR": "Current Year", + "YEARS": "By Year", + "CUSTOM_PERIOD": "Custom Period", + "SELECT_PERIOD": "Select Period", + "FROM": "From", + "TO": "To", + "SELECT": "Select", + "LABELS": { + "WEEK": "Week", + "WEEKDAYS": { + "MON": "Mon", + "TUE": "Tue", + "WED": "Wed", + "THU": "Thu", + "FRI": "Fri", + "SAT": "Sat", + "SUN": "Sun" + }, + "MONTHS": { + "JAN": "Jan", + "FEB": "Feb", + "MAR": "Mar", + "APR": "Apr", + "MAY": "May", + "JUN": "Jun", + "JUL": "Jul", + "AUG": "Aug", + "SEP": "Sep", + "OCT": "Oct", + "NOV": "Nov", + "DEC": "Dec" + } + } + } + }, + "HEADER_VIEW": { + "ADMIN": "Admin", + "EVER": "Ever", + "PROFILE": "Profile", + "LOG_OUT": "Log out" + }, + "FOOTER_VIEW": { + "COPY_RIGHT": "Copyright © 2016-present", + "ALL_RIGHTS_RESERVED": "All rights reserved" + }, + "PROFILE_VIEW": { + "PROFILE_PAGE": "Profile page", + "BASIC_INFO": "Basic Info", + "ACCOUNT": "Account", + "USERNAME": "Username", + "ERROR": "Error", + "EMAIL": "Email", + "FIRST_NAME": "First Name", + "FIRST_NAME_OPTIONAL": "First Name (optional)", + "LAST_NAME": "Last Name (optional)", + "PICTURE_URL": "Picture url (optional)", + "BROWSE": "Browse", + "REMOVE": "Remove", + "SAVE": "Save", + "OLD_PASSWORD": "Old password", + "NEW_PASSWORD": "New password", + "REPEAT_NEW_PASSWORD": "Repeat new password", + "INVALID_EMAIL_ADDRESS": "Invalid email address", + "INVALID_URL": "Invalid URL", + "NAME_MUST_CONTAIN_ONLY_LETTERS": "Name must contain only letters", + "PASSWORDS_DO_NOT_MATCH": "Passwords do not match", + "SUCCESSFULLY_CHANGE_PASSWORD": "Successfully change password" + }, + "PRODUCTS_VIEW": { + "DELETE": "DELETE", + "CREATE": "CREATE", + "EDIT_VIEW": { + "EDIT_PRODUCT": "Edit Product", + "BASIC_INFO": "Basic Info", + "SAVE": "Save" + } + }, + "FAKE_DATA": { + "FAKE_DATA_GENERATOR": "Fake Data Generator", + "GENERATE_ALL": "Generate All", + "CREATE_100_USERS": "Create 100 customers", + "CREATE_100_CARRIERS": "Create 100 carriers", + "CREATE_100_WAREHOUSES": "Create 100 warehouses", + "SETUP": "SETUP", + "GENERATE_INITIAL_DATA": "Generate Initial Data", + "CREATE_INVITE": "CREATE INVITE", + "HARDCODED_DATA": "Hardcoded data", + "CLEAR_ALL": "CLEAR ALL", + "GENERATE_HARDCODED_ONLY": "Generate Hardcoded Only", + "INCLUDED_HARDCODED_DATA": "Include hardcoded data", + "CREATE_1st_INVITE": "Create 1st invite", + "CREATE_2st_INVITE": "Create 2nd invite", + "CREATE_3st_INVITE": "Create 3rd invite", + "CREATE_4st_INVITE": "Create 4rd invite", + "CREATE_CUSTOMER": "Create Customer", + "CREATE_USER": "CREATE USER", + "CREATE_1st_USER": "Create 1st user (using 1st invite)", + "CREATE_CARRIER": "CREATE CARRIER", + "CREATE_1st_CARRIER": "Create 1st carrier", + "CREATE_2nd_CARRIER": "Create 2nd carrier", + "CREATE_3rd_CARRIER": "Create 3rd carrier", + "CREATE_PRODUCT": "CREATE PRODUCT", + "CREATE_PEPERONI&MUSHROOM_PIZZA_PRODUCT": "Create Peperoni & Mushroom pizza product", + "CREATE_SUSHI&CAVIAR_PRODUCT": "Create Sushi & Caviar product", + "CREATE_SUSHI_MIX_PRODUCT": "Create Sushi mix product", + "CREATE_PASTA_PRODUCT": "Create Pasta product", + "CREATE_SUSHI_BOX_PRODUCT": "Create Sushi box product", + "CREATE_PEPERONI&TOMATO_PIZZA_PRODUCT": "Create Peperoni & Tomato pizza product", + "CREATE_WAREHOUSE": "CREATE WAREHOUSE", + "CREATE_1st_WAREHOUSE": "Create 1st warehouse", + "CREATE_2nd_WAREHOUSE": "Create 2nd warehouse", + "CREATE_3rd_WAREHOUSE": "Create 3rd warehouse", + "CREATE_WAREHOUSE_PRODUCT": "CREATE WAREHOUSE PRODUCT", + "CREATE_1st_WAREHOUSE_PRODUCTS(using_product_number_1_2_3_4_5_and_6)": "Create 1st warehouse products (using product number 1, 2, 3, 4, 5 and 6)", + "CREATE_3rd_WAREHOUSE_PRODUCTS(using_1st_product)": "Create 3rd warehouse products (using 1st product)", + "CREATE_2nd_WAREHOUSE_PRODUCTS(using_product_number_1_2_and_3": "Create 2nd warehouse products (using product number 1, 2, and 3", + "UPDATE_WAREHOUSE_GEO_LOCATION": "UPDATE WAREHOUSE GEO LOCATION", + "UPDATE_1ST_WAREHOUSE_GEO_LOCATION": "Update 1st warehouse geo location", + "CREATE_1RD_ORDER(using_1st_warehouse_1st_user_and_1st_product)": "Create 1rd order (using 1st warehouse, 1st user and 1st product)", + "CREATE_2ND_ORDER(using_1st_warehouse_1st_user_and_2nd_product)": "Create 2nd order (using 1st warehouse, 1st user and 2nd product)", + "CONFIRM_ORDER": "CONFIRM ORDER", + "CREATE_ORDER": "CREATE ORDER", + "CONFIMR_1st_ORDER": "Confirm 1st order", + "CONFIMR_2nd_ORDER": "Confirm 2nd order", + "SETUP_MERCHANTS": { + "SETUP_MERCHANTS": "Setup merchants", + "PREV": "PREV", + "NEXT": "NEXT", + "ADD": "Add", + "BACK": "Back", + "SAVE": "Save", + "SELECT": "Select", + "CREATE": "Create", + "HOW_TO_SET_UP": { + "HOW_TO_SET_UP": "How to setup" + }, + "STEPPER": { + "ACCOUNT": "Account", + "BASIC_INFO": "Basic info", + "CONTACT_INFO": "Contact info", + "LOCATION": "Location", + "PAYMENTS": "Payments", + "MANUFACTURING": "Manufacturing", + "DELIVERY_AND_TAKEAWAY_SETTINGS": "Delivery & Takeaway Settings", + "ORDERS_SETTINGS": "Orders Settings", + "PRODUCT_CATEGORIES": "Product categories", + "PRODUCTS": "Products" + }, + "ACCOUNT": { + "ACCOUNT": "Account", + "EMAIL_ADDRESS": "Email address", + "EMAIL": "Email", + "PASSWORD": "Password", + "REPEAT_PASSWORD": "Repeat password", + "EMAIL_IS_REQUIRED": "Email is required", + "INVALID_EMAIL_FORMAT": "Invalid email format", + "USERNAME": "Username", + "USERNAME_IS_REQUIRED": "Username is required", + "USERNAME_MUST_BE_AT_LEAST_3_CHARACTERS": "Username must be at least 3 characters", + "PASSWORD_IS_REQUIRED": "Password is required", + "PASSWORD_MUST_BE_AT_LEAST_4_CHARACTERS": "Password must be at least 4 characters", + "REPEAT_PASSWORD_IS_REQUIRED": "Repeat password is required", + "PASSWORDS_DO_NOT_MATCH": "Passwords do not match" + }, + "BASIC_INFO": { + "BASIC_INFO": "Basic info", + "NAME": "Name", + "NAME_IS_REQUIRED": "Name is required", + "NAME_MUST_BE_AT_LEAST_4_CHARACTERS": "Name must be at least 4 characters", + "PHOTO": "Photo", + "ENTER_A_VALID_LOGO_URL_OR_BROWSE_FROM_A_DEVICE": "Enter a valid logo URL or browse from a device", + "REMOVE": "Remove", + "PHOTO_OPTIONAL": "Photo (optional)", + "BARCODE_DATA": "Barcode Data", + "BARCODE_DATA_IS_REQUIRED": "Barcode Data is required" + }, + "CONTACT_INFO": { + "CONTACT_INFO": "Contact info", + "CONTACT_PHONE": "Contact Phone", + "INVALID_PHONE_NUMBER_FORMAT": "Invalid phone number format", + "ORDER_FORWARDING_EMAIL": "Order Forwarding Email", + "ORDER_FORWARDING_PHONE": "Order Forwarding Phone", + "ORDERS_EMAIL": "Orders Email", + "ORDERS_EMAIL_IS_REQUIRED": "Orders email is required", + "INVALID_EMAIL_FORMAT": "Invalid email format", + "ORDERS_PHONE": "Orders Phone", + "ORDERS_PHONE_IS_REQUIRED": "Orders phone is required" + }, + "LOCATION": { + "LOCATION": "Location" + }, + "PAYMENTS": { + "PAYMENTS": "Payments", + "ALLOW_ONLINE_PAYMENT": "Allow online payments?", + "ALLOW_CASH_PAYMENT": "Allow cash payments?", + "STRIPE": { + "PAY_BUTTON_TEXT": "Pay Button text", + "CURRENCY": "Currency", + "PAY_BUTTON_TEXT_IS_REQUIRED": "Pay Button text is required", + "CHOOSE_CURRENCY_CODE": "Choose currency code", + "CURRENCY_TEXT_IS_REQUIRED": "Currency text is required", + "COMPANY_BRAND_LOGO": "Company brand logo", + "COMPANY_BRAND_LOGO_IS_REQUIRED": "Company brand logo is required", + "INVALID_LOGO_URL": "Invalid logo url", + "INVALID_LOGO": "Invalid logo", + "PUBLISHABLE_KEY": "Publishable key", + "PUBLISHABLE_KEY_IS_REQUIRED": "Publishable key is required", + "ALLOW_REMEMBER_ME": "Allow remember me?", + "REMOVE": "Remove" + }, + "PAYPAL": { + "MODE": "Mode", + "CHOOSE_PAYPAL_MODE": "Choose PayPal mode", + "TYPE": "type", + "CURRENCY": "Currency", + "CHOOSE_CURRENCY_CODE": "Choose currency code", + "CURRENCY_TEXT_IS_REQUIRED": "Currency text is required", + "PUBLISHABLE_KEY": "Publishable key", + "PUBLISHABLE_KEY_IS_REQUIRED": "Publishable key is required", + "SECRET_KEY": "Secret key", + "SECRET_KEY_IS_REQUIRED": "Secret key is required", + "PAYMENT_DESCRIPTION": "Payment description", + "PAYMENT_DESCRIPTION_IS_REQUIRED": "Payment description is required" + } + }, + "MANUFACTURING": { + "MANUFACTURING": "Manufacturing", + "PRODUCTS_MANUFACTURING": "Products Manufacturing" + }, + "DELIVERY_AND_TAKEAWAY_SETTINGS": { + "DELIVERY_AND_TAKEAWAY_SETTINGS": "Delivery & Takeaway Settings", + "SELECT_FROM_SHARED_CARRIERS": "Select from shared carriers", + "ADD_YOUR_CARRIER": "Add your carrier", + "EDIT_CARRIER": "Edit carrier", + "CARRIER_REQUIRED": "Carrier required", + "PRODUCTS_DELIVERY_BY_DEFAULT": "Products Delivery (by default)", + "PRODUCTS_TAKEAWAY_BY_DEFAULT": "Products Takeaway (by default)", + "USE_SELECTED_SHARED_CARRIERS": "Use selected shared carriers", + "ADD_YOUR_CARRIERS": "Add your carriers" + }, + "ORDERS_SETTINGS": { + "ORDERS_SETTINGS": "Orders Settings", + "ORDER_BARCODE_QR_CODE_TYPES": "Order Barcode/QR code types" + }, + "PRODUCT_CATEGORIES": { + "PRODUCT_CATEGORIES": "Product categories", + "ADD_OWN_PRODUCT_CATEGORY": "Add own product category" + }, + "PRODUCTS": { + "PRODUCTS": "Products", + "SELECT_FROM_PRODUCTS_CATALOG": "Select from products catalog", + "CREATE_PRODUCT": "Create product", + "EDIT_PRODUCT": "Edit product", + "ADD_PRODUCT": "Add product", + "CREATE_NEW_PRODUCT": "Create new product" + } + } + }, + "WAREHOUSE_MANAGE": { + "TITLE": "Manage Warehouse", + "MANAGE_STORE": "Manage Store", + "SAVE": "SAVE", + "NAME": "Name", + "USERNAME": "Username", + "PASSWORD": "Password", + "COUNTRY": "Country", + "CITY": "City", + "POSTCODE": "Postcode", + "IS_ACTIVE": "Is Active", + "PRODUCTS_MANUFACTURING": "Products Manufacturing", + "CARRIER_REQUIRED": "Carrier required", + "RIGHT_NOW": "(right now)", + "CARRIERS": "Carriers", + "ADDRESS": "Address", + "CARRIERS_SPECIFIC": "Use only specific carriers", + "VALIDATION": { + "NAME": "Name is required", + "USERNAME": "Username is required", + "PASSWORD": "Password is required", + "COUNTRY": "Country is required", + "CITY": "City is required", + "STREET": "Street is required", + "HOUSE": "House number is required", + "POSTCODE": "Postcode is required" + }, + "WIZARD_TITLES": { + "DETAILS": "Details", + "ACCOUNT": "Account", + "CONTACT_INFO": "Contact Info", + "LOCATION": "Location", + "PAYMENT": "Payment", + "DELIVERY_ZONES": "Delivery zones" + } + }, + "WAREHOUSES_VIEW": { + "TITLE": "Stores", + "DELETE_WAREHOUSES": "Delete Selected", + "DELETE": "DELETE", + "CREATE": "CREATE", + "SHOW_ON_MAP": "Show on map", + "SMART_TABLE_COLUMNS": { + "IMAGE": "Image", + "NAME": "Name", + "EMAIL": "Email", + "PHONE": "Phone", + "CITY": "City", + "ADDRESS": "Address", + "ORDERS_QTY": "Orders QTY", + "ORDERS": "Orders" + }, + "INFO": { + "STORE_INFO": "Store Info", + "STORE_ID": "Store ID", + "STORE_NAME": "Store Name" + }, + "MERCHANTS": { + "TRACK_ALL_MERCHANTS": "Track all merchants", + "FILTER_MERCHANTS": "Filter merchants", + "FILTER_BY_NAME": "Filter by name", + "FILTER_BY_CITY": "Filter by city", + "FILTER_BY_COUNTRY": "Filter by country" + } + }, + "WAREHOUSE_VIEW": { + "ORDER": { + "CONFIRM": "Confirm", + "START_PROCESSING": "Start Processing", + "START_ALLOCATION": " Start Allocation", + "ALLOCATED": "Allocated", + "ALLOCATION_FAILS": "Allocation Fails", + "START_PACKAGING": "Start Packaging", + "PACKAGED": "Packaged", + "PACKAGING_FAILS": "Packaging Fails", + "GIVEN_TO_CARRIER": "Given to Carrier", + "GIVEN_TO_CUSTOMER": "Given to Customer", + "ORDER": "Order", + "CANT_PROCESSING_WITHOUT_PRODUCTS": "Can't processing the order without products." + }, + "MUTATION": { + "TITLE": "Register New Store", + "NAME": "Name", + "LOGO": "Logo", + "PHOTO": "Photo", + "IS_ACTIVE": "Is Active", + "ORDERS_SHORT_PROCESS": "Orders Short Process", + "PRODUCTS_MANUFACTURING": "Products Manufacturing", + "CARRIER_REQUIRED": "Carrier required", + "RIGHT_NOW": "Right now", + "ORDERS_PHONE": "Orders Phone", + "CONTACT_PHONE": "Contact Phone", + "ORDERS_EMAIL": "Orders Email", + "CONTACT_EMAIL": "Contact Email", + "FORWARD_ORDERS_WITH": "Forward Orders With", + "USERNAME": "Username", + "OLD_PASSWORD": "Old Password", + "NEW_PASSWORD": "New Password", + "CONFIRM_PASSWORD": "Confirm Password", + "PASSWORDS_DO_NOT_MATCH": "Passwords do not match", + "PASSWORD": "Password", + "COUNTRY": "Country", + "USA": "USA", + "ISRAEL": "Israel", + "BULGARIA": "Bulgaria", + "CITY": "City", + "ADDRESS": "Address", + "POSTCODE": "Postcode", + "COORDINATES": "Coordinates", + "AUTO_DETECT_COORDINATES": "Auto detect coordinates", + "CARRIERS": "Carriers", + "USE_ONLY_SPECIFIC_CARRIERS": "Use only specific carriers", + "SELECT_SHAPE_TO_ADD_ZONE": "Select shape to add a new zone", + "CIRCLE": "Circle", + "SHAPE": "Shape", + "DRAW_SHAPE_ON_MAP": "Draw a shape on the map", + "MINIMUM_AMOUNT": "Minimum amount", + "DELIVERY_FEE": "Delivery fee", + "CANCEL": "Cancel", + "ADD": "Add", + "EDIT": "Edit", + "ZONE_NAME": "Zone name", + "UNALLOWED_ORDER_CANCELATION": "Unallowed Order Cancelation", + "IN_STORE_MODE": "In-store mode", + "ORDER_CANCELATION_OPTIONS": { + "ORDERING": "After Ordering", + "START_PROCESSING": "After Start Processing", + "START_ALLOCATION": "After Start Allocation", + "ALLOCATED": "After Allocated", + "START_PACKAGING": "After Start Packaging", + "PACKAGED": "After Packaged", + "CARRIER_TAKE_WORK": "After Carrier Take Work", + "CARRIER_GOT_IT": "After Carrier Got It", + "CARRIER_START_DELIVERY": "After Carrier Start Delivery", + "DELIVERED": "After Delivered" + }, + "ERRORS": { + "NAME_IS_REQUIRED": "Warehouse name is required", + "NAME_ATLEAST_3_CHARS": "Name must be at least 3 characters long", + "NAME_MORE_THAN_255_CHARS": "Name cannot be more than 255 characters long", + "LOGO_IS_REQUIRED": "Warehouse logo is required", + "INVALID_URL": "Enter a valid logo URL or upload from a device", + "PHONE_CONTAINS_ONLY_DIGIT": "The phone number must only contain digits", + "INVALID_EMAIL": "Invalid Email", + "ORDERS_PHONE_IS_REQUIRED": "Orders Phone is required", + "CONTACT_PHONE_IS_REQUIRED": "Contact Phone is required", + "ORDERS_EMAIL_IS_REQUIRED": "Orders Email is required", + "CONTACT_EMAIL_IS_REQUIRED": "Contact Email is required", + "USERNAME_IS_REQUIRED": "Username is required", + "PASSWORD_IS_REQUIRED": "Password is required", + "COORDINATES_ARE_REQUIRED": "Coordinates are required", + "COUNTRY_IS_REQUIRED": "Country is required" + }, + "WIZARD_TITLES": { + "BASIC_INFO": "Basic Info", + "CONTACT_INFO": "Contact Info", + "LOCATION": "Location" + }, + "CONTACT_INFO_TAB": { + "ORDER_FORWARDING_EMAIL": "Order Forwarding Email", + "ORDER_FORWARDING_PHONE": "Order Forwarding Phone" + } + }, + "NEW_PRODUCT": { + "SELECT_FROM_PRODUCTS_CATALOG": "Select from Products Catalog", + "CREATE_NEW_PRODUCT": "Create new Product", + "HOW_TO_ADD": "How to add", + "ADD": "Add", + "SAVE": "Save", + "ADD_PRODUCTS_TO_STORE": "Add Products to Store", + "NOTHING_FOUND": "Nothing found..." + }, + "SELECT_PRODUCTS": { + "TITLE": "Title", + "DESCRIPTION": "Description", + "DETAILS": "Details", + "IMAGES": "Images", + "CATEGORY": "Category" + }, + "SAVE": { + "PRODUCT_NAME": "Product Name", + "PRICE": "Price", + "COUNT": "Count", + "DELIVERY": "Delivery", + "TAKEAWAY": "Takeaway" + }, + "PLACEHOLDER": { + "EXAMPLE": "Example: Domino's Pizza", + "IMAGE_URL": "Image url", + "HERE_GOES_A_SHORT_DESCRIPTION": "Here goes a short description", + "HERE_GOES_A_DETAILS_ABOUT_PRODUCT_(OPTION)": "Here goes a details about product (option)", + "REMOVE_IMAGE": "Remove image" + }, + "CREATE_ORDER_MODAL": { + "TITLE": { + "CREATE_ORDER": "Create Order" + }, + "WIZARD_FORM": { + "BUTTON_NEXT": "Next", + "BUTTON_PREV": "Back", + "BUTTON_DONE": "Finish Order", + "STEP1": { + "TITLE": "Choose Option", + "SELECT_FROM_EXISTING": "Select From Existing", + "ADD_NEW_CUSTOMER": "Add New Customer" + }, + "STEP2": { + "TITLE": { + "SELECT_CUSTOMER": "Select Customer", + "SELECT_ADD": "Select/Add", + "ADD_NEW": "Add New" + }, + "SMART_TABLE": { + "TITLES": { + "FULL_NAME": "Full Name", + "EMAIL": "Email", + "PHONE": "Phone", + "ADDRESS": "Address" + } + } + }, + "STEP3": { + "TITLE": "Create Order" + } + } + }, + "PRODUCTS_TAB": { + "PRODUCTS": "Products", + "ADD_PRODUCTS": "Add Products", + "DELETE": "Delete", + "IMAGE": "Image", + "TITLE": "Title", + "DESCRIPTION": "Description", + "DETAILS": "Details", + "CATEGORY": "Category", + "PRICE": "Price", + "QUANTITY": "Quantity", + "CLICK_ON_PRODUCT_IMAGE_TO_INCREASE_AVAILABLE_QUANTITY": "Click on Product Image to increase available quantity", + "AVAILABILITY": "Availability", + "TYPE": "Type", + "DELIVERY": "Delivery", + "TAKEAWAY": "Takeaway" + }, + "NEW_PRODUCT_TYPE": "New Type Product", + "ADD_PRODUCTS": "Add Products", + "CREATE_ORDER": "Create Order", + "STATUS": "Status", + "ORDERS": "Orders", + "WAREHOUSE": "Warehouse", + "PRODUCT": "Product", + "PRODUCTS": "Products", + "ORDER_NUMBER": "Order Number", + "CANCELLED": "Cancelled", + "WAREHOUSE_STATUS": "Warehouse Status", + "CARRIER_STATUS": "Carrier Status", + "PAID": "Paid", + "CARRIER": "Carrier", + "CREATED": "Created", + "ELAPSED": "Elapsed", + "CONTACT_DETAILS": "Contact Details", + "EMAIL": "Email", + "PHONE": "Phone", + "ORDERS_FORWARDING_DETAILS": "Orders Forwarding Details", + "ORDERS_FORWARDING_WITH": "Orders Forwarding With", + "MANAGE_STORE": "Manage Store", + "TOP_PRODUCTS": "Top Products", + "PRODUCTS_MANUFACTURING": "Products Manufacturing", + "CARRIER_REQUIRED": "Carrier required", + "MANAGE_WAREHOUSE": "Manage warehouse", + "MANAGE_STORE_PRODUCTS_&_ORDERS": "Manage store products & orders" + }, + "SIMULATION_VIEW": { + "SIMULATION": "Simulation", + "PURCHASE_PRODUCTS": "Purchase products", + "CREATE_INVITE_REQUEST": "Create Invite Request", + "SEND": "Send", + "CREATE_USER": "Create User", + "ORDER_CONFIRM": "Order Confirm", + "ORDER_CANCEL": "Order Cancel", + "PRODUCTS": "Products", + "STORE": "Store", + "ORDER": "Order", + "INVITE_REQUEST": "Invite-Request", + "INVITE_USER": "Invite User", + "TAB_BUTTONS": { + "PRODUCTS": "Products", + "ORDER_HISTORY": "Order history" + }, + "USER_MUTATION": { + "TITLE": "Create Customer", + "NAME": "Name", + "EMAIL": "Email", + "COUNTRY": "Country", + "CITY": "City", + "USA": "USA", + "ISRAEL": "Israel", + "BULGARIA": "Bulgaria", + "ADDRESS": "Address", + "POSTCODE": "Postcode", + "COORDINATES": "Coordinates", + "AUTO_DETECT_COORDINATES": "Auto detect coordinates", + "ERRORS": { + "FIRST_NAME_IS_REQUIRED": "First name is required", + "LAST_NAME_IS_REQUIRED": "Last name is required", + "INVALID_EMAIL": "Invalid Email", + "EMAIL_IS_REQUIRED": "Email is required", + "COORDINATES_ARE_REQUIRED": "Coordinates are required" + }, + "WIZARD_TITLES": { + "ADDITIONAL_INFO": "Additional Info", + "LOCATION": "Location" + } + }, + "SMART_TABLE": { + "TITLE": "Title", + "ID": "Id", + "IMAGE": "Image" + }, + "ORDER_INFO": { + "STATUSES": [ + { + "TITLE": "We're preparing the order!", + "DETAILS": "You will get it in %t minutes.", + "NOT_PAID_NOTE": "Prepare your wallet (%s in cash)." + }, + { + "TITLE": "Carrier on the way!", + "DETAILS": "You will get the order in %t min.", + "NOT_PAID_NOTE": "Prepare your wallet (%s in cash)." + }, + { + "TITLE": "Check your door!", + "DETAILS": "You will get the order in seconds.", + "NOT_PAID_NOTE": "Prepare your wallet (%s in cash)." + }, + { + "TITLE": "Order Completed!", + "DETAILS": "Thanks for using Ever", + "NOT_PAID_NOTE": "" + } + ], + "DELIVERY_STATUS": { + "WE": "We", + "CARRIER": "Carrier", + "YOU": "You" + }, + "DELIVERY_WRONG": { + "DELIVERY_WRONG": "The Delivery Went Wrong!", + "PROCESSING_WRONG": "Processing Went Wrong!", + "TRY_AGAIN": "Please try again.", + "CALL_FOR_DETAILS": "Call for details" + } + }, + "INSTRUCTIONS_STEPS": { + "INSTRUTIONS": "Instructions", + "CREATE_USER_STEP": { + "CREATE_USER": "Create User", + "STEP_1": "Step 1.", + "ORDER": "Order", + "TO_CONTINUE_IS_REQUIRED_TO_REGISTER_IN_THE_SYSTEM": "To continue is required to register in the system", + "CLICK_ON_BUTTON_CREATE_USER": "Click on button 'Create user'", + "FILL_THE_FORM_FOR_ADDITIONAL_INFO": "Fill the form for additional info (optional)", + "FILL_THE_FORM_FOR_LOCATION_AND_PRESS_DONE_BUTTON": "Fill the form for location and press DONE button" + }, + "ORDER_STEP": { + "ORDER": "Order", + "STEP_2": "Step 2.", + "CREATE_ORDER": "Create order", + "CHOICE_SOME_PRODUCTS_FROM_THE_TABLE": "Choice some products from the table (you can see more details about when press on his name or image)", + "SELECT_PRODUCT": "To be selected one product have to press on his row.", + "SELECT_BUTTON_ORDER_TO_CREATE_ORDER": "Press button 'Order' to create order with the selected product.", + "REVIEW_ORDER_HISTORY": "Review order history:", + "ON_PRESS_ORDER_HISTORY_TAB": "On press 'Order history' shows all your orders..", + "HERE_YOU_CAN_SEE_DETAILS_ABOUT_EACH_ORDER": "Here you can see details about each order", + "PRESS_ON_CARRIER_ORDER_OR_PRODUCT_NAME": "Press on carrier, order or product name for more information." + }, + "CONFIRM_OR_CANCEL_STEP": { + "STEP_3": "Step 3", + "CONFIRM_CANCEL_ORDER": "Confirm/Cancel order", + "REAL_TIME": "Real time", + "TRACK_STATUS_ON_YOUR_ORDER": "Track status on your order.", + "ELAPSED_TIME_FROM_CREATE_TO_DELIVERED": "Elapsed time from created to delivered.", + "SHOWS_MERCHANT_LOCATION": "Shows merchant location.", + "SHOWS_CARRIER_LOCATION": "Show carrier location.", + "POSSIBILITIES": "Possibilities:", + "SLIDER_REVIEW_OF_THE_ALL_PRODUCTS": "Slider review of the all products.", + "CAN_CANCEL_IT_WITH_ORDER_CANCEL_BUTTON": "Untill the order is not delivered you user cancel it with 'Order Cancel' button.", + "AFTER_THE_ORDER_IS_DELIVER_USER_CAN_CLICK_BUTTON_ORDER_CONFIRM_TO_CONTINUE": "After the order is delivered the user can click button 'Order Confirm' to continue.." + }, + "INVITE_STEP": { + "TO_CONTINUE_ITs_REQUIRED_TO_BE_INVITED_IN_THE_SYSTEM": "To continue it's required to be invited in the system:", + "SEND_INVITE_REQUEST": "Send 'Invite Request' to the system from the form which will be opened after pressing Invite Request button.", + "ALL_INVITE_REQUESTS_ARE_REVIEWED": "All invite requests are reviewed by admin and they can be invited if the system is available near your location (for test you can do immediately form Invite User button).", + "AFTER_YOU_GET_INVITED_BEFORE": "After you get invite you can easy login the system, just have to enter your invite code, which will be provided from the system (you will see here", + "AFTER_YOU_GET_INVITED_AFTER": "code after press Invite button, and can enter it when press Create User button)." + } + } + }, + "CUSTOMERS_VIEW": { + "TITLE": "Customers", + "DELETE_CUSTOMERS": "Delete", + "CREATE_CUSTOMER": "Create", + "DELETE": "DELETE", + "CREATE": "CREATE", + "BAN": "BAN", + "UNBAN": "UNBAN", + "MANAGE_CUSTOMER": "Manage customer", + "CUSTOMER": "Customer", + "CUSTOMERS_DEVICES": "Devices", + "INVITE": "Invite", + "NOT_INVITED_ONLY": "Not invited only", + "ORDER": "Order", + "ORDERS_STATISTICS": "Orders statistics", + "NUMBER_OF_ORDERS": "Number of Orders", + "CANCELED_ORDERS": "Canceled Orders", + "COMPLETED_ORDERS_TOTAL": "Completed Orders Total", + "Order": "Order", + "CANCEL_ORDER": "Cancel Order", + "CATEGORY": "Category", + "ORDERS_HISTORY": "Orders History", + "AVAILABLE_PRODUCTS": "Available products", + "NEARBY_STORES": "Nearby Stores", + "INVITES_REQUESTS_MANAGEMENT": "Invites requests management", + "INVITES_MANAGEMENT": "Invites management", + "DESCRIPTION": "Description", + "DETAILS": "Details", + "MAKE_A_CUSTOM_ORDER": "Make a custom order", + "PRODUCT_COUNT": "Product Count", + "QUANTITY_CAN'T_BE_EMPTY": "Quantity can't be empty", + "QUANTITY_CAN'T_BE_0": "Quantity can't be 0", + "NOT_ENOUGH_PRODUCTS_AVAILABLE": "Not enough products available", + "ORDER_INFO": "Order Info", + "ORDER_ID": "Order Id", + "STORE_ID": "Store Id", + "CARRIER_ID": "Carrier Id", + "NO_CARRIER": "No Carrier", + "SMART_TABLE_COLUMNS": { + "IMAGE": "Image", + "NAME": "Name", + "EMAIL": "Email", + "PHONE": "Phone", + "ADDRESS": "Address", + "ORDERS_QTY": "Orders QTY", + "COUNTRY": "Country", + "CITY": "City", + "STREET_ADDRESS": "Street Address", + "HOUSE": "House", + "APARTMENT": "Apartment", + "INVITE_CODE": "Invite Code", + "INVITED_DATE": "Invited date", + "ORDER_NUMBER": "Order Number", + "WAREHOUSE": "Warehouse", + "CARRIER": "Carrier", + "PRODUCT_LIST": "Product list", + "STATS": "Stats", + "DELIVERY_TIME": "Delivery Time", + "CREATED": "Created", + "ACTIONS": "Actions", + "PAID": "Paid", + "COMPLETED": "Completed", + "CANCELLED": "Cancelled", + "NOT_DELIVERED": "Not delivered", + "PRODUCT": "Product", + "PRICE": "Price", + "STORE": "Store", + "AVAILABLE_COUNT": "Available Count", + "ORDER": "Order", + "STATUS": "Status" + }, + "EDIT": { + "EDIT_CUSTOMER": "Edit Customer", + "BASIC_INFO": "Basic Info", + "SAVE": "Save" + }, + "DEVICE": { + "ALL_DEVICE": "All Device", + "DEVICE_ID": "Device ID", + "ID": "id", + "UPDATE": "Update", + "LANGUAGE": "language", + "TYPE": "type", + "TYPEU": "Type", + "UUID": "uuid", + "DEVICE_UUID": "Device UUID", + "UPDATE_DEVICE": "Update Device", + "CUSTOMERS_DEVICES": "Customers Devices", + "DELETE": "DELETE", + "CREATE": "CREATE" + }, + "WAREHOUSE": { + "WAREHOUSE_INFO": "Warehouse Info", + "WAREHOUSE_ID": "Warehouse Id", + "WAREHOUSE_NAME": "Warehouse Name" + }, + "CUSTOMER_VIEW": { + "MANAGE_CUSTOMER": "Manage customer", + "EDIT": "EDIT", + "CUSTOMER": "Customer" + }, + "INVITES_VIEW": { + "DELETE": "Delete", + "INVITE": "Invite" + } + }, + "CARRIERS_VIEW": { + "TITLE": "Carriers", + "DELETE_CARRIERS": "Delete Selected", + "CREATE_CARRIER": "Create Carrier", + "DELETE": "DELETE", + "CREATE_BUTTON": "CREATE", + "ACTIVE_AND_AVAILABLE_ORDERS": "Active and Available Orders", + "ORDERS_HISTORY": "Orders History", + "TRACK": "Track", + "SMART_TABLE_COLUMNS": { + "IMAGE": "Image", + "NAME": "Name", + "PHONE": "Phone", + "STATUS": "Status", + "ADDRESS": "Address", + "DELIVERIES": "Deliveries" + }, + "CARRIER_PAGE": { + "WAREHOUSE": "Warehouse", + "CUSTOMER": "Customer", + "SAVE": "Save", + "EDIT": "Edit", + "CARRIER_INFO": "Carrier Info", + "CARRIER_ID": "Carrier Id", + "REGISTER_NEW_CARRIER": "Register New Carrier", + "WAREHOUSE_STATUS": "Warehouse Status", + "CARRIER_STATUS": "Carrier Status", + "CREATED": "Created", + "ARRIVED_TO_CUSTOMER": "Arrived to Customer", + "FAILED": "Failed", + "DELIVERED": "Delivered", + "CLIENT_REFUSE_ORDER": "Client Refuse Order", + "AVAIBLE_ORDER_TO_PICK_UP": "Available Orders to pick up (each carrier can pick up multiple orders)", + "ACTIVE": "Active", + "CARRIER_CAN_BE_SHARED": "Carrier can be shared?", + "NOT_ACTIVE": "Not Active", + "WORKING": "Working", + "NOT_WORKING": "Not Working", + "SELECT_CARRIER": "Select carrier", + "CARRIER_ORDERS_STATUS": "Carrier orders status", + "Start": "Start", + "PICKED_UP_ORDER": "Picked Up Order", + "CANCEL": "Cancel", + "Arrived To Client": "Arrived To Client", + "No Carrier": "No Carrier", + "Order Selected For Delivery": "Order Selected For Delivery", + "Order Picked Up": "Order Picked Up", + "Order In Delivery": "Order In Delivery", + "Delivered": "Delivered", + "Delivery Issues": "Delivery Issues", + "Client Refuse to Take Order": "Client Refuse to Take Order", + "BAD_STATUS": "BAD_STATUS", + "Created": "Created", + "Confirmed": "Confirmed", + "Processing": "Processing", + "Allocation Started": "Allocation Started", + "Allocation Finished": "Allocation Finished", + "Packaging Started": "Packaging Started", + "Packaged": "Packaged", + "Given to Carrier": "Given to Carrier", + "Allocation Failed": "Allocation Failed", + "Packaging Failed": "Packaging Failed", + "LOCATION": "Location", + "TIME": "Time", + "NAME": "Name" + }, + "EDIT": { + "EDIT_CARRIER": "Edit Carrier", + "BASIC_INFO": "Basic Info", + "LOCATION": "Location", + "PHOTO_URL": "Photo Url", + "CONTACT_PHONE": "Contact Phone", + "FIRST_NAME": "First Name", + "LAST_NAME": "Last Name" + }, + "CREATE": { + "BASIC_INFO": "Basic Info", + "LOCATION": "Location" + }, + "TRACK_PAGE": { + "TRACK_ALL_WORKING_CARRIERS": "Track all working carriers", + "FILTER_CARRIERS": "Filter Carriers", + "PHONE": "Phone", + "EMAIL": "Email", + "ADDRESS": "Address", + "DELIVERY_COUNT": "Delivery Count" + } + }, + "PRODUCT_TYPE_VIEW": { + "TITLE": "New Product", + "WIZARD_FORM": { + "VALIDATION_MESSAGES": { + "TITLE": "Title is required", + "THE_LENGHT_OF_THE_TITLE": "The length of title should be max 255 characters long!", + "IMAGE": "Picture is required", + "DESCRIPTION": "Description is required", + "LANGUAGE": "Language is required", + "THE_LENGHT_OF_THE_DESCRIPTION": "The length of description should be max 255 characters long!", + "PRICE": "Price is required", + "COUNT": "Product count is required" + } + } + }, + "SHARED": { + "FORMS": { + "ERRORS": { + "FIRST_NAME_REQUIRED": "First name is required", + "LAST_NAME_REQUIRED": "Last name is required", + "USERNAME_REQUIRED": "Username is required", + "PASSWORD_REQUIRED": "Password is required", + "PHONE_REQUIRED": "Phone is required", + "LOGO_URL_REQUIRED": "Enter valid image URL or browse for a file", + "IS_ACTIVE": "Is active field is required", + "COUNTRY_REQUIRED": "Country is required", + "CITY_REQUIRED": "City is required", + "STREET_ADDRESS_REQUIRED": "Street address is required", + "HOUSE_REQUIRED": "House number is required", + "COORDINATES_REQUIRED": "Coordinates are required", + "MUST_CONTAIN_ONLY_LETTERS": "Must contain only letters", + "PHONE_MUST_CONTAINS_ONLY(specail_signs)AND_DIGIT_CHARACTER": "Phone number can start with '+' or '(some numbers)' and must contains only only: '-, ., (space), #'' and digit characters" + } + }, + "USER": { + "FORMS": { + "BASIC_INFO": { + "TITLE": "Basic Info", + "FIRST_NAME": "First Name", + "FIRST_NAME_OPTIONAL": "First Name (optional)", + "LAST_NAME_OPTIONAL": "Last Name (optional)", + "PHOTO_URL": "Photo Url", + "PICTURE_URL": "Picture url (optional)", + "EMAIL": "Email", + "EMAIL_OPTIONAL": "Email (optional)", + "ERRORS": { + "INVALID_EMAIL": "Invalid Email", + "EMAIL_IS_ALREADY_IN_USE": "Email is already in use" + } + } + } + }, + "WAREHOUSE": { + "ORDER_MODAL": { + "MAKE_ORDER": "Make Order", + "ONLY_AVAILABLE": "Show only available products", + "ORDER": "Order", + "SMART_TABLE": { + "TITLES": { + "IMG": "Img", + "PRODUCT": "Product", + "PRICE": "Price", + "AVAILABLE": "Available", + "AMOUNT": "Amount", + "COMMENT": "Comment" + } + } + } + } + }, + "MENU_VIEW": { + "DASHBOARD": "Dashboard", + "STORES": "Stores", + "PRODUCTS": { + "PRODUCTS": "Products", + "MANAGEMENT": "Management", + "CATEGORIES": "Categories" + }, + "CUSTOMERS": { + "CUSTOMERS": "Customers", + "MANAGEMENT": "Management", + "INVITES": "Invites" + }, + "CARRIERS": "Carriers", + "SIMULATION": "Simulation", + "SETUP": "Setup" + }, + "CATEGORY_VIEW": { + "TITLE": "Title", + "IMAGE": "Image", + "CREATE_BUTTON": "CREATE", + "DELETE": "DELETE", + "EDIT": { + "EDIT_CATEGORY": "Edit Category", + "CATEGORY_NAME": "Category Name", + "ENTER_THE_CATEGORY_NAME": "Enter the category name", + "DONE": "Done" + }, + "CREATE": { + "CREATE_CATEGORY": "Create Category", + "CATEGORY_NAME": "Category Name", + "ENTER_THE_CATEGORY_NAME": "Enter the category name", + "PHOTO": "Photo", + "BROWSE": "Browse", + "INVALID_URL": "Invalid Url", + "REMOVE_IMAGE": "Remove image", + "PHOTO_OPTIONAL": "Photo (optional)", + "DONE": "Done" + } + }, + "PLACEHOLDER": { + "EXAMPLE": "Example: Domino's Pizza", + "IMAGE_URL": "Image url", + "HERE_GOES_A_SHORT_DESCRIPTION": "Here goes a short description", + "HERE_GOES_A_DETAILS_ABOUT_PRODUCT_(OPTION)": "Here goes a details about product (option)", + "REMOVE_IMAGE": "Remove image", + "PASSWORD": "Password", + "LATITUDE": "Latitude", + "LONGITUDE": "Longitude", + "APARTMENT": "Apartment", + "HOUSE": "House", + "STREET": "Street", + "ZIP": "ZIP", + "CITY": "City", + "FIND_ADDRESS": "Find Address" + }, + "STATUS_TEXT": { + "Created": "Created", + "Confirmed": "Confirmed", + "Processing": "Processing", + "Allocation Started": "Allocation Started", + "Allocation Finished": "Allocation Finished", + "Packaging Started": "Packaging Started", + "Packaged": "Packaged", + "Given to Carrier": "Given to Carrier", + "Allocation Failed": "Allocation Failed", + "Packaging Failed": "Packaging Failed", + "No Carrier": "No Carrier", + "Order Selected For Delivery": "Order Selected For Delivery", + "Order Picked Up": "Order Picked Up", + "Order In Delivery": "Order In Delivery", + "Arrived To Client": "Arrived To Client", + "Delivered": "Delivered", + "Delivery Issues": "Delivery Issues", + "Client Refuse to Take Order": "Client Refuse to Take Order", + "Given to Customer": "Given to Customer", + "BAD_STATUS": "BAD_STATUS" + }, + "ELAPSED_TIME": { + "TITLE": "Elapsed time" + }, + "CONFIRM_MODAL": { + "ARE_YOU_SURE": "Are you sure?", + "ARE_YOU_SURE_YOU_WANT_TO_INCREASE": "Are you sure you want to increase the qty of products?", + "ARE_YOU_SURE_YOU_WANT_TO_DECREASE": "Are you sure you want to decrease the qty of products?", + "YES": "Yes", + "NO": "No" + }, + "NO_SERVER_VIEW": { + "NO_SERVER": "Server connection is lost" + }, + "BUTTON_NEXT": "Next", + "BUTTON_PREV": "Back", + "BUTTON_DONE": "Done", + "TERRAIN": "Terrain", + "SATELLITE": "Satellite", + "LOCATION": "Location", + "ROAD_MAP": "Road Map", + "Manage warehouse": "Manage store products & orders", + "Warehouse": "Warehouse", + "Create Warehouse": "Create Warehouse", + "SIMULATION": "Simulation", + "Purchase products": "Purchase products", + "Manage": "Manage", + "Orders": "Orders", + "Confirmed": "Confirmed", + "In Delivery": "In Delivery", + "Not Confirmed": "Not Confirmed", + "Not paid": "Not paid", + "Cancelled": "Cancelled", + "All": "All", + "CANCEL": "Cancel", + "Default Settings": "Default Settings", + "Products Manufacturing": "Products Manufacturing", + "Carrier required before sale": "Carrier required before sale", + "New Product Type": "New Product Type", + "Products": "Products", + "Product": "Product", + "Title": "Title", + "Picture Url": "Picture Url", + "Description": "Description", + "Details": "Details", + "Price": "Price", + "CATEGORY": "Category", + "LANGUAGE": "Language", + "BROWSE": "Browse", + "ENGLISH": "English", + "HEBREW": "Hebrew", + "RUSSIAN": "Russian", + "BULGARIAN": "Bulgarian", + "SPANISH": "Spanish", + "FRENCH": "French", + "SELECT": "Select", + "Name": "Name", + "Id": "Id", + "Warehouse name is required": "Warehouse name is required", + "Name must be at least 1 characters long": "Name must be at least 1 characters long", + "Title cannot be more than 255 characters long": "Title cannot be more than 255 characters long", + "Logo": "Logo", + "Warehouse logo is required": "Warehouse logo is required", + "is Active": "is Active", + "right now": "right now", + "Unselected": "Unselected", + "Phone": "Phone", + "Email": "Email", + "Username": "Username", + "Username is required": "Username is required", + "Password": "Password", + "Country": "Country", + "USA": "USA", + "Israel": "Israel", + "Bulgaria": "Bulgaria", + "City": "City", + "Address": "Address", + "Postcode": "Postcode", + "Coordinates": "Coordinates", + "Auto detect coordinates": "Auto detect coordinates", + "Carriers": "Carriers", + "Carrier": "Carrier", + "Use only specific carriers": "Use only specific carriers", + "Manage carrier and deliveries": "Manage carrier and deliveries", + "Register New Carrier": "Register New Carrier", + "Create User": "Create User", + "OPTIONAL": "optional" +} diff --git a/packages/admin-web-angular/src/assets/i18n/es-ES.json b/packages/admin-web-angular/src/assets/i18n/es-ES.json new file mode 100644 index 0000000..cbe8898 --- /dev/null +++ b/packages/admin-web-angular/src/assets/i18n/es-ES.json @@ -0,0 +1,1162 @@ +{ + "COMMON": { + "SAVE": "Guardar", + "CANCEL": "Cancelar", + "USA": "EEUU", + "ISRAEL": "Israel", + "BULGARIA": "Bulgaria", + "ESPAÑA": "España" + }, + "ORDER_VIEW": { + "ORDER_HEADER_INFO": { + "ORDER": "Pedido", + "MANAGE_ORDER": "Gestionar Pedido", + "TOTAL": "Total" + }, + "ORDER_PRODUCT_INFO": { + "ORDER_PRODUCTS": "Productos en el Pedido", + "ADD_PRODUCTS": "Agregar Productos", + "REMOVE_PRODUCTS": "Quitar Productos", + "CANCEL_ORDER": "Cancelar Pedido", + "THE_ORDER_IS_CANCELED": "Pedido Cancelado", + "THE_ORDER_IS_GIVEN_TO_CARRIER": "Pedido entregado al repartidor", + "THE_ORDER_IS_DELIVERED": "Pedido entregado", + "ADD_PRODUCTS_MODAL": "Agregar Productos", + "ADD": "Agregar", + "SUCCESS_TOAST": "Productos agregados al pedido", + "ERROR_TOAST": "Error, algo ha salido mal.", + "SMART_TABLE": { + "NAME": "Nombre", + "QTY": "Cantidad", + "PRICE": "Precio", + "IMAGE": "Imagen", + "COMMENT": "Comentario" + } + }, + "ORDER_SIDEBAR": { + "CONTACT_DETAILS": "Detalles de contacto", + "WAREHOUSE": "Sucursal", + "CUSTOMER": "Cliente", + "CARRIER": "Reparto", + "QTY": "Cantidad" + }, + "LOCATION_INFO": { + "MAP": "Mapa", + "DELIVERY_DISTANCE": "Distancia de entrega:" + } + }, + "DASHBOARD_VIEW": { + "TOTAL_CUSTOMER": "Total de Clientes", + "TOTAL_QUANTITY_OF_EXISTING_CUSTOMERS": "Cantidad total de clientes existentes", + "TOTAL_COMPLETED_ORDERS": "Total de pedidos completados", + "TOTAL_QUANTITY_OF_COMPLETED_ORDERS": "Cantidad total de pedidos completados", + "TOTAL_REVENUE": "Total de Facturación", + "TOTAL_SUM_SUM_OF_ALL_COMPLETED_ORDERS": "Suma total de todos los pedidos completados completados", + "TODAYs_CUSTOMERS": "Clientes de hoy", + "TODAYs_QUANTITY_OF_REGISTERED_CUSTOMERS": "Cantidad de clientes registrados hoy", + "TODAYs_COMPLETED_ORDERS": "Pedidos completados hoy", + "TODAYs_QUANTITY_OF_NEW_COMPLETED_ORDERS": "Cantidad de nuevos pedidos completados hoy", + "TODAYs_REVENUE": "Facturación de hoy", + "TODAYs_SUM_OF_COMPLETED_ORDERS": "Suma de pedidos completados hoy", + "TILL_AVERAGE": "promedio", + "BETTER_THAN_AVERAGE": "mejor que promedio", + "SELECT_COMPONENT": { + "STORES": "Sucursales", + "CONTACT_DETAILS": "Detalles de Contacto", + "PHONE": "Teléfono", + "EMAIL": "Correo", + "ORDERS_FORWARDING_WITH": "Pedidos Repartidos Por", + "SELECT_STORE": "Seleccionar Sucursal" + }, + "CHARTS": { + "ORDERS": "Pedidos", + "PROFIT": "Renta", + + "TOTAL_ORDERS": "Pedidos", + "TOTAL_COMPLETED_ORDERS": "Pedidos completados", + "TOTAL_CANCELLED_ORDERS": "Pedidos cancelados", + "TOTAL_REVENUE_ALL_ORDERS": "Facturación de pedidos completados y cancelados", + "TOTAL_REVENUE_COMPLETED_ORDERS": "Facturación de pedidos completados", + "TOTAL_LOST_REVENUE_CANCELLED_ORDERS": "Facturación perdida de pedidos cancelados", + + "PAYMENT": "Pago", + "CANCELED": "Cancelado", + "ALL_ORDERS": "Todos los Pedidos", + "TODAY": "Hoy", + "LAST_WEEK": "Ultima Semana", + "LAST_MONTH": "Ultimo Mes", + "CURRENT_YEAR": "Año Actual", + "YEARS": "Por Año", + "CUSTOM_PERIOD": "Período Personalizado", + "SELECT_PERIOD": "Seleccionar Período", + "FROM": "Desde", + "TO": "Hasta", + "SELECT": "Seleccionar", + "LABELS": { + "WEEK": "Semana", + "WEEKDAYS": { + "MON": "Lun", + "TUE": "Mar", + "WED": "Mie", + "THU": "Jue", + "FRI": "Vie", + "SAT": "Sab", + "SUN": "Dom" + }, + "MONTHS": { + "JAN": "Ene", + "FEB": "Feb", + "MAR": "Mar", + "APR": "Abr", + "MAY": "May", + "JUN": "Jun", + "JUL": "Jul", + "AUG": "Ago", + "SEP": "Sep", + "OCT": "Oct", + "NOV": "Nov", + "DEC": "Dic" + } + } + } + }, + "HEADER_VIEW": { + "ADMIN": "Admin", + "EVER": "Ever", + "PROFILE": "Perfil", + "LOG_OUT": "Salir" + }, + "FOOTER_VIEW": { + "COPY_RIGHT": "Copyright © 2016-presente", + "ALL_RIGHTS_RESERVED": "Todos los derechos reservados" + }, + "PROFILE_VIEW": { + "PROFILE_PAGE": "Página de Perfil", + "BASIC_INFO": "Información Básica", + "ACCOUNT": "Cuenta", + "USERNAME": "Usuario", + "ERROR": "Error", + "EMAIL": "Correo", + "FIRST_NAME": "Nombre", + "FIRST_NAME_OPTIONAL": "Nombre (opcional)", + "LAST_NAME": "Apellido (opcional)", + "PICTURE_URL": "Enlace de la Imágen (opcional)", + "BROWSE": "Buscar", + "REMOVE": "Eliminar", + "SAVE": "Guardar", + "OLD_PASSWORD": "Clave actual", + "NEW_PASSWORD": "Clave nueva", + "REPEAT_NEW_PASSWORD": "Repetir clave nueva", + "INVALID_EMAIL_ADDRESS": "Correo inválido", + "INVALID_URL": "Enlace inválido", + "NAME_MUST_CONTAIN_ONLY_LETTERS": "El nombre debe tener sólo letras", + "PASSWORDS_DO_NOT_MATCH": "Las claves no coinciden", + "SUCCESSFULLY_CHANGE_PASSWORD": "Cambio de clave exitoso" + }, + "PRODUCTS_VIEW": { + "DELETE": "ELIMINAR", + "CREATE": "CREAR", + "EDIT_VIEW": { + "EDIT_PRODUCT": "Modificar Producto", + "BASIC_INFO": "Información Básica", + "SAVE": "Guardar" + } + }, + "FAKE_DATA": { + "FAKE_DATA_GENERATOR": "Generador de datos aleatorios", + "GENERATE_ALL": "Generar Todo", + "CREATE_100_USERS": "Crear 100 clientes", + "CREATE_100_CARRIERS": "Crear 100 repartos", + "CREATE_100_WAREHOUSES": "Crear 100 sucursales", + "SETUP": "INICIALIZAR", + "GENERATE_INITIAL_DATA": "Generar Datos Iniciales", + "CREATE_INVITE": "CREAR INVITADO", + "HARDCODED_DATA": "Datos fijos", + "CLEAR_ALL": "LIMPIAR TODO", + "GENERATE_HARDCODED_ONLY": "Genear Sólo Datos Fijos", + "INCLUDED_HARDCODED_DATA": "Incluir Datos Fijos", + "CREATE_1st_INVITE": "Crear 1er invitado", + "CREATE_2st_INVITE": "Crear 2do invitado", + "CREATE_3st_INVITE": "Crear 3er invitado", + "CREATE_4st_INVITE": "Crear 4to invitado", + "CREATE_CUSTOMER": "Crear Cliente", + "CREATE_USER": "CREAR USUARIO", + "CREATE_1st_USER": "Crear 1er usuario (usando 1er invitado)", + "CREATE_CARRIER": "CREAR REPARTO", + "CREATE_1st_CARRIER": "Crear 1er reparto", + "CREATE_2nd_CARRIER": "Crear 2do reparto", + "CREATE_3rd_CARRIER": "Crear 3er reparto", + "CREATE_PRODUCT": "CREAR PRODUCTO", + "CREATE_PEPERONI&MUSHROOM_PIZZA_PRODUCT": "Crear producto pizza de Peperoni & Hongos", + "CREATE_SUSHI&CAVIAR_PRODUCT": "Crear producto Sushi & Caviar", + "CREATE_SUSHI_MIX_PRODUCT": "Crear porducto Sushi Mix", + "CREATE_PASTA_PRODUCT": "Crear producto pasta", + "CREATE_SUSHI_BOX_PRODUCT": "Crear producto caja de Sushi", + "CREATE_PEPERONI&TOMATO_PIZZA_PRODUCT": "Crear producto pizza de Peperoni & Tomate", + "CREATE_WAREHOUSE": "CREAR SUCURSAL", + "CREATE_1st_WAREHOUSE": "Crear 1er sucursal", + "CREATE_2nd_WAREHOUSE": "Crear 2da sucursal", + "CREATE_3rd_WAREHOUSE": "Crear 3er sucursal", + "CREATE_WAREHOUSE_PRODUCT": "CREAR PRODUCTO DE SUCURSAL", + "CREATE_1st_WAREHOUSE_PRODUCTS(using_product_number_1_2_3_4_5_and_6)": "Crear 1er producto de sucursal (usando número de producto 1, 2, 3, 4, 5 y 6)", + "CREATE_3rd_WAREHOUSE_PRODUCTS(using_1st_product)": "Crear 3er producto de sucursal (usando 1er producto)", + "CREATE_2nd_WAREHOUSE_PRODUCTS(using_product_number_1_2_and_3": "Crear 2do producto de sucursal (usando número de producto 1, 2, and 3", + "UPDATE_WAREHOUSE_GEO_LOCATION": "ACTUALIZAR UBICACIÓN EN EL MAPA DE SUCURSAL", + "UPDATE_1ST_WAREHOUSE_GEO_LOCATION": "Actualizar ubicación en el mapa de 1er sucursal", + "CREATE_1RD_ORDER(using_1st_warehouse_1st_user_and_1st_product)": "Crear 1er pedido (usando 1er sucursal, 1er usuario, 1er producto)", + "CREATE_2ND_ORDER(using_1st_warehouse_1st_user_and_2nd_product)": "Crear 2do pedido (usando 1er sucursal, 1er usuario y 2do producto)", + "CONFIRM_ORDER": "CONFIRMAR PEDIDO", + "CREATE_ORDER": "CREAR PEDIDO", + "CONFIMR_1st_ORDER": "Confirmar 1er pedido", + "CONFIMR_2nd_ORDER": "Confirmar 2do pedido", + "SETUP_MERCHANTS": { + "SETUP_MERCHANTS": "Inicializar restaurantes", + "PREV": "ANT", + "NEXT": "SIG", + "ADD": "Agregar", + "BACK": "Atrás", + "SAVE": "Guardar", + "SELECT": "Seleccionar", + "CREATE": "Crear", + "HOW_TO_SET_UP": { + "HOW_TO_SET_UP": "Cómo inicializar" + }, + "STEPPER": { + "ACCOUNT": "Cuenta", + "BASIC_INFO": "Información básica", + "CONTACT_INFO": "Información de Contacto", + "LOCATION": "Ubicación", + "PAYMENTS": "Pagos", + "MANUFACTURING": "Elaboración", + "DELIVERY_AND_TAKEAWAY_SETTINGS": "Configuraciones de Reparto & Retiro", + "ORDERS_SETTINGS": "Configuraciones de Pedidos", + "PRODUCT_CATEGORIES": "Categorías de Producto", + "PRODUCTS": "Productos" + }, + "ACCOUNT": { + "ACCOUNT": "Cuenta", + "EMAIL_ADDRESS": "Correo", + "EMAIL": "Correo", + "PASSWORD": "Clave", + "REPEAT_PASSWORD": "Repetir clave", + "EMAIL_IS_REQUIRED": "Correo requerido", + "INVALID_EMAIL_FORMAT": "Formato de correo inválido", + "USERNAME": "Usuario", + "USERNAME_IS_REQUIRED": "Usuario requerido", + "USERNAME_MUST_BE_AT_LEAST_3_CHARACTERS": "El usuario debe tener al menos 3 caractéres", + "PASSWORD_IS_REQUIRED": "Clave requerida", + "PASSWORD_MUST_BE_AT_LEAST_4_CHARACTERS": "La clave debe tener al menos 4 caractéres", + "REPEAT_PASSWORD_IS_REQUIRED": "Repetir la clave requerido", + "PASSWORDS_DO_NOT_MATCH": "Claves no coinciden" + }, + "BASIC_INFO": { + "BASIC_INFO": "Información básica", + "NAME": "Nombre", + "NAME_IS_REQUIRED": "Nombre requerido", + "NAME_MUST_BE_AT_LEAST_4_CHARACTERS": "El nombre debe tener al menos 4 caractéres", + "PHOTO": "Foto", + "ENTER_A_VALID_LOGO_URL_OR_BROWSE_FROM_A_DEVICE": "Ingrese un enlace del logo válido o busque en el dispositivo", + "REMOVE": "Eliminar", + "PHOTO_OPTIONAL": "Foto (opcional)", + "BARCODE_DATA": "Código de barras", + "BARCODE_DATA_IS_REQUIRED": "Código de barras requerido" + }, + "CONTACT_INFO": { + "CONTACT_INFO": "Información de contacto", + "CONTACT_PHONE": "Teléfono de contacto", + "INVALID_PHONE_NUMBER_FORMAT": "Formato de teléfono inválido", + "ORDER_FORWARDING_EMAIL": "Correo de reparto de pedido", + "ORDER_FORWARDING_PHONE": "Teléfono de reparto de pedido", + "ORDERS_EMAIL": "Correo de pedidos", + "ORDERS_EMAIL_IS_REQUIRED": "Correo de pedidos requerido", + "INVALID_EMAIL_FORMAT": "Formato de correo inválido", + "ORDERS_PHONE": "Teléfono de pedidos", + "ORDERS_PHONE_IS_REQUIRED": "Teléfono de pedidos es requerido" + }, + "LOCATION": { + "LOCATION": "Ubicación" + }, + "PAYMENTS": { + "PAYMENTS": "Pagos", + "ALLOW_ONLINE_PAYMENT": "¿Permitir pagos online?", + "ALLOW_CASH_PAYMENT": "¿Permitir pagos en efectivo?", + "STRIPE": { + "PAY_BUTTON_TEXT": "Texto de botón de pago", + "CURRENCY": "Moneda", + "PAY_BUTTON_TEXT_IS_REQUIRED": "Texto de botón de pago requerido", + "CHOOSE_CURRENCY_CODE": "Elegir código de moneda", + "CURRENCY_TEXT_IS_REQUIRED": "Código de moneda requerido", + "COMPANY_BRAND_LOGO": "Logo de la compañía", + "COMPANY_BRAND_LOGO_IS_REQUIRED": "Logo de la compañía requerido", + "INVALID_LOGO_URL": "Enlace del logo inválido", + "INVALID_LOGO": "Logo inválido", + "PUBLISHABLE_KEY": "Llave publicable", + "PUBLISHABLE_KEY_IS_REQUIRED": "Llave publicable requerida", + "ALLOW_REMEMBER_ME": "Permitir recordarme?", + "REMOVE": "Eliminar" + }, + "PAYPAL": { + "MODE": "Modo", + "CHOOSE_PAYPAL_MODE": "Elegir modo de PayPal", + "TYPE": "tipo", + "CURRENCY": "Moneda", + "CHOOSE_CURRENCY_CODE": "Elegir código de moneda", + "CURRENCY_TEXT_IS_REQUIRED": "Código de moneda requerido", + "PUBLISHABLE_KEY": "Llave publicable", + "PUBLISHABLE_KEY_IS_REQUIRED": "Llave publicable requerida", + "SECRET_KEY": "Llave secreta", + "SECRET_KEY_IS_REQUIRED": "Llave secreta requerida", + "PAYMENT_DESCRIPTION": "Descripción del pago", + "PAYMENT_DESCRIPTION_IS_REQUIRED": "Descripción del pago requerida" + } + }, + "MANUFACTURING": { + "MANUFACTURING": "Elaboración", + "PRODUCTS_MANUFACTURING": "Elaboración de Productos" + }, + "DELIVERY_AND_TAKEAWAY_SETTINGS": { + "DELIVERY_AND_TAKEAWAY_SETTINGS": "Configuraciones de Reparto & Retiro", + "SELECT_FROM_SHARED_CARRIERS": "Seleccionar de repartos compartidos", + "ADD_YOUR_CARRIER": "Agregar tu reparto", + "EDIT_CARRIER": "Modificar reparto", + "CARRIER_REQUIRED": "Reparto requerido", + "PRODUCTS_DELIVERY_BY_DEFAULT": "Repartir (por defecto)", + "PRODUCTS_TAKEAWAY_BY_DEFAULT": "Retirar (por defecto)", + "USE_SELECTED_SHARED_CARRIERS": "Usar repartos compartidos seleccionados", + "ADD_YOUR_CARRIERS": "Agregar tus repartos" + }, + "ORDERS_SETTINGS": { + "ORDERS_SETTINGS": "Configuraciones de pedido", + "ORDER_BARCODE_QR_CODE_TYPES": "Pedido del tipo código de barras/código QR" + }, + "PRODUCT_CATEGORIES": { + "PRODUCT_CATEGORIES": "Categorías de producto", + "ADD_OWN_PRODUCT_CATEGORY": "Agregar categoría de producto propia" + }, + "PRODUCTS": { + "PRODUCTS": "Productos", + "SELECT_FROM_PRODUCTS_CATALOG": "Seleccionar del catálogo de productos", + "CREATE_PRODUCT": "Crear producto", + "EDIT_PRODUCT": "Modificar producto", + "ADD_PRODUCT": "Agregar producto", + "CREATE_NEW_PRODUCT": "Crear nuevo producto" + } + } + }, + "WAREHOUSE_MANAGE": { + "TITLE": "Gestionar Sucursal", + "MANAGE_STORE": "Gestionar Sucursal", + "SAVE": "Guardar", + "NAME": "Nombre", + "USERNAME": "Usuario", + "PASSWORD": "Clave", + "COUNTRY": "País", + "CITY": "Ciudad", + "POSTCODE": "Código Postal", + "IS_ACTIVE": "Está activo", + "PRODUCTS_MANUFACTURING": "Elaboración de Productos", + "CARRIER_REQUIRED": "Reparto requerido", + "RIGHT_NOW": "(ahora)", + "CARRIERS": "Repartos", + "ADDRESS": "Dirección", + "CARRIERS_SPECIFIC": "Usar sólo repartos específicos", + "VALIDATION": { + "NAME": "Nombre requerido", + "USERNAME": "Usuario requerido", + "PASSWORD": "Clave requerida", + "COUNTRY": "Country is required", + "CITY": "Ciudad requerida", + "STREET": "Calle requerida", + "HOUSE": "Número de puerta requerido", + "POSTCODE": "Código Postal requerido" + }, + "WIZARD_TITLES": { + "DETAILS": "Detalles", + "ACCOUNT": "Cuenta", + "CONTACT_INFO": "Información de Contacto", + "LOCATION": "Ubicación", + "PAYMENT": "Pago", + "DELIVERY_ZONES": "Zonas de entrega" + } + }, + "WAREHOUSES_VIEW": { + "TITLE": "Sucursales", + "DELETE_WAREHOUSES": "Eliminar Seleccionadas", + "DELETE": "ELIMINAR", + "CREATE": "CREAR", + "SHOW_ON_MAP": "Mostrar en el mapa", + "SMART_TABLE_COLUMNS": { + "IMAGE": "Imágen", + "NAME": "Nombre", + "EMAIL": "Correo", + "PHONE": "Teléfono", + "CITY": "Ciudad", + "ADDRESS": "Dirección", + "ORDERS_QTY": "Cantidad de pedidos", + "ORDERS": "Pedidos" + }, + "INFO": { + "STORE_INFO": "Información de Sucursal", + "STORE_ID": "ID de Sucursal", + "STORE_NAME": "Nombre de Sucursal" + }, + "MERCHANTS": { + "TRACK_ALL_MERCHANTS": "Rastrear a todas las comerciantes", + "FILTER_MERCHANTS": "Filtrar comerciantes", + "FILTER_BY_NAME": "Filtrar por nombre", + "FILTER_BY_CITY": "Filtrar por ciudad", + "FILTER_BY_COUNTRY": "Filtrar por país" + } + }, + "WAREHOUSE_VIEW": { + "ORDER": { + "CONFIRM": "Confirmar", + "START_PROCESSING": "Empezar a Procesar", + "START_ALLOCATION": "Empezar Asignación", + "ALLOCATED": "Asignado", + "ALLOCATION_FAILS": "Asignación Fallida", + "START_PACKAGING": "Empezar a Embalar", + "PACKAGED": "Embalado", + "PACKAGING_FAILS": "Embalado Fallido", + "GIVEN_TO_CARRIER": "Entregado al Repartidor", + "GIVEN_TO_CUSTOMER": "Entregado al Cliente", + "ORDER": "Pedido", + "CANT_PROCESSING_WITHOUT_PRODUCTS": "No se puede procesar el pedido sin productos." + }, + "MUTATION": { + "TITLE": "Registrar Nueva Sucursal", + "NAME": "Nombre", + "LOGO": "Logo", + "PHOTO": "Foto", + "IS_ACTIVE": "Está Activa", + "PRODUCTS_MANUFACTURING": "Elaboración de Productos", + "CARRIER_REQUIRED": "Reparto requerido", + "RIGHT_NOW": "Ahora mismo", + "ORDERS_PHONE": "Teléfono de pedidos", + "CONTACT_PHONE": "Teléfono de contacto", + "ORDERS_EMAIL": "Correo de pedidos", + "CONTACT_EMAIL": "Teléfono de contacto", + "FORWARD_ORDERS_WITH": "Repartir Pedidos Con", + "USERNAME": "Usuario", + "OLD_PASSWORD": "Clave actual", + "NEW_PASSWORD": "Clave nueva", + "CONFIRM_PASSWORD": "Repetir calve nueva", + "PASSWORDS_DO_NOT_MATCH": "Las claves no coinciden", + "PASSWORD": "Clave", + "COUNTRY": "País", + "USA": "EEUU", + "ISRAEL": "Israel", + "BULGARIA": "Bulgaria", + "ESPAÑA": "España", + "CITY": "Ciudad", + "ADDRESS": "Dirección", + "POSTCODE": "Código Postal", + "COORDINATES": "Coordenadas", + "AUTO_DETECT_COORDINATES": "Auto detectar coordenadas", + "CARRIERS": "Repartos", + "USE_ONLY_SPECIFIC_CARRIERS": "Usar sólo repartos específicos", + "SELECT_SHAPE_TO_ADD_ZONE": "Seleccione forma para agregar una nueva zona", + "CIRCLE": "Circulo", + "SHAPE": "Forma", + "DRAW_SHAPE_ON_MAP": "Dibuja una forma en el mapa", + "MINIMUM_AMOUNT": "Monto minimo", + "DELIVERY_FEE": "Gastos de envío", + "CANCEL": "Cancelar", + "ADD": "Añadir", + "EDIT": "Editar", + "ZONE_NAME": "Nombre de zona", + "IN_STORE_MODE": "En modo tienda", + "ERRORS": { + "NAME_IS_REQUIRED": "Nombre de sucursal requerido", + "NAME_ATLEAST_3_CHARS": "El nombre debe tener al menos 3 caractéres", + "NAME_MORE_THAN_255_CHARS": "El nombre no puede tener más de 255 caractéres", + "LOGO_IS_REQUIRED": "Logo de sucursal requerido", + "INVALID_URL": "Ingresar un enlace del logo válido o subir desde el dispositivo", + "PHONE_CONTAINS_ONLY_DIGIT": "El teléfono sólo puede contener dígitos", + "INVALID_EMAIL": "Correo inválido", + "ORDERS_PHONE_IS_REQUIRED": "Teléfono del pedido requerido", + "CONTACT_PHONE_IS_REQUIRED": "Teléfono de contacto requerido", + "ORDERS_EMAIL_IS_REQUIRED": "Correo del pedido requerido", + "CONTACT_EMAIL_IS_REQUIRED": "Correo de contacto requerido", + "USERNAME_IS_REQUIRED": "Usuario requerido", + "PASSWORD_IS_REQUIRED": "Clave requerida", + "COORDINATES_ARE_REQUIRED": "Coordenadas requeridas", + "COUNTRY_IS_REQUIRED": "País requerido" + }, + "WIZARD_TITLES": { + "BASIC_INFO": "Información básica", + "CONTACT_INFO": "Información de contacto", + "LOCATION": "Ubicación" + }, + "CONTACT_INFO_TAB": { + "ORDER_FORWARDING_EMAIL": "Correo de reparto de pedidos", + "ORDER_FORWARDING_PHONE": "Teléfono de reparto de pedidos" + } + }, + "NEW_PRODUCT": { + "SELECT_FROM_PRODUCTS_CATALOG": "Seleccionar del catálogo de productos", + "CREATE_NEW_PRODUCT": "Crear nuevo producto", + "HOW_TO_ADD": "Cómo agregar", + "ADD": "Agregar", + "SAVE": "Guardar", + "ADD_PRODUCTS_TO_STORE": "Agregar Productos a la Sucursal", + "NOTHING_FOUND": "Nada encontrado..." + }, + "SELECT_PRODUCTS": { + "TITLE": "Título", + "DESCRIPTION": "Descripción", + "DETAILS": "Detalles", + "IMAGES": "Imágenes", + "CATEGORY": "Categoría" + }, + "SAVE": { + "PRODUCT_NAME": "Nombre del Producto", + "PRICE": "Precio", + "COUNT": "Cantidad", + "DELIVERY": "Repartir", + "TAKEAWAY": "Retirar" + }, + "PLACEHOLDER": { + "EXAMPLE": "Ejemplo: Lo de Pepe", + "IMAGE_URL": "Enlace a la imágen", + "HERE_GOES_A_SHORT_DESCRIPTION": "Aquí va una breve descripción", + "HERE_GOES_A_DETAILS_ABOUT_PRODUCT_(OPTION)": "Aquí van detalles del producto (opcional)", + "REMOVE_IMAGE": "Eliminar imágen" + }, + "CREATE_ORDER_MODAL": { + "TITLE": { + "CREATE_ORDER": "Crear Pedido" + }, + "WIZARD_FORM": { + "BUTTON_NEXT": "Siguiente", + "BUTTON_PREV": "Anterior", + "BUTTON_DONE": "Crear", + "STEP1": { + "TITLE": "Elegir Opción", + "SELECT_FROM_EXISTING": "Elegir Existente", + "ADD_NEW_CUSTOMER": "Agregar Nuevo Cliente" + }, + "STEP2": { + "TITLE": { + "SELECT_CUSTOMER": "Seleccionar Cliente", + "SELECT_ADD": "Seleccionar/Agregar", + "ADD_NEW": "Agregar Nuevo" + }, + "SMART_TABLE": { + "TITLES": { + "FULL_NAME": "Nombre", + "EMAIL": "Correo", + "PHONE": "Teléfono", + "ADDRESS": "Dirección" + } + } + }, + "STEP3": { + "TITLE": "Crear Pedido" + } + } + }, + "PRODUCTS_TAB": { + "PRODUCTS": "Productos", + "ADD_PRODUCTS": "Agregar Productos", + "DELETE": "Eliminar", + "IMAGE": "Imágen", + "TITLE": "Título", + "DESCRIPTION": "Descripción", + "DETAILS": "Detalles", + "CATEGORY": "Categoría", + "PRICE": "Precio", + "QUANTITY": "Cantidad", + "CLICK_ON_PRODUCT_IMAGE_TO_INCREASE_AVAILABLE_QUANTITY": "Presiona en la imágen del producto para incrementar su cantidad disponible", + "AVAILABILITY": "Disponibilidad", + "TYPE": "Tipo", + "DELIVERY": "Entrega", + "TAKEAWAY": "Para llevar" + }, + "NEW_PRODUCT_TYPE": "Nuevo tipo de producto", + "ADD_PRODUCTS": "Agregar Productos", + "CREATE_ORDER": "Crear Pedido", + "STATUS": "Estado", + "ORDERS": "Pedidos", + "WAREHOUSE": "Sucursal", + "PRODUCT": "Producto", + "PRODUCTS": "Productos", + "ORDER_NUMBER": "Número de Pedido", + "CANCELLED": "Cancelado", + "WAREHOUSE_STATUS": "Estado en Sucursal", + "CARRIER_STATUS": "Estado en el Repartidor", + "PAID": "Pago", + "CARRIER": "Reparto", + "CREATED": "Creado", + "ELAPSED": "Transcurrido", + "CONTACT_DETAILS": "Detalles de Contacto", + "EMAIL": "Correo", + "PHONE": "Teléfono", + "ORDERS_FORWARDING_DETAILS": "Detalles de Reparto del Pedido", + "ORDERS_FORWARDING_WITH": "Repartir Pedido con", + "MANAGE_STORE": "Gestionar Sucursal", + "TOP_PRODUCTS": "Productos Top", + "PRODUCTS_MANUFACTURING": "Elaboración de Productos", + "CARRIER_REQUIRED": "Reparto requerido", + "MANAGE_WAREHOUSE": "Gestionar Sucursal", + "MANAGE_STORE_PRODUCTS_&_ORDERS": "Gestionar Productos & Pedidos de Sucursal" + }, + "SIMULATION_VIEW": { + "SIMULATION": "Simulación", + "PURCHASE_PRODUCTS": "Comprar productos", + "CREATE_INVITE_REQUEST": "Crear invitación", + "SEND": "Enviar", + "CREATE_USER": "Crear Usuario", + "ORDER_CONFIRM": "Confirmar Pedido", + "ORDER_CANCEL": "Cancelar Pedido", + "PRODUCTS": "Productos", + "STORE": "Sucursal", + "ORDER": "Pedido", + "INVITE_REQUEST": "Invitación", + "INVITE_USER": "Usuario de Invitado", + "TAB_BUTTONS": { + "PRODUCTS": "Productos", + "ORDER_HISTORY": "Historial de pedidos" + }, + "USER_MUTATION": { + "TITLE": "Crear Cliente", + "NAME": "Nombre", + "EMAIL": "Correo", + "COUNTRY": "País", + "CITY": "Ciudad", + "USA": "EEUU", + "ISRAEL": "Israel", + "BULGARIA": "Bulgaria", + "ESPAÑA": "España", + "ADDRESS": "Dirección", + "POSTCODE": "Código Postal", + "COORDINATES": "Coordenadas", + "AUTO_DETECT_COORDINATES": "Auto detectar coordenadas", + "ERRORS": { + "FIRST_NAME_IS_REQUIRED": "Nombre requerido", + "LAST_NAME_IS_REQUIRED": "Apellido requerido", + "INVALID_EMAIL": "Correo Inválido", + "EMAIL_IS_REQUIRED": "Correo requerido", + "COORDINATES_ARE_REQUIRED": "Coordenadas requeridas" + }, + "WIZARD_TITLES": { + "ADDITIONAL_INFO": "Información Adicional", + "LOCATION": "Ubiación" + } + }, + "SMART_TABLE": { + "TITLE": "Título", + "ID": "Id", + "IMAGE": "Imágen" + }, + "ORDER_INFO": { + "STATUSES": [ + { + "TITLE": "Estamos preparando el pedido", + "DETAILS": "Recibirás tu pedido en %t minutos", + "NOT_PAID_NOTE": "Ten listo %s en efectivo" + }, + { + "TITLE": "Repartidor en camino", + "DETAILS": "Recibirás tu pedido en %t minutos", + "NOT_PAID_NOTE": "Ten listo %s en efectivo" + }, + { + "TITLE": "Ve a la puerta", + "DETAILS": "Estás a punto de recibir tu pedido", + "NOT_PAID_NOTE": "Ten listo %s en efectivo" + }, + { + "TITLE": "Pedido Entregado", + "DETAILS": "Gracias por elegirnos", + "NOT_PAID_NOTE": "" + } + ], + "DELIVERY_STATUS": { + "WE": "Nosotros", + "CARRIER": "Reparto", + "YOU": "Tu" + }, + "DELIVERY_WRONG": { + "DELIVERY_WRONG": "La entrega falló", + "PROCESSING_WRONG": "La preparación falló", + "TRY_AGAIN": "Por favor intenta de nuevo", + "CALL_FOR_DETAILS": "Llama por detalles" + } + }, + "INSTRUCTIONS_STEPS": { + "INSTRUTIONS": "Instrucciones", + "CREATE_USER_STEP": { + "CREATE_USER": "Crear Usuario", + "STEP_1": "Paso 1.", + "ORDER": "Pedido", + "TO_CONTINUE_IS_REQUIRED_TO_REGISTER_IN_THE_SYSTEM": "Para continuar se requiere registrarse", + "CLICK_ON_BUTTON_CREATE_USER": "Presiona el botón 'Crear Usuario'", + "FILL_THE_FORM_FOR_ADDITIONAL_INFO": "Llena el formulario para información adicional (opcional)", + "FILL_THE_FORM_FOR_LOCATION_AND_PRESS_DONE_BUTTON": "Llena el formulario para ubicación y presiona el botón CREAR" + }, + "ORDER_STEP": { + "ORDER": "Pedido", + "STEP_2": "Paso 2.", + "CREATE_ORDER": "Crear pedido", + "CHOICE_SOME_PRODUCTS_FROM_THE_TABLE": "Selecciona productos del menú, podrás ver detalles cuando presiones su nombre o imágen.", + "SELECT_PRODUCT": "Para seleccionar un producto debes presionar en su fila.", + "SELECT_BUTTON_ORDER_TO_CREATE_ORDER": "Presiona el botón 'Pedir' para crear pedido con el producto seleccionado.", + "REVIEW_ORDER_HISTORY": "Revisar historial de pedidos:", + "ON_PRESS_ORDER_HISTORY_TAB": "Presionando 'Historial de Pedidos' muestra tus pedidos pasados...", + "HERE_YOU_CAN_SEE_DETAILS_ABOUT_EACH_ORDER": "Aquí puedes ver detalles de cada pedido", + "PRESS_ON_CARRIER_ORDER_OR_PRODUCT_NAME": "Presiona sobre reparto, pedido o nombre de producto para más información." + }, + "CONFIRM_OR_CANCEL_STEP": { + "STEP_3": "Paso 3", + "CONFIRM_CANCEL_ORDER": "Confirmar/Cancelar pedido", + "REAL_TIME": "Tiempo/Seguimiento", + "TRACK_STATUS_ON_YOUR_ORDER": "Seguir estado de tu pedido.", + "ELAPSED_TIME_FROM_CREATE_TO_DELIVERED": "Tiempo transcurrido desde creado hasta entregado.", + "SHOWS_MERCHANT_LOCATION": "Mostrar ubicación del comercio.", + "SHOWS_CARRIER_LOCATION": "Mostrar ubicación del repartidor.", + "POSSIBILITIES": "Possibilidades:", + "SLIDER_REVIEW_OF_THE_ALL_PRODUCTS": "Revisión de todos los productos.", + "CAN_CANCEL_IT_WITH_ORDER_CANCEL_BUTTON": "Hasta que el pedido no está entregado puede cancelarlo con el botón 'Cancelar Pedido'.", + "AFTER_THE_ORDER_IS_DELIVER_USER_CAN_CLICK_BUTTON_ORDER_CONFIRM_TO_CONTINUE": "Luego de que el pedido ha sido entregado el usuario puede presionar el botón de 'Confirmar Pedido' para continuar..." + }, + "INVITE_STEP": { + "TO_CONTINUE_ITs_REQUIRED_TO_BE_INVITED_IN_THE_SYSTEM": "Para continuar se requiere estar invitado:", + "SEND_INVITE_REQUEST": "Invitar al sistema a través del formulario de Invitación.", + "ALL_INVITE_REQUESTS_ARE_REVIEWED": "Todas las solicitudes de invitación son revisadas por el admin y pueden ser realizadas si el servicio está disponible próximo a tu ubicación (para pruebas basta con el botón de Invitar Usuario).", + "AFTER_YOU_GET_INVITED_BEFORE": "Luego de ser invitado puedes ingresar al sistema ingresando el código de invitación que es provisto por el sistema, lo verás aquí.", + "AFTER_YOU_GET_INVITED_AFTER": "Código luego de presionar el botón de Invitar, puedes ingresarlo presionando el botón de Crear Usuario." + } + } + }, + "CUSTOMERS_VIEW": { + "TITLE": "Clientes", + "DELETE_CUSTOMERS": "Eliminar", + "CREATE_CUSTOMER": "Crear", + "DELETE": "ELIMINAR", + "CREATE": "CREAR", + "BAN": "BLOQUEAR", + "UNBAN": "DESBLOQUEAR", + "MANAGE_CUSTOMER": "Gestionar cliente", + "CUSTOMER": "Cliente", + "CUSTOMERS_DEVICES": "Dispositivos", + "INVITE": "Invitación", + "NOT_INVITED_ONLY": "No sólo invitado", + "ORDER": "Pedido", + "ORDERS_STATISTICS": "Estadístias del pedido", + "NUMBER_OF_ORDERS": "Pedidos", + "CANCELED_ORDERS": "Pedidos Cancelados", + "COMPLETED_ORDERS_TOTAL": "Pedidos Completados", + "Order": "Pedido", + "CANCEL_ORDER": "Cancelar Pedido", + "CATEGORY": "Categoría", + "ORDERS_HISTORY": "Historial de Pedidos", + "AVAILABLE_PRODUCTS": "Productos disponibles", + "NEARBY_STORES": "Comercios cercanos", + "INVITES_REQUESTS_MANAGEMENT": "Gestión de invitaciones", + "INVITES_MANAGEMENT": "Gestión de invitados", + "DESCRIPTION": "Descripción", + "DETAILS": "Detalles", + "MAKE_A_CUSTOM_ORDER": "Crear pedido personalizado", + "PRODUCT_COUNT": "Conteo de Productos", + "QUANTITY_CAN'T_BE_EMPTY": "La cantidad no puede estar vacía", + "QUANTITY_CAN'T_BE_0": "La cantidad no puede ser 0", + "NOT_ENOUGH_PRODUCTS_AVAILABLE": "No hay productos suficientes disponibles", + "ORDER_INFO": "Información de Pedido", + "ORDER_ID": "Id de Pedido", + "STORE_ID": "Id de Comercio", + "CARRIER_ID": "Id de Reparto", + "NO_CARRIER": "Sin Reparto", + "SMART_TABLE_COLUMNS": { + "IMAGE": "Imágen", + "NAME": "Nombre", + "EMAIL": "Correo", + "PHONE": "Teléfono", + "ADDRESS": "Dirección", + "ORDERS_QTY": "Cantidad de Pedidos", + "COUNTRY": "País", + "CITY": "Ciudad", + "STREET_ADDRESS": "Calle", + "HOUSE": "Número de puerta", + "APARTMENT": "Apartamento", + "INVITE_CODE": "Código de Invitación", + "INVITED_DATE": "Fecha de Invitación", + "ORDER_NUMBER": "Número de Pedido", + "WAREHOUSE": "Sucursal", + "CARRIER": "Reparto", + "PRODUCT_LIST": "Lista de Productos", + "STATS": "Estadísticas", + "DELIVERY_TIME": "Tiempo de entrega", + "CREATED": "Creada", + "ACTIONS": "Acciones", + "PAID": "Pago", + "COMPLETED": "Completado", + "CANCELLED": "Cancelado", + "NOT_DELIVERED": "No entregado", + "PRODUCT": "Producto", + "PRICE": "Precio", + "STORE": "Comercio", + "AVAILABLE_COUNT": "Cantidad disponible", + "ORDER": "Pedido", + "STATUS": "Estado" + }, + "EDIT": { + "EDIT_CUSTOMER": "Modificar Cliente", + "BASIC_INFO": "Información Básica", + "SAVE": "Guardar" + }, + "DEVICE": { + "ALL_DEVICE": "Todos los dispositivos", + "DEVICE_ID": "ID de dispositivo", + "ID": "id", + "UPDATE": "Actualizar", + "LANGUAGE": "idioma", + "TYPE": "tipo", + "TYPEU": "Tipo", + "UUID": "uuid", + "DEVICE_UUID": "UUID de dispositivo", + "UPDATE_DEVICE": "Actualizar dispositivo", + "CUSTOMERS_DEVICES": "Dispositivo de Cliente", + "DELETE": "ELIMINAR", + "CREATE": "CREAR" + }, + "WAREHOUSE": { + "WAREHOUSE_INFO": "Información de Sucursal", + "WAREHOUSE_ID": "Id de Sucursal", + "WAREHOUSE_NAME": "Nombre de Sucursal" + }, + "CUSTOMER_VIEW": { + "MANAGE_CUSTOMER": "Gestionar cliente", + "EDIT": "MODIFICAR", + "CUSTOMER": "Cliente" + }, + "INVITES_VIEW": { + "DELETE": "Eliminar", + "INVITE": "Invitación" + } + }, + "CARRIERS_VIEW": { + "TITLE": "Reparto", + "DELETE_CARRIERS": "Eliminar Selección", + "CREATE_CARRIER": "Crear Reparto", + "DELETE": "ELIMINAR", + "CREATE_BUTTON": "CREAR", + "ACTIVE_AND_AVAILABLE_ORDERS": "Pedidos Activos y Disponibles", + "ORDERS_HISTORY": "Historial de Pedidos", + "TRACK": "Seguimiento", + "SMART_TABLE_COLUMNS": { + "IMAGE": "Imágen", + "NAME": "Nombre", + "PHONE": "Teléfono", + "STATUS": "Estado", + "ADDRESS": "Dirección", + "DELIVERIES": "Repartos" + }, + "CARRIER_PAGE": { + "WAREHOUSE": "Sucursal", + "CUSTOMER": "Cliente", + "SAVE": "Guardar", + "EDIT": "Modificar", + "CARRIER_INFO": "Información de Reparto", + "CARRIER_ID": "Id de Reparto", + "REGISTER_NEW_CARRIER": "Registrar Nuevo Reparto", + "WAREHOUSE_STATUS": "Estado en Sucursal", + "CARRIER_STATUS": "Estado en el Repartidor", + "CREATED": "Creado", + "ARRIVED_TO_CUSTOMER": "En el Cliente", + "FAILED": "Fallido", + "DELIVERED": "Entregado", + "CLIENT_REFUSE_ORDER": "Cliente Rechaza Pedido", + "AVAIBLE_ORDER_TO_PICK_UP": "Pedidos Disponibles para Llevar (cada repartidor puede llevar varios pedidos)", + "ACTIVE": "Activo", + "CARRIER_CAN_BE_SHARED": "Repartidor puede ser compartido?", + "NOT_ACTIVE": "No Activo", + "WORKING": "Trabajando", + "NOT_WORKING": "No Trabajando", + "SELECT_CARRIER": "Seleccionar Reparto", + "CARRIER_ORDERS_STATUS": "Estado de Pedidos del Reparto", + "Start": "Comenzar", + "PICKED_UP_ORDER": "Pedido levantado", + "CANCEL": "Cancelar", + "Arrived To Client": "En el Cliente", + "No Carrier": "Sin Reparto", + "Order Selected For Delivery": "Pedido para Repartir", + "Order Picked Up": "Pedido Retirado", + "Order In Delivery": "Pedido en el Repartidor", + "Delivered": "Entregado", + "Delivery Issues": "Problemas de Entrega", + "Client Refuse to Take Order": "Cliente Rechaza el Pedido", + "BAD_STATUS": "ESTADO ERRONEO", + "Created": "Creado", + "Confirmed": "Confirmado", + "Processing": "Procesando", + "Allocation Started": "Asignación Comenzó", + "Allocation Finished": "Asignado", + "Packaging Started": "Embalado Comenzó", + "Packaged": "Embalado", + "Given to Carrier": "Entregado al Repartidor", + "Allocation Failed": "Asignación Falló", + "Packaging Failed": "Embalado Falló", + "LOCATION": "Ubicación", + "TIME": "Tiempo", + "NAME": "Nombre" + }, + "EDIT": { + "EDIT_CARRIER": "Modificar Reparto", + "BASIC_INFO": "Información Básica", + "LOCATION": "Ubicación", + "PHOTO_URL": "Enlace a Foto", + "CONTACT_PHONE": "Teléfono de Contacto", + "FIRST_NAME": "Nombre", + "LAST_NAME": "Apellido" + }, + "CREATE": { + "BASIC_INFO": "Información Básica", + "LOCATION": "Ubicación" + }, + "TRACK_PAGE": { + "TRACK_ALL_WORKING_CARRIERS": "Seguir todos los repartidores trabajando", + "FILTER_CARRIERS": "Filtrar Repartos", + "PHONE": "Teléfono", + "EMAIL": "Correo", + "ADDRESS": "Dirección", + "DELIVERY_COUNT": "Conteo de Repartos" + } + }, + "PRODUCT_TYPE_VIEW": { + "TITLE": "Nuevo Producto", + "WIZARD_FORM": { + "VALIDATION_MESSAGES": { + "TITLE": "Título requerido", + "THE_LENGHT_OF_THE_TITLE": "El largo del título debe tener como máximo 255 caractéres.", + "IMAGE": "Imágen requerida", + "DESCRIPTION": "Descripción requerida", + "LANGUAGE": "Idioma requerido", + "THE_LENGHT_OF_THE_DESCRIPTION": "El largo de la descripción debe tener como máximo 255 caractéres", + "PRICE": "Precio requerido", + "COUNT": "Cantidad de unidades del producto requerida" + } + } + }, + "SHARED": { + "FORMS": { + "ERRORS": { + "FIRST_NAME_REQUIRED": "Nombre requerido", + "LAST_NAME_REQUIRED": "Apellido requerido", + "USERNAME_REQUIRED": "Usuario requerido", + "PASSWORD_REQUIRED": "Clave requerida", + "PHONE_REQUIRED": "Teléfono requerido", + "LOGO_URL_REQUIRED": "Enlace al logo o archivo del dispositivo requerido", + "IS_ACTIVE": "Campo 'Está Activo' requerido", + "COUNTRY_REQUIRED": "País Requerido", + "CITY_REQUIRED": "Ciudad Requerida", + "STREET_ADDRESS_REQUIRED": "Dirección Requerida", + "HOUSE_REQUIRED": "Número de puerta requerido", + "COORDINATES_REQUIRED": "Coordenadas requeridas", + "MUST_CONTAIN_ONLY_LETTERS": "Debe contener sólo letras", + "PHONE_MUST_CONTAINS_ONLY(specail_signs)AND_DIGIT_CHARACTER": "El teléfono puede comenzar con '+' o '(números)' y debe contener sólo: -, ., (espacio), y dígitos" + } + }, + "USER": { + "FORMS": { + "BASIC_INFO": { + "TITLE": "Información Básica", + "FIRST_NAME": "Nombre", + "FIRST_NAME_OPTIONAL": "Nombre (opcional)", + "LAST_NAME_OPTIONAL": "Apellido (opcional)", + "PHOTO_URL": "Enlace a foto", + "PICTURE_URL": "Enlace a imágen (opcional)", + "EMAIL": "Correo", + "EMAIL_OPTIONAL": "Correo (opcional)", + "ERRORS": { + "INVALID_EMAIL": "Correo Inválido", + "EMAIL_IS_ALREADY_IN_USE": "El correo ya se encuentra en uso" + } + } + } + }, + "WAREHOUSE": { + "ORDER_MODAL": { + "MAKE_ORDER": "Realizar Pedido", + "ONLY_AVAILABLE": "Mostras sólo productos disponibles", + "ORDER": "Pedido", + "SMART_TABLE": { + "TITLES": { + "IMG": "Imágen", + "PRODUCT": "Producto", + "PRICE": "Precio", + "AVAILABLE": "Disponible", + "AMOUNT": "Cantidad", + "COMMENT": "Comentario" + } + } + } + } + }, + "MENU_VIEW": { + "DASHBOARD": "Tablero", + "STORES": "Sucursales", + "PRODUCTS": { + "PRODUCTS": "Productos", + "MANAGEMENT": "Gestión", + "CATEGORIES": "Categorías" + }, + "CUSTOMERS": { + "CUSTOMERS": "Clientes", + "MANAGEMENT": "Gestión", + "INVITES": "Invitaciones" + }, + "CARRIERS": "Repartos", + "SIMULATION": "Simulación", + "SETUP": "Inicializar" + }, + "CATEGORY_VIEW": { + "TITLE": "Título", + "IMAGE": "Imágen", + "CREATE_BUTTON": "CREAR", + "DELETE": "ELIMINAR", + "EDIT": { + "EDIT_CATEGORY": "Modificar Categoría", + "CATEGORY_NAME": "Nombre de Categoría", + "ENTER_THE_CATEGORY_NAME": "Ingresar nombre de categoría", + "DONE": "Realizado" + }, + "CREATE": { + "CREATE_CATEGORY": "Crear Categoría", + "CATEGORY_NAME": "Nombre de Categoría", + "ENTER_THE_CATEGORY_NAME": "Ingresar nombre de categoría", + "PHOTO": "Imágen", + "BROWSE": "Buscar", + "INVALID_URL": "Enlace inválido", + "REMOVE_IMAGE": "Eliminar imágen", + "PHOTO_OPTIONAL": "Imágen (opcional)", + "DONE": "Realizado" + } + }, + "PLACEHOLDER": { + "EXAMPLE": "Example: Lo de Pepe", + "IMAGE_URL": "Enlace a Imágen", + "HERE_GOES_A_SHORT_DESCRIPTION": "Aquí va una breve descripción", + "HERE_GOES_A_DETAILS_ABOUT_PRODUCT_(OPTION)": "Aquí van detalles del producto (opcional)", + "REMOVE_IMAGE": "Eliminar imágen", + "PASSWORD": "Clave", + "LATITUDE": "Latitud", + "LONGITUDE": "Longitud", + "APARTMENT": "Apartamento", + "HOUSE": "Número de puerta", + "STREET": "Calle", + "ZIP": "Código Postal", + "CITY": "Ciudad", + "FIND_ADDRESS": "Buscar Dirección" + }, + "STATUS_TEXT": { + "Created": "Creado", + "Confirmed": "Confirmado", + "Processing": "Procesando", + "Allocation Started": "Asignación Comenzó", + "Allocation Finished": "Asignado", + "Packaging Started": "Embalado Comenzó", + "Packaged": "Embalado", + "Given to Carrier": "Entregado al Repartidor", + "Allocation Failed": "Asignación Falló", + "Packaging Failed": "Embalado Falló", + "No Carrier": "Sin Reparto", + "Order Selected For Delivery": "Pedido Seleccionado para Repartir", + "Order Picked Up": "Pedido Retirado", + "Order In Delivery": "Pedido en el Repartidor", + "Arrived To Client": "Pedido en Cliente", + "Delivered": "Entregado", + "Delivery Issues": "Problemas de Entrega", + "Client Refuse to Take Order": "Cliente Rechaza el Pedido", + "Given to Customer": "Entregado al Cliente", + "BAD_STATUS": "ESTADO ERRONEO" + }, + "ELAPSED_TIME": { + "TITLE": "Tiempo transcurrido" + }, + "CONFIRM_MODAL": { + "ARE_YOU_SURE": "Está seguro?", + "ARE_YOU_SURE_YOU_WANT_TO_INCREASE": "Seguro que desea incrementar la cantidad de unidades del producto?", + "ARE_YOU_SURE_YOU_WANT_TO_DECREASE": "Seguro que desea decrementar la cantidad de unidades del producto?", + "YES": "Si", + "NO": "No" + }, + "NO_SERVER_VIEW": { + "NO_SERVER": "La conexión con el servidor se ha perdido" + }, + "BUTTON_NEXT": "Siguiente", + "BUTTON_PREV": "Anterior", + "BUTTON_DONE": "Realizado", + "TERRAIN": "Terreno", + "SATELLITE": "Satelite", + "LOCATION": "Ubicación", + "ROAD_MAP": "Mapa del trayecto", + "Manage warehouse": "Gestionar productos y pedidos de la sucursal", + "Warehouse": "Sucursal", + "Create Warehouse": "Crear Sucursal", + "SIMULATION": "Simulación", + "Purchase products": "Comprar productos", + "Manage": "Gestionar", + "Orders": "Pedidos", + "Confirmed": "Confirmado", + "In Delivery": "En Reparto", + "Not Confirmed": "No Confirmado", + "Not paid": "No pago", + "Cancelled": "Cancelado", + "All": "Todos", + "CANCEL": "Cancelar", + "Default Settings": "Configuración por Defecto", + "Products Manufacturing": "Elaboración de Productos", + "Carrier required before sale": "Reparto requerido antes de la venta", + "New Product Type": "Nuevo Tipo de Producto", + "Products": "Productos", + "Product": "Producto", + "Title": "Título", + "Picture Url": "Enlace de Imágen", + "Description": "Descripción", + "Details": "Detalles", + "Price": "Precio", + "CATEGORY": "Categoría", + "LANGUAGE": "Idioma", + "BROWSE": "Buscar", + "ENGLISH": "Inglés", + "HEBREW": "Hebreo", + "RUSSIAN": "Ruso", + "BULGARIAN": "Búlgaro", + "ESPAÑOL": "Español", + "SPANISH": "Español", + "FRENCH": "Francés", + "SELECT": "Seleccionar", + "Name": "Nombre", + "Id": "Id", + "Warehouse name is required": "Nombre de Sucursal requerido", + "Name must be at least 1 characters long": "Nombre debe tener al menos 1 carcter", + "Title cannot be more than 255 characters long": "El título no pude tener más de 255 caractéres", + "Logo": "Logo", + "Warehouse logo is required": "Logo de Sucursal requerido", + "is Active": "está Activo", + "right now": "ahora mismo", + "Unselected": "Sin seleccionar", + "Phone": "Teléfono", + "Email": "Correo", + "Username": "Usuario", + "Username is required": "Usuario requerido", + "Password": "Clave", + "Country": "País", + "USA": "EEUU", + "Israel": "Israel", + "Bulgaria": "Bulgaria", + "ESPAÑA": "España", + "City": "Ciudad", + "Address": "Dirección", + "Postcode": "Código Postal", + "Coordinates": "Coordenadas", + "Auto detect coordinates": "Autodetectar coordenadas", + "Carriers": "Repartos", + "Carrier": "Reparto", + "Use only specific carriers": "Usar sólo repartos específicos", + "Manage carrier and deliveries": "Gestionar repartos", + "Register New Carrier": "Registar Nuevo Reparto", + "Create User": "Crear Usuario", + "OPTIONAL": "opcional" +} diff --git a/packages/admin-web-angular/src/assets/i18n/fr-FR.json b/packages/admin-web-angular/src/assets/i18n/fr-FR.json new file mode 100644 index 0000000..2997a24 --- /dev/null +++ b/packages/admin-web-angular/src/assets/i18n/fr-FR.json @@ -0,0 +1,1170 @@ +{ + "COMMON": { + "SAVE": "Save", + "CANCEL": "Cancel", + "USA": "USA", + "ISRAEL": "Israel", + "BULGARIA": "Bulgaria" + }, + "ORDER_VIEW": { + "ORDER_HEADER_INFO": { + "ORDER": "Order", + "MANAGE_ORDER": "Manage Order", + "TOTAL": "Total" + }, + "ORDER_PRODUCT_INFO": { + "ORDER_PRODUCTS": "Order Products", + "ADD_PRODUCTS": "Add products", + "REMOVE_PRODUCTS": "Remove Products", + "CANCEL_ORDER": "Cancel Order", + "THE_ORDER_IS_CANCELED": "The order is canceled", + "THE_ORDER_IS_GIVEN_TO_CARRIER": "The order is given to carrier.", + "THE_ORDER_IS_DELIVERED": "The order is delivered.", + "ADD_PRODUCTS_MODAL": "Add Products", + "ADD": "Add", + "SUCCESS_TOAST": "Products were added to the order", + "ERROR_TOAST": "Error, something went wrong", + "SMART_TABLE": { + "NAME": "Name", + "QTY": "QTY", + "PRICE": "Price", + "IMAGE": "Image", + "COMMENT": "Comment" + } + }, + "ORDER_SIDEBAR": { + "CONTACT_DETAILS": "Contact Details", + "WAREHOUSE": "Warehouse", + "CUSTOMER": "Customer", + "CARRIER": "Carrier", + "QTY": "qty" + }, + "LOCATION_INFO": { + "MAP": "Map", + "DELIVERY_DISTANCE": "Delivery Distance:" + } + }, + "DASHBOARD_VIEW": { + "TOTAL_CUSTOMER": "Total Customers", + "TOTAL_QUANTITY_OF_EXISTING_CUSTOMERS": "Total quantity of existing customers", + "TOTAL_COMPLETED_ORDERS": "Total completed orders", + "TOTAL_QUANTITY_OF_COMPLETED_ORDERS": "Total quantity of completed orders", + "TOTAL_REVENUE": "Total Revenue", + "TOTAL_SUM_SUM_OF_ALL_COMPLETED_ORDERS": "Total sum of all completed orders", + "TODAYs_CUSTOMERS": "Today's Customers", + "TODAYs_QUANTITY_OF_REGISTERED_CUSTOMERS": "Today's quantity of registered customers", + "TODAYs_COMPLETED_ORDERS": "Today's completed orders", + "TODAYs_QUANTITY_OF_NEW_COMPLETED_ORDERS": "Today's quantity of new completed orders", + "TODAYs_REVENUE": "Today's Revenue", + "TODAYs_SUM_OF_COMPLETED_ORDERS": "Today's sum of completed orders", + "TILL_AVERAGE": "till average", + "BETTER_THAN_AVERAGE": "better than average", + "SELECT_COMPONENT": { + "STORES": "Stores", + "CONTACT_DETAILS": "Contact Details", + "PHONE": "Phone", + "EMAIL": "Email", + "ORDERS_FORWARDING_WITH": "Orders Forwarding With", + "SELECT_STORE": "Select store" + }, + "CHARTS": { + "ORDERS": "Orders", + "PROFIT": "Profit", + "TOTAL_ORDERS": "Total orders", + "TOTAL_COMPLETED_ORDERS": "Total completed orders", + "TOTAL_CANCELLED_ORDERS": "Total canceled orders", + "TOTAL_REVENUE_ALL_ORDERS": "Total revenue from all orders", + "TOTAL_REVENUE_COMPLETED_ORDERS": "Total revenue from completed orders", + "TOTAL_LOST_REVENUE_CANCELLED_ORDERS": "Total lost revenue from canceled orders", + "PAYMENT": "Payment", + "CANCELED": "Canceled", + "ALL_ORDERS": "All Orders", + "TODAY": "Today", + "LAST_WEEK": "Last Week", + "LAST_MONTH": "Last Month", + "CURRENT_YEAR": "Current Year", + "YEARS": "By Year", + "CUSTOM_PERIOD": "Custom Period", + "SELECT_PERIOD": "Select Period", + "FROM": "From", + "TO": "To", + "SELECT": "Select", + "LABELS": { + "WEEK": "Week", + "WEEKDAYS": { + "MON": "Mon", + "TUE": "Tue", + "WED": "Wed", + "THU": "Thu", + "FRI": "Fri", + "SAT": "Sat", + "SUN": "Sun" + }, + "MONTHS": { + "JAN": "Jan", + "FEB": "Feb", + "MAR": "Mar", + "APR": "Apr", + "MAY": "May", + "JUN": "Jun", + "JUL": "Jul", + "AUG": "Aug", + "SEP": "Sep", + "OCT": "Oct", + "NOV": "Nov", + "DEC": "Dec" + } + } + } + }, + "HEADER_VIEW": { + "ADMIN": "Admin", + "EVER": "Ever", + "PROFILE": "Profile", + "LOG_OUT": "Log out" + }, + "FOOTER_VIEW": { + "COPY_RIGHT": "Copyright © 2016-present", + "ALL_RIGHTS_RESERVED": "All rights reserved" + }, + "PROFILE_VIEW": { + "PROFILE_PAGE": "Profile page", + "BASIC_INFO": "Basic Info", + "ACCOUNT": "Account", + "USERNAME": "Username", + "ERROR": "Error", + "EMAIL": "Email", + "FIRST_NAME": "First Name", + "FIRST_NAME_OPTIONAL": "First Name (optional)", + "LAST_NAME": "Last Name (optional)", + "PICTURE_URL": "Picture url (optional)", + "BROWSE": "Browse", + "REMOVE": "Remove", + "SAVE": "Save", + "OLD_PASSWORD": "Old password", + "NEW_PASSWORD": "New password", + "REPEAT_NEW_PASSWORD": "Repeat new password", + "INVALID_EMAIL_ADDRESS": "Invalid email address", + "INVALID_URL": "Invalid URL", + "NAME_MUST_CONTAIN_ONLY_LETTERS": "Name must contain only letters", + "PASSWORDS_DO_NOT_MATCH": "Passwords do not match", + "SUCCESSFULLY_CHANGE_PASSWORD": "Successfully change password" + }, + "PRODUCTS_VIEW": { + "DELETE": "DELETE", + "CREATE": "CREATE", + "EDIT_VIEW": { + "EDIT_PRODUCT": "Edit Product", + "BASIC_INFO": "Basic Info", + "SAVE": "Save" + } + }, + "FAKE_DATA": { + "FAKE_DATA_GENERATOR": "Fake Data Generator", + "GENERATE_ALL": "Generate All", + "CREATE_100_USERS": "Create 100 customers", + "CREATE_100_CARRIERS": "Create 100 carriers", + "CREATE_100_WAREHOUSES": "Create 100 warehouses", + "SETUP": "SETUP", + "GENERATE_INITIAL_DATA": "Generate Initial Data", + "CREATE_INVITE": "CREATE INVITE", + "HARDCODED_DATA": "Hardcoded data", + "CLEAR_ALL": "CLEAR ALL", + "GENERATE_HARDCODED_ONLY": "Generate Hardcoded Only", + "INCLUDED_HARDCODED_DATA": "Include hardcoded data", + "CREATE_1st_INVITE": "Create 1st invite", + "CREATE_2st_INVITE": "Create 2nd invite", + "CREATE_3st_INVITE": "Create 3rd invite", + "CREATE_4st_INVITE": "Create 4rd invite", + "CREATE_CUSTOMER": "Create Customer", + "CREATE_USER": "CREATE USER", + "CREATE_1st_USER": "Create 1st user (using 1st invite)", + "CREATE_CARRIER": "CREATE CARRIER", + "CREATE_1st_CARRIER": "Create 1st carrier", + "CREATE_2nd_CARRIER": "Create 2nd carrier", + "CREATE_3rd_CARRIER": "Create 3rd carrier", + "CREATE_PRODUCT": "CREATE PRODUCT", + "CREATE_PEPERONI&MUSHROOM_PIZZA_PRODUCT": "Create Peperoni & Mushroom pizza product", + "CREATE_SUSHI&CAVIAR_PRODUCT": "Create Sushi & Caviar product", + "CREATE_SUSHI_MIX_PRODUCT": "Create Sushi mix product", + "CREATE_PASTA_PRODUCT": "Create Pasta product", + "CREATE_SUSHI_BOX_PRODUCT": "Create Sushi box product", + "CREATE_PEPERONI&TOMATO_PIZZA_PRODUCT": "Create Peperoni & Tomato pizza product", + "CREATE_WAREHOUSE": "CREATE WAREHOUSE", + "CREATE_1st_WAREHOUSE": "Create 1st warehouse", + "CREATE_2nd_WAREHOUSE": "Create 2nd warehouse", + "CREATE_3rd_WAREHOUSE": "Create 3rd warehouse", + "CREATE_WAREHOUSE_PRODUCT": "CREATE WAREHOUSE PRODUCT", + "CREATE_1st_WAREHOUSE_PRODUCTS(using_product_number_1_2_3_4_5_and_6)": "Create 1st warehouse products (using product number 1, 2, 3, 4, 5 and 6)", + "CREATE_3rd_WAREHOUSE_PRODUCTS(using_1st_product)": "Create 3rd warehouse products (using 1st product)", + "CREATE_2nd_WAREHOUSE_PRODUCTS(using_product_number_1_2_and_3": "Create 2nd warehouse products (using product number 1, 2, and 3", + "UPDATE_WAREHOUSE_GEO_LOCATION": "UPDATE WAREHOUSE GEO LOCATION", + "UPDATE_1ST_WAREHOUSE_GEO_LOCATION": "Update 1st warehouse geo location", + "CREATE_1RD_ORDER(using_1st_warehouse_1st_user_and_1st_product)": "Create 1rd order (using 1st warehouse, 1st user and 1st product)", + "CREATE_2ND_ORDER(using_1st_warehouse_1st_user_and_2nd_product)": "Create 2nd order (using 1st warehouse, 1st user and 2nd product)", + "CONFIRM_ORDER": "CONFIRM ORDER", + "CREATE_ORDER": "CREATE ORDER", + "CONFIMR_1st_ORDER": "Confirm 1st order", + "CONFIMR_2nd_ORDER": "Confirm 2nd order", + "SETUP_MERCHANTS": { + "SETUP_MERCHANTS": "Setup merchants", + "PREV": "PREV", + "NEXT": "NEXT", + "ADD": "Add", + "BACK": "Back", + "SAVE": "Save", + "SELECT": "Select", + "CREATE": "Create", + "HOW_TO_SET_UP": { + "HOW_TO_SET_UP": "How to setup" + }, + "STEPPER": { + "ACCOUNT": "Account", + "BASIC_INFO": "Basic info", + "CONTACT_INFO": "Contact info", + "LOCATION": "Location", + "PAYMENTS": "Payments", + "MANUFACTURING": "Manufacturing", + "DELIVERY_AND_TAKEAWAY_SETTINGS": "Delivery & Takeaway Settings", + "ORDERS_SETTINGS": "Orders Settings", + "PRODUCT_CATEGORIES": "Product categories", + "PRODUCTS": "Products" + }, + "ACCOUNT": { + "ACCOUNT": "Account", + "EMAIL_ADDRESS": "Email address", + "EMAIL": "Email", + "PASSWORD": "Password", + "REPEAT_PASSWORD": "Repeat password", + "EMAIL_IS_REQUIRED": "Email is required", + "INVALID_EMAIL_FORMAT": "Invalid email format", + "USERNAME": "Username", + "USERNAME_IS_REQUIRED": "Username is required", + "USERNAME_MUST_BE_AT_LEAST_3_CHARACTERS": "Username must be at least 3 characters", + "PASSWORD_IS_REQUIRED": "Password is required", + "PASSWORD_MUST_BE_AT_LEAST_4_CHARACTERS": "Password must be at least 4 characters", + "REPEAT_PASSWORD_IS_REQUIRED": "Repeat password is required", + "PASSWORDS_DO_NOT_MATCH": "Passwords do not match" + }, + "BASIC_INFO": { + "BASIC_INFO": "Basic info", + "NAME": "Name", + "NAME_IS_REQUIRED": "Name is required", + "NAME_MUST_BE_AT_LEAST_4_CHARACTERS": "Name must be at least 4 characters", + "PHOTO": "Photo", + "ENTER_A_VALID_LOGO_URL_OR_BROWSE_FROM_A_DEVICE": "Enter a valid logo URL or browse from a device", + "REMOVE": "Remove", + "PHOTO_OPTIONAL": "Photo (optional)", + "BARCODE_DATA": "Barcode Data", + "BARCODE_DATA_IS_REQUIRED": "Barcode Data is required" + }, + "CONTACT_INFO": { + "CONTACT_INFO": "Contact info", + "CONTACT_PHONE": "Contact Phone", + "INVALID_PHONE_NUMBER_FORMAT": "Invalid phone number format", + "ORDER_FORWARDING_EMAIL": "Order Forwarding Email", + "ORDER_FORWARDING_PHONE": "Order Forwarding Phone", + "ORDERS_EMAIL": "Orders Email", + "ORDERS_EMAIL_IS_REQUIRED": "Orders email is required", + "INVALID_EMAIL_FORMAT": "Invalid email format", + "ORDERS_PHONE": "Orders Phone", + "ORDERS_PHONE_IS_REQUIRED": "Orders phone is required" + }, + "LOCATION": { + "LOCATION": "Location" + }, + "PAYMENTS": { + "PAYMENTS": "Payments", + "ALLOW_ONLINE_PAYMENT": "Allow online payments?", + "ALLOW_CASH_PAYMENT": "Allow cash payments?", + "STRIPE": { + "PAY_BUTTON_TEXT": "Pay Button text", + "CURRENCY": "Currency", + "PAY_BUTTON_TEXT_IS_REQUIRED": "Pay Button text is required", + "CHOOSE_CURRENCY_CODE": "Choose currency code", + "CURRENCY_TEXT_IS_REQUIRED": "Currency text is required", + "COMPANY_BRAND_LOGO": "Company brand logo", + "COMPANY_BRAND_LOGO_IS_REQUIRED": "Company brand logo is required", + "INVALID_LOGO_URL": "Invalid logo url", + "INVALID_LOGO": "Invalid logo", + "PUBLISHABLE_KEY": "Publishable key", + "PUBLISHABLE_KEY_IS_REQUIRED": "Publishable key is required", + "ALLOW_REMEMBER_ME": "Allow remember me?", + "REMOVE": "Remove" + }, + "PAYPAL": { + "MODE": "Mode", + "CHOOSE_PAYPAL_MODE": "Choose PayPal mode", + "TYPE": "type", + "CURRENCY": "Currency", + "CHOOSE_CURRENCY_CODE": "Choose currency code", + "CURRENCY_TEXT_IS_REQUIRED": "Currency text is required", + "PUBLISHABLE_KEY": "Publishable key", + "PUBLISHABLE_KEY_IS_REQUIRED": "Publishable key is required", + "SECRET_KEY": "Secret key", + "SECRET_KEY_IS_REQUIRED": "Secret key is required", + "PAYMENT_DESCRIPTION": "Payment description", + "PAYMENT_DESCRIPTION_IS_REQUIRED": "Payment description is required" + } + }, + "MANUFACTURING": { + "MANUFACTURING": "Manufacturing", + "PRODUCTS_MANUFACTURING": "Products Manufacturing" + }, + "DELIVERY_AND_TAKEAWAY_SETTINGS": { + "DELIVERY_AND_TAKEAWAY_SETTINGS": "Delivery & Takeaway Settings", + "SELECT_FROM_SHARED_CARRIERS": "Select from shared carriers", + "ADD_YOUR_CARRIER": "Add your carrier", + "EDIT_CARRIER": "Edit carrier", + "CARRIER_REQUIRED": "Carrier required", + "PRODUCTS_DELIVERY_BY_DEFAULT": "Products Delivery (by default)", + "PRODUCTS_TAKEAWAY_BY_DEFAULT": "Products Takeaway (by default)", + "USE_SELECTED_SHARED_CARRIERS": "Use selected shared carriers", + "ADD_YOUR_CARRIERS": "Add your carriers" + }, + "ORDERS_SETTINGS": { + "ORDERS_SETTINGS": "Orders Settings", + "ORDER_BARCODE_QR_CODE_TYPES": "Order Barcode/QR code types" + }, + "PRODUCT_CATEGORIES": { + "PRODUCT_CATEGORIES": "Product categories", + "ADD_OWN_PRODUCT_CATEGORY": "Add own product category" + }, + "PRODUCTS": { + "PRODUCTS": "Products", + "SELECT_FROM_PRODUCTS_CATALOG": "Select from products catalog", + "CREATE_PRODUCT": "Create product", + "EDIT_PRODUCT": "Edit product", + "ADD_PRODUCT": "Add product", + "CREATE_NEW_PRODUCT": "Create new product" + } + } + }, + "WAREHOUSE_MANAGE": { + "TITLE": "Manage Warehouse", + "MANAGE_STORE": "Manage Store", + "SAVE": "SAVE", + "NAME": "Name", + "USERNAME": "Username", + "PASSWORD": "Password", + "COUNTRY": "Country", + "CITY": "City", + "POSTCODE": "Postcode", + "IS_ACTIVE": "Is Active", + "PRODUCTS_MANUFACTURING": "Products Manufacturing", + "CARRIER_REQUIRED": "Carrier required", + "RIGHT_NOW": "(right now)", + "CARRIERS": "Carriers", + "ADDRESS": "Address", + "CARRIERS_SPECIFIC": "Use only specific carriers", + "VALIDATION": { + "NAME": "Name is required", + "USERNAME": "Username is required", + "PASSWORD": "Password is required", + "COUNTRY": "Country is required", + "CITY": "City is required", + "STREET": "Street is required", + "HOUSE": "House number is required", + "POSTCODE": "Postcode is required" + }, + "WIZARD_TITLES": { + "DETAILS": "Details", + "ACCOUNT": "Account", + "CONTACT_INFO": "Contact Info", + "LOCATION": "Location", + "PAYMENT": "Payment", + "DELIVERY_ZONES": "Delivery zones" + } + }, + "WAREHOUSES_VIEW": { + "TITLE": "Stores", + "DELETE_WAREHOUSES": "Delete Selected", + "DELETE": "DELETE", + "CREATE": "CREATE", + "SHOW_ON_MAP": "Show on map", + "SMART_TABLE_COLUMNS": { + "IMAGE": "Image", + "NAME": "Name", + "EMAIL": "Email", + "PHONE": "Phone", + "CITY": "City", + "ADDRESS": "Address", + "ORDERS_QTY": "Orders QTY", + "ORDERS": "Orders" + }, + "INFO": { + "STORE_INFO": "Store Info", + "STORE_ID": "Store ID", + "STORE_NAME": "Store Name" + }, + "MERCHANTS": { + "TRACK_ALL_MERCHANTS": "Track all merchants", + "FILTER_MERCHANTS": "Filter merchants", + "FILTER_BY_NAME": "Filter by name", + "FILTER_BY_CITY": "Filter by city", + "FILTER_BY_COUNTRY": "Filter by country" + } + }, + "WAREHOUSE_VIEW": { + "ORDER": { + "CONFIRM": "Confirm", + "START_PROCESSING": "Start Processing", + "START_ALLOCATION": " Start Allocation", + "ALLOCATED": "Allocated", + "ALLOCATION_FAILS": "Allocation Fails", + "START_PACKAGING": "Start Packaging", + "PACKAGED": "Packaged", + "PACKAGING_FAILS": "Packaging Fails", + "GIVEN_TO_CARRIER": "Given to Carrier", + "GIVEN_TO_CUSTOMER": "Given to Customer", + "ORDER": "Order", + "CANT_PROCESSING_WITHOUT_PRODUCTS": "Can't processing the order without products." + }, + "MUTATION": { + "TITLE": "Register New Store", + "NAME": "Name", + "LOGO": "Logo", + "PHOTO": "Photo", + "IS_ACTIVE": "Is Active", + "ORDERS_SHORT_PROCESS": "Orders Short Process", + "PRODUCTS_MANUFACTURING": "Products Manufacturing", + "CARRIER_REQUIRED": "Carrier required", + "RIGHT_NOW": "Right now", + "ORDERS_PHONE": "Orders Phone", + "CONTACT_PHONE": "Contact Phone", + "ORDERS_EMAIL": "Orders Email", + "CONTACT_EMAIL": "Contact Email", + "FORWARD_ORDERS_WITH": "Forward Orders With", + "USERNAME": "Username", + "OLD_PASSWORD": "Old Password", + "NEW_PASSWORD": "New Password", + "CONFIRM_PASSWORD": "Confirm Password", + "PASSWORDS_DO_NOT_MATCH": "Passwords do not match", + "PASSWORD": "Password", + "COUNTRY": "Country", + "USA": "USA", + "ISRAEL": "Israel", + "BULGARIA": "Bulgaria", + "CITY": "City", + "ADDRESS": "Address", + "POSTCODE": "Postcode", + "COORDINATES": "Coordinates", + "AUTO_DETECT_COORDINATES": "Auto detect coordinates", + "CARRIERS": "Carriers", + "USE_ONLY_SPECIFIC_CARRIERS": "Use only specific carriers", + "SELECT_SHAPE_TO_ADD_ZONE": "Select shape to add a new zone", + "CIRCLE": "Circle", + "SHAPE": "Shape", + "DRAW_SHAPE_ON_MAP": "Draw a shape on the map", + "MINIMUM_AMOUNT": "Minimum amount", + "DELIVERY_FEE": "Delivery fee", + "CANCEL": "Cancel", + "ADD": "Add", + "EDIT": "Edit", + "ZONE_NAME": "Zone name", + "UNALLOWED_ORDER_CANCELATION": "Unallowed Order Cancelation", + "IN_STORE_MODE": "In-store mode", + "CARRIER_WORK_COMPETITION": "Carrier Work Competition", + "ORDER_CANCELATION_OPTIONS": { + "ORDERING": "After Ordering", + "START_PROCESSING": "After Start Processing", + "START_ALLOCATION": "After Start Allocation", + "ALLOCATED": "After Allocated", + "START_PACKAGING": "After Start Packaging", + "PACKAGED": "After Packaged", + "CARRIER_TAKE_WORK": "After Carrier Take Work", + "CARRIER_GOT_IT": "After Carrier Got It", + "CARRIER_START_DELIVERY": "After Carrier Start Delivery", + "DELIVERED": "After Delivered" + }, + "ERRORS": { + "NAME_IS_REQUIRED": "Warehouse name is required", + "NAME_ATLEAST_3_CHARS": "Name must be at least 3 characters long", + "NAME_MORE_THAN_255_CHARS": "Name cannot be more than 255 characters long", + "LOGO_IS_REQUIRED": "Warehouse logo is required", + "INVALID_URL": "Enter a valid logo URL or upload from a device", + "PHONE_CONTAINS_ONLY_DIGIT": "The phone number must only contain digits", + "INVALID_EMAIL": "Invalid Email", + "ORDERS_PHONE_IS_REQUIRED": "Orders Phone is required", + "CONTACT_PHONE_IS_REQUIRED": "Contact Phone is required", + "ORDERS_EMAIL_IS_REQUIRED": "Orders Email is required", + "CONTACT_EMAIL_IS_REQUIRED": "Contact Email is required", + "USERNAME_IS_REQUIRED": "Username is required", + "PASSWORD_IS_REQUIRED": "Password is required", + "COORDINATES_ARE_REQUIRED": "Coordinates are required", + "COUNTRY_IS_REQUIRED": "Country is required" + }, + "WIZARD_TITLES": { + "BASIC_INFO": "Basic Info", + "CONTACT_INFO": "Contact Info", + "LOCATION": "Location" + }, + "CONTACT_INFO_TAB": { + "ORDER_FORWARDING_EMAIL": "Order Forwarding Email", + "ORDER_FORWARDING_PHONE": "Order Forwarding Phone" + } + }, + "NEW_PRODUCT": { + "SELECT_FROM_PRODUCTS_CATALOG": "Select from Products Catalog", + "CREATE_NEW_PRODUCT": "Create new Product", + "HOW_TO_ADD": "How to add", + "ADD": "Add", + "SAVE": "Save", + "ADD_PRODUCTS_TO_STORE": "Add Products to Store", + "NOTHING_FOUND": "Nothing found..." + }, + "SELECT_PRODUCTS": { + "TITLE": "Title", + "DESCRIPTION": "Description", + "DETAILS": "Details", + "IMAGES": "Images", + "CATEGORY": "Category" + }, + "SAVE": { + "PRODUCT_NAME": "Product Name", + "PRICE": "Price", + "COUNT": "Count", + "DELIVERY": "Delivery", + "TAKEAWAY": "Takeaway" + }, + "PLACEHOLDER": { + "EXAMPLE": "Example: Domino's Pizza", + "IMAGE_URL": "Image url", + "HERE_GOES_A_SHORT_DESCRIPTION": "Here goes a short description", + "HERE_GOES_A_DETAILS_ABOUT_PRODUCT_(OPTION)": "Here goes a details about product (option)", + "REMOVE_IMAGE": "Remove image" + }, + "CREATE_ORDER_MODAL": { + "TITLE": { + "CREATE_ORDER": "Create Order" + }, + "WIZARD_FORM": { + "BUTTON_NEXT": "Next", + "BUTTON_PREV": "Back", + "BUTTON_DONE": "Finish Order", + "STEP1": { + "TITLE": "Choose Option", + "SELECT_FROM_EXISTING": "Select From Existing", + "ADD_NEW_CUSTOMER": "Add New Customer" + }, + "STEP2": { + "TITLE": { + "SELECT_CUSTOMER": "Select Customer", + "SELECT_ADD": "Select/Add", + "ADD_NEW": "Add New" + }, + "SMART_TABLE": { + "TITLES": { + "FULL_NAME": "Full Name", + "EMAIL": "Email", + "PHONE": "Phone", + "ADDRESS": "Address" + } + } + }, + "STEP3": { + "TITLE": "Create Order" + } + } + }, + "PRODUCTS_TAB": { + "PRODUCTS": "Products", + "ADD_PRODUCTS": "Add Products", + "DELETE": "Delete", + "IMAGE": "Image", + "TITLE": "Title", + "DESCRIPTION": "Description", + "DETAILS": "Details", + "CATEGORY": "Category", + "PRICE": "Price", + "QUANTITY": "Quantity", + "CLICK_ON_PRODUCT_IMAGE_TO_INCREASE_AVAILABLE_QUANTITY": "Click on Product Image to increase available quantity", + "AVAILABILITY": "Availability", + "TYPE": "Type", + "DELIVERY": "Delivery", + "TAKEAWAY": "Takeaway" + }, + "NEW_PRODUCT_TYPE": "New Type Product", + "ADD_PRODUCTS": "Add Products", + "CREATE_ORDER": "Create Order", + "STATUS": "Status", + "ORDERS": "Orders", + "WAREHOUSE": "Warehouse", + "PRODUCT": "Product", + "PRODUCTS": "Products", + "ORDER_NUMBER": "Order Number", + "CANCELLED": "Cancelled", + "WAREHOUSE_STATUS": "Warehouse Status", + "CARRIER_STATUS": "Carrier Status", + "PAID": "Paid", + "CARRIER": "Carrier", + "CREATED": "Created", + "ELAPSED": "Elapsed", + "CONTACT_DETAILS": "Contact Details", + "EMAIL": "Email", + "PHONE": "Phone", + "ORDERS_FORWARDING_DETAILS": "Orders Forwarding Details", + "ORDERS_FORWARDING_WITH": "Orders Forwarding With", + "MANAGE_STORE": "Manage Store", + "TOP_PRODUCTS": "Top Products", + "PRODUCTS_MANUFACTURING": "Products Manufacturing", + "CARRIER_REQUIRED": "Carrier required", + "MANAGE_WAREHOUSE": "Manage warehouse", + "MANAGE_STORE_PRODUCTS_&_ORDERS": "Manage store products & orders" + }, + "SIMULATION_VIEW": { + "SIMULATION": "Simulation", + "PURCHASE_PRODUCTS": "Purchase products", + "CREATE_INVITE_REQUEST": "Create Invite Request", + "SEND": "Send", + "CREATE_USER": "Create User", + "ORDER_CONFIRM": "Order Confirm", + "ORDER_CANCEL": "Order Cancel", + "PRODUCTS": "Products", + "STORE": "Store", + "ORDER": "Order", + "INVITE_REQUEST": "Invite-Request", + "INVITE_USER": "Invite User", + "TAB_BUTTONS": { + "PRODUCTS": "Products", + "ORDER_HISTORY": "Order history" + }, + "USER_MUTATION": { + "TITLE": "Create Customer", + "NAME": "Name", + "EMAIL": "Email", + "COUNTRY": "Country", + "CITY": "City", + "USA": "USA", + "ISRAEL": "Israel", + "BULGARIA": "Bulgaria", + "ADDRESS": "Address", + "POSTCODE": "Postcode", + "COORDINATES": "Coordinates", + "AUTO_DETECT_COORDINATES": "Auto detect coordinates", + "ERRORS": { + "FIRST_NAME_IS_REQUIRED": "First name is required", + "LAST_NAME_IS_REQUIRED": "Last name is required", + "INVALID_EMAIL": "Invalid Email", + "EMAIL_IS_REQUIRED": "Email is required", + "COORDINATES_ARE_REQUIRED": "Coordinates are required" + }, + "WIZARD_TITLES": { + "ADDITIONAL_INFO": "Additional Info", + "LOCATION": "Location" + } + }, + "SMART_TABLE": { + "TITLE": "Title", + "ID": "Id", + "IMAGE": "Image" + }, + "ORDER_INFO": { + "STATUSES": [ + { + "TITLE": "We're preparing the order!", + "DETAILS": "You will get it in %t minutes.", + "NOT_PAID_NOTE": "Prepare your wallet (%s in cash)." + }, + { + "TITLE": "Carrier on the way!", + "DETAILS": "You will get the order in %t min.", + "NOT_PAID_NOTE": "Prepare your wallet (%s in cash)." + }, + { + "TITLE": "Check your door!", + "DETAILS": "You will get the order in seconds.", + "NOT_PAID_NOTE": "Prepare your wallet (%s in cash)." + }, + { + "TITLE": "Order Completed!", + "DETAILS": "Thanks for using Ever", + "NOT_PAID_NOTE": "" + } + ], + "DELIVERY_STATUS": { + "WE": "We", + "CARRIER": "Carrier", + "YOU": "You" + }, + "DELIVERY_WRONG": { + "DELIVERY_WRONG": "The Delivery Went Wrong!", + "PROCESSING_WRONG": "Processing Went Wrong!", + "TRY_AGAIN": "Please try again.", + "CALL_FOR_DETAILS": "Call for details" + } + }, + "INSTRUCTIONS_STEPS": { + "INSTRUTIONS": "Instructions", + "CREATE_USER_STEP": { + "CREATE_USER": "Create User", + "STEP_1": "Step 1.", + "ORDER": "Order", + "TO_CONTINUE_IS_REQUIRED_TO_REGISTER_IN_THE_SYSTEM": "To continue is required to register in the system", + "CLICK_ON_BUTTON_CREATE_USER": "Click on button 'Create user'", + "FILL_THE_FORM_FOR_ADDITIONAL_INFO": "Fill the form for additional info (optional)", + "FILL_THE_FORM_FOR_LOCATION_AND_PRESS_DONE_BUTTON": "Fill the form for location and press DONE button" + }, + "ORDER_STEP": { + "ORDER": "Order", + "STEP_2": "Step 2.", + "CREATE_ORDER": "Create order", + "CHOICE_SOME_PRODUCTS_FROM_THE_TABLE": "Choice some products from the table (you can see more details about when press on his name or image)", + "SELECT_PRODUCT": "To be selected one product have to press on his row.", + "SELECT_BUTTON_ORDER_TO_CREATE_ORDER": "Press button 'Order' to create order with the selected product.", + "REVIEW_ORDER_HISTORY": "Review order history:", + "ON_PRESS_ORDER_HISTORY_TAB": "On press 'Order history' shows all your orders..", + "HERE_YOU_CAN_SEE_DETAILS_ABOUT_EACH_ORDER": "Here you can see details about each order", + "PRESS_ON_CARRIER_ORDER_OR_PRODUCT_NAME": "Press on carrier, order or product name for more information." + }, + "CONFIRM_OR_CANCEL_STEP": { + "STEP_3": "Step 3", + "CONFIRM_CANCEL_ORDER": "Confirm/Cancel order", + "REAL_TIME": "Real time", + "TRACK_STATUS_ON_YOUR_ORDER": "Track status on your order.", + "ELAPSED_TIME_FROM_CREATE_TO_DELIVERED": "Elapsed time from created to delivered.", + "SHOWS_MERCHANT_LOCATION": "Shows merchant location.", + "SHOWS_CARRIER_LOCATION": "Show carrier location.", + "POSSIBILITIES": "Possibilities:", + "SLIDER_REVIEW_OF_THE_ALL_PRODUCTS": "Slider review of the all products.", + "CAN_CANCEL_IT_WITH_ORDER_CANCEL_BUTTON": "Untill the order is not delivered you user cancel it with 'Order Cancel' button.", + "AFTER_THE_ORDER_IS_DELIVER_USER_CAN_CLICK_BUTTON_ORDER_CONFIRM_TO_CONTINUE": "After the order is delivered the user can click button 'Order Confirm' to continue.." + }, + "INVITE_STEP": { + "TO_CONTINUE_ITs_REQUIRED_TO_BE_INVITED_IN_THE_SYSTEM": "To continue it's required to be invited in the system:", + "SEND_INVITE_REQUEST": "Send 'Invite Request' to the system from the form which will be opened after pressing Invite Request button.", + "ALL_INVITE_REQUESTS_ARE_REVIEWED": "All invite requests are reviewed by admin and they can be invited if the system is available near your location (for test you can do immediately form Invite User button).", + "AFTER_YOU_GET_INVITED_BEFORE": "After you get invite you can easy login the system, just have to enter your invite code, which will be provided from the system (you will see here", + "AFTER_YOU_GET_INVITED_AFTER": "code after press Invite button, and can enter it when press Create User button)." + } + } + }, + "CUSTOMERS_VIEW": { + "TITLE": "Customers", + "DELETE_CUSTOMERS": "Delete", + "CREATE_CUSTOMER": "Create", + "DELETE": "DELETE", + "CREATE": "CREATE", + "BAN": "BAN", + "UNBAN": "UNBAN", + "MANAGE_CUSTOMER": "Manage customer", + "CUSTOMER": "Customer", + "CUSTOMERS_DEVICES": "Devices", + "INVITE": "Invite", + "NOT_INVITED_ONLY": "Not invited only", + "ORDER": "Order", + "ORDERS_STATISTICS": "Orders statistics", + "NUMBER_OF_ORDERS": "Number of Orders", + "CANCELED_ORDERS": "Canceled Orders", + "COMPLETED_ORDERS_TOTAL": "Completed Orders Total", + "Order": "Order", + "CANCEL_ORDER": "Cancel Order", + "CATEGORY": "Category", + "ORDERS_HISTORY": "Orders History", + "AVAILABLE_PRODUCTS": "Available products", + "NEARBY_STORES": "Nearby Stores", + "INVITES_REQUESTS_MANAGEMENT": "Invites requests management", + "INVITES_MANAGEMENT": "Invites management", + "DESCRIPTION": "Description", + "DETAILS": "Details", + "MAKE_A_CUSTOM_ORDER": "Make a custom order", + "PRODUCT_COUNT": "Product Count", + "QUANTITY_CAN'T_BE_EMPTY": "Quantity can't be empty", + "QUANTITY_CAN'T_BE_0": "Quantity can't be 0", + "NOT_ENOUGH_PRODUCTS_AVAILABLE": "Not enough products available", + "ORDER_INFO": "Order Info", + "ORDER_ID": "Order Id", + "STORE_ID": "Store Id", + "CARRIER_ID": "Carrier Id", + "NO_CARRIER": "No Carrier", + "SMART_TABLE_COLUMNS": { + "IMAGE": "Image", + "NAME": "Name", + "EMAIL": "Email", + "PHONE": "Phone", + "ADDRESS": "Address", + "ORDERS_QTY": "Orders QTY", + "COUNTRY": "Country", + "CITY": "City", + "STREET_ADDRESS": "Street Address", + "HOUSE": "House", + "APARTMENT": "Apartment", + "INVITE_CODE": "Invite Code", + "INVITED_DATE": "Invited date", + "ORDER_NUMBER": "Order Number", + "WAREHOUSE": "Warehouse", + "CARRIER": "Carrier", + "PRODUCT_LIST": "Product list", + "STATS": "Stats", + "DELIVERY_TIME": "Delivery Time", + "CREATED": "Created", + "ACTIONS": "Actions", + "PAID": "Paid", + "COMPLETED": "Completed", + "CANCELLED": "Cancelled", + "NOT_DELIVERED": "Not delivered", + "PRODUCT": "Product", + "PRICE": "Price", + "STORE": "Store", + "AVAILABLE_COUNT": "Available Count", + "ORDER": "Order", + "STATUS": "Status" + }, + "EDIT": { + "EDIT_CUSTOMER": "Edit Customer", + "BASIC_INFO": "Basic Info", + "SAVE": "Save" + }, + "DEVICE": { + "ALL_DEVICE": "All Device", + "DEVICE_ID": "Device ID", + "ID": "id", + "UPDATE": "Update", + "LANGUAGE": "language", + "TYPE": "type", + "TYPEU": "Type", + "UUID": "uuid", + "DEVICE_UUID": "Device UUID", + "UPDATE_DEVICE": "Update Device", + "CUSTOMERS_DEVICES": "Customers Devices", + "DELETE": "DELETE", + "CREATE": "CREATE" + }, + "WAREHOUSE": { + "WAREHOUSE_INFO": "Warehouse Info", + "WAREHOUSE_ID": "Warehouse Id", + "WAREHOUSE_NAME": "Warehouse Name" + }, + "CUSTOMER_VIEW": { + "MANAGE_CUSTOMER": "Manage customer", + "EDIT": "EDIT", + "CUSTOMER": "Customer" + }, + "INVITES_VIEW": { + "DELETE": "Delete", + "INVITE": "Invite" + } + }, + "CARRIERS_VIEW": { + "TITLE": "Carriers", + "DELETE_CARRIERS": "Delete Selected", + "CREATE_CARRIER": "Create Carrier", + "DELETE": "DELETE", + "CREATE_BUTTON": "CREATE", + "ACTIVE_AND_AVAILABLE_ORDERS": "Active and Available Orders", + "ORDERS_HISTORY": "Orders History", + "TRACK": "Track", + "SMART_TABLE_COLUMNS": { + "IMAGE": "Image", + "NAME": "Name", + "PHONE": "Phone", + "STATUS": "Status", + "ADDRESS": "Address", + "DELIVERIES": "Deliveries" + }, + "CARRIER_PAGE": { + "WAREHOUSE": "Warehouse", + "CUSTOMER": "Customer", + "SAVE": "Save", + "EDIT": "Edit", + "CARRIER_INFO": "Carrier Info", + "CARRIER_ID": "Carrier Id", + "REGISTER_NEW_CARRIER": "Register New Carrier", + "WAREHOUSE_STATUS": "Warehouse Status", + "CARRIER_STATUS": "Carrier Status", + "CREATED": "Created", + "ARRIVED_TO_CUSTOMER": "Arrived to Customer", + "FAILED": "Failed", + "DELIVERED": "Delivered", + "CLIENT_REFUSE_ORDER": "Client Refuse Order", + "AVAIBLE_ORDER_TO_PICK_UP": "Available Orders to pick up (each carrier can pick up multiple orders)", + "ACTIVE": "Active", + "CARRIER_CAN_BE_SHARED": "Carrier can be shared?", + "NOT_ACTIVE": "Not Active", + "WORKING": "Working", + "NOT_WORKING": "Not Working", + "SELECT_CARRIER": "Select carrier", + "CARRIER_ORDERS_STATUS": "Carrier orders status", + "Start": "Start", + "PICKED_UP_ORDER": "Picked Up Order", + "CANCEL": "Cancel", + "Arrived To Client": "Arrived To Client", + "No Carrier": "No Carrier", + "Order Selected For Delivery": "Order Selected For Delivery", + "Order Picked Up": "Order Picked Up", + "Order In Delivery": "Order In Delivery", + "Delivered": "Delivered", + "Delivery Issues": "Delivery Issues", + "Client Refuse to Take Order": "Client Refuse to Take Order", + "BAD_STATUS": "BAD_STATUS", + "Created": "Created", + "Confirmed": "Confirmed", + "Processing": "Processing", + "Allocation Started": "Allocation Started", + "Allocation Finished": "Allocation Finished", + "Packaging Started": "Packaging Started", + "Packaged": "Packaged", + "Given to Carrier": "Given to Carrier", + "Allocation Failed": "Allocation Failed", + "Packaging Failed": "Packaging Failed", + "LOCATION": "Location", + "TIME": "Time", + "NAME": "Name" + }, + "EDIT": { + "EDIT_CARRIER": "Edit Carrier", + "BASIC_INFO": "Basic Info", + "LOCATION": "Location", + "PHOTO_URL": "Photo Url", + "CONTACT_PHONE": "Contact Phone", + "FIRST_NAME": "First Name", + "LAST_NAME": "Last Name" + }, + "CREATE": { + "BASIC_INFO": "Basic Info", + "LOCATION": "Location" + }, + "TRACK_PAGE": { + "TRACK_ALL_WORKING_CARRIERS": "Track all working carriers", + "FILTER_CARRIERS": "Filter Carriers", + "PHONE": "Phone", + "EMAIL": "Email", + "ADDRESS": "Address", + "DELIVERY_COUNT": "Delivery Count" + } + }, + "PRODUCT_TYPE_VIEW": { + "TITLE": "New Product", + "WIZARD_FORM": { + "VALIDATION_MESSAGES": { + "TITLE": "Title is required", + "THE_LENGHT_OF_THE_TITLE": "The length of title should be max 255 characters long!", + "IMAGE": "Picture is required", + "DESCRIPTION": "Description is required", + "LANGUAGE": "Language is required", + "THE_LENGHT_OF_THE_DESCRIPTION": "The length of description should be max 255 characters long!", + "PRICE": "Price is required", + "COUNT": "Product count is required" + } + } + }, + "SHARED": { + "FORMS": { + "ERRORS": { + "FIRST_NAME_REQUIRED": "First name is required", + "LAST_NAME_REQUIRED": "Last name is required", + "USERNAME_REQUIRED": "Username is required", + "PASSWORD_REQUIRED": "Password is required", + "PHONE_REQUIRED": "Phone is required", + "LOGO_URL_REQUIRED": "Enter valid image URL or browse for a file", + "IS_ACTIVE": "Is active field is required", + "COUNTRY_REQUIRED": "Country is required", + "CITY_REQUIRED": "City is required", + "STREET_ADDRESS_REQUIRED": "Street address is required", + "HOUSE_REQUIRED": "House number is required", + "COORDINATES_REQUIRED": "Coordinates are required", + "MUST_CONTAIN_ONLY_LETTERS": "Must contain only letters", + "PHONE_MUST_CONTAINS_ONLY(specail_signs)AND_DIGIT_CHARACTER": "Phone number can start with '+' or '(some numbers)' and must contains only only: '-, ., (space), #'' and digit characters" + } + }, + "USER": { + "FORMS": { + "BASIC_INFO": { + "TITLE": "Basic Info", + "FIRST_NAME": "First Name", + "FIRST_NAME_OPTIONAL": "First Name (optional)", + "LAST_NAME_OPTIONAL": "Last Name (optional)", + "PHOTO_URL": "Photo Url", + "PICTURE_URL": "Picture url (optional)", + "EMAIL": "Email", + "EMAIL_OPTIONAL": "Email (optional)", + "ERRORS": { + "INVALID_EMAIL": "Invalid Email", + "EMAIL_IS_ALREADY_IN_USE": "Email is already in use" + } + } + } + }, + "WAREHOUSE": { + "ORDER_MODAL": { + "MAKE_ORDER": "Make Order", + "ONLY_AVAILABLE": "Show only available products", + "ORDER": "Order", + "SMART_TABLE": { + "TITLES": { + "IMG": "Img", + "PRODUCT": "Product", + "PRICE": "Price", + "AVAILABLE": "Available", + "AMOUNT": "Amount", + "COMMENT": "Comment" + } + } + } + } + }, + "MENU_VIEW": { + "DASHBOARD": "Dashboard", + "STORES": "Stores", + "PRODUCTS": { + "PRODUCTS": "Products", + "MANAGEMENT": "Management", + "CATEGORIES": "Categories" + }, + "CUSTOMERS": { + "CUSTOMERS": "Customers", + "MANAGEMENT": "Management", + "INVITES": "Invites" + }, + "CARRIERS": "Carriers", + "SIMULATION": "Simulation", + "SETUP": "Setup" + }, + "CATEGORY_VIEW": { + "TITLE": "Title", + "IMAGE": "Image", + "CREATE_BUTTON": "CREATE", + "DELETE": "DELETE", + "EDIT": { + "EDIT_CATEGORY": "Edit Category", + "CATEGORY_NAME": "Category Name", + "ENTER_THE_CATEGORY_NAME": "Enter the category name", + "DONE": "Done" + }, + "CREATE": { + "CREATE_CATEGORY": "Create Category", + "CATEGORY_NAME": "Category Name", + "ENTER_THE_CATEGORY_NAME": "Enter the category name", + "PHOTO": "Photo", + "BROWSE": "Browse", + "INVALID_URL": "Invalid Url", + "REMOVE_IMAGE": "Remove image", + "PHOTO_OPTIONAL": "Photo (optional)", + "DONE": "Done" + } + }, + "PLACEHOLDER": { + "EXAMPLE": "Example: Domino's Pizza", + "IMAGE_URL": "Image url", + "HERE_GOES_A_SHORT_DESCRIPTION": "Here goes a short description", + "HERE_GOES_A_DETAILS_ABOUT_PRODUCT_(OPTION)": "Here goes a details about product (option)", + "REMOVE_IMAGE": "Remove image", + "PASSWORD": "Password", + "LATITUDE": "Latitude", + "LONGITUDE": "Longitude", + "APARTMENT": "Apartment", + "HOUSE": "House", + "STREET": "Street", + "ZIP": "ZIP", + "CITY": "City", + "FIND_ADDRESS": "Find Address" + }, + "STATUS_TEXT": { + "Created": "Created", + "Confirmed": "Confirmed", + "Processing": "Processing", + "Allocation Started": "Allocation Started", + "Allocation Finished": "Allocation Finished", + "Packaging Started": "Packaging Started", + "Packaged": "Packaged", + "Given to Carrier": "Given to Carrier", + "Allocation Failed": "Allocation Failed", + "Packaging Failed": "Packaging Failed", + "No Carrier": "No Carrier", + "Order Selected For Delivery": "Order Selected For Delivery", + "Order Picked Up": "Order Picked Up", + "Order In Delivery": "Order In Delivery", + "Arrived To Client": "Arrived To Client", + "Delivered": "Delivered", + "Delivery Issues": "Delivery Issues", + "Client Refuse to Take Order": "Client Refuse to Take Order", + "Given to Customer": "Given to Customer", + "BAD_STATUS": "BAD_STATUS" + }, + "ELAPSED_TIME": { + "TITLE": "Elapsed time" + }, + "CONFIRM_MODAL": { + "ARE_YOU_SURE": "Are you sure?", + "ARE_YOU_SURE_YOU_WANT_TO_INCREASE": "Are you sure you want to increase the qty of products?", + "ARE_YOU_SURE_YOU_WANT_TO_DECREASE": "Are you sure you want to decrease the qty of products?", + "YES": "Yes", + "NO": "No" + }, + "NO_SERVER_VIEW": { + "NO_SERVER": "Server connection is lost" + }, + "BUTTON_NEXT": "Next", + "BUTTON_PREV": "Back", + "BUTTON_DONE": "Done", + "TERRAIN": "Terrain", + "SATELLITE": "Satellite", + "LOCATION": "Location", + "ROAD_MAP": "Road Map", + "Manage warehouse": "Manage store products & orders", + "Warehouse": "Warehouse", + "Create Warehouse": "Create Warehouse", + "SIMULATION": "Simulation", + "Purchase products": "Purchase products", + "Manage": "Manage", + "Orders": "Orders", + "Confirmed": "Confirmed", + "In Delivery": "In Delivery", + "Not Confirmed": "Not Confirmed", + "Not paid": "Not paid", + "Cancelled": "Cancelled", + "All": "All", + "CANCEL": "Cancel", + "Default Settings": "Default Settings", + "Products Manufacturing": "Products Manufacturing", + "Carrier required before sale": "Carrier required before sale", + "New Product Type": "New Product Type", + "Products": "Products", + "Product": "Product", + "Title": "Title", + "Picture Url": "Picture Url", + "Description": "Description", + "Details": "Details", + "Price": "Price", + "CATEGORY": "Category", + "LANGUAGE": "Language", + "BROWSE": "Browse", + "ENGLISH": "English", + "HEBREW": "Hebrew", + "RUSSIAN": "Russian", + "BULGARIAN": "Bulgarian", + "SPANISH": "Spanish", + "FRENCH": "French", + "SELECT": "Select", + "Name": "Name", + "Id": "Id", + "Warehouse name is required": "Warehouse name is required", + "Name must be at least 1 characters long": "Name must be at least 1 characters long", + "Title cannot be more than 255 characters long": "Title cannot be more than 255 characters long", + "Logo": "Logo", + "Warehouse logo is required": "Warehouse logo is required", + "is Active": "is Active", + "right now": "right now", + "Unselected": "Unselected", + "Phone": "Phone", + "Email": "Email", + "Username": "Username", + "Username is required": "Username is required", + "Password": "Password", + "Country": "Country", + "USA": "USA", + "Israel": "Israel", + "Bulgaria": "Bulgaria", + "City": "City", + "Address": "Address", + "Postcode": "Postcode", + "Coordinates": "Coordinates", + "Auto detect coordinates": "Auto detect coordinates", + "Carriers": "Carriers", + "Carrier": "Carrier", + "Use only specific carriers": "Use only specific carriers", + "Manage carrier and deliveries": "Manage carrier and deliveries", + "Register New Carrier": "Register New Carrier", + "Create User": "Create User", + "OPTIONAL": "optional" +} diff --git a/packages/admin-web-angular/src/assets/i18n/fr.json b/packages/admin-web-angular/src/assets/i18n/fr.json new file mode 100644 index 0000000..2997a24 --- /dev/null +++ b/packages/admin-web-angular/src/assets/i18n/fr.json @@ -0,0 +1,1170 @@ +{ + "COMMON": { + "SAVE": "Save", + "CANCEL": "Cancel", + "USA": "USA", + "ISRAEL": "Israel", + "BULGARIA": "Bulgaria" + }, + "ORDER_VIEW": { + "ORDER_HEADER_INFO": { + "ORDER": "Order", + "MANAGE_ORDER": "Manage Order", + "TOTAL": "Total" + }, + "ORDER_PRODUCT_INFO": { + "ORDER_PRODUCTS": "Order Products", + "ADD_PRODUCTS": "Add products", + "REMOVE_PRODUCTS": "Remove Products", + "CANCEL_ORDER": "Cancel Order", + "THE_ORDER_IS_CANCELED": "The order is canceled", + "THE_ORDER_IS_GIVEN_TO_CARRIER": "The order is given to carrier.", + "THE_ORDER_IS_DELIVERED": "The order is delivered.", + "ADD_PRODUCTS_MODAL": "Add Products", + "ADD": "Add", + "SUCCESS_TOAST": "Products were added to the order", + "ERROR_TOAST": "Error, something went wrong", + "SMART_TABLE": { + "NAME": "Name", + "QTY": "QTY", + "PRICE": "Price", + "IMAGE": "Image", + "COMMENT": "Comment" + } + }, + "ORDER_SIDEBAR": { + "CONTACT_DETAILS": "Contact Details", + "WAREHOUSE": "Warehouse", + "CUSTOMER": "Customer", + "CARRIER": "Carrier", + "QTY": "qty" + }, + "LOCATION_INFO": { + "MAP": "Map", + "DELIVERY_DISTANCE": "Delivery Distance:" + } + }, + "DASHBOARD_VIEW": { + "TOTAL_CUSTOMER": "Total Customers", + "TOTAL_QUANTITY_OF_EXISTING_CUSTOMERS": "Total quantity of existing customers", + "TOTAL_COMPLETED_ORDERS": "Total completed orders", + "TOTAL_QUANTITY_OF_COMPLETED_ORDERS": "Total quantity of completed orders", + "TOTAL_REVENUE": "Total Revenue", + "TOTAL_SUM_SUM_OF_ALL_COMPLETED_ORDERS": "Total sum of all completed orders", + "TODAYs_CUSTOMERS": "Today's Customers", + "TODAYs_QUANTITY_OF_REGISTERED_CUSTOMERS": "Today's quantity of registered customers", + "TODAYs_COMPLETED_ORDERS": "Today's completed orders", + "TODAYs_QUANTITY_OF_NEW_COMPLETED_ORDERS": "Today's quantity of new completed orders", + "TODAYs_REVENUE": "Today's Revenue", + "TODAYs_SUM_OF_COMPLETED_ORDERS": "Today's sum of completed orders", + "TILL_AVERAGE": "till average", + "BETTER_THAN_AVERAGE": "better than average", + "SELECT_COMPONENT": { + "STORES": "Stores", + "CONTACT_DETAILS": "Contact Details", + "PHONE": "Phone", + "EMAIL": "Email", + "ORDERS_FORWARDING_WITH": "Orders Forwarding With", + "SELECT_STORE": "Select store" + }, + "CHARTS": { + "ORDERS": "Orders", + "PROFIT": "Profit", + "TOTAL_ORDERS": "Total orders", + "TOTAL_COMPLETED_ORDERS": "Total completed orders", + "TOTAL_CANCELLED_ORDERS": "Total canceled orders", + "TOTAL_REVENUE_ALL_ORDERS": "Total revenue from all orders", + "TOTAL_REVENUE_COMPLETED_ORDERS": "Total revenue from completed orders", + "TOTAL_LOST_REVENUE_CANCELLED_ORDERS": "Total lost revenue from canceled orders", + "PAYMENT": "Payment", + "CANCELED": "Canceled", + "ALL_ORDERS": "All Orders", + "TODAY": "Today", + "LAST_WEEK": "Last Week", + "LAST_MONTH": "Last Month", + "CURRENT_YEAR": "Current Year", + "YEARS": "By Year", + "CUSTOM_PERIOD": "Custom Period", + "SELECT_PERIOD": "Select Period", + "FROM": "From", + "TO": "To", + "SELECT": "Select", + "LABELS": { + "WEEK": "Week", + "WEEKDAYS": { + "MON": "Mon", + "TUE": "Tue", + "WED": "Wed", + "THU": "Thu", + "FRI": "Fri", + "SAT": "Sat", + "SUN": "Sun" + }, + "MONTHS": { + "JAN": "Jan", + "FEB": "Feb", + "MAR": "Mar", + "APR": "Apr", + "MAY": "May", + "JUN": "Jun", + "JUL": "Jul", + "AUG": "Aug", + "SEP": "Sep", + "OCT": "Oct", + "NOV": "Nov", + "DEC": "Dec" + } + } + } + }, + "HEADER_VIEW": { + "ADMIN": "Admin", + "EVER": "Ever", + "PROFILE": "Profile", + "LOG_OUT": "Log out" + }, + "FOOTER_VIEW": { + "COPY_RIGHT": "Copyright © 2016-present", + "ALL_RIGHTS_RESERVED": "All rights reserved" + }, + "PROFILE_VIEW": { + "PROFILE_PAGE": "Profile page", + "BASIC_INFO": "Basic Info", + "ACCOUNT": "Account", + "USERNAME": "Username", + "ERROR": "Error", + "EMAIL": "Email", + "FIRST_NAME": "First Name", + "FIRST_NAME_OPTIONAL": "First Name (optional)", + "LAST_NAME": "Last Name (optional)", + "PICTURE_URL": "Picture url (optional)", + "BROWSE": "Browse", + "REMOVE": "Remove", + "SAVE": "Save", + "OLD_PASSWORD": "Old password", + "NEW_PASSWORD": "New password", + "REPEAT_NEW_PASSWORD": "Repeat new password", + "INVALID_EMAIL_ADDRESS": "Invalid email address", + "INVALID_URL": "Invalid URL", + "NAME_MUST_CONTAIN_ONLY_LETTERS": "Name must contain only letters", + "PASSWORDS_DO_NOT_MATCH": "Passwords do not match", + "SUCCESSFULLY_CHANGE_PASSWORD": "Successfully change password" + }, + "PRODUCTS_VIEW": { + "DELETE": "DELETE", + "CREATE": "CREATE", + "EDIT_VIEW": { + "EDIT_PRODUCT": "Edit Product", + "BASIC_INFO": "Basic Info", + "SAVE": "Save" + } + }, + "FAKE_DATA": { + "FAKE_DATA_GENERATOR": "Fake Data Generator", + "GENERATE_ALL": "Generate All", + "CREATE_100_USERS": "Create 100 customers", + "CREATE_100_CARRIERS": "Create 100 carriers", + "CREATE_100_WAREHOUSES": "Create 100 warehouses", + "SETUP": "SETUP", + "GENERATE_INITIAL_DATA": "Generate Initial Data", + "CREATE_INVITE": "CREATE INVITE", + "HARDCODED_DATA": "Hardcoded data", + "CLEAR_ALL": "CLEAR ALL", + "GENERATE_HARDCODED_ONLY": "Generate Hardcoded Only", + "INCLUDED_HARDCODED_DATA": "Include hardcoded data", + "CREATE_1st_INVITE": "Create 1st invite", + "CREATE_2st_INVITE": "Create 2nd invite", + "CREATE_3st_INVITE": "Create 3rd invite", + "CREATE_4st_INVITE": "Create 4rd invite", + "CREATE_CUSTOMER": "Create Customer", + "CREATE_USER": "CREATE USER", + "CREATE_1st_USER": "Create 1st user (using 1st invite)", + "CREATE_CARRIER": "CREATE CARRIER", + "CREATE_1st_CARRIER": "Create 1st carrier", + "CREATE_2nd_CARRIER": "Create 2nd carrier", + "CREATE_3rd_CARRIER": "Create 3rd carrier", + "CREATE_PRODUCT": "CREATE PRODUCT", + "CREATE_PEPERONI&MUSHROOM_PIZZA_PRODUCT": "Create Peperoni & Mushroom pizza product", + "CREATE_SUSHI&CAVIAR_PRODUCT": "Create Sushi & Caviar product", + "CREATE_SUSHI_MIX_PRODUCT": "Create Sushi mix product", + "CREATE_PASTA_PRODUCT": "Create Pasta product", + "CREATE_SUSHI_BOX_PRODUCT": "Create Sushi box product", + "CREATE_PEPERONI&TOMATO_PIZZA_PRODUCT": "Create Peperoni & Tomato pizza product", + "CREATE_WAREHOUSE": "CREATE WAREHOUSE", + "CREATE_1st_WAREHOUSE": "Create 1st warehouse", + "CREATE_2nd_WAREHOUSE": "Create 2nd warehouse", + "CREATE_3rd_WAREHOUSE": "Create 3rd warehouse", + "CREATE_WAREHOUSE_PRODUCT": "CREATE WAREHOUSE PRODUCT", + "CREATE_1st_WAREHOUSE_PRODUCTS(using_product_number_1_2_3_4_5_and_6)": "Create 1st warehouse products (using product number 1, 2, 3, 4, 5 and 6)", + "CREATE_3rd_WAREHOUSE_PRODUCTS(using_1st_product)": "Create 3rd warehouse products (using 1st product)", + "CREATE_2nd_WAREHOUSE_PRODUCTS(using_product_number_1_2_and_3": "Create 2nd warehouse products (using product number 1, 2, and 3", + "UPDATE_WAREHOUSE_GEO_LOCATION": "UPDATE WAREHOUSE GEO LOCATION", + "UPDATE_1ST_WAREHOUSE_GEO_LOCATION": "Update 1st warehouse geo location", + "CREATE_1RD_ORDER(using_1st_warehouse_1st_user_and_1st_product)": "Create 1rd order (using 1st warehouse, 1st user and 1st product)", + "CREATE_2ND_ORDER(using_1st_warehouse_1st_user_and_2nd_product)": "Create 2nd order (using 1st warehouse, 1st user and 2nd product)", + "CONFIRM_ORDER": "CONFIRM ORDER", + "CREATE_ORDER": "CREATE ORDER", + "CONFIMR_1st_ORDER": "Confirm 1st order", + "CONFIMR_2nd_ORDER": "Confirm 2nd order", + "SETUP_MERCHANTS": { + "SETUP_MERCHANTS": "Setup merchants", + "PREV": "PREV", + "NEXT": "NEXT", + "ADD": "Add", + "BACK": "Back", + "SAVE": "Save", + "SELECT": "Select", + "CREATE": "Create", + "HOW_TO_SET_UP": { + "HOW_TO_SET_UP": "How to setup" + }, + "STEPPER": { + "ACCOUNT": "Account", + "BASIC_INFO": "Basic info", + "CONTACT_INFO": "Contact info", + "LOCATION": "Location", + "PAYMENTS": "Payments", + "MANUFACTURING": "Manufacturing", + "DELIVERY_AND_TAKEAWAY_SETTINGS": "Delivery & Takeaway Settings", + "ORDERS_SETTINGS": "Orders Settings", + "PRODUCT_CATEGORIES": "Product categories", + "PRODUCTS": "Products" + }, + "ACCOUNT": { + "ACCOUNT": "Account", + "EMAIL_ADDRESS": "Email address", + "EMAIL": "Email", + "PASSWORD": "Password", + "REPEAT_PASSWORD": "Repeat password", + "EMAIL_IS_REQUIRED": "Email is required", + "INVALID_EMAIL_FORMAT": "Invalid email format", + "USERNAME": "Username", + "USERNAME_IS_REQUIRED": "Username is required", + "USERNAME_MUST_BE_AT_LEAST_3_CHARACTERS": "Username must be at least 3 characters", + "PASSWORD_IS_REQUIRED": "Password is required", + "PASSWORD_MUST_BE_AT_LEAST_4_CHARACTERS": "Password must be at least 4 characters", + "REPEAT_PASSWORD_IS_REQUIRED": "Repeat password is required", + "PASSWORDS_DO_NOT_MATCH": "Passwords do not match" + }, + "BASIC_INFO": { + "BASIC_INFO": "Basic info", + "NAME": "Name", + "NAME_IS_REQUIRED": "Name is required", + "NAME_MUST_BE_AT_LEAST_4_CHARACTERS": "Name must be at least 4 characters", + "PHOTO": "Photo", + "ENTER_A_VALID_LOGO_URL_OR_BROWSE_FROM_A_DEVICE": "Enter a valid logo URL or browse from a device", + "REMOVE": "Remove", + "PHOTO_OPTIONAL": "Photo (optional)", + "BARCODE_DATA": "Barcode Data", + "BARCODE_DATA_IS_REQUIRED": "Barcode Data is required" + }, + "CONTACT_INFO": { + "CONTACT_INFO": "Contact info", + "CONTACT_PHONE": "Contact Phone", + "INVALID_PHONE_NUMBER_FORMAT": "Invalid phone number format", + "ORDER_FORWARDING_EMAIL": "Order Forwarding Email", + "ORDER_FORWARDING_PHONE": "Order Forwarding Phone", + "ORDERS_EMAIL": "Orders Email", + "ORDERS_EMAIL_IS_REQUIRED": "Orders email is required", + "INVALID_EMAIL_FORMAT": "Invalid email format", + "ORDERS_PHONE": "Orders Phone", + "ORDERS_PHONE_IS_REQUIRED": "Orders phone is required" + }, + "LOCATION": { + "LOCATION": "Location" + }, + "PAYMENTS": { + "PAYMENTS": "Payments", + "ALLOW_ONLINE_PAYMENT": "Allow online payments?", + "ALLOW_CASH_PAYMENT": "Allow cash payments?", + "STRIPE": { + "PAY_BUTTON_TEXT": "Pay Button text", + "CURRENCY": "Currency", + "PAY_BUTTON_TEXT_IS_REQUIRED": "Pay Button text is required", + "CHOOSE_CURRENCY_CODE": "Choose currency code", + "CURRENCY_TEXT_IS_REQUIRED": "Currency text is required", + "COMPANY_BRAND_LOGO": "Company brand logo", + "COMPANY_BRAND_LOGO_IS_REQUIRED": "Company brand logo is required", + "INVALID_LOGO_URL": "Invalid logo url", + "INVALID_LOGO": "Invalid logo", + "PUBLISHABLE_KEY": "Publishable key", + "PUBLISHABLE_KEY_IS_REQUIRED": "Publishable key is required", + "ALLOW_REMEMBER_ME": "Allow remember me?", + "REMOVE": "Remove" + }, + "PAYPAL": { + "MODE": "Mode", + "CHOOSE_PAYPAL_MODE": "Choose PayPal mode", + "TYPE": "type", + "CURRENCY": "Currency", + "CHOOSE_CURRENCY_CODE": "Choose currency code", + "CURRENCY_TEXT_IS_REQUIRED": "Currency text is required", + "PUBLISHABLE_KEY": "Publishable key", + "PUBLISHABLE_KEY_IS_REQUIRED": "Publishable key is required", + "SECRET_KEY": "Secret key", + "SECRET_KEY_IS_REQUIRED": "Secret key is required", + "PAYMENT_DESCRIPTION": "Payment description", + "PAYMENT_DESCRIPTION_IS_REQUIRED": "Payment description is required" + } + }, + "MANUFACTURING": { + "MANUFACTURING": "Manufacturing", + "PRODUCTS_MANUFACTURING": "Products Manufacturing" + }, + "DELIVERY_AND_TAKEAWAY_SETTINGS": { + "DELIVERY_AND_TAKEAWAY_SETTINGS": "Delivery & Takeaway Settings", + "SELECT_FROM_SHARED_CARRIERS": "Select from shared carriers", + "ADD_YOUR_CARRIER": "Add your carrier", + "EDIT_CARRIER": "Edit carrier", + "CARRIER_REQUIRED": "Carrier required", + "PRODUCTS_DELIVERY_BY_DEFAULT": "Products Delivery (by default)", + "PRODUCTS_TAKEAWAY_BY_DEFAULT": "Products Takeaway (by default)", + "USE_SELECTED_SHARED_CARRIERS": "Use selected shared carriers", + "ADD_YOUR_CARRIERS": "Add your carriers" + }, + "ORDERS_SETTINGS": { + "ORDERS_SETTINGS": "Orders Settings", + "ORDER_BARCODE_QR_CODE_TYPES": "Order Barcode/QR code types" + }, + "PRODUCT_CATEGORIES": { + "PRODUCT_CATEGORIES": "Product categories", + "ADD_OWN_PRODUCT_CATEGORY": "Add own product category" + }, + "PRODUCTS": { + "PRODUCTS": "Products", + "SELECT_FROM_PRODUCTS_CATALOG": "Select from products catalog", + "CREATE_PRODUCT": "Create product", + "EDIT_PRODUCT": "Edit product", + "ADD_PRODUCT": "Add product", + "CREATE_NEW_PRODUCT": "Create new product" + } + } + }, + "WAREHOUSE_MANAGE": { + "TITLE": "Manage Warehouse", + "MANAGE_STORE": "Manage Store", + "SAVE": "SAVE", + "NAME": "Name", + "USERNAME": "Username", + "PASSWORD": "Password", + "COUNTRY": "Country", + "CITY": "City", + "POSTCODE": "Postcode", + "IS_ACTIVE": "Is Active", + "PRODUCTS_MANUFACTURING": "Products Manufacturing", + "CARRIER_REQUIRED": "Carrier required", + "RIGHT_NOW": "(right now)", + "CARRIERS": "Carriers", + "ADDRESS": "Address", + "CARRIERS_SPECIFIC": "Use only specific carriers", + "VALIDATION": { + "NAME": "Name is required", + "USERNAME": "Username is required", + "PASSWORD": "Password is required", + "COUNTRY": "Country is required", + "CITY": "City is required", + "STREET": "Street is required", + "HOUSE": "House number is required", + "POSTCODE": "Postcode is required" + }, + "WIZARD_TITLES": { + "DETAILS": "Details", + "ACCOUNT": "Account", + "CONTACT_INFO": "Contact Info", + "LOCATION": "Location", + "PAYMENT": "Payment", + "DELIVERY_ZONES": "Delivery zones" + } + }, + "WAREHOUSES_VIEW": { + "TITLE": "Stores", + "DELETE_WAREHOUSES": "Delete Selected", + "DELETE": "DELETE", + "CREATE": "CREATE", + "SHOW_ON_MAP": "Show on map", + "SMART_TABLE_COLUMNS": { + "IMAGE": "Image", + "NAME": "Name", + "EMAIL": "Email", + "PHONE": "Phone", + "CITY": "City", + "ADDRESS": "Address", + "ORDERS_QTY": "Orders QTY", + "ORDERS": "Orders" + }, + "INFO": { + "STORE_INFO": "Store Info", + "STORE_ID": "Store ID", + "STORE_NAME": "Store Name" + }, + "MERCHANTS": { + "TRACK_ALL_MERCHANTS": "Track all merchants", + "FILTER_MERCHANTS": "Filter merchants", + "FILTER_BY_NAME": "Filter by name", + "FILTER_BY_CITY": "Filter by city", + "FILTER_BY_COUNTRY": "Filter by country" + } + }, + "WAREHOUSE_VIEW": { + "ORDER": { + "CONFIRM": "Confirm", + "START_PROCESSING": "Start Processing", + "START_ALLOCATION": " Start Allocation", + "ALLOCATED": "Allocated", + "ALLOCATION_FAILS": "Allocation Fails", + "START_PACKAGING": "Start Packaging", + "PACKAGED": "Packaged", + "PACKAGING_FAILS": "Packaging Fails", + "GIVEN_TO_CARRIER": "Given to Carrier", + "GIVEN_TO_CUSTOMER": "Given to Customer", + "ORDER": "Order", + "CANT_PROCESSING_WITHOUT_PRODUCTS": "Can't processing the order without products." + }, + "MUTATION": { + "TITLE": "Register New Store", + "NAME": "Name", + "LOGO": "Logo", + "PHOTO": "Photo", + "IS_ACTIVE": "Is Active", + "ORDERS_SHORT_PROCESS": "Orders Short Process", + "PRODUCTS_MANUFACTURING": "Products Manufacturing", + "CARRIER_REQUIRED": "Carrier required", + "RIGHT_NOW": "Right now", + "ORDERS_PHONE": "Orders Phone", + "CONTACT_PHONE": "Contact Phone", + "ORDERS_EMAIL": "Orders Email", + "CONTACT_EMAIL": "Contact Email", + "FORWARD_ORDERS_WITH": "Forward Orders With", + "USERNAME": "Username", + "OLD_PASSWORD": "Old Password", + "NEW_PASSWORD": "New Password", + "CONFIRM_PASSWORD": "Confirm Password", + "PASSWORDS_DO_NOT_MATCH": "Passwords do not match", + "PASSWORD": "Password", + "COUNTRY": "Country", + "USA": "USA", + "ISRAEL": "Israel", + "BULGARIA": "Bulgaria", + "CITY": "City", + "ADDRESS": "Address", + "POSTCODE": "Postcode", + "COORDINATES": "Coordinates", + "AUTO_DETECT_COORDINATES": "Auto detect coordinates", + "CARRIERS": "Carriers", + "USE_ONLY_SPECIFIC_CARRIERS": "Use only specific carriers", + "SELECT_SHAPE_TO_ADD_ZONE": "Select shape to add a new zone", + "CIRCLE": "Circle", + "SHAPE": "Shape", + "DRAW_SHAPE_ON_MAP": "Draw a shape on the map", + "MINIMUM_AMOUNT": "Minimum amount", + "DELIVERY_FEE": "Delivery fee", + "CANCEL": "Cancel", + "ADD": "Add", + "EDIT": "Edit", + "ZONE_NAME": "Zone name", + "UNALLOWED_ORDER_CANCELATION": "Unallowed Order Cancelation", + "IN_STORE_MODE": "In-store mode", + "CARRIER_WORK_COMPETITION": "Carrier Work Competition", + "ORDER_CANCELATION_OPTIONS": { + "ORDERING": "After Ordering", + "START_PROCESSING": "After Start Processing", + "START_ALLOCATION": "After Start Allocation", + "ALLOCATED": "After Allocated", + "START_PACKAGING": "After Start Packaging", + "PACKAGED": "After Packaged", + "CARRIER_TAKE_WORK": "After Carrier Take Work", + "CARRIER_GOT_IT": "After Carrier Got It", + "CARRIER_START_DELIVERY": "After Carrier Start Delivery", + "DELIVERED": "After Delivered" + }, + "ERRORS": { + "NAME_IS_REQUIRED": "Warehouse name is required", + "NAME_ATLEAST_3_CHARS": "Name must be at least 3 characters long", + "NAME_MORE_THAN_255_CHARS": "Name cannot be more than 255 characters long", + "LOGO_IS_REQUIRED": "Warehouse logo is required", + "INVALID_URL": "Enter a valid logo URL or upload from a device", + "PHONE_CONTAINS_ONLY_DIGIT": "The phone number must only contain digits", + "INVALID_EMAIL": "Invalid Email", + "ORDERS_PHONE_IS_REQUIRED": "Orders Phone is required", + "CONTACT_PHONE_IS_REQUIRED": "Contact Phone is required", + "ORDERS_EMAIL_IS_REQUIRED": "Orders Email is required", + "CONTACT_EMAIL_IS_REQUIRED": "Contact Email is required", + "USERNAME_IS_REQUIRED": "Username is required", + "PASSWORD_IS_REQUIRED": "Password is required", + "COORDINATES_ARE_REQUIRED": "Coordinates are required", + "COUNTRY_IS_REQUIRED": "Country is required" + }, + "WIZARD_TITLES": { + "BASIC_INFO": "Basic Info", + "CONTACT_INFO": "Contact Info", + "LOCATION": "Location" + }, + "CONTACT_INFO_TAB": { + "ORDER_FORWARDING_EMAIL": "Order Forwarding Email", + "ORDER_FORWARDING_PHONE": "Order Forwarding Phone" + } + }, + "NEW_PRODUCT": { + "SELECT_FROM_PRODUCTS_CATALOG": "Select from Products Catalog", + "CREATE_NEW_PRODUCT": "Create new Product", + "HOW_TO_ADD": "How to add", + "ADD": "Add", + "SAVE": "Save", + "ADD_PRODUCTS_TO_STORE": "Add Products to Store", + "NOTHING_FOUND": "Nothing found..." + }, + "SELECT_PRODUCTS": { + "TITLE": "Title", + "DESCRIPTION": "Description", + "DETAILS": "Details", + "IMAGES": "Images", + "CATEGORY": "Category" + }, + "SAVE": { + "PRODUCT_NAME": "Product Name", + "PRICE": "Price", + "COUNT": "Count", + "DELIVERY": "Delivery", + "TAKEAWAY": "Takeaway" + }, + "PLACEHOLDER": { + "EXAMPLE": "Example: Domino's Pizza", + "IMAGE_URL": "Image url", + "HERE_GOES_A_SHORT_DESCRIPTION": "Here goes a short description", + "HERE_GOES_A_DETAILS_ABOUT_PRODUCT_(OPTION)": "Here goes a details about product (option)", + "REMOVE_IMAGE": "Remove image" + }, + "CREATE_ORDER_MODAL": { + "TITLE": { + "CREATE_ORDER": "Create Order" + }, + "WIZARD_FORM": { + "BUTTON_NEXT": "Next", + "BUTTON_PREV": "Back", + "BUTTON_DONE": "Finish Order", + "STEP1": { + "TITLE": "Choose Option", + "SELECT_FROM_EXISTING": "Select From Existing", + "ADD_NEW_CUSTOMER": "Add New Customer" + }, + "STEP2": { + "TITLE": { + "SELECT_CUSTOMER": "Select Customer", + "SELECT_ADD": "Select/Add", + "ADD_NEW": "Add New" + }, + "SMART_TABLE": { + "TITLES": { + "FULL_NAME": "Full Name", + "EMAIL": "Email", + "PHONE": "Phone", + "ADDRESS": "Address" + } + } + }, + "STEP3": { + "TITLE": "Create Order" + } + } + }, + "PRODUCTS_TAB": { + "PRODUCTS": "Products", + "ADD_PRODUCTS": "Add Products", + "DELETE": "Delete", + "IMAGE": "Image", + "TITLE": "Title", + "DESCRIPTION": "Description", + "DETAILS": "Details", + "CATEGORY": "Category", + "PRICE": "Price", + "QUANTITY": "Quantity", + "CLICK_ON_PRODUCT_IMAGE_TO_INCREASE_AVAILABLE_QUANTITY": "Click on Product Image to increase available quantity", + "AVAILABILITY": "Availability", + "TYPE": "Type", + "DELIVERY": "Delivery", + "TAKEAWAY": "Takeaway" + }, + "NEW_PRODUCT_TYPE": "New Type Product", + "ADD_PRODUCTS": "Add Products", + "CREATE_ORDER": "Create Order", + "STATUS": "Status", + "ORDERS": "Orders", + "WAREHOUSE": "Warehouse", + "PRODUCT": "Product", + "PRODUCTS": "Products", + "ORDER_NUMBER": "Order Number", + "CANCELLED": "Cancelled", + "WAREHOUSE_STATUS": "Warehouse Status", + "CARRIER_STATUS": "Carrier Status", + "PAID": "Paid", + "CARRIER": "Carrier", + "CREATED": "Created", + "ELAPSED": "Elapsed", + "CONTACT_DETAILS": "Contact Details", + "EMAIL": "Email", + "PHONE": "Phone", + "ORDERS_FORWARDING_DETAILS": "Orders Forwarding Details", + "ORDERS_FORWARDING_WITH": "Orders Forwarding With", + "MANAGE_STORE": "Manage Store", + "TOP_PRODUCTS": "Top Products", + "PRODUCTS_MANUFACTURING": "Products Manufacturing", + "CARRIER_REQUIRED": "Carrier required", + "MANAGE_WAREHOUSE": "Manage warehouse", + "MANAGE_STORE_PRODUCTS_&_ORDERS": "Manage store products & orders" + }, + "SIMULATION_VIEW": { + "SIMULATION": "Simulation", + "PURCHASE_PRODUCTS": "Purchase products", + "CREATE_INVITE_REQUEST": "Create Invite Request", + "SEND": "Send", + "CREATE_USER": "Create User", + "ORDER_CONFIRM": "Order Confirm", + "ORDER_CANCEL": "Order Cancel", + "PRODUCTS": "Products", + "STORE": "Store", + "ORDER": "Order", + "INVITE_REQUEST": "Invite-Request", + "INVITE_USER": "Invite User", + "TAB_BUTTONS": { + "PRODUCTS": "Products", + "ORDER_HISTORY": "Order history" + }, + "USER_MUTATION": { + "TITLE": "Create Customer", + "NAME": "Name", + "EMAIL": "Email", + "COUNTRY": "Country", + "CITY": "City", + "USA": "USA", + "ISRAEL": "Israel", + "BULGARIA": "Bulgaria", + "ADDRESS": "Address", + "POSTCODE": "Postcode", + "COORDINATES": "Coordinates", + "AUTO_DETECT_COORDINATES": "Auto detect coordinates", + "ERRORS": { + "FIRST_NAME_IS_REQUIRED": "First name is required", + "LAST_NAME_IS_REQUIRED": "Last name is required", + "INVALID_EMAIL": "Invalid Email", + "EMAIL_IS_REQUIRED": "Email is required", + "COORDINATES_ARE_REQUIRED": "Coordinates are required" + }, + "WIZARD_TITLES": { + "ADDITIONAL_INFO": "Additional Info", + "LOCATION": "Location" + } + }, + "SMART_TABLE": { + "TITLE": "Title", + "ID": "Id", + "IMAGE": "Image" + }, + "ORDER_INFO": { + "STATUSES": [ + { + "TITLE": "We're preparing the order!", + "DETAILS": "You will get it in %t minutes.", + "NOT_PAID_NOTE": "Prepare your wallet (%s in cash)." + }, + { + "TITLE": "Carrier on the way!", + "DETAILS": "You will get the order in %t min.", + "NOT_PAID_NOTE": "Prepare your wallet (%s in cash)." + }, + { + "TITLE": "Check your door!", + "DETAILS": "You will get the order in seconds.", + "NOT_PAID_NOTE": "Prepare your wallet (%s in cash)." + }, + { + "TITLE": "Order Completed!", + "DETAILS": "Thanks for using Ever", + "NOT_PAID_NOTE": "" + } + ], + "DELIVERY_STATUS": { + "WE": "We", + "CARRIER": "Carrier", + "YOU": "You" + }, + "DELIVERY_WRONG": { + "DELIVERY_WRONG": "The Delivery Went Wrong!", + "PROCESSING_WRONG": "Processing Went Wrong!", + "TRY_AGAIN": "Please try again.", + "CALL_FOR_DETAILS": "Call for details" + } + }, + "INSTRUCTIONS_STEPS": { + "INSTRUTIONS": "Instructions", + "CREATE_USER_STEP": { + "CREATE_USER": "Create User", + "STEP_1": "Step 1.", + "ORDER": "Order", + "TO_CONTINUE_IS_REQUIRED_TO_REGISTER_IN_THE_SYSTEM": "To continue is required to register in the system", + "CLICK_ON_BUTTON_CREATE_USER": "Click on button 'Create user'", + "FILL_THE_FORM_FOR_ADDITIONAL_INFO": "Fill the form for additional info (optional)", + "FILL_THE_FORM_FOR_LOCATION_AND_PRESS_DONE_BUTTON": "Fill the form for location and press DONE button" + }, + "ORDER_STEP": { + "ORDER": "Order", + "STEP_2": "Step 2.", + "CREATE_ORDER": "Create order", + "CHOICE_SOME_PRODUCTS_FROM_THE_TABLE": "Choice some products from the table (you can see more details about when press on his name or image)", + "SELECT_PRODUCT": "To be selected one product have to press on his row.", + "SELECT_BUTTON_ORDER_TO_CREATE_ORDER": "Press button 'Order' to create order with the selected product.", + "REVIEW_ORDER_HISTORY": "Review order history:", + "ON_PRESS_ORDER_HISTORY_TAB": "On press 'Order history' shows all your orders..", + "HERE_YOU_CAN_SEE_DETAILS_ABOUT_EACH_ORDER": "Here you can see details about each order", + "PRESS_ON_CARRIER_ORDER_OR_PRODUCT_NAME": "Press on carrier, order or product name for more information." + }, + "CONFIRM_OR_CANCEL_STEP": { + "STEP_3": "Step 3", + "CONFIRM_CANCEL_ORDER": "Confirm/Cancel order", + "REAL_TIME": "Real time", + "TRACK_STATUS_ON_YOUR_ORDER": "Track status on your order.", + "ELAPSED_TIME_FROM_CREATE_TO_DELIVERED": "Elapsed time from created to delivered.", + "SHOWS_MERCHANT_LOCATION": "Shows merchant location.", + "SHOWS_CARRIER_LOCATION": "Show carrier location.", + "POSSIBILITIES": "Possibilities:", + "SLIDER_REVIEW_OF_THE_ALL_PRODUCTS": "Slider review of the all products.", + "CAN_CANCEL_IT_WITH_ORDER_CANCEL_BUTTON": "Untill the order is not delivered you user cancel it with 'Order Cancel' button.", + "AFTER_THE_ORDER_IS_DELIVER_USER_CAN_CLICK_BUTTON_ORDER_CONFIRM_TO_CONTINUE": "After the order is delivered the user can click button 'Order Confirm' to continue.." + }, + "INVITE_STEP": { + "TO_CONTINUE_ITs_REQUIRED_TO_BE_INVITED_IN_THE_SYSTEM": "To continue it's required to be invited in the system:", + "SEND_INVITE_REQUEST": "Send 'Invite Request' to the system from the form which will be opened after pressing Invite Request button.", + "ALL_INVITE_REQUESTS_ARE_REVIEWED": "All invite requests are reviewed by admin and they can be invited if the system is available near your location (for test you can do immediately form Invite User button).", + "AFTER_YOU_GET_INVITED_BEFORE": "After you get invite you can easy login the system, just have to enter your invite code, which will be provided from the system (you will see here", + "AFTER_YOU_GET_INVITED_AFTER": "code after press Invite button, and can enter it when press Create User button)." + } + } + }, + "CUSTOMERS_VIEW": { + "TITLE": "Customers", + "DELETE_CUSTOMERS": "Delete", + "CREATE_CUSTOMER": "Create", + "DELETE": "DELETE", + "CREATE": "CREATE", + "BAN": "BAN", + "UNBAN": "UNBAN", + "MANAGE_CUSTOMER": "Manage customer", + "CUSTOMER": "Customer", + "CUSTOMERS_DEVICES": "Devices", + "INVITE": "Invite", + "NOT_INVITED_ONLY": "Not invited only", + "ORDER": "Order", + "ORDERS_STATISTICS": "Orders statistics", + "NUMBER_OF_ORDERS": "Number of Orders", + "CANCELED_ORDERS": "Canceled Orders", + "COMPLETED_ORDERS_TOTAL": "Completed Orders Total", + "Order": "Order", + "CANCEL_ORDER": "Cancel Order", + "CATEGORY": "Category", + "ORDERS_HISTORY": "Orders History", + "AVAILABLE_PRODUCTS": "Available products", + "NEARBY_STORES": "Nearby Stores", + "INVITES_REQUESTS_MANAGEMENT": "Invites requests management", + "INVITES_MANAGEMENT": "Invites management", + "DESCRIPTION": "Description", + "DETAILS": "Details", + "MAKE_A_CUSTOM_ORDER": "Make a custom order", + "PRODUCT_COUNT": "Product Count", + "QUANTITY_CAN'T_BE_EMPTY": "Quantity can't be empty", + "QUANTITY_CAN'T_BE_0": "Quantity can't be 0", + "NOT_ENOUGH_PRODUCTS_AVAILABLE": "Not enough products available", + "ORDER_INFO": "Order Info", + "ORDER_ID": "Order Id", + "STORE_ID": "Store Id", + "CARRIER_ID": "Carrier Id", + "NO_CARRIER": "No Carrier", + "SMART_TABLE_COLUMNS": { + "IMAGE": "Image", + "NAME": "Name", + "EMAIL": "Email", + "PHONE": "Phone", + "ADDRESS": "Address", + "ORDERS_QTY": "Orders QTY", + "COUNTRY": "Country", + "CITY": "City", + "STREET_ADDRESS": "Street Address", + "HOUSE": "House", + "APARTMENT": "Apartment", + "INVITE_CODE": "Invite Code", + "INVITED_DATE": "Invited date", + "ORDER_NUMBER": "Order Number", + "WAREHOUSE": "Warehouse", + "CARRIER": "Carrier", + "PRODUCT_LIST": "Product list", + "STATS": "Stats", + "DELIVERY_TIME": "Delivery Time", + "CREATED": "Created", + "ACTIONS": "Actions", + "PAID": "Paid", + "COMPLETED": "Completed", + "CANCELLED": "Cancelled", + "NOT_DELIVERED": "Not delivered", + "PRODUCT": "Product", + "PRICE": "Price", + "STORE": "Store", + "AVAILABLE_COUNT": "Available Count", + "ORDER": "Order", + "STATUS": "Status" + }, + "EDIT": { + "EDIT_CUSTOMER": "Edit Customer", + "BASIC_INFO": "Basic Info", + "SAVE": "Save" + }, + "DEVICE": { + "ALL_DEVICE": "All Device", + "DEVICE_ID": "Device ID", + "ID": "id", + "UPDATE": "Update", + "LANGUAGE": "language", + "TYPE": "type", + "TYPEU": "Type", + "UUID": "uuid", + "DEVICE_UUID": "Device UUID", + "UPDATE_DEVICE": "Update Device", + "CUSTOMERS_DEVICES": "Customers Devices", + "DELETE": "DELETE", + "CREATE": "CREATE" + }, + "WAREHOUSE": { + "WAREHOUSE_INFO": "Warehouse Info", + "WAREHOUSE_ID": "Warehouse Id", + "WAREHOUSE_NAME": "Warehouse Name" + }, + "CUSTOMER_VIEW": { + "MANAGE_CUSTOMER": "Manage customer", + "EDIT": "EDIT", + "CUSTOMER": "Customer" + }, + "INVITES_VIEW": { + "DELETE": "Delete", + "INVITE": "Invite" + } + }, + "CARRIERS_VIEW": { + "TITLE": "Carriers", + "DELETE_CARRIERS": "Delete Selected", + "CREATE_CARRIER": "Create Carrier", + "DELETE": "DELETE", + "CREATE_BUTTON": "CREATE", + "ACTIVE_AND_AVAILABLE_ORDERS": "Active and Available Orders", + "ORDERS_HISTORY": "Orders History", + "TRACK": "Track", + "SMART_TABLE_COLUMNS": { + "IMAGE": "Image", + "NAME": "Name", + "PHONE": "Phone", + "STATUS": "Status", + "ADDRESS": "Address", + "DELIVERIES": "Deliveries" + }, + "CARRIER_PAGE": { + "WAREHOUSE": "Warehouse", + "CUSTOMER": "Customer", + "SAVE": "Save", + "EDIT": "Edit", + "CARRIER_INFO": "Carrier Info", + "CARRIER_ID": "Carrier Id", + "REGISTER_NEW_CARRIER": "Register New Carrier", + "WAREHOUSE_STATUS": "Warehouse Status", + "CARRIER_STATUS": "Carrier Status", + "CREATED": "Created", + "ARRIVED_TO_CUSTOMER": "Arrived to Customer", + "FAILED": "Failed", + "DELIVERED": "Delivered", + "CLIENT_REFUSE_ORDER": "Client Refuse Order", + "AVAIBLE_ORDER_TO_PICK_UP": "Available Orders to pick up (each carrier can pick up multiple orders)", + "ACTIVE": "Active", + "CARRIER_CAN_BE_SHARED": "Carrier can be shared?", + "NOT_ACTIVE": "Not Active", + "WORKING": "Working", + "NOT_WORKING": "Not Working", + "SELECT_CARRIER": "Select carrier", + "CARRIER_ORDERS_STATUS": "Carrier orders status", + "Start": "Start", + "PICKED_UP_ORDER": "Picked Up Order", + "CANCEL": "Cancel", + "Arrived To Client": "Arrived To Client", + "No Carrier": "No Carrier", + "Order Selected For Delivery": "Order Selected For Delivery", + "Order Picked Up": "Order Picked Up", + "Order In Delivery": "Order In Delivery", + "Delivered": "Delivered", + "Delivery Issues": "Delivery Issues", + "Client Refuse to Take Order": "Client Refuse to Take Order", + "BAD_STATUS": "BAD_STATUS", + "Created": "Created", + "Confirmed": "Confirmed", + "Processing": "Processing", + "Allocation Started": "Allocation Started", + "Allocation Finished": "Allocation Finished", + "Packaging Started": "Packaging Started", + "Packaged": "Packaged", + "Given to Carrier": "Given to Carrier", + "Allocation Failed": "Allocation Failed", + "Packaging Failed": "Packaging Failed", + "LOCATION": "Location", + "TIME": "Time", + "NAME": "Name" + }, + "EDIT": { + "EDIT_CARRIER": "Edit Carrier", + "BASIC_INFO": "Basic Info", + "LOCATION": "Location", + "PHOTO_URL": "Photo Url", + "CONTACT_PHONE": "Contact Phone", + "FIRST_NAME": "First Name", + "LAST_NAME": "Last Name" + }, + "CREATE": { + "BASIC_INFO": "Basic Info", + "LOCATION": "Location" + }, + "TRACK_PAGE": { + "TRACK_ALL_WORKING_CARRIERS": "Track all working carriers", + "FILTER_CARRIERS": "Filter Carriers", + "PHONE": "Phone", + "EMAIL": "Email", + "ADDRESS": "Address", + "DELIVERY_COUNT": "Delivery Count" + } + }, + "PRODUCT_TYPE_VIEW": { + "TITLE": "New Product", + "WIZARD_FORM": { + "VALIDATION_MESSAGES": { + "TITLE": "Title is required", + "THE_LENGHT_OF_THE_TITLE": "The length of title should be max 255 characters long!", + "IMAGE": "Picture is required", + "DESCRIPTION": "Description is required", + "LANGUAGE": "Language is required", + "THE_LENGHT_OF_THE_DESCRIPTION": "The length of description should be max 255 characters long!", + "PRICE": "Price is required", + "COUNT": "Product count is required" + } + } + }, + "SHARED": { + "FORMS": { + "ERRORS": { + "FIRST_NAME_REQUIRED": "First name is required", + "LAST_NAME_REQUIRED": "Last name is required", + "USERNAME_REQUIRED": "Username is required", + "PASSWORD_REQUIRED": "Password is required", + "PHONE_REQUIRED": "Phone is required", + "LOGO_URL_REQUIRED": "Enter valid image URL or browse for a file", + "IS_ACTIVE": "Is active field is required", + "COUNTRY_REQUIRED": "Country is required", + "CITY_REQUIRED": "City is required", + "STREET_ADDRESS_REQUIRED": "Street address is required", + "HOUSE_REQUIRED": "House number is required", + "COORDINATES_REQUIRED": "Coordinates are required", + "MUST_CONTAIN_ONLY_LETTERS": "Must contain only letters", + "PHONE_MUST_CONTAINS_ONLY(specail_signs)AND_DIGIT_CHARACTER": "Phone number can start with '+' or '(some numbers)' and must contains only only: '-, ., (space), #'' and digit characters" + } + }, + "USER": { + "FORMS": { + "BASIC_INFO": { + "TITLE": "Basic Info", + "FIRST_NAME": "First Name", + "FIRST_NAME_OPTIONAL": "First Name (optional)", + "LAST_NAME_OPTIONAL": "Last Name (optional)", + "PHOTO_URL": "Photo Url", + "PICTURE_URL": "Picture url (optional)", + "EMAIL": "Email", + "EMAIL_OPTIONAL": "Email (optional)", + "ERRORS": { + "INVALID_EMAIL": "Invalid Email", + "EMAIL_IS_ALREADY_IN_USE": "Email is already in use" + } + } + } + }, + "WAREHOUSE": { + "ORDER_MODAL": { + "MAKE_ORDER": "Make Order", + "ONLY_AVAILABLE": "Show only available products", + "ORDER": "Order", + "SMART_TABLE": { + "TITLES": { + "IMG": "Img", + "PRODUCT": "Product", + "PRICE": "Price", + "AVAILABLE": "Available", + "AMOUNT": "Amount", + "COMMENT": "Comment" + } + } + } + } + }, + "MENU_VIEW": { + "DASHBOARD": "Dashboard", + "STORES": "Stores", + "PRODUCTS": { + "PRODUCTS": "Products", + "MANAGEMENT": "Management", + "CATEGORIES": "Categories" + }, + "CUSTOMERS": { + "CUSTOMERS": "Customers", + "MANAGEMENT": "Management", + "INVITES": "Invites" + }, + "CARRIERS": "Carriers", + "SIMULATION": "Simulation", + "SETUP": "Setup" + }, + "CATEGORY_VIEW": { + "TITLE": "Title", + "IMAGE": "Image", + "CREATE_BUTTON": "CREATE", + "DELETE": "DELETE", + "EDIT": { + "EDIT_CATEGORY": "Edit Category", + "CATEGORY_NAME": "Category Name", + "ENTER_THE_CATEGORY_NAME": "Enter the category name", + "DONE": "Done" + }, + "CREATE": { + "CREATE_CATEGORY": "Create Category", + "CATEGORY_NAME": "Category Name", + "ENTER_THE_CATEGORY_NAME": "Enter the category name", + "PHOTO": "Photo", + "BROWSE": "Browse", + "INVALID_URL": "Invalid Url", + "REMOVE_IMAGE": "Remove image", + "PHOTO_OPTIONAL": "Photo (optional)", + "DONE": "Done" + } + }, + "PLACEHOLDER": { + "EXAMPLE": "Example: Domino's Pizza", + "IMAGE_URL": "Image url", + "HERE_GOES_A_SHORT_DESCRIPTION": "Here goes a short description", + "HERE_GOES_A_DETAILS_ABOUT_PRODUCT_(OPTION)": "Here goes a details about product (option)", + "REMOVE_IMAGE": "Remove image", + "PASSWORD": "Password", + "LATITUDE": "Latitude", + "LONGITUDE": "Longitude", + "APARTMENT": "Apartment", + "HOUSE": "House", + "STREET": "Street", + "ZIP": "ZIP", + "CITY": "City", + "FIND_ADDRESS": "Find Address" + }, + "STATUS_TEXT": { + "Created": "Created", + "Confirmed": "Confirmed", + "Processing": "Processing", + "Allocation Started": "Allocation Started", + "Allocation Finished": "Allocation Finished", + "Packaging Started": "Packaging Started", + "Packaged": "Packaged", + "Given to Carrier": "Given to Carrier", + "Allocation Failed": "Allocation Failed", + "Packaging Failed": "Packaging Failed", + "No Carrier": "No Carrier", + "Order Selected For Delivery": "Order Selected For Delivery", + "Order Picked Up": "Order Picked Up", + "Order In Delivery": "Order In Delivery", + "Arrived To Client": "Arrived To Client", + "Delivered": "Delivered", + "Delivery Issues": "Delivery Issues", + "Client Refuse to Take Order": "Client Refuse to Take Order", + "Given to Customer": "Given to Customer", + "BAD_STATUS": "BAD_STATUS" + }, + "ELAPSED_TIME": { + "TITLE": "Elapsed time" + }, + "CONFIRM_MODAL": { + "ARE_YOU_SURE": "Are you sure?", + "ARE_YOU_SURE_YOU_WANT_TO_INCREASE": "Are you sure you want to increase the qty of products?", + "ARE_YOU_SURE_YOU_WANT_TO_DECREASE": "Are you sure you want to decrease the qty of products?", + "YES": "Yes", + "NO": "No" + }, + "NO_SERVER_VIEW": { + "NO_SERVER": "Server connection is lost" + }, + "BUTTON_NEXT": "Next", + "BUTTON_PREV": "Back", + "BUTTON_DONE": "Done", + "TERRAIN": "Terrain", + "SATELLITE": "Satellite", + "LOCATION": "Location", + "ROAD_MAP": "Road Map", + "Manage warehouse": "Manage store products & orders", + "Warehouse": "Warehouse", + "Create Warehouse": "Create Warehouse", + "SIMULATION": "Simulation", + "Purchase products": "Purchase products", + "Manage": "Manage", + "Orders": "Orders", + "Confirmed": "Confirmed", + "In Delivery": "In Delivery", + "Not Confirmed": "Not Confirmed", + "Not paid": "Not paid", + "Cancelled": "Cancelled", + "All": "All", + "CANCEL": "Cancel", + "Default Settings": "Default Settings", + "Products Manufacturing": "Products Manufacturing", + "Carrier required before sale": "Carrier required before sale", + "New Product Type": "New Product Type", + "Products": "Products", + "Product": "Product", + "Title": "Title", + "Picture Url": "Picture Url", + "Description": "Description", + "Details": "Details", + "Price": "Price", + "CATEGORY": "Category", + "LANGUAGE": "Language", + "BROWSE": "Browse", + "ENGLISH": "English", + "HEBREW": "Hebrew", + "RUSSIAN": "Russian", + "BULGARIAN": "Bulgarian", + "SPANISH": "Spanish", + "FRENCH": "French", + "SELECT": "Select", + "Name": "Name", + "Id": "Id", + "Warehouse name is required": "Warehouse name is required", + "Name must be at least 1 characters long": "Name must be at least 1 characters long", + "Title cannot be more than 255 characters long": "Title cannot be more than 255 characters long", + "Logo": "Logo", + "Warehouse logo is required": "Warehouse logo is required", + "is Active": "is Active", + "right now": "right now", + "Unselected": "Unselected", + "Phone": "Phone", + "Email": "Email", + "Username": "Username", + "Username is required": "Username is required", + "Password": "Password", + "Country": "Country", + "USA": "USA", + "Israel": "Israel", + "Bulgaria": "Bulgaria", + "City": "City", + "Address": "Address", + "Postcode": "Postcode", + "Coordinates": "Coordinates", + "Auto detect coordinates": "Auto detect coordinates", + "Carriers": "Carriers", + "Carrier": "Carrier", + "Use only specific carriers": "Use only specific carriers", + "Manage carrier and deliveries": "Manage carrier and deliveries", + "Register New Carrier": "Register New Carrier", + "Create User": "Create User", + "OPTIONAL": "optional" +} diff --git a/packages/admin-web-angular/src/assets/i18n/he-IL.json b/packages/admin-web-angular/src/assets/i18n/he-IL.json new file mode 100644 index 0000000..6eba3c2 --- /dev/null +++ b/packages/admin-web-angular/src/assets/i18n/he-IL.json @@ -0,0 +1,1155 @@ +{ + "COMMON": { + "SAVE": "להציל", + "CANCEL": "לְבַטֵל", + "USA": "ארה ב", + "ISRAEL": "ישראל", + "BULGARIA": "בולגריה" + }, + "ORDER_VIEW": { + "ORDER_HEADER_INFO": { + "ORDER": "להזמין", + "MANAGE_ORDER": "ניהול הזמנה", + "TOTAL": "סך הכל" + }, + "ORDER_PRODUCT_INFO": { + "ORDER_PRODUCTS": "הזמנת מוצרים", + "ADD_PRODUCTS": "הוסף מוצרים", + "REMOVE_PRODUCTS": "הסר מוצרים", + "CANCEL_ORDER": "בטל הזמנה", + "THE_ORDER_IS_CANCELED": "ההזמנה בוטלה", + "THE_ORDER_IS_GIVEN_TO_CARRIER": "ההזמנה ניתנת למוביל.", + "THE_ORDER_IS_DELIVERED": "ההזמנה מסופקת.", + "ADD_PRODUCTS_MODAL": "הוסף מוצרים", + "ADD": "הוסף", + "SUCCESS_TOAST": "המוצרים נוספו להזמנה", + "ERROR_TOAST": "שגיאה, משהו השתבש", + "SMART_TABLE": { + "NAME": "שם", + "QTY": "QTY", + "PRICE": "מחיר", + "IMAGE": "תמונה", + "COMMENT": "תגובה" + } + }, + "ORDER_SIDEBAR": { + "CONTACT_DETAILS": "פרטי קשר", + "WAREHOUSE": "מחסן", + "CUSTOMER": "צרכן", + "CARRIER": "המוביל", + "QTY": "qty" + }, + "LOCATION_INFO": { + "MAP": "מפה", + "DELIVERY_DISTANCE": "מרחק נסיעה:" + } + }, + "DASHBOARD_VIEW": { + "TOTAL_CUSTOMER": "סה'כ לקוחות", + "TOTAL_QUANTITY_OF_EXISTING_CUSTOMERS": "סך כל הלקוחות הקיימים", + "TOTAL_COMPLETED_ORDERS": "סה\"כ הזמנות שהושלמו", + "TOTAL_QUANTITY_OF_COMPLETED_ORDERS": "סך כל ההזמנות שהושלמו", + "TOTAL_REVENUE": "סך הרווחים", + "TOTAL_SUM_SUM_OF_ALL_COMPLETED_ORDERS": "סך כל ההזמנות שהושלמו", + "TODAYs_CUSTOMERS": "לקוחות של היום", + "TODAYs_QUANTITY_OF_REGISTERED_CUSTOMERS": "כמות הלקוחות הרשומים כיום", + "TODAYs_COMPLETED_ORDERS": "הזמנות שהושלמו היום", + "TODAYs_QUANTITY_OF_NEW_COMPLETED_ORDERS": "כמות היום של הזמנות שהושלמו", + "TODAYs_REVENUE": "הכנסות היום", + "TODAYs_SUM_OF_COMPLETED_ORDERS": "הסכום הכולל של הזמנות שהושלמו", + "TILL_AVERAGE": "עד ממוצע", + "BETTER_THAN_AVERAGE": "יותר מהממוצע", + "SELECT_COMPONENT": { + "STORES": "חנויות", + "CONTACT_DETAILS": "פרטי קשר", + "PHONE": "מכשיר טלפון", + "EMAIL": "דוא'ל", + "ORDERS_FORWARDING_WITH": "משלוח עם", + "SELECT_STORE": "בחר בחנות" + }, + "CHARTS": { + "ORDERS": "הזמנות", + "PROFIT": "רווח", + "TOTAL_ORDERS": "סה\"כ הזמנות", + "TOTAL_COMPLETED_ORDERS": "סה\"כ הזמנות שהושלמו", + "TOTAL_CANCELLED_ORDERS": "Total canceled orders", + "TOTAL_REVENUE_ALL_ORDERS": "סך כל ההכנסות מכל ההזמנות", + "TOTAL_REVENUE_COMPLETED_ORDERS": "סך ההכנסות מהזמנות שהושלמו", + "TOTAL_LOST_REVENUE_CANCELLED_ORDERS": "Total lost revenue from canceled orders", + "PAYMENT": "תשלום", + "CANCELED": "מבוטל", + "ALL_ORDERS": "כל ההזמנות", + "TODAY": "היום", + "LAST_WEEK": "שבוע שעבר", + "LAST_MONTH": "חודש שעבר", + "CURRENT_YEAR": "השנה הנוכחית", + "YEARS": "לפי שנה", + "CUSTOM_PERIOD": "תקופה מותאמת אישית", + "SELECT_PERIOD": "בחר תקופה", + "FROM": "מ", + "TO": "כדי", + "SELECT": "בחר", + "LABELS": { + "WEEK": "שבוע", + "WEEKDAYS": { + "MON": "שני", + "TUE": "שלישי", + "WED": "רביעי", + "THU": "חמישי", + "FRI": "שישי", + "SAT": "שבת", + "SUN": "שמש" + }, + "MONTHS": { + "JAN": "יאן", + "FEB": "פברואר", + "MAR": "מאר", + "APR": "אפריל", + "MAY": "מאי", + "JUN": "יוני", + "JUL": "יול", + "AUG": "אוגוסט", + "SEP": "ספטמבר", + "OCT": "אוקטובר", + "NOV": "נובמבר", + "DEC": "דצמבר" + } + } + } + }, + "HEADER_VIEW": { + "ADMIN": "מנהל מערכת", + "EVER": "EverHE", + "PROFILE": "פרופיל", + "LOG_OUT": "להתנתק" + }, + "FOOTER_VIEW": { + "COPY_RIGHT": "זכויות יוצרים © 2016-היום", + "ALL_RIGHTS_RESERVED": "כל הזכויות שמורות" + }, + "PROFILE_VIEW": { + "PROFILE_PAGE": "עמוד פרופיל", + "BASIC_INFO": "מידע בסיסי", + "ACCOUNT": "חשבון", + "USERNAME": "שם משתמש", + "ERROR": "שגיאה", + "EMAIL": "דוא'ל", + "FIRST_NAME": "שם פרטי", + "FIRST_NAME_OPTIONAL": "שם פרטי (אופציונלי)", + "LAST_NAME": "שם משפחה (אופציונלי)", + "PICTURE_URL": "כתובת אתר של תמונה (אופציונלי)", + "BROWSE": "עיון", + "REMOVE": "הסר", + "SAVE": "להציל", + "OLD_PASSWORD": "סיסמה ישנה", + "NEW_PASSWORD": "סיסמה חדשה", + "REPEAT_NEW_PASSWORD": "חזור על סיסמה חדשה", + "INVALID_EMAIL_ADDRESS": "כתובת אימייל לא חוקית", + "INVALID_URL": "כתובת אתר לא חוקית", + "NAME_MUST_CONTAIN_ONLY_LETTERS": "השם חייב להכיל רק אותיות", + "PASSWORDS_DO_NOT_MATCH": "סיסמאות לא תואמות", + "SUCCESSFULLY_CHANGE_PASSWORD": "שנה סיסמה בהצלחה" + }, + "PRODUCTS_VIEW": { + "DELETE": "מחק", + "CREATE": "צור", + "EDIT_VIEW": { + "EDIT_PRODUCT": "ערוך מוצר", + "BASIC_INFO": "מידע בסיסי", + "SAVE": "להציל" + } + }, + "FAKE_DATA": { + "FAKE_DATA_GENERATOR": "מחולל נתונים שקרי", + "GENERATE_ALL": "צור הכל", + "CREATE_100_USERS": "צור 100 משתמשים", + "CREATE_100_CARRIERS": "צור 100 מחסנים", + "CREATE_100_WAREHOUSES": "צור 100 מחסנים", + "SETUP": "להכין", + "GENERATE_INITIAL_DATA": "יצירת נתונים ראשוניים", + "CREATE_INVITE": "צור הזמנה", + "HARDCODED_DATA": "נתונים קשיחים", + "CLEAR_ALL": "נקה הכל", + "GENERATE_HARDCODED_ONLY": "צור Hardcoded בלבד", + "INCLUDED_HARDCODED_DATA": "כלול נתונים עם קוד קשיח", + "CREATE_1st_INVITE": "צור הזמנה ראשונה", + "CREATE_2st_INVITE": "צור הזמנה שנייה", + "CREATE_3st_INVITE": "צור הזמנה שלישית", + "CREATE_4st_INVITE": "צור הזמנה 4", + "CREATE_CUSTOMER": "צור לקוח", + "CREATE_USER": "צור משתמש", + "CREATE_1st_USER": "צור משתמש ראשון (באמצעות הזמנה ראשונה)", + "CREATE_CARRIER": "צור המוביל", + "CREATE_1st_CARRIER": "צור ספק ראשון", + "CREATE_2nd_CARRIER": "צור ספק שני", + "CREATE_3rd_CARRIER": "צור ספק שלישי", + "CREATE_PRODUCT": "צור מוצר", + "CREATE_PEPERONI&MUSHROOM_PIZZA_PRODUCT": "יצירת Peperoni & פטריות מוצר פיצה", + "CREATE_SUSHI&CAVIAR_PRODUCT": "יצירת סושי & קוויאר המוצר", + "CREATE_SUSHI_MIX_PRODUCT": "צור מוצר לערבב סושי", + "CREATE_PASTA_PRODUCT": "צור מוצר פסטה", + "CREATE_SUSHI_BOX_PRODUCT": "יצירת מוצר תיבת סושי", + "CREATE_PEPERONI&TOMATO_PIZZA_PRODUCT": "יצירת Peperoni & פיצה עגבניות המוצר", + "CREATE_WAREHOUSE": "ליצור חדר כושר", + "CREATE_1st_WAREHOUSE": "צור מחסן ראשון", + "CREATE_2nd_WAREHOUSE": "צור מחסן שני", + "CREATE_3rd_WAREHOUSE": "צור מחסן שלישי", + "CREATE_WAREHOUSE_PRODUCT": "צור מוצר", + "CREATE_1st_WAREHOUSE_PRODUCTS(using_product_number_1_2_3_4_5_and_6)": "יצירת מוצרי מחסן 1 (באמצעות מספר מוצר 1, 2, 3, 4, 5 ו -6)", + "CREATE_3rd_WAREHOUSE_PRODUCTS(using_1st_product)": "צור מוצרי מחסן שני (באמצעות מוצר מספר 1, 2 ו -3", + "CREATE_2nd_WAREHOUSE_PRODUCTS(using_product_number_1_2_and_3": "צור מוצרי מחסן שני (באמצעות מוצר מספר 1, 2 ו -3", + "UPDATE_WAREHOUSE_GEO_LOCATION": "עדכון מיקום מקום", + "UPDATE_1ST_WAREHOUSE_GEO_LOCATION": "עדכון מיקום גיאוגרפי 1 מחסן", + "CREATE_1RD_ORDER(using_1st_warehouse_1st_user_and_1st_product)": "יצירת הזמנה 1rd (באמצעות מחסן 1, המשתמש הראשון ו 1 מוצר)", + "CREATE_2ND_ORDER(using_1st_warehouse_1st_user_and_2nd_product)": "צור הזמנה שנייה (באמצעות המחסן הראשון, המשתמש הראשון והמוצר השני)", + "CONFIRM_ORDER": "אשר הזמנה", + "CREATE_ORDER": "צור הזמנה", + "CONFIMR_1st_ORDER": "אשר הזמנה ראשונה", + "CONFIMR_2nd_ORDER": "אשר את ההזמנה השנייה", + "SETUP_MERCHANTS": { + "SETUP_MERCHANTS": "הגדר סוחרים", + "PREV": "PREV", + "NEXT": "הבא", + "ADD": "הוסף", + "BACK": "חזור", + "SAVE": "להציל", + "SELECT": "בחר", + "CREATE": "צור", + "HOW_TO_SET_UP": { + "HOW_TO_SET_UP": "איך להציב" + }, + "STEPPER": { + "ACCOUNT": "חשבון", + "BASIC_INFO": "מידע בסיסי", + "CONTACT_INFO": "פרטים ליצירת קשר", + "LOCATION": "מקום", + "PAYMENTS": "תשלומים", + "MANUFACTURING": "ייצור", + "DELIVERY_AND_TAKEAWAY_SETTINGS": "הגדרות משלוח & Takeaway", + "ORDERS_SETTINGS": "הגדרות הזמנות", + "PRODUCT_CATEGORIES": "קטגוריות מוצרים", + "PRODUCTS": "מוצרים" + }, + "ACCOUNT": { + "ACCOUNT": "חשבון", + "EMAIL_ADDRESS": "כתובת דוא'ל", + "EMAIL": "דוא'ל", + "PASSWORD": "סיסמה", + "REPEAT_PASSWORD": "חזור על הסיסמה", + "EMAIL_IS_REQUIRED": "נדרש דוא'ל", + "INVALID_EMAIL_FORMAT": "פורמט דוא'ל לא חוקי", + "USERNAME": "שם משתמש", + "USERNAME_IS_REQUIRED": "נדרש שם משתמש", + "USERNAME_MUST_BE_AT_LEAST_3_CHARACTERS": "שם המשתמש חייב להיות לפחות 3 תווים", + "PASSWORD_IS_REQUIRED": "דרושה סיסמא", + "PASSWORD_MUST_BE_AT_LEAST_4_CHARACTERS": "הסיסמה צריכה להיות לפחות 4 תווים", + "REPEAT_PASSWORD_IS_REQUIRED": "נדרשת סיסמה חוזרת", + "PASSWORDS_DO_NOT_MATCH": "סיסמאות לא תואמות" + }, + "BASIC_INFO": { + "BASIC_INFO": "מידע בסיסי", + "NAME": "שם", + "NAME_IS_REQUIRED": "נדרש שם", + "NAME_MUST_BE_AT_LEAST_4_CHARACTERS": "השם חייב להיות לפחות 4 תווים", + "PHOTO": "תמונה", + "ENTER_A_VALID_LOGO_URL_OR_BROWSE_FROM_A_DEVICE": "הזן כתובת אתר חוקית של לוגו או דפדף ממכשיר", + "REMOVE": "הסר", + "PHOTO_OPTIONAL": "תמונה (אופציונלי)", + "BARCODE_DATA": "נתוני ברקוד", + "BARCODE_DATA_IS_REQUIRED": "נתוני ברקוד נדרשים" + }, + "CONTACT_INFO": { + "CONTACT_INFO": "פרטים ליצירת קשר", + "CONTACT_PHONE": "צור קשר עם הטלפון", + "INVALID_PHONE_NUMBER_FORMAT": "פורמט מספר טלפון לא חוקי", + "ORDER_FORWARDING_EMAIL": "סדר העברת דואר אלקטרוני", + "ORDER_FORWARDING_PHONE": "הזמנת טלפון העברה", + "ORDERS_EMAIL": "דוא'ל דוא'ל", + "ORDERS_EMAIL_IS_REQUIRED": "יש צורך בהודעות דוא'ל", + "INVALID_EMAIL_FORMAT": "פורמט דוא'ל לא חוקי", + "ORDERS_PHONE": "טלפון הטלפונים", + "ORDERS_PHONE_IS_REQUIRED": "טלפון הטלפון נדרש" + }, + "LOCATION": { + "LOCATION": "מקום" + }, + "PAYMENTS": { + "PAYMENTS": "תשלומים", + "ALLOW_ONLINE_PAYMENT": "האם לאפשר תשלומים מקוונים?", + "ALLOW_CASH_PAYMENT": "אפשר תשלומים במזומן?", + "STRIPE": { + "PAY_BUTTON_TEXT": "טקסט של לחצן 'תשלום'", + "CURRENCY": "מטבע", + "PAY_BUTTON_TEXT_IS_REQUIRED": "יש צורך בטקסט של לחצן שלם", + "CHOOSE_CURRENCY_CODE": "בחר קוד מטבע", + "CURRENCY_TEXT_IS_REQUIRED": "טקסט מטבע נדרש", + "COMPANY_BRAND_LOGO": "לוגו מותג החברה", + "COMPANY_BRAND_LOGO_IS_REQUIRED": "הלוגו של מותג החברה נדרש", + "INVALID_LOGO_URL": "כתובת אתר של לוגו לא חוקית", + "INVALID_LOGO": "לוגו לא חוקי", + "PUBLISHABLE_KEY": "מפתח ניתן לפרסום", + "PUBLISHABLE_KEY_IS_REQUIRED": "נדרש מפתח לפרסום", + "ALLOW_REMEMBER_ME": "אתה זוכר אותי?", + "REMOVE": "הסר" + }, + "PAYPAL": { + "MODE": "מצב", + "CHOOSE_PAYPAL_MODE": "בחר במצב PayPal", + "TYPE": "הקלד", + "CURRENCY": "מטבע", + "CHOOSE_CURRENCY_CODE": "בחר קוד מטבע", + "CURRENCY_TEXT_IS_REQUIRED": "טקסט מטבע נדרש", + "PUBLISHABLE_KEY": "מפתח ניתן לפרסום", + "PUBLISHABLE_KEY_IS_REQUIRED": "נדרש מפתח לפרסום", + "SECRET_KEY": "מפתח סודי", + "SECRET_KEY_IS_REQUIRED": "נדרש מפתח סודי", + "PAYMENT_DESCRIPTION": "תיאור התשלום", + "PAYMENT_DESCRIPTION_IS_REQUIRED": "תיאור התשלום נדרש" + } + }, + "MANUFACTURING": { + "MANUFACTURING": "ייצור", + "PRODUCTS_MANUFACTURING": "ייצור מוצרים" + }, + "DELIVERY_AND_TAKEAWAY_SETTINGS": { + "DELIVERY_AND_TAKEAWAY_SETTINGS": "הגדרות משלוח & Takeaway", + "SELECT_FROM_SHARED_CARRIERS": "בחר מתוך ספקים משותפים", + "ADD_YOUR_CARRIER": "הוסף את הספק שלך", + "EDIT_CARRIER": "ערוך ספק", + "CARRIER_REQUIRED": "נדרש נושא", + "PRODUCTS_DELIVERY_BY_DEFAULT": "משלוח מוצרים (כברירת מחדל)", + "PRODUCTS_TAKEAWAY_BY_DEFAULT": "מוצרים Takeaway (כברירת מחדל)", + "USE_SELECTED_SHARED_CARRIERS": "השתמש במפעילים משותפים נבחרים", + "ADD_YOUR_CARRIERS": "הוסף את הספקים שלך" + }, + "ORDERS_SETTINGS": { + "ORDERS_SETTINGS": "הגדרות הזמנות", + "ORDER_BARCODE_QR_CODE_TYPES": "ציין את סוג קוד הברקוד / qr" + }, + "PRODUCT_CATEGORIES": { + "PRODUCT_CATEGORIES": "קטגוריות מוצרים", + "ADD_OWN_PRODUCT_CATEGORY": "הוסף את הקטגוריה של המוצר עצמו" + }, + "PRODUCTS": { + "PRODUCTS": "מוצרים", + "SELECT_FROM_PRODUCTS_CATALOG": "בחר מתוך קטלוג מוצרים", + "CREATE_PRODUCT": "צור מוצר", + "EDIT_PRODUCT": "ערוך מוצר", + "ADD_PRODUCT": "הוסף מוצר", + "CREATE_NEW_PRODUCT": "צור מוצר חדש" + } + } + }, + "WAREHOUSE_MANAGE": { + "TITLE": "ניהול מחסן", + "MANAGE_STORE": "ניהול חנות", + "SAVE": "להציל", + "NAME": "שֵׁם", + "USERNAME": "שם משתמש", + "PASSWORD": "סיסמה", + "COUNTRY": "מדינה", + "CITY": "עִיר", + "POSTCODE": "מיקוד", + "IS_ACTIVE": "הוא פעיל", + "PRODUCTS_MANUFACTURING": "ייצור מוצרים", + "CARRIER_REQUIRED": "נדרש נושא", + "RIGHT_NOW": "(עכשיו)", + "CARRIERS": "ספקים", + "ADDRESS": "כתובת", + "CARRIERS_SPECIFIC": "השתמש רק בספקים ספציפיים", + "VALIDATION": { + "NAME": "נדרש שם", + "USERNAME": "נדרש שם משתמש", + "PASSWORD": "דרושה סיסמא", + "COUNTRY": "ארץ נדרשת", + "CITY": "עיר נדרשת", + "STREET": "רחוב נדרש", + "HOUSE": "מספר הבית נדרש", + "POSTCODE": "מיקוד נדרש" + }, + "WIZARD_TITLES": { + "DETAILS": "פרטים", + "ACCOUNT": "חֶשְׁבּוֹן", + "CONTACT_INFO": "פרטים ליצירת קשר", + "LOCATION": "מקום", + "PAYMENT": "תשלום", + "DELIVERY_ZONES": "אזורי אספקה" + } + }, + "WAREHOUSES_VIEW": { + "TITLE": "מחסנים", + "DELETE_WAREHOUSES": "מחק נבחרה", + "DELETE": "מחק", + "CREATE": "צור", + "SHOW_ON_MAP": "הצג על המפה", + "SMART_TABLE_COLUMNS": { + "IMAGE": "תמונה", + "NAME": "שֵׁם", + "EMAIL": "אֶלֶקטרוֹנִי", + "PHONE": "טלפון", + "CITY": "עִיר", + "ADDRESS": "כתובת", + "ORDERS_QTY": "כמות ברזל", + "ORDERS": "הזמנות" + }, + "INFO": { + "STORE_INFO": "מידע חנות", + "STORE_ID": "מזהה חנות", + "STORE_NAME": "שם חנות" + }, + "MERCHANTS": { + "TRACK_ALL_MERCHANTS": "עקוב אחר כל הסוחרים", + "FILTER_MERCHANTS": "סוחרי סינון", + "FILTER_BY_NAME": "לסנן לפי שם", + "FILTER_BY_CITY": "סנן לפי עיר", + "FILTER_BY_COUNTRY": "סנן לפי מדינה" + } + }, + "WAREHOUSE_VIEW": { + "ORDER": { + "CONFIRM": "לְאַשֵׁר", + "START_PROCESSING": "התחל עיבוד", + "START_ALLOCATION": "התחל הקצאה", + "ALLOCATED": "מוּקצֶה", + "ALLOCATION_FAILS": "כישלון ההקצאה", + "START_PACKAGING": "התחל אריזה", + "PACKAGED": "ארוז", + "PACKAGING_FAILS": "אריזות נכשל", + "GIVEN_TO_CARRIER": "ניתן ל- Carrier", + "GIVEN_TO_CUSTOMER": "ניתן ללקוח", + "ORDER": "להזמין", + "CANT_PROCESSING_WITHOUT_PRODUCTS": "לא ניתן לעבד את ההזמנה ללא מוצרים." + }, + "MUTATION": { + "TITLE": "צור מחסן חדש", + "NAME": "שֵׁם", + "LOGO": "סֵמֶל", + "PHOTO": "תמונה", + "IS_ACTIVE": "הוא פעיל", + "PRODUCTS_MANUFACTURING": "ייצור מוצרים", + "CARRIER_REQUIRED": "נדרש נושא", + "RIGHT_NOW": "עכשיו", + "ORDERS_PHONE": "טלפון הטלפונים", + "CONTACT_PHONE": "צור קשר עם הטלפון", + "ORDERS_EMAIL": "דוא'ל עבור הזמנות", + "CONTACT_EMAIL": "אליקטרונה פוצ'ה פורצ'קי", + "FORWARD_ORDERS_WITH": "קדימה עם הזמנות", + "USERNAME": "שם משתמש", + "OLD_PASSWORD": "סיסמה ישנה", + "NEW_PASSWORD": "סיסמה חדשה", + "CONFIRM_PASSWORD": "אשר סיסמה", + "PASSWORDS_DO_NOT_MATCH": "סיסמאות לא תואמות", + "PASSWORD": "סיסמה", + "COUNTRY": "מדינה", + "USA": "ארה ב", + "ISRAEL": "ישראל", + "BULGARIA": "בולגריה", + "CITY": "עִיר", + "ADDRESS": "כתובת", + "POSTCODE": "מיקוד", + "COORDINATES": "קואורדינטות", + "AUTO_DETECT_COORDINATES": "זיהוי אוטומטי של קואורדינטות", + "CARRIERS": "ספקים", + "USE_ONLY_SPECIFIC_CARRIERS": "השתמש רק בספקים ספציפיים", + "SELECT_SHAPE_TO_ADD_ZONE": "בחר צורה להוסיף אזור חדש", + "CIRCLE": "מעגל", + "SHAPE": "צוּרָה", + "DRAW_SHAPE_ON_MAP": "צייר צורה על המפה", + "MINIMUM_AMOUNT": "כמות מינימלית", + "DELIVERY_FEE": "דמי משלוח", + "CANCEL": "לְבַטֵל", + "ADD": "לְהוֹסִיף", + "EDIT": "לַעֲרוֹך", + "ZONE_NAME": "שם אזור", + "IN_STORE_MODE": "במצב חנות", + "ERRORS": { + "NAME_IS_REQUIRED": "שם המחסן נדרש", + "NAME_ATLEAST_3_CHARS": "השם חייב להיות באורך של לפחות 3 תווים", + "NAME_MORE_THAN_255_CHARS": "השם לא יכול להכיל יותר מ -255 תווים", + "LOGO_IS_REQUIRED": "הלוגו נדרש", + "INVALID_URL": "הזן את כתובת האתר של התמונה", + "PHONE_CONTAINS_ONLY_DIGIT": "מספר הטלפון יכול להתחיל עם '+' והוא חייב להכיל רק: ',,., (רווח), #' 'ותווים ספרותיים", + "INVALID_EMAIL": "אימייל שגוי", + "ORDERS_PHONE_IS_REQUIRED": "טלפון טלפון נדרש", + "CONTACT_PHONE_IS_REQUIRED": "טלפון ליצירת קשר נדרש", + "ORDERS_EMAIL_IS_REQUIRED": "כתובת דוא'ל של מייל נדרשת", + "CONTACT_EMAIL_IS_REQUIRED": "דוא'ל ליצירת קשר נדרש", + "USERNAME_IS_REQUIRED": "נדרש שם משתמש", + "PASSWORD_IS_REQUIRED": "דרושה סיסמא", + "COORDINATES_ARE_REQUIRED": "יש צורך בקואורדינטות", + "COUNTRY_IS_REQUIRED": "ארץ נדרשת" + }, + "WIZARD_TITLES": { + "BASIC_INFO": "מידע בסיסי", + "CONTACT_INFO": "פרטים ליצירת קשר", + "LOCATION": "מקום" + }, + "CONTACT_INFO_TAB": { + "ORDER_FORWARDING_EMAIL": "סדר העברת דואר אלקטרוני", + "ORDER_FORWARDING_PHONE": "הזמנת טלפון העברה" + } + }, + "NEW_PRODUCT": { + "SELECT_FROM_PRODUCTS_CATALOG": "בחר מתוך קטלוג מוצרים", + "CREATE_NEW_PRODUCT": "צור מוצר חדש", + "HOW_TO_ADD": "איך להוסיף", + "ADD": "לְהוֹסִיף", + "SAVE": "להציל", + "ADD_PRODUCTS_TO_STORE": "הוסף מוצרים לאחסון", + "NOTHING_FOUND": "שום דבר לא נמצא..." + }, + "SELECT_PRODUCTS": { + "TITLE": "כותרת", + "DESCRIPTION": "תיאור", + "DETAILS": "פרטים", + "IMAGES": "תמונות", + "CATEGORY": "קטגוריה" + }, + "SAVE": { + "PRODUCT_NAME": "שם מוצר", + "PRICE": "מחיר", + "COUNT": "לספור", + "DELIVERY": "משלוח", + "TAKEAWAY": "להסיר" + }, + "PLACEHOLDER": { + "EXAMPLE": "דוגמה: פיצה של דומינו", + "IMAGE_URL": "כתובת אתר של תמונה", + "HERE_GOES_A_SHORT_DESCRIPTION": "הנה תיאור קצר", + "HERE_GOES_A_DETAILS_ABOUT_PRODUCT_(OPTION)": "הנה הולך פרטים על המוצר (אפשרות)", + "REMOVE_IMAGE": "הסר את התמונה" + }, + "CREATE_ORDER_MODAL": { + "TITLE": { + "CREATE_ORDER": "צור הזמנה" + }, + "WIZARD_FORM": { + "BUTTON_NEXT": "הַבָּא", + "BUTTON_PREV": "חזור", + "BUTTON_DONE": "סיים את ההזמנה", + "STEP1": { + "TITLE": "בחר באפשרות 'אפשרות", + "SELECT_FROM_EXISTING": "בחר מתוך קיים", + "ADD_NEW_CUSTOMER": "הוסף לקוח חדש" + }, + "STEP2": { + "TITLE": { + "SELECT_CUSTOMER": "בחר לקוח", + "SELECT_ADD": "בחר / הוסף", + "ADD_NEW": "הוסף חדש" + }, + "SMART_TABLE": { + "TITLES": { + "FULL_NAME": "שם מלא", + "EMAIL": "אֶלֶקטרוֹנִי", + "PHONE": "טלפון", + "ADDRESS": "כתובת" + } + } + }, + "STEP3": { + "TITLE": "צור הזמנה" + } + } + }, + "PRODUCTS_TAB": { + "PRODUCTS": "מוצרים", + "ADD_PRODUCTS": "הוסף מוצרים", + "DELETE": "מחק", + "IMAGE": "תמונה", + "TITLE": "כותרת", + "DESCRIPTION": "תיאור", + "DETAILS": "פרטים", + "CATEGORY": "קטגוריה", + "PRICE": "מחיר", + "QUANTITY": "כמות", + "CLICK_ON_PRODUCT_IMAGE_TO_INCREASE_AVAILABLE_QUANTITY": "לחץ על Image Product כדי להגדיל את הכמות הזמינה", + "AVAILABILITY": "זמינות", + "TYPE": "סוּג", + "DELIVERY": "מְסִירָה", + "TAKEAWAY": "להסיר" + }, + "NEW_PRODUCT_TYPE": "הספק נדרש לפני המכירה", + "ADD_PRODUCTS": "הוסף מוצרים", + "CREATE_ORDER": "צור הזמנה", + "STATUS": "סטָטוּס", + "ORDERS": "הזמנות", + "WAREHOUSE": "מחסן", + "PRODUCT": "מוצר", + "PRODUCTS": "מוצרים", + "ORDER_NUMBER": "מספר הזמנה", + "CANCELLED": "מבוטל", + "WAREHOUSE_STATUS": "סטטוס מחסן", + "CARRIER_STATUS": "סטטוס הספק", + "PAID": "בתשלום", + "CARRIER": "מוֹבִיל", + "CREATED": "נוצר", + "ELAPSED": "חלפו", + "CONTACT_DETAILS": "פרטי קשר", + "EMAIL": "אֶלֶקטרוֹנִי", + "PHONE": "טלפון", + "ORDERS_FORWARDING_DETAILS": "פרטים על העברת המשלוח", + "ORDERS_FORWARDING_WITH": "משלוח עם", + "MANAGE_STORE": "ניהול חנות", + "TOP_PRODUCTS": "מוצרים מובילים", + "PRODUCTS_MANUFACTURING": "ייצור מוצרים", + "CARRIER_REQUIRED": "נדרש נושא", + "MANAGE_WAREHOUSE": "ניהול מחסן", + "MANAGE_STORE_PRODUCTS_&_ORDERS": "ניהול מוצרי המחסן והזמנות" + }, + "SIMULATION_VIEW": { + "SIMULATION": "סימולציה", + "PURCHASE_PRODUCTS": "רכישת מוצרים", + "CREATE_INVITE_REQUEST": "צור בקשת הזמנה", + "SEND": "שלח", + "CREATE_USER": "צור משתמש", + "ORDER_CONFIRM": "אישור ההזמנה", + "ORDER_CANCEL": "ביטול הזמנה", + "PRODUCTS": "מוצרים", + "STORE": "חנות", + "ORDER": "להזמין", + "INVITE_REQUEST": "הזמן הזמנה", + "INVITE_USER": "הזמנת משתמש", + "TAB_BUTTONS": { + "PRODUCTS": "מוצרים", + "ORDER_HISTORY": "היסטוריית הזמנות" + }, + "USER_MUTATION": { + "TITLE": "צור לקוח", + "NAME": "שֵׁם", + "EMAIL": "אֶלֶקטרוֹנִי", + "COUNTRY": "מדינה", + "CITY": "עִיר", + "USA": "ארה ב", + "ISRAEL": "ישראל", + "BULGARIA": "בולגריה", + "ADDRESS": "כתובת", + "POSTCODE": "מיקוד", + "COORDINATES": "קואורדינטות", + "AUTO_DETECT_COORDINATES": "זיהוי אוטומטי של קואורדינטות", + "ERRORS": { + "FIRST_NAME_IS_REQUIRED": "נדרש שם פרטי", + "LAST_NAME_IS_REQUIRED": "נדרש שם משפחה", + "INVALID_EMAIL": "אימייל שגוי", + "EMAIL_IS_REQUIRED": "נדרש דואל", + "COORDINATES_ARE_REQUIRED": "יש צורך בקואורדינטותd" + }, + "WIZARD_TITLES": { + "ADDITIONAL_INFO": "מידע נוסף", + "LOCATION": "מקום" + } + }, + "SMART_TABLE": { + "TITLE": "כותרת", + "ID": "תְעוּדַת זֶהוּת", + "IMAGE": "תמונה" + }, + "ORDER_INFO": { + "STATUSES": [ + { + "TITLE": "אנחנו מכינים את ההזמנה!", + "DETAILS": "ההזמנה תהיה בידך בעוד %t דקות.", + "NOT_PAID_NOTE": "תכין/י את הארנק (%s במזומן)." + }, + { + "TITLE": "השליח בדרך!", + "DETAILS": "ההזמנה תהיה בידך בעוד %t דקות.", + "NOT_PAID_NOTE": "תכין/י את הארנק (%s במזומן)." + }, + { + "TITLE": "בדוק את הדלת שלך!", + "DETAILS": "ההזמנה תהיה בידך בעוד רגע.", + "NOT_PAID_NOTE": "תכין/י את הארנק (%s במזומן)." + }, + { + "TITLE": "ההזמנה הושלמה!", + "DETAILS": "תודה על השימוש ב-Ever", + "NOT_PAID_NOTE": "" + } + ], + "DELIVERY_STATUS": { + "WE": "אנחנו", + "CARRIER": "השליח", + "YOU": "אתה" + }, + "DELIVERY_WRONG": { + "DELIVERY_WRONG": "המסירה השתבשה!", + "PROCESSING_WRONG": "עיבוד התבצעה טעות!", + "TRY_AGAIN": "בבקשה נסה שוב.", + "CALL_FOR_DETAILS": "התקשר לקבלת פרטים" + } + }, + "INSTRUCTIONS_STEPS": { + "INSTRUTIONS": "הוראות", + "CREATE_USER_STEP": { + "CREATE_USER": "צור משתמש", + "STEP_1": "שלב 1", + "ORDER": "להזמין", + "TO_CONTINUE_IS_REQUIRED_TO_REGISTER_IN_THE_SYSTEM": "כדי להמשיך צריך להירשם במערכת", + "CLICK_ON_BUTTON_CREATE_USER": "לחץ על כפתור 'צור משתמש'", + "FILL_THE_FORM_FOR_ADDITIONAL_INFO": "מלא את הטופס לקבלת מידע נוסף (אופציונלי)", + "FILL_THE_FORM_FOR_LOCATION_AND_PRESS_DONE_BUTTON": "מלא את הטופס עבור המיקום ולחץ על כפתור DONE" + }, + "ORDER_STEP": { + "ORDER": "להזמין", + "STEP_2": "שלב 2.", + "CREATE_ORDER": "צור הזמנה", + "CHOICE_SOME_PRODUCTS_FROM_THE_TABLE": "לבחור כמה מוצרים מהטבלה (אתה יכול לראות פרטים נוספים על מתי ללחוץ על שמו או התמונה)", + "SELECT_PRODUCT": "כדי לבחור מוצר אחד צריך ללחוץ על השורה שלו.", + "SELECT_BUTTON_ORDER_TO_CREATE_ORDER": "לחץ על הלחצן 'הזמנה' כדי ליצור הזמנה עם המוצר שנבחר.", + "REVIEW_ORDER_HISTORY": "סקור את היסטוריית ההזמנות:", + "ON_PRESS_ORDER_HISTORY_TAB": "לחץ על 'היסטוריית הזמנות' מציג את כל ההזמנות שלך ..", + "HERE_YOU_CAN_SEE_DETAILS_ABOUT_EACH_ORDER": "כאן תוכל לראות פרטים על כל הזמנה", + "PRESS_ON_CARRIER_ORDER_OR_PRODUCT_NAME": "לחץ על שם הספק, ההזמנה או שם המוצר לקבלת מידע נוסף." + }, + "CONFIRM_OR_CANCEL_STEP": { + "STEP_3": "שלב 3", + "CONFIRM_CANCEL_ORDER": "אישור / ביטול הזמנה", + "REAL_TIME": "זמן אמת", + "TRACK_STATUS_ON_YOUR_ORDER": "עקוב אחר סטטוס ההזמנה שלך.", + "ELAPSED_TIME_FROM_CREATE_TO_DELIVERED": "הזמן שחלף מ נוצר כדי נמסר.", + "SHOWS_MERCHANT_LOCATION": "מציג מיקום של סוחר.", + "SHOWS_CARRIER_LOCATION": "הצג מיקום של ספק.", + "POSSIBILITIES": "אפשרויות:", + "SLIDER_REVIEW_OF_THE_ALL_PRODUCTS": "סקירה המחוון של כל המוצרים.", + "CAN_CANCEL_IT_WITH_ORDER_CANCEL_BUTTON": "עד שההזמנה לא תישלח, המשתמש יבטל אותה בלחצן 'ביטול הזמנה'.", + "AFTER_THE_ORDER_IS_DELIVER_USER_CAN_CLICK_BUTTON_ORDER_CONFIRM_TO_CONTINUE": "לאחר ביצוע ההזמנה, המשתמש יכול ללחוץ על הלחצן 'אישור הזמנה' כדי להמשיך .." + }, + "INVITE_STEP": { + "TO_CONTINUE_ITs_REQUIRED_TO_BE_INVITED_IN_THE_SYSTEM": "כדי להמשיך את הדרוש לה להיות מוזמן במערכת:", + "SEND_INVITE_REQUEST": "שלח 'הזמן הזמנה' למערכת מהטופס שייפתח לאחר לחיצה על הלחצן 'בקש הזמנה'.", + "ALL_INVITE_REQUESTS_ARE_REVIEWED": "כל הזמנות ההזמנות נבדקות על ידי מנהל המערכת וניתן להזמין אותן אם המערכת זמינה ליד המיקום שלך (לבדיקה ניתן לבצע באופן מיידי טופס הזמנת משתמש כפתור).", + "AFTER_YOU_GET_INVITED_BEFORE": "לאחר שתזמין אתה יכול להיכנס למערכת בקלות, רק צריך להזין את קוד ההזמנה שלך, אשר יסופקו מהמערכת (תראה כאן", + "AFTER_YOU_GET_INVITED_AFTER": "לאחר לחיצה על כפתור ההזמנה, ניתן להזין אותו בעת לחיצה על לחצן יצירת משתמש)." + } + } + }, + "CUSTOMERS_VIEW": { + "TITLE": "לקוחות", + "DELETE_CUSTOMERS": "מחק נבחרה", + "CREATE_CUSTOMER": "צור לקוח", + "DELETE": "מחק", + "CREATE": "צור", + "BAN": "BAN", + "UNBAN": "UNBAN", + "MANAGE_CUSTOMER": "ניהול לקוחות", + "CUSTOMER": "צרכן", + "CUSTOMERS_DEVICES": "התקני לקוחות", + "INVITE": "להזמין", + "NOT_INVITED_ONLY": "לא הוזמן בלבד", + "ORDER": "להזמין", + "ORDERS_STATISTICS": "הסטטיסטיקה של ההזמנה", + "NUMBER_OF_ORDERS": "סה\"כ הזמנות", + "CANCELED_ORDERS": "הזמנות שנעשו", + "COMPLETED_ORDERS_TOTAL": "מחיר של כל ההזמנות", + "Order": "להזמין", + "CANCEL_ORDER": "בטל הזמנה", + "CATEGORY": "קטגוריה", + "ORDERS_HISTORY": "היסטוריה", + "AVAILABLE_PRODUCTS": "מוצרים זמינים", + "NEARBY_STORES": "ניהול לקוחות", + "INVITES_REQUESTS_MANAGEMENT": "מזמין ניהול בקשות", + "INVITES_MANAGEMENT": "ניהול הזמנות", + "DESCRIPTION": "תיאור", + "DETAILS": "פרטים", + "MAKE_A_CUSTOM_ORDER": "בצע הזמנה מותאמת אישית", + "PRODUCT_COUNT": "ספירת מוצרים", + "QUANTITY_CAN'T_BE_EMPTY": "הכמות אינה יכולה להיות ריקה", + "QUANTITY_CAN'T_BE_0": "הכמות אינה יכולה להיות 0", + "NOT_ENOUGH_PRODUCTS_AVAILABLE": "אין מספיק מוצרים זמינים", + "ORDER_INFO": "פרטי הזמנה", + "ORDER_ID": "מספר הזמנה", + "STORE_ID": "מזהה חנות", + "CARRIER_ID": "מזהה ספק", + "NO_CARRIER": "אין ספק", + "SMART_TABLE_COLUMNS": { + "IMAGE": "תמונה", + "NAME": "שֵׁם", + "EMAIL": "אֶלֶקטרוֹנִי", + "PHONE": "טלפון", + "ADDRESS": "כתובת", + "ORDERS_QTY": "כמות ברזל", + "COUNTRY": "מדינה", + "CITY": "עִיר", + "STREET_ADDRESS": "כתובת רחוב", + "HOUSE": "בַּיִת", + "APARTMENT": "דִירָה", + "INVITE_CODE": "קוד הזמנה", + "INVITED_DATE": "תאריך מוזמן", + "ORDER_NUMBER": "מספר הזמנה", + "WAREHOUSE": "מַחסָן", + "CARRIER": "מוֹבִיל", + "PRODUCT_LIST": "רשימת מוצרים", + "STATS": "נתונים סטטיסטיים", + "DELIVERY_TIME": "זמן משלוח", + "CREATED": "נוצר", + "ACTIONS": "פעולות", + "PAID": "בתשלום", + "COMPLETED": "הושלם", + "CANCELLED": "מבוטל", + "NOT_DELIVERED": "לא נשלח", + "PRODUCT": "מוצר", + "PRICE": "מחיר", + "STORE": "חֲנוּת", + "AVAILABLE_COUNT": "ספירה זמינה", + "ORDER": "להזמין", + "STATUS": "סטָטוּס" + }, + "EDIT": { + "EDIT_CUSTOMER": "ערוך לקוח", + "BASIC_INFO": "מידע בסיסי", + "SAVE": "להציל" + }, + "DEVICE": { + "ALL_DEVICE": "כל ההתקנים", + "DEVICE_ID": "מזהה מכשיר", + "ID": "תְעוּדַת זֶהוּת", + "UPDATE": "עדכון", + "LANGUAGE": "שפה", + "TYPE": "סוּג", + "TYPEU": "סוּג", + "UUID": "מזהה ייחודי אוניברסלי", + "DEVICE_UUID": "מזהה ייחודי אוניברסלי", + "UPDATE_DEVICE": "עדכן את ההתקן", + "CUSTOMERS_DEVICES": "התקני לקוחות", + "DELETE": "מחק", + "CREATE": "צור" + }, + "WAREHOUSE": { + "WAREHOUSE_INFO": "מידע על מחסן", + "WAREHOUSE_ID": "מזהה מחסן", + "WAREHOUSE_NAME": "שם המחסן" + }, + "CUSTOMER_VIEW": { + "MANAGE_CUSTOMER": "ניהול לקוחות", + "EDIT": "ערוך", + "CUSTOMER": "צרכן" + }, + "INVITES_VIEW": { + "DELETE": "מחק", + "INVITE": "להזמין" + } + }, + "CARRIERS_VIEW": { + "TITLE": "ספקים", + "DELETE_CARRIERS": "מחק נבחרה", + "CREATE_CARRIER": "צור ספק", + "DELETE": "מחק", + "CREATE_BUTTON": "צור", + "ACTIVE_AND_AVAILABLE_ORDERS": "הזמנות זמינות וזמינות", + "ORDERS_HISTORY": "היסטוריה", + "TRACK": "לעקוב", + "SMART_TABLE_COLUMNS": { + "IMAGE": "תמונה", + "NAME": "שם", + "PHONE": "טלפון", + "STATUS": "סטטוס", + "ADDRESS": "כתובת", + "DELIVERIES": "משלוחים" + }, + "CARRIER_PAGE": { + "WAREHOUSE": "מַחסָן", + "CUSTOMER": "צרכן", + "SAVE": "להציל", + "EDIT": "לַעֲרוֹך", + "CARRIER_INFO": "מידע על ספק", + "CARRIER_ID": "מזהה ספק", + "REGISTER_NEW_CARRIER": "הרשמה ספק חדש", + "WAREHOUSE_STATUS": "סטטוס מחסן", + "CARRIER_STATUS": "סטטוס הספק", + "CREATED": "נוצר", + "ARRIVED_TO_CUSTOMER": "הגיע ללקוח", + "FAILED": "נִכשָׁל", + "DELIVERED": "נמסר", + "CLIENT_REFUSE_ORDER": "הזמנת לקוח", + "AVAIBLE_ORDER_TO_PICK_UP": "הזמנות זמינות להרים (כל מוביל יכול לאסוף הזמנות מרובות)", + "ACTIVE": "פָּעִיל", + "CARRIER_CAN_BE_SHARED": "ניתן לשתף את המוביל?", + "NOT_ACTIVE": "לא פעיל", + "WORKING": "עובד", + "NOT_WORKING": "לא עובד", + "SELECT_CARRIER": "בחר ספק", + "CARRIER_ORDERS_STATUS": "סטטוס הזמנות של ספק", + "Start": "הַתחָלָה", + "PICKED_UP_ORDER": "הזמנת הזמנה", + "CANCEL": "לְבַטֵל", + "Arrived To Client": "הגיע ללקוח", + "No Carrier": "אין ספק", + "Order Selected For Delivery": "ההזמנה נבחרה למשלוח", + "Order Picked Up": "ההזמנה הסתיימה", + "Order In Delivery": "סדר בהזמנה", + "Delivered": "נמסר", + "Delivery Issues": "הנפקות משלוח", + "Client Refuse to Take Order": "הלקוח מסרב לקבל הזמנה", + "BAD_STATUS": "BAD_STATUS", + "Created": "נוצר", + "Confirmed": "מְאוּשָׁר", + "Processing": "מעבד", + "Allocation Started": "ההקצאה החלה", + "Allocation Finished": "ההקצאה הסתיימה", + "Packaging Started": "האריזה התחילה", + "Packaged": "ארוז", + "Given to Carrier": "ניתן ל- Carrier", + "Allocation Failed": "ההקצאה נכשלה", + "Packaging Failed": "האריזות נכשלו", + "LOCATION": "מקום", + "TIME": "זמן", + "NAME": "שם" + }, + "EDIT": { + "EDIT_CARRIER": "ערוך ספק", + "BASIC_INFO": "ערוך ספק", + "LOCATION": "מקום", + "PHOTO_URL": "כתובת תמונה", + "CONTACT_PHONE": "צור קשר עם הטלפון", + "FIRST_NAME": "שם פרטי", + "LAST_NAME": "שם משפחה" + }, + "CREATE": { + "BASIC_INFO": "מידע בסיסי", + "LOCATION": "מקום" + }, + "TRACK_PAGE": { + "TRACK_ALL_WORKING_CARRIERS": "עקוב אחר כל ספקי השירות", + "FILTER_CARRIERS": "מסנני מסננים", + "PHONE": "מכשיר טלפון", + "EMAIL": "דוא'ל", + "ADDRESS": "כתובת", + "DELIVERY_COUNT": "ספירת משלוח" + } + }, + "PRODUCT_TYPE_VIEW": { + "TITLE": "מוצר חדש", + "WIZARD_FORM": { + "VALIDATION_MESSAGES": { + "TITLE": "הכותרת נדרשת", + "THE_LENGHT_OF_THE_TITLE": "אורך הכותרת צריך להיות מקסימום 255 תווים!", + "IMAGE": "תמונה נדרשת", + "DESCRIPTION": "תיאור נדרש", + "LANGUAGE": "נדרשת שפה", + "THE_LENGHT_OF_THE_DESCRIPTION": "אורך התיאור צריך להיות מקסימלי 255 תווים!", + "PRICE": "המחיר נדרש", + "COUNT": "יש לספור את המוצר" + } + } + }, + "SHARED": { + "FORMS": { + "ERRORS": { + "FIRST_NAME_REQUIRED": "נדרש שם פרטי", + "LAST_NAME_REQUIRED": "נדרש שם משפחה", + "USERNAME_REQUIRED": "נדרש שם משתמש", + "PASSWORD_REQUIRED": "דרושה סיסמא", + "PHONE_REQUIRED": "טלפון נדרש", + "LOGO_URL_REQUIRED": "כתובת האתר של הלוגו חייבת להתחיל עם 'https'", + "IS_ACTIVE": "האם נדרש שדה פעיל", + "COUNTRY_REQUIRED": "ארץ נדרשת", + "CITY_REQUIRED": "ארץ נדרשת", + "STREET_ADDRESS_REQUIRED": "נדרשת כתובת רחוב", + "HOUSE_REQUIRED": "מספר הבית נדרש", + "COORDINATES_REQUIRED": "יש צורך בקואורדינטות", + "MUST_CONTAIN_ONLY_LETTERS": "חייב להכיל רק אותיות", + "PHONE_MUST_CONTAINS_ONLY(specail_signs)AND_DIGIT_CHARACTER": "מספר הטלפון יכול להתחיל עם '+' והוא חייב להכיל רק: ',,., (רווח), #' 'ותווים ספרותיים" + } + }, + "USER": { + "FORMS": { + "BASIC_INFO": { + "TITLE": "מידע בסיסי", + "FIRST_NAME": "שם פרטי", + "FIRST_NAME_OPTIONAL": "שם פרטי (אופציונלי)", + "LAST_NAME_OPTIONAL": "שם משפחה (אופציונלי)", + "PHOTO_URL": "כתובת תמונה", + "PICTURE_URL": "כתובת אתר של תמונה (אופציונלי)", + "EMAIL": "אֶלֶקטרוֹנִי", + "EMAIL_OPTIONAL": "דוא'ל (אופציונלי)", + "ERRORS": { + "INVALID_EMAIL": "אימייל שגוי", + "EMAIL_IS_ALREADY_IN_USE": "דוא'ל כבר בשימוש" + } + } + } + }, + "WAREHOUSE": { + "ORDER_MODAL": { + "MAKE_ORDER": "בצע הזמנה", + "ONLY_AVAILABLE": "הצג רק מוצרים זמינים", + "ORDER": "להזמין", + "SMART_TABLE": { + "TITLES": { + "IMG": "תמונה", + "PRODUCT": "מוצר", + "PRICE": "מחיר", + "AVAILABLE": "זמין", + "AMOUNT": "כמות", + "COMMENT": "תגובה" + } + } + } + } + }, + "MENU_VIEW": { + "DASHBOARD": "מרכז השליטה", + "STORES": "חנויות", + "PRODUCTS": { + "PRODUCTS": "מוצרים", + "MANAGEMENT": "הַנהָלָה", + "CATEGORIES": "קטגוריות" + }, + "CUSTOMERS": { + "CUSTOMERS": "לקוחות", + "MANAGEMENT": "הַנהָלָה", + "INVITES": "הזמנות" + }, + "CARRIERS": "ספקים", + "SIMULATION": "סימולציה", + "SETUP": "להכין" + }, + "CATEGORY_VIEW": { + "TITLE": "כותרת", + "IMAGE": "תמונה", + "CREATE_BUTTON": "צור", + "DELETE": "מחק", + "EDIT": { + "EDIT_CATEGORY": "ערוך קטגוריה", + "CATEGORY_NAME": "שם קטגוריה", + "ENTER_THE_CATEGORY_NAME": "הזן את שם הקטגוריה", + "DONE": "בוצע" + }, + "CREATE": { + "CREATE_CATEGORY": "צור קטגוריה", + "CATEGORY_NAME": "שם קטגוריה", + "ENTER_THE_CATEGORY_NAME": "הזן את שם הקטגוריה", + "PHOTO": "תמונה", + "BROWSE": "לְדַפדֵף", + "INVALID_URL": "כתובת אתר לא חוקית", + "REMOVE_IMAGE": "הסר את התמונה", + "PHOTO_OPTIONAL": "תמונה (אופציונלי)", + "DONE": "בוצע" + } + }, + "PLACEHOLDER": { + "EXAMPLE": "דוגמה: פיצה של דומינו", + "IMAGE_URL": "כתובת אתר של תמונה", + "HERE_GOES_A_SHORT_DESCRIPTION": "הנה תיאור קצר", + "HERE_GOES_A_DETAILS_ABOUT_PRODUCT_(OPTION)": "הנה הולך פרטים על המוצר (אפשרות)", + "REMOVE_IMAGE": "הסר את התמונה", + "PASSWORD": "סיסמה", + "LATITUDE": "קו רוחב", + "LONGITUDE": "קו האורך", + "APARTMENT": "דירה", + "HOUSE": "בַּיִת", + "STREET": "רחוב", + "ZIP": "פוסט קוד", + "CITY": "עִיר", + "FIND_ADDRESS": "חפש כתובת" + }, + "STATUS_TEXT": { + "Created": "נוצר", + "Confirmed": "מְאוּשָׁר", + "Processing": "מעבד", + "Allocation Started": "ההקצאה החלה", + "Allocation Finished": "ההקצאה הסתיימה", + "Packaging Started": "האריזה התחילה", + "Packaged": "ארוז", + "Given to Carrier": "ניתן ל- Carrier", + "Allocation Failed": "ההקצאה נכשלה", + "Packaging Failed": "האריזות נכשלו", + "No Carrier": "אין ספק", + "Order Selected For Delivery": "ההזמנה נבחרה למשלוח", + "Order Picked Up": "ההזמנה הסתיימה", + "Order In Delivery": "סדר בהזמנה", + "Arrived To Client": "הגיע ללקוח", + "Delivered": "נמסר", + "Delivery Issues": "הנפקות משלוח", + "Client Refuse to Take Order": "הלקוח מסרב לקבל הזמנה", + "Given to Customer": "ניתן ללקוח", + "BAD_STATUS": "BAD_STATUS" + }, + "ELAPSED_TIME": { + "TITLE": "הזמן שחלף" + }, + "CONFIRM_MODAL": { + "ARE_YOU_SURE": "האם אתה בטוח", + "ARE_YOU_SURE_YOU_WANT_TO_INCREASE": "האם אתה בטוח שברצונך להגדיל את כמות המוצרים?", + "ARE_YOU_SURE_YOU_WANT_TO_DECREASE": "האם אתה בטוח שברצונך להקטין את כמות המוצרים?", + "YES": "כן", + "NO": "לא" + }, + "NO_SERVER_VIEW": { + "NO_SERVER": "השרת נפל" + }, + "BUTTON_NEXT": "הַבָּא", + "BUTTON_PREV": "חזור", + "BUTTON_DONE": "סיים את ההזמנה", + "TERRAIN": "פְּנֵי הַשֵׁטַח", + "SATELLITE": "הלוויין", + "LOCATION": "מקום", + "ROAD_MAP": "מַפַּת דְרָכִים", + "Manage warehouse": "ניהול מוצרי המחסן והזמנות", + "Warehouse": "מַחסָן", + "Create Warehouse": "צור גלריית תמונות", + "SIMULATION": "סימולציה", + "Purchase products": "רכישת מוצרים", + "Manage": "לנהל", + "Orders": "הזמנות", + "Confirmed": "מְאוּשָׁר", + "In Delivery": "במסירה", + "Not Confirmed": "לא אושר", + "Not paid": "לא שולמו", + "Cancelled": "מבוטל", + "All": "את כל", + "CANCEL": "לְבַטֵל", + "Default Settings": "הגדרות ברירת מחדל", + "Products Manufacturing": "ייצור מוצרים", + "Carrier required before sale": "הספק נדרש לפני המכירה", + "New Product Type": "הספק נדרש לפני המכירה", + "Products": "מוצרים", + "Product": "מוצר", + "Title": "כותרת", + "Picture Url": "כתובת אתר של תמונה", + "Description": "תיאור", + "Details": "פרטים", + "Price": "מחיר", + "CATEGORY": "קטגוריה", + "LANGUAGE": "שפה", + "BROWSE": "לְדַפדֵף", + "ENGLISH": "אנגלית", + "HEBREW": "עִברִית", + "RUSSIAN": "רוּסִי", + "BULGARIAN": "בולגרית", + "SPANISH": "ספרדית", + "FRENCH": "צָרְפָתִית", + "SELECT": "בחר", + "Name": "שֵׁם", + "Id": "תְעוּדַת זֶהוּת", + "Warehouse name is required": "שם המחסן נדרש", + "Name must be at least 1 characters long": "השם חייב להיות באורך של לפחות 1 תווים", + "Title cannot be more than 255 characters long": "אורך הכותרת לא יכול להיות באורך של יותר מ -255 תווים", + "Logo": "סֵמֶל", + "Warehouse logo is required": "לוגו מחסן נדרש", + "is Active": "הוא פעיל", + "right now": "עכשיו", + "Unselected": "Unselected", + "Phone": "מכשיר טלפון", + "Email": "אֶלֶקטרוֹנִי", + "Username": "שם משתמש", + "Username is required": "נדרש שם משתמש", + "Password": "סיסמה", + "Country": "מדינה", + "USA": "ארה'ב", + "Israel": "ישראל", + "Bulgaria": "בולגריה", + "City": "עִיר", + "Address": "כתובת", + "Postcode": "מיקוד", + "Coordinates": "קואורדינטות", + "Auto detect coordinates": "זיהוי אוטומטי של קואורדינטות", + "Carriers": "ספקים", + "Carrier": "מוֹבִיל", + "Use only specific carriers": "השתמש רק בספקים ספציפיים", + "Manage carrier and deliveries": "נהל ספק ומשלוחים", + "Register New Carrier": "הרשמה ספק חדש", + "Create User": "צור משתמש", + "OPTIONAL": "אופציונאלי" +} diff --git a/packages/admin-web-angular/src/assets/i18n/he.json b/packages/admin-web-angular/src/assets/i18n/he.json new file mode 100644 index 0000000..99b2af1 --- /dev/null +++ b/packages/admin-web-angular/src/assets/i18n/he.json @@ -0,0 +1,1169 @@ +{ + "COMMON": { + "SAVE": "Save", + "CANCEL": "Cancel", + "USA": "USA", + "ISRAEL": "Israel", + "BULGARIA": "Bulgaria" + }, + "ORDER_VIEW": { + "ORDER_HEADER_INFO": { + "ORDER": "Order", + "MANAGE_ORDER": "Manage Order", + "TOTAL": "Total" + }, + "ORDER_PRODUCT_INFO": { + "ORDER_PRODUCTS": "Order Products", + "ADD_PRODUCTS": "Add products", + "REMOVE_PRODUCTS": "Remove Products", + "CANCEL_ORDER": "Cancel Order", + "THE_ORDER_IS_CANCELED": "The order is canceled", + "THE_ORDER_IS_GIVEN_TO_CARRIER": "The order is given to carrier.", + "THE_ORDER_IS_DELIVERED": "The order is delivered.", + "ADD_PRODUCTS_MODAL": "Add Products", + "ADD": "Add", + "SUCCESS_TOAST": "Products were added to the order", + "ERROR_TOAST": "Error, something went wrong", + "SMART_TABLE": { + "NAME": "Name", + "QTY": "QTY", + "PRICE": "Price", + "IMAGE": "Image", + "COMMENT": "Comment" + } + }, + "ORDER_SIDEBAR": { + "CONTACT_DETAILS": "Contact Details", + "WAREHOUSE": "Warehouse", + "CUSTOMER": "Customer", + "CARRIER": "Carrier", + "QTY": "qty" + }, + "LOCATION_INFO": { + "MAP": "Map", + "DELIVERY_DISTANCE": "Delivery Distance:" + } + }, + "DASHBOARD_VIEW": { + "TOTAL_CUSTOMER": "Total Customers", + "TOTAL_QUANTITY_OF_EXISTING_CUSTOMERS": "Total quantity of existing customers", + "TOTAL_COMPLETED_ORDERS": "Total completed orders", + "TOTAL_QUANTITY_OF_COMPLETED_ORDERS": "Total quantity of completed orders", + "TOTAL_REVENUE": "Total Revenue", + "TOTAL_SUM_SUM_OF_ALL_COMPLETED_ORDERS": "Total sum of all completed orders", + "TODAYs_CUSTOMERS": "Today's Customers", + "TODAYs_QUANTITY_OF_REGISTERED_CUSTOMERS": "Today's quantity of registered customers", + "TODAYs_COMPLETED_ORDERS": "Today's completed orders", + "TODAYs_QUANTITY_OF_NEW_COMPLETED_ORDERS": "Today's quantity of new completed orders", + "TODAYs_REVENUE": "Today's Revenue", + "TODAYs_SUM_OF_COMPLETED_ORDERS": "Today's sum of completed orders", + "TILL_AVERAGE": "till average", + "BETTER_THAN_AVERAGE": "better than average", + "SELECT_COMPONENT": { + "STORES": "Stores", + "CONTACT_DETAILS": "Contact Details", + "PHONE": "Phone", + "EMAIL": "Email", + "ORDERS_FORWARDING_WITH": "Orders Forwarding With", + "SELECT_STORE": "Select store" + }, + "CHARTS": { + "ORDERS": "Orders", + "PROFIT": "Profit", + "TOTAL_ORDERS": "Total orders", + "TOTAL_COMPLETED_ORDERS": "Total completed orders", + "TOTAL_CANCELLED_ORDERS": "Total canceled orders", + "TOTAL_REVENUE_ALL_ORDERS": "Total revenue from all orders", + "TOTAL_REVENUE_COMPLETED_ORDERS": "Total revenue from completed orders", + "TOTAL_LOST_REVENUE_CANCELLED_ORDERS": "Total lost revenue from canceled orders", + "PAYMENT": "Payment", + "CANCELED": "Canceled", + "ALL_ORDERS": "All Orders", + "TODAY": "Today", + "LAST_WEEK": "Last Week", + "LAST_MONTH": "Last Month", + "CURRENT_YEAR": "Current Year", + "YEARS": "By Year", + "CUSTOM_PERIOD": "Custom Period", + "SELECT_PERIOD": "Select Period", + "FROM": "From", + "TO": "To", + "SELECT": "Select", + "LABELS": { + "WEEK": "Week", + "WEEKDAYS": { + "MON": "Mon", + "TUE": "Tue", + "WED": "Wed", + "THU": "Thu", + "FRI": "Fri", + "SAT": "Sat", + "SUN": "Sun" + }, + "MONTHS": { + "JAN": "Jan", + "FEB": "Feb", + "MAR": "Mar", + "APR": "Apr", + "MAY": "May", + "JUN": "Jun", + "JUL": "Jul", + "AUG": "Aug", + "SEP": "Sep", + "OCT": "Oct", + "NOV": "Nov", + "DEC": "Dec" + } + } + } + }, + "HEADER_VIEW": { + "ADMIN": "Admin", + "EVER": "Ever", + "PROFILE": "Profile", + "LOG_OUT": "Log out" + }, + "FOOTER_VIEW": { + "COPY_RIGHT": "Copyright © 2016-present", + "ALL_RIGHTS_RESERVED": "All rights reserved" + }, + "PROFILE_VIEW": { + "PROFILE_PAGE": "Profile page", + "BASIC_INFO": "Basic Info", + "ACCOUNT": "Account", + "USERNAME": "Username", + "ERROR": "Error", + "EMAIL": "Email", + "FIRST_NAME": "First Name", + "FIRST_NAME_OPTIONAL": "First Name (optional)", + "LAST_NAME": "Last Name (optional)", + "PICTURE_URL": "Picture url (optional)", + "BROWSE": "Browse", + "REMOVE": "Remove", + "SAVE": "Save", + "OLD_PASSWORD": "Old password", + "NEW_PASSWORD": "New password", + "REPEAT_NEW_PASSWORD": "Repeat new password", + "INVALID_EMAIL_ADDRESS": "Invalid email address", + "INVALID_URL": "Invalid URL", + "NAME_MUST_CONTAIN_ONLY_LETTERS": "Name must contain only letters", + "PASSWORDS_DO_NOT_MATCH": "Passwords do not match", + "SUCCESSFULLY_CHANGE_PASSWORD": "Successfully change password" + }, + "PRODUCTS_VIEW": { + "DELETE": "DELETE", + "CREATE": "CREATE", + "EDIT_VIEW": { + "EDIT_PRODUCT": "Edit Product", + "BASIC_INFO": "Basic Info", + "SAVE": "Save" + } + }, + "FAKE_DATA": { + "FAKE_DATA_GENERATOR": "Fake Data Generator", + "GENERATE_ALL": "Generate All", + "CREATE_100_USERS": "Create 100 customers", + "CREATE_100_CARRIERS": "Create 100 carriers", + "CREATE_100_WAREHOUSES": "Create 100 warehouses", + "SETUP": "SETUP", + "GENERATE_INITIAL_DATA": "Generate Initial Data", + "CREATE_INVITE": "CREATE INVITE", + "HARDCODED_DATA": "Hardcoded data", + "CLEAR_ALL": "CLEAR ALL", + "GENERATE_HARDCODED_ONLY": "Generate Hardcoded Only", + "INCLUDED_HARDCODED_DATA": "Include hardcoded data", + "CREATE_1st_INVITE": "Create 1st invite", + "CREATE_2st_INVITE": "Create 2nd invite", + "CREATE_3st_INVITE": "Create 3rd invite", + "CREATE_4st_INVITE": "Create 4rd invite", + "CREATE_CUSTOMER": "Create Customer", + "CREATE_USER": "CREATE USER", + "CREATE_1st_USER": "Create 1st user (using 1st invite)", + "CREATE_CARRIER": "CREATE CARRIER", + "CREATE_1st_CARRIER": "Create 1st carrier", + "CREATE_2nd_CARRIER": "Create 2nd carrier", + "CREATE_3rd_CARRIER": "Create 3rd carrier", + "CREATE_PRODUCT": "CREATE PRODUCT", + "CREATE_PEPERONI&MUSHROOM_PIZZA_PRODUCT": "Create Peperoni & Mushroom pizza product", + "CREATE_SUSHI&CAVIAR_PRODUCT": "Create Sushi & Caviar product", + "CREATE_SUSHI_MIX_PRODUCT": "Create Sushi mix product", + "CREATE_PASTA_PRODUCT": "Create Pasta product", + "CREATE_SUSHI_BOX_PRODUCT": "Create Sushi box product", + "CREATE_PEPERONI&TOMATO_PIZZA_PRODUCT": "Create Peperoni & Tomato pizza product", + "CREATE_WAREHOUSE": "CREATE WAREHOUSE", + "CREATE_1st_WAREHOUSE": "Create 1st warehouse", + "CREATE_2nd_WAREHOUSE": "Create 2nd warehouse", + "CREATE_3rd_WAREHOUSE": "Create 3rd warehouse", + "CREATE_WAREHOUSE_PRODUCT": "CREATE WAREHOUSE PRODUCT", + "CREATE_1st_WAREHOUSE_PRODUCTS(using_product_number_1_2_3_4_5_and_6)": "Create 1st warehouse products (using product number 1, 2, 3, 4, 5 and 6)", + "CREATE_3rd_WAREHOUSE_PRODUCTS(using_1st_product)": "Create 3rd warehouse products (using 1st product)", + "CREATE_2nd_WAREHOUSE_PRODUCTS(using_product_number_1_2_and_3": "Create 2nd warehouse products (using product number 1, 2, and 3", + "UPDATE_WAREHOUSE_GEO_LOCATION": "UPDATE WAREHOUSE GEO LOCATION", + "UPDATE_1ST_WAREHOUSE_GEO_LOCATION": "Update 1st warehouse geo location", + "CREATE_1RD_ORDER(using_1st_warehouse_1st_user_and_1st_product)": "Create 1rd order (using 1st warehouse, 1st user and 1st product)", + "CREATE_2ND_ORDER(using_1st_warehouse_1st_user_and_2nd_product)": "Create 2nd order (using 1st warehouse, 1st user and 2nd product)", + "CONFIRM_ORDER": "CONFIRM ORDER", + "CREATE_ORDER": "CREATE ORDER", + "CONFIMR_1st_ORDER": "Confirm 1st order", + "CONFIMR_2nd_ORDER": "Confirm 2nd order", + "SETUP_MERCHANTS": { + "SETUP_MERCHANTS": "Setup merchants", + "PREV": "PREV", + "NEXT": "NEXT", + "ADD": "Add", + "BACK": "Back", + "SAVE": "Save", + "SELECT": "Select", + "CREATE": "Create", + "HOW_TO_SET_UP": { + "HOW_TO_SET_UP": "How to setup" + }, + "STEPPER": { + "ACCOUNT": "Account", + "BASIC_INFO": "Basic info", + "CONTACT_INFO": "Contact info", + "LOCATION": "Location", + "PAYMENTS": "Payments", + "MANUFACTURING": "Manufacturing", + "DELIVERY_AND_TAKEAWAY_SETTINGS": "Delivery & Takeaway Settings", + "ORDERS_SETTINGS": "Orders Settings", + "PRODUCT_CATEGORIES": "Product categories", + "PRODUCTS": "Products" + }, + "ACCOUNT": { + "ACCOUNT": "Account", + "EMAIL_ADDRESS": "Email address", + "EMAIL": "Email", + "PASSWORD": "Password", + "REPEAT_PASSWORD": "Repeat password", + "EMAIL_IS_REQUIRED": "Email is required", + "INVALID_EMAIL_FORMAT": "Invalid email format", + "USERNAME": "Username", + "USERNAME_IS_REQUIRED": "Username is required", + "USERNAME_MUST_BE_AT_LEAST_3_CHARACTERS": "Username must be at least 3 characters", + "PASSWORD_IS_REQUIRED": "Password is required", + "PASSWORD_MUST_BE_AT_LEAST_4_CHARACTERS": "Password must be at least 4 characters", + "REPEAT_PASSWORD_IS_REQUIRED": "Repeat password is required", + "PASSWORDS_DO_NOT_MATCH": "Passwords do not match" + }, + "BASIC_INFO": { + "BASIC_INFO": "Basic info", + "NAME": "Name", + "NAME_IS_REQUIRED": "Name is required", + "NAME_MUST_BE_AT_LEAST_4_CHARACTERS": "Name must be at least 4 characters", + "PHOTO": "Photo", + "ENTER_A_VALID_LOGO_URL_OR_BROWSE_FROM_A_DEVICE": "Enter a valid logo URL or browse from a device", + "REMOVE": "Remove", + "PHOTO_OPTIONAL": "Photo (optional)", + "BARCODE_DATA": "Barcode Data", + "BARCODE_DATA_IS_REQUIRED": "Barcode Data is required" + }, + "CONTACT_INFO": { + "CONTACT_INFO": "Contact info", + "CONTACT_PHONE": "Contact Phone", + "INVALID_PHONE_NUMBER_FORMAT": "Invalid phone number format", + "ORDER_FORWARDING_EMAIL": "Order Forwarding Email", + "ORDER_FORWARDING_PHONE": "Order Forwarding Phone", + "ORDERS_EMAIL": "Orders Email", + "ORDERS_EMAIL_IS_REQUIRED": "Orders email is required", + "INVALID_EMAIL_FORMAT": "Invalid email format", + "ORDERS_PHONE": "Orders Phone", + "ORDERS_PHONE_IS_REQUIRED": "Orders phone is required" + }, + "LOCATION": { + "LOCATION": "Location" + }, + "PAYMENTS": { + "PAYMENTS": "Payments", + "ALLOW_ONLINE_PAYMENT": "Allow online payments?", + "ALLOW_CASH_PAYMENT": "Allow cash payments?", + "STRIPE": { + "PAY_BUTTON_TEXT": "Pay Button text", + "CURRENCY": "Currency", + "PAY_BUTTON_TEXT_IS_REQUIRED": "Pay Button text is required", + "CHOOSE_CURRENCY_CODE": "Choose currency code", + "CURRENCY_TEXT_IS_REQUIRED": "Currency text is required", + "COMPANY_BRAND_LOGO": "Company brand logo", + "COMPANY_BRAND_LOGO_IS_REQUIRED": "Company brand logo is required", + "INVALID_LOGO_URL": "Invalid logo url", + "INVALID_LOGO": "Invalid logo", + "PUBLISHABLE_KEY": "Publishable key", + "PUBLISHABLE_KEY_IS_REQUIRED": "Publishable key is required", + "ALLOW_REMEMBER_ME": "Allow remember me?", + "REMOVE": "Remove" + }, + "PAYPAL": { + "MODE": "Mode", + "CHOOSE_PAYPAL_MODE": "Choose PayPal mode", + "TYPE": "type", + "CURRENCY": "Currency", + "CHOOSE_CURRENCY_CODE": "Choose currency code", + "CURRENCY_TEXT_IS_REQUIRED": "Currency text is required", + "PUBLISHABLE_KEY": "Publishable key", + "PUBLISHABLE_KEY_IS_REQUIRED": "Publishable key is required", + "SECRET_KEY": "Secret key", + "SECRET_KEY_IS_REQUIRED": "Secret key is required", + "PAYMENT_DESCRIPTION": "Payment description", + "PAYMENT_DESCRIPTION_IS_REQUIRED": "Payment description is required" + } + }, + "MANUFACTURING": { + "MANUFACTURING": "Manufacturing", + "PRODUCTS_MANUFACTURING": "Products Manufacturing" + }, + "DELIVERY_AND_TAKEAWAY_SETTINGS": { + "DELIVERY_AND_TAKEAWAY_SETTINGS": "Delivery & Takeaway Settings", + "SELECT_FROM_SHARED_CARRIERS": "Select from shared carriers", + "ADD_YOUR_CARRIER": "Add your carrier", + "EDIT_CARRIER": "Edit carrier", + "CARRIER_REQUIRED": "Carrier required", + "PRODUCTS_DELIVERY_BY_DEFAULT": "Products Delivery (by default)", + "PRODUCTS_TAKEAWAY_BY_DEFAULT": "Products Takeaway (by default)", + "USE_SELECTED_SHARED_CARRIERS": "Use selected shared carriers", + "ADD_YOUR_CARRIERS": "Add your carriers" + }, + "ORDERS_SETTINGS": { + "ORDERS_SETTINGS": "Orders Settings", + "ORDER_BARCODE_QR_CODE_TYPES": "Order Barcode/QR code types" + }, + "PRODUCT_CATEGORIES": { + "PRODUCT_CATEGORIES": "Product categories", + "ADD_OWN_PRODUCT_CATEGORY": "Add own product category" + }, + "PRODUCTS": { + "PRODUCTS": "Products", + "SELECT_FROM_PRODUCTS_CATALOG": "Select from products catalog", + "CREATE_PRODUCT": "Create product", + "EDIT_PRODUCT": "Edit product", + "ADD_PRODUCT": "Add product", + "CREATE_NEW_PRODUCT": "Create new product" + } + } + }, + "WAREHOUSE_MANAGE": { + "TITLE": "Manage Warehouse", + "MANAGE_STORE": "Manage Store", + "SAVE": "SAVE", + "NAME": "Name", + "USERNAME": "Username", + "PASSWORD": "Password", + "COUNTRY": "Country", + "CITY": "City", + "POSTCODE": "Postcode", + "IS_ACTIVE": "Is Active", + "PRODUCTS_MANUFACTURING": "Products Manufacturing", + "CARRIER_REQUIRED": "Carrier required", + "RIGHT_NOW": "(right now)", + "CARRIERS": "Carriers", + "ADDRESS": "Address", + "CARRIERS_SPECIFIC": "Use only specific carriers", + "VALIDATION": { + "NAME": "Name is required", + "USERNAME": "Username is required", + "PASSWORD": "Password is required", + "COUNTRY": "Country is required", + "CITY": "City is required", + "STREET": "Street is required", + "HOUSE": "House number is required", + "POSTCODE": "Postcode is required" + }, + "WIZARD_TITLES": { + "DETAILS": "Details", + "ACCOUNT": "Account", + "CONTACT_INFO": "Contact Info", + "LOCATION": "Location", + "PAYMENT": "Payment", + "DELIVERY_ZONES": "Delivery zones" + } + }, + "WAREHOUSES_VIEW": { + "TITLE": "Stores", + "DELETE_WAREHOUSES": "Delete Selected", + "DELETE": "DELETE", + "CREATE": "CREATE", + "SHOW_ON_MAP": "Show on map", + "SMART_TABLE_COLUMNS": { + "IMAGE": "Image", + "NAME": "Name", + "EMAIL": "Email", + "PHONE": "Phone", + "CITY": "City", + "ADDRESS": "Address", + "ORDERS_QTY": "Orders QTY", + "ORDERS": "Orders" + }, + "INFO": { + "STORE_INFO": "Store Info", + "STORE_ID": "Store ID", + "STORE_NAME": "Store Name" + }, + "MERCHANTS": { + "TRACK_ALL_MERCHANTS": "Track all merchants", + "FILTER_MERCHANTS": "Filter merchants", + "FILTER_BY_NAME": "Filter by name", + "FILTER_BY_CITY": "Filter by city", + "FILTER_BY_COUNTRY": "Filter by country" + } + }, + "WAREHOUSE_VIEW": { + "ORDER": { + "CONFIRM": "Confirm", + "START_PROCESSING": "Start Processing", + "START_ALLOCATION": " Start Allocation", + "ALLOCATED": "Allocated", + "ALLOCATION_FAILS": "Allocation Fails", + "START_PACKAGING": "Start Packaging", + "PACKAGED": "Packaged", + "PACKAGING_FAILS": "Packaging Fails", + "GIVEN_TO_CARRIER": "Given to Carrier", + "GIVEN_TO_CUSTOMER": "Given to Customer", + "ORDER": "Order", + "CANT_PROCESSING_WITHOUT_PRODUCTS": "Can't processing the order without products." + }, + "MUTATION": { + "TITLE": "Register New Store", + "NAME": "Name", + "LOGO": "Logo", + "PHOTO": "Photo", + "IS_ACTIVE": "Is Active", + "ORDERS_SHORT_PROCESS": "Orders Short Process", + "PRODUCTS_MANUFACTURING": "Products Manufacturing", + "CARRIER_REQUIRED": "Carrier required", + "RIGHT_NOW": "Right now", + "ORDERS_PHONE": "Orders Phone", + "CONTACT_PHONE": "Contact Phone", + "ORDERS_EMAIL": "Orders Email", + "CONTACT_EMAIL": "Contact Email", + "FORWARD_ORDERS_WITH": "Forward Orders With", + "USERNAME": "Username", + "OLD_PASSWORD": "Old Password", + "NEW_PASSWORD": "New Password", + "CONFIRM_PASSWORD": "Confirm Password", + "PASSWORDS_DO_NOT_MATCH": "Passwords do not match", + "PASSWORD": "Password", + "COUNTRY": "Country", + "USA": "USA", + "ISRAEL": "Israel", + "BULGARIA": "Bulgaria", + "CITY": "City", + "ADDRESS": "Address", + "POSTCODE": "Postcode", + "COORDINATES": "Coordinates", + "AUTO_DETECT_COORDINATES": "Auto detect coordinates", + "CARRIERS": "Carriers", + "USE_ONLY_SPECIFIC_CARRIERS": "Use only specific carriers", + "SELECT_SHAPE_TO_ADD_ZONE": "Select shape to add a new zone", + "CIRCLE": "Circle", + "SHAPE": "Shape", + "DRAW_SHAPE_ON_MAP": "Draw a shape on the map", + "MINIMUM_AMOUNT": "Minimum amount", + "DELIVERY_FEE": "Delivery fee", + "CANCEL": "Cancel", + "ADD": "Add", + "EDIT": "Edit", + "ZONE_NAME": "Zone name", + "UNALLOWED_ORDER_CANCELATION": "Unallowed Order Cancelation", + "IN_STORE_MODE": "In-store mode", + "ORDER_CANCELATION_OPTIONS": { + "ORDERING": "After Ordering", + "START_PROCESSING": "After Start Processing", + "START_ALLOCATION": "After Start Allocation", + "ALLOCATED": "After Allocated", + "START_PACKAGING": "After Start Packaging", + "PACKAGED": "After Packaged", + "CARRIER_TAKE_WORK": "After Carrier Take Work", + "CARRIER_GOT_IT": "After Carrier Got It", + "CARRIER_START_DELIVERY": "After Carrier Start Delivery", + "DELIVERED": "After Delivered" + }, + "ERRORS": { + "NAME_IS_REQUIRED": "Warehouse name is required", + "NAME_ATLEAST_3_CHARS": "Name must be at least 3 characters long", + "NAME_MORE_THAN_255_CHARS": "Name cannot be more than 255 characters long", + "LOGO_IS_REQUIRED": "Warehouse logo is required", + "INVALID_URL": "Enter a valid logo URL or upload from a device", + "PHONE_CONTAINS_ONLY_DIGIT": "The phone number must only contain digits", + "INVALID_EMAIL": "Invalid Email", + "ORDERS_PHONE_IS_REQUIRED": "Orders Phone is required", + "CONTACT_PHONE_IS_REQUIRED": "Contact Phone is required", + "ORDERS_EMAIL_IS_REQUIRED": "Orders Email is required", + "CONTACT_EMAIL_IS_REQUIRED": "Contact Email is required", + "USERNAME_IS_REQUIRED": "Username is required", + "PASSWORD_IS_REQUIRED": "Password is required", + "COORDINATES_ARE_REQUIRED": "Coordinates are required", + "COUNTRY_IS_REQUIRED": "Country is required" + }, + "WIZARD_TITLES": { + "BASIC_INFO": "Basic Info", + "CONTACT_INFO": "Contact Info", + "LOCATION": "Location" + }, + "CONTACT_INFO_TAB": { + "ORDER_FORWARDING_EMAIL": "Order Forwarding Email", + "ORDER_FORWARDING_PHONE": "Order Forwarding Phone" + } + }, + "NEW_PRODUCT": { + "SELECT_FROM_PRODUCTS_CATALOG": "Select from Products Catalog", + "CREATE_NEW_PRODUCT": "Create new Product", + "HOW_TO_ADD": "How to add", + "ADD": "Add", + "SAVE": "Save", + "ADD_PRODUCTS_TO_STORE": "Add Products to Store", + "NOTHING_FOUND": "Nothing found..." + }, + "SELECT_PRODUCTS": { + "TITLE": "Title", + "DESCRIPTION": "Description", + "DETAILS": "Details", + "IMAGES": "Images", + "CATEGORY": "Category" + }, + "SAVE": { + "PRODUCT_NAME": "Product Name", + "PRICE": "Price", + "COUNT": "Count", + "DELIVERY": "Delivery", + "TAKEAWAY": "Takeaway" + }, + "PLACEHOLDER": { + "EXAMPLE": "Example: Domino's Pizza", + "IMAGE_URL": "Image url", + "HERE_GOES_A_SHORT_DESCRIPTION": "Here goes a short description", + "HERE_GOES_A_DETAILS_ABOUT_PRODUCT_(OPTION)": "Here goes a details about product (option)", + "REMOVE_IMAGE": "Remove image" + }, + "CREATE_ORDER_MODAL": { + "TITLE": { + "CREATE_ORDER": "Create Order" + }, + "WIZARD_FORM": { + "BUTTON_NEXT": "Next", + "BUTTON_PREV": "Back", + "BUTTON_DONE": "Finish Order", + "STEP1": { + "TITLE": "Choose Option", + "SELECT_FROM_EXISTING": "Select From Existing", + "ADD_NEW_CUSTOMER": "Add New Customer" + }, + "STEP2": { + "TITLE": { + "SELECT_CUSTOMER": "Select Customer", + "SELECT_ADD": "Select/Add", + "ADD_NEW": "Add New" + }, + "SMART_TABLE": { + "TITLES": { + "FULL_NAME": "Full Name", + "EMAIL": "Email", + "PHONE": "Phone", + "ADDRESS": "Address" + } + } + }, + "STEP3": { + "TITLE": "Create Order" + } + } + }, + "PRODUCTS_TAB": { + "PRODUCTS": "Products", + "ADD_PRODUCTS": "Add Products", + "DELETE": "Delete", + "IMAGE": "Image", + "TITLE": "Title", + "DESCRIPTION": "Description", + "DETAILS": "Details", + "CATEGORY": "Category", + "PRICE": "Price", + "QUANTITY": "Quantity", + "CLICK_ON_PRODUCT_IMAGE_TO_INCREASE_AVAILABLE_QUANTITY": "Click on Product Image to increase available quantity", + "AVAILABILITY": "Availability", + "TYPE": "Type", + "DELIVERY": "Delivery", + "TAKEAWAY": "Takeaway" + }, + "NEW_PRODUCT_TYPE": "New Type Product", + "ADD_PRODUCTS": "Add Products", + "CREATE_ORDER": "Create Order", + "STATUS": "Status", + "ORDERS": "Orders", + "WAREHOUSE": "Warehouse", + "PRODUCT": "Product", + "PRODUCTS": "Products", + "ORDER_NUMBER": "Order Number", + "CANCELLED": "Cancelled", + "WAREHOUSE_STATUS": "Warehouse Status", + "CARRIER_STATUS": "Carrier Status", + "PAID": "Paid", + "CARRIER": "Carrier", + "CREATED": "Created", + "ELAPSED": "Elapsed", + "CONTACT_DETAILS": "Contact Details", + "EMAIL": "Email", + "PHONE": "Phone", + "ORDERS_FORWARDING_DETAILS": "Orders Forwarding Details", + "ORDERS_FORWARDING_WITH": "Orders Forwarding With", + "MANAGE_STORE": "Manage Store", + "TOP_PRODUCTS": "Top Products", + "PRODUCTS_MANUFACTURING": "Products Manufacturing", + "CARRIER_REQUIRED": "Carrier required", + "MANAGE_WAREHOUSE": "Manage warehouse", + "MANAGE_STORE_PRODUCTS_&_ORDERS": "Manage store products & orders" + }, + "SIMULATION_VIEW": { + "SIMULATION": "Simulation", + "PURCHASE_PRODUCTS": "Purchase products", + "CREATE_INVITE_REQUEST": "Create Invite Request", + "SEND": "Send", + "CREATE_USER": "Create User", + "ORDER_CONFIRM": "Order Confirm", + "ORDER_CANCEL": "Order Cancel", + "PRODUCTS": "Products", + "STORE": "Store", + "ORDER": "Order", + "INVITE_REQUEST": "Invite-Request", + "INVITE_USER": "Invite User", + "TAB_BUTTONS": { + "PRODUCTS": "Products", + "ORDER_HISTORY": "Order history" + }, + "USER_MUTATION": { + "TITLE": "Create Customer", + "NAME": "Name", + "EMAIL": "Email", + "COUNTRY": "Country", + "CITY": "City", + "USA": "USA", + "ISRAEL": "Israel", + "BULGARIA": "Bulgaria", + "ADDRESS": "Address", + "POSTCODE": "Postcode", + "COORDINATES": "Coordinates", + "AUTO_DETECT_COORDINATES": "Auto detect coordinates", + "ERRORS": { + "FIRST_NAME_IS_REQUIRED": "First name is required", + "LAST_NAME_IS_REQUIRED": "Last name is required", + "INVALID_EMAIL": "Invalid Email", + "EMAIL_IS_REQUIRED": "Email is required", + "COORDINATES_ARE_REQUIRED": "Coordinates are required" + }, + "WIZARD_TITLES": { + "ADDITIONAL_INFO": "Additional Info", + "LOCATION": "Location" + } + }, + "SMART_TABLE": { + "TITLE": "Title", + "ID": "Id", + "IMAGE": "Image" + }, + "ORDER_INFO": { + "STATUSES": [ + { + "TITLE": "We're preparing the order!", + "DETAILS": "You will get it in %t minutes.", + "NOT_PAID_NOTE": "Prepare your wallet (%s in cash)." + }, + { + "TITLE": "Carrier on the way!", + "DETAILS": "You will get the order in %t min.", + "NOT_PAID_NOTE": "Prepare your wallet (%s in cash)." + }, + { + "TITLE": "Check your door!", + "DETAILS": "You will get the order in seconds.", + "NOT_PAID_NOTE": "Prepare your wallet (%s in cash)." + }, + { + "TITLE": "Order Completed!", + "DETAILS": "Thanks for using Ever", + "NOT_PAID_NOTE": "" + } + ], + "DELIVERY_STATUS": { + "WE": "We", + "CARRIER": "Carrier", + "YOU": "You" + }, + "DELIVERY_WRONG": { + "DELIVERY_WRONG": "The Delivery Went Wrong!", + "PROCESSING_WRONG": "Processing Went Wrong!", + "TRY_AGAIN": "Please try again.", + "CALL_FOR_DETAILS": "Call for details" + } + }, + "INSTRUCTIONS_STEPS": { + "INSTRUTIONS": "Instructions", + "CREATE_USER_STEP": { + "CREATE_USER": "Create User", + "STEP_1": "Step 1.", + "ORDER": "Order", + "TO_CONTINUE_IS_REQUIRED_TO_REGISTER_IN_THE_SYSTEM": "To continue is required to register in the system", + "CLICK_ON_BUTTON_CREATE_USER": "Click on button 'Create user'", + "FILL_THE_FORM_FOR_ADDITIONAL_INFO": "Fill the form for additional info (optional)", + "FILL_THE_FORM_FOR_LOCATION_AND_PRESS_DONE_BUTTON": "Fill the form for location and press DONE button" + }, + "ORDER_STEP": { + "ORDER": "Order", + "STEP_2": "Step 2.", + "CREATE_ORDER": "Create order", + "CHOICE_SOME_PRODUCTS_FROM_THE_TABLE": "Choice some products from the table (you can see more details about when press on his name or image)", + "SELECT_PRODUCT": "To be selected one product have to press on his row.", + "SELECT_BUTTON_ORDER_TO_CREATE_ORDER": "Press button 'Order' to create order with the selected product.", + "REVIEW_ORDER_HISTORY": "Review order history:", + "ON_PRESS_ORDER_HISTORY_TAB": "On press 'Order history' shows all your orders..", + "HERE_YOU_CAN_SEE_DETAILS_ABOUT_EACH_ORDER": "Here you can see details about each order", + "PRESS_ON_CARRIER_ORDER_OR_PRODUCT_NAME": "Press on carrier, order or product name for more information." + }, + "CONFIRM_OR_CANCEL_STEP": { + "STEP_3": "Step 3", + "CONFIRM_CANCEL_ORDER": "Confirm/Cancel order", + "REAL_TIME": "Real time", + "TRACK_STATUS_ON_YOUR_ORDER": "Track status on your order.", + "ELAPSED_TIME_FROM_CREATE_TO_DELIVERED": "Elapsed time from created to delivered.", + "SHOWS_MERCHANT_LOCATION": "Shows merchant location.", + "SHOWS_CARRIER_LOCATION": "Show carrier location.", + "POSSIBILITIES": "Possibilities:", + "SLIDER_REVIEW_OF_THE_ALL_PRODUCTS": "Slider review of the all products.", + "CAN_CANCEL_IT_WITH_ORDER_CANCEL_BUTTON": "Untill the order is not delivered you user cancel it with 'Order Cancel' button.", + "AFTER_THE_ORDER_IS_DELIVER_USER_CAN_CLICK_BUTTON_ORDER_CONFIRM_TO_CONTINUE": "After the order is delivered the user can click button 'Order Confirm' to continue.." + }, + "INVITE_STEP": { + "TO_CONTINUE_ITs_REQUIRED_TO_BE_INVITED_IN_THE_SYSTEM": "To continue it's required to be invited in the system:", + "SEND_INVITE_REQUEST": "Send 'Invite Request' to the system from the form which will be opened after pressing Invite Request button.", + "ALL_INVITE_REQUESTS_ARE_REVIEWED": "All invite requests are reviewed by admin and they can be invited if the system is available near your location (for test you can do immediately form Invite User button).", + "AFTER_YOU_GET_INVITED_BEFORE": "After you get invite you can easy login the system, just have to enter your invite code, which will be provided from the system (you will see here", + "AFTER_YOU_GET_INVITED_AFTER": "code after press Invite button, and can enter it when press Create User button)." + } + } + }, + "CUSTOMERS_VIEW": { + "TITLE": "Customers", + "DELETE_CUSTOMERS": "Delete", + "CREATE_CUSTOMER": "Create", + "DELETE": "DELETE", + "CREATE": "CREATE", + "BAN": "BAN", + "UNBAN": "UNBAN", + "MANAGE_CUSTOMER": "Manage customer", + "CUSTOMER": "Customer", + "CUSTOMERS_DEVICES": "Devices", + "INVITE": "Invite", + "NOT_INVITED_ONLY": "Not invited only", + "ORDER": "Order", + "ORDERS_STATISTICS": "Orders statistics", + "NUMBER_OF_ORDERS": "Number of Orders", + "CANCELED_ORDERS": "Canceled Orders", + "COMPLETED_ORDERS_TOTAL": "Completed Orders Total", + "Order": "Order", + "CANCEL_ORDER": "Cancel Order", + "CATEGORY": "Category", + "ORDERS_HISTORY": "Orders History", + "AVAILABLE_PRODUCTS": "Available products", + "NEARBY_STORES": "Nearby Stores", + "INVITES_REQUESTS_MANAGEMENT": "Invites requests management", + "INVITES_MANAGEMENT": "Invites management", + "DESCRIPTION": "Description", + "DETAILS": "Details", + "MAKE_A_CUSTOM_ORDER": "Make a custom order", + "PRODUCT_COUNT": "Product Count", + "QUANTITY_CAN'T_BE_EMPTY": "Quantity can't be empty", + "QUANTITY_CAN'T_BE_0": "Quantity can't be 0", + "NOT_ENOUGH_PRODUCTS_AVAILABLE": "Not enough products available", + "ORDER_INFO": "Order Info", + "ORDER_ID": "Order Id", + "STORE_ID": "Store Id", + "CARRIER_ID": "Carrier Id", + "NO_CARRIER": "No Carrier", + "SMART_TABLE_COLUMNS": { + "IMAGE": "Image", + "NAME": "Name", + "EMAIL": "Email", + "PHONE": "Phone", + "ADDRESS": "Address", + "ORDERS_QTY": "Orders QTY", + "COUNTRY": "Country", + "CITY": "City", + "STREET_ADDRESS": "Street Address", + "HOUSE": "House", + "APARTMENT": "Apartment", + "INVITE_CODE": "Invite Code", + "INVITED_DATE": "Invited date", + "ORDER_NUMBER": "Order Number", + "WAREHOUSE": "Warehouse", + "CARRIER": "Carrier", + "PRODUCT_LIST": "Product list", + "STATS": "Stats", + "DELIVERY_TIME": "Delivery Time", + "CREATED": "Created", + "ACTIONS": "Actions", + "PAID": "Paid", + "COMPLETED": "Completed", + "CANCELLED": "Cancelled", + "NOT_DELIVERED": "Not delivered", + "PRODUCT": "Product", + "PRICE": "Price", + "STORE": "Store", + "AVAILABLE_COUNT": "Available Count", + "ORDER": "Order", + "STATUS": "Status" + }, + "EDIT": { + "EDIT_CUSTOMER": "Edit Customer", + "BASIC_INFO": "Basic Info", + "SAVE": "Save" + }, + "DEVICE": { + "ALL_DEVICE": "All Device", + "DEVICE_ID": "Device ID", + "ID": "id", + "UPDATE": "Update", + "LANGUAGE": "language", + "TYPE": "type", + "TYPEU": "Type", + "UUID": "uuid", + "DEVICE_UUID": "Device UUID", + "UPDATE_DEVICE": "Update Device", + "CUSTOMERS_DEVICES": "Customers Devices", + "DELETE": "DELETE", + "CREATE": "CREATE" + }, + "WAREHOUSE": { + "WAREHOUSE_INFO": "Warehouse Info", + "WAREHOUSE_ID": "Warehouse Id", + "WAREHOUSE_NAME": "Warehouse Name" + }, + "CUSTOMER_VIEW": { + "MANAGE_CUSTOMER": "Manage customer", + "EDIT": "EDIT", + "CUSTOMER": "Customer" + }, + "INVITES_VIEW": { + "DELETE": "Delete", + "INVITE": "Invite" + } + }, + "CARRIERS_VIEW": { + "TITLE": "Carriers", + "DELETE_CARRIERS": "Delete Selected", + "CREATE_CARRIER": "Create Carrier", + "DELETE": "DELETE", + "CREATE_BUTTON": "CREATE", + "ACTIVE_AND_AVAILABLE_ORDERS": "Active and Available Orders", + "ORDERS_HISTORY": "Orders History", + "TRACK": "Track", + "SMART_TABLE_COLUMNS": { + "IMAGE": "Image", + "NAME": "Name", + "PHONE": "Phone", + "STATUS": "Status", + "ADDRESS": "Address", + "DELIVERIES": "Deliveries" + }, + "CARRIER_PAGE": { + "WAREHOUSE": "Warehouse", + "CUSTOMER": "Customer", + "SAVE": "Save", + "EDIT": "Edit", + "CARRIER_INFO": "Carrier Info", + "CARRIER_ID": "Carrier Id", + "REGISTER_NEW_CARRIER": "Register New Carrier", + "WAREHOUSE_STATUS": "Warehouse Status", + "CARRIER_STATUS": "Carrier Status", + "CREATED": "Created", + "ARRIVED_TO_CUSTOMER": "Arrived to Customer", + "FAILED": "Failed", + "DELIVERED": "Delivered", + "CLIENT_REFUSE_ORDER": "Client Refuse Order", + "AVAIBLE_ORDER_TO_PICK_UP": "Available Orders to pick up (each carrier can pick up multiple orders)", + "ACTIVE": "Active", + "CARRIER_CAN_BE_SHARED": "Carrier can be shared?", + "NOT_ACTIVE": "Not Active", + "WORKING": "Working", + "NOT_WORKING": "Not Working", + "SELECT_CARRIER": "Select carrier", + "CARRIER_ORDERS_STATUS": "Carrier orders status", + "Start": "Start", + "PICKED_UP_ORDER": "Picked Up Order", + "CANCEL": "Cancel", + "Arrived To Client": "Arrived To Client", + "No Carrier": "No Carrier", + "Order Selected For Delivery": "Order Selected For Delivery", + "Order Picked Up": "Order Picked Up", + "Order In Delivery": "Order In Delivery", + "Delivered": "Delivered", + "Delivery Issues": "Delivery Issues", + "Client Refuse to Take Order": "Client Refuse to Take Order", + "BAD_STATUS": "BAD_STATUS", + "Created": "Created", + "Confirmed": "Confirmed", + "Processing": "Processing", + "Allocation Started": "Allocation Started", + "Allocation Finished": "Allocation Finished", + "Packaging Started": "Packaging Started", + "Packaged": "Packaged", + "Given to Carrier": "Given to Carrier", + "Allocation Failed": "Allocation Failed", + "Packaging Failed": "Packaging Failed", + "LOCATION": "Location", + "TIME": "Time", + "NAME": "Name" + }, + "EDIT": { + "EDIT_CARRIER": "Edit Carrier", + "BASIC_INFO": "Basic Info", + "LOCATION": "Location", + "PHOTO_URL": "Photo Url", + "CONTACT_PHONE": "Contact Phone", + "FIRST_NAME": "First Name", + "LAST_NAME": "Last Name" + }, + "CREATE": { + "BASIC_INFO": "Basic Info", + "LOCATION": "Location" + }, + "TRACK_PAGE": { + "TRACK_ALL_WORKING_CARRIERS": "Track all working carriers", + "FILTER_CARRIERS": "Filter Carriers", + "PHONE": "Phone", + "EMAIL": "Email", + "ADDRESS": "Address", + "DELIVERY_COUNT": "Delivery Count" + } + }, + "PRODUCT_TYPE_VIEW": { + "TITLE": "New Product", + "WIZARD_FORM": { + "VALIDATION_MESSAGES": { + "TITLE": "Title is required", + "THE_LENGHT_OF_THE_TITLE": "The length of title should be max 255 characters long!", + "IMAGE": "Picture is required", + "DESCRIPTION": "Description is required", + "LANGUAGE": "Language is required", + "THE_LENGHT_OF_THE_DESCRIPTION": "The length of description should be max 255 characters long!", + "PRICE": "Price is required", + "COUNT": "Product count is required" + } + } + }, + "SHARED": { + "FORMS": { + "ERRORS": { + "FIRST_NAME_REQUIRED": "First name is required", + "LAST_NAME_REQUIRED": "Last name is required", + "USERNAME_REQUIRED": "Username is required", + "PASSWORD_REQUIRED": "Password is required", + "PHONE_REQUIRED": "Phone is required", + "LOGO_URL_REQUIRED": "Enter valid image URL or browse for a file", + "IS_ACTIVE": "Is active field is required", + "COUNTRY_REQUIRED": "Country is required", + "CITY_REQUIRED": "City is required", + "STREET_ADDRESS_REQUIRED": "Street address is required", + "HOUSE_REQUIRED": "House number is required", + "COORDINATES_REQUIRED": "Coordinates are required", + "MUST_CONTAIN_ONLY_LETTERS": "Must contain only letters", + "PHONE_MUST_CONTAINS_ONLY(specail_signs)AND_DIGIT_CHARACTER": "Phone number can start with '+' or '(some numbers)' and must contains only only: '-, ., (space), #'' and digit characters" + } + }, + "USER": { + "FORMS": { + "BASIC_INFO": { + "TITLE": "Basic Info", + "FIRST_NAME": "First Name", + "FIRST_NAME_OPTIONAL": "First Name (optional)", + "LAST_NAME_OPTIONAL": "Last Name (optional)", + "PHOTO_URL": "Photo Url", + "PICTURE_URL": "Picture url (optional)", + "EMAIL": "Email", + "EMAIL_OPTIONAL": "Email (optional)", + "ERRORS": { + "INVALID_EMAIL": "Invalid Email", + "EMAIL_IS_ALREADY_IN_USE": "Email is already in use" + } + } + } + }, + "WAREHOUSE": { + "ORDER_MODAL": { + "MAKE_ORDER": "Make Order", + "ONLY_AVAILABLE": "Show only available products", + "ORDER": "Order", + "SMART_TABLE": { + "TITLES": { + "IMG": "Img", + "PRODUCT": "Product", + "PRICE": "Price", + "AVAILABLE": "Available", + "AMOUNT": "Amount", + "COMMENT": "Comment" + } + } + } + } + }, + "MENU_VIEW": { + "DASHBOARD": "Dashboard", + "STORES": "Stores", + "PRODUCTS": { + "PRODUCTS": "Products", + "MANAGEMENT": "Management", + "CATEGORIES": "Categories" + }, + "CUSTOMERS": { + "CUSTOMERS": "Customers", + "MANAGEMENT": "Management", + "INVITES": "Invites" + }, + "CARRIERS": "Carriers", + "SIMULATION": "Simulation", + "SETUP": "Setup" + }, + "CATEGORY_VIEW": { + "TITLE": "Title", + "IMAGE": "Image", + "CREATE_BUTTON": "CREATE", + "DELETE": "DELETE", + "EDIT": { + "EDIT_CATEGORY": "Edit Category", + "CATEGORY_NAME": "Category Name", + "ENTER_THE_CATEGORY_NAME": "Enter the category name", + "DONE": "Done" + }, + "CREATE": { + "CREATE_CATEGORY": "Create Category", + "CATEGORY_NAME": "Category Name", + "ENTER_THE_CATEGORY_NAME": "Enter the category name", + "PHOTO": "Photo", + "BROWSE": "Browse", + "INVALID_URL": "Invalid Url", + "REMOVE_IMAGE": "Remove image", + "PHOTO_OPTIONAL": "Photo (optional)", + "DONE": "Done" + } + }, + "PLACEHOLDER": { + "EXAMPLE": "Example: Domino's Pizza", + "IMAGE_URL": "Image url", + "HERE_GOES_A_SHORT_DESCRIPTION": "Here goes a short description", + "HERE_GOES_A_DETAILS_ABOUT_PRODUCT_(OPTION)": "Here goes a details about product (option)", + "REMOVE_IMAGE": "Remove image", + "PASSWORD": "Password", + "LATITUDE": "Latitude", + "LONGITUDE": "Longitude", + "APARTMENT": "Apartment", + "HOUSE": "House", + "STREET": "Street", + "ZIP": "ZIP", + "CITY": "City", + "FIND_ADDRESS": "Find Address" + }, + "STATUS_TEXT": { + "Created": "Created", + "Confirmed": "Confirmed", + "Processing": "Processing", + "Allocation Started": "Allocation Started", + "Allocation Finished": "Allocation Finished", + "Packaging Started": "Packaging Started", + "Packaged": "Packaged", + "Given to Carrier": "Given to Carrier", + "Allocation Failed": "Allocation Failed", + "Packaging Failed": "Packaging Failed", + "No Carrier": "No Carrier", + "Order Selected For Delivery": "Order Selected For Delivery", + "Order Picked Up": "Order Picked Up", + "Order In Delivery": "Order In Delivery", + "Arrived To Client": "Arrived To Client", + "Delivered": "Delivered", + "Delivery Issues": "Delivery Issues", + "Client Refuse to Take Order": "Client Refuse to Take Order", + "Given to Customer": "Given to Customer", + "BAD_STATUS": "BAD_STATUS" + }, + "ELAPSED_TIME": { + "TITLE": "Elapsed time" + }, + "CONFIRM_MODAL": { + "ARE_YOU_SURE": "Are you sure?", + "ARE_YOU_SURE_YOU_WANT_TO_INCREASE": "Are you sure you want to increase the qty of products?", + "ARE_YOU_SURE_YOU_WANT_TO_DECREASE": "Are you sure you want to decrease the qty of products?", + "YES": "Yes", + "NO": "No" + }, + "NO_SERVER_VIEW": { + "NO_SERVER": "Server connection is lost" + }, + "BUTTON_NEXT": "Next", + "BUTTON_PREV": "Back", + "BUTTON_DONE": "Done", + "TERRAIN": "Terrain", + "SATELLITE": "Satellite", + "LOCATION": "Location", + "ROAD_MAP": "Road Map", + "Manage warehouse": "Manage store products & orders", + "Warehouse": "Warehouse", + "Create Warehouse": "Create Warehouse", + "SIMULATION": "Simulation", + "Purchase products": "Purchase products", + "Manage": "Manage", + "Orders": "Orders", + "Confirmed": "Confirmed", + "In Delivery": "In Delivery", + "Not Confirmed": "Not Confirmed", + "Not paid": "Not paid", + "Cancelled": "Cancelled", + "All": "All", + "CANCEL": "Cancel", + "Default Settings": "Default Settings", + "Products Manufacturing": "Products Manufacturing", + "Carrier required before sale": "Carrier required before sale", + "New Product Type": "New Product Type", + "Products": "Products", + "Product": "Product", + "Title": "Title", + "Picture Url": "Picture Url", + "Description": "Description", + "Details": "Details", + "Price": "Price", + "CATEGORY": "Category", + "LANGUAGE": "Language", + "BROWSE": "Browse", + "ENGLISH": "English", + "HEBREW": "Hebrew", + "RUSSIAN": "Russian", + "BULGARIAN": "Bulgarian", + "SPANISH": "ספרדית", + "FRENCH": "צָרְפָתִית", + "SELECT": "Select", + "Name": "Name", + "Id": "Id", + "Warehouse name is required": "Warehouse name is required", + "Name must be at least 1 characters long": "Name must be at least 1 characters long", + "Title cannot be more than 255 characters long": "Title cannot be more than 255 characters long", + "Logo": "Logo", + "Warehouse logo is required": "Warehouse logo is required", + "is Active": "is Active", + "right now": "right now", + "Unselected": "Unselected", + "Phone": "Phone", + "Email": "Email", + "Username": "Username", + "Username is required": "Username is required", + "Password": "Password", + "Country": "Country", + "USA": "USA", + "Israel": "Israel", + "Bulgaria": "Bulgaria", + "City": "City", + "Address": "Address", + "Postcode": "Postcode", + "Coordinates": "Coordinates", + "Auto detect coordinates": "Auto detect coordinates", + "Carriers": "Carriers", + "Carrier": "Carrier", + "Use only specific carriers": "Use only specific carriers", + "Manage carrier and deliveries": "Manage carrier and deliveries", + "Register New Carrier": "Register New Carrier", + "Create User": "Create User", + "OPTIONAL": "optional" +} diff --git a/packages/admin-web-angular/src/assets/i18n/ru-RU.json b/packages/admin-web-angular/src/assets/i18n/ru-RU.json new file mode 100644 index 0000000..c8bdb04 --- /dev/null +++ b/packages/admin-web-angular/src/assets/i18n/ru-RU.json @@ -0,0 +1,1155 @@ +{ + "COMMON": { + "SAVE": "Сохранить", + "CANCEL": "Отмена", + "USA": "США", + "ISRAEL": "Израиль", + "BULGARIA": "Болгария" + }, + "ORDER_VIEW": { + "ORDER_HEADER_INFO": { + "ORDER": "Порядок", + "MANAGE_ORDER": "Управление заказом", + "TOTAL": "Всего" + }, + "ORDER_PRODUCT_INFO": { + "ORDER_PRODUCTS": "Заказать продукцию", + "ADD_PRODUCTS": "Добавить продукты", + "REMOVE_PRODUCTS": "Удалить продукты", + "CANCEL_ORDER": "Отменить заказ", + "THE_ORDER_IS_CANCELED": "Заказ отменяется", + "THE_ORDER_IS_GIVEN_TO_CARRIER": "Заказ предоставляется перевозчику.", + "THE_ORDER_IS_DELIVERED": "Заказ доставляется.", + "ADD_PRODUCTS_MODAL": "Добавить продукты", + "ADD": "добавлять", + "SUCCESS_TOAST": "Продуктите бяха прибавени към поръчката", + "ERROR_TOAST": "Грешка, нещо се обърка", + "SMART_TABLE": { + "NAME": "Название", + "QTY": "КОЛ", + "PRICE": "Цена", + "IMAGE": "Образ", + "COMMENT": "Комментарий" + } + }, + "ORDER_SIDEBAR": { + "CONTACT_DETAILS": "Контактная информация", + "WAREHOUSE": "Склад", + "CUSTOMER": "Покупатель", + "CARRIER": "Перевозчик", + "QTY": "кол" + }, + "LOCATION_INFO": { + "MAP": "Карта", + "DELIVERY_DISTANCE": "Расстояние доставки:" + } + }, + "DASHBOARD_VIEW": { + "TOTAL_CUSTOMER": "Всего клиентов", + "TOTAL_QUANTITY_OF_EXISTING_CUSTOMERS": "Общее количество существующих клиентов", + "TOTAL_COMPLETED_ORDERS": "Всего выполненных заказов", + "TOTAL_QUANTITY_OF_COMPLETED_ORDERS": "Общее количество завершенных заказов", + "TOTAL_REVENUE": "Общий доход", + "TOTAL_SUM_SUM_OF_ALL_COMPLETED_ORDERS": "Общая сумма всех выполненных заказов", + "TODAYs_CUSTOMERS": "Сегодняшние клиенты", + "TODAYs_QUANTITY_OF_REGISTERED_CUSTOMERS": "Количество зарегистрированных клиентов за сегодня", + "TODAYs_COMPLETED_ORDERS": "Завершенные сегодня заказы", + "TODAYs_QUANTITY_OF_NEW_COMPLETED_ORDERS": "Сегодняшнее количество новых завершенных заказов", + "TODAYs_REVENUE": "Сегодняшний доход", + "TODAYs_SUM_OF_COMPLETED_ORDERS": "Сегодняшняя сумма завершенных заказов", + "TILL_AVERAGE": "до среднего", + "BETTER_THAN_AVERAGE": "лучше среднего", + "SELECT_COMPONENT": { + "STORES": "магазины", + "CONTACT_DETAILS": "Контактная информация", + "PHONE": "Телефон", + "EMAIL": "Эл. адрес", + "ORDERS_FORWARDING_WITH": "Заказы на пересылку", + "SELECT_STORE": "Выберите магазин" + }, + "CHARTS": { + "ORDERS": "заказы", + "PROFIT": "прибыль", + "TOTAL_ORDERS": "Всего заказов", + "TOTAL_COMPLETED_ORDERS": "Всего выполненных заказов", + "TOTAL_CANCELLED_ORDERS": "Всего отмененных заказов", + "TOTAL_REVENUE_ALL_ORDERS": "Общий доход от всех заказов", + "TOTAL_REVENUE_COMPLETED_ORDERS": "Общий доход от выполненных заказов", + "TOTAL_LOST_REVENUE_CANCELLED_ORDERS": "Всего потерянных доходов от отмененных заказов", + "PAYMENT": "Оплата", + "CANCELED": "Отменен", + "ALL_ORDERS": "Все заказы", + "TODAY": "Сегодня", + "LAST_WEEK": "Прошлая неделя", + "LAST_MONTH": "Прошлый месяц", + "CURRENT_YEAR": "Текущий Год", + "YEARS": "По годам", + "CUSTOM_PERIOD": "Период Пользователя", + "SELECT_PERIOD": "Выберите период", + "FROM": "От", + "TO": "До", + "SELECT": "Выбрать", + "LABELS": { + "WEEK": "Неделю", + "WEEKDAYS": { + "MON": "Пн", + "TUE": "Вт", + "WED": "Ср", + "THU": "Чт", + "FRI": "Пт", + "SAT": "Сб", + "SUN": "Вс" + }, + "MONTHS": { + "JAN": "Янв", + "FEB": "Февр", + "MAR": "Март", + "APR": "Апр", + "MAY": "Май", + "JUN": "Июнь", + "JUL": "Июль", + "AUG": "Авг", + "SEP": "Сент", + "OCT": "Окт", + "NOV": "Ноябрь", + "DEC": "Дек" + } + } + } + }, + "HEADER_VIEW": { + "ADMIN": "Администратор", + "EVER": "Евер", + "PROFILE": "Профиль", + "LOG_OUT": "Выйти" + }, + "FOOTER_VIEW": { + "COPY_RIGHT": "Copyright © 2016-настоящее время", + "ALL_RIGHTS_RESERVED": "Все права защищены" + }, + "PROFILE_VIEW": { + "PROFILE_PAGE": "Страница профиля", + "BASIC_INFO": "Базовая информация", + "ACCOUNT": "учетная запись", + "USERNAME": "имя пользователя", + "ERROR": "ошибка", + "EMAIL": "Эл. адрес", + "FIRST_NAME": "Имя", + "FIRST_NAME_OPTIONAL": "Имя (необязательно)", + "LAST_NAME": "Фамилия (необязательно)", + "PICTURE_URL": "URL изображения (необязательно)", + "BROWSE": "Просматривать", + "REMOVE": "Удалить", + "SAVE": "Сохранить", + "OLD_PASSWORD": "Старый пароль", + "NEW_PASSWORD": "Новый пароль", + "REPEAT_NEW_PASSWORD": "повторите новый пароль", + "INVALID_EMAIL_ADDRESS": "Неверный адрес электронной почты", + "INVALID_URL": "Неверная ссылка", + "NAME_MUST_CONTAIN_ONLY_LETTERS": "Имя должно содержать только буквы", + "PASSWORDS_DO_NOT_MATCH": "Пароли не совпадают", + "SUCCESSFULLY_CHANGE_PASSWORD": "Успешно сменить пароль" + }, + "PRODUCTS_VIEW": { + "DELETE": "удалять", + "CREATE": "Создайте", + "EDIT_VIEW": { + "EDIT_PRODUCT": "Изменить продукт", + "BASIC_INFO": "Базовая информация", + "SAVE": "Сохранить" + } + }, + "FAKE_DATA": { + "FAKE_DATA_GENERATOR": "Ложный генератор данных", + "GENERATE_ALL": "Создать Все", + "CREATE_100_USERS": "Создать 100 пользователей", + "CREATE_100_CARRIERS": "Создать 100 перевозчиков", + "CREATE_100_WAREHOUSES": "Создание 100 складов", + "SETUP": "НАСТРОИТЬ", + "GENERATE_INITIAL_DATA": "Генерировать исходные данные", + "CREATE_INVITE": "СОЗДАТЬ ПРИГЛАШЕНИЕ", + "HARDCODED_DATA": "Жестко закодированные данные", + "CLEAR_ALL": "ОЧИСТИТЬ ВСЕ", + "GENERATE_HARDCODED_ONLY": "Создать только Hardcoded", + "INCLUDED_HARDCODED_DATA": "Включить жестко кодированные данные", + "CREATE_1st_INVITE": "Создать 1-е приглашение", + "CREATE_2st_INVITE": "Создать второе приглашение", + "CREATE_3st_INVITE": "Создать 3-е приглашение", + "CREATE_4st_INVITE": "Создание 4-го приглашения", + "CREATE_CUSTOMER": "Создать клиента", + "CREATE_USER": "СОЗДАТЬ ПОЛЬЗОВАТЕЛЯ", + "CREATE_1st_USER": "Создание 1-го пользователя (с использованием 1-го приглашения)", + "CREATE_CARRIER": "СОЗДАТЬ ПЕРЕВОЗЧИК", + "CREATE_1st_CARRIER": "Создание 1-го носителя", + "CREATE_2nd_CARRIER": "Создание второго носителя", + "CREATE_3rd_CARRIER": "Создание третьего носителя", + "CREATE_PRODUCT": "СОЗДАТЬ ПРОДУКТ", + "CREATE_PEPERONI&MUSHROOM_PIZZA_PRODUCT": "Создайте продукт пиццы Peperoni & Mushroom", + "CREATE_SUSHI&CAVIAR_PRODUCT": "Создайте продукт Sushi & Caviar", + "CREATE_SUSHI_MIX_PRODUCT": "Создайте продукт для смешивания суши", + "CREATE_PASTA_PRODUCT": "Создать макаронный продукт", + "CREATE_SUSHI_BOX_PRODUCT": "Создайте продукт Sushi box", + "CREATE_PEPERONI&TOMATO_PIZZA_PRODUCT": "Создайте продукт пиццы Peperoni & Tomato", + "CREATE_WAREHOUSE": "СОЗДАТЬ СКЛАД", + "CREATE_1st_WAREHOUSE": "Создать первый склад", + "CREATE_2nd_WAREHOUSE": "Създайте 2-ри склад", + "CREATE_3rd_WAREHOUSE": "Создать 3-й склад", + "CREATE_WAREHOUSE_PRODUCT": "СОЗДАТЬ ТОВАРНЫЙ ПРОДУКТ", + "CREATE_1st_WAREHOUSE_PRODUCTS(using_product_number_1_2_3_4_5_and_6)": "Создайте 1-й складской продукт (используя номера продуктов 1, 2, 3, 4, 5 и 6)", + "CREATE_3rd_WAREHOUSE_PRODUCTS(using_1st_product)": "Създайте 3-ти складови продукти (с 1-ва продукт)", + "CREATE_2nd_WAREHOUSE_PRODUCTS(using_product_number_1_2_and_3": "Создайте 2-й складской продукт (используя номера продуктов 1, 2 и 3", + "UPDATE_WAREHOUSE_GEO_LOCATION": "РАСПРОСТРАНЕНИЕ СКЛАДСКИХ СКЛАДОВ", + "UPDATE_1ST_WAREHOUSE_GEO_LOCATION": "Обновление местоположения 1-го хранилища", + "CREATE_1RD_ORDER(using_1st_warehouse_1st_user_and_1st_product)": "Создайте 1-й заказ (используя 1-й склад, 1-й пользователь и 1-й продукт)", + "CREATE_2ND_ORDER(using_1st_warehouse_1st_user_and_2nd_product)": "Создайте 2-й заказ (используя 1-й склад, 1-й пользователь и 2-й продукт)", + "CONFIRM_ORDER": "ПОДТВЕРДИТЕ ЗАКАЗ", + "CREATE_ORDER": "СОЗДАТЬ ЗАКАЗ", + "CONFIMR_1st_ORDER": "Подтвердить 1 заказ", + "CONFIMR_2nd_ORDER": "Подтвердить второй заказ", + "SETUP_MERCHANTS": { + "SETUP_MERCHANTS": "Настройки ресторана", + "PREV": "НАЗАД", + "NEXT": "СЛЕДУЮЩИЙ", + "ADD": "Добавлять", + "BACK": "Назад", + "SAVE": "Сохранить", + "SELECT": "Выбрать", + "CREATE": "Создайте", + "HOW_TO_SET_UP": { + "HOW_TO_SET_UP": "Как настроить" + }, + "STEPPER": { + "ACCOUNT": "Счет", + "BASIC_INFO": "Базовая информация", + "CONTACT_INFO": "Контактная информация", + "LOCATION": "Место нахождения", + "PAYMENTS": "Платежи", + "MANUFACTURING": "Производство", + "DELIVERY_AND_TAKEAWAY_SETTINGS": "Настройки доставки и доставки", + "ORDERS_SETTINGS": "Настройки заказов", + "PRODUCT_CATEGORIES": "Категории продукта", + "PRODUCTS": "Товары" + }, + "ACCOUNT": { + "ACCOUNT": "Учетная запись", + "EMAIL_ADDRESS": "Адрес электронной почты", + "EMAIL": "Эл. адрес", + "PASSWORD": "Пароль", + "REPEAT_PASSWORD": "Повторите пароль", + "EMAIL_IS_REQUIRED": "Требуется электронная почта", + "INVALID_EMAIL_FORMAT": "Неверный формат электронной почты", + "USERNAME": "Имя пользователя", + "USERNAME_IS_REQUIRED": "Имя пользователя требуется", + "USERNAME_MUST_BE_AT_LEAST_3_CHARACTERS": "Имя пользователя должно быть не менее 3 символов", + "PASSWORD_IS_REQUIRED": "Необходим пароль", + "PASSWORD_MUST_BE_AT_LEAST_4_CHARACTERS": "Пароль должен содержать не менее 4 символов", + "REPEAT_PASSWORD_IS_REQUIRED": "Требуется повторный пароль", + "PASSWORDS_DO_NOT_MATCH": "Пароли не совпадают" + }, + "BASIC_INFO": { + "BASIC_INFO": "Базовая информация", + "NAME": "Название", + "NAME_IS_REQUIRED": "Имя обязательно", + "NAME_MUST_BE_AT_LEAST_4_CHARACTERS": "Имя должно содержать не менее 4 символов", + "PHOTO": "Фото", + "ENTER_A_VALID_LOGO_URL_OR_BROWSE_FROM_A_DEVICE": "Введите действительный URL-адрес логотипа или перейдите с устройства", + "REMOVE": "Удалить", + "PHOTO_OPTIONAL": "Фото (необязательно)", + "BARCODE_DATA": "Данные штрих-кода", + "BARCODE_DATA_IS_REQUIRED": "Данные штрих-кода обязательны" + }, + "CONTACT_INFO": { + "CONTACT_INFO": "Контактная информация", + "CONTACT_PHONE": "Контактный телефон", + "INVALID_PHONE_NUMBER_FORMAT": "Неверный формат номера телефона", + "ORDER_FORWARDING_EMAIL": "Пересылка заказа по электронной почте", + "ORDER_FORWARDING_PHONE": "Заказать переадресацию телефона", + "ORDERS_EMAIL": "Электронная почта заказов", + "ORDERS_EMAIL_IS_REQUIRED": "Заказы по электронной почте требуется", + "INVALID_EMAIL_FORMAT": "Неверный формат электронной почты", + "ORDERS_PHONE": "Заказ телефона", + "ORDERS_PHONE_IS_REQUIRED": "Заказы по телефону обязательны" + }, + "LOCATION": { + "LOCATION": "Место нахождения" + }, + "PAYMENTS": { + "PAYMENTS": "Плащания", + "ALLOW_ONLINE_PAYMENT": "Разрешаване на онлайн плащания?", + "ALLOW_CASH_PAYMENT": "Разрешить наличные платежи?", + "STRIPE": { + "PAY_BUTTON_TEXT": "Текст кнопки оплаты", + "CURRENCY": "валюта", + "PAY_BUTTON_TEXT_IS_REQUIRED": "Текст кнопки оплаты требуется", + "CHOOSE_CURRENCY_CODE": "Выберите код валюты", + "CURRENCY_TEXT_IS_REQUIRED": "Текст валюты требуется", + "COMPANY_BRAND_LOGO": "Логотип бренда компании", + "COMPANY_BRAND_LOGO_IS_REQUIRED": "Требуется логотип компании", + "INVALID_LOGO_URL": "Неверный URL-адрес логотипа", + "INVALID_LOGO": "Неверный логотип", + "PUBLISHABLE_KEY": "Публикуемый ключ", + "PUBLISHABLE_KEY_IS_REQUIRED": "Требуется публикуемый ключ", + "ALLOW_REMEMBER_ME": "Разрешить запомнить меня?", + "REMOVE": "Удалить" + }, + "PAYPAL": { + "MODE": "Режим", + "CHOOSE_PAYPAL_MODE": "Выберите режим PayPal", + "TYPE": "тип", + "CURRENCY": "валюта", + "CHOOSE_CURRENCY_CODE": "Выберите код валюты", + "CURRENCY_TEXT_IS_REQUIRED": "Текст валюты требуется", + "PUBLISHABLE_KEY": "Публикуемый ключ", + "PUBLISHABLE_KEY_IS_REQUIRED": "Требуется публикуемый ключ", + "SECRET_KEY": "Секретный ключ", + "SECRET_KEY_IS_REQUIRED": "Требуется секретный ключ", + "PAYMENT_DESCRIPTION": "Описание оплаты", + "PAYMENT_DESCRIPTION_IS_REQUIRED": "Требуется описание платежа" + } + }, + "MANUFACTURING": { + "MANUFACTURING": "Производство", + "PRODUCTS_MANUFACTURING": "Производство продукции" + }, + "DELIVERY_AND_TAKEAWAY_SETTINGS": { + "DELIVERY_AND_TAKEAWAY_SETTINGS": "Настройки доставки и выноса", + "SELECT_FROM_SHARED_CARRIERS": "Выберите из общих перевозчиков", + "ADD_YOUR_CARRIER": "Добавьте ваш оператор", + "EDIT_CARRIER": "Редактировать перевозчик", + "CARRIER_REQUIRED": "Требуется перевозчик", + "PRODUCTS_DELIVERY_BY_DEFAULT": "Доставка продуктов (по умолчанию)", + "PRODUCTS_TAKEAWAY_BY_DEFAULT": "Продукты на вынос (по умолчанию)", + "USE_SELECTED_SHARED_CARRIERS": "Использовать выбранные общие носители", + "ADD_YOUR_CARRIERS": "Добавьте своих перевозчиков" + }, + "ORDERS_SETTINGS": { + "ORDERS_SETTINGS": "Настройки заказов", + "ORDER_BARCODE_QR_CODE_TYPES": "Укажите тип штрих-кода / QR-код" + }, + "PRODUCT_CATEGORIES": { + "PRODUCT_CATEGORIES": "Категории продукта", + "ADD_OWN_PRODUCT_CATEGORY": "Добавить собственную категорию продукта" + }, + "PRODUCTS": { + "PRODUCTS": "Товары", + "SELECT_FROM_PRODUCTS_CATALOG": "Выберите из каталога продукции", + "CREATE_PRODUCT": "Создать продукт", + "EDIT_PRODUCT": "Изменить продукт", + "ADD_PRODUCT": "Добавить товар", + "CREATE_NEW_PRODUCT": "Создать новый продукт" + } + } + }, + "WAREHOUSE_MANAGE": { + "TITLE": "Управление складом", + "MANAGE_STORE": "Управление магазином", + "SAVE": "Сохранить", + "NAME": "Имя", + "USERNAME": "Имя пользователя", + "PASSWORD": "Пароль", + "COUNTRY": "Страна", + "CITY": "Город", + "POSTCODE": "Почтовый индекс", + "IS_ACTIVE": "Активен", + "PRODUCTS_MANUFACTURING": "Производство продукции", + "CARRIER_REQUIRED": "Требуется перевозчик", + "RIGHT_NOW": "(прямо сейчас)", + "CARRIERS": "Носители", + "ADDRESS": "Адрес", + "CARRIERS_SPECIFIC": "Используйте только определенные носители", + "VALIDATION": { + "NAME": "Требуется имя", + "USERNAME": "Имя пользователя требуется", + "PASSWORD": "Необходим пароль", + "COUNTRY": "Страна обязательна", + "CITY": "Город требуется", + "STREET": "Улица необходима", + "HOUSE": "Требуется номер дома", + "POSTCODE": "Требуется почтовый индекс" + }, + "WIZARD_TITLES": { + "DETAILS": "Детали", + "ACCOUNT": "Счет", + "CONTACT_INFO": "Контактная информация", + "LOCATION": "Место нахождения", + "PAYMENT": "Оплата", + "DELIVERY_ZONES": "Зоны доставки" + } + }, + "WAREHOUSES_VIEW": { + "TITLE": "Складские помещения", + "DELETE_WAREHOUSES": "Удалить Выбранное", + "DELETE": "удалять", + "CREATE": "Создайте", + "SHOW_ON_MAP": "Показать на карте", + "SMART_TABLE_COLUMNS": { + "IMAGE": "Образ", + "NAME": "Имя", + "EMAIL": "Эл. адрес", + "PHONE": "Телефон", + "CITY": "Город", + "ADDRESS": "Адрес", + "ORDERS_QTY": "Количество заказов", + "ORDERS": "заказы" + }, + "INFO": { + "STORE_INFO": "Информация о магазине", + "STORE_ID": "Идентификатор магазина", + "STORE_NAME": "Название магазина" + }, + "MERCHANTS": { + "TRACK_ALL_MERCHANTS": "Отслеживать всех продавцов", + "FILTER_MERCHANTS": "Торговцы фильтрами", + "FILTER_BY_NAME": "Фильтр по имени", + "FILTER_BY_CITY": "Фильтр по городу", + "FILTER_BY_COUNTRY": "Фильтр по стране" + } + }, + "WAREHOUSE_VIEW": { + "ORDER": { + "CONFIRM": "Подтвердить", + "START_PROCESSING": "Начать обработку", + "START_ALLOCATION": "Начать распределение", + "ALLOCATED": "Выделено", + "ALLOCATION_FAILS": "Сбой распределения", + "START_PACKAGING": "Начало упаковки", + "PACKAGED": "В упаковке", + "PACKAGING_FAILS": "Сбой упаковки", + "GIVEN_TO_CARRIER": "Предоставлено перевозчику", + "GIVEN_TO_CUSTOMER": "Передано клиенту", + "ORDER": "Заказ", + "CANT_PROCESSING_WITHOUT_PRODUCTS": "Невозможно обработать заказ без продуктов." + }, + "MUTATION": { + "TITLE": "Создать новый склад", + "NAME": "Имя", + "LOGO": "Логотип", + "PHOTO": "Снимка", + "IS_ACTIVE": "Активен", + "PRODUCTS_MANUFACTURING": "Производство продукции", + "CARRIER_REQUIRED": "Требуется перевозчик", + "RIGHT_NOW": "Прямо сейчас", + "ORDERS_PHONE": "Телефон заказов", + "CONTACT_PHONE": "Контактный телефон", + "ORDERS_EMAIL": "Заказать электронную почту", + "CONTACT_EMAIL": "Электронная почта для контактов", + "FORWARD_ORDERS_WITH": "Форвардные заказы с", + "USERNAME": "Имя пользователя", + "OLD_PASSWORD": "Старый пароль", + "NEW_PASSWORD": "Новый Пароль", + "CONFIRM_PASSWORD": "Подтвердите Пароль", + "PASSWORDS_DO_NOT_MATCH": "Пароли не совпадают", + "PASSWORD": "Пароль", + "COUNTRY": "Страна", + "USA": "США", + "ISRAEL": "Израиль", + "BULGARIA": "Болгария", + "CITY": "Город", + "ADDRESS": "Адрес", + "POSTCODE": "Почтовый индекс", + "COORDINATES": "Координаты", + "AUTO_DETECT_COORDINATES": "Автоматическое определение координат", + "CARRIERS": "Носители", + "USE_ONLY_SPECIFIC_CARRIERS": "Используйте только определенные носители", + "SELECT_SHAPE_TO_ADD_ZONE": "Выберите фигуру, чтобы добавить новую зону", + "CIRCLE": "Круг", + "SHAPE": "Форма", + "DRAW_SHAPE_ON_MAP": "Нарисуйте фигуру на карте", + "MINIMUM_AMOUNT": "Минимальная сумма", + "DELIVERY_FEE": "Стоимость доставки", + "CANCEL": "Отмена", + "ADD": "Добавить", + "EDIT": "Редактировать", + "ZONE_NAME": "Название зоны", + "IN_STORE_MODE": "В режиме магазина", + "ERRORS": { + "NAME_IS_REQUIRED": "Требуется имя склада", + "NAME_ATLEAST_3_CHARS": "Имя должно быть не менее 3 символов.", + "NAME_MORE_THAN_255_CHARS": "Длина имени не может превышать 255 символов", + "LOGO_IS_REQUIRED": "Требуется логотип склада", + "INVALID_URL": "Введите правильный URL логотипа или загрузите с устройства", + "PHONE_CONTAINS_ONLY_DIGIT": "Номер телефона может начинаться с '+' и должен содержать только: '-,., (Пробел), #' и цифры символов", + "INVALID_EMAIL": "Неверный адрес электронной почты", + "ORDERS_PHONE_IS_REQUIRED": "Заказы Телефон требуется", + "CONTACT_PHONE_IS_REQUIRED": "Требуется контактный телефон", + "ORDERS_EMAIL_IS_REQUIRED": "Требуется электронная почта", + "CONTACT_EMAIL_IS_REQUIRED": "Требуется контактный адрес электронной почты", + "USERNAME_IS_REQUIRED": "Имя пользователя требуется", + "PASSWORD_IS_REQUIRED": "Необходим пароль", + "COORDINATES_ARE_REQUIRED": "Требуются координаты", + "COUNTRY_IS_REQUIRED": "Страна обязательна" + }, + "WIZARD_TITLES": { + "BASIC_INFO": "Базовая информация", + "CONTACT_INFO": "Контактная информация", + "LOCATION": "Место нахождения" + }, + "CONTACT_INFO_TAB": { + "ORDER_FORWARDING_EMAIL": "Пересылка заказа по электронной почте", + "ORDER_FORWARDING_PHONE": "Заказать переадресацию телефона" + } + }, + "NEW_PRODUCT": { + "SELECT_FROM_PRODUCTS_CATALOG": "Выберите из каталога продукции", + "CREATE_NEW_PRODUCT": "Создать новый продукт", + "HOW_TO_ADD": "Как добавить", + "ADD": "Добавлять", + "SAVE": "Сохранить", + "ADD_PRODUCTS_TO_STORE": "Добавить продукты в магазин", + "NOTHING_FOUND": "Ничего не найдено..." + }, + "SELECT_PRODUCTS": { + "TITLE": "Заглавие", + "DESCRIPTION": "Описание", + "DETAILS": "Детали", + "IMAGES": "Изображений", + "CATEGORY": "Kатегория" + }, + "SAVE": { + "PRODUCT_NAME": "Наименование товара", + "PRICE": "Цена", + "COUNT": "Подсчитывать", + "DELIVERY": "Доставка", + "TAKEAWAY": "Навынос" + }, + "PLACEHOLDER": { + "EXAMPLE": "Пример: Domino's Pizza", + "IMAGE_URL": "URL изображения", + "HERE_GOES_A_SHORT_DESCRIPTION": "Краткое описание", + "HERE_GOES_A_DETAILS_ABOUT_PRODUCT_(OPTION)": "Приводится подробная информация о продукте (опция)", + "REMOVE_IMAGE": "Удалить изображение" + }, + "CREATE_ORDER_MODAL": { + "TITLE": { + "CREATE_ORDER": "Создать заказ" + }, + "WIZARD_FORM": { + "BUTTON_NEXT": "Продолжайте", + "BUTTON_PREV": "Назад", + "BUTTON_DONE": "Закончить Заказ", + "STEP1": { + "TITLE": "Выберите вариант", + "SELECT_FROM_EXISTING": "Добави Нового", + "ADD_NEW_CUSTOMER": "Выбрать Клиента" + }, + "STEP2": { + "TITLE": { + "SELECT_CUSTOMER": "Выбрать клиента", + "SELECT_ADD": "Выбрать/Добавить", + "ADD_NEW": "Добавить Новое" + }, + "SMART_TABLE": { + "TITLES": { + "FULL_NAME": "Полное Имя", + "EMAIL": "Эл. Адрес", + "PHONE": "Телефон", + "ADDRESS": "Адрес" + } + } + }, + "STEP3": { + "TITLE": "Создать Заказ" + } + } + }, + "PRODUCTS_TAB": { + "PRODUCTS": "Товары", + "ADD_PRODUCTS": "Добавить товары", + "DELETE": "Добавить продукты", + "IMAGE": "Образ", + "TITLE": "заглавие", + "DESCRIPTION": "Описание", + "DETAILS": "подробности", + "CATEGORY": "категория", + "PRICE": "Цена", + "QUANTITY": "Количество", + "CLICK_ON_PRODUCT_IMAGE_TO_INCREASE_AVAILABLE_QUANTITY": "Нажмите на изображение продукта, чтобы увеличить доступное количество", + "AVAILABILITY": "Доступность", + "TYPE": "Тип", + "DELIVERY": "Доставка", + "TAKEAWAY": "Навынос" + }, + "NEW_PRODUCT_TYPE": "Новый тип продукта", + "ADD_PRODUCTS": "Добавить продукты", + "CREATE_ORDER": "Создать заказ", + "STATUS": "Положение дел", + "ORDERS": "заказы", + "WAREHOUSE": "Склад", + "PRODUCT": "Продукт", + "PRODUCTS": "Продукты", + "ORDER_NUMBER": "Номер заказа", + "CANCELLED": "Отменен", + "WAREHOUSE_STATUS": "Состояние склада", + "CARRIER_STATUS": "Статус перевозчика", + "PAID": "Оплаченный", + "CARRIER": "Перевозчик", + "CREATED": "Созданный", + "ELAPSED": "Прошедшее", + "CONTACT_DETAILS": "Контактная информация", + "EMAIL": "Эл. Адрес", + "PHONE": "Телефон", + "ORDERS_FORWARDING_DETAILS": "Заказы", + "ORDERS_FORWARDING_WITH": "Заказы на пересылку", + "MANAGE_STORE": "Управление магазином", + "TOP_PRODUCTS": "Лучшие продукты", + "PRODUCTS_MANUFACTURING": "Производство продукции", + "CARRIER_REQUIRED": "Требуется перевозчик", + "MANAGE_WAREHOUSE": "Управление складом", + "MANAGE_STORE_PRODUCTS_&_ORDERS": "Управление складскими продуктами и заказами" + }, + "SIMULATION_VIEW": { + "SIMULATION": "Моделирование", + "PURCHASE_PRODUCTS": "Покупка продуктов", + "CREATE_INVITE_REQUEST": "Создать запрос на приглашение", + "SEND": "Послать", + "CREATE_USER": "Создать пользователя", + "ORDER_CONFIRM": "Подтверждение заказа", + "ORDER_CANCEL": "Заказать отменить", + "PRODUCTS": "Товары", + "STORE": "хранить", + "ORDER": "Порядок", + "INVITE_REQUEST": "Запрос-приглашения", + "INVITE_USER": "Пригласить пользователя", + "TAB_BUTTONS": { + "PRODUCTS": "Товары", + "ORDER_HISTORY": "История заказов" + }, + "USER_MUTATION": { + "TITLE": "Создать клиента", + "NAME": "Имя", + "EMAIL": "Эл. адрес", + "COUNTRY": "Страна", + "CITY": "Город", + "USA": "США", + "ISRAEL": "Израиль", + "BULGARIA": "Болгария", + "ADDRESS": "Адрес", + "POSTCODE": "Почтовый индекс", + "COORDINATES": "Координаты", + "AUTO_DETECT_COORDINATES": "Автоматическое определение координат", + "ERRORS": { + "FIRST_NAME_IS_REQUIRED": "Имя требуется", + "LAST_NAME_IS_REQUIRED": "Фамилия обязательна", + "INVALID_EMAIL": "Неверный Email", + "EMAIL_IS_REQUIRED": "Требуется электронная почта", + "COORDINATES_ARE_REQUIRED": "Требуются координатыd" + }, + "WIZARD_TITLES": { + "ADDITIONAL_INFO": "Дополнительная информация", + "LOCATION": "Место нахождения" + } + }, + "SMART_TABLE": { + "TITLE": "Заглавие", + "ID": "Я бы", + "IMAGE": "Образ" + }, + "ORDER_INFO": { + "STATUSES": [ + { + "TITLE": "Мы готовим заказ!", + "DETAILS": "Вы получите его через %t минут.", + "NOT_PAID_NOTE": "Подготовьте ваш кошелек (%s наличными)." + }, + { + "TITLE": "Перевозчик на пути!", + "DETAILS": "Вы получите заказ в %t мин.", + "NOT_PAID_NOTE": "Подготовьте ваш кошелек (%s наличными)." + }, + { + "TITLE": "Проверьте дверь!", + "DETAILS": "Вы получите заказ в секундах.", + "NOT_PAID_NOTE": "Подготовьте ваш кошелек (%s наличными)." + }, + { + "TITLE": "Заказ завершен!", + "DETAILS": "Спасибо за использование Ever", + "NOT_PAID_NOTE": "" + } + ], + "DELIVERY_STATUS": { + "WE": "Мы", + "CARRIER": "Перевозчик", + "YOU": "Вы" + }, + "DELIVERY_WRONG": { + "DELIVERY_WRONG": "Доставка была неверной!", + "PROCESSING_WRONG": "Обработка была неверна!", + "TRY_AGAIN": "Пожалуйста, попробуйте еще раз.", + "CALL_FOR_DETAILS": "Вызов для подробностей" + } + }, + "INSTRUCTIONS_STEPS": { + "INSTRUTIONS": "инструкции", + "CREATE_USER_STEP": { + "CREATE_USER": "Создать пользователя", + "STEP_1": "Шаг 1", + "ORDER": "Заказ", + "TO_CONTINUE_IS_REQUIRED_TO_REGISTER_IN_THE_SYSTEM": "Для продолжения необходимо зарегистрироваться в системе", + "CLICK_ON_BUTTON_CREATE_USER": "Нажмите кнопку 'Создать пользователя'", + "FILL_THE_FORM_FOR_ADDITIONAL_INFO": "Заполните форму для дополнительной информации (необязательно)", + "FILL_THE_FORM_FOR_LOCATION_AND_PRESS_DONE_BUTTON": "Заполните форму для местоположения и нажмите кнопку DONE" + }, + "ORDER_STEP": { + "ORDER": "порядок", + "STEP_2": "Шаг 2.", + "CREATE_ORDER": "Создать заказ", + "CHOICE_SOME_PRODUCTS_FROM_THE_TABLE": "Выбирайте некоторые продукты из таблицы (вы можете увидеть более подробную информацию о том, когда нажимаете на его имя или изображение)", + "SELECT_PRODUCT": "Чтобы выбрать один продукт, нужно нажать на его строку.", + "SELECT_BUTTON_ORDER_TO_CREATE_ORDER": "Нажмите кнопку «Заказать», чтобы создать заказ с выбранным продуктом.", + "REVIEW_ORDER_HISTORY": "Просмотрите историю заказов:", + "ON_PRESS_ORDER_HISTORY_TAB": "В прессе «История заказов» отображаются все ваши заказы ..", + "HERE_YOU_CAN_SEE_DETAILS_ABOUT_EACH_ORDER": "Здесь вы можете увидеть подробности о каждом заказе", + "PRESS_ON_CARRIER_ORDER_OR_PRODUCT_NAME": "Для получения дополнительной информации нажмите на имя оператора, заказа или продукта." + }, + "CONFIRM_OR_CANCEL_STEP": { + "STEP_3": "Шаг 3", + "CONFIRM_CANCEL_ORDER": "Подтвердить / Отменить заказ", + "REAL_TIME": "В реальном времени", + "TRACK_STATUS_ON_YOUR_ORDER": "Отслеживайте статус в своем заказе.", + "ELAPSED_TIME_FROM_CREATE_TO_DELIVERED": "Истекшее время от созданного до поставленного.", + "SHOWS_MERCHANT_LOCATION": "Показывает местоположение продавца.", + "SHOWS_CARRIER_LOCATION": "Показывать местоположение оператора.", + "POSSIBILITIES": "Возможности:", + "SLIDER_REVIEW_OF_THE_ALL_PRODUCTS": "Ползунок обзор всех продуктов.", + "CAN_CANCEL_IT_WITH_ORDER_CANCEL_BUTTON": "Пока заказ не будет доставлен, пользователь отменит его с помощью кнопки 'Отмена заказа'.", + "AFTER_THE_ORDER_IS_DELIVER_USER_CAN_CLICK_BUTTON_ORDER_CONFIRM_TO_CONTINUE": "После доставки заказа пользователь может нажать кнопку «Подтвердить заказ», чтобы продолжить." + }, + "INVITE_STEP": { + "TO_CONTINUE_ITs_REQUIRED_TO_BE_INVITED_IN_THE_SYSTEM": "Чтобы продолжить его приглашение в систему:", + "SEND_INVITE_REQUEST": "Отправьте запрос «Пригласить запрос» в форму, которая будет открыта после нажатия кнопки «Пригласить запрос».", + "ALL_INVITE_REQUESTS_ARE_REVIEWED": "Все запросы на приглашение проверяются администратором, и их можно пригласить, если система доступна рядом с вашим местоположением (для теста вы можете сразу же создать кнопку Пригласить пользователя).", + "AFTER_YOU_GET_INVITED_BEFORE": "После того, как вы получите приглашение, вы можете легко войти в систему, просто введите код приглашения, который будет предоставлен из системы (здесь вы увидите код", + "AFTER_YOU_GET_INVITED_AFTER": "после нажатия кнопки Пригласить, и можете ввести его, когда нажимаете кнопку 'Создать пользователя')." + } + } + }, + "CUSTOMERS_VIEW": { + "TITLE": "Клиенты", + "DELETE_CUSTOMERS": "Удалить Выбранное", + "CREATE_CUSTOMER": "Создать клиента", + "DELETE": "удалять", + "CREATE": "Создайте", + "BAN": "БАН", + "UNBAN": "НЕБНГ", + "MANAGE_CUSTOMER": "Управление клиентом", + "CUSTOMER": "Клиент", + "CUSTOMERS_DEVICES": "Устройства для клиентов", + "INVITE": "Приглашение", + "NOT_INVITED_ONLY": "Только не приглашенные", + "ORDER": "Порядок", + "ORDERS_STATISTICS": "Статистика заказов", + "NUMBER_OF_ORDERS": "Количество заказов", + "CANCELED_ORDERS": "Отмененные заказы", + "COMPLETED_ORDERS_TOTAL": "Всего выполненных заказов", + "Order": "Порядок", + "CANCEL_ORDER": "Отменить заказ", + "CATEGORY": "Kатегория", + "ORDERS_HISTORY": "История на поръчките", + "AVAILABLE_PRODUCTS": "Доступные продукты", + "NEARBY_STORES": "Магазины поблизости", + "INVITES_REQUESTS_MANAGEMENT": "Приглашает управление запросами", + "INVITES_MANAGEMENT": "Управление приглашениями", + "DESCRIPTION": "Описание", + "DETAILS": "Детали", + "MAKE_A_CUSTOM_ORDER": "Сделать заказ", + "PRODUCT_COUNT": "Количество продуктов", + "QUANTITY_CAN'T_BE_EMPTY": "Количество не может быть пустым", + "QUANTITY_CAN'T_BE_0": "Количество не может быть 0", + "NOT_ENOUGH_PRODUCTS_AVAILABLE": "Недостаточно доступных продуктов", + "ORDER_INFO": "Информация о заказе", + "ORDER_ID": "Номер заказа", + "STORE_ID": "Идентификатор магазина", + "CARRIER_ID": "Идентификатор носителя", + "NO_CARRIER": "Нет перевозчика", + "SMART_TABLE_COLUMNS": { + "IMAGE": "Образ", + "NAME": "Имя", + "EMAIL": "Эл. адрес", + "PHONE": "Телефон", + "ADDRESS": "Адрес", + "ORDERS_QTY": "Количество заказов", + "COUNTRY": "Страна", + "CITY": "Город", + "STREET_ADDRESS": "Адрес улицы", + "HOUSE": "Дом", + "APARTMENT": "Квартира", + "INVITE_CODE": "Код приглашения", + "INVITED_DATE": "Приглашенная дата", + "ORDER_NUMBER": "Номер заказа", + "WAREHOUSE": "Склад", + "CARRIER": "Перевозчик", + "PRODUCT_LIST": "Список продуктов", + "STATS": "Статистика", + "DELIVERY_TIME": "Срок поставки", + "CREATED": "Созданный", + "ACTIONS": "Действия", + "PAID": "Оплаченный", + "COMPLETED": "Завершенный", + "CANCELLED": "Отменен", + "NOT_DELIVERED": "Не доставлен", + "PRODUCT": "Продукт", + "PRICE": "Цена", + "STORE": "Хранить", + "AVAILABLE_COUNT": "Доступный счетчик", + "ORDER": "Порядок", + "STATUS": "Положение дел" + }, + "EDIT": { + "EDIT_CUSTOMER": "Изменить клиента", + "BASIC_INFO": "Базовая информация", + "SAVE": "Сохранить" + }, + "DEVICE": { + "ALL_DEVICE": "Все устройства", + "DEVICE_ID": "Идентификатор устройства", + "ID": "Я бы", + "UPDATE": "Обновить", + "LANGUAGE": "язык", + "TYPE": "тип", + "TYPEU": "Тип", + "UUID": "Универсальный уникальный идентификатор", + "DEVICE_UUID": "Универсальный уникальный идентификатор", + "UPDATE_DEVICE": "Актуализиране на устройството", + "CUSTOMERS_DEVICES": "Устройства для клиентов", + "DELETE": "удалять", + "CREATE": "Создайте" + }, + "WAREHOUSE": { + "WAREHOUSE_INFO": "Складская информация", + "WAREHOUSE_ID": "Идентификатор склада", + "WAREHOUSE_NAME": "Название склада" + }, + "CUSTOMER_VIEW": { + "MANAGE_CUSTOMER": "Управление клиентом", + "EDIT": "редактировать", + "CUSTOMER": "Клиент" + }, + "INVITES_VIEW": { + "DELETE": "удалять", + "INVITE": "Приглашение" + } + }, + "CARRIERS_VIEW": { + "TITLE": "Носители", + "DELETE_CARRIERS": "Удалить выбранное", + "CREATE_CARRIER": "Создать перевозчика", + "DELETE": "удалять", + "CREATE_BUTTON": "Создайте", + "ACTIVE_AND_AVAILABLE_ORDERS": "Активные и доступные заказы", + "ORDERS_HISTORY": "История на поръчките", + "TRACK": "Следовать", + "SMART_TABLE_COLUMNS": { + "IMAGE": "Образ", + "NAME": "Имя", + "PHONE": "Телефон", + "STATUS": "Статус", + "ADDRESS": "Адрес", + "DELIVERIES": "Поставки" + }, + "CARRIER_PAGE": { + "WAREHOUSE": "Склад", + "CUSTOMER": "Клиент", + "SAVE": "Сохранить", + "EDIT": "Редактировать", + "CARRIER_INFO": "Информация о перевозчике", + "CARRIER_ID": "Идентификатор носителя", + "REGISTER_NEW_CARRIER": "Регистрация нового перевозчика", + "WAREHOUSE_STATUS": "Статус склада", + "CARRIER_STATUS": "Статус несущей", + "CREATED": "Созданный", + "ARRIVED_TO_CUSTOMER": "Прибытие в клиенту", + "FAILED": "Не смогли", + "DELIVERED": "Доставлен", + "CLIENT_REFUSE_ORDER": "Заказ клиента", + "AVAIBLE_ORDER_TO_PICK_UP": "Доступные заказы для подбора (каждый перевозчик может выбрать несколько заказов)", + "ACTIVE": "Активный", + "CARRIER_CAN_BE_SHARED": "Перевозчик может быть общим?", + "NOT_ACTIVE": "Не активен", + "WORKING": "За работой", + "NOT_WORKING": "Не работает", + "SELECT_CARRIER": "Выбрать перевозчика", + "CARRIER_ORDERS_STATUS": "Статус заказов перевозчика", + "Start": "Начало", + "PICKED_UP_ORDER": "Выбранный заказ", + "CANCEL": "Отмена", + "Arrived To Client": "Прибыл в клиенту", + "No Carrier": "Нет перевозчика", + "Order Selected For Delivery": "Заказ выбран для доставки", + "Order Picked Up": "Выбранный заказ", + "Order In Delivery": "Заказ в доставке", + "Delivered": "Доставлен", + "Delivery Issues": "Проблемы с доставкой", + "Client Refuse to Take Order": "Клиент отказывается принимать заказы", + "BAD_STATUS": "BAD_STATUS", + "Created": "Созданный", + "Confirmed": "Подтвердил", + "Processing": "Обработка", + "Allocation Started": "Началось распределение", + "Allocation Finished": "Выделение завершено", + "Packaging Started": "Начало упаковки", + "Packaged": "В упаковке", + "Given to Carrier": "Предоставлено перевозчику", + "Allocation Failed": "Не удалось выполнить распределение", + "Packaging Failed": "Ошибка упаковки", + "LOCATION": "Место нахождения", + "TIME": "Ввремя", + "NAME": "Имя" + }, + "EDIT": { + "EDIT_CARRIER": "Изменить несущую", + "BASIC_INFO": "Базовая информация", + "LOCATION": "Место нахождения", + "PHOTO_URL": "URL Адрес фотографии", + "CONTACT_PHONE": "Контактный телефон", + "FIRST_NAME": "Имя", + "LAST_NAME": "Фамилия" + }, + "CREATE": { + "BASIC_INFO": "Базовая информация", + "LOCATION": "Место нахождения" + }, + "TRACK_PAGE": { + "TRACK_ALL_WORKING_CARRIERS": "Отслеживание всех работающих перевозчиков", + "FILTER_CARRIERS": "Фильтр-носители", + "PHONE": "Телефон", + "EMAIL": "Эл. адрес", + "ADDRESS": "Адрес", + "DELIVERY_COUNT": "Счетчик доставки" + } + }, + "PRODUCT_TYPE_VIEW": { + "TITLE": "Новый продукт", + "WIZARD_FORM": { + "VALIDATION_MESSAGES": { + "TITLE": "Требуется название", + "THE_LENGHT_OF_THE_TITLE": "Длина заголовка должна быть не более 255 символов!", + "IMAGE": "Требуется фото", + "DESCRIPTION": "Требуется описание", + "LANGUAGE": "Требуется язык", + "THE_LENGHT_OF_THE_DESCRIPTION": "Длина описания должна быть не более 255 символов!", + "PRICE": "Требуется цена", + "COUNT": "Требуется количество товаров" + } + } + }, + "SHARED": { + "FORMS": { + "ERRORS": { + "FIRST_NAME_REQUIRED": "Имя требуется", + "LAST_NAME_REQUIRED": "Фамилия обязательна", + "USERNAME_REQUIRED": "Имя пользователя требуется", + "PASSWORD_REQUIRED": "Необходим пароль", + "PHONE_REQUIRED": "Требуется телефон", + "LOGO_URL_REQUIRED": "Введите допустимый URL-адрес изображения или найдите файл", + "IS_ACTIVE": "Требуется активное поле", + "COUNTRY_REQUIRED": "Страна обязательна", + "CITY_REQUIRED": "Страна обязательна", + "STREET_ADDRESS_REQUIRED": "Требуется адрес улицы", + "HOUSE_REQUIRED": "Требуется номер дома", + "COORDINATES_REQUIRED": "Требуются координаты", + "MUST_CONTAIN_ONLY_LETTERS": "Должен содержать только буквы", + "PHONE_MUST_CONTAINS_ONLY(specail_signs)AND_DIGIT_CHARACTER": "Номер телефона может начинаться с '+' и должен содержать только: '-,., (Пробел), #' и цифры символов" + } + }, + "USER": { + "FORMS": { + "BASIC_INFO": { + "TITLE": "Базовая Инфо.", + "FIRST_NAME": "Имя", + "FIRST_NAME_OPTIONAL": "Имя (необязательно)", + "LAST_NAME_OPTIONAL": "Фамилия (необязательно)", + "PHOTO_URL": "URL Адрес фотографии", + "PICTURE_URL": "URL изображения (необязательно)", + "EMAIL": "Эл. Адрес", + "EMAIL_OPTIONAL": "Электронная почта (необязательно)", + "ERRORS": { + "INVALID_EMAIL": "Неверный Эл. Адрес", + "EMAIL_IS_ALREADY_IN_USE": "Email уже используется" + } + } + } + }, + "WAREHOUSE": { + "ORDER_MODAL": { + "MAKE_ORDER": "Сделать Заказ", + "ONLY_AVAILABLE": "Доступные продукты", + "ORDER": "Порядок", + "SMART_TABLE": { + "TITLES": { + "IMG": "Образ", + "PRODUCT": "Продукт", + "PRICE": "Цена", + "AVAILABLE": "Доступный", + "AMOUNT": "Количество", + "COMMENT": "Комментарий" + } + } + } + } + }, + "MENU_VIEW": { + "DASHBOARD": "Доска", + "STORES": "Магазины", + "PRODUCTS": { + "PRODUCTS": "Продукты", + "MANAGEMENT": "Управление", + "CATEGORIES": "Категории" + }, + "CUSTOMERS": { + "CUSTOMERS": "Клиенты", + "MANAGEMENT": "Управление", + "INVITES": "Предлагает" + }, + "CARRIERS": "Перевозчик", + "SIMULATION": "Моделирование", + "SETUP": "Настроить" + }, + "CATEGORY_VIEW": { + "TITLE": "Заглавие", + "IMAGE": "Образ", + "CREATE_BUTTON": "СОЗДАЙТЕ", + "DELETE": "УДАЛЯТЬ", + "EDIT": { + "EDIT_CATEGORY": "Изменить Категорию", + "CATEGORY_NAME": "Название категории", + "ENTER_THE_CATEGORY_NAME": "Введите название категории", + "DONE": "Готово" + }, + "CREATE": { + "CREATE_CATEGORY": "Создать категорию", + "CATEGORY_NAME": "Название категории", + "ENTER_THE_CATEGORY_NAME": "Введите название категории", + "PHOTO": "Фото", + "BROWSE": "Просматривать", + "INVALID_URL": "Неверная ссылка", + "REMOVE_IMAGE": "Удалить изображение", + "PHOTO_OPTIONAL": "Фото (необязательно)", + "DONE": "Готово" + } + }, + "PLACEHOLDER": { + "EXAMPLE": "Пример: Domino's Pizza", + "IMAGE_URL": "URL изображения", + "HERE_GOES_A_SHORT_DESCRIPTION": "Краткое описание", + "HERE_GOES_A_DETAILS_ABOUT_PRODUCT_(OPTION)": "Приводится подробная информация о продукте (опция)", + "REMOVE_IMAGE": "Удалить изображение", + "PASSWORD": "Пароль", + "LATITUDE": "Широта", + "LONGITUDE": "Долгота", + "APARTMENT": "Квартира", + "HOUSE": "Дом", + "STREET": "Улица", + "ZIP": "Почтовый индекс", + "CITY": "Город", + "FIND_ADDRESS": "Найти адрес" + }, + "STATUS_TEXT": { + "Created": "Созданный", + "Confirmed": "Подтвердил", + "Processing": "Обработка", + "Allocation Started": "Началось распределение", + "Allocation Finished": "Выделение завершено", + "Packaging Started": "Начало упаковки", + "Packaged": "В упаковке", + "Given to Carrier": "Предоставлено перевозчику", + "Allocation Failed": "Не удалось выполнить распределение", + "Packaging Failed": "Ошибка упаковки", + "No Carrier": "Нет перевозчика", + "Order Selected For Delivery": "Заказ выбран для доставки", + "Order Picked Up": "Выбранный заказ", + "Order In Delivery": "Заказ в доставке", + "Arrived To Client": "Прибыл в клиенту", + "Delivered": "Доставлен", + "Delivery Issues": "Проблемы с доставкой", + "Client Refuse to Take Order": "Клиент отказывается принимать заказы", + "Given to Customer": "Передано клиенту", + "BAD_STATUS": "BAD_STATUS" + }, + "ELAPSED_TIME": { + "TITLE": "Пройденное время" + }, + "CONFIRM_MODAL": { + "ARE_YOU_SURE": "Уверены ли вы", + "ARE_YOU_SURE_YOU_WANT_TO_INCREASE": "Вы уверены, что хотите увеличить количество продуктов?", + "ARE_YOU_SURE_YOU_WANT_TO_DECREASE": "Вы уверены, что хотите уменьшить количество продуктов?", + "YES": "Да", + "NO": "Нет" + }, + "NO_SERVER_VIEW": { + "NO_SERVER": "Сервер упал" + }, + "BUTTON_NEXT": "Продолжайте", + "BUTTON_PREV": "Назад", + "BUTTON_DONE": "Закончить Заказ", + "TERRAIN": "Местность", + "SATELLITE": "Спутник", + "LOCATION": "Место нахождения", + "ROAD_MAP": "Дорожная карта", + "Manage warehouse": "Управление складскими продуктами и заказами", + "Warehouse": "Склад", + "Create Warehouse": "Создать склад", + "SIMULATION": "Моделирование", + "Purchase products": "Покупка продуктов", + "Manage": "Управлять", + "Orders": "Заказы", + "Confirmed": "Подтвердил", + "In Delivery": "По мере доставки", + "Not Confirmed": "Не подтверждено", + "Not paid": "Не выплачивается", + "Cancelled": "Отменено", + "All": "Все", + "CANCEL": "Отмена", + "Default Settings": "Настройки по умолчанию", + "Products Manufacturing": "Производство продукции", + "Carrier required before sale": "Перевозчик, требуемый до продажи", + "New Product Type": "Перевозчик, требуемый до продажи", + "Products": "Продукты", + "Product": "Продукт", + "Title": "Заглавие", + "Picture Url": "Ссылка на фотографию / Фото гиперссылка", + "Description": "Описание", + "Details": "Детали", + "Price": "Цена", + "CATEGORY": "Kатегория", + "LANGUAGE": "Язык", + "BROWSE": "ПРОСМАТРИВАТЬ", + "ENGLISH": "Английский", + "HEBREW": "Иврит", + "RUSSIAN": "Русский", + "BULGARIAN": "Болгарский", + "SPANISH": "Испанский", + "FRENCH": "Французский", + "SELECT": "Выбрать", + "Name": "Имя", + "Id": "Id", + "Warehouse name is required": "Требуется имя склада", + "Name must be at least 1 characters long": "Имя должно быть не менее 1 символа", + "Title cannot be more than 255 characters long": "Название не может содержать более 255 символов", + "Logo": "Логотип", + "Warehouse logo is required": "Требуется логотип склада", + "is Active": "Активен", + "right now": "прямо сейчас", + "Unselected": "Не выбрано", + "Phone": "Телефон", + "Email": "Эл. адрес", + "Username": "Имя пользователя", + "Username is required": "Имя пользователя требуется", + "Password": "Пароль", + "Country": "Страна", + "USA": "США", + "Israel": "Израиль", + "Bulgaria": "Болгария", + "City": "Город", + "Address": "Адрес", + "Postcode": "Почтовый индекс", + "Coordinates": "Координаты", + "Auto detect coordinates": "Автоматическое определение координат", + "Carriers": "Носители", + "Carrier": "Перевозчик", + "Use only specific carriers": "Используйте только определенные носители", + "Manage carrier and deliveries": "Управление перевозчиком и поставками", + "Register New Carrier": "Регистрация нового перевозчика", + "Create User": "Создать пользователя", + "OPTIONAL": "необязательный" +} diff --git a/packages/admin-web-angular/src/assets/i18n/ru.json b/packages/admin-web-angular/src/assets/i18n/ru.json new file mode 100644 index 0000000..9e37a34 --- /dev/null +++ b/packages/admin-web-angular/src/assets/i18n/ru.json @@ -0,0 +1,1169 @@ +{ + "COMMON": { + "SAVE": "Сохранить", + "CANCEL": "Отмена", + "USA": "США", + "ISRAEL": "Israel", + "BULGARIA": "Болгария" + }, + "ORDER_VIEW": { + "ORDER_HEADER_INFO": { + "ORDER": "Заказ", + "MANAGE_ORDER": "Управление заказами", + "TOTAL": "Итого" + }, + "ORDER_PRODUCT_INFO": { + "ORDER_PRODUCTS": "Заказать товары", + "ADD_PRODUCTS": "Добавить товары", + "REMOVE_PRODUCTS": "Удалить товары", + "CANCEL_ORDER": "Отменить заказ", + "THE_ORDER_IS_CANCELED": "Заказ отменен", + "THE_ORDER_IS_GIVEN_TO_CARRIER": "Заказ выдан перевозчику.", + "THE_ORDER_IS_DELIVERED": "Заказ доставлен.", + "ADD_PRODUCTS_MODAL": "Добавить товары", + "ADD": "Добавить", + "SUCCESS_TOAST": "Товары были добавлены в заказ", + "ERROR_TOAST": "Ошибка, что-то пошло не так", + "SMART_TABLE": { + "NAME": "Наименование", + "QTY": "Кол-во", + "PRICE": "Цена", + "IMAGE": "Изображение", + "COMMENT": "Комментарий" + } + }, + "ORDER_SIDEBAR": { + "CONTACT_DETAILS": "Контактная информация", + "WAREHOUSE": "Склад", + "CUSTOMER": "Покупатель", + "CARRIER": "Перевозчик", + "QTY": "кол-во" + }, + "LOCATION_INFO": { + "MAP": "Карта", + "DELIVERY_DISTANCE": "Расстояние доставки:" + } + }, + "DASHBOARD_VIEW": { + "TOTAL_CUSTOMER": "Всего клиентов", + "TOTAL_QUANTITY_OF_EXISTING_CUSTOMERS": "Общее количество существующих клиентов", + "TOTAL_COMPLETED_ORDERS": "Всего выполненных заказов", + "TOTAL_QUANTITY_OF_COMPLETED_ORDERS": "Общее количество выполненных заказов", + "TOTAL_REVENUE": "Общий доход", + "TOTAL_SUM_SUM_OF_ALL_COMPLETED_ORDERS": "Общая сумма всех выполненных заказов", + "TODAYs_CUSTOMERS": "Клиенты сегодня", + "TODAYs_QUANTITY_OF_REGISTERED_CUSTOMERS": "Количество зарегистрированных клиентов сегодня", + "TODAYs_COMPLETED_ORDERS": "Сегодня выполненные заказы", + "TODAYs_QUANTITY_OF_NEW_COMPLETED_ORDERS": "Количество новых выполненных заказов на сегодня", + "TODAYs_REVENUE": "Доход за сегодня", + "TODAYs_SUM_OF_COMPLETED_ORDERS": "Итоговая сумма выполненных заказов", + "TILL_AVERAGE": "до среднего", + "BETTER_THAN_AVERAGE": "лучше среднего", + "SELECT_COMPONENT": { + "STORES": "Магазин", + "CONTACT_DETAILS": "Контактная информация", + "PHONE": "Телефон", + "EMAIL": "Почта", + "ORDERS_FORWARDING_WITH": "Переадресация заказов с", + "SELECT_STORE": "Выберите магазин" + }, + "CHARTS": { + "ORDERS": "Заказы", + "PROFIT": "Прибыль", + "TOTAL_ORDERS": "Всего заказов", + "TOTAL_COMPLETED_ORDERS": "Всего выполненных заказов", + "TOTAL_CANCELLED_ORDERS": "Всего отмененных заказов", + "TOTAL_REVENUE_ALL_ORDERS": "Общая выручка от всех заказов", + "TOTAL_REVENUE_COMPLETED_ORDERS": "Общая выручка от выполненных заказов", + "TOTAL_LOST_REVENUE_CANCELLED_ORDERS": "Всего потерянных доходов от отмененных заказов", + "PAYMENT": "Оплата", + "CANCELED": "Отменено", + "ALL_ORDERS": "Все заказы", + "TODAY": "Сегодня", + "LAST_WEEK": "Прошлая неделя", + "LAST_MONTH": "Прошлый месяц", + "CURRENT_YEAR": "Текущий год", + "YEARS": "По году", + "CUSTOM_PERIOD": "Пользовательский период", + "SELECT_PERIOD": "Выберите период", + "FROM": "От", + "TO": "Кому", + "SELECT": "Выбрать", + "LABELS": { + "WEEK": "Неделя", + "WEEKDAYS": { + "MON": "Mon", + "TUE": "Вт", + "WED": "Ср", + "THU": "Thu", + "FRI": "Пт", + "SAT": "Сб", + "SUN": "Вс" + }, + "MONTHS": { + "JAN": "Ян", + "FEB": "Февраль", + "MAR": "Мар", + "APR": "Апр", + "MAY": "Май", + "JUN": "Июнь", + "JUL": "Июль", + "AUG": "Авг", + "SEP": "Сен", + "OCT": "Окт", + "NOV": "Ноя", + "DEC": "Дек" + } + } + } + }, + "HEADER_VIEW": { + "ADMIN": "Админ", + "EVER": "Всегда", + "PROFILE": "Профиль", + "LOG_OUT": "Выйти" + }, + "FOOTER_VIEW": { + "COPY_RIGHT": "Авторское право © 2016 подарок", + "ALL_RIGHTS_RESERVED": "Все права защищены" + }, + "PROFILE_VIEW": { + "PROFILE_PAGE": "Страница профиля", + "BASIC_INFO": "Основная информация", + "ACCOUNT": "Аккаунт", + "USERNAME": "Имя пользователя", + "ERROR": "Ошибка", + "EMAIL": "Почта", + "FIRST_NAME": "First Name", + "FIRST_NAME_OPTIONAL": "Имя (необязательно)", + "LAST_NAME": "Фамилия (необязательно)", + "PICTURE_URL": "URL изображения (необязательно)", + "BROWSE": "Обзор", + "REMOVE": "Удалить", + "SAVE": "Сохранить", + "OLD_PASSWORD": "Старый пароль", + "NEW_PASSWORD": "Новый пароль", + "REPEAT_NEW_PASSWORD": "Повторите новый пароль", + "INVALID_EMAIL_ADDRESS": "Неверный адрес электронной почты", + "INVALID_URL": "Неверный URL", + "NAME_MUST_CONTAIN_ONLY_LETTERS": "Имя должно содержать только буквы", + "PASSWORDS_DO_NOT_MATCH": "Пароли не совпадают", + "SUCCESSFULLY_CHANGE_PASSWORD": "Пароль успешно изменен" + }, + "PRODUCTS_VIEW": { + "DELETE": "УДАЛИТЬ", + "CREATE": "СОЗДАТЬ", + "EDIT_VIEW": { + "EDIT_PRODUCT": "Изменить продукт", + "BASIC_INFO": "Основная информация", + "SAVE": "Сохранить" + } + }, + "FAKE_DATA": { + "FAKE_DATA_GENERATOR": "Фальшивый генератор данных", + "GENERATE_ALL": "Сгенерировать все", + "CREATE_100_USERS": "Создать 100 клиентов", + "CREATE_100_CARRIERS": "Создать 100 перевозчиков", + "CREATE_100_WAREHOUSES": "Создать 100 складов", + "SETUP": "НАСТРОЙКИ", + "GENERATE_INITIAL_DATA": "Создать начальные данные", + "CREATE_INVITE": "СОЗДАТЬ ПРИГЛАСИТЬ", + "HARDCODED_DATA": "Хардкодированные данные", + "CLEAR_ALL": "ОЧИСТИТЬ ВСЕ", + "GENERATE_HARDCODED_ONLY": "Генерировать только Hardcoded", + "INCLUDED_HARDCODED_DATA": "Включить жесткие данные", + "CREATE_1st_INVITE": "Создать первое приглашение", + "CREATE_2st_INVITE": "Создать 2-е приглашение", + "CREATE_3st_INVITE": "Создать 3-е приглашение", + "CREATE_4st_INVITE": "Создать 4-е приглашение", + "CREATE_CUSTOMER": "Создать клиента", + "CREATE_USER": "СОЗДАТЬ ПОЛЬЗОВАТЕЛЯ", + "CREATE_1st_USER": "Создать первого пользователя (используя первое приглашение)", + "CREATE_CARRIER": "СОЗДАТЬ КАРТЕРА", + "CREATE_1st_CARRIER": "Создать первого оператора", + "CREATE_2nd_CARRIER": "Создать второго перевозчика", + "CREATE_3rd_CARRIER": "Создать 3-го оператора", + "CREATE_PRODUCT": "СОЗДАТЬ ПРОДУКЦИЮ", + "CREATE_PEPERONI&MUSHROOM_PIZZA_PRODUCT": "Создать продукт Peperoni и Грибной пиццы", + "CREATE_SUSHI&CAVIAR_PRODUCT": "Создать продукт Sushi & Caviar", + "CREATE_SUSHI_MIX_PRODUCT": "Создать смесь товаров Sushi", + "CREATE_PASTA_PRODUCT": "Создать продукт Pasta", + "CREATE_SUSHI_BOX_PRODUCT": "Создать продукт Sushi box", + "CREATE_PEPERONI&TOMATO_PIZZA_PRODUCT": "Создать продукт Peperoni и пиццу томатов", + "CREATE_WAREHOUSE": "СОЗДАТЬ ВОССТАНОВИТЬ", + "CREATE_1st_WAREHOUSE": "Создать 1-й склад", + "CREATE_2nd_WAREHOUSE": "Создать второй склад", + "CREATE_3rd_WAREHOUSE": "Создать 3-й склад", + "CREATE_WAREHOUSE_PRODUCT": "СОЗДАТЬ ОБВАЛЕНИЕ ПРОДУКЦИИ", + "CREATE_1st_WAREHOUSE_PRODUCTS(using_product_number_1_2_3_4_5_and_6)": "Создайте 1-й склад (используя номер товара 1, 2, 3, 4, 5 и 6)", + "CREATE_3rd_WAREHOUSE_PRODUCTS(using_1st_product)": "Создать 3-й склад (используя 1-й товар)", + "CREATE_2nd_WAREHOUSE_PRODUCTS(using_product_number_1_2_and_3": "Создайте 2-й склад (используя товары номер 1, 2 и 3", + "UPDATE_WAREHOUSE_GEO_LOCATION": "ОБНОВИТЬ ВОССТАНОВЛЕНИЕ ГОДА", + "UPDATE_1ST_WAREHOUSE_GEO_LOCATION": "Обновить гео-расположение 1-го склада", + "CREATE_1RD_ORDER(using_1st_warehouse_1st_user_and_1st_product)": "Создать 1-й заказ (используя 1-й склад, 1-й и 1-й товар)", + "CREATE_2ND_ORDER(using_1st_warehouse_1st_user_and_2nd_product)": "Создать второй заказ (используя 1-й склад, 1-й и 2-й товар)", + "CONFIRM_ORDER": "Подтвердить заказ", + "CREATE_ORDER": "СОЗДАТЬ ЗАКАЗ", + "CONFIMR_1st_ORDER": "Подтвердите первый заказ", + "CONFIMR_2nd_ORDER": "Подтвердите второй заказ", + "SETUP_MERCHANTS": { + "SETUP_MERCHANTS": "Настроить торговцев", + "PREV": "ПРЕДУПРЕЖДЕНИЕ", + "NEXT": "СЛЕДУЮЩИЙ", + "ADD": "Добавить", + "BACK": "Назад", + "SAVE": "Сохранить", + "SELECT": "Выбрать", + "CREATE": "Создать", + "HOW_TO_SET_UP": { + "HOW_TO_SET_UP": "Как настроить" + }, + "STEPPER": { + "ACCOUNT": "Аккаунт", + "BASIC_INFO": "Основная информация", + "CONTACT_INFO": "Контактная информация", + "LOCATION": "Местоположение", + "PAYMENTS": "Платежи", + "MANUFACTURING": "Производство", + "DELIVERY_AND_TAKEAWAY_SETTINGS": "Настройки доставки и размещения", + "ORDERS_SETTINGS": "Настройки заказов", + "PRODUCT_CATEGORIES": "Категории товаров", + "PRODUCTS": "Товары" + }, + "ACCOUNT": { + "ACCOUNT": "Аккаунт", + "EMAIL_ADDRESS": "Email адрес", + "EMAIL": "Почта", + "PASSWORD": "Пароль", + "REPEAT_PASSWORD": "Повторить пароль", + "EMAIL_IS_REQUIRED": "Требуется адрес электронной почты", + "INVALID_EMAIL_FORMAT": "Invalid email format", + "USERNAME": "Имя пользователя", + "USERNAME_IS_REQUIRED": "Требуется имя пользователя", + "USERNAME_MUST_BE_AT_LEAST_3_CHARACTERS": "Имя пользователя должно содержать не менее 3 символов", + "PASSWORD_IS_REQUIRED": "Требуется пароль", + "PASSWORD_MUST_BE_AT_LEAST_4_CHARACTERS": "Пароль должен содержать не менее 4 символов", + "REPEAT_PASSWORD_IS_REQUIRED": "Требуется повтор пароля", + "PASSWORDS_DO_NOT_MATCH": "Пароли не совпадают" + }, + "BASIC_INFO": { + "BASIC_INFO": "Основная информация", + "NAME": "Наименование", + "NAME_IS_REQUIRED": "Требуется имя", + "NAME_MUST_BE_AT_LEAST_4_CHARACTERS": "Имя должно быть не менее 4 символов", + "PHOTO": "Фото", + "ENTER_A_VALID_LOGO_URL_OR_BROWSE_FROM_A_DEVICE": "Введите правильный URL логотипа или выберите устройство", + "REMOVE": "Удалить", + "PHOTO_OPTIONAL": "Фото (необязательно)", + "BARCODE_DATA": "Данные штрих-кода", + "BARCODE_DATA_IS_REQUIRED": "Требуется ввести данные штрих-кода" + }, + "CONTACT_INFO": { + "CONTACT_INFO": "Контактная информация", + "CONTACT_PHONE": "Контактный телефон", + "INVALID_PHONE_NUMBER_FORMAT": "Неверный формат номера телефона", + "ORDER_FORWARDING_EMAIL": "Письмо о пересылке заказа", + "ORDER_FORWARDING_PHONE": "Телефон пересылки заказа", + "ORDERS_EMAIL": "Email Заказов", + "ORDERS_EMAIL_IS_REQUIRED": "Требуется адрес электронной почты заказов", + "INVALID_EMAIL_FORMAT": "Invalid email format", + "ORDERS_PHONE": "Телефон заказов", + "ORDERS_PHONE_IS_REQUIRED": "Требуется телефон заказов" + }, + "LOCATION": { + "LOCATION": "Местоположение" + }, + "PAYMENTS": { + "PAYMENTS": "Платежи", + "ALLOW_ONLINE_PAYMENT": "Разрешить онлайн-платежи?", + "ALLOW_CASH_PAYMENT": "Разрешить оплату наличными?", + "STRIPE": { + "PAY_BUTTON_TEXT": "Текст кнопки оплаты", + "CURRENCY": "Валюта", + "PAY_BUTTON_TEXT_IS_REQUIRED": "Требуется текст кнопки оплаты", + "CHOOSE_CURRENCY_CODE": "Выберите код валюты", + "CURRENCY_TEXT_IS_REQUIRED": "Текст валюты обязательно вводить", + "COMPANY_BRAND_LOGO": "Логотип бренда компании", + "COMPANY_BRAND_LOGO_IS_REQUIRED": "Требуется логотип бренда компании", + "INVALID_LOGO_URL": "Неверный URL логотипа", + "INVALID_LOGO": "Неверный логотип", + "PUBLISHABLE_KEY": "Публичный ключ", + "PUBLISHABLE_KEY_IS_REQUIRED": "Требуется опубликованный ключ", + "ALLOW_REMEMBER_ME": "Позвольте запомнить меня?", + "REMOVE": "Удалить" + }, + "PAYPAL": { + "MODE": "Режим", + "CHOOSE_PAYPAL_MODE": "Выберите режим PayPal", + "TYPE": "тип", + "CURRENCY": "Валюта", + "CHOOSE_CURRENCY_CODE": "Выберите код валюты", + "CURRENCY_TEXT_IS_REQUIRED": "Текст валюты обязательно вводить", + "PUBLISHABLE_KEY": "Публичный ключ", + "PUBLISHABLE_KEY_IS_REQUIRED": "Требуется опубликованный ключ", + "SECRET_KEY": "Секретный ключ", + "SECRET_KEY_IS_REQUIRED": "Требуется секретный ключ", + "PAYMENT_DESCRIPTION": "Описание платежа", + "PAYMENT_DESCRIPTION_IS_REQUIRED": "Требуется описание платежа" + } + }, + "MANUFACTURING": { + "MANUFACTURING": "Производство", + "PRODUCTS_MANUFACTURING": "Производство товаров" + }, + "DELIVERY_AND_TAKEAWAY_SETTINGS": { + "DELIVERY_AND_TAKEAWAY_SETTINGS": "Настройки доставки и размещения", + "SELECT_FROM_SHARED_CARRIERS": "Выбрать из общих перевозчиков", + "ADD_YOUR_CARRIER": "Добавить оператора", + "EDIT_CARRIER": "Редактирование оператора", + "CARRIER_REQUIRED": "Перевозчик требуется", + "PRODUCTS_DELIVERY_BY_DEFAULT": "Доставка товаров (по умолчанию)", + "PRODUCTS_TAKEAWAY_BY_DEFAULT": "Вынос товаров (по умолчанию)", + "USE_SELECTED_SHARED_CARRIERS": "Использовать выбранные общие перевозчики", + "ADD_YOUR_CARRIERS": "Добавить перевозчиков" + }, + "ORDERS_SETTINGS": { + "ORDERS_SETTINGS": "Настройки заказов", + "ORDER_BARCODE_QR_CODE_TYPES": "Order Barcode/QR code types" + }, + "PRODUCT_CATEGORIES": { + "PRODUCT_CATEGORIES": "Категории товаров", + "ADD_OWN_PRODUCT_CATEGORY": "Добавить собственную категорию товара" + }, + "PRODUCTS": { + "PRODUCTS": "Товары", + "SELECT_FROM_PRODUCTS_CATALOG": "Выберите из каталога товаров", + "CREATE_PRODUCT": "Создать продукт", + "EDIT_PRODUCT": "Редактировать товар", + "ADD_PRODUCT": "Добавить товар", + "CREATE_NEW_PRODUCT": "Создать новый товар" + } + } + }, + "WAREHOUSE_MANAGE": { + "TITLE": "Управление складом", + "MANAGE_STORE": "Управление магазином", + "SAVE": "Сэкономьте", + "NAME": "Наименование", + "USERNAME": "Имя пользователя", + "PASSWORD": "Пароль", + "COUNTRY": "Страна", + "CITY": "Город", + "POSTCODE": "Почтовый индекс", + "IS_ACTIVE": "Активно", + "PRODUCTS_MANUFACTURING": "Производство товаров", + "CARRIER_REQUIRED": "Перевозчик требуется", + "RIGHT_NOW": "(прямо сейчас)", + "CARRIERS": "Перевозчики", + "ADDRESS": "Адрес", + "CARRIERS_SPECIFIC": "Использовать только определенные перевозчики", + "VALIDATION": { + "NAME": "Требуется имя", + "USERNAME": "Требуется имя пользователя", + "PASSWORD": "Требуется пароль", + "COUNTRY": "Необходимо указать страну", + "CITY": "Необходимо указать город", + "STREET": "Улица обязана", + "HOUSE": "Требуется указать номер дома", + "POSTCODE": "Требуется почтовый индекс" + }, + "WIZARD_TITLES": { + "DETAILS": "Детали", + "ACCOUNT": "Аккаунт", + "CONTACT_INFO": "Контактная информация", + "LOCATION": "Местоположение", + "PAYMENT": "Оплата", + "DELIVERY_ZONES": "Зоны доставки" + } + }, + "WAREHOUSES_VIEW": { + "TITLE": "Магазин", + "DELETE_WAREHOUSES": "Удалить выбранные", + "DELETE": "УДАЛИТЬ", + "CREATE": "СОЗДАТЬ", + "SHOW_ON_MAP": "Показать на карте", + "SMART_TABLE_COLUMNS": { + "IMAGE": "Изображение", + "NAME": "Наименование", + "EMAIL": "Почта", + "PHONE": "Телефон", + "CITY": "Город", + "ADDRESS": "Адрес", + "ORDERS_QTY": "QTY заказов", + "ORDERS": "Заказы" + }, + "INFO": { + "STORE_INFO": "Информация о магазине", + "STORE_ID": "ID магазина", + "STORE_NAME": "Название магазина" + }, + "MERCHANTS": { + "TRACK_ALL_MERCHANTS": "Отслеживать всех торговцев", + "FILTER_MERCHANTS": "Фильтровать торговцев", + "FILTER_BY_NAME": "Фильтр по имени", + "FILTER_BY_CITY": "Фильтр по городу", + "FILTER_BY_COUNTRY": "Фильтр по стране" + } + }, + "WAREHOUSE_VIEW": { + "ORDER": { + "CONFIRM": "Подтвердить", + "START_PROCESSING": "Начать обработку", + "START_ALLOCATION": " Начать распределение", + "ALLOCATED": "Выделено", + "ALLOCATION_FAILS": "Не удалось выделить", + "START_PACKAGING": "Начать упаковку", + "PACKAGED": "Упакован", + "PACKAGING_FAILS": "Сбой упаковки", + "GIVEN_TO_CARRIER": "Дано перевозчику", + "GIVEN_TO_CUSTOMER": "Выдано клиенту", + "ORDER": "Заказ", + "CANT_PROCESSING_WITHOUT_PRODUCTS": "Не удается обработать заказ без продуктов." + }, + "MUTATION": { + "TITLE": "Зарегистрировать новый магазин", + "NAME": "Наименование", + "LOGO": "Логотип", + "PHOTO": "Фото", + "IS_ACTIVE": "Активно", + "ORDERS_SHORT_PROCESS": "Краткий процесс заказов", + "PRODUCTS_MANUFACTURING": "Производство товаров", + "CARRIER_REQUIRED": "Перевозчик требуется", + "RIGHT_NOW": "Прямо сейчас", + "ORDERS_PHONE": "Телефон заказов", + "CONTACT_PHONE": "Контактный телефон", + "ORDERS_EMAIL": "Email Заказов", + "CONTACT_EMAIL": "Контактный адрес электронной почты", + "FORWARD_ORDERS_WITH": "Переслать заказы с", + "USERNAME": "Имя пользователя", + "OLD_PASSWORD": "Старый пароль", + "NEW_PASSWORD": "Новый пароль", + "CONFIRM_PASSWORD": "Подтверждение пароля", + "PASSWORDS_DO_NOT_MATCH": "Пароли не совпадают", + "PASSWORD": "Пароль", + "COUNTRY": "Страна", + "USA": "США", + "ISRAEL": "Israel", + "BULGARIA": "Болгария", + "CITY": "Город", + "ADDRESS": "Адрес", + "POSTCODE": "Почтовый индекс", + "COORDINATES": "Координаты", + "AUTO_DETECT_COORDINATES": "Автоопределение координат", + "CARRIERS": "Перевозчики", + "USE_ONLY_SPECIFIC_CARRIERS": "Использовать только определенные перевозчики", + "SELECT_SHAPE_TO_ADD_ZONE": "Выберите форму для добавления новой зоны", + "CIRCLE": "Круг", + "SHAPE": "Форма", + "DRAW_SHAPE_ON_MAP": "Нарисуйте фигуру на карте", + "MINIMUM_AMOUNT": "Минимальная сумма", + "DELIVERY_FEE": "Стоимость доставки", + "CANCEL": "Отмена", + "ADD": "Добавить", + "EDIT": "Редактирование", + "ZONE_NAME": "Название зоны", + "UNALLOWED_ORDER_CANCELATION": "Неразрешенная отмена заказа", + "IN_STORE_MODE": "Режим в магазине", + "ORDER_CANCELATION_OPTIONS": { + "ORDERING": "После заказа", + "START_PROCESSING": "После начала обработки", + "START_ALLOCATION": "После начала распределения", + "ALLOCATED": "После выделенного", + "START_PACKAGING": "После начала упаковки", + "PACKAGED": "После Упакованного", + "CARRIER_TAKE_WORK": "После того, как Carrier выполнит работу", + "CARRIER_GOT_IT": "После того, как перевозчик понял это", + "CARRIER_START_DELIVERY": "После начала доставки перевозчика", + "DELIVERED": "После доставки" + }, + "ERRORS": { + "NAME_IS_REQUIRED": "Требуется название склада", + "NAME_ATLEAST_3_CHARS": "Имя должно быть не менее 3 символов", + "NAME_MORE_THAN_255_CHARS": "Имя не может быть длиннее 255 символов", + "LOGO_IS_REQUIRED": "Требуется логотип склада", + "INVALID_URL": "Введите правильный URL логотипа или загрузите его с устройства", + "PHONE_CONTAINS_ONLY_DIGIT": "Номер телефона должен содержать только цифры", + "INVALID_EMAIL": "Invalid Email", + "ORDERS_PHONE_IS_REQUIRED": "Требуется телефон заказа", + "CONTACT_PHONE_IS_REQUIRED": "Требуется контактный телефон", + "ORDERS_EMAIL_IS_REQUIRED": "Требуется адрес электронной почты заказов", + "CONTACT_EMAIL_IS_REQUIRED": "Требуется контактный e-mail", + "USERNAME_IS_REQUIRED": "Требуется имя пользователя", + "PASSWORD_IS_REQUIRED": "Требуется пароль", + "COORDINATES_ARE_REQUIRED": "Необходимо указать координаты", + "COUNTRY_IS_REQUIRED": "Необходимо указать страну" + }, + "WIZARD_TITLES": { + "BASIC_INFO": "Основная информация", + "CONTACT_INFO": "Контактная информация", + "LOCATION": "Местоположение" + }, + "CONTACT_INFO_TAB": { + "ORDER_FORWARDING_EMAIL": "Письмо о пересылке заказа", + "ORDER_FORWARDING_PHONE": "Телефон пересылки заказа" + } + }, + "NEW_PRODUCT": { + "SELECT_FROM_PRODUCTS_CATALOG": "Выбрать из каталога товаров", + "CREATE_NEW_PRODUCT": "Создать новый продукт", + "HOW_TO_ADD": "Как добавить", + "ADD": "Добавить", + "SAVE": "Сохранить", + "ADD_PRODUCTS_TO_STORE": "Добавить товары в магазин", + "NOTHING_FOUND": "Ничего не найдено..." + }, + "SELECT_PRODUCTS": { + "TITLE": "Заголовок", + "DESCRIPTION": "Описание", + "DETAILS": "Детали", + "IMAGES": "Изображения", + "CATEGORY": "Категория" + }, + "SAVE": { + "PRODUCT_NAME": "Название товара", + "PRICE": "Цена", + "COUNT": "Счетчик", + "DELIVERY": "Доставка", + "TAKEAWAY": "На вынос" + }, + "PLACEHOLDER": { + "EXAMPLE": "Пример: Пицца Домино", + "IMAGE_URL": "Ссылка на изображение", + "HERE_GOES_A_SHORT_DESCRIPTION": "Краткое описание", + "HERE_GOES_A_DETAILS_ABOUT_PRODUCT_(OPTION)": "Здесь идет подробная информация о продукте (опцион)", + "REMOVE_IMAGE": "Remove image" + }, + "CREATE_ORDER_MODAL": { + "TITLE": { + "CREATE_ORDER": "Создать заказ" + }, + "WIZARD_FORM": { + "BUTTON_NEXT": "Следующий", + "BUTTON_PREV": "Назад", + "BUTTON_DONE": "Закончить заказ", + "STEP1": { + "TITLE": "Выберите вариант", + "SELECT_FROM_EXISTING": "Выбрать из существующих", + "ADD_NEW_CUSTOMER": "Добавить нового клиента" + }, + "STEP2": { + "TITLE": { + "SELECT_CUSTOMER": "Выберите клиента", + "SELECT_ADD": "Выбрать/Добавить", + "ADD_NEW": "Добавить новый" + }, + "SMART_TABLE": { + "TITLES": { + "FULL_NAME": "Полное имя", + "EMAIL": "Почта", + "PHONE": "Телефон", + "ADDRESS": "Адрес" + } + } + }, + "STEP3": { + "TITLE": "Создать заказ" + } + } + }, + "PRODUCTS_TAB": { + "PRODUCTS": "Товары", + "ADD_PRODUCTS": "Добавить товары", + "DELETE": "Удалить", + "IMAGE": "Изображение", + "TITLE": "Заголовок", + "DESCRIPTION": "Описание", + "DETAILS": "Детали", + "CATEGORY": "Категория", + "PRICE": "Цена", + "QUANTITY": "Количество", + "CLICK_ON_PRODUCT_IMAGE_TO_INCREASE_AVAILABLE_QUANTITY": "Нажмите на изображение товара, чтобы увеличить доступное количество", + "AVAILABILITY": "Доступность", + "TYPE": "Тип", + "DELIVERY": "Доставка", + "TAKEAWAY": "На вынос" + }, + "NEW_PRODUCT_TYPE": "Новый продукт типа", + "ADD_PRODUCTS": "Добавить товары", + "CREATE_ORDER": "Создать заказ", + "STATUS": "Статус", + "ORDERS": "Заказы", + "WAREHOUSE": "Склад", + "PRODUCT": "Товар", + "PRODUCTS": "Товары", + "ORDER_NUMBER": "Номер заказа", + "CANCELLED": "Отменено", + "WAREHOUSE_STATUS": "Состояние склада", + "CARRIER_STATUS": "Статус перевозчика", + "PAID": "Оплачено", + "CARRIER": "Перевозчик", + "CREATED": "Создано", + "ELAPSED": "Прошло", + "CONTACT_DETAILS": "Контактная информация", + "EMAIL": "Почта", + "PHONE": "Телефон", + "ORDERS_FORWARDING_DETAILS": "Детали перенаправления заказов", + "ORDERS_FORWARDING_WITH": "Переадресация заказов с", + "MANAGE_STORE": "Управление магазином", + "TOP_PRODUCTS": "Лучшие товары", + "PRODUCTS_MANUFACTURING": "Производство товаров", + "CARRIER_REQUIRED": "Перевозчик требуется", + "MANAGE_WAREHOUSE": "Управление складом", + "MANAGE_STORE_PRODUCTS_&_ORDERS": "Управление товарами и заказами" + }, + "SIMULATION_VIEW": { + "SIMULATION": "Симуляция", + "PURCHASE_PRODUCTS": "Покупка товаров", + "CREATE_INVITE_REQUEST": "Создать запрос на приглашение", + "SEND": "Отправить", + "CREATE_USER": "Создать пользователя", + "ORDER_CONFIRM": "Подтвердить заказ", + "ORDER_CANCEL": "Отмена ордера", + "PRODUCTS": "Товары", + "STORE": "Магазин", + "ORDER": "Заказ", + "INVITE_REQUEST": "Запрос приглашения", + "INVITE_USER": "Пригласить пользователя", + "TAB_BUTTONS": { + "PRODUCTS": "Товары", + "ORDER_HISTORY": "История заказов" + }, + "USER_MUTATION": { + "TITLE": "Создать клиента", + "NAME": "Наименование", + "EMAIL": "Почта", + "COUNTRY": "Страна", + "CITY": "Город", + "USA": "США", + "ISRAEL": "Israel", + "BULGARIA": "Болгария", + "ADDRESS": "Адрес", + "POSTCODE": "Почтовый индекс", + "COORDINATES": "Координаты", + "AUTO_DETECT_COORDINATES": "Автоопределение координат", + "ERRORS": { + "FIRST_NAME_IS_REQUIRED": "Требуется имя", + "LAST_NAME_IS_REQUIRED": "Требуется фамилия", + "INVALID_EMAIL": "Invalid Email", + "EMAIL_IS_REQUIRED": "Требуется адрес электронной почты", + "COORDINATES_ARE_REQUIRED": "Необходимо указать координаты" + }, + "WIZARD_TITLES": { + "ADDITIONAL_INFO": "Дополнительная информация", + "LOCATION": "Местоположение" + } + }, + "SMART_TABLE": { + "TITLE": "Заголовок", + "ID": "Id", + "IMAGE": "Изображение" + }, + "ORDER_INFO": { + "STATUSES": [ + { + "TITLE": "Мы готовим заказ!", + "DETAILS": "Вы получите это через %t минут.", + "NOT_PAID_NOTE": "Подготовьте ваш кошелек (%s наличными)." + }, + { + "TITLE": "Перевозчик на пути!", + "DETAILS": "Вы получите заказ через %t мин.", + "NOT_PAID_NOTE": "Подготовьте ваш кошелек (%s наличными)." + }, + { + "TITLE": "Проверьте дверь!", + "DETAILS": "Вы получите заказ в секундах.", + "NOT_PAID_NOTE": "Подготовьте ваш кошелек (%s наличными)." + }, + { + "TITLE": "Заказ выполнен!", + "DETAILS": "Спасибо за использование Ever", + "NOT_PAID_NOTE": "" + } + ], + "DELIVERY_STATUS": { + "WE": "Мы", + "CARRIER": "Перевозчик", + "YOU": "Вы" + }, + "DELIVERY_WRONG": { + "DELIVERY_WRONG": "Доставка не удалась!", + "PROCESSING_WRONG": "Неправильная обработка!", + "TRY_AGAIN": "Пожалуйста, попробуйте еще раз.", + "CALL_FOR_DETAILS": "Звонок для подробностей" + } + }, + "INSTRUCTIONS_STEPS": { + "INSTRUTIONS": "Инструкции", + "CREATE_USER_STEP": { + "CREATE_USER": "Создать пользователя", + "STEP_1": "Этап 1.", + "ORDER": "Заказ", + "TO_CONTINUE_IS_REQUIRED_TO_REGISTER_IN_THE_SYSTEM": "Для продолжения необходимо зарегистрироваться в системе", + "CLICK_ON_BUTTON_CREATE_USER": "Нажмите на кнопку «Создать пользователя»", + "FILL_THE_FORM_FOR_ADDITIONAL_INFO": "Заполните форму для дополнительной информации (опционально)", + "FILL_THE_FORM_FOR_LOCATION_AND_PRESS_DONE_BUTTON": "Заполните форму для определения местоположения и нажмите кнопку \"Готово\"" + }, + "ORDER_STEP": { + "ORDER": "Заказ", + "STEP_2": "Этап 2.", + "CREATE_ORDER": "Создать заказ", + "CHOICE_SOME_PRODUCTS_FROM_THE_TABLE": "Выбор некоторых продуктов из таблицы (подробнее о том, когда нажимаете на его имя или изображение)", + "SELECT_PRODUCT": "Чтобы быть выбран один продукт должен нажать на его ряд.", + "SELECT_BUTTON_ORDER_TO_CREATE_ORDER": "Нажмите кнопку «Заказ» для создания заказа с выбранным товаром.", + "REVIEW_ORDER_HISTORY": "История заказа:", + "ON_PRESS_ORDER_HISTORY_TAB": "При нажатии \"История заказов\" отображается все ваши заказы..", + "HERE_YOU_CAN_SEE_DETAILS_ABOUT_EACH_ORDER": "Здесь вы можете увидеть детали о каждом заказе", + "PRESS_ON_CARRIER_ORDER_OR_PRODUCT_NAME": "Нажмите на перевозчика, заказ или название товара для получения дополнительной информации." + }, + "CONFIRM_OR_CANCEL_STEP": { + "STEP_3": "Шаг3", + "CONFIRM_CANCEL_ORDER": "Подтвердить/подтвердить заказ", + "REAL_TIME": "Реальное время", + "TRACK_STATUS_ON_YOUR_ORDER": "Отслеживать статус вашего заказа.", + "ELAPSED_TIME_FROM_CREATE_TO_DELIVERED": "Прошло время, созданное для доставки.", + "SHOWS_MERCHANT_LOCATION": "Показывает местоположение продавца.", + "SHOWS_CARRIER_LOCATION": "Показать местоположение оператора.", + "POSSIBILITIES": "Возможности:", + "SLIDER_REVIEW_OF_THE_ALL_PRODUCTS": "Ползунок обзора всех продуктов.", + "CAN_CANCEL_IT_WITH_ORDER_CANCEL_BUTTON": "До тех пор пока заказ не доставлен, вы отмените его с помощью кнопки 'Отмена заказа'.", + "AFTER_THE_ORDER_IS_DELIVER_USER_CAN_CLICK_BUTTON_ORDER_CONFIRM_TO_CONTINUE": "После доставки заказа пользователь может нажать кнопку «Подтвердить заказ» для продолжения." + }, + "INVITE_STEP": { + "TO_CONTINUE_ITs_REQUIRED_TO_BE_INVITED_IN_THE_SYSTEM": "Для продолжения необходимо пригласить в систему:", + "SEND_INVITE_REQUEST": "Отправить \"Пригласить запрос\" в систему из формы, которая будет открыта после нажатия кнопки \"Запрос приглашения\".", + "ALL_INVITE_REQUESTS_ARE_REVIEWED": "Все приглашения рассматриваются администратором и они могут быть приглашены, если система доступна рядом с вашим местоположением (для теста вы можете немедленно получить кнопку Пригласить пользователя).", + "AFTER_YOU_GET_INVITED_BEFORE": "После получения приглашения вы можете легко войти в систему, просто введите ваш пригласительный код, который будет предоставлен системой (вы увидите здесь", + "AFTER_YOU_GET_INVITED_AFTER": "код после нажатия кнопки Пригласить и вводить его при нажатии кнопки Создать пользователя)." + } + } + }, + "CUSTOMERS_VIEW": { + "TITLE": "Клиенты", + "DELETE_CUSTOMERS": "Удалить", + "CREATE_CUSTOMER": "Создать", + "DELETE": "УДАЛИТЬ", + "CREATE": "СОЗДАТЬ", + "BAN": "БАН", + "UNBAN": "НЕБАН", + "MANAGE_CUSTOMER": "Управление клиентом", + "CUSTOMER": "Покупатель", + "CUSTOMERS_DEVICES": "Устройства", + "INVITE": "Пригласить", + "NOT_INVITED_ONLY": "Не приглашены только", + "ORDER": "Заказ", + "ORDERS_STATISTICS": "Статистика заказов", + "NUMBER_OF_ORDERS": "Количество заказов", + "CANCELED_ORDERS": "Отмененные заказы", + "COMPLETED_ORDERS_TOTAL": "Всего выполненных заказов", + "Order": "Заказ", + "CANCEL_ORDER": "Отменить заказ", + "CATEGORY": "Категория", + "ORDERS_HISTORY": "История заказов", + "AVAILABLE_PRODUCTS": "Доступные товары", + "NEARBY_STORES": "Магазин поблизости", + "INVITES_REQUESTS_MANAGEMENT": "Предлагает управлению запросами", + "INVITES_MANAGEMENT": "Управление приглашениями", + "DESCRIPTION": "Описание", + "DETAILS": "Детали", + "MAKE_A_CUSTOM_ORDER": "Сделать свой заказ", + "PRODUCT_COUNT": "Количество товаров", + "QUANTITY_CAN'T_BE_EMPTY": "Количество не может быть пустым", + "QUANTITY_CAN'T_BE_0": "Количество не может быть 0", + "NOT_ENOUGH_PRODUCTS_AVAILABLE": "Недостаточно товаров", + "ORDER_INFO": "Информация о заказе", + "ORDER_ID": "ID заказа", + "STORE_ID": "Id магазина", + "CARRIER_ID": "Идентификатор перевозчика", + "NO_CARRIER": "Нет перевозчика", + "SMART_TABLE_COLUMNS": { + "IMAGE": "Изображение", + "NAME": "Наименование", + "EMAIL": "Почта", + "PHONE": "Телефон", + "ADDRESS": "Адрес", + "ORDERS_QTY": "QTY заказов", + "COUNTRY": "Страна", + "CITY": "Город", + "STREET_ADDRESS": "Адрес улицы", + "HOUSE": "Дом", + "APARTMENT": "Квартира", + "INVITE_CODE": "Код приглашения", + "INVITED_DATE": "Дата приглашения", + "ORDER_NUMBER": "Номер заказа", + "WAREHOUSE": "Склад", + "CARRIER": "Перевозчик", + "PRODUCT_LIST": "Список товаров", + "STATS": "Статистика", + "DELIVERY_TIME": "Время доставки", + "CREATED": "Создано", + "ACTIONS": "Действия", + "PAID": "Оплачено", + "COMPLETED": "Выполнено", + "CANCELLED": "Отменено", + "NOT_DELIVERED": "Не доставлено", + "PRODUCT": "Товар", + "PRICE": "Цена", + "STORE": "Магазин", + "AVAILABLE_COUNT": "Доступное количество", + "ORDER": "Заказ", + "STATUS": "Статус" + }, + "EDIT": { + "EDIT_CUSTOMER": "Изменить клиента", + "BASIC_INFO": "Основная информация", + "SAVE": "Сохранить" + }, + "DEVICE": { + "ALL_DEVICE": "Все устройства", + "DEVICE_ID": "ID устройства", + "ID": "id", + "UPDATE": "Обновить", + "LANGUAGE": "язык", + "TYPE": "тип", + "TYPEU": "Тип", + "UUID": "uuid", + "DEVICE_UUID": "UUID устройства", + "UPDATE_DEVICE": "Обновить устройство", + "CUSTOMERS_DEVICES": "Устройства покупателей", + "DELETE": "УДАЛИТЬ", + "CREATE": "СОЗДАТЬ" + }, + "WAREHOUSE": { + "WAREHOUSE_INFO": "Информация о складе", + "WAREHOUSE_ID": "Идентификатор склада", + "WAREHOUSE_NAME": "Название склада" + }, + "CUSTOMER_VIEW": { + "MANAGE_CUSTOMER": "Управление клиентом", + "EDIT": "Редактирование", + "CUSTOMER": "Покупатель" + }, + "INVITES_VIEW": { + "DELETE": "Удалить", + "INVITE": "Пригласить" + } + }, + "CARRIERS_VIEW": { + "TITLE": "Перевозчики", + "DELETE_CARRIERS": "Удалить выбранные", + "CREATE_CARRIER": "Создать перевозчика", + "DELETE": "УДАЛИТЬ", + "CREATE_BUTTON": "СОЗДАТЬ", + "ACTIVE_AND_AVAILABLE_ORDERS": "Активные и доступные заказы", + "ORDERS_HISTORY": "История заказов", + "TRACK": "Трек", + "SMART_TABLE_COLUMNS": { + "IMAGE": "Изображение", + "NAME": "Наименование", + "PHONE": "Телефон", + "STATUS": "Статус", + "ADDRESS": "Адрес", + "DELIVERIES": "Доставки" + }, + "CARRIER_PAGE": { + "WAREHOUSE": "Склад", + "CUSTOMER": "Покупатель", + "SAVE": "Сохранить", + "EDIT": "Редактирование", + "CARRIER_INFO": "Информация о перевозчике", + "CARRIER_ID": "Идентификатор перевозчика", + "REGISTER_NEW_CARRIER": "Регистрация новых перевозчиков", + "WAREHOUSE_STATUS": "Состояние склада", + "CARRIER_STATUS": "Статус перевозчика", + "CREATED": "Создано", + "ARRIVED_TO_CUSTOMER": "Прибытие к клиенту", + "FAILED": "Неудачный", + "DELIVERED": "Доставлено", + "CLIENT_REFUSE_ORDER": "Заказ об отказе клиенту", + "AVAIBLE_ORDER_TO_PICK_UP": "Доступные заказы на покупку (каждый оператор может забрать несколько заказов)", + "ACTIVE": "Активный", + "CARRIER_CAN_BE_SHARED": "Перевозчику можно поделиться?", + "NOT_ACTIVE": "Не активен", + "WORKING": "Работает", + "NOT_WORKING": "Не работает", + "SELECT_CARRIER": "Выберите оператора", + "CARRIER_ORDERS_STATUS": "Статус заказов перевозчика", + "Start": "Начать", + "PICKED_UP_ORDER": "Заказ Выбран", + "CANCEL": "Отмена", + "Arrived To Client": "Прибытие клиенту", + "No Carrier": "Нет перевозчика", + "Order Selected For Delivery": "Выбранный заказ для доставки", + "Order Picked Up": "Заказ подобран", + "Order In Delivery": "Заказ в доставке", + "Delivered": "Доставлено", + "Delivery Issues": "Вопросы доставки", + "Client Refuse to Take Order": "Клиент отказывается принимать заказ", + "BAD_STATUS": "BAD_STATUS", + "Created": "Создано", + "Confirmed": "Подтверждено", + "Processing": "Обработка", + "Allocation Started": "Выделение начато", + "Allocation Finished": "Выделение завершено", + "Packaging Started": "Упаковка началась", + "Packaged": "Упакован", + "Given to Carrier": "Дано перевозчику", + "Allocation Failed": "Не удалось выделить", + "Packaging Failed": "Сбой упаковки", + "LOCATION": "Местоположение", + "TIME": "Время", + "NAME": "Наименование" + }, + "EDIT": { + "EDIT_CARRIER": "Изменить оператора", + "BASIC_INFO": "Основная информация", + "LOCATION": "Местоположение", + "PHOTO_URL": "Ссылка на фото", + "CONTACT_PHONE": "Контактный телефон", + "FIRST_NAME": "First Name", + "LAST_NAME": "Фамилия" + }, + "CREATE": { + "BASIC_INFO": "Основная информация", + "LOCATION": "Местоположение" + }, + "TRACK_PAGE": { + "TRACK_ALL_WORKING_CARRIERS": "Отслеживать все рабочие перевозчики", + "FILTER_CARRIERS": "Фильтр носителей", + "PHONE": "Телефон", + "EMAIL": "Почта", + "ADDRESS": "Адрес", + "DELIVERY_COUNT": "Кол-во доставки" + } + }, + "PRODUCT_TYPE_VIEW": { + "TITLE": "Новый товар", + "WIZARD_FORM": { + "VALIDATION_MESSAGES": { + "TITLE": "Требуется название", + "THE_LENGHT_OF_THE_TITLE": "Длина заголовка должна быть не более 255 символов!", + "IMAGE": "Изображение обязательно", + "DESCRIPTION": "Описание обязательно", + "LANGUAGE": "Требуется язык", + "THE_LENGHT_OF_THE_DESCRIPTION": "Длина описания должна быть не более 255 символов!", + "PRICE": "Требуется цена", + "COUNT": "Необходимо указать количество товаров" + } + } + }, + "SHARED": { + "FORMS": { + "ERRORS": { + "FIRST_NAME_REQUIRED": "Требуется имя", + "LAST_NAME_REQUIRED": "Требуется фамилия", + "USERNAME_REQUIRED": "Требуется имя пользователя", + "PASSWORD_REQUIRED": "Требуется пароль", + "PHONE_REQUIRED": "Необходимо указать номер телефона", + "LOGO_URL_REQUIRED": "Введите правильный URL изображения или выберите файл", + "IS_ACTIVE": "Обязательное поле является обязательным", + "COUNTRY_REQUIRED": "Необходимо указать страну", + "CITY_REQUIRED": "Необходимо указать город", + "STREET_ADDRESS_REQUIRED": "Необходимо указать адрес", + "HOUSE_REQUIRED": "Требуется указать номер дома", + "COORDINATES_REQUIRED": "Необходимо указать координаты", + "MUST_CONTAIN_ONLY_LETTERS": "Должно содержать только буквы", + "PHONE_MUST_CONTAINS_ONLY(specail_signs)AND_DIGIT_CHARACTER": "Номер телефона может начинаться с букв '+' или '(некоторые цифры)' и должен содержать только символы: '-, ., (пробел), #'' и цифры" + } + }, + "USER": { + "FORMS": { + "BASIC_INFO": { + "TITLE": "Основная информация", + "FIRST_NAME": "First Name", + "FIRST_NAME_OPTIONAL": "Имя (необязательно)", + "LAST_NAME_OPTIONAL": "Фамилия (необязательно)", + "PHOTO_URL": "Ссылка на фото", + "PICTURE_URL": "URL изображения (необязательно)", + "EMAIL": "Почта", + "EMAIL_OPTIONAL": "Email (необязательно)", + "ERRORS": { + "INVALID_EMAIL": "Invalid Email", + "EMAIL_IS_ALREADY_IN_USE": "Адрес электронной почты уже используется" + } + } + } + }, + "WAREHOUSE": { + "ORDER_MODAL": { + "MAKE_ORDER": "Сделать заказ", + "ONLY_AVAILABLE": "Показать только доступные товары", + "ORDER": "Заказ", + "SMART_TABLE": { + "TITLES": { + "IMG": "Img", + "PRODUCT": "Товар", + "PRICE": "Цена", + "AVAILABLE": "Доступно", + "AMOUNT": "Сумма", + "COMMENT": "Комментарий" + } + } + } + } + }, + "MENU_VIEW": { + "DASHBOARD": "Панель", + "STORES": "Магазин", + "PRODUCTS": { + "PRODUCTS": "Товары", + "MANAGEMENT": "Управление", + "CATEGORIES": "Категории" + }, + "CUSTOMERS": { + "CUSTOMERS": "Клиенты", + "MANAGEMENT": "Управление", + "INVITES": "Приглашения" + }, + "CARRIERS": "Перевозчики", + "SIMULATION": "Симуляция", + "SETUP": "Настройка" + }, + "CATEGORY_VIEW": { + "TITLE": "Заголовок", + "IMAGE": "Изображение", + "CREATE_BUTTON": "СОЗДАТЬ", + "DELETE": "УДАЛИТЬ", + "EDIT": { + "EDIT_CATEGORY": "Изменить категорию", + "CATEGORY_NAME": "Название категории", + "ENTER_THE_CATEGORY_NAME": "Введите название категории", + "DONE": "Готово" + }, + "CREATE": { + "CREATE_CATEGORY": "Создать категорию", + "CATEGORY_NAME": "Название категории", + "ENTER_THE_CATEGORY_NAME": "Введите название категории", + "PHOTO": "Фото", + "BROWSE": "Обзор", + "INVALID_URL": "Неверный URL", + "REMOVE_IMAGE": "Remove image", + "PHOTO_OPTIONAL": "Фото (необязательно)", + "DONE": "Готово" + } + }, + "PLACEHOLDER": { + "EXAMPLE": "Пример: Пицца Домино", + "IMAGE_URL": "Ссылка на изображение", + "HERE_GOES_A_SHORT_DESCRIPTION": "Краткое описание", + "HERE_GOES_A_DETAILS_ABOUT_PRODUCT_(OPTION)": "Здесь идет подробная информация о продукте (опцион)", + "REMOVE_IMAGE": "Remove image", + "PASSWORD": "Пароль", + "LATITUDE": "Широта", + "LONGITUDE": "Долгота", + "APARTMENT": "Квартира", + "HOUSE": "Дом", + "STREET": "Улица", + "ZIP": "ZIP", + "CITY": "Город", + "FIND_ADDRESS": "Найти адрес" + }, + "STATUS_TEXT": { + "Created": "Создано", + "Confirmed": "Подтверждено", + "Processing": "Обработка", + "Allocation Started": "Выделение начато", + "Allocation Finished": "Выделение завершено", + "Packaging Started": "Упаковка началась", + "Packaged": "Упакован", + "Given to Carrier": "Дано перевозчику", + "Allocation Failed": "Не удалось выделить", + "Packaging Failed": "Сбой упаковки", + "No Carrier": "Нет перевозчика", + "Order Selected For Delivery": "Выбранный заказ для доставки", + "Order Picked Up": "Заказ подобран", + "Order In Delivery": "Заказ в доставке", + "Arrived To Client": "Прибытие клиенту", + "Delivered": "Доставлено", + "Delivery Issues": "Вопросы доставки", + "Client Refuse to Take Order": "Клиент отказывается принимать заказ", + "Given to Customer": "Выдано клиенту", + "BAD_STATUS": "BAD_STATUS" + }, + "ELAPSED_TIME": { + "TITLE": "Прошло времени" + }, + "CONFIRM_MODAL": { + "ARE_YOU_SURE": "Вы уверены?", + "ARE_YOU_SURE_YOU_WANT_TO_INCREASE": "Вы уверены, что хотите увеличить количество товаров?", + "ARE_YOU_SURE_YOU_WANT_TO_DECREASE": "Вы уверены, что хотите уменьшить количество товаров?", + "YES": "Да", + "NO": "Нет" + }, + "NO_SERVER_VIEW": { + "NO_SERVER": "Соединение с сервером потеряно" + }, + "BUTTON_NEXT": "Следующий", + "BUTTON_PREV": "Назад", + "BUTTON_DONE": "Готово", + "TERRAIN": "Terrain", + "SATELLITE": "Спутник", + "LOCATION": "Местоположение", + "ROAD_MAP": "Карта дорог", + "Manage warehouse": "Управление товарами и заказами", + "Warehouse": "Склад", + "Create Warehouse": "Создать склад", + "SIMULATION": "Симуляция", + "Purchase products": "Покупка товаров", + "Manage": "Управлять", + "Orders": "Заказы", + "Confirmed": "Подтверждено", + "In Delivery": "В доставке", + "Not Confirmed": "Не подтверждено", + "Not paid": "Не оплачено", + "Cancelled": "Отменено", + "All": "Все", + "CANCEL": "Отмена", + "Default Settings": "Настройки по умолчанию", + "Products Manufacturing": "Производство товаров", + "Carrier required before sale": "Перевозчик требуется перед продажей", + "New Product Type": "Новый тип товара", + "Products": "Товары", + "Product": "Товар", + "Title": "Заголовок", + "Picture Url": "Ссылка на изображение", + "Description": "Описание", + "Details": "Детали", + "Price": "Цена", + "CATEGORY": "Категория", + "LANGUAGE": "Язык", + "BROWSE": "Обзор", + "ENGLISH": "Russian", + "HEBREW": "Иврит", + "RUSSIAN": "Русский", + "BULGARIAN": "Болгарский", + "SPANISH": "Испанский", + "FRENCH": "Французский", + "SELECT": "Выбрать", + "Name": "Наименование", + "Id": "Id", + "Warehouse name is required": "Требуется название склада", + "Name must be at least 1 characters long": "Имя должно быть не менее 1 символов", + "Title cannot be more than 255 characters long": "Название не может быть длиннее 255 символов", + "Logo": "Логотип", + "Warehouse logo is required": "Требуется логотип склада", + "is Active": "активен", + "right now": "прямо сейчас", + "Unselected": "Не выбрано", + "Phone": "Телефон", + "Email": "Почта", + "Username": "Имя пользователя", + "Username is required": "Требуется имя пользователя", + "Password": "Пароль", + "Country": "Страна", + "USA": "США", + "Israel": "Israel", + "Bulgaria": "Болгария", + "City": "Город", + "Address": "Адрес", + "Postcode": "Почтовый индекс", + "Coordinates": "Координаты", + "Auto detect coordinates": "Автоопределение координат", + "Carriers": "Перевозчики", + "Carrier": "Перевозчик", + "Use only specific carriers": "Использовать только определенные перевозчики", + "Manage carrier and deliveries": "Управление перевозчиком и доставкой", + "Register New Carrier": "Регистрация новых перевозчиков", + "Create User": "Создать пользователя", + "OPTIONAL": "опционально" +} diff --git a/packages/admin-web-angular/src/assets/images/ever-logo.svg b/packages/admin-web-angular/src/assets/images/ever-logo.svg new file mode 100644 index 0000000..53335b0 --- /dev/null +++ b/packages/admin-web-angular/src/assets/images/ever-logo.svg @@ -0,0 +1,11 @@ + + + ever logo + Created with Sketch. + + + + + + + \ No newline at end of file diff --git a/packages/admin-web-angular/src/assets/images/google-map-customer-marker.png b/packages/admin-web-angular/src/assets/images/google-map-customer-marker.png new file mode 100644 index 0000000..7a07b62 Binary files /dev/null and b/packages/admin-web-angular/src/assets/images/google-map-customer-marker.png differ diff --git a/packages/admin-web-angular/src/assets/images/square_pattern.svg b/packages/admin-web-angular/src/assets/images/square_pattern.svg new file mode 100644 index 0000000..dda0f42 --- /dev/null +++ b/packages/admin-web-angular/src/assets/images/square_pattern.svg @@ -0,0 +1 @@ +Asset 2_svg \ No newline at end of file diff --git a/packages/admin-web-angular/src/assets/images/square_pattern_cosmic.svg b/packages/admin-web-angular/src/assets/images/square_pattern_cosmic.svg new file mode 100644 index 0000000..fa11141 --- /dev/null +++ b/packages/admin-web-angular/src/assets/images/square_pattern_cosmic.svg @@ -0,0 +1 @@ +Asset 2_svg diff --git a/packages/admin-web-angular/src/assets/lib/hljs/CHANGES.md b/packages/admin-web-angular/src/assets/lib/hljs/CHANGES.md new file mode 100644 index 0000000..dba891c --- /dev/null +++ b/packages/admin-web-angular/src/assets/lib/hljs/CHANGES.md @@ -0,0 +1,1579 @@ +## Version 9.12.0 + +New language: + +- _MikroTik_ RouterOS Scripting language by [Ivan Dementev][]. + +New style: + +- _VisualStudio 2015 Dark_ by [Nicolas LLOBERA][] + +Improvements: + +- _Crystal_ updated with new keywords and syntaxes by [Tsuyusato Kitsune][]. +- _Julia_ updated to the modern definitions by [Alex Arslan][]. +- _julia-repl_ added by [Morten Piibeleht][]. +- [Stanislav Belov][] wrote a new definition for _1C_, replacing the one that + has not been updated for more than 8 years. The new version supports syntax + for versions 7.7 and 8. +- [Nicolas LLOBERA][] improved C# definition fixing edge cases with function + titles detection and added highlighting of `[Attributes]`. +- [nnnik][] provided a few correctness fixes for _Autohotkey_. +- [Martin Clausen][] made annotation collections in _Clojure_ to look + consistently with other kinds. +- [Alejandro Alonso][] updated _Swift_ keywords. + +[tsuyusato kitsune]: https://github.com/MakeNowJust +[alex arslan]: https://github.com/ararslan +[morten piibeleht]: https://github.com/mortenpi +[stanislav belov]: https://github.com/4ppl +[ivan dementev]: https://github.com/DiVAN1x +[nicolas llobera]: https://github.com/Nicolas01 +[nnnik]: https://github.com/nnnik +[martin clausen]: https://github.com/maacl +[alejandro alonso]: https://github.com/Azoy + +## Version 9.11.0 + +New languages: + +- _Shell_ by [Tsuyusato Kitsune][] +- _jboss-cli_ by [Raphaël Parrëe][] + +Improvements: + +- [Joël Porquet] has [greatly improved the definition of _makefile_][5b3e0e6]. +- _C++_ class titles are now highlighted as in other languages with classes. +- [Jordi Petit][] added rarely used `or`, `and` and `not` keywords to _C++_. +- [Pieter Vantorre][] fixed highlighting of negative floating point values. + +[tsuyusato kitsune]: https://github.com/MakeNowJust +[jordi petit]: https://github.com/jordi-petit +[raphaël parrëe]: https://github.com/rparree +[pieter vantorre]: https://github.com/NuclearCookie +[5b3e0e6]: https://github.com/isagalaev/highlight.js/commit/5b3e0e68bfaae282faff6697d6a490567fa9d44b + +## Version 9.10.0 + +Apologies for missing the previous release cycle. Some thing just can't be +automated… Anyway, we're back! + +New languages: + +- _Hy_ by [Sergey Sobko][] +- _Leaf_ by [Hale Chan][] +- _N1QL_ by [Andres Täht][] and [Rene Saarsoo][] + +Improvements: + +- _Rust_ got updated with new keywords by [Kasper Andersen][] and then + significantly modernized even more by [Eduard-Mihai Burtescu][] (yes, @eddyb, + Rust core team member!) +- _Python_ updated with f-literals by [Philipp A][]. +- _YAML_ updated with unquoted strings support. +- _Gauss_ updated with new keywords by [Matt Evans][]. +- _Lua_ updated with new keywords by [Joe Blow][]. +- _Kotlin_ updated with new keywords by [Philipp Hauer][]. +- _TypeScript_ got highlighting of function params and updated keywords by + [Ike Ku][]. +- _Scheme_ now correctly handles \`-quoted lists thanks to [Guannan Wei]. +- [Sam Wu][] fixed handling of `<<` in _C++_ defines. + +[philipp a]: https://github.com/flying-sheep +[philipp hauer]: https://github.com/phauer +[sergey sobko]: https://github.com/profitware +[hale chan]: https://github.com/halechan +[matt evans]: https://github.com/matthewevans +[joe blow]: https://github.com/mossarelli +[kasper andersen]: https://github.com/kasma1990 +[eduard-mihai burtescu]: https://github.com/eddyb +[andres täht]: https://github.com/andrestaht +[rene saarsoo]: https://github.com/nene +[philipp hauer]: https://github.com/phauer +[ike ku]: https://github.com/dempfi +[guannan wei]: https://github.com/Kraks +[sam wu]: https://github.com/samsam2310 + +## Version 9.9.0 + +New languages + +- _LLVM_ by [Michael Rodler][] + +Improvements: + +- _TypeScript_ updated with annotations and param lists inside constructors, by + [Raphael Parree][]. +- _CoffeeScript_ updated with new keywords and fixed to recognize JavaScript + in \`\`\`, thanks to thanks to [Geoffrey Booth][]. +- Compiler directives in _Delphi_ are now correctly highlighted as "meta". + +[raphael parree]: https://github.com/rparree +[michael rodler]: https://github.com/f0rki +[geoffrey booth]: https://github.com/GeoffreyBooth + +## Version 9.8.0 "New York" + +This version is the second one that deserved a name. Because I'm in New York, +and the release isn't missing the deadline only because it's still Tuesday on +West Coast. + +New languages: + +- _Clean_ by [Camil Staps][] +- _Flix_ by [Magnus Madsen][] + +Improvements: + +- [Kenton Hamaluik][] did a comprehensive update for _Haxe_. +- New commands for _PowerShell_ from [Nicolas Le Gall][]. +- [Jan T. Sott][] updated _NSIS_. +- _Java_ and _Swift_ support unicode characters in identifiers thanks to + [Alexander Lichter][]. + +[camil staps]: https://github.com/camilstaps +[magnus madsen]: https://github.com/magnus-madsen +[kenton hamaluik]: https://github.com/FuzzyWuzzie +[nicolas le gall]: https://github.com/darkitty +[jan t. sott]: https://github.com/idleberg +[alexander lichter]: https://github.com/manniL + +## Version 9.7.0 + +A comprehensive bugfix release. This is one of the best things about +highlight.js: even boring things keep getting better (even if slow). + +- VHDL updated with PSL keywords and uses more consistent styling. +- Nested C-style comments no longer break highlighting in many languages. +- JavaScript updated with `=>` functions, highlighted object attributes and + parsing within template string substitution blocks (`${...}`). +- Fixed another corner case with self-closing `` in JSX. +- Added `HEALTHCHECK` directive in Docker. +- Delphi updated with new Free Pascal keywords. +- Fixed digit separator parsing in C++. +- C# updated with new keywords and fixed to allow multiple identifiers within + generics `<...>`. +- Fixed another slow regex in Less. + +## Version 9.6.0 + +New languages: + +- _ABNF_ and _EBNF_ by [Alex McKibben][] +- _Awk_ by [Matthew Daly][] +- _SubUnit_ by [Sergey Bronnikov][] + +New styles: + +- _Atom One_ in both Dark and Light variants by [Daniel Gamage][] + +Plus, a few smaller updates for _Lasso_, _Elixir_, _C++_ and _SQL_. + +[alex mckibben]: https://github.com/mckibbenta +[daniel gamage]: https://github.com/danielgamage +[matthew daly]: https://github.com/matthewbdaly +[sergey bronnikov]: https://github.com/ligurio + +## Version 9.5.0 + +New languages: + +- _Excel_ by [Victor Zhou][] +- _Linden Scripting Language_ by [Builder's Brewery][] +- _TAP_ (Test Anything Protocol) by [Sergey Bronnikov][] +- _Pony_ by [Joe Eli McIlvain][] +- _Coq_ by [Stephan Boyer][] +- _dsconfig_ and _LDIF_ by [Jacob Childress][] + +New styles: + +- _Ocean Dark_ by [Gavin Siu][] + +Notable changes: + +- [Minh Nguyễn][] added more built-ins to Objective C. +- [Jeremy Hull][] fixed corner cases in C++ preprocessor directives and Diff + comments. +- [Victor Zhou][] added support for digit separators in C++ numbers. + +[gavin siu]: https://github.com/gavsiu +[builder's brewery]: https://github.com/buildersbrewery +[victor zhou]: https://github.com/OiCMudkips +[sergey bronnikov]: https://github.com/ligurio +[joe eli mcilvain]: https://github.com/jemc +[stephan boyer]: https://github.com/boyers +[jacob childress]: https://github.com/braveulysses +[minh nguyễn]: https://github.com/1ec5 +[jeremy hull]: https://github.com/sourrust + +## Version 9.4.0 + +New languages: + +- _PureBASIC_ by [Tristano Ajmone][] +- _BNF_ by [Oleg Efimov][] +- _Ada_ by [Lars Schulna][] + +New styles: + +- _PureBASIC_ by [Tristano Ajmone][] + +Improvements to existing languages and styles: + +- We now highlight function declarations in Go. +- [Taisuke Fujimoto][] contributed very convoluted rules for raw and + interpolated strings in C#. +- [Boone Severson][] updated Verilog to comply with IEEE 1800-2012 + SystemVerilog. +- [Victor Zhou][] improved rules for comments and strings in PowerShell files. +- [Janis Voigtländer][] updated the definition of Elm to version 0.17 of the + languages. Elm is now featured on the front page of . +- Special variable `$this` is highlighted as a keyword in PHP. +- `usize` and `isize` are now highlighted in Rust. +- Fixed labels and directives in x86 assembler. + +[tristano ajmone]: https://github.com/tajmone +[taisuke fujimoto]: https://github.com/temp-impl +[oleg efimov]: https://github.com/Sannis +[boone severson]: https://github.com/BooneJS +[victor zhou]: https://github.com/OiCMudkips +[lars schulna]: https://github.com/captain-hanuta +[janis voigtländer]: https://github.com/jvoigtlaender + +## Version 9.3.0 + +New languages: + +- _Tagger Script_ by [Philipp Wolfer][] +- _MoonScript_ by [Billy Quith][] + +New styles: + +- _xt256_ by [Herbert Shin][] + +Improvements to existing languages and styles: + +- More robust handling of unquoted HTML tag attributes +- Relevance tuning for QML which was unnecessary eager at seizing other + languages' code +- Improve GAMS language parsing +- Fixed a bunch of bugs around selectors in Less +- Kotlin's got a new definition for annotations, updated keywords and other + minor improvements +- Added `move` to Rust keywords +- Markdown now recognizes \`\`\`-fenced code blocks +- Improved detection of function declarations in C++ and C# + +[philipp wolfer]: https://github.com/phw +[billy quith]: https://github.com/billyquith +[herbert shin]: https://github.com/initbar + +## Version 9.2.0 + +New languages: + +- _QML_ by [John Foster][] +- _HTMLBars_ by [Michael Johnston][] +- _CSP_ by [Taras][] +- _Maxima_ by [Robert Dodier][] + +New styles: + +- _Gruvbox_ by [Qeole][] +- _Dracula_ by [Denis Ciccale][] + +Improvements to existing languages and styles: + +- We now correctly handle JSX with arbitrary node tree depth. +- Argument list for `(lambda)` in Scheme is no longer highlighted as a function + call. +- Stylus syntax doesn't break on valid CSS. +- More correct handling of comments and strings and other improvements for + VimScript. +- More subtle work on the default style. +- We now use anonymous modules for AMD. +- `macro_rules!` is now recognized as a built-in in Rust. + +[john foster]: https://github.com/jf990 +[qeole]: https://github.com/Qeole +[denis ciccale]: https://github.com/dciccale +[michael johnston]: https://github.com/lastobelus +[taras]: https://github.com/oxdef +[robert dodier]: https://github.com/robert-dodier + +## Version 9.1.0 + +New languages: + +- _Stan_ by [Brendan Rocks][] +- _BASIC_ by [Raphaël Assénat][] +- _GAUSS_ by [Matt Evans][] +- _DTS_ by [Martin Braun][] +- _Arduino_ by [Stefania Mellai][] + +New Styles: + +- _Arduino Light_ by [Stefania Mellai][] + +Improvements to existing languages and styles: + +- Handle return type annotations in Python +- Allow shebang headers in Javascript +- Support strings in Rust meta +- Recognize `struct` as a class-level definition in Rust +- Recognize b-prefixed chars and strings in Rust +- Better numbers handling in Verilog + +[brendan rocks]: http://brendanrocks.com +[raphaël assénat]: https://github.com/raphnet +[matt evans]: https://github.com/matthewevans +[martin braun]: https://github.com/mbr0wn +[stefania mellai]: https://github.com/smellai + +## Version 9.0.0 + +The new major version brings a reworked styling system. Highlight.js now defines +a limited set of highlightable classes giving a consistent result across all the +styles and languages. You can read a more detailed explanation and background in +the [tracking issue][#348] that started this long process back in May. + +This change is backwards incompatible for those who uses highlight.js with a +custom stylesheet. The [new style guide][sg] explains how to write styles +in this new world. + +Bundled themes have also suffered a significant amount of improvements and may +look different in places, but all the things now consistent and make more sense. +Among others, the Default style has got a refresh and will probably be tweaked +some more in next releases. Please do give your feedback in our +[issue tracker][issues]. + +New languages in this release: + +- _Caché Object Script_ by [Nikita Savchenko][] +- _YAML_ by [Stefan Wienert][] +- _MIPS Assembler_ by [Nebuleon Fumika][] +- _HSP_ by [prince][] + +Improvements to existing languages and styles: + +- ECMAScript 6 modules import now do not require closing semicolon. +- ECMAScript 6 classes constructors now highlighted. +- Template string support for Typescript, as for ECMAScript 6. +- Scala case classes params highlight fixed. +- Built-in names introduced in Julia v0.4 added by [Kenta Sato][]. +- Refreshed Default style. + +Other notable changes: + +- [Web workers support][webworkers] added bu [Jan Kühle][]. +- We now have tests for compressed browser builds as well. +- The building tool chain has been switched to node.js 4.x. and is now + shamelessly uses ES6 features all over the place, courtesy of [Jeremy Hull][]. +- License added to non-compressed browser build. + +[jan kühle]: https://github.com/frigus02 +[stefan wienert]: https://github.com/zealot128 +[kenta sato]: https://github.com/bicycle1885 +[nikita savchenko]: https://github.com/ZitRos +[webworkers]: https://github.com/isagalaev/highlight.js#web-workers +[jeremy hull]: https://github.com/sourrust +[#348]: https://github.com/isagalaev/highlight.js/issues/348 +[sg]: http://highlightjs.readthedocs.org/en/latest/style-guide.html +[issues]: https://github.com/isagalaev/highlight.js/issues +[nebuleon fumika]: https://github.com/Nebuleon +[prince]: https://github.com/prince-0203 + +## Version 8.9.1 + +Some last-minute changes reverted due to strange bug with minified browser build: + +- Scala case classes params highlight fixed +- ECMAScript 6 modules import now do not require closing semicolon +- ECMAScript 6 classes constructors now highlighted +- Template string support for Typescript, as for ECMAScript 6 +- License added to not minified browser build + +## Version 8.9.0 + +New languages: + +- _crmsh_ by [Kristoffer Gronlund][] +- _SQF_ by [Soren Enevoldsen][] + +[kristoffer gronlund]: https://github.com/krig +[soren enevoldsen]: https://github.com/senevoldsen90 + +Notable fixes and improvements to existing languages: + +- Added `abstract` and `namespace` keywords to TypeScript by [Daniel Rosenwasser][] +- Added `label` support to Dockerfile by [Ladislav Prskavec][] +- Crystal highlighting improved by [Tsuyusato Kitsune][] +- Missing Swift keywords added by [Nate Cook][] +- Improve detection of C block comments +- ~~Scala case classes params highlight fixed~~ +- ~~ECMAScript 6 modules import now do not require closing semicolon~~ +- ~~ECMAScript 6 classes constructors now highlighted~~ +- ~~Template string support for Typescript, as for ECMAScript 6~~ + +Other notable changes: + +- ~~License added to not minified browser build~~ + +[kristoffer gronlund]: https://github.com/krig +[søren enevoldsen]: https://github.com/senevoldsen90 +[daniel rosenwasser]: https://github.com/DanielRosenwasser +[ladislav prskavec]: https://github.com/abtris +[tsuyusato kitsune]: https://github.com/MakeNowJust +[nate cook]: https://github.com/natecook1000 + +## Version 8.8.0 + +New languages: + +- _Golo_ by [Philippe Charrière][] +- _GAMS_ by [Stefan Bechert][] +- _IRPF90_ by [Anthony Scemama][] +- _Access logs_ by [Oleg Efimov][] +- _Crystal_ by [Tsuyusato Kitsune][] + +Notable fixes and improvements to existing languages: + +- JavaScript highlighting no longer fails with ES6 default parameters +- Added keywords `async` and `await` to Python +- PHP heredoc support improved +- Allow preprocessor directives within C++ functions + +Other notable changes: + +- Change versions to X.Y.Z SemVer-compatible format +- Added ability to build all targets at once + +[philippe charrière]: https://github.com/k33g +[stefan bechert]: https://github.com/b-pos465 +[anthony scemama]: https://github.com/scemama +[oleg efimov]: https://github.com/Sannis +[tsuyusato kitsune]: https://github.com/MakeNowJust + +## Version 8.7 + +New languages: + +- _Zephir_ by [Oleg Efimov][] +- _Elm_ by [Janis Voigtländer][] +- _XQuery_ by [Dirk Kirsten][] +- _Mojolicious_ by [Dotan Dimet][] +- _AutoIt_ by Manh Tuan from [J2TeaM][] +- _Toml_ (ini extension) by [Guillaume Gomez][] + +New styles: + +- _Hopscotch_ by [Jan T. Sott][] +- _Grayscale_ by [MY Sun][] + +Notable fixes and improvements to existing languages: + +- Fix encoding of images when copied over in certain builds +- Fix incorrect highlighting of the word "bug" in comments +- Treat decorators different from matrix multiplication in Python +- Fix traits inheritance highlighting in Rust +- Fix incorrect document +- Oracle keywords added to SQL language definition by [Vadimtro][] +- Postgres keywords added to SQL language definition by [Benjamin Auder][] +- Fix registers in x86asm being highlighted as a hex number +- Fix highlighting for numbers with a leading decimal point +- Correctly highlight numbers and strings inside of C/C++ macros +- C/C++ functions now support pointer, reference, and move returns + +[oleg efimov]: https://github.com/Sannis +[guillaume gomez]: https://github.com/GuillaumeGomez +[janis voigtländer]: https://github.com/jvoigtlaender +[jan t. sott]: https://github.com/idleberg +[dirk kirsten]: https://github.com/dirkk +[my sun]: https://github.com/simonmysun +[vadimtro]: https://github.com/Vadimtro +[benjamin auder]: https://github.com/ghost +[dotan dimet]: https://github.com/dotandimet +[j2team]: https://github.com/J2TeaM + +## Version 8.6 + +New languages: + +- _C/AL_ by [Kenneth Fuglsang][] +- _DNS zone file_ by [Tim Schumacher][] +- _Ceylon_ by [Lucas Werkmeister][] +- _OpenSCAD_ by [Dan Panzarella][] +- _Inform7_ by [Bruno Dias][] +- _armasm_ by [Dan Panzarella][] +- _TP_ by [Jay Strybis][] + +New styles: + +- _Atelier Cave_, _Atelier Estuary_, + _Atelier Plateau_ and _Atelier Savanna_ by [Bram de Haan][] +- _Github Gist_ by [Louis Barranqueiro][] + +Notable fixes and improvements to existing languages: + +- Multi-line raw strings from C++11 are now supported +- Fix class names with dashes in HAML +- The `async` keyword from ES6/7 is now supported +- TypeScript functions handle type and parameter complexity better +- We unified phpdoc/javadoc/yardoc etc modes across all languages +- CSS .class selectors relevance was dropped to prevent wrong language detection +- Images is now included to CDN build +- Release process is now automated + +[bram de haan]: https://github.com/atelierbram +[kenneth fuglsang]: https://github.com/kfuglsang +[louis barranqueiro]: https://github.com/LouisBarranqueiro +[tim schumacher]: https://github.com/enko +[lucas werkmeister]: https://github.com/lucaswerkmeister +[dan panzarella]: https://github.com/pzl +[bruno dias]: https://github.com/sequitur +[jay strybis]: https://github.com/unreal + +## Version 8.5 + +New languages: + +- _pf.conf_ by [Peter Piwowarski][] +- _Julia_ by [Kenta Sato][] +- _Prolog_ by [Raivo Laanemets][] +- _Docker_ by [Alexis Hénaut][] +- _Fortran_ by [Anthony Scemama][] and [Thomas Applencourt][] +- _Kotlin_ by [Sergey Mashkov][] + +New styles: + +- _Agate_ by [Taufik Nurrohman][] +- _Darcula_ by [JetBrains][] +- _Atelier Sulphurpool_ by [Bram de Haan][] +- _Android Studio_ by [Pedro Oliveira][] + +Notable fixes and improvements to existing languages: + +- ES6 features in JavaScript are better supported now by [Gu Yiling][]. +- Swift now recognizes body-less method definitions. +- Single expression functions `def foo, do: ...` now work in Elixir. +- More uniform detection of built-in classes in Objective C. +- Fixes for number literals and processor directives in Rust. +- HTML ` + ``` + +- `tabReplace` and `useBR` that were used in different places are also unified + into the global options object and are to be set using `configure(options)`. + This function is documented in our [API docs][]. Also note that these + parameters are gone from `highlightBlock` and `fixMarkup` which are now also + rely on `configure`. + +- We removed public-facing (though undocumented) object `hljs.LANGUAGES` which + was used to register languages with the library in favor of two new methods: + `registerLanguage` and `getLanguage`. Both are documented in our [API docs][]. + +- Result returned from `highlight` and `highlightAuto` no longer contains two + separate attributes contributing to relevance score, `relevance` and + `keyword_count`. They are now unified in `relevance`. + +Another technically compatible change that nonetheless might need attention: + +- The structure of the NPM package was refactored, so if you had installed it + locally, you'll have to update your paths. The usual `require('highlight.js')` + works as before. This is contributed by [Dmitry Smolin][]. + +New features: + +- Languages now can be recognized by multiple names like "js" for JavaScript or + "html" for, well, HTML (which earlier insisted on calling it "xml"). These + aliases can be specified in the class attribute of the code container in your + HTML as well as in various API calls. For now there are only a few very common + aliases but we'll expand it in the future. All of them are listed in the + [class reference][cr]. + +- Language detection can now be restricted to a subset of languages relevant in + a given context — a web page or even a single highlighting call. This is + especially useful for node.js build that includes all the known languages. + Another example is a StackOverflow-style site where users specify languages + as tags rather than in the markdown-formatted code snippets. This is + documented in the [API reference][] (see methods `highlightAuto` and + `configure`). + +- Language definition syntax streamlined with [variants][] and + [beginKeywords][]. + +New languages and styles: + +- _Oxygene_ by [Carlo Kok][] +- _Mathematica_ by [Daniel Kvasnička][] +- _Autohotkey_ by [Seongwon Lee][] +- _Atelier_ family of styles in 10 variants by [Bram de Haan][] +- _Paraíso_ styles by [Jan T. Sott][] + +Miscellaneous improvements: + +- Highlighting `=>` prompts in Clojure. +- [Jeremy Hull][] fixed a lot of styles for consistency. +- Finally, highlighting PHP and HTML [mixed in peculiar ways][php-html]. +- Objective C and C# now properly highlight titles in method definition. +- Big overhaul of relevance counting for a number of languages. Please do report + bugs about mis-detection of non-trivial code snippets! + +[api reference]: http://highlightjs.readthedocs.org/en/latest/api.html +[cr]: http://highlightjs.readthedocs.org/en/latest/css-classes-reference.html +[api docs]: http://highlightjs.readthedocs.org/en/latest/api.html +[variants]: https://groups.google.com/d/topic/highlightjs/VoGC9-1p5vk/discussion +[beginkeywords]: https://github.com/isagalaev/highlight.js/commit/6c7fdea002eb3949577a85b3f7930137c7c3038d +[php-html]: https://twitter.com/highlightjs/status/408890903017689088 +[carlo kok]: https://github.com/carlokok +[bram de haan]: https://github.com/atelierbram +[daniel kvasnička]: https://github.com/dkvasnicka +[dmitry smolin]: https://github.com/dimsmol +[jeremy hull]: https://github.com/sourrust +[seongwon lee]: https://github.com/dlimpid +[jan t. sott]: https://github.com/idleberg + +## Version 7.5 + +A catch-up release dealing with some of the accumulated contributions. This one +is probably will be the last before the 8.0 which will be slightly backwards +incompatible regarding some advanced use-cases. + +One outstanding change in this version is the addition of 6 languages to the +[hosted script][d]: Markdown, ObjectiveC, CoffeeScript, Apache, Nginx and +Makefile. It now weighs about 6K more but we're going to keep it under 30K. + +New languages: + +- OCaml by [Mehdi Dogguy][mehdid] and [Nicolas Braud-Santoni][nbraud] +- [LiveCode Server][lcs] by [Ralf Bitter][revig] +- Scilab by [Sylvestre Ledru][sylvestre] +- basic support for Makefile by [Ivan Sagalaev][isagalaev] + +Improvements: + +- Ruby's got support for characters like `?A`, `?1`, `?\012` etc. and `%r{..}` + regexps. +- Clojure now allows a function call in the beginning of s-expressions + `(($filter "myCount") (arr 1 2 3 4 5))`. +- Haskell's got new keywords and now recognizes more things like pragmas, + preprocessors, modules, containers, FFIs etc. Thanks to [Zena Treep][treep] + for the implementation and to [Jeremy Hull][sourrust] for guiding it. +- Miscellaneous fixes in PHP, Brainfuck, SCSS, Asciidoc, CMake, Python and F#. + +[mehdid]: https://github.com/mehdid +[nbraud]: https://github.com/nbraud +[revig]: https://github.com/revig +[lcs]: http://livecode.com/developers/guides/server/ +[sylvestre]: https://github.com/sylvestre +[isagalaev]: https://github.com/isagalaev +[treep]: https://github.com/treep +[sourrust]: https://github.com/sourrust +[d]: http://highlightjs.org/download/ + +## New core developers + +The latest long period of almost complete inactivity in the project coincided +with growing interest to it led to a decision that now seems completely obvious: +we need more core developers. + +So without further ado let me welcome to the core team two long-time +contributors: [Jeremy Hull][] and [Oleg +Efimov][]. + +Hope now we'll be able to work through stuff faster! + +P.S. The historical commit is [here][1] for the record. + +[jeremy hull]: https://github.com/sourrust +[oleg efimov]: https://github.com/sannis +[1]: https://github.com/isagalaev/highlight.js/commit/f3056941bda56d2b72276b97bc0dd5f230f2473f + +## Version 7.4 + +This long overdue version is a snapshot of the current source tree with all the +changes that happened during the past year. Sorry for taking so long! + +Along with the changes in code highlight.js has finally got its new home at +, moving from its cradle on Software Maniacs which it +outgrew a long time ago. Be sure to report any bugs about the site to +. + +On to what's new… + +New languages: + +- Handlebars templates by [Robin Ward][] +- Oracle Rules Language by [Jason Jacobson][] +- F# by [Joans Follesø][] +- AsciiDoc and Haml by [Dan Allen][] +- Lasso by [Eric Knibbe][] +- SCSS by [Kurt Emch][] +- VB.NET by [Poren Chiang][] +- Mizar by [Kelley van Evert][] + +[robin ward]: https://github.com/eviltrout +[jason jacobson]: https://github.com/jayce7 +[joans follesø]: https://github.com/follesoe +[dan allen]: https://github.com/mojavelinux +[eric knibbe]: https://github.com/EricFromCanada +[kurt emch]: https://github.com/kemch +[poren chiang]: https://github.com/rschiang +[kelley van evert]: https://github.com/kelleyvanevert + +New style themes: + +- Monokai Sublime by [noformnocontent][] +- Railscasts by [Damien White][] +- Obsidian by [Alexander Marenin][] +- Docco by [Simon Madine][] +- Mono Blue by [Ivan Sagalaev][] (uses a single color hue for everything) +- Foundation by [Dan Allen][] + +[noformnocontent]: http://nn.mit-license.org/ +[damien white]: https://github.com/visoft +[alexander marenin]: https://github.com/ioncreature +[simon madine]: https://github.com/thingsinjars +[ivan sagalaev]: https://github.com/isagalaev + +Other notable changes: + +- Corrected many corner cases in CSS. +- Dropped Python 2 version of the build tool. +- Implemented building for the AMD format. +- Updated Rust keywords (thanks to [Dmitry Medvinsky][]). +- Literal regexes can now be used in language definitions. +- CoffeeScript highlighting is now significantly more robust and rich due to + input from [Cédric Néhémie][]. + +[dmitry medvinsky]: https://github.com/dmedvinsky +[cédric néhémie]: https://github.com/abe33 + +## Version 7.3 + +- Since this version highlight.js no longer works in IE version 8 and older. + It's made it possible to reduce the library size and dramatically improve code + readability and made it easier to maintain. Time to go forward! + +- New languages: AppleScript (by [Nathan Grigg][ng] and [Dr. Drang][dd]) and + Brainfuck (by [Evgeny Stepanischev][bolk]). + +- Improvements to existing languages: + + - interpreter prompt in Python (`>>>` and `...`) + - @-properties and classes in CoffeeScript + - E4X in JavaScript (by [Oleg Efimov][oe]) + - new keywords in Perl (by [Kirk Kimmel][kk]) + - big Ruby syntax update (by [Vasily Polovnyov][vast]) + - small fixes in Bash + +- Also Oleg Efimov did a great job of moving all the docs for language and style + developers and contributors from the old wiki under the source code in the + "docs" directory. Now these docs are nicely presented at + . + +[ng]: https://github.com/nathan11g +[dd]: https://github.com/drdrang +[bolk]: https://github.com/bolknote +[oe]: https://github.com/Sannis +[kk]: https://github.com/kimmel +[vast]: https://github.com/vast + +## Version 7.2 + +A regular bug-fix release without any significant new features. Enjoy! + +## Version 7.1 + +A Summer crop: + +- [Marc Fornos][mf] made the definition for Clojure along with the matching + style Rainbow (which, of course, works for other languages too). +- CoffeeScript support continues to improve getting support for regular + expressions. +- Yoshihide Jimbo ported to highlight.js [five Tomorrow styles][tm] from the + [project by Chris Kempson][tm0]. +- Thanks to [Casey Duncun][cd] the library can now be built in the popular + [AMD format][amd]. +- And last but not least, we've got a fair number of correctness and consistency + fixes, including a pretty significant refactoring of Ruby. + +[mf]: https://github.com/mfornos +[tm]: http://jmblog.github.com/color-themes-for-highlightjs/ +[tm0]: https://github.com/ChrisKempson/Tomorrow-Theme +[cd]: https://github.com/caseman +[amd]: http://requirejs.org/docs/whyamd.html + +## Version 7.0 + +The reason for the new major version update is a global change of keyword syntax +which resulted in the library getting smaller once again. For example, the +hosted build is 2K less than at the previous version while supporting two new +languages. + +Notable changes: + +- The library now works not only in a browser but also with [node.js][]. It is + installable with `npm install highlight.js`. [API][] docs are available on our + wiki. + +- The new unique feature (apparently) among syntax highlighters is highlighting + _HTTP_ headers and an arbitrary language in the request body. The most useful + languages here are _XML_ and _JSON_ both of which highlight.js does support. + Here's [the detailed post][p] about the feature. + +- Two new style themes: a dark "south" _[Pojoaque][]_ by Jason Tate and an + emulation of*XCode* IDE by [Angel Olloqui][ao]. + +- Three new languages: _D_ by [Aleksandar Ružičić][ar], _R_ by [Joe Cheng][jc] + and _GLSL_ by [Sergey Tikhomirov][st]. + +- _Nginx_ syntax has become a million times smaller and more universal thanks to + remaking it in a more generic manner that doesn't require listing all the + directives in the known universe. + +- Function titles are now highlighted in _PHP_. + +- _Haskell_ and _VHDL_ were significantly reworked to be more rich and correct + by their respective maintainers [Jeremy Hull][sr] and [Igor Kalnitsky][ik]. + +And last but not least, many bugs have been fixed around correctness and +language detection. + +Overall highlight.js currently supports 51 languages and 20 style themes. + +[node.js]: http://nodejs.org/ +[api]: http://softwaremaniacs.org/wiki/doku.php/highlight.js:api +[p]: http://softwaremaniacs.org/blog/2012/05/10/http-and-json-in-highlight-js/en/ +[pojoaque]: http://web-cms-designs.com/ftopict-10-pojoaque-style-for-highlight-js-code-highlighter.html +[ao]: https://github.com/angelolloqui +[ar]: https://github.com/raleksandar +[jc]: https://github.com/jcheng5 +[st]: https://github.com/tikhomirov +[sr]: https://github.com/sourrust +[ik]: https://github.com/ikalnitsky + +## Version 6.2 + +A lot of things happened in highlight.js since the last version! We've got nine +new contributors, the discussion group came alive, and the main branch on GitHub +now counts more than 350 followers. Here are most significant results coming +from all this activity: + +- 5 (five!) new languages: Rust, ActionScript, CoffeeScript, MatLab and + experimental support for markdown. Thanks go to [Andrey Vlasovskikh][av], + [Alexander Myadzel][am], [Dmytrii Nagirniak][dn], [Oleg Efimov][oe], [Denis + Bardadym][db] and [John Crepezzi][jc]. + +- 2 new style themes: Monokai by [Luigi Maselli][lm] and stylistic imitation of + another well-known highlighter Google Code Prettify by [Aahan Krish][ak]. + +- A vast number of [correctness fixes and code refactorings][log], mostly made + by [Oleg Efimov][oe] and [Evgeny Stepanischev][es]. + +[av]: https://github.com/vlasovskikh +[am]: https://github.com/myadzel +[dn]: https://github.com/dnagir +[oe]: https://github.com/Sannis +[db]: https://github.com/btd +[jc]: https://github.com/seejohnrun +[lm]: http://grigio.org/ +[ak]: https://github.com/geekpanth3r +[es]: https://github.com/bolknote +[log]: https://github.com/isagalaev/highlight.js/commits/ + +## Version 6.1 — Solarized + +[Jeremy Hull][jh] has implemented my dream feature — a port of [Solarized][] +style theme famous for being based on the intricate color theory to achieve +correct contrast and color perception. It is now available for highlight.js in +both variants — light and dark. + +This version also adds a new original style Arta. Its author pumbur maintains a +[heavily modified fork of highlight.js][pb] on GitHub. + +[jh]: https://github.com/sourrust +[solarized]: http://ethanschoonover.com/solarized +[pb]: https://github.com/pumbur/highlight.js + +## Version 6.0 + +New major version of the highlighter has been built on a significantly +refactored syntax. Due to this it's even smaller than the previous one while +supporting more languages! + +New languages are: + +- Haskell by [Jeremy Hull][sourrust] +- Erlang in two varieties — module and REPL — made collectively by [Nikolay + Zakharov][desh], [Dmitry Kovega][arhibot] and [Sergey Ignatov][ignatov] +- Objective C by [Valerii Hiora][vhbit] +- Vala by [Antono Vasiljev][antono] +- Go by [Stephan Kountso][steplg] + +[sourrust]: https://github.com/sourrust +[desh]: http://desh.su/ +[arhibot]: https://github.com/arhibot +[ignatov]: https://github.com/ignatov +[vhbit]: https://github.com/vhbit +[antono]: https://github.com/antono +[steplg]: https://github.com/steplg + +Also this version is marginally faster and fixes a number of small long-standing +bugs. + +Developer overview of the new language syntax is available in a [blog post about +recent beta release][beta]. + +[beta]: http://softwaremaniacs.org/blog/2011/04/25/highlight-js-60-beta/en/ + +P.S. New version is not yet available on a Yandex CDN, so for now you have to +download [your own copy][d]. + +[d]: /soft/highlight/en/download/ + +## Version 5.14 + +Fixed bugs in HTML/XML detection and relevance introduced in previous +refactoring. + +Also test.html now shows the second best result of language detection by +relevance. + +## Version 5.13 + +Past weekend began with a couple of simple additions for existing languages but +ended up in a big code refactoring bringing along nice improvements for language +developers. + +### For users + +- Description of C++ has got new keywords from the upcoming [C++ 0x][] standard. +- Description of HTML has got new tags from [HTML 5][]. +- CSS-styles have been unified to use consistent padding and also have lost + pop-outs with names of detected languages. +- [Igor Kalnitsky][ik] has sent two new language descriptions: CMake & VHDL. + +This makes total number of languages supported by highlight.js to reach 35. + +Bug fixes: + +- Custom classes on `
` tags are not being overridden anymore
+-   More correct highlighting of code blocks inside non-`
` containers:
+    highlighter now doesn't insist on replacing them with its own container and
+    just replaces the contents.
+-   Small fixes in browser compatibility and heuristics.
+
+[c++ 0x]: http://ru.wikipedia.org/wiki/C%2B%2B0x
+[html 5]: http://en.wikipedia.org/wiki/HTML5
+[ik]: http://kalnitsky.org.ua/
+
+### For developers
+
+The most significant change is the ability to include language submodes right
+under `contains` instead of defining explicit named submodes in the main array:
+
+    contains: [
+      'string',
+      'number',
+      {begin: '\\n', end: hljs.IMMEDIATE_RE}
+    ]
+
+This is useful for auxiliary modes needed only in one place to define parsing.
+Note that such modes often don't have `className` and hence won't generate a
+separate `` in the resulting markup. This is similar in effect to
+`noMarkup: true`. All existing languages have been refactored accordingly.
+
+Test file test.html has at last become a real test. Now it not only puts the
+detected language name under the code snippet but also tests if it matches the
+expected one. Test summary is displayed right above all language snippets.
+
+## CDN
+
+Fine people at [Yandex][] agreed to host highlight.js on their big fast servers.
+[Link up][l]!
+
+[yandex]: http://yandex.com/
+[l]: http://softwaremaniacs.org/soft/highlight/en/download/
+
+## Version 5.10 — "Paris".
+
+Though I'm on a vacation in Paris, I decided to release a new version with a
+couple of small fixes:
+
+-   Tomas Vitvar discovered that TAB replacement doesn't always work when used
+    with custom markup in code
+-   SQL parsing is even more rigid now and doesn't step over SmallTalk in tests
+
+## Version 5.9
+
+A long-awaited version is finally released.
+
+New languages:
+
+-   Andrew Fedorov made a definition for Lua
+-   a long-time highlight.js contributor [Peter Leonov][pl] made a definition for
+    Nginx config
+-   [Vladimir Moskva][vm] made a definition for TeX
+
+[pl]: http://kung-fu-tzu.ru/
+[vm]: http://fulc.ru/
+
+Fixes for existing languages:
+
+-   [Loren Segal][ls] reworked the Ruby definition and added highlighting for
+    [YARD][] inline documentation
+-   the definition of SQL has become more solid and now it shouldn't be overly
+    greedy when it comes to language detection
+
+[ls]: http://gnuu.org/
+[yard]: http://yardoc.org/
+
+The highlighter has become more usable as a library allowing to do highlighting
+from initialization code of JS frameworks and in ajax methods (see.
+readme.eng.txt).
+
+Also this version drops support for the [WordPress][wp] plugin. Everyone is
+welcome to [pick up its maintenance][p] if needed.
+
+[wp]: http://wordpress.org/
+[p]: http://bazaar.launchpad.net/~isagalaev/+junk/highlight/annotate/342/src/wp_highlight.js.php
+
+## Version 5.8
+
+-   Jan Berkel has contributed a definition for Scala. +1 to hotness!
+-   All CSS-styles are rewritten to work only inside `
` tags to avoid
+    conflicts with host site styles.
+
+## Version 5.7.
+
+Fixed escaping of quotes in VBScript strings.
+
+## Version 5.5
+
+This version brings a small change: now .ini-files allow digits, underscores and
+square brackets in key names.
+
+## Version 5.4
+
+Fixed small but upsetting bug in the packer which caused incorrect highlighting
+of explicitly specified languages. Thanks to Andrew Fedorov for precise
+diagnostics!
+
+## Version 5.3
+
+The version to fulfil old promises.
+
+The most significant change is that highlight.js now preserves custom user
+markup in code along with its own highlighting markup. This means that now it's
+possible to use, say, links in code. Thanks to [Vladimir Dolzhenko][vd] for the
+[initial proposal][1] and for making a proof-of-concept patch.
+
+Also in this version:
+
+-   [Vasily Polovnyov][vp] has sent a GitHub-like style and has implemented
+    support for CSS @-rules and Ruby symbols.
+-   Yura Zaripov has sent two styles: Brown Paper and School Book.
+-   Oleg Volchkov has sent a definition for [Parser 3][p3].
+
+[1]: http://softwaremaniacs.org/forum/highlightjs/6612/
+[p3]: http://www.parser.ru/
+[vp]: http://vasily.polovnyov.ru/
+[vd]: http://dolzhenko.blogspot.com/
+
+## Version 5.2
+
+-   at last it's possible to replace indentation TABs with something sensible
+    (e.g. 2 or 4 spaces)
+-   new keywords and built-ins for 1C by Sergey Baranov
+-   a couple of small fixes to Apache highlighting
+
+## Version 5.1
+
+This is one of those nice version consisting entirely of new and shiny
+contributions!
+
+-   [Vladimir Ermakov][vooon] created highlighting for AVR Assembler
+-   [Ruslan Keba][rukeba] created highlighting for Apache config file. Also his
+    original visual style for it is now available for all highlight.js languages
+    under the name "Magula".
+-   [Shuen-Huei Guan][drake] (aka Drake) sent new keywords for RenderMan
+    languages. Also thanks go to [Konstantin Evdokimenko][ke] for his advice on
+    the matter.
+
+[vooon]: http://vehq.ru/about/
+[rukeba]: http://rukeba.com/
+[drake]: http://drakeguan.org/
+[ke]: http://k-evdokimenko.moikrug.ru/
+
+## Version 5.0
+
+The main change in the new major version of highlight.js is a mechanism for
+packing several languages along with the library itself into a single compressed
+file. Now sites using several languages will load considerably faster because
+the library won't dynamically include additional files while loading.
+
+Also this version fixes a long-standing bug with Javascript highlighting that
+couldn't distinguish between regular expressions and division operations.
+
+And as usually there were a couple of minor correctness fixes.
+
+Great thanks to all contributors! Keep using highlight.js.
+
+## Version 4.3
+
+This version comes with two contributions from [Jason Diamond][jd]:
+
+-   language definition for C# (yes! it was a long-missed thing!)
+-   Visual Studio-like highlighting style
+
+Plus there are a couple of minor bug fixes for parsing HTML and XML attributes.
+
+[jd]: http://jason.diamond.name/weblog/
+
+## Version 4.2
+
+The biggest news is highlighting for Lisp, courtesy of Vasily Polovnyov. It's
+somewhat experimental meaning that for highlighting "keywords" it doesn't use
+any pre-defined set of a Lisp dialect. Instead it tries to highlight first word
+in parentheses wherever it makes sense. I'd like to ask people programming in
+Lisp to confirm if it's a good idea and send feedback to [the forum][f].
+
+Other changes:
+
+-   Smalltalk was excluded from DEFAULT_LANGUAGES to save traffic
+-   [Vladimir Epifanov][voldmar] has implemented javascript style switcher for
+    test.html
+-   comments now allowed inside Ruby function definition
+-   [MEL][] language from [Shuen-Huei Guan][drake]
+-   whitespace now allowed between `
` and ``
+-   better auto-detection of C++ and PHP
+-   HTML allows embedded VBScript (`<% .. %>`)
+
+[f]: http://softwaremaniacs.org/forum/highlightjs/
+[voldmar]: http://voldmar.ya.ru/
+[mel]: http://en.wikipedia.org/wiki/Maya_Embedded_Language
+[drake]: http://drakeguan.org/
+
+## Version 4.1
+
+Languages:
+
+-   Bash from Vah
+-   DOS bat-files from Alexander Makarov (Sam)
+-   Diff files from Vasily Polovnyov
+-   Ini files from myself though initial idea was from Sam
+
+Styles:
+
+-   Zenburn from Vladimir Epifanov, this is an imitation of a
+    [well-known theme for Vim][zenburn].
+-   Ascetic from myself, as a realization of ideals of non-flashy highlighting:
+    just one color in only three gradations :-)
+
+In other news. [One small bug][bug] was fixed, built-in keywords were added for
+Python and C++ which improved auto-detection for the latter (it was shame that
+[my wife's blog][alenacpp] had issues with it from time to time). And lastly
+thanks go to Sam for getting rid of my stylistic comments in code that were
+getting in the way of [JSMin][].
+
+[zenburn]: http://en.wikipedia.org/wiki/Zenburn
+[alenacpp]: http://alenacpp.blogspot.com/
+[bug]: http://softwaremaniacs.org/forum/viewtopic.php?id=1823
+[jsmin]: http://code.google.com/p/jsmin-php/
+
+## Version 4.0
+
+New major version is a result of vast refactoring and of many contributions.
+
+Visible new features:
+
+-   Highlighting of embedded languages. Currently is implemented highlighting of
+    Javascript and CSS inside HTML.
+-   Bundled 5 ready-made style themes!
+
+Invisible new features:
+
+-   Highlight.js no longer pollutes global namespace. Only one object and one
+    function for backward compatibility.
+-   Performance is further increased by about 15%.
+
+Changing of a major version number caused by a new format of language definition
+files. If you use some third-party language files they should be updated.
+
+## Version 3.5
+
+A very nice version in my opinion fixing a number of small bugs and slightly
+increased speed in a couple of corner cases. Thanks to everybody who reports
+bugs in he [forum][f] and by email!
+
+There is also a new language — XML. A custom XML formerly was detected as HTML
+and didn't highlight custom tags. In this version I tried to make custom XML to
+be detected and highlighted by its own rules. Which by the way include such
+things as CDATA sections and processing instructions (``).
+
+[f]: http://softwaremaniacs.org/forum/viewforum.php?id=6
+
+## Version 3.3
+
+[Vladimir Gubarkov][xonix] has provided an interesting and useful addition.
+File export.html contains a little program that shows and allows to copy and
+paste an HTML code generated by the highlighter for any code snippet. This can
+be useful in situations when one can't use the script itself on a site.
+
+[xonix]: http://xonixx.blogspot.com/
+
+## Version 3.2 consists completely of contributions:
+
+-   Vladimir Gubarkov has described SmallTalk
+-   Yuri Ivanov has described 1C
+-   Peter Leonov has packaged the highlighter as a Firefox extension
+-   Vladimir Ermakov has compiled a mod for phpBB
+
+Many thanks to you all!
+
+## Version 3.1
+
+Three new languages are available: Django templates, SQL and Axapta. The latter
+two are sent by [Dmitri Roudakov][1]. However I've almost entirely rewrote an
+SQL definition but I'd never started it be it from the ground up :-)
+
+The engine itself has got a long awaited feature of grouping keywords
+("keyword", "built-in function", "literal"). No more hacks!
+
+[1]: http://roudakov.ru/
+
+## Version 3.0
+
+It is major mainly because now highlight.js has grown large and has become
+modular. Now when you pass it a list of languages to highlight it will
+dynamically load into a browser only those languages.
+
+Also:
+
+-   Konstantin Evdokimenko of [RibKit][] project has created a highlighting for
+    RenderMan Shading Language and RenderMan Interface Bytestream. Yay for more
+    languages!
+-   Heuristics for C++ and HTML got better.
+-   I've implemented (at last) a correct handling of backslash escapes in C-like
+    languages.
+
+There is also a small backwards incompatible change in the new version. The
+function initHighlighting that was used to initialize highlighting instead of
+initHighlightingOnLoad a long time ago no longer works. If you by chance still
+use it — replace it with the new one.
+
+[ribkit]: http://ribkit.sourceforge.net/
+
+## Version 2.9
+
+Highlight.js is a parser, not just a couple of regular expressions. That said
+I'm glad to announce that in the new version 2.9 has support for:
+
+-   in-string substitutions for Ruby -- `#{...}`
+-   strings from from numeric symbol codes (like #XX) for Delphi
+
+## Version 2.8
+
+A maintenance release with more tuned heuristics. Fully backwards compatible.
+
+## Version 2.7
+
+-   Nikita Ledyaev presents highlighting for VBScript, yay!
+-   A couple of bugs with escaping in strings were fixed thanks to Mickle
+-   Ongoing tuning of heuristics
+
+Fixed bugs were rather unpleasant so I encourage everyone to upgrade!
+
+## Version 2.4
+
+-   Peter Leonov provides another improved highlighting for Perl
+-   Javascript gets a new kind of keywords — "literals". These are the words
+    "true", "false" and "null"
+
+Also highlight.js homepage now lists sites that use the library. Feel free to
+add your site by [dropping me a message][mail] until I find the time to build a
+submit form.
+
+[mail]: mailto:Maniac@SoftwareManiacs.Org
+
+## Version 2.3
+
+This version fixes IE breakage in previous version. My apologies to all who have
+already downloaded that one!
+
+## Version 2.2
+
+-   added highlighting for Javascript
+-   at last fixed parsing of Delphi's escaped apostrophes in strings
+-   in Ruby fixed highlighting of keywords 'def' and 'class', same for 'sub' in
+    Perl
+
+## Version 2.0
+
+-   Ruby support by [Anton Kovalyov][ak]
+-   speed increased by orders of magnitude due to new way of parsing
+-   this same way allows now correct highlighting of keywords in some tricky
+    places (like keyword "End" at the end of Delphi classes)
+
+[ak]: http://anton.kovalyov.net/
+
+## Version 1.0
+
+Version 1.0 of javascript syntax highlighter is released!
+
+It's the first version available with English description. Feel free to post
+your comments and question to [highlight.js forum][forum]. And don't be afraid
+if you find there some fancy Cyrillic letters -- it's for Russian users too :-)
+
+[forum]: http://softwaremaniacs.org/forum/viewforum.php?id=6
diff --git a/packages/admin-web-angular/src/assets/lib/hljs/LICENSE b/packages/admin-web-angular/src/assets/lib/hljs/LICENSE
new file mode 100644
index 0000000..422deb7
--- /dev/null
+++ b/packages/admin-web-angular/src/assets/lib/hljs/LICENSE
@@ -0,0 +1,24 @@
+Copyright (c) 2006, Ivan Sagalaev
+All rights reserved.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above copyright
+      notice, this list of conditions and the following disclaimer in the
+      documentation and/or other materials provided with the distribution.
+    * Neither the name of highlight.js nor the names of its contributors 
+      may be used to endorse or promote products derived from this software 
+      without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY
+EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY
+DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/packages/admin-web-angular/src/assets/lib/hljs/README.md b/packages/admin-web-angular/src/assets/lib/hljs/README.md
new file mode 100644
index 0000000..0a03091
--- /dev/null
+++ b/packages/admin-web-angular/src/assets/lib/hljs/README.md
@@ -0,0 +1,151 @@
+# Highlight.js
+
+[![Build Status](https://travis-ci.org/isagalaev/highlight.js.svg?branch=master)](https://travis-ci.org/isagalaev/highlight.js)
+
+Highlight.js is a syntax highlighter written in JavaScript. It works in
+the browser as well as on the server. It works with pretty much any
+markup, doesn’t depend on any framework and has automatic language
+detection.
+
+## Getting Started
+
+The bare minimum for using highlight.js on a web page is linking to the
+library along with one of the styles and calling
+[`initHighlightingOnLoad`][1]:
+
+```html
+
+
+
+```
+
+This will find and highlight code inside of `
` tags; it tries
+to detect the language automatically. If automatic detection doesn’t
+work for you, you can specify the language in the `class` attribute:
+
+```html
+
...
+``` + +The list of supported language classes is available in the [class +reference][2]. Classes can also be prefixed with either `language-` or +`lang-`. + +To disable highlighting altogether use the `nohighlight` class: + +```html +
...
+``` + +## Custom Initialization + +When you need a bit more control over the initialization of +highlight.js, you can use the [`highlightBlock`][3] and [`configure`][4] +functions. This allows you to control _what_ to highlight and _when_. + +Here’s an equivalent way to calling [`initHighlightingOnLoad`][1] using +jQuery: + +```javascript +$(document).ready(function () { + $('pre code').each(function (i, block) { + hljs.highlightBlock(block); + }); +}); +``` + +You can use any tags instead of `
` to mark up your code. If
+you don't use a container that preserve line breaks you will need to
+configure highlight.js to use the `
` tag: + +```javascript +hljs.configure({ useBR: true }); + +$('div.code').each(function (i, block) { + hljs.highlightBlock(block); +}); +``` + +For other options refer to the documentation for [`configure`][4]. + +## Web Workers + +You can run highlighting inside a web worker to avoid freezing the browser +window while dealing with very big chunks of code. + +In your main script: + +```javascript +addEventListener('load', function () { + var code = document.querySelector('#code'); + var worker = new Worker('worker.js'); + worker.onmessage = function (event) { + code.innerHTML = event.data; + }; + worker.postMessage(code.textContent); +}); +``` + +In worker.js: + +```javascript +onmessage = function (event) { + importScripts('/highlight.pack.js'); + var result = self.hljs.highlightAuto(event.data); + postMessage(result.value); +}; +``` + +## Getting the Library + +You can get highlight.js as a hosted, or custom-build, browser script or +as a server module. Right out of the box the browser script supports +both AMD and CommonJS, so if you wish you can use RequireJS or +Browserify without having to build from source. The server module also +works perfectly fine with Browserify, but there is the option to use a +build specific to browsers rather than something meant for a server. +Head over to the [download page][5] for all the options. + +**Don't link to GitHub directly.** The library is not supposed to work straight +from the source, it requires building. If none of the pre-packaged options +work for you refer to the [building documentation][6]. + +**The CDN-hosted package doesn't have all the languages.** Otherwise it'd be +too big. If you don't see the language you need in the ["Common" section][5], +it can be added manually: + +```html + +``` + +**On Almond.** You need to use the optimizer to give the module a name. For +example: + +``` +r.js -o name=hljs paths.hljs=/path/to/highlight out=highlight.js +``` + +## License + +Highlight.js is released under the BSD License. See [LICENSE][7] file +for details. + +## Links + +The official site for the library is at . + +Further in-depth documentation for the API and other topics is at +. + +Authors and contributors are listed in the [AUTHORS.en.txt][8] file. + +[1]: http://highlightjs.readthedocs.io/en/latest/api.html#inithighlightingonload +[2]: http://highlightjs.readthedocs.io/en/latest/css-classes-reference.html +[3]: http://highlightjs.readthedocs.io/en/latest/api.html#highlightblock-block +[4]: http://highlightjs.readthedocs.io/en/latest/api.html#configure-options +[5]: https://highlightjs.org/download/ +[6]: http://highlightjs.readthedocs.io/en/latest/building-testing.html +[7]: https://github.com/isagalaev/highlight.js/blob/master/LICENSE +[8]: https://github.com/isagalaev/highlight.js/blob/master/AUTHORS.en.txt diff --git a/packages/admin-web-angular/src/assets/lib/hljs/README.ru.md b/packages/admin-web-angular/src/assets/lib/hljs/README.ru.md new file mode 100644 index 0000000..27e39e0 --- /dev/null +++ b/packages/admin-web-angular/src/assets/lib/hljs/README.ru.md @@ -0,0 +1,141 @@ +# Highlight.js + +Highlight.js — это инструмент для подсветки синтаксиса, написанный на JavaScript. Он работает +и в браузере, и на сервере. Он работает с практически любой HTML разметкой, не +зависит от каких-либо фреймворков и умеет автоматически определять язык. + +## Начало работы + +Минимум, что нужно сделать для использования highlight.js на веб-странице — это +подключить библиотеку, CSS-стили и вызывать [`initHighlightingOnLoad`][1]: + +```html + + + +``` + +Библиотека найдёт и раскрасит код внутри тегов `
`, попытавшись
+автоматически определить язык. Когда автоопределение не срабатывает, можно явно
+указать язык в атрибуте class:
+
+```html
+
...
+``` + +Список поддерживаемых классов языков доступен в [справочнике по классам][2]. +Класс также можно предварить префиксами `language-` или `lang-`. + +Чтобы отключить подсветку для какого-то блока, используйте класс `nohighlight`: + +```html +
...
+``` + +## Инициализация вручную + +Чтобы иметь чуть больше контроля за инициализацией подсветки, вы можете +использовать функции [`highlightBlock`][3] и [`configure`][4]. Таким образом +можно управлять тем, _что_ и _когда_ подсвечивать. + +Вот пример инициализации, эквивалентной вызову [`initHighlightingOnLoad`][1], но +с использованием jQuery: + +```javascript +$(document).ready(function () { + $('pre code').each(function (i, block) { + hljs.highlightBlock(block); + }); +}); +``` + +Вы можете использовать любые теги разметки вместо `
`. Если
+используете контейнер, не сохраняющий переводы строк, вам нужно сказать
+highlight.js использовать для них тег `
`: + +```javascript +hljs.configure({ useBR: true }); + +$('div.code').each(function (i, block) { + hljs.highlightBlock(block); +}); +``` + +Другие опции можно найти в документации функции [`configure`][4]. + +## Web Workers + +Подсветку можно запустить внутри web worker'а, чтобы окно +браузера не подтормаживало при работе с большими кусками кода. + +В основном скрипте: + +```javascript +addEventListener('load', function () { + var code = document.querySelector('#code'); + var worker = new Worker('worker.js'); + worker.onmessage = function (event) { + code.innerHTML = event.data; + }; + worker.postMessage(code.textContent); +}); +``` + +В worker.js: + +```javascript +onmessage = function (event) { + importScripts('/highlight.pack.js'); + var result = self.hljs.highlightAuto(event.data); + postMessage(result.value); +}; +``` + +## Установка библиотеки + +Highlight.js можно использовать в браузере прямо с CDN хостинга или скачать +индивидуальную сборку, а также установив модуль на сервере. На +[странице загрузки][5] подробно описаны все варианты. + +**Не подключайте GitHub напрямую.** Библиотека не предназначена для +использования в виде исходного кода, а требует отдельной сборки. Если вам не +подходит ни один из готовых вариантов, читайте [документацию по сборке][6]. + +**Файл на CDN содержит не все языки.** Иначе он будет слишком большого размера. +Если нужного вам языка нет в [категории "Common"][5], можно дообавить его +вручную: + +```html + +``` + +**Про Almond.** Нужно задать имя модуля в оптимизаторе, например: + +``` +r.js -o name=hljs paths.hljs=/path/to/highlight out=highlight.js +``` + +## Лицензия + +Highlight.js распространяется под лицензией BSD. Подробнее читайте файл +[LICENSE][7]. + +## Ссылки + +Официальный сайт билиотеки расположен по адресу . + +Более подробная документация по API и другим темам расположена на +. + +Авторы и контрибьюторы перечислены в файле [AUTHORS.ru.txt][8] file. + +[1]: http://highlightjs.readthedocs.io/en/latest/api.html#inithighlightingonload +[2]: http://highlightjs.readthedocs.io/en/latest/css-classes-reference.html +[3]: http://highlightjs.readthedocs.io/en/latest/api.html#highlightblock-block +[4]: http://highlightjs.readthedocs.io/en/latest/api.html#configure-options +[5]: https://highlightjs.org/download/ +[6]: http://highlightjs.readthedocs.io/en/latest/building-testing.html +[7]: https://github.com/isagalaev/highlight.js/blob/master/LICENSE +[8]: https://github.com/isagalaev/highlight.js/blob/master/AUTHORS.ru.txt diff --git a/packages/admin-web-angular/src/assets/lib/hljs/styles/a11y-dark.css b/packages/admin-web-angular/src/assets/lib/hljs/styles/a11y-dark.css new file mode 100644 index 0000000..bcdf118 --- /dev/null +++ b/packages/admin-web-angular/src/assets/lib/hljs/styles/a11y-dark.css @@ -0,0 +1,99 @@ +/* a11y-dark theme */ +/* Based on the Tomorrow Night Eighties theme: https://github.com/isagalaev/highlight.js/blob/master/src/styles/tomorrow-night-eighties.css */ +/* @author: ericwbailey */ + +/* Comment */ +.hljs-comment, +.hljs-quote { + color: #d4d0ab; +} + +/* Red */ +.hljs-variable, +.hljs-template-variable, +.hljs-tag, +.hljs-name, +.hljs-selector-id, +.hljs-selector-class, +.hljs-regexp, +.hljs-deletion { + color: #ffa07a; +} + +/* Orange */ +.hljs-number, +.hljs-built_in, +.hljs-builtin-name, +.hljs-literal, +.hljs-type, +.hljs-params, +.hljs-meta, +.hljs-link { + color: #f5ab35; +} + +/* Yellow */ +.hljs-attribute { + color: #ffd700; +} + +/* Green */ +.hljs-string, +.hljs-symbol, +.hljs-bullet, +.hljs-addition { + color: #abe338; +} + +/* Blue */ +.hljs-title, +.hljs-section { + color: #00e0e0; +} + +/* Purple */ +.hljs-keyword, +.hljs-selector-tag { + color: #dcc6e0; +} + +.hljs { + display: block; + overflow-x: auto; + background: #2b2b2b; + color: #f8f8f2; + padding: 0.5em; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} + +@media screen and (-ms-high-contrast: active) { + .hljs-addition, + .hljs-attribute, + .hljs-built_in, + .hljs-builtin-name, + .hljs-bullet, + .hljs-comment, + .hljs-link, + .hljs-literal, + .hljs-meta, + .hljs-number, + .hljs-params, + .hljs-string, + .hljs-symbol, + .hljs-type, + .hljs-quote { + color: highlight; + } + + .hljs-keyword, + .hljs-selector-tag { + font-weight: bold; + } +} diff --git a/packages/admin-web-angular/src/assets/lib/hljs/styles/a11y-light.css b/packages/admin-web-angular/src/assets/lib/hljs/styles/a11y-light.css new file mode 100644 index 0000000..2dcea34 --- /dev/null +++ b/packages/admin-web-angular/src/assets/lib/hljs/styles/a11y-light.css @@ -0,0 +1,99 @@ +/* a11y-light theme */ +/* Based on the Tomorrow Night Eighties theme: https://github.com/isagalaev/highlight.js/blob/master/src/styles/tomorrow-night-eighties.css */ +/* @author: ericwbailey */ + +/* Comment */ +.hljs-comment, +.hljs-quote { + color: #696969; +} + +/* Red */ +.hljs-variable, +.hljs-template-variable, +.hljs-tag, +.hljs-name, +.hljs-selector-id, +.hljs-selector-class, +.hljs-regexp, +.hljs-deletion { + color: #d91e18; +} + +/* Orange */ +.hljs-number, +.hljs-built_in, +.hljs-builtin-name, +.hljs-literal, +.hljs-type, +.hljs-params, +.hljs-meta, +.hljs-link { + color: #aa5d00; +} + +/* Yellow */ +.hljs-attribute { + color: #aa5d00; +} + +/* Green */ +.hljs-string, +.hljs-symbol, +.hljs-bullet, +.hljs-addition { + color: #008000; +} + +/* Blue */ +.hljs-title, +.hljs-section { + color: #007faa; +} + +/* Purple */ +.hljs-keyword, +.hljs-selector-tag { + color: #7928a1; +} + +.hljs { + display: block; + overflow-x: auto; + background: #fefefe; + color: #545454; + padding: 0.5em; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} + +@media screen and (-ms-high-contrast: active) { + .hljs-addition, + .hljs-attribute, + .hljs-built_in, + .hljs-builtin-name, + .hljs-bullet, + .hljs-comment, + .hljs-link, + .hljs-literal, + .hljs-meta, + .hljs-number, + .hljs-params, + .hljs-string, + .hljs-symbol, + .hljs-type, + .hljs-quote { + color: highlight; + } + + .hljs-keyword, + .hljs-selector-tag { + font-weight: bold; + } +} diff --git a/packages/admin-web-angular/src/assets/lib/hljs/styles/agate.css b/packages/admin-web-angular/src/assets/lib/hljs/styles/agate.css new file mode 100644 index 0000000..c247458 --- /dev/null +++ b/packages/admin-web-angular/src/assets/lib/hljs/styles/agate.css @@ -0,0 +1,108 @@ +/*! + * Agate by Taufik Nurrohman + * ---------------------------------------------------- + * + * #ade5fc + * #a2fca2 + * #c6b4f0 + * #d36363 + * #fcc28c + * #fc9b9b + * #ffa + * #fff + * #333 + * #62c8f3 + * #888 + * + */ + +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + background: #333; + color: white; +} + +.hljs-name, +.hljs-strong { + font-weight: bold; +} + +.hljs-code, +.hljs-emphasis { + font-style: italic; +} + +.hljs-tag { + color: #62c8f3; +} + +.hljs-variable, +.hljs-template-variable, +.hljs-selector-id, +.hljs-selector-class { + color: #ade5fc; +} + +.hljs-string, +.hljs-bullet { + color: #a2fca2; +} + +.hljs-type, +.hljs-title, +.hljs-section, +.hljs-attribute, +.hljs-quote, +.hljs-built_in, +.hljs-builtin-name { + color: #ffa; +} + +.hljs-number, +.hljs-symbol, +.hljs-bullet { + color: #d36363; +} + +.hljs-keyword, +.hljs-selector-tag, +.hljs-literal { + color: #fcc28c; +} + +.hljs-comment, +.hljs-deletion, +.hljs-code { + color: #888; +} + +.hljs-regexp, +.hljs-link { + color: #c6b4f0; +} + +.hljs-meta { + color: #fc9b9b; +} + +.hljs-deletion { + background-color: #fc9b9b; + color: #333; +} + +.hljs-addition { + background-color: #a2fca2; + color: #333; +} + +.hljs a { + color: inherit; +} + +.hljs a:focus, +.hljs a:hover { + color: inherit; + text-decoration: underline; +} diff --git a/packages/admin-web-angular/src/assets/lib/hljs/styles/an-old-hope.css b/packages/admin-web-angular/src/assets/lib/hljs/styles/an-old-hope.css new file mode 100644 index 0000000..3cbe1c7 --- /dev/null +++ b/packages/admin-web-angular/src/assets/lib/hljs/styles/an-old-hope.css @@ -0,0 +1,79 @@ +/* + +An Old Hope – Star Wars Syntax (c) Gustavo Costa +Original theme - Ocean Dark Theme – by https://github.com/gavsiu +Based on Jesse Leite's Atom syntax theme 'An Old Hope' – https://github.com/JesseLeite/an-old-hope-syntax-atom + +*/ + +/* Death Star Comment */ +.hljs-comment, +.hljs-quote { + color: #b6b18b; +} + +/* Darth Vader */ +.hljs-variable, +.hljs-template-variable, +.hljs-tag, +.hljs-name, +.hljs-selector-id, +.hljs-selector-class, +.hljs-regexp, +.hljs-deletion { + color: #eb3c54; +} + +/* Threepio */ +.hljs-number, +.hljs-built_in, +.hljs-builtin-name, +.hljs-literal, +.hljs-type, +.hljs-params, +.hljs-meta, +.hljs-link { + color: #e7ce56; +} + +/* Luke Skywalker */ +.hljs-attribute { + color: #ee7c2b; +} + +/* Obi Wan Kenobi */ +.hljs-string, +.hljs-symbol, +.hljs-bullet, +.hljs-addition { + color: #4fb4d7; +} + +/* Yoda */ +.hljs-title, +.hljs-section { + color: #78bb65; +} + +/* Mace Windu */ +.hljs-keyword, +.hljs-selector-tag { + color: #b45ea4; +} + +/* Millenium Falcon */ +.hljs { + display: block; + overflow-x: auto; + background: #1c1d21; + color: #c0c5ce; + padding: 0.5em; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} diff --git a/packages/admin-web-angular/src/assets/lib/hljs/styles/androidstudio.css b/packages/admin-web-angular/src/assets/lib/hljs/styles/androidstudio.css new file mode 100644 index 0000000..25a05bb --- /dev/null +++ b/packages/admin-web-angular/src/assets/lib/hljs/styles/androidstudio.css @@ -0,0 +1,66 @@ +/* +Date: 24 Fev 2015 +Author: Pedro Oliveira +*/ + +.hljs { + color: #a9b7c6; + background: #282b2e; + display: block; + overflow-x: auto; + padding: 0.5em; +} + +.hljs-number, +.hljs-literal, +.hljs-symbol, +.hljs-bullet { + color: #6897bb; +} + +.hljs-keyword, +.hljs-selector-tag, +.hljs-deletion { + color: #cc7832; +} + +.hljs-variable, +.hljs-template-variable, +.hljs-link { + color: #629755; +} + +.hljs-comment, +.hljs-quote { + color: #808080; +} + +.hljs-meta { + color: #bbb529; +} + +.hljs-string, +.hljs-attribute, +.hljs-addition { + color: #6a8759; +} + +.hljs-section, +.hljs-title, +.hljs-type { + color: #ffc66d; +} + +.hljs-name, +.hljs-selector-id, +.hljs-selector-class { + color: #e8bf6a; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} diff --git a/packages/admin-web-angular/src/assets/lib/hljs/styles/arduino-light.css b/packages/admin-web-angular/src/assets/lib/hljs/styles/arduino-light.css new file mode 100644 index 0000000..f8e24d3 --- /dev/null +++ b/packages/admin-web-angular/src/assets/lib/hljs/styles/arduino-light.css @@ -0,0 +1,88 @@ +/* + +Arduino® Light Theme - Stefania Mellai + +*/ + +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + background: #ffffff; +} + +.hljs, +.hljs-subst { + color: #434f54; +} + +.hljs-keyword, +.hljs-attribute, +.hljs-selector-tag, +.hljs-doctag, +.hljs-name { + color: #00979d; +} + +.hljs-built_in, +.hljs-literal, +.hljs-bullet, +.hljs-code, +.hljs-addition { + color: #d35400; +} + +.hljs-regexp, +.hljs-symbol, +.hljs-variable, +.hljs-template-variable, +.hljs-link, +.hljs-selector-attr, +.hljs-selector-pseudo { + color: #00979d; +} + +.hljs-type, +.hljs-string, +.hljs-selector-id, +.hljs-selector-class, +.hljs-quote, +.hljs-template-tag, +.hljs-deletion { + color: #005c5f; +} + +.hljs-title, +.hljs-section { + color: #880000; + font-weight: bold; +} + +.hljs-comment { + color: rgba(149, 165, 166, 0.8); +} + +.hljs-meta-keyword { + color: #728e00; +} + +.hljs-meta { + color: #728e00; + color: #434f54; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} + +.hljs-function { + color: #728e00; +} + +.hljs-number { + color: #8a7b52; +} diff --git a/packages/admin-web-angular/src/assets/lib/hljs/styles/arta.css b/packages/admin-web-angular/src/assets/lib/hljs/styles/arta.css new file mode 100644 index 0000000..01678ed --- /dev/null +++ b/packages/admin-web-angular/src/assets/lib/hljs/styles/arta.css @@ -0,0 +1,73 @@ +/* +Date: 17.V.2011 +Author: pumbur +*/ + +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + background: #222; +} + +.hljs, +.hljs-subst { + color: #aaa; +} + +.hljs-section { + color: #fff; +} + +.hljs-comment, +.hljs-quote, +.hljs-meta { + color: #444; +} + +.hljs-string, +.hljs-symbol, +.hljs-bullet, +.hljs-regexp { + color: #ffcc33; +} + +.hljs-number, +.hljs-addition { + color: #00cc66; +} + +.hljs-built_in, +.hljs-builtin-name, +.hljs-literal, +.hljs-type, +.hljs-template-variable, +.hljs-attribute, +.hljs-link { + color: #32aaee; +} + +.hljs-keyword, +.hljs-selector-tag, +.hljs-name, +.hljs-selector-id, +.hljs-selector-class { + color: #6644aa; +} + +.hljs-title, +.hljs-variable, +.hljs-deletion, +.hljs-template-tag { + color: #bb1166; +} + +.hljs-section, +.hljs-doctag, +.hljs-strong { + font-weight: bold; +} + +.hljs-emphasis { + font-style: italic; +} diff --git a/packages/admin-web-angular/src/assets/lib/hljs/styles/ascetic.css b/packages/admin-web-angular/src/assets/lib/hljs/styles/ascetic.css new file mode 100644 index 0000000..d8d06b6 --- /dev/null +++ b/packages/admin-web-angular/src/assets/lib/hljs/styles/ascetic.css @@ -0,0 +1,45 @@ +/* + +Original style from softwaremaniacs.org (c) Ivan Sagalaev + +*/ + +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + background: white; + color: black; +} + +.hljs-string, +.hljs-variable, +.hljs-template-variable, +.hljs-symbol, +.hljs-bullet, +.hljs-section, +.hljs-addition, +.hljs-attribute, +.hljs-link { + color: #888; +} + +.hljs-comment, +.hljs-quote, +.hljs-meta, +.hljs-deletion { + color: #ccc; +} + +.hljs-keyword, +.hljs-selector-tag, +.hljs-section, +.hljs-name, +.hljs-type, +.hljs-strong { + font-weight: bold; +} + +.hljs-emphasis { + font-style: italic; +} diff --git a/packages/admin-web-angular/src/assets/lib/hljs/styles/atelier-cave-dark.css b/packages/admin-web-angular/src/assets/lib/hljs/styles/atelier-cave-dark.css new file mode 100644 index 0000000..dbeb8a0 --- /dev/null +++ b/packages/admin-web-angular/src/assets/lib/hljs/styles/atelier-cave-dark.css @@ -0,0 +1,83 @@ +/* Base16 Atelier Cave Dark - Theme */ +/* by Bram de Haan (http://atelierbram.github.io/syntax-highlighting/atelier-schemes/cave) */ +/* Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) */ + +/* Atelier-Cave Comment */ +.hljs-comment, +.hljs-quote { + color: #7e7887; +} + +/* Atelier-Cave Red */ +.hljs-variable, +.hljs-template-variable, +.hljs-attribute, +.hljs-regexp, +.hljs-link, +.hljs-tag, +.hljs-name, +.hljs-selector-id, +.hljs-selector-class { + color: #be4678; +} + +/* Atelier-Cave Orange */ +.hljs-number, +.hljs-meta, +.hljs-built_in, +.hljs-builtin-name, +.hljs-literal, +.hljs-type, +.hljs-params { + color: #aa573c; +} + +/* Atelier-Cave Green */ +.hljs-string, +.hljs-symbol, +.hljs-bullet { + color: #2a9292; +} + +/* Atelier-Cave Blue */ +.hljs-title, +.hljs-section { + color: #576ddb; +} + +/* Atelier-Cave Purple */ +.hljs-keyword, +.hljs-selector-tag { + color: #955ae7; +} + +.hljs-deletion, +.hljs-addition { + color: #19171c; + display: inline-block; + width: 100%; +} + +.hljs-deletion { + background-color: #be4678; +} + +.hljs-addition { + background-color: #2a9292; +} + +.hljs { + display: block; + overflow-x: auto; + background: #19171c; + color: #8b8792; + padding: 0.5em; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} diff --git a/packages/admin-web-angular/src/assets/lib/hljs/styles/atelier-cave-light.css b/packages/admin-web-angular/src/assets/lib/hljs/styles/atelier-cave-light.css new file mode 100644 index 0000000..1f9347e --- /dev/null +++ b/packages/admin-web-angular/src/assets/lib/hljs/styles/atelier-cave-light.css @@ -0,0 +1,85 @@ +/* Base16 Atelier Cave Light - Theme */ +/* by Bram de Haan (http://atelierbram.github.io/syntax-highlighting/atelier-schemes/cave) */ +/* Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) */ + +/* Atelier-Cave Comment */ +.hljs-comment, +.hljs-quote { + color: #655f6d; +} + +/* Atelier-Cave Red */ +.hljs-variable, +.hljs-template-variable, +.hljs-attribute, +.hljs-tag, +.hljs-name, +.hljs-regexp, +.hljs-link, +.hljs-name, +.hljs-name, +.hljs-selector-id, +.hljs-selector-class { + color: #be4678; +} + +/* Atelier-Cave Orange */ +.hljs-number, +.hljs-meta, +.hljs-built_in, +.hljs-builtin-name, +.hljs-literal, +.hljs-type, +.hljs-params { + color: #aa573c; +} + +/* Atelier-Cave Green */ +.hljs-string, +.hljs-symbol, +.hljs-bullet { + color: #2a9292; +} + +/* Atelier-Cave Blue */ +.hljs-title, +.hljs-section { + color: #576ddb; +} + +/* Atelier-Cave Purple */ +.hljs-keyword, +.hljs-selector-tag { + color: #955ae7; +} + +.hljs-deletion, +.hljs-addition { + color: #19171c; + display: inline-block; + width: 100%; +} + +.hljs-deletion { + background-color: #be4678; +} + +.hljs-addition { + background-color: #2a9292; +} + +.hljs { + display: block; + overflow-x: auto; + background: #efecf4; + color: #585260; + padding: 0.5em; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} diff --git a/packages/admin-web-angular/src/assets/lib/hljs/styles/atelier-dune-dark.css b/packages/admin-web-angular/src/assets/lib/hljs/styles/atelier-dune-dark.css new file mode 100644 index 0000000..4d69d9e --- /dev/null +++ b/packages/admin-web-angular/src/assets/lib/hljs/styles/atelier-dune-dark.css @@ -0,0 +1,69 @@ +/* Base16 Atelier Dune Dark - Theme */ +/* by Bram de Haan (http://atelierbram.github.io/syntax-highlighting/atelier-schemes/dune) */ +/* Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) */ + +/* Atelier-Dune Comment */ +.hljs-comment, +.hljs-quote { + color: #999580; +} + +/* Atelier-Dune Red */ +.hljs-variable, +.hljs-template-variable, +.hljs-attribute, +.hljs-tag, +.hljs-name, +.hljs-regexp, +.hljs-link, +.hljs-name, +.hljs-selector-id, +.hljs-selector-class { + color: #d73737; +} + +/* Atelier-Dune Orange */ +.hljs-number, +.hljs-meta, +.hljs-built_in, +.hljs-builtin-name, +.hljs-literal, +.hljs-type, +.hljs-params { + color: #b65611; +} + +/* Atelier-Dune Green */ +.hljs-string, +.hljs-symbol, +.hljs-bullet { + color: #60ac39; +} + +/* Atelier-Dune Blue */ +.hljs-title, +.hljs-section { + color: #6684e1; +} + +/* Atelier-Dune Purple */ +.hljs-keyword, +.hljs-selector-tag { + color: #b854d4; +} + +.hljs { + display: block; + overflow-x: auto; + background: #20201d; + color: #a6a28c; + padding: 0.5em; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} diff --git a/packages/admin-web-angular/src/assets/lib/hljs/styles/atelier-dune-light.css b/packages/admin-web-angular/src/assets/lib/hljs/styles/atelier-dune-light.css new file mode 100644 index 0000000..87721eb --- /dev/null +++ b/packages/admin-web-angular/src/assets/lib/hljs/styles/atelier-dune-light.css @@ -0,0 +1,69 @@ +/* Base16 Atelier Dune Light - Theme */ +/* by Bram de Haan (http://atelierbram.github.io/syntax-highlighting/atelier-schemes/dune) */ +/* Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) */ + +/* Atelier-Dune Comment */ +.hljs-comment, +.hljs-quote { + color: #7d7a68; +} + +/* Atelier-Dune Red */ +.hljs-variable, +.hljs-template-variable, +.hljs-attribute, +.hljs-tag, +.hljs-name, +.hljs-regexp, +.hljs-link, +.hljs-name, +.hljs-selector-id, +.hljs-selector-class { + color: #d73737; +} + +/* Atelier-Dune Orange */ +.hljs-number, +.hljs-meta, +.hljs-built_in, +.hljs-builtin-name, +.hljs-literal, +.hljs-type, +.hljs-params { + color: #b65611; +} + +/* Atelier-Dune Green */ +.hljs-string, +.hljs-symbol, +.hljs-bullet { + color: #60ac39; +} + +/* Atelier-Dune Blue */ +.hljs-title, +.hljs-section { + color: #6684e1; +} + +/* Atelier-Dune Purple */ +.hljs-keyword, +.hljs-selector-tag { + color: #b854d4; +} + +.hljs { + display: block; + overflow-x: auto; + background: #fefbec; + color: #6e6b5e; + padding: 0.5em; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} diff --git a/packages/admin-web-angular/src/assets/lib/hljs/styles/atelier-estuary-dark.css b/packages/admin-web-angular/src/assets/lib/hljs/styles/atelier-estuary-dark.css new file mode 100644 index 0000000..8afeb47 --- /dev/null +++ b/packages/admin-web-angular/src/assets/lib/hljs/styles/atelier-estuary-dark.css @@ -0,0 +1,84 @@ +/* Base16 Atelier Estuary Dark - Theme */ +/* by Bram de Haan (http://atelierbram.github.io/syntax-highlighting/atelier-schemes/estuary) */ +/* Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) */ + +/* Atelier-Estuary Comment */ +.hljs-comment, +.hljs-quote { + color: #878573; +} + +/* Atelier-Estuary Red */ +.hljs-variable, +.hljs-template-variable, +.hljs-attribute, +.hljs-tag, +.hljs-name, +.hljs-regexp, +.hljs-link, +.hljs-name, +.hljs-selector-id, +.hljs-selector-class { + color: #ba6236; +} + +/* Atelier-Estuary Orange */ +.hljs-number, +.hljs-meta, +.hljs-built_in, +.hljs-builtin-name, +.hljs-literal, +.hljs-type, +.hljs-params { + color: #ae7313; +} + +/* Atelier-Estuary Green */ +.hljs-string, +.hljs-symbol, +.hljs-bullet { + color: #7d9726; +} + +/* Atelier-Estuary Blue */ +.hljs-title, +.hljs-section { + color: #36a166; +} + +/* Atelier-Estuary Purple */ +.hljs-keyword, +.hljs-selector-tag { + color: #5f9182; +} + +.hljs-deletion, +.hljs-addition { + color: #22221b; + display: inline-block; + width: 100%; +} + +.hljs-deletion { + background-color: #ba6236; +} + +.hljs-addition { + background-color: #7d9726; +} + +.hljs { + display: block; + overflow-x: auto; + background: #22221b; + color: #929181; + padding: 0.5em; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} diff --git a/packages/admin-web-angular/src/assets/lib/hljs/styles/atelier-estuary-light.css b/packages/admin-web-angular/src/assets/lib/hljs/styles/atelier-estuary-light.css new file mode 100644 index 0000000..e55ece8 --- /dev/null +++ b/packages/admin-web-angular/src/assets/lib/hljs/styles/atelier-estuary-light.css @@ -0,0 +1,84 @@ +/* Base16 Atelier Estuary Light - Theme */ +/* by Bram de Haan (http://atelierbram.github.io/syntax-highlighting/atelier-schemes/estuary) */ +/* Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) */ + +/* Atelier-Estuary Comment */ +.hljs-comment, +.hljs-quote { + color: #6c6b5a; +} + +/* Atelier-Estuary Red */ +.hljs-variable, +.hljs-template-variable, +.hljs-attribute, +.hljs-tag, +.hljs-name, +.hljs-regexp, +.hljs-link, +.hljs-name, +.hljs-selector-id, +.hljs-selector-class { + color: #ba6236; +} + +/* Atelier-Estuary Orange */ +.hljs-number, +.hljs-meta, +.hljs-built_in, +.hljs-builtin-name, +.hljs-literal, +.hljs-type, +.hljs-params { + color: #ae7313; +} + +/* Atelier-Estuary Green */ +.hljs-string, +.hljs-symbol, +.hljs-bullet { + color: #7d9726; +} + +/* Atelier-Estuary Blue */ +.hljs-title, +.hljs-section { + color: #36a166; +} + +/* Atelier-Estuary Purple */ +.hljs-keyword, +.hljs-selector-tag { + color: #5f9182; +} + +.hljs-deletion, +.hljs-addition { + color: #22221b; + display: inline-block; + width: 100%; +} + +.hljs-deletion { + background-color: #ba6236; +} + +.hljs-addition { + background-color: #7d9726; +} + +.hljs { + display: block; + overflow-x: auto; + background: #f4f3ec; + color: #5f5e4e; + padding: 0.5em; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} diff --git a/packages/admin-web-angular/src/assets/lib/hljs/styles/atelier-forest-dark.css b/packages/admin-web-angular/src/assets/lib/hljs/styles/atelier-forest-dark.css new file mode 100644 index 0000000..b9ca06a --- /dev/null +++ b/packages/admin-web-angular/src/assets/lib/hljs/styles/atelier-forest-dark.css @@ -0,0 +1,69 @@ +/* Base16 Atelier Forest Dark - Theme */ +/* by Bram de Haan (http://atelierbram.github.io/syntax-highlighting/atelier-schemes/forest) */ +/* Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) */ + +/* Atelier-Forest Comment */ +.hljs-comment, +.hljs-quote { + color: #9c9491; +} + +/* Atelier-Forest Red */ +.hljs-variable, +.hljs-template-variable, +.hljs-attribute, +.hljs-tag, +.hljs-name, +.hljs-regexp, +.hljs-link, +.hljs-name, +.hljs-selector-id, +.hljs-selector-class { + color: #f22c40; +} + +/* Atelier-Forest Orange */ +.hljs-number, +.hljs-meta, +.hljs-built_in, +.hljs-builtin-name, +.hljs-literal, +.hljs-type, +.hljs-params { + color: #df5320; +} + +/* Atelier-Forest Green */ +.hljs-string, +.hljs-symbol, +.hljs-bullet { + color: #7b9726; +} + +/* Atelier-Forest Blue */ +.hljs-title, +.hljs-section { + color: #407ee7; +} + +/* Atelier-Forest Purple */ +.hljs-keyword, +.hljs-selector-tag { + color: #6666ea; +} + +.hljs { + display: block; + overflow-x: auto; + background: #1b1918; + color: #a8a19f; + padding: 0.5em; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} diff --git a/packages/admin-web-angular/src/assets/lib/hljs/styles/atelier-forest-light.css b/packages/admin-web-angular/src/assets/lib/hljs/styles/atelier-forest-light.css new file mode 100644 index 0000000..1cd9ded --- /dev/null +++ b/packages/admin-web-angular/src/assets/lib/hljs/styles/atelier-forest-light.css @@ -0,0 +1,69 @@ +/* Base16 Atelier Forest Light - Theme */ +/* by Bram de Haan (http://atelierbram.github.io/syntax-highlighting/atelier-schemes/forest) */ +/* Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) */ + +/* Atelier-Forest Comment */ +.hljs-comment, +.hljs-quote { + color: #766e6b; +} + +/* Atelier-Forest Red */ +.hljs-variable, +.hljs-template-variable, +.hljs-attribute, +.hljs-tag, +.hljs-name, +.hljs-regexp, +.hljs-link, +.hljs-name, +.hljs-selector-id, +.hljs-selector-class { + color: #f22c40; +} + +/* Atelier-Forest Orange */ +.hljs-number, +.hljs-meta, +.hljs-built_in, +.hljs-builtin-name, +.hljs-literal, +.hljs-type, +.hljs-params { + color: #df5320; +} + +/* Atelier-Forest Green */ +.hljs-string, +.hljs-symbol, +.hljs-bullet { + color: #7b9726; +} + +/* Atelier-Forest Blue */ +.hljs-title, +.hljs-section { + color: #407ee7; +} + +/* Atelier-Forest Purple */ +.hljs-keyword, +.hljs-selector-tag { + color: #6666ea; +} + +.hljs { + display: block; + overflow-x: auto; + background: #f1efee; + color: #68615e; + padding: 0.5em; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} diff --git a/packages/admin-web-angular/src/assets/lib/hljs/styles/atelier-heath-dark.css b/packages/admin-web-angular/src/assets/lib/hljs/styles/atelier-heath-dark.css new file mode 100644 index 0000000..0636ff9 --- /dev/null +++ b/packages/admin-web-angular/src/assets/lib/hljs/styles/atelier-heath-dark.css @@ -0,0 +1,69 @@ +/* Base16 Atelier Heath Dark - Theme */ +/* by Bram de Haan (http://atelierbram.github.io/syntax-highlighting/atelier-schemes/heath) */ +/* Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) */ + +/* Atelier-Heath Comment */ +.hljs-comment, +.hljs-quote { + color: #9e8f9e; +} + +/* Atelier-Heath Red */ +.hljs-variable, +.hljs-template-variable, +.hljs-attribute, +.hljs-tag, +.hljs-name, +.hljs-regexp, +.hljs-link, +.hljs-name, +.hljs-selector-id, +.hljs-selector-class { + color: #ca402b; +} + +/* Atelier-Heath Orange */ +.hljs-number, +.hljs-meta, +.hljs-built_in, +.hljs-builtin-name, +.hljs-literal, +.hljs-type, +.hljs-params { + color: #a65926; +} + +/* Atelier-Heath Green */ +.hljs-string, +.hljs-symbol, +.hljs-bullet { + color: #918b3b; +} + +/* Atelier-Heath Blue */ +.hljs-title, +.hljs-section { + color: #516aec; +} + +/* Atelier-Heath Purple */ +.hljs-keyword, +.hljs-selector-tag { + color: #7b59c0; +} + +.hljs { + display: block; + overflow-x: auto; + background: #1b181b; + color: #ab9bab; + padding: 0.5em; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} diff --git a/packages/admin-web-angular/src/assets/lib/hljs/styles/atelier-heath-light.css b/packages/admin-web-angular/src/assets/lib/hljs/styles/atelier-heath-light.css new file mode 100644 index 0000000..c58344a --- /dev/null +++ b/packages/admin-web-angular/src/assets/lib/hljs/styles/atelier-heath-light.css @@ -0,0 +1,69 @@ +/* Base16 Atelier Heath Light - Theme */ +/* by Bram de Haan (http://atelierbram.github.io/syntax-highlighting/atelier-schemes/heath) */ +/* Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) */ + +/* Atelier-Heath Comment */ +.hljs-comment, +.hljs-quote { + color: #776977; +} + +/* Atelier-Heath Red */ +.hljs-variable, +.hljs-template-variable, +.hljs-attribute, +.hljs-tag, +.hljs-name, +.hljs-regexp, +.hljs-link, +.hljs-name, +.hljs-selector-id, +.hljs-selector-class { + color: #ca402b; +} + +/* Atelier-Heath Orange */ +.hljs-number, +.hljs-meta, +.hljs-built_in, +.hljs-builtin-name, +.hljs-literal, +.hljs-type, +.hljs-params { + color: #a65926; +} + +/* Atelier-Heath Green */ +.hljs-string, +.hljs-symbol, +.hljs-bullet { + color: #918b3b; +} + +/* Atelier-Heath Blue */ +.hljs-title, +.hljs-section { + color: #516aec; +} + +/* Atelier-Heath Purple */ +.hljs-keyword, +.hljs-selector-tag { + color: #7b59c0; +} + +.hljs { + display: block; + overflow-x: auto; + background: #f7f3f7; + color: #695d69; + padding: 0.5em; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} diff --git a/packages/admin-web-angular/src/assets/lib/hljs/styles/atelier-lakeside-dark.css b/packages/admin-web-angular/src/assets/lib/hljs/styles/atelier-lakeside-dark.css new file mode 100644 index 0000000..8d1d26f --- /dev/null +++ b/packages/admin-web-angular/src/assets/lib/hljs/styles/atelier-lakeside-dark.css @@ -0,0 +1,69 @@ +/* Base16 Atelier Lakeside Dark - Theme */ +/* by Bram de Haan (http://atelierbram.github.io/syntax-highlighting/atelier-schemes/lakeside) */ +/* Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) */ + +/* Atelier-Lakeside Comment */ +.hljs-comment, +.hljs-quote { + color: #7195a8; +} + +/* Atelier-Lakeside Red */ +.hljs-variable, +.hljs-template-variable, +.hljs-attribute, +.hljs-tag, +.hljs-name, +.hljs-regexp, +.hljs-link, +.hljs-name, +.hljs-selector-id, +.hljs-selector-class { + color: #d22d72; +} + +/* Atelier-Lakeside Orange */ +.hljs-number, +.hljs-meta, +.hljs-built_in, +.hljs-builtin-name, +.hljs-literal, +.hljs-type, +.hljs-params { + color: #935c25; +} + +/* Atelier-Lakeside Green */ +.hljs-string, +.hljs-symbol, +.hljs-bullet { + color: #568c3b; +} + +/* Atelier-Lakeside Blue */ +.hljs-title, +.hljs-section { + color: #257fad; +} + +/* Atelier-Lakeside Purple */ +.hljs-keyword, +.hljs-selector-tag { + color: #6b6bb8; +} + +.hljs { + display: block; + overflow-x: auto; + background: #161b1d; + color: #7ea2b4; + padding: 0.5em; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} diff --git a/packages/admin-web-angular/src/assets/lib/hljs/styles/atelier-lakeside-light.css b/packages/admin-web-angular/src/assets/lib/hljs/styles/atelier-lakeside-light.css new file mode 100644 index 0000000..58b0ed8 --- /dev/null +++ b/packages/admin-web-angular/src/assets/lib/hljs/styles/atelier-lakeside-light.css @@ -0,0 +1,69 @@ +/* Base16 Atelier Lakeside Light - Theme */ +/* by Bram de Haan (http://atelierbram.github.io/syntax-highlighting/atelier-schemes/lakeside) */ +/* Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) */ + +/* Atelier-Lakeside Comment */ +.hljs-comment, +.hljs-quote { + color: #5a7b8c; +} + +/* Atelier-Lakeside Red */ +.hljs-variable, +.hljs-template-variable, +.hljs-attribute, +.hljs-tag, +.hljs-name, +.hljs-regexp, +.hljs-link, +.hljs-name, +.hljs-selector-id, +.hljs-selector-class { + color: #d22d72; +} + +/* Atelier-Lakeside Orange */ +.hljs-number, +.hljs-meta, +.hljs-built_in, +.hljs-builtin-name, +.hljs-literal, +.hljs-type, +.hljs-params { + color: #935c25; +} + +/* Atelier-Lakeside Green */ +.hljs-string, +.hljs-symbol, +.hljs-bullet { + color: #568c3b; +} + +/* Atelier-Lakeside Blue */ +.hljs-title, +.hljs-section { + color: #257fad; +} + +/* Atelier-Lakeside Purple */ +.hljs-keyword, +.hljs-selector-tag { + color: #6b6bb8; +} + +.hljs { + display: block; + overflow-x: auto; + background: #ebf8ff; + color: #516d7b; + padding: 0.5em; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} diff --git a/packages/admin-web-angular/src/assets/lib/hljs/styles/atelier-plateau-dark.css b/packages/admin-web-angular/src/assets/lib/hljs/styles/atelier-plateau-dark.css new file mode 100644 index 0000000..3764e67 --- /dev/null +++ b/packages/admin-web-angular/src/assets/lib/hljs/styles/atelier-plateau-dark.css @@ -0,0 +1,84 @@ +/* Base16 Atelier Plateau Dark - Theme */ +/* by Bram de Haan (http://atelierbram.github.io/syntax-highlighting/atelier-schemes/plateau) */ +/* Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) */ + +/* Atelier-Plateau Comment */ +.hljs-comment, +.hljs-quote { + color: #7e7777; +} + +/* Atelier-Plateau Red */ +.hljs-variable, +.hljs-template-variable, +.hljs-attribute, +.hljs-tag, +.hljs-name, +.hljs-regexp, +.hljs-link, +.hljs-name, +.hljs-selector-id, +.hljs-selector-class { + color: #ca4949; +} + +/* Atelier-Plateau Orange */ +.hljs-number, +.hljs-meta, +.hljs-built_in, +.hljs-builtin-name, +.hljs-literal, +.hljs-type, +.hljs-params { + color: #b45a3c; +} + +/* Atelier-Plateau Green */ +.hljs-string, +.hljs-symbol, +.hljs-bullet { + color: #4b8b8b; +} + +/* Atelier-Plateau Blue */ +.hljs-title, +.hljs-section { + color: #7272ca; +} + +/* Atelier-Plateau Purple */ +.hljs-keyword, +.hljs-selector-tag { + color: #8464c4; +} + +.hljs-deletion, +.hljs-addition { + color: #1b1818; + display: inline-block; + width: 100%; +} + +.hljs-deletion { + background-color: #ca4949; +} + +.hljs-addition { + background-color: #4b8b8b; +} + +.hljs { + display: block; + overflow-x: auto; + background: #1b1818; + color: #8a8585; + padding: 0.5em; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} diff --git a/packages/admin-web-angular/src/assets/lib/hljs/styles/atelier-plateau-light.css b/packages/admin-web-angular/src/assets/lib/hljs/styles/atelier-plateau-light.css new file mode 100644 index 0000000..2a4c52a --- /dev/null +++ b/packages/admin-web-angular/src/assets/lib/hljs/styles/atelier-plateau-light.css @@ -0,0 +1,84 @@ +/* Base16 Atelier Plateau Light - Theme */ +/* by Bram de Haan (http://atelierbram.github.io/syntax-highlighting/atelier-schemes/plateau) */ +/* Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) */ + +/* Atelier-Plateau Comment */ +.hljs-comment, +.hljs-quote { + color: #655d5d; +} + +/* Atelier-Plateau Red */ +.hljs-variable, +.hljs-template-variable, +.hljs-attribute, +.hljs-tag, +.hljs-name, +.hljs-regexp, +.hljs-link, +.hljs-name, +.hljs-selector-id, +.hljs-selector-class { + color: #ca4949; +} + +/* Atelier-Plateau Orange */ +.hljs-number, +.hljs-meta, +.hljs-built_in, +.hljs-builtin-name, +.hljs-literal, +.hljs-type, +.hljs-params { + color: #b45a3c; +} + +/* Atelier-Plateau Green */ +.hljs-string, +.hljs-symbol, +.hljs-bullet { + color: #4b8b8b; +} + +/* Atelier-Plateau Blue */ +.hljs-title, +.hljs-section { + color: #7272ca; +} + +/* Atelier-Plateau Purple */ +.hljs-keyword, +.hljs-selector-tag { + color: #8464c4; +} + +.hljs-deletion, +.hljs-addition { + color: #1b1818; + display: inline-block; + width: 100%; +} + +.hljs-deletion { + background-color: #ca4949; +} + +.hljs-addition { + background-color: #4b8b8b; +} + +.hljs { + display: block; + overflow-x: auto; + background: #f4ecec; + color: #585050; + padding: 0.5em; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} diff --git a/packages/admin-web-angular/src/assets/lib/hljs/styles/atelier-savanna-dark.css b/packages/admin-web-angular/src/assets/lib/hljs/styles/atelier-savanna-dark.css new file mode 100644 index 0000000..5d26388 --- /dev/null +++ b/packages/admin-web-angular/src/assets/lib/hljs/styles/atelier-savanna-dark.css @@ -0,0 +1,84 @@ +/* Base16 Atelier Savanna Dark - Theme */ +/* by Bram de Haan (http://atelierbram.github.io/syntax-highlighting/atelier-schemes/savanna) */ +/* Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) */ + +/* Atelier-Savanna Comment */ +.hljs-comment, +.hljs-quote { + color: #78877d; +} + +/* Atelier-Savanna Red */ +.hljs-variable, +.hljs-template-variable, +.hljs-attribute, +.hljs-tag, +.hljs-name, +.hljs-regexp, +.hljs-link, +.hljs-name, +.hljs-selector-id, +.hljs-selector-class { + color: #b16139; +} + +/* Atelier-Savanna Orange */ +.hljs-number, +.hljs-meta, +.hljs-built_in, +.hljs-builtin-name, +.hljs-literal, +.hljs-type, +.hljs-params { + color: #9f713c; +} + +/* Atelier-Savanna Green */ +.hljs-string, +.hljs-symbol, +.hljs-bullet { + color: #489963; +} + +/* Atelier-Savanna Blue */ +.hljs-title, +.hljs-section { + color: #478c90; +} + +/* Atelier-Savanna Purple */ +.hljs-keyword, +.hljs-selector-tag { + color: #55859b; +} + +.hljs-deletion, +.hljs-addition { + color: #171c19; + display: inline-block; + width: 100%; +} + +.hljs-deletion { + background-color: #b16139; +} + +.hljs-addition { + background-color: #489963; +} + +.hljs { + display: block; + overflow-x: auto; + background: #171c19; + color: #87928a; + padding: 0.5em; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} diff --git a/packages/admin-web-angular/src/assets/lib/hljs/styles/atelier-savanna-light.css b/packages/admin-web-angular/src/assets/lib/hljs/styles/atelier-savanna-light.css new file mode 100644 index 0000000..8b41d00 --- /dev/null +++ b/packages/admin-web-angular/src/assets/lib/hljs/styles/atelier-savanna-light.css @@ -0,0 +1,84 @@ +/* Base16 Atelier Savanna Light - Theme */ +/* by Bram de Haan (http://atelierbram.github.io/syntax-highlighting/atelier-schemes/savanna) */ +/* Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) */ + +/* Atelier-Savanna Comment */ +.hljs-comment, +.hljs-quote { + color: #5f6d64; +} + +/* Atelier-Savanna Red */ +.hljs-variable, +.hljs-template-variable, +.hljs-attribute, +.hljs-tag, +.hljs-name, +.hljs-regexp, +.hljs-link, +.hljs-name, +.hljs-selector-id, +.hljs-selector-class { + color: #b16139; +} + +/* Atelier-Savanna Orange */ +.hljs-number, +.hljs-meta, +.hljs-built_in, +.hljs-builtin-name, +.hljs-literal, +.hljs-type, +.hljs-params { + color: #9f713c; +} + +/* Atelier-Savanna Green */ +.hljs-string, +.hljs-symbol, +.hljs-bullet { + color: #489963; +} + +/* Atelier-Savanna Blue */ +.hljs-title, +.hljs-section { + color: #478c90; +} + +/* Atelier-Savanna Purple */ +.hljs-keyword, +.hljs-selector-tag { + color: #55859b; +} + +.hljs-deletion, +.hljs-addition { + color: #171c19; + display: inline-block; + width: 100%; +} + +.hljs-deletion { + background-color: #b16139; +} + +.hljs-addition { + background-color: #489963; +} + +.hljs { + display: block; + overflow-x: auto; + background: #ecf4ee; + color: #526057; + padding: 0.5em; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} diff --git a/packages/admin-web-angular/src/assets/lib/hljs/styles/atelier-seaside-dark.css b/packages/admin-web-angular/src/assets/lib/hljs/styles/atelier-seaside-dark.css new file mode 100644 index 0000000..073a853 --- /dev/null +++ b/packages/admin-web-angular/src/assets/lib/hljs/styles/atelier-seaside-dark.css @@ -0,0 +1,69 @@ +/* Base16 Atelier Seaside Dark - Theme */ +/* by Bram de Haan (http://atelierbram.github.io/syntax-highlighting/atelier-schemes/seaside) */ +/* Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) */ + +/* Atelier-Seaside Comment */ +.hljs-comment, +.hljs-quote { + color: #809980; +} + +/* Atelier-Seaside Red */ +.hljs-variable, +.hljs-template-variable, +.hljs-attribute, +.hljs-tag, +.hljs-name, +.hljs-regexp, +.hljs-link, +.hljs-name, +.hljs-selector-id, +.hljs-selector-class { + color: #e6193c; +} + +/* Atelier-Seaside Orange */ +.hljs-number, +.hljs-meta, +.hljs-built_in, +.hljs-builtin-name, +.hljs-literal, +.hljs-type, +.hljs-params { + color: #87711d; +} + +/* Atelier-Seaside Green */ +.hljs-string, +.hljs-symbol, +.hljs-bullet { + color: #29a329; +} + +/* Atelier-Seaside Blue */ +.hljs-title, +.hljs-section { + color: #3d62f5; +} + +/* Atelier-Seaside Purple */ +.hljs-keyword, +.hljs-selector-tag { + color: #ad2bee; +} + +.hljs { + display: block; + overflow-x: auto; + background: #131513; + color: #8ca68c; + padding: 0.5em; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} diff --git a/packages/admin-web-angular/src/assets/lib/hljs/styles/atelier-seaside-light.css b/packages/admin-web-angular/src/assets/lib/hljs/styles/atelier-seaside-light.css new file mode 100644 index 0000000..872dbe1 --- /dev/null +++ b/packages/admin-web-angular/src/assets/lib/hljs/styles/atelier-seaside-light.css @@ -0,0 +1,69 @@ +/* Base16 Atelier Seaside Light - Theme */ +/* by Bram de Haan (http://atelierbram.github.io/syntax-highlighting/atelier-schemes/seaside) */ +/* Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) */ + +/* Atelier-Seaside Comment */ +.hljs-comment, +.hljs-quote { + color: #687d68; +} + +/* Atelier-Seaside Red */ +.hljs-variable, +.hljs-template-variable, +.hljs-attribute, +.hljs-tag, +.hljs-name, +.hljs-regexp, +.hljs-link, +.hljs-name, +.hljs-selector-id, +.hljs-selector-class { + color: #e6193c; +} + +/* Atelier-Seaside Orange */ +.hljs-number, +.hljs-meta, +.hljs-built_in, +.hljs-builtin-name, +.hljs-literal, +.hljs-type, +.hljs-params { + color: #87711d; +} + +/* Atelier-Seaside Green */ +.hljs-string, +.hljs-symbol, +.hljs-bullet { + color: #29a329; +} + +/* Atelier-Seaside Blue */ +.hljs-title, +.hljs-section { + color: #3d62f5; +} + +/* Atelier-Seaside Purple */ +.hljs-keyword, +.hljs-selector-tag { + color: #ad2bee; +} + +.hljs { + display: block; + overflow-x: auto; + background: #f4fbf4; + color: #5e6e5e; + padding: 0.5em; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} diff --git a/packages/admin-web-angular/src/assets/lib/hljs/styles/atelier-sulphurpool-dark.css b/packages/admin-web-angular/src/assets/lib/hljs/styles/atelier-sulphurpool-dark.css new file mode 100644 index 0000000..334bddd --- /dev/null +++ b/packages/admin-web-angular/src/assets/lib/hljs/styles/atelier-sulphurpool-dark.css @@ -0,0 +1,69 @@ +/* Base16 Atelier Sulphurpool Dark - Theme */ +/* by Bram de Haan (http://atelierbram.github.io/syntax-highlighting/atelier-schemes/sulphurpool) */ +/* Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) */ + +/* Atelier-Sulphurpool Comment */ +.hljs-comment, +.hljs-quote { + color: #898ea4; +} + +/* Atelier-Sulphurpool Red */ +.hljs-variable, +.hljs-template-variable, +.hljs-attribute, +.hljs-tag, +.hljs-name, +.hljs-regexp, +.hljs-link, +.hljs-name, +.hljs-selector-id, +.hljs-selector-class { + color: #c94922; +} + +/* Atelier-Sulphurpool Orange */ +.hljs-number, +.hljs-meta, +.hljs-built_in, +.hljs-builtin-name, +.hljs-literal, +.hljs-type, +.hljs-params { + color: #c76b29; +} + +/* Atelier-Sulphurpool Green */ +.hljs-string, +.hljs-symbol, +.hljs-bullet { + color: #ac9739; +} + +/* Atelier-Sulphurpool Blue */ +.hljs-title, +.hljs-section { + color: #3d8fd1; +} + +/* Atelier-Sulphurpool Purple */ +.hljs-keyword, +.hljs-selector-tag { + color: #6679cc; +} + +.hljs { + display: block; + overflow-x: auto; + background: #202746; + color: #979db4; + padding: 0.5em; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} diff --git a/packages/admin-web-angular/src/assets/lib/hljs/styles/atelier-sulphurpool-light.css b/packages/admin-web-angular/src/assets/lib/hljs/styles/atelier-sulphurpool-light.css new file mode 100644 index 0000000..0507530 --- /dev/null +++ b/packages/admin-web-angular/src/assets/lib/hljs/styles/atelier-sulphurpool-light.css @@ -0,0 +1,69 @@ +/* Base16 Atelier Sulphurpool Light - Theme */ +/* by Bram de Haan (http://atelierbram.github.io/syntax-highlighting/atelier-schemes/sulphurpool) */ +/* Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) */ + +/* Atelier-Sulphurpool Comment */ +.hljs-comment, +.hljs-quote { + color: #6b7394; +} + +/* Atelier-Sulphurpool Red */ +.hljs-variable, +.hljs-template-variable, +.hljs-attribute, +.hljs-tag, +.hljs-name, +.hljs-regexp, +.hljs-link, +.hljs-name, +.hljs-selector-id, +.hljs-selector-class { + color: #c94922; +} + +/* Atelier-Sulphurpool Orange */ +.hljs-number, +.hljs-meta, +.hljs-built_in, +.hljs-builtin-name, +.hljs-literal, +.hljs-type, +.hljs-params { + color: #c76b29; +} + +/* Atelier-Sulphurpool Green */ +.hljs-string, +.hljs-symbol, +.hljs-bullet { + color: #ac9739; +} + +/* Atelier-Sulphurpool Blue */ +.hljs-title, +.hljs-section { + color: #3d8fd1; +} + +/* Atelier-Sulphurpool Purple */ +.hljs-keyword, +.hljs-selector-tag { + color: #6679cc; +} + +.hljs { + display: block; + overflow-x: auto; + background: #f5f7ff; + color: #5e6687; + padding: 0.5em; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} diff --git a/packages/admin-web-angular/src/assets/lib/hljs/styles/atom-one-dark-reasonable.css b/packages/admin-web-angular/src/assets/lib/hljs/styles/atom-one-dark-reasonable.css new file mode 100644 index 0000000..6a478e2 --- /dev/null +++ b/packages/admin-web-angular/src/assets/lib/hljs/styles/atom-one-dark-reasonable.css @@ -0,0 +1,101 @@ +/* + +Atom One Dark With support for ReasonML by Gidi Morris, based off work by Daniel Gamage + +Original One Dark Syntax theme from https://github.com/atom/one-dark-syntax + +*/ +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + line-height: 1.3em; + color: #abb2bf; + background: #282c34; + border-radius: 5px; +} +.hljs-keyword, +.hljs-operator { + color: #f92672; +} +.hljs-pattern-match { + color: #f92672; +} +.hljs-pattern-match .hljs-constructor { + color: #61aeee; +} +.hljs-function { + color: #61aeee; +} +.hljs-function .hljs-params { + color: #a6e22e; +} +.hljs-function .hljs-params .hljs-typing { + color: #fd971f; +} +.hljs-module-access .hljs-module { + color: #7e57c2; +} +.hljs-constructor { + color: #e2b93d; +} +.hljs-constructor .hljs-string { + color: #9ccc65; +} +.hljs-comment, +.hljs-quote { + color: #b18eb1; + font-style: italic; +} +.hljs-doctag, +.hljs-formula { + color: #c678dd; +} +.hljs-section, +.hljs-name, +.hljs-selector-tag, +.hljs-deletion, +.hljs-subst { + color: #e06c75; +} +.hljs-literal { + color: #56b6c2; +} +.hljs-string, +.hljs-regexp, +.hljs-addition, +.hljs-attribute, +.hljs-meta-string { + color: #98c379; +} +.hljs-built_in, +.hljs-class .hljs-title { + color: #e6c07b; +} +.hljs-attr, +.hljs-variable, +.hljs-template-variable, +.hljs-type, +.hljs-selector-class, +.hljs-selector-attr, +.hljs-selector-pseudo, +.hljs-number { + color: #d19a66; +} +.hljs-symbol, +.hljs-bullet, +.hljs-link, +.hljs-meta, +.hljs-selector-id, +.hljs-title { + color: #61aeee; +} +.hljs-emphasis { + font-style: italic; +} +.hljs-strong { + font-weight: bold; +} +.hljs-link { + text-decoration: underline; +} diff --git a/packages/admin-web-angular/src/assets/lib/hljs/styles/atom-one-dark.css b/packages/admin-web-angular/src/assets/lib/hljs/styles/atom-one-dark.css new file mode 100644 index 0000000..1f9d18e --- /dev/null +++ b/packages/admin-web-angular/src/assets/lib/hljs/styles/atom-one-dark.css @@ -0,0 +1,96 @@ +/* + +Atom One Dark by Daniel Gamage +Original One Dark Syntax theme from https://github.com/atom/one-dark-syntax + +base: #282c34 +mono-1: #abb2bf +mono-2: #818896 +mono-3: #5c6370 +hue-1: #56b6c2 +hue-2: #61aeee +hue-3: #c678dd +hue-4: #98c379 +hue-5: #e06c75 +hue-5-2: #be5046 +hue-6: #d19a66 +hue-6-2: #e6c07b + +*/ + +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + color: #abb2bf; + background: #282c34; +} + +.hljs-comment, +.hljs-quote { + color: #5c6370; + font-style: italic; +} + +.hljs-doctag, +.hljs-keyword, +.hljs-formula { + color: #c678dd; +} + +.hljs-section, +.hljs-name, +.hljs-selector-tag, +.hljs-deletion, +.hljs-subst { + color: #e06c75; +} + +.hljs-literal { + color: #56b6c2; +} + +.hljs-string, +.hljs-regexp, +.hljs-addition, +.hljs-attribute, +.hljs-meta-string { + color: #98c379; +} + +.hljs-built_in, +.hljs-class .hljs-title { + color: #e6c07b; +} + +.hljs-attr, +.hljs-variable, +.hljs-template-variable, +.hljs-type, +.hljs-selector-class, +.hljs-selector-attr, +.hljs-selector-pseudo, +.hljs-number { + color: #d19a66; +} + +.hljs-symbol, +.hljs-bullet, +.hljs-link, +.hljs-meta, +.hljs-selector-id, +.hljs-title { + color: #61aeee; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} + +.hljs-link { + text-decoration: underline; +} diff --git a/packages/admin-web-angular/src/assets/lib/hljs/styles/atom-one-light.css b/packages/admin-web-angular/src/assets/lib/hljs/styles/atom-one-light.css new file mode 100644 index 0000000..5149211 --- /dev/null +++ b/packages/admin-web-angular/src/assets/lib/hljs/styles/atom-one-light.css @@ -0,0 +1,96 @@ +/* + +Atom One Light by Daniel Gamage +Original One Light Syntax theme from https://github.com/atom/one-light-syntax + +base: #fafafa +mono-1: #383a42 +mono-2: #686b77 +mono-3: #a0a1a7 +hue-1: #0184bb +hue-2: #4078f2 +hue-3: #a626a4 +hue-4: #50a14f +hue-5: #e45649 +hue-5-2: #c91243 +hue-6: #986801 +hue-6-2: #c18401 + +*/ + +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + color: #383a42; + background: #fafafa; +} + +.hljs-comment, +.hljs-quote { + color: #a0a1a7; + font-style: italic; +} + +.hljs-doctag, +.hljs-keyword, +.hljs-formula { + color: #a626a4; +} + +.hljs-section, +.hljs-name, +.hljs-selector-tag, +.hljs-deletion, +.hljs-subst { + color: #e45649; +} + +.hljs-literal { + color: #0184bb; +} + +.hljs-string, +.hljs-regexp, +.hljs-addition, +.hljs-attribute, +.hljs-meta-string { + color: #50a14f; +} + +.hljs-built_in, +.hljs-class .hljs-title { + color: #c18401; +} + +.hljs-attr, +.hljs-variable, +.hljs-template-variable, +.hljs-type, +.hljs-selector-class, +.hljs-selector-attr, +.hljs-selector-pseudo, +.hljs-number { + color: #986801; +} + +.hljs-symbol, +.hljs-bullet, +.hljs-link, +.hljs-meta, +.hljs-selector-id, +.hljs-title { + color: #4078f2; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} + +.hljs-link { + text-decoration: underline; +} diff --git a/packages/admin-web-angular/src/assets/lib/hljs/styles/brown-paper.css b/packages/admin-web-angular/src/assets/lib/hljs/styles/brown-paper.css new file mode 100644 index 0000000..bcdb9c1 --- /dev/null +++ b/packages/admin-web-angular/src/assets/lib/hljs/styles/brown-paper.css @@ -0,0 +1,64 @@ +/* + +Brown Paper style from goldblog.com.ua (c) Zaripov Yura + +*/ + +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + background: #b7a68e url(./brown-papersq.png); +} + +.hljs-keyword, +.hljs-selector-tag, +.hljs-literal { + color: #005599; + font-weight: bold; +} + +.hljs, +.hljs-subst { + color: #363c69; +} + +.hljs-string, +.hljs-title, +.hljs-section, +.hljs-type, +.hljs-attribute, +.hljs-symbol, +.hljs-bullet, +.hljs-built_in, +.hljs-addition, +.hljs-variable, +.hljs-template-tag, +.hljs-template-variable, +.hljs-link, +.hljs-name { + color: #2c009f; +} + +.hljs-comment, +.hljs-quote, +.hljs-meta, +.hljs-deletion { + color: #802022; +} + +.hljs-keyword, +.hljs-selector-tag, +.hljs-literal, +.hljs-doctag, +.hljs-title, +.hljs-section, +.hljs-type, +.hljs-name, +.hljs-strong { + font-weight: bold; +} + +.hljs-emphasis { + font-style: italic; +} diff --git a/packages/admin-web-angular/src/assets/lib/hljs/styles/brown-papersq.png b/packages/admin-web-angular/src/assets/lib/hljs/styles/brown-papersq.png new file mode 100644 index 0000000..3813903 Binary files /dev/null and b/packages/admin-web-angular/src/assets/lib/hljs/styles/brown-papersq.png differ diff --git a/packages/admin-web-angular/src/assets/lib/hljs/styles/codepen-embed.css b/packages/admin-web-angular/src/assets/lib/hljs/styles/codepen-embed.css new file mode 100644 index 0000000..0caccd5 --- /dev/null +++ b/packages/admin-web-angular/src/assets/lib/hljs/styles/codepen-embed.css @@ -0,0 +1,60 @@ +/* + codepen.io Embed Theme + Author: Justin Perry + Original theme - https://github.com/chriskempson/tomorrow-theme +*/ + +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + background: #222; + color: #fff; +} + +.hljs-comment, +.hljs-quote { + color: #777; +} + +.hljs-variable, +.hljs-template-variable, +.hljs-tag, +.hljs-regexp, +.hljs-meta, +.hljs-number, +.hljs-built_in, +.hljs-builtin-name, +.hljs-literal, +.hljs-params, +.hljs-symbol, +.hljs-bullet, +.hljs-link, +.hljs-deletion { + color: #ab875d; +} + +.hljs-section, +.hljs-title, +.hljs-name, +.hljs-selector-id, +.hljs-selector-class, +.hljs-type, +.hljs-attribute { + color: #9b869b; +} + +.hljs-string, +.hljs-keyword, +.hljs-selector-tag, +.hljs-addition { + color: #8f9c6c; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} diff --git a/packages/admin-web-angular/src/assets/lib/hljs/styles/color-brewer.css b/packages/admin-web-angular/src/assets/lib/hljs/styles/color-brewer.css new file mode 100644 index 0000000..8c8cacc --- /dev/null +++ b/packages/admin-web-angular/src/assets/lib/hljs/styles/color-brewer.css @@ -0,0 +1,69 @@ +/* + +Colorbrewer theme +Original: https://github.com/mbostock/colorbrewer-theme (c) Mike Bostock +Ported by Fabrício Tavares de Oliveira + +*/ + +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + background: #fff; +} + +.hljs, +.hljs-subst { + color: #000; +} + +.hljs-string, +.hljs-meta, +.hljs-symbol, +.hljs-template-tag, +.hljs-template-variable, +.hljs-addition { + color: #756bb1; +} + +.hljs-comment, +.hljs-quote { + color: #636363; +} + +.hljs-number, +.hljs-regexp, +.hljs-literal, +.hljs-bullet, +.hljs-link { + color: #31a354; +} + +.hljs-deletion, +.hljs-variable { + color: #88f; +} + +.hljs-keyword, +.hljs-selector-tag, +.hljs-title, +.hljs-section, +.hljs-built_in, +.hljs-doctag, +.hljs-type, +.hljs-tag, +.hljs-name, +.hljs-selector-id, +.hljs-selector-class, +.hljs-strong { + color: #3182bd; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-attribute { + color: #e6550d; +} diff --git a/packages/admin-web-angular/src/assets/lib/hljs/styles/darcula.css b/packages/admin-web-angular/src/assets/lib/hljs/styles/darcula.css new file mode 100644 index 0000000..77b5d93 --- /dev/null +++ b/packages/admin-web-angular/src/assets/lib/hljs/styles/darcula.css @@ -0,0 +1,76 @@ +/* + +Darcula color scheme from the JetBrains family of IDEs + +*/ + +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + background: #2b2b2b; +} + +.hljs { + color: #bababa; +} + +.hljs-strong, +.hljs-emphasis { + color: #a8a8a2; +} + +.hljs-bullet, +.hljs-quote, +.hljs-link, +.hljs-number, +.hljs-regexp, +.hljs-literal { + color: #6896ba; +} + +.hljs-code, +.hljs-selector-class { + color: #a6e22e; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-keyword, +.hljs-selector-tag, +.hljs-section, +.hljs-attribute, +.hljs-name, +.hljs-variable { + color: #cb7832; +} + +.hljs-params { + color: #b9b9b9; +} + +.hljs-string { + color: #6a8759; +} + +.hljs-subst, +.hljs-type, +.hljs-built_in, +.hljs-builtin-name, +.hljs-symbol, +.hljs-selector-id, +.hljs-selector-attr, +.hljs-selector-pseudo, +.hljs-template-tag, +.hljs-template-variable, +.hljs-addition { + color: #e0c46c; +} + +.hljs-comment, +.hljs-deletion, +.hljs-meta { + color: #7f7f7f; +} diff --git a/packages/admin-web-angular/src/assets/lib/hljs/styles/dark.css b/packages/admin-web-angular/src/assets/lib/hljs/styles/dark.css new file mode 100644 index 0000000..fb5d1a5 --- /dev/null +++ b/packages/admin-web-angular/src/assets/lib/hljs/styles/dark.css @@ -0,0 +1,63 @@ +/* + +Dark style from softwaremaniacs.org (c) Ivan Sagalaev + +*/ + +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + background: #444; +} + +.hljs-keyword, +.hljs-selector-tag, +.hljs-literal, +.hljs-section, +.hljs-link { + color: white; +} + +.hljs, +.hljs-subst { + color: #ddd; +} + +.hljs-string, +.hljs-title, +.hljs-name, +.hljs-type, +.hljs-attribute, +.hljs-symbol, +.hljs-bullet, +.hljs-built_in, +.hljs-addition, +.hljs-variable, +.hljs-template-tag, +.hljs-template-variable { + color: #d88; +} + +.hljs-comment, +.hljs-quote, +.hljs-deletion, +.hljs-meta { + color: #777; +} + +.hljs-keyword, +.hljs-selector-tag, +.hljs-literal, +.hljs-title, +.hljs-section, +.hljs-doctag, +.hljs-type, +.hljs-name, +.hljs-strong { + font-weight: bold; +} + +.hljs-emphasis { + font-style: italic; +} diff --git a/packages/admin-web-angular/src/assets/lib/hljs/styles/darkula.css b/packages/admin-web-angular/src/assets/lib/hljs/styles/darkula.css new file mode 100644 index 0000000..f4646c3 --- /dev/null +++ b/packages/admin-web-angular/src/assets/lib/hljs/styles/darkula.css @@ -0,0 +1,6 @@ +/* + Deprecated due to a typo in the name and left here for compatibility purpose only. + Please use darcula.css instead. +*/ + +@import url('darcula.css'); diff --git a/packages/admin-web-angular/src/assets/lib/hljs/styles/default.css b/packages/admin-web-angular/src/assets/lib/hljs/styles/default.css new file mode 100644 index 0000000..8185952 --- /dev/null +++ b/packages/admin-web-angular/src/assets/lib/hljs/styles/default.css @@ -0,0 +1,94 @@ +/* + +Original highlight.js style (c) Ivan Sagalaev + +*/ + +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + background: #f0f0f0; +} + +/* Base color: saturation 0; */ + +.hljs, +.hljs-subst { + color: #444; +} + +.hljs-comment { + color: #888888; +} + +.hljs-keyword, +.hljs-attribute, +.hljs-selector-tag, +.hljs-meta-keyword, +.hljs-doctag, +.hljs-name { + font-weight: bold; +} + +/* User color: hue: 0 */ + +.hljs-type, +.hljs-string, +.hljs-number, +.hljs-selector-id, +.hljs-selector-class, +.hljs-quote, +.hljs-template-tag, +.hljs-deletion { + color: #880000; +} + +.hljs-title, +.hljs-section { + color: #880000; + font-weight: bold; +} + +.hljs-regexp, +.hljs-symbol, +.hljs-variable, +.hljs-template-variable, +.hljs-link, +.hljs-selector-attr, +.hljs-selector-pseudo { + color: #bc6060; +} + +/* Language color: hue: 90; */ + +.hljs-literal { + color: #78a960; +} + +.hljs-built_in, +.hljs-bullet, +.hljs-code, +.hljs-addition { + color: #397300; +} + +/* Meta color: hue: 200 */ + +.hljs-meta { + color: #1f7199; +} + +.hljs-meta-string { + color: #4d99bf; +} + +/* Misc effects */ + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} diff --git a/packages/admin-web-angular/src/assets/lib/hljs/styles/docco.css b/packages/admin-web-angular/src/assets/lib/hljs/styles/docco.css new file mode 100644 index 0000000..eabc866 --- /dev/null +++ b/packages/admin-web-angular/src/assets/lib/hljs/styles/docco.css @@ -0,0 +1,97 @@ +/* +Docco style used in http://jashkenas.github.com/docco/ converted by Simon Madine (@thingsinjars) +*/ + +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + color: #000; + background: #f8f8ff; +} + +.hljs-comment, +.hljs-quote { + color: #408080; + font-style: italic; +} + +.hljs-keyword, +.hljs-selector-tag, +.hljs-literal, +.hljs-subst { + color: #954121; +} + +.hljs-number { + color: #40a070; +} + +.hljs-string, +.hljs-doctag { + color: #219161; +} + +.hljs-selector-id, +.hljs-selector-class, +.hljs-section, +.hljs-type { + color: #19469d; +} + +.hljs-params { + color: #00f; +} + +.hljs-title { + color: #458; + font-weight: bold; +} + +.hljs-tag, +.hljs-name, +.hljs-attribute { + color: #000080; + font-weight: normal; +} + +.hljs-variable, +.hljs-template-variable { + color: #008080; +} + +.hljs-regexp, +.hljs-link { + color: #b68; +} + +.hljs-symbol, +.hljs-bullet { + color: #990073; +} + +.hljs-built_in, +.hljs-builtin-name { + color: #0086b3; +} + +.hljs-meta { + color: #999; + font-weight: bold; +} + +.hljs-deletion { + background: #fdd; +} + +.hljs-addition { + background: #dfd; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} diff --git a/packages/admin-web-angular/src/assets/lib/hljs/styles/dracula.css b/packages/admin-web-angular/src/assets/lib/hljs/styles/dracula.css new file mode 100644 index 0000000..397ef18 --- /dev/null +++ b/packages/admin-web-angular/src/assets/lib/hljs/styles/dracula.css @@ -0,0 +1,76 @@ +/* + +Dracula Theme v1.2.0 + +https://github.com/zenorocha/dracula-theme + +Copyright 2015, All rights reserved + +Code licensed under the MIT license +http://zenorocha.mit-license.org + +@author Éverton Ribeiro +@author Zeno Rocha + +*/ + +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + background: #282a36; +} + +.hljs-keyword, +.hljs-selector-tag, +.hljs-literal, +.hljs-section, +.hljs-link { + color: #8be9fd; +} + +.hljs-function .hljs-keyword { + color: #ff79c6; +} + +.hljs, +.hljs-subst { + color: #f8f8f2; +} + +.hljs-string, +.hljs-title, +.hljs-name, +.hljs-type, +.hljs-attribute, +.hljs-symbol, +.hljs-bullet, +.hljs-addition, +.hljs-variable, +.hljs-template-tag, +.hljs-template-variable { + color: #f1fa8c; +} + +.hljs-comment, +.hljs-quote, +.hljs-deletion, +.hljs-meta { + color: #6272a4; +} + +.hljs-keyword, +.hljs-selector-tag, +.hljs-literal, +.hljs-title, +.hljs-section, +.hljs-doctag, +.hljs-type, +.hljs-name, +.hljs-strong { + font-weight: bold; +} + +.hljs-emphasis { + font-style: italic; +} diff --git a/packages/admin-web-angular/src/assets/lib/hljs/styles/far.css b/packages/admin-web-angular/src/assets/lib/hljs/styles/far.css new file mode 100644 index 0000000..2ab8096 --- /dev/null +++ b/packages/admin-web-angular/src/assets/lib/hljs/styles/far.css @@ -0,0 +1,71 @@ +/* + +FAR Style (c) MajestiC + +*/ + +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + background: #000080; +} + +.hljs, +.hljs-subst { + color: #0ff; +} + +.hljs-string, +.hljs-attribute, +.hljs-symbol, +.hljs-bullet, +.hljs-built_in, +.hljs-builtin-name, +.hljs-template-tag, +.hljs-template-variable, +.hljs-addition { + color: #ff0; +} + +.hljs-keyword, +.hljs-selector-tag, +.hljs-section, +.hljs-type, +.hljs-name, +.hljs-selector-id, +.hljs-selector-class, +.hljs-variable { + color: #fff; +} + +.hljs-comment, +.hljs-quote, +.hljs-doctag, +.hljs-deletion { + color: #888; +} + +.hljs-number, +.hljs-regexp, +.hljs-literal, +.hljs-link { + color: #0f0; +} + +.hljs-meta { + color: #008080; +} + +.hljs-keyword, +.hljs-selector-tag, +.hljs-title, +.hljs-section, +.hljs-name, +.hljs-strong { + font-weight: bold; +} + +.hljs-emphasis { + font-style: italic; +} diff --git a/packages/admin-web-angular/src/assets/lib/hljs/styles/foundation.css b/packages/admin-web-angular/src/assets/lib/hljs/styles/foundation.css new file mode 100644 index 0000000..569c5b7 --- /dev/null +++ b/packages/admin-web-angular/src/assets/lib/hljs/styles/foundation.css @@ -0,0 +1,89 @@ +/* +Description: Foundation 4 docs style for highlight.js +Author: Dan Allen +Website: http://foundation.zurb.com/docs/ +Version: 1.0 +Date: 2013-04-02 +*/ + +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + background: #eee; + color: black; +} + +.hljs-link, +.hljs-emphasis, +.hljs-attribute, +.hljs-addition { + color: #070; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong, +.hljs-string, +.hljs-deletion { + color: #d14; +} + +.hljs-strong { + font-weight: bold; +} + +.hljs-quote, +.hljs-comment { + color: #998; + font-style: italic; +} + +.hljs-section, +.hljs-title { + color: #900; +} + +.hljs-class .hljs-title, +.hljs-type { + color: #458; +} + +.hljs-variable, +.hljs-template-variable { + color: #336699; +} + +.hljs-bullet { + color: #997700; +} + +.hljs-meta { + color: #3344bb; +} + +.hljs-code, +.hljs-number, +.hljs-literal, +.hljs-keyword, +.hljs-selector-tag { + color: #099; +} + +.hljs-regexp { + background-color: #fff0ff; + color: #880088; +} + +.hljs-symbol { + color: #990073; +} + +.hljs-tag, +.hljs-name, +.hljs-selector-id, +.hljs-selector-class { + color: #007700; +} diff --git a/packages/admin-web-angular/src/assets/lib/hljs/styles/github-gist.css b/packages/admin-web-angular/src/assets/lib/hljs/styles/github-gist.css new file mode 100644 index 0000000..4f58e52 --- /dev/null +++ b/packages/admin-web-angular/src/assets/lib/hljs/styles/github-gist.css @@ -0,0 +1,71 @@ +/** + * GitHub Gist Theme + * Author : Louis Barranqueiro - https://github.com/LouisBarranqueiro + */ + +.hljs { + display: block; + background: white; + padding: 0.5em; + color: #333333; + overflow-x: auto; +} + +.hljs-comment, +.hljs-meta { + color: #969896; +} + +.hljs-string, +.hljs-variable, +.hljs-template-variable, +.hljs-strong, +.hljs-emphasis, +.hljs-quote { + color: #df5000; +} + +.hljs-keyword, +.hljs-selector-tag, +.hljs-type { + color: #a71d5d; +} + +.hljs-literal, +.hljs-symbol, +.hljs-bullet, +.hljs-attribute { + color: #0086b3; +} + +.hljs-section, +.hljs-name { + color: #63a35c; +} + +.hljs-tag { + color: #333333; +} + +.hljs-title, +.hljs-attr, +.hljs-selector-id, +.hljs-selector-class, +.hljs-selector-attr, +.hljs-selector-pseudo { + color: #795da3; +} + +.hljs-addition { + color: #55a532; + background-color: #eaffea; +} + +.hljs-deletion { + color: #bd2c00; + background-color: #ffecec; +} + +.hljs-link { + text-decoration: underline; +} diff --git a/packages/admin-web-angular/src/assets/lib/hljs/styles/github.css b/packages/admin-web-angular/src/assets/lib/hljs/styles/github.css new file mode 100644 index 0000000..79e30b6 --- /dev/null +++ b/packages/admin-web-angular/src/assets/lib/hljs/styles/github.css @@ -0,0 +1,99 @@ +/* + +github.com style (c) Vasily Polovnyov + +*/ + +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + color: #333; + background: #f8f8f8; +} + +.hljs-comment, +.hljs-quote { + color: #998; + font-style: italic; +} + +.hljs-keyword, +.hljs-selector-tag, +.hljs-subst { + color: #333; + font-weight: bold; +} + +.hljs-number, +.hljs-literal, +.hljs-variable, +.hljs-template-variable, +.hljs-tag .hljs-attr { + color: #008080; +} + +.hljs-string, +.hljs-doctag { + color: #d14; +} + +.hljs-title, +.hljs-section, +.hljs-selector-id { + color: #900; + font-weight: bold; +} + +.hljs-subst { + font-weight: normal; +} + +.hljs-type, +.hljs-class .hljs-title { + color: #458; + font-weight: bold; +} + +.hljs-tag, +.hljs-name, +.hljs-attribute { + color: #000080; + font-weight: normal; +} + +.hljs-regexp, +.hljs-link { + color: #009926; +} + +.hljs-symbol, +.hljs-bullet { + color: #990073; +} + +.hljs-built_in, +.hljs-builtin-name { + color: #0086b3; +} + +.hljs-meta { + color: #999; + font-weight: bold; +} + +.hljs-deletion { + background: #fdd; +} + +.hljs-addition { + background: #dfd; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} diff --git a/packages/admin-web-angular/src/assets/lib/hljs/styles/gml.css b/packages/admin-web-angular/src/assets/lib/hljs/styles/gml.css new file mode 100644 index 0000000..e5f3b03 --- /dev/null +++ b/packages/admin-web-angular/src/assets/lib/hljs/styles/gml.css @@ -0,0 +1,78 @@ +/* + +GML Theme - Meseta + +*/ + +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + background: #222222; + color: #c0c0c0; +} + +.hljs-keywords { + color: #ffb871; + font-weight: bold; +} + +.hljs-built_in { + color: #ffb871; +} + +.hljs-literal { + color: #ff8080; +} + +.hljs-symbol { + color: #58e55a; +} + +.hljs-comment { + color: #5b995b; +} + +.hljs-string { + color: #ffff00; +} + +.hljs-number { + color: #ff8080; +} + +.hljs-attribute, +.hljs-selector-tag, +.hljs-doctag, +.hljs-name, +.hljs-bullet, +.hljs-code, +.hljs-addition, +.hljs-regexp, +.hljs-variable, +.hljs-template-variable, +.hljs-link, +.hljs-selector-attr, +.hljs-selector-pseudo, +.hljs-type, +.hljs-selector-id, +.hljs-selector-class, +.hljs-quote, +.hljs-template-tag, +.hljs-deletion, +.hljs-title, +.hljs-section, +.hljs-function, +.hljs-meta-keyword, +.hljs-meta, +.hljs-subst { + color: #c0c0c0; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} diff --git a/packages/admin-web-angular/src/assets/lib/hljs/styles/googlecode.css b/packages/admin-web-angular/src/assets/lib/hljs/styles/googlecode.css new file mode 100644 index 0000000..eab69f9 --- /dev/null +++ b/packages/admin-web-angular/src/assets/lib/hljs/styles/googlecode.css @@ -0,0 +1,89 @@ +/* + +Google Code style (c) Aahan Krish + +*/ + +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + background: white; + color: black; +} + +.hljs-comment, +.hljs-quote { + color: #800; +} + +.hljs-keyword, +.hljs-selector-tag, +.hljs-section, +.hljs-title, +.hljs-name { + color: #008; +} + +.hljs-variable, +.hljs-template-variable { + color: #660; +} + +.hljs-string, +.hljs-selector-attr, +.hljs-selector-pseudo, +.hljs-regexp { + color: #080; +} + +.hljs-literal, +.hljs-symbol, +.hljs-bullet, +.hljs-meta, +.hljs-number, +.hljs-link { + color: #066; +} + +.hljs-title, +.hljs-doctag, +.hljs-type, +.hljs-attr, +.hljs-built_in, +.hljs-builtin-name, +.hljs-params { + color: #606; +} + +.hljs-attribute, +.hljs-subst { + color: #000; +} + +.hljs-formula { + background-color: #eee; + font-style: italic; +} + +.hljs-selector-id, +.hljs-selector-class { + color: #9b703f; +} + +.hljs-addition { + background-color: #baeeba; +} + +.hljs-deletion { + background-color: #ffc8bd; +} + +.hljs-doctag, +.hljs-strong { + font-weight: bold; +} + +.hljs-emphasis { + font-style: italic; +} diff --git a/packages/admin-web-angular/src/assets/lib/hljs/styles/grayscale.css b/packages/admin-web-angular/src/assets/lib/hljs/styles/grayscale.css new file mode 100644 index 0000000..94fcc2d --- /dev/null +++ b/packages/admin-web-angular/src/assets/lib/hljs/styles/grayscale.css @@ -0,0 +1,106 @@ +/* + +grayscale style (c) MY Sun + +*/ + +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + color: #333; + background: #fff; +} + +.hljs-comment, +.hljs-quote { + color: #777; + font-style: italic; +} + +.hljs-keyword, +.hljs-selector-tag, +.hljs-subst { + color: #333; + font-weight: bold; +} + +.hljs-number, +.hljs-literal { + color: #777; +} + +.hljs-string, +.hljs-doctag, +.hljs-formula { + color: #333; + background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAAECAYAAACp8Z5+AAAAJ0lEQVQIW2O8e/fufwYGBgZBQUEQxcCIIfDu3Tuwivfv30NUoAsAALHpFMMLqZlPAAAAAElFTkSuQmCC) + repeat; +} + +.hljs-title, +.hljs-section, +.hljs-selector-id { + color: #000; + font-weight: bold; +} + +.hljs-subst { + font-weight: normal; +} + +.hljs-class .hljs-title, +.hljs-type, +.hljs-name { + color: #333; + font-weight: bold; +} + +.hljs-tag { + color: #333; +} + +.hljs-regexp { + color: #333; + background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAICAYAAADA+m62AAAAPUlEQVQYV2NkQAN37979r6yszIgujiIAU4RNMVwhuiQ6H6wQl3XI4oy4FMHcCJPHcDS6J2A2EqUQpJhohQDexSef15DBCwAAAABJRU5ErkJggg==) + repeat; +} + +.hljs-symbol, +.hljs-bullet, +.hljs-link { + color: #000; + background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAKElEQVQIW2NkQAO7d+/+z4gsBhJwdXVlhAvCBECKwIIwAbhKZBUwBQA6hBpm5efZsgAAAABJRU5ErkJggg==) + repeat; +} + +.hljs-built_in, +.hljs-builtin-name { + color: #000; + text-decoration: underline; +} + +.hljs-meta { + color: #999; + font-weight: bold; +} + +.hljs-deletion { + color: #fff; + background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAADCAYAAABS3WWCAAAAE0lEQVQIW2MMDQ39zzhz5kwIAQAyxweWgUHd1AAAAABJRU5ErkJggg==) + repeat; +} + +.hljs-addition { + color: #000; + background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAkAAAAJCAYAAADgkQYQAAAALUlEQVQYV2N89+7dfwYk8P79ewZBQUFkIQZGOiu6e/cuiptQHAPl0NtNxAQBAM97Oejj3Dg7AAAAAElFTkSuQmCC) + repeat; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} diff --git a/packages/admin-web-angular/src/assets/lib/hljs/styles/gruvbox-dark.css b/packages/admin-web-angular/src/assets/lib/hljs/styles/gruvbox-dark.css new file mode 100644 index 0000000..d8cbf76 --- /dev/null +++ b/packages/admin-web-angular/src/assets/lib/hljs/styles/gruvbox-dark.css @@ -0,0 +1,108 @@ +/* + +Gruvbox style (dark) (c) Pavel Pertsev (original style at https://github.com/morhetz/gruvbox) + +*/ + +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + background: #282828; +} + +.hljs, +.hljs-subst { + color: #ebdbb2; +} + +/* Gruvbox Red */ +.hljs-deletion, +.hljs-formula, +.hljs-keyword, +.hljs-link, +.hljs-selector-tag { + color: #fb4934; +} + +/* Gruvbox Blue */ +.hljs-built_in, +.hljs-emphasis, +.hljs-name, +.hljs-quote, +.hljs-strong, +.hljs-title, +.hljs-variable { + color: #83a598; +} + +/* Gruvbox Yellow */ +.hljs-attr, +.hljs-params, +.hljs-template-tag, +.hljs-type { + color: #fabd2f; +} + +/* Gruvbox Purple */ +.hljs-builtin-name, +.hljs-doctag, +.hljs-literal, +.hljs-number { + color: #8f3f71; +} + +/* Gruvbox Orange */ +.hljs-code, +.hljs-meta, +.hljs-regexp, +.hljs-selector-id, +.hljs-template-variable { + color: #fe8019; +} + +/* Gruvbox Green */ +.hljs-addition, +.hljs-meta-string, +.hljs-section, +.hljs-selector-attr, +.hljs-selector-class, +.hljs-string, +.hljs-symbol { + color: #b8bb26; +} + +/* Gruvbox Aqua */ +.hljs-attribute, +.hljs-bullet, +.hljs-class, +.hljs-function, +.hljs-function .hljs-keyword, +.hljs-meta-keyword, +.hljs-selector-pseudo, +.hljs-tag { + color: #8ec07c; +} + +/* Gruvbox Gray */ +.hljs-comment { + color: #928374; +} + +/* Gruvbox Purple */ +.hljs-link_label, +.hljs-literal, +.hljs-number { + color: #d3869b; +} + +.hljs-comment, +.hljs-emphasis { + font-style: italic; +} + +.hljs-section, +.hljs-strong, +.hljs-tag { + font-weight: bold; +} diff --git a/packages/admin-web-angular/src/assets/lib/hljs/styles/gruvbox-light.css b/packages/admin-web-angular/src/assets/lib/hljs/styles/gruvbox-light.css new file mode 100644 index 0000000..7608016 --- /dev/null +++ b/packages/admin-web-angular/src/assets/lib/hljs/styles/gruvbox-light.css @@ -0,0 +1,108 @@ +/* + +Gruvbox style (light) (c) Pavel Pertsev (original style at https://github.com/morhetz/gruvbox) + +*/ + +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + background: #fbf1c7; +} + +.hljs, +.hljs-subst { + color: #3c3836; +} + +/* Gruvbox Red */ +.hljs-deletion, +.hljs-formula, +.hljs-keyword, +.hljs-link, +.hljs-selector-tag { + color: #9d0006; +} + +/* Gruvbox Blue */ +.hljs-built_in, +.hljs-emphasis, +.hljs-name, +.hljs-quote, +.hljs-strong, +.hljs-title, +.hljs-variable { + color: #076678; +} + +/* Gruvbox Yellow */ +.hljs-attr, +.hljs-params, +.hljs-template-tag, +.hljs-type { + color: #b57614; +} + +/* Gruvbox Purple */ +.hljs-builtin-name, +.hljs-doctag, +.hljs-literal, +.hljs-number { + color: #8f3f71; +} + +/* Gruvbox Orange */ +.hljs-code, +.hljs-meta, +.hljs-regexp, +.hljs-selector-id, +.hljs-template-variable { + color: #af3a03; +} + +/* Gruvbox Green */ +.hljs-addition, +.hljs-meta-string, +.hljs-section, +.hljs-selector-attr, +.hljs-selector-class, +.hljs-string, +.hljs-symbol { + color: #79740e; +} + +/* Gruvbox Aqua */ +.hljs-attribute, +.hljs-bullet, +.hljs-class, +.hljs-function, +.hljs-function .hljs-keyword, +.hljs-meta-keyword, +.hljs-selector-pseudo, +.hljs-tag { + color: #427b58; +} + +/* Gruvbox Gray */ +.hljs-comment { + color: #928374; +} + +/* Gruvbox Purple */ +.hljs-link_label, +.hljs-literal, +.hljs-number { + color: #8f3f71; +} + +.hljs-comment, +.hljs-emphasis { + font-style: italic; +} + +.hljs-section, +.hljs-strong, +.hljs-tag { + font-weight: bold; +} diff --git a/packages/admin-web-angular/src/assets/lib/hljs/styles/hopscotch.css b/packages/admin-web-angular/src/assets/lib/hljs/styles/hopscotch.css new file mode 100644 index 0000000..5be179a --- /dev/null +++ b/packages/admin-web-angular/src/assets/lib/hljs/styles/hopscotch.css @@ -0,0 +1,83 @@ +/* + * Hopscotch + * by Jan T. Sott + * https://github.com/idleberg/Hopscotch + * + * This work is licensed under the Creative Commons CC0 1.0 Universal License + */ + +/* Comment */ +.hljs-comment, +.hljs-quote { + color: #989498; +} + +/* Red */ +.hljs-variable, +.hljs-template-variable, +.hljs-attribute, +.hljs-tag, +.hljs-name, +.hljs-selector-id, +.hljs-selector-class, +.hljs-regexp, +.hljs-link, +.hljs-deletion { + color: #dd464c; +} + +/* Orange */ +.hljs-number, +.hljs-built_in, +.hljs-builtin-name, +.hljs-literal, +.hljs-type, +.hljs-params { + color: #fd8b19; +} + +/* Yellow */ +.hljs-class .hljs-title { + color: #fdcc59; +} + +/* Green */ +.hljs-string, +.hljs-symbol, +.hljs-bullet, +.hljs-addition { + color: #8fc13e; +} + +/* Aqua */ +.hljs-meta { + color: #149b93; +} + +/* Blue */ +.hljs-function, +.hljs-section, +.hljs-title { + color: #1290bf; +} + +/* Purple */ +.hljs-keyword, +.hljs-selector-tag { + color: #c85e7c; +} + +.hljs { + display: block; + background: #322931; + color: #b9b5b8; + padding: 0.5em; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} diff --git a/packages/admin-web-angular/src/assets/lib/hljs/styles/hybrid.css b/packages/admin-web-angular/src/assets/lib/hljs/styles/hybrid.css new file mode 100644 index 0000000..1752d26 --- /dev/null +++ b/packages/admin-web-angular/src/assets/lib/hljs/styles/hybrid.css @@ -0,0 +1,102 @@ +/* + +vim-hybrid theme by w0ng (https://github.com/w0ng/vim-hybrid) + +*/ + +/*background color*/ +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + background: #1d1f21; +} + +/*selection color*/ +.hljs::selection, +.hljs span::selection { + background: #373b41; +} + +.hljs::-moz-selection, +.hljs span::-moz-selection { + background: #373b41; +} + +/*foreground color*/ +.hljs { + color: #c5c8c6; +} + +/*color: fg_yellow*/ +.hljs-title, +.hljs-name { + color: #f0c674; +} + +/*color: fg_comment*/ +.hljs-comment, +.hljs-meta, +.hljs-meta .hljs-keyword { + color: #707880; +} + +/*color: fg_red*/ +.hljs-number, +.hljs-symbol, +.hljs-literal, +.hljs-deletion, +.hljs-link { + color: #cc6666; +} + +/*color: fg_green*/ +.hljs-string, +.hljs-doctag, +.hljs-addition, +.hljs-regexp, +.hljs-selector-attr, +.hljs-selector-pseudo { + color: #b5bd68; +} + +/*color: fg_purple*/ +.hljs-attribute, +.hljs-code, +.hljs-selector-id { + color: #b294bb; +} + +/*color: fg_blue*/ +.hljs-keyword, +.hljs-selector-tag, +.hljs-bullet, +.hljs-tag { + color: #81a2be; +} + +/*color: fg_aqua*/ +.hljs-subst, +.hljs-variable, +.hljs-template-tag, +.hljs-template-variable { + color: #8abeb7; +} + +/*color: fg_orange*/ +.hljs-type, +.hljs-built_in, +.hljs-builtin-name, +.hljs-quote, +.hljs-section, +.hljs-selector-class { + color: #de935f; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} diff --git a/packages/admin-web-angular/src/assets/lib/hljs/styles/idea.css b/packages/admin-web-angular/src/assets/lib/hljs/styles/idea.css new file mode 100644 index 0000000..21f8992 --- /dev/null +++ b/packages/admin-web-angular/src/assets/lib/hljs/styles/idea.css @@ -0,0 +1,97 @@ +/* + +Intellij Idea-like styling (c) Vasily Polovnyov + +*/ + +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + color: #000; + background: #fff; +} + +.hljs-subst, +.hljs-title { + font-weight: normal; + color: #000; +} + +.hljs-comment, +.hljs-quote { + color: #808080; + font-style: italic; +} + +.hljs-meta { + color: #808000; +} + +.hljs-tag { + background: #efefef; +} + +.hljs-section, +.hljs-name, +.hljs-literal, +.hljs-keyword, +.hljs-selector-tag, +.hljs-type, +.hljs-selector-id, +.hljs-selector-class { + font-weight: bold; + color: #000080; +} + +.hljs-attribute, +.hljs-number, +.hljs-regexp, +.hljs-link { + font-weight: bold; + color: #0000ff; +} + +.hljs-number, +.hljs-regexp, +.hljs-link { + font-weight: normal; +} + +.hljs-string { + color: #008000; + font-weight: bold; +} + +.hljs-symbol, +.hljs-bullet, +.hljs-formula { + color: #000; + background: #d0eded; + font-style: italic; +} + +.hljs-doctag { + text-decoration: underline; +} + +.hljs-variable, +.hljs-template-variable { + color: #660e7a; +} + +.hljs-addition { + background: #baeeba; +} + +.hljs-deletion { + background: #ffc8bd; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} diff --git a/packages/admin-web-angular/src/assets/lib/hljs/styles/ir-black.css b/packages/admin-web-angular/src/assets/lib/hljs/styles/ir-black.css new file mode 100644 index 0000000..726ab3a --- /dev/null +++ b/packages/admin-web-angular/src/assets/lib/hljs/styles/ir-black.css @@ -0,0 +1,73 @@ +/* + IR_Black style (c) Vasily Mikhailitchenko +*/ + +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + background: #000; + color: #f8f8f8; +} + +.hljs-comment, +.hljs-quote, +.hljs-meta { + color: #7c7c7c; +} + +.hljs-keyword, +.hljs-selector-tag, +.hljs-tag, +.hljs-name { + color: #96cbfe; +} + +.hljs-attribute, +.hljs-selector-id { + color: #ffffb6; +} + +.hljs-string, +.hljs-selector-attr, +.hljs-selector-pseudo, +.hljs-addition { + color: #a8ff60; +} + +.hljs-subst { + color: #daefa3; +} + +.hljs-regexp, +.hljs-link { + color: #e9c062; +} + +.hljs-title, +.hljs-section, +.hljs-type, +.hljs-doctag { + color: #ffffb6; +} + +.hljs-symbol, +.hljs-bullet, +.hljs-variable, +.hljs-template-variable, +.hljs-literal { + color: #c6c5fe; +} + +.hljs-number, +.hljs-deletion { + color: #ff73fd; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} diff --git a/packages/admin-web-angular/src/assets/lib/hljs/styles/isbl-editor-dark.css b/packages/admin-web-angular/src/assets/lib/hljs/styles/isbl-editor-dark.css new file mode 100644 index 0000000..e8eb9bf --- /dev/null +++ b/packages/admin-web-angular/src/assets/lib/hljs/styles/isbl-editor-dark.css @@ -0,0 +1,110 @@ +/* + +ISBL Editor style dark color scheme (c) Dmitriy Tarasov + +*/ + +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + background: #404040; + color: #f0f0f0; +} + +/* Base color: saturation 0; */ + +.hljs, +.hljs-subst { + color: #f0f0f0; +} + +.hljs-comment { + color: #b5b5b5; + font-style: italic; +} + +.hljs-keyword, +.hljs-attribute, +.hljs-selector-tag, +.hljs-meta-keyword, +.hljs-doctag, +.hljs-name { + color: #f0f0f0; + font-weight: bold; +} + +/* User color: hue: 0 */ + +.hljs-string { + color: #97bf0d; +} + +.hljs-type, +.hljs-number, +.hljs-selector-id, +.hljs-selector-class, +.hljs-quote, +.hljs-template-tag, +.hljs-deletion { + color: #f0f0f0; +} + +.hljs-title, +.hljs-section { + color: #df471e; +} + +.hljs-title > .hljs-built_in { + color: #81bce9; + font-weight: normal; +} + +.hljs-regexp, +.hljs-symbol, +.hljs-variable, +.hljs-template-variable, +.hljs-link, +.hljs-selector-attr, +.hljs-selector-pseudo { + color: #e2c696; +} + +/* Language color: hue: 90; */ + +.hljs-built_in, +.hljs-literal { + color: #97bf0d; + font-weight: bold; +} + +.hljs-bullet, +.hljs-code, +.hljs-addition { + color: #397300; +} + +.hljs-class { + color: #ce9d4d; + font-weight: bold; +} + +/* Meta color: hue: 200 */ + +.hljs-meta { + color: #1f7199; +} + +.hljs-meta-string { + color: #4d99bf; +} + +/* Misc effects */ + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} diff --git a/packages/admin-web-angular/src/assets/lib/hljs/styles/isbl-editor-light.css b/packages/admin-web-angular/src/assets/lib/hljs/styles/isbl-editor-light.css new file mode 100644 index 0000000..46afd6c --- /dev/null +++ b/packages/admin-web-angular/src/assets/lib/hljs/styles/isbl-editor-light.css @@ -0,0 +1,110 @@ +/* + +ISBL Editor style light color schemec (c) Dmitriy Tarasov + +*/ + +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + background: white; + color: black; +} + +/* Base color: saturation 0; */ + +.hljs, +.hljs-subst { + color: #000000; +} + +.hljs-comment { + color: #555555; + font-style: italic; +} + +.hljs-keyword, +.hljs-attribute, +.hljs-selector-tag, +.hljs-meta-keyword, +.hljs-doctag, +.hljs-name { + color: #000000; + font-weight: bold; +} + +/* User color: hue: 0 */ + +.hljs-string { + color: #000080; +} + +.hljs-type, +.hljs-number, +.hljs-selector-id, +.hljs-selector-class, +.hljs-quote, +.hljs-template-tag, +.hljs-deletion { + color: #000000; +} + +.hljs-title, +.hljs-section { + color: #fb2c00; +} + +.hljs-title > .hljs-built_in { + color: #008080; + font-weight: normal; +} + +.hljs-regexp, +.hljs-symbol, +.hljs-variable, +.hljs-template-variable, +.hljs-link, +.hljs-selector-attr, +.hljs-selector-pseudo { + color: #5e1700; +} + +/* Language color: hue: 90; */ + +.hljs-built_in, +.hljs-literal { + color: #000080; + font-weight: bold; +} + +.hljs-bullet, +.hljs-code, +.hljs-addition { + color: #397300; +} + +.hljs-class { + color: #6f1c00; + font-weight: bold; +} + +/* Meta color: hue: 200 */ + +.hljs-meta { + color: #1f7199; +} + +.hljs-meta-string { + color: #4d99bf; +} + +/* Misc effects */ + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} diff --git a/packages/admin-web-angular/src/assets/lib/hljs/styles/kimbie.dark.css b/packages/admin-web-angular/src/assets/lib/hljs/styles/kimbie.dark.css new file mode 100644 index 0000000..ae4bd1d --- /dev/null +++ b/packages/admin-web-angular/src/assets/lib/hljs/styles/kimbie.dark.css @@ -0,0 +1,74 @@ +/* + Name: Kimbie (dark) + Author: Jan T. Sott + License: Creative Commons Attribution-ShareAlike 4.0 Unported License + URL: https://github.com/idleberg/Kimbie-highlight.js +*/ + +/* Kimbie Comment */ +.hljs-comment, +.hljs-quote { + color: #d6baad; +} + +/* Kimbie Red */ +.hljs-variable, +.hljs-template-variable, +.hljs-tag, +.hljs-name, +.hljs-selector-id, +.hljs-selector-class, +.hljs-regexp, +.hljs-meta { + color: #dc3958; +} + +/* Kimbie Orange */ +.hljs-number, +.hljs-built_in, +.hljs-builtin-name, +.hljs-literal, +.hljs-type, +.hljs-params, +.hljs-deletion, +.hljs-link { + color: #f79a32; +} + +/* Kimbie Yellow */ +.hljs-title, +.hljs-section, +.hljs-attribute { + color: #f06431; +} + +/* Kimbie Green */ +.hljs-string, +.hljs-symbol, +.hljs-bullet, +.hljs-addition { + color: #889b4a; +} + +/* Kimbie Purple */ +.hljs-keyword, +.hljs-selector-tag, +.hljs-function { + color: #98676a; +} + +.hljs { + display: block; + overflow-x: auto; + background: #221a0f; + color: #d3af86; + padding: 0.5em; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} diff --git a/packages/admin-web-angular/src/assets/lib/hljs/styles/kimbie.light.css b/packages/admin-web-angular/src/assets/lib/hljs/styles/kimbie.light.css new file mode 100644 index 0000000..8397594 --- /dev/null +++ b/packages/admin-web-angular/src/assets/lib/hljs/styles/kimbie.light.css @@ -0,0 +1,74 @@ +/* + Name: Kimbie (light) + Author: Jan T. Sott + License: Creative Commons Attribution-ShareAlike 4.0 Unported License + URL: https://github.com/idleberg/Kimbie-highlight.js +*/ + +/* Kimbie Comment */ +.hljs-comment, +.hljs-quote { + color: #a57a4c; +} + +/* Kimbie Red */ +.hljs-variable, +.hljs-template-variable, +.hljs-tag, +.hljs-name, +.hljs-selector-id, +.hljs-selector-class, +.hljs-regexp, +.hljs-meta { + color: #dc3958; +} + +/* Kimbie Orange */ +.hljs-number, +.hljs-built_in, +.hljs-builtin-name, +.hljs-literal, +.hljs-type, +.hljs-params, +.hljs-deletion, +.hljs-link { + color: #f79a32; +} + +/* Kimbie Yellow */ +.hljs-title, +.hljs-section, +.hljs-attribute { + color: #f06431; +} + +/* Kimbie Green */ +.hljs-string, +.hljs-symbol, +.hljs-bullet, +.hljs-addition { + color: #889b4a; +} + +/* Kimbie Purple */ +.hljs-keyword, +.hljs-selector-tag, +.hljs-function { + color: #98676a; +} + +.hljs { + display: block; + overflow-x: auto; + background: #fbebd4; + color: #84613d; + padding: 0.5em; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} diff --git a/packages/admin-web-angular/src/assets/lib/hljs/styles/lightfair.css b/packages/admin-web-angular/src/assets/lib/hljs/styles/lightfair.css new file mode 100644 index 0000000..6e6450b --- /dev/null +++ b/packages/admin-web-angular/src/assets/lib/hljs/styles/lightfair.css @@ -0,0 +1,88 @@ +/* + +Lightfair style (c) Tristian Kelly + +*/ + +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; +} + +.hljs-name { + color: #01a3a3; +} + +.hljs-tag, +.hljs-meta { + color: #778899; +} + +.hljs, +.hljs-subst { + color: #444; +} + +.hljs-comment { + color: #888888; +} + +.hljs-keyword, +.hljs-attribute, +.hljs-selector-tag, +.hljs-meta-keyword, +.hljs-doctag, +.hljs-name { + font-weight: bold; +} + +.hljs-type, +.hljs-string, +.hljs-number, +.hljs-selector-id, +.hljs-selector-class, +.hljs-quote, +.hljs-template-tag, +.hljs-deletion { + color: #4286f4; +} + +.hljs-title, +.hljs-section { + color: #4286f4; + font-weight: bold; +} + +.hljs-regexp, +.hljs-symbol, +.hljs-variable, +.hljs-template-variable, +.hljs-link, +.hljs-selector-attr, +.hljs-selector-pseudo { + color: #bc6060; +} + +.hljs-literal { + color: #62bcbc; +} + +.hljs-built_in, +.hljs-bullet, +.hljs-code, +.hljs-addition { + color: #25c6c6; +} + +.hljs-meta-string { + color: #4d99bf; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} diff --git a/packages/admin-web-angular/src/assets/lib/hljs/styles/magula.css b/packages/admin-web-angular/src/assets/lib/hljs/styles/magula.css new file mode 100644 index 0000000..76ad4b1 --- /dev/null +++ b/packages/admin-web-angular/src/assets/lib/hljs/styles/magula.css @@ -0,0 +1,70 @@ +/* +Description: Magula style for highligh.js +Author: Ruslan Keba +Website: http://rukeba.com/ +Version: 1.0 +Date: 2009-01-03 +Music: Aphex Twin / Xtal +*/ + +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + background-color: #f4f4f4; +} + +.hljs, +.hljs-subst { + color: black; +} + +.hljs-string, +.hljs-title, +.hljs-symbol, +.hljs-bullet, +.hljs-attribute, +.hljs-addition, +.hljs-variable, +.hljs-template-tag, +.hljs-template-variable { + color: #050; +} + +.hljs-comment, +.hljs-quote { + color: #777; +} + +.hljs-number, +.hljs-regexp, +.hljs-literal, +.hljs-type, +.hljs-link { + color: #800; +} + +.hljs-deletion, +.hljs-meta { + color: #00e; +} + +.hljs-keyword, +.hljs-selector-tag, +.hljs-doctag, +.hljs-title, +.hljs-section, +.hljs-built_in, +.hljs-tag, +.hljs-name { + font-weight: bold; + color: navy; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} diff --git a/packages/admin-web-angular/src/assets/lib/hljs/styles/mono-blue.css b/packages/admin-web-angular/src/assets/lib/hljs/styles/mono-blue.css new file mode 100644 index 0000000..126b3f3 --- /dev/null +++ b/packages/admin-web-angular/src/assets/lib/hljs/styles/mono-blue.css @@ -0,0 +1,59 @@ +/* + Five-color theme from a single blue hue. +*/ +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + background: #eaeef3; +} + +.hljs { + color: #00193a; +} + +.hljs-keyword, +.hljs-selector-tag, +.hljs-title, +.hljs-section, +.hljs-doctag, +.hljs-name, +.hljs-strong { + font-weight: bold; +} + +.hljs-comment { + color: #738191; +} + +.hljs-string, +.hljs-title, +.hljs-section, +.hljs-built_in, +.hljs-literal, +.hljs-type, +.hljs-addition, +.hljs-tag, +.hljs-quote, +.hljs-name, +.hljs-selector-id, +.hljs-selector-class { + color: #0048ab; +} + +.hljs-meta, +.hljs-subst, +.hljs-symbol, +.hljs-regexp, +.hljs-attribute, +.hljs-deletion, +.hljs-variable, +.hljs-template-variable, +.hljs-link, +.hljs-bullet { + color: #4c81c9; +} + +.hljs-emphasis { + font-style: italic; +} diff --git a/packages/admin-web-angular/src/assets/lib/hljs/styles/monokai-sublime.css b/packages/admin-web-angular/src/assets/lib/hljs/styles/monokai-sublime.css new file mode 100644 index 0000000..6e5e116 --- /dev/null +++ b/packages/admin-web-angular/src/assets/lib/hljs/styles/monokai-sublime.css @@ -0,0 +1,83 @@ +/* + +Monokai Sublime style. Derived from Monokai by noformnocontent http://nn.mit-license.org/ + +*/ + +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + background: #23241f; +} + +.hljs, +.hljs-tag, +.hljs-subst { + color: #f8f8f2; +} + +.hljs-strong, +.hljs-emphasis { + color: #a8a8a2; +} + +.hljs-bullet, +.hljs-quote, +.hljs-number, +.hljs-regexp, +.hljs-literal, +.hljs-link { + color: #ae81ff; +} + +.hljs-code, +.hljs-title, +.hljs-section, +.hljs-selector-class { + color: #a6e22e; +} + +.hljs-strong { + font-weight: bold; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-keyword, +.hljs-selector-tag, +.hljs-name, +.hljs-attr { + color: #f92672; +} + +.hljs-symbol, +.hljs-attribute { + color: #66d9ef; +} + +.hljs-params, +.hljs-class .hljs-title { + color: #f8f8f2; +} + +.hljs-string, +.hljs-type, +.hljs-built_in, +.hljs-builtin-name, +.hljs-selector-id, +.hljs-selector-attr, +.hljs-selector-pseudo, +.hljs-addition, +.hljs-variable, +.hljs-template-variable { + color: #e6db74; +} + +.hljs-comment, +.hljs-deletion, +.hljs-meta { + color: #75715e; +} diff --git a/packages/admin-web-angular/src/assets/lib/hljs/styles/monokai.css b/packages/admin-web-angular/src/assets/lib/hljs/styles/monokai.css new file mode 100644 index 0000000..1c448aa --- /dev/null +++ b/packages/admin-web-angular/src/assets/lib/hljs/styles/monokai.css @@ -0,0 +1,71 @@ +/* +Monokai style - ported by Luigi Maselli - http://grigio.org +*/ + +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + background: #272822; + color: #ddd; +} + +.hljs-tag, +.hljs-keyword, +.hljs-selector-tag, +.hljs-literal, +.hljs-strong, +.hljs-name { + color: #f92672; +} + +.hljs-code { + color: #66d9ef; +} + +.hljs-class .hljs-title { + color: white; +} + +.hljs-attribute, +.hljs-symbol, +.hljs-regexp, +.hljs-link { + color: #bf79db; +} + +.hljs-string, +.hljs-bullet, +.hljs-subst, +.hljs-title, +.hljs-section, +.hljs-emphasis, +.hljs-type, +.hljs-built_in, +.hljs-builtin-name, +.hljs-selector-attr, +.hljs-selector-pseudo, +.hljs-addition, +.hljs-variable, +.hljs-template-tag, +.hljs-template-variable { + color: #a6e22e; +} + +.hljs-comment, +.hljs-quote, +.hljs-deletion, +.hljs-meta { + color: #75715e; +} + +.hljs-keyword, +.hljs-selector-tag, +.hljs-literal, +.hljs-doctag, +.hljs-title, +.hljs-section, +.hljs-type, +.hljs-selector-id { + font-weight: bold; +} diff --git a/packages/admin-web-angular/src/assets/lib/hljs/styles/nord.css b/packages/admin-web-angular/src/assets/lib/hljs/styles/nord.css new file mode 100644 index 0000000..ccaa6de --- /dev/null +++ b/packages/admin-web-angular/src/assets/lib/hljs/styles/nord.css @@ -0,0 +1,309 @@ +/* + * Copyright (c) 2017-present Arctic Ice Studio + * Copyright (c) 2017-present Sven Greb + * + * Project: Nord highlight.js + * Version: 0.1.0 + * Repository: https://github.com/arcticicestudio/nord-highlightjs + * License: MIT + * References: + * https://github.com/arcticicestudio/nord + */ + +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + background: #2e3440; +} + +.hljs, +.hljs-subst { + color: #d8dee9; +} + +.hljs-selector-tag { + color: #81a1c1; +} + +.hljs-selector-id { + color: #8fbcbb; + font-weight: bold; +} + +.hljs-selector-class { + color: #8fbcbb; +} + +.hljs-selector-attr { + color: #8fbcbb; +} + +.hljs-selector-pseudo { + color: #88c0d0; +} + +.hljs-addition { + background-color: rgba(163, 190, 140, 0.5); +} + +.hljs-deletion { + background-color: rgba(191, 97, 106, 0.5); +} + +.hljs-built_in, +.hljs-type { + color: #8fbcbb; +} + +.hljs-class { + color: #8fbcbb; +} + +.hljs-function { + color: #88c0d0; +} + +.hljs-function > .hljs-title { + color: #88c0d0; +} + +.hljs-keyword, +.hljs-literal, +.hljs-symbol { + color: #81a1c1; +} + +.hljs-number { + color: #b48ead; +} + +.hljs-regexp { + color: #ebcb8b; +} + +.hljs-string { + color: #a3be8c; +} + +.hljs-title { + color: #8fbcbb; +} + +.hljs-params { + color: #d8dee9; +} + +.hljs-bullet { + color: #81a1c1; +} + +.hljs-code { + color: #8fbcbb; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-formula { + color: #8fbcbb; +} + +.hljs-strong { + font-weight: bold; +} + +.hljs-link:hover { + text-decoration: underline; +} + +.hljs-quote { + color: #4c566a; +} + +.hljs-comment { + color: #4c566a; +} + +.hljs-doctag { + color: #8fbcbb; +} + +.hljs-meta, +.hljs-meta-keyword { + color: #5e81ac; +} + +.hljs-meta-string { + color: #a3be8c; +} + +.hljs-attr { + color: #8fbcbb; +} + +.hljs-attribute { + color: #d8dee9; +} + +.hljs-builtin-name { + color: #81a1c1; +} + +.hljs-name { + color: #81a1c1; +} + +.hljs-section { + color: #88c0d0; +} + +.hljs-tag { + color: #81a1c1; +} + +.hljs-variable { + color: #d8dee9; +} + +.hljs-template-variable { + color: #d8dee9; +} + +.hljs-template-tag { + color: #5e81ac; +} + +.abnf .hljs-attribute { + color: #88c0d0; +} + +.abnf .hljs-symbol { + color: #ebcb8b; +} + +.apache .hljs-attribute { + color: #88c0d0; +} + +.apache .hljs-section { + color: #81a1c1; +} + +.arduino .hljs-built_in { + color: #88c0d0; +} + +.aspectj .hljs-meta { + color: #d08770; +} + +.aspectj > .hljs-title { + color: #88c0d0; +} + +.bnf .hljs-attribute { + color: #8fbcbb; +} + +.clojure .hljs-name { + color: #88c0d0; +} + +.clojure .hljs-symbol { + color: #ebcb8b; +} + +.coq .hljs-built_in { + color: #88c0d0; +} + +.cpp .hljs-meta-string { + color: #8fbcbb; +} + +.css .hljs-built_in { + color: #88c0d0; +} + +.css .hljs-keyword { + color: #d08770; +} + +.diff .hljs-meta { + color: #8fbcbb; +} + +.ebnf .hljs-attribute { + color: #8fbcbb; +} + +.glsl .hljs-built_in { + color: #88c0d0; +} + +.groovy .hljs-meta:not(:first-child) { + color: #d08770; +} + +.haxe .hljs-meta { + color: #d08770; +} + +.java .hljs-meta { + color: #d08770; +} + +.ldif .hljs-attribute { + color: #8fbcbb; +} + +.lisp .hljs-name { + color: #88c0d0; +} + +.lua .hljs-built_in { + color: #88c0d0; +} + +.moonscript .hljs-built_in { + color: #88c0d0; +} + +.nginx .hljs-attribute { + color: #88c0d0; +} + +.nginx .hljs-section { + color: #5e81ac; +} + +.pf .hljs-built_in { + color: #88c0d0; +} + +.processing .hljs-built_in { + color: #88c0d0; +} + +.scss .hljs-keyword { + color: #81a1c1; +} + +.stylus .hljs-keyword { + color: #81a1c1; +} + +.swift .hljs-meta { + color: #d08770; +} + +.vim .hljs-built_in { + color: #88c0d0; + font-style: italic; +} + +.yaml .hljs-meta { + color: #d08770; +} diff --git a/packages/admin-web-angular/src/assets/lib/hljs/styles/obsidian.css b/packages/admin-web-angular/src/assets/lib/hljs/styles/obsidian.css new file mode 100644 index 0000000..52c76f0 --- /dev/null +++ b/packages/admin-web-angular/src/assets/lib/hljs/styles/obsidian.css @@ -0,0 +1,88 @@ +/** + * Obsidian style + * ported by Alexander Marenin (http://github.com/ioncreature) + */ + +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + background: #282b2e; +} + +.hljs-keyword, +.hljs-selector-tag, +.hljs-literal, +.hljs-selector-id { + color: #93c763; +} + +.hljs-number { + color: #ffcd22; +} + +.hljs { + color: #e0e2e4; +} + +.hljs-attribute { + color: #668bb0; +} + +.hljs-code, +.hljs-class .hljs-title, +.hljs-section { + color: white; +} + +.hljs-regexp, +.hljs-link { + color: #d39745; +} + +.hljs-meta { + color: #557182; +} + +.hljs-tag, +.hljs-name, +.hljs-bullet, +.hljs-subst, +.hljs-emphasis, +.hljs-type, +.hljs-built_in, +.hljs-selector-attr, +.hljs-selector-pseudo, +.hljs-addition, +.hljs-variable, +.hljs-template-tag, +.hljs-template-variable { + color: #8cbbad; +} + +.hljs-string, +.hljs-symbol { + color: #ec7600; +} + +.hljs-comment, +.hljs-quote, +.hljs-deletion { + color: #818e96; +} + +.hljs-selector-class { + color: #a082bd; +} + +.hljs-keyword, +.hljs-selector-tag, +.hljs-literal, +.hljs-doctag, +.hljs-title, +.hljs-section, +.hljs-type, +.hljs-name, +.hljs-strong { + font-weight: bold; +} diff --git a/packages/admin-web-angular/src/assets/lib/hljs/styles/ocean.css b/packages/admin-web-angular/src/assets/lib/hljs/styles/ocean.css new file mode 100644 index 0000000..c1c1386 --- /dev/null +++ b/packages/admin-web-angular/src/assets/lib/hljs/styles/ocean.css @@ -0,0 +1,74 @@ +/* Ocean Dark Theme */ +/* https://github.com/gavsiu */ +/* Original theme - https://github.com/chriskempson/base16 */ + +/* Ocean Comment */ +.hljs-comment, +.hljs-quote { + color: #65737e; +} + +/* Ocean Red */ +.hljs-variable, +.hljs-template-variable, +.hljs-tag, +.hljs-name, +.hljs-selector-id, +.hljs-selector-class, +.hljs-regexp, +.hljs-deletion { + color: #bf616a; +} + +/* Ocean Orange */ +.hljs-number, +.hljs-built_in, +.hljs-builtin-name, +.hljs-literal, +.hljs-type, +.hljs-params, +.hljs-meta, +.hljs-link { + color: #d08770; +} + +/* Ocean Yellow */ +.hljs-attribute { + color: #ebcb8b; +} + +/* Ocean Green */ +.hljs-string, +.hljs-symbol, +.hljs-bullet, +.hljs-addition { + color: #a3be8c; +} + +/* Ocean Blue */ +.hljs-title, +.hljs-section { + color: #8fa1b3; +} + +/* Ocean Purple */ +.hljs-keyword, +.hljs-selector-tag { + color: #b48ead; +} + +.hljs { + display: block; + overflow-x: auto; + background: #2b303b; + color: #c0c5ce; + padding: 0.5em; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} diff --git a/packages/admin-web-angular/src/assets/lib/hljs/styles/paraiso-dark.css b/packages/admin-web-angular/src/assets/lib/hljs/styles/paraiso-dark.css new file mode 100644 index 0000000..a04bf41 --- /dev/null +++ b/packages/admin-web-angular/src/assets/lib/hljs/styles/paraiso-dark.css @@ -0,0 +1,72 @@ +/* + Paraíso (dark) + Created by Jan T. Sott (http://github.com/idleberg) + Inspired by the art of Rubens LP (http://www.rubenslp.com.br) +*/ + +/* Paraíso Comment */ +.hljs-comment, +.hljs-quote { + color: #8d8687; +} + +/* Paraíso Red */ +.hljs-variable, +.hljs-template-variable, +.hljs-tag, +.hljs-name, +.hljs-selector-id, +.hljs-selector-class, +.hljs-regexp, +.hljs-link, +.hljs-meta { + color: #ef6155; +} + +/* Paraíso Orange */ +.hljs-number, +.hljs-built_in, +.hljs-builtin-name, +.hljs-literal, +.hljs-type, +.hljs-params, +.hljs-deletion { + color: #f99b15; +} + +/* Paraíso Yellow */ +.hljs-title, +.hljs-section, +.hljs-attribute { + color: #fec418; +} + +/* Paraíso Green */ +.hljs-string, +.hljs-symbol, +.hljs-bullet, +.hljs-addition { + color: #48b685; +} + +/* Paraíso Purple */ +.hljs-keyword, +.hljs-selector-tag { + color: #815ba4; +} + +.hljs { + display: block; + overflow-x: auto; + background: #2f1e2e; + color: #a39e9b; + padding: 0.5em; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} diff --git a/packages/admin-web-angular/src/assets/lib/hljs/styles/paraiso-light.css b/packages/admin-web-angular/src/assets/lib/hljs/styles/paraiso-light.css new file mode 100644 index 0000000..f7e3b04 --- /dev/null +++ b/packages/admin-web-angular/src/assets/lib/hljs/styles/paraiso-light.css @@ -0,0 +1,72 @@ +/* + Paraíso (light) + Created by Jan T. Sott (http://github.com/idleberg) + Inspired by the art of Rubens LP (http://www.rubenslp.com.br) +*/ + +/* Paraíso Comment */ +.hljs-comment, +.hljs-quote { + color: #776e71; +} + +/* Paraíso Red */ +.hljs-variable, +.hljs-template-variable, +.hljs-tag, +.hljs-name, +.hljs-selector-id, +.hljs-selector-class, +.hljs-regexp, +.hljs-link, +.hljs-meta { + color: #ef6155; +} + +/* Paraíso Orange */ +.hljs-number, +.hljs-built_in, +.hljs-builtin-name, +.hljs-literal, +.hljs-type, +.hljs-params, +.hljs-deletion { + color: #f99b15; +} + +/* Paraíso Yellow */ +.hljs-title, +.hljs-section, +.hljs-attribute { + color: #fec418; +} + +/* Paraíso Green */ +.hljs-string, +.hljs-symbol, +.hljs-bullet, +.hljs-addition { + color: #48b685; +} + +/* Paraíso Purple */ +.hljs-keyword, +.hljs-selector-tag { + color: #815ba4; +} + +.hljs { + display: block; + overflow-x: auto; + background: #e7e9db; + color: #4f424c; + padding: 0.5em; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} diff --git a/packages/admin-web-angular/src/assets/lib/hljs/styles/pojoaque.css b/packages/admin-web-angular/src/assets/lib/hljs/styles/pojoaque.css new file mode 100644 index 0000000..6238521 --- /dev/null +++ b/packages/admin-web-angular/src/assets/lib/hljs/styles/pojoaque.css @@ -0,0 +1,83 @@ +/* + +Pojoaque Style by Jason Tate +http://web-cms-designs.com/ftopict-10-pojoaque-style-for-highlight-js-code-highlighter.html +Based on Solarized Style from http://ethanschoonover.com/solarized + +*/ + +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + color: #dccf8f; + background: url(./pojoaque.jpg) repeat scroll left top #181914; +} + +.hljs-comment, +.hljs-quote { + color: #586e75; + font-style: italic; +} + +.hljs-keyword, +.hljs-selector-tag, +.hljs-literal, +.hljs-addition { + color: #b64926; +} + +.hljs-number, +.hljs-string, +.hljs-doctag, +.hljs-regexp { + color: #468966; +} + +.hljs-title, +.hljs-section, +.hljs-built_in, +.hljs-name { + color: #ffb03b; +} + +.hljs-variable, +.hljs-template-variable, +.hljs-class .hljs-title, +.hljs-type, +.hljs-tag { + color: #b58900; +} + +.hljs-attribute { + color: #b89859; +} + +.hljs-symbol, +.hljs-bullet, +.hljs-link, +.hljs-subst, +.hljs-meta { + color: #cb4b16; +} + +.hljs-deletion { + color: #dc322f; +} + +.hljs-selector-id, +.hljs-selector-class { + color: #d3a60c; +} + +.hljs-formula { + background: #073642; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} diff --git a/packages/admin-web-angular/src/assets/lib/hljs/styles/pojoaque.jpg b/packages/admin-web-angular/src/assets/lib/hljs/styles/pojoaque.jpg new file mode 100644 index 0000000..9c07d4a Binary files /dev/null and b/packages/admin-web-angular/src/assets/lib/hljs/styles/pojoaque.jpg differ diff --git a/packages/admin-web-angular/src/assets/lib/hljs/styles/purebasic.css b/packages/admin-web-angular/src/assets/lib/hljs/styles/purebasic.css new file mode 100644 index 0000000..fbd9604 --- /dev/null +++ b/packages/admin-web-angular/src/assets/lib/hljs/styles/purebasic.css @@ -0,0 +1,97 @@ +/* + +PureBASIC native IDE style ( version 1.0 - April 2016 ) + +by Tristano Ajmone + +Public Domain + +NOTE_1: PureBASIC code syntax highlighting only applies the following classes: + .hljs-comment + .hljs-function + .hljs-keywords + .hljs-string + .hljs-symbol + + Other classes are added here for the benefit of styling other languages with the look and feel of PureBASIC native IDE style. + If you need to customize a stylesheet for PureBASIC only, remove all non-relevant classes -- PureBASIC-related classes are followed by + a "--- used for PureBASIC ... ---" comment on same line. + +NOTE_2: Color names provided in comments were derived using "Name that Color" online tool: + http://chir.ag/projects/name-that-color +*/ + +.hljs { + /* Common set of rules required by highlight.js (don'r remove!) */ + display: block; + overflow-x: auto; + padding: 0.5em; + background: #ffffdf; /* Half and Half (approx.) */ + /* --- Uncomment to add PureBASIC native IDE styled font! + font-family: Consolas; +*/ +} + +.hljs, /* --- used for PureBASIC base color --- */ +.hljs-type, /* --- used for PureBASIC Procedures return type --- */ +.hljs-function, /* --- used for wrapping PureBASIC Procedures definitions --- */ +.hljs-name, +.hljs-number, +.hljs-attr, +.hljs-params, +.hljs-subst { + color: #000000; /* Black */ +} + +.hljs-comment, /* --- used for PureBASIC Comments --- */ +.hljs-regexp, +.hljs-section, +.hljs-selector-pseudo, +.hljs-addition { + color: #00aaaa; /* Persian Green (approx.) */ +} + +.hljs-title, /* --- used for PureBASIC Procedures Names --- */ +.hljs-tag, +.hljs-variable, +.hljs-code { + color: #006666; /* Blue Stone (approx.) */ +} + +.hljs-keyword, /* --- used for PureBASIC Keywords --- */ +.hljs-class, +.hljs-meta-keyword, +.hljs-selector-class, +.hljs-built_in, +.hljs-builtin-name { + color: #006666; /* Blue Stone (approx.) */ + font-weight: bold; +} + +.hljs-string, /* --- used for PureBASIC Strings --- */ +.hljs-selector-attr { + color: #0080ff; /* Azure Radiance (approx.) */ +} + +.hljs-symbol, /* --- used for PureBASIC Constants --- */ +.hljs-link, +.hljs-deletion, +.hljs-attribute { + color: #924b72; /* Cannon Pink (approx.) */ +} + +.hljs-meta, +.hljs-literal, +.hljs-selector-id { + color: #924b72; /* Cannon Pink (approx.) */ + font-weight: bold; +} + +.hljs-strong, +.hljs-name { + font-weight: bold; +} + +.hljs-emphasis { + font-style: italic; +} diff --git a/packages/admin-web-angular/src/assets/lib/hljs/styles/qtcreator_dark.css b/packages/admin-web-angular/src/assets/lib/hljs/styles/qtcreator_dark.css new file mode 100644 index 0000000..5526d61 --- /dev/null +++ b/packages/admin-web-angular/src/assets/lib/hljs/styles/qtcreator_dark.css @@ -0,0 +1,81 @@ +/* + +Qt Creator dark color scheme + +*/ + +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + background: #000000; +} + +.hljs, +.hljs-subst, +.hljs-tag, +.hljs-title { + color: #aaaaaa; +} + +.hljs-strong, +.hljs-emphasis { + color: #a8a8a2; +} + +.hljs-bullet, +.hljs-quote, +.hljs-number, +.hljs-regexp, +.hljs-literal { + color: #ff55ff; +} + +.hljs-code .hljs-selector-class { + color: #aaaaff; +} + +.hljs-emphasis, +.hljs-stronge, +.hljs-type { + font-style: italic; +} + +.hljs-keyword, +.hljs-selector-tag, +.hljs-function, +.hljs-section, +.hljs-symbol, +.hljs-name { + color: #ffff55; +} + +.hljs-attribute { + color: #ff5555; +} + +.hljs-variable, +.hljs-params, +.hljs-class .hljs-title { + color: #8888ff; +} + +.hljs-string, +.hljs-selector-id, +.hljs-selector-attr, +.hljs-selector-pseudo, +.hljs-type, +.hljs-built_in, +.hljs-builtin-name, +.hljs-template-tag, +.hljs-template-variable, +.hljs-addition, +.hljs-link { + color: #ff55ff; +} + +.hljs-comment, +.hljs-meta, +.hljs-deletion { + color: #55ffff; +} diff --git a/packages/admin-web-angular/src/assets/lib/hljs/styles/qtcreator_light.css b/packages/admin-web-angular/src/assets/lib/hljs/styles/qtcreator_light.css new file mode 100644 index 0000000..1d4475f --- /dev/null +++ b/packages/admin-web-angular/src/assets/lib/hljs/styles/qtcreator_light.css @@ -0,0 +1,81 @@ +/* + +Qt Creator light color scheme + +*/ + +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + background: #ffffff; +} + +.hljs, +.hljs-subst, +.hljs-tag, +.hljs-title { + color: #000000; +} + +.hljs-strong, +.hljs-emphasis { + color: #000000; +} + +.hljs-bullet, +.hljs-quote, +.hljs-number, +.hljs-regexp, +.hljs-literal { + color: #000080; +} + +.hljs-code .hljs-selector-class { + color: #800080; +} + +.hljs-emphasis, +.hljs-stronge, +.hljs-type { + font-style: italic; +} + +.hljs-keyword, +.hljs-selector-tag, +.hljs-function, +.hljs-section, +.hljs-symbol, +.hljs-name { + color: #808000; +} + +.hljs-attribute { + color: #800000; +} + +.hljs-variable, +.hljs-params, +.hljs-class .hljs-title { + color: #0055af; +} + +.hljs-string, +.hljs-selector-id, +.hljs-selector-attr, +.hljs-selector-pseudo, +.hljs-type, +.hljs-built_in, +.hljs-builtin-name, +.hljs-template-tag, +.hljs-template-variable, +.hljs-addition, +.hljs-link { + color: #008000; +} + +.hljs-comment, +.hljs-meta, +.hljs-deletion { + color: #008000; +} diff --git a/packages/admin-web-angular/src/assets/lib/hljs/styles/railscasts.css b/packages/admin-web-angular/src/assets/lib/hljs/styles/railscasts.css new file mode 100644 index 0000000..a0316b0 --- /dev/null +++ b/packages/admin-web-angular/src/assets/lib/hljs/styles/railscasts.css @@ -0,0 +1,105 @@ +/* + +Railscasts-like style (c) Visoft, Inc. (Damien White) + +*/ + +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + background: #232323; + color: #e6e1dc; +} + +.hljs-comment, +.hljs-quote { + color: #bc9458; + font-style: italic; +} + +.hljs-keyword, +.hljs-selector-tag { + color: #c26230; +} + +.hljs-string, +.hljs-number, +.hljs-regexp, +.hljs-variable, +.hljs-template-variable { + color: #a5c261; +} + +.hljs-subst { + color: #519f50; +} + +.hljs-tag, +.hljs-name { + color: #e8bf6a; +} + +.hljs-type { + color: #da4939; +} + +.hljs-symbol, +.hljs-bullet, +.hljs-built_in, +.hljs-builtin-name, +.hljs-attr, +.hljs-link { + color: #6d9cbe; +} + +.hljs-params { + color: #d0d0ff; +} + +.hljs-attribute { + color: #cda869; +} + +.hljs-meta { + color: #9b859d; +} + +.hljs-title, +.hljs-section { + color: #ffc66d; +} + +.hljs-addition { + background-color: #144212; + color: #e6e1dc; + display: inline-block; + width: 100%; +} + +.hljs-deletion { + background-color: #600; + color: #e6e1dc; + display: inline-block; + width: 100%; +} + +.hljs-selector-class { + color: #9b703f; +} + +.hljs-selector-id { + color: #8b98ab; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} + +.hljs-link { + text-decoration: underline; +} diff --git a/packages/admin-web-angular/src/assets/lib/hljs/styles/rainbow.css b/packages/admin-web-angular/src/assets/lib/hljs/styles/rainbow.css new file mode 100644 index 0000000..68cccd2 --- /dev/null +++ b/packages/admin-web-angular/src/assets/lib/hljs/styles/rainbow.css @@ -0,0 +1,84 @@ +/* + +Style with support for rainbow parens + +*/ + +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + background: #474949; + color: #d1d9e1; +} + +.hljs-comment, +.hljs-quote { + color: #969896; + font-style: italic; +} + +.hljs-keyword, +.hljs-selector-tag, +.hljs-literal, +.hljs-type, +.hljs-addition { + color: #cc99cc; +} + +.hljs-number, +.hljs-selector-attr, +.hljs-selector-pseudo { + color: #f99157; +} + +.hljs-string, +.hljs-doctag, +.hljs-regexp { + color: #8abeb7; +} + +.hljs-title, +.hljs-name, +.hljs-section, +.hljs-built_in { + color: #b5bd68; +} + +.hljs-variable, +.hljs-template-variable, +.hljs-selector-id, +.hljs-class .hljs-title { + color: #ffcc66; +} + +.hljs-section, +.hljs-name, +.hljs-strong { + font-weight: bold; +} + +.hljs-symbol, +.hljs-bullet, +.hljs-subst, +.hljs-meta, +.hljs-link { + color: #f99157; +} + +.hljs-deletion { + color: #dc322f; +} + +.hljs-formula { + background: #eee8d5; +} + +.hljs-attr, +.hljs-attribute { + color: #81a2be; +} + +.hljs-emphasis { + font-style: italic; +} diff --git a/packages/admin-web-angular/src/assets/lib/hljs/styles/routeros.css b/packages/admin-web-angular/src/assets/lib/hljs/styles/routeros.css new file mode 100644 index 0000000..f13a94d --- /dev/null +++ b/packages/admin-web-angular/src/assets/lib/hljs/styles/routeros.css @@ -0,0 +1,105 @@ +/* + + highlight.js style for Microtik RouterOS script + +*/ + +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + background: #f0f0f0; +} + +/* Base color: saturation 0; */ + +.hljs, +.hljs-subst { + color: #444; +} + +.hljs-comment { + color: #888888; +} + +.hljs-keyword, +.hljs-selector-tag, +.hljs-meta-keyword, +.hljs-doctag, +.hljs-name { + font-weight: bold; +} + +.hljs-attribute { + color: #0e9a00; +} + +.hljs-function { + color: #99069a; +} + +.hljs-builtin-name { + color: #99069a; +} + +/* User color: hue: 0 */ + +.hljs-type, +.hljs-string, +.hljs-number, +.hljs-selector-id, +.hljs-selector-class, +.hljs-quote, +.hljs-template-tag, +.hljs-deletion { + color: #880000; +} + +.hljs-title, +.hljs-section { + color: #880000; + font-weight: bold; +} + +.hljs-regexp, +.hljs-symbol, +.hljs-variable, +.hljs-template-variable, +.hljs-link, +.hljs-selector-attr, +.hljs-selector-pseudo { + color: #bc6060; +} + +/* Language color: hue: 90; */ + +.hljs-literal { + color: #78a960; +} + +.hljs-built_in, +.hljs-bullet, +.hljs-code, +.hljs-addition { + color: #0c9a9a; +} + +/* Meta color: hue: 200 */ + +.hljs-meta { + color: #1f7199; +} + +.hljs-meta-string { + color: #4d99bf; +} + +/* Misc effects */ + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} diff --git a/packages/admin-web-angular/src/assets/lib/hljs/styles/school-book.css b/packages/admin-web-angular/src/assets/lib/hljs/styles/school-book.css new file mode 100644 index 0000000..0f8adc2 --- /dev/null +++ b/packages/admin-web-angular/src/assets/lib/hljs/styles/school-book.css @@ -0,0 +1,72 @@ +/* + +School Book style from goldblog.com.ua (c) Zaripov Yura + +*/ + +.hljs { + display: block; + overflow-x: auto; + padding: 15px 0.5em 0.5em 30px; + font-size: 11px; + line-height: 16px; +} + +pre { + background: #f6f6ae url(./school-book.png); + border-top: solid 2px #d2e8b9; + border-bottom: solid 1px #d2e8b9; +} + +.hljs-keyword, +.hljs-selector-tag, +.hljs-literal { + color: #005599; + font-weight: bold; +} + +.hljs, +.hljs-subst { + color: #3e5915; +} + +.hljs-string, +.hljs-title, +.hljs-section, +.hljs-type, +.hljs-symbol, +.hljs-bullet, +.hljs-attribute, +.hljs-built_in, +.hljs-builtin-name, +.hljs-addition, +.hljs-variable, +.hljs-template-tag, +.hljs-template-variable, +.hljs-link { + color: #2c009f; +} + +.hljs-comment, +.hljs-quote, +.hljs-deletion, +.hljs-meta { + color: #e60415; +} + +.hljs-keyword, +.hljs-selector-tag, +.hljs-literal, +.hljs-doctag, +.hljs-title, +.hljs-section, +.hljs-type, +.hljs-name, +.hljs-selector-id, +.hljs-strong { + font-weight: bold; +} + +.hljs-emphasis { + font-style: italic; +} diff --git a/packages/admin-web-angular/src/assets/lib/hljs/styles/school-book.png b/packages/admin-web-angular/src/assets/lib/hljs/styles/school-book.png new file mode 100644 index 0000000..956e979 Binary files /dev/null and b/packages/admin-web-angular/src/assets/lib/hljs/styles/school-book.png differ diff --git a/packages/admin-web-angular/src/assets/lib/hljs/styles/shades-of-purple.css b/packages/admin-web-angular/src/assets/lib/hljs/styles/shades-of-purple.css new file mode 100644 index 0000000..ced2477 --- /dev/null +++ b/packages/admin-web-angular/src/assets/lib/hljs/styles/shades-of-purple.css @@ -0,0 +1,97 @@ +/** + * Shades of Purple Theme — for Highlightjs. + * + * @author (c) Ahmad Awais + * @link GitHub Repo → https://github.com/ahmadawais/Shades-of-Purple-HighlightJS + * @version 1.5.0 + */ + +.hljs { + display: block; + overflow-x: auto; + /* Custom font is optional */ + /* font-family: 'Operator Mono', 'Fira Code', 'Menlo', 'Monaco', 'Courier New', 'monospace'; */ + line-height: 1.45; + padding: 2rem; + background: #2d2b57; + font-weight: normal; +} + +.hljs-title { + color: #fad000; + font-weight: normal; +} + +.hljs-name { + color: #a1feff; +} + +.hljs-tag { + color: #ffffff; +} + +.hljs-attr { + color: #f8d000; + font-style: italic; +} + +.hljs-built_in, +.hljs-selector-tag, +.hljs-section { + color: #fb9e00; +} + +.hljs-keyword { + color: #fb9e00; +} + +.hljs, +.hljs-subst { + color: #e3dfff; +} + +.hljs-string, +.hljs-attribute, +.hljs-symbol, +.hljs-bullet, +.hljs-addition, +.hljs-code, +.hljs-regexp, +.hljs-selector-class, +.hljs-selector-attr, +.hljs-selector-pseudo, +.hljs-template-tag, +.hljs-quote, +.hljs-deletion { + color: #4cd213; +} + +.hljs-meta, +.hljs-meta-string { + color: #fb9e00; +} + +.hljs-comment { + color: #ac65ff; +} + +.hljs-keyword, +.hljs-selector-tag, +.hljs-literal, +.hljs-name, +.hljs-strong { + font-weight: normal; +} + +.hljs-literal, +.hljs-number { + color: #fa658d; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} diff --git a/packages/admin-web-angular/src/assets/lib/hljs/styles/solarized-dark.css b/packages/admin-web-angular/src/assets/lib/hljs/styles/solarized-dark.css new file mode 100644 index 0000000..1b04308 --- /dev/null +++ b/packages/admin-web-angular/src/assets/lib/hljs/styles/solarized-dark.css @@ -0,0 +1,84 @@ +/* + +Orginal Style from ethanschoonover.com/solarized (c) Jeremy Hull + +*/ + +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + background: #002b36; + color: #839496; +} + +.hljs-comment, +.hljs-quote { + color: #586e75; +} + +/* Solarized Green */ +.hljs-keyword, +.hljs-selector-tag, +.hljs-addition { + color: #859900; +} + +/* Solarized Cyan */ +.hljs-number, +.hljs-string, +.hljs-meta .hljs-meta-string, +.hljs-literal, +.hljs-doctag, +.hljs-regexp { + color: #2aa198; +} + +/* Solarized Blue */ +.hljs-title, +.hljs-section, +.hljs-name, +.hljs-selector-id, +.hljs-selector-class { + color: #268bd2; +} + +/* Solarized Yellow */ +.hljs-attribute, +.hljs-attr, +.hljs-variable, +.hljs-template-variable, +.hljs-class .hljs-title, +.hljs-type { + color: #b58900; +} + +/* Solarized Orange */ +.hljs-symbol, +.hljs-bullet, +.hljs-subst, +.hljs-meta, +.hljs-meta .hljs-keyword, +.hljs-selector-attr, +.hljs-selector-pseudo, +.hljs-link { + color: #cb4b16; +} + +/* Solarized Red */ +.hljs-built_in, +.hljs-deletion { + color: #dc322f; +} + +.hljs-formula { + background: #073642; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} diff --git a/packages/admin-web-angular/src/assets/lib/hljs/styles/solarized-light.css b/packages/admin-web-angular/src/assets/lib/hljs/styles/solarized-light.css new file mode 100644 index 0000000..808e015 --- /dev/null +++ b/packages/admin-web-angular/src/assets/lib/hljs/styles/solarized-light.css @@ -0,0 +1,84 @@ +/* + +Orginal Style from ethanschoonover.com/solarized (c) Jeremy Hull + +*/ + +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + background: #fdf6e3; + color: #657b83; +} + +.hljs-comment, +.hljs-quote { + color: #93a1a1; +} + +/* Solarized Green */ +.hljs-keyword, +.hljs-selector-tag, +.hljs-addition { + color: #859900; +} + +/* Solarized Cyan */ +.hljs-number, +.hljs-string, +.hljs-meta .hljs-meta-string, +.hljs-literal, +.hljs-doctag, +.hljs-regexp { + color: #2aa198; +} + +/* Solarized Blue */ +.hljs-title, +.hljs-section, +.hljs-name, +.hljs-selector-id, +.hljs-selector-class { + color: #268bd2; +} + +/* Solarized Yellow */ +.hljs-attribute, +.hljs-attr, +.hljs-variable, +.hljs-template-variable, +.hljs-class .hljs-title, +.hljs-type { + color: #b58900; +} + +/* Solarized Orange */ +.hljs-symbol, +.hljs-bullet, +.hljs-subst, +.hljs-meta, +.hljs-meta .hljs-keyword, +.hljs-selector-attr, +.hljs-selector-pseudo, +.hljs-link { + color: #cb4b16; +} + +/* Solarized Red */ +.hljs-built_in, +.hljs-deletion { + color: #dc322f; +} + +.hljs-formula { + background: #eee8d5; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} diff --git a/packages/admin-web-angular/src/assets/lib/hljs/styles/sunburst.css b/packages/admin-web-angular/src/assets/lib/hljs/styles/sunburst.css new file mode 100644 index 0000000..7bf8eab --- /dev/null +++ b/packages/admin-web-angular/src/assets/lib/hljs/styles/sunburst.css @@ -0,0 +1,102 @@ +/* + +Sunburst-like style (c) Vasily Polovnyov + +*/ + +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + background: #000; + color: #f8f8f8; +} + +.hljs-comment, +.hljs-quote { + color: #aeaeae; + font-style: italic; +} + +.hljs-keyword, +.hljs-selector-tag, +.hljs-type { + color: #e28964; +} + +.hljs-string { + color: #65b042; +} + +.hljs-subst { + color: #daefa3; +} + +.hljs-regexp, +.hljs-link { + color: #e9c062; +} + +.hljs-title, +.hljs-section, +.hljs-tag, +.hljs-name { + color: #89bdff; +} + +.hljs-class .hljs-title, +.hljs-doctag { + text-decoration: underline; +} + +.hljs-symbol, +.hljs-bullet, +.hljs-number { + color: #3387cc; +} + +.hljs-params, +.hljs-variable, +.hljs-template-variable { + color: #3e87e3; +} + +.hljs-attribute { + color: #cda869; +} + +.hljs-meta { + color: #8996a8; +} + +.hljs-formula { + background-color: #0e2231; + color: #f8f8f8; + font-style: italic; +} + +.hljs-addition { + background-color: #253b22; + color: #f8f8f8; +} + +.hljs-deletion { + background-color: #420e09; + color: #f8f8f8; +} + +.hljs-selector-class { + color: #9b703f; +} + +.hljs-selector-id { + color: #8b98ab; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} diff --git a/packages/admin-web-angular/src/assets/lib/hljs/styles/tomorrow-night-blue.css b/packages/admin-web-angular/src/assets/lib/hljs/styles/tomorrow-night-blue.css new file mode 100644 index 0000000..ee6f253 --- /dev/null +++ b/packages/admin-web-angular/src/assets/lib/hljs/styles/tomorrow-night-blue.css @@ -0,0 +1,75 @@ +/* Tomorrow Night Blue Theme */ +/* http://jmblog.github.com/color-themes-for-google-code-highlightjs */ +/* Original theme - https://github.com/chriskempson/tomorrow-theme */ +/* http://jmblog.github.com/color-themes-for-google-code-highlightjs */ + +/* Tomorrow Comment */ +.hljs-comment, +.hljs-quote { + color: #7285b7; +} + +/* Tomorrow Red */ +.hljs-variable, +.hljs-template-variable, +.hljs-tag, +.hljs-name, +.hljs-selector-id, +.hljs-selector-class, +.hljs-regexp, +.hljs-deletion { + color: #ff9da4; +} + +/* Tomorrow Orange */ +.hljs-number, +.hljs-built_in, +.hljs-builtin-name, +.hljs-literal, +.hljs-type, +.hljs-params, +.hljs-meta, +.hljs-link { + color: #ffc58f; +} + +/* Tomorrow Yellow */ +.hljs-attribute { + color: #ffeead; +} + +/* Tomorrow Green */ +.hljs-string, +.hljs-symbol, +.hljs-bullet, +.hljs-addition { + color: #d1f1a9; +} + +/* Tomorrow Blue */ +.hljs-title, +.hljs-section { + color: #bbdaff; +} + +/* Tomorrow Purple */ +.hljs-keyword, +.hljs-selector-tag { + color: #ebbbff; +} + +.hljs { + display: block; + overflow-x: auto; + background: #002451; + color: white; + padding: 0.5em; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} diff --git a/packages/admin-web-angular/src/assets/lib/hljs/styles/tomorrow-night-bright.css b/packages/admin-web-angular/src/assets/lib/hljs/styles/tomorrow-night-bright.css new file mode 100644 index 0000000..a94c646 --- /dev/null +++ b/packages/admin-web-angular/src/assets/lib/hljs/styles/tomorrow-night-bright.css @@ -0,0 +1,74 @@ +/* Tomorrow Night Bright Theme */ +/* Original theme - https://github.com/chriskempson/tomorrow-theme */ +/* http://jmblog.github.com/color-themes-for-google-code-highlightjs */ + +/* Tomorrow Comment */ +.hljs-comment, +.hljs-quote { + color: #969896; +} + +/* Tomorrow Red */ +.hljs-variable, +.hljs-template-variable, +.hljs-tag, +.hljs-name, +.hljs-selector-id, +.hljs-selector-class, +.hljs-regexp, +.hljs-deletion { + color: #d54e53; +} + +/* Tomorrow Orange */ +.hljs-number, +.hljs-built_in, +.hljs-builtin-name, +.hljs-literal, +.hljs-type, +.hljs-params, +.hljs-meta, +.hljs-link { + color: #e78c45; +} + +/* Tomorrow Yellow */ +.hljs-attribute { + color: #e7c547; +} + +/* Tomorrow Green */ +.hljs-string, +.hljs-symbol, +.hljs-bullet, +.hljs-addition { + color: #b9ca4a; +} + +/* Tomorrow Blue */ +.hljs-title, +.hljs-section { + color: #7aa6da; +} + +/* Tomorrow Purple */ +.hljs-keyword, +.hljs-selector-tag { + color: #c397d8; +} + +.hljs { + display: block; + overflow-x: auto; + background: black; + color: #eaeaea; + padding: 0.5em; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} diff --git a/packages/admin-web-angular/src/assets/lib/hljs/styles/tomorrow-night-eighties.css b/packages/admin-web-angular/src/assets/lib/hljs/styles/tomorrow-night-eighties.css new file mode 100644 index 0000000..e0c8724 --- /dev/null +++ b/packages/admin-web-angular/src/assets/lib/hljs/styles/tomorrow-night-eighties.css @@ -0,0 +1,74 @@ +/* Tomorrow Night Eighties Theme */ +/* Original theme - https://github.com/chriskempson/tomorrow-theme */ +/* http://jmblog.github.com/color-themes-for-google-code-highlightjs */ + +/* Tomorrow Comment */ +.hljs-comment, +.hljs-quote { + color: #999999; +} + +/* Tomorrow Red */ +.hljs-variable, +.hljs-template-variable, +.hljs-tag, +.hljs-name, +.hljs-selector-id, +.hljs-selector-class, +.hljs-regexp, +.hljs-deletion { + color: #f2777a; +} + +/* Tomorrow Orange */ +.hljs-number, +.hljs-built_in, +.hljs-builtin-name, +.hljs-literal, +.hljs-type, +.hljs-params, +.hljs-meta, +.hljs-link { + color: #f99157; +} + +/* Tomorrow Yellow */ +.hljs-attribute { + color: #ffcc66; +} + +/* Tomorrow Green */ +.hljs-string, +.hljs-symbol, +.hljs-bullet, +.hljs-addition { + color: #99cc99; +} + +/* Tomorrow Blue */ +.hljs-title, +.hljs-section { + color: #6699cc; +} + +/* Tomorrow Purple */ +.hljs-keyword, +.hljs-selector-tag { + color: #cc99cc; +} + +.hljs { + display: block; + overflow-x: auto; + background: #2d2d2d; + color: #cccccc; + padding: 0.5em; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} diff --git a/packages/admin-web-angular/src/assets/lib/hljs/styles/tomorrow-night.css b/packages/admin-web-angular/src/assets/lib/hljs/styles/tomorrow-night.css new file mode 100644 index 0000000..775273f --- /dev/null +++ b/packages/admin-web-angular/src/assets/lib/hljs/styles/tomorrow-night.css @@ -0,0 +1,75 @@ +/* Tomorrow Night Theme */ +/* http://jmblog.github.com/color-themes-for-google-code-highlightjs */ +/* Original theme - https://github.com/chriskempson/tomorrow-theme */ +/* http://jmblog.github.com/color-themes-for-google-code-highlightjs */ + +/* Tomorrow Comment */ +.hljs-comment, +.hljs-quote { + color: #969896; +} + +/* Tomorrow Red */ +.hljs-variable, +.hljs-template-variable, +.hljs-tag, +.hljs-name, +.hljs-selector-id, +.hljs-selector-class, +.hljs-regexp, +.hljs-deletion { + color: #cc6666; +} + +/* Tomorrow Orange */ +.hljs-number, +.hljs-built_in, +.hljs-builtin-name, +.hljs-literal, +.hljs-type, +.hljs-params, +.hljs-meta, +.hljs-link { + color: #de935f; +} + +/* Tomorrow Yellow */ +.hljs-attribute { + color: #f0c674; +} + +/* Tomorrow Green */ +.hljs-string, +.hljs-symbol, +.hljs-bullet, +.hljs-addition { + color: #b5bd68; +} + +/* Tomorrow Blue */ +.hljs-title, +.hljs-section { + color: #81a2be; +} + +/* Tomorrow Purple */ +.hljs-keyword, +.hljs-selector-tag { + color: #b294bb; +} + +.hljs { + display: block; + overflow-x: auto; + background: #1d1f21; + color: #c5c8c6; + padding: 0.5em; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} diff --git a/packages/admin-web-angular/src/assets/lib/hljs/styles/tomorrow.css b/packages/admin-web-angular/src/assets/lib/hljs/styles/tomorrow.css new file mode 100644 index 0000000..96c3f61 --- /dev/null +++ b/packages/admin-web-angular/src/assets/lib/hljs/styles/tomorrow.css @@ -0,0 +1,72 @@ +/* http://jmblog.github.com/color-themes-for-google-code-highlightjs */ + +/* Tomorrow Comment */ +.hljs-comment, +.hljs-quote { + color: #8e908c; +} + +/* Tomorrow Red */ +.hljs-variable, +.hljs-template-variable, +.hljs-tag, +.hljs-name, +.hljs-selector-id, +.hljs-selector-class, +.hljs-regexp, +.hljs-deletion { + color: #c82829; +} + +/* Tomorrow Orange */ +.hljs-number, +.hljs-built_in, +.hljs-builtin-name, +.hljs-literal, +.hljs-type, +.hljs-params, +.hljs-meta, +.hljs-link { + color: #f5871f; +} + +/* Tomorrow Yellow */ +.hljs-attribute { + color: #eab700; +} + +/* Tomorrow Green */ +.hljs-string, +.hljs-symbol, +.hljs-bullet, +.hljs-addition { + color: #718c00; +} + +/* Tomorrow Blue */ +.hljs-title, +.hljs-section { + color: #4271ae; +} + +/* Tomorrow Purple */ +.hljs-keyword, +.hljs-selector-tag { + color: #8959a8; +} + +.hljs { + display: block; + overflow-x: auto; + background: white; + color: #4d4d4c; + padding: 0.5em; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} diff --git a/packages/admin-web-angular/src/assets/lib/hljs/styles/vs.css b/packages/admin-web-angular/src/assets/lib/hljs/styles/vs.css new file mode 100644 index 0000000..22a6927 --- /dev/null +++ b/packages/admin-web-angular/src/assets/lib/hljs/styles/vs.css @@ -0,0 +1,67 @@ +/* + +Visual Studio-like style based on original C# coloring by Jason Diamond + +*/ +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + background: white; + color: black; +} + +.hljs-comment, +.hljs-quote, +.hljs-variable { + color: #008000; +} + +.hljs-keyword, +.hljs-selector-tag, +.hljs-built_in, +.hljs-name, +.hljs-tag { + color: #00f; +} + +.hljs-string, +.hljs-title, +.hljs-section, +.hljs-attribute, +.hljs-literal, +.hljs-template-tag, +.hljs-template-variable, +.hljs-type, +.hljs-addition { + color: #a31515; +} + +.hljs-deletion, +.hljs-selector-attr, +.hljs-selector-pseudo, +.hljs-meta { + color: #2b91af; +} + +.hljs-doctag { + color: #808080; +} + +.hljs-attr { + color: #f00; +} + +.hljs-symbol, +.hljs-bullet, +.hljs-link { + color: #00b0e8; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} diff --git a/packages/admin-web-angular/src/assets/lib/hljs/styles/vs2015.css b/packages/admin-web-angular/src/assets/lib/hljs/styles/vs2015.css new file mode 100644 index 0000000..64ae717 --- /dev/null +++ b/packages/admin-web-angular/src/assets/lib/hljs/styles/vs2015.css @@ -0,0 +1,115 @@ +/* + * Visual Studio 2015 dark style + * Author: Nicolas LLOBERA + */ + +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + background: #1e1e1e; + color: #dcdcdc; +} + +.hljs-keyword, +.hljs-literal, +.hljs-symbol, +.hljs-name { + color: #569cd6; +} +.hljs-link { + color: #569cd6; + text-decoration: underline; +} + +.hljs-built_in, +.hljs-type { + color: #4ec9b0; +} + +.hljs-number, +.hljs-class { + color: #b8d7a3; +} + +.hljs-string, +.hljs-meta-string { + color: #d69d85; +} + +.hljs-regexp, +.hljs-template-tag { + color: #9a5334; +} + +.hljs-subst, +.hljs-function, +.hljs-title, +.hljs-params, +.hljs-formula { + color: #dcdcdc; +} + +.hljs-comment, +.hljs-quote { + color: #57a64a; + font-style: italic; +} + +.hljs-doctag { + color: #608b4e; +} + +.hljs-meta, +.hljs-meta-keyword, +.hljs-tag { + color: #9b9b9b; +} + +.hljs-variable, +.hljs-template-variable { + color: #bd63c5; +} + +.hljs-attr, +.hljs-attribute, +.hljs-builtin-name { + color: #9cdcfe; +} + +.hljs-section { + color: gold; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} + +/*.hljs-code { + font-family:'Monospace'; +}*/ + +.hljs-bullet, +.hljs-selector-tag, +.hljs-selector-id, +.hljs-selector-class, +.hljs-selector-attr, +.hljs-selector-pseudo { + color: #d7ba7d; +} + +.hljs-addition { + background-color: #144212; + display: inline-block; + width: 100%; +} + +.hljs-deletion { + background-color: #600; + display: inline-block; + width: 100%; +} diff --git a/packages/admin-web-angular/src/assets/lib/hljs/styles/xcode.css b/packages/admin-web-angular/src/assets/lib/hljs/styles/xcode.css new file mode 100644 index 0000000..3e0e734 --- /dev/null +++ b/packages/admin-web-angular/src/assets/lib/hljs/styles/xcode.css @@ -0,0 +1,103 @@ +/* + +XCode style (c) Angel Garcia + +*/ + +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + background: #fff; + color: black; +} + +/* Gray DOCTYPE selectors like WebKit */ +.xml .hljs-meta { + color: #c0c0c0; +} + +.hljs-comment, +.hljs-quote { + color: #007400; +} + +.hljs-tag, +.hljs-attribute, +.hljs-keyword, +.hljs-selector-tag, +.hljs-literal, +.hljs-name { + color: #aa0d91; +} + +.hljs-variable, +.hljs-template-variable { + color: #3f6e74; +} + +.hljs-code, +.hljs-string, +.hljs-meta-string { + color: #c41a16; +} + +.hljs-regexp, +.hljs-link { + color: #0e0eff; +} + +.hljs-title, +.hljs-symbol, +.hljs-bullet, +.hljs-number { + color: #1c00cf; +} + +.hljs-section, +.hljs-meta { + color: #643820; +} + +.hljs-class .hljs-title, +.hljs-type, +.hljs-built_in, +.hljs-builtin-name, +.hljs-params { + color: #5c2699; +} + +.hljs-attr { + color: #836c28; +} + +.hljs-subst { + color: #000; +} + +.hljs-formula { + background-color: #eee; + font-style: italic; +} + +.hljs-addition { + background-color: #baeeba; +} + +.hljs-deletion { + background-color: #ffc8bd; +} + +.hljs-selector-id, +.hljs-selector-class { + color: #9b703f; +} + +.hljs-doctag, +.hljs-strong { + font-weight: bold; +} + +.hljs-emphasis { + font-style: italic; +} diff --git a/packages/admin-web-angular/src/assets/lib/hljs/styles/xt256.css b/packages/admin-web-angular/src/assets/lib/hljs/styles/xt256.css new file mode 100644 index 0000000..88e2c5f --- /dev/null +++ b/packages/admin-web-angular/src/assets/lib/hljs/styles/xt256.css @@ -0,0 +1,91 @@ +/* + xt256.css + + Contact: initbar [at] protonmail [dot] ch + : github.com/initbar +*/ + +.hljs { + display: block; + overflow-x: auto; + color: #eaeaea; + background: #000; + padding: 0.5; +} + +.hljs-subst { + color: #eaeaea; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} + +.hljs-builtin-name, +.hljs-type { + color: #eaeaea; +} + +.hljs-params { + color: #da0000; +} + +.hljs-literal, +.hljs-number, +.hljs-name { + color: #ff0000; + font-weight: bolder; +} + +.hljs-comment { + color: #969896; +} + +.hljs-selector-id, +.hljs-quote { + color: #00ffff; +} + +.hljs-template-variable, +.hljs-variable, +.hljs-title { + color: #00ffff; + font-weight: bold; +} + +.hljs-selector-class, +.hljs-keyword, +.hljs-symbol { + color: #fff000; +} + +.hljs-string, +.hljs-bullet { + color: #00ff00; +} + +.hljs-tag, +.hljs-section { + color: #000fff; +} + +.hljs-selector-tag { + color: #000fff; + font-weight: bold; +} + +.hljs-attribute, +.hljs-built_in, +.hljs-regexp, +.hljs-link { + color: #ff00ff; +} + +.hljs-meta { + color: #fff; + font-weight: bolder; +} diff --git a/packages/admin-web-angular/src/assets/lib/hljs/styles/zenburn.css b/packages/admin-web-angular/src/assets/lib/hljs/styles/zenburn.css new file mode 100644 index 0000000..c5fadb7 --- /dev/null +++ b/packages/admin-web-angular/src/assets/lib/hljs/styles/zenburn.css @@ -0,0 +1,79 @@ +/* + +Zenburn style from voldmar.ru (c) Vladimir Epifanov +based on dark.css by Ivan Sagalaev + +*/ + +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + background: #3f3f3f; + color: #dcdcdc; +} + +.hljs-keyword, +.hljs-selector-tag, +.hljs-tag { + color: #e3ceab; +} + +.hljs-template-tag { + color: #dcdcdc; +} + +.hljs-number { + color: #8cd0d3; +} + +.hljs-variable, +.hljs-template-variable, +.hljs-attribute { + color: #efdcbc; +} + +.hljs-literal { + color: #efefaf; +} + +.hljs-subst { + color: #8f8f8f; +} + +.hljs-title, +.hljs-name, +.hljs-selector-id, +.hljs-selector-class, +.hljs-section, +.hljs-type { + color: #efef8f; +} + +.hljs-symbol, +.hljs-bullet, +.hljs-link { + color: #dca3a3; +} + +.hljs-deletion, +.hljs-string, +.hljs-built_in, +.hljs-builtin-name { + color: #cc9393; +} + +.hljs-addition, +.hljs-comment, +.hljs-quote, +.hljs-meta { + color: #7f9f7f; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} diff --git a/packages/admin-web-angular/src/assets/map/world.json b/packages/admin-web-angular/src/assets/map/world.json new file mode 100644 index 0000000..db0246c --- /dev/null +++ b/packages/admin-web-angular/src/assets/map/world.json @@ -0,0 +1,32127 @@ +{ + "type": "FeatureCollection", + "crs": { + "type": "name", + "properties": { "name": "urn:ogc:def:crs:OGC:1.3:CRS84" } + }, + "features": [ + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [47.97822265625001, 7.9970703125], + [46.97822265625001, 7.9970703125], + [43.98378906250002, 9.008837890624989], + [43.482519531250006, 9.379492187499991], + [43.181640625, 9.879980468749991], + [42.84160156250002, 10.203076171874997], + [42.65644531250001, 10.6], + [42.92275390625002, 10.999316406249989], + [43.24599609375002, 11.499804687499989], + [43.85273437500001, 10.784277343749991], + [44.38652343750002, 10.430224609374989], + [44.94296875, 10.43671875], + [45.81669921875002, 10.835888671874997], + [46.565039062500006, 10.745996093749994], + [47.40498046875001, 11.174023437499997], + [48.01923828125001, 11.139355468749997], + [48.57255859375002, 11.320507812499997], + [48.938574218750006, 11.258447265624994], + [50.11005859375001, 11.529296875], + [50.79228515625002, 11.983691406249989], + [51.2548828125, 11.830712890624994], + [51.08427734375002, 11.335644531249997], + [51.140625, 10.656884765624994], + [51.031835937500006, 10.444775390624997], + [51.19296875, 10.554638671874997], + [51.390234375, 10.422607421875], + [50.93007812500002, 10.33554687499999], + [50.825, 9.428173828124997], + [50.10283203125002, 8.199804687499991], + [49.85205078125, 7.962548828124994], + [49.234960937500006, 6.77734375], + [49.04931640625, 6.173632812499989], + [47.97529296875001, 4.497021484374997], + [46.87880859375002, 3.28564453125], + [46.05117187500002, 2.475146484374989], + [44.92021484375002, 1.81015625], + [43.71757812500002, 0.857861328124997], + [41.97988281250002, -0.973046875], + [41.53271484375, -1.6953125], + [41.521875, -1.572265625], + [41.42695312500001, -1.449511718750003], + [41.24980468750002, -1.220507812500003], + [40.97871093750001, -0.870312500000011], + [40.964453125, 2.814648437499997], + [41.341796875, 3.20166015625], + [41.61347656250001, 3.590478515624994], + [41.88398437500001, 3.977734375], + [41.91533203125002, 4.031298828124989], + [42.02412109375001, 4.137939453125], + [42.85664062500001, 4.32421875], + [43.12568359375001, 4.644482421874997], + [43.58349609375, 4.85498046875], + [43.988867187500006, 4.950537109374991], + [44.940527343750006, 4.912011718749994], + [47.97822265625001, 7.9970703125] + ] + ] + }, + "properties": { "name": "Somalia", "childNum": 1 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [9.579979133936737, 47.05856388629306], + [9.409458596647225, 47.02019676540292], + [9.46249431093294, 47.09010747968864], + [9.46249431093294, 47.19858962254578], + [9.527658197470123, 47.27026989773668], + [9.579979133936737, 47.05856388629306] + ] + ] + }, + "properties": { "name": "Liechtenstein", "childNum": 1 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [-8.683349609375, 27.77800740805682], + [-13.038761787013554, 27.81190166624856], + [-12.948925781249926, 27.914160156250034], + [-11.552685546874955, 28.31010742187496], + [-10.486474609374994, 29.06494140625], + [-10.200585937499994, 29.380371093750057], + [-9.667089843749949, 30.10927734375005], + [-9.652929687499977, 30.447558593750045], + [-9.875488281249943, 30.717919921874966], + [-9.80869140624992, 31.42460937499996], + [-9.347460937499932, 32.086376953124955], + [-9.245849609375, 32.572460937499955], + [-8.512841796874994, 33.25244140625003], + [-6.900976562499949, 33.96904296874999], + [-6.353125, 34.77607421875001], + [-5.924804687499943, 35.78579101562502], + [-5.277832031249943, 35.90273437500002], + [-5.252685546874972, 35.61474609374997], + [-4.628320312499966, 35.206396484375006], + [-4.329980468749937, 35.161474609375006], + [-3.693261718749994, 35.27998046874998], + [-3.394726562499926, 35.21181640625005], + [-2.972216796874989, 35.40727539062499], + [-2.839941406249949, 35.127832031249994], + [-2.731396484374955, 35.13520507812498], + [-2.636816406249977, 35.11269531250002], + [-2.423730468749994, 35.12348632812498], + [-2.219628906249966, 35.10419921874998], + [-1.795605468749926, 34.751904296874955], + [-1.67919921875, 33.31865234375002], + [-1.550732421874955, 33.073583984375006], + [-1.510009765625, 32.877636718749955], + [-1.45, 32.784814453124966], + [-1.352148437499977, 32.70336914062497], + [-1.29638671875, 32.67568359375002], + [-1.188232421875, 32.608496093750006], + [-1.111035156249983, 32.55229492187502], + [-1.065527343749949, 32.46831054687496], + [-1.16259765625, 32.399169921875], + [-1.275341796874983, 32.089013671874966], + [-2.863427734374937, 32.07470703124997], + [-2.930859374999926, 32.04252929687499], + [-2.988232421874983, 31.874218749999983], + [-3.01738281249996, 31.834277343750017], + [-3.439794921874949, 31.704541015624983], + [-3.604589843749949, 31.686767578125], + [-3.700244140624989, 31.70009765625005], + [-3.768164062499977, 31.689550781250034], + [-3.837109374999983, 31.512353515624994], + [-3.833398437499937, 31.197802734375045], + [-3.626904296874955, 31.000927734374983], + [-4.148779296874977, 30.8095703125], + [-4.322851562500006, 30.698876953124994], + [-4.52915039062492, 30.62553710937499], + [-4.778515624999926, 30.552392578124994], + [-4.968261718749943, 30.465380859375045], + [-5.061914062499937, 30.326416015625057], + [-5.180126953124955, 30.166162109374994], + [-5.293652343749983, 30.058642578125045], + [-5.44877929687496, 29.956933593750023], + [-6.00429687499999, 29.83125], + [-6.479736328124943, 29.82036132812499], + [-6.520556640624989, 29.659863281249983], + [-6.59775390624992, 29.578955078125006], + [-6.635351562499949, 29.568798828124983], + [-6.755126953125, 29.583837890625034], + [-6.855566406249949, 29.601611328125017], + [-7.142431640624949, 29.61958007812504], + [-7.427685546874983, 29.425], + [-7.485742187499994, 29.392236328124994], + [-8.659912109375, 28.718603515625063], + [-8.683349609375, 27.900390625], + [-8.683349609375, 27.77800740805682] + ] + ] + }, + "properties": { "name": "Morocco", "childNum": 1 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [-13.038761787013554, 27.81190166624856], + [-8.683349609375, 27.77800740805682], + [-8.683349609375, 27.65644531250004], + [-8.817822265624955, 27.65644531250004], + [-8.817822265624951, 27.656445312499997], + [-8.683349609375, 27.656445312499997], + [-8.683349609375, 27.2859375], + [-8.682861328125, 26.921337890624997], + [-8.6826171875, 26.72314453125], + [-8.682324218749983, 26.497705078124994], + [-8.68212890625, 26.273193359375], + [-8.68212890625, 26.10947265625], + [-8.682226562499977, 25.995507812499994], + [-12.016308593749983, 25.995410156250003], + [-12.016308593749983, 25.740136718749994], + [-12.016308593749983, 25.331689453124994], + [-12.016308593749983, 25.059375], + [-12.016308593749983, 24.923242187499994], + [-12.016308593749983, 24.378662109375], + [-12.016308593749983, 23.97021484375], + [-12.0234375, 23.467578125], + [-12.372900390624977, 23.318017578124994], + [-12.559375, 23.290820312500003], + [-12.620410156249989, 23.27133789062499], + [-13.031494140625, 23.000244140625], + [-13.153271484374983, 22.820507812499997], + [-13.12702845982141, 22.703770926339278], + [-13.136540684091575, 22.708182548616723], + [-13.094335937499977, 22.495996093749994], + [-13.051220703124983, 21.854785156250003], + [-13.041748046875, 21.713818359374997], + [-13.0322265625, 21.572070312500003], + [-13.025097656249983, 21.466796875], + [-13.016210937499977, 21.333935546874997], + [-15.231201171875, 21.331298828125], + [-16.964550781249983, 21.329248046874994], + [-17.06396484375, 20.89882812499999], + [-17.048046874999983, 20.80615234375], + [-17.098779296874994, 20.856884765624997], + [-16.930859374999983, 21.9], + [-16.35874023437495, 22.594531250000045], + [-16.21025390624999, 23.097900390625], + [-15.789257812499926, 23.792871093750023], + [-15.980712890624943, 23.670312500000023], + [-15.899316406249966, 23.844433593749955], + [-14.904296875000028, 24.719775390625017], + [-14.794921874999943, 25.404150390625006], + [-14.413867187499932, 26.25371093749999], + [-13.57578125, 26.735107421875], + [-13.175976562499983, 27.655712890624983], + [-13.038761787013554, 27.81190166624856] + ], + [ + [-8.774365234374983, 27.460546875], + [-8.794873046874983, 27.120703125000034], + [-8.794873046874983, 27.120703125], + [-8.774365234374983, 27.460546875] + ] + ] + }, + "properties": { "name": "W. Sahara", "childNum": 2 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [21.5625, 42.247509765625], + [21.560839843750017, 42.24765625], + [21.389550781250023, 42.21982421875], + [21.28662109375, 42.100390625], + [21.05976562500001, 42.171289062499994], + [20.778125, 42.071044921875], + [20.725, 41.87353515625], + [20.566210937500017, 41.873681640624994], + [20.485449218750006, 42.223388671875], + [20.06396484375, 42.54726562499999], + [20.054296875, 42.760058593749996], + [20.344335937500006, 42.827929687499996], + [20.40996305279786, 42.84373166741877], + [20.344335937500063, 42.82792968750002], + [19.670996093750006, 43.163964843749994], + [19.21875, 43.449951171875], + [19.196484375000068, 43.48500976562502], + [19.19160156250004, 43.52104492187499], + [19.19433593749997, 43.533300781250006], + [19.495117187500057, 43.642871093750045], + [19.245019531249994, 43.96503906250004], + [19.583789062500017, 44.04345703125003], + [19.118457031250074, 44.359960937500006], + [19.348632812500057, 44.88090820312502], + [19.007128906250045, 44.86918945312502], + [19.062890625000023, 45.13720703125], + [19.4, 45.2125], + [19.004687500000074, 45.39951171875006], + [19.064257812500045, 45.51499023437506], + [18.839062499999983, 45.83574218750002], + [18.905371093750006, 45.931738281250034], + [19.421289062500023, 46.064453125], + [19.61347656250001, 46.169189453125], + [19.84443359375001, 46.145898437499966], + [19.934082031250057, 46.161474609375034], + [20.161425781250017, 46.14189453124996], + [20.210156250000068, 46.12602539062502], + [20.241796875000034, 46.10859375000001], + [20.301367187500006, 46.05068359375002], + [20.35859375000004, 45.975488281249994], + [20.581152343749977, 45.86948242187506], + [20.65273437499999, 45.779394531250006], + [20.709277343750074, 45.735253906249994], + [20.727832031250017, 45.73740234374998], + [20.746875, 45.74897460937501], + [20.76015625000005, 45.75810546875002], + [20.775, 45.74980468750002], + [20.794042968750006, 45.467871093750034], + [21.431445312500017, 45.192529296874994], + [21.465429687500006, 45.171875], + [21.357031250000034, 44.99077148437502], + [21.532324218750063, 44.900683593750045], + [21.519921875000023, 44.88081054687498], + [21.442187500000074, 44.87338867187498], + [21.384375, 44.87006835937501], + [21.357910156250057, 44.86181640625003], + [21.36005859375004, 44.82666015624997], + [21.52314453125004, 44.79008789062499], + [21.63613281250005, 44.71044921875], + [21.909277343750034, 44.666113281250034], + [22.026953125, 44.61987304687503], + [22.093066406250074, 44.541943359374955], + [22.200976562500017, 44.560693359374966], + [22.350683593750063, 44.676123046875034], + [22.497656249999977, 44.70625], + [22.64208984375, 44.65097656249998], + [22.720898437499983, 44.605517578125045], + [22.734375, 44.56992187499998], + [22.700781250000063, 44.55551757812498], + [22.620117187500057, 44.562353515625034], + [22.554003906250017, 44.54033203124999], + [22.49453125000005, 44.43544921875002], + [22.687890625000023, 44.248291015625], + [22.42080078125005, 44.00742187500006], + [22.399023437500063, 43.96953125], + [22.36542968750004, 43.86210937500002], + [22.36962890625003, 43.78129882812499], + [22.55458984375005, 43.45449218750002], + [22.767578125, 43.35415039062502], + [22.81972656250005, 43.300732421874955], + [22.85957031250001, 43.252343749999966], + [22.97685546874999, 43.18798828125], + [22.799902343750006, 42.985742187499994], + [22.706152343750006, 42.88393554687505], + [22.466796875, 42.842480468749955], + [22.53242187500004, 42.48120117187497], + [22.523535156250006, 42.440966796875045], + [22.44570312500005, 42.35913085937497], + [22.42207031250004, 42.32885742187503], + [22.344042968750045, 42.31396484375003], + [22.23974609375003, 42.303110028468716], + [21.81464843750001, 42.303125], + [21.5625, 42.24750976562498], + [21.5625, 42.247509765625] + ] + ] + }, + "properties": { "name": "Serbia", "childNum": 1 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [74.54140625000002, 37.02216796875], + [74.03886718750002, 36.825732421874996], + [73.116796875, 36.868554687499994], + [72.24980468750002, 36.734716796875], + [71.23291015625, 36.12177734375], + [71.18505859375, 36.04208984375], + [71.57197265625001, 35.546826171875], + [71.62050781250002, 35.183007812499994], + [70.965625, 34.53037109375], + [71.095703125, 34.369433593749996], + [71.05156250000002, 34.049707031249994], + [70.65400390625001, 33.952294921874994], + [69.8896484375, 34.007275390625], + [70.26113281250002, 33.289013671875], + [69.5015625, 33.020068359374996], + [69.24140625000001, 32.433544921875], + [69.279296875, 31.936816406249996], + [68.86894531250002, 31.634228515624997], + [68.59765625, 31.802978515625], + [68.16103515625002, 31.802978515625], + [67.57822265625, 31.506494140624994], + [67.737890625, 31.343945312499997], + [67.45283203125001, 31.234619140625], + [66.82929687500001, 31.263671875], + [66.346875, 30.802783203124996], + [66.23125, 29.86572265625], + [65.09550781250002, 29.559472656249994], + [64.39375, 29.544335937499994], + [64.09873046875, 29.391943359375], + [63.56757812500001, 29.497998046874997], + [62.4765625, 29.408349609374994], + [62.0009765625, 29.530419921874994], + [61.22441406250002, 29.749414062499994], + [60.843359375, 29.858691406249996], + [61.331640625, 30.363720703124997], + [61.55947265625002, 30.599365234375], + [61.7841796875, 30.831933593749994], + [61.81083984375002, 30.91328125], + [61.81425781250002, 31.072558593749996], + [61.75507812500001, 31.285302734374994], + [61.66015625, 31.382421875], + [61.34648437500002, 31.421630859375], + [61.11074218750002, 31.451123046874997], + [60.854101562500006, 31.483251953125], + [60.82070312500002, 31.495166015624996], + [60.791601562500006, 31.660595703124997], + [60.804296875, 31.73447265625], + [60.7875, 31.877197265625], + [60.78994140625002, 31.987109375], + [60.827246093750006, 32.16796875], + [60.82929687500001, 32.249414062499994], + [60.71044921875, 32.6], + [60.57656250000002, 32.994873046875], + [60.560546875, 33.137841796874994], + [60.9169921875, 33.505224609375], + [60.573828125, 33.588330078125], + [60.4859375, 33.7119140625], + [60.48574218750002, 34.094775390624996], + [60.642675781250006, 34.307177734374996], + [60.88945312500002, 34.31943359375], + [60.80390625000001, 34.418017578124996], + [60.76259765625002, 34.475244140624994], + [60.73613281250002, 34.491796875], + [60.72626953125001, 34.51826171875], + [60.73945312500001, 34.544726562499996], + [60.80234375, 34.554638671875], + [60.8453125, 34.587695312499996], + [60.91474609375001, 34.633984375], + [60.951171875, 34.653857421874996], + [61.080078125, 34.855615234374994], + [61.1, 35.272314453125], + [61.18925781250002, 35.31201171875], + [61.24550781250002, 35.474072265625], + [61.27851562500001, 35.51376953125], + [61.281835937500006, 35.55341796875], + [61.26201171875002, 35.619580078125], + [61.3447265625, 35.6294921875], + [61.62099609375002, 35.43232421875], + [62.08964843750002, 35.3796875], + [62.30781250000001, 35.170800781249994], + [62.688085937500006, 35.255322265625], + [63.056640625, 35.44580078125], + [63.08417968750001, 35.56806640625], + [63.16972656250002, 35.678125], + [63.129980468750006, 35.84619140625], + [63.8625, 36.012353515624994], + [64.184375, 36.14892578125], + [64.51103515625002, 36.340673828125], + [64.56582031250002, 36.427587890625], + [64.6025390625, 36.554541015625], + [64.78242187500001, 37.05927734375], + [64.81630859375002, 37.132080078125], + [64.95156250000002, 37.1935546875], + [65.08964843750002, 37.237939453124994], + [65.30361328125002, 37.24677734375], + [65.55498046875002, 37.251171875], + [65.76503906250002, 37.569140625], + [66.471875, 37.3447265625], + [66.52226562500002, 37.348486328125], + [66.827734375, 37.3712890625], + [67.06884765625, 37.334814453125], + [67.19550781250001, 37.235205078125], + [67.31972656250002, 37.2095703125], + [67.44169921875002, 37.2580078125], + [67.51728515625001, 37.266650390624996], + [67.546484375, 37.235644531249996], + [67.607421875, 37.222509765625], + [67.7, 37.22724609375], + [67.7529296875, 37.1998046875], + [67.75898437500001, 37.172216796875], + [67.76601562500002, 37.14013671875], + [67.83447265625, 37.064208984375], + [67.9580078125, 36.972021484375], + [68.06777343750002, 36.9498046875], + [68.26093750000001, 37.013085937499994], + [68.284765625, 37.036328125], + [68.29951171875001, 37.088427734374996], + [68.38691406250001, 37.1375], + [68.66914062500001, 37.2583984375], + [68.7232421875, 37.268017578125], + [68.78203125000002, 37.2580078125], + [68.82373046875, 37.270703125], + [68.8384765625, 37.30283203125], + [68.85537109375002, 37.316845703125], + [68.88525390625, 37.328076171875], + [68.91181640625001, 37.333935546875], + [68.96044921875, 37.325048828125], + [69.18017578125, 37.15830078125], + [69.26484375000001, 37.1083984375], + [69.30390625000001, 37.116943359375], + [69.35380859375002, 37.150048828124994], + [69.41445312500002, 37.207763671875], + [69.4296875, 37.290869140625], + [69.39921875000002, 37.399316406249994], + [69.42011718750001, 37.48671875], + [69.49208984375002, 37.553076171875], + [69.62578125000002, 37.594042968749996], + [69.8208984375, 37.6095703125], + [69.9849609375, 37.566162109375], + [70.18867187500001, 37.582470703125], + [70.25146484375, 37.66416015625], + [70.25498046875, 37.765380859375], + [70.19941406250001, 37.886035156249996], + [70.21464843750002, 37.9244140625], + [70.41777343750002, 38.075439453125], + [70.7359375, 38.42255859375], + [71.255859375, 38.306982421875], + [71.33271484375001, 38.170263671875], + [71.27851562500001, 37.918408203125], + [71.319921875, 37.90185546875], + [71.3896484375, 37.906298828124996], + [71.48779296875, 37.931884765625], + [71.55195312500001, 37.933154296874996], + [71.58222656250001, 37.910107421875], + [71.43291015625002, 37.1275390625], + [71.530859375, 36.845117187499994], + [71.665625, 36.696923828124994], + [72.65742187500001, 37.029052734375], + [72.8955078125, 37.267529296875], + [73.21113281250001, 37.408496093749996], + [73.38291015625, 37.462255859375], + [73.48134765625002, 37.4716796875], + [73.60468750000001, 37.446044921875], + [73.65712890625002, 37.43046875], + [73.72060546875002, 37.41875], + [73.73378906250002, 37.37578125], + [73.71728515625, 37.329443359375], + [73.6275390625, 37.261572265625], + [73.65351562500001, 37.23935546875], + [73.749609375, 37.231787109375], + [74.16708984375, 37.329443359375], + [74.20351562500002, 37.372460937499994], + [74.25966796875002, 37.415429687499994], + [74.659375, 37.394482421875], + [74.73056640625, 37.35703125], + [74.83046875000002, 37.2859375], + [74.89130859375001, 37.231640625], + [74.84023437500002, 37.225048828125], + [74.76738281250002, 37.249169921874994], + [74.73896484375001, 37.28564453125], + [74.72666015625, 37.29072265625], + [74.6689453125, 37.26669921875], + [74.55898437500002, 37.236621093749996], + [74.37216796875, 37.15771484375], + [74.37617187500001, 37.137353515624994], + [74.49794921875002, 37.0572265625], + [74.52646484375, 37.030664062499994], + [74.54140625000002, 37.02216796875] + ] + ] + }, + "properties": { "name": "Afghanistan", "childNum": 1 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [17.57958984375, -8.099023437500009], + [17.643359375000017, -8.090722656250009], + [18.00878906250003, -8.107617187499983], + [18.56269531250001, -7.9359375], + [18.944433593750063, -8.001464843750028], + [19.142675781250034, -8.001464843750028], + [19.34082031249997, -7.966601562500031], + [19.369921875000045, -7.706542968749986], + [19.371679687500063, -7.655078124999989], + [19.47988281250008, -7.472167968750028], + [19.48378906250008, -7.279492187500026], + [19.527636718750017, -7.144433593749952], + [19.87519531250004, -6.986328124999986], + [19.99746093750008, -6.976464843750023], + [20.190039062500063, -6.9462890625], + [20.482226562500074, -6.915820312500017], + [20.59003906250001, -6.919921874999957], + [20.598730468750006, -6.935156249999949], + [20.536914062500045, -7.121777343749955], + [20.535839843749983, -7.182812499999955], + [20.558398437500045, -7.244433593749989], + [20.60781250000008, -7.277734375000023], + [20.910937500000017, -7.281445312499983], + [21.190332031250023, -7.284960937499989], + [21.751074218750034, -7.305468749999989], + [21.80605468750005, -7.32861328125], + [21.905371093750034, -8.693359374999943], + [21.813183593750068, -9.46875], + [22.19775390625, -10.040625], + [22.30703125000005, -10.691308593750023], + [22.203515625000023, -10.829492187500009], + [22.226171875, -11.121972656250009], + [22.27880859375, -11.19414062499996], + [22.314941406250057, -11.198632812499994], + [22.39296875000005, -11.159472656250003], + [22.486132812500045, -11.086718750000017], + [22.56103515625003, -11.05585937500004], + [22.814746093750017, -11.08027343750004], + [23.076269531250006, -11.087890624999986], + [23.463964843750034, -10.969335937499991], + [23.83388671875008, -11.013671874999972], + [23.96650390625001, -10.871777343750011], + [23.98388671875, -11.725], + [23.909375, -12.636132812500009], + [23.886523437500045, -12.743261718749991], + [23.882421875, -12.799023437499983], + [23.968066406250045, -12.956933593749994], + [23.962988281250006, -12.988476562500026], + [23.843164062500023, -13.0009765625], + [22.209570312500006, -13.0009765625], + [21.97890625000008, -13.0009765625], + [21.979101562500034, -13.798730468749994], + [21.979296875000074, -14.11962890625], + [21.979394531249994, -14.440527343750006], + [21.97978515624999, -15.955566406250014], + [22.193945312500006, -16.628125], + [23.380664062500017, -17.640625], + [22.32421875, -17.8375], + [20.74550781250008, -18.019726562499983], + [20.194335937500057, -17.86367187499999], + [18.95527343750004, -17.80351562499999], + [18.39638671875005, -17.3994140625], + [16.14843750000003, -17.39023437499999], + [14.017480468750023, -17.40888671874997], + [13.475976562500023, -17.04003906249997], + [13.179492187500017, -16.971679687499986], + [12.548144531250017, -17.212695312499974], + [12.35927734375008, -17.205859375], + [12.318457031250006, -17.21337890625003], + [12.213378906250028, -17.209960937500043], + [12.013964843750074, -17.168554687500034], + [11.902539062500011, -17.226562499999957], + [11.743066406250023, -17.24921875000004], + [11.780078125000017, -16.87128906249997], + [11.818945312500034, -16.704101562500014], + [11.750878906250023, -15.831933593749966], + [12.016113281250057, -15.513671874999957], + [12.55048828125004, -13.437792968750003], + [12.983203124999989, -12.775683593750017], + [13.4169921875, -12.52041015624998], + [13.597949218750017, -12.286132812500028], + [13.785351562499983, -11.81279296874996], + [13.833593750000063, -10.9296875], + [13.33222656250004, -9.998925781250009], + [12.99853515625, -9.048046875], + [13.358984375, -8.687207031250026], + [13.378515625000063, -8.369726562500006], + [12.82343750000004, -6.954785156249955], + [12.283300781250063, -6.12431640624996], + [13.184375, -5.85625], + [13.346484375000017, -5.863378906250006], + [13.978515625, -5.857226562500003], + [16.315234375000074, -5.865625], + [16.431445312500045, -5.90019531249996], + [16.53710937499997, -5.9658203125], + [16.697265625, -6.164257812500026], + [16.74296874999999, -6.618457031250003], + [16.813085937500063, -6.772558593749963], + [16.919433593750057, -6.93398437499998], + [16.98476562500005, -7.257421874999977], + [17.57958984375, -8.099023437500009] + ] + ], + [ + [ + [12.50146484375, -4.5875], + [12.848144531250028, -4.428906249999983], + [12.881054687500068, -4.445117187499989], + [12.971386718750068, -4.551757812499957], + [13.048046875000068, -4.619238281250034], + [13.072753906250028, -4.634765625], + [13.057324218750011, -4.651074218750026], + [12.947460937500011, -4.695312499999986], + [12.829687499999977, -4.73662109374996], + [12.451464843750017, -5.071484374999969], + [12.453222656250034, -5.090625], + [12.52236328125008, -5.148925781250028], + [12.484570312500011, -5.71875], + [12.213671875000074, -5.758691406249994], + [12.177148437499994, -5.324804687499977], + [12.018359375000074, -5.004296874999966], + [12.50146484375, -4.5875] + ] + ] + ] + }, + "properties": { "name": "Angola", "childNum": 2 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [20.06396484375, 42.54726562499999], + [20.485449218750006, 42.223388671875], + [20.566210937500017, 41.873681640624994], + [20.48896484375001, 41.272607421874994], + [20.709277343750017, 40.928369140624994], + [20.964257812500023, 40.849902343749996], + [20.95576171875001, 40.775292968749994], + [21.030859375, 40.622460937499994], + [20.881640625000017, 40.467919921874994], + [20.65742187500001, 40.1173828125], + [20.4560546875, 40.065576171874994], + [20.408007812500017, 40.049462890624994], + [20.383691406250023, 40.0171875], + [20.338476562500006, 39.991064453125], + [20.311132812500006, 39.979443359375], + [20.311328125000017, 39.95078125], + [20.381640625000017, 39.841796875], + [20.382421875, 39.802636718749994], + [20.206835937500017, 39.653515625], + [20.13105468750001, 39.66162109375], + [20.05976562500001, 39.699121093749994], + [20.022558593750006, 39.710693359375], + [20.001269531250017, 39.709423828125], + [19.851855468750017, 40.0435546875], + [19.322265625, 40.407080078125], + [19.45917968750001, 40.40537109375], + [19.3375, 40.663818359375], + [19.57568359375, 41.640429687499996], + [19.577539062500023, 41.7875], + [19.342382812500006, 41.869091796875], + [19.280664062500023, 42.17255859375], + [19.65449218750001, 42.628564453124994], + [19.78828125000001, 42.476171875], + [20.06396484375, 42.54726562499999] + ] + ] + }, + "properties": { "name": "Albania", "childNum": 1 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [19.66230468750001, 60.187158203124994], + [19.53652343750005, 60.14497070312501], + [19.551367187500063, 60.24384765625001], + [19.66230468750001, 60.187158203124994] + ] + ], + [ + [ + [19.989550781250074, 60.351171875], + [20.258886718750063, 60.26127929687499], + [19.799804687500057, 60.08173828125001], + [19.68691406250005, 60.267626953125045], + [19.84765625000003, 60.22055664062506], + [19.823046875000074, 60.390185546875045], + [19.989550781250074, 60.351171875] + ] + ] + ] + }, + "properties": { "name": "Aland", "childNum": 2 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [1.7060546875, 42.503320312499994], + [1.534082031250023, 42.441699218749996], + [1.448828125, 42.437451171875], + [1.428125, 42.46132812499999], + [1.414843750000017, 42.548388671874996], + [1.428320312500006, 42.5958984375], + [1.501367187500023, 42.642724609374994], + [1.568164062500017, 42.635009765625], + [1.709863281250023, 42.604443359375], + [1.739453125000011, 42.575927734375], + [1.740234375, 42.55673828125], + [1.713964843750006, 42.525634765625], + [1.7060546875, 42.503320312499994] + ] + ] + }, + "properties": { "name": "Andorra", "childNum": 1 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [53.927832031250006, 24.177197265624983], + [53.63447265625004, 24.169775390624977], + [53.83378906250002, 24.258935546875023], + [53.927832031250006, 24.177197265624983] + ] + ], + [ + [ + [53.3322265625001, 24.258593750000045], + [53.19091796874997, 24.290917968749966], + [53.412402343750074, 24.411035156250023], + [53.3322265625001, 24.258593750000045] + ] + ], + [ + [ + [56.29785156250003, 25.650683593750045], + [56.38798828125002, 24.97919921875004], + [56.06386718750005, 24.73876953125], + [56.00058593750006, 24.953222656249977], + [55.795703125000074, 24.868115234374955], + [55.76083984375006, 24.24267578125], + [55.92861328125005, 24.215136718750074], + [55.98515625000002, 24.063378906249966], + [55.4684570312501, 23.94111328125001], + [55.53164062499999, 23.81904296875001], + [55.1999023437501, 23.034765625000034], + [55.185839843750074, 22.7041015625], + [55.104296875000074, 22.621484375000023], + [52.55507812500005, 22.932812499999955], + [51.592578125000074, 24.07885742187503], + [51.56835937500003, 24.286181640625074], + [51.76757812500003, 24.25439453125], + [51.84316406250005, 24.010888671875023], + [52.118554687499994, 23.97109375], + [52.64824218750002, 24.154638671875006], + [53.80175781249997, 24.069482421874966], + [54.14794921875003, 24.17119140624999], + [54.39707031250006, 24.278173828125034], + [54.74677734375004, 24.810449218750023], + [55.94121093750002, 25.793994140625017], + [56.08046875, 26.06264648437505], + [56.16748046875003, 26.047460937499977], + [56.144628906250006, 25.690527343750006], + [56.29785156250003, 25.650683593750045] + ] + ] + ] + }, + "properties": { "name": "United Arab Emirates", "childNum": 3 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [-64.54916992187498, -54.71621093749998], + [-63.81542968749997, -54.725097656250014], + [-64.637353515625, -54.90253906250001], + [-64.75732421875, -54.82656249999999], + [-64.54916992187498, -54.71621093749998] + ] + ], + [ + [ + [-68.65322265624994, -54.85361328124999], + [-68.62993164062499, -52.65263671875004], + [-68.24013671875, -53.08183593749999], + [-68.43115234375, -53.0552734375], + [-68.48852539062497, -53.260937499999976], + [-68.16113281249997, -53.30644531250001], + [-68.00849609374995, -53.5640625], + [-67.29423828125002, -54.049804687500014], + [-66.23564453124999, -54.53349609374997], + [-65.17900390624993, -54.678125], + [-65.47114257812495, -54.91464843749999], + [-66.5111328125, -55.032128906249945], + [-67.127099609375, -54.90380859375001], + [-68.65322265624994, -54.85361328124999] + ] + ], + [ + [ + [-61.084716796875, -23.65644531250001], + [-60.83984375000003, -23.85810546874997], + [-59.89248046874994, -24.093554687499974], + [-59.18725585937497, -24.56230468749999], + [-57.82167968749994, -25.136425781249983], + [-57.56313476562494, -25.473730468749963], + [-57.943115234375, -26.05292968750001], + [-58.18149414062498, -26.30742187499999], + [-58.222070312499994, -26.65], + [-58.618603515624955, -27.13212890624996], + [-58.64174804687494, -27.196093750000017], + [-58.60483398437498, -27.314355468750037], + [-58.16826171874993, -27.27343749999997], + [-56.437158203124966, -27.553808593749977], + [-56.16406250000003, -27.321484374999983], + [-55.95146484374996, -27.325683593749957], + [-55.789990234374926, -27.416406249999966], + [-55.71464843749996, -27.41484375], + [-55.632910156250006, -27.35712890624997], + [-55.59379882812502, -27.288085937500014], + [-55.597265625, -27.207617187499963], + [-55.56489257812498, -27.15], + [-55.496728515624966, -27.11533203124999], + [-55.45063476562498, -27.068359375000014], + [-55.426660156249994, -27.00927734374997], + [-55.13593750000001, -26.931152343749957], + [-54.934472656249994, -26.70253906250001], + [-54.677734375, -26.308789062499997], + [-54.631933593750006, -26.005761718749994], + [-54.615869140624994, -25.576074218750023], + [-54.44394531249998, -25.625], + [-54.15458984374999, -25.523046874999963], + [-53.89116210937499, -25.66884765625001], + [-53.668554687500006, -26.288183593749977], + [-53.83818359375002, -27.121093750000014], + [-54.32700195312495, -27.423535156249997], + [-54.82910156250003, -27.55058593750003], + [-55.10151367187501, -27.866796874999963], + [-55.72548828125002, -28.20410156250003], + [-55.68725585937497, -28.38164062499996], + [-55.890527343749994, -28.370019531249994], + [-56.938623046874994, -29.594824218750034], + [-57.22465820312499, -29.782128906249994], + [-57.40522460937501, -30.03388671875004], + [-57.563867187499994, -30.139941406249974], + [-57.60888671875003, -30.187792968750045], + [-57.65087890624997, -30.295019531250034], + [-57.71269531249996, -30.38447265624997], + [-57.83120117187502, -30.495214843749963], + [-57.87250976562501, -30.591015625000026], + [-57.81059570312499, -30.85859375000001], + [-57.88632812499998, -30.937402343749994], + [-57.86840820312497, -31.104394531249994], + [-57.89335937499999, -31.195312499999957], + [-58.03339843750001, -31.416601562500006], + [-58.053857421874994, -31.494921874999974], + [-58.009667968749966, -31.534375], + [-57.98798828124998, -31.576171875], + [-58.00698242187494, -31.684960937499966], + [-58.04233398437495, -31.769238281249997], + [-58.16748046874997, -31.87265625], + [-58.18901367187499, -31.924218750000037], + [-58.16040039062503, -31.986523437500026], + [-58.156347656250006, -32.0515625], + [-58.17700195312494, -32.11904296875002], + [-58.16479492187494, -32.18486328125002], + [-58.119726562500006, -32.24892578125002], + [-58.12304687499997, -32.321875], + [-58.201171875, -32.471679687500014], + [-58.219970703125, -32.563964843749986], + [-58.17099609374998, -32.95927734374996], + [-58.424462890624994, -33.11152343749998], + [-58.54721679687498, -33.66347656249998], + [-58.392480468749966, -34.192968750000034], + [-58.52548828124998, -34.29619140625002], + [-58.28334960937494, -34.68349609375005], + [-57.54785156250003, -35.018945312499994], + [-57.170654296875, -35.3625], + [-57.35390624999994, -35.72031249999998], + [-57.33544921875, -36.026757812499966], + [-57.07617187499994, -36.296777343749994], + [-56.74946289062501, -36.346484375], + [-56.67202148437494, -36.85126953124998], + [-57.546972656250034, -38.085644531250026], + [-58.17919921874994, -38.435839843750045], + [-59.82832031250001, -38.83818359375003], + [-61.112207031249994, -38.99296875000003], + [-61.84790039062497, -38.961816406249994], + [-62.33476562499993, -38.80009765625], + [-62.29506835937502, -39.24326171874996], + [-62.053662109374955, -39.373828125], + [-62.179345703124994, -39.38046875000002], + [-62.076806640624966, -39.46152343750002], + [-62.131542968749926, -39.82539062499998], + [-62.28691406249996, -39.89531250000002], + [-62.40185546875003, -40.19658203125002], + [-62.24633789062494, -40.674609374999974], + [-62.39501953124997, -40.89082031249997], + [-62.95903320312493, -41.10966796875006], + [-63.621777343749955, -41.15976562499996], + [-64.86948242187503, -40.735839843750014], + [-65.13339843749998, -40.88066406250003], + [-64.98637695312496, -42.102050781249986], + [-64.53774414062494, -42.25458984374998], + [-64.57099609374998, -42.416015625], + [-64.42041015625003, -42.43378906249998], + [-64.10087890624993, -42.395117187500006], + [-64.06118164062494, -42.266113281250014], + [-64.228515625, -42.21826171874996], + [-63.795556640624994, -42.113867187500006], + [-63.6298828125, -42.28271484375003], + [-63.61733398437502, -42.695800781249986], + [-64.03476562499998, -42.88125], + [-64.48784179687499, -42.51347656250006], + [-64.97070312499997, -42.66630859375002], + [-65.02690429687496, -42.75888671874996], + [-64.31914062499999, -42.968945312500026], + [-64.83994140624998, -43.18886718749998], + [-65.25234374999997, -43.571875], + [-65.26552734375, -44.2796875], + [-65.64760742187502, -44.661425781250045], + [-65.63876953125, -45.0078125], + [-66.19013671874995, -44.96474609375002], + [-66.94140625, -45.25732421875003], + [-67.59956054687495, -46.05253906250003], + [-67.5064453125, -46.44277343749995], + [-66.77685546874994, -47.005859375], + [-65.99853515625, -47.09375], + [-65.73808593749999, -47.34492187499998], + [-65.81430664062495, -47.63818359374996], + [-66.22524414062502, -47.826757812500006], + [-65.93422851562497, -47.826757812500006], + [-65.81005859374997, -47.941113281250026], + [-67.46630859375, -48.95175781250004], + [-67.68486328125002, -49.2466796875], + [-67.82597656249999, -49.91962890625005], + [-68.2572265625, -50.104589843749984], + [-68.66757812500003, -49.75253906250003], + [-68.66162109374997, -49.93574218750005], + [-68.97958984375, -50.003027343749984], + [-68.59794921874996, -50.00947265624997], + [-68.421875, -50.15791015625001], + [-69.04477539062495, -50.49912109374998], + [-69.35859374999993, -51.028125], + [-69.20102539062498, -50.99365234375001], + [-69.03530273437497, -51.48896484375002], + [-69.46542968750003, -51.58447265625003], + [-68.96533203125003, -51.67714843749999], + [-68.443359375, -52.35664062500004], + [-69.96025390624993, -52.00820312500002], + [-71.91865234374995, -51.98955078125004], + [-72.40766601562501, -51.54082031250002], + [-72.34023437499997, -50.68183593749999], + [-72.50981445312496, -50.607519531250034], + [-73.15292968749998, -50.73828125000003], + [-73.50126953124996, -50.125292968750024], + [-73.55419921875, -49.463867187500014], + [-73.46157226562497, -49.31386718750001], + [-73.13525390625, -49.30068359374999], + [-73.03364257812501, -49.014355468750004], + [-72.65126953125, -48.84160156249998], + [-72.582861328125, -48.47539062499999], + [-72.35473632812497, -48.36582031250005], + [-72.32832031250001, -48.11005859374998], + [-72.517919921875, -47.87636718749998], + [-72.34594726562497, -47.49267578124997], + [-71.90498046875001, -47.201660156250014], + [-71.94023437499999, -46.83125], + [-71.69965820312501, -46.6513671875], + [-71.87568359374998, -46.160546875], + [-71.63154296874998, -45.95371093749998], + [-71.74619140624998, -45.57890625], + [-71.34931640624995, -45.33193359374995], + [-71.5962890625, -44.97919921875004], + [-72.04169921874998, -44.90419921875004], + [-72.06372070312503, -44.771875], + [-71.26113281250002, -44.763085937499966], + [-71.15971679687496, -44.56025390625004], + [-71.21259765624998, -44.44121093750003], + [-71.82001953124993, -44.38310546875], + [-71.68007812500002, -43.92958984374998], + [-71.90498046875001, -43.34755859374998], + [-71.750634765625, -43.237304687499986], + [-72.14643554687498, -42.990039062499974], + [-72.10820312499993, -42.25185546874995], + [-71.75, -42.04677734375001], + [-71.91127929687497, -41.650390624999986], + [-71.93212890624994, -40.69169921874999], + [-71.70898437499997, -40.381738281249994], + [-71.81831054687493, -40.17666015624995], + [-71.65976562499998, -40.02080078125], + [-71.71992187499995, -39.63525390624997], + [-71.53945312499997, -39.60244140624995], + [-71.40156249999995, -38.93505859374996], + [-70.858642578125, -38.60449218750003], + [-71.16757812499998, -37.76230468749996], + [-71.19218750000002, -36.84365234375004], + [-71.05551757812498, -36.52373046874996], + [-70.40478515625, -36.06171874999998], + [-70.41572265625001, -35.52304687500002], + [-70.55517578125, -35.246875], + [-70.39316406250003, -35.146875], + [-70.05205078124999, -34.30078124999997], + [-69.85244140625, -34.224316406250026], + [-69.81962890624999, -33.28378906249999], + [-70.08486328125002, -33.20175781249998], + [-70.02197265625, -32.88457031250002], + [-70.36376953125, -32.08349609374997], + [-70.25439453125, -31.957714843750026], + [-70.585205078125, -31.569433593749963], + [-70.51958007812493, -31.1484375], + [-70.30908203124994, -31.02265625000004], + [-70.15322265625, -30.360937499999963], + [-69.95634765624996, -30.35820312500003], + [-69.84428710937493, -30.175], + [-69.95996093749997, -30.078320312500026], + [-70.02680664062501, -29.324023437500017], + [-69.82788085937497, -29.10322265624997], + [-69.65693359374995, -28.413574218749986], + [-69.17441406249998, -27.924707031250037], + [-68.84633789062494, -27.153710937499994], + [-68.59208984375002, -27.140039062499966], + [-68.31865234374999, -26.973242187500006], + [-68.59160156249999, -26.47041015624997], + [-68.41450195312498, -26.153710937500023], + [-68.59208984375002, -25.420019531250034], + [-68.38422851562495, -25.091894531249977], + [-68.56201171875, -24.74736328125003], + [-68.25029296875002, -24.391992187500023], + [-67.35620117187503, -24.033789062499963], + [-67.00878906249994, -23.00136718750005], + [-67.19487304687493, -22.821679687500037], + [-66.99111328125, -22.509863281250006], + [-66.71171874999999, -22.216308593749986], + [-66.36518554687501, -22.113769531249957], + [-66.32246093750001, -22.053125], + [-66.28212890624997, -21.94746093750001], + [-66.24760742187496, -21.83046875], + [-66.22016601562495, -21.802539062499974], + [-66.174658203125, -21.805664062499986], + [-66.09858398437495, -21.83505859375002], + [-66.05859375, -21.87949218750002], + [-65.86015624999999, -22.019726562499983], + [-65.77104492187493, -22.099609375000014], + [-65.68618164062497, -22.11025390625005], + [-65.51879882812497, -22.094531250000045], + [-64.99262695312498, -22.109667968750017], + [-64.60551757812499, -22.228808593750045], + [-64.52363281250001, -22.37158203125], + [-64.47773437499998, -22.485351562499986], + [-64.44550781249998, -22.585351562500023], + [-64.37397460937498, -22.761035156250017], + [-64.32529296875, -22.82763671875], + [-64.30791015624993, -22.7953125], + [-64.26640625000002, -22.60332031249996], + [-63.97612304687502, -22.072558593750003], + [-63.92167968749993, -22.028613281250017], + [-62.843359375, -21.997265625000026], + [-62.62597656250003, -22.29042968749998], + [-62.54155273437496, -22.349609374999957], + [-62.37250976562498, -22.439160156249997], + [-62.21416015624996, -22.612402343750034], + [-61.798535156249955, -23.18203125], + [-61.084716796875, -23.65644531250001] + ] + ] + ] + }, + "properties": { "name": "Argentina", "childNum": 3 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [46.490625, 38.90668945312498], + [46.1144531250001, 38.877783203125034], + [45.977441406249994, 39.24389648437503], + [45.76630859375004, 39.37846679687499], + [45.78447265625002, 39.54560546875001], + [45.456835937500074, 39.494482421875006], + [45.15283203125003, 39.58266601562502], + [45.03164062500005, 39.76513671874997], + [44.76826171875004, 39.70351562500005], + [44.28925781250004, 40.040380859375006], + [43.66621093750004, 40.12636718750002], + [43.56933593750003, 40.48237304687498], + [43.72265624999997, 40.71953124999999], + [43.43945312500003, 41.10712890625001], + [44.077246093750006, 41.182519531249994], + [44.81132812500002, 41.259375], + [45.001367187499994, 41.29096679687498], + [45.188574218750006, 41.14741210937504], + [45.07050781250004, 41.075585937499966], + [45.5875, 40.846923828125], + [45.37890624999997, 40.67358398437506], + [45.45439453125002, 40.532373046874966], + [45.96464843750002, 40.233789062499966], + [45.8859375000001, 40.024853515624955], + [45.57978515625004, 39.9775390625], + [46.202050781249994, 39.59448242187503], + [46.48144531249997, 39.55517578125003], + [46.36523437500003, 39.402490234374994], + [46.584765625000074, 39.22368164062499], + [46.400292968749994, 39.1921875], + [46.490625, 38.90668945312498] + ] + ] + }, + "properties": { "name": "Armenia", "childNum": 1 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [-170.72626953125, -14.351171875], + [-170.8205078125, -14.312109375], + [-170.568115234375, -14.266796875000011], + [-170.72626953125, -14.351171875] + ] + ] + }, + "properties": { "name": "American Samoa", "childNum": 1 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [69.2824218750001, -49.05888671875002], + [69.16718750000004, -48.88291015624996], + [69.36875, -48.89042968749998], + [69.2824218750001, -49.05888671875002] + ] + ], + [ + [ + [69.18486328125002, -49.10957031250004], + [69.59277343749997, -48.97099609375005], + [69.64404296875003, -49.11738281250003], + [69.40507812500002, -49.18173828125], + [69.5423828125, -49.25566406250005], + [70.32021484375005, -49.05859374999996], + [70.55546875000007, -49.201464843750024], + [70.38613281250005, -49.433984374999966], + [70.16582031250002, -49.34296874999998], + [69.75996093750004, -49.430175781249986], + [69.98642578125006, -49.58164062500003], + [70.2477539062501, -49.53066406250003], + [70.12431640625002, -49.70439453124999], + [69.153125, -49.5296875], + [68.99296875000007, -49.704980468750016], + [68.81474609375002, -49.69960937499999], + [68.88339843750006, -49.16494140624995], + [68.76953125000003, -49.06591796875003], + [69.00244140624997, -48.661230468750006], + [69.13613281250005, -48.86103515625003], + [69.05214843750005, -49.08193359375001], + [69.18486328125002, -49.10957031250004] + ] + ], + [ + [ + [51.83457031250006, -46.43994140625], + [51.65927734375006, -46.37363281249999], + [51.7418945312501, -46.32685546874997], + [51.83457031250006, -46.43994140625] + ] + ] + ] + }, + "properties": { "name": "Fr. S. Antarctic Lands", "childNum": 3 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [-61.71606445312503, 17.037011718749994], + [-61.85966796874996, 17.013330078124966], + [-61.887109374999966, 17.09814453125], + [-61.81728515624994, 17.168945312500057], + [-61.71606445312503, 17.037011718749994] + ] + ] + }, + "properties": { "name": "Antigua and Barb.", "childNum": 1 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [147.35605468750006, -43.396972656250014], + [147.30888671875007, -43.50078125000002], + [147.10498046875003, -43.43115234374996], + [147.28388671875004, -43.278906250000034], + [147.35605468750006, -43.396972656250014] + ] + ], + [ + [ + [145.04296875000003, -40.78671875], + [145.28300781250002, -40.76992187500002], + [146.31748046875006, -41.16347656250001], + [146.72343750000002, -41.07802734375001], + [146.84814453124997, -41.16806640624996], + [146.98984375000006, -40.99238281249997], + [147.45478515625004, -41.00166015624998], + [147.62167968750012, -40.844726562499986], + [147.87294921875005, -40.87255859374997], + [147.96875, -40.779589843750045], + [148.215234375, -40.85488281250002], + [148.34257812500007, -42.21533203124997], + [148.21367187500002, -41.97001953125], + [147.92441406250006, -42.5724609375], + [147.94541015625006, -43.18183593749997], + [147.7858398437501, -43.22001953125002], + [147.69892578125004, -43.12255859374997], + [147.64794921874997, -43.02060546874999], + [147.8, -42.928125], + [147.57382812500006, -42.84570312499997], + [147.4523437500001, -43.03339843750001], + [147.29794921875006, -42.790917968749994], + [147.24501953125005, -43.21591796874999], + [146.99697265625005, -43.15634765625002], + [147.07734375000004, -43.27587890625003], + [146.87392578125, -43.6125], + [146.54853515625004, -43.50888671874999], + [146.04316406250004, -43.547167968749974], + [145.99443359375007, -43.37607421875002], + [146.20800781249997, -43.31621093749999], + [145.8732421875001, -43.29238281250002], + [145.48759765625002, -42.92666015625004], + [145.19882812500006, -42.23085937500004], + [145.46826171874997, -42.492871093750026], + [145.51660156249997, -42.3544921875], + [145.33105468750003, -42.14707031250002], + [145.23486328124997, -42.19697265624997], + [145.23818359375, -42.01962890624999], + [144.76611328125003, -41.39003906249998], + [144.64609375000006, -40.980859375], + [144.71855468750002, -40.67226562500002], + [145.04296875000003, -40.78671875] + ] + ], + [ + [ + [148.23691406250006, -40.515136718749986], + [148.18779296875007, -40.592578125000045], + [148.11728515625012, -40.52148437499996], + [148.23691406250006, -40.515136718749986] + ] + ], + [ + [ + [144.784375, -40.506738281249966], + [144.74804687499997, -40.589453125000034], + [144.7833984375001, -40.434863281249974], + [144.784375, -40.506738281249966] + ] + ], + [ + [ + [148.32626953125006, -40.30693359375003], + [148.40400390625004, -40.486523437500026], + [148.02011718750012, -40.40419921874995], + [148.32626953125006, -40.30693359375003] + ] + ], + [ + [ + [148.000390625, -39.75761718750003], + [148.29736328125003, -39.985742187499966], + [148.31357421875012, -40.173535156250026], + [148.10566406250004, -40.26210937499995], + [147.76718750000012, -39.87031249999998], + [148.000390625, -39.75761718750003] + ] + ], + [ + [ + [143.92792968750004, -40.116113281249966], + [143.83857421875004, -39.90410156250003], + [144.00078125000007, -39.580175781250034], + [144.14101562500005, -39.953808593750026], + [143.92792968750004, -40.116113281249966] + ] + ], + [ + [ + [145.31445312500003, -38.49082031249996], + [145.35507812500012, -38.55703124999995], + [145.12841796875003, -38.52763671875], + [145.31445312500003, -38.49082031249996] + ] + ], + [ + [ + [137.59648437500007, -35.73867187499998], + [137.92890625000004, -35.72607421875], + [138.12343750000005, -35.85234375], + [137.67089843749997, -35.897949218750014], + [137.44843750000004, -36.07480468749999], + [137.20957031250012, -35.982421875], + [136.7550781250001, -36.03310546875002], + [136.540625, -35.89013671875003], + [136.63867187499997, -35.74882812500002], + [137.33408203125006, -35.59248046875004], + [137.58496093749997, -35.620214843750006], + [137.59648437500007, -35.73867187499998] + ] + ], + [ + [ + [153.53876953125004, -27.436425781250037], + [153.42656250000002, -27.70644531249998], + [153.43544921875, -27.40537109375002], + [153.53876953125004, -27.436425781250037] + ] + ], + [ + [ + [113.18300781250005, -26.053125], + [112.96425781250005, -25.78310546875001], + [112.94707031250002, -25.531542968750017], + [113.18300781250005, -26.053125] + ] + ], + [ + [ + [153.07744140625002, -25.75078125], + [152.97666015625012, -25.551367187499963], + [153.03808593750003, -25.193164062500003], + [153.22753906249997, -25.00576171875001], + [153.14375, -24.814843750000023], + [153.25693359375012, -24.72890625], + [153.35019531250012, -25.063085937499963], + [153.07744140625002, -25.75078125] + ] + ], + [ + [ + [151.14658203125006, -23.49082031250002], + [151.24013671875, -23.529687500000037], + [151.23828124999997, -23.77578125], + [151.03330078125006, -23.530175781250037], + [151.14658203125006, -23.49082031250002] + ] + ], + [ + [ + [115.44619140625005, -20.78779296875001], + [115.31806640625004, -20.850585937500014], + [115.43457031249997, -20.66796875000003], + [115.44619140625005, -20.78779296875001] + ] + ], + [ + [ + [149.04375, -20.29150390624997], + [148.93886718750005, -20.283691406249986], + [148.98105468750012, -20.153515625000026], + [149.04375, -20.29150390624997] + ] + ], + [ + [ + [146.27832031249997, -18.23125], + [146.29882812499997, -18.48476562500005], + [146.09882812500004, -18.251757812500003], + [146.27832031249997, -18.23125] + ] + ], + [ + [ + [139.45917968750004, -17.11455078124996], + [139.49277343750006, -16.990429687499983], + [139.57089843750006, -17.09443359375004], + [139.45917968750004, -17.11455078124996] + ] + ], + [ + [ + [139.50781250000003, -16.57304687499996], + [139.1595703125, -16.74169921875003], + [139.29296875000003, -16.467285156249986], + [139.58789062499997, -16.39521484374997], + [139.69775390624997, -16.514941406250017], + [139.50781250000003, -16.57304687499996] + ] + ], + [ + [ + [137.09365234375005, -15.778125], + [136.94267578125002, -15.711718749999989], + [137.00957031250007, -15.594824218749977], + [137.09365234375005, -15.778125] + ] + ], + [ + [ + [124.59726562500006, -15.40195312500002], + [124.52421875000002, -15.421484375], + [124.51933593750002, -15.26748046874998], + [124.59726562500006, -15.40195312500002] + ] + ], + [ + [ + [125.19882812500006, -14.57949218749998], + [125.0912109375, -14.59169921874998], + [125.15996093750002, -14.456054687499972], + [125.19882812500006, -14.57949218749998] + ] + ], + [ + [ + [136.71464843750002, -13.803906249999983], + [136.89082031250004, -13.786621093750014], + [136.74531250000004, -14.072656250000023], + [136.95078125000006, -14.184277343750026], + [136.89433593750002, -14.293066406249977], + [136.33544921875003, -14.211816406250037], + [136.42470703125, -13.864843749999963], + [136.6556640625, -13.675878906250006], + [136.71464843750002, -13.803906249999983] + ] + ], + [ + [ + [136.23740234375006, -13.824511718750003], + [136.12265625000012, -13.816601562499983], + [136.21542968750012, -13.664746093750054], + [136.23740234375006, -13.824511718750003] + ] + ], + [ + [ + [136.33867187500007, -11.602343749999989], + [136.18027343750006, -11.676757812499957], + [136.47929687500002, -11.465917968749991], + [136.33867187500007, -11.602343749999989] + ] + ], + [ + [ + [130.45927734375007, -11.679296875000034], + [130.60625, -11.816601562500026], + [130.04326171875007, -11.787304687500011], + [130.19755859375007, -11.658203125], + [130.15283203124997, -11.477539062499972], + [130.29492187499997, -11.33681640624998], + [130.45927734375007, -11.679296875000034] + ] + ], + [ + [ + [130.6188476562501, -11.376074218749991], + [131.02304687500006, -11.334375], + [131.26826171875004, -11.18984375], + [131.53857421874997, -11.436914062500037], + [130.95097656250007, -11.926464843750026], + [130.51191406250004, -11.617871093749955], + [130.38457031250002, -11.1921875], + [130.6188476562501, -11.376074218749991] + ] + ], + [ + [ + [136.59853515625, -11.378906249999943], + [136.52656250000004, -11.438867187499994], + [136.78027343749997, -11.0125], + [136.59853515625, -11.378906249999943] + ] + ], + [ + [ + [132.59335937500006, -11.302832031249991], + [132.48378906250005, -11.037304687499983], + [132.57880859375004, -10.968847656249977], + [132.59335937500006, -11.302832031249991] + ] + ], + [ + [ + [143.17890625000004, -11.954492187499966], + [143.11025390625, -12.303515625000017], + [143.40156250000004, -12.639941406249989], + [143.5866210937501, -13.443652343750031], + [143.54843750000012, -13.74101562499996], + [143.75634765625003, -14.348828124999969], + [143.96181640625005, -14.462890625000028], + [144.473046875, -14.231835937500023], + [144.64804687500006, -14.492480468750017], + [145.28769531250006, -14.943164062499989], + [145.42607421875002, -16.406152343749966], + [145.75478515625, -16.879492187500034], + [145.91210937499997, -16.9125], + [146.12587890625005, -17.63525390625], + [146.03222656249997, -18.272851562500037], + [146.3332031250001, -18.55371093749997], + [146.38339843750006, -18.97705078124997], + [147.13876953125006, -19.39316406250002], + [147.41855468750012, -19.378125], + [147.7423828125001, -19.770117187499977], + [148.759375, -20.28955078125003], + [148.88476562499997, -20.480859375], + [148.72998046874997, -20.4677734375], + [148.68369140625012, -20.58017578124999], + [149.20488281250007, -21.125097656249977], + [149.45410156249997, -21.57871093750002], + [149.70390625000002, -22.440527343750006], + [149.82246093750004, -22.389843749999983], + [149.97441406250007, -22.55068359374998], + [149.94189453125003, -22.30810546875003], + [150.07617187500003, -22.16445312499998], + [150.54130859375002, -22.55908203125], + [150.56855468750004, -22.38398437500004], + [150.67246093750012, -22.418164062499983], + [150.84316406250005, -23.4580078125], + [151.15380859375003, -23.784082031249994], + [151.83164062500006, -24.12294921875001], + [152.12988281250003, -24.59755859374998], + [152.45634765625007, -24.802441406249983], + [152.65429687499997, -25.201953125000017], + [152.91347656250005, -25.432128906250014], + [152.98496093750012, -25.816210937500003], + [153.16494140625, -25.964160156250045], + [153.11679687500006, -27.194433593750034], + [153.57568359375003, -28.24052734374999], + [153.6168945312501, -28.673046875], + [153.03056640625002, -30.563378906249994], + [152.94394531250012, -31.43486328124999], + [152.5592773437501, -32.045703125], + [152.4704101562501, -32.439062500000034], + [152.13652343750002, -32.678125], + [152.1642578125001, -32.75742187499996], + [151.812890625, -32.90107421875001], + [151.29208984375012, -33.580957031249966], + [151.28027343750003, -33.92666015625005], + [151.12480468750007, -34.00527343749998], + [151.23154296875006, -34.0296875], + [150.8712890625001, -34.49912109374996], + [150.80458984375, -35.01289062500001], + [150.19531249999997, -35.83359374999996], + [149.93271484375012, -37.528515625000026], + [149.480859375, -37.77119140625], + [147.87675781250002, -37.93417968749998], + [146.8568359375, -38.663476562499966], + [146.21748046875004, -38.72744140625004], + [146.33662109375004, -38.89423828125], + [146.46660156250007, -38.84033203125003], + [146.40000000000012, -39.14550781250003], + [146.1583984375001, -38.86572265624996], + [145.93535156250002, -38.90175781250002], + [145.79082031250007, -38.66699218749997], + [145.39726562500002, -38.53535156249998], + [145.54218750000004, -38.39384765625002], + [145.4757812500001, -38.24375], + [145.29277343750002, -38.237597656249974], + [144.95957031250012, -38.500781250000045], + [144.71777343749997, -38.34033203125004], + [144.91142578125007, -38.34404296874999], + [145.11992187500007, -38.091308593750014], + [144.89130859375004, -37.899804687499994], + [144.39550781250003, -38.13691406249998], + [144.6652343750001, -38.20996093750003], + [143.53896484375005, -38.82089843749998], + [142.45585937500002, -38.38632812499999], + [141.725, -38.27138671875002], + [141.5939453125001, -38.38779296875002], + [141.42421875, -38.36347656250004], + [141.0109375000001, -38.07695312500003], + [140.39042968750007, -37.89667968749998], + [139.78427734375012, -37.24580078124998], + [139.85732421875, -36.662109375], + [139.72900390625003, -36.37138671875002], + [138.9689453125001, -35.58076171874997], + [139.17802734375007, -35.52304687500002], + [139.289453125, -35.61132812499997], + [139.28251953125002, -35.375390624999966], + [138.521875, -35.6423828125], + [138.184375, -35.612695312499994], + [138.5111328125, -35.02441406249996], + [138.48994140625004, -34.76357421875002], + [138.0892578125, -34.16982421875002], + [137.69169921875002, -35.14296875000004], + [136.88359375000007, -35.23974609375004], + [137.01425781250012, -34.91582031250003], + [137.39101562500005, -34.91328124999997], + [137.49384765625004, -34.16113281250003], + [137.9318359375001, -33.57910156250003], + [137.85234375000007, -33.20078124999996], + [137.99257812500005, -33.094238281250014], + [137.78320312500003, -32.578125], + [137.79091796875, -32.82324218749996], + [137.44228515625, -33.1935546875], + [137.23730468750003, -33.62949218749999], + [136.43066406249997, -34.02998046875004], + [135.891015625, -34.660937499999974], + [135.96972656249997, -34.98183593749998], + [135.7923828125, -34.863281249999986], + [135.64755859375006, -34.93964843750001], + [135.12304687499997, -34.58574218750003], + [135.21679687499997, -34.48730468749996], + [135.45, -34.58105468749996], + [135.21894531250004, -33.959765625000045], + [134.88876953125012, -33.62636718749998], + [134.79101562499997, -33.32832031250001], + [134.60771484375002, -33.19013671875001], + [134.30126953124997, -33.16503906249996], + [134.17353515625004, -32.979101562500006], + [134.10039062500007, -32.748632812500034], + [134.22714843750006, -32.73056640624999], + [134.23417968750007, -32.54853515625004], + [133.66533203125007, -32.207226562500054], + [133.21210937500004, -32.18378906249998], + [132.75742187500012, -31.95625], + [132.21464843750002, -32.00712890624996], + [131.14365234375006, -31.49570312500005], + [130.78300781250002, -31.604003906249986], + [129.1876953125001, -31.659960937500017], + [127.31982421874997, -32.2640625], + [125.91718750000004, -32.296972656250034], + [124.75878906250003, -32.882714843749994], + [124.24375, -33.01523437499999], + [123.50683593749997, -33.916210937500054], + [122.15097656250006, -33.99179687499999], + [122.06113281250006, -33.874414062499966], + [121.40507812500007, -33.826757812500034], + [119.85410156250012, -33.97470703124998], + [119.45058593750005, -34.368261718750034], + [118.89531250000007, -34.47988281250004], + [118.13554687500002, -34.98662109374999], + [117.58193359375005, -35.09775390624998], + [116.51718750000012, -34.98789062499998], + [115.98671875000005, -34.795019531250034], + [115.56503906250012, -34.42578125000003], + [115.00878906250003, -34.25585937499997], + [114.9938476562501, -33.51533203125], + [115.3587890625, -33.63994140624999], + [115.68300781250005, -33.19287109375003], + [115.6984375000001, -31.694531250000054], + [115.07792968750007, -30.560449218750023], + [114.85683593750005, -29.14296875], + [114.16513671875012, -28.08066406250002], + [114.028125, -27.34726562499999], + [113.18476562500004, -26.182226562499963], + [113.32324218749997, -26.243847656249997], + [113.35605468750012, -26.080468750000023], + [113.58164062500006, -26.558105468749986], + [113.73369140625002, -26.59511718749998], + [113.83642578125003, -26.50058593749999], + [113.85283203125007, -26.33212890625005], + [113.39531250000002, -25.71328125], + [113.4513671875001, -25.599121093750014], + [113.7130859375001, -25.83076171875004], + [113.72373046875006, -26.129785156250037], + [113.85390625, -26.01445312499999], + [113.99199218750007, -26.32148437500001], + [114.09033203124997, -26.393652343749963], + [114.21572265625, -26.289453124999966], + [114.2142578125, -25.851562500000014], + [113.41767578125004, -24.435644531250034], + [113.48984375000012, -23.869628906250014], + [113.7570312500001, -23.418164062500054], + [113.79511718750004, -22.91455078125003], + [113.68281250000004, -22.637792968749963], + [114.02285156250005, -21.881445312499977], + [114.12392578125005, -21.828613281249957], + [114.14160156250003, -22.483105468749983], + [114.37773437500007, -22.341503906249997], + [114.70927734375002, -21.82343749999997], + [115.45615234375012, -21.49169921874997], + [116.0109375000001, -21.030371093749963], + [116.7067382812501, -20.653808593749986], + [117.40625, -20.72119140625003], + [118.19921875000003, -20.37519531249997], + [118.75146484374997, -20.261914062499983], + [119.10449218749997, -19.995312500000026], + [119.58593750000003, -20.03828125], + [120.99794921875, -19.604394531249966], + [121.33769531250002, -19.31992187500002], + [121.83378906250002, -18.477050781249986], + [122.34541015625004, -18.11191406250002], + [122.14746093749997, -17.54902343750001], + [122.2609375000001, -17.135742187500014], + [122.72041015625004, -16.78769531249999], + [122.97070312499997, -16.436816406250003], + [123.56308593750006, -17.520898437499966], + [123.59355468750007, -17.03037109375005], + [123.83105468750003, -17.120800781249997], + [123.8744140625, -16.918652343750026], + [123.4904296875001, -16.49072265624997], + [123.62597656249997, -16.416308593750003], + [123.60703125000006, -16.224023437499994], + [123.72890625, -16.192480468749963], + [123.85917968750007, -16.38232421875], + [124.04443359374997, -16.264941406249974], + [124.30039062500006, -16.388281249999977], + [124.77197265624997, -16.40263671874996], + [124.40488281250006, -16.298925781249977], + [124.41640625, -16.133496093750026], + [124.5768554687501, -16.11367187499998], + [124.64853515625012, -15.870214843750034], + [124.50429687500005, -15.972460937499989], + [124.38164062500002, -15.758203125000037], + [124.43955078125012, -15.493554687500037], + [124.56162109375012, -15.496289062499969], + [124.69257812500004, -15.273632812499997], + [125.06298828125003, -15.44228515624998], + [125.0729492187501, -15.306738281249991], + [124.90917968750003, -15.310058593749957], + [124.83906250000004, -15.160742187500006], + [125.03818359375012, -15.004101562499969], + [125.35566406250004, -15.119824218750011], + [125.17871093749997, -14.714746093749994], + [125.57978515625004, -14.483203124999989], + [125.62773437500002, -14.256640625000017], + [125.70458984374997, -14.29140625], + [125.66162109375003, -14.529492187500011], + [125.81953125000004, -14.469140624999966], + [125.890625, -14.61796875], + [126.0207031250001, -14.49453125], + [126.0539062500001, -13.977246093750026], + [126.1842773437501, -14.00205078125002], + [126.25849609375004, -14.163574218749972], + [126.403125, -14.018945312499994], + [126.5697265625, -14.160937499999974], + [126.7806640625, -13.955175781249977], + [126.77558593750004, -13.788476562500037], + [126.90322265625, -13.744140624999972], + [127.45761718750006, -14.031445312499969], + [128.18046875000007, -14.711621093749983], + [128.06943359375012, -15.329296874999969], + [128.15546875000004, -15.225585937499972], + [128.25468750000002, -15.298535156250011], + [128.175, -15.043164062500026], + [128.57578125000006, -14.774511718750006], + [129.05820312500012, -14.884375], + [129.21582031249997, -15.160253906249991], + [129.26757812500003, -14.871484375000051], + [129.63476562499997, -15.139746093749991], + [129.637109375, -14.850976562500037], + [129.84873046875012, -14.828906249999989], + [129.60468750000004, -14.647070312499977], + [129.69794921875004, -14.557421875000017], + [129.37871093750002, -14.39248046874998], + [129.70986328125, -13.979980468749972], + [129.83886718749997, -13.572949218749997], + [130.25976562500003, -13.30224609375], + [130.1349609375001, -13.145507812499957], + [130.1681640625001, -12.957421875], + [130.39990234374997, -12.68789062499999], + [130.61748046875007, -12.646875], + [130.62265625000006, -12.43105468749998], + [130.8673828125001, -12.557812499999955], + [130.87382812500007, -12.367187500000028], + [131.29160156250006, -12.067871093749972], + [131.43828125000002, -12.27695312500002], + [132.06406250000006, -12.28076171875], + [132.25322265625007, -12.186035156249972], + [132.41103515625, -12.295117187499997], + [132.51054687500002, -12.134863281250034], + [132.71279296875, -12.1234375], + [132.63046875000012, -12.035156249999972], + [132.67421875000005, -11.649023437499991], + [132.47519531250006, -11.491503906249974], + [132.07285156250006, -11.474707031250006], + [131.82246093750004, -11.302441406249997], + [131.96152343750006, -11.180859375000011], + [132.15546875000004, -11.311132812499991], + [132.33398437499997, -11.223535156249994], + [132.6828125000001, -11.505566406249997], + [132.96103515625012, -11.407324218749963], + [133.18525390625004, -11.705664062499991], + [133.90419921875, -11.832031249999972], + [134.4173828125, -12.052734375], + [134.73027343750002, -11.984375], + [135.02968750000005, -12.19375], + [135.2179687500001, -12.221679687499957], + [135.92246093750012, -11.825781250000034], + [135.70439453125007, -12.209863281250037], + [136.00849609375004, -12.19140625], + [136.08183593750007, -12.422460937500006], + [136.26064453125, -12.433789062499997], + [136.32851562500005, -12.305566406249994], + [136.24990234375, -12.173046875], + [136.44335937499997, -11.951464843749974], + [136.7194335937501, -12.226464843749952], + [136.89746093749997, -12.243554687499966], + [136.94746093750004, -12.34990234374996], + [136.53701171875, -12.784277343749991], + [136.59433593750012, -13.003808593750051], + [136.46103515625006, -13.225195312500034], + [136.29414062500004, -13.137988281250031], + [135.92734375000012, -13.304296874999977], + [135.95449218750005, -13.934863281250017], + [135.40517578125005, -14.758203124999966], + [135.4533203125001, -14.923144531250003], + [136.20537109375002, -15.403417968749963], + [136.29140625000005, -15.570117187500003], + [136.70488281250007, -15.685253906250011], + [136.78466796874997, -15.89423828125004], + [137.00214843750004, -15.878320312499994], + [137.70371093750006, -16.233007812499963], + [138.24501953125005, -16.718359374999977], + [139.00986328125006, -16.899316406249994], + [139.2484375, -17.328613281249957], + [140.03583984375004, -17.702636718749957], + [140.51113281250005, -17.62451171875003], + [140.83046875, -17.414453125000037], + [141.29140625, -16.46347656250002], + [141.62548828124997, -15.056640625000014], + [141.52294921875003, -14.470117187499994], + [141.59433593750006, -14.152832031250014], + [141.47255859375, -13.797558593750011], + [141.64541015625, -13.259082031250003], + [141.61357421875002, -12.943457031250006], + [141.92978515625006, -12.73984375], + [141.67773437500003, -12.491406250000011], + [141.68857421875012, -12.351074218750028], + [141.87050781250005, -11.9755859375], + [141.96113281250004, -12.054296874999963], + [142.168359375, -10.946582031249974], + [142.45644531250005, -10.707324218749989], + [142.60507812500012, -10.748242187499983], + [142.55273437500003, -10.874414062500023], + [142.7796875, -11.115332031249977], + [142.87255859374997, -11.821386718750034], + [143.17890625000004, -11.954492187499966] + ] + ], + [ + [ + [142.2748046875, -10.704785156250011], + [142.19140624999997, -10.762011718750031], + [142.1310546875001, -10.640625], + [142.19794921875004, -10.59199218750004], + [142.2748046875, -10.704785156250011] + ] + ] + ] + }, + "properties": { "name": "Australia", "childNum": 30 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [16.953125, 48.598828125], + [16.86542968750001, 48.3869140625], + [17.147363281250023, 48.00595703125], + [17.06660156250001, 47.707568359374996], + [16.421289062500023, 47.674462890624994], + [16.676562500000017, 47.536035156249994], + [16.44287109375, 47.39951171875], + [16.453417968750017, 47.006787109375], + [16.093066406250017, 46.86328125], + [15.957617187500006, 46.677636718749994], + [14.893261718750011, 46.605908203125], + [14.5498046875, 46.399707031249996], + [13.7, 46.520263671875], + [13.490039062500017, 46.555566406249994], + [13.3515625, 46.557910156249996], + [13.16875, 46.57265625], + [12.479199218750011, 46.672509765624994], + [12.38828125, 46.70263671875], + [12.330078125, 46.759814453124996], + [12.267968750000023, 46.835888671875], + [12.154101562500017, 46.93525390625], + [12.130761718750023, 46.98476562499999], + [12.16552734375, 47.028173828125], + [12.201269531250006, 47.060888671875], + [12.197167968750023, 47.075], + [12.16943359375, 47.08212890625], + [11.775683593750017, 46.986083984375], + [11.527539062500011, 46.997412109375], + [11.433203125, 46.983056640624994], + [11.244433593750017, 46.97568359375], + [11.133886718750006, 46.936181640624994], + [11.0634765625, 46.859130859375], + [11.025097656250011, 46.79697265625], + [10.993261718750006, 46.777001953124994], + [10.92734375, 46.769482421875], + [10.828906250000017, 46.775244140625], + [10.759765625, 46.793310546875], + [10.689257812500017, 46.84638671875], + [10.579785156250011, 46.8537109375], + [10.479394531250023, 46.855126953124994], + [10.452832031250011, 46.86494140625], + [10.45458984375, 46.8994140625], + [10.414941406250023, 46.964404296874996], + [10.349414062500017, 46.98476562499999], + [10.133496093750011, 46.851513671875], + [9.580273437500011, 47.057373046875], + [9.527539062500011, 47.270751953125], + [9.625878906250023, 47.467041015625], + [9.524023437500006, 47.52421875], + [9.748925781250023, 47.575537109375], + [9.839160156250017, 47.552294921874996], + [9.971582031250023, 47.505322265625], + [10.034082031250023, 47.473583984375], + [10.059863281250017, 47.449072265625], + [10.066308593750023, 47.393359375], + [10.200292968750006, 47.363427734374994], + [10.183007812500023, 47.27880859375], + [10.369140625, 47.366064453125], + [10.40390625, 47.4169921875], + [10.439453125, 47.5515625], + [10.482812500000023, 47.541796875], + [10.65869140625, 47.547216796875], + [10.741601562500023, 47.52412109375], + [10.873046875, 47.52021484375], + [11.0419921875, 47.393115234374996], + [12.185644531250006, 47.61953125], + [12.203808593750011, 47.646728515625], + [12.196875, 47.70908203125], + [12.209277343750017, 47.71826171875], + [12.268359375000017, 47.702734375], + [12.353540736607165, 47.70264787946429], + [12.492553013392856, 47.68551897321428], + [12.685839843750017, 47.669335937499994], + [12.771386718750023, 47.639404296875], + [12.796191406250017, 47.60703125], + [12.781152343750023, 47.5904296875], + [12.7828125, 47.56416015625], + [12.809375, 47.5421875], + [12.87890625, 47.5064453125], + [12.968066406250017, 47.47568359375], + [13.014355468750011, 47.478076171874996], + [13.031542968750017, 47.5080078125], + [13.047949218750006, 47.579150390624996], + [13.054101562500023, 47.655126953125], + [12.897656250000011, 47.721875], + [12.953515625000023, 47.890625], + [12.760351562500006, 48.106982421874996], + [13.215234375000023, 48.301904296874994], + [13.322851562500006, 48.33125], + [13.409375, 48.394140625], + [13.459863281250023, 48.56455078125], + [13.4716796875, 48.571826171874996], + [13.486621093750017, 48.581835937499996], + [13.636623883928596, 48.580904017857144], + [13.785351562500011, 48.587451171874996], + [13.798828125, 48.6216796875], + [13.802929687500011, 48.747509765625], + [13.814746093750017, 48.766943359375], + [14.049121093750017, 48.602490234375], + [14.691308593750023, 48.59921875], + [15.066796875000023, 48.997851562499996], + [16.057226562500006, 48.754785156249994], + [16.543554687500006, 48.796240234375], + [16.953125, 48.598828125] + ] + ] + }, + "properties": { "name": "Austria", "childNum": 1 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [46.1144531250001, 38.877783203125034], + [45.4796875000001, 39.00625], + [44.81718750000002, 39.65043945312496], + [44.76826171875004, 39.70351562500005], + [45.03164062500005, 39.76513671874997], + [45.15283203125003, 39.58266601562502], + [45.456835937500074, 39.494482421875006], + [45.78447265625002, 39.54560546875001], + [45.76630859375004, 39.37846679687499], + [45.977441406249994, 39.24389648437503], + [46.1144531250001, 38.877783203125034] + ] + ], + [ + [ + [48.572851562500006, 41.84448242187503], + [49.45673828125004, 40.79985351562502], + [49.77597656250006, 40.583984375], + [50.18251953125005, 40.50478515625002], + [50.3659179687501, 40.279492187499955], + [49.91884765625005, 40.31640625000003], + [49.55117187499999, 40.19414062499999], + [49.3244140625001, 39.60834960937501], + [49.36279296875003, 39.349560546874955], + [49.16533203125002, 39.03027343750003], + [49.013476562500074, 39.13398437500001], + [48.85449218750003, 38.83881835937501], + [48.86875, 38.43549804687498], + [48.59267578125005, 38.41108398437498], + [47.99648437499999, 38.85375976562503], + [48.292089843750006, 39.01884765624999], + [48.10439453125005, 39.241113281249994], + [48.322167968749994, 39.39907226562502], + [47.995898437500074, 39.683935546875034], + [46.490625, 38.90668945312498], + [46.400292968749994, 39.1921875], + [46.584765625000074, 39.22368164062499], + [46.36523437500003, 39.402490234374994], + [46.48144531249997, 39.55517578125003], + [46.202050781249994, 39.59448242187503], + [45.57978515625004, 39.9775390625], + [45.8859375000001, 40.024853515624955], + [45.96464843750002, 40.233789062499966], + [45.45439453125002, 40.532373046874966], + [45.37890624999997, 40.67358398437506], + [45.5875, 40.846923828125], + [45.07050781250004, 41.075585937499966], + [45.188574218750006, 41.14741210937504], + [45.001367187499994, 41.29096679687498], + [45.2171875, 41.423193359375006], + [45.28095703125004, 41.449560546875034], + [46.086523437500006, 41.183837890625], + [46.43095703125002, 41.077050781249994], + [46.534375, 41.08857421875004], + [46.62636718750005, 41.15966796875006], + [46.66240234375002, 41.24550781250002], + [46.67255859375004, 41.28681640625001], + [46.61894531250002, 41.34375], + [46.30546875000002, 41.507714843749994], + [46.18427734375004, 41.70214843749997], + [46.42988281250004, 41.890966796875006], + [46.74931640625002, 41.812597656250006], + [47.31767578125002, 41.28242187500001], + [47.79101562499997, 41.19926757812502], + [48.572851562500006, 41.84448242187503] + ] + ] + ] + }, + "properties": { "name": "Azerbaijan", "childNum": 2 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [30.553613281250023, -2.400097656250011], + [30.53369140625, -2.42626953125], + [30.441992187500006, -2.613476562500011], + [30.424218750000023, -2.6416015625], + [30.47333984375001, -2.6943359375], + [30.42402343750001, -2.824023437500003], + [30.433496093750023, -2.87451171875], + [30.515039062500023, -2.917578125], + [30.604296875000017, -2.935253906250011], + [30.70947265625, -2.977246093750011], + [30.7802734375, -2.98486328125], + [30.811132812500006, -3.116406250000011], + [30.79023437500001, -3.274609375000011], + [30.4, -3.65390625], + [29.947265625, -4.307324218750011], + [29.7177734375, -4.455859375], + [29.403222656250023, -4.449316406250006], + [29.211816406250023, -3.833789062500003], + [29.224414062500017, -3.053515625], + [29.01435546875001, -2.72021484375], + [29.10205078125, -2.595703125], + [29.390234375, -2.80859375], + [29.698046875000017, -2.794726562500003], + [29.8681640625, -2.71640625], + [29.93017578125, -2.339550781250011], + [30.117285156250006, -2.416601562500006], + [30.408496093750017, -2.31298828125], + [30.553613281250023, -2.400097656250011] + ] + ] + }, + "properties": { "name": "Burundi", "childNum": 1 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [5.693554687500011, 50.774755859375006], + [5.993945312500017, 50.75043945312504], + [6.340917968750006, 50.451757812500034], + [6.116503906250045, 50.120996093749966], + [6.08906250000004, 50.15458984374996], + [6.054785156249977, 50.154296875], + [5.976269531250068, 50.167187499999955], + [5.866894531250068, 50.08281250000002], + [5.817382812500028, 50.01269531250003], + [5.7880859375, 49.96123046875002], + [5.744042968749994, 49.91962890624998], + [5.789746093749983, 49.53828125000001], + [5.50732421875, 49.51088867187502], + [4.867578125000051, 49.78813476562502], + [4.818652343750045, 50.153173828125034], + [4.545019531250063, 49.96025390624999], + [4.149316406250023, 49.971582031249994], + [4.174609375000017, 50.24648437500005], + [3.689355468750023, 50.30605468750002], + [3.595410156250068, 50.47734374999999], + [3.27333984375008, 50.53154296875002], + [3.10683593750008, 50.779443359374994], + [2.759375, 50.750634765624994], + [2.52490234375, 51.097119140624955], + [3.35009765625, 51.37768554687503], + [3.43251953125008, 51.24575195312505], + [3.902050781250011, 51.20766601562502], + [4.226171875000034, 51.38647460937503], + [5.03095703125004, 51.46909179687498], + [5.214160156250045, 51.278955078124966], + [5.796484375000034, 51.153076171875], + [5.693554687500011, 50.774755859375006] + ] + ] + }, + "properties": { "name": "Belgium", "childNum": 1 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [3.595410156250011, 11.6962890625], + [3.553906250000011, 11.631884765624989], + [3.490527343750017, 11.49921875], + [3.48779296875, 11.395410156249994], + [3.638867187500011, 11.176855468749991], + [3.65625, 11.154589843749989], + [3.6953125, 11.1203125], + [3.71640625, 11.07958984375], + [3.7568359375, 10.76875], + [3.83447265625, 10.607421875], + [3.771777343750017, 10.417626953124994], + [3.646582031250006, 10.408984374999989], + [3.60205078125, 10.004541015624994], + [3.3251953125, 9.778466796874994], + [3.044921875, 9.083837890624991], + [2.774804687500023, 9.048535156249997], + [2.703125, 8.371826171875], + [2.68603515625, 7.873730468749997], + [2.719335937500006, 7.616259765624989], + [2.7509765625, 7.541894531249994], + [2.78515625, 7.476855468749989], + [2.783984375000017, 7.443408203124989], + [2.765820312500011, 7.422509765624994], + [2.75048828125, 7.395068359374989], + [2.756738281250023, 7.067919921874989], + [2.721386718750011, 6.980273437499989], + [2.731738281250017, 6.852832031249989], + [2.7529296875, 6.771630859374994], + [2.774609375000011, 6.711718749999989], + [2.753710937500017, 6.661767578124994], + [2.735644531250017, 6.595703125], + [2.706445312500023, 6.369238281249991], + [1.62265625, 6.216796875], + [1.777929687500006, 6.294628906249997], + [1.530957031250011, 6.992431640625], + [1.624707031250011, 6.997314453125], + [1.600195312500006, 9.050048828125], + [1.3857421875, 9.361669921874991], + [1.330078125, 9.996972656249994], + [0.763378906250011, 10.386669921874997], + [0.900488281250006, 10.993261718749991], + [1.4267578125, 11.447119140624991], + [1.980371093750023, 11.418408203124997], + [2.38916015625, 11.897070312499991], + [2.366015625000017, 12.221923828125], + [2.805273437500006, 12.383837890624989], + [3.595410156250011, 11.6962890625] + ] + ] + }, + "properties": { "name": "Benin", "childNum": 1 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [0.217480468750011, 14.911474609374991], + [0.163867187500017, 14.497216796874994], + [0.382519531250011, 14.245800781249997], + [0.42919921875, 13.972119140624997], + [0.6181640625, 13.703417968750003], + [1.201171875, 13.357519531249991], + [0.988476562500011, 13.36484375], + [0.9873046875, 13.041894531249994], + [1.56494140625, 12.635400390624994], + [2.104589843750006, 12.701269531249991], + [2.226269531250011, 12.466064453125], + [2.072949218750011, 12.309375], + [2.38916015625, 11.897070312499991], + [1.980371093750023, 11.418408203124997], + [1.4267578125, 11.447119140624991], + [0.900488281250006, 10.993261718749991], + [0.49267578125, 10.954980468749994], + [-0.068603515625, 11.115625], + [-0.299462890624994, 11.166894531249994], + [-0.627148437499983, 10.927392578124994], + [-1.04248046875, 11.010058593749989], + [-2.829931640624977, 10.998388671874991], + [-2.914892578124977, 10.592333984374989], + [-2.791162109374994, 10.432421874999989], + [-2.780517578125, 9.745849609375], + [-2.765966796874977, 9.658056640624991], + [-2.706201171874994, 9.533935546875], + [-2.695849609374989, 9.481347656249994], + [-2.7171875, 9.457128906249991], + [-2.7666015625, 9.424707031249994], + [-2.816748046874977, 9.425830078124989], + [-2.875146484374994, 9.500927734374997], + [-2.90087890625, 9.534619140624997], + [-2.948144531249994, 9.610742187499994], + [-2.98828125, 9.687353515624991], + [-3.042626953124994, 9.720898437499997], + [-3.095800781249977, 9.752099609374994], + [-3.160693359374989, 9.849169921874989], + [-3.223535156249994, 9.895458984374997], + [-3.289697265624994, 9.882226562499994], + [-3.581152343749977, 9.92431640625], + [-3.790625, 9.9171875], + [-4.18115234375, 9.78173828125], + [-4.267187499999977, 9.743261718749991], + [-4.332226562499983, 9.645703125], + [-4.406201171874983, 9.647998046874989], + [-4.526611328125, 9.723486328124991], + [-4.625830078124977, 9.713574218749997], + [-4.721777343749977, 9.756542968749997], + [-5.262304687499977, 10.319677734374991], + [-5.523535156249977, 10.426025390625], + [-5.490478515625, 11.042382812499994], + [-5.250244140625, 11.375781249999989], + [-5.288134765624989, 11.827929687499989], + [-4.699316406249977, 12.076171875], + [-4.4287109375, 12.337597656249997], + [-4.480615234374994, 12.672216796874991], + [-4.227099609374989, 12.793701171875], + [-4.328710937499977, 13.119042968749994], + [-4.151025390624994, 13.306201171875003], + [-3.947314453124989, 13.402197265624991], + [-3.527636718749989, 13.182714843749991], + [-3.3017578125, 13.28076171875], + [-3.248632812499977, 13.658349609374994], + [-2.950830078124994, 13.6484375], + [-2.873925781249994, 13.950732421875003], + [-2.586718749999989, 14.227587890625003], + [-2.113232421874983, 14.16845703125], + [-1.97304687499999, 14.45654296875], + [-1.049560546875, 14.81953125], + [-0.760449218749983, 15.047753906249994], + [-0.235888671874989, 15.059423828124991], + [0.217480468750011, 14.911474609374991] + ] + ] + }, + "properties": { "name": "Burkina Faso", "childNum": 1 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [91.94921875000003, 21.50805664062503], + [91.85947265625012, 21.532958984375057], + [91.90771484374997, 21.722949218750017], + [91.94921875000003, 21.50805664062503] + ] + ], + [ + [ + [91.87382812500002, 21.832128906249977], + [91.8375976562501, 21.750244140625], + [91.85068359375012, 21.927050781250045], + [91.87382812500002, 21.832128906249977] + ] + ], + [ + [ + [91.15078125000005, 22.175195312499966], + [91.04472656250002, 22.10517578125001], + [91.0794921875, 22.519726562499983], + [91.15078125000005, 22.175195312499966] + ] + ], + [ + [ + [91.55673828125006, 22.38222656250005], + [91.41132812500004, 22.475683593750006], + [91.45605468749997, 22.61650390624999], + [91.55673828125006, 22.38222656250005] + ] + ], + [ + [ + [90.77763671875007, 22.089306640624983], + [90.51503906250005, 22.06513671875001], + [90.68046875000007, 22.327490234375006], + [90.50292968749997, 22.835351562499994], + [90.59648437500002, 22.863525390625057], + [90.86816406250003, 22.48486328125], + [90.77763671875007, 22.089306640624983] + ] + ], + [ + [ + [88.94072265625002, 26.24536132812497], + [88.97041015625004, 26.250878906250023], + [88.95195312500002, 26.412109375], + [89.01865234375012, 26.410253906249977], + [89.28925781250004, 26.03759765625], + [89.54990234375006, 26.005273437499994], + [89.57275390625003, 26.13232421875003], + [89.67089843750003, 26.21381835937504], + [89.8229492187501, 25.94140625000003], + [89.82490234375004, 25.56015625], + [89.80087890625012, 25.33613281250001], + [89.81406250000006, 25.305371093749955], + [89.86630859375012, 25.293164062499955], + [90.11962890625003, 25.21997070312497], + [90.61308593750002, 25.16772460937497], + [92.04970703125005, 25.16948242187499], + [92.46835937500006, 24.94414062499999], + [92.38496093750004, 24.848779296875023], + [92.25126953125007, 24.895068359375045], + [92.22832031250002, 24.88134765625], + [92.22666015625012, 24.77099609374997], + [92.11748046875002, 24.493945312500017], + [92.06416015625004, 24.374365234375006], + [91.84619140624997, 24.17529296875003], + [91.72656250000003, 24.20507812499997], + [91.35019531250012, 24.06049804687501], + [91.16044921875007, 23.66064453125], + [91.359375, 23.06835937500003], + [91.43623046875004, 23.19990234375001], + [91.55351562500002, 22.991552734375006], + [91.61953125, 22.97968750000001], + [91.75097656250003, 23.053515625000017], + [91.75419921875007, 23.287304687499955], + [91.79003906249997, 23.361035156249983], + [91.937890625, 23.504687500000017], + [91.92949218750007, 23.598242187499977], + [91.92958984375, 23.68598632812501], + [91.97851562500003, 23.691992187499977], + [92.04404296875006, 23.677783203125017], + [92.24609375000003, 23.683593750000057], + [92.33378906250002, 23.242382812499955], + [92.36162109375002, 22.929003906250074], + [92.46445312500006, 22.734423828125045], + [92.49140625000004, 22.685400390625006], + [92.5612304687501, 22.04804687500001], + [92.57490234375004, 21.978076171875045], + [92.58281250000002, 21.940332031249994], + [92.5934570312501, 21.46733398437499], + [92.63164062500002, 21.306201171875045], + [92.33056640624997, 21.439794921874977], + [92.17958984375005, 21.293115234375023], + [92.32412109375, 20.791845703125063], + [92.0560546875, 21.1748046875], + [91.86337890625012, 22.350488281249966], + [91.7970703125001, 22.297460937500006], + [91.48007812500006, 22.884814453125045], + [91.2162109375, 22.642236328124994], + [90.94560546875002, 22.597021484375034], + [90.65625, 23.025488281250006], + [90.60400390624997, 23.59135742187499], + [90.55566406249997, 23.42153320312505], + [90.26914062500012, 23.455859375000017], + [90.59091796875012, 23.266406250000045], + [90.43505859374997, 22.751904296874955], + [90.61611328125, 22.362158203125034], + [90.23056640625006, 21.82978515625004], + [90.07119140625005, 21.887255859375017], + [90.20957031250006, 22.156591796875006], + [89.95419921875006, 22.022851562500023], + [89.91806640625012, 22.11616210937501], + [89.98515625000002, 22.466406250000063], + [89.81191406250005, 21.983496093750006], + [89.56855468750004, 21.767431640625034], + [89.48320312500007, 22.275537109374994], + [89.50058593750006, 21.914355468750045], + [89.35371093750004, 21.72109375], + [89.09394531250004, 21.872753906249983], + [89.05, 22.274609374999983], + [88.92070312500002, 22.632031249999955], + [88.89970703125002, 22.843505859375057], + [88.85058593749997, 23.040527343750057], + [88.928125, 23.186621093750063], + [88.72441406250002, 23.254980468750034], + [88.69765625, 23.493017578125034], + [88.63574218749997, 23.55], + [88.56738281249997, 23.674414062500034], + [88.69980468750006, 24.002539062500006], + [88.71376953125, 24.069628906250017], + [88.72656250000003, 24.186230468749955], + [88.7335937500001, 24.23090820312501], + [88.72353515625, 24.27490234375], + [88.64228515625004, 24.325976562500017], + [88.49853515625003, 24.34663085937504], + [88.3375, 24.45385742187503], + [88.225, 24.460644531249983], + [88.14550781250003, 24.485791015624955], + [88.07910156249997, 24.549902343750063], + [88.02343750000003, 24.62783203125005], + [88.03027343749997, 24.66445312500005], + [88.0451171875001, 24.713037109374994], + [88.1498046875, 24.914648437500034], + [88.1888671875, 24.92060546875001], + [88.27949218750004, 24.881933593750034], + [88.31337890625005, 24.8818359375], + [88.37294921875, 24.961523437499977], + [88.45625, 25.18842773437504], + [88.57382812500006, 25.18789062499999], + [88.92978515625012, 25.222998046875063], + [88.94414062500002, 25.290771484375], + [88.85478515625002, 25.333544921875017], + [88.76914062500006, 25.490478515625], + [88.50244140624997, 25.537011718749994], + [88.14746093749997, 25.811425781250023], + [88.1066406250001, 25.841113281250045], + [88.15078125000005, 26.08715820312497], + [88.33398437499997, 26.257519531249955], + [88.44042968749997, 26.369482421875034], + [88.38623046875003, 26.471533203125034], + [88.35146484375005, 26.482568359374966], + [88.36992187500002, 26.564111328124994], + [88.51826171875004, 26.517773437499955], + [88.68281250000004, 26.291699218749983], + [88.94072265625002, 26.24536132812497] + ] + ] + ] + }, + "properties": { "name": "Bangladesh", "childNum": 6 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [28.585351562500023, 43.742236328124996], + [28.465429687500006, 43.389306640624994], + [28.133691406250023, 43.39560546875], + [27.92890625000001, 43.1861328125], + [27.88886718750001, 42.74970703125], + [27.484765625000023, 42.468066406249996], + [28.014453125000017, 41.969042968749996], + [27.47480468750001, 41.946875], + [27.294921875, 42.079541015625], + [27.24433593750001, 42.09326171875], + [27.01171875, 42.058642578124996], + [26.96875, 42.02685546875], + [26.884863281250006, 41.991845703124994], + [26.615332031250006, 41.964892578124996], + [26.549707031250023, 41.896728515625], + [26.51142578125001, 41.8263671875], + [26.3603515625, 41.8015625], + [26.327246093750006, 41.772802734375], + [26.31796875, 41.744677734374996], + [26.320898437500006, 41.716552734375], + [26.200585937500023, 41.743798828124994], + [26.107421875, 41.72568359375], + [26.085546875, 41.704150390624996], + [26.066015625, 41.673242187499994], + [26.1435546875, 41.521533203124996], + [26.155175781250023, 41.434863281249996], + [26.135351562500006, 41.3857421875], + [26.06640625, 41.35068359375], + [25.92333984375, 41.311914062499994], + [25.784960937500017, 41.330419921875], + [25.52705078125001, 41.2998046875], + [25.381933593750006, 41.26435546875], + [25.25117187500001, 41.243554687499994], + [24.773730468750017, 41.356103515624994], + [24.595996093750017, 41.442724609375], + [24.5693359375, 41.4673828125], + [24.51826171875001, 41.552539062499996], + [24.487890625, 41.555224609374996], + [24.056054687500023, 41.527246093749994], + [24.03291015625001, 41.469091796875], + [24.011328125, 41.46005859375], + [23.635156250000023, 41.386767578124996], + [23.53583984375001, 41.386035156249996], + [23.433398437500017, 41.398730468749996], + [23.3720703125, 41.3896484375], + [23.23984375, 41.3849609375], + [23.15595703125001, 41.322070312499996], + [22.916015625, 41.336279296875], + [23.00361328125001, 41.73984375], + [22.836816406250023, 41.993603515625], + [22.344042968750017, 42.31396484375], + [22.42207031250001, 42.328857421875], + [22.445703125000023, 42.359130859375], + [22.523535156250006, 42.440966796874996], + [22.53242187500001, 42.481201171875], + [22.524218750000017, 42.50390625], + [22.43623046875001, 42.6291015625], + [22.466796875, 42.84248046875], + [22.799902343750006, 42.985742187499994], + [22.976855468750017, 43.18798828125], + [22.85957031250001, 43.25234375], + [22.819726562500023, 43.300732421875], + [22.767578125, 43.354150390624994], + [22.554589843750023, 43.454492187499994], + [22.36962890625, 43.781298828124996], + [22.36542968750001, 43.862109375], + [22.399023437500006, 43.96953125], + [22.420800781250023, 44.007421875], + [22.452529688228115, 44.0510441391688], + [22.547921095934313, 44.113823956634434], + [22.688564844478098, 44.254306249271906], + [23.02851562500001, 44.077978515625], + [22.868261718750006, 43.947900390624994], + [22.919042968750006, 43.83447265625], + [25.4970703125, 43.670800781249994], + [26.2158203125, 44.007275390625], + [27.0869140625, 44.167382812499994], + [27.425390625, 44.0205078125], + [27.88427734375, 43.987353515624996], + [28.221972656250017, 43.772851562499994], + [28.585351562500023, 43.742236328124996] + ] + ] + }, + "properties": { "name": "Bulgaria", "childNum": 1 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [50.60722656250002, 25.883105468750003], + [50.57490234375001, 25.806787109374994], + [50.465917968750006, 25.965527343749997], + [50.46992187500001, 26.228955078124997], + [50.5859375, 26.24072265625], + [50.60722656250002, 25.883105468750003] + ] + ] + }, + "properties": { "name": "Bahrain", "childNum": 1 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [-73.02685546874994, 21.19238281250003], + [-73.16455078125003, 20.979150390625023], + [-73.68115234375003, 20.9755859375], + [-73.68037109374995, 21.103320312500017], + [-73.52309570312497, 21.190820312499966], + [-73.23535156249997, 21.15449218750004], + [-73.05849609375, 21.313378906249994], + [-73.02685546874994, 21.19238281250003] + ] + ], + [ + [ + [-73.041015625, 22.429052734375006], + [-72.74726562500001, 22.32739257812497], + [-73.16191406250002, 22.380712890625006], + [-73.041015625, 22.429052734375006] + ] + ], + [ + [ + [-74.20673828124998, 22.213769531250023], + [-74.27690429687499, 22.183691406250006], + [-73.906396484375, 22.527441406250063], + [-73.95419921874995, 22.71552734375001], + [-73.84995117187503, 22.731054687500063], + [-73.83652343749998, 22.538427734374977], + [-74.20673828124998, 22.213769531250023] + ] + ], + [ + [ + [-74.05751953124997, 22.723486328125034], + [-74.27460937499995, 22.71166992187503], + [-74.30703125, 22.83959960937497], + [-74.05751953124997, 22.723486328125034] + ] + ], + [ + [ + [-74.84047851562494, 22.894335937500017], + [-75.22333984374995, 23.165332031250074], + [-75.13056640624998, 23.267919921875006], + [-75.31596679687502, 23.668359374999966], + [-74.84047851562494, 22.894335937500017] + ] + ], + [ + [ + [-75.66455078124997, 23.45014648437501], + [-76.03710937500003, 23.60278320312503], + [-76.01044921875001, 23.671386718750057], + [-75.66455078124997, 23.45014648437501] + ] + ], + [ + [ + [-74.42944335937497, 24.068066406249955], + [-74.55092773437502, 23.96894531250001], + [-74.52690429687502, 24.105078125000034], + [-74.42944335937497, 24.068066406249955] + ] + ], + [ + [ + [-77.65771484374994, 24.249462890624955], + [-77.75527343750002, 24.163476562500023], + [-77.61538085937494, 24.216357421875045], + [-77.5615234375, 24.136816406250006], + [-77.57373046875, 23.739160156249994], + [-77.77128906249999, 23.752539062499977], + [-77.99990234374994, 24.219824218750063], + [-77.65771484374994, 24.249462890624955] + ] + ], + [ + [ + [-75.30839843749999, 24.2], + [-75.50322265624996, 24.139062500000023], + [-75.40893554687503, 24.265771484374994], + [-75.72666015625, 24.68935546875005], + [-75.30839843749999, 24.2] + ] + ], + [ + [ + [-77.34755859375, 25.013867187499983], + [-77.56191406249997, 25.030029296875], + [-77.27558593750001, 25.055761718750006], + [-77.34755859375, 25.013867187499983] + ] + ], + [ + [ + [-77.74384765625001, 24.70742187499999], + [-77.74521484375, 24.463476562500034], + [-78.04492187499997, 24.287451171875063], + [-78.14580078125002, 24.493457031250017], + [-78.36650390624993, 24.544189453125057], + [-78.435302734375, 24.627587890624994], + [-78.24272460937493, 24.65380859375], + [-78.21137695312495, 25.191259765624977], + [-77.97529296874998, 25.084814453125063], + [-77.74384765625001, 24.70742187499999] + ] + ], + [ + [ + [-76.64882812499994, 25.487402343750006], + [-76.34379882812496, 25.33203124999997], + [-76.12661132812497, 25.14052734375005], + [-76.16953125, 24.6494140625], + [-76.319970703125, 24.81767578124999], + [-76.21376953124994, 24.822460937499983], + [-76.160400390625, 25.119335937499983], + [-76.36928710937502, 25.312597656250006], + [-76.62070312499998, 25.43164062500003], + [-76.78066406249997, 25.426855468750006], + [-76.71083984374997, 25.564892578124983], + [-76.64882812499994, 25.487402343750006] + ] + ], + [ + [ + [-78.49287109375001, 26.729052734375017], + [-77.92246093749998, 26.69111328125001], + [-78.74365234374994, 26.50068359375004], + [-78.98564453124996, 26.689501953125045], + [-78.79804687500001, 26.58242187499999], + [-78.59711914062493, 26.797949218750006], + [-78.49287109375001, 26.729052734375017] + ] + ], + [ + [ + [-77.22563476562496, 25.904199218750023], + [-77.40317382812498, 26.02470703124996], + [-77.24677734374998, 26.156347656250034], + [-77.238623046875, 26.561132812500006], + [-77.510595703125, 26.845996093750045], + [-77.94375, 26.90356445312503], + [-77.53388671874995, 26.903417968750006], + [-77.06635742187501, 26.530175781249994], + [-77.03828124999998, 26.333447265624983], + [-77.16728515624996, 26.240332031250006], + [-77.22563476562496, 25.904199218750023] + ] + ] + ] + }, + "properties": { "name": "Bahamas", "childNum": 14 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [19.007128906250045, 44.86918945312502], + [19.348632812500057, 44.88090820312502], + [19.118457031250074, 44.359960937500006], + [19.583789062500017, 44.04345703125003], + [19.245019531249994, 43.96503906250004], + [19.495117187500057, 43.642871093750045], + [19.19433593749997, 43.533300781250006], + [19.164355468750017, 43.53544921874999], + [19.11279296874997, 43.52773437500002], + [19.080078125000057, 43.51772460937502], + [19.0283203125, 43.53251953125002], + [18.97421875, 43.54233398437498], + [18.95068359375, 43.52666015624999], + [19.036718750000034, 43.35732421875002], + [19.026660156250017, 43.292431640624955], + [18.97871093750001, 43.28540039062503], + [18.934667968750006, 43.339453125000034], + [18.85107421875003, 43.34633789062502], + [18.749218750000068, 43.283544921875006], + [18.67421875000008, 43.230810546875006], + [18.623632812500063, 43.027685546875034], + [18.488476562500068, 43.01215820312498], + [18.44384765625003, 42.96845703125004], + [18.46601562500001, 42.777246093749994], + [18.54589843750003, 42.64160156249997], + [18.436328125000017, 42.559716796874994], + [17.667578125000063, 42.897119140624994], + [17.585156250000068, 42.93837890625005], + [17.650488281250063, 43.006591796875], + [17.27382812500005, 43.44575195312501], + [16.300097656250017, 44.12451171875], + [16.10341796875008, 44.52099609375006], + [15.736621093750045, 44.76582031250001], + [15.788085937500057, 45.17895507812497], + [16.028320312500057, 45.18959960937502], + [16.29335937500005, 45.00883789062496], + [16.53066406250008, 45.21669921875002], + [16.918652343749983, 45.27656249999998], + [17.812792968750074, 45.078125], + [18.66259765625, 45.07744140624999], + [18.83642578125, 44.883251953124955], + [19.007128906250045, 44.86918945312502] + ] + ] + }, + "properties": { "name": "Bosnia and Herz.", "childNum": 1 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [28.14794921875, 56.142919921875], + [28.284277343750006, 56.055908203125], + [29.375, 55.938720703125], + [29.353417968750023, 55.784375], + [29.412988281250023, 55.724853515625], + [29.482226562500017, 55.6845703125], + [29.63007812500001, 55.751171875], + [29.6845703125, 55.7697265625], + [29.744140625, 55.77041015625], + [29.82392578125001, 55.7951171875], + [29.881640625000017, 55.832324218749996], + [29.93701171875, 55.845263671874996], + [30.04267578125001, 55.83642578125], + [30.23359375000001, 55.84521484375], + [30.625585937500006, 55.666259765625], + [30.906835937500006, 55.57001953125], + [30.90058593750001, 55.397412109375], + [30.82099609375001, 55.3302734375], + [30.810546875, 55.306982421875], + [30.814453125, 55.2787109375], + [30.87744140625, 55.2234375], + [30.958886718750023, 55.13759765625], + [30.97773437500001, 55.08779296875], + [30.97773437500001, 55.05048828125], + [30.829882812500017, 54.914990234375], + [30.804492187500017, 54.8609375], + [30.791015625, 54.806005859375], + [30.798828125, 54.783251953124996], + [30.984179687500017, 54.6958984375], + [31.12128906250001, 54.648486328124996], + [31.152148437500017, 54.625341796875], + [31.074804687500006, 54.491796875], + [31.18476562500001, 54.452978515625], + [31.299121093750017, 54.29169921875], + [31.403613281250017, 54.195947265625], + [31.62841796875, 54.111181640625], + [31.7919921875, 54.055908203125], + [31.825976562500017, 54.030712890625], + [31.837792968750023, 54.00078125], + [31.825292968750006, 53.935009765625], + [31.783007812500017, 53.85498046875], + [31.754199218750017, 53.81044921875], + [31.82080078125, 53.791943359375], + [31.9921875, 53.796875], + [32.20039062500001, 53.78125], + [32.45097656250002, 53.6533203125], + [32.70429687500001, 53.336328125], + [32.64443359375002, 53.32890625], + [32.57802734375002, 53.31240234375], + [32.469335937500006, 53.2703125], + [32.14199218750002, 53.091162109375], + [31.849707031250006, 53.106201171875], + [31.668261718750017, 53.200927734375], + [31.417871093750023, 53.196044921875], + [31.38837890625001, 53.184814453125], + [31.364550781250017, 53.138964843749996], + [31.30292968750001, 53.060888671875], + [31.2587890625, 53.01669921875], + [31.29511718750001, 52.989794921874996], + [31.35302734375, 52.933447265625], + [31.442773437500023, 52.86181640625], + [31.53515625, 52.7982421875], + [31.564843750000023, 52.759228515625], + [31.585546875, 52.532470703125], + [31.57734375000001, 52.312304687499996], + [31.6015625, 52.284814453125], + [31.64990234375, 52.26220703125], + [31.690625, 52.220654296875], + [31.758593750000017, 52.125830078125], + [31.76337890625001, 52.10107421875], + [31.57373046875, 52.10810546875], + [31.345996093750017, 52.10537109375], + [31.21796875000001, 52.050244140625], + [30.98066406250001, 52.046191406249996], + [30.845703125, 51.953076171875], + [30.755273437500023, 51.895166015625], + [30.667285156250017, 51.814111328125], + [30.583886718750023, 51.68896484375], + [30.533007812500017, 51.596337890624994], + [30.56074218750001, 51.531494140625], + [30.602343750000017, 51.471240234374996], + [30.611718750000023, 51.40634765625], + [30.63251953125001, 51.355419921875], + [30.449511718750017, 51.274316406249994], + [30.160742187500006, 51.477880859375], + [29.346484375000017, 51.382568359375], + [29.10205078125, 51.6275390625], + [29.06074218750001, 51.625439453125], + [29.013085937500023, 51.598925781249996], + [28.97773437500001, 51.57177734375], + [28.927539062500017, 51.562158203124994], + [28.849511718750023, 51.540185546874994], + [28.73125, 51.433398437499996], + [28.690234375000017, 51.438867187499994], + [28.647753906250017, 51.45654296875], + [28.599023437500023, 51.542626953124994], + [28.532031250000017, 51.562451171875], + [27.85859375000001, 51.5923828125], + [27.7, 51.477978515625], + [27.689746093750017, 51.572412109374994], + [27.296289062500023, 51.597412109375], + [27.270117187500006, 51.613574218749996], + [27.141992187500023, 51.75205078125], + [27.074121093750023, 51.76083984375], + [26.95283203125001, 51.75400390625], + [26.7734375, 51.770703125], + [25.785742187500006, 51.923828125], + [24.361914062500006, 51.867529296875], + [24.280078125000017, 51.774707031249996], + [24.126855468750023, 51.6646484375], + [23.978320312500017, 51.59130859375], + [23.951171875, 51.58505859375], + [23.8642578125, 51.623974609375], + [23.79169921875001, 51.637109375], + [23.706835937500017, 51.64130859375], + [23.61376953125, 51.525390625], + [23.605273437500017, 51.517919921875], + [23.652441406250006, 52.040380859375], + [23.175097656250017, 52.28662109375], + [23.915429687500023, 52.770263671875], + [23.484667968750017, 53.939794921875], + [23.55908203125, 53.91982421875], + [23.733691406250017, 53.912255859375], + [24.191308593750023, 53.950439453125], + [24.236621093750017, 53.919970703124996], + [24.31796875, 53.89296875], + [24.620703125, 53.979833984375], + [24.768164062500006, 53.974658203124996], + [24.78925781250001, 53.9982421875], + [24.82568359375, 54.118994140625], + [24.869531250000023, 54.145166015625], + [25.04609375000001, 54.133056640625], + [25.111425781250006, 54.154931640625], + [25.179492187500017, 54.2142578125], + [25.46113281250001, 54.292773437499996], + [25.505664062500017, 54.264941406249996], + [25.52734375, 54.215136718749996], + [25.497363281250017, 54.175244140625], + [25.573046875000017, 54.139892578125], + [25.765234375, 54.17978515625], + [25.702539062500023, 54.29296875], + [25.61689453125001, 54.310107421874996], + [25.557519531250023, 54.310693359375], + [25.54736328125, 54.331835937499996], + [25.56757812500001, 54.37705078125], + [25.62031250000001, 54.460400390625], + [25.68515625, 54.535791015625], + [25.72480468750001, 54.564257812499996], + [25.73164062500001, 54.590380859374996], + [25.722460937500017, 54.71787109375], + [25.859277343750023, 54.919287109375], + [25.964453125, 54.94716796875], + [26.09296875000001, 54.9623046875], + [26.175195312500023, 55.003271484375], + [26.250781250000017, 55.12451171875], + [26.291796875000017, 55.139599609375], + [26.601171875, 55.130175781249996], + [26.6484375, 55.20419921875], + [26.775683593750017, 55.273095703125], + [26.760156250000023, 55.293359375], + [26.68125, 55.306445312499996], + [26.49531250000001, 55.318017578125], + [26.457617187500006, 55.34248046875], + [26.469531250000017, 55.371923828125], + [26.51923828125001, 55.44814453125], + [26.56660156250001, 55.546484375], + [26.5908203125, 55.62265625], + [26.593554687500017, 55.667529296874996], + [27.052539062500017, 55.83056640625], + [27.576757812500006, 55.798779296875], + [28.14794921875, 56.142919921875] + ] + ] + }, + "properties": { "name": "Belarus", "childNum": 1 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [-87.8529296875, 17.4228515625], + [-87.92998046874996, 17.283007812500017], + [-87.826416015625, 17.546289062499994], + [-87.8529296875, 17.4228515625] + ] + ], + [ + [ + [-88.89404296875, 15.890625], + [-89.2328125, 15.888671875], + [-89.16147460937503, 17.81484375], + [-89.13354492187503, 17.970800781249977], + [-88.80634765624998, 17.965527343749983], + [-88.52299804687499, 18.445898437500063], + [-88.29565429687494, 18.47241210937503], + [-88.34926757812494, 18.358837890624983], + [-88.1302734375, 18.350732421875023], + [-88.08525390624999, 18.226123046875045], + [-88.27172851562494, 17.60986328125], + [-88.203466796875, 17.5166015625], + [-88.31342773437501, 16.632763671874983], + [-88.89404296875, 15.890625] + ] + ] + ] + }, + "properties": { "name": "Belize", "childNum": 2 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [-64.73027343749999, 32.29345703125], + [-64.86284179687499, 32.273876953125], + [-64.66831054687499, 32.38193359375], + [-64.73027343749999, 32.29345703125] + ] + ] + }, + "properties": { "name": "Bermuda", "childNum": 1 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [-58.15976562499999, -20.164648437500006], + [-58.18017578125, -19.81787109375], + [-59.09052734375, -19.286230468750006], + [-60.00737304687499, -19.29755859375001], + [-61.7568359375, -19.6453125], + [-62.276318359375, -20.5625], + [-62.27666015624999, -21.066015625], + [-62.65097656249999, -22.233691406250003], + [-62.84335937499999, -21.99726562500001], + [-63.92167968749999, -22.028613281250003], + [-63.97612304687499, -22.072558593750003], + [-64.26640624999999, -22.603320312500003], + [-64.30791015624999, -22.7953125], + [-64.32529296874999, -22.82763671875], + [-64.373974609375, -22.761035156250003], + [-64.4455078125, -22.58535156250001], + [-64.477734375, -22.4853515625], + [-64.5236328125, -22.37158203125], + [-64.60551757812499, -22.228808593750003], + [-64.992626953125, -22.109667968750003], + [-65.518798828125, -22.09453125], + [-65.686181640625, -22.11025390625001], + [-65.77104492187499, -22.099609375], + [-65.86015624999999, -22.01972656250001], + [-66.05859375, -21.879492187500006], + [-66.098583984375, -21.835058593750006], + [-66.17465820312499, -21.8056640625], + [-66.220166015625, -21.802539062500003], + [-66.24760742187499, -21.83046875], + [-66.28212890625, -21.94746093750001], + [-66.3224609375, -22.053125], + [-66.365185546875, -22.11376953125], + [-66.71171874999999, -22.21630859375], + [-66.99111328125, -22.509863281250006], + [-67.19487304687499, -22.82167968750001], + [-67.362255859375, -22.85517578125001], + [-67.57993164062499, -22.891699218750006], + [-67.79443359375, -22.879492187500006], + [-67.87944335937499, -22.82294921875001], + [-67.88173828125, -22.49335937500001], + [-68.18642578125, -21.61855468750001], + [-68.197021484375, -21.30029296875], + [-68.558251953125, -20.901953125], + [-68.484326171875, -20.62841796875], + [-68.74516601562499, -20.45859375], + [-68.75932617187499, -20.115527343750003], + [-68.560693359375, -19.967089843750003], + [-68.559375, -19.90234375], + [-68.578271484375, -19.856542968750006], + [-68.69619140625, -19.74072265625], + [-68.69829101562499, -19.72109375], + [-68.57529296874999, -19.56015625], + [-68.462890625, -19.43281250000001], + [-68.470166015625, -19.409960937500003], + [-68.49199218749999, -19.381933593750006], + [-68.85795898437499, -19.093359375], + [-68.96831054687499, -18.96796875000001], + [-68.97885742187499, -18.81298828125], + [-69.026806640625, -18.65625], + [-69.09228515625, -18.28242187500001], + [-69.145458984375, -18.14404296875], + [-69.0939453125, -18.05048828125001], + [-69.28232421874999, -17.96484375], + [-69.31337890625, -17.943164062500003], + [-69.5109375, -17.50605468750001], + [-69.51108398437499, -17.5048828125], + [-69.510986328125, -17.46035156250001], + [-69.521923828125, -17.388964843750003], + [-69.645703125, -17.24853515625], + [-69.62485351562499, -17.2001953125], + [-69.020703125, -16.6421875], + [-69.03291015625, -16.47597656250001], + [-68.8427734375, -16.337890625], + [-69.21757812499999, -16.14912109375001], + [-69.4208984375, -15.640625], + [-69.17246093749999, -15.236621093750003], + [-69.37470703125, -14.962988281250006], + [-69.35947265624999, -14.7953125], + [-68.87089843749999, -14.169726562500003], + [-69.07412109375, -13.682812500000011], + [-68.97861328124999, -12.880078125000011], + [-68.68525390625, -12.501953125], + [-69.57861328125, -10.951757812500006], + [-69.228515625, -10.955664062500006], + [-68.84833984375, -11.011132812500009], + [-68.678369140625, -11.11279296875], + [-68.39799804687499, -11.01875], + [-68.0716796875, -10.703125], + [-67.99169921875, -10.674414062500006], + [-67.83500976562499, -10.662792968750011], + [-67.72177734374999, -10.68310546875], + [-67.416943359375, -10.389843750000011], + [-66.575341796875, -9.89990234375], + [-65.396142578125, -9.71240234375], + [-65.298583984375, -10.146777343750003], + [-65.31308593749999, -10.253027343750006], + [-65.395458984375, -10.392285156250011], + [-65.4369140625, -10.449023437500003], + [-65.44711914062499, -10.507421875], + [-65.33403320312499, -10.892773437500011], + [-65.32377929687499, -11.024804687500009], + [-65.389892578125, -11.246289062500011], + [-65.1857421875, -11.74951171875], + [-64.783447265625, -12.059375], + [-64.42050781249999, -12.439746093750003], + [-63.68857421874999, -12.47802734375], + [-63.3466796875, -12.680078125], + [-63.06748046874999, -12.669140625000011], + [-62.76547851562499, -12.997265625000011], + [-62.11801757812499, -13.159765625], + [-62.09477539062499, -13.241992187500003], + [-61.944726562499994, -13.40625], + [-61.87412109374999, -13.470410156250011], + [-61.789941406249994, -13.525585937500011], + [-61.57568359375, -13.524804687500009], + [-61.51157226562499, -13.541210937500011], + [-61.41606445312499, -13.526562500000011], + [-61.129150390625, -13.49853515625], + [-61.07700195312499, -13.48974609375], + [-60.506591796875, -13.78984375], + [-60.372705078124994, -14.41875], + [-60.273339843749994, -15.088769531250009], + [-60.402001953124994, -15.0927734375], + [-60.583203125, -15.098339843750011], + [-60.53046875, -15.143164062500006], + [-60.38046875, -15.318261718750009], + [-60.242333984374994, -15.479589843750006], + [-60.20664062499999, -15.901953125], + [-60.18720703125, -16.132128906250003], + [-60.17558593749999, -16.269335937500003], + [-58.53793945312499, -16.328222656250006], + [-58.49658203125, -16.32666015625], + [-58.42368164062499, -16.307910156250003], + [-58.37539062499999, -16.28359375], + [-58.345605468749994, -16.284375], + [-58.35039062499999, -16.490820312500006], + [-58.470605468749994, -16.650195312500003], + [-58.478125, -16.70068359375], + [-58.45981445312499, -16.910742187500006], + [-58.417382812499994, -17.08056640625], + [-58.39599609375, -17.23427734375001], + [-58.34775390624999, -17.28212890625001], + [-57.99091796875, -17.51289062500001], + [-57.905029296875, -17.532324218750006], + [-57.832470703125, -17.512109375], + [-57.78886718749999, -17.573046875], + [-57.780175781249994, -17.67177734375001], + [-57.66166992187499, -17.947363281250006], + [-57.58647460937499, -18.12226562500001], + [-57.49565429687499, -18.214648437500003], + [-57.57402343749999, -18.279296875], + [-57.725, -18.733203125], + [-57.783105468749994, -18.91425781250001], + [-57.716796875, -19.044042968750006], + [-58.131494140624994, -19.74453125], + [-57.860742187499994, -19.979589843750006], + [-57.887597656249994, -20.02041015625001], + [-57.96015625, -20.04072265625001], + [-58.021142578124994, -20.05517578125], + [-58.09375, -20.15107421875001], + [-58.15976562499999, -20.164648437500006] + ] + ] + }, + "properties": { "name": "Bolivia", "childNum": 1 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [-48.48588867187493, -27.76699218749998], + [-48.554589843749994, -27.81220703125004], + [-48.542187499999955, -27.57480468749999], + [-48.41489257812495, -27.399609375], + [-48.48588867187493, -27.76699218749998] + ] + ], + [ + [ + [-48.584423828124955, -26.401562499999983], + [-48.665771484375, -26.289648437500006], + [-48.53974609374998, -26.170312500000023], + [-48.584423828124955, -26.401562499999983] + ] + ], + [ + [ + [-45.26025390624997, -23.889160156249986], + [-45.451416015625, -23.895605468749977], + [-45.30234375, -23.727539062500014], + [-45.26025390624997, -23.889160156249986] + ] + ], + [ + [ + [-44.12929687499994, -23.14189453124999], + [-44.36015624999999, -23.17207031250001], + [-44.24287109374998, -23.074121093750037], + [-44.12929687499994, -23.14189453124999] + ] + ], + [ + [ + [-38.90356445312497, -13.473437499999974], + [-38.97758789062496, -13.523535156249963], + [-39.02216796874998, -13.445605468749989], + [-38.907128906249994, -13.401074218749983], + [-38.90356445312497, -13.473437499999974] + ] + ], + [ + [ + [-38.743847656249955, -13.097070312500037], + [-38.668115234374966, -12.880175781249989], + [-38.601171875, -12.99257812499998], + [-38.743847656249955, -13.097070312500037] + ] + ], + [ + [ + [-44.49931640625002, -2.939648437499983], + [-44.597753906250006, -3.037597656249943], + [-44.4814453125, -2.717578125000031], + [-44.49931640625002, -2.939648437499983] + ] + ], + [ + [ + [-44.88310546874996, -1.317871093749986], + [-45.020849609375034, -1.372363281249974], + [-44.978662109374966, -1.267285156249983], + [-44.88310546874996, -1.317871093749986] + ] + ], + [ + [ + [-51.83251953124997, -1.433789062499969], + [-51.938378906249966, -1.452636718749986], + [-51.680029296875006, -1.086132812500026], + [-51.546044921874966, -0.649609375], + [-51.25400390624998, -0.54140625], + [-51.16074218749998, -0.666699218750011], + [-51.27631835937498, -1.02177734374996], + [-51.83251953124997, -1.433789062499969] + ] + ], + [ + [ + [-49.62866210937497, -0.229199218749969], + [-49.11699218749999, -0.163574218750014], + [-48.39267578124995, -0.29736328125], + [-48.83359375, -1.390039062500023], + [-49.038476562499994, -1.5140625], + [-49.17270507812498, -1.41259765625], + [-49.233984375000034, -1.59951171874998], + [-49.50664062499999, -1.511621093750023], + [-49.587890625, -1.712402343749972], + [-49.805126953124955, -1.790234375000026], + [-50.06572265625002, -1.703808593749997], + [-50.50761718749999, -1.787988281250009], + [-50.759765625, -1.240234374999972], + [-50.72949218749997, -1.126757812499946], + [-50.57695312499999, -1.103125], + [-50.709619140624994, -1.07773437499999], + [-50.796093749999955, -0.90625], + [-50.6455078125, -0.27285156249998], + [-50.24824218749998, -0.11640625], + [-49.62866210937497, -0.229199218749969] + ] + ], + [ + [ + [-50.65288085937499, -0.131640624999989], + [-50.926367187500034, -0.327343749999983], + [-51.03808593749994, -0.225878906250003], + [-50.84218750000002, -0.050195312500009], + [-50.65288085937499, -0.131640624999989] + ] + ], + [ + [ + [-49.44389648437499, -0.112402343749977], + [-49.83007812499997, -0.093896484375023], + [-49.50346679687496, 0.083691406250011], + [-49.37231445312497, 0.001074218749963], + [-49.44389648437499, -0.112402343749977] + ] + ], + [ + [ + [-49.73823242187498, 0.26816406250002], + [-49.917089843750006, -0.023193359375014], + [-50.339453125, 0.043359375000051], + [-50.27265624999998, 0.231738281249974], + [-49.73823242187498, 0.26816406250002] + ] + ], + [ + [ + [-50.42612304687498, 0.139257812500048], + [-50.44394531249998, -0.007666015624949], + [-50.623925781249966, 0.054394531249983], + [-50.372753906249955, 0.590869140625031], + [-50.33227539062497, 0.259033203125028], + [-50.42612304687498, 0.139257812500048] + ] + ], + [ + [ + [-50.152929687500006, 0.393017578125054], + [-50.26132812499998, 0.359179687500003], + [-50.281689453124955, 0.51650390624998], + [-50.05883789062503, 0.638037109374963], + [-50.152929687500006, 0.393017578125054] + ] + ], + [ + [ + [-50.29897460937502, 1.93852539062496], + [-50.45610351562496, 1.910498046875034], + [-50.49101562499996, 2.128613281249969], + [-50.34199218749998, 2.14174804687498], + [-50.29897460937502, 1.93852539062496] + ] + ], + [ + [ + [-59.69970703125, 4.353515625], + [-59.73857421874993, 4.226757812500026], + [-59.62021484374998, 4.023144531250026], + [-59.557763671874966, 3.960009765625031], + [-59.551123046875034, 3.933544921874969], + [-59.854394531249994, 3.5875], + [-59.99433593749998, 2.689990234375031], + [-59.88964843749997, 2.362939453125009], + [-59.75522460937495, 2.27412109375004], + [-59.74350585937498, 2.12163085937496], + [-59.75175781249996, 1.962402343750028], + [-59.75620117187498, 1.900634765624972], + [-59.666601562500006, 1.746289062499969], + [-59.53569335937499, 1.7], + [-59.23120117187494, 1.376025390625031], + [-58.82177734374994, 1.201220703125031], + [-58.787207031250006, 1.208496093750014], + [-58.73032226562498, 1.247509765625054], + [-58.68461914062499, 1.28105468749996], + [-58.511865234374966, 1.284667968749986], + [-58.506054687499926, 1.438671875000011], + [-58.39580078124993, 1.481738281249989], + [-58.38037109375, 1.530224609375011], + [-58.34067382812498, 1.587548828125051], + [-58.03466796875, 1.520263671875014], + [-57.9828125, 1.648437500000014], + [-57.87343750000002, 1.667285156250045], + [-57.79565429687497, 1.7], + [-57.59443359375001, 1.704101562499986], + [-57.54575195312495, 1.726074218750028], + [-57.31748046874998, 1.963476562499991], + [-57.27558593749998, 1.959228515625014], + [-57.189599609374966, 1.981591796875037], + [-57.11889648437494, 2.013964843749974], + [-57.09267578125002, 2.005810546874997], + [-57.03759765625, 1.936474609374997], + [-56.96953124999999, 1.91640625], + [-56.48281249999994, 1.942138671874986], + [-56.019921874999966, 1.842236328124983], + [-55.96333007812498, 1.85708007812498], + [-55.929638671874955, 1.8875], + [-55.92163085937503, 1.976660156250006], + [-55.91533203124999, 2.039550781250028], + [-55.96196289062496, 2.09511718749998], + [-56.02006835937499, 2.15815429687504], + [-56.073632812499994, 2.236767578124969], + [-56.13769531249997, 2.259033203124986], + [-56.12939453124997, 2.299511718749969], + [-56.08779296875002, 2.341308593750043], + [-56.045117187499955, 2.364404296875037], + [-56.02036132812498, 2.392773437500054], + [-55.993505859375006, 2.497509765624983], + [-55.9755859375, 2.515966796875006], + [-55.957470703124955, 2.52045898437504], + [-55.730566406250006, 2.406152343750023], + [-55.385351562500006, 2.440625], + [-55.34399414062503, 2.488769531249972], + [-55.28603515625002, 2.49965820312498], + [-55.18769531249998, 2.547509765625037], + [-55.114111328125006, 2.539208984375037], + [-55.07031249999994, 2.548339843750028], + [-55.005810546874955, 2.592968749999983], + [-54.97866210937502, 2.597656250000043], + [-54.968408203124966, 2.548339843750028], + [-54.92656249999999, 2.497363281250045], + [-54.876074218750006, 2.450390624999969], + [-54.72221679687499, 2.441650390624972], + [-54.69741210937502, 2.359814453124997], + [-54.66186523437497, 2.327539062499994], + [-54.61625976562499, 2.326757812500006], + [-54.59194335937502, 2.313769531250031], + [-54.55048828125001, 2.293066406249991], + [-54.51508789062498, 2.245458984374963], + [-54.43310546875, 2.207519531250057], + [-54.13007812499998, 2.121044921875026], + [-53.76777343749998, 2.354833984375048], + [-52.90346679687502, 2.211523437499977], + [-52.58300781250003, 2.528906249999977], + [-52.327880859375, 3.18173828125002], + [-51.65253906249998, 4.061279296874972], + [-51.54707031250001, 4.31088867187502], + [-51.219921874999955, 4.093603515624991], + [-50.71440429687502, 2.134033203125], + [-50.458886718749994, 1.829589843749972], + [-49.957128906250006, 1.65986328125004], + [-49.898876953124955, 1.16298828124998], + [-50.29443359374997, 0.835742187500003], + [-50.755078124999955, 0.222558593749966], + [-51.28291015625001, -0.085205078125028], + [-51.98081054687498, -1.367968749999974], + [-52.22924804687497, -1.3625], + [-52.664160156250034, -1.551757812500028], + [-51.94755859374996, -1.586718749999946], + [-50.89492187500002, -0.937597656249963], + [-50.690039062500006, -1.761718749999986], + [-50.40322265625002, -2.015527343750009], + [-49.999218749999955, -1.831835937499974], + [-49.71953125000002, -1.926367187499963], + [-49.31367187500001, -1.731738281250003], + [-49.63652343749996, -2.656933593750026], + [-49.45751953125, -2.504589843749983], + [-49.21103515624998, -1.916503906249986], + [-48.99130859374998, -1.829785156249997], + [-48.71000976562496, -1.487695312500023], + [-48.46293945312499, -1.613964843749997], + [-48.349804687499926, -1.482128906249955], + [-48.46806640624996, -1.393847656250003], + [-48.44980468749998, -1.145507812499943], + [-48.11508789062498, -0.7375], + [-47.557324218749955, -0.669921874999957], + [-47.418652343749955, -0.765917968749974], + [-47.39809570312502, -0.626660156250026], + [-45.45859374999995, -1.35625], + [-45.32915039062496, -1.71728515625], + [-45.07636718749998, -1.466406249999949], + [-44.72114257812498, -1.733496093750006], + [-44.778515624999955, -1.798828125], + [-44.651269531249966, -1.745800781250026], + [-44.537792968749955, -2.052734374999943], + [-44.75634765624997, -2.265527343749952], + [-44.66240234375002, -2.373242187499955], + [-44.435449218749966, -2.168066406249991], + [-44.38183593749997, -2.365527343749989], + [-44.52011718749998, -2.40546875000004], + [-44.589013671874994, -2.573437499999983], + [-44.72304687500002, -3.204785156249997], + [-44.43754882812496, -2.944433593749977], + [-44.228613281250006, -2.471289062499949], + [-44.105566406250006, -2.493457031250031], + [-44.19267578124999, -2.809570312499943], + [-43.93291015624999, -2.583496093749986], + [-43.45512695312499, -2.502050781250006], + [-43.38007812499998, -2.376074218750006], + [-42.93671874999998, -2.465039062500011], + [-42.24960937499998, -2.7919921875], + [-41.876171874999926, -2.746582031249986], + [-41.479931640624955, -2.916503906249972], + [-40.474560546874926, -2.795605468750026], + [-39.96469726562498, -2.861523437499955], + [-38.475781249999955, -3.717480468749997], + [-38.04882812500003, -4.216406250000034], + [-37.626318359375006, -4.592089843750003], + [-37.30146484375001, -4.713085937499969], + [-37.174658203125006, -4.912402343749974], + [-36.590722656249966, -5.097558593749952], + [-35.549414062500006, -5.129394531249957], + [-35.39257812499994, -5.250878906250009], + [-34.833886718749994, -7.024414062500014], + [-34.83466796874998, -7.97148437499996], + [-35.34086914062499, -9.230664062499983], + [-35.76396484374993, -9.702539062500023], + [-35.890820312499926, -9.687011718749957], + [-35.88544921875001, -9.84765625], + [-36.39833984374994, -10.484082031249983], + [-36.768310546875, -10.671679687500017], + [-37.18281249999998, -11.06845703125002], + [-37.35600585937502, -11.403906249999977], + [-37.35922851562495, -11.252539062499963], + [-37.68872070312503, -12.1], + [-38.019238281249955, -12.591308593750028], + [-38.401757812499994, -12.966210937500023], + [-38.69096679687502, -12.623925781250009], + [-38.85175781250001, -12.790136718750034], + [-38.76372070312502, -12.9072265625], + [-38.835302734375034, -13.147167968750026], + [-39.030908203124994, -13.365136718750023], + [-39.08935546875, -13.588183593749989], + [-38.988623046875006, -13.61503906249996], + [-39.04814453124996, -14.043945312500028], + [-38.94233398437498, -14.030664062499994], + [-39.05957031249997, -14.654785156249957], + [-38.88061523437503, -15.864257812499972], + [-39.20288085937503, -17.178125], + [-39.154003906249926, -17.70390625000003], + [-39.650781249999966, -18.252343750000037], + [-39.78330078124998, -19.571777343749986], + [-40.001367187499994, -19.74199218750003], + [-40.39594726562501, -20.56943359375002], + [-40.78925781250001, -20.90605468750003], + [-40.954541015624926, -21.237890624999963], + [-41.04726562499999, -21.505664062499974], + [-41.00029296875002, -21.99902343750003], + [-41.70551757812498, -22.30966796874999], + [-41.980419921874955, -22.580664062499963], + [-42.042382812499966, -22.947070312500003], + [-42.95830078124996, -22.96708984374999], + [-43.154296875, -22.725195312500006], + [-43.22416992187502, -22.991210937500014], + [-43.898828124999966, -23.10146484375001], + [-43.97382812499998, -23.057324218749983], + [-43.675976562499955, -23.00947265625001], + [-43.86616210937498, -22.910546875000023], + [-44.63725585937496, -23.05546875], + [-44.67382812499994, -23.206640625000034], + [-44.56967773437495, -23.27402343749999], + [-45.32539062499998, -23.59970703124999], + [-45.464306640624955, -23.802539062500017], + [-45.97207031250002, -23.795507812500006], + [-46.86728515624998, -24.236328125000014], + [-47.989160156249994, -25.03574218749999], + [-47.92939453124998, -25.16826171874999], + [-48.20273437499998, -25.41650390625003], + [-48.18593749999994, -25.309863281249974], + [-48.402490234374994, -25.27207031249999], + [-48.47612304687499, -25.44296875], + [-48.73173828124993, -25.36875], + [-48.6921875, -25.49150390625003], + [-48.40117187500002, -25.59736328125001], + [-48.665771484375, -25.844335937499963], + [-48.576318359374994, -25.935449218749966], + [-48.61943359374996, -26.17939453125001], + [-48.74829101562503, -26.26865234374999], + [-48.55415039062498, -27.195996093749997], + [-48.62080078124998, -28.075585937499966], + [-48.799658203125006, -28.575292968749977], + [-49.27128906249999, -28.87119140625005], + [-49.745996093749966, -29.363183593749994], + [-50.299511718749955, -30.42578125000003], + [-50.92138671874997, -31.25839843750002], + [-52.039208984374994, -32.11484374999996], + [-52.063232421875, -31.830371093750017], + [-51.68066406249994, -31.774609375000026], + [-51.272167968749955, -31.476953125000037], + [-51.16142578124996, -31.11884765625001], + [-50.980078125000034, -31.09423828124997], + [-50.94082031249994, -30.903710937499966], + [-50.68930664062495, -30.70419921874999], + [-50.71630859374994, -30.425976562499983], + [-50.58193359375002, -30.438867187500037], + [-50.56352539062499, -30.25361328125004], + [-51.02495117187493, -30.36865234375003], + [-51.29804687499998, -30.03486328124997], + [-51.15727539062499, -30.364257812500014], + [-51.283056640625034, -30.751562499999963], + [-51.35908203124998, -30.674511718749983], + [-51.506298828124955, -31.104492187500014], + [-51.97246093749999, -31.383789062499986], + [-52.19355468749998, -31.885546874999974], + [-52.12739257812501, -32.1677734375], + [-52.652246093749994, -33.137792968750006], + [-53.37060546874997, -33.74218750000003], + [-53.39755859374995, -33.737304687500014], + [-53.46357421875001, -33.70986328125002], + [-53.531347656250034, -33.65546875000004], + [-53.531347656250034, -33.1708984375], + [-53.511865234374966, -33.10869140625003], + [-53.482861328124926, -33.068554687500026], + [-53.39521484375001, -33.01035156249998], + [-53.31010742187499, -32.927050781249974], + [-53.21406249999998, -32.82109375], + [-53.12558593749998, -32.73671875], + [-53.15727539062496, -32.680078125], + [-53.601708984374994, -32.40302734374997], + [-53.76171875, -32.05683593749997], + [-53.920605468749926, -31.95234375], + [-54.220556640625034, -31.855175781249997], + [-54.58764648437503, -31.48515625000003], + [-55.036035156249994, -31.27900390625004], + [-55.091162109375034, -31.31396484374997], + [-55.173535156249926, -31.279589843749974], + [-55.557324218749955, -30.8759765625], + [-55.60302734375003, -30.85078125000001], + [-55.62714843749998, -30.858105468749997], + [-55.650488281250034, -30.89208984375], + [-55.66523437500001, -30.92490234375002], + [-55.807763671874994, -31.036718749999977], + [-55.87368164062502, -31.069628906250017], + [-55.95200195312498, -31.08085937499999], + [-56.0046875, -31.079199218750006], + [-56.01845703125002, -30.991894531249983], + [-55.998974609374955, -30.837207031250003], + [-56.4072265625, -30.44746093750001], + [-56.83271484374998, -30.107226562499974], + [-57.120507812499994, -30.144433593749994], + [-57.21445312499995, -30.283398437499983], + [-57.55229492187496, -30.261230468749986], + [-57.60888671875003, -30.187792968750045], + [-57.563867187499994, -30.139941406249974], + [-57.40522460937501, -30.03388671875004], + [-57.22465820312499, -29.782128906249994], + [-56.938623046874994, -29.594824218750034], + [-55.890527343749994, -28.370019531249994], + [-55.68725585937497, -28.38164062499996], + [-55.72548828125002, -28.20410156250003], + [-55.10151367187501, -27.866796874999963], + [-54.82910156250003, -27.55058593750003], + [-54.32700195312495, -27.423535156249997], + [-53.83818359375002, -27.121093750000014], + [-53.668554687500006, -26.288183593749977], + [-53.89116210937499, -25.66884765625001], + [-54.15458984374999, -25.523046874999963], + [-54.44394531249998, -25.625], + [-54.615869140624994, -25.576074218750023], + [-54.61054687499998, -25.432714843750034], + [-54.47314453124997, -25.22021484375], + [-54.43623046875001, -25.12128906250001], + [-54.281005859375, -24.30605468750001], + [-54.31826171874994, -24.128125], + [-54.26689453124996, -24.06582031250001], + [-54.241796875, -24.047265624999966], + [-54.44023437500002, -23.90175781249998], + [-54.62548828125, -23.8125], + [-54.98266601562494, -23.974511718749966], + [-55.081884765625006, -23.997656249999977], + [-55.1943359375, -24.017480468750023], + [-55.28691406249993, -24.00429687499999], + [-55.366308593750034, -23.99101562499996], + [-55.41591796875002, -23.95136718749997], + [-55.4423828125, -23.86533203125002], + [-55.4423828125, -23.792578125000034], + [-55.458886718749966, -23.686718750000054], + [-55.51845703124994, -23.627246093750017], + [-55.53828124999998, -23.580957031249994], + [-55.61767578125, -22.67148437499999], + [-55.74663085937499, -22.51269531249997], + [-55.753271484375006, -22.410156250000043], + [-55.84916992187499, -22.307617187500014], + [-55.991406249999926, -22.28115234375005], + [-56.18984374999994, -22.28115234375005], + [-56.246044921874926, -22.26464843749997], + [-56.39487304687498, -22.092675781250023], + [-56.44780273437502, -22.07617187500003], + [-56.77519531249999, -22.261328125], + [-57.955908203125034, -22.109179687500003], + [-57.94267578124999, -21.79833984375], + [-57.830224609374994, -20.99794921875001], + [-57.91513671874998, -20.690332031249966], + [-57.97905273437493, -20.65732421874999], + [-58.00224609374996, -20.465429687499977], + [-58.02539062499997, -20.41582031249999], + [-58.05844726562495, -20.38613281249998], + [-58.091503906249926, -20.33320312500004], + [-58.124609375000034, -20.293457031250014], + [-58.13779296874995, -20.237304687500043], + [-58.15976562499998, -20.164648437499977], + [-58.09375, -20.15107421874997], + [-58.021142578124994, -20.05517578124997], + [-57.96015625000001, -20.04072265625004], + [-57.887597656249966, -20.020410156249994], + [-57.860742187499994, -19.97958984375002], + [-58.029931640624994, -19.83271484375004], + [-58.131494140624994, -19.74453125], + [-57.71679687499997, -19.044042968750034], + [-57.73085937499999, -18.91718750000004], + [-57.783105468749994, -18.91425781249997], + [-57.725, -18.73320312500003], + [-57.57402343749993, -18.279296875000014], + [-57.49565429687496, -18.21464843749999], + [-57.58647460937499, -18.122265625], + [-57.66166992187493, -17.94736328124999], + [-57.78017578125002, -17.67177734374998], + [-57.78886718750002, -17.573046875000017], + [-57.83247070312501, -17.512109375000037], + [-57.90502929687497, -17.53232421874999], + [-57.990917968749955, -17.512890625000026], + [-58.20556640625, -17.363085937499974], + [-58.347753906250006, -17.282128906249994], + [-58.39599609374997, -17.234277343750023], + [-58.417382812499994, -17.08056640624997], + [-58.459814453125006, -16.910742187500006], + [-58.478125, -16.70068359375003], + [-58.470605468749994, -16.650195312500045], + [-58.35039062500002, -16.49082031249999], + [-58.34560546875002, -16.284375], + [-58.375390624999966, -16.283593749999966], + [-58.423681640625034, -16.30791015625003], + [-58.49658203124994, -16.32666015625003], + [-58.537939453125034, -16.32822265624999], + [-60.17558593749996, -16.26933593749999], + [-60.187207031249955, -16.132128906250017], + [-60.206640625, -15.90195312500002], + [-60.242333984374994, -15.479589843750034], + [-60.38046874999998, -15.318261718750023], + [-60.53046874999998, -15.143164062499977], + [-60.58320312499998, -15.098339843749983], + [-60.273339843749994, -15.088769531249994], + [-60.372705078124994, -14.41875], + [-60.506591796875, -13.78984375], + [-61.077001953125034, -13.489746093750014], + [-61.129150390625, -13.498535156250028], + [-61.41606445312502, -13.526562499999969], + [-61.511572265625006, -13.541210937500011], + [-61.789941406249966, -13.525585937500026], + [-61.87412109374998, -13.470410156249983], + [-61.944726562499966, -13.40625], + [-62.09477539062499, -13.241992187499989], + [-62.118017578125006, -13.15976562500002], + [-62.765478515625034, -12.99726562500004], + [-63.01518554687502, -12.80556640624998], + [-63.067480468750006, -12.669140624999983], + [-63.34667968749994, -12.68007812499999], + [-63.68857421874998, -12.478027343749957], + [-64.42050781249995, -12.439746093749974], + [-64.783447265625, -12.059375], + [-65.18574218749998, -11.749511718749957], + [-65.389892578125, -11.246289062500011], + [-65.33403320312499, -10.892773437500026], + [-65.44711914062503, -10.507421875000034], + [-65.4369140625, -10.449023437499946], + [-65.39545898437498, -10.392285156250026], + [-65.31308593749998, -10.253027343749991], + [-65.29858398437497, -10.146777343750017], + [-65.39614257812494, -9.712402343749986], + [-66.57534179687502, -9.899902343749986], + [-67.41694335937495, -10.389843749999969], + [-67.72177734374998, -10.683105468749943], + [-67.83500976562496, -10.662792968749983], + [-67.99169921875, -10.674414062499949], + [-68.07167968749994, -10.703125], + [-68.39799804687499, -11.01875], + [-68.678369140625, -11.11279296875], + [-68.84833984374998, -11.01113281249998], + [-69.228515625, -10.955664062499963], + [-69.46254882812497, -10.948144531250023], + [-69.57861328125, -10.951757812499963], + [-69.67402343749998, -10.9541015625], + [-69.83979492187501, -10.93339843749996], + [-69.96035156249997, -10.92988281250004], + [-70.06630859374997, -10.982421875], + [-70.22006835937503, -11.04765625], + [-70.29038085937498, -11.064257812499974], + [-70.34199218750001, -11.066699218750017], + [-70.39228515624995, -11.058593749999972], + [-70.45087890624998, -11.024804687500009], + [-70.53325195312496, -10.946875], + [-70.59653320312498, -10.976855468750017], + [-70.642333984375, -11.010253906249986], + [-70.59916992187499, -9.620507812500009], + [-70.54111328124998, -9.4375], + [-70.60791015625, -9.463671875000031], + [-70.63691406249995, -9.478222656249969], + [-71.041748046875, -9.81875], + [-71.11528320312499, -9.852441406250009], + [-71.33940429687499, -9.988574218750031], + [-72.18159179687495, -10.003710937500003], + [-72.37905273437497, -9.51015625], + [-73.20942382812493, -9.411425781249946], + [-73.08984375, -9.26572265625002], + [-72.970361328125, -9.120117187500028], + [-72.97402343750002, -8.9931640625], + [-73.07050781249995, -8.8828125], + [-73.203125, -8.719335937499991], + [-73.30244140624995, -8.654003906250011], + [-73.36040039062496, -8.479296875000031], + [-73.39814453125001, -8.458984374999986], + [-73.54912109374993, -8.34580078125002], + [-73.73203125, -7.875390625], + [-73.72041015624993, -7.782519531250017], + [-73.76689453124999, -7.753515624999963], + [-73.82207031249996, -7.738964843750026], + [-73.89462890624998, -7.654785156250014], + [-73.946875, -7.611230468750023], + [-73.98173828124996, -7.58505859375002], + [-74.00205078125003, -7.556054687499966], + [-73.98173828124996, -7.535742187500006], + [-73.95849609374994, -7.506640625000031], + [-73.96430664062498, -7.378906250000028], + [-73.74946289062498, -7.335351562500037], + [-73.72041015624993, -7.309277343749969], + [-73.758203125, -7.172753906249952], + [-73.79301757812499, -7.135058593750003], + [-73.75810546874999, -6.90576171875], + [-73.137353515625, -6.4658203125], + [-73.23554687500001, -6.098437500000017], + [-73.209375, -6.028710937500023], + [-73.16289062499996, -5.933398437499974], + [-72.97988281249997, -5.634863281249991], + [-72.88706054687498, -5.122753906250026], + [-72.83193359374994, -5.09375], + [-72.69873046874997, -5.067187499999989], + [-72.60834960937495, -5.009570312499974], + [-72.46899414062497, -4.901269531250023], + [-72.35283203124993, -4.786035156249994], + [-72.25678710937501, -4.74892578124998], + [-71.8447265625, -4.504394531249986], + [-70.97368164062499, -4.350488281249994], + [-70.86601562499999, -4.229589843749963], + [-70.79951171874995, -4.173339843749957], + [-70.72158203124997, -4.15888671875004], + [-70.53066406249997, -4.167578125000034], + [-70.40463867187498, -4.150097656250026], + [-70.34365234375, -4.193652343750017], + [-70.31689453124994, -4.246972656250037], + [-70.23916015625002, -4.30117187499998], + [-70.12880859375, -4.286621093749943], + [-70.05332031249998, -4.333105468750006], + [-70.00395507812496, -4.327246093749963], + [-69.97202148437503, -4.30117187499998], + [-69.96591796875003, -4.2359375], + [-69.94819335937498, -4.200585937500009], + [-69.66904296875003, -2.667675781249997], + [-69.40024414062498, -1.194921874999977], + [-69.63398437500001, -0.50927734375], + [-70.07050781249993, -0.13886718750004], + [-70.05390624999993, 0.578613281250028], + [-69.47211914062498, 0.72993164062504], + [-69.15332031249994, 0.65878906250002], + [-69.31181640624999, 1.050488281249969], + [-69.85214843750003, 1.05952148437504], + [-69.84858398437493, 1.708740234375043], + [-68.17656249999999, 1.719824218749991], + [-68.25595703125, 1.845507812500017], + [-68.19379882812495, 1.987011718749983], + [-67.93623046874998, 1.748486328124969], + [-67.40043945312499, 2.116699218750028], + [-67.11923828124998, 1.703613281249986], + [-67.082275390625, 1.185400390625006], + [-66.87602539062499, 1.223046875000037], + [-66.34711914062498, 0.7671875], + [-66.06005859375003, 0.78535156250004], + [-65.68144531249999, 0.983447265624989], + [-65.52299804687493, 0.843408203124966], + [-65.55605468750002, 0.687988281250014], + [-65.47338867187497, 0.691259765624977], + [-65.10375976562497, 1.108105468749983], + [-64.20502929687493, 1.52949218750004], + [-64.00849609374995, 1.931591796874969], + [-63.43251953124994, 2.155566406250045], + [-63.389257812500006, 2.411914062500045], + [-64.04658203124998, 2.502392578124997], + [-64.22109375000002, 3.587402343749972], + [-64.66899414062496, 4.01181640625002], + [-64.788671875, 4.276025390625023], + [-64.57636718750001, 4.139892578125], + [-64.19248046874995, 4.126855468750009], + [-64.02148437500003, 3.929101562500051], + [-63.33867187500002, 3.943896484375045], + [-62.85698242187502, 3.593457031249969], + [-62.71210937499998, 4.01791992187502], + [-62.41064453124994, 4.156738281249972], + [-62.153125, 4.098388671874986], + [-61.82084960937496, 4.197021484375], + [-61.28007812500002, 4.516894531249974], + [-61.00283203125002, 4.535253906249991], + [-60.603857421875006, 4.94936523437498], + [-60.671972656250034, 5.164355468749989], + [-60.71196289062499, 5.191552734375023], + [-60.742138671874926, 5.202050781250037], + [-60.6513671875, 5.221142578125011], + [-60.45952148437499, 5.188085937500034], + [-60.40878906249998, 5.21015625], + [-60.33520507812497, 5.199316406250006], + [-60.241650390624926, 5.257958984374966], + [-60.14204101562498, 5.238818359374974], + [-59.990673828124955, 5.082861328124991], + [-60.14863281249998, 4.533251953125031], + [-59.69970703125, 4.353515625] + ] + ] + ] + }, + "properties": { "name": "Brazil", "childNum": 17 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [-59.493310546874994, 13.081982421874997], + [-59.611328125, 13.102099609374989], + [-59.6466796875, 13.303125], + [-59.427636718749994, 13.152783203124997], + [-59.493310546874994, 13.081982421874997] + ] + ] + }, + "properties": { "name": "Barbados", "childNum": 1 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [115.02675781250005, 4.899707031249989], + [115.1400390625, 4.899755859374991], + [115.290625, 4.352587890624989], + [115.10703125000006, 4.390429687499974], + [115.02675781250005, 4.899707031249989] + ] + ], + [ + [ + [115.02675781250005, 4.899707031249989], + [114.74667968750006, 4.718066406250017], + [114.84023437500005, 4.393212890625009], + [114.65410156250007, 4.037646484375045], + [114.0638671875, 4.592675781249966], + [114.42441406250006, 4.660400390625], + [114.99541015625002, 5.022363281250023], + [115.02675781250005, 4.899707031249989] + ] + ] + ] + }, + "properties": { "name": "Brunei", "childNum": 2 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [91.63193359375003, 27.759960937499997], + [91.5947265625, 27.557666015624996], + [91.74306640625002, 27.442529296874994], + [91.85126953125001, 27.438623046874994], + [91.95097656249999, 27.458300781249996], + [91.99082031250003, 27.4501953125], + [92.044921875, 27.364697265624997], + [92.08339843750002, 27.290625], + [92.03115234375002, 27.214306640624997], + [92.00253906250003, 27.147363281249994], + [91.99228515625003, 27.099902343749996], + [91.99863281250003, 27.079296875], + [92.03085937500003, 27.040820312499996], + [92.06816406249999, 26.9751953125], + [92.07343750000001, 26.91484375], + [92.04970703125002, 26.874853515625], + [91.99833984374999, 26.85498046875], + [91.84208984374999, 26.852978515624997], + [91.67158203125001, 26.802001953125], + [91.517578125, 26.807324218749997], + [91.45585937499999, 26.866894531249997], + [91.4267578125, 26.867089843749994], + [91.28652343750002, 26.789941406249994], + [90.73964843750002, 26.771679687499997], + [90.34589843750001, 26.890332031249997], + [90.2060546875, 26.847509765625], + [90.12294921875002, 26.754589843749997], + [89.94316406249999, 26.723925781249996], + [89.76386718750001, 26.7015625], + [89.60996093750003, 26.719433593749997], + [89.58613281250001, 26.778955078124994], + [89.33212890625003, 26.8486328125], + [89.14824218749999, 26.816162109375], + [89.04091796875002, 26.865039062499996], + [88.85761718750001, 26.961474609374996], + [88.73876953125, 27.175585937499996], + [88.76035156250003, 27.218115234375], + [88.88164062499999, 27.2974609375], + [88.89140624999999, 27.316064453124994], + [88.94755859374999, 27.464013671874994], + [89.48066406250001, 28.059960937499994], + [89.53691406249999, 28.107421875], + [89.65273437500002, 28.15830078125], + [89.74980468749999, 28.188183593749997], + [89.81689453125, 28.256298828124997], + [89.89785156250002, 28.294140625], + [89.98105468750003, 28.311181640624994], + [90.34824218750003, 28.243945312499996], + [90.36298828125001, 28.216503906249997], + [90.33310546875003, 28.093994140625], + [90.35273437500001, 28.080224609374994], + [90.47734374999999, 28.070849609374996], + [90.63007812500001, 28.078564453124997], + [90.71572265625002, 28.071728515624997], + [91.02080078124999, 27.970068359375], + [91.07773437500003, 27.974462890625], + [91.22587890624999, 28.071240234374997], + [91.27304687500003, 28.078369140625], + [91.30683593750001, 28.064013671874996], + [91.36757812500002, 28.021630859374994], + [91.64189453124999, 27.923242187499994], + [91.63193359375003, 27.759960937499997] + ] + ] + }, + "properties": { "name": "Bhutan", "childNum": 1 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [25.2587890625, -17.793554687500006], + [25.242285156250006, -17.969042968750003], + [25.939355468750023, -18.93867187500001], + [26.168066406250006, -19.53828125000001], + [27.17822265625, -20.10097656250001], + [27.28076171875, -20.47871093750001], + [27.679296875, -20.503027343750006], + [27.66943359375, -21.064257812500003], + [28.014062500000023, -21.55419921875], + [29.02558593750001, -21.796875], + [29.042382812500023, -22.018359375], + [29.237207031250023, -22.07949218750001], + [29.315234375000017, -22.15771484375], + [29.36484375, -22.193945312500006], + [29.1298828125, -22.21328125], + [29.013476562500017, -22.278417968750006], + [28.94580078125, -22.395117187500006], + [28.83984375, -22.480859375], + [28.21015625000001, -22.693652343750003], + [27.812597656250006, -23.108007812500006], + [27.7685546875, -23.14892578125], + [27.085546875, -23.577929687500003], + [26.835058593750006, -24.240820312500006], + [26.617773437500006, -24.3955078125], + [26.451757812500006, -24.58271484375001], + [26.39716796875001, -24.613574218750003], + [26.130859375, -24.671484375], + [26.031835937500006, -24.702441406250003], + [25.912109375, -24.74746093750001], + [25.518164062500006, -25.66279296875001], + [25.21337890625, -25.75625], + [24.33056640625, -25.74287109375001], + [24.19296875, -25.632910156250006], + [23.969531250000017, -25.626074218750006], + [23.89375, -25.600878906250003], + [23.389257812500006, -25.29140625], + [23.148730468750017, -25.288671875], + [22.878808593750023, -25.45791015625001], + [22.59765625, -26.13271484375001], + [22.548632812500017, -26.17841796875001], + [22.47089843750001, -26.219042968750003], + [22.217578125000017, -26.38886718750001], + [22.090917968750006, -26.580175781250006], + [22.01093750000001, -26.635839843750006], + [21.78828125000001, -26.710058593750006], + [21.738085937500017, -26.80683593750001], + [21.694726562500023, -26.840917968750006], + [20.73984375, -26.84882812500001], + [20.641406250000017, -26.7421875], + [20.79316406250001, -25.915625], + [20.4306640625, -25.147070312500006], + [19.98046875, -24.77675781250001], + [19.977343750000017, -22.00019531250001], + [20.9794921875, -21.9619140625], + [20.97412109375, -18.31884765625], + [23.219335937500006, -17.99970703125001], + [23.599707031250006, -18.4599609375], + [24.243945312500017, -18.0234375], + [24.530566406250017, -18.052734375], + [24.909082031250023, -17.821386718750006], + [25.2587890625, -17.793554687500006] + ] + ] + }, + "properties": { "name": "Botswana", "childNum": 1 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [22.86005859375001, 10.919677734375], + [23.646289062500017, 9.822900390624994], + [23.62265625, 9.340625], + [23.46826171875, 9.11474609375], + [23.53730468750001, 8.815820312499994], + [24.147363281250023, 8.665625], + [24.291406250000023, 8.29140625], + [24.853320312500017, 8.137548828124991], + [25.20039062500001, 7.807910156249989], + [25.18134765625001, 7.557226562499991], + [25.27890625, 7.427490234375], + [26.36181640625, 6.635302734374989], + [26.30859375, 6.455322265625], + [26.514257812500006, 6.069238281249994], + [27.143945312500023, 5.722949218749989], + [27.4033203125, 5.109179687499989], + [27.071875, 5.199755859374989], + [26.822070312500017, 5.062402343749994], + [25.52509765625001, 5.31210937499999], + [25.065234375000017, 4.967431640624994], + [24.31982421875, 4.994140625], + [23.41718750000001, 4.663134765624989], + [22.864550781250017, 4.723876953125], + [22.422167968750017, 4.134960937499997], + [20.55810546875, 4.462695312499989], + [20.226367187500017, 4.829638671874989], + [19.806542968750023, 5.089306640624997], + [19.5009765625, 5.127490234374989], + [19.06855468750001, 4.891406249999989], + [18.594140625000023, 4.346240234374989], + [18.6103515625, 3.478417968749994], + [18.474414062500017, 3.622998046874997], + [18.160937500000017, 3.499804687499989], + [17.491601562500023, 3.687304687499989], + [16.610742187500023, 3.50537109375], + [16.468554687500017, 2.831738281249997], + [16.183398437500017, 2.270068359374989], + [16.0634765625, 2.90859375], + [15.128710937500017, 3.826904296875], + [15.063574218750006, 4.284863281249997], + [14.73125, 4.602392578124991], + [14.56298828125, 5.279931640624994], + [14.616894531250011, 5.865136718749994], + [14.43115234375, 6.038720703124994], + [14.7392578125, 6.27978515625], + [15.206738281250011, 7.206152343749991], + [15.480078125, 7.523779296874991], + [15.957617187500006, 7.507568359375], + [16.37890625, 7.683544921874997], + [16.545312500000023, 7.865478515625], + [16.784765625, 7.550976562499997], + [17.6494140625, 7.98359375], + [18.56416015625001, 8.0458984375], + [19.108691406250017, 8.656152343749994], + [18.886035156250017, 8.836035156249991], + [18.95625, 8.938867187499994], + [20.342089843750017, 9.127099609374994], + [20.773242187500017, 9.405664062499994], + [21.682714843750006, 10.289843749999989], + [21.771484375, 10.642822265625], + [22.49384765625001, 10.996240234374994], + [22.86005859375001, 10.919677734375] + ] + ] + }, + "properties": { "name": "Central African Rep.", "childNum": 1 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [-59.78759765624997, 43.939599609374994], + [-60.11748046874996, 43.95336914062506], + [-59.727148437500006, 44.002832031249994], + [-59.78759765624997, 43.939599609374994] + ] + ], + [ + [ + [-66.7625, 44.68178710937502], + [-66.8970703125, 44.62890625], + [-66.80214843749994, 44.80537109374998], + [-66.7625, 44.68178710937502] + ] + ], + [ + [ + [-60.961572265624966, 45.48994140625001], + [-61.081738281249926, 45.55781249999998], + [-60.91245117187498, 45.56728515625005], + [-60.961572265624966, 45.48994140625001] + ] + ], + [ + [ + [-73.69531249999997, 45.58549804687502], + [-73.85771484375002, 45.573583984375006], + [-73.57236328124998, 45.69448242187502], + [-73.69531249999997, 45.58549804687502] + ] + ], + [ + [ + [-73.56650390625003, 45.469091796875034], + [-73.960546875, 45.44140624999997], + [-73.68745117187498, 45.561425781249994], + [-73.47607421874997, 45.704736328124994], + [-73.56650390625003, 45.469091796875034] + ] + ], + [ + [ + [-61.10517578124998, 45.94472656250002], + [-60.86523437499997, 45.983496093750006], + [-61.05903320312501, 45.70336914062497], + [-60.73789062499995, 45.75141601562498], + [-60.46059570312494, 45.96870117187501], + [-60.733300781249994, 45.956591796875045], + [-60.297949218750034, 46.31123046874998], + [-60.22646484374994, 46.19555664062506], + [-59.86503906249993, 46.159521484375006], + [-59.8421875, 45.941552734374994], + [-60.67294921874995, 45.59082031250006], + [-61.28369140624994, 45.573876953124966], + [-61.44980468749995, 45.71621093750002], + [-61.40864257812501, 46.17036132812498], + [-60.87016601562499, 46.796777343749966], + [-60.40820312500003, 47.00351562499998], + [-60.332910156249966, 46.737011718749955], + [-60.49453125000002, 46.270263671875], + [-61.10517578124998, 45.94472656250002] + ] + ], + [ + [ + [-63.811279296875, 46.46870117187501], + [-63.68144531249993, 46.561914062499994], + [-63.12939453125, 46.422216796875034], + [-62.02373046874999, 46.42158203125001], + [-62.52607421875001, 46.20288085937503], + [-62.531347656250034, 45.977294921875], + [-63.02207031249998, 46.06660156249998], + [-62.89453125000003, 46.12358398437496], + [-63.056347656249955, 46.22392578124996], + [-62.97846679687498, 46.31635742187498], + [-63.21347656249998, 46.15986328124998], + [-63.641015624999966, 46.23046874999997], + [-63.758642578125034, 46.397607421874994], + [-64.11083984375003, 46.425439453124994], + [-64.13603515624999, 46.59970703125006], + [-64.388037109375, 46.640869140625], + [-63.99355468750002, 47.06157226562502], + [-64.08789062499997, 46.77543945312499], + [-63.811279296875, 46.46870117187501] + ] + ], + [ + [ + [-61.91411132812496, 47.284521484375034], + [-61.77255859374998, 47.25981445312499], + [-62.00830078124994, 47.23427734375002], + [-61.924707031249966, 47.425146484375006], + [-61.3955078125, 47.63764648437504], + [-61.91411132812496, 47.284521484375034] + ] + ], + [ + [ + [-54.227148437500034, 47.44135742187501], + [-54.32597656250002, 47.408105468749994], + [-54.12817382812494, 47.646826171875034], + [-54.227148437500034, 47.44135742187501] + ] + ], + [ + [ + [-74.70888671874997, 45.0038574218751], + [-73.55810546875, 45.425097656250045], + [-73.1595703125, 46.01005859375002], + [-72.10927734374997, 46.55122070312504], + [-71.26118164062495, 46.75625], + [-70.51948242187501, 47.032519531250045], + [-69.47104492187503, 47.96728515625006], + [-68.23818359374994, 48.62641601562504], + [-66.17817382812493, 49.21313476562503], + [-64.83632812499994, 49.191748046875006], + [-64.2162109375, 48.873632812500034], + [-64.51372070312493, 48.84111328124999], + [-64.24609374999994, 48.69111328124998], + [-64.34882812500001, 48.423193359375034], + [-65.259423828125, 48.02124023437503], + [-65.92670898437495, 48.188867187499994], + [-66.70439453125002, 48.0224609375], + [-66.35961914062494, 48.06064453125006], + [-65.84941406250002, 47.91103515625005], + [-65.60722656249996, 47.67001953125006], + [-65.00166015624995, 47.84682617187502], + [-64.70322265625, 47.72485351562503], + [-64.91220703125003, 47.36865234375003], + [-65.31889648437502, 47.101220703124994], + [-64.831396484375, 47.06079101562503], + [-64.88251953124993, 46.822851562500034], + [-64.54150390625, 46.240332031250034], + [-63.91591796875002, 46.165820312500045], + [-63.831933593749966, 46.107177734375], + [-64.05639648437503, 46.021337890625006], + [-63.70288085937494, 45.858007812500034], + [-62.70068359374997, 45.740576171875006], + [-62.750097656250006, 45.64824218750002], + [-62.483056640624966, 45.62182617187506], + [-61.955517578124955, 45.86816406249997], + [-61.776513671874994, 45.655615234375006], + [-61.49228515624998, 45.68701171875], + [-61.350488281249966, 45.57368164062501], + [-61.28198242187494, 45.441064453124994], + [-61.46098632812502, 45.36669921875003], + [-61.03154296875002, 45.29174804687506], + [-63.306298828124994, 44.64257812500003], + [-63.60400390624997, 44.68320312500006], + [-63.60976562499999, 44.47998046875006], + [-63.999707031249926, 44.64492187499999], + [-64.10087890624993, 44.487451171874966], + [-64.1669921875, 44.58666992187503], + [-64.28608398437493, 44.55034179687499], + [-64.27568359374993, 44.33408203124998], + [-65.48168945312497, 43.51806640625], + [-65.73813476562498, 43.56074218750001], + [-65.88691406250001, 43.79521484374999], + [-66.125732421875, 43.813818359375034], + [-66.19306640624995, 44.143847656250045], + [-65.86801757812498, 44.56879882812501], + [-66.14638671875002, 44.43593750000005], + [-66.090625, 44.50493164062499], + [-64.44814453125, 45.33745117187502], + [-64.13549804687497, 45.023046875], + [-64.09316406249997, 45.21708984375002], + [-63.368017578125034, 45.36479492187502], + [-64.87314453124998, 45.35458984375006], + [-64.31464843749998, 45.83569335937503], + [-64.48222656250002, 45.80634765624998], + [-64.63271484375002, 45.94663085937506], + [-64.77851562499998, 45.63842773437497], + [-65.88447265624995, 45.22290039062506], + [-66.10976562500002, 45.316601562499955], + [-66.02656249999995, 45.417578125], + [-66.43984374999994, 45.09589843750001], + [-66.87246093749997, 45.067285156249966], + [-67.12485351562498, 45.16943359375], + [-67.366943359375, 45.17377929687498], + [-67.43266601562496, 45.603125], + [-67.80224609374994, 45.7275390625], + [-67.806787109375, 47.08281249999999], + [-68.23549804687502, 47.34594726562503], + [-68.93720703124998, 47.21123046875002], + [-69.0501953125, 47.426611328125034], + [-69.24287109374998, 47.46298828124998], + [-70.00771484375002, 46.70893554687501], + [-70.296240234375, 45.90610351562506], + [-70.86503906249999, 45.27070312500001], + [-71.327294921875, 45.29008789062496], + [-71.51752929687495, 45.00756835937497], + [-74.663232421875, 45.00390625000003], + [-74.70888671874997, 45.0038574218751] + ] + ], + [ + [ + [-126.09208984374995, 49.35400390625003], + [-126.06401367187499, 49.26362304687501], + [-126.22963867187498, 49.29565429687506], + [-126.09208984374995, 49.35400390625003] + ] + ], + [ + [ + [-54.55439453125001, 49.5888671875], + [-54.786523437499966, 49.496142578125045], + [-54.86357421875002, 49.576074218749966], + [-54.55439453125001, 49.5888671875] + ] + ], + [ + [ + [-54.093701171874955, 49.74443359374999], + [-53.98066406250001, 49.66196289062498], + [-54.28613281249997, 49.595361328124994], + [-54.27763671875002, 49.71147460937502], + [-54.093701171874955, 49.74443359374999] + ] + ], + [ + [ + [-126.64121093749999, 49.605810546875006], + [-126.93857421874999, 49.71845703125004], + [-126.92583007812497, 49.837744140625006], + [-126.73813476562502, 49.84365234375005], + [-126.64121093749999, 49.605810546875006] + ] + ], + [ + [ + [-61.801123046875034, 49.093896484374966], + [-63.04150390624994, 49.224951171875034], + [-64.485205078125, 49.88696289062497], + [-64.13144531249995, 49.94165039062503], + [-62.858544921874966, 49.70546875000005], + [-61.817138671875, 49.28354492187498], + [-61.69614257812495, 49.139013671875006], + [-61.801123046875034, 49.093896484374966] + ] + ], + [ + [ + [-125.18413085937497, 50.09711914062498], + [-125.301171875, 50.4140625], + [-125.07402343750002, 50.22065429687501], + [-125.18413085937497, 50.09711914062498] + ] + ], + [ + [ + [-127.19731445312495, 50.640380859375], + [-125.48208007812501, 50.316796874999966], + [-124.83061523437499, 49.53007812500002], + [-123.99580078125, 49.22402343750002], + [-123.49702148437498, 48.58208007812499], + [-123.38989257812501, 48.67021484374999], + [-123.31064453125003, 48.41103515625002], + [-123.57314453124995, 48.32280273437499], + [-123.91694335937501, 48.386572265625034], + [-125.12070312500002, 48.76079101562496], + [-124.84965820312496, 49.02827148437501], + [-124.81264648437497, 49.212646484375], + [-124.92734374999998, 49.01420898437499], + [-125.489453125, 48.933789062499955], + [-125.82851562499998, 49.09184570312499], + [-125.64423828125001, 49.18579101562506], + [-125.95166015625001, 49.24804687500003], + [-125.93540039062499, 49.401464843750006], + [-126.51914062499999, 49.396777343750045], + [-126.54189453125001, 49.590478515624966], + [-126.13408203124997, 49.672314453124955], + [-126.52524414062499, 49.71958007812498], + [-126.90332031250001, 49.94414062499999], + [-127.114306640625, 49.879736328125034], + [-127.24980468749999, 50.13798828124996], + [-127.34941406249995, 50.05195312500001], + [-127.46713867187503, 50.163427734375006], + [-127.86391601562495, 50.12773437500002], + [-127.90585937499998, 50.44521484375002], + [-127.48652343749998, 50.404638671875034], + [-127.46591796874996, 50.58310546875006], + [-128.05834960937494, 50.498486328124955], + [-128.34604492187503, 50.744238281250006], + [-127.91806640624998, 50.86054687500001], + [-127.19731445312495, 50.640380859375] + ] + ], + [ + [ + [-55.45874023437494, 51.53652343750005], + [-55.58339843749994, 51.38857421875002], + [-56.031103515625034, 51.328369140625], + [-55.8, 51.033300781250034], + [-56.732324218749966, 50.007714843749994], + [-56.822167968749966, 49.613476562499955], + [-56.179394531249955, 50.114990234375], + [-56.161279296874994, 49.94013671874998], + [-55.50292968749997, 49.98315429687503], + [-56.14018554687496, 49.61914062500006], + [-55.869824218749955, 49.67016601562506], + [-56.08730468750002, 49.45195312499999], + [-55.375927734374955, 49.48974609374997], + [-55.34384765624998, 49.37290039062506], + [-55.22954101562496, 49.508154296875006], + [-55.35317382812502, 49.07944335937506], + [-54.50219726562503, 49.52734375], + [-54.44824218749997, 49.329443359375006], + [-53.957714843749955, 49.44184570312498], + [-53.61943359374996, 49.321630859375006], + [-53.57343750000001, 49.141210937500034], + [-54.16127929687494, 48.787695312500034], + [-53.852880859375006, 48.81132812499996], + [-53.966015624999955, 48.70668945312505], + [-53.70634765624999, 48.65551757812503], + [-54.11445312499998, 48.393603515625045], + [-53.027587890625, 48.634716796874955], + [-53.1357421875, 48.40185546875003], + [-53.60976562500002, 48.20771484375001], + [-53.56943359374998, 48.088085937499955], + [-53.869580078124926, 48.019677734374966], + [-53.63823242187496, 48.01464843750003], + [-53.863671874999966, 47.787011718749994], + [-53.67236328125, 47.64824218749999], + [-53.28271484375, 47.99785156249996], + [-52.86601562499993, 48.11298828124998], + [-53.16982421875002, 47.51210937500005], + [-52.945019531249955, 47.55283203124998], + [-52.782421874999955, 47.769433593749966], + [-52.653662109375034, 47.549414062500006], + [-53.11484375, 46.65581054687502], + [-53.32304687499996, 46.71835937499998], + [-53.589794921874955, 46.638867187499955], + [-53.59736328124998, 47.14599609374997], + [-54.00957031249993, 46.839599609375], + [-54.173730468749994, 46.88037109375003], + [-53.84951171875002, 47.440332031249994], + [-53.98901367187503, 47.756201171875034], + [-54.191845703124955, 47.85981445312501], + [-54.488134765625006, 47.40385742187502], + [-54.47392578124996, 47.54707031249998], + [-54.856640624999955, 47.385009765625], + [-55.31572265624993, 46.905712890624955], + [-55.78852539062498, 46.86723632812502], + [-55.91923828124996, 47.01689453124996], + [-55.49150390624996, 47.16064453125003], + [-54.78461914062501, 47.664746093749955], + [-55.366308593750034, 47.66108398437501], + [-55.57612304687498, 47.46523437499999], + [-56.12724609374999, 47.50283203125002], + [-55.867089843749994, 47.592333984375045], + [-55.85791015625, 47.81918945312498], + [-56.774121093749955, 47.56499023437499], + [-58.33686523437501, 47.73085937500002], + [-59.11694335937494, 47.57070312499999], + [-59.32065429687498, 47.736914062500006], + [-59.272070312500034, 47.99555664062504], + [-58.330224609374994, 48.52211914062502], + [-59.16767578124998, 48.558496093749966], + [-58.84179687500003, 48.74643554687498], + [-58.906445312499955, 48.65019531249999], + [-58.716455078124994, 48.59804687500002], + [-58.403662109375034, 49.08432617187498], + [-57.99052734374996, 48.987939453124966], + [-58.09892578124993, 49.07744140624999], + [-57.98007812499998, 49.229638671874994], + [-58.19091796875003, 49.25874023437498], + [-58.21337890625, 49.38666992187501], + [-58.01582031249998, 49.54248046874997], + [-57.79130859374999, 49.48999023437503], + [-57.92617187499999, 49.700830078124994], + [-57.4326171875, 50.50581054687504], + [-57.179589843749966, 50.614843750000034], + [-57.29799804687502, 50.69873046874997], + [-57.03593750000002, 51.01083984374998], + [-56.68242187500002, 51.332763671875], + [-56.025585937499955, 51.56835937500006], + [-55.6904296875, 51.471337890624994], + [-55.666406249999966, 51.57890624999999], + [-55.45874023437494, 51.53652343750005] + ] + ], + [ + [ + [-127.92465820312498, 51.47387695312497], + [-128.14877929687498, 51.62670898437503], + [-128.03173828125006, 51.708398437499966], + [-127.92465820312498, 51.47387695312497] + ] + ], + [ + [ + [-79.38427734374997, 51.951953125000045], + [-79.64375, 52.01005859374996], + [-79.27128906249996, 52.086816406249966], + [-79.38427734374997, 51.951953125000045] + ] + ], + [ + [ + [-128.36875, 52.40087890625], + [-128.43979492187503, 52.696386718750006], + [-128.24726562499998, 52.784375], + [-128.36875, 52.40087890625] + ] + ], + [ + [ + [-80.73168945312494, 52.74726562499998], + [-82.03925781249998, 53.04990234374998], + [-81.84731445312494, 53.18627929687497], + [-81.135595703125, 53.20581054687503], + [-80.73168945312494, 52.74726562499998] + ] + ], + [ + [ + [-131.7537109375, 53.195556640625], + [-131.63466796874997, 52.92216796874999], + [-131.97177734374998, 52.87983398437498], + [-131.45522460937502, 52.70170898437502], + [-131.59057617187494, 52.578222656250006], + [-131.25971679687495, 52.415917968749966], + [-131.31992187499998, 52.30307617187498], + [-131.142626953125, 52.291113281250034], + [-131.221533203125, 52.15361328124999], + [-132.16508789062493, 52.783300781250034], + [-132.14375, 52.99931640624999], + [-132.54677734374997, 53.1375], + [-131.7537109375, 53.195556640625] + ] + ], + [ + [ + [-128.55244140624998, 52.93974609375002], + [-128.50991210937502, 52.51860351562502], + [-128.678955078125, 52.289648437500006], + [-128.74633789062494, 52.763378906249955], + [-128.89980468749997, 52.67382812500003], + [-129.175927734375, 52.964941406250006], + [-129.033251953125, 53.27993164062505], + [-128.63266601562498, 53.1125], + [-128.55244140624998, 52.93974609375002] + ] + ], + [ + [ + [-129.167724609375, 53.11787109374998], + [-129.32387695312502, 53.142138671875045], + [-129.23818359374997, 53.33007812500006], + [-129.167724609375, 53.11787109374998] + ] + ], + [ + [ + [-129.84858398437498, 53.167919921874955], + [-130.51757812500003, 53.54423828124999], + [-130.45200195312498, 53.63115234375002], + [-129.94472656250002, 53.436376953125034], + [-129.75483398437498, 53.244775390624994], + [-129.84858398437498, 53.167919921874955] + ] + ], + [ + [ + [-130.236279296875, 53.95854492187502], + [-130.38422851562504, 53.84394531250001], + [-130.703173828125, 53.892236328124994], + [-130.44799804687497, 54.08901367187502], + [-130.236279296875, 53.95854492187502] + ] + ], + [ + [ + [-132.65551757812503, 54.12749023437496], + [-132.30336914062497, 54.098876953125], + [-132.16611328124998, 53.95522460937505], + [-132.53466796875, 53.651708984375034], + [-132.18696289062504, 53.68481445312503], + [-132.134423828125, 54.03427734374998], + [-131.66762695312502, 54.14135742187503], + [-131.957421875, 53.308691406250034], + [-132.34726562500003, 53.18920898437503], + [-132.747509765625, 53.310498046874955], + [-132.425, 53.33696289062502], + [-132.84501953125, 53.507714843749994], + [-133.07949218749997, 53.837011718750034], + [-133.04838867187493, 54.15893554687497], + [-132.65551757812503, 54.12749023437496] + ] + ], + [ + [ + [-130.92714843749997, 54.47905273437499], + [-130.90683593750003, 54.63178710937504], + [-130.75800781249998, 54.61376953125], + [-130.92714843749997, 54.47905273437499] + ] + ], + [ + [ + [-130.57534179687497, 54.769677734374966], + [-130.2140625, 55.02587890625003], + [-130.34941406249996, 54.814550781250034], + [-130.57534179687497, 54.769677734374966] + ] + ], + [ + [ + [-79.97758789062499, 56.20703125000006], + [-80.057470703125, 56.28735351562497], + [-79.57973632812502, 56.466357421875045], + [-79.97758789062499, 56.20703125000006] + ] + ], + [ + [ + [-78.93559570312496, 56.26606445312498], + [-79.17548828124998, 55.88505859374999], + [-79.18212890625, 56.21215820312503], + [-79.4951171875, 55.87475585937503], + [-79.76474609374995, 55.80678710937505], + [-79.54472656249999, 56.12836914062501], + [-79.9875, 55.89213867187502], + [-79.45888671875, 56.53974609374998], + [-79.53632812499995, 56.180078124999966], + [-79.27241210937493, 56.600439453125006], + [-78.93559570312496, 56.26606445312498] + ] + ], + [ + [ + [-61.743603515624955, 57.55458984375005], + [-61.6375, 57.41606445312499], + [-62.01123046875003, 57.54848632812505], + [-61.743603515624955, 57.55458984375005] + ] + ], + [ + [ + [-79.71650390624998, 57.515527343749994], + [-79.80844726562498, 57.44243164062502], + [-79.74257812499997, 57.60795898437499], + [-79.71650390624998, 57.515527343749994] + ] + ], + [ + [ + [-69.16005859375, 59.04023437500001], + [-69.35283203125002, 58.96074218749999], + [-69.30322265625003, 59.144873046875006], + [-69.16005859375, 59.04023437500001] + ] + ], + [ + [ + [-64.40703125, 60.367089843749966], + [-64.44194335937496, 60.2978515625], + [-64.73793945312497, 60.37563476562502], + [-64.83642578124997, 60.50102539062499], + [-64.40703125, 60.367089843749966] + ] + ], + [ + [ + [-68.23378906250002, 60.24091796875001], + [-68.36787109374998, 60.314746093750045], + [-68.08759765624998, 60.58784179687501], + [-67.81884765624994, 60.449511718750074], + [-68.23378906250002, 60.24091796875001] + ] + ], + [ + [ + [-78.531640625, 60.72856445312499], + [-78.66889648437498, 60.716894531250006], + [-78.24169921875, 60.818652343750045], + [-78.531640625, 60.72856445312499] + ] + ], + [ + [ + [-64.83261718749998, 61.366064453125006], + [-65.43212890625, 61.649511718750034], + [-64.78964843750003, 61.662207031250034], + [-64.83261718749998, 61.366064453125006] + ] + ], + [ + [ + [-65.03056640624999, 61.879052734374966], + [-64.89658203124995, 61.73330078125005], + [-65.23535156249997, 61.89770507812506], + [-65.03056640624999, 61.879052734374966] + ] + ], + [ + [ + [-79.54531250000002, 62.41171875000006], + [-79.28647460937495, 62.247656250000034], + [-79.32392578124995, 62.02607421875001], + [-79.81611328124995, 61.59462890625002], + [-80.26518554687496, 61.818212890625006], + [-80.26005859374996, 62.10903320312502], + [-79.9267578125, 62.39287109375002], + [-79.54531250000002, 62.41171875000006] + ] + ], + [ + [ + [-64.82382812499998, 62.558740234374994], + [-64.46503906249998, 62.535937500000045], + [-64.47832031250002, 62.417871093749966], + [-64.901220703125, 62.421044921874994], + [-64.82382812499998, 62.558740234374994] + ] + ], + [ + [ + [-70.33706054687497, 62.548730468749994], + [-70.76606445312498, 62.596875], + [-71.22011718750002, 62.873925781249966], + [-70.44262695312497, 62.73378906250002], + [-70.33706054687497, 62.548730468749994] + ] + ], + [ + [ + [-82.00048828124997, 62.95419921874998], + [-82.02583007812498, 62.73007812499998], + [-82.56826171875002, 62.403222656249994], + [-83.01582031249998, 62.20991210937498], + [-83.69887695312497, 62.16025390624998], + [-83.91049804687498, 62.45415039062499], + [-83.37641601562498, 62.904931640624994], + [-82.00048828124997, 62.95419921874998] + ] + ], + [ + [ + [-77.87670898437497, 63.470556640625034], + [-77.53271484374997, 63.233642578125], + [-77.94243164062496, 63.11440429687502], + [-78.536767578125, 63.423730468749994], + [-77.87670898437497, 63.470556640625034] + ] + ], + [ + [ + [-76.67758789062503, 63.393945312499966], + [-77.36474609374994, 63.588330078124955], + [-77.13369140624997, 63.68203125000002], + [-76.65244140624998, 63.503564453124994], + [-76.67758789062503, 63.393945312499966] + ] + ], + [ + [ + [-84.91962890624995, 65.26108398437503], + [-84.50112304687497, 65.45844726562501], + [-84.08486328125, 65.21782226562502], + [-82.05, 64.64428710937506], + [-81.67612304687498, 64.21264648437503], + [-81.88710937499997, 64.01640625000002], + [-80.82895507812495, 64.08994140625], + [-80.30205078124999, 63.76220703125003], + [-81.04638671875003, 63.461572265624966], + [-82.378125, 63.706787109375], + [-82.46708984375002, 63.92695312500001], + [-83.30395507812497, 64.14379882812506], + [-84.63291015625, 63.30922851562502], + [-85.39262695312496, 63.119677734375045], + [-85.76894531249997, 63.70034179687502], + [-87.15190429687499, 63.58564453125001], + [-86.93203124999997, 63.90166015625002], + [-86.252099609375, 64.13686523437497], + [-86.37426757812503, 64.56582031249997], + [-86.074609375, 65.533837890625], + [-85.55468750000003, 65.91865234374995], + [-85.17622070312501, 65.746875], + [-85.23994140624993, 65.51030273437499], + [-84.91962890624995, 65.26108398437503] + ] + ], + [ + [ + [-84.67475585937498, 65.575], + [-85.096337890625, 65.756201171875], + [-85.14960937500001, 66.01538085937506], + [-84.75737304687496, 65.85893554687505], + [-84.67475585937498, 65.575] + ] + ], + [ + [ + [-83.72597656249997, 65.796728515625], + [-83.23374023437495, 65.71503906249995], + [-83.332421875, 65.63105468749998], + [-84.11826171874995, 65.77177734375007], + [-84.40717773437501, 66.13100585937497], + [-83.78696289062495, 65.96577148437498], + [-83.72597656249997, 65.796728515625] + ] + ], + [ + [ + [-108.09272460937501, 67.00517578124999], + [-107.80551757812493, 66.99858398437507], + [-107.94394531249999, 66.8578125], + [-108.09272460937501, 67.00517578124999] + ] + ], + [ + [ + [-62.681542968749966, 67.05629882812502], + [-62.87163085937499, 67.06259765625006], + [-62.41679687499996, 67.18847656250003], + [-62.681542968749966, 67.05629882812502] + ] + ], + [ + [ + [-107.89985351562497, 67.40180664062495], + [-107.95024414062503, 67.31821289062498], + [-108.15224609374997, 67.429443359375], + [-108.04897460937498, 67.664892578125], + [-107.89985351562497, 67.40180664062495] + ] + ], + [ + [ + [-73.621728515625, 67.783837890625], + [-74.573388671875, 67.82866210937507], + [-74.70654296875003, 68.06708984374995], + [-73.49375, 68.00063476562502], + [-73.40717773437498, 67.79306640625], + [-73.621728515625, 67.783837890625] + ] + ], + [ + [ + [-86.59555664062498, 67.7359375], + [-86.89252929687498, 67.836572265625], + [-86.95981445312503, 68.10024414062497], + [-86.70209960937501, 68.30561523437498], + [-86.42114257812497, 68.18344726562503], + [-86.59555664062498, 67.7359375] + ] + ], + [ + [ + [-75.67587890624998, 68.32250976562506], + [-75.078125, 68.17314453124999], + [-75.20195312499996, 67.45917968750001], + [-75.78007812499996, 67.28354492187503], + [-76.94418945312498, 67.25029296875002], + [-77.30439453125001, 67.68510742187505], + [-77.12587890624997, 67.94707031250002], + [-76.59580078124998, 68.27895507812497], + [-75.67587890624998, 68.32250976562506] + ] + ], + [ + [ + [-78.98271484374999, 68.19282226562501], + [-79.17475585937493, 68.26445312500002], + [-78.95258789062495, 68.35302734375006], + [-78.98271484374999, 68.19282226562501] + ] + ], + [ + [ + [-104.54067382812497, 68.405908203125], + [-105.05136718749999, 68.55903320312501], + [-104.60200195312503, 68.56152343749997], + [-104.54067382812497, 68.405908203125] + ] + ], + [ + [ + [-74.880859375, 68.34868164062505], + [-75.40024414062503, 68.52548828125], + [-75.28740234374996, 68.68774414062503], + [-74.98364257812497, 68.64760742187502], + [-74.880859375, 68.34868164062505] + ] + ], + [ + [ + [-101.84589843749994, 68.58632812499997], + [-102.30815429687497, 68.681982421875], + [-102.01337890624995, 68.82539062500001], + [-101.73295898437495, 68.75341796875], + [-101.84589843749994, 68.58632812499997] + ] + ], + [ + [ + [-100.21723632812497, 68.80668945312502], + [-100.59653320312496, 68.76640625000007], + [-100.56547851562495, 69.02680664062501], + [-100.21723632812497, 68.80668945312502] + ] + ], + [ + [ + [-99.99467773437502, 69.01352539062503], + [-100.19570312500002, 68.991455078125], + [-100.153125, 69.12949218750003], + [-99.99467773437502, 69.01352539062503] + ] + ], + [ + [ + [-79.21064453124995, 68.845458984375], + [-79.24267578125, 69.04926757812495], + [-78.33256835937496, 69.38603515624999], + [-78.77919921875, 68.95048828124999], + [-79.21064453124995, 68.845458984375] + ] + ], + [ + [ + [-90.1998046875, 69.419091796875], + [-90.33027343749993, 69.252197265625], + [-90.49204101562503, 69.369873046875], + [-90.1998046875, 69.419091796875] + ] + ], + [ + [ + [-76.99536132812503, 69.14375], + [-77.37939453125, 69.2740234375], + [-77.18754882812502, 69.440087890625], + [-76.66884765625002, 69.36616210937504], + [-76.99536132812503, 69.14375] + ] + ], + [ + [ + [-101.171728515625, 69.39707031250003], + [-101.31289062499998, 69.57607421875], + [-101.00063476562497, 69.4619140625], + [-101.171728515625, 69.39707031250003] + ] + ], + [ + [ + [-95.51367187499997, 69.57363281250002], + [-95.43745117187498, 69.37846679687505], + [-95.73012695312502, 69.34755859374997], + [-95.80620117187499, 69.56049804687501], + [-95.89345703125, 69.35175781250004], + [-95.87583007812495, 69.60600585937505], + [-95.51367187499997, 69.57363281250002] + ] + ], + [ + [ + [-67.91469726562494, 69.54096679687504], + [-68.22138671874998, 69.61674804687502], + [-67.908837890625, 69.68183593749995], + [-67.91469726562494, 69.54096679687504] + ] + ], + [ + [ + [-78.02910156249993, 69.71489257812502], + [-78.03999023437495, 69.6083984375], + [-78.84819335937502, 69.4828125], + [-78.02910156249993, 69.71489257812502] + ] + ], + [ + [ + [-79.43066406250003, 69.78779296874995], + [-79.55283203124995, 69.63085937500006], + [-80.04750976562502, 69.63432617187505], + [-79.97783203124993, 69.50966796874997], + [-80.794775390625, 69.68925781250005], + [-80.42421875000002, 69.797607421875], + [-79.43066406250003, 69.78779296874995] + ] + ], + [ + [ + [-97.439453125, 69.64267578125006], + [-96.29995117187494, 69.34438476562505], + [-95.7513671875, 68.89765624999998], + [-95.26777343749998, 68.82607421874997], + [-96.40156249999995, 68.47070312500003], + [-97.47202148437498, 68.543701171875], + [-98.320556640625, 68.84272460937498], + [-98.70380859374993, 68.80278320312502], + [-98.90449218749995, 68.93242187500005], + [-99.25400390625002, 68.86318359374997], + [-99.49467773437493, 68.95957031249998], + [-99.455712890625, 69.13120117187503], + [-98.45595703124997, 69.33466796875001], + [-98.54599609375, 69.57290039062497], + [-98.04135742187498, 69.456640625], + [-98.20048828124996, 69.79697265625006], + [-97.79072265624998, 69.86162109374999], + [-97.439453125, 69.64267578125006] + ] + ], + [ + [ + [-86.91303710937501, 70.11323242187501], + [-86.55766601562499, 69.99531249999995], + [-87.3232421875, 70.08012695312502], + [-86.91303710937501, 70.11323242187501] + ] + ], + [ + [ + [-74.70888671874997, 45.0038574218751], + [-74.76245117187494, 44.99907226562502], + [-74.99614257812496, 44.970117187499966], + [-75.40126953124997, 44.77226562499999], + [-75.81933593749997, 44.468017578125], + [-76.18579101562503, 44.24223632812502], + [-76.819970703125, 43.62880859375011], + [-77.59653320312492, 43.62861328125007], + [-78.45825195312497, 43.63149414062511], + [-78.72041015624993, 43.62495117187501], + [-78.84555664062492, 43.58334960937506], + [-79.171875, 43.466552734375085], + [-79.0830566406249, 43.33139648437509], + [-79.05922851562494, 43.27807617187506], + [-79.066064453125, 43.10610351562502], + [-79.02617187499996, 43.01733398437506], + [-78.98076171874993, 42.98061523437502], + [-78.91508789062496, 42.90913085937504], + [-79.17373046875, 42.74853515625], + [-80.24755859374991, 42.366015625000045], + [-81.02822265624997, 42.247167968750006], + [-81.50732421874997, 42.10346679687504], + [-81.97416992187496, 41.88872070312499], + [-82.43906249999989, 41.6748535156251], + [-82.69003906249995, 41.675195312499994], + [-83.141943359375, 41.97587890624996], + [-83.10952148437497, 42.25068359375001], + [-82.54531249999997, 42.62470703124998], + [-82.19038085937495, 43.47407226562501], + [-82.137841796875, 43.570898437500034], + [-82.48505859374993, 45.08374023437503], + [-82.55107421874987, 45.3473632812501], + [-82.91933593749994, 45.51796875000002], + [-83.59267578125, 45.81713867187506], + [-83.46948242187503, 45.99467773437499], + [-83.61596679687503, 46.116845703124994], + [-83.97778320312494, 46.08491210937507], + [-84.12319335937497, 46.50292968749997], + [-84.44047851562496, 46.49814453125006], + [-84.66577148437503, 46.54326171875002], + [-84.87597656249994, 46.89990234375003], + [-85.07006835937497, 46.97993164062498], + [-85.65224609375, 47.21997070312503], + [-86.67216796874996, 47.636425781249955], + [-87.20800781249997, 47.848486328125006], + [-87.74389648437497, 48.06054687500003], + [-88.37817382812497, 48.30307617187506], + [-89.45566406249992, 47.99624023437508], + [-90.79731445312495, 48.13105468750001], + [-91.04345703124991, 48.19370117187498], + [-91.38720703124997, 48.05854492187498], + [-92.00517578125002, 48.301855468750006], + [-92.3484375, 48.276611328125], + [-92.41459960937493, 48.276611328125], + [-92.50058593749995, 48.43535156250002], + [-92.83671875, 48.567773437499994], + [-93.25795898437497, 48.62885742187501], + [-93.37788085937498, 48.61655273437498], + [-93.70771484374995, 48.525439453125074], + [-93.85161132812496, 48.607275390625034], + [-94.6208984374999, 48.7426269531251], + [-94.71279296874997, 48.863427734374994], + [-94.80346679687497, 49.0029296875], + [-94.86040039062493, 49.258593750000045], + [-94.85434570312495, 49.304589843749994], + [-95.15527343749997, 49.3696777343751], + [-95.16206054687493, 48.991748046875045], + [-95.39790039062493, 48.99316406249997], + [-96.25068359374993, 48.99316406249997], + [-96.67705078124993, 48.99316406249997], + [-97.52983398437493, 48.99316406249997], + [-98.80898437499995, 48.99316406249997], + [-104.77832031249997, 48.993115234375125], + [-110.7476562499999, 48.993066406250136], + [-116.71704101562493, 48.993066406250136], + [-118.84892578124993, 48.993066406250136], + [-119.27534179687494, 48.993066406250136], + [-119.70170898437495, 48.99301757812495], + [-120.98085937499995, 48.99301757812495], + [-122.78876953124994, 48.99301757812495], + [-122.82670898437495, 49.028417968750034], + [-122.9241699218749, 49.07465820312504], + [-122.96269531249993, 49.07460937500005], + [-123.06328125, 48.97773437500001], + [-123.22944335937493, 49.260498046875085], + [-122.87910156249995, 49.39892578125003], + [-123.27675781249997, 49.34394531250001], + [-123.1875, 49.680322265624994], + [-123.53056640624989, 49.39731445312506], + [-124.02861328125002, 49.602880859375006], + [-123.99262695312497, 49.736181640625006], + [-123.81718749999993, 49.58657226562508], + [-123.58247070312498, 49.68125], + [-123.87441406250005, 49.736816406250114], + [-123.82543945312493, 50.14423828124998], + [-123.94589843749995, 50.18393554687509], + [-123.9849121093749, 49.87558593749998], + [-124.28125, 49.77211914062502], + [-124.78237304687492, 50.02011718749992], + [-125.05668945312495, 50.418652343750125], + [-124.8598632812499, 50.872412109375006], + [-125.05878906249993, 50.51386718749998], + [-125.4763183593749, 50.49716796874995], + [-125.53935546874996, 50.64902343749998], + [-125.64130859374994, 50.46621093750005], + [-126.09433593749995, 50.497607421875045], + [-126.44746093750004, 50.58774414062492], + [-125.90410156250002, 50.704931640625006], + [-126.51435546875, 50.679394531250125], + [-126.37460937499995, 50.83735351562498], + [-126.5217773437499, 50.86606445312498], + [-126.51733398437497, 51.0568359375001], + [-126.63178710937494, 50.915136718750006], + [-127.057568359375, 50.86752929687509], + [-127.70810546875, 51.15117187499996], + [-127.41967773437496, 51.608056640625136], + [-126.69145507812502, 51.70341796875002], + [-127.33872070312489, 51.70737304687495], + [-127.66870117187497, 51.47758789062502], + [-127.85053710937498, 51.67319335937509], + [-127.79536132812493, 52.19101562500006], + [-127.43793945312504, 52.356152343750125], + [-127.24223632812496, 52.39511718750009], + [-126.71396484374989, 52.060693359374994], + [-127.19399414062498, 52.45766601562502], + [-126.95136718749994, 52.7510253906251], + [-127.01933593750002, 52.8424804687501], + [-127.06621093749989, 52.65268554687498], + [-127.79189453124994, 52.28935546875002], + [-128.10224609374993, 51.78842773437495], + [-128.3576171875, 52.1588867187501], + [-128.0375, 52.318164062500045], + [-127.94023437499996, 52.545166015625085], + [-128.27153320312493, 52.3629882812501], + [-128.05327148437487, 52.91069335937496], + [-128.3650390624999, 52.82578125000006], + [-128.52470703125002, 53.1406738281251], + [-129.08090820312492, 53.36728515625006], + [-129.1715820312499, 53.53359375000002], + [-128.8545898437499, 53.70454101562504], + [-128.90561523437492, 53.559326171875114], + [-128.5421386718749, 53.420654296875114], + [-128.13271484375002, 53.417773437500045], + [-127.92783203125, 53.274707031250045], + [-128.2072265624999, 53.483203125000074], + [-128.67553710937494, 53.55458984375005], + [-128.76367187500003, 53.746875], + [-128.5321289062499, 53.85810546875007], + [-128.959375, 53.84145507812505], + [-129.2578613281249, 53.417968750000085], + [-129.56372070312506, 53.251464843750114], + [-130.33525390625002, 53.723925781250074], + [-130.04331054687495, 54.13354492187503], + [-129.62602539062493, 54.23027343750002], + [-130.08422851562503, 54.18139648437503], + [-130.4302734375, 54.42099609374998], + [-129.56064453124995, 55.46254882812508], + [-129.79516601562503, 55.559570312500114], + [-130.04848632812494, 55.05727539062511], + [-130.01406249999997, 55.950537109375006], + [-130.09785156249995, 56.10927734375002], + [-130.41313476562487, 56.12250976562507], + [-130.47709960937496, 56.230566406250034], + [-130.649072265625, 56.26367187500003], + [-131.471875, 56.55673828125006], + [-131.82426757812496, 56.58999023437508], + [-131.86616210937495, 56.792822265625006], + [-132.1042968749999, 56.85678710937509], + [-132.062890625, 56.95336914062503], + [-132.33798828124992, 57.07944335937498], + [-132.27939453124998, 57.14536132812506], + [-132.23217773437494, 57.198535156250074], + [-132.30166015625005, 57.2763183593751], + [-132.44248046874986, 57.40673828125003], + [-132.55048828124995, 57.499902343749994], + [-133.00141601562495, 57.948974609375], + [-133.27529296875, 58.22285156250004], + [-133.54638671874997, 58.50346679687499], + [-134.21850585937503, 58.849902343750045], + [-134.32963867187505, 58.93969726562506], + [-134.39306640625, 59.009179687499994], + [-134.67724609374997, 59.19926757812499], + [-134.94375, 59.28828125000001], + [-135.05102539062491, 59.57866210937502], + [-135.36787109374998, 59.743310546874994], + [-135.70258789062504, 59.72875976562506], + [-136.3218261718749, 59.604833984375034], + [-136.27797851562494, 59.48032226562506], + [-136.46635742187493, 59.459082031250006], + [-136.57875976562494, 59.15224609375002], + [-136.81328125000002, 59.15004882812511], + [-137.12622070312491, 59.04096679687507], + [-137.2775390624999, 58.988183593749994], + [-137.43857421874995, 58.903125], + [-137.52089843749994, 58.91538085937506], + [-137.59331054687493, 59.22626953124998], + [-138.317626953125, 59.611132812500074], + [-138.86875, 59.94575195312501], + [-139.18515624999986, 60.083593750000034], + [-139.13696289062494, 60.17270507812506], + [-139.07924804687497, 60.279443359375136], + [-139.07924804687497, 60.3437011718751], + [-139.23476562499997, 60.339746093749994], + [-139.67631835937505, 60.32832031249998], + [-139.97329101562497, 60.183154296875074], + [-140.45283203125004, 60.29970703125002], + [-140.5254394531249, 60.21835937499995], + [-140.76274414062505, 60.25913085937509], + [-141.00214843750004, 60.300244140625125], + [-141.00214843750004, 60.884667968749994], + [-141.00214843750004, 61.761279296875045], + [-141.00214843750004, 63.22226562499998], + [-141.00214843750004, 64.09887695312506], + [-141.00214843750004, 65.55991210937498], + [-141.00214843750004, 66.43652343750006], + [-141.00214843750004, 67.89755859374998], + [-141.00214843750004, 68.77416992187506], + [-141.00214843750004, 69.65078125000011], + [-139.18154296874997, 69.51552734375008], + [-137.25996093749998, 68.96411132812503], + [-136.12236328124993, 68.88222656250002], + [-135.258837890625, 68.68432617187503], + [-135.93901367187487, 68.9741699218751], + [-135.575537109375, 69.02695312500003], + [-135.91020507812487, 69.11147460937502], + [-135.6914550781249, 69.31118164062502], + [-135.29282226562486, 69.30786132812506], + [-135.1408203124999, 69.46782226562496], + [-134.45683593749993, 69.47763671875], + [-134.40893554687494, 69.68178710937502], + [-133.87978515624997, 69.50771484375011], + [-134.17431640624991, 69.25283203125005], + [-133.16313476562496, 69.43388671874999], + [-132.91533203125002, 69.62963867187506], + [-132.40390625, 69.65874023437496], + [-132.48847656249993, 69.73808593749996], + [-132.16342773437498, 69.70498046875014], + [-131.13637695312497, 69.90688476562505], + [-130.66547851562495, 70.12705078124998], + [-129.944970703125, 70.09091796875006], + [-129.675634765625, 70.19296875000009], + [-129.64829101562495, 69.9977539062501], + [-130.83208007812487, 69.65146484375006], + [-131.9377929687499, 69.5347167968751], + [-132.8174804687499, 69.20576171875004], + [-133.41831054687492, 68.84428710937493], + [-133.138037109375, 68.74658203125011], + [-133.33666992187497, 68.83525390625005], + [-132.57763671874997, 68.84780273437514], + [-132.71894531249998, 69.07919921875], + [-131.78837890625002, 69.43198242187495], + [-131.32470703124997, 69.36118164062509], + [-131.06342773437504, 69.45068359375003], + [-130.97065429687495, 69.20908203125], + [-130.1176269531249, 69.720068359375], + [-128.89892578124994, 69.96616210937506], + [-129.15791015624995, 69.80009765624999], + [-129.05434570312502, 69.70107421875005], + [-128.85302734375003, 69.7510253906251], + [-127.68378906249994, 70.26035156249995], + [-128.17011718749998, 70.41845703125], + [-127.99101562499992, 70.57382812500003], + [-127.22597656249992, 70.29614257812497], + [-126.25043945312495, 69.54526367187492], + [-125.52495117187495, 69.35156250000009], + [-125.171875, 69.42797851562503], + [-125.35693359374991, 69.62597656250003], + [-124.767919921875, 69.99003906249996], + [-124.99038085937494, 70.02661132812511], + [-124.55502929687488, 70.15122070312509], + [-124.40693359374991, 69.76743164062506], + [-124.12460937499995, 69.6899902343751], + [-124.33808593749991, 69.36484374999995], + [-123.5284179687499, 69.38935546874995], + [-123.02578125, 69.81000976562504], + [-122.07006835937499, 69.81616210937506], + [-120.96245117187502, 69.66040039062511], + [-120.13999023437488, 69.38056640625013], + [-117.22695312499998, 68.913427734375], + [-116.05947265625, 68.83701171875006], + [-116.2434082031249, 68.9740722656251], + [-115.44228515624994, 68.94091796875009], + [-114.62016601562496, 68.74609375], + [-113.96440429687495, 68.39907226562502], + [-114.09594726562491, 68.26679687500007], + [-114.76528320312494, 68.27021484375004], + [-115.12705078124992, 68.13203124999995], + [-115.43447265624994, 67.90234375000006], + [-115.13320312499994, 67.819189453125], + [-112.50302734374993, 67.6819335937501], + [-110.9900390624999, 67.79082031250007], + [-110.07392578124995, 67.99291992187506], + [-109.63037109374991, 67.73271484374996], + [-109.03803710937504, 67.69116210937503], + [-108.85200195312497, 67.42197265625009], + [-108.61333007812493, 67.59804687500008], + [-107.98872070312495, 67.2563964843751], + [-107.99130859374995, 67.09516601562513], + [-108.49604492187493, 67.09228515625006], + [-107.25947265624998, 66.39853515624995], + [-107.71035156250001, 66.74003906250007], + [-107.7250976562499, 66.98413085937506], + [-107.15649414062497, 66.88173828124997], + [-107.9583984375, 67.81860351562506], + [-107.79829101562498, 68.03691406249996], + [-106.42426757812491, 68.20058593750008], + [-105.7501953125, 68.59228515625011], + [-106.45805664062496, 68.51645507812495], + [-106.60849609374988, 68.35737304687504], + [-107.61933593749994, 68.3310546875], + [-107.73417968749989, 68.17373046875011], + [-108.3228027343749, 68.15410156250002], + [-108.71811523437488, 68.29746093750009], + [-108.31347656249996, 68.61079101562498], + [-106.16445312499992, 68.91987304687507], + [-105.68559570312489, 68.82817382812505], + [-105.3774414062499, 68.413818359375], + [-104.65317382812488, 68.23007812500003], + [-104.48681640624991, 68.06318359374998], + [-103.47412109374993, 68.11503906250005], + [-102.32036132812489, 67.73564453125005], + [-101.55498046874992, 67.69316406250007], + [-100.21293945312489, 67.83857421875004], + [-98.92045898437502, 67.72578124999998], + [-98.41210937499991, 67.80717773437505], + [-98.63154296875004, 68.0725585937501], + [-97.45493164062486, 67.61699218750002], + [-97.20654296874989, 67.85507812500003], + [-97.73911132812495, 67.97817382812505], + [-98.19252929687494, 67.92299804687502], + [-98.65048828124989, 68.36352539062506], + [-98.21855468750002, 68.31743164062507], + [-97.7942382812499, 68.38759765625], + [-97.9250976562499, 68.523681640625], + [-97.41035156249993, 68.49653320312498], + [-96.97670898437497, 68.25541992187505], + [-96.43066406249991, 68.3105957031251], + [-96.72207031250005, 68.03876953124998], + [-95.9703125, 68.24912109375], + [-96.36914062499991, 67.50976562500003], + [-96.14145507812489, 67.27182617187503], + [-95.71992187499998, 67.31679687500014], + [-95.77768554687495, 67.18461914062505], + [-95.41591796875005, 67.15556640624999], + [-95.41889648437504, 67.01323242187493], + [-96.42255859374995, 67.05175781249997], + [-95.7875488281249, 66.616796875], + [-96.03686523437489, 66.9375], + [-95.39965820312503, 66.94946289062509], + [-95.25874023437493, 67.26254882812492], + [-95.65048828124986, 67.73745117187505], + [-95.46069335937503, 68.02138671875], + [-94.74443359374993, 68.07089843749995], + [-93.44892578124998, 68.61889648437503], + [-93.85244140624994, 69.00034179687495], + [-94.06489257812495, 68.78476562500006], + [-94.600439453125, 68.80322265625011], + [-94.08364257812497, 69.12309570312507], + [-94.254736328125, 69.31376953125002], + [-93.61948242187492, 69.41699218750009], + [-93.74853515624991, 69.2261230468751], + [-93.5322753906249, 69.48090820312495], + [-94.2708007812499, 69.45512695312505], + [-94.63383789062496, 69.64965820312506], + [-94.82250976562494, 69.577783203125], + [-95.96494140624989, 69.80278320312499], + [-96.5513671875, 70.21030273437506], + [-96.29770507812492, 70.51137695312511], + [-95.87861328124998, 70.54897460937514], + [-95.88632812499986, 70.69428710937507], + [-96.25800781249993, 70.64228515625013], + [-96.54892578124995, 70.80874023437511], + [-96.44658203124996, 71.23989257812502], + [-96.06201171874997, 71.41386718749993], + [-95.5642578124999, 71.33676757812503], + [-95.40625, 71.49165039062498], + [-95.87231445312494, 71.57314453125005], + [-94.73486328124994, 71.98295898437507], + [-94.30834960937491, 71.76489257812506], + [-93.74628906249998, 71.742822265625], + [-92.94868164062493, 71.26210937500011], + [-92.98144531249994, 70.8522460937501], + [-91.56406249999995, 70.1782714843751], + [-92.32050781250004, 70.2353515625], + [-92.51186523437494, 70.10385742187503], + [-91.976708984375, 70.03867187500009], + [-92.88779296874989, 69.66821289062511], + [-92.31166992187494, 69.67290039062499], + [-91.91196289062495, 69.53125], + [-91.20180664062494, 69.64477539062494], + [-91.43994140624997, 69.52568359375002], + [-90.4155761718749, 69.45698242187507], + [-90.89228515625004, 69.26728515624995], + [-91.23720703125005, 69.28554687500014], + [-90.47900390624994, 68.88115234374999], + [-90.57363281250005, 68.47470703124998], + [-90.20478515625004, 68.25747070312511], + [-89.27954101562491, 69.25546875000003], + [-88.22353515625, 68.91503906249997], + [-87.81357421874986, 68.34570312499997], + [-87.89267578125, 68.24814453125], + [-88.34697265624993, 68.28828125000001], + [-88.313818359375, 67.95034179687508], + [-87.359375, 67.17724609374997], + [-86.56079101562491, 67.48212890625007], + [-85.64316406249992, 68.69970703124997], + [-84.86757812499994, 68.77333984375005], + [-85.10664062499995, 68.84404296875007], + [-84.86220703125, 69.07397460937503], + [-85.38676757812493, 69.23188476562504], + [-85.50737304687487, 69.84526367187493], + [-82.61835937499993, 69.69106445312514], + [-82.39023437499989, 69.60087890625007], + [-82.75483398437493, 69.49438476562506], + [-82.30986328124996, 69.41000976562509], + [-82.22753906249997, 69.24887695312495], + [-81.37783203125005, 69.18564453125003], + [-81.95791015624991, 68.88364257812498], + [-81.38090820312496, 68.85004882812504], + [-81.28154296874987, 68.65722656250003], + [-81.91484374999993, 68.4587890625001], + [-82.55268554687504, 68.44648437500007], + [-82.22241210937489, 68.145263671875], + [-82.0125, 68.19389648437496], + [-81.97646484374997, 67.86201171875001], + [-81.2943359375, 67.497412109375], + [-81.46757812499996, 67.0698730468751], + [-83.40644531249998, 66.37124023437508], + [-84.53847656249994, 66.97280273437505], + [-84.84575195312502, 67.02871093750008], + [-85.11372070312498, 66.90693359375013], + [-84.73774414062504, 66.93359375000006], + [-84.223046875, 66.68247070312506], + [-83.86904296875, 66.2135742187501], + [-84.29306640624995, 66.29179687500005], + [-84.628076171875, 66.20771484374998], + [-85.603857421875, 66.56826171875005], + [-86.708154296875, 66.52304687500009], + [-86.68510742187502, 66.36040039062499], + [-85.95874023437491, 66.11904296875002], + [-87.45288085937503, 65.33896484375009], + [-87.96997070312503, 65.34892578124999], + [-89.7494140625, 65.93603515625006], + [-89.88969726562487, 65.86855468749997], + [-91.42724609374994, 65.94790039062497], + [-91.04111328124989, 65.82983398437509], + [-90.98344726562496, 65.91923828124999], + [-89.92407226562497, 65.78027343750011], + [-88.97402343749994, 65.34829101562502], + [-87.02753906249995, 65.19809570312498], + [-88.10561523437497, 64.18330078125001], + [-88.81772460937489, 63.99223632812499], + [-89.20063476562493, 64.11376953125006], + [-89.13154296874998, 63.96850585937494], + [-89.61582031249995, 64.030615234375], + [-89.8113281249999, 64.18056640625], + [-90.04165039062494, 64.14086914062509], + [-89.85571289062497, 63.9569824218751], + [-90.16816406250004, 63.978759765625085], + [-90.15473632812498, 63.68964843749998], + [-90.81191406249991, 63.580908203125034], + [-91.98222656249996, 63.82241210937502], + [-92.33842773437496, 63.787646484375045], + [-93.69633789062493, 64.14716796875013], + [-93.55981445312491, 63.865283203125074], + [-93.27021484374998, 63.840869140625074], + [-93.37851562499992, 63.94848632812497], + [-92.15688476562491, 63.691699218750045], + [-92.46508789062491, 63.55507812500011], + [-91.84184570312496, 63.69755859374999], + [-90.97006835937489, 63.442773437500136], + [-90.69858398437492, 63.06386718750005], + [-91.44897460937503, 62.804052734375034], + [-92.3612792968749, 62.81938476562496], + [-91.93583984374993, 62.59238281250009], + [-92.55141601562491, 62.546728515625034], + [-92.76596679687492, 62.34995117187509], + [-92.52797851562494, 62.16840820312504], + [-93.20537109374993, 62.364941406250125], + [-92.90551757812503, 62.21513671874996], + [-93.3330566406249, 61.93291015625002], + [-93.58178710937494, 61.94204101562511], + [-93.31201171874997, 61.76728515625004], + [-93.91274414062497, 61.48144531250006], + [-94.509375, 60.60454101562493], + [-94.76171874999991, 60.498242187500125], + [-94.78828124999998, 59.26787109374993], + [-94.95732421874996, 59.068847656250085], + [-94.28706054687493, 58.716015625000125], + [-94.33222656249998, 58.297363281250114], + [-94.12319335937494, 58.73671875000008], + [-93.1787597656249, 58.72563476562496], + [-92.43281249999993, 57.3203125], + [-92.7981445312499, 56.921972656250034], + [-90.89746093750003, 57.25693359375006], + [-88.94848632812489, 56.85131835937503], + [-88.07509765624997, 56.46728515624994], + [-87.48242187499991, 56.021289062500045], + [-85.55932617187491, 55.54018554687508], + [-85.21801757812491, 55.348974609375034], + [-85.3652832031249, 55.07929687499998], + [-85.06093749999997, 55.285644531250085], + [-83.91059570312493, 55.314648437499955], + [-82.39326171874998, 55.067822265625125], + [-82.219384765625, 54.8134765625], + [-82.42416992187486, 54.2445800781251], + [-82.14145507812492, 53.81762695312497], + [-82.29155273437496, 53.03071289062507], + [-81.5994140624999, 52.432617187500085], + [-81.82788085937489, 52.22421875000009], + [-81.46621093749994, 52.204492187500136], + [-80.588037109375, 51.667236328125114], + [-80.4433105468749, 51.38857421875002], + [-80.85122070312497, 51.125], + [-80.47832031249993, 51.30732421874998], + [-80.10356445312487, 51.282861328125136], + [-79.34790039062494, 50.76264648437504], + [-79.737451171875, 51.186279296875], + [-79.33867187500002, 51.62817382812497], + [-79.04052734375003, 51.46376953125005], + [-78.90317382812495, 51.200292968750034], + [-78.73134765624994, 51.497460937499994], + [-78.98164062499993, 51.774560546875136], + [-78.44809570312495, 52.26137695312502], + [-78.74414062499994, 52.65537109374998], + [-79.10034179687497, 53.65664062500005], + [-78.99604492187493, 54.00249023437499], + [-79.241796875, 54.098876953125085], + [-79.14672851562491, 54.16923828125002], + [-79.71235351562495, 54.6718261718751], + [-77.77529296874994, 55.291259765625], + [-76.60405273437496, 56.19956054687495], + [-76.52558593749998, 56.8917968750001], + [-76.80981445312497, 57.65795898437506], + [-77.15678710937496, 58.018896484375034], + [-78.51508789062493, 58.68237304687503], + [-77.76069335937498, 59.38002929687505], + [-77.72617187499995, 59.67587890624992], + [-77.34907226562495, 59.57895507812509], + [-77.48530273437493, 59.684570312500114], + [-77.28920898437494, 60.0220214843751], + [-77.58588867187498, 60.088183593750074], + [-77.45288085937497, 60.1458007812501], + [-77.6814453124999, 60.427099609375034], + [-77.503564453125, 60.54272460937497], + [-77.7908203124999, 60.63984375000004], + [-77.58955078124993, 60.808593750000114], + [-78.18134765624995, 60.81914062499996], + [-77.51435546874998, 61.55629882812505], + [-78.02138671874997, 61.8320800781251], + [-78.13339843749986, 62.28227539062496], + [-77.372412109375, 62.572509765625114], + [-75.81689453124991, 62.31586914062507], + [-75.7898437499999, 62.17958984375002], + [-75.3412109375, 62.312109375], + [-74.63256835937497, 62.115673828125125], + [-74.6458007812499, 62.21113281250004], + [-73.70507812499991, 62.47314453124994], + [-72.68696289062498, 62.12456054687499], + [-72.771630859375, 61.840429687500006], + [-72.50556640624998, 61.922656250000074], + [-72.22612304687487, 61.83159179687499], + [-72.04003906249991, 61.68027343750006], + [-72.21586914062502, 61.58725585937495], + [-71.86611328125, 61.68852539062499], + [-71.63828124999995, 61.6171875], + [-71.85439453124991, 61.43979492187492], + [-71.42270507812489, 61.158935546875085], + [-70.27929687499991, 61.06865234374999], + [-69.99243164062491, 60.8564941406251], + [-69.50332031249994, 61.04042968750011], + [-69.40473632812493, 60.84677734375009], + [-69.75947265624998, 60.440234375000045], + [-69.67373046874994, 60.07587890625007], + [-70.65483398437496, 60.02622070312506], + [-69.73393554687493, 59.918017578125045], + [-69.68188476562489, 59.34174804687507], + [-69.3440429687499, 59.303076171875006], + [-69.53164062499994, 58.86923828125009], + [-69.64838867187493, 58.82080078125], + [-69.78417968749994, 58.95571289062511], + [-70.15434570312496, 58.76059570312498], + [-69.78989257812486, 58.689306640625034], + [-69.27109374999986, 58.88393554687505], + [-68.69819335937495, 58.904541015625], + [-68.38115234374993, 58.74350585937506], + [-68.22939453124994, 58.48457031250007], + [-68.35654296874989, 58.163232421875136], + [-69.04082031249996, 57.902490234375136], + [-68.41357421874997, 58.0517578125], + [-68.02104492187493, 58.48530273437504], + [-67.88828124999989, 58.29575195312495], + [-68.06386718750005, 58.13896484374999], + [-67.75595703124992, 58.4045898437501], + [-67.6782714843749, 57.99111328125008], + [-67.5696289062499, 58.21347656250006], + [-66.72216796874991, 58.49101562499996], + [-66.36240234374989, 58.791162109374994], + [-66.0023925781249, 58.43120117187502], + [-66.04306640624995, 58.82065429687495], + [-65.72099609374996, 59.02377929687495], + [-65.38354492187494, 59.06020507812508], + [-65.7, 59.21333007812501], + [-65.4117187499999, 59.31499023437496], + [-65.47509765624994, 59.47031249999998], + [-65.03823242187494, 59.38789062500007], + [-65.40742187499993, 59.53935546875002], + [-65.4333984374999, 59.776513671874994], + [-65.02817382812495, 59.77070312500007], + [-65.17172851562489, 59.90800781249996], + [-64.81733398437498, 60.3310546875], + [-64.49941406250005, 60.26826171875001], + [-64.41958007812494, 60.17138671874997], + [-64.76845703124997, 60.01210937500005], + [-64.28349609374993, 60.06406249999998], + [-64.22631835937491, 59.741210937500085], + [-64.05605468750005, 59.82255859374996], + [-63.7501953124999, 59.51259765625005], + [-63.945458984374994, 59.380175781250074], + [-63.775878906249915, 59.277148437500045], + [-63.539892578124864, 59.332861328125034], + [-63.41513671874995, 59.194384765625074], + [-63.97114257812498, 59.053808593750034], + [-63.24843749999991, 59.068310546874955], + [-63.28212890624994, 58.86738281250007], + [-63.05029296874997, 58.87817382812494], + [-62.87387695312489, 58.67246093749998], + [-63.537060546874926, 58.329931640625006], + [-63.209960937499886, 58.46694335937502], + [-62.593847656249864, 58.47402343750005], + [-62.81206054687502, 58.20039062500007], + [-63.26152343749993, 58.014697265625074], + [-62.486230468749966, 58.15405273437506], + [-62.30566406249997, 57.97226562499995], + [-61.95864257812505, 57.91176757812508], + [-61.9679687499999, 57.61191406250009], + [-62.495556640624926, 57.489208984375125], + [-61.92114257812497, 57.42080078125005], + [-61.977441406249966, 57.24794921875002], + [-61.33374023437494, 57.01059570312498], + [-61.37163085937502, 56.68081054687511], + [-62.497265624999926, 56.80170898437504], + [-61.73774414062498, 56.52602539062502], + [-61.940429687499886, 56.423583984375114], + [-61.42529296874994, 56.360644531250074], + [-61.713085937499955, 56.230957031250114], + [-61.364697265624926, 56.2160156250001], + [-61.30112304687495, 56.04716796874999], + [-61.4495117187499, 55.99570312499998], + [-61.08935546874997, 55.86635742187511], + [-60.74326171874989, 55.94145507812493], + [-60.56210937499995, 55.727001953125125], + [-60.341015624999926, 55.78466796874997], + [-60.40830078124995, 55.649560546874994], + [-60.19238281249994, 55.4809082031251], + [-60.617138671874955, 55.060205078124994], + [-59.75878906249997, 55.3095703125], + [-59.68906249999989, 55.19633789062502], + [-59.43789062500005, 55.175927734375136], + [-59.837792968749994, 54.813964843750114], + [-59.25957031249996, 55.19995117187506], + [-58.99711914062496, 55.149462890625074], + [-58.780175781249994, 54.838378906250114], + [-58.39814453124998, 54.77412109374998], + [-57.96245117187493, 54.875732421875085], + [-57.40449218750004, 54.59086914062496], + [-57.69926757812496, 54.38657226562506], + [-58.435205078124966, 54.228125], + [-58.63320312499999, 54.04956054687497], + [-59.8230468749999, 53.83442382812504], + [-60.14492187499994, 53.59614257812498], + [-60.395410156249994, 53.653320312500085], + [-60.1002929687499, 53.48696289062511], + [-60.329492187499966, 53.26611328125006], + [-58.652050781249926, 53.97788085937495], + [-57.935986328124955, 54.09116210937492], + [-58.31748046874989, 54.11445312500007], + [-58.192089843749926, 54.228173828125136], + [-57.4160644531249, 54.162744140625136], + [-57.134960937499926, 53.79184570312506], + [-57.524072265624966, 53.61142578125006], + [-57.331738281249955, 53.469091796875034], + [-56.84086914062496, 53.73945312500004], + [-56.46499023437505, 53.76503906250011], + [-55.96611328125002, 53.4711425781251], + [-55.79794921874995, 53.211962890625045], + [-55.80283203124989, 52.64316406249998], + [-56.324902343749926, 52.54453124999998], + [-55.74648437499994, 52.4745605468751], + [-55.7771484374999, 52.3642578125], + [-56.01171874999997, 52.394482421875125], + [-55.695214843749994, 52.13779296875006], + [-56.97597656250005, 51.45766601562505], + [-58.510351562500006, 51.295068359375136], + [-59.88632812499992, 50.316406250000085], + [-61.72485351562503, 50.10405273437499], + [-61.91953124999989, 50.2328613281251], + [-62.71542968749995, 50.30166015625008], + [-66.49550781249991, 50.2118652343751], + [-66.94116210937503, 49.993701171875045], + [-67.37202148437495, 49.348437500000045], + [-68.28193359374998, 49.197167968750136], + [-69.67387695312496, 48.19916992187504], + [-71.01826171874993, 48.455615234375045], + [-69.86552734374993, 48.17226562500005], + [-69.775, 48.09809570312504], + [-69.9944335937499, 47.73989257812508], + [-70.70585937499996, 47.13979492187505], + [-71.26777343749995, 46.79594726562499], + [-71.87958984374998, 46.68681640624996], + [-72.98100585937493, 46.209716796875085], + [-73.4766113281249, 45.738232421874955], + [-74.03784179687494, 45.501855468750136], + [-74.31508789062494, 45.531054687500045], + [-73.97382812499995, 45.345117187499994], + [-74.70888671874997, 45.0038574218751] + ] + ], + [ + [ + [-96.78232421874998, 72.93662109375], + [-97.0927734375, 72.99692382812503], + [-96.86240234374995, 73.18881835937506], + [-96.78232421874998, 72.93662109375] + ] + ], + [ + [ + [-114.52153320312502, 72.592919921875], + [-113.57807617187501, 72.65209960937506], + [-113.2923828125, 72.94980468750003], + [-112.75361328125001, 72.98603515624995], + [-111.26972656249994, 72.71372070312498], + [-111.895166015625, 72.35610351562497], + [-111.67509765625002, 72.30014648437503], + [-110.20512695312495, 72.66127929687497], + [-110.66083984374998, 73.00820312500002], + [-110.00844726562494, 72.983642578125], + [-108.75498046875002, 72.55107421874999], + [-108.18823242187501, 71.72377929687502], + [-107.812841796875, 71.62617187500004], + [-107.30600585937496, 71.89467773437502], + [-108.23740234374999, 73.14990234375003], + [-108.029052734375, 73.34873046875003], + [-106.48212890624998, 73.19619140624997], + [-105.41513671874995, 72.788330078125], + [-104.38593749999997, 71.57695312500005], + [-104.51479492187502, 71.06425781250005], + [-103.58457031249995, 70.63085937500003], + [-103.07719726562497, 70.50883789062505], + [-103.04956054687503, 70.65507812499999], + [-101.67631835937495, 70.27827148437495], + [-101.56240234375001, 70.135009765625], + [-101.04267578125, 70.11079101562504], + [-100.98237304687497, 69.67988281250001], + [-101.483837890625, 69.85019531250006], + [-101.64765624999997, 69.69853515625007], + [-102.18212890624997, 69.845947265625], + [-102.59589843749997, 69.71791992187502], + [-102.62109374999996, 69.55151367187506], + [-103.464892578125, 69.64448242187498], + [-103.04892578124999, 69.47177734375006], + [-103.12021484374995, 69.20458984374997], + [-102.44677734374997, 69.476318359375], + [-102.04594726562493, 69.46484374999997], + [-101.85712890625001, 69.02397460937505], + [-102.89506835937499, 68.8236328125], + [-104.57143554687501, 68.87211914062502], + [-105.105859375, 68.92041015625], + [-105.019580078125, 69.08125], + [-106.27016601562497, 69.19458007812497], + [-106.65908203124997, 69.439599609375], + [-107.43989257812497, 69.00214843749995], + [-108.36499023437497, 68.93476562499998], + [-109.47211914062501, 68.67670898437498], + [-113.12773437500002, 68.49414062500003], + [-113.61684570312501, 68.8384765625], + [-113.69414062499995, 69.19501953124998], + [-115.61811523437495, 69.28295898437506], + [-116.51347656249993, 69.42460937500005], + [-117.19541015624995, 70.05405273437503], + [-114.59233398437497, 70.31245117187498], + [-112.63789062499997, 70.225244140625], + [-111.63256835937497, 70.30883789062497], + [-113.75727539062503, 70.69072265625005], + [-115.99091796874997, 70.586279296875], + [-117.58706054687498, 70.62954101562502], + [-118.2640625, 70.888330078125], + [-118.26909179687493, 71.03471679687505], + [-115.30341796874997, 71.49370117187505], + [-117.93564453125003, 71.39208984375003], + [-118.22646484374995, 71.46708984375005], + [-117.742333984375, 71.65932617187502], + [-118.58300781250003, 71.64902343749998], + [-118.98769531249997, 71.7642578125], + [-118.94462890624997, 71.98554687499995], + [-118.21347656249998, 72.26289062499998], + [-118.481298828125, 72.42768554687498], + [-118.13310546874995, 72.63281250000003], + [-114.63823242187499, 73.37265625000003], + [-114.20639648437495, 73.29780273437495], + [-114.05170898437497, 73.07099609375004], + [-114.52153320312502, 72.592919921875] + ] + ], + [ + [ + [-105.28891601562499, 72.919921875], + [-106.92153320312497, 73.479833984375], + [-106.61396484375001, 73.69560546875002], + [-105.31796874999995, 73.76713867187502], + [-104.5875, 73.57807617187495], + [-104.62172851562495, 73.3111328125], + [-105.28891601562499, 72.919921875] + ] + ], + [ + [ + [-79.53730468749998, 73.65449218749998], + [-78.2865234375, 73.66582031250007], + [-77.20654296874997, 73.49956054687505], + [-76.18339843749999, 72.84306640625005], + [-77.83593750000003, 72.89682617187498], + [-79.3193359375, 72.75771484375], + [-79.820703125, 72.82631835937502], + [-80.18330078124995, 73.22465820312499], + [-80.77641601562502, 73.33417968750001], + [-80.84887695312503, 73.72124023437499], + [-79.53730468749998, 73.65449218749998] + ] + ], + [ + [ + [-86.58935546874997, 71.01079101562507], + [-85.64384765624999, 71.15244140624998], + [-85.09487304687497, 71.15195312500006], + [-84.82373046874997, 71.02861328125005], + [-84.69941406249995, 71.63144531250003], + [-85.33906249999998, 71.69726562500003], + [-85.91162109375, 71.98652343749998], + [-85.321875, 72.23315429687506], + [-84.28374023437499, 72.04448242187499], + [-84.84199218749995, 72.30815429687505], + [-84.62304687500003, 72.37656250000003], + [-85.34111328124993, 72.42153320312497], + [-85.64990234374997, 72.72216796875003], + [-85.26210937500002, 72.95400390625], + [-84.25664062499999, 72.79672851562503], + [-85.454736328125, 73.10546875000003], + [-84.41606445312496, 73.45649414062495], + [-83.781884765625, 73.41689453125], + [-83.72983398437495, 73.57587890624995], + [-81.946142578125, 73.72983398437506], + [-81.40615234374997, 73.634521484375], + [-80.27724609375, 72.77016601562502], + [-81.229345703125, 72.31171874999998], + [-80.61147460937497, 72.450830078125], + [-80.925146484375, 71.90766601562501], + [-80.18193359374996, 72.20878906250007], + [-79.884375, 72.17719726562501], + [-80.10893554687499, 72.33217773437497], + [-79.83129882812503, 72.44628906250003], + [-79.000244140625, 72.27202148437507], + [-79.00781250000003, 72.04291992187501], + [-78.58510742187497, 71.880615234375], + [-78.86274414062495, 72.100830078125], + [-78.69926757812496, 72.35141601562498], + [-77.51650390624997, 72.17778320312505], + [-78.48427734374994, 72.47060546875002], + [-77.75322265624996, 72.72475585937502], + [-75.70429687499998, 72.57153320312497], + [-75.05268554687493, 72.22636718749999], + [-75.92280273437501, 71.71723632812501], + [-74.90317382812503, 72.10048828125002], + [-74.20932617187498, 71.978662109375], + [-74.31572265624999, 71.84267578125], + [-75.20478515625001, 71.70913085937497], + [-74.70078125, 71.67558593750005], + [-74.99619140624998, 71.21811523437503], + [-74.48808593750002, 71.64838867187501], + [-73.8140625, 71.77143554687495], + [-74.197265625, 71.404150390625], + [-73.71284179687498, 71.58759765624998], + [-73.18061523437501, 71.282861328125], + [-73.27822265625, 71.53798828125], + [-72.901953125, 71.67778320312507], + [-71.64067382812499, 71.51625976562502], + [-71.22939453124997, 71.33876953125], + [-71.49501953124997, 71.10512695312502], + [-71.93793945312498, 71.09428710937502], + [-72.63271484374994, 70.83076171874998], + [-71.74252929687495, 71.046875], + [-71.370849609375, 70.97514648437499], + [-70.82607421874994, 71.10874023437503], + [-70.67265625, 71.05219726562498], + [-70.76171874999997, 70.79223632812503], + [-71.89018554687502, 70.43154296875002], + [-71.27587890625, 70.50029296874999], + [-71.42944335937503, 70.12778320312503], + [-70.97978515624999, 70.5810546875], + [-69.94980468750003, 70.84501953125005], + [-68.49575195312502, 70.61025390625], + [-68.363525390625, 70.48125], + [-70.05771484375, 70.042626953125], + [-68.77822265625, 70.20356445312501], + [-69.00830078124997, 69.97895507812501], + [-68.74404296874997, 69.94140625], + [-68.05908203124997, 70.317236328125], + [-67.36367187499994, 70.03442382812503], + [-67.22163085937495, 69.73071289062506], + [-68.02041015625, 69.77006835937499], + [-69.25078124999999, 69.51191406249998], + [-68.51303710937498, 69.57729492187497], + [-67.236962890625, 69.460107421875], + [-66.71674804687495, 69.31186523437498], + [-66.70742187500002, 69.16821289062503], + [-68.40629882812499, 69.23222656250002], + [-69.040625, 69.09799804687503], + [-68.41552734375, 69.17207031250001], + [-67.8326171875, 69.06596679687499], + [-67.88320312500002, 68.78398437499999], + [-69.31909179687497, 68.85698242187505], + [-68.21040039062495, 68.702978515625], + [-67.9384765625, 68.524169921875], + [-66.74272460937502, 68.45776367187497], + [-67.032958984375, 68.32607421874997], + [-66.923095703125, 68.06572265625005], + [-66.72900390624997, 68.12900390625006], + [-66.66269531249995, 68.03442382812497], + [-66.63095703124998, 68.21064453124998], + [-66.21240234374997, 68.280419921875], + [-66.44394531249998, 67.83383789062506], + [-65.94238281250003, 68.07094726562505], + [-65.86435546875003, 67.92285156249997], + [-65.50908203124996, 67.96826171875], + [-65.40126953125002, 67.67485351562499], + [-65.41533203124996, 67.87924804687498], + [-64.92231445312495, 68.03164062500002], + [-65.02109375, 67.78754882812495], + [-64.63779296875, 67.84023437500002], + [-63.850195312500034, 67.56606445312502], + [-64.00795898437502, 67.34731445312497], + [-64.69995117187494, 67.35053710937501], + [-63.83623046874993, 67.26411132812498], + [-63.59160156250002, 67.3775390625], + [-63.040136718750034, 67.235009765625], + [-63.70156249999994, 66.82236328125003], + [-62.962304687499966, 66.94926757812505], + [-62.37973632812495, 66.90537109375], + [-62.12358398437499, 67.046728515625], + [-61.35341796874994, 66.689208984375], + [-61.52783203124994, 66.55810546875003], + [-62.12333984374993, 66.64306640625003], + [-61.57080078125, 66.37290039062506], + [-61.95634765624993, 66.30932617187497], + [-62.553125, 66.40683593750003], + [-62.53359374999994, 66.22700195312498], + [-61.99160156250002, 66.03530273437502], + [-62.624121093750006, 66.01625976562505], + [-62.381982421874966, 65.83330078124999], + [-62.65888671874998, 65.63994140625002], + [-63.16894531249997, 65.65732421875], + [-63.45874023437494, 65.85302734375], + [-63.42089843749997, 65.70859374999998], + [-63.651074218749955, 65.66098632812506], + [-63.33745117187493, 65.61674804687502], + [-63.36337890624998, 65.22973632812503], + [-63.606591796874966, 64.92807617187503], + [-64.345703125, 65.17241210937499], + [-64.26967773437497, 65.40078124999997], + [-64.55507812500002, 65.1166015625], + [-65.401611328125, 65.764013671875], + [-64.44536132812496, 66.31713867187497], + [-65.0044921875, 66.07773437500003], + [-65.82573242187499, 65.996923828125], + [-65.65634765625003, 66.204736328125], + [-66.06372070312497, 66.13271484374997], + [-66.986328125, 66.62749023437505], + [-67.07685546874995, 66.52548828125006], + [-67.30732421874993, 66.5697265625], + [-67.22539062499993, 66.31025390624998], + [-67.88339843749995, 66.46743164062502], + [-67.18320312499995, 66.03442382812503], + [-67.350439453125, 65.92973632812502], + [-67.82802734374997, 65.96518554687503], + [-68.45991210937498, 66.249267578125], + [-68.74892578125, 66.200048828125], + [-68.21718750000002, 66.078857421875], + [-68.18671874999993, 65.87099609375002], + [-67.86645507812497, 65.773681640625], + [-67.936767578125, 65.56489257812501], + [-67.56962890624999, 65.64355468749997], + [-67.11796874999999, 65.44038085937495], + [-67.3365234375, 65.34658203125005], + [-66.69741210937502, 64.81518554687506], + [-66.63549804687503, 65.00034179687503], + [-66.21464843749999, 64.72241210937497], + [-65.93852539062496, 64.88574218750003], + [-65.2748046875, 64.63154296875004], + [-65.52934570312499, 64.50478515624997], + [-65.074609375, 64.43666992187502], + [-65.21298828125003, 64.30327148437502], + [-65.580322265625, 64.29384765624997], + [-65.16987304687495, 64.02817382812503], + [-64.67846679687503, 64.027978515625], + [-64.79814453124999, 63.91596679687498], + [-64.4109375, 63.70634765625002], + [-64.66464843749995, 63.24536132812497], + [-65.19184570312498, 63.764257812500006], + [-65.06894531249998, 63.26347656250002], + [-64.67236328125003, 62.921972656250006], + [-65.16279296875001, 62.93261718750003], + [-65.10849609374998, 62.62646484375], + [-66.22402343749994, 63.10717773437497], + [-66.228662109375, 62.99096679687503], + [-66.41445312500002, 63.027197265625034], + [-66.65498046874998, 63.264746093750006], + [-66.69746093749993, 63.069531249999955], + [-67.89326171874993, 63.733740234375006], + [-67.72255859374997, 63.422753906249966], + [-68.49375, 63.725488281249994], + [-68.91108398437498, 63.703222656250006], + [-68.141259765625, 63.17231445312501], + [-67.67597656249998, 63.093554687500045], + [-67.73696289062497, 63.00957031249999], + [-65.98017578125001, 62.20888671875002], + [-66.12387695312498, 61.89306640625], + [-68.53588867187503, 62.25561523437506], + [-69.12558593749998, 62.423974609374966], + [-69.604736328125, 62.76772460937502], + [-70.23613281250002, 62.76337890625001], + [-70.801416015625, 62.91049804687506], + [-71.10576171874999, 63.00224609375002], + [-70.94604492187497, 63.12070312499998], + [-71.34726562499998, 63.066113281249955], + [-71.99223632812493, 63.41616210937505], + [-71.380859375, 63.580322265625], + [-72.29013671874995, 63.72797851562498], + [-72.17426757812498, 63.893408203125006], + [-72.49843749999994, 63.82348632812497], + [-73.45454101562495, 64.39926757812503], + [-73.27128906250002, 64.58251953125], + [-73.91035156249998, 64.578125], + [-74.064794921875, 64.42465820312498], + [-74.13046874999998, 64.6078125], + [-74.46123046874996, 64.64467773437505], + [-74.68139648437497, 64.8306640625], + [-74.91943359374997, 64.76552734374997], + [-74.69472656250002, 64.49658203124997], + [-75.71503906249995, 64.52436523437495], + [-75.76669921875, 64.39194335937498], + [-76.85615234374998, 64.23764648437498], + [-77.76049804687503, 64.36015624999999], + [-78.04521484374993, 64.499267578125], + [-78.09560546875, 64.93925781250002], + [-77.36088867187496, 65.19653320312503], + [-77.32670898437493, 65.453125], + [-75.82832031249993, 65.22705078125003], + [-75.45209960937495, 64.84160156250002], + [-75.35712890624995, 65.00874023437495], + [-75.79868164062503, 65.297509765625], + [-75.16630859374999, 65.28393554687497], + [-74.13847656250002, 65.50346679687502], + [-73.55078125000003, 65.48525390625005], + [-74.41640624999997, 66.16708984375003], + [-73.03325195312502, 66.72817382812505], + [-72.78881835937494, 67.030615234375], + [-72.22001953124999, 67.25429687500002], + [-73.28447265624993, 68.35698242187505], + [-73.82050781249998, 68.36293945312502], + [-73.82211914062495, 68.68598632812501], + [-74.11796875000002, 68.70092773437506], + [-73.9892578125, 68.54863281250002], + [-74.2701171875, 68.54121093750001], + [-74.89296875, 68.80815429687505], + [-74.71669921874997, 69.04550781249998], + [-76.58505859375, 68.69873046875003], + [-76.55722656250003, 69.00947265625001], + [-75.9537109375, 69.03081054687502], + [-75.64775390625002, 69.212548828125], + [-76.46494140624995, 69.46943359375001], + [-76.23408203125001, 69.66210937500003], + [-76.742333984375, 69.57290039062497], + [-77.08994140625, 69.63510742187503], + [-76.85859374999995, 69.775390625], + [-77.591650390625, 69.84560546875002], + [-77.77402343750003, 70.23852539062503], + [-78.28281250000003, 70.229150390625], + [-79.06640624999997, 70.60356445312507], + [-79.40522460937498, 70.40073242187503], + [-78.86284179687499, 70.24189453125001], + [-78.88964843750003, 69.97749023437495], + [-79.51542968749996, 69.88759765625005], + [-81.65195312500003, 70.09462890625002], + [-80.92172851562503, 69.73090820312501], + [-81.56469726562503, 69.94272460937498], + [-82.29384765624997, 69.83691406250003], + [-83.14995117187493, 70.00908203125002], + [-83.85908203124998, 69.96274414062498], + [-85.43237304687497, 70.11137695312507], + [-85.780029296875, 70.03666992187505], + [-86.32202148437503, 70.14541015625], + [-86.396875, 70.46533203124997], + [-87.838134765625, 70.24658203125], + [-88.78271484374997, 70.49448242187503], + [-89.45590820312498, 71.06171874999995], + [-87.84492187499995, 70.94438476562505], + [-87.14008789062498, 71.01162109374997], + [-89.80537109374993, 71.46230468750005], + [-89.86152343750001, 72.41191406250005], + [-88.70517578124998, 73.40327148437495], + [-87.71977539062496, 73.72290039062497], + [-85.95078124999998, 73.85014648437505], + [-84.94677734375, 73.72163085937498], + [-86.00053710937499, 73.31254882812505], + [-86.65629882812502, 72.72402343750005], + [-86.21845703124998, 71.89912109375004], + [-85.02338867187495, 71.35322265625001], + [-86.58935546874997, 71.01079101562507] + ] + ], + [ + [ + [-100.00190429687497, 73.9458984375], + [-99.15795898437499, 73.73159179687497], + [-97.66997070312499, 73.88774414062499], + [-97.1705078125, 73.82485351562497], + [-97.001708984375, 73.66650390625003], + [-97.62587890624997, 73.50229492187498], + [-97.27250976562502, 73.38681640624998], + [-98.42177734375002, 72.94101562500003], + [-97.63632812499998, 73.02763671874999], + [-97.128125, 72.62758789062502], + [-96.59208984374996, 72.71025390624999], + [-96.44560546874996, 72.55244140624998], + [-96.80146484374998, 72.32241210937502], + [-96.61342773437494, 71.83383789062506], + [-97.58227539062497, 71.62968750000005], + [-98.18134765624998, 71.66245117187503], + [-98.32270507812501, 71.85234375000002], + [-98.19863281249994, 71.44086914062501], + [-98.66289062499993, 71.302099609375], + [-99.22363281249996, 71.387109375], + [-100.594482421875, 72.15234375000003], + [-101.20854492187495, 72.31699218749998], + [-101.72392578124996, 72.31489257812501], + [-102.70874023437496, 72.76450195312503], + [-102.20400390624998, 73.077294921875], + [-101.27319335937497, 72.7216796875], + [-100.48476562500002, 72.77294921874997], + [-100.395703125, 72.97700195312498], + [-100.128125, 72.90668945312495], + [-100.53637695312497, 73.19785156250003], + [-99.82514648437503, 73.2138671875], + [-100.36611328125001, 73.359033203125], + [-100.88935546875003, 73.27534179687501], + [-101.52319335937501, 73.48637695312502], + [-100.97578124999995, 73.59975585937502], + [-100.5216796875, 73.44931640625], + [-100.96298828125002, 73.79140625], + [-99.99111328125, 73.79516601562503], + [-100.00190429687497, 73.9458984375] + ] + ], + [ + [ + [-98.270361328125, 73.86850585937498], + [-98.97392578124997, 73.81206054687502], + [-99.4169921875, 73.89541015625002], + [-97.69824218749997, 74.10869140625005], + [-98.270361328125, 73.86850585937498] + ] + ], + [ + [ + [-93.17084960937498, 74.16098632812506], + [-92.22270507812502, 73.97236328124998], + [-90.62744140625, 73.95170898437505], + [-90.38139648437496, 73.82475585937502], + [-92.11791992187497, 72.75380859375], + [-94.21132812499997, 72.75693359375], + [-93.77055664062496, 72.66821289062506], + [-93.55517578124994, 72.42114257812497], + [-94.03754882812498, 72.02875976562498], + [-95.00786132812496, 72.01279296875], + [-95.60214843749998, 72.88447265624995], + [-95.63291015625003, 73.69545898437497], + [-94.697607421875, 73.66357421874997], + [-95.134130859375, 73.88125], + [-94.97353515625, 74.04140625000002], + [-93.17084960937498, 74.16098632812506] + ] + ], + [ + [ + [-119.73632812499997, 74.11264648437498], + [-119.20595703125002, 74.19799804687503], + [-119.11796874999995, 74.01552734375], + [-118.54399414062499, 74.24462890625003], + [-117.51484375000001, 74.23173828124999], + [-115.51069335937501, 73.61875], + [-115.446875, 73.43886718750002], + [-118.96157226562497, 72.68413085937499], + [-119.51284179687501, 72.30268554687501], + [-120.17988281250001, 72.21264648437506], + [-120.61933593750001, 71.50576171875002], + [-121.47216796875003, 71.38901367187503], + [-121.74936523437502, 71.44477539062501], + [-123.09565429687503, 71.09379882812502], + [-124.00776367187494, 71.67744140624998], + [-125.29667968749999, 71.973046875], + [-125.84531250000002, 71.978662109375], + [-123.79726562499997, 73.76816406250003], + [-124.69624023437497, 74.34819335937499], + [-121.50415039062497, 74.54511718749998], + [-119.56264648437494, 74.23281250000002], + [-119.73632812499997, 74.11264648437498] + ] + ], + [ + [ + [-97.35551757812496, 74.52631835937495], + [-97.75, 74.51054687500005], + [-97.41650390624994, 74.62656250000003], + [-97.35551757812496, 74.52631835937495] + ] + ], + [ + [ + [-95.306640625, 74.50541992187505], + [-95.850732421875, 74.58247070312504], + [-95.51020507812498, 74.63676757812499], + [-95.306640625, 74.50541992187505] + ] + ], + [ + [ + [-104.11992187499995, 75.03632812500004], + [-104.88740234374998, 75.14775390624999], + [-104.34619140624996, 75.42993164062503], + [-103.64350585937497, 75.18657226562499], + [-104.11992187499995, 75.03632812500004] + ] + ], + [ + [ + [-93.54257812499995, 75.0279296875], + [-93.57309570312495, 74.66884765625005], + [-94.53452148437498, 74.63671874999997], + [-96.59960937499997, 75.03178710937499], + [-95.95463867187493, 75.44379882812501], + [-94.878173828125, 75.63002929687502], + [-93.90908203125002, 75.42250976562502], + [-93.54257812499995, 75.0279296875] + ] + ], + [ + [ + [-96.07856445312495, 75.510107421875], + [-96.91513671875003, 75.37968749999999], + [-96.98281249999997, 75.50981445312505], + [-96.367822265625, 75.65463867187506], + [-96.07856445312495, 75.510107421875] + ] + ], + [ + [ + [-94.52656249999995, 75.74931640624999], + [-94.901220703125, 75.93076171875], + [-94.53789062499996, 75.99643554687506], + [-94.52656249999995, 75.74931640624999] + ] + ], + [ + [ + [-118.328125, 75.57968749999998], + [-118.81713867187503, 75.52211914062497], + [-119.39458007812499, 75.617333984375], + [-117.63369140624998, 76.11508789062498], + [-118.328125, 75.57968749999998] + ] + ], + [ + [ + [-79.0630859375, 75.92587890624998], + [-79.63876953124995, 75.84291992187505], + [-79.00932617187499, 76.14589843750005], + [-79.0630859375, 75.92587890624998] + ] + ], + [ + [ + [-102.22734374999995, 76.014892578125], + [-102.00800781250003, 75.93940429687498], + [-102.57958984375003, 75.78022460937498], + [-103.31474609374996, 75.76420898437499], + [-103.04150390624999, 75.91884765624997], + [-103.98525390624997, 75.93310546875003], + [-103.80078124999994, 76.03701171874997], + [-104.24248046874996, 76.04697265625006], + [-104.35063476562497, 76.18232421875001], + [-102.72802734374999, 76.30703125], + [-102.22734374999995, 76.014892578125] + ] + ], + [ + [ + [-104.02285156249998, 76.58310546875003], + [-103.05131835937495, 76.44985351562497], + [-103.31137695312499, 76.34755859375], + [-104.35751953124995, 76.33461914062502], + [-104.58569335937499, 76.60649414062499], + [-104.07451171875003, 76.66611328124998], + [-104.02285156249998, 76.58310546875003] + ] + ], + [ + [ + [-97.70092773437497, 76.46650390624998], + [-97.89052734374997, 75.7603515625], + [-97.40751953124999, 75.67250976562497], + [-97.33603515624998, 75.41982421875], + [-97.65332031249997, 75.50776367187498], + [-97.87822265624996, 75.41611328125003], + [-97.67431640624997, 75.127294921875], + [-98.04531249999997, 75.20083007812497], + [-98.12094726562503, 75.03271484375], + [-100.234375, 75.00771484374997], + [-100.48349609374995, 75.18842773437501], + [-100.14570312499995, 75.24614257812505], + [-100.71191406250003, 75.40634765625], + [-99.19458007812499, 75.698388671875], + [-102.58740234375001, 75.51367187500003], + [-102.79750976562501, 75.59965820312505], + [-102.14472656249998, 75.87504882812502], + [-100.97280273437498, 75.79843750000003], + [-101.414990234375, 75.84584960937502], + [-101.87211914062496, 76.08310546875003], + [-101.52895507812495, 76.21728515625003], + [-102.1046875, 76.33120117187505], + [-101.41518554687495, 76.42490234375003], + [-99.86547851562499, 75.92421875], + [-100.11284179687502, 76.11723632812507], + [-99.54106445312497, 76.14628906250005], + [-100.41420898437495, 76.242529296875], + [-99.97773437500003, 76.31245117187495], + [-100.82973632812497, 76.52387695312495], + [-99.8140625, 76.6322265625], + [-98.89033203125, 76.46557617187497], + [-98.71083984374994, 76.69384765625003], + [-97.70092773437497, 76.46650390624998] + ] + ], + [ + [ + [-101.22612304687497, 76.57934570312497], + [-101.61308593749995, 76.60458984375006], + [-100.26914062499998, 76.73413085937497], + [-101.22612304687497, 76.57934570312497] + ] + ], + [ + [ + [-108.29238281250001, 76.05712890625], + [-107.72348632812502, 75.99541015625002], + [-108.020703125, 75.80478515625], + [-107.21621093749997, 75.89155273437501], + [-106.91352539062503, 75.67963867187501], + [-106.67700195312499, 76.02373046875002], + [-105.63266601562493, 75.94536132812505], + [-105.51948242187497, 75.63237304687505], + [-106.09262695312495, 75.08945312500003], + [-107.15341796874996, 74.9271484375], + [-108.47475585937495, 74.94721679687501], + [-108.83129882812501, 75.06489257812498], + [-112.51933593749997, 74.41684570312503], + [-113.67158203124997, 74.45302734375005], + [-114.31269531250003, 74.71508789062506], + [-112.835986328125, 74.9755859375], + [-111.67109375, 75.01943359374997], + [-111.09345703125001, 75.25629882812498], + [-113.71176757812499, 75.06860351562503], + [-113.85332031249996, 75.259375], + [-113.46708984374996, 75.41611328125003], + [-114.01650390624998, 75.43427734375001], + [-114.16845703124994, 75.23950195312503], + [-114.51381835937497, 75.27548828125], + [-114.45175781250002, 75.08789062499997], + [-115.02011718749999, 74.97617187500003], + [-115.41318359374995, 75.11499023437497], + [-115.72885742187496, 74.968115234375], + [-116.47607421874996, 75.17177734375], + [-117.56523437499997, 75.23334960937504], + [-117.25761718750002, 75.45952148437502], + [-116.07714843749996, 75.49296874999999], + [-115.14184570312501, 75.67851562500005], + [-116.42563476562498, 75.58535156249997], + [-117.16362304687496, 75.64487304687503], + [-116.80214843749995, 75.77158203124998], + [-114.99150390625002, 75.896337890625], + [-116.66455078124999, 75.95756835937505], + [-116.20986328125, 76.19443359374998], + [-114.77861328124999, 76.17260742187497], + [-115.82216796874997, 76.27001953125003], + [-114.99848632812503, 76.4974609375], + [-114.19394531249999, 76.45146484375005], + [-113.82348632812501, 76.20683593750002], + [-112.69760742187496, 76.20170898437505], + [-111.05268554687495, 75.54853515625001], + [-108.94716796875, 75.54179687499999], + [-108.94477539062495, 75.69897460937503], + [-109.8705078125, 75.929052734375], + [-109.48681640624999, 76.14467773437497], + [-110.31445312500001, 76.369384765625], + [-109.09824218749996, 76.811865234375], + [-108.46699218749997, 76.73759765625007], + [-108.29238281250001, 76.05712890625] + ] + ], + [ + [ + [-89.72646484374994, 76.50742187499998], + [-90.55625, 76.73457031249998], + [-90.13632812499995, 76.83696289062505], + [-89.69541015625, 76.74116210937498], + [-89.72646484374994, 76.50742187499998] + ] + ], + [ + [ + [-113.56069335937494, 76.74326171874998], + [-114.83525390624999, 76.79467773437497], + [-113.89165039062495, 76.89487304687503], + [-113.56069335937494, 76.74326171874998] + ] + ], + [ + [ + [-94.29497070312493, 76.91245117187498], + [-93.23002929687496, 76.77026367187497], + [-93.53457031250002, 76.44770507812498], + [-92.99536132812494, 76.62041015624999], + [-91.305029296875, 76.68076171875003], + [-90.54262695312494, 76.495751953125], + [-91.41508789062496, 76.45585937500005], + [-89.28452148437498, 76.30161132812506], + [-89.40659179687498, 76.18916015624998], + [-91.40732421874998, 76.22006835937506], + [-89.27758789062497, 75.79506835937497], + [-89.64604492187499, 75.5650390625], + [-88.91669921874998, 75.45395507812503], + [-88.64497070312495, 75.65844726562503], + [-88.201318359375, 75.51201171875005], + [-87.72973632812503, 75.57563476562495], + [-87.53911132812502, 75.48486328125003], + [-87.25693359374998, 75.61772460937499], + [-85.95146484374993, 75.39501953125], + [-85.97299804687498, 75.5287109375], + [-83.931982421875, 75.81894531250003], + [-83.23710937499993, 75.75083007812503], + [-82.153662109375, 75.83105468750003], + [-80.32197265624998, 75.62910156250001], + [-79.50908203125002, 75.25981445312499], + [-80.38198242187494, 75.03417968750003], + [-79.40141601562502, 74.91762695312502], + [-79.944482421875, 74.83364257812505], + [-80.34775390624998, 74.90297851562505], + [-80.26274414062499, 74.58447265625], + [-81.94018554687494, 74.47270507812505], + [-82.73579101562495, 74.53027343749997], + [-83.5220703125, 74.90146484375], + [-83.53188476562494, 74.58569335937497], + [-84.42553710937503, 74.50810546875007], + [-85.06142578125, 74.60693359375003], + [-85.133447265625, 74.517431640625], + [-85.44233398437495, 74.6005859375], + [-85.80800781249994, 74.49897460937498], + [-88.42304687499995, 74.49414062499997], + [-88.53496093749993, 74.83173828125001], + [-89.55869140624995, 74.55473632812507], + [-90.55327148437499, 74.61274414062498], + [-90.88022460937498, 74.8177734375], + [-91.13457031250002, 74.64985351562498], + [-91.54912109375002, 74.65556640624999], + [-92.3892578125, 75.263330078125], + [-92.18510742187499, 75.84653320312498], + [-93.09174804687495, 76.35400390624997], + [-95.27387695312498, 76.26440429687503], + [-96.03969726562494, 76.48671875000002], + [-95.65097656249998, 76.58466796874998], + [-96.88071289062495, 76.73833007812505], + [-96.40156249999995, 76.79721679687503], + [-96.75830078124997, 76.97177734374998], + [-95.84951171875002, 77.06621093750005], + [-94.29497070312493, 76.91245117187498] + ] + ], + [ + [ + [-115.55126953125001, 77.36328125], + [-116.32919921874996, 77.137060546875], + [-115.81005859374999, 76.939111328125], + [-116.25273437500002, 76.90141601562505], + [-115.94628906250003, 76.71127929687503], + [-116.99921874999995, 76.531591796875], + [-117.23359375000001, 76.28154296875005], + [-117.99296874999999, 76.40581054687505], + [-117.88081054687497, 76.80507812500005], + [-118.79140624999994, 76.51298828125005], + [-119.080712890625, 76.12407226562505], + [-119.58037109375, 76.32651367187498], + [-119.52612304687496, 75.99721679687505], + [-119.91289062499997, 75.85883789062501], + [-120.40888671874995, 75.82563476562498], + [-120.84838867187496, 76.18266601562499], + [-121.21347656249999, 75.98369140625005], + [-122.53305664062498, 75.95092773437503], + [-122.59272460937497, 76.16206054687495], + [-122.90278320312498, 76.13471679687498], + [-122.51938476562503, 76.353173828125], + [-121.56113281250003, 76.453466796875], + [-119.09018554687496, 77.30507812500002], + [-116.84355468749995, 77.33955078124995], + [-117.03974609374995, 77.46513671875005], + [-116.51132812500003, 77.54760742187497], + [-115.55126953125001, 77.36328125] + ] + ], + [ + [ + [-89.83325195312503, 77.26762695312505], + [-90.22827148437503, 77.21245117187499], + [-90.99321289062499, 77.32949218750002], + [-91.01904296875003, 77.64389648437503], + [-89.83896484375003, 77.49140624999998], + [-89.83325195312503, 77.26762695312505] + ] + ], + [ + [ + [-104.55815429687497, 77.14174804687497], + [-105.21508789062496, 77.18208007812501], + [-106.03559570312495, 77.73984375000006], + [-105.58789062499997, 77.73598632812497], + [-104.54223632812501, 77.33774414062503], + [-104.55815429687497, 77.14174804687497] + ] + ], + [ + [ + [-95.484375, 77.79199218750003], + [-93.30097656249995, 77.73979492187505], + [-93.54394531249997, 77.466650390625], + [-95.98706054687497, 77.484130859375], + [-96.19458007812497, 77.70053710937503], + [-95.484375, 77.79199218750003] + ] + ], + [ + [ + [-101.6935546875, 77.69658203125005], + [-102.37783203124995, 77.728125], + [-102.44770507812498, 77.88061523437506], + [-101.19321289062493, 77.82978515624998], + [-101.00205078124998, 77.73510742187497], + [-101.6935546875, 77.69658203125005] + ] + ], + [ + [ + [-113.83247070312497, 77.75463867187506], + [-114.28720703124998, 77.72148437500005], + [-114.98041992187498, 77.91542968750002], + [-114.33037109374997, 78.07753906250002], + [-113.83247070312497, 77.75463867187506] + ] + ], + [ + [ + [-110.45805664062496, 78.10322265625001], + [-109.62226562499995, 78.07475585937499], + [-110.865625, 77.834130859375], + [-110.15273437500002, 77.76293945312506], + [-110.19848632812501, 77.52451171874998], + [-112.37265625000002, 77.36411132812498], + [-113.16435546875002, 77.5302734375], + [-113.21518554687498, 77.90351562500001], + [-110.45805664062496, 78.10322265625001] + ] + ], + [ + [ + [-109.81596679687499, 78.65039062500003], + [-109.48447265624995, 78.31640625], + [-111.16918945312499, 78.38627929687505], + [-111.51748046874997, 78.27470703125005], + [-112.13125, 78.366064453125], + [-113.22304687499998, 78.29790039062505], + [-112.85585937499997, 78.46684570312502], + [-110.877587890625, 78.73505859375004], + [-109.81596679687499, 78.65039062500003] + ] + ], + [ + [ + [-96.20449218749994, 78.53129882812499], + [-94.91538085937495, 78.39052734375002], + [-95.32924804687497, 78.22504882812495], + [-94.93427734374998, 78.07563476562498], + [-96.98964843749994, 77.80600585937503], + [-97.65815429687498, 78.090625], + [-96.944677734375, 78.15185546874997], + [-98.04951171874995, 78.325927734375], + [-98.33261718749998, 78.77353515625006], + [-97.38232421875, 78.78291015625001], + [-96.20449218749994, 78.53129882812499] + ] + ], + [ + [ + [-103.42602539062499, 79.315625], + [-102.57617187499996, 78.87939453125003], + [-101.70366210937502, 79.07890625000002], + [-101.128125, 78.80166015625002], + [-100.43549804687503, 78.8203125], + [-99.60942382812495, 78.58305664062507], + [-99.16640625000002, 77.85693359375003], + [-100.27465820312503, 77.83271484374995], + [-101.07412109375001, 78.19384765625], + [-102.60698242187502, 78.24892578125002], + [-102.73134765624995, 78.37104492187495], + [-103.94658203124999, 78.26000976562497], + [-104.76357421874998, 78.35166015625], + [-104.90961914062498, 78.55263671875], + [-103.57050781250003, 78.53984375000005], + [-104.02084960937502, 78.63491210937497], + [-103.37158203125, 78.73632812500003], + [-104.18500976562498, 78.78129882812505], + [-104.15195312499999, 78.989892578125], + [-104.89550781249996, 78.80815429687502], + [-104.74677734375003, 79.02709960937503], + [-105.53564453124999, 79.03251953125007], + [-105.51455078124995, 79.24248046875002], + [-105.38769531249994, 79.32358398437503], + [-103.42602539062499, 79.315625] + ] + ], + [ + [ + [-98.79160156249995, 79.98110351562505], + [-98.94521484375, 79.72407226562498], + [-100.05683593749997, 79.89824218750005], + [-100.05327148437496, 80.093359375], + [-99.15322265625001, 80.12421874999998], + [-98.79160156249995, 79.98110351562505] + ] + ], + [ + [ + [-91.88554687499999, 81.13286132812505], + [-90.64301757812498, 80.59370117187498], + [-89.23559570312494, 80.51064453125002], + [-88.85732421874997, 80.16621093750001], + [-88.19990234374998, 80.11147460937497], + [-88.5248046875, 80.41801757812507], + [-87.675, 80.37211914062505], + [-87.92231445312501, 80.09770507812499], + [-86.97719726562502, 79.89423828125001], + [-87.29516601562494, 79.58017578124998], + [-86.33696289062496, 79.63496093749995], + [-86.00703124999998, 79.47944335937498], + [-85.6478515625, 79.61142578125006], + [-85.04213867187497, 79.2845703125], + [-86.95717773437502, 78.97490234375005], + [-87.61738281249995, 78.67631835937505], + [-88.04018554687494, 78.99531250000004], + [-87.98286132812498, 78.53706054687501], + [-88.74160156250002, 78.58403320312499], + [-88.82241210937497, 78.18588867187498], + [-90.037109375, 78.60683593750002], + [-89.52568359374999, 78.15961914062495], + [-90.29721679687495, 78.32802734374997], + [-90.614404296875, 78.14985351562501], + [-92.35126953125001, 78.312890625], + [-92.8482421875, 78.46010742187497], + [-91.86689453124998, 78.54267578125001], + [-93.26660156249997, 78.60830078124997], + [-93.63442382812502, 78.75092773437498], + [-93.15986328124998, 78.77563476562503], + [-94.11459960937498, 78.92890625000001], + [-92.54721679687495, 79.28261718750002], + [-91.29990234375003, 79.372705078125], + [-92.82192382812497, 79.44990234375001], + [-93.93315429687496, 79.29072265624998], + [-94.11030273437498, 79.40156250000001], + [-95.10317382812502, 79.289892578125], + [-95.66289062500002, 79.52734374999997], + [-94.40185546874997, 79.736328125], + [-95.73935546874995, 79.66015625000003], + [-96.58906249999995, 79.91665039062497], + [-96.77324218749999, 80.13579101562502], + [-94.64589843749994, 80.04873046874997], + [-94.26259765625002, 80.19487304687499], + [-95.40507812499996, 80.13500976562506], + [-96.39409179687493, 80.31503906250003], + [-95.549072265625, 80.36660156249997], + [-95.92695312499998, 80.72065429687498], + [-93.92792968749995, 80.55917968750003], + [-95.51474609375003, 80.83813476562503], + [-94.98051757812499, 81.04965820312503], + [-93.28671874999998, 81.10029296874998], + [-94.22011718749997, 81.33076171875004], + [-93.03466796874997, 81.3462890625], + [-91.88554687499999, 81.13286132812505] + ] + ], + [ + [ + [-69.4888671875, 83.01679687499998], + [-66.42255859374998, 82.92685546875003], + [-68.46933593749995, 82.65336914062502], + [-65.29902343749995, 82.79960937500005], + [-64.98388671874997, 82.90229492187501], + [-64.50400390625, 82.77841796874998], + [-63.641015624999966, 82.81259765625003], + [-63.246777343749926, 82.4501953125], + [-62.47519531249995, 82.51958007812502], + [-61.392480468749994, 82.44189453125], + [-61.61538085937502, 82.18442382812503], + [-64.43579101562497, 81.74262695312501], + [-66.62573242187497, 81.61640624999995], + [-68.68852539062493, 81.29331054687503], + [-64.78007812499993, 81.49287109375001], + [-69.55068359375, 80.38325195312498], + [-70.71259765625001, 80.53959960937505], + [-70.264892578125, 80.23359374999998], + [-72.05595703124996, 80.12324218749995], + [-70.56840820312493, 80.09370117187498], + [-71.387841796875, 79.76176757812505], + [-72.43652343750003, 79.69438476562499], + [-74.39448242187495, 79.87407226562499], + [-73.47246093749996, 79.7564453125], + [-73.36152343750001, 79.50400390625], + [-75.50341796875, 79.41416015625], + [-76.898828125, 79.5123046875], + [-75.60273437499998, 79.23955078125005], + [-74.48120117187503, 79.22949218750006], + [-74.64091796874996, 79.03554687499997], + [-78.58164062499998, 79.075], + [-77.88276367187498, 78.9423828125], + [-76.255859375, 79.00683593749997], + [-74.486328125, 78.75009765624998], + [-74.87861328124998, 78.54482421875], + [-76.41611328124995, 78.51152343750005], + [-75.19345703125, 78.327734375], + [-75.86596679687497, 78.00981445312499], + [-78.01259765624997, 77.94604492187506], + [-78.07617187500003, 77.51904296875], + [-78.70849609374997, 77.34213867187503], + [-80.57304687499996, 77.31479492187506], + [-81.65908203124997, 77.52543945312499], + [-81.3013671875, 77.34404296875007], + [-82.056787109375, 77.29653320312497], + [-81.75634765624997, 77.20400390625005], + [-79.49726562500001, 77.19609375000005], + [-78.97919921874998, 76.89287109374999], + [-78.28886718750002, 76.97797851562501], + [-77.98330078124994, 76.75498046875006], + [-78.284326171875, 76.57124023437501], + [-80.79970703124997, 76.173583984375], + [-80.97451171874994, 76.470068359375], + [-81.71738281250003, 76.494970703125], + [-82.52983398437499, 76.723291015625], + [-82.23315429687494, 76.46582031250003], + [-83.88569335937501, 76.453125], + [-84.22377929687497, 76.67534179687499], + [-84.27534179687498, 76.35654296875006], + [-85.141259765625, 76.30458984375005], + [-86.45371093750003, 76.58486328125002], + [-86.68022460937499, 76.37661132812497], + [-87.35419921874998, 76.44804687500005], + [-87.48979492187499, 76.58583984374997], + [-87.49755859374997, 76.38627929687499], + [-88.39599609374997, 76.40527343750003], + [-88.49584960937497, 76.77285156249997], + [-88.54580078125002, 76.42089843750003], + [-89.36962890624997, 76.474462890625], + [-89.49975585937503, 76.82680664062502], + [-88.39814453124995, 77.10395507812501], + [-86.81225585937497, 77.18491210937498], + [-87.68144531249996, 77.43637695312503], + [-88.01699218750002, 77.78471679687505], + [-86.75507812499998, 77.86372070312498], + [-85.58847656249998, 77.46113281250004], + [-84.73867187499997, 77.36103515624998], + [-83.72128906249998, 77.41420898437497], + [-82.7103515625, 77.84951171875002], + [-82.5953125, 77.99213867187504], + [-83.77939453125, 77.53261718750002], + [-85.28935546874996, 77.55903320312498], + [-85.54755859374998, 77.92768554687495], + [-84.61542968749998, 78.19570312500002], + [-84.22270507812499, 78.176025390625], + [-84.91035156249993, 78.23969726562501], + [-84.78320312499997, 78.52758789062506], + [-85.5859375, 78.10957031249998], + [-86.21777343750003, 78.08120117187497], + [-85.92006835937494, 78.34287109374998], + [-86.91323242187494, 78.126806640625], + [-87.5517578125, 78.17661132812503], + [-86.80791015624999, 78.77436523437495], + [-85.00375976562495, 78.912255859375], + [-83.27143554687501, 78.77031250000002], + [-81.75009765624995, 78.97578124999995], + [-82.43876953125002, 78.903662109375], + [-84.41201171875002, 78.99658203125003], + [-84.38359375000002, 79.1185546875], + [-83.57587890624995, 79.05366210937501], + [-86.42075195312498, 79.84521484374997], + [-86.49853515625003, 80.25825195312501], + [-83.72363281250003, 80.22895507812501], + [-81.68837890625, 79.685791015625], + [-80.47592773437498, 79.60625], + [-80.12446289062495, 79.66948242187507], + [-81.01015625000002, 79.693115234375], + [-82.98701171874995, 80.32260742187498], + [-76.86298828124995, 80.86479492187505], + [-78.71621093749994, 80.95166015624997], + [-76.88510742187503, 81.43027343750006], + [-81.00703125000001, 80.6548828125], + [-82.88432617187502, 80.57753906249997], + [-82.22236328124998, 80.77231445312503], + [-84.41782226562495, 80.52675781250002], + [-86.250341796875, 80.56577148437506], + [-86.60307617187499, 80.66401367187498], + [-85.63930664062494, 80.92460937500007], + [-83.288818359375, 81.14794921875], + [-85.780859375, 81.03505859375], + [-87.32988281250002, 80.669775390625], + [-88.00366210937497, 80.675390625], + [-89.16689453125, 80.94130859375], + [-86.47675781249993, 81.03574218750006], + [-84.94121093750002, 81.28623046875], + [-87.27509765624995, 81.080810546875], + [-89.623046875, 81.032470703125], + [-89.94731445312499, 81.17265625000005], + [-89.20869140624998, 81.25009765625003], + [-89.67368164062503, 81.32861328125003], + [-87.59702148437498, 81.52583007812498], + [-88.47905273437502, 81.56464843749998], + [-90.41630859374996, 81.40537109375003], + [-89.82167968749997, 81.63486328124998], + [-91.29238281250002, 81.57124023437498], + [-91.64755859374998, 81.68383789062503], + [-88.06318359375001, 82.09648437500007], + [-87.01821289062502, 81.95874023437497], + [-86.62680664062495, 82.05102539062503], + [-85.04482421874997, 81.9828125], + [-86.615625, 82.21855468750007], + [-84.89682617187503, 82.44941406250001], + [-82.63369140625002, 82.07729492187497], + [-82.53691406250002, 82.24726562499995], + [-79.465625, 81.85112304687499], + [-82.44755859374993, 82.39501953125003], + [-81.68115234375003, 82.51865234375], + [-82.11684570312497, 82.62866210937503], + [-80.8625, 82.57153320312503], + [-81.01015625000002, 82.77905273437503], + [-78.748779296875, 82.67939453124998], + [-80.15493164062497, 82.91113281250003], + [-77.61806640624997, 82.89584960937503], + [-76.009375, 82.53515625], + [-75.565625, 82.60854492187502], + [-77.12490234374994, 83.00854492187497], + [-74.41416015624995, 83.01313476562501], + [-72.65869140625, 82.72163085937495], + [-73.44189453124994, 82.90483398437499], + [-72.811669921875, 83.08120117187502], + [-71.98320312499996, 83.10141601562498], + [-70.94038085937495, 82.90224609375], + [-71.08481445312498, 83.08266601562497], + [-69.96992187499995, 83.11611328125005], + [-69.4888671875, 83.01679687499998] + ] + ] + ] + }, + "properties": { "name": "Canada", "childNum": 110 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [9.527658197470123, 47.27026989773668], + [9.46249431093294, 47.19858962254578], + [9.46249431093294, 47.09010747968864], + [9.409458596647225, 47.02019676540292], + [9.579979133936737, 47.05856388629306], + [9.580273437500011, 47.057373046875], + [10.133496093750011, 46.851513671875], + [10.349414062500017, 46.98476562499999], + [10.414941406250023, 46.964404296874996], + [10.45458984375, 46.8994140625], + [10.452832031250011, 46.86494140625], + [10.406054687500017, 46.73486328125], + [10.39794921875, 46.6650390625], + [10.4306640625, 46.550048828125], + [10.195507812500011, 46.62109375], + [10.1375, 46.61435546875], + [10.087011718750006, 46.599902343749996], + [10.061230468750011, 46.546777343749994], + [10.038281250000011, 46.483203125], + [10.045605468750011, 46.447900390624994], + [10.081933593750023, 46.420751953125], + [10.109667968750017, 46.362841796874996], + [10.128320312500023, 46.238232421875], + [10.08056640625, 46.227978515625], + [10.041015625, 46.238085937499996], + [9.939257812500017, 46.36181640625], + [9.884472656250011, 46.3677734375], + [9.787792968750011, 46.346044921875], + [9.639453125000017, 46.2958984375], + [9.57958984375, 46.29609375], + [9.528710937500023, 46.306201171874996], + [9.427636718750023, 46.482324218749994], + [9.399316406250023, 46.4806640625], + [9.304394531250011, 46.495556640625], + [9.203417968750017, 46.21923828125], + [9.11874162946429, 46.014892578125], + [8.97551618303573, 45.81677455357143], + [8.74961495535715, 46.02246372767857], + [8.818554687500011, 46.0771484375], + [8.458398437500023, 46.245898437499996], + [8.370703125, 46.445117187499996], + [8.298535156250011, 46.40341796875], + [8.23193359375, 46.341210937499994], + [8.08154296875, 46.256005859374994], + [7.9931640625, 46.015917968749996], + [7.327929687500017, 45.912353515625], + [7.129003906250006, 45.880419921874996], + [7.055761718750006, 45.90380859375], + [7.02109375, 45.92578125], + [6.953710937500006, 46.017138671874996], + [6.897265625000017, 46.0517578125], + [6.772070312500006, 46.16513671875], + [6.758105468750017, 46.415771484375], + [6.578222656250006, 46.437353515625], + [6.428906250000011, 46.430517578125], + [6.321875, 46.393701171874994], + [6.234667968750017, 46.3326171875], + [6.199414062500011, 46.19306640625], + [6.086621093750011, 46.147021484374996], + [6.006640625000017, 46.142333984375], + [5.971484375000017, 46.151220703125], + [5.970019531250017, 46.214697265625], + [6.0361328125, 46.238085937499996], + [6.095898437500011, 46.27939453125], + [6.129687500000017, 46.5669921875], + [6.41015625, 46.755419921874996], + [6.429003906250017, 46.832275390625], + [6.45625, 46.94833984375], + [6.624804687500017, 47.004345703125], + [6.666894531250023, 47.026513671874994], + [6.688085937500006, 47.058251953124994], + [6.820703125000023, 47.16318359375], + [6.952050781250023, 47.2671875], + [6.978515625, 47.302050781249996], + [7.000585937500006, 47.322509765625], + [7.000585937500006, 47.339453125], + [6.900390625, 47.39423828125], + [6.968359375, 47.45322265625], + [7.136035156250017, 47.48984375], + [7.343164062500023, 47.43310546875], + [7.615625, 47.592724609375], + [8.454003906250023, 47.59619140625], + [8.559472656250023, 47.6240234375], + [8.570507812500011, 47.63779296875], + [8.567089843750011, 47.651904296874996], + [8.55234375, 47.659130859375], + [8.451757812500006, 47.651806640625], + [8.413281250000011, 47.6626953125], + [8.403417968750006, 47.687792968749996], + [8.435742187500011, 47.731347656249994], + [8.572656250000023, 47.775634765625], + [9.524023437500006, 47.52421875], + [9.625878906250023, 47.467041015625], + [9.527539062500011, 47.270751953125], + [9.527658197470123, 47.27026989773668] + ] + ] + }, + "properties": { "name": "Switzerland", "childNum": 1 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [-67.28886718749999, -55.776855468749964], + [-67.55996093749997, -55.72480468750002], + [-67.39736328124997, -55.58515625], + [-67.28886718749999, -55.776855468749964] + ] + ], + [ + [ + [-67.07993164062498, -55.15380859374996], + [-67.33969726562495, -55.292578124999984], + [-67.4947265625, -55.177441406249976], + [-68.07001953124995, -55.22109374999999], + [-68.30136718750003, -54.98066406250003], + [-67.245263671875, -54.977636718750034], + [-67.07993164062498, -55.15380859374996] + ] + ], + [ + [ + [-69.70297851562503, -54.91904296875], + [-68.90078125000002, -55.01777343750004], + [-68.45800781249997, -54.95966796875002], + [-68.61328124999997, -55.155566406250045], + [-68.28266601562495, -55.25517578125], + [-68.04833984375, -55.6431640625], + [-68.86704101562498, -55.45019531250003], + [-68.89008789062498, -55.2412109375], + [-69.19262695312497, -55.171875], + [-69.35922851562498, -55.300683593749945], + [-69.18085937499995, -55.47480468749998], + [-69.41181640624995, -55.44423828124997], + [-69.97978515625002, -55.14746093749999], + [-69.88442382812494, -54.88203125000001], + [-69.70297851562503, -54.91904296875] + ] + ], + [ + [ + [-70.9916015625, -54.86796874999999], + [-70.80483398437497, -54.96767578124996], + [-70.41752929687493, -54.908886718749976], + [-70.29785156249997, -55.11376953124997], + [-70.47558593749994, -55.17705078124998], + [-71.43720703125001, -54.88925781249997], + [-70.9916015625, -54.86796874999999] + ] + ], + [ + [ + [-71.390478515625, -54.03281250000002], + [-71.02192382812495, -54.111816406250036], + [-71.14326171874998, -54.374023437499986], + [-71.473291015625, -54.23115234375001], + [-71.94853515624999, -54.300878906250006], + [-72.21044921874997, -54.04775390624995], + [-71.996484375, -53.884863281249984], + [-71.390478515625, -54.03281250000002] + ] + ], + [ + [ + [-72.92324218749997, -53.481640625], + [-72.88222656249997, -53.578320312499976], + [-72.48227539062503, -53.58808593750001], + [-72.20541992187503, -53.80742187500002], + [-72.408544921875, -54.00380859374997], + [-72.87099609375, -54.12656250000002], + [-72.76376953125, -53.86484375], + [-73.03945312499994, -53.83281250000004], + [-73.08076171875001, -53.99804687499995], + [-73.21064453125001, -53.98583984374995], + [-73.31435546875, -53.72919921874998], + [-73.845458984375, -53.54580078125001], + [-73.44707031249993, -53.41005859374998], + [-72.92324218749997, -53.481640625] + ] + ], + [ + [ + [-74.38574218749994, -52.92236328125001], + [-73.65400390624998, -53.06982421875003], + [-73.13520507812498, -53.35390625], + [-73.56728515625, -53.3068359375], + [-73.86694335937494, -53.096875], + [-74.27021484374995, -53.08154296875002], + [-74.71201171874998, -52.74873046874998], + [-74.38574218749994, -52.92236328125001] + ] + ], + [ + [ + [-68.62993164062499, -52.65263671875004], + [-68.65322265624994, -54.85361328124999], + [-69.48627929687493, -54.85888671875], + [-69.72343750000002, -54.71210937500003], + [-70.49716796875, -54.80957031249999], + [-71.83154296874997, -54.62617187500002], + [-71.92773437500003, -54.52871093749997], + [-71.80014648437498, -54.433984374999945], + [-71.07993164062498, -54.444238281249994], + [-70.79726562500002, -54.32724609374996], + [-70.70112304687498, -54.48544921875004], + [-70.31098632812498, -54.52851562500002], + [-70.86308593749993, -54.11044921875003], + [-70.86772460937499, -53.88417968750002], + [-70.53129882812502, -53.627343750000016], + [-70.37973632812495, -53.98671874999995], + [-70.62983398437493, -54.005566406249976], + [-70.53530273437494, -54.136132812500016], + [-70.16899414062502, -54.37929687499999], + [-69.74184570312494, -54.30585937500005], + [-69.25317382812494, -54.557421875000045], + [-69.04433593749997, -54.40673828124999], + [-69.98813476562503, -54.10908203125001], + [-70.15112304687503, -53.88808593750002], + [-70.09111328124996, -53.72177734374998], + [-69.35595703125003, -53.41630859375001], + [-69.63701171874999, -53.33408203125004], + [-70.32929687499998, -53.37763671875003], + [-70.44335937499994, -53.085546875000034], + [-70.130615234375, -52.942773437499994], + [-70.38012695312494, -52.75195312500002], + [-69.93544921874997, -52.82109374999998], + [-69.41406249999997, -52.48623046874997], + [-69.16704101562499, -52.66757812499997], + [-68.78979492187497, -52.576757812500034], + [-68.62993164062499, -52.65263671875004] + ] + ], + [ + [ + [-74.82294921874993, -51.63017578125001], + [-74.53681640624998, -51.96513671875004], + [-74.69448242187497, -52.27919921874999], + [-74.85180664062494, -52.27070312500003], + [-75.10537109375, -51.78886718750001], + [-74.82294921874993, -51.63017578125001] + ] + ], + [ + [ + [-74.55864257812499, -51.27705078125001], + [-74.62036132812497, -51.395703125000026], + [-75.04736328125, -51.39833984375003], + [-75.28911132812496, -51.625390625000016], + [-75.15366210937498, -51.278808593750014], + [-74.73666992187503, -51.20761718749999], + [-74.55864257812499, -51.27705078125001] + ] + ], + [ + [ + [-75.302001953125, -50.67998046875005], + [-75.411376953125, -50.76435546875001], + [-75.42763671875002, -50.48056640625002], + [-75.11533203124998, -50.510449218749976], + [-75.302001953125, -50.67998046875005] + ] + ], + [ + [ + [-75.05478515625, -50.29609375], + [-75.44912109374997, -50.34335937500004], + [-75.32666015624997, -50.01181640625], + [-74.8759765625, -50.10996093750001], + [-75.05478515625, -50.29609375] + ] + ], + [ + [ + [-75.106689453125, -48.83652343750001], + [-75.38994140624999, -49.15917968750002], + [-75.64116210937499, -49.195410156250034], + [-75.48764648437498, -49.082421875000016], + [-75.58310546874998, -48.85888671874995], + [-75.106689453125, -48.83652343750001] + ] + ], + [ + [ + [-74.47617187499998, -49.14785156250002], + [-74.59472656249997, -50.00664062500001], + [-74.76298828124996, -50.01142578125001], + [-74.88041992187502, -49.72587890625001], + [-74.72382812499998, -49.42382812500003], + [-74.960107421875, -49.533007812499974], + [-75.06601562499998, -49.85234375000002], + [-75.54980468749994, -49.79130859375002], + [-75.30585937499998, -49.49404296875003], + [-75.46748046874995, -49.35888671875003], + [-75.08603515624998, -49.27021484375], + [-75.21015624999995, -49.14804687499998], + [-74.94921875, -48.960156249999976], + [-74.89624023437503, -48.73320312500002], + [-74.54609374999993, -48.76689453125004], + [-74.47617187499998, -49.14785156250002] + ] + ], + [ + [ + [-75.51025390624997, -48.76347656250005], + [-75.65092773437496, -48.58632812500002], + [-75.57148437499993, -48.095898437500026], + [-75.39140625000002, -48.01972656249997], + [-75.15849609374999, -48.62265624999996], + [-75.51025390624997, -48.76347656250005] + ] + ], + [ + [ + [-74.56728515625, -48.591992187500026], + [-74.92304687499998, -48.62646484375003], + [-75.21289062499997, -48.141699218750034], + [-75.19829101562502, -47.974609375000014], + [-74.895654296875, -47.839355468749986], + [-74.56728515625, -48.591992187500026] + ] + ], + [ + [ + [-75.11220703124997, -47.8376953125], + [-75.26103515625002, -47.76386718749998], + [-74.92646484374998, -47.72314453125003], + [-75.11220703124997, -47.8376953125] + ] + ], + [ + [ + [-74.31289062500002, -45.69150390625002], + [-74.46552734374995, -45.757226562499994], + [-74.68984375, -45.66259765625], + [-74.310546875, -45.17265625000002], + [-74.31289062500002, -45.69150390625002] + ] + ], + [ + [ + [-73.63217773437498, -44.82148437499997], + [-73.81845703125, -44.65214843750002], + [-73.72392578124993, -44.544238281249974], + [-73.63217773437498, -44.82148437499997] + ] + ], + [ + [ + [-72.98613281249999, -44.780078124999974], + [-73.22846679687498, -44.85996093749999], + [-73.39707031249998, -44.77431640624995], + [-73.44506835937497, -44.641015624999966], + [-73.20771484374993, -44.33496093749997], + [-72.7763671875, -44.50859374999999], + [-72.98613281249999, -44.780078124999974] + ] + ], + [ + [ + [-73.73535156249997, -44.39453125000003], + [-74.00205078125003, -44.59091796874998], + [-73.728173828125, -45.195898437500034], + [-74.016259765625, -45.344921875000026], + [-74.61777343749998, -44.64794921874996], + [-74.50180664062498, -44.47353515624995], + [-74.09721679687496, -44.38935546875004], + [-73.99492187499999, -44.140234375], + [-73.70322265624998, -44.27412109375001], + [-73.73535156249997, -44.39453125000003] + ] + ], + [ + [ + [-73.81064453125003, -43.827246093750006], + [-73.95566406249998, -43.921972656250034], + [-74.14296874999997, -43.872167968750006], + [-73.81064453125003, -43.827246093750006] + ] + ], + [ + [ + [-73.77338867187498, -43.3458984375], + [-74.114404296875, -43.35791015624996], + [-74.387353515625, -43.231640625], + [-74.03666992187496, -41.79550781249998], + [-73.52783203124997, -41.89628906249999], + [-73.42290039062499, -42.192871093750014], + [-73.47080078124998, -42.46630859375004], + [-73.78925781249993, -42.58574218750003], + [-73.43632812499996, -42.9365234375], + [-73.74965820312494, -43.15908203124995], + [-73.77338867187498, -43.3458984375] + ] + ], + [ + [ + [-78.80415039062501, -33.646484374999986], + [-78.98945312499993, -33.66171874999998], + [-78.87744140625003, -33.57519531250003], + [-78.80415039062501, -33.646484374999986] + ] + ], + [ + [ + [-109.27998046874994, -27.14042968749996], + [-109.434130859375, -27.171289062500023], + [-109.39047851562499, -27.068359375000014], + [-109.27998046874994, -27.14042968749996] + ] + ], + [ + [ + [-67.19487304687493, -22.821679687500037], + [-67.00878906249994, -23.00136718750005], + [-67.35620117187503, -24.033789062499963], + [-68.25029296875002, -24.391992187500023], + [-68.56201171875, -24.74736328125003], + [-68.38422851562495, -25.091894531249977], + [-68.59208984375002, -25.420019531250034], + [-68.41450195312498, -26.153710937500023], + [-68.59160156249999, -26.47041015624997], + [-68.31865234374999, -26.973242187500006], + [-68.59208984375002, -27.140039062499966], + [-68.84633789062494, -27.153710937499994], + [-69.17441406249998, -27.924707031250037], + [-69.65693359374995, -28.413574218749986], + [-69.82788085937497, -29.10322265624997], + [-70.02680664062501, -29.324023437500017], + [-69.95996093749997, -30.078320312500026], + [-69.84428710937493, -30.175], + [-69.95634765624996, -30.35820312500003], + [-70.15322265625, -30.360937499999963], + [-70.30908203124994, -31.02265625000004], + [-70.51958007812493, -31.1484375], + [-70.585205078125, -31.569433593749963], + [-70.25439453125, -31.957714843750026], + [-70.36376953125, -32.08349609374997], + [-70.02197265625, -32.88457031250002], + [-70.08486328125002, -33.20175781249998], + [-69.81962890624999, -33.28378906249999], + [-69.85244140625, -34.224316406250026], + [-70.05205078124999, -34.30078124999997], + [-70.39316406250003, -35.146875], + [-70.55517578125, -35.246875], + [-70.41572265625001, -35.52304687500002], + [-70.40478515625, -36.06171874999998], + [-71.05551757812498, -36.52373046874996], + [-71.19218750000002, -36.84365234375004], + [-71.16757812499998, -37.76230468749996], + [-70.858642578125, -38.60449218750003], + [-71.40156249999995, -38.93505859374996], + [-71.53945312499997, -39.60244140624995], + [-71.71992187499995, -39.63525390624997], + [-71.65976562499998, -40.02080078125], + [-71.81831054687493, -40.17666015624995], + [-71.70898437499997, -40.381738281249994], + [-71.93212890624994, -40.69169921874999], + [-71.91127929687497, -41.650390624999986], + [-71.75, -42.04677734375001], + [-72.10820312499993, -42.25185546874995], + [-72.14643554687498, -42.990039062499974], + [-71.750634765625, -43.237304687499986], + [-71.90498046875001, -43.34755859374998], + [-71.68007812500002, -43.92958984374998], + [-71.82001953124993, -44.38310546875], + [-71.21259765624998, -44.44121093750003], + [-71.15971679687496, -44.56025390625004], + [-71.26113281250002, -44.763085937499966], + [-72.06372070312503, -44.771875], + [-72.04169921874998, -44.90419921875004], + [-71.5962890625, -44.97919921875004], + [-71.34931640624995, -45.33193359374995], + [-71.74619140624998, -45.57890625], + [-71.63154296874998, -45.95371093749998], + [-71.87568359374998, -46.160546875], + [-71.69965820312501, -46.6513671875], + [-71.94023437499999, -46.83125], + [-71.90498046875001, -47.201660156250014], + [-72.34594726562497, -47.49267578124997], + [-72.517919921875, -47.87636718749998], + [-72.32832031250001, -48.11005859374998], + [-72.35473632812497, -48.36582031250005], + [-72.582861328125, -48.47539062499999], + [-72.65126953125, -48.84160156249998], + [-73.03364257812501, -49.014355468750004], + [-73.13525390625, -49.30068359374999], + [-73.46157226562497, -49.31386718750001], + [-73.55419921875, -49.463867187500014], + [-73.50126953124996, -50.125292968750024], + [-73.15292968749998, -50.73828125000003], + [-72.50981445312496, -50.607519531250034], + [-72.34023437499997, -50.68183593749999], + [-72.40766601562501, -51.54082031250002], + [-71.91865234374995, -51.98955078125004], + [-69.96025390624993, -52.00820312500002], + [-68.443359375, -52.35664062500004], + [-69.24101562499996, -52.20546874999997], + [-69.62031249999995, -52.46474609374995], + [-70.79511718749995, -52.76875], + [-70.99584960937497, -53.77929687499997], + [-71.29775390625002, -53.88339843750004], + [-72.1744140625, -53.632324218749964], + [-72.41289062500002, -53.35019531250004], + [-71.94169921874993, -53.23408203125001], + [-71.89169921874998, -53.523535156250006], + [-71.79145507812498, -53.48457031249997], + [-71.74052734374999, -53.232617187499976], + [-71.28896484375002, -53.03369140624995], + [-71.22714843750003, -52.810644531249984], + [-71.38774414062496, -52.76425781250004], + [-72.27802734374998, -53.13232421874997], + [-72.54892578125, -53.4607421875], + [-73.05273437499997, -53.24345703125005], + [-72.72768554687502, -52.7623046875], + [-72.453466796875, -52.814453124999964], + [-72.11757812499997, -52.65], + [-71.51127929687502, -52.60537109375], + [-72.22568359374998, -52.52099609374995], + [-72.43769531250001, -52.62578124999998], + [-72.71210937499995, -52.53554687499999], + [-73.12246093749997, -53.073925781249976], + [-73.64521484374998, -52.83701171875003], + [-73.2408203125, -52.707128906250034], + [-73.12392578125, -52.487988281249976], + [-73.24414062499997, -52.62402343749998], + [-73.58569335937503, -52.68574218750003], + [-74.01445312499999, -52.63935546875], + [-74.26494140624993, -52.1048828125], + [-73.83447265625, -52.23398437500001], + [-73.68432617187494, -52.07773437499998], + [-73.26044921874993, -52.157812500000034], + [-72.79501953124998, -51.94951171875005], + [-72.57084960937496, -52.200097656249945], + [-72.67705078125002, -52.38466796874998], + [-72.52333984374997, -52.255468750000034], + [-72.62460937499998, -51.94648437499997], + [-72.48964843750002, -51.76367187500003], + [-72.76123046875, -51.57324218749996], + [-73.16875, -51.45390624999998], + [-72.60004882812495, -51.79912109374997], + [-73.51816406250003, -52.04101562499996], + [-73.75263671874993, -51.795507812500034], + [-74.19667968749997, -51.68056640624997], + [-73.92978515624995, -51.61787109374999], + [-73.93950195312499, -51.26630859375005], + [-74.81474609374996, -51.06289062499999], + [-75.09467773437495, -50.68125], + [-74.68574218749995, -50.662011718749945], + [-74.77587890625003, -50.46992187499998], + [-74.64448242187498, -50.360937499999984], + [-74.365576171875, -50.487890625], + [-74.13940429687503, -50.81777343749997], + [-73.80654296875, -50.93837890625003], + [-73.654443359375, -50.49267578125], + [-73.97802734375003, -50.827050781249994], + [-74.18559570312493, -50.485351562500014], + [-73.95034179687497, -50.510546875], + [-74.62958984374998, -50.19404296875], + [-74.333740234375, -49.97460937499997], + [-73.95859374999998, -49.994726562499984], + [-74.32392578124995, -49.783398437500004], + [-74.29082031249996, -49.604101562499984], + [-73.83637695312493, -49.609375], + [-74.09443359374993, -49.42968749999998], + [-73.93496093749994, -49.02089843750001], + [-74.2212890625, -49.500585937500034], + [-74.36655273437503, -49.40048828124998], + [-74.34101562499998, -48.59570312499998], + [-74.00908203124996, -48.475], + [-74.47441406249999, -48.46396484374996], + [-74.58466796874998, -47.999023437500014], + [-73.39106445312498, -48.14589843750001], + [-73.60991210937499, -47.993945312500045], + [-73.71586914062499, -47.65546875000001], + [-73.94086914062498, -47.92939453125004], + [-74.22705078124994, -47.96894531250001], + [-74.654931640625, -47.702246093750034], + [-74.5337890625, -47.567675781249974], + [-74.24296874999999, -47.67929687499998], + [-74.13408203125002, -47.590820312499986], + [-74.48266601562497, -47.43046875], + [-74.15839843749998, -47.18251953125002], + [-74.31357421874998, -46.78818359374998], + [-74.45419921875003, -46.76679687499997], + [-74.51225585937496, -46.88515625000002], + [-75.00595703125, -46.74111328124998], + [-74.98417968750002, -46.51210937499995], + [-75.54033203124999, -46.69873046874996], + [-75.43037109374995, -46.93457031249996], + [-75.70639648437498, -46.70527343749997], + [-74.924462890625, -46.159667968750014], + [-75.06669921874993, -45.874902343749994], + [-74.15786132812497, -45.7671875], + [-74.122705078125, -45.49619140625002], + [-73.95717773437494, -45.40439453124998], + [-73.825, -45.446875], + [-74.01992187500002, -46.055859375], + [-74.39296875, -46.21738281250005], + [-73.96757812500002, -46.15410156250003], + [-73.87871093749993, -45.846875], + [-73.73525390624994, -45.81171875], + [-73.70815429687502, -46.070312500000014], + [-73.94863281249997, -46.533105468749966], + [-73.845361328125, -46.56601562500002], + [-73.59184570312493, -45.89912109375004], + [-73.73076171874999, -45.47998046875], + [-73.26621093749995, -45.346191406250014], + [-72.933837890625, -45.45234374999997], + [-73.44497070312497, -45.23818359374995], + [-73.36245117187502, -44.97822265625001], + [-72.73896484375001, -44.73417968750003], + [-72.680078125, -44.59394531249997], + [-72.66386718749999, -44.43642578124995], + [-73.26508789062498, -44.16865234375001], + [-73.22446289062498, -43.89794921875003], + [-73.06879882812495, -43.86201171874998], + [-72.99658203125, -43.63154296875001], + [-73.07597656250002, -43.323632812499994], + [-72.75800781249998, -43.039453125], + [-72.84804687500002, -42.66914062499997], + [-72.77392578125003, -42.505175781250045], + [-72.63183593750003, -42.509667968749994], + [-72.77324218749996, -42.257714843749994], + [-72.63105468749995, -42.199804687500006], + [-72.412353515625, -42.388183593750014], + [-72.49941406249997, -41.98085937499999], + [-72.82407226562503, -41.90878906249996], + [-72.36040039062499, -41.64912109375], + [-72.31826171875, -41.49902343749997], + [-72.54238281250002, -41.690625], + [-72.95283203124995, -41.51474609374998], + [-73.24179687499995, -41.78085937500002], + [-73.62402343750003, -41.77363281249997], + [-73.73515625000002, -41.74248046875002], + [-73.62392578125, -41.581347656250045], + [-73.81074218749995, -41.51748046875001], + [-73.96586914062493, -41.118261718750034], + [-73.67099609375, -39.96318359374999], + [-73.41040039062503, -39.78916015624998], + [-73.22646484375002, -39.22441406250003], + [-73.52021484375001, -38.509375], + [-73.46479492187498, -38.04033203125003], + [-73.66181640624998, -37.69853515625003], + [-73.66240234375002, -37.341015625000026], + [-73.60166015624998, -37.18847656250003], + [-73.21596679687502, -37.16689453124998], + [-73.11806640624997, -36.68837890625002], + [-72.58735351562493, -35.759667968749994], + [-72.62392578125002, -35.5857421875], + [-72.22377929687494, -35.096191406250014], + [-72.00283203124997, -34.16533203125], + [-71.66435546875002, -33.65263671875], + [-71.74296875, -33.09511718750001], + [-71.45224609374998, -32.65957031250001], + [-71.70893554687495, -30.62802734375002], + [-71.66948242187499, -30.33037109374996], + [-71.40039062499997, -30.142968749999966], + [-71.31572265624996, -29.649707031250017], + [-71.51923828124993, -28.926464843750026], + [-71.30673828124998, -28.672460937499963], + [-71.08652343749998, -27.814453124999957], + [-70.92578125, -27.588671874999974], + [-70.64658203124998, -26.329394531250017], + [-70.71372070312498, -25.78417968749997], + [-70.44536132812502, -25.17265624999999], + [-70.57412109374994, -24.644335937500003], + [-70.39233398437494, -23.565917968749957], + [-70.59335937499995, -23.255468750000034], + [-70.56318359374995, -23.057031250000023], + [-70.33168945312494, -22.848632812500014], + [-70.08002929687501, -21.356835937500037], + [-70.19702148437494, -20.725390625], + [-70.15742187499995, -19.70585937500003], + [-70.41826171874999, -18.345605468750023], + [-69.92636718749998, -18.206054687500014], + [-69.80258789062498, -17.990234375000014], + [-69.85209960937493, -17.70380859375001], + [-69.68476562499995, -17.649804687500023], + [-69.58642578125, -17.57324218749997], + [-69.51093749999998, -17.50605468749997], + [-69.31337890624997, -17.943164062500017], + [-69.28232421875003, -17.96484375], + [-69.09394531249993, -18.05048828125004], + [-69.14545898437495, -18.14404296875], + [-69.09228515624994, -18.28242187500004], + [-69.02680664062493, -18.65625], + [-68.97885742187503, -18.81298828125003], + [-68.96831054687502, -18.967968749999983], + [-68.85795898437499, -19.09335937500005], + [-68.62055664062495, -19.29667968749999], + [-68.54785156249997, -19.341113281249974], + [-68.49199218749996, -19.381933593750034], + [-68.47016601562495, -19.409960937499974], + [-68.46289062499997, -19.43281250000001], + [-68.57529296874998, -19.56015625000002], + [-68.69829101562499, -19.721093750000037], + [-68.69619140625, -19.74072265625003], + [-68.57827148437494, -19.856542968750006], + [-68.559375, -19.902343750000014], + [-68.56069335937502, -19.96708984374996], + [-68.75932617187499, -20.115527343750003], + [-68.74516601562493, -20.45859375], + [-68.48432617187498, -20.628417968749957], + [-68.55825195312497, -20.90195312499999], + [-68.197021484375, -21.30029296874997], + [-68.18642578124997, -21.618554687499966], + [-67.88173828124997, -22.493359375000026], + [-67.87944335937496, -22.822949218750026], + [-67.57993164062495, -22.89169921874999], + [-67.36225585937493, -22.85517578125001], + [-67.19487304687493, -22.821679687500037] + ] + ] + ] + }, + "properties": { "name": "Chile", "childNum": 26 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [110.88876953125006, 19.99194335937497], + [111.01367187500003, 19.65546875000001], + [110.64091796875002, 19.291210937499955], + [110.45126953125012, 18.747949218750023], + [110.06738281249997, 18.447558593750045], + [109.51933593750007, 18.21826171875003], + [108.7015625, 18.535253906250034], + [108.66552734375003, 19.304101562499994], + [109.27666015625002, 19.761132812500023], + [109.17744140625004, 19.768457031250023], + [109.26347656250007, 19.882666015625006], + [110.1715820312501, 20.053710937500057], + [110.58818359375002, 19.976367187500017], + [110.6517578125, 20.137744140625017], + [110.88876953125006, 19.99194335937497] + ] + ], + [ + [ + [110.38515625000005, 21.093164062499966], + [110.52158203125006, 21.083105468750063], + [110.50390625000003, 20.96772460937501], + [110.28095703125004, 21.001171874999983], + [110.38515625000005, 21.093164062499966] + ] + ], + [ + [ + [112.64375, 21.63964843750003], + [112.525, 21.62304687500003], + [112.64765625000004, 21.710253906250017], + [112.64375, 21.63964843750003] + ] + ], + [ + [ + [112.79023437500004, 21.601855468750045], + [112.78203125000007, 21.772265625000045], + [112.86259765625002, 21.75263671875004], + [112.79023437500004, 21.601855468750045] + ] + ], + [ + [ + [118.1830078125, 24.496289062499983], + [118.0905273437501, 24.446142578125063], + [118.10380859375002, 24.552343750000034], + [118.1830078125, 24.496289062499983] + ] + ], + [ + [ + [119.82089843750006, 25.45698242187504], + [119.70029296875012, 25.432714843750063], + [119.72255859375005, 25.638818359375023], + [119.83837890625003, 25.591064453125], + [119.82089843750006, 25.45698242187504] + ] + ], + [ + [ + [121.2513671875, 28.086425781250057], + [121.13154296875004, 28.062597656250006], + [121.20546875, 28.204394531250017], + [121.2513671875, 28.086425781250057] + ] + ], + [ + [ + [122.29589843750003, 29.96342773437499], + [122.02402343750012, 30.01333007812505], + [121.96943359375004, 30.143115234375017], + [122.28447265625007, 30.068017578124994], + [122.29589843750003, 29.96342773437499] + ] + ], + [ + [ + [121.86269531250005, 31.492285156249977], + [121.519921875, 31.549609375000017], + [121.2111328125001, 31.80537109375001], + [121.86269531250005, 31.492285156249977] + ] + ], + [ + [ + [130.52695312500012, 42.535400390625], + [130.24667968750012, 42.744824218749955], + [130.24033203125006, 42.891796874999955], + [129.89824218750002, 42.998144531250034], + [129.69785156250012, 42.448144531249994], + [129.3136718750001, 42.41357421874997], + [128.92343750000006, 42.038232421874966], + [128.04521484375007, 41.9875], + [128.28925781250004, 41.60742187500006], + [128.14941406249997, 41.38774414062496], + [127.17968750000003, 41.531347656250006], + [126.95478515625004, 41.76948242187501], + [126.74306640625, 41.724853515625], + [125.98906250000002, 40.904638671875034], + [124.8893554687501, 40.459814453125006], + [124.36210937500002, 40.004052734374994], + [124.10576171875002, 39.84101562499998], + [123.65087890625003, 39.881591796875], + [122.8400390625001, 39.600830078125], + [121.98232421875, 39.05317382812498], + [121.67724609374997, 39.00341796875006], + [121.64990234375003, 38.865087890625034], + [121.16357421874997, 38.73164062500001], + [121.10673828125002, 38.920800781249994], + [121.6798828125001, 39.10869140625002], + [121.62763671875004, 39.22016601562498], + [121.81845703125006, 39.38652343750002], + [121.27548828125006, 39.38476562500003], + [121.26748046875, 39.544677734375], + [121.51757812499997, 39.638964843750045], + [121.51738281250002, 39.84482421875006], + [121.8009765625001, 39.950537109375006], + [122.27500000000012, 40.541845703125034], + [121.83486328125005, 40.97426757812502], + [121.72929687500002, 40.84614257812504], + [121.1745117187501, 40.901269531249994], + [120.47910156250006, 40.23095703125003], + [119.39111328125003, 39.75249023437499], + [118.976953125, 39.182568359374955], + [118.29785156249997, 39.067089843749955], + [118.04091796875, 39.22675781249998], + [117.86572265625003, 39.191259765625034], + [117.61669921875003, 38.852880859375034], + [117.5578125000001, 38.625146484374994], + [117.76669921875012, 38.311669921874994], + [118.01494140625007, 38.18339843749996], + [118.94003906250006, 38.04277343750002], + [119.08916015625007, 37.70073242187496], + [118.95263671875003, 37.33115234374998], + [119.28740234375002, 37.138281250000034], + [119.76054687500007, 37.15507812499999], + [120.31152343750003, 37.62270507812505], + [120.2572265625, 37.67900390624996], + [120.75, 37.83393554687501], + [121.64023437500012, 37.46035156250002], + [122.05664062500003, 37.528906250000034], + [122.66699218750003, 37.40283203125003], + [122.4466796875, 37.06811523437503], + [122.51972656250004, 36.94682617187502], + [122.34091796875012, 36.83222656250004], + [121.93271484375006, 36.95947265625003], + [121.05380859375006, 36.61137695312499], + [120.81083984375007, 36.6328125], + [120.89580078125007, 36.44414062500002], + [120.71152343750006, 36.41328125000004], + [120.6378906250001, 36.129931640625045], + [120.39306640625003, 36.053857421874994], + [120.32773437500006, 36.228173828124994], + [120.18330078125004, 36.20244140624999], + [120.094140625, 36.11889648437503], + [120.28476562500006, 35.98442382812499], + [119.42968749999997, 35.301416015624994], + [119.16533203125002, 34.84882812499998], + [119.20097656250002, 34.748437499999966], + [120.26669921875006, 34.274023437500034], + [120.87109374999997, 33.016503906249994], + [120.8532226562501, 32.66137695312503], + [121.34169921875005, 32.42504882812503], + [121.40390625000006, 32.20625], + [121.85634765625, 31.816455078125045], + [121.86630859375006, 31.703564453124955], + [121.68085937500004, 31.71215820312503], + [121.351953125, 31.85878906250005], + [120.97353515625, 31.86938476562497], + [120.52011718750006, 32.10585937500002], + [120.03593750000002, 31.93627929687503], + [120.7155273437501, 31.983740234375006], + [120.7877929687501, 31.81977539062501], + [121.66064453124997, 31.319726562499994], + [121.87792968750003, 30.91699218750003], + [121.41894531249997, 30.789794921875057], + [120.8214843750001, 30.354638671875023], + [120.44980468750006, 30.38784179687505], + [120.19462890625002, 30.241308593750034], + [120.49453125, 30.303076171875006], + [120.63339843750006, 30.133154296875034], + [121.25800781250004, 30.30410156250005], + [121.67792968750004, 29.979101562500006], + [122.08291015625005, 29.870361328125057], + [121.50625, 29.484570312499955], + [121.94121093750002, 29.605908203124983], + [121.91777343750007, 29.13500976562497], + [121.71748046875004, 29.25634765625], + [121.48710937500007, 29.193164062500017], + [121.67968749999997, 28.953125], + [121.54003906250003, 28.931884765625], + [121.6625, 28.851416015625034], + [121.47519531250006, 28.64140625], + [121.60996093750006, 28.29213867187505], + [121.27226562500002, 28.222119140624983], + [121.14570312500004, 28.32666015624997], + [120.95859375000006, 28.037011718750023], + [120.74765625000006, 28.00996093750001], + [120.83300781249997, 27.891455078125034], + [120.58750000000012, 27.580761718749983], + [120.60751953125012, 27.41240234374996], + [120.2787109375, 27.097070312500023], + [120.08671875000007, 26.67158203125004], + [119.88222656250005, 26.610449218750006], + [119.82421874999997, 26.84638671875001], + [119.71044921874997, 26.728662109375023], + [119.58818359375002, 26.784960937500045], + [119.8810546875001, 26.33417968750004], + [119.46308593750004, 26.05468750000003], + [119.13945312500007, 26.12177734375001], + [119.33203124999997, 25.94873046875003], + [119.61875000000012, 26.003564453124994], + [119.53945312500005, 25.59125976562504], + [119.6224609375, 25.391162109375017], + [119.180078125, 25.449804687499977], + [119.285546875, 25.232226562500074], + [118.97753906249997, 25.209277343750017], + [118.90908203125005, 24.92890625000001], + [118.63691406250004, 24.835546874999977], + [118.65703125000002, 24.621435546874977], + [118.0871093750001, 24.627001953125045], + [118.00595703125006, 24.48198242187499], + [117.84267578125005, 24.47431640625004], + [118.0560546875, 24.24609374999997], + [117.62822265625002, 23.836718750000074], + [117.46640625000012, 23.84057617187497], + [117.36767578124997, 23.58862304687497], + [117.29082031250007, 23.71435546875], + [117.08251953124997, 23.578759765625023], + [116.91064453124997, 23.646679687499983], + [116.86093750000006, 23.453076171874983], + [116.62939453124997, 23.353857421875034], + [116.69882812500006, 23.277783203124983], + [116.53828125000004, 23.17968749999997], + [116.47070312499997, 22.945898437500034], + [116.25185546875005, 22.981347656249994], + [115.85214843750006, 22.801562500000045], + [115.64042968750002, 22.853417968750023], + [115.49833984375002, 22.718847656250063], + [115.19580078125003, 22.81728515625005], + [114.85380859375007, 22.616796875000063], + [114.65166015625002, 22.755273437500023], + [114.55419921874997, 22.52890625], + [114.26601562500005, 22.540966796874983], + [114.01542968750007, 22.51191406250001], + [113.61962890624997, 22.861425781249977], + [113.6205078125, 23.12749023437499], + [113.51972656250004, 23.102099609375074], + [113.33105468749997, 22.912011718749966], + [113.55302734375002, 22.594042968750045], + [113.54912109375002, 22.225195312500034], + [113.14902343750012, 22.075], + [113.08876953125, 22.207958984374983], + [112.95390625000007, 21.907324218750034], + [112.80859374999997, 21.944628906250074], + [112.58632812500005, 21.77685546875], + [112.35966796875007, 21.97802734375003], + [112.30498046875002, 21.74169921875003], + [111.94394531250012, 21.84965820312499], + [111.60273437500004, 21.55908203125003], + [111.01689453125007, 21.51171874999997], + [110.56718750000002, 21.21406250000001], + [110.41093750000007, 21.33813476562497], + [110.15400390625004, 20.944628906250017], + [110.36542968750004, 20.837597656249955], + [110.31308593750012, 20.67167968749999], + [110.51152343750007, 20.51826171875001], + [110.34472656249997, 20.29482421875005], + [109.88251953125004, 20.364062500000045], + [109.96835937500006, 20.448144531250023], + [109.66259765625003, 20.91689453125005], + [109.68125000000012, 21.13164062499999], + [109.93076171875012, 21.480566406250034], + [109.6869140625, 21.52460937500004], + [109.56640624999997, 21.690576171874994], + [109.54404296875012, 21.537939453125006], + [109.14863281250004, 21.425537109375], + [109.1017578125001, 21.59047851562505], + [108.77167968750004, 21.63046875], + [108.59375, 21.901025390624994], + [108.47988281250005, 21.904638671875006], + [108.50214843750004, 21.633447265624994], + [108.32480468750006, 21.693505859374994], + [108.24628906250004, 21.55839843749999], + [107.97265624999997, 21.507958984375023], + [107.75927734374997, 21.655029296875057], + [107.35117187500012, 21.60888671874997], + [106.97099609375002, 21.923925781250034], + [106.66357421875003, 21.97890625000005], + [106.55039062500006, 22.501367187499994], + [106.78027343749997, 22.778906250000034], + [106.54179687500007, 22.908349609375023], + [106.2790039062501, 22.857470703125045], + [106.14843749999997, 22.970068359375006], + [105.8429687500001, 22.922802734374955], + [105.27539062500003, 23.34521484375003], + [104.86474609375003, 23.136376953125023], + [104.68730468750002, 22.822216796874983], + [104.37177734375004, 22.704052734374983], + [104.14306640624997, 22.800146484375006], + [103.94150390625006, 22.540087890625045], + [103.62021484375006, 22.782031250000045], + [103.49296875000007, 22.587988281250034], + [103.32666015625003, 22.769775390625057], + [102.98193359374997, 22.4482421875], + [102.47089843750004, 22.75092773437501], + [102.40644531250004, 22.70800781249997], + [102.2370117187501, 22.466015624999983], + [102.1759765625001, 22.414648437500006], + [102.12744140624997, 22.379199218750045], + [101.84179687500003, 22.38847656249999], + [101.75996093750004, 22.490332031250034], + [101.73876953124997, 22.495263671874994], + [101.70751953125003, 22.486572265625], + [101.67148437500006, 22.462304687500023], + [101.64619140625004, 22.405419921874966], + [101.61992187500002, 22.32744140624999], + [101.56787109374997, 22.27636718749997], + [101.52451171875006, 22.25366210937497], + [101.7365234375001, 21.826513671874977], + [101.74394531250007, 21.77797851562505], + [101.74726562500004, 21.605761718750045], + [101.72294921875007, 21.31494140625003], + [101.80058593750007, 21.212597656249983], + [101.78349609375007, 21.204150390625017], + [101.728125, 21.156396484374994], + [101.7047851562501, 21.15014648437503], + [101.54238281250005, 21.23427734375005], + [101.2814453125001, 21.184130859375045], + [101.24785156250007, 21.197314453125045], + [101.22441406250002, 21.223730468750034], + [101.21181640625, 21.278222656250023], + [101.2199218750001, 21.34243164062505], + [101.17539062500006, 21.407519531250074], + [101.19667968750005, 21.522070312500063], + [101.1388671875001, 21.567480468749977], + [101.07978515625004, 21.75585937499997], + [100.60458984375012, 21.471777343750006], + [100.14765625000004, 21.480517578125017], + [99.94072265625007, 21.75874023437504], + [99.9176757812501, 22.02802734375001], + [99.19296875000006, 22.12597656249997], + [99.50712890625002, 22.959130859374994], + [99.41806640625006, 23.069238281250023], + [98.86376953125003, 23.191259765625034], + [98.8322265625001, 23.624365234374977], + [98.67675781250003, 23.905078125000045], + [98.83505859375006, 24.121191406250034], + [98.2125, 24.110644531250017], + [97.56455078125012, 23.911035156250023], + [97.7082031250001, 24.228759765625], + [97.53144531250004, 24.49169921875003], + [97.58330078125002, 24.77480468750005], + [97.73789062500006, 24.869873046875057], + [97.8195312500001, 25.251855468749994], + [98.01074218749997, 25.292529296875017], + [98.14287109375007, 25.571093750000017], + [98.33378906250007, 25.586767578125006], + [98.65625, 25.86357421874999], + [98.56406250000006, 26.072412109374994], + [98.68554687499997, 26.189355468750023], + [98.7384765625001, 26.785742187500006], + [98.65117187500007, 27.572460937499983], + [98.4525390625, 27.6572265625], + [98.29882812499997, 27.550097656250045], + [98.06162109375012, 28.185888671874977], + [97.59921875000006, 28.51704101562504], + [97.53789062500002, 28.510205078124983], + [97.43144531250002, 28.353906250000023], + [97.35644531249997, 28.254492187500006], + [97.32158929493812, 28.217097107438057], + [97.3027336276825, 28.08710519614969], + [97.34382779482424, 27.982305259167095], + [97.04929369561631, 27.76000444316393], + [96.96494598325154, 27.699301564540924], + [96.19423412199573, 28.04146177926422], + [95.73730002295082, 28.117613231051525], + [95.11298892962586, 27.748338353239472], + [94.07167814294401, 27.588707868507477], + [93.61247595136224, 27.323800298697016], + [93.30681393470121, 26.786120363519142], + [92.74319481218781, 26.833531317384058], + [92.04974640832253, 26.874866505386724], + [92.07342257335648, 26.915311275859864], + [92.06813426293174, 26.9752569185349], + [92.02985139563152, 27.03987087331446], + [91.99856592104459, 27.079255842602592], + [91.99177981607339, 27.100605151743654], + [92.0025114452454, 27.147290053160265], + [92.03101585307499, 27.214271359861193], + [92.08387457645458, 27.29090135496722], + [92.04520857607581, 27.364442429033787], + [91.99069061380867, 27.450181624174498], + [91.95099838734396, 27.45828799115413], + [91.85276579410389, 27.438593286730903], + [91.74366351462741, 27.442853010105477], + [91.59505352446729, 27.557262710287986], + [91.63193359375012, 27.759960937499983], + [91.64189453125002, 27.923242187500023], + [91.36259958579089, 28.02438066407592], + [91.27304687500012, 28.078369140625], + [91.22587890625007, 28.071240234374983], + [91.07773437500012, 27.974462890624977], + [91.02080078125002, 27.970068359374977], + [90.71572265625, 28.071728515624983], + [90.63007812500004, 28.078564453124955], + [90.47734375000007, 28.07084960937499], + [90.3527343750001, 28.080224609375023], + [90.33310546875012, 28.093994140625], + [90.36298828125004, 28.21650390625001], + [90.34824218750006, 28.24394531249999], + [90.22080078125006, 28.27773437500005], + [90.10449218749997, 28.302050781250017], + [89.98105468750006, 28.311181640625023], + [89.8978515625, 28.29414062500001], + [89.81689453125003, 28.25629882812501], + [89.74980468750002, 28.18818359375001], + [89.65273437500005, 28.158300781250034], + [89.53691406250007, 28.10742187499997], + [89.4806640625001, 28.059960937499994], + [88.89140625000002, 27.316064453124966], + [88.83251953125003, 27.36284179687499], + [88.7648437500001, 27.429882812499983], + [88.74902343749997, 27.521875], + [88.82988281250002, 27.76738281249999], + [88.84882812500004, 27.86865234375], + [88.80371093750003, 28.006933593750034], + [88.57792968750002, 28.093359375000034], + [88.42597656250004, 28.01166992187501], + [88.27519531250007, 27.968847656250006], + [88.14111328125003, 27.94892578125001], + [88.10898437500006, 27.933007812499966], + [88.10976562500005, 27.870605468750057], + [87.8607421875, 27.886083984375006], + [87.62255859374997, 27.81518554687503], + [87.29072265625004, 27.821923828124994], + [87.14140625000002, 27.838330078124955], + [87.02011718750006, 27.928662109374983], + [86.9337890625001, 27.96845703125001], + [86.84238281250012, 27.99916992187505], + [86.750390625, 28.022070312500006], + [86.71962890625005, 28.070654296875034], + [86.69052734375006, 28.09492187500001], + [86.61445312500004, 28.10302734374997], + [86.55449218750007, 28.08520507812497], + [86.51689453125007, 27.963525390624966], + [86.40869140625003, 27.928662109374983], + [86.32861328124997, 27.95952148437496], + [86.2179687500001, 28.022070312500006], + [86.13701171875002, 28.114355468750063], + [86.07871093750006, 28.08359375], + [86.0641601562501, 27.934716796874966], + [85.99453125000005, 27.910400390625], + [85.95410156249997, 27.92822265624997], + [85.92167968750002, 27.989697265624983], + [85.84023437500005, 28.135351562499977], + [85.75947265625004, 28.220654296874955], + [85.67832031250012, 28.277441406249977], + [85.41064453125003, 28.27602539062505], + [85.21210937500004, 28.292626953124966], + [85.1224609375, 28.315966796875017], + [85.08857421875004, 28.37226562500001], + [85.121484375, 28.484277343750023], + [85.16015624999997, 28.571875], + [85.15908203125, 28.592236328124983], + [85.1263671875, 28.602636718750063], + [85.06914062500007, 28.60966796874999], + [84.85507812500006, 28.553613281250023], + [84.796875, 28.560205078125023], + [84.2287109375001, 28.911767578124966], + [84.17558593750002, 29.036376953125057], + [84.12783203125005, 29.15629882812496], + [84.10136718750002, 29.21997070312497], + [84.02197265624997, 29.25385742187504], + [83.93593750000005, 29.27949218750001], + [83.58349609375003, 29.18359375000003], + [83.15546875000004, 29.612646484375034], + [82.22070312500003, 30.063867187500023], + [82.04335937500005, 30.326757812500034], + [81.8548828125, 30.362402343750006], + [81.64189453125007, 30.3875], + [81.4171875000001, 30.33759765625001], + [81.25507812500004, 30.09331054687499], + [81.17714843750005, 30.039892578125034], + [80.98544921875006, 30.23710937499999], + [80.87353515625003, 30.290576171875045], + [80.19121093750002, 30.56840820312496], + [80.20712890625006, 30.683740234375023], + [79.92451171875004, 30.888769531250034], + [79.66425781250004, 30.96523437499999], + [79.38847656250007, 31.064208984375], + [79.10712890625004, 31.402636718750017], + [78.74355468750005, 31.323779296875017], + [78.7550781250001, 31.55029296875], + [78.69345703125006, 31.740380859374994], + [78.72558593750003, 31.983789062500023], + [78.49589843750002, 32.21577148437504], + [78.4552734375001, 32.30034179687502], + [78.41748046874997, 32.466699218749994], + [78.38964843749997, 32.51987304687498], + [78.73671875, 32.55839843750002], + [78.75351562500012, 32.49926757812506], + [78.91894531249997, 32.35820312500002], + [79.16992187500003, 32.497216796874994], + [79.14550781250003, 33.00146484375006], + [79.10283203125007, 33.05253906249996], + [79.13515625000005, 33.17192382812496], + [79.1125, 33.22626953125001], + [78.94843750000004, 33.346533203125006], + [78.86503906250002, 33.43110351562501], + [78.78378906250006, 33.80878906250004], + [78.72666015625006, 34.013378906249955], + [78.97060546875, 34.22822265625004], + [78.93642578125, 34.35195312500002], + [78.86484375000006, 34.39033203125001], + [78.32695312500007, 34.60639648437498], + [78.15849609375002, 34.94648437499998], + [78.07578125000006, 35.13491210937502], + [78.0426757812501, 35.47978515625002], + [77.79941406250006, 35.49589843750002], + [77.44648437500004, 35.47558593750006], + [77.29482421875005, 35.508154296875034], + [77.09003906250004, 35.55205078124999], + [76.87890625000003, 35.61328125000003], + [76.76689453125002, 35.661718750000034], + [76.72753906250003, 35.67866210937504], + [76.63183593749997, 35.729394531249966], + [76.56347656249997, 35.77299804687499], + [76.55126953124997, 35.887060546875034], + [76.50205078125006, 35.87822265625002], + [76.38574218750003, 35.837158203125], + [76.25166015625004, 35.8109375], + [76.17783203125012, 35.810546875], + [76.14785156250005, 35.82900390625002], + [76.07089843750006, 35.983007812500034], + [75.91230468750004, 36.048974609374994], + [75.97441406250007, 36.38242187500006], + [75.9518554687501, 36.458105468750034], + [75.9330078125, 36.52158203124998], + [75.840234375, 36.64970703124999], + [75.7721679687501, 36.694921875000034], + [75.6671875000001, 36.741992187500045], + [75.57373046874997, 36.75932617187502], + [75.46025390625002, 36.725048828124955], + [75.42421875000005, 36.73823242187498], + [75.37685546875, 36.88369140625005], + [75.34667968749997, 36.913476562499966], + [75.05390625000004, 36.98715820312498], + [74.94912109375, 36.96835937500006], + [74.88925781250006, 36.95244140625002], + [74.69218750000007, 37.035742187500006], + [74.60058593749997, 37.03666992187502], + [74.54140625, 37.02216796875001], + [74.52646484375006, 37.03066406250005], + [74.49794921875, 37.057226562500034], + [74.37617187500004, 37.13735351562502], + [74.37216796875006, 37.15771484375], + [74.558984375, 37.23662109374999], + [74.66894531250003, 37.266699218750006], + [74.72666015625006, 37.29072265625001], + [74.7389648437501, 37.28564453125003], + [74.76738281250002, 37.249169921874966], + [74.840234375, 37.22504882812504], + [74.89130859375004, 37.231640624999955], + [75.11875, 37.38569335937498], + [74.8942382812501, 37.60141601562498], + [74.81230468750002, 38.46030273437498], + [74.27744140625, 38.659765625000034], + [74.02558593750004, 38.53984375000002], + [73.80166015625, 38.60688476562501], + [73.69609375000007, 38.85429687499996], + [73.8052734375, 38.968652343749994], + [73.60732421875, 39.229199218749955], + [73.63632812500006, 39.396679687499955], + [73.63164062500007, 39.44887695312502], + [73.82294921875004, 39.48896484375004], + [73.90712890625, 39.578515624999966], + [73.9146484375, 39.60649414062499], + [73.88251953125004, 39.71455078124998], + [73.83974609375005, 39.76284179687505], + [73.8353515625, 39.800146484375006], + [73.85625, 39.828662109375045], + [73.88457031250002, 39.87792968750006], + [73.93876953125002, 39.97880859374999], + [73.99160156250005, 40.04311523437502], + [74.83046875, 40.32851562499999], + [74.80126953124997, 40.428515625000045], + [74.83515625000004, 40.482617187499955], + [74.865625, 40.493505859375034], + [75.0044921875, 40.44951171874996], + [75.11132812499997, 40.4541015625], + [75.24101562500002, 40.48027343750002], + [75.52080078125002, 40.627539062500006], + [75.55556640625, 40.625195312499955], + [75.6771484375, 40.305810546874994], + [75.87197265625, 40.30322265625], + [76.25830078124997, 40.43076171875006], + [76.3185546875001, 40.352246093749955], + [76.39638671875005, 40.389794921874966], + [76.4801757812501, 40.44951171874996], + [76.57792968750002, 40.577880859375], + [76.62216796875006, 40.66235351562497], + [76.6398437500001, 40.74223632812499], + [76.66113281249997, 40.77963867187498], + [76.70839843750005, 40.818115234375], + [76.82402343750002, 40.982324218749966], + [76.90771484374997, 41.02416992187497], + [76.98662109375002, 41.039160156250006], + [77.58173828125004, 40.99277343750006], + [77.71933593750012, 41.024316406249994], + [77.81523437500002, 41.05561523437498], + [77.9564453125, 41.05068359375005], + [78.1234375, 41.07563476562498], + [78.34628906250012, 41.28144531249998], + [78.36240234375012, 41.37163085937496], + [78.44287109374997, 41.41752929687499], + [78.742578125, 41.56005859375], + [79.29355468750006, 41.78281249999998], + [79.76611328124997, 41.89887695312501], + [79.84042968750012, 41.99575195312502], + [79.90966796875003, 42.014990234375034], + [80.21621093750005, 42.03242187500004], + [80.23515625000007, 42.04345703124997], + [80.24619140625012, 42.05981445312503], + [80.209375, 42.190039062500006], + [80.20224609375012, 42.73447265624998], + [80.53896484375005, 42.873486328124955], + [80.39023437500006, 43.043115234374966], + [80.78574218750006, 43.16157226562504], + [80.35527343750002, 44.09726562500006], + [80.48154296875006, 44.71464843749999], + [79.871875, 44.88378906249997], + [80.05917968750012, 45.006445312500006], + [81.69199218750012, 45.34936523437497], + [81.94492187500006, 45.16083984375001], + [82.26660156249997, 45.21909179687498], + [82.52148437500003, 45.12548828125], + [82.61162109375007, 45.424267578124955], + [82.31523437500002, 45.59492187499998], + [83.02949218750004, 47.18593750000002], + [84.016015625, 46.97050781250002], + [84.66660156250006, 46.97236328125004], + [84.78613281249997, 46.83071289062505], + [85.484765625, 47.06352539062496], + [85.65664062500005, 47.254638671875], + [85.52597656250006, 47.915625], + [85.7494140625, 48.38505859374999], + [86.54941406250012, 48.52861328125002], + [86.8083007812501, 49.04970703125002], + [87.32285156250012, 49.085791015625006], + [87.41669921875004, 49.07661132812501], + [87.5158203125001, 49.122412109375006], + [87.7625, 49.16582031249996], + [87.81425781250002, 49.162304687499955], + [87.87216796875012, 49.000146484374966], + [87.74316406250003, 48.88164062499999], + [87.83183593750007, 48.79165039062505], + [88.02792968750006, 48.735595703125], + [88.06005859375003, 48.707177734374966], + [87.9796875000001, 48.55512695312498], + [88.30996093750005, 48.47207031250002], + [88.41396484375, 48.403417968750006], + [88.51708984374997, 48.384472656249955], + [88.56679687500005, 48.31743164062496], + [88.57597656250007, 48.220166015624955], + [88.68183593750004, 48.170556640624994], + [88.83828125000005, 48.101708984374994], + [88.91777343750007, 48.089013671874966], + [89.04765625000007, 48.002539062500034], + [89.47919921875004, 48.02905273437503], + [89.5609375, 48.00395507812496], + [89.778125, 47.82700195312498], + [89.83134765625002, 47.82329101562502], + [89.91044921875007, 47.844335937500034], + [89.95869140625004, 47.88632812499998], + [90.02792968750012, 47.877685546875], + [90.1032226562501, 47.74541015624996], + [90.19101562500012, 47.70209960937501], + [90.31328125000007, 47.67617187499999], + [90.33066406250006, 47.655175781249966], + [90.42519531250005, 47.50410156250001], + [90.49619140625012, 47.28515625], + [90.64335937500007, 47.10029296874998], + [90.71552734375004, 47.00385742187498], + [90.7990234375001, 46.98515624999999], + [90.86992187500002, 46.95449218750005], + [90.91054687500005, 46.88325195312501], + [90.9857421875, 46.7490234375], + [90.9115234375, 46.270654296874994], + [90.94755859375002, 46.17729492187499], + [90.99677734375004, 46.10498046875], + [91.00175781250007, 46.03579101562502], + [90.6618164062501, 45.525244140625006], + [90.87724609375002, 45.19609375000002], + [91.05, 45.217431640624994], + [91.584375, 45.07651367187498], + [92.42382812499997, 45.008935546874994], + [92.57890625000002, 45.01098632812506], + [92.78789062500007, 45.035742187500034], + [93.51621093750012, 44.944482421874994], + [94.71201171875012, 44.35083007812503], + [95.35029296875004, 44.27807617187503], + [95.32558593750005, 44.03935546874999], + [95.52558593750004, 43.953955078125006], + [95.85957031250004, 43.27597656249998], + [96.38544921875004, 42.72036132812502], + [97.20566406250012, 42.78979492187506], + [99.46787109375012, 42.568212890625034], + [99.98378906250005, 42.67734375000006], + [100.08632812500005, 42.67075195312506], + [100.51904296875003, 42.61679687499998], + [101.09199218750004, 42.55131835937496], + [101.49531250000004, 42.53876953124998], + [101.57910156249997, 42.52353515624998], + [101.65996093750002, 42.50004882812499], + [101.97294921875002, 42.21586914062502], + [102.15664062500005, 42.158105468749966], + [102.57519531249997, 42.09208984375002], + [103.07285156250006, 42.00595703125006], + [103.7111328125001, 41.75131835937506], + [103.99726562500004, 41.796972656250034], + [104.30517578124997, 41.84614257812501], + [104.49824218750004, 41.87700195312499], + [104.49824218750004, 41.65869140625], + [104.86035156250003, 41.64375], + [104.98203125000012, 41.59550781250002], + [105.05058593750002, 41.61591796875001], + [105.1154296875001, 41.66328124999998], + [105.19707031250002, 41.738037109375], + [105.31435546875005, 41.77089843750005], + [105.86757812500005, 41.993994140625034], + [106.77001953125003, 42.28872070312502], + [108.17119140625002, 42.44731445312502], + [108.68730468750002, 42.416113281250034], + [109.33984374999997, 42.43837890625005], + [109.44316406250002, 42.455957031249994], + [110.40039062499997, 42.77368164062497], + [111.00722656250005, 43.34140624999998], + [111.878125, 43.68017578125], + [111.93173828125012, 43.81494140625], + [111.40224609375005, 44.367285156250006], + [111.89804687500006, 45.064062500000034], + [112.03261718750005, 45.08164062500006], + [112.11289062500006, 45.06293945312498], + [112.41132812500004, 45.05820312499998], + [112.49931640625002, 45.01093750000004], + [112.59677734375006, 44.917675781249955], + [112.7067382812501, 44.883447265624994], + [113.04941406250006, 44.81035156250002], + [113.3009765625001, 44.79165039062502], + [113.50791015625006, 44.76235351562502], + [113.58701171875006, 44.745703125], + [113.65263671875002, 44.76347656249999], + [113.87705078125012, 44.89619140625001], + [114.03027343749997, 44.942578124999955], + [114.08027343750004, 44.97114257812501], + [114.41914062500004, 45.20258789062501], + [114.56015625000012, 45.38999023437498], + [114.73876953124997, 45.41962890624998], + [114.91923828125007, 45.378271484375006], + [115.16259765624997, 45.390234375000034], + [115.6810546875, 45.45825195312503], + [116.19765625, 45.739355468750006], + [116.240625, 45.795996093750006], + [116.22910156250012, 45.84575195312502], + [116.21298828125012, 45.88691406249998], + [116.56259765625012, 46.28979492187497], + [116.85908203125004, 46.387939453125], + [117.3333984375, 46.36201171875004], + [117.35693359375003, 46.391308593749955], + [117.35634765625, 46.436669921874966], + [117.39218750000012, 46.53759765625003], + [117.40556640625007, 46.57089843750006], + [117.43808593750012, 46.58623046874999], + [117.546875, 46.58828125000005], + [117.74121093749997, 46.51816406250006], + [118.07128906249997, 46.666601562500006], + [118.15683593750006, 46.678564453125034], + [118.30869140625012, 46.71704101562497], + [118.40439453125006, 46.70317382812499], + [118.58046875, 46.69189453125], + [118.64873046875002, 46.70166015625006], + [118.72294921875007, 46.69189453125], + [118.8439453125001, 46.76020507812498], + [118.95712890625006, 46.73486328124997], + [119.16210937499997, 46.638671875], + [119.33183593750002, 46.61381835937499], + [119.47402343750005, 46.626660156249955], + [119.62021484375006, 46.60395507812504], + [119.70664062500006, 46.60600585937502], + [119.74746093750005, 46.62719726562497], + [119.86718750000003, 46.67216796874999], + [119.89785156250005, 46.857812499999966], + [119.71113281250004, 47.15], + [119.08193359375, 47.654150390625034], + [119.01757812500003, 47.68535156249999], + [118.88027343750005, 47.72509765625], + [118.75996093750004, 47.75761718749996], + [118.69052734375012, 47.822265625], + [118.56777343750005, 47.94326171875005], + [118.49843750000005, 47.98398437499998], + [117.76835937500002, 47.98789062499998], + [117.3507812500001, 47.65219726562498], + [117.28593750000002, 47.666357421875034], + [117.06972656250005, 47.80639648437506], + [116.95166015624997, 47.836572265624966], + [116.90117187500007, 47.85307617187496], + [116.76054687500002, 47.869775390624994], + [116.65195312500012, 47.86450195312497], + [116.51347656250007, 47.839550781249955], + [116.37822265625002, 47.84404296874999], + [116.31718750000002, 47.85986328125], + [116.2311523437501, 47.85820312500002], + [116.07480468750012, 47.78955078125], + [115.99384765625004, 47.71132812500005], + [115.89824218750002, 47.68691406250005], + [115.6164062500001, 47.874804687500045], + [115.52509765625004, 48.13085937499997], + [115.63945312500007, 48.18623046874998], + [115.785546875, 48.24824218750001], + [115.7965820312501, 48.346337890624994], + [115.7916992187501, 48.455712890624994], + [115.8205078125001, 48.57724609375006], + [116.6833007812501, 49.82377929687499], + [117.8734375, 49.51347656250002], + [118.4515625, 49.84448242187503], + [119.25986328125012, 50.06640625000003], + [119.34628906250012, 50.278955078124994], + [119.16367187500006, 50.40600585937503], + [120.06689453125003, 51.60068359375006], + [120.74980468750007, 52.096533203125006], + [120.65615234375, 52.56665039062503], + [120.0675781250001, 52.632910156250034], + [120.09453125000007, 52.787207031250034], + [120.98544921875012, 53.28457031250002], + [123.6078125, 53.546533203124994], + [124.81230468750002, 53.133837890625045], + [125.075, 53.20366210937496], + [125.64902343750012, 53.042285156250045], + [126.34169921875, 52.36201171875001], + [126.92480468749997, 51.10014648437496], + [127.30703125000005, 50.70795898437501], + [127.33720703125007, 50.35014648437502], + [127.590234375, 50.20898437500003], + [127.55078124999997, 49.801806640625045], + [127.99960937500006, 49.56860351562506], + [128.70400390625, 49.60014648437499], + [129.0651367187501, 49.374658203124966], + [129.49814453125012, 49.38881835937502], + [130.1959960937501, 48.89165039062499], + [130.553125, 48.861181640625006], + [130.5521484375, 48.602490234374955], + [130.80429687500012, 48.34150390624998], + [130.7326171875001, 48.01923828124998], + [130.96191406249997, 47.70932617187498], + [132.47626953125004, 47.714990234374994], + [132.7072265625001, 47.94726562500006], + [133.14404296875003, 48.10566406249998], + [133.46835937500006, 48.09716796875003], + [134.29335937500005, 48.37343750000002], + [134.66523437500004, 48.25390625], + [134.56601562500006, 48.02250976562502], + [134.75234375, 47.71542968749998], + [134.1676757812501, 47.30219726562501], + [133.86132812500003, 46.24775390625004], + [133.43642578125, 45.60468750000004], + [133.18603515625003, 45.49482421875004], + [133.1134765625001, 45.130712890625006], + [132.93603515624997, 45.029931640624994], + [131.85185546875002, 45.32685546875001], + [131.44687500000012, 44.984033203124966], + [130.9816406250001, 44.844335937500034], + [131.2552734375, 44.07158203124999], + [131.25732421875003, 43.378076171874994], + [131.06855468750004, 42.90224609375005], + [130.42480468749997, 42.72705078124997], + [130.52695312500012, 42.535400390625] + ] + ], + [ + [ + [113.9977539062501, 22.210498046875045], + [113.83886718749997, 22.24169921875003], + [114.04394531250003, 22.33339843750005], + [113.9977539062501, 22.210498046875045] + ] + ], + [ + [ + [114.01542968750007, 22.51191406250001], + [114.26601562500005, 22.540966796874983], + [114.26796875, 22.295556640624966], + [113.93730468750002, 22.364990234375], + [114.01542968750007, 22.51191406250001] + ] + ], + [], + [ + [ + [118.4074218750001, 24.522119140624994], + [118.43271484375006, 24.414355468750074], + [118.29511718750004, 24.436328125000017], + [118.4074218750001, 24.522119140624994] + ] + ], + [ + [ + [121.00878906249997, 22.62036132812497], + [120.83984375000003, 21.925], + [120.2328125, 22.71791992187505], + [120.0724609375001, 23.149755859375006], + [120.13212890625007, 23.652929687500034], + [121.040625, 25.032812500000034], + [121.59365234375, 25.275341796874983], + [121.92900390625002, 24.973730468749977], + [121.39746093750003, 23.172509765625023], + [121.00878906249997, 22.62036132812497] + ] + ] + ] + }, + "properties": { "name": "China", "childNum": 15 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [-5.262304687499977, 10.319677734374991], + [-4.72177734374992, 9.756542968750026], + [-4.625830078125006, 9.713574218749969], + [-4.526611328124943, 9.723486328125034], + [-4.406201171874926, 9.647998046875031], + [-4.332226562499955, 9.645703125], + [-4.18115234375, 9.78173828125], + [-3.790625, 9.917187499999983], + [-3.581152343749977, 9.924316406250014], + [-3.289697265625023, 9.882226562500051], + [-3.223535156249937, 9.895458984374997], + [-3.160693359374932, 9.849169921874974], + [-3.095800781249949, 9.752099609375009], + [-3.042626953124937, 9.72089843750004], + [-2.988281249999972, 9.687353515624963], + [-2.900878906249943, 9.534619140625026], + [-2.875146484374937, 9.500927734374997], + [-2.816748046874949, 9.425830078124974], + [-2.766601562499943, 9.424707031250009], + [-2.7171875, 9.457128906250048], + [-2.695849609374989, 9.481347656250009], + [-2.686132812499977, 9.43173828125002], + [-2.705761718749983, 9.351367187499989], + [-2.74692382812492, 9.04511718750004], + [-2.689892578124955, 9.02509765625004], + [-2.649218750000017, 8.956591796875031], + [-2.600390625000017, 8.800439453125023], + [-2.505859375000028, 8.208740234375], + [-2.538281249999955, 8.171630859374986], + [-2.61171875, 8.147558593749963], + [-2.619970703125006, 8.12109375], + [-2.600976562499937, 8.082226562499983], + [-2.613378906249977, 8.046679687500017], + [-2.668847656249994, 8.022216796875014], + [-2.789746093749955, 7.931933593750003], + [-2.959082031249977, 7.454541015624997], + [-3.227148437499977, 6.749121093749991], + [-2.998291015624972, 5.711328125000051], + [-2.793652343749955, 5.600097656250028], + [-2.754980468749977, 5.432519531249994], + [-2.815673828125, 5.153027343749997], + [-3.168701171874972, 5.203027343749966], + [-3.199951171874943, 5.3544921875], + [-3.347558593749994, 5.13066406249996], + [-4.120166015625017, 5.309716796875023], + [-4.60888671875, 5.235888671875003], + [-4.037207031249977, 5.23012695312498], + [-4.899707031249932, 5.138330078125023], + [-5.282373046874994, 5.210253906250017], + [-5.36752929687492, 5.15078125], + [-5.061816406249989, 5.13066406249996], + [-5.913769531249926, 5.0109375], + [-7.544970703124989, 4.351318359375], + [-7.574658203124983, 4.572314453124989], + [-7.585058593749977, 4.916748046875], + [-7.39990234375, 5.550585937499989], + [-7.454394531249989, 5.841308593749972], + [-7.636132812499994, 5.90771484375], + [-7.730371093749994, 5.919042968749991], + [-7.800927734374994, 6.038916015624991], + [-7.833251953125, 6.076367187499983], + [-7.855517578125017, 6.150146484375], + [-7.888623046875011, 6.234863281250028], + [-7.981591796874937, 6.2861328125], + [-8.287109375, 6.31904296875004], + [-8.587890625, 6.490527343749989], + [-8.324511718749989, 6.920019531249991], + [-8.408740234374989, 7.411816406249997], + [-8.429980468749989, 7.601855468749989], + [-8.351757812499926, 7.590576171875], + [-8.231884765624955, 7.556738281250034], + [-8.205957031249994, 7.590234375000023], + [-8.115429687499926, 7.760742187500028], + [-8.126855468749937, 7.867724609374974], + [-8.00986328124992, 8.078515625000023], + [-8.048583984375, 8.169726562500045], + [-8.140625, 8.181445312500031], + [-8.217138671874949, 8.219677734375011], + [-8.256103515625, 8.253710937500017], + [-8.244140624999943, 8.407910156249983], + [-8.236962890624994, 8.455664062500034], + [-7.953125, 8.477734375], + [-7.823583984374977, 8.467675781249994], + [-7.738964843749983, 8.375244140624986], + [-7.696093749999932, 8.375585937499977], + [-7.71958007812492, 8.643017578125011], + [-7.950976562499989, 8.786816406249997], + [-7.938183593749983, 8.97978515624996], + [-7.902099609375, 9.017089843750014], + [-7.777978515624937, 9.080859375000031], + [-7.799804687499943, 9.115039062499989], + [-7.839404296875017, 9.151611328124972], + [-7.918066406249949, 9.188525390625031], + [-7.896191406249955, 9.415869140624991], + [-8.136962890624972, 9.49570312499999], + [-8.155175781249937, 9.973193359375017], + [-7.990625, 10.1625], + [-7.661132812500028, 10.427441406250011], + [-7.385058593749989, 10.340136718749989], + [-7.01708984375, 10.143261718750026], + [-6.950341796874994, 10.342333984374989], + [-6.693261718750023, 10.34946289062502], + [-6.669335937499937, 10.39218750000002], + [-6.69199218749992, 10.512011718750017], + [-6.686132812499977, 10.578027343750051], + [-6.676367187499949, 10.633789062500043], + [-6.654150390624949, 10.65644531250004], + [-6.482617187499983, 10.561230468749997], + [-6.250244140625, 10.717919921875037], + [-6.190673828124943, 10.400292968749994], + [-6.192626953124972, 10.369433593750003], + [-6.241308593749949, 10.279199218750009], + [-6.238378906249977, 10.26162109374998], + [-6.117187499999972, 10.201904296874986], + [-6.034570312499937, 10.194824218750057], + [-5.907568359375006, 10.307226562500034], + [-5.896191406249983, 10.354736328125028], + [-5.843847656249977, 10.389550781250023], + [-5.694287109374983, 10.433203125000034], + [-5.556591796874983, 10.439941406249986], + [-5.382275390625011, 10.314013671875003], + [-5.262304687499977, 10.319677734374991] + ] + ] + }, + "properties": { "name": "Côte d'Ivoire", "childNum": 1 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [15.480078125, 7.523779296874991], + [15.206738281250011, 7.206152343749991], + [14.7392578125, 6.27978515625], + [14.43115234375, 6.038720703124994], + [14.616894531250011, 5.865136718749994], + [14.56298828125, 5.279931640624994], + [14.73125, 4.602392578124991], + [15.063574218750006, 4.284863281249997], + [15.128710937500017, 3.826904296875], + [16.0634765625, 2.90859375], + [16.183398437500017, 2.270068359374989], + [16.059375, 1.676220703124997], + [15.741601562500023, 1.914990234374997], + [14.902441406250006, 2.012304687499991], + [14.578906250000017, 2.199121093749994], + [13.293554687500006, 2.161572265624997], + [13.2203125, 2.256445312499991], + [11.558984375000023, 2.302197265624997], + [11.348437500000017, 2.299707031249994], + [11.328710937500006, 2.167431640624997], + [11.096582031250023, 2.16748046875], + [10.790917968750023, 2.16757812499999], + [9.979882812500023, 2.167773437499989], + [9.8701171875, 2.21328125], + [9.8369140625, 2.242382812499997], + [9.830371093750017, 2.275488281249991], + [9.826171875, 2.297802734374997], + [9.80078125, 2.304443359375], + [9.82177734375, 2.539257812499997], + [9.948437500000011, 3.079052734374997], + [9.672070312500011, 3.53759765625], + [9.765722656250006, 3.623828124999989], + [9.642382812500017, 3.611767578124997], + [9.55615234375, 3.798046875], + [9.739648437500023, 3.852929687499994], + [9.639941406250017, 3.96533203125], + [9.688867187500023, 4.056396484375], + [9.483691406250017, 4.066113281249997], + [9.42529296875, 3.922314453124997], + [9.000097656250006, 4.091601562499989], + [8.918261718750017, 4.553759765624989], + [8.660351562500011, 4.670996093749991], + [8.65625, 4.516357421875], + [8.53955078125, 4.571875], + [8.715625, 5.046875], + [8.997167968750006, 5.917724609375], + [9.490234375, 6.418652343749997], + [9.779882812500006, 6.76015625], + [9.820703125000023, 6.783935546875], + [9.874218750000011, 6.803271484374989], + [10.038867187500017, 6.92138671875], + [10.1435546875, 6.996435546874991], + [10.167773437500017, 6.959179687499997], + [10.185546875, 6.912792968749997], + [10.205468750000023, 6.8916015625], + [10.293066406250006, 6.876757812499989], + [10.413183593750006, 6.877734374999989], + [10.60625, 7.063085937499991], + [10.954199218750006, 6.7765625], + [11.032519531250017, 6.697900390624994], + [11.1064453125, 6.457714843749997], + [11.1533203125, 6.437939453124997], + [11.2373046875, 6.450537109374991], + [11.401757812500023, 6.533935546875], + [11.551660156250023, 6.697265625], + [11.580078125, 6.888867187499997], + [11.657519531250017, 6.9515625], + [11.861425781250006, 7.11640625], + [11.767382812500017, 7.272265624999989], + [11.809179687500006, 7.345068359374991], + [12.016015625000023, 7.589746093749994], + [12.2333984375, 8.282324218749991], + [12.403515625000011, 8.595556640624991], + [12.582714843750011, 8.624121093749991], + [12.651562500000011, 8.667773437499989], + [12.7822265625, 8.81787109375], + [12.806542968750023, 8.886621093749994], + [12.875683593750011, 9.303515624999989], + [12.929492187500017, 9.42626953125], + [13.19873046875, 9.563769531249989], + [13.269921875000023, 10.036181640624989], + [13.41455078125, 10.171435546874989], + [13.535351562500011, 10.60507812499999], + [13.699902343750011, 10.873144531249991], + [13.89208984375, 11.140087890624997], + [13.9814453125, 11.211865234374997], + [14.056738281250006, 11.245019531249994], + [14.143261718750011, 11.24853515625], + [14.202343750000011, 11.268164062499991], + [14.559765625000011, 11.492285156249991], + [14.619726562500006, 12.150976562499991], + [14.518945312500023, 12.298242187499994], + [14.272851562500023, 12.356494140624989], + [14.184863281250017, 12.447216796874997], + [14.06396484375, 13.07851562499999], + [14.244824218750011, 13.07734375], + [14.461718750000017, 13.021777343749989], + [14.847070312500023, 12.502099609374994], + [15.08125, 11.845507812499989], + [15.029882812500006, 11.11367187499999], + [15.132226562500023, 10.648486328124989], + [15.276074218750011, 10.357373046874997], + [15.654882812500006, 10.0078125], + [14.243261718750006, 9.979736328125], + [13.977246093750011, 9.691552734374994], + [14.332324218750017, 9.20351562499999], + [15.1162109375, 8.557324218749997], + [15.5498046875, 7.787890624999989], + [15.480078125, 7.523779296874991] + ] + ] + }, + "properties": { "name": "Cameroon", "childNum": 1 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [27.4033203125, 5.109179687499989], + [27.7880859375, 4.644677734374994], + [28.19208984375001, 4.350244140624994], + [28.427539062500017, 4.324169921874997], + [28.72705078125, 4.504980468749991], + [29.224902343750017, 4.391894531249989], + [29.469628906250023, 4.61181640625], + [29.676855468750006, 4.5869140625], + [30.194921875, 3.98193359375], + [30.50830078125, 3.835693359375], + [30.586718750000017, 3.62421875], + [30.757226562500023, 3.62421875], + [30.83857421875001, 3.49072265625], + [30.90644531250001, 3.408935546875], + [30.754003906250006, 3.041796874999989], + [30.8466796875, 2.847021484374991], + [30.728613281250006, 2.455371093749989], + [31.176367187500006, 2.270068359374989], + [31.252734375000017, 2.044580078124994], + [29.94287109375, 0.819238281249994], + [29.934472656250023, 0.4990234375], + [29.717675781250023, 0.098339843749997], + [29.576953125000017, -1.387890625000011], + [29.196582031250017, -1.719921875000011], + [29.13154296875001, -2.195117187500003], + [28.876367187500023, -2.400292968750009], + [28.893945312500023, -2.635058593750003], + [29.01435546875001, -2.72021484375], + [29.224414062500017, -3.053515625], + [29.211816406250023, -3.833789062500003], + [29.403222656250023, -4.449316406250006], + [29.404199218750023, -4.496679687500006], + [29.32568359375, -4.835644531250011], + [29.32343750000001, -4.898828125], + [29.3427734375, -4.983105468750011], + [29.542382812500023, -5.499804687500003], + [29.594140625000023, -5.65078125], + [29.60703125, -5.72265625], + [29.59638671875001, -5.775976562500006], + [29.490820312500006, -5.965429687500006], + [29.480078125, -6.025], + [29.50625, -6.172070312500011], + [29.540820312500017, -6.313867187500009], + [29.590625, -6.394433593750009], + [29.70966796875001, -6.616894531250011], + [29.798144531250017, -6.69189453125], + [29.961816406250023, -6.803125], + [30.10625, -6.9150390625], + [30.212695312500017, -7.037890625], + [30.31318359375001, -7.203710937500006], + [30.40673828125, -7.460644531250011], + [30.75117187500001, -8.193652343750003], + [28.89814453125001, -8.485449218750006], + [28.869531250000023, -8.785839843750011], + [28.400683593750017, -9.224804687500011], + [28.60419921875001, -9.678808593750006], + [28.6455078125, -10.550195312500009], + [28.383398437500006, -11.566699218750003], + [28.482519531250006, -11.812109375], + [29.064355468750023, -12.348828125000011], + [29.48554687500001, -12.41845703125], + [29.508203125000023, -12.228222656250011], + [29.79511718750001, -12.155468750000011], + [29.775195312500017, -13.438085937500006], + [29.55419921875, -13.248925781250009], + [29.20185546875001, -13.398339843750009], + [29.014257812500006, -13.368847656250011], + [28.730078125, -12.925488281250011], + [28.550878906250006, -12.836132812500011], + [28.412890625000017, -12.51806640625], + [27.573828125, -12.22705078125], + [27.1591796875, -11.579199218750006], + [26.824023437500017, -11.965234375], + [26.025976562500006, -11.89013671875], + [25.349414062500017, -11.623046875], + [25.28876953125001, -11.21240234375], + [24.3779296875, -11.417089843750006], + [24.36572265625, -11.1298828125], + [23.96650390625001, -10.871777343750011], + [23.901171875000017, -10.983203125], + [23.833886718750023, -11.013671875], + [23.463964843750006, -10.969335937500006], + [23.076269531250006, -11.087890625], + [22.814746093750017, -11.080273437500011], + [22.56103515625, -11.055859375000011], + [22.486132812500017, -11.08671875], + [22.392968750000023, -11.159472656250003], + [22.31494140625, -11.198632812500009], + [22.27880859375, -11.194140625], + [22.226171875, -11.121972656250009], + [22.203515625000023, -10.829492187500009], + [22.307031250000023, -10.691308593750009], + [22.19775390625, -10.040625], + [21.81318359375001, -9.46875], + [21.905371093750006, -8.693359375], + [21.806054687500023, -7.32861328125], + [21.751074218750006, -7.30546875], + [21.190332031250023, -7.284960937500003], + [20.910937500000017, -7.281445312500011], + [20.607812500000023, -7.277734375], + [20.558398437500017, -7.244433593750003], + [20.53583984375001, -7.182812500000011], + [20.536914062500017, -7.121777343750011], + [20.598730468750006, -6.93515625], + [20.59003906250001, -6.919921875], + [20.482226562500017, -6.915820312500003], + [20.190039062500006, -6.9462890625], + [19.997460937500023, -6.976464843750009], + [19.87519531250001, -6.986328125], + [19.527636718750017, -7.144433593750009], + [19.483789062500023, -7.279492187500011], + [19.479882812500023, -7.47216796875], + [19.371679687500006, -7.655078125], + [19.369921875000017, -7.70654296875], + [19.3408203125, -7.966601562500003], + [19.142675781250006, -8.00146484375], + [18.944433593750006, -8.00146484375], + [18.56269531250001, -7.9359375], + [18.0087890625, -8.107617187500011], + [17.643359375000017, -8.090722656250009], + [17.57958984375, -8.099023437500009], + [16.984765625000023, -7.257421875], + [16.91943359375, -6.933984375], + [16.813085937500006, -6.772558593750006], + [16.742968750000017, -6.618457031250003], + [16.697265625, -6.164257812500011], + [16.537109375, -5.9658203125], + [16.431445312500017, -5.900195312500003], + [16.315234375000017, -5.865625], + [13.978515625, -5.857226562500003], + [13.346484375000017, -5.863378906250006], + [13.184375, -5.85625], + [12.452929687500017, -6.00048828125], + [12.213671875000017, -5.758691406250009], + [12.484570312500011, -5.71875], + [12.451464843750017, -5.071484375000011], + [12.502734375000017, -5.036914062500003], + [12.573535156250017, -4.99658203125], + [12.59619140625, -4.978417968750009], + [12.8296875, -4.736621093750003], + [12.947460937500011, -4.6953125], + [13.057324218750011, -4.651074218750011], + [13.07275390625, -4.634765625], + [13.08740234375, -4.601953125], + [13.136621093750023, -4.604296875], + [13.414941406250023, -4.83740234375], + [13.659570312500023, -4.721484375], + [13.717089843750017, -4.454492187500009], + [13.94091796875, -4.484667968750003], + [14.358300781250023, -4.299414062500006], + [14.449804687500006, -4.449511718750003], + [14.365429687500011, -4.585546875], + [14.410742187500006, -4.83125], + [14.707910156250023, -4.881738281250009], + [15.990039062500017, -3.766210937500006], + [16.217382812500006, -3.0302734375], + [16.21533203125, -2.177832031250006], + [16.54072265625001, -1.840136718750003], + [16.8798828125, -1.225878906250003], + [17.752832031250023, -0.549023437500011], + [18.072167968750023, 2.01328125], + [18.49091796875001, 2.924414062499991], + [18.6103515625, 3.478417968749994], + [18.594140625000023, 4.346240234374989], + [19.06855468750001, 4.891406249999989], + [19.5009765625, 5.127490234374989], + [19.806542968750023, 5.089306640624997], + [20.226367187500017, 4.829638671874989], + [20.55810546875, 4.462695312499989], + [22.422167968750017, 4.134960937499997], + [22.864550781250017, 4.723876953125], + [23.41718750000001, 4.663134765624989], + [24.31982421875, 4.994140625], + [25.065234375000017, 4.967431640624994], + [25.52509765625001, 5.31210937499999], + [26.822070312500017, 5.062402343749994], + [27.071875, 5.199755859374989], + [27.4033203125, 5.109179687499989] + ] + ] + }, + "properties": { "name": "Dem. Rep. Congo", "childNum": 1 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [18.6103515625, 3.478417968749994], + [18.49091796875001, 2.924414062499991], + [18.072167968750023, 2.01328125], + [17.752832031250023, -0.549023437500011], + [16.8798828125, -1.225878906250003], + [16.54072265625001, -1.840136718750003], + [16.21533203125, -2.177832031250006], + [16.217382812500006, -3.0302734375], + [15.990039062500017, -3.766210937500006], + [14.707910156250023, -4.881738281250009], + [14.410742187500006, -4.83125], + [14.365429687500011, -4.585546875], + [14.449804687500006, -4.449511718750003], + [14.358300781250023, -4.299414062500006], + [13.94091796875, -4.484667968750003], + [13.717089843750017, -4.454492187500009], + [13.659570312500023, -4.721484375], + [13.414941406250023, -4.83740234375], + [13.136621093750023, -4.604296875], + [13.08740234375, -4.601953125], + [13.07275390625, -4.634765625], + [13.048046875000011, -4.619238281250006], + [12.971386718750011, -4.5517578125], + [12.881054687500011, -4.445117187500003], + [12.84814453125, -4.428906250000011], + [12.50146484375, -4.5875], + [12.018359375000017, -5.004296875], + [11.777539062500011, -4.565820312500009], + [11.130175781250017, -3.916308593750003], + [11.234472656250006, -3.690820312500009], + [11.504296875000023, -3.5203125], + [11.685742187500011, -3.68203125], + [11.8798828125, -3.665917968750009], + [11.934179687500006, -3.318554687500011], + [11.715429687500006, -3.176953125000011], + [11.760156250000023, -2.983105468750011], + [11.537792968750011, -2.83671875], + [11.60546875, -2.342578125], + [12.064453125, -2.41259765625], + [12.446386718750006, -2.329980468750009], + [12.43212890625, -1.928906250000011], + [12.590429687500006, -1.826855468750011], + [12.793554687500006, -1.931835937500011], + [12.991992187500017, -2.313378906250009], + [13.464941406250006, -2.395410156250009], + [13.733789062500023, -2.138476562500003], + [13.886914062500011, -2.465429687500006], + [13.993847656250011, -2.490625], + [14.199804687500006, -2.354199218750011], + [14.162890625000017, -2.217578125], + [14.383984375000011, -1.890039062500009], + [14.47412109375, -0.573437500000011], + [13.860058593750011, -0.203320312500011], + [13.949609375000023, 0.353808593749989], + [14.32421875, 0.62421875], + [14.429882812500011, 0.901464843749991], + [14.180859375000011, 1.370214843749991], + [13.851367187500017, 1.41875], + [13.21630859375, 1.2484375], + [13.172167968750017, 1.78857421875], + [13.293554687500006, 2.161572265624997], + [14.578906250000017, 2.199121093749994], + [14.902441406250006, 2.012304687499991], + [15.741601562500023, 1.914990234374997], + [16.059375, 1.676220703124997], + [16.183398437500017, 2.270068359374989], + [16.468554687500017, 2.831738281249997], + [16.610742187500023, 3.50537109375], + [17.491601562500023, 3.687304687499989], + [18.160937500000017, 3.499804687499989], + [18.474414062500017, 3.622998046874997], + [18.6103515625, 3.478417968749994] + ] + ] + }, + "properties": { "name": "Congo", "childNum": 1 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [-71.31972656249997, 11.861914062500048], + [-71.95810546875, 11.66640625], + [-72.24848632812501, 11.196435546875009], + [-72.690087890625, 10.835839843749994], + [-72.86933593750001, 10.49125976562496], + [-73.00654296874998, 9.789160156250006], + [-73.36621093749997, 9.194140625000017], + [-73.05839843749999, 9.259570312500031], + [-72.79638671874997, 9.10898437499999], + [-72.66542968749994, 8.62758789062498], + [-72.39033203124995, 8.287060546874969], + [-72.47197265624996, 7.524267578124991], + [-72.20771484374995, 7.37026367187498], + [-72.00664062499993, 7.032617187500023], + [-71.12861328124993, 6.98671875], + [-70.73715820312503, 7.090039062499997], + [-70.12919921874999, 6.95361328125], + [-69.42714843749997, 6.123974609374997], + [-68.47177734375, 6.156542968749974], + [-67.85917968749999, 6.289892578124963], + [-67.48198242187499, 6.18027343750002], + [-67.47387695312503, 5.929980468750003], + [-67.82490234374995, 5.270458984375026], + [-67.85527343750002, 4.506884765624989], + [-67.66162109375, 3.864257812499986], + [-67.3111328125, 3.41586914062502], + [-67.85908203124998, 2.793603515624994], + [-67.61870117187496, 2.793603515624994], + [-67.21083984375, 2.390136718750043], + [-66.87602539062499, 1.223046875000037], + [-67.082275390625, 1.185400390625006], + [-67.11923828124998, 1.703613281249986], + [-67.40043945312499, 2.116699218750028], + [-67.93623046874998, 1.748486328124969], + [-68.19379882812495, 1.987011718749983], + [-68.25595703125, 1.845507812500017], + [-68.17656249999999, 1.719824218749991], + [-69.84858398437493, 1.708740234375043], + [-69.85214843750003, 1.05952148437504], + [-69.31181640624999, 1.050488281249969], + [-69.15332031249994, 0.65878906250002], + [-69.47211914062498, 0.72993164062504], + [-70.05390624999993, 0.578613281250028], + [-70.07050781249993, -0.13886718750004], + [-69.63398437500001, -0.50927734375], + [-69.40024414062498, -1.194921874999977], + [-69.66904296875003, -2.667675781249997], + [-69.94819335937498, -4.200585937500009], + [-69.96591796875003, -4.2359375], + [-70.16752929687499, -4.050195312500009], + [-70.24028320312496, -3.882714843749994], + [-70.2984375, -3.844238281249972], + [-70.33950195312502, -3.814355468750009], + [-70.73510742187497, -3.781542968749989], + [-70.09584960937494, -2.658203125000014], + [-70.16474609374995, -2.639843750000011], + [-70.24443359375002, -2.606542968749977], + [-70.29462890624995, -2.552539062499989], + [-70.57587890624995, -2.418261718749989], + [-70.64799804687499, -2.405761718750014], + [-70.70537109374996, -2.341992187499983], + [-70.91455078125003, -2.218554687499974], + [-70.96855468750002, -2.206835937499989], + [-71.02729492187498, -2.225781250000026], + [-71.11337890625003, -2.245410156250031], + [-71.19638671874998, -2.313085937499963], + [-71.39697265625, -2.334082031249977], + [-71.55947265624997, -2.224218749999977], + [-71.75253906249995, -2.15273437499998], + [-71.80273437499997, -2.166308593749989], + [-71.86728515624998, -2.227734374999983], + [-71.932470703125, -2.288671874999963], + [-71.98427734375, -2.326562499999952], + [-72.21845703125001, -2.400488281250006], + [-72.94111328124998, -2.394042968750028], + [-72.9896484375, -2.33974609374998], + [-73.15449218749993, -2.278222656249966], + [-73.19697265624995, -1.830273437500011], + [-73.49628906249993, -1.69306640625004], + [-73.66430664062497, -1.248828124999946], + [-73.86318359374997, -1.19667968749998], + [-73.92695312500001, -1.125195312499983], + [-73.98681640625003, -1.098144531249986], + [-74.05439453124995, -1.028613281250031], + [-74.18076171875, -0.997753906249955], + [-74.24638671874999, -0.970605468750023], + [-74.28388671874998, -0.927832031250006], + [-74.33442382812498, -0.85087890624996], + [-74.41787109375, -0.580664062499977], + [-74.46518554687498, -0.517675781250034], + [-74.51386718749993, -0.470117187500023], + [-74.555078125, -0.429882812499997], + [-74.61635742187494, -0.370019531249966], + [-74.691650390625, -0.335253906249989], + [-74.75537109375003, -0.298632812499989], + [-74.78046874999998, -0.24453125], + [-74.80175781249997, -0.200097656249994], + [-75.13837890624998, -0.050488281249969], + [-75.28447265624999, -0.10654296875002], + [-75.77666015624999, 0.08925781249998], + [-76.27060546874998, 0.439404296874997], + [-76.49462890624997, 0.23544921875002], + [-77.396337890625, 0.393896484374963], + [-77.46767578124997, 0.636523437500017], + [-77.702880859375, 0.837841796874997], + [-78.1806640625, 0.968554687499974], + [-78.85966796874996, 1.455371093750031], + [-79.02543945312499, 1.623681640625037], + [-78.79296874999994, 1.848730468749963], + [-78.576904296875, 1.773779296874977], + [-78.59169921875, 2.356640624999969], + [-78.41689453125, 2.483496093749963], + [-78.06665039062494, 2.509130859375034], + [-77.81357421875, 2.716357421874974], + [-77.076806640625, 3.913281250000026], + [-77.26352539062503, 3.893212890625023], + [-77.27802734374995, 4.058496093750023], + [-77.35820312499996, 3.944726562500037], + [-77.40874023437496, 4.24775390625004], + [-77.52070312499993, 4.212792968750023], + [-77.35351562499997, 4.398291015624977], + [-77.28632812499995, 4.72172851562496], + [-77.373291015625, 5.323974609375], + [-77.53442382812497, 5.537109374999986], + [-77.24926757812497, 5.780175781250037], + [-77.46943359374995, 6.176757812500014], + [-77.368798828125, 6.575585937499994], + [-77.90117187499999, 7.229345703125048], + [-77.76191406249995, 7.698828125000034], + [-77.53828124999995, 7.56625976562502], + [-77.19599609374995, 7.972460937500003], + [-77.47851562499994, 8.498437500000037], + [-77.37421874999993, 8.65830078125002], + [-76.85185546875002, 8.09047851562498], + [-76.924658203125, 7.973193359374974], + [-76.78657226562493, 7.931591796875026], + [-76.7720703125, 8.310546875000043], + [-76.92045898437496, 8.573730468750014], + [-76.27685546875, 8.989111328124991], + [-76.02724609374997, 9.365771484374989], + [-75.63935546874998, 9.450439453125014], + [-75.680029296875, 9.729785156249989], + [-75.53857421874997, 10.205175781250034], + [-75.708349609375, 10.143408203124963], + [-75.44599609374995, 10.610888671874989], + [-74.84458007812498, 11.109716796875006], + [-74.330224609375, 10.996679687499991], + [-74.51625976562497, 10.8625], + [-74.40087890625, 10.76523437499999], + [-74.14291992187503, 11.320849609375031], + [-73.31337890624997, 11.295751953124991], + [-72.275, 11.88925781250002], + [-72.13574218749994, 12.188574218749977], + [-71.71455078124993, 12.41997070312496], + [-71.26210937499997, 12.335302734375034], + [-71.13730468750003, 12.04633789062504], + [-71.31972656249997, 11.861914062500048] + ] + ] + }, + "properties": { "name": "Colombia", "childNum": 1 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [43.788671875, -12.307031250000023], + [43.85898437500006, -12.368261718749977], + [43.66367187500006, -12.342871093749949], + [43.63134765624997, -12.247070312499972], + [43.788671875, -12.307031250000023] + ] + ], + [ + [ + [44.476367187500074, -12.08154296875], + [44.504980468750006, -12.356542968749991], + [44.220117187499994, -12.171386718750014], + [44.476367187500074, -12.08154296875] + ] + ], + [ + [ + [43.46582031249997, -11.901269531249966], + [43.226660156250006, -11.75185546874998], + [43.2990234375001, -11.374511718750028], + [43.39296875000005, -11.408593749999952], + [43.46582031249997, -11.901269531249966] + ] + ] + ] + }, + "properties": { "name": "Comoros", "childNum": 3 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [-24.308251953124966, 14.856298828124991], + [-24.44052734374992, 14.834814453124963], + [-24.496875, 14.980273437500017], + [-24.329492187499937, 15.019482421875011], + [-24.308251953124966, 14.856298828124991] + ] + ], + [ + [ + [-23.18212890624997, 15.136767578125017], + [-23.210253906250017, 15.32353515625006], + [-23.119335937499955, 15.26840820312502], + [-23.18212890624997, 15.136767578125017] + ] + ], + [ + [ + [-23.444238281249994, 15.00795898437498], + [-23.5046875, 14.916113281250006], + [-23.70537109374999, 14.96132812499998], + [-23.74809570312499, 15.328515625], + [-23.444238281249994, 15.00795898437498] + ] + ], + [ + [ + [-22.917724609375, 16.237255859374955], + [-22.69262695312497, 16.169042968750006], + [-22.710107421874994, 16.043359374999966], + [-22.95927734374996, 16.045117187499983], + [-22.917724609375, 16.237255859374955] + ] + ], + [ + [ + [-24.08769531249999, 16.62250976562501], + [-24.03271484374997, 16.57202148437503], + [-24.243066406250023, 16.599414062500017], + [-24.32236328124992, 16.49311523437504], + [-24.398095703124966, 16.61840820312497], + [-24.08769531249999, 16.62250976562501] + ] + ], + [ + [ + [-22.888330078124966, 16.659082031249994], + [-22.980615234374937, 16.700878906249983], + [-22.93291015624999, 16.84101562500004], + [-22.888330078124966, 16.659082031249994] + ] + ], + [ + [ + [-24.88706054687495, 16.81811523437497], + [-25.09306640624999, 16.83251953125], + [-24.936474609374983, 16.92211914062503], + [-24.88706054687495, 16.81811523437497] + ] + ], + [ + [ + [-25.169824218749994, 16.94648437500001], + [-25.308300781249955, 16.93583984374999], + [-25.337109374999955, 17.091015624999983], + [-25.03466796875, 17.176464843749983], + [-24.979687499999983, 17.09472656250003], + [-25.169824218749994, 16.94648437500001] + ] + ] + ] + }, + "properties": { "name": "Cape Verde", "childNum": 8 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [-83.6419921875, 10.917236328125], + [-83.346826171875, 10.315380859374997], + [-82.77841796874999, 9.66953125], + [-82.56357421874999, 9.57666015625], + [-82.56923828125, 9.55820312499999], + [-82.58652343749999, 9.538818359375], + [-82.64409179687499, 9.505859375], + [-82.801025390625, 9.591796875], + [-82.843994140625, 9.57080078125], + [-82.86015624999999, 9.511474609375], + [-82.88896484374999, 9.481005859374989], + [-82.925048828125, 9.469042968749989], + [-82.93984375, 9.449169921874997], + [-82.94033203125, 9.060107421874989], + [-82.88134765625, 9.055859375], + [-82.78305664062499, 8.990283203124989], + [-82.741162109375, 8.951708984374989], + [-82.72783203124999, 8.916064453124989], + [-82.91704101562499, 8.740332031249991], + [-82.855712890625, 8.635302734374989], + [-82.84477539062499, 8.489355468749991], + [-82.86162109374999, 8.45351562499999], + [-83.02734375, 8.337744140624991], + [-82.879345703125, 8.070654296874991], + [-83.12333984374999, 8.353076171874989], + [-83.16240234374999, 8.588183593749989], + [-83.4697265625, 8.706835937499989], + [-83.29150390625, 8.406005859375], + [-83.54375, 8.445849609374989], + [-83.73408203125, 8.614453125], + [-83.613720703125, 8.804052734374991], + [-83.73691406249999, 9.150292968749994], + [-84.58159179687499, 9.568359375], + [-84.71494140624999, 9.8994140625], + [-85.23564453124999, 10.242089843749994], + [-85.2365234375, 10.107373046874997], + [-84.88642578125, 9.820947265624994], + [-85.07705078125, 9.60195312499999], + [-85.31455078124999, 9.8109375], + [-85.62485351562499, 9.902441406249991], + [-85.84965820312499, 10.292041015624989], + [-85.667236328125, 10.745019531249994], + [-85.90800781249999, 10.897558593749991], + [-85.7443359375, 11.06210937499999], + [-85.5841796875, 11.189453125], + [-84.9091796875, 10.9453125], + [-84.6341796875, 11.045605468749997], + [-83.91928710937499, 10.7353515625], + [-83.71293945312499, 10.785888671875], + [-83.6419921875, 10.917236328125] + ] + ] + }, + "properties": { "name": "Costa Rica", "childNum": 1 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [-82.56176757812503, 21.571679687500023], + [-82.959619140625, 21.441308593750023], + [-83.18378906250001, 21.59345703125004], + [-82.97358398437498, 21.592285156250057], + [-83.08251953124997, 21.791406250000023], + [-82.99121093750003, 21.942724609375034], + [-82.71455078124998, 21.890283203125023], + [-82.56176757812503, 21.571679687500023] + ] + ], + [ + [ + [-77.66899414062493, 21.951953125000045], + [-77.91855468749998, 22.088085937499983], + [-77.63369140624994, 22.054003906250074], + [-77.66899414062493, 21.951953125000045] + ] + ], + [ + [ + [-77.87939453125, 22.127539062500034], + [-78.04165039062502, 22.201269531250034], + [-77.99921874999998, 22.298730468749994], + [-77.87939453125, 22.127539062500034] + ] + ], + [ + [ + [-81.83745117187499, 23.163037109374955], + [-81.26235351562497, 23.156835937500034], + [-81.14462890624998, 23.054931640625057], + [-80.65014648437494, 23.10307617187499], + [-80.36489257812502, 22.943408203125074], + [-79.82026367187498, 22.887011718750045], + [-79.27568359374999, 22.407617187499994], + [-78.68647460937493, 22.366845703125023], + [-77.63681640624995, 21.79736328125], + [-77.49711914062502, 21.78833007812503], + [-77.58315429687497, 21.889257812499977], + [-77.49726562499995, 21.871630859375045], + [-77.14414062499995, 21.643603515625017], + [-77.36616210937498, 21.612646484375034], + [-77.25288085937498, 21.483496093750006], + [-77.0986328125, 21.589013671875023], + [-76.86743164062497, 21.330419921875006], + [-75.72294921874996, 21.111035156249983], + [-75.59580078125, 20.99467773437499], + [-75.72456054687493, 20.71455078125004], + [-74.882568359375, 20.65063476562497], + [-74.51313476562495, 20.384570312500045], + [-74.16748046874997, 20.292187499999955], + [-74.15371093750002, 20.168554687500006], + [-75.11640624999995, 19.901416015625017], + [-75.151611328125, 20.008349609375045], + [-75.29047851562495, 19.893115234375017], + [-76.15844726562497, 19.98974609374997], + [-77.715087890625, 19.85546874999997], + [-77.10380859374999, 20.407519531250017], + [-77.22958984374995, 20.64375], + [-78.11635742187497, 20.761865234374994], + [-78.49077148437493, 21.05371093750003], + [-78.72768554687497, 21.592724609374955], + [-79.35742187500003, 21.58515625000001], + [-80.23134765625, 21.872167968750063], + [-80.48544921874998, 22.1234375], + [-81.03564453124997, 22.073583984375063], + [-81.18549804687495, 22.26796875000005], + [-81.284375, 22.109423828125074], + [-81.84941406249993, 22.21367187499999], + [-82.077734375, 22.3876953125], + [-81.71035156250002, 22.496679687500006], + [-81.83881835937498, 22.672460937500034], + [-82.73803710937497, 22.689257812500074], + [-83.37963867187503, 22.222998046875034], + [-83.90073242187495, 22.17011718750001], + [-84.03095703124993, 21.94311523437503], + [-84.502587890625, 21.776171875000045], + [-84.50136718750002, 21.930273437499977], + [-84.88720703125003, 21.856982421875074], + [-84.32636718749998, 22.074316406250034], + [-84.36127929687498, 22.37890625], + [-84.04492187500003, 22.666015625000057], + [-83.25781249999997, 22.967578125000017], + [-81.83745117187499, 23.163037109374955] + ] + ] + ] + }, + "properties": { "name": "Cuba", "childNum": 4 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [-68.75107421874999, 12.059765625], + [-68.9951171875, 12.141845703125], + [-69.15888671875, 12.380273437499994], + [-68.75107421874999, 12.059765625] + ] + ] + }, + "properties": { "name": "Curaçao", "childNum": 1 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [-81.36953124999997, 19.34887695312497], + [-81.10712890624995, 19.305175781250057], + [-81.40478515624994, 19.278417968750006], + [-81.36953124999997, 19.34887695312497] + ] + ], + [ + [ + [-79.823388671875, 19.711914062500057], + [-79.90620117187501, 19.702539062499994], + [-79.74228515625, 19.757128906250017], + [-79.823388671875, 19.711914062500057] + ] + ] + ] + }, + "properties": { "name": "Cayman Is.", "childNum": 2 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [34.004492187500006, 35.065234375], + [33.47578125000001, 35.000341796875], + [33.3837890625, 35.1626953125], + [32.91953125, 35.087841796875], + [32.71269531250002, 35.171044921874994], + [32.8798828125, 35.180566406249994], + [32.94160156250001, 35.390429687499996], + [33.60761718750001, 35.354150390624994], + [34.55605468750002, 35.662060546875], + [33.941992187500006, 35.292041015624996], + [34.004492187500006, 35.065234375] + ] + ] + }, + "properties": { "name": "N. Cyprus", "childNum": 1 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [32.71269531250002, 35.171044921874994], + [32.91953125, 35.087841796875], + [33.3837890625, 35.1626953125], + [33.47578125000001, 35.000341796875], + [34.004492187500006, 35.065234375], + [34.05019531250002, 34.98837890625], + [33.69941406250001, 34.969873046874994], + [33.007910156250006, 34.569580078125], + [32.44902343750002, 34.729443359375], + [32.31718750000002, 34.9533203125], + [32.30097656250001, 35.082958984375], + [32.71269531250002, 35.171044921874994] + ] + ] + }, + "properties": { "name": "Cyprus", "childNum": 1 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [14.809375, 50.858984375], + [14.895800781250017, 50.861376953124996], + [14.98291015625, 50.886572265625], + [14.99375, 51.01435546875], + [16.007226562500023, 50.611621093749996], + [16.2822265625, 50.655615234375], + [16.419726562500017, 50.573632812499994], + [16.210351562500023, 50.423730468749994], + [16.63916015625, 50.1021484375], + [16.989648437500023, 50.2369140625], + [16.88007812500001, 50.427050781249996], + [17.41523437500001, 50.254785156249994], + [17.702246093750006, 50.307177734374996], + [17.627050781250006, 50.11640625], + [17.874804687500017, 49.972265625], + [18.0283203125, 50.03525390625], + [18.562402343750023, 49.879345703125], + [18.83222656250001, 49.510791015624996], + [18.160937500000017, 49.257373046874996], + [18.0859375, 49.06513671875], + [17.75849609375001, 48.888134765625], + [17.135644531250023, 48.841064453125], + [16.953125, 48.598828125], + [16.543554687500006, 48.796240234375], + [16.057226562500006, 48.754785156249994], + [15.066796875000023, 48.997851562499996], + [14.691308593750023, 48.59921875], + [14.049121093750017, 48.602490234375], + [13.814746093750017, 48.766943359375], + [13.769921875000023, 48.815966796874996], + [13.684960937500023, 48.876708984375], + [13.547656250000017, 48.95966796875], + [13.440722656250017, 48.95556640625], + [13.401171875000017, 48.977587890624996], + [12.916699218750011, 49.33046875], + [12.68115234375, 49.414501953125], + [12.390527343750023, 49.739648437499994], + [12.5125, 49.87744140625], + [12.09921875, 50.310986328125], + [12.134863281250006, 50.3109375], + [12.1748046875, 50.288378906249996], + [12.231152343750011, 50.244873046875], + [12.27734375, 50.181445312499996], + [12.3056640625, 50.205712890624994], + [12.549023437500011, 50.393408203125], + [13.016406250000017, 50.490380859374994], + [13.18115234375, 50.510498046875], + [14.369042968750023, 50.898730468749996], + [14.319726562500023, 51.03779296875], + [14.545703125000017, 50.993945312499996], + [14.559667968750006, 50.954931640625], + [14.59521484375, 50.918603515624994], + [14.623828125000017, 50.91474609375], + [14.613574218750017, 50.85556640625], + [14.658203125, 50.8326171875], + [14.723339843750011, 50.814697265625], + [14.766503906250023, 50.818310546875], + [14.797460937500006, 50.842333984374996], + [14.809375, 50.858984375] + ] + ] + }, + "properties": { "name": "Czech Rep.", "childNum": 1 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [14.1982421875, 53.919042968750034], + [13.92578125, 53.879052734374966], + [13.827734375, 54.12724609374999], + [14.1982421875, 53.919042968750034] + ] + ], + [ + [ + [13.709179687500011, 54.382714843749994], + [13.707324218750074, 54.281152343749994], + [13.190039062500034, 54.32563476562501], + [13.336816406249994, 54.697119140625006], + [13.65761718750008, 54.55957031249997], + [13.709179687500011, 54.382714843749994] + ] + ], + [ + [ + [9.739746093750028, 54.82553710937498], + [10.022167968750011, 54.673925781250006], + [9.86865234375, 54.47246093749999], + [10.731542968750006, 54.31625976562506], + [11.013378906250068, 54.37915039062497], + [11.008593750000074, 54.18115234374997], + [10.810742187500068, 54.075146484374955], + [10.917773437500045, 53.99531250000004], + [11.39960937500004, 53.94462890625002], + [12.111328125, 54.168310546875006], + [12.57539062500004, 54.467382812500006], + [13.028613281250017, 54.411035156249994], + [13.448046875000017, 54.14086914062503], + [13.724218750000063, 54.153222656249966], + [13.865527343750074, 53.85336914062498], + [14.258886718750006, 53.729638671874994], + [14.298730468750051, 53.55644531249999], + [14.41455078125, 53.28349609374996], + [14.412304687500011, 53.216748046874955], + [14.410937500000074, 53.19902343749999], + [14.368554687500051, 53.105566406250034], + [14.293164062500068, 53.026757812499966], + [14.138867187500068, 52.93286132812503], + [14.128613281250011, 52.87822265625002], + [14.253710937500017, 52.78251953124996], + [14.514062500000023, 52.645605468750034], + [14.619433593750017, 52.52851562499998], + [14.569726562499994, 52.431103515624955], + [14.554589843750023, 52.35966796874996], + [14.573925781250068, 52.31416015625001], + [14.615625, 52.277636718750045], + [14.679882812500068, 52.25], + [14.752539062500034, 52.08183593750002], + [14.601660156250034, 51.832373046875006], + [14.738671875000051, 51.62714843750004], + [14.7109375, 51.54492187499997], + [14.724707031250063, 51.523876953124955], + [14.90595703125004, 51.463330078124955], + [14.935546875000028, 51.435351562500045], + [14.9638671875, 51.095117187499994], + [14.917480468750057, 51.00874023437498], + [14.814257812499989, 50.871630859375045], + [14.809375, 50.858984375000034], + [14.797460937500034, 50.84233398437502], + [14.766503906250051, 50.81831054687501], + [14.72333984375004, 50.81469726562497], + [14.658203125, 50.832617187500006], + [14.613574218750045, 50.85556640625006], + [14.623828125000017, 50.91474609375004], + [14.595214843750057, 50.91860351562502], + [14.559667968750006, 50.954931640625034], + [14.545703124999989, 50.99394531249999], + [14.319726562500051, 51.037792968749955], + [14.36904296875008, 50.89873046874996], + [13.18115234375, 50.510498046875], + [13.016406250000017, 50.490380859374994], + [12.549023437500011, 50.393408203125034], + [12.3056640625, 50.205712890624994], + [12.27734375, 50.18144531250002], + [12.231152343749983, 50.24487304687497], + [12.174804687500057, 50.28837890624996], + [12.134863281250006, 50.31093750000002], + [12.099218750000034, 50.31098632812504], + [12.089843749999972, 50.30175781250003], + [12.089746093750051, 50.2685546875], + [12.294598214285761, 50.13608119419641], + [12.5125, 49.87744140625], + [12.390527343750051, 49.739648437499994], + [12.68115234375, 49.41450195312501], + [12.91669921875004, 49.33046875000002], + [13.401171875000074, 48.97758789062499], + [13.440722656250045, 48.95556640625003], + [13.547656250000074, 48.95966796874998], + [13.684960937500051, 48.87670898437506], + [13.769921875000051, 48.81596679687502], + [13.814746093750017, 48.76694335937498], + [13.802929687500011, 48.74750976562501], + [13.798828124999972, 48.62167968750006], + [13.785351562499983, 48.58745117187502], + [13.486621093750074, 48.58183593750002], + [13.471679687500028, 48.57182617187502], + [13.459863281250023, 48.564550781250034], + [13.409375, 48.39414062500006], + [13.322851562500006, 48.33125], + [13.215234375000023, 48.301904296874994], + [12.760351562500063, 48.10698242187499], + [12.95351562500008, 47.890625], + [12.897656250000068, 47.721875], + [13.054101562500051, 47.655126953125034], + [13.047949218750034, 47.57915039062502], + [13.031542968750074, 47.50800781250001], + [13.01435546875004, 47.478076171875045], + [12.968066406250017, 47.475683593750006], + [12.878906250000057, 47.506445312500034], + [12.809375, 47.542187499999955], + [12.782812500000034, 47.56416015624998], + [12.781152343750051, 47.590429687500006], + [12.796191406249989, 47.60703125], + [12.771386718750023, 47.63940429687503], + [12.685839843750074, 47.66933593750002], + [12.209277343750074, 47.71826171875003], + [12.196875, 47.709082031250034], + [12.203808593750011, 47.64672851562503], + [12.185644531250063, 47.61953125], + [11.041992187500028, 47.39311523437496], + [10.98085937499999, 47.39814453125001], + [10.893945312500051, 47.470458984375], + [10.870605468750028, 47.500781250000045], + [10.873046874999972, 47.52021484375001], + [10.741601562500023, 47.52412109375001], + [10.65869140625, 47.547216796875006], + [10.482812500000051, 47.54179687499996], + [10.439453125000028, 47.55156249999999], + [10.403906250000063, 47.41699218750003], + [10.369140625, 47.366064453125034], + [10.18300781250008, 47.27880859375003], + [10.200292968750063, 47.36342773437505], + [10.066308593750023, 47.39335937500002], + [10.064575892857171, 47.42369419642856], + [10.059863281250045, 47.44907226562498], + [10.034082031250023, 47.47358398437501], + [9.971582031249994, 47.50532226562498], + [9.839160156250017, 47.55229492187496], + [9.748925781250023, 47.575537109375006], + [9.524023437500034, 47.52421875000002], + [8.572656250000023, 47.775634765625], + [8.435742187500011, 47.73134765625002], + [8.403417968750006, 47.687792968750045], + [8.413281250000068, 47.66269531249998], + [8.451757812500006, 47.65180664062498], + [8.552343750000063, 47.65913085937498], + [8.56708984375004, 47.65190429687502], + [8.57050781250004, 47.63779296874998], + [8.55947265625008, 47.62402343750003], + [8.477636718750034, 47.61269531250002], + [8.454003906249994, 47.59619140625003], + [7.615625, 47.59272460937504], + [7.616601562500023, 48.15678710937502], + [8.134863281250006, 48.97358398437498], + [7.450585937500051, 49.152197265625034], + [6.735449218750006, 49.16059570312498], + [6.344335937500006, 49.45273437499998], + [6.4873046875, 49.798486328124994], + [6.204882812500017, 49.915136718750034], + [6.13818359375, 49.97431640625001], + [6.10976562500008, 50.034375], + [6.116503906250045, 50.120996093749966], + [6.340917968750006, 50.451757812500034], + [5.993945312500017, 50.75043945312504], + [6.048437500000034, 50.90488281250006], + [5.857519531250034, 51.030126953125006], + [6.129980468750034, 51.14741210937501], + [6.198828125000034, 51.45], + [5.948730468750057, 51.80268554687501], + [6.800390625, 51.96738281249998], + [6.724511718749994, 52.080224609374966], + [7.035156250000057, 52.38022460937498], + [6.748828125000074, 52.464013671874994], + [6.710742187500045, 52.61787109374998], + [7.033007812500045, 52.65136718749997], + [7.197265625000028, 53.28227539062499], + [7.074316406250034, 53.477636718750006], + [7.285253906250034, 53.68134765625001], + [8.00927734375, 53.69072265624999], + [8.108496093750063, 53.46767578125002], + [8.245214843750006, 53.44531249999997], + [8.333886718750051, 53.606201171875], + [8.495214843750063, 53.39423828124998], + [8.618945312500045, 53.875], + [9.20556640625, 53.85595703124997], + [9.783984375000074, 53.554638671874955], + [9.31201171875, 53.859130859375], + [8.92041015625, 53.96533203125006], + [8.906640625000023, 54.26079101562502], + [8.625781250000017, 54.35395507812501], + [8.951855468750011, 54.46757812499996], + [8.670312500000023, 54.903417968750034], + [9.739746093750028, 54.82553710937498] + ] + ], + [ + [ + [8.307714843750034, 54.786962890625034], + [8.451464843750017, 55.05537109374998], + [8.3798828125, 54.89985351562501], + [8.629589843750068, 54.891748046874966], + [8.307714843750034, 54.786962890625034] + ] + ] + ] + }, + "properties": { "name": "Germany", "childNum": 4 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [43.24599609375002, 11.499804687499989], + [42.92275390625002, 10.999316406249989], + [42.557714843750006, 11.080761718749997], + [41.79824218750002, 10.98046875], + [41.79267578125001, 11.68603515625], + [42.378515625, 12.46640625], + [42.40859375000002, 12.494384765625], + [42.45, 12.521337890624991], + [42.47939453125002, 12.513623046874997], + [42.703710937500006, 12.380322265624997], + [42.76748046875002, 12.4228515625], + [42.825292968750006, 12.5693359375], + [42.86591796875001, 12.622802734375], + [42.88330078125, 12.621289062499997], + [43.00566406250002, 12.662304687499997], + [43.11669921875, 12.70859375], + [43.353515625, 12.367041015624991], + [43.38027343750002, 12.091259765624997], + [42.64003906250002, 11.560107421874989], + [42.52177734375002, 11.572167968749994], + [42.58378906250002, 11.496777343749997], + [43.04277343750002, 11.588476562499991], + [43.24599609375002, 11.499804687499989] + ] + ] + }, + "properties": { "name": "Djibouti", "childNum": 1 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [-61.281689453125, 15.2490234375], + [-61.37539062499999, 15.227294921875], + [-61.45810546874999, 15.633105468750003], + [-61.277246093749994, 15.526708984374991], + [-61.281689453125, 15.2490234375] + ] + ] + }, + "properties": { "name": "Dominica", "childNum": 1 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [11.361425781250006, 54.891650390625045], + [11.739550781250017, 54.80742187500002], + [11.765917968750074, 54.67944335937506], + [11.457421875000023, 54.628857421874955], + [11.035546875000051, 54.77309570312505], + [11.058593750000028, 54.940576171874966], + [11.361425781250006, 54.891650390625045] + ] + ], + [ + [ + [12.549218750000051, 54.96577148437504], + [12.11884765625004, 54.91440429687506], + [12.274023437500034, 55.064111328124994], + [12.549218750000051, 54.96577148437504] + ] + ], + [ + [ + [10.061230468750068, 54.88637695312502], + [9.80625, 54.90600585937503], + [9.78125, 55.06904296875001], + [10.061230468750068, 54.88637695312502] + ] + ], + [ + [ + [10.734082031250011, 54.750732421875], + [10.621679687500006, 54.851416015625006], + [10.95107421875008, 55.15620117187501], + [10.734082031250011, 54.750732421875] + ] + ], + [ + [ + [15.087695312500017, 55.021875], + [14.684179687500063, 55.10224609375004], + [14.765332031250068, 55.296728515625034], + [15.132617187500017, 55.14453125000003], + [15.087695312500017, 55.021875] + ] + ], + [ + [ + [10.645117187500006, 55.60981445312498], + [10.785253906250034, 55.13339843749998], + [10.44277343750008, 55.04877929687498], + [9.988769531250028, 55.163183593750006], + [9.860644531250045, 55.515478515625034], + [10.645117187500006, 55.60981445312498] + ] + ], + [ + [ + [12.665722656250068, 55.596533203125006], + [12.550878906250034, 55.55625], + [12.59921875, 55.68022460937502], + [12.665722656250068, 55.596533203125006] + ] + ], + [ + [ + [12.56875, 55.785058593749966], + [12.215039062500011, 55.46650390624998], + [12.413085937500028, 55.28618164062502], + [12.089941406250006, 55.18813476562505], + [12.050390625000034, 54.81533203125002], + [11.8623046875, 54.77260742187502], + [11.653808593750057, 55.186914062499966], + [11.286328125000068, 55.20444335937498], + [10.978906250000051, 55.721533203125006], + [11.322265625000028, 55.752539062500006], + [11.627734375000074, 55.95688476562498], + [11.819726562500023, 55.69765625000002], + [11.86640625000004, 55.968164062499966], + [12.218945312499983, 56.11865234374997], + [12.578710937500006, 56.06406250000006], + [12.56875, 55.785058593749966] + ] + ], + [ + [ + [11.052148437500051, 57.25253906250006], + [10.873828125000045, 57.26225585937499], + [11.174511718750011, 57.322900390624994], + [11.052148437500051, 57.25253906250006] + ] + ], + [ + [ + [9.739746093750028, 54.82553710937498], + [8.670312500000023, 54.903417968750034], + [8.61591796875004, 55.41821289062503], + [8.132128906250074, 55.59980468749998], + [8.16396484375008, 56.60688476562498], + [8.671679687500045, 56.49565429687496], + [8.88808593750008, 56.73505859374998], + [9.06708984375004, 56.79384765625005], + [9.196386718750006, 56.70166015625], + [9.2548828125, 57.01171875000003], + [8.992773437499977, 57.01611328125003], + [8.771972656250028, 56.72529296875004], + [8.468359375, 56.66455078125], + [8.284082031250023, 56.85234374999999], + [8.618554687500051, 57.11127929687498], + [9.43359375, 57.17431640625003], + [9.96230468750008, 57.580957031249994], + [10.609960937500034, 57.73691406249998], + [10.282714843750057, 56.620507812499994], + [10.926171875000051, 56.44326171875002], + [10.753417968750028, 56.24199218749999], + [10.31875, 56.212890625], + [10.18300781250008, 55.86518554687504], + [9.903710937500023, 55.84282226562502], + [10.02363281250004, 55.76142578125004], + [9.591113281250017, 55.49321289062502], + [9.670996093750063, 55.26640624999999], + [9.453710937500006, 55.03955078125006], + [9.732324218750023, 54.96801757812506], + [9.739746093750028, 54.82553710937498] + ] + ] + ] + }, + "properties": { + "name": "Denmark", + "childNum": 10, + "cp": [10.2768332, 56.1773879] + } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [-71.647216796875, 19.195947265624994], + [-71.746484375, 19.285839843749997], + [-71.71147460937499, 19.486572265625], + [-71.75742187499999, 19.688183593749997], + [-71.779248046875, 19.718164062499994], + [-71.6673828125, 19.8486328125], + [-70.95415039062499, 19.913964843749994], + [-70.19384765625, 19.63803710937499], + [-69.95683593749999, 19.671875], + [-69.739404296875, 19.29921875], + [-69.23247070312499, 19.27182617187499], + [-69.60595703125, 19.206494140624997], + [-69.62363281249999, 19.117822265624994], + [-68.684765625, 18.90478515625], + [-68.33916015624999, 18.611523437499997], + [-68.68740234375, 18.21494140624999], + [-68.9349609375, 18.408007812500003], + [-69.27451171874999, 18.43984375], + [-69.770654296875, 18.443554687499997], + [-70.479931640625, 18.21728515625], + [-70.644677734375, 18.336230468750003], + [-71.02783203125, 18.273193359375], + [-71.43896484375, 17.63559570312499], + [-71.63173828125, 17.773632812499997], + [-71.768310546875, 18.03916015624999], + [-71.76376953124999, 18.20395507812499], + [-71.737255859375, 18.270800781250003], + [-71.7619140625, 18.34130859375], + [-71.87255859375, 18.416210937499997], + [-71.940380859375, 18.512597656249994], + [-72.000390625, 18.597900390625], + [-71.98686523437499, 18.6103515625], + [-71.86650390624999, 18.614160156249994], + [-71.74321289062499, 18.73291015625], + [-71.72705078125, 18.80322265625], + [-71.733642578125, 18.856396484374997], + [-71.80712890625, 18.987011718749997], + [-71.647216796875, 19.195947265624994] + ] + ] + }, + "properties": { "name": "Dominican Rep.", "childNum": 1 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [8.207617187500006, 36.518945312499994], + [8.348730468750006, 36.36796875], + [8.318066406250011, 35.654931640624994], + [8.31640625, 35.403125], + [8.35986328125, 35.299609375], + [8.394238281250011, 35.203857421875], + [8.312109375, 35.084619140624994], + [8.27685546875, 34.9794921875], + [8.24560546875, 34.73408203125], + [7.513867187500011, 34.080517578125], + [7.534375, 33.717919921874994], + [7.877246093750017, 33.172119140625], + [8.1125, 33.055322265624994], + [8.333398437500023, 32.543603515624994], + [9.044042968750006, 32.07236328125], + [9.160253906250006, 31.621337890625], + [9.224023437500023, 31.373681640624994], + [9.51875, 30.229394531249994], + [9.310253906250011, 30.115234375], + [9.805273437500006, 29.176953125], + [9.916015625, 27.785693359374996], + [9.74755859375, 27.330859375], + [9.883203125000023, 26.630810546874997], + [9.491406250000011, 26.333740234375], + [9.4482421875, 26.067138671875], + [10.000683593750011, 25.332080078125003], + [10.255859375, 24.591015625], + [10.395898437500023, 24.485595703125], + [10.686132812500006, 24.55136718749999], + [11.507617187500017, 24.314355468749994], + [11.967871093750006, 23.517871093750003], + [7.481738281250017, 20.873095703125003], + [5.836621093750011, 19.479150390624994], + [4.227636718750006, 19.142773437499997], + [3.3564453125, 18.986621093750003], + [3.119726562500006, 19.103173828124994], + [3.255859375, 19.4109375], + [3.130273437500023, 19.85019531249999], + [1.685449218750023, 20.378369140624997], + [1.610644531250017, 20.555566406249994], + [1.165722656250011, 20.817431640625003], + [1.1455078125, 21.102246093749997], + [-1.947900390624994, 23.124804687500003], + [-4.822607421874977, 24.99560546875], + [-8.683349609375, 27.2859375], + [-8.683349609375, 27.656445312499997], + [-8.683349609375, 27.900390625], + [-8.659912109375, 28.718603515625], + [-7.485742187499994, 29.392236328124994], + [-7.427685546874983, 29.425], + [-7.142431640624977, 29.619580078124997], + [-6.855566406249977, 29.601611328124996], + [-6.755126953125, 29.583837890625], + [-6.635351562499977, 29.568798828124997], + [-6.597753906249977, 29.578955078125], + [-6.520556640624989, 29.659863281249997], + [-6.479736328125, 29.820361328124996], + [-6.00429687499999, 29.83125], + [-5.448779296874989, 29.956933593749994], + [-5.293652343749983, 30.058642578124996], + [-5.180126953124983, 30.166162109374994], + [-4.96826171875, 30.465380859374996], + [-4.778515624999983, 30.552392578124994], + [-4.529150390624977, 30.625537109374996], + [-4.322851562499977, 30.698876953124994], + [-4.148779296874977, 30.8095703125], + [-3.626904296874983, 31.000927734374997], + [-3.833398437499994, 31.197802734374996], + [-3.837109374999983, 31.512353515624994], + [-3.768164062499977, 31.68955078125], + [-3.700244140624989, 31.700097656249994], + [-3.604589843749977, 31.686767578125], + [-3.439794921874977, 31.704541015624997], + [-3.017382812499989, 31.834277343749996], + [-2.988232421874983, 31.87421875], + [-2.930859374999983, 32.042529296874996], + [-2.863427734374994, 32.07470703125], + [-1.275341796874983, 32.089013671874994], + [-1.16259765625, 32.399169921875], + [-1.111035156249983, 32.552294921874996], + [-1.188232421875, 32.60849609375], + [-1.29638671875, 32.675683593749994], + [-1.352148437499977, 32.703369140625], + [-1.45, 32.784814453124994], + [-1.510009765625, 32.87763671875], + [-1.550732421874983, 33.073583984375], + [-1.67919921875, 33.318652343749996], + [-1.795605468749983, 34.751904296875], + [-2.131787109374983, 34.970849609374994], + [-2.190771484374977, 35.02978515625], + [-2.219628906249994, 35.10419921875], + [-1.673632812499989, 35.18310546875], + [-0.426123046874977, 35.8615234375], + [-0.048242187499994, 35.8328125], + [0.312207031250011, 36.162353515625], + [0.9716796875, 36.4439453125], + [2.593359375, 36.60068359375], + [2.972851562500011, 36.784472656249996], + [3.779003906250011, 36.89619140625], + [4.758105468750017, 36.896337890625], + [5.29541015625, 36.648242187499996], + [6.486523437500011, 37.085742187499996], + [6.927539062500017, 36.91943359375], + [7.238476562500011, 36.968505859375], + [7.204296875000011, 37.0923828125], + [7.910449218750017, 36.856347656249994], + [8.576562500000023, 36.93720703125], + [8.601269531250011, 36.833935546875], + [8.207617187500006, 36.518945312499994] + ] + ] + }, + "properties": { "name": "Algeria", "childNum": 1 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [-80.131591796875, -2.973144531249957], + [-80.27294921875003, -2.995898437499974], + [-80.22368164062502, -2.753125], + [-80.08076171874995, -2.668847656249966], + [-79.90903320312495, -2.725585937499972], + [-80.131591796875, -2.973144531249957] + ] + ], + [ + [ + [-90.42392578125, -1.339941406250034], + [-90.51953124999994, -1.299121093749974], + [-90.47719726562494, -1.22099609374996], + [-90.42392578125, -1.339941406250034] + ] + ], + [ + [ + [-89.41889648437498, -0.911035156249966], + [-89.60859374999998, -0.888574218750009], + [-89.28784179687503, -0.689843750000023], + [-89.41889648437498, -0.911035156249966] + ] + ], + [ + [ + [-90.33486328125, -0.771582031249977], + [-90.54213867187502, -0.676464843749955], + [-90.53168945312493, -0.581445312499966], + [-90.26938476562498, -0.48466796874996], + [-90.19272460937498, -0.658789062500006], + [-90.33486328125, -0.771582031249977] + ] + ], + [ + [ + [-91.42597656249995, -0.460839843749994], + [-91.61074218749994, -0.44394531250002], + [-91.64667968749998, -0.284472656249946], + [-91.46015625000001, -0.255664062500031], + [-91.42597656249995, -0.460839843749994] + ] + ], + [ + [ + [-90.57392578124993, -0.333984375], + [-90.8677734375, -0.271386718750037], + [-90.78037109374998, -0.160449218749989], + [-90.57392578124993, -0.333984375] + ] + ], + [ + [ + [-91.27216796874998, 0.025146484374986], + [-90.799658203125, -0.752050781249991], + [-90.90551757812497, -0.94052734375002], + [-91.13105468750001, -1.019628906249977], + [-91.41904296874998, -0.996679687500006], + [-91.49541015624999, -0.860937499999977], + [-91.120947265625, -0.559082031250028], + [-91.36918945312493, -0.287207031249977], + [-91.42885742187502, -0.023388671874955], + [-91.59682617187497, 0.002099609374994], + [-91.36137695312496, 0.125830078124977], + [-91.27216796874998, 0.025146484374986] + ] + ], + [ + [ + [-78.90922851562502, 1.252783203124977], + [-78.99169921875003, 1.293212890625043], + [-78.89980468749997, 1.359765625], + [-78.90922851562502, 1.252783203124977] + ] + ], + [ + [ + [-75.28447265624999, -0.10654296875002], + [-75.62626953124999, -0.122851562499974], + [-75.63203125000001, -0.157617187500037], + [-75.56059570312502, -0.200097656249994], + [-75.49106445312498, -0.24833984374996], + [-75.42470703124997, -0.408886718749983], + [-75.259375, -0.59013671874996], + [-75.24960937499998, -0.951855468750026], + [-75.34819335937499, -0.966796874999957], + [-75.38012695312503, -0.94023437499996], + [-75.40805664062503, -0.92431640625], + [-75.42041015624997, -0.962207031250003], + [-75.570556640625, -1.53125], + [-76.08979492187501, -2.133105468749974], + [-76.6791015625, -2.562597656249991], + [-77.860595703125, -2.981640625000011], + [-78.240380859375, -3.472558593750009], + [-78.345361328125, -3.397363281249966], + [-78.64799804687499, -4.248144531250006], + [-78.68603515625003, -4.562402343749994], + [-78.86152343749998, -4.665039062499943], + [-78.90761718749997, -4.714453124999977], + [-78.92578125, -4.770703124999983], + [-78.91420898437497, -4.818652343749974], + [-78.919189453125, -4.858398437499986], + [-78.97539062499999, -4.873242187499997], + [-78.99526367187497, -4.908007812499974], + [-79.03330078124998, -4.96914062499999], + [-79.07626953125003, -4.990625], + [-79.18666992187497, -4.958203124999983], + [-79.26811523437493, -4.957617187499949], + [-79.33095703124997, -4.92783203125002], + [-79.39941406249997, -4.840039062499983], + [-79.45576171874998, -4.766210937499949], + [-79.50190429687495, -4.670605468750011], + [-79.51616210937493, -4.539160156249963], + [-79.57768554687496, -4.50058593750002], + [-79.638525390625, -4.454882812500031], + [-79.71098632812502, -4.467578124999946], + [-79.79726562500002, -4.47636718749996], + [-79.8451171875, -4.445898437499977], + [-79.962890625, -4.390332031250026], + [-80.06352539062499, -4.327539062500023], + [-80.13955078125002, -4.296093750000011], + [-80.19746093750001, -4.311035156249943], + [-80.293359375, -4.416796875], + [-80.38349609374998, -4.46367187499996], + [-80.424169921875, -4.461425781250028], + [-80.47856445312499, -4.430078125000037], + [-80.48847656249995, -4.393652343749991], + [-80.44384765625003, -4.335839843750023], + [-80.35288085937495, -4.208496093750014], + [-80.453759765625, -4.205175781249963], + [-80.48847656249995, -4.165527343749972], + [-80.49345703124999, -4.119140625000014], + [-80.510009765625, -4.06953125000004], + [-80.49013671874994, -4.010058593750003], + [-80.43720703125001, -3.978613281249991], + [-80.30327148437499, -4.005078124999969], + [-80.26689453124993, -3.948828124999963], + [-80.23051757812499, -3.924023437499969], + [-80.19414062499996, -3.905859375], + [-80.24375, -3.576757812500006], + [-80.32465820312498, -3.387890625], + [-79.96333007812501, -3.15771484375], + [-79.72988281249997, -2.579101562499972], + [-79.842138671875, -2.0673828125], + [-79.92558593749996, -2.548535156249969], + [-80.03017578124994, -2.556738281249949], + [-80.00664062499993, -2.353808593750003], + [-80.28471679687502, -2.706738281249955], + [-80.93217773437493, -2.269140624999977], + [-80.76059570312498, -1.934570312500028], + [-80.90239257812499, -1.078906249999974], + [-80.55390624999998, -0.847949218749989], + [-80.45546875, -0.585449218749986], + [-80.282373046875, -0.620507812500023], + [-80.48227539062503, -0.368261718749963], + [-80.046142578125, 0.155371093750048], + [-80.08828124999997, 0.78476562500002], + [-78.89965820312503, 1.20625], + [-78.85966796874996, 1.455371093750031], + [-78.1806640625, 0.968554687499974], + [-77.702880859375, 0.837841796874997], + [-77.46767578124997, 0.636523437500017], + [-77.396337890625, 0.393896484374963], + [-76.49462890624997, 0.23544921875002], + [-76.27060546874998, 0.439404296874997], + [-75.77666015624999, 0.08925781249998], + [-75.28447265624999, -0.10654296875002] + ] + ] + ] + }, + "properties": { "name": "Ecuador", "childNum": 9 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [34.24531250000001, 31.208300781249996], + [34.904296875, 29.47734375], + [34.736425781250006, 29.27060546875], + [34.39970703125002, 28.01601562499999], + [34.22011718750002, 27.764306640624994], + [33.76025390625, 28.04765625], + [33.24775390625001, 28.567724609375], + [32.56572265625002, 29.973974609375], + [32.35976562500002, 29.630664062499996], + [32.89824218750002, 28.565234375], + [33.54707031250001, 27.898144531249997], + [33.5498046875, 27.607373046874997], + [33.84931640625001, 27.184912109375], + [33.959082031250006, 26.6490234375], + [35.19414062500002, 24.475146484375003], + [35.78388671875001, 23.937792968750003], + [35.54082031250002, 23.920654296875], + [35.50439453125, 23.779296875], + [35.697851562500006, 22.946191406249994], + [36.22968750000001, 22.628808593749994], + [36.87138671875002, 21.996728515624994], + [31.434472656250023, 21.995849609375], + [31.486132812500017, 22.14780273437499], + [31.400292968750023, 22.202441406250003], + [31.260644531250023, 22.00229492187499], + [31.092675781250023, 21.994873046875], + [28.036425781250017, 21.995361328125], + [24.980273437500017, 21.995849609375], + [24.980273437500017, 25.5888671875], + [24.980273437500017, 29.181884765625], + [24.703222656250006, 30.201074218749994], + [24.96142578125, 30.678515625], + [24.85273437500001, 31.334814453125], + [25.150488281250006, 31.654980468749997], + [25.382226562500023, 31.51279296875], + [25.89326171875001, 31.620898437499996], + [27.248046875, 31.377880859374997], + [27.5400390625, 31.212695312499996], + [28.51484375000001, 31.050439453124994], + [29.072070312500017, 30.830273437499997], + [29.929785156250006, 31.227490234374997], + [30.22265625, 31.2583984375], + [30.395117187500006, 31.4576171875], + [30.92353515625001, 31.566845703124997], + [30.56298828125, 31.4169921875], + [31.001757812500017, 31.462792968749994], + [31.082910156250023, 31.603320312499996], + [31.5244140625, 31.458251953125], + [31.888964843750017, 31.54140625], + [32.13603515625002, 31.341064453125], + [31.8921875, 31.482470703124996], + [31.77109375, 31.292578125], + [32.10175781250001, 31.092822265624996], + [32.281835937500006, 31.200878906249997], + [32.21621093750002, 31.29375], + [32.60332031250002, 31.06875], + [33.66650390625, 31.130419921874996], + [34.19814453125002, 31.322607421875], + [34.24531250000001, 31.208300781249996] + ] + ] + }, + "properties": { "name": "Egypt", "childNum": 1 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [40.141210937500006, 15.696142578125034], + [40.399023437500006, 15.579882812500045], + [39.975195312500006, 15.612451171875023], + [39.94746093750004, 15.696142578125034], + [40.07050781250004, 15.676611328125034], + [39.93994140625003, 15.744531250000023], + [39.9567382812501, 15.889404296875057], + [40.141210937500006, 15.696142578125034] + ] + ], + [ + [ + [40.07646484375002, 16.082421875000023], + [40.11005859375004, 15.985742187500051], + [39.99609375000003, 16.04267578125001], + [40.07646484375002, 16.082421875000023] + ] + ], + [ + [ + [40.938574218750006, 13.983105468749997], + [40.82011718750002, 14.111669921874991], + [40.22148437500002, 14.431152343749972], + [39.531835937500006, 14.53671875], + [39.198046875000074, 14.479394531250037], + [39.1354492187501, 14.581884765625034], + [39.07421874999997, 14.628222656249974], + [39.02382812499999, 14.628222656249974], + [38.99570312500006, 14.586865234374983], + [38.81201171875003, 14.482324218750009], + [38.50439453124997, 14.42441406250002], + [38.43144531250002, 14.428613281249994], + [38.221484375000074, 14.649658203124986], + [38.002539062500006, 14.737109375000045], + [37.94345703125006, 14.810546875], + [37.884179687499994, 14.852294921874972], + [37.82031250000003, 14.708496093749986], + [37.70839843750005, 14.45722656250004], + [37.64843750000003, 14.32255859375006], + [37.571191406249994, 14.149072265624966], + [37.546777343749994, 14.143847656249974], + [37.507226562499994, 14.156396484375037], + [37.257226562499994, 14.453759765625051], + [37.024511718750006, 14.271972656250057], + [36.81191406250005, 14.315039062500034], + [36.67910156250005, 14.307568359375026], + [36.542382812499994, 14.25820312499999], + [36.52431640625005, 14.256835937499986], + [36.492285156250006, 14.544335937500023], + [36.470800781250006, 14.736474609375009], + [36.448144531249994, 14.940087890625009], + [36.42675781249997, 15.132080078125043], + [36.566015625, 15.362109375], + [36.9137695312501, 16.296191406250045], + [36.887792968750006, 16.624658203124994], + [36.9787109375001, 16.800585937500045], + [36.9757812500001, 16.866552734375006], + [36.99521484375006, 17.020556640625017], + [37.00898437500004, 17.058886718750017], + [37.06152343749997, 17.061279296875057], + [37.16953125000006, 17.04140625], + [37.41103515625005, 17.061718749999955], + [37.452929687500074, 17.108691406250017], + [37.51015625, 17.28813476562499], + [37.54746093750006, 17.32412109375005], + [37.78242187500004, 17.458007812500057], + [38.253515625, 17.584765625000017], + [38.26728515625004, 17.616699218750057], + [38.28984375000002, 17.637011718750017], + [38.34736328125004, 17.68359375], + [38.37373046875004, 17.717333984375045], + [38.42246093750006, 17.823925781249983], + [38.60947265625006, 18.00507812500004], + [39.03447265625002, 17.085546875000034], + [39.298925781250006, 15.921093750000011], + [39.78554687499999, 15.124853515624991], + [39.86376953124997, 15.470312500000034], + [40.20410156250003, 15.014111328124983], + [41.17646484375004, 14.620312500000054], + [41.65820312499997, 13.983056640624994], + [42.24511718749997, 13.587646484374986], + [42.39931640625005, 13.212597656249969], + [42.522851562499994, 13.221484375], + [42.796191406250074, 12.864257812500057], + [42.96953125000002, 12.808349609375028], + [42.99902343750003, 12.899511718750048], + [43.08291015625005, 12.824609374999966], + [43.11669921874997, 12.708593749999963], + [43.00566406250002, 12.66230468750004], + [42.88330078124997, 12.621289062500026], + [42.86591796875004, 12.622802734374986], + [42.82529296875006, 12.569335937500014], + [42.767480468749994, 12.422851562500014], + [42.70371093750006, 12.380322265625054], + [42.479394531249994, 12.513623046875026], + [42.45, 12.521337890625006], + [42.40859375, 12.494384765625014], + [42.37851562500006, 12.46640625], + [42.28994140625005, 12.570214843750009], + [42.225, 12.661962890624963], + [42.13427734374997, 12.771435546874969], + [41.95214843749997, 12.88232421875], + [41.85957031250004, 13.025878906250028], + [41.76503906250005, 13.183935546874991], + [41.362890625, 13.499804687500031], + [40.938574218750006, 13.983105468749997] + ] + ] + ] + }, + "properties": { "name": "Eritrea", "childNum": 3 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [-17.887939453125, 27.809570312500057], + [-17.984765625000023, 27.646386718750023], + [-18.160546874999937, 27.76147460937503], + [-17.887939453125, 27.809570312500057] + ] + ], + [ + [ + [-15.400585937499955, 28.147363281250023], + [-15.436767578124972, 27.810693359375023], + [-15.71030273437492, 27.784082031250023], + [-15.809472656249966, 27.994482421874977], + [-15.682763671874994, 28.15405273437497], + [-15.400585937499955, 28.147363281250023] + ] + ], + [ + [ + [-17.184667968749977, 28.02197265624997], + [-17.324902343749955, 28.11767578125003], + [-17.25859375, 28.203173828125045], + [-17.103759765624943, 28.111132812500017], + [-17.184667968749977, 28.02197265624997] + ] + ], + [ + [ + [-16.33447265624997, 28.37993164062499], + [-16.41821289062497, 28.15141601562496], + [-16.65800781249999, 28.007177734374977], + [-16.905322265625017, 28.33959960937503], + [-16.12363281249992, 28.57597656249996], + [-16.33447265624997, 28.37993164062499] + ] + ], + [ + [ + [-14.196777343749943, 28.169287109375063], + [-14.332617187500006, 28.056005859374977], + [-14.49179687499992, 28.100927734374977], + [-14.231982421875017, 28.21582031250003], + [-14.003369140624983, 28.706689453125023], + [-13.85722656249996, 28.73803710937503], + [-13.928027343749989, 28.25346679687499], + [-14.196777343749943, 28.169287109375063] + ] + ], + [ + [ + [-17.83427734374999, 28.49321289062496], + [-18.00078124999999, 28.758251953124955], + [-17.928808593749977, 28.844580078125063], + [-17.7265625, 28.724462890625006], + [-17.83427734374999, 28.49321289062496] + ] + ], + [ + [ + [-13.715966796874966, 28.911230468750034], + [-13.85991210937496, 28.869091796874983], + [-13.823632812499966, 29.013330078124966], + [-13.463574218749955, 29.237207031250023], + [-13.477929687499966, 29.00659179687503], + [-13.715966796874966, 28.911230468750034] + ] + ], + [ + [ + [1.593945312500068, 38.672070312499955], + [1.40576171875, 38.670996093750006], + [1.436328125000017, 38.768212890624994], + [1.593945312500068, 38.672070312499955] + ] + ], + [ + [ + [1.445214843750051, 38.91870117187503], + [1.223339843750068, 38.90385742187502], + [1.3486328125, 39.080810546875], + [1.564453125, 39.12104492187504], + [1.623632812499977, 39.03881835937497], + [1.445214843750051, 38.91870117187503] + ] + ], + [ + [ + [3.145312500000017, 39.79008789062499], + [3.461816406250023, 39.69775390625003], + [3.072851562500006, 39.30126953124997], + [2.799804687500057, 39.38505859374999], + [2.700585937500023, 39.54213867187502], + [2.49951171875, 39.47788085937498], + [2.37001953125008, 39.57207031249999], + [3.15869140625, 39.97050781249999], + [3.145312500000017, 39.79008789062499] + ] + ], + [ + [ + [4.293652343750011, 39.84184570312499], + [3.8671875, 39.958740234375], + [3.853417968750051, 40.06303710937502], + [4.22578125000004, 40.032373046874966], + [4.293652343750011, 39.84184570312499] + ] + ], + [ + [ + [-1.794042968749949, 43.407324218750006], + [-1.410693359374932, 43.240087890625034], + [-1.460839843749937, 43.05175781250006], + [-1.300048828124943, 43.10097656250002], + [-0.586425781249943, 42.798974609374966], + [0.631640625000045, 42.689599609374994], + [0.696875, 42.84511718750005], + [1.428320312499977, 42.59589843749998], + [1.414843750000074, 42.54838867187499], + [1.448828124999977, 42.43745117187504], + [1.534082031250051, 42.44169921875002], + [1.7060546875, 42.50332031250005], + [1.859765625000051, 42.457080078125045], + [1.927929687500068, 42.42631835937499], + [2.032714843750028, 42.353515625], + [3.21142578125, 42.43115234375], + [3.248046875, 41.94423828125002], + [3.0048828125, 41.76743164062506], + [2.082617187500063, 41.287402343750045], + [1.032910156250068, 41.06206054687496], + [0.714648437500074, 40.822851562500006], + [0.891113281250057, 40.72236328125004], + [0.59609375000008, 40.614501953125], + [-0.327001953124949, 39.519873046875006], + [-0.204931640624949, 39.062597656250034], + [0.20156250000008, 38.75917968750002], + [-0.520800781249989, 38.317285156249966], + [-0.814648437500011, 37.76992187500002], + [-0.721582031249966, 37.63105468749998], + [-1.327539062499937, 37.561132812500034], + [-1.640966796874949, 37.38696289062497], + [-2.111523437499983, 36.77666015624999], + [-4.366845703124994, 36.71811523437506], + [-4.67412109374996, 36.506445312500006], + [-5.171484374999949, 36.423779296874955], + [-5.3609375, 36.134912109374994], + [-5.62548828125, 36.02592773437499], + [-6.040673828124937, 36.18842773437498], + [-6.38413085937492, 36.63701171874996], + [-6.216796875000028, 36.91357421875], + [-6.396191406249983, 36.831640625], + [-6.863769531250028, 37.27890625], + [-7.406152343749937, 37.17944335937497], + [-7.44394531249992, 37.72827148437497], + [-6.957568359374932, 38.18789062499999], + [-7.106396484374983, 38.181005859375006], + [-7.343017578124943, 38.45742187500002], + [-6.997949218749994, 39.05644531250002], + [-7.53569335937496, 39.66157226562501], + [-7.117675781249972, 39.681689453125045], + [-6.975390624999932, 39.79838867187502], + [-6.896093749999949, 40.02182617187506], + [-7.032617187499966, 40.16791992187498], + [-6.8101562499999, 40.343115234375034], + [-6.928466796874972, 41.009130859375006], + [-6.2125, 41.53203125], + [-6.542187499999955, 41.672509765624994], + [-6.61826171874992, 41.9423828125], + [-7.147119140625023, 41.98115234374998], + [-7.40361328124996, 41.833691406249955], + [-8.152490234374937, 41.81196289062498], + [-8.266064453124983, 42.13740234375001], + [-8.777148437500017, 41.941064453124994], + [-8.887207031249943, 42.105273437500045], + [-8.690917968749943, 42.274169921875], + [-8.815820312499966, 42.285253906250034], + [-8.730029296874989, 42.411718750000034], + [-8.8115234375, 42.64033203124998], + [-9.033105468750023, 42.593847656250006], + [-8.927197265624926, 42.79858398437497], + [-9.235205078124977, 42.97690429687498], + [-9.178076171874977, 43.17402343749998], + [-8.248925781249937, 43.43940429687498], + [-8.256738281249937, 43.57988281249999], + [-8.004687499999932, 43.69438476562496], + [-7.503613281249983, 43.73994140625001], + [-7.060986328124955, 43.55395507812503], + [-5.846679687499943, 43.645068359375045], + [-4.52304687499992, 43.41572265625004], + [-3.604638671874966, 43.51948242187504], + [-3.045605468749926, 43.37158203125], + [-2.875048828125017, 43.454443359375006], + [-2.337109374999926, 43.32802734375002], + [-1.794042968749949, 43.407324218750006] + ] + ] + ] + }, + "properties": { + "name": "Spain", + "childNum": 12, + "cp": [-2.9366964, 40.3438963] + } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [22.61738281250004, 58.62124023437502], + [23.323242187500057, 58.45083007812502], + [22.730273437500045, 58.23066406250001], + [22.371679687499977, 58.217138671875006], + [21.996875, 57.93134765624998], + [22.187695312500068, 58.15434570312502], + [21.88212890624999, 58.262353515624994], + [21.862304687500057, 58.497167968750034], + [22.61738281250004, 58.62124023437502] + ] + ], + [ + [ + [23.343554687500017, 58.550341796875045], + [23.10908203125004, 58.65922851562502], + [23.332812500000045, 58.648583984374994], + [23.343554687500017, 58.550341796875045] + ] + ], + [ + [ + [22.923730468750023, 58.826904296875], + [22.54218750000001, 58.68999023437499], + [22.411035156250023, 58.863378906250034], + [22.05625, 58.94360351562506], + [22.6494140625, 59.08710937499998], + [22.90986328125004, 58.99121093749997], + [22.923730468750023, 58.826904296875] + ] + ], + [ + [ + [28.0125, 59.484277343749966], + [28.15107421875004, 59.374414062499966], + [27.434179687500006, 58.787255859374994], + [27.502441406250057, 58.221337890624994], + [27.778515625000068, 57.87070312500006], + [27.542089843750063, 57.799414062500006], + [27.4, 57.66679687499999], + [27.35195312500005, 57.528125], + [26.96601562500001, 57.60913085937506], + [26.532617187499994, 57.53100585937503], + [26.29804687500001, 57.60107421875], + [25.66015625, 57.920166015625], + [25.27265625000001, 58.009375], + [25.11103515625004, 58.06342773437498], + [24.45888671875005, 57.907861328124994], + [24.3625, 57.86616210937501], + [24.322558593750074, 57.87060546875003], + [24.529101562500045, 58.35424804687497], + [24.114843750000034, 58.26611328125006], + [23.767578125000057, 58.36083984374997], + [23.50927734375003, 58.65854492187498], + [23.680761718750063, 58.787158203125074], + [23.43203125, 58.920654296875], + [23.494433593750017, 59.19565429687498], + [24.083398437500023, 59.29189453125005], + [24.38037109375003, 59.47265625], + [25.44375, 59.52114257812502], + [25.50927734374997, 59.63901367187506], + [26.974707031250006, 59.450634765624955], + [28.0125, 59.484277343749966] + ] + ] + ] + }, + "properties": { "name": "Estonia", "childNum": 4 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [38.43144531250002, 14.428613281249994], + [38.50439453125, 14.424414062499991], + [38.81201171875, 14.482324218749994], + [38.995703125, 14.586865234374997], + [39.02382812500002, 14.628222656250003], + [39.07421875, 14.628222656250003], + [39.13544921875001, 14.581884765624991], + [39.19804687500002, 14.479394531249994], + [39.531835937500006, 14.53671875], + [40.22148437500002, 14.43115234375], + [40.82011718750002, 14.111669921874991], + [40.938574218750006, 13.983105468749997], + [41.362890625, 13.499804687500003], + [41.76503906250002, 13.183935546874991], + [41.85957031250001, 13.02587890625], + [41.9521484375, 12.88232421875], + [42.13427734375, 12.771435546874997], + [42.225, 12.661962890624991], + [42.28994140625002, 12.570214843749994], + [42.378515625, 12.46640625], + [41.79267578125001, 11.68603515625], + [41.79824218750002, 10.98046875], + [42.557714843750006, 11.080761718749997], + [42.92275390625002, 10.999316406249989], + [42.65644531250001, 10.6], + [42.84160156250002, 10.203076171874997], + [43.181640625, 9.879980468749991], + [43.482519531250006, 9.379492187499991], + [43.98378906250002, 9.008837890624989], + [46.97822265625001, 7.9970703125], + [47.97822265625001, 7.9970703125], + [44.940527343750006, 4.912011718749994], + [43.988867187500006, 4.950537109374991], + [43.58349609375, 4.85498046875], + [43.12568359375001, 4.644482421874997], + [42.85664062500001, 4.32421875], + [42.02412109375001, 4.137939453125], + [41.91533203125002, 4.031298828124989], + [41.88398437500001, 3.977734375], + [41.73769531250002, 3.979052734374989], + [41.48193359375, 3.96328125], + [41.37246093750002, 3.946191406249994], + [41.22089843750001, 3.943554687499997], + [41.02080078125002, 4.057470703124991], + [40.765234375, 4.27304687499999], + [39.84218750000002, 3.851464843749994], + [39.79033203125002, 3.754248046874991], + [39.65751953125002, 3.577832031249997], + [39.49443359375002, 3.456103515624989], + [38.608007812500006, 3.60009765625], + [38.45156250000002, 3.604833984374991], + [38.22529296875001, 3.618994140624991], + [38.08613281250001, 3.64882812499999], + [37.15458984375002, 4.254541015624994], + [36.90556640625002, 4.411474609374991], + [36.02197265625, 4.468115234374991], + [35.76308593750002, 4.808007812499994], + [35.75615234375002, 4.950488281249989], + [35.779296875, 5.105566406249991], + [35.80029296875, 5.156933593749997], + [35.74501953125002, 5.343994140625], + [35.325292968750006, 5.364892578124994], + [35.2646484375, 5.412060546874997], + [35.26386718750001, 5.457910156249994], + [35.26835937500002, 5.492285156249991], + [34.98359375000001, 5.858300781249994], + [34.71064453125001, 6.660302734374994], + [34.06425781250002, 7.225732421874994], + [33.902441406250006, 7.509521484375], + [32.99892578125002, 7.899511718749991], + [33.28105468750002, 8.437255859375], + [33.95332031250001, 8.443505859374994], + [34.07275390625, 8.545263671874991], + [34.078125, 9.461523437499991], + [34.31123046875001, 10.190869140624997], + [34.34394531250001, 10.658642578124997], + [34.571875, 10.880175781249989], + [34.77128906250002, 10.746191406249991], + [34.93144531250002, 10.864794921874989], + [35.1123046875, 11.816552734374994], + [35.67021484375002, 12.623730468749997], + [36.12519531250001, 12.75703125], + [36.52431640625002, 14.2568359375], + [36.54238281250002, 14.25820312499999], + [36.67910156250002, 14.307568359374997], + [36.81191406250002, 14.315039062499991], + [37.024511718750006, 14.27197265625], + [37.25722656250002, 14.453759765624994], + [37.50722656250002, 14.156396484374994], + [37.54677734375002, 14.143847656250003], + [37.57119140625002, 14.149072265624994], + [37.6484375, 14.322558593750003], + [37.70839843750002, 14.457226562499997], + [37.8203125, 14.70849609375], + [37.88417968750002, 14.852294921875], + [37.943457031250006, 14.810546875], + [38.002539062500006, 14.737109375], + [38.22148437500002, 14.649658203125], + [38.43144531250002, 14.428613281249994] + ] + ] + }, + "properties": { "name": "Ethiopia", "childNum": 1 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [22.17509765624999, 60.370751953124994], + [22.41552734375003, 60.30336914062505], + [22.36054687500004, 60.165576171875045], + [22.07714843750003, 60.286328124999955], + [22.17509765624999, 60.370751953124994] + ] + ], + [ + [ + [21.450878906250068, 60.529589843750045], + [21.3, 60.47978515625002], + [21.224707031250006, 60.62060546875003], + [21.450878906250068, 60.529589843750045] + ] + ], + [ + [ + [21.2177734375, 63.241308593750034], + [21.415625, 63.19736328125006], + [21.25341796875, 63.152001953124966], + [21.08388671875008, 63.277539062499955], + [21.2177734375, 63.241308593750034] + ] + ], + [ + [ + [24.848242187500034, 64.99101562499999], + [24.576562500000023, 65.04287109375], + [24.970605468750023, 65.05532226562502], + [24.848242187500034, 64.99101562499999] + ] + ], + [ + [ + [28.96582031250003, 69.02197265625], + [28.414062500000057, 68.90415039062506], + [28.77285156250005, 68.84003906249995], + [28.470703125000057, 68.48837890625], + [28.685156250000034, 68.189794921875], + [29.343847656250006, 68.06186523437506], + [29.988085937500017, 67.66826171874999], + [29.066210937500045, 66.89174804687497], + [30.102734375000097, 65.72626953125004], + [29.715917968750063, 65.62456054687502], + [29.608007812500006, 65.248681640625], + [29.826953125000017, 65.14506835937502], + [29.60419921875004, 64.968408203125], + [30.072851562500063, 64.76503906250005], + [30.04189453125005, 64.44335937499997], + [30.513769531250006, 64.2], + [30.50390625000003, 64.02060546875], + [29.991503906250074, 63.73515625000002], + [31.180859375000097, 63.208300781250074], + [31.533984375000017, 62.885400390624994], + [31.18671875000004, 62.48139648437504], + [29.69013671875004, 61.54609375000001], + [27.797656250000074, 60.53613281250003], + [26.53466796874997, 60.412890625000074], + [26.56933593750003, 60.62456054687502], + [26.377734375000074, 60.42407226562503], + [25.955957031250023, 60.474218750000034], + [26.03583984375004, 60.34150390625001], + [25.75800781250004, 60.26752929687504], + [25.65644531250004, 60.33320312499998], + [24.44560546874999, 60.021289062500045], + [23.46357421875004, 59.986230468749994], + [23.021289062500074, 59.81601562500006], + [23.19843750000001, 60.02182617187498], + [22.911718750000063, 60.20971679687497], + [22.749804687500017, 60.057275390624994], + [22.462695312500045, 60.029199218749966], + [22.5849609375, 60.380566406249955], + [21.436035156250057, 60.596386718749955], + [21.605957031250057, 61.59155273437503], + [21.255957031250063, 61.98964843750005], + [21.143847656250045, 62.73999023437506], + [21.650976562500063, 63.039306640625], + [21.545117187499983, 63.204296874999955], + [22.31972656250005, 63.310449218749994], + [22.532324218750034, 63.647851562499994], + [23.598925781250074, 64.04091796874997], + [24.557910156250045, 64.801025390625], + [25.288183593750063, 64.8603515625], + [25.34785156250004, 65.47924804687497], + [24.674902343750006, 65.67070312499999], + [24.628027343750034, 65.85917968750002], + [24.15546875000004, 65.80527343750006], + [23.700292968750034, 66.25263671874998], + [23.988574218750045, 66.81054687500003], + [23.64150390625005, 67.12939453124997], + [23.733593750000068, 67.42290039062499], + [23.454882812500045, 67.46025390625007], + [23.63886718750004, 67.95439453125002], + [22.854101562500034, 68.36733398437502], + [21.99746093750005, 68.52060546874998], + [20.622167968750006, 69.036865234375], + [21.065722656250017, 69.04174804687503], + [21.06611328125001, 69.21411132812497], + [21.59375, 69.273583984375], + [22.410937500000074, 68.719873046875], + [23.324023437500017, 68.64897460937502], + [23.85400390625, 68.80590820312503], + [24.94140625000003, 68.59326171875006], + [25.748339843750017, 68.99013671875], + [26.07246093750004, 69.69155273437497], + [26.525390625000057, 69.91503906250003], + [27.127539062500063, 69.90649414062497], + [27.747851562500045, 70.06484375], + [29.14160156250003, 69.67143554687505], + [29.33339843750005, 69.47299804687503], + [28.846289062500006, 69.17690429687502], + [28.96582031250003, 69.02197265625] + ] + ] + ] + }, + "properties": { "name": "Finland", "childNum": 5 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [178.48789062500018, -18.97412109375], + [177.95869140624998, -19.121582031250014], + [178.33427734375013, -18.93447265625001], + [178.48789062500018, -18.97412109375] + ] + ], + [ + [ + [179.34931640625015, -18.10234375000003], + [179.25351562500018, -18.030566406249974], + [179.30644531250013, -17.944042968750026], + [179.34931640625015, -18.10234375000003] + ] + ], + [ + [ + [178.28017578124994, -17.37197265625001], + [178.59160156249996, -17.651464843750006], + [178.66767578125004, -18.080859375], + [177.95546875000005, -18.264062500000023], + [177.32138671875, -18.077539062500037], + [177.26396484375007, -17.86347656250004], + [177.5044921875, -17.539550781250043], + [177.81796875000012, -17.38847656249999], + [178.28017578124994, -17.37197265625001] + ] + ], + [ + [ + [180, -16.96308593750001], + [179.89697265625003, -16.96406250000004], + [180, -16.785742187500034], + [180, -16.96308593750001] + ] + ], + [ + [ + [-179.97490234374996, -16.92480468750003], + [-180, -16.96298828124999], + [-180, -16.907812500000034], + [-180, -16.82431640624999], + [-180, -16.78554687499999], + [-179.86098632812502, -16.68828124999999], + [-179.97490234374996, -16.92480468750003] + ] + ], + [ + [ + [-179.92944335937503, -16.502832031250037], + [-179.999951171875, -16.540039062499986], + [-179.900927734375, -16.431542968749994], + [-179.92944335937503, -16.502832031250037] + ] + ], + [ + [ + [179.99921875000004, -16.168554687499977], + [179.56416015625004, -16.636914062499997], + [179.56816406249996, -16.747460937499966], + [179.93037109375004, -16.51943359375005], + [179.9279296875001, -16.74443359374996], + [179.41933593750005, -16.80654296875001], + [179.20234375000004, -16.71269531249999], + [179.00683593750003, -16.90019531249999], + [178.70664062500018, -16.97617187500002], + [178.4974609375, -16.78789062500003], + [178.58359375000012, -16.621875], + [178.80507812499994, -16.631445312500034], + [179.55175781250003, -16.249902343750023], + [180, -16.15292968749999], + [179.99921875000004, -16.168554687499977] + ] + ] + ] + }, + "properties": { "name": "Fiji", "childNum": 7 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [-61.01875, -51.7857421875], + [-60.87597656250003, -51.79423828125004], + [-60.94755859374996, -51.94628906250002], + [-61.14501953125003, -51.83945312500001], + [-61.01875, -51.7857421875] + ] + ], + [ + [ + [-60.28623046874995, -51.461914062500014], + [-59.38759765625002, -51.35996093750003], + [-59.26806640625, -51.42753906250003], + [-59.92138671874997, -51.969531250000045], + [-60.246337890625, -51.98642578125003], + [-60.35346679687498, -52.13994140625004], + [-60.686376953125034, -52.18837890624996], + [-60.96142578125003, -52.05732421874999], + [-60.23847656249998, -51.771972656250036], + [-60.58251953125, -51.71269531250004], + [-60.24516601562493, -51.638867187500004], + [-60.56845703124998, -51.357812499999945], + [-60.28623046874995, -51.461914062500014] + ] + ], + [ + [ + [-60.11171875000002, -51.39589843749998], + [-60.275341796874955, -51.28056640625002], + [-60.06982421875, -51.307910156249996], + [-60.11171875000002, -51.39589843749998] + ] + ], + [ + [ + [-58.85019531249995, -51.26992187499998], + [-58.42583007812502, -51.32421875000003], + [-58.508935546874994, -51.48359375], + [-58.271582031250034, -51.57470703124999], + [-58.25922851562501, -51.417089843750034], + [-57.976513671874955, -51.384375], + [-57.80849609375002, -51.51796875], + [-57.96044921874997, -51.58320312500003], + [-57.79179687499999, -51.63613281249998], + [-58.68349609375002, -51.93623046875001], + [-58.65278320312498, -52.09921875], + [-59.19584960937496, -52.01767578125], + [-59.06801757812502, -52.17304687500003], + [-59.341503906249955, -52.19599609375], + [-59.395654296874966, -52.308007812499994], + [-59.64873046875002, -52.134375], + [-59.57080078124994, -51.92539062500003], + [-59.05952148437498, -51.685449218749994], + [-59.09663085937498, -51.49140624999998], + [-58.85019531249995, -51.26992187499998] + ] + ] + ] + }, + "properties": { "name": "Falkland Is.", "childNum": 4 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [55.79736328125003, -21.33935546875003], + [55.36269531250005, -21.27363281250004], + [55.23281250000005, -21.05839843749999], + [55.311328125000074, -20.90410156249999], + [55.661914062500074, -20.90625], + [55.8390625000001, -21.13857421874998], + [55.79736328125003, -21.33935546875003] + ] + ], + [ + [ + [45.180273437500006, -12.97675781250004], + [45.069433593750006, -12.895605468750034], + [45.09238281250006, -12.653027343749997], + [45.22314453124997, -12.752148437500026], + [45.180273437500006, -12.97675781250004] + ] + ], + [ + [ + [-51.65253906249998, 4.061279296874972], + [-52.327880859375, 3.18173828125002], + [-52.58300781250003, 2.528906249999977], + [-52.90346679687502, 2.211523437499977], + [-53.76777343749998, 2.354833984375048], + [-54.13007812499998, 2.121044921875026], + [-54.43310546875, 2.207519531250057], + [-54.51508789062498, 2.245458984374963], + [-54.55048828125001, 2.293066406249991], + [-54.59194335937502, 2.313769531250031], + [-54.61625976562499, 2.326757812500006], + [-54.60473632812497, 2.335791015624991], + [-54.56840820312502, 2.342578125000031], + [-54.53593749999999, 2.343310546875003], + [-54.48554687500001, 2.416113281250006], + [-54.402001953124966, 2.46152343750002], + [-54.25673828125002, 2.713720703124977], + [-54.19550781249998, 2.817871093750057], + [-54.17070312499999, 2.993603515624969], + [-54.203125, 3.138183593750028], + [-54.18803710937499, 3.178759765625031], + [-54.063183593749955, 3.353320312499989], + [-54.00957031249993, 3.448535156250017], + [-54.03422851562499, 3.62939453125], + [-54.350732421874994, 4.054101562500023], + [-54.47968749999998, 4.836523437499991], + [-53.91992187499997, 5.768994140624983], + [-52.899316406249966, 5.425048828124986], + [-52.29052734375003, 4.942187500000031], + [-52.324609374999966, 4.770898437500037], + [-52.21997070312494, 4.862792968750014], + [-52.05810546875003, 4.717382812499963], + [-52.00292968749997, 4.352294921875014], + [-51.82753906250002, 4.635693359375026], + [-51.65253906249998, 4.061279296874972] + ] + ], + [ + [ + [-60.826269531250006, 14.494482421874991], + [-61.063720703125, 14.467089843750017], + [-61.01132812499998, 14.601904296875034], + [-61.21333007812501, 14.848583984375011], + [-60.927148437499966, 14.755175781249989], + [-60.826269531250006, 14.494482421874991] + ] + ], + [ + [ + [-61.23046875000003, 15.889941406250074], + [-61.310742187499955, 15.894677734374966], + [-61.25, 16.006298828124983], + [-61.23046875000003, 15.889941406250074] + ] + ], + [ + [ + [-61.58955078125001, 16.006933593750006], + [-61.759423828124966, 16.062060546875045], + [-61.74804687499997, 16.355273437500017], + [-61.55234374999998, 16.270898437499966], + [-61.58955078125001, 16.006933593750006] + ] + ], + [ + [ + [-61.3271484375, 16.230419921874983], + [-61.522167968749955, 16.22802734375003], + [-61.47119140624994, 16.506640625000045], + [-61.17260742187497, 16.25610351562497], + [-61.3271484375, 16.230419921874983] + ] + ], + [ + [ + [9.480371093750023, 42.80541992187503], + [9.550683593750051, 42.12973632812506], + [9.186132812500034, 41.38491210937502], + [8.80751953125008, 41.58837890625], + [8.886816406249977, 41.70068359375003], + [8.621875, 41.93071289062502], + [8.700976562500045, 42.09560546875002], + [8.565625, 42.35771484374996], + [8.81484375000008, 42.60791015625003], + [9.313378906250023, 42.71318359374999], + [9.363183593750051, 43.01738281249996], + [9.480371093750023, 42.80541992187503] + ] + ], + [ + [ + [-1.17832031249992, 45.904052734375], + [-1.213574218750011, 45.81660156250004], + [-1.388671874999972, 46.05039062500006], + [-1.17832031249992, 45.904052734375] + ] + ], + [ + [ + [5.789746093749983, 49.53828125000001], + [5.823437500000011, 49.50507812499998], + [5.9013671875, 49.48974609374997], + [5.928906250000011, 49.47753906249997], + [5.959472656250028, 49.45463867187502], + [6.01142578125004, 49.44545898437502], + [6.074121093750023, 49.45463867187502], + [6.119921875000017, 49.485205078125034], + [6.181054687500051, 49.498925781249966], + [6.344335937500006, 49.45273437499998], + [6.735449218750006, 49.16059570312498], + [7.450585937500051, 49.152197265625034], + [8.134863281250006, 48.97358398437498], + [7.616601562500023, 48.15678710937502], + [7.615625, 47.59272460937504], + [7.343164062499994, 47.43310546875003], + [7.136035156249989, 47.489843750000034], + [6.968359375000034, 47.453222656250034], + [6.900390625000028, 47.39423828125001], + [7.000585937500034, 47.339453125000034], + [7.000585937500034, 47.32250976562506], + [6.978515625000057, 47.30205078124996], + [6.95205078125008, 47.26718750000006], + [6.820703125000051, 47.163183593750006], + [6.688085937500034, 47.05825195312505], + [6.66689453125008, 47.026513671874966], + [6.624804687500017, 47.00434570312498], + [6.45625, 46.948339843750034], + [6.438646763392874, 46.774418247767855], + [6.129687500000045, 46.56699218750006], + [6.118111049107182, 46.447459542410726], + [6.095898437500011, 46.279394531250006], + [5.970019531250045, 46.214697265625034], + [5.971484375000074, 46.151220703125006], + [6.006640625000045, 46.14233398437506], + [6.086621093750068, 46.14702148437502], + [6.19941406250004, 46.19306640624998], + [6.234667968750045, 46.332617187500006], + [6.321875, 46.39370117187502], + [6.428906250000011, 46.43051757812506], + [6.578222656250034, 46.437353515625034], + [6.758105468750017, 46.41577148437497], + [6.772070312500006, 46.16513671874998], + [6.897265625000017, 46.05175781249997], + [6.953710937500063, 46.017138671875045], + [7.00390625, 45.95883789062506], + [7.021093750000034, 45.92578124999997], + [6.790917968750023, 45.740869140624966], + [7.146386718750051, 45.381738281249994], + [7.07832031250004, 45.23994140624998], + [6.634765625000028, 45.06816406249996], + [6.99267578125, 44.82729492187502], + [6.900195312499989, 44.33574218749996], + [7.318554687500068, 44.13798828125002], + [7.637207031250057, 44.16484375], + [7.4931640625, 43.767138671875045], + [6.570214843750023, 43.199072265625034], + [6.115917968750011, 43.07236328124998], + [5.406542968750074, 43.228515625], + [5.05976562500004, 43.44453125000004], + [4.712109375000011, 43.373291015625], + [3.910839843750011, 43.563085937500034], + [3.258886718750063, 43.193212890625006], + [3.051757812500057, 42.915136718750006], + [3.21142578125, 42.43115234375], + [2.032714843750028, 42.353515625], + [1.927929687500068, 42.42631835937499], + [1.859765625000051, 42.457080078125045], + [1.7060546875, 42.50332031250005], + [1.709863281250051, 42.604443359374955], + [1.568164062500045, 42.63500976562506], + [1.501367187500023, 42.64272460937502], + [1.428320312499977, 42.59589843749998], + [0.696875, 42.84511718750005], + [0.631640625000045, 42.689599609374994], + [-0.586425781249943, 42.798974609374966], + [-1.300048828124943, 43.10097656250002], + [-1.460839843749937, 43.05175781250006], + [-1.410693359374932, 43.240087890625034], + [-1.794042968749949, 43.407324218750006], + [-1.484863281249943, 43.56376953124999], + [-1.245507812499937, 44.55986328124999], + [-1.07695312499996, 44.68984375], + [-1.152880859374989, 44.764013671875006], + [-1.245214843749977, 44.66669921874998], + [-1.081005859374983, 45.532421874999955], + [-0.548486328124966, 45.00058593750006], + [-0.790771484375028, 45.46801757812497], + [-1.195996093749983, 45.714453125], + [-1.03173828125, 45.741064453125006], + [-1.14628906249996, 46.311376953125034], + [-1.786523437499937, 46.51484375000001], + [-2.059375, 46.81030273437497], + [-2.01889648437492, 47.03764648437502], + [-2.197070312499989, 47.16293945312506], + [-2.027587890625028, 47.27358398437502], + [-1.742529296874949, 47.21596679687502], + [-1.97539062499996, 47.31069335937505], + [-2.503125, 47.31206054687496], + [-2.427685546874983, 47.47089843749998], + [-2.770312499999989, 47.513867187499955], + [-2.787207031249949, 47.62553710937496], + [-4.312109374999949, 47.82290039062502], + [-4.678808593749949, 48.03950195312501], + [-4.32944335937492, 48.169970703125045], + [-4.577148437499943, 48.2900390625], + [-4.241406249999926, 48.30366210937501], + [-4.719384765624966, 48.363134765625034], + [-4.7625, 48.45024414062502], + [-4.531201171874983, 48.61997070312506], + [-3.231445312499972, 48.84082031250003], + [-2.692333984374983, 48.53681640624998], + [-2.446191406249937, 48.64829101562506], + [-2.00371093749996, 48.58208007812499], + [-1.905712890624955, 48.69711914062506], + [-1.376464843749972, 48.65258789062503], + [-1.565478515624932, 48.805517578125034], + [-1.583105468749977, 49.20239257812506], + [-1.856445312499972, 49.68378906249998], + [-1.258642578124949, 49.68017578125006], + [-1.138525390624977, 49.38789062500001], + [-0.163476562499937, 49.296777343749994], + [0.41689453125008, 49.448388671874994], + [0.129394531250028, 49.508447265624966], + [0.186718749999983, 49.703027343749994], + [1.245507812500051, 49.99824218750001], + [1.5927734375, 50.25219726562506], + [1.672265625000023, 50.885009765625], + [2.52490234375, 51.097119140624955], + [2.759375, 50.750634765624994], + [3.10683593750008, 50.779443359374994], + [3.27333984375008, 50.53154296875002], + [3.595410156250068, 50.47734374999999], + [3.689355468750023, 50.30605468750002], + [4.174609375000017, 50.24648437500005], + [4.149316406250023, 49.971582031249994], + [4.545019531250063, 49.96025390624999], + [4.818652343750045, 50.153173828125034], + [4.867578125000051, 49.78813476562502], + [5.50732421875, 49.51088867187502], + [5.789746093749983, 49.53828125000001] + ] + ] + ] + }, + "properties": { + "name": "France", + "childNum": 10, + "cp": [2.8719426, 46.8222422] + } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [-7.186865234374949, 62.139306640624966], + [-7.116796874999977, 62.046826171874955], + [-7.379101562499926, 62.07480468749998], + [-7.186865234374949, 62.139306640624966] + ] + ], + [ + [ + [-6.631054687499955, 62.22788085937498], + [-6.655810546874932, 62.09360351562498], + [-6.840527343749983, 62.119287109374994], + [-6.725195312499949, 61.95146484374999], + [-7.17216796874996, 62.28559570312501], + [-6.631054687499955, 62.22788085937498] + ] + ], + [ + [ + [-6.406054687499932, 62.258642578125034], + [-6.544140624999926, 62.20561523437499], + [-6.554589843749994, 62.35566406250001], + [-6.406054687499932, 62.258642578125034] + ] + ] + ] + }, + "properties": { "name": "Faeroe Is.", "childNum": 3 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [158.31484375, 6.813671875], + [158.18339843750002, 6.801269531250057], + [158.13476562499997, 6.944824218749986], + [158.29462890625004, 6.951074218750023], + [158.31484375, 6.813671875] + ] + ], + [ + [ + [138.14267578125006, 9.50068359375004], + [138.06708984375004, 9.419042968750006], + [138.18583984375007, 9.593310546874989], + [138.14267578125006, 9.50068359375004] + ] + ] + ] + }, + "properties": { "name": "Micronesia", "childNum": 2 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [13.293554687500006, 2.161572265624997], + [13.172167968750017, 1.78857421875], + [13.21630859375, 1.2484375], + [13.851367187500017, 1.41875], + [14.180859375000011, 1.370214843749991], + [14.429882812500011, 0.901464843749991], + [14.32421875, 0.62421875], + [13.949609375000023, 0.353808593749989], + [13.860058593750011, -0.203320312500011], + [14.47412109375, -0.573437500000011], + [14.383984375000011, -1.890039062500009], + [14.162890625000017, -2.217578125], + [14.199804687500006, -2.354199218750011], + [13.993847656250011, -2.490625], + [13.886914062500011, -2.465429687500006], + [13.733789062500023, -2.138476562500003], + [13.464941406250006, -2.395410156250009], + [12.991992187500017, -2.313378906250009], + [12.793554687500006, -1.931835937500011], + [12.590429687500006, -1.826855468750011], + [12.43212890625, -1.928906250000011], + [12.446386718750006, -2.329980468750009], + [12.064453125, -2.41259765625], + [11.60546875, -2.342578125], + [11.537792968750011, -2.83671875], + [11.760156250000023, -2.983105468750011], + [11.715429687500006, -3.176953125000011], + [11.934179687500006, -3.318554687500011], + [11.8798828125, -3.665917968750009], + [11.685742187500011, -3.68203125], + [11.504296875000023, -3.5203125], + [11.234472656250006, -3.690820312500009], + [11.130175781250017, -3.916308593750003], + [10.34765625, -3.013085937500009], + [9.722070312500023, -2.467578125], + [10.06201171875, -2.549902343750006], + [9.624609375, -2.367089843750009], + [9.298925781250006, -1.903027343750011], + [9.483203125000017, -1.894628906250006], + [9.265625, -1.825097656250009], + [9.036328125000011, -1.308886718750003], + [9.31884765625, -1.632031250000011], + [9.501074218750006, -1.55517578125], + [9.295800781250023, -1.515234375], + [9.3466796875, -1.325], + [9.203808593750011, -1.382421875], + [9.064648437500011, -1.29833984375], + [8.703125, -0.591015625000011], + [8.946386718750006, -0.688769531250003], + [9.296679687500017, -0.351269531250011], + [9.354882812500023, 0.343603515624991], + [9.468164062500023, 0.15976562499999], + [9.796777343750023, 0.044238281249989], + [10.00146484375, 0.194970703124994], + [9.546484375, 0.295947265624989], + [9.324804687500006, 0.552099609374991], + [9.495312500000011, 0.664843749999989], + [9.617968750000017, 0.576513671874991], + [9.5908203125, 1.031982421875], + [9.636132812500023, 1.046679687499989], + [9.676464843750011, 1.07470703125], + [9.70458984375, 1.079980468749994], + [9.760546875000017, 1.07470703125], + [9.788671875, 1.025683593749989], + [9.803906250000011, 0.998730468749997], + [9.90673828125, 0.960107421874994], + [11.335351562500023, 0.999707031249997], + [11.332324218750017, 1.528369140624989], + [11.328710937500006, 2.167431640624997], + [11.348437500000017, 2.299707031249994], + [11.558984375000023, 2.302197265624997], + [13.2203125, 2.256445312499991], + [13.293554687500006, 2.161572265624997] + ] + ] + }, + "properties": { "name": "Gabon", "childNum": 1 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [-1.065576171874966, 50.69023437500002], + [-1.25146484375, 50.58881835937498], + [-1.563427734374955, 50.666113281250006], + [-1.31279296874996, 50.77348632812502], + [-1.065576171874966, 50.69023437500002] + ] + ], + [ + [ + [-4.196777343749972, 53.321435546874966], + [-4.04936523437496, 53.30576171874998], + [-4.373046875, 53.13417968750002], + [-4.56787109375, 53.386474609375], + [-4.315087890625023, 53.41723632812503], + [-4.196777343749972, 53.321435546874966] + ] + ], + [ + [ + [-6.218017578125, 54.08872070312506], + [-6.649804687499937, 54.05864257812496], + [-7.007714843749937, 54.40668945312501], + [-7.324511718750017, 54.13344726562502], + [-7.606542968750006, 54.14384765625002], + [-8.118261718749977, 54.41425781250004], + [-7.75439453125, 54.59492187499998], + [-7.910595703124955, 54.698339843750006], + [-7.55039062499992, 54.767968749999966], + [-7.218652343749937, 55.09199218749998], + [-6.475048828124955, 55.24101562499999], + [-6.035791015624994, 55.14453125000003], + [-5.71684570312496, 54.817480468750034], + [-5.878613281249955, 54.64130859375001], + [-5.582519531249943, 54.66342773437498], + [-5.470410156249926, 54.500195312499955], + [-5.671093749999955, 54.54975585937501], + [-5.60678710937492, 54.272558593750034], + [-6.019042968749972, 54.05126953124997], + [-6.218017578125, 54.08872070312506] + ] + ], + [ + [ + [-5.105419921875011, 55.448828125000034], + [-5.331494140624955, 55.481054687500034], + [-5.318115234375, 55.709179687499955], + [-5.105419921875011, 55.448828125000034] + ] + ], + [ + [ + [-6.128906249999972, 55.93056640625002], + [-6.055322265624994, 55.69531249999997], + [-6.305078124999966, 55.60693359375], + [-6.286425781249989, 55.77250976562499], + [-6.491357421874994, 55.697314453125045], + [-6.462841796874955, 55.808251953124994], + [-6.128906249999972, 55.93056640625002] + ] + ], + [ + [ + [-5.970068359374949, 55.814550781250034], + [-6.071972656250011, 55.893115234375045], + [-5.72514648437496, 56.118554687499966], + [-5.970068359374949, 55.814550781250034] + ] + ], + [ + [ + [-5.77788085937496, 56.344335937500034], + [-6.313427734374983, 56.29365234375001], + [-6.138867187499955, 56.490625], + [-6.286328124999983, 56.61186523437502], + [-6.102734374999955, 56.645654296874966], + [-5.760839843749949, 56.49067382812501], + [-5.77788085937496, 56.344335937500034] + ] + ], + [ + [ + [-7.249853515624977, 57.115332031250006], + [-7.410546874999937, 57.38110351562506], + [-7.26713867187496, 57.37177734375001], + [-7.249853515624977, 57.115332031250006] + ] + ], + [ + [ + [-6.144726562499983, 57.50498046874998], + [-6.135546874999989, 57.31425781250002], + [-5.672460937499977, 57.252685546875], + [-5.94907226562492, 57.045166015625], + [-6.034375, 57.20122070312499], + [-6.322705078124926, 57.20249023437498], + [-6.761132812499994, 57.4423828125], + [-6.305957031249989, 57.67197265624998], + [-6.144726562499983, 57.50498046874998] + ] + ], + [ + [ + [-7.205566406250028, 57.682958984375006], + [-7.182617187499972, 57.53330078125006], + [-7.514746093749949, 57.60195312500002], + [-7.205566406250028, 57.682958984375006] + ] + ], + [ + [ + [-6.198681640624983, 58.36328125000003], + [-6.554589843749994, 58.092871093750006], + [-6.425195312499937, 58.02128906249999], + [-6.983105468749983, 57.75], + [-7.083447265624926, 57.81376953124999], + [-6.856835937499937, 57.92353515624998], + [-7.085253906249932, 58.18217773437499], + [-6.726464843749937, 58.189404296874955], + [-6.776464843750006, 58.30151367187497], + [-6.237451171874966, 58.50283203125005], + [-6.198681640624983, 58.36328125000003] + ] + ], + [ + [ + [-3.109667968749932, 58.515478515625034], + [-3.212353515624983, 58.32124023437501], + [-3.99003906249996, 57.95903320312502], + [-4.035595703124926, 57.85200195312498], + [-3.857128906249983, 57.81855468750001], + [-4.134521484375, 57.57773437500006], + [-3.402783203124955, 57.708251953125], + [-2.074072265624977, 57.70239257812506], + [-1.780664062499994, 57.474023437499966], + [-2.592675781249937, 56.56157226562499], + [-3.309960937499966, 56.36347656250004], + [-2.885156249999937, 56.397509765625045], + [-2.674267578124955, 56.25341796875], + [-3.362255859374955, 56.02763671875002], + [-3.789062499999972, 56.09521484375], + [-3.048730468749937, 55.951953125000045], + [-2.599316406249955, 56.02729492187501], + [-2.14707031249992, 55.90297851562502], + [-1.655371093749949, 55.57036132812502], + [-1.232421874999943, 54.703710937500034], + [-0.084375, 54.118066406249994], + [-0.20556640625, 54.021728515625], + [0.115332031250006, 53.609277343749994], + [-0.270019531249972, 53.73676757812504], + [-0.659912109375, 53.72402343750002], + [-0.293701171875, 53.69233398437504], + [0.270996093750028, 53.33549804687499], + [0.355761718750045, 53.15996093750002], + [0.0458984375, 52.90561523437498], + [0.279785156250028, 52.80869140625006], + [0.55878906250004, 52.96694335937505], + [1.05556640625008, 52.95898437500003], + [1.656738281249972, 52.753710937500045], + [1.74658203125, 52.46899414062503], + [1.59140625, 52.11977539062502], + [1.232421875000057, 51.97124023437496], + [1.188476562500057, 51.803369140624966], + [0.752246093750017, 51.729589843750034], + [0.890917968750017, 51.571435546874966], + [0.42451171875004, 51.465625], + [1.414941406250023, 51.36328125], + [1.397558593750034, 51.18203125000002], + [0.960156250000011, 50.92587890624998], + [0.299707031249994, 50.775976562500006], + [-0.785253906249949, 50.76542968749999], + [-1.416455078124955, 50.896875], + [-1.334472656249943, 50.82080078124997], + [-1.516748046874937, 50.747460937499966], + [-2.031054687499932, 50.72539062499999], + [-2.035839843749926, 50.603076171875045], + [-2.999414062499937, 50.71660156249999], + [-3.40458984374996, 50.63242187499998], + [-3.679785156250006, 50.239941406249955], + [-4.194580078124972, 50.39331054687503], + [-4.727978515624926, 50.29047851562504], + [-5.11850585937492, 50.038330078125], + [-5.622119140624932, 50.05068359375002], + [-4.188183593749926, 51.18852539062502], + [-3.135986328124972, 51.20502929687501], + [-2.433056640624926, 51.74072265625], + [-3.293115234374994, 51.390429687500045], + [-3.890771484374994, 51.591650390625006], + [-4.234570312499955, 51.56909179687503], + [-4.091015624999926, 51.65991210937506], + [-4.38627929687496, 51.74106445312506], + [-4.902294921874926, 51.626269531250045], + [-5.168359374999937, 51.74072265625], + [-5.183349609374972, 51.94965820312501], + [-4.217724609374983, 52.277441406250006], + [-3.980322265624949, 52.54174804687503], + [-4.101464843750023, 52.915478515624955], + [-4.683056640624926, 52.80615234374997], + [-4.268554687499943, 53.14453125], + [-3.427734374999972, 53.34067382812498], + [-3.097558593749937, 53.260302734375045], + [-3.064746093749932, 53.426855468750034], + [-2.74951171875, 53.310205078124994], + [-3.064599609374994, 53.512841796874966], + [-2.84648437499996, 54.135302734375045], + [-3.165966796874955, 54.12792968750006], + [-3.56938476562496, 54.46757812499996], + [-3.464599609374943, 54.77309570312505], + [-3.036230468749977, 54.95307617187501], + [-3.550439453124937, 54.94741210937502], + [-3.957910156249994, 54.780957031249955], + [-4.818066406249983, 54.84614257812501], + [-4.911230468749949, 54.68945312500006], + [-5.032324218749949, 54.76137695312505], + [-5.172705078124949, 54.98588867187496], + [-4.676757812499972, 55.50131835937498], + [-4.871679687499977, 55.87392578125005], + [-4.58408203124992, 55.93867187500001], + [-4.844091796874949, 56.05117187499999], + [-4.80029296875, 56.158349609374994], + [-5.228222656249983, 55.886328125], + [-5.084326171874977, 56.197460937499955], + [-5.41044921874996, 55.995361328125], + [-5.55644531249996, 55.389599609374955], + [-5.730664062499926, 55.33413085937502], + [-5.504492187499949, 55.80239257812502], + [-5.609570312499955, 56.055273437500034], + [-5.188378906249937, 56.75805664062503], + [-5.652441406249977, 56.531982421875], + [-6.133691406249966, 56.706689453124966], + [-5.730615234374994, 56.853076171875045], + [-5.86142578124992, 56.902685546875006], + [-5.561914062499994, 57.23271484375002], + [-5.794921874999972, 57.37880859375002], + [-5.581787109374972, 57.546777343749966], + [-5.744921874999989, 57.668310546875034], + [-5.608349609374955, 57.88134765625], + [-5.157226562499972, 57.88134765625], + [-5.413183593750006, 58.06972656250002], + [-5.338281250000023, 58.23872070312498], + [-5.008300781250028, 58.262646484374955], + [-5.016748046874966, 58.566552734374966], + [-4.433251953124937, 58.51284179687505], + [-3.25913085937492, 58.65], + [-3.053076171874949, 58.63481445312502], + [-3.109667968749932, 58.515478515625034] + ] + ], + [ + [ + [-3.057421874999932, 59.02963867187498], + [-2.793017578124989, 58.906933593749955], + [-3.331640624999949, 58.97124023437499], + [-3.31035156249996, 59.13081054687498], + [-3.057421874999932, 59.02963867187498] + ] + ], + [ + [ + [-1.30810546875, 60.5375], + [-1.052441406249955, 60.44448242187502], + [-1.299462890624994, 59.87866210937503], + [-1.290917968749937, 60.153466796874966], + [-1.663769531249983, 60.282519531250074], + [-1.374609374999949, 60.33291015625002], + [-1.571777343749972, 60.494433593750074], + [-1.363964843750011, 60.60957031249998], + [-1.30810546875, 60.5375] + ] + ] + ] + }, + "properties": { + "name": "United Kingdom", + "childNum": 14, + "cp": [-2.5830348, 54.4598409] + } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [46.30546875000002, 41.507714843749994], + [46.61894531250002, 41.34375], + [46.67255859375001, 41.28681640625], + [46.66240234375002, 41.245507812499994], + [46.62636718750002, 41.15966796875], + [46.534375, 41.08857421875], + [46.43095703125002, 41.077050781249994], + [46.086523437500006, 41.183837890625], + [45.28095703125001, 41.449560546875], + [45.21718750000002, 41.423193359375], + [45.00136718750002, 41.290966796875], + [44.97587890625002, 41.277490234374994], + [44.81132812500002, 41.259375], + [44.077246093750006, 41.182519531249994], + [43.43339843750002, 41.155517578125], + [43.20546875000002, 41.199169921875], + [43.15283203125, 41.23642578125], + [43.14101562500002, 41.26484375], + [43.17128906250002, 41.287939453125], + [43.149023437500006, 41.30712890625], + [43.05712890625, 41.352832031249996], + [42.90673828125, 41.466845703124996], + [42.82167968750002, 41.4923828125], + [42.78789062500002, 41.563720703125], + [42.75410156250001, 41.57890625], + [42.68242187500002, 41.585742187499996], + [42.60683593750002, 41.57880859375], + [42.590429687500006, 41.57070312499999], + [42.5673828125, 41.55927734375], + [42.46640625, 41.43984375], + [41.92578125, 41.495654296874996], + [41.82353515625002, 41.432373046875], + [41.779394531250006, 41.44052734375], + [41.701757812500006, 41.471582031249994], + [41.57656250000002, 41.497314453125], + [41.51005859375002, 41.517480468749994], + [41.701757812500006, 41.705419921875], + [41.76298828125002, 41.970019531249996], + [41.48876953125, 42.659326171874994], + [40.83662109375001, 43.0634765625], + [40.46210937500001, 43.145703125], + [39.97832031250002, 43.419824218749994], + [40.02373046875002, 43.48486328125], + [40.084570312500006, 43.553125], + [40.648046875, 43.53388671875], + [40.941992187500006, 43.41806640625], + [41.083105468750006, 43.374462890625], + [41.35820312500002, 43.333398437499994], + [41.46074218750002, 43.276318359375], + [41.58056640625, 43.21923828125], + [42.76064453125002, 43.169580078124994], + [42.99160156250002, 43.09150390625], + [43.00019531250001, 43.049658203125], + [43.08916015625002, 42.9890625], + [43.55781250000001, 42.844482421875], + [43.623046875, 42.80771484375], + [43.78261718750002, 42.747021484375], + [43.79873046875002, 42.727783203125], + [43.79541015625, 42.702978515625], + [43.74990234375002, 42.657519531249996], + [43.738378906250006, 42.616992187499996], + [43.759863281250006, 42.59384765625], + [43.82597656250002, 42.571533203125], + [43.95742187500002, 42.566552734374994], + [44.00468750000002, 42.595605468749994], + [44.10273437500001, 42.616357421874994], + [44.32949218750002, 42.70351562499999], + [44.505859375, 42.7486328125], + [44.77109375, 42.616796875], + [44.85048828125002, 42.746826171875], + [44.87099609375002, 42.756396484374996], + [44.943359375, 42.730273437499996], + [45.07158203125002, 42.694140625], + [45.160253906250006, 42.675], + [45.34375, 42.52978515625], + [45.56289062500002, 42.5357421875], + [45.70527343750001, 42.498095703124996], + [45.7275390625, 42.475048828125], + [45.63427734375, 42.234716796875], + [45.63857421875002, 42.205078125], + [46.21269531250002, 41.989892578124994], + [46.42988281250001, 41.890966796875], + [46.18427734375001, 41.7021484375], + [46.30546875000002, 41.507714843749994] + ] + ] + }, + "properties": { "name": "Georgia", "childNum": 1 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [-0.068603515625, 11.115625], + [0.009423828125023, 11.02099609375], + [-0.08632812499999, 10.673046875], + [0.380859375, 10.291845703124991], + [0.264550781250023, 9.644726562499997], + [0.342578125000017, 9.604150390624994], + [0.2333984375, 9.463525390624994], + [0.525683593750017, 9.398486328124989], + [0.48876953125, 8.851464843749994], + [0.37255859375, 8.75927734375], + [0.686328125000017, 8.354882812499994], + [0.5, 7.546875], + [0.634765625, 7.353662109374994], + [0.525585937500011, 6.850927734374991], + [0.736914062500006, 6.452587890624997], + [1.187207031250011, 6.089404296874989], + [0.94970703125, 5.810253906249997], + [0.259667968750023, 5.75732421875], + [-2.001855468749994, 4.762451171875], + [-3.114013671875, 5.088671874999989], + [-2.815673828125, 5.153027343749997], + [-2.754980468749977, 5.432519531249994], + [-2.793652343749983, 5.60009765625], + [-2.998291015625, 5.71132812499999], + [-3.227148437499977, 6.749121093749991], + [-2.959082031249977, 7.454541015624997], + [-2.789746093749983, 7.931933593749989], + [-2.668847656249994, 8.022216796875], + [-2.613378906249977, 8.046679687499989], + [-2.600976562499994, 8.082226562499997], + [-2.619970703124977, 8.12109375], + [-2.61171875, 8.147558593749991], + [-2.538281249999983, 8.171630859375], + [-2.505859375, 8.208740234375], + [-2.600390624999989, 8.800439453124994], + [-2.649218749999989, 8.956591796874989], + [-2.689892578124983, 9.025097656249997], + [-2.746923828124977, 9.045117187499997], + [-2.705761718749983, 9.351367187499989], + [-2.695849609374989, 9.481347656249994], + [-2.706201171874994, 9.533935546875], + [-2.765966796874977, 9.658056640624991], + [-2.780517578125, 9.745849609375], + [-2.791162109374994, 10.432421874999989], + [-2.914892578124977, 10.592333984374989], + [-2.829931640624977, 10.998388671874991], + [-1.04248046875, 11.010058593749989], + [-0.627148437499983, 10.927392578124994], + [-0.299462890624994, 11.166894531249994], + [-0.068603515625, 11.115625] + ] + ] + }, + "properties": { "name": "Ghana", "childNum": 1 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [-11.389404296875, 12.404394531249991], + [-11.502197265625, 12.198632812499994], + [-11.30517578125, 12.015429687499989], + [-10.933203124999977, 12.205175781249991], + [-10.709228515625, 11.898730468749989], + [-10.274853515624983, 12.212646484375], + [-9.754003906249977, 12.029931640624994], + [-9.358105468749983, 12.255419921874989], + [-9.395361328124977, 12.464648437499989], + [-9.043066406249977, 12.40234375], + [-8.818310546874983, 11.922509765624994], + [-8.822021484375, 11.673242187499994], + [-8.398535156249977, 11.366552734374991], + [-8.666699218749983, 11.009472656249997], + [-8.33740234375, 10.990625], + [-8.266650390624989, 10.485986328124994], + [-8.007275390624983, 10.321875], + [-7.990625, 10.1625], + [-8.155175781249994, 9.973193359374989], + [-8.136962890625, 9.49570312499999], + [-7.896191406249983, 9.415869140624991], + [-7.918066406249977, 9.188525390624989], + [-7.839404296874989, 9.151611328125], + [-7.7998046875, 9.115039062499989], + [-7.777978515624994, 9.080859374999989], + [-7.902099609375, 9.01708984375], + [-7.938183593749983, 8.979785156249989], + [-7.950976562499989, 8.786816406249997], + [-7.719580078124977, 8.643017578124997], + [-7.696093749999989, 8.375585937499991], + [-7.823583984374977, 8.467675781249994], + [-7.953125, 8.477734375], + [-8.236962890624994, 8.455664062499991], + [-8.244140625, 8.407910156249997], + [-8.256103515625, 8.253710937499989], + [-8.217138671874977, 8.219677734374997], + [-8.140625, 8.181445312499989], + [-8.048583984375, 8.169726562499989], + [-8.009863281249977, 8.07851562499999], + [-8.126855468749994, 7.867724609374989], + [-8.115429687499983, 7.7607421875], + [-8.205957031249994, 7.59023437499999], + [-8.231884765624983, 7.556738281249991], + [-8.429980468749989, 7.601855468749989], + [-8.486425781249977, 7.558496093749994], + [-8.659765624999977, 7.688378906249994], + [-8.8896484375, 7.2626953125], + [-9.11757812499999, 7.215917968749991], + [-9.463818359374983, 7.415869140624991], + [-9.369140625, 7.703808593749997], + [-9.518261718749983, 8.34609375], + [-9.781982421875, 8.537695312499991], + [-10.064355468749994, 8.429882812499997], + [-10.147412109374983, 8.519726562499997], + [-10.233056640624994, 8.488818359374989], + [-10.283203125, 8.485156249999989], + [-10.360058593749983, 8.495507812499994], + [-10.394433593749994, 8.48095703125], + [-10.496435546874977, 8.362109374999989], + [-10.557714843749977, 8.315673828125], + [-10.686962890624983, 8.321679687499994], + [-10.712109374999983, 8.335253906249989], + [-10.677343749999977, 8.400585937499997], + [-10.500537109374989, 8.687548828124989], + [-10.615966796875, 9.059179687499991], + [-10.726855468749989, 9.081689453124994], + [-10.747021484374983, 9.095263671874989], + [-10.749951171874983, 9.122363281249989], + [-10.687646484374994, 9.261132812499994], + [-10.682714843749977, 9.289355468749989], + [-10.758593749999989, 9.385351562499991], + [-11.047460937499977, 9.786328125], + [-11.180859374999983, 9.925341796874989], + [-11.205664062499977, 9.977734375], + [-11.273632812499983, 9.996533203124997], + [-11.911083984374983, 9.993017578124991], + [-12.142333984375, 9.87539062499999], + [-12.427978515625, 9.898144531249997], + [-12.557861328125, 9.704980468749994], + [-12.755859375, 9.373583984374989], + [-12.958789062499989, 9.263330078124994], + [-13.077294921874994, 9.069628906249989], + [-13.292675781249983, 9.04921875], + [-13.436279296875, 9.4203125], + [-13.691357421874983, 9.535791015624994], + [-13.689794921874977, 9.927783203124989], + [-13.820117187499989, 9.88720703125], + [-14.045019531249977, 10.141259765624994], + [-14.426904296874994, 10.248339843749989], + [-14.609570312499983, 10.549853515624989], + [-14.593505859375, 10.766699218749991], + [-14.677343749999977, 10.68896484375], + [-14.775927734374989, 10.931640625], + [-14.88671875, 10.968066406249989], + [-14.975, 10.803417968749997], + [-15.051220703124983, 10.834570312499991], + [-15.043017578124989, 10.940136718749997], + [-14.9990234375, 10.9921875], + [-14.944433593749977, 11.072167968749994], + [-14.779296875, 11.405517578125], + [-14.720263671874989, 11.48193359375], + [-14.682958984374977, 11.508496093749997], + [-14.604785156249989, 11.511621093749994], + [-14.452441406249989, 11.556201171874989], + [-14.327832031249983, 11.629785156249994], + [-14.265576171874983, 11.659912109375], + [-14.122314453125, 11.65195312499999], + [-13.953222656249977, 11.664599609374989], + [-13.732763671874977, 11.736035156249997], + [-13.730664062499983, 11.959863281249994], + [-13.737988281249983, 12.009667968749994], + [-13.816308593749994, 12.054492187499989], + [-13.948876953124994, 12.178173828124997], + [-13.8875, 12.246875], + [-13.759765625, 12.262353515624994], + [-13.673535156249983, 12.478515625], + [-13.732617187499983, 12.592822265624989], + [-13.729248046875, 12.673925781249991], + [-13.082910156249994, 12.633544921875], + [-13.061279296875, 12.489990234375], + [-12.930712890624989, 12.532275390624989], + [-12.399072265624994, 12.340087890625], + [-11.389404296875, 12.404394531249991] + ] + ] + }, + "properties": { "name": "Guinea", "childNum": 1 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [-16.763330078124994, 13.064160156249997], + [-16.824804687499977, 13.341064453125], + [-16.669335937499994, 13.475], + [-16.41337890624999, 13.269726562499997], + [-15.427490234375, 13.46835937499999], + [-16.135449218749983, 13.4482421875], + [-16.351806640625, 13.34335937499999], + [-16.56230468749999, 13.587304687499994], + [-15.509667968749994, 13.586230468750003], + [-15.426855468749977, 13.727001953124997], + [-15.108349609374983, 13.81210937499999], + [-14.405468749999983, 13.503710937500003], + [-13.977392578124977, 13.54345703125], + [-13.826708984374989, 13.4078125], + [-14.246777343749983, 13.23583984375], + [-15.151123046875, 13.556494140624991], + [-15.286230468749977, 13.39599609375], + [-15.814404296874983, 13.325146484374997], + [-15.834277343749989, 13.156445312499997], + [-16.648779296874977, 13.154150390624991], + [-16.763330078124994, 13.064160156249997] + ] + ] + }, + "properties": { "name": "Gambia", "childNum": 1 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [-15.895898437499966, 11.082470703124969], + [-15.963964843749977, 11.05898437499998], + [-15.937695312499955, 11.192773437499966], + [-15.895898437499966, 11.082470703124969] + ] + ], + [ + [ + [-16.11450195312503, 11.059423828124977], + [-16.236425781249977, 11.113427734374966], + [-16.06733398437501, 11.197216796874983], + [-16.11450195312503, 11.059423828124977] + ] + ], + [ + [ + [-15.901806640624926, 11.4658203125], + [-16.02319335937497, 11.477148437499991], + [-15.964550781249926, 11.59829101562498], + [-15.901806640624926, 11.4658203125] + ] + ], + [ + [ + [-15.986425781249949, 11.882031249999969], + [-16.038330078124943, 11.759716796875011], + [-16.15244140624992, 11.876806640624963], + [-15.986425781249949, 11.882031249999969] + ] + ], + [ + [ + [-13.759765625, 12.262353515624994], + [-13.8875, 12.246875], + [-13.948876953124966, 12.178173828124997], + [-13.737988281250011, 12.009667968750037], + [-13.730664062499926, 11.959863281250009], + [-13.73276367187492, 11.736035156249983], + [-13.953222656249977, 11.664599609374989], + [-14.265576171874926, 11.659912109375014], + [-14.327832031250011, 11.629785156250009], + [-14.452441406249989, 11.556201171875017], + [-14.604785156249932, 11.511621093749994], + [-14.682958984374949, 11.508496093749983], + [-14.720263671875017, 11.481933593749986], + [-14.779296874999972, 11.405517578125057], + [-14.944433593749949, 11.072167968749994], + [-14.999023437499972, 10.992187500000043], + [-15.04301757812496, 10.940136718750011], + [-15.09375, 11.011035156249974], + [-15.054589843749994, 11.141943359375006], + [-15.222119140624926, 11.030908203125037], + [-15.216699218749994, 11.15625], + [-15.39311523437496, 11.217236328124983], + [-15.354687499999955, 11.396337890624963], + [-15.479492187499972, 11.410302734374966], + [-15.072656249999937, 11.597802734374966], + [-15.230371093750023, 11.686767578124972], + [-15.412988281249994, 11.615234374999972], + [-15.501904296875011, 11.723779296874966], + [-15.467187499999937, 11.842822265624974], + [-15.078271484374937, 11.968994140625014], + [-15.941748046875006, 11.786621093749986], + [-15.92021484374996, 11.93779296874996], + [-16.138427734375, 11.917285156250045], + [-16.32807617187501, 12.051611328124963], + [-16.244580078124955, 12.237109375], + [-16.43681640624996, 12.204150390625045], + [-16.711816406249937, 12.354833984375006], + [-16.656933593749955, 12.364355468749991], + [-16.52133789062495, 12.348632812499986], + [-16.41630859374996, 12.367675781250057], + [-16.24150390624996, 12.443310546875011], + [-16.144189453124937, 12.457421875000037], + [-15.839550781249955, 12.437890624999966], + [-15.57480468749992, 12.490380859375009], + [-15.19609375, 12.679931640624986], + [-14.3492187499999, 12.67641601562498], + [-14.064843749999966, 12.675292968750014], + [-13.729248046875, 12.673925781250006], + [-13.732617187499983, 12.592822265625003], + [-13.673535156249926, 12.478515624999986], + [-13.759765625, 12.262353515624994] + ] + ] + ] + }, + "properties": { "name": "Guinea-Bissau", "childNum": 5 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [11.332324218750017, 1.528369140624989], + [11.335351562500023, 0.999707031250011], + [9.906738281250028, 0.960107421875037], + [9.80390625000004, 0.998730468749997], + [9.788671875000034, 1.025683593749974], + [9.760546874999989, 1.074707031250014], + [9.704589843750057, 1.079980468750023], + [9.676464843750011, 1.074707031250014], + [9.636132812500051, 1.046679687499989], + [9.590820312500057, 1.031982421875014], + [9.599414062500045, 1.054443359374972], + [9.509863281250006, 1.114794921875017], + [9.385937500000068, 1.13925781250002], + [9.807031250000051, 1.927490234375028], + [9.77968750000008, 2.068212890625006], + [9.800781250000028, 2.304443359375], + [9.826171875000057, 2.297802734374969], + [9.8369140625, 2.242382812500054], + [9.870117187500028, 2.21328125], + [9.979882812499994, 2.167773437500045], + [10.790917968750023, 2.167578125], + [11.096582031250051, 2.167480468749986], + [11.328710937500006, 2.167431640624969], + [11.332324218750017, 1.528369140624989] + ] + ], + [ + [ + [8.735742187500023, 3.758300781249972], + [8.910058593750023, 3.758203125000051], + [8.946093750000074, 3.627539062499977], + [8.704003906250051, 3.223632812500028], + [8.474902343749989, 3.264648437500043], + [8.464648437500045, 3.450585937499994], + [8.735742187500023, 3.758300781249972] + ] + ] + ] + }, + "properties": { "name": "Eq. Guinea", "childNum": 2 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [23.852246093749983, 35.53544921874999], + [24.166015625000057, 35.59521484375], + [24.108984374999977, 35.49580078124998], + [24.35400390625, 35.359472656250034], + [25.73017578125004, 35.34858398437501], + [25.791308593750074, 35.122851562500045], + [26.32021484375008, 35.315136718749955], + [26.165625, 35.018603515625045], + [24.79980468750003, 34.93447265625002], + [24.70888671875008, 35.08906250000001], + [24.463671875000045, 35.160351562499955], + [23.59277343749997, 35.257226562499966], + [23.56982421875, 35.534765625000034], + [23.67265624999999, 35.51391601562506], + [23.736914062500034, 35.65551757812503], + [23.852246093749983, 35.53544921874999] + ] + ], + [ + [ + [27.17607421874999, 35.46528320312498], + [27.070703125000023, 35.59775390624998], + [27.22314453125, 35.820458984374966], + [27.17607421874999, 35.46528320312498] + ] + ], + [ + [ + [23.053808593750034, 36.18979492187498], + [22.91083984375004, 36.220996093750045], + [22.950488281250045, 36.38393554687502], + [23.053808593750034, 36.18979492187498] + ] + ], + [ + [ + [27.84277343750003, 35.929296875000034], + [27.71552734375004, 35.95732421874996], + [27.71630859375003, 36.17158203125001], + [28.23183593750005, 36.43364257812502], + [28.087792968750023, 36.06533203125002], + [27.84277343750003, 35.929296875000034] + ] + ], + [ + [ + [25.48242187500003, 36.39262695312502], + [25.37050781250005, 36.35893554687499], + [25.408984375000074, 36.473730468750006], + [25.48242187500003, 36.39262695312502] + ] + ], + [ + [ + [26.46064453125001, 36.58540039062501], + [26.270019531250057, 36.54692382812499], + [26.370019531250023, 36.63857421875002], + [26.46064453125001, 36.58540039062501] + ] + ], + [ + [ + [26.94960937500005, 36.72709960937502], + [27.214941406250006, 36.89863281249998], + [27.352148437499977, 36.86889648437506], + [26.94960937500005, 36.72709960937502] + ] + ], + [ + [ + [25.859375, 36.79042968750005], + [25.74316406250003, 36.78974609374998], + [26.06445312500003, 36.90273437500002], + [25.859375, 36.79042968750005] + ] + ], + [ + [ + [27.01972656250004, 36.95903320312502], + [26.91992187500003, 36.94521484375005], + [26.88867187499997, 37.087255859375034], + [27.01972656250004, 36.95903320312502] + ] + ], + [ + [ + [25.278906250000034, 37.06840820312502], + [25.105468750000057, 37.034960937500045], + [25.235058593750068, 37.148535156250006], + [25.278906250000034, 37.06840820312502] + ] + ], + [ + [ + [25.54589843749997, 36.96757812499999], + [25.45673828125001, 36.9296875], + [25.361914062500063, 37.07041015624998], + [25.52529296875005, 37.19638671875006], + [25.54589843749997, 36.96757812499999] + ] + ], + [ + [ + [24.523535156250063, 37.125097656250006], + [24.42480468750003, 37.131982421874994], + [24.48378906250005, 37.21020507812503], + [24.523535156250063, 37.125097656250006] + ] + ], + [ + [ + [25.402734375000023, 37.419140624999955], + [25.312695312500068, 37.48930664062496], + [25.462988281250063, 37.47109375], + [25.402734375000023, 37.419140624999955] + ] + ], + [ + [ + [26.029296875000057, 37.529394531250034], + [26.086328125000023, 37.63491210937505], + [26.351367187500017, 37.67431640625], + [26.029296875000057, 37.529394531250034] + ] + ], + [ + [ + [25.255859375000057, 37.59960937500006], + [25.156347656250034, 37.54506835937505], + [24.99648437500005, 37.676904296874994], + [25.255859375000057, 37.59960937500006] + ] + ], + [ + [ + [24.35595703125003, 37.57685546875004], + [24.28896484375005, 37.52827148437498], + [24.37910156250004, 37.682714843750006], + [24.35595703125003, 37.57685546875004] + ] + ], + [ + [ + [26.82441406250004, 37.81142578125005], + [27.05507812500005, 37.70927734375002], + [26.84492187500004, 37.64472656250001], + [26.58105468750003, 37.723730468750034], + [26.82441406250004, 37.81142578125005] + ] + ], + [ + [ + [20.888476562500074, 37.805371093749955], + [20.993945312500074, 37.70800781250003], + [20.81855468750004, 37.66474609375001], + [20.61953125000008, 37.855029296875045], + [20.691503906250006, 37.929541015625034], + [20.888476562500074, 37.805371093749955] + ] + ], + [ + [ + [24.991699218750057, 37.75961914062506], + [24.962207031250074, 37.69238281250003], + [24.7001953125, 37.961669921875], + [24.956347656250045, 37.90478515625006], + [24.991699218750057, 37.75961914062506] + ] + ], + [ + [ + [20.61230468750003, 38.38334960937502], + [20.761328125, 38.07055664062497], + [20.523535156250063, 38.106640624999955], + [20.4521484375, 38.23417968750002], + [20.35253906250003, 38.179882812499955], + [20.563183593750068, 38.474951171875034], + [20.61230468750003, 38.38334960937502] + ] + ], + [ + [ + [26.094042968750017, 38.21806640625002], + [25.891894531250045, 38.243310546874994], + [25.991406250000068, 38.353515625], + [25.846093750000023, 38.57402343749996], + [26.16035156250001, 38.54072265625001], + [26.094042968750017, 38.21806640625002] + ] + ], + [ + [ + [20.68671875000001, 38.60869140625002], + [20.5546875, 38.58256835937502], + [20.69414062499999, 38.84423828125003], + [20.68671875000001, 38.60869140625002] + ] + ], + [ + [ + [24.67470703125005, 38.80922851562502], + [24.54101562499997, 38.788671875], + [24.485644531250074, 38.980273437500045], + [24.67470703125005, 38.80922851562502] + ] + ], + [ + [ + [23.41542968750008, 38.958642578124994], + [23.525, 38.8134765625], + [24.127539062500034, 38.648486328125045], + [24.27578125000005, 38.22001953124996], + [24.58837890625003, 38.12397460937504], + [24.53652343750005, 37.97973632812506], + [24.212011718750006, 38.11752929687506], + [24.040136718750006, 38.389990234375034], + [23.65078125000008, 38.44306640625001], + [23.25214843750004, 38.80122070312498], + [22.870312500000068, 38.870507812499966], + [23.258203125000023, 39.03134765625006], + [23.41542968750008, 38.958642578124994] + ] + ], + [ + [ + [26.41015625000003, 39.329443359375034], + [26.59560546875005, 39.04882812499997], + [26.488671875000023, 39.074804687500034], + [26.46875, 38.97280273437502], + [26.10791015625, 39.08105468749997], + [26.273144531249983, 39.19755859374999], + [26.072363281250034, 39.095605468749994], + [25.84414062500008, 39.20004882812506], + [26.16542968750008, 39.37353515625006], + [26.41015625000003, 39.329443359375034] + ] + ], + [ + [ + [20.077929687500045, 39.432714843750034], + [19.883984375000068, 39.461523437500034], + [19.646484375, 39.76708984375003], + [19.926074218750017, 39.773730468750045], + [19.8466796875, 39.66811523437502], + [20.077929687500045, 39.432714843750034] + ] + ], + [ + [ + [25.43769531250004, 39.98330078125002], + [25.357031250000063, 39.80810546875003], + [25.24941406250005, 39.89414062500006], + [25.06220703125004, 39.852392578125006], + [25.05800781250005, 39.999658203124966], + [25.43769531250004, 39.98330078125002] + ] + ], + [ + [ + [24.774218750000074, 40.615185546874955], + [24.515527343750023, 40.64702148437496], + [24.623339843750045, 40.79291992187501], + [24.774218750000074, 40.615185546874955] + ] + ], + [ + [ + [26.03896484375008, 40.726757812499955], + [25.10449218750003, 40.994726562500006], + [24.792968750000057, 40.857519531250034], + [24.47705078125, 40.94775390625003], + [24.082324218750074, 40.72407226562504], + [23.762792968750063, 40.74780273437497], + [23.866796875000034, 40.41855468750006], + [24.21279296875008, 40.32778320312502], + [24.343359375000034, 40.14770507812503], + [23.913183593750063, 40.35878906250005], + [23.72792968750008, 40.329736328124994], + [23.96748046875001, 40.11455078125002], + [23.947070312500045, 39.96557617187506], + [23.66455078125003, 40.22382812499998], + [23.42626953125, 40.26396484374999], + [23.62734375, 39.92407226562503], + [22.896484375000057, 40.39990234374997], + [22.92226562500008, 40.59086914062499], + [22.629492187500034, 40.49555664062501], + [22.59218750000005, 40.03691406250002], + [23.327734374999977, 39.174902343750006], + [23.15468750000008, 39.10146484375005], + [23.16171875, 39.25776367187501], + [22.92138671874997, 39.30634765625004], + [22.886035156250074, 39.16997070312496], + [23.066699218750017, 39.03793945312498], + [22.569140625000074, 38.86748046874999], + [23.25292968750003, 38.66123046875006], + [23.68398437500008, 38.35244140625002], + [23.96699218750001, 38.275], + [24.024511718750006, 38.139794921874966], + [24.01972656250001, 37.67773437499997], + [23.50175781249999, 38.03486328124998], + [23.03632812500004, 37.87836914062501], + [23.48925781250003, 37.440185546875], + [23.16152343750005, 37.333837890625006], + [22.725390625000017, 37.542138671874966], + [23.16015625000003, 36.448095703125034], + [22.717187500000023, 36.79394531250006], + [22.42773437500003, 36.47578124999998], + [22.08046875000008, 37.028955078124966], + [21.95556640625003, 36.990087890625034], + [21.892382812500045, 36.73730468749997], + [21.58291015625005, 37.080957031249994], + [21.678906250000068, 37.38720703125003], + [21.124707031250068, 37.89160156250003], + [21.40371093750005, 38.19667968750002], + [21.658398437500068, 38.17509765624996], + [21.82470703125003, 38.328125], + [22.846386718750068, 37.96757812499996], + [23.18349609375008, 38.133691406249966], + [22.421679687500045, 38.43852539062499], + [22.319921875, 38.35683593750005], + [21.96533203124997, 38.412451171875006], + [21.47255859375005, 38.321386718750006], + [21.3310546875, 38.48730468749997], + [21.303320312500034, 38.373925781249966], + [21.113183593750023, 38.38466796875002], + [20.768554687500057, 38.874414062499966], + [21.111621093750045, 38.89628906249999], + [21.11835937500001, 39.029980468749955], + [20.71337890625, 39.03515625000003], + [20.300781250000057, 39.32709960937501], + [20.19140625, 39.545800781249966], + [20.099414062500074, 39.641259765624966], + [20.001269531250074, 39.70942382812501], + [20.022558593750063, 39.710693359375], + [20.059765624999983, 39.69912109375002], + [20.13105468750004, 39.66162109375003], + [20.206835937500017, 39.65351562499998], + [20.382421875, 39.802636718749994], + [20.381640625000017, 39.84179687500006], + [20.311328125000074, 39.95078125000006], + [20.311132812500034, 39.97944335937504], + [20.338476562500006, 39.991064453125006], + [20.38369140625008, 40.0171875], + [20.408007812500074, 40.049462890624994], + [20.4560546875, 40.065576171874994], + [20.657421875000068, 40.11738281249998], + [20.881640625000017, 40.467919921874994], + [21.030859375000034, 40.62246093750002], + [20.95576171875001, 40.775292968749994], + [20.96425781250005, 40.84990234374999], + [21.575781250000034, 40.86894531249996], + [21.627539062500006, 40.896337890625034], + [21.77949218750004, 40.95043945312506], + [21.99335937500001, 41.13095703125006], + [22.18447265625005, 41.15864257812501], + [22.49355468750005, 41.118505859375006], + [22.603613281249977, 41.14018554687499], + [22.724804687500068, 41.17851562499999], + [22.78388671875004, 41.33198242187498], + [23.155957031250068, 41.32207031249999], + [23.239843750000034, 41.38496093750001], + [23.372070312500057, 41.3896484375], + [23.433398437500017, 41.39873046874999], + [23.53583984375001, 41.38603515624999], + [23.63515625000008, 41.386767578125045], + [24.011328124999977, 41.460058593750034], + [24.03291015625004, 41.469091796875034], + [24.05605468750005, 41.527246093749966], + [24.38671875, 41.523535156250006], + [24.487890625, 41.55522460937499], + [24.518261718750068, 41.55253906249996], + [24.773730468750045, 41.356103515624994], + [24.99355468750008, 41.36499023437503], + [25.133398437500063, 41.31577148437506], + [25.251171875000068, 41.243554687499994], + [25.923339843750057, 41.311914062499966], + [26.066406250000057, 41.35068359375006], + [26.135351562499977, 41.3857421875], + [26.155175781250023, 41.43486328124999], + [26.143554687500057, 41.52153320312496], + [26.085546875000063, 41.704150390625045], + [26.10742187499997, 41.72568359374998], + [26.20058593750005, 41.74379882812502], + [26.320898437500034, 41.716552734375], + [26.581347656250074, 41.60126953125004], + [26.62490234375008, 41.401757812499994], + [26.330664062499977, 41.23876953125], + [26.331054687500057, 40.954492187499994], + [26.03896484375008, 40.726757812499955] + ] + ] + ] + }, + "properties": { "name": "Greece", "childNum": 29 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [-61.71552734375, 12.012646484374997], + [-61.714990234374994, 12.18515625], + [-61.60703125, 12.223291015624994], + [-61.71552734375, 12.012646484374997] + ] + ] + }, + "properties": { "name": "Grenada", "childNum": 1 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [-46.266699218750006, 60.781396484374994], + [-46.381542968749955, 60.66030273437502], + [-46.7880859375, 60.758398437500034], + [-46.205224609374994, 60.943505859374994], + [-46.266699218750006, 60.781396484374994] + ] + ], + [ + [ + [-37.03125, 65.53198242187497], + [-37.23842773437494, 65.60986328125003], + [-37.047509765624966, 65.722265625], + [-37.03125, 65.53198242187497] + ] + ], + [ + [ + [-51.01367187499994, 69.55249023437497], + [-51.202050781249966, 69.525], + [-51.33886718749994, 69.73203125000006], + [-51.094580078125006, 69.92416992187503], + [-50.67900390624999, 69.84853515625], + [-51.01367187499994, 69.55249023437497] + ] + ], + [ + [ + [-52.73115234375001, 69.94472656250005], + [-52.0453125, 69.8072265625], + [-51.90019531249999, 69.60478515625007], + [-53.57841796874996, 69.25664062500002], + [-54.18271484374995, 69.40351562500001], + [-53.65830078124998, 69.46513671875005], + [-53.825, 69.54033203124999], + [-54.91914062499998, 69.71362304687503], + [-54.78789062499996, 69.94985351562502], + [-54.322607421875034, 69.94189453125], + [-54.83076171875001, 70.13295898437502], + [-54.37163085937499, 70.31728515625], + [-53.296728515625034, 70.20537109375002], + [-52.73115234375001, 69.94472656250005] + ] + ], + [ + [ + [-51.67514648437498, 70.855224609375], + [-52.11938476562497, 70.87065429687502], + [-52.10673828124999, 70.96801757812497], + [-51.67514648437498, 70.855224609375] + ] + ], + [ + [ + [-25.43232421875001, 70.92133789062495], + [-25.402246093749994, 70.65268554687503], + [-26.217871093749977, 70.45405273437498], + [-26.604687499999926, 70.55336914062497], + [-28.03525390624995, 70.48681640625], + [-27.61723632812496, 70.91376953125001], + [-26.621777343749955, 70.87563476562497], + [-25.81889648437499, 71.04365234375001], + [-25.43232421875001, 70.92133789062495] + ] + ], + [ + [ + [-53.53520507812493, 71.04082031250005], + [-53.9578125, 71.12773437499999], + [-53.58447265625003, 71.29707031249995], + [-53.53520507812493, 71.04082031250005] + ] + ], + [ + [ + [-55.01689453124999, 72.79111328125003], + [-55.56660156249998, 72.56435546875002], + [-56.214794921874955, 72.71918945312495], + [-55.01689453124999, 72.79111328125003] + ] + ], + [ + [ + [-18.000537109374932, 75.40732421875003], + [-17.391992187499937, 75.03691406250007], + [-18.670800781249966, 75.00166015624998], + [-18.856054687499977, 75.31914062500002], + [-18.000537109374932, 75.40732421875003] + ] + ], + [ + [ + [-18.58261718749995, 76.042333984375], + [-19.085351562499966, 76.43037109375001], + [-18.882470703124937, 76.70380859375001], + [-18.58261718749995, 76.042333984375] + ] + ], + [ + [ + [-71.667333984375, 77.32529296874998], + [-72.48955078124999, 77.43164062499997], + [-71.43344726562495, 77.394384765625], + [-71.667333984375, 77.32529296874998] + ] + ], + [ + [ + [-17.6125, 79.82587890624995], + [-18.662011718749966, 79.72001953125005], + [-19.13828125, 79.85234375000002], + [-17.98291015625, 80.05517578125003], + [-17.471386718749955, 80.02871093749997], + [-17.6125, 79.82587890624995] + ] + ], + [ + [ + [-44.86455078124999, 82.08364257812502], + [-46.75190429687501, 82.34819335937502], + [-47.27226562499996, 82.65693359375001], + [-46.399169921875, 82.692138671875], + [-44.91748046875003, 82.48051757812505], + [-44.86455078124999, 82.08364257812502] + ] + ], + [ + [ + [-29.952880859375, 83.56484374999997], + [-25.795068359374994, 83.26098632812497], + [-31.99267578125, 83.0853515625], + [-32.03271484374997, 82.98344726562502], + [-25.12338867187495, 83.15961914062501], + [-24.47031249999995, 82.87739257812498], + [-21.582519531249943, 82.6341796875], + [-23.118066406249966, 82.32470703125003], + [-29.57939453124996, 82.16118164062502], + [-29.887402343749983, 82.05483398437502], + [-29.543847656249994, 81.93994140624997], + [-27.839501953124966, 82.04887695312505], + [-25.148828124999966, 82.001123046875], + [-24.293066406249977, 81.70097656250005], + [-23.103710937499983, 82.01181640625003], + [-21.337988281249977, 82.068701171875], + [-21.230517578125017, 81.60136718749999], + [-23.11772460937499, 80.77817382812498], + [-19.62993164062499, 81.63989257812503], + [-17.456054687499943, 81.397705078125], + [-16.12070312499995, 81.776611328125], + [-14.241992187500017, 81.81386718750005], + [-12.434423828125006, 81.68251953125002], + [-11.430664062499972, 81.45683593750005], + [-13.126220703124972, 81.08779296875], + [-14.452343749999955, 80.99311523437498], + [-14.503564453124994, 80.76328125000006], + [-16.76059570312492, 80.573388671875], + [-15.937255859374972, 80.42763671874997], + [-16.48876953124997, 80.25195312499997], + [-18.070947265624994, 80.17207031249995], + [-19.429199218749943, 80.25771484375], + [-20.150146484375, 80.01123046874997], + [-18.99199218749996, 79.17836914062502], + [-21.133740234374926, 78.65864257812501], + [-21.729589843749977, 77.70854492187499], + [-20.862597656249932, 77.91186523437503], + [-19.490429687499983, 77.71889648437497], + [-19.46752929687503, 77.56582031250005], + [-20.162060546874926, 77.68984375], + [-20.680810546875023, 77.61899414062503], + [-20.23193359374997, 77.36840820312497], + [-19.30029296874997, 77.22236328124995], + [-18.442626953124943, 77.259375], + [-18.51030273437496, 76.77817382812498], + [-20.48671875, 76.92080078125], + [-21.614697265624926, 76.68789062499997], + [-22.18525390625001, 76.79409179687502], + [-22.609326171874983, 76.70429687500004], + [-21.877343749999966, 76.57348632812503], + [-21.488232421874926, 76.271875], + [-20.10361328124992, 76.21909179687503], + [-19.508984374999926, 75.75751953124995], + [-19.52636718750003, 75.18022460937505], + [-20.484960937500006, 75.31425781249999], + [-21.649316406249966, 75.02343749999997], + [-22.232861328124926, 75.11972656249998], + [-21.69511718749999, 74.96445312500003], + [-20.985791015624983, 75.07436523437497], + [-20.86157226562497, 74.63593750000001], + [-20.41708984374995, 74.9751953125], + [-19.98491210937499, 74.9751953125], + [-19.287011718750023, 74.54638671875006], + [-19.36914062499997, 74.28403320312498], + [-20.256445312499977, 74.2828125], + [-20.653125, 74.13735351562502], + [-21.954931640624977, 74.24428710937497], + [-21.942919921874932, 74.56572265624999], + [-22.32158203124999, 74.30253906250002], + [-22.134814453124932, 73.99047851562503], + [-20.36728515624992, 73.8482421875], + [-20.509667968749966, 73.49287109375001], + [-22.346875, 73.26923828125001], + [-23.23320312499999, 73.39770507812497], + [-24.157714843749943, 73.76445312499999], + [-24.67724609375, 73.602197265625], + [-25.521289062500017, 73.85161132812499], + [-24.79125976562497, 73.51127929687502], + [-26.062304687500017, 73.25302734375], + [-27.270410156250023, 73.43627929687503], + [-26.541845703125006, 73.24897460937495], + [-27.561621093750006, 73.13847656250002], + [-27.348046875000023, 73.06782226562501], + [-25.057031250000023, 73.396484375], + [-24.132666015625006, 73.409375], + [-22.036328124999955, 72.91845703125006], + [-22.29321289062497, 72.11953125], + [-24.06904296875001, 72.49873046874998], + [-24.629980468749977, 73.03764648437499], + [-26.657617187499966, 72.71582031249997], + [-24.81333007812492, 72.90151367187497], + [-24.65, 72.58251953125], + [-25.117871093749983, 72.34697265625005], + [-24.66684570312492, 72.437353515625], + [-21.959667968749955, 71.74467773437502], + [-22.479638671874937, 71.38344726562497], + [-22.417578125, 71.24868164062505], + [-22.29902343750001, 71.43232421874998], + [-21.75224609374999, 71.47832031250002], + [-21.522656249999926, 70.52622070312503], + [-22.38413085937492, 70.46240234375], + [-22.437011718749943, 70.860009765625], + [-22.690673828124943, 70.43730468750002], + [-23.327832031249983, 70.45097656250007], + [-23.97138671875001, 70.64946289062499], + [-24.562207031249926, 71.22353515624997], + [-25.885156249999966, 71.571923828125], + [-27.08720703124999, 71.6265625], + [-27.107031250000034, 71.53266601562498], + [-25.842724609374955, 71.48017578124995], + [-25.74223632812499, 71.18359375], + [-26.717919921874994, 70.95048828125005], + [-28.39843749999997, 70.99291992187497], + [-27.99218749999997, 70.89521484374998], + [-28.06987304687499, 70.69902343750005], + [-29.07207031249999, 70.444970703125], + [-26.621777343749955, 70.46337890625], + [-26.576806640625023, 70.35708007812502], + [-27.560839843749932, 70.12446289062498], + [-27.384179687500023, 69.9916015625], + [-27.027734374999966, 70.20122070312499], + [-25.529882812499977, 70.35317382812502], + [-23.66733398437495, 70.139306640625], + [-22.28447265624996, 70.12583007812498], + [-22.287060546874955, 70.03339843749998], + [-23.03364257812501, 69.90083007812498], + [-23.04956054687497, 69.79272460937497], + [-23.86572265624997, 69.73671875000002], + [-23.739404296874994, 69.58862304687497], + [-24.296679687500017, 69.58554687500006], + [-24.295556640624966, 69.439306640625], + [-25.188574218750006, 69.26054687500002], + [-25.092431640624937, 69.16518554687502], + [-25.697998046874943, 68.889892578125], + [-26.48291015624997, 68.67592773437502], + [-29.24951171874997, 68.29877929687501], + [-29.86850585937495, 68.31157226562505], + [-30.318115234375, 68.19331054687501], + [-30.72001953124999, 68.25117187499998], + [-30.610742187499994, 68.11791992187503], + [-30.97856445312499, 68.06132812500005], + [-32.32744140624999, 68.43730468749999], + [-32.16455078125, 67.99111328125002], + [-33.15698242187497, 67.62670898437506], + [-34.1982421875, 66.65507812499999], + [-35.18857421874995, 66.25029296875002], + [-35.86723632812502, 66.44140624999997], + [-35.630078124999926, 66.13994140625002], + [-36.37919921874996, 65.830810546875], + [-36.52724609375002, 66.00771484375], + [-36.665185546874966, 65.79008789062507], + [-37.06279296874996, 65.87143554687503], + [-37.410058593749994, 65.65634765625], + [-37.954785156249955, 65.63359375000007], + [-37.278710937499994, 66.30439453124995], + [-38.156640624999966, 66.38559570312498], + [-37.75234375000002, 66.26152343750002], + [-38.13994140625002, 65.90351562499998], + [-38.52036132812498, 66.00966796875002], + [-38.20336914062497, 65.71171874999999], + [-40.17353515624998, 65.55615234375], + [-39.57792968749996, 65.34077148437501], + [-39.937255859375, 65.14160156250003], + [-40.253125, 65.04887695312505], + [-41.08442382812501, 65.10083007812497], + [-40.966015624999955, 64.86884765624995], + [-40.655468749999926, 64.91533203125002], + [-40.18222656249998, 64.47993164062495], + [-40.78173828125, 64.22177734375003], + [-41.581005859374926, 64.29833984375], + [-41.03056640624996, 64.12104492187504], + [-40.61777343749998, 64.13173828125], + [-40.550390625000034, 63.72524414062505], + [-40.77519531249999, 63.53364257812501], + [-41.04873046875002, 63.51381835937505], + [-41.387890624999926, 63.06186523437498], + [-41.84448242187497, 63.07026367187501], + [-42.174511718749955, 63.20878906249999], + [-41.63447265624998, 62.972460937500074], + [-41.90898437499996, 62.73710937499999], + [-42.94165039062503, 62.72021484375003], + [-42.15297851562502, 62.568457031250006], + [-42.32148437499998, 62.15273437500005], + [-42.110205078125006, 61.857226562500074], + [-42.58530273437498, 61.71748046875001], + [-42.34736328125001, 61.61743164062497], + [-42.717041015625, 60.767480468749994], + [-43.04409179687502, 60.523681640625], + [-43.92270507812495, 60.59536132812502], + [-43.21298828124998, 60.390673828125074], + [-43.122900390625006, 60.06123046875001], + [-43.32011718749993, 59.928125], + [-43.95502929687498, 60.025488281250006], + [-43.65791015625001, 59.85864257812503], + [-43.90654296874996, 59.815478515625045], + [-44.11699218750002, 59.83193359375002], + [-44.06547851562499, 59.92480468750003], + [-44.412939453125006, 59.922607421875], + [-44.22436523437494, 60.273535156250006], + [-44.61328124999997, 60.01665039062499], + [-45.37924804687495, 60.20292968750002], + [-45.367773437500006, 60.37294921875002], + [-44.97470703124995, 60.457226562499955], + [-44.756738281249966, 60.66459960937502], + [-45.38051757812494, 60.444921875], + [-46.04663085937503, 60.61572265625], + [-46.141943359375006, 60.776513671874994], + [-45.87021484374998, 61.21831054687502], + [-46.87446289062501, 60.81640625000003], + [-48.180810546874966, 60.76923828125001], + [-47.77031249999999, 60.99775390625001], + [-48.386425781249926, 61.004736328125034], + [-48.42817382812501, 61.18740234375002], + [-48.92207031249998, 61.27744140624998], + [-49.28906249999997, 61.58994140625006], + [-49.380273437499994, 61.89018554687502], + [-48.82871093749998, 62.0796875], + [-49.62377929687494, 61.99858398437499], + [-49.553466796875, 62.23271484374999], + [-50.319238281249966, 62.473193359375045], + [-50.298730468749966, 62.72197265625002], + [-49.793115234374994, 63.04462890625004], + [-50.39008789062501, 62.82202148437497], + [-51.46884765624995, 63.64228515625001], + [-51.547509765624994, 64.00610351562497], + [-50.260693359374955, 64.21425781250002], + [-50.48662109374996, 64.20888671875], + [-50.43706054687499, 64.31284179687503], + [-51.58491210937498, 64.10317382812502], + [-51.70786132812498, 64.205078125], + [-51.403759765624926, 64.46318359375002], + [-50.49208984375002, 64.69316406250005], + [-50.00898437500001, 64.44726562499997], + [-50.12163085937493, 64.703759765625], + [-50.51699218750002, 64.76650390625], + [-50.96064453124998, 65.20112304687498], + [-50.721582031249966, 64.79760742187503], + [-51.22060546875002, 64.62846679687502], + [-51.25537109375, 64.75810546875005], + [-51.92260742187503, 64.21875], + [-52.259033203125, 65.154931640625], + [-52.537695312500034, 65.32880859374998], + [-51.61914062500003, 65.71318359375002], + [-51.091894531250006, 65.77578125], + [-51.7234375, 65.723486328125], + [-52.55126953125003, 65.46137695312498], + [-52.760937499999926, 65.59082031249997], + [-53.198974609375, 65.59404296875002], + [-53.106347656249966, 65.97714843749998], + [-53.39204101562498, 66.04833984375], + [-51.225, 66.88154296875001], + [-53.035791015624966, 66.20141601562503], + [-53.538769531249955, 66.13935546874998], + [-53.41875, 66.64853515624998], + [-53.038281249999955, 66.82680664062497], + [-52.38686523437502, 66.88115234375005], + [-53.44360351562503, 66.924658203125], + [-53.88442382812502, 67.13554687499999], + [-53.79858398437494, 67.41816406250001], + [-52.666455078124955, 67.74970703124995], + [-50.613476562499955, 67.5279296875], + [-51.171044921874966, 67.693603515625], + [-50.96884765624998, 67.80664062500003], + [-51.765234375000034, 67.73784179687505], + [-52.34482421874998, 67.83691406249997], + [-53.735205078125006, 67.54902343750004], + [-53.151562499999926, 68.20776367187503], + [-51.779980468749926, 68.05673828124998], + [-51.456494140624926, 68.116064453125], + [-51.21015625000001, 68.419921875], + [-52.19853515624993, 68.22080078125], + [-53.38315429687495, 68.29736328124997], + [-53.03945312500002, 68.61088867187499], + [-52.60458984374998, 68.70874023437503], + [-51.62314453124995, 68.53481445312505], + [-50.945703124999966, 68.68266601562505], + [-50.807714843750006, 68.81699218749998], + [-51.24941406250002, 68.73994140625001], + [-51.084863281249994, 69.12827148437498], + [-50.29736328124994, 69.17060546874998], + [-51.07695312499996, 69.20947265625], + [-50.291699218749955, 70.01445312500005], + [-52.254638671875, 70.05893554687503], + [-53.02304687499995, 70.30190429687497], + [-54.01445312499996, 70.42167968750005], + [-54.53076171875, 70.69926757812502], + [-54.16582031249999, 70.82011718750005], + [-52.801953124999955, 70.7505859375], + [-50.87236328124993, 70.36489257812502], + [-50.66328124999998, 70.417578125], + [-51.32285156249998, 70.58876953124997], + [-51.25659179687497, 70.85268554687502], + [-51.77431640625002, 71.01044921875001], + [-51.018945312499966, 71.001318359375], + [-51.37666015625001, 71.11904296875], + [-53.007568359375, 71.17998046874999], + [-52.89184570312497, 71.457666015625], + [-51.76992187500002, 71.67172851562498], + [-53.44008789062502, 71.57900390625002], + [-53.14453125000003, 71.80742187500002], + [-53.65214843749996, 72.36264648437506], + [-53.92773437499997, 72.31879882812501], + [-53.47758789062502, 71.84995117187506], + [-54.01992187500002, 71.657861328125], + [-53.96298828124995, 71.45898437499997], + [-54.6890625, 71.36723632812505], + [-55.59404296874999, 71.55351562500005], + [-55.315576171874994, 72.11069335937498], + [-54.84013671874996, 72.35610351562497], + [-55.581445312499994, 72.178857421875], + [-55.63583984374998, 72.300439453125], + [-55.29570312499996, 72.35439453124997], + [-55.60170898437494, 72.453466796875], + [-54.924951171874994, 72.57197265624998], + [-54.737939453124994, 72.87250976562501], + [-55.07309570312498, 73.01513671875003], + [-55.28891601562498, 72.93320312500003], + [-55.66855468749998, 73.00791015624998], + [-55.288281249999955, 73.32709960937498], + [-56.10405273437496, 73.55815429687499], + [-55.83828125, 73.75971679687501], + [-56.22539062499999, 74.12910156249995], + [-57.23056640624995, 74.12529296875007], + [-56.70634765625002, 74.21918945312501], + [-56.717675781249994, 74.42924804687499], + [-56.25546874999998, 74.52680664062498], + [-58.56552734374998, 75.35273437500001], + [-58.249658203124994, 75.50668945312503], + [-58.51621093749995, 75.68906250000006], + [-61.18823242187494, 76.157861328125], + [-63.29130859374996, 76.35205078125003], + [-63.84306640624999, 76.21713867187498], + [-64.307275390625, 76.31650390624998], + [-65.36992187499993, 76.13056640625004], + [-65.87573242187494, 76.23833007812505], + [-66.46577148437498, 76.13916015625], + [-66.99257812500002, 76.21293945312502], + [-66.67480468750003, 75.977392578125], + [-68.14873046875002, 76.06704101562497], + [-69.48408203125001, 76.39916992187503], + [-68.1142578125, 76.65063476562503], + [-69.67382812499994, 76.73588867187507], + [-69.69423828125002, 76.98945312500004], + [-70.613134765625, 76.82182617187499], + [-71.14145507812498, 77.02866210937503], + [-70.86284179687496, 77.175439453125], + [-68.97832031250002, 77.19531250000006], + [-68.13554687499999, 77.37958984375001], + [-66.38945312499999, 77.28027343750003], + [-66.69121093749999, 77.68120117187502], + [-67.68808593749995, 77.523779296875], + [-68.62153320312498, 77.60185546875002], + [-69.35136718749999, 77.467138671875], + [-70.53540039062497, 77.699560546875], + [-70.11445312500001, 77.84135742187505], + [-71.27163085937494, 77.81313476562497], + [-72.81806640624995, 78.1943359375], + [-72.47250976562498, 78.48203125], + [-71.65131835937493, 78.62314453124998], + [-68.99345703124999, 78.857421875], + [-68.37705078124998, 79.037841796875], + [-65.82553710937503, 79.17373046874997], + [-64.79228515624993, 80.00063476562502], + [-64.17915039062498, 80.09926757812497], + [-66.84365234374997, 80.07622070312507], + [-67.05063476562503, 80.384521484375], + [-64.51552734374997, 81], + [-63.72197265624993, 81.05732421875001], + [-63.028662109375006, 80.88955078125002], + [-62.90336914062496, 81.21835937500003], + [-61.43598632812498, 81.13359375000002], + [-60.842871093750034, 81.85537109374997], + [-59.28193359374998, 81.88403320312503], + [-56.615136718749994, 81.362890625], + [-59.26181640624998, 82.00664062500005], + [-54.54887695312496, 82.35063476562505], + [-53.671337890624955, 82.16406249999997], + [-53.55566406250003, 81.65327148437501], + [-53.022558593750034, 82.32172851562504], + [-50.894433593749994, 81.89521484375001], + [-49.54106445312496, 81.91806640625003], + [-50.93554687500003, 82.38281250000003], + [-50.03710937499994, 82.472412109375], + [-44.7294921875, 81.77983398437505], + [-44.23886718749998, 82.3681640625], + [-45.55654296875002, 82.74702148437498], + [-41.87646484375, 82.680322265625], + [-41.36962890625003, 82.75], + [-46.136816406250006, 82.85883789062504], + [-46.169042968750006, 83.06386718749997], + [-45.41459960937496, 83.01767578124998], + [-43.00927734375003, 83.26459960937501], + [-41.300146484375006, 83.10078125000004], + [-40.35683593750002, 83.332177734375], + [-38.15625, 82.9986328125], + [-38.74956054687496, 83.37084960937497], + [-37.72333984374998, 83.49775390624998], + [-29.952880859375, 83.56484374999997] + ] + ] + ] + }, + "properties": { "name": "Greenland", "childNum": 14 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [-89.2328125, 15.888671875], + [-88.89404296875, 15.890625], + [-88.60336914062499, 15.76416015625], + [-88.5939453125, 15.950292968749991], + [-88.22832031249999, 15.72900390625], + [-88.271435546875, 15.694873046875003], + [-88.36455078124999, 15.616015625], + [-88.68447265625, 15.360498046874994], + [-88.96098632812499, 15.152441406249991], + [-89.142578125, 15.072314453125003], + [-89.22236328125, 14.866064453124991], + [-89.16220703124999, 14.669238281250003], + [-89.17177734375, 14.606884765624997], + [-89.28671875, 14.529980468749997], + [-89.36259765624999, 14.416015625], + [-89.5736328125, 14.390087890624997], + [-89.54716796874999, 14.241259765625003], + [-90.04814453124999, 13.904052734375], + [-90.09521484375, 13.736523437499997], + [-90.60693359375, 13.929003906250003], + [-91.37734375, 13.990185546874997], + [-92.23515624999999, 14.54541015625], + [-92.15854492187499, 14.963574218749997], + [-92.14423828125, 15.001953125], + [-92.09873046874999, 15.026757812499994], + [-92.07480468749999, 15.07421875], + [-92.187158203125, 15.320898437499991], + [-92.08212890624999, 15.495556640624997], + [-91.9572265625, 15.703222656249991], + [-91.736572265625, 16.07016601562499], + [-91.433984375, 16.070458984374994], + [-90.97958984374999, 16.07080078125], + [-90.70322265624999, 16.071044921875], + [-90.52197265625, 16.071191406249994], + [-90.44716796875, 16.072705078124997], + [-90.45986328125, 16.162353515625], + [-90.450146484375, 16.261376953124994], + [-90.4169921875, 16.351318359375], + [-90.4169921875, 16.39101562499999], + [-90.47109375, 16.43955078124999], + [-90.57578125, 16.467822265625003], + [-90.63408203124999, 16.5107421875], + [-90.634375, 16.565136718749997], + [-90.65996093749999, 16.630908203125003], + [-90.710693359375, 16.70810546874999], + [-90.975830078125, 16.867822265624994], + [-91.409619140625, 17.255859375], + [-91.1955078125, 17.254101562499997], + [-90.99296874999999, 17.25244140625], + [-90.98916015625, 17.81640625], + [-89.16147460937499, 17.81484375], + [-89.2328125, 15.888671875] + ] + ] + }, + "properties": { "name": "Guatemala", "childNum": 1 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [144.74179687500003, 13.25927734375], + [144.64931640625002, 13.4287109375], + [144.87539062500002, 13.614648437499994], + [144.94082031250002, 13.5703125], + [144.74179687500003, 13.25927734375] + ] + ] + }, + "properties": { "name": "Guam", "childNum": 1 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [-57.194775390625, 5.5484375], + [-57.3185546875, 5.335351562499994], + [-57.20981445312499, 5.195410156249991], + [-57.331005859375, 5.020166015624994], + [-57.711083984374994, 4.991064453124991], + [-57.91704101562499, 4.820410156249991], + [-57.84599609374999, 4.668164062499997], + [-58.05429687499999, 4.101660156249991], + [-57.646728515625, 3.39453125], + [-57.303662109375, 3.377099609374994], + [-57.19736328124999, 2.853271484375], + [-56.704345703125, 2.036474609374991], + [-56.4828125, 1.942138671875], + [-56.96953124999999, 1.91640625], + [-57.03759765625, 1.936474609374997], + [-57.092675781249994, 2.005810546874997], + [-57.118896484375, 2.013964843749989], + [-57.31748046874999, 1.963476562499991], + [-57.41269531249999, 1.908935546875], + [-57.500439453125, 1.77382812499999], + [-57.54575195312499, 1.72607421875], + [-57.59443359375, 1.7041015625], + [-57.795654296875, 1.7], + [-57.8734375, 1.667285156249989], + [-57.9828125, 1.6484375], + [-58.03466796875, 1.520263671875], + [-58.34067382812499, 1.587548828124994], + [-58.38037109375, 1.530224609374997], + [-58.39580078124999, 1.481738281249989], + [-58.5060546875, 1.438671875], + [-58.511865234374994, 1.28466796875], + [-58.68461914062499, 1.281054687499989], + [-58.73032226562499, 1.247509765624997], + [-58.78720703124999, 1.20849609375], + [-58.82177734375, 1.201220703124989], + [-59.231201171875, 1.376025390624989], + [-59.53569335937499, 1.7], + [-59.66660156249999, 1.746289062499997], + [-59.66850585937499, 1.842333984374989], + [-59.74072265625, 1.874169921874994], + [-59.75620117187499, 1.900634765625], + [-59.75522460937499, 2.274121093749997], + [-59.8896484375, 2.362939453124994], + [-59.9943359375, 2.689990234374989], + [-59.854394531249994, 3.5875], + [-59.55112304687499, 3.933544921874997], + [-59.557763671874994, 3.960009765624989], + [-59.62021484374999, 4.023144531249997], + [-59.73857421874999, 4.226757812499997], + [-59.69970703125, 4.353515625], + [-60.1486328125, 4.533251953124989], + [-59.990673828125, 5.082861328124991], + [-60.142041015625, 5.238818359374989], + [-60.241650390625, 5.257958984374994], + [-60.335205078125, 5.199316406249991], + [-60.45952148437499, 5.188085937499991], + [-60.6513671875, 5.221142578124997], + [-60.742138671875, 5.202050781249994], + [-61.37680664062499, 5.906982421875], + [-61.3908203125, 5.938769531249989], + [-61.303125, 6.049511718749997], + [-61.22495117187499, 6.129199218749989], + [-61.15947265624999, 6.174414062499991], + [-61.12871093749999, 6.214306640624997], + [-61.152294921875, 6.385107421874991], + [-61.151025390624994, 6.446533203125], + [-61.181591796875, 6.513378906249997], + [-61.20361328125, 6.58837890625], + [-61.14560546874999, 6.69453125], + [-60.717919921874994, 6.768310546875], + [-60.35209960937499, 7.002880859374997], + [-60.32207031249999, 7.092041015625], + [-60.32548828124999, 7.133984375], + [-60.34506835937499, 7.15], + [-60.46494140624999, 7.166552734374989], + [-60.523193359375, 7.143701171874994], + [-60.583203125, 7.156201171874997], + [-60.63330078125, 7.211083984374994], + [-60.718652343749994, 7.535937499999989], + [-60.513623046875, 7.813183593749997], + [-60.032421875, 8.053564453124991], + [-59.99072265625, 8.162011718749994], + [-59.96484375, 8.191601562499997], + [-59.849072265625, 8.248681640624994], + [-59.83164062499999, 8.305957031249989], + [-60.017529296875, 8.54931640625], + [-59.20024414062499, 8.07460937499999], + [-58.51108398437499, 7.39804687499999], + [-58.48056640624999, 7.038134765624989], + [-58.67294921874999, 6.390771484374994], + [-58.414990234375, 6.85117187499999], + [-57.982568359374994, 6.785888671875], + [-57.54013671874999, 6.33154296875], + [-57.2275390625, 6.178417968749997], + [-57.194775390625, 5.5484375] + ] + ] + }, + "properties": { "name": "Guyana", "childNum": 1 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [73.70742187500002, -53.13710937499999], + [73.46513671875002, -53.184179687500006], + [73.25117187500001, -52.97578125000001], + [73.83779296875002, -53.11279296875], + [73.70742187500002, -53.13710937499999] + ] + ] + }, + "properties": { "name": "Heard I. and McDonald Is.", "childNum": 1 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [-83.635498046875, 14.876416015624997], + [-84.53764648437496, 14.633398437499963], + [-84.64594726562498, 14.661083984375011], + [-84.86044921874998, 14.809765625000011], + [-84.98515624999999, 14.752441406249972], + [-85.059375, 14.582958984374997], + [-85.20834960937498, 14.311816406250003], + [-85.73393554687496, 13.85869140625006], + [-85.75341796875, 13.852050781250028], + [-85.78671874999995, 13.844433593749997], + [-85.98378906249997, 13.965673828125006], + [-86.04038085937503, 14.050146484374977], + [-86.33173828124995, 13.770068359375031], + [-86.37695312500003, 13.755664062500031], + [-86.61025390624997, 13.774853515625026], + [-86.73364257812494, 13.763476562500017], + [-86.75898437499995, 13.746142578125045], + [-86.77060546875003, 13.698730468749972], + [-86.763525390625, 13.635253906250014], + [-86.72958984375, 13.4072265625], + [-86.710693359375, 13.31337890624998], + [-86.72929687499996, 13.284375], + [-86.79213867187497, 13.279785156249972], + [-86.87353515624994, 13.266503906250023], + [-86.918212890625, 13.223583984374983], + [-87.00932617187499, 13.007812499999986], + [-87.0591796875, 12.991455078125028], + [-87.337255859375, 12.979248046875028], + [-87.48911132812503, 13.352929687500051], + [-87.814208984375, 13.399169921875057], + [-87.781884765625, 13.521386718749994], + [-87.71533203125003, 13.812695312500011], + [-87.73144531250003, 13.841064453125014], + [-87.80224609374997, 13.889990234375034], + [-87.89199218749997, 13.894970703124983], + [-87.99101562499996, 13.879638671874972], + [-88.15102539062497, 13.987353515624974], + [-88.44912109374994, 13.850976562499994], + [-88.48266601562503, 13.854248046875043], + [-88.49765624999998, 13.904541015624986], + [-88.50434570312501, 13.964208984374963], + [-88.51254882812498, 13.97895507812504], + [-89.12050781249994, 14.370214843749991], + [-89.36259765624996, 14.416015625], + [-89.17177734375, 14.606884765624983], + [-89.16220703125, 14.669238281249989], + [-89.22236328125001, 14.86606445312502], + [-89.142578125, 15.072314453125031], + [-88.96098632812496, 15.15244140625002], + [-88.68447265625002, 15.360498046875037], + [-88.36455078124996, 15.616015625000045], + [-88.27143554687498, 15.694873046875045], + [-88.22832031249999, 15.729003906249972], + [-88.131103515625, 15.701025390625034], + [-87.87495117187495, 15.879345703124955], + [-86.35664062499998, 15.783203125], + [-85.93627929687497, 15.953417968750045], + [-85.98564453124999, 16.02416992187497], + [-85.48369140624996, 15.899511718749977], + [-84.97373046874998, 15.989892578124994], + [-84.55966796875, 15.802001953125], + [-84.26142578124998, 15.822607421875034], + [-83.765283203125, 15.405468750000054], + [-83.972802734375, 15.519628906250034], + [-84.11132812499997, 15.492431640625], + [-84.09506835937503, 15.400927734375017], + [-83.92744140624998, 15.394042968750028], + [-83.76044921874998, 15.220361328124994], + [-83.49794921874997, 15.222119140624997], + [-83.64638671875, 15.368408203125043], + [-83.36918945312493, 15.239990234375], + [-83.29086914062498, 15.078906250000045], + [-83.2255859375, 15.042285156250045], + [-83.15751953124999, 14.993066406249966], + [-83.41503906249994, 15.008056640625], + [-83.5365234375, 14.977001953124983], + [-83.635498046875, 14.876416015624997] + ] + ] + }, + "properties": { "name": "Honduras", "childNum": 1 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [17.60781250000005, 42.76904296875], + [17.744238281250063, 42.70034179687505], + [17.34414062500008, 42.790380859375006], + [17.60781250000005, 42.76904296875] + ] + ], + [ + [ + [16.650683593750017, 42.99658203125], + [17.188281250000045, 42.917041015625045], + [16.850683593750006, 42.8955078125], + [16.650683593750017, 42.99658203125] + ] + ], + [ + [ + [17.667578125000063, 42.897119140624994], + [18.436328125000017, 42.559716796874994], + [18.517480468750023, 42.43291015624999], + [17.823828125, 42.79741210937502], + [17.045410156250057, 43.014892578125], + [17.667578125000063, 42.897119140624994] + ] + ], + [ + [ + [16.785253906250006, 43.270654296874966], + [16.490332031250034, 43.28618164062502], + [16.44892578125004, 43.38706054687506], + [16.89130859375001, 43.314648437499955], + [16.785253906250006, 43.270654296874966] + ] + ], + [ + [ + [15.371386718750074, 43.973828124999955], + [15.437207031250068, 43.899511718750006], + [15.270019531250028, 44.01074218750003], + [15.371386718750074, 43.973828124999955] + ] + ], + [ + [ + [14.488085937500074, 44.66005859375005], + [14.31240234375008, 44.90039062499997], + [14.33125, 45.16499023437498], + [14.488085937500074, 44.66005859375005] + ] + ], + [ + [ + [14.810253906250068, 44.97705078124997], + [14.45039062500004, 45.079199218750006], + [14.571093750000017, 45.224755859374994], + [14.810253906250068, 44.97705078124997] + ] + ], + [ + [ + [18.905371093750006, 45.931738281250034], + [18.839062499999983, 45.83574218750002], + [19.064257812500045, 45.51499023437506], + [19.004687500000074, 45.39951171875006], + [19.4, 45.2125], + [19.062890625000023, 45.13720703125], + [19.007128906250045, 44.86918945312502], + [18.83642578125, 44.883251953124955], + [18.66259765625, 45.07744140624999], + [17.812792968750074, 45.078125], + [16.918652343749983, 45.27656249999998], + [16.53066406250008, 45.21669921875002], + [16.29335937500005, 45.00883789062496], + [16.028320312500057, 45.18959960937502], + [15.788085937500057, 45.17895507812497], + [15.736621093750045, 44.76582031250001], + [16.10341796875008, 44.52099609375006], + [16.300097656250017, 44.12451171875], + [17.27382812500005, 43.44575195312501], + [17.650488281250063, 43.006591796875], + [17.585156250000068, 42.93837890625005], + [16.903125, 43.392431640625006], + [16.393945312500023, 43.54335937500002], + [15.985546875000068, 43.519775390625], + [15.185839843750017, 44.17211914062503], + [15.122949218749994, 44.256787109374955], + [15.470996093750045, 44.27197265625003], + [14.981347656250023, 44.60292968750005], + [14.854589843750034, 45.08100585937501], + [14.550488281249983, 45.297705078125006], + [14.31269531250004, 45.33779296875002], + [13.86074218750008, 44.83740234375003], + [13.517187500000063, 45.481787109375034], + [13.878710937500017, 45.428369140624994], + [14.369921875000074, 45.48144531250006], + [14.427343750000034, 45.50576171875002], + [14.56884765625, 45.65722656249997], + [14.591796875000057, 45.65126953125002], + [14.649511718750006, 45.57148437500001], + [14.793066406250034, 45.47822265625001], + [14.95458984375, 45.499902343749994], + [15.110449218750034, 45.450781250000034], + [15.242089843750023, 45.44140624999997], + [15.339453125000063, 45.46704101562506], + [15.326660156250028, 45.502294921875034], + [15.291210937500011, 45.541552734375045], + [15.283593750000051, 45.5796875], + [15.35371093750004, 45.659912109375], + [15.27705078125004, 45.73261718749998], + [15.652148437500074, 45.86215820312498], + [15.675585937500045, 45.98369140624996], + [15.666210937500011, 46.04848632812502], + [15.596875, 46.10922851562506], + [15.592578125000017, 46.139990234375006], + [15.608984374999977, 46.171923828125045], + [16.1064453125, 46.382226562499994], + [16.32119140625005, 46.53461914062504], + [16.42763671875005, 46.5244140625], + [16.516210937499977, 46.499902343749966], + [16.569921875, 46.48500976562505], + [16.748046875000057, 46.41640625000002], + [16.87148437500008, 46.33930664062504], + [17.310644531250006, 45.99614257812502], + [17.80712890625, 45.79042968750002], + [18.358300781250023, 45.75302734375006], + [18.533593750000023, 45.79614257812503], + [18.56464843750004, 45.81328124999999], + [18.666015625, 45.90747070312497], + [18.905371093750006, 45.931738281250034] + ] + ] + ] + }, + "properties": { "name": "Croatia", "childNum": 8 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [-72.80458984374997, 18.777685546875063], + [-72.82221679687501, 18.707128906249977], + [-73.07797851562498, 18.790917968749994], + [-73.27641601562499, 18.95405273437501], + [-72.80458984374997, 18.777685546875063] + ] + ], + [ + [ + [-71.647216796875, 19.195947265624994], + [-71.80712890624997, 18.987011718749983], + [-71.733642578125, 18.85639648437501], + [-71.72705078125, 18.80322265625003], + [-71.74321289062502, 18.73291015625], + [-71.86650390624999, 18.61416015625005], + [-71.98686523437499, 18.61035156249997], + [-72.000390625, 18.59790039062503], + [-71.94038085937493, 18.51259765625005], + [-71.87255859374997, 18.416210937499955], + [-71.76191406249998, 18.34130859374997], + [-71.73725585937495, 18.27080078124999], + [-71.76831054687497, 18.039160156250063], + [-71.85292968749997, 18.119140625], + [-71.94609375, 18.186083984375045], + [-72.05986328124993, 18.228564453125017], + [-72.87666015624998, 18.151757812499994], + [-73.38515625000002, 18.251171874999983], + [-73.747314453125, 18.190234375000017], + [-73.88496093749998, 18.041894531249994], + [-74.478125, 18.45], + [-74.3875, 18.624707031249983], + [-74.22773437499998, 18.662695312499977], + [-72.78935546874996, 18.434814453125], + [-72.37607421874998, 18.57446289062503], + [-72.34765624999994, 18.674951171874994], + [-72.81108398437496, 19.071582031250074], + [-72.70322265625, 19.441064453125023], + [-73.43837890624994, 19.722119140624983], + [-73.21777343750003, 19.88369140625005], + [-72.63701171875002, 19.90087890625], + [-72.21982421875003, 19.744628906250057], + [-71.834716796875, 19.696728515624983], + [-71.77924804687498, 19.718164062499994], + [-71.75742187499998, 19.68818359375001], + [-71.71147460937493, 19.486572265625057], + [-71.74648437499997, 19.28583984375001], + [-71.647216796875, 19.195947265624994] + ] + ] + ] + }, + "properties": { "name": "Haiti", "childNum": 2 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [22.1318359375, 48.405322265624996], + [22.253710937500017, 48.407373046874994], + [22.582421875000023, 48.134033203125], + [22.769140625, 48.109619140625], + [22.87666015625001, 47.947265625], + [21.99970703125001, 47.505029296874994], + [21.121679687500006, 46.282421875], + [20.76025390625, 46.246240234374994], + [20.613671875000023, 46.13349609375], + [20.508105468750017, 46.166943359375], + [20.28095703125001, 46.1330078125], + [20.241796875, 46.10859375], + [20.21015625000001, 46.126025390624996], + [20.161425781250017, 46.141894531249996], + [19.93408203125, 46.161474609375], + [19.84443359375001, 46.145898437499994], + [19.61347656250001, 46.169189453125], + [19.421289062500023, 46.064453125], + [18.666015625, 45.907470703125], + [18.56464843750001, 45.81328125], + [18.533593750000023, 45.796142578125], + [18.358300781250023, 45.75302734375], + [17.80712890625, 45.790429687499994], + [17.310644531250006, 45.996142578124996], + [16.871484375000023, 46.339306640625], + [16.748046875, 46.41640625], + [16.569921875, 46.485009765624994], + [16.516210937500006, 46.499902343749994], + [16.283593750000023, 46.857275390625], + [16.093066406250017, 46.86328125], + [16.453417968750017, 47.006787109375], + [16.44287109375, 47.39951171875], + [16.676562500000017, 47.536035156249994], + [16.421289062500023, 47.674462890624994], + [17.06660156250001, 47.707568359374996], + [17.147363281250023, 48.00595703125], + [17.76191406250001, 47.770166015624994], + [18.72421875, 47.787158203124996], + [18.791894531250023, 48.000292968749996], + [19.625390625000023, 48.223095703125], + [19.95039062500001, 48.146630859374994], + [20.333789062500017, 48.295556640624994], + [20.490039062500017, 48.526904296874996], + [21.45136718750001, 48.55224609375], + [21.766992187500023, 48.3380859375], + [22.1318359375, 48.405322265624996] + ] + ] + }, + "properties": { "name": "Hungary", "childNum": 1 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [122.9489257812501, -10.90927734375002], + [122.82617187500003, -10.899121093749983], + [122.84570312500003, -10.761816406249991], + [123.37109375000003, -10.474902343749989], + [123.41816406250004, -10.651269531250037], + [122.9489257812501, -10.90927734375002] + ] + ], + [ + [ + [121.8830078125001, -10.590332031249957], + [121.70468750000006, -10.5556640625], + [121.99833984375002, -10.446972656249983], + [121.8830078125001, -10.590332031249957] + ] + ], + [ + [ + [123.41621093750004, -10.302636718749966], + [123.3255859375, -10.264160156249943], + [123.45878906250002, -10.13994140624996], + [123.41621093750004, -10.302636718749966] + ] + ], + [ + [ + [120.0125, -9.374707031250026], + [120.78447265625002, -9.95703125], + [120.83261718750006, -10.0375], + [120.69804687500002, -10.206640624999949], + [120.4391601562501, -10.294042968749991], + [120.14482421875002, -10.200097656249952], + [119.60107421874997, -9.773535156250006], + [119.08544921875003, -9.706933593750023], + [118.95878906250002, -9.519335937500003], + [119.29589843749997, -9.3671875], + [119.9420898437501, -9.301464843750026], + [120.0125, -9.374707031250026] + ] + ], + [ + [ + [125.06816406250002, -9.511914062499997], + [124.42753906250002, -10.14863281250004], + [123.7472656250001, -10.347167968749986], + [123.60478515625002, -10.270117187500006], + [123.71640625000012, -10.078613281249986], + [123.5892578125, -9.966796875000028], + [123.709375, -9.61484375], + [124.0363281250001, -9.341601562500031], + [124.28232421875012, -9.427929687500026], + [124.44443359375012, -9.190332031250023], + [124.92226562500005, -8.942480468749977], + [124.93681640625007, -9.053417968750026], + [125.14902343750012, -9.042578125000034], + [125.10048828125, -9.189843750000023], + [124.96015625000004, -9.213769531250009], + [125.06816406250002, -9.511914062499997] + ] + ], + [ + [ + [115.60996093750012, -8.769824218749974], + [115.48046875000003, -8.715429687500006], + [115.56142578125, -8.669921874999972], + [115.60996093750012, -8.769824218749974] + ] + ], + [ + [ + [122.97734375000002, -8.54521484374996], + [122.88779296875006, -8.587304687500009], + [123.01054687500002, -8.448339843750034], + [123.153125, -8.475781250000026], + [122.97734375000002, -8.54521484374996] + ] + ], + [ + [ + [119.46406250000004, -8.741015624999974], + [119.38554687500002, -8.736035156250026], + [119.4464843750001, -8.429199218749957], + [119.55722656250012, -8.518847656250003], + [119.46406250000004, -8.741015624999974] + ] + ], + [ + [ + [123.31748046875012, -8.354785156249974], + [123.02500000000012, -8.395507812500014], + [123.21708984375002, -8.235449218750006], + [123.33603515625006, -8.269042968750014], + [123.31748046875012, -8.354785156249974] + ] + ], + [ + [ + [116.64082031250004, -8.613867187500006], + [116.51425781250012, -8.820996093750011], + [116.58652343750012, -8.886132812499966], + [116.23935546875006, -8.912109375000014], + [115.85732421875005, -8.787890625000017], + [116.07646484375002, -8.744921874999974], + [116.06113281250006, -8.437402343750023], + [116.4015625000001, -8.204199218750034], + [116.7189453125001, -8.336035156249977], + [116.64082031250004, -8.613867187500006] + ] + ], + [ + [ + [124.28662109375003, -8.32949218749998], + [124.14667968750004, -8.531445312499997], + [123.92773437500003, -8.448925781249969], + [124.23955078125002, -8.20341796874996], + [124.28662109375003, -8.32949218749998] + ] + ], + [ + [ + [123.92480468750003, -8.2724609375], + [123.55302734375007, -8.566796875], + [123.23007812500006, -8.530664062500023], + [123.47587890625007, -8.322265625000014], + [123.39121093750012, -8.280468750000026], + [123.77597656250006, -8.190429687499986], + [123.92480468750003, -8.2724609375] + ] + ], + [ + [ + [138.89511718750006, -8.388671874999957], + [138.56337890625, -8.30908203125], + [138.79619140625007, -8.173632812500017], + [138.89511718750006, -8.388671874999957] + ] + ], + [ + [ + [117.55634765625004, -8.367285156249949], + [117.49052734375007, -8.183398437499974], + [117.66503906249997, -8.148242187500003], + [117.55634765625004, -8.367285156249949] + ] + ], + [ + [ + [124.5755859375, -8.140820312499997], + [125.05029296874997, -8.179589843749994], + [125.13173828125, -8.326464843749989], + [124.38066406250002, -8.41513671875002], + [124.43066406249997, -8.18320312500002], + [124.5755859375, -8.140820312499997] + ] + ], + [ + [ + [127.8234375000001, -8.098828124999969], + [128.11923828125012, -8.17070312499996], + [128.02353515625006, -8.255371093749972], + [127.82089843750012, -8.190234375000031], + [127.8234375000001, -8.098828124999969] + ] + ], + [ + [ + [122.7829101562501, -8.61171875], + [121.65136718749997, -8.898730468749946], + [121.41464843750006, -8.81484375], + [121.32832031250004, -8.916894531250009], + [121.03525390625012, -8.935449218749966], + [120.55048828125004, -8.80185546875002], + [119.909375, -8.857617187500011], + [119.80791015625002, -8.697656250000023], + [119.87480468750007, -8.419824218749994], + [120.61025390625005, -8.24042968750004], + [121.44453125000004, -8.57783203125004], + [121.96650390625004, -8.455175781250006], + [122.32324218749997, -8.628320312500023], + [122.85048828125, -8.304394531250011], + [122.91914062500004, -8.221875], + [122.75859375000002, -8.185937499999952], + [122.91699218749997, -8.105566406250006], + [123.00595703125006, -8.329101562499986], + [122.7829101562501, -8.61171875] + ] + ], + [ + [ + [130.86220703125, -8.31875], + [130.77519531250002, -8.34990234374996], + [131.02011718750012, -8.091308593749943], + [131.17636718750006, -8.130761718749994], + [130.86220703125, -8.31875] + ] + ], + [ + [ + [118.24238281250004, -8.317773437499994], + [118.61191406250006, -8.28066406249998], + [118.71386718749997, -8.41494140624998], + [118.926171875, -8.297656249999974], + [119.12968750000002, -8.668164062499969], + [118.74589843750002, -8.735449218749991], + [118.83261718750012, -8.833398437499966], + [118.47861328125012, -8.856445312499957], + [118.37890625000003, -8.674609375000031], + [118.18994140624997, -8.840527343749997], + [117.06132812500002, -9.099023437499994], + [116.78847656250005, -9.006347656250028], + [116.83505859375012, -8.532421875000026], + [117.16484375000007, -8.367187500000014], + [117.56708984375004, -8.426367187499991], + [117.80605468750005, -8.711132812500011], + [117.96953125000002, -8.728027343749986], + [118.23486328124997, -8.591894531249963], + [117.81484375000005, -8.342089843749974], + [117.7552734375, -8.149511718749991], + [118.11748046875007, -8.12226562500004], + [118.24238281250004, -8.317773437499994] + ] + ], + [ + [ + [115.44785156250012, -8.155175781249994], + [115.70429687500004, -8.40712890624998], + [115.14492187500005, -8.849023437500037], + [115.05507812500005, -8.573046874999946], + [114.61318359375, -8.37832031249998], + [114.46757812500007, -8.166308593749946], + [114.93847656249997, -8.18710937500002], + [115.15400390625004, -8.065722656249974], + [115.44785156250012, -8.155175781249994] + ] + ], + [ + [ + [129.83886718749997, -7.954589843749986], + [129.71347656250012, -8.04072265625004], + [129.60898437500006, -7.803417968750011], + [129.81298828124997, -7.819726562499952], + [129.83886718749997, -7.954589843749986] + ] + ], + [ + [ + [126.80097656250004, -7.667871093750009], + [126.4720703125, -7.950390625000011], + [126.04003906250003, -7.885839843750006], + [125.79824218750005, -7.984570312499969], + [125.97529296875004, -7.663378906249989], + [126.21367187500002, -7.706738281250026], + [126.60957031250004, -7.571777343749972], + [126.80097656250004, -7.667871093750009] + ] + ], + [ + [ + [127.41943359375003, -7.623046875000028], + [127.37070312500012, -7.512792968749949], + [127.47519531250012, -7.531054687500031], + [127.41943359375003, -7.623046875000028] + ] + ], + [ + [ + [138.53535156250004, -8.273632812499969], + [138.2962890625, -8.405175781250037], + [137.65039062499997, -8.386132812499966], + [138.08183593750002, -7.566210937500003], + [138.29550781250012, -7.4384765625], + [138.76982421875002, -7.390429687499974], + [138.98906250000002, -7.696093749999989], + [138.53535156250004, -8.273632812499969] + ] + ], + [ + [ + [131.3255859375, -7.999511718749986], + [131.11376953125003, -7.997363281249989], + [131.13779296875012, -7.684863281250017], + [131.64345703125, -7.11279296875], + [131.73613281250007, -7.197070312500017], + [131.64384765625002, -7.266894531249946], + [131.62441406250005, -7.626171874999955], + [131.3255859375, -7.999511718749986] + ] + ], + [ + [ + [131.98203125000006, -7.202050781249966], + [131.75078125000002, -7.116796875], + [131.92226562500005, -7.104492187499986], + [131.98203125000006, -7.202050781249966] + ] + ], + [ + [ + [128.6701171875001, -7.183300781249969], + [128.52978515625003, -7.134570312499989], + [128.62773437500007, -7.06875], + [128.6701171875001, -7.183300781249969] + ] + ], + [ + [ + [120.77441406250003, -7.118945312500003], + [120.64082031250004, -7.115820312499991], + [120.63339843750006, -7.018261718750011], + [120.77441406250003, -7.118945312500003] + ] + ], + [ + [ + [113.84453125000007, -7.105371093749994], + [113.12695312499997, -7.224121093750028], + [112.72587890625007, -7.072753906250014], + [112.86806640625, -6.899902343749972], + [113.06738281250003, -6.879980468749991], + [113.97470703125012, -6.873046875], + [114.0736328125, -6.960156249999983], + [113.84453125000007, -7.105371093749994] + ] + ], + [ + [ + [115.37705078125006, -6.97080078125002], + [115.22031250000012, -6.952539062500037], + [115.24052734375007, -6.861230468749994], + [115.54609375000004, -6.938671874999955], + [115.37705078125006, -6.97080078125002] + ] + ], + [ + [ + [105.25283203125005, -6.640429687499946], + [105.12138671875007, -6.614941406249997], + [105.26054687500002, -6.523925781250014], + [105.25283203125005, -6.640429687499946] + ] + ], + [ + [ + [134.53681640625004, -6.442285156249994], + [134.32275390624997, -6.84873046875002], + [134.09082031249997, -6.833789062500003], + [134.10703125000006, -6.471582031250009], + [134.19462890625007, -6.459765625], + [134.11464843750005, -6.190820312500009], + [134.53681640625004, -6.442285156249994] + ] + ], + [ + [ + [107.37392578125005, -6.007617187499989], + [107.66679687500002, -6.215820312499957], + [108.33017578125012, -6.286035156249966], + [108.67783203125006, -6.790527343749972], + [110.42626953124997, -6.947265625000028], + [110.83476562500002, -6.424218749999952], + [110.97226562500012, -6.435644531249977], + [111.18154296875005, -6.686718749999969], + [111.54033203125002, -6.648242187500031], + [112.0873046875, -6.89335937499996], + [112.53925781250004, -6.926464843749955], + [112.64873046875007, -7.221289062499977], + [112.7943359375, -7.304492187499974], + [112.79453125000012, -7.55244140625004], + [113.01357421875005, -7.657714843749986], + [113.49765625000006, -7.723828124999955], + [114.07070312500005, -7.633007812500011], + [114.40927734375012, -7.79248046875], + [114.38691406250004, -8.405175781250037], + [114.58378906250002, -8.769628906250034], + [113.25332031250005, -8.286718749999963], + [112.67880859375006, -8.409179687499957], + [111.50996093750004, -8.30507812499998], + [110.60722656250002, -8.149414062499972], + [109.28164062500005, -7.704882812500003], + [108.74121093749997, -7.667089843750034], + [108.45175781250006, -7.79697265625002], + [107.91748046875003, -7.724121093750014], + [107.28496093750007, -7.471679687500014], + [106.45527343750004, -7.368652343749986], + [106.51972656250004, -7.053710937499943], + [106.19824218749997, -6.927832031249977], + [105.25546875000012, -6.835253906250031], + [105.37089843750002, -6.664355468750031], + [105.48369140625007, -6.781542968750017], + [105.65507812500002, -6.469531249999946], + [105.78691406250002, -6.456933593749966], + [105.86826171875006, -6.11640625000004], + [106.075, -5.914160156249963], + [106.82519531249997, -6.098242187499977], + [107.0462890625, -5.90419921874998], + [107.37392578125005, -6.007617187499989] + ] + ], + [ + [ + [120.52832031249997, -6.2984375], + [120.48730468749997, -6.464843749999972], + [120.47734375000007, -5.775292968750009], + [120.52832031249997, -6.2984375] + ] + ], + [ + [ + [112.7194335937501, -5.81103515625], + [112.58603515625006, -5.803613281249994], + [112.69003906250006, -5.726171875000034], + [112.7194335937501, -5.81103515625] + ] + ], + [ + [ + [132.80712890625003, -5.850781250000011], + [132.68144531250002, -5.91259765625], + [132.63017578125002, -5.60703125], + [132.80712890625003, -5.850781250000011] + ] + ], + [ + [ + [134.74697265625, -5.707031249999957], + [134.71416015625007, -6.29511718750004], + [134.44111328125004, -6.334863281249966], + [134.15488281250006, -6.06289062499998], + [134.3019531250001, -6.009765624999986], + [134.34306640625002, -5.833007812499943], + [134.20537109375002, -5.707226562499997], + [134.34130859375003, -5.712890624999986], + [134.57080078124997, -5.42734375], + [134.74697265625, -5.707031249999957] + ] + ], + [ + [ + [132.92626953124997, -5.902050781249983], + [132.84501953125002, -5.987988281249997], + [133.13847656250002, -5.317871093749986], + [133.11962890625003, -5.575976562499989], + [132.92626953124997, -5.902050781249983] + ] + ], + [ + [ + [102.36718750000003, -5.478710937499983], + [102.1107421875, -5.32255859374996], + [102.3717773437501, -5.366406250000011], + [102.36718750000003, -5.478710937499983] + ] + ], + [ + [ + [123.62675781250007, -5.271582031249963], + [123.58261718750006, -5.36738281250004], + [123.54277343750002, -5.271093749999963], + [123.62675781250007, -5.271582031249963] + ] + ], + [ + [ + [122.04296874999997, -5.437988281250028], + [121.80849609375, -5.256152343750017], + [121.91367187500012, -5.072265624999957], + [122.04101562500003, -5.158789062499991], + [122.04296874999997, -5.437988281250028] + ] + ], + [ + [ + [122.64511718750012, -5.26943359374998], + [122.5638671875, -5.3875], + [122.28310546875, -5.319531249999969], + [122.39628906250002, -5.069824218749986], + [122.36894531250007, -4.767187499999977], + [122.70195312500002, -4.61865234375], + [122.75986328125012, -4.933886718750003], + [122.61406250000007, -5.138671874999986], + [122.64511718750012, -5.26943359374998] + ] + ], + [ + [ + [123.17978515625006, -4.551171875000023], + [123.195703125, -4.82265625], + [123.05517578124997, -4.748242187500026], + [122.97167968750003, -5.138476562500031], + [123.18730468750007, -5.333007812499957], + [122.96875, -5.405761718749943], + [122.81210937500012, -5.671289062499952], + [122.64501953124997, -5.663378906250031], + [122.58642578124997, -5.488867187500006], + [122.76650390625005, -5.210156249999983], + [122.85332031250007, -4.618359375000026], + [123.074609375, -4.38691406250004], + [123.17978515625006, -4.551171875000023] + ] + ], + [ + [ + [133.57080078124997, -4.245898437500003], + [133.621875, -4.299316406249957], + [133.32089843750006, -4.111035156249969], + [133.57080078124997, -4.245898437500003] + ] + ], + [ + [ + [123.2423828125001, -4.112988281250011], + [123.07617187499997, -4.227148437499991], + [122.96904296875002, -4.029980468749969], + [123.21191406250003, -3.997558593750028], + [123.2423828125001, -4.112988281250011] + ] + ], + [ + [ + [128.56259765625012, -3.58544921875], + [128.39160156250003, -3.637890625000026], + [128.45156250000005, -3.514746093749991], + [128.56259765625012, -3.58544921875] + ] + ], + [ + [ + [128.2755859375001, -3.67460937499996], + [127.97802734374997, -3.770996093749972], + [127.925, -3.69931640625002], + [128.32910156249997, -3.51591796874996], + [128.2755859375001, -3.67460937499996] + ] + ], + [ + [ + [116.42412109375007, -3.464453124999963], + [116.38779296875012, -3.636718749999972], + [116.3265625, -3.539062499999972], + [116.42412109375007, -3.464453124999963] + ] + ], + [ + [ + [116.30332031250006, -3.868164062499957], + [116.05878906250004, -4.006933593749991], + [116.06357421875006, -3.457910156249952], + [116.26972656250004, -3.251074218750006], + [116.30332031250006, -3.868164062499957] + ] + ], + [ + [ + [126.86113281250007, -3.087890624999986], + [127.22734375000007, -3.391015625], + [127.22958984375006, -3.633007812500011], + [126.68632812500007, -3.823632812500037], + [126.21455078125004, -3.605175781250026], + [126.05654296875, -3.420996093749991], + [126.08828125, -3.105468750000014], + [126.86113281250007, -3.087890624999986] + ] + ], + [ + [ + [106.88642578125004, -3.005273437500023], + [106.7428710937501, -2.932812500000011], + [106.91064453124997, -2.93398437499998], + [106.88642578125004, -3.005273437500023] + ] + ], + [ + [ + [129.75468750000007, -2.865820312500034], + [130.3791015625001, -2.989355468749977], + [130.56992187500006, -3.130859375000028], + [130.85996093750006, -3.570312500000028], + [130.805078125, -3.85771484374996], + [129.844140625, -3.327148437499957], + [129.51171875000003, -3.32851562499998], + [129.46767578125005, -3.453222656249977], + [128.8625, -3.234960937500006], + [128.51660156249997, -3.449121093750037], + [128.13203125000004, -3.157421875000026], + [127.90234374999997, -3.496289062499955], + [127.87792968749997, -3.222070312499966], + [128.19853515625002, -2.865917968749969], + [128.99111328125, -2.82851562499998], + [129.17441406250006, -2.933496093749966], + [129.48417968750002, -2.785742187499977], + [129.75468750000007, -2.865820312500034] + ] + ], + [ + [ + [100.42509765625007, -3.182910156249974], + [100.46513671875007, -3.32851562499998], + [100.20429687500004, -2.98681640625], + [100.19853515625002, -2.785546875000023], + [100.45458984375003, -3.001953124999972], + [100.42509765625007, -3.182910156249974] + ] + ], + [ + [ + [108.2072265625001, -2.997656249999977], + [108.05527343750006, -3.22685546874996], + [107.85820312500002, -3.086328125000023], + [107.61445312500004, -3.209375], + [107.56347656250003, -2.920117187499997], + [107.66630859375002, -2.566308593750037], + [107.83779296875005, -2.530273437499972], + [108.21513671875002, -2.696972656250011], + [108.29062500000012, -2.829980468750023], + [108.2072265625001, -2.997656249999977] + ] + ], + [ + [ + [100.20410156249997, -2.741015625000017], + [100.01494140625007, -2.819726562499966], + [99.98789062500006, -2.525390624999957], + [100.20410156249997, -2.741015625000017] + ] + ], + [ + [ + [99.84306640625007, -2.343066406250031], + [99.60703125000012, -2.257519531250011], + [99.57216796875005, -2.025781249999966], + [99.84306640625007, -2.343066406250031] + ] + ], + [ + [ + [126.055078125, -2.451269531249963], + [125.86289062500006, -2.077148437499943], + [125.92275390625, -1.974804687499969], + [126.055078125, -2.451269531249963] + ] + ], + [ + [ + [126.02421875000007, -1.789746093750011], + [126.33173828125004, -1.822851562500006], + [125.47919921875004, -1.940039062499991], + [125.38720703124997, -1.843066406249946], + [126.02421875000007, -1.789746093750011] + ] + ], + [ + [ + [130.35332031250007, -1.690527343749963], + [130.41884765625, -1.971289062499963], + [130.24804687500003, -2.047753906249994], + [129.7376953125, -1.866894531250011], + [130.35332031250007, -1.690527343749963] + ] + ], + [ + [ + [124.96953125000007, -1.70546875], + [125.18789062500005, -1.712890624999986], + [125.31406250000006, -1.877148437499969], + [124.41777343750002, -2.005175781250031], + [124.32968750000012, -1.858886718749972], + [124.41757812500006, -1.659277343749991], + [124.96953125000007, -1.70546875] + ] + ], + [ + [ + [135.47421875000006, -1.591796875000014], + [136.89257812500003, -1.799707031249994], + [136.22812500000012, -1.893652343749949], + [135.47421875000006, -1.591796875000014] + ] + ], + [ + [ + [108.953125, -1.61962890625], + [108.83789062499997, -1.661621093750028], + [108.80371093750003, -1.567773437499994], + [108.953125, -1.61962890625] + ] + ], + [ + [ + [106.04570312500002, -1.669433593750014], + [106.36591796875004, -2.464843749999972], + [106.81845703125006, -2.573339843749963], + [106.6120117187501, -2.895507812499957], + [106.66718750000004, -3.071777343749986], + [105.99873046875004, -2.824902343749955], + [105.7858398437501, -2.18134765625004], + [105.13339843750012, -2.042578125], + [105.45957031250006, -1.574707031249986], + [105.58544921875003, -1.526757812499994], + [105.7008789062501, -1.731054687499963], + [105.7204101562501, -1.533886718750026], + [105.91005859375, -1.504980468749991], + [106.04570312500002, -1.669433593750014] + ] + ], + [ + [ + [123.59755859375, -1.704296875000011], + [123.48251953125006, -1.681445312499974], + [123.52851562500004, -1.502832031250009], + [123.59755859375, -1.704296875000011] + ] + ], + [ + [ + [128.1530273437501, -1.66054687499998], + [127.56162109375012, -1.728515624999972], + [127.39501953125003, -1.589843749999972], + [127.64667968750004, -1.332421875], + [128.1530273437501, -1.66054687499998] + ] + ], + [ + [ + [123.2123046875, -1.171289062499966], + [123.23779296874997, -1.389355468749983], + [123.43476562500004, -1.236816406249986], + [123.54726562500005, -1.337402343749957], + [123.51191406250004, -1.447363281249977], + [123.27490234374997, -1.437207031249955], + [123.17294921875006, -1.616015624999974], + [123.15039062500003, -1.304492187500003], + [122.89042968750007, -1.58720703124996], + [122.81083984375002, -1.432128906249986], + [122.90800781250002, -1.182226562499963], + [123.2123046875, -1.171289062499966] + ] + ], + [ + [ + [109.71025390625007, -1.1806640625], + [109.46367187500002, -1.277539062500026], + [109.4759765625, -0.9853515625], + [109.74335937500004, -1.039355468749989], + [109.71025390625007, -1.1806640625] + ] + ], + [ + [ + [134.96533203124997, -1.116015624999974], + [134.86171875, -1.114160156249952], + [134.82792968750002, -0.978808593750003], + [134.99628906250004, -1.03408203124998], + [134.96533203124997, -1.116015624999974] + ] + ], + [ + [ + [99.16386718750007, -1.777929687500006], + [98.82773437500006, -1.609960937499977], + [98.60175781250004, -1.197851562499949], + [98.67607421875007, -0.970507812500003], + [98.93261718750003, -0.954003906250009], + [99.2672851562501, -1.62773437499996], + [99.16386718750007, -1.777929687500006] + ] + ], + [ + [ + [131.00185546875005, -1.315527343750034], + [130.78232421875006, -1.255468749999963], + [130.67294921875006, -0.959765625000031], + [131.03300781250007, -0.917578124999963], + [131.00185546875005, -1.315527343750034] + ] + ], + [ + [ + [135.38300781250004, -0.6513671875], + [135.89355468749997, -0.725781249999969], + [136.37529296875007, -1.094042968750031], + [136.1647460937501, -1.214746093750023], + [135.91503906250003, -1.178417968749997], + [135.74707031249997, -0.823046874999974], + [135.64570312500004, -0.881933593749991], + [135.38300781250004, -0.6513671875] + ] + ], + [ + [ + [127.30039062500012, -0.780957031250026], + [127.1564453125001, -0.760937500000026], + [127.20908203125006, -0.619335937499955], + [127.30039062500012, -0.780957031250026] + ] + ], + [ + [ + [130.6266601562501, -0.528710937499966], + [130.46542968750006, -0.486523437499983], + [130.6159179687501, -0.417285156250003], + [130.6266601562501, -0.528710937499966] + ] + ], + [ + [ + [121.86435546875012, -0.406835937500006], + [121.88125, -0.502636718749983], + [121.65527343749997, -0.526171874999989], + [121.86435546875012, -0.406835937500006] + ] + ], + [ + [ + [140.97343750000007, -2.609765625], + [140.97353515625, -2.803417968750026], + [140.975, -6.346093750000023], + [140.86230468749997, -6.740039062499989], + [140.97519531250006, -6.90537109375002], + [140.97617187500012, -9.11875], + [140.00292968749997, -8.19550781250004], + [140.11699218750002, -7.923730468750009], + [139.93476562500004, -8.101171875], + [139.38564453125, -8.189062499999963], + [139.24882812500002, -7.982421874999972], + [138.890625, -8.237792968749943], + [139.08798828125012, -7.587207031250017], + [138.74794921875, -7.25146484375], + [139.17685546875006, -7.1904296875], + [138.84570312500003, -7.13632812499999], + [138.60136718750007, -6.936523437499972], + [138.86455078125007, -6.858398437499943], + [138.43867187500004, -6.343359375], + [138.2962890625, -5.94902343749996], + [138.37460937500006, -5.84365234374998], + [138.19960937500005, -5.80703125], + [138.33964843750007, -5.675683593749966], + [138.08710937500004, -5.70917968750004], + [138.06083984375002, -5.46523437499998], + [137.27978515624997, -4.945410156249949], + [136.61884765625004, -4.81875], + [135.97968750000004, -4.530859374999963], + [135.19560546875007, -4.450683593749972], + [134.67968749999997, -4.079101562499943], + [134.70654296875003, -3.954785156250026], + [134.88652343750007, -3.938476562499986], + [134.26621093750012, -3.945800781249972], + [134.14707031250006, -3.79677734374998], + [133.97382812500004, -3.817968750000034], + [133.67832031250006, -3.4794921875], + [133.8415039062501, -3.054785156249991], + [133.70039062500004, -3.0875], + [133.653125, -3.364355468749991], + [133.51816406250012, -3.411914062500003], + [133.40087890625003, -3.899023437500034], + [133.24873046875004, -4.062304687499989], + [132.91445312500005, -4.05693359374996], + [132.75390625000003, -3.703613281250014], + [132.86972656250006, -3.550976562499997], + [132.75136718750005, -3.294628906249997], + [131.97119140624997, -2.788574218750014], + [132.2306640625001, -2.680371093749997], + [132.725, -2.789062500000028], + [133.19101562500006, -2.43779296874996], + [133.70009765625005, -2.624609375], + [133.75332031250005, -2.450683593750014], + [133.90488281250012, -2.390917968750003], + [133.79101562500003, -2.293652343749997], + [133.92158203125004, -2.102050781249957], + [132.96279296875005, -2.272558593749963], + [132.30761718749997, -2.24228515625002], + [132.02343749999997, -1.99033203125002], + [131.93037109375004, -1.559667968750034], + [131.29375, -1.393457031250009], + [130.99589843750007, -1.42470703124998], + [131.1908203125, -1.165820312500003], + [131.2572265625, -0.855468750000014], + [131.80429687500006, -0.703808593750026], + [132.39375, -0.355468750000028], + [132.85644531250003, -0.417382812500023], + [133.47265624999997, -0.726171874999963], + [133.97451171875, -0.744335937500026], + [134.11152343750004, -0.84677734375002], + [134.07197265625004, -1.001855468749994], + [134.25957031250007, -1.362988281250026], + [134.105859375, -1.720996093749946], + [134.19482421875003, -2.309082031249943], + [134.45996093749997, -2.83232421874996], + [134.48330078125, -2.583007812499972], + [134.62744140624997, -2.536718749999963], + [134.70214843749997, -2.933593749999986], + [134.84335937500006, -2.909179687499986], + [134.88681640625006, -3.209863281249966], + [135.25156250000012, -3.368554687499966], + [135.48662109375002, -3.34511718749998], + [135.85917968750002, -2.99531250000004], + [136.38994140625002, -2.273339843750037], + [137.07207031250002, -2.105078124999949], + [137.1710937500001, -2.025488281249991], + [137.1234375, -1.840917968749963], + [137.80625000000012, -1.483203125], + [139.78955078125003, -2.34824218750002], + [140.62255859374997, -2.44580078125], + [140.74746093750005, -2.607128906249997], + [140.97343750000007, -2.609765625] + ] + ], + [ + [ + [104.47421875000012, -0.334667968749955], + [104.59013671875002, -0.466601562500017], + [104.36318359375, -0.658593749999966], + [104.25712890625002, -0.463281249999966], + [104.47421875000012, -0.334667968749955] + ] + ], + [ + [ + [127.56699218750006, -0.318945312499949], + [127.68242187500002, -0.46835937500002], + [127.60498046874997, -0.610156249999946], + [127.88017578125002, -0.808691406249991], + [127.7611328125, -0.883691406249994], + [127.62382812500002, -0.76601562499999], + [127.46269531250002, -0.80595703124996], + [127.46865234375, -0.64296875], + [127.3, -0.500292968749946], + [127.32509765625, -0.335839843750023], + [127.45517578125012, -0.406347656249991], + [127.56699218750006, -0.318945312499949] + ] + ], + [ + [ + [127.24990234375005, -0.4953125], + [127.11914062500003, -0.520507812499986], + [127.12646484375003, -0.278613281250003], + [127.29003906250003, -0.284375], + [127.24990234375005, -0.4953125] + ] + ], + [ + [ + [103.73652343750004, -0.347949218750003], + [103.461328125, -0.357617187500011], + [103.54892578125006, -0.227539062499986], + [103.73652343750004, -0.347949218750003] + ] + ], + [ + [ + [130.81328125000007, -0.004101562500026], + [131.27685546875003, -0.149804687499952], + [131.33974609375005, -0.290332031249989], + [131.00537109374997, -0.360742187500037], + [130.62216796875006, -0.0859375], + [130.89921875000002, -0.344433593749997], + [130.7501953125001, -0.44384765625], + [130.6886718750001, -0.296582031250011], + [130.55078124999997, -0.366406250000026], + [130.23662109375002, -0.209667968749983], + [130.3625, -0.072851562500006], + [130.81328125000007, -0.004101562500026] + ] + ], + [ + [ + [98.45927734375007, -0.530468749999969], + [98.30966796875012, -0.531835937499977], + [98.4271484375, -0.226464843750037], + [98.3229492187501, -0.000781249999974], + [98.54414062500004, -0.257617187499989], + [98.45927734375007, -0.530468749999969] + ] + ], + [ + [ + [104.77861328125007, -0.175976562499955], + [105.00537109374997, -0.282812499999963], + [104.44707031250002, -0.189160156249983], + [104.54267578125004, 0.01772460937498], + [104.77861328125007, -0.175976562499955] + ] + ], + [ + [ + [103.28447265625002, 0.541943359375011], + [103.13955078125, 0.549072265625043], + [103.18740234375, 0.699755859375017], + [103.28447265625002, 0.541943359375011] + ] + ], + [ + [ + [103.0275390625001, 0.746630859374974], + [102.4904296875001, 0.856640625], + [102.50664062500002, 1.088769531250037], + [103.00244140624997, 0.859277343750009], + [103.0275390625001, 0.746630859374974] + ] + ], + [ + [ + [103.42392578125012, 1.048339843749972], + [103.31542968750003, 1.071289062500028], + [103.37998046875006, 1.133642578125034], + [103.42392578125012, 1.048339843749972] + ] + ], + [ + [ + [103.16640625000005, 0.870166015625003], + [102.7018554687501, 1.0537109375], + [102.72558593749997, 1.158837890625023], + [102.99941406250005, 1.067773437500023], + [103.16640625000005, 0.870166015625003] + ] + ], + [ + [ + [104.02480468750005, 1.180566406250009], + [104.13984375000004, 1.165576171874974], + [104.06611328125004, 0.989550781249989], + [103.93222656250012, 1.071386718749963], + [104.02480468750005, 1.180566406250009] + ] + ], + [ + [ + [104.58535156250005, 1.21611328124996], + [104.66289062500002, 1.04951171875004], + [104.57519531250003, 0.831933593750037], + [104.43925781250002, 1.050439453125051], + [104.25195312499997, 1.014892578125], + [104.36181640624997, 1.18149414062502], + [104.58535156250005, 1.21611328124996] + ] + ], + [ + [ + [102.4271484375, 0.990136718750023], + [102.27958984375002, 1.075683593750043], + [102.25634765625003, 1.397070312499963], + [102.44287109374997, 1.234228515625006], + [102.4271484375, 0.990136718750023] + ] + ], + [ + [ + [97.48154296875006, 1.465087890624972], + [97.93193359375002, 0.973925781250003], + [97.82041015625012, 0.564453124999986], + [97.683984375, 0.596093750000037], + [97.60390625000005, 0.83388671874998], + [97.40537109375012, 0.946972656250026], + [97.07919921875006, 1.425488281249983], + [97.35595703124997, 1.539746093749997], + [97.48154296875006, 1.465087890624972] + ] + ], + [ + [ + [102.49189453125004, 1.459179687500011], + [102.49941406250005, 1.330908203124991], + [102.02402343750012, 1.607958984375031], + [102.49189453125004, 1.459179687500011] + ] + ], + [ + [ + [124.88886718750004, 0.995312500000011], + [124.42753906250002, 0.470605468750051], + [123.75380859375, 0.305517578124991], + [123.26542968750007, 0.326611328125026], + [122.996875, 0.493505859375006], + [121.01298828125002, 0.441699218750017], + [120.57900390625, 0.5283203125], + [120.19228515625, 0.268505859374997], + [120.01328125000012, -0.196191406249994], + [120.062890625, -0.555566406250023], + [120.240625, -0.868261718749949], + [120.51757812499997, -1.039453125], + [120.66738281250005, -1.370117187499972], + [121.14853515625012, -1.33945312500002], + [121.5755859375, -0.828515625000023], + [121.96962890625005, -0.933300781249969], + [122.27998046875004, -0.757031250000026], + [122.88876953125006, -0.755175781250003], + [122.8294921875, -0.658886718750026], + [123.17148437500012, -0.57070312499999], + [123.37968750000002, -0.648535156249949], + [123.43417968750006, -0.778222656249994], + [123.37792968749997, -1.004101562500011], + [122.90283203125003, -0.900976562499963], + [122.25068359375004, -1.555273437500034], + [121.8585937500001, -1.69326171874998], + [121.65097656250006, -1.895410156249952], + [121.35546874999997, -1.878222656250003], + [122.29169921875004, -2.907617187500023], + [122.39902343750006, -3.200878906249997], + [122.25292968749997, -3.620410156250017], + [122.68964843750004, -4.084472656249972], + [122.84794921875002, -4.064550781250006], + [122.8722656250001, -4.391992187500009], + [122.71972656250003, -4.340722656249952], + [122.11425781250003, -4.540234375000011], + [122.03808593749997, -4.832421875000023], + [121.58867187500007, -4.759570312500017], + [121.48652343750004, -4.581054687499972], + [121.61806640625, -4.092675781249952], + [120.89179687500004, -3.520605468750034], + [121.05429687500012, -3.167089843749949], + [121.0521484375, -2.751660156249955], + [120.87939453124997, -2.64560546875002], + [120.65361328125002, -2.667578124999977], + [120.26103515625007, -2.949316406249991], + [120.43662109375012, -3.70732421874996], + [120.42011718750004, -4.617382812500011], + [120.27929687499997, -5.146093749999977], + [120.4303710937501, -5.591015625000026], + [119.9515625, -5.577636718749972], + [119.71728515625003, -5.693359375000014], + [119.55742187500007, -5.611035156250026], + [119.36035156249997, -5.314160156250026], + [119.59404296875007, -4.523144531249997], + [119.62363281250006, -4.034375], + [119.46748046875004, -3.512988281249989], + [118.99462890624997, -3.537597656250028], + [118.86767578124997, -3.39804687500002], + [118.78330078125006, -2.720800781249977], + [119.09218750000005, -2.482910156250014], + [119.32187500000012, -1.929687500000014], + [119.308984375, -1.408203125], + [119.508203125, -0.906738281249972], + [119.71132812500005, -0.680761718750034], + [119.84433593750006, -0.861914062499991], + [119.721875, -0.088476562499991], + [119.865625, 0.040087890625003], + [119.80927734375004, 0.238671875000051], + [119.9132812500001, 0.445068359375], + [120.26953125000003, 0.970800781249991], + [120.60253906249997, 0.854394531249994], + [120.86796875000007, 1.25283203124998], + [121.0817382812501, 1.327636718750028], + [121.40410156250002, 1.243603515624969], + [121.59179687499997, 1.067968749999977], + [122.43662109375006, 1.018066406250028], + [122.83828125, 0.845703125], + [123.06650390625006, 0.941796875000037], + [123.93076171875006, 0.850439453124977], + [124.53369140624997, 1.230468750000043], + [124.94707031250002, 1.672167968749974], + [125.11093750000012, 1.685693359374966], + [125.2337890625, 1.502294921875006], + [124.88886718750004, 0.995312500000011] + ] + ], + [ + [ + [101.70810546875006, 2.078417968750045], + [101.71943359375004, 1.789160156250006], + [101.50078125000002, 1.733203124999974], + [101.40966796875003, 2.021679687500026], + [101.70810546875006, 2.078417968750045] + ] + ], + [ + [ + [127.73271484375007, 0.848144531250043], + [127.8810546875001, 0.832128906249977], + [127.96728515624997, 1.042578125000048], + [128.16074218750006, 1.1578125], + [128.22246093750002, 1.400634765624986], + [128.68837890625, 1.572558593750017], + [128.70263671874997, 1.106396484374997], + [128.29882812500003, 0.87680664062502], + [128.26064453125, 0.733789062500023], + [128.61123046875, 0.549951171875051], + [128.89960937500004, 0.216259765625011], + [127.9831054687501, 0.471875], + [127.88740234375004, 0.298339843750043], + [127.97783203125002, -0.24833984374996], + [128.4254882812501, -0.892675781249949], + [128.04638671875003, -0.706054687499943], + [127.69160156250004, -0.241894531249983], + [127.70869140625004, 0.288085937499986], + [127.53710937500003, 0.610888671875031], + [127.60800781250006, 0.848242187499977], + [127.42851562500002, 1.139990234374991], + [127.63173828125, 1.843701171875011], + [128.03642578125002, 2.199023437500017], + [127.88681640625012, 1.83295898437504], + [128.0109375000001, 1.701220703125031], + [128.01171874999997, 1.331738281249983], + [127.65283203124997, 1.013867187499969], + [127.73271484375007, 0.848144531250043] + ] + ], + [ + [ + [97.3341796875001, 2.075634765625011], + [97.10830078125, 2.216894531250006], + [97.29140625, 2.200830078125023], + [97.3341796875001, 2.075634765625011] + ] + ], + [ + [ + [128.45390625000002, 2.051757812500028], + [128.29589843749997, 2.034716796875017], + [128.2179687500001, 2.297460937499991], + [128.60214843750012, 2.59760742187504], + [128.68847656250003, 2.473681640625017], + [128.62324218750004, 2.224414062500031], + [128.45390625000002, 2.051757812500028] + ] + ], + [ + [ + [96.46367187500002, 2.360009765625037], + [95.80859374999997, 2.655615234375034], + [95.7171875, 2.825976562500017], + [95.89580078125007, 2.8890625], + [96.41728515625007, 2.515185546875031], + [96.46367187500002, 2.360009765625037] + ] + ], + [ + [ + [108.8875, 2.905419921875037], + [108.7865234375, 2.885644531250009], + [108.88574218750003, 2.998974609374997], + [108.8875, 2.905419921875037] + ] + ], + [ + [ + [105.76035156250006, 2.863037109375014], + [105.69218750000002, 3.0625], + [105.83671875000007, 2.97651367187504], + [105.76035156250006, 2.863037109375014] + ] + ], + [ + [ + [106.28525390625006, 3.15712890624998], + [106.28369140624997, 3.088232421874977], + [106.20097656250002, 3.204882812500031], + [106.28525390625006, 3.15712890624998] + ] + ], + [ + [ + [117.65839843750004, 3.280517578124986], + [117.54785156250003, 3.43198242187502], + [117.68085937500004, 3.407519531250017], + [117.65839843750004, 3.280517578124986] + ] + ], + [ + [ + [125.65810546875, 3.436035156250043], + [125.51152343750007, 3.461132812500011], + [125.46884765625006, 3.73325195312502], + [125.65810546875, 3.436035156250043] + ] + ], + [ + [ + [117.88476562499997, 4.186132812500006], + [117.92285156250003, 4.054296874999977], + [117.73681640624997, 4.004003906250034], + [117.64902343750012, 4.168994140624974], + [117.88476562499997, 4.186132812500006] + ] + ], + [ + [ + [108.31601562500006, 3.689648437500026], + [108.10039062500002, 3.70454101562504], + [108.24326171875006, 3.810351562500017], + [108.00234375, 3.982861328124983], + [108.24833984375002, 4.21713867187502], + [108.39287109375007, 3.986181640625034], + [108.31601562500006, 3.689648437500026] + ] + ], + [ + [ + [117.5744140625001, 4.17060546875004], + [117.46533203124997, 4.076074218749966], + [117.77724609375005, 3.689257812500031], + [117.05595703125007, 3.622656249999963], + [117.34628906250006, 3.426611328124991], + [117.35244140625, 3.19375], + [117.61064453125002, 3.064355468749994], + [117.56914062500002, 2.92929687500002], + [117.69765625, 2.887304687499991], + [117.6388671875001, 2.825292968749963], + [118.0666015625001, 2.317822265624969], + [117.7892578125001, 2.026855468750014], + [118.98496093750006, 0.982128906249983], + [118.53476562500006, 0.813525390625017], + [118.19609375000002, 0.874365234374977], + [117.91162109374997, 1.098681640625017], + [117.96425781250005, 0.889550781250051], + [117.74511718749997, 0.72963867187498], + [117.52216796875004, 0.235888671875017], + [117.46289062500003, -0.323730468749957], + [117.5625, -0.770898437500009], + [116.91396484375, -1.223632812499972], + [116.73984375000006, -1.044238281250017], + [116.75341796874997, -1.327343749999955], + [116.27548828125006, -1.784863281249997], + [116.42431640625003, -1.784863281249997], + [116.45195312500002, -1.923144531250017], + [116.31396484374997, -2.139843750000011], + [116.56542968749997, -2.299707031249994], + [116.52929687499997, -2.51054687499996], + [116.31679687500005, -2.55185546875002], + [116.33066406250012, -2.902148437499974], + [116.16630859375002, -2.934570312500014], + [116.2572265625, -3.126367187500009], + [115.95615234375012, -3.595019531250003], + [114.6935546875001, -4.169726562500017], + [114.5255859375001, -3.376660156250011], + [114.44599609375004, -3.481835937500037], + [114.34433593750012, -3.444433593749963], + [114.34433593750012, -3.23515625], + [114.23632812500003, -3.36113281249996], + [114.0822265625001, -3.27890625], + [113.70507812499997, -3.45527343750004], + [113.6100585937501, -3.195703125], + [113.34316406250005, -3.246484374999966], + [113.03398437500002, -2.933496093749966], + [112.97148437500002, -3.187109375000034], + [112.75800781250004, -3.322167968750009], + [112.60029296875004, -3.400488281249977], + [112.28496093750002, -3.32099609375004], + [111.85810546875004, -3.551855468750006], + [111.82304687500007, -3.057226562499949], + [111.69472656250005, -2.88945312499996], + [110.93007812500005, -3.071093750000017], + [110.82968750000012, -2.9951171875], + [110.89931640625, -2.908593749999952], + [110.703125, -3.020898437500009], + [110.57402343750007, -2.89140625], + [110.25605468750004, -2.966113281249946], + [110.09658203125, -2.001367187499966], + [109.95986328125, -1.862792968749972], + [109.98330078125, -1.274804687499994], + [109.78740234375007, -1.011328124999963], + [109.25878906250003, -0.807421874999989], + [109.37275390625004, -0.638183593749972], + [109.12109375000003, -0.39091796874996], + [109.2575195312501, 0.031152343750051], + [108.94453125000004, 0.355664062499997], + [108.91679687500007, 0.912646484375045], + [108.95859375000006, 1.134619140624963], + [109.1315429687501, 1.253857421875011], + [109.01025390624997, 1.239648437500051], + [109.07587890625004, 1.495898437500031], + [109.37851562500006, 1.922705078125034], + [109.62890625000003, 2.027539062499983], + [109.53896484375, 1.89619140625004], + [109.65400390625004, 1.614892578125023], + [110.50576171875005, 0.861962890625023], + [111.10136718750002, 1.050537109374986], + [111.80898437500005, 1.011669921874969], + [112.078515625, 1.143359374999974], + [112.1857421875001, 1.4390625], + [112.47617187500006, 1.559082031250028], + [112.94296875000006, 1.566992187500034], + [113.00654296875004, 1.433886718750003], + [113.6222656250001, 1.2359375], + [113.90234375000003, 1.434277343749997], + [114.5125, 1.452001953124963], + [114.83056640625003, 1.980029296874989], + [114.78642578125002, 2.250488281250014], + [115.1791015625, 2.523193359374972], + [115.08076171875004, 2.63422851562504], + [115.117578125, 2.89487304687502], + [115.24697265625005, 3.025927734374989], + [115.45439453125002, 3.034326171875009], + [115.67880859375006, 4.193017578124994], + [115.86074218750005, 4.348046875000037], + [116.51474609375006, 4.370800781249969], + [117.10058593750003, 4.337060546875023], + [117.5744140625001, 4.17060546875004] + ] + ], + [ + [ + [126.81660156250004, 4.033496093750003], + [126.70449218750005, 4.070996093749997], + [126.81357421875006, 4.258496093750011], + [126.72207031250005, 4.344189453124969], + [126.75732421874997, 4.547900390624989], + [126.9210937500001, 4.291015624999972], + [126.81660156250004, 4.033496093750003] + ] + ], + [ + [ + [96.49257812500005, 5.229345703124991], + [97.54716796875002, 5.205859375], + [98.2484375, 4.41455078125], + [98.3073242187501, 4.09287109375002], + [99.73232421875005, 3.183056640625026], + [100.523828125, 2.18916015625004], + [100.88789062500004, 1.948242187499986], + [100.82822265625012, 2.242578125], + [101.04619140625002, 2.257470703125023], + [101.47666015625006, 1.693066406250054], + [102.019921875, 1.442138671875], + [102.38994140625007, 0.84199218750004], + [103.03183593750006, 0.57890625], + [103.0075195312501, 0.415332031249974], + [102.55, 0.216455078124966], + [103.33896484375012, 0.513720703125045], + [103.67265625000007, 0.288916015624977], + [103.78671875000012, 0.046972656249991], + [103.42851562500007, -0.19179687499998], + [103.40517578125005, -0.36220703124998], + [103.5091796875, -0.465527343749969], + [103.43857421875006, -0.575585937500009], + [103.72109375, -0.886718749999986], + [104.36054687500004, -1.038378906249974], + [104.51591796875002, -1.81943359375002], + [104.84521484375003, -2.092968749999969], + [104.65078125000005, -2.595214843749972], + [104.97080078125012, -2.370898437500017], + [105.39697265624997, -2.380175781249946], + [106.0443359375, -3.10625], + [105.84375, -3.61367187499998], + [105.93046875000007, -3.833007812499986], + [105.83144531250005, -4.16289062499996], + [105.88720703124997, -5.009570312499974], + [105.74833984375007, -5.818261718749966], + [105.34941406250007, -5.549511718750011], + [105.08134765625002, -5.74550781249998], + [104.63955078125005, -5.520410156250037], + [104.68398437500005, -5.89267578125002], + [104.60156249999997, -5.90458984374996], + [103.8314453125, -5.079589843750028], + [102.53769531250006, -4.152148437499989], + [102.12753906250006, -3.599218749999963], + [101.57861328124997, -3.166992187500014], + [100.88955078125, -2.248535156249957], + [100.85527343750002, -1.934179687499949], + [100.30820312500006, -0.82666015625], + [99.66982421875005, 0.045068359375037], + [99.15917968749997, 0.351757812499997], + [98.59531250000006, 1.864599609375006], + [97.70078125000006, 2.358544921875009], + [97.59082031249997, 2.846582031250037], + [97.3913085937501, 2.975292968749969], + [96.9689453125001, 3.575146484374969], + [96.44472656250005, 3.81630859374998], + [95.57861328125003, 4.661962890625048], + [95.20664062500006, 5.284033203125034], + [95.22783203125002, 5.564794921875034], + [95.62890625000003, 5.609082031249997], + [96.13330078125003, 5.294287109374991], + [96.49257812500005, 5.229345703124991] + ] + ] + ] + }, + "properties": { "name": "Indonesia", "childNum": 107 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [-4.412060546874983, 54.185351562499996], + [-4.785351562499983, 54.073046875], + [-4.424707031249994, 54.407177734375], + [-4.412060546874983, 54.185351562499996] + ] + ] + }, + "properties": { "name": "Isle of Man", "childNum": 1 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [75.32221348233018, 32.28516356678968], + [75.62496871116024, 32.28516356678968], + [75.73585997688717, 32.78417426256088], + [76.32728006076415, 32.87658365066666], + [76.62299010270264, 33.32014871357439], + [77.06655516561037, 33.301666835953235], + [77.71342088235082, 32.6917648744551], + [78.10154031239509, 32.87658365066666], + [78.49194250885338, 32.53122786149202], + [78.38964843749997, 32.51987304687498], + [78.41748046874997, 32.466699218749994], + [78.4552734375001, 32.30034179687502], + [78.49589843750002, 32.21577148437504], + [78.72558593750009, 31.983789062500023], + [78.71972656250009, 31.887646484374983], + [78.69345703125006, 31.740380859374994], + [78.7550781250001, 31.55029296875], + [78.74355468750005, 31.323779296875017], + [79.10712890625004, 31.402636718750102], + [79.38847656250013, 31.064208984375085], + [79.66425781250004, 30.96523437499999], + [79.92451171875004, 30.888769531250034], + [80.20712890625006, 30.683740234375023], + [80.19121093750002, 30.56840820312496], + [80.87353515625003, 30.290576171875045], + [80.98544921875006, 30.23710937499999], + [81.01025390625014, 30.164501953125097], + [80.96611328125002, 30.180029296875063], + [80.90761718750005, 30.171923828125017], + [80.84814453125009, 30.139746093750034], + [80.81992187500012, 30.119335937499955], + [80.68408203125014, 29.994335937500068], + [80.54902343750015, 29.899804687499994], + [80.40185546875003, 29.730273437500102], + [80.31689453125014, 29.572070312500017], + [80.25488281250009, 29.423339843750114], + [80.25595703125006, 29.318017578125136], + [80.23300781250006, 29.194628906250045], + [80.16953125000012, 29.124316406250102], + [80.13046875000006, 29.100390625000045], + [80.08457031249995, 28.994189453125074], + [80.05166015625, 28.870312500000068], + [80.07070312500005, 28.830175781250063], + [80.22656250000003, 28.723339843750125], + [80.32480468750012, 28.66640625000008], + [80.41855468749995, 28.61201171875001], + [80.47910156250012, 28.604882812499994], + [80.49580078125015, 28.635791015625074], + [80.51787109375002, 28.665185546875023], + [80.58701171875006, 28.64960937500004], + [81.16894531250014, 28.335009765625074], + [81.85263671875018, 27.867089843750136], + [81.89687500000011, 27.87446289062504], + [81.94521484375005, 27.89926757812495], + [81.98769531250016, 27.91376953125004], + [82.03701171875, 27.90058593750004], + [82.11191406250006, 27.86494140625004], + [82.28769531250018, 27.756542968749983], + [82.45136718750004, 27.671826171874955], + [82.62988281249997, 27.687060546875045], + [82.67734375000006, 27.67343749999995], + [82.71083984375005, 27.596679687500114], + [82.73339843750003, 27.518994140625097], + [83.28974609375004, 27.370996093750136], + [83.36943359375002, 27.410253906249977], + [83.38398437500004, 27.444824218750085], + [83.44716796875011, 27.46533203125], + [83.55166015625011, 27.456347656249932], + [83.74697265625011, 27.395947265625068], + [83.8288085937501, 27.377832031250108], + [84.09101562499993, 27.491357421875136], + [84.22978515625007, 27.427832031250006], + [84.48085937500005, 27.348193359375102], + [84.61015625000002, 27.298681640624977], + [84.64072265625012, 27.249853515624977], + [84.65478515625014, 27.20366210937499], + [84.65380859375009, 27.09169921875008], + [84.68535156250013, 27.041015625000057], + [85.19179687500011, 26.766552734375097], + [85.29296875000009, 26.741015625000045], + [85.56845703125012, 26.839843750000114], + [85.64843749999997, 26.829003906250023], + [85.69990234375004, 26.781640624999966], + [85.73730468750003, 26.639746093750034], + [85.79453125000006, 26.60415039062505], + [86.00732421875009, 26.64936523437504], + [86.70136718750015, 26.435058593750057], + [87.01640625000002, 26.555419921875085], + [87.2874023437499, 26.360302734375125], + [87.41357421875014, 26.42294921875009], + [87.84921875000006, 26.43691406250008], + [87.99511718750014, 26.38237304687499], + [88.02695312500023, 26.395019531250085], + [88.05488281250004, 26.43002929687492], + [88.11152343750004, 26.58642578125], + [88.1615234375, 26.724804687500125], + [88.15722656250009, 26.807324218750068], + [88.1110351562501, 26.928466796875057], + [87.99316406250009, 27.086083984374994], + [87.984375, 27.133935546874994], + [88.14697265625014, 27.749218750000097], + [88.15029296875011, 27.843310546875074], + [88.10976562500005, 27.87060546874997], + [88.10898437499995, 27.93300781250005], + [88.14111328125003, 27.948925781250097], + [88.27519531250013, 27.96884765625009], + [88.42597656250015, 28.011669921875097], + [88.57792968750002, 28.093359375000034], + [88.80371093750003, 28.006933593750034], + [88.74902343749997, 27.521875000000136], + [88.7648437500001, 27.429882812500068], + [88.83251953125003, 27.362841796875074], + [88.89140625000002, 27.316064453125136], + [88.88164062500007, 27.29746093750009], + [88.76035156250006, 27.21811523437509], + [88.73876953125009, 27.175585937499932], + [88.85761718750015, 26.961474609375017], + [89.14824218750002, 26.816162109375085], + [89.33212890625018, 26.848632812500114], + [89.58613281250004, 26.778955078125136], + [89.60996093750012, 26.719433593750097], + [89.71093750000009, 26.713916015625045], + [89.76386718750004, 26.7015625], + [89.94316406250013, 26.723925781249932], + [90.12294921875011, 26.754589843749983], + [90.20605468749997, 26.847509765625063], + [90.34589843750004, 26.890332031250097], + [90.73964843750005, 26.771679687500068], + [91.2865234375, 26.78994140625008], + [91.42675781249997, 26.867089843749966], + [91.45585937500013, 26.866894531250125], + [91.51757812500009, 26.807324218750068], + [91.67158203124993, 26.80200195312503], + [91.84208984375013, 26.852978515625125], + [91.94375, 26.860839843750114], + [91.99833984375013, 26.85498046875], + [92.04970703125016, 26.87485351562495], + [92.73155507489682, 26.833697862861648], + [93.30975376159499, 26.784950522650554], + [93.61047043679247, 27.32239435188504], + [94.06979001484449, 27.589407158584788], + [95.10800937321915, 27.749636881153737], + [95.74000740838363, 28.116850432722256], + [96.19577594042592, 28.04291597700983], + [96.96279296875, 27.698291015625017], + [96.88359375000013, 27.514843750000125], + [96.90195312500012, 27.43959960937508], + [97.10371093749993, 27.163330078125114], + [97.10205078125003, 27.115429687500125], + [96.95341796875013, 27.13330078125003], + [96.79785156249997, 27.29619140624999], + [96.19082031250005, 27.26127929687499], + [95.20146484375007, 26.641406250000017], + [95.05976562500015, 26.473974609375006], + [95.06894531250006, 26.191113281250097], + [95.10839843750014, 26.091406250000034], + [95.12929687500011, 26.070410156250034], + [95.13242187500006, 26.041259765624943], + [94.99199218750002, 25.77045898437504], + [94.66777343750007, 25.458886718749966], + [94.55302734375013, 25.215722656249994], + [94.70371093750012, 25.097851562499955], + [94.49316406250003, 24.637646484374983], + [94.37724609375002, 24.473730468750006], + [94.29306640625012, 24.321875], + [94.07480468750006, 23.8720703125], + [93.68339843750007, 24.00654296875004], + [93.45214843750003, 23.987402343750034], + [93.32626953125006, 24.064208984375057], + [93.36601562500007, 23.132519531249955], + [93.34941406250007, 23.08496093750003], + [93.20390625000002, 23.03701171875005], + [93.07871093750018, 22.718212890625097], + [93.16201171875, 22.360205078125006], + [93.07060546875002, 22.20942382812501], + [92.96455078125015, 22.003759765625034], + [92.90947265625013, 21.988916015625023], + [92.85429687500002, 22.010156250000108], + [92.77138671875, 22.104785156250017], + [92.68896484375009, 22.130957031250006], + [92.63037109375014, 22.011328124999977], + [92.57490234374993, 21.97807617187496], + [92.5612304687501, 22.04804687500001], + [92.49140625000004, 22.685400390625006], + [92.46445312500006, 22.734423828125045], + [92.36162109375002, 22.929003906250074], + [92.33378906250002, 23.242382812499955], + [92.24609375000003, 23.68359374999997], + [92.04404296875006, 23.677783203125017], + [91.97851562500003, 23.691992187500063], + [91.92958984375011, 23.685986328125097], + [91.92949218750019, 23.598242187499977], + [91.93789062500011, 23.504687500000102], + [91.75419921875013, 23.28730468750004], + [91.75097656250003, 23.053515625000017], + [91.55351562500013, 22.991552734375006], + [91.43623046875004, 23.19990234375001], + [91.359375, 23.06835937500003], + [91.16044921875019, 23.660644531250085], + [91.35019531250012, 24.06049804687501], + [91.72656250000003, 24.20507812499997], + [91.84619140624997, 24.175292968749943], + [92.06416015625004, 24.374365234375006], + [92.11748046875002, 24.493945312500017], + [92.22666015625012, 24.77099609374997], + [92.22832031250002, 24.881347656250085], + [92.2512695312499, 24.895068359375045], + [92.38496093750004, 24.848779296875023], + [92.46835937500018, 24.944140625000074], + [92.04970703125016, 25.16948242187499], + [90.61308593750002, 25.16772460937497], + [90.11962890625003, 25.21997070312497], + [89.86630859375012, 25.293164062499955], + [89.81406250000006, 25.305371093749955], + [89.80087890625012, 25.33613281250001], + [89.82490234375004, 25.56015625], + [89.82294921875015, 25.94140625000003], + [89.67089843750009, 26.213818359375125], + [89.57275390625003, 26.13232421875003], + [89.54990234375006, 26.00527343750008], + [89.28925781250015, 26.037597656250085], + [89.01865234375012, 26.410253906249977], + [88.95195312500002, 26.412109375], + [88.97041015625004, 26.250878906250023], + [88.94072265625002, 26.24536132812497], + [88.68281250000004, 26.291699218749983], + [88.51826171875004, 26.51777343750004], + [88.36992187500002, 26.56411132812508], + [88.35146484375005, 26.482568359374966], + [88.38623046875003, 26.471533203125034], + [88.44042968749997, 26.369482421875034], + [88.33398437499997, 26.257519531249955], + [88.15078125000005, 26.087158203125057], + [88.1066406250001, 25.841113281250045], + [88.14746093749997, 25.811425781250023], + [88.50244140625009, 25.53701171875008], + [88.76914062500006, 25.490478515625], + [88.85478515625002, 25.333544921875017], + [88.94414062500002, 25.290771484375], + [88.92978515625012, 25.222998046875063], + [88.57382812500006, 25.18789062499999], + [88.45625, 25.188427734375125], + [88.37294921875016, 24.961523437500063], + [88.31337890625011, 24.8818359375], + [88.27949218750015, 24.881933593750034], + [88.18886718750016, 24.920605468750097], + [88.14980468750011, 24.91464843749995], + [88.04511718750015, 24.71303710937508], + [88.03027343750009, 24.664453125000136], + [88.02343750000003, 24.627832031250136], + [88.07910156250009, 24.549902343750063], + [88.14550781250003, 24.485791015624955], + [88.225, 24.460644531249983], + [88.3375, 24.45385742187503], + [88.49853515625003, 24.34663085937504], + [88.64228515625015, 24.325976562500102], + [88.72353515625011, 24.27490234375], + [88.7335937500001, 24.230908203125097], + [88.72656250000009, 24.18623046875004], + [88.71376953125016, 24.069628906250102], + [88.69980468750006, 24.00253906249992], + [88.56738281250009, 23.674414062500034], + [88.63574218749997, 23.55], + [88.69765625, 23.493017578125034], + [88.72441406250002, 23.254980468750034], + [88.89707031250018, 23.21040039062501], + [88.92812500000011, 23.186621093749977], + [88.89970703125002, 22.843505859375057], + [88.92070312500002, 22.632031249999955], + [89.05, 22.274609374999983], + [89.02792968750023, 21.937207031249983], + [88.94931640625018, 21.937939453125125], + [89.05166015625, 21.654101562500045], + [88.85751953125012, 21.744677734375017], + [88.74501953125011, 21.584375], + [88.74023437500003, 22.005419921875017], + [88.64160156250003, 22.121972656250136], + [88.58466796875015, 21.659716796874932], + [88.44599609375004, 21.614257812500085], + [88.28750000000016, 21.758203125000108], + [88.25371093750002, 21.622314453124943], + [88.0568359375001, 21.694140625000017], + [88.19628906249997, 22.139550781249994], + [87.94140625000003, 22.374316406250045], + [88.15927734375018, 22.12172851562508], + [87.82373046875003, 21.727343750000045], + [87.20068359375009, 21.544873046874983], + [86.95410156250014, 21.365332031250006], + [86.84228515625009, 21.106347656249994], + [86.97548828125005, 20.70014648437501], + [86.75039062500011, 20.313232421875057], + [86.37656250000006, 20.006738281249966], + [86.24521484375012, 20.05302734374999], + [86.27949218750021, 19.919433593749943], + [85.575, 19.69291992187499], + [85.496875, 19.696923828125108], + [85.50410156250004, 19.887695312500057], + [85.24863281250006, 19.757666015625034], + [85.18076171875018, 19.59487304687508], + [85.44160156249993, 19.626562499999977], + [84.77099609375009, 19.125390625000023], + [84.10410156250018, 18.29267578125001], + [82.35957031250004, 17.09619140624997], + [82.25878906250014, 16.55986328124996], + [81.76191406250015, 16.32949218750008], + [81.28613281249997, 16.337060546875023], + [80.97871093750004, 15.758349609375074], + [80.64658203125006, 15.895019531250028], + [80.29345703125014, 15.710742187499989], + [80.0534179687501, 15.074023437499932], + [80.17871093750003, 14.478320312500074], + [80.11171875000005, 14.212207031250045], + [80.30654296875016, 13.485058593750054], + [80.15625, 13.713769531250108], + [80.06210937500006, 13.60625], + [80.34238281250006, 13.361328125000071], + [80.22910156250018, 12.690332031249966], + [79.85849609375018, 11.988769531250043], + [79.69316406250007, 11.312548828124946], + [79.79902343750004, 11.338671874999932], + [79.84863281250009, 11.196875], + [79.83818359375002, 10.322558593750045], + [79.31455078125018, 10.256689453124949], + [78.93994140625009, 9.565771484375063], + [79.01992187500005, 9.333349609374963], + [79.41142578125002, 9.192382812500014], + [78.97958984375018, 9.268554687500085], + [78.42148437500006, 9.105029296874989], + [78.19248046874995, 8.890869140625057], + [78.06015625000006, 8.384570312499932], + [77.51757812500003, 8.078320312500068], + [77.06591796875003, 8.315917968749986], + [76.5534179687501, 8.902783203124997], + [76.32460937500016, 9.452099609374997], + [76.24238281250004, 9.927099609374949], + [76.37558593750006, 9.539892578124935], + [76.45878906250013, 9.536230468750077], + [76.34648437500002, 9.922119140625], + [76.19560546875002, 10.086132812500026], + [75.72382812500015, 11.361767578125026], + [74.94550781250004, 12.56455078124992], + [74.38222656250005, 14.494726562500048], + [73.94921875000014, 15.074755859375088], + [73.80078125000009, 15.39697265625], + [73.93193359375013, 15.39697265625], + [73.77177734375013, 15.573046874999989], + [73.83281250000013, 15.659375], + [73.67988281250015, 15.708886718750136], + [73.47607421875003, 16.05424804687496], + [72.87548828124997, 18.642822265625114], + [72.97207031250011, 19.15332031250003], + [72.8346679687501, 18.975585937500057], + [72.80302734375013, 19.07929687500004], + [72.81162109375, 19.298925781250006], + [72.98720703125, 19.27744140625009], + [72.78789062500013, 19.362988281250097], + [72.66777343750019, 19.83095703125005], + [72.89375, 20.672753906250136], + [72.81386718750011, 21.117187500000085], + [72.62382812500002, 21.371972656250108], + [72.73476562500016, 21.470800781250006], + [72.61328125000009, 21.461816406250108], + [73.1125, 21.750439453125125], + [72.54306640625, 21.69658203124999], + [72.70019531250003, 21.971923828124943], + [72.52226562500013, 21.976220703125108], + [72.55302734375007, 22.159960937500074], + [72.80917968749995, 22.23330078125008], + [72.18281250000015, 22.26972656250004], + [72.30644531250002, 22.18920898437497], + [72.27441406250009, 22.089746093749966], + [72.03720703125006, 21.82304687499999], + [72.2103515625, 21.72822265625004], + [72.25400390625006, 21.531005859375], + [72.01523437500012, 21.155712890625097], + [71.0246093750001, 20.73886718750009], + [70.71933593750006, 20.740429687500068], + [70.12734375, 21.094677734375097], + [68.96992187500021, 22.29028320312497], + [69.05166015625016, 22.437304687500074], + [69.27656250000004, 22.285498046875063], + [70.17724609375014, 22.57275390624997], + [70.48925781250009, 23.08950195312508], + [70.33945312500012, 22.939746093749932], + [69.66464843750006, 22.759082031250074], + [69.23593749999995, 22.848535156250023], + [68.64072265625006, 23.189941406250114], + [68.41748046875009, 23.57148437500004], + [68.7767578125, 23.852099609375017], + [68.23496093749995, 23.596972656250074], + [68.16503906250009, 23.857324218749994], + [68.28251953125013, 23.927978515625], + [68.38125000000016, 23.950878906250068], + [68.48867187500011, 23.96723632812501], + [68.5866210937501, 23.966601562500074], + [68.72412109375003, 23.964697265625034], + [68.72812500000012, 24.265625], + [68.73964843750016, 24.291992187500085], + [68.75898437499993, 24.307226562500006], + [68.78115234375011, 24.313720703125085], + [68.8, 24.30908203125003], + [68.82832031250004, 24.26401367187509], + [68.86347656250015, 24.26650390625005], + [68.90078125000011, 24.29243164062501], + [68.98457031250015, 24.273095703124966], + [69.05156250000013, 24.28632812500001], + [69.11953125000011, 24.26865234374995], + [69.23505859374993, 24.268261718750068], + [69.44345703124995, 24.275390625000085], + [69.55917968750006, 24.273095703124966], + [69.80517578125009, 24.16523437500004], + [70.0982421875, 24.2875], + [70.28906250000009, 24.356298828125063], + [70.54677734375, 24.418310546875063], + [70.56503906250006, 24.385791015625017], + [70.55585937500015, 24.331103515625074], + [70.57929687500015, 24.279052734374943], + [70.65947265625013, 24.24609374999997], + [70.71630859375009, 24.237988281250097], + [70.7672851562501, 24.245410156250017], + [70.80507812500011, 24.26196289062503], + [70.88623046875014, 24.34375], + [70.92812500000016, 24.362353515625045], + [70.98281250000011, 24.361035156250125], + [71.04404296875006, 24.400097656250097], + [71.04531250000005, 24.42998046874996], + [70.96982421875012, 24.571875], + [70.97636718750013, 24.61875], + [71.00234375000016, 24.6539062499999], + [71.04785156250003, 24.687744140625085], + [71.02070312500021, 24.75766601562492], + [70.95087890625015, 24.89160156250003], + [70.87773437500019, 25.06298828124997], + [70.65205078125004, 25.422900390625102], + [70.64843750000003, 25.666943359375068], + [70.5695312500001, 25.705957031250023], + [70.50585937500009, 25.685302734375085], + [70.44853515625013, 25.681347656249983], + [70.26464843750009, 25.70654296874997], + [70.10019531250006, 25.91005859375005], + [70.14921875000002, 26.347558593749994], + [70.11464843750016, 26.548046874999983], + [69.47001953125002, 26.804443359375], + [69.56796875, 27.174609375000102], + [69.89628906250007, 27.473632812500085], + [70.04980468750009, 27.694726562500023], + [70.14453125000003, 27.849023437499994], + [70.19394531250006, 27.89487304687492], + [70.24433593750004, 27.934130859375102], + [70.4037109375, 28.025048828124994], + [70.48857421875013, 28.023144531250125], + [70.62910156250015, 27.937451171875068], + [70.6916015625001, 27.76899414062504], + [70.79794921875012, 27.709619140625023], + [70.87490234375016, 27.71445312499995], + [71.18476562500004, 27.831640625], + [71.54296875000003, 27.869873046875], + [71.8703125000001, 27.9625], + [71.88886718750004, 28.04746093749992], + [71.94804687500002, 28.177294921875102], + [72.12851562500012, 28.34633789062508], + [72.29199218750003, 28.69726562499997], + [72.34189453125006, 28.751904296875097], + [72.90332031250003, 29.02875976562501], + [73.38164062500013, 29.934375], + [73.8091796875, 30.093359375], + [73.88652343750013, 30.162011718750136], + [73.93339843750002, 30.222070312500108], + [73.92460937500007, 30.28164062499999], + [73.88271484375, 30.352148437499977], + [73.89931640625, 30.435351562500045], + [74.00898437500004, 30.519677734374994], + [74.33935546875003, 30.893554687499943], + [74.38037109375003, 30.89340820312509], + [74.50976562500009, 30.959667968750097], + [74.63281250000014, 31.034667968750114], + [74.62578125000002, 31.068750000000108], + [74.61035156250009, 31.112841796875045], + [74.51767578125012, 31.185595703124932], + [74.53496093750007, 31.261376953125108], + [74.59394531249993, 31.465380859375102], + [74.58183593750013, 31.523925781250114], + [74.50996093750015, 31.712939453125074], + [74.52597656249995, 31.765136718750057], + [74.55556640625011, 31.818554687500097], + [74.63574218750003, 31.889746093750034], + [74.73945312500015, 31.948828125], + [75.07148437500015, 32.08935546875003], + [75.13876953125, 32.10478515624999], + [75.25410156250004, 32.140332031250125], + [75.33349609374997, 32.279199218749994], + [75.32221348233018, 32.28516356678968] + ] + ] + ] + }, + "properties": { "name": "India", "childNum": 1 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [72.49199218750002, -7.37744140625], + [72.42910156250002, -7.435351562500003], + [72.34970703125, -7.263378906250011], + [72.447265625, -7.395703125000011], + [72.44560546875002, -7.220410156250011], + [72.49199218750002, -7.37744140625] + ] + ] + }, + "properties": { "name": "Br. Indian Ocean Ter.", "childNum": 1 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [-9.948193359374926, 53.91313476562499], + [-10.265722656249949, 53.977685546874994], + [-9.99638671874996, 54.00361328125004], + [-9.948193359374926, 53.91313476562499] + ] + ], + [ + [ + [-6.218017578125, 54.08872070312506], + [-6.347607421874926, 53.94130859375005], + [-6.027392578124989, 52.927099609375006], + [-6.463183593749932, 52.345361328124994], + [-6.325, 52.246679687500034], + [-6.890234375, 52.15922851562499], + [-6.965771484374926, 52.24951171875], + [-8.057812499999926, 51.82558593750005], + [-8.4091796875, 51.888769531250034], + [-8.349121093749943, 51.73930664062496], + [-8.813427734374926, 51.584912109374955], + [-9.737304687499943, 51.473730468750034], + [-9.524902343750028, 51.68110351562501], + [-10.120751953124994, 51.60068359375006], + [-9.598828124999983, 51.87441406250005], + [-10.341064453124943, 51.798925781250034], + [-9.909667968749972, 52.122949218749966], + [-10.39023437499992, 52.134912109374994], + [-10.356689453125, 52.20693359375002], + [-9.772119140624937, 52.250097656250034], + [-9.90605468749996, 52.403710937499966], + [-9.632226562499937, 52.54692382812502], + [-8.783447265624943, 52.679638671874955], + [-8.990283203124989, 52.755419921875045], + [-9.175390624999949, 52.634912109374994], + [-9.916601562499977, 52.56972656250005], + [-9.46489257812496, 52.82319335937498], + [-9.299218749999966, 53.09755859375002], + [-8.930126953124983, 53.207080078125045], + [-9.51420898437496, 53.23823242187498], + [-10.091259765624926, 53.41284179687503], + [-10.116992187499932, 53.548535156249955], + [-9.720654296874926, 53.6044921875], + [-9.901611328124943, 53.72719726562502], + [-9.578222656249949, 53.80541992187497], + [-9.578857421875, 53.879833984374955], + [-9.9140625, 53.863720703124955], + [-9.856445312499972, 54.095361328124994], + [-10.092675781249966, 54.15576171875003], + [-10.056396484374943, 54.25781250000006], + [-8.545556640624994, 54.24121093750003], + [-8.623144531249977, 54.346875], + [-8.133447265624966, 54.64082031250001], + [-8.763916015624972, 54.68120117187496], + [-8.377294921874977, 54.88945312500002], + [-8.274609374999955, 55.146289062500045], + [-7.667089843749977, 55.25649414062502], + [-7.65874023437496, 54.97094726562503], + [-7.308789062500011, 55.365820312500006], + [-6.961669921874972, 55.23789062500006], + [-7.218652343749937, 55.09199218749998], + [-7.55039062499992, 54.767968749999966], + [-7.910595703124955, 54.698339843750006], + [-7.75439453125, 54.59492187499998], + [-8.118261718749977, 54.41425781250004], + [-7.606542968750006, 54.14384765625002], + [-7.324511718750017, 54.13344726562502], + [-7.007714843749937, 54.40668945312501], + [-6.649804687499937, 54.05864257812496], + [-6.218017578125, 54.08872070312506] + ] + ] + ] + }, + "properties": { "name": "Ireland", "childNum": 2 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [56.18798828125003, 26.92114257812497], + [55.95429687500004, 26.70112304687501], + [55.31152343749997, 26.592626953125006], + [55.76259765625005, 26.81196289062504], + [55.75761718750002, 26.94765625000005], + [56.279394531250006, 26.952099609374983], + [56.18798828125003, 26.92114257812497] + ] + ], + [ + [ + [46.1144531250001, 38.877783203125034], + [46.490625, 38.90668945312498], + [47.995898437500074, 39.683935546875034], + [48.322167968749994, 39.39907226562502], + [48.10439453125005, 39.241113281249994], + [48.292089843750006, 39.01884765624999], + [47.99648437499999, 38.85375976562503], + [48.59267578125005, 38.41108398437498], + [48.86875, 38.43549804687498], + [48.95996093750003, 37.89013671875], + [49.171191406250074, 37.60058593749997], + [50.13046875, 37.407128906249994], + [50.53320312499997, 37.01367187500006], + [51.11855468750005, 36.742578124999966], + [52.19013671875004, 36.62172851562505], + [53.76767578125006, 36.93032226562502], + [53.91542968750005, 36.93032226562502], + [53.67949218750002, 36.853125], + [53.970117187499994, 36.818310546874955], + [53.91416015625006, 37.34355468750002], + [54.6994140625001, 37.47016601562498], + [54.90009765625004, 37.77792968750006], + [55.38085937500003, 38.051123046875034], + [56.272070312500006, 38.080419921875034], + [56.440625, 38.249414062499994], + [57.1935546875001, 38.216406250000034], + [57.35371093750004, 37.97333984374998], + [58.261621093749994, 37.665820312500045], + [58.81542968750003, 37.683496093749994], + [59.30175781249997, 37.51064453125005], + [59.454980468749994, 37.25283203125002], + [60.06279296875002, 36.962890625], + [60.34130859375003, 36.63764648437501], + [61.11962890625003, 36.64257812500003], + [61.212011718750006, 36.190527343750034], + [61.15292968750006, 35.97675781250001], + [61.25214843750004, 35.86762695312498], + [61.26201171875002, 35.61958007812498], + [61.28183593750006, 35.55341796875001], + [61.2785156250001, 35.513769531250006], + [61.245507812499994, 35.47407226562501], + [61.18925781250002, 35.31201171875003], + [61.1, 35.272314453125034], + [61.08007812499997, 34.85561523437505], + [60.95117187499997, 34.65385742187499], + [60.91474609375004, 34.63398437500001], + [60.80234375000006, 34.55463867187501], + [60.73945312500004, 34.544726562500045], + [60.7262695312501, 34.51826171874998], + [60.736132812500074, 34.491796875], + [60.76259765625005, 34.475244140624994], + [60.88945312500002, 34.31943359375006], + [60.642675781250006, 34.30717773437496], + [60.48574218750005, 34.09477539062502], + [60.4859375, 33.7119140625], + [60.57382812500006, 33.58833007812498], + [60.91699218749997, 33.505224609375006], + [60.56054687499997, 33.13784179687502], + [60.5765625, 32.99487304687503], + [60.71044921874997, 32.6], + [60.82929687500004, 32.24941406250005], + [60.82724609375006, 32.16796874999997], + [60.789941406249994, 31.98710937499999], + [60.7875, 31.87719726562497], + [60.791601562500006, 31.660595703124983], + [60.82070312499999, 31.495166015625045], + [60.854101562500006, 31.483251953125006], + [61.110742187499994, 31.45112304687504], + [61.346484375000074, 31.42163085937497], + [61.66015625000003, 31.382421874999977], + [61.7550781250001, 31.285302734374994], + [61.814257812500074, 31.072558593750017], + [61.810839843750074, 30.913281249999983], + [61.78417968749997, 30.831933593750023], + [61.55947265625005, 30.59936523437497], + [61.33164062500006, 30.36372070312501], + [60.84335937500006, 29.85869140624999], + [61.03417968750003, 29.663427734374977], + [61.15214843750002, 29.542724609375], + [61.8898437500001, 28.546533203124994], + [62.7625, 28.202050781249994], + [62.782324218750006, 27.800537109375], + [62.75273437500002, 27.265625], + [63.16679687500002, 27.25249023437499], + [63.19609375000002, 27.243945312500017], + [63.25625, 27.20791015625005], + [63.30156250000002, 27.151464843750006], + [63.30517578124997, 27.124560546875017], + [63.242089843749994, 27.07768554687499], + [63.25039062499999, 26.879248046875063], + [63.24160156250005, 26.86474609375003], + [63.18613281250006, 26.83759765625001], + [63.168066406250006, 26.66557617187496], + [62.31230468750002, 26.490869140624994], + [62.23935546875006, 26.357031249999977], + [62.12597656249997, 26.368994140625034], + [61.842382812500006, 26.225927734375006], + [61.809960937499994, 26.165283203125], + [61.78076171874997, 25.99584960937503], + [61.75439453125003, 25.843359375000063], + [61.737695312499994, 25.821093750000045], + [61.66865234375004, 25.76899414062501], + [61.6618164062501, 25.751269531250017], + [61.67138671874997, 25.69238281250003], + [61.64013671875003, 25.584619140624994], + [61.61542968750004, 25.28613281250003], + [61.58789062499997, 25.20234375000001], + [61.533105468749994, 25.195507812499955], + [61.41220703125006, 25.102099609375017], + [60.66386718750002, 25.28222656250003], + [60.51054687500002, 25.437060546875045], + [60.40019531250002, 25.311572265625074], + [59.45605468749997, 25.481494140625045], + [59.0460937500001, 25.417285156250017], + [58.79785156249997, 25.554589843750023], + [57.334570312500006, 25.791552734375074], + [57.03603515625005, 26.80068359375005], + [56.728125, 27.127685546875057], + [56.118066406249994, 27.14311523437499], + [54.75927734375003, 26.50507812500004], + [54.24707031250003, 26.696630859374977], + [53.70576171875004, 26.72558593750003], + [52.69160156250004, 27.323388671875023], + [52.475878906250074, 27.61650390624999], + [52.03076171874997, 27.824414062499955], + [51.58906250000004, 27.864208984374983], + [51.27890625, 28.13134765624997], + [51.06201171874997, 28.72612304687499], + [50.86699218750002, 28.870166015625017], + [50.87578125000002, 29.062695312499983], + [50.67519531250005, 29.146582031250034], + [50.64960937500004, 29.420068359374966], + [50.16894531250003, 29.921240234375034], + [50.071582031250074, 30.198535156250017], + [49.55488281250004, 30.028955078125023], + [49.028125, 30.333447265624983], + [49.224511718749994, 30.472314453125023], + [49.00195312500003, 30.506542968749983], + [48.91914062500004, 30.120898437500017], + [48.54648437500006, 29.962353515624955], + [48.47851562499997, 30.003808593749966], + [48.43457031249997, 30.03759765625], + [48.33105468749997, 30.28544921874996], + [48.01494140625002, 30.465625], + [48.01064453125005, 30.989794921875017], + [47.679492187500074, 31.00239257812501], + [47.679492187500074, 31.400585937499955], + [47.75390624999997, 31.601367187500017], + [47.829980468749994, 31.79443359375], + [47.71455078125004, 31.936425781249966], + [47.5915039062501, 32.087988281250034], + [47.51191406250004, 32.15083007812504], + [47.3297851562501, 32.45551757812501], + [47.28515625000003, 32.474023437499966], + [47.121386718750074, 32.46660156249996], + [46.569921875, 32.83393554687501], + [46.37705078125006, 32.92924804687499], + [46.29824218750005, 32.95024414062502], + [46.11279296875003, 32.957666015624994], + [46.09306640625002, 32.97587890624999], + [46.08046875, 33.028222656249994], + [46.0807617187501, 33.08652343750006], + [46.14111328125003, 33.174414062500034], + [46.145898437499994, 33.229638671874994], + [46.01992187500005, 33.41572265624998], + [45.39707031250006, 33.970849609374994], + [45.542773437500074, 34.21552734375004], + [45.459375, 34.470361328124994], + [45.50078125000002, 34.58159179687499], + [45.6375, 34.573828125], + [45.678125, 34.798437500000034], + [45.92089843750003, 35.02851562500001], + [46.04179687500002, 35.08017578125006], + [46.13378906249997, 35.127636718749955], + [46.15468750000005, 35.19672851562498], + [46.112109375000074, 35.32167968750005], + [45.97109375000005, 35.524169921875], + [46.03740234375002, 35.67314453124999], + [46.180957031250074, 35.71137695312504], + [46.2625, 35.74414062500006], + [46.27343749999997, 35.77324218750002], + [46.16748046874997, 35.820556640625], + [45.77636718749997, 35.82182617187499], + [45.36162109375002, 36.015332031249955], + [45.241113281249994, 36.35595703125], + [45.20654296874997, 36.397167968749955], + [45.15527343749997, 36.407373046874994], + [45.11240234375006, 36.409277343750034], + [45.053125, 36.47163085937501], + [44.76513671875003, 37.142431640625006], + [44.79414062500004, 37.290380859375034], + [44.574023437500074, 37.435400390625006], + [44.589941406250006, 37.710351562499966], + [44.21132812499999, 37.908056640625006], + [44.4499023437501, 38.33422851562506], + [44.2985351562501, 38.38627929687499], + [44.27167968750004, 38.83603515625006], + [44.02324218750002, 39.37744140625006], + [44.3893554687501, 39.422119140625], + [44.58710937500004, 39.76855468750006], + [44.81718750000002, 39.65043945312496], + [45.4796875000001, 39.00625], + [46.1144531250001, 38.877783203125034] + ] + ] + ] + }, + "properties": { "name": "Iran", "childNum": 2 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [45.6375, 34.573828125], + [45.50078125000002, 34.581591796874996], + [45.459375, 34.470361328124994], + [45.54277343750002, 34.21552734375], + [45.397070312500006, 33.970849609374994], + [46.01992187500002, 33.41572265625], + [46.14589843750002, 33.229638671874994], + [46.14111328125, 33.1744140625], + [46.08076171875001, 33.0865234375], + [46.08046875000002, 33.028222656249994], + [46.09306640625002, 32.975878906249996], + [46.11279296875, 32.957666015624994], + [46.377050781250006, 32.929248046874996], + [46.569921875, 32.833935546875], + [47.12138671875002, 32.466601562499996], + [47.28515625, 32.474023437499994], + [47.32978515625001, 32.455517578125], + [47.51191406250001, 32.150830078125], + [47.59150390625001, 32.08798828125], + [47.71455078125001, 31.936425781249994], + [47.82998046875002, 31.79443359375], + [47.75390625, 31.601367187499996], + [47.67949218750002, 31.400585937499997], + [47.67949218750002, 31.002392578124997], + [48.01064453125002, 30.989794921874996], + [48.01494140625002, 30.465625], + [48.3310546875, 30.285449218749996], + [48.546484375, 29.962353515624997], + [48.454199218750006, 29.9384765625], + [48.354589843750006, 29.956738281249997], + [48.141699218750006, 30.040917968749994], + [47.982519531250006, 30.011328125], + [47.97871093750001, 29.9828125], + [47.64375, 30.097314453124994], + [47.14824218750002, 30.0009765625], + [46.905859375, 29.5375], + [46.76933593750002, 29.347460937499996], + [46.69375, 29.259667968749994], + [46.53144531250001, 29.096240234374996], + [46.3564453125, 29.063671875], + [44.71650390625001, 29.193603515625], + [43.77373046875002, 29.84921875], + [42.07441406250001, 31.080371093749996], + [40.47890625000002, 31.893359375], + [40.36933593750001, 31.93896484375], + [40.02783203125, 31.995019531249994], + [39.7041015625, 32.042529296874996], + [39.14541015625002, 32.12451171875], + [39.29277343750002, 32.24384765625], + [39.24746093750002, 32.350976562499994], + [39.04140625000002, 32.3056640625], + [38.773535156250006, 33.372216796874994], + [40.98701171875001, 34.429052734375], + [41.19472656250002, 34.768994140625], + [41.354101562500006, 35.640429687499996], + [41.295996093750006, 36.383349609374996], + [41.41679687500002, 36.5146484375], + [41.78857421875, 36.59716796875], + [42.358984375, 37.10859375], + [42.45585937500002, 37.128710937499996], + [42.63544921875001, 37.249267578125], + [42.74111328125002, 37.3619140625], + [42.77460937500001, 37.371875], + [42.869140625, 37.334912109375], + [42.936621093750006, 37.324755859374996], + [43.09248046875001, 37.3673828125], + [43.67578125, 37.22724609375], + [43.83642578125, 37.223535156249994], + [44.01318359375, 37.313525390624996], + [44.11445312500001, 37.30185546875], + [44.15625, 37.282958984375], + [44.19179687500002, 37.249853515625], + [44.20839843750002, 37.20263671875], + [44.20166015625, 37.051806640624996], + [44.281835937500006, 36.97802734375], + [44.32558593750002, 37.0107421875], + [44.401953125, 37.058496093749994], + [44.60595703125, 37.176025390625], + [44.66933593750002, 37.173583984375], + [44.73095703125, 37.165283203125], + [44.76513671875, 37.142431640625], + [45.053125, 36.471630859375], + [45.112402343750006, 36.40927734375], + [45.1552734375, 36.407373046874994], + [45.20654296875, 36.39716796875], + [45.24111328125002, 36.35595703125], + [45.36162109375002, 36.01533203125], + [45.7763671875, 35.821826171874996], + [46.16748046875, 35.820556640625], + [46.2734375, 35.773242187499996], + [46.2625, 35.744140625], + [46.18095703125002, 35.711376953125], + [46.03740234375002, 35.673144531249996], + [45.97109375000002, 35.524169921875], + [46.11210937500002, 35.321679687499994], + [46.15468750000002, 35.196728515625], + [46.1337890625, 35.12763671875], + [46.04179687500002, 35.08017578125], + [45.9208984375, 35.028515625], + [45.678125, 34.7984375], + [45.6375, 34.573828125] + ] + ] + }, + "properties": { "name": "Iraq", "childNum": 1 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [-15.543115234374994, 66.228515625], + [-14.595849609374994, 66.38154296875], + [-15.117382812499983, 66.125634765625], + [-14.698193359374983, 66.02021484375], + [-14.827099609374983, 65.7642578125], + [-14.391845703125, 65.78740234375], + [-14.473388671875, 65.575341796875], + [-14.166943359374983, 65.64228515625], + [-13.617871093749983, 65.5193359375], + [-13.804785156249977, 65.35478515625], + [-13.599316406249983, 65.0359375], + [-14.04443359375, 64.74189453125], + [-14.385107421874977, 64.74521484375], + [-14.475390624999989, 64.493994140625], + [-14.927392578124994, 64.319677734375], + [-15.832910156249994, 64.17666015625], + [-16.640332031249983, 63.865478515625], + [-17.81572265624999, 63.71298828125], + [-17.946923828124994, 63.5357421875], + [-18.65361328124999, 63.406689453125], + [-20.198144531249994, 63.555810546874994], + [-20.494042968749994, 63.687353515625], + [-20.413964843749994, 63.80517578125], + [-20.65092773437499, 63.73740234375], + [-21.15239257812499, 63.94453125], + [-22.652197265624977, 63.827734375], + [-22.701171875, 64.083203125], + [-22.51005859374999, 63.991455078125], + [-22.187597656249977, 64.039208984375], + [-21.463330078124983, 64.379150390625], + [-22.053369140624994, 64.313916015625], + [-21.950341796874994, 64.514990234375], + [-21.590625, 64.6263671875], + [-22.10600585937499, 64.533056640625], + [-22.467041015625, 64.794970703125], + [-23.818994140624994, 64.73916015625], + [-24.02617187499999, 64.863427734375], + [-22.7880859375, 65.046484375], + [-21.89213867187499, 65.048779296875], + [-21.779980468749983, 65.1876953125], + [-22.50908203124999, 65.19677734375], + [-21.844384765624994, 65.44736328125], + [-22.902490234374994, 65.58046875], + [-23.89990234375, 65.407568359375], + [-24.475683593749977, 65.5251953125], + [-24.248925781249994, 65.614990234375], + [-23.85673828124999, 65.53837890625], + [-24.092626953124977, 65.77646484375], + [-23.615917968749983, 65.67958984375], + [-23.285351562499983, 65.75], + [-23.832617187499977, 65.84921875], + [-23.52495117187499, 65.880029296875], + [-23.77734375, 66.017578125], + [-23.434472656249994, 66.02421875], + [-23.452539062499994, 66.181005859375], + [-23.018994140624983, 65.98212890625], + [-22.659863281249983, 66.025927734375], + [-22.61601562499999, 65.86748046875], + [-22.44169921874999, 65.90830078125], + [-22.4453125, 66.07001953125], + [-22.947900390624994, 66.212744140625], + [-22.48442382812499, 66.26630859375], + [-23.116943359375, 66.338720703125], + [-22.9443359375, 66.429443359375], + [-22.426123046874977, 66.430126953125], + [-21.406884765624994, 66.0255859375], + [-21.374902343749994, 65.74189453125], + [-21.658447265625, 65.723583984375], + [-21.12968749999999, 65.2666015625], + [-20.804345703124994, 65.63642578125], + [-20.454833984375, 65.571044921875], + [-20.20751953125, 66.10009765625], + [-19.489697265624983, 65.76806640625], + [-19.382958984374994, 66.07568359375], + [-18.845898437499983, 66.183935546875], + [-18.141943359374977, 65.73408203125], + [-18.29716796874999, 66.157421875], + [-17.906982421875, 66.143310546875], + [-17.550439453124994, 65.964404296875], + [-17.153027343749983, 66.20283203125], + [-16.838037109374994, 66.125244140625], + [-16.485009765624994, 66.195947265625], + [-16.540673828124994, 66.446728515625], + [-16.24931640624999, 66.522900390625], + [-15.985400390624989, 66.5146484375], + [-15.543115234374994, 66.228515625] + ] + ] + }, + "properties": { "name": "Iceland", "childNum": 1 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [35.78730468750001, 32.734912109374996], + [35.572851562500006, 32.640869140625], + [35.56904296875001, 32.619873046875], + [35.55146484375001, 32.3955078125], + [35.484375, 32.401660156249996], + [35.40263671875002, 32.450634765625], + [35.38671875, 32.493017578125], + [35.303808593750006, 32.512939453125], + [35.19326171875002, 32.534423828125], + [35.065039062500006, 32.46044921875], + [35.01054687500002, 32.338183593749996], + [34.95595703125002, 32.1609375], + [34.98974609375, 31.91328125], + [34.97832031250002, 31.86640625], + [34.95380859375001, 31.841259765624997], + [34.96113281250001, 31.82333984375], + [34.983007812500006, 31.81679687499999], + [35.05322265625, 31.837939453124996], + [35.12714843750001, 31.816748046875], + [35.203710937500006, 31.75], + [34.95097656250002, 31.602294921875], + [34.88046875, 31.3681640625], + [35.45058593750002, 31.479296875], + [34.97343750000002, 29.555029296875], + [34.904296875, 29.47734375], + [34.24531250000001, 31.208300781249996], + [34.34833984375001, 31.292919921874997], + [34.350195312500006, 31.362744140624997], + [34.52558593750001, 31.525634765625], + [34.47734375000002, 31.584863281249994], + [34.483984375, 31.59228515625], + [34.67841796875001, 31.895703125], + [35.10859375000001, 33.08369140625], + [35.411230468750006, 33.07568359375], + [35.869140625, 33.43173828125], + [35.91347656250002, 32.94960937499999], + [35.78730468750001, 32.734912109374996] + ] + ] + }, + "properties": { "name": "Israel", "childNum": 1 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [15.576562500000051, 38.220312500000034], + [15.099511718750023, 37.45859375], + [15.295703125000017, 37.05517578124997], + [15.112597656250017, 36.687841796875006], + [14.501855468750023, 36.798681640625034], + [14.142968750000023, 37.103662109374994], + [13.90546875000004, 37.10063476562502], + [13.169921875000028, 37.47929687499996], + [12.640234375000034, 37.594335937500034], + [12.435546874999972, 37.819775390624955], + [12.734375, 38.18305664062498], + [12.902734375000023, 38.03486328124998], + [13.291113281250034, 38.19145507812502], + [13.788867187499989, 37.981201171875], + [15.11875, 38.15273437500002], + [15.498730468750011, 38.290869140625006], + [15.576562500000051, 38.220312500000034] + ] + ], + [ + [ + [8.478906250000023, 39.067529296874966], + [8.421484375000034, 38.968652343749994], + [8.366796875, 39.115917968749955], + [8.478906250000023, 39.067529296874966] + ] + ], + [ + [ + [8.28603515625008, 41.03984375], + [8.205664062500034, 40.99746093750005], + [8.320214843750023, 41.121875], + [8.28603515625008, 41.03984375] + ] + ], + [ + [ + [9.632031250000011, 40.88203124999998], + [9.805273437500063, 40.499560546875045], + [9.642968750000023, 40.268408203125006], + [9.5625, 39.16601562500006], + [9.056347656250068, 39.23916015625002], + [8.966601562500074, 38.963720703125034], + [8.648535156250034, 38.92656250000002], + [8.418164062500068, 39.205712890624966], + [8.547753906250023, 39.83920898437506], + [8.4078125, 39.91723632812497], + [8.471289062500063, 40.29267578124998], + [8.189941406250028, 40.651611328125], + [8.22421875, 40.91333007812503], + [8.571875, 40.85019531250006], + [9.228417968750023, 41.257080078125], + [9.615332031249977, 41.01728515624998], + [9.632031250000011, 40.88203124999998] + ] + ], + [ + [ + [10.395117187500034, 42.85815429687503], + [10.419335937499994, 42.71318359374999], + [10.13125, 42.742041015625006], + [10.395117187500034, 42.85815429687503] + ] + ], + [ + [ + [13.420996093750006, 46.212304687499994], + [13.63251953125004, 46.17705078125002], + [13.634960937499983, 46.15776367187499], + [13.61660156250008, 46.133105468750045], + [13.54804687500004, 46.08911132812503], + [13.486425781250034, 46.03955078124997], + [13.480273437500017, 46.00922851562501], + [13.487695312500023, 45.987109375000045], + [13.509179687500051, 45.973779296874994], + [13.6005859375, 45.97978515624996], + [13.663476562500023, 45.7919921875], + [13.831152343750006, 45.680419921875], + [13.719824218750063, 45.58759765625001], + [13.628320312500051, 45.77094726562498], + [13.206347656250074, 45.771386718749966], + [12.27431640625008, 45.44604492187503], + [12.225683593750034, 45.24150390625002], + [12.523437500000028, 44.96796874999998], + [12.248339843750045, 44.72250976562498], + [12.396289062500074, 44.223876953125], + [13.56416015625004, 43.57128906250003], + [14.010449218750011, 42.68955078125006], + [14.54072265625004, 42.24428710937502], + [15.16875, 41.93403320312498], + [16.164648437500034, 41.89619140624998], + [15.900488281250034, 41.51206054687498], + [17.954980468749994, 40.65517578125002], + [18.460644531249983, 40.221044921875034], + [18.34375, 39.82138671874998], + [18.077929687500017, 39.93696289062498], + [17.865039062500074, 40.28017578125002], + [17.395800781250045, 40.34023437499999], + [17.179980468750045, 40.50278320312498], + [16.92822265625, 40.45805664062502], + [16.521875, 39.74755859375003], + [17.114550781250017, 39.38061523437497], + [17.174609375000017, 38.998095703125045], + [16.61669921875003, 38.800146484375034], + [16.54560546875001, 38.40908203125002], + [16.05683593750001, 37.941845703124955], + [15.72451171875008, 37.93911132812502], + [15.645800781250017, 38.034228515625045], + [15.87890625, 38.61391601562502], + [16.19677734375, 38.759228515624955], + [16.20996093750003, 38.94111328124998], + [15.692773437499994, 39.99018554687501], + [14.95087890625004, 40.23901367187497], + [14.94765625000008, 40.469335937500006], + [14.765722656250063, 40.66840820312498], + [14.339941406250006, 40.59882812500001], + [14.460546875000063, 40.72871093750001], + [14.04433593750008, 40.81225585937506], + [13.733398437500057, 41.23564453124999], + [13.088671875000074, 41.243847656249955], + [12.630859374999972, 41.469677734374955], + [11.637304687500063, 42.287548828124955], + [11.141210937499977, 42.38989257812503], + [11.167773437500074, 42.53515625000006], + [10.708398437500023, 42.93632812499999], + [10.514843750000011, 42.96752929687503], + [10.188085937500063, 43.947509765625], + [8.76582031250004, 44.42231445312501], + [8.004980468750006, 43.87675781249999], + [7.4931640625, 43.767138671875045], + [7.637207031250057, 44.16484375], + [7.318554687500068, 44.13798828125002], + [6.900195312499989, 44.33574218749996], + [6.99267578125, 44.82729492187502], + [6.634765625000028, 45.06816406249996], + [7.07832031250004, 45.23994140624998], + [7.146386718750051, 45.381738281249994], + [6.790917968750023, 45.740869140624966], + [7.021093750000034, 45.92578124999997], + [7.055761718749977, 45.90380859375003], + [7.129003906249977, 45.88041992187499], + [7.327929687500017, 45.912353515625], + [7.9931640625, 46.01591796874996], + [8.081542968750057, 46.25600585937502], + [8.231933593750057, 46.341210937499966], + [8.29853515625004, 46.403417968750034], + [8.370703125, 46.44511718750002], + [8.458398437500023, 46.24589843750002], + [8.818554687500011, 46.0771484375], + [8.826757812500006, 46.06103515625], + [8.77802734375004, 45.996191406250034], + [8.953710937500034, 45.83002929687501], + [9.023730468750074, 45.845703125], + [9.203417968750017, 46.21923828125], + [9.304394531250068, 46.49555664062498], + [9.399316406250023, 46.480664062499955], + [9.427636718750023, 46.48232421875002], + [9.528710937500023, 46.306201171875045], + [9.57958984375, 46.29609375000001], + [9.639453125000017, 46.29589843749997], + [9.78779296875004, 46.34604492187498], + [9.884472656250011, 46.36777343750006], + [9.939257812500074, 46.36181640625], + [10.041015625000028, 46.23808593750002], + [10.08056640625, 46.22797851562501], + [10.128320312500051, 46.238232421874955], + [10.109667968750074, 46.36284179687502], + [10.081933593750023, 46.420751953125006], + [10.045605468750068, 46.44790039062505], + [10.038281250000011, 46.483203125000045], + [10.061230468750068, 46.54677734375002], + [10.087011718750063, 46.59990234375002], + [10.1375, 46.614355468750034], + [10.195507812500068, 46.62109374999997], + [10.4306640625, 46.55004882812497], + [10.409352678571473, 46.6092047991071], + [10.39794921875, 46.66503906250006], + [10.406054687500045, 46.73486328124997], + [10.452832031249983, 46.86494140625001], + [10.47939453125008, 46.85512695312505], + [10.579785156250011, 46.85371093750001], + [10.689257812500017, 46.846386718749955], + [10.759765625, 46.79331054687498], + [10.828906250000045, 46.775244140625034], + [10.927343750000034, 46.76948242187501], + [10.993261718750034, 46.77700195312502], + [11.02509765625004, 46.796972656250006], + [11.063476562500057, 46.85913085937497], + [11.133886718750006, 46.93618164062505], + [11.244433593750045, 46.975683593750006], + [11.433203125000063, 46.983056640624994], + [11.527539062500011, 46.99741210937498], + [11.775683593750017, 46.986083984375], + [12.169433593750028, 47.082128906250006], + [12.19716796875008, 47.075], + [12.201269531250034, 47.060888671875034], + [12.165527343750028, 47.028173828125034], + [12.130761718750051, 46.98476562499999], + [12.154101562500017, 46.93525390625004], + [12.267968750000023, 46.83588867187504], + [12.330078125, 46.75981445312499], + [12.388281250000034, 46.70263671874997], + [12.479199218749983, 46.672509765624966], + [13.16875, 46.572656249999966], + [13.3515625, 46.55791015624999], + [13.490039062500045, 46.55556640625002], + [13.7, 46.52026367187503], + [13.679687500000057, 46.46289062499997], + [13.63710937500008, 46.44853515624999], + [13.563281250000045, 46.41508789062502], + [13.399511718749977, 46.31752929687502], + [13.420996093750006, 46.212304687499994] + ] + ] + ] + }, + "properties": { "name": "Italy", "childNum": 6 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [-77.261474609375, 18.45742187499999], + [-76.349853515625, 18.15185546875], + [-76.21079101562499, 17.913525390624997], + [-76.524609375, 17.8662109375], + [-76.85322265625, 17.97373046874999], + [-76.94414062499999, 17.848779296874994], + [-77.11948242187499, 17.880078125], + [-77.20498046875, 17.71494140624999], + [-77.36142578124999, 17.833691406249997], + [-77.76816406249999, 17.877392578124997], + [-78.04448242187499, 18.173828125], + [-78.339501953125, 18.28720703124999], + [-78.21669921875, 18.44809570312499], + [-77.8734375, 18.522216796875], + [-77.261474609375, 18.45742187499999] + ] + ] + }, + "properties": { "name": "Jamaica", "childNum": 1 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [-2.018652343749977, 49.23125], + [-2.23583984375, 49.1763671875], + [-2.220507812499989, 49.266357421875], + [-2.018652343749977, 49.23125] + ] + ] + }, + "properties": { "name": "Jersey", "childNum": 1 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [39.14541015625002, 32.12451171875], + [38.9970703125, 32.007470703124994], + [38.96230468750002, 31.994921875], + [38.37548828125, 31.847460937499996], + [38.111425781250006, 31.781152343749994], + [37.49335937500001, 31.625878906249994], + [37.215625, 31.556103515624997], + [36.95859375, 31.491503906249996], + [37.980078125, 30.5], + [37.862890625, 30.442626953125], + [37.66972656250002, 30.34814453125], + [37.64990234375, 30.330957031249994], + [37.63359375000002, 30.31328125], + [37.55361328125002, 30.144580078124996], + [37.49072265625, 30.01171875], + [37.46923828125, 29.995068359374997], + [36.75527343750002, 29.866015625], + [36.70390625000002, 29.831640625], + [36.591796875, 29.66611328125], + [36.47607421875, 29.4951171875], + [36.2828125, 29.355371093749994], + [36.068457031250006, 29.200537109375], + [34.95078125, 29.353515625], + [34.97343750000002, 29.555029296875], + [35.45058593750002, 31.479296875], + [35.57207031250002, 32.237890625], + [35.55146484375001, 32.3955078125], + [35.56904296875001, 32.619873046875], + [35.572851562500006, 32.640869140625], + [35.78730468750001, 32.734912109374996], + [36.3720703125, 32.3869140625], + [36.818359375, 32.317285156249994], + [38.773535156250006, 33.372216796874994], + [39.04140625000002, 32.3056640625], + [39.24746093750002, 32.350976562499994], + [39.29277343750002, 32.24384765625], + [39.14541015625002, 32.12451171875] + ] + ] + }, + "properties": { "name": "Jordan", "childNum": 1 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [123.88867187499997, 24.280126953124977], + [123.67978515625012, 24.317773437500023], + [123.77148437499997, 24.41445312499999], + [123.93486328125002, 24.362011718749983], + [123.88867187499997, 24.280126953124977] + ] + ], + [ + [ + [124.29316406250004, 24.515917968750074], + [124.13574218750003, 24.347607421874983], + [124.08476562500002, 24.435839843750017], + [124.30195312500004, 24.58710937500001], + [124.29316406250004, 24.515917968750074] + ] + ], + [ + [ + [125.44414062500002, 24.7431640625], + [125.26894531250005, 24.732519531250063], + [125.28359375, 24.871923828125034], + [125.44414062500002, 24.7431640625] + ] + ], + [ + [ + [128.25878906249997, 26.65278320312501], + [127.86708984375, 26.442480468749977], + [127.80361328125005, 26.152539062499983], + [127.653125, 26.0947265625], + [127.90722656250003, 26.69360351562497], + [128.09765624999997, 26.66777343749996], + [128.25488281249997, 26.88188476562496], + [128.25878906249997, 26.65278320312501] + ] + ], + [ + [ + [128.99814453125012, 27.720800781250006], + [128.90000000000012, 27.727783203125], + [128.9076171875, 27.897998046875045], + [128.99814453125012, 27.720800781250006] + ] + ], + [ + [ + [129.45253906250005, 28.20898437499997], + [129.3664062500001, 28.127734375000045], + [129.16464843750012, 28.24975585937503], + [129.68955078125012, 28.517480468750023], + [129.45253906250005, 28.20898437499997] + ] + ], + [ + [ + [130.6227539062501, 30.262988281250017], + [130.44560546875002, 30.264697265625017], + [130.38808593750005, 30.38818359375003], + [130.49716796875006, 30.465527343749983], + [130.64355468749997, 30.388964843750017], + [130.6227539062501, 30.262988281250017] + ] + ], + [ + [ + [130.95976562500007, 30.39692382812504], + [130.87031250000004, 30.444238281249994], + [131.06035156250007, 30.828466796875006], + [130.95976562500007, 30.39692382812504] + ] + ], + [ + [ + [130.38105468750004, 32.42373046875002], + [130.24169921874997, 32.462792968749994], + [130.46142578124997, 32.515722656250034], + [130.38105468750004, 32.42373046875002] + ] + ], + [ + [ + [130.08251953124997, 32.22968750000001], + [129.9601562500001, 32.24375], + [130.00976562499997, 32.521630859374994], + [130.16777343750002, 32.54121093749998], + [130.19951171875002, 32.34057617187506], + [130.08251953124997, 32.22968750000001] + ] + ], + [ + [ + [128.66533203125002, 32.783886718749955], + [128.89453124999997, 32.65214843750002], + [128.69296875000012, 32.60473632812506], + [128.66533203125002, 32.783886718749955] + ] + ], + [ + [ + [129.07695312500002, 32.84028320312498], + [128.99726562500004, 32.95185546874998], + [129.10976562500005, 33.13256835937503], + [129.18193359375002, 32.99311523437504], + [129.07695312500002, 32.84028320312498] + ] + ], + [ + [ + [129.49179687500006, 33.22304687499999], + [129.37041015625002, 33.176025390625], + [129.56992187500006, 33.36103515625004], + [129.49179687500006, 33.22304687499999] + ] + ], + [ + [ + [129.79570312500007, 33.74882812499999], + [129.67480468749997, 33.73969726562498], + [129.71728515624997, 33.8583984375], + [129.79570312500007, 33.74882812499999] + ] + ], + [ + [ + [131.17460937500007, 33.602587890625045], + [131.69628906250003, 33.60283203124999], + [131.53740234375007, 33.274072265624994], + [131.89658203125006, 33.25458984375001], + [131.8478515625001, 33.118066406249994], + [132.0021484375001, 32.882373046875045], + [131.6603515625001, 32.465625], + [131.33720703125007, 31.4046875], + [131.07080078124997, 31.436865234374977], + [131.09843750000002, 31.256152343750017], + [130.68574218750004, 31.01513671875003], + [130.77626953125, 31.70629882812497], + [130.65507812500002, 31.71840820312505], + [130.5560546875, 31.563085937500034], + [130.58876953125, 31.178515625000017], + [130.20068359374997, 31.291894531250023], + [130.14726562500002, 31.40849609374996], + [130.2941406250001, 31.45068359375003], + [130.3219726562501, 31.601464843750023], + [130.18789062500005, 31.768847656250017], + [130.19443359375012, 32.090771484374955], + [130.64052734375005, 32.61923828124998], + [130.49785156250002, 32.65693359375001], + [130.547265625, 32.83159179687499], + [130.2375, 33.177636718749966], + [130.12685546875005, 33.10483398437506], + [130.175, 32.851318359375], + [130.32646484375002, 32.852636718750006], + [130.34042968750012, 32.70185546875004], + [130.05410156250005, 32.770800781250045], + [129.76855468749997, 32.57099609375001], + [129.82675781250006, 32.72534179687503], + [129.67910156250005, 33.059960937499966], + [129.99169921875003, 32.85156249999997], + [129.58007812500003, 33.23627929687501], + [129.61015625000002, 33.34365234375005], + [129.844140625, 33.32177734375003], + [129.82568359374997, 33.43701171875006], + [130.36503906250007, 33.634472656249955], + [130.4837890625, 33.834619140624966], + [130.715625, 33.92778320312502], + [130.953125, 33.87202148437504], + [131.17460937500007, 33.602587890625045] + ] + ], + [ + [ + [132.266015625, 33.945166015625006], + [132.44492187500006, 33.91318359374998], + [132.20878906250007, 33.87285156250002], + [132.266015625, 33.945166015625006] + ] + ], + [ + [ + [129.27949218750004, 34.123388671875006], + [129.18642578125, 34.14501953125006], + [129.21484374999997, 34.320654296875034], + [129.3371093750001, 34.284765625], + [129.27949218750004, 34.123388671875006] + ] + ], + [ + [ + [134.35742187500003, 34.25634765625], + [134.6375, 34.22661132812499], + [134.73886718750012, 33.82050781250001], + [134.37705078125012, 33.60839843749997], + [134.18164062500003, 33.24721679687502], + [133.95869140625004, 33.44833984375006], + [133.63203125000004, 33.51098632812503], + [133.28593750000007, 33.35996093749998], + [132.97724609375004, 32.84199218749998], + [132.80429687500006, 32.75200195312502], + [132.6417968750001, 32.76245117187503], + [132.70898437500003, 32.90249023437505], + [132.49511718749997, 32.91660156249998], + [132.41279296875004, 33.43046875], + [132.0326171875, 33.339990234374994], + [132.64306640624997, 33.68994140624997], + [132.93515625000006, 34.09531250000006], + [133.19306640625004, 33.93320312499998], + [133.58203124999997, 34.01713867187502], + [133.60263671875006, 34.24384765625001], + [133.94833984375006, 34.34804687500002], + [134.35742187500003, 34.25634765625] + ] + ], + [ + [ + [134.35185546875002, 34.48364257812503], + [134.25185546875, 34.42304687500004], + [134.18212890625003, 34.51923828124998], + [134.35185546875002, 34.48364257812503] + ] + ], + [ + [ + [134.9328125000001, 34.28813476562499], + [134.82441406250004, 34.202929687500045], + [134.66787109375005, 34.294140624999955], + [135.00468750000002, 34.54404296874998], + [134.9328125000001, 34.28813476562499] + ] + ], + [ + [ + [129.38564453125, 34.35366210937502], + [129.26669921875012, 34.37045898437506], + [129.45107421875005, 34.68657226562499], + [129.38564453125, 34.35366210937502] + ] + ], + [ + [ + [133.37050781250005, 36.203857421875], + [133.23925781249997, 36.178759765625045], + [133.20615234375006, 36.293408203124955], + [133.29570312500002, 36.34013671874996], + [133.37050781250005, 36.203857421875] + ] + ], + [ + [ + [138.34404296875007, 37.822119140625006], + [138.22519531250006, 37.82939453124996], + [138.25, 38.078466796875006], + [138.50361328125004, 38.31591796875006], + [138.45361328124997, 38.07568359375006], + [138.57519531249997, 38.065527343750034], + [138.34404296875007, 37.822119140625006] + ] + ], + [ + [ + [141.22929687500007, 41.37265625], + [141.45546875000005, 41.404736328124955], + [141.43046875000002, 40.72333984374998], + [141.7970703125001, 40.29116210937502], + [141.97695312500005, 39.428808593750034], + [141.90078125, 39.111328125], + [141.5462890625, 38.762841796874966], + [141.4674804687501, 38.404150390625006], + [141.10839843750003, 38.33793945312502], + [140.9621093750001, 38.148876953124955], + [141.00166015625004, 37.11464843750002], + [140.57353515625007, 36.23134765625002], + [140.87402343749997, 35.72495117187506], + [140.457421875, 35.51025390625], + [140.35468750000004, 35.18144531249999], + [139.8439453125001, 34.914892578125034], + [139.82646484375002, 35.29667968750002], + [140.096875, 35.58515624999998], + [139.83476562500002, 35.658056640625006], + [139.65000000000012, 35.40913085937501], + [139.675, 35.149267578125006], + [139.47441406250002, 35.298535156249955], + [139.24941406250005, 35.27802734375004], + [139.08603515625006, 34.83916015624999], + [138.8375, 34.619238281250034], + [138.80273437499997, 34.97480468749998], + [138.90361328125002, 35.02524414062506], + [138.71962890625, 35.12407226562502], + [138.18906250000012, 34.596337890624994], + [137.543359375, 34.66420898437505], + [137.06171875000004, 34.58281249999999], + [137.27519531250002, 34.77250976562499], + [136.96328125000005, 34.83491210937501], + [136.87128906250004, 34.733105468749955], + [136.89707031250006, 35.03554687500002], + [136.80419921874997, 35.05029296875], + [136.53300781250007, 34.678369140624994], + [136.8802734375, 34.43359375000006], + [136.8537109375001, 34.324072265625034], + [136.32988281250007, 34.17685546875006], + [135.91621093750004, 33.561718749999955], + [135.69531250000003, 33.48696289062502], + [135.4528320312501, 33.55336914062505], + [135.12792968749997, 34.006982421874994], + [135.10009765624997, 34.288378906250045], + [135.41591796875, 34.61748046875002], + [134.74003906250007, 34.765234375], + [134.246875, 34.71386718750003], + [133.96826171874997, 34.52729492187504], + [133.14238281250002, 34.30244140624998], + [132.65654296875007, 34.24609375000003], + [132.31259765625006, 34.32495117187503], + [132.14648437499997, 33.83876953125002], + [131.74052734375007, 34.05205078125002], + [130.91884765625, 33.97573242187502], + [130.88925781250012, 34.261816406250034], + [131.00419921875007, 34.39257812500003], + [131.35439453125, 34.41318359375006], + [132.92294921875006, 35.511279296875045], + [133.98125, 35.50722656250002], + [135.17431640625003, 35.74707031250003], + [135.32695312500002, 35.52553710937502], + [135.68027343750006, 35.503125], + [135.903125, 35.60688476562498], + [136.09531250000006, 35.767626953125045], + [136.06748046875006, 36.11684570312505], + [136.69814453125005, 36.742041015625034], + [136.84345703125004, 37.38212890624999], + [137.32265625, 37.52207031249998], + [136.89990234375003, 37.11767578125], + [137.01669921875006, 36.83720703124999], + [137.24628906250004, 36.753173828125], + [137.5140625, 36.95156250000002], + [138.31992187500012, 37.21840820312502], + [138.88505859375007, 37.84394531250001], + [139.36386718750006, 38.09902343750002], + [139.80195312500004, 38.881591796875], + [140.06474609375002, 39.624414062499994], + [139.99472656250006, 39.855078125], + [139.74150390625002, 39.92084960937498], + [140.01113281250005, 40.26035156250006], + [139.92285156250003, 40.59843750000002], + [140.28125, 40.84609375000002], + [140.3444335937501, 41.203320312499955], + [140.62763671875004, 41.195410156250034], + [140.74863281250012, 40.830322265625], + [140.93603515625003, 40.940771484375034], + [141.1185546875, 40.88227539062501], + [141.24423828125006, 41.20561523437499], + [140.80058593750002, 41.138818359374966], + [140.80185546875012, 41.253662109375], + [140.9369140625, 41.50556640624998], + [141.22929687500007, 41.37265625] + ] + ], + [ + [ + [139.48125, 42.08100585937498], + [139.43134765625004, 42.19956054687498], + [139.55839843750002, 42.235205078125034], + [139.48125, 42.08100585937498] + ] + ], + [ + [ + [141.29541015625003, 45.11933593750001], + [141.14531250000002, 45.153906250000034], + [141.19375, 45.24785156249999], + [141.29541015625003, 45.11933593750001] + ] + ], + [ + [ + [141.07275390624997, 45.33286132812498], + [141.03398437500007, 45.26933593750002], + [140.97167968749997, 45.465478515624994], + [141.07275390624997, 45.33286132812498] + ] + ], + [ + [ + [143.82431640625012, 44.11699218749999], + [144.71523437500005, 43.92797851562503], + [145.36953125000005, 44.32739257812506], + [145.13964843750003, 43.6625], + [145.34082031249997, 43.30253906249999], + [145.83300781249997, 43.38593750000001], + [144.92138671874997, 43.00092773437498], + [143.96933593750006, 42.88139648437499], + [143.42949218750002, 42.41889648437498], + [143.2365234375001, 42.000195312499955], + [141.85136718750007, 42.57905273437501], + [141.40664062500005, 42.54692382812496], + [140.98613281250002, 42.34213867187498], + [140.70976562500002, 42.555615234374955], + [140.48046875000003, 42.559375], + [140.32666015625003, 42.29335937499999], + [141.15097656250012, 41.80507812499999], + [140.99951171874997, 41.73740234375006], + [140.65986328125004, 41.815576171874994], + [140.3849609375001, 41.51928710937503], + [140.08515625000004, 41.43408203125], + [139.99531250000004, 41.57641601562503], + [140.10839843749997, 41.912939453125034], + [139.83544921874997, 42.278076171875], + [139.86015625000002, 42.58173828125004], + [140.43222656250012, 42.95410156250006], + [140.39238281250002, 43.303125], + [141.13818359374997, 43.17993164062506], + [141.37412109375006, 43.279638671875006], + [141.7609375000001, 44.482519531250034], + [141.58300781250003, 45.15595703125001], + [141.66796874999997, 45.401269531249966], + [141.93769531250004, 45.509521484375], + [142.88476562499997, 44.670117187499955], + [143.82431640625012, 44.11699218749999] + ] + ] + ] + }, + "properties": { "name": "Japan", "childNum": 28 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [77.04863281249999, 35.109912109374996], + [76.927734375, 35.346630859375], + [76.88222656250002, 35.4357421875], + [76.81279296874999, 35.571826171874996], + [76.76689453124999, 35.66171875], + [76.87890625, 35.61328125], + [77.09003906250001, 35.552050781249996], + [77.29482421875002, 35.508154296875], + [77.44648437500001, 35.4755859375], + [77.57255859374999, 35.471826171874994], + [77.72402343750002, 35.48056640625], + [77.79941406250003, 35.495898437499996], + [77.42343750000003, 35.302587890625], + [77.16855468750003, 35.171533203124994], + [77.04863281249999, 35.109912109374996] + ] + ] + }, + "properties": { "name": "Siachen Glacier", "childNum": 1 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [50.184472656249994, 44.854638671874994], + [49.99511718750003, 44.93696289062498], + [50.10986328124997, 45.08193359375002], + [50.038867187500074, 44.949121093749966], + [50.184472656249994, 44.854638671874994] + ] + ], + [ + [ + [87.32285156250012, 49.085791015625006], + [86.8083007812501, 49.04970703125002], + [86.54941406250012, 48.52861328125002], + [85.7494140625, 48.38505859374999], + [85.52597656250006, 47.915625], + [85.65664062500005, 47.254638671875], + [85.484765625, 47.06352539062496], + [84.78613281249997, 46.83071289062505], + [84.66660156250006, 46.97236328125004], + [84.016015625, 46.97050781250002], + [83.02949218750004, 47.18593750000002], + [82.31523437500002, 45.59492187499998], + [82.61162109375007, 45.424267578124955], + [82.52148437500003, 45.12548828125], + [82.26660156249997, 45.21909179687498], + [81.94492187500006, 45.16083984375001], + [81.69199218750012, 45.34936523437497], + [80.05917968750012, 45.006445312500006], + [79.871875, 44.88378906249997], + [80.48154296875006, 44.71464843749999], + [80.35527343750002, 44.09726562500006], + [80.78574218750006, 43.16157226562504], + [80.39023437500006, 43.043115234374966], + [80.53896484375005, 42.873486328124955], + [80.20224609375012, 42.73447265624998], + [80.209375, 42.190039062500006], + [80.07128906249997, 42.302978515625], + [79.92109375000004, 42.41313476562496], + [79.49013671875, 42.45756835937496], + [79.42822265624997, 42.483496093750006], + [79.20302734375005, 42.66601562499997], + [79.16484375000007, 42.759033203125], + [79.1266601562501, 42.775732421875034], + [76.98808593750007, 42.97358398437501], + [76.64648437500003, 42.928808593750034], + [76.50917968750005, 42.91889648437498], + [75.9322265625, 42.92851562499999], + [75.84033203125003, 42.9375], + [75.78955078124997, 42.93291015624999], + [75.68173828125, 42.83046875], + [75.04765625000007, 42.904394531250034], + [74.20908203125006, 43.24038085937502], + [73.88603515625002, 43.132568359375], + [73.55625, 43.002783203125006], + [73.45019531249997, 42.703027343749966], + [73.421875, 42.59350585937503], + [73.49296875000007, 42.409033203125034], + [73.41162109375003, 42.41977539062498], + [73.316015625, 42.46699218750001], + [73.2829101562501, 42.50410156250004], + [72.85507812500006, 42.561132812500006], + [72.75292968750003, 42.63789062500001], + [72.54316406250004, 42.67773437500006], + [72.27578125, 42.757666015625006], + [71.76054687500002, 42.82148437500004], + [71.5142578125, 42.766943359375006], + [71.42207031250004, 42.78315429687504], + [71.25664062500002, 42.733544921874966], + [70.89287109375007, 42.339990234374994], + [70.94677734374997, 42.24868164062505], + [69.15361328125002, 41.42524414062498], + [68.58408203125, 40.876269531250045], + [68.57265625, 40.62265624999998], + [68.29189453125, 40.656103515625034], + [68.04765625000007, 40.80927734374998], + [68.11308593750007, 41.02861328124999], + [67.9357421875001, 41.19658203125002], + [66.70966796875004, 41.17915039062501], + [66.49863281250006, 41.99487304687503], + [66.00957031250007, 42.00488281250003], + [66.1002929687501, 42.99082031249998], + [65.80302734375002, 42.87695312500006], + [65.49619140625, 43.310546875], + [64.9054687500001, 43.714697265625006], + [64.44316406250007, 43.55117187499999], + [63.20703125000003, 43.62797851562502], + [61.99023437500003, 43.492138671874955], + [61.007910156250006, 44.39379882812497], + [58.555273437500006, 45.55537109375001], + [55.97568359375006, 44.99492187499996], + [55.97744140625005, 41.32221679687504], + [55.434375, 41.296289062499994], + [54.85380859375002, 41.965185546875006], + [54.120996093749994, 42.335205078125], + [53.0558593750001, 42.14775390624999], + [52.4938476562501, 41.780371093750034], + [52.59658203125005, 42.760156249999966], + [51.898242187500074, 42.86962890624997], + [51.61601562500002, 43.15844726562503], + [51.29541015624997, 43.17412109375002], + [51.30175781249997, 43.48237304687501], + [50.8307617187501, 44.192773437499966], + [50.331152343750006, 44.32548828125002], + [50.25292968749997, 44.461523437500006], + [50.409472656250074, 44.6240234375], + [51.543554687500006, 44.53100585937506], + [51.009375, 44.92182617187501], + [51.4157226562501, 45.35786132812501], + [53.20039062500004, 45.33198242187498], + [52.77382812499999, 45.57275390625], + [53.13525390625003, 46.19165039062497], + [53.069433593750006, 46.85605468750006], + [52.48320312500002, 46.99067382812504], + [52.13828125, 46.82861328124997], + [51.178027343750074, 47.110156250000045], + [49.886328125, 46.59565429687504], + [49.347460937500074, 46.51914062499998], + [49.232226562500074, 46.33715820312503], + [48.54121093750004, 46.60561523437502], + [48.558398437500074, 46.75712890624999], + [48.959375, 46.77460937499998], + [48.16699218750003, 47.70878906249996], + [47.48193359374997, 47.80390624999998], + [47.292382812499994, 47.74091796875004], + [47.06464843750004, 48.23247070312499], + [46.660937500000074, 48.41225585937502], + [46.70263671875003, 48.80556640625002], + [47.031347656250006, 49.150292968749994], + [46.80205078125002, 49.36708984375002], + [46.889550781249994, 49.69697265625001], + [47.42919921874997, 50.35795898437502], + [47.7057617187501, 50.37797851562502], + [48.33496093750003, 49.858251953125006], + [48.7589843750001, 49.92832031250006], + [48.625097656250006, 50.61269531250005], + [49.32343750000004, 50.851708984374966], + [49.49804687500003, 51.08359375000006], + [50.246875, 51.28950195312498], + [50.79394531249997, 51.729199218749955], + [51.16347656250005, 51.6474609375], + [51.344531250000074, 51.47534179687503], + [52.21914062499999, 51.709375], + [52.57119140625005, 51.481640624999955], + [53.33808593750004, 51.48237304687504], + [54.139746093750006, 51.04077148437503], + [54.555273437500006, 50.535791015624994], + [54.64160156250003, 51.011572265625034], + [55.68623046875004, 50.582861328125006], + [56.49140625000004, 51.01953124999997], + [57.01171874999997, 51.06518554687503], + [57.44218750000002, 50.88886718749998], + [57.83886718750003, 51.091650390625006], + [58.359179687500074, 51.063818359375034], + [58.88369140625005, 50.694433593750006], + [59.4523437500001, 50.62041015625002], + [59.523046875, 50.492871093749955], + [59.812402343749994, 50.58203125], + [60.05859374999997, 50.850292968749955], + [60.42480468749997, 50.67915039062498], + [60.94228515625005, 50.69550781250004], + [61.38945312500002, 50.86103515625001], + [61.55468750000003, 51.32460937500005], + [60.464746093749994, 51.651171875000045], + [60.03027343749997, 51.93325195312505], + [60.99453125000005, 52.33686523437504], + [60.77441406249997, 52.67578124999997], + [61.047460937500006, 52.97246093750002], + [62.08271484375004, 53.00541992187499], + [61.65986328125004, 53.22846679687504], + [61.19921874999997, 53.28715820312502], + [61.22890625, 53.445898437500006], + [61.53496093750002, 53.52329101562506], + [60.97949218749997, 53.62172851562505], + [61.231054687500006, 54.01948242187498], + [61.92871093750003, 53.94648437500004], + [64.46123046875002, 54.38417968750002], + [65.08837890624997, 54.340185546875034], + [65.476953125, 54.62329101562497], + [68.15585937500006, 54.97670898437505], + [68.20625, 55.16093750000002], + [68.9772460937501, 55.389599609374955], + [70.18242187500002, 55.162451171875034], + [70.73808593750007, 55.30517578125], + [71.18554687500003, 54.59931640624998], + [71.09316406250005, 54.21220703124999], + [72.00449218750006, 54.20566406249998], + [72.18603515625003, 54.32563476562501], + [72.44677734375003, 53.94184570312498], + [72.62226562500004, 54.13432617187502], + [73.22988281250005, 53.957812500000045], + [73.71240234375003, 54.04238281250002], + [73.30566406250003, 53.707226562499955], + [73.40693359375004, 53.44755859374999], + [73.85898437500006, 53.61972656249998], + [74.35156250000003, 53.487646484375006], + [74.45195312500007, 53.64726562500002], + [75.22021484374997, 53.89379882812506], + [75.43720703125004, 54.08964843749999], + [76.8373046875, 54.4423828125], + [76.65458984375007, 54.14526367187503], + [76.42167968750007, 54.151513671874966], + [76.48476562500005, 54.02255859374998], + [77.85996093750006, 53.269189453124994], + [79.98623046875, 50.774560546874966], + [80.42363281250002, 50.94628906249997], + [80.44804687500002, 51.18334960937503], + [80.73525390625, 51.29340820312498], + [81.12724609375002, 51.19106445312502], + [81.0714843750001, 50.96875], + [81.38828125000006, 50.95649414062501], + [81.46591796875006, 50.73984375], + [82.49394531250007, 50.72758789062499], + [82.76083984375012, 50.89335937500002], + [83.35732421875005, 50.99458007812504], + [83.94511718750007, 50.774658203125], + [84.32324218749997, 50.239160156249966], + [84.9894531250001, 50.061425781249994], + [85.2326171875001, 49.61582031249998], + [86.1808593750001, 49.49931640624996], + [86.67548828125004, 49.77729492187501], + [86.62646484374997, 49.56269531250001], + [87.32285156250012, 49.085791015625006] + ] + ] + ] + }, + "properties": { "name": "Kazakhstan", "childNum": 2 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [35.325292968750006, 5.364892578124994], + [35.745019531249994, 5.343994140625], + [35.80029296874997, 5.156933593749983], + [35.77929687499997, 5.105566406250006], + [35.756152343750074, 4.950488281250031], + [35.76308593750005, 4.808007812500051], + [36.02197265625003, 4.468115234374991], + [36.90556640625002, 4.411474609374991], + [37.15458984375002, 4.254541015624994], + [37.944921875, 3.746728515625023], + [38.0861328125001, 3.648828124999966], + [38.22529296875004, 3.61899414062502], + [38.45156250000005, 3.604833984374977], + [38.608007812500006, 3.600097656249986], + [39.49443359375002, 3.45610351562496], + [39.65751953125002, 3.577832031249983], + [39.79033203125002, 3.754248046875034], + [39.8421875, 3.851464843750037], + [40.765234375, 4.273046875000034], + [41.02080078125002, 4.057470703124991], + [41.22089843750004, 3.943554687499969], + [41.372460937499994, 3.94619140624998], + [41.48193359375003, 3.96328125], + [41.737695312499994, 3.979052734375003], + [41.88398437500004, 3.977734375000011], + [41.6134765625001, 3.59047851562498], + [41.34179687499997, 3.20166015625], + [40.964453125, 2.814648437500026], + [40.9787109375001, -0.870312500000011], + [41.249804687500074, -1.220507812499946], + [41.4269531250001, -1.449511718749974], + [41.521875, -1.572265625000028], + [41.53271484374997, -1.695312499999957], + [41.26748046875005, -1.945019531250026], + [40.889746093750006, -2.023535156250034], + [40.89824218750002, -2.269921874999966], + [40.64414062500006, -2.53945312499998], + [40.22246093750002, -2.688378906250037], + [40.1154296875001, -3.250585937499991], + [39.8609375, -3.576757812500006], + [39.49091796875004, -4.478417968750023], + [39.221777343750006, -4.692382812500014], + [37.608203125000074, -3.497070312500028], + [37.643847656250074, -3.045410156250028], + [33.90322265625005, -1.002050781250034], + [33.94316406250002, 0.173779296874969], + [34.160937500000074, 0.605175781250026], + [34.4108398437501, 0.867285156250034], + [34.48173828125002, 1.042138671875051], + [34.79863281250002, 1.24453125], + [34.976464843749994, 1.719628906250051], + [34.97753906249997, 1.861914062499991], + [34.9640625000001, 2.06240234374998], + [34.8830078125001, 2.417919921875026], + [34.90576171875003, 2.4796875], + [34.44785156250006, 3.163476562500037], + [34.40722656249997, 3.357519531250034], + [34.39941406249997, 3.412695312500006], + [34.44179687499999, 3.60625], + [34.43769531250004, 3.650585937499969], + [34.392871093750074, 3.691503906250048], + [34.26708984375003, 3.733154296875], + [34.16503906250003, 3.812988281250014], + [34.18574218750004, 3.869775390625037], + [34.13203125000004, 3.889160156249986], + [33.97607421874997, 4.220214843750028], + [34.176855468750006, 4.419091796875037], + [34.38017578125002, 4.620654296874974], + [34.6398437500001, 4.875488281250028], + [34.878320312499994, 5.109570312500026], + [35.08447265624997, 5.31186523437502], + [35.268359375000074, 5.492285156250006], + [35.325292968750006, 5.364892578124994] + ] + ] + }, + "properties": { "name": "Kenya", "childNum": 1 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [72.63994140625002, 39.385986328125], + [72.22998046875, 39.20751953125], + [72.14736328125002, 39.2607421875], + [72.08417968750001, 39.31064453125], + [72.04277343750002, 39.3521484375], + [71.77861328125002, 39.277978515624994], + [71.73222656250002, 39.422998046874994], + [71.50332031250002, 39.478808593749996], + [71.51738281250002, 39.553857421874994], + [71.50302734375, 39.582177734374994], + [71.4703125, 39.603662109374994], + [70.79931640625, 39.3947265625], + [70.50117187500001, 39.587353515625], + [69.29765625000002, 39.524804687499994], + [69.2447265625, 39.827099609375], + [69.27880859375, 39.917773437499996], + [69.3072265625, 39.968554687499996], + [69.36542968750001, 39.947070312499996], + [69.43193359375002, 39.909765625], + [69.47626953125001, 39.919726562499996], + [69.47099609375002, 39.990625], + [69.46875, 40.020751953125], + [69.966796875, 40.20224609375], + [70.59921875, 39.974511718749994], + [70.990625, 40.2548828125], + [71.3046875, 40.286914062499996], + [71.69248046875, 40.15234375], + [72.13125, 40.438623046874994], + [72.3892578125, 40.427392578124994], + [72.40205078125001, 40.578076171875], + [72.6041015625, 40.525439453124996], + [73.13212890625002, 40.82851562499999], + [72.65830078125, 40.869921875], + [72.36406250000002, 41.04345703125], + [72.294921875, 41.039941406249994], + [72.21308593750001, 41.0142578125], + [72.18730468750002, 41.025927734374996], + [72.18095703125002, 41.118457031249996], + [72.16425781250001, 41.173730468749994], + [72.11542968750001, 41.186572265624996], + [72.05244140625001, 41.16474609375], + [71.95849609375, 41.187060546874996], + [71.87861328125001, 41.19501953125], + [71.8580078125, 41.311376953125], + [71.79248046875, 41.413134765624996], + [71.75771484375002, 41.428027343749996], + [71.70068359375, 41.454003906249994], + [71.66494140625002, 41.5412109375], + [71.6375, 41.5341796875], + [71.60224609375001, 41.503271484375], + [71.60625, 41.367431640625], + [71.54560546875001, 41.308056640625], + [71.5, 41.307470703125], + [71.4208984375, 41.34189453125], + [71.40839843750001, 41.136035156249996], + [71.39306640625, 41.123388671875], + [71.11074218750002, 41.152636718749996], + [70.86044921875, 41.224902343749996], + [70.734375, 41.400537109374994], + [70.18095703125002, 41.571435546874994], + [70.85664062500001, 42.030810546874996], + [71.0322265625, 42.077783203124994], + [71.228515625, 42.162890625], + [71.23232421875002, 42.186279296875], + [71.21269531250002, 42.206445312499994], + [71.12998046875, 42.25], + [71.03603515625002, 42.28466796875], + [70.97900390625, 42.266552734375], + [70.94677734375, 42.248681640624994], + [70.89287109375002, 42.339990234374994], + [71.25664062500002, 42.733544921874994], + [71.42207031250001, 42.783154296875], + [71.5142578125, 42.766943359375], + [71.76054687500002, 42.821484375], + [72.16181640625001, 42.760693359375], + [72.27578125000002, 42.757666015625], + [72.54316406250001, 42.677734375], + [72.7529296875, 42.637890625], + [72.855078125, 42.5611328125], + [73.28291015625001, 42.5041015625], + [73.316015625, 42.4669921875], + [73.41162109375, 42.419775390625], + [73.49296875000002, 42.409033203125], + [73.421875, 42.593505859375], + [73.4501953125, 42.703027343749994], + [73.55625, 43.002783203125], + [73.88603515625002, 43.132568359375], + [74.20908203125, 43.240380859374994], + [75.04765625000002, 42.90439453125], + [75.68173828125, 42.83046875], + [75.78955078125, 42.932910156249996], + [75.84033203125, 42.9375], + [75.9322265625, 42.928515625], + [76.50917968750002, 42.918896484375], + [76.646484375, 42.92880859375], + [76.98808593749999, 42.973583984375], + [79.12666015625001, 42.775732421875], + [79.20302734375002, 42.666015625], + [79.29550781250003, 42.604833984375], + [79.36777343750003, 42.547216796875], + [79.42822265625, 42.48349609375], + [79.92109375000001, 42.413134765624996], + [80.0712890625, 42.302978515625], + [80.209375, 42.1900390625], + [80.24619140625003, 42.059814453125], + [80.23515624999999, 42.04345703125], + [80.21621093750002, 42.032421875], + [79.90966796875, 42.014990234375], + [79.84042968750003, 41.995751953124994], + [79.76611328125, 41.898876953125], + [78.74257812500002, 41.56005859375], + [78.54316406250001, 41.4595703125], + [78.44287109375, 41.417529296874996], + [78.36240234375003, 41.371630859374996], + [78.34628906250003, 41.2814453125], + [78.12343750000002, 41.075634765625], + [77.95644531250002, 41.050683593749994], + [77.81523437499999, 41.055615234375], + [77.71933593750003, 41.024316406249994], + [77.58173828125001, 40.9927734375], + [76.98662109374999, 41.03916015625], + [76.90771484375, 41.024169921875], + [76.82402343749999, 40.982324218749994], + [76.70839843750002, 40.818115234375], + [76.6611328125, 40.779638671875], + [76.63984375000001, 40.742236328124996], + [76.62216796875003, 40.662353515625], + [76.57792968749999, 40.577880859375], + [76.48017578125001, 40.449511718749996], + [76.39638671875002, 40.389794921874994], + [76.31855468750001, 40.35224609375], + [76.25830078125, 40.43076171875], + [75.87197265625002, 40.30322265625], + [75.67714843750002, 40.305810546874994], + [75.55556640625002, 40.6251953125], + [75.52080078125002, 40.6275390625], + [75.24101562500002, 40.480273437499996], + [75.111328125, 40.4541015625], + [75.0044921875, 40.449511718749996], + [74.865625, 40.493505859375], + [74.80126953125, 40.428515625], + [74.83046875000002, 40.32851562499999], + [74.41191406250002, 40.13720703125], + [74.24267578125, 40.092041015625], + [74.08515625000001, 40.07431640625], + [73.99160156250002, 40.043115234374994], + [73.93876953125002, 39.978808593749996], + [73.88457031250002, 39.8779296875], + [73.85625, 39.828662109374996], + [73.83535156250002, 39.800146484375], + [73.83974609375002, 39.762841796874994], + [73.88251953125001, 39.71455078125], + [73.9146484375, 39.606494140624996], + [73.90712890625002, 39.57851562499999], + [73.87275390625001, 39.53330078125], + [73.82294921875001, 39.48896484375], + [73.71572265625002, 39.462255859375], + [73.63164062500002, 39.448876953124994], + [73.47041015625001, 39.460595703124994], + [73.38740234375001, 39.442724609375], + [73.33613281250001, 39.412353515625], + [73.2349609375, 39.374560546874996], + [73.10927734375002, 39.3619140625], + [72.63994140625002, 39.385986328125] + ], + [ + [70.66416015625, 39.85546875], + [70.56708984375001, 39.866601562499994], + [70.49775390625001, 39.882421875], + [70.48281250000002, 39.882714843749994], + [70.4892578125, 39.863037109375], + [70.5595703125, 39.790917968749994], + [70.61210937500002, 39.786767578124994], + [70.70166015625, 39.82529296875], + [70.66416015625, 39.85546875] + ], + [ + [71.20615234375, 39.892578125], + [71.22871093750001, 40.048144531249996], + [71.08037109375002, 40.079882812499996], + [71.02412109375001, 40.149169921875], + [71.00546875, 40.152294921875], + [70.96064453125001, 40.08798828125], + [71.04482421875002, 39.992529296875], + [71.04365234375001, 39.976318359375], + [71.01171875, 39.8951171875], + [71.06425781250002, 39.884912109374994], + [71.15625, 39.883447265624994], + [71.20615234375, 39.892578125] + ] + ] + }, + "properties": { "name": "Kyrgyzstan", "childNum": 3 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [104.42636718750006, 10.411230468749991], + [103.87050781250005, 10.655126953125034], + [103.58710937500004, 10.552197265625026], + [103.54042968750005, 10.668701171875043], + [103.721875, 10.890136718750043], + [103.5324218750001, 11.146679687499997], + [103.35361328125006, 10.921582031250054], + [103.15283203124997, 10.913720703125051], + [103.12548828124997, 11.460644531250011], + [102.9486328125, 11.773486328124974], + [102.93388671875002, 11.706689453125037], + [102.73662109375007, 12.089794921875011], + [102.75566406250002, 12.42626953125], + [102.49960937500012, 12.669970703125003], + [102.33632812500005, 13.560302734375014], + [102.546875, 13.585693359375043], + [102.90927734375006, 14.136718750000028], + [103.19941406250004, 14.332617187499977], + [104.77900390625004, 14.427832031250006], + [105.07412109375005, 14.227441406250037], + [105.12597656250003, 14.280957031250011], + [105.16914062500004, 14.336083984374966], + [105.1833007812501, 14.346240234374989], + [105.18554687500003, 14.319091796874972], + [105.20703125000003, 14.259375], + [105.24570312500006, 14.200537109374977], + [105.35019531250006, 14.109570312500011], + [105.53154296875007, 14.156152343749994], + [105.73974609375003, 14.084960937500057], + [105.83144531250005, 13.976611328125003], + [105.9044921875001, 13.924511718750054], + [106.06679687500005, 13.921191406250003], + [106.12470703125004, 14.049121093750031], + [106.09667968749997, 14.127099609375023], + [106.00410156250004, 14.262890624999983], + [105.97890625, 14.343017578125043], + [106.00839843750012, 14.357177734375], + [106.1652343750001, 14.372363281249989], + [106.19072265625007, 14.388134765624997], + [106.22539062500002, 14.476220703125009], + [106.26796875, 14.466210937500009], + [106.35498046875003, 14.454785156249997], + [106.44697265625004, 14.515039062500009], + [106.50146484375003, 14.578222656250006], + [106.53115234375005, 14.549414062499991], + [106.5636718750001, 14.505078125000026], + [106.59921875000006, 14.479394531250037], + [106.66542968750005, 14.441308593749994], + [106.73818359375005, 14.387744140625017], + [106.78349609375002, 14.335107421875037], + [106.81992187500006, 14.314697265625057], + [106.91318359375006, 14.329394531250031], + [106.93808593750006, 14.327343750000054], + [106.99218750000003, 14.391015624999966], + [107.03017578125, 14.425683593750009], + [107.06240234375, 14.415771484375043], + [107.109375, 14.416699218750054], + [107.29267578125004, 14.592382812500048], + [107.37988281250003, 14.555322265625051], + [107.41474609375004, 14.56289062499999], + [107.51943359375005, 14.705078125], + [107.3314453125, 14.126611328125009], + [107.60546874999997, 13.437792968750017], + [107.47539062500002, 13.030371093749963], + [107.50644531250006, 12.364550781250031], + [107.39335937500002, 12.260498046874972], + [107.21210937500004, 12.30400390624996], + [106.70009765625, 11.979296874999974], + [106.41386718750002, 11.9484375], + [106.39921875000007, 11.687011718750028], + [106.0060546875001, 11.758007812500011], + [105.85146484375005, 11.635009765625], + [105.85605468750006, 11.294287109375048], + [106.16093750000002, 11.037109375000057], + [106.16396484375005, 10.794921875], + [105.85332031250007, 10.86357421874996], + [105.75507812500004, 10.989990234375043], + [105.40576171875003, 10.95161132812504], + [105.3146484375001, 10.845166015625026], + [105.04570312500002, 10.911376953125014], + [105.04638671874997, 10.701660156250014], + [104.85058593749997, 10.534472656249974], + [104.42636718750006, 10.411230468749991] + ] + ] + }, + "properties": { "name": "Cambodia", "childNum": 1 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [-157.34213867187503, 1.855566406250034], + [-157.17578125, 1.73984375], + [-157.57895507812498, 1.902050781249997], + [-157.43583984374993, 1.84726562500002], + [-157.365185546875, 1.94609375], + [-157.44189453125003, 2.025048828125009], + [-157.321875, 1.968554687500045], + [-157.34213867187503, 1.855566406250034] + ] + ], + [ + [ + [-159.3390625, 3.923535156249983], + [-159.27475585937503, 3.796582031250054], + [-159.40903320312503, 3.87324218750004], + [-159.3390625, 3.923535156249983] + ] + ] + ] + }, + "properties": { "name": "Kiribati", "childNum": 2 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [126.32695312500002, 33.2236328125], + [126.16562500000012, 33.31201171875], + [126.33769531250002, 33.46040039062501], + [126.90117187500002, 33.51513671874997], + [126.87285156250002, 33.34116210937498], + [126.32695312500002, 33.2236328125] + ] + ], + [ + [ + [126.23369140625002, 34.370507812499994], + [126.12285156250002, 34.443945312500034], + [126.34384765625012, 34.544921875], + [126.23369140625002, 34.370507812499994] + ] + ], + [ + [ + [126.17197265625006, 34.73115234375001], + [126.00751953125004, 34.86748046874999], + [126.07841796875002, 34.914843750000045], + [126.17197265625006, 34.73115234375001] + ] + ], + [ + [ + [128.0658203125, 34.80585937500004], + [128.05468750000003, 34.70805664062502], + [127.87343750000005, 34.73496093749998], + [127.8322265625001, 34.87451171875], + [128.0658203125, 34.80585937500004] + ] + ], + [ + [ + [128.74101562500007, 34.798535156249955], + [128.64667968750004, 34.73686523437502], + [128.48925781250003, 34.86528320312496], + [128.66796875000003, 35.0087890625], + [128.74101562500007, 34.798535156249955] + ] + ], + [ + [ + [126.52070312500004, 37.73681640625003], + [126.516015625, 37.60468750000001], + [126.42333984375003, 37.62363281250006], + [126.41162109374997, 37.82265625000002], + [126.52070312500004, 37.73681640625003] + ] + ], + [ + [ + [128.37460937500012, 38.6234375], + [129.41826171875002, 37.059033203124955], + [129.40351562500004, 36.052148437499994], + [129.57285156250006, 36.05053710937503], + [129.4191406250001, 35.49785156249996], + [129.07675781250006, 35.12270507812502], + [128.5109375000001, 35.10097656250002], + [128.44394531250012, 34.87036132812503], + [128.03623046875006, 35.02197265625], + [127.71484374999997, 34.95468749999998], + [127.71542968750012, 34.72104492187498], + [127.40429687499997, 34.823095703125006], + [127.47910156250012, 34.625244140625], + [127.324609375, 34.463281249999966], + [127.17343750000006, 34.54614257812497], + [127.24707031249997, 34.755126953125], + [126.89746093749997, 34.438867187499966], + [126.75478515625005, 34.511865234374994], + [126.53144531250004, 34.31425781249999], + [126.26445312500002, 34.67324218750002], + [126.52451171875006, 34.697900390624966], + [126.59335937500012, 34.824365234374994], + [126.42070312500002, 34.823388671874966], + [126.29111328125012, 35.154150390625034], + [126.61406250000007, 35.57099609375004], + [126.4884765625001, 35.647070312500006], + [126.75302734375006, 35.871972656249994], + [126.5404296875, 36.166162109374966], + [126.4876953125, 36.69379882812498], + [126.18085937500004, 36.69160156249998], + [126.16054687500005, 36.77192382812501], + [126.48701171875004, 37.00747070312502], + [126.78447265625007, 36.94843749999998], + [126.87207031249997, 36.82446289062506], + [126.97685546875002, 36.93940429687501], + [126.74638671875002, 37.19355468750001], + [126.63388671875012, 37.78183593750006], + [127.09033203125003, 38.28388671875001], + [128.03896484375, 38.30854492187498], + [128.37460937500012, 38.6234375] + ] + ] + ] + }, + "properties": { "name": "Korea", "childNum": 7 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [48.27539062499997, 29.624316406250017], + [48.17968750000003, 29.611425781250063], + [48.081445312499994, 29.798925781250063], + [48.1847656250001, 29.978857421875034], + [48.348242187500006, 29.78266601562504], + [48.27539062499997, 29.624316406250017] + ] + ], + [ + [ + [48.442480468750006, 28.542919921874983], + [47.671289062499994, 28.53315429687504], + [47.433203125, 28.989550781250017], + [46.53144531250004, 29.09624023437499], + [46.69375, 29.259667968749966], + [46.76933593750002, 29.347460937500017], + [46.90585937500006, 29.5375], + [47.14824218750002, 30.0009765625], + [47.64375, 30.097314453125023], + [47.75390624999997, 30.076611328124955], + [47.97871093750004, 29.98281250000005], + [48.00566406250002, 29.835791015625034], + [48.143457031249994, 29.57246093750001], + [47.96962890625005, 29.61669921874997], + [47.72265624999997, 29.393017578124955], + [48.0514648437501, 29.355371093750023], + [48.442480468750006, 28.542919921874983] + ] + ] + ] + }, + "properties": { "name": "Kuwait", "childNum": 2 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [102.12744140625011, 22.37919921874999], + [102.58251953125006, 21.904296875000057], + [102.66201171875008, 21.676025390625057], + [102.73857421875005, 21.677929687500125], + [102.77109375000015, 21.70966796875001], + [102.79824218750014, 21.797949218750034], + [102.81591796875, 21.807373046875], + [102.94960937500008, 21.681347656250068], + [102.85117187500009, 21.26591796874999], + [102.8837890625, 21.202587890625068], + [103.1044921875, 20.89165039062499], + [103.21074218749999, 20.840625], + [103.46357421874995, 20.779833984375102], + [103.6350585937501, 20.697070312500102], + [104.10136718750005, 20.945507812500125], + [104.1953125, 20.91396484375008], + [104.349609375, 20.82109374999999], + [104.58320312500001, 20.646679687499955], + [104.53271484375, 20.554882812500125], + [104.47861328124998, 20.529589843750102], + [104.40781250000015, 20.485742187500023], + [104.36777343750015, 20.441406250000057], + [104.39218750000015, 20.424755859375068], + [104.49619140625003, 20.41367187499992], + [104.61884765624995, 20.374511718750114], + [104.65644531250001, 20.328515624999966], + [104.66191406250005, 20.289013671875125], + [104.67695312500007, 20.224707031249977], + [104.69873046875006, 20.205322265625114], + [104.84785156250007, 20.202441406250045], + [104.88867187500006, 20.169091796875023], + [104.92919921874994, 20.082812500000045], + [104.92792968750007, 20.01811523437499], + [104.81513671875001, 19.90400390625001], + [104.80175781250011, 19.836132812500068], + [104.74316406250006, 19.754736328124977], + [104.58789062500006, 19.61875], + [104.54628906250014, 19.610546875000068], + [104.25986328125003, 19.685498046875068], + [104.06279296875005, 19.678417968750068], + [104.03203124999999, 19.67514648437492], + [104.0134765625001, 19.646484374999943], + [104.05156250000005, 19.564160156250068], + [104.06289062500002, 19.482568359375136], + [104.02753906250013, 19.420458984375102], + [103.93203125000002, 19.366064453125034], + [103.89638671875002, 19.339990234375023], + [103.89160156249994, 19.30498046874999], + [105.146484375, 18.650976562499977], + [105.14541015625014, 18.616796874999977], + [105.08701171875015, 18.49624023437508], + [105.11455078125005, 18.405273437500057], + [105.45820312500007, 18.154296875000057], + [105.51855468750011, 18.077441406250045], + [105.58847656250015, 17.983691406249932], + [105.69140625, 17.737841796874932], + [106.00625, 17.415283203124943], + [106.26953125, 17.216796875000057], + [106.33339843750002, 17.14370117187508], + [106.42597656250007, 17.00253906250009], + [106.50224609374999, 16.9541015625], + [106.52597656250003, 16.876611328125023], + [106.53369140625, 16.821044921875057], + [106.54619140625005, 16.650732421874977], + [106.65644531250013, 16.492626953125125], + [106.73955078124999, 16.452539062500136], + [106.79160156250015, 16.490332031249977], + [106.83242187500008, 16.526269531250023], + [106.85107421875, 16.515625], + [106.89277343750013, 16.396533203125102], + [106.93066406250006, 16.353125], + [107.39638671875008, 16.04301757812499], + [107.39199218750008, 15.951660156250057], + [107.36064453125005, 15.921728515624977], + [107.18886718750008, 15.838623046875114], + [107.16591796875002, 15.802490234375], + [107.27939453125003, 15.618701171875045], + [107.33876953125002, 15.560498046875125], + [107.56425781249999, 15.3916015625], + [107.62167968750015, 15.309863281250045], + [107.653125, 15.255224609375091], + [107.63369140625008, 15.18984375000008], + [107.58964843749999, 15.118457031250102], + [107.55527343750009, 15.057031250000023], + [107.48037109375014, 14.979882812500136], + [107.5046875000001, 14.91591796875008], + [107.52451171875003, 14.871826171874943], + [107.51376953124998, 14.817382812500057], + [107.51943359375008, 14.705078125000114], + [107.46513671875005, 14.664990234375125], + [107.41474609375007, 14.56289062500008], + [107.37988281250006, 14.555322265625136], + [107.29267578125007, 14.592382812500034], + [107.109375, 14.416699218749955], + [107.06240234375008, 14.415771484374943], + [107.03017578125008, 14.425683593750023], + [106.99218749999994, 14.39101562500008], + [106.93808593750015, 14.327343750000068], + [106.91318359375003, 14.329394531249932], + [106.81992187500003, 14.314697265624943], + [106.7834960937501, 14.335107421875023], + [106.73818359375008, 14.387744140625102], + [106.66542968750002, 14.441308593750023], + [106.59921875000003, 14.479394531250136], + [106.56367187500007, 14.505078125000011], + [106.53115234375002, 14.549414062499977], + [106.50146484375, 14.578222656250034], + [106.22539062500005, 14.476220703125023], + [106.1907226562501, 14.388134765625011], + [106.16523437500007, 14.372363281249989], + [106.00839843750009, 14.357177734375114], + [105.97890625000014, 14.343017578125057], + [106.00410156250013, 14.262890625000068], + [106.09667968750011, 14.127099609375136], + [106.12470703124995, 14.049121093750045], + [106.06679687500008, 13.921191406250102], + [105.90449218750007, 13.924511718750068], + [105.83144531250008, 13.976611328124989], + [105.73974609375006, 14.084960937500057], + [105.5315429687501, 14.156152343750023], + [105.35019531250009, 14.109570312500125], + [105.24570312500015, 14.200537109374977], + [105.20703125000006, 14.259375], + [105.18554687499994, 14.319091796875], + [105.18330078125001, 14.346240234374989], + [105.24365234375006, 14.367871093749955], + [105.34218750000008, 14.416699218749955], + [105.42265624999993, 14.471630859374955], + [105.47558593750006, 14.530126953124977], + [105.49736328125005, 14.590673828125034], + [105.52304687500015, 14.843310546874989], + [105.54667968749999, 14.932470703125034], + [105.53339843750013, 15.041601562500091], + [105.49042968750007, 15.127587890625023], + [105.49042968750007, 15.256591796875], + [105.615625, 15.488281249999943], + [105.63886718750013, 15.585937499999943], + [105.64101562500002, 15.656542968749932], + [105.62207031250006, 15.699951171875114], + [105.39892578125011, 15.829882812500102], + [105.40625, 15.987451171875023], + [105.33066406250003, 16.037890625000045], + [105.1487304687501, 16.09355468749999], + [105.04716796874999, 16.16025390625009], + [104.81933593749994, 16.466064453125057], + [104.75058593750015, 16.647558593750034], + [104.74355468750014, 16.884375], + [104.75898437500013, 17.0771484375], + [104.81601562499998, 17.30029296875], + [104.73964843750008, 17.461669921875], + [104.428125, 17.698974609375057], + [104.32265625000002, 17.815820312500023], + [104.19619140625002, 17.988378906250034], + [104.04873046875002, 18.216699218749966], + [103.94960937500008, 18.318994140625023], + [103.89882812500002, 18.295312500000023], + [103.79228515624999, 18.31650390625009], + [103.62968750000005, 18.382568359375057], + [103.48798828124995, 18.41816406250001], + [103.36699218750005, 18.42333984375], + [103.28828124999995, 18.408398437499955], + [103.25175781249999, 18.373486328125125], + [103.24892578125014, 18.338964843750034], + [103.27958984374999, 18.304980468750045], + [103.26318359375, 18.278466796875136], + [103.19970703125006, 18.25947265625001], + [103.14853515625009, 18.221728515624932], + [103.09121093750014, 18.13823242187499], + [103.05136718750003, 18.02851562500001], + [102.80742187500005, 17.945556640625], + [102.71757812500005, 17.892236328125136], + [102.67519531250014, 17.851757812500068], + [102.68007812500008, 17.824121093750136], + [102.66064453125, 17.8179687499999], + [102.61679687500015, 17.833349609375034], + [102.59824218750009, 17.926757812500057], + [102.55253906249999, 17.965087890625057], + [102.4587890625001, 17.984619140624943], + [102.35185546874999, 18.045947265625045], + [102.14824218750005, 18.203857421875057], + [102.10146484375014, 18.21064453125001], + [102.03457031250002, 18.169824218750023], + [101.94746093750001, 18.081494140624955], + [101.87548828125011, 18.046435546874932], + [101.81865234375005, 18.064648437500125], + [101.77480468750002, 18.033398437500125], + [101.6875, 17.889404296875114], + [101.56367187500001, 17.820507812500125], + [101.55507812500002, 17.812353515625034], + [101.41367187500015, 17.71875], + [101.16748046875011, 17.4990234375], + [101.10517578125001, 17.479541015625102], + [100.9084960937501, 17.583886718750023], + [101.14394531250008, 18.14262695312499], + [101.1375, 18.286865234375057], + [101.0505859375001, 18.407031250000045], + [101.04697265625003, 18.441992187500034], + [101.28632812499995, 18.977148437500034], + [101.19755859374999, 19.327929687500045], + [101.22080078125015, 19.486621093750045], + [101.21191406250011, 19.548339843750057], + [100.51357421875008, 19.553466796875], + [100.39765625000013, 19.756103515625057], + [100.51953125000006, 20.177929687500068], + [100.31796875000003, 20.385888671875136], + [100.2180664062501, 20.339599609375114], + [100.13974609375015, 20.245410156250102], + [100.11494140625007, 20.25766601562492], + [100.12246093750002, 20.316650390625057], + [100.12968750000005, 20.372216796875023], + [100.1838867187501, 20.589111328124943], + [100.2493164062501, 20.730273437499932], + [100.32607421875008, 20.795703124999932], + [100.40742187499995, 20.823242187500057], + [100.56513671875013, 20.82509765625008], + [100.62294921875002, 20.85957031250001], + [100.61767578125, 20.87924804687509], + [100.54931640625011, 20.884228515625068], + [100.5222656250001, 20.921923828125102], + [100.53613281250006, 20.992382812500068], + [100.703125, 21.25136718750008], + [101.0803710937501, 21.46865234375008], + [101.13886718750013, 21.567480468749977], + [101.19667968750002, 21.522070312499977], + [101.17539062500009, 21.407519531250102], + [101.21992187500013, 21.342431640625136], + [101.21181640625008, 21.278222656250023], + [101.22441406249999, 21.22373046874992], + [101.24785156249993, 21.197314453125045], + [101.28144531250007, 21.184130859375045], + [101.44355468750001, 21.230810546874977], + [101.54238281250008, 21.234277343750136], + [101.70478515625013, 21.150146484375057], + [101.728125, 21.15639648437508], + [101.78349609374999, 21.204150390625045], + [101.8005859375001, 21.212597656249955], + [101.7229492187501, 21.314941406250057], + [101.74726562500007, 21.60576171874999], + [101.7439453125001, 21.77797851562508], + [101.73652343750001, 21.826513671874977], + [101.52451171874998, 22.253662109375], + [101.56787109375011, 22.2763671875], + [101.6199218750001, 22.327441406250102], + [101.67148437500009, 22.462304687500023], + [101.70751953125, 22.486572265625], + [101.73876953125011, 22.495263671874966], + [101.75996093750001, 22.490332031250034], + [101.841796875, 22.388476562500102], + [102.02441406250006, 22.439208984375114], + [102.09150390625007, 22.412255859375136], + [102.12744140625011, 22.37919921874999] + ] + ] + }, + "properties": { "name": "Lao PDR", "childNum": 1 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [35.869140625, 33.43173828125], + [35.411230468750006, 33.07568359375], + [35.10859375000001, 33.08369140625], + [35.64785156250002, 34.2482421875], + [35.97626953125001, 34.629199218749996], + [36.383886718750006, 34.65791015625], + [36.32988281250002, 34.499609375], + [36.50439453125, 34.432373046875], + [36.5849609375, 34.221240234374996], + [36.27783203125, 33.92529296875], + [36.36503906250002, 33.83935546875], + [35.98613281250002, 33.75263671875], + [36.03447265625002, 33.58505859375], + [35.869140625, 33.43173828125] + ] + ] + }, + "properties": { "name": "Lebanon", "childNum": 1 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [-8.486425781249977, 7.558496093749994], + [-8.408740234374989, 7.411816406249997], + [-8.324511718749989, 6.920019531249991], + [-8.587890625, 6.490527343749989], + [-8.287109375, 6.319042968749997], + [-7.981591796874994, 6.2861328125], + [-7.888623046874983, 6.23486328125], + [-7.800927734374994, 6.038916015624991], + [-7.730371093749994, 5.919042968749991], + [-7.636132812499994, 5.90771484375], + [-7.454394531249989, 5.84130859375], + [-7.39990234375, 5.550585937499989], + [-7.585058593749977, 4.916748046875], + [-7.574658203124983, 4.572314453124989], + [-7.544970703124989, 4.351318359375], + [-8.259033203125, 4.589990234374994], + [-9.132177734374977, 5.054638671874997], + [-10.2763671875, 6.07763671875], + [-11.291601562499977, 6.688232421875], + [-11.507519531249983, 6.906542968749989], + [-11.267675781249977, 7.232617187499997], + [-10.878076171874994, 7.538232421874994], + [-10.6474609375, 7.759375], + [-10.570849609374989, 8.071142578124991], + [-10.516748046874994, 8.125292968749989], + [-10.359814453124983, 8.187939453124997], + [-10.283203125, 8.485156249999989], + [-10.233056640624994, 8.488818359374989], + [-10.147412109374983, 8.519726562499997], + [-10.064355468749994, 8.429882812499997], + [-9.781982421875, 8.537695312499991], + [-9.518261718749983, 8.34609375], + [-9.369140625, 7.703808593749997], + [-9.463818359374983, 7.415869140624991], + [-9.11757812499999, 7.215917968749991], + [-8.8896484375, 7.2626953125], + [-8.659765624999977, 7.688378906249994], + [-8.486425781249977, 7.558496093749994] + ] + ] + }, + "properties": { "name": "Liberia", "childNum": 1 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [25.150488281250006, 31.654980468749997], + [24.85273437500001, 31.334814453125], + [24.96142578125, 30.678515625], + [24.703222656250006, 30.201074218749994], + [24.980273437500017, 29.181884765625], + [24.980273437500017, 25.5888671875], + [24.980273437500017, 21.995849609375], + [24.9794921875, 20.002587890624994], + [23.980273437500017, 19.99594726562499], + [23.980273437500017, 19.496630859375003], + [20.14765625000001, 21.38925781249999], + [15.984082031250011, 23.445214843749994], + [14.97900390625, 22.99619140624999], + [14.215527343750011, 22.619677734375003], + [13.48125, 23.18017578125], + [11.967871093750006, 23.517871093750003], + [11.507617187500017, 24.314355468749994], + [10.686132812500006, 24.55136718749999], + [10.395898437500023, 24.485595703125], + [10.255859375, 24.591015625], + [10.000683593750011, 25.332080078125003], + [9.4482421875, 26.067138671875], + [9.491406250000011, 26.333740234375], + [9.883203125000023, 26.630810546874997], + [9.74755859375, 27.330859375], + [9.916015625, 27.785693359374996], + [9.805273437500006, 29.176953125], + [9.310253906250011, 30.115234375], + [9.51875, 30.229394531249994], + [9.89501953125, 30.3873046875], + [9.932519531250023, 30.425341796874996], + [10.059765625000011, 30.580078125], + [10.21640625, 30.783203125], + [10.114941406250011, 31.463769531249994], + [10.274609375000011, 31.684960937499994], + [10.475781250000011, 31.736035156249997], + [10.60888671875, 31.929541015625], + [10.826367187500011, 32.0806640625], + [11.005175781250017, 32.172705078125], + [11.168261718750017, 32.256738281249994], + [11.358007812500006, 32.34521484375], + [11.504980468750006, 32.413671875], + [11.535937500000017, 32.47333984375], + [11.533789062500006, 32.524951171874996], + [11.453906250000017, 32.642578125], + [11.453906250000017, 32.781689453125], + [11.459179687500011, 32.897363281249994], + [11.467187500000023, 32.965722656249994], + [11.504589843750011, 33.181933593749996], + [11.657128906250023, 33.118896484375], + [11.8134765625, 33.093701171875], + [12.279882812500006, 32.858544921874994], + [12.753515625, 32.801074218749996], + [13.283496093750017, 32.9146484375], + [15.176562500000017, 32.391162109374996], + [15.705957031250023, 31.426416015624994], + [17.830468750000023, 30.927587890625], + [18.669824218750023, 30.415673828124994], + [19.12373046875001, 30.26611328125], + [19.713281250000023, 30.48837890625], + [20.11152343750001, 30.963720703125], + [19.926367187500006, 31.817529296874994], + [20.121484375000023, 32.21875], + [20.62109375, 32.58017578125], + [21.63593750000001, 32.937304687499996], + [22.187402343750023, 32.918261718749996], + [23.090625, 32.61875], + [23.10625, 32.331445312499994], + [23.28632812500001, 32.213818359375], + [24.129687500000017, 32.009228515625], + [24.878515625, 31.984277343749994], + [25.150488281250006, 31.654980468749997] + ] + ] + }, + "properties": { "name": "Libya", "childNum": 1 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [-60.89521484375, 13.821972656249997], + [-60.951416015625, 13.717578125], + [-61.073144531249994, 13.865576171874991], + [-60.908105468749994, 14.09335937499999], + [-60.89521484375, 13.821972656249997] + ] + ] + }, + "properties": { "name": "Saint Lucia", "childNum": 1 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [79.87480468750002, 9.050732421875026], + [79.90371093750005, 8.975], + [79.74765625000006, 9.104589843749991], + [79.87480468750002, 9.050732421875026] + ] + ], + [ + [ + [79.98232421875, 9.812695312500011], + [80.25283203125005, 9.796337890625054], + [80.71113281250004, 9.366357421875023], + [81.226953125, 8.50551757812498], + [81.37285156250002, 8.431445312499989], + [81.42216796875007, 8.147851562500023], + [81.87412109375012, 7.288330078124986], + [81.86142578125012, 6.901269531249994], + [81.63740234375004, 6.425146484374991], + [80.72412109375003, 5.97905273437496], + [80.26738281250007, 6.009765625], + [80.09531250000012, 6.153173828125006], + [79.859375, 6.829296874999983], + [79.71298828125012, 8.18232421875004], + [79.74980468750007, 8.294238281250003], + [79.78349609375007, 8.018457031250051], + [79.92890625000004, 8.899218749999974], + [80.09960937499997, 9.209960937500043], + [80.08632812500005, 9.577832031250026], + [80.42832031250006, 9.480957031250014], + [80.04580078125005, 9.649902343749972], + [79.98232421875, 9.812695312500011] + ] + ] + ] + }, + "properties": { "name": "Sri Lanka", "childNum": 2 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [28.646875, -30.1265625], + [28.39208984375, -30.147558593750006], + [28.128710937500017, -30.52509765625001], + [28.05683593750001, -30.63105468750001], + [27.753125, -30.6], + [27.364062500000017, -30.27919921875001], + [27.19355468750001, -29.94130859375001], + [27.056933593750017, -29.625585937500006], + [27.29453125, -29.519335937500003], + [27.73554687500001, -28.940039062500006], + [27.959863281250023, -28.873339843750003], + [28.084375, -28.77998046875001], + [28.23261718750001, -28.701269531250006], + [28.471875, -28.615820312500006], + [28.583398437500023, -28.594140625], + [28.625781250000017, -28.58173828125001], + [29.301367187500006, -29.08984375], + [29.38671875, -29.31972656250001], + [29.34882812500001, -29.441992187500006], + [29.293554687500006, -29.56689453125], + [29.1421875, -29.700976562500003], + [29.098046875000023, -29.919042968750006], + [28.646875, -30.1265625] + ] + ] + }, + "properties": { "name": "Lesotho", "childNum": 1 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [20.957812500000074, 55.27890625000006], + [20.89980468750008, 55.286669921875045], + [21.11484375, 55.61650390624999], + [20.957812500000074, 55.27890625000006] + ] + ], + [ + [ + [25.573046875000017, 54.139892578125], + [25.497363281250045, 54.17524414062501], + [25.52734375000003, 54.21513671874996], + [25.505664062500045, 54.26494140624999], + [25.46113281250004, 54.29277343749996], + [25.179492187500017, 54.214257812499966], + [25.111425781250006, 54.15493164062505], + [25.04609375000004, 54.13305664062503], + [24.869531250000023, 54.14516601562502], + [24.82568359374997, 54.118994140625006], + [24.78925781250001, 53.99824218750001], + [24.768164062499977, 53.97465820312499], + [24.31796875, 53.892968749999966], + [24.236621093750045, 53.91997070312496], + [24.19130859375005, 53.95043945312503], + [23.559082031250057, 53.91982421875002], + [23.484667968750074, 53.939794921875006], + [23.453613281250057, 54.14345703125002], + [23.3701171875, 54.20048828124999], + [23.282324218750063, 54.240332031250034], + [23.17031250000008, 54.28144531249998], + [23.0875, 54.299462890624994], + [23.042187500000068, 54.30419921875], + [23.01552734375005, 54.34833984375001], + [22.976757812500068, 54.36635742187505], + [22.89394531250008, 54.390527343749994], + [22.82373046874997, 54.39580078124999], + [22.766210937499977, 54.356787109375034], + [22.679882812500068, 54.493017578125006], + [22.684472656250023, 54.56293945312504], + [22.82470703125, 54.87128906249998], + [22.56728515625005, 55.05913085937496], + [22.072363281250034, 55.06367187499998], + [21.235742187500023, 55.26411132812498], + [21.237890625000034, 55.455029296874955], + [21.06191406250005, 55.81342773437498], + [21.053808593750006, 56.02294921875003], + [21.04609375000004, 56.07006835937503], + [21.31464843750004, 56.18813476562502], + [21.65351562500004, 56.314550781250006], + [22.084570312500034, 56.40673828125006], + [22.875585937500063, 56.39643554687501], + [22.96826171875003, 56.38041992187502], + [23.042968750000057, 56.324072265625006], + [23.119824218749983, 56.330664062500006], + [23.195898437500034, 56.36713867187498], + [24.120703125000063, 56.26425781249998], + [24.90302734375001, 56.398193359375], + [25.069921875, 56.20039062500004], + [25.663183593750063, 56.104833984375006], + [26.593554687500074, 55.66752929687502], + [26.590820312500057, 55.62265625], + [26.56660156250001, 55.546484375000034], + [26.51923828125004, 55.448144531249994], + [26.469531250000045, 55.371923828125006], + [26.457617187500006, 55.342480468749955], + [26.49531250000004, 55.31801757812502], + [26.68125, 55.30644531249999], + [26.76015625000008, 55.29335937499999], + [26.775683593750045, 55.27309570312502], + [26.601171875000034, 55.130175781250045], + [26.291796875000074, 55.13959960937501], + [26.250781250000045, 55.12451171875006], + [26.175195312500023, 55.003271484375034], + [26.092968750000068, 54.96230468750005], + [25.964453124999977, 54.947167968749966], + [25.85927734375005, 54.91928710937498], + [25.722460937500074, 54.71787109374998], + [25.731640625000068, 54.59038085937502], + [25.72480468750001, 54.564257812500045], + [25.68515625, 54.53579101562502], + [25.62031250000004, 54.46040039062501], + [25.56757812500004, 54.377050781250006], + [25.54736328125, 54.33183593750002], + [25.55751953125005, 54.310693359374994], + [25.702539062499994, 54.29296875], + [25.765234374999977, 54.179785156250034], + [25.573046875000017, 54.139892578125] + ] + ] + ] + }, + "properties": { "name": "Lithuania", "childNum": 2 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [6.4873046875, 49.798486328124994], + [6.344335937500006, 49.452734375], + [6.181054687500023, 49.498925781249994], + [6.119921875000017, 49.485205078125], + [6.074121093750023, 49.454638671874996], + [6.011425781250011, 49.445458984374994], + [5.95947265625, 49.454638671874996], + [5.928906250000011, 49.4775390625], + [5.9013671875, 49.48974609375], + [5.823437500000011, 49.505078125], + [5.789746093750011, 49.53828125], + [5.776710379464286, 49.639953962053575], + [5.744042968750023, 49.91962890625], + [5.7880859375, 49.961230468749996], + [5.8173828125, 50.0126953125], + [5.866894531250011, 50.0828125], + [5.976269531250011, 50.1671875], + [6.089062500000011, 50.154589843749996], + [6.110058593750011, 50.123779296875], + [6.116503906250017, 50.120996093749994], + [6.109765625000023, 50.034375], + [6.13818359375, 49.97431640625], + [6.204882812500017, 49.91513671875], + [6.272327008928583, 49.887234933035714], + [6.4873046875, 49.798486328124994] + ] + ] + }, + "properties": { "name": "Luxembourg", "childNum": 1 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [28.14794921875, 56.142919921875], + [27.576757812500006, 55.798779296875], + [27.052539062500017, 55.83056640625], + [26.593554687500017, 55.667529296874996], + [25.663183593750006, 56.104833984375], + [25.069921875, 56.200390625], + [24.90302734375001, 56.398193359375], + [24.120703125, 56.2642578125], + [23.81269531250001, 56.329248046875], + [23.195898437500006, 56.367138671875], + [23.11982421875001, 56.3306640625], + [23.04296875, 56.324072265625], + [22.875585937500006, 56.396435546875], + [22.084570312500006, 56.40673828125], + [21.730566406250006, 56.325976562499996], + [21.65351562500001, 56.31455078125], + [21.31464843750001, 56.188134765625], + [21.04609375000001, 56.070068359375], + [21.0712890625, 56.82373046875], + [21.72871093750001, 57.57099609375], + [22.554589843750023, 57.724267578125], + [23.28730468750001, 57.08974609375], + [23.647753906250017, 56.971044921875], + [24.382617187500017, 57.250048828124996], + [24.322558593750017, 57.87060546875], + [24.3625, 57.866162109375], + [24.458886718750023, 57.907861328125], + [25.11103515625001, 58.063427734375], + [25.27265625000001, 58.009375], + [25.66015625, 57.920166015625], + [26.29804687500001, 57.60107421875], + [26.532617187500023, 57.531005859375], + [26.96601562500001, 57.609130859375], + [27.187109375, 57.538330078125], + [27.326562500000023, 57.52548828125], + [27.4697265625, 57.5240234375], + [27.538671875, 57.42978515625], + [27.796875, 57.316943359374996], + [27.82861328125, 57.293310546875], + [27.838281250000023, 57.247705078125], + [27.83027343750001, 57.194482421875], + [27.639453125000017, 56.845654296875], + [27.806054687500023, 56.86708984375], + [27.8486328125, 56.85341796875], + [27.89208984375, 56.741064453125], + [28.00751953125001, 56.599853515625], + [28.103125, 56.545703125], + [28.11083984375, 56.510693359375], + [28.169238281250017, 56.386865234375], + [28.191699218750017, 56.315576171875], + [28.202050781250023, 56.260400390625], + [28.14794921875, 56.142919921875] + ] + ] + }, + "properties": { "name": "Latvia", "childNum": 1 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [28.2125, 45.450439453125], + [28.07470703125, 45.598974609375], + [28.23945312500001, 46.6408203125], + [28.07177734375, 46.978417968749994], + [27.614062500000017, 47.34052734375], + [26.980761718750017, 48.155029296875], + [26.618945312500017, 48.25986328125], + [26.640429687500017, 48.294140625], + [26.847070312500023, 48.387158203125], + [26.90058593750001, 48.371923828125], + [27.228515625, 48.371435546875], + [27.549218750000023, 48.477734375], + [28.34052734375001, 48.144433593749994], + [28.42304687500001, 48.146875], + [29.125390625000023, 47.96455078125], + [29.134863281250006, 47.489697265625], + [29.455664062500006, 47.292626953124994], + [29.57197265625001, 46.964013671874994], + [29.7197265625, 46.88291015625], + [29.877832031250023, 46.82890625], + [29.942480468750006, 46.723779296874994], + [29.93476562500001, 46.625], + [29.92431640625, 46.538867187499996], + [30.13105468750001, 46.423095703125], + [30.07568359375, 46.377832031249994], + [29.878027343750006, 46.360205078125], + [29.837890625, 46.350537109375], + [29.458789062500017, 46.453759765624994], + [29.30488281250001, 46.466601562499996], + [29.22382812500001, 46.376953125], + [29.20458984375, 46.379345703125], + [29.20078125, 46.50498046875], + [29.18623046875001, 46.523974609374996], + [29.146289062500017, 46.526904296874996], + [28.958398437500023, 46.45849609375], + [28.92744140625001, 46.424121093749996], + [28.930566406250023, 46.362255859375], + [28.94375, 46.288427734375], + [29.00625, 46.17646484375], + [28.971875, 46.12763671875], + [28.94775390625, 46.049951171874994], + [28.849511718750023, 45.978662109374994], + [28.73876953125, 45.937158203124994], + [28.729296875000017, 45.852001953125], + [28.667578125, 45.793847656249994], + [28.562304687500017, 45.735791015625], + [28.491601562500023, 45.665771484375], + [28.4990234375, 45.517724609374994], + [28.310351562500017, 45.498583984374996], + [28.26484375000001, 45.48388671875], + [28.2125, 45.450439453125] + ] + ] + }, + "properties": { "name": "Moldova", "childNum": 1 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [49.936425781249994, -16.90292968750002], + [49.82402343750002, -17.08652343750002], + [50.02304687500006, -16.6953125], + [49.936425781249994, -16.90292968750002] + ] + ], + [ + [ + [48.3421875, -13.363867187500034], + [48.21191406250003, -13.385253906249957], + [48.191210937500074, -13.259960937500011], + [48.308886718750074, -13.198242187499957], + [48.3421875, -13.363867187500034] + ] + ], + [ + [ + [49.53828125000004, -12.432128906250014], + [49.9375, -13.072265624999957], + [50.23535156249997, -14.732031249999963], + [50.482714843750074, -15.385644531249994], + [50.20898437499997, -15.960449218750028], + [50.02041015625005, -15.801757812500028], + [49.89257812500003, -15.457714843750011], + [49.664355468750074, -15.521582031249977], + [49.83906250000004, -16.486523437499997], + [49.76718750000006, -16.815136718749983], + [49.44931640625006, -17.240625], + [49.477832031250074, -17.89853515624999], + [49.362890625, -18.336328125], + [47.934472656249994, -22.393945312500023], + [47.55800781250005, -23.874609374999963], + [47.17734375, -24.787207031249977], + [46.72851562499997, -25.14990234374997], + [46.15869140624997, -25.230371093750023], + [45.5080078125001, -25.56318359374997], + [45.2057617187501, -25.57050781250004], + [44.0353515625001, -24.995703125], + [43.670019531250006, -24.30029296875], + [43.722265625, -23.529687500000037], + [43.2648437500001, -22.38359375], + [43.29052734374997, -21.93251953124998], + [43.50185546875005, -21.356445312499957], + [43.800195312499994, -21.179199218749986], + [44.40468750000005, -19.922070312500026], + [44.44882812500006, -19.42871093749997], + [44.23876953124997, -19.075195312499986], + [44.23310546875004, -18.740625], + [44.04003906249997, -18.288476562500023], + [43.979394531249994, -17.3916015625], + [44.42138671874997, -16.70263671874997], + [44.476171875, -16.217285156249957], + [44.90917968749997, -16.174511718750026], + [45.2228515625001, -15.95048828124996], + [45.3421875, -16.03671875000002], + [45.598242187500006, -15.992578125], + [45.70019531249997, -15.813769531249989], + [46.157519531250074, -15.738281249999972], + [46.3996093750001, -15.924609375000017], + [46.331445312499994, -15.713671875000031], + [46.47509765625003, -15.513476562500003], + [46.942285156249994, -15.219042968749974], + [47.09921875, -15.43417968750002], + [47.092578125000074, -15.150097656249969], + [47.35195312500005, -14.766113281249986], + [47.46474609375005, -14.713281249999966], + [47.47832031250002, -15.009375], + [47.77402343750006, -14.63671875], + [47.964160156250074, -14.672558593750026], + [47.773339843749994, -14.369921875], + [47.995507812499994, -13.960449218749986], + [47.88359375000002, -13.807519531250009], + [47.94101562500006, -13.662402343750017], + [48.03984375000002, -13.596289062499963], + [48.25527343750005, -13.719335937499977], + [48.796484375, -13.267480468750023], + [48.91943359375003, -12.839062499999969], + [48.78632812500004, -12.470898437500011], + [48.931738281250006, -12.4390625], + [49.20703124999997, -12.079589843749957], + [49.53828125000004, -12.432128906250014] + ] + ] + ] + }, + "properties": { "name": "Madagascar", "childNum": 3 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [-91.68369140624998, 18.677343750000034], + [-91.81611328124995, 18.675878906250006], + [-91.53671874999998, 18.760009765625], + [-91.68369140624998, 18.677343750000034] + ] + ], + [ + [ + [-86.93964843750001, 20.303320312500006], + [-86.97797851562498, 20.489794921875074], + [-86.76328124999995, 20.579052734374955], + [-86.93964843750001, 20.303320312500006] + ] + ], + [ + [ + [-106.50224609374999, 21.61083984375003], + [-106.60703124999993, 21.561474609374983], + [-106.63935546874995, 21.697851562499977], + [-106.50224609374999, 21.61083984375003] + ] + ], + [ + [ + [-110.56738281249994, 25.003466796875017], + [-110.5388671875, 24.89155273437504], + [-110.69926757812499, 25.081445312499994], + [-110.56738281249994, 25.003466796875017] + ] + ], + [ + [ + [-112.05727539062498, 24.545703125000017], + [-112.29677734375002, 24.789648437500063], + [-112.15942382812501, 25.28564453125003], + [-112.19501953124998, 24.841064453125057], + [-112.05727539062498, 24.545703125000017] + ] + ], + [ + [ + [-111.10029296874998, 26.020605468750006], + [-111.224658203125, 25.83588867187504], + [-111.18291015625002, 26.040625], + [-111.10029296874998, 26.020605468750006] + ] + ], + [ + [ + [-115.17060546875001, 28.06938476562496], + [-115.35292968750002, 28.103955078124983], + [-115.23354492187495, 28.36835937500004], + [-115.17060546875001, 28.06938476562496] + ] + ], + [ + [ + [-112.20307617187503, 29.00532226562504], + [-112.27841796875, 28.769335937500017], + [-112.51406249999997, 28.847607421874955], + [-112.42353515625, 29.203662109375017], + [-112.28505859374994, 29.240429687499955], + [-112.20307617187503, 29.00532226562504] + ] + ], + [ + [ + [-113.15561523437502, 29.05224609375], + [-113.49633789062497, 29.30761718749997], + [-113.58720703125002, 29.57304687499996], + [-113.20214843749999, 29.301855468750034], + [-113.15561523437502, 29.05224609375] + ] + ], + [ + [ + [-97.14624023437494, 25.961474609375045], + [-97.66767578124995, 24.389990234374977], + [-97.84248046874995, 22.510302734375017], + [-97.76328124999998, 22.105859374999966], + [-97.31450195312496, 21.56420898437503], + [-97.40917968749997, 21.272558593750034], + [-97.38344726562497, 21.56669921874999], + [-97.75380859375002, 22.02666015624999], + [-97.18632812499996, 20.717041015625], + [-96.45605468749994, 19.869775390624966], + [-96.28955078124994, 19.34375], + [-95.778125, 18.805517578125034], + [-95.92036132812495, 18.81958007812497], + [-95.62680664062503, 18.690576171874994], + [-95.71982421874998, 18.768359375000017], + [-95.18183593749995, 18.700732421875017], + [-94.79814453124996, 18.51459960937501], + [-94.45976562499993, 18.166650390624994], + [-93.55234375, 18.430468750000017], + [-92.88476562499997, 18.468652343749966], + [-92.44101562499998, 18.67529296874997], + [-91.97377929687502, 18.715869140625074], + [-91.91357421875, 18.52851562500001], + [-91.53398437499993, 18.45654296875], + [-91.27524414062498, 18.62446289062501], + [-91.34306640624996, 18.900585937499955], + [-91.43666992187502, 18.889794921874966], + [-90.73925781249994, 19.352246093749955], + [-90.69316406249996, 19.729882812499966], + [-90.49169921874997, 19.94677734375003], + [-90.353125, 21.009423828124966], + [-89.81977539062495, 21.274609374999983], + [-88.46669921874997, 21.569384765625017], + [-88.0068359375, 21.604052734375045], + [-87.25087890625, 21.44697265625004], + [-87.18828124999993, 21.546435546875045], + [-87.36850585937498, 21.57373046875], + [-87.034765625, 21.592236328124955], + [-86.824072265625, 21.421679687500017], + [-86.77177734374999, 21.150537109375023], + [-86.92622070312493, 20.786474609375034], + [-87.42138671875, 20.23139648437501], + [-87.44174804687498, 19.861523437499983], + [-87.68769531249998, 19.63710937499999], + [-87.6453125, 19.55390625000001], + [-87.42475585937498, 19.583349609375063], + [-87.65869140625003, 19.352343750000074], + [-87.65576171874997, 19.25786132812499], + [-87.50107421874998, 19.287792968749983], + [-87.76181640624998, 18.446142578125006], + [-87.88198242187497, 18.27387695312501], + [-88.05644531249996, 18.524462890625074], + [-88.03173828125, 18.838916015625017], + [-88.29565429687494, 18.47241210937503], + [-88.52299804687499, 18.445898437500063], + [-88.80634765624998, 17.965527343749983], + [-89.13354492187503, 17.970800781249977], + [-89.16147460937503, 17.81484375], + [-90.98916015624997, 17.81640624999997], + [-90.99296874999993, 17.25244140625], + [-91.19550781249998, 17.254101562499983], + [-91.40961914062501, 17.255859375], + [-90.975830078125, 16.867822265624994], + [-90.710693359375, 16.708105468750034], + [-90.65996093749996, 16.630908203125045], + [-90.634375, 16.565136718749955], + [-90.63408203125002, 16.51074218749997], + [-90.57578124999995, 16.467822265625017], + [-90.47109374999994, 16.439550781250034], + [-90.41699218750003, 16.391015625000023], + [-90.41699218750003, 16.351318359375], + [-90.45014648437493, 16.261376953124994], + [-90.45986328124997, 16.16235351562497], + [-90.44716796874994, 16.07270507812501], + [-90.52197265625, 16.07119140625005], + [-90.70322265624998, 16.07104492187503], + [-90.97958984374998, 16.07080078124997], + [-91.433984375, 16.070458984374994], + [-91.736572265625, 16.070166015625006], + [-91.95722656250001, 15.703222656250034], + [-92.08212890624998, 15.495556640625011], + [-92.18715820312497, 15.320898437499963], + [-92.07480468749998, 15.074218749999972], + [-92.09873046874998, 15.026757812499994], + [-92.14423828125001, 15.001953125], + [-92.158544921875, 14.963574218749997], + [-92.23515625, 14.545410156249986], + [-93.91606445312493, 16.053564453125006], + [-94.374169921875, 16.284765625000034], + [-94.426416015625, 16.22626953125001], + [-94.00126953124996, 16.018945312499966], + [-94.66152343750002, 16.20190429687503], + [-94.58710937499995, 16.315820312499966], + [-94.79082031249999, 16.28715820312499], + [-94.85869140624996, 16.41972656249999], + [-95.02084960937503, 16.277636718750017], + [-94.79941406249995, 16.20966796875001], + [-95.134375, 16.17695312500001], + [-96.21357421874993, 15.693066406250011], + [-96.80795898437495, 15.726416015624977], + [-97.18466796874998, 15.909277343750006], + [-97.75478515624994, 15.966845703125017], + [-98.52031249999993, 16.30483398437505], + [-98.76220703125, 16.534765624999977], + [-99.69067382812499, 16.719628906249994], + [-100.847802734375, 17.20048828124999], + [-101.91870117187494, 17.959765625000045], + [-102.69956054687495, 18.062841796875006], + [-103.44160156249995, 18.32539062500001], + [-103.91245117187496, 18.828466796875006], + [-104.9384765625, 19.309375], + [-105.482080078125, 19.97607421875003], + [-105.66943359374997, 20.385595703124977], + [-105.26015625, 20.579052734374955], + [-105.32705078124994, 20.752978515625045], + [-105.51083984374999, 20.808740234375023], + [-105.23706054687499, 21.119189453125045], + [-105.20869140624998, 21.490820312499977], + [-105.43144531249997, 21.618261718750006], + [-105.64912109375001, 21.988085937500045], + [-105.64550781249999, 22.32690429687497], + [-105.79179687500003, 22.627490234375017], + [-106.93549804687497, 23.88125], + [-107.76494140625002, 24.47192382812497], + [-107.52724609375001, 24.36005859375001], + [-107.51191406249998, 24.489160156250023], + [-107.95117187499994, 24.614892578124966], + [-108.28076171874994, 25.08154296875], + [-108.05146484374995, 25.067041015624994], + [-108.69638671874998, 25.382910156250034], + [-108.78725585937502, 25.53803710937501], + [-109.02880859375003, 25.48046875000003], + [-108.886572265625, 25.733447265625045], + [-109.19648437499998, 25.59252929687503], + [-109.38496093750001, 25.727148437500006], + [-109.42563476562495, 26.032568359375063], + [-109.19970703125003, 26.30522460937499], + [-109.11669921874999, 26.25273437499996], + [-109.27626953125, 26.533886718749955], + [-109.48286132812498, 26.710351562500023], + [-109.75478515624995, 26.702929687500017], + [-109.94399414062495, 27.079345703125057], + [-110.37729492187495, 27.233300781249966], + [-110.59267578124995, 27.544335937500023], + [-110.52988281249995, 27.864208984374983], + [-111.12138671875002, 27.966992187499983], + [-112.16176757812495, 29.018896484375034], + [-113.05766601562496, 30.651025390625023], + [-113.04672851562495, 31.17924804687499], + [-113.62348632812494, 31.34589843750001], + [-113.75942382812501, 31.557763671874994], + [-113.94775390625001, 31.62934570312501], + [-114.14931640624995, 31.507373046875045], + [-114.93359374999994, 31.900732421874977], + [-114.78989257812498, 31.647119140624994], + [-114.88188476562499, 31.156396484375023], + [-114.55048828124997, 30.02226562499999], + [-113.75546875, 29.367480468750017], + [-113.49970703124995, 28.92670898437501], + [-113.20556640624997, 28.798779296874955], + [-113.09365234375001, 28.511767578125017], + [-112.870849609375, 28.42421875000005], + [-112.73403320312501, 27.825976562500017], + [-112.32919921874996, 27.52343750000003], + [-111.86264648437495, 26.678515625000017], + [-111.6994140625, 26.58095703125005], + [-111.79526367187499, 26.8796875], + [-111.56967773437495, 26.707617187500006], + [-111.29160156249996, 25.78979492187497], + [-110.68676757812501, 24.867675781250057], + [-110.65932617187502, 24.34145507812505], + [-110.36743164062497, 24.100488281249994], + [-110.30375976562497, 24.339453125], + [-110.02280273437502, 24.17460937499999], + [-109.6765625, 23.66157226562501], + [-109.42084960937495, 23.480126953124994], + [-109.49570312500002, 23.159814453125023], + [-110.00625, 22.894042968750057], + [-110.3626953125, 23.60493164062501], + [-111.68291015625002, 24.555810546875023], + [-111.80249023437494, 24.542529296875074], + [-112.07255859374999, 24.84003906250001], + [-112.06987304687497, 25.572851562500006], + [-112.37724609374997, 26.21391601562496], + [-113.02075195312499, 26.58325195312497], + [-113.15581054687496, 26.94624023437504], + [-113.27226562499997, 26.79096679687501], + [-113.59853515625001, 26.721289062500034], + [-113.84096679687502, 26.966503906249983], + [-114.44526367187503, 27.218164062499994], + [-114.53989257812495, 27.431103515624955], + [-114.99350585937499, 27.736035156249983], + [-115.03647460937495, 27.84184570312496], + [-114.57001953124995, 27.78393554687497], + [-114.30058593749995, 27.87299804687501], + [-114.30224609375003, 27.775732421875006], + [-114.0693359375, 27.67568359375005], + [-114.15839843750003, 27.919677734375], + [-114.26586914062499, 27.934472656249994], + [-114.04848632812502, 28.42617187499999], + [-114.93730468749999, 29.35161132812496], + [-115.67382812500003, 29.756396484375017], + [-116.06215820312501, 30.80415039062504], + [-116.29628906250001, 30.97050781249999], + [-116.33344726562494, 31.202783203124994], + [-116.66215820312495, 31.56489257812504], + [-116.72207031249998, 31.734570312499955], + [-116.62080078124995, 31.85107421874997], + [-116.84799804687496, 31.997363281250045], + [-117.12827148437495, 32.533349609374994], + [-114.72475585937495, 32.71533203125003], + [-114.83593749999994, 32.50830078125003], + [-111.0419921875, 31.32421875000003], + [-108.21445312499993, 31.329443359375034], + [-108.21181640625002, 31.779345703125017], + [-106.44541015624996, 31.768408203125006], + [-106.14804687499995, 31.450927734375], + [-104.97880859374996, 30.645947265624955], + [-104.50400390624995, 29.677685546874955], + [-104.110595703125, 29.386132812499994], + [-103.16831054687498, 28.998193359374994], + [-102.8919921875, 29.216406250000034], + [-102.61494140624994, 29.75234375], + [-102.26894531249998, 29.871191406250034], + [-101.44038085937503, 29.77685546875], + [-100.75458984375001, 29.182519531249994], + [-100.29604492187495, 28.32768554687499], + [-99.50532226562497, 27.54833984375003], + [-99.45654296874999, 27.05668945312496], + [-99.10776367187498, 26.446923828124994], + [-97.37563476562497, 25.871826171875], + [-97.14624023437494, 25.961474609375045] + ] + ] + ] + }, + "properties": { "name": "Mexico", "childNum": 10 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [22.344042968750017, 42.31396484375], + [22.836816406250023, 41.993603515625], + [23.00361328125001, 41.73984375], + [22.916015625, 41.336279296875], + [22.78388671875001, 41.331982421875], + [22.72480468750001, 41.178515625], + [22.603613281250006, 41.140185546874996], + [22.493554687500023, 41.118505859375], + [22.184472656250023, 41.158642578125], + [21.99335937500001, 41.13095703125], + [21.77949218750001, 40.950439453125], + [21.627539062500006, 40.896337890625], + [21.57578125, 40.868945312499996], + [20.964257812500023, 40.849902343749996], + [20.709277343750017, 40.928369140624994], + [20.48896484375001, 41.272607421874994], + [20.566210937500017, 41.873681640624994], + [20.725, 41.87353515625], + [20.778125, 42.071044921875], + [21.05976562500001, 42.171289062499994], + [21.28662109375, 42.100390625], + [21.389550781250023, 42.21982421875], + [21.560839843750017, 42.24765625], + [21.5625, 42.247509765625], + [21.81464843750001, 42.303125], + [22.344042968750017, 42.31396484375] + ] + ] + }, + "properties": { "name": "Macedonia", "childNum": 1 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [4.227636718750006, 19.142773437499997], + [4.234667968750017, 16.996386718750003], + [4.121289062500011, 16.357714843750003], + [3.842968750000011, 15.701708984375003], + [3.5205078125, 15.483105468749997], + [3.504296875000023, 15.356347656249994], + [3.06015625, 15.427197265624997], + [3.001074218750006, 15.340966796874994], + [1.300195312500023, 15.272265625], + [0.947460937500011, 14.982128906249997], + [0.217480468750011, 14.911474609374991], + [-0.235888671874989, 15.059423828124991], + [-0.760449218749983, 15.047753906249994], + [-1.049560546875, 14.81953125], + [-1.97304687499999, 14.45654296875], + [-2.113232421874983, 14.16845703125], + [-2.586718749999989, 14.227587890625003], + [-2.873925781249994, 13.950732421875003], + [-2.950830078124994, 13.6484375], + [-3.248632812499977, 13.658349609374994], + [-3.3017578125, 13.28076171875], + [-3.527636718749989, 13.182714843749991], + [-3.947314453124989, 13.402197265624991], + [-4.151025390624994, 13.306201171875003], + [-4.328710937499977, 13.119042968749994], + [-4.227099609374989, 12.793701171875], + [-4.480615234374994, 12.672216796874991], + [-4.4287109375, 12.337597656249997], + [-4.699316406249977, 12.076171875], + [-5.288134765624989, 11.827929687499989], + [-5.250244140625, 11.375781249999989], + [-5.490478515625, 11.042382812499994], + [-5.523535156249977, 10.426025390625], + [-5.556591796874983, 10.43994140625], + [-5.694287109374983, 10.43320312499999], + [-5.843847656249977, 10.389550781249994], + [-5.896191406249983, 10.354736328125], + [-5.907568359374977, 10.307226562499991], + [-6.034570312499994, 10.19482421875], + [-6.1171875, 10.201904296875], + [-6.238378906249977, 10.261621093749994], + [-6.241308593749977, 10.279199218749994], + [-6.192626953125, 10.369433593749989], + [-6.190673828125, 10.400292968749994], + [-6.250244140625, 10.717919921874994], + [-6.482617187499983, 10.561230468749997], + [-6.564599609374994, 10.58642578125], + [-6.654150390624977, 10.656445312499997], + [-6.676367187499977, 10.6337890625], + [-6.686132812499977, 10.578027343749994], + [-6.691992187499977, 10.512011718749989], + [-6.669335937499994, 10.3921875], + [-6.693261718749994, 10.349462890624991], + [-6.950341796874994, 10.342333984374989], + [-7.01708984375, 10.143261718749997], + [-7.385058593749989, 10.340136718749989], + [-7.6611328125, 10.427441406249997], + [-7.990625, 10.1625], + [-8.007275390624983, 10.321875], + [-8.266650390624989, 10.485986328124994], + [-8.33740234375, 10.990625], + [-8.666699218749983, 11.009472656249997], + [-8.398535156249977, 11.366552734374991], + [-8.822021484375, 11.673242187499994], + [-8.818310546874983, 11.922509765624994], + [-9.043066406249977, 12.40234375], + [-9.395361328124977, 12.464648437499989], + [-9.358105468749983, 12.255419921874989], + [-9.754003906249977, 12.029931640624994], + [-10.274853515624983, 12.212646484375], + [-10.709228515625, 11.898730468749989], + [-10.933203124999977, 12.205175781249991], + [-11.30517578125, 12.015429687499989], + [-11.502197265625, 12.198632812499994], + [-11.389404296875, 12.404394531249991], + [-11.390380859375, 12.941992187499991], + [-11.634960937499983, 13.369873046875], + [-11.831689453124994, 13.315820312499994], + [-12.05419921875, 13.633056640625], + [-11.960888671874983, 13.875292968750003], + [-12.019189453124994, 14.206494140624997], + [-12.228417968749994, 14.45859375], + [-12.280615234374977, 14.809033203124997], + [-12.104687499999983, 14.745361328125], + [-12.08154296875, 14.766357421875], + [-12.021582031249977, 14.804931640625], + [-11.76015625, 15.425537109375], + [-11.675878906249977, 15.512060546874991], + [-11.502685546875, 15.636816406249991], + [-11.455224609374994, 15.62539062499999], + [-10.9482421875, 15.151123046875], + [-10.696582031249989, 15.42265625], + [-9.94140625, 15.373779296875], + [-9.446923828124994, 15.458203125], + [-9.447705078124983, 15.574853515624994], + [-9.426562499999989, 15.623046875], + [-9.3505859375, 15.677392578124994], + [-9.33544921875, 15.525683593750003], + [-9.293701171875, 15.502832031249994], + [-5.5125, 15.496289062499997], + [-5.359912109374989, 16.282861328124994], + [-5.509619140624977, 16.442041015624994], + [-5.628662109375, 16.568652343750003], + [-5.65625, 16.8095703125], + [-5.684765624999983, 17.058251953124994], + [-5.713183593749989, 17.306884765625], + [-5.74169921875, 17.555566406249994], + [-5.827099609374983, 18.3015625], + [-6.026416015624989, 20.0421875], + [-6.396582031249977, 23.274804687499994], + [-6.482031249999977, 24.020800781250003], + [-6.538964843749994, 24.51816406249999], + [-6.5673828125, 24.766796875], + [-6.594091796874977, 24.99462890625], + [-6.287207031249977, 24.994824218749997], + [-5.959814453124977, 24.99497070312499], + [-5.640771484374994, 24.995166015625003], + [-4.822607421874977, 24.99560546875], + [-1.947900390624994, 23.124804687500003], + [1.1455078125, 21.102246093749997], + [1.165722656250011, 20.817431640625003], + [1.610644531250017, 20.555566406249994], + [1.685449218750023, 20.378369140624997], + [3.130273437500023, 19.85019531249999], + [3.255859375, 19.4109375], + [3.119726562500006, 19.103173828124994], + [3.3564453125, 18.986621093750003], + [4.227636718750006, 19.142773437499997] + ] + ] + }, + "properties": { "name": "Mali", "childNum": 1 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [14.566210937499989, 35.85273437499998], + [14.436425781250023, 35.82167968750005], + [14.351269531250011, 35.978417968749994], + [14.566210937499989, 35.85273437499998] + ] + ] + }, + "properties": { "name": "Malta", "childNum": 1 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [98.18261718749997, 9.933447265625006], + [98.11806640625, 9.877880859375054], + [98.2916992187501, 10.051318359375031], + [98.18261718749997, 9.933447265625006] + ] + ], + [ + [ + [98.20976562500002, 10.952734375], + [98.27148437499997, 10.73989257812498], + [98.08046875000005, 10.886621093750037], + [98.20976562500002, 10.952734375] + ] + ], + [ + [ + [98.55380859375012, 11.744873046875], + [98.52841796875012, 11.538671875], + [98.43476562500004, 11.567089843750026], + [98.37646484374997, 11.79150390625], + [98.55380859375012, 11.744873046875] + ] + ], + [ + [ + [98.516015625, 11.905029296875028], + [98.46621093750005, 12.08427734374996], + [98.60957031250004, 11.956640624999977], + [98.516015625, 11.905029296875028] + ] + ], + [ + [ + [98.06611328125004, 12.389794921875023], + [98.00234375000005, 12.279003906250011], + [97.93867187500004, 12.34609375], + [98.06611328125004, 12.389794921875023] + ] + ], + [ + [ + [98.41396484375005, 12.597949218749974], + [98.45947265625003, 12.473730468749991], + [98.3138671875, 12.335986328124989], + [98.31210937500006, 12.678173828124983], + [98.41396484375005, 12.597949218749974] + ] + ], + [ + [ + [98.31542968749997, 13.099072265625026], + [98.30917968750012, 12.934716796875023], + [98.26533203125004, 13.202246093749991], + [98.31542968749997, 13.099072265625026] + ] + ], + [ + [ + [94.80488281250004, 15.8193359375], + [94.73349609375006, 15.823046875000045], + [94.82802734375005, 15.933007812499966], + [94.80488281250004, 15.8193359375] + ] + ], + [ + [ + [94.47675781250004, 15.945947265625023], + [94.41191406250007, 15.848388671875057], + [94.3878906250001, 15.994140624999972], + [94.60126953125004, 16.205517578124983], + [94.47675781250004, 15.945947265625023] + ] + ], + [ + [ + [97.575, 16.253222656250017], + [97.48037109375, 16.305712890625045], + [97.54199218749997, 16.505078124999983], + [97.575, 16.253222656250017] + ] + ], + [ + [ + [93.6908203125, 18.68427734375004], + [93.4875, 18.867529296875063], + [93.74472656250006, 18.865527343750017], + [93.6908203125, 18.68427734375004] + ] + ], + [ + [ + [93.71484374999997, 19.558251953124994], + [93.94570312500005, 19.428613281249966], + [93.90195312500012, 19.33203125], + [93.75585937500003, 19.325683593750057], + [93.64404296874997, 19.49506835937501], + [93.71484374999997, 19.558251953124994] + ] + ], + [ + [ + [93.49179687500012, 19.892578125], + [93.51328125000006, 19.754785156249994], + [93.41289062500002, 19.950341796875023], + [93.49179687500012, 19.892578125] + ] + ], + [ + [ + [93.01015625000005, 19.923925781249977], + [93.02324218750007, 19.82885742187497], + [92.91464843750006, 20.086474609375045], + [93.01015625000005, 19.923925781249977] + ] + ], + [ + [ + [101.1388671875001, 21.567480468749977], + [101.08037109375007, 21.468652343749994], + [100.703125, 21.251367187499966], + [100.613671875, 21.059326171875], + [100.56660156250004, 21.038183593750063], + [100.53613281250003, 20.992382812499955], + [100.52226562500007, 20.92192382812499], + [100.54931640624997, 20.884228515624955], + [100.61767578125003, 20.879248046875006], + [100.62294921875005, 20.859570312499983], + [100.5651367187501, 20.825097656249994], + [100.4074218750001, 20.823242187500057], + [100.32607421875005, 20.795703125000045], + [100.24931640625002, 20.730273437500045], + [100.18388671875002, 20.589111328125057], + [100.12968750000002, 20.372216796874994], + [100.12246093750005, 20.316650390625057], + [100.0036132812501, 20.37958984375001], + [99.9542968750001, 20.415429687500023], + [99.8903320312501, 20.424414062499977], + [99.72011718750005, 20.32543945312497], + [99.45888671875005, 20.363037109375], + [99.48593750000006, 20.14985351562501], + [99.07421875000003, 20.09936523437503], + [98.9166992187501, 19.77290039062504], + [98.37128906250004, 19.68916015625004], + [98.01503906250005, 19.74951171874997], + [97.816796875, 19.459960937500057], + [97.74589843750002, 18.58818359374999], + [97.37392578125, 18.51796875], + [97.63222656250005, 18.290332031250074], + [97.7064453125, 17.79711914062503], + [98.4388671875, 16.975683593750034], + [98.66074218750006, 16.330419921875006], + [98.83544921875003, 16.417578125], + [98.88828125000006, 16.351904296875034], + [98.81796875000012, 16.180810546874994], + [98.59238281250006, 16.05068359375005], + [98.55693359375007, 15.367675781249986], + [98.19101562500012, 15.204101562499972], + [98.20214843749997, 14.97592773437502], + [98.57001953125004, 14.359912109375031], + [99.13681640625006, 13.716699218749994], + [99.12392578125, 13.030761718750043], + [99.40507812500002, 12.547900390625003], + [99.61474609374997, 11.781201171875026], + [99.1901367187501, 11.105273437499989], + [98.7572265625, 10.660937499999974], + [98.70253906250005, 10.19038085937504], + [98.56259765625006, 10.034960937499989], + [98.46494140625006, 10.675830078124989], + [98.67558593750007, 10.986914062500034], + [98.74140625000004, 11.591699218749966], + [98.87597656250003, 11.719726562500028], + [98.63632812500006, 11.738378906250006], + [98.69628906250003, 12.225244140624994], + [98.6002929687501, 12.2453125], + [98.67871093749997, 12.348486328124963], + [98.57597656250002, 13.161914062500031], + [98.20039062500004, 13.980175781250026], + [98.14951171875012, 13.647607421875037], + [98.11064453125007, 13.712890625000014], + [98.10019531250006, 14.161523437500023], + [97.90976562500012, 14.652685546874991], + [98.01875, 14.652587890625057], + [97.81230468750007, 14.858935546874989], + [97.7103515625, 15.875537109375074], + [97.58427734375007, 16.019580078125017], + [97.72597656250005, 16.56855468750004], + [97.37587890625005, 16.52294921874997], + [97.20019531249997, 17.095410156249983], + [96.85146484375005, 17.401025390624994], + [96.90859375000005, 17.03095703125001], + [96.76542968750002, 16.710351562499966], + [96.43115234374997, 16.504931640625045], + [96.18906250000012, 16.768310546875057], + [96.32431640625006, 16.444433593750063], + [95.76328125000006, 16.169042968750006], + [95.38955078125005, 15.722753906250034], + [95.30146484375004, 15.756152343749989], + [95.34677734375012, 16.09760742187501], + [95.17695312500004, 15.825683593750028], + [94.9425781250001, 15.818261718750023], + [94.89316406250006, 16.182812499999955], + [94.66152343750005, 15.904394531250006], + [94.70332031250004, 16.511914062499955], + [94.4416015625001, 16.094384765624966], + [94.22382812500004, 16.016455078125006], + [94.58896484375006, 17.5693359375], + [94.17070312500007, 18.73242187499997], + [94.24570312500006, 18.741162109374983], + [94.07001953125004, 18.893408203125006], + [94.04492187500003, 19.287402343750074], + [93.92919921874997, 18.89965820312503], + [93.70546875000005, 19.026904296875017], + [93.49306640625005, 19.369482421875006], + [93.82490234375004, 19.238476562499955], + [93.99814453125006, 19.440869140624983], + [93.61171875000005, 19.776074218749983], + [93.70703125000003, 19.912158203125074], + [93.25, 20.070117187500017], + [93.12949218750012, 19.858007812500063], + [93.00195312499997, 20.074853515624994], + [93.06679687500005, 20.377636718749955], + [92.82832031250004, 20.177587890625063], + [92.89111328124997, 20.34033203125], + [92.73564453125007, 20.56269531250001], + [92.72285156250004, 20.29560546875004], + [92.32412109375, 20.791845703125063], + [92.17958984375005, 21.293115234375023], + [92.33056640624997, 21.439794921874977], + [92.63164062500002, 21.306201171875045], + [92.5934570312501, 21.46733398437499], + [92.58281250000002, 21.940332031249994], + [92.57490234375004, 21.978076171875045], + [92.68896484374997, 22.130957031250006], + [92.72099609375002, 22.132421875000063], + [92.77138671875, 22.104785156250017], + [92.9645507812501, 22.003759765625034], + [93.07060546875002, 22.20942382812501], + [93.16201171875, 22.360205078125006], + [93.07871093750006, 22.71821289062501], + [93.20390625000002, 23.03701171875005], + [93.34941406250007, 23.08496093750003], + [93.36601562500007, 23.132519531249955], + [93.32626953125006, 24.064208984375057], + [93.45214843750003, 23.987402343750034], + [93.68339843750007, 24.00654296875004], + [94.07480468750006, 23.8720703125], + [94.29306640625012, 24.321875], + [94.37724609375002, 24.473730468750006], + [94.49316406250003, 24.637646484374983], + [94.70371093750012, 25.097851562499955], + [94.55302734375007, 25.215722656249994], + [94.66777343750007, 25.458886718749966], + [94.99199218750002, 25.77045898437504], + [95.01523437500006, 25.912939453125006], + [95.0929687500001, 25.98730468749997], + [95.13242187500006, 26.041259765625057], + [95.12929687500005, 26.070410156250034], + [95.10839843749997, 26.091406250000034], + [95.06894531250006, 26.19111328125001], + [95.0597656250001, 26.473974609375006], + [95.20146484375007, 26.641406250000017], + [96.19082031250005, 27.26127929687499], + [96.79785156249997, 27.29619140624999], + [96.95341796875002, 27.13330078125003], + [97.10205078125003, 27.11542968750004], + [97.10371093750004, 27.16333007812503], + [96.90195312500012, 27.439599609374994], + [96.88359375000002, 27.514843749999955], + [96.96279296875, 27.698291015625017], + [97.04970703125005, 27.760009765625], + [97.34355468750002, 27.982324218749994], + [97.30273437499997, 28.08598632812496], + [97.3224609375001, 28.21796875000004], + [97.35644531249997, 28.254492187500006], + [97.43144531250002, 28.353906250000023], + [97.53789062500002, 28.510205078124983], + [97.59921875000006, 28.51704101562504], + [98.06162109375012, 28.185888671874977], + [98.29882812499997, 27.550097656250045], + [98.4525390625, 27.6572265625], + [98.65117187500007, 27.572460937499983], + [98.7384765625001, 26.785742187500006], + [98.68554687499997, 26.189355468750023], + [98.56406250000006, 26.072412109374994], + [98.65625, 25.86357421874999], + [98.33378906250007, 25.586767578125006], + [98.14287109375007, 25.571093750000017], + [98.01074218749997, 25.292529296875017], + [97.8195312500001, 25.251855468749994], + [97.73789062500006, 24.869873046875057], + [97.58330078125002, 24.77480468750005], + [97.53144531250004, 24.49169921875003], + [97.7082031250001, 24.228759765625], + [97.56455078125012, 23.911035156250023], + [98.2125, 24.110644531250017], + [98.83505859375006, 24.121191406250034], + [98.67675781250003, 23.905078125000045], + [98.8322265625001, 23.624365234374977], + [98.86376953125003, 23.191259765625034], + [99.41806640625006, 23.069238281250023], + [99.50712890625002, 22.959130859374994], + [99.19296875000006, 22.12597656249997], + [99.9176757812501, 22.02802734375001], + [99.94072265625007, 21.75874023437504], + [100.14765625000004, 21.480517578125017], + [100.60458984375012, 21.471777343750006], + [101.07978515625004, 21.75585937499997], + [101.1388671875001, 21.567480468749977] + ] + ] + ] + }, + "properties": { "name": "Myanmar", "childNum": 15 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [19.21875, 43.449951171875], + [19.670996093750006, 43.163964843749994], + [20.344335937500006, 42.827929687499996], + [20.054296875, 42.760058593749996], + [20.06396484375, 42.54726562499999], + [19.78828125000001, 42.476171875], + [19.65449218750001, 42.628564453124994], + [19.280664062500023, 42.17255859375], + [19.342382812500006, 41.869091796875], + [18.436328125000017, 42.559716796874994], + [18.5458984375, 42.6416015625], + [18.46601562500001, 42.777246093749994], + [18.44384765625, 42.96845703125], + [18.46015625000001, 42.997900390625], + [18.48847656250001, 43.012158203125], + [18.623632812500006, 43.027685546875], + [18.621875, 43.124609375], + [18.674218750000023, 43.230810546875], + [18.74921875000001, 43.283544921875], + [18.85107421875, 43.346337890624994], + [18.934667968750006, 43.339453125], + [18.97871093750001, 43.285400390625], + [19.026660156250017, 43.292431640625], + [19.03671875, 43.357324218749994], + [18.940234375000017, 43.496728515624994], + [18.95068359375, 43.526660156249996], + [18.97421875, 43.542333984375], + [19.0283203125, 43.532519531249996], + [19.080078125, 43.517724609374994], + [19.11279296875, 43.52773437499999], + [19.164355468750017, 43.535449218749996], + [19.1943359375, 43.53330078125], + [19.21875, 43.449951171875] + ] + ] + }, + "properties": { "name": "Montenegro", "childNum": 1 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [111.878125, 43.68017578125], + [111.00722656250002, 43.34140625], + [110.400390625, 42.773681640625], + [109.44316406249999, 42.455957031249994], + [109.33984375, 42.438378906249994], + [108.68730468749999, 42.41611328125], + [108.17119140624999, 42.447314453124996], + [106.77001953125, 42.288720703124994], + [105.86757812500002, 41.993994140625], + [105.31435546875002, 41.770898437499994], + [105.19707031249999, 41.738037109375], + [105.11542968750001, 41.66328125], + [105.05058593749999, 41.61591796875], + [104.98203125000003, 41.595507812499996], + [104.49824218750001, 41.65869140625], + [104.49824218750001, 41.877001953124996], + [104.30517578125, 41.846142578125], + [103.99726562500001, 41.79697265625], + [103.71113281250001, 41.751318359375], + [103.07285156250003, 42.00595703125], + [102.5751953125, 42.092089843749996], + [102.15664062500002, 42.158105468749994], + [101.97294921874999, 42.215869140624996], + [101.65996093749999, 42.500048828124996], + [101.5791015625, 42.52353515625], + [101.49531250000001, 42.53876953125], + [101.09199218750001, 42.551318359374996], + [100.51904296875, 42.616796875], + [100.08632812500002, 42.670751953125], + [99.98378906250002, 42.67734375], + [99.46787109375003, 42.568212890625], + [97.20566406250003, 42.789794921875], + [96.38544921875001, 42.720361328124994], + [95.85957031250001, 43.2759765625], + [95.52558593750001, 43.953955078125], + [95.32558593750002, 44.039355468749996], + [95.35029296875001, 44.278076171875], + [94.71201171875003, 44.350830078125], + [93.51621093750003, 44.944482421874994], + [92.78789062499999, 45.0357421875], + [92.57890624999999, 45.010986328125], + [92.423828125, 45.008935546874994], + [92.17265624999999, 45.03525390625], + [92.02978515625, 45.068505859374994], + [91.584375, 45.076513671875], + [91.05, 45.217431640624994], + [90.87724609374999, 45.19609375], + [90.66181640625001, 45.525244140625], + [91.00175781249999, 46.035791015624994], + [90.99677734375001, 46.10498046875], + [90.94755859374999, 46.177294921874996], + [90.91152343750002, 46.270654296874994], + [90.98574218750002, 46.7490234375], + [90.91054687500002, 46.883251953125], + [90.86992187499999, 46.954492187499994], + [90.79902343750001, 46.98515625], + [90.71552734375001, 47.003857421875], + [90.49619140625003, 47.28515625], + [90.42519531250002, 47.5041015625], + [90.34746093749999, 47.596972656249996], + [90.33066406250003, 47.655175781249994], + [90.31328124999999, 47.67617187499999], + [90.19101562500003, 47.702099609375], + [90.10322265625001, 47.745410156249996], + [90.02792968750003, 47.877685546875], + [89.95869140625001, 47.886328125], + [89.91044921874999, 47.8443359375], + [89.83134765624999, 47.823291015624996], + [89.778125, 47.827001953125], + [89.56093750000002, 48.003955078124996], + [89.47919921875001, 48.029052734375], + [89.04765624999999, 48.0025390625], + [88.97109375000002, 48.049951171874994], + [88.91777343749999, 48.089013671874994], + [88.83828125000002, 48.101708984374994], + [88.68183593750001, 48.170556640624994], + [88.57597656249999, 48.220166015625], + [88.56679687500002, 48.317431640624996], + [88.51708984375, 48.38447265625], + [88.41396484375002, 48.40341796875], + [88.30996093750002, 48.472070312499994], + [87.97968750000001, 48.555126953125], + [88.06005859375, 48.707177734374994], + [87.83183593749999, 48.791650390624994], + [87.7431640625, 48.881640625], + [87.87216796875003, 49.000146484374994], + [87.81630859375002, 49.0802734375], + [87.8251953125, 49.11630859375], + [87.81425781249999, 49.1623046875], + [87.93476562500001, 49.16455078125], + [87.98808593749999, 49.186914062499994], + [88.02851562500001, 49.219775390624996], + [88.11572265625, 49.256298828125], + [88.19257812500001, 49.451708984374996], + [88.63320312500002, 49.486132812499996], + [88.83164062500003, 49.4484375], + [88.86386718750003, 49.527636718749996], + [88.90019531249999, 49.539697265624994], + [88.94541015625003, 49.507666015625], + [88.97060546875002, 49.483740234375], + [89.00839843750003, 49.472802734374994], + [89.10947265625003, 49.501367187499994], + [89.17998046874999, 49.5322265625], + [89.20292968749999, 49.595703125], + [89.24394531249999, 49.62705078125], + [89.39560546875003, 49.6115234375], + [89.475, 49.66054687499999], + [89.57919921875003, 49.69970703125], + [89.65410156249999, 49.71748046875], + [89.64384765624999, 49.90302734375], + [90.0537109375, 50.09375], + [90.65507812499999, 50.22236328125], + [90.71435546875, 50.259423828124994], + [90.7607421875, 50.305957031249996], + [91.02158203125003, 50.415478515625], + [91.23378906250002, 50.452392578125], + [91.30058593749999, 50.46337890625], + [91.3408203125, 50.470068359375], + [91.4150390625, 50.468017578125], + [91.44648437500001, 50.52216796875], + [91.80429687500003, 50.693603515625], + [92.10400390625, 50.6919921875], + [92.1923828125, 50.700585937499994], + [92.35478515624999, 50.864160156249994], + [92.42636718750003, 50.803076171875], + [92.62666015625001, 50.68828125], + [92.68134765625001, 50.683203125], + [92.73867187500002, 50.7109375], + [92.779296875, 50.778662109375], + [92.8564453125, 50.789111328124996], + [92.94130859375002, 50.778222656249994], + [93.103125, 50.60390625], + [94.25107421875003, 50.556396484375], + [94.35468750000001, 50.221826171874994], + [94.61474609375, 50.023730468749996], + [94.67548828125001, 50.028076171875], + [94.71806640624999, 50.043261718749996], + [94.93027343750003, 50.04375], + [95.11142578125003, 49.935449218749994], + [95.52265625000001, 49.91123046875], + [96.06552734375003, 49.99873046875], + [96.31503906250003, 49.901123046875], + [96.98574218750002, 49.8828125], + [97.20859375000003, 49.730810546875], + [97.35976562500002, 49.741455078125], + [97.58935546875, 49.911474609375], + [98.00390625, 50.0142578125], + [98.25029296874999, 50.30244140625], + [98.27949218750001, 50.533251953124996], + [98.14501953125, 50.5685546875], + [98.07890624999999, 50.603808593749996], + [98.02978515625, 50.64462890625], + [97.82529296875003, 50.985253906249994], + [98.103125, 51.483544921874994], + [98.64052734375002, 51.801171875], + [98.89316406250003, 52.11728515625], + [99.92167968749999, 51.755517578125], + [100.03457031250002, 51.737109375], + [100.23037109375002, 51.729833984375], + [100.46894531250001, 51.72607421875], + [100.53623046875003, 51.7134765625], + [101.38125, 51.45263671875], + [101.57089843750003, 51.4671875], + [101.82119140625002, 51.421044921874994], + [102.11152343750001, 51.353466796875], + [102.15566406250002, 51.313769531249996], + [102.16005859375002, 51.26083984375], + [102.14238281249999, 51.216064453125], + [102.15195312500003, 51.10751953125], + [102.19453125000001, 51.050683593749994], + [102.21503906250001, 50.829443359375], + [102.31660156250001, 50.71845703125], + [102.28837890624999, 50.585107421874994], + [103.30439453125001, 50.20029296875], + [103.63291015625003, 50.138574218749994], + [103.72324218750003, 50.153857421874996], + [103.80263671875002, 50.176074218749996], + [104.07871093750003, 50.154248046875], + [105.38359374999999, 50.47373046875], + [106.21787109375003, 50.304589843749994], + [106.36845703124999, 50.317578125], + [106.57441406250001, 50.32880859375], + [106.71113281250001, 50.31259765625], + [106.94130859375002, 50.196679687499994], + [107.04023437500001, 50.086474609374996], + [107.14306640625, 50.033007812499996], + [107.23330078125002, 49.989404296874994], + [107.34707031250002, 49.986669921875], + [107.63095703125003, 49.98310546875], + [107.91660156250003, 49.947802734374996], + [107.96542968750003, 49.653515625], + [108.40693359375001, 49.396386718749994], + [108.5224609375, 49.34150390625], + [108.61367187500002, 49.322802734374996], + [109.23671875000002, 49.334912109375], + [109.45371093750003, 49.296337890625], + [109.52871093750002, 49.269873046875], + [110.19990234375001, 49.17041015625], + [110.42783203125003, 49.219970703125], + [110.70976562499999, 49.14296875], + [110.82792968749999, 49.166162109374994], + [111.20419921875003, 49.304296875], + [111.33662109375001, 49.35585937499999], + [111.42929687500003, 49.342626953125], + [112.07968750000003, 49.42421875], + [112.49492187499999, 49.53232421875], + [112.69736328125003, 49.507275390625], + [112.80644531249999, 49.523583984374994], + [112.91484374999999, 49.569238281249994], + [113.05556640625002, 49.616259765624996], + [113.09208984374999, 49.692529296874994], + [113.16416015625003, 49.797167968749996], + [113.31904296875001, 49.874316406249996], + [113.44550781250001, 49.9416015625], + [113.57421875, 50.00703125], + [114.29707031250001, 50.2744140625], + [114.7431640625, 50.233691406249996], + [115.00332031250002, 50.138574218749994], + [115.27451171875003, 49.948876953124994], + [115.36503906249999, 49.911767578124994], + [115.42919921875, 49.896484375], + [115.58798828125003, 49.886035156249996], + [115.7177734375, 49.880615234375], + [115.79521484374999, 49.905908203124994], + [115.92597656250001, 49.9521484375], + [116.13457031249999, 50.010791015624996], + [116.216796875, 50.00927734375], + [116.35117187500003, 49.978076171874996], + [116.55117187500002, 49.9203125], + [116.68330078125001, 49.823779296874996], + [115.82050781250001, 48.57724609375], + [115.79169921875001, 48.455712890624994], + [115.79658203125001, 48.346337890624994], + [115.78554687500002, 48.2482421875], + [115.63945312499999, 48.18623046875], + [115.52509765625001, 48.130859375], + [115.61640625000001, 47.874804687499996], + [115.89824218749999, 47.686914062499994], + [115.99384765625001, 47.71132812499999], + [116.07480468750003, 47.78955078125], + [116.23115234375001, 47.858203125], + [116.31718749999999, 47.85986328125], + [116.37822265624999, 47.844042968749996], + [116.51347656249999, 47.83955078125], + [116.65195312500003, 47.864501953125], + [116.76054687499999, 47.869775390624994], + [116.90117187499999, 47.853076171874996], + [116.95166015625, 47.836572265624994], + [117.06972656250002, 47.806396484375], + [117.28593749999999, 47.666357421875], + [117.35078125000001, 47.652197265625], + [117.76835937499999, 47.987890625], + [118.49843750000002, 47.983984375], + [118.56777343750002, 47.943261718749994], + [118.69052734375003, 47.822265625], + [118.75996093750001, 47.757617187499996], + [118.88027343750002, 47.72509765625], + [119.017578125, 47.685351562499996], + [119.08193359375002, 47.654150390625], + [119.71113281250001, 47.15], + [119.89785156250002, 46.8578125], + [119.8671875, 46.672167968749996], + [119.74746093750002, 46.627197265625], + [119.70664062500003, 46.606005859374996], + [119.62021484375003, 46.603955078125], + [119.47402343750002, 46.62666015625], + [119.33183593749999, 46.613818359374996], + [119.162109375, 46.638671875], + [118.95712890625003, 46.73486328125], + [118.84394531250001, 46.760205078125], + [118.79033203124999, 46.7470703125], + [118.72294921874999, 46.69189453125], + [118.64873046874999, 46.70166015625], + [118.58046875000002, 46.69189453125], + [118.40439453125003, 46.703173828124996], + [118.30869140625003, 46.717041015625], + [118.15683593750003, 46.678564453125], + [118.0712890625, 46.6666015625], + [117.7412109375, 46.5181640625], + [117.546875, 46.58828125], + [117.43808593750003, 46.586230468749996], + [117.40556640624999, 46.5708984375], + [117.39218750000003, 46.53759765625], + [117.35634765625002, 46.436669921874994], + [117.35693359375, 46.39130859375], + [117.33339843750002, 46.36201171875], + [116.85908203125001, 46.387939453125], + [116.56259765625003, 46.289794921875], + [116.21298828125003, 45.8869140625], + [116.22910156250003, 45.845751953124996], + [116.240625, 45.79599609375], + [116.19765625000002, 45.73935546875], + [115.68105468750002, 45.458251953125], + [115.16259765625, 45.390234375], + [114.91923828124999, 45.378271484375], + [114.73876953125, 45.41962890625], + [114.56015625000003, 45.389990234375], + [114.41914062500001, 45.202587890625], + [114.16738281250002, 45.049853515624996], + [114.08027343750001, 44.971142578125], + [113.87705078125003, 44.89619140625], + [113.65263671874999, 44.763476562499996], + [113.58701171875003, 44.745703125], + [113.04941406250003, 44.810351562499996], + [112.70673828125001, 44.883447265624994], + [112.59677734375003, 44.91767578125], + [112.49931640624999, 45.0109375], + [112.41132812500001, 45.058203125], + [112.11289062500003, 45.062939453125], + [112.03261718750002, 45.081640625], + [111.89804687500003, 45.0640625], + [111.40224609375002, 44.36728515625], + [111.93173828125003, 43.81494140625], + [111.878125, 43.68017578125] + ] + ] + }, + "properties": { "name": "Mongolia", "childNum": 1 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [145.75195312499997, 15.133154296874991], + [145.71318359375007, 15.215283203125026], + [145.821875, 15.265380859375014], + [145.75195312499997, 15.133154296874991] + ] + ] + }, + "properties": { "name": "N. Mariana Is.", "childNum": 1 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [32.112890625, -26.839453125], + [32.10595703125, -26.52001953125], + [32.04140625000002, -26.28125], + [32.060546875, -26.018359375], + [31.9482421875, -25.957617187500006], + [31.98583984375, -24.46064453125001], + [31.799609375000017, -23.8921875], + [31.54560546875001, -23.48232421875001], + [31.287890625000017, -22.40205078125001], + [31.429492187500017, -22.298828125], + [32.429785156250006, -21.29707031250001], + [32.353613281250006, -21.136523437500003], + [32.49238281250001, -20.659765625], + [32.992773437500006, -19.98486328125], + [32.77763671875002, -19.388769531250006], + [32.84980468750001, -19.10439453125001], + [32.69970703125, -18.94091796875], + [32.99306640625002, -18.35957031250001], + [32.87626953125002, -16.88359375], + [32.94804687500002, -16.71230468750001], + [31.939843750000023, -16.428808593750006], + [31.236230468750023, -16.02363281250001], + [30.437792968750017, -15.995312500000011], + [30.39609375, -15.64306640625], + [30.231835937500023, -14.990332031250006], + [33.201757812500006, -14.013378906250011], + [33.63642578125001, -14.568164062500003], + [34.375, -14.4248046875], + [34.50527343750002, -14.59814453125], + [34.54082031250002, -15.297265625], + [34.24609375, -15.829394531250003], + [34.528125, -16.319140625], + [34.93339843750002, -16.760351562500006], + [35.11210937500002, -16.898535156250006], + [35.06464843750001, -17.07861328125], + [35.124609375, -17.127246093750003], + [35.20136718750001, -17.13105468750001], + [35.272558593750006, -17.118457031250003], + [35.29042968750002, -17.096972656250003], + [35.28115234375002, -16.80781250000001], + [35.22978515625002, -16.639257812500006], + [35.178320312500006, -16.573339843750006], + [35.16718750000001, -16.56025390625001], + [35.242773437500006, -16.375390625], + [35.358496093750006, -16.160546875], + [35.59931640625001, -16.12587890625001], + [35.70888671875002, -16.095800781250006], + [35.75527343750002, -16.05830078125001], + [35.79121093750001, -15.958691406250011], + [35.89277343750001, -14.891796875000011], + [35.86669921875, -14.86376953125], + [35.84716796875, -14.6708984375], + [35.6904296875, -14.465527343750011], + [35.48847656250001, -14.201074218750009], + [35.37578125000002, -14.058691406250006], + [35.24746093750002, -13.896875], + [35.01386718750001, -13.643457031250009], + [34.61152343750001, -13.437890625], + [34.54570312500002, -13.21630859375], + [34.542578125, -13.108691406250003], + [34.35781250000002, -12.164746093750011], + [34.60625, -11.690039062500006], + [34.65957031250002, -11.588671875], + [34.82656250000002, -11.57568359375], + [34.95947265625, -11.578125], + [35.1826171875, -11.574804687500006], + [35.41826171875002, -11.583203125000011], + [35.50439453125, -11.604785156250003], + [35.56435546875002, -11.60234375], + [35.630957031250006, -11.58203125], + [35.78544921875002, -11.452929687500003], + [35.91132812500001, -11.4546875], + [36.08222656250001, -11.537304687500011], + [36.17548828125001, -11.609277343750009], + [36.19130859375002, -11.670703125], + [36.3056640625, -11.706347656250003], + [36.97890625000002, -11.566992187500006], + [37.37285156250002, -11.71044921875], + [37.54169921875001, -11.675097656250003], + [37.72480468750001, -11.580664062500006], + [37.92021484375002, -11.294726562500003], + [38.491796875, -11.413281250000011], + [38.9875, -11.167285156250003], + [39.81708984375001, -10.912402343750003], + [39.98867187500002, -10.82080078125], + [40.46357421875001, -10.46435546875], + [40.61171875000002, -10.661523437500009], + [40.48662109375002, -10.76513671875], + [40.59716796875, -10.830664062500006], + [40.40283203125, -11.33203125], + [40.53154296875002, -12.004589843750011], + [40.48710937500002, -12.4921875], + [40.58085937500002, -12.635546875], + [40.43681640625002, -12.983105468750011], + [40.56875, -12.984667968750003], + [40.595703125, -14.122851562500003], + [40.715625, -14.214453125], + [40.64609375, -14.538671875], + [40.775, -14.421289062500009], + [40.84453125000002, -14.718652343750009], + [40.617773437500006, -15.115527343750003], + [40.650976562500006, -15.260937500000011], + [39.98359375000001, -16.22548828125001], + [39.79091796875002, -16.29453125], + [39.84462890625002, -16.435644531250006], + [39.084375, -16.97285156250001], + [38.14492187500002, -17.242773437500006], + [37.24453125000002, -17.73994140625001], + [36.93935546875002, -17.993457031250003], + [36.40371093750002, -18.76972656250001], + [36.26289062500001, -18.71962890625001], + [36.23564453125002, -18.861328125], + [35.85371093750001, -18.99335937500001], + [34.947851562500006, -19.81269531250001], + [34.6494140625, -19.70136718750001], + [34.75576171875002, -19.82197265625001], + [34.705078125, -20.473046875], + [34.98232421875002, -20.80625], + [35.267675781250006, -21.650976562500006], + [35.31572265625002, -22.396875], + [35.38300781250001, -22.45458984375], + [35.45634765625002, -22.11591796875001], + [35.53007812500002, -22.248144531250006], + [35.57539062500001, -22.96308593750001], + [35.37041015625002, -23.79824218750001], + [35.5419921875, -23.82441406250001], + [35.48964843750002, -24.065527343750006], + [34.99208984375002, -24.65058593750001], + [32.96113281250001, -25.49042968750001], + [32.590429687500006, -26.00410156250001], + [32.84882812500001, -26.26806640625], + [32.95488281250002, -26.08359375], + [32.93359375, -26.25234375], + [32.88916015625, -26.83046875], + [32.88613281250002, -26.84931640625001], + [32.353515625, -26.861621093750003], + [32.19960937500002, -26.83349609375], + [32.112890625, -26.839453125] + ] + ] + }, + "properties": { "name": "Mozambique", "childNum": 1 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [-16.37333984374999, 19.706445312499994], + [-16.437548828124932, 19.609277343749994], + [-16.477001953124983, 19.710351562499994], + [-16.343652343749994, 19.86621093750003], + [-16.37333984374999, 19.706445312499994] + ] + ], + [ + [ + [-5.359912109374989, 16.282861328124994], + [-5.5125, 15.496289062499983], + [-9.293701171875, 15.502832031249994], + [-9.350585937499943, 15.677392578125023], + [-9.38535156249992, 15.667626953124994], + [-9.4265625, 15.623046875000057], + [-9.447705078124926, 15.574853515624994], + [-9.446923828124937, 15.458203124999955], + [-9.941406249999972, 15.373779296874986], + [-10.696582031249989, 15.42265625], + [-10.9482421875, 15.151123046875014], + [-11.455224609374994, 15.62539062499999], + [-11.760156249999937, 15.425537109375057], + [-11.828759765624966, 15.244873046875014], + [-11.872851562499989, 14.995166015625031], + [-12.02158203124992, 14.804931640625], + [-12.081542968749972, 14.766357421875057], + [-12.104687499999955, 14.745361328125043], + [-12.40869140625, 14.889013671874991], + [-12.735253906249994, 15.13125], + [-13.105273437499989, 15.57177734375], + [-13.40966796875, 16.059179687500006], + [-13.756640624999989, 16.172509765624994], + [-13.868457031249932, 16.14814453125001], + [-14.300097656249932, 16.58027343750001], + [-14.990625, 16.676904296874994], + [-15.768212890624994, 16.485107421875], + [-16.23901367187497, 16.53129882812499], + [-16.44101562499992, 16.20454101562504], + [-16.480078124999977, 16.097216796875017], + [-16.50205078124992, 15.917333984375063], + [-16.53525390624995, 15.838378906250057], + [-16.53574218749995, 16.28681640625001], + [-16.463623046875, 16.60151367187501], + [-16.030322265625017, 17.88793945312497], + [-16.213085937499926, 19.003320312500023], + [-16.51445312499996, 19.361962890624994], + [-16.305273437499977, 19.51264648437504], + [-16.44487304687499, 19.47314453124997], + [-16.21044921875003, 20.227929687500023], + [-16.42978515624995, 20.652343750000057], + [-16.622509765624955, 20.634179687499994], + [-16.87607421874992, 21.086132812499955], + [-16.998242187499926, 21.039697265625023], + [-17.048046874999955, 20.80615234375003], + [-17.06396484375, 20.89882812499999], + [-16.96455078125001, 21.329248046875023], + [-15.231201171875, 21.331298828125], + [-14.084667968749926, 21.33271484375001], + [-13.626025390624932, 21.33325195312503], + [-13.396728515624943, 21.333544921875017], + [-13.167431640624926, 21.333789062500074], + [-13.016210937499949, 21.33393554687501], + [-13.025097656249983, 21.46679687499997], + [-13.032226562500028, 21.572070312500017], + [-13.041748046875, 21.71381835937504], + [-13.051220703124983, 21.854785156250074], + [-13.094335937499977, 22.49599609375005], + [-13.153271484374983, 22.820507812499983], + [-13.031494140624943, 23.000244140625], + [-12.895996093749972, 23.08955078125001], + [-12.739599609375006, 23.192724609375063], + [-12.62041015624996, 23.271337890625006], + [-12.559375, 23.290820312500045], + [-12.372900390624977, 23.318017578124994], + [-12.023437499999943, 23.467578125000017], + [-12.016308593749983, 23.97021484375], + [-12.016308593749983, 24.378662109375], + [-12.016308593749983, 24.923242187499994], + [-12.016308593749983, 25.059375], + [-12.016308593749983, 25.331689453124994], + [-12.016308593749983, 25.740136718749994], + [-12.016308593749983, 25.995410156250017], + [-10.376123046874966, 25.995458984375034], + [-9.444531249999983, 25.99550781250005], + [-9.071923828124937, 25.99550781250005], + [-8.885644531249994, 25.99550781250005], + [-8.682226562499949, 25.99550781250005], + [-8.68212890625, 26.109472656250006], + [-8.68212890625, 26.273193359375057], + [-8.682324218749955, 26.49770507812505], + [-8.682617187500028, 26.723144531250057], + [-8.682861328124972, 26.92133789062501], + [-8.683349609375, 27.285937500000045], + [-4.822607421874949, 24.99560546875], + [-5.640771484374994, 24.99516601562499], + [-5.959814453124977, 24.994970703125063], + [-6.287207031249977, 24.99482421875001], + [-6.594091796874977, 24.99462890624997], + [-6.396582031249977, 23.274804687499994], + [-6.02641601562496, 20.04218750000001], + [-5.827099609374955, 18.301562500000045], + [-5.741699218749943, 17.555566406250023], + [-5.713183593750017, 17.306884765625], + [-5.684765624999983, 17.058251953124966], + [-5.628662109375028, 16.568652343750045], + [-5.50961914062492, 16.442041015625023], + [-5.359912109374989, 16.282861328124994] + ] + ] + ] + }, + "properties": { "name": "Mauritania", "childNum": 2 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [-62.1484375, 16.74033203124999], + [-62.221630859375, 16.699511718750003], + [-62.191357421875, 16.804394531249997], + [-62.1484375, 16.74033203124999] + ] + ] + }, + "properties": { "name": "Montserrat", "childNum": 1 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [57.65126953125002, -20.48486328125], + [57.31767578125002, -20.42763671875001], + [57.416015625, -20.18378906250001], + [57.65654296875002, -19.98994140625001], + [57.7919921875, -20.21259765625001], + [57.65126953125002, -20.48486328125] + ] + ] + }, + "properties": { "name": "Mauritius", "childNum": 1 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [34.95947265625003, -11.578125], + [34.82656250000005, -11.575683593749972], + [34.65957031250005, -11.58867187499996], + [34.61855468750005, -11.620214843749991], + [34.60625, -11.690039062500006], + [34.3578125, -12.164746093749997], + [34.542578125, -13.108691406250003], + [34.54570312500002, -13.21630859375], + [34.6115234375001, -13.437890625000023], + [35.0138671875001, -13.64345703124998], + [35.247460937499994, -13.896875], + [35.37578125000002, -14.05869140625002], + [35.48847656250004, -14.20107421874998], + [35.69042968749997, -14.465527343750026], + [35.84716796875003, -14.670898437500043], + [35.8927734375001, -14.891796875000011], + [35.7912109375001, -15.958691406250026], + [35.75527343750005, -16.058300781249983], + [35.708886718749994, -16.095800781249977], + [35.5993164062501, -16.12587890624998], + [35.35849609375006, -16.160546875000023], + [35.242773437500006, -16.375390625], + [35.16718750000004, -16.56025390625001], + [35.178320312500006, -16.57333984375002], + [35.22978515625002, -16.639257812500034], + [35.281152343749994, -16.8078125], + [35.29042968750005, -17.096972656250017], + [35.27255859375006, -17.11845703124996], + [35.2013671875001, -17.13105468750004], + [35.124609375, -17.127246093749974], + [35.06464843750004, -17.078613281250014], + [35.11210937500002, -16.898535156250006], + [34.93339843750002, -16.760351562500006], + [34.528125, -16.319140625], + [34.24609374999997, -15.829394531249974], + [34.54082031250002, -15.297265625], + [34.50527343750005, -14.598144531249957], + [34.375, -14.4248046875], + [33.63642578125004, -14.568164062499974], + [33.148046875, -13.94091796875], + [32.98125, -14.009375], + [32.797460937500006, -13.6884765625], + [32.67041015624997, -13.590429687500006], + [32.96757812500002, -13.225], + [32.97519531250006, -12.701367187499983], + [33.51230468750006, -12.347753906249977], + [33.340136718750074, -12.308300781250011], + [33.25234375000005, -12.112597656250031], + [33.3039062500001, -11.69082031249998], + [33.23271484375002, -11.417675781250026], + [33.26835937500002, -11.403906249999977], + [33.379785156249994, -11.15791015625004], + [33.29277343750002, -10.85234375], + [33.661523437499994, -10.553125], + [33.55371093749997, -10.391308593750011], + [33.53759765624997, -10.351562499999986], + [33.52890625, -10.234667968749974], + [33.31152343750003, -10.037988281249966], + [33.3371093750001, -9.954003906249994], + [33.350976562499994, -9.862207031250037], + [33.25, -9.759570312500003], + [33.148046875, -9.603515625], + [32.99599609375005, -9.622851562499946], + [32.91992187500003, -9.407421875000026], + [33.88886718750004, -9.670117187499983], + [33.99560546875003, -9.495410156250003], + [34.32089843750006, -9.731542968749977], + [34.56992187500006, -10.241113281249966], + [34.66708984375006, -10.792480468750028], + [34.60791015624997, -11.08046875], + [34.77382812500005, -11.341699218750009], + [34.890625, -11.3935546875], + [34.93701171874997, -11.463476562500034], + [34.95947265625003, -11.578125] + ] + ] + }, + "properties": { "name": "Malawi", "childNum": 1 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [111.38925781250006, 2.415332031250031], + [111.31152343749997, 2.437597656250034], + [111.33349609374997, 2.768310546875], + [111.38925781250006, 2.415332031250031] + ] + ], + [ + [ + [104.22158203125, 2.731738281250003], + [104.1291015625001, 2.767236328125037], + [104.18476562500004, 2.871728515625009], + [104.22158203125, 2.731738281250003] + ] + ], + [ + [ + [117.88476562499997, 4.186132812500006], + [117.64902343750012, 4.168994140624974], + [117.70800781249997, 4.262402343749997], + [117.88476562499997, 4.186132812500006] + ] + ], + [ + [ + [100.28896484375005, 5.294726562499989], + [100.19101562500006, 5.28286132812498], + [100.2455078125, 5.467773437499986], + [100.33886718749997, 5.410058593750037], + [100.28896484375005, 5.294726562499989] + ] + ], + [ + [ + [99.848046875, 6.465722656249994], + [99.9186523437501, 6.358593750000011], + [99.74375, 6.263281249999963], + [99.64628906250002, 6.418359375000023], + [99.848046875, 6.465722656249994] + ] + ], + [ + [ + [102.10107421874997, 6.242236328125031], + [102.34013671875002, 6.172021484375023], + [102.534375, 5.862548828125028], + [103.09707031250005, 5.408447265624986], + [103.41582031250007, 4.85029296875004], + [103.43945312499997, 2.93310546875], + [103.8122070312501, 2.58046875], + [104.21855468750002, 1.722851562499997], + [104.25009765625012, 1.388574218750009], + [104.11494140625004, 1.412255859375037], + [103.98144531250003, 1.623632812500034], + [103.99150390625002, 1.454785156249997], + [103.6945312500001, 1.449658203125026], + [103.48027343750007, 1.329492187499966], + [103.35683593750005, 1.546142578125057], + [102.72714843750012, 1.855566406250034], + [101.29550781250012, 2.885205078125011], + [101.29990234375012, 3.253271484375034], + [100.71542968750006, 3.966210937499966], + [100.79550781250012, 4.023388671874983], + [100.61455078125002, 4.3734375], + [100.34326171874997, 5.984179687500031], + [100.11914062499997, 6.441992187500048], + [100.26142578125004, 6.682714843749963], + [100.3454101562501, 6.549902343750006], + [100.75449218750012, 6.460058593749991], + [100.87392578125, 6.24541015624996], + [101.05351562500002, 6.242578125], + [100.98164062500004, 5.771044921875045], + [101.1139648437501, 5.636767578125045], + [101.5560546875, 5.907763671875003], + [101.67841796875004, 5.778808593750028], + [101.87363281250012, 5.825292968749991], + [102.10107421874997, 6.242236328125031] + ] + ], + [ + [ + [117.5744140625001, 4.17060546875004], + [117.10058593750003, 4.337060546875023], + [116.51474609375006, 4.370800781249969], + [115.86074218750005, 4.348046875000037], + [115.67880859375006, 4.193017578124994], + [115.45439453125002, 3.034326171875009], + [115.24697265625005, 3.025927734374989], + [115.117578125, 2.89487304687502], + [115.08076171875004, 2.63422851562504], + [115.1791015625, 2.523193359374972], + [114.78642578125002, 2.250488281250014], + [114.83056640625003, 1.980029296874989], + [114.5125, 1.452001953124963], + [113.90234375000003, 1.434277343749997], + [113.6222656250001, 1.2359375], + [113.00654296875004, 1.433886718750003], + [112.94296875000006, 1.566992187500034], + [112.47617187500006, 1.559082031250028], + [112.1857421875001, 1.4390625], + [112.078515625, 1.143359374999974], + [111.80898437500005, 1.011669921874969], + [111.10136718750002, 1.050537109374986], + [110.50576171875005, 0.861962890625023], + [109.65400390625004, 1.614892578125023], + [109.53896484375, 1.89619140625004], + [109.62890625000003, 2.027539062499983], + [109.86484375000012, 1.764453125000031], + [110.34921875000012, 1.719726562499972], + [111.22324218750012, 1.395849609374991], + [111.0287109375, 1.557812500000026], + [111.26816406250012, 2.13974609375002], + [111.20859375000012, 2.379638671875043], + [111.44384765625003, 2.381542968749983], + [111.5125, 2.743017578124991], + [112.98789062500006, 3.161914062499974], + [113.92392578125006, 4.243212890625003], + [114.0638671875, 4.592675781249966], + [114.65410156250007, 4.037646484375045], + [114.84023437500005, 4.393212890625009], + [114.74667968750006, 4.718066406250017], + [115.02675781250005, 4.899707031249989], + [115.10703125000006, 4.390429687499974], + [115.290625, 4.352587890624989], + [115.1400390625, 4.899755859374991], + [115.37490234375, 4.932763671874966], + [115.55449218750007, 5.093554687500045], + [115.41904296875012, 5.413183593749963], + [115.60390625, 5.603417968749994], + [115.74082031250012, 5.533007812500045], + [115.8771484375001, 5.613525390625014], + [116.74980468750007, 6.977099609374989], + [116.8498046875001, 6.826708984374989], + [116.78808593749997, 6.606103515624994], + [117.12851562500012, 6.968896484375009], + [117.2298828125, 6.939990234374974], + [117.29404296875006, 6.676904296875023], + [117.60966796875002, 6.512646484375054], + [117.69375, 6.35], + [117.64453124999997, 6.001855468749994], + [117.5011718750001, 5.884667968750009], + [118.00380859375, 6.053320312499991], + [118.11582031250006, 5.8625], + [117.93476562500004, 5.7875], + [117.97363281249997, 5.70625], + [118.35312500000012, 5.80605468749998], + [118.59482421875006, 5.592089843750003], + [119.22343750000007, 5.412646484375031], + [119.2663085937501, 5.308105468750057], + [119.21962890625, 5.159814453125037], + [118.9125, 5.02290039062504], + [118.26054687500007, 4.988867187500034], + [118.18535156250002, 4.828515625000051], + [118.5625, 4.502148437499997], + [118.54833984375003, 4.379248046875006], + [118.008203125, 4.250244140625014], + [117.6964843750001, 4.342822265625045], + [117.5744140625001, 4.17060546875004] + ] + ], + [ + [ + [117.14160156250003, 7.168212890625028], + [117.08066406250006, 7.115283203124989], + [117.06425781250007, 7.26069335937504], + [117.2640625, 7.351660156250006], + [117.26679687500004, 7.220800781249991], + [117.14160156250003, 7.168212890625028] + ] + ] + ] + }, + "properties": { "name": "Malaysia", "childNum": 8 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [23.380664062500017, -17.640625], + [24.27490234375, -17.481054687500006], + [24.73291015625, -17.51777343750001], + [25.001757812500017, -17.56855468750001], + [25.2587890625, -17.793554687500006], + [24.909082031250023, -17.821386718750006], + [24.530566406250017, -18.052734375], + [24.243945312500017, -18.0234375], + [23.599707031250006, -18.4599609375], + [23.219335937500006, -17.99970703125001], + [20.97412109375, -18.31884765625], + [20.9794921875, -21.9619140625], + [19.977343750000017, -22.00019531250001], + [19.98046875, -24.77675781250001], + [19.98046875, -28.310351562500003], + [19.98046875, -28.451269531250006], + [19.539843750000017, -28.574609375], + [19.31269531250001, -28.73330078125001], + [19.24580078125001, -28.901660156250003], + [19.16171875, -28.938769531250003], + [18.310839843750017, -28.88623046875], + [17.44794921875001, -28.69814453125001], + [17.34785156250001, -28.50117187500001], + [17.358691406250017, -28.26943359375001], + [17.1884765625, -28.13251953125001], + [17.05625, -28.031054687500003], + [16.93330078125001, -28.069628906250003], + [16.875292968750017, -28.1279296875], + [16.841210937500023, -28.21894531250001], + [16.81015625, -28.26455078125001], + [16.7875, -28.39472656250001], + [16.755761718750023, -28.4521484375], + [16.62617187500001, -28.487890625], + [16.487109375000017, -28.572851562500006], + [16.447558593750017, -28.617578125], + [15.719042968750017, -27.9658203125], + [15.341503906250011, -27.386523437500003], + [15.139062500000023, -26.50800781250001], + [14.9677734375, -26.31806640625001], + [14.837109375000011, -25.033203125], + [14.5015625, -24.201953125], + [14.462792968750023, -22.44912109375001], + [13.450585937500023, -20.91669921875001], + [13.168359375000023, -20.184667968750006], + [12.458203125000011, -18.9267578125], + [11.77587890625, -18.001757812500003], + [11.733496093750006, -17.7509765625], + [11.743066406250023, -17.24921875000001], + [11.902539062500011, -17.2265625], + [12.013964843750017, -17.168554687500006], + [12.21337890625, -17.2099609375], + [12.318457031250006, -17.21337890625], + [12.359277343750023, -17.205859375], + [12.548144531250017, -17.212695312500003], + [13.179492187500017, -16.9716796875], + [13.475976562500023, -17.0400390625], + [14.017480468750023, -17.40888671875001], + [16.1484375, -17.390234375], + [18.396386718750023, -17.3994140625], + [18.95527343750001, -17.803515625], + [20.1943359375, -17.863671875], + [20.745507812500023, -18.01972656250001], + [22.32421875, -17.8375], + [23.380664062500017, -17.640625] + ] + ] + }, + "properties": { "name": "Namibia", "childNum": 1 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [167.54443359375003, -22.62324218750001], + [167.44375, -22.63916015624997], + [167.44345703125006, -22.541406250000037], + [167.54443359375003, -22.62324218750001] + ] + ], + [ + [ + [168.01093750000004, -21.429980468750017], + [168.1390625, -21.44521484375001], + [168.12070312500012, -21.615820312500034], + [167.96679687500003, -21.641601562499957], + [167.81542968749997, -21.392675781249963], + [167.9884765625001, -21.337890624999986], + [168.01093750000004, -21.429980468750017] + ] + ], + [ + [ + [167.40087890625003, -21.16064453125003], + [167.07265625, -20.99726562499997], + [167.03271484374997, -20.922558593750026], + [167.18945312500003, -20.803515625000017], + [167.05576171875012, -20.720214843750014], + [167.29794921875006, -20.732519531250034], + [167.40087890625003, -21.16064453125003] + ] + ], + [ + [ + [164.20234375000004, -20.246093749999957], + [164.4359375, -20.282226562499957], + [165.191796875, -20.768847656249974], + [165.66279296875004, -21.267187499999977], + [166.94238281250003, -22.09013671875003], + [166.97031250000012, -22.32285156250002], + [166.77412109375004, -22.37617187500004], + [166.4679687500001, -22.256054687499997], + [164.92744140625004, -21.289843749999974], + [164.16972656250007, -20.48017578125004], + [164.05966796875012, -20.141503906249966], + [164.20234375000004, -20.246093749999957] + ] + ] + ] + }, + "properties": { "name": "New Caledonia", "childNum": 4 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [14.97900390625, 22.99619140624999], + [15.181835937500011, 21.523388671874997], + [15.607324218750023, 20.954394531250003], + [15.587109375000011, 20.733300781249994], + [15.963183593750017, 20.34619140625], + [15.735058593750011, 19.904052734375], + [15.474316406250011, 16.908398437499997], + [14.367968750000017, 15.750146484374994], + [13.4482421875, 14.380664062500003], + [13.505761718750023, 14.134423828124994], + [13.606347656250023, 13.70458984375], + [13.426953125000011, 13.701757812499991], + [13.323828125, 13.670849609374997], + [12.871679687500006, 13.449023437500003], + [12.65478515625, 13.3265625], + [12.463183593750017, 13.09375], + [10.958886718750023, 13.371533203124997], + [10.475878906250017, 13.330224609374994], + [10.229589843750006, 13.281005859375], + [10.184667968750006, 13.270117187499991], + [9.615917968750011, 12.810644531249991], + [9.201562500000023, 12.821484375], + [8.750585937500006, 12.908154296874997], + [8.4560546875, 13.059667968749991], + [8.095019531250017, 13.291162109374994], + [7.955761718750011, 13.32275390625], + [7.788671875, 13.337890625], + [7.056738281250006, 13.000195312499997], + [6.804296875, 13.107666015625], + [6.2998046875, 13.658789062499991], + [6.184277343750011, 13.66367187499999], + [5.838183593750017, 13.765380859375], + [5.491992187500017, 13.872851562500003], + [5.415820312500017, 13.859179687500003], + [5.361621093750017, 13.836865234374997], + [5.241894531250011, 13.757226562499994], + [4.664843750000017, 13.733203125], + [4.147558593750006, 13.457714843749997], + [3.947851562500006, 12.775048828124994], + [3.646679687500011, 12.529980468749997], + [3.595410156250011, 11.6962890625], + [2.805273437500006, 12.383837890624989], + [2.366015625000017, 12.221923828125], + [2.38916015625, 11.897070312499991], + [2.072949218750011, 12.309375], + [2.226269531250011, 12.466064453125], + [2.104589843750006, 12.701269531249991], + [1.56494140625, 12.635400390624994], + [0.9873046875, 13.041894531249994], + [0.988476562500011, 13.36484375], + [1.201171875, 13.357519531249991], + [0.6181640625, 13.703417968750003], + [0.42919921875, 13.972119140624997], + [0.382519531250011, 14.245800781249997], + [0.163867187500017, 14.497216796874994], + [0.217480468750011, 14.911474609374991], + [0.947460937500011, 14.982128906249997], + [1.300195312500023, 15.272265625], + [3.001074218750006, 15.340966796874994], + [3.06015625, 15.427197265624997], + [3.504296875000023, 15.356347656249994], + [3.5205078125, 15.483105468749997], + [3.842968750000011, 15.701708984375003], + [4.121289062500011, 16.357714843750003], + [4.234667968750017, 16.996386718750003], + [4.227636718750006, 19.142773437499997], + [5.836621093750011, 19.479150390624994], + [7.481738281250017, 20.873095703125003], + [11.967871093750006, 23.517871093750003], + [13.48125, 23.18017578125], + [14.215527343750011, 22.619677734375003], + [14.97900390625, 22.99619140624999] + ] + ] + }, + "properties": { "name": "Niger", "childNum": 1 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [7.30078125, 4.418164062500026], + [7.140429687500017, 4.395117187500034], + [7.227343750000045, 4.527343749999972], + [7.30078125, 4.418164062500026] + ] + ], + [ + [ + [6.804296875, 13.107666015625], + [7.056738281250006, 13.00019531250004], + [7.788671875, 13.337890625], + [7.955761718750011, 13.322753906250028], + [8.095019531250045, 13.29116210937498], + [8.750585937500034, 12.908154296875026], + [9.20156250000008, 12.82148437500004], + [9.615917968750011, 12.810644531249963], + [10.184667968750063, 13.270117187499963], + [10.229589843749977, 13.281005859375043], + [10.475878906250074, 13.330224609375037], + [10.958886718750051, 13.371533203125011], + [12.463183593750017, 13.09375], + [12.654785156250057, 13.3265625], + [13.426953125000068, 13.701757812499963], + [13.606347656250023, 13.704589843750014], + [13.932324218750011, 13.258496093749997], + [14.06396484375, 13.078515625], + [14.160058593750023, 12.612792968749986], + [14.184863281250017, 12.447216796874997], + [14.272851562500023, 12.356494140624989], + [14.518945312500051, 12.298242187500023], + [14.619726562500063, 12.150976562500048], + [14.559765625000011, 11.492285156249963], + [14.20234375000004, 11.268164062499963], + [14.143261718750068, 11.248535156250043], + [14.056738281250034, 11.245019531250037], + [13.981445312500057, 11.21186523437504], + [13.892089843750057, 11.140087890624983], + [13.699902343749983, 10.873144531250048], + [13.53535156250004, 10.605078124999963], + [13.414550781250028, 10.171435546874989], + [13.269921875000051, 10.036181640624974], + [13.198730468750028, 9.563769531250003], + [12.929492187500074, 9.426269531249972], + [12.87568359375004, 9.303515625000017], + [12.80654296875008, 8.886621093749994], + [12.7822265625, 8.817871093750014], + [12.651562500000011, 8.667773437499989], + [12.40351562500004, 8.59555664062502], + [12.311328125000074, 8.419726562499989], + [12.2333984375, 8.282324218749977], + [12.016015625000051, 7.589746093750009], + [11.809179687500006, 7.345068359374991], + [11.767382812500017, 7.272265624999989], + [11.861425781249977, 7.11640625000004], + [11.657519531250017, 6.951562500000023], + [11.580078125000057, 6.88886718750004], + [11.551660156250023, 6.697265625], + [11.153320312500057, 6.437939453125011], + [11.1064453125, 6.457714843750054], + [11.032519531250045, 6.697900390625037], + [10.954199218750006, 6.7765625], + [10.60625, 7.063085937500006], + [10.413183593750006, 6.877734375], + [10.293066406250034, 6.876757812499974], + [10.205468750000051, 6.891601562499986], + [10.185546874999972, 6.91279296875004], + [10.167773437500017, 6.959179687499983], + [10.143554687500057, 6.99643554687502], + [10.038867187500045, 6.921386718750014], + [9.874218750000068, 6.803271484375017], + [9.82070312500008, 6.783935546874986], + [9.779882812500034, 6.760156250000023], + [9.725585937499972, 6.65], + [9.659960937500017, 6.531982421874986], + [9.490234375, 6.418652343749997], + [8.997167968750006, 5.917724609375], + [8.715625, 5.046875], + [8.514843750000068, 4.724707031250034], + [8.23378906250008, 4.907470703124972], + [8.293066406250006, 4.557617187500014], + [7.644238281250068, 4.525341796875011], + [7.530761718750028, 4.655175781249994], + [7.284375, 4.547656250000031], + [7.076562500000051, 4.716162109374991], + [7.15468750000008, 4.514404296875], + [6.92324218750008, 4.390673828125017], + [6.767675781250006, 4.724707031250034], + [6.860351562500057, 4.373339843750045], + [6.633007812500011, 4.340234375000051], + [6.579980468750051, 4.475976562499994], + [6.554589843750023, 4.34140625000002], + [6.263671875, 4.309423828124991], + [6.270996093749972, 4.432128906250028], + [6.173339843749972, 4.277392578125031], + [5.970703125, 4.338574218749983], + [5.587792968750051, 4.647216796874972], + [5.448144531250023, 4.945849609374974], + [5.383300781250057, 5.129003906249977], + [5.475976562500023, 5.153857421874989], + [5.370019531250023, 5.195019531250026], + [5.367968750000045, 5.337744140624963], + [5.549707031250023, 5.474218749999963], + [5.385839843750034, 5.401757812500037], + [5.199218750000028, 5.533544921874977], + [5.456640624999977, 5.61171875], + [5.327343750000011, 5.707519531249986], + [5.112402343750034, 5.64155273437504], + [4.861035156250068, 6.026318359374997], + [4.431347656250011, 6.348583984375026], + [3.450781249999977, 6.427050781250017], + [3.71699218750004, 6.597949218750017], + [3.430175781250057, 6.525], + [3.335546875000063, 6.396923828125011], + [2.706445312500051, 6.369238281249963], + [2.735644531250045, 6.595703125], + [2.753710937499989, 6.661767578124966], + [2.774609374999983, 6.711718750000017], + [2.752929687500028, 6.771630859374966], + [2.731738281250045, 6.852832031249989], + [2.721386718750068, 6.980273437500017], + [2.75673828125008, 7.067919921875017], + [2.750488281250057, 7.39506835937496], + [2.765820312500068, 7.422509765625051], + [2.783984375000045, 7.443408203125045], + [2.78515625, 7.476855468750017], + [2.703125, 8.371826171875], + [2.774804687500023, 9.048535156250026], + [3.044921875, 9.08383789062502], + [3.325195312499972, 9.778466796875051], + [3.60205078125, 10.004541015625009], + [3.646582031250006, 10.408984374999989], + [3.771777343750017, 10.417626953124966], + [3.83447265625, 10.607421875], + [3.7568359375, 10.76875], + [3.71640625, 11.07958984375], + [3.695312499999972, 11.12031250000004], + [3.63886718750004, 11.176855468750006], + [3.487792968749972, 11.395410156250037], + [3.490527343750017, 11.499218750000054], + [3.55390625000004, 11.631884765624989], + [3.595410156250068, 11.696289062500057], + [3.664746093750068, 11.762451171875028], + [3.646679687500011, 12.529980468749983], + [3.947851562500006, 12.775048828124994], + [4.147558593750006, 13.457714843749983], + [4.664843750000045, 13.733203124999974], + [5.241894531250011, 13.757226562499994], + [5.361621093750074, 13.836865234375054], + [5.415820312500017, 13.859179687499974], + [5.491992187500074, 13.872851562500003], + [6.2998046875, 13.658789062500006], + [6.804296875, 13.107666015625] + ] + ] + ] + }, + "properties": { "name": "Nigeria", "childNum": 2 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [-83.6419921875, 10.917236328125], + [-83.71293945312499, 10.785888671875], + [-83.91928710937499, 10.7353515625], + [-84.6341796875, 11.045605468749997], + [-84.9091796875, 10.9453125], + [-85.5841796875, 11.189453125], + [-85.7443359375, 11.06210937499999], + [-87.670166015625, 12.965673828124991], + [-87.58505859374999, 13.043310546874991], + [-87.42436523437499, 12.921142578125], + [-87.33725585937499, 12.979248046875], + [-87.05917968749999, 12.991455078125], + [-87.00932617187499, 13.0078125], + [-86.918212890625, 13.223583984374997], + [-86.87353515625, 13.266503906249994], + [-86.792138671875, 13.27978515625], + [-86.72929687499999, 13.284375], + [-86.710693359375, 13.313378906249994], + [-86.76352539062499, 13.63525390625], + [-86.77060546874999, 13.69873046875], + [-86.758984375, 13.746142578125003], + [-86.733642578125, 13.763476562500003], + [-86.61025390625, 13.774853515624997], + [-86.376953125, 13.755664062500003], + [-86.33173828125, 13.770068359375003], + [-86.238232421875, 13.899462890625003], + [-86.15122070312499, 13.994580078124997], + [-86.0892578125, 14.037207031249991], + [-86.04038085937499, 14.050146484374991], + [-85.9837890625, 13.965673828124991], + [-85.78671875, 13.844433593749997], + [-85.75341796875, 13.85205078125], + [-85.73393554687499, 13.858691406250003], + [-85.727734375, 13.876074218749991], + [-85.731201171875, 13.931835937499997], + [-85.68193359374999, 13.982568359374994], + [-85.20834960937499, 14.311816406250003], + [-85.059375, 14.582958984374997], + [-84.86044921874999, 14.809765625], + [-84.645947265625, 14.661083984374997], + [-84.53764648437499, 14.633398437499991], + [-83.635498046875, 14.876416015624997], + [-83.5365234375, 14.977001953124997], + [-83.4150390625, 15.008056640625], + [-83.15751953124999, 14.993066406249994], + [-83.18535156249999, 14.956396484374991], + [-83.21591796874999, 14.932373046875], + [-83.27988281249999, 14.812792968750003], + [-83.344384765625, 14.902099609375], + [-83.413720703125, 14.825341796874994], + [-83.29921875, 14.7490234375], + [-83.187744140625, 14.340087890625], + [-83.4123046875, 13.99648437499999], + [-83.567333984375, 13.3203125], + [-83.5109375, 12.411816406249997], + [-83.627197265625, 12.459326171874991], + [-83.59335937499999, 12.713085937499997], + [-83.75424804687499, 12.501953125], + [-83.680419921875, 12.024316406249994], + [-83.7671875, 12.059277343749997], + [-83.82890624999999, 11.861035156249997], + [-83.70458984375, 11.824560546874991], + [-83.6517578125, 11.642041015624997], + [-83.86787109375, 11.300048828125], + [-83.6419921875, 10.917236328125] + ] + ] + }, + "properties": { "name": "Nicaragua", "childNum": 1 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [-169.80341796875, -19.0830078125], + [-169.94833984375, -19.072851562500006], + [-169.834033203125, -18.96601562500001], + [-169.80341796875, -19.0830078125] + ] + ] + }, + "properties": { "name": "Niue", "childNum": 1 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [-68.205810546875, 12.144580078124989], + [-68.25434570312495, 12.032080078124977], + [-68.36923828125, 12.301953124999983], + [-68.205810546875, 12.144580078124989] + ] + ], + [ + [ + [4.226171875000034, 51.38647460937503], + [3.902050781250011, 51.20766601562502], + [3.43251953125008, 51.24575195312505], + [3.35009765625, 51.37768554687503], + [4.226171875000034, 51.38647460937503] + ] + ], + [ + [ + [3.94912109375008, 51.73945312500001], + [4.07509765625008, 51.648779296875006], + [3.699023437500017, 51.70991210937501], + [3.94912109375008, 51.73945312500001] + ] + ], + [ + [ + [4.886132812500023, 53.07070312500005], + [4.70917968750004, 53.036035156249994], + [4.886425781249983, 53.18330078124998], + [4.886132812500023, 53.07070312500005] + ] + ], + [ + [ + [4.226171875000034, 51.38647460937503], + [3.448925781250068, 51.54077148437503], + [3.743945312500017, 51.596044921875006], + [4.27412109375004, 51.47163085937498], + [4.004785156250051, 51.595849609374966], + [4.182617187500057, 51.61030273437498], + [3.946875, 51.810546875], + [4.482812500000023, 52.30917968749998], + [4.76875, 52.941308593749966], + [5.061230468750068, 52.96064453125001], + [5.532031250000074, 53.268701171874966], + [6.062207031250068, 53.407080078125006], + [6.816210937500045, 53.44116210937503], + [7.197265625000028, 53.28227539062499], + [7.033007812500045, 52.65136718749997], + [6.710742187500045, 52.61787109374998], + [6.748828125000074, 52.464013671874994], + [7.035156250000057, 52.38022460937498], + [6.724511718749994, 52.080224609374966], + [6.800390625, 51.96738281249998], + [5.948730468750057, 51.80268554687501], + [6.198828125000034, 51.45], + [6.129980468750034, 51.14741210937501], + [5.857519531250034, 51.030126953125006], + [6.048437500000034, 50.90488281250006], + [5.993945312500017, 50.75043945312504], + [5.693554687500011, 50.774755859375006], + [5.796484375000034, 51.153076171875], + [5.214160156250045, 51.278955078124966], + [5.03095703125004, 51.46909179687498], + [4.226171875000034, 51.38647460937503] + ] + ], + [ + [ + [5.325781250000063, 53.38574218750003], + [5.190234375000074, 53.39179687500001], + [5.582617187500063, 53.438085937500034], + [5.325781250000063, 53.38574218750003] + ] + ] + ] + }, + "properties": { + "name": "Netherlands", + "childNum": 6, + "cp": [5.0752777, 52.358465] + } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [5.085839843750023, 60.30756835937501], + [5.089062500000068, 60.188769531250045], + [4.95722656250004, 60.44726562500006], + [5.085839843750023, 60.30756835937501] + ] + ], + [ + [ + [4.958691406250068, 61.084570312500034], + [4.79902343750004, 61.08271484375001], + [4.861621093749989, 61.19384765625], + [4.958691406250068, 61.084570312500034] + ] + ], + [ + [ + [8.10273437500004, 63.33759765625004], + [7.804003906250017, 63.413916015625034], + [8.073535156250045, 63.47080078124998], + [8.10273437500004, 63.33759765625004] + ] + ], + [ + [ + [8.470800781250063, 63.66713867187502], + [8.287109375000028, 63.68715820312502], + [8.764648437500057, 63.804638671874955], + [8.78652343750008, 63.703466796875034], + [8.470800781250063, 63.66713867187502] + ] + ], + [ + [ + [11.2314453125, 64.865869140625], + [10.739843750000034, 64.87031250000001], + [11.02099609375, 64.97871093749995], + [11.2314453125, 64.865869140625] + ] + ], + [ + [ + [12.971777343750063, 67.87412109375], + [12.824023437500074, 67.82124023437498], + [13.068066406250068, 68.07133789062505], + [12.971777343750063, 67.87412109375] + ] + ], + [ + [ + [13.872851562500045, 68.26533203125004], + [14.096777343750034, 68.218603515625], + [13.229394531250051, 67.995361328125], + [13.300195312499994, 68.16044921875007], + [13.872851562500045, 68.26533203125004] + ] + ], + [ + [ + [15.207128906250006, 68.943115234375], + [15.222070312500023, 68.61630859375003], + [14.404687500000051, 68.663232421875], + [15.037792968750068, 69.00053710937507], + [15.207128906250006, 68.943115234375] + ] + ], + [ + [ + [15.760351562500006, 68.56123046875001], + [16.328906250000017, 68.87631835937498], + [16.519238281250068, 68.63300781249998], + [15.975292968750011, 68.402490234375], + [14.257519531249983, 68.19077148437503], + [15.412597656250028, 68.61582031250003], + [15.483007812500006, 69.04345703125003], + [16.04804687500001, 69.30205078125002], + [15.760351562500006, 68.56123046875001] + ] + ], + [ + [ + [17.503027343750034, 69.59624023437502], + [18.004101562500068, 69.50498046874998], + [17.95068359375003, 69.19814453125], + [17.487890625000063, 69.19682617187499], + [17.08251953124997, 69.013671875], + [16.81044921875008, 69.07070312499997], + [17.001757812500045, 69.36191406250006], + [17.36083984375003, 69.38149414062497], + [17.503027343750034, 69.59624023437502] + ] + ], + [ + [ + [29.956152343750006, 69.79677734375002], + [29.766210937500006, 69.76752929687501], + [29.835839843749994, 69.90556640625005], + [29.956152343750006, 69.79677734375002] + ] + ], + [ + [ + [20.779199218750023, 70.08974609375002], + [20.46425781250005, 70.0765625], + [20.492773437500006, 70.20332031249995], + [20.78603515625008, 70.21953124999999], + [20.779199218750023, 70.08974609375002] + ] + ], + [ + [ + [19.25507812500001, 70.06640625000006], + [19.607812500000023, 70.019140625], + [19.334765625000074, 69.82026367187501], + [18.784765625000034, 69.57900390624997], + [18.12988281250003, 69.557861328125], + [18.34931640625004, 69.76787109374999], + [18.67402343750004, 69.78164062500002], + [19.13271484375005, 70.24414062500003], + [19.25507812500001, 70.06640625000006] + ] + ], + [ + [ + [19.76748046875005, 70.21669921875002], + [20.005957031250034, 70.07622070312502], + [19.599023437499994, 70.26616210937507], + [19.76748046875005, 70.21669921875002] + ] + ], + [ + [ + [23.615332031250034, 70.54931640625003], + [23.15917968750003, 70.28261718750005], + [22.941015625000063, 70.444580078125], + [23.546679687500017, 70.61708984374997], + [23.615332031250034, 70.54931640625003] + ] + ], + [ + [ + [24.01757812500003, 70.56738281249997], + [23.716601562500074, 70.561865234375], + [23.778417968750063, 70.74736328125005], + [24.01757812500003, 70.56738281249997] + ] + ], + [ + [ + [23.440527343750063, 70.81577148437503], + [22.8291015625, 70.54155273437505], + [22.358691406250017, 70.514794921875], + [21.99453125000008, 70.65712890624997], + [23.440527343750063, 70.81577148437503] + ] + ], + [ + [ + [30.869726562500006, 69.78344726562506], + [30.860742187499994, 69.53842773437503], + [30.18017578124997, 69.63583984375], + [30.08730468750005, 69.43286132812503], + [29.38828125, 69.29814453125005], + [28.96582031250003, 69.02197265625], + [28.846289062500006, 69.17690429687502], + [29.33339843750005, 69.47299804687503], + [29.14160156250003, 69.67143554687505], + [27.747851562500045, 70.06484375], + [27.127539062500063, 69.90649414062497], + [26.525390625000057, 69.91503906250003], + [26.07246093750004, 69.69155273437497], + [25.748339843750017, 68.99013671875], + [24.94140625000003, 68.59326171875006], + [23.85400390625, 68.80590820312503], + [23.324023437500017, 68.64897460937502], + [22.410937500000074, 68.719873046875], + [21.59375, 69.273583984375], + [21.06611328125001, 69.21411132812497], + [21.065722656250017, 69.04174804687503], + [20.622167968750006, 69.036865234375], + [20.116699218750057, 69.02089843750005], + [20.348046875000023, 68.84873046875003], + [19.969824218750063, 68.35639648437501], + [18.303027343750045, 68.55541992187497], + [17.91669921875001, 67.96489257812502], + [17.324609375000023, 68.10380859374999], + [16.783593750000023, 67.89501953125], + [16.12744140625, 67.42583007812507], + [16.40351562500004, 67.05498046875002], + [15.422949218750006, 66.48984374999998], + [15.483789062500051, 66.30595703124999], + [14.543261718750045, 66.12934570312498], + [14.47968750000004, 65.30146484374998], + [13.650292968750023, 64.58154296874997], + [14.077636718750028, 64.464013671875], + [14.141210937500006, 64.17353515624998], + [13.960546875000063, 64.01401367187498], + [13.203515625000023, 64.07509765625], + [12.792773437500017, 64], + [12.175195312500051, 63.595947265625], + [11.999902343750051, 63.29169921875001], + [12.303515625000074, 62.28559570312501], + [12.155371093750006, 61.720751953125045], + [12.88076171875008, 61.35229492187506], + [12.706054687500028, 61.059863281250074], + [12.29414062500004, 61.00268554687506], + [12.588671874999989, 60.450732421875045], + [12.486132812500074, 60.10678710937506], + [11.680761718750034, 59.59228515625003], + [11.798144531250074, 59.28989257812498], + [11.64277343750004, 58.92607421875002], + [11.470703125000057, 58.909521484375034], + [11.388281250000063, 59.036523437499966], + [10.834472656250028, 59.18393554687498], + [10.595312500000063, 59.764550781249966], + [10.179394531250068, 59.00927734375003], + [9.842578125000017, 58.95849609374997], + [9.557226562500063, 59.11269531250002], + [9.65693359375004, 58.97119140624997], + [8.166113281250063, 58.145312500000045], + [7.0048828125, 58.024218750000074], + [6.877050781250006, 58.15073242187498], + [6.590527343750068, 58.09731445312502], + [6.659863281250068, 58.26274414062499], + [5.706835937500074, 58.52363281250001], + [5.55556640625008, 58.975195312500006], + [6.099023437500023, 58.87026367187502], + [6.363281250000028, 59.00092773437501], + [6.099414062500017, 58.951953125000074], + [5.88916015625, 59.097949218750045], + [5.951855468750068, 59.299072265625], + [6.415332031250074, 59.547119140625], + [5.17324218750008, 59.16254882812498], + [5.2421875, 59.564306640625034], + [5.472460937500017, 59.713085937499955], + [5.77216796875004, 59.66093749999999], + [6.216601562499989, 59.818359375], + [5.73046875, 59.863085937500045], + [6.348730468750006, 60.35297851562504], + [6.57363281250008, 60.36059570312497], + [6.526855468750057, 60.152929687500034], + [6.995703125, 60.511962890625], + [6.1533203125, 60.34624023437499], + [5.145800781250074, 59.63881835937502], + [5.205664062500006, 60.087939453125045], + [5.688574218749977, 60.12319335937502], + [5.285839843750011, 60.20571289062505], + [5.13710937500008, 60.445605468750074], + [5.648339843750051, 60.68798828124997], + [5.244042968750023, 60.569580078125], + [5.115820312500006, 60.63598632812503], + [5.008593750000017, 61.038183593750006], + [6.777832031250028, 61.142431640625006], + [7.038671875000063, 60.952929687500045], + [7.040136718750006, 61.091162109375034], + [7.604492187500057, 61.210546875000034], + [7.34658203125008, 61.30058593749999], + [7.442578125000011, 61.43461914062502], + [7.173535156250011, 61.16596679687501], + [6.599902343750017, 61.28964843749998], + [6.383496093750068, 61.133886718750034], + [5.451269531250034, 61.10234375000002], + [5.106738281250017, 61.187548828125045], + [5.002734375000074, 61.43359375], + [5.338671875000017, 61.485498046874994], + [4.927832031249977, 61.71069335937506], + [4.93007812499999, 61.878320312499994], + [6.01582031250004, 61.7875], + [6.730761718750045, 61.86977539062505], + [5.266894531250045, 61.935595703125045], + [5.143164062500063, 62.159912109375], + [5.908300781249977, 62.41601562500003], + [6.083496093750057, 62.349609375], + [6.580078125000057, 62.407275390625045], + [6.692382812500028, 62.46806640624999], + [6.136132812500051, 62.40747070312497], + [6.352929687500051, 62.61113281249999], + [7.653125, 62.56401367187499], + [7.538378906250074, 62.67207031249998], + [8.045507812500006, 62.77124023437503], + [6.734960937500006, 62.72070312500003], + [6.940429687500028, 62.930468750000045], + [7.571875, 63.09951171875002], + [8.100585937500028, 63.090966796874966], + [8.623144531250006, 62.84624023437502], + [8.158007812500017, 63.16152343750005], + [8.635546875000045, 63.34233398437502], + [8.360742187500023, 63.498876953125034], + [8.576171875000028, 63.60117187499998], + [9.135839843750006, 63.593652343749966], + [9.156054687500045, 63.459326171875034], + [9.696875, 63.624560546875045], + [10.020996093750028, 63.39082031250004], + [10.76015625000008, 63.461279296875006], + [10.725292968750068, 63.625], + [11.370703125000034, 63.804833984374994], + [11.175585937500074, 63.89887695312498], + [11.457617187500063, 64.00297851562505], + [11.306640625000028, 64.04887695312499], + [10.91425781250004, 63.92109374999998], + [10.934863281250045, 63.770214843749955], + [10.055078125000051, 63.5126953125], + [9.567285156250051, 63.70615234374998], + [10.565625, 64.418310546875], + [11.523828125000051, 64.744384765625], + [11.632910156250063, 64.81391601562495], + [11.296777343750051, 64.75478515625], + [11.489355468750034, 64.975830078125], + [12.15966796875, 65.178955078125], + [12.508398437499977, 65.09941406250005], + [12.915527343750057, 65.33925781249997], + [12.417578125000063, 65.18408203124997], + [12.133886718749977, 65.27915039062498], + [12.68886718750008, 65.90219726562498], + [13.033105468750051, 65.95625], + [12.783789062500063, 66.10043945312506], + [14.034179687500057, 66.29755859374998], + [13.118847656250011, 66.23066406250004], + [13.211425781250028, 66.64082031250001], + [13.959472656250028, 66.79433593750002], + [13.651562500000011, 66.90708007812498], + [14.10878906250008, 67.11923828125003], + [15.41572265625004, 67.20244140625002], + [14.441699218750045, 67.27138671875005], + [14.961914062500057, 67.57426757812502], + [15.59443359375004, 67.34853515625005], + [15.691503906250006, 67.52138671875], + [15.24873046875004, 67.6021484375], + [15.303906250000011, 67.76528320312502], + [14.854687500000068, 67.66333007812506], + [14.798925781250063, 67.80932617187503], + [15.13427734375, 67.97270507812502], + [15.621386718750017, 67.94829101562502], + [15.316015624999977, 68.06875], + [16.007910156250006, 68.22871093750004], + [16.312304687500017, 67.88144531249998], + [16.20380859375001, 68.31674804687503], + [17.552832031250063, 68.42626953125006], + [16.51435546875004, 68.53256835937503], + [18.101464843749994, 69.15629882812499], + [18.259765625, 69.47060546875], + [18.915917968750023, 69.33559570312502], + [18.614453125000068, 69.49057617187498], + [19.197265625000057, 69.74785156249999], + [19.722460937500017, 69.78164062500002], + [19.64150390625005, 69.42402343750001], + [20.324218750000057, 69.94531249999997], + [20.054492187500074, 69.33266601562497], + [20.486718750000023, 69.54208984375], + [20.739453124999983, 69.52050781250003], + [20.622070312500057, 69.91391601562498], + [21.163085937500057, 69.88950195312498], + [21.432910156250045, 70.01318359375006], + [21.974707031250034, 69.83457031249998], + [21.355761718750045, 70.23339843749997], + [22.321972656250068, 70.264501953125], + [22.684570312500057, 70.374755859375], + [23.35390625000008, 69.98339843750003], + [23.3291015625, 70.20722656249995], + [24.420019531250034, 70.70200195312503], + [24.263476562500017, 70.82631835937497], + [24.658007812500017, 71.00102539062505], + [25.264648437500057, 70.843505859375], + [25.768164062500063, 70.85317382812502], + [25.043847656250023, 70.10903320312502], + [26.66132812500004, 70.93974609374999], + [26.585058593750034, 70.41000976562498], + [26.989355468750063, 70.51137695312502], + [27.183691406250034, 70.74404296875], + [27.546484375000063, 70.80400390625005], + [27.23525390625008, 70.94721679687498], + [27.59707031250005, 71.09130859375003], + [28.392285156250068, 70.97529296875004], + [27.898046875, 70.67792968750001], + [28.271777343750017, 70.66796875000003], + [28.192968750000034, 70.24858398437505], + [28.83154296875003, 70.86396484375001], + [29.7375, 70.646826171875], + [30.065136718750097, 70.70297851562498], + [30.944140625000017, 70.27441406249997], + [30.262988281250074, 70.12470703125004], + [28.804296875000063, 70.09252929687506], + [29.601367187500017, 69.97675781249998], + [29.792089843750063, 69.727880859375], + [30.08828125, 69.71757812500005], + [30.237597656250017, 69.86220703125002], + [30.428320312500006, 69.722265625], + [30.869726562500006, 69.78344726562506] + ] + ], + [ + [ + [25.58632812500005, 71.14208984375], + [26.13378906250003, 70.99580078125004], + [25.582031250000057, 70.960791015625], + [25.31494140625, 71.03413085937504], + [25.58632812500005, 71.14208984375] + ] + ], + [ + [ + [-8.953564453124983, 70.83916015625002], + [-8.001367187499966, 71.17768554687495], + [-8.002099609374937, 71.04125976562497], + [-8.953564453124983, 70.83916015625002] + ] + ], + [ + [ + [19.219335937500006, 74.39101562500002], + [18.86123046875008, 74.51416015624997], + [19.182910156250045, 74.51791992187503], + [19.219335937500006, 74.39101562500002] + ] + ], + [ + [ + [21.60810546875004, 78.59570312499997], + [22.04316406250004, 78.57695312500007], + [22.29951171875004, 78.22817382812497], + [23.451953125000074, 78.14946289062502], + [23.11669921874997, 77.99150390624999], + [24.901855468750057, 77.756591796875], + [22.55371093750003, 77.26665039062502], + [22.685351562500045, 77.55351562500002], + [20.928125, 77.45966796874998], + [21.653125, 77.92353515624998], + [20.22792968750005, 78.47783203125005], + [21.60810546875004, 78.59570312499997] + ] + ], + [ + [ + [11.250292968750017, 78.610693359375], + [12.116406250000068, 78.232568359375], + [11.121289062500011, 78.46328125], + [10.558203125000063, 78.90292968750003], + [11.250292968750017, 78.610693359375] + ] + ], + [ + [ + [29.047070312500068, 78.91206054687504], + [29.69667968750005, 78.90473632812495], + [27.88906250000005, 78.8521484375], + [28.511132812500023, 78.96733398437502], + [29.047070312500068, 78.91206054687504] + ] + ], + [ + [ + [16.786718750000034, 79.90673828125], + [17.834570312499977, 79.80004882812503], + [17.66875, 79.38593750000004], + [18.39736328125008, 79.60517578125001], + [18.677832031250006, 79.26171875000003], + [19.893554687500057, 79.05620117187499], + [20.61103515625004, 79.10664062499998], + [21.388769531250034, 78.74042968749998], + [19.67675781250003, 78.60957031249995], + [16.700488281250045, 76.57929687499995], + [14.365820312500034, 77.23447265625003], + [13.995703125000034, 77.50820312500002], + [14.69501953125004, 77.525048828125], + [14.920800781250023, 77.68881835937506], + [17.033300781250006, 77.79770507812503], + [16.91406250000003, 77.89799804687505], + [14.089941406250063, 77.77138671875], + [13.680566406250051, 78.028125], + [14.307226562500006, 78.00507812500001], + [15.783886718750011, 78.32705078125005], + [17.00292968750003, 78.36938476562497], + [16.44863281250008, 78.50356445312502], + [16.78261718750008, 78.66362304687505], + [15.417382812500023, 78.47324218749998], + [15.384179687500023, 78.77119140625001], + [15.01630859375004, 78.63012695312497], + [14.689257812500017, 78.720947265625], + [14.638281250000034, 78.41459960937502], + [14.110449218750063, 78.27089843749997], + [13.150195312499989, 78.2375], + [11.365429687500011, 78.95039062500004], + [12.323437500000068, 78.91425781249995], + [12.083984375000028, 79.26752929687498], + [11.579785156250068, 79.28349609375005], + [11.208105468750034, 79.12963867187503], + [10.737597656250017, 79.52016601562502], + [10.804003906250045, 79.79877929687504], + [11.150390625, 79.71699218749998], + [11.702343750000011, 79.82060546875005], + [12.287792968750068, 79.713134765625], + [12.279980468749983, 79.81596679687507], + [13.692871093749972, 79.860986328125], + [13.777539062500011, 79.71528320312498], + [12.555371093750068, 79.56948242187502], + [13.333789062500017, 79.57480468750006], + [14.029589843750017, 79.34414062500005], + [14.59365234375008, 79.79873046875002], + [16.34375, 78.97612304687502], + [15.816113281250011, 79.68183593750001], + [16.245703125000034, 80.04946289062502], + [16.786718750000034, 79.90673828125] + ] + ], + [ + [ + [32.52597656250006, 80.119140625], + [31.48193359374997, 80.10791015625003], + [33.62929687499999, 80.21743164062497], + [32.52597656250006, 80.119140625] + ] + ], + [ + [ + [20.897851562500023, 80.24995117187501], + [22.289746093749983, 80.04921874999997], + [22.450781250000034, 80.40224609375005], + [23.00800781250004, 80.473974609375], + [23.114550781250074, 80.18696289062498], + [24.29755859375004, 80.36040039062505], + [26.86083984375, 80.16000976562498], + [27.19863281250008, 79.90659179687506], + [25.641210937500034, 79.40302734374995], + [23.94775390625, 79.19428710937498], + [22.903710937500023, 79.23066406250001], + [22.865527343750045, 79.41186523437497], + [20.861132812500017, 79.39785156249997], + [20.128222656250074, 79.489599609375], + [19.674609375000045, 79.591162109375], + [20.784082031250023, 79.74858398437502], + [18.725, 79.7607421875], + [18.25537109375, 79.92919921875003], + [18.855957031250057, 80.03662109375], + [17.91689453125005, 80.14311523437502], + [19.343359375000063, 80.11640624999998], + [19.733300781249994, 80.47783203124999], + [20.897851562500023, 80.24995117187501] + ] + ] + ] + }, + "properties": { "name": "Norway", "childNum": 27 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [87.984375, 27.133935546874994], + [87.9931640625, 27.086083984374994], + [88.11103515625001, 26.928466796875], + [88.1572265625, 26.807324218749997], + [88.16152343750002, 26.724804687499997], + [88.11152343750001, 26.58642578125], + [88.05488281250001, 26.430029296875], + [88.02695312500003, 26.39501953125], + [87.9951171875, 26.382373046874996], + [87.28740234374999, 26.360302734374997], + [87.01640624999999, 26.555419921875], + [86.70136718750001, 26.43505859375], + [86.00732421875, 26.649365234374997], + [85.79453125000003, 26.604150390624994], + [85.7373046875, 26.63974609375], + [85.6484375, 26.829003906249994], + [85.56845703125003, 26.83984375], + [85.29296875, 26.741015625], + [85.19179687500002, 26.766552734374997], + [84.68535156249999, 27.041015625], + [84.65380859375, 27.091699218749994], + [84.65478515625, 27.203662109374996], + [84.64072265625003, 27.249853515625], + [84.61015624999999, 27.298681640625], + [84.48085937500002, 27.348193359374996], + [84.22978515624999, 27.42783203125], + [84.09101562500001, 27.491357421874994], + [83.82880859375001, 27.377832031249994], + [83.74697265625002, 27.395947265624997], + [83.55166015625002, 27.456347656249996], + [83.44716796875002, 27.46533203125], + [83.38398437500001, 27.44482421875], + [83.36943359374999, 27.41025390625], + [83.28974609375001, 27.370996093749994], + [82.7333984375, 27.518994140624997], + [82.71083984375002, 27.5966796875], + [82.67734375000003, 27.6734375], + [82.6298828125, 27.687060546874996], + [82.45136718750001, 27.671826171874997], + [82.28769531250003, 27.756542968749997], + [82.11191406250003, 27.864941406249997], + [82.03701171875002, 27.900585937499997], + [81.98769531250002, 27.913769531249997], + [81.94521484375002, 27.899267578125], + [81.896875, 27.874462890624997], + [81.85263671875003, 27.867089843749994], + [81.1689453125, 28.335009765624996], + [80.58701171875003, 28.649609375], + [80.51787109374999, 28.665185546874994], + [80.49580078125001, 28.635791015624996], + [80.47910156250003, 28.604882812499994], + [80.41855468750003, 28.612011718749997], + [80.32480468750003, 28.66640625], + [80.2265625, 28.723339843749997], + [80.07070312500002, 28.83017578125], + [80.05166015625002, 28.8703125], + [80.08457031250003, 28.994189453124996], + [80.13046875000003, 29.100390625], + [80.16953125000003, 29.124316406249996], + [80.23300781250003, 29.194628906249996], + [80.25595703125003, 29.318017578124994], + [80.2548828125, 29.42333984375], + [80.31689453125, 29.572070312499996], + [80.40185546875, 29.730273437499996], + [80.54902343750001, 29.899804687499994], + [80.81992187500003, 30.119335937499997], + [80.84814453125, 30.13974609375], + [80.90761718750002, 30.171923828124996], + [80.96611328124999, 30.180029296875], + [81.17714843750002, 30.039892578125], + [81.25507812500001, 30.093310546874996], + [81.41718750000001, 30.337597656249997], + [81.64189453124999, 30.3875], + [81.85488281250002, 30.36240234375], + [82.04335937500002, 30.3267578125], + [82.220703125, 30.063867187499994], + [83.15546875000001, 29.612646484375], + [83.58349609375, 29.18359375], + [83.93593750000002, 29.279492187499997], + [84.02197265625, 29.253857421874997], + [84.10136718749999, 29.219970703125], + [84.12783203125002, 29.156298828124996], + [84.17558593749999, 29.036376953125], + [84.22871093750001, 28.911767578124994], + [84.796875, 28.560205078124994], + [84.85507812500003, 28.553613281249994], + [85.06914062499999, 28.609667968749996], + [85.12636718750002, 28.60263671875], + [85.15908203125002, 28.592236328124997], + [85.16015625, 28.571875], + [85.12148437500002, 28.484277343749994], + [85.08857421875001, 28.372265625], + [85.12246093750002, 28.315966796874996], + [85.21210937500001, 28.292626953124994], + [85.41064453125, 28.276025390624994], + [85.67832031250003, 28.27744140625], + [85.75947265625001, 28.220654296874997], + [85.84023437500002, 28.1353515625], + [85.92167968749999, 27.989697265624997], + [85.9541015625, 27.92822265625], + [85.99453125000002, 27.910400390625], + [86.06416015625001, 27.934716796874994], + [86.07871093750003, 28.08359375], + [86.13701171874999, 28.11435546875], + [86.21796875000001, 28.0220703125], + [86.32861328125, 27.959521484374996], + [86.40869140625, 27.928662109374997], + [86.51689453124999, 27.963525390624994], + [86.55449218749999, 28.085205078125], + [86.61445312500001, 28.10302734375], + [86.69052734375003, 28.094921875], + [86.71962890625002, 28.070654296875], + [86.75039062500002, 28.0220703125], + [86.93378906250001, 27.968457031249997], + [87.02011718750003, 27.928662109374997], + [87.14140624999999, 27.838330078124997], + [87.29072265625001, 27.821923828124994], + [87.62255859375, 27.815185546875], + [87.86074218750002, 27.886083984375], + [88.10976562500002, 27.87060546875], + [87.984375, 27.133935546874994] + ] + ] + }, + "properties": { "name": "Nepal", "childNum": 1 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [169.17822265624997, -52.497265625], + [169.12753906250006, -52.570312499999964], + [169.02177734375002, -52.49541015624998], + [169.17822265624997, -52.497265625] + ] + ], + [ + [ + [166.22109375, -50.76152343749997], + [166.2428710937501, -50.84570312499998], + [165.88916015624997, -50.80771484374996], + [166.10136718750002, -50.538964843750016], + [166.26748046875005, -50.558593750000014], + [166.22109375, -50.76152343749997] + ] + ], + [ + [ + [168.14492187500005, -46.862207031249966], + [168.04316406250004, -46.9326171875], + [168.2409179687501, -47.070019531250026], + [167.52197265624997, -47.258691406249994], + [167.80078125000003, -46.90654296875002], + [167.78398437500007, -46.699804687500006], + [167.9557617187501, -46.69443359374998], + [168.14492187500005, -46.862207031249966] + ] + ], + [ + [ + [166.97949218749997, -45.17968750000003], + [167.02265625000004, -45.299804687499986], + [166.89267578125012, -45.24052734374999], + [166.97949218749997, -45.17968750000003] + ] + ], + [ + [ + [-176.17763671874997, -43.74033203124998], + [-176.38173828124997, -43.86679687499998], + [-176.40737304687497, -43.7609375], + [-176.516552734375, -43.78476562499996], + [-176.33359375000003, -44.02529296875004], + [-176.51552734374997, -44.11660156249998], + [-176.62934570312495, -44.036132812500014], + [-176.55512695312504, -43.85195312499998], + [-176.84765625000003, -43.82392578125004], + [-176.56611328124995, -43.717578125000045], + [-176.17763671874997, -43.74033203124998] + ] + ], + [ + [ + [173.91464843750018, -40.86367187500004], + [173.78085937500012, -40.921777343749966], + [173.964453125, -40.71298828124998], + [173.91464843750018, -40.86367187500004] + ] + ], + [ + [ + [173.11533203125006, -41.27929687499997], + [173.94716796875005, -40.92412109375], + [173.79785156250003, -41.271972656249986], + [173.99941406250005, -40.99326171874996], + [174.30253906249996, -41.019531249999986], + [174.03857421875003, -41.24189453125], + [174.37011718750009, -41.1037109375], + [174.06933593750009, -41.42949218750002], + [174.08369140625015, -41.67080078124998], + [174.2831054687501, -41.740625], + [173.22119140624997, -42.976562499999986], + [172.62402343749997, -43.27246093749996], + [172.73476562500005, -43.35478515625003], + [172.52666015625002, -43.464746093749966], + [172.69345703125006, -43.444335937499986], + [172.80703125000005, -43.620996093749994], + [173.07324218750003, -43.676171874999966], + [173.065625, -43.87460937499998], + [172.50273437500002, -43.84365234374998], + [172.48037109375, -43.726660156250034], + [172.29658203125004, -43.867871093750026], + [172.035546875, -43.70175781250002], + [172.17978515625006, -43.895996093749986], + [171.24072265624997, -44.26416015625003], + [171.14628906250002, -44.9123046875], + [170.99902343750003, -44.91142578124999], + [171.11328125000003, -45.03925781250001], + [170.7005859375, -45.68427734374997], + [170.77626953125005, -45.870898437499974], + [170.4191406250001, -45.94101562499996], + [169.68662109375006, -46.55166015625002], + [169.34228515625003, -46.62050781250001], + [168.38212890625007, -46.60537109374995], + [168.1891601562501, -46.362207031249966], + [167.8419921875001, -46.366210937499986], + [167.539453125, -46.14853515624996], + [167.36894531250007, -46.24150390624999], + [166.73154296875006, -46.19785156249998], + [166.91669921875004, -45.95722656249998], + [166.64990234374997, -46.04169921875004], + [166.71796875000004, -45.88935546875001], + [166.49316406249997, -45.9638671875], + [166.48828124999997, -45.83183593750002], + [167.0033203125, -45.71210937500004], + [166.79765625000002, -45.64560546874999], + [166.99082031250012, -45.531738281249986], + [166.73398437500012, -45.54355468749999], + [166.74306640625, -45.46845703124997], + [166.91992187499997, -45.40791015624998], + [166.86923828125006, -45.31123046875], + [167.15566406250005, -45.410937499999974], + [167.23007812500012, -45.29033203125], + [167.02587890624997, -45.12363281249998], + [167.25947265625004, -45.08222656249997], + [167.19453125000004, -44.963476562500034], + [167.41074218750006, -44.82792968750003], + [167.4662109375, -44.958300781250045], + [167.48496093750006, -44.77138671874998], + [167.78701171875, -44.59501953125002], + [167.90898437500002, -44.66474609375001], + [167.85654296875012, -44.50068359374998], + [168.45742187500005, -44.030566406250045], + [169.17890625000004, -43.9130859375], + [169.16953125000006, -43.77705078125], + [169.83388671875, -43.53701171875004], + [170.24023437499997, -43.163867187500045], + [170.39609375000012, -43.18222656249996], + [170.30283203125012, -43.10761718750004], + [170.61181640625003, -43.091796875000014], + [170.5236328125001, -43.00898437500001], + [170.6654296875, -42.961230468749974], + [170.73525390625005, -43.029785156249986], + [170.96992187500004, -42.71835937499996], + [171.01171875000003, -42.88505859374999], + [171.027734375, -42.696093750000045], + [171.31337890625005, -42.460156250000026], + [171.48623046875, -41.7947265625], + [171.94804687500002, -41.53867187499996], + [172.13945312500002, -40.947265625000014], + [172.640625, -40.51826171875001], + [172.94365234375007, -40.51875], + [172.73261718750004, -40.54375], + [172.70439453125002, -40.6677734375], + [172.988671875, -40.84824218749999], + [173.11533203125006, -41.27929687499997] + ] + ], + [ + [ + [175.54316406250015, -36.279296874999986], + [175.34619140624997, -36.217773437499986], + [175.3895507812501, -36.07773437499996], + [175.54316406250015, -36.279296874999986] + ] + ], + [ + [ + [173.26943359375, -34.93476562499998], + [173.44785156250012, -34.844335937500034], + [173.47265625000003, -34.94697265624998], + [174.10400390625003, -35.14287109375002], + [174.1431640625, -35.3], + [174.32031250000003, -35.246679687500034], + [174.58066406250018, -35.78554687500004], + [174.39580078124996, -35.79736328124996], + [174.8021484375, -36.30947265625001], + [174.72246093750007, -36.84121093749998], + [175.29951171875004, -36.99326171874996], + [175.38535156250012, -37.206933593749966], + [175.54248046874997, -37.2013671875], + [175.46083984375005, -36.475683593750034], + [175.77216796875004, -36.73515625], + [176.10839843749997, -37.64511718749998], + [177.27402343750012, -37.993457031249974], + [178.0091796875, -37.55488281249998], + [178.53623046875006, -37.69208984375004], + [178.26767578125006, -38.551171875], + [177.976171875, -38.72226562500005], + [177.90878906250012, -39.23955078125], + [177.52294921875003, -39.07382812499999], + [177.07675781250012, -39.22177734375002], + [176.93925781249996, -39.55527343750002], + [177.10986328125009, -39.673144531250045], + [176.8421875000001, -40.15781250000002], + [175.98291015625003, -41.21328125000002], + [175.30976562499998, -41.610644531249974], + [175.16562500000012, -41.41738281249995], + [174.88134765624997, -41.42402343749997], + [174.8656250000001, -41.223046874999966], + [174.63535156250012, -41.28945312499999], + [175.1625, -40.62158203125], + [175.25410156250004, -40.28935546875], + [175.1559570312501, -40.11494140625], + [175.00927734375009, -39.95214843749996], + [173.93437500000013, -39.50908203125002], + [173.76367187499997, -39.31875], + [173.84433593750006, -39.13935546875001], + [174.39843749999997, -38.96259765624998], + [174.59736328124998, -38.78505859374995], + [174.80166015625005, -37.895507812500014], + [174.92802734375002, -37.80449218750003], + [174.58583984374994, -37.09775390625002], + [174.73427734375, -37.21523437499998], + [174.92890625000004, -37.084765625000045], + [174.78203125000013, -36.94375], + [174.47558593750009, -36.94189453124997], + [174.1888671875001, -36.492285156250034], + [174.4015625000001, -36.60195312499999], + [174.39277343750004, -36.24003906249999], + [174.26787109375002, -36.16308593750003], + [174.25371093749996, -36.24912109374998], + [174.03642578125013, -36.12246093750001], + [173.91445312499994, -35.908691406249986], + [173.91728515625002, -36.01816406249999], + [174.16640624999994, -36.327636718749986], + [174.05468749999991, -36.35976562500004], + [173.41220703125012, -35.542578125], + [173.62617187500004, -35.31914062499996], + [173.3763671875001, -35.50009765624996], + [173.31396484375003, -35.44335937499996], + [173.11669921874997, -35.205273437500026], + [173.190625, -35.01621093749998], + [172.70595703125005, -34.45517578124998], + [173.04394531249997, -34.429101562499994], + [172.96376953125, -34.53515625000003], + [173.26943359375, -34.93476562499998] + ] + ] + ] + }, + "properties": { "name": "New Zealand", "childNum": 9 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [58.722070312499994, 20.21875], + [58.640917968750074, 20.210693359375057], + [58.64121093750006, 20.33735351562501], + [58.884375, 20.680566406250023], + [58.95078125000006, 20.516162109375017], + [58.722070312499994, 20.21875] + ] + ], + [ + [ + [56.38798828125002, 24.97919921875004], + [56.640625, 24.4703125], + [57.12304687500003, 23.980712890625], + [58.773046875, 23.517187499999977], + [59.42939453125004, 22.660839843749955], + [59.82324218749997, 22.50898437500004], + [59.8, 22.21992187500001], + [59.37148437500005, 21.498828125000017], + [58.89570312500004, 21.11279296874997], + [58.47421875000006, 20.406884765624966], + [58.20898437500003, 20.423974609374994], + [58.245019531249994, 20.599218749999977], + [58.16943359375003, 20.58950195312505], + [57.86181640624997, 20.24414062500003], + [57.71416015625002, 19.678417968749983], + [57.81162109375006, 19.01708984374997], + [56.825976562500074, 18.753515625], + [56.3834960937501, 17.98798828125001], + [55.479101562500006, 17.84326171875003], + [55.25537109375003, 17.58564453125004], + [55.275195312500074, 17.320898437500006], + [55.06416015625004, 17.038916015625034], + [54.06816406250002, 17.005517578124966], + [53.60986328124997, 16.75996093750004], + [53.08564453125004, 16.648388671874955], + [51.977636718750006, 18.996142578125074], + [54.97734375000002, 19.995947265625006], + [55.64101562499999, 22.001855468749994], + [55.185839843750074, 22.7041015625], + [55.1999023437501, 23.034765625000034], + [55.53164062499999, 23.81904296875001], + [55.4684570312501, 23.94111328125001], + [55.98515625000002, 24.063378906249966], + [55.92861328125005, 24.215136718750074], + [55.76083984375006, 24.24267578125], + [55.795703125000074, 24.868115234374955], + [56.00058593750006, 24.953222656249977], + [56.06386718750005, 24.73876953125], + [56.38798828125002, 24.97919921875004] + ] + ], + [ + [ + [56.29785156250003, 25.650683593750045], + [56.144628906250006, 25.690527343750006], + [56.16748046875003, 26.047460937499977], + [56.08046875, 26.06264648437505], + [56.41308593749997, 26.351171875000034], + [56.29785156250003, 25.650683593750045] + ] + ] + ] + }, + "properties": { "name": "Oman", "childNum": 3 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [73.08961802927895, 36.86435907947333], + [73.08203125000107, 36.43949943991182], + [72.31128647748268, 35.77290936638241], + [73.13410859949555, 34.82510160558277], + [73.19895048106557, 33.88770931468204], + [74.00809389139292, 33.25375789331485], + [73.98984375, 33.22119140625], + [74.30361328125002, 32.991796875], + [74.30546875000002, 32.810449218749994], + [74.35458984375, 32.768701171874994], + [74.58828125000002, 32.753222656249996], + [74.632421875, 32.770898437499994], + [74.66328125000001, 32.757666015625], + [74.64335937500002, 32.607714843749996], + [74.68574218750001, 32.493798828124994], + [74.78886718750002, 32.4578125], + [74.9873046875, 32.462207031249996], + [75.33349609375, 32.279199218749994], + [75.25410156250001, 32.14033203125], + [75.13876953125, 32.104785156249996], + [75.07148437500001, 32.08935546875], + [74.73945312500001, 31.948828125], + [74.6357421875, 31.88974609375], + [74.55556640625002, 31.818554687499997], + [74.5259765625, 31.76513671875], + [74.50996093750001, 31.712939453124996], + [74.58183593750002, 31.52392578125], + [74.59394531250001, 31.465380859374996], + [74.53496093750002, 31.261376953124994], + [74.51767578125, 31.185595703124996], + [74.6103515625, 31.112841796874996], + [74.62578125000002, 31.06875], + [74.6328125, 31.03466796875], + [74.509765625, 30.959667968749997], + [74.38037109375, 30.893408203125], + [74.33935546875, 30.8935546875], + [74.00898437500001, 30.519677734374994], + [73.89931640625002, 30.435351562499996], + [73.88271484375002, 30.3521484375], + [73.92460937500002, 30.28164062499999], + [73.93339843750002, 30.222070312499994], + [73.88652343750002, 30.162011718749994], + [73.8091796875, 30.093359375], + [73.38164062500002, 29.934375], + [72.9033203125, 29.028759765624997], + [72.34189453125, 28.751904296874997], + [72.2919921875, 28.697265625], + [72.128515625, 28.346337890624994], + [71.94804687500002, 28.177294921874996], + [71.88886718750001, 28.0474609375], + [71.87031250000001, 27.9625], + [71.54296875, 27.869873046875], + [71.18476562500001, 27.831640625], + [70.87490234375002, 27.714453125], + [70.79794921875, 27.709619140624994], + [70.69160156250001, 27.768994140624997], + [70.62910156250001, 27.937451171874997], + [70.40371093750002, 28.025048828124994], + [70.24433593750001, 27.934130859374996], + [70.1939453125, 27.894873046875], + [70.14453125, 27.849023437499994], + [70.0498046875, 27.694726562499994], + [69.89628906250002, 27.4736328125], + [69.56796875, 27.174609375], + [69.47001953125002, 26.804443359375], + [70.11464843750002, 26.548046875], + [70.14921875000002, 26.347558593749994], + [70.1001953125, 25.910058593749994], + [70.2646484375, 25.70654296875], + [70.3251953125, 25.685742187499997], + [70.44853515625002, 25.681347656249997], + [70.505859375, 25.685302734375], + [70.56953125000001, 25.705957031249994], + [70.6484375, 25.666943359374997], + [70.65205078125001, 25.422900390625003], + [70.87773437500002, 25.06298828125], + [70.95087890625001, 24.8916015625], + [71.02070312500001, 24.75766601562499], + [71.0478515625, 24.687744140625], + [71.00234375000002, 24.65390625], + [70.97636718750002, 24.61875], + [70.96982421875, 24.571875], + [71.04531250000002, 24.429980468750003], + [71.04404296875, 24.400097656249997], + [70.98281250000002, 24.361035156249997], + [70.928125, 24.362353515625003], + [70.88623046875, 24.34375], + [70.80507812500002, 24.261962890625], + [70.76728515625001, 24.245410156250003], + [70.71630859375, 24.237988281249997], + [70.65947265625002, 24.24609375], + [70.57929687500001, 24.279052734375], + [70.55585937500001, 24.331103515625003], + [70.5650390625, 24.385791015625003], + [70.54677734375002, 24.41831054687499], + [70.2890625, 24.35629882812499], + [70.0982421875, 24.2875], + [69.80517578125, 24.165234375], + [69.71621093750002, 24.172607421875], + [69.63417968750002, 24.22519531249999], + [69.5591796875, 24.273095703124994], + [69.44345703125, 24.275390625], + [69.23505859375001, 24.268261718749997], + [69.11953125000002, 24.26865234374999], + [69.05156250000002, 24.286328125], + [68.98457031250001, 24.273095703124994], + [68.90078125000002, 24.292431640624997], + [68.86347656250001, 24.266503906249994], + [68.82832031250001, 24.26401367187499], + [68.78115234375002, 24.313720703125], + [68.75898437500001, 24.30722656249999], + [68.73964843750002, 24.2919921875], + [68.728125, 24.265625], + [68.72412109375, 23.96469726562499], + [68.48867187500002, 23.967236328124997], + [68.38125, 23.950878906249997], + [68.28251953125002, 23.927978515625], + [68.1650390625, 23.857324218749994], + [68.11552734375002, 23.753369140624997], + [67.8599609375, 23.90268554687499], + [67.66845703125, 23.810986328124997], + [67.309375, 24.1748046875], + [67.171484375, 24.756103515625], + [66.70302734375002, 24.8609375], + [66.69863281250002, 25.226318359375], + [66.32421875, 25.601806640625], + [66.13115234375002, 25.49326171874999], + [66.46767578125002, 25.4453125], + [64.77666015625002, 25.307324218749997], + [64.65898437500002, 25.18408203125], + [64.059375, 25.40292968749999], + [63.556640625, 25.353173828124994], + [63.49140625000001, 25.210839843749994], + [61.56689453125, 25.186328125], + [61.587890625, 25.20234375], + [61.61542968750001, 25.2861328125], + [61.64013671875, 25.584619140624994], + [61.67138671875, 25.6923828125], + [61.66181640625001, 25.751269531250003], + [61.66865234375001, 25.768994140624997], + [61.73769531250002, 25.82109375], + [61.75439453125, 25.84335937499999], + [61.78076171875, 25.995849609375], + [61.80996093750002, 26.165283203125], + [61.842382812500006, 26.225927734375], + [62.1259765625, 26.368994140625], + [62.239355468750006, 26.35703125], + [62.31230468750002, 26.490869140624994], + [63.168066406250006, 26.665576171874996], + [63.186132812500006, 26.837597656249997], + [63.24160156250002, 26.86474609375], + [63.25039062500002, 26.879248046875], + [63.24208984375002, 27.077685546874996], + [63.30517578125, 27.124560546874996], + [63.30156250000002, 27.15146484375], + [63.25625, 27.207910156249994], + [63.19609375000002, 27.243945312499996], + [63.16679687500002, 27.252490234374996], + [62.75273437500002, 27.265625], + [62.782324218750006, 27.800537109375], + [62.7625, 28.202050781249994], + [61.88984375000001, 28.546533203124994], + [61.15214843750002, 29.542724609375], + [61.0341796875, 29.663427734375], + [60.843359375, 29.858691406249996], + [61.22441406250002, 29.749414062499994], + [62.0009765625, 29.530419921874994], + [62.4765625, 29.408349609374994], + [63.56757812500001, 29.497998046874997], + [64.09873046875, 29.391943359375], + [64.39375, 29.544335937499994], + [65.09550781250002, 29.559472656249994], + [66.23125, 29.86572265625], + [66.346875, 30.802783203124996], + [66.82929687500001, 31.263671875], + [67.45283203125001, 31.234619140625], + [67.737890625, 31.343945312499997], + [67.57822265625, 31.506494140624994], + [68.16103515625002, 31.802978515625], + [68.59765625, 31.802978515625], + [68.86894531250002, 31.634228515624997], + [69.279296875, 31.936816406249996], + [69.24140625000001, 32.433544921875], + [69.5015625, 33.020068359374996], + [70.26113281250002, 33.289013671875], + [69.8896484375, 34.007275390625], + [70.65400390625001, 33.952294921874994], + [71.05156250000002, 34.049707031249994], + [71.095703125, 34.369433593749996], + [70.965625, 34.53037109375], + [71.62050781250002, 35.183007812499994], + [71.57197265625001, 35.546826171875], + [71.18505859375, 36.04208984375], + [71.23291015625, 36.12177734375], + [72.24980468750002, 36.734716796875], + [73.08961802927895, 36.86435907947333] + ] + ] + }, + "properties": { "name": "Pakistan", "childNum": 1 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [-81.60327148437497, 7.332812499999989], + [-81.85205078125003, 7.453320312500026], + [-81.812158203125, 7.59238281250002], + [-81.72875976562494, 7.62119140625002], + [-81.60327148437497, 7.332812499999989] + ] + ], + [ + [ + [-78.89833984375002, 8.27426757812502], + [-78.960595703125, 8.435839843749989], + [-78.88325195312495, 8.460253906249989], + [-78.89833984375002, 8.27426757812502] + ] + ], + [ + [ + [-77.37421874999993, 8.65830078125002], + [-77.47851562499994, 8.498437500000037], + [-77.19599609374995, 7.972460937500003], + [-77.53828124999995, 7.56625976562502], + [-77.76191406249995, 7.698828125000034], + [-77.90117187499999, 7.229345703125048], + [-78.42158203124995, 8.060986328125011], + [-78.28735351562497, 8.091796874999972], + [-78.14189453125002, 8.386083984374977], + [-77.76054687499993, 8.133251953124983], + [-78.09946289062498, 8.496972656250009], + [-78.22304687500002, 8.396630859374994], + [-78.39921874999993, 8.505664062500003], + [-78.40986328124998, 8.35532226562502], + [-78.51406249999997, 8.628173828125], + [-79.08637695312495, 8.997167968750034], + [-79.50708007812494, 8.97006835937502], + [-79.68745117187493, 8.850976562500009], + [-79.81591796875, 8.639208984375031], + [-79.75043945312498, 8.595507812500017], + [-80.458984375, 8.213867187499972], + [-80.45810546875, 8.077050781249994], + [-80.01123046875, 7.500048828125031], + [-80.66669921874995, 7.225683593750006], + [-80.90122070312503, 7.277148437500017], + [-81.06386718749994, 7.89975585937502], + [-81.26840820312495, 7.625488281250014], + [-81.50415039062503, 7.721191406249972], + [-81.72763671875, 8.137548828124977], + [-82.15986328124995, 8.19482421875], + [-82.23544921874998, 8.311035156250057], + [-82.67954101562503, 8.321972656249969], + [-82.86611328124994, 8.246337890625014], + [-82.87934570312498, 8.07065429687502], + [-83.02734375, 8.337744140624991], + [-82.86162109374999, 8.453515625000037], + [-82.84477539062493, 8.489355468749963], + [-82.85571289062494, 8.635302734375031], + [-82.91704101562502, 8.740332031250034], + [-82.88198242187497, 8.805322265625037], + [-82.72783203125002, 8.916064453125031], + [-82.78305664062498, 8.990283203124974], + [-82.88134765625003, 9.055859375000011], + [-82.94033203124997, 9.060107421874989], + [-82.93984374999994, 9.449169921875026], + [-82.92504882812494, 9.469042968749989], + [-82.88896484374999, 9.481005859375017], + [-82.86015625, 9.511474609375014], + [-82.84399414062497, 9.570800781250014], + [-82.801025390625, 9.591796875000028], + [-82.64409179687502, 9.505859375000028], + [-82.56357421875003, 9.576660156249972], + [-82.50034179687503, 9.523242187500017], + [-82.37080078124993, 9.428564453124991], + [-82.33974609375, 9.209179687499983], + [-82.18813476562502, 9.191748046874977], + [-82.24418945312499, 9.031494140625014], + [-82.07788085937503, 8.93486328124996], + [-81.78022460937495, 8.957226562499983], + [-81.89448242187495, 9.140429687500003], + [-81.35478515624996, 8.78056640624996], + [-80.83867187499999, 8.887207031250014], + [-80.12709960937497, 9.20991210937504], + [-79.57729492187497, 9.597851562500026], + [-78.08276367187494, 9.236279296874997], + [-77.37421874999993, 8.65830078125002] + ] + ] + ] + }, + "properties": { "name": "Panama", "childNum": 3 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [-73.137353515625, -6.4658203125], + [-73.75810546874999, -6.90576171875], + [-73.79301757812499, -7.135058593750003], + [-73.758203125, -7.172753906250009], + [-73.72041015625, -7.309277343750011], + [-73.964306640625, -7.37890625], + [-73.95849609375, -7.506640625], + [-73.98173828124999, -7.535742187500006], + [-74.00205078124999, -7.556054687500009], + [-73.98173828124999, -7.585058593750006], + [-73.946875, -7.611230468750009], + [-73.89462890624999, -7.65478515625], + [-73.82207031249999, -7.738964843750011], + [-73.76689453124999, -7.753515625], + [-73.72041015625, -7.782519531250003], + [-73.73203125, -7.875390625], + [-73.54912109374999, -8.345800781250006], + [-73.39814453125, -8.458984375], + [-73.36040039062499, -8.479296875], + [-73.351708984375, -8.51416015625], + [-73.35673828124999, -8.566992187500006], + [-73.30244140625, -8.654003906250011], + [-73.203125, -8.719335937500006], + [-73.0705078125, -8.8828125], + [-72.9740234375, -8.9931640625], + [-72.970361328125, -9.1201171875], + [-73.08984375, -9.265722656250006], + [-73.209423828125, -9.411425781250003], + [-72.379052734375, -9.51015625], + [-72.181591796875, -10.003710937500003], + [-71.33940429687499, -9.988574218750003], + [-71.11528320312499, -9.852441406250009], + [-71.041748046875, -9.81875], + [-70.6369140625, -9.478222656250011], + [-70.60791015625, -9.463671875], + [-70.54111328124999, -9.4375], + [-70.57016601562499, -9.48984375], + [-70.592236328125, -9.54345703125], + [-70.59916992187499, -9.620507812500009], + [-70.642333984375, -11.01025390625], + [-70.59653320312499, -10.976855468750003], + [-70.53325195312499, -10.946875], + [-70.45087890625, -11.024804687500009], + [-70.39228515625, -11.05859375], + [-70.3419921875, -11.066699218750003], + [-70.29038085937499, -11.064257812500003], + [-70.22006835937499, -11.04765625], + [-70.06630859375, -10.982421875], + [-69.9603515625, -10.929882812500011], + [-69.839794921875, -10.933398437500003], + [-69.6740234375, -10.9541015625], + [-69.57861328125, -10.951757812500006], + [-68.68525390625, -12.501953125], + [-68.97861328124999, -12.880078125000011], + [-69.07412109375, -13.682812500000011], + [-68.87089843749999, -14.169726562500003], + [-69.35947265624999, -14.7953125], + [-69.37470703125, -14.962988281250006], + [-69.17246093749999, -15.236621093750003], + [-69.4208984375, -15.640625], + [-69.21757812499999, -16.14912109375001], + [-68.8427734375, -16.337890625], + [-69.03291015625, -16.47597656250001], + [-69.020703125, -16.6421875], + [-69.62485351562499, -17.2001953125], + [-69.645703125, -17.24853515625], + [-69.521923828125, -17.388964843750003], + [-69.510986328125, -17.46035156250001], + [-69.51108398437499, -17.5048828125], + [-69.5109375, -17.50605468750001], + [-69.58642578125, -17.5732421875], + [-69.684765625, -17.64980468750001], + [-69.85209960937499, -17.70380859375001], + [-69.80258789062499, -17.990234375], + [-69.92636718749999, -18.2060546875], + [-70.41826171874999, -18.34560546875001], + [-71.33696289062499, -17.68251953125001], + [-71.5322265625, -17.29433593750001], + [-72.46767578125, -16.708105468750006], + [-73.727685546875, -16.20166015625], + [-75.104248046875, -15.411914062500003], + [-75.533642578125, -14.89921875], + [-75.93388671874999, -14.63359375], + [-76.37646484375, -13.863085937500003], + [-76.259228515625, -13.802832031250006], + [-76.2236328125, -13.371191406250006], + [-76.83212890624999, -12.348730468750006], + [-77.152734375, -12.060351562500003], + [-77.2203125, -11.663378906250003], + [-77.633203125, -11.287792968750011], + [-77.736083984375, -10.83671875], + [-78.18559570312499, -10.089062500000011], + [-78.76225585937499, -8.616992187500003], + [-79.37724609374999, -7.835546875], + [-79.99497070312499, -6.768945312500009], + [-81.142041015625, -6.056738281250006], + [-81.164306640625, -5.875292968750003], + [-80.9306640625, -5.8408203125], + [-80.88193359374999, -5.635058593750003], + [-81.33662109375, -4.66953125], + [-81.283203125, -4.322265625], + [-80.503662109375, -3.49609375], + [-80.324658203125, -3.387890625000011], + [-80.24375, -3.576757812500006], + [-80.19414062499999, -3.905859375], + [-80.23051757812499, -3.924023437500011], + [-80.26689453124999, -3.948828125], + [-80.30327148437499, -4.005078125000011], + [-80.43720703125, -3.978613281250006], + [-80.49013671875, -4.010058593750003], + [-80.510009765625, -4.069531250000011], + [-80.49345703124999, -4.119140625], + [-80.4884765625, -4.16552734375], + [-80.453759765625, -4.205175781250006], + [-80.35288085937499, -4.20849609375], + [-80.44384765625, -4.335839843750009], + [-80.4884765625, -4.393652343750006], + [-80.47856445312499, -4.430078125], + [-80.42416992187499, -4.46142578125], + [-80.38349609375, -4.463671875], + [-80.293359375, -4.416796875], + [-80.1974609375, -4.31103515625], + [-80.13955078125, -4.296093750000011], + [-80.06352539062499, -4.327539062500009], + [-79.962890625, -4.390332031250011], + [-79.8451171875, -4.445898437500006], + [-79.797265625, -4.476367187500003], + [-79.71098632812499, -4.467578125], + [-79.63852539062499, -4.454882812500003], + [-79.57768554687499, -4.500585937500006], + [-79.51616210937499, -4.539160156250006], + [-79.501904296875, -4.670605468750011], + [-79.45576171875, -4.766210937500006], + [-79.3994140625, -4.840039062500011], + [-79.33095703125, -4.927832031250006], + [-79.26811523437499, -4.957617187500006], + [-79.186669921875, -4.958203125000011], + [-79.07626953124999, -4.990625], + [-79.03330078124999, -4.969140625], + [-78.995263671875, -4.908007812500003], + [-78.97539062499999, -4.873242187500011], + [-78.919189453125, -4.8583984375], + [-78.92578125, -4.770703125000011], + [-78.9076171875, -4.714453125], + [-78.8615234375, -4.6650390625], + [-78.68603515625, -4.562402343750009], + [-78.64799804687499, -4.248144531250006], + [-78.345361328125, -3.397363281250009], + [-78.240380859375, -3.472558593750009], + [-77.860595703125, -2.981640625000011], + [-76.6791015625, -2.562597656250006], + [-76.089794921875, -2.133105468750003], + [-75.570556640625, -1.53125], + [-75.42041015625, -0.962207031250003], + [-75.40805664062499, -0.92431640625], + [-75.24960937499999, -0.951855468750011], + [-75.259375, -0.590136718750003], + [-75.42470703125, -0.408886718750011], + [-75.49106445312499, -0.248339843750003], + [-75.56059570312499, -0.200097656250009], + [-75.63203125, -0.157617187500009], + [-75.62626953124999, -0.122851562500003], + [-75.340478515625, -0.1421875], + [-75.13837890625, -0.050488281250011], + [-74.8017578125, -0.200097656250009], + [-74.78046875, -0.24453125], + [-74.75537109375, -0.298632812500003], + [-74.691650390625, -0.335253906250003], + [-74.616357421875, -0.370019531250009], + [-74.555078125, -0.429882812500011], + [-74.5138671875, -0.470117187500009], + [-74.46518554687499, -0.517675781250006], + [-74.41787109375, -0.580664062500006], + [-74.334423828125, -0.850878906250003], + [-74.28388671875, -0.927832031250006], + [-74.24638671874999, -0.970605468750009], + [-74.05439453125, -1.028613281250003], + [-73.98681640625, -1.09814453125], + [-73.926953125, -1.125195312500011], + [-73.86318359375, -1.196679687500009], + [-73.664306640625, -1.248828125], + [-73.4962890625, -1.693066406250011], + [-73.19697265625, -1.830273437500011], + [-73.1544921875, -2.278222656250009], + [-72.9896484375, -2.339746093750009], + [-72.94111328125, -2.39404296875], + [-72.21845703125, -2.400488281250006], + [-71.98427734375, -2.3265625], + [-71.93247070312499, -2.288671875], + [-71.86728515624999, -2.227734375000011], + [-71.802734375, -2.166308593750003], + [-71.75253906249999, -2.152734375], + [-71.55947265625, -2.22421875], + [-71.39697265625, -2.334082031250006], + [-71.19638671874999, -2.313085937500006], + [-71.11337890624999, -2.245410156250003], + [-71.027294921875, -2.225781250000011], + [-70.96855468749999, -2.206835937500003], + [-70.70537109374999, -2.341992187500011], + [-70.64799804687499, -2.40576171875], + [-70.57587890625, -2.418261718750003], + [-70.29462890625, -2.552539062500003], + [-70.24443359374999, -2.606542968750006], + [-70.16474609375, -2.639843750000011], + [-70.095849609375, -2.658203125], + [-70.735107421875, -3.781542968750003], + [-70.5296875, -3.866406250000011], + [-70.48583984375, -3.869335937500011], + [-70.42109375, -3.849609375], + [-70.37919921874999, -3.81875], + [-70.339501953125, -3.814355468750009], + [-70.2984375, -3.84423828125], + [-70.24028320312499, -3.882714843750009], + [-70.16752929687499, -4.050195312500009], + [-70.0171875, -4.162011718750009], + [-69.96591796874999, -4.2359375], + [-69.97202148437499, -4.301171875], + [-70.00395507812499, -4.327246093750006], + [-70.05332031249999, -4.333105468750006], + [-70.12880859375, -4.28662109375], + [-70.23916015625, -4.301171875], + [-70.31689453125, -4.246972656250009], + [-70.34365234375, -4.193652343750003], + [-70.40463867187499, -4.150097656250011], + [-70.5306640625, -4.167578125], + [-70.72158203125, -4.158886718750011], + [-70.79951171875, -4.17333984375], + [-70.97368164062499, -4.350488281250009], + [-71.8447265625, -4.50439453125], + [-72.256787109375, -4.748925781250009], + [-72.35283203124999, -4.786035156250009], + [-72.468994140625, -4.901269531250009], + [-72.608349609375, -5.009570312500003], + [-72.69873046875, -5.0671875], + [-72.83193359375, -5.09375], + [-72.88706054687499, -5.122753906250011], + [-72.9798828125, -5.634863281250006], + [-73.16289062499999, -5.933398437500003], + [-73.209375, -6.028710937500009], + [-73.235546875, -6.0984375], + [-73.137353515625, -6.4658203125] + ] + ] + }, + "properties": { "name": "Peru", "childNum": 1 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [120.250390625, 5.256591796875043], + [119.82148437500004, 5.06953125000004], + [120.1652343750001, 5.332421875000037], + [120.250390625, 5.256591796875043] + ] + ], + [ + [ + [121.159375, 6.075634765625011], + [121.41103515625005, 5.939843749999966], + [121.29443359374997, 5.869970703125034], + [120.8763671875, 5.95263671875], + [121.159375, 6.075634765625011] + ] + ], + [ + [ + [122.09287109375012, 6.428320312500006], + [121.95917968750004, 6.415820312500045], + [121.83203125000003, 6.664062499999986], + [122.0583007812501, 6.740722656249972], + [122.32353515625002, 6.602246093750011], + [122.09287109375012, 6.428320312500006] + ] + ], + [ + [ + [122.93710937500006, 7.409130859374983], + [122.80468750000003, 7.315966796875017], + [122.82216796875, 7.428466796875014], + [122.93710937500006, 7.409130859374983] + ] + ], + [ + [ + [117.07988281250007, 7.883398437499977], + [117.02832031249997, 7.807519531249966], + [116.96953125000007, 7.894921875], + [116.9935546875, 8.050537109375014], + [117.07705078125, 8.069140624999974], + [117.07988281250007, 7.883398437499977] + ] + ], + [ + [ + [117.35527343750002, 8.21464843749996], + [117.28701171875, 8.191015625000034], + [117.28085937500006, 8.314990234374974], + [117.35527343750002, 8.21464843749996] + ] + ], + [ + [ + [124.80664062500003, 9.142626953125003], + [124.66582031250002, 9.132324218750043], + [124.65332031250003, 9.225830078125], + [124.80664062500003, 9.142626953125003] + ] + ], + [ + [ + [123.69765625000005, 9.237304687500028], + [123.61445312500004, 9.103320312499989], + [123.49345703125002, 9.192089843750054], + [123.69765625000005, 9.237304687500028] + ] + ], + [ + [ + [126.00595703125006, 9.320947265625009], + [126.19335937499997, 9.276708984374963], + [126.30458984375, 8.952050781249994], + [126.13955078125005, 8.59565429687504], + [126.36533203125012, 8.483886718750014], + [126.45869140625004, 8.20283203125004], + [126.43535156250002, 7.832812499999974], + [126.57011718750002, 7.677246093749986], + [126.58154296875003, 7.247753906249969], + [126.1920898437501, 6.852539062500014], + [126.18935546875, 6.309667968749991], + [125.82441406250004, 7.333300781249989], + [125.68925781250007, 7.263037109374977], + [125.38066406250007, 6.689941406250014], + [125.58847656250012, 6.465771484374997], + [125.66796874999997, 5.97866210937498], + [125.34648437500002, 5.598974609374977], + [125.23154296875006, 6.069531250000011], + [124.92734375000006, 5.875341796874977], + [124.21279296875, 6.233251953124977], + [124.078125, 6.404443359375037], + [123.98525390625, 6.993701171875003], + [124.20664062500006, 7.396435546874983], + [123.66582031250002, 7.817773437500023], + [123.49306640625, 7.80791015624996], + [123.39091796875007, 7.407519531250017], + [123.09667968749997, 7.700439453125], + [122.8429687500001, 7.529296875000043], + [122.79179687500002, 7.72246093749996], + [122.61621093749997, 7.763134765624983], + [122.14248046875, 6.949658203124997], + [121.96425781250005, 6.96821289062504], + [121.92460937500002, 7.199511718750003], + [122.24335937500004, 7.945117187500031], + [122.91113281250003, 8.156445312499997], + [123.05058593750002, 8.433935546875048], + [123.43457031249997, 8.70332031250004], + [123.84921875000006, 8.432714843749977], + [123.79941406250006, 8.049121093749989], + [124.19765625, 8.229541015624974], + [124.40488281250006, 8.599853515625014], + [124.7311523437501, 8.562988281250043], + [124.86894531250002, 8.972265625000034], + [125.141015625, 8.86875], + [125.20966796875004, 9.027148437500017], + [125.49873046875004, 9.014746093749977], + [125.47128906250006, 9.756787109374983], + [126.00595703125006, 9.320947265625009] + ] + ], + [ + [ + [126.059375, 9.766210937500034], + [125.99121093750003, 9.838525390625023], + [126.07382812500006, 10.059228515625051], + [126.1725585937501, 9.79995117187498], + [126.059375, 9.766210937500034] + ] + ], + [ + [ + [124.59384765625006, 9.787207031249963], + [124.1224609375, 9.599316406249969], + [123.93564453125012, 9.623974609375011], + [123.81718750000002, 9.817382812499986], + [124.17285156250003, 10.135205078124983], + [124.33574218750002, 10.159912109375043], + [124.57714843749997, 10.026708984374991], + [124.59384765625006, 9.787207031249963] + ] + ], + [ + [ + [125.69023437500007, 9.914453125000037], + [125.49482421875004, 10.118701171875003], + [125.66679687500002, 10.440136718750026], + [125.69023437500007, 9.914453125000037] + ] + ], + [ + [ + [119.91621093750004, 10.485986328125037], + [119.79316406250004, 10.455273437499997], + [119.85205078124997, 10.64013671875], + [120.00839843750012, 10.570117187500031], + [119.91621093750004, 10.485986328125037] + ] + ], + [ + [ + [122.64951171875012, 10.472705078125003], + [122.53837890625002, 10.424951171875037], + [122.5375, 10.607568359375023], + [122.70126953125006, 10.740625], + [122.64951171875012, 10.472705078125003] + ] + ], + [ + [ + [123.13085937500003, 9.064111328124994], + [122.99472656250006, 9.058837890624986], + [122.8666015625, 9.319824218750043], + [122.5625, 9.482812500000037], + [122.39951171875006, 9.823046874999989], + [122.47148437500007, 9.961523437500034], + [122.85556640625006, 10.0869140625], + [122.81699218750012, 10.503808593750023], + [122.98330078125, 10.886621093750037], + [123.25664062500007, 10.99394531249996], + [123.51064453125005, 10.923046875], + [123.5675781250001, 10.780761718750057], + [123.16201171875, 9.864257812500028], + [123.1498046875, 9.606152343750026], + [123.32050781250004, 9.27294921875], + [123.13085937500003, 9.064111328124994] + ] + ], + [ + [ + [123.37031250000004, 9.449609375000023], + [123.38623046874997, 9.967089843750017], + [124.03886718750002, 11.273535156249991], + [124.00498046875012, 10.40009765625004], + [123.70048828125007, 10.128320312500009], + [123.37031250000004, 9.449609375000023] + ] + ], + [ + [ + [123.75703125000004, 11.28330078125002], + [123.815625, 11.15073242187502], + [123.73671875, 11.151464843749991], + [123.75703125000004, 11.28330078125002] + ] + ], + [ + [ + [117.31113281250012, 8.439599609375051], + [117.21855468750007, 8.367285156249963], + [117.34990234375002, 8.713574218749997], + [119.22382812500004, 10.477294921875043], + [119.30566406250003, 10.9736328125], + [119.55332031250012, 11.31352539062496], + [119.52666015625002, 10.953173828125003], + [119.68691406250005, 10.500341796875034], + [119.36933593750004, 10.327294921875037], + [119.19150390625012, 10.061083984374989], + [118.78212890625005, 9.91611328125002], + [118.4349609375, 9.256005859375009], + [117.31113281250012, 8.439599609375051] + ] + ], + [ + [ + [119.86142578125006, 11.52534179687504], + [119.83066406250012, 11.375683593750011], + [119.72998046874997, 11.431933593750017], + [119.86142578125006, 11.52534179687504] + ] + ], + [ + [ + [124.574609375, 11.343066406250031], + [124.92998046875002, 11.372851562499974], + [125.02656250000004, 11.21171875], + [125.01318359374997, 10.785693359374989], + [125.26845703125005, 10.307714843750048], + [125.14257812499997, 10.189453125000028], + [124.9875, 10.36757812499998], + [125.02656250000004, 10.033105468749966], + [124.78076171874997, 10.16806640625002], + [124.78671875000012, 10.781396484375009], + [124.66269531250006, 10.961962890625017], + [124.44550781250004, 10.923583984375014], + [124.33066406250012, 11.535205078125003], + [124.574609375, 11.343066406250031] + ] + ], + [ + [ + [124.60839843750003, 11.492187500000043], + [124.48349609375006, 11.485839843749986], + [124.36035156250003, 11.665917968749994], + [124.5109375000001, 11.687109375000048], + [124.60839843750003, 11.492187500000043] + ] + ], + [ + [ + [122.49619140625006, 11.615087890625034], + [122.83808593750004, 11.595654296874983], + [122.89453125000003, 11.44130859374998], + [123.15830078125012, 11.53554687499999], + [123.11953125, 11.286816406250026], + [122.8029296875001, 10.99003906249996], + [122.76992187500005, 10.823828125000034], + [121.95400390625, 10.444384765625003], + [122.10351562499997, 11.64291992187502], + [121.91601562499997, 11.854345703125006], + [122.02919921875005, 11.895410156250023], + [122.49619140625006, 11.615087890625034] + ] + ], + [ + [ + [120.03876953125004, 11.703320312499969], + [119.94492187500006, 11.690722656249989], + [119.86093750000006, 11.953955078124963], + [120.03593750000002, 11.917236328125028], + [120.03876953125004, 11.703320312499969] + ] + ], + [ + [ + [120.1, 12.167675781249983], + [120.22822265625004, 12.219824218750034], + [120.31455078125012, 12.012402343749969], + [120.01054687500002, 12.008251953125011], + [119.88574218749997, 12.299853515625003], + [120.1, 12.167675781249983] + ] + ], + [ + [ + [122.65449218750004, 12.309033203125011], + [122.42294921875006, 12.455078125], + [122.60361328125006, 12.49160156249998], + [122.65449218750004, 12.309033203125011] + ] + ], + [ + [ + [125.23955078125002, 12.527880859375003], + [125.32021484375, 12.321826171875031], + [125.53564453125003, 12.191406250000028], + [125.49179687500006, 11.594335937499977], + [125.57353515625002, 11.238232421874997], + [125.73564453125002, 11.049609375000017], + [125.23339843749997, 11.145068359375017], + [125.03427734375012, 11.341259765625026], + [124.91699218750003, 11.558398437500031], + [124.99501953125, 11.764941406250003], + [124.445703125, 12.152783203124969], + [124.29472656250007, 12.569335937500014], + [125.23955078125002, 12.527880859375003] + ] + ], + [ + [ + [123.71660156250007, 12.287353515625028], + [124.04033203125002, 11.966796875], + [124.04550781250012, 11.752441406250028], + [123.47373046875006, 12.21665039062502], + [123.15781250000012, 11.925634765624963], + [123.23642578125012, 12.583496093750057], + [123.71660156250007, 12.287353515625028] + ] + ], + [ + [ + [122.09404296875002, 12.354882812500023], + [122.01396484375002, 12.105615234375037], + [121.9232421875, 12.331298828125014], + [122.00156250000006, 12.598535156250009], + [122.14501953124997, 12.652636718750017], + [122.09404296875002, 12.354882812500023] + ] + ], + [ + [ + [123.77539062499997, 12.453906250000031], + [123.77910156250002, 12.366259765625031], + [123.62148437500005, 12.67490234375002], + [123.77539062499997, 12.453906250000031] + ] + ], + [ + [ + [123.28183593750006, 12.85341796874998], + [123.36718750000003, 12.70083007812498], + [122.95751953124997, 13.107177734374986], + [123.28183593750006, 12.85341796874998] + ] + ], + [ + [ + [120.70439453125002, 13.479492187499986], + [121.20273437500006, 13.432324218749969], + [121.52275390625007, 13.131201171874991], + [121.540625, 12.63818359375], + [121.39433593750002, 12.300585937499974], + [121.23671875000005, 12.218798828125003], + [120.92216796875002, 12.51162109374998], + [120.65136718749997, 13.169140625], + [120.33847656250012, 13.412353515624986], + [120.40126953125, 13.517041015624997], + [120.70439453125002, 13.479492187499986] + ] + ], + [ + [ + [121.91484375000002, 13.540332031250031], + [122.11455078125002, 13.463183593750031], + [122.00488281249997, 13.204980468750009], + [121.82919921875006, 13.328613281249972], + [121.91484375000002, 13.540332031250031] + ] + ], + [ + [ + [124.35361328125006, 13.632226562500009], + [124.17539062500012, 13.531542968750017], + [124.03886718750002, 13.663134765625003], + [124.22490234375007, 14.077587890624969], + [124.41718750000004, 13.871044921874997], + [124.35361328125006, 13.632226562500009] + ] + ], + [ + [ + [122.03349609375002, 15.005029296875009], + [121.93300781250005, 14.656054687500045], + [121.83984374999997, 15.038134765625003], + [122.03349609375002, 15.005029296875009] + ] + ], + [ + [ + [121.10156249999997, 18.615283203125017], + [121.84560546875, 18.29541015625003], + [122.03847656250005, 18.32792968749999], + [122.14667968750004, 18.486572265625], + [122.26552734375005, 18.458837890625034], + [122.15234374999997, 17.664404296875006], + [122.51914062500012, 17.124853515625034], + [122.13515625000005, 16.18481445312503], + [121.59531250000012, 15.933251953125023], + [121.60703125000006, 15.669824218749994], + [121.39228515625004, 15.324414062499969], + [121.69541015625006, 14.7373046875], + [121.62792968749997, 14.581152343749977], + [121.76660156249997, 14.16806640625002], + [122.21171875000002, 13.930175781250057], + [122.2875, 13.996191406250006], + [122.19970703125003, 14.148046875000034], + [122.6271484375001, 14.317529296875009], + [122.93417968750012, 14.18808593750002], + [123.101953125, 13.750244140624986], + [123.29697265625012, 13.836425781250043], + [123.32031249999997, 14.061669921875023], + [123.81572265625002, 13.837109375000011], + [123.80625000000012, 13.721728515625045], + [123.54960937500007, 13.645751953125014], + [123.81923828125, 13.269482421875011], + [123.78515625000003, 13.110546875000054], + [124.14277343750004, 13.035791015625009], + [124.0597656250001, 12.567089843749997], + [123.87783203125005, 12.689697265625014], + [123.94853515625007, 12.916406250000023], + [123.31093750000005, 13.044091796875009], + [123.16328125000004, 13.44174804687502], + [122.59521484374997, 13.90761718749998], + [122.46796875000004, 13.886718749999986], + [122.66787109375, 13.395361328124991], + [122.59990234375002, 13.194140625000031], + [122.37656250000012, 13.520605468750006], + [121.77792968750006, 13.93764648437498], + [121.50107421875006, 13.8421875], + [121.344140625, 13.649121093749997], + [121.09550781250007, 13.679492187500045], + [120.84072265625, 13.884716796875026], + [120.637109375, 13.804492187500031], + [120.61679687500006, 14.188037109375003], + [120.9220703125001, 14.493115234374983], + [120.94130859375, 14.645068359375031], + [120.58369140625004, 14.88125], + [120.58867187500002, 14.483105468749983], + [120.43876953125002, 14.453369140624972], + [120.25078125000002, 14.793310546875034], + [120.08212890625012, 14.851074218749986], + [119.77255859375012, 16.25512695312503], + [119.83076171875004, 16.326562500000023], + [120.15976562500012, 16.047656250000045], + [120.36875, 16.109570312499955], + [120.35839843749997, 17.63818359375], + [120.59970703125012, 18.507861328125074], + [121.10156249999997, 18.615283203125017] + ] + ], + [ + [ + [121.92167968750007, 18.89472656250001], + [121.82519531250003, 18.842724609374983], + [121.94335937500003, 19.010449218749955], + [121.92167968750007, 18.89472656250001] + ] + ], + [ + [ + [121.52089843750005, 19.361962890624994], + [121.53125, 19.271337890625006], + [121.37460937500006, 19.356298828124977], + [121.52089843750005, 19.361962890624994] + ] + ] + ] + }, + "properties": { "name": "Philippines", "childNum": 37 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [134.5954101562501, 7.382031249999969], + [134.51572265625012, 7.525781250000037], + [134.65117187500002, 7.712109374999983], + [134.5954101562501, 7.382031249999969] + ] + ] + }, + "properties": { "name": "Palau", "childNum": 1 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [153.53613281249997, -11.476171874999949], + [153.75986328125006, -11.586328125], + [153.55371093749997, -11.630566406249969], + [153.28681640625004, -11.516992187500009], + [153.20361328124997, -11.32412109374998], + [153.53613281249997, -11.476171874999949] + ] + ], + [ + [ + [154.28076171874997, -11.36142578125002], + [154.12119140625006, -11.425683593749966], + [154.02343750000003, -11.347949218750031], + [154.28076171874997, -11.36142578125002] + ] + ], + [ + [ + [150.89873046875002, -10.565332031250023], + [150.88466796875, -10.643457031250037], + [150.78574218750006, -10.603417968749966], + [150.89873046875002, -10.565332031250023] + ] + ], + [ + [ + [151.08095703125, -10.020117187499963], + [151.29648437500012, -9.956738281250026], + [151.230859375, -10.194726562500009], + [150.95917968750004, -10.092578124999989], + [150.77607421875004, -9.70908203125002], + [151.08095703125, -10.020117187499963] + ] + ], + [ + [ + [150.52841796875006, -9.34658203124998], + [150.78867187500006, -9.417968749999957], + [150.89404296875003, -9.667480468749986], + [150.43623046875004, -9.624609374999949], + [150.5084960937501, -9.536132812499957], + [150.43730468750007, -9.359960937500034], + [150.52841796875006, -9.34658203124998] + ] + ], + [ + [ + [150.3454101562501, -9.493847656249955], + [150.10976562500005, -9.361914062499991], + [150.20830078125002, -9.206347656250003], + [150.32011718750007, -9.264160156249972], + [150.3454101562501, -9.493847656249955] + ] + ], + [ + [ + [152.63095703125012, -8.959375], + [152.95292968750007, -9.07011718749996], + [152.96689453125006, -9.208984375000014], + [152.51513671874997, -9.009863281250034], + [152.63095703125012, -8.959375] + ] + ], + [ + [ + [151.10683593750005, -8.733496093749949], + [151.12412109375012, -8.804882812500011], + [151.00498046875006, -8.523828124999952], + [151.117578125, -8.41884765624998], + [151.10683593750005, -8.733496093749949] + ] + ], + [ + [ + [143.58681640625005, -8.481738281250003], + [143.321875, -8.367578125], + [143.5814453125, -8.390917968749974], + [143.58681640625005, -8.481738281250003] + ] + ], + [ + [ + [148.02578125, -5.826367187500011], + [147.78105468750007, -5.627246093749946], + [147.7946289062501, -5.492382812500011], + [148.05478515625006, -5.61152343750004], + [148.02578125, -5.826367187500011] + ] + ], + [ + [ + [155.95761718750006, -6.686816406249989], + [155.71933593750012, -6.862792968749957], + [155.34404296875007, -6.721679687499986], + [155.20214843750003, -6.3076171875], + [154.75927734375003, -5.931347656249997], + [154.72929687500002, -5.444433593750006], + [155.09384765625006, -5.620214843750034], + [155.46699218750004, -6.145117187500034], + [155.82255859375002, -6.38046875000002], + [155.95761718750006, -6.686816406249989] + ] + ], + [ + [ + [147.17626953124997, -5.431933593749946], + [147.00585937499997, -5.30703125], + [147.1310546875001, -5.190820312500037], + [147.17626953124997, -5.431933593749946] + ] + ], + [ + [ + [154.64726562500002, -5.43271484375002], + [154.54003906250003, -5.11083984375], + [154.63261718750007, -5.013867187499955], + [154.72714843750006, -5.218066406249989], + [154.64726562500002, -5.43271484375002] + ] + ], + [ + [ + [146.01933593750007, -4.726171874999963], + [145.88359375000007, -4.66748046875], + [145.9958007812501, -4.539257812499983], + [146.01933593750007, -4.726171874999963] + ] + ], + [ + [ + [151.915625, -4.296777343749966], + [152.11718749999997, -4.212207031249974], + [152.40566406250005, -4.340722656249952], + [152.35117187500006, -4.82216796874998], + [151.98369140625007, -5.07441406250004], + [152.14296875, -5.357031249999963], + [152.07705078125, -5.458300781249989], + [151.86542968750004, -5.564843750000023], + [151.51513671874997, -5.552343749999963], + [151.22929687500002, -5.919921874999986], + [150.47353515625, -6.263378906249969], + [149.65253906250004, -6.290429687499966], + [149.38232421874997, -6.078125], + [149.0990234375, -6.116992187499989], + [148.33720703125007, -5.669433593750014], + [148.43203125, -5.471777343749991], + [149.35888671875003, -5.583984375000014], + [149.8314453125, -5.524121093749997], + [149.96279296875, -5.447753906249972], + [150.0900390625001, -5.011816406249977], + [150.1703125, -5.070605468749974], + [150.0724609375001, -5.309570312499986], + [150.18310546874997, -5.523632812499983], + [150.90029296875005, -5.447167968750037], + [151.32656250000005, -4.96035156249998], + [151.67119140625007, -4.88330078125], + [151.59306640625007, -4.200781249999949], + [151.915625, -4.296777343749966] + ] + ], + [ + [ + [152.67060546875004, -3.13339843750002], + [152.64619140625004, -3.221191406249957], + [152.54326171875002, -3.095605468749952], + [152.63876953125012, -3.042773437500031], + [152.67060546875004, -3.13339843750002] + ] + ], + [ + [ + [140.97617187500012, -9.11875], + [140.97519531250006, -6.90537109375002], + [140.86230468749997, -6.740039062499989], + [140.975, -6.346093750000023], + [140.97353515625, -2.803417968750026], + [140.97343750000007, -2.609765625], + [142.90517578125, -3.32070312499998], + [143.50898437500004, -3.431152343750014], + [144.06640625000003, -3.80517578125], + [144.4777343750001, -3.82529296875002], + [145.08779296875, -4.349121093749972], + [145.33457031250012, -4.385253906249972], + [145.7669921875, -4.823046874999989], + [145.74521484375012, -5.402441406249977], + [147.56669921875002, -6.056933593750003], + [147.80205078125002, -6.31523437499996], + [147.84550781250007, -6.662402343749989], + [147.11914062499997, -6.721679687499986], + [146.95361328124997, -6.834082031249963], + [147.19003906250012, -7.378125], + [148.12675781250007, -8.103613281249963], + [148.246875, -8.554296875000034], + [148.45117187499997, -8.694531250000011], + [148.58310546875006, -9.051757812499957], + [149.19833984375006, -9.03125], + [149.26318359374997, -9.497851562499974], + [150.01103515625007, -9.688183593750026], + [149.76123046874997, -9.805859375000011], + [149.87441406250005, -10.012988281250031], + [150.84951171875, -10.236035156249997], + [150.44609375000007, -10.30732421875004], + [150.6471679687501, -10.517968749999966], + [150.31992187500012, -10.654882812499963], + [150.0167968750001, -10.577148437500028], + [149.75410156250004, -10.353027343750028], + [147.76865234375012, -10.070117187500031], + [147.01718750000006, -9.38789062500004], + [146.96376953125, -9.059570312499943], + [146.63085937499997, -8.951171874999972], + [146.03320312499997, -8.076367187500011], + [144.97382812500004, -7.802148437500009], + [144.86425781249997, -7.631542968749983], + [144.50986328125006, -7.567382812499972], + [144.14287109375007, -7.757226562500009], + [143.65488281250012, -7.460351562500009], + [143.94228515625005, -7.944238281250009], + [143.8333984375, -8.029101562499974], + [143.51816406250006, -8.000683593749955], + [143.61376953125003, -8.200390624999969], + [142.52412109375004, -8.32167968749998], + [142.34746093750002, -8.167480468750014], + [142.20683593750002, -8.195800781250014], + [142.47480468750004, -8.369433593750031], + [142.79794921875006, -8.345019531250031], + [143.11181640624997, -8.474511718750037], + [143.37724609375007, -8.762207031250028], + [143.36621093750003, -8.961035156250034], + [142.6471679687501, -9.327832031249969], + [142.22958984375012, -9.169921874999957], + [141.13320312500005, -9.221289062500034], + [140.97617187500012, -9.11875] + ] + ], + [ + [ + [152.96582031249997, -4.756347656249986], + [152.89169921875006, -4.832421875000023], + [152.73994140625004, -4.635839843750034], + [152.66816406250004, -4.131835937500028], + [152.27939453125006, -3.582421875], + [151.06679687500005, -2.829003906249994], + [150.74609374999997, -2.73886718750002], + [150.8253906250001, -2.572949218749969], + [152.03291015625004, -3.25136718749998], + [153.01679687500004, -4.105664062500026], + [153.1325195312501, -4.352441406250037], + [152.96582031249997, -4.756347656249986] + ] + ], + [ + [ + [150.43662109375012, -2.66181640625004], + [150.16572265625004, -2.660253906249991], + [149.96162109375004, -2.473828125000026], + [150.22714843750006, -2.384179687499966], + [150.42949218750007, -2.47041015625004], + [150.43662109375012, -2.66181640625004] + ] + ], + [ + [ + [147.06757812500004, -1.96015625], + [147.43808593750012, -2.05898437499998], + [147.20634765625007, -2.181933593749974], + [146.54648437500012, -2.20859375], + [146.65625, -1.97402343749998], + [147.06757812500004, -1.96015625] + ] + ], + [ + [ + [149.76542968750007, -1.553027343750017], + [149.54589843749997, -1.471679687499957], + [149.58095703125005, -1.353222656249983], + [149.76542968750007, -1.553027343750017] + ] + ] + ] + }, + "properties": { "name": "Papua New Guinea", "childNum": 21 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [23.484667968750017, 53.939794921875], + [23.915429687500023, 52.770263671875], + [23.175097656250017, 52.28662109375], + [23.652441406250006, 52.040380859375], + [23.605273437500017, 51.517919921875], + [23.664453125000023, 51.31005859375], + [24.095800781250006, 50.87275390625], + [23.9970703125, 50.809375], + [24.089941406250006, 50.53046875], + [23.97265625, 50.410058593749994], + [23.711718750000017, 50.37734375], + [23.03632812500001, 49.899072265624994], + [22.706152343750006, 49.606201171875], + [22.6494140625, 49.539013671875], + [22.66064453125, 49.483691406249996], + [22.71992187500001, 49.353808593749996], + [22.732421875, 49.295166015625], + [22.705664062500006, 49.171191406249996], + [22.847070312500023, 49.08125], + [22.538671875, 49.072705078125], + [22.473046875000023, 49.081298828125], + [22.020117187500006, 49.209521484374996], + [21.6396484375, 49.411962890625], + [21.079394531250017, 49.418261718749996], + [20.868457031250017, 49.314697265625], + [20.36298828125001, 49.38525390625], + [20.0576171875, 49.181298828124994], + [19.756640625000017, 49.204394531249996], + [19.77392578125, 49.37216796875], + [19.44160156250001, 49.597705078124996], + [19.1494140625, 49.4], + [18.83222656250001, 49.510791015624996], + [18.562402343750023, 49.879345703125], + [18.0283203125, 50.03525390625], + [17.874804687500017, 49.972265625], + [17.627050781250006, 50.11640625], + [17.702246093750006, 50.307177734374996], + [17.41523437500001, 50.254785156249994], + [16.88007812500001, 50.427050781249996], + [16.989648437500023, 50.2369140625], + [16.63916015625, 50.1021484375], + [16.210351562500023, 50.423730468749994], + [16.419726562500017, 50.573632812499994], + [16.2822265625, 50.655615234375], + [16.007226562500023, 50.611621093749996], + [14.99375, 51.01435546875], + [14.98291015625, 50.886572265625], + [14.895800781250017, 50.861376953124996], + [14.809375, 50.858984375], + [14.814257812500017, 50.871630859374996], + [14.91748046875, 51.008740234375], + [14.9638671875, 51.095117187499994], + [14.935546875, 51.435351562499996], + [14.905957031250011, 51.463330078125], + [14.724707031250006, 51.523876953125], + [14.7109375, 51.544921875], + [14.738671875000023, 51.6271484375], + [14.601660156250006, 51.832373046875], + [14.752539062500006, 52.081835937499996], + [14.679882812500011, 52.25], + [14.615625, 52.277636718749996], + [14.573925781250011, 52.31416015625], + [14.554589843750023, 52.359667968749996], + [14.569726562500023, 52.431103515625], + [14.619433593750017, 52.528515625], + [14.514062500000023, 52.64560546875], + [14.253710937500017, 52.782519531249996], + [14.128613281250011, 52.878222656249996], + [14.138867187500011, 52.932861328125], + [14.293164062500011, 53.0267578125], + [14.368554687500023, 53.10556640625], + [14.410937500000017, 53.199023437499996], + [14.412304687500011, 53.216748046875], + [14.41455078125, 53.283496093749996], + [14.258886718750006, 53.729638671875], + [14.58349609375, 53.63935546875], + [14.558398437500017, 53.823193359375], + [14.21142578125, 53.950341796875], + [16.186328125000017, 54.290380859375], + [16.55976562500001, 54.55380859375], + [18.32343750000001, 54.838183593749996], + [18.75927734375, 54.6845703125], + [18.43623046875001, 54.7447265625], + [18.83642578125, 54.369580078125], + [19.604394531250023, 54.4591796875], + [20.20820312500001, 54.420751953125], + [22.16845703125, 54.35986328125], + [22.731835937500023, 54.35009765625], + [22.766210937500006, 54.356787109375], + [22.82373046875, 54.395800781249996], + [22.893945312500023, 54.39052734375], + [22.97675781250001, 54.366357421875], + [23.015527343750023, 54.34833984375], + [23.04218750000001, 54.30419921875], + [23.0875, 54.299462890625], + [23.170312500000023, 54.2814453125], + [23.282324218750006, 54.24033203125], + [23.3701171875, 54.200488281249996], + [23.45361328125, 54.14345703125], + [23.484667968750017, 53.939794921875] + ] + ] + }, + "properties": { "name": "Poland", "childNum": 1 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [-65.42558593749999, 18.105615234374994], + [-65.57221679687493, 18.137304687499977], + [-65.29487304687501, 18.133349609375045], + [-65.42558593749999, 18.105615234374994] + ] + ], + [ + [ + [-66.12939453125003, 18.444921875000034], + [-65.62880859375, 18.381396484375045], + [-65.62084960937497, 18.242333984374966], + [-65.97080078124995, 17.974365234375], + [-67.196875, 17.994189453125045], + [-67.2640625, 18.364599609375006], + [-67.15864257812501, 18.499218749999983], + [-66.12939453125003, 18.444921875000034] + ] + ] + ] + }, + "properties": { "name": "Puerto Rico", "childNum": 2 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [130.52695312500012, 42.535400390625], + [130.68730468750007, 42.30253906249999], + [130.2357421875, 42.183203125000034], + [129.75634765624997, 41.712255859375006], + [129.70869140625004, 40.857324218749994], + [129.34111328125002, 40.72631835937506], + [128.51123046874997, 40.130224609375006], + [127.56816406250002, 39.78198242187503], + [127.39453125000003, 39.207910156249966], + [127.78613281250003, 39.084130859374966], + [128.37460937500012, 38.6234375], + [128.03896484375, 38.30854492187498], + [127.09033203125003, 38.28388671875001], + [126.63388671875012, 37.78183593750006], + [126.36992187500007, 37.87836914062501], + [126.11669921875003, 37.74291992187503], + [125.76914062500006, 37.98535156250003], + [125.35781250000005, 37.72480468749998], + [125.31074218750004, 37.843505859375], + [124.98876953124997, 37.93144531249999], + [125.2067382812501, 38.08154296875], + [124.69091796874997, 38.12919921875002], + [125.06738281250003, 38.556738281250006], + [125.55449218750002, 38.68623046875001], + [125.16884765625, 38.80551757812506], + [125.40966796875003, 39.28837890625002], + [125.36083984375003, 39.52661132812497], + [124.77529296875, 39.75805664062506], + [124.63828125000006, 39.61508789062506], + [124.36210937500002, 40.004052734374994], + [124.8893554687501, 40.459814453125006], + [125.98906250000002, 40.904638671875034], + [126.74306640625, 41.724853515625], + [126.95478515625004, 41.76948242187501], + [127.17968750000003, 41.531347656250006], + [128.14941406249997, 41.38774414062496], + [128.28925781250004, 41.60742187500006], + [128.04521484375007, 41.9875], + [128.92343750000006, 42.038232421874966], + [129.3136718750001, 42.41357421874997], + [129.69785156250012, 42.448144531249994], + [129.89824218750002, 42.998144531250034], + [130.24033203125006, 42.891796874999955], + [130.24667968750012, 42.744824218749955], + [130.52695312500012, 42.535400390625] + ] + ] + }, + "properties": { "name": "Dem. Rep. Korea", "childNum": 1 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [-17.190869140624926, 32.86860351562498], + [-16.693261718749966, 32.75800781250001], + [-17.018261718749926, 32.66279296874998], + [-17.226025390624983, 32.76684570312503], + [-17.190869140624926, 32.86860351562498] + ] + ], + [ + [ + [-25.64897460937499, 37.840917968750006], + [-25.18193359374996, 37.837890625], + [-25.19072265624999, 37.764355468749955], + [-25.73447265624992, 37.76289062500001], + [-25.845898437499983, 37.89404296875], + [-25.64897460937499, 37.840917968750006] + ] + ], + [ + [ + [-28.14726562499996, 38.45268554687502], + [-28.064794921875034, 38.412744140624966], + [-28.454492187500023, 38.40864257812504], + [-28.54882812499997, 38.51855468750003], + [-28.14726562499996, 38.45268554687502] + ] + ], + [ + [ + [-28.641308593749983, 38.525], + [-28.842041015625, 38.5984375], + [-28.69775390625, 38.638476562500045], + [-28.641308593749983, 38.525] + ] + ], + [ + [ + [-27.07524414062496, 38.643457031249994], + [-27.38593750000001, 38.765820312499955], + [-27.127001953125017, 38.78984375], + [-27.07524414062496, 38.643457031249994] + ] + ], + [ + [ + [-31.137109374999937, 39.40693359375001], + [-31.282958984375, 39.39409179687496], + [-31.260839843750034, 39.49677734375001], + [-31.137109374999937, 39.40693359375001] + ] + ], + [ + [ + [-7.406152343749937, 37.17944335937497], + [-7.834130859374994, 37.005712890625034], + [-8.597656249999943, 37.12133789062506], + [-8.997802734375028, 37.03227539062502], + [-8.814160156249983, 37.43081054687502], + [-8.881103515624943, 38.44667968750005], + [-8.668310546874949, 38.42431640625003], + [-8.798876953124989, 38.518164062500034], + [-9.213281249999937, 38.44809570312498], + [-9.250390624999966, 38.65673828125003], + [-9.021484374999943, 38.746875], + [-8.79160156249992, 39.07817382812502], + [-9.13579101562496, 38.74277343749998], + [-9.35673828124996, 38.697900390624994], + [-9.479736328124972, 38.79877929687501], + [-9.374755859374972, 39.338281249999966], + [-8.837841796874926, 40.11567382812498], + [-8.684619140624989, 40.75253906250006], + [-8.755419921874932, 41.69838867187502], + [-8.887597656249937, 41.76459960937501], + [-8.777148437500017, 41.941064453124994], + [-8.266064453124983, 42.13740234375001], + [-8.152490234374937, 41.81196289062498], + [-7.40361328124996, 41.833691406249955], + [-7.147119140625023, 41.98115234374998], + [-6.61826171874992, 41.9423828125], + [-6.542187499999955, 41.672509765624994], + [-6.2125, 41.53203125], + [-6.928466796874972, 41.009130859375006], + [-6.8101562499999, 40.343115234375034], + [-7.032617187499966, 40.16791992187498], + [-6.896093749999949, 40.02182617187506], + [-6.975390624999932, 39.79838867187502], + [-7.117675781249972, 39.681689453125045], + [-7.53569335937496, 39.66157226562501], + [-6.997949218749994, 39.05644531250002], + [-7.343017578124943, 38.45742187500002], + [-7.106396484374983, 38.181005859375006], + [-6.957568359374932, 38.18789062499999], + [-7.44394531249992, 37.72827148437497], + [-7.406152343749937, 37.17944335937497] + ] + ] + ] + }, + "properties": { + "name": "Portugal", + "childNum": 7, + "cp": [-8.7440694, 39.9251454] + } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [-58.15976562499999, -20.164648437500006], + [-58.13779296874999, -20.2373046875], + [-58.12460937499999, -20.29345703125], + [-58.09150390625, -20.33320312500001], + [-58.05844726562499, -20.38613281250001], + [-58.025390625, -20.415820312500003], + [-58.00224609374999, -20.465429687500006], + [-57.97905273437499, -20.657324218750006], + [-57.91513671874999, -20.69033203125001], + [-57.830224609374994, -20.99794921875001], + [-57.94267578124999, -21.79833984375], + [-57.95590820312499, -22.109179687500003], + [-56.77519531249999, -22.261328125], + [-56.44780273437499, -22.076171875], + [-56.39487304687499, -22.09267578125001], + [-56.35185546874999, -22.17861328125001], + [-56.246044921875, -22.2646484375], + [-56.18984375, -22.28115234375001], + [-55.99140625, -22.28115234375001], + [-55.84916992187499, -22.3076171875], + [-55.75327148437499, -22.41015625], + [-55.74663085937499, -22.5126953125], + [-55.61767578125, -22.671484375], + [-55.53828125, -23.58095703125001], + [-55.518457031249994, -23.627246093750003], + [-55.458886718749994, -23.68671875000001], + [-55.4423828125, -23.792578125], + [-55.4423828125, -23.865332031250006], + [-55.415917968749994, -23.95136718750001], + [-55.36630859374999, -23.991015625], + [-55.28691406249999, -24.004296875], + [-55.1943359375, -24.01748046875001], + [-55.08188476562499, -23.99765625], + [-54.982666015625, -23.97451171875001], + [-54.62548828125, -23.8125], + [-54.44023437499999, -23.90175781250001], + [-54.37080078125, -23.97119140625], + [-54.24179687499999, -24.047265625], + [-54.281005859375, -24.30605468750001], + [-54.43623046875, -25.12128906250001], + [-54.47314453125, -25.22021484375], + [-54.610546875, -25.432714843750006], + [-54.615869140624994, -25.57607421875001], + [-54.63193359374999, -26.00576171875001], + [-54.677734375, -26.30878906250001], + [-54.934472656249994, -26.70253906250001], + [-55.1359375, -26.93115234375], + [-55.426660156249994, -27.00927734375], + [-55.450634765625, -27.068359375], + [-55.496728515624994, -27.115332031250006], + [-55.564892578125, -27.15], + [-55.59726562499999, -27.207617187500006], + [-55.59379882812499, -27.2880859375], + [-55.63291015624999, -27.35712890625001], + [-55.71464843749999, -27.41484375], + [-55.789990234375, -27.41640625], + [-55.95146484374999, -27.32568359375], + [-56.1640625, -27.32148437500001], + [-56.437158203124994, -27.553808593750006], + [-58.16826171874999, -27.2734375], + [-58.60483398437499, -27.31435546875001], + [-58.641748046874994, -27.19609375], + [-58.618603515625, -27.132128906250003], + [-58.222070312499994, -26.65], + [-58.18149414062499, -26.307421875], + [-57.943115234375, -26.05292968750001], + [-57.563134765624994, -25.473730468750006], + [-57.821679687499994, -25.13642578125001], + [-59.187255859375, -24.562304687500003], + [-59.892480468749994, -24.093554687500003], + [-60.83984375, -23.85810546875001], + [-61.084716796875, -23.65644531250001], + [-61.79853515625, -23.18203125], + [-62.21416015624999, -22.612402343750006], + [-62.372509765625, -22.43916015625001], + [-62.54155273437499, -22.349609375], + [-62.6259765625, -22.29042968750001], + [-62.62568359375, -22.261523437500003], + [-62.65097656249999, -22.233691406250003], + [-62.27666015624999, -21.066015625], + [-62.276318359375, -20.5625], + [-61.7568359375, -19.6453125], + [-60.00737304687499, -19.29755859375001], + [-59.09052734375, -19.286230468750006], + [-58.18017578125, -19.81787109375], + [-58.15976562499999, -20.164648437500006] + ] + ] + }, + "properties": { "name": "Paraguay", "childNum": 1 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [34.34833984375004, 31.292919921874955], + [34.2453125000001, 31.208300781250045], + [34.2125, 31.292285156250017], + [34.198144531249994, 31.322607421875063], + [34.47734375000002, 31.584863281250023], + [34.52412109375004, 31.541650390624994], + [34.5255859375001, 31.52563476562503], + [34.34833984375004, 31.292919921874955] + ] + ], + [ + [ + [34.88046875, 31.3681640625], + [34.950976562500074, 31.60229492187503], + [35.20371093750006, 31.75], + [35.1271484375001, 31.816748046875006], + [35.05322265625003, 31.83793945312496], + [34.983007812500006, 31.816796875000023], + [34.9611328125001, 31.823339843750006], + [34.95380859375004, 31.84125976562504], + [34.98974609374997, 31.913281249999955], + [34.955957031249994, 32.1609375], + [35.01054687500002, 32.33818359375002], + [35.06503906250006, 32.46044921875006], + [35.19326171875005, 32.53442382812503], + [35.303808593750006, 32.512939453125], + [35.38671875000003, 32.493017578125034], + [35.402636718750074, 32.45063476562501], + [35.484375, 32.40166015624999], + [35.5514648437501, 32.39550781250006], + [35.57207031250002, 32.237890625], + [35.450585937499994, 31.479296875000017], + [34.88046875, 31.3681640625] + ] + ] + ] + }, + "properties": { "name": "Palestine", "childNum": 2 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [-149.321533203125, -17.690039062499963], + [-149.177685546875, -17.736621093750045], + [-149.18178710937497, -17.86230468749997], + [-149.34111328125, -17.732421874999986], + [-149.57890624999993, -17.734960937499963], + [-149.635009765625, -17.564257812500003], + [-149.37919921874993, -17.522363281249994], + [-149.321533203125, -17.690039062499963] + ] + ], + [ + [ + [-143.44057617187497, -16.619726562499963], + [-143.38618164062498, -16.668847656250023], + [-143.55068359375002, -16.62109374999997], + [-143.44057617187497, -16.619726562499963] + ] + ], + [ + [ + [-139.02431640624997, -9.695214843750037], + [-138.82734375, -9.74160156249998], + [-139.13408203124996, -9.829492187500037], + [-139.02431640624997, -9.695214843750037] + ] + ], + [ + [ + [-140.075634765625, -9.425976562499983], + [-140.14438476562498, -9.359375], + [-140.07094726562497, -9.328125], + [-140.075634765625, -9.425976562499983] + ] + ], + [ + [ + [-140.07260742187503, -8.910449218750031], + [-140.21743164062497, -8.929687499999957], + [-140.24003906249993, -8.79755859375004], + [-140.057666015625, -8.801464843750026], + [-140.07260742187503, -8.910449218750031] + ] + ] + ] + }, + "properties": { "name": "Fr. Polynesia", "childNum": 5 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [51.26796875000002, 24.607226562500003], + [51.17802734375002, 24.58671875], + [51.093359375, 24.564648437499997], + [51.02275390625002, 24.565234375], + [50.96601562500001, 24.573925781249997], + [50.928320312500006, 24.595117187499994], + [50.85566406250001, 24.679638671874997], + [50.80439453125001, 24.789257812499997], + [50.8359375, 24.850390625], + [50.846777343750006, 24.888574218749994], + [50.75458984375001, 25.39926757812499], + [51.003125, 25.9814453125], + [51.262304687500006, 26.153271484374997], + [51.543066406250006, 25.902392578125003], + [51.4853515625, 25.524707031250003], + [51.60888671875, 25.052880859374994], + [51.42792968750001, 24.668261718750003], + [51.26796875000002, 24.607226562500003] + ] + ] + }, + "properties": { "name": "Qatar", "childNum": 1 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [28.2125, 45.450439453125], + [28.317675781250017, 45.347119140625], + [28.451269531250006, 45.2921875], + [28.78828125000001, 45.240966796875], + [28.78173828125, 45.309863281249996], + [28.894335937500017, 45.289941406249994], + [29.223535156250023, 45.4029296875], + [29.403710937500023, 45.419677734375], + [29.567675781250017, 45.37080078125], + [29.705859375000017, 45.259912109374994], + [29.557519531250023, 44.843408203124994], + [29.048242187500023, 44.757568359375], + [29.0953125, 44.975048828125], + [28.891503906250023, 44.91865234375], + [28.585351562500023, 43.742236328124996], + [28.221972656250017, 43.772851562499994], + [27.88427734375, 43.987353515624996], + [27.425390625, 44.0205078125], + [27.0869140625, 44.167382812499994], + [26.2158203125, 44.007275390625], + [25.4970703125, 43.670800781249994], + [22.919042968750006, 43.83447265625], + [22.868261718750006, 43.947900390624994], + [23.02851562500001, 44.077978515625], + [22.705078125, 44.23779296875], + [22.687890625000023, 44.248291015625], + [22.494531250000023, 44.435449218749994], + [22.554003906250017, 44.540332031249996], + [22.6201171875, 44.562353515625], + [22.70078125, 44.555517578125], + [22.734375, 44.569921875], + [22.72089843750001, 44.605517578124996], + [22.64208984375, 44.6509765625], + [22.49765625, 44.70625], + [22.350683593750006, 44.676123046875], + [22.200976562500017, 44.560693359374994], + [22.093066406250017, 44.541943359375], + [21.909277343750006, 44.66611328125], + [21.636132812500023, 44.71044921875], + [21.52314453125001, 44.790087890624996], + [21.36005859375001, 44.82666015625], + [21.35791015625, 44.86181640625], + [21.384375, 44.870068359375], + [21.442187500000017, 44.873388671875], + [21.519921875000023, 44.880810546875], + [21.532324218750006, 44.900683593749996], + [21.35703125, 44.990771484374996], + [21.465429687500006, 45.171875], + [21.431445312500017, 45.192529296874994], + [20.794042968750006, 45.46787109375], + [20.775, 45.749804687499996], + [20.760156250000023, 45.758105468749996], + [20.746875, 45.748974609375], + [20.727832031250017, 45.73740234375], + [20.709277343750017, 45.735253906249994], + [20.652734375000023, 45.77939453125], + [20.581152343750006, 45.869482421875], + [20.35859375000001, 45.975488281249994], + [20.241796875, 46.10859375], + [20.28095703125001, 46.1330078125], + [20.508105468750017, 46.166943359375], + [20.613671875000023, 46.13349609375], + [20.76025390625, 46.246240234374994], + [21.121679687500006, 46.282421875], + [21.99970703125001, 47.505029296874994], + [22.87666015625001, 47.947265625], + [23.054785156250006, 48.00654296875], + [23.139453125000017, 48.08740234375], + [23.20263671875, 48.084521484374996], + [23.408203125, 47.989990234375], + [23.628710937500017, 47.995849609375], + [24.578906250000017, 47.931054687499994], + [24.979101562500006, 47.72412109375], + [25.464257812500023, 47.910791015624994], + [25.689257812500017, 47.932470703125], + [25.90869140625, 47.967578125], + [26.162695312500006, 47.992529296875], + [26.236230468750023, 48.064355468749994], + [26.276953125, 48.113232421875], + [26.3056640625, 48.203759765624994], + [26.4423828125, 48.22998046875], + [26.618945312500017, 48.25986328125], + [26.980761718750017, 48.155029296875], + [27.614062500000017, 47.34052734375], + [28.07177734375, 46.978417968749994], + [28.23945312500001, 46.6408203125], + [28.07470703125, 45.598974609375], + [28.2125, 45.450439453125] + ] + ] + }, + "properties": { "name": "Romania", "childNum": 1 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [146.71396484375012, 43.743798828124994], + [146.62197265625, 43.81298828125006], + [146.88408203125002, 43.82915039062496], + [146.71396484375012, 43.743798828124994] + ] + ], + [ + [ + [146.20761718750006, 44.49765625], + [146.5677734375, 44.44042968749997], + [145.91406249999997, 44.10371093750004], + [145.58681640625, 43.84511718750002], + [145.5558593750001, 43.66459960937502], + [145.46171875000007, 43.870898437500045], + [146.20761718750006, 44.49765625] + ] + ], + [ + [ + [148.59951171875, 45.317626953125], + [147.91376953125004, 44.99038085937502], + [147.65781250000012, 44.97714843749998], + [146.89746093750003, 44.404296875], + [147.24658203124997, 44.856054687500006], + [147.88554687500007, 45.22563476562499], + [147.9240234375001, 45.38330078125006], + [148.05605468750005, 45.26210937500005], + [148.32421874999997, 45.28242187500001], + [148.8122070312501, 45.510009765625], + [148.83710937500004, 45.36269531250002], + [148.59951171875, 45.317626953125] + ] + ], + [ + [ + [149.68769531250004, 45.64204101562501], + [149.44707031250002, 45.593359375000034], + [149.9623046875, 46.02192382812504], + [150.553125, 46.208544921875045], + [149.68769531250004, 45.64204101562501] + ] + ], + [ + [ + [152.00205078125006, 46.89716796874998], + [151.72343750000007, 46.82880859375001], + [152.28886718750007, 47.1421875], + [152.00205078125006, 46.89716796874998] + ] + ], + [ + [ + [154.81044921875005, 49.31201171875], + [154.61093750000006, 49.29404296874998], + [154.82490234375004, 49.64692382812501], + [154.81044921875005, 49.31201171875] + ] + ], + [ + [ + [155.9210937500001, 50.30219726562501], + [155.39716796875004, 50.04125976562497], + [155.24306640625, 50.09462890625002], + [155.21835937500012, 50.29785156250003], + [155.68017578124997, 50.400732421875034], + [156.096875, 50.771875], + [155.9210937500001, 50.30219726562501] + ] + ], + [ + [ + [156.40507812500002, 50.65761718750005], + [156.16796874999997, 50.73188476562498], + [156.37646484374997, 50.86210937499996], + [156.4875, 50.84296874999998], + [156.40507812500002, 50.65761718750005] + ] + ], + [ + [ + [142.76103515625002, 54.393945312499966], + [143.32470703125003, 52.96308593749998], + [143.15556640625002, 52.08374023437497], + [143.29951171875004, 51.632373046875045], + [143.81601562500006, 50.28261718750002], + [144.71376953125, 48.64028320312502], + [144.04873046875, 49.249169921874994], + [143.73232421875, 49.31201171875], + [143.10498046875003, 49.198828125000034], + [142.57421874999997, 48.07216796875002], + [142.55693359375002, 47.737890625000034], + [143.21767578125005, 46.79487304687504], + [143.48564453125002, 46.752050781250006], + [143.58066406250012, 46.360693359375034], + [143.43164062500003, 46.02866210937498], + [143.28232421875006, 46.55898437500002], + [142.57802734375005, 46.700781250000034], + [142.07714843749997, 45.91704101562499], + [141.83037109375002, 46.451074218749966], + [142.03867187500012, 47.140283203124966], + [141.9640625000001, 47.58745117187502], + [142.18173828125012, 48.01337890625001], + [141.86630859375006, 48.750097656250006], + [142.1422851562501, 49.56914062499999], + [142.06601562500006, 50.630468750000034], + [142.20673828125004, 51.22255859375002], + [141.72236328125004, 51.73632812499997], + [141.66083984375004, 52.27294921874997], + [141.85556640625012, 52.79350585937499], + [141.82353515625007, 53.33950195312502], + [142.1419921875, 53.49560546875003], + [142.52617187500002, 53.44746093749998], + [142.70595703125, 53.89570312499998], + [142.33496093749997, 54.28071289062501], + [142.76103515625002, 54.393945312499966] + ] + ], + [ + [ + [137.17861328125005, 55.100439453125034], + [137.05527343750006, 54.9267578125], + [136.71464843750002, 54.956152343750034], + [137.17861328125005, 55.100439453125034] + ] + ], + [ + [ + [137.94052734375012, 55.092626953125034], + [138.20615234375012, 55.03354492187498], + [137.72148437500007, 54.66323242187505], + [137.46269531250002, 54.873388671875034], + [137.23291015624997, 54.79057617187496], + [137.5773437500001, 55.19702148437497], + [137.94052734375012, 55.092626953125034] + ] + ], + [ + [ + [21.235742187500023, 55.26411132812498], + [22.072363281250034, 55.06367187499998], + [22.56728515625005, 55.05913085937496], + [22.82470703125, 54.87128906249998], + [22.684472656250023, 54.56293945312504], + [22.679882812500068, 54.493017578125006], + [22.766210937499977, 54.356787109375034], + [22.168457031250057, 54.35986328125006], + [21.14052734375008, 54.39179687499998], + [19.604394531250023, 54.45917968750004], + [19.974511718750023, 54.92119140625002], + [20.520312500000017, 54.994873046875], + [20.89980468750008, 55.286669921875045], + [20.957812500000074, 55.27890625000006], + [20.594824218750006, 54.982373046874955], + [20.995898437500017, 54.90268554687506], + [21.18886718750008, 54.93520507812502], + [21.235742187500023, 55.26411132812498] + ] + ], + [ + [ + [166.65029296875005, 54.83906249999998], + [166.64511718750006, 54.69409179687503], + [165.75107421875006, 55.294531250000034], + [166.27578125000005, 55.311962890624955], + [166.24804687499997, 55.16542968750002], + [166.65029296875005, 54.83906249999998] + ] + ], + [ + [ + [150.58994140625006, 59.01875], + [150.47021484375003, 59.05405273437498], + [150.66621093750004, 59.16015625000003], + [150.58994140625006, 59.01875] + ] + ], + [ + [ + [163.63515625000005, 58.603369140625006], + [163.47138671875004, 58.509375], + [163.7609375000001, 59.01503906250002], + [164.57265625, 59.22114257812501], + [164.61572265624997, 58.885595703125034], + [163.63515625000005, 58.603369140625006] + ] + ], + [ + [ + [35.8161132812501, 65.18208007812501], + [35.77871093750005, 64.97666015625], + [35.52890625000006, 65.15107421875001], + [35.8161132812501, 65.18208007812501] + ] + ], + [ + [ + [70.02070312500004, 66.502197265625], + [69.65136718750003, 66.56533203125], + [69.50273437500002, 66.75107421875], + [70.07666015624997, 66.69589843750003], + [70.02070312500004, 66.502197265625] + ] + ], + [ + [ + [-179.79853515625, 68.9404296875], + [-178.873876953125, 68.75410156249995], + [-178.69262695312503, 68.54599609375], + [-178.09746093750002, 68.4248046875], + [-178.05581054687497, 68.26489257812503], + [-177.79677734374997, 68.33798828125], + [-178.37304687500003, 68.56567382812503], + [-177.52724609375002, 68.29438476562501], + [-177.58920898437503, 68.22421875], + [-175.34521484375, 67.67807617187503], + [-175.37470703124998, 67.35737304687498], + [-175.00268554687494, 67.4375], + [-174.849853515625, 67.34887695312503], + [-174.92490234375, 66.62314453125006], + [-174.503759765625, 66.537939453125], + [-174.39409179687496, 66.34423828124997], + [-174.084765625, 66.47309570312504], + [-174.06503906249998, 66.22958984374998], + [-173.77397460937502, 66.43466796875003], + [-174.23159179687497, 66.63188476562505], + [-174.08642578125, 66.94287109375], + [-174.55009765624993, 67.090625], + [-173.6796875, 67.144775390625], + [-173.15781249999998, 67.06909179687503], + [-173.32353515625, 66.95483398437503], + [-173.25893554687497, 66.84008789062503], + [-173.19301757812497, 66.99360351562504], + [-172.5201171875, 66.952490234375], + [-173.00751953125, 67.06489257812498], + [-171.79555664062502, 66.93173828125003], + [-170.50952148437503, 66.34365234375005], + [-170.604443359375, 66.24892578125002], + [-170.30122070312504, 66.29404296874998], + [-170.24394531250002, 66.16928710937503], + [-169.777880859375, 66.14311523437505], + [-169.83168945312497, 65.99892578124997], + [-170.54067382812497, 65.86542968749995], + [-170.66630859375, 65.62153320312501], + [-171.42153320312502, 65.81035156250002], + [-171.10585937500002, 65.51103515625005], + [-171.90712890625, 65.495947265625], + [-172.78330078124998, 65.68105468749997], + [-172.23281250000002, 65.45571289062497], + [-172.30927734375004, 65.27563476562497], + [-172.66191406249993, 65.24853515625006], + [-172.28603515625002, 65.20571289062502], + [-172.21318359375, 65.04814453124999], + [-173.08579101562498, 64.81733398437495], + [-172.80107421874996, 64.79052734375], + [-172.90087890624994, 64.62885742187501], + [-172.40146484374998, 64.413916015625], + [-172.73916015624997, 64.41225585937502], + [-172.90317382812498, 64.52607421875004], + [-172.96005859375003, 64.32768554687502], + [-173.27548828124998, 64.2896484375], + [-173.327490234375, 64.53955078125003], + [-173.72973632812497, 64.36450195312497], + [-174.57055664062503, 64.7177734375], + [-175.39511718749998, 64.80239257812502], + [-175.85385742187498, 65.01083984375003], + [-176.09326171875, 65.471044921875], + [-177.05625, 65.613623046875], + [-177.48876953125, 65.50371093749999], + [-178.4125, 65.49555664062501], + [-178.93906249999998, 66.03276367187505], + [-178.74672851562497, 66.01367187500006], + [-178.52656250000004, 66.40156250000004], + [-178.86811523437498, 66.18706054687502], + [-179.14340820312503, 66.37504882812505], + [-179.327197265625, 66.16259765625003], + [-179.68330078124998, 66.18413085937505], + [-179.78969726562497, 65.90087890625], + [-179.352099609375, 65.51674804687497], + [-180, 65.06723632812498], + [-180, 65.31196289062501], + [-180, 65.55678710937497], + [-180, 65.80156250000002], + [-180, 66.04628906250002], + [-180, 66.29106445312499], + [-180, 66.53583984375004], + [-180, 66.78056640625005], + [-180, 67.02534179687501], + [-180, 67.27011718750006], + [-180, 67.51484374999998], + [-180, 67.75961914062503], + [-180, 68.00439453124997], + [-180, 68.24912109375], + [-180, 68.49389648437497], + [-180, 68.738671875], + [-179.999951171875, 68.98344726562505], + [-179.79853515625, 68.9404296875] + ] + ], + [ + [ + [50.265234375, 69.18559570312502], + [49.62626953125002, 68.85971679687498], + [48.91035156250004, 68.74306640625002], + [48.4390625, 68.80488281249998], + [48.319921875, 69.26923828125001], + [48.8449218750001, 69.49472656250003], + [49.22519531250006, 69.51123046875], + [50.265234375, 69.18559570312502] + ] + ], + [ + [ + [161.46708984375002, 68.90097656250003], + [161.08281250000007, 69.4056640625], + [161.50517578125007, 69.63945312500002], + [161.46708984375002, 68.90097656250003] + ] + ], + [ + [ + [169.20078125000006, 69.58046875], + [168.34804687500005, 69.66435546875005], + [167.86474609375003, 69.90107421875004], + [168.35791015625003, 70.01567382812502], + [169.37480468750007, 69.88261718749999], + [169.20078125000006, 69.58046875] + ] + ], + [ + [ + [60.450488281250074, 69.93486328124999], + [60.44023437500002, 69.72592773437506], + [59.637011718750074, 69.72104492187503], + [59.50263671875004, 69.86621093750003], + [58.952734375, 69.89277343750004], + [58.51992187500005, 70.31831054687504], + [59.04804687500004, 70.46049804687505], + [60.450488281250074, 69.93486328124999] + ] + ], + [ + [ + [52.90332031250003, 71.36499023437503], + [53.19257812500004, 71.21528320312498], + [53.0226562500001, 70.96870117187501], + [52.24960937500006, 71.28491210937506], + [52.90332031250003, 71.36499023437503] + ] + ], + [ + [ + [178.8615234375001, 70.826416015625], + [178.68388671875013, 71.10566406250004], + [180, 71.53774414062505], + [180, 70.993017578125], + [178.8615234375001, 70.826416015625] + ] + ], + [ + [ + [137.95986328125005, 71.50766601562503], + [137.71181640625005, 71.4232421875], + [137.06406250000006, 71.52988281250003], + [137.816796875, 71.58789062500006], + [137.95986328125005, 71.50766601562503] + ] + ], + [ + [ + [-178.87646484375, 71.57705078124997], + [-178.13388671874998, 71.46547851562497], + [-177.523583984375, 71.16689453125], + [-179.415673828125, 70.91899414062502], + [-179.999951171875, 70.993017578125], + [-179.999951171875, 71.53774414062505], + [-178.87646484375, 71.57705078124997] + ] + ], + [ + [ + [77.6325195312501, 72.291259765625], + [76.87109374999997, 72.317041015625], + [77.74853515625003, 72.63120117187506], + [78.36513671875005, 72.48242187500003], + [77.6325195312501, 72.291259765625] + ] + ], + [ + [ + [79.50146484374997, 72.72192382812497], + [78.63320312500005, 72.85073242187502], + [79.16425781250004, 73.0943359375], + [79.50146484374997, 72.72192382812497] + ] + ], + [ + [ + [74.660546875, 72.87343750000002], + [74.18066406250003, 72.975341796875], + [74.19853515625002, 73.10908203124998], + [74.9615234375, 73.0625], + [74.660546875, 72.87343750000002] + ] + ], + [ + [ + [120.26132812500012, 73.08984374999997], + [119.79208984375006, 73.04541015624997], + [119.64042968750002, 73.12431640625007], + [120.26132812500012, 73.08984374999997] + ] + ], + [ + [ + [55.31982421875003, 73.30830078124998], + [56.42958984375005, 73.201171875], + [56.121679687500006, 72.80659179687498], + [55.40332031249997, 72.54907226562503], + [55.29785156249997, 71.93535156250005], + [56.45439453125002, 71.10737304687504], + [57.62539062500005, 70.72880859374999], + [57.14589843750005, 70.58911132812506], + [56.38574218749997, 70.73413085937503], + [56.49970703125004, 70.56640625000003], + [55.687304687500074, 70.69218749999999], + [54.60117187500006, 70.68007812500002], + [53.383593750000074, 70.87353515625], + [53.670507812500006, 71.08691406250003], + [54.155664062499994, 71.12548828125], + [53.40996093750002, 71.34013671875002], + [53.41162109375003, 71.530126953125], + [51.93789062500005, 71.47470703124998], + [51.511328125, 71.64809570312497], + [51.58251953124997, 72.07119140625], + [52.252050781250006, 72.12973632812503], + [52.66191406250002, 72.33686523437495], + [52.91660156250006, 72.66889648437501], + [52.5792968750001, 72.791357421875], + [53.3698242187501, 72.91674804687506], + [53.2511718750001, 73.182958984375], + [54.80390625000004, 73.38764648437498], + [55.31982421875003, 73.30830078124998] + ] + ], + [ + [ + [70.67392578125006, 73.09501953125005], + [70.04072265625004, 73.03715820312507], + [69.99589843750002, 73.359375], + [70.94023437500002, 73.51440429687503], + [71.6261718750001, 73.17397460937497], + [70.67392578125006, 73.09501953125005] + ] + ], + [ + [ + [142.18486328125007, 73.89589843750005], + [143.34375, 73.56875], + [143.45146484375007, 73.231298828125], + [141.59667968750003, 73.31083984375005], + [140.66279296875004, 73.45200195312503], + [139.785546875, 73.35522460937503], + [141.08476562500002, 73.86586914062497], + [142.18486328125007, 73.89589843750005] + ] + ], + [ + [ + [83.5490234375001, 74.07177734375], + [82.8177734375, 74.09160156250005], + [83.14980468750005, 74.151611328125], + [83.5490234375001, 74.07177734375] + ] + ], + [ + [ + [141.01025390625003, 73.99946289062501], + [140.40947265625002, 73.92167968750005], + [140.1935546875001, 74.23671875000002], + [141.03857421875003, 74.24272460937502], + [141.01025390625003, 73.99946289062501] + ] + ], + [ + [ + [113.38720703124997, 74.40043945312499], + [112.78242187500004, 74.09506835937503], + [111.50341796874997, 74.35307617187502], + [111.87978515625, 74.36381835937499], + [112.08447265624997, 74.54897460937505], + [113.38720703124997, 74.40043945312499] + ] + ], + [ + [ + [86.653125, 74.981298828125], + [87.05214843750005, 74.982568359375], + [86.92714843750005, 74.83076171874998], + [86.25859375000002, 74.89350585937498], + [86.653125, 74.981298828125] + ] + ], + [ + [ + [82.17236328125003, 75.41938476562501], + [81.97851562499997, 75.24711914062499], + [81.65478515625003, 75.28891601562498], + [81.71210937500004, 75.45141601562506], + [82.165625, 75.515625], + [82.17236328125003, 75.41938476562501] + ] + ], + [ + [ + [146.79521484375007, 75.37075195312505], + [148.43242187500002, 75.41352539062495], + [148.59013671875007, 75.23637695312502], + [150.82236328125006, 75.15654296875002], + [150.64628906250002, 74.944580078125], + [149.596875, 74.77260742187505], + [148.296875, 74.80043945312502], + [146.14853515625012, 75.19829101562499], + [146.5375, 75.58178710937506], + [146.79521484375007, 75.37075195312505] + ] + ], + [ + [ + [135.9486328125, 75.40957031250005], + [135.45195312500007, 75.38955078124997], + [135.6986328125, 75.84526367187499], + [136.16894531249997, 75.60556640625], + [135.9486328125, 75.40957031250005] + ] + ], + [ + [ + [140.04873046875, 75.82895507812503], + [140.81591796874997, 75.63071289062498], + [141.48544921875012, 76.13715820312495], + [142.66953125000012, 75.86342773437497], + [143.68583984375002, 75.86367187500002], + [145.35996093750006, 75.53046874999998], + [144.01972656250004, 75.04467773437506], + [143.1703125, 75.11689453125001], + [142.72949218749997, 75.33764648437506], + [142.941796875, 75.71328125000002], + [142.30791015625007, 75.69169921875005], + [142.19882812500006, 75.39267578124998], + [143.12792968749997, 74.9703125], + [142.47275390625006, 74.82041015625], + [141.98730468750003, 74.99125976562499], + [140.26787109375002, 74.846923828125], + [139.68125, 74.96406249999995], + [139.09912109374997, 74.65654296875002], + [138.09228515625003, 74.79746093750003], + [136.94765625000005, 75.32553710937498], + [137.28974609375004, 75.34863281249997], + [137.26884765625002, 75.7494140625], + [137.70654296875003, 75.75957031250002], + [137.56054687499997, 75.95522460937502], + [138.20761718750006, 76.11494140624995], + [138.91953125000006, 76.19672851562501], + [140.04873046875, 75.82895507812503] + ] + ], + [ + [ + [96.5324218750001, 76.278125], + [96.30058593750002, 76.121728515625], + [95.31113281250006, 76.21474609375002], + [95.37988281250003, 76.2890625], + [96.5324218750001, 76.278125] + ] + ], + [ + [ + [112.47802734375003, 76.62089843749999], + [112.531640625, 76.450048828125], + [111.96894531250004, 76.62617187500001], + [112.47802734375003, 76.62089843749999] + ] + ], + [ + [ + [149.15019531250002, 76.65991210937506], + [148.39863281250004, 76.64824218750007], + [149.4064453125001, 76.78208007812498], + [149.15019531250002, 76.65991210937506] + ] + ], + [ + [ + [67.7653320312501, 76.23759765624999], + [61.35595703124997, 75.31484375000002], + [60.27685546875003, 75.00756835937503], + [60.501367187499994, 74.90463867187503], + [59.67402343750004, 74.61015624999999], + [59.24013671875005, 74.69296874999998], + [59.040429687499994, 74.48554687500001], + [58.53466796875003, 74.49892578124997], + [58.6178710937501, 74.22739257812498], + [57.76738281250002, 74.013818359375], + [57.755957031250006, 73.769189453125], + [57.313085937500006, 73.838037109375], + [57.54257812500006, 73.65820312500003], + [56.96386718750003, 73.36655273437503], + [56.43037109375004, 73.29721679687503], + [55.00683593750003, 73.45385742187506], + [54.29990234375006, 73.35097656249997], + [53.7628906250001, 73.76616210937499], + [54.64267578125006, 73.95957031250006], + [55.34091796875006, 74.41962890624998], + [56.13710937500005, 74.49609375000003], + [55.5822265625001, 74.627685546875], + [56.4987304687501, 74.95708007812505], + [55.81005859374997, 75.12490234374997], + [56.03554687499999, 75.19423828124997], + [56.57031250000003, 75.09775390625003], + [56.8444335937501, 75.351416015625], + [57.606835937499994, 75.34125976562498], + [58.05830078125004, 75.6630859375], + [58.88125, 75.85478515625007], + [60.27929687499997, 76.09624023437505], + [60.94218750000002, 76.07128906250003], + [61.20166015624997, 76.28203125000007], + [62.97148437500002, 76.23666992187498], + [64.4634765625, 76.37817382812503], + [67.65185546874997, 77.011572265625], + [68.48574218750005, 76.93369140625003], + [68.94169921875002, 76.707666015625], + [67.7653320312501, 76.23759765624999] + ] + ], + [ + [ + [96.28544921875002, 77.02666015625007], + [95.27031250000007, 77.01884765624999], + [96.52841796875006, 77.20551757812501], + [96.28544921875002, 77.02666015625007] + ] + ], + [ + [ + [89.51425781250006, 77.18881835937498], + [89.14169921875012, 77.22680664062497], + [89.61621093749997, 77.31103515625], + [89.51425781250006, 77.18881835937498] + ] + ], + [ + [ + [130.68730468750007, 42.30253906249999], + [130.52695312500012, 42.535400390625], + [130.42480468749997, 42.72705078124997], + [131.06855468750004, 42.90224609375005], + [131.25732421875003, 43.378076171874994], + [131.2552734375, 44.07158203124999], + [130.9816406250001, 44.844335937500034], + [131.44687500000012, 44.984033203124966], + [131.85185546875002, 45.32685546875001], + [132.93603515624997, 45.029931640624994], + [133.1134765625001, 45.130712890625006], + [133.18603515625003, 45.49482421875004], + [133.43642578125, 45.60468750000004], + [133.86132812500003, 46.24775390625004], + [134.1676757812501, 47.30219726562501], + [134.75234375, 47.71542968749998], + [134.56601562500006, 48.02250976562502], + [134.66523437500004, 48.25390625], + [134.29335937500005, 48.37343750000002], + [133.46835937500006, 48.09716796875003], + [133.14404296875003, 48.10566406249998], + [132.7072265625001, 47.94726562500006], + [132.47626953125004, 47.714990234374994], + [130.96191406249997, 47.70932617187498], + [130.7326171875001, 48.01923828124998], + [130.80429687500012, 48.34150390624998], + [130.5521484375, 48.602490234374955], + [130.553125, 48.861181640625006], + [130.1959960937501, 48.89165039062499], + [129.49814453125012, 49.38881835937502], + [129.0651367187501, 49.374658203124966], + [128.70400390625, 49.60014648437499], + [127.99960937500006, 49.56860351562506], + [127.55078124999997, 49.801806640625045], + [127.590234375, 50.20898437500003], + [127.33720703125007, 50.35014648437502], + [127.30703125000005, 50.70795898437501], + [126.92480468749997, 51.10014648437496], + [126.34169921875, 52.36201171875001], + [125.64902343750012, 53.042285156250045], + [125.075, 53.20366210937496], + [124.81230468750002, 53.133837890625045], + [123.6078125, 53.546533203124994], + [120.98544921875012, 53.28457031250002], + [120.09453125000007, 52.787207031250034], + [120.0675781250001, 52.632910156250034], + [120.65615234375, 52.56665039062503], + [120.74980468750007, 52.096533203125006], + [120.06689453125003, 51.60068359375006], + [119.16367187500006, 50.40600585937503], + [119.34628906250012, 50.278955078124994], + [119.25986328125012, 50.06640625000003], + [118.4515625, 49.84448242187503], + [117.8734375, 49.51347656250002], + [116.6833007812501, 49.82377929687499], + [116.551171875, 49.92031250000002], + [116.35117187500012, 49.97807617187499], + [116.21679687500003, 50.00927734375003], + [116.13457031250002, 50.01079101562499], + [115.9259765625001, 49.95214843750003], + [115.79521484375002, 49.90590820312502], + [115.71777343750003, 49.88061523437503], + [115.58798828125006, 49.88603515624996], + [115.42919921874997, 49.89648437499997], + [115.36503906250002, 49.911767578124966], + [115.27451171875006, 49.948876953124994], + [115.00332031250005, 50.138574218749994], + [114.74316406249997, 50.23369140625002], + [114.29707031250004, 50.27441406250006], + [113.57421874999997, 50.00703125000001], + [113.44550781250004, 49.94160156250001], + [113.31904296875004, 49.87431640624999], + [113.16416015625012, 49.79716796874999], + [113.09208984375007, 49.692529296874994], + [113.05556640625, 49.61625976562499], + [112.91484375000002, 49.569238281249994], + [112.80644531250007, 49.52358398437502], + [112.69736328125012, 49.50727539062498], + [112.49492187500002, 49.532324218750034], + [112.07968750000006, 49.42421875000002], + [111.42929687500006, 49.342626953125034], + [111.3366210937501, 49.355859374999966], + [111.20419921875012, 49.304296875000034], + [110.82792968750002, 49.16616210937505], + [110.70976562500002, 49.14296875000002], + [110.42783203125006, 49.219970703125], + [110.32138671875012, 49.215869140625045], + [110.19990234375004, 49.17041015625003], + [109.5287109375, 49.269873046875034], + [109.45371093750012, 49.29633789062501], + [109.23671875000005, 49.334912109374955], + [108.61367187500005, 49.32280273437499], + [108.52246093750003, 49.34150390624998], + [108.4069335937501, 49.39638671875005], + [107.96542968750012, 49.65351562500004], + [107.91660156250012, 49.947802734375045], + [107.63095703125012, 49.98310546875004], + [107.3470703125, 49.986669921875034], + [107.23330078125, 49.989404296874994], + [107.14306640625003, 50.03300781249999], + [107.04023437500004, 50.086474609375045], + [106.94130859375005, 50.19667968750002], + [106.71113281250004, 50.312597656250006], + [106.57441406250004, 50.32880859375004], + [106.36845703125002, 50.317578124999955], + [106.21787109375006, 50.304589843749966], + [105.38359375000002, 50.47373046874998], + [104.07871093750012, 50.15424804687498], + [103.63291015625006, 50.138574218749994], + [103.49628906250004, 50.16494140625005], + [103.42119140625002, 50.18706054687502], + [103.3043945312501, 50.200292968750034], + [102.28837890625007, 50.58510742187502], + [102.31660156250004, 50.71845703125001], + [102.21503906250004, 50.82944335937506], + [102.19453125000004, 51.05068359375002], + [102.15195312500006, 51.107519531250034], + [102.14238281250007, 51.21606445312503], + [102.16005859375005, 51.260839843750006], + [102.1556640625, 51.31376953124996], + [102.1115234375001, 51.353466796874955], + [101.97919921875004, 51.382226562499966], + [101.82119140625, 51.421044921874966], + [101.57089843750006, 51.46718750000005], + [101.38125, 51.45263671875], + [100.53623046875006, 51.713476562500034], + [100.46894531250004, 51.72607421875003], + [100.23037109375, 51.729833984375006], + [100.0345703125, 51.73710937499996], + [99.92167968750002, 51.755517578124994], + [99.71923828124997, 51.87163085937502], + [98.89316406250006, 52.11728515625006], + [98.64052734375005, 51.80117187500005], + [98.103125, 51.483544921874994], + [97.82529296875012, 50.985253906249994], + [97.953125, 50.85517578124998], + [98.02978515625003, 50.64462890624998], + [98.07890625000002, 50.60380859375002], + [98.14501953124997, 50.56855468750001], + [98.22050781250007, 50.55717773437502], + [98.2794921875001, 50.53325195312502], + [98.25029296875002, 50.30244140624998], + [98.00390625000003, 50.01425781249998], + [97.35976562500005, 49.741455078125], + [97.20859375000006, 49.73081054687506], + [96.98574218750005, 49.88281250000003], + [96.31503906250012, 49.90112304687503], + [96.06552734375006, 49.99873046875001], + [95.52265625000004, 49.911230468750034], + [95.11142578125012, 49.935449218749994], + [94.93027343750006, 50.04375], + [94.8112304687501, 50.04819335937506], + [94.71806640625002, 50.04326171875002], + [94.67548828125004, 50.02807617187506], + [94.61474609375003, 50.02373046874996], + [94.56464843750004, 50.08793945312499], + [94.35468750000004, 50.221826171874994], + [94.25107421875006, 50.55639648437503], + [93.103125, 50.60390625000002], + [92.94130859375005, 50.77822265625002], + [92.85644531250003, 50.78911132812502], + [92.77929687500003, 50.778662109375006], + [92.738671875, 50.71093749999997], + [92.68134765625004, 50.683203125], + [92.6266601562501, 50.68828124999999], + [92.57890625000002, 50.725439453125006], + [92.42636718750006, 50.803076171875006], + [92.35478515625002, 50.86416015625002], + [92.29580078125, 50.84980468750004], + [92.19238281249997, 50.700585937499994], + [91.80429687500006, 50.693603515625], + [91.4464843750001, 50.52216796874998], + [91.41503906249997, 50.46801757812506], + [91.34082031249997, 50.470068359375034], + [91.30058593750002, 50.46337890625], + [91.2337890625, 50.45239257812497], + [91.02158203125012, 50.41547851562501], + [90.83808593750004, 50.32373046874997], + [90.76074218749997, 50.30595703124999], + [90.71435546874997, 50.25942382812502], + [90.65507812500007, 50.22236328125001], + [90.05371093750003, 50.09375], + [89.64384765625002, 49.90302734374998], + [89.65410156250007, 49.71748046875001], + [89.57919921875006, 49.69970703125003], + [89.475, 49.66054687500005], + [89.39560546875006, 49.61152343750001], + [89.24394531250007, 49.62705078125006], + [89.20292968750007, 49.59570312499997], + [89.17998046875002, 49.5322265625], + [89.10947265625012, 49.50136718750002], + [89.00839843750006, 49.472802734374994], + [88.97060546875, 49.483740234375006], + [88.94541015625012, 49.50766601562498], + [88.90019531250002, 49.53969726562502], + [88.86386718750006, 49.52763671874996], + [88.83164062500012, 49.44843749999998], + [88.633203125, 49.486132812500045], + [88.19257812500004, 49.451708984375045], + [88.13554687500002, 49.38149414062502], + [88.11572265624997, 49.25629882812501], + [88.0285156250001, 49.219775390625045], + [87.98808593750002, 49.186914062499994], + [87.9347656250001, 49.16455078124997], + [87.81425781250002, 49.162304687499955], + [87.7625, 49.16582031249996], + [87.5158203125001, 49.122412109375006], + [87.41669921875004, 49.07661132812501], + [87.32285156250012, 49.085791015625006], + [86.62646484374997, 49.56269531250001], + [86.67548828125004, 49.77729492187501], + [86.1808593750001, 49.49931640624996], + [85.2326171875001, 49.61582031249998], + [84.9894531250001, 50.061425781249994], + [84.32324218749997, 50.239160156249966], + [83.94511718750007, 50.774658203125], + [83.35732421875005, 50.99458007812504], + [82.76083984375012, 50.89335937500002], + [82.49394531250007, 50.72758789062499], + [81.46591796875006, 50.73984375], + [81.38828125000006, 50.95649414062501], + [81.0714843750001, 50.96875], + [81.12724609375002, 51.19106445312502], + [80.73525390625, 51.29340820312498], + [80.44804687500002, 51.18334960937503], + [80.42363281250002, 50.94628906249997], + [79.98623046875, 50.774560546874966], + [77.85996093750006, 53.269189453124994], + [76.48476562500005, 54.02255859374998], + [76.42167968750007, 54.151513671874966], + [76.65458984375007, 54.14526367187503], + [76.8373046875, 54.4423828125], + [75.43720703125004, 54.08964843749999], + [75.22021484374997, 53.89379882812506], + [74.45195312500007, 53.64726562500002], + [74.35156250000003, 53.487646484375006], + [73.85898437500006, 53.61972656249998], + [73.40693359375004, 53.44755859374999], + [73.30566406250003, 53.707226562499955], + [73.71240234375003, 54.04238281250002], + [73.22988281250005, 53.957812500000045], + [72.62226562500004, 54.13432617187502], + [72.44677734375003, 53.94184570312498], + [72.18603515625003, 54.32563476562501], + [72.00449218750006, 54.20566406249998], + [71.09316406250005, 54.21220703124999], + [71.18554687500003, 54.59931640624998], + [70.73808593750007, 55.30517578125], + [70.18242187500002, 55.162451171875034], + [68.9772460937501, 55.389599609374955], + [68.20625, 55.16093750000002], + [68.15585937500006, 54.97670898437505], + [65.476953125, 54.62329101562497], + [65.08837890624997, 54.340185546875034], + [64.46123046875002, 54.38417968750002], + [61.92871093750003, 53.94648437500004], + [61.231054687500006, 54.01948242187498], + [60.97949218749997, 53.62172851562505], + [61.53496093750002, 53.52329101562506], + [61.22890625, 53.445898437500006], + [61.19921874999997, 53.28715820312502], + [61.65986328125004, 53.22846679687504], + [62.08271484375004, 53.00541992187499], + [61.047460937500006, 52.97246093750002], + [60.77441406249997, 52.67578124999997], + [60.99453125000005, 52.33686523437504], + [60.03027343749997, 51.93325195312505], + [60.464746093749994, 51.651171875000045], + [61.55468750000003, 51.32460937500005], + [61.38945312500002, 50.86103515625001], + [60.94228515625005, 50.69550781250004], + [60.42480468749997, 50.67915039062498], + [60.05859374999997, 50.850292968749955], + [59.812402343749994, 50.58203125], + [59.523046875, 50.492871093749955], + [59.4523437500001, 50.62041015625002], + [58.88369140625005, 50.694433593750006], + [58.359179687500074, 51.063818359375034], + [57.83886718750003, 51.091650390625006], + [57.44218750000002, 50.88886718749998], + [57.01171874999997, 51.06518554687503], + [56.49140625000004, 51.01953124999997], + [55.68623046875004, 50.582861328125006], + [54.64160156250003, 51.011572265625034], + [54.555273437500006, 50.535791015624994], + [54.139746093750006, 51.04077148437503], + [53.33808593750004, 51.48237304687504], + [52.57119140625005, 51.481640624999955], + [52.21914062499999, 51.709375], + [51.344531250000074, 51.47534179687503], + [51.16347656250005, 51.6474609375], + [50.79394531249997, 51.729199218749955], + [50.246875, 51.28950195312498], + [49.49804687500003, 51.08359375000006], + [49.32343750000004, 50.851708984374966], + [48.625097656250006, 50.61269531250005], + [48.7589843750001, 49.92832031250006], + [48.33496093750003, 49.858251953125006], + [47.7057617187501, 50.37797851562502], + [47.42919921874997, 50.35795898437502], + [46.889550781249994, 49.69697265625001], + [46.80205078125002, 49.36708984375002], + [47.031347656250006, 49.150292968749994], + [46.70263671875003, 48.80556640625002], + [46.660937500000074, 48.41225585937502], + [47.06464843750004, 48.23247070312499], + [47.292382812499994, 47.74091796875004], + [47.48193359374997, 47.80390624999998], + [48.16699218750003, 47.70878906249996], + [48.959375, 46.77460937499998], + [48.558398437500074, 46.75712890624999], + [48.54121093750004, 46.60561523437502], + [49.232226562500074, 46.33715820312503], + [48.683691406250006, 46.08618164062497], + [48.72958984375006, 45.896826171875034], + [48.4870117187501, 45.93486328124996], + [47.63330078124997, 45.58403320312499], + [47.46328125, 45.67968750000003], + [47.5294921875001, 45.530224609374955], + [47.3512695312501, 45.21772460937498], + [46.7072265625001, 44.503320312499994], + [47.30703125000005, 44.103125], + [47.462792968749994, 43.55502929687498], + [47.64648437500003, 43.88461914062498], + [47.463183593750074, 43.03505859375002], + [48.572851562500006, 41.84448242187503], + [47.79101562499997, 41.19926757812502], + [47.31767578125002, 41.28242187500001], + [46.74931640625002, 41.812597656250006], + [46.42988281250004, 41.890966796875006], + [46.21269531250002, 41.989892578124994], + [45.63857421875005, 42.20507812500003], + [45.63427734374997, 42.234716796875034], + [45.72753906249997, 42.47504882812498], + [45.70527343750004, 42.49809570312496], + [45.56289062499999, 42.53574218749998], + [45.34375, 42.52978515625003], + [45.16025390625006, 42.675], + [45.07158203125002, 42.69414062500002], + [44.94335937499997, 42.73027343750002], + [44.870996093749994, 42.75639648437499], + [44.850488281249994, 42.746826171875], + [44.77109375000006, 42.61679687499998], + [44.69179687499999, 42.709619140624966], + [44.64433593750002, 42.734716796875034], + [44.50585937500003, 42.748632812500006], + [44.329492187499994, 42.703515624999966], + [44.10273437500004, 42.616357421874994], + [44.004687500000074, 42.59560546875002], + [43.95742187500005, 42.56655273437505], + [43.825976562500074, 42.571533203125], + [43.759863281250006, 42.593847656250006], + [43.738378906250006, 42.61699218750002], + [43.74990234375005, 42.65751953125002], + [43.79541015624997, 42.702978515625034], + [43.78261718750005, 42.747021484374955], + [43.62304687500003, 42.80771484374998], + [43.5578125000001, 42.844482421875], + [43.089160156250074, 42.9890625], + [43.00019531250004, 43.04965820312506], + [42.991601562499994, 43.09150390624998], + [42.76064453125005, 43.169580078124966], + [41.58056640624997, 43.21923828124997], + [41.460742187500074, 43.276318359374955], + [41.35820312500002, 43.33339843750005], + [41.08310546875006, 43.37446289062498], + [40.94199218750006, 43.41806640624998], + [40.801660156249994, 43.479931640624955], + [40.64804687500006, 43.53388671875004], + [40.084570312500006, 43.553125], + [40.02373046875002, 43.48486328125], + [39.873632812500006, 43.47280273437502], + [38.71728515624997, 44.28808593750003], + [38.18125, 44.41967773437503], + [37.851464843749994, 44.698828125000034], + [37.49511718750003, 44.69526367187504], + [37.20478515625004, 44.97197265624999], + [36.62763671875004, 45.15131835937504], + [36.941210937500074, 45.289697265624994], + [36.72041015625004, 45.371875], + [36.8659179687501, 45.42705078124999], + [37.21357421875004, 45.272314453125006], + [37.6471679687501, 45.37719726562506], + [37.61240234375006, 45.56469726562506], + [37.93310546875003, 46.001708984375], + [38.014257812500006, 46.047753906249966], + [38.07958984375003, 45.93481445312506], + [38.18359374999997, 46.09482421875006], + [38.49228515625006, 46.09052734374998], + [37.913867187500074, 46.40649414062503], + [37.766503906249994, 46.63613281250002], + [38.50097656249997, 46.663671875000034], + [38.43867187500004, 46.813085937500006], + [39.29345703125003, 47.105761718750045], + [39.19570312499999, 47.268847656250045], + [39.023730468750074, 47.27221679687503], + [38.928320312500006, 47.175683593749994], + [38.55244140625004, 47.15034179687498], + [38.7619140625001, 47.261621093749994], + [38.21435546875003, 47.091455078124966], + [38.36884765625004, 47.609960937500006], + [38.90029296875005, 47.85512695312502], + [39.77871093750005, 47.88754882812506], + [39.95791015625005, 48.268896484375034], + [39.8356445312501, 48.54277343749996], + [39.6447265625001, 48.591210937499966], + [39.792871093749994, 48.807714843750034], + [40.00361328125004, 48.82207031250002], + [39.68652343749997, 49.007910156250034], + [40.10878906250005, 49.251562500000034], + [40.080664062500006, 49.576855468749955], + [39.780566406250074, 49.57202148437503], + [39.17480468750003, 49.85595703124997], + [38.91835937499999, 49.82470703125], + [38.258593750000074, 50.05234375], + [38.046875, 49.92001953125006], + [37.42285156249997, 50.411474609375006], + [36.619433593750074, 50.209228515625], + [36.1164062500001, 50.408544921875006], + [35.59111328125002, 50.36875], + [35.31191406250005, 51.043896484374955], + [35.0640625, 51.203417968750045], + [34.21386718750003, 51.25537109375006], + [34.12109375000003, 51.67915039062498], + [34.397851562499994, 51.780419921874994], + [33.735253906249994, 52.344775390625045], + [32.435449218749994, 52.307226562500034], + [32.12226562500004, 52.05058593749996], + [31.763378906250097, 52.10107421875003], + [31.758593750000017, 52.125830078125034], + [31.690625, 52.22065429687498], + [31.64990234374997, 52.26220703125], + [31.60156250000003, 52.284814453124994], + [31.57734375000004, 52.31230468749999], + [31.585546875, 52.532470703125], + [31.56484375, 52.75922851562501], + [31.53515624999997, 52.798242187499966], + [31.442773437499994, 52.86181640625003], + [31.35302734374997, 52.93344726562498], + [31.295117187500097, 52.98979492187499], + [31.25878906249997, 53.01669921875006], + [31.364550781250017, 53.13896484375002], + [31.388378906250097, 53.18481445312503], + [31.41787109375005, 53.196044921875], + [31.849707031250006, 53.106201171875], + [32.14199218750005, 53.091162109375034], + [32.46933593750006, 53.270312500000045], + [32.578027343749994, 53.312402343749994], + [32.644433593749994, 53.32890624999999], + [32.70429687500004, 53.33632812499999], + [32.45097656250002, 53.6533203125], + [32.20039062500004, 53.78125], + [31.99218750000003, 53.796875], + [31.82080078124997, 53.79194335937498], + [31.754199218750017, 53.81044921875002], + [31.825292968750006, 53.93500976562501], + [31.837792968749994, 54.00078124999999], + [31.825976562500074, 54.030712890624955], + [31.79199218749997, 54.05590820312503], + [31.62841796874997, 54.111181640625006], + [31.403613281250017, 54.195947265624966], + [31.299121093750017, 54.29169921875001], + [31.184765625000097, 54.452978515625006], + [31.074804687500063, 54.491796875], + [31.154882812500063, 54.610937500000034], + [31.152148437500017, 54.625341796875034], + [31.12128906250004, 54.64848632812496], + [30.984179687500074, 54.695898437500034], + [30.79882812499997, 54.78325195312499], + [30.79101562499997, 54.806005859375006], + [30.804492187500074, 54.8609375], + [30.829882812500017, 54.91499023437498], + [30.977734375000097, 55.05048828124998], + [30.977734375000097, 55.08779296875002], + [30.958886718749994, 55.13759765625005], + [30.87744140625003, 55.223437500000045], + [30.81445312499997, 55.27871093750002], + [30.81054687499997, 55.306982421875006], + [30.82099609375004, 55.330273437499955], + [30.900585937500097, 55.397412109374955], + [30.906835937500063, 55.57001953125004], + [30.625585937500006, 55.666259765625], + [30.23359375000004, 55.84521484375006], + [30.04267578125004, 55.83642578125003], + [29.93701171874997, 55.84526367187499], + [29.881640625000074, 55.83232421875002], + [29.82392578125004, 55.79511718749998], + [29.74414062499997, 55.770410156249994], + [29.630078125000097, 55.75117187499998], + [29.482226562500074, 55.6845703125], + [29.412988281249994, 55.72485351562506], + [29.35341796875005, 55.784375], + [29.375, 55.938720703125], + [28.284277343750006, 56.055908203125], + [28.14794921875003, 56.142919921875034], + [28.202050781250023, 56.260400390624994], + [28.191699218750045, 56.31557617187505], + [28.169238281250017, 56.386865234374994], + [28.11083984375, 56.51069335937501], + [28.103125, 56.545703125000045], + [27.89208984375003, 56.741064453125034], + [27.88154296875001, 56.82416992187501], + [27.848632812500057, 56.85341796875002], + [27.806054687499994, 56.86708984375005], + [27.639453125000074, 56.84565429687504], + [27.83027343750004, 57.19448242187505], + [27.83828125000008, 57.247705078124966], + [27.82861328124997, 57.293310546875006], + [27.796875, 57.316943359375045], + [27.538671875000063, 57.429785156250034], + [27.51113281250005, 57.508154296875006], + [27.469726562500057, 57.524023437500034], + [27.35195312500005, 57.528125], + [27.4, 57.66679687499999], + [27.542089843750063, 57.799414062500006], + [27.778515625000068, 57.87070312500006], + [27.502441406250057, 58.221337890624994], + [27.434179687500006, 58.787255859374994], + [28.15107421875004, 59.374414062499966], + [28.0125, 59.484277343749966], + [28.05800781250008, 59.781542968750045], + [28.334570312500034, 59.69252929687502], + [28.518164062500034, 59.849560546874955], + [28.947265625000057, 59.828759765624994], + [29.147265625000045, 59.999755859375], + [30.12255859374997, 59.873583984375074], + [30.172656250000017, 59.957128906250034], + [29.72119140624997, 60.19531249999997], + [29.069140625000017, 60.19145507812499], + [28.643164062500006, 60.375292968750045], + [28.512792968750006, 60.67729492187502], + [27.797656250000074, 60.53613281250003], + [29.69013671875004, 61.54609375000001], + [31.18671875000004, 62.48139648437504], + [31.533984375000017, 62.885400390624994], + [31.180859375000097, 63.208300781250074], + [29.991503906250074, 63.73515625000002], + [30.50390625000003, 64.02060546875], + [30.513769531250006, 64.2], + [30.04189453125005, 64.44335937499997], + [30.072851562500063, 64.76503906250005], + [29.60419921875004, 64.968408203125], + [29.826953125000017, 65.14506835937502], + [29.608007812500006, 65.248681640625], + [29.715917968750063, 65.62456054687502], + [30.102734375000097, 65.72626953125004], + [29.066210937500045, 66.89174804687497], + [29.988085937500017, 67.66826171874999], + [29.343847656250006, 68.06186523437506], + [28.685156250000034, 68.189794921875], + [28.470703125000057, 68.48837890625], + [28.77285156250005, 68.84003906249995], + [28.414062500000057, 68.90415039062506], + [28.96582031250003, 69.02197265625], + [29.38828125, 69.29814453125005], + [30.08730468750005, 69.43286132812503], + [30.18017578124997, 69.63583984375], + [30.860742187499994, 69.53842773437503], + [30.869726562500006, 69.78344726562506], + [31.546972656250063, 69.696923828125], + [31.997949218749994, 69.80991210937503], + [31.98457031250004, 69.95366210937499], + [33.00781249999997, 69.72211914062498], + [32.91503906249997, 69.60170898437497], + [32.17675781250003, 69.67402343749995], + [32.37773437500002, 69.47910156250003], + [32.99980468750002, 69.4701171875], + [32.97890625000005, 69.367333984375], + [33.45429687500004, 69.42817382812495], + [33.14121093750006, 69.068701171875], + [33.684375, 69.31025390625001], + [35.85791015625003, 69.19174804687503], + [37.73056640625006, 68.69213867187503], + [38.43017578125003, 68.35561523437505], + [39.568945312500006, 68.07172851562501], + [39.82333984375006, 68.05859375], + [39.80927734375004, 68.15083007812498], + [40.38066406250002, 67.831884765625], + [40.96640625000006, 67.71347656250003], + [41.358789062499994, 67.20966796874998], + [41.18896484375003, 66.82617187500003], + [40.10332031250002, 66.29995117187502], + [38.65390625000006, 66.06904296874995], + [35.51347656250002, 66.39580078125002], + [34.82460937499999, 66.61113281249999], + [34.48261718750004, 66.55034179687505], + [34.4515625, 66.651220703125], + [33.15019531250002, 66.84394531250001], + [32.93046875000002, 67.08681640625002], + [31.895312500000074, 67.16142578125002], + [33.65595703125004, 66.44262695312506], + [33.36054687500004, 66.32954101562501], + [34.112695312499994, 66.225244140625], + [34.69179687500005, 65.95185546874998], + [34.77695312500006, 65.76826171874998], + [34.40644531250004, 65.39575195312503], + [35.03535156250004, 64.44023437500005], + [35.802050781250074, 64.3353515625], + [36.3649414062501, 64.00283203125002], + [37.44218750000002, 63.813378906249966], + [37.9679687500001, 63.949121093749994], + [38.0622070312501, 64.09101562499995], + [37.953710937500006, 64.32011718749999], + [37.183691406250006, 64.40849609375007], + [36.6242187500001, 64.75053710937502], + [36.534570312499994, 64.93862304687497], + [36.88281249999997, 65.17236328124997], + [39.7580078125001, 64.57705078125002], + [40.05781250000004, 64.77075195312497], + [40.44492187500006, 64.7787109375], + [39.7980468750001, 65.349853515625], + [39.816503906250006, 65.59794921874999], + [41.4757812500001, 66.12343750000002], + [42.21054687500006, 66.51967773437502], + [43.23320312500002, 66.41552734375003], + [43.653125, 66.2509765625], + [43.54189453125005, 66.12338867187503], + [43.84375, 66.14238281249999], + [44.10439453125005, 66.00859374999999], + [44.42929687500006, 66.93774414062503], + [43.7824218750001, 67.25449218749998], + [44.20468750000006, 68.25375976562498], + [43.33320312500004, 68.67338867187502], + [44.04804687500004, 68.54882812499997], + [45.891992187499994, 68.47968750000001], + [46.69042968750003, 67.84882812500001], + [45.52871093750005, 67.75756835937497], + [44.90214843750002, 67.41313476562505], + [45.56220703125004, 67.18559570312507], + [45.88535156250006, 66.89106445312501], + [46.4923828125001, 66.80019531249997], + [47.65585937500006, 66.97592773437498], + [47.87470703125004, 67.58417968749998], + [48.83320312500004, 67.681494140625], + [48.75429687500005, 67.89594726562501], + [49.15527343750003, 67.87041015625005], + [51.994726562500006, 68.53876953124995], + [52.3966796875001, 68.35170898437505], + [52.72265624999997, 68.484033203125], + [52.34404296875002, 68.60815429687497], + [53.80195312500004, 68.99589843750002], + [54.49121093750003, 68.992333984375], + [53.797656250000074, 68.90747070312503], + [53.9308593750001, 68.43554687499997], + [53.260546875000074, 68.26748046875002], + [54.476171875, 68.29414062499995], + [54.86132812500003, 68.20185546874998], + [55.418066406250006, 68.56782226562501], + [56.04365234375004, 68.64887695312501], + [57.126855468749994, 68.55400390625005], + [58.17304687500004, 68.88974609375006], + [59.0573242187501, 69.00605468750004], + [59.37050781250005, 68.73837890625003], + [59.09902343750005, 68.4443359375], + [59.725683593750006, 68.35161132812502], + [59.89599609374997, 68.70634765624999], + [60.489160156249994, 68.72895507812498], + [60.93359374999997, 68.98676757812501], + [60.17060546875004, 69.59091796875], + [60.90908203125005, 69.84711914062495], + [64.19042968750003, 69.53466796875], + [64.89628906250002, 69.247802734375], + [67.00244140625003, 68.87358398437505], + [68.37119140625006, 68.31425781250005], + [69.14052734375005, 68.95063476562501], + [68.54277343750002, 68.96708984374999], + [68.00585937499997, 69.48002929687505], + [67.62412109375, 69.58442382812501], + [67.06445312500003, 69.69370117187498], + [66.89667968750004, 69.55380859374998], + [67.28476562500006, 70.73872070312498], + [67.14335937500002, 70.83754882812502], + [66.70224609375006, 70.81850585937497], + [66.63964843749997, 71.08139648437498], + [68.2692382812501, 71.68281250000001], + [69.61181640625003, 72.98193359375], + [69.73828124999997, 72.88496093749998], + [71.5001953125001, 72.91367187500003], + [72.812109375, 72.69140624999997], + [72.57412109375, 72.01254882812506], + [71.86728515625, 71.457373046875], + [72.70449218750005, 70.96323242187498], + [72.5767578125, 68.96870117187498], + [73.59169921875005, 68.48188476562501], + [73.13945312500002, 68.18134765624998], + [73.06679687500005, 67.766943359375], + [71.84746093750002, 67.00761718750005], + [71.36523437500003, 66.96152343749998], + [71.53955078125003, 66.68310546875], + [70.72490234375007, 66.51943359374997], + [70.38281249999997, 66.60249023437501], + [70.69072265625002, 66.74531249999998], + [70.2833984375001, 66.68579101562503], + [69.8771484375001, 66.84545898437506], + [69.21777343749997, 66.82861328125], + [69.01347656250002, 66.78833007812503], + [69.19433593749997, 66.57866210937505], + [70.33945312500006, 66.34238281250006], + [71.35800781250006, 66.35942382812505], + [71.91699218749997, 66.24672851562502], + [72.32158203125002, 66.33212890625], + [72.4173828125, 66.56079101562506], + [73.79208984375, 66.99531250000001], + [74.07451171875007, 67.41411132812499], + [74.76953124999997, 67.76635742187497], + [74.39140625000007, 68.42060546874995], + [74.57958984375003, 68.751220703125], + [76.10751953125006, 68.975732421875], + [76.45917968750004, 68.97827148437497], + [77.2384765625001, 68.46958007812498], + [77.17441406250012, 67.77851562499998], + [77.77158203125006, 67.57026367187501], + [78.92246093750006, 67.58911132812503], + [77.58828125000005, 67.75190429687498], + [77.66484375000002, 68.19038085937495], + [77.99511718749997, 68.25947265624998], + [77.65068359375007, 68.90302734375001], + [76.00097656249997, 69.23505859374998], + [75.42001953125, 69.23862304687498], + [74.81484375, 69.09057617187503], + [73.83603515625006, 69.143212890625], + [73.578125, 69.80297851562503], + [74.34335937500006, 70.57871093749998], + [73.08623046875007, 71.44492187500006], + [73.67177734375, 71.84506835937503], + [74.99218749999997, 72.14482421874999], + [74.78681640625004, 72.811865234375], + [75.15244140625, 72.85273437499998], + [75.74140625000004, 72.29624023437503], + [75.273828125, 71.95893554687495], + [75.33203125000003, 71.34174804687498], + [76.92900390625002, 71.12788085937504], + [77.58964843750007, 71.16791992187501], + [78.32060546875002, 70.93041992187503], + [78.94218750000002, 70.93378906250001], + [79.08388671875, 71.00200195312505], + [78.58769531250007, 70.993896484375], + [78.21259765625004, 71.26630859374998], + [76.43339843750002, 71.55249023437503], + [76.03242187500004, 71.91040039062503], + [76.87138671875002, 72.03300781250005], + [77.77753906250004, 71.83642578125006], + [78.23242187500003, 71.95229492187502], + [78.01640625000007, 72.092041015625], + [77.49287109375004, 72.07172851562504], + [77.47158203125, 72.19213867187506], + [78.22539062500007, 72.37744140625006], + [79.4220703125001, 72.38076171875002], + [80.7625, 72.08916015625002], + [81.66162109374997, 71.71596679687502], + [82.75781250000003, 71.76411132812498], + [83.23359375000004, 71.66816406249995], + [82.32285156250006, 71.26000976562503], + [82.16318359375012, 70.59814453125003], + [82.22119140625003, 70.39570312499998], + [82.86914062499997, 70.95483398437503], + [83.03017578125, 70.58051757812498], + [82.6823242187501, 70.21772460937498], + [83.0807617187501, 70.09301757812497], + [83.07382812500012, 70.276708984375], + [83.73593750000006, 70.54648437499998], + [83.15126953125005, 71.10361328124998], + [83.534375, 71.68393554687498], + [83.20029296875012, 71.87470703125004], + [82.64541015625005, 71.92524414062504], + [82.09365234375, 72.26542968750005], + [80.82705078125005, 72.48828124999997], + [80.84160156250007, 72.94916992187498], + [80.4245117187501, 73.23115234374998], + [80.5832031250001, 73.56845703125003], + [85.20058593750005, 73.72153320312506], + [86.89296875, 73.88710937500002], + [85.79257812500012, 73.438330078125], + [86.67705078125002, 73.10678710937503], + [85.93896484374997, 73.45649414062495], + [87.12011718750003, 73.61503906250002], + [87.57119140625, 73.81074218750001], + [86.57109375000007, 74.24375], + [86.0013671875, 74.316015625], + [86.39580078125007, 74.45009765624997], + [86.89794921874997, 74.32534179687497], + [87.22968750000004, 74.3638671875], + [85.79101562499997, 74.6451171875], + [86.20126953125006, 74.81621093750005], + [86.65146484375012, 74.68242187500005], + [87.04179687500007, 74.77885742187499], + [87.46757812500002, 75.01323242187505], + [86.93906250000006, 75.06811523437503], + [87.00595703125012, 75.16982421874997], + [87.67138671874997, 75.12958984375004], + [90.18496093750005, 75.59106445312497], + [94.07519531249997, 75.91289062499999], + [92.89042968750002, 75.90996093750002], + [93.25927734375003, 76.09877929687502], + [95.57871093750012, 76.13730468749998], + [96.07548828125007, 76.08198242187498], + [95.65332031250003, 75.89218750000003], + [96.50859375000002, 76.00556640624995], + [96.49707031249997, 75.89121093750003], + [98.66201171875005, 76.24267578125003], + [99.77041015625, 76.02875976562498], + [99.5407226562501, 75.79858398437497], + [99.85136718750007, 75.93027343749998], + [99.8253906250001, 76.13593749999995], + [98.80566406250003, 76.48066406250004], + [100.84375, 76.52519531250005], + [101.59775390625006, 76.43920898437503], + [100.92802734375002, 76.55673828124998], + [100.98994140625004, 76.99047851562497], + [102.61015625000007, 77.508544921875], + [104.01455078125, 77.73041992187501], + [106.05957031249997, 77.39052734375002], + [104.20244140625002, 77.101806640625], + [106.9416015625001, 77.034375], + [107.42978515625006, 76.92656250000002], + [106.41357421874997, 76.51225585937499], + [107.72216796875003, 76.52231445312498], + [108.18164062500003, 76.73784179687502], + [111.39248046875, 76.686669921875], + [112.09394531250004, 76.48032226562506], + [111.94267578125002, 76.38046875000003], + [112.61953125, 76.38354492187506], + [112.65625, 76.05356445312498], + [113.2726562500001, 76.25166015625001], + [113.5638671875, 75.89165039062502], + [113.85722656250007, 75.92128906250002], + [113.56757812500004, 75.56840820312499], + [112.45302734375, 75.83017578125003], + [112.95566406250006, 75.571923828125], + [113.24296875000007, 75.61142578125003], + [113.72617187500012, 75.45063476562498], + [112.92490234375012, 75.01503906249997], + [109.84033203124997, 74.32197265624998], + [109.8102539062501, 74.16918945312503], + [108.19951171875002, 73.69409179687497], + [107.27109375000006, 73.62104492187501], + [106.67939453125004, 73.3306640625], + [106.1886718750001, 73.3080078125], + [105.14394531250005, 72.77705078125001], + [105.7082031250001, 72.836669921875], + [106.47792968750005, 73.13940429687503], + [107.750390625, 73.17314453125007], + [109.33105468749997, 73.48745117187497], + [109.85527343750002, 73.47246093750002], + [110.86816406249997, 73.73071289062497], + [109.70673828125004, 73.74375], + [110.2614257812501, 74.01743164062503], + [111.05625, 73.93935546875002], + [111.13085937500003, 74.05283203125003], + [111.55058593750007, 74.02851562499998], + [111.22812500000012, 73.96855468750002], + [111.40039062500003, 73.827734375], + [112.14726562500007, 73.70893554687498], + [112.79541015625003, 73.74609375], + [112.83593750000003, 73.96206054687502], + [113.03281250000006, 73.91386718750007], + [113.4162109375001, 73.647607421875], + [113.15693359375004, 73.45957031249998], + [113.49091796875004, 73.34609375000002], + [113.12783203125, 72.8306640625], + [113.66455078124997, 72.63452148437503], + [113.2155273437501, 72.80585937500001], + [113.88623046875003, 73.34580078124998], + [113.51035156250012, 73.50498046874998], + [115.33769531250007, 73.70258789062501], + [118.87089843750007, 73.53789062500002], + [118.45703124999997, 73.46440429687507], + [118.43027343750012, 73.24653320312501], + [119.750390625, 72.97910156250006], + [122.26015625, 72.88056640624995], + [122.75195312500003, 72.906494140625], + [122.61523437499997, 73.02792968750006], + [123.1603515625001, 72.95488281250002], + [123.62226562500004, 73.19326171875], + [123.49111328125005, 73.666357421875], + [124.54121093750004, 73.75126953125007], + [125.59853515625005, 73.447412109375], + [126.25449218750012, 73.548193359375], + [126.55253906250007, 73.33491210937498], + [127.03134765625006, 73.54746093750003], + [127.74033203125012, 73.48154296875], + [129.10058593750003, 73.11235351562502], + [128.5990234375, 72.895166015625], + [129.01728515625004, 72.8724609375], + [129.250390625, 72.70517578125003], + [128.41826171875002, 72.53515625000003], + [129.28134765625006, 72.43769531249998], + [129.41064453124997, 72.16630859375002], + [128.93496093750005, 72.07949218750002], + [127.8034179687501, 72.43403320312504], + [127.84140625000012, 72.308251953125], + [128.91142578125002, 71.75532226562495], + [129.21025390625007, 71.91694335937501], + [129.46083984375, 71.73930664062499], + [128.84326171875003, 71.6634765625], + [129.76191406250004, 71.11953125000002], + [130.53710937500003, 70.89252929687495], + [130.75712890625002, 70.96235351562498], + [131.02158203125006, 70.74609374999997], + [132.0353515625001, 71.24404296875], + [132.65390625000006, 71.92597656250001], + [133.6888671875, 71.434228515625], + [134.70273437500012, 71.38681640625003], + [135.55917968750006, 71.6103515625], + [136.09033203125003, 71.61958007812501], + [137.9396484375001, 71.1333984375], + [137.84404296875007, 71.22680664062503], + [138.31406250000006, 71.32553710937498], + [137.918359375, 71.38408203124999], + [138.23417968750007, 71.596337890625], + [138.78017578125, 71.62900390624998], + [139.209375, 71.44477539062501], + [139.98417968750007, 71.49150390625005], + [139.72294921875002, 71.88496093749998], + [139.35927734375005, 71.95136718750001], + [140.18769531250004, 72.19130859374997], + [139.17636718750006, 72.16347656249997], + [139.14082031250004, 72.32973632812502], + [139.60117187500012, 72.49609374999997], + [141.07929687500004, 72.5869140625], + [140.80820312500006, 72.89096679687503], + [142.06142578125005, 72.72080078125], + [146.25292968749997, 72.442236328125], + [146.234765625, 72.34970703125], + [144.77636718749997, 72.38227539062495], + [144.16923828125002, 72.25878906250003], + [144.29492187499997, 72.19262695312497], + [146.83183593750007, 72.29541015625003], + [146.11328125000003, 71.94497070312497], + [146.23027343750007, 72.1375], + [145.75859375000007, 72.22587890624999], + [145.75673828125005, 71.94130859375002], + [145.06396484374997, 71.92607421875002], + [145.18857421875012, 71.69580078125], + [146.07324218749997, 71.80834960937503], + [147.26181640625006, 72.327880859375], + [149.50156250000012, 72.16430664062497], + [150.01689453125002, 71.89565429687505], + [149.04873046875005, 71.79575195312503], + [148.9681640625, 71.69047851562499], + [150.59980468750004, 71.5201171875], + [150.09765624999997, 71.22656249999997], + [150.96777343749997, 71.38046874999998], + [151.58242187500005, 71.28696289062503], + [152.09277343749997, 71.02329101562503], + [151.76201171875002, 70.98247070312499], + [152.50878906250003, 70.83447265625003], + [156.68457031250003, 71.09375], + [158.03701171875005, 71.03925781250001], + [159.35068359375006, 70.79072265625001], + [160.00644531250006, 70.30966796875006], + [159.72939453125005, 69.87021484375006], + [160.91074218750012, 69.60634765625002], + [161.03554687500005, 69.09819335937507], + [161.30986328125007, 68.98227539062498], + [160.85605468750006, 68.53833007812506], + [161.565625, 68.90517578125], + [161.53691406250002, 69.379541015625], + [162.16601562499997, 69.61157226562503], + [163.20136718750004, 69.71474609375], + [166.82031250000003, 69.49956054687505], + [167.8568359375, 69.72822265624998], + [168.30302734375002, 69.27148437500003], + [169.31064453125006, 69.07954101562498], + [169.60986328124997, 68.78603515624997], + [170.53759765624997, 68.82539062500001], + [170.99541015625002, 69.04531250000005], + [170.58222656250004, 69.58334960937506], + [170.16093750000007, 69.62656249999998], + [170.48681640625003, 70.107568359375], + [173.27744140625006, 69.823828125], + [173.43867187500004, 69.94682617187502], + [175.92148437500012, 69.89531250000002], + [179.27265624999998, 69.25966796875002], + [180, 68.98344726562505], + [180, 65.06723632812498], + [178.51953125000003, 64.60297851562498], + [177.7486328125, 64.71704101562503], + [176.88085937499997, 65.08193359375002], + [176.34101562500015, 65.04731445312501], + [177.03730468750004, 64.99965820312497], + [177.22285156250004, 64.861669921875], + [177.06875, 64.78666992187502], + [176.06113281250012, 64.96088867187498], + [174.54882812500009, 64.68388671875005], + [176.0565429687501, 64.90473632812498], + [176.35097656250005, 64.70512695312502], + [176.14091796875007, 64.58583984375005], + [177.42744140625015, 64.76337890624998], + [177.43291015625002, 64.44448242187502], + [177.6875, 64.30473632812507], + [178.04472656250013, 64.21958007812503], + [178.22949218749991, 64.36440429687497], + [178.38144531250018, 64.26088867187502], + [178.73144531250003, 63.667089843750006], + [178.44042968750009, 63.605566406250006], + [178.74404296874994, 63.39477539062503], + [178.79296874999997, 63.54033203125002], + [179.38857421875, 63.14721679687497], + [179.25957031250002, 63.00830078125], + [179.5705078125001, 62.6875], + [179.12070312500012, 62.32036132812499], + [177.292578125, 62.59902343750002], + [177.33896484375006, 62.781347656250034], + [177.02353515625012, 62.777246093749994], + [177.15947265625007, 62.56098632812498], + [174.51435546875015, 61.823632812499966], + [173.6234375, 61.716064453125], + [173.13183593749997, 61.40664062500002], + [172.85654296875006, 61.469189453124955], + [172.90800781250002, 61.311621093750006], + [172.39609375000006, 61.16738281250002], + [172.39277343750004, 61.061767578125], + [170.60820312500007, 60.434912109375034], + [170.3509765625, 59.965527343749955], + [169.9826171875001, 60.067089843749955], + [169.2267578125001, 60.59594726562497], + [168.1375, 60.57392578125001], + [167.22675781250004, 60.406298828125045], + [166.27304687500012, 59.85625], + [166.13603515625007, 59.979345703125034], + [166.35214843750006, 60.48481445312498], + [165.08457031250006, 60.09858398437498], + [164.95371093750006, 59.843603515625006], + [164.52529296875, 60.06127929687503], + [164.11328125000003, 59.89755859374998], + [164.13505859375002, 59.984375], + [163.74384765625004, 60.02802734374998], + [163.36484375000012, 59.78144531250004], + [163.27285156250005, 59.302587890625006], + [162.14160156249997, 58.44741210937502], + [161.96005859375012, 58.07690429687506], + [162.39140625000002, 57.717236328124955], + [162.65429687499997, 57.94824218750003], + [163.22578125000004, 57.790380859375034], + [162.77929687500003, 57.35761718749998], + [162.79111328125012, 56.875390624999966], + [162.92207031250004, 56.72265625000003], + [163.2565429687501, 56.68803710937499], + [163.33554687500012, 56.232519531250006], + [163.04736328125003, 56.044677734375], + [162.84033203125003, 56.065625], + [162.628125, 56.232275390625034], + [163.03837890625002, 56.521875], + [162.67148437500006, 56.49008789062498], + [162.52822265625005, 56.260693359374955], + [162.08496093749997, 56.08964843750002], + [161.72392578125002, 55.49614257812499], + [162.10556640625006, 54.75214843750004], + [161.62480468750002, 54.51625976562502], + [160.77265625000004, 54.54135742187498], + [160.0744140625001, 54.18916015625001], + [159.84375, 53.78364257812498], + [160.02509765625004, 53.129589843749955], + [159.58593750000003, 53.237695312499966], + [158.74541015625002, 52.90893554687506], + [158.47207031250005, 53.032373046874966], + [158.6087890625, 52.873632812500034], + [158.49316406249997, 52.383154296875034], + [158.10351562500003, 51.80961914062499], + [156.84746093750002, 51.006591796875], + [156.74775390625004, 50.969287109375045], + [156.52119140625004, 51.38027343750002], + [156.36474609374997, 52.509375], + [156.11035156250003, 52.86616210937504], + [155.62031250000004, 54.86455078125002], + [155.5548828125001, 55.348486328125034], + [155.98251953125012, 56.69521484375002], + [156.8488281250001, 57.290185546874994], + [156.97675781250004, 57.46630859375], + [156.82988281250007, 57.77963867187498], + [157.4503906250001, 57.79926757812498], + [157.66640625000005, 58.01977539062506], + [158.27519531250007, 58.00898437499998], + [159.21064453125004, 58.519433593749966], + [159.8473632812501, 59.127148437499955], + [161.75351562500012, 60.15229492187501], + [162.06816406250002, 60.466406250000034], + [163.70996093749997, 60.916796875000045], + [163.55351562500002, 61.02563476562503], + [164.00546875000006, 61.34379882812499], + [163.80439453125004, 61.46137695312498], + [164.20722656250004, 62.29223632812506], + [164.59833984375004, 62.470556640625034], + [165.20810546875012, 62.37397460937501], + [165.41738281250005, 62.447070312500045], + [164.418359375, 62.704638671875045], + [163.33173828125004, 62.550927734374994], + [163.01767578125006, 61.89106445312504], + [163.25781249999997, 61.69946289062497], + [163.08525390625002, 61.570556640625], + [162.85595703125003, 61.705029296874955], + [162.39257812500003, 61.662109375], + [160.76660156249997, 60.753320312499966], + [160.17363281250002, 60.638427734375], + [160.37890625000003, 61.02548828124998], + [159.79042968750005, 60.956640625], + [160.309375, 61.894384765625006], + [159.55234375000012, 61.71948242187497], + [159.18925781250007, 61.92939453125001], + [158.07011718750002, 61.75361328125001], + [157.46933593750012, 61.798925781250006], + [157.0841796875001, 61.67568359375002], + [155.71611328125002, 60.682373046875], + [154.97080078125012, 60.376660156249955], + [154.29306640625006, 59.833349609375034], + [154.1498046875, 59.52851562500001], + [154.97128906250006, 59.44960937500002], + [155.16044921875002, 59.19013671875001], + [154.45800781250003, 59.21655273437497], + [154.01093750000004, 59.075537109375006], + [153.69521484375005, 59.22475585937505], + [153.36113281250002, 59.214794921874955], + [152.81787109375003, 58.92626953124997], + [152.31962890625002, 59.03076171875003], + [152.08789062499997, 58.910449218750045], + [151.32675781250006, 58.875097656250034], + [151.12109375000003, 59.08251953125003], + [152.26064453125, 59.22358398437498], + [151.34824218750012, 59.561132812500006], + [150.4835937500001, 59.494384765625], + [150.66728515625002, 59.55634765625001], + [149.64257812499997, 59.770410156249994], + [149.06523437500002, 59.63051757812502], + [149.20498046875, 59.488183593749966], + [148.79707031250004, 59.532324218750006], + [148.74414062499997, 59.37353515624997], + [148.96464843750007, 59.36914062499997], + [148.72666015625006, 59.257910156250034], + [148.25742187500006, 59.414208984374994], + [147.51445312500002, 59.2685546875], + [146.53720703125006, 59.45698242187501], + [146.0495117187501, 59.17055664062502], + [145.55458984375, 59.413525390624955], + [143.19218750000002, 59.3701171875], + [142.58027343750004, 59.240136718749966], + [140.79023437500004, 58.30346679687503], + [140.446875, 57.81367187499998], + [138.66210937500003, 56.96552734375004], + [137.69150390625006, 56.13935546875004], + [135.2625, 54.94331054687498], + [135.25771484375005, 54.73149414062499], + [135.85156249999997, 54.583935546874955], + [136.797265625, 54.62099609375005], + [136.71884765625006, 53.804101562499994], + [137.15537109375012, 53.82167968750002], + [137.14160156249997, 54.182226562500006], + [137.66601562500003, 54.283300781250006], + [137.3392578125, 54.10053710937498], + [137.83476562500002, 53.94672851562498], + [137.25371093750007, 53.546142578125], + [137.95048828125007, 53.60356445312499], + [138.52792968750012, 53.959863281249994], + [138.56914062500002, 53.818798828124955], + [138.24970703125004, 53.524023437500034], + [138.45068359375003, 53.53701171875002], + [138.69941406250004, 53.869726562500034], + [138.65722656249997, 54.29833984375003], + [139.31972656250005, 54.19296874999998], + [139.707421875, 54.27714843749999], + [140.68759765625012, 53.59643554687503], + [141.3737304687501, 53.29277343749999], + [141.18125, 53.01528320312505], + [140.83964843750002, 53.087890625], + [141.25585937499997, 52.84013671874996], + [141.13242187500006, 52.435693359374994], + [141.48525390625, 52.17851562500002], + [141.36689453125004, 51.92065429687506], + [140.93261718750003, 51.61992187499999], + [140.5208984375, 50.80019531250005], + [140.62451171874997, 50.08242187500002], + [140.46269531250002, 49.911474609375006], + [140.51718750000012, 49.59614257812498], + [140.17060546875004, 48.52368164062497], + [138.58681640625005, 47.057226562500006], + [138.33691406250003, 46.543408203124955], + [137.68544921875, 45.81835937500003], + [136.14228515625004, 44.489111328125034], + [135.87460937500012, 44.37353515625003], + [135.1310546875001, 43.52573242187506], + [134.01044921875004, 42.94746093750001], + [133.15996093750007, 42.69697265624998], + [132.70898437500003, 42.875830078125006], + [132.30380859375006, 42.88330078125], + [132.30957031249997, 43.31352539062499], + [131.8666015625, 43.09516601562501], + [131.93896484374997, 43.30195312500004], + [131.15830078125012, 42.62602539062499], + [130.709375, 42.656396484374966], + [130.8341796875001, 42.52294921875006], + [130.68730468750007, 42.30253906249999] + ] + ], + [ + [ + [107.69550781250004, 78.13090820312505], + [107.48164062500004, 78.057763671875], + [106.41552734375003, 78.13984375000001], + [107.69550781250004, 78.13090820312505] + ] + ], + [ + [ + [102.88476562499997, 79.25395507812505], + [102.4123046875001, 78.83544921874997], + [103.80078124999997, 79.14926757812503], + [104.45205078125005, 78.880029296875], + [105.14599609375003, 78.81884765625006], + [105.31259765625012, 78.49990234375], + [104.74179687500012, 78.33974609374997], + [102.79667968750007, 78.18789062500002], + [101.20410156249997, 78.19194335937505], + [99.50029296875002, 77.97607421875003], + [101.590625, 79.350439453125], + [102.25126953125002, 79.25605468749995], + [102.40488281250006, 79.43320312499998], + [102.88476562499997, 79.25395507812505] + ] + ], + [ + [ + [76.24892578125005, 79.65107421874995], + [77.58896484375012, 79.50190429687504], + [76.64951171875012, 79.493408203125], + [76.24892578125005, 79.65107421874995] + ] + ], + [ + [ + [92.68349609375005, 79.685205078125], + [91.37626953125007, 79.83549804687505], + [91.22929687500007, 80.03071289062504], + [93.803125, 79.904541015625], + [92.68349609375005, 79.685205078125] + ] + ], + [ + [ + [51.409277343750006, 79.94423828125], + [50.09140625, 79.98056640625003], + [50.93632812500002, 80.09423828125], + [51.409277343750006, 79.94423828125] + ] + ], + [ + [ + [59.68886718750005, 79.95581054687506], + [58.91923828125002, 79.98461914062506], + [59.54453125000006, 80.11884765624995], + [59.68886718750005, 79.95581054687506] + ] + ], + [ + [ + [97.67451171875004, 80.15825195312499], + [97.65166015625002, 79.76064453125], + [98.59648437500002, 80.05219726562495], + [100.0612304687501, 79.77709960937506], + [99.68066406250003, 79.32333984374998], + [99.04179687500007, 79.29301757812502], + [99.92929687500012, 78.96142578124997], + [98.41113281250003, 78.78779296875004], + [95.53105468750007, 79.09809570312501], + [95.02041015625005, 79.05268554687498], + [94.21875, 79.40234375], + [93.07080078124997, 79.49531250000001], + [94.98730468749997, 80.096826171875], + [95.28134765625012, 80.030517578125], + [97.67451171875004, 80.15825195312499] + ] + ], + [ + [ + [50.05175781250003, 80.07431640625003], + [49.55605468750005, 80.15893554687503], + [49.883691406249994, 80.230224609375], + [50.05175781250003, 80.07431640625003] + ] + ], + [ + [ + [57.07871093750006, 80.35092773437498], + [56.986914062500006, 80.07148437499998], + [55.811621093750006, 80.08715820312497], + [56.02441406250003, 80.34130859374997], + [57.07871093750006, 80.35092773437498] + ] + ], + [ + [ + [53.521386718749994, 80.18520507812497], + [52.34355468750002, 80.213232421875], + [52.85390625, 80.40239257812499], + [53.85166015625006, 80.26835937500005], + [53.521386718749994, 80.18520507812497] + ] + ], + [ + [ + [57.95625, 80.12324218749995], + [57.33232421875002, 80.15810546875005], + [57.075, 80.49394531249999], + [59.25546875000006, 80.34321289062501], + [58.39794921874997, 80.31875], + [57.95625, 80.12324218749995] + ] + ], + [ + [ + [54.41533203125002, 80.47280273437502], + [53.811914062499994, 80.47622070312502], + [53.87724609375002, 80.60527343750002], + [54.41533203125002, 80.47280273437502] + ] + ], + [ + [ + [47.441992187500006, 80.853662109375], + [48.44570312500005, 80.80600585937506], + [48.68359375000003, 80.63325195312504], + [47.7052734375001, 80.76518554687499], + [46.141406250000074, 80.44672851562495], + [45.969042968750074, 80.56948242187502], + [44.9049804687501, 80.61127929687501], + [47.441992187500006, 80.853662109375] + ] + ], + [ + [ + [62.167773437500074, 80.83476562500005], + [62.07578125000006, 80.616943359375], + [61.05126953124997, 80.418603515625], + [60.27832031249997, 80.49443359374999], + [59.649804687499994, 80.43125], + [59.59228515625003, 80.81650390624998], + [62.167773437500074, 80.83476562500005] + ] + ], + [ + [ + [50.278125, 80.92724609374997], + [51.70361328125003, 80.68764648437502], + [48.81103515625003, 80.35371093750001], + [48.97753906250003, 80.16259765624997], + [47.73730468749997, 80.08168945312502], + [47.89296875000005, 80.23925781249997], + [46.991015625000074, 80.182763671875], + [46.644433593749994, 80.30034179687507], + [47.89580078125002, 80.52905273437503], + [49.087792968749994, 80.515771484375], + [49.24433593750004, 80.82138671875], + [50.278125, 80.92724609374997] + ] + ], + [ + [ + [80.02666015625007, 80.84814453125003], + [79.09853515625005, 80.81206054687505], + [79.21738281250012, 80.96035156249997], + [80.27958984375007, 80.94980468750003], + [80.02666015625007, 80.84814453125003] + ] + ], + [ + [ + [61.1408203125001, 80.95034179687497], + [60.0783203125001, 80.99916992187497], + [61.45742187499999, 81.10395507812501], + [61.1408203125001, 80.95034179687497] + ] + ], + [ + [ + [54.71894531250004, 81.11596679687497], + [56.47226562500006, 80.99824218749995], + [57.58037109375002, 80.75546874999998], + [55.88339843750006, 80.62841796875003], + [54.66816406250004, 80.73867187500002], + [54.04541015624997, 80.87197265625], + [54.71894531250004, 81.11596679687497] + ] + ], + [ + [ + [58.62236328125002, 81.04165039062502], + [58.930566406249994, 80.83168945312497], + [58.28564453124997, 80.76489257812503], + [57.21093749999997, 81.01708984374997], + [58.04951171875004, 81.11845703125002], + [58.62236328125002, 81.04165039062502] + ] + ], + [ + [ + [63.37382812500002, 80.70009765624997], + [62.59257812500002, 80.85302734375006], + [64.80205078125002, 81.197265625], + [65.43740234375005, 80.93071289062507], + [63.37382812500002, 80.70009765624997] + ] + ], + [ + [ + [91.56718750000007, 81.14121093750003], + [91.2228515625001, 81.063818359375], + [89.90117187500002, 81.17070312500002], + [91.56718750000007, 81.14121093750003] + ] + ], + [ + [ + [96.52656250000004, 81.0755859375], + [97.86992187500007, 80.76328125000006], + [97.02539062499997, 80.53554687500002], + [97.29843750000006, 80.27275390625005], + [93.6546875, 80.009619140625], + [91.52382812500005, 80.35854492187502], + [93.2625, 80.79125976562497], + [92.59257812500007, 80.780859375], + [92.7103515625, 80.87216796875003], + [95.1595703125, 81.27099609375003], + [95.80068359375005, 81.28046874999998], + [96.52656250000004, 81.0755859375] + ] + ], + [ + [ + [57.81025390625004, 81.54604492187502], + [58.563867187499994, 81.41840820312504], + [57.858691406250074, 81.36806640625], + [57.76972656250004, 81.16972656249999], + [55.71669921875005, 81.1884765625], + [55.46601562500004, 81.31118164062502], + [57.81025390625004, 81.54604492187502] + ] + ], + [ + [ + [63.65097656250006, 81.60932617187501], + [62.10644531249997, 81.679345703125], + [63.709570312500006, 81.68730468750002], + [63.65097656250006, 81.60932617187501] + ] + ], + [ + [ + [58.29541015625003, 81.715185546875], + [58.13457031250002, 81.82797851562498], + [59.261816406250006, 81.85419921874998], + [59.35644531250003, 81.75898437499995], + [58.29541015625003, 81.715185546875] + ] + ] + ] + }, + "properties": { "name": "Russia", "childNum": 73 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [30.50996093750001, -1.067285156250009], + [30.47705078125, -1.0830078125], + [30.47021484375, -1.131152343750003], + [30.508105468750017, -1.208203125000011], + [30.631933593750006, -1.367480468750003], + [30.710742187500017, -1.396777343750003], + [30.76220703125, -1.458691406250011], + [30.812597656250006, -1.563085937500006], + [30.8765625, -2.143359375], + [30.85498046875, -2.265429687500003], + [30.828710937500006, -2.338476562500006], + [30.7625, -2.371679687500006], + [30.71484375, -2.363476562500011], + [30.656640625000023, -2.373828125], + [30.593359375, -2.396777343750003], + [30.553613281250023, -2.400097656250011], + [30.408496093750017, -2.31298828125], + [30.117285156250006, -2.416601562500006], + [29.93017578125, -2.339550781250011], + [29.8681640625, -2.71640625], + [29.698046875000017, -2.794726562500003], + [29.390234375, -2.80859375], + [29.10205078125, -2.595703125], + [29.01435546875001, -2.72021484375], + [28.893945312500023, -2.635058593750003], + [28.876367187500023, -2.400292968750009], + [29.13154296875001, -2.195117187500003], + [29.196582031250017, -1.719921875000011], + [29.576953125000017, -1.387890625000011], + [29.82539062500001, -1.335546875], + [29.930078125000023, -1.469921875000011], + [30.360253906250023, -1.074609375], + [30.41230468750001, -1.063085937500006], + [30.46992187500001, -1.066015625], + [30.50996093750001, -1.067285156250009] + ] + ] + }, + "properties": { "name": "Rwanda", "childNum": 1 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [41.98769531250005, 16.715625], + [42.059960937499994, 16.803515625000017], + [42.15781250000006, 16.570703125000023], + [41.80156250000002, 16.778759765624955], + [41.86044921875006, 17.002539062499977], + [41.98769531250005, 16.715625] + ] + ], + [ + [ + [46.53144531250004, 29.09624023437499], + [47.433203125, 28.989550781250017], + [47.671289062499994, 28.53315429687504], + [48.442480468750006, 28.542919921874983], + [48.80898437499999, 27.895898437499966], + [48.797167968750074, 27.72431640625001], + [49.2375, 27.49272460937499], + [49.17509765625002, 27.43764648437505], + [49.40527343749997, 27.18095703124996], + [50.149804687499994, 26.66264648437499], + [50.00810546875002, 26.678515625000017], + [50.21386718750003, 26.30849609375005], + [50.15546875000004, 26.100537109374955], + [50.03164062499999, 26.11098632812505], + [50.55791015625002, 25.086669921875], + [50.66689453125005, 24.96381835937501], + [50.72558593749997, 24.869384765625057], + [50.80439453125004, 24.789257812499983], + [50.928320312500006, 24.595117187500023], + [50.96601562500004, 24.573925781249983], + [51.022753906250074, 24.56523437499999], + [51.09335937500006, 24.564648437499955], + [51.178027343750074, 24.586718750000017], + [51.26796875, 24.607226562500017], + [51.33847656250006, 24.564355468749994], + [51.41123046875006, 24.570800781250057], + [51.30986328125002, 24.340380859375017], + [51.56835937500003, 24.286181640625074], + [51.592578125000074, 24.07885742187503], + [52.55507812500005, 22.932812499999955], + [55.104296875000074, 22.621484375000023], + [55.185839843750074, 22.7041015625], + [55.64101562499999, 22.001855468749994], + [54.97734375000002, 19.995947265625006], + [51.977636718750006, 18.996142578125074], + [49.04199218750003, 18.58178710937503], + [48.17216796875002, 18.156933593749983], + [47.57958984374997, 17.448339843750034], + [47.44179687499999, 17.111865234375045], + [47.14355468749997, 16.946679687499966], + [46.97568359375006, 16.953466796875034], + [46.72763671875006, 17.26557617187501], + [45.5353515625001, 17.30205078124999], + [45.14804687500006, 17.427441406249955], + [43.91699218749997, 17.32470703124997], + [43.41796875000003, 17.516259765625023], + [43.19091796875003, 17.359375], + [43.16503906249997, 16.689404296874955], + [42.79931640624997, 16.37177734375001], + [42.29394531249997, 17.434960937499966], + [41.75, 17.88574218749997], + [41.22949218750003, 18.678417968749983], + [40.75917968750005, 19.755468750000034], + [40.080664062500006, 20.265917968750017], + [39.728320312500074, 20.390332031249955], + [39.27607421875004, 20.973974609375034], + [39.093554687500074, 21.31035156249999], + [39.14707031250006, 21.518994140624955], + [38.98789062500006, 21.88173828125005], + [39.06201171874997, 22.592187500000023], + [38.46416015625002, 23.71186523437504], + [37.91972656250002, 24.185400390625063], + [37.54306640625006, 24.291650390625023], + [37.18085937500004, 24.82001953125001], + [37.26630859375004, 24.960058593750034], + [37.14882812499999, 25.291113281249977], + [35.18046875000002, 28.03486328125004], + [34.722070312499994, 28.130664062500017], + [34.625, 28.064501953125017], + [34.95078125, 29.353515625], + [36.068457031250006, 29.200537109375006], + [36.28281250000006, 29.355371093750023], + [36.47607421874997, 29.49511718749997], + [36.59179687500003, 29.666113281250006], + [36.703906250000074, 29.831640624999977], + [36.75527343750005, 29.86601562499996], + [37.46923828125003, 29.995068359374955], + [37.49072265625003, 30.01171874999997], + [37.55361328125005, 30.14458007812496], + [37.63359375000002, 30.313281250000045], + [37.64990234374997, 30.330957031249994], + [37.669726562500074, 30.34814453125003], + [37.862890625, 30.44262695312503], + [37.98007812500006, 30.5], + [37.47900390624997, 31.007763671874955], + [37.10527343750002, 31.35517578125004], + [36.95859375000006, 31.491503906250017], + [37.215625, 31.55610351562501], + [37.49335937500004, 31.625878906250023], + [38.111425781250006, 31.78115234375005], + [38.37548828124997, 31.84746093749996], + [38.962304687499994, 31.99492187499999], + [38.99707031249997, 32.00747070312505], + [39.145410156249994, 32.12451171875], + [39.36865234374997, 32.09174804687498], + [39.70410156250003, 32.04252929687499], + [40.02783203124997, 31.995019531249994], + [40.3693359375001, 31.93896484375003], + [40.47890625000005, 31.89335937499999], + [42.07441406250004, 31.08037109374999], + [43.77373046875002, 29.84921875], + [44.71650390625004, 29.19360351562503], + [46.35644531250003, 29.06367187500001], + [46.53144531250004, 29.09624023437499] + ] + ] + ] + }, + "properties": { "name": "Saudi Arabia", "childNum": 2 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [36.87138671875002, 21.996728515624994], + [36.92695312500001, 21.58652343749999], + [37.25859375000002, 21.108544921874994], + [37.25722656250002, 21.03940429687499], + [37.15058593750001, 21.103759765625], + [37.14111328125, 20.98178710937499], + [37.19316406250002, 20.12070312499999], + [37.471289062500006, 18.820117187500003], + [38.609472656250006, 18.005078125], + [38.422460937500006, 17.823925781249997], + [38.39716796875001, 17.778369140625003], + [38.38554687500002, 17.751269531250003], + [38.37373046875001, 17.717333984375003], + [38.34736328125001, 17.68359375], + [38.28984375000002, 17.637011718750003], + [38.26728515625001, 17.61669921875], + [38.253515625, 17.584765625], + [37.78242187500001, 17.4580078125], + [37.547460937500006, 17.324121093749994], + [37.51015625000002, 17.288134765625003], + [37.45292968750002, 17.108691406250003], + [37.41103515625002, 17.06171875], + [37.24882812500002, 17.056884765625], + [37.16953125, 17.04140625], + [37.0615234375, 17.061279296875], + [37.00898437500001, 17.058886718750003], + [36.995214843750006, 17.020556640625003], + [36.97578125000001, 16.86655273437499], + [36.97871093750001, 16.800585937500003], + [36.887792968750006, 16.624658203124994], + [36.91376953125001, 16.296191406250003], + [36.566015625, 15.362109375], + [36.4267578125, 15.132080078125], + [36.44814453125002, 14.940087890624994], + [36.470800781250006, 14.736474609374994], + [36.52431640625002, 14.2568359375], + [36.12519531250001, 12.75703125], + [35.67021484375002, 12.623730468749997], + [35.1123046875, 11.816552734374994], + [34.93144531250002, 10.864794921874989], + [34.77128906250002, 10.746191406249991], + [34.571875, 10.880175781249989], + [34.34394531250001, 10.658642578124997], + [34.31123046875001, 10.190869140624997], + [34.078125, 9.461523437499991], + [33.87148437500002, 9.506152343749989], + [33.96328125000002, 9.861767578124997], + [33.90703125000002, 10.181445312499989], + [33.13007812500001, 10.745947265624991], + [33.073339843750006, 11.606103515624994], + [33.199316406250006, 12.21728515625], + [32.721875, 12.223095703124997], + [32.73671875000002, 12.009667968749994], + [32.072265625, 12.006738281249994], + [32.338476562500006, 11.710107421874994], + [32.42080078125002, 11.089111328125], + [31.224902343750017, 9.799267578124997], + [30.75537109375, 9.731201171875], + [30.003027343750006, 10.277392578124989], + [29.60546875, 10.065087890624994], + [29.47314453125, 9.768603515624989], + [28.979589843750006, 9.594189453124997], + [28.844531250000017, 9.326074218749994], + [28.048925781250006, 9.32861328125], + [27.880859375, 9.601611328124989], + [27.07421875, 9.613818359374989], + [26.65869140625, 9.484130859375], + [25.91914062500001, 10.169335937499994], + [25.858203125000017, 10.406494140625], + [25.211718750000017, 10.329931640624991], + [25.066992187500006, 10.293798828124991], + [24.785253906250006, 9.774658203125], + [24.53193359375001, 8.886914062499997], + [24.147363281250023, 8.665625], + [23.53730468750001, 8.815820312499994], + [23.46826171875, 9.11474609375], + [23.62265625, 9.340625], + [23.646289062500017, 9.822900390624994], + [22.86005859375001, 10.919677734375], + [22.922656250000017, 11.344873046874994], + [22.591113281250017, 11.579882812499989], + [22.580957031250023, 11.990136718749994], + [22.472460937500017, 12.067773437499994], + [22.352343750000017, 12.660449218749989], + [21.928125, 12.678125], + [21.825292968750006, 12.79052734375], + [22.228125, 13.32958984375], + [22.1064453125, 13.7998046875], + [22.53857421875, 14.161865234375], + [22.38154296875001, 14.550488281249997], + [22.6708984375, 14.722460937500003], + [22.93232421875001, 15.162109375], + [22.933886718750017, 15.533105468749994], + [23.10517578125001, 15.702539062499994], + [23.970800781250006, 15.721533203124991], + [23.980273437500017, 19.496630859375003], + [23.980273437500017, 19.99594726562499], + [24.9794921875, 20.002587890624994], + [24.980273437500017, 21.995849609375], + [28.036425781250017, 21.995361328125], + [31.092675781250023, 21.994873046875], + [31.260644531250023, 22.00229492187499], + [31.400292968750023, 22.202441406250003], + [31.486132812500017, 22.14780273437499], + [31.434472656250023, 21.995849609375], + [36.87138671875002, 21.996728515624994] + ] + ] + }, + "properties": { "name": "Sudan", "childNum": 1 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [34.078125, 9.461523437499991], + [34.07275390625, 8.545263671874991], + [33.95332031250001, 8.443505859374994], + [33.28105468750002, 8.437255859375], + [32.99892578125002, 7.899511718749991], + [33.902441406250006, 7.509521484375], + [34.06425781250002, 7.225732421874994], + [34.71064453125001, 6.660302734374994], + [34.98359375000001, 5.858300781249994], + [35.26835937500002, 5.492285156249991], + [35.08447265625, 5.311865234374991], + [34.87832031250002, 5.109570312499997], + [34.63984375000001, 4.87548828125], + [34.38017578125002, 4.620654296874989], + [34.176855468750006, 4.419091796874994], + [33.97607421875, 4.22021484375], + [33.74160156250002, 3.985253906249994], + [33.568457031250006, 3.81171875], + [33.489355468750006, 3.755078125], + [32.99726562500001, 3.880175781249989], + [32.33574218750002, 3.706201171874994], + [32.13593750000001, 3.519726562499997], + [31.79804687500001, 3.802636718749994], + [31.547167968750017, 3.677587890624991], + [31.15234375, 3.785595703124997], + [30.83857421875001, 3.49072265625], + [30.757226562500023, 3.62421875], + [30.586718750000017, 3.62421875], + [30.50830078125, 3.835693359375], + [30.194921875, 3.98193359375], + [29.676855468750006, 4.5869140625], + [29.469628906250023, 4.61181640625], + [29.224902343750017, 4.391894531249989], + [28.72705078125, 4.504980468749991], + [28.427539062500017, 4.324169921874997], + [28.19208984375001, 4.350244140624994], + [27.7880859375, 4.644677734374994], + [27.4033203125, 5.109179687499989], + [27.143945312500023, 5.722949218749989], + [26.514257812500006, 6.069238281249994], + [26.30859375, 6.455322265625], + [26.36181640625, 6.635302734374989], + [25.27890625, 7.427490234375], + [25.18134765625001, 7.557226562499991], + [25.20039062500001, 7.807910156249989], + [24.853320312500017, 8.137548828124991], + [24.291406250000023, 8.29140625], + [24.147363281250023, 8.665625], + [24.53193359375001, 8.886914062499997], + [24.785253906250006, 9.774658203125], + [25.066992187500006, 10.293798828124991], + [25.211718750000017, 10.329931640624991], + [25.858203125000017, 10.406494140625], + [25.91914062500001, 10.169335937499994], + [26.65869140625, 9.484130859375], + [27.07421875, 9.613818359374989], + [27.880859375, 9.601611328124989], + [28.048925781250006, 9.32861328125], + [28.844531250000017, 9.326074218749994], + [28.979589843750006, 9.594189453124997], + [29.47314453125, 9.768603515624989], + [29.60546875, 10.065087890624994], + [30.003027343750006, 10.277392578124989], + [30.75537109375, 9.731201171875], + [31.224902343750017, 9.799267578124997], + [32.42080078125002, 11.089111328125], + [32.338476562500006, 11.710107421874994], + [32.072265625, 12.006738281249994], + [32.73671875000002, 12.009667968749994], + [32.721875, 12.223095703124997], + [33.199316406250006, 12.21728515625], + [33.073339843750006, 11.606103515624994], + [33.13007812500001, 10.745947265624991], + [33.90703125000002, 10.181445312499989], + [33.96328125000002, 9.861767578124997], + [33.87148437500002, 9.506152343749989], + [34.078125, 9.461523437499991] + ] + ] + }, + "properties": { "name": "S. Sudan", "childNum": 1 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [-12.280615234374977, 14.809033203124997], + [-12.228417968749994, 14.45859375], + [-12.019189453124994, 14.206494140624997], + [-11.960888671874983, 13.875292968750003], + [-12.05419921875, 13.633056640625], + [-11.831689453124994, 13.315820312499994], + [-11.634960937499983, 13.369873046875], + [-11.390380859375, 12.941992187499991], + [-11.389404296875, 12.404394531249991], + [-12.399072265624994, 12.340087890625], + [-12.930712890624989, 12.532275390624989], + [-13.061279296875, 12.489990234375], + [-13.082910156249994, 12.633544921875], + [-13.729248046875, 12.673925781249991], + [-14.06484375, 12.67529296875], + [-14.349218749999977, 12.676416015624994], + [-15.196093749999989, 12.679931640625], + [-15.3779296875, 12.588964843749991], + [-15.574804687499977, 12.490380859374994], + [-15.839550781249983, 12.43789062499999], + [-16.144189453124994, 12.45742187499999], + [-16.24150390624999, 12.443310546874997], + [-16.41630859374999, 12.36767578125], + [-16.521337890624977, 12.3486328125], + [-16.656933593749983, 12.364355468749991], + [-16.711816406249994, 12.354833984374991], + [-16.76030273437499, 12.52578125], + [-16.44287109375, 12.609472656249991], + [-16.59765625, 12.715283203124997], + [-16.743896484375, 12.58544921875], + [-16.763330078124994, 13.064160156249997], + [-16.648779296874977, 13.154150390624991], + [-15.834277343749989, 13.156445312499997], + [-15.814404296874983, 13.325146484374997], + [-15.286230468749977, 13.39599609375], + [-15.151123046875, 13.556494140624991], + [-14.246777343749983, 13.23583984375], + [-13.826708984374989, 13.4078125], + [-13.977392578124977, 13.54345703125], + [-14.405468749999983, 13.503710937500003], + [-15.108349609374983, 13.81210937499999], + [-15.426855468749977, 13.727001953124997], + [-15.509667968749994, 13.586230468750003], + [-16.56230468749999, 13.587304687499994], + [-16.766943359374977, 13.904931640624994], + [-16.618115234374983, 14.04052734375], + [-16.791748046875, 14.004150390625], + [-17.168066406249977, 14.640625], + [-17.345800781249977, 14.729296875], + [-17.445019531249983, 14.651611328125], + [-17.53564453125, 14.755126953125], + [-17.147167968749983, 14.922021484374994], + [-16.843408203124994, 15.293994140625003], + [-16.570751953124983, 15.734423828125003], + [-16.535253906249977, 15.83837890625], + [-16.502050781249977, 15.917333984374991], + [-16.480078124999977, 16.097216796875003], + [-16.441015624999977, 16.204541015624997], + [-16.239013671875, 16.531298828125003], + [-15.768212890624994, 16.485107421875], + [-14.990625, 16.676904296874994], + [-14.300097656249989, 16.580273437499997], + [-13.868457031249989, 16.148144531249997], + [-13.756640624999989, 16.172509765624994], + [-13.40966796875, 16.05917968749999], + [-13.105273437499989, 15.57177734375], + [-12.735253906249994, 15.13125], + [-12.40869140625, 14.889013671874991], + [-12.280615234374977, 14.809033203124997] + ] + ] + }, + "properties": { "name": "Senegal", "childNum": 1 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [103.9697265625, 1.331445312499994], + [103.65019531249999, 1.325537109374991], + [103.81796875000003, 1.447070312499989], + [103.9697265625, 1.331445312499994] + ] + ] + }, + "properties": { "name": "Singapore", "childNum": 1 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [-37.10332031249996, -54.065625], + [-36.70380859375001, -54.10810546874999], + [-36.64741210937498, -54.26230468749996], + [-36.32646484374996, -54.251171875], + [-35.79858398437497, -54.76347656250002], + [-36.08549804687499, -54.86679687500001], + [-36.885986328125, -54.33945312499996], + [-37.63090820312496, -54.16748046875001], + [-37.61884765625001, -54.04208984375004], + [-38.017431640625034, -54.008007812500026], + [-37.10332031249996, -54.065625] + ] + ] + }, + "properties": { "name": "S. Geo. and S. Sandw. Is.", "childNum": 1 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [-5.692138671874972, -15.997753906249997], + [-5.782519531250017, -16.00400390625002], + [-5.707861328124977, -15.90615234374998], + [-5.692138671874972, -15.997753906249997] + ] + ] + }, + "properties": { "name": "Saint Helena", "childNum": 1 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [160.57626953125006, -11.797851562500028], + [160.44306640625004, -11.814941406249957], + [159.98632812499997, -11.494726562500006], + [160.57626953125006, -11.797851562500028] + ] + ], + [ + [ + [166.13320312500005, -10.757812499999972], + [165.90400390625004, -10.851464843749966], + [165.79101562500003, -10.784765624999963], + [166.02382812500005, -10.6611328125], + [166.13320312500005, -10.757812499999972] + ] + ], + [ + [ + [161.71533203124997, -10.387304687499991], + [162.10537109375005, -10.45380859375004], + [162.37333984375002, -10.823242187499986], + [161.78681640625004, -10.716894531249991], + [161.53789062500007, -10.566406249999972], + [161.4870117187501, -10.361425781249963], + [161.29394531250003, -10.326464843750031], + [161.30478515625012, -10.204394531250031], + [161.71533203124997, -10.387304687499991] + ] + ], + [ + [ + [161.54785156249997, -9.625683593749997], + [161.55380859375012, -9.769726562500026], + [161.40976562500006, -9.681640625000028], + [161.36416015625, -9.353417968750037], + [161.54785156249997, -9.625683593749997] + ] + ], + [ + [ + [159.75039062500005, -9.272656250000011], + [159.97060546875, -9.433300781249969], + [160.35458984375006, -9.421582031249983], + [160.81894531250006, -9.862792968749986], + [160.64921875000002, -9.92861328124998], + [159.80273437499997, -9.763476562500003], + [159.61230468749997, -9.470703124999943], + [159.62558593750012, -9.311230468749969], + [159.75039062500005, -9.272656250000011] + ] + ], + [ + [ + [160.1681640625001, -8.995507812500037], + [160.40751953125007, -9.140332031249969], + [160.10537109375, -9.080761718749997], + [160.1681640625001, -8.995507812500037] + ] + ], + [ + [ + [159.18857421875006, -9.123535156250014], + [159.03632812500004, -9.075], + [159.12978515625, -8.99306640624998], + [159.22841796875005, -9.029980468749955], + [159.18857421875006, -9.123535156250014] + ] + ], + [ + [ + [158.10791015625003, -8.684179687500034], + [157.93759765625006, -8.73642578125002], + [157.90927734375006, -8.565625], + [158.10546874999997, -8.536816406250026], + [158.10791015625003, -8.684179687500034] + ] + ], + [ + [ + [157.38896484375002, -8.713476562499963], + [157.2123046875, -8.565039062500006], + [157.37949218750012, -8.420898437499943], + [157.38896484375002, -8.713476562499963] + ] + ], + [ + [ + [160.7494140625, -8.313964843750014], + [160.99765625000006, -8.612011718749983], + [160.94433593750003, -8.799023437499983], + [161.15869140624997, -8.961816406250009], + [161.36738281250004, -9.61123046874998], + [160.77207031250012, -8.963867187499986], + [160.7140625000001, -8.539257812499997], + [160.59042968750006, -8.372753906249997], + [160.7494140625, -8.313964843750014] + ] + ], + [ + [ + [157.76347656250002, -8.242187499999957], + [157.89843749999997, -8.506347656249943], + [157.81933593750003, -8.612011718749983], + [157.58789062500003, -8.445410156249963], + [157.5580078125, -8.269921875], + [157.30244140625004, -8.33330078124996], + [157.21757812500002, -8.262792968749977], + [157.490625, -7.965722656250037], + [157.76347656250002, -8.242187499999957] + ] + ], + [ + [ + [157.171875, -8.108105468749997], + [156.95830078125002, -8.014355468749997], + [157.02412109375004, -7.867871093749997], + [157.18613281250006, -7.941210937500017], + [157.171875, -8.108105468749997] + ] + ], + [ + [ + [156.687890625, -7.92304687500004], + [156.5109375000001, -7.707812499999974], + [156.5609375, -7.574023437499989], + [156.80908203124997, -7.722851562500026], + [156.687890625, -7.92304687500004] + ] + ], + [ + [ + [159.8791015625001, -8.534277343749949], + [158.9440429687501, -8.04072265625004], + [158.457421875, -7.544726562499974], + [158.734375, -7.604296875000031], + [159.43144531250002, -8.029003906249955], + [159.84306640625002, -8.326953124999989], + [159.8791015625001, -8.534277343749949] + ] + ], + [ + [ + [155.83984374999997, -7.097167968750014], + [155.67753906250002, -7.08896484375002], + [155.73896484375004, -6.972949218750017], + [155.83984374999997, -7.097167968750014] + ] + ], + [ + [ + [157.48671875000005, -7.330371093750003], + [157.44130859375, -7.425683593749966], + [157.10156249999997, -7.323632812499966], + [156.4525390625, -6.638281249999963], + [157.03027343750003, -6.891992187499952], + [157.19335937499997, -7.160351562499997], + [157.48671875000005, -7.330371093750003] + ] + ] + ] + }, + "properties": { "name": "Solomon Is.", "childNum": 16 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [-12.526074218749926, 7.436328125000017], + [-12.951611328124926, 7.570849609374989], + [-12.615234374999972, 7.63720703125], + [-12.5125, 7.582421875000037], + [-12.526074218749926, 7.436328125000017] + ] + ], + [ + [ + [-10.758593749999989, 9.385351562499991], + [-10.682714843750006, 9.289355468749974], + [-10.687646484374937, 9.261132812499994], + [-10.749951171874926, 9.12236328124996], + [-10.747021484374955, 9.095263671875045], + [-10.726855468749932, 9.081689453125023], + [-10.615966796875, 9.059179687499977], + [-10.500537109375017, 8.687548828125017], + [-10.677343749999977, 8.400585937499997], + [-10.712109374999955, 8.335253906250017], + [-10.686962890624983, 8.321679687500009], + [-10.652636718749989, 8.330273437499983], + [-10.604003906249943, 8.319482421874994], + [-10.55771484374992, 8.315673828125028], + [-10.496435546874977, 8.362109374999974], + [-10.394433593749966, 8.480957031250028], + [-10.360058593749983, 8.49550781249998], + [-10.283203124999972, 8.48515625], + [-10.285742187499949, 8.454101562499986], + [-10.314648437499983, 8.310839843750017], + [-10.359814453124926, 8.187939453125026], + [-10.570849609374932, 8.071142578125034], + [-10.6474609375, 7.759375], + [-10.878076171874994, 7.538232421874994], + [-11.267675781249977, 7.232617187499997], + [-11.507519531249983, 6.906542968750003], + [-12.48564453124996, 7.386279296875045], + [-12.480273437499932, 7.75327148437502], + [-12.697607421874977, 7.715869140625045], + [-12.850878906249932, 7.818701171875034], + [-12.956933593749966, 8.145312500000045], + [-13.148974609374989, 8.214599609375043], + [-13.272753906249989, 8.429736328124989], + [-13.085009765624932, 8.42475585937504], + [-12.894091796874932, 8.62978515624998], + [-13.181835937499955, 8.576904296875043], + [-13.206933593749994, 8.843115234375006], + [-13.059472656249966, 8.881152343750031], + [-13.292675781249955, 9.04921875], + [-13.077294921874966, 9.069628906249974], + [-12.958789062499989, 9.263330078124994], + [-12.755859374999943, 9.373583984374989], + [-12.557861328125, 9.704980468749994], + [-12.427978515625028, 9.898144531250011], + [-12.142333984375, 9.87539062499999], + [-11.911083984374955, 9.993017578124977], + [-11.273632812499955, 9.996533203124983], + [-11.205664062499949, 9.977734374999969], + [-11.180859374999955, 9.925341796875045], + [-11.047460937499977, 9.786328125000054], + [-10.758593749999989, 9.385351562499991] + ] + ] + ] + }, + "properties": { "name": "Sierra Leone", "childNum": 2 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [-89.36259765624999, 14.416015625], + [-89.1205078125, 14.370214843749991], + [-88.51254882812499, 13.978955078124997], + [-88.504345703125, 13.964208984374991], + [-88.49765625, 13.904541015625], + [-88.482666015625, 13.854248046875], + [-88.44912109375, 13.850976562499994], + [-88.40849609374999, 13.87539062499999], + [-88.27622070312499, 13.942675781250003], + [-88.151025390625, 13.987353515625003], + [-87.99101562499999, 13.879638671875], + [-87.8919921875, 13.894970703124997], + [-87.80224609375, 13.889990234374991], + [-87.7314453125, 13.841064453125], + [-87.71533203125, 13.812695312499997], + [-87.781884765625, 13.521386718749994], + [-87.930859375, 13.1806640625], + [-88.68564453124999, 13.281494140625], + [-88.51201171874999, 13.183935546874991], + [-89.80419921875, 13.560107421875003], + [-90.09521484375, 13.736523437499997], + [-90.04814453124999, 13.904052734375], + [-89.54716796874999, 14.241259765625003], + [-89.5736328125, 14.390087890624997], + [-89.36259765624999, 14.416015625] + ] + ] + }, + "properties": { "name": "El Salvador", "childNum": 1 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [-56.26708984374997, 46.838476562500034], + [-56.38476562499994, 46.81943359375006], + [-56.36464843749994, 47.09897460937498], + [-56.26708984374997, 46.838476562500034] + ] + ] + }, + "properties": { "name": "St. Pierre and Miquelon", "childNum": 1 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [6.659960937499989, 0.120654296874989], + [6.51972656250004, 0.066308593750023], + [6.468164062499994, 0.22734375], + [6.68691406250008, 0.404394531249977], + [6.75, 0.24345703124996], + [6.659960937499989, 0.120654296874989] + ] + ], + [ + [ + [7.423828125, 1.567724609375006], + [7.330664062500034, 1.603369140624991], + [7.414453125000051, 1.699121093750037], + [7.423828125, 1.567724609375006] + ] + ] + ] + }, + "properties": { "name": "São Tomé and Principe", "childNum": 2 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [-54.03422851562499, 3.62939453125], + [-54.00957031249999, 3.448535156249989], + [-54.06318359375, 3.353320312499989], + [-54.18803710937499, 3.178759765624989], + [-54.203125, 3.13818359375], + [-54.17070312499999, 2.993603515624997], + [-54.18808593749999, 2.874853515624991], + [-54.1955078125, 2.81787109375], + [-54.256738281249994, 2.713720703124991], + [-54.402001953124994, 2.461523437499991], + [-54.53593749999999, 2.343310546874989], + [-54.56840820312499, 2.342578124999989], + [-54.604736328125, 2.335791015624991], + [-54.61625976562499, 2.326757812499991], + [-54.661865234375, 2.327539062499994], + [-54.697412109374994, 2.359814453124997], + [-54.72221679687499, 2.441650390625], + [-54.87607421874999, 2.450390625], + [-54.92656249999999, 2.497363281249989], + [-54.968408203124994, 2.54833984375], + [-54.978662109374994, 2.59765625], + [-55.005810546875, 2.59296875], + [-55.0703125, 2.54833984375], + [-55.11411132812499, 2.539208984374994], + [-55.1876953125, 2.547509765624994], + [-55.286035156249994, 2.499658203124994], + [-55.343994140625, 2.48876953125], + [-55.38535156249999, 2.440625], + [-55.73056640624999, 2.406152343749994], + [-55.957470703125, 2.520458984374997], + [-55.99350585937499, 2.497509765624997], + [-56.02036132812499, 2.392773437499997], + [-56.0451171875, 2.364404296874994], + [-56.087792968749994, 2.34130859375], + [-56.12939453125, 2.299511718749997], + [-56.1376953125, 2.259033203125], + [-56.073632812499994, 2.236767578124997], + [-56.02006835937499, 2.158154296874997], + [-55.96196289062499, 2.095117187499994], + [-55.91533203124999, 2.03955078125], + [-55.921630859375, 1.976660156249991], + [-55.929638671875, 1.8875], + [-56.01992187499999, 1.842236328124997], + [-56.4828125, 1.942138671875], + [-56.704345703125, 2.036474609374991], + [-57.19736328124999, 2.853271484375], + [-57.303662109375, 3.377099609374994], + [-57.646728515625, 3.39453125], + [-58.05429687499999, 4.101660156249991], + [-57.84599609374999, 4.668164062499997], + [-57.91704101562499, 4.820410156249991], + [-57.711083984374994, 4.991064453124991], + [-57.331005859375, 5.020166015624994], + [-57.20981445312499, 5.195410156249991], + [-57.3185546875, 5.335351562499994], + [-57.194775390625, 5.5484375], + [-56.96982421874999, 5.992871093749997], + [-56.235595703125, 5.885351562499991], + [-55.897607421874994, 5.699316406249991], + [-55.909912109375, 5.892626953124989], + [-55.648339843749994, 5.985888671874989], + [-54.83369140625, 5.988330078124989], + [-54.05419921875, 5.807910156249989], + [-54.08046875, 5.502246093749989], + [-54.4796875, 4.836523437499991], + [-54.350732421874994, 4.054101562499994], + [-54.03422851562499, 3.62939453125] + ] + ] + }, + "properties": { "name": "Suriname", "childNum": 1 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [22.538671875, 49.072705078125], + [22.52412109375001, 49.031396484374994], + [22.389453125000017, 48.873486328125], + [22.295214843750017, 48.685839843749996], + [22.142871093750017, 48.568505859374994], + [22.1318359375, 48.405322265624996], + [21.766992187500023, 48.3380859375], + [21.45136718750001, 48.55224609375], + [20.490039062500017, 48.526904296874996], + [20.333789062500017, 48.295556640624994], + [19.95039062500001, 48.146630859374994], + [19.625390625000023, 48.223095703125], + [18.791894531250023, 48.000292968749996], + [18.72421875, 47.787158203124996], + [17.76191406250001, 47.770166015624994], + [17.147363281250023, 48.00595703125], + [16.86542968750001, 48.3869140625], + [16.953125, 48.598828125], + [17.135644531250023, 48.841064453125], + [17.75849609375001, 48.888134765625], + [18.0859375, 49.06513671875], + [18.160937500000017, 49.257373046874996], + [18.83222656250001, 49.510791015624996], + [19.1494140625, 49.4], + [19.44160156250001, 49.597705078124996], + [19.77392578125, 49.37216796875], + [19.756640625000017, 49.204394531249996], + [20.0576171875, 49.181298828124994], + [20.36298828125001, 49.38525390625], + [20.868457031250017, 49.314697265625], + [21.079394531250017, 49.418261718749996], + [21.6396484375, 49.411962890625], + [22.020117187500006, 49.209521484374996], + [22.473046875000023, 49.081298828125], + [22.538671875, 49.072705078125] + ] + ] + }, + "properties": { "name": "Slovakia", "childNum": 1 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [16.516210937500006, 46.499902343749994], + [16.427636718750023, 46.5244140625], + [16.321191406250023, 46.534619140625], + [16.1064453125, 46.382226562499994], + [15.608984375, 46.171923828124996], + [15.592578125000017, 46.139990234375], + [15.596875, 46.109228515625], + [15.675585937500017, 45.983691406249996], + [15.652148437500017, 45.862158203125], + [15.277050781250011, 45.7326171875], + [15.353710937500011, 45.659912109375], + [15.283593750000023, 45.5796875], + [15.291210937500011, 45.541552734374996], + [15.32666015625, 45.502294921875], + [15.339453125, 45.467041015625], + [15.242089843750023, 45.44140625], + [15.110449218750006, 45.45078125], + [14.95458984375, 45.499902343749994], + [14.793066406250006, 45.47822265625], + [14.649511718750006, 45.571484375], + [14.591796875, 45.651269531249994], + [14.56884765625, 45.6572265625], + [14.548448660714302, 45.628388671875], + [14.507586495535731, 45.59039341517857], + [14.42734375, 45.505761718749994], + [14.369921875000017, 45.4814453125], + [13.878710937500017, 45.428369140624994], + [13.577929687500017, 45.516894531249996], + [13.8447265625, 45.59287109375], + [13.831152343750006, 45.680419921875], + [13.663476562500023, 45.7919921875], + [13.6005859375, 45.979785156249996], + [13.509179687500023, 45.973779296874994], + [13.487695312500023, 45.987109375], + [13.480273437500017, 46.009228515625], + [13.486425781250006, 46.03955078125], + [13.548046875000011, 46.089111328125], + [13.616601562500023, 46.133105468749996], + [13.634960937500011, 46.157763671874996], + [13.632519531250011, 46.177050781249996], + [13.420996093750006, 46.212304687499994], + [13.399511718750006, 46.317529296874994], + [13.563281250000017, 46.415087890624996], + [13.637109375000023, 46.448535156249996], + [13.6796875, 46.462890625], + [13.7, 46.520263671875], + [14.5498046875, 46.399707031249996], + [14.893261718750011, 46.605908203125], + [15.957617187500006, 46.677636718749994], + [16.093066406250017, 46.86328125], + [16.283593750000023, 46.857275390625], + [16.516210937500006, 46.499902343749994] + ] + ] + }, + "properties": { "name": "Slovenia", "childNum": 1 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [16.52851562500001, 56.29052734375], + [16.431640625, 56.24375], + [16.41230468750004, 56.568994140624994], + [17.02539062499997, 57.345068359375006], + [16.52851562500001, 56.29052734375] + ] + ], + [ + [ + [19.076464843750045, 57.8359375], + [18.813867187500023, 57.70620117187502], + [18.907910156250068, 57.39833984375002], + [18.146386718749994, 56.920507812500006], + [18.285351562500068, 57.08320312500001], + [18.136523437500045, 57.55664062500003], + [18.53740234374999, 57.83056640625006], + [18.90058593750001, 57.91547851562504], + [19.076464843750045, 57.8359375] + ] + ], + [ + [ + [19.156347656250063, 57.92260742187497], + [19.086523437500034, 57.86499023437506], + [19.134863281250034, 57.98134765625002], + [19.331445312500023, 57.962890625], + [19.156347656250063, 57.92260742187497] + ] + ], + [ + [ + [24.15546875000004, 65.80527343750006], + [23.102343750000074, 65.73535156250003], + [22.400976562500006, 65.86210937499999], + [22.254003906250006, 65.59755859375002], + [21.565527343750063, 65.40810546874997], + [21.609179687500074, 65.261376953125], + [21.410351562500068, 65.31743164062505], + [21.57392578125001, 65.12578124999999], + [21.138183593750057, 64.80869140625006], + [21.519628906250034, 64.46308593749998], + [20.76269531250003, 63.86782226562505], + [18.60644531250003, 63.17827148437499], + [18.31289062500008, 62.996386718750045], + [18.46308593750004, 62.895849609375006], + [18.170019531250034, 62.789355468750074], + [17.906640625000023, 62.88676757812502], + [18.037304687500068, 62.60053710937498], + [17.834472656250057, 62.50273437500002], + [17.410253906250063, 62.508398437500034], + [17.633691406249994, 62.23300781250006], + [17.374511718750057, 61.866308593750034], + [17.465429687500006, 61.68447265625005], + [17.196386718750006, 61.72456054687504], + [17.13076171875005, 61.57573242187499], + [17.25097656250003, 60.70078125], + [17.6611328125, 60.53515625000003], + [17.955761718750068, 60.589794921874955], + [18.85273437500001, 60.02587890625], + [18.970507812500045, 59.757226562499994], + [17.964257812500023, 59.359375], + [18.56025390625004, 59.39448242187498], + [18.285351562500068, 59.109375], + [16.978125, 58.65415039062506], + [16.214257812500023, 58.636669921874955], + [16.92382812499997, 58.49257812499999], + [16.651953125, 58.43432617187503], + [16.65224609375008, 57.50068359374998], + [16.348730468750063, 56.70927734374996], + [15.826660156250028, 56.12495117187501], + [14.782031250000017, 56.16191406250002], + [14.754785156250051, 56.03315429687498], + [14.401953125000034, 55.97675781250004], + [14.21503906250004, 55.83261718749998], + [14.341699218749994, 55.52773437500002], + [14.17373046875008, 55.396630859374966], + [12.885839843750063, 55.41137695312506], + [12.973925781250074, 55.748144531250006], + [12.471191406250057, 56.29052734375], + [12.801660156250051, 56.263916015625], + [12.65644531250004, 56.44057617187502], + [12.857421875000028, 56.45239257812503], + [12.883691406250051, 56.61772460937496], + [12.421484375000034, 56.906396484374966], + [11.449316406250063, 58.118359374999955], + [11.43154296875008, 58.339990234374994], + [11.24824218750004, 58.369140625], + [11.14716796875004, 58.98862304687498], + [11.19580078125, 59.07827148437505], + [11.388281250000063, 59.036523437499966], + [11.470703125000057, 58.909521484375034], + [11.64277343750004, 58.92607421875002], + [11.798144531250074, 59.28989257812498], + [11.680761718750034, 59.59228515625003], + [12.486132812500074, 60.10678710937506], + [12.588671874999989, 60.450732421875045], + [12.29414062500004, 61.00268554687506], + [12.706054687500028, 61.059863281250074], + [12.88076171875008, 61.35229492187506], + [12.155371093750006, 61.720751953125045], + [12.303515625000074, 62.28559570312501], + [11.999902343750051, 63.29169921875001], + [12.175195312500051, 63.595947265625], + [12.792773437500017, 64], + [13.203515625000023, 64.07509765625], + [13.960546875000063, 64.01401367187498], + [14.141210937500006, 64.17353515624998], + [14.077636718750028, 64.464013671875], + [13.650292968750023, 64.58154296874997], + [14.47968750000004, 65.30146484374998], + [14.543261718750045, 66.12934570312498], + [15.483789062500051, 66.30595703124999], + [15.422949218750006, 66.48984374999998], + [16.40351562500004, 67.05498046875002], + [16.12744140625, 67.42583007812507], + [16.783593750000023, 67.89501953125], + [17.324609375000023, 68.10380859374999], + [17.91669921875001, 67.96489257812502], + [18.303027343750045, 68.55541992187497], + [19.969824218750063, 68.35639648437501], + [20.348046875000023, 68.84873046875003], + [20.116699218750057, 69.02089843750005], + [20.622167968750006, 69.036865234375], + [21.99746093750005, 68.52060546874998], + [22.854101562500034, 68.36733398437502], + [23.63886718750004, 67.95439453125002], + [23.454882812500045, 67.46025390625007], + [23.733593750000068, 67.42290039062499], + [23.64150390625005, 67.12939453124997], + [23.988574218750045, 66.81054687500003], + [23.700292968750034, 66.25263671874998], + [24.15546875000004, 65.80527343750006] + ] + ] + ] + }, + "properties": { "name": "Sweden", "childNum": 4 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [31.9482421875, -25.957617187500006], + [32.060546875, -26.018359375], + [32.04140625000002, -26.28125], + [32.10595703125, -26.52001953125], + [32.112890625, -26.839453125], + [32.02480468750002, -26.811132812500006], + [31.994726562500006, -26.817480468750006], + [31.967187500000023, -26.96064453125001], + [31.946093750000017, -27.173632812500003], + [31.958398437500023, -27.30585937500001], + [31.742578125000023, -27.30996093750001], + [31.469531250000017, -27.295507812500006], + [31.274023437500006, -27.238378906250006], + [31.063378906250023, -27.1123046875], + [30.938085937500006, -26.915820312500003], + [30.88330078125, -26.79238281250001], + [30.806738281250006, -26.785253906250006], + [30.794335937500023, -26.764257812500006], + [30.803320312500006, -26.41347656250001], + [31.08808593750001, -25.98066406250001], + [31.207324218750017, -25.843359375], + [31.33515625000001, -25.75556640625001], + [31.382617187500017, -25.74296875], + [31.415136718750006, -25.74658203125], + [31.921679687500017, -25.96875], + [31.9482421875, -25.957617187500006] + ] + ] + }, + "properties": { "name": "Swaziland", "childNum": 1 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [55.54033203125002, -4.693066406250011], + [55.54296875, -4.785546875], + [55.383398437500006, -4.609277343750009], + [55.45576171875001, -4.558789062500011], + [55.54033203125002, -4.693066406250011] + ] + ] + }, + "properties": { "name": "Seychelles", "childNum": 1 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [42.358984375, 37.10859375], + [41.78857421875, 36.59716796875], + [41.41679687500002, 36.5146484375], + [41.295996093750006, 36.383349609374996], + [41.354101562500006, 35.640429687499996], + [41.19472656250002, 34.768994140625], + [40.98701171875001, 34.429052734375], + [38.773535156250006, 33.372216796874994], + [36.818359375, 32.317285156249994], + [36.3720703125, 32.3869140625], + [35.78730468750001, 32.734912109374996], + [35.91347656250002, 32.94960937499999], + [35.869140625, 33.43173828125], + [36.03447265625002, 33.58505859375], + [35.98613281250002, 33.75263671875], + [36.36503906250002, 33.83935546875], + [36.27783203125, 33.92529296875], + [36.5849609375, 34.221240234374996], + [36.50439453125, 34.432373046875], + [36.32988281250002, 34.499609375], + [36.383886718750006, 34.65791015625], + [35.97626953125001, 34.629199218749996], + [35.902441406250006, 35.420703125], + [35.76445312500002, 35.571582031249996], + [35.83964843750002, 35.84921875], + [35.892675781250006, 35.916552734374996], + [35.96757812500002, 35.910058593749994], + [36.12734375000002, 35.831445312499994], + [36.15361328125002, 35.833886718749994], + [36.34755859375002, 36.003515625], + [36.37539062500002, 36.171240234375], + [36.63671875, 36.233984375], + [36.64140625000002, 36.263525390625], + [36.5375, 36.45742187499999], + [36.54667968750002, 36.50634765625], + [36.596875, 36.7013671875], + [36.62841796875, 36.777685546875], + [36.65859375000002, 36.802539062499996], + [36.77656250000001, 36.79267578125], + [36.94179687500002, 36.7583984375], + [36.9853515625, 36.702392578125], + [37.06621093750002, 36.652636718749996], + [37.43632812500002, 36.643310546875], + [37.523535156250006, 36.6783203125], + [37.7203125, 36.743701171874996], + [37.90664062500002, 36.79462890625], + [38.19169921875002, 36.9015625], + [38.7666015625, 36.693115234375], + [38.90644531250001, 36.694677734375], + [39.1083984375, 36.680566406249994], + [39.35664062500001, 36.681591796875], + [39.50146484375, 36.70224609375], + [39.6865234375, 36.738623046875], + [40.01640625000002, 36.826074218749994], + [40.705664062500006, 37.097705078124996], + [41.886816406250006, 37.156396484374994], + [42.05986328125002, 37.2060546875], + [42.16787109375002, 37.288623046874996], + [42.202734375, 37.29726562499999], + [42.24755859375, 37.2822265625], + [42.2685546875, 37.2765625], + [42.31289062500002, 37.22958984375], + [42.358984375, 37.10859375] + ] + ] + }, + "properties": { "name": "Syria", "childNum": 1 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [-72.3328125, 21.85136718749999], + [-72.14433593750002, 21.79272460937503], + [-72.33544921874994, 21.758007812499983], + [-72.3328125, 21.85136718749999] + ] + ] + }, + "properties": { "name": "Turks and Caicos Is.", "childNum": 1 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [23.980273437500017, 19.496630859375003], + [23.970800781250006, 15.721533203124991], + [23.10517578125001, 15.702539062499994], + [22.933886718750017, 15.533105468749994], + [22.93232421875001, 15.162109375], + [22.6708984375, 14.722460937500003], + [22.38154296875001, 14.550488281249997], + [22.53857421875, 14.161865234375], + [22.1064453125, 13.7998046875], + [22.228125, 13.32958984375], + [21.825292968750006, 12.79052734375], + [21.928125, 12.678125], + [22.352343750000017, 12.660449218749989], + [22.472460937500017, 12.067773437499994], + [22.580957031250023, 11.990136718749994], + [22.591113281250017, 11.579882812499989], + [22.922656250000017, 11.344873046874994], + [22.86005859375001, 10.919677734375], + [22.49384765625001, 10.996240234374994], + [21.771484375, 10.642822265625], + [21.682714843750006, 10.289843749999989], + [20.773242187500017, 9.405664062499994], + [20.342089843750017, 9.127099609374994], + [18.95625, 8.938867187499994], + [18.886035156250017, 8.836035156249991], + [19.108691406250017, 8.656152343749994], + [18.56416015625001, 8.0458984375], + [17.6494140625, 7.98359375], + [16.784765625, 7.550976562499997], + [16.545312500000023, 7.865478515625], + [16.37890625, 7.683544921874997], + [15.957617187500006, 7.507568359375], + [15.480078125, 7.523779296874991], + [15.5498046875, 7.787890624999989], + [15.1162109375, 8.557324218749997], + [14.332324218750017, 9.20351562499999], + [13.977246093750011, 9.691552734374994], + [14.243261718750006, 9.979736328125], + [15.654882812500006, 10.0078125], + [15.276074218750011, 10.357373046874997], + [15.132226562500023, 10.648486328124989], + [15.029882812500006, 11.11367187499999], + [15.08125, 11.845507812499989], + [14.847070312500023, 12.502099609374994], + [14.461718750000017, 13.021777343749989], + [14.244824218750011, 13.07734375], + [14.06396484375, 13.07851562499999], + [13.932324218750011, 13.258496093749997], + [13.606347656250023, 13.70458984375], + [13.505761718750023, 14.134423828124994], + [13.4482421875, 14.380664062500003], + [14.367968750000017, 15.750146484374994], + [15.474316406250011, 16.908398437499997], + [15.735058593750011, 19.904052734375], + [15.963183593750017, 20.34619140625], + [15.587109375000011, 20.733300781249994], + [15.607324218750023, 20.954394531250003], + [15.181835937500011, 21.523388671874997], + [14.97900390625, 22.99619140624999], + [15.984082031250011, 23.445214843749994], + [20.14765625000001, 21.38925781249999], + [23.980273437500017, 19.496630859375003] + ] + ] + }, + "properties": { "name": "Chad", "childNum": 1 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [0.900488281250006, 10.993261718749991], + [0.763378906250011, 10.386669921874997], + [1.330078125, 9.996972656249994], + [1.3857421875, 9.361669921874991], + [1.600195312500006, 9.050048828125], + [1.624707031250011, 6.997314453125], + [1.530957031250011, 6.992431640625], + [1.777929687500006, 6.294628906249997], + [1.62265625, 6.216796875], + [1.187207031250011, 6.089404296874989], + [0.736914062500006, 6.452587890624997], + [0.525585937500011, 6.850927734374991], + [0.634765625, 7.353662109374994], + [0.5, 7.546875], + [0.686328125000017, 8.354882812499994], + [0.37255859375, 8.75927734375], + [0.48876953125, 8.851464843749994], + [0.525683593750017, 9.398486328124989], + [0.2333984375, 9.463525390624994], + [0.342578125000017, 9.604150390624994], + [0.264550781250023, 9.644726562499997], + [0.380859375, 10.291845703124991], + [-0.08632812499999, 10.673046875], + [0.009423828125023, 11.02099609375], + [-0.068603515625, 11.115625], + [0.49267578125, 10.954980468749994], + [0.900488281250006, 10.993261718749991] + ] + ] + }, + "properties": { "name": "Togo", "childNum": 1 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [98.40908203125005, 7.90205078125004], + [98.2962890625, 7.776074218750054], + [98.32207031250007, 8.166308593749974], + [98.4349609375, 8.085644531249969], + [98.40908203125005, 7.90205078125004] + ] + ], + [ + [ + [100.070703125, 9.58603515625002], + [99.96240234375003, 9.421630859375], + [99.93955078125006, 9.559960937500037], + [100.070703125, 9.58603515625002] + ] + ], + [ + [ + [102.42675781250003, 11.988720703125026], + [102.30195312500004, 11.98081054687502], + [102.27744140625006, 12.151855468750043], + [102.42675781250003, 11.988720703125026] + ] + ], + [ + [ + [100.12246093750005, 20.316650390625057], + [100.11494140625004, 20.257666015625034], + [100.13974609375012, 20.245410156250017], + [100.31796875000006, 20.38588867187505], + [100.51953125000003, 20.17792968750004], + [100.39765625000004, 19.756103515625], + [100.51357421875005, 19.553466796875], + [101.21191406249997, 19.54833984375003], + [101.22080078125006, 19.486621093750074], + [101.19755859375007, 19.327929687500074], + [101.2863281250001, 18.977148437500006], + [101.04697265625012, 18.441992187500063], + [101.05058593750002, 18.407031250000045], + [101.1375, 18.28686523437497], + [101.14394531250005, 18.14262695312499], + [100.90849609375002, 17.583886718750023], + [100.95585937500002, 17.541113281250006], + [101.10517578125004, 17.47954101562499], + [101.16748046874997, 17.49902343749997], + [101.41367187500012, 17.71875], + [101.55507812500005, 17.812353515625034], + [101.56367187500004, 17.82050781250001], + [101.6875, 17.889404296875], + [101.77480468750005, 18.03339843750004], + [101.81865234375002, 18.06464843750001], + [101.87548828124997, 18.046435546875017], + [101.94746093750004, 18.081494140624983], + [102.03457031250005, 18.169824218750023], + [102.10146484375, 18.210644531249983], + [102.14824218750002, 18.20385742187503], + [102.35185546875002, 18.045947265625017], + [102.45878906250002, 17.984619140625057], + [102.55253906250007, 17.96508789062497], + [102.61679687500006, 17.833349609375034], + [102.66064453124997, 17.817968750000034], + [102.680078125, 17.824121093750023], + [103.05136718750006, 18.02851562500001], + [103.0912109375, 18.13823242187499], + [103.14853515625006, 18.221728515625045], + [103.19970703124997, 18.259472656249983], + [103.26318359374997, 18.27846679687505], + [103.27958984375002, 18.304980468750017], + [103.24892578125, 18.338964843750034], + [103.25175781250002, 18.373486328124955], + [103.2882812500001, 18.408398437499955], + [103.36699218750007, 18.42333984374997], + [103.48798828125004, 18.418164062499983], + [103.62968750000002, 18.38256835937503], + [103.79228515625002, 18.316503906249977], + [103.89882812500005, 18.295312500000023], + [103.949609375, 18.31899414062505], + [104.04873046875005, 18.216699218749994], + [104.19619140625005, 17.988378906250006], + [104.32265625, 17.815820312500023], + [104.428125, 17.69897460937503], + [104.7396484375, 17.461669921875], + [104.81601562500012, 17.30029296874997], + [104.75898437500004, 17.0771484375], + [104.7435546875, 16.884375], + [104.75058593750012, 16.647558593750063], + [104.81933593750003, 16.46606445312503], + [105.04716796875007, 16.160253906249977], + [105.14873046875007, 16.09355468749999], + [105.33066406250006, 16.037890625000017], + [105.40625, 15.987451171875051], + [105.39892578124997, 15.829882812500017], + [105.62207031250003, 15.699951171875], + [105.641015625, 15.656542968750045], + [105.6388671875001, 15.585937500000057], + [105.615625, 15.488281250000057], + [105.49042968750004, 15.256591796875], + [105.49042968750004, 15.127587890625009], + [105.5333984375001, 15.041601562499991], + [105.54667968750002, 14.932470703124963], + [105.52304687500012, 14.843310546875003], + [105.49736328125002, 14.590673828124963], + [105.47558593750003, 14.530126953124977], + [105.42265625000007, 14.471630859375054], + [105.34218750000005, 14.416699218750054], + [105.24365234375003, 14.367871093750054], + [105.1833007812501, 14.346240234374989], + [105.16914062500004, 14.336083984374966], + [105.12597656250003, 14.280957031250011], + [105.07412109375005, 14.227441406250037], + [104.77900390625004, 14.427832031250006], + [103.19941406250004, 14.332617187499977], + [102.90927734375006, 14.136718750000028], + [102.546875, 13.585693359375043], + [102.33632812500005, 13.560302734375014], + [102.49960937500012, 12.669970703125003], + [102.75566406250002, 12.42626953125], + [102.73662109375007, 12.089794921875011], + [102.93388671875002, 11.706689453125037], + [102.594140625, 12.203027343749994], + [102.54023437500004, 12.109228515624977], + [101.83574218750002, 12.640380859375014], + [100.89775390625007, 12.653808593749986], + [100.96269531250007, 13.431982421874991], + [100.60292968750005, 13.568164062500017], + [100.23564453125002, 13.48447265625002], + [99.99052734375007, 13.243457031250031], + [100.08994140625006, 13.045654296874972], + [99.96396484375006, 12.690039062500006], + [99.98906250000007, 12.170800781249994], + [99.16503906250003, 10.319824218750028], + [99.25390625000003, 9.265234375000034], + [99.83554687500012, 9.288378906250031], + [99.98955078125007, 8.589208984374977], + [100.129296875, 8.428076171875006], + [100.16347656250005, 8.508398437500034], + [100.27939453125006, 8.268505859375011], + [100.54521484375002, 7.226904296874991], + [100.43935546875005, 7.280761718750043], + [100.38037109375003, 7.541503906250043], + [100.28378906250006, 7.551513671875043], + [100.25664062500002, 7.774902343749986], + [100.16074218750012, 7.599267578124994], + [100.4235351562501, 7.18784179687502], + [101.01787109375002, 6.860937500000034], + [101.49794921875005, 6.865283203125031], + [102.10107421874997, 6.242236328125031], + [101.87363281250012, 5.825292968749991], + [101.67841796875004, 5.778808593750028], + [101.5560546875, 5.907763671875003], + [101.1139648437501, 5.636767578125045], + [100.98164062500004, 5.771044921875045], + [101.05351562500002, 6.242578125], + [100.87392578125, 6.24541015624996], + [100.75449218750012, 6.460058593749991], + [100.3454101562501, 6.549902343750006], + [100.26142578125004, 6.682714843749963], + [100.11914062499997, 6.441992187500048], + [99.69599609375004, 6.87666015625004], + [99.72031250000012, 7.106201171875], + [99.55302734375002, 7.218798828125031], + [99.59697265625002, 7.355615234375009], + [99.35859375000004, 7.372216796875023], + [99.26367187499997, 7.619042968750037], + [99.07763671874997, 7.718066406250045], + [99.05107421875002, 7.887841796874994], + [98.78867187500012, 8.059814453125028], + [98.703515625, 8.256738281250009], + [98.57919921875006, 8.344287109374989], + [98.42099609375006, 8.17822265625], + [98.30546875000007, 8.226220703125009], + [98.24179687500006, 8.767871093750045], + [98.70253906250005, 10.19038085937504], + [98.7572265625, 10.660937499999974], + [99.1901367187501, 11.105273437499989], + [99.61474609374997, 11.781201171875026], + [99.40507812500002, 12.547900390625003], + [99.12392578125, 13.030761718750043], + [99.13681640625006, 13.716699218749994], + [98.57001953125004, 14.359912109375031], + [98.20214843749997, 14.97592773437502], + [98.19101562500012, 15.204101562499972], + [98.55693359375007, 15.367675781249986], + [98.59238281250006, 16.05068359375005], + [98.81796875000012, 16.180810546874994], + [98.88828125000006, 16.351904296875034], + [98.83544921875003, 16.417578125], + [98.66074218750006, 16.330419921875006], + [98.4388671875, 16.975683593750034], + [97.7064453125, 17.79711914062503], + [97.63222656250005, 18.290332031250074], + [97.37392578125, 18.51796875], + [97.74589843750002, 18.58818359374999], + [97.816796875, 19.459960937500057], + [98.01503906250005, 19.74951171874997], + [98.37128906250004, 19.68916015625004], + [98.9166992187501, 19.77290039062504], + [99.07421875000003, 20.09936523437503], + [99.48593750000006, 20.14985351562501], + [99.45888671875005, 20.363037109375], + [99.72011718750005, 20.32543945312497], + [99.8903320312501, 20.424414062499977], + [99.9542968750001, 20.415429687500023], + [100.0036132812501, 20.37958984375001], + [100.12246093750005, 20.316650390625057] + ] + ] + ] + }, + "properties": { "name": "Thailand", "childNum": 4 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [70.66416015625, 39.85546875], + [70.55957031250003, 39.790917968749994], + [70.48925781250003, 39.86303710937503], + [70.48281250000005, 39.88271484375005], + [70.49775390625004, 39.88242187499998], + [70.56708984375004, 39.86660156250005], + [70.66416015625, 39.85546875] + ] + ], + [ + [ + [70.95800781250003, 40.238867187500034], + [70.59921875, 39.974511718749994], + [69.96679687499997, 40.202246093750034], + [69.46875, 40.020751953125], + [69.47099609375002, 39.990625], + [69.43193359375007, 39.909765625000034], + [69.36542968750004, 39.94707031250002], + [69.30722656250006, 39.968554687500045], + [69.27880859374997, 39.91777343749999], + [69.24472656250006, 39.82709960937498], + [69.29765625000007, 39.52480468750005], + [70.50117187500004, 39.58735351562501], + [70.79931640625003, 39.39472656250001], + [71.4703125, 39.60366210937502], + [71.50302734375006, 39.58217773437502], + [71.51738281250002, 39.55385742187502], + [71.50585937499997, 39.51708984374997], + [71.5033203125, 39.47880859374999], + [71.73222656250002, 39.422998046874994], + [71.77861328125007, 39.27797851562502], + [72.04277343750002, 39.352148437500034], + [72.08417968750004, 39.310644531250034], + [72.14736328125005, 39.26074218749997], + [72.22998046874997, 39.20751953124997], + [72.63994140625002, 39.385986328125], + [73.10927734375, 39.36191406249998], + [73.2349609375, 39.37456054687499], + [73.3361328125001, 39.41235351562506], + [73.38740234375004, 39.442724609375034], + [73.4704101562501, 39.46059570312502], + [73.63164062500007, 39.44887695312502], + [73.63632812500006, 39.396679687499955], + [73.60732421875, 39.229199218749955], + [73.8052734375, 38.968652343749994], + [73.69609375000007, 38.85429687499996], + [73.80166015625, 38.60688476562501], + [74.02558593750004, 38.53984375000002], + [74.27744140625, 38.659765625000034], + [74.81230468750002, 38.46030273437498], + [74.8942382812501, 37.60141601562498], + [75.11875, 37.38569335937498], + [74.89130859375004, 37.231640624999955], + [74.875390625, 37.24199218750002], + [74.83046875, 37.28593750000002], + [74.73056640625006, 37.35703125], + [74.659375, 37.39448242187501], + [74.34902343750005, 37.41875], + [74.25966796875005, 37.41542968750002], + [74.20351562500005, 37.37246093750005], + [74.16708984375, 37.32944335937498], + [73.74960937500006, 37.23178710937498], + [73.6535156250001, 37.239355468750034], + [73.62753906250006, 37.261572265625006], + [73.71728515625003, 37.32944335937498], + [73.7337890625, 37.37578125000002], + [73.72060546875, 37.41875], + [73.65712890625005, 37.43046875], + [73.6046875000001, 37.44604492187503], + [73.48134765625, 37.4716796875], + [73.38291015625006, 37.462255859375034], + [73.21113281250004, 37.40849609375002], + [72.89550781250003, 37.26752929687498], + [72.65742187500004, 37.029052734375], + [71.665625, 36.696923828124994], + [71.530859375, 36.845117187499994], + [71.43291015625007, 37.12753906249998], + [71.5822265625001, 37.91010742187498], + [71.55195312500004, 37.93315429687496], + [71.48779296874997, 37.93188476562497], + [71.38964843750003, 37.90629882812502], + [71.31992187500006, 37.90185546875], + [71.27851562500004, 37.91840820312498], + [71.33271484375004, 38.170263671875034], + [71.25585937499997, 38.306982421875006], + [70.7359375, 38.42255859375001], + [70.41777343750002, 38.075439453125], + [70.21464843750002, 37.92441406250006], + [70.19941406250004, 37.88603515624996], + [70.25498046875006, 37.76538085937497], + [70.25146484374997, 37.66416015625006], + [70.18867187500004, 37.58247070312501], + [70.11982421875004, 37.54350585937499], + [69.9849609375, 37.566162109375], + [69.8208984375, 37.60957031250004], + [69.62578125000002, 37.59404296874999], + [69.49208984375, 37.55307617187498], + [69.42011718750004, 37.486718749999966], + [69.39921875000007, 37.39931640625002], + [69.42968749999997, 37.290869140625034], + [69.414453125, 37.20776367187497], + [69.35380859375007, 37.15004882812502], + [69.3039062500001, 37.11694335937503], + [69.26484375000004, 37.1083984375], + [69.18017578125003, 37.158300781250034], + [68.96044921875003, 37.32504882812498], + [68.9118164062501, 37.33393554687501], + [68.88525390624997, 37.32807617187498], + [68.85537109375005, 37.31684570312501], + [68.83847656250006, 37.30283203124998], + [68.82373046874997, 37.27070312500001], + [68.78203125000002, 37.25800781250001], + [68.7232421875, 37.26801757812501], + [68.6691406250001, 37.258398437500006], + [68.3869140625001, 37.1375], + [68.29951171875004, 37.08842773437502], + [68.28476562500006, 37.036328124999955], + [68.2609375000001, 37.01308593750002], + [68.2121093750001, 37.02153320312496], + [68.0677734375, 36.949804687500006], + [67.95800781249997, 36.972021484375006], + [67.83447265624997, 37.06420898437506], + [67.75898437500004, 37.172216796875034], + [67.7980468750001, 37.244970703125006], + [67.81435546875005, 37.48701171875004], + [68.3502929687501, 38.211035156250006], + [68.08720703125002, 38.47353515625002], + [68.13251953125004, 38.927636718749966], + [67.69443359375006, 38.99462890625003], + [67.64833984375005, 39.13105468750004], + [67.3576171875001, 39.216699218749994], + [67.426171875, 39.46557617187497], + [67.71904296875007, 39.62138671875002], + [68.46328125, 39.53671874999998], + [68.63896484375007, 39.8388671875], + [68.86875, 39.90747070312503], + [68.80468750000003, 40.05034179687499], + [68.9720703125, 40.08994140624998], + [68.63066406250007, 40.16708984374998], + [69.27490234374997, 40.19809570312498], + [69.20625, 40.566552734374994], + [69.35722656250002, 40.76738281249996], + [69.71289062500003, 40.65698242187503], + [70.40195312500006, 41.03510742187498], + [70.75107421875006, 40.721777343750006], + [70.37158203125003, 40.38413085937506], + [70.653125, 40.201171875], + [70.95800781250003, 40.238867187500034] + ] + ] + ] + }, + "properties": { "name": "Tajikistan", "childNum": 2 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [66.52226562500007, 37.34848632812506], + [66.471875, 37.3447265625], + [65.7650390625, 37.56914062499996], + [65.55498046875002, 37.25117187500004], + [65.30361328125005, 37.24677734375001], + [65.08964843750007, 37.237939453124994], + [64.9515625, 37.19355468750001], + [64.81630859375005, 37.13208007812503], + [64.7824218750001, 37.05927734375001], + [64.60253906250003, 36.554541015625034], + [64.5658203125, 36.427587890625034], + [64.51103515625002, 36.34067382812498], + [64.184375, 36.14892578125], + [63.8625, 36.012353515624994], + [63.12998046875006, 35.84619140624997], + [63.169726562500074, 35.678125], + [63.05664062500003, 35.44580078125003], + [62.98027343750002, 35.40917968750003], + [62.85800781250006, 35.34965820312499], + [62.688085937500006, 35.25532226562504], + [62.3078125000001, 35.17080078125005], + [62.08964843750002, 35.3796875], + [61.62099609375005, 35.43232421875004], + [61.34472656249997, 35.62949218750006], + [61.26201171875002, 35.61958007812498], + [61.25214843750004, 35.86762695312498], + [61.15292968750006, 35.97675781250001], + [61.212011718750006, 36.190527343750034], + [61.11962890625003, 36.64257812500003], + [60.34130859375003, 36.63764648437501], + [60.06279296875002, 36.962890625], + [59.454980468749994, 37.25283203125002], + [59.30175781249997, 37.51064453125005], + [58.81542968750003, 37.683496093749994], + [58.261621093749994, 37.665820312500045], + [57.35371093750004, 37.97333984374998], + [57.1935546875001, 38.216406250000034], + [56.440625, 38.249414062499994], + [56.272070312500006, 38.080419921875034], + [55.38085937500003, 38.051123046875034], + [54.90009765625004, 37.77792968750006], + [54.6994140625001, 37.47016601562498], + [53.91416015625006, 37.34355468750002], + [53.86865234375003, 38.949267578125045], + [53.70458984375003, 39.209570312500034], + [53.33632812500005, 39.34082031250006], + [53.15664062499999, 39.26499023437506], + [53.23564453125002, 39.608544921874966], + [53.603125, 39.546972656250034], + [53.472265625, 39.66879882812498], + [53.48730468749997, 39.909375], + [52.9875, 39.98759765625002], + [53.03554687500005, 39.7744140625], + [52.80468749999997, 40.054003906250045], + [52.73369140625002, 40.39873046875002], + [52.943457031250006, 41.03808593750006], + [53.1452148437501, 40.82495117187497], + [53.61523437500003, 40.818505859374994], + [53.87001953125005, 40.64868164062503], + [54.37734375, 40.693261718749966], + [54.319433593750006, 40.83457031249998], + [54.68505859375003, 40.873046875], + [54.70371093750006, 41.071142578125034], + [54.094824218750006, 41.51938476562506], + [53.80468749999997, 42.11762695312498], + [53.16416015625006, 42.09379882812502], + [52.97001953125002, 41.97622070312505], + [52.81484375, 41.711816406249994], + [52.850390625000074, 41.20029296875006], + [52.4938476562501, 41.780371093750034], + [53.0558593750001, 42.14775390624999], + [54.120996093749994, 42.335205078125], + [54.85380859375002, 41.965185546875006], + [55.434375, 41.296289062499994], + [55.97744140625005, 41.32221679687504], + [57.01796875, 41.26347656249996], + [57.11884765625004, 41.35029296874998], + [56.96406250000004, 41.856542968750006], + [57.290625, 42.123779296875], + [57.814257812500074, 42.18984375000005], + [58.02890625, 42.48764648437506], + [58.474414062500074, 42.29936523437496], + [58.15156250000004, 42.628076171874966], + [58.477148437500006, 42.66284179687503], + [58.5890625000001, 42.778466796874966], + [59.35429687500002, 42.32329101562496], + [59.98515625000002, 42.21171875], + [59.94179687499999, 41.97353515625002], + [60.20078125000006, 41.803125], + [60.07558593750005, 41.759667968749966], + [60.089648437500074, 41.39941406250003], + [60.454980468749994, 41.221630859374955], + [61.2423828125001, 41.18920898437503], + [61.496972656249994, 41.276074218749955], + [61.90283203124997, 41.09370117187501], + [62.48320312500002, 39.97563476562496], + [63.76367187500003, 39.16054687499999], + [64.3099609375, 38.97729492187497], + [65.612890625, 38.23857421875002], + [65.97119140624997, 38.244238281250006], + [66.60625, 37.98671875000005], + [66.52558593750004, 37.785742187500034], + [66.51132812500006, 37.59916992187496], + [66.51064453125, 37.45869140625004], + [66.52226562500007, 37.34848632812506] + ] + ] + }, + "properties": { "name": "Turkmenistan", "childNum": 1 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [124.0363281250001, -9.341601562500031], + [124.44443359375012, -9.190332031250023], + [124.28232421875012, -9.427929687500026], + [124.0363281250001, -9.341601562500031] + ] + ], + [ + [ + [125.06816406250002, -9.511914062499997], + [124.96015625000004, -9.213769531250009], + [125.10048828125, -9.189843750000023], + [125.14902343750012, -9.042578125000034], + [124.93681640625007, -9.053417968750026], + [124.92226562500005, -8.942480468749977], + [125.17802734375002, -8.647851562499994], + [125.38183593749997, -8.575390624999983], + [126.61972656250006, -8.459472656249986], + [126.96640625000012, -8.315722656250017], + [127.29609375000004, -8.424511718749969], + [126.91523437500004, -8.715234374999966], + [125.40800781250002, -9.275781250000023], + [125.06816406250002, -9.511914062499997] + ] + ], + [ + [ + [125.64609375000006, -8.139941406250003], + [125.5794921875, -8.311816406250017], + [125.50712890625007, -8.275097656249997], + [125.64609375000006, -8.139941406250003] + ] + ] + ] + }, + "properties": { "name": "Timor-Leste", "childNum": 3 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [-175.1619140625, -21.169335937500023], + [-175.07817382812496, -21.129003906249977], + [-175.15659179687495, -21.26367187499997], + [-175.36235351562496, -21.106835937499994], + [-175.1619140625, -21.169335937500023] + ] + ], + [ + [ + [-173.953515625, -18.63935546875001], + [-174.06914062500002, -18.640234375], + [-173.96806640624993, -18.565332031250023], + [-173.953515625, -18.63935546875001] + ] + ] + ] + }, + "properties": { "name": "Tonga", "childNum": 2 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [-61.012109374999966, 10.134326171874989], + [-61.906103515625006, 10.069140625000031], + [-61.49931640624999, 10.268554687499972], + [-61.47827148437497, 10.603369140624977], + [-61.65117187499993, 10.718066406249974], + [-60.917626953124966, 10.84023437499999], + [-61.03374023437502, 10.669873046875026], + [-61.012109374999966, 10.134326171874989] + ] + ] + }, + "properties": { "name": "Trinidad and Tobago", "childNum": 1 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [10.957617187500063, 33.72207031250005], + [10.722070312500051, 33.738916015624994], + [10.745214843750063, 33.88867187500006], + [11.017871093749989, 33.82333984374998], + [10.957617187500063, 33.72207031250005] + ] + ], + [ + [ + [11.278027343750068, 34.753808593749994], + [11.123632812500063, 34.68168945312496], + [11.254882812500057, 34.82031250000006], + [11.278027343750068, 34.753808593749994] + ] + ], + [ + [ + [10.274609375000011, 31.684960937499994], + [10.114941406250068, 31.46376953125005], + [10.216406250000063, 30.78320312500003], + [10.05976562500004, 30.58007812500003], + [9.932519531250051, 30.42534179687496], + [9.895019531250028, 30.387304687500034], + [9.51875, 30.229394531249994], + [9.224023437500023, 31.373681640624994], + [9.160253906250006, 31.621337890625], + [9.044042968750034, 32.072363281250034], + [8.333398437500051, 32.54360351562502], + [8.1125, 33.055322265624994], + [7.877246093750017, 33.172119140625], + [7.534375, 33.717919921874994], + [7.513867187500068, 34.080517578124955], + [8.24560546875, 34.73408203124998], + [8.276855468750057, 34.97949218749997], + [8.312109375000063, 35.084619140624994], + [8.394238281250011, 35.20385742187503], + [8.318066406250011, 35.654931640624994], + [8.348730468750063, 36.367968750000045], + [8.207617187500006, 36.518945312499994], + [8.601269531250068, 36.83393554687504], + [8.576562500000023, 36.93720703125001], + [9.687988281250057, 37.34038085937499], + [9.838476562500063, 37.30898437499999], + [9.830273437499983, 37.13535156250006], + [9.875585937499977, 37.25415039062503], + [10.196386718750063, 37.205859375000045], + [10.293261718750074, 36.781494140625], + [10.412304687499983, 36.73183593750002], + [11.053906250000068, 37.07250976562506], + [11.12666015625004, 36.874072265625045], + [10.476562500000028, 36.175146484375006], + [10.590820312500028, 35.88725585937499], + [11.00429687500008, 35.63383789062496], + [11.120117187500057, 35.24028320312499], + [10.69091796875, 34.67846679687503], + [10.118359375000068, 34.280078125000045], + [10.049023437500068, 34.056298828124994], + [10.305273437500034, 33.72827148437497], + [10.713183593750017, 33.68901367187496], + [10.722753906250006, 33.514404296875], + [10.958007812500057, 33.62631835937498], + [11.257421875000034, 33.30883789062506], + [11.202636718749972, 33.24921874999998], + [11.50458984375004, 33.181933593750045], + [11.502441406250028, 33.15556640624999], + [11.467187500000051, 32.96572265625005], + [11.459179687500011, 32.897363281249966], + [11.453906250000017, 32.64257812500003], + [11.533789062500034, 32.52495117187496], + [11.535937500000017, 32.47333984375001], + [11.504980468750034, 32.413671875000034], + [11.358007812500006, 32.34521484375003], + [11.168261718750074, 32.25673828125002], + [11.005175781250074, 32.17270507812506], + [10.826367187500068, 32.080664062500034], + [10.771582031250006, 32.02119140625001], + [10.60888671875, 31.929541015624977], + [10.47578125000004, 31.736035156249983], + [10.274609375000011, 31.684960937499994] + ] + ] + ] + }, + "properties": { "name": "Tunisia", "childNum": 3 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [25.970019531250045, 40.136328125], + [25.6689453125, 40.13588867187502], + [25.918359375000023, 40.23798828125004], + [25.970019531250045, 40.136328125] + ] + ], + [ + [ + [43.43339843750002, 41.155517578125], + [43.43945312500003, 41.10712890625001], + [43.72265624999997, 40.71953124999999], + [43.56933593750003, 40.48237304687498], + [43.66621093750004, 40.12636718750002], + [44.28925781250004, 40.040380859375006], + [44.76826171875004, 39.70351562500005], + [44.81718750000002, 39.65043945312496], + [44.58710937500004, 39.76855468750006], + [44.3893554687501, 39.422119140625], + [44.02324218750002, 39.37744140625006], + [44.27167968750004, 38.83603515625006], + [44.2985351562501, 38.38627929687499], + [44.4499023437501, 38.33422851562506], + [44.21132812499999, 37.908056640625006], + [44.589941406250006, 37.710351562499966], + [44.574023437500074, 37.435400390625006], + [44.79414062500004, 37.290380859375034], + [44.76513671875003, 37.142431640625006], + [44.73095703124997, 37.16528320312503], + [44.66933593750005, 37.17358398437503], + [44.60595703124997, 37.176025390625], + [44.401953125, 37.05849609375002], + [44.325585937499994, 37.0107421875], + [44.28183593750006, 36.97802734374997], + [44.24570312500006, 36.983300781249994], + [44.20166015624997, 37.05180664062502], + [44.208398437499994, 37.20263671875], + [44.19179687499999, 37.249853515625034], + [44.15625, 37.28295898437503], + [44.11445312500004, 37.30185546875006], + [44.01318359375003, 37.313525390625045], + [43.83642578124997, 37.223535156249994], + [43.67578125000003, 37.227246093749955], + [43.09248046875004, 37.36738281249998], + [42.936621093750006, 37.32475585937502], + [42.77460937500004, 37.371875], + [42.74111328125005, 37.361914062500034], + [42.6354492187501, 37.249267578125], + [42.45585937500002, 37.128710937500045], + [42.358984375, 37.10859375000004], + [42.31289062499999, 37.22958984374998], + [42.26855468749997, 37.276562499999955], + [42.24755859375003, 37.28222656250006], + [42.20273437500006, 37.29726562499999], + [42.16787109375005, 37.28862304687502], + [42.059863281250074, 37.2060546875], + [41.886816406250006, 37.156396484374994], + [40.70566406250006, 37.09770507812502], + [40.4503906250001, 37.00888671875006], + [40.016406250000074, 36.82607421875002], + [39.68652343749997, 36.73862304687506], + [39.50146484374997, 36.702246093750034], + [39.35664062500004, 36.68159179687498], + [39.10839843749997, 36.68056640625005], + [38.90644531250004, 36.69467773437498], + [38.76660156249997, 36.69311523437503], + [38.19169921875002, 36.90156250000004], + [37.90664062500005, 36.79462890625001], + [37.7203125, 36.74370117187502], + [37.52353515625006, 36.678320312500034], + [37.436328125000074, 36.643310546875], + [37.327050781249994, 36.64658203125006], + [37.18740234375005, 36.655908203124994], + [37.066210937500074, 36.652636718750045], + [36.98535156250003, 36.70239257812506], + [36.94179687499999, 36.758398437500006], + [36.77656250000004, 36.79267578124998], + [36.65859375000005, 36.80253906250002], + [36.62841796875003, 36.777685546875034], + [36.596875, 36.70136718750001], + [36.546679687500074, 36.50634765625], + [36.5375, 36.457421874999966], + [36.63671874999997, 36.233984375], + [36.37539062499999, 36.171240234375034], + [36.347558593749994, 36.003515625000034], + [36.20195312500002, 35.93754882812502], + [36.15361328125002, 35.83388671875005], + [36.12734375, 35.831445312499994], + [35.967578125000074, 35.91005859375002], + [35.89267578125006, 35.91655273437502], + [35.81093750000005, 36.30986328125002], + [36.18847656250003, 36.65898437499999], + [36.048925781250006, 36.91059570312501], + [35.393164062500006, 36.57519531249997], + [34.70361328125003, 36.81679687499999], + [33.694726562499994, 36.18198242187498], + [32.794824218749994, 36.03588867187497], + [32.37773437500002, 36.18364257812496], + [32.02197265625003, 36.53530273437502], + [31.35253906249997, 36.80107421874999], + [30.64404296874997, 36.86567382812501], + [30.446093750000074, 36.269873046875034], + [29.6890625, 36.15668945312498], + [29.22363281249997, 36.32446289062497], + [28.96962890625008, 36.71533203125003], + [28.303710937500057, 36.81196289062498], + [28.01943359375005, 36.63447265624998], + [28.083984375000057, 36.75146484375], + [27.453906250000017, 36.712158203125], + [28.00537109375003, 36.83198242187498], + [28.242382812500068, 37.029052734375], + [27.262988281250045, 36.97656250000003], + [27.30019531250005, 37.12685546875002], + [27.53505859375005, 37.16386718750002], + [27.06796875, 37.65791015625004], + [27.224414062500074, 37.725439453125006], + [27.23242187500003, 37.978662109374994], + [26.29072265625001, 38.27719726562498], + [26.44130859375005, 38.64121093749998], + [26.67421875000008, 38.33574218750002], + [27.14423828125001, 38.45195312499996], + [26.906835937500034, 38.48173828124999], + [26.763671875, 38.709619140624966], + [27.013671875000057, 38.88686523437502], + [26.814941406250057, 38.96098632812502], + [26.853613281250034, 39.115625], + [26.68183593750004, 39.292236328125], + [26.89921874999999, 39.549658203125034], + [26.113085937500074, 39.46738281249998], + [26.101367187500074, 39.56894531249998], + [26.18134765625004, 39.99008789062498], + [26.738085937500045, 40.40024414062506], + [27.28457031250008, 40.45561523437496], + [27.4755859375, 40.319921875000034], + [27.72802734375, 40.32880859374998], + [27.84853515625005, 40.38173828125002], + [27.73183593750008, 40.48149414062499], + [27.87490234375008, 40.512939453125], + [27.989550781250074, 40.48945312500001], + [27.96259765625001, 40.369873046875], + [29.00712890624999, 40.389746093750034], + [28.787890625000017, 40.534033203125034], + [28.95800781250003, 40.63056640624998], + [29.849218750000063, 40.760107421875006], + [29.113867187499977, 40.93784179687506], + [29.14814453125004, 41.221044921875034], + [31.25488281249997, 41.10761718750001], + [31.45800781249997, 41.32001953125004], + [32.306445312500074, 41.72958984374998], + [33.38134765625003, 42.01757812500003], + [34.75048828124997, 41.95683593749999], + [35.006445312500006, 42.06328125000002], + [35.15488281250006, 42.02753906250001], + [35.12207031250003, 41.89111328125003], + [35.297753906249994, 41.72851562500003], + [35.558007812499994, 41.63403320312506], + [36.05175781249997, 41.68256835937498], + [36.40537109375006, 41.27460937500001], + [36.77773437499999, 41.36347656250001], + [37.066210937500074, 41.184423828125034], + [38.38105468750004, 40.92451171875001], + [39.426367187500006, 41.10644531250003], + [40.26523437500006, 40.96132812500005], + [41.08359375000006, 41.26118164062504], + [41.41435546875002, 41.42363281249999], + [41.510058593750074, 41.51748046875002], + [41.70175781250006, 41.471582031249994], + [41.77939453125006, 41.44052734374998], + [41.823535156250074, 41.432373046875], + [41.92578125000003, 41.49565429687502], + [42.46640625, 41.43984375000002], + [42.56738281249997, 41.55927734375001], + [42.590429687500006, 41.57070312500002], + [42.60683593750005, 41.57880859374998], + [42.682421875000074, 41.58574218749999], + [42.75410156250004, 41.57890625000002], + [42.787890625000074, 41.56372070312503], + [42.82167968750005, 41.49238281249998], + [42.90673828125003, 41.46684570312502], + [43.05712890625003, 41.35283203124996], + [43.149023437500006, 41.30712890624997], + [43.171289062499994, 41.28793945312498], + [43.14101562499999, 41.26484374999998], + [43.15283203124997, 41.23642578125006], + [43.20546875000005, 41.19916992187501], + [43.43339843750002, 41.155517578125] + ] + ], + [ + [ + [27.47480468750001, 41.946875], + [28.014453125000017, 41.96904296874999], + [28.197851562500063, 41.55449218750002], + [29.057226562500006, 41.22973632812503], + [28.95625, 41.00820312499999], + [28.172167968750074, 41.08071289062502], + [27.49941406250005, 40.97314453124997], + [27.258007812499983, 40.687353515625006], + [26.772070312500034, 40.498046875], + [26.202734375000034, 40.07539062500004], + [26.25380859375005, 40.31469726562503], + [26.792089843750034, 40.626611328124994], + [26.10546875000003, 40.61132812499997], + [26.03896484375008, 40.726757812499955], + [26.331054687500057, 40.954492187499994], + [26.330664062499977, 41.23876953125], + [26.62490234375008, 41.401757812499994], + [26.581347656250074, 41.60126953125004], + [26.320898437500034, 41.716552734375], + [26.3603515625, 41.80156249999999], + [26.51142578125004, 41.82636718749998], + [26.549707031250023, 41.896728515625], + [26.5796875, 41.947949218749955], + [26.615332031250063, 41.964892578125045], + [26.884863281250006, 41.99184570312502], + [26.96875, 42.02685546875006], + [27.01171875, 42.05864257812496], + [27.193359375000057, 42.07709960937498], + [27.24433593750004, 42.09326171875], + [27.294921875000057, 42.079541015624955], + [27.47480468750001, 41.946875] + ] + ] + ] + }, + "properties": { "name": "Turkey", "childNum": 3 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [39.71132812499999, -7.977441406250023], + [39.602929687499994, -7.936132812499949], + [39.907128906249994, -7.649218750000031], + [39.71132812499999, -7.977441406250023] + ] + ], + [ + [ + [39.49648437499999, -6.174609375], + [39.573046875000074, -6.387402343750011], + [39.48095703124997, -6.45371093750002], + [39.18232421875004, -6.172558593750026], + [39.30898437499999, -5.721972656249974], + [39.49648437499999, -6.174609375] + ] + ], + [ + [ + [39.86503906250002, -4.906152343750037], + [39.74931640625002, -5.443847656249986], + [39.646777343750074, -5.368554687500009], + [39.6734375, -4.927050781250031], + [39.86503906250002, -4.906152343750037] + ] + ], + [ + [ + [33.90322265625005, -1.002050781250034], + [37.643847656250074, -3.045410156250028], + [37.608203125000074, -3.497070312500028], + [39.221777343750006, -4.692382812500014], + [38.80468750000003, -6.070117187500031], + [38.87402343750003, -6.33125], + [39.5460937500001, -7.024023437500034], + [39.288476562499994, -7.517871093750003], + [39.28701171875005, -7.787695312500006], + [39.4284179687501, -7.81279296874996], + [39.441015625, -8.011523437499946], + [39.304003906250074, -8.44384765625], + [39.451269531250006, -8.94296875], + [39.64130859375004, -9.19248046875002], + [39.72519531250006, -10.000488281249972], + [40.46357421875004, -10.464355468749972], + [39.98867187499999, -10.820800781250014], + [39.81708984375004, -10.912402343750031], + [38.9875, -11.167285156250003], + [38.49179687500006, -11.413281250000026], + [37.92021484375002, -11.294726562500031], + [37.72480468750004, -11.58066406250002], + [37.54169921875004, -11.675097656249974], + [37.37285156250002, -11.710449218749986], + [36.97890625000005, -11.566992187499977], + [36.30566406250003, -11.706347656249946], + [36.191308593749994, -11.670703124999974], + [36.17548828125004, -11.60927734374998], + [36.08222656250004, -11.537304687499969], + [35.91132812500004, -11.45468750000002], + [35.785449218750074, -11.452929687500017], + [35.63095703125006, -11.582031250000028], + [35.564355468749994, -11.602343749999989], + [35.418261718750074, -11.583203125], + [35.18261718750003, -11.574804687499977], + [34.95947265625003, -11.578125], + [34.93701171874997, -11.463476562500034], + [34.890625, -11.3935546875], + [34.77382812500005, -11.341699218750009], + [34.60791015624997, -11.08046875], + [34.66708984375006, -10.792480468750028], + [34.56992187500006, -10.241113281249966], + [34.32089843750006, -9.731542968749977], + [33.99560546875003, -9.495410156250003], + [33.88886718750004, -9.670117187499983], + [32.91992187500003, -9.407421875000026], + [32.75664062500002, -9.322265625], + [31.94257812500004, -9.05400390624996], + [31.91865234375004, -8.942187500000017], + [31.886132812499994, -8.921972656249977], + [31.81806640625004, -8.902246093749952], + [31.673632812500017, -8.908789062499963], + [31.55625, -8.80546875], + [31.44921874999997, -8.65390625], + [31.35058593750003, -8.607031250000034], + [31.07636718750004, -8.611914062499963], + [30.968359375000063, -8.550976562499983], + [30.89199218750005, -8.473730468749963], + [30.830664062500063, -8.385546875000031], + [30.720898437500097, -8.104394531250037], + [30.40673828125003, -7.460644531249983], + [30.313183593750097, -7.203710937499949], + [30.212695312500017, -7.037890625000017], + [30.10625, -6.915039062500028], + [29.961816406249994, -6.803125], + [29.798144531250017, -6.691894531249957], + [29.70966796875004, -6.61689453125004], + [29.590625, -6.394433593750023], + [29.540820312500017, -6.313867187500037], + [29.50625, -6.172070312500011], + [29.480078125, -6.025], + [29.490820312500063, -5.96542968750002], + [29.59638671875004, -5.775976562499963], + [29.60703125, -5.722656250000028], + [29.59414062500005, -5.650781250000037], + [29.542382812499994, -5.499804687500017], + [29.34277343749997, -4.983105468749997], + [29.32343750000004, -4.898828124999966], + [29.32568359374997, -4.835644531249969], + [29.404199218749994, -4.49667968750002], + [29.40322265625005, -4.449316406249963], + [29.71777343750003, -4.45585937499996], + [29.94726562499997, -4.307324218749983], + [30.4, -3.65390625], + [30.790234375000097, -3.274609375000011], + [30.811132812500006, -3.116406250000011], + [30.78027343750003, -2.984863281249957], + [30.70947265624997, -2.977246093749997], + [30.604296875000074, -2.935253906249969], + [30.515039062499994, -2.917578125], + [30.45556640625003, -2.893164062500006], + [30.433496093749994, -2.874511718750028], + [30.424023437500097, -2.82402343749996], + [30.473339843750097, -2.6943359375], + [30.42421875000005, -2.641601562500014], + [30.441992187500006, -2.613476562499969], + [30.53369140624997, -2.426269531250014], + [30.55361328125005, -2.400097656250011], + [30.593359375000063, -2.39677734374996], + [30.65664062500005, -2.373828124999989], + [30.71484375000003, -2.363476562500011], + [30.7625, -2.371679687499991], + [30.828710937500006, -2.338476562499977], + [30.85498046874997, -2.265429687500017], + [30.8765625, -2.143359375000017], + [30.864648437499994, -2.044042968749949], + [30.819140625000017, -1.967480468749983], + [30.812597656250006, -1.56308593750002], + [30.76220703124997, -1.458691406249983], + [30.710742187500074, -1.396777343749974], + [30.631933593750006, -1.36748046874996], + [30.508105468750074, -1.208203125000026], + [30.47021484374997, -1.13115234374996], + [30.47705078124997, -1.0830078125], + [30.509960937500097, -1.067285156249994], + [30.51992187499999, -1.0625], + [30.67275390625005, -1.051367187499949], + [30.741992187500017, -1.007519531249997], + [30.809179687500063, -0.994921875], + [30.82363281250005, -0.999023437499943], + [30.84472656250003, -1.002050781250034], + [32.371875, -1.002050781250034], + [33.90322265625005, -1.002050781250034] + ] + ] + ] + }, + "properties": { "name": "Tanzania", "childNum": 4 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [30.50996093750001, -1.067285156250009], + [30.46992187500001, -1.066015625], + [30.41230468750001, -1.063085937500006], + [30.360253906250023, -1.074609375], + [29.930078125000023, -1.469921875000011], + [29.82539062500001, -1.335546875], + [29.576953125000017, -1.387890625000011], + [29.717675781250023, 0.098339843749997], + [29.934472656250023, 0.4990234375], + [29.94287109375, 0.819238281249994], + [31.252734375000017, 2.044580078124994], + [31.176367187500006, 2.270068359374989], + [30.728613281250006, 2.455371093749989], + [30.8466796875, 2.847021484374991], + [30.754003906250006, 3.041796874999989], + [30.90644531250001, 3.408935546875], + [30.83857421875001, 3.49072265625], + [31.15234375, 3.785595703124997], + [31.547167968750017, 3.677587890624991], + [31.79804687500001, 3.802636718749994], + [32.13593750000001, 3.519726562499997], + [32.33574218750002, 3.706201171874994], + [32.99726562500001, 3.880175781249989], + [33.489355468750006, 3.755078125], + [33.568457031250006, 3.81171875], + [33.74160156250002, 3.985253906249994], + [33.97607421875, 4.22021484375], + [34.13203125000001, 3.88916015625], + [34.18574218750001, 3.869775390624994], + [34.1650390625, 3.81298828125], + [34.26708984375, 3.733154296875], + [34.39287109375002, 3.691503906249991], + [34.43769531250001, 3.650585937499997], + [34.44179687500002, 3.60625], + [34.3994140625, 3.412695312499991], + [34.4072265625, 3.357519531249991], + [34.447851562500006, 3.163476562499994], + [34.90576171875, 2.4796875], + [34.88300781250001, 2.417919921874997], + [34.96406250000001, 2.062402343749994], + [34.9775390625, 1.861914062499991], + [34.97646484375002, 1.719628906249994], + [34.79863281250002, 1.24453125], + [34.48173828125002, 1.042138671874994], + [34.41083984375001, 0.867285156249991], + [34.16093750000002, 0.605175781249997], + [33.94316406250002, 0.173779296874997], + [33.90322265625002, -1.002050781250006], + [32.371875, -1.002050781250006], + [30.8447265625, -1.002050781250006], + [30.823632812500023, -0.9990234375], + [30.809179687500006, -0.994921875], + [30.741992187500017, -1.007519531250011], + [30.672753906250023, -1.051367187500006], + [30.598730468750006, -1.069726562500009], + [30.519921875000023, -1.0625], + [30.50996093750001, -1.067285156250009] + ] + ] + }, + "properties": { "name": "Uganda", "childNum": 1 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [32.01220703124997, 46.20390624999999], + [32.15009765625004, 46.1546875], + [31.56386718750005, 46.25776367187504], + [31.50878906250003, 46.373144531250006], + [32.01220703124997, 46.20390624999999] + ] + ], + [ + [ + [38.21435546875003, 47.091455078124966], + [37.54335937499999, 47.07456054687498], + [36.794824218749994, 46.71440429687499], + [36.55878906250004, 46.76269531250006], + [35.82714843749997, 46.62431640625002], + [35.01455078125005, 46.10600585937502], + [35.280175781249994, 46.27949218750001], + [35.23037109375005, 46.440625], + [34.84960937500003, 46.189892578124955], + [35.02285156250005, 45.70097656250002], + [35.45751953124997, 45.316308593749994], + [36.170507812500006, 45.453076171874955], + [36.575, 45.3935546875], + [36.39335937500002, 45.06538085937501], + [35.87011718750003, 45.005322265624955], + [35.472558593749994, 45.098486328125006], + [35.08769531250002, 44.802636718749966], + [34.46992187500004, 44.7216796875], + [33.909960937500074, 44.387597656249966], + [33.45068359374997, 44.553662109374955], + [33.55517578125003, 45.09765625000003], + [32.5080078125001, 45.40380859375006], + [33.664843750000074, 45.94707031249996], + [33.59414062500005, 46.09624023437499], + [33.42988281250004, 46.05761718750003], + [33.20224609375006, 46.17573242187501], + [32.47675781250004, 46.08369140625001], + [31.83125, 46.28168945312501], + [32.00849609375004, 46.42998046875002], + [31.554882812500097, 46.554296875000034], + [32.36132812499997, 46.474951171875034], + [32.578027343749994, 46.615625], + [32.04433593750005, 46.642480468749966], + [31.75917968750005, 47.21284179687501], + [31.872851562500017, 46.649755859375034], + [31.532128906249994, 46.66474609374998], + [31.56337890625005, 46.77729492187501], + [31.402929687500063, 46.62880859375002], + [30.796289062499994, 46.55200195312503], + [30.219042968750074, 45.866748046875045], + [29.62841796875003, 45.722460937500045], + [29.705859375000074, 45.25991210937505], + [29.567675781250074, 45.37080078124998], + [29.40371093750005, 45.419677734375], + [29.22353515625005, 45.402929687500034], + [28.894335937500017, 45.28994140625002], + [28.78173828125, 45.30986328125002], + [28.76660156250003, 45.28623046874998], + [28.78828125000001, 45.240966796875], + [28.451269531250006, 45.292187499999955], + [28.317675781250045, 45.347119140624955], + [28.2125, 45.45043945312506], + [28.26484375000004, 45.48388671875003], + [28.310351562500074, 45.49858398437499], + [28.499023437500057, 45.517724609374994], + [28.513769531250034, 45.57241210937502], + [28.49160156250005, 45.66577148437503], + [28.562304687500074, 45.73579101562501], + [28.667578125, 45.79384765625002], + [28.729296875000074, 45.852001953124955], + [28.73876953125003, 45.937158203124994], + [28.84951171875005, 45.97866210937502], + [28.94775390624997, 46.049951171874966], + [28.971875, 46.12763671874998], + [29.00625, 46.17646484374998], + [28.94375, 46.28842773437506], + [28.930566406250023, 46.36225585937501], + [28.92744140625001, 46.42412109374999], + [28.958398437500023, 46.45849609374997], + [29.146289062500017, 46.52690429687496], + [29.186230468750068, 46.52397460937499], + [29.20078125, 46.504980468750034], + [29.20458984374997, 46.37934570312501], + [29.223828125000097, 46.37695312499997], + [29.458789062500017, 46.453759765624994], + [29.83789062499997, 46.35053710937501], + [29.878027343750063, 46.360205078125034], + [30.07568359375003, 46.377832031249966], + [30.131054687500097, 46.42309570312506], + [29.92431640624997, 46.53886718750002], + [29.934765625000097, 46.625], + [29.942480468750063, 46.72377929687502], + [29.918066406250063, 46.78242187499998], + [29.877832031249994, 46.828906250000045], + [29.57197265625004, 46.96401367187502], + [29.455664062500006, 47.292626953124994], + [29.134863281250006, 47.48969726562501], + [29.125390625000023, 47.96455078125001], + [28.42304687500001, 48.146875], + [28.34052734375001, 48.144433593749994], + [27.54921875000008, 48.47773437500004], + [27.22851562500003, 48.37143554687506], + [26.90058593750001, 48.37192382812506], + [26.847070312500023, 48.387158203124955], + [26.640429687500045, 48.29414062500001], + [26.618945312500017, 48.25986328125006], + [26.4423828125, 48.22998046875], + [26.162695312500063, 47.992529296875034], + [25.90869140625, 47.96757812500002], + [25.689257812500045, 47.93247070312506], + [25.46425781250005, 47.910791015624994], + [24.979101562500063, 47.72412109374997], + [24.578906250000074, 47.93105468750005], + [23.628710937500017, 47.995849609375], + [23.40820312500003, 47.98999023437506], + [23.20263671875, 48.084521484375045], + [23.13945312499999, 48.08740234375], + [22.87666015625001, 47.94726562500006], + [22.769140625000063, 48.109619140625], + [22.582421875000023, 48.134033203125], + [22.253710937500017, 48.407373046874994], + [22.131835937500057, 48.40532226562502], + [22.142871093750017, 48.568505859374966], + [22.295214843750045, 48.68583984374999], + [22.389453125000045, 48.87348632812501], + [22.52412109375004, 49.03139648437502], + [22.538671875, 49.07270507812501], + [22.847070312500023, 49.08125], + [22.705664062500006, 49.17119140624999], + [22.6494140625, 49.53901367187498], + [22.706152343750006, 49.60620117187497], + [23.03632812500004, 49.899072265624966], + [23.711718750000045, 50.377343749999966], + [23.97265625, 50.410058593749966], + [24.089941406250006, 50.53046874999998], + [24.0947265625, 50.617041015625034], + [23.9970703125, 50.809375], + [24.095800781250063, 50.87275390625001], + [23.664453125000023, 51.31005859375], + [23.61376953125, 51.525390625], + [23.706835937500045, 51.64130859374998], + [23.79169921875001, 51.63710937500002], + [23.864257812500057, 51.62397460937501], + [23.951171875, 51.58505859374998], + [23.978320312500017, 51.59130859375003], + [24.12685546875008, 51.664648437500034], + [24.280078125000017, 51.77470703124999], + [24.361914062500006, 51.86752929687498], + [25.785742187500006, 51.923828125], + [26.77343750000003, 51.77070312499998], + [26.952832031249983, 51.754003906250034], + [27.074121093750023, 51.760839843750006], + [27.14199218750008, 51.75205078124998], + [27.29628906250008, 51.59741210937503], + [27.689746093750017, 51.572412109374994], + [27.7, 51.47797851562501], + [27.85859375000004, 51.59238281250006], + [28.532031250000017, 51.56245117187501], + [28.59902343750008, 51.54262695312505], + [28.647753906250074, 51.45654296875], + [28.690234375000017, 51.43886718750005], + [28.73125, 51.43339843749999], + [28.84951171875005, 51.540185546874994], + [28.927539062500045, 51.56215820312502], + [28.97773437500004, 51.57177734375003], + [29.01308593750005, 51.59892578124996], + [29.06074218750001, 51.625439453124955], + [29.102050781250057, 51.627539062500034], + [29.346484375000017, 51.38256835937503], + [30.160742187500006, 51.477880859375006], + [30.449511718750017, 51.274316406249994], + [30.63251953125004, 51.35541992187501], + [30.61171875000005, 51.406347656250006], + [30.602343750000017, 51.47124023437499], + [30.56074218750004, 51.531494140625], + [30.533007812500017, 51.596337890624966], + [30.583886718749994, 51.68896484375003], + [30.667285156250017, 51.81411132812502], + [30.755273437499994, 51.89516601562502], + [30.84570312500003, 51.95307617187501], + [30.980664062500097, 52.04619140624996], + [31.217968750000097, 52.05024414062498], + [31.345996093750074, 52.10537109375002], + [31.57373046875003, 52.108105468749955], + [31.763378906250097, 52.10107421875003], + [32.12226562500004, 52.05058593749996], + [32.435449218749994, 52.307226562500034], + [33.735253906249994, 52.344775390625045], + [34.397851562499994, 51.780419921874994], + [34.12109375000003, 51.67915039062498], + [34.21386718750003, 51.25537109375006], + [35.0640625, 51.203417968750045], + [35.31191406250005, 51.043896484374955], + [35.59111328125002, 50.36875], + [36.1164062500001, 50.408544921875006], + [36.619433593750074, 50.209228515625], + [37.42285156249997, 50.411474609375006], + [38.046875, 49.92001953125006], + [38.258593750000074, 50.05234375], + [38.91835937499999, 49.82470703125], + [39.17480468750003, 49.85595703124997], + [39.780566406250074, 49.57202148437503], + [40.080664062500006, 49.576855468749955], + [40.10878906250005, 49.251562500000034], + [39.68652343749997, 49.007910156250034], + [40.00361328125004, 48.82207031250002], + [39.792871093749994, 48.807714843750034], + [39.6447265625001, 48.591210937499966], + [39.8356445312501, 48.54277343749996], + [39.95791015625005, 48.268896484375034], + [39.77871093750005, 47.88754882812506], + [38.90029296875005, 47.85512695312502], + [38.36884765625004, 47.609960937500006], + [38.21435546875003, 47.091455078124966] + ] + ] + ] + }, + "properties": { "name": "Ukraine", "childNum": 2 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [-57.81059570312499, -30.85859375000001], + [-57.872509765625, -30.59101562500001], + [-57.831201171874994, -30.495214843750006], + [-57.71269531249999, -30.38447265625001], + [-57.65087890625, -30.295019531250006], + [-57.645751953125, -30.226953125], + [-57.60888671875, -30.187792968750003], + [-57.55229492187499, -30.26123046875], + [-57.21445312499999, -30.28339843750001], + [-57.186914062499994, -30.26484375000001], + [-57.120507812499994, -30.14443359375001], + [-56.83271484375, -30.107226562500003], + [-56.4072265625, -30.44746093750001], + [-55.998974609375, -30.837207031250003], + [-56.018457031249994, -30.99189453125001], + [-56.00468749999999, -31.079199218750006], + [-55.873681640624994, -31.069628906250003], + [-55.6271484375, -30.85810546875001], + [-55.60302734375, -30.85078125000001], + [-55.55732421875, -30.8759765625], + [-55.17353515625, -31.279589843750003], + [-55.09116210937499, -31.31396484375], + [-55.036035156249994, -31.27900390625001], + [-54.587646484375, -31.48515625], + [-54.22055664062499, -31.85517578125001], + [-53.76171875, -32.05683593750001], + [-53.601708984374994, -32.40302734375001], + [-53.12558593749999, -32.73671875], + [-53.2140625, -32.82109375], + [-53.31010742187499, -32.92705078125], + [-53.39521484375, -33.010351562500006], + [-53.482861328125, -33.06855468750001], + [-53.511865234374994, -33.10869140625], + [-53.53134765624999, -33.1708984375], + [-53.53134765624999, -33.65546875000001], + [-53.37060546875, -33.7421875], + [-53.419580078124994, -33.77919921875001], + [-53.47246093749999, -33.84931640625001], + [-53.53452148437499, -34.01748046875001], + [-53.742919921875, -34.24951171875], + [-53.785302734374994, -34.38037109375], + [-54.16855468749999, -34.670703125], + [-54.902294921875, -34.93281250000001], + [-55.67314453124999, -34.77568359375], + [-56.249951171875, -34.90126953125001], + [-57.17070312499999, -34.45234375000001], + [-57.8291015625, -34.47734375], + [-58.40019531249999, -33.91240234375], + [-58.363525390625, -33.18232421875001], + [-58.08232421874999, -32.893652343750006], + [-58.12958984375, -32.75722656250001], + [-58.16220703124999, -32.566503906250006], + [-58.201171875, -32.4716796875], + [-58.123046875, -32.321875], + [-58.11972656249999, -32.24892578125001], + [-58.164794921875, -32.18486328125], + [-58.177001953125, -32.11904296875001], + [-58.15634765624999, -32.0515625], + [-58.160400390625, -31.98652343750001], + [-58.18901367187499, -31.92421875], + [-58.16748046875, -31.87265625], + [-58.04233398437499, -31.76923828125001], + [-58.006982421874994, -31.68496093750001], + [-58.053857421874994, -31.494921875], + [-58.0333984375, -31.416601562500006], + [-57.89335937499999, -31.1953125], + [-57.868408203125, -31.10439453125001], + [-57.88632812499999, -30.93740234375001], + [-57.81059570312499, -30.85859375000001] + ] + ] + }, + "properties": { "name": "Uruguay", "childNum": 1 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [-155.58134765624996, 19.012011718750017], + [-155.88129882812495, 19.07050781250001], + [-156.04868164062498, 19.749951171874983], + [-155.82031249999997, 20.01416015624997], + [-155.83164062499998, 20.27583007812501], + [-155.198779296875, 19.99438476562503], + [-154.80419921875, 19.524462890625045], + [-155.58134765624996, 19.012011718750017] + ] + ], + [ + [ + [-156.84960937499997, 20.772656249999955], + [-156.97338867187497, 20.757519531249983], + [-157.0505859375, 20.912451171875034], + [-156.88056640624995, 20.904833984375074], + [-156.84960937499997, 20.772656249999955] + ] + ], + [ + [ + [-156.48681640624994, 20.93256835937504], + [-156.27753906250004, 20.951269531250034], + [-155.98984374999998, 20.75712890624999], + [-156.40878906249998, 20.60517578125004], + [-156.480078125, 20.80122070312501], + [-156.69775390625003, 20.949072265625034], + [-156.58540039062495, 21.034326171874994], + [-156.48681640624994, 20.93256835937504] + ] + ], + [ + [ + [-157.21362304687497, 21.215380859375017], + [-156.71215820312506, 21.155078125000074], + [-156.85986328125, 21.05634765625004], + [-157.29033203124996, 21.112597656250017], + [-157.21362304687497, 21.215380859375017] + ] + ], + [ + [ + [-157.79936523437502, 21.456640625000034], + [-157.63540039062502, 21.30761718749997], + [-158.11035156249994, 21.318603515625], + [-158.27314453125, 21.585253906250045], + [-157.9625, 21.701367187499983], + [-157.79936523437502, 21.456640625000034] + ] + ], + [ + [ + [-159.37275390625, 21.93237304687497], + [-159.60883789062495, 21.909521484375034], + [-159.78916015625003, 22.041796875000074], + [-159.57919921874998, 22.22314453124997], + [-159.35205078124997, 22.219580078125034], + [-159.37275390625, 21.93237304687497] + ] + ], + [ + [ + [-81.04418945312503, 24.716796875000057], + [-81.137353515625, 24.710498046875017], + [-80.93046875, 24.75947265625004], + [-81.04418945312503, 24.716796875000057] + ] + ], + [ + [ + [-80.3818359375, 25.142285156249955], + [-80.58056640624997, 24.954248046875023], + [-80.25708007812497, 25.34760742187504], + [-80.3818359375, 25.142285156249955] + ] + ], + [ + [ + [-97.17070312499996, 26.159375], + [-97.40209960937494, 26.820507812499983], + [-97.38598632812494, 27.19648437500004], + [-97.17070312499996, 26.159375] + ] + ], + [ + [ + [-80.18676757812497, 27.278417968750034], + [-80.17050781250003, 27.20478515625004], + [-80.43691406249994, 27.850537109374955], + [-80.18676757812497, 27.278417968750034] + ] + ], + [ + [ + [-91.793701171875, 29.50073242187497], + [-92.00664062499996, 29.61030273437501], + [-91.875244140625, 29.640966796875034], + [-91.793701171875, 29.50073242187497] + ] + ], + [ + [ + [-84.90791015624998, 29.642626953125017], + [-85.11674804687499, 29.63281249999997], + [-84.737158203125, 29.732421875], + [-84.90791015624998, 29.642626953125017] + ] + ], + [ + [ + [-89.22397460937498, 30.084082031249977], + [-89.34199218749995, 30.062841796875006], + [-89.18466796874995, 30.168652343749983], + [-89.22397460937498, 30.084082031249977] + ] + ], + [ + [ + [-118.34794921875002, 33.3857421875], + [-118.29746093750003, 33.312109375], + [-118.44628906249997, 33.317089843749955], + [-118.56943359375002, 33.46416015624999], + [-118.34794921875002, 33.3857421875] + ] + ], + [ + [ + [-120.04355468749995, 33.918847656249994], + [-120.25190429687494, 34.01386718749998], + [-120.07182617187493, 34.026513671874966], + [-120.04355468749995, 33.918847656249994] + ] + ], + [ + [ + [-119.88237304687497, 34.07968749999998], + [-119.54926757812497, 34.02817382812506], + [-119.80957031249997, 33.9677734375], + [-119.88237304687497, 34.07968749999998] + ] + ], + [ + [ + [-75.54414062499995, 35.240087890625034], + [-75.69008789062502, 35.221582031249994], + [-75.53637695312497, 35.27861328124999], + [-75.50351562500003, 35.769140625], + [-75.46474609374994, 35.448632812499966], + [-75.54414062499995, 35.240087890625034] + ] + ], + [ + [ + [-74.13320312500002, 39.680761718750034], + [-74.25048828125, 39.529394531250006], + [-74.10673828124996, 39.74643554687498], + [-74.13320312500002, 39.680761718750034] + ] + ], + [ + [ + [-72.50976562500003, 40.98603515625001], + [-72.58085937499996, 40.92133789062498], + [-71.90322265625, 41.06069335937505], + [-73.19428710937495, 40.654199218749994], + [-74.01489257812497, 40.581201171874966], + [-73.87924804687498, 40.79165039062502], + [-73.573828125, 40.91962890624998], + [-72.62509765624998, 40.99184570312505], + [-72.27412109374998, 41.15302734375001], + [-72.50976562500003, 40.98603515625001] + ] + ], + [ + [ + [-69.9779296875, 41.26557617187504], + [-70.23305664062502, 41.28632812500001], + [-70.04121093750001, 41.3974609375], + [-69.9779296875, 41.26557617187504] + ] + ], + [ + [ + [-70.50991210937502, 41.376318359375034], + [-70.82919921874995, 41.35898437500006], + [-70.61601562499996, 41.45722656250001], + [-70.50991210937502, 41.376318359375034] + ] + ], + [ + [ + [-71.24140625000001, 41.49194335937497], + [-71.34624023437496, 41.469384765624994], + [-71.23203124999995, 41.654296875], + [-71.24140625000001, 41.49194335937497] + ] + ], + [ + [ + [-68.18725585937497, 44.33247070312501], + [-68.41171875000003, 44.294335937499966], + [-68.29941406249998, 44.456494140624955], + [-68.18725585937497, 44.33247070312501] + ] + ], + [ + [ + [-122.394140625, 47.39526367187503], + [-122.50991210937497, 47.358007812500006], + [-122.486474609375, 47.48876953125], + [-122.394140625, 47.39526367187503] + ] + ], + [ + [ + [-122.57275390624999, 48.15664062499999], + [-122.38315429687499, 47.923193359375034], + [-122.74150390624999, 48.22529296875004], + [-122.62861328125, 48.38422851562498], + [-122.54243164062503, 48.29399414062499], + [-122.69702148437499, 48.228662109374994], + [-122.57275390624999, 48.15664062499999] + ] + ], + [ + [ + [-94.80346679687497, 49.0029296875], + [-94.71279296874997, 48.863427734374994], + [-94.62089843749999, 48.74262695312501], + [-93.85161132812496, 48.607275390625034], + [-93.70771484374995, 48.52543945312499], + [-93.37788085937498, 48.61655273437498], + [-93.25795898437497, 48.62885742187501], + [-92.83671875, 48.567773437499994], + [-92.50058593749995, 48.43535156250002], + [-92.41459960937493, 48.276611328125], + [-92.3484375, 48.276611328125], + [-92.00517578125002, 48.301855468750006], + [-91.38720703124997, 48.05854492187498], + [-91.04345703125003, 48.19370117187498], + [-90.84033203125003, 48.20053710937506], + [-90.79731445312495, 48.13105468750001], + [-89.4556640625, 47.996240234374994], + [-88.37817382812497, 48.30307617187498], + [-87.74389648437497, 48.06054687500003], + [-87.20800781249997, 47.848486328125006], + [-86.67216796874996, 47.636425781249955], + [-85.65224609375, 47.21997070312503], + [-85.07006835937497, 46.97993164062498], + [-84.87597656249994, 46.89990234375003], + [-84.66577148437503, 46.54326171875002], + [-84.44047851562496, 46.49814453125006], + [-84.12319335937497, 46.50292968749997], + [-83.97778320312503, 46.08491210937498], + [-83.61596679687503, 46.116845703124994], + [-83.46948242187503, 45.99467773437499], + [-83.59267578125, 45.81713867187506], + [-82.91933593749994, 45.51796875000002], + [-82.55107421874996, 45.34736328125001], + [-82.48505859374993, 45.08374023437503], + [-82.137841796875, 43.570898437500034], + [-82.19038085937495, 43.47407226562501], + [-82.54531249999997, 42.62470703124998], + [-83.10952148437497, 42.25068359375001], + [-83.141943359375, 41.97587890624996], + [-82.69003906249995, 41.675195312499994], + [-82.43906249999998, 41.67485351562502], + [-81.97416992187496, 41.88872070312499], + [-81.50732421874997, 42.10346679687504], + [-81.02822265624997, 42.247167968750006], + [-80.24755859375, 42.36601562499996], + [-79.17373046875, 42.74853515625], + [-78.91508789062496, 42.90913085937504], + [-78.98076171874993, 42.98061523437502], + [-79.02617187499996, 43.01733398437506], + [-79.066064453125, 43.10610351562502], + [-79.171875, 43.466552734375], + [-79.00249023437502, 43.52714843749999], + [-78.845556640625, 43.58334960937498], + [-78.72041015625001, 43.62495117187501], + [-78.45825195312497, 43.63149414062502], + [-77.596533203125, 43.62861328124998], + [-76.819970703125, 43.62880859375002], + [-76.18579101562503, 44.24223632812502], + [-75.81933593749997, 44.468017578125], + [-75.40126953124997, 44.77226562499999], + [-74.99614257812496, 44.970117187499966], + [-74.76245117187494, 44.99907226562502], + [-74.663232421875, 45.00390625000003], + [-71.51752929687495, 45.00756835937497], + [-71.327294921875, 45.29008789062496], + [-70.86503906249999, 45.27070312500001], + [-70.296240234375, 45.90610351562506], + [-70.00771484375002, 46.70893554687501], + [-69.24287109374998, 47.46298828124998], + [-69.0501953125, 47.426611328125034], + [-68.93720703124998, 47.21123046875002], + [-68.23549804687502, 47.34594726562503], + [-67.806787109375, 47.08281249999999], + [-67.80224609374994, 45.7275390625], + [-67.43266601562496, 45.603125], + [-67.366943359375, 45.17377929687498], + [-67.12485351562498, 45.16943359375], + [-66.98701171874995, 44.82768554687502], + [-67.191259765625, 44.67558593750002], + [-67.83906249999998, 44.576269531250034], + [-68.056640625, 44.38432617187502], + [-68.15205078124998, 44.50200195312499], + [-68.45058593749997, 44.50761718749999], + [-68.53251953124996, 44.25864257812498], + [-68.81191406249994, 44.33935546875], + [-68.76269531249994, 44.57075195312498], + [-69.22607421875003, 43.98647460937505], + [-69.52075195312503, 43.89736328125002], + [-69.55668945312496, 43.982763671875006], + [-69.62392578125, 43.88061523437497], + [-69.65288085937493, 43.99389648437506], + [-69.808349609375, 43.772314453125034], + [-69.965234375, 43.855078125], + [-70.17880859374998, 43.76635742187506], + [-70.73310546875001, 43.07001953125004], + [-70.82905273437493, 42.82534179687502], + [-70.61293945312497, 42.623242187499955], + [-71.04619140624993, 42.331103515625045], + [-70.73828125, 42.228857421875006], + [-70.42666015625002, 41.75727539062501], + [-70.00141601562498, 41.82617187500003], + [-70.24106445312495, 42.09121093750002], + [-70.10893554687496, 42.07832031249998], + [-69.97788085937498, 41.961279296875006], + [-69.94863281249997, 41.67714843750005], + [-70.65712890625, 41.53422851562496], + [-70.70112304687498, 41.71484375], + [-71.1685546875, 41.489404296874994], + [-71.14873046874996, 41.74570312499998], + [-71.27109375, 41.68125], + [-71.39013671875003, 41.79531250000005], + [-71.52285156249997, 41.378955078125045], + [-72.92470703125002, 41.28515625000003], + [-73.98710937499999, 40.751367187499994], + [-73.87197265625, 41.05517578124997], + [-73.96992187499995, 41.24970703125001], + [-73.92719726562495, 40.914257812499955], + [-74.26420898437496, 40.52861328124999], + [-73.972265625, 40.40034179687498], + [-74.079931640625, 39.78813476562496], + [-74.06459960937497, 39.99311523437498], + [-74.79448242187499, 39.00190429687501], + [-74.95429687499995, 38.949951171875], + [-74.89702148437502, 39.14545898437504], + [-75.52421874999999, 39.49018554687501], + [-75.421875, 39.78969726562502], + [-75.07416992187495, 39.98349609375006], + [-75.40063476562503, 39.83159179687502], + [-75.58759765625001, 39.64077148437505], + [-75.3921875, 39.09277343750006], + [-75.08867187499999, 38.777539062499955], + [-75.18710937499995, 38.59111328124999], + [-75.03876953124993, 38.426367187500006], + [-75.934375, 37.15190429687496], + [-75.97504882812498, 37.3984375], + [-75.65927734374995, 37.953955078125034], + [-75.850830078125, 37.971582031249994], + [-75.85869140624999, 38.36206054687503], + [-76.05122070312495, 38.27954101562503], + [-76.2646484375, 38.436425781249994], + [-76.26416015625, 38.599951171875006], + [-76.016943359375, 38.62509765624998], + [-76.21298828124998, 38.75830078125003], + [-76.34116210937498, 38.70966796874998], + [-76.16816406249998, 38.85273437499998], + [-76.32958984375, 38.95278320312505], + [-76.13520507812493, 39.082128906250006], + [-76.23569335937498, 39.19160156250001], + [-76.153125, 39.315039062500034], + [-75.87597656249997, 39.3759765625], + [-76.003125, 39.41083984375001], + [-75.87294921874997, 39.510888671874966], + [-75.95893554687498, 39.58505859374998], + [-76.2763671875, 39.32275390625], + [-76.330810546875, 39.40390625], + [-76.42089843749997, 39.225], + [-76.57041015624995, 39.26933593749996], + [-76.42758789062498, 39.12602539062499], + [-76.55854492187493, 39.065234375000045], + [-76.39409179687502, 38.368994140625034], + [-76.67734374999998, 38.611962890624966], + [-76.66855468749998, 38.5375], + [-76.34116210937498, 38.08701171875006], + [-76.86811523437495, 38.39028320312502], + [-76.88974609375, 38.292089843750006], + [-77.00117187499995, 38.44526367187504], + [-77.23251953125, 38.40771484375003], + [-77.03037109374995, 38.88925781249998], + [-77.26040039062502, 38.6], + [-77.27324218749996, 38.35175781249998], + [-77.04677734375002, 38.356689453125], + [-76.26425781250003, 37.89355468749997], + [-76.34414062499997, 37.675683593749994], + [-76.49248046874999, 37.682226562500006], + [-77.11108398437497, 38.165673828124994], + [-76.54946289062494, 37.66914062500001], + [-76.30556640625, 37.57148437500001], + [-76.26347656249996, 37.35703125], + [-76.40097656249998, 37.386132812499994], + [-76.45390624999993, 37.27353515625006], + [-76.75771484375002, 37.50541992187496], + [-76.28330078125, 37.05268554687501], + [-76.40087890624997, 36.991308593750034], + [-76.63090820312493, 37.22172851562499], + [-77.25087890624994, 37.329199218750034], + [-76.671875, 37.172949218750006], + [-76.48784179687502, 36.89702148437499], + [-75.99941406249997, 36.91264648437499], + [-75.53417968749997, 35.81909179687506], + [-75.94648437499995, 36.65908203125002], + [-75.99277343749995, 36.47377929687502], + [-75.82006835937494, 36.11284179687502], + [-76.14785156250002, 36.279296875], + [-76.15, 36.14575195312497], + [-76.27060546874998, 36.18989257812501], + [-76.22739257812498, 36.11601562499996], + [-76.559375, 36.015332031249955], + [-76.733642578125, 36.229150390624994], + [-76.726220703125, 35.957617187500034], + [-76.06977539062501, 35.970312500000034], + [-76.08359374999998, 35.69052734375006], + [-75.85390625, 35.96015625000001], + [-75.75883789062499, 35.84326171875], + [-75.77392578124997, 35.64697265624997], + [-76.17382812499997, 35.354150390624994], + [-76.489501953125, 35.397021484375045], + [-76.57719726562502, 35.53232421874998], + [-76.74140624999998, 35.431494140625034], + [-77.03999023437495, 35.527392578125045], + [-76.51293945312497, 35.270410156249994], + [-76.77915039062503, 34.990332031250034], + [-77.07026367187501, 35.154638671875034], + [-76.97495117187503, 35.025195312500045], + [-76.74497070312498, 34.94096679687502], + [-76.45673828124998, 34.989355468750034], + [-76.36220703125, 34.9365234375], + [-76.43979492187498, 34.84291992187502], + [-77.29624023437503, 34.602929687499994], + [-77.41225585937497, 34.730810546875034], + [-77.37978515625, 34.526611328125], + [-77.750732421875, 34.28496093749996], + [-77.92783203125, 33.93974609374999], + [-77.95327148437494, 34.16899414062496], + [-78.01333007812502, 33.91181640624998], + [-78.40585937499995, 33.91757812499998], + [-78.84145507812497, 33.72407226562501], + [-79.19379882812498, 33.24414062500003], + [-79.22646484375, 33.40488281249998], + [-79.27602539062497, 33.135400390624966], + [-79.80498046874999, 32.78740234374996], + [-79.93310546874997, 32.81005859375006], + [-79.94072265625002, 32.667138671874966], + [-80.36284179687496, 32.500732421875], + [-80.6341796875, 32.51171875000003], + [-80.474267578125, 32.42275390625002], + [-80.579345703125, 32.28730468750004], + [-80.80253906249999, 32.44804687500002], + [-80.69423828124997, 32.21572265625002], + [-81.11328124999997, 31.87861328125001], + [-81.06611328124995, 31.787988281250023], + [-81.259375, 31.538916015624977], + [-81.17543945312494, 31.531298828125017], + [-81.38095703124998, 31.353271484375], + [-81.28847656249997, 31.263916015625], + [-81.441748046875, 31.19970703124997], + [-81.5162109375, 30.801806640625017], + [-81.24951171875003, 29.793798828125006], + [-80.52412109374995, 28.48608398437503], + [-80.5849609375, 28.271582031250034], + [-80.456884765625, 27.90068359374996], + [-80.61000976562494, 28.177587890624977], + [-80.60693359375003, 28.522900390624983], + [-80.693505859375, 28.34497070312497], + [-80.68847656250003, 28.578515625000023], + [-80.83818359374999, 28.757666015625034], + [-80.74863281250003, 28.381005859375023], + [-80.050048828125, 26.807714843750063], + [-80.1263671875, 25.83349609375], + [-80.48466796874999, 25.229833984375034], + [-81.11049804687494, 25.138037109374977], + [-81.13603515624999, 25.309667968750034], + [-80.94042968750003, 25.264208984375017], + [-81.11333007812499, 25.367236328125045], + [-81.36494140625001, 25.83105468750003], + [-81.715478515625, 25.98315429687503], + [-81.95893554687495, 26.489941406249983], + [-81.82866210937496, 26.68706054687499], + [-82.03959960937496, 26.552050781250017], + [-82.01328125, 26.96157226562505], + [-82.24287109374998, 26.848876953125], + [-82.44135742187501, 27.059667968750034], + [-82.71459960937497, 27.499609375000063], + [-82.40576171874994, 27.862890624999977], + [-82.67519531249994, 27.963769531250023], + [-82.61098632812502, 27.77724609375005], + [-82.74287109374995, 27.709375], + [-82.84350585937494, 27.845996093750017], + [-82.65146484375, 28.8875], + [-83.69438476562502, 29.92597656250001], + [-84.04423828124996, 30.10380859374999], + [-84.30966796874995, 30.064746093750045], + [-84.38281250000003, 29.90737304687505], + [-85.31894531249995, 29.680224609375045], + [-85.413818359375, 29.76757812499997], + [-85.413818359375, 29.842480468749955], + [-85.31489257812493, 29.758105468750017], + [-85.35361328125, 29.875732421875], + [-85.67578125, 30.121923828125063], + [-85.60351562500003, 30.286767578124966], + [-85.75581054687495, 30.1669921875], + [-86.454443359375, 30.39912109375004], + [-86.12382812499999, 30.40581054687499], + [-86.25737304687502, 30.493017578124977], + [-87.201171875, 30.339257812499994], + [-86.98579101562498, 30.43085937500001], + [-86.99755859375, 30.5703125], + [-87.17060546874998, 30.538769531249983], + [-87.28105468750002, 30.339257812499994], + [-87.47578124999998, 30.294287109375006], + [-87.44829101562499, 30.394140625], + [-87.62226562499998, 30.264746093750006], + [-88.00595703124998, 30.230908203124955], + [-87.79028320312503, 30.291796875000017], + [-88.011328125, 30.694189453125006], + [-88.13544921874998, 30.366601562499994], + [-88.90522460937495, 30.415136718750006], + [-89.32055664062503, 30.3453125], + [-89.58847656249998, 30.165966796874955], + [-90.12597656249997, 30.369091796874955], + [-90.33198242187493, 30.277587890625057], + [-90.41303710937501, 30.140332031249983], + [-90.17534179687499, 30.02910156249996], + [-89.73745117187497, 30.171972656250034], + [-89.66503906249994, 30.117041015625034], + [-89.81518554687497, 30.007275390624955], + [-89.631689453125, 29.90380859375003], + [-89.400732421875, 30.04604492187505], + [-89.35444335937501, 29.82021484375005], + [-89.72089843749995, 29.619287109374966], + [-89.01572265625, 29.202880859375057], + [-89.15551757812497, 29.01660156250003], + [-89.23608398437494, 29.081103515625017], + [-89.37612304687497, 28.981347656250023], + [-89.44316406249996, 29.194140625000045], + [-90.15908203124997, 29.537158203125017], + [-90.05278320312499, 29.336816406249966], + [-90.21279296875, 29.104931640624983], + [-90.37919921874996, 29.29511718750001], + [-90.75102539062496, 29.13085937500003], + [-91.29013671875, 29.288964843749994], + [-91.15078124999994, 29.317919921875045], + [-91.24882812499993, 29.56420898437503], + [-91.51420898437499, 29.55537109375001], + [-91.8931640625, 29.836035156249977], + [-92.135498046875, 29.699462890625057], + [-92.08403320312499, 29.59282226562499], + [-92.26083984374995, 29.55683593750004], + [-93.17568359375, 29.778955078124994], + [-93.82646484374999, 29.725146484375045], + [-93.84145507812502, 29.97973632812503], + [-93.89047851562495, 29.689355468750023], + [-94.759619140625, 29.384277343750057], + [-94.52626953125, 29.547949218750006], + [-94.77827148437498, 29.54785156249997], + [-94.74194335937497, 29.75], + [-95.0228515625, 29.70234375000001], + [-94.88828125000003, 29.37055664062501], + [-95.27348632812499, 28.96386718750003], + [-96.23452148437502, 28.488964843749983], + [-96.01103515624996, 28.631933593749977], + [-96.44873046874997, 28.594482421875], + [-96.64003906249994, 28.708789062500017], + [-96.42109374999993, 28.457324218750045], + [-96.67636718749998, 28.34130859375003], + [-96.77353515624998, 28.421630859375057], + [-96.839501953125, 28.194384765625017], + [-97.156494140625, 28.144335937500045], + [-97.141259765625, 28.060742187499983], + [-97.034326171875, 28.093847656250063], + [-97.07309570312498, 27.98608398437503], + [-97.43149414062498, 27.83720703124999], + [-97.28872070312494, 27.670605468749983], + [-97.43911132812502, 27.328271484374966], + [-97.76845703124997, 27.45751953125], + [-97.69238281250003, 27.287158203125017], + [-97.48510742187497, 27.237402343750006], + [-97.55468749999994, 26.96733398437496], + [-97.43505859375, 26.48583984375003], + [-97.14624023437494, 25.961474609375045], + [-97.37563476562497, 25.871826171875], + [-99.10776367187498, 26.446923828124994], + [-99.45654296874999, 27.05668945312496], + [-99.50532226562497, 27.54833984375003], + [-100.29604492187495, 28.32768554687499], + [-100.75458984375001, 29.182519531249994], + [-101.44038085937503, 29.77685546875], + [-102.26894531249998, 29.871191406250034], + [-102.61494140624994, 29.75234375], + [-102.8919921875, 29.216406250000034], + [-103.16831054687498, 28.998193359374994], + [-104.110595703125, 29.386132812499994], + [-104.50400390624995, 29.677685546874955], + [-104.97880859374996, 30.645947265624955], + [-106.14804687499995, 31.450927734375], + [-106.44541015624996, 31.768408203125006], + [-108.21181640625002, 31.779345703125017], + [-108.21445312499993, 31.329443359375034], + [-111.0419921875, 31.32421875000003], + [-114.83593749999994, 32.50830078125003], + [-114.72475585937495, 32.71533203125003], + [-117.12827148437495, 32.533349609374994], + [-117.46743164062495, 33.295507812500006], + [-118.08051757812497, 33.72216796874997], + [-118.41044921874996, 33.74394531249996], + [-118.506201171875, 34.01738281249999], + [-119.14375, 34.11201171874998], + [-119.60605468749999, 34.41801757812499], + [-120.48120117187503, 34.47163085937498], + [-120.64467773437502, 34.57998046875002], + [-120.65908203124994, 35.122412109375034], + [-120.85737304687501, 35.209667968749955], + [-120.899609375, 35.42509765624999], + [-121.28383789062494, 35.67631835937499], + [-121.87739257812498, 36.33105468749997], + [-121.80742187499995, 36.851220703124994], + [-122.394921875, 37.20751953125003], + [-122.49921875000001, 37.542626953124994], + [-122.44560546875002, 37.797998046874966], + [-122.07050781249998, 37.47827148437503], + [-122.38544921875001, 37.960595703124966], + [-122.31425781249999, 38.00732421874997], + [-121.52534179687503, 38.05590820312503], + [-122.39335937499995, 38.14482421875002], + [-122.52133789062499, 37.82641601562497], + [-122.93198242187498, 38.05546875000002], + [-122.998779296875, 37.98862304687498], + [-122.90815429687501, 38.19658203124999], + [-123.701123046875, 38.90727539062502], + [-123.83291015624994, 39.775488281250034], + [-124.35654296875003, 40.37109374999997], + [-124.07192382812497, 41.45952148437502], + [-124.53964843750003, 42.812890624999966], + [-124.14873046874997, 43.691748046875034], + [-123.92934570312495, 45.57695312499996], + [-123.989306640625, 46.21938476562502], + [-123.22060546874998, 46.153613281250045], + [-123.46484375, 46.27109374999998], + [-124.07275390624996, 46.279443359374994], + [-124.04433593750002, 46.605078125], + [-123.946142578125, 46.43256835937501], + [-123.88916015625003, 46.660009765625006], + [-124.11254882812497, 46.862695312499994], + [-123.84287109375002, 46.963183593750045], + [-124.11171875, 47.03520507812496], + [-124.1392578125, 46.95468749999998], + [-124.376025390625, 47.658642578124955], + [-124.66308593749996, 47.97412109375003], + [-124.7099609375, 48.38037109375], + [-123.97578125, 48.16845703125], + [-122.97387695312499, 48.07329101562496], + [-122.77861328125, 48.13759765625002], + [-122.65664062500002, 47.88115234374999], + [-122.77841796874996, 47.738427734374966], + [-122.82138671875, 47.79316406250001], + [-123.1390625, 47.386083984375034], + [-122.92216796874993, 47.40766601562498], + [-123.066796875, 47.39965820312506], + [-123.04863281249995, 47.479345703125034], + [-122.53281250000002, 47.919726562500045], + [-122.67548828124995, 47.612353515625045], + [-122.57788085937496, 47.29316406250001], + [-122.76777343750001, 47.21835937500006], + [-122.82846679687503, 47.336572265624994], + [-123.02758789062501, 47.13891601562503], + [-122.70195312500002, 47.11088867187502], + [-122.35380859374996, 47.37158203125], + [-122.40180664062497, 47.78427734374998], + [-122.24199218750002, 48.01074218750003], + [-122.5169921875, 48.15966796874997], + [-122.40854492187502, 48.29389648437498], + [-122.66899414062496, 48.465234374999966], + [-122.49677734374995, 48.50556640625001], + [-122.51274414062502, 48.66943359375], + [-122.56201171875001, 48.777978515624994], + [-122.68593749999995, 48.794287109375034], + [-122.72246093750002, 48.85302734375003], + [-122.78876953125003, 48.993017578125034], + [-121.40722656249994, 48.993017578125034], + [-119.70170898437495, 48.993017578125034], + [-119.27534179687494, 48.99306640625005], + [-118.84892578124993, 48.99306640625005], + [-117.99619140625002, 48.99306640625005], + [-116.71704101562501, 48.99306640625005], + [-110.74765625, 48.99306640625005], + [-104.77832031249997, 48.993115234374955], + [-98.80898437499995, 48.99316406249997], + [-97.52983398437493, 48.99316406249997], + [-96.67705078124993, 48.99316406249997], + [-96.25068359374993, 48.99316406249997], + [-95.39790039062493, 48.99316406249997], + [-95.16206054687493, 48.991748046875045], + [-95.15527343749997, 49.36967773437502], + [-94.85434570312495, 49.304589843749994], + [-94.86040039062493, 49.258593750000045], + [-94.80346679687497, 49.0029296875] + ] + ], + [ + [ + [-176.28671874999998, 51.79199218750006], + [-176.34965820312502, 51.733300781249994], + [-176.41372070312502, 51.840576171875], + [-176.28671874999998, 51.79199218750006] + ] + ], + [ + [ + [-177.87905273437502, 51.64970703125002], + [-178.05888671875, 51.67260742187497], + [-177.98637695312493, 51.76425781249998], + [-178.16826171874996, 51.90302734375001], + [-177.644482421875, 51.826269531250006], + [-177.87905273437502, 51.64970703125002] + ] + ], + [ + [ + [-177.14819335937497, 51.71674804687498], + [-177.67021484375002, 51.701074218749994], + [-177.11005859375, 51.92875976562502], + [-177.14819335937497, 51.71674804687498] + ] + ], + [ + [ + [-176.593310546875, 51.86669921875], + [-176.45234374999995, 51.735693359375034], + [-176.96162109374998, 51.60366210937505], + [-176.69833984374998, 51.986035156249955], + [-176.593310546875, 51.86669921875] + ] + ], + [ + [ + [179.72773437500015, 51.905419921874966], + [179.50390625000003, 51.97958984374998], + [179.6271484375001, 52.03041992187502], + [179.72773437500015, 51.905419921874966] + ] + ], + [ + [ + [177.4154296875, 51.88281249999997], + [177.25029296875013, 51.902929687500006], + [177.6696289062501, 52.10302734375], + [177.4154296875, 51.88281249999997] + ] + ], + [ + [ + [-173.5533203125, 52.13627929687502], + [-173.02290039062504, 52.07915039062502], + [-173.83579101562498, 52.048193359375006], + [-173.99248046874993, 52.12333984374996], + [-173.5533203125, 52.13627929687502] + ] + ], + [ + [ + [-172.464794921875, 52.27226562500002], + [-172.61982421874998, 52.27285156250005], + [-172.47041015625, 52.38803710937506], + [-172.31362304687497, 52.32958984375006], + [-172.464794921875, 52.27226562500002] + ] + ], + [ + [ + [-174.67739257812502, 52.035009765625006], + [-175.29555664062502, 52.022167968749955], + [-174.30615234375, 52.216162109375034], + [-174.43554687499997, 52.317236328125034], + [-174.168896484375, 52.42016601562503], + [-174.04560546875, 52.36723632812499], + [-174.12065429687493, 52.13520507812498], + [-174.67739257812502, 52.035009765625006] + ] + ], + [ + [ + [173.72275390625018, 52.35957031250004], + [173.40234375000009, 52.40478515625], + [173.77607421875004, 52.49511718750003], + [173.72275390625018, 52.35957031250004] + ] + ], + [ + [ + [172.81181640625002, 53.01298828125002], + [173.43603515625003, 52.85205078125], + [172.93515625000012, 52.752099609374966], + [172.49482421875004, 52.93789062499999], + [172.81181640625002, 53.01298828125002] + ] + ], + [ + [ + [-167.96435546875003, 53.345117187499994], + [-169.088916015625, 52.83203125], + [-168.68984375000002, 53.227246093749955], + [-168.38041992187496, 53.28344726562506], + [-168.28769531249998, 53.500146484374966], + [-167.82807617187495, 53.50795898437505], + [-167.96435546875003, 53.345117187499994] + ] + ], + [ + [ + [-166.61533203124998, 53.90092773437499], + [-166.37231445312494, 53.99897460937498], + [-166.230859375, 53.93261718750006], + [-166.54560546875, 53.726464843749966], + [-166.354541015625, 53.67353515625004], + [-166.85097656249997, 53.45288085937503], + [-167.78085937500003, 53.30024414062501], + [-167.13608398437503, 53.526464843750006], + [-167.01572265625003, 53.69838867187502], + [-166.80898437500002, 53.64614257812505], + [-166.741259765625, 53.71293945312496], + [-167.10561523437497, 53.813378906249994], + [-167.03808593749997, 53.9421875], + [-166.67329101562498, 54.00595703124998], + [-166.61533203124998, 53.90092773437499] + ] + ], + [ + [ + [-165.841552734375, 54.070654296875006], + [-166.05664062500003, 54.054345703124994], + [-166.08774414062498, 54.16914062500001], + [-165.89287109375, 54.20698242187498], + [-165.69287109375, 54.09990234375002], + [-165.841552734375, 54.070654296875006] + ] + ], + [ + [ + [-165.56113281249998, 54.13671874999997], + [-165.55063476562498, 54.28452148437498], + [-165.40786132812502, 54.19682617187496], + [-165.56113281249998, 54.13671874999997] + ] + ], + [ + [ + [-162.29814453124993, 54.847021484375006], + [-162.43388671875, 54.931542968749994], + [-162.26459960937504, 54.983496093750006], + [-162.29814453124993, 54.847021484375006] + ] + ], + [ + [ + [-163.476025390625, 54.98071289062497], + [-163.37895507812496, 54.81552734374998], + [-163.083251953125, 54.66899414062496], + [-163.35810546874995, 54.73569335937506], + [-164.82343749999998, 54.41909179687505], + [-164.887646484375, 54.60781250000002], + [-164.47861328124998, 54.906835937500006], + [-163.80712890624997, 55.04907226562503], + [-163.476025390625, 54.98071289062497] + ] + ], + [ + [ + [-159.51513671875, 55.15185546875003], + [-159.617724609375, 55.05732421875004], + [-159.54506835937497, 55.22597656250002], + [-159.51513671875, 55.15185546875003] + ] + ], + [ + [ + [-131.33974609375002, 55.079833984375], + [-131.32954101562498, 54.887744140625045], + [-131.592236328125, 55.02568359374999], + [-131.5654296875, 55.26411132812498], + [-131.33974609375002, 55.079833984375] + ] + ], + [ + [ + [-159.87299804687495, 55.128759765625034], + [-160.22705078124997, 54.92270507812506], + [-160.17207031249995, 55.123046875], + [-159.88735351562497, 55.27299804687502], + [-159.87299804687495, 55.128759765625034] + ] + ], + [ + [ + [-132.86225585937504, 54.894433593749966], + [-132.61723632812493, 54.892431640625006], + [-132.70581054687497, 54.684179687500034], + [-133.42905273437498, 55.30380859374998], + [-133.097412109375, 55.213720703125006], + [-132.86225585937504, 54.894433593749966] + ] + ], + [ + [ + [-160.329296875, 55.337695312500045], + [-160.34331054687493, 55.25878906250006], + [-160.51748046875, 55.33383789062506], + [-160.329296875, 55.337695312500045] + ] + ], + [ + [ + [-160.68491210937498, 55.314794921875006], + [-160.552783203125, 55.38076171875002], + [-160.48754882812503, 55.18486328124999], + [-160.79506835937497, 55.14521484375001], + [-160.72392578124993, 55.404638671875006], + [-160.68491210937498, 55.314794921875006] + ] + ], + [ + [ + [-133.30507812500002, 55.54375], + [-133.6501953125, 55.26928710937506], + [-133.73710937500002, 55.49692382812498], + [-133.30507812500002, 55.54375] + ] + ], + [ + [ + [-155.56601562500003, 55.82119140625005], + [-155.73735351562493, 55.82978515625001], + [-155.59394531250004, 55.92431640625], + [-155.56601562500003, 55.82119140625005] + ] + ], + [ + [ + [-130.97915039062502, 55.489160156249994], + [-131.187890625, 55.206298828125], + [-131.44755859374996, 55.40878906250006], + [-131.7625, 55.16582031250002], + [-131.84609374999997, 55.41625976562497], + [-131.62495117187504, 55.831689453124966], + [-131.26923828125004, 55.95537109375002], + [-130.997802734375, 55.727636718750006], + [-130.97915039062502, 55.489160156249994] + ] + ], + [ + [ + [-133.56611328125, 56.33920898437498], + [-133.202978515625, 56.31982421875003], + [-133.096630859375, 56.09003906250001], + [-132.59760742187504, 55.89501953125], + [-132.17270507812498, 55.48061523437502], + [-132.51127929687493, 55.59394531250001], + [-132.63129882812495, 55.47319335937502], + [-132.41787109375002, 55.48291015625006], + [-132.20668945312497, 55.22441406249996], + [-131.97641601562498, 55.208593750000034], + [-132.06474609375002, 54.713134765625], + [-133.11855468750002, 55.32763671875003], + [-132.95888671875002, 55.39555664062502], + [-133.0333984375, 55.589697265625034], + [-133.68017578124994, 55.78515625], + [-133.24150390624993, 55.920800781249994], + [-133.371240234375, 56.035888671875], + [-133.74252929687498, 55.96484375], + [-133.530859375, 56.145654296874966], + [-133.56611328125, 56.33920898437498] + ] + ], + [ + [ + [-132.77988281249998, 56.24726562499998], + [-133.03500976562498, 56.34091796875006], + [-132.90205078124998, 56.45375976562505], + [-132.62910156249995, 56.411914062500045], + [-132.77988281249998, 56.24726562499998] + ] + ], + [ + [ + [-132.11235351562493, 56.109375], + [-132.13295898437497, 55.94326171875005], + [-132.28730468749995, 55.92939453124998], + [-132.65991210937503, 56.07817382812499], + [-132.379833984375, 56.49877929687497], + [-132.06689453125, 56.24423828124998], + [-132.11235351562493, 56.109375] + ] + ], + [ + [ + [-154.208642578125, 56.51489257812497], + [-154.32221679687504, 56.570605468750045], + [-154.11040039062496, 56.602929687499966], + [-154.208642578125, 56.51489257812497] + ] + ], + [ + [ + [-169.755224609375, 56.63505859375002], + [-169.47431640624998, 56.59404296875002], + [-169.6326171875, 56.545703125000045], + [-169.755224609375, 56.63505859375002] + ] + ], + [ + [ + [-132.746875, 56.525683593750045], + [-132.94804687500002, 56.56723632812498], + [-132.842529296875, 56.79477539062506], + [-132.56796875000003, 56.57583007812505], + [-132.746875, 56.525683593750045] + ] + ], + [ + [ + [-133.98959960937503, 56.84497070312497], + [-133.73837890625, 56.65043945312496], + [-133.94970703125, 56.12773437499996], + [-134.18959960937502, 56.07695312500002], + [-134.084375, 56.456347656250045], + [-134.37368164062502, 56.838671875000045], + [-134.14326171874998, 56.93232421875001], + [-133.98959960937503, 56.84497070312497] + ] + ], + [ + [ + [-133.36621093750006, 57.003515625000034], + [-132.99624023437497, 56.93041992187497], + [-132.95917968749998, 56.67705078124996], + [-133.03491210937494, 56.62075195312505], + [-133.32895507812498, 56.83007812499997], + [-133.158154296875, 56.495166015625045], + [-133.4841796875, 56.45175781249998], + [-133.979443359375, 57.009570312500045], + [-133.36621093750006, 57.003515625000034] + ] + ], + [ + [ + [-153.007080078125, 57.12485351562498], + [-153.37460937499998, 57.05190429687505], + [-153.285205078125, 57.18505859375], + [-152.90839843750004, 57.152441406250006], + [-153.007080078125, 57.12485351562498] + ] + ], + [ + [ + [-134.96977539062496, 57.351416015625034], + [-134.62070312499998, 56.71831054687502], + [-134.68188476562503, 56.216162109375034], + [-134.98056640625003, 56.518945312499994], + [-134.88344726562497, 56.679052734375034], + [-135.33061523437505, 56.821875], + [-135.19960937499997, 57.02734375], + [-135.45493164062503, 57.24941406250005], + [-135.81230468750002, 57.00952148437503], + [-135.82275390625, 57.280419921874966], + [-135.448681640625, 57.534375], + [-134.96977539062496, 57.351416015625034] + ] + ], + [ + [ + [-152.89804687499998, 57.82392578125004], + [-152.42875976562493, 57.82568359375003], + [-152.48261718749998, 57.70332031249998], + [-152.21621093749997, 57.577001953125006], + [-152.41220703125003, 57.454785156249955], + [-152.94077148437498, 57.49809570312499], + [-152.67905273437503, 57.345117187499994], + [-153.274365234375, 57.22636718749996], + [-153.732568359375, 57.052343750000034], + [-153.643310546875, 56.960742187500045], + [-154.02734375, 56.77797851562502], + [-153.793212890625, 56.98950195312503], + [-154.24375, 57.143017578124955], + [-154.33896484374998, 56.9208984375], + [-154.67319335937498, 57.44609375], + [-154.11616210937498, 57.651220703125006], + [-153.6876953125, 57.30512695312504], + [-153.841552734375, 57.86284179687496], + [-153.48793945312497, 57.73095703125], + [-153.21748046875004, 57.79575195312506], + [-153.16044921875, 57.97197265624999], + [-152.85039062499993, 57.896777343750045], + [-152.89804687499998, 57.82392578125004] + ] + ], + [ + [ + [-135.73037109375002, 58.244238281250034], + [-135.61323242187507, 57.99184570312505], + [-135.346630859375, 58.12412109374998], + [-134.9546875, 58.01533203125004], + [-134.97065429687495, 57.817236328125006], + [-135.33847656250003, 57.768652343750034], + [-134.97885742187503, 57.724365234375], + [-134.93149414062498, 57.48115234375001], + [-135.564208984375, 57.66640625], + [-135.691943359375, 57.41992187500006], + [-135.91079101562502, 57.44658203124999], + [-136.568603515625, 57.97216796875003], + [-136.32197265625, 58.21889648437502], + [-136.14375, 58.098486328125006], + [-136.09438476562502, 58.198144531249966], + [-135.73037109375002, 58.244238281250034] + ] + ], + [ + [ + [-134.68027343749998, 58.16166992187499], + [-134.24008789062498, 58.143994140624955], + [-133.82275390624997, 57.62866210937503], + [-134.29233398437498, 58.044726562500074], + [-133.91113281250003, 57.3525390625], + [-134.51601562499997, 57.042578125], + [-134.48676757812495, 57.48203125], + [-134.92348632812497, 58.354638671874966], + [-134.68027343749998, 58.16166992187499] + ] + ], + [ + [ + [-152.416943359375, 58.360205078125034], + [-151.974365234375, 58.30986328124999], + [-152.068896484375, 58.17792968750001], + [-152.26835937499993, 58.25170898437506], + [-152.30922851562502, 58.133886718750034], + [-152.5982421875, 58.16259765625], + [-152.92841796875004, 57.99370117187499], + [-153.38134765625003, 58.08720703125002], + [-152.976123046875, 58.29702148437505], + [-152.771875, 58.278564453125], + [-152.84111328125002, 58.41640625000002], + [-152.416943359375, 58.360205078125034] + ] + ], + [ + [ + [-152.486083984375, 58.485009765624966], + [-152.63662109375002, 58.54169921874998], + [-152.3955078125, 58.619384765625], + [-152.486083984375, 58.485009765624966] + ] + ], + [ + [ + [-160.918994140625, 58.57709960937498], + [-161.13149414062502, 58.668212890625], + [-160.71513671875005, 58.79521484375002], + [-160.918994140625, 58.57709960937498] + ] + ], + [ + [ + [-148.02177734375, 60.06533203125005], + [-148.271875, 60.05327148437499], + [-148.07958984375003, 60.151660156250045], + [-148.02177734375, 60.06533203125005] + ] + ], + [ + [ + [-147.735888671875, 59.81323242187503], + [-147.76806640625, 59.94375], + [-147.180859375, 60.358251953125034], + [-147.01987304687498, 60.33222656249998], + [-147.735888671875, 59.81323242187503] + ] + ], + [ + [ + [-166.13544921875, 60.38354492187503], + [-165.72968750000004, 60.31420898437503], + [-165.591796875, 59.913134765625045], + [-166.14873046874996, 59.764111328124955], + [-167.13886718749998, 60.00854492187503], + [-167.43642578125002, 60.20664062500006], + [-166.836328125, 60.21699218750004], + [-166.47568359374998, 60.382763671874955], + [-166.13544921875, 60.38354492187503] + ] + ], + [ + [ + [-146.3939453125, 60.44965820312501], + [-146.10224609374998, 60.41118164062499], + [-146.61831054687497, 60.27368164062503], + [-146.70253906249997, 60.40854492187498], + [-146.3939453125, 60.44965820312501] + ] + ], + [ + [ + [-147.658251953125, 60.45048828124999], + [-147.787841796875, 60.17792968749998], + [-147.89145507812498, 60.299414062500034], + [-147.658251953125, 60.45048828124999] + ] + ], + [ + [ + [-172.74223632812496, 60.45737304687498], + [-172.23208007812494, 60.299121093750074], + [-172.63574218750003, 60.328857421875], + [-173.04765625000002, 60.56831054687501], + [-172.74223632812496, 60.45737304687498] + ] + ], + [ + [ + [-171.46303710937494, 63.640039062499994], + [-171.03486328125, 63.58549804687499], + [-170.29936523437502, 63.68061523437501], + [-169.55454101562498, 63.373486328124955], + [-168.71601562500004, 63.310595703125045], + [-168.76132812500003, 63.21376953125002], + [-169.364697265625, 63.17114257812506], + [-169.67636718750003, 62.95610351562502], + [-169.81860351562494, 63.122363281250045], + [-170.84838867187494, 63.44438476562502], + [-171.63183593749997, 63.351220703124966], + [-171.74638671874993, 63.703076171874955], + [-171.46303710937494, 63.640039062499994] + ] + ], + [ + [ + [-141.00214843750004, 68.77416992187506], + [-141.00214843750004, 67.89755859374998], + [-141.00214843750004, 66.43652343750006], + [-141.00214843750004, 65.55991210937498], + [-141.00214843750004, 64.09887695312506], + [-141.00214843750004, 63.22226562499998], + [-141.00214843750004, 61.761279296875045], + [-141.00214843750004, 60.884667968749994], + [-141.00214843750004, 60.30024414062504], + [-140.76274414062505, 60.259130859375006], + [-140.525439453125, 60.218359375000034], + [-140.45283203125004, 60.29970703125002], + [-139.97329101562497, 60.183154296875074], + [-139.67631835937505, 60.32832031249998], + [-139.23476562499997, 60.339746093749994], + [-139.07924804687497, 60.34370117187501], + [-139.07924804687497, 60.279443359374966], + [-139.136962890625, 60.17270507812498], + [-139.18515624999998, 60.083593750000034], + [-138.86875, 59.94575195312501], + [-138.317626953125, 59.611132812500074], + [-137.59331054687493, 59.22626953124998], + [-137.52089843750002, 58.91538085937498], + [-137.43857421875003, 58.903125], + [-137.2775390625, 58.988183593749994], + [-137.126220703125, 59.04096679687498], + [-136.81328125000002, 59.150048828124994], + [-136.57875976562502, 59.15224609375002], + [-136.46635742187493, 59.459082031250006], + [-136.27797851562502, 59.48032226562506], + [-136.321826171875, 59.604833984375034], + [-135.70258789062504, 59.72875976562506], + [-135.36787109374998, 59.743310546874994], + [-135.051025390625, 59.57866210937502], + [-134.94375, 59.28828125000001], + [-134.67724609374997, 59.19926757812499], + [-134.39306640625, 59.009179687499994], + [-134.32963867187505, 58.93969726562506], + [-134.21850585937503, 58.849902343750045], + [-133.54638671874997, 58.50346679687499], + [-133.27529296875, 58.22285156250004], + [-133.00141601562495, 57.948974609375], + [-132.55048828125, 57.499902343749994], + [-132.44248046874998, 57.40673828125003], + [-132.30166015624997, 57.27631835937501], + [-132.232177734375, 57.19853515624999], + [-132.27939453124998, 57.14536132812498], + [-132.33798828124998, 57.07944335937506], + [-132.15703125, 57.048193359375006], + [-132.03154296875, 57.02656250000004], + [-132.062890625, 56.95336914062503], + [-132.104296875, 56.856787109375006], + [-131.86616210937495, 56.792822265625006], + [-131.82426757812496, 56.589990234374994], + [-131.471875, 56.55673828125006], + [-130.649072265625, 56.26367187500003], + [-130.47709960937496, 56.230566406250034], + [-130.413134765625, 56.12250976562498], + [-130.09785156249995, 56.10927734375002], + [-130.01406249999997, 55.950537109375006], + [-130.2140625, 55.02587890625003], + [-130.57534179687497, 54.769677734374966], + [-130.849609375, 54.80761718750006], + [-131.04785156249997, 55.157666015624955], + [-130.74819335937502, 55.31801757812502], + [-131.127685546875, 55.96015625000001], + [-131.032763671875, 56.08808593749998], + [-131.78417968749997, 55.876562500000034], + [-131.98339843749994, 55.535009765625006], + [-132.15541992187502, 55.59956054687501], + [-132.20751953124997, 55.75341796875], + [-131.84384765625003, 56.16010742187498], + [-131.55136718749998, 56.206787109375], + [-131.88789062500004, 56.24165039062498], + [-132.18203125000002, 56.42065429687506], + [-132.82460937500002, 57.05581054687505], + [-133.465869140625, 57.17216796875002], + [-133.64873046874993, 57.64228515624998], + [-133.11704101562498, 57.56621093750002], + [-133.535205078125, 57.83295898437501], + [-133.1943359375, 57.87768554687506], + [-133.559375, 57.924462890624994], + [-133.72231445312502, 57.84423828125], + [-134.03110351562498, 58.072167968749966], + [-133.87675781249996, 58.51816406249998], + [-134.20883789062503, 58.232958984375045], + [-134.77612304687506, 58.45385742187503], + [-135.36367187500002, 59.41943359375], + [-135.50234375000002, 59.202294921874994], + [-135.090234375, 58.245849609375], + [-135.57177734374994, 58.41206054687504], + [-135.89755859374998, 58.40019531250002], + [-136.04311523437497, 58.82163085937498], + [-135.82636718750004, 58.89794921874997], + [-136.0166015625, 58.87397460937498], + [-136.150048828125, 59.04809570312503], + [-136.22583007812497, 58.765478515625006], + [-136.98901367187503, 59.03447265624999], + [-137.05903320312498, 58.87373046875001], + [-136.613916015625, 58.809277343749955], + [-136.48374023437503, 58.61767578125], + [-136.224609375, 58.602246093749955], + [-136.06147460937495, 58.45273437500006], + [-136.607421875, 58.24399414062498], + [-137.54399414062502, 58.58120117187502], + [-138.51489257812503, 59.16591796875005], + [-139.77329101562498, 59.52729492187504], + [-139.51303710937498, 59.698095703125006], + [-139.5123046875, 59.95356445312501], + [-139.28671874999998, 59.610937500000034], + [-139.22080078125003, 59.819873046875045], + [-138.9880859375, 59.83500976562502], + [-139.43144531249996, 60.012255859375074], + [-140.41982421874997, 59.71074218750002], + [-141.40830078125, 59.90278320312498], + [-141.408740234375, 60.11767578125006], + [-141.67016601562497, 59.969873046874966], + [-142.94565429687503, 60.09697265625002], + [-144.14721679687494, 60.01640625000002], + [-144.185498046875, 60.150732421875034], + [-144.901318359375, 60.335156249999955], + [-144.69111328125, 60.66909179687502], + [-145.248291015625, 60.38012695312506], + [-145.898876953125, 60.47817382812505], + [-145.67490234374998, 60.65112304687503], + [-146.57045898437497, 60.72915039062502], + [-146.39199218749997, 60.810839843750045], + [-146.63842773437497, 60.89731445312498], + [-146.59912109374994, 61.05351562500002], + [-146.284912109375, 61.11264648437498], + [-147.89111328125, 60.889892578125], + [-148.00512695312494, 60.96855468750002], + [-147.75185546874997, 61.218945312499955], + [-148.34189453125, 61.060400390625006], + [-148.34443359374998, 60.853564453125045], + [-148.55615234374994, 60.82700195312506], + [-148.25673828124997, 60.67529296874997], + [-148.64013671875, 60.48945312500004], + [-148.11918945312502, 60.57514648437498], + [-147.96411132812494, 60.48486328124997], + [-148.430712890625, 59.98911132812498], + [-149.2666015625, 59.99829101562497], + [-149.395263671875, 60.10576171875002], + [-149.59804687500002, 59.77045898437501], + [-149.7138671875, 59.91958007812502], + [-149.80126953124994, 59.737939453124966], + [-150.00532226562507, 59.78442382812503], + [-150.19804687499996, 59.56655273437505], + [-150.60737304687504, 59.56337890625002], + [-150.934521484375, 59.249121093750034], + [-151.18276367187502, 59.30078124999997], + [-151.73818359375002, 59.18852539062502], + [-151.94951171875, 59.26508789062498], + [-151.88461914062503, 59.386328125], + [-151.39960937499995, 59.51630859375001], + [-151.04648437499998, 59.771826171875034], + [-151.45009765624997, 59.65039062499997], + [-151.85322265625, 59.78208007812498], + [-151.39599609375006, 60.27446289062502], + [-151.35644531249997, 60.72294921874999], + [-150.44125976562503, 61.02358398437505], + [-149.07509765624997, 60.87641601562498], + [-150.05327148437496, 61.17109374999998], + [-149.433544921875, 61.50078125000002], + [-149.97568359374998, 61.27934570312502], + [-150.61225585937495, 61.301123046875006], + [-151.59350585937494, 60.979638671874966], + [-152.54091796874997, 60.265429687500045], + [-153.025, 60.29565429687497], + [-152.660107421875, 59.99721679687502], + [-153.21123046875002, 59.84272460937498], + [-153.09360351562503, 59.70913085937505], + [-153.65253906250004, 59.64702148437499], + [-154.17832031250003, 59.155566406250074], + [-153.41826171875, 58.9599609375], + [-153.43759765625003, 58.754833984374955], + [-154.289013671875, 58.30434570312502], + [-154.247021484375, 58.15942382812497], + [-155.006884765625, 58.01606445312501], + [-155.77797851562497, 57.56821289062498], + [-156.43588867187498, 57.359960937500006], + [-156.62900390624998, 57.00996093750001], + [-158.41440429687498, 56.435839843750045], + [-158.5521484375, 56.31269531249998], + [-158.27563476562497, 56.19624023437498], + [-158.5046875, 56.062109375], + [-158.59116210937503, 56.18452148437498], + [-158.78984375000002, 55.98691406250006], + [-159.52324218749993, 55.81000976562498], + [-159.65966796875003, 55.625927734374955], + [-159.77138671874997, 55.84111328125002], + [-160.49931640625002, 55.53730468750004], + [-161.38193359374998, 55.371289062499955], + [-161.44379882812495, 55.513281250000034], + [-161.202099609375, 55.54355468750006], + [-161.51694335937503, 55.61840820312503], + [-162.073974609375, 55.13930664062505], + [-162.38637695312497, 55.05234375], + [-162.63037109375003, 55.24667968749998], + [-162.67436523437505, 54.99658203125], + [-162.86503906249996, 54.954541015624955], + [-163.11962890624997, 55.06469726562503], + [-163.131103515625, 54.916552734375045], + [-163.33530273437503, 54.83916015624999], + [-163.27880859374997, 55.12182617187503], + [-162.906591796875, 55.19555664062503], + [-161.69731445312502, 55.9072265625], + [-161.215625, 56.02143554687498], + [-160.8986328125, 55.99365234375], + [-161.00537109375, 55.88715820312498], + [-160.80283203125003, 55.754443359375045], + [-160.70634765625002, 55.870458984375034], + [-160.29169921875, 55.80507812500005], + [-160.53906250000006, 56.00629882812501], + [-160.30205078125, 56.31411132812502], + [-158.91801757812502, 56.882177734375006], + [-158.675146484375, 56.79487304687498], + [-158.66079101562502, 57.03940429687498], + [-158.32094726562497, 57.29790039062499], + [-157.84575195312496, 57.52807617187497], + [-157.4619140625, 57.506201171875034], + [-157.697216796875, 57.679443359375], + [-157.610888671875, 58.050830078125074], + [-157.19370117187498, 58.19418945312506], + [-157.48837890624998, 58.25371093750002], + [-157.52363281249998, 58.421337890624955], + [-156.97465820312496, 58.736328125], + [-156.80888671875005, 59.13427734375], + [-157.14204101562504, 58.87763671875001], + [-158.19091796875003, 58.6142578125], + [-158.50317382812494, 58.85034179687497], + [-158.42563476562498, 58.99931640625002], + [-158.080517578125, 58.97744140625002], + [-158.422802734375, 59.08984375], + [-158.67827148437502, 58.92939453124998], + [-158.80947265625002, 58.973876953125], + [-158.78862304687493, 58.440966796875045], + [-158.95068359375, 58.404541015625], + [-159.67026367187498, 58.9111328125], + [-159.92021484375, 58.819873046875074], + [-160.36313476562498, 59.05117187500002], + [-161.246826171875, 58.799462890624994], + [-161.36132812499994, 58.66953124999998], + [-162.144921875, 58.64423828124998], + [-161.724365234375, 58.794287109375006], + [-161.64438476562498, 59.109667968750045], + [-161.9810546875, 59.14614257812502], + [-161.82871093749998, 59.588623046875], + [-162.421337890625, 60.28398437500002], + [-161.96201171875003, 60.695361328125045], + [-162.68496093749997, 60.268945312499966], + [-162.57075195312495, 59.98974609375], + [-163.68037109374998, 59.80151367187503], + [-164.14282226562497, 59.89677734374999], + [-165.02651367187497, 60.500634765624994], + [-165.35380859375002, 60.54121093750001], + [-164.80517578125, 60.89204101562498], + [-164.31850585937497, 60.77128906249999], + [-164.37236328125002, 60.59184570312502], + [-163.999560546875, 60.76606445312498], + [-163.72998046874997, 60.589990234374994], + [-163.420947265625, 60.757421875], + [-163.90654296874996, 60.85380859375002], + [-163.58691406249994, 60.902978515624994], + [-163.74902343750003, 60.9697265625], + [-163.99462890624997, 60.86469726562501], + [-165.11484375, 60.93281250000004], + [-164.86899414062503, 61.11176757812498], + [-165.27978515624994, 61.169628906249955], + [-165.27363281250004, 61.27485351562498], + [-165.56586914062498, 61.10234375000002], + [-165.86396484375004, 61.33569335937503], + [-165.84531249999998, 61.536230468750034], + [-166.152734375, 61.545947265625074], + [-166.16811523437502, 61.65083007812501], + [-165.80893554687503, 61.69609375000002], + [-166.07880859375, 61.803125], + [-165.61279296875003, 61.86928710937502], + [-165.707275390625, 62.10043945312506], + [-165.19453125, 62.47353515625002], + [-164.75786132812493, 62.496728515624966], + [-164.589453125, 62.709375], + [-164.79267578125, 62.623193359374966], + [-164.79965820312503, 62.918066406250006], + [-164.384228515625, 63.03046874999998], + [-164.40903320312503, 63.21503906250001], + [-163.94287109375, 63.247216796874994], + [-163.61630859374998, 63.125146484374994], + [-163.73784179687496, 63.016406250000045], + [-163.504345703125, 63.105859374999966], + [-163.28784179687494, 63.046435546875045], + [-162.621484375, 63.26582031249998], + [-162.28281250000003, 63.529199218749994], + [-161.97397460937498, 63.45292968749999], + [-161.09970703125003, 63.557910156250045], + [-160.778564453125, 63.818945312500034], + [-160.987548828125, 64.25126953125002], + [-161.49072265625003, 64.43378906249998], + [-160.93193359374996, 64.5791015625], + [-160.855908203125, 64.755615234375], + [-161.13017578125005, 64.92543945312505], + [-161.759375, 64.816259765625], + [-162.80703124999997, 64.37421875000001], + [-163.20390625, 64.65200195312502], + [-163.14433593750002, 64.423828125], + [-163.71308593749998, 64.588232421875], + [-164.978759765625, 64.45366210937502], + [-166.1427734375, 64.58276367187503], + [-166.48139648437498, 64.72807617187507], + [-166.415234375, 64.926513671875], + [-166.92841796875, 65.15708007812498], + [-166.15703125, 65.28583984375001], + [-167.40400390625, 65.42211914062497], + [-168.08837890624997, 65.65776367187502], + [-166.39873046875002, 66.14443359375005], + [-165.62993164062496, 66.131201171875], + [-165.77617187500002, 66.31904296875001], + [-164.46049804687502, 66.58842773437499], + [-163.63823242187502, 66.57465820312504], + [-163.89394531249997, 66.57587890625001], + [-164.03374023437493, 66.21552734374995], + [-163.69536132812502, 66.08383789062503], + [-161.93369140625003, 66.04287109374997], + [-161.45541992187503, 66.28139648437497], + [-161.03427734375003, 66.18881835937503], + [-161.12031249999995, 66.334326171875], + [-161.91689453124997, 66.41181640624998], + [-162.54365234375004, 66.80512695312501], + [-162.36162109375, 66.94731445312502], + [-161.591015625, 66.45952148437502], + [-160.23168945312503, 66.420263671875], + [-160.360888671875, 66.6125], + [-160.864013671875, 66.67084960937501], + [-161.39804687499998, 66.55185546875], + [-161.85668945312497, 66.70034179687497], + [-161.719921875, 67.02055664062502], + [-163.5318359375, 67.10258789062502], + [-164.1251953125, 67.60673828125007], + [-166.786279296875, 68.35961914062497], + [-166.38051757812502, 68.425146484375], + [-166.20908203125, 68.88535156250003], + [-165.04394531249994, 68.882470703125], + [-163.867919921875, 69.03666992187505], + [-161.88095703125003, 70.33173828125001], + [-162.073876953125, 70.16196289062498], + [-160.9962890625, 70.30458984375], + [-160.11713867187495, 70.59121093750002], + [-159.86567382812498, 70.27885742187499], + [-159.81499023437496, 70.49707031250003], + [-159.38676757812493, 70.52451171875003], + [-160.081591796875, 70.63486328125003], + [-159.680908203125, 70.786767578125], + [-159.31450195312496, 70.87851562500003], + [-159.251171875, 70.7484375], + [-157.909375, 70.860107421875], + [-156.47021484374994, 71.40766601562501], + [-156.469970703125, 71.29155273437507], + [-155.57944335937503, 71.12109374999997], + [-156.14658203125, 70.92783203125003], + [-155.97353515625002, 70.84199218749995], + [-155.16684570312498, 71.09921875000006], + [-154.19521484375002, 70.80112304687498], + [-153.23291015625, 70.93256835937504], + [-152.49121093749994, 70.88095703125], + [-152.23291015625, 70.81035156249999], + [-152.39921875, 70.62045898437503], + [-151.76904296875, 70.56015625], + [-151.94467773437498, 70.45209960937501], + [-149.26943359374997, 70.50078124999999], + [-147.70537109375, 70.21723632812495], + [-145.82314453124997, 70.16005859375002], + [-145.19736328125003, 70.00869140625002], + [-143.218310546875, 70.11625976562499], + [-142.70786132812498, 70.03378906249998], + [-141.40791015625, 69.65336914062502], + [-141.00214843750004, 69.65078125000002], + [-141.00214843750004, 68.77416992187506] + ] + ] + ] + }, + "properties": { "name": "United States", "childNum": 76 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [71.20615234375006, 39.892578125], + [71.15625, 39.88344726562502], + [71.06425781250002, 39.88491210937505], + [71.01171874999997, 39.895117187500006], + [71.04365234375004, 39.97631835937503], + [71.04482421875005, 39.992529296875034], + [70.96064453125004, 40.087988281250034], + [71.00546875, 40.15229492187498], + [71.0241210937501, 40.14916992187497], + [71.08037109375007, 40.07988281249999], + [71.2287109375001, 40.04814453124999], + [71.20615234375006, 39.892578125] + ] + ], + [ + [ + [70.94677734374997, 42.24868164062505], + [70.97900390625003, 42.26655273437504], + [71.03603515625, 42.28466796875], + [71.12998046875006, 42.25], + [71.21269531250002, 42.20644531250005], + [71.23232421875005, 42.18627929687503], + [71.22851562499997, 42.16289062499996], + [70.18095703125007, 41.571435546874994], + [70.734375, 41.400537109374994], + [70.86044921875006, 41.22490234375002], + [71.11074218750005, 41.152636718750045], + [71.29882812500003, 41.152490234374994], + [71.39306640625003, 41.123388671875034], + [71.40839843750004, 41.13603515625002], + [71.42089843750003, 41.341894531250034], + [71.60625, 41.367431640625], + [71.66494140625, 41.54121093749998], + [71.70068359374997, 41.454003906249966], + [71.75771484375005, 41.42802734375002], + [71.79248046875003, 41.41313476562499], + [71.85800781250006, 41.311376953125034], + [71.8786132812501, 41.195019531249955], + [71.95849609375003, 41.18706054687502], + [72.05244140625004, 41.16474609375001], + [72.1154296875001, 41.18657226562502], + [72.1642578125001, 41.173730468749966], + [72.18095703125002, 41.11845703124999], + [72.18730468750002, 41.02592773437499], + [72.2130859375001, 41.014257812500006], + [72.36406250000002, 41.04345703125], + [72.65830078125, 40.86992187499999], + [73.13212890625002, 40.82851562499999], + [72.6041015625, 40.52543945312499], + [72.40205078125004, 40.578076171874955], + [72.3892578125, 40.427392578124994], + [72.13125, 40.438623046874966], + [71.69248046875, 40.15234375], + [71.30468749999997, 40.28691406249996], + [70.990625, 40.2548828125], + [70.95800781250003, 40.238867187500034], + [70.653125, 40.201171875], + [70.37158203125003, 40.38413085937506], + [70.75107421875006, 40.721777343750006], + [70.40195312500006, 41.03510742187498], + [69.71289062500003, 40.65698242187503], + [69.35722656250002, 40.76738281249996], + [69.20625, 40.566552734374994], + [69.27490234374997, 40.19809570312498], + [68.63066406250007, 40.16708984374998], + [68.9720703125, 40.08994140624998], + [68.80468750000003, 40.05034179687499], + [68.86875, 39.90747070312503], + [68.63896484375007, 39.8388671875], + [68.46328125, 39.53671874999998], + [67.71904296875007, 39.62138671875002], + [67.426171875, 39.46557617187497], + [67.3576171875001, 39.216699218749994], + [67.64833984375005, 39.13105468750004], + [67.69443359375006, 38.99462890625003], + [68.13251953125004, 38.927636718749966], + [68.08720703125002, 38.47353515625002], + [68.3502929687501, 38.211035156250006], + [67.81435546875005, 37.48701171875004], + [67.7980468750001, 37.244970703125006], + [67.75898437500004, 37.172216796875034], + [67.75292968749997, 37.199804687500034], + [67.7, 37.227246093749955], + [67.60742187499997, 37.22250976562506], + [67.5172851562501, 37.26665039062499], + [67.44169921875007, 37.25800781250001], + [67.3197265625, 37.209570312500006], + [67.1955078125001, 37.23520507812498], + [67.06884765624997, 37.334814453125006], + [66.82773437500006, 37.37128906249998], + [66.52226562500007, 37.34848632812506], + [66.51064453125, 37.45869140625004], + [66.51132812500006, 37.59916992187496], + [66.52558593750004, 37.785742187500034], + [66.60625, 37.98671875000005], + [65.97119140624997, 38.244238281250006], + [65.612890625, 38.23857421875002], + [64.3099609375, 38.97729492187497], + [63.76367187500003, 39.16054687499999], + [62.48320312500002, 39.97563476562496], + [61.90283203124997, 41.09370117187501], + [61.496972656249994, 41.276074218749955], + [61.2423828125001, 41.18920898437503], + [60.454980468749994, 41.221630859374955], + [60.089648437500074, 41.39941406250003], + [60.07558593750005, 41.759667968749966], + [60.20078125000006, 41.803125], + [59.94179687499999, 41.97353515625002], + [59.98515625000002, 42.21171875], + [59.35429687500002, 42.32329101562496], + [58.5890625000001, 42.778466796874966], + [58.477148437500006, 42.66284179687503], + [58.15156250000004, 42.628076171874966], + [58.474414062500074, 42.29936523437496], + [58.02890625, 42.48764648437506], + [57.814257812500074, 42.18984375000005], + [57.290625, 42.123779296875], + [56.96406250000004, 41.856542968750006], + [57.11884765625004, 41.35029296874998], + [57.01796875, 41.26347656249996], + [55.97744140625005, 41.32221679687504], + [55.97568359375006, 44.99492187499996], + [58.555273437500006, 45.55537109375001], + [61.007910156250006, 44.39379882812497], + [61.99023437500003, 43.492138671874955], + [63.20703125000003, 43.62797851562502], + [64.44316406250007, 43.55117187499999], + [64.9054687500001, 43.714697265625006], + [65.49619140625, 43.310546875], + [65.80302734375002, 42.87695312500006], + [66.1002929687501, 42.99082031249998], + [66.00957031250007, 42.00488281250003], + [66.49863281250006, 41.99487304687503], + [66.70966796875004, 41.17915039062501], + [67.9357421875001, 41.19658203125002], + [68.11308593750007, 41.02861328124999], + [68.04765625000007, 40.80927734374998], + [68.29189453125, 40.656103515625034], + [68.57265625, 40.62265624999998], + [68.58408203125, 40.876269531250045], + [69.15361328125002, 41.42524414062498], + [70.94677734374997, 42.24868164062505] + ] + ] + ] + }, + "properties": { "name": "Uzbekistan", "childNum": 2 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [-61.17451171875001, 13.158105468749966], + [-61.268457031249966, 13.287695312499991], + [-61.13896484374996, 13.358740234374991], + [-61.17451171875001, 13.158105468749966] + ] + ] + }, + "properties": { "name": "St. Vin. and Gren.", "childNum": 1 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [-60.99790039062498, 8.867333984374966], + [-61.069189453125034, 8.947314453125003], + [-60.91582031249996, 9.070312500000014], + [-60.86142578124998, 8.949609375000037], + [-60.99790039062498, 8.867333984374966] + ] + ], + [ + [ + [-60.821191406249966, 9.138378906250026], + [-60.94140625000003, 9.105566406250006], + [-60.73583984374997, 9.203320312500026], + [-60.821191406249966, 9.138378906250026] + ] + ], + [ + [ + [-63.84936523437494, 11.131005859374994], + [-63.917626953124994, 10.887548828125048], + [-64.40234375, 10.981591796875023], + [-64.21367187500002, 11.086132812499997], + [-64.0283203125, 11.00185546874998], + [-63.84936523437494, 11.131005859374994] + ] + ], + [ + [ + [-60.742138671874926, 5.202050781250037], + [-60.71196289062499, 5.191552734375023], + [-60.671972656250034, 5.164355468749989], + [-60.603857421875006, 4.94936523437498], + [-61.00283203125002, 4.535253906249991], + [-61.28007812500002, 4.516894531249974], + [-61.82084960937496, 4.197021484375], + [-62.153125, 4.098388671874986], + [-62.41064453124994, 4.156738281249972], + [-62.71210937499998, 4.01791992187502], + [-62.85698242187502, 3.593457031249969], + [-63.33867187500002, 3.943896484375045], + [-64.02148437500003, 3.929101562500051], + [-64.19248046874995, 4.126855468750009], + [-64.57636718750001, 4.139892578125], + [-64.788671875, 4.276025390625023], + [-64.66899414062496, 4.01181640625002], + [-64.22109375000002, 3.587402343749972], + [-64.04658203124998, 2.502392578124997], + [-63.389257812500006, 2.411914062500045], + [-63.43251953124994, 2.155566406250045], + [-64.00849609374995, 1.931591796874969], + [-64.20502929687493, 1.52949218750004], + [-65.10375976562497, 1.108105468749983], + [-65.47338867187497, 0.691259765624977], + [-65.55605468750002, 0.687988281250014], + [-65.52299804687493, 0.843408203124966], + [-65.68144531249999, 0.983447265624989], + [-66.06005859375003, 0.78535156250004], + [-66.34711914062498, 0.7671875], + [-66.87602539062499, 1.223046875000037], + [-67.21083984375, 2.390136718750043], + [-67.61870117187496, 2.793603515624994], + [-67.85908203124998, 2.793603515624994], + [-67.3111328125, 3.41586914062502], + [-67.66162109375, 3.864257812499986], + [-67.85527343750002, 4.506884765624989], + [-67.82490234374995, 5.270458984375026], + [-67.47387695312503, 5.929980468750003], + [-67.48198242187499, 6.18027343750002], + [-67.85917968749999, 6.289892578124963], + [-68.47177734375, 6.156542968749974], + [-69.42714843749997, 6.123974609374997], + [-70.12919921874999, 6.95361328125], + [-70.73715820312503, 7.090039062499997], + [-71.12861328124993, 6.98671875], + [-72.00664062499993, 7.032617187500023], + [-72.20771484374995, 7.37026367187498], + [-72.47197265624996, 7.524267578124991], + [-72.39033203124995, 8.287060546874969], + [-72.66542968749994, 8.62758789062498], + [-72.79638671874997, 9.10898437499999], + [-73.05839843749999, 9.259570312500031], + [-73.36621093749997, 9.194140625000017], + [-73.00654296874998, 9.789160156250006], + [-72.86933593750001, 10.49125976562496], + [-72.690087890625, 10.835839843749994], + [-72.24848632812501, 11.196435546875009], + [-71.95810546875, 11.66640625], + [-71.31972656249997, 11.861914062500048], + [-71.95693359375002, 11.569921874999977], + [-71.835107421875, 11.190332031250009], + [-71.6416015625, 11.013525390625048], + [-71.73090820312498, 10.994677734375017], + [-71.59433593749995, 10.657373046875051], + [-72.11284179687499, 9.815576171874966], + [-71.61953124999994, 9.047949218749991], + [-71.24140625000001, 9.160449218750003], + [-71.08583984375002, 9.348242187499977], + [-71.05268554687501, 9.705810546874986], + [-71.49423828125, 10.533203124999972], + [-71.46953124999993, 10.964160156250017], + [-70.23251953124998, 11.372998046874997], + [-70.09711914062493, 11.519775390624972], + [-69.80478515624998, 11.47421875000002], + [-69.81733398437495, 11.672070312499997], + [-70.19257812499993, 11.62460937500002], + [-70.28652343749997, 11.886035156249989], + [-70.20278320312497, 12.098388671874986], + [-70.00395507812496, 12.177880859375023], + [-69.63159179687494, 11.479931640625026], + [-68.827978515625, 11.431738281249977], + [-68.39863281249995, 11.160986328124977], + [-68.29628906249997, 10.689355468749994], + [-68.13994140624999, 10.492724609374989], + [-66.24721679687497, 10.632226562499994], + [-65.85175781249995, 10.257763671874997], + [-65.12910156249998, 10.070068359375043], + [-64.85048828125, 10.098095703124969], + [-64.188330078125, 10.457812499999989], + [-63.73188476562501, 10.503417968750043], + [-64.24750976562498, 10.54257812500002], + [-64.298193359375, 10.635156249999966], + [-61.879492187500006, 10.741015625000031], + [-62.379980468750006, 10.546875], + [-62.91357421875, 10.531494140624986], + [-62.68583984374996, 10.289794921875043], + [-62.740576171875006, 10.056152343750043], + [-62.55034179687499, 10.200439453125043], + [-62.320410156250034, 9.783056640625006], + [-62.22114257812498, 9.882568359375028], + [-62.15336914062493, 9.821777343749986], + [-62.15532226562499, 9.979248046875014], + [-62.077099609374926, 9.97504882812504], + [-61.73593749999998, 9.631201171874977], + [-61.76591796874996, 9.813818359374963], + [-61.58886718749994, 9.894531249999986], + [-60.79248046874997, 9.360742187500037], + [-61.02314453124998, 9.15458984374996], + [-61.24726562499998, 8.600341796875014], + [-61.61870117187499, 8.59746093749996], + [-61.30400390624999, 8.410400390625043], + [-60.800976562499926, 8.592138671875034], + [-60.16748046875, 8.616992187500031], + [-60.01752929687501, 8.549316406250014], + [-59.83164062499998, 8.305957031250003], + [-59.84907226562498, 8.248681640624966], + [-59.96484375000003, 8.191601562499969], + [-59.99072265624997, 8.16201171874998], + [-60.032421874999926, 8.053564453125006], + [-60.51362304687501, 7.813183593749969], + [-60.71865234374994, 7.535937499999974], + [-60.606542968750006, 7.320849609375031], + [-60.63330078124997, 7.211083984374966], + [-60.58320312499998, 7.156201171874969], + [-60.523193359375, 7.143701171875009], + [-60.464941406250034, 7.166552734375045], + [-60.39238281249999, 7.164550781249986], + [-60.34506835937495, 7.15], + [-60.32548828124996, 7.133984374999983], + [-60.32207031249996, 7.092041015625043], + [-60.35209960937496, 7.002880859374997], + [-60.39501953125, 6.945361328125003], + [-60.717919921874966, 6.768310546875], + [-61.14560546874998, 6.694531249999983], + [-61.20361328124997, 6.588378906250028], + [-61.181591796874926, 6.513378906250026], + [-61.15102539062502, 6.446533203124986], + [-61.15229492187501, 6.385107421875006], + [-61.12871093749999, 6.214306640625026], + [-61.15947265624996, 6.174414062499977], + [-61.22495117187498, 6.129199218750003], + [-61.303125, 6.049511718750026], + [-61.39082031250001, 5.938769531250017], + [-61.376806640625006, 5.906982421875028], + [-61.167187499999926, 5.674218750000037], + [-60.95400390625002, 5.437402343750023], + [-60.742138671874926, 5.202050781250037] + ] + ] + ] + }, + "properties": { "name": "Venezuela", "childNum": 4 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [-64.765625, 17.794335937499994], + [-64.58046874999994, 17.750195312499983], + [-64.88911132812495, 17.701708984375045], + [-64.765625, 17.794335937499994] + ] + ] + }, + "properties": { "name": "U.S. Virgin Is.", "childNum": 1 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [104.06396484375003, 10.390820312500011], + [104.01845703125, 10.029199218749966], + [103.84951171875005, 10.371093749999986], + [104.06396484375003, 10.390820312500011] + ] + ], + [ + [ + [107.52128906250007, 20.926611328124977], + [107.39921875000007, 20.903466796874966], + [107.55126953125003, 21.034033203125006], + [107.52128906250007, 20.926611328124977] + ] + ], + [ + [ + [107.60273437500004, 21.21679687500003], + [107.40351562500004, 21.093652343749994], + [107.47626953125004, 21.268945312499994], + [107.60273437500004, 21.21679687500003] + ] + ], + [ + [ + [107.97265624999997, 21.507958984375023], + [107.40996093750002, 21.284814453125023], + [107.35429687500007, 21.055175781250057], + [107.1647460937501, 20.94873046875003], + [106.68339843750007, 21.000292968750074], + [106.75341796875003, 20.73505859375004], + [106.55078124999997, 20.52656250000001], + [106.57285156250012, 20.392187499999977], + [105.98408203125004, 19.939062500000034], + [105.62177734375004, 18.96630859375003], + [105.88828125000006, 18.502490234375045], + [106.49902343749997, 17.946435546874994], + [106.47890625000005, 17.719580078125063], + [106.3705078125, 17.746875], + [107.83378906250002, 16.322460937499983], + [108.02939453125012, 16.331103515625074], + [108.82128906249997, 15.377929687500028], + [109.30332031250012, 13.856445312500043], + [109.271875, 13.279345703124974], + [109.42392578125006, 12.955957031249994], + [109.44492187500006, 12.599609375000057], + [109.33554687500012, 12.751904296874997], + [109.21894531250004, 12.64580078124996], + [109.30468750000003, 12.391162109375045], + [109.20683593750007, 12.415380859375006], + [109.1986328125, 11.724853515625014], + [109.03964843750012, 11.592675781249994], + [108.98671875, 11.336376953124997], + [108.09492187500004, 10.897265624999989], + [108.0013671875, 10.720361328125009], + [107.26152343750007, 10.39838867187504], + [107.00664062500002, 10.66054687499998], + [106.94746093750004, 10.400341796874997], + [106.72734375000007, 10.535644531250028], + [106.605859375, 10.46494140625002], + [106.74121093750003, 10.444384765625003], + [106.75742187500006, 10.295800781250023], + [106.46406250000004, 10.298291015624997], + [106.78525390625012, 10.116455078124986], + [106.59560546875005, 9.859863281250028], + [106.1364257812501, 10.221679687500014], + [106.56435546875005, 9.715625], + [106.48408203125004, 9.559423828125006], + [105.83095703125005, 10.000732421875028], + [106.15859375, 9.59414062499998], + [106.16835937500005, 9.396728515625], + [105.50097656249997, 9.093212890624983], + [105.11435546875006, 8.629199218750031], + [104.77041015625, 8.59765625], + [104.89628906250007, 8.746630859374974], + [104.81855468750004, 8.801855468750034], + [104.84521484375003, 9.606152343750026], + [105.08447265625003, 9.99570312499999], + [104.8019531250001, 10.202734374999977], + [104.66347656250005, 10.169921875000043], + [104.42636718750006, 10.411230468749991], + [104.85058593749997, 10.534472656249974], + [105.04638671874997, 10.701660156250014], + [105.04570312500002, 10.911376953125014], + [105.3146484375001, 10.845166015625026], + [105.40576171875003, 10.95161132812504], + [105.75507812500004, 10.989990234375043], + [105.85332031250007, 10.86357421874996], + [106.16396484375005, 10.794921875], + [106.16093750000002, 11.037109375000057], + [105.85605468750006, 11.294287109375048], + [105.85146484375005, 11.635009765625], + [106.0060546875001, 11.758007812500011], + [106.39921875000007, 11.687011718750028], + [106.41386718750002, 11.9484375], + [106.70009765625, 11.979296874999974], + [107.21210937500004, 12.30400390624996], + [107.39335937500002, 12.260498046874972], + [107.50644531250006, 12.364550781250031], + [107.47539062500002, 13.030371093749963], + [107.60546874999997, 13.437792968750017], + [107.3314453125, 14.126611328125009], + [107.51943359375005, 14.705078125], + [107.51376953125012, 14.817382812500057], + [107.52451171875012, 14.871826171875043], + [107.50468750000007, 14.915917968749966], + [107.48037109375, 14.979882812500037], + [107.55527343750006, 15.057031250000023], + [107.58964843750002, 15.118457031250017], + [107.63369140625005, 15.18984375], + [107.653125, 15.255224609374991], + [107.62167968750006, 15.309863281250017], + [107.56425781250002, 15.391601562499972], + [107.45957031250012, 15.4658203125], + [107.33876953125, 15.560498046875011], + [107.27939453125006, 15.618701171875045], + [107.16591796875005, 15.802490234375028], + [107.1888671875, 15.838623046875], + [107.36064453125002, 15.921728515624977], + [107.3919921875, 15.951660156250028], + [107.39638671875, 16.04301757812499], + [106.93066406249997, 16.353125], + [106.8927734375001, 16.396533203125074], + [106.85107421875003, 16.515625], + [106.83242187500005, 16.526269531250023], + [106.79160156250006, 16.490332031250006], + [106.73955078125007, 16.452539062500023], + [106.6564453125001, 16.49262695312501], + [106.54619140625002, 16.650732421874977], + [106.53369140625003, 16.821044921875057], + [106.52597656250006, 16.876611328124994], + [106.50224609375002, 16.95410156249997], + [106.26953125000003, 17.21679687500003], + [106.00625, 17.415283203125057], + [105.69140625000003, 17.737841796875045], + [105.58847656250012, 17.983691406250045], + [105.51855468749997, 18.077441406250045], + [105.45820312500004, 18.15429687499997], + [105.11455078125002, 18.40527343750003], + [105.08701171875006, 18.496240234374994], + [105.14541015625, 18.616796875000063], + [105.14648437500003, 18.650976562500006], + [103.89160156250003, 19.304980468750017], + [103.89638671875, 19.339990234375023], + [103.93203125, 19.366064453125006], + [104.0275390625001, 19.42045898437499], + [104.062890625, 19.48256835937505], + [104.05156250000007, 19.564160156249955], + [104.01347656250007, 19.64648437500003], + [104.03203125000002, 19.675146484375006], + [104.06279296875007, 19.678417968749983], + [104.25986328125006, 19.685498046874983], + [104.5462890625, 19.61054687500001], + [104.58789062500003, 19.61875], + [104.74316406250003, 19.754736328124977], + [104.80175781249997, 19.83613281250004], + [104.81513671875004, 19.90400390625001], + [104.9279296875001, 20.01811523437499], + [104.92919921875003, 20.082812500000017], + [104.88867187500003, 20.169091796875023], + [104.84785156250004, 20.202441406250045], + [104.69873046875003, 20.20532226562503], + [104.67695312500004, 20.224707031249977], + [104.66191406250007, 20.28901367187501], + [104.65644531250004, 20.32851562499999], + [104.6188476562501, 20.37451171875003], + [104.49619140625006, 20.413671875], + [104.39218750000012, 20.424755859374955], + [104.36777343750012, 20.44140624999997], + [104.40781250000012, 20.48574218750005], + [104.47861328125006, 20.529589843750017], + [104.53271484374997, 20.55488281250001], + [104.58320312500004, 20.646679687499955], + [104.34960937499997, 20.821093750000074], + [104.19531249999997, 20.913964843749966], + [104.10136718750002, 20.94550781250001], + [103.63505859375007, 20.697070312500017], + [103.46357421875004, 20.779833984375017], + [103.21074218750002, 20.840625], + [103.10449218749997, 20.891650390625045], + [102.88378906250003, 21.202587890624983], + [102.85117187500006, 21.26591796874999], + [102.94960937500005, 21.681347656249983], + [102.84521484374997, 21.73476562500005], + [102.81591796874997, 21.807373046875], + [102.7982421875, 21.797949218750034], + [102.77109375000006, 21.709667968749983], + [102.73857421875002, 21.67792968750001], + [102.66201171875005, 21.67602539062497], + [102.58251953125003, 21.90429687500003], + [102.12744140624997, 22.379199218750045], + [102.1759765625001, 22.414648437500006], + [102.2370117187501, 22.466015624999983], + [102.40644531250004, 22.70800781249997], + [102.47089843750004, 22.75092773437501], + [102.98193359374997, 22.4482421875], + [103.32666015625003, 22.769775390625057], + [103.49296875000007, 22.587988281250034], + [103.62021484375006, 22.782031250000045], + [103.94150390625006, 22.540087890625045], + [104.14306640624997, 22.800146484375006], + [104.37177734375004, 22.704052734374983], + [104.68730468750002, 22.822216796874983], + [104.86474609375003, 23.136376953125023], + [105.27539062500003, 23.34521484375003], + [105.8429687500001, 22.922802734374955], + [106.14843749999997, 22.970068359375006], + [106.2790039062501, 22.857470703125045], + [106.54179687500007, 22.908349609375023], + [106.78027343749997, 22.778906250000034], + [106.55039062500006, 22.501367187499994], + [106.66357421875003, 21.97890625000005], + [106.97099609375002, 21.923925781250034], + [107.35117187500012, 21.60888671874997], + [107.75927734374997, 21.655029296875057], + [107.97265624999997, 21.507958984375023] + ] + ] + ] + }, + "properties": { "name": "Vietnam", "childNum": 4 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [169.49130859375006, -19.54013671875002], + [169.34726562500006, -19.623535156249957], + [169.2174804687501, -19.476367187500003], + [169.24746093750005, -19.3447265625], + [169.49130859375006, -19.54013671875002] + ] + ], + [ + [ + [169.334375, -18.940234375000017], + [168.98691406250006, -18.87128906250001], + [169.01582031250004, -18.64375], + [169.14384765625002, -18.63105468750001], + [169.334375, -18.940234375000017] + ] + ], + [ + [ + [168.44580078124997, -17.54218750000004], + [168.58496093750003, -17.695898437500006], + [168.52460937500004, -17.798046875000026], + [168.15820312500003, -17.710546874999963], + [168.2731445312501, -17.552246093749957], + [168.44580078124997, -17.54218750000004] + ] + ], + [ + [ + [168.44677734375003, -16.778808593749957], + [168.18144531250002, -16.804003906250017], + [168.13535156250006, -16.636914062499997], + [168.44677734375003, -16.778808593749957] + ] + ], + [ + [ + [168.29667968750007, -16.33652343749999], + [167.92900390625002, -16.22871093749997], + [168.16386718750002, -16.081640625000034], + [168.29667968750007, -16.33652343749999] + ] + ], + [ + [ + [167.4125, -16.095898437499997], + [167.83662109375004, -16.449707031249957], + [167.44931640625012, -16.554980468750003], + [167.34921875000006, -16.15449218750004], + [167.15146484375006, -16.080468749999966], + [167.19951171875002, -15.885058593750031], + [167.33574218750007, -15.916699218749997], + [167.4125, -16.095898437499997] + ] + ], + [ + [ + [167.9113281250001, -15.435937500000023], + [167.67421875, -15.4515625], + [168.00253906250012, -15.283203124999986], + [167.9113281250001, -15.435937500000023] + ] + ], + [ + [ + [166.74580078125004, -14.826855468750011], + [166.81015625000012, -15.15742187500004], + [167.0755859375, -14.935644531249977], + [167.20078125000012, -15.443066406249969], + [167.0939453125001, -15.580859374999974], + [166.75830078125003, -15.631152343750003], + [166.63105468750004, -15.406054687499974], + [166.56738281250003, -14.641796874999969], + [166.74580078125004, -14.826855468750011] + ] + ], + [ + [ + [167.58486328125, -14.260937500000011], + [167.43027343750012, -14.294921875], + [167.41074218750006, -14.19746093750004], + [167.50644531250012, -14.142187499999977], + [167.58486328125, -14.260937500000011] + ] + ], + [ + [ + [167.48886718750006, -13.907226562499972], + [167.3917968750001, -13.788378906250017], + [167.48105468750006, -13.709472656250014], + [167.48886718750006, -13.907226562499972] + ] + ] + ] + }, + "properties": { "name": "Vanuatu", "childNum": 10 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [-171.4541015625, -14.04648437500002], + [-171.9119140625, -14.001660156250026], + [-172.04589843750003, -13.857128906249983], + [-171.60390624999997, -13.879199218750045], + [-171.4541015625, -14.04648437500002] + ] + ], + [ + [ + [-172.33349609375, -13.46523437499999], + [-172.17685546874998, -13.68466796875002], + [-172.224951171875, -13.804296874999963], + [-172.535693359375, -13.791699218749983], + [-172.77851562499998, -13.516796875000011], + [-172.33349609375, -13.46523437499999] + ] + ] + ] + }, + "properties": { "name": "Samoa", "childNum": 2 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [53.76318359374997, 12.636816406249991], + [54.18740234375005, 12.664013671875026], + [54.511132812499994, 12.552783203125017], + [54.12949218750006, 12.360644531250045], + [53.71884765625006, 12.318994140624994], + [53.31582031250005, 12.533154296875011], + [53.53496093750002, 12.715771484374997], + [53.76318359374997, 12.636816406249991] + ] + ], + [ + [ + [42.75585937500003, 13.70429687500004], + [42.689746093750074, 13.673632812500017], + [42.7941406250001, 13.766113281250028], + [42.75585937500003, 13.70429687500004] + ] + ], + [ + [ + [42.787402343750074, 13.971484375000031], + [42.69404296875004, 14.007910156249991], + [42.76210937500005, 14.067480468750048], + [42.787402343750074, 13.971484375000031] + ] + ], + [ + [ + [53.08564453125004, 16.648388671874955], + [52.327734375, 16.293554687500063], + [52.17402343750004, 15.956835937500017], + [52.2174804687501, 15.655517578125], + [51.3224609375001, 15.22626953125004], + [49.34990234375002, 14.637792968749977], + [48.66835937499999, 14.050146484374977], + [47.9899414062501, 14.048095703125], + [47.40771484374997, 13.661621093750057], + [46.78886718750002, 13.465576171874986], + [45.65732421875006, 13.338720703124991], + [45.03867187500006, 12.815869140624969], + [44.617773437500006, 12.817236328124977], + [44.00585937499997, 12.607666015625], + [43.634375, 12.744482421874991], + [43.487597656250074, 12.69882812500002], + [43.23193359375003, 13.267089843750057], + [43.2824218750001, 13.692529296875037], + [43.08906250000004, 14.010986328125], + [42.93642578125005, 14.938574218749963], + [42.85566406250004, 15.132958984375037], + [42.65781250000006, 15.232812500000051], + [42.79902343750004, 15.326269531249991], + [42.71718750000005, 15.654638671875006], + [42.83964843750002, 16.032031250000074], + [42.79931640624997, 16.37177734375001], + [43.16503906249997, 16.689404296874955], + [43.19091796875003, 17.359375], + [43.41796875000003, 17.516259765625023], + [43.91699218749997, 17.32470703124997], + [45.14804687500006, 17.427441406249955], + [45.5353515625001, 17.30205078124999], + [46.72763671875006, 17.26557617187501], + [46.97568359375006, 16.953466796875034], + [47.14355468749997, 16.946679687499966], + [47.44179687499999, 17.111865234375045], + [47.57958984374997, 17.448339843750034], + [48.17216796875002, 18.156933593749983], + [49.04199218750003, 18.58178710937503], + [51.977636718750006, 18.996142578125074], + [53.08564453125004, 16.648388671874955] + ] + ] + ] + }, + "properties": { "name": "Yemen", "childNum": 4 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [37.85693359375003, -46.94423828124998], + [37.5900390625001, -46.90800781250006], + [37.78955078124997, -46.8375], + [37.85693359375003, -46.94423828124998] + ] + ], + [ + [ + [31.799609375000017, -23.8921875], + [31.98583984374997, -24.460644531249983], + [31.921679687500017, -25.96875], + [31.335156250000097, -25.755566406249997], + [31.207324218750074, -25.843359375000034], + [31.08808593750004, -25.980664062500026], + [30.803320312500006, -26.41347656250001], + [30.806738281250006, -26.78525390624999], + [30.88330078124997, -26.792382812500023], + [30.938085937500006, -26.91582031250003], + [31.06337890625005, -27.1123046875], + [31.274023437500063, -27.23837890625002], + [31.469531250000017, -27.29550781250002], + [31.74257812500005, -27.309960937500037], + [31.95839843750005, -27.305859375], + [31.946093750000017, -27.173632812499974], + [31.96718750000005, -26.96064453125001], + [31.994726562500006, -26.817480468749977], + [32.024804687499994, -26.81113281250002], + [32.112890625, -26.83945312500002], + [32.19960937499999, -26.833496093749957], + [32.35351562499997, -26.861621093750003], + [32.7765625000001, -26.850976562499966], + [32.88613281250005, -26.849316406249983], + [32.53476562500006, -28.19970703125003], + [32.285742187500006, -28.62148437499998], + [31.335156250000097, -29.378125], + [29.97119140625003, -31.322070312500017], + [28.449414062500068, -32.62460937499999], + [27.077441406250074, -33.52119140625004], + [26.429492187500045, -33.75957031250002], + [25.80585937500001, -33.737109374999974], + [25.574218750000057, -34.03535156249998], + [25.00292968750003, -33.97363281250003], + [24.8271484375, -34.16894531250003], + [24.595507812500074, -34.17451171875], + [23.697851562500063, -33.99277343750002], + [23.268164062500006, -34.08115234374999], + [22.553808593750063, -34.01005859374999], + [22.24550781250005, -34.06914062500003], + [21.788964843750023, -34.37265624999996], + [20.529882812500034, -34.4630859375], + [20.020605468750006, -34.785742187500006], + [19.298242187500023, -34.61503906249996], + [19.330761718750068, -34.49238281250001], + [19.098339843750068, -34.350097656249986], + [18.831347656250017, -34.36406249999999], + [18.75214843750004, -34.08261718750002], + [18.50039062499999, -34.10927734375004], + [18.46162109375001, -34.346875], + [18.35205078124997, -34.1884765625], + [18.43300781250005, -33.71728515625003], + [17.851074218750057, -32.82744140625002], + [17.96523437500005, -32.70859374999996], + [18.125, -32.74912109374996], + [18.325292968750034, -32.50498046874996], + [18.21083984375008, -31.74248046874996], + [17.34707031250005, -30.44482421875], + [16.95, -29.40341796875002], + [16.739453124999983, -29.009375], + [16.447558593750045, -28.61757812499998], + [16.755761718750023, -28.45214843750003], + [16.7875, -28.39472656249997], + [16.81015625, -28.264550781249994], + [16.841210937500023, -28.21894531250004], + [16.875292968750045, -28.12792968749997], + [16.93330078125004, -28.06962890624999], + [17.05625, -28.03105468750003], + [17.1884765625, -28.13251953125001], + [17.358691406250017, -28.269433593750023], + [17.44794921875001, -28.698144531249966], + [18.310839843750017, -28.88623046875], + [19.16171875, -28.93876953124996], + [19.245800781250068, -28.90166015625003], + [19.31269531250004, -28.733300781250023], + [19.539843750000017, -28.574609375000023], + [19.98046875, -28.45126953125002], + [19.98046875, -28.310351562500003], + [19.98046875, -24.77675781249998], + [20.430664062500057, -25.14707031250002], + [20.79316406250001, -25.915625], + [20.641406250000017, -26.7421875], + [20.739843749999977, -26.84882812499997], + [21.694726562500023, -26.840917968749963], + [21.738085937500045, -26.806835937500026], + [21.788281250000068, -26.710058593750034], + [22.01093750000004, -26.635839843750006], + [22.090917968749977, -26.580175781250034], + [22.217578125000045, -26.38886718749997], + [22.47089843750004, -26.219042968750003], + [22.548632812500074, -26.178417968749997], + [22.59765625000003, -26.13271484375001], + [22.878808593750023, -25.457910156250023], + [23.148730468750017, -25.288671875], + [23.389257812500006, -25.291406250000023], + [23.89375, -25.600878906250017], + [23.96953124999999, -25.62607421874999], + [24.192968750000034, -25.632910156249963], + [24.33056640625, -25.742871093749983], + [25.21337890625, -25.75625], + [25.518164062500006, -25.66279296875001], + [25.91210937499997, -24.747460937499966], + [26.031835937500034, -24.70244140625003], + [26.130859375000057, -24.671484375000034], + [26.39716796875004, -24.61357421874996], + [26.451757812500063, -24.582714843749983], + [26.835058593750063, -24.240820312499963], + [27.085546875000034, -23.577929687500003], + [27.7685546875, -23.14892578125], + [27.812597656250006, -23.108007812500006], + [28.210156249999983, -22.693652343749974], + [28.83984375000003, -22.480859374999966], + [28.94580078125003, -22.39511718749999], + [29.013476562500045, -22.27841796875002], + [29.129882812500057, -22.21328125], + [29.364843750000063, -22.19394531250005], + [29.37744140625003, -22.19277343749998], + [29.66308593749997, -22.146289062500017], + [29.90234375000003, -22.184179687500006], + [30.19042968750003, -22.291113281250034], + [30.460156250000097, -22.329003906250023], + [30.71162109375004, -22.297851562499986], + [31.07343750000004, -22.30781249999997], + [31.19726562499997, -22.344921874999983], + [31.287890625000074, -22.402050781249983], + [31.54560546875004, -23.48232421874998], + [31.799609375000017, -23.8921875] + ], + [ + [27.19355468750001, -29.94130859375001], + [27.364062500000017, -30.27919921875001], + [27.753125, -30.6], + [28.05683593750001, -30.63105468750001], + [28.128710937500017, -30.52509765625001], + [28.39208984375003, -30.14755859375002], + [28.646875, -30.1265625], + [29.09804687500005, -29.919042968750006], + [29.142187500000063, -29.70097656249999], + [29.293554687500006, -29.56689453125003], + [29.348828125000097, -29.441992187499977], + [29.38671874999997, -29.319726562500023], + [29.301367187500006, -29.08984375], + [28.625781250000017, -28.581738281250054], + [28.583398437499994, -28.59414062499999], + [28.471875, -28.615820312499977], + [28.23261718750004, -28.701269531249977], + [28.084375, -28.779980468750026], + [27.95986328125008, -28.87333984375003], + [27.73554687500004, -28.940039062500034], + [27.294531250000063, -29.519335937500017], + [27.056933593750074, -29.62558593749999], + [27.19355468750001, -29.94130859375001] + ] + ] + ] + }, + "properties": { "name": "South Africa", "childNum": 2 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [33.148046875, -9.603515625], + [33.25, -9.759570312500003], + [33.35097656250002, -9.862207031250009], + [33.33710937500001, -9.954003906250009], + [33.3115234375, -10.037988281250009], + [33.52890625, -10.234667968750003], + [33.53759765625, -10.3515625], + [33.5537109375, -10.391308593750011], + [33.66152343750002, -10.553125], + [33.29277343750002, -10.85234375], + [33.37978515625002, -11.157910156250011], + [33.26835937500002, -11.40390625], + [33.23271484375002, -11.417675781250011], + [33.22636718750002, -11.534863281250011], + [33.30390625000001, -11.690820312500009], + [33.25234375000002, -12.112597656250003], + [33.34013671875002, -12.308300781250011], + [33.512304687500006, -12.347753906250006], + [32.975195312500006, -12.701367187500011], + [32.96757812500002, -13.225], + [32.67041015625, -13.590429687500006], + [32.797460937500006, -13.6884765625], + [32.98125, -14.009375], + [33.148046875, -13.94091796875], + [33.201757812500006, -14.013378906250011], + [30.231835937500023, -14.990332031250006], + [30.39609375, -15.64306640625], + [29.4873046875, -15.69677734375], + [28.9130859375, -15.98779296875], + [28.760546875000017, -16.53212890625001], + [27.932226562500006, -16.89619140625001], + [27.020800781250017, -17.95839843750001], + [26.779882812500006, -18.04150390625], + [26.333398437500023, -17.929296875], + [25.995898437500017, -17.969824218750006], + [25.2587890625, -17.793554687500006], + [25.001757812500017, -17.56855468750001], + [24.73291015625, -17.51777343750001], + [24.27490234375, -17.481054687500006], + [23.380664062500017, -17.640625], + [22.193945312500006, -16.628125], + [21.979785156250017, -15.95556640625], + [21.979394531250023, -14.440527343750006], + [21.979296875000017, -14.11962890625], + [21.979101562500006, -13.798730468750009], + [21.978906250000023, -13.0009765625], + [22.209570312500006, -13.0009765625], + [23.843164062500023, -13.0009765625], + [23.962988281250006, -12.988476562500011], + [23.882421875, -12.799023437500011], + [23.886523437500017, -12.743261718750006], + [23.909375, -12.636132812500009], + [23.98388671875, -11.725], + [23.96650390625001, -10.871777343750011], + [24.36572265625, -11.1298828125], + [24.3779296875, -11.417089843750006], + [25.28876953125001, -11.21240234375], + [25.349414062500017, -11.623046875], + [26.025976562500006, -11.89013671875], + [26.824023437500017, -11.965234375], + [27.1591796875, -11.579199218750006], + [27.573828125, -12.22705078125], + [28.412890625000017, -12.51806640625], + [28.550878906250006, -12.836132812500011], + [28.730078125, -12.925488281250011], + [29.014257812500006, -13.368847656250011], + [29.20185546875001, -13.398339843750009], + [29.55419921875, -13.248925781250009], + [29.775195312500017, -13.438085937500006], + [29.79511718750001, -12.155468750000011], + [29.508203125000023, -12.228222656250011], + [29.48554687500001, -12.41845703125], + [29.064355468750023, -12.348828125000011], + [28.482519531250006, -11.812109375], + [28.383398437500006, -11.566699218750003], + [28.6455078125, -10.550195312500009], + [28.60419921875001, -9.678808593750006], + [28.400683593750017, -9.224804687500011], + [28.869531250000023, -8.785839843750011], + [28.89814453125001, -8.485449218750006], + [30.75117187500001, -8.193652343750003], + [30.830664062500006, -8.385546875], + [30.891992187500023, -8.473730468750006], + [30.968359375, -8.550976562500011], + [31.07636718750001, -8.611914062500006], + [31.3505859375, -8.60703125], + [31.44921875, -8.65390625], + [31.53486328125001, -8.71328125], + [31.55625, -8.80546875], + [31.673632812500017, -8.908789062500006], + [31.91865234375001, -8.9421875], + [31.921875, -9.019433593750009], + [31.94257812500001, -9.054003906250003], + [32.75664062500002, -9.322265625], + [32.919921875, -9.407421875000011], + [32.99599609375002, -9.622851562500003], + [33.148046875, -9.603515625] + ] + ] + }, + "properties": { "name": "Zambia", "childNum": 1 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [31.287890625000017, -22.40205078125001], + [31.07343750000001, -22.30781250000001], + [30.71162109375001, -22.2978515625], + [30.46015625000001, -22.32900390625001], + [30.1904296875, -22.291113281250006], + [29.90234375, -22.184179687500006], + [29.6630859375, -22.146289062500003], + [29.37744140625, -22.19277343750001], + [29.36484375, -22.193945312500006], + [29.315234375000017, -22.15771484375], + [29.237207031250023, -22.07949218750001], + [29.042382812500023, -22.018359375], + [29.02558593750001, -21.796875], + [28.014062500000023, -21.55419921875], + [27.66943359375, -21.064257812500003], + [27.679296875, -20.503027343750006], + [27.28076171875, -20.47871093750001], + [27.17822265625, -20.10097656250001], + [26.168066406250006, -19.53828125000001], + [25.939355468750023, -18.93867187500001], + [25.242285156250006, -17.969042968750003], + [25.2587890625, -17.793554687500006], + [25.995898437500017, -17.969824218750006], + [26.333398437500023, -17.929296875], + [26.779882812500006, -18.04150390625], + [27.020800781250017, -17.95839843750001], + [27.932226562500006, -16.89619140625001], + [28.760546875000017, -16.53212890625001], + [28.9130859375, -15.98779296875], + [29.4873046875, -15.69677734375], + [30.39609375, -15.64306640625], + [30.437792968750017, -15.995312500000011], + [31.236230468750023, -16.02363281250001], + [31.939843750000023, -16.428808593750006], + [32.94804687500002, -16.71230468750001], + [32.87626953125002, -16.88359375], + [32.99306640625002, -18.35957031250001], + [32.69970703125, -18.94091796875], + [32.84980468750001, -19.10439453125001], + [32.77763671875002, -19.388769531250006], + [32.992773437500006, -19.98486328125], + [32.49238281250001, -20.659765625], + [32.353613281250006, -21.136523437500003], + [32.429785156250006, -21.29707031250001], + [31.429492187500017, -22.298828125], + [31.287890625000017, -22.40205078125001] + ] + ] + }, + "properties": { "name": "Zimbabwe", "childNum": 1 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [74.00809389139292, 33.25375789331485], + [73.19660141888893, 33.898124784580936], + [73.13410859949555, 34.82510160558277], + [72.31128647748268, 35.77290936638241], + [73.08203125000107, 36.43949943991182], + [73.08961802927895, 36.86435907947333], + [73.116796875, 36.868554687499994], + [74.03886718750002, 36.825732421874996], + [74.54140625000002, 37.02216796875], + [74.69218750000002, 37.0357421875], + [74.8892578125, 36.952441406249996], + [74.94912109375002, 36.968359375], + [75.05390625000001, 36.987158203125], + [75.14521484375001, 36.9732421875], + [75.3466796875, 36.913476562499994], + [75.37685546875002, 36.883691406249994], + [75.42421875000002, 36.738232421875], + [75.46025390625002, 36.725048828125], + [75.57373046875, 36.759326171874996], + [75.66718750000001, 36.741992187499996], + [75.77216796875001, 36.694921875], + [75.84023437500002, 36.649707031249996], + [75.88496093750001, 36.600732421874994], + [75.93300781250002, 36.52158203125], + [75.95185546875001, 36.45810546875], + [75.97441406250002, 36.382421875], + [75.91230468750001, 36.048974609374994], + [76.07089843750003, 35.9830078125], + [76.14785156250002, 35.829003906249994], + [76.17783203125003, 35.810546875], + [76.25166015625001, 35.8109375], + [76.3857421875, 35.837158203125], + [76.50205078125003, 35.878222656249996], + [76.55126953125, 35.887060546875], + [76.5634765625, 35.772998046874996], + [76.6318359375, 35.729394531249994], + [76.7275390625, 35.678662109375], + [76.76689453124999, 35.66171875], + [76.81279296874999, 35.571826171874996], + [76.88222656250002, 35.4357421875], + [76.927734375, 35.346630859375], + [77.04863281249999, 35.109912109374996], + [77.00087890625002, 34.991992187499996], + [76.78291015625001, 34.900195312499996], + [76.75751953125001, 34.877832031249994], + [76.7490234375, 34.847558593749994], + [76.6962890625, 34.786914062499996], + [76.59443359375001, 34.73583984375], + [76.45673828125001, 34.756103515625], + [76.17246093750003, 34.667724609375], + [76.041015625, 34.669921875], + [75.93828125000002, 34.612548828125], + [75.86210937500002, 34.56025390625], + [75.70917968750001, 34.503076171874994], + [74.300390625, 34.765380859375], + [74.17197265625, 34.7208984375], + [74.05585937500001, 34.6806640625], + [73.96123046875002, 34.653466796874994], + [73.79453125, 34.378222656249996], + [73.80996093750002, 34.325341796874994], + [73.92460937500002, 34.287841796875], + [73.97236328125001, 34.236621093749996], + [73.9794921875, 34.191308593749994], + [73.90390625, 34.1080078125], + [73.94990234375001, 34.018798828125], + [74.24648437500002, 33.990185546875], + [73.97646484375002, 33.7212890625], + [74.15, 33.506982421874994], + [74.00809389139292, 33.25375789331485] + ] + ] + }, + "properties": { "name": "", "childNum": 1 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [78.49194250885338, 32.53122786149202], + [78.10154031239509, 32.87658365066666], + [77.71342088235082, 32.6917648744551], + [77.06655516561037, 33.301666835953235], + [76.62299010270264, 33.32014871357439], + [76.32728006076415, 32.87658365066666], + [75.73585997688717, 32.78417426256088], + [75.62496871116024, 32.28516356678968], + [75.32221348233018, 32.28516356678968], + [74.98730468749997, 32.46220703124996], + [74.78886718750013, 32.4578125], + [74.6857421875001, 32.493798828124994], + [74.66328125000004, 32.75766601562495], + [74.63242187499995, 32.770898437500136], + [74.58828125000011, 32.7532226562501], + [74.35458984375012, 32.76870117187505], + [74.30546875000007, 32.81044921875002], + [74.30361328125005, 32.991796875000034], + [73.98984375000006, 33.22119140625006], + [74.15, 33.506982421874994], + [73.97646484375016, 33.72128906249998], + [74.24648437500011, 33.99018554687504], + [73.9499023437501, 34.018798828125], + [73.90390625000012, 34.10800781250006], + [73.97949218750009, 34.191308593749966], + [73.97236328125004, 34.23662109374996], + [73.92460937500007, 34.287841796875114], + [73.80996093750016, 34.32534179687511], + [73.79453125000006, 34.378222656250045], + [73.96123046875007, 34.653466796874994], + [74.05585937500015, 34.68066406250003], + [74.17197265624995, 34.72089843750004], + [74.30039062500006, 34.76538085937506], + [75.70917968750004, 34.50307617187508], + [75.86210937500002, 34.56025390625001], + [75.93828125000019, 34.612548828125], + [76.04101562500014, 34.66992187499997], + [76.17246093750006, 34.66772460937506], + [76.4567382812501, 34.756103515625114], + [76.5944335937501, 34.73583984375006], + [76.69628906249997, 34.78691406249999], + [76.74902343750014, 34.84755859375008], + [76.7575195312501, 34.87783203125005], + [76.7829101562501, 34.90019531249999], + [77.00087890625011, 34.99199218750002], + [77.03066406250011, 35.06235351562498], + [77.04863281250007, 35.109912109375074], + [77.42343749999995, 35.30258789062506], + [77.57158203125002, 35.37875976562495], + [77.69697265625015, 35.443261718750136], + [77.79941406250006, 35.49589843750002], + [78.0426757812501, 35.4797851562501], + [78.07578125000006, 35.13491210937502], + [78.15849609375002, 34.94648437499998], + [78.32695312500007, 34.60639648437498], + [78.86484375000006, 34.39033203125001], + [78.93642578125, 34.35195312500002], + [78.97060546875011, 34.22822265625004], + [78.72666015625006, 34.013378906249955], + [78.78378906250006, 33.80878906250004], + [78.86503906250002, 33.43110351562501], + [78.94843750000004, 33.346533203125006], + [79.1125, 33.22626953125001], + [79.13515625000005, 33.17192382812496], + [79.10283203125007, 33.05253906249996], + [79.14550781250003, 33.00146484375006], + [79.16992187500003, 32.497216796874994], + [78.91894531249997, 32.3582031250001], + [78.75351562500012, 32.49926757812506], + [78.73671875, 32.55839843750002], + [78.49194250885338, 32.53122786149202] + ] + ] + ] + }, + "properties": { "name": "", "childNum": 1 } + } + ] +} diff --git a/packages/admin-web-angular/src/assets/skins/lightgray/content.inline.min.css b/packages/admin-web-angular/src/assets/skins/lightgray/content.inline.min.css new file mode 100644 index 0000000..8c59be3 --- /dev/null +++ b/packages/admin-web-angular/src/assets/skins/lightgray/content.inline.min.css @@ -0,0 +1,166 @@ +.mce-content-body .mce-reset { + margin: 0; + padding: 0; + border: 0; + outline: 0; + vertical-align: top; + background: transparent; + text-decoration: none; + color: black; + font-family: Arial; + font-size: 11px; + text-shadow: none; + float: none; + position: static; + width: auto; + height: auto; + white-space: nowrap; + cursor: inherit; + line-height: normal; + font-weight: normal; + text-align: left; + -webkit-tap-highlight-color: transparent; + -moz-box-sizing: content-box; + -webkit-box-sizing: content-box; + box-sizing: content-box; + direction: ltr; + max-width: none; +} +.mce-object { + border: 1px dotted #3a3a3a; + background: #d5d5d5 url(img/object.gif) no-repeat center; +} +.mce-preview-object { + display: inline-block; + position: relative; + margin: 0 2px 0 2px; + line-height: 0; + border: 1px solid gray; +} +.mce-preview-object[data-mce-selected='2'] .mce-shim { + display: none; +} +.mce-preview-object .mce-shim { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7); +} +figure.align-left { + float: left; +} +figure.align-right { + float: right; +} +figure.image.align-center { + display: table; + margin-left: auto; + margin-right: auto; +} +figure.image { + display: inline-block; + border: 1px solid gray; + margin: 0 2px 0 1px; + background: #f5f2f0; +} +figure.image img { + margin: 8px 8px 0 8px; +} +figure.image figcaption { + margin: 6px 8px 6px 8px; + text-align: center; +} +.mce-toc { + border: 1px solid gray; +} +.mce-toc h2 { + margin: 4px; +} +.mce-toc li { + list-style-type: none; +} +.mce-pagebreak { + cursor: default; + display: block; + border: 0; + width: 100%; + height: 5px; + border: 1px dashed #666; + margin-top: 15px; + page-break-before: always; +} +@media print { + .mce-pagebreak { + border: 0; + } +} +.mce-item-anchor { + cursor: default; + display: inline-block; + -webkit-user-select: all; + -webkit-user-modify: read-only; + -moz-user-select: all; + -moz-user-modify: read-only; + user-select: all; + user-modify: read-only; + width: 9px !important; + height: 9px !important; + border: 1px dotted #3a3a3a; + background: #d5d5d5 url(img/anchor.gif) no-repeat center; +} +.mce-nbsp, +.mce-shy { + background: #aaa; +} +.mce-shy::after { + content: '-'; +} +hr { + cursor: default; +} +.mce-match-marker { + background: #aaa; + color: #fff; +} +.mce-match-marker-selected { + background: #3399ff; + color: #fff; +} +.mce-spellchecker-word { + border-bottom: 2px solid #f00; + cursor: default; +} +.mce-spellchecker-grammar { + border-bottom: 2px solid #008000; + cursor: default; +} +.mce-item-table, +.mce-item-table td, +.mce-item-table th, +.mce-item-table caption { + border: 1px dashed #bbb; +} +td[data-mce-selected], +th[data-mce-selected] { + background-color: #3399ff !important; +} +.mce-edit-focus { + outline: 1px dotted #333; +} +.mce-content-body *[contentEditable='false'] *[contentEditable='true']:focus { + outline: 2px solid #2d8ac7; +} +.mce-content-body *[contentEditable='false'] *[contentEditable='true']:hover { + outline: 2px solid #7acaff; +} +.mce-content-body *[contentEditable='false'][data-mce-selected] { + outline: 2px solid #2d8ac7; +} +.mce-resize-bar-dragging { + background-color: blue; + opacity: 0.25; + filter: alpha(opacity=25); + zoom: 1; +} diff --git a/packages/admin-web-angular/src/assets/skins/lightgray/content.min.css b/packages/admin-web-angular/src/assets/skins/lightgray/content.min.css new file mode 100644 index 0000000..1850ffa --- /dev/null +++ b/packages/admin-web-angular/src/assets/skins/lightgray/content.min.css @@ -0,0 +1,185 @@ +body { + background-color: #ffffff; + color: #000000; + font-family: Verdana, Arial, Helvetica, sans-serif; + font-size: 14px; + scrollbar-3dlight-color: #f0f0ee; + scrollbar-arrow-color: #676662; + scrollbar-base-color: #f0f0ee; + scrollbar-darkshadow-color: #dddddd; + scrollbar-face-color: #e0e0dd; + scrollbar-highlight-color: #f0f0ee; + scrollbar-shadow-color: #f0f0ee; + scrollbar-track-color: #f5f5f5; +} +td, +th { + font-family: Verdana, Arial, Helvetica, sans-serif; + font-size: 14px; +} +.mce-content-body .mce-reset { + margin: 0; + padding: 0; + border: 0; + outline: 0; + vertical-align: top; + background: transparent; + text-decoration: none; + color: black; + font-family: Arial; + font-size: 11px; + text-shadow: none; + float: none; + position: static; + width: auto; + height: auto; + white-space: nowrap; + cursor: inherit; + line-height: normal; + font-weight: normal; + text-align: left; + -webkit-tap-highlight-color: transparent; + -moz-box-sizing: content-box; + -webkit-box-sizing: content-box; + box-sizing: content-box; + direction: ltr; + max-width: none; +} +.mce-object { + border: 1px dotted #3a3a3a; + background: #d5d5d5 url(img/object.gif) no-repeat center; +} +.mce-preview-object { + display: inline-block; + position: relative; + margin: 0 2px 0 2px; + line-height: 0; + border: 1px solid gray; +} +.mce-preview-object[data-mce-selected='2'] .mce-shim { + display: none; +} +.mce-preview-object .mce-shim { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7); +} +figure.align-left { + float: left; +} +figure.align-right { + float: right; +} +figure.image.align-center { + display: table; + margin-left: auto; + margin-right: auto; +} +figure.image { + display: inline-block; + border: 1px solid gray; + margin: 0 2px 0 1px; + background: #f5f2f0; +} +figure.image img { + margin: 8px 8px 0 8px; +} +figure.image figcaption { + margin: 6px 8px 6px 8px; + text-align: center; +} +.mce-toc { + border: 1px solid gray; +} +.mce-toc h2 { + margin: 4px; +} +.mce-toc li { + list-style-type: none; +} +.mce-pagebreak { + cursor: default; + display: block; + border: 0; + width: 100%; + height: 5px; + border: 1px dashed #666; + margin-top: 15px; + page-break-before: always; +} +@media print { + .mce-pagebreak { + border: 0; + } +} +.mce-item-anchor { + cursor: default; + display: inline-block; + -webkit-user-select: all; + -webkit-user-modify: read-only; + -moz-user-select: all; + -moz-user-modify: read-only; + user-select: all; + user-modify: read-only; + width: 9px !important; + height: 9px !important; + border: 1px dotted #3a3a3a; + background: #d5d5d5 url(img/anchor.gif) no-repeat center; +} +.mce-nbsp, +.mce-shy { + background: #aaa; +} +.mce-shy::after { + content: '-'; +} +hr { + cursor: default; +} +.mce-match-marker { + background: #aaa; + color: #fff; +} +.mce-match-marker-selected { + background: #3399ff; + color: #fff; +} +.mce-spellchecker-word { + border-bottom: 2px solid #f00; + cursor: default; +} +.mce-spellchecker-grammar { + border-bottom: 2px solid #008000; + cursor: default; +} +.mce-item-table, +.mce-item-table td, +.mce-item-table th, +.mce-item-table caption { + border: 1px dashed #bbb; +} +td[data-mce-selected], +th[data-mce-selected] { + background-color: #3399ff !important; +} +.mce-edit-focus { + outline: 1px dotted #333; +} +.mce-content-body *[contentEditable='false'] *[contentEditable='true']:focus { + outline: 2px solid #2d8ac7; +} +.mce-content-body *[contentEditable='false'] *[contentEditable='true']:hover { + outline: 2px solid #7acaff; +} +.mce-content-body *[contentEditable='false'][data-mce-selected] { + outline: 2px solid #2d8ac7; +} +.mce-resize-bar-dragging { + background-color: blue; + opacity: 0.25; + filter: alpha(opacity=25); + zoom: 1; +} diff --git a/packages/admin-web-angular/src/assets/skins/lightgray/fonts/tinymce-small.eot b/packages/admin-web-angular/src/assets/skins/lightgray/fonts/tinymce-small.eot new file mode 100644 index 0000000..b144ba0 Binary files /dev/null and b/packages/admin-web-angular/src/assets/skins/lightgray/fonts/tinymce-small.eot differ diff --git a/packages/admin-web-angular/src/assets/skins/lightgray/fonts/tinymce-small.svg b/packages/admin-web-angular/src/assets/skins/lightgray/fonts/tinymce-small.svg new file mode 100644 index 0000000..b4ee6f4 --- /dev/null +++ b/packages/admin-web-angular/src/assets/skins/lightgray/fonts/tinymce-small.svg @@ -0,0 +1,63 @@ + + + +Generated by IcoMoon + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/admin-web-angular/src/assets/skins/lightgray/fonts/tinymce-small.ttf b/packages/admin-web-angular/src/assets/skins/lightgray/fonts/tinymce-small.ttf new file mode 100644 index 0000000..a983e2d Binary files /dev/null and b/packages/admin-web-angular/src/assets/skins/lightgray/fonts/tinymce-small.ttf differ diff --git a/packages/admin-web-angular/src/assets/skins/lightgray/fonts/tinymce-small.woff b/packages/admin-web-angular/src/assets/skins/lightgray/fonts/tinymce-small.woff new file mode 100644 index 0000000..d8962df Binary files /dev/null and b/packages/admin-web-angular/src/assets/skins/lightgray/fonts/tinymce-small.woff differ diff --git a/packages/admin-web-angular/src/assets/skins/lightgray/fonts/tinymce.eot b/packages/admin-web-angular/src/assets/skins/lightgray/fonts/tinymce.eot new file mode 100644 index 0000000..f99c13f Binary files /dev/null and b/packages/admin-web-angular/src/assets/skins/lightgray/fonts/tinymce.eot differ diff --git a/packages/admin-web-angular/src/assets/skins/lightgray/fonts/tinymce.svg b/packages/admin-web-angular/src/assets/skins/lightgray/fonts/tinymce.svg new file mode 100644 index 0000000..5727cea --- /dev/null +++ b/packages/admin-web-angular/src/assets/skins/lightgray/fonts/tinymce.svg @@ -0,0 +1,131 @@ + + + +Generated by IcoMoon + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/admin-web-angular/src/assets/skins/lightgray/fonts/tinymce.ttf b/packages/admin-web-angular/src/assets/skins/lightgray/fonts/tinymce.ttf new file mode 100644 index 0000000..16536bf Binary files /dev/null and b/packages/admin-web-angular/src/assets/skins/lightgray/fonts/tinymce.ttf differ diff --git a/packages/admin-web-angular/src/assets/skins/lightgray/fonts/tinymce.woff b/packages/admin-web-angular/src/assets/skins/lightgray/fonts/tinymce.woff new file mode 100644 index 0000000..74b50f4 Binary files /dev/null and b/packages/admin-web-angular/src/assets/skins/lightgray/fonts/tinymce.woff differ diff --git a/packages/admin-web-angular/src/assets/skins/lightgray/img/anchor.gif b/packages/admin-web-angular/src/assets/skins/lightgray/img/anchor.gif new file mode 100644 index 0000000..606348c Binary files /dev/null and b/packages/admin-web-angular/src/assets/skins/lightgray/img/anchor.gif differ diff --git a/packages/admin-web-angular/src/assets/skins/lightgray/img/loader.gif b/packages/admin-web-angular/src/assets/skins/lightgray/img/loader.gif new file mode 100644 index 0000000..c69e937 Binary files /dev/null and b/packages/admin-web-angular/src/assets/skins/lightgray/img/loader.gif differ diff --git a/packages/admin-web-angular/src/assets/skins/lightgray/img/object.gif b/packages/admin-web-angular/src/assets/skins/lightgray/img/object.gif new file mode 100644 index 0000000..cccd7f0 Binary files /dev/null and b/packages/admin-web-angular/src/assets/skins/lightgray/img/object.gif differ diff --git a/packages/admin-web-angular/src/assets/skins/lightgray/img/trans.gif b/packages/admin-web-angular/src/assets/skins/lightgray/img/trans.gif new file mode 100644 index 0000000..3884865 Binary files /dev/null and b/packages/admin-web-angular/src/assets/skins/lightgray/img/trans.gif differ diff --git a/packages/admin-web-angular/src/assets/skins/lightgray/skin.ie7.min.css b/packages/admin-web-angular/src/assets/skins/lightgray/skin.ie7.min.css new file mode 100644 index 0000000..b1f54b0 --- /dev/null +++ b/packages/admin-web-angular/src/assets/skins/lightgray/skin.ie7.min.css @@ -0,0 +1,2181 @@ +.mce-container, +.mce-container *, +.mce-widget, +.mce-widget *, +.mce-reset { + margin: 0; + padding: 0; + border: 0; + outline: 0; + vertical-align: top; + background: transparent; + text-decoration: none; + color: #333; + font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; + font-size: 14px; + text-shadow: none; + float: none; + position: static; + width: auto; + height: auto; + white-space: nowrap; + cursor: inherit; + -webkit-tap-highlight-color: transparent; + line-height: normal; + font-weight: normal; + text-align: left; + -moz-box-sizing: content-box; + -webkit-box-sizing: content-box; + box-sizing: content-box; + direction: ltr; + max-width: none; +} +.mce-widget button { + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + box-sizing: border-box; +} +.mce-container *[unselectable] { + -moz-user-select: none; + -webkit-user-select: none; + -o-user-select: none; + user-select: none; +} +.mce-fade { + opacity: 0; + -webkit-transition: opacity 0.15s linear; + transition: opacity 0.15s linear; +} +.mce-fade.mce-in { + opacity: 1; +} +.mce-tinymce { + visibility: inherit !important; + position: relative; +} +.mce-fullscreen { + border: 0; + padding: 0; + margin: 0; + overflow: hidden; + height: 100%; + z-index: 100; +} +div.mce-fullscreen { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: auto; +} +.mce-tinymce { + display: block; +} +.mce-wordcount { + position: absolute; + top: 0; + right: 0; + padding: 8px; +} +div.mce-edit-area { + background: #fff; + filter: none; +} +.mce-statusbar { + position: relative; +} +.mce-statusbar .mce-container-body { + position: relative; +} +.mce-fullscreen .mce-resizehandle { + display: none; +} +.mce-charmap { + border-collapse: collapse; +} +.mce-charmap td { + cursor: default; + border: 1px solid rgba(0, 0, 0, 0.2); + width: 20px; + height: 20px; + line-height: 20px; + text-align: center; + vertical-align: middle; + padding: 2px; +} +.mce-charmap td div { + text-align: center; +} +.mce-charmap td:hover { + background: #d9d9d9; +} +.mce-grid td.mce-grid-cell div { + border: 1px solid #d6d6d6; + width: 15px; + height: 15px; + margin: 0; + cursor: pointer; +} +.mce-grid td.mce-grid-cell div:focus { + border-color: #3498db; +} +.mce-grid td.mce-grid-cell div[disabled] { + cursor: not-allowed; +} +.mce-grid { + border-spacing: 2px; + border-collapse: separate; +} +.mce-grid a { + display: block; + border: 1px solid transparent; +} +.mce-grid a:hover, +.mce-grid a:focus { + border-color: #3498db; +} +.mce-grid-border { + margin: 0 4px 0 4px; +} +.mce-grid-border a { + border-color: #d6d6d6; + width: 13px; + height: 13px; +} +.mce-grid-border a:hover, +.mce-grid-border a.mce-active { + border-color: #3498db; + background: #3498db; +} +.mce-text-center { + text-align: center; +} +div.mce-tinymce-inline { + width: 100%; +} +.mce-colorbtn-trans div { + text-align: center; + vertical-align: middle; + font-weight: bold; + font-size: 20px; + line-height: 16px; + color: #707070; +} +.mce-monospace { + font-family: 'Courier New', Courier, monospace; +} +.mce-toolbar-grp { + padding: 2px 0; +} +.mce-toolbar-grp .mce-flow-layout-item { + margin-bottom: 0; +} +.mce-rtl .mce-wordcount { + left: 0; + right: auto; +} +.mce-croprect-container { + position: absolute; + top: 0; + left: 0; +} +.mce-croprect-handle { + position: absolute; + top: 0; + left: 0; + width: 20px; + height: 20px; + border: 2px solid white; +} +.mce-croprect-handle-nw { + border-width: 2px 0 0 2px; + margin: -2px 0 0 -2px; + cursor: nw-resize; + top: 100px; + left: 100px; +} +.mce-croprect-handle-ne { + border-width: 2px 2px 0 0; + margin: -2px 0 0 -20px; + cursor: ne-resize; + top: 100px; + left: 200px; +} +.mce-croprect-handle-sw { + border-width: 0 0 2px 2px; + margin: -20px 2px 0 -2px; + cursor: sw-resize; + top: 200px; + left: 100px; +} +.mce-croprect-handle-se { + border-width: 0 2px 2px 0; + margin: -20px 0 0 -20px; + cursor: se-resize; + top: 200px; + left: 200px; +} +.mce-croprect-handle-move { + position: absolute; + cursor: move; + border: 0; +} +.mce-croprect-block { + opacity: 0.3; + filter: alpha(opacity=30); + zoom: 1; + position: absolute; + background: black; +} +.mce-croprect-handle:focus { + border-color: #3498db; +} +.mce-croprect-handle-move:focus { + outline: 1px solid #3498db; +} +.mce-imagepanel { + overflow: auto; + background: black; +} +.mce-imagepanel-bg { + position: absolute; + background: url('data:image/gif;base64,R0lGODdhDAAMAIABAMzMzP///ywAAAAADAAMAAACFoQfqYeabNyDMkBQb81Uat85nxguUAEAOw=='); +} +.mce-imagepanel img { + position: absolute; +} +.mce-imagetool.mce-btn .mce-ico { + display: block; + width: 20px; + height: 20px; + text-align: center; + line-height: 20px; + font-size: 20px; + padding: 5px; +} +.mce-arrow-up { + margin-top: 12px; +} +.mce-arrow-down { + margin-top: -12px; +} +.mce-arrow:before, +.mce-arrow:after { + position: absolute; + left: 50%; + display: block; + width: 0; + height: 0; + border-style: solid; + border-color: transparent; + content: ''; +} +.mce-arrow.mce-arrow-up:before { + top: -9px; + border-bottom-color: rgba(0, 0, 0, 0.2); + border-width: 0 9px 9px; + margin-left: -9px; +} +.mce-arrow.mce-arrow-down:before { + bottom: -9px; + border-top-color: rgba(0, 0, 0, 0.2); + border-width: 9px 9px 0; + margin-left: -9px; +} +.mce-arrow.mce-arrow-up:after { + top: -8px; + border-bottom-color: #f0f0f0; + border-width: 0 8px 8px; + margin-left: -8px; +} +.mce-arrow.mce-arrow-down:after { + bottom: -8px; + border-top-color: #f0f0f0; + border-width: 8px 8px 0; + margin-left: -8px; +} +.mce-arrow.mce-arrow-left:before, +.mce-arrow.mce-arrow-left:after { + margin: 0; +} +.mce-arrow.mce-arrow-left:before { + left: 8px; +} +.mce-arrow.mce-arrow-left:after { + left: 9px; +} +.mce-arrow.mce-arrow-right:before, +.mce-arrow.mce-arrow-right:after { + left: auto; + margin: 0; +} +.mce-arrow.mce-arrow-right:before { + right: 8px; +} +.mce-arrow.mce-arrow-right:after { + right: 9px; +} +.mce-arrow.mce-arrow-center.mce-arrow.mce-arrow-left:before { + left: -9px; + top: 50%; + border-right-color: rgba(0, 0, 0, 0.2); + border-width: 9px 9px 9px 0; + margin-top: -9px; +} +.mce-arrow.mce-arrow-center.mce-arrow.mce-arrow-left:after { + left: -8px; + top: 50%; + border-right-color: #f0f0f0; + border-width: 8px 8px 8px 0; + margin-top: -8px; +} +.mce-arrow.mce-arrow-center.mce-arrow.mce-arrow-left { + margin-left: 12px; +} +.mce-arrow.mce-arrow-center.mce-arrow.mce-arrow-right:before { + right: -9px; + top: 50%; + border-left-color: rgba(0, 0, 0, 0.2); + border-width: 9px 0 9px 9px; + margin-top: -9px; +} +.mce-arrow.mce-arrow-center.mce-arrow.mce-arrow-right:after { + right: -8px; + top: 50%; + border-left-color: #f0f0f0; + border-width: 8px 0 8px 8px; + margin-top: -8px; +} +.mce-arrow.mce-arrow-center.mce-arrow.mce-arrow-right { + margin-left: -14px; +} +.mce-container, +.mce-container-body { + display: block; +} +.mce-autoscroll { + overflow: hidden; +} +.mce-scrollbar { + position: absolute; + width: 7px; + height: 100%; + top: 2px; + right: 2px; + opacity: 0.4; + filter: alpha(opacity=40); + zoom: 1; +} +.mce-scrollbar-h { + top: auto; + right: auto; + left: 2px; + bottom: 2px; + width: 100%; + height: 7px; +} +.mce-scrollbar-thumb { + position: absolute; + background-color: #000; + border: 1px solid #888; + border-color: rgba(85, 85, 85, 0.6); + width: 5px; + height: 100%; +} +.mce-scrollbar-h .mce-scrollbar-thumb { + width: 100%; + height: 5px; +} +.mce-scrollbar:hover, +.mce-scrollbar.mce-active { + background-color: #aaa; + opacity: 0.6; + filter: alpha(opacity=60); + zoom: 1; +} +.mce-scroll { + position: relative; +} +.mce-panel { + border: 0 solid #cacaca; + border: 0 solid rgba(0, 0, 0, 0.2); + background-color: #f0f0f0; +} +.mce-floatpanel { + position: absolute; +} +.mce-floatpanel.mce-fixed { + position: fixed; +} +.mce-floatpanel .mce-arrow, +.mce-floatpanel .mce-arrow:after { + position: absolute; + display: block; + width: 0; + height: 0; + border-color: transparent; + border-style: solid; +} +.mce-floatpanel .mce-arrow { + border-width: 11px; +} +.mce-floatpanel .mce-arrow:after { + border-width: 10px; + content: ''; +} +.mce-floatpanel.mce-popover { + filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); + background: transparent; + top: 0; + left: 0; + background: #fff; + border: 1px solid rgba(0, 0, 0, 0.2); + border: 1px solid rgba(0, 0, 0, 0.25); +} +.mce-floatpanel.mce-popover.mce-bottom { + margin-top: 10px; + *margin-top: 0; +} +.mce-floatpanel.mce-popover.mce-bottom > .mce-arrow { + left: 50%; + margin-left: -11px; + border-top-width: 0; + border-bottom-color: rgba(0, 0, 0, 0.2); + border-bottom-color: rgba(0, 0, 0, 0.25); + top: -11px; +} +.mce-floatpanel.mce-popover.mce-bottom > .mce-arrow:after { + top: 1px; + margin-left: -10px; + border-top-width: 0; + border-bottom-color: #fff; +} +.mce-floatpanel.mce-popover.mce-bottom.mce-start { + margin-left: -22px; +} +.mce-floatpanel.mce-popover.mce-bottom.mce-start > .mce-arrow { + left: 20px; +} +.mce-floatpanel.mce-popover.mce-bottom.mce-end { + margin-left: 22px; +} +.mce-floatpanel.mce-popover.mce-bottom.mce-end > .mce-arrow { + right: 10px; + left: auto; +} +.mce-fullscreen { + border: 0; + padding: 0; + margin: 0; + overflow: hidden; + height: 100%; +} +div.mce-fullscreen { + position: fixed; + top: 0; + left: 0; +} +#mce-modal-block { + opacity: 0; + filter: alpha(opacity=0); + zoom: 1; + position: fixed; + left: 0; + top: 0; + width: 100%; + height: 100%; + background: #000; +} +#mce-modal-block.mce-in { + opacity: 0.3; + filter: alpha(opacity=30); + zoom: 1; +} +.mce-window-move { + cursor: move; +} +.mce-window { + filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); + background: transparent; + background: #fff; + position: fixed; + top: 0; + left: 0; + opacity: 0; + transform: scale(0.1); + transition: transform 100ms ease-in, opacity 150ms ease-in; +} +.mce-window.mce-in { + transform: scale(1); + opacity: 1; +} +.mce-window-head { + padding: 9px 15px; + border-bottom: 1px solid #c5c5c5; + position: relative; +} +.mce-window-head .mce-close { + position: absolute; + right: 0; + top: 0; + height: 38px; + width: 38px; + text-align: center; + cursor: pointer; +} +.mce-window-head .mce-close i { + color: #858585; +} +.mce-close:hover i { + color: #adadad; +} +.mce-window-head .mce-title { + line-height: 20px; + font-size: 20px; + font-weight: bold; + text-rendering: optimizelegibility; + padding-right: 20px; +} +.mce-window .mce-container-body { + display: block; +} +.mce-foot { + display: block; + background-color: #fff; + border-top: 1px solid #c5c5c5; +} +.mce-window-head .mce-dragh { + position: absolute; + top: 0; + left: 0; + cursor: move; + width: 90%; + height: 100%; +} +.mce-window iframe { + width: 100%; + height: 100%; +} +.mce-window-body .mce-listbox { + border-color: #ccc; +} +.mce-rtl .mce-window-head .mce-close { + position: absolute; + right: auto; + left: 15px; +} +.mce-rtl .mce-window-head .mce-dragh { + left: auto; + right: 0; +} +.mce-rtl .mce-window-head .mce-title { + direction: rtl; + text-align: right; +} +.mce-tooltip { + position: absolute; + padding: 5px; + opacity: 0.8; + filter: alpha(opacity=80); + zoom: 1; +} +.mce-tooltip-inner { + font-size: 11px; + background-color: #000; + color: white; + max-width: 200px; + padding: 5px 8px 4px 8px; + text-align: center; + white-space: normal; +} +.mce-tooltip-arrow { + position: absolute; + width: 0; + height: 0; + line-height: 0; + border: 5px dashed #000; +} +.mce-tooltip-arrow-n { + border-bottom-color: #000; +} +.mce-tooltip-arrow-s { + border-top-color: #000; +} +.mce-tooltip-arrow-e { + border-left-color: #000; +} +.mce-tooltip-arrow-w { + border-right-color: #000; +} +.mce-tooltip-nw, +.mce-tooltip-sw { + margin-left: -14px; +} +.mce-tooltip-ne, +.mce-tooltip-se { + margin-left: 14px; +} +.mce-tooltip-n .mce-tooltip-arrow { + top: 0; + left: 50%; + margin-left: -5px; + border-bottom-style: solid; + border-top: none; + border-left-color: transparent; + border-right-color: transparent; +} +.mce-tooltip-nw .mce-tooltip-arrow { + top: 0; + left: 10px; + border-bottom-style: solid; + border-top: none; + border-left-color: transparent; + border-right-color: transparent; +} +.mce-tooltip-ne .mce-tooltip-arrow { + top: 0; + right: 10px; + border-bottom-style: solid; + border-top: none; + border-left-color: transparent; + border-right-color: transparent; +} +.mce-tooltip-s .mce-tooltip-arrow { + bottom: 0; + left: 50%; + margin-left: -5px; + border-top-style: solid; + border-bottom: none; + border-left-color: transparent; + border-right-color: transparent; +} +.mce-tooltip-sw .mce-tooltip-arrow { + bottom: 0; + left: 10px; + border-top-style: solid; + border-bottom: none; + border-left-color: transparent; + border-right-color: transparent; +} +.mce-tooltip-se .mce-tooltip-arrow { + bottom: 0; + right: 10px; + border-top-style: solid; + border-bottom: none; + border-left-color: transparent; + border-right-color: transparent; +} +.mce-tooltip-e .mce-tooltip-arrow { + right: 0; + top: 50%; + margin-top: -5px; + border-left-style: solid; + border-right: none; + border-top-color: transparent; + border-bottom-color: transparent; +} +.mce-tooltip-w .mce-tooltip-arrow { + left: 0; + top: 50%; + margin-top: -5px; + border-right-style: solid; + border-left: none; + border-top-color: transparent; + border-bottom-color: transparent; +} +.mce-progress { + display: inline-block; + position: relative; + height: 20px; +} +.mce-progress .mce-bar-container { + display: inline-block; + width: 100px; + height: 100%; + margin-right: 8px; + border: 1px solid #ccc; + overflow: hidden; +} +.mce-progress .mce-text { + display: inline-block; + margin-top: auto; + margin-bottom: auto; + font-size: 14px; + width: 40px; + color: #333; +} +.mce-bar { + display: block; + width: 0; + height: 100%; + background-color: #d7d7d7; + -webkit-transition: width 0.2s ease; + transition: width 0.2s ease; +} +.mce-notification { + position: absolute; + background-color: #f0f0f0; + padding: 5px; + margin-top: 5px; + border-width: 1px; + border-style: solid; + border-color: #cccccc; + transition: transform 100ms ease-in, opacity 150ms ease-in; + opacity: 0; +} +.mce-notification.mce-in { + opacity: 1; +} +.mce-notification-success { + background-color: #dff0d8; + border-color: #d6e9c6; +} +.mce-notification-info { + background-color: #d9edf7; + border-color: #779ecb; +} +.mce-notification-warning { + background-color: #fcf8e3; + border-color: #faebcc; +} +.mce-notification-error { + background-color: #f2dede; + border-color: #ebccd1; +} +.mce-notification.mce-has-close { + padding-right: 15px; +} +.mce-notification .mce-ico { + margin-top: 5px; +} +.mce-notification-inner { + display: inline-block; + font-size: 14px; + margin: 5px 8px 4px 8px; + text-align: center; + white-space: normal; + color: #31708f; +} +.mce-notification-inner a { + text-decoration: underline; + cursor: pointer; +} +.mce-notification .mce-progress { + margin-right: 8px; +} +.mce-notification .mce-progress .mce-text { + margin-top: 5px; +} +.mce-notification *, +.mce-notification .mce-progress .mce-text { + color: #333333; +} +.mce-notification .mce-progress .mce-bar-container { + border-color: #cccccc; +} +.mce-notification .mce-progress .mce-bar-container .mce-bar { + background-color: #333333; +} +.mce-notification-success *, +.mce-notification-success .mce-progress .mce-text { + color: #3c763d; +} +.mce-notification-success .mce-progress .mce-bar-container { + border-color: #d6e9c6; +} +.mce-notification-success .mce-progress .mce-bar-container .mce-bar { + background-color: #3c763d; +} +.mce-notification-info *, +.mce-notification-info .mce-progress .mce-text { + color: #31708f; +} +.mce-notification-info .mce-progress .mce-bar-container { + border-color: #779ecb; +} +.mce-notification-info .mce-progress .mce-bar-container .mce-bar { + background-color: #31708f; +} +.mce-notification-warning *, +.mce-notification-warning .mce-progress .mce-text { + color: #8a6d3b; +} +.mce-notification-warning .mce-progress .mce-bar-container { + border-color: #faebcc; +} +.mce-notification-warning .mce-progress .mce-bar-container .mce-bar { + background-color: #8a6d3b; +} +.mce-notification-error *, +.mce-notification-error .mce-progress .mce-text { + color: #a94442; +} +.mce-notification-error .mce-progress .mce-bar-container { + border-color: #ebccd1; +} +.mce-notification-error .mce-progress .mce-bar-container .mce-bar { + background-color: #a94442; +} +.mce-notification .mce-close { + position: absolute; + top: 6px; + right: 8px; + font-size: 20px; + font-weight: bold; + line-height: 20px; + color: #858585; + cursor: pointer; + height: 20px; + overflow: hidden; +} +.mce-abs-layout { + position: relative; +} +body .mce-abs-layout-item, +.mce-abs-end { + position: absolute; +} +.mce-abs-end { + width: 1px; + height: 1px; +} +.mce-container-body.mce-abs-layout { + overflow: hidden; +} +.mce-btn { + border: 1px solid #b1b1b1; + border-color: transparent transparent transparent transparent; + position: relative; + text-shadow: 0 1px 1px rgba(255, 255, 255, 0.75); + display: inline-block; + *display: inline; + *zoom: 1; + background-color: #f0f0f0; +} +.mce-btn:hover, +.mce-btn:focus { + color: #333; + background-color: #e3e3e3; + border-color: #ccc; +} +.mce-btn.mce-disabled button, +.mce-btn.mce-disabled:hover button { + cursor: default; + opacity: 0.4; + filter: alpha(opacity=40); + zoom: 1; +} +.mce-btn.mce-active, +.mce-btn.mce-active:hover { + background-color: #dbdbdb; + border-color: #ccc; +} +.mce-btn:active { + background-color: #e0e0e0; + border-color: #ccc; +} +.mce-btn button { + padding: 4px 8px; + font-size: 14px; + line-height: 20px; + *line-height: 16px; + cursor: pointer; + color: #333; + text-align: center; + overflow: visible; + -webkit-appearance: none; +} +.mce-btn button::-moz-focus-inner { + border: 0; + padding: 0; +} +.mce-btn i { + text-shadow: 1px 1px none; +} +.mce-primary.mce-btn-has-text { + min-width: 50px; +} +.mce-primary { + color: #fff; + border: 1px solid transparent; + border-color: transparent; + background-color: #2d8ac7; +} +.mce-primary:hover, +.mce-primary:focus { + background-color: #257cb6; + border-color: transparent; +} +.mce-primary.mce-disabled button, +.mce-primary.mce-disabled:hover button { + cursor: default; + opacity: 0.4; + filter: alpha(opacity=40); + zoom: 1; +} +.mce-primary.mce-active, +.mce-primary.mce-active:hover, +.mce-primary:not(.mce-disabled):active { + background-color: #206ea1; +} +.mce-primary button, +.mce-primary button i { + color: #fff; + text-shadow: 1px 1px none; +} +.mce-btn .mce-txt { + font-size: inherit; + line-height: inherit; + color: inherit; +} +.mce-btn-large button { + padding: 9px 14px; + font-size: 16px; + line-height: normal; +} +.mce-btn-large i { + margin-top: 2px; +} +.mce-btn-small button { + padding: 1px 5px; + font-size: 12px; + *padding-bottom: 2px; +} +.mce-btn-small i { + line-height: 20px; + vertical-align: top; + *line-height: 18px; +} +.mce-btn .mce-caret { + margin-top: 8px; + margin-left: 0; +} +.mce-btn-small .mce-caret { + margin-top: 8px; + margin-left: 0; +} +.mce-caret { + display: inline-block; + *display: inline; + *zoom: 1; + width: 0; + height: 0; + vertical-align: top; + border-top: 4px solid #333; + border-right: 4px solid transparent; + border-left: 4px solid transparent; + content: ''; +} +.mce-disabled .mce-caret { + border-top-color: #aaa; +} +.mce-caret.mce-up { + border-bottom: 4px solid #333; + border-top: 0; +} +.mce-btn-flat { + border: 0; + background: transparent; + filter: none; +} +.mce-btn-flat:hover, +.mce-btn-flat.mce-active, +.mce-btn-flat:focus, +.mce-btn-flat:active { + border: 0; + background: #e6e6e6; + filter: none; +} +.mce-btn-has-text .mce-ico { + padding-right: 5px; +} +.mce-rtl .mce-btn button { + direction: rtl; +} +.mce-btn-group .mce-btn { + border-width: 1px; + margin: 0; + margin-left: 2px; +} +.mce-btn-group:not(:first-child) { + border-left: 1px solid #d9d9d9; + padding-left: 3px; + margin-left: 3px; +} +.mce-btn-group .mce-first { + margin-left: 0; +} +.mce-btn-group .mce-btn.mce-flow-layout-item { + margin: 0; +} +.mce-rtl .mce-btn-group .mce-btn { + margin-left: 0; + margin-right: 2px; +} +.mce-rtl .mce-btn-group .mce-first { + margin-right: 0; +} +.mce-rtl .mce-btn-group:not(:first-child) { + border-left: none; + border-right: 1px solid #d9d9d9; + padding-right: 4px; + margin-right: 4px; +} +.mce-checkbox { + cursor: pointer; +} +i.mce-i-checkbox { + margin: 0 3px 0 0; + border: 1px solid #c5c5c5; + background-color: #f0f0f0; + text-indent: -10em; + *font-size: 0; + *line-height: 0; + *text-indent: 0; + overflow: hidden; +} +.mce-checked i.mce-i-checkbox { + color: #333; + font-size: 16px; + line-height: 16px; + text-indent: 0; +} +.mce-checkbox:focus i.mce-i-checkbox, +.mce-checkbox.mce-focus i.mce-i-checkbox { + border: 1px solid rgba(82, 168, 236, 0.8); +} +.mce-checkbox.mce-disabled .mce-label, +.mce-checkbox.mce-disabled i.mce-i-checkbox { + color: #acacac; +} +.mce-checkbox .mce-label { + vertical-align: middle; +} +.mce-rtl .mce-checkbox { + direction: rtl; + text-align: right; +} +.mce-rtl i.mce-i-checkbox { + margin: 0 0 0 3px; +} +.mce-combobox { + position: relative; + display: inline-block; + *display: inline; + *zoom: 1; + *height: 32px; +} +.mce-combobox input { + border: 1px solid #c5c5c5; + border-right-color: #c5c5c5; + height: 28px; +} +.mce-combobox.mce-disabled input { + color: #adadad; +} +.mce-combobox .mce-btn { + border: 1px solid #c5c5c5; + border-left: 0; + margin: 0; +} +.mce-combobox button { + padding-right: 8px; + padding-left: 8px; +} +.mce-combobox.mce-disabled .mce-btn button { + cursor: default; + opacity: 0.4; + filter: alpha(opacity=40); + zoom: 1; +} +.mce-combobox .mce-status { + position: absolute; + right: 2px; + top: 50%; + line-height: 16px; + margin-top: -8px; + font-size: 12px; + width: 15px; + height: 15px; + text-align: center; + cursor: pointer; +} +.mce-combobox.mce-has-status input { + padding-right: 20px; +} +.mce-combobox.mce-has-open .mce-status { + right: 37px; +} +.mce-combobox .mce-status.mce-i-warning { + color: #c09853; +} +.mce-combobox .mce-status.mce-i-checkmark { + color: #468847; +} +.mce-menu.mce-combobox-menu { + border-top: 0; + margin-top: 0; + max-height: 200px; +} +.mce-menu.mce-combobox-menu .mce-menu-item { + padding: 4px 6px 4px 4px; + font-size: 11px; +} +.mce-menu.mce-combobox-menu .mce-menu-item-sep { + padding: 0; +} +.mce-menu.mce-combobox-menu .mce-text { + font-size: 11px; +} +.mce-menu.mce-combobox-menu .mce-menu-item-link, +.mce-menu.mce-combobox-menu .mce-menu-item-link b { + font-size: 11px; +} +.mce-menu.mce-combobox-menu .mce-text b { + font-size: 11px; +} +.mce-colorbox i { + border: 1px solid #c5c5c5; + width: 14px; + height: 14px; +} +.mce-colorbutton .mce-ico { + position: relative; +} +.mce-colorbutton-grid { + margin: 4px; +} +.mce-colorbutton button { + padding-right: 6px; + padding-left: 6px; +} +.mce-colorbutton .mce-preview { + padding-right: 3px; + display: block; + position: absolute; + left: 50%; + top: 50%; + margin-left: -17px; + margin-top: 7px; + background: gray; + width: 13px; + height: 2px; + overflow: hidden; +} +.mce-colorbutton.mce-btn-small .mce-preview { + margin-left: -16px; + padding-right: 0; + width: 16px; +} +.mce-colorbutton .mce-open { + padding-left: 4px; + padding-right: 4px; + border-left: 1px solid transparent; +} +.mce-colorbutton:hover .mce-open { + border-color: #ccc; +} +.mce-colorbutton.mce-btn-small .mce-open { + padding: 0 3px 0 3px; +} +.mce-rtl .mce-colorbutton { + direction: rtl; +} +.mce-rtl .mce-colorbutton .mce-preview { + margin-left: 0; + padding-right: 0; + padding-left: 3px; +} +.mce-rtl .mce-colorbutton.mce-btn-small .mce-preview { + margin-left: 0; + padding-right: 0; + padding-left: 2px; +} +.mce-rtl .mce-colorbutton .mce-open { + padding-left: 4px; + padding-right: 4px; + border-left: 0; +} +.mce-colorpicker { + position: relative; + width: 250px; + height: 220px; +} +.mce-colorpicker-sv { + position: absolute; + top: 0; + left: 0; + width: 90%; + height: 100%; + border: 1px solid #c5c5c5; + cursor: crosshair; + overflow: hidden; +} +.mce-colorpicker-h-chunk { + width: 100%; +} +.mce-colorpicker-overlay1, +.mce-colorpicker-overlay2 { + width: 100%; + height: 100%; + position: absolute; + top: 0; + left: 0; +} +.mce-colorpicker-overlay1 { + filter: progid:DXImageTransform.Microsoft.gradient(GradientType=1, startColorstr='#ffffff', endColorstr='#00ffffff'); + -ms-filter: "progid:DXImageTransform.Microsoft.gradient(GradientType=1,startColorstr='#ffffff', endColorstr='#00ffffff')"; + background: linear-gradient(to right, #fff, rgba(255, 255, 255, 0)); +} +.mce-colorpicker-overlay2 { + filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0, startColorstr='#00000000', endColorstr='#000000'); + -ms-filter: "progid:DXImageTransform.Microsoft.gradient(GradientType=0,startColorstr='#00000000', endColorstr='#000000')"; + background: linear-gradient(to bottom, rgba(0, 0, 0, 0), #000); +} +.mce-colorpicker-selector1 { + background: none; + position: absolute; + width: 12px; + height: 12px; + margin: -8px 0 0 -8px; + border: 1px solid black; + border-radius: 50%; +} +.mce-colorpicker-selector2 { + position: absolute; + width: 10px; + height: 10px; + border: 1px solid white; + border-radius: 50%; +} +.mce-colorpicker-h { + position: absolute; + top: 0; + right: 0; + width: 6.5%; + height: 100%; + border: 1px solid #c5c5c5; + cursor: crosshair; +} +.mce-colorpicker-h-marker { + margin-top: -4px; + position: absolute; + top: 0; + left: -1px; + width: 100%; + border: 1px solid #333; + background: #fff; + height: 4px; + z-index: 100; +} +.mce-path { + display: inline-block; + *display: inline; + *zoom: 1; + padding: 8px; + white-space: normal; +} +.mce-path .mce-txt { + display: inline-block; + padding-right: 3px; +} +.mce-path .mce-path-body { + display: inline-block; +} +.mce-path-item { + display: inline-block; + *display: inline; + *zoom: 1; + cursor: pointer; + color: #333; +} +.mce-path-item:hover { + text-decoration: underline; +} +.mce-path-item:focus { + background: #666; + color: #fff; +} +.mce-path .mce-divider { + display: inline; +} +.mce-disabled .mce-path-item { + color: #aaa; +} +.mce-rtl .mce-path { + direction: rtl; +} +.mce-fieldset { + border: 0 solid #9e9e9e; +} +.mce-fieldset > .mce-container-body { + margin-top: -15px; +} +.mce-fieldset-title { + margin-left: 5px; + padding: 0 5px 0 5px; +} +.mce-fit-layout { + display: inline-block; + *display: inline; + *zoom: 1; +} +.mce-fit-layout-item { + position: absolute; +} +.mce-flow-layout-item { + display: inline-block; + *display: inline; + *zoom: 1; +} +.mce-flow-layout-item { + margin: 2px 0 2px 2px; +} +.mce-flow-layout-item.mce-last { + margin-right: 2px; +} +.mce-flow-layout { + white-space: normal; +} +.mce-tinymce-inline .mce-flow-layout { + white-space: nowrap; +} +.mce-rtl .mce-flow-layout { + text-align: right; + direction: rtl; +} +.mce-rtl .mce-flow-layout-item { + margin: 2px 2px 2px 0; +} +.mce-rtl .mce-flow-layout-item.mce-last { + margin-left: 2px; +} +.mce-iframe { + border: 0 solid rgba(0, 0, 0, 0.2); + width: 100%; + height: 100%; +} +.mce-infobox { + display: inline-block; + *display: inline; + *zoom: 1; + text-shadow: 0 1px 1px rgba(255, 255, 255, 0.75); + overflow: hidden; + border: 1px solid red; +} +.mce-infobox div { + display: block; + margin: 5px; +} +.mce-infobox div button { + position: absolute; + top: 50%; + right: 4px; + cursor: pointer; + margin-top: -8px; + display: none; +} +.mce-infobox div button:focus { + outline: 2px solid #ccc; +} +.mce-infobox.mce-has-help div { + margin-right: 25px; +} +.mce-infobox.mce-has-help button { + display: block; +} +.mce-infobox.mce-success { + background: #dff0d8; + border-color: #d6e9c6; +} +.mce-infobox.mce-success div { + color: #3c763d; +} +.mce-infobox.mce-warning { + background: #fcf8e3; + border-color: #faebcc; +} +.mce-infobox.mce-warning div { + color: #8a6d3b; +} +.mce-infobox.mce-error { + background: #f2dede; + border-color: #ebccd1; +} +.mce-infobox.mce-error div { + color: #a94442; +} +.mce-rtl .mce-infobox div { + text-align: right; + direction: rtl; +} +.mce-label { + display: inline-block; + *display: inline; + *zoom: 1; + text-shadow: 0 1px 1px rgba(255, 255, 255, 0.75); + overflow: hidden; +} +.mce-label.mce-autoscroll { + overflow: auto; +} +.mce-label.mce-disabled { + color: #aaa; +} +.mce-label.mce-multiline { + white-space: pre-wrap; +} +.mce-label.mce-success { + color: #468847; +} +.mce-label.mce-warning { + color: #c09853; +} +.mce-label.mce-error { + color: #b94a48; +} +.mce-rtl .mce-label { + text-align: right; + direction: rtl; +} +.mce-menubar .mce-menubtn { + border-color: transparent; + background: transparent; + filter: none; +} +.mce-menubar .mce-menubtn button { + color: #333; +} +.mce-menubar { + border: 1px solid rgba(217, 217, 217, 0.52); +} +.mce-menubar .mce-menubtn button span { + color: #333; +} +.mce-menubar .mce-caret { + border-top-color: #333; +} +.mce-menubar .mce-menubtn:hover, +.mce-menubar .mce-menubtn.mce-active, +.mce-menubar .mce-menubtn:focus { + border-color: #ccc; + background: #fff; + filter: none; +} +.mce-menubtn button { + color: #333; +} +.mce-menubtn.mce-btn-small span { + font-size: 12px; +} +.mce-menubtn.mce-fixed-width span { + display: inline-block; + overflow-x: hidden; + text-overflow: ellipsis; + width: 90px; +} +.mce-menubtn.mce-fixed-width.mce-btn-small span { + width: 70px; +} +.mce-menubtn .mce-caret { + *margin-top: 6px; +} +.mce-rtl .mce-menubtn button { + direction: rtl; + text-align: right; +} +.mce-menu-item { + display: block; + padding: 6px 15px 6px 12px; + clear: both; + font-weight: normal; + line-height: 20px; + color: #333; + white-space: nowrap; + cursor: pointer; + line-height: normal; + border-left: 4px solid transparent; + margin-bottom: 1px; +} +.mce-menu-item .mce-ico, +.mce-menu-item .mce-text { + color: #333; +} +.mce-menu-item.mce-disabled .mce-text, +.mce-menu-item.mce-disabled .mce-ico { + color: #adadad; +} +.mce-menu-item:hover .mce-text, +.mce-menu-item.mce-selected .mce-text, +.mce-menu-item:focus .mce-text { + color: white; +} +.mce-menu-item:hover .mce-ico, +.mce-menu-item.mce-selected .mce-ico, +.mce-menu-item:focus .mce-ico { + color: white; +} +.mce-menu-item.mce-disabled:hover { + background: #ccc; +} +.mce-menu-shortcut { + display: inline-block; + color: #adadad; +} +.mce-menu-shortcut { + display: inline-block; + *display: inline; + *zoom: 1; + padding: 0 15px 0 20px; +} +.mce-menu-item:hover .mce-menu-shortcut, +.mce-menu-item.mce-selected .mce-menu-shortcut, +.mce-menu-item:focus .mce-menu-shortcut { + color: white; +} +.mce-menu-item .mce-caret { + margin-top: 4px; + *margin-top: 3px; + margin-right: 6px; + border-top: 4px solid transparent; + border-bottom: 4px solid transparent; + border-left: 4px solid #333; +} +.mce-menu-item.mce-selected .mce-caret, +.mce-menu-item:focus .mce-caret, +.mce-menu-item:hover .mce-caret { + border-left-color: white; +} +.mce-menu-align .mce-menu-shortcut { + *margin-top: -2px; +} +.mce-menu-align .mce-menu-shortcut, +.mce-menu-align .mce-caret { + position: absolute; + right: 0; +} +.mce-menu-item.mce-active i { + visibility: visible; +} +.mce-menu-item-normal.mce-active { + background-color: #3498db; +} +.mce-menu-item-preview.mce-active { + border-left: 5px solid #aaa; +} +.mce-menu-item-normal.mce-active .mce-text { + color: white; +} +.mce-menu-item-normal.mce-active:hover .mce-text, +.mce-menu-item-normal.mce-active:hover .mce-ico { + color: white; +} +.mce-menu-item-normal.mce-active:focus .mce-text, +.mce-menu-item-normal.mce-active:focus .mce-ico { + color: white; +} +.mce-menu-item:hover, +.mce-menu-item.mce-selected, +.mce-menu-item:focus { + text-decoration: none; + color: white; + background-color: #2d8ac7; +} +.mce-menu-item-link { + color: #093; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.mce-menu-item-link b { + color: #093; +} +.mce-menu-item-ellipsis { + display: block; + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; +} +.mce-menu-item:hover *, +.mce-menu-item.mce-selected *, +.mce-menu-item:focus * { + color: white; +} +div.mce-menu .mce-menu-item-sep, +.mce-menu-item-sep:hover { + border: 0; + padding: 0; + height: 1px; + margin: 9px 1px; + overflow: hidden; + background: transparent; + border-bottom: 1px solid rgba(0, 0, 0, 0.1); + cursor: default; + filter: none; +} +div.mce-menu .mce-menu-item b { + font-weight: bold; +} +.mce-menu-item-indent-1 { + padding-left: 20px; +} +.mce-menu-item-indent-2 { + padding-left: 35px; +} +.mce-menu-item-indent-2 { + padding-left: 35px; +} +.mce-menu-item-indent-3 { + padding-left: 40px; +} +.mce-menu-item-indent-4 { + padding-left: 45px; +} +.mce-menu-item-indent-5 { + padding-left: 50px; +} +.mce-menu-item-indent-6 { + padding-left: 55px; +} +.mce-menu.mce-rtl { + direction: rtl; +} +.mce-rtl .mce-menu-item { + text-align: right; + direction: rtl; + padding: 6px 12px 6px 15px; +} +.mce-menu-align.mce-rtl .mce-menu-shortcut, +.mce-menu-align.mce-rtl .mce-caret { + right: auto; + left: 0; +} +.mce-rtl .mce-menu-item .mce-caret { + margin-left: 6px; + margin-right: 0; + border-right: 4px solid #333; + border-left: 0; +} +.mce-rtl .mce-menu-item.mce-selected .mce-caret, +.mce-rtl .mce-menu-item:focus .mce-caret, +.mce-rtl .mce-menu-item:hover .mce-caret { + border-left-color: transparent; + border-right-color: white; +} +.mce-throbber { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + opacity: 0.6; + filter: alpha(opacity=60); + zoom: 1; + background: #fff url('img/loader.gif') no-repeat center center; +} +.mce-throbber-inline { + position: static; + height: 50px; +} +.mce-menu .mce-throbber-inline { + height: 25px; + background-size: contain; +} +.mce-menu { + position: absolute; + left: 0; + top: 0; + filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); + background: transparent; + z-index: 1000; + padding: 5px 0 5px 0; + margin: -1px 0 0; + min-width: 160px; + background: #fff; + border: 1px solid #989898; + border: 1px solid rgba(0, 0, 0, 0.2); + z-index: 1002; + max-height: 400px; + overflow: auto; + overflow-x: hidden; +} +.mce-menu i { + display: none; +} +.mce-menu-has-icons i { + display: inline-block; + *display: inline; +} +.mce-menu-sub-tr-tl { + margin: -6px 0 0 -1px; +} +.mce-menu-sub-br-bl { + margin: 6px 0 0 -1px; +} +.mce-menu-sub-tl-tr { + margin: -6px 0 0 1px; +} +.mce-menu-sub-bl-br { + margin: 6px 0 0 1px; +} +.mce-listbox button { + text-align: left; + padding-right: 20px; + position: relative; +} +.mce-listbox .mce-caret { + position: absolute; + margin-top: -2px; + right: 8px; + top: 50%; +} +.mce-rtl .mce-listbox .mce-caret { + right: auto; + left: 8px; +} +.mce-rtl .mce-listbox button { + padding-right: 10px; + padding-left: 20px; +} +.mce-container-body .mce-resizehandle { + position: absolute; + right: 0; + bottom: 0; + width: 16px; + height: 16px; + visibility: visible; + cursor: s-resize; + margin: 0; +} +.mce-container-body .mce-resizehandle-both { + cursor: se-resize; +} +i.mce-i-resize { + color: #333; +} +.mce-selectbox { + background: #fff; + border: 1px solid #c5c5c5; +} +.mce-slider { + border: 1px solid #aaa; + background: #eee; + width: 100px; + height: 10px; + position: relative; + display: block; +} +.mce-slider.mce-vertical { + width: 10px; + height: 100px; +} +.mce-slider-handle { + border: 1px solid #bbb; + background: #ddd; + display: block; + width: 13px; + height: 13px; + position: absolute; + top: 0; + left: 0; + margin-left: -1px; + margin-top: -2px; +} +.mce-slider-handle:focus { + background: #bbb; +} +.mce-spacer { + visibility: hidden; +} +.mce-splitbtn .mce-open { + border-left: 1px solid transparent; +} +.mce-splitbtn:hover .mce-open { + border-left-color: #ccc; +} +.mce-splitbtn button { + padding-right: 6px; + padding-left: 6px; +} +.mce-splitbtn .mce-open { + padding-right: 4px; + padding-left: 4px; +} +.mce-splitbtn .mce-open.mce-active { + background-color: #dbdbdb; + outline: 1px solid #ccc; +} +.mce-splitbtn.mce-btn-small .mce-open { + padding: 0 3px 0 3px; +} +.mce-rtl .mce-splitbtn { + direction: rtl; + text-align: right; +} +.mce-rtl .mce-splitbtn button { + padding-right: 4px; + padding-left: 4px; +} +.mce-rtl .mce-splitbtn .mce-open { + border-left: 0; +} +.mce-stack-layout-item { + display: block; +} +.mce-tabs { + display: block; + border-bottom: 1px solid #c5c5c5; +} +.mce-tabs, +.mce-tabs + .mce-container-body { + background: #fff; +} +.mce-tab { + display: inline-block; + *display: inline; + *zoom: 1; + border: 1px solid #c5c5c5; + border-width: 0 1px 0 0; + background: #ffffff; + padding: 8px; + text-shadow: 0 1px 1px rgba(255, 255, 255, 0.75); + height: 13px; + cursor: pointer; +} +.mce-tab:hover { + background: #fdfdfd; +} +.mce-tab.mce-active { + background: #fdfdfd; + border-bottom-color: transparent; + margin-bottom: -1px; + height: 14px; +} +.mce-rtl .mce-tabs { + text-align: right; + direction: rtl; +} +.mce-rtl .mce-tab { + border-width: 0 0 0 1px; +} +.mce-textbox { + background: #fff; + border: 1px solid #c5c5c5; + display: inline-block; + -webkit-transition: border linear 0.2s, box-shadow linear 0.2s; + transition: border linear 0.2s, box-shadow linear 0.2s; + height: 28px; + resize: none; + padding: 0 4px 0 4px; + white-space: pre-wrap; + *white-space: pre; + color: #333; +} +.mce-textbox:focus, +.mce-textbox.mce-focus { + border-color: #3498db; +} +.mce-placeholder .mce-textbox { + color: #aaa; +} +.mce-textbox.mce-multiline { + padding: 4px; + height: auto; +} +.mce-textbox.mce-disabled { + color: #adadad; +} +.mce-rtl .mce-textbox { + text-align: right; + direction: rtl; +} +@font-face { + font-family: 'tinymce'; + src: url('fonts/tinymce.eot'); + src: url('fonts/tinymce.eot?#iefix') format('embedded-opentype'), + url('fonts/tinymce.woff') format('woff'), + url('fonts/tinymce.ttf') format('truetype'), + url('fonts/tinymce.svg#tinymce') format('svg'); + font-weight: normal; + font-style: normal; +} +@font-face { + font-family: 'tinymce-small'; + src: url('fonts/tinymce-small.eot'); + src: url('fonts/tinymce-small.eot?#iefix') format('embedded-opentype'), + url('fonts/tinymce-small.woff') format('woff'), + url('fonts/tinymce-small.ttf') format('truetype'), + url('fonts/tinymce-small.svg#tinymce') format('svg'); + font-weight: normal; + font-style: normal; +} +.mce-ico { + font-family: 'tinymce'; + font-style: normal; + font-weight: normal; + font-size: 16px; + line-height: 16px; + vertical-align: text-top; + -webkit-font-smoothing: antialiased; + display: inline-block; + background: transparent center center; + width: 16px; + height: 16px; + color: #333; + -ie7-icon: ' '; +} +.mce-btn-small .mce-ico { + font-family: 'tinymce-small'; +} +.mce-ico, +i.mce-i-checkbox { + zoom: expression( + this.runtimeStyle[ 'zoom' ] = '1', + this.innerHTML = this.currentStyle[ '-ie7-icon' ].substr(1, 1) + + ' ' + ); +} +.mce-i-save { + -ie7-icon: '\e000'; +} +.mce-i-newdocument { + -ie7-icon: '\e001'; +} +.mce-i-fullpage { + -ie7-icon: '\e002'; +} +.mce-i-alignleft { + -ie7-icon: '\e003'; +} +.mce-i-aligncenter { + -ie7-icon: '\e004'; +} +.mce-i-alignright { + -ie7-icon: '\e005'; +} +.mce-i-alignjustify { + -ie7-icon: '\e006'; +} +.mce-i-alignnone { + -ie7-icon: '\e003'; +} +.mce-i-cut { + -ie7-icon: '\e007'; +} +.mce-i-paste { + -ie7-icon: '\e008'; +} +.mce-i-searchreplace { + -ie7-icon: '\e009'; +} +.mce-i-bullist { + -ie7-icon: '\e00a'; +} +.mce-i-numlist { + -ie7-icon: '\e00b'; +} +.mce-i-indent { + -ie7-icon: '\e00c'; +} +.mce-i-outdent { + -ie7-icon: '\e00d'; +} +.mce-i-blockquote { + -ie7-icon: '\e00e'; +} +.mce-i-undo { + -ie7-icon: '\e00f'; +} +.mce-i-redo { + -ie7-icon: '\e010'; +} +.mce-i-link { + -ie7-icon: '\e011'; +} +.mce-i-unlink { + -ie7-icon: '\e012'; +} +.mce-i-anchor { + -ie7-icon: '\e013'; +} +.mce-i-image { + -ie7-icon: '\e014'; +} +.mce-i-media { + -ie7-icon: '\e015'; +} +.mce-i-help { + -ie7-icon: '\e016'; +} +.mce-i-code { + -ie7-icon: '\e017'; +} +.mce-i-insertdatetime { + -ie7-icon: '\e018'; +} +.mce-i-preview { + -ie7-icon: '\e019'; +} +.mce-i-forecolor { + -ie7-icon: '\e01a'; +} +.mce-i-backcolor { + -ie7-icon: '\e01a'; +} +.mce-i-table { + -ie7-icon: '\e01b'; +} +.mce-i-hr { + -ie7-icon: '\e01c'; +} +.mce-i-removeformat { + -ie7-icon: '\e01d'; +} +.mce-i-subscript { + -ie7-icon: '\e01e'; +} +.mce-i-superscript { + -ie7-icon: '\e01f'; +} +.mce-i-charmap { + -ie7-icon: '\e020'; +} +.mce-i-emoticons { + -ie7-icon: '\e021'; +} +.mce-i-print { + -ie7-icon: '\e022'; +} +.mce-i-fullscreen { + -ie7-icon: '\e023'; +} +.mce-i-spellchecker { + -ie7-icon: '\e024'; +} +.mce-i-nonbreaking { + -ie7-icon: '\e025'; +} +.mce-i-template { + -ie7-icon: '\e026'; +} +.mce-i-pagebreak { + -ie7-icon: '\e027'; +} +.mce-i-restoredraft { + -ie7-icon: '\e028'; +} +.mce-i-untitled { + -ie7-icon: '\e029'; +} +.mce-i-bold { + -ie7-icon: '\e02a'; +} +.mce-i-italic { + -ie7-icon: '\e02b'; +} +.mce-i-underline { + -ie7-icon: '\e02c'; +} +.mce-i-strikethrough { + -ie7-icon: '\e02d'; +} +.mce-i-visualchars { + -ie7-icon: '\e02e'; +} +.mce-i-ltr { + -ie7-icon: '\e02f'; +} +.mce-i-rtl { + -ie7-icon: '\e030'; +} +.mce-i-copy { + -ie7-icon: '\e031'; +} +.mce-i-resize { + -ie7-icon: '\e032'; +} +.mce-i-browse { + -ie7-icon: '\e034'; +} +.mce-i-pastetext { + -ie7-icon: '\e035'; +} +.mce-i-rotateleft { + -ie7-icon: '\eaa8'; +} +.mce-i-rotateright { + -ie7-icon: '\eaa9'; +} +.mce-i-crop { + -ie7-icon: '\ee78'; +} +.mce-i-editimage { + -ie7-icon: '\e914'; +} +.mce-i-options { + -ie7-icon: '\ec6a'; +} +.mce-i-flipv { + -ie7-icon: '\eaaa'; +} +.mce-i-fliph { + -ie7-icon: '\eaac'; +} +.mce-i-zoomin { + -ie7-icon: '\eb35'; +} +.mce-i-zoomout { + -ie7-icon: '\eb36'; +} +.mce-i-sun { + -ie7-icon: '\eccc'; +} +.mce-i-moon { + -ie7-icon: '\eccd'; +} +.mce-i-arrowleft { + -ie7-icon: '\edc0'; +} +.mce-i-arrowright { + -ie7-icon: '\edb8'; +} +.mce-i-drop { + -ie7-icon: '\e934'; +} +.mce-i-contrast { + -ie7-icon: '\ecd4'; +} +.mce-i-sharpen { + -ie7-icon: '\eba7'; +} +.mce-i-palette { + -ie7-icon: '\e92a'; +} +.mce-i-resize2 { + -ie7-icon: '\edf9'; +} +.mce-i-orientation { + -ie7-icon: '\e601'; +} +.mce-i-invert { + -ie7-icon: '\e602'; +} +.mce-i-gamma { + -ie7-icon: '\e600'; +} +.mce-i-remove { + -ie7-icon: '\ed6a'; +} +.mce-i-codesample { + -ie7-icon: '\e603'; +} +.mce-i-checkbox, +.mce-i-selected { + -ie7-icon: '\e033'; +} +.mce-i-selected { + visibility: hidden; +} +.mce-i-backcolor { + background: #bbb; +} diff --git a/packages/admin-web-angular/src/assets/skins/lightgray/skin.min.css b/packages/admin-web-angular/src/assets/skins/lightgray/skin.min.css new file mode 100644 index 0000000..10f3ceb --- /dev/null +++ b/packages/admin-web-angular/src/assets/skins/lightgray/skin.min.css @@ -0,0 +1,2372 @@ +.mce-container, +.mce-container *, +.mce-widget, +.mce-widget *, +.mce-reset { + margin: 0; + padding: 0; + border: 0; + outline: 0; + vertical-align: top; + background: transparent; + text-decoration: none; + color: #333; + font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; + font-size: 14px; + text-shadow: none; + float: none; + position: static; + width: auto; + height: auto; + white-space: nowrap; + cursor: inherit; + -webkit-tap-highlight-color: transparent; + line-height: normal; + font-weight: normal; + text-align: left; + -moz-box-sizing: content-box; + -webkit-box-sizing: content-box; + box-sizing: content-box; + direction: ltr; + max-width: none; +} +.mce-widget button { + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + box-sizing: border-box; +} +.mce-container *[unselectable] { + -moz-user-select: none; + -webkit-user-select: none; + -o-user-select: none; + user-select: none; +} +.mce-fade { + opacity: 0; + -webkit-transition: opacity 0.15s linear; + transition: opacity 0.15s linear; +} +.mce-fade.mce-in { + opacity: 1; +} +.mce-tinymce { + visibility: inherit !important; + position: relative; +} +.mce-fullscreen { + border: 0; + padding: 0; + margin: 0; + overflow: hidden; + height: 100%; + z-index: 100; +} +div.mce-fullscreen { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: auto; +} +.mce-tinymce { + display: block; +} +.mce-wordcount { + position: absolute; + top: 0; + right: 0; + padding: 8px; +} +div.mce-edit-area { + background: #fff; + filter: none; +} +.mce-statusbar { + position: relative; +} +.mce-statusbar .mce-container-body { + position: relative; +} +.mce-fullscreen .mce-resizehandle { + display: none; +} +.mce-charmap { + border-collapse: collapse; +} +.mce-charmap td { + cursor: default; + border: 1px solid rgba(0, 0, 0, 0.2); + width: 20px; + height: 20px; + line-height: 20px; + text-align: center; + vertical-align: middle; + padding: 2px; +} +.mce-charmap td div { + text-align: center; +} +.mce-charmap td:hover { + background: #d9d9d9; +} +.mce-grid td.mce-grid-cell div { + border: 1px solid #d6d6d6; + width: 15px; + height: 15px; + margin: 0; + cursor: pointer; +} +.mce-grid td.mce-grid-cell div:focus { + border-color: #3498db; +} +.mce-grid td.mce-grid-cell div[disabled] { + cursor: not-allowed; +} +.mce-grid { + border-spacing: 2px; + border-collapse: separate; +} +.mce-grid a { + display: block; + border: 1px solid transparent; +} +.mce-grid a:hover, +.mce-grid a:focus { + border-color: #3498db; +} +.mce-grid-border { + margin: 0 4px 0 4px; +} +.mce-grid-border a { + border-color: #d6d6d6; + width: 13px; + height: 13px; +} +.mce-grid-border a:hover, +.mce-grid-border a.mce-active { + border-color: #3498db; + background: #3498db; +} +.mce-text-center { + text-align: center; +} +div.mce-tinymce-inline { + width: 100%; +} +.mce-colorbtn-trans div { + text-align: center; + vertical-align: middle; + font-weight: bold; + font-size: 20px; + line-height: 16px; + color: #707070; +} +.mce-monospace { + font-family: 'Courier New', Courier, monospace; +} +.mce-toolbar-grp { + padding: 2px 0; +} +.mce-toolbar-grp .mce-flow-layout-item { + margin-bottom: 0; +} +.mce-rtl .mce-wordcount { + left: 0; + right: auto; +} +.mce-croprect-container { + position: absolute; + top: 0; + left: 0; +} +.mce-croprect-handle { + position: absolute; + top: 0; + left: 0; + width: 20px; + height: 20px; + border: 2px solid white; +} +.mce-croprect-handle-nw { + border-width: 2px 0 0 2px; + margin: -2px 0 0 -2px; + cursor: nw-resize; + top: 100px; + left: 100px; +} +.mce-croprect-handle-ne { + border-width: 2px 2px 0 0; + margin: -2px 0 0 -20px; + cursor: ne-resize; + top: 100px; + left: 200px; +} +.mce-croprect-handle-sw { + border-width: 0 0 2px 2px; + margin: -20px 2px 0 -2px; + cursor: sw-resize; + top: 200px; + left: 100px; +} +.mce-croprect-handle-se { + border-width: 0 2px 2px 0; + margin: -20px 0 0 -20px; + cursor: se-resize; + top: 200px; + left: 200px; +} +.mce-croprect-handle-move { + position: absolute; + cursor: move; + border: 0; +} +.mce-croprect-block { + opacity: 0.3; + filter: alpha(opacity=30); + zoom: 1; + position: absolute; + background: black; +} +.mce-croprect-handle:focus { + border-color: #3498db; +} +.mce-croprect-handle-move:focus { + outline: 1px solid #3498db; +} +.mce-imagepanel { + overflow: auto; + background: black; +} +.mce-imagepanel-bg { + position: absolute; + background: url('data:image/gif;base64,R0lGODdhDAAMAIABAMzMzP///ywAAAAADAAMAAACFoQfqYeabNyDMkBQb81Uat85nxguUAEAOw=='); +} +.mce-imagepanel img { + position: absolute; +} +.mce-imagetool.mce-btn .mce-ico { + display: block; + width: 20px; + height: 20px; + text-align: center; + line-height: 20px; + font-size: 20px; + padding: 5px; +} +.mce-arrow-up { + margin-top: 12px; +} +.mce-arrow-down { + margin-top: -12px; +} +.mce-arrow:before, +.mce-arrow:after { + position: absolute; + left: 50%; + display: block; + width: 0; + height: 0; + border-style: solid; + border-color: transparent; + content: ''; +} +.mce-arrow.mce-arrow-up:before { + top: -9px; + border-bottom-color: rgba(0, 0, 0, 0.2); + border-width: 0 9px 9px; + margin-left: -9px; +} +.mce-arrow.mce-arrow-down:before { + bottom: -9px; + border-top-color: rgba(0, 0, 0, 0.2); + border-width: 9px 9px 0; + margin-left: -9px; +} +.mce-arrow.mce-arrow-up:after { + top: -8px; + border-bottom-color: #f0f0f0; + border-width: 0 8px 8px; + margin-left: -8px; +} +.mce-arrow.mce-arrow-down:after { + bottom: -8px; + border-top-color: #f0f0f0; + border-width: 8px 8px 0; + margin-left: -8px; +} +.mce-arrow.mce-arrow-left:before, +.mce-arrow.mce-arrow-left:after { + margin: 0; +} +.mce-arrow.mce-arrow-left:before { + left: 8px; +} +.mce-arrow.mce-arrow-left:after { + left: 9px; +} +.mce-arrow.mce-arrow-right:before, +.mce-arrow.mce-arrow-right:after { + left: auto; + margin: 0; +} +.mce-arrow.mce-arrow-right:before { + right: 8px; +} +.mce-arrow.mce-arrow-right:after { + right: 9px; +} +.mce-arrow.mce-arrow-center.mce-arrow.mce-arrow-left:before { + left: -9px; + top: 50%; + border-right-color: rgba(0, 0, 0, 0.2); + border-width: 9px 9px 9px 0; + margin-top: -9px; +} +.mce-arrow.mce-arrow-center.mce-arrow.mce-arrow-left:after { + left: -8px; + top: 50%; + border-right-color: #f0f0f0; + border-width: 8px 8px 8px 0; + margin-top: -8px; +} +.mce-arrow.mce-arrow-center.mce-arrow.mce-arrow-left { + margin-left: 12px; +} +.mce-arrow.mce-arrow-center.mce-arrow.mce-arrow-right:before { + right: -9px; + top: 50%; + border-left-color: rgba(0, 0, 0, 0.2); + border-width: 9px 0 9px 9px; + margin-top: -9px; +} +.mce-arrow.mce-arrow-center.mce-arrow.mce-arrow-right:after { + right: -8px; + top: 50%; + border-left-color: #f0f0f0; + border-width: 8px 0 8px 8px; + margin-top: -8px; +} +.mce-arrow.mce-arrow-center.mce-arrow.mce-arrow-right { + margin-left: -14px; +} +.mce-edit-aria-container > .mce-container-body { + display: flex; +} +.mce-edit-aria-container > .mce-container-body .mce-edit-area { + flex: 1; +} +.mce-edit-aria-container + > .mce-container-body + .mce-sidebar + > .mce-container-body { + display: flex; + align-items: stretch; + height: 100%; +} +.mce-edit-aria-container > .mce-container-body .mce-sidebar-panel { + min-width: 250px; + max-width: 250px; + position: relative; +} +.mce-edit-aria-container + > .mce-container-body + .mce-sidebar-panel + > .mce-container-body { + position: absolute; + width: 100%; + height: 100%; + overflow: auto; + top: 0; + left: 0; +} +.mce-sidebar-toolbar { + border: 0 solid rgba(0, 0, 0, 0.2); + border-left-width: 1px; +} +.mce-sidebar-toolbar .mce-btn.mce-active, +.mce-sidebar-toolbar .mce-btn.mce-active:hover { + border: 1px solid transparent; + border-color: transparent; + background-color: #2d8ac7; +} +.mce-sidebar-toolbar .mce-btn.mce-active button, +.mce-sidebar-toolbar .mce-btn.mce-active:hover button, +.mce-sidebar-toolbar .mce-btn.mce-active button i, +.mce-sidebar-toolbar .mce-btn.mce-active:hover button i { + color: #fff; + text-shadow: 1px 1px none; +} +.mce-sidebar-panel { + border: 0 solid rgba(0, 0, 0, 0.2); + border-left-width: 1px; +} +.mce-container, +.mce-container-body { + display: block; +} +.mce-autoscroll { + overflow: hidden; +} +.mce-scrollbar { + position: absolute; + width: 7px; + height: 100%; + top: 2px; + right: 2px; + opacity: 0.4; + filter: alpha(opacity=40); + zoom: 1; +} +.mce-scrollbar-h { + top: auto; + right: auto; + left: 2px; + bottom: 2px; + width: 100%; + height: 7px; +} +.mce-scrollbar-thumb { + position: absolute; + background-color: #000; + border: 1px solid #888; + border-color: rgba(85, 85, 85, 0.6); + width: 5px; + height: 100%; +} +.mce-scrollbar-h .mce-scrollbar-thumb { + width: 100%; + height: 5px; +} +.mce-scrollbar:hover, +.mce-scrollbar.mce-active { + background-color: #aaa; + opacity: 0.6; + filter: alpha(opacity=60); + zoom: 1; +} +.mce-scroll { + position: relative; +} +.mce-panel { + border: 0 solid #cacaca; + border: 0 solid rgba(0, 0, 0, 0.2); + background-color: #f0f0f0; +} +.mce-floatpanel { + position: absolute; +} +.mce-floatpanel.mce-fixed { + position: fixed; +} +.mce-floatpanel .mce-arrow, +.mce-floatpanel .mce-arrow:after { + position: absolute; + display: block; + width: 0; + height: 0; + border-color: transparent; + border-style: solid; +} +.mce-floatpanel .mce-arrow { + border-width: 11px; +} +.mce-floatpanel .mce-arrow:after { + border-width: 10px; + content: ''; +} +.mce-floatpanel.mce-popover { + filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); + background: transparent; + top: 0; + left: 0; + background: #fff; + border: 1px solid rgba(0, 0, 0, 0.2); + border: 1px solid rgba(0, 0, 0, 0.25); +} +.mce-floatpanel.mce-popover.mce-bottom { + margin-top: 10px; + *margin-top: 0; +} +.mce-floatpanel.mce-popover.mce-bottom > .mce-arrow { + left: 50%; + margin-left: -11px; + border-top-width: 0; + border-bottom-color: rgba(0, 0, 0, 0.2); + border-bottom-color: rgba(0, 0, 0, 0.25); + top: -11px; +} +.mce-floatpanel.mce-popover.mce-bottom > .mce-arrow:after { + top: 1px; + margin-left: -10px; + border-top-width: 0; + border-bottom-color: #fff; +} +.mce-floatpanel.mce-popover.mce-bottom.mce-start { + margin-left: -22px; +} +.mce-floatpanel.mce-popover.mce-bottom.mce-start > .mce-arrow { + left: 20px; +} +.mce-floatpanel.mce-popover.mce-bottom.mce-end { + margin-left: 22px; +} +.mce-floatpanel.mce-popover.mce-bottom.mce-end > .mce-arrow { + right: 10px; + left: auto; +} +.mce-fullscreen { + border: 0; + padding: 0; + margin: 0; + overflow: hidden; + height: 100%; +} +div.mce-fullscreen { + position: fixed; + top: 0; + left: 0; +} +#mce-modal-block { + opacity: 0; + filter: alpha(opacity=0); + zoom: 1; + position: fixed; + left: 0; + top: 0; + width: 100%; + height: 100%; + background: #000; +} +#mce-modal-block.mce-in { + opacity: 0.3; + filter: alpha(opacity=30); + zoom: 1; +} +.mce-window-move { + cursor: move; +} +.mce-window { + filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); + background: transparent; + background: #fff; + position: fixed; + top: 0; + left: 0; + opacity: 0; + transform: scale(0.1); + transition: transform 100ms ease-in, opacity 150ms ease-in; +} +.mce-window.mce-in { + transform: scale(1); + opacity: 1; +} +.mce-window-head { + padding: 9px 15px; + border-bottom: 1px solid #c5c5c5; + position: relative; +} +.mce-window-head .mce-close { + position: absolute; + right: 0; + top: 0; + height: 38px; + width: 38px; + text-align: center; + cursor: pointer; +} +.mce-window-head .mce-close i { + color: #858585; +} +.mce-close:hover i { + color: #adadad; +} +.mce-window-head .mce-title { + line-height: 20px; + font-size: 20px; + font-weight: bold; + text-rendering: optimizelegibility; + padding-right: 20px; +} +.mce-window .mce-container-body { + display: block; +} +.mce-foot { + display: block; + background-color: #fff; + border-top: 1px solid #c5c5c5; +} +.mce-window-head .mce-dragh { + position: absolute; + top: 0; + left: 0; + cursor: move; + width: 90%; + height: 100%; +} +.mce-window iframe { + width: 100%; + height: 100%; +} +.mce-window-body .mce-listbox { + border-color: #ccc; +} +.mce-rtl .mce-window-head .mce-close { + position: absolute; + right: auto; + left: 15px; +} +.mce-rtl .mce-window-head .mce-dragh { + left: auto; + right: 0; +} +.mce-rtl .mce-window-head .mce-title { + direction: rtl; + text-align: right; +} +.mce-tooltip { + position: absolute; + padding: 5px; + opacity: 0.8; + filter: alpha(opacity=80); + zoom: 1; +} +.mce-tooltip-inner { + font-size: 11px; + background-color: #000; + color: white; + max-width: 200px; + padding: 5px 8px 4px 8px; + text-align: center; + white-space: normal; +} +.mce-tooltip-arrow { + position: absolute; + width: 0; + height: 0; + line-height: 0; + border: 5px dashed #000; +} +.mce-tooltip-arrow-n { + border-bottom-color: #000; +} +.mce-tooltip-arrow-s { + border-top-color: #000; +} +.mce-tooltip-arrow-e { + border-left-color: #000; +} +.mce-tooltip-arrow-w { + border-right-color: #000; +} +.mce-tooltip-nw, +.mce-tooltip-sw { + margin-left: -14px; +} +.mce-tooltip-ne, +.mce-tooltip-se { + margin-left: 14px; +} +.mce-tooltip-n .mce-tooltip-arrow { + top: 0; + left: 50%; + margin-left: -5px; + border-bottom-style: solid; + border-top: none; + border-left-color: transparent; + border-right-color: transparent; +} +.mce-tooltip-nw .mce-tooltip-arrow { + top: 0; + left: 10px; + border-bottom-style: solid; + border-top: none; + border-left-color: transparent; + border-right-color: transparent; +} +.mce-tooltip-ne .mce-tooltip-arrow { + top: 0; + right: 10px; + border-bottom-style: solid; + border-top: none; + border-left-color: transparent; + border-right-color: transparent; +} +.mce-tooltip-s .mce-tooltip-arrow { + bottom: 0; + left: 50%; + margin-left: -5px; + border-top-style: solid; + border-bottom: none; + border-left-color: transparent; + border-right-color: transparent; +} +.mce-tooltip-sw .mce-tooltip-arrow { + bottom: 0; + left: 10px; + border-top-style: solid; + border-bottom: none; + border-left-color: transparent; + border-right-color: transparent; +} +.mce-tooltip-se .mce-tooltip-arrow { + bottom: 0; + right: 10px; + border-top-style: solid; + border-bottom: none; + border-left-color: transparent; + border-right-color: transparent; +} +.mce-tooltip-e .mce-tooltip-arrow { + right: 0; + top: 50%; + margin-top: -5px; + border-left-style: solid; + border-right: none; + border-top-color: transparent; + border-bottom-color: transparent; +} +.mce-tooltip-w .mce-tooltip-arrow { + left: 0; + top: 50%; + margin-top: -5px; + border-right-style: solid; + border-left: none; + border-top-color: transparent; + border-bottom-color: transparent; +} +.mce-progress { + display: inline-block; + position: relative; + height: 20px; +} +.mce-progress .mce-bar-container { + display: inline-block; + width: 100px; + height: 100%; + margin-right: 8px; + border: 1px solid #ccc; + overflow: hidden; +} +.mce-progress .mce-text { + display: inline-block; + margin-top: auto; + margin-bottom: auto; + font-size: 14px; + width: 40px; + color: #333; +} +.mce-bar { + display: block; + width: 0; + height: 100%; + background-color: #d7d7d7; + -webkit-transition: width 0.2s ease; + transition: width 0.2s ease; +} +.mce-notification { + position: absolute; + background-color: #f0f0f0; + padding: 5px; + margin-top: 5px; + border-width: 1px; + border-style: solid; + border-color: #cccccc; + transition: transform 100ms ease-in, opacity 150ms ease-in; + opacity: 0; +} +.mce-notification.mce-in { + opacity: 1; +} +.mce-notification-success { + background-color: #dff0d8; + border-color: #d6e9c6; +} +.mce-notification-info { + background-color: #d9edf7; + border-color: #779ecb; +} +.mce-notification-warning { + background-color: #fcf8e3; + border-color: #faebcc; +} +.mce-notification-error { + background-color: #f2dede; + border-color: #ebccd1; +} +.mce-notification.mce-has-close { + padding-right: 15px; +} +.mce-notification .mce-ico { + margin-top: 5px; +} +.mce-notification-inner { + display: inline-block; + font-size: 14px; + margin: 5px 8px 4px 8px; + text-align: center; + white-space: normal; + color: #31708f; +} +.mce-notification-inner a { + text-decoration: underline; + cursor: pointer; +} +.mce-notification .mce-progress { + margin-right: 8px; +} +.mce-notification .mce-progress .mce-text { + margin-top: 5px; +} +.mce-notification *, +.mce-notification .mce-progress .mce-text { + color: #333333; +} +.mce-notification .mce-progress .mce-bar-container { + border-color: #cccccc; +} +.mce-notification .mce-progress .mce-bar-container .mce-bar { + background-color: #333333; +} +.mce-notification-success *, +.mce-notification-success .mce-progress .mce-text { + color: #3c763d; +} +.mce-notification-success .mce-progress .mce-bar-container { + border-color: #d6e9c6; +} +.mce-notification-success .mce-progress .mce-bar-container .mce-bar { + background-color: #3c763d; +} +.mce-notification-info *, +.mce-notification-info .mce-progress .mce-text { + color: #31708f; +} +.mce-notification-info .mce-progress .mce-bar-container { + border-color: #779ecb; +} +.mce-notification-info .mce-progress .mce-bar-container .mce-bar { + background-color: #31708f; +} +.mce-notification-warning *, +.mce-notification-warning .mce-progress .mce-text { + color: #8a6d3b; +} +.mce-notification-warning .mce-progress .mce-bar-container { + border-color: #faebcc; +} +.mce-notification-warning .mce-progress .mce-bar-container .mce-bar { + background-color: #8a6d3b; +} +.mce-notification-error *, +.mce-notification-error .mce-progress .mce-text { + color: #a94442; +} +.mce-notification-error .mce-progress .mce-bar-container { + border-color: #ebccd1; +} +.mce-notification-error .mce-progress .mce-bar-container .mce-bar { + background-color: #a94442; +} +.mce-notification .mce-close { + position: absolute; + top: 6px; + right: 8px; + font-size: 20px; + font-weight: bold; + line-height: 20px; + color: #858585; + cursor: pointer; + height: 20px; + overflow: hidden; +} +.mce-abs-layout { + position: relative; +} +body .mce-abs-layout-item, +.mce-abs-end { + position: absolute; +} +.mce-abs-end { + width: 1px; + height: 1px; +} +.mce-container-body.mce-abs-layout { + overflow: hidden; +} +.mce-btn { + border: 1px solid #b1b1b1; + border-color: transparent transparent transparent transparent; + position: relative; + text-shadow: 0 1px 1px rgba(255, 255, 255, 0.75); + display: inline-block; + *display: inline; + *zoom: 1; + background-color: #f0f0f0; +} +.mce-btn:hover, +.mce-btn:focus { + color: #333; + background-color: #e3e3e3; + border-color: #ccc; +} +.mce-btn.mce-disabled button, +.mce-btn.mce-disabled:hover button { + cursor: default; + opacity: 0.4; + filter: alpha(opacity=40); + zoom: 1; +} +.mce-btn.mce-active, +.mce-btn.mce-active:hover { + background-color: #dbdbdb; + border-color: #ccc; +} +.mce-btn:active { + background-color: #e0e0e0; + border-color: #ccc; +} +.mce-btn button { + padding: 4px 8px; + font-size: 14px; + line-height: 20px; + *line-height: 16px; + cursor: pointer; + color: #333; + text-align: center; + overflow: visible; + -webkit-appearance: none; +} +.mce-btn button::-moz-focus-inner { + border: 0; + padding: 0; +} +.mce-btn i { + text-shadow: 1px 1px none; +} +.mce-primary.mce-btn-has-text { + min-width: 50px; +} +.mce-primary { + color: #fff; + border: 1px solid transparent; + border-color: transparent; + background-color: #2d8ac7; +} +.mce-primary:hover, +.mce-primary:focus { + background-color: #257cb6; + border-color: transparent; +} +.mce-primary.mce-disabled button, +.mce-primary.mce-disabled:hover button { + cursor: default; + opacity: 0.4; + filter: alpha(opacity=40); + zoom: 1; +} +.mce-primary.mce-active, +.mce-primary.mce-active:hover, +.mce-primary:not(.mce-disabled):active { + background-color: #206ea1; +} +.mce-primary button, +.mce-primary button i { + color: #fff; + text-shadow: 1px 1px none; +} +.mce-btn .mce-txt { + font-size: inherit; + line-height: inherit; + color: inherit; +} +.mce-btn-large button { + padding: 9px 14px; + font-size: 16px; + line-height: normal; +} +.mce-btn-large i { + margin-top: 2px; +} +.mce-btn-small button { + padding: 1px 5px; + font-size: 12px; + *padding-bottom: 2px; +} +.mce-btn-small i { + line-height: 20px; + vertical-align: top; + *line-height: 18px; +} +.mce-btn .mce-caret { + margin-top: 8px; + margin-left: 0; +} +.mce-btn-small .mce-caret { + margin-top: 8px; + margin-left: 0; +} +.mce-caret { + display: inline-block; + *display: inline; + *zoom: 1; + width: 0; + height: 0; + vertical-align: top; + border-top: 4px solid #333; + border-right: 4px solid transparent; + border-left: 4px solid transparent; + content: ''; +} +.mce-disabled .mce-caret { + border-top-color: #aaa; +} +.mce-caret.mce-up { + border-bottom: 4px solid #333; + border-top: 0; +} +.mce-btn-flat { + border: 0; + background: transparent; + filter: none; +} +.mce-btn-flat:hover, +.mce-btn-flat.mce-active, +.mce-btn-flat:focus, +.mce-btn-flat:active { + border: 0; + background: #e6e6e6; + filter: none; +} +.mce-btn-has-text .mce-ico { + padding-right: 5px; +} +.mce-rtl .mce-btn button { + direction: rtl; +} +.mce-btn-group .mce-btn { + border-width: 1px; + margin: 0; + margin-left: 2px; +} +.mce-btn-group:not(:first-child) { + border-left: 1px solid #d9d9d9; + padding-left: 3px; + margin-left: 3px; +} +.mce-btn-group .mce-first { + margin-left: 0; +} +.mce-btn-group .mce-btn.mce-flow-layout-item { + margin: 0; +} +.mce-rtl .mce-btn-group .mce-btn { + margin-left: 0; + margin-right: 2px; +} +.mce-rtl .mce-btn-group .mce-first { + margin-right: 0; +} +.mce-rtl .mce-btn-group:not(:first-child) { + border-left: none; + border-right: 1px solid #d9d9d9; + padding-right: 4px; + margin-right: 4px; +} +.mce-checkbox { + cursor: pointer; +} +i.mce-i-checkbox { + margin: 0 3px 0 0; + border: 1px solid #c5c5c5; + background-color: #f0f0f0; + text-indent: -10em; + *font-size: 0; + *line-height: 0; + *text-indent: 0; + overflow: hidden; +} +.mce-checked i.mce-i-checkbox { + color: #333; + font-size: 16px; + line-height: 16px; + text-indent: 0; +} +.mce-checkbox:focus i.mce-i-checkbox, +.mce-checkbox.mce-focus i.mce-i-checkbox { + border: 1px solid rgba(82, 168, 236, 0.8); +} +.mce-checkbox.mce-disabled .mce-label, +.mce-checkbox.mce-disabled i.mce-i-checkbox { + color: #acacac; +} +.mce-checkbox .mce-label { + vertical-align: middle; +} +.mce-rtl .mce-checkbox { + direction: rtl; + text-align: right; +} +.mce-rtl i.mce-i-checkbox { + margin: 0 0 0 3px; +} +.mce-combobox { + position: relative; + display: inline-block; + *display: inline; + *zoom: 1; + *height: 32px; +} +.mce-combobox input { + border: 1px solid #c5c5c5; + border-right-color: #c5c5c5; + height: 28px; +} +.mce-combobox.mce-disabled input { + color: #adadad; +} +.mce-combobox .mce-btn { + border: 1px solid #c5c5c5; + border-left: 0; + margin: 0; +} +.mce-combobox button { + padding-right: 8px; + padding-left: 8px; +} +.mce-combobox.mce-disabled .mce-btn button { + cursor: default; + opacity: 0.4; + filter: alpha(opacity=40); + zoom: 1; +} +.mce-combobox .mce-status { + position: absolute; + right: 2px; + top: 50%; + line-height: 16px; + margin-top: -8px; + font-size: 12px; + width: 15px; + height: 15px; + text-align: center; + cursor: pointer; +} +.mce-combobox.mce-has-status input { + padding-right: 20px; +} +.mce-combobox.mce-has-open .mce-status { + right: 37px; +} +.mce-combobox .mce-status.mce-i-warning { + color: #c09853; +} +.mce-combobox .mce-status.mce-i-checkmark { + color: #468847; +} +.mce-menu.mce-combobox-menu { + border-top: 0; + margin-top: 0; + max-height: 200px; +} +.mce-menu.mce-combobox-menu .mce-menu-item { + padding: 4px 6px 4px 4px; + font-size: 11px; +} +.mce-menu.mce-combobox-menu .mce-menu-item-sep { + padding: 0; +} +.mce-menu.mce-combobox-menu .mce-text { + font-size: 11px; +} +.mce-menu.mce-combobox-menu .mce-menu-item-link, +.mce-menu.mce-combobox-menu .mce-menu-item-link b { + font-size: 11px; +} +.mce-menu.mce-combobox-menu .mce-text b { + font-size: 11px; +} +.mce-colorbox i { + border: 1px solid #c5c5c5; + width: 14px; + height: 14px; +} +.mce-colorbutton .mce-ico { + position: relative; +} +.mce-colorbutton-grid { + margin: 4px; +} +.mce-colorbutton button { + padding-right: 6px; + padding-left: 6px; +} +.mce-colorbutton .mce-preview { + padding-right: 3px; + display: block; + position: absolute; + left: 50%; + top: 50%; + margin-left: -17px; + margin-top: 7px; + background: gray; + width: 13px; + height: 2px; + overflow: hidden; +} +.mce-colorbutton.mce-btn-small .mce-preview { + margin-left: -16px; + padding-right: 0; + width: 16px; +} +.mce-colorbutton .mce-open { + padding-left: 4px; + padding-right: 4px; + border-left: 1px solid transparent; +} +.mce-colorbutton:hover .mce-open { + border-color: #ccc; +} +.mce-colorbutton.mce-btn-small .mce-open { + padding: 0 3px 0 3px; +} +.mce-rtl .mce-colorbutton { + direction: rtl; +} +.mce-rtl .mce-colorbutton .mce-preview { + margin-left: 0; + padding-right: 0; + padding-left: 3px; +} +.mce-rtl .mce-colorbutton.mce-btn-small .mce-preview { + margin-left: 0; + padding-right: 0; + padding-left: 2px; +} +.mce-rtl .mce-colorbutton .mce-open { + padding-left: 4px; + padding-right: 4px; + border-left: 0; +} +.mce-colorpicker { + position: relative; + width: 250px; + height: 220px; +} +.mce-colorpicker-sv { + position: absolute; + top: 0; + left: 0; + width: 90%; + height: 100%; + border: 1px solid #c5c5c5; + cursor: crosshair; + overflow: hidden; +} +.mce-colorpicker-h-chunk { + width: 100%; +} +.mce-colorpicker-overlay1, +.mce-colorpicker-overlay2 { + width: 100%; + height: 100%; + position: absolute; + top: 0; + left: 0; +} +.mce-colorpicker-overlay1 { + filter: progid:DXImageTransform.Microsoft.gradient(GradientType=1, startColorstr='#ffffff', endColorstr='#00ffffff'); + -ms-filter: "progid:DXImageTransform.Microsoft.gradient(GradientType=1,startColorstr='#ffffff', endColorstr='#00ffffff')"; + background: linear-gradient(to right, #fff, rgba(255, 255, 255, 0)); +} +.mce-colorpicker-overlay2 { + filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0, startColorstr='#00000000', endColorstr='#000000'); + -ms-filter: "progid:DXImageTransform.Microsoft.gradient(GradientType=0,startColorstr='#00000000', endColorstr='#000000')"; + background: linear-gradient(to bottom, rgba(0, 0, 0, 0), #000); +} +.mce-colorpicker-selector1 { + background: none; + position: absolute; + width: 12px; + height: 12px; + margin: -8px 0 0 -8px; + border: 1px solid black; + border-radius: 50%; +} +.mce-colorpicker-selector2 { + position: absolute; + width: 10px; + height: 10px; + border: 1px solid white; + border-radius: 50%; +} +.mce-colorpicker-h { + position: absolute; + top: 0; + right: 0; + width: 6.5%; + height: 100%; + border: 1px solid #c5c5c5; + cursor: crosshair; +} +.mce-colorpicker-h-marker { + margin-top: -4px; + position: absolute; + top: 0; + left: -1px; + width: 100%; + border: 1px solid #333; + background: #fff; + height: 4px; + z-index: 100; +} +.mce-path { + display: inline-block; + *display: inline; + *zoom: 1; + padding: 8px; + white-space: normal; +} +.mce-path .mce-txt { + display: inline-block; + padding-right: 3px; +} +.mce-path .mce-path-body { + display: inline-block; +} +.mce-path-item { + display: inline-block; + *display: inline; + *zoom: 1; + cursor: pointer; + color: #333; +} +.mce-path-item:hover { + text-decoration: underline; +} +.mce-path-item:focus { + background: #666; + color: #fff; +} +.mce-path .mce-divider { + display: inline; +} +.mce-disabled .mce-path-item { + color: #aaa; +} +.mce-rtl .mce-path { + direction: rtl; +} +.mce-fieldset { + border: 0 solid #9e9e9e; +} +.mce-fieldset > .mce-container-body { + margin-top: -15px; +} +.mce-fieldset-title { + margin-left: 5px; + padding: 0 5px 0 5px; +} +.mce-fit-layout { + display: inline-block; + *display: inline; + *zoom: 1; +} +.mce-fit-layout-item { + position: absolute; +} +.mce-flow-layout-item { + display: inline-block; + *display: inline; + *zoom: 1; +} +.mce-flow-layout-item { + margin: 2px 0 2px 2px; +} +.mce-flow-layout-item.mce-last { + margin-right: 2px; +} +.mce-flow-layout { + white-space: normal; +} +.mce-tinymce-inline .mce-flow-layout { + white-space: nowrap; +} +.mce-rtl .mce-flow-layout { + text-align: right; + direction: rtl; +} +.mce-rtl .mce-flow-layout-item { + margin: 2px 2px 2px 0; +} +.mce-rtl .mce-flow-layout-item.mce-last { + margin-left: 2px; +} +.mce-iframe { + border: 0 solid rgba(0, 0, 0, 0.2); + width: 100%; + height: 100%; +} +.mce-infobox { + display: inline-block; + *display: inline; + *zoom: 1; + text-shadow: 0 1px 1px rgba(255, 255, 255, 0.75); + overflow: hidden; + border: 1px solid red; +} +.mce-infobox div { + display: block; + margin: 5px; +} +.mce-infobox div button { + position: absolute; + top: 50%; + right: 4px; + cursor: pointer; + margin-top: -8px; + display: none; +} +.mce-infobox div button:focus { + outline: 2px solid #ccc; +} +.mce-infobox.mce-has-help div { + margin-right: 25px; +} +.mce-infobox.mce-has-help button { + display: block; +} +.mce-infobox.mce-success { + background: #dff0d8; + border-color: #d6e9c6; +} +.mce-infobox.mce-success div { + color: #3c763d; +} +.mce-infobox.mce-warning { + background: #fcf8e3; + border-color: #faebcc; +} +.mce-infobox.mce-warning div { + color: #8a6d3b; +} +.mce-infobox.mce-error { + background: #f2dede; + border-color: #ebccd1; +} +.mce-infobox.mce-error div { + color: #a94442; +} +.mce-rtl .mce-infobox div { + text-align: right; + direction: rtl; +} +.mce-label { + display: inline-block; + *display: inline; + *zoom: 1; + text-shadow: 0 1px 1px rgba(255, 255, 255, 0.75); + overflow: hidden; +} +.mce-label.mce-autoscroll { + overflow: auto; +} +.mce-label.mce-disabled { + color: #aaa; +} +.mce-label.mce-multiline { + white-space: pre-wrap; +} +.mce-label.mce-success { + color: #468847; +} +.mce-label.mce-warning { + color: #c09853; +} +.mce-label.mce-error { + color: #b94a48; +} +.mce-rtl .mce-label { + text-align: right; + direction: rtl; +} +.mce-menubar .mce-menubtn { + border-color: transparent; + background: transparent; + filter: none; +} +.mce-menubar .mce-menubtn button { + color: #333; +} +.mce-menubar { + border: 1px solid rgba(217, 217, 217, 0.52); +} +.mce-menubar .mce-menubtn button span { + color: #333; +} +.mce-menubar .mce-caret { + border-top-color: #333; +} +.mce-menubar .mce-menubtn:hover, +.mce-menubar .mce-menubtn.mce-active, +.mce-menubar .mce-menubtn:focus { + border-color: #ccc; + background: #fff; + filter: none; +} +.mce-menubtn button { + color: #333; +} +.mce-menubtn.mce-btn-small span { + font-size: 12px; +} +.mce-menubtn.mce-fixed-width span { + display: inline-block; + overflow-x: hidden; + text-overflow: ellipsis; + width: 90px; +} +.mce-menubtn.mce-fixed-width.mce-btn-small span { + width: 70px; +} +.mce-menubtn .mce-caret { + *margin-top: 6px; +} +.mce-rtl .mce-menubtn button { + direction: rtl; + text-align: right; +} +.mce-menu-item { + display: block; + padding: 6px 15px 6px 12px; + clear: both; + font-weight: normal; + line-height: 20px; + color: #333; + white-space: nowrap; + cursor: pointer; + line-height: normal; + border-left: 4px solid transparent; + margin-bottom: 1px; +} +.mce-menu-item .mce-ico, +.mce-menu-item .mce-text { + color: #333; +} +.mce-menu-item.mce-disabled .mce-text, +.mce-menu-item.mce-disabled .mce-ico { + color: #adadad; +} +.mce-menu-item:hover .mce-text, +.mce-menu-item.mce-selected .mce-text, +.mce-menu-item:focus .mce-text { + color: white; +} +.mce-menu-item:hover .mce-ico, +.mce-menu-item.mce-selected .mce-ico, +.mce-menu-item:focus .mce-ico { + color: white; +} +.mce-menu-item.mce-disabled:hover { + background: #ccc; +} +.mce-menu-shortcut { + display: inline-block; + color: #adadad; +} +.mce-menu-shortcut { + display: inline-block; + *display: inline; + *zoom: 1; + padding: 0 15px 0 20px; +} +.mce-menu-item:hover .mce-menu-shortcut, +.mce-menu-item.mce-selected .mce-menu-shortcut, +.mce-menu-item:focus .mce-menu-shortcut { + color: white; +} +.mce-menu-item .mce-caret { + margin-top: 4px; + *margin-top: 3px; + margin-right: 6px; + border-top: 4px solid transparent; + border-bottom: 4px solid transparent; + border-left: 4px solid #333; +} +.mce-menu-item.mce-selected .mce-caret, +.mce-menu-item:focus .mce-caret, +.mce-menu-item:hover .mce-caret { + border-left-color: white; +} +.mce-menu-align .mce-menu-shortcut { + *margin-top: -2px; +} +.mce-menu-align .mce-menu-shortcut, +.mce-menu-align .mce-caret { + position: absolute; + right: 0; +} +.mce-menu-item.mce-active i { + visibility: visible; +} +.mce-menu-item-normal.mce-active { + background-color: #3498db; +} +.mce-menu-item-preview.mce-active { + border-left: 5px solid #aaa; +} +.mce-menu-item-normal.mce-active .mce-text { + color: white; +} +.mce-menu-item-normal.mce-active:hover .mce-text, +.mce-menu-item-normal.mce-active:hover .mce-ico { + color: white; +} +.mce-menu-item-normal.mce-active:focus .mce-text, +.mce-menu-item-normal.mce-active:focus .mce-ico { + color: white; +} +.mce-menu-item:hover, +.mce-menu-item.mce-selected, +.mce-menu-item:focus { + text-decoration: none; + color: white; + background-color: #2d8ac7; +} +.mce-menu-item-link { + color: #093; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.mce-menu-item-link b { + color: #093; +} +.mce-menu-item-ellipsis { + display: block; + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; +} +.mce-menu-item:hover *, +.mce-menu-item.mce-selected *, +.mce-menu-item:focus * { + color: white; +} +div.mce-menu .mce-menu-item-sep, +.mce-menu-item-sep:hover { + border: 0; + padding: 0; + height: 1px; + margin: 9px 1px; + overflow: hidden; + background: transparent; + border-bottom: 1px solid rgba(0, 0, 0, 0.1); + cursor: default; + filter: none; +} +div.mce-menu .mce-menu-item b { + font-weight: bold; +} +.mce-menu-item-indent-1 { + padding-left: 20px; +} +.mce-menu-item-indent-2 { + padding-left: 35px; +} +.mce-menu-item-indent-2 { + padding-left: 35px; +} +.mce-menu-item-indent-3 { + padding-left: 40px; +} +.mce-menu-item-indent-4 { + padding-left: 45px; +} +.mce-menu-item-indent-5 { + padding-left: 50px; +} +.mce-menu-item-indent-6 { + padding-left: 55px; +} +.mce-menu.mce-rtl { + direction: rtl; +} +.mce-rtl .mce-menu-item { + text-align: right; + direction: rtl; + padding: 6px 12px 6px 15px; +} +.mce-menu-align.mce-rtl .mce-menu-shortcut, +.mce-menu-align.mce-rtl .mce-caret { + right: auto; + left: 0; +} +.mce-rtl .mce-menu-item .mce-caret { + margin-left: 6px; + margin-right: 0; + border-right: 4px solid #333; + border-left: 0; +} +.mce-rtl .mce-menu-item.mce-selected .mce-caret, +.mce-rtl .mce-menu-item:focus .mce-caret, +.mce-rtl .mce-menu-item:hover .mce-caret { + border-left-color: transparent; + border-right-color: white; +} +.mce-throbber { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + opacity: 0.6; + filter: alpha(opacity=60); + zoom: 1; + background: #fff url('img/loader.gif') no-repeat center center; +} +.mce-throbber-inline { + position: static; + height: 50px; +} +.mce-menu .mce-throbber-inline { + height: 25px; + background-size: contain; +} +.mce-menu { + position: absolute; + left: 0; + top: 0; + filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); + background: transparent; + z-index: 1000; + padding: 5px 0 5px 0; + margin: -1px 0 0; + min-width: 160px; + background: #fff; + border: 1px solid #989898; + border: 1px solid rgba(0, 0, 0, 0.2); + z-index: 1002; + max-height: 400px; + overflow: auto; + overflow-x: hidden; +} +.mce-menu i { + display: none; +} +.mce-menu-has-icons i { + display: inline-block; + *display: inline; +} +.mce-menu-sub-tr-tl { + margin: -6px 0 0 -1px; +} +.mce-menu-sub-br-bl { + margin: 6px 0 0 -1px; +} +.mce-menu-sub-tl-tr { + margin: -6px 0 0 1px; +} +.mce-menu-sub-bl-br { + margin: 6px 0 0 1px; +} +.mce-listbox button { + text-align: left; + padding-right: 20px; + position: relative; +} +.mce-listbox .mce-caret { + position: absolute; + margin-top: -2px; + right: 8px; + top: 50%; +} +.mce-rtl .mce-listbox .mce-caret { + right: auto; + left: 8px; +} +.mce-rtl .mce-listbox button { + padding-right: 10px; + padding-left: 20px; +} +.mce-container-body .mce-resizehandle { + position: absolute; + right: 0; + bottom: 0; + width: 16px; + height: 16px; + visibility: visible; + cursor: s-resize; + margin: 0; +} +.mce-container-body .mce-resizehandle-both { + cursor: se-resize; +} +i.mce-i-resize { + color: #333; +} +.mce-selectbox { + background: #fff; + border: 1px solid #c5c5c5; +} +.mce-slider { + border: 1px solid #aaa; + background: #eee; + width: 100px; + height: 10px; + position: relative; + display: block; +} +.mce-slider.mce-vertical { + width: 10px; + height: 100px; +} +.mce-slider-handle { + border: 1px solid #bbb; + background: #ddd; + display: block; + width: 13px; + height: 13px; + position: absolute; + top: 0; + left: 0; + margin-left: -1px; + margin-top: -2px; +} +.mce-slider-handle:focus { + background: #bbb; +} +.mce-spacer { + visibility: hidden; +} +.mce-splitbtn .mce-open { + border-left: 1px solid transparent; +} +.mce-splitbtn:hover .mce-open { + border-left-color: #ccc; +} +.mce-splitbtn button { + padding-right: 6px; + padding-left: 6px; +} +.mce-splitbtn .mce-open { + padding-right: 4px; + padding-left: 4px; +} +.mce-splitbtn .mce-open.mce-active { + background-color: #dbdbdb; + outline: 1px solid #ccc; +} +.mce-splitbtn.mce-btn-small .mce-open { + padding: 0 3px 0 3px; +} +.mce-rtl .mce-splitbtn { + direction: rtl; + text-align: right; +} +.mce-rtl .mce-splitbtn button { + padding-right: 4px; + padding-left: 4px; +} +.mce-rtl .mce-splitbtn .mce-open { + border-left: 0; +} +.mce-stack-layout-item { + display: block; +} +.mce-tabs { + display: block; + border-bottom: 1px solid #c5c5c5; +} +.mce-tabs, +.mce-tabs + .mce-container-body { + background: #fff; +} +.mce-tab { + display: inline-block; + *display: inline; + *zoom: 1; + border: 1px solid #c5c5c5; + border-width: 0 1px 0 0; + background: #ffffff; + padding: 8px; + text-shadow: 0 1px 1px rgba(255, 255, 255, 0.75); + height: 13px; + cursor: pointer; +} +.mce-tab:hover { + background: #fdfdfd; +} +.mce-tab.mce-active { + background: #fdfdfd; + border-bottom-color: transparent; + margin-bottom: -1px; + height: 14px; +} +.mce-rtl .mce-tabs { + text-align: right; + direction: rtl; +} +.mce-rtl .mce-tab { + border-width: 0 0 0 1px; +} +.mce-textbox { + background: #fff; + border: 1px solid #c5c5c5; + display: inline-block; + -webkit-transition: border linear 0.2s, box-shadow linear 0.2s; + transition: border linear 0.2s, box-shadow linear 0.2s; + height: 28px; + resize: none; + padding: 0 4px 0 4px; + white-space: pre-wrap; + *white-space: pre; + color: #333; +} +.mce-textbox:focus, +.mce-textbox.mce-focus { + border-color: #3498db; +} +.mce-placeholder .mce-textbox { + color: #aaa; +} +.mce-textbox.mce-multiline { + padding: 4px; + height: auto; +} +.mce-textbox.mce-disabled { + color: #adadad; +} +.mce-rtl .mce-textbox { + text-align: right; + direction: rtl; +} +@font-face { + font-family: 'tinymce'; + src: url('fonts/tinymce.eot'); + src: url('fonts/tinymce.eot?#iefix') format('embedded-opentype'), + url('fonts/tinymce.woff') format('woff'), + url('fonts/tinymce.ttf') format('truetype'), + url('fonts/tinymce.svg#tinymce') format('svg'); + font-weight: normal; + font-style: normal; +} +@font-face { + font-family: 'tinymce-small'; + src: url('fonts/tinymce-small.eot'); + src: url('fonts/tinymce-small.eot?#iefix') format('embedded-opentype'), + url('fonts/tinymce-small.woff') format('woff'), + url('fonts/tinymce-small.ttf') format('truetype'), + url('fonts/tinymce-small.svg#tinymce') format('svg'); + font-weight: normal; + font-style: normal; +} +.mce-ico { + font-family: 'tinymce', Arial; + font-style: normal; + font-weight: normal; + font-variant: normal; + font-size: 16px; + line-height: 16px; + speak: none; + vertical-align: text-top; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + display: inline-block; + background: transparent center center; + background-size: cover; + width: 16px; + height: 16px; + color: #333; +} +.mce-btn-small .mce-ico { + font-family: 'tinymce-small', Arial; +} +.mce-i-save:before { + content: '\e000'; +} +.mce-i-newdocument:before { + content: '\e001'; +} +.mce-i-fullpage:before { + content: '\e002'; +} +.mce-i-alignleft:before { + content: '\e003'; +} +.mce-i-aligncenter:before { + content: '\e004'; +} +.mce-i-alignright:before { + content: '\e005'; +} +.mce-i-alignjustify:before { + content: '\e006'; +} +.mce-i-alignnone:before { + content: '\e003'; +} +.mce-i-cut:before { + content: '\e007'; +} +.mce-i-paste:before { + content: '\e008'; +} +.mce-i-searchreplace:before { + content: '\e009'; +} +.mce-i-bullist:before { + content: '\e00a'; +} +.mce-i-numlist:before { + content: '\e00b'; +} +.mce-i-indent:before { + content: '\e00c'; +} +.mce-i-outdent:before { + content: '\e00d'; +} +.mce-i-blockquote:before { + content: '\e00e'; +} +.mce-i-undo:before { + content: '\e00f'; +} +.mce-i-redo:before { + content: '\e010'; +} +.mce-i-link:before { + content: '\e011'; +} +.mce-i-unlink:before { + content: '\e012'; +} +.mce-i-anchor:before { + content: '\e013'; +} +.mce-i-image:before { + content: '\e014'; +} +.mce-i-media:before { + content: '\e015'; +} +.mce-i-help:before { + content: '\e016'; +} +.mce-i-code:before { + content: '\e017'; +} +.mce-i-insertdatetime:before { + content: '\e018'; +} +.mce-i-preview:before { + content: '\e019'; +} +.mce-i-forecolor:before { + content: '\e01a'; +} +.mce-i-backcolor:before { + content: '\e01a'; +} +.mce-i-table:before { + content: '\e01b'; +} +.mce-i-hr:before { + content: '\e01c'; +} +.mce-i-removeformat:before { + content: '\e01d'; +} +.mce-i-subscript:before { + content: '\e01e'; +} +.mce-i-superscript:before { + content: '\e01f'; +} +.mce-i-charmap:before { + content: '\e020'; +} +.mce-i-emoticons:before { + content: '\e021'; +} +.mce-i-print:before { + content: '\e022'; +} +.mce-i-fullscreen:before { + content: '\e023'; +} +.mce-i-spellchecker:before { + content: '\e024'; +} +.mce-i-nonbreaking:before { + content: '\e025'; +} +.mce-i-template:before { + content: '\e026'; +} +.mce-i-pagebreak:before { + content: '\e027'; +} +.mce-i-restoredraft:before { + content: '\e028'; +} +.mce-i-bold:before { + content: '\e02a'; +} +.mce-i-italic:before { + content: '\e02b'; +} +.mce-i-underline:before { + content: '\e02c'; +} +.mce-i-strikethrough:before { + content: '\e02d'; +} +.mce-i-visualchars:before { + content: '\e02e'; +} +.mce-i-visualblocks:before { + content: '\e02e'; +} +.mce-i-ltr:before { + content: '\e02f'; +} +.mce-i-rtl:before { + content: '\e030'; +} +.mce-i-copy:before { + content: '\e031'; +} +.mce-i-resize:before { + content: '\e032'; +} +.mce-i-browse:before { + content: '\e034'; +} +.mce-i-pastetext:before { + content: '\e035'; +} +.mce-i-rotateleft:before { + content: '\eaa8'; +} +.mce-i-rotateright:before { + content: '\eaa9'; +} +.mce-i-crop:before { + content: '\ee78'; +} +.mce-i-editimage:before { + content: '\e915'; +} +.mce-i-options:before { + content: '\ec6a'; +} +.mce-i-flipv:before { + content: '\eaaa'; +} +.mce-i-fliph:before { + content: '\eaac'; +} +.mce-i-zoomin:before { + content: '\eb35'; +} +.mce-i-zoomout:before { + content: '\eb36'; +} +.mce-i-sun:before { + content: '\eccc'; +} +.mce-i-moon:before { + content: '\eccd'; +} +.mce-i-arrowleft:before { + content: '\edc0'; +} +.mce-i-arrowright:before { + content: '\e93c'; +} +.mce-i-drop:before { + content: '\e935'; +} +.mce-i-contrast:before { + content: '\ecd4'; +} +.mce-i-sharpen:before { + content: '\eba7'; +} +.mce-i-resize2:before { + content: '\edf9'; +} +.mce-i-orientation:before { + content: '\e601'; +} +.mce-i-invert:before { + content: '\e602'; +} +.mce-i-gamma:before { + content: '\e600'; +} +.mce-i-remove:before { + content: '\ed6a'; +} +.mce-i-tablerowprops:before { + content: '\e604'; +} +.mce-i-tablecellprops:before { + content: '\e605'; +} +.mce-i-table2:before { + content: '\e606'; +} +.mce-i-tablemergecells:before { + content: '\e607'; +} +.mce-i-tableinsertcolbefore:before { + content: '\e608'; +} +.mce-i-tableinsertcolafter:before { + content: '\e609'; +} +.mce-i-tableinsertrowbefore:before { + content: '\e60a'; +} +.mce-i-tableinsertrowafter:before { + content: '\e60b'; +} +.mce-i-tablesplitcells:before { + content: '\e60d'; +} +.mce-i-tabledelete:before { + content: '\e60e'; +} +.mce-i-tableleftheader:before { + content: '\e62a'; +} +.mce-i-tabletopheader:before { + content: '\e62b'; +} +.mce-i-tabledeleterow:before { + content: '\e800'; +} +.mce-i-tabledeletecol:before { + content: '\e801'; +} +.mce-i-codesample:before { + content: '\e603'; +} +.mce-i-fill:before { + content: '\e902'; +} +.mce-i-borderwidth:before { + content: '\e903'; +} +.mce-i-line:before { + content: '\e904'; +} +.mce-i-count:before { + content: '\e905'; +} +.mce-i-translate:before { + content: '\e907'; +} +.mce-i-drag:before { + content: '\e908'; +} +.mce-i-home:before { + content: '\e90b'; +} +.mce-i-upload:before { + content: '\e914'; +} +.mce-i-bubble:before { + content: '\e91c'; +} +.mce-i-user:before { + content: '\e91d'; +} +.mce-i-lock:before { + content: '\e926'; +} +.mce-i-unlock:before { + content: '\e927'; +} +.mce-i-settings:before { + content: '\e928'; +} +.mce-i-remove2:before { + content: '\e92a'; +} +.mce-i-menu:before { + content: '\e92d'; +} +.mce-i-warning:before { + content: '\e930'; +} +.mce-i-question:before { + content: '\e931'; +} +.mce-i-pluscircle:before { + content: '\e932'; +} +.mce-i-info:before { + content: '\e933'; +} +.mce-i-notice:before { + content: '\e934'; +} +.mce-i-arrowup:before { + content: '\e93b'; +} +.mce-i-arrowdown:before { + content: '\e93d'; +} +.mce-i-arrowup2:before { + content: '\e93f'; +} +.mce-i-arrowdown2:before { + content: '\e940'; +} +.mce-i-menu2:before { + content: '\e941'; +} +.mce-i-newtab:before { + content: '\e961'; +} +.mce-i-a11y:before { + content: '\e900'; +} +.mce-i-plus:before { + content: '\e93a'; +} +.mce-i-insert:before { + content: '\e93a'; +} +.mce-i-minus:before { + content: '\e939'; +} +.mce-i-books:before { + content: '\e911'; +} +.mce-i-reload:before { + content: '\e906'; +} +.mce-i-toc:before { + content: '\e901'; +} +.mce-i-checkmark:before { + content: '\e033'; +} +.mce-i-checkbox:before, +.mce-i-selected:before { + content: '\e033'; +} +.mce-i-insert { + font-size: 14px; +} +.mce-i-selected { + visibility: hidden; +} +i.mce-i-backcolor { + text-shadow: none; + background: #bbb; +} diff --git a/packages/admin-web-angular/src/environments/model.ts b/packages/admin-web-angular/src/environments/model.ts new file mode 100644 index 0000000..0c8db37 --- /dev/null +++ b/packages/admin-web-angular/src/environments/model.ts @@ -0,0 +1,39 @@ +export interface Environment { + production: boolean; + + SERVICES_ENDPOINT: string; + HTTPS_SERVICES_ENDPOINT: string; + GQL_ENDPOINT: string; + GQL_SUBSCRIPTIONS_ENDPOINT: string; + + GOOGLE_MAPS_API_KEY: string; + + DEFAULT_LATITUDE: number; + DEFAULT_LONGITUDE: number; + + NO_INTERNET_LOGO: string; + + MAP_MERCHANT_ICON_LINK: string; + + MAP_USER_ICON_LINK: string; + + MAP_CARRIER_ICON_LINK: string; + + API_FILE_UPLOAD_URL: string; + + COMPANY_NAME: string; + COMPANY_SITE_LINK: string; + COMPANY_GITHUB_LINK: string; + COMPANY_FACEBOOK_LINK: string; + COMPANY_TWITTER_LINK: string; + COMPANY_LINKEDIN_LINK: string; + + GENERATE_PASSWORD_CHARSET: string; + + CURRENCY_SYMBOL: string; + + DEFAULT_LANGUAGE: string; + // For maintenance micro service + SETTINGS_APP_TYPE?: string; + SETTINGS_MAINTENANCE_API_URL?: string; +} diff --git a/packages/admin-web-angular/src/favicon.ico b/packages/admin-web-angular/src/favicon.ico new file mode 100644 index 0000000..79c60ff Binary files /dev/null and b/packages/admin-web-angular/src/favicon.ico differ diff --git a/packages/admin-web-angular/src/favicon.png b/packages/admin-web-angular/src/favicon.png new file mode 100644 index 0000000..d053869 Binary files /dev/null and b/packages/admin-web-angular/src/favicon.png differ diff --git a/packages/admin-web-angular/src/favicon16x16.png b/packages/admin-web-angular/src/favicon16x16.png new file mode 100644 index 0000000..d053869 Binary files /dev/null and b/packages/admin-web-angular/src/favicon16x16.png differ diff --git a/packages/admin-web-angular/src/favicon32x32.png b/packages/admin-web-angular/src/favicon32x32.png new file mode 100644 index 0000000..5864e89 Binary files /dev/null and b/packages/admin-web-angular/src/favicon32x32.png differ diff --git a/packages/admin-web-angular/src/favicon48x48.png b/packages/admin-web-angular/src/favicon48x48.png new file mode 100644 index 0000000..41741bc Binary files /dev/null and b/packages/admin-web-angular/src/favicon48x48.png differ diff --git a/packages/admin-web-angular/src/index.html b/packages/admin-web-angular/src/index.html new file mode 100644 index 0000000..b3a0446 --- /dev/null +++ b/packages/admin-web-angular/src/index.html @@ -0,0 +1,106 @@ + + + + + Ever Admin + + + + + + + + Loading... + + +
+
+
+
+
+
+
+
+ + diff --git a/packages/admin-web-angular/src/main.ts b/packages/admin-web-angular/src/main.ts new file mode 100644 index 0000000..74bdca5 --- /dev/null +++ b/packages/admin-web-angular/src/main.ts @@ -0,0 +1,19 @@ +/** + * @license + * Copyright Akveo. All Rights Reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ + +import { enableProdMode } from '@angular/core'; +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; + +import { AppModule } from './app/app.module'; +import { environment } from 'environments/environment'; + +if (environment.production) { + enableProdMode(); +} + +platformBrowserDynamic() + .bootstrapModule(AppModule) + .catch((err) => console.error(err)); diff --git a/packages/admin-web-angular/src/pm2bootstrap.ts b/packages/admin-web-angular/src/pm2bootstrap.ts new file mode 100644 index 0000000..02262d9 --- /dev/null +++ b/packages/admin-web-angular/src/pm2bootstrap.ts @@ -0,0 +1,56 @@ +require('dotenv').config(); + +const pm2 = require('pm2'); + +import { env } from '../scripts/env'; + +const MACHINE_NAME = process.env.KEYMETRICS_MACHINE_NAME; +const PRIVATE_KEY = process.env.KEYMETRICS_SECRET_KEY; +const PUBLIC_KEY = process.env.KEYMETRICS_PUBLIC_KEY; +const appName = process.env.PM2_APP_NAME || 'EverAdmin'; +const instances = env.WEB_CONCURRENCY; +const maxMemory = env.WEB_MEMORY; +const port = env.PORT; + +pm2.connect(function () { + pm2.start( + { + script: './dist/out-tsc/packages/admin-web-angular/src/app.js', + name: appName, // ----> THESE ATTRIBUTES ARE OPTIONAL: + exec_mode: 'fork', // ----> https://github.com/Unitech/PM2/blob/master/ADVANCED_README.md#schema + instances, + max_memory_restart: maxMemory + 'M', // Auto restart if process taking more than XXmo + env: { + // If needed declare some environment variables + NODE_ENV: 'production', + PORT: port, + KEYMETRICS_PUBLIC: PUBLIC_KEY, + KEYMETRICS_SECRET: PRIVATE_KEY, + }, + post_update: ['yarn install'], // Commands to execute once we do a pull from Keymetrics + }, + function () { + pm2.dump(console.error); + // Display logs in standard output + pm2.launchBus(function (err, bus) { + console.log('[PM2] Log streaming started'); + + bus.on('log:out', function (packet) { + console.log( + '[App:%s] %s', + packet.process.name, + packet.data + ); + }); + + bus.on('log:err', function (packet) { + console.error( + '[App:%s][Err] %s', + packet.process.name, + packet.data + ); + }); + }); + } + ); +}); diff --git a/packages/admin-web-angular/src/polyfills.ts b/packages/admin-web-angular/src/polyfills.ts new file mode 100644 index 0000000..09aba8b --- /dev/null +++ b/packages/admin-web-angular/src/polyfills.ts @@ -0,0 +1,71 @@ +/** + * @license + * Copyright Akveo. All Rights Reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ +/** + * This file includes polyfills needed by Angular and is loaded before the app. + * You can add your own extra polyfills to this file. + * + * This file is divided into 2 sections: + * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. + * 2. Application imports. Files imported after ZoneJS that should be loaded before your main + * file. + * + * The current setup is for so-called "evergreen" browsers; the last versions of browsers that + * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), + * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. + * + * Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html + */ + +/*************************************************************************************************** + * BROWSER POLYFILLS + */ + +/** IE9, IE10 and IE11 requires all of the following polyfills. */ +import 'core-js/es6/symbol'; +import 'core-js/es6/object'; +import 'core-js/es6/function'; +import 'core-js/es6/parse-int'; +import 'core-js/es6/parse-float'; +import 'core-js/es6/number'; +import 'core-js/es6/math'; +import 'core-js/es6/string'; +import 'core-js/es6/date'; +import 'core-js/es6/array'; +import 'core-js/es6/regexp'; +import 'core-js/es6/map'; +import 'core-js/es6/set'; // Run `npm install --save classlist.js`. // Run `npm install --save web-animations-js`. + +/** Evergreen browsers require these. */ +import 'core-js/es6/reflect'; +import 'core-js/es7/reflect'; + +/*************************************************************************************************** + * Zone JS is required by Angular itself. + */ +import 'zone.js/dist/zone'; // Included with Angular CLI. + +/*************************************************************************************************** + * APPLICATION IMPORTS + */ + +/** + * Date, currency, decimal and percent pipes. + * Needed for: All but Chrome, Firefox, Edge, IE11 and Safari 10 + */ +import 'core-js/es7/array'; +import 'core-js/es7/object'; + +/****************************************************************** + * Load `$localize` - used if i18n tags appear in Angular templates. + */ + import '@angular/localize/init'; + +if (typeof SVGElement.prototype.contains === 'undefined') { + SVGElement.prototype.contains = HTMLDivElement.prototype.contains; +} + +(window as any).global = window; +global.Buffer = global.Buffer || require('buffer').Buffer; diff --git a/packages/admin-web-angular/src/test.ts b/packages/admin-web-angular/src/test.ts new file mode 100644 index 0000000..7165c98 --- /dev/null +++ b/packages/admin-web-angular/src/test.ts @@ -0,0 +1,40 @@ +/** + * @license + * Copyright Akveo. All Rights Reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ + +// This file is required by karma.conf.js and loads recursively all the .spec and framework files + +import 'zone.js/dist/long-stack-trace-zone'; +import 'zone.js/dist/proxy.js'; +import 'zone.js/dist/sync-test'; +import 'zone.js/dist/jasmine-patch'; +import 'zone.js/dist/async-test'; +import 'zone.js/dist/fake-async-test'; +import { getTestBed } from '@angular/core/testing'; +import { + BrowserDynamicTestingModule, + platformBrowserDynamicTesting, +} from '@angular/platform-browser-dynamic/testing'; + +// Unfortunately there's no typing for the `__karma__` variable. Just declare it as any. +declare const __karma__: any; +declare const require: any; + +// Prevent Karma from running prematurely. +__karma__.loaded = function () {}; + +// First, initialize the Angular testing environment. +getTestBed().initTestEnvironment( + BrowserDynamicTestingModule, + platformBrowserDynamicTesting(), { + teardown: { destroyAfterEach: false } +} +); +// Then we find all the tests. +const context = require.context('./', true, /\.spec\.ts$/); +// And load the modules. +context.keys().map(context); +// Finally, start Karma to run the tests. +__karma__.start(); diff --git a/packages/admin-web-angular/src/vendor.ts b/packages/admin-web-angular/src/vendor.ts new file mode 100644 index 0000000..d41fdb2 --- /dev/null +++ b/packages/admin-web-angular/src/vendor.ts @@ -0,0 +1,9 @@ +// Angular +import '@angular/animations'; +import '@angular/common'; +import '@angular/core'; +import '@angular/forms'; +import '@angular/router'; + +// RxJS +import 'rxjs'; diff --git a/packages/admin-web-angular/tsconfig.commonjs.json b/packages/admin-web-angular/tsconfig.commonjs.json new file mode 100644 index 0000000..fa2000b --- /dev/null +++ b/packages/admin-web-angular/tsconfig.commonjs.json @@ -0,0 +1,44 @@ +{ + "extends": "../../tsconfig.base.json", + "compileOnSave": false, + "compilerOptions": { + "module": "commonjs", + "target": "es6", + "baseUrl": "./src", + "experimentalDecorators": true, + "outDir": "./build", + "rootDir": "./", + "types": ["node", "reflect-metadata", "googlemaps", "jasmine"], + "paths": { + "@angular/*": ["../node_modules/@angular/*"], + "@modules/server.common/*": ["../../common/src/*"], + "@modules/client.common.angular2/*": ["../../common-angular/src/*"], + "@pyro/*": ["../../common/src/@pyro/*"], + "mongoose": ["../../common-angular/src/mongoose-placeholder"], + "typeorm": ["../../common-angular/src/typeorm-placeholder"], + "angular2-wizard": [ + "../node_modules/@ever-co/angular2-wizard/dist/*" + ], + "@app/*": ["app/*"], + "environments/*": ["./environments/*"], + "core-js/es7/reflect": [ + "../../../node_modules/core-js/proposals/reflect-metadata" + ], + "core-js/es7/array": ["../../../node_modules/core-js/stable/array"], + "core-js/es7/object": [ + "../../../node_modules/core-js/stable/object" + ], + "core-js/es6/*": ["../../../node_modules/core-js/es"] + } + }, + "angularCompilerOptions": { + "annotateForClosureCompiler": true, + "skipTemplateCodegen": true, + "strictMetadataEmit": true, + "fullTemplateTypeCheck": true, + "strictInjectionParameters": true, + "enableResourceInlining": true + }, + "files": ["./src/app/app.module.ts", "./src/main.ts", "./src/polyfills.ts"], + "include": ["./src"] +} diff --git a/packages/admin-web-angular/tsconfig.json b/packages/admin-web-angular/tsconfig.json new file mode 100644 index 0000000..8b0b397 --- /dev/null +++ b/packages/admin-web-angular/tsconfig.json @@ -0,0 +1,54 @@ +{ + "extends": "../../tsconfig.base.json", + "compileOnSave": false, + "compilerOptions": { + "outDir": "./dist/out-tsc", + "module": "commonjs", + "target": "es2016", + "baseUrl": "./src", + "experimentalDecorators": true, + "types": ["node", "jest", "reflect-metadata", "googlemaps", "jasmine"], + "paths": { + "@angular/*": ["../node_modules/@angular/*"], + "@nebular/*": [ + "../../../node_modules/@nebular/*" + ], + "@modules/server.common/*": ["../../common/src/*"], + "@modules/client.common.angular2/*": ["../../common-angular/src/*"], + "@pyro/*": ["../../common/src/@pyro/*"], + "mongoose": ["../../common-angular/src/mongoose-placeholder"], + "typeorm": ["../../common-angular/src/typeorm-placeholder"], + "@ngx-translate/*": [ + "@ngx-translate/*", + "../node_modules/@ngx-translate/*" + ], + "angular2-wizard": [ + "../node_modules/@ever-co/angular2-wizard/dist/*" + ], + "@app/*": ["app/*"], + "environments/*": ["./environments/*"], + "core-js/es7/reflect": [ + "../../../node_modules/core-js/proposals/reflect-metadata" + ], + "core-js/es7/array": ["../../../node_modules/core-js/stable/array"], + "core-js/es7/object": [ + "../../../node_modules/core-js/stable/object" + ], + "core-js/es6/*": ["../../../node_modules/core-js/es"] + } + }, + "files": ["src/main.ts", "src/polyfills.ts"], + "angularCompilerOptions": { + "strictTemplates": false, + "strictInjectionParameters": false, + "annotateForClosureCompiler": true, + "skipTemplateCodegen": true, + "fullTemplateTypeCheck": false, + "enableResourceInlining": true + }, + "include": [ + "./src/**/*.d.ts", + "../common/**/*d.ts", + "../common-angular/**/*d.ts" + ] +} diff --git a/packages/admin-web-angular/tsconfig.spec.json b/packages/admin-web-angular/tsconfig.spec.json new file mode 100644 index 0000000..dec7252 --- /dev/null +++ b/packages/admin-web-angular/tsconfig.spec.json @@ -0,0 +1,37 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "baseUrl": "./src", + "outDir": "./build", + "rootDir": "./", + "target": "es5", + "types": ["node", "reflect-metadata", "googlemaps", "jasmine"], + "paths": { + "@angular/*": ["../node_modules/@angular/*"], + "@modules/server.common/*": ["../../common/src/*"], + "@modules/client.common.angular2/*": ["../../common-angular/src/*"], + "@pyro/*": ["../../common/src/@pyro/*"], + "mongoose": ["../../common-angular/src/mongoose-placeholder"], + "typeorm": ["../../common-angular/src/typeorm-placeholder"], + "angular2-wizard": [ + "../node_modules/@ever-co/angular2-wizard/dist/*" + ], + "@app/*": ["app/*"], + "environments/*": ["./environments/*"], + "core-js/es7/reflect": [ + "../../../node_modules/core-js/proposals/reflect-metadata" + ], + "core-js/es7/array": ["../../../node_modules/core-js/stable/array"], + "core-js/es7/object": [ + "../../../node_modules/core-js/stable/object" + ], + "core-js/es6/*": ["../../../node_modules/core-js/es"] + } + }, + "include": [ + "./src/**/*.ts", + "./src/modules/client.common.angular2/mongoose-placeholder.ts", + "./src/modules/client.common.angular2/typeorm-placeholder.ts", + "./src/modules/**/*.ts" + ] +} diff --git a/packages/admin-web-angular/tslint.json b/packages/admin-web-angular/tslint.json new file mode 100644 index 0000000..0218d77 --- /dev/null +++ b/packages/admin-web-angular/tslint.json @@ -0,0 +1,125 @@ +{ + "extends": ["tslint:latest", "tslint-config-prettier"], + "rulesDirectory": [ + "../../node_modules/codelyzer" + ], + "linterOptions": { + "exclude": ["node_modules", "dist"] + }, + "rules": { + "no-implicit-dependencies": false, + "no-submodule-imports": false, + "trailing-comma": [ + false, + { + "multiline": "always", + "singleline": "never" + } + ], + "interface-name": [false, "always-prefix"], + "arrow-return-shorthand": true, + "callable-types": true, + "class-name": true, + "comment-format": [true, "check-space"], + "forin": true, + "import-blacklist": [true], + "ordered-imports": false, + "interface-over-type-literal": true, + "label-position": true, + "max-line-length": [true, 120], + "member-access": false, + "no-arg": true, + "no-console": [false], + "no-construct": true, + "no-debugger": true, + "no-duplicate-super": true, + "no-empty": false, + "no-empty-interface": false, + "no-eval": true, + "no-misused-new": true, + "no-non-null-assertion": false, + "no-shadowed-variable": true, + "no-string-literal": false, + "no-string-throw": true, + "no-switch-case-fall-through": true, + "no-unnecessary-initializer": true, + "no-use-before-declare": true, + "no-var-keyword": true, + "object-literal-sort-keys": false, + "prefer-const": true, + "object-literal-key-quotes": false, + "no-angle-bracket-type-assertion": false, + "member-ordering": false, + "no-consecutive-blank-lines": false, + "radix": true, + "semicolon": [true, "always"], + "triple-equals": [true, "allow-null-check"], + "typeof-compare": true, + "unified-signatures": true, + "variable-name": false, + "directive-selector": [true, "attribute", "ngx", "camelCase"], + "component-selector": [ + true, + "element", + [ + "google", + "e-cu", + "ngx", + "ea", + "es" + ], + "kebab-case" + ], + "no-attribute-parameter-decorator": true, + "no-forward-ref": true, + "no-input-rename": true, + "no-output-rename": true, + "only-arrow-functions": false, + "pipe-naming": [true, "camelCase", "my"], + "use-host-property-decorator": true, + "ban": [ + true, + "eval", + "fit", + "fdescribe", + { + "name": "$", + "message": "please don't" + } + ], + "max-classes-per-file": [false], + "import-destructuring-spacing": true, + "invoke-injectable": true, + "no-access-missing-member": true, + "templates-use-public": true, + "use-input-property-decorator": true, + "use-life-cycle-interface": true, + "use-output-property-decorator": true, + "use-pipe-transform-interface": true, + "quotemark": [true, "single", "avoid-escape"], + "eofline": true, + "import-spacing": true, + "indent": [true, "tabs"], + "no-trailing-whitespace": true, + "one-line": [false], + "typedef-whitespace": [ + true, + { + "call-signature": "nospace", + "index-signature": "nospace", + "parameter": "nospace", + "property-declaration": "nospace", + "variable-declaration": "nospace" + } + ], + "whitespace": [ + true, + "check-branch", + "check-decl", + "check-operator", + "check-separator", + "check-type" + ], + "no-namespace": false + } +} diff --git a/packages/carrier-mobile-ionic/.dockerignore b/packages/carrier-mobile-ionic/.dockerignore new file mode 100644 index 0000000..be6d4c4 --- /dev/null +++ b/packages/carrier-mobile-ionic/.dockerignore @@ -0,0 +1,11 @@ +.git +.gitignore +.gitmodules +README.md +docker +node_modules +tmp +build +dist +.env +www diff --git a/packages/carrier-mobile-ionic/.env.template b/packages/carrier-mobile-ionic/.env.template new file mode 100644 index 0000000..3a4b79b --- /dev/null +++ b/packages/carrier-mobile-ionic/.env.template @@ -0,0 +1,44 @@ +# NOTE: do NOT ever put here any secure settings! (e.g. Secret Keys) +# We are using dotenv (.env) for consistency with other Platform projects +# This is Angular app and all settings will be loaded into the client browser! + +# Don't forget to update scripts/*.ts and src/environments/*.ts on changes! + +APP_VERSION=0.2.0 + +DEFAULT_CUSTOMER_LOGO=http://res.cloudinary.com/evereq/image/upload/v1536843011/everbie-products-images/btzn3o8pimhercepno2d.png +LOGIN_LOGO=assets/imgs/ever-logo.svg +NO_INTERNET_LOGO=assets/imgs/ever-logo.svg + +COMPANY_NAME=Ever Co. LTD +APP_NAME=Ever® Carrier + +GOOGLE_MAPS_API_KEY= + +GOOGLE_ANALYTICS_API_KEY= +FAKE_UUID=c2360292-3b42-456d-ac37-1cbd9429d4d1 + +# Not secret MixPanel Token +MIXPANEL_API_KEY= + +DEFAULT_LOGIN_USERNAME=ever +DEFAULT_LOGIN_PASSWORD=changeme + +DEFAULT_LATITUDE=42.6459136 +DEFAULT_LONGITUDE=23.3932736 + +DEFAULT_LANGUAGE=en + +# Graphql endpoints for apollo services +GQL_ENDPOINT=http://localhost:8443/graphql +GQL_SUBSCRIPTIONS_ENDPOINT=ws://localhost:2086/subscriptions +SERVICES_ENDPOINT=http://localhost:5500 +HTTPS_SERVICES_ENDPOINT=https://localhost:2087 + +# For maintenance micro service. Ever maintenance API URL: https://maintenance.ever.co/status +SETTINGS_APP_TYPE=carrier-mobile +SETTINGS_MAINTENANCE_API_URL= + +PORT=4203 +WEB_MEMORY=4096 +WEB_CONCURRENCY=1 \ No newline at end of file diff --git a/packages/carrier-mobile-ionic/.gitignore b/packages/carrier-mobile-ionic/.gitignore new file mode 100644 index 0000000..7878ead --- /dev/null +++ b/packages/carrier-mobile-ionic/.gitignore @@ -0,0 +1,46 @@ +/.angular/cache +*~ +*.sw[mnpcod] +*.log +*.tmp +*.tmp.* +**/*.d.ts +**/*.d.ts.map +log.txt +*.sublime-project +*.sublime-workspace +.vscode/ +npm-debug.log* +.idea/ +.sourcemaps/ +.sass-cache/ +.tmp/ +.versions/ +coverage/ +dist/ +node_modules/ +tmp/ +temp/ +hooks/ +platforms/ +plugins/ +plugins/android.json +plugins/ios.json +www/ +$RECYCLE.BIN/ + +.DS_Store +Thumbs.db +UserInterfaceState.xcuserstate + +/src/**/*.js +/src/**/*.js.map + +# Do not store autogenerated docs in repo +docs/ + +# environment files +.env +.env.prod +/src/environments/environment.ts +/src/environments/environment.prod.ts diff --git a/packages/carrier-mobile-ionic/.io-config.json b/packages/carrier-mobile-ionic/.io-config.json new file mode 100644 index 0000000..e4c0e6d --- /dev/null +++ b/packages/carrier-mobile-ionic/.io-config.json @@ -0,0 +1,3 @@ +{ + "app_id": "16ee2ab7" +} diff --git a/packages/carrier-mobile-ionic/CREDITS.md b/packages/carrier-mobile-ionic/CREDITS.md new file mode 100644 index 0000000..ef9ab7c --- /dev/null +++ b/packages/carrier-mobile-ionic/CREDITS.md @@ -0,0 +1,9 @@ +# CREDITS + +This application uses Open Source components and 3rd party libraries, which are licensed under their own respective Open-Source licenses. +You can find the links to source code of their open source projects along with license information below. +We acknowledge and are grateful to these developers for their contributions to open source. + +- Project: ionic https://github.com/ionic-team/ionic + Copyright 2015-present Drifty Co. + License (MIT) https://github.com/ionic-team/ionic/blob/master/LICENSE diff --git a/packages/carrier-mobile-ionic/LICENSE.md b/packages/carrier-mobile-ionic/LICENSE.md new file mode 100644 index 0000000..feba58c --- /dev/null +++ b/packages/carrier-mobile-ionic/LICENSE.md @@ -0,0 +1,41 @@ +# License + +Copyright © 2016-present, Ever Co. LTD. All rights reserved. + +This software is available under different licenses + +### _Ever Platform Community Edition_ License for Carrier Mobile App + +If you decide to choose the Ever Platform Community Edition License for Carrier Mobile App, you must comply with the following terms: + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License, version 3, +as published by the Free Software Foundation. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . + +[GNU General Public License v3.0](https://www.gnu.org/licenses/gpl-3.0.txt) + +### _Ever Platform Enterprise_ License + +Alternatively, commercial versions of the software must be used in accordance with the terms and conditions of separate written agreement between you and Ever Co. LTD. + +For more information about Ever Platform Enterprise License please contact . + +#### The default Ever Platform license, without a valid Ever Platform Enterprise License agreement, is the Ever Platform Community Edition License. + +## Credits + +Please see [CREDITS.md](CREDITS.md) file for a list of libraries and software included in this program and information about licenses. + +## Trademarks + +**Ever**® is a registered trademark of [Ever Co. LTD](https://ever.co). +The trademark may only be used with the written permission of Ever Co. LTD. and may not be used to promote or otherwise market competitive products or services. +All other brand and product names are trademarks, registered trademarks or service marks of their respective holders. diff --git a/packages/carrier-mobile-ionic/README.md b/packages/carrier-mobile-ionic/README.md new file mode 100644 index 0000000..4fc3441 --- /dev/null +++ b/packages/carrier-mobile-ionic/README.md @@ -0,0 +1 @@ +# Ever Demand Carrier Mobile App (Ionic) diff --git a/packages/carrier-mobile-ionic/angular.json b/packages/carrier-mobile-ionic/angular.json new file mode 100644 index 0000000..ef8ead6 --- /dev/null +++ b/packages/carrier-mobile-ionic/angular.json @@ -0,0 +1,174 @@ +{ + "$schema": "./node_modules/@angular/cli/lib/config/schema.json", + "version": 1, + "defaultProject": "app", + "newProjectRoot": "projects", + "projects": { + "app": { + "root": "", + "sourceRoot": "src", + "projectType": "application", + "prefix": "e-cu", + "schematics": {}, + "architect": { + "build": { + "builder": "@angular-builders/custom-webpack:browser", + "options": { + "customWebpackConfig": { + "path": "./extra-webpack.config.js" + }, + "progress": false, + "outputPath": "www", + "index": "src/index.html", + "main": "src/main.ts", + "polyfills": "src/polyfills.ts", + "tsConfig": "src/tsconfig.app.json", + "assets": [ + { + "glob": "**/*", + "input": "src/assets", + "output": "assets" + }, + { + "glob": "**/*.svg", + "input": "../../node_modules/ionicons/dist/ionicons/svg", + "output": "./svg" + }, + { + "glob": "**/*.svg", + "input": "../../node_modules/@ionic/core/dist/ionic/svg", + "output": "./svg" + }, + "src/manifest.json" + ], + "styles": [ + "../../node_modules/font-awesome/scss/font-awesome.scss", + "src/theme/variables.scss", + "src/global.scss" + ], + "scripts": [] + }, + "configurations": { + "production": { + "fileReplacements": [ + { + "replace": "src/environments/environment.ts", + "with": "src/environments/environment.prod.ts" + } + ], + "optimization": true, + "outputHashing": "all", + "sourceMap": false, + "extractCss": true, + "namedChunks": false, + "aot": true, + "extractLicenses": true, + "vendorChunk": false, + "buildOptimizer": true, + "serviceWorker": true + } + } + }, + "serve": { + "builder": "@angular-builders/custom-webpack:dev-server", + "options": { + "browserTarget": "app:build" + }, + "configurations": { + "production": { + "browserTarget": "app:build:production" + } + } + }, + "extract-i18n": { + "builder": "@angular-devkit/build-angular:extract-i18n", + "options": { + "browserTarget": "app:build" + } + }, + "test": { + "builder": "@angular-devkit/build-angular:karma", + "options": { + "main": "src/test.ts", + "polyfills": "src/polyfills.ts", + "tsConfig": "src/tsconfig.spec.json", + "karmaConfig": "src/karma.conf.js", + "styles": [ + "../../node_modules/font-awesome/scss/font-awesome.scss", + { + "input": "src/theme/variables.scss" + }, + { + "input": "src/global.scss" + } + ], + "scripts": [], + "assets": [ + { + "glob": "favicon.ico", + "input": "src/", + "output": "/" + }, + { + "glob": "**/*", + "input": "src/assets", + "output": "/assets" + }, + "src/manifest.json" + ] + } + }, + "ionic-cordova-build": { + "builder": "@ionic/angular-toolkit:cordova-build", + "options": { + "browserTarget": "app:build" + }, + "configurations": { + "production": { + "browserTarget": "app:build:production" + } + } + }, + "ionic-cordova-serve": { + "builder": "@ionic/angular-toolkit:cordova-serve", + "options": { + "cordovaBuildTarget": "app:ionic-cordova-build", + "devServerTarget": "app:serve", + "browserTarget": "app:build" + }, + "configurations": { + "production": { + "cordovaBuildTarget": "app:ionic-cordova-build:production", + "devServerTarget": "app:serve:production" + } + } + } + } + }, + "app-e2e": { + "root": "e2e/", + "projectType": "application", + "architect": { + "e2e": { + "builder": "@angular-devkit/build-angular:protractor", + "options": { + "protractorConfig": "e2e/protractor.conf.js", + "devServerTarget": "app:serve" + } + } + } + } + }, + "cli": { + "packageManager": "yarn", + "defaultCollection": "@ionic/angular-toolkit" + }, + "schematics": { + "@ionic/angular-toolkit:component": { + "styleext": "scss" + }, + "@ionic/angular-toolkit:page": { + "styleext": "scss" + } + } +} diff --git a/packages/carrier-mobile-ionic/app.ts b/packages/carrier-mobile-ionic/app.ts new file mode 100644 index 0000000..187b421 --- /dev/null +++ b/packages/carrier-mobile-ionic/app.ts @@ -0,0 +1,12 @@ +import connect from 'connect'; +import path from 'path'; +import serveStatic from 'serve-static'; +import { env } from './scripts/env'; + +const port: number = env.PORT; + +connect() + .use(serveStatic(path.join(__dirname, '../../../'))) + .listen(port); + +console.log(`listening on ${port}`); diff --git a/packages/carrier-mobile-ionic/config.xml b/packages/carrier-mobile-ionic/config.xml new file mode 100644 index 0000000..e883028 --- /dev/null +++ b/packages/carrier-mobile-ionic/config.xml @@ -0,0 +1,117 @@ + + + ever.carrier + Ever Carrier Mobile App + Ever Co. LTD + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/carrier-mobile-ionic/config/webpack.config.js b/packages/carrier-mobile-ionic/config/webpack.config.js new file mode 100644 index 0000000..5889cf8 --- /dev/null +++ b/packages/carrier-mobile-ionic/config/webpack.config.js @@ -0,0 +1,5 @@ +const useDefaultConfig = require('@ionic/app-scripts/config/webpack.config.js'); + +module.exports = function () { + return useDefaultConfig; +}; diff --git a/packages/carrier-mobile-ionic/extra-webpack.config.js b/packages/carrier-mobile-ionic/extra-webpack.config.js new file mode 100644 index 0000000..652462e --- /dev/null +++ b/packages/carrier-mobile-ionic/extra-webpack.config.js @@ -0,0 +1,8 @@ +console.log('The custom config is used'); + +module.exports = { + resolve: { + symlinks: false, + }, + module: {}, +}; diff --git a/packages/carrier-mobile-ionic/gpl-3.0.txt b/packages/carrier-mobile-ionic/gpl-3.0.txt new file mode 100644 index 0000000..94a9ed0 --- /dev/null +++ b/packages/carrier-mobile-ionic/gpl-3.0.txt @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/packages/carrier-mobile-ionic/graphql.config.json b/packages/carrier-mobile-ionic/graphql.config.json new file mode 100644 index 0000000..07651ba --- /dev/null +++ b/packages/carrier-mobile-ionic/graphql.config.json @@ -0,0 +1,30 @@ +{ + "README_schema": "Specifies how to load the GraphQL schema that completion, error highlighting, and documentation is based on in the IDE", + "schema": { + "README_request": "To request the schema from a url instead, remove the 'file' JSON property above (and optionally delete the default graphql.schema.json file).", + "request": { + "url": "http://localhost:8443/graphql", + "method": "POST", + "README_postIntrospectionQuery": "Whether to POST an introspectionQuery to the url. If the url always returns the schema JSON, set to false and consider using GET", + "postIntrospectionQuery": true, + "README_options": "See the 'Options' section at https://github.com/then/then-request", + "options": { + "headers": { + "user-agent": "JS GraphQL" + } + } + } + }, + "README_endpoints": "A list of GraphQL endpoints that can be queried from '.graphql' files in the IDE", + "endpoints": [ + { + "name": "Default (http://localhost:8443/graphql)", + "url": "http://localhost:8443/graphql", + "options": { + "headers": { + "user-agent": "JS GraphQL" + } + } + } + ] +} diff --git a/packages/carrier-mobile-ionic/img/ever-background-gradient.svg b/packages/carrier-mobile-ionic/img/ever-background-gradient.svg new file mode 100644 index 0000000..0527f15 --- /dev/null +++ b/packages/carrier-mobile-ionic/img/ever-background-gradient.svg @@ -0,0 +1,16 @@ + + + Rectangle + Created with Sketch. + + + + + + + + + + + + diff --git a/packages/carrier-mobile-ionic/img/ever-logo.svg b/packages/carrier-mobile-ionic/img/ever-logo.svg new file mode 100644 index 0000000..53335b0 --- /dev/null +++ b/packages/carrier-mobile-ionic/img/ever-logo.svg @@ -0,0 +1,11 @@ + + + ever logo + Created with Sketch. + + + + + + + \ No newline at end of file diff --git a/packages/carrier-mobile-ionic/img/image_placeholder.png b/packages/carrier-mobile-ionic/img/image_placeholder.png new file mode 100644 index 0000000..79a48dd Binary files /dev/null and b/packages/carrier-mobile-ionic/img/image_placeholder.png differ diff --git a/packages/carrier-mobile-ionic/img/login_back.png b/packages/carrier-mobile-ionic/img/login_back.png new file mode 100644 index 0000000..565cd8e Binary files /dev/null and b/packages/carrier-mobile-ionic/img/login_back.png differ diff --git a/packages/carrier-mobile-ionic/img/logo.png b/packages/carrier-mobile-ionic/img/logo.png new file mode 100644 index 0000000..51dfd56 Binary files /dev/null and b/packages/carrier-mobile-ionic/img/logo.png differ diff --git a/packages/carrier-mobile-ionic/ionic.config.json b/packages/carrier-mobile-ionic/ionic.config.json new file mode 100644 index 0000000..13663cd --- /dev/null +++ b/packages/carrier-mobile-ionic/ionic.config.json @@ -0,0 +1,8 @@ +{ + "name": "ever-carrier", + "integrations": { + "cordova": {} + }, + "type": "angular", + "id": "16ee2ab7" +} diff --git a/packages/carrier-mobile-ionic/ionic.project b/packages/carrier-mobile-ionic/ionic.project new file mode 100644 index 0000000..c16db3f --- /dev/null +++ b/packages/carrier-mobile-ionic/ionic.project @@ -0,0 +1,8 @@ +{ + "name": "ever-carrier", + "integrations": { + "cordova": {} + }, + "type": "angular", + "app_id": "16ee2ab7" +} \ No newline at end of file diff --git a/packages/carrier-mobile-ionic/ngsw-config.json b/packages/carrier-mobile-ionic/ngsw-config.json new file mode 100644 index 0000000..0cb79da --- /dev/null +++ b/packages/carrier-mobile-ionic/ngsw-config.json @@ -0,0 +1,20 @@ +{ + "index": "/index.html", + "assetGroups": [ + { + "name": "app", + "installMode": "prefetch", + "resources": { + "files": ["/favicon.ico", "/index.html", "/*.css", "/*.js"] + } + }, + { + "name": "assets", + "installMode": "lazy", + "updateMode": "prefetch", + "resources": { + "files": ["/assets/**"] + } + } + ] +} diff --git a/packages/carrier-mobile-ionic/package.json b/packages/carrier-mobile-ionic/package.json new file mode 100644 index 0000000..1c6ae66 --- /dev/null +++ b/packages/carrier-mobile-ionic/package.json @@ -0,0 +1,232 @@ +{ + "name": "@ever-platform/carrier-mobile-ionic", + "version": "0.4.3", + "description": "Ever Demand Carrier Mobile App", + "license": "GPL-3.0", + "homepage": "https://ever.co/", + "repository": { + "type": "git", + "url": "https://github.com/ever-co/ever-demand.git" + }, + "bugs": { + "url": "https://github.com/ever-co/ever-demand/issues" + }, + "private": false, + "author": { + "name": "Ever Co. LTD", + "email": "ever@ever.co", + "url": "https://ever.co" + }, + "scripts": { + "ng:dev": "cross-env NODE_ENV=development NODE_OPTIONS=--max_old_space_size=4096 yarn ng", + "ng:prod": "cross-env NODE_ENV=production NODE_OPTIONS=--max_old_space_size=4096 yarn ng", + "ngsw-config": "cross-env NODE_ENV=development NODE_OPTIONS=--max_old_space_size=4096 yarn run config:dev && node_modules/.bin/ngsw-config www ngsw-config.json", + "ngsw-copy": "cross-env NODE_ENV=development NODE_OPTIONS=--max_old_space_size=4096 yarn run config:dev && yarn cpr ../../node_modules/@angular/service-worker/ngsw-worker.js www/ngsw-worker.js -o", + "config": "cross-env NODE_ENV=development NODE_OPTIONS=--max_old_space_size=4096 yarn ts-node ./scripts/configure.ts", + "config:dev": "cross-env NODE_ENV=development NODE_OPTIONS=--max_old_space_size=4096 yarn run config -- --environment=dev", + "config:prod": "cross-env NODE_ENV=production NODE_OPTIONS=--max_old_space_size=4096 yarn run config -- --environment=prod", + "start": "cross-env NODE_ENV=development NODE_OPTIONS=--max_old_space_size=4096 yarn run config:dev && yarn run build && yarn ng:dev serve --port 4203", + "start:prod": "cross-env NODE_ENV=production NODE_OPTIONS=--max_old_space_size=4096 yarn run config:prod && yarn run build:prod && yarn ng:prod serve --port 4203 --prod", + "start:server:prod": "cross-env NODE_ENV=development NODE_OPTIONS=--max_old_space_size=4096 yarn run build:prod && node --harmony ./www/out-tsc/app.js", + "start:server:pm2": "cross-env NODE_ENV=production NODE_OPTIONS=--max_old_space_size=4096 yarn run build:prod && node --harmony ./www/out-tsc/packages/carrier-mobile-ionic/pm2bootstrap.js", + "build": "cross-env NODE_ENV=development NODE_OPTIONS=--max_old_space_size=4096 yarn run config:dev && yarn ng:dev build && yarn tsc", + "build:prod": "cross-env NODE_ENV=production NODE_OPTIONS=--max_old_space_size=4096 yarn run config:prod && yarn ng:prod build --prod --aot=false --build-optimizer=false && yarn tsc", + "test": "cross-env NODE_ENV=development NODE_OPTIONS=--max_old_space_size=4096 yarn run config:dev && yarn ng:dev test", + "lint": "cross-env NODE_ENV=development NODE_OPTIONS=--max_old_space_size=4096 yarn run config:dev && yarn ng:dev lint", + "e2e": "cross-env NODE_ENV=development NODE_OPTIONS=--max_old_space_size=4096 yarn run config:dev && yarn ng:dev e2e", + "bundle-report": "cross-env NODE_ENV=development NODE_OPTIONS=--max_old_space_size=4096 yarn run config:dev && yarn webpack-bundle-analyzer www/stats.json", + "docs": "cross-env NODE_ENV=development NODE_OPTIONS=--max_old_space_size=4096 yarn run config:dev && yarn compodoc -p src/tsconfig.app.json -d docs", + "docs:serve": "cross-env NODE_ENV=development NODE_OPTIONS=--max_old_space_size=4096 yarn run config:dev && yarn compodoc -p src/tsconfig.app.json -d docs -s", + "ionic:build:prod": "cross-env NODE_ENV=production NODE_OPTIONS=--max_old_space_size=4096 yarn run config:prod && yarn ionic build --prod --release", + "cordova:build": "cross-env NODE_ENV=development NODE_OPTIONS=--max_old_space_size=4096 yarn run config:dev && yarn ionic cordova build android", + "cordova:build:prod": "cross-env NODE_ENV=production NODE_OPTIONS=--max_old_space_size=4096 yarn run config:prod && yarn ionic cordova build android --prod --release", + "cordova:run:prod": "cross-env NODE_ENV=production NODE_OPTIONS=--max_old_space_size=4096 yarn run config:prod && yarn ionic cordova run android --prod --release", + "cordova:run:prod:device": "cross-env NODE_ENV=production NODE_OPTIONS=--max_old_space_size=4096 yarn run config:prod && yarn ionic cordova run android --prod --release --device" + }, + "dependencies": { + "@angular/animations": "^13.1.0", + "@angular/common": "^13.1.0", + "@angular/compiler": "^13.1.0", + "@angular/core": "^13.1.0", + "@angular/forms": "^13.1.0", + "@angular/language-service": "^13.1.0", + "@angular/platform-browser": "^13.1.0", + "@angular/platform-browser-dynamic": "^13.1.0", + "@angular/router": "^13.1.0", + "@angular/service-worker": "^13.1.0", + "@apollo/client": "^3.5.5", + "@ever-platform/common": "^0.4.3", + "@ever-platform/common-angular": "^0.4.3", + "@ionic-native/barcode-scanner": "^5.36.0", + "@ionic-native/call-number": "^5.36.0", + "@ionic-native/camera": "^5.36.0", + "@ionic-native/core": "^5.36.0", + "@ionic-native/device": "^5.36.0", + "@ionic-native/dialogs": "^5.36.0", + "@ionic-native/geolocation": "^5.36.0", + "@ionic-native/globalization": "^5.36.0", + "@ionic-native/google-analytics": "^5.36.0", + "@ionic-native/google-maps": "~5.5.0", + "@ionic-native/in-app-browser": "^5.36.0", + "@ionic-native/intercom": "^5.36.0", + "@ionic-native/local-notifications": "^5.36.0", + "@ionic-native/mixpanel": "^5.36.0", + "@ionic-native/network": "^5.36.0", + "@ionic-native/splash-screen": "^5.36.0", + "@ionic-native/status-bar": "^5.36.0", + "@ionic-native/unique-device-id": "^5.36.0", + "@ionic-native/vibration": "^5.36.0", + "@ionic/angular": "^5.8.4", + "@ionic/pro": "^2.0.4", + "@ionic/storage": "^3.0.6", + "@ngx-translate/core": "^13.0.0", + "@ngx-translate/http-loader": "^6.0.0", + "apollo-angular": "^2.6.0", + "call-number": "^1.0.1", + "connect": "^3.7.0", + "cordova-android": "^10.1.1", + "cordova-android-support-gradle-release": "^3.0.1", + "cordova-browser": "^6.0.0", + "cordova-open-native-settings": "^1.5.5", + "cordova-plugin-androidx": "^3.0.0", + "cordova-plugin-androidx-adapter": "^1.1.3", + "cordova-plugin-appavailability": "^0.4.2", + "cordova-plugin-badge": "^0.8.8", + "cordova-plugin-camera": "^6.0.0", + "cordova-plugin-device": "^2.0.3", + "cordova-plugin-dialogs": "^2.0.2", + "cordova-plugin-email-composer": "^0.9.2", + "cordova-plugin-geolocation": "^4.1.0", + "cordova-plugin-globalization": "^1.11.0", + "cordova-plugin-google-analytics": "^1.9.0", + "cordova-plugin-inappbrowser": "^5.0.0", + "cordova-plugin-intercom": "^10.2.0", + "cordova-plugin-ionic-keyboard": "^2.2.0", + "cordova-plugin-ionic-webview": "^5.0.0", + "cordova-plugin-local-notification": "^0.9.0-beta.2", + "cordova-plugin-mixpanel": "^4.7.2", + "cordova-plugin-network-information": "^3.0.0", + "cordova-plugin-screen-orientation": "^3.0.2", + "cordova-plugin-splashscreen": "^6.0.0", + "cordova-plugin-statusbar": "^2.4.3", + "cordova-plugin-uniquedeviceid": "^1.3.2", + "cordova-plugin-vibration": "^3.1.1", + "cordova-plugin-whitelist": "^1.3.5", + "core-js": "^3.18.3", + "cryptiles": "^4.1.3", + "es6-promise-plugin": "^4.2.2", + "font-awesome": "^4.7.0", + "fstream": "^1.0.12", + "graphql": "15.7.2", + "graphql-tag": "^2.12.6", + "handlebars": "^4.7.7", + "hooks-fixed": "^2.0.2", + "ionicons": "^5.5.3", + "kareem": "^2.3.2", + "lodash": "^4.17.21", + "lodash.mergewith": "^4.6.2", + "mongoose": "^6.0.11", + "mpath": "^0.8.4", + "mquery": "^4.0.0", + "mx.ferreyra.callnumber": "0.0.2", + "neo-async": "^2.6.2", + "ngx-progressbar": "^6.1.1", + "phonegap-plugin-barcodescanner": "^8.0.1", + "pm2": "^5.1.2", + "qrcode": "^1.4.4", + "reflect-metadata": "^0.1.13", + "rxjs": "^7.4.0", + "rxjs-compat": "^6.6.7", + "sass-loader": "^12.2.0", + "save": "^2.4.0", + "socket.io-client": "^4.3.0", + "stripe": "^8.183.0", + "sw-toolbox": "^3.6.0", + "swiper": "^7.0.8", + "tslib": "^2.3.1", + "uglify-js": "^3.14.2", + "uuid": "^8.3.2", + "waves": "^0.1.1", + "zone.js": "~0.11.4" + }, + "devDependencies": { + "@angular-builders/custom-webpack": "^13.0.0", + "@angular-devkit/architect": "^0.1301.0", + "@angular-devkit/build-angular": "^13.1.0", + "@angular-devkit/build-optimizer": "^0.1300.4", + "@angular-devkit/build-webpack": "^0.1301.0", + "@angular-devkit/core": "^13.1.0", + "@angular-devkit/schematics": "^13.1.0", + "@angular/cli": "^13.1.0", + "@angular/compiler-cli": "^13.1.0", + "@ionic/angular-toolkit": "^4.0.0", + "@ionic/lab": "^3.2.10", + "@types/jasmine": "~3.9.1", + "@types/jasminewd2": "~2.0.10", + "@types/node": "^16.11.0", + "codelyzer": "^6.0.2", + "ionic": "^5.4.16", + "jasmine-core": "~3.10.0", + "jasmine-spec-reporter": "~7.0.0", + "karma": "~6.3.4", + "karma-chrome-launcher": "~3.1.0", + "karma-coverage-istanbul-reporter": "~3.0.3", + "karma-jasmine": "~4.0.1", + "karma-jasmine-html-reporter": "^1.7.0", + "protractor": "~7.0.0", + "ts-node": "~10.3.0", + "tslint": "~5.20.1", + "typescript": "~4.5.3" + }, + "config": { + "ionic_webpack": "./config/webpack.config.js" + }, + "cordova": { + "platforms": [ + "browser", + "android" + ], + "plugins": { + "cordova-open-native-settings": {}, + "cordova-plugin-whitelist": {}, + "cordova-plugin-device": {}, + "cordova-plugin-ionic-webview": { + "ANDROID_SUPPORT_ANNOTATIONS_VERSION": "27.+" + }, + "cordova-plugin-ionic-keyboard": {}, + "cordova-plugin-google-analytics": { + "GMS_VERSION": "11.0.1" + }, + "cordova-plugin-network-information": {}, + "cordova-plugin-mixpanel": { + "PLAY_SERVICES_VERSION": "+", + "FIREBASE_VERSION": "+" + }, + "cordova-plugin-intercom": {}, + "cordova-plugin-screen-orientation": {}, + "cordova-plugin-globalization": {}, + "cordova-plugin-statusbar": {}, + "cordova-plugin-camera": { + "ANDROID_SUPPORT_V4_VERSION": "27.+" + }, + "cordova-plugin-splashscreen": {}, + "mx.ferreyra.callnumber": {}, + "cordova-plugin-geolocation": { + "GEOLOCATION_USAGE_DESCRIPTION": "To locate you" + }, + "cordova-plugin-email-composer": { + "ANDROID_SUPPORT_V4_VERSION": "27.+" + }, + "cordova-plugin-vibration": {}, + "cordova-plugin-local-notification": {}, + "call-number": {}, + "cordova-plugin-androidx": {}, + "cordova-plugin-androidx-adapter": {} + } + }, + "engines": { + "node": ">=14.4.0", + "yarn": ">=1.13.0" + }, + "snyk": false +} \ No newline at end of file diff --git a/packages/carrier-mobile-ionic/pm2bootstrap.ts b/packages/carrier-mobile-ionic/pm2bootstrap.ts new file mode 100644 index 0000000..8c561e1 --- /dev/null +++ b/packages/carrier-mobile-ionic/pm2bootstrap.ts @@ -0,0 +1,56 @@ +require('dotenv').config(); + +const pm2 = require('pm2'); + +import { env } from './scripts/env'; + +const MACHINE_NAME = process.env.KEYMETRICS_MACHINE_NAME; +const PRIVATE_KEY = process.env.KEYMETRICS_SECRET_KEY; +const PUBLIC_KEY = process.env.KEYMETRICS_PUBLIC_KEY; +const appName = process.env.PM2_APP_NAME || 'EverCarrier'; +const instances = env.WEB_CONCURRENCY; +const maxMemory = env.WEB_MEMORY; +const port = env.PORT; + +pm2.connect(function () { + pm2.start( + { + script: './www/out-tsc/packages/carrier-mobile-ionic/app.js', + name: appName, // ----> THESE ATTRIBUTES ARE OPTIONAL: + exec_mode: 'fork', // ----> https://github.com/Unitech/PM2/blob/master/ADVANCED_README.md#schema + instances, + max_memory_restart: maxMemory + 'M', // Auto restart if process taking more than XXmo + env: { + // If needed declare some environment variables + NODE_ENV: 'production', + PORT: port, + KEYMETRICS_PUBLIC: PUBLIC_KEY, + KEYMETRICS_SECRET: PRIVATE_KEY, + }, + post_update: ['yarn install'], // Commands to execute once we do a pull from Keymetrics + }, + function () { + pm2.dump(console.error); + // Display logs in standard output + pm2.launchBus(function (err, bus) { + console.log('[PM2] Log streaming started'); + + bus.on('log:out', function (packet) { + console.log( + '[App:%s] %s', + packet.process.name, + packet.data + ); + }); + + bus.on('log:err', function (packet) { + console.error( + '[App:%s][Err] %s', + packet.process.name, + packet.data + ); + }); + }); + } + ); +}); diff --git a/packages/carrier-mobile-ionic/resources/README.md b/packages/carrier-mobile-ionic/resources/README.md new file mode 100644 index 0000000..8d4bb5f --- /dev/null +++ b/packages/carrier-mobile-ionic/resources/README.md @@ -0,0 +1,8 @@ +These are Cordova resources. You can replace icon.png and splash.png and run +`ionic cordova resources` to generate custom icons and splash screens for your +app. See `ionic cordova resources --help` for details. + +Cordova reference documentation: + +- Icons: https://cordova.apache.org/docs/en/latest/config_ref/images.html +- Splash Screens: https://cordova.apache.org/docs/en/latest/reference/cordova-plugin-splashscreen/ diff --git a/packages/carrier-mobile-ionic/resources/android/icon/drawable-hdpi-icon.png b/packages/carrier-mobile-ionic/resources/android/icon/drawable-hdpi-icon.png new file mode 100644 index 0000000..e90ee3e Binary files /dev/null and b/packages/carrier-mobile-ionic/resources/android/icon/drawable-hdpi-icon.png differ diff --git a/packages/carrier-mobile-ionic/resources/android/icon/drawable-ldpi-icon.png b/packages/carrier-mobile-ionic/resources/android/icon/drawable-ldpi-icon.png new file mode 100644 index 0000000..a82c000 Binary files /dev/null and b/packages/carrier-mobile-ionic/resources/android/icon/drawable-ldpi-icon.png differ diff --git a/packages/carrier-mobile-ionic/resources/android/icon/drawable-mdpi-icon.png b/packages/carrier-mobile-ionic/resources/android/icon/drawable-mdpi-icon.png new file mode 100644 index 0000000..51d312a Binary files /dev/null and b/packages/carrier-mobile-ionic/resources/android/icon/drawable-mdpi-icon.png differ diff --git a/packages/carrier-mobile-ionic/resources/android/icon/drawable-xhdpi-icon.png b/packages/carrier-mobile-ionic/resources/android/icon/drawable-xhdpi-icon.png new file mode 100644 index 0000000..79d0c22 Binary files /dev/null and b/packages/carrier-mobile-ionic/resources/android/icon/drawable-xhdpi-icon.png differ diff --git a/packages/carrier-mobile-ionic/resources/android/icon/drawable-xxhdpi-icon.png b/packages/carrier-mobile-ionic/resources/android/icon/drawable-xxhdpi-icon.png new file mode 100644 index 0000000..4cf16ec Binary files /dev/null and b/packages/carrier-mobile-ionic/resources/android/icon/drawable-xxhdpi-icon.png differ diff --git a/packages/carrier-mobile-ionic/resources/android/icon/drawable-xxxhdpi-icon.png b/packages/carrier-mobile-ionic/resources/android/icon/drawable-xxxhdpi-icon.png new file mode 100644 index 0000000..23dd1a8 Binary files /dev/null and b/packages/carrier-mobile-ionic/resources/android/icon/drawable-xxxhdpi-icon.png differ diff --git a/packages/carrier-mobile-ionic/resources/android/splash/drawable-land-hdpi-screen.png b/packages/carrier-mobile-ionic/resources/android/splash/drawable-land-hdpi-screen.png new file mode 100644 index 0000000..839834c Binary files /dev/null and b/packages/carrier-mobile-ionic/resources/android/splash/drawable-land-hdpi-screen.png differ diff --git a/packages/carrier-mobile-ionic/resources/android/splash/drawable-land-ldpi-screen.png b/packages/carrier-mobile-ionic/resources/android/splash/drawable-land-ldpi-screen.png new file mode 100644 index 0000000..7a22639 Binary files /dev/null and b/packages/carrier-mobile-ionic/resources/android/splash/drawable-land-ldpi-screen.png differ diff --git a/packages/carrier-mobile-ionic/resources/android/splash/drawable-land-mdpi-screen.png b/packages/carrier-mobile-ionic/resources/android/splash/drawable-land-mdpi-screen.png new file mode 100644 index 0000000..4cd12f8 Binary files /dev/null and b/packages/carrier-mobile-ionic/resources/android/splash/drawable-land-mdpi-screen.png differ diff --git a/packages/carrier-mobile-ionic/resources/android/splash/drawable-land-xhdpi-screen.png b/packages/carrier-mobile-ionic/resources/android/splash/drawable-land-xhdpi-screen.png new file mode 100644 index 0000000..473726e Binary files /dev/null and b/packages/carrier-mobile-ionic/resources/android/splash/drawable-land-xhdpi-screen.png differ diff --git a/packages/carrier-mobile-ionic/resources/android/splash/drawable-land-xxhdpi-screen.png b/packages/carrier-mobile-ionic/resources/android/splash/drawable-land-xxhdpi-screen.png new file mode 100644 index 0000000..8c6466e Binary files /dev/null and b/packages/carrier-mobile-ionic/resources/android/splash/drawable-land-xxhdpi-screen.png differ diff --git a/packages/carrier-mobile-ionic/resources/android/splash/drawable-land-xxxhdpi-screen.png b/packages/carrier-mobile-ionic/resources/android/splash/drawable-land-xxxhdpi-screen.png new file mode 100644 index 0000000..27e7b10 Binary files /dev/null and b/packages/carrier-mobile-ionic/resources/android/splash/drawable-land-xxxhdpi-screen.png differ diff --git a/packages/carrier-mobile-ionic/resources/android/splash/drawable-port-hdpi-screen.png b/packages/carrier-mobile-ionic/resources/android/splash/drawable-port-hdpi-screen.png new file mode 100644 index 0000000..b62e6c3 Binary files /dev/null and b/packages/carrier-mobile-ionic/resources/android/splash/drawable-port-hdpi-screen.png differ diff --git a/packages/carrier-mobile-ionic/resources/android/splash/drawable-port-ldpi-screen.png b/packages/carrier-mobile-ionic/resources/android/splash/drawable-port-ldpi-screen.png new file mode 100644 index 0000000..b65ed1a Binary files /dev/null and b/packages/carrier-mobile-ionic/resources/android/splash/drawable-port-ldpi-screen.png differ diff --git a/packages/carrier-mobile-ionic/resources/android/splash/drawable-port-mdpi-screen.png b/packages/carrier-mobile-ionic/resources/android/splash/drawable-port-mdpi-screen.png new file mode 100644 index 0000000..90b2824 Binary files /dev/null and b/packages/carrier-mobile-ionic/resources/android/splash/drawable-port-mdpi-screen.png differ diff --git a/packages/carrier-mobile-ionic/resources/android/splash/drawable-port-xhdpi-screen.png b/packages/carrier-mobile-ionic/resources/android/splash/drawable-port-xhdpi-screen.png new file mode 100644 index 0000000..cccdb5d Binary files /dev/null and b/packages/carrier-mobile-ionic/resources/android/splash/drawable-port-xhdpi-screen.png differ diff --git a/packages/carrier-mobile-ionic/resources/android/splash/drawable-port-xxhdpi-screen.png b/packages/carrier-mobile-ionic/resources/android/splash/drawable-port-xxhdpi-screen.png new file mode 100644 index 0000000..c4b76bd Binary files /dev/null and b/packages/carrier-mobile-ionic/resources/android/splash/drawable-port-xxhdpi-screen.png differ diff --git a/packages/carrier-mobile-ionic/resources/android/splash/drawable-port-xxxhdpi-screen.png b/packages/carrier-mobile-ionic/resources/android/splash/drawable-port-xxxhdpi-screen.png new file mode 100644 index 0000000..d961909 Binary files /dev/null and b/packages/carrier-mobile-ionic/resources/android/splash/drawable-port-xxxhdpi-screen.png differ diff --git a/packages/carrier-mobile-ionic/resources/icon.png b/packages/carrier-mobile-ionic/resources/icon.png new file mode 100644 index 0000000..ebdbd3f Binary files /dev/null and b/packages/carrier-mobile-ionic/resources/icon.png differ diff --git a/packages/carrier-mobile-ionic/resources/icon.png.md5 b/packages/carrier-mobile-ionic/resources/icon.png.md5 new file mode 100644 index 0000000..cd586f2 --- /dev/null +++ b/packages/carrier-mobile-ionic/resources/icon.png.md5 @@ -0,0 +1 @@ +6cbfe3f9036a120f0c6ea1d20edc72c1 \ No newline at end of file diff --git a/packages/carrier-mobile-ionic/resources/ios/icon/icon-1024.png b/packages/carrier-mobile-ionic/resources/ios/icon/icon-1024.png new file mode 100644 index 0000000..bee7766 Binary files /dev/null and b/packages/carrier-mobile-ionic/resources/ios/icon/icon-1024.png differ diff --git a/packages/carrier-mobile-ionic/resources/ios/icon/icon-40.png b/packages/carrier-mobile-ionic/resources/ios/icon/icon-40.png new file mode 100644 index 0000000..5716e7f Binary files /dev/null and b/packages/carrier-mobile-ionic/resources/ios/icon/icon-40.png differ diff --git a/packages/carrier-mobile-ionic/resources/ios/icon/icon-40@2x.png b/packages/carrier-mobile-ionic/resources/ios/icon/icon-40@2x.png new file mode 100644 index 0000000..75c0efd Binary files /dev/null and b/packages/carrier-mobile-ionic/resources/ios/icon/icon-40@2x.png differ diff --git a/packages/carrier-mobile-ionic/resources/ios/icon/icon-40@3x.png b/packages/carrier-mobile-ionic/resources/ios/icon/icon-40@3x.png new file mode 100644 index 0000000..560c7c6 Binary files /dev/null and b/packages/carrier-mobile-ionic/resources/ios/icon/icon-40@3x.png differ diff --git a/packages/carrier-mobile-ionic/resources/ios/icon/icon-50.png b/packages/carrier-mobile-ionic/resources/ios/icon/icon-50.png new file mode 100644 index 0000000..bdf0857 Binary files /dev/null and b/packages/carrier-mobile-ionic/resources/ios/icon/icon-50.png differ diff --git a/packages/carrier-mobile-ionic/resources/ios/icon/icon-50@2x.png b/packages/carrier-mobile-ionic/resources/ios/icon/icon-50@2x.png new file mode 100644 index 0000000..54deade Binary files /dev/null and b/packages/carrier-mobile-ionic/resources/ios/icon/icon-50@2x.png differ diff --git a/packages/carrier-mobile-ionic/resources/ios/icon/icon-60.png b/packages/carrier-mobile-ionic/resources/ios/icon/icon-60.png new file mode 100644 index 0000000..48383e6 Binary files /dev/null and b/packages/carrier-mobile-ionic/resources/ios/icon/icon-60.png differ diff --git a/packages/carrier-mobile-ionic/resources/ios/icon/icon-60@2x.png b/packages/carrier-mobile-ionic/resources/ios/icon/icon-60@2x.png new file mode 100644 index 0000000..560c7c6 Binary files /dev/null and b/packages/carrier-mobile-ionic/resources/ios/icon/icon-60@2x.png differ diff --git a/packages/carrier-mobile-ionic/resources/ios/icon/icon-60@3x.png b/packages/carrier-mobile-ionic/resources/ios/icon/icon-60@3x.png new file mode 100644 index 0000000..29a43af Binary files /dev/null and b/packages/carrier-mobile-ionic/resources/ios/icon/icon-60@3x.png differ diff --git a/packages/carrier-mobile-ionic/resources/ios/icon/icon-72.png b/packages/carrier-mobile-ionic/resources/ios/icon/icon-72.png new file mode 100644 index 0000000..aeadd15 Binary files /dev/null and b/packages/carrier-mobile-ionic/resources/ios/icon/icon-72.png differ diff --git a/packages/carrier-mobile-ionic/resources/ios/icon/icon-72@2x.png b/packages/carrier-mobile-ionic/resources/ios/icon/icon-72@2x.png new file mode 100644 index 0000000..a9de365 Binary files /dev/null and b/packages/carrier-mobile-ionic/resources/ios/icon/icon-72@2x.png differ diff --git a/packages/carrier-mobile-ionic/resources/ios/icon/icon-76.png b/packages/carrier-mobile-ionic/resources/ios/icon/icon-76.png new file mode 100644 index 0000000..d8e4b05 Binary files /dev/null and b/packages/carrier-mobile-ionic/resources/ios/icon/icon-76.png differ diff --git a/packages/carrier-mobile-ionic/resources/ios/icon/icon-76@2x.png b/packages/carrier-mobile-ionic/resources/ios/icon/icon-76@2x.png new file mode 100644 index 0000000..1b5e449 Binary files /dev/null and b/packages/carrier-mobile-ionic/resources/ios/icon/icon-76@2x.png differ diff --git a/packages/carrier-mobile-ionic/resources/ios/icon/icon-83.5@2x.png b/packages/carrier-mobile-ionic/resources/ios/icon/icon-83.5@2x.png new file mode 100644 index 0000000..609fc8f Binary files /dev/null and b/packages/carrier-mobile-ionic/resources/ios/icon/icon-83.5@2x.png differ diff --git a/packages/carrier-mobile-ionic/resources/ios/icon/icon-small.png b/packages/carrier-mobile-ionic/resources/ios/icon/icon-small.png new file mode 100644 index 0000000..725fb9f Binary files /dev/null and b/packages/carrier-mobile-ionic/resources/ios/icon/icon-small.png differ diff --git a/packages/carrier-mobile-ionic/resources/ios/icon/icon-small@2x.png b/packages/carrier-mobile-ionic/resources/ios/icon/icon-small@2x.png new file mode 100644 index 0000000..441ef3d Binary files /dev/null and b/packages/carrier-mobile-ionic/resources/ios/icon/icon-small@2x.png differ diff --git a/packages/carrier-mobile-ionic/resources/ios/icon/icon-small@3x.png b/packages/carrier-mobile-ionic/resources/ios/icon/icon-small@3x.png new file mode 100644 index 0000000..66d109e Binary files /dev/null and b/packages/carrier-mobile-ionic/resources/ios/icon/icon-small@3x.png differ diff --git a/packages/carrier-mobile-ionic/resources/ios/icon/icon.png b/packages/carrier-mobile-ionic/resources/ios/icon/icon.png new file mode 100644 index 0000000..f487d28 Binary files /dev/null and b/packages/carrier-mobile-ionic/resources/ios/icon/icon.png differ diff --git a/packages/carrier-mobile-ionic/resources/ios/icon/icon@2x.png b/packages/carrier-mobile-ionic/resources/ios/icon/icon@2x.png new file mode 100644 index 0000000..a1fc994 Binary files /dev/null and b/packages/carrier-mobile-ionic/resources/ios/icon/icon@2x.png differ diff --git a/packages/carrier-mobile-ionic/resources/ios/splash/Default-568h@2x~iphone.png b/packages/carrier-mobile-ionic/resources/ios/splash/Default-568h@2x~iphone.png new file mode 100644 index 0000000..663deb1 Binary files /dev/null and b/packages/carrier-mobile-ionic/resources/ios/splash/Default-568h@2x~iphone.png differ diff --git a/packages/carrier-mobile-ionic/resources/ios/splash/Default-667h.png b/packages/carrier-mobile-ionic/resources/ios/splash/Default-667h.png new file mode 100644 index 0000000..56b3317 Binary files /dev/null and b/packages/carrier-mobile-ionic/resources/ios/splash/Default-667h.png differ diff --git a/packages/carrier-mobile-ionic/resources/ios/splash/Default-736h.png b/packages/carrier-mobile-ionic/resources/ios/splash/Default-736h.png new file mode 100644 index 0000000..96ce8bf Binary files /dev/null and b/packages/carrier-mobile-ionic/resources/ios/splash/Default-736h.png differ diff --git a/packages/carrier-mobile-ionic/resources/ios/splash/Default-Landscape-736h.png b/packages/carrier-mobile-ionic/resources/ios/splash/Default-Landscape-736h.png new file mode 100644 index 0000000..aaff74a Binary files /dev/null and b/packages/carrier-mobile-ionic/resources/ios/splash/Default-Landscape-736h.png differ diff --git a/packages/carrier-mobile-ionic/resources/ios/splash/Default-Landscape@2x~ipad.png b/packages/carrier-mobile-ionic/resources/ios/splash/Default-Landscape@2x~ipad.png new file mode 100644 index 0000000..19770a2 Binary files /dev/null and b/packages/carrier-mobile-ionic/resources/ios/splash/Default-Landscape@2x~ipad.png differ diff --git a/packages/carrier-mobile-ionic/resources/ios/splash/Default-Landscape@~ipadpro.png b/packages/carrier-mobile-ionic/resources/ios/splash/Default-Landscape@~ipadpro.png new file mode 100644 index 0000000..6438232 Binary files /dev/null and b/packages/carrier-mobile-ionic/resources/ios/splash/Default-Landscape@~ipadpro.png differ diff --git a/packages/carrier-mobile-ionic/resources/ios/splash/Default-Landscape~ipad.png b/packages/carrier-mobile-ionic/resources/ios/splash/Default-Landscape~ipad.png new file mode 100644 index 0000000..6fe8925 Binary files /dev/null and b/packages/carrier-mobile-ionic/resources/ios/splash/Default-Landscape~ipad.png differ diff --git a/packages/carrier-mobile-ionic/resources/ios/splash/Default-Portrait@2x~ipad.png b/packages/carrier-mobile-ionic/resources/ios/splash/Default-Portrait@2x~ipad.png new file mode 100644 index 0000000..a2c1489 Binary files /dev/null and b/packages/carrier-mobile-ionic/resources/ios/splash/Default-Portrait@2x~ipad.png differ diff --git a/packages/carrier-mobile-ionic/resources/ios/splash/Default-Portrait@~ipadpro.png b/packages/carrier-mobile-ionic/resources/ios/splash/Default-Portrait@~ipadpro.png new file mode 100644 index 0000000..a370419 Binary files /dev/null and b/packages/carrier-mobile-ionic/resources/ios/splash/Default-Portrait@~ipadpro.png differ diff --git a/packages/carrier-mobile-ionic/resources/ios/splash/Default-Portrait~ipad.png b/packages/carrier-mobile-ionic/resources/ios/splash/Default-Portrait~ipad.png new file mode 100644 index 0000000..9aa0a26 Binary files /dev/null and b/packages/carrier-mobile-ionic/resources/ios/splash/Default-Portrait~ipad.png differ diff --git a/packages/carrier-mobile-ionic/resources/ios/splash/Default@2x~iphone.png b/packages/carrier-mobile-ionic/resources/ios/splash/Default@2x~iphone.png new file mode 100644 index 0000000..f3c709a Binary files /dev/null and b/packages/carrier-mobile-ionic/resources/ios/splash/Default@2x~iphone.png differ diff --git a/packages/carrier-mobile-ionic/resources/ios/splash/Default@2x~universal~anyany.png b/packages/carrier-mobile-ionic/resources/ios/splash/Default@2x~universal~anyany.png new file mode 100644 index 0000000..960cb82 Binary files /dev/null and b/packages/carrier-mobile-ionic/resources/ios/splash/Default@2x~universal~anyany.png differ diff --git a/packages/carrier-mobile-ionic/resources/ios/splash/Default~iphone.png b/packages/carrier-mobile-ionic/resources/ios/splash/Default~iphone.png new file mode 100644 index 0000000..8b47ad8 Binary files /dev/null and b/packages/carrier-mobile-ionic/resources/ios/splash/Default~iphone.png differ diff --git a/packages/carrier-mobile-ionic/resources/splash.png b/packages/carrier-mobile-ionic/resources/splash.png new file mode 100644 index 0000000..e3b4d1d Binary files /dev/null and b/packages/carrier-mobile-ionic/resources/splash.png differ diff --git a/packages/carrier-mobile-ionic/resources/splash.png.md5 b/packages/carrier-mobile-ionic/resources/splash.png.md5 new file mode 100644 index 0000000..5c5cf29 --- /dev/null +++ b/packages/carrier-mobile-ionic/resources/splash.png.md5 @@ -0,0 +1 @@ +3eeb0addd2aa8c118958337ac5fb9d1b \ No newline at end of file diff --git a/packages/carrier-mobile-ionic/scripts/configure.ts b/packages/carrier-mobile-ionic/scripts/configure.ts new file mode 100644 index 0000000..b639ae1 --- /dev/null +++ b/packages/carrier-mobile-ionic/scripts/configure.ts @@ -0,0 +1,102 @@ +// NOTE: do NOT ever put here any secure settings! (e.g. Secret Keys) +// We are using dotenv (.env) for consistency with other Platform projects +// This is Angular app and all settings will be loaded into the client browser! + +import { env } from './env'; +import { writeFile, unlinkSync } from 'fs'; +import { argv } from 'yargs'; + +const environment = argv["environment"]; +const isProd = environment === 'prod'; + +if (!env.GOOGLE_MAPS_API_KEY) { + console.warn( + 'WARNING: No Google Maps API Key defined in the .env. Google Maps may not be visible!' + ); +} + +const envFileContent = `// NOTE: Auto-generated file +// The file contents for the current environment will overwrite these during build. +// The build system defaults to the dev environment which uses 'environment.ts', but if you do +// 'ng build --env=prod' then 'environment.prod.ts' will be used instead. +// The list of which env maps to which file can be found in '.angular-cli.json'. + +import { Environment } from './model'; + +export const environment: Environment = { + production: ${isProd}, + SERVICES_ENDPOINT: '${env.SERVICES_ENDPOINT}', + HTTPS_SERVICES_ENDPOINT: '${env.HTTPS_SERVICES_ENDPOINT}', + + GQL_ENDPOINT: '${env.GQL_ENDPOINT}', + GQL_SUBSCRIPTIONS_ENDPOINT: '${env.GQL_SUBSCRIPTIONS_ENDPOINT}', + + APP_VERSION: '${env.APP_VERSION}', + + GOOGLE_MAPS_API_KEY: '${env.GOOGLE_MAPS_API_KEY}', + GOOGLE_ANALYTICS_API_KEY: '${env.GOOGLE_ANALYTICS_API_KEY}', + FAKE_UUID: '${env.FAKE_UUID}', + // Not secret MixPanel Token + MIXPANEL_API_KEY: '${env.MIXPANEL_API_KEY}', + + DEFAULT_CUSTOMER_LOGO: '${env.DEFAULT_CUSTOMER_LOGO}', + + LOGIN_LOGO: '${env.LOGIN_LOGO}', + NO_INTERNET_LOGO: '${env.NO_INTERNET_LOGO}', + + COMPANY_NAME: '${env.COMPANY_NAME}', + APP_NAME: '${env.APP_NAME}', + + DEFAULT_LOGIN_USERNAME: '${env.DEFAULT_LOGIN_USERNAME}', + DEFAULT_LOGIN_PASSWORD: '${env.DEFAULT_LOGIN_PASSWORD}', + + DEFAULT_LATITUDE: ${env.DEFAULT_LATITUDE}, + DEFAULT_LONGITUDE: ${env.DEFAULT_LONGITUDE}, + + DEFAULT_LANGUAGE: '${env.DEFAULT_LANGUAGE}', + + // For maintenance micro service + SETTINGS_APP_TYPE: '${env.SETTINGS_APP_TYPE}', + SETTINGS_MAINTENANCE_API_URL: '${env.SETTINGS_MAINTENANCE_API_URL}' +}; + +/* + * In development mode, to ignore zone related error stack frames such as + * 'zone.run', 'zoneDelegate.invokeTask' for easier debugging, you can + * import the following file, but please comment it out in production mode + * because it will have performance impact when throw error + */ +// import 'zone.js/dist/zone-error'; // Included with Angular CLI. + +`; + +const envFileDest: string = isProd ? 'environment.prod.ts' : 'environment.ts'; +const envFileDestOther: string = !isProd + ? 'environment.prod.ts' + : 'environment.ts'; + +// we always want first to remove old generated files (one of them is not needed for current build) +try { + unlinkSync(`./src/environments/environment.ts`); +} catch {} +try { + unlinkSync(`./src/environments/environment.prod.ts`); +} catch {} + +writeFile(`./src/environments/${envFileDest}`, envFileContent, function (err) { + if (err) { + console.log(err); + } else { + console.log(`Generated Angular environment file: ${envFileDest}`); + } +}); + +writeFile(`./src/environments/${envFileDestOther}`, envFileContent, function ( + err +) { + if (err) { + console.log(err); + } else { + console.log(`Generated Angular environment file: ${envFileDestOther}`); + } +}); diff --git a/packages/carrier-mobile-ionic/scripts/env.js b/packages/carrier-mobile-ionic/scripts/env.js new file mode 100644 index 0000000..d901f3e --- /dev/null +++ b/packages/carrier-mobile-ionic/scripts/env.js @@ -0,0 +1,49 @@ +'use strict'; +Object.defineProperty(exports, '__esModule', { value: true }); +var envalid_1 = require('envalid'); +var uuid_1 = require('uuid'); +exports.env = envalid_1.cleanEnv( + process.env, + { + production: envalid_1.bool({ default: false }), + APP_VERSION: envalid_1.str({ default: '0.2.0' }), + DEFAULT_CUSTOMER_LOGO: envalid_1.str({ + default: + 'http://res.cloudinary.com/evereq/image/upload/v1536843011/everbie-products-images/btzn3o8pimhercepno2d.png', + }), + LOGIN_LOGO: envalid_1.str({ default: 'assets/imgs/ever-logo.svg' }), + NO_INTERNET_LOGO: envalid_1.str({ + default: 'assets/imgs/ever-logo.svg', + }), + COMPANY_NAME: envalid_1.str({ default: 'Ever Co. LTD' }), + APP_NAME: envalid_1.str({ default: 'Ever® Carrier' }), + DEFAULT_LOGIN_USERNAME: envalid_1.str({ default: 'ever' }), + DEFAULT_LOGIN_PASSWORD: envalid_1.str({ default: 'changeme' }), + GOOGLE_MAPS_API_KEY: envalid_1.str({ default: '' }), + GOOGLE_ANALYTICS_API_KEY: envalid_1.str({ default: '' }), + FAKE_UUID: envalid_1.str({ default: uuid_1.v4() }), + MIXPANEL_API_KEY: envalid_1.str({ default: '' }), + DEFAULT_LATITUDE: envalid_1.num({ default: 42.6459136 }), + DEFAULT_LONGITUDE: envalid_1.num({ default: 23.3932736 }), + DEFAULT_LANGUAGE: envalid_1.str({ default: 'en' }), + GQL_ENDPOINT: envalid_1.str({ + default: 'http://localhost:5555/graphql', + }), + GQL_SUBSCRIPTIONS_ENDPOINT: envalid_1.str({ + default: 'ws://localhost:5050/subscriptions', + }), + SERVICES_ENDPOINT: envalid_1.str({ default: 'http://localhost:5500' }), + HTTPS_SERVICES_ENDPOINT: envalid_1.str({ + default: 'https://localhost:5501', + }), + SETTINGS_APP_TYPE: envalid_1.str({ default: 'carrier-mobile' }), + SETTINGS_MAINTENANCE_API_URL: envalid_1.str({ + default: '', + }), + WEB_CONCURRENCY: envalid_1.num({ default: 1 }), + WEB_MEMORY: envalid_1.num({ default: 2048 }), + PORT: envalid_1.num({ default: 4203 }), + }, + { strict: true, dotEnvPath: __dirname + '/../.env' } +); +//# sourceMappingURL=env.js.map diff --git a/packages/carrier-mobile-ionic/scripts/env.js.map b/packages/carrier-mobile-ionic/scripts/env.js.map new file mode 100644 index 0000000..85afce0 --- /dev/null +++ b/packages/carrier-mobile-ionic/scripts/env.js.map @@ -0,0 +1 @@ +{"version":3,"file":"env.js","sourceRoot":"","sources":["env.ts"],"names":[],"mappings":";;AAIA,mCAAmD;AACnD,6BAAkC;AA2CrB,QAAA,GAAG,GAAQ,kBAAQ,CAC/B,OAAO,CAAC,GAAG,EACX;IACC,UAAU,EAAE,cAAI,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;IAEpC,WAAW,EAAE,aAAG,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC;IAEtC,qBAAqB,EAAE,aAAG,CAAC;QAC1B,OAAO,EACN,4GAA4G;KAC7G,CAAC;IAEF,UAAU,EAAE,aAAG,CAAC,EAAE,OAAO,EAAE,2BAA2B,EAAE,CAAC;IACzD,gBAAgB,EAAE,aAAG,CAAC,EAAE,OAAO,EAAE,2BAA2B,EAAE,CAAC;IAE/D,YAAY,EAAE,aAAG,CAAC,EAAE,OAAO,EAAE,cAAc,EAAE,CAAC;IAC9C,QAAQ,EAAE,aAAG,CAAC,EAAE,OAAO,EAAE,eAAe,EAAE,CAAC;IAE3C,sBAAsB,EAAE,aAAG,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC;IAChD,sBAAsB,EAAE,aAAG,CAAC,EAAE,OAAO,EAAE,UAAU,EAAE,CAAC;IAEpD,mBAAmB,EAAE,aAAG,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;IAEzC,wBAAwB,EAAE,aAAG,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;IAC9C,SAAS,EAAE,aAAG,CAAC,EAAE,OAAO,EAAE,SAAI,EAAE,EAAE,CAAC;IAGnC,gBAAgB,EAAE,aAAG,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;IAEtC,gBAAgB,EAAE,aAAG,CAAC,EAAE,OAAO,EAAE,UAAU,EAAE,CAAC;IAC9C,iBAAiB,EAAE,aAAG,CAAC,EAAE,OAAO,EAAE,UAAU,EAAE,CAAC;IAE/C,gBAAgB,EAAE,aAAG,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;IAGxC,YAAY,EAAE,aAAG,CAAC,EAAE,OAAO,EAAE,+BAA+B,EAAE,CAAC;IAC/D,0BAA0B,EAAE,aAAG,CAAC;QAC/B,OAAO,EAAE,mCAAmC;KAC5C,CAAC;IACF,iBAAiB,EAAE,aAAG,CAAC,EAAE,OAAO,EAAE,uBAAuB,EAAE,CAAC;IAC5D,uBAAuB,EAAE,aAAG,CAAC,EAAE,OAAO,EAAE,wBAAwB,EAAE,CAAC;IAGnE,iBAAiB,EAAE,aAAG,CAAC,EAAE,OAAO,EAAE,gBAAgB,EAAE,CAAC;IACrD,4BAA4B,EAAE,aAAG,CAAC;QACjC,OAAO,EAAE,EAAE;KACX,CAAC;IAEF,eAAe,EAAE,aAAG,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC;IACpC,UAAU,EAAE,aAAG,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;IAClC,IAAI,EAAE,aAAG,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;CAC5B,EACD,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,EAAE,SAAS,GAAG,UAAU,EAAE,CACpD,CAAC"} \ No newline at end of file diff --git a/packages/carrier-mobile-ionic/scripts/env.ts b/packages/carrier-mobile-ionic/scripts/env.ts new file mode 100644 index 0000000..bc44ea7 --- /dev/null +++ b/packages/carrier-mobile-ionic/scripts/env.ts @@ -0,0 +1,107 @@ +// NOTE: do NOT ever put here any secure settings! (e.g. Secret Keys) +// We are using dotenv (.env) for consistency with other Platform projects +// This is Angular app and all settings will be loaded into the client browser! + +require('dotenv').config(); + +import { cleanEnv, num, str, bool, CleanOptions } from 'envalid'; +import { v4 as uuid } from 'uuid'; + +export type Env = Readonly<{ + production: boolean; + + // Graphql endpoints for apollo services + GQL_ENDPOINT: string; + GQL_SUBSCRIPTIONS_ENDPOINT: string; + SERVICES_ENDPOINT: string; + HTTPS_SERVICES_ENDPOINT: string; + + APP_VERSION: string; + + GOOGLE_MAPS_API_KEY: string; + GOOGLE_ANALYTICS_API_KEY: string; + FAKE_UUID: string; + // Not secret MixPanel Token + MIXPANEL_API_KEY: string; + + DEFAULT_CUSTOMER_LOGO: string; + LOGIN_LOGO: string; + NO_INTERNET_LOGO: string; + + COMPANY_NAME: string; + APP_NAME: string; + + DEFAULT_LOGIN_USERNAME: string; + DEFAULT_LOGIN_PASSWORD: string; + + DEFAULT_LATITUDE: number; + DEFAULT_LONGITUDE: number; + + DEFAULT_LANGUAGE: string; + + // For maintenance micro service + SETTINGS_APP_TYPE?: string; + SETTINGS_MAINTENANCE_API_URL?: string; + + WEB_CONCURRENCY: number; + WEB_MEMORY: number; + PORT: number; +}>; + +const opt: CleanOptions = { +}; + +export const env: Env = cleanEnv( + process.env, + { + production: bool({ default: false }), + + APP_VERSION: str({ default: '0.2.0' }), + + DEFAULT_CUSTOMER_LOGO: str({ + default: + 'http://res.cloudinary.com/evereq/image/upload/v1536843011/everbie-products-images/btzn3o8pimhercepno2d.png', + }), + + LOGIN_LOGO: str({ default: 'assets/imgs/ever-logo.svg' }), + NO_INTERNET_LOGO: str({ default: 'assets/imgs/ever-logo.svg' }), + + COMPANY_NAME: str({ default: 'Ever Co. LTD' }), + APP_NAME: str({ default: 'Ever® Carrier' }), + + DEFAULT_LOGIN_USERNAME: str({ default: 'ever' }), + DEFAULT_LOGIN_PASSWORD: str({ default: 'changeme' }), + + GOOGLE_MAPS_API_KEY: str({ default: '' }), + + GOOGLE_ANALYTICS_API_KEY: str({ default: '' }), + FAKE_UUID: str({ default: uuid() }), + + // Not secret MixPanel Token + MIXPANEL_API_KEY: str({ default: '' }), + + DEFAULT_LATITUDE: num({ default: 42.6459136 }), + DEFAULT_LONGITUDE: num({ default: 23.3932736 }), + + DEFAULT_LANGUAGE: str({ default: 'en' }), + + // Graphql endpoints for apollo services + GQL_ENDPOINT: str({ default: 'http://localhost:8443/graphql' }), + GQL_SUBSCRIPTIONS_ENDPOINT: str({ + default: 'ws://localhost:2086/subscriptions', + }), + SERVICES_ENDPOINT: str({ default: 'http://localhost:5500' }), + HTTPS_SERVICES_ENDPOINT: str({ default: 'https://localhost:2087' }), + + // For maintenance micro service. Ever maintenance API URL: https://maintenance.ever.co/status + SETTINGS_APP_TYPE: str({ default: 'carrier-mobile' }), + SETTINGS_MAINTENANCE_API_URL: str({ + default: '', + }), + + WEB_CONCURRENCY: num({ default: 1 }), + WEB_MEMORY: num({ default: 2048 }), + PORT: num({ default: 4203 }), + }, + opt +); diff --git a/packages/carrier-mobile-ionic/src/app/app-routing.module.ts b/packages/carrier-mobile-ionic/src/app/app-routing.module.ts new file mode 100644 index 0000000..eb0c6db --- /dev/null +++ b/packages/carrier-mobile-ionic/src/app/app-routing.module.ts @@ -0,0 +1,37 @@ +import { NgModule } from '@angular/core'; +import { RouterModule, Routes, ExtraOptions } from '@angular/router'; +import { PagesModuleGuard } from 'pages/pages.module.guard'; +import { InfoModuleGuard } from './info/info.module.guard'; + +const routes: Routes = [ + { + path: 'info', + loadChildren: () => + import('./info/info.module').then((m) => m.InfoPageModule), + canLoad: [InfoModuleGuard], + }, + { + path: '', + loadChildren: () => + import('../pages/pages.module').then((m) => m.PagesModule), + canLoad: [PagesModuleGuard], + }, + { + path: '**', + pathMatch: 'full', + redirectTo: '', + }, +]; + +const config: ExtraOptions = { + useHash: true, + enableTracing: true, +}; + +@NgModule({ + // imports: [RouterModule.forRoot(routes, config)], + imports: [RouterModule.forRoot(routes)], + exports: [RouterModule], + providers: [PagesModuleGuard, InfoModuleGuard], +}) +export class AppRoutingModule {} diff --git a/packages/carrier-mobile-ionic/src/app/app.component.html b/packages/carrier-mobile-ionic/src/app/app.component.html new file mode 100644 index 0000000..4f46c88 --- /dev/null +++ b/packages/carrier-mobile-ionic/src/app/app.component.html @@ -0,0 +1,5 @@ + + + + + diff --git a/packages/carrier-mobile-ionic/src/app/app.component.scss b/packages/carrier-mobile-ionic/src/app/app.component.scss new file mode 100644 index 0000000..7e90693 --- /dev/null +++ b/packages/carrier-mobile-ionic/src/app/app.component.scss @@ -0,0 +1,15 @@ +// http://ionicframework.com/docs/theming/ + +// App Global Sass +// -------------------------------------------------- +// Put style rules here that you want to apply globally. These +// styles are for the entire app and not just one component. +// Additionally, this file can be also used as an entry point +// to import other Sass files to be included in the output CSS. +// +// Shared Sass variables, which can be used to adjust Ionic's +// default Sass variables, belong in "theme/variables.scss". +// +// To declare rules for a specific mode, create a child rule +// for the .md, .ios, or .wp mode classes. The mode class is +// automatically applied to the element in the app. diff --git a/packages/carrier-mobile-ionic/src/app/app.component.ts b/packages/carrier-mobile-ionic/src/app/app.component.ts new file mode 100644 index 0000000..c55d892 --- /dev/null +++ b/packages/carrier-mobile-ionic/src/app/app.component.ts @@ -0,0 +1,185 @@ +import { Component, OnInit } from '@angular/core'; +import { StatusBar } from '@ionic-native/status-bar/ngx'; +import { SplashScreen } from '@ionic-native/splash-screen/ngx'; +import { GoogleAnalytics } from '@ionic-native/google-analytics/ngx'; +import { Mixpanel } from '@ionic-native/mixpanel/ngx'; +import { Network } from '@ionic-native/network/ngx'; +import { Globalization } from '@ionic-native/globalization/ngx'; +import { Device } from '@ionic-native/device/ngx'; +import { TranslateService } from '@ngx-translate/core'; +import { environment } from '../environments/environment'; +import { Store } from '../services/store.service'; +import ILanguage from '@modules/server.common/interfaces/ILanguage'; +import { Platform } from '@ionic/angular'; +import { DeviceRouter } from '@modules/client.common.angular2/routers/device-router.service'; +import { IDeviceCreateObject } from '@modules/server.common/interfaces/IDevice'; +import IPlatform from '@modules/server.common/interfaces/IPlatform'; +import { Router } from '@angular/router'; + +@Component({ + selector: 'e-cu-root', + templateUrl: 'app.component.html', +}) +export class AppComponent implements OnInit { + defaultLanguage = ''; + + constructor( + public platform: Platform, + public statusBar: StatusBar, + public splashScreen: SplashScreen, + private ga: GoogleAnalytics, + private mixpanel: Mixpanel, + private network: Network, + private globalization: Globalization, + private device: Device, + private _langTranslator: TranslateService, + private store: Store, + private router: Router, + private deviceRouter: DeviceRouter + ) {} + + ngOnInit(): void { + this.initializeApp(); + } + + initializeApp() { + this.platform.ready().then(() => { + if (!this.store.deviceId) { + this._registerDevice(); + } + + this.networkWatch(); + this._setupLangTranslator(); + + if (this.device.platform) { + this.startGoogleAnalytics(); + this.preferredLanguage(); + this.startMixpanel(); + } + // Okay, so the platform is ready and our plugins are available. + // Here you can do any higher level native things you might need. + this.statusBar.styleBlackOpaque(); + this.splashScreen.hide(); + }); + } + + startGoogleAnalytics() { + setTimeout(() => { + this.ga + .startTrackerWithId(environment.GOOGLE_ANALYTICS_API_KEY, 30) + .then(() => { + console.log('Google analytics is ready now!'); + this.ga.trackView('test'); + // Tracker is ready + // You can now track pages or set additional information such as AppVersion or UserId + }) + .catch((e) => console.log('Error starting GoogleAnalytics', e)); + }, 3000); + } + + networkWatch() { + const disconnectSubscription = this.network + .onDisconnect() + .subscribe(() => { + this.store.noInternet = 'noInternet'; + this.router.navigateByUrl('info/no-internet', { + skipLocationChange: false, + }); + console.log('network was disconnected :-('); + }); + } + + preferredLanguage() { + console.log('Preferred Language'); + this.globalization + .getPreferredLanguage() + .then((res) => { + console.log(res.value); + }) + .catch((e) => console.log(e)); + } + + startMixpanel() { + this.mixpanel.init(environment.MIXPANEL_API_KEY).then(() => { + console.log('Mixpanel is ready now!'); + this.mixpanel.track('App Booted'); + }); + } + + get showInformationPage() { + return this.store.showInformationPage; + } + + private async _setupLangTranslator() { + this.defaultLanguage = environment['DEFAULT_LANGUAGE']; + + console.log('this.defaultLanguage'); + console.log(this.defaultLanguage); + + const lang = (localStorage.getItem('_language') as ILanguage) || null; + const langs = { + he: 'he', + ru: 'ru', + bg: 'bg', + en: 'en', + es: 'es', + fr: 'fr', + }; + // This 6 lines is here because of bug => without this lines, lang translation doesn't work. + // (The bug is unknown) + this._langTranslator.use(langs.he); + this._langTranslator.use(langs.ru); + this._langTranslator.use(langs.bg); + this._langTranslator.use(langs.en); + this._langTranslator.use(langs.es); + this._langTranslator.use(langs.fr); + + this._langTranslator.resetLang(langs.en); + + if (lang) { + this._langTranslator.use(lang.substr(0, 2)); + } else { + const browserLang = this.platform.is('cordova') + ? (await this.globalization.getPreferredLanguage()).value + : this._langTranslator.getBrowserLang(); + if (this.defaultLanguage) { + this._langTranslator.use(this.defaultLanguage); + } else { + if (langs[browserLang]) { + this._langTranslator.use(langs[browserLang]); + } else { + this._langTranslator.use(langs.en); + } + } + } + } + + private async _registerDevice() { + const deviceCreateObject = this.deviceCreateObject(); + + const device = await this.deviceRouter.create(deviceCreateObject); + + this.store.deviceId = device.id; + this.store.language = device.language; + this.store.platform = device.type; + } + + private deviceCreateObject(): IDeviceCreateObject { + const language = localStorage.getItem('_language') || 'en-US'; + if (!this.device.platform || this.device.platform === 'browser') { + return { + channelId: null, + language: language as ILanguage, + type: 'browser' as IPlatform, + // have to find way how to generate it from browser + uuid: environment['FAKE_UUID'], + }; + } + return { + channelId: null, + language: language as ILanguage, + type: this.device.platform as IPlatform, + uuid: this.device.uuid, + }; + } +} diff --git a/packages/carrier-mobile-ionic/src/app/app.module.ts b/packages/carrier-mobile-ionic/src/app/app.module.ts new file mode 100644 index 0000000..8a0e818 --- /dev/null +++ b/packages/carrier-mobile-ionic/src/app/app.module.ts @@ -0,0 +1,118 @@ +import { NgModule, NO_ERRORS_SCHEMA, APP_INITIALIZER } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; +import { HttpClient, HttpClientModule } from '@angular/common/http'; +import { StatusBar } from '@ionic-native/status-bar/ngx'; +import { SplashScreen } from '@ionic-native/splash-screen/ngx'; +import { GoogleMaps } from '@ionic-native/google-maps'; +import { GoogleAnalytics } from '@ionic-native/google-analytics/ngx'; +import { Mixpanel } from '@ionic-native/mixpanel/ngx'; +import { Network } from '@ionic-native/network/ngx'; +import { Globalization } from '@ionic-native/globalization/ngx'; +import { Device } from '@ionic-native/device/ngx'; +import { CommonModule } from '@modules/client.common.angular2/common.module'; +import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; +import { TranslateHttpLoader } from '@ngx-translate/http-loader'; +import { GraphQLModule } from '../graphql/apollo.config'; +import { environment } from '../environments/environment'; +import { GoogleMapsLoader } from '@modules/client.common.angular2/services/googleMapsLoader'; +import { CarriersOrdersService } from '../services/carriers-orders.service'; +import { MaintenanceService } from '@modules/client.common.angular2/services/maintenance.service'; +import { Vibration } from '@ionic-native/vibration/ngx'; +import { LocalNotifications } from '@ionic-native/local-notifications/ngx'; +import { PipesModule } from '@modules/client.common.angular2/pipes/pipes.module'; +import { Store } from '../services/store.service'; +import { ServerConnectionService } from '@modules/client.common.angular2/services/server-connection.service'; +import { IonicModule, IonicRouteStrategy } from '@ionic/angular'; +import { AppRoutingModule } from './app-routing.module'; +import { RouteReuseStrategy } from '@angular/router'; +import { AppComponent } from './app.component'; +import { MenuModule } from 'components/menu/menu.module'; + +@NgModule({ + schemas: [NO_ERRORS_SCHEMA], + declarations: [AppComponent], + imports: [ + BrowserModule, + AppRoutingModule, + GraphQLModule, + MenuModule, + TranslateModule.forRoot({ + loader: { + provide: TranslateLoader, + useFactory: HttpLoaderFactory, + deps: [HttpClient], + }, + }), + IonicModule.forRoot(), + CommonModule.forRoot({ + apiUrl: environment.SERVICES_ENDPOINT, + }), + HttpClientModule, + PipesModule, + ], + bootstrap: [AppComponent], + providers: [ + ServerConnectionService, + { + provide: APP_INITIALIZER, + useFactory: serverConnectionFactory, + deps: [ServerConnectionService, Store], + multi: true, + }, + GoogleMapsLoader, + { + provide: APP_INITIALIZER, + useFactory: googleMapsLoaderFactory, + deps: [GoogleMapsLoader], + multi: true, + }, + MaintenanceService, + { + provide: APP_INITIALIZER, + useFactory: maintenanceFactory, + deps: [MaintenanceService], + multi: true, + }, + StatusBar, + SplashScreen, + GoogleMaps, + // { provide: ErrorHandler, useClass: IonicErrorHandler }, + { provide: RouteReuseStrategy, useClass: IonicRouteStrategy }, + GoogleAnalytics, + Mixpanel, + Network, + Globalization, + Device, + CarriersOrdersService, + Vibration, + LocalNotifications, + Store, + ] +}) +export class AppModule { + constructor() {} +} + +// @ngx-translation needs this function +export function HttpLoaderFactory(http: HttpClient) { + return new TranslateHttpLoader(http, './assets/i18n/', '.json'); +} + +export function googleMapsLoaderFactory(provider: GoogleMapsLoader) { + return () => provider.load(environment.GOOGLE_MAPS_API_KEY); +} + +export function maintenanceFactory(provider: MaintenanceService) { + return () => + provider.load( + environment['SETTINGS_APP_TYPE'], + environment['SETTINGS_MAINTENANCE_API_URL'] + ); +} + +export function serverConnectionFactory( + provider: ServerConnectionService, + store: Store +) { + return () => provider.load(environment.SERVICES_ENDPOINT, store); +} diff --git a/packages/carrier-mobile-ionic/src/app/info/info.module.guard.ts b/packages/carrier-mobile-ionic/src/app/info/info.module.guard.ts new file mode 100644 index 0000000..34e40b1 --- /dev/null +++ b/packages/carrier-mobile-ionic/src/app/info/info.module.guard.ts @@ -0,0 +1,21 @@ +import { Injectable } from '@angular/core'; +import { Router, CanLoad, Route } from '@angular/router'; +import { Store } from 'services/store.service'; + +@Injectable() +export class InfoModuleGuard implements CanLoad { + constructor( + private readonly router: Router, + private readonly store: Store + ) {} + + canLoad(route: Route): boolean { + const showInformationPage = this.store.showInformationPage; + if (!showInformationPage) { + this.router.navigateByUrl(''); + return false; + } + + return true; + } +} diff --git a/packages/carrier-mobile-ionic/src/app/info/info.module.ts b/packages/carrier-mobile-ionic/src/app/info/info.module.ts new file mode 100644 index 0000000..1a865c1 --- /dev/null +++ b/packages/carrier-mobile-ionic/src/app/info/info.module.ts @@ -0,0 +1,47 @@ +import { Routes, RouterModule } from '@angular/router'; +import { NgModule } from '@angular/core'; +import { MaintenanceModuleGuard } from './maintenance-info/maintenance.module.guard'; +import { ServerDownModuleGuard } from './no-server-connection/no-server.module.guard'; +import { NoInternetModuleGuard } from './no-internet/no-internet.modue.guard'; + +const routes: Routes = [ + { + path: 'maintenance', + loadChildren: () => + import('./maintenance-info/maintenance-info.module').then( + (m) => m.MaintenanceInfoPageModule + ), + canLoad: [MaintenanceModuleGuard], + }, + { + path: 'server-down', + loadChildren: () => + import('./no-server-connection/no-server.module').then( + (m) => m.ServerDownModule + ), + canLoad: [ServerDownModuleGuard], + }, + { + path: 'no-internet', + loadChildren: () => + import('./no-internet/no-internet.module').then( + (m) => m.NoInternerModule + ), + canLoad: [NoInternetModuleGuard], + }, + { + path: '', + pathMatch: 'full', + redirectTo: 'maintenance', + }, +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + providers: [ + MaintenanceModuleGuard, + ServerDownModuleGuard, + NoInternetModuleGuard, + ], +}) +export class InfoPageModule {} diff --git a/packages/carrier-mobile-ionic/src/app/info/information.scss b/packages/carrier-mobile-ionic/src/app/info/information.scss new file mode 100644 index 0000000..8e2e7f4 --- /dev/null +++ b/packages/carrier-mobile-ionic/src/app/info/information.scss @@ -0,0 +1,19 @@ +@import '~@ever-platform/common-angular/src/scss/everbie.common'; + +.info-page { + background: $brand !important; + height: 100%; + display: flex; + align-items: center; + .view-content { + margin: auto; + + .logo { + text-align: center; + } + } + .info-massage h3 { + color: red; + width: 100%; + } +} diff --git a/packages/carrier-mobile-ionic/src/app/info/maintenance-info/maintenance-info.html b/packages/carrier-mobile-ionic/src/app/info/maintenance-info/maintenance-info.html new file mode 100644 index 0000000..c621b79 --- /dev/null +++ b/packages/carrier-mobile-ionic/src/app/info/maintenance-info/maintenance-info.html @@ -0,0 +1,5 @@ +
diff --git a/packages/carrier-mobile-ionic/src/app/info/maintenance-info/maintenance-info.module.ts b/packages/carrier-mobile-ionic/src/app/info/maintenance-info/maintenance-info.module.ts new file mode 100644 index 0000000..2f5c7d2 --- /dev/null +++ b/packages/carrier-mobile-ionic/src/app/info/maintenance-info/maintenance-info.module.ts @@ -0,0 +1,20 @@ +import { NgModule } from '@angular/core'; + +import { MaintenanceInfoPage } from './maintenance-info'; +import { PipesModule } from '@modules/client.common.angular2/pipes/pipes.module'; +import { Routes, RouterModule } from '@angular/router'; +import { CommonModule } from '@angular/common'; + +const routes: Routes = [ + { + path: '', + component: MaintenanceInfoPage, + }, +]; + +@NgModule({ + declarations: [MaintenanceInfoPage], + imports: [CommonModule, RouterModule.forChild(routes), PipesModule], + exports: [MaintenanceInfoPage] +}) +export class MaintenanceInfoPageModule {} diff --git a/packages/carrier-mobile-ionic/src/app/info/maintenance-info/maintenance-info.scss b/packages/carrier-mobile-ionic/src/app/info/maintenance-info/maintenance-info.scss new file mode 100644 index 0000000..7a191cf --- /dev/null +++ b/packages/carrier-mobile-ionic/src/app/info/maintenance-info/maintenance-info.scss @@ -0,0 +1,2 @@ +page-maintenance-info { +} diff --git a/packages/carrier-mobile-ionic/src/app/info/maintenance-info/maintenance-info.ts b/packages/carrier-mobile-ionic/src/app/info/maintenance-info/maintenance-info.ts new file mode 100644 index 0000000..b3792ea --- /dev/null +++ b/packages/carrier-mobile-ionic/src/app/info/maintenance-info/maintenance-info.ts @@ -0,0 +1,51 @@ +import { Component } from '@angular/core'; +import { environment } from '../../../environments/environment'; +import { MaintenanceService } from '@modules/client.common.angular2/services/maintenance.service'; +import { Store } from '../../../services/store.service'; +import { Router } from '@angular/router'; + +@Component({ + selector: 'page-maintenance-info', + templateUrl: 'maintenance-info.html', +}) +export class MaintenanceInfoPage { + public message: string; + public interval; + constructor( + private maintenanceService: MaintenanceService, + private store: Store, + private router: Router + ) { + this.getMessage(); + this.getStatus(); + } + + async getMessage() { + this.message = await this.maintenanceService.getMessage( + this.store.maintenanceMode, + environment['SETTINGS_MAINTENANCE_API_URL'] + ); + } + + private async getStatus() { + this.interval = setInterval(async () => { + const status = await this.maintenanceService.getStatus( + environment['SETTINGS_APP_TYPE'], + environment['SETTINGS_MAINTENANCE_API_URL'] + ); + console.warn( + `Maintenance on '${this.store.maintenanceMode}': ${status}` + ); + + if (!status) { + clearInterval(this.interval); + this.store.clearMaintenanceMode(); + this.router.navigateByUrl(''); + } + }, 5000); + } + + ionViewWillLeave() { + clearInterval(this.interval); + } +} diff --git a/packages/carrier-mobile-ionic/src/app/info/maintenance-info/maintenance.module.guard.ts b/packages/carrier-mobile-ionic/src/app/info/maintenance-info/maintenance.module.guard.ts new file mode 100644 index 0000000..3538bb7 --- /dev/null +++ b/packages/carrier-mobile-ionic/src/app/info/maintenance-info/maintenance.module.guard.ts @@ -0,0 +1,21 @@ +import { Injectable } from '@angular/core'; +import { Router, CanLoad } from '@angular/router'; +import { Store } from 'services/store.service'; + +@Injectable() +export class MaintenanceModuleGuard implements CanLoad { + constructor( + private readonly router: Router, + private readonly store: Store + ) {} + + canLoad(): boolean { + const maintenanceMode = this.store.maintenanceMode; + if (!maintenanceMode) { + this.router.navigateByUrl('info/server-down'); + return false; + } + + return true; + } +} diff --git a/packages/carrier-mobile-ionic/src/app/info/no-internet/no-internet.modue.guard.ts b/packages/carrier-mobile-ionic/src/app/info/no-internet/no-internet.modue.guard.ts new file mode 100644 index 0000000..6485471 --- /dev/null +++ b/packages/carrier-mobile-ionic/src/app/info/no-internet/no-internet.modue.guard.ts @@ -0,0 +1,19 @@ +import { Router, CanLoad } from '@angular/router'; +import { Injectable } from '@angular/core'; +import { Store } from 'services/store.service'; + +@Injectable() +export class NoInternetModuleGuard implements CanLoad { + constructor( + private readonly router: Router, + private readonly store: Store + ) {} + + canLoad(): boolean { + if (!this.store.noInternet) { + this.router.navigate(['']); + return false; + } + return true; + } +} diff --git a/packages/carrier-mobile-ionic/src/app/info/no-internet/no-internet.module.ts b/packages/carrier-mobile-ionic/src/app/info/no-internet/no-internet.module.ts new file mode 100644 index 0000000..afb9b80 --- /dev/null +++ b/packages/carrier-mobile-ionic/src/app/info/no-internet/no-internet.module.ts @@ -0,0 +1,25 @@ +import { NgModule } from '@angular/core'; +import { Routes, RouterModule } from '@angular/router'; +import { CommonModule } from '@angular/common'; +import { IonicModule } from '@ionic/angular'; +import { NoInternetPage } from './no-internet'; +import { TranslateModule } from '@ngx-translate/core'; + +const routes: Routes = [ + { + path: '', + component: NoInternetPage, + }, +]; + +@NgModule({ + declarations: [NoInternetPage], + imports: [ + CommonModule, + TranslateModule.forChild(), + RouterModule.forChild(routes), + IonicModule, + ], + exports: [NoInternetPage] +}) +export class NoInternerModule {} diff --git a/packages/carrier-mobile-ionic/src/app/info/no-internet/no-internet.ts b/packages/carrier-mobile-ionic/src/app/info/no-internet/no-internet.ts new file mode 100644 index 0000000..6b012dc --- /dev/null +++ b/packages/carrier-mobile-ionic/src/app/info/no-internet/no-internet.ts @@ -0,0 +1,48 @@ +import { Component } from '@angular/core'; +import { environment } from '../../../environments/environment'; +import { Network } from '@ionic-native/network/ngx'; +import { first } from 'rxjs/operators'; +import { Router } from '@angular/router'; +import { Store } from 'services/store.service'; + +@Component({ + selector: 'no-internet', + styleUrls: [`../information.scss`], + template: ` +
+
+ + +
+

+ {{ + 'NO_INTERNET_VIEW.NETWORK_WAS_DISCONNECTED' + | translate + }} +

+
+
+
+ `, +}) +export class NoInternetPage { + public noInternetLogo: string; + + constructor( + private network: Network, + private router: Router, + private store: Store + ) { + this.noInternetLogo = environment.NO_INTERNET_LOGO; + + this.networkWatch(); + } + + async networkWatch() { + await this.network.onConnect().pipe(first()).toPromise(); + this.store.clearNoInternet(); + this.router.navigateByUrl(''); + } +} diff --git a/packages/carrier-mobile-ionic/src/app/info/no-server-connection/no-server-connection.html b/packages/carrier-mobile-ionic/src/app/info/no-server-connection/no-server-connection.html new file mode 100644 index 0000000..2b8702c --- /dev/null +++ b/packages/carrier-mobile-ionic/src/app/info/no-server-connection/no-server-connection.html @@ -0,0 +1,13 @@ +
+
+ + +
+

+ {{ 'NO_SERVER_VIEW.NO_SERVER' | translate }} +

+
+
+
diff --git a/packages/carrier-mobile-ionic/src/app/info/no-server-connection/no-server-connection.ts b/packages/carrier-mobile-ionic/src/app/info/no-server-connection/no-server-connection.ts new file mode 100644 index 0000000..082d74f --- /dev/null +++ b/packages/carrier-mobile-ionic/src/app/info/no-server-connection/no-server-connection.ts @@ -0,0 +1,43 @@ +import { Component } from '@angular/core'; +import { environment } from '../../../environments/environment'; +import { Store } from '../../../services/store.service'; +import { ServerConnectionService } from '@modules/client.common.angular2/services/server-connection.service'; +import { Router } from '@angular/router'; + +@Component({ + selector: 'no-server-connection', + styleUrls: [`../information.scss`], + templateUrl: 'no-server-connection.html', +}) +export class NoServerConnectionComponent { + noInternetLogo: string; + interval; + + constructor( + private store: Store, + private router: Router, + private serverConnectionService: ServerConnectionService + ) { + this.noInternetLogo = environment.NO_INTERNET_LOGO; + this.testConnection(); + } + + private async testConnection() { + this.interval = setInterval(async () => { + await this.serverConnectionService.checkServerConnection( + environment.SERVICES_ENDPOINT, + this.store + ); + + if (!this.store.showInformationPage) { + clearInterval(this.interval); + this.store.clearMaintenanceMode(); + this.router.navigateByUrl(''); + } + }, 5000); + } + + ionViewWillLeave() { + clearInterval(this.interval); + } +} diff --git a/packages/carrier-mobile-ionic/src/app/info/no-server-connection/no-server.module.guard.ts b/packages/carrier-mobile-ionic/src/app/info/no-server-connection/no-server.module.guard.ts new file mode 100644 index 0000000..d5ab617 --- /dev/null +++ b/packages/carrier-mobile-ionic/src/app/info/no-server-connection/no-server.module.guard.ts @@ -0,0 +1,20 @@ +import { Router, CanLoad } from '@angular/router'; +import { Injectable } from '@angular/core'; +import { Store } from 'services/store.service'; + +@Injectable() +export class ServerDownModuleGuard implements CanLoad { + constructor( + private readonly router: Router, + private readonly store: Store + ) {} + + canLoad(): boolean { + const serverDown = Number(this.store.serverConnection) === 0; + if (!serverDown) { + this.router.navigate(['']); + return false; + } + return true; + } +} diff --git a/packages/carrier-mobile-ionic/src/app/info/no-server-connection/no-server.module.ts b/packages/carrier-mobile-ionic/src/app/info/no-server-connection/no-server.module.ts new file mode 100644 index 0000000..6b20ff3 --- /dev/null +++ b/packages/carrier-mobile-ionic/src/app/info/no-server-connection/no-server.module.ts @@ -0,0 +1,24 @@ +import { NgModule } from '@angular/core'; +import { Routes, RouterModule } from '@angular/router'; +import { CommonModule } from '@angular/common'; +import { NoServerConnectionComponent } from './no-server-connection'; +import { IonicModule } from '@ionic/angular'; +import { TranslateModule } from '@ngx-translate/core'; + +const routes: Routes = [ + { + path: '', + component: NoServerConnectionComponent, + }, +]; + +@NgModule({ + declarations: [NoServerConnectionComponent], + imports: [ + CommonModule, + RouterModule.forChild(routes), + TranslateModule.forChild(), + IonicModule, + ], +}) +export class ServerDownModule {} diff --git a/packages/carrier-mobile-ionic/src/app/main.ts b/packages/carrier-mobile-ionic/src/app/main.ts new file mode 100644 index 0000000..2470c95 --- /dev/null +++ b/packages/carrier-mobile-ionic/src/app/main.ts @@ -0,0 +1,4 @@ +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; +import { AppModule } from './app.module'; + +platformBrowserDynamic().bootstrapModule(AppModule); diff --git a/packages/carrier-mobile-ionic/src/assets/fonts/FontAwesome.otf b/packages/carrier-mobile-ionic/src/assets/fonts/FontAwesome.otf new file mode 100644 index 0000000..401ec0f Binary files /dev/null and b/packages/carrier-mobile-ionic/src/assets/fonts/FontAwesome.otf differ diff --git a/packages/carrier-mobile-ionic/src/assets/fonts/fontawesome-webfont.eot b/packages/carrier-mobile-ionic/src/assets/fonts/fontawesome-webfont.eot new file mode 100644 index 0000000..e9f60ca Binary files /dev/null and b/packages/carrier-mobile-ionic/src/assets/fonts/fontawesome-webfont.eot differ diff --git a/packages/carrier-mobile-ionic/src/assets/fonts/fontawesome-webfont.svg b/packages/carrier-mobile-ionic/src/assets/fonts/fontawesome-webfont.svg new file mode 100644 index 0000000..855c845 --- /dev/null +++ b/packages/carrier-mobile-ionic/src/assets/fonts/fontawesome-webfont.svg @@ -0,0 +1,2671 @@ + + + + +Created by FontForge 20120731 at Mon Oct 24 17:37:40 2016 + By ,,, +Copyright Dave Gandy 2016. All rights reserved. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/carrier-mobile-ionic/src/assets/fonts/fontawesome-webfont.ttf b/packages/carrier-mobile-ionic/src/assets/fonts/fontawesome-webfont.ttf new file mode 100644 index 0000000..35acda2 Binary files /dev/null and b/packages/carrier-mobile-ionic/src/assets/fonts/fontawesome-webfont.ttf differ diff --git a/packages/carrier-mobile-ionic/src/assets/fonts/fontawesome-webfont.woff b/packages/carrier-mobile-ionic/src/assets/fonts/fontawesome-webfont.woff new file mode 100644 index 0000000..400014a Binary files /dev/null and b/packages/carrier-mobile-ionic/src/assets/fonts/fontawesome-webfont.woff differ diff --git a/packages/carrier-mobile-ionic/src/assets/fonts/fontawesome-webfont.woff2 b/packages/carrier-mobile-ionic/src/assets/fonts/fontawesome-webfont.woff2 new file mode 100644 index 0000000..4d13fc6 Binary files /dev/null and b/packages/carrier-mobile-ionic/src/assets/fonts/fontawesome-webfont.woff2 differ diff --git a/packages/carrier-mobile-ionic/src/assets/i18n/bg.json b/packages/carrier-mobile-ionic/src/assets/i18n/bg.json new file mode 100644 index 0000000..d62b783 --- /dev/null +++ b/packages/carrier-mobile-ionic/src/assets/i18n/bg.json @@ -0,0 +1,85 @@ +{ + "LANGUAGE": { + "ID": "bg-BG", + "NAME": "Български" + }, + "CURRENT_DIRECTION": "ltr", + "SIDEBAR_SIDE": "left", + "MAIN_VIEW": { + "EVERCO_DRIVER": "Шофьор" + }, + "MENU": { + "MAIN": "Oсновен", + "DELIVERIES": "Доставките", + "SETTINGS": "Настройки", + "LANGUAGE": "Език", + "ABOUT_US": "За нас", + "TERMS_OF_USE": "Условия за ползване" + }, + "DELIVERY_VIEW": { + "THE_CUSTOMER_HAS_TO_PAY": "Клиентът трябва да плати", + "CUSTOMER_ALREADY_PAID_WITH_CARD": "Клиент вече е платил с карта!", + "DELIVERED": "Доставени", + "CANCEL": "Отказ", + "DELIVERIES_HISTORY": "История на доставките", + "TO": "До", + "FROM": "От", + "ID": "ИД", + "CANCELED": "Отменен", + "IS_PAID": "Платено е", + "CONFIRMED": "Потвърдено", + "DETAILS": "Детайли" + }, + "DRIVE_TO_WAREHOUSE_VIEW": { + "TAKE_WORK": "Вземете работа", + "SKIP_WORK": "Пропуснете работата", + "I'M_THERE": "Tам съм", + "CANCEL": "Отказ" + }, + "HOME_VIEW": { + "DELIVERIES": "Доставки", + "WORKING": "Работещ", + "NOT_WORKING": "Не работи", + "START_WORKING": "Започнете работа", + "STOP_WORKING": "Спри да работиш" + }, + "STARTING_DELIVERY_VIEW": { + "START": "Начало", + "CANCEL": "Отказ" + }, + "NO_INTERNET_VIEW": { + "NETWORK_WAS_DISCONNECTED": "Мрежата е прекъсната" + }, + "NO_SERVER_VIEW": { + "NO_SERVER": "Няма връзка със сървъра" + }, + "GET_PRODUCT_VIEW": { + "GOT_IT": "Получих го", + "CANCEL": "Отказ" + }, + "RETURN_PRODUCT_VIEW": { + "RETURN_PRODUCT": "Връщане на продукта", + "CANCEL": "Отказ" + }, + "LOGIN_VIEW": { + "LOGIN": "Влез" + }, + "ABOUT_VIEW": { + "TITLE": "За нас" + }, + "TERMS_OF_USE_VIEW": { + "TITLE": "Условия за ползване" + }, + "LANGUAGE_VIEW": { + "TITLE": "Изберете език", + "ENGLISH": "Английски", + "BULGARIAN": "Български", + "HEBREW": "Иврит", + "RUSSIAN": "Руски", + "SPANISH": "Испански", + "FRENCH": "Френски" + }, + "LIST_VIEW": { + "YOU_NAVIGATED_HERE_FROM": "От тук сте навигирали" + } +} diff --git a/packages/carrier-mobile-ionic/src/assets/i18n/en.json b/packages/carrier-mobile-ionic/src/assets/i18n/en.json new file mode 100644 index 0000000..37d3ec2 --- /dev/null +++ b/packages/carrier-mobile-ionic/src/assets/i18n/en.json @@ -0,0 +1,85 @@ +{ + "LANGUAGE": { + "ID": "en-US", + "NAME": "English" + }, + "CURRENT_DIRECTION": "ltr", + "SIDEBAR_SIDE": "left", + "MAIN_VIEW": { + "EVERCO_DRIVER": "Carrier" + }, + "MENU": { + "MAIN": "Main", + "DELIVERIES": "Deliveries", + "SETTINGS": "Settings", + "LANGUAGE": "Language", + "ABOUT_US": "About Us", + "TERMS_OF_USE": "Terms of Use" + }, + "DELIVERY_VIEW": { + "THE_CUSTOMER_HAS_TO_PAY": "The Customer has to pay", + "CUSTOMER_ALREADY_PAID_WITH_CARD": "Customer already paid with card!", + "DELIVERED": "Delivered", + "CANCEL": "Cancel", + "DELIVERIES_HISTORY": "Deliveries History", + "TO": "To", + "FROM": "From", + "ID": "ID", + "IS_PAID": "Is Paid", + "CANCELED": "Canceled", + "CONFIRMED": "Confirmed", + "DETAILS": "Details" + }, + "DRIVE_TO_WAREHOUSE_VIEW": { + "TAKE_WORK": "Take work", + "SKIP_WORK": "Skip Work", + "I'M_THERE": "I'm there", + "CANCEL": "Cancel" + }, + "HOME_VIEW": { + "DELIVERIES": "Deliveries", + "WORKING": "Working", + "NOT_WORKING": "Not Working", + "START_WORKING": "Start Working", + "STOP_WORKING": "Stop Working" + }, + "STARTING_DELIVERY_VIEW": { + "START": "Start", + "CANCEL": "Cancel" + }, + "NO_INTERNET_VIEW": { + "NETWORK_WAS_DISCONNECTED": "Network was disconnected" + }, + "NO_SERVER_VIEW": { + "NO_SERVER": "Server connection is lost" + }, + "GET_PRODUCT_VIEW": { + "GOT_IT": "Got It", + "CANCEL": "Cancel" + }, + "RETURN_PRODUCT_VIEW": { + "RETURN_PRODUCT": "Return Product", + "CANCEL": "Cancel" + }, + "LOGIN_VIEW": { + "LOGIN": "Login" + }, + "ABOUT_VIEW": { + "TITLE": "About Us" + }, + "TERMS_OF_USE_VIEW": { + "TITLE": "Terms of Use" + }, + "LANGUAGE_VIEW": { + "TITLE": "Select language", + "ENGLISH": "English", + "BULGARIAN": "Bulgarian", + "HEBREW": "Hebrew", + "RUSSIAN": "Rusian", + "SPANISH": "Spanish", + "French": "French" + }, + "LIST_VIEW": { + "YOU_NAVIGATED_HERE_FROM": "You navigated here from" + } +} diff --git a/packages/carrier-mobile-ionic/src/assets/i18n/es.json b/packages/carrier-mobile-ionic/src/assets/i18n/es.json new file mode 100644 index 0000000..40a3fc3 --- /dev/null +++ b/packages/carrier-mobile-ionic/src/assets/i18n/es.json @@ -0,0 +1,85 @@ +{ + "LANGUAGE": { + "ID": "es-ES", + "NAME": "Español" + }, + "CURRENT_DIRECTION": "ltr", + "SIDEBAR_SIDE": "left", + "MAIN_VIEW": { + "EVERCO_DRIVER": "Carrier" + }, + "MENU": { + "MAIN": "Principal", + "DELIVERIES": "Repartos", + "SETTINGS": "Configuraciones", + "LANGUAGE": "Idioma", + "ABOUT_US": "Acerca de", + "TERMS_OF_USE": "Condiciones de Uso" + }, + "DELIVERY_VIEW": { + "THE_CUSTOMER_HAS_TO_PAY": "El cliente tiene que pagar", + "CUSTOMER_ALREADY_PAID_WITH_CARD": "¡El cliente ya pagó con tarjeta!", + "DELIVERED": "Entregado", + "CANCEL": "Cancelado", + "DELIVERIES_HISTORY": "Historial de Repartos", + "TO": "Hasta", + "FROM": "Desde", + "ID": "ID", + "IS_PAID": "Está pago", + "CANCELED": "Cancelado", + "CONFIRMED": "Confirmado", + "DETAILS": "Detalles" + }, + "DRIVE_TO_WAREHOUSE_VIEW": { + "TAKE_WORK": "Tomar pedido", + "SKIP_WORK": "Saltar pedido", + "I'M_THERE": "Estoy aquí", + "CANCEL": "Cancelar" + }, + "HOME_VIEW": { + "DELIVERIES": "Repartos", + "WORKING": "Trabajando", + "NOT_WORKING": "No Trabajando", + "START_WORKING": "Comenzar a Trabajar", + "STOP_WORKING": "Parar de Trabajar" + }, + "STARTING_DELIVERY_VIEW": { + "START": "Comenzar", + "CANCEL": "Cancelar" + }, + "NO_INTERNET_VIEW": { + "NETWORK_WAS_DISCONNECTED": "Sin Red" + }, + "NO_SERVER_VIEW": { + "NO_SERVER": "Se perdió conexión con el servidor" + }, + "GET_PRODUCT_VIEW": { + "GOT_IT": "Lo Tengo", + "CANCEL": "Cancelar" + }, + "RETURN_PRODUCT_VIEW": { + "RETURN_PRODUCT": "Devolver Producto", + "CANCEL": "Cancelar" + }, + "LOGIN_VIEW": { + "LOGIN": "Entrar" + }, + "ABOUT_VIEW": { + "TITLE": "Acerca de" + }, + "TERMS_OF_USE_VIEW": { + "TITLE": "Condiciones de Uso" + }, + "LANGUAGE_VIEW": { + "TITLE": "Seleccionar Idioma", + "ENGLISH": "Inglés", + "BULGARIAN": "Bulgaro", + "HEBREW": "Hebreo", + "RUSSIAN": "Ruso", + "ESPAÑOL": "Español", + "FRENCH": "Francés" + }, + "LIST_VIEW": { + "YOU_NAVIGATED_HERE_FROM": "Usted llegó acá desde" + } +} diff --git a/packages/carrier-mobile-ionic/src/assets/i18n/fr.json b/packages/carrier-mobile-ionic/src/assets/i18n/fr.json new file mode 100644 index 0000000..9f6531a --- /dev/null +++ b/packages/carrier-mobile-ionic/src/assets/i18n/fr.json @@ -0,0 +1,85 @@ +{ + "LANGUAGE": { + "ID": "fr-FR", + "NAME": "Français" + }, + "CURRENT_DIRECTION": "Ltr", + "SIDEBAR_SIDE": "Gauche", + "MAIN_VIEW": { + "EVERCO_DRIVER": "Transporteur" + }, + "MENU": { + "MAIN": "Principal", + "DELIVERIES": "Livraisons", + "SETTINGS": "Paramètres", + "LANGUAGE": "Langue", + "ABOUT_US": "Qui sommes-nous", + "TERMS_OF_USE": "Conditions d’utilisation" + }, + "DELIVERY_VIEW": { + "THE_CUSTOMER_HAS_TO_PAY": "Le Client doit payer", + "CUSTOMER_ALREADY_PAID_WITH_CARD": "Client déjà payé par carte!", + "DELIVERED": "Livré", + "CANCEL": "Annuler", + "DELIVERIES_HISTORY": "Historique des livraisons", + "TO": "To", + "FROM": "From", + "ID": "ID", + "IS_PAID": "Is Paid", + "CANCELED": "Canceled", + "CONFIRMED": "Confirmed", + "DETAILS": "Details" + }, + "DRIVE_TO_WAREHOUSE_VIEW": { + "TAKE_WORK": "Take work", + "SKIP_WORK": "Skip Work", + "I'M_THERE": "I'm there", + "CANCEL": "Annuler" + }, + "HOME_VIEW": { + "DELIVERIES": "Livraisons", + "WORKING": "en marche", + "NOT_WORKING": "Ne marche pas", + "START_WORKING": "Commence à fonctionner", + "STOP_WORKING": "Arrêt" + }, + "STARTING_DELIVERY_VIEW": { + "START": "Commencer", + "CANCEL": "Annuler" + }, + "NO_INTERNET_VIEW": { + "NETWORK_WAS_DISCONNECTED": "Network was disconnected" + }, + "NO_SERVER_VIEW": { + "NO_SERVER": "Server connection is lost" + }, + "GET_PRODUCT_VIEW": { + "GOT_IT": "Ok", + "CANCEL": "Annuler" + }, + "RETURN_PRODUCT_VIEW": { + "RETURN_PRODUCT": "Return Product", + "CANCEL": "Annuler" + }, + "LOGIN_VIEW": { + "LOGIN": "Connexion" + }, + "ABOUT_VIEW": { + "TITLE": "Qui sommes-nous" + }, + "TERMS_OF_USE_VIEW": { + "TITLE": "Conditions d’utilisation" + }, + "LANGUAGE_VIEW": { + "TITLE": "Sélectionner la langue", + "ENGLISH": "Anglais", + "BULGARIAN": "Bulgarian", + "HEBREW": "Hebrew", + "RUSSIAN": "Russe", + "SPANISH": "Spanish", + "FRENCH": "Français" + }, + "LIST_VIEW": { + "YOU_NAVIGATED_HERE_FROM": "You navigated here from" + } +} diff --git a/packages/carrier-mobile-ionic/src/assets/i18n/he.json b/packages/carrier-mobile-ionic/src/assets/i18n/he.json new file mode 100644 index 0000000..7e3104d --- /dev/null +++ b/packages/carrier-mobile-ionic/src/assets/i18n/he.json @@ -0,0 +1,85 @@ +{ + "LANGUAGE": { + "ID": "he-IL", + "NAME": "עברית" + }, + "CURRENT_DIRECTION": "rtl", + "SIDEBAR_SIDE": "right", + "MAIN_VIEW": { + "EVERCO_DRIVER": "נהג" + }, + "MENU": { + "MAIN": "ראשי", + "DELIVERIES": "משלוחים", + "SETTINGS": "הגדרות", + "LANGUAGE": "שפה", + "ABOUT_US": "עלינו", + "TERMS_OF_USE": "תנאי שימוש" + }, + "DELIVERY_VIEW": { + "THE_CUSTOMER_HAS_TO_PAY": "הלקוח חייב לשלם", + "CUSTOMER_ALREADY_PAID_WITH_CARD": "הלקוח כבר שילם עם כרטיס!", + "DELIVERED": "נמסר", + "CANCEL": "לְבַטֵל", + "DELIVERIES_HISTORY": "ההיסטוריה של משלוחים", + "TO": "אֶל", + "FROM": "מ", + "ID": "תְעוּדַת זֶהוּת", + "CANCELED": "מבוטל", + "CONFIRMED": "מְאוּשָׁר", + "IS_PAID": "שולם", + "DETAILS": "פרטים" + }, + "DRIVE_TO_WAREHOUSE_VIEW": { + "TAKE_WORK": "קח עבודה", + "SKIP_WORK": "דלג על עבודה", + "I'M_THERE": "אני שם", + "CANCEL": "לְבַטֵל" + }, + "HOME_VIEW": { + "DELIVERIES": "משלוחים", + "WORKING": "עובד", + "NOT_WORKING": "לא עובד", + "START_WORKING": "תתחיל לעבוד", + "STOP_WORKING": "תפסיק לעבוד" + }, + "STARTING_DELIVERY_VIEW": { + "START": "התחל", + "CANCEL": "בטל" + }, + "NO_INTERNET_VIEW": { + "NETWORK_WAS_DISCONNECTED": "הרשת נותקה" + }, + "NO_SERVER_VIEW": { + "NO_SERVER": "השרת נפל" + }, + "GET_PRODUCT_VIEW": { + "GOT_IT": "הבנתי", + "CANCEL": "לְבַטֵל" + }, + "RETURN_PRODUCT_VIEW": { + "RETURN_PRODUCT": "מוצר החזרה", + "CANCEL": "לְבַטֵל" + }, + "LOGIN_VIEW": { + "LOGIN": "התחברות" + }, + "ABOUT_VIEW": { + "TITLE": "עלינו" + }, + "TERMS_OF_USE_VIEW": { + "TITLE": "תנאי שימוש" + }, + "LANGUAGE_VIEW": { + "TITLE": "בחירת שפה", + "ENGLISH": "אנגלית", + "BULGARIAN": "בולגרית", + "HEBREW": "עברית", + "RUSSIAN": "רוסית", + "SPANISH": "ספרדית", + "FRENCH": "צָרְפָתִית" + }, + "LIST_VIEW": { + "YOU_NAVIGATED_HERE_FROM": "אתה מנווט כאן מ" + } +} diff --git a/packages/carrier-mobile-ionic/src/assets/i18n/ru.json b/packages/carrier-mobile-ionic/src/assets/i18n/ru.json new file mode 100644 index 0000000..fe0ebca --- /dev/null +++ b/packages/carrier-mobile-ionic/src/assets/i18n/ru.json @@ -0,0 +1,85 @@ +{ + "LANGUAGE": { + "ID": "ru-RU", + "NAME": "Русский" + }, + "CURRENT_DIRECTION": "ltr", + "SIDEBAR_SIDE": "left", + "MAIN_VIEW": { + "EVERCO_DRIVER": "Курьер" + }, + "MENU": { + "MAIN": "Главный", + "DELIVERIES": "Поставки", + "SETTINGS": "Настройки", + "LANGUAGE": "Язык", + "ABOUT_US": "Насчет нас", + "TERMS_OF_USE": "Условия эксплуатации" + }, + "DELIVERY_VIEW": { + "THE_CUSTOMER_HAS_TO_PAY": "Клиент должен заплатить", + "CUSTOMER_ALREADY_PAID_WITH_CARD": "Клиент уже заплатил карточкой!", + "DELIVERED": "доставлен", + "CANCEL": "Отмена", + "DELIVERIES_HISTORY": "История поставок", + "TO": "До", + "FROM": "Из", + "ID": "Я БЫ", + "CANCELED": "Отменен", + "CONFIRMED": "Подтвердил", + "IS_PAID": "Оплачивается", + "DETAILS": "Детали" + }, + "DRIVE_TO_WAREHOUSE_VIEW": { + "TAKE_WORK": "Возьмите работу", + "SKIP_WORK": "Пропустить работу", + "I'M_THERE": "я там", + "CANCEL": "Отмена" + }, + "HOME_VIEW": { + "DELIVERIES": "Поставки", + "WORKING": "За работой", + "NOT_WORKING": "Не работает", + "START_WORKING": "Начать работать", + "STOP_WORKING": "Прекрати работать" + }, + "STARTING_DELIVERY_VIEW": { + "START": "Начало", + "CANCEL": "Отменить" + }, + "NO_INTERNET_VIEW": { + "NETWORK_WAS_DISCONNECTED": "Сеть была отключена" + }, + "NO_SERVER_VIEW": { + "NO_SERVER": "Сервер упал" + }, + "GET_PRODUCT_VIEW": { + "GOT_IT": "Понял", + "CANCEL": "Отмена" + }, + "RETURN_PRODUCT_VIEW": { + "RETURN_PRODUCT": "Вернуться", + "CANCEL": "Отмена" + }, + "LOGIN_VIEW": { + "LOGIN": "Авторизоваться" + }, + "ABOUT_VIEW": { + "TITLE": "О нас" + }, + "TERMS_OF_USE_VIEW": { + "TITLE": "Условия эксплуатации" + }, + "LANGUAGE_VIEW": { + "TITLE": "Выберите язык", + "ENGLISH": "Английский", + "BULGARIAN": "Болгарский", + "HEBREW": "Иврит", + "RUSSIAN": "Русский", + "SPANISH": "Испанский", + "FRENCH": "Французский" + }, + "LIST_VIEW": { + "YOU_NAVIGATED_HERE_FROM": "Вы перешли сюда из" + } +} diff --git a/packages/carrier-mobile-ionic/src/assets/icon/favicon.ico b/packages/carrier-mobile-ionic/src/assets/icon/favicon.ico new file mode 100644 index 0000000..79c60ff Binary files /dev/null and b/packages/carrier-mobile-ionic/src/assets/icon/favicon.ico differ diff --git a/packages/carrier-mobile-ionic/src/assets/icon/favicon.png b/packages/carrier-mobile-ionic/src/assets/icon/favicon.png new file mode 100644 index 0000000..d053869 Binary files /dev/null and b/packages/carrier-mobile-ionic/src/assets/icon/favicon.png differ diff --git a/packages/carrier-mobile-ionic/src/assets/icon/favicon16x16.png b/packages/carrier-mobile-ionic/src/assets/icon/favicon16x16.png new file mode 100644 index 0000000..d053869 Binary files /dev/null and b/packages/carrier-mobile-ionic/src/assets/icon/favicon16x16.png differ diff --git a/packages/carrier-mobile-ionic/src/assets/icon/favicon32x32.png b/packages/carrier-mobile-ionic/src/assets/icon/favicon32x32.png new file mode 100644 index 0000000..5864e89 Binary files /dev/null and b/packages/carrier-mobile-ionic/src/assets/icon/favicon32x32.png differ diff --git a/packages/carrier-mobile-ionic/src/assets/icon/favicon48x48.png b/packages/carrier-mobile-ionic/src/assets/icon/favicon48x48.png new file mode 100644 index 0000000..41741bc Binary files /dev/null and b/packages/carrier-mobile-ionic/src/assets/icon/favicon48x48.png differ diff --git a/packages/carrier-mobile-ionic/src/assets/imgs/drawable-xxxhdpi-icon.png b/packages/carrier-mobile-ionic/src/assets/imgs/drawable-xxxhdpi-icon.png new file mode 100644 index 0000000..afb95ef Binary files /dev/null and b/packages/carrier-mobile-ionic/src/assets/imgs/drawable-xxxhdpi-icon.png differ diff --git a/packages/carrier-mobile-ionic/src/assets/imgs/ever-logo.svg b/packages/carrier-mobile-ionic/src/assets/imgs/ever-logo.svg new file mode 100644 index 0000000..6348df2 --- /dev/null +++ b/packages/carrier-mobile-ionic/src/assets/imgs/ever-logo.svg @@ -0,0 +1,12 @@ + + + + ever logo + Created with Sketch. + + + + + + + \ No newline at end of file diff --git a/packages/carrier-mobile-ionic/src/assets/imgs/logo.png b/packages/carrier-mobile-ionic/src/assets/imgs/logo.png new file mode 100644 index 0000000..51dfd56 Binary files /dev/null and b/packages/carrier-mobile-ionic/src/assets/imgs/logo.png differ diff --git a/packages/carrier-mobile-ionic/src/components/components.module.ts b/packages/carrier-mobile-ionic/src/components/components.module.ts new file mode 100644 index 0000000..55ca6fd --- /dev/null +++ b/packages/carrier-mobile-ionic/src/components/components.module.ts @@ -0,0 +1,10 @@ +import { NgModule } from '@angular/core'; +import { LoadingComponent } from './loading/loading'; +import { IonicModule } from '@ionic/angular'; + +@NgModule({ + declarations: [LoadingComponent], + imports: [IonicModule], + exports: [LoadingComponent], +}) +export class ComponentsModule {} diff --git a/packages/carrier-mobile-ionic/src/components/loading/loading.html b/packages/carrier-mobile-ionic/src/components/loading/loading.html new file mode 100644 index 0000000..d8653d0 --- /dev/null +++ b/packages/carrier-mobile-ionic/src/components/loading/loading.html @@ -0,0 +1,3 @@ +
+ +
diff --git a/packages/carrier-mobile-ionic/src/components/loading/loading.scss b/packages/carrier-mobile-ionic/src/components/loading/loading.scss new file mode 100644 index 0000000..6ce9aec --- /dev/null +++ b/packages/carrier-mobile-ionic/src/components/loading/loading.scss @@ -0,0 +1,2 @@ +loading { +} diff --git a/packages/carrier-mobile-ionic/src/components/loading/loading.ts b/packages/carrier-mobile-ionic/src/components/loading/loading.ts new file mode 100644 index 0000000..9005762 --- /dev/null +++ b/packages/carrier-mobile-ionic/src/components/loading/loading.ts @@ -0,0 +1,14 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'loading', + templateUrl: 'loading.html', +}) +export class LoadingComponent { + text: string; + + constructor() { + console.log('Hello LoadingComponent Component'); + this.text = 'Hello World'; + } +} diff --git a/packages/carrier-mobile-ionic/src/components/menu/menu.component.html b/packages/carrier-mobile-ionic/src/components/menu/menu.component.html new file mode 100644 index 0000000..4c99a39 --- /dev/null +++ b/packages/carrier-mobile-ionic/src/components/menu/menu.component.html @@ -0,0 +1,37 @@ + + +

+ + {{ companyName }} +

+
+ + + +
+ + + +

+ + {{ companyName }} +

+
+ + + +
diff --git a/packages/carrier-mobile-ionic/src/components/menu/menu.component.scss b/packages/carrier-mobile-ionic/src/components/menu/menu.component.scss new file mode 100644 index 0000000..e45a765 --- /dev/null +++ b/packages/carrier-mobile-ionic/src/components/menu/menu.component.scss @@ -0,0 +1,24 @@ +ion-menu { + ion-title { + text-align: center; + } + + ion-item-divider { + visibility: visible; + ion-icon { + margin-right: 10px; + } + } + + ion-item { + visibility: visible; + ion-icon { + color: black; + margin-right: 15px; + } + } + + ion-header { + position: relative; + } +} diff --git a/packages/carrier-mobile-ionic/src/components/menu/menu.component.spec.ts b/packages/carrier-mobile-ionic/src/components/menu/menu.component.spec.ts new file mode 100644 index 0000000..ef3873a --- /dev/null +++ b/packages/carrier-mobile-ionic/src/components/menu/menu.component.spec.ts @@ -0,0 +1,24 @@ +import 'jasmine'; +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { MenuComponent } from './menu.component'; + +describe('MenuComponent', () => { + let component: MenuComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [MenuComponent], + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(MenuComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/packages/carrier-mobile-ionic/src/components/menu/menu.component.ts b/packages/carrier-mobile-ionic/src/components/menu/menu.component.ts new file mode 100644 index 0000000..1d81cd4 --- /dev/null +++ b/packages/carrier-mobile-ionic/src/components/menu/menu.component.ts @@ -0,0 +1,49 @@ +import { Component, OnDestroy } from '@angular/core'; +import { Store } from 'services/store.service'; +import { Platform, MenuController } from '@ionic/angular'; +import { TranslateService, LangChangeEvent } from '@ngx-translate/core'; +import { environment } from 'environments/environment'; +import { takeUntil } from 'rxjs/operators'; +import { Subject } from 'rxjs'; + +@Component({ + selector: 'e-cu-menu', + templateUrl: './menu.component.html', + styleUrls: ['./menu.component.scss'], +}) +export class MenuComponent implements OnDestroy { + companyName: string; + + private ngDestroy$ = new Subject(); + + constructor( + private store: Store, + public platform: Platform, + private translateService: TranslateService, + private menuCtrl: MenuController + ) { + this.companyName = environment.APP_NAME; + this.translateService.onLangChange + .pipe(takeUntil(this.ngDestroy$)) + .subscribe((event: LangChangeEvent) => { + if (event.lang === 'he') { + this.menuCtrl.enable(true, 'rtl'); + this.menuCtrl.enable(false, 'ltr'); + } else { + this.menuCtrl.enable(true, 'ltr'); + this.menuCtrl.enable(false, 'rtl'); + } + }); + } + + get showInformationPage() { + return this.store.showInformationPage; + } + + menuOpened() {} + + ngOnDestroy() { + this.ngDestroy$.next(); + this.ngDestroy$.complete(); + } +} diff --git a/packages/carrier-mobile-ionic/src/components/menu/menu.module.ts b/packages/carrier-mobile-ionic/src/components/menu/menu.module.ts new file mode 100644 index 0000000..6786526 --- /dev/null +++ b/packages/carrier-mobile-ionic/src/components/menu/menu.module.ts @@ -0,0 +1,19 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { IonicModule } from '@ionic/angular'; +import { MenuComponent } from './menu.component'; +import { RouterModule } from '@angular/router'; +import { TranslateModule } from '@ngx-translate/core'; +import { SubMenuComponent } from './submenu/submenu.component'; + +@NgModule({ + imports: [ + CommonModule, + RouterModule, + IonicModule, + TranslateModule.forChild(), + ], + exports: [MenuComponent, SubMenuComponent], + declarations: [MenuComponent, SubMenuComponent], +}) +export class MenuModule {} diff --git a/packages/carrier-mobile-ionic/src/components/menu/submenu/submenu.component.html b/packages/carrier-mobile-ionic/src/components/menu/submenu/submenu.component.html new file mode 100644 index 0000000..aea7bcd --- /dev/null +++ b/packages/carrier-mobile-ionic/src/components/menu/submenu/submenu.component.html @@ -0,0 +1,66 @@ + + + + + + + {{ 'MENU.MAIN' | translate }} + + + + + + + {{ 'MENU.DELIVERIES' | translate }} + + + + + + {{ 'MENU.SETTINGS' | translate }} + + + + + + + {{ 'MENU.LANGUAGE' | translate }} + + + + + + + {{ 'MENU.ABOUT_US' | translate }} + + + + + + + {{ 'MENU.TERMS_OF_USE' | translate }} + + + + + diff --git a/packages/carrier-mobile-ionic/src/components/menu/submenu/submenu.component.scss b/packages/carrier-mobile-ionic/src/components/menu/submenu/submenu.component.scss new file mode 100644 index 0000000..1d58271 --- /dev/null +++ b/packages/carrier-mobile-ionic/src/components/menu/submenu/submenu.component.scss @@ -0,0 +1,7 @@ +ion-item { + visibility: visible; + ion-icon { + color: black; + margin-right: 15px; + } +} diff --git a/packages/carrier-mobile-ionic/src/components/menu/submenu/submenu.component.ts b/packages/carrier-mobile-ionic/src/components/menu/submenu/submenu.component.ts new file mode 100644 index 0000000..a573fbf --- /dev/null +++ b/packages/carrier-mobile-ionic/src/components/menu/submenu/submenu.component.ts @@ -0,0 +1,8 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'e-cu-submenu', + templateUrl: './submenu.component.html', + styleUrls: ['./submenu.component.scss'], +}) +export class SubMenuComponent {} diff --git a/packages/carrier-mobile-ionic/src/components/order-card/order-card.html b/packages/carrier-mobile-ionic/src/components/order-card/order-card.html new file mode 100644 index 0000000..dca16b0 --- /dev/null +++ b/packages/carrier-mobile-ionic/src/components/order-card/order-card.html @@ -0,0 +1,6 @@ + + + diff --git a/packages/carrier-mobile-ionic/src/components/order-card/order-card.module.ts b/packages/carrier-mobile-ionic/src/components/order-card/order-card.module.ts new file mode 100644 index 0000000..95bd59c --- /dev/null +++ b/packages/carrier-mobile-ionic/src/components/order-card/order-card.module.ts @@ -0,0 +1,25 @@ +import { NgModule } from '@angular/core'; + +import { TranslateModule } from '@ngx-translate/core'; +import { CommonModule } from '@angular/common'; +import { FormsModule } from '@angular/forms'; +import { OrderCardComponent } from './order-card'; +import { OrderModule } from './order/order.module'; +import { ProductModule } from './order/product/product.module'; +import { WarehouseLogoModule } from '../warehouse-logo/warehouse-logo.module'; +import { IonicModule } from '@ionic/angular'; + +@NgModule({ + declarations: [OrderCardComponent], + exports: [OrderCardComponent], + imports: [ + CommonModule, + FormsModule, + OrderModule, + IonicModule, + ProductModule, + WarehouseLogoModule, + TranslateModule.forChild(), + ], +}) +export class OrderCardModule {} diff --git a/packages/carrier-mobile-ionic/src/components/order-card/order-card.scss b/packages/carrier-mobile-ionic/src/components/order-card/order-card.scss new file mode 100644 index 0000000..2471265 --- /dev/null +++ b/packages/carrier-mobile-ionic/src/components/order-card/order-card.scss @@ -0,0 +1,15 @@ +html, +body { + width: 100%; + height: 100%; +} + +#ceil { + padding-bottom: 1px; +} + +ion-card { + width: 100%; + margin: 0; + background-color: #fff; +} diff --git a/packages/carrier-mobile-ionic/src/components/order-card/order-card.ts b/packages/carrier-mobile-ionic/src/components/order-card/order-card.ts new file mode 100644 index 0000000..8577ed7 --- /dev/null +++ b/packages/carrier-mobile-ionic/src/components/order-card/order-card.ts @@ -0,0 +1,23 @@ +import { Component, Input, OnInit } from '@angular/core'; +import Order from '@modules/server.common/entities/Order'; +import ICarrier from '@modules/server.common/interfaces/ICarrier'; + +@Component({ + selector: 'order-card', + templateUrl: './order-card.html', + styleUrls: ['./order-card.scss'] +}) +export class OrderCardComponent implements OnInit { + private static NOT_EXPANDED_MAX_PRODUCTS_AMOUNT = 3; + @Input() + carrier: ICarrier; + + @Input() + order: Order; + + ngOnInit() {} + + get maxProductsAmountToShow() { + return OrderCardComponent.NOT_EXPANDED_MAX_PRODUCTS_AMOUNT; + } +} diff --git a/packages/carrier-mobile-ionic/src/components/order-card/order/order.component.html b/packages/carrier-mobile-ionic/src/components/order-card/order/order.component.html new file mode 100644 index 0000000..37fb9b7 --- /dev/null +++ b/packages/carrier-mobile-ionic/src/components/order-card/order/order.component.html @@ -0,0 +1,85 @@ +
+
+ +
+
+ {{ 'DELIVERY_VIEW.TO' | translate }}: {{ customerBasicInfo }} +
+ +
{{ customerAddress }}
+
{{ customerNotes }}
+ +
+ {{ 'DELIVERY_VIEW.CANCELED' | translate }} +
+
+ {{ 'DELIVERY_VIEW.IS_PAID' | translate }} +
+
+ {{ 'DELIVERY_VIEW.CONFIRMED' | translate }} +
+
+
+
+ +
+ +
+ + +
+ ${{ totalPrice }} +
+ {{ 'DELIVERY_VIEW.FROM' | translate }}: {{ warehouseName }} +
{{ warehouseAddress }}
+
+
{{ createdAt | date: 'short' }}
+ {{ deliveryTime }} +
+
+ +
+ +
+
+ +
+
+ + +
diff --git a/packages/carrier-mobile-ionic/src/components/order-card/order/order.component.scss b/packages/carrier-mobile-ionic/src/components/order-card/order/order.component.scss new file mode 100644 index 0000000..ae55c7c --- /dev/null +++ b/packages/carrier-mobile-ionic/src/components/order-card/order/order.component.scss @@ -0,0 +1,81 @@ +.order-component { + .order-header { + font-size: 0.8em; + padding: 10px; + + display: flex; + align-items: center; + justify-content: space-around; + width: 100%; + + .logo { + // padding-left: 0%; + padding-right: 4%; + } + + .address { + flex: 2 0 auto; + font-size: 13px; + + .top { + color: #000000; + opacity: 0.87; + } + + .bottom { + display: inline-block; + color: #000000; + opacity: 0.54; + } + + .delivery-time { + color: #008f0e; + opacity: 0.73; + } + } + + .total-price { + font-size: 18px; + color: #000000; + opacity: 0.8; + } + } + + .hr { + border-bottom: 1px solid #ccc; + width: 100%; + } + + .order-body { + padding: 15px; + + .product-container { + padding-bottom: 15px; + + &:last-of-type { + padding-bottom: 0; + } + } + } + + .actions { + display: flex; + justify-content: center; + margin-bottom: 10px !important; + margin: auto; + font-size: 10px; + opacity: 0.8; + + > * { + color: #000000; + } + a ion-icon { + font-size: 1.4em; + margin-bottom: -15px !important; + } + .more, + .less { + margin-bottom: -7px; + } + } +} diff --git a/packages/carrier-mobile-ionic/src/components/order-card/order/order.component.ts b/packages/carrier-mobile-ionic/src/components/order-card/order/order.component.ts new file mode 100644 index 0000000..1f743ba --- /dev/null +++ b/packages/carrier-mobile-ionic/src/components/order-card/order/order.component.ts @@ -0,0 +1,167 @@ +import { + ChangeDetectionStrategy, + Component, + Input, + OnInit, +} from '@angular/core'; +import OrderStatus from '@modules/server.common/enums/OrderStatus'; +import Order from '@modules/server.common/entities/Order'; +import Warehouse from '@modules/server.common/entities/Warehouse'; +import * as _ from 'lodash'; +import { Store } from '../../../services/store.service'; +import { environment } from '../../../environments/environment'; +import Carrier from '@modules/server.common/entities/Carrier'; +import OrderProduct from '@modules/server.common/entities/OrderProduct'; +import { getCountryName } from '@modules/server.common/entities/GeoLocation'; +import { Platform } from '@ionic/angular'; + +@Component({ + selector: 'e-cu-order', + templateUrl: 'order.component.html', + styleUrls: ['./order.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class OrderComponent implements OnInit { + carrierAddress: string; + createOrderdAt: any; + deliveryOrderTime: any; + + customerDefaultLogo: string = environment.DEFAULT_CUSTOMER_LOGO; + orderProductsToShow: OrderProduct[] = []; + + @Input() + productsMaxAmountToShow: number; + carrier: Carrier; + + @Input() + order: Order; + + @Input() + showDetailsButton: boolean = false; + + constructor(private readonly store: Store, public platform: Platform) {} + + get carrierId(): string { + return this.store.carrierId; + } + + get customerLogo() { + return this.order.user['image'] || this.customerDefaultLogo; + } + + get id() { + return this.order.id; + } + + get warehouseLogo() { + return (this.order.warehouse as Warehouse).logo; + } + + get warehouseName() { + return (this.order.warehouse as Warehouse).name; + } + + get customerBasicInfo() { + const firstName = this.order.user.firstName; + const lastName = this.order.user.lastName; + + return _.isEmpty(firstName) || _.isEmpty(lastName) + ? this.order.user._id + : `${firstName} ${lastName}`; + } + + get customerAddress() { + const countryName = getCountryName( + this.order.user.geoLocation.countryId + ); + + return `${countryName} ${this.order.user.geoLocation.postcode}, ${this.order.user.geoLocation.city}`; + } + + get customerNotes() { + const customerNotes = + this.order.user.geoLocation.notes === null + ? '' + : this.order.user.geoLocation.notes; + + return `Notes: ${customerNotes}`; + } + + get warehouseAddress() { + const warehouse = this.order.warehouse as Warehouse; + const countryName = getCountryName(warehouse.geoLocation.countryId); + + return `${countryName} ${warehouse.geoLocation.postcode}, ${warehouse.geoLocation.city}`; + } + + get totalPrice() { + return _.chain(this.order.products) + .map((p) => p.count * p.price) + .reduce((p1, p2) => p1 + p2) + .value(); + } + + get createdAt() { + return this.order._createdAt; + } + + get deliveryTime() { + const createOrderAtDate = this.order._createdAt; + this.createOrderdAt = new Date(createOrderAtDate.toString()).getTime(); + const deliveryOrderDate = + this.order.deliveryTime !== null + ? this.order.deliveryTime + : this.createOrderdAt; + this.deliveryOrderTime = new Date(deliveryOrderDate).getTime(); + const time = this.deliveryOrderTime - this.createOrderdAt; + return this.order.deliveryTime !== undefined + ? this._millisToMinutes(time) + ' min' + : 'In Delivery'; + } + + get statusText() { + return this.order.getStatusText(this.store.language); + } + + get badgeClass() { + switch (this.order.status) { + case OrderStatus.CanceledWhileInDelivery: + case OrderStatus.CanceledWhileWarehousePreparation: + return 'badge-energized'; + case OrderStatus.CarrierIssue: + case OrderStatus.WarehouseIssue: + return 'badge-assertive'; + default: + return 'badge-balanced'; + } + } + + get showMoreIcon() { + return this.productsMaxAmountToShow < this.order.products.length; + } + + ngOnInit() { + this._sliceOrderProducts(); + } + + toggleOrderProducts() { + if (this.orderProductsToShow.length > this.productsMaxAmountToShow) { + this._sliceOrderProducts(); + } else { + this.orderProductsToShow = this.order.products; + } + } + + private _sliceOrderProducts() { + this.orderProductsToShow = this.order.products.slice( + 0, + this.productsMaxAmountToShow + ); + } + + private _millisToMinutes(ms) { + const minutes = Math.floor(ms / 60000); + const seconds = ((ms % 60000) / 1000).toFixed(0); + return minutes + ':' + (+seconds < 10 ? '0' : '') + seconds; + } +} diff --git a/packages/carrier-mobile-ionic/src/components/order-card/order/order.model.ts b/packages/carrier-mobile-ionic/src/components/order-card/order/order.model.ts new file mode 100644 index 0000000..7677258 --- /dev/null +++ b/packages/carrier-mobile-ionic/src/components/order-card/order/order.model.ts @@ -0,0 +1,33 @@ +import { gql } from 'apollo-angular'; +import { OrderProductFragment } from './product/product.model'; + +export const OrderFragment = gql` + fragment OrderFragment on Order { + id + deliveryTime + warehouseId + deliveryTime + createdAt + user { + id + geoLocation { + countryName + countryId + streetAddress + house + postcode + notes + house + city + } + } + warehouse { + id + logo + } + products { + ...OrderProductFragment + } + } + ${OrderProductFragment} +`; diff --git a/packages/carrier-mobile-ionic/src/components/order-card/order/order.module.ts b/packages/carrier-mobile-ionic/src/components/order-card/order/order.module.ts new file mode 100644 index 0000000..2700d7b --- /dev/null +++ b/packages/carrier-mobile-ionic/src/components/order-card/order/order.module.ts @@ -0,0 +1,22 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { TranslateModule } from '@ngx-translate/core'; +import { WarehouseLogoModule } from '../../warehouse-logo/warehouse-logo.module'; +import { OrderComponent } from './order.component'; +import { ProductModule } from './product/product.module'; +import { Store } from '../../../services/store.service'; +import { IonicModule } from '@ionic/angular'; + +@NgModule({ + imports: [ + IonicModule, + CommonModule, + WarehouseLogoModule, + ProductModule, + TranslateModule.forChild(), + ], + providers: [Store], + exports: [OrderComponent], + declarations: [OrderComponent], +}) +export class OrderModule {} diff --git a/packages/carrier-mobile-ionic/src/components/order-card/order/product/product.component.html b/packages/carrier-mobile-ionic/src/components/order-card/order/product/product.component.html new file mode 100644 index 0000000..5ea90fd --- /dev/null +++ b/packages/carrier-mobile-ionic/src/components/order-card/order/product/product.component.html @@ -0,0 +1,33 @@ +
+
+
+
+ +
+ +
+
+ {{ title }}    + {{ count }} +
+ +
{{ description }}
+ + +
+
+ +
+ ${{ price }} +
+
+
diff --git a/packages/carrier-mobile-ionic/src/components/order-card/order/product/product.component.scss b/packages/carrier-mobile-ionic/src/components/order-card/order/product/product.component.scss new file mode 100644 index 0000000..7e85990 --- /dev/null +++ b/packages/carrier-mobile-ionic/src/components/order-card/order/product/product.component.scss @@ -0,0 +1,112 @@ +.order-product { + .product-image { + padding-left: 1px; + padding-right: 10px; + text-align: center; + + img { + &.square { + width: 55px; + height: 55px; + } + &.horizontal { + width: 60px; + height: 40px; + } + &.vertical { + width: 40px; + height: 65px; + } + } + } + + .product-content { + flex: 2 0 auto; + + .title { + color: #000000; + + .text { + font-size: 16px; + max-width: 200px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + display: inline-block; + } + + .qty { + background: #c8c8c8; + padding: 2px 4px; + top: -8px; + position: relative; + } + } + + .description { + font-size: 14px; + color: #000000; + width: 200px; + opacity: 0.54; + } + + .details { + margin-top: 5px; + align-self: flex-end; + a { + color: #000000; + font-size: 13px; + } + } + } + + .products-price { + font-size: 14px; + color: #000000; + opacity: 0.8; + // padding-left: 3%; + // padding-right: 2%; + position: absolute; + top: 23%; + } + + .details { + margin-top: 5px; + align-self: flex-end; + a { + color: #000000; + font-size: 13px; + } + } + + .details-outer { + a { + color: #000000; + font-size: 13px; + + &.square { + margin: 66px; + } + &.horizontal { + margin: 71px; + } + &.vertical { + margin: 51px; + } + } + } + + .container { + position: relative; + padding: 0 !important; + + .test { + font-size: 0.8em; + display: flex; + align-items: center; + justify-content: space-around; + + width: 100%; + } + } +} diff --git a/packages/carrier-mobile-ionic/src/components/order-card/order/product/product.component.ts b/packages/carrier-mobile-ionic/src/components/order-card/order/product/product.component.ts new file mode 100644 index 0000000..ecaf14a --- /dev/null +++ b/packages/carrier-mobile-ionic/src/components/order-card/order/product/product.component.ts @@ -0,0 +1,110 @@ +import { + ChangeDetectionStrategy, + Component, + Input, + OnInit, +} from '@angular/core'; +import OrderProduct from '@modules/server.common/entities/OrderProduct'; +import { ProductLocalesService } from '@modules/client.common.angular2/locale/product-locales.service'; +import { Store } from '../../../../services/store.service'; +import { Platform } from '@ionic/angular'; + +@Component({ + selector: 'e-cu-order-product', + styleUrls: ['./product.component.scss'], + templateUrl: './product.component.html', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class ProductComponent implements OnInit { + private static MAX_DESCRIPTION_LENGTH: number = 53; + + @Input() + orderProduct: OrderProduct; + @Input() + showDetailsButton: boolean = false; + + constructor( + private readonly translateProductLocales: ProductLocalesService, + private readonly store: Store, + public platform: Platform + ) {} + + ngOnInit() {} + + get title() { + return this.translateProductLocales.getTranslate( + this.orderProduct.product.title + ); + } + + get description() { + let description = this.translateProductLocales.getTranslate( + this.orderProduct.product.description + ); + + return description.length < ProductComponent.MAX_DESCRIPTION_LENGTH + ? description + : description.substring( + 0, + ProductComponent.MAX_DESCRIPTION_LENGTH - 3 + ) + '...'; + } + + get image() { + return ( + this.orderProduct.product.images.find( + (product) => product.locale === this.store.language + ) || + this.orderProduct.product.images.find( + (product) => product.locale === 'en-US' + ) || + this.orderProduct.product.images[0] + ); + } + + get imageClass() { + switch (this.image.orientation) { + case 1: + return 'vertical'; + case 2: + return 'horizontal'; + default: + return 'square'; + } + } + + get count() { + return this.orderProduct.count; + } + + get price(): number { + return this.orderProduct.count * this.orderProduct.price; + } + + get showInsideDetailsButton() { + const description = this.translateProductLocales.getTranslate( + this.orderProduct.product.description + ); + const isTwoRowsDesc = + description.length > ProductComponent.MAX_DESCRIPTION_LENGTH / 2; + + return ( + this.showDetailsButton && + !isTwoRowsDesc && + this.image.orientation === 1 + ); + } + + get showOutsideDetailsButton() { + const description = this.translateProductLocales.getTranslate( + this.orderProduct.product.description + ); + const isTwoRowsDesc = + description.length > ProductComponent.MAX_DESCRIPTION_LENGTH / 2; + + return ( + this.showDetailsButton && + (this.image.orientation !== 1 || isTwoRowsDesc) + ); + } +} diff --git a/packages/carrier-mobile-ionic/src/components/order-card/order/product/product.model.ts b/packages/carrier-mobile-ionic/src/components/order-card/order/product/product.model.ts new file mode 100644 index 0000000..cb4f5a4 --- /dev/null +++ b/packages/carrier-mobile-ionic/src/components/order-card/order/product/product.model.ts @@ -0,0 +1,23 @@ +import { gql } from 'apollo-angular'; + +export const OrderProductFragment = gql` + fragment OrderProductFragment on OrderProduct { + count + price + product { + images { + locale + orientation + url + } + title { + locale + value + } + description { + locale + value + } + } + } +`; diff --git a/packages/carrier-mobile-ionic/src/components/order-card/order/product/product.module.ts b/packages/carrier-mobile-ionic/src/components/order-card/order/product/product.module.ts new file mode 100644 index 0000000..28699a4 --- /dev/null +++ b/packages/carrier-mobile-ionic/src/components/order-card/order/product/product.module.ts @@ -0,0 +1,14 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { TranslateModule } from '@ngx-translate/core'; +import { ProductComponent } from './product.component'; +import { Store } from '../../../../services/store.service'; +import { IonicModule } from '@ionic/angular'; + +@NgModule({ + imports: [IonicModule, CommonModule, TranslateModule.forChild()], + providers: [Store], + exports: [ProductComponent], + declarations: [ProductComponent], +}) +export class ProductModule {} diff --git a/packages/carrier-mobile-ionic/src/components/warehouse-logo/warehouse-logo.component.ts b/packages/carrier-mobile-ionic/src/components/warehouse-logo/warehouse-logo.component.ts new file mode 100644 index 0000000..064b3b9 --- /dev/null +++ b/packages/carrier-mobile-ionic/src/components/warehouse-logo/warehouse-logo.component.ts @@ -0,0 +1,61 @@ +import { Component, HostBinding, Input, OnInit } from '@angular/core'; + +@Component({ + selector: 'e-cu-warehouse-logo', + styles: [ + ` + :host { + width: 56px; + height: 56px; + border-radius: 50%; + background-color: #fff; + display: flex; + justify-content: center; + align-items: center; + } + + :host.bordered { + border: solid #95989a 1px; + } + + img { + max-width: 69%; + max-height: 69%; + user-drag: none; + user-select: none; + -moz-user-select: none; + -webkit-user-drag: none; + -webkit-user-select: none; + -ms-user-select: none; + width: auto; + height: auto; + } + + img.light { + opacity: 0.8; + } + `, + ], + template: ` + + `, +}) +export class WarehouseLogoComponent implements OnInit { + @Input() + public logo: string; + + @Input() + @HostBinding('class.bordered') + public border: boolean = false; + + @Input() + public light: boolean = false; + + constructor() { + return; + } + + ngOnInit() { + return; + } +} diff --git a/packages/carrier-mobile-ionic/src/components/warehouse-logo/warehouse-logo.module.ts b/packages/carrier-mobile-ionic/src/components/warehouse-logo/warehouse-logo.module.ts new file mode 100644 index 0000000..c6c76aa --- /dev/null +++ b/packages/carrier-mobile-ionic/src/components/warehouse-logo/warehouse-logo.module.ts @@ -0,0 +1,11 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { WarehouseLogoComponent } from './warehouse-logo.component'; + +@NgModule({ + imports: [CommonModule], + exports: [WarehouseLogoComponent], + declarations: [WarehouseLogoComponent], + providers: [], +}) +export class WarehouseLogoModule {} diff --git a/packages/carrier-mobile-ionic/src/environments/model.ts b/packages/carrier-mobile-ionic/src/environments/model.ts new file mode 100644 index 0000000..adb8def --- /dev/null +++ b/packages/carrier-mobile-ionic/src/environments/model.ts @@ -0,0 +1,37 @@ +export interface Environment { + production: boolean; + + APP_VERSION: string; + + DEFAULT_CUSTOMER_LOGO: string; + LOGIN_LOGO: string; + NO_INTERNET_LOGO: string; + + COMPANY_NAME: string; + APP_NAME: string; + + GOOGLE_MAPS_API_KEY: string; + + GOOGLE_ANALYTICS_API_KEY: string; + FAKE_UUID: string; + + // Not secret MixPanel Token + MIXPANEL_API_KEY: string; + + DEFAULT_LATITUDE: number; + DEFAULT_LONGITUDE: number; + + DEFAULT_LOGIN_USERNAME: string; + DEFAULT_LOGIN_PASSWORD: string; + + GQL_ENDPOINT: string; + GQL_SUBSCRIPTIONS_ENDPOINT: string; + SERVICES_ENDPOINT: string; + HTTPS_SERVICES_ENDPOINT: string; + + DEFAULT_LANGUAGE: string; + + // For maintenance micro service + SETTINGS_APP_TYPE: string; + SETTINGS_MAINTENANCE_API_URL: string; +} diff --git a/packages/carrier-mobile-ionic/src/global.scss b/packages/carrier-mobile-ionic/src/global.scss new file mode 100644 index 0000000..99afc0d --- /dev/null +++ b/packages/carrier-mobile-ionic/src/global.scss @@ -0,0 +1,153 @@ +// App Global Sass +// ---------------------------------------------------------------------------- +// Put style rules here that you want to apply globally. These styles are for +// the entire app and not just one component. Additionally, this file can be +// also used as an entry point to import other Sass files to be included in the +// output CSS. + +/* Core CSS required for Ionic components to work properly */ +@import '~@ionic/angular/css/core.css'; + +/* Basic CSS for apps built with Ionic */ +@import '~@ionic/angular/css/normalize.css'; +@import '~@ionic/angular/css/structure.css'; +@import '~@ionic/angular/css/typography.css'; + +/* Optional CSS utils that can be commented out */ +@import '~@ionic/angular/css/padding.css'; +@import '~@ionic/angular/css/float-elements.css'; +@import '~@ionic/angular/css/text-alignment.css'; +@import '~@ionic/angular/css/text-transformation.css'; +@import '~@ionic/angular/css/flex-utils.css'; + +@import 'theme/fonts/open-sans-hebrew'; +@import 'theme/fonts/google-font-Roboto'; +@import 'theme/fonts/google-font-Jura'; + +$fa-font-path: './assets/fonts' !default; + +@import '~@ever-platform/common-angular/src/scss/everbie.common'; + +@import 'theme/styles'; + +* { + font-family: 'Roboto', sans-serif; +} + +html, +body { + overflow: visible !important; +} + +.rtl { + float: left !important; + left: 0 !important; +} + +.ltr { + float: right !important; + right: 0 !important; +} + +.ion-page { + background-color: $brand-darken; +} + +// This is for remove bad popup background +ion-popover.popover.popover-ios.hydrated { + width: 0px; +} + +ion-popover#ion-overlay-1 { + width: 0px; +} + +.popover-content { + width: 250px !important; +} + +ion-backdrop { + display: none; +} + +.order-info-modal { + display: block; + + .modal-wrapper { + background: rgba(255, 255, 255, 0); + } + + .ion-page { + max-width: 250px; + margin: 0 auto; + display: flex; + position: absolute; + flex-direction: column; + justify-content: center; + contain: layout size style; + overflow: hidden; + background: none; + } +} + +.map-info-modal { + display: block; +} + +ion-menu-button { + color: white !important; +} + +.brand-dark { + background: #1f212a; +} + +.brand-light { + background: #353748; +} + +.blur-filter { + -webkit-filter: blur(2px); + -moz-filter: blur(2px); + -o-filter: blur(2px); + -ms-filter: blur(2px); + filter: blur(2px); +} + +.blur-lighter { + z-index: 1000; + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + background: rgba(255, 255, 255, 0.15); +} + +.max-height { + height: 100% !important; +} + +.pl-5px { + padding-left: 5px !important; +} + +.brand-dark.products-view { + ion-spinner, + .infinite-loading-text { + color: white !important; + } +} + +ion-header { + ion-menu-toggle { + display: contents; + ion-icon { + font-size: 2rem !important; + } + } +} + +ion-header.bar { + position: relative; +} diff --git a/packages/carrier-mobile-ionic/src/graphql/apollo.config.ts b/packages/carrier-mobile-ionic/src/graphql/apollo.config.ts new file mode 100644 index 0000000..d990cb7 --- /dev/null +++ b/packages/carrier-mobile-ionic/src/graphql/apollo.config.ts @@ -0,0 +1,49 @@ +import { NgModule } from '@angular/core'; +import { HttpClientModule } from '@angular/common/http'; +import { Apollo } from 'apollo-angular'; +import { HttpLink, HttpLinkHandler } from 'apollo-angular/http'; +import { ApolloLink, InMemoryCache } from '@apollo/client/core'; +import { setContext } from '@apollo/client/link/context'; +import { environment } from '../environments/environment'; + +@NgModule({ + exports: [ + HttpClientModule + ], +}) +export class GraphQLModule { + constructor( + private readonly apollo: Apollo, + private readonly httpLink: HttpLink + ) { + const uri = environment.GQL_ENDPOINT; + const http: HttpLinkHandler = httpLink.create({ uri }); + + const authLink: ApolloLink = setContext((_, { headers }) => { + // get the authentication token from local storage if it exists + const token = localStorage.getItem('token'); + // return the headers to the context so httpLink can read them + return { + headers: { + ...headers, + authorization: token ? `Bearer ${token}` : '', + }, + }; + }); + + apollo.create({ + link: authLink.concat(http), + cache: new InMemoryCache(), + defaultOptions: { + watchQuery: { + fetchPolicy: 'network-only', + errorPolicy: 'ignore', + }, + query: { + fetchPolicy: 'network-only', + errorPolicy: 'all', + }, + }, + }); + } +} diff --git a/packages/carrier-mobile-ionic/src/index.html b/packages/carrier-mobile-ionic/src/index.html new file mode 100644 index 0000000..d3a2c90 --- /dev/null +++ b/packages/carrier-mobile-ionic/src/index.html @@ -0,0 +1,28 @@ + + + + + Ever® Carrier App + + + + + + + + + + + + + + + + + + + + diff --git a/packages/carrier-mobile-ionic/src/main.ts b/packages/carrier-mobile-ionic/src/main.ts new file mode 100644 index 0000000..05951c2 --- /dev/null +++ b/packages/carrier-mobile-ionic/src/main.ts @@ -0,0 +1,15 @@ +import { enableProdMode } from '@angular/core'; +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; +import { AppModule } from 'app/app.module'; +import { environment } from 'environments/environment'; + +if (environment.production) { + enableProdMode(); +} + +platformBrowserDynamic() + .bootstrapModule(AppModule) + .catch((err) => { + // TODO: we probably should also send error to Sentry here + console.log(err); + }); diff --git a/packages/carrier-mobile-ionic/src/manifest.json b/packages/carrier-mobile-ionic/src/manifest.json new file mode 100644 index 0000000..dd9e6d3 --- /dev/null +++ b/packages/carrier-mobile-ionic/src/manifest.json @@ -0,0 +1,47 @@ +{ + "name": "ever-carrier", + "short_name": "ever-carrier", + "start_url": "index.html", + "display": "standalone", + "gcm_sender_id": "103953800507", + "icons": [ + { + "src": "assets/icon/favicon72x72.png", + "sizes": "72x72", + "type": "image/png" + }, + { + "src": "assets/icon/favicon96x96.png", + "sizes": "96x96", + "type": "image/png" + }, + { + "src": "assets/icon/favicon128x128.png", + "sizes": "128x128", + "type": "image/png" + } + // TODO add next images in assets/icon + // { + // "src": "assets/icon/favicon144x144.png", + // "sizes": "144x144", + // "type": "image/png" + // }, + // { + // "src": "assets/icon/favicon152x152.png", + // "sizes": "152x152", + // "type": "image/png" + // }, + // { + // "src": "assets/icon/favicon192x192.png", + // "sizes": "192x192", + // "type": "image/png" + // }, + // { + // "src": "assets/icon/favicon512x512.png", + // "sizes": "512x512", + // "type": "image/png" + // } + ], + "background_color": "#4e8ef7", + "theme_color": "#4e8ef7" +} diff --git a/packages/carrier-mobile-ionic/src/pages/auth.guard.ts b/packages/carrier-mobile-ionic/src/pages/auth.guard.ts new file mode 100644 index 0000000..8fa5ade --- /dev/null +++ b/packages/carrier-mobile-ionic/src/pages/auth.guard.ts @@ -0,0 +1,20 @@ +import { Injectable } from '@angular/core'; +import { CanLoad, Route, Router } from '@angular/router'; +import { Store } from 'services/store.service'; + +@Injectable() +export class AuthGuard implements CanLoad { + constructor( + private readonly store: Store, + private readonly router: Router + ) {} + + async canLoad(route: Route) { + const isLogged = await this.store.isLogged(); + if (!isLogged) { + this.router.navigateByUrl('/login'); + return false; + } + return true; + } +} diff --git a/packages/carrier-mobile-ionic/src/pages/deliveries/deliveries-history.model.ts b/packages/carrier-mobile-ionic/src/pages/deliveries/deliveries-history.model.ts new file mode 100644 index 0000000..fcfa777 --- /dev/null +++ b/packages/carrier-mobile-ionic/src/pages/deliveries/deliveries-history.model.ts @@ -0,0 +1,11 @@ +import { gql } from 'apollo-angular'; +import { OrderFragment } from '../../components/order-card/order/order.model'; + +export const OrdersHistoryQuery = gql` + query OrdersHistoryPageQuery($userId: String!) { + getOrders(userId: $userId) { + ...OrderFragment + } + } + ${OrderFragment} +`; diff --git a/packages/carrier-mobile-ionic/src/pages/deliveries/deliveries.html b/packages/carrier-mobile-ionic/src/pages/deliveries/deliveries.html new file mode 100644 index 0000000..ca81d8b --- /dev/null +++ b/packages/carrier-mobile-ionic/src/pages/deliveries/deliveries.html @@ -0,0 +1,27 @@ + +
+ +
+ + + +
+
+
+ + {{ 'DELIVERY_VIEW.DELIVERIES_HISTORY' | translate }} + +
+ + +
+
+
+ +
+
+
+
diff --git a/packages/carrier-mobile-ionic/src/pages/deliveries/deliveries.module.ts b/packages/carrier-mobile-ionic/src/pages/deliveries/deliveries.module.ts new file mode 100644 index 0000000..23586b6 --- /dev/null +++ b/packages/carrier-mobile-ionic/src/pages/deliveries/deliveries.module.ts @@ -0,0 +1,28 @@ +import { NgModule } from '@angular/core'; +import { TranslateModule } from '@ngx-translate/core'; +import { DeliveriesPage } from './deliveries'; +import { CommonModule } from '@angular/common'; +import { FormsModule } from '@angular/forms'; +import { OrderCardModule } from '../../components/order-card/order-card.module'; +import { IonicModule } from '@ionic/angular'; +import { Routes, RouterModule } from '@angular/router'; + +const routes: Routes = [ + { + path: '', + component: DeliveriesPage, + }, +]; + +@NgModule({ + declarations: [DeliveriesPage], + imports: [ + CommonModule, + FormsModule, + IonicModule, + OrderCardModule, + RouterModule.forChild(routes), + TranslateModule.forChild(), + ], +}) +export class DeliveriesPageModule {} diff --git a/packages/carrier-mobile-ionic/src/pages/deliveries/deliveries.scss b/packages/carrier-mobile-ionic/src/pages/deliveries/deliveries.scss new file mode 100644 index 0000000..ba59f77 --- /dev/null +++ b/packages/carrier-mobile-ionic/src/pages/deliveries/deliveries.scss @@ -0,0 +1,33 @@ +// page-deliveries { +ion-title { + > * { + color: white !important; + } +} +.toolbar-title.toolbar-title-md { + text-align: center; + color: white; + font-size: 100%; +} + +.toolbar-background { + background-color: #2a2c39; +} + +ion-content { + background-color: #eeeeee !important; +} + +ion-content { + background-color: #eeeeee !important; +} + +// .orders { +// padding: 5px 10px; + +// .order-container { +// padding: 5px 0; +// } +// } + +// } diff --git a/packages/carrier-mobile-ionic/src/pages/deliveries/deliveries.ts b/packages/carrier-mobile-ionic/src/pages/deliveries/deliveries.ts new file mode 100644 index 0000000..2aba1b1 --- /dev/null +++ b/packages/carrier-mobile-ionic/src/pages/deliveries/deliveries.ts @@ -0,0 +1,53 @@ +import { Component, OnDestroy } from '@angular/core'; +import Order from '@modules/server.common/entities/Order'; +import { Subject } from 'rxjs'; +import { CarriersOrdersService } from '../../services/carriers-orders.service'; +import { Store } from '../../services/store.service'; +import { Platform } from '@ionic/angular'; + +@Component({ + selector: 'page-deliveries', + templateUrl: './deliveries.html', + styleUrls: ['deliveries.scss'], +}) +export class DeliveriesPage implements OnDestroy { + currentCompletedOrders: Order[]; + + private _ngDestroy$ = new Subject(); + + constructor( + private readonly _carriersOrdersService: CarriersOrdersService, + private readonly store: Store, + public platform: Platform + ) { + console.warn('DeliveriesPage loaded'); + + this._getAllCompletedOrders(); + } + + private get _carrierId() { + return this.store.carrierId; + } + + ionViewCanEnter() { + return this.store.carrierId !== null && !this.store.showInformationPage; + } + + private async _getAllCompletedOrders() { + this.currentCompletedOrders = await this._carriersOrdersService.getCarrierOrders( + this._carrierId, + { + populateWarehouse: true, + completion: 'all', + } + ); + + console.warn('Orders - ' + this.currentCompletedOrders.length); + } + + ngOnDestroy() { + console.warn('DeliveriesPage leave'); + this._ngDestroy$.next(); + this._ngDestroy$.complete(); + } +} diff --git a/packages/carrier-mobile-ionic/src/pages/information/about/about.html b/packages/carrier-mobile-ionic/src/pages/information/about/about.html new file mode 100644 index 0000000..70289e7 --- /dev/null +++ b/packages/carrier-mobile-ionic/src/pages/information/about/about.html @@ -0,0 +1,30 @@ + +
+ +
+ + + +
+
+
+ + {{ 'ABOUT_VIEW.TITLE' | translate }} + +
+ + +
+ +
+
+ App Version: {{ appVersion }} +
+
+
diff --git a/packages/carrier-mobile-ionic/src/pages/information/about/about.module.ts b/packages/carrier-mobile-ionic/src/pages/information/about/about.module.ts new file mode 100644 index 0000000..2d880d5 --- /dev/null +++ b/packages/carrier-mobile-ionic/src/pages/information/about/about.module.ts @@ -0,0 +1,30 @@ +import { NgModule } from '@angular/core'; +import { AboutPage } from './about'; +import { PipesModule } from '@modules/client.common.angular2/pipes/pipes.module'; +import { TranslateModule } from '@ngx-translate/core'; +import { ComponentsModule } from 'components/components.module'; +import { IonicModule } from '@ionic/angular'; +import { CommonModule } from '@angular/common'; +import { FormsModule } from '@angular/forms'; +import { RouterModule, Routes } from '@angular/router'; + +const routes: Routes = [ + { + path: '', + component: AboutPage, + }, +]; + +@NgModule({ + declarations: [AboutPage], + imports: [ + PipesModule, + ComponentsModule, + IonicModule, + CommonModule, + FormsModule, + RouterModule.forChild(routes), + TranslateModule.forChild(), + ], +}) +export class AboutPageModule {} diff --git a/packages/carrier-mobile-ionic/src/pages/information/about/about.scss b/packages/carrier-mobile-ionic/src/pages/information/about/about.scss new file mode 100644 index 0000000..15c6296 --- /dev/null +++ b/packages/carrier-mobile-ionic/src/pages/information/about/about.scss @@ -0,0 +1,3 @@ +.about-content { + padding: 0; +} diff --git a/packages/carrier-mobile-ionic/src/pages/information/about/about.ts b/packages/carrier-mobile-ionic/src/pages/information/about/about.ts new file mode 100644 index 0000000..70be1e4 --- /dev/null +++ b/packages/carrier-mobile-ionic/src/pages/information/about/about.ts @@ -0,0 +1,36 @@ +import { Component, OnInit, OnDestroy } from '@angular/core'; +import { UserRouter } from '@modules/client.common.angular2/routers/user-router.service'; +import { Subscription } from 'rxjs'; +import { environment } from 'environments/environment'; + +@Component({ + selector: 'page-about', + templateUrl: 'about.html', + styleUrls: ['about.scss'], +}) +export class AboutPage implements OnInit, OnDestroy { + public useAboutHtml: string = '

Loading...

'; + public selectedLanguage: string; + private sub: Subscription; + public deviceId: string; + public userId: string; + public appVersion: string; + + constructor(private userRouter: UserRouter) { + this.selectedLanguage = localStorage.getItem('_language') || 'en-US'; + this.deviceId = localStorage.getItem('_deviceId'); + this.userId = localStorage.getItem('_userId'); + this.appVersion = environment.APP_VERSION; + } + ngOnInit() { + this.sub = this.userRouter + .getAboutUs(this.userId, this.deviceId, this.selectedLanguage) + .subscribe((html) => { + this.useAboutHtml = html; + }); + } + + ngOnDestroy() { + this.sub.unsubscribe(); + } +} diff --git a/packages/carrier-mobile-ionic/src/pages/information/information.module.guard.ts b/packages/carrier-mobile-ionic/src/pages/information/information.module.guard.ts new file mode 100644 index 0000000..c7c1021 --- /dev/null +++ b/packages/carrier-mobile-ionic/src/pages/information/information.module.guard.ts @@ -0,0 +1,20 @@ +import { Injectable } from '@angular/core'; +import { CanLoad, Route, Router } from '@angular/router'; +import { Store } from '../../services/store.service'; + +@Injectable() +export class InformationModuleGuard implements CanLoad { + constructor( + private readonly store: Store, + private readonly router: Router + ) {} + + canLoad(route: Route) { + if (!this.store.deviceId) { + this.router.navigate(['login']); + return false; + } + + return true; + } +} diff --git a/packages/carrier-mobile-ionic/src/pages/information/information.module.ts b/packages/carrier-mobile-ionic/src/pages/information/information.module.ts new file mode 100644 index 0000000..b9394d4 --- /dev/null +++ b/packages/carrier-mobile-ionic/src/pages/information/information.module.ts @@ -0,0 +1,23 @@ +import { NgModule } from '@angular/core'; +import { RouterModule, Routes } from '@angular/router'; + +const routes: Routes = [ + { + path: 'about', + loadChildren: () => + import('./about/about.module').then((m) => m.AboutPageModule), + }, + { + path: 'terms-of-use', + loadChildren: () => + import('./terms-of-use/terms-of-use.module').then( + (m) => m.TermsOfUsePageModule + ), + }, +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule], +}) +export class InformationModule {} diff --git a/packages/carrier-mobile-ionic/src/pages/information/terms-of-use/terms-of-use.html b/packages/carrier-mobile-ionic/src/pages/information/terms-of-use/terms-of-use.html new file mode 100644 index 0000000..f5b4717 --- /dev/null +++ b/packages/carrier-mobile-ionic/src/pages/information/terms-of-use/terms-of-use.html @@ -0,0 +1,23 @@ + +
+ +
+ + + +
+
+
+ + {{ 'TERMS_OF_USE_VIEW.TITLE' | translate }} + +
+ + +
+
+
+
diff --git a/packages/carrier-mobile-ionic/src/pages/information/terms-of-use/terms-of-use.module.ts b/packages/carrier-mobile-ionic/src/pages/information/terms-of-use/terms-of-use.module.ts new file mode 100644 index 0000000..3d23088 --- /dev/null +++ b/packages/carrier-mobile-ionic/src/pages/information/terms-of-use/terms-of-use.module.ts @@ -0,0 +1,26 @@ +import { NgModule } from '@angular/core'; +import { TermsOfUsePage } from './terms-of-use'; +import { PipesModule } from '@modules/client.common.angular2/pipes/pipes.module'; +import { TranslateModule } from '@ngx-translate/core'; +import { RouterModule, Routes } from '@angular/router'; +import { CommonModule } from '@angular/common'; +import { IonicModule } from '@ionic/angular'; + +const routes: Routes = [ + { + path: '', + component: TermsOfUsePage, + }, +]; + +@NgModule({ + declarations: [TermsOfUsePage], + imports: [ + IonicModule, + CommonModule, + RouterModule.forChild(routes), + TranslateModule.forChild(), + PipesModule, + ], +}) +export class TermsOfUsePageModule {} diff --git a/packages/carrier-mobile-ionic/src/pages/information/terms-of-use/terms-of-use.scss b/packages/carrier-mobile-ionic/src/pages/information/terms-of-use/terms-of-use.scss new file mode 100644 index 0000000..2766859 --- /dev/null +++ b/packages/carrier-mobile-ionic/src/pages/information/terms-of-use/terms-of-use.scss @@ -0,0 +1,3 @@ +.terms-of-use-content { + padding: 0; +} diff --git a/packages/carrier-mobile-ionic/src/pages/information/terms-of-use/terms-of-use.ts b/packages/carrier-mobile-ionic/src/pages/information/terms-of-use/terms-of-use.ts new file mode 100644 index 0000000..3408ed6 --- /dev/null +++ b/packages/carrier-mobile-ionic/src/pages/information/terms-of-use/terms-of-use.ts @@ -0,0 +1,34 @@ +import { Component, OnInit, OnDestroy } from '@angular/core'; + +import { UserRouter } from '@modules/client.common.angular2/routers/user-router.service'; +import { Subscription } from 'rxjs'; + +@Component({ + selector: 'page-terms-of-use', + templateUrl: 'terms-of-use.html', + styleUrls: ['terms-of-use.scss'], +}) +export class TermsOfUsePage implements OnInit, OnDestroy { + public useTermsHtml: string = '

Loading...

'; + public selectedLanguage: string; + private sub: Subscription; + public deviceId: string; + public userId: string; + + constructor(private userRouter: UserRouter) { + this.selectedLanguage = localStorage.getItem('_language') || 'en-US'; + this.deviceId = localStorage.getItem('_deviceId'); + this.userId = localStorage.getItem('_userId'); + } + + ngOnInit() { + this.sub = this.userRouter + .getTermsOfUse(this.userId, this.deviceId, this.selectedLanguage) + .subscribe((html) => { + this.useTermsHtml = html; + }); + } + ngOnDestroy() { + this.sub.unsubscribe(); + } +} diff --git a/packages/carrier-mobile-ionic/src/pages/language/language.html b/packages/carrier-mobile-ionic/src/pages/language/language.html new file mode 100644 index 0000000..879531f --- /dev/null +++ b/packages/carrier-mobile-ionic/src/pages/language/language.html @@ -0,0 +1,48 @@ + +
+ +
+ + + +
+
+
+ + {{ 'LANGUAGE_VIEW.TITLE' | translate }} + +
+ + +
+ + + {{ 'LANGUAGE_VIEW.ENGLISH' | translate }} + + + + + {{ 'LANGUAGE_VIEW.BULGARIAN' | translate }} + + + + + {{ 'LANGUAGE_VIEW.HEBREW' | translate }} + + + + + {{ 'LANGUAGE_VIEW.RUSSIAN' | translate }} + + + + {{ 'LANGUAGE_VIEW.SPANISH' | translate }} + + + + {{ 'LANGUAGE_VIEW.FRENCH' | translate }} + + + +
+
diff --git a/packages/carrier-mobile-ionic/src/pages/language/language.module.ts b/packages/carrier-mobile-ionic/src/pages/language/language.module.ts new file mode 100644 index 0000000..d0f1c8a --- /dev/null +++ b/packages/carrier-mobile-ionic/src/pages/language/language.module.ts @@ -0,0 +1,26 @@ +import { NgModule } from '@angular/core'; +import { LanguagePage } from './language'; +import { TranslateModule } from '@ngx-translate/core'; +import { IonicModule } from '@ionic/angular'; +import { CommonModule } from '@angular/common'; +import { RouterModule, Routes } from '@angular/router'; +import { FormsModule } from '@angular/forms'; + +const routes: Routes = [ + { + path: '', + component: LanguagePage, + }, +]; + +@NgModule({ + declarations: [LanguagePage], + imports: [ + IonicModule, + CommonModule, + FormsModule, + RouterModule.forChild(routes), + TranslateModule.forChild(), + ], +}) +export class LanguagePageModule {} diff --git a/packages/carrier-mobile-ionic/src/pages/language/language.scss b/packages/carrier-mobile-ionic/src/pages/language/language.scss new file mode 100644 index 0000000..2d1bbe1 --- /dev/null +++ b/packages/carrier-mobile-ionic/src/pages/language/language.scss @@ -0,0 +1,4 @@ +.language-content { + .content { + } +} diff --git a/packages/carrier-mobile-ionic/src/pages/language/language.ts b/packages/carrier-mobile-ionic/src/pages/language/language.ts new file mode 100644 index 0000000..6b9dcba --- /dev/null +++ b/packages/carrier-mobile-ionic/src/pages/language/language.ts @@ -0,0 +1,47 @@ +import { Component, Inject } from '@angular/core'; +import { TranslateService } from '@ngx-translate/core'; +import { DeviceRouter } from '@modules/client.common.angular2/routers/device-router.service'; +import { DOCUMENT } from '@angular/common'; + +@Component({ + selector: 'page-language', + templateUrl: 'language.html', + styleUrls: ['language.scss'], +}) +export class LanguagePage { + language: any = localStorage.getItem('_language'); + dir: 'ltr' | 'rtl'; + + constructor( + private _langTranslator: TranslateService, + private _deviceRouter: DeviceRouter, + @Inject(DOCUMENT) private document: Document + ) { + console.warn('LanguagePage loaded'); + } + + get deviceId() { + return localStorage.getItem('_deviceId'); + } + + languageChange() { + localStorage.setItem('_language', this.language); + + // Example: "en-US".substr(0, 2) = "en" + const langAbbreviation = this.language.substr(0, 2); + + this._langTranslator.use(langAbbreviation); + + if (this.deviceId) { + this._deviceRouter.updateLanguage(this.deviceId, this.language); + } + + if (this.language === 'he-IL') { + this.dir = 'rtl'; + } else { + this.dir = 'ltr'; + } + this.document.documentElement.dir = this.dir; + this.document.documentElement.lang = langAbbreviation; + } +} diff --git a/packages/carrier-mobile-ionic/src/pages/login/login.html b/packages/carrier-mobile-ionic/src/pages/login/login.html new file mode 100644 index 0000000..289c9be --- /dev/null +++ b/packages/carrier-mobile-ionic/src/pages/login/login.html @@ -0,0 +1,52 @@ + diff --git a/packages/carrier-mobile-ionic/src/pages/login/login.module.guard.ts b/packages/carrier-mobile-ionic/src/pages/login/login.module.guard.ts new file mode 100644 index 0000000..972f711 --- /dev/null +++ b/packages/carrier-mobile-ionic/src/pages/login/login.module.guard.ts @@ -0,0 +1,20 @@ +import { Injectable } from '@angular/core'; +import { CanLoad, Route, Router } from '@angular/router'; +import { Store } from '../../services/store.service'; + +@Injectable() +export class LoginModuleGuard implements CanLoad { + constructor( + private readonly store: Store, + private readonly router: Router + ) {} + + async canLoad(route: Route) { + const isLogged = await this.store.isLogged(); + if (isLogged) { + this.router.navigateByUrl('main'); + return false; + } + return true; + } +} diff --git a/packages/carrier-mobile-ionic/src/pages/login/login.module.ts b/packages/carrier-mobile-ionic/src/pages/login/login.module.ts new file mode 100644 index 0000000..b6206b6 --- /dev/null +++ b/packages/carrier-mobile-ionic/src/pages/login/login.module.ts @@ -0,0 +1,29 @@ +import { NgModule } from '@angular/core'; +import { LoginPage } from './login'; +import { TranslateModule } from '@ngx-translate/core'; +import { AuthService } from '../../services/auth.service'; +import { Store } from '../../services/store.service'; +import { Routes, RouterModule } from '@angular/router'; +import { IonicModule } from '@ionic/angular'; +import { FormsModule } from '@angular/forms'; +import { CommonModule } from '@angular/common'; + +const routes: Routes = [ + { + path: '', + component: LoginPage, + }, +]; + +@NgModule({ + declarations: [LoginPage], + imports: [ + IonicModule, + FormsModule, + CommonModule, + TranslateModule.forChild(), + RouterModule.forChild(routes), + ], + providers: [AuthService, Store], +}) +export class LoginPageModule {} diff --git a/packages/carrier-mobile-ionic/src/pages/login/login.scss b/packages/carrier-mobile-ionic/src/pages/login/login.scss new file mode 100644 index 0000000..74250f6 --- /dev/null +++ b/packages/carrier-mobile-ionic/src/pages/login/login.scss @@ -0,0 +1,62 @@ +@import '~@ever-platform/common-angular/src/scss/everbie.common'; + +.login-view { + background-color: $brand; + + .login-view-content { + background-color: $brand; + text-align: center; + max-width: 500px; + width: 80%; + @include center(absolute); + + .logo { + color: #fff; + + img { + width: 80%; + + @media (max-height: 550px) { + width: 60%; + } + } + } + + .login-form { + margin: auto; + + .login-input { + margin-top: 20px; + /* text-align: center; + border: solid white 4px; */ + width: 100%; + padding: 12px !important; + font-size: 24px; + border: 0; + border-bottom: solid 3px #fff; + background: none; + display: inline; + height: auto; + color: #fff; + line-height: 1.42857; + outline: none; + /* prevents textbox highlight in chrome */ + } + + .login-button { + margin-top: 50px; + width: 100%; + color: white; + background: darken($brand, 5%); + padding: 16px; + font-size: 24px; + border: solid darken($brand, 10%) 1px; + border-radius: 10px; + + &:active { + background-color: darken($brand, 3%); + } + } + } + } +} diff --git a/packages/carrier-mobile-ionic/src/pages/login/login.ts b/packages/carrier-mobile-ionic/src/pages/login/login.ts new file mode 100644 index 0000000..69a8c49 --- /dev/null +++ b/packages/carrier-mobile-ionic/src/pages/login/login.ts @@ -0,0 +1,47 @@ +import { Component } from '@angular/core'; +import { first } from 'rxjs/operators'; +import { AuthService } from '../../services/auth.service'; +import { Store } from '../../services/store.service'; +import { environment } from '../../environments/environment'; +import { Router } from '@angular/router'; + +@Component({ + selector: 'page-login', + templateUrl: 'login.html', + styleUrls: ['login.scss'], +}) +export class LoginPage { + user; + loginLogo: string; + + constructor( + private authService: AuthService, + private store: Store, + private router: Router + ) { + this.user = { + username: environment.DEFAULT_LOGIN_USERNAME, + password: environment.DEFAULT_LOGIN_PASSWORD, + }; + this.loginLogo = environment.LOGIN_LOGO; + } + + async login() { + const res = await this.authService + .login(this.user.username, this.user.password) + .pipe(first()) + .toPromise(); + + if (!res || !res.carrier) { + alert('Carrier not exists!'); + return; + } + + console.log(`Carrier logged in with id ${res.carrier.id}`); + + this.store.carrierId = res.carrier.id; + this.store.token = res.token; + + this.router.navigateByUrl('main', { skipLocationChange: false }); + } +} diff --git a/packages/carrier-mobile-ionic/src/pages/main/common/map/map.component.html b/packages/carrier-mobile-ionic/src/pages/main/common/map/map.component.html new file mode 100644 index 0000000..6bd88e2 --- /dev/null +++ b/packages/carrier-mobile-ionic/src/pages/main/common/map/map.component.html @@ -0,0 +1 @@ +
diff --git a/packages/carrier-mobile-ionic/src/pages/main/common/map/map.component.ts b/packages/carrier-mobile-ionic/src/pages/main/common/map/map.component.ts new file mode 100644 index 0000000..237c501 --- /dev/null +++ b/packages/carrier-mobile-ionic/src/pages/main/common/map/map.component.ts @@ -0,0 +1,63 @@ +import { Component, ViewChild, ElementRef, OnInit } from '@angular/core'; + +declare var google: any; +const directionsDisplay = new google.maps.DirectionsRenderer(); +const directionsService = new google.maps.DirectionsService(); +@Component({ + selector: 'carrier-map', + templateUrl: 'map.component.html', +}) +export class MapComponent implements OnInit { + @ViewChild('map', { static: true }) + mapElement: ElementRef; + + map: google.maps.Map; + + ngOnInit(): void { + this.showMap(); + } + + showMap() { + const option = { + zoom: 17, + }; + + this.map = new google.maps.Map(this.mapElement.nativeElement, option); + } + + setCenter(position) { + if (this.map) { + this.map.setCenter(position); + } + } + + addMarker(position) { + if (this.map) { + const map = this.map; + return new google.maps.Marker({ + position, + map, + }); + } + } + + drawRoute(origin, destination) { + if (this.map) { + directionsDisplay.setMap(this.map); + + const request = { + origin, + destination, + travelMode: 'DRIVING', + }; + + directionsService.route(request, function (res, stat) { + if (stat === 'OK') { + directionsDisplay.setDirections({ routes: [] }); + + directionsDisplay.setDirections(res); + } + }); + } + } +} diff --git a/packages/carrier-mobile-ionic/src/pages/main/common/map/map.module.ts b/packages/carrier-mobile-ionic/src/pages/main/common/map/map.module.ts new file mode 100644 index 0000000..c7dc9f0 --- /dev/null +++ b/packages/carrier-mobile-ionic/src/pages/main/common/map/map.module.ts @@ -0,0 +1,10 @@ +import { NgModule } from '@angular/core'; +import { MapComponent } from './map.component'; +import { CommonModule } from '@angular/common'; + +@NgModule({ + declarations: [MapComponent], + exports: [MapComponent], + imports: [CommonModule], +}) +export class MapModule {} diff --git a/packages/carrier-mobile-ionic/src/pages/main/delivery/delivery.html b/packages/carrier-mobile-ionic/src/pages/main/delivery/delivery.html new file mode 100644 index 0000000..55557da --- /dev/null +++ b/packages/carrier-mobile-ionic/src/pages/main/delivery/delivery.html @@ -0,0 +1,55 @@ +
+
+ {{ 'DELIVERY_VIEW.THE_CUSTOMER_HAS_TO_PAY' | translate }} ${{ + selectedOrder?.totalPrice }}! +
+
+ +
+
+ {{ 'DELIVERY_VIEW.CUSTOMER_ALREADY_PAID_WITH_CARD' | translate }} +
+
+ + + +
+
+ {{ selectedOrder?.user.fullAddress }} + {{ carrierUserDistance + 'km' }} +
+ +
+
+ + + +
+
+
diff --git a/packages/carrier-mobile-ionic/src/pages/main/delivery/delivery.module.guard.ts b/packages/carrier-mobile-ionic/src/pages/main/delivery/delivery.module.guard.ts new file mode 100644 index 0000000..0fd39ec --- /dev/null +++ b/packages/carrier-mobile-ionic/src/pages/main/delivery/delivery.module.guard.ts @@ -0,0 +1,25 @@ +import { Injectable } from '@angular/core'; +import { CanLoad, Route, Router } from '@angular/router'; +import { Store } from 'services/store.service'; + +@Injectable() +export class DeliveryModuleGuard implements CanLoad { + constructor( + private readonly store: Store, + private readonly router: Router + ) {} + + async canLoad(route: Route) { + const orderId = await this.store.orderId; + if (!orderId) { + this.router.navigateByUrl('/main/home'); + return false; + } + const driveToWarehouseFrom = await this.store.driveToWarehouseFrom; + if (driveToWarehouseFrom === 'delivery') { + this.router.navigateByUrl('/main/drive-to-warehouse'); + return false; + } + return true; + } +} diff --git a/packages/carrier-mobile-ionic/src/pages/main/delivery/delivery.module.ts b/packages/carrier-mobile-ionic/src/pages/main/delivery/delivery.module.ts new file mode 100644 index 0000000..2c88afd --- /dev/null +++ b/packages/carrier-mobile-ionic/src/pages/main/delivery/delivery.module.ts @@ -0,0 +1,28 @@ +import { NgModule } from '@angular/core'; + +import { DeliveryPage } from './delivery'; +import { TranslateModule } from '@ngx-translate/core'; +import { Geolocation } from '@ionic-native/geolocation/ngx'; +import { GeoLocationService } from '../../../services/geo-location.service'; +import { Routes, RouterModule } from '@angular/router'; +import { CommonModule } from '@angular/common'; +import { MapModule } from '../common/map/map.module'; + +const routes: Routes = [ + { + path: '', + component: DeliveryPage, + }, +]; + +@NgModule({ + declarations: [DeliveryPage], + imports: [ + CommonModule, + TranslateModule.forChild(), + RouterModule.forChild(routes), + MapModule, + ], + providers: [Geolocation, GeoLocationService], +}) +export class DeliveryPageModule {} diff --git a/packages/carrier-mobile-ionic/src/pages/main/delivery/delivery.scss b/packages/carrier-mobile-ionic/src/pages/main/delivery/delivery.scss new file mode 100644 index 0000000..4878758 --- /dev/null +++ b/packages/carrier-mobile-ionic/src/pages/main/delivery/delivery.scss @@ -0,0 +1,2 @@ +page-delivery { +} diff --git a/packages/carrier-mobile-ionic/src/pages/main/delivery/delivery.ts b/packages/carrier-mobile-ionic/src/pages/main/delivery/delivery.ts new file mode 100644 index 0000000..4f39c40 --- /dev/null +++ b/packages/carrier-mobile-ionic/src/pages/main/delivery/delivery.ts @@ -0,0 +1,141 @@ +import { Component, ViewChild, AfterViewInit, OnDestroy } from '@angular/core'; +import IOrder from '@modules/server.common/interfaces/IOrder'; +import { OrderRouter } from '@modules/client.common.angular2/routers/order-router.service'; +import OrderCarrierStatus from '@modules/server.common/enums/OrderCarrierStatus'; +import { Mixpanel } from '@ionic-native/mixpanel/ngx'; +import Utils from '@modules/server.common/utils'; +import { Geolocation } from '@ionic-native/geolocation/ngx'; +import IGeoLocation from '@modules/server.common/interfaces/IGeoLocation'; +import GeoLocation from '@modules/server.common/entities/GeoLocation'; +import { GeoLocationService } from '../../../services/geo-location.service'; +import { MapComponent } from '../common/map/map.component'; +import { Router } from '@angular/router'; +import { Store } from 'services/store.service'; +import { takeUntil } from 'rxjs/operators'; +import { Subject } from 'rxjs'; + +declare var google: any; + +@Component({ + selector: 'page-delivery', + templateUrl: 'delivery.html', +}) +export class DeliveryPage implements AfterViewInit, OnDestroy { + @ViewChild('map') + carrierMap: MapComponent; + + selectedOrder: IOrder; + carrierUserDistance: string; + disabledButtons: boolean = true; + + private destroy$ = new Subject(); + + get fullAddress() { + return this.selectedOrder.user.fullAddress; + } + + constructor( + private orderRouter: OrderRouter, + private mixpanel: Mixpanel, + private geoLocationService: GeoLocationService, + private geolocation: Geolocation, + private router: Router, + private store: Store + ) {} + + async delivered() { + this.disabledButtons = true; + if (this.selectedOrder) { + this.router.navigateByUrl('/main/home', { + skipLocationChange: false, + }); + + this.unselectOrder(); + + await this.orderRouter.updateCarrierStatus( + this.selectedOrder['id'], + OrderCarrierStatus.DeliveryCompleted + ); + + this.mixpanel.track('Order Delivered'); + } else { + alert('Try again!'); + } + this.disabledButtons = false; + } + + cancel() { + this.disabledButtons = true; + this.store.driveToWarehouseFrom = 'delivery'; + this.router.navigateByUrl('/main/drive-to-warehouse', { + skipLocationChange: false, + }); + } + + ngAfterViewInit(): void { + this.loadData(); + } + + ionViewWillEnter() {} + + ionViewWillLeave() {} + + private unselectOrder() { + localStorage.removeItem('orderId'); + this.store.selectedOrder = null; + } + + private loadData() { + this.orderRouter + .get(localStorage.getItem('orderId'), { populateWarehouse: true }) + .pipe(takeUntil(this.destroy$)) + .subscribe(async (order) => { + this.selectedOrder = order; + this.store.selectedOrder = order; + // const carrier = await this.carrierRouter + // .get(order.carrierId) + // .pipe(first()) + // .toPromise(); + + const position = this.geoLocationService.defaultLocation() + ? this.geoLocationService.defaultLocation() + : await this.geolocation.getCurrentPosition(); + + // MongoDb store coordinates lng => lat + const dbGeoInput = { + loc: { + type: 'Point', + coordinates: [ + position.coords.longitude, + position.coords.latitude, + ], + }, + } as IGeoLocation; + + const origin = new google.maps.LatLng( + position.coords.latitude, + position.coords.longitude + ); + const userGeo = order.user['geoLocation']; + + this.carrierUserDistance = Utils.getDistance( + userGeo, + dbGeoInput as GeoLocation + ).toFixed(2); + + const destination = new google.maps.LatLng( + userGeo.loc.coordinates[1], + userGeo.loc.coordinates[0] + ); + + this.carrierMap.setCenter(origin); + this.carrierMap.drawRoute(origin, destination); + this.disabledButtons = false; + }); + } + + ngOnDestroy(): void { + this.destroy$.next(); + this.destroy$.complete(); + } +} diff --git a/packages/carrier-mobile-ionic/src/pages/main/drive-to-warehouse/drive-to-warehouse.html b/packages/carrier-mobile-ionic/src/pages/main/drive-to-warehouse/drive-to-warehouse.html new file mode 100644 index 0000000..9ef9c44 --- /dev/null +++ b/packages/carrier-mobile-ionic/src/pages/main/drive-to-warehouse/drive-to-warehouse.html @@ -0,0 +1,59 @@ + + +
+
+
+
+ To: {{ selectedOrder?.user.fullAddress }} + {{ carrierUserDistance + 'km' }} +
+
+ Order ID: {{ selectedOrderID }} +
+
+
+
+
+ + + +
+
+ + + +
+
+ +
+
+
diff --git a/packages/carrier-mobile-ionic/src/pages/main/drive-to-warehouse/drive-to-warehouse.module.guard.ts b/packages/carrier-mobile-ionic/src/pages/main/drive-to-warehouse/drive-to-warehouse.module.guard.ts new file mode 100644 index 0000000..1eeadf5 --- /dev/null +++ b/packages/carrier-mobile-ionic/src/pages/main/drive-to-warehouse/drive-to-warehouse.module.guard.ts @@ -0,0 +1,43 @@ +import { Injectable } from '@angular/core'; +import { CanLoad, Route, Router } from '@angular/router'; +import { Store } from 'services/store.service'; +import { OrderRouter } from '@modules/client.common.angular2/routers/order-router.service'; +import { first } from 'rxjs/operators'; +import OrderCarrierStatus from '@modules/server.common/enums/OrderCarrierStatus'; + +@Injectable() +export class DriveToWarehouseModuleGuard implements CanLoad { + constructor( + private readonly store: Store, + private readonly router: Router, + private readonly orderRouter: OrderRouter + ) {} + + async canLoad(route: Route) { + const orderId = await this.store.orderId; + if (orderId) { + const order = await this.orderRouter + .get(orderId, { + populateCarrier: false, + populateWarehouse: false, + }) + .pipe(first()) + .toPromise(); + + const driveToWarehouseFrom = await this.store.driveToWarehouseFrom; + + if (driveToWarehouseFrom === 'delivery') { + return true; + } + + if (order.carrierStatus > OrderCarrierStatus.CarrierSelectedOrder) { + this.router.navigateByUrl('/main/starting-delivery'); + return false; + } + } else { + this.router.navigateByUrl('/main/home'); + return false; + } + return true; + } +} diff --git a/packages/carrier-mobile-ionic/src/pages/main/drive-to-warehouse/drive-to-warehouse.module.ts b/packages/carrier-mobile-ionic/src/pages/main/drive-to-warehouse/drive-to-warehouse.module.ts new file mode 100644 index 0000000..aa808e6 --- /dev/null +++ b/packages/carrier-mobile-ionic/src/pages/main/drive-to-warehouse/drive-to-warehouse.module.ts @@ -0,0 +1,29 @@ +import { NgModule } from '@angular/core'; +import { DriveToWarehousePage } from './drive-to-warehouse'; +import { TranslateModule } from '@ngx-translate/core'; +import { Geolocation } from '@ionic-native/geolocation/ngx'; +import { GeoLocationService } from '../../../services/geo-location.service'; +import { IonicModule } from '@ionic/angular'; +import { CommonModule } from '@angular/common'; +import { Routes, RouterModule } from '@angular/router'; +import { MapModule } from '../common/map/map.module'; + +const routes: Routes = [ + { + path: '', + component: DriveToWarehousePage, + }, +]; + +@NgModule({ + declarations: [DriveToWarehousePage], + imports: [ + IonicModule, + CommonModule, + TranslateModule.forChild(), + RouterModule.forChild(routes), + MapModule, + ], + providers: [Geolocation, GeoLocationService], +}) +export class DriveToWarehousePageModule {} diff --git a/packages/carrier-mobile-ionic/src/pages/main/drive-to-warehouse/drive-to-warehouse.scss b/packages/carrier-mobile-ionic/src/pages/main/drive-to-warehouse/drive-to-warehouse.scss new file mode 100644 index 0000000..589a618 --- /dev/null +++ b/packages/carrier-mobile-ionic/src/pages/main/drive-to-warehouse/drive-to-warehouse.scss @@ -0,0 +1,5 @@ +page-drive-to-warehouse { +} +.center-info { + margin-top: 8px; +} diff --git a/packages/carrier-mobile-ionic/src/pages/main/drive-to-warehouse/drive-to-warehouse.ts b/packages/carrier-mobile-ionic/src/pages/main/drive-to-warehouse/drive-to-warehouse.ts new file mode 100644 index 0000000..5019a3b --- /dev/null +++ b/packages/carrier-mobile-ionic/src/pages/main/drive-to-warehouse/drive-to-warehouse.ts @@ -0,0 +1,215 @@ +import { Component, ViewChild, OnInit } from '@angular/core'; + +import IOrder from '@modules/server.common/interfaces/IOrder'; +import { OrderRouter } from '@modules/client.common.angular2/routers/order-router.service'; +import Utils from '@modules/server.common/utils'; +import ICarrier from '@modules/server.common/interfaces/ICarrier'; +import { CarrierRouter } from '@modules/client.common.angular2/routers/carrier-router.service'; +import GeoLocation from '@modules/server.common/entities/GeoLocation'; +import OrderCarrierStatus from '@modules/server.common/enums/OrderCarrierStatus'; +import { CarrierOrdersRouter } from '@modules/client.common.angular2/routers/carrier-orders-router.service'; +import { Store } from '../../../services/store.service'; +import { Geolocation } from '@ionic-native/geolocation/ngx'; +import IGeoLocation from '@modules/server.common/interfaces/IGeoLocation'; +import { GeoLocationService } from '../../../services/geo-location.service'; +import { MapComponent } from '../common/map/map.component'; +import { Router } from '@angular/router'; +import { NavController, Platform } from '@ionic/angular'; +import { getIdFromTheDate } from '@modules/server.common/utils'; + +declare var google: any; + +@Component({ + selector: 'page-drive-to-warehouse', + templateUrl: 'drive-to-warehouse.html', + styleUrls: ['drive-to-warehouse.scss'], +}) +export class DriveToWarehousePage implements OnInit { + @ViewChild('map') + carrierMap: MapComponent; + + selectedOrder: IOrder; + carrier: ICarrier; + carrierUserDistance: string; + workTaken: boolean; + fromDelivery: boolean; + selectedOrderID: string; + orderCarrierCompetition: boolean; + isTakenFromAnotherCarrier: boolean = false; + + carrier$; + order$; + + constructor( + private orderRouter: OrderRouter, + private carrierRouter: CarrierRouter, + private carrierOrdersRouter: CarrierOrdersRouter, + private store: Store, + private geoLocationService: GeoLocationService, + private geolocation: Geolocation, + private router: Router, + private navCtrl: NavController, + public platform: Platform + ) {} + + ngOnInit(): void { + this.fromDelivery = this.store.driveToWarehouseFrom === 'delivery'; + } + + ionViewWillEnter() { + this.carrier$ = this.carrierRouter + .get(this.store.carrierId) + .subscribe(async (c) => { + this.carrier = c; + + const position = this.geoLocationService.defaultLocation() + ? this.geoLocationService.defaultLocation() + : await this.geolocation.getCurrentPosition(); + + // MongoDb store coordinates lng => lat + let dbGeoInput = { + loc: { + type: 'Point', + coordinates: [ + position.coords.longitude, + position.coords.latitude, + ], + }, + } as IGeoLocation; + + if (this.order$) { + await this.order$.unsubscribe(); + } + + const orderId = localStorage.getItem('orderId'); + if (orderId) { + this.order$ = this.orderRouter + .get(orderId, { + populateWarehouse: true, + }) + .subscribe((order) => { + this.orderCarrierCompetition = + order.warehouse['carrierCompetition']; + + this.isTakenFromAnotherCarrier = + !!order.carrierId && + order.carrierId !== this.carrier._id && + order.carrierStatus > + (this.orderCarrierCompetition + ? OrderCarrierStatus.CarrierSelectedOrder + : OrderCarrierStatus.NoCarrier); + + this.selectedOrder = order; + this.store.selectedOrder = order; + this.selectedOrderID = getIdFromTheDate(order); + + if (!this.orderCarrierCompetition) { + this.workTaken = + order.carrierStatus !== + OrderCarrierStatus.NoCarrier; + } + + const origin = new google.maps.LatLng( + position.coords.latitude, + position.coords.longitude + ); + + const merchantGeo = order.warehouse['geoLocation']; + + this.carrierUserDistance = Utils.getDistance( + merchantGeo, + dbGeoInput as GeoLocation + ).toFixed(2); + + const destination = new google.maps.LatLng( + merchantGeo.loc.coordinates[1], + merchantGeo.loc.coordinates[0] + ); + + this.carrierMap.setCenter(origin); + this.carrierMap.drawRoute(origin, destination); + }); + } + }); + } + + async takeWork() { + if (this.carrier && this.selectedOrder) { + return await this.carrierOrdersRouter.selectedForDelivery( + this.carrier['id'], + [this.selectedOrder['id']] + ); + } else { + // TODO: replace with some popup + alert('Try again!'); + } + } + + async skipWork() { + if (this.carrier && this.selectedOrder) { + await this.carrierOrdersRouter.skipOrders(this.carrier['id'], [ + this.selectedOrder['id'], + ]); + + this.unselectOrder(); + } + } + + async carrierInWarehouse() { + if (this.fromDelivery) { + this.store.returnProductFrom = 'driveToWarehouse'; + + this.router.navigate(['/product/return'], { + skipLocationChange: false, + }); + } else { + this.router.navigateByUrl('/product/get', { + skipLocationChange: false, + }); + } + + this.unselectDriveToWarehouseFrom(); + this.unsubscribeAll(); + } + + async cancelWork() { + if (this.fromDelivery) { + this.unselectDriveToWarehouseFrom(); + this.router.navigateByUrl('/main/delivery', { + skipLocationChange: true, + }); + } else { + if (this.carrier && this.selectedOrder) { + await this.carrierOrdersRouter.cancelDelivery( + this.carrier['id'], + [this.selectedOrder['id']] + ); + this.unselectOrder(); + } + } + } + + ionViewWillLeave() { + this.unselectDriveToWarehouseFrom(); + this.unsubscribeAll(); + } + + unselectOrder() { + this.store.selectedOrder = null; + localStorage.removeItem('orderId'); + this.navCtrl.navigateRoot('/main/home'); + } + + private unsubscribeAll() { + if (this.carrier$) { + this.carrier$.unsubscribe(); + } + if (this.order$) { + this.order$.unsubscribe(); + } + } + + private unselectDriveToWarehouseFrom() { + localStorage.removeItem('driveToWarehouseFrom'); + } +} diff --git a/packages/carrier-mobile-ionic/src/pages/main/home/home.html b/packages/carrier-mobile-ionic/src/pages/main/home/home.html new file mode 100644 index 0000000..daf63f4 --- /dev/null +++ b/packages/carrier-mobile-ionic/src/pages/main/home/home.html @@ -0,0 +1,33 @@ + + +
+
+ {{ 'HOME_VIEW.DELIVERIES' | translate }}: {{ carrier?.numberOfDeliveries + }} + + {{ 'HOME_VIEW.WORKING' | translate }} + + + {{ 'HOME_VIEW.NOT_WORKING' | translate }} + +
+
+
+ + + +
+
+
diff --git a/packages/carrier-mobile-ionic/src/pages/main/home/home.module.guard.ts b/packages/carrier-mobile-ionic/src/pages/main/home/home.module.guard.ts new file mode 100644 index 0000000..bf822ef --- /dev/null +++ b/packages/carrier-mobile-ionic/src/pages/main/home/home.module.guard.ts @@ -0,0 +1,20 @@ +import { Injectable } from '@angular/core'; +import { CanLoad, Route, Router } from '@angular/router'; +import { Store } from 'services/store.service'; + +@Injectable() +export class HomeModuleGuard implements CanLoad { + constructor( + private readonly store: Store, + private readonly router: Router + ) {} + + async canLoad(route: Route) { + const orderId = await this.store.orderId; + if (orderId) { + this.router.navigateByUrl('/main/drive-to-warehouse'); + return false; + } + return true; + } +} diff --git a/packages/carrier-mobile-ionic/src/pages/main/home/home.module.ts b/packages/carrier-mobile-ionic/src/pages/main/home/home.module.ts new file mode 100644 index 0000000..06c436b --- /dev/null +++ b/packages/carrier-mobile-ionic/src/pages/main/home/home.module.ts @@ -0,0 +1,30 @@ +import { NgModule } from '@angular/core'; +import { HomePage } from './home'; +import { TranslateModule } from '@ngx-translate/core'; +import { GeoLocationOrdersService } from '../../../services/geo-location-order.service'; +import { Geolocation } from '@ionic-native/geolocation/ngx'; +import { GeoLocationService } from '../../../services/geo-location.service'; +import { Routes, RouterModule } from '@angular/router'; +import { IonicModule } from '@ionic/angular'; +import { CommonModule } from '@angular/common'; +import { MapModule } from '../common/map/map.module'; + +const routes: Routes = [ + { + path: '', + component: HomePage, + }, +]; + +@NgModule({ + declarations: [HomePage], + imports: [ + IonicModule, + CommonModule, + RouterModule.forChild(routes), + TranslateModule.forChild(), + MapModule, + ], + providers: [GeoLocationOrdersService, Geolocation, GeoLocationService], +}) +export class HomePageModule {} diff --git a/packages/carrier-mobile-ionic/src/pages/main/home/home.scss b/packages/carrier-mobile-ionic/src/pages/main/home/home.scss new file mode 100644 index 0000000..dcb420b --- /dev/null +++ b/packages/carrier-mobile-ionic/src/pages/main/home/home.scss @@ -0,0 +1,2 @@ +page-home { +} diff --git a/packages/carrier-mobile-ionic/src/pages/main/home/home.ts b/packages/carrier-mobile-ionic/src/pages/main/home/home.ts new file mode 100644 index 0000000..062d557 --- /dev/null +++ b/packages/carrier-mobile-ionic/src/pages/main/home/home.ts @@ -0,0 +1,198 @@ +import { Component, ViewChild } from '@angular/core'; +import { CarrierRouter } from '@modules/client.common.angular2/routers/carrier-router.service'; +import ICarrier from '@modules/server.common/interfaces/ICarrier'; +import CarrierStatus from '@modules/server.common/enums/CarrierStatus'; +import * as _ from 'lodash'; +import { LocalNotifications } from '@ionic-native/local-notifications/ngx'; +import { Store } from '../../../services/store.service'; +import { GeoLocationOrdersService } from '../../../services/geo-location-order.service'; +import IGeoLocation from '@modules/server.common/interfaces/IGeoLocation'; +import { Geolocation } from '@ionic-native/geolocation/ngx'; +import { GeoLocationService } from '../../../services/geo-location.service'; +import { MapComponent } from '../common/map/map.component'; +import { Router } from '@angular/router'; + +declare var google: any; + +@Component({ + selector: 'page-home', + templateUrl: 'home.html', +}) +export class HomePage { + @ViewChild('map') + carrierMap: MapComponent; + + carrier: ICarrier; + isWorking: boolean; + carrier$; + order$; + marker; + map: any; + + constructor( + private carrierRouter: CarrierRouter, + private localNotifications: LocalNotifications, + private store: Store, + private geoLocationOrdersService: GeoLocationOrdersService, + private geolocation: Geolocation, + private geoLocationService: GeoLocationService, + private router: Router + ) {} + + ionViewWillEnter() { + localStorage.removeItem('returnProductFrom'); + localStorage.removeItem('driveToWarehouseFrom'); + + this.loadData(); + } + + ionViewWillLeave() { + this.unsubscribeAll(); + } + + async startWorking() { + const res = await this.carrierRouter.updateStatus( + this.store.carrierId, + CarrierStatus.Online + ); + + this.isWorking = res.status === CarrierStatus.Online; + } + + async stopWorking() { + const res = await this.carrierRouter.updateStatus( + this.store.carrierId, + CarrierStatus.Offline + ); + + this.isWorking = res.status === CarrierStatus.Online; + localStorage.removeItem('orderId'); + this.store.selectedOrder = null; + } + + notification() { + this.localNotifications.schedule({ + id: 1, + title: 'Attention', + text: 'New order nearby you!', + led: 'FFFF00', + vibrate: true, + wakeup: false, + }); + } + + private async loadData() { + this.carrier$ = this.carrierRouter + .get(this.store.carrierId) + + .subscribe(async (carrier) => { + this.isWorking = carrier.status === CarrierStatus.Online; + this.carrier = carrier; + const position = this.geoLocationService.defaultLocation() + ? this.geoLocationService.defaultLocation() + : await this.geolocation.getCurrentPosition(); + + // MongoDb store coordinates lng => lat + let dbGeoInput = { + loc: { + type: 'Point', + coordinates: [ + position.coords.longitude, + position.coords.latitude, + ], + }, + } as IGeoLocation; + + this.carrierMap.addMarker( + new google.maps.LatLng( + position.coords.latitude, + position.coords.longitude + ) + ); + + if (this.order$) { + await this.order$.unsubscribe(); + } + + if (carrier.status === CarrierStatus.Online) { + this.order$ = this.geoLocationOrdersService + .getOrderForWork( + dbGeoInput, + carrier.skippedOrderIds, + { sort: 'asc' }, + { + isCancelled: false, + } + ) + + .subscribe(async (order) => { + if (order || this.store.orderId) { + if (this.marker) { + this.marker.setMap(null); + } + this.notification(); + if (!this.store.orderId) { + this.store.orderId = order.id; + } + + this.unsubscribeAll(); + + this.router.navigateByUrl( + '/main/drive-to-warehouse', + { skipLocationChange: false } + ); + } + }); + + // Old code here get all avaiable orders near carrier + // this.order$ = this.geoLocationOrdersRouter + // .get(carrier.geoLocation as GeoLocation) + // .map((orders) => { + // return _.find(orders, (order) => { + // return ( + // order.warehouseStatus >= + // OrderWarehouseStatus.PackagingFinished && + // order.status === + // OrderStatus.WarehousePreparation && + // !_.includes( + // carrier.skippedOrderIds, + // order.id + // ) + // ); + // }); + // }) + // .filter((availableOrder) => availableOrder != null) + // .take(1) + // .subscribe((availableOrder) => { + // if (this.marker) { + // this.marker.setMap(null); + // } + // this.notification(); + // localStorage.setItem( + // 'orderId', + // availableOrder.id + // ); + // this.navCtrl.push('drive-to-warehouse', { + // map: this.navParams.get('map') + // }); + // }); + } + + this.carrierMap.setCenter( + new google.maps.LatLng( + position.coords.latitude, + position.coords.longitude + ) + ); + }); + } + + private unsubscribeAll() { + if (this.carrier$) { + this.carrier$.unsubscribe(); + } + if (this.order$) { + this.order$.unsubscribe(); + } + } +} diff --git a/packages/carrier-mobile-ionic/src/pages/main/main.html b/packages/carrier-mobile-ionic/src/pages/main/main.html new file mode 100644 index 0000000..faebe28 --- /dev/null +++ b/packages/carrier-mobile-ionic/src/pages/main/main.html @@ -0,0 +1,33 @@ + +
+ +
+ + + +
+
+
+ + + {{ 'MAIN_VIEW.EVERCO_DRIVER' | translate }} + +
+ + +
+
+ + The customer cancel order +
+
+
+
+ + Another Carrier took the order first ! +
+
+ +
diff --git a/packages/carrier-mobile-ionic/src/pages/main/main.module.guard.ts b/packages/carrier-mobile-ionic/src/pages/main/main.module.guard.ts new file mode 100644 index 0000000..503c556 --- /dev/null +++ b/packages/carrier-mobile-ionic/src/pages/main/main.module.guard.ts @@ -0,0 +1,15 @@ +import { Injectable } from '@angular/core'; +import { CanLoad, Route, Router } from '@angular/router'; +import { Store } from '../../services/store.service'; + +@Injectable() +export class MainModuleGuard implements CanLoad { + constructor( + private readonly store: Store, + private readonly router: Router + ) {} + + async canLoad(route: Route) { + return true; + } +} diff --git a/packages/carrier-mobile-ionic/src/pages/main/main.module.ts b/packages/carrier-mobile-ionic/src/pages/main/main.module.ts new file mode 100644 index 0000000..8aa1bce --- /dev/null +++ b/packages/carrier-mobile-ionic/src/pages/main/main.module.ts @@ -0,0 +1,79 @@ +import { NgModule } from '@angular/core'; +import { MainPage } from './main'; +import { Geolocation } from '@ionic-native/geolocation/ngx'; +import { Vibration } from '@ionic-native/vibration/ngx'; +import { CommonModule } from '@angular/common'; +import { LocalNotifications } from '@ionic-native/local-notifications/ngx'; +import { TranslateModule } from '@ngx-translate/core'; +import { GeoLocationService } from '../../services/geo-location.service'; +import { Routes, RouterModule } from '@angular/router'; +import { IonicModule } from '@ionic/angular'; +import { HomeModuleGuard } from './home/home.module.guard'; +import { DeliveryModuleGuard } from './delivery/delivery.module.guard'; +import { StartingDeliveryModuleGuard } from './starting-delivery/starting-delivery.module.guard'; +import { DriveToWarehouseModuleGuard } from './drive-to-warehouse/drive-to-warehouse.module.guard'; + +const routes: Routes = [ + { + path: '', + component: MainPage, + children: [ + { + path: 'home', + loadChildren: () => + import('./home/home.module').then((m) => m.HomePageModule), + canLoad: [HomeModuleGuard], + }, + { + path: 'drive-to-warehouse', + loadChildren: () => + import( + './drive-to-warehouse/drive-to-warehouse.module' + ).then((m) => m.DriveToWarehousePageModule), + canLoad: [DriveToWarehouseModuleGuard], + }, + { + path: 'starting-delivery', + loadChildren: () => + import('./starting-delivery/starting-delivery.module').then( + (m) => m.StartingDeliveryPageModule + ), + canLoad: [StartingDeliveryModuleGuard], + }, + { + path: 'delivery', + loadChildren: () => + import('./delivery/delivery.module').then( + (m) => m.DeliveryPageModule + ), + canLoad: [DeliveryModuleGuard], + }, + { + path: '', + pathMatch: 'full', + redirectTo: 'home', + }, + ], + }, +]; + +@NgModule({ + declarations: [MainPage], + imports: [ + CommonModule, + IonicModule, + RouterModule.forChild(routes), + TranslateModule.forChild(), + ], + providers: [ + Geolocation, + Vibration, + LocalNotifications, + GeoLocationService, + HomeModuleGuard, + DriveToWarehouseModuleGuard, + StartingDeliveryModuleGuard, + DeliveryModuleGuard, + ] +}) +export class MainPageModule {} diff --git a/packages/carrier-mobile-ionic/src/pages/main/main.scss b/packages/carrier-mobile-ionic/src/pages/main/main.scss new file mode 100644 index 0000000..aeac7e6 --- /dev/null +++ b/packages/carrier-mobile-ionic/src/pages/main/main.scss @@ -0,0 +1,3 @@ +.carrier-view { + padding-top: 42px; +} diff --git a/packages/carrier-mobile-ionic/src/pages/main/main.ts b/packages/carrier-mobile-ionic/src/pages/main/main.ts new file mode 100644 index 0000000..9dae988 --- /dev/null +++ b/packages/carrier-mobile-ionic/src/pages/main/main.ts @@ -0,0 +1,139 @@ +import { + Component, + OnInit, + OnDestroy, +} from '@angular/core'; + +import { CarrierRouter } from '@modules/client.common.angular2/routers/carrier-router.service'; +import { Geolocation } from '@ionic-native/geolocation/ngx'; +import CarrierStatus from '@modules/server.common/enums/CarrierStatus'; +import { generateObjectIdString } from '@modules/server.common/utils'; +import GeoLocation from '@modules/server.common/entities/GeoLocation'; +import { Store } from '../../services/store.service'; +import { Platform } from '@ionic/angular'; +import IOrder from '@modules/server.common/interfaces/IOrder'; +import { Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; + +@Component({ + selector: 'page-main', + templateUrl: 'main.html', + styleUrls: ['main.scss'], +}) +export class MainPage implements OnInit, OnDestroy { + selectedOrder: IOrder; + + private isOnline: boolean; + private destroy$ = new Subject(); + isTakenFromAnotherCarrier: boolean = false; + + constructor( + private platform: Platform, + private carrierRouter: CarrierRouter, + private geolocation: Geolocation, + private store: Store + ) {} + + ngOnInit(): void { + this.platform.ready().then(() => { + console.warn('MainPage Loaded'); + this.watchLocation(); + this.watchOrderStatus(); + }); + } + + watchLocation() { + setInterval(() => { + if (this.isOnline) { + const carrier$ = this.carrierRouter + .get(this.store.carrierId) + .pipe(takeUntil(this.destroy$)) + .subscribe(async (carrier) => { + if (carrier.status === CarrierStatus.Online) { + this.geolocation + .getCurrentPosition() + .then((position) => { + const carrierLong = + carrier.geoLocation.coordinates.lng; + const carrierLat = + carrier.geoLocation.coordinates.lat; + + const currentLong = + position.coords.longitude; + const currentLat = position.coords.latitude; + + if ( + carrierLong !== currentLong || + carrierLat !== currentLat + ) { + this.carrierRouter + .updateGeoLocation( + carrier.id, + new GeoLocation({ + _createdAt: new Date().toString(), + _updatedAt: new Date().toString(), + _id: generateObjectIdString(), + city: + carrier.geoLocation + .city, + countryId: + carrier.geoLocation + .countryId, + streetAddress: + carrier.geoLocation + .streetAddress, + house: + carrier.geoLocation + .house, + postcode: carrier + .geoLocation.postcode + ? carrier.geoLocation + .postcode + : '', + loc: { + type: 'Point', + coordinates: [ + currentLong, + currentLat, + ], + }, + }) + ) + .then(() => { + console.log( + 'User location updated.' + ); + carrier$.unsubscribe(); + }); + } else { + carrier$.unsubscribe(); + } + }) + .catch((error) => { + console.log( + 'Error getting location', + error + ); + carrier$.unsubscribe(); + }); + } + }); + } + }, 3000); + } + + watchOrderStatus() { + this.store.selectedOrder$ + .pipe(takeUntil(this.destroy$)) + .subscribe((o) => { + this.isTakenFromAnotherCarrier = + !!o && !!o.carrier && o.carrier.id !== this.store.carrierId; + this.selectedOrder = o; + }); + } + + ngOnDestroy(): void { + this.destroy$.next(); + this.destroy$.complete(); + } +} diff --git a/packages/carrier-mobile-ionic/src/pages/main/starting-delivery/starting-delivery.html b/packages/carrier-mobile-ionic/src/pages/main/starting-delivery/starting-delivery.html new file mode 100644 index 0000000..3fb7565 --- /dev/null +++ b/packages/carrier-mobile-ionic/src/pages/main/starting-delivery/starting-delivery.html @@ -0,0 +1,35 @@ + + +
+
+ {{ selectedOrder?.user.fullAddress }} + {{ carrierUserDistance + 'km' }} +
+ +
+
+ + + +
+
+
diff --git a/packages/carrier-mobile-ionic/src/pages/main/starting-delivery/starting-delivery.module.guard.ts b/packages/carrier-mobile-ionic/src/pages/main/starting-delivery/starting-delivery.module.guard.ts new file mode 100644 index 0000000..bb961a3 --- /dev/null +++ b/packages/carrier-mobile-ionic/src/pages/main/starting-delivery/starting-delivery.module.guard.ts @@ -0,0 +1,37 @@ +import { Injectable } from '@angular/core'; +import { CanLoad, Route, Router } from '@angular/router'; +import { Store } from 'services/store.service'; +import { OrderRouter } from '@modules/client.common.angular2/routers/order-router.service'; +import { first } from 'rxjs/operators'; +import OrderCarrierStatus from '@modules/server.common/enums/OrderCarrierStatus'; + +@Injectable() +export class StartingDeliveryModuleGuard implements CanLoad { + constructor( + private readonly store: Store, + private readonly router: Router, + private readonly orderRouter: OrderRouter + ) {} + + async canLoad(route: Route) { + const orderId = await this.store.orderId; + if (!orderId) { + this.router.navigateByUrl('/main/home'); + return false; + } else { + const order = await this.orderRouter + .get(orderId, { + populateCarrier: false, + populateWarehouse: false, + }) + .pipe(first()) + .toPromise(); + + if (order.carrierStatus > OrderCarrierStatus.CarrierPickedUpOrder) { + this.router.navigateByUrl('/main/delivery'); + return false; + } + } + return true; + } +} diff --git a/packages/carrier-mobile-ionic/src/pages/main/starting-delivery/starting-delivery.module.ts b/packages/carrier-mobile-ionic/src/pages/main/starting-delivery/starting-delivery.module.ts new file mode 100644 index 0000000..8406571 --- /dev/null +++ b/packages/carrier-mobile-ionic/src/pages/main/starting-delivery/starting-delivery.module.ts @@ -0,0 +1,24 @@ +import { NgModule } from '@angular/core'; +import { StartingDeliveryPage } from './starting-delivery'; +import { TranslateModule } from '@ngx-translate/core'; +import { Routes, RouterModule } from '@angular/router'; +import { MapModule } from '../common/map/map.module'; +import { CommonModule } from '@angular/common'; + +const routes: Routes = [ + { + path: '', + component: StartingDeliveryPage, + }, +]; + +@NgModule({ + declarations: [StartingDeliveryPage], + imports: [ + CommonModule, + RouterModule.forChild(routes), + TranslateModule.forChild(), + MapModule, + ], +}) +export class StartingDeliveryPageModule {} diff --git a/packages/carrier-mobile-ionic/src/pages/main/starting-delivery/starting-delivery.scss b/packages/carrier-mobile-ionic/src/pages/main/starting-delivery/starting-delivery.scss new file mode 100644 index 0000000..5857e34 --- /dev/null +++ b/packages/carrier-mobile-ionic/src/pages/main/starting-delivery/starting-delivery.scss @@ -0,0 +1,2 @@ +page-starting-delivery { +} diff --git a/packages/carrier-mobile-ionic/src/pages/main/starting-delivery/starting-delivery.ts b/packages/carrier-mobile-ionic/src/pages/main/starting-delivery/starting-delivery.ts new file mode 100644 index 0000000..5c2b0d0 --- /dev/null +++ b/packages/carrier-mobile-ionic/src/pages/main/starting-delivery/starting-delivery.ts @@ -0,0 +1,122 @@ +import { Component, ViewChild, AfterViewInit, OnDestroy } from '@angular/core'; +import { MapComponent } from '../common/map/map.component'; +import { OrderRouter } from '@modules/client.common.angular2/routers/order-router.service'; +import IOrder from '@modules/server.common/interfaces/IOrder'; +import { Router } from '@angular/router'; +import { first, takeUntil } from 'rxjs/operators'; +import { GeoLocationService } from 'services/geo-location.service'; +import GeoLocation from '@modules/server.common/entities/GeoLocation'; +import { Geolocation } from '@ionic-native/geolocation/ngx'; +import IGeoLocation from '@modules/server.common/interfaces/IGeoLocation'; +import Utils from '@modules/server.common/utils'; +import { CarrierOrdersRouter } from '@modules/client.common.angular2/routers/carrier-orders-router.service'; +import { Store } from 'services/store.service'; +import OrderCarrierStatus from '@modules/server.common/enums/OrderCarrierStatus'; +import { Subject } from 'rxjs'; + +@Component({ + selector: 'page-starting-delivery', + templateUrl: 'starting-delivery.html', +}) +export class StartingDeliveryPage implements AfterViewInit, OnDestroy { + @ViewChild('map') + carrierMap: MapComponent; + + selectedOrder: IOrder; + carrierUserDistance: string; + disabledButtons: boolean = true; + + private destroy$ = new Subject(); + + constructor( + private orderRouter: OrderRouter, + private carrierOrdersRouter: CarrierOrdersRouter, + private geoLocationService: GeoLocationService, + private geolocation: Geolocation, + private router: Router, + private store: Store + ) {} + + ngAfterViewInit(): void { + this.loadData(); + } + + ionViewWillEnter() { + this.loadData(); + } + + async startDelivery() { + this.disabledButtons = true; + await this.carrierOrdersRouter.updateStatus( + this.store.carrierId, + OrderCarrierStatus.CarrierStartDelivery + ); + + this.router.navigateByUrl('/main/delivery', { + skipLocationChange: false, + }); + this.disabledButtons = false; + } + + returnProduct() { + this.disabledButtons = true; + this.store.returnProductFrom = 'startingDelivery'; + + this.router.navigateByUrl('/product/return', { + skipLocationChange: false, + }); + this.disabledButtons = false; + } + + private loadData() { + this.orderRouter + .get(localStorage.getItem('orderId'), { populateWarehouse: true }) + .pipe(takeUntil(this.destroy$)) + .subscribe(async (order) => { + this.selectedOrder = order; + this.store.selectedOrder = order; + + const position = this.geoLocationService.defaultLocation() + ? this.geoLocationService.defaultLocation() + : await this.geolocation.getCurrentPosition(); + + // MongoDb store coordinates lng => lat + const dbGeoInput = { + loc: { + type: 'Point', + coordinates: [ + position.coords.longitude, + position.coords.latitude, + ], + }, + } as IGeoLocation; + + const origin = new google.maps.LatLng( + position.coords.latitude, + position.coords.longitude + ); + + const userGeo = this.selectedOrder.user['geoLocation']; + + this.carrierUserDistance = Utils.getDistance( + userGeo as GeoLocation, + dbGeoInput as GeoLocation + ).toFixed(2); + + const destination = new google.maps.LatLng( + userGeo.loc.coordinates[1], + userGeo.loc.coordinates[0] + ); + + this.carrierMap.setCenter(origin); + this.carrierMap.drawRoute(origin, destination); + + this.disabledButtons = false; + }); + } + + ngOnDestroy(): void { + this.destroy$.next(); + this.destroy$.complete(); + } +} diff --git a/packages/carrier-mobile-ionic/src/pages/pages.module.guard.ts b/packages/carrier-mobile-ionic/src/pages/pages.module.guard.ts new file mode 100644 index 0000000..3015ae4 --- /dev/null +++ b/packages/carrier-mobile-ionic/src/pages/pages.module.guard.ts @@ -0,0 +1,22 @@ +import { Injectable } from '@angular/core'; +import { Router, CanLoad, Route } from '@angular/router'; +import { Store } from 'services/store.service'; + +@Injectable() +export class PagesModuleGuard implements CanLoad { + constructor( + private readonly router: Router, + private readonly store: Store + ) {} + + async canLoad(route: Route) { + const showInformationPage = this.store.showInformationPage; + + if (showInformationPage) { + await this.router.navigateByUrl('/info'); + return false; + } + + return true; + } +} diff --git a/packages/carrier-mobile-ionic/src/pages/pages.module.ts b/packages/carrier-mobile-ionic/src/pages/pages.module.ts new file mode 100644 index 0000000..c550ed4 --- /dev/null +++ b/packages/carrier-mobile-ionic/src/pages/pages.module.ts @@ -0,0 +1,70 @@ +import { NgModule } from '@angular/core'; +import { RouterModule, Routes } from '@angular/router'; +import { MainModuleGuard } from './main/main.module.guard'; +import { LoginModuleGuard } from './login/login.module.guard'; +import { ProductModuleGuard } from './product/product.module.guard'; +import { InformationModuleGuard } from './information/information.module.guard'; +import { AuthGuard } from './auth.guard'; + +const routes: Routes = [ + { + path: 'login', + loadChildren: () => + import('./login/login.module').then((m) => m.LoginPageModule), + canLoad: [LoginModuleGuard], + }, + { + path: 'main', + loadChildren: () => + import('./main/main.module').then((m) => m.MainPageModule), + canLoad: [AuthGuard, MainModuleGuard], + }, + { + path: 'product', + loadChildren: () => + import('./product/product.module').then((m) => m.ProductModule), + canLoad: [AuthGuard, ProductModuleGuard], + }, + { + path: 'deliveries', + loadChildren: () => + import('./deliveries/deliveries.module').then( + (m) => m.DeliveriesPageModule + ), + canLoad: [AuthGuard], + }, + { + path: 'language', + loadChildren: () => + import('./language/language.module').then( + (m) => m.LanguagePageModule + ), + canLoad: [AuthGuard], + }, + { + path: 'information', + loadChildren: () => + import('./information/information.module').then( + (m) => m.InformationModule + ), + canLoad: [AuthGuard, InformationModuleGuard], + }, + { + path: '', + pathMatch: 'full', + redirectTo: 'login', + }, +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + providers: [ + MainModuleGuard, + LoginModuleGuard, + ProductModuleGuard, + InformationModuleGuard, + AuthGuard, + ], + exports: [RouterModule], +}) +export class PagesModule {} diff --git a/packages/carrier-mobile-ionic/src/pages/product/get-product/get-product.html b/packages/carrier-mobile-ionic/src/pages/product/get-product/get-product.html new file mode 100644 index 0000000..60e3127 --- /dev/null +++ b/packages/carrier-mobile-ionic/src/pages/product/get-product/get-product.html @@ -0,0 +1,88 @@ +
+
+ + The customer cancel order +
+
+ +
+
+ + Another Carrier took the order first ! +
+
+ + + +
+ +
+ + + +
+
+
+ + {{ 'MAIN_VIEW.EVERCO_DRIVER' | translate }} + +
+ + + +
+ + +

+ {{selectedProductTitles[i]}} +

+ #{{selectedOrder?.orderNumber}} +
+
+
+
+
+ + +
+
+ + + +
+ +
+ +
+
+
+
+
diff --git a/packages/carrier-mobile-ionic/src/pages/product/get-product/get-product.module.ts b/packages/carrier-mobile-ionic/src/pages/product/get-product/get-product.module.ts new file mode 100644 index 0000000..c0bd967 --- /dev/null +++ b/packages/carrier-mobile-ionic/src/pages/product/get-product/get-product.module.ts @@ -0,0 +1,27 @@ +import { NgModule } from '@angular/core'; +import { GetProductPage } from './get-product'; +import { TranslateModule } from '@ngx-translate/core'; +import { Routes, RouterModule } from '@angular/router'; +import { CommonModule } from '@angular/common'; +import { IonicModule } from '@ionic/angular'; +import { MenuModule } from 'components/menu/menu.module'; + +const routes: Routes = [ + { + path: '', + component: GetProductPage, + }, +]; + +@NgModule({ + declarations: [GetProductPage], + imports: [ + TranslateModule.forChild(), + RouterModule.forChild(routes), + CommonModule, + IonicModule, + MenuModule, + ], + providers: [], +}) +export class GetProductPageModule {} diff --git a/packages/carrier-mobile-ionic/src/pages/product/get-product/get-product.scss b/packages/carrier-mobile-ionic/src/pages/product/get-product/get-product.scss new file mode 100644 index 0000000..1d1a304 --- /dev/null +++ b/packages/carrier-mobile-ionic/src/pages/product/get-product/get-product.scss @@ -0,0 +1,14 @@ +page-get-product { +} + +.image-container { +} + +ion-content { + --background: #242530; +} + +.description { + padding-left: 5px; + padding-right: 5px; +} diff --git a/packages/carrier-mobile-ionic/src/pages/product/get-product/get-product.ts b/packages/carrier-mobile-ionic/src/pages/product/get-product/get-product.ts new file mode 100644 index 0000000..9e4526a --- /dev/null +++ b/packages/carrier-mobile-ionic/src/pages/product/get-product/get-product.ts @@ -0,0 +1,157 @@ +import { Component, OnDestroy } from '@angular/core'; +import ICarrier from '@modules/server.common/interfaces/ICarrier'; +import IOrder from '@modules/server.common/interfaces/IOrder'; +import { OrderRouter } from '@modules/client.common.angular2/routers/order-router.service'; +import { CarrierRouter } from '@modules/client.common.angular2/routers/carrier-router.service'; +import OrderCarrierStatus from '@modules/server.common/enums/OrderCarrierStatus'; +import { CarrierOrdersRouter } from '@modules/client.common.angular2/routers/carrier-orders-router.service'; +import { ILocaleMember } from '@modules/server.common/interfaces/ILocale'; +import { ProductLocalesService } from '@modules/client.common.angular2/locale/product-locales.service'; +import { Store } from '../../../services/store.service'; +import { first, takeUntil } from 'rxjs/operators'; +import { Subject } from 'rxjs'; +import { NavController } from '@ionic/angular'; +import { environment } from 'environments/environment'; + +@Component({ + selector: 'page-get-product', + templateUrl: 'get-product.html', + styleUrls: ['./get-product.scss'], +}) +export class GetProductPage implements OnDestroy { + carrier: ICarrier; + selectedOrder: IOrder; + disabledButtons: boolean = true; + private productsLocale: string; + selectedProductImages: any; + selectedProductTitles: any; + orderCarrierCompetition: boolean; + isTakenFromAnotherCarrier: boolean = false; + + private destroy$ = new Subject(); + constructor( + private orderRouter: OrderRouter, + private carrierRouter: CarrierRouter, + private carrierOrdersRouter: CarrierOrdersRouter, + private _translateProductLocales: ProductLocalesService, + private store: Store, + private navCtrl: NavController + ) { + this.productsLocale = + this.store.language || environment.DEFAULT_LANGUAGE; + } + + ionViewWillEnter() { + this.loadData(); + } + + async gotProduct() { + this.disabledButtons = true; + if (this.carrier && this.selectedOrder) { + await this.carrierOrdersRouter.updateStatus( + this.carrier['id'], + OrderCarrierStatus.CarrierPickedUpOrder + ); + + this.navCtrl.navigateRoot('/main/starting-delivery'); + } else { + // TODO: replace with popup + alert('Try again!'); + } + this.disabledButtons = false; + } + async gotProductWithCarrierCompetition() { + this.disabledButtons = true; + if (this.carrier && this.selectedOrder) { + await this.carrierOrdersRouter.selectedForDelivery( + this.carrier['id'], + [this.selectedOrder['id']], + this.orderCarrierCompetition + ); + + this.navCtrl.navigateRoot('/main/starting-delivery'); + } else { + // TODO: replace with popup + alert('Try again!'); + } + this.disabledButtons = false; + } + + async cancelWork() { + this.disabledButtons = true; + if (this.carrier && this.selectedOrder) { + await this.carrierOrdersRouter.cancelDelivery(this.carrier['id'], [ + this.selectedOrder['id'], + ]); + + this.unselectOrder(); + } else { + // // TODO: replace with popup + alert('Try again!'); + } + this.disabledButtons = false; + } + + localeTranslate(member: ILocaleMember[]): string { + return this._translateProductLocales.getTranslate(member); + } + + private async loadData() { + this.carrier = await this.carrierRouter + .get(this.store.carrierId) + .pipe(first()) + .toPromise(); + await this.orderRouter + .get(this.store.orderId, { + populateWarehouse: true, + }) + .pipe(takeUntil(this.destroy$)) + .subscribe((o) => { + this.orderCarrierCompetition = + o.warehouse['carrierCompetition']; + + this.isTakenFromAnotherCarrier = + !!o.carrierId && + o.carrierId !== this.carrier._id && + o.carrierStatus > + (this.orderCarrierCompetition + ? OrderCarrierStatus.CarrierSelectedOrder + : OrderCarrierStatus.NoCarrier); + + this.selectedOrder = o; + this.store.selectedOrder = o; + this.disabledButtons = false; + const imageUrls = []; + const titles = []; + this.selectedOrder.products.forEach((x) => { + x.product.images.forEach((x) => { + if (x.locale.match(this.productsLocale)) { + imageUrls.push(x.url); + } + }); + }); + this.selectedOrder.products.forEach((x) => { + x.product.title.forEach((x) => { + if (x.locale.match(this.productsLocale)) { + titles.push(x.value); + } + }); + }); + + this.selectedProductImages = imageUrls; + this.selectedProductTitles = titles; + }); + } + + unselectOrder() { + this.store.selectedOrder = null; + localStorage.removeItem('orderId'); + + this.navCtrl.navigateRoot('/main/home'); + } + + ngOnDestroy(): void { + this.destroy$.next(); + this.destroy$.complete(); + } +} diff --git a/packages/carrier-mobile-ionic/src/pages/product/product.module.guard.ts b/packages/carrier-mobile-ionic/src/pages/product/product.module.guard.ts new file mode 100644 index 0000000..2cfe446 --- /dev/null +++ b/packages/carrier-mobile-ionic/src/pages/product/product.module.guard.ts @@ -0,0 +1,19 @@ +import { Injectable } from '@angular/core'; +import { CanLoad, Route, Router } from '@angular/router'; +import { Store } from '../../services/store.service'; + +@Injectable() +export class ProductModuleGuard implements CanLoad { + constructor( + private readonly store: Store, + private readonly router: Router + ) {} + + async canLoad(route: Route) { + if (!this.store.orderId) { + this.router.navigateByUrl('/main/home'); + return false; + } + return true; + } +} diff --git a/packages/carrier-mobile-ionic/src/pages/product/product.module.ts b/packages/carrier-mobile-ionic/src/pages/product/product.module.ts new file mode 100644 index 0000000..c86536e --- /dev/null +++ b/packages/carrier-mobile-ionic/src/pages/product/product.module.ts @@ -0,0 +1,30 @@ +import { NgModule } from '@angular/core'; +import { TranslateModule } from '@ngx-translate/core'; +import { Routes, RouterModule } from '@angular/router'; + +const routes: Routes = [ + { + path: 'get', + loadChildren: () => + import('./get-product/get-product.module').then( + (m) => m.GetProductPageModule + ), + }, + { + path: 'return', + loadChildren: () => + import('./return-product/return-product.module').then( + (m) => m.ReturnProductPageModule + ), + }, + { + path: '', + pathMatch: 'full', + redirectTo: 'get', + }, +]; + +@NgModule({ + imports: [TranslateModule.forChild(), RouterModule.forChild(routes)], +}) +export class ProductModule {} diff --git a/packages/carrier-mobile-ionic/src/pages/product/return-product/return-product.html b/packages/carrier-mobile-ionic/src/pages/product/return-product/return-product.html new file mode 100644 index 0000000..6b548f5 --- /dev/null +++ b/packages/carrier-mobile-ionic/src/pages/product/return-product/return-product.html @@ -0,0 +1,38 @@ +
+
+ + The customer cancel order +
+
+ +
+ +
+
+ {{ + localeTranslate((selectedOrder?.products)[0]?.product?.title) + }} + #{{ selectedOrder?.orderNumber }} +
+
+
+ + + +
+
+
+
diff --git a/packages/carrier-mobile-ionic/src/pages/product/return-product/return-product.module.ts b/packages/carrier-mobile-ionic/src/pages/product/return-product/return-product.module.ts new file mode 100644 index 0000000..8b882d9 --- /dev/null +++ b/packages/carrier-mobile-ionic/src/pages/product/return-product/return-product.module.ts @@ -0,0 +1,25 @@ +import { NgModule } from '@angular/core'; + +import { ReturnProductPage } from './return-product'; +import { TranslateModule } from '@ngx-translate/core'; +import { Routes, RouterModule } from '@angular/router'; +import { IonicModule } from '@ionic/angular'; +import { CommonModule } from '@angular/common'; + +const routes: Routes = [ + { + path: '', + component: ReturnProductPage, + }, +]; + +@NgModule({ + declarations: [ReturnProductPage], + imports: [ + IonicModule, + CommonModule, + RouterModule.forChild(routes), + TranslateModule.forChild(), + ], +}) +export class ReturnProductPageModule {} diff --git a/packages/carrier-mobile-ionic/src/pages/product/return-product/return-product.scss b/packages/carrier-mobile-ionic/src/pages/product/return-product/return-product.scss new file mode 100644 index 0000000..1b3d8e0 --- /dev/null +++ b/packages/carrier-mobile-ionic/src/pages/product/return-product/return-product.scss @@ -0,0 +1,2 @@ +page-return-product { +} diff --git a/packages/carrier-mobile-ionic/src/pages/product/return-product/return-product.ts b/packages/carrier-mobile-ionic/src/pages/product/return-product/return-product.ts new file mode 100644 index 0000000..351b247 --- /dev/null +++ b/packages/carrier-mobile-ionic/src/pages/product/return-product/return-product.ts @@ -0,0 +1,103 @@ +import { Component, OnDestroy } from '@angular/core'; +import { ILocaleMember } from '@modules/server.common/interfaces/ILocale'; +import { ProductLocalesService } from '@modules/client.common.angular2/locale/product-locales.service'; +import ICarrier from '@modules/server.common/interfaces/ICarrier'; +import IOrder from '@modules/server.common/interfaces/IOrder'; +import { CarrierRouter } from '@modules/client.common.angular2/routers/carrier-router.service'; +import { OrderRouter } from '@modules/client.common.angular2/routers/order-router.service'; +import { first, takeUntil } from 'rxjs/operators'; +import { Store } from 'services/store.service'; +import OrderCarrierStatus from '@modules/server.common/enums/OrderCarrierStatus'; +import { Subject } from 'rxjs'; +import { NavController } from '@ionic/angular'; + +@Component({ + selector: 'page-return-product', + templateUrl: 'return-product.html', +}) +export class ReturnProductPage implements OnDestroy { + carrier: ICarrier; + selectedOrder: IOrder; + + private destroy$ = new Subject(); + + constructor( + private translateProductLocales: ProductLocalesService, + private carrierRouter: CarrierRouter, + private orderRouter: OrderRouter, + private store: Store, + private navCtrl: NavController + ) {} + + get allowCancel() { + switch (this.store.returnProductFrom) { + case 'driveToWarehouse': + return false; + case 'startingDelivery': + return this.selectedOrder && !this.selectedOrder.isCancelled; + default: + return false; + } + } + + ionViewWillEnter() { + this.loadData(); + } + + localeTranslate(member: ILocaleMember[]) { + return this.translateProductLocales.getTranslate(member); + } + + cancelReturn() { + if (!this.allowCancel) throw new Error('Cancel not allowed!'); + + this.navCtrl.navigateRoot('/main/starting-delivery'); + } + + async returnProduct() { + if (!this.selectedOrder.isCancelled) { + await this.orderRouter.updateCarrierStatus( + this.store.orderId, + OrderCarrierStatus.IssuesDuringDelivery + ); + + this.unselectOrder(); + + this.navCtrl.navigateRoot('/main/home'); + } else { + this.unselectOrder(); + this.navCtrl.navigateRoot('/main/home'); + } + } + + ionViewWillLeave() { + localStorage.removeItem('returnProductFrom'); + } + + private async loadData() { + this.carrier = await this.carrierRouter + .get(this.store.carrierId) + .pipe(first()) + .toPromise(); + + this.orderRouter + .get(this.store.orderId, { + populateWarehouse: true, + }) + .pipe(takeUntil(this.destroy$)) + .subscribe((o) => { + this.selectedOrder = o; + this.store.selectedOrder = o; + }); + } + + private unselectOrder() { + localStorage.removeItem('orderId'); + this.store.selectedOrder = null; + } + + ngOnDestroy(): void { + this.destroy$.next(); + this.destroy$.complete(); + } +} diff --git a/packages/carrier-mobile-ionic/src/polyfills.ts b/packages/carrier-mobile-ionic/src/polyfills.ts new file mode 100644 index 0000000..d9c48d9 --- /dev/null +++ b/packages/carrier-mobile-ionic/src/polyfills.ts @@ -0,0 +1,67 @@ +/** + * This file includes polyfills needed by Angular and is loaded before the app. + * You can add your own extra polyfills to this file. + * + * This file is divided into 2 sections: + * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. + * 2. Application imports. Files imported after ZoneJS that should be loaded before your main + * file. + * + * The current setup is for so-called "evergreen" browsers; the last versions of browsers that + * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), + * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. + * + * Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html + */ + +/*************************************************************************************************** + * BROWSER POLYFILLS + */ +(window as any).process = { + env: { DEBUG: undefined }, +}; + +(window as any).global = window; +import '@webcomponents/webcomponentsjs/custom-elements-es5-adapter.js'; + +/** IE9, IE10 and IE11 requires all of the following polyfills. **/ +// import 'core-js/es6/symbol'; +// import 'core-js/es6/object'; +// import 'core-js/es6/function'; +// import 'core-js/es6/parse-int'; +// import 'core-js/es6/parse-float'; +// import 'core-js/es6/number'; +// import 'core-js/es6/math'; +// import 'core-js/es6/string'; +// import 'core-js/es6/date'; +// import 'core-js/es6/array'; +// import 'core-js/es6/regexp'; +// import 'core-js/es6/map'; +// import 'core-js/es6/weak-map'; +// import 'core-js/es6/set'; + +/** IE10 and IE11 requires the following for the Reflect API. */ +// import 'core-js/es6/reflect'; + +/** Evergreen browsers require these. **/ +// Used for reflect-metadata in JIT. If you use AOT (and only Angular decorators), you can remove. +import 'core-js/es7/reflect'; + +/*************************************************************************************************** + * Zone JS is required by Angular itself. + */ +import 'zone.js/dist/zone'; // Included with Angular CLI. + +/*************************************************************************************************** + * APPLICATION IMPORTS + */ + +/** + * Date, currency, decimal and percent pipes. + * Needed for: All but Chrome, Firefox, Edge, IE11 and Safari 10 + */ +// import 'intl'; // Run `npm install --save intl`. +/** + * Need to import at least one locale-data with intl. + */ +// import 'intl/locale-data/jsonp/en'; diff --git a/packages/carrier-mobile-ionic/src/services/auth.service.ts b/packages/carrier-mobile-ionic/src/services/auth.service.ts new file mode 100644 index 0000000..3dd4e02 --- /dev/null +++ b/packages/carrier-mobile-ionic/src/services/auth.service.ts @@ -0,0 +1,40 @@ +import { Injectable } from '@angular/core'; +import { Apollo, gql } from 'apollo-angular'; +import { map, share } from 'rxjs/operators'; +import Carrier from '@modules/server.common/entities/Carrier'; + +export interface CarrierLoginInfo { + carrier: Carrier; + token: string; +} + +@Injectable() +export class AuthService { + constructor(private readonly apollo: Apollo) {} + login(username: string, password: string) { + return this.apollo + .mutate<{ carrierLogin: CarrierLoginInfo }>({ + mutation: gql` + mutation CarrierLogin( + $username: String! + $password: String! + ) { + carrierLogin(username: $username, password: $password) { + token + carrier { + id + } + } + } + `, + variables: { + username, + password, + }, + }) + .pipe( + map((result) => result.data.carrierLogin), + share() + ); + } +} diff --git a/packages/carrier-mobile-ionic/src/services/carriers-orders.service.ts b/packages/carrier-mobile-ionic/src/services/carriers-orders.service.ts new file mode 100644 index 0000000..eb7b119 --- /dev/null +++ b/packages/carrier-mobile-ionic/src/services/carriers-orders.service.ts @@ -0,0 +1,101 @@ +import { Injectable } from '@angular/core'; +import { Apollo, gql } from 'apollo-angular'; +import { ICarrierOrdersRouterGetOptions } from '@modules/server.common/routers/ICarrierOrdersRouter'; +import IOrder from '@modules/server.common/interfaces/IOrder'; +import Order from '@modules/server.common/entities/Order'; +import { map, share } from 'rxjs/operators'; + +@Injectable() +export class CarriersOrdersService { + constructor(private readonly _apollo: Apollo) {} + + async getCarrierOrders( + carrierId: string, + options: ICarrierOrdersRouterGetOptions + ) { + const res = await this._apollo + .query<{ getCarrierOrders: Order[] }>({ + query: gql` + query GetCarrierOrders( + $carrierId: String! + $options: CarrierOrdersOptions! + ) { + getCarrierOrders( + carrierId: $carrierId + options: $options + ) { + id + isConfirmed + isCancelled + isPaid + warehouseStatus + carrierStatus + orderNumber + _createdAt + user { + _id + firstName + lastName + geoLocation { + countryId + postcode + notes + city + } + } + warehouse { + logo + name + geoLocation { + countryId + postcode + city + } + } + # carrier { + # id + # } + products { + count + isManufacturing + isCarrierRequired + isDeliveryRequired + initialPrice + price + product { + id + title { + locale + value + } + details { + locale + value + } + description { + locale + value + } + images { + locale + url + width + height + orientation + } + } + } + } + } + `, + variables: { + carrierId, + options, + }, + }) + .toPromise(); + + return res.data.getCarrierOrders; + // .valueChanges.pipe(map((res) => res.data.getCarrierOrders)); + } +} diff --git a/packages/carrier-mobile-ionic/src/services/geo-location-order.service.ts b/packages/carrier-mobile-ionic/src/services/geo-location-order.service.ts new file mode 100644 index 0000000..ccbb505 --- /dev/null +++ b/packages/carrier-mobile-ionic/src/services/geo-location-order.service.ts @@ -0,0 +1,47 @@ +import { Injectable } from '@angular/core'; +import { Apollo, gql } from 'apollo-angular'; +import Order from '@modules/server.common/entities/Order'; +import { map, share } from 'rxjs/operators'; +import IGeoLocation from '@modules/server.common/interfaces/IGeoLocation'; + +@Injectable() +export class GeoLocationOrdersService { + constructor(private readonly apollo: Apollo) {} + + getOrderForWork( + geoLocation: IGeoLocation, + skippedOrderIds: string[] = [], + options: { sort: string } = { sort: 'asc' }, + searchObj?: { + isCancelled?: boolean; + byRegex?: Array<{ key: string; value: string }>; + } + ) { + return this.apollo + .watchQuery<{ getOrderForWork: Order }>({ + query: gql` + query GetOrderForWork( + $geoLocation: GeoLocationFindInput! + $skippedOrderIds: [String!]! + $options: GeoLocationOrdersOptions + $searchObj: SearchOrdersForWork + ) { + getOrderForWork( + geoLocation: $geoLocation + skippedOrderIds: $skippedOrderIds + options: $options + searchObj: $searchObj + ) { + id + } + } + `, + variables: { geoLocation, skippedOrderIds, options, searchObj }, + pollInterval: 2000, + }) + .valueChanges.pipe( + map((res) => res.data.getOrderForWork), + share() + ); + } +} diff --git a/packages/carrier-mobile-ionic/src/services/geo-location.service.ts b/packages/carrier-mobile-ionic/src/services/geo-location.service.ts new file mode 100644 index 0000000..6b644b1 --- /dev/null +++ b/packages/carrier-mobile-ionic/src/services/geo-location.service.ts @@ -0,0 +1,19 @@ +import { Injectable } from '@angular/core'; +import { environment } from '../environments/environment'; + +@Injectable() +export class GeoLocationService { + defaultLocation() { + const longitude = environment['DEFAULT_LONGITUDE']; + const latitude = environment['DEFAULT_LATITUDE']; + if (latitude && longitude) { + return { + coords: { + longitude, + latitude, + }, + }; + } + return; + } +} diff --git a/packages/carrier-mobile-ionic/src/services/store.service.ts b/packages/carrier-mobile-ionic/src/services/store.service.ts new file mode 100644 index 0000000..a56583d --- /dev/null +++ b/packages/carrier-mobile-ionic/src/services/store.service.ts @@ -0,0 +1,182 @@ +import { Injectable } from '@angular/core'; +import Order from '@modules/server.common/entities/Order'; +import Device from '@modules/server.common/entities/Device'; +import { TranslateService } from '@ngx-translate/core'; +import Carrier from '@modules/server.common/entities/Carrier'; +import ILanguage from '@modules/server.common/interfaces/ILanguage'; +import { CarrierRouter } from '@modules/client.common.angular2/routers/carrier-router.service'; +import { first } from 'rxjs/operators'; +import { BehaviorSubject } from 'rxjs'; +import IOrder from '@modules/server.common/interfaces/IOrder'; + +// TODO use https://beta.ionicframework.com/docs/building/storage + +@Injectable() +export class Store { + private _selectedOrder: IOrder; + + constructor( + private readonly carrierRouter: CarrierRouter, + private readonly translate: TranslateService // private readonly platform: Platform + ) { + // this._initLanguage(); + } + + selectedOrder$: BehaviorSubject = new BehaviorSubject( + this.selectedOrder + ); + + set selectedOrder(order: IOrder) { + this.selectedOrder$.next(order); + this._selectedOrder = order; + } + + get selectedOrder(): IOrder { + return this._selectedOrder; + } + + get token(): string | null { + return localStorage.getItem('token') || null; + } + + set token(token: string) { + if (token == null) { + localStorage.removeItem('token'); + } else { + localStorage.setItem('token', token); + } + } + + get carrierId(): string | null { + return localStorage.getItem('carrier') || null; + } + + set carrierId(id: Carrier['id'] | null) { + if (id == null) { + localStorage.removeItem('carrier'); + } else { + localStorage.setItem('carrier', id); + } + } + + get orderId(): string | null { + return localStorage.getItem('orderId') || null; + } + + set orderId(id: Order['id'] | null) { + if (id == null) { + localStorage.removeItem('orderId'); + } else { + localStorage.setItem('orderId', id); + } + } + + get deviceId(): string | null { + return localStorage.getItem('_deviceId') || null; + } + + set deviceId(id: Device['id'] | null) { + if (id == null) { + localStorage.removeItem('_deviceId'); + } else { + localStorage.setItem('_deviceId', id); + } + } + + get platform(): string | null { + return localStorage.getItem('_platform') || null; + } + + set platform(type: string | null) { + if (type == null) { + localStorage.removeItem('_platform'); + } else { + localStorage.setItem('_platform', type); + } + } + + get language(): ILanguage { + return (localStorage.getItem('_language') as ILanguage) || null; + } + + set language(language: ILanguage) { + if (language == null) { + localStorage.removeItem('_language'); + } else { + localStorage.setItem('_language', language); + } + + this.translate.use(language.substr(0, 2)); + } + + get maintenanceMode(): string | null { + return localStorage.getItem('maintenanceMode') || null; + } + + get noInternet(): string | null { + return localStorage.getItem('noInternet') || null; + } + + set noInternet(text) { + localStorage.setItem('noInternet', text); + } + + get serverConnection() { + return localStorage.getItem('serverConnection'); + } + + set serverConnection(val: string) { + localStorage.setItem('serverConnection', val); + } + + get showInformationPage() { + return ( + this.noInternet || + this.maintenanceMode || + Number(this.serverConnection) === 0 + ); + } + + set returnProductFrom(val: string) { + localStorage.setItem('returnProductFrom', val); + } + + get returnProductFrom() { + return localStorage.getItem('returnProductFrom'); + } + + set driveToWarehouseFrom(val: string) { + localStorage.setItem('driveToWarehouseFrom', val); + } + + get driveToWarehouseFrom() { + return localStorage.getItem('driveToWarehouseFrom'); + } + + async isLogged() { + const carrierId = this.carrierId; + if (carrierId) { + try { + await this.carrierRouter + .get(carrierId) + .pipe(first()) + .toPromise(); + return true; + } catch (error) {} + } + console.warn(`Carrier with id '${carrierId}' does not exists!"`); + return false; + } + + clearMaintenanceMode() { + localStorage.removeItem('maintenanceMode'); + } + + clearNoInternet() { + localStorage.removeItem('noInternet'); + } + + clear() { + localStorage.clear(); + } +} diff --git a/packages/carrier-mobile-ionic/src/test.ts b/packages/carrier-mobile-ionic/src/test.ts new file mode 100644 index 0000000..fc74169 --- /dev/null +++ b/packages/carrier-mobile-ionic/src/test.ts @@ -0,0 +1,22 @@ +// This file is required by karma.conf.js and loads recursively all the .spec and framework files + +import 'zone.js/dist/zone-testing'; +import { getTestBed } from '@angular/core/testing'; +import { + BrowserDynamicTestingModule, + platformBrowserDynamicTesting, +} from '@angular/platform-browser-dynamic/testing'; + +declare const require: any; + +// First, initialize the Angular testing environment. +getTestBed().initTestEnvironment( + BrowserDynamicTestingModule, + platformBrowserDynamicTesting(), { + teardown: { destroyAfterEach: false } +} +); +// Then we find all the tests. +const context = require.context('./', true, /\.spec\.ts$/); +// And load the modules. +context.keys().map(context); diff --git a/packages/carrier-mobile-ionic/src/theme/fonts/google-font-Jura.scss b/packages/carrier-mobile-ionic/src/theme/fonts/google-font-Jura.scss new file mode 100644 index 0000000..f530a08 --- /dev/null +++ b/packages/carrier-mobile-ionic/src/theme/fonts/google-font-Jura.scss @@ -0,0 +1,74 @@ +/* cyrillic-ext */ +@font-face { + font-family: 'Jura'; + font-style: normal; + font-weight: 400; + src: local('Jura Regular'), local('Jura-Regular'), + url(https://fonts.gstatic.com/s/jura/v9/z7NbdRfiaC4VXclJURRD.woff2) + format('woff2'); + unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, + U+FE2E-FE2F; +} +/* cyrillic */ +@font-face { + font-family: 'Jura'; + font-style: normal; + font-weight: 400; + src: local('Jura Regular'), local('Jura-Regular'), + url(https://fonts.gstatic.com/s/jura/v9/z7NbdRfiaC4VXcBJURRD.woff2) + format('woff2'); + unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; +} +/* greek-ext */ +@font-face { + font-family: 'Jura'; + font-style: normal; + font-weight: 400; + src: local('Jura Regular'), local('Jura-Regular'), + url(https://fonts.gstatic.com/s/jura/v9/z7NbdRfiaC4VXchJURRD.woff2) + format('woff2'); + unicode-range: U+1F00-1FFF; +} +/* greek */ +@font-face { + font-family: 'Jura'; + font-style: normal; + font-weight: 400; + src: local('Jura Regular'), local('Jura-Regular'), + url(https://fonts.gstatic.com/s/jura/v9/z7NbdRfiaC4VXcdJURRD.woff2) + format('woff2'); + unicode-range: U+0370-03FF; +} +/* vietnamese */ +@font-face { + font-family: 'Jura'; + font-style: normal; + font-weight: 400; + src: local('Jura Regular'), local('Jura-Regular'), + url(https://fonts.gstatic.com/s/jura/v9/z7NbdRfiaC4VXctJURRD.woff2) + format('woff2'); + unicode-range: U+0102-0103, U+0110-0111, U+1EA0-1EF9, U+20AB; +} +/* latin-ext */ +@font-face { + font-family: 'Jura'; + font-style: normal; + font-weight: 400; + src: local('Jura Regular'), local('Jura-Regular'), + url(https://fonts.gstatic.com/s/jura/v9/z7NbdRfiaC4VXcpJURRD.woff2) + format('woff2'); + unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, + U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF; +} +/* latin */ +@font-face { + font-family: 'Jura'; + font-style: normal; + font-weight: 400; + src: local('Jura Regular'), local('Jura-Regular'), + url(https://fonts.gstatic.com/s/jura/v9/z7NbdRfiaC4VXcRJUQ.woff2) + format('woff2'); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, + U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, + U+2215, U+FEFF, U+FFFD; +} diff --git a/packages/carrier-mobile-ionic/src/theme/fonts/google-font-Roboto.scss b/packages/carrier-mobile-ionic/src/theme/fonts/google-font-Roboto.scss new file mode 100644 index 0000000..9698a92 --- /dev/null +++ b/packages/carrier-mobile-ionic/src/theme/fonts/google-font-Roboto.scss @@ -0,0 +1,74 @@ +/* cyrillic-ext */ +@font-face { + font-family: 'Roboto'; + font-style: normal; + font-weight: 400; + src: local('Roboto'), local('Roboto-Regular'), + url(https://fonts.gstatic.com/s/roboto/v18/KFOmCnqEu92Fr1Mu72xKOzY.woff2) + format('woff2'); + unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, + U+FE2E-FE2F; +} +/* cyrillic */ +@font-face { + font-family: 'Roboto'; + font-style: normal; + font-weight: 400; + src: local('Roboto'), local('Roboto-Regular'), + url(https://fonts.gstatic.com/s/roboto/v18/KFOmCnqEu92Fr1Mu5mxKOzY.woff2) + format('woff2'); + unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; +} +/* greek-ext */ +@font-face { + font-family: 'Roboto'; + font-style: normal; + font-weight: 400; + src: local('Roboto'), local('Roboto-Regular'), + url(https://fonts.gstatic.com/s/roboto/v18/KFOmCnqEu92Fr1Mu7mxKOzY.woff2) + format('woff2'); + unicode-range: U+1F00-1FFF; +} +/* greek */ +@font-face { + font-family: 'Roboto'; + font-style: normal; + font-weight: 400; + src: local('Roboto'), local('Roboto-Regular'), + url(https://fonts.gstatic.com/s/roboto/v18/KFOmCnqEu92Fr1Mu4WxKOzY.woff2) + format('woff2'); + unicode-range: U+0370-03FF; +} +/* vietnamese */ +@font-face { + font-family: 'Roboto'; + font-style: normal; + font-weight: 400; + src: local('Roboto'), local('Roboto-Regular'), + url(https://fonts.gstatic.com/s/roboto/v18/KFOmCnqEu92Fr1Mu7WxKOzY.woff2) + format('woff2'); + unicode-range: U+0102-0103, U+0110-0111, U+1EA0-1EF9, U+20AB; +} +/* latin-ext */ +@font-face { + font-family: 'Roboto'; + font-style: normal; + font-weight: 400; + src: local('Roboto'), local('Roboto-Regular'), + url(https://fonts.gstatic.com/s/roboto/v18/KFOmCnqEu92Fr1Mu7GxKOzY.woff2) + format('woff2'); + unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, + U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF; +} +/* latin */ +@font-face { + font-family: 'Roboto'; + font-style: normal; + font-weight: 400; + src: local('Roboto'), local('Roboto-Regular'), + url(https://fonts.gstatic.com/s/roboto/v18/KFOmCnqEu92Fr1Mu4mxK.woff2) + format('woff2'); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, + U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, + U+2215, U+FEFF, U+FFFD; +} diff --git a/packages/carrier-mobile-ionic/src/theme/fonts/open-sans-hebrew.scss b/packages/carrier-mobile-ionic/src/theme/fonts/open-sans-hebrew.scss new file mode 100644 index 0000000..f803d71 --- /dev/null +++ b/packages/carrier-mobile-ionic/src/theme/fonts/open-sans-hebrew.scss @@ -0,0 +1,29 @@ +/*TODO ADD FONT +@font-face { + font-family: 'Open Sans Hebrew'; + font-style: normal; + font-weight: 200; + src: url("../../fonts/OpenSansHebrew-Light.ttf") format('truetype'); +} + +@font-face { + font-family: 'Open Sans Hebrew'; + font-style: normal; + font-weight: 400; + src: url("../../fonts/OpenSansHebrew-Regular.ttf") format('truetype'); +} + +@font-face { + font-family: 'Open Sans Hebrew'; + font-style: normal; + font-weight: 600; + src: url("../../fonts/OpenSansHebrew-Bold.ttf") format('truetype'); +} + +@font-face { + font-family: 'Open Sans Hebrew'; + font-style: normal; + font-weight: 800; + src: url("../../fonts/OpenSansHebrew-ExtraBold.ttf") format('truetype'); +} +*/ diff --git a/packages/carrier-mobile-ionic/src/theme/styles.scss b/packages/carrier-mobile-ionic/src/theme/styles.scss new file mode 100644 index 0000000..88fec8c --- /dev/null +++ b/packages/carrier-mobile-ionic/src/theme/styles.scss @@ -0,0 +1,360 @@ +$home-view-map-height: 116px; + +.carrier-view { + .map { + height: 78%; + } + + .bottom-container { + bottom: 0; + position: fixed; + width: 100%; + background-color: #f3f3f3; + height: 22%; + .info { + font-size: 16px; + padding: 12px 20px 0 20px; + + height: 55%; + display: flex !important; + align-content: center !important; + align-items: center !important; + padding-top: 0 !important; + + .right-text.rtl { + position: absolute; + left: 20px !important; + } + .right-text.ltr { + position: absolute; + right: 20px !important; + } + .left-info { + max-width: 80%; + } + } + + .buttons { + text-align: center; + font-size: 40px; + padding: 0px; + + .button { + padding: 10px 30px; + + &.big-text { + font-size: 20px; + } + } + + .button-bar { + height: 100%; + .button { + height: 100% !important; + font-size: auto !important; + padding: 0 !important; + } + } + height: 45%; + } + } + + // .product-image { + // width: 100%; + // } + .angular-google-map { + width: 100%; + height: 100%; + + .angular-google-map-container { + height: calc(100% - #{$home-view-map-height}); + + .map-marker-label { + font-size: 15px; + } + } + } +} + +.money-amount-container { + top: 0; + position: absolute; + width: 100%; + background-color: $assertive; + opacity: 0.8; + z-index: 9999; + + .money-amount { + opacity: 1; + font-size: 17px; + padding: 11px; + color: #fff; + font-weight: 500; + text-align: center; + position: relative; + } +} + +.already-paid-container { + top: 0; + position: absolute; + width: 100%; + background-color: $balanced; + opacity: 0.8; + z-index: 9999; + + .already-paid { + opacity: 1; + font-size: 15px; + padding: 8px; + color: #fff; + text-align: center; + position: relative; + } +} + +.font-size-28 { + font-size: 28px !important; +} + +// Style from old ionic +.button-bar { + display: -webkit-box; + display: -webkit-flex; + display: -moz-box; + display: -moz-flex; + display: -ms-flexbox; + display: flex; + -webkit-box-flex: 1; + -webkit-flex: 1; + -moz-box-flex: 1; + -moz-flex: 1; + -ms-flex: 1; + flex: 1; + width: 100%; +} + +.button-bar > .button { + -webkit-box-flex: 1; + -webkit-flex: 1; + -moz-box-flex: 1; + -moz-flex: 1; + -ms-flex: 1; + flex: 1; + display: block; + overflow: hidden; + + padding: 0 16px; + width: 0; + border-width: 1px 0px 1px 1px; + border-radius: 0; + text-align: center; + text-overflow: ellipsis; + white-space: nowrap; +} + +.button.button-brand { + background-color: #2a2c39; + border-color: #242530; + color: #fff; +} + +.button.button-brand.active, +.button.button-brand.activated { + background-color: #282a36; + box-shadow: inset 0 1px 4px rgba(0, 0, 0, 0.1); +} + +.button.button-assertive { + border-color: #973935; + background-color: #bd4742; + color: #fff; +} + +.button { + border-color: #b2b2b2; + background-color: #f8f8f8; + color: #444; + position: relative; + display: inline-block; + margin: 0; + padding: 0 12px; + min-width: 52px; + min-height: 47px; + border-width: 1px; + border-style: solid; + border-radius: 2px; + vertical-align: top; + text-align: center; + text-overflow: ellipsis; + font-size: 16px; + line-height: 42px; + cursor: pointer; +} + +.bar.bar-assertive { + border-color: #973935; + background-color: #bd4742; // background-image: linear-gradient(0deg, #973935, #973935 50%, transparent 50%); + color: #fff; +} + +.bar.bar-assertive .title { + color: #fff; +} + +.bar-header { + top: 0; + border-top-width: 0; + border-bottom-width: 1px; +} + +.bar.bar-brand { + background-image: none; + border-bottom: 2px solid #262733; + background-color: #2a2c39; + color: white !important; +} + +.bar.bar-brand .title { + color: white; +} + +.bar.bar-brand .button-icon { + color: white; +} + +.bar { + display: -webkit-box; + display: -webkit-flex; + display: -moz-box; + display: -moz-flex; + display: -ms-flexbox; + display: flex; + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + position: absolute; + right: 0; + left: 0; + z-index: 9; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + padding: 5px; + width: 100%; + height: 44px; + border-width: 0; + border-style: solid; + border-top: 1px solid transparent; + border-bottom: 1px solid #ddd; + background-color: white; + /* border-width: 1px will actually create 2 device pixels on retina */ + /* this nifty trick sets an actual 1px border on hi-res displays */ + background-size: 0; +} + +.bar-header { + top: 0; + border-top-width: 0; + border-bottom-width: 1px; + + .disable-user-behavior { + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + -webkit-touch-callout: none; + -webkit-tap-highlight-color: transparent; + -webkit-tap-highlight-color: transparent; + -webkit-user-drag: none; + -ms-touch-action: none; + -ms-content-zooming: none; + } +} + +.bar .buttons-left span { + margin-right: 5px; + display: inherit; +} + +.button-icon { + -webkit-transition: opacity 0.1s; + transition: opacity 0.1s; + padding: 0 6px; + min-width: initial; + border-color: transparent; + background: none; +} + +.bar .title { + position: absolute; + top: 0; + right: 0; + left: 0; + z-index: 0; + overflow: hidden; + margin: 0 10px; + min-width: 30px; + height: 43px; + text-align: center; + text-overflow: ellipsis; + white-space: nowrap; + font-size: 17px; + font-weight: 500; + line-height: 44px; +} + +.bar .button.button-icon:before, +.bar .button .icon:before, +.bar .button.icon:before, +.bar .button.icon-left:before, +.bar .button.icon-right:before { + padding-right: 2px; + padding-left: 2px; + font-size: 20px; + line-height: 32px; +} + +.bar .button.button-icon .icon:before, +.bar .button.button-icon:before, +.bar .button.button-icon.icon-left:before, +.bar .button.button-icon.icon-right:before { + vertical-align: top; + font-size: 32px; + line-height: 32px; +} + +.button-icon .icon:before, +.button-icon.icon:before { + font-size: 32px; +} + +.waves-effect { + position: relative; + cursor: pointer; + display: inline-block; + overflow: hidden; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + -webkit-tap-highlight-color: transparent; +} + +.bar .button-bar > .button, +.bar .buttons > .button { + min-height: 31px; + line-height: 32px; +} + +.bar .buttons span { + display: inline-block; +} + +.bar .buttons-left span { + margin-right: 5px; + display: inherit; +} diff --git a/packages/carrier-mobile-ionic/src/theme/variables.scss b/packages/carrier-mobile-ionic/src/theme/variables.scss new file mode 100644 index 0000000..0503c4e --- /dev/null +++ b/packages/carrier-mobile-ionic/src/theme/variables.scss @@ -0,0 +1,77 @@ +// Both RTL and LTR +$app-direction: multi; + +/** Ionic CSS Variables **/ +:root { + /** primary **/ + --ion-color-primary: #2a2c39; + --ion-color-primary-rgb: 42, 44, 57; + --ion-color-primary-contrast: #ffffff; + --ion-color-primary-contrast-rgb: 255, 255, 255; + --ion-color-primary-shade: #252732; + --ion-color-primary-tint: #3f414d; + + /** secondary **/ + --ion-color-secondary: #bd4742; + --ion-color-secondary-rgb: 189, 71, 66; + --ion-color-secondary-contrast: #ffffff; + --ion-color-secondary-contrast-rgb: 255, 255, 255; + --ion-color-secondary-shade: #a63e3a; + --ion-color-secondary-tint: #c45955; + + /** tertiary **/ + --ion-color-tertiary: #7044ff; + --ion-color-tertiary-rgb: 112, 68, 255; + --ion-color-tertiary-contrast: #ffffff; + --ion-color-tertiary-contrast-rgb: 255, 255, 255; + --ion-color-tertiary-shade: #633ce0; + --ion-color-tertiary-tint: #7e57ff; + + /** success **/ + --ion-color-success: #10dc60; + --ion-color-success-rgb: 16, 220, 96; + --ion-color-success-contrast: #000000; + --ion-color-success-contrast-rgb: 0, 0, 0; + --ion-color-success-shade: #0ec254; + --ion-color-success-tint: #28e070; + + /** warning **/ + --ion-color-warning: #ffce00; + --ion-color-warning-rgb: 255, 206, 0; + --ion-color-warning-contrast: #ffffff; + --ion-color-warning-contrast-rgb: 255, 255, 255; + --ion-color-warning-shade: #e0b500; + --ion-color-warning-tint: #ffd31a; + + /** danger **/ + --ion-color-danger: #f04141; + --ion-color-danger-rgb: 245, 61, 61; + --ion-color-danger-contrast: #ffffff; + --ion-color-danger-contrast-rgb: 255, 255, 255; + --ion-color-danger-shade: #d33939; + --ion-color-danger-tint: #f25454; + + /** dark **/ + --ion-color-dark: #222428; + --ion-color-dark-rgb: 34, 34, 34; + --ion-color-dark-contrast: #ffffff; + --ion-color-dark-contrast-rgb: 255, 255, 255; + --ion-color-dark-shade: #1e2023; + --ion-color-dark-tint: #383a3e; + + /** medium **/ + --ion-color-medium: #989aa2; + --ion-color-medium-rgb: 152, 154, 162; + --ion-color-medium-contrast: #ffffff; + --ion-color-medium-contrast-rgb: 255, 255, 255; + --ion-color-medium-shade: #86888f; + --ion-color-medium-tint: #a2a4ab; + + /** light **/ + --ion-color-light: #f4f5f8; + --ion-color-light-rgb: 244, 244, 244; + --ion-color-light-contrast: #000000; + --ion-color-light-contrast-rgb: 0, 0, 0; + --ion-color-light-shade: #d7d8da; + --ion-color-light-tint: #f5f6f9; +} diff --git a/packages/carrier-mobile-ionic/src/tsconfig.app.json b/packages/carrier-mobile-ionic/src/tsconfig.app.json new file mode 100644 index 0000000..b96cf3a --- /dev/null +++ b/packages/carrier-mobile-ionic/src/tsconfig.app.json @@ -0,0 +1,9 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "outDir": "../out-tsc/app", + "baseUrl": "./", + "module": "esnext" + }, + "exclude": ["test.ts", "**/*.spec.ts"] +} diff --git a/packages/carrier-mobile-ionic/src/tsconfig.spec.json b/packages/carrier-mobile-ionic/src/tsconfig.spec.json new file mode 100644 index 0000000..bd636b1 --- /dev/null +++ b/packages/carrier-mobile-ionic/src/tsconfig.spec.json @@ -0,0 +1,9 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "outDir": "../out-tsc/spec", + "baseUrl": "./" + }, + "files": ["test.ts"], + "include": ["polyfills.ts", "**/*.spec.ts", "**/*.d.ts"] +} diff --git a/packages/carrier-mobile-ionic/tsconfig.json b/packages/carrier-mobile-ionic/tsconfig.json new file mode 100644 index 0000000..61ac625 --- /dev/null +++ b/packages/carrier-mobile-ionic/tsconfig.json @@ -0,0 +1,55 @@ +{ + "extends": "../../tsconfig.base.json", + "compileOnSave": false, + "compilerOptions": { + "outDir": "./www/out-tsc", + "sourceMap": true, + "declaration": false, + "moduleResolution": "node", + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "importHelpers": true, + "noImplicitAny": false, + "preserveConstEnums": true, + "allowSyntheticDefaultImports": true, + "target": "es5", + "baseUrl": "./src", + "paths": { + "@angular/*": ["../node_modules/@angular/*"], + "@modules/server.common/*": ["../../common/src/*"], + "@modules/client.common.angular2/*": ["../../common-angular/src/*"], + "@modules/*": ["./modules/*"], + "@pyro/*": ["../../common/src/@pyro/*"], + "mongoose": ["../../common-angular/src/mongoose-placeholder"], + "typeorm": ["../../common-angular/src/typeorm-placeholder"], + "environment": ["./environments/environment"], + "core-js/es7/reflect": [ + "../../../node_modules/core-js/proposals/reflect-metadata" + ] + }, + "lib": ["es2017", "dom", "esnext"], + "noUnusedLocals": false, + "types": ["node", "reflect-metadata", "googlemaps", "jasmine"] + }, + "files": [ + "src/main.ts", + "src/polyfills.ts" + ], + "include": [ + "./*.ts", + "src/**/*.d.ts", + "../common/**/*d.ts", + "../common-angular/**/*d.ts" + ], + "exclude": ["node_modules"], + "angularCompilerOptions": { + "preserveWhitespaces": false, + "strictInjectionParameters": true, + "fullTemplateTypeCheck": true, + "strictTemplates": true, + "strictMetadataEmit": false + }, + "atom": { + "rewriteTsconfig": false + } +} diff --git a/packages/carrier-mobile-ionic/tslint.json b/packages/carrier-mobile-ionic/tslint.json new file mode 100644 index 0000000..35bce76 --- /dev/null +++ b/packages/carrier-mobile-ionic/tslint.json @@ -0,0 +1,122 @@ +{ + "extends": ["tslint:latest", "tslint-config-prettier"], + "rulesDirectory": ["node_modules/codelyzer"], + "linterOptions": { + "exclude": ["node_modules", "dist"] + }, + "rules": { + "no-implicit-dependencies": false, + "no-submodule-imports": false, + "trailing-comma": [ + false, + { + "multiline": "always", + "singleline": "never" + } + ], + "interface-name": [false, "always-prefix"], + "arrow-return-shorthand": true, + "callable-types": true, + "class-name": true, + "comment-format": [true, "check-space"], + "forin": true, + "import-blacklist": [true], + "ordered-imports": false, + "interface-over-type-literal": true, + "label-position": true, + "max-line-length": [true, 120], + "member-access": false, + "no-arg": true, + "no-console": [false], + "no-construct": true, + "no-debugger": true, + "no-duplicate-super": true, + "no-empty": false, + "no-empty-interface": false, + "no-eval": true, + "no-misused-new": true, + "no-non-null-assertion": false, + "no-shadowed-variable": true, + "no-string-literal": false, + "no-string-throw": true, + "no-switch-case-fall-through": true, + "no-unnecessary-initializer": true, + "no-use-before-declare": true, + "no-var-keyword": true, + "object-literal-sort-keys": false, + "prefer-const": true, + "object-literal-key-quotes": false, + "no-angle-bracket-type-assertion": false, + "member-ordering": false, + "no-consecutive-blank-lines": false, + "radix": true, + "semicolon": [true, "always"], + "triple-equals": [true, "allow-null-check"], + "typeof-compare": true, + "unified-signatures": true, + "variable-name": false, + "directive-selector": [true, "attribute", "ngx", "camelCase"], + "component-selector": [ + true, + "element", + [ + "e-cu", + "ngx", + "ea", + "es" + ], + "kebab-case" + ], + "no-attribute-parameter-decorator": true, + "no-forward-ref": true, + "no-input-rename": true, + "no-output-rename": true, + "only-arrow-functions": false, + "pipe-naming": [true, "camelCase", "my"], + "use-host-property-decorator": true, + "ban": [ + true, + "eval", + "fit", + "fdescribe", + { + "name": "$", + "message": "please don't" + } + ], + "max-classes-per-file": [false], + "import-destructuring-spacing": true, + "invoke-injectable": true, + "no-access-missing-member": true, + "templates-use-public": true, + "use-input-property-decorator": true, + "use-life-cycle-interface": true, + "use-output-property-decorator": true, + "use-pipe-transform-interface": true, + "quotemark": [true, "single", "avoid-escape"], + "eofline": true, + "import-spacing": true, + "indent": [true, "tabs"], + "no-trailing-whitespace": true, + "one-line": [false], + "typedef-whitespace": [ + true, + { + "call-signature": "nospace", + "index-signature": "nospace", + "parameter": "nospace", + "property-declaration": "nospace", + "variable-declaration": "nospace" + } + ], + "whitespace": [ + true, + "check-branch", + "check-decl", + "check-operator", + "check-separator", + "check-type" + ], + "no-namespace": false + } +} diff --git a/packages/common-angular/.dockerignore b/packages/common-angular/.dockerignore new file mode 100644 index 0000000..3438721 --- /dev/null +++ b/packages/common-angular/.dockerignore @@ -0,0 +1,10 @@ +.git +.gitignore +.gitmodules +README.md +docker +node_modules +tmp +build +dist +.env diff --git a/packages/common-angular/.gitignore b/packages/common-angular/.gitignore new file mode 100644 index 0000000..44cb20f --- /dev/null +++ b/packages/common-angular/.gitignore @@ -0,0 +1,13 @@ +**/*.js +**/*.js.map +lib/**/*.js +lib/**/*.js.map +locale/**/*.js +locale/**/*.js.map +routers/**/*.js +routers/**/*.js.map +node_modules +**/*.d.ts +**/*.d.ts.map +build +dist \ No newline at end of file diff --git a/packages/common-angular/LICENSE.md b/packages/common-angular/LICENSE.md new file mode 100644 index 0000000..24a5e6a --- /dev/null +++ b/packages/common-angular/LICENSE.md @@ -0,0 +1,41 @@ +# License + +Copyright © 2016-present, Ever Co. LTD. All rights reserved. + +This software is available under different licenses + +### _Ever Platform Community Edition_ License for Shared Modules + +If you decide to choose the Ever Platform Community Edition License for Shared Modules, you must comply with the following terms: + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License, version 3, +as published by the Free Software Foundation. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . + +[GNU General Public License v3.0](https://www.gnu.org/licenses/gpl-3.0.txt) + +### _Ever Platform Enterprise_ License + +Alternatively, commercial versions of the software must be used in accordance with the terms and conditions of separate written agreement between you and Ever Co. LTD. + +For more information about Ever Platform Enterprise License please contact . + +#### The default Ever Platform license, without a valid Ever Platform Enterprise License agreement, is the Ever Platform Community Edition License. + +## Credits + +Please see [CREDITS.md](CREDITS.md) file for a list of libraries and software included in this program and information about licenses. + +## Trademarks + +**Ever**® is a registered trademark of [Ever Co. LTD](https://ever.co). +The trademark may only be used with the written permission of Ever Co. LTD. and may not be used to promote or otherwise market competitive products or services. +All other brand and product names are trademarks, registered trademarks or service marks of their respective holders. diff --git a/packages/common-angular/README.md b/packages/common-angular/README.md new file mode 100644 index 0000000..d7117ed --- /dev/null +++ b/packages/common-angular/README.md @@ -0,0 +1 @@ +# Angular Clients Shared Library diff --git a/packages/common-angular/gpl-3.0.txt b/packages/common-angular/gpl-3.0.txt new file mode 100644 index 0000000..94a9ed0 --- /dev/null +++ b/packages/common-angular/gpl-3.0.txt @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/packages/common-angular/package.json b/packages/common-angular/package.json new file mode 100644 index 0000000..e1e1434 --- /dev/null +++ b/packages/common-angular/package.json @@ -0,0 +1,58 @@ +{ + "name": "@ever-platform/common-angular", + "description": "Ever Platform Shared Angular Core", + "license": "AGPL-3.0", + "version": "0.4.3", + "homepage": "https://ever.co", + "repository": { + "type": "git", + "url": "https://github.com/ever-co/ever-demand.git" + }, + "bugs": { + "url": "https://github.com/ever-co/ever-demand/issues" + }, + "private": false, + "author": { + "name": "Ever Co. LTD", + "email": "ever@ever.co", + "url": "https://ever.co" + }, + "scripts": { + "watch": "tsc -p ./tsconfig.build.json -w", + "build": "rimraf build && tsc -p ./tsconfig.build.json", + "lint": "tslint --fix --project ./", + "test": "jest --config ./jest.config.js" + }, + "publishConfig": { + "access": "public" + }, + "dependencies": { + "@angular/animations": "^13.1.0", + "@angular/cdk": "^13.1.0", + "@angular/common": "^13.1.0", + "@angular/compiler": "^13.1.0", + "@angular/core": "^13.1.0", + "@angular/forms": "^13.1.0", + "@angular/language-service": "^13.1.0", + "@angular/localize": "^13.1.0", + "@angular/platform-browser": "^13.1.0", + "@angular/platform-browser-dynamic": "^13.1.0", + "@angular/router": "^13.1.0", + "@angular/service-worker": "^13.1.0", + "@ever-platform/common": "^0.4.3", + "@ngx-translate/core": "^13.0.0", + "@ngx-translate/http-loader": "^6.0.0", + "tslib": "^2.3.1", + "underscore": "^1.13.1", + "underscore.string": "^3.3.5", + "zone.js": "~0.11.4" + }, + "devDependencies": { + "@types/node": "16.11.0", + "lint": "^0.7.0", + "rimraf": "^3.0.2", + "ts-node": "~10.3.0", + "typescript": "~4.5.3" + }, + "snyk": false +} diff --git a/packages/common-angular/src/common.module.ts b/packages/common-angular/src/common.module.ts new file mode 100644 index 0000000..ef8d2c2 --- /dev/null +++ b/packages/common-angular/src/common.module.ts @@ -0,0 +1,21 @@ +import { ModuleWithProviders, NgModule } from '@angular/core'; +import { CommonLibModule } from './lib'; +import { RoutersModule } from './routers'; +import { API_URL } from './lib/router'; +import { LocaleModule } from './locale/locale.module'; + +interface CommonModuleConfig { + apiUrl: string; +} + +@NgModule({ + imports: [CommonLibModule, RoutersModule, LocaleModule], +}) +export class CommonModule { + static forRoot(options: CommonModuleConfig): ModuleWithProviders { + return { + ngModule: CommonModule, + providers: [{ provide: API_URL, useValue: options.apiUrl }], + }; + } +} diff --git a/packages/common-angular/src/components/ngx-barcode/ngx-barcode.component.ts b/packages/common-angular/src/components/ngx-barcode/ngx-barcode.component.ts new file mode 100644 index 0000000..c6e6e5e --- /dev/null +++ b/packages/common-angular/src/components/ngx-barcode/ngx-barcode.component.ts @@ -0,0 +1,177 @@ +// adapted from https://github.com/yobryon/ngx-barcode (MIT © Bryon Williams) +import { + Component, + Input, + OnChanges, + ViewChild, + Renderer2, + ElementRef, +} from '@angular/core'; + +declare var require: any; + +const jsbarcode = require('jsbarcode'); + +@Component({ + selector: 'ngx-barcode', + template: `
`, + styles: [], +}) +export class NgxBarcodeComponent implements OnChanges { + @Input('bc-element-type') + elementType: 'svg' | 'img' | 'canvas' = 'svg'; + + @Input('bc-class') + cssClass = 'barcode'; + + @Input('bc-format') + format: + | '' + | 'CODE128' + | 'CODE128A' + | 'CODE128B' + | 'CODE128C' + | 'EAN' + | 'UPC' + | 'EAN8' + | 'EAN5' + | 'EAN2' + | 'CODE39' + | 'ITF14' + | 'MSI' + | 'MSI10' + | 'MSI11' + | 'MSI1010' + | 'MSI1110' + | 'pharmacode' + | 'codabar' = 'CODE128'; + + @Input('bc-line-color') + lineColor = '#000000'; + + @Input('bc-width') + width = 2; + + @Input('bc-height') + height = 100; + + @Input('bc-display-value') + displayValue = false; + + @Input('bc-font-options') + fontOptions = ''; + + @Input('bc-font') + font = 'monospace'; + + @Input('bc-text-align') + textAlign = 'center'; + + @Input('bc-text-position') + textPosition = 'bottom'; + + @Input('bc-text-margin') + textMargin = 2; + + @Input('bc-font-size') + fontSize = 20; + + @Input('bc-background') + background = '#ffffff'; + + @Input('bc-margin') + margin; + + @Input('bc-margin-top') + marginTop; + + @Input('bc-margin-bottom') + marginBottom; + + @Input('bc-margin-left') + marginLeft; + + @Input('bc-margin-right') + marginRight; + + @Input('bc-value') + value = ''; + + @ViewChild('bcElement', { static: true }) + bcElement: ElementRef; + + @Input('bc-valid') + valid: () => boolean = () => true; + + get options() { + const options = { + format: this.format, + lineColor: this.lineColor, + width: this.width, + height: this.height, + displayValue: this.displayValue, + fontOptions: this.fontOptions, + font: this.font, + textAlign: this.textAlign, + textPosition: this.textPosition, + textMargin: this.textMargin, + fontSize: this.fontSize, + background: this.background, + valid: this.valid, + }; + + if (this.margin) { + options['margin'] = this.margin; + } + + if (this.marginTop) { + options['marginTop'] = this.marginTop; + } + + if (this.marginBottom) { + options['marginBottom'] = this.marginBottom; + } + + if (this.marginLeft) { + options['marginLeft'] = this.marginLeft; + } + + if (this.marginRight) { + options['marginRight'] = this.marginRight; + } + + return options; + } + + constructor(private renderer: Renderer2) {} + + ngOnChanges() { + this.createBarcode(); + } + + createBarcode() { + if (!this.value) { + return; + } + let element: Element; + switch (this.elementType) { + case 'img': + element = this.renderer.createElement('img'); + break; + case 'canvas': + element = this.renderer.createElement('canvas'); + break; + case 'svg': + default: + element = this.renderer.createElement('svg', 'svg'); + } + + jsbarcode(element, this.value, this.options); + + for (const node of this.bcElement.nativeElement.childNodes) { + this.renderer.removeChild(this.bcElement.nativeElement, node); + } + + this.renderer.appendChild(this.bcElement.nativeElement, element); + } +} diff --git a/packages/common-angular/src/components/ngx-barcode/ngx-barcode.module.ts b/packages/common-angular/src/components/ngx-barcode/ngx-barcode.module.ts new file mode 100644 index 0000000..b16885a --- /dev/null +++ b/packages/common-angular/src/components/ngx-barcode/ngx-barcode.module.ts @@ -0,0 +1,18 @@ +// adapted from https://github.com/yobryon/ngx-barcode (MIT © Bryon Williams) + +import { NgModule, ModuleWithProviders } from '@angular/core'; +import { NgxBarcodeComponent } from './ngx-barcode.component'; + +@NgModule({ + imports: [], + declarations: [NgxBarcodeComponent], + exports: [NgxBarcodeComponent], +}) +export class NgxBarcodeModule { + static forRoot(): ModuleWithProviders { + return { + ngModule: NgxBarcodeModule, + providers: [], + }; + } +} diff --git a/packages/common-angular/src/index.ts b/packages/common-angular/src/index.ts new file mode 100644 index 0000000..6a34a93 --- /dev/null +++ b/packages/common-angular/src/index.ts @@ -0,0 +1,2 @@ +export { CommonModule } from './common.module'; +export { RoutersModule } from './routers/routers.module'; diff --git a/packages/common-angular/src/lib/index.ts b/packages/common-angular/src/lib/index.ts new file mode 100644 index 0000000..78c7518 --- /dev/null +++ b/packages/common-angular/src/lib/index.ts @@ -0,0 +1 @@ +export { CommonLibModule } from './lib.module'; diff --git a/packages/common-angular/src/lib/lib.module.ts b/packages/common-angular/src/lib/lib.module.ts new file mode 100644 index 0000000..8a58534 --- /dev/null +++ b/packages/common-angular/src/lib/lib.module.ts @@ -0,0 +1,16 @@ +import { NgModule } from '@angular/core'; +import { io } from "socket.io-client"; +import { RouterFactory, RoutersService } from './router'; +import { SOCKET_IO } from './socket.service'; +import { SocketFactory } from './socket.factory'; + +@NgModule({ + providers: [ + { provide: SOCKET_IO, useValue: io }, + SocketFactory, + RouterFactory, + RoutersService, + ], + exports: [], +}) +export class CommonLibModule {} diff --git a/packages/common-angular/src/lib/router/ObservableRequest.ts b/packages/common-angular/src/lib/router/ObservableRequest.ts new file mode 100644 index 0000000..07eabe2 --- /dev/null +++ b/packages/common-angular/src/lib/router/ObservableRequest.ts @@ -0,0 +1,36 @@ +import { Observable } from 'rxjs'; +import { v4 as uuid } from 'uuid'; +import { exhaustMap, first, switchMap } from 'rxjs/operators'; +import { ObservableResponseSubscriber } from './ObservableResponseSubscriber'; +import { Socket } from '../socket.service'; + +export class ObservableRequest { + private callId: string; + + constructor( + private readonly socket: Socket, + private readonly event: string, + private readonly args: any[] + ) { + this.callId = uuid(); + } + + run(): Observable { + return this.socket.connection.pipe( + first(), + switchMap(() => { + this.socket.emit(this.event, ...this.args, this.callId); + + return this.socket.connection.pipe( + exhaustMap(() => { + const subscriber = new ObservableResponseSubscriber( + this.socket, + this.callId + ); + return subscriber.getResponse(); + }) + ); + }) + ); + } +} diff --git a/packages/common-angular/src/lib/router/ObservableResponseSubscriber.ts b/packages/common-angular/src/lib/router/ObservableResponseSubscriber.ts new file mode 100644 index 0000000..1849166 --- /dev/null +++ b/packages/common-angular/src/lib/router/ObservableResponseSubscriber.ts @@ -0,0 +1,61 @@ +import { v4 as uuid } from 'uuid'; +import { exhaustMap, merge, share, takeUntil } from 'rxjs/operators'; +import { Socket } from '../socket.service'; +import { Observable, throwError } from 'rxjs'; + +export class ObservableResponseSubscriber { + private readonly response: Observable; + private readonly subscriptionId: string; + + constructor( + private readonly socket: Socket, + private readonly callId: string + ) { + this.subscriptionId = uuid(); + this.response = this.createResponseObservable(); + } + + getResponse(): Observable { + return this.response; + } + + private createResponseObservable(): Observable { + return Observable.create(() => { + this.socket.emit(`${this.callId}_subscribe`, this.subscriptionId); + + return () => { + this.socket.emit(`${this.subscriptionId}_unsubscribe`); + }; + }).pipe( + merge(this.nexts(), this.errors()), + takeUntil(this.completes()), + share() + ); + } + + private nexts() { + return this.socket.fromEvent(`${this.subscriptionId}_next`); + } + + private errors() { + return this.socket + .fromEvent(`${this.subscriptionId}_error`) + .pipe( + exhaustMap((error) => throwError(this.deserializeError(error))) + ); + } + + private completes() { + return this.socket.fromEvent(`${this.subscriptionId}_complete`); + } + + private deserializeError(error) { + if (error.__isError__) { + const _error = new Error(error.message); + _error.name = error.name; + return _error; + } else { + return error; + } + } +} diff --git a/packages/common-angular/src/lib/router/Request.ts b/packages/common-angular/src/lib/router/Request.ts new file mode 100644 index 0000000..a9b3d35 --- /dev/null +++ b/packages/common-angular/src/lib/router/Request.ts @@ -0,0 +1,31 @@ +import { Socket } from '../socket.service'; + +export class Request { + constructor( + private readonly socket: Socket, + private readonly event: string, + private readonly args: any[] + ) {} + + async run(): Promise { + return new Promise((resolve, reject) => { + this.socket.emit(this.event, ...this.args, (err, res) => { + if (err != null) { + reject(this.deserializeError(err)); + } else { + resolve(res); + } + }); + }); + } + + private deserializeError(error) { + if (error.__isError__) { + const _error = new Error(error.message); + _error.name = error.name; + return _error; + } else { + return error; + } + } +} diff --git a/packages/common-angular/src/lib/router/index.ts b/packages/common-angular/src/lib/router/index.ts new file mode 100644 index 0000000..57de638 --- /dev/null +++ b/packages/common-angular/src/lib/router/index.ts @@ -0,0 +1,3 @@ +export * from './router.factory'; +export * from './router.service'; +export * from './routers.service'; diff --git a/packages/common-angular/src/lib/router/router.factory.ts b/packages/common-angular/src/lib/router/router.factory.ts new file mode 100644 index 0000000..1471fb3 --- /dev/null +++ b/packages/common-angular/src/lib/router/router.factory.ts @@ -0,0 +1,22 @@ +import { Inject, Injectable } from '@angular/core'; +import { API_URL, Router } from './router.service'; +import { RoutersService } from './routers.service'; +import { SocketFactory } from '../socket.factory'; + +@Injectable() +export class RouterFactory { + constructor( + private readonly socketFactory: SocketFactory, + private readonly routersService: RoutersService, + @Inject(API_URL) private readonly apiUrl: string + ) {} + + create(name: string): Router { + return new Router( + this.socketFactory, + this.routersService, + name, + this.apiUrl + ); + } +} diff --git a/packages/common-angular/src/lib/router/router.service.ts b/packages/common-angular/src/lib/router/router.service.ts new file mode 100644 index 0000000..0db7019 --- /dev/null +++ b/packages/common-angular/src/lib/router/router.service.ts @@ -0,0 +1,35 @@ +import { Socket } from '../socket.service'; +import { Observable } from 'rxjs'; +import { ObservableRequest } from './ObservableRequest'; +import { Request } from './Request'; +import { RoutersService } from './routers.service'; +import { SocketFactory } from '../socket.factory'; +import { InjectionToken } from '@angular/core'; + +export const API_URL = new InjectionToken('api_url'); + +export class Router { + private readonly socket: Socket; + + constructor( + socketFactory: SocketFactory, + private readonly routersService: RoutersService, + private readonly name: string, + private readonly apiUrl: string + ) { + this.socket = socketFactory.build(`${apiUrl}/${name}`); + this.routersService.sockets.next(this.socket); + + console.log(`Router named ${name} created!`); + } + + runAndObserve(methodName: string, ...args: any[]): Observable { + const request = new ObservableRequest(this.socket, methodName, args); + return request.run(); + } + + run(methodName: string, ...args: any[]): Promise { + const request = new Request(this.socket, methodName, args); + return request.run(); + } +} diff --git a/packages/common-angular/src/lib/router/router.spec.ts b/packages/common-angular/src/lib/router/router.spec.ts new file mode 100644 index 0000000..fd990ac --- /dev/null +++ b/packages/common-angular/src/lib/router/router.spec.ts @@ -0,0 +1,10 @@ +import 'jasmine'; +import { TestBed } from '@angular/core/testing'; + +describe('router', () => { + beforeEach(() => + TestBed.configureTestingModule({ + providers: [], + }) + ); +}); diff --git a/packages/common-angular/src/lib/router/routers.service.ts b/packages/common-angular/src/lib/router/routers.service.ts new file mode 100644 index 0000000..7a95b72 --- /dev/null +++ b/packages/common-angular/src/lib/router/routers.service.ts @@ -0,0 +1,59 @@ +import { Observable, combineLatest, of, ReplaySubject } from 'rxjs'; +import { map, mergeAll, publishReplay, refCount, scan } from 'rxjs/operators'; +import { ConnectionStatus, Socket } from '../socket.service'; + +export class RoutersService { + /** + * routers would register themselves here + * Note: routers can be initialized later so we need observable here + * + * @type {ReplaySubject} + * @memberof RoutersService + */ + public sockets: ReplaySubject = new ReplaySubject(); + + public isConnectionProblem: Observable; + + constructor() { + /** + * Here We get observable of routers, then we map it to observable of connectionStatus observables, + * Each time router registered we combineLatest it's isSpecificErrorObs observable with the isAnyErrorObs + * observable which initializes with the value of Observable.of(false) + * By checking if any of the observables next's true, the result of the combineLatest becomes the new isAnyErrorObs. + * At the end we do mergeAll, and forget about the fact there is any dependency on the routers observable by that. + * Then comes publishReplay and refCount which makes sure we won't have to run this whole crazy code + * each time some one subscribes isConnectionProblem. + */ + this.isConnectionProblem = this.sockets.pipe( + map((socket) => { + return socket.connectionStatus.pipe( + map( + (status) => + status === ConnectionStatus.Disconnected || + status === ConnectionStatus.ConnectError + ) + ); + }), + scan( + ( + isAnyErrorObs: Observable, + isSpecificErrorObs: Observable + ) => { + return combineLatest( + isAnyErrorObs, + isSpecificErrorObs + ).pipe( + map( + ([isAnyError, isSpecificError]) => + isAnyError || isSpecificError + ) + ); + }, + of(false) + ), + mergeAll(), + publishReplay(1), + refCount() + ); + } +} diff --git a/packages/common-angular/src/lib/socket.factory.ts b/packages/common-angular/src/lib/socket.factory.ts new file mode 100644 index 0000000..d385bc7 --- /dev/null +++ b/packages/common-angular/src/lib/socket.factory.ts @@ -0,0 +1,16 @@ +import { Socket, SOCKET_IO } from './socket.service'; +import { Inject, Injectable } from '@angular/core'; +import { io as _io } from 'socket.io-client'; + +@Injectable({ + providedIn: 'root' +}) +export class SocketFactory { + constructor( + @Inject(SOCKET_IO) private readonly io: typeof _io + ) {} + + build(socketUrl: string): Socket { + return new Socket(socketUrl, this.io); + } +} diff --git a/packages/common-angular/src/lib/socket.service.ts b/packages/common-angular/src/lib/socket.service.ts new file mode 100644 index 0000000..f992cdc --- /dev/null +++ b/packages/common-angular/src/lib/socket.service.ts @@ -0,0 +1,146 @@ +import { InjectionToken } from '@angular/core'; +import { Observable, of } from 'rxjs'; +import { + delay, + filter, + map, + mergeWith, + publishReplay, + refCount, + share, +} from 'rxjs/operators'; + +import { io as _io, Socket as SocketIOClientSocket } from 'socket.io-client'; + +export enum ConnectionStatus { + NotConnected, + Disconnected, + Connected, + ConnectError, +} + +export const SOCKET_IO = new InjectionToken('socket.io'); + +export class Socket { + public subscribersCounter: number = 0; + public ioSocket: SocketIOClientSocket; + + public connectionStatus: Observable = of( + ConnectionStatus.NotConnected + ).pipe( + mergeWith( + this.fromEvent('connect').pipe( + map(() => ConnectionStatus.Connected) + ), + this.fromEvent('disconnect').pipe( + map(() => ConnectionStatus.Disconnected) + ), + this.fromEvent('connect_error').pipe( + map(() => ConnectionStatus.ConnectError) + ) + ), + publishReplay(1), + refCount() + ); + + public connection: Observable< + ConnectionStatus + > = this.connectionStatus.pipe( + filter((status) => status === ConnectionStatus.Connected) + ); + + public disconnection: Observable< + ConnectionStatus + > = this.connectionStatus.pipe( + filter((status) => status === ConnectionStatus.Disconnected) + ); + + public connectionErrors: Observable< + ConnectionStatus + > = this.connectionStatus.pipe( + filter((status) => status === ConnectionStatus.ConnectError) + ); + + constructor( + private readonly socketUrl: string, + private readonly io: typeof _io + ) { + console.log(`Socket with url ${socketUrl} created!`); + + const ioCallable = this.io; + + this.ioSocket = ioCallable(`${this.socketUrl}`, { + reconnection: false, + }); + + this.connectionStatus + .pipe( + filter( + (status) => + status === ConnectionStatus.Disconnected || + status === ConnectionStatus.ConnectError + ), + delay(1000) + ) + .subscribe(() => { + this.connect(); + }); + } + + on(eventName: string, callback: () => void) { + this.ioSocket.on(eventName, callback); + } + + once(eventName: string, callback: () => void) { + this.ioSocket.once(eventName, callback); + } + + connect() { + return this.ioSocket.connect(); + } + + disconnect(close?: any) { + return this.ioSocket.disconnect.apply(this.ioSocket, arguments); + } + + emit(eventName: string, ...args: any[]) { + return this.ioSocket.emit.apply(this.ioSocket, arguments); + } + + removeListener(eventName: string, callback?: () => void) { + return this.ioSocket.removeListener.apply( + this.ioSocket, + arguments + ); + } + + removeAllListeners(eventName?: string) { + return this.ioSocket.removeAllListeners.apply( + this.ioSocket, + arguments + ); + } + + /** + * create an Observable from an event + * + * @template T + * @param {string} eventName + * @returns {Observable} + * @memberof Socket + */ + fromEvent(eventName: string): Observable { + this.subscribersCounter++; + + return Observable.create((observer: any) => { + this.ioSocket.on(eventName, (data: T) => { + observer.next(data); + }); + return () => { + if (this.subscribersCounter === 1) { + this.ioSocket.removeListener(eventName); + } + }; + }).pipe(share()); + } +} diff --git a/packages/common-angular/src/lib/socket.spec.ts b/packages/common-angular/src/lib/socket.spec.ts new file mode 100644 index 0000000..fd990ac --- /dev/null +++ b/packages/common-angular/src/lib/socket.spec.ts @@ -0,0 +1,10 @@ +import 'jasmine'; +import { TestBed } from '@angular/core/testing'; + +describe('router', () => { + beforeEach(() => + TestBed.configureTestingModule({ + providers: [], + }) + ); +}); diff --git a/packages/common-angular/src/locale/image-locales.service.ts b/packages/common-angular/src/locale/image-locales.service.ts new file mode 100644 index 0000000..2944d63 --- /dev/null +++ b/packages/common-angular/src/locale/image-locales.service.ts @@ -0,0 +1 @@ +export class ImageLocalesService {} diff --git a/packages/common-angular/src/locale/locale.module.ts b/packages/common-angular/src/locale/locale.module.ts new file mode 100644 index 0000000..2beed50 --- /dev/null +++ b/packages/common-angular/src/locale/locale.module.ts @@ -0,0 +1,18 @@ +import { NgModule } from '@angular/core'; +import { ProductLocalesService } from './product-locales.service'; +import { + TranslateService, + TranslateModule, + TranslateLoader, + TranslateFakeLoader, +} from '@ngx-translate/core'; + +@NgModule({ + imports: [ + TranslateModule.forRoot({ + loader: { provide: TranslateLoader, useClass: TranslateFakeLoader }, + }), + ], + providers: [ProductLocalesService, TranslateService], +}) +export class LocaleModule {} diff --git a/packages/common-angular/src/locale/product-locales.service.ts b/packages/common-angular/src/locale/product-locales.service.ts new file mode 100644 index 0000000..b8b44d9 --- /dev/null +++ b/packages/common-angular/src/locale/product-locales.service.ts @@ -0,0 +1,134 @@ +import { Injectable } from '@angular/core'; +import { TranslateService } from '@ngx-translate/core'; +import { ILocaleMember } from '@modules/server.common/interfaces/ILocale'; + +class ProductTransientViewModel { + public title: string; + public description: string; + + constructor() { + this.title = ''; + this.description = ''; + } +} + +@Injectable() +export class ProductLocalesService { + private readonly _defaultLang: string = 'en-US'; + private readonly _defaultLocale: string = 'en-US'; + private _productTransientProperties = new ProductTransientViewModel(); + + public currentLocale: string; + + constructor(private readonly _translateService: TranslateService) {} + + public get isServiceStateValid() { + return ( + this._productTransientProperties.title !== '' && + this._productTransientProperties.description !== '' + ); + } + + /** + * Get current product member and returns the translation equivalent. + * @param member Current product member to pass. + * @param langChoice Optional language of choice + * that function use to translate, if not specified just use the current context language. + * @returns String value of the current member translation. + */ + getTranslate(member: ILocaleMember[], langChoice?: string): string { + if (!member || member.length <= 0) { + return ''; + } + + const productMember: ILocaleMember = + member.find((x) => + x.locale.startsWith( + langChoice || this._translateService.currentLang + ) + ) || + // Use default lang + member.find((x) => x.locale.startsWith(this._defaultLang)) || + // Or first + member[0]; + // this is for pictures, they support url instead of value + const value: string = productMember.value || productMember['url']; + + return value; + } + + getMemberValue(productMember: ILocaleMember[]) { + let valueMember = this._getProductLocaleMember(productMember); + + if (valueMember === undefined) { + // Use default + const useDefaultLocale = true; + valueMember = this._getProductLocaleMember( + productMember, + useDefaultLocale + ); + } + + if (valueMember === undefined && productMember) { + // Or use first + valueMember = productMember[0]; + } + + // When we create new product it has no members at all, because of that we use empty string for this case + return valueMember ? valueMember.value : ''; + } + + setMemberValue(memberKey: string, memberValue: string) { + this._productTransientProperties[memberKey] = memberValue; + } + + assignPropertyValue(member: ILocaleMember[], memberKey: string) { + const memberValue = member.find((m) => m.locale === this.currentLocale); + const memberValueToAssign = this._productTransientProperties[memberKey]; + + if (memberValue !== undefined) { + memberValue.value = memberValueToAssign; + } else { + const locale: ILocaleMember = { + locale: this.currentLocale, + value: memberValueToAssign, + }; + member.push(locale); + } + } + + takeSelectedLang(lang: string) { + let translateLang = this._defaultLocale; + switch (lang) { + case 'en-US': + translateLang = 'en-US'; + break; + case 'he-IL': + translateLang = 'he-IL'; + break; + case 'ru-RU': + translateLang = 'ru-RU'; + break; + case 'bg-BG': + translateLang = 'bg-BG'; + break; + case 'es-ES': + translateLang = 'es-ES'; + break; + } + return translateLang; + } + + private _getProductLocaleMember( + productMember: ILocaleMember[], + defaultLocale?: boolean + ) { + if (productMember) { + return productMember.find( + (t) => + t.locale === + (defaultLocale ? this._defaultLocale : this.currentLocale) + ); + } + } +} diff --git a/packages/common-angular/src/mongoose-placeholder.ts b/packages/common-angular/src/mongoose-placeholder.ts new file mode 100644 index 0000000..ff789e8 --- /dev/null +++ b/packages/common-angular/src/mongoose-placeholder.ts @@ -0,0 +1,30 @@ +export type SchemaDefinition = any; + +export class Schema { + constructor(...args) {} + + index( + fields: any, + options?: { + expires?: string; + [other: string]: any; + } + ) { + return this; + } + + pre() { + return; + } + + indexes(): any[] { + return []; + } +} + +export const Types: any = {}; + +export default { + Schema, + Types, +}; diff --git a/packages/common-angular/src/pipes/capitalize.pipe.ts b/packages/common-angular/src/pipes/capitalize.pipe.ts new file mode 100644 index 0000000..08e574f --- /dev/null +++ b/packages/common-angular/src/pipes/capitalize.pipe.ts @@ -0,0 +1,10 @@ +import { Pipe, PipeTransform } from '@angular/core'; + +@Pipe({ name: 'ngxCapitalize' }) +export class CapitalizePipe implements PipeTransform { + transform(input: string): string { + return input && input.length + ? input.charAt(0).toUpperCase() + input.slice(1).toLowerCase() + : input; + } +} diff --git a/packages/common-angular/src/pipes/index.ts b/packages/common-angular/src/pipes/index.ts new file mode 100644 index 0000000..7aeecfb --- /dev/null +++ b/packages/common-angular/src/pipes/index.ts @@ -0,0 +1,7 @@ +export * from './capitalize.pipe'; +export * from './plural.pipe'; +export * from './round.pipe'; +export * from './timing.pipe'; +export * from './number-with-commas.pipe'; +export * from './safe.pipe'; +export * from './replace.pipe'; diff --git a/packages/common-angular/src/pipes/number-with-commas.pipe.ts b/packages/common-angular/src/pipes/number-with-commas.pipe.ts new file mode 100644 index 0000000..2a91b3a --- /dev/null +++ b/packages/common-angular/src/pipes/number-with-commas.pipe.ts @@ -0,0 +1,8 @@ +import { Pipe, PipeTransform } from '@angular/core'; + +@Pipe({ name: 'myNumberWithCommas' }) +export class NumberWithCommasPipe implements PipeTransform { + transform(input: number): string { + return new Intl.NumberFormat().format(input); + } +} diff --git a/packages/common-angular/src/pipes/pipes.module.ts b/packages/common-angular/src/pipes/pipes.module.ts new file mode 100644 index 0000000..1f55dd9 --- /dev/null +++ b/packages/common-angular/src/pipes/pipes.module.ts @@ -0,0 +1,31 @@ +import { NgModule } from '@angular/core'; +import { SafePipe } from './safe.pipe'; +import { ReplacePipe } from './replace.pipe'; +import { CapitalizePipe } from './capitalize.pipe'; +import { PluralPipe } from './plural.pipe'; +import { RoundPipe } from './round.pipe'; +import { TimingPipe } from './timing.pipe'; +import { NumberWithCommasPipe } from './number-with-commas.pipe'; + +@NgModule({ + exports: [ + SafePipe, + ReplacePipe, + CapitalizePipe, + PluralPipe, + RoundPipe, + TimingPipe, + NumberWithCommasPipe, + ], + imports: [], + declarations: [ + SafePipe, + ReplacePipe, + CapitalizePipe, + PluralPipe, + RoundPipe, + TimingPipe, + NumberWithCommasPipe, + ], +}) +export class PipesModule {} diff --git a/packages/common-angular/src/pipes/plural.pipe.ts b/packages/common-angular/src/pipes/plural.pipe.ts new file mode 100644 index 0000000..f1aeaff --- /dev/null +++ b/packages/common-angular/src/pipes/plural.pipe.ts @@ -0,0 +1,13 @@ +import { Pipe, PipeTransform } from '@angular/core'; + +@Pipe({ name: 'ngxPlural' }) +export class PluralPipe implements PipeTransform { + transform(input: number, label: string, pluralLabel: string = ''): string { + input = input || 0; + return input === 1 + ? `${input} ${label}` + : pluralLabel + ? `${input} ${pluralLabel}` + : `${input} ${label}s`; + } +} diff --git a/packages/common-angular/src/pipes/replace.pipe.ts b/packages/common-angular/src/pipes/replace.pipe.ts new file mode 100644 index 0000000..ce01556 --- /dev/null +++ b/packages/common-angular/src/pipes/replace.pipe.ts @@ -0,0 +1,20 @@ +import { Pipe, PipeTransform } from '@angular/core'; + +@Pipe({ name: 'myReplacePipe' }) +export class ReplacePipe implements PipeTransform { + transform( + value: string, + searchValue: string, + replaceValue: string + ): string { + if ( + typeof value !== typeof 'string' || + typeof searchValue !== typeof 'string' || + typeof replaceValue !== typeof 'string' + ) { + throw Error('All pipe parameters should be strings!'); + } + + return value.replace(new RegExp(searchValue, 'g'), replaceValue); + } +} diff --git a/packages/common-angular/src/pipes/round.pipe.ts b/packages/common-angular/src/pipes/round.pipe.ts new file mode 100644 index 0000000..8dea107 --- /dev/null +++ b/packages/common-angular/src/pipes/round.pipe.ts @@ -0,0 +1,8 @@ +import { Pipe, PipeTransform } from '@angular/core'; + +@Pipe({ name: 'ngxRound' }) +export class RoundPipe implements PipeTransform { + transform(input: number): number { + return Math.round(input); + } +} diff --git a/packages/common-angular/src/pipes/safe.pipe.ts b/packages/common-angular/src/pipes/safe.pipe.ts new file mode 100644 index 0000000..93e6a21 --- /dev/null +++ b/packages/common-angular/src/pipes/safe.pipe.ts @@ -0,0 +1,36 @@ +import { Pipe, PipeTransform } from '@angular/core'; +import { + DomSanitizer, + SafeHtml, + SafeResourceUrl, + SafeScript, + SafeStyle, + SafeUrl, +} from '@angular/platform-browser'; + +@Pipe({ + name: 'safe', +}) +export class SafePipe implements PipeTransform { + constructor(protected sanitizer: DomSanitizer) {} + + transform( + value: any, + type: string + ): SafeHtml | SafeStyle | SafeScript | SafeUrl | SafeResourceUrl { + switch (type) { + case 'html': + return this.sanitizer.bypassSecurityTrustHtml(value); + case 'style': + return this.sanitizer.bypassSecurityTrustStyle(value); + case 'script': + return this.sanitizer.bypassSecurityTrustScript(value); + case 'url': + return this.sanitizer.bypassSecurityTrustUrl(value); + case 'resourceUrl': + return this.sanitizer.bypassSecurityTrustResourceUrl(value); + default: + throw new Error(`Invalid safe type specified: ${type}`); + } + } +} diff --git a/packages/common-angular/src/pipes/timing.pipe.ts b/packages/common-angular/src/pipes/timing.pipe.ts new file mode 100644 index 0000000..e355482 --- /dev/null +++ b/packages/common-angular/src/pipes/timing.pipe.ts @@ -0,0 +1,20 @@ +import { Pipe, PipeTransform } from '@angular/core'; + +@Pipe({ name: 'timing' }) +export class TimingPipe implements PipeTransform { + transform(time: number): string { + if (time) { + const minutes = Math.floor(time / 60); + const seconds = Math.floor(time % 60); + return `${this.initZero(minutes)}${minutes}:${this.initZero( + seconds + )}${seconds}`; + } + + return '00:00'; + } + + private initZero(time: number): string { + return time < 10 ? '0' : ''; + } +} diff --git a/packages/common-angular/src/routers/carrier-orders-router.service.ts b/packages/common-angular/src/routers/carrier-orders-router.service.ts new file mode 100644 index 0000000..9b2537a --- /dev/null +++ b/packages/common-angular/src/routers/carrier-orders-router.service.ts @@ -0,0 +1,108 @@ +import { map } from 'rxjs/operators'; +import { Observable } from 'rxjs'; +import * as _ from 'underscore'; +import { Router, RouterFactory } from '../lib/router'; +import { Injectable } from '@angular/core'; +import ICarrierOrdersRouter, { + ICarrierOrdersRouterGetAvailableOptions, + ICarrierOrdersRouterGetOptions, +} from '@modules/server.common/routers/ICarrierOrdersRouter'; +import Order from '@modules/server.common/entities/Order'; +import Carrier from '@modules/server.common/entities/Carrier'; +import OrderCarrierStatus from '@modules/server.common/enums/OrderCarrierStatus'; +import IOrder from '@modules/server.common/interfaces/IOrder'; +import ICarrier from '@modules/server.common/interfaces/ICarrier'; + +@Injectable() +export class CarrierOrdersRouter implements ICarrierOrdersRouter { + private readonly router: Router; + + constructor(routerFactory: RouterFactory) { + this.router = routerFactory.create('carrier-orders'); + } + + get( + id: string, + options: ICarrierOrdersRouterGetOptions + ): Observable { + return this.router + .runAndObserve('get', id, options) + .pipe( + map((orders) => + _.map(orders, (order) => this._orderFactory(order)) + ) + ); + } + + getAvailable( + id: string, + options: ICarrierOrdersRouterGetAvailableOptions + ): Observable { + return this.router + .runAndObserve('getAvailable', id, options) + .pipe( + map((orders) => + _.map(orders, (order) => this._orderFactory(order)) + ) + ); + } + + async selectedForDelivery( + carrierId: string, + orderIds: string[], + carrierCompetition?: boolean + ): Promise { + const carrier = await this.router.run( + 'selectedForDelivery', + carrierId, + orderIds, + carrierCompetition + ); + return this._carrierFactory(carrier); + } + + async updateStatus( + carrierId: string, + newStatus: OrderCarrierStatus + ): Promise { + const carrier = await this.router.run( + 'updateStatus', + carrierId, + newStatus + ); + return this._carrierFactory(carrier); + } + + async cancelDelivery( + carrierId: string, + orderIds: string[] + ): Promise { + const carrier = await this.router.run( + 'cancelDelivery', + carrierId, + orderIds + ); + return this._carrierFactory(carrier); + } + + getCount(carrierId: string): Promise { + return this.router.run('getCount', carrierId); + } + + async skipOrders(carrierId: string, ordersIds: string[]): Promise { + const carrier = await this.router.run( + 'skipOrders', + carrierId, + ordersIds + ); + return this._carrierFactory(carrier); + } + + protected _carrierFactory(carrier: ICarrier) { + return carrier == null ? null : new Carrier(carrier); + } + + protected _orderFactory(order: IOrder) { + return order == null ? null : new Order(order); + } +} diff --git a/packages/common-angular/src/routers/carrier-router.service.ts b/packages/common-angular/src/routers/carrier-router.service.ts new file mode 100644 index 0000000..2a3cafc --- /dev/null +++ b/packages/common-angular/src/routers/carrier-router.service.ts @@ -0,0 +1,119 @@ +import * as _ from 'underscore'; +import { map } from 'rxjs/operators'; +import { Router, RouterFactory } from '../lib/router'; +import { Observable } from 'rxjs'; +import { Injectable } from '@angular/core'; +import ICarrier from '@modules/server.common/interfaces/ICarrier'; +import Carrier from '@modules/server.common/entities/Carrier'; +import ICarrierRouter from '@modules/server.common/routers/ICarrierRouter'; +import GeoLocation from '@modules/server.common/entities/GeoLocation'; +import { + ICarrierLoginResponse, + ICarrierRegistrationInput, +} from '@modules/server.common/routers/ICarrierRouter'; + +@Injectable() +export class CarrierRouter implements ICarrierRouter { + private readonly router: Router; + + constructor(routerFactory: RouterFactory) { + this.router = routerFactory.create('carrier'); + } + + get(id: string): Observable { + return this.router + .runAndObserve('get', id) + .pipe(map((carrier) => this._carrierFactory(carrier))); + } + + getAllActive(): Observable { + return this.router + .runAndObserve('getAllActive') + .pipe( + map((carriers) => + _.map(carriers, (carrier) => this._carrierFactory(carrier)) + ) + ); + } + + async updateStatus(carrierId: string, newStatus: number): Promise { + const carrier = await this.router.run( + 'updateStatus', + carrierId, + newStatus + ); + return this._carrierFactory(carrier); + } + + async updateActivity( + carrierId: string, + activity: boolean + ): Promise { + const carrier = await this.router.run( + 'updateActivity', + carrierId, + activity + ); + return this._carrierFactory(carrier); + } + + async updateGeoLocation( + carrierId: string, + geoLocation: GeoLocation + ): Promise { + const carrier = await this.router.run( + 'updateGeoLocation', + carrierId, + geoLocation + ); + return this._carrierFactory(carrier); + } + + async updatePassword( + id: string, + password: { current?: string; new: string } + ): Promise { + await this.router.run('updatePassword', id, password); + } + + async updateById( + id: string, + updateObject: Partial + ): Promise { + const c = await this.router.run( + 'updateById', + id, + updateObject + ); + return this._carrierFactory(c); + } + + async register(input: ICarrierRegistrationInput): Promise { + const warehouse = await this.router.run('register', input); + return this._carrierFactory(warehouse); + } + + async login( + username: string, + password: string + ): Promise { + const res = await this.router.run( + 'login', + username, + password + ); + + if (res == null) { + return null; + } else { + return { + token: res.token, + carrier: this._carrierFactory(res.carrier), + }; + } + } + + protected _carrierFactory(carrier: ICarrier) { + return carrier == null ? null : new Carrier(carrier); + } +} diff --git a/packages/common-angular/src/routers/device-router.service.ts b/packages/common-angular/src/routers/device-router.service.ts new file mode 100644 index 0000000..68d8915 --- /dev/null +++ b/packages/common-angular/src/routers/device-router.service.ts @@ -0,0 +1,59 @@ +import { map } from 'rxjs/operators'; +import { Observable } from 'rxjs'; +import { Router, RouterFactory } from '../lib/router'; +import { Injectable } from '@angular/core'; +import { + IDeviceRawObject, + IDeviceCreateObject, +} from '@modules/server.common/interfaces/IDevice'; +import Device from '@modules/server.common/entities/Device'; +import IDeviceRouter from '@modules/server.common/routers/IDeviceRouter'; +import ILanguage from '@modules/server.common/interfaces/ILanguage'; + +@Injectable() +export class DeviceRouter implements IDeviceRouter { + private readonly router: Router; + + constructor(routerFactory: RouterFactory) { + this.router = routerFactory.create('device'); + } + + async create(deviceCreateObject: IDeviceCreateObject): Promise { + return this._deviceFactory( + await this.router.run( + 'create', + deviceCreateObject + ) + ); + } + + get(id: string): Observable { + return this.router + .runAndObserve('get', id) + .pipe(map((device) => this._deviceFactory(device))); + } + + /*public getByUUIDAndPlatform(uuid: string, platform: IPlatform): Observable { + return this.router.runAndObserve('getByUUIDAndPlatform', uuid, platform) + .map((device) => this._deviceFactory(device)); + }*/ + + async updateLanguage( + deviceId: string, + language: ILanguage + ): Promise { + return this._deviceFactory( + await this.router.run( + 'updateLanguage', + deviceId, + language + ) + ); + } + + protected _deviceFactory(device: null): null; + protected _deviceFactory(device: IDeviceRawObject): Device; + protected _deviceFactory(device: IDeviceRawObject | null) { + return device == null ? null : new Device(device); + } +} diff --git a/packages/common-angular/src/routers/geo-location-orders-router.service.ts b/packages/common-angular/src/routers/geo-location-orders-router.service.ts new file mode 100644 index 0000000..86f284c --- /dev/null +++ b/packages/common-angular/src/routers/geo-location-orders-router.service.ts @@ -0,0 +1,37 @@ +import { map } from 'rxjs/operators'; +import { Router, RouterFactory } from '../lib/router'; +import * as _ from 'underscore'; +import { Observable } from 'rxjs'; +import { Injectable } from '@angular/core'; +import IGeoLocationOrdersRouter, { + IGeoLocationOrdersRouterGetOptions, +} from '@modules/server.common/routers/IGeoLocationOrdersRouter'; +import IOrder from '@modules/server.common/interfaces/IOrder'; +import Order from '@modules/server.common/entities/Order'; +import GeoLocation from '@modules/server.common/entities/GeoLocation'; + +@Injectable() +export class GeoLocationOrdersRouter implements IGeoLocationOrdersRouter { + private readonly router: Router; + + constructor(routerFactory: RouterFactory) { + this.router = routerFactory.create('geo-location-orders'); + } + + get( + geoLocation: GeoLocation, + options: IGeoLocationOrdersRouterGetOptions = {} + ): Observable { + return this.router + .runAndObserve('get', geoLocation, options) + .pipe( + map((orders) => + _.map(orders, (order) => this._orderFactory(order)) + ) + ); + } + + protected _orderFactory(order: IOrder) { + return order == null ? null : new Order(order); + } +} diff --git a/packages/common-angular/src/routers/geo-location-products-router.service.ts b/packages/common-angular/src/routers/geo-location-products-router.service.ts new file mode 100644 index 0000000..c5d91b6 --- /dev/null +++ b/packages/common-angular/src/routers/geo-location-products-router.service.ts @@ -0,0 +1,37 @@ +import * as _ from 'underscore'; +import { map } from 'rxjs/operators'; +import { Router, RouterFactory } from '../lib/router'; +import { Observable } from 'rxjs'; +import { Injectable } from '@angular/core'; +import IGeoLocationProductsRouter from '@modules/server.common/routers/IGeoLocationProductsRouter'; +import ProductInfo from '@modules/server.common/entities/ProductInfo'; +import IProductInfo from '@modules/server.common/interfaces/IProductInfo'; +import GeoLocation from '@modules/server.common/entities/GeoLocation'; + +@Injectable() +export class GeoLocationProductsRouter implements IGeoLocationProductsRouter { + private readonly router: Router; + + constructor(routerFactory: RouterFactory) { + this.router = routerFactory.create('geo-location-products'); + } + + get( + geoLocation: GeoLocation, + options?: { isDeliveryRequired?: boolean; isTakeaway?: boolean } + ): Observable { + return this.router + .runAndObserve('get', geoLocation, options) + .pipe( + map((products) => + _.map(products, (productInfo) => + this._productInfoFactory(productInfo) + ) + ) + ); + } + + protected _productInfoFactory(productInfo: IProductInfo) { + return productInfo == null ? null : new ProductInfo(productInfo); + } +} diff --git a/packages/common-angular/src/routers/geo-location-router.service.ts b/packages/common-angular/src/routers/geo-location-router.service.ts new file mode 100644 index 0000000..443a508 --- /dev/null +++ b/packages/common-angular/src/routers/geo-location-router.service.ts @@ -0,0 +1,19 @@ +import { Injectable } from '@angular/core'; +import { RouterFactory, Router } from '../lib/router'; +import IGeoLocationsRouter from '@modules/server.common/routers/IGeoLocationsRouter'; + +@Injectable() +export class GeoLocationRouter implements IGeoLocationsRouter { + private readonly router: Router; + + constructor(routerFactory: RouterFactory) { + this.router = routerFactory.create('geo-location'); + } + + getAddressByCoordinatesUsingArcGIS( + lat: number, + lng: number + ): Promise { + return this.router.run('getAddressByCoordinatesUsingArcGIS', lat, lng); + } +} diff --git a/packages/common-angular/src/routers/geo-location-warehouses-router.service.ts b/packages/common-angular/src/routers/geo-location-warehouses-router.service.ts new file mode 100644 index 0000000..0473d9c --- /dev/null +++ b/packages/common-angular/src/routers/geo-location-warehouses-router.service.ts @@ -0,0 +1,40 @@ +import * as _ from 'underscore'; +import { map } from 'rxjs/operators'; +import { RouterFactory, Router } from '../lib/router'; +import { Observable } from 'rxjs'; +import { Injectable } from '@angular/core'; +import IWarehouse from '@modules/server.common/interfaces/IWarehouse'; +import Warehouse from '@modules/server.common/entities/Warehouse'; +import IGeoLocationWarehousesRouter, { + IGeoLocationWarehousesRouterGetOptions, +} from '@modules/server.common/routers/IGeoLocationWarehousesRouter'; +import GeoLocation from '@modules/server.common/entities/GeoLocation'; + +@Injectable() +export class GeoLocationWarehousesRouter + implements IGeoLocationWarehousesRouter { + private readonly router: Router; + + constructor(routerFactory: RouterFactory) { + this.router = routerFactory.create('geo-location-warehouses'); + } + + get( + geoLocation: GeoLocation, + options?: IGeoLocationWarehousesRouterGetOptions + ): Observable { + return this.router + .runAndObserve('get', geoLocation, options) + .pipe( + map((warehouses) => + _.map(warehouses, (warehouse) => + this._warehouseFactory(warehouse) + ) + ) + ); + } + + protected _warehouseFactory(warehouse: IWarehouse) { + return warehouse == null ? null : new Warehouse(warehouse); + } +} diff --git a/packages/common-angular/src/routers/index.ts b/packages/common-angular/src/routers/index.ts new file mode 100644 index 0000000..adb65fd --- /dev/null +++ b/packages/common-angular/src/routers/index.ts @@ -0,0 +1 @@ +export { RoutersModule } from './routers.module'; diff --git a/packages/common-angular/src/routers/invite-request-router.service.ts b/packages/common-angular/src/routers/invite-request-router.service.ts new file mode 100644 index 0000000..5aea0b7 --- /dev/null +++ b/packages/common-angular/src/routers/invite-request-router.service.ts @@ -0,0 +1,39 @@ +import { map } from 'rxjs/operators'; +import { RouterFactory, Router } from '../lib/router'; +import { Observable } from 'rxjs'; +import { Injectable } from '@angular/core'; +import IInviteRequestRouter from '@modules/server.common/routers/IInviteRequestRouter'; +import { + IInviteRequestCreateObject, + IInviteRequestRawObject, +} from '@modules/server.common/interfaces/IInviteRequest'; +import InviteRequest from '@modules/server.common/entities/InviteRequest'; + +@Injectable() +export class InviteRequestRouter implements IInviteRequestRouter { + private readonly router: Router; + + constructor(routerFactory: RouterFactory) { + this.router = routerFactory.create('invite-request'); + } + + get(id: string): Observable { + return this.router + .runAndObserve('get', id) + .pipe(map((o) => this._inviteRequestFactory(o))); + } + + async create( + inviteRequest: IInviteRequestCreateObject + ): Promise { + const o = await this.router.run( + 'create', + inviteRequest + ); + return this._inviteRequestFactory(o); + } + + protected _inviteRequestFactory(inviteRequest: IInviteRequestRawObject) { + return inviteRequest == null ? null : new InviteRequest(inviteRequest); + } +} diff --git a/packages/common-angular/src/routers/invite-router.service.ts b/packages/common-angular/src/routers/invite-router.service.ts new file mode 100644 index 0000000..0107967 --- /dev/null +++ b/packages/common-angular/src/routers/invite-router.service.ts @@ -0,0 +1,58 @@ +import { map } from 'rxjs/operators'; +import { RouterFactory, Router } from '../lib/router'; +import { Observable } from 'rxjs'; +import { Injectable } from '@angular/core'; +import IInviteRouter from '@modules/server.common/routers/IInviteRouter'; +import IEnterByCode from '@modules/server.common/interfaces/IEnterByCode'; +import IEnterByLocation from '@modules/server.common/interfaces/IEnterByLocation'; +import Invite from '@modules/server.common/entities/Invite'; +import IStreetLocation from '@modules/server.common/interfaces/IStreetLocation'; +import IInvite, { + IInviteCreateObject, +} from '@modules/server.common/interfaces/IInvite'; + +@Injectable() +export class InviteRouter implements IInviteRouter { + private readonly router: Router; + + constructor(routerFactory: RouterFactory) { + this.router = routerFactory.create('invite'); + } + + get(id: string): Observable { + return this.router + .runAndObserve('get', id) + .pipe(map((invite) => this._inviteFactory(invite))); + } + + getInvitedStreetLocations(): Observable { + return this.router.runAndObserve( + 'getInvitedStreetLocations' + ); + } + + getByLocation(info: IEnterByLocation): Observable { + return this.router + .runAndObserve('getByLocation', info) + .pipe(map((i) => this._inviteFactory(i))); + } + + getByCode(info: IEnterByCode): Observable { + return this.router + .runAndObserve('getByCode', info) + .pipe(map((i) => this._inviteFactory(i))); + } + + async create(inviteCreateObject: IInviteCreateObject): Promise { + const i = await this.router.run('create', inviteCreateObject); + return this._inviteFactory(i); + } + + getInvitesSettings(): Promise<{ isEnabled: boolean }> { + return this.router.run<{ isEnabled: boolean }>('getInvitesSettings'); + } + + protected _inviteFactory(invite: IInvite) { + return invite == null ? null : new Invite(invite); + } +} diff --git a/packages/common-angular/src/routers/order-router.service.ts b/packages/common-angular/src/routers/order-router.service.ts new file mode 100644 index 0000000..791c9ef --- /dev/null +++ b/packages/common-angular/src/routers/order-router.service.ts @@ -0,0 +1,136 @@ +import { map } from 'rxjs/operators'; +import { Injectable } from '@angular/core'; +import { Observable } from 'rxjs'; +import { Router, RouterFactory } from '../lib/router'; +import IOrderRouter, { + IOrderRouterGetOptions, +} from '@modules/server.common/routers/IOrderRouter'; +import Order from '@modules/server.common/entities/Order'; +import Warehouse from '@modules/server.common/entities/Warehouse'; +import OrderCarrierStatus from '@modules/server.common/enums/OrderCarrierStatus'; +import OrderWarehouseStatus from '@modules/server.common/enums/OrderWarehouseStatus'; +import IOrder from '@modules/server.common/interfaces/IOrder'; + +@Injectable() +export class OrderRouter implements IOrderRouter { + private readonly router: Router; + + constructor(routerFactory: RouterFactory) { + this.router = routerFactory.create('order'); + } + + get(id: string, options?: IOrderRouterGetOptions): Observable { + return this.router + .runAndObserve('get', id, options) + .pipe(map((order) => this._orderFactory(order))); + } + + async confirm(orderId: string): Promise { + const order = await this.router.run('confirm', orderId); + return this._orderFactory(order); + } + + async complete(orderId: string): Promise { + const order = await this.router.run('complete', orderId); + return this._orderFactory(order); + } + + async updateCarrierStatus( + orderId: string, + status: OrderCarrierStatus + ): Promise { + const order = await this.router.run( + 'updateCarrierStatus', + orderId, + status + ); + return this._orderFactory(order); + } + + async updateWarehouseStatus( + orderId: string, + status: OrderWarehouseStatus + ): Promise { + const order = await this.router.run( + 'updateWarehouseStatus', + orderId, + status + ); + return this._orderFactory(order); + } + + async payWithStripe(orderId: string, cardId: string): Promise { + const order = await this.router.run( + 'payWithStripe', + orderId, + cardId + ); + return this._orderFactory(order); + } + + async refundWithStripe(orderId: string): Promise { + const order = await this.router.run( + 'refundWithStripe', + orderId + ); + return this._orderFactory(order); + } + + async addProducts( + orderId: Order['id'], + products, + warehouseId: Warehouse['id'] + ): Promise { + const order = await this.router.run( + 'addProducts', + orderId, + products, + warehouseId + ); + return this._orderFactory(order); + } + + async decreaseOrderProducts( + orderId: Order['id'], + products, + warehouseId: Warehouse['id'] + ): Promise { + const order = await this.router.run( + 'decreaseOrderProducts', + orderId, + products, + warehouseId + ); + return this._orderFactory(order); + } + + async removeProducts( + orderId: Order['id'], + productsIds: string[] + ): Promise { + const order = await this.router.run( + 'removeProducts', + orderId, + productsIds + ); + return this._orderFactory(order); + } + + async addProductComment( + orderId: Order['id'], + productId: string, + comment: string + ): Promise { + const order = await this.router.run( + 'addProductComment', + orderId, + productId, + comment + ); + return this._orderFactory(order); + } + + protected _orderFactory(order: IOrder) { + return order == null ? null : new Order(order); + } +} diff --git a/packages/common-angular/src/routers/product-router.service.ts b/packages/common-angular/src/routers/product-router.service.ts new file mode 100644 index 0000000..e860ca2 --- /dev/null +++ b/packages/common-angular/src/routers/product-router.service.ts @@ -0,0 +1,43 @@ +import { Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; +import { Router, RouterFactory } from '../lib/router'; +import { Injectable } from '@angular/core'; +import IProductRouter from '@modules/server.common/routers/IProductRouter'; +import Product from '@modules/server.common/entities/Product'; +import IProduct, { + IProductCreateObject, +} from '@modules/server.common/interfaces/IProduct'; + +@Injectable() +export class ProductRouter implements IProductRouter { + private readonly router: Router; + + constructor(routerFactory: RouterFactory) { + this.router = routerFactory.create('product'); + } + + get(id: string): Observable { + return this.router + .runAndObserve('get', id) + .pipe(map((product) => new Product(product))); + } + + async create(p: IProductCreateObject): Promise { + const product = await this.router.run('create', p); + return new Product(product); + } + + async update(id: string, updateObject): Promise { + const product = await this.router.run( + 'update', + id, + updateObject + ); + return new Product(product); + } + + async save(updatedProduct: IProduct): Promise { + const product = await this.router.run('save', updatedProduct); + return new Product(product); + } +} diff --git a/packages/common-angular/src/routers/products-category-router.service.ts b/packages/common-angular/src/routers/products-category-router.service.ts new file mode 100644 index 0000000..01167b4 --- /dev/null +++ b/packages/common-angular/src/routers/products-category-router.service.ts @@ -0,0 +1,43 @@ +import { Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; +import { Router, RouterFactory } from '../lib/router'; +import { Injectable } from '@angular/core'; +import IProductsCategoryRouter from '@modules/server.common/routers/IProductsCategoryRouter'; +import ProductsCategory from '@modules/server.common/entities/ProductsCategory'; +import { + IProductsCategory, + IProductsCategoryCreateObject, +} from '@modules/server.common/interfaces/IProductsCategory'; + +@Injectable() +export class ProductsCategoryRouter implements IProductsCategoryRouter { + private readonly router: Router; + + constructor(routerFactory: RouterFactory) { + this.router = routerFactory.create('productsCategory'); + } + + get(id: string): Observable { + return this.router + .runAndObserve('get', id) + .pipe(map((category) => new ProductsCategory(category))); + } + + async create(c: IProductsCategoryCreateObject): Promise { + const category = await this.router.run('create', c); + return new ProductsCategory(category); + } + + async update(id: string, updateObject): Promise { + const category = await this.router.run( + 'update', + id, + updateObject + ); + return new ProductsCategory(category); + } + + remove(id: string): any { + return this.router.run('remove', id); + } +} diff --git a/packages/common-angular/src/routers/routers.module.ts b/packages/common-angular/src/routers/routers.module.ts new file mode 100644 index 0000000..3097dfc --- /dev/null +++ b/packages/common-angular/src/routers/routers.module.ts @@ -0,0 +1,49 @@ +import { NgModule } from '@angular/core'; +import { CommonLibModule } from '../lib'; +import { CarrierOrdersRouter } from './carrier-orders-router.service'; +import { InviteRequestRouter } from './invite-request-router.service'; +import { ProductRouter } from './product-router.service'; +import { InviteRouter } from './invite-router.service'; +import { GeoLocationWarehousesRouter } from './geo-location-warehouses-router.service'; +import { GeoLocationProductsRouter } from './geo-location-products-router.service'; +import { CarrierRouter } from './carrier-router.service'; +import { DeviceRouter } from './device-router.service'; +import { GeoLocationOrdersRouter } from './geo-location-orders-router.service'; +import { OrderRouter } from './order-router.service'; +import { UserRouter } from './user-router.service'; +import { WarehouseCarriersRouter } from './warehouse-carriers-router.service'; +import { WarehouseProductsRouter } from './warehouse-products-router.service'; +import { WarehouseRouter } from './warehouse-router.service'; +import { UserProductsRouter } from './user-products-router.service'; +import { WarehouseOrdersRouter } from './warehouse-orders-router.service'; +import { UserOrdersRouter } from './user-orders-router.service'; +import { UserAuthRouter } from './user-auth-router.service'; +import { GeoLocationRouter } from './geo-location-router.service'; + +@NgModule({ + imports: [CommonLibModule], + exports: [], + declarations: [], + providers: [ + CarrierOrdersRouter, + CarrierRouter, + DeviceRouter, + GeoLocationOrdersRouter, + GeoLocationProductsRouter, + GeoLocationWarehousesRouter, + GeoLocationRouter, + InviteRequestRouter, + InviteRouter, + OrderRouter, + ProductRouter, + UserOrdersRouter, + UserRouter, + UserAuthRouter, + WarehouseCarriersRouter, + WarehouseRouter, + WarehouseProductsRouter, + WarehouseOrdersRouter, + UserProductsRouter, + ], +}) +export class RoutersModule {} diff --git a/packages/common-angular/src/routers/user-auth-router.service.ts b/packages/common-angular/src/routers/user-auth-router.service.ts new file mode 100644 index 0000000..626843e --- /dev/null +++ b/packages/common-angular/src/routers/user-auth-router.service.ts @@ -0,0 +1,77 @@ +import { Router, RouterFactory } from '../lib/router'; +import { Injectable } from '@angular/core'; +import IUserAuthRouter, { + AddableRegistrationInfo, + IUserRegistrationInput, + IUserLoginResponse, +} from '@modules/server.common/routers/IUserAuthRouter'; +import User from '@modules/server.common/entities/User'; +import IUser from '@modules/server.common/interfaces/IUser'; + +@Injectable() +export class UserAuthRouter implements IUserAuthRouter { + private readonly router: Router; + + constructor(routerFactory: RouterFactory) { + this.router = routerFactory.create('user-auth'); + } + + /** + * Register Customer + * Note: if invites system is on - throws NotInvited if not invited + * + * @param {IUserRegistrationInput} input + * @returns {Promise} + * @memberof UserAuthRouter + */ + async register(input: IUserRegistrationInput): Promise { + const u = await this.router.run('register', input); + return this._userFactory(u); + } + + async addRegistrationInfo( + id: User['id'], + info: AddableRegistrationInfo + ): Promise { + await this.router.run('addRegistrationInfo', id, info); + } + + async login( + username: string, + password: string + ): Promise { + const res = await this.router.run( + 'login', + username, + password + ); + + if (res == null) { + return null; + } else { + return { + token: res.token, + user: this._userFactory(res.user), + }; + } + } + + async updatePassword( + id: string, + password: { current: string; new: string } + ): Promise { + await this.router.run('updatePassword', id, password); + } + + protected _userFactory(user: IUser) { + return user == null ? null : new User(user); + } + + getRegistrationsSettings(): Promise<{ + registrationRequiredOnStart: boolean; + }> { + return this.router.run<{ registrationRequiredOnStart: boolean }>( + 'getRegistrationsSettings' + ); + } +} diff --git a/packages/common-angular/src/routers/user-orders-router.service.ts b/packages/common-angular/src/routers/user-orders-router.service.ts new file mode 100644 index 0000000..880d1f0 --- /dev/null +++ b/packages/common-angular/src/routers/user-orders-router.service.ts @@ -0,0 +1,39 @@ +import { Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; +import { Router, RouterFactory } from '../lib/router'; +import { Injectable } from '@angular/core'; +import IUserOrdersRouter from '@modules/server.common/routers/IUserOrdersRouter'; +import IOrder from '@modules/server.common/interfaces/IOrder'; +import Order from '@modules/server.common/entities/Order'; +import IOrderProductInfo from '@modules/server.common/interfaces/IOrderProductInfo'; +import * as _ from 'underscore'; + +@Injectable() +export class UserOrdersRouter implements IUserOrdersRouter { + private readonly router: Router; + + constructor(routerFactory: RouterFactory) { + this.router = routerFactory.create('user-orders'); + } + + get(userId: string): Observable { + return this.router + .runAndObserve('get', userId) + .pipe( + map((orders) => + _.map(orders, (order) => this._orderFactory(order)) + ) + ); + } + + getOrderedProducts(userId: string): Observable { + return this.router.runAndObserve( + 'getOrderedProducts', + userId + ); + } + + protected _orderFactory(order: IOrder) { + return order == null ? null : new Order(order); + } +} diff --git a/packages/common-angular/src/routers/user-products-router.service.ts b/packages/common-angular/src/routers/user-products-router.service.ts new file mode 100644 index 0000000..09f2e3c --- /dev/null +++ b/packages/common-angular/src/routers/user-products-router.service.ts @@ -0,0 +1,21 @@ +import { Observable } from 'rxjs'; +import { Router, RouterFactory } from '../lib/router'; +import { Injectable } from '@angular/core'; +import IUserProductsRouter from '@modules/server.common/routers/IUserProductsRouter'; + +@Injectable() +export class UserProductsRouter implements IUserProductsRouter { + private readonly router: Router; + + constructor(routerFactory: RouterFactory) { + this.router = routerFactory.create('user-products'); + } + + getPlaceholder(userId: string, deviceId: string): Observable { + return this.router.runAndObserve( + 'getPlaceholder', + userId, + deviceId + ); + } +} diff --git a/packages/common-angular/src/routers/user-router.service.ts b/packages/common-angular/src/routers/user-router.service.ts new file mode 100644 index 0000000..2a504fb --- /dev/null +++ b/packages/common-angular/src/routers/user-router.service.ts @@ -0,0 +1,118 @@ +import { Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; +import { Router, RouterFactory } from '../lib/router'; +import { Injectable } from '@angular/core'; +import IUserRouter from '@modules/server.common/routers/IUserRouter'; +import IUser, { + IUserCreateObject, +} from '@modules/server.common/interfaces/IUser'; +import User from '@modules/server.common/entities/User'; +import GeoLocation from '@modules/server.common/entities/GeoLocation'; +import { Stripe } from 'stripe'; + +@Injectable() +export class UserRouter implements IUserRouter { + private readonly router: Router; + + constructor(routerFactory: RouterFactory) { + this.router = routerFactory.create('user'); + } + + get(id: string): Observable { + return this.router + .runAndObserve('get', id) + .pipe(map((user) => this._userFactory(user))); + } + + async updateUser( + id: string, + userCreateObject: IUserCreateObject + ): Promise { + const user = await this.router.run( + 'updateUser', + id, + userCreateObject + ); + return this._userFactory(user); + } + + addPaymentMethod( + userId: string, + tokenId: string + ): Promise { + return this.router.run('addPaymentMethod', userId, tokenId); + } + + getCards(userId: string): Promise { + return this.router.run('getCards', userId); + } + + async updateEmail(userId: string, email: string): Promise { + const user = await this.router.run('updateEmail', userId, email); + return this._userFactory(user); + } + + async updateGeoLocation( + userId: string, + geoLocation: GeoLocation + ): Promise { + const user = await this.router.run(userId, geoLocation); + return this._userFactory(user); + } + + getAboutUs( + userId: string, + deviceId: string, + selectedLanguage: string + ): Observable { + return this.router.runAndObserve( + 'getAboutUs', + userId, + deviceId, + selectedLanguage + ); + } + + getTermsOfUse( + userId: string, + deviceId: string, + selectedLanguage: string + ): Observable { + return this.router.runAndObserve( + 'getTermsOfUse', + userId, + deviceId, + selectedLanguage + ); + } + + getHelp( + userId: string, + deviceId: string, + selectedLanguage: string + ): Observable { + return this.router.runAndObserve( + 'getHelp', + userId, + deviceId, + selectedLanguage + ); + } + + getPrivacy( + userId: string, + deviceId: string, + selectedLanguage: string + ): Observable { + return this.router.runAndObserve( + 'getPrivacy', + userId, + deviceId, + selectedLanguage + ); + } + + protected _userFactory(user: IUser) { + return user == null ? null : new User(user); + } +} diff --git a/packages/common-angular/src/routers/warehouse-carriers-router.service.ts b/packages/common-angular/src/routers/warehouse-carriers-router.service.ts new file mode 100644 index 0000000..aa365d5 --- /dev/null +++ b/packages/common-angular/src/routers/warehouse-carriers-router.service.ts @@ -0,0 +1,35 @@ +import { Observable } from 'rxjs'; +import { Router, RouterFactory } from '../lib/router'; +import { map } from 'rxjs/operators'; +import * as _ from 'underscore'; +import { Injectable } from '@angular/core'; +import IWarehouseCarriersRouter from '@modules/server.common/routers/IWarehouseCarriersRouter'; +import Carrier from '@modules/server.common/entities/Carrier'; +import ICarrier from '@modules/server.common/interfaces/ICarrier'; + +@Injectable() +export class WarehouseCarriersRouter implements IWarehouseCarriersRouter { + private readonly router: Router; + + constructor(routerFactory: RouterFactory) { + this.router = routerFactory.create('warehouse-carriers'); + } + + get(warehouseId: string): Observable { + return this.router + .runAndObserve('get', warehouseId) + .pipe( + map((carriers) => + _.map(carriers, (carrier) => this._carrierFactory(carrier)) + ) + ); + } + + async updatePassword(id: string, password: string): Promise { + await this.router.run('updatePassword', id, password); + } + + protected _carrierFactory(carrier: ICarrier) { + return carrier == null ? null : new Carrier(carrier); + } +} diff --git a/packages/common-angular/src/routers/warehouse-orders-router.service.ts b/packages/common-angular/src/routers/warehouse-orders-router.service.ts new file mode 100644 index 0000000..30cea31 --- /dev/null +++ b/packages/common-angular/src/routers/warehouse-orders-router.service.ts @@ -0,0 +1,86 @@ +import { Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; +import { Router, RouterFactory } from '../lib/router'; +import * as _ from 'underscore'; +import { Injectable } from '@angular/core'; +import IOrder from '@modules/server.common/interfaces/IOrder'; +import Order from '@modules/server.common/entities/Order'; +import IWarehouseOrdersRouter, { + IWarehouseOrdersRouterGetOptions, + IOrderCreateInput, + IOrderCreateInputProduct, +} from '@modules/server.common/routers/IWarehouseOrdersRouter'; +import DeliveryType from '@modules/server.common/enums/DeliveryType'; + +@Injectable() +export class WarehouseOrdersRouter implements IWarehouseOrdersRouter { + private readonly router: Router; + + constructor(routerFactory: RouterFactory) { + this.router = routerFactory.create('warehouse-orders'); + } + + get( + warehouseId: string, + options: IWarehouseOrdersRouterGetOptions = {} + ): Observable { + return this.router + .runAndObserve('get', warehouseId, options) + .pipe( + map((orders) => + _.map(orders, (order) => this._orderFactory(order)) + ) + ); + } + + async create(createInput: IOrderCreateInput): Promise { + const order = await this.router.run('create', createInput); + return this._orderFactory(order); + } + + async cancel(orderId: string): Promise { + const order = await this.router.run('cancel', orderId); + return this._orderFactory(order); + } + + async userComplete(orderId: string): Promise { + const order = await this.router.run('userComplete', orderId); + return this._orderFactory(order); + } + + async addMore( + warehouseId: string, + userId: string, + orderId: string, + products: IOrderCreateInputProduct[] + ): Promise { + const order = await this.router.run( + 'addMore', + warehouseId, + userId, + orderId, + products + ); + return this._orderFactory(order); + } + + async createByProductType( + userId: string, + warehouseId: string, + productId: string, + orderType?: DeliveryType + ): Promise { + const order = await this.router.run( + 'createByProductType', + userId, + warehouseId, + productId, + orderType + ); + return this._orderFactory(order); + } + + protected _orderFactory(order: IOrder) { + return order == null ? null : new Order(order); + } +} diff --git a/packages/common-angular/src/routers/warehouse-products-router.service.ts b/packages/common-angular/src/routers/warehouse-products-router.service.ts new file mode 100644 index 0000000..af0083c --- /dev/null +++ b/packages/common-angular/src/routers/warehouse-products-router.service.ts @@ -0,0 +1,209 @@ +import { Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; +import { Router, RouterFactory } from '../lib/router'; +import * as _ from 'underscore'; +import { Injectable } from '@angular/core'; +import IWarehouseProduct, { + IWarehouseProductCreateObject, +} from '@modules/server.common/interfaces/IWarehouseProduct'; +import WarehouseProduct from '@modules/server.common/entities/WarehouseProduct'; +import IWarehouseProductsRouter from '@modules/server.common/routers/IWarehouseProductsRouter'; + +@Injectable() +export class WarehouseProductsRouter implements IWarehouseProductsRouter { + private readonly router: Router; + + constructor(routerFactory: RouterFactory) { + this.router = routerFactory.create('warehouse-products'); + } + + get(id: string, fullProducts = true): Observable { + return this.router + .runAndObserve('get', id, fullProducts) + .pipe( + map((warehouseProducts) => + _.map(warehouseProducts, (warehouseProduct) => + this._warehouseProductFactory(warehouseProduct) + ) + ) + ); + } + + getAvailable(warehouseId: string): Observable { + return this.router + .runAndObserve('getAvailable', warehouseId) + .pipe( + map((warehouseProducts) => + _.map(warehouseProducts, (warehouseProduct) => + this._warehouseProductFactory(warehouseProduct) + ) + ) + ); + } + + async add( + warehouseId: string, + products: IWarehouseProductCreateObject[] + ): Promise { + const warehouseProducts = await this.router.run( + 'add', + warehouseId, + products + ); + return _.map(warehouseProducts, (warehouseProduct) => + this._warehouseProductFactory(warehouseProduct) + ); + } + + async saveUpdated( + warehouseId: string, + updatedWarehouseProduct: IWarehouseProduct + ): Promise { + const warehouseProduct = await this.router.run( + 'saveUpdated', + warehouseId, + updatedWarehouseProduct + ); + return this._warehouseProductFactory(warehouseProduct); + } + + async changePrice( + warehouseId: string, + productId: string, + price: number + ): Promise { + const warehouseProduct = await this.router.run( + 'changePrice', + warehouseId, + productId, + price + ); + return this._warehouseProductFactory(warehouseProduct); + } + + async decreaseCount( + warehouseId: string, + productId: string, + count: number + ): Promise { + const warehouseProduct = await this.router.run( + 'decreaseCount', + warehouseId, + productId, + count + ); + return this._warehouseProductFactory(warehouseProduct); + } + + async increaseCount( + warehouseId: string, + productId: string, + count: number + ): Promise { + const warehouseProduct = await this.router.run( + 'increaseCount', + warehouseId, + productId, + count + ); + return this._warehouseProductFactory(warehouseProduct); + } + + async changeProductAvailability( + warehouseId: string, + productId: string, + isAvailable: boolean + ): Promise { + const warehouseProduct = await this.router.run( + 'changeProductAvailability', + warehouseId, + productId, + isAvailable + ); + + return this._warehouseProductFactory(warehouseProduct); + } + + async changeProductTakeaway( + warehouseId: string, + productId: string, + isTakeaway: boolean + ): Promise { + const warehouseProduct = await this.router.run( + 'changeProductTakeaway', + warehouseId, + productId, + isTakeaway + ); + + return this._warehouseProductFactory(warehouseProduct); + } + + async changeProductDelivery( + warehouseId: string, + productId: string, + isDelivery: boolean + ): Promise { + const warehouseProduct = await this.router.run( + 'changeProductDelivery', + warehouseId, + productId, + isDelivery + ); + + return this._warehouseProductFactory(warehouseProduct); + } + + async increaseSoldCount( + warehouseId: string, + productId: string, + count: number + ): Promise { + const warehouseProduct = await this.router.run( + 'increaseSoldCount', + warehouseId, + productId, + count + ); + return this._warehouseProductFactory(warehouseProduct); + } + + async decreaseSoldCount( + warehouseId: string, + productId: string, + count: number + ): Promise { + const warehouseProduct = await this.router.run( + 'decreaseSoldCount', + warehouseId, + productId, + count + ); + return this._warehouseProductFactory(warehouseProduct); + } + + getTopProducts( + warehouseId: string, + quantity: number + ): Observable { + return this.router + .runAndObserve( + 'getTopProducts', + warehouseId, + quantity + ) + .pipe( + map((warehouseProducts) => + _.map(warehouseProducts, (warehouseProduct) => + this._warehouseProductFactory(warehouseProduct) + ) + ) + ); + } + + protected _warehouseProductFactory(warehouseProduct: IWarehouseProduct) { + return warehouseProduct == null + ? null + : new WarehouseProduct(warehouseProduct); + } +} diff --git a/packages/common-angular/src/routers/warehouse-router.service.ts b/packages/common-angular/src/routers/warehouse-router.service.ts new file mode 100644 index 0000000..2b9da9d --- /dev/null +++ b/packages/common-angular/src/routers/warehouse-router.service.ts @@ -0,0 +1,117 @@ +import { Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; +import { Router, RouterFactory } from '../lib/router'; +import * as _ from 'underscore'; +import { Injectable } from '@angular/core'; +import IWarehouseRouter from '@modules/server.common/routers/IWarehouseRouter'; +import Warehouse from '@modules/server.common/entities/Warehouse'; +import IWarehouse from '@modules/server.common/interfaces/IWarehouse'; +import { IGeoLocationCreateObject } from '@modules/server.common/interfaces/IGeoLocation'; +import { + IWarehouseLoginResponse, + IWarehouseRegistrationInput, +} from '@modules/server.common/routers/IWarehouseRouter'; + +@Injectable() +export class WarehouseRouter implements IWarehouseRouter { + private readonly router: Router; + + constructor(routerFactory: RouterFactory) { + this.router = routerFactory.create('warehouse'); + } + + get(id: string, fullProducts: boolean = true): Observable { + return this.router + .runAndObserve('get', id, fullProducts) + .pipe(map((warehouse) => this._warehouseFactory(warehouse))); + } + + getAllActive(fullProducts: boolean = false): Observable { + return this.router + .runAndObserve('getAllActive', fullProducts) + .pipe( + map((warehouses) => + _.map(warehouses, (warehouse) => + this._warehouseFactory(warehouse) + ) + ) + ); + } + + getAll(fullProducts: boolean = false): Observable { + return this.router + .runAndObserve('getAllStores', fullProducts) + .pipe( + map((warehouses) => + _.map(warehouses, (warehouse) => + this._warehouseFactory(warehouse) + ) + ) + ); + } + + async login( + username: string, + password: string + ): Promise { + const res = await this.router.run( + 'login', + username, + password + ); + + if (res == null) { + return null; + } else { + return { + token: res.token, + warehouse: this._warehouseFactory(res.warehouse), + }; + } + } + + async register(input: IWarehouseRegistrationInput): Promise { + const warehouse = await this.router.run('register', input); + return this._warehouseFactory(warehouse); + } + + async updateGeoLocation( + warehouseId: string, + geoLocation: IGeoLocationCreateObject + ): Promise { + const warehouse = await this.router.run( + 'updateGeoLocation', + warehouseId, + geoLocation + ); + return this._warehouseFactory(warehouse); + } + + async updatePassword( + id: string, + password: { current?: string; new: string } + ): Promise { + await this.router.run('updatePassword', id, password); + } + + async updateAvailability( + warehouseId: string, + isAvailable: boolean + ): Promise { + const warehouse = await this.router.run( + 'updateAvailability', + warehouseId, + isAvailable + ); + return this._warehouseFactory(warehouse); + } + + async save(w: Warehouse): Promise { + const warehouse = await this.router.run('save', w); + return this._warehouseFactory(warehouse); + } + + protected _warehouseFactory(warehouse: IWarehouse) { + return warehouse == null ? null : new Warehouse(warehouse); + } +} diff --git a/packages/common-angular/src/routers/warehouse-users.service.ts b/packages/common-angular/src/routers/warehouse-users.service.ts new file mode 100644 index 0000000..10b8674 --- /dev/null +++ b/packages/common-angular/src/routers/warehouse-users.service.ts @@ -0,0 +1,19 @@ +import { Injectable } from '@angular/core'; +import { Observable } from 'rxjs'; +import { Router, RouterFactory } from '../lib/router'; +import IWarehouseUsersRouter from '@modules/server.common/routers/IWarehouseUsersRouter'; +import User from '@modules/server.common/entities/User'; +import Warehouse from '@modules/server.common/entities/Warehouse'; + +@Injectable() +export class WarehouseUsersService implements IWarehouseUsersRouter { + private readonly router: Router; + + constructor(routerFactory: RouterFactory) { + this.router = routerFactory.create('warehouse-users'); + } + + get(warehouseId: Warehouse['id']): Observable { + return this.router.runAndObserve('get', warehouseId); + } +} diff --git a/packages/common-angular/src/scss/everbie.common.scss b/packages/common-angular/src/scss/everbie.common.scss new file mode 100644 index 0000000..cbf560d --- /dev/null +++ b/packages/common-angular/src/scss/everbie.common.scss @@ -0,0 +1,364 @@ +// The path for our ionicons font files, relative to the built CSS in www/css +$ionicons-font-path: '../../lib/ionic/release/fonts' !default; +$fa-font-path: '../../lib/font-awesome/fonts' !default; + +$brand: #2a2c39; +$brand-lighted: #353748; +$brand-darken: darken($brand, 5%); // #1f212a + +$assertive: #bd4742; +$assertive-lighted: #ce4843; +$assertive-darken: darken($assertive, 5%); + +$balanced: #33cd5f; +$balanced-lighted: #47d26f; + +.app-keyboard-open { + .app-hide-on-keyboard-open { + display: none; + } +} + +/////////////// +/// Mixins //// +/////////////// + +@mixin calc($property, $expression...) { + #{$property}: -moz-calc(#{$expression}); + #{$property}: -webkit-calc(#{$expression}); + #{$property}: calc(#{$expression}); +} + +@mixin transform($transforms) { + -moz-transform: $transforms; + -o-transform: $transforms; + -ms-transform: $transforms; + -webkit-transform: $transforms; + transform: $transforms; +} + +@mixin vertical-align($position: relative) { + position: $position; + top: 50%; + -webkit-transform: translateY(-50%); + -ms-transform: translateY(-50%); + transform: translateY(-50%); +} + +@mixin center($position: relative) { + position: $position; + top: 50%; + left: 50%; + -webkit-transform: translate(-50%, -50%); + -ms-transform: translate(-50%, -50%); + transform: translate(-50%, -50%); +} + +@mixin top-center($position: relative) { + position: $position; + left: 50%; + -webkit-transform: translate(-50%, 0); + -ms-transform: translate(-50%, 0); + top: 0; +} + +@mixin bottom-center($position: relative) { + position: $position; + left: 50%; + -webkit-transform: translate(-50%, 0); + -ms-transform: translate(-50%, 0); + bottom: 0; +} + +@mixin calc($property, $expression) { + #{$property}: -webkit-calc(#{$expression}); + #{$property}: calc(#{$expression}); +} + +@mixin placeholder($color) { + &::-webkit-input-placeholder { + color: $color; + } + &::-moz-placeholder { + color: $color; + } + &:-ms-input-placeholder { + color: $color; + } +} + +/////////////////////////////// +/// Normalize Html And Body /// +/////////////////////////////// + +html, +body { + overflow: visible !important; + background-color: $brand-darken !important; +} + +/////////////// +// Ionic Add // +/////////////// + +.menu-open { + .side-menu-content-ltr { + -webkit-transform: translate3d(250px, 0px, 0px) !important; + } + + .side-menu-content-rtl { + -webkit-transform: translate3d(-250px, 0px, 0px) !important; + } +} + +.loading-container .loading { + background-color: transparent; + + .spinner { + fill: #fff; + stroke: #fff; + svg { + width: 40px; + height: 40px; + } + } +} + +.bright-vie .loading-container .loading .spinner { + fill: $brand; + stroke: $brand; +} + +.swiper-slide { + img { + width: 100%; + } +} + +////////////////// +/// Bar Styles /// +////////////////// + +.bar { + &.bar-brand { + border-bottom: 2px solid darken($brand, 2%); + background: $brand none; + color: white !important; + .title { + color: white; + } + .button-icon mr-1 { + color: white; + } + } +} + +/////////////////// +/// Custom link /// +////////////////// + +.brand-link { + color: $brand; + &:active { + color: $brand-lighted; + } +} + +///////////////////// +/// Everbie Input /// +///////////////////// + +.everbie-input { + height: 57px; + width: 100%; + font-size: 16px; + line-height: 20px; + border: solid #5c5f73 1px; + color: #fff; + background-color: inherit; + border-radius: 8px; + padding: 20px; + text-align: center; + -webkit-appearance: none; + + @include placeholder(#6d6f80); +} + +///////////////////////// +/// Add Button Styles /// +///////////////////////// + +.button.button-brand { + background-color: $brand; + border-color: darken($brand, 3%); + color: #fff; + &.active, + &.activated { + background-color: darken($brand, 1%); + box-shadow: inset 0 1px 4px rgba(0, 0, 0, 0.1); + } +} + +.button.button-dark-brand { + background-color: $brand-darken; + border-color: darken($brand-darken, 3%); + color: #fff; + &.active, + &.activated { + background-color: darken($brand-darken, 1%); + box-shadow: inset 0 1px 4px rgba(0, 0, 0, 0.1); + } +} + +.button.button-brand-lighted { + background-color: $brand-lighted; + border-color: darken($brand-lighted, 3%); + color: #fff; + &.active, + &.activated { + background-color: darken($brand-lighted, 1%); + box-shadow: inset 0 1px 4px rgba(0, 0, 0, 0.1); + } +} + +.button.button-assertive { + &.active, + &.activated { + border-color: $assertive-darken !important; + } +} + +.button.button-balanced { + &.active, + &.activated { + border-color: darken($balanced, 3%) !important; + } +} + +.button.button-balanced { + background-color: darken($balanced, 5%); +} + +.everbie-button { + // @extend .waves-effect; + // @extend .waves-classic; + @extend .button; + @extend .button-assertive; + + border-radius: 8px !important; + + font-size: 20px; + padding: 10px 16px; + margin-bottom: 0; + line-height: 34px; + + background-color: #ce4843; +} + +///////////// +/// Utils /// +///////////// + +.bottom-0 { + position: absolute; + bottom: 0; +} + +.no-padding { + padding: 0; +} + +.no-padding-right { + padding-right: 0; +} + +.no-padding-left { + padding-left: 0; +} + +.no-padding-top { + padding-top: 0; +} + +.no-padding-bottom { + padding-bottom: 0; +} + +.text-align-right { + text-align: right; +} + +.text-align-center { + text-align: center; +} + +.text-align-left { + text-align: left; +} + +.col-100 { + width: 100%; +} + +.display-block { + display: block; +} + +.header-bar-big-title { + font-size: 22px !important; + text-align: center !important; +} + +.button-bar-right-container { + width: 280px; + margin-left: auto; +} + +.pull-right { + margin-left: auto; +} + +.plus-navbar-button { + &::before { + font-size: 24px !important; + } +} + +.border-color-brand { + border-color: $brand; +} + +.flip { + @include transform(scale(-1, 1)); +} + +.transition-on-color-change { + $speed: 0.3s; + -webkit-transition: background-color $speed, border-color $speed, + color $speed; + -moz-transition: background-color $speed, border-color $speed, color $speed; + -o-transition: background-color $speed, border-color $speed, color $speed; + transition: background-color $speed, border-color $speed, color $speed; +} + +.horizontal-space-10 { + width: 10px; +} + +.margin-top-bottom { + margin-top: 5px; + margin-bottom: 5px; +} + +.maintenance-message-container { + color: white; + padding: 20px; + + background: $brand; + + display: flex; + align-items: center; + justify-content: center; + text-align: center; + height: 100%; +} diff --git a/packages/common-angular/src/services/googleMapsLoader.ts b/packages/common-angular/src/services/googleMapsLoader.ts new file mode 100644 index 0000000..de81a13 --- /dev/null +++ b/packages/common-angular/src/services/googleMapsLoader.ts @@ -0,0 +1,20 @@ +import { Injectable } from '@angular/core'; + +@Injectable() +export class GoogleMapsLoader { + constructor() {} + + load(googleMapsApiKey: string) { + const src = `https://maps.googleapis.com/maps/api/js?key=${googleMapsApiKey}&libraries=places,drawing&callback=__onGoogleLoaded`; + + return new Promise(async (resolve, reject) => { + window['__onGoogleLoaded'] = (ev) => { + resolve('google maps api loaded'); + }; + const node = document.createElement('script'); + node.src = src; + node.type = 'text/javascript'; + document.getElementsByTagName('head')[0].appendChild(node); + }); + } +} diff --git a/packages/common-angular/src/services/maintenance.service.ts b/packages/common-angular/src/services/maintenance.service.ts new file mode 100644 index 0000000..f9eff87 --- /dev/null +++ b/packages/common-angular/src/services/maintenance.service.ts @@ -0,0 +1,94 @@ +import { Injectable } from '@angular/core'; +import { HttpClient, HttpHeaders } from '@angular/common/http'; +import { first } from 'rxjs/operators'; + +export interface IMaintenanceInfo { + type: MaintenanceTypes | string; + status: boolean; + message: string; +} + +export enum MaintenanceTypes { + ShopMobile = 'shop-mobile', + ShopWeb = 'shop-web', + CarrierMobile = 'carrier-mobile', + MerchantTablet = 'merchant-tablet', + Admin = 'admin', + Api = 'api', +} + +@Injectable() +export class MaintenanceService { + private headers: HttpHeaders = new HttpHeaders({ + 'Content-Type': 'application/json', + }); + constructor(private http: HttpClient) {} + + async getMaintenanceInfo( + maintenanceApiUrl: string + ): Promise { + const maintenanceInfo = await this.http + .get(maintenanceApiUrl, { + headers: this.headers, + }) + .pipe(first()) + .toPromise(); + return maintenanceInfo['maintenance']; + } + + load(appTyle: string, maintenanceApiUrl: string) { + return new Promise(async (resolve, reject) => { + try { + const maintenanceInfo = await this.getMaintenanceInfo( + maintenanceApiUrl + ); + const apiInfo = await maintenanceInfo.find( + (m: IMaintenanceInfo) => + m.type === MaintenanceTypes.Api && m.status + ); + const appInfo = maintenanceInfo.find( + (m: IMaintenanceInfo) => m.type === appTyle && m.status + ); + const maintenanceMode: IMaintenanceInfo = apiInfo || appInfo; + if (maintenanceMode) { + localStorage.setItem( + 'maintenanceMode', + maintenanceMode.type + ); + } else { + localStorage.removeItem('maintenanceMode'); + } + resolve(true); + } catch (error) { + localStorage.removeItem('maintenanceMode'); + resolve(true); + } + }); + } + + async getMessage(type: string, maintenanceApiUrl: string) { + try { + const maintenanceInfo = await this.getMaintenanceInfo( + maintenanceApiUrl + ); + return maintenanceInfo.find( + (m: IMaintenanceInfo) => m.type === type + ).message; + } catch (error) {} + } + + async getStatus(type: string, maintenanceApiUrl: string): Promise { + try { + const maintenanceInfo = await this.getMaintenanceInfo( + maintenanceApiUrl + ); + const apiStatus = maintenanceInfo.find( + (m: IMaintenanceInfo) => m.type === MaintenanceTypes.Api + ).status; + const appStatus = maintenanceInfo.find( + (m: IMaintenanceInfo) => m.type === type + ).status; + return apiStatus && appStatus; + } catch (error) {} + } +} diff --git a/packages/common-angular/src/services/server-connection.service.ts b/packages/common-angular/src/services/server-connection.service.ts new file mode 100644 index 0000000..b2de8c7 --- /dev/null +++ b/packages/common-angular/src/services/server-connection.service.ts @@ -0,0 +1,27 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { first } from 'rxjs/operators'; + +@Injectable() +export class ServerConnectionService { + constructor(private readonly httpClient: HttpClient) {} + + load(endPoint: string, store: { serverConnection: string }) { + return new Promise(async (resolve, reject) => { + await this.checkServerConnection(endPoint, store); + + resolve(true); + }); + } + + async checkServerConnection( + endPoint: string, + store: { serverConnection: string } + ) { + try { + await this.httpClient.get(endPoint).pipe(first()).toPromise(); + } catch (error) { + store.serverConnection = error.status; + } + } +} diff --git a/packages/common-angular/src/typeorm-placeholder.ts b/packages/common-angular/src/typeorm-placeholder.ts new file mode 100644 index 0000000..d196f4c --- /dev/null +++ b/packages/common-angular/src/typeorm-placeholder.ts @@ -0,0 +1,11 @@ +export function Entity(options?: any): Function { + return () => {}; +} + +export function Column(options?: any): Function { + return () => {}; +} + +export function PrimaryColumn(options?: any): Function { + return () => {}; +} diff --git a/packages/common-angular/tsconfig.build.json b/packages/common-angular/tsconfig.build.json new file mode 100644 index 0000000..ea6be8e --- /dev/null +++ b/packages/common-angular/tsconfig.build.json @@ -0,0 +1,3 @@ +{ + "extends": "./tsconfig.json" +} diff --git a/packages/common-angular/tsconfig.json b/packages/common-angular/tsconfig.json new file mode 100644 index 0000000..c9146ad --- /dev/null +++ b/packages/common-angular/tsconfig.json @@ -0,0 +1,22 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "baseUrl": "./src", + "outDir": "./build", + "rootDir": "./src", + "paths": { + "@pyro/*": [ + "@pyro/*", + "../../../node_modules/@ever-platform/common/src/@pyro/*" + ], + "@modules/server.common/*": [ + "@modules/server.common/*", + "../../../node_modules/@ever-platform/common/src/*" + ] + } + }, + "include": [ + "./src/**/*.d.ts", + "../../common/**/*d.ts" + ] +} diff --git a/packages/common-angular/tslint.json b/packages/common-angular/tslint.json new file mode 100644 index 0000000..d8c4873 --- /dev/null +++ b/packages/common-angular/tslint.json @@ -0,0 +1,121 @@ +{ + "extends": ["tslint:latest", "tslint-config-prettier"], + "linterOptions": { + "exclude": ["node_modules", "dist"] + }, + "rules": { + "no-implicit-dependencies": false, + "no-submodule-imports": false, + "trailing-comma": [ + false, + { + "multiline": "always", + "singleline": "never" + } + ], + "interface-name": [false, "always-prefix"], + "arrow-return-shorthand": true, + "callable-types": true, + "class-name": true, + "comment-format": [true, "check-space"], + "forin": true, + "import-blacklist": [true], + "ordered-imports": false, + "interface-over-type-literal": true, + "label-position": true, + "max-line-length": [true, 120], + "member-access": false, + "no-arg": true, + "no-console": [false], + "no-construct": true, + "no-debugger": true, + "no-duplicate-super": true, + "no-empty": false, + "no-empty-interface": false, + "no-eval": true, + "no-misused-new": true, + "no-non-null-assertion": false, + "no-shadowed-variable": true, + "no-string-literal": false, + "no-string-throw": true, + "no-switch-case-fall-through": true, + "no-unnecessary-initializer": true, + "no-use-before-declare": true, + "no-var-keyword": true, + "object-literal-sort-keys": false, + "prefer-const": true, + "object-literal-key-quotes": false, + "no-angle-bracket-type-assertion": false, + "member-ordering": false, + "no-consecutive-blank-lines": false, + "radix": true, + "semicolon": [true, "always"], + "triple-equals": [true, "allow-null-check"], + "typeof-compare": true, + "unified-signatures": true, + "variable-name": false, + "directive-selector": [true, "attribute", "ngx", "camelCase"], + "component-selector": [ + true, + "element", + [ + "e-cu", // e-cu = ever customer + "ngx", + "ea", // ea = ever admin + "es" // es = ever store + ], + "kebab-case" + ], + "no-attribute-parameter-decorator": true, + "no-forward-ref": true, + "no-input-rename": true, + "no-output-rename": true, + "only-arrow-functions": false, + "pipe-naming": [true, "camelCase", "my"], + "use-host-property-decorator": true, + "ban": [ + true, + "eval", + "fit", + "fdescribe", + { + "name": "$", + "message": "please don't" + } + ], + "max-classes-per-file": [false], + "import-destructuring-spacing": true, + "invoke-injectable": true, + "no-access-missing-member": true, + "templates-use-public": true, + "use-input-property-decorator": true, + "use-life-cycle-interface": true, + "use-output-property-decorator": true, + "use-pipe-transform-interface": true, + "quotemark": [true, "single", "avoid-escape"], + "eofline": true, + "import-spacing": true, + "indent": [true, "tabs"], + "no-trailing-whitespace": true, + "one-line": [false], + "typedef-whitespace": [ + true, + { + "call-signature": "nospace", + "index-signature": "nospace", + "parameter": "nospace", + "property-declaration": "nospace", + "variable-declaration": "nospace" + } + ], + "whitespace": [ + true, + "check-branch", + "check-decl", + "check-operator", + "check-separator", + "check-type" + ], + "no-namespace": false + } +} diff --git a/packages/common/.dockerignore b/packages/common/.dockerignore new file mode 100644 index 0000000..3438721 --- /dev/null +++ b/packages/common/.dockerignore @@ -0,0 +1,10 @@ +.git +.gitignore +.gitmodules +README.md +docker +node_modules +tmp +build +dist +.env diff --git a/packages/common/.gitignore b/packages/common/.gitignore new file mode 100644 index 0000000..8cee9b3 --- /dev/null +++ b/packages/common/.gitignore @@ -0,0 +1,7 @@ +**/*.js +**/*.js.map +**/*.d.ts +**/*.d.ts.map +node_modules +dist +build \ No newline at end of file diff --git a/packages/common/LICENSE.md b/packages/common/LICENSE.md new file mode 100644 index 0000000..24a5e6a --- /dev/null +++ b/packages/common/LICENSE.md @@ -0,0 +1,41 @@ +# License + +Copyright © 2016-present, Ever Co. LTD. All rights reserved. + +This software is available under different licenses + +### _Ever Platform Community Edition_ License for Shared Modules + +If you decide to choose the Ever Platform Community Edition License for Shared Modules, you must comply with the following terms: + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License, version 3, +as published by the Free Software Foundation. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . + +[GNU General Public License v3.0](https://www.gnu.org/licenses/gpl-3.0.txt) + +### _Ever Platform Enterprise_ License + +Alternatively, commercial versions of the software must be used in accordance with the terms and conditions of separate written agreement between you and Ever Co. LTD. + +For more information about Ever Platform Enterprise License please contact . + +#### The default Ever Platform license, without a valid Ever Platform Enterprise License agreement, is the Ever Platform Community Edition License. + +## Credits + +Please see [CREDITS.md](CREDITS.md) file for a list of libraries and software included in this program and information about licenses. + +## Trademarks + +**Ever**® is a registered trademark of [Ever Co. LTD](https://ever.co). +The trademark may only be used with the written permission of Ever Co. LTD. and may not be used to promote or otherwise market competitive products or services. +All other brand and product names are trademarks, registered trademarks or service marks of their respective holders. diff --git a/packages/common/README.md b/packages/common/README.md new file mode 100644 index 0000000..1246130 --- /dev/null +++ b/packages/common/README.md @@ -0,0 +1 @@ +# NodeJs Shared Server Library diff --git a/packages/common/gpl-3.0.txt b/packages/common/gpl-3.0.txt new file mode 100644 index 0000000..94a9ed0 --- /dev/null +++ b/packages/common/gpl-3.0.txt @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/packages/common/package.json b/packages/common/package.json new file mode 100644 index 0000000..2195db3 --- /dev/null +++ b/packages/common/package.json @@ -0,0 +1,47 @@ +{ + "name": "@ever-platform/common", + "description": "Ever Platform Shared Core", + "license": "AGPL-3.0", + "version": "0.4.3", + "homepage": "https://ever.co", + "repository": { + "type": "git", + "url": "https://github.com/ever-co/ever-demand.git" + }, + "bugs": { + "url": "https://github.com/ever-co/ever-demand/issues" + }, + "private": false, + "author": { + "name": "Ever Co. LTD", + "email": "ever@ever.co", + "url": "https://ever.co" + }, + "scripts": { + "watch": "tsc -p ./tsconfig.build.json -w", + "build": "rimraf build && tsc -p ./tsconfig.build.json", + "lint": "tslint --fix --project ./", + "test": "jest --config ./jest.config.js" + }, + "publishConfig": { + "access": "public" + }, + "dependencies": { + "lodash": "^4.17.21", + "mongoose": "^6.0.11", + "reflect-metadata": "^0.1.13", + "rxjs": "^7.4.0", + "stripe": "^8.183.0", + "typeorm": "^0.2.38", + "underscore": "^1.13.1", + "underscore.string": "^3.3.5" + }, + "devDependencies": { + "@types/node": "16.11.0", + "ts-node": "~10.3.0", + "tslint": "^6.1.1", + "rimraf": "^3.0.2", + "typescript": "~4.5.3" + }, + "snyk": false +} diff --git a/packages/common/src/@pyro/db/db-create-object.ts b/packages/common/src/@pyro/db/db-create-object.ts new file mode 100644 index 0000000..267b9f0 --- /dev/null +++ b/packages/common/src/@pyro/db/db-create-object.ts @@ -0,0 +1,8 @@ +import { PyroObjectId } from './object-id'; +import { DBObject } from '@pyro/db/db-object'; + +export interface DBCreateObject { + _id?: PyroObjectId; +} + +export type CreateObject> = T['CreateObjectTYPE']; diff --git a/packages/common/src/@pyro/db/db-find-object.ts b/packages/common/src/@pyro/db/db-find-object.ts new file mode 100644 index 0000000..33e9ad9 --- /dev/null +++ b/packages/common/src/@pyro/db/db-find-object.ts @@ -0,0 +1,4 @@ +import { DBObject } from '@pyro/db/index'; + +// TODO maybe use here the new type mapping feature. +export type FindObject> = any; diff --git a/packages/common/src/@pyro/db/db-object.ts b/packages/common/src/@pyro/db/db-object.ts new file mode 100644 index 0000000..bc68524 --- /dev/null +++ b/packages/common/src/@pyro/db/db-object.ts @@ -0,0 +1,89 @@ +import { getPreSchema } from './schema'; +import { DBRawObject } from './db-raw-object'; +import { DBCreateObject } from './db-create-object'; +import { PyroObjectId } from './object-id'; +import * as mongoose from 'mongoose'; +import { toDate } from '../../utils'; +import * as _ from 'lodash'; +import { Column, PrimaryColumn } from 'typeorm'; + +export interface DBObjectClass extends Function { + modelName?: string; +} + +export abstract class DBObject< + RawObject extends CreateObject & DBRawObject, + CreateObject extends DBCreateObject +> implements DBRawObject +{ + // to allow inferring the generic args types + readonly CreateObjectTYPE: CreateObject; + + // to allow inferring the generic args types + readonly RawObjectTYPE: RawObject; + + static modelName = ''; + + constructor(obj: RawObject) { + + if (obj != undefined) { + _.assign(this, obj); + + if ( + mongoose != null && + mongoose.Types != null && + mongoose.Types.ObjectId != null + ) { + if (obj && obj['_id']) { + this['_id'] = mongoose.Types.ObjectId.createFromHexString( + obj['_id'].toString() + ); + } + } + } + } + + @PrimaryColumn() + _id: PyroObjectId; + + /** + * Time when entity was created + * Note: can be string too because send in socket io. + * + * @type {(Date | string)} + * @memberof DBObject + */ + @Column() + _createdAt: Date | string; + + /** + * Time when entity was updated last time + * + * @type {(Date | string)} + * @memberof DBObject + */ + @Column() + _updatedAt: Date | string; + + get createdAt(): Date { + return this._createdAt != null ? toDate(this._createdAt) : null; + } + + get updatedAt(): Date { + return this._updatedAt != null ? toDate(this._updatedAt) : null; + } + + get id(): string { + return this._id.toString(); + } + + isEqual(p: this) { + for (const prop in getPreSchema(this.constructor as any)) { + if (this[prop] !== p[prop]) { + return false; + } + } + + return true; + } +} diff --git a/packages/common/src/@pyro/db/db-raw-object.ts b/packages/common/src/@pyro/db/db-raw-object.ts new file mode 100644 index 0000000..10d03a7 --- /dev/null +++ b/packages/common/src/@pyro/db/db-raw-object.ts @@ -0,0 +1,14 @@ +import { PyroObjectId } from './object-id'; +import { DBCreateObject } from './db-create-object'; +import { DBObject } from '@pyro/db/db-object'; + +/** + * Data Transfer Object (DTO) + */ +export interface DBRawObject extends DBCreateObject { + _id: PyroObjectId; + _createdAt: Date | string; + _updatedAt: Date | string; +} + +export type RawObject> = T['CreateObjectTYPE']; diff --git a/packages/common/src/@pyro/db/db-update-object.ts b/packages/common/src/@pyro/db/db-update-object.ts new file mode 100644 index 0000000..dc2379b --- /dev/null +++ b/packages/common/src/@pyro/db/db-update-object.ts @@ -0,0 +1,4 @@ +import { DBObject } from '@pyro/db/index'; + +// TODO maybe use here the new type mapping feature. +export type UpdateObject> = any; diff --git a/packages/common/src/@pyro/db/index.ts b/packages/common/src/@pyro/db/index.ts new file mode 100644 index 0000000..6c381bd --- /dev/null +++ b/packages/common/src/@pyro/db/index.ts @@ -0,0 +1,7 @@ +export { DBCreateObject } from './db-create-object'; +export { DBObject } from './db-object'; +export { DBRawObject } from './db-raw-object'; +export { ModelName } from './model'; +export { PyroObjectId } from './object-id'; +export { Schema, getSchema, getPreSchema, Index } from './schema'; +export { Types } from './types'; diff --git a/packages/common/src/@pyro/db/model.ts b/packages/common/src/@pyro/db/model.ts new file mode 100644 index 0000000..ebde396 --- /dev/null +++ b/packages/common/src/@pyro/db/model.ts @@ -0,0 +1,7 @@ +import { DBObjectClass } from './db-object'; + +export function ModelName(name: string): ClassDecorator { + return (target: DBObjectClass) => { + target.modelName = name; + }; +} diff --git a/packages/common/src/@pyro/db/object-id.ts b/packages/common/src/@pyro/db/object-id.ts new file mode 100644 index 0000000..c93bde1 --- /dev/null +++ b/packages/common/src/@pyro/db/object-id.ts @@ -0,0 +1,3 @@ +export interface PyroObjectId { + toString(): string; +} diff --git a/packages/common/src/@pyro/db/schema.ts b/packages/common/src/@pyro/db/schema.ts new file mode 100644 index 0000000..dc2d173 --- /dev/null +++ b/packages/common/src/@pyro/db/schema.ts @@ -0,0 +1,91 @@ +import { DBObject, DBObjectClass } from './db-object'; +import 'reflect-metadata'; +import * as _ from 'lodash'; +import { Schema as MongooseSchema, SchemaDefinition } from 'mongoose'; + +const mongooseSchemasKey: string = 'mongooseSchemas'; +const mongooseIndexesKey: string = 'mongooseIndexes'; + +export function Schema(schema: any): PropertyDecorator { + return (target: object, propertyKey: string | symbol) => { + let mongooseSchemas = Reflect.getMetadata( + mongooseSchemasKey, + target.constructor + ); + + if (typeof mongooseSchemas === 'undefined' || mongooseSchemas == null) { + mongooseSchemas = {}; + } + + mongooseSchemas[propertyKey] = schema; + + Reflect.defineMetadata( + mongooseSchemasKey, + mongooseSchemas, + target.constructor + ); + }; +} + +export function Index(value: string | number): PropertyDecorator { + return (target: object, propertyKey: string | symbol) => { + let indexesObj = Reflect.getMetadata( + mongooseIndexesKey, + target.constructor + ); + + if (typeof indexesObj === 'undefined' || indexesObj == null) { + indexesObj = {}; + } + + indexesObj[propertyKey] = value; + + Reflect.defineMetadata( + mongooseIndexesKey, + indexesObj, + target.constructor + ); + }; +} + +export function getPreSchema(DBObj: DBObjectClass): SchemaDefinition { + const mongooseSchemas = Reflect.getMetadata(mongooseSchemasKey, DBObj); + + if (mongooseSchemas != null) { + return mongooseSchemas; + } else { + return {}; + } +} + +export function getSchema(DBObj: DBObjectClass): MongooseSchema { + const preSchema = getPreSchema(DBObj); + + const schema = new MongooseSchema(preSchema, { + timestamps: { + createdAt: '_createdAt', + updatedAt: '_updatedAt', + }, + }); + + const mongooseIndexes = Reflect.getMetadata(mongooseIndexesKey, DBObj); + + if (mongooseIndexes != null) { + schema.index(mongooseIndexes); + } + + _.each(preSchema, (SubType: any, property) => { + if (SubType.prototype instanceof DBObject) { + // check if SubType extends DBObject + _.each(getSchema(SubType).indexes(), (index: any) => { + _.each(index, (indexValue: any, indexProperty: string) => { + schema.index({ + [property + '.' + indexProperty]: indexValue, + }); + }); + }); + } + }); + + return schema; +} diff --git a/packages/common/src/@pyro/db/types.ts b/packages/common/src/@pyro/db/types.ts new file mode 100644 index 0000000..bf66f89 --- /dev/null +++ b/packages/common/src/@pyro/db/types.ts @@ -0,0 +1,47 @@ +import { Schema } from './schema'; + +export const Types = { + String: (s?: string) => { + if (s == null) { + return Schema({ type: String, required: true }); + } else { + return Schema({ type: String, default: s }); + } + }, + + Number: (n?: number) => { + if (n == null) { + return Schema({ type: Number, required: true }); + } else { + return Schema({ type: Number, default: n }); + } + }, + + Boolean: (b?: boolean) => { + if (b == null) { + return Schema({ type: Boolean, required: true }); + } else { + return Schema({ type: Boolean, default: b }); + } + }, + + Date: (d?: number | ((n: number) => void)) => { + if (d == null) { + return Schema({ type: Date, required: true }); + } else { + return Schema({ type: Date, default: d }); + } + }, + + Ref(Type: any, options: any = {}): PropertyDecorator { + return (target: object, propertyKey: string | symbol) => { + const multi = Array.isArray(Type); + + const op = { ...options }; + op.type = String; + op.ref = (multi ? Type[0] : Type).modelName; + + Schema(multi ? [op] : op)(target, propertyKey); + }; + }, +}; diff --git a/packages/common/src/StorageService.ts b/packages/common/src/StorageService.ts new file mode 100644 index 0000000..c2ceec6 --- /dev/null +++ b/packages/common/src/StorageService.ts @@ -0,0 +1,27 @@ +export class StorageService { + /* /**\ + * Allows us to associate class property with storage property, + * So they would refer to the same object on get or set of it. + * @param storageKey + * @returns PropertyDecorator + * @constructor + */ + /*public Store(storageKey: string): PropertyDecorator { + function StorageDecorator(target, prop: string) { + + Object.defineProperty(target, prop, { + get: () => { + return this[storageKey]; + }, + set: (v) => { + this[storageKey] = v; + }, + enumerable: true, + configurable: true + }); + + } + + return StorageDecorator; + }*/ +} diff --git a/packages/common/src/consts/consts.ts b/packages/common/src/consts/consts.ts new file mode 100644 index 0000000..69093bc --- /dev/null +++ b/packages/common/src/consts/consts.ts @@ -0,0 +1,11 @@ +export class Consts { + // TODO: read from DB + static get langAbbreviations() { + return { + en: 'en', + bg: 'bg', + he: 'he', + ru: 'ru', + }; + } +} diff --git a/packages/common/src/data/abbreviation-to-country.ts b/packages/common/src/data/abbreviation-to-country.ts new file mode 100644 index 0000000..0f39c4e --- /dev/null +++ b/packages/common/src/data/abbreviation-to-country.ts @@ -0,0 +1,252 @@ +export const countries = { + AD: 'Andorra', + AE: 'United Arab Emirates', + AF: 'Afghanistan', + AG: 'Antigua and Barbuda', + AI: 'Anguilla', + AL: 'Albania', + AM: 'Armenia', + AO: 'Angola', + AQ: 'Antarctica', + AR: 'Argentina', + AS: 'American Samoa', + AT: 'Austria', + AU: 'Australia', + AW: 'Aruba', + AX: 'Åland', + AZ: 'Azerbaijan', + BA: 'Bosnia and Herzegovina', + BB: 'Barbados', + BD: 'Bangladesh', + BE: 'Belgium', + BF: 'Burkina Faso', + BG: 'Bulgaria', + BH: 'Bahrain', + BI: 'Burundi', + BJ: 'Benin', + BL: 'Saint Barthélemy', + BM: 'Bermuda', + BN: 'Brunei', + BO: 'Bolivia', + BQ: 'Bonaire', + BR: 'Brazil', + BS: 'Bahamas', + BT: 'Bhutan', + BV: 'Bouvet Island', + BW: 'Botswana', + BY: 'Belarus', + BZ: 'Belize', + CA: 'Canada', + CC: 'Cocos [Keeling] Islands', + CD: 'Democratic Republic of the Congo', + CF: 'Central African Republic', + CG: 'Republic of the Congo', + CH: 'Switzerland', + CI: 'Ivory Coast', + CK: 'Cook Islands', + CL: 'Chile', + CM: 'Cameroon', + CN: 'China', + CO: 'Colombia', + CR: 'Costa Rica', + CU: 'Cuba', + CV: 'Cape Verde', + CW: 'Curacao', + CX: 'Christmas Island', + CY: 'Cyprus', + CZ: 'Czech Republic', + DE: 'Germany', + DJ: 'Djibouti', + DK: 'Denmark', + DM: 'Dominica', + DO: 'Dominican Republic', + DZ: 'Algeria', + EC: 'Ecuador', + EE: 'Estonia', + EG: 'Egypt', + EH: 'Western Sahara', + ER: 'Eritrea', + ES: 'Spain', + ET: 'Ethiopia', + FI: 'Finland', + FJ: 'Fiji', + FK: 'Falkland Islands', + FM: 'Micronesia', + FO: 'Faroe Islands', + FR: 'France', + GA: 'Gabon', + GB: 'United Kingdom', + GD: 'Grenada', + GE: 'Georgia', + GF: 'French Guiana', + GG: 'Guernsey', + GH: 'Ghana', + GI: 'Gibraltar', + GL: 'Greenland', + GM: 'Gambia', + GN: 'Guinea', + GP: 'Guadeloupe', + GQ: 'Equatorial Guinea', + GR: 'Greece', + GS: 'South Georgia and the South Sandwich Islands', + GT: 'Guatemala', + GU: 'Guam', + GW: 'Guinea-Bissau', + GY: 'Guyana', + HK: 'Hong Kong', + HM: 'Heard Island and McDonald Islands', + HN: 'Honduras', + HR: 'Croatia', + HT: 'Haiti', + HU: 'Hungary', + ID: 'Indonesia', + IE: 'Ireland', + IL: 'Israel', + IM: 'Isle of Man', + IN: 'India', + IO: 'British Indian Ocean Territory', + IQ: 'Iraq', + IR: 'Iran', + IS: 'Iceland', + IT: 'Italy', + JE: 'Jersey', + JM: 'Jamaica', + JO: 'Jordan', + JP: 'Japan', + KE: 'Kenya', + KG: 'Kyrgyzstan', + KH: 'Cambodia', + KI: 'Kiribati', + KM: 'Comoros', + KN: 'Saint Kitts and Nevis', + KP: 'North Korea', + KR: 'South Korea', + KW: 'Kuwait', + KY: 'Cayman Islands', + KZ: 'Kazakhstan', + LA: 'Laos', + LB: 'Lebanon', + LC: 'Saint Lucia', + LI: 'Liechtenstein', + LK: 'Sri Lanka', + LR: 'Liberia', + LS: 'Lesotho', + LT: 'Lithuania', + LU: 'Luxembourg', + LV: 'Latvia', + LY: 'Libya', + MA: 'Morocco', + MC: 'Monaco', + MD: 'Moldova', + ME: 'Montenegro', + MF: 'Saint Martin', + MG: 'Madagascar', + MH: 'Marshall Islands', + MK: 'Macedonia', + ML: 'Mali', + MM: 'Myanmar [Burma]', + MN: 'Mongolia', + MO: 'Macao', + MP: 'Northern Mariana Islands', + MQ: 'Martinique', + MR: 'Mauritania', + MS: 'Montserrat', + MT: 'Malta', + MU: 'Mauritius', + MV: 'Maldives', + MW: 'Malawi', + MX: 'Mexico', + MY: 'Malaysia', + MZ: 'Mozambique', + NA: 'Namibia', + NC: 'New Caledonia', + NE: 'Niger', + NF: 'Norfolk Island', + NG: 'Nigeria', + NI: 'Nicaragua', + NL: 'Netherlands', + NO: 'Norway', + NP: 'Nepal', + NR: 'Nauru', + NU: 'Niue', + NZ: 'New Zealand', + OM: 'Oman', + PA: 'Panama', + PE: 'Peru', + PF: 'French Polynesia', + PG: 'Papua New Guinea', + PH: 'Philippines', + PK: 'Pakistan', + PL: 'Poland', + PM: 'Saint Pierre and Miquelon', + PN: 'Pitcairn Islands', + PR: 'Puerto Rico', + PS: 'Palestine', + PT: 'Portugal', + PW: 'Palau', + PY: 'Paraguay', + QA: 'Qatar', + RE: 'Réunion', + RO: 'Romania', + RS: 'Serbia', + RU: 'Russia', + RW: 'Rwanda', + SA: 'Saudi Arabia', + SB: 'Solomon Islands', + SC: 'Seychelles', + SD: 'Sudan', + SE: 'Sweden', + SG: 'Singapore', + SH: 'Saint Helena', + SI: 'Slovenia', + SJ: 'Svalbard and Jan Mayen', + SK: 'Slovakia', + SL: 'Sierra Leone', + SM: 'San Marino', + SN: 'Senegal', + SO: 'Somalia', + SR: 'Suriname', + SS: 'South Sudan', + ST: 'São Tomé and Príncipe', + SV: 'El Salvador', + SX: 'Sint Maarten', + SY: 'Syria', + SZ: 'Swaziland', + TC: 'Turks and Caicos Islands', + TD: 'Chad', + TF: 'French Southern Territories', + TG: 'Togo', + TH: 'Thailand', + TJ: 'Tajikistan', + TK: 'Tokelau', + TL: 'East Timor', + TM: 'Turkmenistan', + TN: 'Tunisia', + TO: 'Tonga', + TR: 'Turkey', + TT: 'Trinidad and Tobago', + TV: 'Tuvalu', + TW: 'Taiwan', + TZ: 'Tanzania', + UA: 'Ukraine', + UG: 'Uganda', + UM: 'U.S. Minor Outlying Islands', + US: 'United States', + UY: 'Uruguay', + UZ: 'Uzbekistan', + VA: 'Vatican City', + VC: 'Saint Vincent and the Grenadines', + VE: 'Venezuela', + VG: 'British Virgin Islands', + VI: 'U.S. Virgin Islands', + VN: 'Vietnam', + VU: 'Vanuatu', + WF: 'Wallis and Futuna', + WS: 'Samoa', + XK: 'Kosovo', + YE: 'Yemen', + YT: 'Mayotte', + ZA: 'South Africa', + ZM: 'Zambia', + ZW: 'Zimbabwe', +}; diff --git a/packages/common/src/data/countries.json b/packages/common/src/data/countries.json new file mode 100644 index 0000000..c1c6c50 --- /dev/null +++ b/packages/common/src/data/countries.json @@ -0,0 +1,254 @@ +{ + "countries": [ + "Andorra", + "United Arab Emirates", + "Afghanistan", + "Antigua and Barbuda", + "Anguilla", + "Albania", + "Armenia", + "Angola", + "Antarctica", + "Argentina", + "American Samoa", + "Austria", + "Australia", + "Aruba", + "Åland", + "Azerbaijan", + "Bosnia and Herzegovina", + "Barbados", + "Bangladesh", + "Belgium", + "Burkina Faso", + "Bulgaria", + "Bahrain", + "Burundi", + "Benin", + "Saint Barthélemy", + "Bermuda", + "Brunei", + "Bolivia", + "Bonaire", + "Brazil", + "Bahamas", + "Bhutan", + "Bouvet Island", + "Botswana", + "Belarus", + "Belize", + "Canada", + "Cocos [Keeling] Islands", + "Democratic Republic of the Congo", + "Central African Republic", + "Republic of the Congo", + "Switzerland", + "Ivory Coast", + "Cook Islands", + "Chile", + "Cameroon", + "China", + "Colombia", + "Costa Rica", + "Cuba", + "Cape Verde", + "Curacao", + "Christmas Island", + "Cyprus", + "Czech Republic", + "Germany", + "Djibouti", + "Denmark", + "Dominica", + "Dominican Republic", + "Algeria", + "Ecuador", + "Estonia", + "Egypt", + "Western Sahara", + "Eritrea", + "Spain", + "Ethiopia", + "Finland", + "Fiji", + "Falkland Islands", + "Micronesia", + "Faroe Islands", + "France", + "Gabon", + "United Kingdom", + "Grenada", + "Georgia", + "French Guiana", + "Guernsey", + "Ghana", + "Gibraltar", + "Greenland", + "Gambia", + "Guinea", + "Guadeloupe", + "Equatorial Guinea", + "Greece", + "South Georgia and the South Sandwich Islands", + "Guatemala", + "Guam", + "Guinea-Bissau", + "Guyana", + "Hong Kong", + "Heard Island and McDonald Islands", + "Honduras", + "Croatia", + "Haiti", + "Hungary", + "Indonesia", + "Ireland", + "Israel", + "Isle of Man", + "India", + "British Indian Ocean Territory", + "Iraq", + "Iran", + "Iceland", + "Italy", + "Jersey", + "Jamaica", + "Jordan", + "Japan", + "Kenya", + "Kyrgyzstan", + "Cambodia", + "Kiribati", + "Comoros", + "Saint Kitts and Nevis", + "North Korea", + "South Korea", + "Kuwait", + "Cayman Islands", + "Kazakhstan", + "Laos", + "Lebanon", + "Saint Lucia", + "Liechtenstein", + "Sri Lanka", + "Liberia", + "Lesotho", + "Lithuania", + "Luxembourg", + "Latvia", + "Libya", + "Morocco", + "Monaco", + "Moldova", + "Montenegro", + "Saint Martin", + "Madagascar", + "Marshall Islands", + "Macedonia", + "Mali", + "Myanmar [Burma]", + "Mongolia", + "Macao", + "Northern Mariana Islands", + "Martinique", + "Mauritania", + "Montserrat", + "Malta", + "Mauritius", + "Maldives", + "Malawi", + "Mexico", + "Malaysia", + "Mozambique", + "Namibia", + "New Caledonia", + "Niger", + "Norfolk Island", + "Nigeria", + "Nicaragua", + "Netherlands", + "Norway", + "Nepal", + "Nauru", + "Niue", + "New Zealand", + "Oman", + "Panama", + "Peru", + "French Polynesia", + "Papua New Guinea", + "Philippines", + "Pakistan", + "Poland", + "Saint Pierre and Miquelon", + "Pitcairn Islands", + "Puerto Rico", + "Palestine", + "Portugal", + "Palau", + "Paraguay", + "Qatar", + "Réunion", + "Romania", + "Serbia", + "Russia", + "Rwanda", + "Saudi Arabia", + "Solomon Islands", + "Seychelles", + "Sudan", + "Sweden", + "Singapore", + "Saint Helena", + "Slovenia", + "Svalbard and Jan Mayen", + "Slovakia", + "Sierra Leone", + "San Marino", + "Senegal", + "Somalia", + "Suriname", + "South Sudan", + "São Tomé and Príncipe", + "El Salvador", + "Sint Maarten", + "Syria", + "Swaziland", + "Turks and Caicos Islands", + "Chad", + "French Southern Territories", + "Togo", + "Thailand", + "Tajikistan", + "Tokelau", + "East Timor", + "Turkmenistan", + "Tunisia", + "Tonga", + "Turkey", + "Trinidad and Tobago", + "Tuvalu", + "Taiwan", + "Tanzania", + "Ukraine", + "Uganda", + "U.S. Minor Outlying Islands", + "United States", + "Uruguay", + "Uzbekistan", + "Vatican City", + "Saint Vincent and the Grenadines", + "Venezuela", + "British Virgin Islands", + "U.S. Virgin Islands", + "Vietnam", + "Vanuatu", + "Wallis and Futuna", + "Samoa", + "Kosovo", + "Yemen", + "Mayotte", + "South Africa", + "Zambia", + "Zimbabwe" + ] +} diff --git a/packages/common/src/data/food-product-names.ts b/packages/common/src/data/food-product-names.ts new file mode 100644 index 0000000..55e63d9 --- /dev/null +++ b/packages/common/src/data/food-product-names.ts @@ -0,0 +1,281 @@ +export const productNames: string[] = [ + 'Tenderized Herbs & Duck', + 'Thermal-Cooked Bittersweet Pigeon', + 'Pressure-Cooked Soy Alligator', + 'Stewed Thyme & Parsley Tuna', + 'Baked Onions & Cream Bisque', + 'Steamed Fennel & Lime Rice', + 'Pecan and White Wine Milk', + 'Banana and Blueberry Pie', + 'Cranberry Bread', + 'Cashew Pie', + 'Pan-Fried Basil & Clove Bear', + 'Oven-Baked Mushroom Duck', + 'Tenderized Mustard & Rosemary Fish', + 'Pressure-Fried Juniper Herring', + 'Marinated Sweet & Savory Tart', + 'Pressure-Fried Saffron Stuffed Bread', + 'Avocado and Coffee Crumble', + 'Red Wine and Grapefruit Trifle', + 'Caramel Delight', + 'Mandarin Fruitcake', + 'Fried Vanilla Horse', + 'Dried Saffron & Shallot Pheasant', + 'Oven-Grilled Hot & Sweet Shrimps', + 'Engine-Cooked Salt & Savory Scallops', + 'Blanched Peppermint Tart', + 'Roasted Garlic & Rosemary Winter Vegetables', + 'Peanut and Cherry Mooncake', + 'Red Wine and Grapefruit Gingerbread', + 'Caramel Surprise', + 'Kiwi Milk', + 'Slow-Cooked Jasmine Pigeon', + 'Engine-Cooked Sour & Cream Pork', + 'Pressure-Fried Mushroom & Garlic Alligator', + 'Seared Honey & Nuts Scallops', + 'Roasted Potatoes & Roll', + 'Roasted Mushroom Stuffed Bread', + 'Cocoa and Cranberry Wafer', + 'Chocolate and Orange Surprise', + 'Caramel Toffee', + 'Licorice Cone', + 'Slow-Cooked Sweet & Fresh Mammoth', + 'Stuffed Rhubarb Quail', + 'Brined Coconut & Ginger Salmon', + 'Pressure-Fried Mustard Crocodile', + 'Poached Savory Salad', + 'Thermal-Cooked Olive Omelet', + 'Almond and Strawberry Pud', + 'Cherry and Peanut Pastry', + 'Nutmeg Split', + 'Brined Ginger Duck', + 'Stuffed Raspberry & Peanut Mutton', + 'Tenderized Raspberry & Peanut Lobster', + 'Pan-Fried Basil & Clove Crocodile', + 'Blanched Stew of Tofu', + 'Roasted Pine Forest Mushrooms', + 'Chestnut and White Wine Pudding', + 'Apple and Mango Wafer', + 'Cherry Crumble', + 'Pecan Pie', + 'Basted Cinnamon & Thyme Ostrich', + 'Baked Beets & Orange Mutton', + 'Stewed Mushroom & Rosemary Frog', + 'Dried Mushroom & Rosemary Shrimps', + 'Pan-Fried Basil & Lime Stracciatella', + 'Tea-Smoked Salted Bake', + 'Pecan and White Wine Toast', + 'Pineapple and Dark Chocolate Trifle', + 'Apple Yogurt', + 'Cinnamon Crumble', + 'Dried Fennel & Lemon Mammoth', + 'Dried Apricots & Honey Pheasant', + 'Braised Hot & Sweet Cockles', + 'Dried Rosemary & Onion Oysters', + 'Braised Mint Linguine', + 'Slow-Cooked Figs & Olive Winter Vegetables', + 'Walnut and Cinnamon Wafer', + 'Melon and Red Wine Doughnut', + 'Banana Pastry', + 'Peach Cobbler', + 'Simmered Garlic & Onion Ostrich', + 'Tenderized Mountain Pork', + 'Poached Easter-Style Herring', + 'Brined Vegetables & Snapper', + 'Cured Rhubarb Linguine', + 'Sautéed Lemon Winter Greens', + 'Mango and Cherry Bombe', + 'Orange and Grape Cake', + 'Papaya Gingerbread', + 'Raspberry Sorbet', + 'Braised Parmesan Chicken', + 'Grilled Mushroom Turkey', + 'Barbecued Vegetables & Alligator', + 'Cured Fennel & Garlic Herring', + 'Poached Fennel & Lemon Scrambled Egg', + 'Fire-Roasted Rice & Bisque', + 'Vanilla and Gooseberry Waffles', + 'Honey and Papaya Candy', + 'Saffron Genoise', + 'White Chocolate Mooncake', + 'Basted Beets & Orange Turkey', + 'Oven-Grilled Yogurt Duck', + 'Sautéed Garlic & Rosemary Scallops', + 'Baked Black Pepper Salmon', + 'Simmered Mountain Bruschetta', + 'Braised Vanilla & Mint Bake', + 'Nutmeg and Caramel Molten Cake', + 'Vanilla and Lemon Milk', + 'Pecan Fudge', + 'Milk Chocolate Snacks', + 'Brined Peanuts & Chicken', + 'Tenderized Vanilla Lamb', + 'Oven-Grilled Cucumber & Lime Cockles', + 'Breaded Stew of Crab', + 'Slow-Cooked Ginger & Honey Sandwich', + 'Breaded Peach & Vinegar Spring Greens', + 'Peanut and Peach Sorbet', + 'White Chocolate and Ginger Delight', + 'Grapefruit Waffles', + 'Hazelnut Tarte Tatin', + 'Grilled Jasmine Mutton', + 'Brined Honey & Almond Mutton', + 'Stuffed Asparagus Oysters', + 'Roasted Honey & Thyme Crocodile', + 'Barbecued Orange & Mustard Lasagna', + 'Blanched Lemon Chestnuts', + 'Avocado and Gooseberry Trifle', + 'Lemon and Peanut Snacks', + 'Guava Steamed Pudding', + 'Cocoa Bonbons', + 'Gentle-Fried Forest Mutton', + 'Stuffed Vinegar Pork', + 'Deep-Fried Parmesan Prawns', + 'Roasted Egg & Beet Herring', + 'Cooked Wasabi Bake', + 'Thermal-Cooked Vanilla & Mint Bake', + 'Grapefruit and Red Wine Soufflé', + 'Guava and Milk Chocolate Gingerbread', + 'Licorice Fruit Salad', + 'Nutmeg Cookies', + 'Pressure-Cooked Pepper & Mango Quail', + 'Cooked Honey & Almond Pork', + 'Deep-Fried Apples & Mustard Tuna', + 'Slow-Cooked Basil & Lime Clams', + 'Dried Honey & Nuts Risotto', + 'Tea-Smoked Orange & Mustard Tart', + 'Cranberry and Cardamom Pudding', + 'Dark Chocolate and Caramel Jelly', + 'Plum Cobbler', + 'Praline Bombe', + 'Stuffed Blueberry Lamb', + 'Sautéed Saffron Venison', + 'Broasted Olives & Mustard Lobster', + 'Steamed Saffron Salmon', + 'Dried Basil & Lime Taco', + 'Sautéed Nuts & Bruschetta', + 'Honey and Elderberry Crumble', + 'Cardamom and Licorice Tarte Tatin', + 'Cherry Delight', + 'Dry-Roasted Western-Style Horse', + 'Pressure-Fried Saffron & Shallot Bear', + 'Stewed Rosemary Fish', + 'Simmered Thyme & Parsley Mussels', + 'Simmered Fennel Scrambled Egg', + 'Slow-Cooked Sugar Bread', + 'Lime and Cherry Fruitcake', + 'Apple and Gooseberry Pound Cake', + 'Pecan Bombe', + 'White Chocolate Cake', + 'Orange Split', + 'Blanched Sour & Cream Pigeon', + 'Pressure-Cooked Egg & Beans Bear', + 'Smoked Lemon Frog', + 'Fried Dark Ale Cod', + 'Stewed Rosemary Bruschetta', + 'Tenderized Mint & Orange Bisque', + 'Rum and Papaya Yogurt', + 'Milk Chocolate and Dark Chocolate Roll', + 'Coconut Candy', + 'Peach Tart', + 'Stuffed Carrot & Coriander Beef', + 'Deep-Fried Truffles & Lamb', + 'Smoked Carrots & Ginger Scallops', + 'Shallow-Fried Vanilla Tuna', + 'Basted Beets & Lemon Bake', + 'Stewed Light Beer Pasta', + 'Chocolate and Grapefruit Waffles', + 'Kiwi and Gooseberry Sundae', + 'Cinnamon Whip', + 'Passion Fruit Cake', + 'Sautéed Beets & Lemon Ostrich', + 'Poached Cucumber & Lime Pheasant', + 'Broasted Cinnamon & Thyme Crab', + 'Fire-Roasted Juniper Mussels', + 'Dried Fennel & Lemon Stracciatella', + 'Dry-Roasted Mustard & Rosemary Nut Mix', + 'Cardamom and Rum Cookies', + 'Passion Fruit and Date Gingerbread', + 'White Wine Wafer', + 'Walnut Soufflé', + 'Fire-Grilled Ginger Rabbit', + 'Broasted Egg & Beans Pork', + 'Cooked Lemon Lobster', + 'Oven-Baked Mango & Pine Lobster', + 'Breaded Cucumber & Lime Bake', + 'Baked Savory Tofu', + 'Pineapple and Saffron Mooncake', + 'Cinnamon and Cocoa Toffee', + 'Chestnut Ice Cream', + 'Cranberry Gingerbread', + 'Tea-Smoked Western-Style Pork', + 'Broasted Sour Rabbit', + 'Cured Honey Prawns', + 'Dried Egg & Beans Alligator', + 'Sautéed Mustard & Rosemary Stracciatella', + 'Fire-Grilled Raspberry & Peanut Lasagna', + 'Coffee and Dark Chocolate Pound Cake', + 'Banana and Mango Sundae', + 'Cardamom Cheesecake', + 'Honey Pudding', + 'Tea-Smoked Western-Style Pork', + 'Broasted Sour Rabbit', + 'Cured Honey Prawns', + 'Dried Egg & Beans Alligator', + 'Sautéed Mustard & Rosemary Stracciatella', + 'Fire-Grilled Raspberry & Peanut Lasagna', + 'Coffee and Dark Chocolate Pound Cake', + 'Banana and Mango Sundae', + 'Cardamom Cheesecake', + 'Honey Pudding', + 'Marinated Orange Rabbit', + 'Smoked Asparagus Mutton', + 'Marinated Easter-Style Herring', + 'Roasted Sweet & Savory Scallops', + 'Engine-Cooked Red Whine Moussaka', + 'Pickled Fennel & Lemon Stuffed Bread', + 'Praline and Plum Cheesecake', + 'Lime and Cardamom Yogurt', + 'Mint Tarte Tatin', + 'Cooked Pepper Ostrich', + 'Dry-Roasted Onions & Tomato Bear', + 'Infused Confit of Snapper', + 'Slow-Cooked Rhubarb Crab', + 'Shallow-Fried Northern-Style Kebabs', + 'Broasted Pepper Calzone', + 'Pecan and Honey Waffles', + 'Grapefruit and Grape Tart', + 'Elderberry Pie', + 'Pineapple Yogurt', + 'White Wine Soufflé', + 'Dry-Roasted Curry of Rabbit', + 'Oven-Grilled Parsnip & Pear Horse', + 'Oven-Grilled Egg & Coconut Fish', + 'Shallow-Fried Vinegar Crocodile', + 'Seared Honey-Coated Flatbread', + 'Stewed Confit of Tofu', + 'Honey and Mint Pancakes', + 'Plum and Nutmeg Cobbler', + 'Lemon Cheesecake', + 'Cocoa Pudding', + 'Gentle-Fried Mango & Pine Quail', + 'Fried Mushroom Horse', + 'Tenderized Mint & Mustard Alligator', + 'Broasted Ginger Alligator', + 'Stewed Beets & Lemon Minestrone', + 'Blanched Mushroom Forest Mushrooms', + 'Plum and Coconut Soufflé', + 'Praline and Dark Chocolate Surprise', + 'Almond Yogurt', + 'Elderberry Delight', + 'Stuffed Black Pepper Quail', + 'Tenderized Coriander & Lemon Boar', + 'Fire-Roasted Pepper & Lime Salmon', + 'Brined Pepper & Lime Herring', + 'Dry-Roasted Dark Ale Omelet', + 'Marinated Figs & Olive Spring Greens', + 'Cashew and Melon Mooncake', + 'Coconut and Pistachio Crispies', + 'Cherry Cobbler', + 'Saffron Genoise', +]; diff --git a/packages/common/src/data/image-urls.ts b/packages/common/src/data/image-urls.ts new file mode 100644 index 0000000..4d69be6 --- /dev/null +++ b/packages/common/src/data/image-urls.ts @@ -0,0 +1,134 @@ +// Images are from https://www.pexels.com, see license https://www.pexels.com/photo-license +export const images = { + food: [ + 'https://images.pexels.com/photos/675951/pexels-photo-675951.jpeg?auto=compress&cs=tinysrgb&h=350', + 'https://images.pexels.com/photos/1268551/pexels-photo-1268551.jpeg?auto=compress&cs=tinysrgb&h=350', + 'https://images.pexels.com/photos/262918/pexels-photo-262918.jpeg?auto=compress&cs=tinysrgb&h=350', + 'https://images.pexels.com/photos/958545/pexels-photo-958545.jpeg?auto=compress&cs=tinysrgb&h=350', + 'https://images.pexels.com/photos/262978/pexels-photo-262978.jpeg?auto=compress&cs=tinysrgb&h=350', + 'https://images.pexels.com/photos/370984/pexels-photo-370984.jpeg?auto=compress&cs=tinysrgb&h=350', + 'https://images.pexels.com/photos/5938/food-salad-healthy-lunch.jpg?auto=compress&cs=tinysrgb&h=350', + 'https://images.pexels.com/photos/5317/food-salad-restaurant-person.jpg?auto=compress&cs=tinysrgb&h=350', + 'https://images.pexels.com/photos/299347/pexels-photo-299347.jpeg?auto=compress&cs=tinysrgb&h=350', + 'https://images.pexels.com/photos/62097/pexels-photo-62097.jpeg?auto=compress&cs=tinysrgb&h=350', + 'https://images.pexels.com/photos/374052/pexels-photo-374052.jpeg?auto=compress&cs=tinysrgb&h=350', + 'https://images.pexels.com/photos/541216/pexels-photo-541216.jpeg?auto=compress&cs=tinysrgb&h=350', + 'https://images.pexels.com/photos/8758/food-dinner-lemon-rice.jpg?auto=compress&cs=tinysrgb&h=350', + 'https://images.pexels.com/photos/2232/vegetables-italian-pizza-restaurant.jpg?auto=compress&cs=tinysrgb&h=350', + 'https://images.pexels.com/photos/1234535/pexels-photo-1234535.jpeg?auto=compress&cs=tinysrgb&h=350', + 'https://images.pexels.com/photos/687824/pexels-photo-687824.jpeg?auto=compress&cs=tinysrgb&h=350', + 'https://images.pexels.com/photos/306059/pexels-photo-306059.jpeg?auto=compress&cs=tinysrgb&h=350', + 'https://images.pexels.com/photos/64208/pexels-photo-64208.jpeg?auto=compress&cs=tinysrgb&h=350', + 'https://images.pexels.com/photos/5928/salad-healthy-diet-spinach.jpg?auto=compress&cs=tinysrgb&h=350', + 'https://images.pexels.com/photos/1059943/pexels-photo-1059943.jpeg?auto=compress&cs=tinysrgb&h=350', + 'https://images.pexels.com/photos/1058435/pexels-photo-1058435.jpeg?auto=compress&cs=tinysrgb&h=350', + 'https://images.pexels.com/photos/461382/pexels-photo-461382.jpeg?auto=compress&cs=tinysrgb&h=350', + 'https://images.pexels.com/photos/407293/pexels-photo-407293.jpeg?auto=compress&cs=tinysrgb&h=350', + 'https://images.pexels.com/photos/929192/pexels-photo-929192.jpeg?auto=compress&cs=tinysrgb&h=350', + 'https://images.pexels.com/photos/461326/pexels-photo-461326.jpeg?auto=compress&cs=tinysrgb&h=350', + 'https://images.pexels.com/photos/551997/pexels-photo-551997.jpeg?auto=compress&cs=tinysrgb&h=350', + 'https://images.pexels.com/photos/59943/pexels-photo-59943.jpeg?auto=compress&cs=tinysrgb&h=350', + 'https://images.pexels.com/photos/5249/bread-food-restaurant-people.jpg?auto=compress&cs=tinysrgb&h=350', + 'https://images.pexels.com/photos/723031/pexels-photo-723031.jpeg?auto=compress&cs=tinysrgb&h=350', + 'https://images.pexels.com/photos/299348/pexels-photo-299348.jpeg?auto=compress&cs=tinysrgb&h=350', + 'https://images.pexels.com/photos/858508/pexels-photo-858508.jpeg?auto=compress&cs=tinysrgb&h=350', + 'https://images.pexels.com/photos/7782/food-plate-wood-restaurant.jpg?auto=compress&cs=tinysrgb&h=350', + 'https://images.pexels.com/photos/842142/pexels-photo-842142.jpeg?auto=compress&cs=tinysrgb&h=350', + 'https://images.pexels.com/photos/1152237/pexels-photo-1152237.jpeg?auto=compress&cs=tinysrgb&h=350', + 'https://images.pexels.com/photos/341048/pexels-photo-341048.png?auto=compress&cs=tinysrgb&h=350', + 'https://images.pexels.com/photos/299351/pexels-photo-299351.jpeg?auto=compress&cs=tinysrgb&h=350', + 'https://images.pexels.com/photos/236887/pexels-photo-236887.jpeg?auto=compress&cs=tinysrgb&h=350', + 'https://images.pexels.com/photos/670705/pexels-photo-670705.jpeg?auto=compress&cs=tinysrgb&h=350', + 'https://images.pexels.com/photos/698857/pexels-photo-698857.jpeg?auto=compress&cs=tinysrgb&h=350', + 'https://images.pexels.com/photos/37405/food-prawn-asian.jpg?auto=compress&cs=tinysrgb&h=350', + 'https://images.pexels.com/photos/263041/pexels-photo-263041.jpeg?auto=compress&cs=tinysrgb&h=350', + 'https://images.pexels.com/photos/1310777/pexels-photo-1310777.jpeg?auto=compress&cs=tinysrgb&h=350', + 'https://images.pexels.com/photos/1123250/pexels-photo-1123250.jpeg?auto=compress&cs=tinysrgb&h=350', + 'https://images.pexels.com/photos/6461/food-plate-rucola-salad.jpg?auto=compress&cs=tinysrgb&h=350', + 'https://images.pexels.com/photos/5619/plate-white-fish-chilli.jpg?auto=compress&cs=tinysrgb&h=350', + 'https://images.pexels.com/photos/128408/pexels-photo-128408.jpeg?auto=compress&cs=tinysrgb&h=350', + 'https://images.pexels.com/photos/958547/pexels-photo-958547.jpeg?auto=compress&cs=tinysrgb&h=350', + 'https://images.pexels.com/photos/323682/pexels-photo-323682.jpeg?auto=compress&cs=tinysrgb&h=350', + 'https://images.pexels.com/photos/590477/pexels-photo-590477.jpeg?auto=compress&cs=tinysrgb&h=350', + 'https://images.pexels.com/photos/3690/food-restaurant-hand-dinner.jpg?auto=compress&cs=tinysrgb&h=350', + 'https://images.pexels.com/photos/858496/pexels-photo-858496.jpeg?auto=compress&cs=tinysrgb&h=350', + 'https://images.pexels.com/photos/107527/pexels-photo-107527.jpeg?auto=compress&cs=tinysrgb&h=350', + 'https://images.pexels.com/photos/23086/food-restaurant-kitchen-meat-23086.jpg?auto=compress&cs=tinysrgb&h=350', + 'https://images.pexels.com/photos/326278/pexels-photo-326278.jpeg?auto=compress&cs=tinysrgb&h=350', + 'https://images.pexels.com/photos/277253/pexels-photo-277253.jpeg?auto=compress&cs=tinysrgb&h=350', + 'https://images.pexels.com/photos/461198/pexels-photo-461198.jpeg?auto=compress&cs=tinysrgb&h=350', + 'https://images.pexels.com/photos/54455/cook-food-kitchen-eat-54455.jpeg?auto=compress&cs=tinysrgb&h=350', + 'https://images.pexels.com/photos/70497/pexels-photo-70497.jpeg?auto=compress&cs=tinysrgb&h=350', + 'https://images.pexels.com/photos/376464/pexels-photo-376464.jpeg?auto=compress&cs=tinysrgb&h=350', + 'https://images.pexels.com/photos/769289/pexels-photo-769289.jpeg?auto=compress&cs=tinysrgb&h=350', + 'https://images.pexels.com/photos/262959/pexels-photo-262959.jpeg?auto=compress&cs=tinysrgb&h=350', + 'https://images.pexels.com/photos/1464601/pexels-photo-1464601.jpeg?auto=compress&cs=tinysrgb&h=350', + 'https://images.pexels.com/photos/1458756/pexels-photo-1458756.jpeg?auto=compress&cs=tinysrgb&h=350', + 'https://images.pexels.com/photos/296888/pexels-photo-296888.jpeg?auto=compress&cs=tinysrgb&h=350', + 'https://images.pexels.com/photos/1267320/pexels-photo-1267320.jpeg?auto=compress&cs=tinysrgb&h=350', + 'https://images.pexels.com/photos/205961/pexels-photo-205961.jpeg?auto=compress&cs=tinysrgb&h=350', + 'https://images.pexels.com/photos/629093/pexels-photo-629093.jpeg?auto=compress&cs=tinysrgb&h=350', + 'https://images.pexels.com/photos/708587/pexels-photo-708587.jpeg?auto=compress&cs=tinysrgb&h=350', + 'https://images.pexels.com/photos/905847/pexels-photo-905847.jpeg?auto=compress&cs=tinysrgb&h=350', + 'https://images.pexels.com/photos/1246957/pexels-photo-1246957.jpeg?auto=compress&cs=tinysrgb&h=350', + 'https://images.pexels.com/photos/221143/pexels-photo-221143.jpeg?auto=compress&cs=tinysrgb&h=350', + 'https://images.pexels.com/photos/545058/pexels-photo-545058.jpeg?auto=compress&cs=tinysrgb&h=350', + 'https://images.pexels.com/photos/65175/pexels-photo-65175.jpeg?auto=compress&cs=tinysrgb&h=350', + 'https://images.pexels.com/photos/1268549/pexels-photo-1268549.jpeg?auto=compress&cs=tinysrgb&h=350', + 'https://images.pexels.com/photos/842142/pexels-photo-842142.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=350', + 'https://images.pexels.com/photos/9532/food-plate-restaurant-eating.jpg?auto=compress&cs=tinysrgb&h=350', + 'https://images.pexels.com/photos/192933/pexels-photo-192933.jpeg?auto=compress&cs=tinysrgb&h=350', + 'https://images.pexels.com/photos/671956/pexels-photo-671956.jpeg?auto=compress&cs=tinysrgb&h=350', + 'https://images.pexels.com/photos/1376962/pexels-photo-1376962.jpeg?auto=compress&cs=tinysrgb&h=350', + 'https://images.pexels.com/photos/697058/pexels-photo-697058.jpeg?auto=compress&cs=tinysrgb&h=350', + 'https://images.pexels.com/photos/365459/pexels-photo-365459.jpeg?auto=compress&cs=tinysrgb&h=350', + 'https://images.pexels.com/photos/1034940/pexels-photo-1034940.jpeg?auto=compress&cs=tinysrgb&h=350', + 'https://images.pexels.com/photos/1439320/pexels-photo-1439320.jpeg?auto=compress&cs=tinysrgb&h=350', + 'https://images.pexels.com/photos/5916/food-salad-healthy-colorful.jpg?auto=compress&cs=tinysrgb&h=350', + 'https://images.pexels.com/photos/271715/pexels-photo-271715.jpeg?auto=compress&cs=tinysrgb&h=350', + 'https://images.pexels.com/photos/225236/pexels-photo-225236.jpeg?auto=compress&cs=tinysrgb&h=350', + 'https://images.pexels.com/photos/229493/pexels-photo-229493.jpeg?auto=compress&cs=tinysrgb&h=350', + 'https://images.pexels.com/photos/299351/pexels-photo-299351.jpeg?auto=compress&cs=tinysrgb&h=350', + 'https://images.pexels.com/photos/263017/pexels-photo-263017.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=350', + 'https://images.pexels.com/photos/221149/pexels-photo-221149.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=350', + 'https://images.pexels.com/photos/36749/food-salmon-seeded-mustard-dinner.jpg?auto=compress&cs=tinysrgb&dpr=2&h=350', + 'https://images.pexels.com/photos/128388/pexels-photo-128388.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=350', + 'https://images.pexels.com/photos/39634/pexels-photo-39634.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=350', + 'https://images.pexels.com/photos/233305/pexels-photo-233305.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=350', + 'https://images.pexels.com/photos/1309595/pexels-photo-1309595.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=350', + 'https://images.pexels.com/photos/958550/pexels-photo-958550.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=350', + 'https://images.pexels.com/photos/299352/pexels-photo-299352.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=350', + 'https://images.pexels.com/photos/370015/pexels-photo-370015.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=350', + 'https://images.pexels.com/photos/764925/pexels-photo-764925.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=350', + 'https://images.pexels.com/photos/56014/pizza-pizza-' + + 'service-italian-eat-56014.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=350', + 'https://images.pexels.com/photos/2215/food-salad-healthy-vegetables.jpg?auto=compress&cs=tinysrgb&dpr=2&h=350', + 'https://images.pexels.com/photos/257816/pexels-photo-257816.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=350', + ], + productCategories: { + burger: + 'https://res.cloudinary.com/da0dq1udf/image/upload/v1545135485/Burgers.jpg', + alcohol: + 'https://res.cloudinary.com/da0dq1udf/image/upload/v1545135485/Alcohol.jpg', + vegetarian: + 'https://res.cloudinary.com/da0dq1udf/image/upload/v1545135484/Vegetarian.jpg', + salads: + 'https://res.cloudinary.com/da0dq1udf/image/upload/v1545135482/Salad.jpg', + fastFood: + 'https://res.cloudinary.com/da0dq1udf/image/upload/v1545135482/FastFood.jpg', + sushi: + 'https://res.cloudinary.com/da0dq1udf/image/upload/v1545135482/Sushi.jpg', + pasta: + 'https://res.cloudinary.com/da0dq1udf/image/upload/v1545135481/Pasta.jpg', + pizza: + 'https://res.cloudinary.com/da0dq1udf/image/upload/v1545135481/Pizza.jpg', + drinks: + 'https://res.cloudinary.com/da0dq1udf/image/upload/v1545135481/Drinks.jpg', + soups: + 'https://res.cloudinary.com/da0dq1udf/image/upload/v1545135481/Soups.jpg', + dessert: + 'https://res.cloudinary.com/da0dq1udf/image/upload/v1545135481/Desserts.jpg', + meat: + 'https://res.cloudinary.com/da0dq1udf/image/upload/v1545135481/Meat.jpg', + }, +}; diff --git a/packages/common/src/entities/Admin.ts b/packages/common/src/entities/Admin.ts new file mode 100644 index 0000000..ab74267 --- /dev/null +++ b/packages/common/src/entities/Admin.ts @@ -0,0 +1,99 @@ +import { DBObject, ModelName } from '../@pyro/db'; +import IAdmin, { IAdminCreateObject } from '../interfaces/IAdmin'; +import { Schema, Types } from '@pyro/db'; +import { Entity, Column } from 'typeorm'; + +/** + * Registered Admin Users (e.g. Administrators) + * TODO: will be renamed to "Users" (after we rename "Users" to "Customers") + * Note: not related to Customers! + * + * @class Admin + * @extends {DBObject} + * @implements {IAdmin} + */ +@ModelName('Admin') +@Entity({ name: 'admins' }) +class Admin extends DBObject implements IAdmin { + /** + * User Email + * + * @type {string} + * @memberof Admin + */ + @Schema({ type: String, unique: true }) + @Types.String() + @Column() + email: string; + + /** + * Username + * + * @type {string} + * @memberof Admin + */ + @Types.String() + @Column() + name: string; + + /** + * Password hash + * + * @type {string} + * @memberof Admin + */ + @Schema({ type: String, select: false }) + @Types.String() + @Column() + hash: string; + + /** + * User Picture (Avatar) Url + * + * @type {string} + * @memberof Admin + */ + @Types.String() + @Column() + pictureUrl: string; + + /** + * Is User Removed (Deleted) + * + * @type {boolean} + * @memberof Admin + */ + @Types.Boolean(false) + @Column() + isDeleted: boolean; + + /** + * User First Name + * + * @type {string} + * @memberof Admin + */ + @Schema({ + type: String, + required: false, + validate: new RegExp(`^[a-z ,.'-]+$`, 'i'), + }) + @Column() + firstName?: string; + + /** + * User Last Name + * + * @type {string} + * @memberof Admin + */ + @Schema({ + type: String, + required: false, + validate: new RegExp(`^[a-z ,.'-]+$`, 'i'), + }) + @Column() + lastName?: string; +} + +export default Admin; diff --git a/packages/common/src/entities/Carrier.ts b/packages/common/src/entities/Carrier.ts new file mode 100644 index 0000000..6d4ec2f --- /dev/null +++ b/packages/common/src/entities/Carrier.ts @@ -0,0 +1,247 @@ +import CarrierStatus from '../enums/CarrierStatus'; +import GeoLocation from './GeoLocation'; +import { DBObject, getSchema, ModelName, Schema, Types } from '../@pyro/db'; +import ICarrier, { ICarrierCreateObject } from '../interfaces/ICarrier'; +import { Entity, Column } from 'typeorm'; + +/** + * Carriers (Drivers, people or organizations which deliver products to customers) + * + * @class Carrier + * @extends {DBObject} + * @implements {ICarrier} + */ +@ModelName('Carrier') +@Entity({ name: 'carriers' }) +class Carrier extends DBObject + implements ICarrier { + constructor(carrier: ICarrier) { + super(carrier); + + if (carrier && carrier.geoLocation) { + this.geoLocation = new GeoLocation(carrier.geoLocation); + } + } + + /** + * First Name (required) + * + * @type {string} + * @memberof Carrier + */ + @Schema({ + type: String, + required: true, + validate: new RegExp(`^[a-z ,.'-]+$`, 'i'), + }) + @Column() + firstName: string; + + /** + * Last Name (required) + * + * @type {string} + * @memberof Carrier + */ + @Schema({ + type: String, + required: true, + validate: new RegExp(`^[a-z ,.'-]+$`, 'i'), + }) + @Column() + lastName: string; + + /** + * How many deliveries totally this carrier made + * + * @type {number} + * @memberof Carrier + */ + @Types.Number(0) + @Column() + numberOfDeliveries: number; + + /** + * Current location of the Carrier (updated periodically during carrier movements) + * + * @type {GeoLocation} + * @memberof Carrier + */ + @Schema(getSchema(GeoLocation)) + geoLocation: GeoLocation; + + /** + * Current Apartment (if carrier located in some appartment at the moment) + * + * @type {string} + * @memberof Carrier + */ + @Schema(String) + @Column() + apartment?: string; + + /** + * Is Carrier Active at the moment in the system + * Value 'True' means carrier enabled in system, + * Value 'False' means carrier completely disabled in the system (e.g. was fired) + * This setting is set by Admin, + * not by carrier itself (he/she set 'status' field instead in the carrier mobile app) + * @type {boolean} + * @memberof Carrier + */ + @Types.Boolean(true) + @Column() + isActive: boolean; + + /** + * Is Carrier removed completely from the system + * + * @type {boolean} + * @memberof Carrier + */ + @Types.Boolean(false) + @Column() + isDeleted: boolean; + + /** + * Current carrier status (set via his mobile app), e.g. Online or Offline + * + * @type {CarrierStatus} + * @memberof Carrier + */ + @Types.Number(CarrierStatus.Offline) + @Column() + status: CarrierStatus; + + /** + * Unique Carrier username, which used for Authentication of the Carrier in the app + * + * @type {string} + * @memberof Carrier + */ + @Schema({ type: String, unique: true, required: true }) + @Column() + username: string; + + /** + * Shows if carrier is shared or assigned to some store + * + * @type {boolean} + * @memberof Carrier + */ + @Schema({ type: Boolean, required: false }) + @Column() + shared: boolean; + + /** + * Hashed password + * + * @type {string} + * @memberof Carrier + */ + @Schema({ type: String, required: false, select: false }) + @Column() + hash?: string; + + /** + * Carrier Phone number (usually mobile phone) + * + * @type {string} + * @memberof Carrier + */ + @Types.String() + @Column() + phone: string; + + /** + * URL for Logo (brand image) or Avatar of the Carrier + * + * @type {string} + * @memberof Carrier + */ + @Schema({ + type: String, + required: true, + validate: new RegExp( + `(http(s?):)s?:?(\/\/[^"']*\.(?:png|jpg|jpeg|gif|png|svg))` + ), + }) + @Column() + logo: string; + + /** + * Carrier email (optional) + * + * @type {string} + * @memberof Carrier + */ + @Schema({ + type: String, + required: false, + sparse: true, + unique: true, + }) + @Column() + email: string; + + @Schema([String]) + @Column() + skippedOrderIds: string[]; + + /** + * How many deliveries carrier made today + * TODO: implement calculation, useful for stats + * + * @type {number} + * @memberof Carrier + */ + @Types.Number(0) + @Column() + deliveriesCountToday: number; + + /** + * How much km (or miles) Carrier made today during deliveries + * TODO: implement calculation, useful for stats + * @type {number} + * @memberof Carrier + */ + @Types.Number(0) + @Column() + totalDistanceToday: number; + + /** + * IDs of Carrier devices (mobiles) + * Used to send Push Notifications to Carrier devices + * + * + * @type {string[]} + * @memberof Carrier + */ + @Schema([String]) + devicesIds: string[]; + + /** + * Is shared the carrier, i.e if not then only merchant which already has such carrier assigned + * (inside "carriersIds" array on Merchant) can use such carrier and no one else + * + * @type {boolean} + * @memberof Carrier + */ + @Types.Boolean(false) + @Column() + isSharedCarrier: boolean; + + /** + * Full name of the Carrier + * + * @readonly + * @type {string} + * @memberof Carrier + * @returns full name of the carrier + */ + get fullName(): string { + return `${this.firstName} ${this.lastName}`; + } +} + +export default Carrier; diff --git a/packages/common/src/entities/Currency.ts b/packages/common/src/entities/Currency.ts new file mode 100644 index 0000000..943326e --- /dev/null +++ b/packages/common/src/entities/Currency.ts @@ -0,0 +1,42 @@ +import { ModelName, DBObject, Types, Schema } from '@pyro/db'; +import { Entity, Column } from 'typeorm'; +import ICurrency, { ICurrencyCreateObject } from '../interfaces/ICurrency'; + +/** + * @class Currency + * @extends {DBObject} + * @implements {ICurrency} + */ +@ModelName('Currency') +@Entity({ name: 'currencies' }) +class Currency extends DBObject + implements ICurrency { + /** + * Currency Code + * + * @type {string} + * @memberof Currency + */ + @Schema({ type: String, unique: true }) + @Column() + currencyCode: string; + + /** + * Is Currency removed completely from the system + * + * @type {boolean} + * @memberof Currency + */ + @Types.Boolean(false) + @Column() + isDeleted: boolean; +} + +export default Currency; + +export const countriesDefaultCurrencies = { + IL: 'ILS', + RU: 'RUB', + US: 'USD', + BG: 'BGN', +}; diff --git a/packages/common/src/entities/Device.ts b/packages/common/src/entities/Device.ts new file mode 100644 index 0000000..9ad8a1d --- /dev/null +++ b/packages/common/src/entities/Device.ts @@ -0,0 +1,63 @@ +import { ModelName, DBObject, Schema, Types } from '../@pyro/db'; +import { IDeviceRawObject, IDeviceCreateObject } from '../interfaces/IDevice'; +import IPlatform from '../interfaces/IPlatform'; +import ILanguage from '../interfaces/ILanguage'; +import { Entity, Column } from 'typeorm'; + +/** + * Represent Device (e.g. Table or Browser, used by someone to access one of our apps) + * + * @class Device + * @extends {DBObject} + * @implements {IDeviceRawObject} + */ +@ModelName('Device') +@Entity({ name: 'devices' }) +class Device extends DBObject + implements IDeviceRawObject { + /** + * ChannelID used for push notifications + * + * @type {(string | null)} + * @memberof Device + */ + @Schema({ type: String, required: false }) + @Column() + channelId: string | null; + + /** + * Platform of the device (broser, ios, android, etc) + * + * @type {IPlatform} + * @memberof Device + */ + @Types.String() + @Column() + type: IPlatform; + + /** + * Language setting on the device + * + * @type {ILanguage} + * @memberof Device + */ + @Types.String('en-US') + @Column() + language: ILanguage; + + /** + * Unique device Id + * + * @type {string} + * @memberof Device + */ + @Types.String() + @Column() + uuid: string; + + @Types.Boolean(false) + @Column() + isDeleted: boolean; +} + +export default Device; diff --git a/packages/common/src/entities/GeoLocation.ts b/packages/common/src/entities/GeoLocation.ts new file mode 100644 index 0000000..a6b2840 --- /dev/null +++ b/packages/common/src/entities/GeoLocation.ts @@ -0,0 +1,618 @@ +import { DBObject, Index, ModelName, Schema } from '../@pyro/db'; +import { + default as IGeoLocation, + IGeoLocationCreateObject, + ILocation, +} from '../interfaces/IGeoLocation'; +import * as _ from 'underscore'; +import { countries } from '../data/abbreviation-to-country'; +import { Column } from 'typeorm'; + +export const locationPreSchema = { + type: { type: String }, + coordinates: [Number], +}; + +/** + * Stores Geo Location (Address) of some physical entity (Customer, Warehouse, Carrier, etc) + * + * @class GeoLocation + * @extends {DBObject} + * @implements {IGeoLocation} + */ +@ModelName('GeoLocation') +class GeoLocation extends DBObject + implements IGeoLocation { + @Schema({ type: Number, required: false }) + @Column() + countryId: Country | null; + + @Schema({ required: false, type: String }) + @Column() + postcode?: string | null; + + @Schema({ required: false, type: String }) + @Column() + notes?: string | null; + + @Schema({ type: String, required: false }) + @Column() + apartment?: string | null; + + @Schema({ type: String, required: false }) + @Column() + city: string | null; + + @Schema({ type: String, required: false }) + @Column() + streetAddress: string | null; + + @Schema({ type: String, required: false }) + @Column() + house: string | null; + + @Index('2dsphere') + @Schema(locationPreSchema) + loc: ILocation; + + get countryName(): CountryName { + return getCountryName(this.countryId); + } + + set countryName(countryName: CountryName) {} + + get isLocValid(): any { + return ( + this.loc.type === 'Point' && + typeof _.isArray(this.loc.coordinates) && + this.loc.coordinates.length === 2 && + _.every(this.loc.coordinates, (c) => _.isFinite(c)) + ); + } + + get isValid(): any { + const notEmptyString = (s: string) => _.isString(s) && !_.isEmpty(s); + return _.every( + [this.city, this.streetAddress, this.house], + notEmptyString + ); + } + + get coordinates(): { lng: number; lat: number } { + // In "MongoDB" geojson standard coordinates list the longitude first and then latitude: + return { + lng: this.loc.coordinates[0], + lat: this.loc.coordinates[1], + }; + } + + set coordinates(coords: { lng: number; lat: number }) { + this.loc.coordinates = [coords.lng, coords.lat]; + } +} + +export default GeoLocation; + +export enum Country { + // If you wonder what abbreviation must have to use for + // specific country see "ever\shared\core\data\abbreviation-to-country.json" + AD, + AE, + AF, + AG, + AI, + AL, + AM, + AO, + AQ, + AR, + AS, + AT, + AU, + AW, + AX, + AZ, + BA, + BB, + BD, + BE, + BF, + BG, + BH, + BI, + BJ, + BL, + BM, + BN, + BO, + BQ, + BR, + BS, + BT, + BV, + BW, + BY, + BZ, + CA, + CC, + CD, + CF, + CG, + CH, + CI, + CK, + CL, + CM, + CN, + CO, + CR, + CU, + CV, + CW, + CX, + CY, + CZ, + DE, + DJ, + DK, + DM, + DO, + DZ, + EC, + EE, + EG, + EH, + ER, + ES, + ET, + FI, + FJ, + FK, + FM, + FO, + FR, + GA, + GB, + GD, + GE, + GF, + GG, + GH, + GI, + GL, + GM, + GN, + GP, + GQ, + GR, + GS, + GT, + GU, + GW, + GY, + HK, + HM, + HN, + HR, + HT, + HU, + ID, + IE, + IL, + IM, + IN, + IO, + IQ, + IR, + IS, + IT, + JE, + JM, + JO, + JP, + KE, + KG, + KH, + KI, + KM, + KN, + KP, + KR, + KW, + KY, + KZ, + LA, + LB, + LC, + LI, + LK, + LR, + LS, + LT, + LU, + LV, + LY, + MA, + MC, + MD, + ME, + MF, + MG, + MH, + MK, + ML, + MM, + MN, + MO, + MP, + MQ, + MR, + MS, + MT, + MU, + MV, + MW, + MX, + MY, + MZ, + NA, + NC, + NE, + NF, + NG, + NI, + NL, + NO, + NP, + NR, + NU, + NZ, + OM, + PA, + PE, + PF, + PG, + PH, + PK, + PL, + PM, + PN, + PR, + PS, + PT, + PW, + PY, + QA, + RE, + RO, + RS, + RU, + RW, + SA, + SB, + SC, + SD, + SE, + SG, + SH, + SI, + SJ, + SK, + SL, + SM, + SN, + SO, + SR, + SS, + ST, + SV, + SX, + SY, + SZ, + TC, + TD, + TF, + TG, + TH, + TJ, + TK, + TL, + TM, + TN, + TO, + TR, + TT, + TV, + TW, + TZ, + UA, + UG, + UM, + US, + UY, + UZ, + VA, + VC, + VE, + VG, + VI, + VN, + VU, + WF, + WS, + XK, + YE, + YT, + ZA, + ZM, + ZW, +} + +export type CountryName = + | 'Andorra' + | 'United Arab Emirates' + | 'Afghanistan' + | 'Antigua and Barbuda' + | 'Anguilla' + | 'Albania' + | 'Armenia' + | 'Angola' + | 'Antarctica' + | 'Argentina' + | 'American Samoa' + | 'Austria' + | 'Australia' + | 'Aruba' + | 'Åland' + | 'Azerbaijan' + | 'Bosnia and Herzegovina' + | 'Barbados' + | 'Bangladesh' + | 'Belgium' + | 'Burkina Faso' + | 'Bulgaria' + | 'Bahrain' + | 'Burundi' + | 'Benin' + | 'Saint Barthélemy' + | 'Bermuda' + | 'Brunei' + | 'Bolivia' + | 'Bonaire' + | 'Brazil' + | 'Bahamas' + | 'Bhutan' + | 'Bouvet Island' + | 'Botswana' + | 'Belarus' + | 'Belize' + | 'Canada' + | 'Cocos [Keeling] Islands' + | 'Democratic Republic of the Congo' + | 'Central African Republic' + | 'Republic of the Congo' + | 'Switzerland' + | 'Ivory Coast' + | 'Cook Islands' + | 'Chile' + | 'Cameroon' + | 'China' + | 'Colombia' + | 'Costa Rica' + | 'Cuba' + | 'Cape Verde' + | 'Curacao' + | 'Christmas Island' + | 'Cyprus' + | 'Czech Republic' + | 'Germany' + | 'Djibouti' + | 'Denmark' + | 'Dominica' + | 'Dominican Republic' + | 'Algeria' + | 'Ecuador' + | 'Estonia' + | 'Egypt' + | 'Western Sahara' + | 'Eritrea' + | 'Spain' + | 'Ethiopia' + | 'Finland' + | 'Fiji' + | 'Falkland Islands' + | 'Micronesia' + | 'Faroe Islands' + | 'France' + | 'Gabon' + | 'United Kingdom' + | 'Grenada' + | 'Georgia' + | 'French Guiana' + | 'Guernsey' + | 'Ghana' + | 'Gibraltar' + | 'Greenland' + | 'Gambia' + | 'Guinea' + | 'Guadeloupe' + | 'Equatorial Guinea' + | 'Greece' + | 'South Georgia and the South Sandwich Islands' + | 'Guatemala' + | 'Guam' + | 'Guinea-Bissau' + | 'Guyana' + | 'Hong Kong' + | 'Heard Island and McDonald Islands' + | 'Honduras' + | 'Croatia' + | 'Haiti' + | 'Hungary' + | 'Indonesia' + | 'Ireland' + | 'Israel' + | 'Isle of Man' + | 'India' + | 'British Indian Ocean Territory' + | 'Iraq' + | 'Iran' + | 'Iceland' + | 'Italy' + | 'Jersey' + | 'Jamaica' + | 'Jordan' + | 'Japan' + | 'Kenya' + | 'Kyrgyzstan' + | 'Cambodia' + | 'Kiribati' + | 'Comoros' + | 'Saint Kitts and Nevis' + | 'North Korea' + | 'South Korea' + | 'Kuwait' + | 'Cayman Islands' + | 'Kazakhstan' + | 'Laos' + | 'Lebanon' + | 'Saint Lucia' + | 'Liechtenstein' + | 'Sri Lanka' + | 'Liberia' + | 'Lesotho' + | 'Lithuania' + | 'Luxembourg' + | 'Latvia' + | 'Libya' + | 'Morocco' + | 'Monaco' + | 'Moldova' + | 'Montenegro' + | 'Saint Martin' + | 'Madagascar' + | 'Marshall Islands' + | 'Macedonia' + | 'Mali' + | 'Myanmar [Burma]' + | 'Mongolia' + | 'Macao' + | 'Northern Mariana Islands' + | 'Martinique' + | 'Mauritania' + | 'Montserrat' + | 'Malta' + | 'Mauritius' + | 'Maldives' + | 'Malawi' + | 'Mexico' + | 'Malaysia' + | 'Mozambique' + | 'Namibia' + | 'New Caledonia' + | 'Niger' + | 'Norfolk Island' + | 'Nigeria' + | 'Nicaragua' + | 'Netherlands' + | 'Norway' + | 'Nepal' + | 'Nauru' + | 'Niue' + | 'New Zealand' + | 'Oman' + | 'Panama' + | 'Peru' + | 'French Polynesia' + | 'Papua New Guinea' + | 'Philippines' + | 'Pakistan' + | 'Poland' + | 'Saint Pierre and Miquelon' + | 'Pitcairn Islands' + | 'Puerto Rico' + | 'Palestine' + | 'Portugal' + | 'Palau' + | 'Paraguay' + | 'Qatar' + | 'Réunion' + | 'Romania' + | 'Serbia' + | 'Russia' + | 'Rwanda' + | 'Saudi Arabia' + | 'Solomon Islands' + | 'Seychelles' + | 'Sudan' + | 'Sweden' + | 'Singapore' + | 'Saint Helena' + | 'Slovenia' + | 'Svalbard and Jan Mayen' + | 'Slovakia' + | 'Sierra Leone' + | 'San Marino' + | 'Senegal' + | 'Somalia' + | 'Suriname' + | 'South Sudan' + | 'São Tomé and Príncipe' + | 'El Salvador' + | 'Sint Maarten' + | 'Syria' + | 'Swaziland' + | 'Turks and Caicos Islands' + | 'Chad' + | 'French Southern Territories' + | 'Togo' + | 'Thailand' + | 'Tajikistan' + | 'Tokelau' + | 'East Timor' + | 'Turkmenistan' + | 'Tunisia' + | 'Tonga' + | 'Turkey' + | 'Trinidad and Tobago' + | 'Tuvalu' + | 'Taiwan' + | 'Tanzania' + | 'Ukraine' + | 'Uganda' + | 'U.S. Minor Outlying Islands' + | 'United States' + | 'Uruguay' + | 'Uzbekistan' + | 'Vatican City' + | 'Saint Vincent and the Grenadines' + | 'Venezuela' + | 'British Virgin Islands' + | 'U.S. Virgin Islands' + | 'Vietnam' + | 'Vanuatu' + | 'Wallis and Futuna' + | 'Samoa' + | 'Kosovo' + | 'Yemen' + | 'Mayotte' + | 'South Africa' + | 'Zambia' + | 'Zimbabwe' + | null; + +export function getCountryName(country: null): null; +export function getCountryName(country: Country): CountryName; +export function getCountryName(country: Country | null): CountryName | null; +export function getCountryName(country: Country | null): CountryName | null { + return countries[Country[country]] || null; +} + +export const countriesIdsToNamesArray: { + id: Country; + name: CountryName; +}[] = Object.keys(countries) + .map((abbr) => { + return { id: Country[abbr], name: getCountryName(+Country[abbr]) }; + }) + .sort((c1, c2) => c1.name.localeCompare(c2.name)); diff --git a/packages/common/src/entities/Invite.ts b/packages/common/src/entities/Invite.ts new file mode 100644 index 0000000..be2c769 --- /dev/null +++ b/packages/common/src/entities/Invite.ts @@ -0,0 +1,71 @@ +import GeoLocation from './GeoLocation'; +import { DBObject, Schema, ModelName, Types, getSchema } from '../@pyro/db'; +import IInvite from '../interfaces/IInvite'; +import { IInviteCreateObject } from '../interfaces/IInvite'; +import { Entity, Column } from 'typeorm'; + +/** + * Invite for potential Customer + * Created as result of aproval of the Invite Request + * + * @class Invite + * @extends {DBObject} + * @implements {IInvite} + */ +@ModelName('Invite') +@Entity({ name: 'invites' }) +class Invite extends DBObject implements IInvite { + constructor(invite: IInvite) { + super(invite); + + if (invite && invite.geoLocation) { + this.geoLocation = new GeoLocation(invite.geoLocation); + } + } + + /** + * Invite code (usually just 4 digit number), which Customer can use to continue registration + * Note: invite code works only within specific radius location around customer address! + * + * @type {string} + * @memberof Invite + */ + @Types.String() + @Column() + code: string; + + /** + * Apartment (optional) for which invite will work + * + * @type {string} + * @memberof Invite + */ + @Types.String() + @Column() + apartment: string; + + /** + * Location (Address) of invited Customer + * + * @type {GeoLocation} + * @memberof Invite + */ + @Schema(getSchema(GeoLocation)) + geoLocation: GeoLocation; + + @Types.Boolean(false) + @Column() + isDeleted: boolean; + + toInviteRequestFindObject(): { + geoLocation: GeoLocation; + apartment: string; + } { + return { + geoLocation: this.geoLocation, + apartment: this.apartment, + }; + } +} + +export default Invite; diff --git a/packages/common/src/entities/InviteRequest.ts b/packages/common/src/entities/InviteRequest.ts new file mode 100644 index 0000000..d4f47dc --- /dev/null +++ b/packages/common/src/entities/InviteRequest.ts @@ -0,0 +1,128 @@ +import { DBObject, Schema, ModelName, Types, getSchema } from '../@pyro/db'; +import { + IInviteRequestCreateObject, + IInviteRequestRawObject, +} from '../interfaces/IInviteRequest'; +import { default as GeoLocation } from './GeoLocation'; +import IEnterByLocation from '../interfaces/IEnterByLocation'; +import { Entity, Column } from 'typeorm'; + +/** + * Requests for invite from potential Customers + * Customers ask to be invited (optionally, if invites system enabled) for given location (address). + * Later, Store owner decide if he wants to approve invite request (by generating invite record). + * + * @class InviteRequest + * @extends {DBObject} + * @implements {IInviteRequestRawObject} + */ +@ModelName('InviteRequest') +@Entity({ name: 'inviterequests' }) +class InviteRequest + extends DBObject + implements IInviteRequestRawObject { + constructor(inviteRequest: IInviteRequestRawObject) { + super(inviteRequest); + + if (inviteRequest && inviteRequest.geoLocation) { + this.geoLocation = new GeoLocation(inviteRequest.geoLocation); + } + } + + /** + * Current location (address) of person who request invite + * + * @type {GeoLocation} + * @memberof InviteRequest + */ + @Schema(getSchema(GeoLocation)) + geoLocation: GeoLocation; + + /** + * ID of potential customer device + * Note: used to send push notification to customer when store owner decide to invite her/him + * + * @type {string} + * @memberof InviteRequest + */ + @Schema({ required: false, type: String }) + @Column() + deviceId?: string; + + /** + * Apartment of potential Customer (optionally) + * + * @type {string} + * @memberof InviteRequest + */ + @Types.String() + @Column() + apartment: string; + + @Schema({ required: false, type: Boolean, default: false }) + @Column() + isManual?: boolean; + + @Types.Boolean(false) + @Column() + isDeleted: boolean; + + /** + * If this require invite approved, isInvited field set to true + * + * @type {boolean} + * @memberof InviteRequest + */ + @Types.Boolean(false) + @Column() + isInvited: boolean; + + /** + * The date when invite was sent to the potential customer + * + * @type {Date} + * @memberof InviteRequest + */ + @Schema({ type: Date, required: false }) + @Column() + invitedDate: Date; + + toAddressString(): string | null { + if (!this.geoLocation) { + return null; + } + + let address = `${this.geoLocation.streetAddress} ${this.geoLocation.house}`; + + if (this.apartment) { + address += `/${this.apartment}`; + } + + address += `, ${this.geoLocation.city}`; + + return address; + } + + toEnterByLocationToken(): IEnterByLocation { + if ( + this.geoLocation.house != null && + this.geoLocation.streetAddress != null && + this.geoLocation.city != null && + this.geoLocation.countryId != null + ) { + return { + apartment: this.apartment, + house: this.geoLocation.house, + streetAddress: this.geoLocation.streetAddress, + city: this.geoLocation.city, + postcode: this.geoLocation.postcode, + notes: this.geoLocation.notes, + countryId: this.geoLocation.countryId, + }; + } else { + throw new Error('GeoLocation is not full!'); + } + } +} + +export default InviteRequest; diff --git a/packages/common/src/entities/Order.ts b/packages/common/src/entities/Order.ts new file mode 100644 index 0000000..5829f17 --- /dev/null +++ b/packages/common/src/entities/Order.ts @@ -0,0 +1,489 @@ +import UserOrder from './UserOrder'; +import Warehouse from './Warehouse'; +import OrderProduct from './OrderProduct'; +import Carrier from './Carrier'; +import { + DBObject, + Schema, + Index, + ModelName, + Types, + getSchema, +} from '../@pyro/db'; +import { map } from 'underscore'; +import { sum } from 'lodash'; + +import OrderWarehouseStatus, { + warehouseStatusToString, +} from '../enums/OrderWarehouseStatus'; +import OrderCarrierStatus, { + carrierStatusToString, +} from '../enums/OrderCarrierStatus'; +import IOrder, { IOrderCreateObject } from '../interfaces/IOrder'; +import IWarehouse from '../interfaces/IWarehouse'; +import ICarrier from '../interfaces/ICarrier'; +import OrderStatus from '../enums/OrderStatus'; +import ILanguage from '../interfaces/ILanguage'; +import { Entity, Column } from 'typeorm'; +import IOrderProduct from '../interfaces/IOrderProduct'; +import DeliveryType from '../enums/DeliveryType'; + +/** + * Customer Order (for some products or services) + * + * @class Order + * @extends {DBObject} + * @implements {IOrder} + */ +@ModelName('Order') +@Entity({ name: 'orders' }) +class Order extends DBObject implements IOrder { + constructor(order: IOrder) { + super(order); + + if (order) { + if (order.user) { + this.user = new UserOrder(order.user); + } + + if (order.warehouse && typeof order.warehouse !== 'string') { + this.warehouse = new Warehouse(order.warehouse as IWarehouse); + } + + if (order.carrier && typeof order.carrier !== 'string') { + this.carrier = new Carrier(order.carrier as ICarrier); + } + + if (order.products) { + this.products = map( + order.products, + (orderProduct: IOrderProduct) => { + return new OrderProduct(orderProduct); + } + ); + } + } + } + + /** + * User who make order. Note: this is not a reference, + * but info we put here in the moment when Order is creating. + * This is needed because we want to not allow user to change + * this data in his profile later so it effects his orders + * + * @type {UserOrder} + * @memberof Order + */ + @Index(1) + @Schema(getSchema(UserOrder)) + user: UserOrder; + + /** + * Every order go to single merchant only. + * Currently, it is not possible to include items in the order from different merchants! + * + * @type {(Warehouse | string)} + * @memberof Order + */ + @Index(1) + @Types.Ref(Warehouse) + warehouse: Warehouse | string; + + get warehouseId(): string { + if (typeof this.warehouse === 'string') { + return this.warehouse; + } else { + return this.warehouse.id; + } + } + + /** + * Same as for customer, it's not a reference but copy of data about products + * + * @type {OrderProduct[]} + * @memberof Order + */ + @Schema([getSchema(OrderProduct)]) + products: OrderProduct[]; + + /** + * Client can confirm order or order can be auto-confirmed + * For example, client make an order from mobile app (press button "Buy"). + * Next, when he pay by CC mobile app send another request to server and set this field to true (confirm order) + * Alternative, could be to ask customer again like "Please confirm your order". + * Some stores however, may want orders to be automatically confirmed, e.g. when user click "Buy" button. + * They still will set separate request to set this field to "true" in such cases, but do not ask customer + * to explicitly confirm order + * + * Note: this field have nothing to do with Warehouse "ReadyForProcessing" + * which is set when warehouse Confirms an order, not when client (customer) confirms it + * + * @type {boolean} + * @memberof Order + */ + @Types.Boolean(false) + @Column() + isConfirmed: boolean; + + /** + * Is Order Cancelled + * + * @type {boolean} + * @memberof Order + */ + @Types.Boolean(false) + @Column() + isCancelled: boolean; + + /** + * Is Order wait for completion by user + * + * @type {boolean} + * @memberof Order + */ + @Types.Boolean(false) + @Column() + waitForCompletion: boolean; + + /** + * Is Order Paid by Customer + * + * @type {boolean} + * @memberof Order + */ + @Types.Boolean(false) + @Column() + isPaid: boolean; + + /** + * Completed may mean it's paid and customer get it + * OR customer don't take order and it get back to warehouse. It's FINAL in the flow. + * + * @readonly + * @type {boolean} + * @memberof Order + */ + get isCompleted(): boolean { + return ( + (this.isPaid && this.status === OrderStatus.Delivered) || + this.isCancelled + ); + } + + /** + * Deliver time for order (DateTime when order was actually delivered to customer) + * + * @type {Date} + * @memberof Order + */ + @Schema({ type: Date, required: false }) + @Column() + deliveryTime?: Date; + + /** + * Time when order processing is finished + * i.e order become canceled from customer or get failed during Store preparing etc. + * + * @type {Date} + * @memberof Order + */ + @Schema({ type: Date, required: false }) + @Column() + finishedProcessingTime?: Date; + + /** + * The time when some carrier start delivery the order + * + * @type {Date} + * @memberof Order + */ + @Schema({ type: Date, required: false }) + @Column() + startDeliveryTime?: Date; + + /** + * How many seconds more it should take to delivery order to customer + * (reset to 0 when order is delivered or when carrier is not deliver any order to the customer) + * + * @type {number} + * @memberof Order + */ + @Types.Number(0) + @Column() + deliveryTimeEstimate: number; + + /** + * Current Warehouse status of the order + * + * @type {OrderWarehouseStatus} + * @memberof Order + */ + @Types.Number(OrderWarehouseStatus.NoStatus) + @Column() + warehouseStatus: OrderWarehouseStatus; + + /** + * Current Carrier status of the order + * + * @type {OrderCarrierStatus} + * @memberof Order + */ + @Types.Number(OrderCarrierStatus.NoCarrier) + @Column() + carrierStatus: OrderCarrierStatus; + + /** + * Some carrier which responsible to deliver order to the client + * (can be empty if order is not planed for delivery yet) + * + * @type {(Carrier | string | null)} + * @memberof Order + */ + @Types.Ref(Carrier, { required: false }) + carrier?: Carrier; + + @Types.Boolean(false) + @Column() + isDeleted: boolean; + + @Schema({ type: String, required: false }) + @Column() + stripeChargeId?: string; + + /** + * Human readable Order Number in the Store (short id of order, unique in the given store only) + * + * @type {number} + * @memberof Order + */ + @Types.Number() + @Column() + orderNumber: number; + + /** + * Type of the order: Delivery or Takeaway + * + * @type {DeliveryType} + * @memberof Order + */ + @Types.Number(DeliveryType.Delivery) + @Column() + orderType: DeliveryType; + + get carrierId(): string | null { + if (this.carrier == null) { + return null; + } else if (typeof this.carrier === 'string') { + return this.carrier; + } else { + return this.carrier.id; + } + } + + get warehouseStatusText(): string { + return warehouseStatusToString(this.warehouseStatus); + } + + get carrierStatusText(): string { + return carrierStatusToString(this.carrierStatus); + } + + /** + * Get Total Price for an order + * + * @readonly + * @type {number} + * @memberof Order + */ + get totalPrice(): number { + return sum( + map( + this.products, + (product: OrderProduct) => product.count * product.price + ) + ); + } + + /** + * TODO: we should refactor and add support for all locales + * + * @param {ILanguage} language + * @returns {string} + * @memberof Order + */ + getStatusText(language: ILanguage): string { + switch (language) { + case 'en-US': + return this._getStatusTextEnglish(); + case 'he-IL': + return this._getStatusTextHebrew(); + case 'ru-RU': + return this._getStatusTextRussian(); + case 'bg-BG': + return this._getStatusTextBulgarian(); + case 'es-ES': + return this._getStatusTextSpanish(); + case 'fr-FR': + return this._getStatusTextFrench(); + default: + return 'BAD_STATUS'; + } + } + + /** + * Order Status based on Warehouse and Carrier Statuses together + * + * @readonly + * @type {OrderStatus} + * @memberof Order + */ + get status(): OrderStatus { + if ( + this.carrier == null || + this.carrierStatus <= OrderCarrierStatus.CarrierPickedUpOrder + ) { + if (this.warehouseStatus >= 200) { + return OrderStatus.WarehouseIssue; + } else if (this.isCancelled) { + return OrderStatus.CanceledWhileWarehousePreparation; + } else { + return OrderStatus.WarehousePreparation; + } + } else { + if (this.carrierStatus >= 200) { + return OrderStatus.CarrierIssue; + } else if (this.isCancelled) { + return OrderStatus.CanceledWhileInDelivery; + } else if ( + this.isPaid && + this.carrierStatus === OrderCarrierStatus.DeliveryCompleted + ) { + return OrderStatus.Delivered; + } else { + return OrderStatus.InDelivery; + } + } + } + + private _getStatusTextEnglish(): string { + switch (this.status) { + case OrderStatus.WarehousePreparation: + return 'Preparation'; + case OrderStatus.InDelivery: + return 'In Delivery'; + case OrderStatus.Delivered: + return 'Delivered'; + case OrderStatus.CanceledWhileWarehousePreparation: + case OrderStatus.CanceledWhileInDelivery: + return 'Cancelled'; + case OrderStatus.WarehouseIssue: + return 'Preparation Issue'; + case OrderStatus.CarrierIssue: + return 'Delivery Issue'; + default: + return 'BAD_STATUS'; + } + } + + private _getStatusTextBulgarian(): string { + switch (this.status) { + case OrderStatus.WarehousePreparation: + return 'Подготовка'; + case OrderStatus.InDelivery: + return 'Доставя се'; + case OrderStatus.Delivered: + return 'Доставено'; + case OrderStatus.CanceledWhileWarehousePreparation: + case OrderStatus.CanceledWhileInDelivery: + return 'Отказана'; + case OrderStatus.WarehouseIssue: + return 'Проблем при подготовката'; + case OrderStatus.CarrierIssue: + return 'Проблем при доставката'; + default: + return 'Проблем с поръчката'; + } + } + + private _getStatusTextHebrew(): string { + switch (this.status) { + case OrderStatus.WarehousePreparation: + return 'בהכנה'; + case OrderStatus.InDelivery: + return 'במשלוח'; + case OrderStatus.Delivered: + return 'הסתיים בצלחה'; + case OrderStatus.CanceledWhileWarehousePreparation: + case OrderStatus.CanceledWhileInDelivery: + return 'התבטל'; + case OrderStatus.WarehouseIssue: + return 'בעייה בהכנה'; + case OrderStatus.CarrierIssue: + return 'בעייה במשלוח'; + default: + return 'BAD_STATUS'; + } + } + + private _getStatusTextRussian(): string { + switch (this.status) { + case OrderStatus.WarehousePreparation: + return 'В подготовке'; + case OrderStatus.InDelivery: + return 'В доставки'; + case OrderStatus.Delivered: + return 'Доставлено'; + case OrderStatus.CanceledWhileWarehousePreparation: + case OrderStatus.CanceledWhileInDelivery: + return 'Отменено'; + case OrderStatus.WarehouseIssue: + return 'Проблема с подготовкой'; + case OrderStatus.CarrierIssue: + return 'Проблема с доставкой'; + default: + return 'BAD_STATUS'; + } + } + + private _getStatusTextSpanish() { + switch (this.status) { + case OrderStatus.WarehousePreparation: + return 'Preparación'; + case OrderStatus.InDelivery: + return 'En la entrega'; + case OrderStatus.Delivered: + return 'Entregado'; + case OrderStatus.CanceledWhileWarehousePreparation: + case OrderStatus.CanceledWhileInDelivery: + return 'Cancelado'; + case OrderStatus.WarehouseIssue: + return 'Problema de preparación'; + case OrderStatus.CarrierIssue: + return 'Problema de envio'; + default: + return 'BAD_STATUS'; + } + } + + private _getStatusTextFrench(): string { + switch (this.status) { + case OrderStatus.WarehousePreparation: + return 'Preparation'; + case OrderStatus.InDelivery: + return 'En Livraison'; + case OrderStatus.Delivered: + return 'livré'; + case OrderStatus.CanceledWhileWarehousePreparation: + case OrderStatus.CanceledWhileInDelivery: + return 'Annulé'; + case OrderStatus.WarehouseIssue: + return 'Problème de préparation'; + case OrderStatus.CarrierIssue: + return 'Problème de livraison'; + default: + return 'BAD_STATUS'; + } + } +} + +export default Order; diff --git a/packages/common/src/entities/OrderProduct.ts b/packages/common/src/entities/OrderProduct.ts new file mode 100644 index 0000000..96be71d --- /dev/null +++ b/packages/common/src/entities/OrderProduct.ts @@ -0,0 +1,125 @@ +import Product from './Product'; +import { Schema, DBObject, ModelName, getSchema, Types } from '../@pyro/db'; +import IOrderProduct from '../interfaces/IOrderProduct'; +import { IOrderProductCreateObject } from '../interfaces/IOrderProduct'; +import { Column } from 'typeorm'; + +/** + * Store Product information inside Order (similar to OrderLineItem concept) + * + * @class OrderProduct + * @extends {DBObject} + * @implements {IOrderProduct} + */ +@ModelName('OrderProduct') +class OrderProduct extends DBObject + implements IOrderProduct { + constructor(orderProduct: IOrderProduct) { + super(orderProduct); + + if (orderProduct && orderProduct.product) { + this.product = new Product(orderProduct.product); + } + } + + // current price of the Product (could be lower or higher compared to initial price) + @Types.Number() + @Column() + price: number; + + // initial price of the Product + @Types.Number() + @Column() + initialPrice: number; + + /** + * Qty of purchased products + * + * @type {number} + * @memberof OrderProduct + */ + @Types.Number(0) + @Column() + count: number; + + /** + * Product (not ref) + * + * @type {Product} + * @memberof OrderProduct + */ + @Schema(getSchema(Product)) + product: Product; + + /** + * Is product(s) require manufacturing + * + * @type {boolean} + * @memberof OrderProduct + */ + @Types.Boolean(true) + @Column() + isManufacturing: boolean; + + /** + * Is product(s) becomes available for purchase only when carrier found + * + * @type {boolean} + * @memberof OrderProduct + */ + @Types.Boolean(true) + @Column() + isCarrierRequired: boolean; + + /** + * Should product be delivered by carrier or it's pickup (takeaway) + * + * @type {boolean} + * @memberof OrderProduct + */ + @Types.Boolean(true) + @Column() + isDeliveryRequired: boolean; + + /** + * Is it Takeaway order (including in-store purchase) + * + * @type {boolean} + * @memberof OrderProduct + */ + @Schema({ required: false, type: Boolean }) + @Column() + isTakeaway?: boolean; + + /** + * Min delivery time (in minutes) + * + * @type {number} + * @memberof OrderProduct + */ + @Schema({ required: false, type: Number }) + @Column() + deliveryTimeMin?: number; + + /** + * Max delivery time (in minutes) + * + * @type {number} + * @memberof OrderProduct + */ + @Schema({ required: false, type: Number }) + @Column() + deliveryTimeMax?: number; + + /** + * Comment + * + * @type {string} + * @memberof OrderProduct + */ + @Schema({ type: String, required: false }) + @Column() + comment?: string; +} + +export default OrderProduct; diff --git a/packages/common/src/entities/PaymentGateway.ts b/packages/common/src/entities/PaymentGateway.ts new file mode 100644 index 0000000..c5faf7e --- /dev/null +++ b/packages/common/src/entities/PaymentGateway.ts @@ -0,0 +1,43 @@ +import { ModelName, DBObject, Types, Schema } from '@pyro/db'; +import IPaymentGatewayCreateObject, { + IPaymentGateway, +} from '../interfaces/IPaymentGateway'; +import PaymentGateways from '../enums/PaymentGateways'; +import { Column } from 'typeorm'; + +/** + * Stores type of payment gateway and configuration object for such payment gateway + * + * @class PaymentGateway + * @extends {DBObject} + * @implements {IPaymentGateway} + */ +@ModelName('PaymentGateway') +class PaymentGateway + extends DBObject + implements IPaymentGateway { + /** + * Type of the payment gateway + * + * @type {PaymentGateways} + * @memberof PaymentGateway + */ + @Types.Number() + @Column() + paymentGateway: PaymentGateways; + + /** + * Configuration object for such payment gateway. + * + * Note: this field has no concrete type, because different payment gateways + * may have different fields in the configure object + * + * @type {any} + * @memberof PaymentGateway + */ + @Schema({ type: Object }) + @Column() + configureObject: any; +} + +export default PaymentGateway; diff --git a/packages/common/src/entities/Product.ts b/packages/common/src/entities/Product.ts new file mode 100644 index 0000000..3a202fa --- /dev/null +++ b/packages/common/src/entities/Product.ts @@ -0,0 +1,101 @@ +import { DBObject, ModelName, Schema, Types } from '../@pyro/db'; +import IProduct, { + IProductCreateObject, + IProductDescription, + IProductDescriptionHTML, + IProductDetails, + IProductDetailsHTML, + IProductImage, + IProductTitle, +} from '../interfaces/IProduct'; +import ProductsCategory from './ProductsCategory'; +import { IProductsCategory } from '../interfaces/IProductsCategory'; +import { Entity, Column } from 'typeorm'; + +/** + * Product Information (organized as a global Products Catalog) + * + * Note: Products in specific warehouse are stored as a WarehouseProduct records instead + * (in MongoDB as sub-documents of the Warehouse document) + * + * @class Product + * @extends {DBObject} + * @implements {IProduct} + */ +@ModelName('Product') +@Entity({ name: 'products' }) +class Product extends DBObject + implements IProduct { + /** + * Title of Product (normally short and display as a bold text) + * + * @type {IProductTitle[]} + * @memberof Product + */ + @Schema({ type: Array }) + title: IProductTitle[]; + + /** + * Short description of the product (normally longer than title) + * + * @type {IProductDescription[]} + * @memberof Product + */ + @Schema({ type: Array }) + description: IProductDescription[]; + + /** + * HTML content for Short description of the product (normally longer than title) + * + * @type {IProductDescriptionHTML[]} + * @memberof Product + */ + @Schema({ type: Array }) + descriptionHTML: IProductDescriptionHTML[]; + + /** + * Long description of the product (normally longer than product description) + * + * @type {IProductDetails[]} + * @memberof Product + */ + @Schema({ type: Array }) + details: IProductDetails[]; + + /** + * HTML content for Long description of the product (normally longer than product description) + * + * @type {IProductDetailsHTML[]} + * @memberof Product + */ + @Schema({ type: Array }) + detailsHTML: IProductDetailsHTML[]; + + /** + * Product Images + * + * @type {IProductImage[]} + * @memberof Product + */ + @Schema({ type: Array }) + images: IProductImage[]; + + /** + * Categories to which product belong + * + * @type {IProductsCategory[]} + * @memberof Product + */ + @Types.Ref([ProductsCategory]) + categories: IProductsCategory[]; + + @Types.Boolean(false) + @Column() + isDeleted: boolean; + + @Types.Boolean(true) + @Column() + isProductAvailable: boolean; +} + +export default Product; diff --git a/packages/common/src/entities/ProductInfo.ts b/packages/common/src/entities/ProductInfo.ts new file mode 100644 index 0000000..6c3b935 --- /dev/null +++ b/packages/common/src/entities/ProductInfo.ts @@ -0,0 +1,69 @@ +import * as _ from 'lodash'; +import IProductInfo from '../interfaces/IProductInfo'; +import Product from './Product'; +import { IOrderProductCreateObject } from '../interfaces/IOrderProduct'; +import WarehouseProduct from './WarehouseProduct'; + +/** + * Product Information + * Note: That entity doesn't have schema, so its not stored in db + * + * @class ProductInfo + * @implements {IProductInfo} + */ +class ProductInfo implements IProductInfo { + constructor(productInfo: IProductInfo) { + if (productInfo) { + _.assign(this, productInfo); + + if (productInfo.warehouseProduct) { + this.warehouseProduct = new WarehouseProduct( + productInfo.warehouseProduct + ); + } + } + } + + /** + * Product field is populated inside this property + * + * @type {WarehouseProduct} + * @memberof ProductInfo + */ + warehouseProduct: WarehouseProduct; + + /** + * Id of merchant/store/warehouse where product is available + * + * @type {string} + * @memberof ProductInfo + */ + warehouseId: string; + + /** + * Url of image for merchant/store/warehouse + * + * @type {string} + * @memberof ProductInfo + */ + warehouseLogo: string; + + distance: number; + + get product(): Product { + return this.warehouseProduct.product as Product; + } + + getOrderProductCreateObject(count: number): IOrderProductCreateObject { + return { + initialPrice: this.warehouseProduct.initialPrice, + price: this.warehouseProduct.price, + deliveryTimeMin: this.warehouseProduct.deliveryTimeMin, + deliveryTimeMax: this.warehouseProduct.deliveryTimeMax, + count, + product: this.warehouseProduct.product as Product, + }; + } +} + +export default ProductInfo; diff --git a/packages/common/src/entities/ProductsCategory.ts b/packages/common/src/entities/ProductsCategory.ts new file mode 100644 index 0000000..1f409cb --- /dev/null +++ b/packages/common/src/entities/ProductsCategory.ts @@ -0,0 +1,48 @@ +import { DBObject, ModelName, Schema, Types } from '../@pyro/db'; +import { + IProductsCategory, + IProductsCategoryCreateObject, + IProductsCategoryName, +} from '../interfaces/IProductsCategory'; +import { Entity, Column } from 'typeorm'; + +/** + * Product Category + * + * @class ProductsCategory + * @extends {DBObject} + * @implements {IProductsCategory} + */ +@ModelName('ProductCategory') +@Entity({ name: 'productcategories' }) +class ProductsCategory + extends DBObject + implements IProductsCategory { + /** + * Product Category Name + * + * @type {IProductsCategoryName[]} + * @memberof ProductsCategory + */ + @Schema({ type: Array }) + name: IProductsCategoryName[]; + + /** + * Product Category Image URL (optional) + * + * @type {string} + * @memberof ProductsCategory + */ + @Schema({ + type: String, + required: false, + }) + @Column() + image: string; + + @Types.Boolean(false) + @Column() + isDeleted: boolean; +} + +export default ProductsCategory; diff --git a/packages/common/src/entities/Promotion.ts b/packages/common/src/entities/Promotion.ts new file mode 100644 index 0000000..c2d17b4 --- /dev/null +++ b/packages/common/src/entities/Promotion.ts @@ -0,0 +1,106 @@ +import { DBObject, ModelName, Types, Schema } from '@pyro/db'; +import { Entity, Column } from 'typeorm'; +import Product from './Product'; +import { + IPromotion, + IPromotionCreateObject, + IPromotionTitle, + IPromotionDescription, +} from '../interfaces/IPromotion'; +import Warehouse from './Warehouse'; +import IWarehouse from '../interfaces/IWarehouse'; + +/** + * + * @class Promotion + * @extends {DBObject} + * @implements {IPromotion} + */ +@ModelName('Promotion') +@Entity({ name: 'promotions' }) +class Promotion extends DBObject + implements IPromotion { + /** + * @type {IPromotionTitle[]} + * @memberof Promotion + */ + @Schema({ type: Array, required: true }) + title: IPromotionTitle[]; + + /** + * @type {IPromotionDescription[]} + * @memberof Promotion + */ + @Schema({ type: Array, required: false }) + description: IPromotionDescription[]; + + /** + * @type {boolean} + * @memberof Promotion + */ + @Types.Boolean(true) + @Column() + active: boolean; + + /** + * @type {number} + * @memberof Promotion + */ + @Types.Number() + @Column() + promoPrice: number; + + /** + * Warehouse promotion is associated with + * + * @type {IWarehouse} + * @memberof Promotion + */ + @Types.Ref(Warehouse) + warehouse: Warehouse; + + /** + * @type {Date} + * @memberof Promotion + */ + @Schema({ type: Date, required: false }) + @Column() + activeFrom: Date; + + /** + * @type {Date} + * @memberof Promotion + */ + @Schema({ type: Date, required: false }) + @Column() + activeTo: Date; + + /** + * @type {string} + * @memberof Promotion + */ + @Schema({ type: String, required: false }) + @Column() + image: string; + + /** + * @type {Product} + * @memberof Promotion + */ + @Types.Ref(Product) + product: Product; + + /** + * @type {number} + * @memberof Promotion + */ + @Types.Number() + @Column() + purchasesCount: number; + + @Types.Boolean(false) + @Column() + isDeleted: boolean; +} + +export default Promotion; diff --git a/packages/common/src/entities/User.ts b/packages/common/src/entities/User.ts new file mode 100644 index 0000000..20f2e2f --- /dev/null +++ b/packages/common/src/entities/User.ts @@ -0,0 +1,181 @@ +import GeoLocation from './GeoLocation'; +import { DBObject, getSchema, ModelName, Schema, Types } from '../@pyro/db'; +import IUser, { IUserCreateObject } from '../interfaces/IUser'; +import { Entity, Column } from 'typeorm'; + +/** + * Customer who make orders + * TODO: historically called 'User', but we will rename to 'Customer' + * + * @class User + * @extends {DBObject} + * @implements {IUser} + */ +@ModelName('User') +@Entity({ name: 'users' }) +class User extends DBObject implements IUser { + constructor(user: IUser) { + super(user); + + if (user && user.geoLocation) { + this.geoLocation = new GeoLocation(user.geoLocation); + } + } + + /** + * First Name + * + * @type {string} + * @memberof User + */ + @Schema({ type: String, required: false }) + @Column() + firstName?: string; + + /** + * Last Name + * + * @type {string} + * @memberof User + */ + @Schema({ type: String, required: false }) + @Column() + lastName?: string; + + /** + * Customer Image (Photo/Avatar) URL + * (optional) + * + * @type {string} + * @memberof User + */ + @Schema({ type: String, required: false }) + @Column() + image: string; + + /** + * Primary Email Address + * + * @type {string} + * @memberof UserOrder + */ + @Schema({ + type: String, + required: false, + sparse: true, + unique: true, + }) + @Column() + email?: string; + + /** + * Password hash + * + * @type {string} + * @memberof User + */ + @Schema({ + type: String, + required: false, + select: false, + }) + @Column() + hash?: string; + + /** + * Current customer location (customer address, last known location of the customer) + * + * @type {GeoLocation} + * @memberof User + */ + @Schema(getSchema(GeoLocation)) + geoLocation: GeoLocation; + + /** + * Apartment (stored separately from geolocation/address for efficiency) + * + * @type {string} + * @memberof User + */ + @Schema(String) + @Column() + apartment: string; + + /** + * CustomerId in the Stripe payment gateway (if customer added to Stripe, optional) + * + * @type {string} + * @memberof User + */ + @Schema({ type: String, required: false }) + @Column() + stripeCustomerId?: string; + + /** + * IDs of customer mobile devices / browser (see Device entity) + * + * @type {string[]} + * @memberof User + */ + @Schema([String]) + devicesIds: string[]; + + /** + * IDs of customer in social networks / OAuth providers + * + * @type {string[]} + * @memberof User + */ + @Schema([String]) + socialIds: string[]; + + /** + * Customer Primary Phone Number + * + * @type {string} + * @memberof User + */ + @Schema(String) + @Column() + phone?: string; + + /** + * Is customer completed registration + * + * @type {boolean} + * @memberof User + */ + @Schema(Boolean) + @Column() + isRegistrationCompleted: boolean; + + /** + * Is customer banned + * + * @type {boolean} + * @memberof User + */ + @Types.Boolean(false) + @Column() + isBanned: boolean; + + @Types.Boolean(false) + @Column() + isDeleted: boolean; + + /** + * Get full address of customer (including apartment) + * Note: does not include country + * + * @readonly + * @memberof User + */ + get fullAddress(): string { + return ( + `${this.geoLocation.city}, ${this.geoLocation.streetAddress} ` + + `${this.apartment}/${this.geoLocation.house}` + ); + } +} + +export default User; diff --git a/packages/common/src/entities/UserOrder.ts b/packages/common/src/entities/UserOrder.ts new file mode 100644 index 0000000..5288633 --- /dev/null +++ b/packages/common/src/entities/UserOrder.ts @@ -0,0 +1,163 @@ +import GeoLocation from './GeoLocation'; +import { DBObject, getSchema, ModelName, Schema } from '../@pyro/db'; +import IUserOrder, { IUserOrderCreateObject } from '../interfaces/IUserOrder'; +import { Column } from 'typeorm'; + +/** + * Store information about Customer inside (embeded into) Order + * The data is usually copied from the Customer record in DB right after order created + * TODO: rename to CustomerOrder + * + * @class UserOrder + * @extends {DBObject} + * @implements {IUserOrder} + */ +@ModelName('UserOrder') +class UserOrder extends DBObject + implements IUserOrder { + constructor(userOrder: IUserOrder) { + super(userOrder); + + if (userOrder && userOrder.geoLocation) { + this.geoLocation = new GeoLocation(userOrder.geoLocation); + } + } + + /** + * First Name + * + * @type {string} + * @memberof UserOrder + */ + @Schema({ type: String, required: false }) + @Column() + firstName?: string; + + /** + * Last Name + * + * @type {string} + * @memberof UserOrder + */ + @Schema({ type: String, required: false }) + @Column() + lastName?: string; + + /** + * Customer Image (Photo/Avatar) URL + * (optional) + * + * @type {string} + * @memberof UserOrder + */ + @Schema({ type: String, required: false }) + @Column() + image: string; + + /** + * Primary Email Address + * + * @type {string} + * @memberof UserOrder + */ + @Schema({ + type: String, + required: false, + sparse: true, + }) + @Column() + email?: string; + + /** + * Password Hash + * + * @type {string} + * @memberof UserOrder + */ + @Schema({ type: String, required: false, select: false }) + @Column() + hash?: string; + + /** + * Current customer location (customer address, last known location of the customer) + * + * @type {GeoLocation} + * @memberof UserOrder + */ + @Schema(getSchema(GeoLocation)) + geoLocation: GeoLocation; + + /** + * Apartment (stored separately from geolocation/address for efficiency) + * + * @type {string} + * @memberof UserOrder + */ + @Schema(String) + @Column() + apartment: string; + + /** + * CustomerId in the Stripe payment gateway (if customer added to Stripe, optional) + * + * @type {string} + * @memberof UserOrder + */ + @Schema({ type: String, required: false }) + @Column() + stripeCustomerId?: string; + + /** + * IDs of customer mobile devices / browser + * + * @type {string[]} + * @memberof UserOrder + */ + @Schema([String]) + devicesIds: string[]; + + /** + * IDs of customer in social networks / OAuth providers + * + * @type {string[]} + * @memberof UserOrder + */ + @Schema([String]) + socialIds: string[]; + + /** + * Customer Primary Phone Number + * + * @type {string} + * @memberof UserOrder + */ + @Schema(String) + @Column() + phone?: string; + + /** + * Is customer completed registration + * + * @type {boolean} + * @memberof UserOrder + */ + @Schema(Boolean) + @Column() + isRegistrationCompleted: boolean; + + /** + * Get full address of customer (including apartment) + * Note: does not include country + * + * @readonly + * @memberof UserOrder + */ + get fullAddress(): string { + return ( + `${this.geoLocation.city}, ${this.geoLocation.streetAddress} ` + + `${this.apartment}/${this.geoLocation.house}` + ); + } +} + +export default UserOrder; diff --git a/packages/common/src/entities/Warehouse.ts b/packages/common/src/entities/Warehouse.ts new file mode 100644 index 0000000..15c51c1 --- /dev/null +++ b/packages/common/src/entities/Warehouse.ts @@ -0,0 +1,382 @@ +import { map } from 'underscore'; +import { DBObject, getSchema, ModelName, Schema, Types } from '../@pyro/db'; +import GeoLocation from './GeoLocation'; +import WarehouseProduct, { WithPopulatedProduct } from './WarehouseProduct'; +import IWarehouse, { IWarehouseCreateObject } from '../interfaces/IWarehouse'; +import ForwardOrdersMethod from '../enums/ForwardOrdersMethod'; +import { Entity, Column } from 'typeorm'; +import IWarehouseProduct from '../interfaces/IWarehouseProduct'; +import OrderBarcodeTypes from '../enums/OrderBarcodeTypes'; +import PaymentGateway from './PaymentGateway'; + +/** + * Warehouse / Merchant / Store + * We are using all of above as synonyms today, however at some point we will have + * multiple warehouses per each store OR multiple stores using same warehouse. + * So, while we think Merchant and Store are interchangeable, + * in the future versions of Ever Platform, we will have Merchants and Warehouses as separated entities + * + * @class Warehouse + * @extends {DBObject} + * @implements {IWarehouse} + */ +@ModelName('Warehouse') +@Entity({ name: 'warehouses' }) +class Warehouse extends DBObject + implements IWarehouse { + constructor(warehouse: IWarehouse) { + super(warehouse); + + if (warehouse) { + if (warehouse.geoLocation) { + this.geoLocation = new GeoLocation(warehouse.geoLocation); + } + + if (warehouse.products) { + this.products = map( + warehouse.products, + (warehouseProduct: IWarehouseProduct) => { + return new WarehouseProduct(warehouseProduct); + } + ); + } + + if (!warehouse.barcodeData) { + this.barcodeData = warehouse._id.toString(); + } + } + } + + /** + * Is Warehouse working right now + * + * @type {boolean} + * @memberof Warehouse + */ + @Types.Boolean(false) + @Column() + isActive: boolean; + + /** + * Is online payment enabled? + * If disabled, sales only possible using cash on delivery or takeaway and no online payments allowed + * + * @type {boolean} + * @memberof Warehouse + */ + @Types.Boolean(true) + @Column() + isPaymentEnabled: boolean; + + /** + * Enable or disable cash payment method + * + * @type {boolean} + * @memberof Warehouse + */ + + @Types.Boolean(true) + @Column() + isCashPaymentEnabled: boolean; + + /** + * Enable or disable in-store mode + * + * @type {boolean} + * @memberof Warehouse + */ + + @Types.Boolean(false) + @Column() + inStoreMode: boolean; + + /** + * Warehouse current location (address) + * Note: we do support "moving" warehouses (e.g. car with products) + * + * @type {GeoLocation} + * @memberof Warehouse + */ + @Schema(getSchema(GeoLocation)) + geoLocation: GeoLocation; + + /** + * Warehouse delivery areas in GeoJSON format + * + * @type {Object} + * @memberof Warehouse + */ + @Schema({ type: Object }) + deliveryAreas: object; + + /** + * Products available at this warehouse for customer to purchase + * + * @type {WarehouseProduct[]} + * @memberof Warehouse + */ + @Schema([getSchema(WarehouseProduct)]) + products: WarehouseProduct[]; + + /** + * The name of Store/Merchant + * + * @type {string} + * @memberof Warehouse + */ + @Types.String() + @Column() + name: string; + + /** + * The URL of store brand logo + * + * @type {string} + * @memberof Warehouse + */ + @Schema({ type: String, required: false }) + @Column() + logo: string; + + /** + * Default administrator user for the store + * (used for authentication during login into Merchant app together with hashed password) + * + * @type {string} + * @memberof Warehouse + */ + @Schema({ type: String, unique: true }) + @Column() + username: string; + + /** + * Hashed password of default store administrator + * + * @type {string} + * @memberof Warehouse + */ + @Schema({ type: String, required: false, select: false }) + @Column() + hash?: string; + + /** + * Primary contact email for the store + * + * @type {(string | null)} + * @memberof Warehouse + */ + @Schema({ type: String, required: false }) + @Column() + contactEmail: string | null; + + /** + * Primary contact phone for the store + * + * @type {(string | null)} + * @memberof Warehouse + */ + @Schema({ type: String, required: false }) + @Column() + contactPhone: string | null; + + /** + * Phone number, which should be used to accept orders by phone calls + * If null, Store did not accept orders by phone calls + * + * @type {(string | null)} + * @memberof Warehouse + */ + @Schema({ type: String, required: false }) + @Column() + ordersPhone: string | null; + + /** + * Email address, which should be used to accept orders by incoming emails + * If null, Store did not accept orders by incoming emails + * + * @type {(string | null)} + * @memberof Warehouse + */ + @Schema({ type: String, required: false }) + @Column() + ordersEmail: string | null; + + /** + * Preferable way to forward orders to the Store (email, phone, etc) + * + * @type {ForwardOrdersMethod} + * @memberof Warehouse + */ + @Schema([Number]) + @Column() + forwardOrdersUsing: ForwardOrdersMethod[]; + + /** + * Is Warehouse products by default require manufacturing + * (e.g. pizza requires manufacturing, but bottle of beer usually don't) + * + * @type {boolean} + * @memberof Warehouse + */ + @Types.Boolean(true) + @Column() + isManufacturing: boolean; + + /** + * Is warehouse products by default become available only when carrier found + * (Should we search for carrier before or after customer purchases product) + * + * @type {boolean} + * @memberof Warehouse + */ + @Types.Boolean(true) + @Column() + isCarrierRequired: boolean; + + /** + * IDs of devices used to access Warehouse app (where app installed or running) + * Note: used to send push notifications + * + * @type {string[]} + * @memberof Warehouse + */ + @Schema([String]) + devicesIds: string[]; + + /** + * IDs of carriers which used by Store + * + * @type {string[]} + * @memberof Warehouse + */ + @Schema([String]) + usedCarriersIds: string[]; + + /** + * if true, has some carriers assigned to the Store + * + * @type {boolean} + * @memberof Warehouse + */ + @Types.Boolean(false) + @Column() + hasRestrictedCarriers: boolean; + + /** + * IDs of carriers which are "connected" (assigned) to the Store ("own carriers"). + * + * @type {string[]} + * @memberof Warehouse + */ + @Schema([String]) + carriersIds: string[]; + + @Types.Boolean(false) + @Column() + isDeleted: boolean; + + /** + * Is Store allows deliveries for purchases + * + * @type {boolean} + * @memberof Warehouse + */ + @Schema({ type: Boolean, required: false }) + @Column() + productsDelivery?: boolean; + + /** + * Is Store allows takeaway purchases + * + * @type {boolean} + * @memberof Warehouse + */ + @Schema({ type: Boolean, required: false }) + @Column() + productsTakeaway?: boolean; + + /** + * The type of order barcode + * + * @type {Number} + * @memberof Warehouse + */ + @Schema({ type: Number, required: false }) + @Column() + orderBarcodeType?: OrderBarcodeTypes; + + /** + * Unique data for generate on barcode + * + * @type {string} + * @memberof Warehouse + */ + @Schema({ type: String, unique: true, sparse: true }) + @Column() + barcodeData: string; + + /** + * Payment gateways + * + * @type {PaymentGateway[]} + * @memberof Warehouse + */ + @Schema([getSchema(PaymentGateway)]) + paymentGateways?: PaymentGateway[]; + + /** + * if true, only assigned carriers (in carriersIds) can be used for delivery + * + * @type {boolean} + * @memberof Warehouse + */ + @Schema({ type: Boolean, required: false }) + @Column() + useOnlyRestrictedCarriersForDelivery?: boolean; + + /** + * if true, assigned carriers (in carriersIds) will be prefer for delivery + * + * @type {boolean} + * @memberof Warehouse + */ + @Schema({ type: Boolean, required: false }) + @Column() + preferRestrictedCarriersForDelivery?: boolean; + + /** + * if true, accepting the orders not goes of all the states + * + * @type {boolean} + * @memberof Warehouse + */ + @Schema({ type: Boolean, required: false }) + @Column() + ordersShortProcess?: boolean; + + /** + * + * + * @type { {enabled: Boolean, onState: Number} } + * @memberof Warehouse + */ + @Schema({ type: { enabled: Boolean, onState: Number }, required: false }) + orderCancelation?: { enabled: boolean; onState: number }; + + /** + * Enable or disable carrier works competition mode + * + * @type {boolean} + * @memberof Warehouse + */ + + @Types.Boolean(false) + @Column() + carrierCompetition?: boolean; +} + +export type WithFullProducts = Warehouse & { + products: WithPopulatedProduct[]; +}; + +export default Warehouse; diff --git a/packages/common/src/entities/WarehouseProduct.ts b/packages/common/src/entities/WarehouseProduct.ts new file mode 100644 index 0000000..82c9958 --- /dev/null +++ b/packages/common/src/entities/WarehouseProduct.ts @@ -0,0 +1,169 @@ +import Product from './Product'; +import { DBObject, ModelName, Schema, Types } from '../@pyro/db'; +import IWarehouseProduct, { + IWarehouseProductCreateObject, +} from '../interfaces/IWarehouseProduct'; +import IProduct from '../interfaces/IProduct'; +import { Column } from 'typeorm'; + +/** + * Represent Warehouse (Merchant) inventory item (some product / service) for sale + * Each warehouse may have some qty of items of some product and price can change for all of + * them only (i.e. say price decrease every 1 min for 1$ but for all 4 products in warehouse) + * In MongoDB stored as sub-documents inside Warehouse document (not as a separate collection) + * + * @class WarehouseProduct + * @extends {DBObject} + * @implements {IWarehouseProduct} + */ +@ModelName('WarehouseProduct') +class WarehouseProduct + extends DBObject + implements IWarehouseProduct { + constructor(warehouseProduct: IWarehouseProduct) { + super(warehouseProduct); + + if (typeof warehouseProduct.product !== 'string') { + this.product = new Product(warehouseProduct.product as IProduct); + } + } + + /** + * Current Price of product (real-time) + * Note: some warehouses may have different prices for the same product + * compared to other warehouses. It is especially true, when prices go down/up in some + * warehouses more quickly compared to others etc + * + * @type {number} + * @memberof WarehouseProduct + */ + @Types.Number() + @Column() + price: number; + + /** + * Start price for product. + * Initially equals to the value from price field, but over the time price can go down/up, + * while initialPrice will always remain the same. + * We calculate % of discount using price and initialPrice values + * + * @type {number} + * @memberof WarehouseProduct + */ + @Types.Number() + @Column() + initialPrice: number; + + /** + * How many products available currently for purchase + * + * @type {number} + * @memberof WarehouseProduct + */ + @Types.Number(0) + @Column() + count: number; + + /** + * How many products are sold + * + * @type {number} + * @memberof WarehouseProduct + */ + @Types.Number(0) + @Column() + soldCount: number; + + /** + * Ref to Product + * + * @type {(Product)} + * @memberof WarehouseProduct + */ + @Types.Ref(Product) + product: Product; + + /** + * Is product(s) require manufacturing + * + * @type {boolean} + * @memberof WarehouseProduct + */ + @Column() + @Types.Boolean(true) + isManufacturing: boolean; + + /** + * Is product(s) become available only when carrier found + * + * @type {boolean} + * @memberof WarehouseProduct + */ + @Column() + @Types.Boolean(true) + isCarrierRequired: boolean; + + /** + * Is product(s) require delivery to customer or available for pickup + * + * @type {boolean} + * @memberof WarehouseProduct + */ + @Column() + @Types.Boolean(true) + isDeliveryRequired: boolean; + + /** + * Is product available for purchase + * + * @type {boolean} + * @memberof WarehouseProduct + */ + @Column() + @Types.Boolean(true) + isProductAvailable: boolean; + + @Schema({ required: false, type: Boolean }) + @Column() + isTakeaway?: boolean; + + /** + * Min delivery time (in minutes) + * + * @type {number} + * @memberof WarehouseProduct + */ + @Schema({ required: false, type: Number }) + @Column() + deliveryTimeMin?: number; + + /** + * Max delivery time (in minutes) + * + * @type {number} + * @memberof WarehouseProduct + */ + @Schema({ required: false, type: Number }) + @Column() + deliveryTimeMax?: number; + + /** + * Get ProductId + * + * @readonly + * @type {string} + * @memberof WarehouseProduct + */ + + get productId(): string { + if (typeof this.product === 'string') { + return this.product as string; + } else { + return (this.product as Product).id; + } + } +} + +export type WithPopulatedProduct = WarehouseProduct & { product: Product }; + +export default WarehouseProduct; diff --git a/packages/common/src/entities/index.ts b/packages/common/src/entities/index.ts new file mode 100644 index 0000000..4a7788c --- /dev/null +++ b/packages/common/src/entities/index.ts @@ -0,0 +1,16 @@ +export * from './Admin'; +export * from './Carrier'; +export * from './Device'; +export * from './GeoLocation'; +export * from './Invite'; +export * from './InviteRequest'; +export * from './Order'; +export * from './OrderProduct'; +export * from './Product'; +export * from './ProductInfo'; +export * from './ProductsCategory'; +export * from './User'; +export * from './UserOrder'; +export * from './Warehouse'; +export * from './WarehouseProduct'; +export * from './Promotion'; diff --git a/packages/common/src/enums/CarrierStatus.ts b/packages/common/src/enums/CarrierStatus.ts new file mode 100644 index 0000000..bce286d --- /dev/null +++ b/packages/common/src/enums/CarrierStatus.ts @@ -0,0 +1,25 @@ +/** + * Current Carrier status + * This status is set by carrier itself in his mobile app to indicate if he working now or can't work now + * + * @enum {number} + */ +enum CarrierStatus { + /** + * Carrier online and ready to accept new jobs or already processing them + * (check orders collection for carrier to know what jobs he processing if any) + */ + Online = 0, + + /** + * Carrier offline and did not accept jobs right now (not working at the moment, e.g. sleep) + */ + Offline = 1, + + /** + * Carrier is blocked (temporary or permanently from work), e.g. banned from our platform + */ + Blocked = 2, +} + +export default CarrierStatus; diff --git a/packages/common/src/enums/DeliveryType.ts b/packages/common/src/enums/DeliveryType.ts new file mode 100644 index 0000000..0a9cb0a --- /dev/null +++ b/packages/common/src/enums/DeliveryType.ts @@ -0,0 +1,12 @@ +/** + * Supported Purchase types: Delivery or Takeaway + * TODO: rename to something like "PurchaseType" + * + * @enum {number} + */ +enum DeliveryType { + Delivery = 0, + Takeaway = 1, +} + +export default DeliveryType; diff --git a/packages/common/src/enums/ForwardOrdersMethod.ts b/packages/common/src/enums/ForwardOrdersMethod.ts new file mode 100644 index 0000000..00f86b3 --- /dev/null +++ b/packages/common/src/enums/ForwardOrdersMethod.ts @@ -0,0 +1,13 @@ +/** + * How orders will be forwarded to the Store from Customers + * (Phone, Email, etc) + * + * @enum {number} + */ +enum ForwardOrdersMethod { + Unselected, + Phone, + Email, +} + +export default ForwardOrdersMethod; diff --git a/packages/common/src/enums/OrderBarcodeTypes.ts b/packages/common/src/enums/OrderBarcodeTypes.ts new file mode 100644 index 0000000..08c49dc --- /dev/null +++ b/packages/common/src/enums/OrderBarcodeTypes.ts @@ -0,0 +1,31 @@ +/** + * Order Barcode Types + * + * @enum {number} + */ +enum OrderBarcodeTypes { + QR = 0, + CODE128 = 1, + CODE39 = 2, + pharmacode = 3, + MSI = 4, +} + +export function orderBarcodeTypesToString(status: OrderBarcodeTypes): string { + switch (status) { + case OrderBarcodeTypes.QR: + return 'QR code'; + case OrderBarcodeTypes.CODE128: + return 'CODE128'; + case OrderBarcodeTypes.CODE39: + return 'CODE39'; + case OrderBarcodeTypes.pharmacode: + return 'pharmacode'; + case OrderBarcodeTypes.MSI: + return 'MSI'; + default: + return 'BAD_STATUS'; + } +} + +export default OrderBarcodeTypes; diff --git a/packages/common/src/enums/OrderCarrierStatus.ts b/packages/common/src/enums/OrderCarrierStatus.ts new file mode 100644 index 0000000..fd47cd5 --- /dev/null +++ b/packages/common/src/enums/OrderCarrierStatus.ts @@ -0,0 +1,83 @@ +/** + * Status of Carrier assigned to an Order + * + * @enum {number} + */ +enum OrderCarrierStatus { + /** + * No Status + * Shipping was not planed or started yet for this Order by any carrier, i.e. no carrier assigned to an order + */ + NoCarrier = 0, + + /** + * Some carrier decide to pick up this order and will start moving to warehouse soon. + * Carrier change order status to this when see packaged order is waiting him in warehouse + */ + CarrierSelectedOrder = 1, + + /** + * Some carrier arrived to Warehouse and picked up an order products for delivery + * (carrier change order status to this) + */ + CarrierPickedUpOrder = 2, + + /** + * Some carrier start delivery of order products to customer + */ + CarrierStartDelivery = 3, + + /** + * Some carrier arrived to customer location + */ + CarrierArrivedToCustomer = 4, + + /** + * Order is given to the client (delivery completed) + */ + DeliveryCompleted = 5, + + /** + * Carrier have issues during delivery (e.g. incident happens or carrier can't found address, etc) + */ + IssuesDuringDelivery = 204, + + /** + * Carrier arrives to customer location and client refuse to take order + */ + ClientRefuseTakingOrder = 205, +} + +// TODO: this should be translated +export function carrierStatusToString(status: OrderCarrierStatus): string { + switch (status) { + case OrderCarrierStatus.NoCarrier: + return 'No Carrier'; + + case OrderCarrierStatus.CarrierSelectedOrder: + return 'Order Selected For Delivery'; + + case OrderCarrierStatus.CarrierPickedUpOrder: + return 'Order Picked Up'; + + case OrderCarrierStatus.CarrierStartDelivery: + return 'Order In Delivery'; + + case OrderCarrierStatus.CarrierArrivedToCustomer: + return 'Arrived To Client'; + + case OrderCarrierStatus.DeliveryCompleted: + return 'Delivered'; + + case OrderCarrierStatus.IssuesDuringDelivery: + return 'Delivery Issues'; + + case OrderCarrierStatus.ClientRefuseTakingOrder: + return 'Client Refuse to Take Order'; + + default: + return 'BAD_STATUS'; + } +} + +export default OrderCarrierStatus; diff --git a/packages/common/src/enums/OrderStatus.ts b/packages/common/src/enums/OrderStatus.ts new file mode 100644 index 0000000..e65a7bf --- /dev/null +++ b/packages/common/src/enums/OrderStatus.ts @@ -0,0 +1,17 @@ +/** + * Status of Order + * (used usually as combined value from Carrier status and Warehouse status) + * + * @enum {number} + */ +enum OrderStatus { + WarehousePreparation = 0, + InDelivery = 1, + Delivered = 2, + CanceledWhileWarehousePreparation = 200, + CanceledWhileInDelivery = 201, + WarehouseIssue = 202, + CarrierIssue = 203, +} + +export default OrderStatus; diff --git a/packages/common/src/enums/OrderWarehouseStatus.ts b/packages/common/src/enums/OrderWarehouseStatus.ts new file mode 100644 index 0000000..f5b6819 --- /dev/null +++ b/packages/common/src/enums/OrderWarehouseStatus.ts @@ -0,0 +1,98 @@ +/** + * Warehouse Statuses + * + * @enum {number} + */ +enum OrderWarehouseStatus { + /** + * No Status (order just created) + */ + NoStatus = 0, + + /** + * Order just received from client (OperationMode.Warehouse) + * or initialized by warehouse (OperationMode.Order) and confirmed ("ready" for warehouse processing) + * This field is useful to indicate that Warehouse Confirms an order + * (e.g. to let client/customer know that Warehouse will ship/sell product) + * + * Note: some warehouses want to automatically confirm all orders, + * but some want first to review them and confirm each one manually, one by one + */ + ReadyForProcessing = 1, + + // Warehouse start work on the order + // E.g. take order to himself for OperationMode.Warehouse or just start working on order for OperationMode.Order + WarehouseStartedProcessing = 2, + + // Products allocation started (warehouse) + AllocationStarted = 3, + + // Warehouse finish allocation of products for packaging + // (warehouse change order to this status) + AllocationFinished = 4, + + // Products packaging started (warehouse) + PackagingStarted = 5, + + // Products are packed for shipping by warehouse + // Warehouse change order to this status and carriers see packaged orders in warehouses + // and move to closest warehouse to pick order + PackagingFinished = 6, + + // Order is given to some carrier for delivery + GivenToCarrier = 7, + + // Order is given to customer + GivenToCustomer = 8, + + // Fail to allocate products + // (some products are missing in stock/warehouse) + AllocationFailed = 200, + + // Fail to pack products + // (something was wrong during packaging or maybe pack broken etc) + PackagingFailed = 201, +} + +// TODO: this should be translated +export function warehouseStatusToString(status: OrderWarehouseStatus): string { + switch (status) { + case OrderWarehouseStatus.NoStatus: + return 'Created'; + + case OrderWarehouseStatus.ReadyForProcessing: + return 'Confirmed'; + + case OrderWarehouseStatus.WarehouseStartedProcessing: + return 'Processing'; + + case OrderWarehouseStatus.AllocationStarted: + return 'Allocation Started'; + + case OrderWarehouseStatus.AllocationFinished: + return 'Allocation Finished'; + + case OrderWarehouseStatus.PackagingStarted: + return 'Packaging Started'; + + case OrderWarehouseStatus.PackagingFinished: + return 'Packaged'; + + case OrderWarehouseStatus.GivenToCarrier: + return 'Given to Carrier'; + + case OrderWarehouseStatus.GivenToCustomer: + return 'Given to Customer'; + + case OrderWarehouseStatus.AllocationFailed: + return 'Allocation Failed'; + + case OrderWarehouseStatus.PackagingFailed: + return 'Packaging Failed'; + + default: + return 'BAD_STATUS'; + } +} + +export default OrderWarehouseStatus; diff --git a/packages/common/src/enums/PaymentGateways.ts b/packages/common/src/enums/PaymentGateways.ts new file mode 100644 index 0000000..bd0a8c2 --- /dev/null +++ b/packages/common/src/enums/PaymentGateways.ts @@ -0,0 +1,35 @@ +/** + * Payment Gateways + * + * @enum {number} + */ +enum PaymentGateways { + Stripe, + PayPal, +} + +export function paymentGatewaysToString( + paymentGateway: PaymentGateways +): string { + switch (paymentGateway) { + case PaymentGateways.Stripe: + return 'Stripe'; + case PaymentGateways.PayPal: + return 'PayPal'; + default: + return 'BAD_PAYMENT_GATEWAY'; + } +} + +export function paymentGatewaysLogo(paymentGateway: PaymentGateways): string { + switch (paymentGateway) { + case PaymentGateways.Stripe: + return 'https://stripe.com/img/v3/home/twitter.png'; + case PaymentGateways.PayPal: + return 'https://avatars1.githubusercontent.com/u/476675?s=200&v=4'; + default: + return 'BAD_PAYMENT_GATEWAY'; + } +} + +export default PaymentGateways; diff --git a/packages/common/src/enums/RegistrationSystem.ts b/packages/common/src/enums/RegistrationSystem.ts new file mode 100644 index 0000000..a32bf39 --- /dev/null +++ b/packages/common/src/enums/RegistrationSystem.ts @@ -0,0 +1,7 @@ +enum RegistrationSystem { + Enabled = 'enabled', + Disabled = 'disabled', + Once = 'once', +} + +export default RegistrationSystem; diff --git a/packages/common/src/errors/NotFoundError.ts b/packages/common/src/errors/NotFoundError.ts new file mode 100644 index 0000000..cc543b6 --- /dev/null +++ b/packages/common/src/errors/NotFoundError.ts @@ -0,0 +1,4 @@ +export class NotFoundError { + readonly code = 'not-found'; + readonly message = 'Not found!'; +} diff --git a/packages/common/src/errors/NotInvitedError.ts b/packages/common/src/errors/NotInvitedError.ts new file mode 100644 index 0000000..b1ed0b5 --- /dev/null +++ b/packages/common/src/errors/NotInvitedError.ts @@ -0,0 +1,4 @@ +export class NotInvitedError { + readonly code = 'not-invited'; + readonly message = 'Not invited!'; +} diff --git a/packages/common/src/errors/WrongPasswordError.ts b/packages/common/src/errors/WrongPasswordError.ts new file mode 100644 index 0000000..d1fa55d --- /dev/null +++ b/packages/common/src/errors/WrongPasswordError.ts @@ -0,0 +1,4 @@ +export class WrongPasswordError { + readonly code = 'wrong-password'; + readonly message = 'Wrong password!'; +} diff --git a/packages/common/src/interfaces/IAdmin.ts b/packages/common/src/interfaces/IAdmin.ts new file mode 100644 index 0000000..dd6144f --- /dev/null +++ b/packages/common/src/interfaces/IAdmin.ts @@ -0,0 +1,46 @@ +import { DBCreateObject, DBRawObject, PyroObjectId } from '@pyro/db'; + +export interface IAdminCreateObject extends DBCreateObject { + /** + * Full name (First Name plus Last Name) + * + * @type {string} + * @memberof IAdminCreateObject + */ + name: string; + + email: string; + + /** + * Password Hash + * + * @type {string} + * @memberof IAdminCreateObject + */ + hash: string; + + pictureUrl: string; + firstName?: string; + lastName?: string; +} + +export interface IAdminUpdateObject extends DBCreateObject { + /** + * Full name (First Name plus Last Name) + * + * @type {string} + * @memberof IAdminCreateObject + */ + name?: string; + + email?: string; + pictureUrl?: string; + firstName?: string; + lastName?: string; +} + +interface IAdmin extends DBRawObject, IAdminCreateObject { + _id: PyroObjectId; +} + +export default IAdmin; diff --git a/packages/common/src/interfaces/IAppsSettings.ts b/packages/common/src/interfaces/IAppsSettings.ts new file mode 100644 index 0000000..c497815 --- /dev/null +++ b/packages/common/src/interfaces/IAppsSettings.ts @@ -0,0 +1,5 @@ +export interface IAdminAppSettings { + // use 0 for false and 1 for true because is more easy then cast boolean string on the client + adminPasswordReset: 0 | 1; + fakeDataGenerator: 0 | 1; +} diff --git a/packages/common/src/interfaces/ICarrier.ts b/packages/common/src/interfaces/ICarrier.ts new file mode 100644 index 0000000..a91641a --- /dev/null +++ b/packages/common/src/interfaces/ICarrier.ts @@ -0,0 +1,68 @@ +import IGeoLocation, { IGeoLocationCreateObject } from './IGeoLocation'; +import CarrierStatus from '../enums/CarrierStatus'; +import { DBCreateObject, DBRawObject, PyroObjectId } from '../@pyro/db'; + +export interface ICarrierCreateObject extends DBCreateObject { + firstName: string; + lastName: string; + email?: string; + numberOfDeliveries?: number; + geoLocation: IGeoLocationCreateObject; + apartment?: string; + + /** + * True carrier enabled in system, + * False carrier completely disabled in the system (e.g. was fired / banned) + * This setting is set by Admin, not by carrier itself + * (he/she set 'status' field instead in the carrier mobile app) + * + * @type {boolean} + * @memberof ICarrierCreateObject + */ + isDeleted?: boolean; + + // Current carrier status (set via his mobile app), e.g. Online or Offline + status?: CarrierStatus; + + username: string; + phone: string; + + skippedOrderIds?: string[]; + deliveriesCountToday?: number; + totalDistanceToday?: number; + + devicesIds?: string[]; + isSharedCarrier: boolean; + + /** + * Url to Carrier logo/photo + * + * @type {string} + * @memberof ICarrierCreateObject + */ + logo: string; + + /** + * Password Hash + * + * @type {string} + * @memberof ICarrierCreateObject + */ + hash?: string; +} + +interface ICarrier extends DBRawObject, ICarrierCreateObject { + _id: PyroObjectId; + numberOfDeliveries: number; + isDeleted: boolean; + geoLocation: IGeoLocation; + skippedOrderIds: string[]; + status: CarrierStatus; + deliveriesCountToday: number; + totalDistanceToday: number; + devicesIds: string[]; + logo: string; + isSharedCarrier: boolean; +} + +export default ICarrier; diff --git a/packages/common/src/interfaces/ICurrency.ts b/packages/common/src/interfaces/ICurrency.ts new file mode 100644 index 0000000..9736dc8 --- /dev/null +++ b/packages/common/src/interfaces/ICurrency.ts @@ -0,0 +1,13 @@ +import { DBCreateObject, DBRawObject, PyroObjectId } from '@pyro/db'; + +export interface ICurrencyCreateObject extends DBCreateObject { + currencyCode: string; + isDeleted?: boolean; +} + +interface ICurrency extends ICurrencyCreateObject, DBRawObject { + _id: PyroObjectId; + isDeleted: boolean; +} + +export default ICurrency; diff --git a/packages/common/src/interfaces/IDevice.ts b/packages/common/src/interfaces/IDevice.ts new file mode 100644 index 0000000..8c576f9 --- /dev/null +++ b/packages/common/src/interfaces/IDevice.ts @@ -0,0 +1,15 @@ +import IPlatform from './IPlatform'; +import ILanguage from './ILanguage'; +import { DBCreateObject, DBRawObject, PyroObjectId } from '../@pyro/db'; + +export interface IDeviceCreateObject extends DBCreateObject { + channelId: string | null; + type: IPlatform; + language?: ILanguage; + uuid: string; +} + +export interface IDeviceRawObject extends IDeviceCreateObject, DBRawObject { + _id: PyroObjectId; + language: ILanguage; +} diff --git a/packages/common/src/interfaces/IEnterByCode.ts b/packages/common/src/interfaces/IEnterByCode.ts new file mode 100644 index 0000000..4e56e68 --- /dev/null +++ b/packages/common/src/interfaces/IEnterByCode.ts @@ -0,0 +1,16 @@ +import { ILocation } from './IGeoLocation'; + +/** + * Users (customers/carriers) could be invited by some code, + * which valid for given user location + * + * @interface IEnterByCode + */ +interface IEnterByCode { + location: ILocation; + inviteCode: string; + firstName?: string; + lastName?: string; +} + +export default IEnterByCode; diff --git a/packages/common/src/interfaces/IEnterByLocation.ts b/packages/common/src/interfaces/IEnterByLocation.ts new file mode 100644 index 0000000..9985023 --- /dev/null +++ b/packages/common/src/interfaces/IEnterByLocation.ts @@ -0,0 +1,29 @@ +import { Country } from '../entities/GeoLocation'; +import { IAddress } from './IGeoLocation'; + +interface IEnterByLocation { + countryId: Country; + city: string; + streetAddress: string; + house: string; + postcode?: string | null; + notes?: string | null; + apartment: string; +} + +export function toEnterByLocation( + location: IAddress, + apartment: string +): IEnterByLocation { + return { + apartment, + house: location.house, + streetAddress: location.streetAddress, + city: location.city, + countryId: location.countryId, + postcode: location.postcode, + notes: location.notes, + } as any; +} + +export default IEnterByLocation; diff --git a/packages/common/src/interfaces/IGeoLocation.ts b/packages/common/src/interfaces/IGeoLocation.ts new file mode 100644 index 0000000..eafc409 --- /dev/null +++ b/packages/common/src/interfaces/IGeoLocation.ts @@ -0,0 +1,49 @@ +import { DBCreateObject, DBRawObject, PyroObjectId } from '../@pyro/db'; +import { Country } from '../entities/GeoLocation'; + +export interface ILocation { + type: 'Point'; + coordinates: [number, number]; +} + +export interface IAddress { + countryId: Country | null; + city: string | null; + postcode?: string | null; + notes?: string | null; + streetAddress: string | null; + house: string | null; +} + +export function getEmptyAddress(): IAddress { + return { + countryId: null, + city: '', + postcode: '', + notes: '', + streetAddress: '', + house: '', + }; +} + +export interface IGeoLocationCreateObject extends IAddress, DBCreateObject { + loc: ILocation; +} + +export interface IGeolocationUpdateObject { + countryId?: Country | null; + city?: string | null; + postcode?: string | null; + notes?: string | null; + streetAddress?: string | null; + house?: string | null; + loc?: ILocation; +} + +interface IGeoLocation extends DBRawObject, IGeoLocationCreateObject { + _id: PyroObjectId; + _createdAt: Date | string; + _updatedAt: Date | string; +} + +export default IGeoLocation; diff --git a/packages/common/src/interfaces/IInvite.ts b/packages/common/src/interfaces/IInvite.ts new file mode 100644 index 0000000..4b01720 --- /dev/null +++ b/packages/common/src/interfaces/IInvite.ts @@ -0,0 +1,26 @@ +import IGeoLocation, { + IGeoLocationCreateObject, + IGeolocationUpdateObject, +} from './IGeoLocation'; + +import { DBCreateObject, DBRawObject, PyroObjectId } from '../@pyro/db'; + +export interface IInviteCreateObject extends DBCreateObject { + code?: string; + apartment: string; + geoLocation: IGeoLocationCreateObject; +} + +export interface IInviteUpdateObject { + code?: string; + apartment?: string; + geoLocation?: IGeolocationUpdateObject; +} + +interface IInvite extends IInviteCreateObject, DBRawObject { + _id: PyroObjectId; + code: string; + geoLocation: IGeoLocation; +} + +export default IInvite; diff --git a/packages/common/src/interfaces/IInviteRequest.ts b/packages/common/src/interfaces/IInviteRequest.ts new file mode 100644 index 0000000..024484a --- /dev/null +++ b/packages/common/src/interfaces/IInviteRequest.ts @@ -0,0 +1,30 @@ +import IGeoLocation, { + IGeoLocationCreateObject, + IGeolocationUpdateObject, +} from './IGeoLocation'; + +import { DBCreateObject, DBRawObject, PyroObjectId } from '../@pyro/db'; + +export interface IInviteRequestCreateObject extends DBCreateObject { + apartment: string; + geoLocation: IGeoLocationCreateObject; + deviceId?: string; + isManual?: boolean; + isInvited?: boolean; + invitedDate?: Date; +} + +export interface IInviteRequestUpdateObject { + apartment?: string; + isManual?: boolean; + geoLocation?: IGeolocationUpdateObject; + isInvited?: boolean; + invitedDate?: Date; +} + +export interface IInviteRequestRawObject + extends IInviteRequestCreateObject, + DBRawObject { + _id: PyroObjectId; + geoLocation: IGeoLocation; +} diff --git a/packages/common/src/interfaces/ILanguage.ts b/packages/common/src/interfaces/ILanguage.ts new file mode 100644 index 0000000..8b837c7 --- /dev/null +++ b/packages/common/src/interfaces/ILanguage.ts @@ -0,0 +1,22 @@ +// TODO: add other locales +type ILanguage = 'he-IL' | 'en-US' | 'ru-RU' | 'bg-BG' | 'es-ES' | 'fr-FR'; + +export default ILanguage; + +export enum LanguageCodesEnum { + ENGLISH = 'en-US', + HEBREW = 'he-IL', + RUSSIAN = 'ru-RU', + BULGARIAN = 'bg-BG', + SPANISH = 'es-ES', + FRENCH = 'fr-FR', +} + +export enum LanguagesEnum { + ENGLISH = 'ENGLISH', + HEBREW = 'HEBREW', + RUSSIAN = 'RUSSIAN', + BULGARIAN = 'BULGARIAN', + SPANISH = 'SPANISH', + FRENCH = 'FRENCH', +} diff --git a/packages/common/src/interfaces/ILocale.ts b/packages/common/src/interfaces/ILocale.ts new file mode 100644 index 0000000..81ae8dc --- /dev/null +++ b/packages/common/src/interfaces/ILocale.ts @@ -0,0 +1,10 @@ +/** + * Used in translation of fields depending of selected locale + * + * @export + * @interface ILocaleMember + */ +export interface ILocaleMember { + locale: string; + value?: string; +} diff --git a/packages/common/src/interfaces/IOrder.ts b/packages/common/src/interfaces/IOrder.ts new file mode 100644 index 0000000..21a9fc9 --- /dev/null +++ b/packages/common/src/interfaces/IOrder.ts @@ -0,0 +1,166 @@ +import { ICarrierCreateObject } from './ICarrier'; +import IOrderProduct, { IOrderProductCreateObject } from './IOrderProduct'; +import IUser, { IUserCreateObject } from './IUser'; +import IWarehouse, { IWarehouseCreateObject } from './IWarehouse'; +import OrderCarrierStatus from '../enums/OrderCarrierStatus'; +import OrderWarehouseStatus from '../enums/OrderWarehouseStatus'; +import { DBCreateObject, DBRawObject, PyroObjectId } from '../@pyro/db'; +import Carrier from '../entities/Carrier'; +import DeliveryType from '../enums/DeliveryType'; + +export interface IOrderCreateObject extends DBCreateObject { + /** + * User which makes an order. + * Note: this is not a reference, but value we put here in the moment when creating an Order. + * This is needed because we don't want to allow user to + * change this data in his profile later so it effects user orders + * + * @type {IUserCreateObject} + * @memberof IOrderCreateObject + */ + user: IUserCreateObject; + + /** + * Every order go to single warehouse only. + * Currently, it's not possible to include items in the order from different warehouses by design! + * + * @type {(IWarehouseCreateObject | string)} + * @memberof IOrderCreateObject + */ + warehouse: IWarehouseCreateObject | string; + + /** + * Same as for user, it's not a reference but copy of data about products + * + * @type {IOrderProductCreateObject[]} + * @memberof IOrderCreateObject + */ + products: IOrderProductCreateObject[]; + + /** + * Client can confirm order or order can be auto-confirmed + * + * @type {boolean} + * @memberof IOrderCreateObject + */ + isConfirmed?: boolean; + + /** + * Order can be cancelled by user or by merchant + * + * @type {boolean} + * @memberof IOrderCreateObject + */ + isCancelled?: boolean; + + /** + * + * @type {boolean} + * @memberof IOrderCreateObject + */ + waitForCompletion?: boolean; + + /** + * Indicate if Order was fully paid + * + * @type {boolean} + * @memberof IOrderCreateObject + */ + isPaid?: boolean; + + /** + * Deliver time for order (DateTime when order was actually delivered to customer) + * + * @type {Date} + * @memberof IOrderCreateObject + */ + deliveryTime?: Date; + + /** + * Time when order processing is finished + * i.e order become canceled from customer or get failed during Store preparing etc. + * + * @type {Date} + * @memberof IOrderCreateObject + */ + finishedProcessingTime?: Date; + + /** + * The time when some carrier start delivery the order + * + * @type {Date} + * @memberof IOrderCreateObject + */ + startDeliveryTime?: Date; + + /** + * How many seconds more it should take to delivery order to customer + * (reset to 0 when order is delivered or when carrier is not deliver any order to the customer) + * + * @type {number} + * @memberof IOrderCreateObject + */ + deliveryTimeEstimate?: number; + + /** + * Check out OrderWarehouseStatus + * + * @type {OrderWarehouseStatus} + * @memberof IOrderCreateObject + */ + warehouseStatus?: OrderWarehouseStatus; + + /** + * Check out OrderCarrierStatus + * + * @type {OrderCarrierStatus} + * @memberof IOrderCreateObject + */ + carrierStatus?: OrderCarrierStatus; + + /** + * Some carrier which responsible to deliver order to the client + * (can be empty if order is not planed for delivery yet or not require delivery, e.g. pickup) + * + * @type {(ICarrierCreateObject | string | null)} + * @memberof IOrderCreateObject + */ + carrier?: ICarrierCreateObject | string | null; + + stripeChargeId?: string; + + /** + * Used for quick destinction between orders made in the same day (for packaging and etc) + * This value is autogenerated every day, so next day order numbers can repeat! + * To make unique order Id, add order date and make format like: 'orderDate-orderNumber', e.g. '04062018-5' + * + * @type {number} + * @memberof IOrderCreateObject + */ + orderNumber?: number; + + /** + * Check out DeliveryType + * + * @type {DeliveryType} + * @memberof IOrderCreateObject + */ + orderType?: DeliveryType; +} + +interface IOrder extends IOrderCreateObject, DBRawObject { + _id: PyroObjectId; + user: IUser; + warehouse: IWarehouse | string; + products: IOrderProduct[]; + carrier?: Carrier; + isConfirmed: boolean; + isCancelled: boolean; + isPaid: boolean; + warehouseStatus: OrderWarehouseStatus; + carrierStatus: OrderCarrierStatus; + orderNumber: number; + totalPrice?: number; +} + +export default IOrder; diff --git a/packages/common/src/interfaces/IOrderProduct.ts b/packages/common/src/interfaces/IOrderProduct.ts new file mode 100644 index 0000000..72eae39 --- /dev/null +++ b/packages/common/src/interfaces/IOrderProduct.ts @@ -0,0 +1,54 @@ +import IProduct from './IProduct'; +import { DBCreateObject, DBRawObject, PyroObjectId } from '../@pyro/db'; + +export interface IOrderProductCreateObject extends DBCreateObject { + initialPrice: number; + price: number; + count: number; + product: IProduct; + + /** + * Min delivery time (in minutes) + * + * @type {number} + * @memberof IOrderProductCreateObject + */ + deliveryTimeMin?: number; + + /** + * Max delivery time (in minutes) + * + * @type {number} + * @memberof IOrderProductCreateObject + */ + deliveryTimeMax?: number; + + /** + * Is product(s) require manufacturing + * + * @type {boolean} + * @memberof IOrderProductCreateObject + */ + isManufacturing?: boolean; + + /** + * Is product(s) become available only when carrier found + * + * @type {boolean} + * @memberof IOrderProductCreateObject + */ + isCarrierRequired?: boolean; + isDeliveryRequired?: boolean; + isTakeaway?: boolean; + comment?: string; +} + +interface IOrderProduct extends IOrderProductCreateObject, DBRawObject { + _id: PyroObjectId; + isManufacturing: boolean; + isCarrierRequired: boolean; + isDeliveryRequired: boolean; + isTakeaway?: boolean; +} + +export default IOrderProduct; diff --git a/packages/common/src/interfaces/IOrderProductInfo.ts b/packages/common/src/interfaces/IOrderProductInfo.ts new file mode 100644 index 0000000..45aa88e --- /dev/null +++ b/packages/common/src/interfaces/IOrderProductInfo.ts @@ -0,0 +1,8 @@ +import IOrderProduct from './IOrderProduct'; + +interface IOrderProductInfo { + orderId: string; + orderProduct: IOrderProduct; +} + +export default IOrderProductInfo; diff --git a/packages/common/src/interfaces/IPagingOptions.ts b/packages/common/src/interfaces/IPagingOptions.ts new file mode 100644 index 0000000..2b68f85 --- /dev/null +++ b/packages/common/src/interfaces/IPagingOptions.ts @@ -0,0 +1,5 @@ +export default interface IPagingOptions { + sort?: { field: string; sortBy: string }; + limit?: number; + skip?: number; +} diff --git a/packages/common/src/interfaces/IPaymentGateway.ts b/packages/common/src/interfaces/IPaymentGateway.ts new file mode 100644 index 0000000..94a4b6e --- /dev/null +++ b/packages/common/src/interfaces/IPaymentGateway.ts @@ -0,0 +1,17 @@ +import PaymentGateways from '../enums/PaymentGateways'; +import { DBCreateObject, DBRawObject, PyroObjectId } from '@pyro/db'; + +export interface IPaymentGatewayCreateObject extends DBCreateObject { + paymentGateway: PaymentGateways; + configureObject: any; +} + +export interface IPaymentGateway + extends DBRawObject, + IPaymentGatewayCreateObject { + _id: PyroObjectId; + _createdAt: Date | string; + _updatedAt: Date | string; +} + +export default IPaymentGatewayCreateObject; diff --git a/packages/common/src/interfaces/IPlatform.ts b/packages/common/src/interfaces/IPlatform.ts new file mode 100644 index 0000000..ed300fc --- /dev/null +++ b/packages/common/src/interfaces/IPlatform.ts @@ -0,0 +1,4 @@ +// Type of the client platform (browser, android or ios) +type IPlatform = 'android' | 'ios' | 'browser'; + +export default IPlatform; diff --git a/packages/common/src/interfaces/IProduct.ts b/packages/common/src/interfaces/IProduct.ts new file mode 100644 index 0000000..5ce8d2f --- /dev/null +++ b/packages/common/src/interfaces/IProduct.ts @@ -0,0 +1,39 @@ +import { DBCreateObject, DBRawObject, PyroObjectId } from '../@pyro/db'; +import { IProductsCategory } from './IProductsCategory'; +import { ILocaleMember } from './ILocale'; + +export interface IProductCreateObject extends DBCreateObject { + title: IProductTitle[]; + images: IProductImage[]; + details?: IProductDetails[]; + detailsHTML?: IProductDetailsHTML[]; + description: IProductDescription[]; + categories?: IProductsCategory[]; + descriptionHTML?: IProductDescriptionHTML[]; +} + +interface IProduct extends IProductCreateObject, DBRawObject { + _id: PyroObjectId; + descriptionHTML: IProductDescriptionHTML[]; + detailsHTML: IProductDetailsHTML[]; + categories: IProductsCategory[]; +} + +export interface IProductImage extends ILocaleMember { + url: string; + width: number; + height: number; + orientation: number; +} + +export interface IProductTitle extends ILocaleMember {} + +export interface IProductDetails extends ILocaleMember {} + +export interface IProductDetailsHTML extends ILocaleMember {} + +export interface IProductDescription extends ILocaleMember {} + +export interface IProductDescriptionHTML extends ILocaleMember {} + +export default IProduct; diff --git a/packages/common/src/interfaces/IProductInfo.ts b/packages/common/src/interfaces/IProductInfo.ts new file mode 100644 index 0000000..38acb38 --- /dev/null +++ b/packages/common/src/interfaces/IProductInfo.ts @@ -0,0 +1,25 @@ +import IWarehouseProduct from './IWarehouseProduct'; + +interface IProductInfo { + /** + * Where product field is populated! + * + * @type {IWarehouseProduct} + * @memberof IProductInfo + */ + warehouseProduct: IWarehouseProduct; + + distance: number; + + /** + * Id of warehouse where product is available + * + * @type {string} + * @memberof IProductInfo + */ + warehouseId: string; + + warehouseLogo: string; +} + +export default IProductInfo; diff --git a/packages/common/src/interfaces/IProductsCategory.ts b/packages/common/src/interfaces/IProductsCategory.ts new file mode 100644 index 0000000..06ce602 --- /dev/null +++ b/packages/common/src/interfaces/IProductsCategory.ts @@ -0,0 +1,20 @@ +import { DBCreateObject, DBRawObject, PyroObjectId } from '../@pyro/db'; +import { ILocaleMember } from './ILocale'; + +export interface IProductsCategoryCreateObject extends DBCreateObject { + name: IProductsCategoryName[]; + image?: string; +} + +/** + * Products category - allows organization (grouping) of the products + * Categories like "Pizza", "Burgers" and "Sushi" or "Main dishes", "Extras" and "Drinks" + * Or even "TV", "Car", etc + */ +export interface IProductsCategory + extends IProductsCategoryCreateObject, + DBRawObject { + _id: PyroObjectId; +} + +export interface IProductsCategoryName extends ILocaleMember {} diff --git a/packages/common/src/interfaces/IPromotion.ts b/packages/common/src/interfaces/IPromotion.ts new file mode 100644 index 0000000..f67ae76 --- /dev/null +++ b/packages/common/src/interfaces/IPromotion.ts @@ -0,0 +1,96 @@ +import { ILocaleMember } from './ILocale'; +import Product from '../entities/Product'; +import { DBRawObject, PyroObjectId, DBCreateObject } from '@pyro/db'; +import IWarehouse from './IWarehouse'; + +export interface IPromotionCreateObject extends DBCreateObject { + /** + * Promotion title locale + * + * @type {IPromotionTitle[]} + * @memberof IPromotionCreateObject + */ + title: IPromotionTitle[]; + + /** + * Promotion description locale + * + * @type {IPromotionDescription[]} + * @memberof IPromotionCreateObject + */ + description: IPromotionDescription[]; + + /** + * + * @type {boolean} + * @memberof IPromotionCreateObject + */ + active: boolean; + + /** + * @type {Date} + * @memberof IPromotionCreateObject + */ + activeFrom: Date; + + /** + * @type {Date} + * @memberof IPromotionCreateObject + */ + activeTo: Date; + + /** + * Url to Promotion picture/photo + * + * @type {string} + * @memberof IPromotionCreateObject + */ + image: string; + + /** + * @type {Product} + * @memberof IPromotionCreateObject + */ + product: Product; + + /** + * @type {string} + * @memberof IPromotionCreateObject + */ + productId?: string; + + /** + * @type {number} + * @memberof IPromotionCreateObject + */ + purchasesCount: number; + + /** + * + * @type {number} + * @memberof IPromotionCreateObject + */ + promoPrice: number; + + /** + * + * Id of warehouse this Promotion is associated with + * + * @type {IWarehouse} + * @memberof IPromotionCreateObject + */ + warehouse: IWarehouse; + + /** + * @type {string} + * @memberof IPromotionCreateObject + */ + warehouseId?: string; +} + +export interface IPromotion extends IPromotionCreateObject, DBRawObject { + _id: PyroObjectId; +} + +export interface IPromotionDescription extends ILocaleMember {} +export interface IPromotionTitle extends ILocaleMember {} diff --git a/packages/common/src/interfaces/IStreetLocation.ts b/packages/common/src/interfaces/IStreetLocation.ts new file mode 100644 index 0000000..3f8a80c --- /dev/null +++ b/packages/common/src/interfaces/IStreetLocation.ts @@ -0,0 +1,9 @@ +import { Country } from '../entities/GeoLocation'; + +interface IStreetLocation { + country: Country; + city: string; + streetAddress: string; +} + +export default IStreetLocation; diff --git a/packages/common/src/interfaces/IUser.ts b/packages/common/src/interfaces/IUser.ts new file mode 100644 index 0000000..e819d53 --- /dev/null +++ b/packages/common/src/interfaces/IUser.ts @@ -0,0 +1,35 @@ +import IGeoLocation, { IGeoLocationCreateObject } from './IGeoLocation'; +import { DBCreateObject, DBRawObject, PyroObjectId } from '../@pyro/db'; + +export interface IUserInitializeObject extends DBCreateObject { + firstName?: string; + lastName?: string; + email?: string; + phone?: string; + image?: string; + socialIds?: string[]; + isRegistrationCompleted?: boolean; + hash?: string; + isBanned?: boolean; +} + +export interface IUserCreateObject extends IUserInitializeObject { + geoLocation: IGeoLocationCreateObject; + devicesIds?: string[]; + apartment?: string; + stripeCustomerId?: string; +} + +export interface IResponseGenerate1000Customers { + success: boolean; + message: string; +} + +interface IUser extends IUserCreateObject, IUserInitializeObject, DBRawObject { + _id: PyroObjectId; + geoLocation: IGeoLocation; + devicesIds: string[]; + readonly fullAddress: string; +} + +export default IUser; diff --git a/packages/common/src/interfaces/IUserOrder.ts b/packages/common/src/interfaces/IUserOrder.ts new file mode 100644 index 0000000..7312aca --- /dev/null +++ b/packages/common/src/interfaces/IUserOrder.ts @@ -0,0 +1,31 @@ +import IGeoLocation, { IGeoLocationCreateObject } from './IGeoLocation'; +import { DBCreateObject, DBRawObject, PyroObjectId } from '../@pyro/db'; + +export interface IUserOrderInitializeObject extends DBCreateObject { + firstName?: string; + lastName?: string; + email?: string; + phone?: string; + socialIds?: string[]; + isRegistrationCompleted?: boolean; + hash?: string; +} + +export interface IUserOrderCreateObject extends IUserOrderInitializeObject { + geoLocation: IGeoLocationCreateObject; + devicesIds?: string[]; + apartment?: string; + stripeCustomerId?: string; +} + +interface IUserOrder + extends IUserOrderCreateObject, + IUserOrderInitializeObject, + DBRawObject { + _id: PyroObjectId; + geoLocation: IGeoLocation; + devicesIds: string[]; + readonly fullAddress: string; +} + +export default IUserOrder; diff --git a/packages/common/src/interfaces/IWarehouse.ts b/packages/common/src/interfaces/IWarehouse.ts new file mode 100644 index 0000000..30f0ce6 --- /dev/null +++ b/packages/common/src/interfaces/IWarehouse.ts @@ -0,0 +1,170 @@ +import IWarehouseProduct, { + IWarehouseProductCreateObject, +} from './IWarehouseProduct'; +import IGeoLocation, { IGeoLocationCreateObject } from './IGeoLocation'; +import { DBCreateObject, DBRawObject, PyroObjectId } from '../@pyro/db'; +import ForwardOrdersMethod from '../enums/ForwardOrdersMethod'; +import OrderBarcodeTypes from '../enums/OrderBarcodeTypes'; +import IPaymentGatewayCreateObject, { + IPaymentGateway, +} from './IPaymentGateway'; + +export interface IWarehouseCreateObject extends DBCreateObject { + /** + * Is Warehouse working now + * + * @type {boolean} + * @memberof IWarehouseCreateObject + */ + isActive?: boolean; + + /** + * Is Payment enabled (Warehouse may decide to accept only cash or accept also online payments) + * + * @type {boolean} + * @memberof IWarehouseCreateObject + */ + isPaymentEnabled?: boolean; + + /** + * Is Cash payment enabled (Warehouse may decide if they dont want cash payments) + * + * @type {boolean} + * @memberof IWarehouseCreateObject + */ + isCashPaymentEnabled?: boolean; + + /** + * Enable or disable in-store mode + * + * @type {boolean} + * @memberof IWarehouseCreateObject + */ + inStoreMode?: boolean; + + /** + * Warehouse current location (we support 'moving' warehouses/merchants) + * + * @type {IGeoLocationCreateObject} + * @memberof IWarehouseCreateObject + */ + geoLocation: IGeoLocationCreateObject; + + /** + * Products available at this warehouse for customer to purchase + * + * @type {IWarehouseProductCreateObject[]} + * @memberof IWarehouseCreateObject + */ + products?: IWarehouseProductCreateObject[]; + + name: string; + + /** + * URL of Merchant/Warehouse brand logo + * + * @type {string} + * @memberof IWarehouseCreateObject + */ + logo: string; + + /** + * Merchant admin user name + * + * @type {string} + * @memberof IWarehouseCreateObject + */ + username: string; + + contactEmail: string | null; + contactPhone: string | null; + + ordersEmail: string | null; + ordersPhone: string | null; + + /** + * The way how Orders forwarded to Merchant (email, phone, etc) + * + * @type {ForwardOrdersMethod} + * @memberof IWarehouseCreateObject + */ + forwardOrdersUsing: ForwardOrdersMethod[]; + + /** + * Is Warehouse products by default require manufacturing + * + * @type {boolean} + * @memberof IWarehouseCreateObject + */ + isManufacturing?: boolean; + + /** + * Is warehouse products by default become available only when carrier found + * (should we search for carrier before or after customer purchases product) + * + * @type {boolean} + * @memberof IWarehouseCreateObject + */ + isCarrierRequired?: boolean; + + devicesIds?: string[]; + + usedCarriersIds?: string[]; + + /** + * Merchant may use own carriers or use carriers shared between multiple merchants (e.g. from global catalog) + * + * @type {boolean} + * @memberof IWarehouseCreateObject + */ + hasRestrictedCarriers?: boolean; + carriersIds?: string[]; + + /** + * Password hash + * + * @type {string} + * @memberof IWarehouseCreateObject + */ + hash?: string; + + /** + * Payment Gateways + * + * @type {IPaymentGatewayCreateObject[]} + * @memberof IWarehouseCreateObject + */ + paymentGateways?: IPaymentGatewayCreateObject[]; + + productsDelivery?: boolean; + productsTakeaway?: boolean; + orderBarcodeType?: OrderBarcodeTypes; + barcodeData?: string; + useOnlyRestrictedCarriersForDelivery?: boolean; + preferRestrictedCarriersForDelivery?: boolean; + ordersShortProcess?: boolean; + orderCancelation?: { enabled: boolean; onState: number }; + carrierCompetition?: boolean; +} + +interface IWarehouse extends IWarehouseCreateObject, DBRawObject { + _id: PyroObjectId; + geoLocation: IGeoLocation; + isActive: boolean; + products: IWarehouseProduct[]; + isManufacturing: boolean; + isCarrierRequired: boolean; + devicesIds?: string[]; + usedCarriersIds: string[]; + hasRestrictedCarriers: boolean; + barcodeData?: string; + paymentGateways?: IPaymentGateway[]; + useOnlyRestrictedCarriersForDelivery?: boolean; + preferRestrictedCarriersForDelivery?: boolean; + ordersShortProcess?: boolean; + orderCancelation?: { enabled: boolean; onState: number }; + inStoreMode?: boolean; + carrierCompetition?: boolean; +} + +export default IWarehouse; diff --git a/packages/common/src/interfaces/IWarehouseProduct.ts b/packages/common/src/interfaces/IWarehouseProduct.ts new file mode 100644 index 0000000..98f588f --- /dev/null +++ b/packages/common/src/interfaces/IWarehouseProduct.ts @@ -0,0 +1,117 @@ +import IProduct from './IProduct'; +import { DBCreateObject, DBRawObject, PyroObjectId } from '../@pyro/db'; + +/** + * Represent Warehouse/Merchant inventory item (some product) for sale + * Each warehouse may have some qty of items of some product + * and price can change for all specific products (i.e. we currently do not support price per each product 'instance') + * E.g. say price decrease every 1 min for 1$ but for all 4 products in warehouse, + * not just for some products of given type + * + * @export + * @interface IWarehouseProductCreateObject + * @extends {DBCreateObject} + */ +export interface IWarehouseProductCreateObject extends DBCreateObject { + /** + * Price of product + * Note: some warehouses may have different prices for the same product (from global products catalog) + * compared to other warehouses. + * + * It is especially true, when prices go down/up in some warehouses more quickly compared to others, etc. + * + * @type {number} + * @memberof IWarehouseProductCreateObject + */ + price: number; + + initialPrice: number; + + /** + * How many products (qty) + * + * @type {number} + * @memberof IWarehouseProductCreateObject + */ + count?: number; + + /** + * How many products are sold + * + * @type {number} + * @memberof IWarehouseProductCreateObject + */ + soldCount?: number; + + product: IProduct | string; + + /** + * Is product(s) require manufacturing + * + * @type {boolean} + * @memberof IWarehouseProductCreateObject + */ + isManufacturing?: boolean; + + /** + * Is product aviavable + * + * @type {boolean} + * @memberof IWarehouseProductCreateObject + */ + isProductAvailable?: boolean; + + /** + * Is product(s) become available only when carrier found + * + * @type {boolean} + * @memberof IWarehouseProductCreateObject + */ + isCarrierRequired?: boolean; + + /** + * true - Delivery required, false - Takeaway + * + * @type {boolean} + * @memberof IWarehouseProductCreateObject + */ + isDeliveryRequired?: boolean; + + isTakeaway?: boolean; + + /** + * Min delivery time (in minutes) + * + * @type {number} + * @memberof IWarehouseProductCreateObject + */ + deliveryTimeMin?: number; + + /** + * Max delivery time (in minutes) + * + * @type {number} + * @memberof IWarehouseProductCreateObject + */ + deliveryTimeMax?: number; +} + +interface IWarehouseProduct extends IWarehouseProductCreateObject, DBRawObject { + _id: PyroObjectId; + + count: number; + + product: IProduct | string; + + isManufacturing: boolean; + + isCarrierRequired: boolean; + + isDeliveryRequired: boolean; + + isTakeaway?: boolean; + + isProductAvailable?: boolean; +} + +export default IWarehouseProduct; diff --git a/packages/common/src/notifications.ts b/packages/common/src/notifications.ts new file mode 100644 index 0000000..d88983c --- /dev/null +++ b/packages/common/src/notifications.ts @@ -0,0 +1,20 @@ +import Invite from './entities/Invite'; + +export const launched = 'launched_notification'; +export type launched = 'launched_notification'; + +export interface INotification { + event: launched; + + [key: string]: string; +} + +export interface ILaunchedNotification extends INotification { + event: launched; + invite: string; +} + +export interface ILaunchedNotificationParsed { + event: launched; + invite: Invite; +} diff --git a/packages/common/src/routers/IAdminRouter.ts b/packages/common/src/routers/IAdminRouter.ts new file mode 100644 index 0000000..50558a1 --- /dev/null +++ b/packages/common/src/routers/IAdminRouter.ts @@ -0,0 +1,23 @@ +import { Observable } from 'rxjs'; +import Admin from '../entities/Admin'; +import { CreateObject } from '@pyro/db/db-create-object'; +import IAdmin from '../interfaces/IAdmin'; + +export interface IAdminRegistrationInput { + admin: CreateObject; + password?: string; +} + +export interface IAdminLoginResponse { + admin: Admin; + token: string; +} + +interface IAdminRouter { + get(id: Admin['id']): Observable; + register(input: IAdminRegistrationInput): Promise; + login(email: string, password: string): Promise; + updateById(id: Admin['id'], updateObject: Partial): Promise; +} + +export default IAdminRouter; diff --git a/packages/common/src/routers/ICarrierOrdersRouter.ts b/packages/common/src/routers/ICarrierOrdersRouter.ts new file mode 100644 index 0000000..eac14a0 --- /dev/null +++ b/packages/common/src/routers/ICarrierOrdersRouter.ts @@ -0,0 +1,49 @@ +import OrderCarrierStatus from '../enums/OrderCarrierStatus'; +import Order from '../entities/Order'; +import { Observable } from 'rxjs'; +import Carrier from '../entities/Carrier'; + +export interface ICarrierOrdersRouterGetOptions { + populateWarehouse: boolean; + completion: 'completed' | 'not-completed' | 'all'; +} + +export interface ICarrierOrdersRouterGetAvailableOptions { + populateWarehouse: boolean; +} + +interface ICarrierOrdersRouter { + get( + id: Carrier['id'], + options?: ICarrierOrdersRouterGetOptions + ): Observable; + + getAvailable( + id: Carrier['id'], + options?: ICarrierOrdersRouterGetAvailableOptions + ): Observable; + + selectedForDelivery( + carrierId: Carrier['id'], + orderIds: Array + ): Promise; + + updateStatus( + carrierId: Carrier['id'], + newStatus: OrderCarrierStatus + ): Promise; + + cancelDelivery( + carrierId: Carrier['id'], + orderIds: Array + ): Promise; + + getCount(carrierId: Carrier['id']): Promise; + + skipOrders( + carrierId: Carrier['id'], + ordersIds: Array + ): Promise; +} + +export default ICarrierOrdersRouter; diff --git a/packages/common/src/routers/ICarrierRouter.ts b/packages/common/src/routers/ICarrierRouter.ts new file mode 100644 index 0000000..7ce442e --- /dev/null +++ b/packages/common/src/routers/ICarrierRouter.ts @@ -0,0 +1,52 @@ +import Carrier from '../entities/Carrier'; +import { Observable } from 'rxjs'; +import ICarrier from '../interfaces/ICarrier'; +import GeoLocation from '../entities/GeoLocation'; +import { CreateObject } from '@pyro/db/db-create-object'; + +export interface ICarrierRegistrationInput { + carrier: CreateObject; + password: string; +} + +export interface ICarrierLoginResponse { + carrier: Carrier; + token: string; +} + +interface ICarrierRouter { + login( + username: string, + password: string + ): Promise; + + get(id: Carrier['id']): Observable; + + getAllActive(): Observable; + + updateStatus(carrierId: Carrier['id'], newStatus: number): Promise; + + updateActivity( + carrierId: Carrier['id'], + activity: boolean + ): Promise; + + updateGeoLocation( + carrierId: Carrier['id'], + geoLocation: GeoLocation + ): Promise; + + register(input: ICarrierRegistrationInput): Promise; + + updatePassword( + id: Carrier['id'], + password: { current?: string; new: string } + ): Promise; + + updateById( + id: Carrier['id'], + updateObject: Partial + ): Promise; +} + +export default ICarrierRouter; diff --git a/packages/common/src/routers/IDeviceRouter.ts b/packages/common/src/routers/IDeviceRouter.ts new file mode 100644 index 0000000..40f18be --- /dev/null +++ b/packages/common/src/routers/IDeviceRouter.ts @@ -0,0 +1,19 @@ +import { IDeviceCreateObject } from '../interfaces/IDevice'; +import Device from '../entities/Device'; +import { Observable } from 'rxjs'; +import ILanguage from '../interfaces/ILanguage'; + +interface IDeviceRouter { + get(id: Device['id']): Observable; + + // getByUUIDAndPlatform(uuid: string, platform: IPlatform): Observable; + + create(device: IDeviceCreateObject): Promise; + + updateLanguage( + deviceId: Device['id'], + language: ILanguage + ): Promise; +} + +export default IDeviceRouter; diff --git a/packages/common/src/routers/IGeoLocationOrdersRouter.ts b/packages/common/src/routers/IGeoLocationOrdersRouter.ts new file mode 100644 index 0000000..93c27e0 --- /dev/null +++ b/packages/common/src/routers/IGeoLocationOrdersRouter.ts @@ -0,0 +1,17 @@ +import { Observable } from 'rxjs'; +import Order from '../entities/Order'; +import IGeoLocation from '../interfaces/IGeoLocation'; + +export interface IGeoLocationOrdersRouterGetOptions { + populateWarehouse?: boolean; + populateCarrier?: boolean; +} + +interface IGeoLocationOrdersRouter { + get( + geoLocation: IGeoLocation, + options?: IGeoLocationOrdersRouterGetOptions + ): Observable; +} + +export default IGeoLocationOrdersRouter; diff --git a/packages/common/src/routers/IGeoLocationProductsRouter.ts b/packages/common/src/routers/IGeoLocationProductsRouter.ts new file mode 100644 index 0000000..d06f167 --- /dev/null +++ b/packages/common/src/routers/IGeoLocationProductsRouter.ts @@ -0,0 +1,9 @@ +import { Observable } from 'rxjs'; +import IGeoLocation from '../interfaces/IGeoLocation'; +import ProductInfo from '../entities/ProductInfo'; + +interface IGeoLocationProductsRouter { + get(geoLocation: IGeoLocation): Observable; +} + +export default IGeoLocationProductsRouter; diff --git a/packages/common/src/routers/IGeoLocationWarehousesRouter.ts b/packages/common/src/routers/IGeoLocationWarehousesRouter.ts new file mode 100644 index 0000000..4fb8ed7 --- /dev/null +++ b/packages/common/src/routers/IGeoLocationWarehousesRouter.ts @@ -0,0 +1,17 @@ +import { Observable } from 'rxjs'; +import IGeoLocation from '../interfaces/IGeoLocation'; +import Warehouse from '../entities/Warehouse'; + +export interface IGeoLocationWarehousesRouterGetOptions { + fullProducts?: boolean; + activeOnly?: boolean; +} + +interface IGeoLocationWarehousesRouter { + get( + geoLocation: IGeoLocation, + options?: IGeoLocationWarehousesRouterGetOptions + ): Observable; +} + +export default IGeoLocationWarehousesRouter; diff --git a/packages/common/src/routers/IGeoLocationsRouter.ts b/packages/common/src/routers/IGeoLocationsRouter.ts new file mode 100644 index 0000000..efb3442 --- /dev/null +++ b/packages/common/src/routers/IGeoLocationsRouter.ts @@ -0,0 +1,8 @@ +interface IGeoLocationsRouter { + getAddressByCoordinatesUsingArcGIS( + lat: number, + lng: number + ): Promise; +} + +export default IGeoLocationsRouter; diff --git a/packages/common/src/routers/IInviteRequestRouter.ts b/packages/common/src/routers/IInviteRequestRouter.ts new file mode 100644 index 0000000..6bfd75b --- /dev/null +++ b/packages/common/src/routers/IInviteRequestRouter.ts @@ -0,0 +1,11 @@ +import { Observable } from 'rxjs'; +import InviteRequest from '../entities/InviteRequest'; +import { CreateObject } from '@pyro/db/db-create-object'; + +interface IInviteRequestRouter { + get(id: InviteRequest['id']): Observable; + + create(inviteRequest: CreateObject): Promise; +} + +export default IInviteRequestRouter; diff --git a/packages/common/src/routers/IInviteRouter.ts b/packages/common/src/routers/IInviteRouter.ts new file mode 100644 index 0000000..82c0d65 --- /dev/null +++ b/packages/common/src/routers/IInviteRouter.ts @@ -0,0 +1,22 @@ +import { Observable } from 'rxjs'; +import Invite from '../entities/Invite'; +import IEnterByLocation from '../interfaces/IEnterByLocation'; +import IEnterByCode from '../interfaces/IEnterByCode'; +import IStreetLocation from '../interfaces/IStreetLocation'; +import { CreateObject } from '@pyro/db/db-create-object'; + +interface IInviteRouter { + get(id: Invite['id']): Observable; + + getInvitedStreetLocations(): Observable; + + getByLocation(info: IEnterByLocation): Observable; + + getByCode(info: IEnterByCode): Observable; + + create(inviteCreateObject: CreateObject): Promise; + + getInvitesSettings(): Promise<{ isEnabled: boolean }>; +} + +export default IInviteRouter; diff --git a/packages/common/src/routers/IOrderRouter.ts b/packages/common/src/routers/IOrderRouter.ts new file mode 100644 index 0000000..cf0a951 --- /dev/null +++ b/packages/common/src/routers/IOrderRouter.ts @@ -0,0 +1,43 @@ +import { Observable } from 'rxjs'; +import Order from '../entities/Order'; +import OrderCarrierStatus from '../enums/OrderCarrierStatus'; +import OrderWarehouseStatus from '../enums/OrderWarehouseStatus'; +import Warehouse from '../entities/Warehouse'; + +export interface IOrderRouterGetOptions { + populateWarehouse?: boolean; + populateCarrier?: boolean; +} + +interface IOrderRouter { + get( + id: Order['id'], + options?: IOrderRouterGetOptions + ): Observable; + + confirm(orderId: Order['id']): Promise; + + updateCarrierStatus( + orderId: Order['id'], + status: OrderCarrierStatus + ): Promise; + + updateWarehouseStatus( + orderId: Order['id'], + status: OrderWarehouseStatus + ): Promise; + + addProducts( + orderId: Order['id'], + products, + warehouseId: Warehouse['id'] + ): Promise; + + removeProducts(orderId: Order['id'], productsIds: string[]): Promise; + + payWithStripe(orderId: Order['id'], cardId: string): Promise; + + refundWithStripe(orderId: Order['id']): Promise; +} + +export default IOrderRouter; diff --git a/packages/common/src/routers/IProductRouter.ts b/packages/common/src/routers/IProductRouter.ts new file mode 100644 index 0000000..a76f8be --- /dev/null +++ b/packages/common/src/routers/IProductRouter.ts @@ -0,0 +1,20 @@ +import { Observable } from 'rxjs'; +import IProduct from '../interfaces/IProduct'; +import Product from '../entities/Product'; +import { CreateObject } from '@pyro/db/db-create-object'; +import { UpdateObject } from '@pyro/db/db-update-object'; + +interface IProductRouter { + get(id: Product['id']): Observable; + + create(product: CreateObject): Promise; + + update( + id: Product['id'], + updateObject: UpdateObject + ): Promise; + + save(updatedProduct: IProduct): Promise; +} + +export default IProductRouter; diff --git a/packages/common/src/routers/IProductsCategoryRouter.ts b/packages/common/src/routers/IProductsCategoryRouter.ts new file mode 100644 index 0000000..01438fc --- /dev/null +++ b/packages/common/src/routers/IProductsCategoryRouter.ts @@ -0,0 +1,21 @@ +import { Observable } from 'rxjs'; +import ProductsCategory from '../entities/ProductsCategory'; +import { CreateObject } from '@pyro/db/db-create-object'; +import { UpdateObject } from '@pyro/db/db-update-object'; + +interface IProductsCategoryRouter { + get(id: ProductsCategory['id']): Observable; + + create( + createInput: CreateObject + ): Promise; + + update( + id: ProductsCategory['id'], + updateInput: UpdateObject + ): Promise; + + remove(id: ProductsCategory['id']): Promise; +} + +export default IProductsCategoryRouter; diff --git a/packages/common/src/routers/IUserAuthRouter.ts b/packages/common/src/routers/IUserAuthRouter.ts new file mode 100644 index 0000000..64c3dd2 --- /dev/null +++ b/packages/common/src/routers/IUserAuthRouter.ts @@ -0,0 +1,54 @@ +import User from '../entities/User'; +import { CreateObject } from '@pyro/db/db-create-object'; + +export interface AddableRegistrationInfo { + email?: string; + password: string; + + firstName?: string; + lastName?: string; + phone?: string; +} + +export interface IUserRegistrationInput { + user: CreateObject; + password?: string; +} + +export interface IUserLoginResponse { + user: User; + token: string; +} + +interface IUserAuthRouter { + /** + * Register Customer with given details + * Note: if invites system is on, it throws NotInvited if customer not invited + * + * @param {IUserRegistrationInput} input + * @returns {Promise} + * @memberof IUserAuthRouter + */ + register(input: IUserRegistrationInput): Promise; + + login( + username: string, + password: string + ): Promise; + + addRegistrationInfo( + id: User['id'], + info: AddableRegistrationInfo + ): Promise; + + updatePassword( + id: User['id'], + password: { current: string; new: string } + ): Promise; + + getRegistrationsSettings(): Promise<{ + registrationRequiredOnStart: boolean; + }>; +} + +export default IUserAuthRouter; diff --git a/packages/common/src/routers/IUserOrdersRouter.ts b/packages/common/src/routers/IUserOrdersRouter.ts new file mode 100644 index 0000000..56403a4 --- /dev/null +++ b/packages/common/src/routers/IUserOrdersRouter.ts @@ -0,0 +1,9 @@ +import Order from '../entities/Order'; +import { Observable } from 'rxjs'; +import User from '../entities/User'; + +interface IUserOrdersRouter { + get(userId: User['id']): Observable; +} + +export default IUserOrdersRouter; diff --git a/packages/common/src/routers/IUserProductsRouter.ts b/packages/common/src/routers/IUserProductsRouter.ts new file mode 100644 index 0000000..f811593 --- /dev/null +++ b/packages/common/src/routers/IUserProductsRouter.ts @@ -0,0 +1,12 @@ +import { Observable } from 'rxjs'; +import User from '../entities/User'; +import Device from '../entities/Device'; + +interface IUserProductsRouter { + getPlaceholder( + userId: User['id'], + deviceId: Device['id'] + ): Observable; +} + +export default IUserProductsRouter; diff --git a/packages/common/src/routers/IUserRouter.ts b/packages/common/src/routers/IUserRouter.ts new file mode 100644 index 0000000..585d122 --- /dev/null +++ b/packages/common/src/routers/IUserRouter.ts @@ -0,0 +1,49 @@ +import { Observable } from 'rxjs'; +import User from '../entities/User'; +import GeoLocation from '../entities/GeoLocation'; +import { IUserCreateObject } from '../interfaces/IUser'; +import { Stripe } from 'stripe'; +import Device from '../entities/Device'; + +interface IUserRouter { + get(id: User['id']): Observable; + + updateUser(id: User['id'], user: IUserCreateObject): Promise; + + addPaymentMethod(userId: User['id'], tokenId: string): Promise; + + getCards(userId: User['id']): Promise; + + updateEmail(userId: User['id'], email: string): Promise; + + updateGeoLocation( + userId: User['id'], + geoLocation: GeoLocation + ): Promise; + + getAboutUs( + userId: User['id'], + deviceId: Device['id'], + selectedLanguage: string + ): Observable; + + getTermsOfUse( + userId: User['id'], + deviceId: Device['id'], + selectedLanguage: string + ): Observable; + + getHelp( + userId: User['id'], + deviceId: Device['id'], + selectedLanguage: string + ): Observable; + + getPrivacy( + userId: User['id'], + deviceId: Device['id'], + selectedLanguage: string + ): Observable; +} + +export default IUserRouter; diff --git a/packages/common/src/routers/IWarehouseCarriersRouter.ts b/packages/common/src/routers/IWarehouseCarriersRouter.ts new file mode 100644 index 0000000..d8f88e7 --- /dev/null +++ b/packages/common/src/routers/IWarehouseCarriersRouter.ts @@ -0,0 +1,10 @@ +import { Observable } from 'rxjs'; +import Warehouse from '../entities/Warehouse'; +import Carrier from '../entities/Carrier'; + +interface IWarehouseCarriersRouter { + get(warehouseId: Warehouse['id']): Observable; + updatePassword(id: Carrier['id'], password: string): Promise; +} + +export default IWarehouseCarriersRouter; diff --git a/packages/common/src/routers/IWarehouseOrdersRouter.ts b/packages/common/src/routers/IWarehouseOrdersRouter.ts new file mode 100644 index 0000000..e6f3e6a --- /dev/null +++ b/packages/common/src/routers/IWarehouseOrdersRouter.ts @@ -0,0 +1,56 @@ +import { Observable } from 'rxjs'; +import Order from '../entities/Order'; +import Product from '../entities/Product'; +import DeliveryType from '../enums/DeliveryType'; + +export interface IWarehouseOrdersRouterGetOptions { + populateWarehouse?: boolean; + populateCarrier?: boolean; + onlyAvailableToCarrier?: boolean; +} + +export interface IWarehouseOrdersRouterCreateOptions { + autoConfirm?: boolean; +} + +export interface IOrderCreateInputProduct { + count: number; + comment?: string; + productId: Product['id']; +} + +export interface IOrderCreateInput { + userId: string; + warehouseId: string; + products: IOrderCreateInputProduct[]; + + orderType?: DeliveryType; + waitForCompletion?: boolean; + options?: IWarehouseOrdersRouterCreateOptions; +} + +interface IWarehouseOrdersRouter { + get( + warehouseId: string, + options?: IWarehouseOrdersRouterGetOptions + ): Observable; + + create(createInput: IOrderCreateInput): Promise; + + cancel(orderId: string): Promise; + + addMore( + warehouseId: string, + userId: string, + orderId: string, + products: IOrderCreateInputProduct[] + ): Promise; + + createByProductType( + userId: string, + warehouseId: string, + productId: string + ): Promise; +} + +export default IWarehouseOrdersRouter; diff --git a/packages/common/src/routers/IWarehouseProductsRouter.ts b/packages/common/src/routers/IWarehouseProductsRouter.ts new file mode 100644 index 0000000..bc75f75 --- /dev/null +++ b/packages/common/src/routers/IWarehouseProductsRouter.ts @@ -0,0 +1,60 @@ +import { Observable } from 'rxjs'; +import WarehouseProduct from '../entities/WarehouseProduct'; + +import { + IWarehouseProductCreateObject, + default as IWarehouseProduct, +} from '../interfaces/IWarehouseProduct'; + +interface IWarehouseProductsRouter { + get(id: string, fullProducts?): Observable; + + getAvailable(warehouseId: string): Observable; + + add( + warehouseId: string, + products: IWarehouseProductCreateObject[] + ): Promise; + + saveUpdated( + warehouseId: string, + updatedWarehouseProduct: IWarehouseProduct + ): Promise; + + changePrice( + warehouseId: string, + productId: string, + price: number + ): Promise; + + decreaseCount( + warehouseId: string, + productId: string, + count: number + ): Promise; + + increaseCount( + warehouseId: string, + productId: string, + count: number + ): Promise; + + increaseSoldCount( + warehouseId: string, + productId: string, + count: number + ): Promise; + + decreaseSoldCount( + warehouseId: string, + productId: string, + count: number + ): Promise; + + getTopProducts( + warehouseId: string, + quantity: number + ): Observable; +} + +export default IWarehouseProductsRouter; diff --git a/packages/common/src/routers/IWarehouseRouter.ts b/packages/common/src/routers/IWarehouseRouter.ts new file mode 100644 index 0000000..79ee8ea --- /dev/null +++ b/packages/common/src/routers/IWarehouseRouter.ts @@ -0,0 +1,46 @@ +import { Observable } from 'rxjs'; +import Warehouse from '../entities/Warehouse'; +import { IGeoLocationCreateObject } from '../interfaces/IGeoLocation'; +import { CreateObject } from '@pyro/db/db-create-object'; + +export interface IWarehouseRegistrationInput { + warehouse: CreateObject; + password: string; +} + +export interface IWarehouseLoginResponse { + warehouse: Warehouse; + token: string; +} + +interface IWarehouseRouter { + login( + username: string, + password: string + ): Promise; + + get(id: string, fullProducts?: boolean): Observable; + + getAllActive(fullProducts?: boolean): Observable; + + register(input: IWarehouseRegistrationInput): Promise; + + updatePassword( + id: Warehouse['id'], + password: { current?: string; new: string } + ): Promise; + + updateGeoLocation( + warehouseId: string, + geoLocation: IGeoLocationCreateObject + ): Promise; + + updateAvailability( + warehouseId: string, + isAvailable: boolean + ): Promise; + + save(warehouse: Warehouse): Promise; +} + +export default IWarehouseRouter; diff --git a/packages/common/src/routers/IWarehouseUsersRouter.ts b/packages/common/src/routers/IWarehouseUsersRouter.ts new file mode 100644 index 0000000..e2c583b --- /dev/null +++ b/packages/common/src/routers/IWarehouseUsersRouter.ts @@ -0,0 +1,9 @@ +import { Observable } from 'rxjs'; +import User from '../entities/User'; +import Warehouse from '../entities/Warehouse'; + +interface IWarehouseUsersRouter { + get(warehouseId: Warehouse['id']): Observable; +} + +export default IWarehouseUsersRouter; diff --git a/packages/common/src/routers/index.ts b/packages/common/src/routers/index.ts new file mode 100644 index 0000000..ca02d5c --- /dev/null +++ b/packages/common/src/routers/index.ts @@ -0,0 +1,22 @@ +export * from './IAdminRouter'; +export * from './ICarrierOrdersRouter'; +export * from './ICarrierRouter'; +export * from './IDeviceRouter'; +export * from './IGeoLocationOrdersRouter'; +export * from './IGeoLocationProductsRouter'; +export * from './IGeoLocationsRouter'; +export * from './IGeoLocationWarehousesRouter'; +export * from './IInviteRequestRouter'; +export * from './IInviteRouter'; +export * from './IOrderRouter'; +export * from './IProductRouter'; +export * from './IProductsCategoryRouter'; +export * from './IUserAuthRouter'; +export * from './IUserOrdersRouter'; +export * from './IUserProductsRouter'; +export * from './IUserRouter'; +export * from './IWarehouseCarriersRouter'; +export * from './IWarehouseOrdersRouter'; +export * from './IWarehouseProductsRouter'; +export * from './IWarehouseRouter'; +export * from './IWarehouseUsersRouter'; diff --git a/packages/common/src/utils.ts b/packages/common/src/utils.ts new file mode 100644 index 0000000..fa41d5c --- /dev/null +++ b/packages/common/src/utils.ts @@ -0,0 +1,159 @@ +import GeoLocation from './entities/GeoLocation'; +import { ILocation } from './interfaces/IGeoLocation'; +import { sample } from 'underscore'; +import Order from './entities/Order'; + +namespace Utils { + /** + * This function takes in latitude and longitude of two location and returns the distance + * between them as the crow flies (in km) + * You are not meant to understand this... :) + * See https://stackoverflow.com/questions/18883601/function-to-calculate-distance-between-two-coordinates-shows-wrong + * + * @export + * @param {number} lat1 + * @param {number} lon1 + * @param {number} lat2 + * @param {number} lon2 + * @returns {number} + */ + export function calcCrow( + lat1: number, + lon1: number, + lat2: number, + lon2: number + ): number { + const R: number = 6371; // km + const dLat: number = Utils._toRad(lat2 - lat1); + const dLon: number = Utils._toRad(lon2 - lon1); + + lat1 = Utils._toRad(lat1); + lat2 = Utils._toRad(lat2); + + const a = + Math.pow(Math.sin(dLat / 2), 2) + + Math.pow(Math.sin(dLon / 2), 2) * Math.cos(lat1) * Math.cos(lat2); + + const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); + + const d = R * c; + + return d; + } + + // tslint:disable-next-line:no-shadowed-variable + export function getDistance( + geoLocation1: GeoLocation, + geoLocation2: GeoLocation + ): number { + return getLocDistance(geoLocation1.loc, geoLocation2.loc); + } + + export function getLocDistance(loc1: ILocation, loc2: ILocation): number { + return calcCrow( + loc1.coordinates[0], + loc1.coordinates[1], + loc2.coordinates[0], + loc2.coordinates[1] + ); + } + + /** + * Converts numeric degrees to radians + * + * @export + * @param {number} v + * @returns {number} + */ + export function _toRad(v: number): number { + return (v * Math.PI) / 180; + } + + /** + * Returns a random integer between min (included) and max (excluded) + * Using Math.round() will give you a non-uniform distribution! + * + * @export + * @param {*} min + * @param {*} max + * @returns {number} + */ + export function getRandomInt(min, max): number { + return Math.floor(Math.random() * (max - min)) + min; + } + + // tslint:disable-next-line:no-shadowed-variable + export function toDate(date: string | Date) { + if (date instanceof Date) { + return date; + } else { + return new Date(date); + } + } + + export function generatedLogoColor() { + return sample(['#269aff', '#ffaf26', '#8b72ff', '#0ecc9D']).replace( + '#', + '' + ); + } +} + +export const getDistance = Utils.getDistance; +export const toDate = Utils.toDate; +export const getDummyImage = ( + width: number, + height: number, + letter: string +) => { + return `https://dummyimage.com/${width}x${height}/${Utils.generatedLogoColor()}/ffffff.jpg&text=${letter}`; +}; + +export const getPlaceholditImgix = ( + width: number, + height: number, + fontSize: number, + name: string +) => { + return `https://placeholdit.imgix.net/~text?txtsize=${fontSize}&txt=${name}&w=${width}&h=${height}`; +}; + +export const getFakeImg = ( + width: number, + height: number, + fontSize: number, + name: string +) => { + return `https://fakeimg.pl/${width}x${height}/FFD890%2C128/000/?text=${name}&font_size=${fontSize}`; +}; + +export const generateObjectIdString = ( + m = Math, + d = Date, + h = 16, + s = (x) => m.floor(x).toString(h) +) => { + return ( + s(d.now() / 1000) + ' '.repeat(h).replace(/./g, () => s(m.random() * h)) + ); +}; + +export function getIdFromTheDate(order: Order): string { + if (!order['createdAt'] || !order.orderNumber) { + throw `Can't use getIdFromTheDate function. Property ${ + !order['createdAt'] ? 'createdAt' : 'orderNumber' + } is missing!`; + } + const [day, month, year] = new Date(order['createdAt']) + .toLocaleDateString() + .split('/'); + + let d = ('0' + day).slice(-2); + d = d.substr(-2); + let m = ('0' + month).slice(-2); + m = m.substr(-2); + + return `${d}${m}${year}-${order.orderNumber}`; +} + +export default Utils; diff --git a/packages/common/tsconfig.build.json b/packages/common/tsconfig.build.json new file mode 100644 index 0000000..ea6be8e --- /dev/null +++ b/packages/common/tsconfig.build.json @@ -0,0 +1,3 @@ +{ + "extends": "./tsconfig.json" +} diff --git a/packages/common/tsconfig.json b/packages/common/tsconfig.json new file mode 100644 index 0000000..7964326 --- /dev/null +++ b/packages/common/tsconfig.json @@ -0,0 +1,13 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "baseUrl": "./src", + "outDir": "./build", + "rootDir": "./src", + "types": ["node", "reflect-metadata", "jest"], + "paths": { + "@pyro/*": ["@pyro/*", "/@pyro/*"] + } + }, + "include": ["./src/**/*.ts"] +} diff --git a/packages/common/tslint.json b/packages/common/tslint.json new file mode 100644 index 0000000..d8c4873 --- /dev/null +++ b/packages/common/tslint.json @@ -0,0 +1,121 @@ +{ + "extends": ["tslint:latest", "tslint-config-prettier"], + "linterOptions": { + "exclude": ["node_modules", "dist"] + }, + "rules": { + "no-implicit-dependencies": false, + "no-submodule-imports": false, + "trailing-comma": [ + false, + { + "multiline": "always", + "singleline": "never" + } + ], + "interface-name": [false, "always-prefix"], + "arrow-return-shorthand": true, + "callable-types": true, + "class-name": true, + "comment-format": [true, "check-space"], + "forin": true, + "import-blacklist": [true], + "ordered-imports": false, + "interface-over-type-literal": true, + "label-position": true, + "max-line-length": [true, 120], + "member-access": false, + "no-arg": true, + "no-console": [false], + "no-construct": true, + "no-debugger": true, + "no-duplicate-super": true, + "no-empty": false, + "no-empty-interface": false, + "no-eval": true, + "no-misused-new": true, + "no-non-null-assertion": false, + "no-shadowed-variable": true, + "no-string-literal": false, + "no-string-throw": true, + "no-switch-case-fall-through": true, + "no-unnecessary-initializer": true, + "no-use-before-declare": true, + "no-var-keyword": true, + "object-literal-sort-keys": false, + "prefer-const": true, + "object-literal-key-quotes": false, + "no-angle-bracket-type-assertion": false, + "member-ordering": false, + "no-consecutive-blank-lines": false, + "radix": true, + "semicolon": [true, "always"], + "triple-equals": [true, "allow-null-check"], + "typeof-compare": true, + "unified-signatures": true, + "variable-name": false, + "directive-selector": [true, "attribute", "ngx", "camelCase"], + "component-selector": [ + true, + "element", + [ + "e-cu", // e-cu = ever customer + "ngx", + "ea", // ea = ever admin + "es" // es = ever store + ], + "kebab-case" + ], + "no-attribute-parameter-decorator": true, + "no-forward-ref": true, + "no-input-rename": true, + "no-output-rename": true, + "only-arrow-functions": false, + "pipe-naming": [true, "camelCase", "my"], + "use-host-property-decorator": true, + "ban": [ + true, + "eval", + "fit", + "fdescribe", + { + "name": "$", + "message": "please don't" + } + ], + "max-classes-per-file": [false], + "import-destructuring-spacing": true, + "invoke-injectable": true, + "no-access-missing-member": true, + "templates-use-public": true, + "use-input-property-decorator": true, + "use-life-cycle-interface": true, + "use-output-property-decorator": true, + "use-pipe-transform-interface": true, + "quotemark": [true, "single", "avoid-escape"], + "eofline": true, + "import-spacing": true, + "indent": [true, "tabs"], + "no-trailing-whitespace": true, + "one-line": [false], + "typedef-whitespace": [ + true, + { + "call-signature": "nospace", + "index-signature": "nospace", + "parameter": "nospace", + "property-declaration": "nospace", + "variable-declaration": "nospace" + } + ], + "whitespace": [ + true, + "check-branch", + "check-decl", + "check-operator", + "check-separator", + "check-type" + ], + "no-namespace": false + } +} diff --git a/packages/core/.dockerignore b/packages/core/.dockerignore new file mode 100644 index 0000000..3438721 --- /dev/null +++ b/packages/core/.dockerignore @@ -0,0 +1,10 @@ +.git +.gitignore +.gitmodules +README.md +docker +node_modules +tmp +build +dist +.env diff --git a/packages/core/.ebextensions/logging.config b/packages/core/.ebextensions/logging.config new file mode 100644 index 0000000..4ac4125 --- /dev/null +++ b/packages/core/.ebextensions/logging.config @@ -0,0 +1,8 @@ +files: + "/opt/elasticbeanstalk/tasks/bundlelogs.d/api.conf": + content: | + /tmp/logs* + + "/opt/elasticbeanstalk/tasks/taillogs.d/api.conf": + content: | + /tmp/logs/*.log diff --git a/packages/core/.ebextensions/nodecommand.config b/packages/core/.ebextensions/nodecommand.config new file mode 100644 index 0000000..c3e3fb0 --- /dev/null +++ b/packages/core/.ebextensions/nodecommand.config @@ -0,0 +1,4 @@ +option_settings: + - namespace: aws:elasticbeanstalk:container:nodejs + option_name: NodeCommand + value: "npm run-script prod" diff --git a/packages/core/.ebignore b/packages/core/.ebignore new file mode 100644 index 0000000..c2658d7 --- /dev/null +++ b/packages/core/.ebignore @@ -0,0 +1 @@ +node_modules/ diff --git a/packages/core/.elasticbeanstalk/config.yml b/packages/core/.elasticbeanstalk/config.yml new file mode 100644 index 0000000..a32d7ac --- /dev/null +++ b/packages/core/.elasticbeanstalk/config.yml @@ -0,0 +1,14 @@ +branch-defaults: + master: + environment: ever-api-env +environment-defaults: + ever-api-env: + branch: null + repository: null +global: + application_name: api + default_ec2_keyname: ever + default_platform: 64bit Amazon Linux 2016.03 v2.1.3 running Node.js + default_region: us-east-1 + profile: null + sc: git diff --git a/packages/core/.env.template b/packages/core/.env.template new file mode 100644 index 0000000..cc3bb24 --- /dev/null +++ b/packages/core/.env.template @@ -0,0 +1,66 @@ +# Don't forget to update src/env.ts on changes + +# NODE_ENV: production|development|test +NODE_ENV=development + +HTTPPORT=5500 +HTTPSPORT=2087 +GQLPORT=8443 +GQLPORT_SUBSCRIPTIONS=2086 + +HTTPS_CERT_PATH=certificates/https/cert.pem +HTTPS_KEY_PATH=certificates/https/key.pem + +LOGS_PATH=./tmp/logs + +DB_URI=mongodb://localhost/ever_development +TESTING_DB_URI=mongodb://localhost/ever_testing + +STRIPE_SECRET_KEY=[SOME SORT OF STRING KEY] + +URBAN_AIRSHIP_KEY=[SOME SORT OF STRING KEY] +URBAN_AIRSHIP_SECRET=[SOME SORT OF STRING KEY] + +AWS_ACCESS_KEY_ID=[SOME SORT OF STRING KEY] +AWS_SECRET_ACCESS_KEY=[SOME SORT OF STRING KEY] + +KEYMETRICS_MACHINE_NAME=[MACHINE NAME] +KEYMETRICS_SECRET_KEY=[SOME SORT OF STRING KEY] +KEYMETRICS_PUBLIC_KEY=[SOME SORT OF STRING KEY] + +GOOGLE_APP_ID=[SOME SORT OF STRING KEY] +GOOGLE_APP_SECRET=[SOME SORT OF STRING KEY] + +FACEBOOK_APP_ID=[SOME SORT OF STRING KEY] +FACEBOOK_APP_SECRET=[SOME SORT OF STRING KEY] + +JWT_SECRET=[SOME SORT OF STRING SECRET] +ADMIN_PASSWORD_BCRYPT_SALT_ROUNDS=12 +WAREHOUSE_PASSWORD_BCRYPT_SALT_ROUNDS=12 +CARRIER_PASSWORD_BCRYPT_SALT_ROUNDS=12 +USER_PASSWORD_BCRYPT_SALT_ROUNDS=10 + +SETTING_INVITES_ENABLED=false +FAKE_INVITE_CODE=8321 + +SETTINGS_REGISTRATIONS_REQUIRED_ON_START=false + +# Allows to reset Admin user Password +ADMIN_PASSWORD_RESET=true + +# Enables Fake data (seed) generator +FAKE_DATA_GENERATOR=true + +ARCGIS_CLIENT_ID=[SOME SORT OF STRING KEY] +ARCGIS_CLIENT_SECRET=[SOME SORT OF STRING SECRET] + +WEB_MEMORY=4096 +WEB_CONCURRENCY=1 + +IP_STACK_API_KEY=[IP_STACK_SERVICE_API_KEY_VALUE] + +# LOG_LEVEL: trace|debug|info|warn|error|fatal +LOG_LEVEL=info + +# Apollo Engine Key (optional, see https://www.apollographql.com/docs/platform/schema-registry) +ENGINE_API_KEY=service:XXXXX:XXXXXXXX diff --git a/packages/core/.gitignore b/packages/core/.gitignore new file mode 100644 index 0000000..7b954b1 --- /dev/null +++ b/packages/core/.gitignore @@ -0,0 +1,77 @@ +# Project specific +node_modules_backup +backup +certificates + +**/*.js +**/*.js.map +**/*.d.ts +**/*.d.ts.map +dist +build + +# Hot module reload configuration +!webpack.config.js + +# Testing +!jest.config.js +!src/test/setup.js + +# Bower +bower_components + +# Logs +log.txt +tmp/logs/*.log +npm-debug.log* +tmp/logs/nodejs/ + +# Runtime data +pids +*.pid +*.seed + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (http://nodejs.org/api/addons.html) +build/Release + +build + +# Dependency directory +# https://docs.npmjs.com/misc/faq#should-i-check-my-node-modules-folder-into-git +node_modules + +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm +*.iml + +## Directory-based project format: +.idea/ +.vscode/symbols.json + +public/js +iisnode/ +.env + +# Elastic Beanstalk Files + +.elasticbeanstalk/* + +!.elasticbeanstalk/config.yml +!.elasticbeanstalk/*.cfg.yml +!.elasticbeanstalk/*.global.yml + +# Do not store autogenerated docs in repo +/docs + +yarn-error.log diff --git a/packages/core/.snyk b/packages/core/.snyk new file mode 100644 index 0000000..3f37a21 --- /dev/null +++ b/packages/core/.snyk @@ -0,0 +1,10 @@ +# Snyk (https://snyk.io) policy file, patches or ignores known vulnerabilities. +version: v1.13.3 +ignore: {} +# patches apply the minimum changes required to fix a vulnerability +patch: + SNYK-JS-AXIOS-174505: + - axios: + patched: '2019-05-05T22:08:14.658Z' + - '@nestjs/common > axios': + patched: '2019-05-05T22:08:14.658Z' diff --git a/packages/core/CREDITS.md b/packages/core/CREDITS.md new file mode 100644 index 0000000..459fc59 --- /dev/null +++ b/packages/core/CREDITS.md @@ -0,0 +1,9 @@ +# CREDITS + +This application uses Open Source components and 3rd party libraries, which are licensed under their own respective Open-Source licenses. +You can find the links to source code of their open source projects along with license information below. +We acknowledge and are grateful to these developers for their contributions to open source. + +- Project: Nest https://github.com/nestjs/nest + Copyright (c) 2017 Kamil Myśliwiec + License (MIT) https://github.com/nestjs/nest/blob/master/LICENSE diff --git a/packages/core/GNU-AGPL-3.0.txt b/packages/core/GNU-AGPL-3.0.txt new file mode 100644 index 0000000..dba13ed --- /dev/null +++ b/packages/core/GNU-AGPL-3.0.txt @@ -0,0 +1,661 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU Affero General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +. diff --git a/packages/core/LICENSE.md b/packages/core/LICENSE.md new file mode 100644 index 0000000..3557116 --- /dev/null +++ b/packages/core/LICENSE.md @@ -0,0 +1,41 @@ +# License + +Copyright © 2016-present, Ever Co. LTD. All rights reserved. + +This software is available under different licenses + +### _Ever Platform Community Edition_ License for for Backend Api (Server) + +If you decide to choose the Ever Platform Community Edition License for for Backend Api (Server), you must comply with the following terms: + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License, version 3, +as published by the Free Software Foundation. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . + +[GNU Affero General Public License v3.0](https://www.gnu.org/licenses/agpl-3.0.txt) + +### _Ever Platform Enterprise_ License + +Alternatively, commercial versions of the software must be used in accordance with the terms and conditions of separate written agreement between you and Ever Co. LTD. + +For more information about Ever Platform Enterprise License please contact . + +#### The default Ever Platform license, without a valid Ever Platform Enterprise License agreement, is the Ever Platform Community Edition License. + +## Credits + +Please see [CREDITS.md](CREDITS.md) file for a list of libraries and software included in this program and information about licenses. + +## Trademarks + +**Ever**® is a registered trademark of [Ever Co. LTD](https://ever.co). +The trademark may only be used with the written permission of Ever Co. LTD. and may not be used to promote or otherwise market competitive products or services. +All other brand and product names are trademarks, registered trademarks or service marks of their respective holders. diff --git a/packages/core/README.md b/packages/core/README.md new file mode 100644 index 0000000..ac214a0 --- /dev/null +++ b/packages/core/README.md @@ -0,0 +1,192 @@ +# Ever Demand Backend API (Core) + +This is a real-time API backend project, written in [TypeScript](https://www.typescriptlang.org/) using [NodeJS](https://nodejs.org/), [Nest](https://nestjs.com), [ExpressJS](https://expressjs.com/) and other libraries. + +It includes/has features of: + +- **Real-time API** - The project uses [Socket.IO](https://socket.io/) and [RxJS](http://reactivex.io/rxjs/) for its reactive and real-time nature. +- **Database** - [MongoDB](https://www.mongodb.com/) is used as the DB and [Mongoose](http://mongoosejs.com/) as the ORM. +- **Deployment** - The production versions could be deployed to [AWS Elastic Beanstalk](https://aws.amazon.com/elasticbeanstalk/), using [PM2](http://pm2.io/) for clustering and fault tolerance. +- **Payments Accepting** - via Stripe API. + +#### Main stack: + +- [TypeScript](https://www.typescriptlang.org) - Main Programming Language. +- [NodeJS](https://nodejs.org) - Run-time environment. +- [NestJS](https://nestjs.com) - A progressive Node.js framework for building efficient, reliable and scalable server-side applications. +- [MongoDB](https://www.mongodb.com/) - Database. +- [AWS Elastic Beanstalk](https://aws.amazon.com/elasticbeanstalk/) - Hosting (optionally). +- [Stripe API](https://stripe.com/docs/api/node) - Payments accepting. + +#### Also used: + +- [ExpressJS](https://expressjs.com/) - Web Framework +- [Socket.IO](https://socket.io/) - For Real-time API +- [RxJS](http://reactivex.io/rxjs/) - For Reactive/Event Based Programming +- [InversifyJS](http://inversify.io/) - Inversion Control (Dependency Injection, used in addition to NestJS DI) +- [Mongoose](http://mongoosejs.com/) - ORM +- [PM2](http://pm2.io/) - NodeJS Production process manager (optionally, used in production environments) + +## Usage + +### Setting Up + +1. Install the dependeinces by running: + ``` + $ yarn install + ``` +2. Take a look for `.env.template` file, create `.env` file with the same variables and fill them appropriately. + +### Starting + +Simply run: + +``` +$ yarn start +``` + +_Note: make sure you have MongoDB installed locally with default settings_ + +### Testing + +To run tests execute following command: + +``` +$ yarn run test +``` + +### Deployment + +_To be added_ + +## Backend Project Structure + +``` +/.ebextensions - folder with AWS Elastic Beanstalk configuration files. +/.elasticbeanstalk - folder AWS Elastic Beanstalk configuration files. +/build - TypeScript transpiler build directory. +/certificates - SSL Certificates (optionally) +/dist - WebPack build output directory +/docker - Docker configuration (currently not in use) +/node_modules - folder with NPM packages (created after `yarn install`) +/res - Resourses, such as pages that sent to the clients in text form or json. + +/src - All the code of the project +|-->/app.ts - The main application file. +|-->/inversify.config.ts - The Dependency Injection config. +|-->/pm2bootstrap.ts - The file AWS runs to start the program. +| +|-->/main - The main application file +|-->/modules - contains the shared modules used in this project. +|-->/pyro - Custom written library for socket io and mongoose wrapping with classes. +\-->/test - Testing code + +/tmp/logs - All the logs created during server run locally or in production + +/.env - The configuration file. (which developer should create from .env.template) +/.env.template - The configuration file template. +/package.json - NPM packages config file. +/tsconfig.json - TypeScript compiler config file. +``` + +## Pyro - Custom-built Micro-Framework + +### Pyro DB + +Pyro DB is a wrapper for mongoose written in TypeScript which allows declaring models like that: + +```javascript +@ModelName('User') +class User extends DBObject implements IUser { + + @Schema({ type: String, required: false }) + public firstName?: string; + + @Schema({ type: String, required: false }) + public lastName?: string; + + @Schema({ type: String, required: false }) + public email?: string; + + @Schema(getSchema(GeoLocation)) public geoLocation: GeoLocation; + @Types.String() public apartment: string; + + @Schema({ type: String, required: false }) + public stripeCustomerId?: string; + + @Schema([String]) public devicesIds: string[]; +} +``` + +- The `@ModelName` decorator signals that a class is a model. +- `@Schema` specifies the schema of some field, `getSchema` is used to use embed other model. +- `@Types.String(default)`, `@Types.Number(default)`, `@Types.Boolean(default)` and `@Types.Date(default)` are used for primitive schemas. All of them are not nullable by default. +- To make a reference to some model in another collection. `@Types.Ref` is used like this: + + ```javascript + @Types.Ref(Carrier, { required: false }) + ``` + +It also contains the `DBService` abstract base class, which contains basic operations for every model with collection in the database. It follows the following structure: + +```javascript +export interface IDBService { + create(createObject: CreateObject): Promise; + remove(objectId: string): Promise; + removeAll(): Promise; + get(objectId: string): Observable; + getMultiple(ids: string[]): Observable; + find(conditions): Promise; + findOne(conditions): Promise; + update(objectId: string, updateObj: any): Promise; + updateMultiple(findObj: any, updateObj: any): Promise; + updateMultipleByIds(ids: string[], updateObj: any): Promise; +} +``` + +Many of the services in this projects extend `DBService`, for example: + +```javascript +class WarehousesService extends DBService implements IWarehouseRouter, IService { + public readonly DBObject = Warehouse; + + ... + + @observableListener() + get(id: string) { + return super.get(id); + } +} +``` + +To make any method of the `DBService` publicly available as part of the API please use the `@Listener` from Pyro IO. + +### Pyro IO + +#### Overview + +Allows declaring routers with auto generated Socket.io API right inside existed Services. +That makes it possible to execute corresponding Service methods from incoming WebSocket messages and reply back without the need to create additional Controllers on top of Services + +```javascript +@routerName('users') +class UsersService { + // returns Observable! + @observableListener() + get(id: string): Observable { + // ... + } + + // returns Promise! + @asyncListener() + async register(user: IUserCreateObject): Promise { + // ... + } +} +``` + +#### Decorators + +- `@routerName` - marks the class as a router. That makes it possible to call Service methods via WebSockets connection. +- `@observableListener()` is used to mark methods that return observable which allows real-time data-transfer to the client. +- `@asyncListener()` is used to mark methods that return promise, which allows returning one time response to the client. diff --git a/packages/core/graphql.config.json b/packages/core/graphql.config.json new file mode 100644 index 0000000..50ba5a6 --- /dev/null +++ b/packages/core/graphql.config.json @@ -0,0 +1,31 @@ +{ + "README_schema": "Specifies how to load the GraphQL schema that completion, error highlighting, and documentation is based on in the IDE", + "schema": { + "README_request": "To request the schema from a url instead, remove the 'file' JSON property above (and optionally delete the default graphql.schema.json file).", + "request": { + "url": "http://localhost:8443/graphql", + "method": "POST", + "README_postIntrospectionQuery": "Whether to POST an introspectionQuery to the url. If the url always returns the schema JSON, set to false and consider using GET", + "postIntrospectionQuery": true, + "README_options": "See the 'Options' section at https://github.com/then/then-request", + "options": { + "headers": { + "user-agent": "JS GraphQL" + } + } + } + }, + + "README_endpoints": "A list of GraphQL endpoints that can be queried from '.graphql' files in the IDE", + "endpoints": [ + { + "name": "Default (http://localhost:8443/graphql)", + "url": "http://localhost:8443/graphql", + "options": { + "headers": { + "user-agent": "JS GraphQL" + } + } + } + ] +} diff --git a/packages/core/jest.config.js b/packages/core/jest.config.js new file mode 100644 index 0000000..e3cfa20 --- /dev/null +++ b/packages/core/jest.config.js @@ -0,0 +1,16 @@ +module.exports = { + roots: ['/src'], + testURL: 'http://localhost', + testEnvironment: 'node', + transform: { + '^.+\\.tsx?$': 'ts-jest', + }, + testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.tsx?$', + moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'], + moduleNameMapper: { + '^@pyro/db-server(.*)$': '/src/@pyro/db-server$1', + '^@pyro/db(.*)$': '/src/modules/server.common/@pyro/db$1', + '^@pyro/io(.*)$': '/src/@pyro/io$1', + '^@modules(.*)$': '/src/modules$1', + }, +}; diff --git a/packages/core/nodemon-debug.json b/packages/core/nodemon-debug.json new file mode 100644 index 0000000..c6dc347 --- /dev/null +++ b/packages/core/nodemon-debug.json @@ -0,0 +1,6 @@ +{ + "watch": ["src"], + "ext": "ts", + "ignore": ["src/**/*.spec.ts"], + "exec": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register src/main.ts" +} diff --git a/packages/core/nodemon.json b/packages/core/nodemon.json new file mode 100644 index 0000000..3c4ebfd --- /dev/null +++ b/packages/core/nodemon.json @@ -0,0 +1,5 @@ +{ + "watch": ["build"], + "ext": "js", + "exec": "node build/main" +} diff --git a/packages/core/package.json b/packages/core/package.json new file mode 100644 index 0000000..e4c1c6c --- /dev/null +++ b/packages/core/package.json @@ -0,0 +1,322 @@ +{ + "name": "@ever-platform/core", + "description": "Ever Demand Platform Headless Framework Core", + "license": "AGPL-3.0", + "version": "0.4.3", + "homepage": "https://ever.co", + "repository": { + "type": "git", + "url": "https://github.com/ever-co/ever-demand.git" + }, + "bugs": { + "url": "https://github.com/ever-co/ever-demand/issues" + }, + "private": false, + "author": { + "name": "Ever Co. LTD", + "email": "ever@ever.co", + "url": "https://ever.co" + }, + "scripts": { + "coverage": "cross-env NODE_ENV=development NODE_OPTIONS=--max_old_space_size=4096 nyc report --reporter=text-lcov | coveralls", + "precommit": "cross-env NODE_ENV=development NODE_OPTIONS=--max_old_space_size=4096 lint-staged", + "start": "cross-env NODE_ENV=development NODE_OPTIONS=--max_old_space_size=4096 yarn tsc -p tsconfig.build.json && node build/main.js", + "start:dev": "cross-env NODE_ENV=development NODE_OPTIONS=--max_old_space_size=4096 concurrently \"wait-on build/main.js && nodemon\" \"tsc -w -p tsconfig.build.json\" ", + "start:prod": "cross-env NODE_ENV=production NODE_OPTIONS=--max_old_space_size=4096 yarn -p tsconfig.build.json tsc && node ./build/pm2bootstrap.js", + "build": "cross-env NODE_ENV=development NODE_OPTIONS=--max_old_space_size=4096 rimraf build && tsc -p tsconfig.build.json && rimraf dist && yarn webpack", + "build:dev": "yarn nest build --webpack", + "dev": "cross-env NODE_ENV=development NODE_OPTIONS=--max_old_space_size=4096 yarn tsc && node ./build/main.js", + "test": "cross-env NODE_ENV=development NODE_OPTIONS=--max_old_space_size=4096 jest", + "prod": "cross-env NODE_ENV=production NODE_OPTIONS=--max_old_space_size=4096 node ./build/pm2bootstrap.js", + "nestjs": "cross-env NODE_OPTIONS=--max_old_space_size=4096 node ./dist/server", + "webpack": "cross-env NODE_ENV=development NODE_OPTIONS=--max_old_space_size=4096 webpack --config webpack.config.js", + "lint": "cross-env NODE_ENV=development NODE_OPTIONS=--max_old_space_size=4096 tslint -p tsconfig.json -c tslint.json", + "format": "cross-env NODE_ENV=development NODE_OPTIONS=--max_old_space_size=4096 prettier **/**/*.ts --ignore-path ./.prettierignore --write && git status", + "docs": "cross-env NODE_ENV=development NODE_OPTIONS=--max_old_space_size=4096 typedoc --out docs --mode modules", + "snyk-protect": "yarn snyk protect" + }, + "publishConfig": { + "access": "public" + }, + "dependencies": { + "@captemulation/get-parameter-names": "^1.4.2", + "@ever-platform/common": "^0.4.3", + "@graphql-tools/load-files": "^6.5.2", + "@graphql-tools/merge": "^8.2.1", + "@graphql-tools/schema": "^8.3.1", + "@graphql-tools/utils": "^8.5.3", + "@godaddy/terminus": "^4.5.0", + "@grpc/grpc-js": "^1.4.0", + "@nestjs/common": "^8.2.3", + "@nestjs/core": "^8.2.3", + "@nestjs/cqrs": "^8.0.0", + "@nestjs/graphql": "^9.1.2", + "@nestjs/jwt": "^8.0.0", + "@nestjs/microservices": "^8.2.3", + "@nestjs/passport": "^8.0.1", + "@nestjs/platform-express": "^8.2.3", + "@nestjs/platform-socket.io": "^8.2.3", + "@nestjs/swagger": "^5.1.5", + "@nestjs/terminus": "^8.0.1", + "@nestjs/typeorm": "^8.0.2", + "@nestjs/websockets": "^8.2.3", + "@nestjsx/crud": "^5.0.0-alpha.3", + "@nestjsx/crud-typeorm": "^5.0.0-alpha.3", + "@ntegral/nestjs-sentry": "^3.0.6", + "@sentry/node": "^6.8.0", + "@sentry/tracing": "^6.8.0", + "apollo-server-core": "^3.3.0", + "apollo-server-express": "^3.5.0", + "apollo-server-fastify": "^3.3.0", + "app-root-path": "^3.0.0", + "archiver": "^5.3.0", + "aws-sdk": "^2.761.0", + "axios": "^0.23.0", + "bcrypt": "5.0.1", + "cache-manager": "^3.4.0", + "class-transformer": "^0.4.0", + "class-validator": "^0.13.0", + "cli-ux": "^5.5.0", + "cls-hooked": "^4.2.2", + "cross-fetch": "^3.1.5", + "csurf": "^1.11.0", + "csv-parser": "^2.3.2", + "csv-writer": "^1.6.0", + "currency.js": "^2.0.3", + "date-fns": "^2.22.1", + "email-templates": "^8.0.8", + "express-session": "^1.17.1", + "fast-json-stringify": "^2.4.1", + "fastify-swagger": "^4.12.4", + "bluebird": "^3.7.2", + "body-parser": "^1.19.0", + "bunyan": "^1.8.15", + "bunyan-cloudwatch": "^2.2.0", + "bunyan-prettystream": "^0.1.3", + "cli-color": "^2.0.1", + "concurix": "^1.1.2", + "connect": "^3.7.0", + "cookie-session": "^2.0.0-beta.3", + "core-decorators": "^0.20.0", + "cors": "^2.8.5", + "cryptiles": "^4.1.3", + "destroy": "^1.0.4", + "envalid": "^7.2.1", + "errorhandler": "^1.5.1", + "etag": "^1.8.1", + "event-stream": "4.0.1", + "express": "^4.17.1", + "express-handlebars": "^5.3.4", + "faker": "^5.5.3", + "fstream": "^1.0.12", + "fs-extra": "^9.1.0", + "graphql": "15.7.2", + "graphql-tools": "^8.2.0", + "graphql-playground-middleware-express": "^1.7.23", + "graphql-subscriptions": "^2.0.0", + "subscriptions-transport-ws": "^0.11.0", + "handlebars": "^4.7.7", + "helmet": "^4.1.1", + "jsonwebtoken": "^8.5.1", + "kafkajs": "^1.14.0", + "mjml": "^4.7.0", + "moment": "^2.29.1", + "moment-range": "^4.0.2", + "moment-timezone": "^0.5.31", + "mqtt": "^4.2.1", + "multer-s3": "^2.9.0", + "mysql": "^2.18.1", + "nats": "^2.0.2", + "nest-router": "^1.0.9", + "nestjs-i18n": "^8.1.2", + "node-fetch": "^2.6.1", + "nodemailer": "^6.4.11", + "nodemailer-handlebars": "^1.0.1", + "image-size": "^1.0.0", + "install": "^0.13.0", + "inversify": "^6.0.1", + "ipstack": "^0.1.1", + "lodash": "^4.17.21", + "lodash.template": "^4.5.0", + "lodash.templatesettings": "^4.2.0", + "method-override": "^3.0.0", + "mkdirp": "^1.0.4", + "module-alias": "^2.2.2", + "mongodb": "^4.2.1", + "mongodb-memory-server": "^8.0.4", + "mongoose": "^6.0.14", + "morgan": "^1.10.0", + "ms": "^2.1.3", + "on-finished": "^2.3.0", + "passport": "^0.5.0", + "passport-jwt": "^4.0.0", + "passport-facebook": "^3.0.0", + "passport-google-oauth20": "^2.0.0", + "passport-http-bearer": "^1.0.1", + "passport-local": "^1.0.0", + "passport-url": "^1.0.4", + "pdfmake": "^0.2.0", + "pg": "^8.6.0", + "pg-query-stream": "^4.1.0", + "redis": "^3.1.2", + "pem": "^1.14.4", + "pm2": "^5.1.2", + "reflect-metadata": "^0.1.13", + "request": "^2.88.0", + "request-ip": "^2.1.3", + "request-promise": "^4.2.4", + "rxjs": "^7.4.0", + "rxjs-compat": "^6.6.7", + "sharp": "^0.29.3", + "slugify": "^1.6.0", + "sql.js": "^1.5.0", + "sqlite3": "^5.0.2", + "serve-favicon": "^2.5.0", + "socket.io": "^4.3.0", + "socket.io-client": "^4.3.0", + "source-map-support": "^0.5.20", + "stripe": "^8.183.0", + "swagger-ui-express": "^4.2.0", + "symbol": "^0.3.1", + "symbol-observable": "^4.0.0", + "typeorm": "^0.2.41", + "typeorm-aurora-data-api-driver": "^2.3.3", + "typeorm-express-query-builder": "https://github.com/ever-co/typeorm-express-query-builder.git", + "typescript-collections": "^1.3.3", + "underscore": "^1.13.1", + "underscore.string": "^3.3.5", + "unleash-client": "^3.4.0", + "unzipper": "^0.10.11", + "upath": "^2.0.1", + "uuid": "^8.3.2", + "web-push": "^3.4.4", + "chalk": "4.1.2", + "validator": "^13.6.0", + "ws": "^8.3.0", + "yargs": "^16.2.0" + }, + "devDependencies": { + "@nestjs/cli": "^8.0.2", + "@nestjs/schematics": "^8.0.0", + "@nestjs/testing": "^8.0.0", + "@types/bcrypt": "^5.0.0", + "@types/bluebird": "^3.5.36", + "@types/email-templates": "^7.1.0", + "@types/express": "^4.17.13", + "@types/faker": "^5.5.8", + "@types/fs-extra": "5.0.2", + "@types/graphql": "^14.5.0", + "@types/i18n": "^0.12.0", + "@types/jest": "^27.0.2", + "@types/multer": "^1.4.4", + "@types/multer-s3": "^2.7.7", + "@types/node": "16.11.0", + "@types/node-fetch": "^1.6.9", + "@types/nodemailer": "^6.4.0", + "@types/passport": "^1.0.7", + "@types/passport-jwt": "^3.0.6", + "@types/socket.io": "^3.0.1", + "@types/socket.io-client": "^1.4.36", + "@types/supertest": "^2.0.10", + "@types/uuid": "^8.3.1", + "@types/web-push": "^3.3.0", + "@types/yargs": "^15.0.9", + "@types/body-parser": "^1.19.1", + "@types/bunyan": "^1.8.7", + "@types/bunyan-prettystream": "0.1.32", + "@types/core-decorators": "^0.20.0", + "@types/cors": "^2.8.12", + "@types/errorhandler": "^1.5.0", + "@types/form-data": "^2.2.1", + "@types/google-maps": "^3.2.3", + "@types/googlemaps": "^3.30.16", + "@types/handlebars": "^4.1.0", + "@types/inversify": "^2.0.33", + "@types/jsonwebtoken": "^8.5.5", + "@types/lodash": "^4.14.175", + "@types/method-override": "^0.0.32", + "@types/mkdirp": "^1.0.2", + "@types/moment": "^2.13.0", + "@types/mongodb": "^4.0.7", + "@types/mongoose": "^5.11.97", + "@types/redis": "^2.8.32", + "@types/reflect-metadata": "^0.1.0", + "@types/request": "^2.48.7", + "@types/request-promise": "^4.1.48", + "@types/sinon": "^10.0.4", + "@types/source-map-support": "^0.5.4", + "@types/stripe": "^8.0.416", + "@types/ws": "^8.2.0", + "copyfiles": "^2.4.1", + "dotenv": "^10.0.0", + "jest": "^26.6.3", + "nodemon": "^2.0.4", + "prettier": "^2.2.1", + "rimraf": "^3.0.2", + "supertest": "^6.0.1", + "ts-jest": "^26.4.4", + "ts-node": "~10.3.0", + "tslint": "^6.1.1", + "tslint-config-prettier": "^1.18.0", + "typescript": "~4.5.3" + }, + "jest": { + "moduleFileExtensions": [ + "js", + "json", + "ts" + ], + "rootDir": "src", + "testRegex": ".*\\.spec\\.ts$", + "transform": { + "^.+\\.(t|j)s$": "ts-jest" + }, + "collectCoverageFrom": [ + "**/*.(t|j)s" + ], + "coverageDirectory": "../coverage/packages/core", + "testEnvironment": "node" + }, + "nyc": { + "include": [ + "packages/**/*.ts" + ], + "exclude": [ + "node_modules/", + "packages/**/*.spec.ts", + "packages/core/adapters/*.ts", + "packages/websockets/adapters/*.ts", + "packages/**/nest-*.ts", + "packages/core/errors/**/*", + "packages/common/exceptions/*.ts", + "packages/common/http/*.ts", + "packages/microservices/exceptions/", + "packages/microservices/microservices-module.ts", + "packages/core/middleware/middleware-module.ts", + "packages/common/services/logger.service.ts" + ], + "extension": [ + ".ts" + ], + "require": [ + "ts-node/register" + ], + "reporter": [ + "text-summary", + "html" + ], + "sourceMap": true, + "instrument": true + }, + "lint-staged": { + "packages/**/*.{ts,json}": [ + "npm run format", + "git add" + ] + }, + "engines": { + "node": ">=14.4.0", + "yarn": ">=1.13.0" + }, + "snyk": false +} diff --git a/packages/core/res/templates/about_us/bg-BG.hbs b/packages/core/res/templates/about_us/bg-BG.hbs new file mode 100644 index 0000000..05b72e9 --- /dev/null +++ b/packages/core/res/templates/about_us/bg-BG.hbs @@ -0,0 +1,83 @@ + + +
+ +

Смятате ли, че онлайн пазаруването може да е по-добре?

+
+ Така че никога да не чакате половин час, вашата поръчката да бъде приготвяне. И никога няма + да бъде повредена или студена. Какво ще кажете за фразата "изчерпана", това е досадно, нали? +
+
В нашето приложение това никога няма да се случи! Ето защо ...
+ +

Незабавен

+

+ Всичко вече е подготвено и горещо. +
Получавате доставката от вашия таблет до вашата вратата в рамките на 5-10 минути. +

+ +

Лесно

+

+ Просто плъзнете за нещо, което харесвате, докоснете "Купете" и поръчката ви е на път. Можете да плащате по време на + доставката + или след нея! +

+ +

Познато и обичано

+ +

+ Ние доставяме от местни магазини и ресторанти, които вече познавате и обичате. Само добри изненади тук! +

+ + + +
+

Авторско право © 2016-2018 Ever Co. LTD. Всички права запазени. +
+

+
EVER® Са търговски марки на Ever Co. LTD +
Всички други търговски марки © към съответните собственици. Ние сме в α (алфа). Моляте бъди добър!
+
+ +
\ No newline at end of file diff --git a/packages/core/res/templates/about_us/en-US.hbs b/packages/core/res/templates/about_us/en-US.hbs new file mode 100644 index 0000000..6b24541 --- /dev/null +++ b/packages/core/res/templates/about_us/en-US.hbs @@ -0,0 +1,70 @@ + + +
+ +

Do you think online shopping could be better?

+
So, you never wait half an hour for your order to be prepared or cooked. And it never again comes broken or cold. + How about the phrase `out of stock`, it's annoying right?

In our app, this will never happen, ever! Here + is why...
+ +

INSTANT

+

+ All already prepared   hot.
+ Get it delivered from your tap to your door in 5-10 minutes. +

+ +

SIMPLE

+

+ Just swipe for something you like, tap `Buy` and your order is on its way. You can pay during or after delivery! +

+ +

SAFE & LOVE

+ +

+ We deliver from local stores and restaurants you already know and love. Only good surprises here! +

+ + + +
+

Copyright © 2016-present, Ever Co. LTD. All Rights Reserved.
+

+
EVER® is a registered trademark of Ever Co. LTD.
+ All other trademarks © to respective owners. We are in α (alpha). Please be nice!
+
+ +
\ No newline at end of file diff --git a/packages/core/res/templates/about_us/he-IL.hbs b/packages/core/res/templates/about_us/he-IL.hbs new file mode 100644 index 0000000..71e931c --- /dev/null +++ b/packages/core/res/templates/about_us/he-IL.hbs @@ -0,0 +1,66 @@ + + +
+ +

חושבים שקניות דרך האינטרנט יכולות להיות טובות יותר?

+
אצלינו אתה לא מחכה חצי שעה עד שהאוכל יהיה מוכן, והוא לעולם לא מגיע קר. + מה לגבי הביטוי "אזל מהמלאי", הוא מעצבן נכון?

+ באפליקציה שלנו, זה לעולם לא יקרה! זה למה...
+ +

INSTANT

+

הכל כבר מוכן מראש וחם.
קבל את זה ב-5-10 דקות מרגע הלחיצה עד לדלת.

+ +

SIMPLE

+

+ פשוט דפדף למשהו שמוצא חן בעינך, לחץ על `קנה` וההזמנה שלך בדרך. אתה יכול לשלם אחרי או בזמן המשלוח. +

+ +

SAFE & LOVE

+

+ אנחנו עושים משלוחים מהמסעדות הקרובות שלך שאתה כבר מכיר ואוהב. רק הפתעות טובות! +

+ + + +
+

זכיות יוצרים © 2016-2018 אוור קו. בע"מ. כל הזכויות שמורות
+

+
EVER® is a registered trademark of Ever Co. LTD.
+ כל שאר סימני המסחר שייכים לבעלי הזכיות המתאימים להם. אנחנו ב-α (אלפא), בבקשה תהיו נחמדים!
+
+ +
\ No newline at end of file diff --git a/packages/core/res/templates/about_us/ru-RU.hbs b/packages/core/res/templates/about_us/ru-RU.hbs new file mode 100644 index 0000000..36b2970 --- /dev/null +++ b/packages/core/res/templates/about_us/ru-RU.hbs @@ -0,0 +1,63 @@ + + +
+

Онлайн покупки могут быть лучше чем сейчас?

+
Так чтобы не ждать час, пока приготовят заказ. И чтобы его никогда не доставляли испорченным или холодным. + Как насчёт фразы `нет в наличии`, это раздражает, не так-ли?

+ В нашем приложении это никогда не случиться, НИКОГДА! И вот почему...
+ +

INSTANT

+

Все уже приготовленно и горячее.
Получите доставку до дверей всего за 5-10 минут.

+ +

SIMPLE

+

Просто найдите то что нравиться, нажмите `Купить` и заказ уже по дороге к Вам. Его можно оплатить во время или + после доставки!

+ +

SAFE & LOVE

+

Мы доставляем товары и еду из близких к Вам магазинов и ресторанов, с тех которые Вы уже знаете и любите. Вас ждут + только приятные сюрпризы!

+ + + +
+

Copyright © 2016-2018 Ever Co. LTD. Все Права Защищены.
+

+
EVER® являеться зарегистрированной торговой маркой Ever Co. LTD
+ Все другие торговые марки являються собственностью их соответствующих владельцев. Мы в α (альфа). Please be nice!
+
+ +
\ No newline at end of file diff --git a/packages/core/res/templates/help/bg-BG.hbs b/packages/core/res/templates/help/bg-BG.hbs new file mode 100644 index 0000000..a38c8c9 --- /dev/null +++ b/packages/core/res/templates/help/bg-BG.hbs @@ -0,0 +1 @@ +

Help content comming soon :)(

diff --git a/packages/core/res/templates/help/en-US.hbs b/packages/core/res/templates/help/en-US.hbs new file mode 100644 index 0000000..a38c8c9 --- /dev/null +++ b/packages/core/res/templates/help/en-US.hbs @@ -0,0 +1 @@ +

Help content comming soon :)(

diff --git a/packages/core/res/templates/help/he-IL.hbs b/packages/core/res/templates/help/he-IL.hbs new file mode 100644 index 0000000..a38c8c9 --- /dev/null +++ b/packages/core/res/templates/help/he-IL.hbs @@ -0,0 +1 @@ +

Help content comming soon :)(

diff --git a/packages/core/res/templates/help/ru-RU.hbs b/packages/core/res/templates/help/ru-RU.hbs new file mode 100644 index 0000000..a38c8c9 --- /dev/null +++ b/packages/core/res/templates/help/ru-RU.hbs @@ -0,0 +1 @@ +

Help content comming soon :)(

diff --git a/packages/core/res/templates/privacy/bg-BG.hbs b/packages/core/res/templates/privacy/bg-BG.hbs new file mode 100644 index 0000000..a027bf3 --- /dev/null +++ b/packages/core/res/templates/privacy/bg-BG.hbs @@ -0,0 +1,25 @@ + + +
+

Пълна Политика за защита на личните данни на Вече налични + тук.

+

+ Контролер на данни и собственик +
Евър Ко ООД, +
HaAtsmaut 38/3, Ashdod 77452, Израел, +
ever@ever.co +

+
\ No newline at end of file diff --git a/packages/core/res/templates/privacy/en-US.hbs b/packages/core/res/templates/privacy/en-US.hbs new file mode 100644 index 0000000..aeb7c8e --- /dev/null +++ b/packages/core/res/templates/privacy/en-US.hbs @@ -0,0 +1,25 @@ + + +
+

Full Privacy Policy of Ever available + here.

+

+ Data Controller and Owner +
Ever Co. LTD, +
HaAtsmaut 38/3, Ashdod 77452, Israel, +
ever@ever.co +

+
\ No newline at end of file diff --git a/packages/core/res/templates/privacy/he-IL.hbs b/packages/core/res/templates/privacy/he-IL.hbs new file mode 100644 index 0000000..f1a5708 --- /dev/null +++ b/packages/core/res/templates/privacy/he-IL.hbs @@ -0,0 +1,25 @@ + + +
+

מדיניות פרטיות מלאה של פעם זמין + here.

+

+ בקר נתונים ובעלים +
אי פעם ושות 'בע"מ, +
העצמאות 38/3, אשדוד 77452, ישראל, +
אי פעם +

+
\ No newline at end of file diff --git a/packages/core/res/templates/privacy/ru-RU.hbs b/packages/core/res/templates/privacy/ru-RU.hbs new file mode 100644 index 0000000..4e7cfad --- /dev/null +++ b/packages/core/res/templates/privacy/ru-RU.hbs @@ -0,0 +1,24 @@ + + +
+

Полные правила обеспечения конфиденциальности персональных данных для Ever® доступны здесь.

+

+ Контролер и Владелец данных
+ Ever Co. LTD,
+ HaAtsmaut 38/3, Ashdod 77452, Israel,
+ ever@ever.co +

+
\ No newline at end of file diff --git a/packages/core/res/templates/terms_of_use/bg-BG.hbs b/packages/core/res/templates/terms_of_use/bg-BG.hbs new file mode 100644 index 0000000..fb95c8f --- /dev/null +++ b/packages/core/res/templates/terms_of_use/bg-BG.hbs @@ -0,0 +1,2678 @@ + + +
+ +

Условия за Ползване

+ +
+ +

+ + + + Thank you for your interest in the Ever application for your mobile device (the "App") provided to you by + EVER CO. LTD ("EVER" "us" or "we"), and our web site at Ever.co (the "Site"), + as well as all related web sites, networks, downloadable software, and other services provided by us and on which + a link to this Terms of Service is displayed (collectively, together with the Apps and Site, our "Service"). + + + +

+

+ + + + PLEASE READ THE FOLLOWING TERMS OF SERVICE AGREEMENT CAREFULLY. + + + +

+

+ + + + These Terms of Service (these "Terms", “Agreement”, “Terms of Service”, "TOS"), including the Privacy + Policy incorporated into these Terms by reference and any other applicable policies and guidelines, as may be updated + from time to time, govern your use of the Service. These Terms constitute a legal agreement between you and EVER. + In order to use the Service you must agree to these Terms. + + + +

+

+ + + + BY DOWNLOADING, INSTALLING, OR OTHERWISE BY ACCESSING OR USING OUR SITES, MOBILE APPLICATIONS AND OUR SERVICES, YOU + HEREBY AGREE THAT YOU HAVE READ, UNDERSTOOD, AND AGREE TO BE TO BE BOUND BY THE TERMS AND ALL TERMS INCORPORATED HEREIN + BY REFERENCE. + + + +

+

+ + + + IT IS THE RESPONSIBILITY OF YOU, THE USER, CUSTOMER, OR PROSPECTIVE CUSTOMER TO READ THE TERMS AND CONDITIONS BEFORE + PROCEEDING TO USE THIS SITE OR OUR MOBILE APPS. + + + +

+

+ + + + IF YOU DO NOT EXPRESSLY AGREE TO ALL OF THE TERMS AND CONDITIONS, THEN PLEASE DO NOT ACCESS OR USE OUR SITES, MOBILE + APPS OR OUR SERVICES. + + + +

+

+ + + + THIS TERMS OF SERVICE AGREEMENT IS EFFECTIVE AS OF 11/06/2016. + + + +

+

+ + + + ACCEPTANCE OF TERMS + + + +

+

+ + + + The following Terms of Service Agreement is a legally binding agreement that shall govern the relationship with our + users and others which may interact or interface with EVER CO. LTD, also known as EVER, located at HaAtsmaut 38/3, + Ashdod 77452, Israel and our subsidiaries and affiliates, in association with the use of the EVER website and mobile + apps, which includes Ever.co, (the "Site") and its Services, EVER Applications (the “Apps”) and its Services.  + + + +

+

+ + + + Your use of the EVER Services may be subject to separate third party terms of service and fees, including without limitation + your mobile network operator’s terms of service and fees, including fees charged for data usage, messaging and overage, + which are your sole responsibility. + + + + +

+

+ + + + DESCRIPTION OF SERVICES OFFERED + + + +

+

+ + + + EVER provides a mobile apps and web-based technology platform (the "Platform", "Marketplace") that + connects consumers (the "Customers"), retail stores, and restaurants (together referenced as "Merchants"), + with third party independent contractors and third party independent contractors under agreement with EVER and certain + of EVER's affiliates (together referenced as "Couriers"). The Platform provides order placement services + for Customers and allows them to view, discuss, and place orders for food, beverages and goods, and pick-up and / + or delivery services in connection therewith, with Merchants and Restaurants. The platform allows Carriers to facilitate + on-demand same day delivery services for food, merchandise, goods and beverages. + + + +

+

+ + + + Through the Platform consumers may request that food, merchandise, goods or beverages, be delivered to them from particular + retail locations, stores or restaurants (Merchants). Couriers can access the Platform and receive delivery opportunities. + + + + +

+

+ + + + EVER IS NOT A RETAIL STORE, MERCHANT OF FOOD OR BEVERAGES, RESTAURANT, FOOD PICKUP AND / OR DELIVERY SERVICE, MERCHANDISE + DELIVERY SERVICE, OR FOOD PREPARATION ENTITY. YOU ACKNOWLEDGE THAT EVER DOES NOT PROVIDE TRANSPORTATION OR LOGISTICS + SERVICES OR FUNCTION AS A TRANSPORTATION CARRIER AND EVER DOES NOT PROVIDE DELIVERY SERVICES AND DOES NOT CONTROL + THE RESTAURANTS OR THE PRODUCTION OF ANY FOOD OR BEVERAGES, OR ANY PICK-UP OR DELIVERY SERVICES THEREWITH. INDEPENDENT + CONTRACTORS (EACH A "COURIER") OFFER DELIVERY SERVICES THROUGH USE OF THE SERVICE. EVER OFFERS INFORMATION + AND A METHOD TO OBTAIN COURIER SERVICES, BUT DOES NOT AND DOES NOT INTEND TO PROVIDE COURIER SERVICES OR ACT IN ANY + WAY AS A COURIER, AND HAS NO RESPONSIBILITY OR LIABILITY FOR ANY COURIER. + + + +

+

+ + + + The Merchants available through our Services operate indepenently of the EVER. The EVER will not assess the suitability, + legality or ability of any Carrier or Merchant. The EVER is not responsible for the Merchants food preparation or + safety and does not verify their compliance with applicable laws or regulations. The EVER has no responsibility or + liability for acts by any third-party Merchant or Carrier, other than as stated herein. EVER, including the Website, + Apps and the EVER Services, does not in any way independently verify the credentials or representations of any of + the Restaurants, the ingredients or the quality of any their products or services, or any Restaurant’s compliance + with applicable laws. + + + +

+

+ + + + Customers using the EVER Services must make themselves comfortable through the information provided by the Restaurants + on the Platform, by contacting the Restaurants directly, or through such other means or methods as they may deem appropriate, + as to the quality and reliability and quality of the Restaurants and the Restaurants’ compliance with applicable laws. + The EVER, including the Website, Apps and the EVER Services, does not in any way guarantee the quality of any Restaurant + or any food or beverage, or any pickup- up or delivery service in connection therewith, or any compliance thereof + with applicable laws. In addition, a Restaurant may represent certain standards with respect to their food preparation + (or other services), such as “organic,” “kosher,” “macrobiotic” or allergen- specific standards such as “nut-free,” + “gluten-free,” or “lactose-free”; EVER does not investigate or verify any such representations. EVER shall not be + liable or responsible for any food or beverages, or any other services, offered by the Restaurants or any errors or + misrepresentations made by them (including on the Website, Apps and through the EVER Services). + + + +

+

+ + + + Unless otherwise agreed by EVER in a separate written agreement with you, the Services are made available solely for + your personal, noncommercial use. + + + +

+

+ + + + As provided in greater detail in these Terms, you agree and acknowledge these material Terms: + + + +

+

+ + + + - The App is licensed, not sold to you, and you may use the Service only as set forth in these Terms; + + + +

+

+ + + + - Your use of the Service may be subject to separate third party terms of service and fees, including without limitation + your mobile network operator’s ("Carrier") terms of service and fees, including fees charged for data usage + and overage, which are your sole responsibility; + + + +

+

+ + + + - You consent to the collection, sharing, and use of your personally identifiable information in accordance with EVER + Privacy Policy; + + + +

+

+ + + + - The Service is provided "as is" without warranties of any kind, and EVER liability to you is limited; + + + +

+

+ + + + SCOPE OF APPS LICENSE + + + +

+

+ + + + Our Apps are licensed, not sold, to you for use only under the terms of this license. EVER reserves all rights not + expressly granted to you. Subject to your complete and ongoing compliance with these Terms, EVER hereby grants you + a personal, limited, revocable, non-transferable license to use our Apps on compatible devices that you own or control, + solely for your non-commercial use. + + + +

+

+ + + + You may not modify, alter, reproduce, distribute or make the Apps available over a network where it could be used by + multiple devices at the same time. You may not rent, lease, lend, sell, redistribute or sublicense the Apps. If you + breach these license restrictions, or otherwise exceed the scope of the license granted in these Terms, you may be + subject to prosecution and legal damages, as well as liability for infringement of intellectual property rights. These + Terms will govern any updates provided to you by EVER that replace and/or supplement the original Apps, unless the + upgrade is accompanied by a separate license in which case the terms of that license will govern. + + + +

+

+ + + + ACCOUNT ACCESS + + + + +

+

+ + + + You must be at least eighteen (18) years old to use the Service. By agreeing to these Terms, you represent and warrant + to us: + + + +

+

+ + + + - That you are at least eighteen (18) years old; + + + +

+

+ + + + - That you have not previously been suspended or removed from the Service; and + + + +

+

+ + + + - That your registration and your use of the Service is in compliance with any and all applicable laws and regulations. + + + +

+

+ + + + If you are using the Service on behalf of an entity, organization, or company, you represent and warrant that you have + the authority to bind that organization to these Terms and you agree to be bound by these Terms on behalf of that + organization. + + + +

+

+ + + + ACCOUNTS AND REGISTRATION + + + +

+

+ + + + Any and all visitors to our site and users of our apps, despite whether they are registered or not, shall be deemed + as "users" of the herein contained Services provided for the purpose of this TOS. + + + +

+

+ + + + Once an individual register's for our Services, through the process of creating an account, the user shall then be + considered a "member." + + + +

+

+ + + + To register and become a "member" of the Site or Apps, you must be at least 18 years of age to enter into + and form a legally binding contract. In addition, you must be in good standing and not an individual that has been + previously barred from receiving EVER's Services under the laws and statutes of the Israel, United States or other + applicable jurisdiction. + + + +

+

+ + + + When you register, EVER may collect information such as your first and last name, e-mail address, birth date, gender, + mailing address, occupation, industry and personal interests. + + + +

+

+ + + + You can edit your account information at any time. Once you register with EVER and sign in to our Services, you are + no longer anonymous to us. + + + +

+

+ + + + Furthermore, the registering party hereby acknowledges, understands and agrees to: + + + +

+

+ + + + a) furnish factual, correct, current and complete information with regards to yourself as may be requested by the data + registration process, and + + + +

+

+ + + + b) maintain and promptly update your registration and profile information in an effort to maintain accuracy and completeness + at all times. + + + +

+

+ + + + If anyone knowingly provides any information of a false, untrue, inaccurate or incomplete nature, EVER CO. LTD will + have sufficient grounds and rights to suspend or terminate the member in violation of this aspect of the Agreement, + and as such refuse any and all current or future use of EVER CO. LTD Services, or any portion thereof. + + + +

+

+ + + + The user and/or member acknowledges and agrees that the Services provided and made available through our website and + applications, which may include some mobile applications and that those applications may be made available on various + social media networking sites and numerous other platforms and downloadable programs, are the sole property of EVER + CO. LTD. At its discretion, EVER CO. LTD may offer additional website Services and/or products, or update, modify + or revise any current content and Services, and this Agreement shall apply to any and all additional Services and/or + products and any and all updated, modified or revised Services unless otherwise stipulated. EVER CO. LTD does hereby + reserve the right to cancel and cease offering any of the aforementioned Services and/or products. You, as the end + user and/or member, acknowledge, accept and agree that EVER CO. LTD shall not be held liable for any such updates, + modifications, revisions, suspensions or discontinuance of any of our Services and/or products. Your continued use + of the Services provided, after such posting of any updates, changes, and/or modifications shall constitute your acceptance + of such updates, changes and/or modifications, and as such, frequent review of this Agreement and any and all applicable + terms and policies should be made by you to ensure you are aware of all terms and policies currently in effect. Should + you not agree to the updated, revised or modified terms, you must stop using the provided Services forthwith. + + + +

+

+ + + + Furthermore, the user and/or member understands, acknowledges and agrees that the Services offered shall be provided + "AS IS" and as such EVER CO. LTD shall not assume any responsibility or obligation for the timeliness, missed + delivery, deletion and/or any failure to store user content, communication or personalization settings. + + + +

+

+ + + + PURCHASES + + + +

+

+ + + + If you wish to purchase any product, food or service made available through the Service ("Purchase"), you + may be asked to supply certain information relevant to your Purchase including, without limitation, your first and + last name, birth date, gender, credit card information, physical and mailing addresses, e-mail address and phone number + or other contact information. + + + +

+

+ + + + When you place an order through the Apps, WebSite and EVER Services, you will be given a choice of payment options, + which may include via Apple Pay, Credit Card, PayPal®, Android Pay, and direct payment to the applicable Merchant + or Restaurant. If you pay for your purchase via Apple Pay, Credit Card, PayPal, or Android Pay, we will ask for a + valid Apple Pay account, Credit Card, PayPal account, or Android Pay account, as applicable, which will be billed + through the EVER Services, for the purchase price of the applicable order, and “EVER CO. LTD” or your name will be + the name that appears on the Apple Pay, Credit Card, PayPal, or Android Pay statement, as applicable. As stated above, + however, EVER, including the Apps, Website and the EVER Services, is not and shall not in any manner be considered + the seller of any of the food, beverages, and services ordered. + + + +

+

+ + + + PRICES + + + +

+

+ + + + You understand that: (a) the prices for food and goods displayed through the Services may differ from the prices offered + or published by Merchants for the same menu items, food or goods and/or from prices available at other third-party + websites and that such prices may not be the lowest prices at which the items are sold; (b) the Company has no obligation + to itemize its costs, profits or margins when publishing such prices; and (c) the Company reserves the right to change + such prices at any time, at its discretion. You are liable for all transaction taxes on the Services provided under + this Agreement (other than taxes based on the Company’s income). Payment will be processed by the Company, using the + preferred payment method designated in your account. + + + +

+

+ + + + GENERAL PAYMENT TERMS + + + +

+

+ + + + Certain features of the Service, including the placing of orders using the Service, may require you to pay fees. Before + you pay any fees, you will have an opportunity to review and accept an estimate of the fees that you will be charged. + All fees are non-refundable. This no refund policy applies at all times regardless of your decision to terminate your + usage, our decision to terminate your usage and any disruption caused to our Service for any reason whatsoever. + + + +

+

+ + + + EVER, at its sole discretion, may offer credits or refunds on a case-by-case basis; all credit and/or refund requests + must be made within fifteen (15) days after the delivery was completed. EVER may change the delivery or other fees + for any feature of the Service, including by adding fees, on a going-forward basis at any time. + + + +

+

+ + + + EVER will charge the payment method you specify at the time of purchase or as otherwise specified by you in your account + information. EVER reserves the right to determine final prevailing pricing. (Please note the pricing information published + on the website may not reflect the prevailing pricing.) EVER, at its sole discretion, may make promotional offers + with different features and different rates to any of our customers. These promotional offers, unless made to you, + shall have no bearing whatsoever on your offer or contract. We may change the fees for the Service at any time as + we deem necessary for our business. + + + +

+

+ + + + PAYMENT AUTHORIZATION + + + + +

+

+ + + + You authorize EVER to charge all sums for orders that you make and services you select to the payment method specified + in your account. When you order on EVER, a temporary pre-authorization hold might be placed on your payment card to + verify that the card is valid and has credit available for your intended purchase. Once your order is complete, you + will be charged the final order total and the pre-authorization hold will be lifted within 24-72 business hours, depending + on your bank. The pre-authorization hold remains even if an order is canceled by a customer or by EVER and may remain + on your card for up to 5 business days. + + + +

+

+ + + + EVER also may place an initial temporary pre-authorization hold on each new payment method you add to your account. + + + +

+

+ + + + PAYMENT WHEN CUSTOMER NOT AVAILABLE + + + +

+

+ + + + EVER reserves the right to charge a customer the full order amount if that customer is not at the designated delivery + location when the courier arrives to complete the delivery. + + + +

+

+ + + + PRIVACY POLICY + + + + +

+

+ + + + Every member's registration data and various other personal information are strictly protected by the EVER CO. LTD + Online Privacy Policy (see the full Privacy Policy at + https://ever.co/privacy/site.html). As a member, you herein consent to the collection and use of the information + provided, including the transfer of information within the United States, Israel and/or other countries for storage, + processing or use by EVER CO. LTD and/or our subsidiaries and affiliates. + + + +

+

+ + + + MEMBER ACCOUNT, USERNAME, PASSWORD AND SECURITY + + + +

+

+ + + + When you register, you will be asked to provide a password. You are solely responsible for maintaining the confidentiality + of your account and password, and any password for Facebook, Google, or other third party login. You accept responsibility + for all activities that occur under your account. If you have reason to believe that your account is no longer secure, + you must immediately notify us by our sending email to security@ever.co + + + + +

+

+ + + + When you set up an account, you are the sole authorized user of your account. You shall be responsible for maintaining + the secrecy and confidentiality of your password and for all activities that transpire on or within your account. + It is your responsibility for any act or omission of any user(s) that access your account information that, if undertaken + by you, would be deemed a violation of the TOS. It shall be your responsibility to notify EVER CO. LTD immediately + if you notice any unauthorized access or use of your account or password or any other breach of security. EVER CO. + LTD shall not be held liable for any loss and/or damage arising from any failure to comply with this term and/or condition + of the TOS. + + + +

+

+ + + + CONDUCT + + + +

+

+ + + + As a user or member of the Site or Apps, you herein acknowledge, understand and agree that all information, text, software, + data, photographs, music, video, messages, tags or any other content, whether it is publicly or privately posted and/or + transmitted, is the expressed sole responsibility of the individual from whom the content originated. In short, this + means that you are solely responsible for any and all content posted, uploaded, emailed, transmitted or otherwise + made available by way of the EVER Services, and as such, we do not guarantee the accuracy, integrity or quality of + such content. It is expressly understood that by use of our Services, you may be exposed to content including, but + not limited to, any errors or omissions in any content posted, and/or any loss or damage of any kind incurred as a + result of the use of any content posted, emailed, transmitted or otherwise made available by EVER. + + + +

+

+ + + + Furthermore, you herein agree not to make use of EVER CO. LTD's Services for the purpose of: + + + +

+

+ + + + a) uploading, posting, emailing, transmitting, or otherwise making available any content that shall be deemed unlawful, + harmful, threatening, abusive, harassing, tortious, defamatory, vulgar, obscene, libelous, or invasive of another's + privacy or which is hateful, and/or racially, ethnically, or otherwise objectionable; + + + +

+

+ + + + b) causing harm to minors in any manner whatsoever; + + + +

+

+ + + + c) impersonating any individual or entity, including, but not limited to, any EVER officials, forum leaders, guides + or hosts or falsely stating or otherwise misrepresenting any affiliation with an individual or entity; + + + + +

+

+ + + + d) forging captions, headings or titles or otherwise offering any content that you personally have no right to pursuant + to any law nor having any contractual or fiduciary relationship with; + + + +

+

+ + + + e) uploading, posting, emailing, transmitting or otherwise offering any such content that may infringe upon any patent, + copyright, trademark, or any other proprietary or intellectual rights of any other party; + + + +

+

+ + + + f) uploading, posting, emailing, transmitting or otherwise offering any content that you do not personally have any + right to offer pursuant to any law or in accordance with any contractual or fiduciary relationship; + + + +

+

+ + + + g) uploading, posting, emailing, transmitting, or otherwise offering any unsolicited or unauthorized advertising, promotional + flyers, "junk mail," "spam," or any other form of solicitation, except in any such areas that + may have been designated for such purpose; + + + + +

+

+ + + + h) uploading, posting, emailing, transmitting, or otherwise offering any source that may contain a software virus or + other computer code, any files and/or programs which have been designed to interfere, destroy and/or limit the operation + of any computer software, hardware, or telecommunication equipment; + + + +

+

+ + + + i) disrupting the normal flow of communication, or otherwise acting in any manner that would negatively affect other + users' ability to participate in any real time interactions; + + + +

+

+ + + + j) interfering with or disrupting any EVER CO. LTD Services, servers and/or networks that may be connected or related + to our website, including, but not limited to, the use of any device software and/or routine to bypass the robot exclusion + headers; + + + +

+

+ + + + k) intentionally or unintentionally violating any local, state, federal, national or international law, including, + but not limited to, rules, guidelines, and/or regulations decreed by the U.S. Securities and Exchange Commission, + in addition to any rules of any nation or other securities exchange, that would include without limitation, the New + York Stock Exchange, the American Stock Exchange, or the NASDAQ, and any regulations having the force of law; + + + +

+

+ + + + l) providing informational support or resources, concealing and/or disguising the character, location, and or source + to any organization delegated by the United States government as a "foreign terrorist organization" in accordance + to Section 219 of the Immigration Nationality Act; + + + +

+

+ + + + m) "stalking" or with the intent to otherwise harass another individual; and/or + + + +

+

+ + + + n) collecting or storing of any personal data relating to any other member or user in connection with the prohibited + conduct and/or activities which have been set forth in the aforementioned paragraphs. + + + +

+

+ + + + EVER CO. LTD herein reserves the right to pre-screen, refuse and/or delete any content currently available through + our Services. In addition, we reserve the right to remove and/or delete any such content that would violate the TOS + or which would otherwise be considered offensive to other visitors, users and/or members.   + + + +

+

+ + + + EVER CO. LTD herein reserves the right to access, preserve and/or disclose member account information and/or content + if it is requested to do so by law or in good faith belief that any such action is deemed reasonably necessary for:  + + + +

+

+ + + + a) compliance with any legal process;  + + + +

+

+ + + + b) enforcement of the TOS;  + + + +

+

+ + + + c) responding to any claim that therein contained content is in violation of the rights of any third party;  + + + +

+

+ + + + d) responding to requests for customer service; or  + + + +

+

+ + + + e) protecting the rights, property or the personal safety of EVER CO. LTD, its visitors, users and members, including + the general public. + + + +

+

+ + + + EVER CO. LTD herein reserves the right to include the use of security components that may permit digital information + or material to be protected, and that such use of information and/or material is subject to usage guidelines and regulations + established by EVER CO. LTD or any other content providers supplying content services to EVER CO. LTD. You are hereby + prohibited from making any attempt to override or circumvent any of the embedded usage rules in our Services. Furthermore, + unauthorized reproduction, publication, distribution, or exhibition of any information or materials supplied by our + Services, despite whether done so in whole or in part, is expressly prohibited. + + + +

+

+ + + + INTERSTATE COMMUNICATION + + + + +

+

+ + + + Upon registration, you hereby acknowledge that by using Site and Apps to send electronic communications, which would + include, but are not limited to, email, searches, instant messages, uploading of files, photos and/or videos, you + will be causing communications to be sent through our computer network. Therefore, through your use, and thus your + agreement with this TOS, you are acknowledging that the use of this Service shall result in interstate transmissions. + + + +

+

+ + + + CAUTIONS FOR GLOBAL USE AND EXPORT AND IMPORT COMPLIANCE + + + +

+

+ + + + Due to the global nature of the internet, through the use of our network you hereby agree to comply with all local + rules relating to online conduct and that which is considered acceptable Content. Uploading, posting and/or transferring + of software, technology and other technical data may be subject to the export and import laws of the United States + and possibly other countries. Through the use of our network, you thus agree to comply with all applicable export + and import laws, statutes and regulations, including, but not limited to, the Export Administration Regulations ( + http://www.access.gpo.gov/bis/ear/ear_data.html), as well as the sanctions control program of the United States + ( + http://www.treasury.gov/resource-center/sanctions/Programs/Pages/Programs.aspx). Furthermore, you state and pledge + that you: + + + +

+

+ + + + a) are not on the list of prohibited individuals which may be identified on any government export exclusion report + ( + http://www.bis.doc.gov/complianceandenforcement/liststocheck.htm) nor a member of any other government which may + be part of an export-prohibited country identified in applicable export and import laws and regulations; + + + +

+

+ + + + b) agree not to transfer any software, technology or any other technical data through the use of our network Services + to any export-prohibited country; + + + +

+

+ + + + c) agree not to use our website network Services for any military, nuclear, missile, chemical or biological weaponry + end uses that would be a violation of the Israel, U.S. export laws; and + + + +

+

+ + + + d) agree not to post, transfer nor upload any software, technology or any other technical data which would be in violation + of the Israel, U.S. or other applicable export and/or import laws. + + + +

+

+ + + + CONTENT PLACED OR MADE AVAILABLE FOR COMPANY SERVICES + + + +

+

+ + + + EVER CO. LTD shall not lay claim to ownership of any content submitted by any visitor, member, or user, nor make such + content available for inclusion on our website Services. Therefore, you hereby grant and allow for EVER CO. LTD the + below listed worldwide, royalty-free and non-exclusive licenses, as applicable: + + + +

+

+ + + + a) The content submitted or made available for inclusion on the publicly accessible areas of EVER CO. LTD's sites, + the license provided to permit to use, distribute, reproduce, modify, adapt, publicly perform and/or publicly display + said Content on our network Services is for the sole purpose of providing and promoting the specific area to which + this content was placed and/or made available for viewing. This license shall be available so long as you are a member + of EVER CO. LTD's sites, and shall terminate at such time when you elect to discontinue your membership. + + + +

+

+ + + + b) Photos, audio, video and/or graphics submitted or made available for inclusion on the publicly accessible areas + of EVER CO. LTD's sites, the license provided to permit to use, distribute, reproduce, modify, adapt, publicly perform + and/or publicly display said Content on our network Services are for the sole purpose of providing and promoting the + specific area in which this content was placed and/or made available for viewing. This license shall be available + so long as you are a member of EVER CO. LTD's sites and shall terminate at such time when you elect to discontinue + your membership. + + + +

+

+ + + + c) For any other content submitted or made available for inclusion on the publicly accessible areas of EVER CO. LTD's + sites, the continuous, binding and completely sub-licensable license which is meant to permit to use, distribute, + reproduce, modify, adapt, publish, translate, publicly perform and/or publicly display said content, whether in whole + or in part, and the incorporation of any such Content into other works in any arrangement or medium current used or + later developed. + + + +

+

+ + + + Those areas which may be deemed "publicly accessible" areas of EVER CO. LTD's sites are those such areas + of our network properties which are meant to be available to the general public, and which would include message boards + and groups that are openly available to both users and members. However, those areas which are not open to the public, + and thus available to members only, would include our mail system and instant messaging. + + + +

+

+ + + + CONTRIBUTIONS TO COMPANY WEBSITE + + + +

+

+ + + + EVER CO. LTD provides an area for our users and members to contribute feedback to our website. When you submit ideas, + documents, suggestions and/or proposals ("Contributions") to our site, you acknowledge and agree that: + + + +

+

+ + + + a) your contributions do not contain any type of confidential or proprietary information; + + + + +

+

+ + + + b) EVER shall not be liable or under any obligation to ensure or maintain confidentiality, expressed or implied, related + to any Contributions; + + + +

+

+ + + + c) EVER shall be entitled to make use of and/or disclose any such Contributions in any such manner as they may see + fit; + + + +

+

+ + + + d) the contributor's Contributions shall automatically become the sole property of EVER; and + + + +

+

+ + + + e) EVER is under no obligation to either compensate or provide any form of reimbursement in any manner or nature. + + + +

+

+ + + + INDEMNITY + + + +

+

+ + + + All users and/or members herein agree to insure and hold EVER CO. LTD, our subsidiaries, affiliates, agents, employees, + officers, partners and/or licensors blameless or not liable for any claim or demand, which may include, but is not + limited to, reasonable attorney fees made by any third party which may arise from any content a member or user of + our site may submit, post, modify, transmit or otherwise make available through our Services, the use of EVER Services + or your connection with these Services, your violations of the Terms of Service and/or your violation of any such + rights of another person. + + + +

+

+ + + + COMMERCIAL REUSE OF SERVICES + + + +

+

+ + + + The member or user herein agrees not to replicate, duplicate, copy, trade, sell, resell nor exploit for any commercial + reason any part, use of, or access to EVER's sites or Apps. + + + +

+

+ + + + USE AND STORAGE GENERAL PRACTICES + + + +

+

+ + + + You herein acknowledge that EVER CO. LTD may set up any such practices and/or limits regarding the use of our Services, + without limitation of the maximum number of days that any email, message posting or any other uploaded content shall + be retained by EVER CO. LTD, nor the maximum number of email messages that may be sent and/or received by any member, + the maximum volume or size of any email message that may be sent from or may be received by an account on our Service, + the maximum disk space allowable that shall be allocated on EVER CO. LTD's servers on the member's behalf, and/or + the maximum number of times and/or duration that any member may access our Services in a given period of time. In + addition, you also agree that EVER CO. LTD has absolutely no responsibility or liability for the removal or failure + to maintain storage of any messages and/or other communications or content maintained or transmitted by our Services. + You also herein acknowledge that we reserve the right to delete or remove any account that is no longer active for + an extended period of time. Furthermore, EVER CO. LTD shall reserve the right to modify, alter and/or update these + general practices and limits at our discretion. + + + + +

+

+ + + + Any messenger service, which may include any web-based versions, shall allow you and the individuals with whom you + communicate with the ability to save your conversations in your account located on EVER CO. LTD's servers. In this + manner, you will be able to access and search your message history from any computer with internet access. You also + acknowledge that others have the option to use and save conversations with you in their own personal account on Ever.co. + It is your agreement to this TOS which establishes your consent to allow EVER CO. LTD to store any and all communications + on its servers. + + + +

+

+ + + + THIRD-PARTY INTERACTIONS + + + +

+

+ + + + 1. Third-Party Providers + + + +

+

+ + + + During use of the Service, you may purchase goods and services from third-party merchants through the Service. Any + such activity, and any disputes, terms, conditions, warranties or representations associated with that activity, is + solely between you and the applicable third party. EVER and its licensors shall have no liability, obligation or responsibility + for any purchase or transaction between you and any third-party provider. In no event shall EVER or its licensors + be responsible for any content, products, services or other materials on or available from third-party sites or third-party + providers. Certain third-party providers of goods and/or services may require your agreement to additional or different + terms and conditions prior to your use of or access to such goods or services, and EVER disclaims any and all responsibility + or liability arising from such agreements between you and a third party. + + + +

+

+ + + + 2. Couriers + + + + +

+

+ + + + You may engage third-party Couriers through the Service to provide delivery services to you and may interact with those + Couriers. Any interactions or disputes between you and a Courier are solely between you and that Courier. EVER and + its licensors shall have no liability, obligation or responsibility for any interaction between you and any Courier. + + + + +

+

+ + + + 3. Third-Party Advertising + + + +

+

+ + + + The Service may contain third-party advertising and marketing. By agreeing to these Terms you agree to receive such + advertising and marketing. + + + + +

+

+ + + + 4. App Stores + + + +

+

+ + + + You acknowledge and agree that the availability of the EVER Apps is dependent on the third party from which you received + the Application license, e.g., the Apple iPhone or Android app stores (“App Store”). You acknowledge that this Agreement + is between you and the EVER and not with the App Store. The EVER, not the App Store, is solely responsible for the + Software and the Services, including the Applications and the Services, the content thereof, maintenance, support + services and warranty therefor, and addressing any claims relating thereto (e.g., product liability, legal compliance + or intellectual property infringement). In order to use the Applications, you must have access to a wireless network, + and you agree to pay all fees associated with such access. You also agree to pay all fees (if any) charged by the + App Store in connection with the Application or the Services. You agree to comply with, and your license to use the + Applications is conditioned upon your compliance with, all applicable third-party terms of agreement (e.g., the App + Store’s terms and policies) when using the Applications. You acknowledge that the App Store (and its subsidiaries) + are intended third-party beneficiaries of the Agreement and have the right to enforce them. + + + +

+

+ + + + TRANSACTIONS INVOLVING ALCOHOL + + + +

+

+ + + + You may have the option to request delivery of alcohol products in some locations and from certain Merchants. If you + receive your delivery in the Israel or United States, you agree that you will only order alcohol products if you are + 21 years of age or older. If you receive your delivery in another country, you agree that you will only order alcohol + products if you are of legal age to purchase alcohol products in the relevant jurisdiction. You also agree that, upon + delivery of alcohol products, you will provide valid government-issued identification proving your age to the Carrier + delivering the alcohol products and that the recipient will not be intoxicated when receiving delivery of such products. + If you order alcohol products, you understand and acknowledge that neither the EVER nor the Carrier can accept your + order of alcohol products, and the order will only be delivered if the Merchant accepts your order. The Carrier reserves + the right to refuse delivery if you are not 21 years of older, if you cannot provide a valid government issued ID, + if the name on your ID does not match the name on your order, or you are visibly intoxicated. If the Carrier is unable + to complete the delivery of alcohol products for one or more of these reasons, you are subject to a full refund. + + + +

+

+ + + + MODIFICATIONS + + + +

+

+ + + + EVER CO. LTD shall reserve the right at any time it may deem fit, to modify, alter and or discontinue, whether temporarily + or permanently, our service, or any part thereof, with or without prior notice. In addition, we shall not be held + liable to you or to any third party for any such alteration, modification, suspension and/or discontinuance of our + Services, or any part thereof.  + + + +

+

+ + + + TERMINATION + + + +

+

+ + + + We may terminate or suspend access to our Service immediately, without prior notice or liability, for any reason whatsoever, + including without limitation if you breach the Terms. + + + +

+

+ + + + As a member of Ever.co or Mobile Apps, you may cancel or terminate your account, associated email address and/or access + to our Services by submitting a cancellation or termination request to ever@ever.co. + + + +

+

+ + + + As a member, you agree that EVER CO. LTD may, without any prior written notice, immediately suspend, terminate, discontinue + and/or limit your account, any email associated with your account, and access to any of our Services. + + + +

+

+ + + + The cause for such termination, discontinuance, suspension and/or limitation of access shall include, but is not limited + to: + + + +

+

+ + + + a) any breach or violation of our TOS or any other incorporated agreement, regulation and/or guideline; + + + +

+

+ + + + b) by way of requests from law enforcement or any other governmental agencies; + + + + +

+

+ + + + c) the discontinuance, alteration and/or material modification to our Services, or any part thereof; + + + +

+

+ + + + d) unexpected technical or security issues and/or problems; + + + +

+

+ + + + e) any extended periods of inactivity; + + + +

+

+ + + + f) any engagement by you in any fraudulent or illegal activities; and/or + + + +

+

+ + + + g) the nonpayment of any associated fees that may be owed by you in connection with your Ever.co and Apps account Services. + + + +

+

+ + + + Furthermore, you herein agree that any and all terminations, suspensions, discontinuances, and or limitations of access + for cause shall be made at our sole discretion and that we shall not be liable to you or any other third party with + regards to the termination of your account, associated email address and/or access to any of our Services. + + + +

+

+ + + + The termination of your account with Ever.co and Apps shall include any and/or all of the following: + + + +

+

+ + + + a) the removal of any access to all or part of the Services offered within Ever.co and Apps; + + + +

+

+ + + + b) the deletion of your password and any and all related information, files, and any such content that may be associated + with or inside your account, or any part thereof; and + + + +

+

+ + + + c) the barring of any further use of all or part of our Services. + + + +

+

+ + + + All provisions of the Terms which by their nature should survive termination shall survive termination, including, + without limitation, ownership provisions, warranty disclaimers, indemnity and limitations of liability. + + + +

+

+ + + + ADVERTISER + + + +

+

+ + + + Any correspondence or business dealings with, or the participation in any promotions of, advertisers located on or + through our Services, which may include the payment and/or delivery of such related goods and/or Services, and any + such other term, condition, warranty and/or representation associated with such dealings, are and shall be solely + between you and any such advertiser. Moreover, you herein agree that EVER CO. LTD shall not be held responsible or + liable for any loss or damage of any nature or manner incurred as a direct result of any such dealings or as a result + of the presence of such advertisers on our website. + + + +

+

+ + + + LINKS + + + +

+

+ + + + Either EVER CO. LTD or any third parties may provide links to other websites and/or resources. Thus, you acknowledge + and agree that we are not responsible for the availability of any such external sites or resources, and as such, we + do not endorse nor are we responsible or liable for any content, products, advertising or any other materials, on + or available from such third party sites or resources. Furthermore, you acknowledge and agree that EVER CO. LTD shall + not be responsible or liable, directly or indirectly, for any such damage or loss which may be a result of, caused + or allegedly to be caused by or in connection with the use of or the reliance on any such content, goods or Services + made available on or through any such site or resource. + + + + +

+

+ + + + PROPRIETARY RIGHTS + + + + +

+

+ + + + You do hereby acknowledge and agree that EVER CO. LTD's Services and any essential software that may be used in connection + with our Services ("Software") shall contain proprietary and confidential material that is protected by + applicable intellectual property rights and other laws. Furthermore, you herein acknowledge and agree that any Content + which may be contained in any advertisements or information presented by and through our Services or by advertisers + is protected by copyrights, trademarks, patents or other proprietary rights and laws. Therefore, except for that which + is expressly permitted by applicable law or as authorized by EVER CO. LTD or such applicable licensor, you agree not + to alter, modify, lease, rent, loan, sell, distribute, transmit, broadcast, publicly perform and/or created any plagiaristic + works which are based on EVER CO. LTD Services (e.g. Content or Software), in whole or part. + + + +

+

+ + + + EVER CO. LTD herein has granted you personal, non-transferable and non-exclusive rights and/or license to make use + of the object code or our Software on a single computer or mobile device, as long as you do not, and shall not, allow + any third party to duplicate, alter, modify, create or plagiarize work from, reverse engineer, reverse assemble or + otherwise make an attempt to locate or discern any source code, sell, assign, sublicense, grant a security interest + in and/or otherwise transfer any such right in the Software. Furthermore, you do herein agree not to alter or change + the Software in any manner, nature or form, and as such, not to use any modified versions of the Software, including + and without limitation, for the purpose of obtaining unauthorized access to our Services. Lastly, you also agree not + to access or attempt to access our Services through any means other than through the interface which is provided by + EVER CO. LTD for use in accessing our Services. + + + +

+

+ + + + WARRANTY DISCLAIMERS + + + + +

+

+ + + + YOU HEREIN EXPRESSLY ACKNOWLEDGE AND AGREE THAT: + + + +

+

+ + + + a) THE USE OF EVER CO. LTD SERVICES AND SOFTWARE ARE AT THE SOLE RISK BY YOU. OUR SERVICES AND SOFTWARE SHALL BE PROVIDED + ON AN "AS IS" AND/OR "AS AVAILABLE" BASIS. EVER CO. LTD AND OUR SUBSIDIARIES, AFFILIATES, OFFICERS, + EMPLOYEES, AGENTS, PARTNERS AND LICENSORS EXPRESSLY DISCLAIM ANY AND ALL WARRANTIES OF ANY KIND WHETHER EXPRESSED + OR IMPLIED, INCLUDING, BUT NOT LIMITED TO ANY IMPLIED WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR + PURPOSE AND NON-INFRINGEMENT. + + + +

+

+ + + + b) EVER CO. LTD AND OUR SUBSIDIARIES, OFFICERS, EMPLOYEES, AGENTS, PARTNERS AND LICENSORS MAKE NO SUCH WARRANTIES THAT + (i) EVER CO. LTD SERVICES OR SOFTWARE WILL MEET YOUR REQUIREMENTS; (ii) EVER CO. LTD SERVICES OR SOFTWARE SHALL BE + UNINTERRUPTED, TIMELY, SECURE OR ERROR-FREE; (iii) THAT SUCH RESULTS WHICH MAY BE OBTAINED FROM THE USE OF THE EVER + CO. LTD SERVICES OR SOFTWARE WILL BE ACCURATE OR RELIABLE; (iv) QUALITY OF ANY PRODUCTS, SERVICES, ANY INFORMATION + OR OTHER MATERIAL WHICH MAY BE PURCHASED OR OBTAINED BY YOU THROUGH OUR SERVICES OR SOFTWARE WILL MEET YOUR EXPECTATIONS; + AND (v) THAT ANY SUCH ERRORS CONTAINED IN THE SOFTWARE SHALL BE CORRECTED. + + + +

+

+ + + + c) ANY INFORMATION OR MATERIAL DOWNLOADED OR OTHERWISE OBTAINED BY WAY OF EVER CO. LTD SERVICES OR SOFTWARE SHALL BE + ACCESSED BY YOUR SOLE DISCRETION AND SOLE RISK, AND AS SUCH YOU SHALL BE SOLELY RESPONSIBLE FOR AND HEREBY WAIVE ANY + AND ALL CLAIMS AND CAUSES OF ACTION WITH RESPECT TO ANY DAMAGE TO YOUR COMPUTER AND/OR INTERNET ACCESS, DOWNLOADING + AND/OR DISPLAYING, OR FOR ANY LOSS OF DATA THAT COULD RESULT FROM THE DOWNLOAD OF ANY SUCH INFORMATION OR MATERIAL. + + + +

+

+ + + + d) NO ADVICE AND/OR INFORMATION, DESPITE WHETHER WRITTEN OR ORAL, THAT MAY BE OBTAINED BY YOU FROM EVER CO. LTD OR + BY WAY OF OR FROM OUR SERVICES OR SOFTWARE SHALL CREATE ANY WARRANTY NOT EXPRESSLY STATED IN THE TOS. + + + +

+

+ + + + e) A SMALL PERCENTAGE OF SOME USERS MAY EXPERIENCE SOME DEGREE OF EPILEPTIC SEIZURE WHEN EXPOSED TO CERTAIN LIGHT PATTERNS + OR BACKGROUNDS THAT MAY BE CONTAINED ON A COMPUTER OR MOBILE DEVICE SCREEN OR WHILE USING OUR SERVICES. CERTAIN CONDITIONS + MAY INDUCE A PREVIOUSLY UNKNOWN CONDITION OR UNDETECTED EPILEPTIC SYMPTOM IN USERS WHO HAVE SHOWN NO HISTORY OF ANY + PRIOR SEIZURE OR EPILEPSY. SHOULD YOU, ANYONE YOU KNOW OR ANYONE IN YOUR FAMILY HAVE AN EPILEPTIC CONDITION, PLEASE + CONSULT A PHYSICIAN IF YOU EXPERIENCE ANY OF THE FOLLOWING SYMPTOMS WHILE USING OUR SERVICES: DIZZINESS, ALTERED VISION, + EYE OR MUSCLE TWITCHES, LOSS OF AWARENESS, DISORIENTATION, ANY INVOLUNTARY MOVEMENT, OR CONVULSIONS. + + + +

+

+ + + + LIMITATION OF LIABILITY + + + +

+

+ + + + YOU EXPLICITLY ACKNOWLEDGE, UNDERSTAND AND AGREE THAT EVER CO. LTD AND OUR SUBSIDIARIES, AFFILIATES, OFFICERS, EMPLOYEES, + AGENTS, PARTNERS AND LICENSORS SHALL NOT BE LIABLE TO YOU FOR ANY PUNITIVE, INDIRECT, INCIDENTAL, SPECIAL, CONSEQUENTIAL + OR EXEMPLARY DAMAGES, INCLUDING, BUT NOT LIMITED TO, DAMAGES WHICH MAY BE RELATED TO THE LOSS OF ANY PROFITS, GOODWILL, + USE, DATA AND/OR OTHER INTANGIBLE LOSSES, EVEN THOUGH WE MAY HAVE BEEN ADVISED OF SUCH POSSIBILITY THAT SAID DAMAGES + MAY OCCUR, AND RESULT FROM: + + + +

+

+ + + + a) THE USE OR INABILITY TO USE OUR SERVICE; + + + +

+

+ + + + b) THE COST OF PROCURING SUBSTITUTE GOODS AND SERVICES; + + + +

+

+ + + + c) UNAUTHORIZED ACCESS TO OR THE ALTERATION OF YOUR TRANSMISSIONS AND/OR DATA; + + + +

+

+ + + + d) STATEMENTS OR CONDUCT OF ANY SUCH THIRD PARTY ON OUR SERVICE; + + + +

+

+ + + + e) AND ANY OTHER MATTER WHICH MAY BE RELATED TO OUR SERVICE. + + + +

+

+ + + + RELEASE + + + +

+

+ + + + In the event you have a dispute, you agree to release EVER CO. LTD (and its officers, directors, employees, agents, + parent subsidiaries, affiliates, co-branders, partners and any other third parties) from claims, demands and damages + (actual and consequential) of every kind and nature, known and unknown, suspected or unsuspected, disclosed and undisclosed, + arising out of or in any way connected to such dispute. + + + + +

+

+ + + + SPECIAL ADMONITION RELATED TO FINANCIAL MATTERS + + + +

+

+ + + + Should you intend to create or to join any service, receive or request any such news, messages, alerts or other information + from our Services concerning companies, stock quotes, investments or securities, please review the above Sections + Warranty Disclaimers and Limitations of Liability again. In addition, for this particular type of information, the + phrase "Let the investor beware" is appropriate. EVER CO. LTD's content is provided primarily for informational + purposes, and no content that shall be provided or included in our Services is intended for trading or investing purposes. + EVER CO. LTD and our licensors shall not be responsible or liable for the accuracy, usefulness or availability of + any information transmitted and/or made available by way of our Services, and shall not be responsible or liable for + any trading and/or investment decisions based on any such information. + + + +

+

+ + + + EXCLUSION AND LIMITATIONS + + + +

+

+ + + + THERE ARE SOME JURISDICTIONS WHICH DO NOT ALLOW THE EXCLUSION OF CERTAIN WARRANTIES OR THE LIMITATION OF EXCLUSION + OF LIABILITY FOR INCIDENTAL OR CONSEQUENTIAL DAMAGES. THEREFORE, SOME OF THE ABOVE LIMITATIONS OF SECTIONS WARRANTY + DISCLAIMERS AND LIMITATION OF LIABILITY MAY NOT APPLY TO YOU. + + + +

+

+ + + + THIRD PARTY BENEFICIARIES + + + +

+

+ + + + You herein acknowledge, understand and agree, unless otherwise expressly provided in this TOS, that there shall be + no third-party beneficiaries to this agreement. + + + +

+

+ + + + NOTICE + + + +

+

+ + + + EVER CO. LTD may furnish you with notices, including those with regards to any changes to the TOS, including but not + limited to email, regular mail, MMS or SMS, text messaging, postings on our website Services, or other reasonable + means currently known or any which may be herein after developed. Any such notices may not be received if you violate + any aspects of the TOS by accessing our Services in an unauthorized manner. Your acceptance of this TOS constitutes + your agreement that you are deemed to have received any and all notices that would have been delivered had you accessed + our Services in an authorized manner. + + + +

+

+ + + + You may receive text messages (SMS / MMS / Push Notifications) from or on behalf of EVER as a part of the EVER Services + at the cell phone number(s) provided by you to EVER, and you consent to receiving such text messages. + + + +

+

+ + + + TRADEMARK INFORMATION + + + + +

+

+ + + + You herein acknowledge, understand and agree that all of the EVER CO. LTD trademarks, copyright, trade name, service + marks, and other EVER CO. LTD logos and any brand features, and/or product and service names are trademarks and as + such, are and shall remain the property of EVER CO. LTD. You herein agree not to display and/or use in any manner + the EVER CO. LTD logo or marks without obtaining EVER CO. LTD's prior written consent. + + + +

+

+ + + + COPYRIGHT OR INTELLECTUAL PROPERTY INFRINGEMENT CLAIMS NOTICE & PROCEDURES + + + +

+

+ + + + EVER CO. LTD will always respect the intellectual property of others, and we ask that all of our users do the same. + With regards to appropriate circumstances and at its sole discretion, EVER CO. LTD may disable and/or terminate the + accounts of any user who violates our TOS and/or infringes the rights of others. If you feel that your work has been + duplicated in such a way that would constitute copyright infringement, or if you believe your intellectual property + rights have been otherwise violated, you should provide to us the following information: + + + + +

+

+ + + + a) The electronic or the physical signature of the individual that is authorized on behalf of the owner of the copyright + or other intellectual property interest; + + + +

+

+ + + + b) A description of the copyrighted work or other intellectual property that you believe has been infringed upon; + + + +

+

+ + + + c) A description of the location of the site which you allege has been infringing upon your work; + + + +

+

+ + + + d) Your physical address, telephone number, and email address; + + + +

+

+ + + + e) A statement, in which you state that the alleged and disputed use of your work is not authorized by the copyright + owner, its agents or the law; + + + + +

+

+ + + + f) And finally, a statement, made under penalty of perjury, that the aforementioned information in your notice is truthful + and accurate, and that you are the copyright or intellectual property owner, representative or agent authorized to + act on the copyright or intellectual property owner's behalf. + + + +

+

+ + + + The EVER CO. LTD Agent for notice of claims of copyright or other intellectual property infringement can be contacted + as follows: + + + +

+

+ + + + Mailing Address: + + + + +

+

+ + + + EVER CO. LTD + + + +

+

+ + + + Attn: Copyright Agent + + + +

+

+ + + + HaAtsmaut 38/3 + + + + +

+

+ + + + Ashdod 77452, + + + + +

+

+ + + + Israel + + + +

+

+ + + + Email: ever@ever.co + + + + +

+

+ + + + CLOSED CAPTIONING + + + + +

+

+ + + + BE IT KNOWN, that EVER CO. LTD complies with all applicable Federal Communications Commission rules and regulations + regarding the closed captioning of video content. For more information, please visit our website at ever.co. + + + +

+

+ + + + GENERAL INFORMATION + + + + +

+

+ + + + ENTIRE AGREEMENT + + + +

+

+ + + + This TOS constitutes the entire agreement between you and EVER CO. LTD and shall govern the use of our Services, superseding + any prior version of this TOS between you and us with respect to EVER CO. LTD Services. You may also be subject to + additional terms and conditions that may apply when you use or purchase certain other EVER CO. LTD Services, affiliate + Services, third-party content or third-party software. + + + +

+

+ + + + CHOICE OF LAW AND FORUM + + + +

+

+ + + + It is at the mutual agreement of both you and EVER CO. LTD with regard to the TOS that the relationship between the + parties shall be governed by the laws of Israel without regard to its conflict of law provisions and that any and + all claims, causes of action and/or disputes, arising out of or relating to the TOS, or the relationship between you + and EVER CO. LTD, shall be filed within the courts having jurisdiction within the Israel. You and EVER CO. LTD agree + to submit to the jurisdiction of the courts as previously mentioned, and agree to waive any and all objections to + the exercise of jurisdiction over the parties by such courts and to venue in such courts. + + + +

+

+ + + + WAIVER AND SEVERABILITY OF TERMS + + + +

+

+ + + + At any time, should EVER CO. LTD fail to exercise or enforce any right or provision of the TOS, such failure shall + not constitute a waiver of such right or provision. If any provision of this TOS is found by a court of competent + jurisdiction to be invalid, the parties nevertheless agree that the court should endeavor to give effect to the parties' + intentions as reflected in the provision, and the other provisions of the TOS remain in full force and effect. + + + +

+

+ + + + NO RIGHT OF SURVIVORSHIP NON-TRANSFERABILITY + + + +

+

+ + + + You acknowledge, understand and agree that your account is non-transferable and any rights to your ID and/or contents + within your account shall terminate upon your death. Upon receipt of a copy of a death certificate, your account may + be terminated and all contents therein permanently deleted. + + + +

+

+ + + + STATUTE OF LIMITATIONS + + + +

+

+ + + + You acknowledge, understand and agree that regardless of any statute or law to the contrary, any claim or action arising + out of or related to the use of our Services or the TOS must be filed within 1 year(s) after said claim or cause of + action arose or shall be forever barred. + + + +

+

+ + + + VIOLATIONS + + + +

+

+ + + + Please report any and all violations of this TOS to EVER CO. LTD as follows: + + + +

+

+ + + + Mailing Address: + + + + +

+

+ + + + EVER CO. LTD + + + +

+

+ + + + HaAtsmaut 38/3 + + + + +

+

+ + + + Ashdod 77452, + + + + +

+

+ + + + Israel + + + +

+

+ + + + Email: ever@ever.co + + + + +

+

+ + + + CHANGES + + + +

+

+ + + + We reserve the right, at our sole discretion, to modify or replace these Terms at any time. If a revision is material + we will try to provide at least 30 (change this) days' notice prior to any new terms taking effect. + + + +

+

+ + + + What constitutes a material change will be determined at our sole discretion. + + + + +

+

+ + + + By continuing to access or use our Service after those revisions become effective, you agree to be bound by the revised + terms. + + + +

+

+ + + + If you do not agree to the new terms, please stop using the Service + + + +

+

+ + + + CONTACT US + + + + +

+

+ + + + If you have any questions about these Terms, please contact us: + + + +

+

+ + + + Mailing Address: + + + + +

+

+ + + + EVER CO. LTD + + + +

+

+ + + + HaAtsmaut 38/3 + + + + +

+

+ + + + Ashdod 77452, + + + + +

+

+ + + + Israel + + + +

+

+ + + + Email: ever@ever.co + + + + +

+

+
+
+

+

+ + + + DIGITAL MILLENNIUM COPYRIGHT ACT (DMCA) INFRINGEMENT NOTICE AND POLICY + + + +

+

+ + + + Notifications + + + +

+

+ + + + If you believe that content available on or through the EVER Apps and / or Website infringes one or more of your copyrights, + please immediately notify our Copyright Agent by mail, email or faxed notice (“Notification”) providing the information + described below, which Notification is pursuant to DMCA 17 U.S.C. § 512(c)(3). A copy of your Notification will be + sent to the person who posted or stored the material addressed in the Notification. Please be advised that pursuant + to federal law you may be held liable for damages if you make material misrepresentations in a Notification. Thus, + if you are not sure that content located on or linked to by the Apps and / or Website infringes your copyright, you + should consider first contacting an attorney. Company has a policy of terminating repeat infringers in appropriate + circumstances. + + + +

+

+ + + + All Notifications should include the following: + + + +

+

+ + + + A physical or electronic signature of a person authorized to act on behalf of the owner of an exclusive right that + is allegedly infringed. + + + + +

+

+ + + + Identification of the copyrighted work claimed to have been infringed, or, if multiple copyrighted works at a single + online website are covered by a single notification, a representative list of such works at that website. + + + + +

+

+ + + + Identification of the material that is claimed to be infringing or to be the subject of infringing activity and that + is to be removed or access to which is to be disabled, and information reasonably sufficient to permit Company to + locate the material. + + + +

+

+ + + + Information reasonably sufficient to permit the Company to contact the complaining party, such as an address, telephone + number, and, if available, an electronic mail address at which the complaining party may be contacted. + + + +

+

+ + + + A statement that the complaining party has a good faith belief that use of the material in the manner complained of + is not authorized by the copyright owner, its agent, or the law. + + + +

+

+ + + + A statement that the information in the notification is accurate, and under penalty of perjury, that the complaining + party is authorized to act on behalf of the owner of an exclusive right that is allegedly infringed. + + + + +

+

+ + + + Notifications should be sent to our Copyright Agent as follows: + + + +

+

+ + + + Copyright Agent + + + + +

+

+ + + + EVER CO. LTD + + + +

+

+ + + + HaAtsmaut 38/3 + + + + +

+

+ + + + Ashdod 77452, + + + + +

+

+ + + + Israel + + + +

+

+ + + + Email: + + + + + + + + dmca@ever.co + + + +

+

+
+
+

+ +
+ +
\ No newline at end of file diff --git a/packages/core/res/templates/terms_of_use/en-US.hbs b/packages/core/res/templates/terms_of_use/en-US.hbs new file mode 100644 index 0000000..559da67 --- /dev/null +++ b/packages/core/res/templates/terms_of_use/en-US.hbs @@ -0,0 +1,1242 @@ + + +
+ +

Terms of Service

+ +
+ +

Thank +you for your interest in the Ever application for your mobile device +(the "App") provided to you by EVER CO. LTD ("EVER" +"us" or "we"), and our web site at Ever.co (the +"Site"), as well as all related web sites, networks, +downloadable software, and other services provided by us and on which +a link to this Terms of Service is displayed (collectively, together +with the Apps and Site, our "Service").

+

PLEASE +READ THE FOLLOWING TERMS OF SERVICE AGREEMENT CAREFULLY. +

+

These +Terms of Service (these "Terms", “Agreement”, “Terms +of Service”, "TOS"), including the Privacy Policy +incorporated into these Terms by reference and any other applicable +policies and guidelines, as may be updated from time to time, govern +your use of the Service. These Terms constitute a legal agreement +between you and EVER. In order to use the Service you must agree to +these Terms. +

+

BY +DOWNLOADING, INSTALLING, OR OTHERWISE BY ACCESSING OR USING OUR +SITES, MOBILE APPLICATIONS AND OUR SERVICES, YOU HEREBY AGREE THAT +YOU HAVE READ, UNDERSTOOD, AND AGREE TO BE TO BE BOUND BY THE TERMS +AND ALL TERMS INCORPORATED HEREIN BY REFERENCE. +

+

IT +IS THE RESPONSIBILITY OF YOU, THE USER, CUSTOMER, OR PROSPECTIVE +CUSTOMER TO READ THE TERMS AND CONDITIONS BEFORE PROCEEDING TO USE +THIS SITE OR OUR MOBILE APPS.

+

IF +YOU DO NOT EXPRESSLY AGREE TO ALL OF THE TERMS AND CONDITIONS, THEN +PLEASE DO NOT ACCESS OR USE OUR SITES, MOBILE APPS OR OUR SERVICES.

+

THIS +TERMS OF SERVICE AGREEMENT IS EFFECTIVE AS OF 11/06/2016.

+

ACCEPTANCE +OF TERMS

+

The +following Terms of Service Agreement is a legally binding agreement +that shall govern the relationship with our users and others which +may interact or interface with EVER CO. LTD, also known as EVER, +located at HaAtsmaut 38/3, Ashdod 77452, Israel and our subsidiaries +and affiliates, in association with the use of the EVER website and +mobile apps, which includes Ever.co, (the "Site") and its +Services, EVER Applications (the “Apps”) and its Services. 

+

Your +use of the EVER Services may be subject to separate third party terms +of service and fees, including without limitation your mobile network +operator’s terms of service and fees, including fees charged for +data usage, messaging and overage, which are your sole +responsibility.

+

DESCRIPTION +OF SERVICES OFFERED

+

EVER +provides a mobile apps and web-based technology platform (the +"Platform", "Marketplace") that connects +consumers (the "Customers"), retail stores, and restaurants +(together referenced as "Merchants"), with third party +independent contractors and third party independent contractors under +agreement with EVER and certain of EVER's affiliates (together +referenced as "Couriers"). The Platform provides order +placement services for Customers and allows them to view, discuss, +and place orders for food, beverages and goods, and pick-up and / or +delivery services in connection therewith, with Merchants and +Restaurants. The platform allows Carriers to facilitate on-demand +same day delivery services for food, merchandise, goods and +beverages. +

+

Through +the Platform consumers may request that food, merchandise, goods or +beverages, be delivered to them from particular retail locations, +stores or restaurants (Merchants). Couriers can access the Platform +and receive delivery opportunities. +

+

EVER +IS NOT A RETAIL STORE, MERCHANT OF FOOD OR BEVERAGES, RESTAURANT, +FOOD PICKUP AND / OR DELIVERY SERVICE, MERCHANDISE DELIVERY SERVICE, +OR FOOD PREPARATION ENTITY. YOU ACKNOWLEDGE THAT EVER DOES NOT +PROVIDE TRANSPORTATION OR LOGISTICS SERVICES OR FUNCTION AS A +TRANSPORTATION CARRIER AND EVER DOES NOT PROVIDE DELIVERY SERVICES +AND DOES NOT CONTROL THE RESTAURANTS OR THE PRODUCTION OF ANY FOOD OR +BEVERAGES, OR ANY PICK-UP OR DELIVERY SERVICES THEREWITH. INDEPENDENT +CONTRACTORS (EACH A "COURIER") OFFER DELIVERY SERVICES +THROUGH USE OF THE SERVICE. EVER OFFERS INFORMATION AND A METHOD TO +OBTAIN COURIER SERVICES, BUT DOES NOT AND DOES NOT INTEND TO PROVIDE +COURIER SERVICES OR ACT IN ANY WAY AS A COURIER, AND HAS NO +RESPONSIBILITY OR LIABILITY FOR ANY COURIER.

+

The +Merchants available through our Services operate indepenently of the +EVER. The EVER will not assess the suitability, legality or ability +of any Carrier or Merchant. The EVER is not responsible for the +Merchants food preparation or safety and does not verify their +compliance with applicable laws or regulations. The EVER has no +responsibility or liability for acts by any third-party Merchant or +Carrier, other than as stated herein. EVER, including the Website, +Apps and the EVER Services, does not in any way independently verify +the credentials or representations of any of the Restaurants, the +ingredients or the quality of any their products or services, or any +Restaurant’s compliance with applicable laws.

+

Customers +using the EVER Services must make themselves comfortable through the +information provided by the Restaurants on the Platform, by +contacting the Restaurants directly, or through such other means or +methods as they may deem appropriate, as to the quality and +reliability and quality of the Restaurants and the Restaurants’ +compliance with applicable laws. The EVER, including the Website, +Apps and the EVER Services, does not in any way guarantee the quality +of any Restaurant or any food or beverage, or any pickup- up or +delivery service in connection therewith, or any compliance thereof +with applicable laws. In addition, a Restaurant may represent certain +standards with respect to their food preparation (or other services), +such as “organic,” “kosher,” “macrobiotic” or allergen- +specific standards such as “nut-free,” “gluten-free,” or +“lactose-free”; EVER does not investigate or verify any such +representations. EVER shall not be liable or responsible for any food +or beverages, or any other services, offered by the Restaurants or +any errors or misrepresentations made by them (including on the +Website, Apps and through the EVER Services).

+

Unless +otherwise agreed by EVER in a separate written agreement with you, +the Services are made available solely for your personal, +noncommercial use. +

+

As +provided in greater detail in these Terms, you agree and acknowledge +these material Terms:

+

- +The App is licensed, not sold to you, and you may use the Service +only as set forth in these Terms;

+

- +Your use of the Service may be subject to separate third party terms +of service and fees, including without limitation your mobile network +operator’s ("Carrier") terms of service and fees, +including fees charged for data usage and overage, which are your +sole responsibility;

+

- +You consent to the collection, sharing, and use of your personally +identifiable information in accordance with EVER Privacy Policy;

+

- +The Service is provided "as is" without warranties of any +kind, and EVER liability to you is limited; +

+

SCOPE +OF APPS LICENSE

+

Our +Apps are licensed, not sold, to you for use only under the terms of +this license. EVER reserves all rights not expressly granted to you. +Subject to your complete and ongoing compliance with these Terms, +EVER hereby grants you a personal, limited, revocable, +non-transferable license to use our Apps on compatible devices that +you own or control, solely for your non-commercial use.

+

You +may not modify, alter, reproduce, distribute or make the Apps +available over a network where it could be used by multiple devices +at the same time. You may not rent, lease, lend, sell, redistribute +or sublicense the Apps. If you breach these license restrictions, or +otherwise exceed the scope of the license granted in these Terms, you +may be subject to prosecution and legal damages, as well as liability +for infringement of intellectual property rights. These Terms will +govern any updates provided to you by EVER that replace and/or +supplement the original Apps, unless the upgrade is accompanied by a +separate license in which case the terms of that license will govern.

+

ACCOUNT +ACCESS

+

You +must be at least eighteen (18) years old to use the Service. By +agreeing to these Terms, you represent and warrant to us:

+

- +That you are at least eighteen (18) years old;

+

- +That you have not previously been suspended or removed from the +Service; and

+

- +That your registration and your use of the Service is in compliance +with any and all applicable laws and regulations.

+

If +you are using the Service on behalf of an entity, organization, or +company, you represent and warrant that you have the authority to +bind that organization to these Terms and you agree to be bound by +these Terms on behalf of that organization.

+

ACCOUNTS +AND REGISTRATION

+

Any +and all visitors to our site and users of our apps, despite whether +they are registered or not, shall be deemed as "users" of +the herein contained Services provided for the purpose of this TOS.

+

Once +an individual register's for our Services, through the process of +creating an account, the user shall then be considered a "member."

+

To +register and become a "member" of the Site or Apps, you +must be at least 18 years of age to enter into and form a legally +binding contract. In addition, you must be in good standing and not +an individual that has been previously barred from receiving EVER's +Services under the laws and statutes of the Israel, United States or +other applicable jurisdiction.

+

When +you register, EVER may collect information such as your first and +last name, e-mail address, birth date, gender, mailing address, +occupation, industry and personal interests. +

+

You +can edit your account information at any time. Once you register with +EVER and sign in to our Services, you are no longer anonymous to us.

+

Furthermore, +the registering party hereby acknowledges, understands and agrees to:

+

a) furnish +factual, correct, current and complete information with regards to +yourself as may be requested by the data registration process, and

+

b) maintain +and promptly update your registration and profile information in an +effort to maintain accuracy and completeness at all times.

+

If +anyone knowingly provides any information of a false, untrue, +inaccurate or incomplete nature, EVER CO. LTD will have sufficient +grounds and rights to suspend or terminate the member in violation of +this aspect of the Agreement, and as such refuse any and all current +or future use of EVER CO. LTD Services, or any portion thereof. +

+

The +user and/or member acknowledges and agrees that the Services provided +and made available through our website and applications, which may +include some mobile applications and that those applications may be +made available on various social media networking sites and numerous +other platforms and downloadable programs, are the sole property of +EVER CO. LTD. At its discretion, EVER CO. LTD may offer additional +website Services and/or products, or update, modify or revise any +current content and Services, and this Agreement shall apply to any +and all additional Services and/or products and any and all updated, +modified or revised Services unless otherwise stipulated. EVER CO. +LTD does hereby reserve the right to cancel and cease offering any of +the aforementioned Services and/or products. You, as the end user +and/or member, acknowledge, accept and agree that EVER CO. LTD shall +not be held liable for any such updates, modifications, revisions, +suspensions or discontinuance of any of our Services and/or products. +Your continued use of the Services provided, after such posting of +any updates, changes, and/or modifications shall constitute your +acceptance of such updates, changes and/or modifications, and as +such, frequent review of this Agreement and any and all applicable +terms and policies should be made by you to ensure you are aware of +all terms and policies currently in effect. Should you not agree to +the updated, revised or modified terms, you must stop using the +provided Services forthwith.

+

Furthermore, +the user and/or member understands, acknowledges and agrees that the +Services offered shall be provided "AS IS" and as such EVER +CO. LTD shall not assume any responsibility or obligation for the +timeliness, missed delivery, deletion and/or any failure to store +user content, communication or personalization settings. +

+

PURCHASES

+

If +you wish to purchase any product, food or service made available +through the Service ("Purchase"), you may be asked to +supply certain information relevant to your Purchase including, +without limitation, your first and last name, birth date, gender, +credit card information, physical and mailing addresses, e-mail +address and phone number or other contact information.

+

When +you place an order through the Apps, WebSite and EVER Services, you +will be given a choice of payment options, which may include via +Apple Pay, Credit Card, PayPal®, Android Pay, and direct payment to +the applicable Merchant or Restaurant. If you pay for your purchase +via Apple Pay, Credit Card, PayPal, or Android Pay, we will ask for a +valid Apple Pay account, Credit Card, PayPal account, or Android Pay +account, as applicable, which will be billed through the EVER +Services, for the purchase price of the applicable order, and “EVER +CO. LTD” or your name will be the name that appears on the Apple +Pay, Credit Card, PayPal, or Android Pay statement, as applicable. As +stated above, however, EVER, including the Apps, Website and the EVER +Services, is not and shall not in any manner be considered the seller +of any of the food, beverages, and services ordered.

+

PRICES

+

You +understand that: (a) the prices for food and goods displayed through +the Services may differ from the prices offered or published by +Merchants for the same menu items, food or goods and/or from prices +available at other third-party websites and that such prices may not +be the lowest prices at which the items are sold; (b) the Company has +no obligation to itemize its costs, profits or margins when +publishing such prices; and (c) the Company reserves the right to +change such prices at any time, at its discretion. You are liable for +all transaction taxes on the Services provided under this Agreement +(other than taxes based on the Company’s income). Payment will be +processed by the Company, using the preferred payment method +designated in your account.

+

GENERAL +PAYMENT TERMS

+

Certain +features of the Service, including the placing of orders using the +Service, may require you to pay fees. Before you pay any fees, you +will have an opportunity to review and accept an estimate of the fees +that you will be charged. All fees are non-refundable. This no refund +policy applies at all times regardless of your decision to terminate +your usage, our decision to terminate your usage and any disruption +caused to our Service for any reason whatsoever.

+

EVER, +at its sole discretion, may offer credits or refunds on a +case-by-case basis; all credit and/or refund requests must be made +within fifteen (15) days after the delivery was completed. EVER may +change the delivery or other fees for any feature of the Service, +including by adding fees, on a going-forward basis at any time.

+

EVER +will charge the payment method you specify at the time of purchase or +as otherwise specified by you in your account information. EVER +reserves the right to determine final prevailing pricing. (Please +note the pricing information published on the website may not reflect +the prevailing pricing.) EVER, at its sole discretion, may make +promotional offers with different features and different rates to any +of our customers. These promotional offers, unless made to you, shall +have no bearing whatsoever on your offer or contract. We may change +the fees for the Service at any time as we deem necessary for our +business. +

+

PAYMENT +AUTHORIZATION

+

You +authorize EVER to charge all sums for orders that you make and +services you select to the payment method specified in your account. +When you order on EVER, a temporary pre-authorization hold might be +placed on your payment card to verify that the card is valid and has +credit available for your intended purchase. Once your order is +complete, you will be charged the final order total and the +pre-authorization hold will be lifted within 24-72 business hours, +depending on your bank. The pre-authorization hold remains even if an +order is canceled by a customer or by EVER and may remain on your +card for up to 5 business days.

+

EVER +also may place an initial temporary pre-authorization hold on each +new payment method you add to your account.

+

PAYMENT +WHEN CUSTOMER NOT AVAILABLE

+

EVER +reserves the right to charge a customer the full order amount if that +customer is not at the designated delivery location when the courier +arrives to complete the delivery.

+

PRIVACY +POLICY

+

Every +member's registration data and various other personal information are +strictly protected by the EVER CO. LTD Online Privacy Policy (see the +full Privacy Policy at https://ever.co/privacy/site.html). +As a member, you herein consent to the collection and use of the +information provided, including the transfer of information within +the United States, Israel and/or other countries for storage, +processing or use by EVER CO. LTD and/or our subsidiaries and +affiliates. +

+

MEMBER +ACCOUNT, USERNAME, PASSWORD AND SECURITY

+

When +you register, you will be asked to provide a password. You are solely +responsible for maintaining the confidentiality of your account and +password, and any password for Facebook, Google, or other third party +login. You accept responsibility for all activities that occur under +your account. If you have reason to believe that your account is no +longer secure, you must immediately notify us by our sending email to +security@ever.co

+

When +you set up an account, you are the sole authorized user of your +account. You shall be responsible for maintaining the secrecy and +confidentiality of your password and for all activities that +transpire on or within your account. It is your responsibility for +any act or omission of any user(s) that access your account +information that, if undertaken by you, would be deemed a violation +of the TOS. It shall be your responsibility to notify EVER CO. LTD +immediately if you notice any unauthorized access or use of your +account or password or any other breach of security. EVER CO. LTD +shall not be held liable for any loss and/or damage arising from any +failure to comply with this term and/or condition of the TOS. +

+

CONDUCT

+

As +a user or member of the Site or Apps, you herein acknowledge, +understand and agree that all information, text, software, data, +photographs, music, video, messages, tags or any other content, +whether it is publicly or privately posted and/or transmitted, is the +expressed sole responsibility of the individual from whom the content +originated. In short, this means that you are solely responsible for +any and all content posted, uploaded, emailed, transmitted or +otherwise made available by way of the EVER Services, and as such, we +do not guarantee the accuracy, integrity or quality of such content. +It is expressly understood that by use of our Services, you may be +exposed to content including, but not limited to, any errors or +omissions in any content posted, and/or any loss or damage of any +kind incurred as a result of the use of any content posted, emailed, +transmitted or otherwise made available by EVER. +

+

Furthermore, +you herein agree not to make use of EVER CO. LTD's Services for the +purpose of:

+

a) uploading, +posting, emailing, transmitting, or otherwise making available any +content that shall be deemed unlawful, harmful, threatening, abusive, +harassing, tortious, defamatory, vulgar, obscene, libelous, or +invasive of another's privacy or which is hateful, and/or racially, +ethnically, or otherwise objectionable;

+

b) causing +harm to minors in any manner whatsoever;

+

c) impersonating +any individual or entity, including, but not limited to, any EVER +officials, forum leaders, guides or hosts or falsely stating or +otherwise misrepresenting any affiliation with an individual or +entity;

+

d) forging +captions, headings or titles or otherwise offering any content that +you personally have no right to pursuant to any law nor having any +contractual or fiduciary relationship with;

+

e) uploading, +posting, emailing, transmitting or otherwise offering any such +content that may infringe upon any patent, copyright, trademark, or +any other proprietary or intellectual rights of any other party;

+

f) uploading, +posting, emailing, transmitting or otherwise offering any content +that you do not personally have any right to offer pursuant to any +law or in accordance with any contractual or fiduciary relationship;

+

g) uploading, +posting, emailing, transmitting, or otherwise offering any +unsolicited or unauthorized advertising, promotional flyers, "junk +mail," "spam," or any other form of solicitation, +except in any such areas that may have been designated for such +purpose;

+

h) uploading, +posting, emailing, transmitting, or otherwise offering any source +that may contain a software virus or other computer code, any files +and/or programs which have been designed to interfere, destroy and/or +limit the operation of any computer software, hardware, or +telecommunication equipment;

+

i) disrupting +the normal flow of communication, or otherwise acting in any manner +that would negatively affect other users' ability to participate in +any real time interactions;

+

j) interfering +with or disrupting any EVER CO. LTD Services, servers and/or networks +that may be connected or related to our website, including, but not +limited to, the use of any device software and/or routine to bypass +the robot exclusion headers;

+

k) intentionally +or unintentionally violating any local, state, federal, national or +international law, including, but not limited to, rules, guidelines, +and/or regulations decreed by the U.S. Securities and Exchange +Commission, in addition to any rules of any nation or other +securities exchange, that would include without limitation, the New +York Stock Exchange, the American Stock Exchange, or the NASDAQ, and +any regulations having the force of law;

+

l) providing +informational support or resources, concealing and/or disguising the +character, location, and or source to any organization delegated by +the United States government as a "foreign terrorist +organization" in accordance to Section 219 of the Immigration +Nationality Act;

+

m) "stalking" +or with the intent to otherwise harass another individual; and/or +

+

n) collecting +or storing of any personal data relating to any other member or user +in connection with the prohibited conduct and/or activities which +have been set forth in the aforementioned paragraphs.

+

EVER +CO. LTD herein reserves the right to pre-screen, refuse and/or delete +any content currently available through our Services. In addition, we +reserve the right to remove and/or delete any such content that would +violate the TOS or which would otherwise be considered offensive to +other visitors, users and/or members.  

+

EVER +CO. LTD herein reserves the right to access, preserve and/or disclose +member account information and/or content if it is requested to do so +by law or in good faith belief that any such action is deemed +reasonably necessary for: 

+

a) compliance +with any legal process; 

+

b) enforcement +of the TOS; 

+

c) responding +to any claim that therein contained content is in violation of the +rights of any third party; 

+

d) responding +to requests for customer service; or 

+

e) protecting +the rights, property or the personal safety of EVER CO. LTD, its +visitors, users and members, including the general public.

+

EVER +CO. LTD herein reserves the right to include the use of security +components that may permit digital information or material to be +protected, and that such use of information and/or material is +subject to usage guidelines and regulations established by EVER CO. +LTD or any other content providers supplying content services to EVER +CO. LTD. You are hereby prohibited from making any attempt to +override or circumvent any of the embedded usage rules in our +Services. Furthermore, unauthorized reproduction, publication, +distribution, or exhibition of any information or materials supplied +by our Services, despite whether done so in whole or in part, is +expressly prohibited.

+

INTERSTATE +COMMUNICATION

+

Upon +registration, you hereby acknowledge that by using Site and Apps to +send electronic communications, which would include, but are not +limited to, email, searches, instant messages, uploading of files, +photos and/or videos, you will be causing communications to be sent +through our computer network. Therefore, through your use, and thus +your agreement with this TOS, you are acknowledging that the use of +this Service shall result in interstate transmissions.

+

CAUTIONS +FOR GLOBAL USE AND EXPORT AND IMPORT COMPLIANCE

+

Due +to the global nature of the internet, through the use of our network +you hereby agree to comply with all local rules relating to online +conduct and that which is considered acceptable Content. Uploading, +posting and/or transferring of software, technology and other +technical data may be subject to the export and import laws of the +United States and possibly other countries. Through the use of our +network, you thus agree to comply with all applicable export and +import laws, statutes and regulations, including, but not limited to, +the Export Administration Regulations +(http://www.access.gpo.gov/bis/ear/ear_data.html), +as well as the sanctions control program of the United States +(http://www.treasury.gov/resource-center/sanctions/Programs/Pages/Programs.aspx). +Furthermore, you state and pledge that you:

+

a) are +not on the list of prohibited individuals which may be identified on +any government export exclusion report +(http://www.bis.doc.gov/complianceandenforcement/liststocheck.htm) +nor a member of any other government which may be part of an +export-prohibited country identified in applicable export and import +laws and regulations;

+

b) agree +not to transfer any software, technology or any other technical data +through the use of our network Services to any export-prohibited +country; +

+

c) agree +not to use our website network Services for any military, nuclear, +missile, chemical or biological weaponry end uses that would be a +violation of the Israel, U.S. export laws; and

+

d) agree +not to post, transfer nor upload any software, technology or any +other technical data which would be in violation of the Israel, U.S. +or other applicable export and/or import laws.

+

CONTENT +PLACED OR MADE AVAILABLE FOR COMPANY SERVICES

+

EVER +CO. LTD shall not lay claim to ownership of any content submitted by +any visitor, member, or user, nor make such content available for +inclusion on our website Services. Therefore, you hereby grant and +allow for EVER CO. LTD the below listed worldwide, royalty-free and +non-exclusive licenses, as applicable:

+

a) The +content submitted or made available for inclusion on the publicly +accessible areas of EVER CO. LTD's sites, the license provided to +permit to use, distribute, reproduce, modify, adapt, publicly perform +and/or publicly display said Content on our network Services is for +the sole purpose of providing and promoting the specific area to +which this content was placed and/or made available for viewing. This +license shall be available so long as you are a member of EVER CO. +LTD's sites, and shall terminate at such time when you elect to +discontinue your membership.

+

b) Photos, +audio, video and/or graphics submitted or made available for +inclusion on the publicly accessible areas of EVER CO. LTD's sites, +the license provided to permit to use, distribute, reproduce, modify, +adapt, publicly perform and/or publicly display said Content on our +network Services are for the sole purpose of providing and promoting +the specific area in which this content was placed and/or made +available for viewing. This license shall be available so long as you +are a member of EVER CO. LTD's sites and shall terminate at such time +when you elect to discontinue your membership.

+

c) For +any other content submitted or made available for inclusion on the +publicly accessible areas of EVER CO. LTD's sites, the continuous, +binding and completely sub-licensable license which is meant to +permit to use, distribute, reproduce, modify, adapt, publish, +translate, publicly perform and/or publicly display said content, +whether in whole or in part, and the incorporation of any such +Content into other works in any arrangement or medium current used or +later developed.

+

Those +areas which may be deemed "publicly accessible" areas of +EVER CO. LTD's sites are those such areas of our network properties +which are meant to be available to the general public, and which +would include message boards and groups that are openly available to +both users and members. However, those areas which are not open to +the public, and thus available to members only, would include our +mail system and instant messaging.

+

CONTRIBUTIONS +TO COMPANY WEBSITE

+

EVER +CO. LTD provides an area for our users and members to contribute +feedback to our website. When you submit ideas, documents, +suggestions and/or proposals ("Contributions") to our site, +you acknowledge and agree that: +

+

a) your +contributions do not contain any type of confidential or proprietary +information;

+

b) EVER +shall not be liable or under any obligation to ensure or maintain +confidentiality, expressed or implied, related to any Contributions; +

+

c) EVER +shall be entitled to make use of and/or disclose any such +Contributions in any such manner as they may see fit; +

+

d) the +contributor's Contributions shall automatically become the sole +property of EVER; and

+

e) EVER +is under no obligation to either compensate or provide any form of +reimbursement in any manner or nature.

+

INDEMNITY

+

All +users and/or members herein agree to insure and hold EVER CO. LTD, +our subsidiaries, affiliates, agents, employees, officers, partners +and/or licensors blameless or not liable for any claim or demand, +which may include, but is not limited to, reasonable attorney fees +made by any third party which may arise from any content a member or +user of our site may submit, post, modify, transmit or otherwise make +available through our Services, the use of EVER Services or your +connection with these Services, your violations of the Terms of +Service and/or your violation of any such rights of another person.

+

COMMERCIAL +REUSE OF SERVICES

+

The +member or user herein agrees not to replicate, duplicate, copy, +trade, sell, resell nor exploit for any commercial reason any part, +use of, or access to EVER's sites or Apps.

+

USE +AND STORAGE GENERAL PRACTICES

+

You +herein acknowledge that EVER CO. LTD may set up any such practices +and/or limits regarding the use of our Services, without limitation +of the maximum number of days that any email, message posting or any +other uploaded content shall be retained by EVER CO. LTD, nor the +maximum number of email messages that may be sent and/or received by +any member, the maximum volume or size of any email message that may +be sent from or may be received by an account on our Service, the +maximum disk space allowable that shall be allocated on EVER CO. +LTD's servers on the member's behalf, and/or the maximum number of +times and/or duration that any member may access our Services in a +given period of time. In addition, you also agree that EVER CO. LTD +has absolutely no responsibility or liability for the removal or +failure to maintain storage of any messages and/or other +communications or content maintained or transmitted by our Services. +You also herein acknowledge that we reserve the right to delete or +remove any account that is no longer active for an extended period of +time. Furthermore, EVER CO. LTD shall reserve the right to modify, +alter and/or update these general practices and limits at our +discretion.

+

Any +messenger service, which may include any web-based versions, shall +allow you and the individuals with whom you communicate with the +ability to save your conversations in your account located on EVER +CO. LTD's servers. In this manner, you will be able to access and +search your message history from any computer with internet access. +You also acknowledge that others have the option to use and save +conversations with you in their own personal account on Ever.co. It +is your agreement to this TOS which establishes your consent to allow +EVER CO. LTD to store any and all communications on its servers.

+

THIRD-PARTY +INTERACTIONS +

+

1. +Third-Party Providers

+

During +use of the Service, you may purchase goods and services from +third-party merchants through the Service. Any such activity, and any +disputes, terms, conditions, warranties or representations associated +with that activity, is solely between you and the applicable third +party. EVER and its licensors shall have no liability, obligation or +responsibility for any purchase or transaction between you and any +third-party provider. In no event shall EVER or its licensors be +responsible for any content, products, services or other materials on +or available from third-party sites or third-party providers. Certain +third-party providers of goods and/or services may require your +agreement to additional or different terms and conditions prior to +your use of or access to such goods or services, and EVER disclaims +any and all responsibility or liability arising from such agreements +between you and a third party.

+

2. +Couriers

+

You +may engage third-party Couriers through the Service to provide +delivery services to you and may interact with those Couriers. Any +interactions or disputes between you and a Courier are solely between +you and that Courier. EVER and its licensors shall have no liability, +obligation or responsibility for any interaction between you and any +Courier.

+

3. +Third-Party Advertising

+

The +Service may contain third-party advertising and marketing. By +agreeing to these Terms you agree to receive such advertising and +marketing.

+

4. +App Stores

+

You +acknowledge and agree that the availability of the EVER Apps is +dependent on the third party from which you received the Application +license, e.g., the Apple iPhone or Android app stores (“App +Store”). You acknowledge that this Agreement is between you and the +EVER and not with the App Store. The EVER, not the App Store, is +solely responsible for the Software and the Services, including the +Applications and the Services, the content thereof, maintenance, +support services and warranty therefor, and addressing any claims +relating thereto (e.g., product liability, legal compliance or +intellectual property infringement). In order to use the +Applications, you must have access to a wireless network, and you +agree to pay all fees associated with such access. You also agree to +pay all fees (if any) charged by the App Store in connection with the +Application or the Services. You agree to comply with, and your +license to use the Applications is conditioned upon your compliance +with, all applicable third-party terms of agreement (e.g., the App +Store’s terms and policies) when using the Applications. You +acknowledge that the App Store (and its subsidiaries) are intended +third-party beneficiaries of the Agreement and have the right to +enforce them.

+

TRANSACTIONS +INVOLVING ALCOHOL +

+

You +may have the option to request delivery of alcohol products in some +locations and from certain Merchants. If you receive your delivery in +the Israel or United States, you agree that you will only order +alcohol products if you are 21 years of age or older. If you receive +your delivery in another country, you agree that you will only order +alcohol products if you are of legal age to purchase alcohol products +in the relevant jurisdiction. You also agree that, upon delivery of +alcohol products, you will provide valid government-issued +identification proving your age to the Carrier delivering the alcohol +products and that the recipient will not be intoxicated when +receiving delivery of such products. If you order alcohol products, +you understand and acknowledge that neither the EVER nor the Carrier +can accept your order of alcohol products, and the order will only be +delivered if the Merchant accepts your order. The Carrier reserves +the right to refuse delivery if you are not 21 years of older, if you +cannot provide a valid government issued ID, if the name on your ID +does not match the name on your order, or you are visibly +intoxicated. If the Carrier is unable to complete the delivery of +alcohol products for one or more of these reasons, you are subject to +a full refund.

+

MODIFICATIONS

+

EVER +CO. LTD shall reserve the right at any time it may deem fit, to +modify, alter and or discontinue, whether temporarily or permanently, +our service, or any part thereof, with or without prior notice. In +addition, we shall not be held liable to you or to any third party +for any such alteration, modification, suspension and/or +discontinuance of our Services, or any part thereof. 

+

TERMINATION

+

We +may terminate or suspend access to our Service immediately, without +prior notice or liability, for any reason whatsoever, including +without limitation if you breach the Terms.

+

As +a member of Ever.co or Mobile Apps, you may cancel or terminate your +account, associated email address and/or access to our Services by +submitting a cancellation or termination request to ever@ever.co.

+

As +a member, you agree that EVER CO. LTD may, without any prior written +notice, immediately suspend, terminate, discontinue and/or limit your +account, any email associated with your account, and access to any of +our Services.

+

The +cause for such termination, discontinuance, suspension and/or +limitation of access shall include, but is not limited to:

+

a) any +breach or violation of our TOS or any other incorporated agreement, +regulation and/or guideline;

+

b) by +way of requests from law enforcement or any other governmental +agencies;

+

c) the +discontinuance, alteration and/or material modification to our +Services, or any part thereof;

+

d) unexpected +technical or security issues and/or problems;

+

e) any +extended periods of inactivity;

+

f) any +engagement by you in any fraudulent or illegal activities; and/or

+

g) the +nonpayment of any associated fees that may be owed by you in +connection with your Ever.co and Apps account Services.

+

Furthermore, +you herein agree that any and all terminations, suspensions, +discontinuances, and or limitations of access for cause shall be made +at our sole discretion and that we shall not be liable to you or any +other third party with regards to the termination of your account, +associated email address and/or access to any of our Services.

+

The +termination of your account with Ever.co and Apps shall include any +and/or all of the following:

+

a) the +removal of any access to all or part of the Services offered within +Ever.co and Apps;

+

b) the +deletion of your password and any and all related information, files, +and any such content that may be associated with or inside your +account, or any part thereof; and

+

c) the +barring of any further use of all or part of our Services.

+

All +provisions of the Terms which by their nature should survive +termination shall survive termination, including, without limitation, +ownership provisions, warranty disclaimers, indemnity and limitations +of liability.

+

ADVERTISER

+

Any +correspondence or business dealings with, or the participation in any +promotions of, advertisers located on or through our Services, which +may include the payment and/or delivery of such related goods and/or +Services, and any such other term, condition, warranty and/or +representation associated with such dealings, are and shall be solely +between you and any such advertiser. Moreover, you herein agree that +EVER CO. LTD shall not be held responsible or liable for any loss or +damage of any nature or manner incurred as a direct result of any +such dealings or as a result of the presence of such advertisers on +our website.

+

LINKS

+

Either +EVER CO. LTD or any third parties may provide links to other websites +and/or resources. Thus, you acknowledge and agree that we are not +responsible for the availability of any such external sites or +resources, and as such, we do not endorse nor are we responsible or +liable for any content, products, advertising or any other materials, +on or available from such third party sites or resources. +Furthermore, you acknowledge and agree that EVER CO. LTD shall not be +responsible or liable, directly or indirectly, for any such damage or +loss which may be a result of, caused or allegedly to be caused by or +in connection with the use of or the reliance on any such content, +goods or Services made available on or through any such site or +resource.

+

PROPRIETARY +RIGHTS

+

You +do hereby acknowledge and agree that EVER CO. LTD's Services and any +essential software that may be used in connection with our Services +("Software") shall contain proprietary and confidential +material that is protected by applicable intellectual property rights +and other laws. Furthermore, you herein acknowledge and agree that +any Content which may be contained in any advertisements or +information presented by and through our Services or by advertisers +is protected by copyrights, trademarks, patents or other proprietary +rights and laws. Therefore, except for that which is expressly +permitted by applicable law or as authorized by EVER CO. LTD or such +applicable licensor, you agree not to alter, modify, lease, rent, +loan, sell, distribute, transmit, broadcast, publicly perform and/or +created any plagiaristic works which are based on EVER CO. LTD +Services (e.g. Content or Software), in whole or part.

+

EVER +CO. LTD herein has granted you personal, non-transferable and +non-exclusive rights and/or license to make use of the object code or +our Software on a single computer or mobile device, as long as you do +not, and shall not, allow any third party to duplicate, alter, +modify, create or plagiarize work from, reverse engineer, reverse +assemble or otherwise make an attempt to locate or discern any source +code, sell, assign, sublicense, grant a security interest in and/or +otherwise transfer any such right in the Software. Furthermore, you +do herein agree not to alter or change the Software in any manner, +nature or form, and as such, not to use any modified versions of the +Software, including and without limitation, for the purpose of +obtaining unauthorized access to our Services. Lastly, you also agree +not to access or attempt to access our Services through any means +other than through the interface which is provided by EVER CO. LTD +for use in accessing our Services.

+

WARRANTY +DISCLAIMERS

+

YOU +HEREIN EXPRESSLY ACKNOWLEDGE AND AGREE THAT:

+

a) THE +USE OF EVER CO. LTD SERVICES AND SOFTWARE ARE AT THE SOLE RISK BY +YOU. OUR SERVICES AND SOFTWARE SHALL BE PROVIDED ON AN "AS IS" +AND/OR "AS AVAILABLE" BASIS. EVER CO. LTD AND OUR +SUBSIDIARIES, AFFILIATES, OFFICERS, EMPLOYEES, AGENTS, PARTNERS AND +LICENSORS EXPRESSLY DISCLAIM ANY AND ALL WARRANTIES OF ANY KIND +WHETHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO ANY +IMPLIED WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NON-INFRINGEMENT.

+

b) EVER +CO. LTD AND OUR SUBSIDIARIES, OFFICERS, EMPLOYEES, AGENTS, PARTNERS +AND LICENSORS MAKE NO SUCH WARRANTIES THAT (i) EVER CO. LTD SERVICES +OR SOFTWARE WILL MEET YOUR REQUIREMENTS; (ii) EVER CO. LTD SERVICES +OR SOFTWARE SHALL BE UNINTERRUPTED, TIMELY, SECURE OR ERROR-FREE; +(iii) THAT SUCH RESULTS WHICH MAY BE OBTAINED FROM THE USE OF THE +EVER CO. LTD SERVICES OR SOFTWARE WILL BE ACCURATE OR RELIABLE; (iv) +QUALITY OF ANY PRODUCTS, SERVICES, ANY INFORMATION OR OTHER MATERIAL +WHICH MAY BE PURCHASED OR OBTAINED BY YOU THROUGH OUR SERVICES OR +SOFTWARE WILL MEET YOUR EXPECTATIONS; AND (v) THAT ANY SUCH ERRORS +CONTAINED IN THE SOFTWARE SHALL BE CORRECTED.

+

c) ANY +INFORMATION OR MATERIAL DOWNLOADED OR OTHERWISE OBTAINED BY WAY OF +EVER CO. LTD SERVICES OR SOFTWARE SHALL BE ACCESSED BY YOUR SOLE +DISCRETION AND SOLE RISK, AND AS SUCH YOU SHALL BE SOLELY RESPONSIBLE +FOR AND HEREBY WAIVE ANY AND ALL CLAIMS AND CAUSES OF ACTION WITH +RESPECT TO ANY DAMAGE TO YOUR COMPUTER AND/OR INTERNET ACCESS, +DOWNLOADING AND/OR DISPLAYING, OR FOR ANY LOSS OF DATA THAT COULD +RESULT FROM THE DOWNLOAD OF ANY SUCH INFORMATION OR MATERIAL.

+

d) NO +ADVICE AND/OR INFORMATION, DESPITE WHETHER WRITTEN OR ORAL, THAT MAY +BE OBTAINED BY YOU FROM EVER CO. LTD OR BY WAY OF OR FROM OUR +SERVICES OR SOFTWARE SHALL CREATE ANY WARRANTY NOT EXPRESSLY STATED +IN THE TOS.

+

e) A +SMALL PERCENTAGE OF SOME USERS MAY EXPERIENCE SOME DEGREE OF +EPILEPTIC SEIZURE WHEN EXPOSED TO CERTAIN LIGHT PATTERNS OR +BACKGROUNDS THAT MAY BE CONTAINED ON A COMPUTER OR MOBILE DEVICE +SCREEN OR WHILE USING OUR SERVICES. CERTAIN CONDITIONS MAY INDUCE A +PREVIOUSLY UNKNOWN CONDITION OR UNDETECTED EPILEPTIC SYMPTOM IN USERS +WHO HAVE SHOWN NO HISTORY OF ANY PRIOR SEIZURE OR EPILEPSY. SHOULD +YOU, ANYONE YOU KNOW OR ANYONE IN YOUR FAMILY HAVE AN EPILEPTIC +CONDITION, PLEASE CONSULT A PHYSICIAN IF YOU EXPERIENCE ANY OF THE +FOLLOWING SYMPTOMS WHILE USING OUR SERVICES: DIZZINESS, ALTERED +VISION, EYE OR MUSCLE TWITCHES, LOSS OF AWARENESS, DISORIENTATION, +ANY INVOLUNTARY MOVEMENT, OR CONVULSIONS.

+

LIMITATION +OF LIABILITY

+

YOU +EXPLICITLY ACKNOWLEDGE, UNDERSTAND AND AGREE THAT EVER CO. LTD AND +OUR SUBSIDIARIES, AFFILIATES, OFFICERS, EMPLOYEES, AGENTS, PARTNERS +AND LICENSORS SHALL NOT BE LIABLE TO YOU FOR ANY PUNITIVE, INDIRECT, +INCIDENTAL, SPECIAL, CONSEQUENTIAL OR EXEMPLARY DAMAGES, INCLUDING, +BUT NOT LIMITED TO, DAMAGES WHICH MAY BE RELATED TO THE LOSS OF ANY +PROFITS, GOODWILL, USE, DATA AND/OR OTHER INTANGIBLE LOSSES, EVEN +THOUGH WE MAY HAVE BEEN ADVISED OF SUCH POSSIBILITY THAT SAID DAMAGES +MAY OCCUR, AND RESULT FROM:

+

a) THE +USE OR INABILITY TO USE OUR SERVICE; +

+

b) THE +COST OF PROCURING SUBSTITUTE GOODS AND SERVICES;

+

c) UNAUTHORIZED +ACCESS TO OR THE ALTERATION OF YOUR TRANSMISSIONS AND/OR DATA; +

+

d) STATEMENTS +OR CONDUCT OF ANY SUCH THIRD PARTY ON OUR SERVICE; +

+

e) AND +ANY OTHER MATTER WHICH MAY BE RELATED TO OUR SERVICE.

+

RELEASE

+

In +the event you have a dispute, you agree to release EVER CO. LTD (and +its officers, directors, employees, agents, parent subsidiaries, +affiliates, co-branders, partners and any other third parties) from +claims, demands and damages (actual and consequential) of every kind +and nature, known and unknown, suspected or unsuspected, disclosed +and undisclosed, arising out of or in any way connected to such +dispute.

+

SPECIAL +ADMONITION RELATED TO FINANCIAL MATTERS

+

Should +you intend to create or to join any service, receive or request any +such news, messages, alerts or other information from our Services +concerning companies, stock quotes, investments or securities, please +review the above Sections Warranty Disclaimers and Limitations of +Liability again. In addition, for this particular type of +information, the phrase "Let the investor beware" is +appropriate. EVER CO. LTD's content is provided primarily for +informational purposes, and no content that shall be provided or +included in our Services is intended for trading or investing +purposes. EVER CO. LTD and our licensors shall not be responsible or +liable for the accuracy, usefulness or availability of any +information transmitted and/or made available by way of our Services, +and shall not be responsible or liable for any trading and/or +investment decisions based on any such information.

+

EXCLUSION +AND LIMITATIONS

+

THERE +ARE SOME JURISDICTIONS WHICH DO NOT ALLOW THE EXCLUSION OF CERTAIN +WARRANTIES OR THE LIMITATION OF EXCLUSION OF LIABILITY FOR INCIDENTAL +OR CONSEQUENTIAL DAMAGES. THEREFORE, SOME OF THE ABOVE LIMITATIONS OF +SECTIONS WARRANTY DISCLAIMERS AND LIMITATION OF LIABILITY MAY NOT +APPLY TO YOU.

+

THIRD +PARTY BENEFICIARIES

+

You +herein acknowledge, understand and agree, unless otherwise expressly +provided in this TOS, that there shall be no third-party +beneficiaries to this agreement.

+

NOTICE

+

EVER +CO. LTD may furnish you with notices, including those with regards to +any changes to the TOS, including but not limited to email, regular +mail, MMS or SMS, text messaging, postings on our website Services, +or other reasonable means currently known or any which may be herein +after developed. Any such notices may not be received if you violate +any aspects of the TOS by accessing our Services in an unauthorized +manner. Your acceptance of this TOS constitutes your agreement that +you are deemed to have received any and all notices that would have +been delivered had you accessed our Services in an authorized manner.

+

You +may receive text messages (SMS / MMS / Push Notifications) from or on +behalf of EVER as a part of the EVER Services at the cell phone +number(s) provided by you to EVER, and you consent to receiving such +text messages.

+

TRADEMARK +INFORMATION

+

You +herein acknowledge, understand and agree that all of the EVER CO. LTD +trademarks, copyright, trade name, service marks, and other EVER CO. +LTD logos and any brand features, and/or product and service names +are trademarks and as such, are and shall remain the property of EVER +CO. LTD. You herein agree not to display and/or use in any manner the +EVER CO. LTD logo or marks without obtaining EVER CO. LTD's prior +written consent.

+

COPYRIGHT +OR INTELLECTUAL PROPERTY INFRINGEMENT CLAIMS NOTICE & PROCEDURES

+

EVER +CO. LTD will always respect the intellectual property of others, and +we ask that all of our users do the same. With regards to appropriate +circumstances and at its sole discretion, EVER CO. LTD may disable +and/or terminate the accounts of any user who violates our TOS and/or +infringes the rights of others. If you feel that your work has been +duplicated in such a way that would constitute copyright +infringement, or if you believe your intellectual property rights +have been otherwise violated, you should provide to us the following +information:

+

a) The +electronic or the physical signature of the individual that is +authorized on behalf of the owner of the copyright or other +intellectual property interest;

+

b) A +description of the copyrighted work or other intellectual property +that you believe has been infringed upon;

+

c) A +description of the location of the site which you allege has been +infringing upon your work;

+

d) Your +physical address, telephone number, and email address;

+

e) A +statement, in which you state that the alleged and disputed use of +your work is not authorized by the copyright owner, its agents or the +law;

+

f) And +finally, a statement, made under penalty of perjury, that the +aforementioned information in your notice is truthful and accurate, +and that you are the copyright or intellectual property owner, +representative or agent authorized to act on the copyright or +intellectual property owner's behalf.

+

The +EVER CO. LTD Agent for notice of claims of copyright or other +intellectual property infringement can be contacted as follows:

+

Mailing +Address:

+

EVER +CO. LTD +

+

Attn: +Copyright Agent

+

HaAtsmaut +38/3

+

Ashdod +77452,

+

Israel

+

Email: + ever@ever.co

+

CLOSED +CAPTIONING

+

BE +IT KNOWN, that EVER CO. LTD complies with all applicable Federal +Communications Commission rules and regulations regarding the closed +captioning of video content. For more information, please visit our +website at ever.co.

+

GENERAL +INFORMATION

+

ENTIRE +AGREEMENT +

+

This +TOS constitutes the entire agreement between you and EVER CO. LTD and +shall govern the use of our Services, superseding any prior version +of this TOS between you and us with respect to EVER CO. LTD Services. +You may also be subject to additional terms and conditions that may +apply when you use or purchase certain other EVER CO. LTD Services, +affiliate Services, third-party content or third-party software.

+

CHOICE +OF LAW AND FORUM

+

It +is at the mutual agreement of both you and EVER CO. LTD with regard +to the TOS that the relationship between the parties shall be +governed by the laws of Israel without regard to its conflict of law +provisions and that any and all claims, causes of action and/or +disputes, arising out of or relating to the TOS, or the relationship +between you and EVER CO. LTD, shall be filed within the courts having +jurisdiction within the Israel. You and EVER CO. LTD agree to submit +to the jurisdiction of the courts as previously mentioned, and agree +to waive any and all objections to the exercise of jurisdiction over +the parties by such courts and to venue in such courts.

+

WAIVER +AND SEVERABILITY OF TERMS

+

At +any time, should EVER CO. LTD fail to exercise or enforce any right +or provision of the TOS, such failure shall not constitute a waiver +of such right or provision. If any provision of this TOS is found by +a court of competent jurisdiction to be invalid, the parties +nevertheless agree that the court should endeavor to give effect to +the parties' intentions as reflected in the provision, and the other +provisions of the TOS remain in full force and effect.

+

NO +RIGHT OF SURVIVORSHIP NON-TRANSFERABILITY

+

You +acknowledge, understand and agree that your account is +non-transferable and any rights to your ID and/or contents within +your account shall terminate upon your death. Upon receipt of a copy +of a death certificate, your account may be terminated and all +contents therein permanently deleted.

+

STATUTE +OF LIMITATIONS

+

You +acknowledge, understand and agree that regardless of any statute or +law to the contrary, any claim or action arising out of or related to +the use of our Services or the TOS must be filed within 1 year(s) +after said claim or cause of action arose or shall be forever barred.

+

VIOLATIONS

+

Please +report any and all violations of this TOS to EVER CO. LTD as follows:

+

Mailing +Address:

+

EVER +CO. LTD

+

HaAtsmaut +38/3

+

Ashdod +77452,

+

Israel

+

Email: + ever@ever.co

+

CHANGES

+

We +reserve the right, at our sole discretion, to modify or replace these +Terms at any time. If a revision is material we will try to provide +at least 30 (change this) days' notice prior to any new terms taking +effect. +

+

What +constitutes a material change will be determined at our sole +discretion.

+

By +continuing to access or use our Service after those revisions become +effective, you agree to be bound by the revised terms.

+

If +you do not agree to the new terms, please stop using the Service

+

CONTACT +US

+

If +you have any questions about these Terms, please contact us:

+

Mailing +Address:

+

EVER +CO. LTD

+

HaAtsmaut +38/3

+

Ashdod +77452,

+

Israel

+

Email: + ever@ever.co

+

+

+

+

DIGITAL +MILLENNIUM COPYRIGHT ACT (DMCA) INFRINGEMENT NOTICE AND POLICY

+

Notifications

+

If +you believe that content available on or through the EVER Apps and / +or Website infringes one or more of your copyrights, please +immediately notify our Copyright Agent by mail, email or faxed notice +(“Notification”) providing the information described below, which +Notification is pursuant to DMCA 17 U.S.C. § 512(c)(3). A copy of +your Notification will be sent to the person who posted or stored the +material addressed in the Notification. Please be advised that +pursuant to federal law you may be held liable for damages if you +make material misrepresentations in a Notification. Thus, if you are +not sure that content located on or linked to by the Apps and / or +Website infringes your copyright, you should consider first +contacting an attorney. Company has a policy of terminating repeat +infringers in appropriate circumstances.

+

All +Notifications should include the following:

+

A +physical or electronic signature of a person authorized to act on +behalf of the owner of an exclusive right that is allegedly +infringed.

+

Identification +of the copyrighted work claimed to have been infringed, or, if +multiple copyrighted works at a single online website are covered by +a single notification, a representative list of such works at that +website.

+

Identification +of the material that is claimed to be infringing or to be the subject +of infringing activity and that is to be removed or access to which +is to be disabled, and information reasonably sufficient to permit +Company to locate the material.

+

Information +reasonably sufficient to permit the Company to contact the +complaining party, such as an address, telephone number, and, if +available, an electronic mail address at which the complaining party +may be contacted.

+

A +statement that the complaining party has a good faith belief that use +of the material in the manner complained of is not authorized by the +copyright owner, its agent, or the law.

+

A +statement that the information in the notification is accurate, and +under penalty of perjury, that the complaining party is authorized to +act on behalf of the owner of an exclusive right that is allegedly +infringed.

+

Notifications +should be sent to our Copyright Agent as follows:

+

Copyright +Agent

+

EVER +CO. LTD

+

HaAtsmaut +38/3

+

Ashdod +77452,

+

Israel

+

Email: + dmca@ever.co

+



+

+ +
+ +
diff --git a/packages/core/res/templates/terms_of_use/he-IL.hbs b/packages/core/res/templates/terms_of_use/he-IL.hbs new file mode 100644 index 0000000..209c5b4 --- /dev/null +++ b/packages/core/res/templates/terms_of_use/he-IL.hbs @@ -0,0 +1,1242 @@ + + +
+ +

תנאי השירות

+ +
+ +

Thank +you for your interest in the Ever application for your mobile device +(the "App") provided to you by EVER CO. LTD ("EVER" +"us" or "we"), and our web site at Ever.co (the +"Site"), as well as all related web sites, networks, +downloadable software, and other services provided by us and on which +a link to this Terms of Service is displayed (collectively, together +with the Apps and Site, our "Service").

+

PLEASE +READ THE FOLLOWING TERMS OF SERVICE AGREEMENT CAREFULLY. +

+

These +Terms of Service (these "Terms", “Agreement”, “Terms +of Service”, "TOS"), including the Privacy Policy +incorporated into these Terms by reference and any other applicable +policies and guidelines, as may be updated from time to time, govern +your use of the Service. These Terms constitute a legal agreement +between you and EVER. In order to use the Service you must agree to +these Terms. +

+

BY +DOWNLOADING, INSTALLING, OR OTHERWISE BY ACCESSING OR USING OUR +SITES, MOBILE APPLICATIONS AND OUR SERVICES, YOU HEREBY AGREE THAT +YOU HAVE READ, UNDERSTOOD, AND AGREE TO BE TO BE BOUND BY THE TERMS +AND ALL TERMS INCORPORATED HEREIN BY REFERENCE. +

+

IT +IS THE RESPONSIBILITY OF YOU, THE USER, CUSTOMER, OR PROSPECTIVE +CUSTOMER TO READ THE TERMS AND CONDITIONS BEFORE PROCEEDING TO USE +THIS SITE OR OUR MOBILE APPS.

+

IF +YOU DO NOT EXPRESSLY AGREE TO ALL OF THE TERMS AND CONDITIONS, THEN +PLEASE DO NOT ACCESS OR USE OUR SITES, MOBILE APPS OR OUR SERVICES.

+

THIS +TERMS OF SERVICE AGREEMENT IS EFFECTIVE AS OF 11/06/2016.

+

ACCEPTANCE +OF TERMS

+

The +following Terms of Service Agreement is a legally binding agreement +that shall govern the relationship with our users and others which +may interact or interface with EVER CO. LTD, also known as EVER, +located at HaAtsmaut 38/3, Ashdod 77452, Israel and our subsidiaries +and affiliates, in association with the use of the EVER website and +mobile apps, which includes Ever.co, (the "Site") and its +Services, EVER Applications (the “Apps”) and its Services. 

+

Your +use of the EVER Services may be subject to separate third party terms +of service and fees, including without limitation your mobile network +operator’s terms of service and fees, including fees charged for +data usage, messaging and overage, which are your sole +responsibility.

+

DESCRIPTION +OF SERVICES OFFERED

+

EVER +provides a mobile apps and web-based technology platform (the +"Platform", "Marketplace") that connects +consumers (the "Customers"), retail stores, and restaurants +(together referenced as "Merchants"), with third party +independent contractors and third party independent contractors under +agreement with EVER and certain of EVER's affiliates (together +referenced as "Couriers"). The Platform provides order +placement services for Customers and allows them to view, discuss, +and place orders for food, beverages and goods, and pick-up and / or +delivery services in connection therewith, with Merchants and +Restaurants. The platform allows Carriers to facilitate on-demand +same day delivery services for food, merchandise, goods and +beverages. +

+

Through +the Platform consumers may request that food, merchandise, goods or +beverages, be delivered to them from particular retail locations, +stores or restaurants (Merchants). Couriers can access the Platform +and receive delivery opportunities. +

+

EVER +IS NOT A RETAIL STORE, MERCHANT OF FOOD OR BEVERAGES, RESTAURANT, +FOOD PICKUP AND / OR DELIVERY SERVICE, MERCHANDISE DELIVERY SERVICE, +OR FOOD PREPARATION ENTITY. YOU ACKNOWLEDGE THAT EVER DOES NOT +PROVIDE TRANSPORTATION OR LOGISTICS SERVICES OR FUNCTION AS A +TRANSPORTATION CARRIER AND EVER DOES NOT PROVIDE DELIVERY SERVICES +AND DOES NOT CONTROL THE RESTAURANTS OR THE PRODUCTION OF ANY FOOD OR +BEVERAGES, OR ANY PICK-UP OR DELIVERY SERVICES THEREWITH. INDEPENDENT +CONTRACTORS (EACH A "COURIER") OFFER DELIVERY SERVICES +THROUGH USE OF THE SERVICE. EVER OFFERS INFORMATION AND A METHOD TO +OBTAIN COURIER SERVICES, BUT DOES NOT AND DOES NOT INTEND TO PROVIDE +COURIER SERVICES OR ACT IN ANY WAY AS A COURIER, AND HAS NO +RESPONSIBILITY OR LIABILITY FOR ANY COURIER.

+

The +Merchants available through our Services operate indepenently of the +EVER. The EVER will not assess the suitability, legality or ability +of any Carrier or Merchant. The EVER is not responsible for the +Merchants food preparation or safety and does not verify their +compliance with applicable laws or regulations. The EVER has no +responsibility or liability for acts by any third-party Merchant or +Carrier, other than as stated herein. EVER, including the Website, +Apps and the EVER Services, does not in any way independently verify +the credentials or representations of any of the Restaurants, the +ingredients or the quality of any their products or services, or any +Restaurant’s compliance with applicable laws.

+

Customers +using the EVER Services must make themselves comfortable through the +information provided by the Restaurants on the Platform, by +contacting the Restaurants directly, or through such other means or +methods as they may deem appropriate, as to the quality and +reliability and quality of the Restaurants and the Restaurants’ +compliance with applicable laws. The EVER, including the Website, +Apps and the EVER Services, does not in any way guarantee the quality +of any Restaurant or any food or beverage, or any pickup- up or +delivery service in connection therewith, or any compliance thereof +with applicable laws. In addition, a Restaurant may represent certain +standards with respect to their food preparation (or other services), +such as “organic,” “kosher,” “macrobiotic” or allergen- +specific standards such as “nut-free,” “gluten-free,” or +“lactose-free”; EVER does not investigate or verify any such +representations. EVER shall not be liable or responsible for any food +or beverages, or any other services, offered by the Restaurants or +any errors or misrepresentations made by them (including on the +Website, Apps and through the EVER Services).

+

Unless +otherwise agreed by EVER in a separate written agreement with you, +the Services are made available solely for your personal, +noncommercial use. +

+

As +provided in greater detail in these Terms, you agree and acknowledge +these material Terms:

+

- +The App is licensed, not sold to you, and you may use the Service +only as set forth in these Terms;

+

- +Your use of the Service may be subject to separate third party terms +of service and fees, including without limitation your mobile network +operator’s ("Carrier") terms of service and fees, +including fees charged for data usage and overage, which are your +sole responsibility;

+

- +You consent to the collection, sharing, and use of your personally +identifiable information in accordance with EVER Privacy Policy;

+

- +The Service is provided "as is" without warranties of any +kind, and EVER liability to you is limited; +

+

SCOPE +OF APPS LICENSE

+

Our +Apps are licensed, not sold, to you for use only under the terms of +this license. EVER reserves all rights not expressly granted to you. +Subject to your complete and ongoing compliance with these Terms, +EVER hereby grants you a personal, limited, revocable, +non-transferable license to use our Apps on compatible devices that +you own or control, solely for your non-commercial use.

+

You +may not modify, alter, reproduce, distribute or make the Apps +available over a network where it could be used by multiple devices +at the same time. You may not rent, lease, lend, sell, redistribute +or sublicense the Apps. If you breach these license restrictions, or +otherwise exceed the scope of the license granted in these Terms, you +may be subject to prosecution and legal damages, as well as liability +for infringement of intellectual property rights. These Terms will +govern any updates provided to you by EVER that replace and/or +supplement the original Apps, unless the upgrade is accompanied by a +separate license in which case the terms of that license will govern.

+

ACCOUNT +ACCESS

+

You +must be at least eighteen (18) years old to use the Service. By +agreeing to these Terms, you represent and warrant to us:

+

- +That you are at least eighteen (18) years old;

+

- +That you have not previously been suspended or removed from the +Service; and

+

- +That your registration and your use of the Service is in compliance +with any and all applicable laws and regulations.

+

If +you are using the Service on behalf of an entity, organization, or +company, you represent and warrant that you have the authority to +bind that organization to these Terms and you agree to be bound by +these Terms on behalf of that organization.

+

ACCOUNTS +AND REGISTRATION

+

Any +and all visitors to our site and users of our apps, despite whether +they are registered or not, shall be deemed as "users" of +the herein contained Services provided for the purpose of this TOS.

+

Once +an individual register's for our Services, through the process of +creating an account, the user shall then be considered a "member."

+

To +register and become a "member" of the Site or Apps, you +must be at least 18 years of age to enter into and form a legally +binding contract. In addition, you must be in good standing and not +an individual that has been previously barred from receiving EVER's +Services under the laws and statutes of the Israel, United States or +other applicable jurisdiction.

+

When +you register, EVER may collect information such as your first and +last name, e-mail address, birth date, gender, mailing address, +occupation, industry and personal interests. +

+

You +can edit your account information at any time. Once you register with +EVER and sign in to our Services, you are no longer anonymous to us.

+

Furthermore, +the registering party hereby acknowledges, understands and agrees to:

+

a) furnish +factual, correct, current and complete information with regards to +yourself as may be requested by the data registration process, and

+

b) maintain +and promptly update your registration and profile information in an +effort to maintain accuracy and completeness at all times.

+

If +anyone knowingly provides any information of a false, untrue, +inaccurate or incomplete nature, EVER CO. LTD will have sufficient +grounds and rights to suspend or terminate the member in violation of +this aspect of the Agreement, and as such refuse any and all current +or future use of EVER CO. LTD Services, or any portion thereof. +

+

The +user and/or member acknowledges and agrees that the Services provided +and made available through our website and applications, which may +include some mobile applications and that those applications may be +made available on various social media networking sites and numerous +other platforms and downloadable programs, are the sole property of +EVER CO. LTD. At its discretion, EVER CO. LTD may offer additional +website Services and/or products, or update, modify or revise any +current content and Services, and this Agreement shall apply to any +and all additional Services and/or products and any and all updated, +modified or revised Services unless otherwise stipulated. EVER CO. +LTD does hereby reserve the right to cancel and cease offering any of +the aforementioned Services and/or products. You, as the end user +and/or member, acknowledge, accept and agree that EVER CO. LTD shall +not be held liable for any such updates, modifications, revisions, +suspensions or discontinuance of any of our Services and/or products. +Your continued use of the Services provided, after such posting of +any updates, changes, and/or modifications shall constitute your +acceptance of such updates, changes and/or modifications, and as +such, frequent review of this Agreement and any and all applicable +terms and policies should be made by you to ensure you are aware of +all terms and policies currently in effect. Should you not agree to +the updated, revised or modified terms, you must stop using the +provided Services forthwith.

+

Furthermore, +the user and/or member understands, acknowledges and agrees that the +Services offered shall be provided "AS IS" and as such EVER +CO. LTD shall not assume any responsibility or obligation for the +timeliness, missed delivery, deletion and/or any failure to store +user content, communication or personalization settings. +

+

PURCHASES

+

If +you wish to purchase any product, food or service made available +through the Service ("Purchase"), you may be asked to +supply certain information relevant to your Purchase including, +without limitation, your first and last name, birth date, gender, +credit card information, physical and mailing addresses, e-mail +address and phone number or other contact information.

+

When +you place an order through the Apps, WebSite and EVER Services, you +will be given a choice of payment options, which may include via +Apple Pay, Credit Card, PayPal®, Android Pay, and direct payment to +the applicable Merchant or Restaurant. If you pay for your purchase +via Apple Pay, Credit Card, PayPal, or Android Pay, we will ask for a +valid Apple Pay account, Credit Card, PayPal account, or Android Pay +account, as applicable, which will be billed through the EVER +Services, for the purchase price of the applicable order, and “EVER +CO. LTD” or your name will be the name that appears on the Apple +Pay, Credit Card, PayPal, or Android Pay statement, as applicable. As +stated above, however, EVER, including the Apps, Website and the EVER +Services, is not and shall not in any manner be considered the seller +of any of the food, beverages, and services ordered.

+

PRICES

+

You +understand that: (a) the prices for food and goods displayed through +the Services may differ from the prices offered or published by +Merchants for the same menu items, food or goods and/or from prices +available at other third-party websites and that such prices may not +be the lowest prices at which the items are sold; (b) the Company has +no obligation to itemize its costs, profits or margins when +publishing such prices; and (c) the Company reserves the right to +change such prices at any time, at its discretion. You are liable for +all transaction taxes on the Services provided under this Agreement +(other than taxes based on the Company’s income). Payment will be +processed by the Company, using the preferred payment method +designated in your account.

+

GENERAL +PAYMENT TERMS

+

Certain +features of the Service, including the placing of orders using the +Service, may require you to pay fees. Before you pay any fees, you +will have an opportunity to review and accept an estimate of the fees +that you will be charged. All fees are non-refundable. This no refund +policy applies at all times regardless of your decision to terminate +your usage, our decision to terminate your usage and any disruption +caused to our Service for any reason whatsoever.

+

EVER, +at its sole discretion, may offer credits or refunds on a +case-by-case basis; all credit and/or refund requests must be made +within fifteen (15) days after the delivery was completed. EVER may +change the delivery or other fees for any feature of the Service, +including by adding fees, on a going-forward basis at any time.

+

EVER +will charge the payment method you specify at the time of purchase or +as otherwise specified by you in your account information. EVER +reserves the right to determine final prevailing pricing. (Please +note the pricing information published on the website may not reflect +the prevailing pricing.) EVER, at its sole discretion, may make +promotional offers with different features and different rates to any +of our customers. These promotional offers, unless made to you, shall +have no bearing whatsoever on your offer or contract. We may change +the fees for the Service at any time as we deem necessary for our +business. +

+

PAYMENT +AUTHORIZATION

+

You +authorize EVER to charge all sums for orders that you make and +services you select to the payment method specified in your account. +When you order on EVER, a temporary pre-authorization hold might be +placed on your payment card to verify that the card is valid and has +credit available for your intended purchase. Once your order is +complete, you will be charged the final order total and the +pre-authorization hold will be lifted within 24-72 business hours, +depending on your bank. The pre-authorization hold remains even if an +order is canceled by a customer or by EVER and may remain on your +card for up to 5 business days.

+

EVER +also may place an initial temporary pre-authorization hold on each +new payment method you add to your account.

+

PAYMENT +WHEN CUSTOMER NOT AVAILABLE

+

EVER +reserves the right to charge a customer the full order amount if that +customer is not at the designated delivery location when the courier +arrives to complete the delivery.

+

PRIVACY +POLICY

+

Every +member's registration data and various other personal information are +strictly protected by the EVER CO. LTD Online Privacy Policy (see the +full Privacy Policy at https://ever.co/privacy/site.html). +As a member, you herein consent to the collection and use of the +information provided, including the transfer of information within +the United States, Israel and/or other countries for storage, +processing or use by EVER CO. LTD and/or our subsidiaries and +affiliates. +

+

MEMBER +ACCOUNT, USERNAME, PASSWORD AND SECURITY

+

When +you register, you will be asked to provide a password. You are solely +responsible for maintaining the confidentiality of your account and +password, and any password for Facebook, Google, or other third party +login. You accept responsibility for all activities that occur under +your account. If you have reason to believe that your account is no +longer secure, you must immediately notify us by our sending email to +security@ever.co

+

When +you set up an account, you are the sole authorized user of your +account. You shall be responsible for maintaining the secrecy and +confidentiality of your password and for all activities that +transpire on or within your account. It is your responsibility for +any act or omission of any user(s) that access your account +information that, if undertaken by you, would be deemed a violation +of the TOS. It shall be your responsibility to notify EVER CO. LTD +immediately if you notice any unauthorized access or use of your +account or password or any other breach of security. EVER CO. LTD +shall not be held liable for any loss and/or damage arising from any +failure to comply with this term and/or condition of the TOS. +

+

CONDUCT

+

As +a user or member of the Site or Apps, you herein acknowledge, +understand and agree that all information, text, software, data, +photographs, music, video, messages, tags or any other content, +whether it is publicly or privately posted and/or transmitted, is the +expressed sole responsibility of the individual from whom the content +originated. In short, this means that you are solely responsible for +any and all content posted, uploaded, emailed, transmitted or +otherwise made available by way of the EVER Services, and as such, we +do not guarantee the accuracy, integrity or quality of such content. +It is expressly understood that by use of our Services, you may be +exposed to content including, but not limited to, any errors or +omissions in any content posted, and/or any loss or damage of any +kind incurred as a result of the use of any content posted, emailed, +transmitted or otherwise made available by EVER. +

+

Furthermore, +you herein agree not to make use of EVER CO. LTD's Services for the +purpose of:

+

a) uploading, +posting, emailing, transmitting, or otherwise making available any +content that shall be deemed unlawful, harmful, threatening, abusive, +harassing, tortious, defamatory, vulgar, obscene, libelous, or +invasive of another's privacy or which is hateful, and/or racially, +ethnically, or otherwise objectionable;

+

b) causing +harm to minors in any manner whatsoever;

+

c) impersonating +any individual or entity, including, but not limited to, any EVER +officials, forum leaders, guides or hosts or falsely stating or +otherwise misrepresenting any affiliation with an individual or +entity;

+

d) forging +captions, headings or titles or otherwise offering any content that +you personally have no right to pursuant to any law nor having any +contractual or fiduciary relationship with;

+

e) uploading, +posting, emailing, transmitting or otherwise offering any such +content that may infringe upon any patent, copyright, trademark, or +any other proprietary or intellectual rights of any other party;

+

f) uploading, +posting, emailing, transmitting or otherwise offering any content +that you do not personally have any right to offer pursuant to any +law or in accordance with any contractual or fiduciary relationship;

+

g) uploading, +posting, emailing, transmitting, or otherwise offering any +unsolicited or unauthorized advertising, promotional flyers, "junk +mail," "spam," or any other form of solicitation, +except in any such areas that may have been designated for such +purpose;

+

h) uploading, +posting, emailing, transmitting, or otherwise offering any source +that may contain a software virus or other computer code, any files +and/or programs which have been designed to interfere, destroy and/or +limit the operation of any computer software, hardware, or +telecommunication equipment;

+

i) disrupting +the normal flow of communication, or otherwise acting in any manner +that would negatively affect other users' ability to participate in +any real time interactions;

+

j) interfering +with or disrupting any EVER CO. LTD Services, servers and/or networks +that may be connected or related to our website, including, but not +limited to, the use of any device software and/or routine to bypass +the robot exclusion headers;

+

k) intentionally +or unintentionally violating any local, state, federal, national or +international law, including, but not limited to, rules, guidelines, +and/or regulations decreed by the U.S. Securities and Exchange +Commission, in addition to any rules of any nation or other +securities exchange, that would include without limitation, the New +York Stock Exchange, the American Stock Exchange, or the NASDAQ, and +any regulations having the force of law;

+

l) providing +informational support or resources, concealing and/or disguising the +character, location, and or source to any organization delegated by +the United States government as a "foreign terrorist +organization" in accordance to Section 219 of the Immigration +Nationality Act;

+

m) "stalking" +or with the intent to otherwise harass another individual; and/or +

+

n) collecting +or storing of any personal data relating to any other member or user +in connection with the prohibited conduct and/or activities which +have been set forth in the aforementioned paragraphs.

+

EVER +CO. LTD herein reserves the right to pre-screen, refuse and/or delete +any content currently available through our Services. In addition, we +reserve the right to remove and/or delete any such content that would +violate the TOS or which would otherwise be considered offensive to +other visitors, users and/or members.  

+

EVER +CO. LTD herein reserves the right to access, preserve and/or disclose +member account information and/or content if it is requested to do so +by law or in good faith belief that any such action is deemed +reasonably necessary for: 

+

a) compliance +with any legal process; 

+

b) enforcement +of the TOS; 

+

c) responding +to any claim that therein contained content is in violation of the +rights of any third party; 

+

d) responding +to requests for customer service; or 

+

e) protecting +the rights, property or the personal safety of EVER CO. LTD, its +visitors, users and members, including the general public.

+

EVER +CO. LTD herein reserves the right to include the use of security +components that may permit digital information or material to be +protected, and that such use of information and/or material is +subject to usage guidelines and regulations established by EVER CO. +LTD or any other content providers supplying content services to EVER +CO. LTD. You are hereby prohibited from making any attempt to +override or circumvent any of the embedded usage rules in our +Services. Furthermore, unauthorized reproduction, publication, +distribution, or exhibition of any information or materials supplied +by our Services, despite whether done so in whole or in part, is +expressly prohibited.

+

INTERSTATE +COMMUNICATION

+

Upon +registration, you hereby acknowledge that by using Site and Apps to +send electronic communications, which would include, but are not +limited to, email, searches, instant messages, uploading of files, +photos and/or videos, you will be causing communications to be sent +through our computer network. Therefore, through your use, and thus +your agreement with this TOS, you are acknowledging that the use of +this Service shall result in interstate transmissions.

+

CAUTIONS +FOR GLOBAL USE AND EXPORT AND IMPORT COMPLIANCE

+

Due +to the global nature of the internet, through the use of our network +you hereby agree to comply with all local rules relating to online +conduct and that which is considered acceptable Content. Uploading, +posting and/or transferring of software, technology and other +technical data may be subject to the export and import laws of the +United States and possibly other countries. Through the use of our +network, you thus agree to comply with all applicable export and +import laws, statutes and regulations, including, but not limited to, +the Export Administration Regulations +(http://www.access.gpo.gov/bis/ear/ear_data.html), +as well as the sanctions control program of the United States +(http://www.treasury.gov/resource-center/sanctions/Programs/Pages/Programs.aspx). +Furthermore, you state and pledge that you:

+

a) are +not on the list of prohibited individuals which may be identified on +any government export exclusion report +(http://www.bis.doc.gov/complianceandenforcement/liststocheck.htm) +nor a member of any other government which may be part of an +export-prohibited country identified in applicable export and import +laws and regulations;

+

b) agree +not to transfer any software, technology or any other technical data +through the use of our network Services to any export-prohibited +country; +

+

c) agree +not to use our website network Services for any military, nuclear, +missile, chemical or biological weaponry end uses that would be a +violation of the Israel, U.S. export laws; and

+

d) agree +not to post, transfer nor upload any software, technology or any +other technical data which would be in violation of the Israel, U.S. +or other applicable export and/or import laws.

+

CONTENT +PLACED OR MADE AVAILABLE FOR COMPANY SERVICES

+

EVER +CO. LTD shall not lay claim to ownership of any content submitted by +any visitor, member, or user, nor make such content available for +inclusion on our website Services. Therefore, you hereby grant and +allow for EVER CO. LTD the below listed worldwide, royalty-free and +non-exclusive licenses, as applicable:

+

a) The +content submitted or made available for inclusion on the publicly +accessible areas of EVER CO. LTD's sites, the license provided to +permit to use, distribute, reproduce, modify, adapt, publicly perform +and/or publicly display said Content on our network Services is for +the sole purpose of providing and promoting the specific area to +which this content was placed and/or made available for viewing. This +license shall be available so long as you are a member of EVER CO. +LTD's sites, and shall terminate at such time when you elect to +discontinue your membership.

+

b) Photos, +audio, video and/or graphics submitted or made available for +inclusion on the publicly accessible areas of EVER CO. LTD's sites, +the license provided to permit to use, distribute, reproduce, modify, +adapt, publicly perform and/or publicly display said Content on our +network Services are for the sole purpose of providing and promoting +the specific area in which this content was placed and/or made +available for viewing. This license shall be available so long as you +are a member of EVER CO. LTD's sites and shall terminate at such time +when you elect to discontinue your membership.

+

c) For +any other content submitted or made available for inclusion on the +publicly accessible areas of EVER CO. LTD's sites, the continuous, +binding and completely sub-licensable license which is meant to +permit to use, distribute, reproduce, modify, adapt, publish, +translate, publicly perform and/or publicly display said content, +whether in whole or in part, and the incorporation of any such +Content into other works in any arrangement or medium current used or +later developed.

+

Those +areas which may be deemed "publicly accessible" areas of +EVER CO. LTD's sites are those such areas of our network properties +which are meant to be available to the general public, and which +would include message boards and groups that are openly available to +both users and members. However, those areas which are not open to +the public, and thus available to members only, would include our +mail system and instant messaging.

+

CONTRIBUTIONS +TO COMPANY WEBSITE

+

EVER +CO. LTD provides an area for our users and members to contribute +feedback to our website. When you submit ideas, documents, +suggestions and/or proposals ("Contributions") to our site, +you acknowledge and agree that: +

+

a) your +contributions do not contain any type of confidential or proprietary +information;

+

b) EVER +shall not be liable or under any obligation to ensure or maintain +confidentiality, expressed or implied, related to any Contributions; +

+

c) EVER +shall be entitled to make use of and/or disclose any such +Contributions in any such manner as they may see fit; +

+

d) the +contributor's Contributions shall automatically become the sole +property of EVER; and

+

e) EVER +is under no obligation to either compensate or provide any form of +reimbursement in any manner or nature.

+

INDEMNITY

+

All +users and/or members herein agree to insure and hold EVER CO. LTD, +our subsidiaries, affiliates, agents, employees, officers, partners +and/or licensors blameless or not liable for any claim or demand, +which may include, but is not limited to, reasonable attorney fees +made by any third party which may arise from any content a member or +user of our site may submit, post, modify, transmit or otherwise make +available through our Services, the use of EVER Services or your +connection with these Services, your violations of the Terms of +Service and/or your violation of any such rights of another person.

+

COMMERCIAL +REUSE OF SERVICES

+

The +member or user herein agrees not to replicate, duplicate, copy, +trade, sell, resell nor exploit for any commercial reason any part, +use of, or access to EVER's sites or Apps.

+

USE +AND STORAGE GENERAL PRACTICES

+

You +herein acknowledge that EVER CO. LTD may set up any such practices +and/or limits regarding the use of our Services, without limitation +of the maximum number of days that any email, message posting or any +other uploaded content shall be retained by EVER CO. LTD, nor the +maximum number of email messages that may be sent and/or received by +any member, the maximum volume or size of any email message that may +be sent from or may be received by an account on our Service, the +maximum disk space allowable that shall be allocated on EVER CO. +LTD's servers on the member's behalf, and/or the maximum number of +times and/or duration that any member may access our Services in a +given period of time. In addition, you also agree that EVER CO. LTD +has absolutely no responsibility or liability for the removal or +failure to maintain storage of any messages and/or other +communications or content maintained or transmitted by our Services. +You also herein acknowledge that we reserve the right to delete or +remove any account that is no longer active for an extended period of +time. Furthermore, EVER CO. LTD shall reserve the right to modify, +alter and/or update these general practices and limits at our +discretion.

+

Any +messenger service, which may include any web-based versions, shall +allow you and the individuals with whom you communicate with the +ability to save your conversations in your account located on EVER +CO. LTD's servers. In this manner, you will be able to access and +search your message history from any computer with internet access. +You also acknowledge that others have the option to use and save +conversations with you in their own personal account on Ever.co. It +is your agreement to this TOS which establishes your consent to allow +EVER CO. LTD to store any and all communications on its servers.

+

THIRD-PARTY +INTERACTIONS +

+

1. +Third-Party Providers

+

During +use of the Service, you may purchase goods and services from +third-party merchants through the Service. Any such activity, and any +disputes, terms, conditions, warranties or representations associated +with that activity, is solely between you and the applicable third +party. EVER and its licensors shall have no liability, obligation or +responsibility for any purchase or transaction between you and any +third-party provider. In no event shall EVER or its licensors be +responsible for any content, products, services or other materials on +or available from third-party sites or third-party providers. Certain +third-party providers of goods and/or services may require your +agreement to additional or different terms and conditions prior to +your use of or access to such goods or services, and EVER disclaims +any and all responsibility or liability arising from such agreements +between you and a third party.

+

2. +Couriers

+

You +may engage third-party Couriers through the Service to provide +delivery services to you and may interact with those Couriers. Any +interactions or disputes between you and a Courier are solely between +you and that Courier. EVER and its licensors shall have no liability, +obligation or responsibility for any interaction between you and any +Courier.

+

3. +Third-Party Advertising

+

The +Service may contain third-party advertising and marketing. By +agreeing to these Terms you agree to receive such advertising and +marketing.

+

4. +App Stores

+

You +acknowledge and agree that the availability of the EVER Apps is +dependent on the third party from which you received the Application +license, e.g., the Apple iPhone or Android app stores (“App +Store”). You acknowledge that this Agreement is between you and the +EVER and not with the App Store. The EVER, not the App Store, is +solely responsible for the Software and the Services, including the +Applications and the Services, the content thereof, maintenance, +support services and warranty therefor, and addressing any claims +relating thereto (e.g., product liability, legal compliance or +intellectual property infringement). In order to use the +Applications, you must have access to a wireless network, and you +agree to pay all fees associated with such access. You also agree to +pay all fees (if any) charged by the App Store in connection with the +Application or the Services. You agree to comply with, and your +license to use the Applications is conditioned upon your compliance +with, all applicable third-party terms of agreement (e.g., the App +Store’s terms and policies) when using the Applications. You +acknowledge that the App Store (and its subsidiaries) are intended +third-party beneficiaries of the Agreement and have the right to +enforce them.

+

TRANSACTIONS +INVOLVING ALCOHOL +

+

You +may have the option to request delivery of alcohol products in some +locations and from certain Merchants. If you receive your delivery in +the Israel or United States, you agree that you will only order +alcohol products if you are 21 years of age or older. If you receive +your delivery in another country, you agree that you will only order +alcohol products if you are of legal age to purchase alcohol products +in the relevant jurisdiction. You also agree that, upon delivery of +alcohol products, you will provide valid government-issued +identification proving your age to the Carrier delivering the alcohol +products and that the recipient will not be intoxicated when +receiving delivery of such products. If you order alcohol products, +you understand and acknowledge that neither the EVER nor the Carrier +can accept your order of alcohol products, and the order will only be +delivered if the Merchant accepts your order. The Carrier reserves +the right to refuse delivery if you are not 21 years of older, if you +cannot provide a valid government issued ID, if the name on your ID +does not match the name on your order, or you are visibly +intoxicated. If the Carrier is unable to complete the delivery of +alcohol products for one or more of these reasons, you are subject to +a full refund.

+

MODIFICATIONS

+

EVER +CO. LTD shall reserve the right at any time it may deem fit, to +modify, alter and or discontinue, whether temporarily or permanently, +our service, or any part thereof, with or without prior notice. In +addition, we shall not be held liable to you or to any third party +for any such alteration, modification, suspension and/or +discontinuance of our Services, or any part thereof. 

+

TERMINATION

+

We +may terminate or suspend access to our Service immediately, without +prior notice or liability, for any reason whatsoever, including +without limitation if you breach the Terms.

+

As +a member of Ever.co or Mobile Apps, you may cancel or terminate your +account, associated email address and/or access to our Services by +submitting a cancellation or termination request to ever@ever.co.

+

As +a member, you agree that EVER CO. LTD may, without any prior written +notice, immediately suspend, terminate, discontinue and/or limit your +account, any email associated with your account, and access to any of +our Services.

+

The +cause for such termination, discontinuance, suspension and/or +limitation of access shall include, but is not limited to:

+

a) any +breach or violation of our TOS or any other incorporated agreement, +regulation and/or guideline;

+

b) by +way of requests from law enforcement or any other governmental +agencies;

+

c) the +discontinuance, alteration and/or material modification to our +Services, or any part thereof;

+

d) unexpected +technical or security issues and/or problems;

+

e) any +extended periods of inactivity;

+

f) any +engagement by you in any fraudulent or illegal activities; and/or

+

g) the +nonpayment of any associated fees that may be owed by you in +connection with your Ever.co and Apps account Services.

+

Furthermore, +you herein agree that any and all terminations, suspensions, +discontinuances, and or limitations of access for cause shall be made +at our sole discretion and that we shall not be liable to you or any +other third party with regards to the termination of your account, +associated email address and/or access to any of our Services.

+

The +termination of your account with Ever.co and Apps shall include any +and/or all of the following:

+

a) the +removal of any access to all or part of the Services offered within +Ever.co and Apps;

+

b) the +deletion of your password and any and all related information, files, +and any such content that may be associated with or inside your +account, or any part thereof; and

+

c) the +barring of any further use of all or part of our Services.

+

All +provisions of the Terms which by their nature should survive +termination shall survive termination, including, without limitation, +ownership provisions, warranty disclaimers, indemnity and limitations +of liability.

+

ADVERTISER

+

Any +correspondence or business dealings with, or the participation in any +promotions of, advertisers located on or through our Services, which +may include the payment and/or delivery of such related goods and/or +Services, and any such other term, condition, warranty and/or +representation associated with such dealings, are and shall be solely +between you and any such advertiser. Moreover, you herein agree that +EVER CO. LTD shall not be held responsible or liable for any loss or +damage of any nature or manner incurred as a direct result of any +such dealings or as a result of the presence of such advertisers on +our website.

+

LINKS

+

Either +EVER CO. LTD or any third parties may provide links to other websites +and/or resources. Thus, you acknowledge and agree that we are not +responsible for the availability of any such external sites or +resources, and as such, we do not endorse nor are we responsible or +liable for any content, products, advertising or any other materials, +on or available from such third party sites or resources. +Furthermore, you acknowledge and agree that EVER CO. LTD shall not be +responsible or liable, directly or indirectly, for any such damage or +loss which may be a result of, caused or allegedly to be caused by or +in connection with the use of or the reliance on any such content, +goods or Services made available on or through any such site or +resource.

+

PROPRIETARY +RIGHTS

+

You +do hereby acknowledge and agree that EVER CO. LTD's Services and any +essential software that may be used in connection with our Services +("Software") shall contain proprietary and confidential +material that is protected by applicable intellectual property rights +and other laws. Furthermore, you herein acknowledge and agree that +any Content which may be contained in any advertisements or +information presented by and through our Services or by advertisers +is protected by copyrights, trademarks, patents or other proprietary +rights and laws. Therefore, except for that which is expressly +permitted by applicable law or as authorized by EVER CO. LTD or such +applicable licensor, you agree not to alter, modify, lease, rent, +loan, sell, distribute, transmit, broadcast, publicly perform and/or +created any plagiaristic works which are based on EVER CO. LTD +Services (e.g. Content or Software), in whole or part.

+

EVER +CO. LTD herein has granted you personal, non-transferable and +non-exclusive rights and/or license to make use of the object code or +our Software on a single computer or mobile device, as long as you do +not, and shall not, allow any third party to duplicate, alter, +modify, create or plagiarize work from, reverse engineer, reverse +assemble or otherwise make an attempt to locate or discern any source +code, sell, assign, sublicense, grant a security interest in and/or +otherwise transfer any such right in the Software. Furthermore, you +do herein agree not to alter or change the Software in any manner, +nature or form, and as such, not to use any modified versions of the +Software, including and without limitation, for the purpose of +obtaining unauthorized access to our Services. Lastly, you also agree +not to access or attempt to access our Services through any means +other than through the interface which is provided by EVER CO. LTD +for use in accessing our Services.

+

WARRANTY +DISCLAIMERS

+

YOU +HEREIN EXPRESSLY ACKNOWLEDGE AND AGREE THAT:

+

a) THE +USE OF EVER CO. LTD SERVICES AND SOFTWARE ARE AT THE SOLE RISK BY +YOU. OUR SERVICES AND SOFTWARE SHALL BE PROVIDED ON AN "AS IS" +AND/OR "AS AVAILABLE" BASIS. EVER CO. LTD AND OUR +SUBSIDIARIES, AFFILIATES, OFFICERS, EMPLOYEES, AGENTS, PARTNERS AND +LICENSORS EXPRESSLY DISCLAIM ANY AND ALL WARRANTIES OF ANY KIND +WHETHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO ANY +IMPLIED WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NON-INFRINGEMENT.

+

b) EVER +CO. LTD AND OUR SUBSIDIARIES, OFFICERS, EMPLOYEES, AGENTS, PARTNERS +AND LICENSORS MAKE NO SUCH WARRANTIES THAT (i) EVER CO. LTD SERVICES +OR SOFTWARE WILL MEET YOUR REQUIREMENTS; (ii) EVER CO. LTD SERVICES +OR SOFTWARE SHALL BE UNINTERRUPTED, TIMELY, SECURE OR ERROR-FREE; +(iii) THAT SUCH RESULTS WHICH MAY BE OBTAINED FROM THE USE OF THE +EVER CO. LTD SERVICES OR SOFTWARE WILL BE ACCURATE OR RELIABLE; (iv) +QUALITY OF ANY PRODUCTS, SERVICES, ANY INFORMATION OR OTHER MATERIAL +WHICH MAY BE PURCHASED OR OBTAINED BY YOU THROUGH OUR SERVICES OR +SOFTWARE WILL MEET YOUR EXPECTATIONS; AND (v) THAT ANY SUCH ERRORS +CONTAINED IN THE SOFTWARE SHALL BE CORRECTED.

+

c) ANY +INFORMATION OR MATERIAL DOWNLOADED OR OTHERWISE OBTAINED BY WAY OF +EVER CO. LTD SERVICES OR SOFTWARE SHALL BE ACCESSED BY YOUR SOLE +DISCRETION AND SOLE RISK, AND AS SUCH YOU SHALL BE SOLELY RESPONSIBLE +FOR AND HEREBY WAIVE ANY AND ALL CLAIMS AND CAUSES OF ACTION WITH +RESPECT TO ANY DAMAGE TO YOUR COMPUTER AND/OR INTERNET ACCESS, +DOWNLOADING AND/OR DISPLAYING, OR FOR ANY LOSS OF DATA THAT COULD +RESULT FROM THE DOWNLOAD OF ANY SUCH INFORMATION OR MATERIAL.

+

d) NO +ADVICE AND/OR INFORMATION, DESPITE WHETHER WRITTEN OR ORAL, THAT MAY +BE OBTAINED BY YOU FROM EVER CO. LTD OR BY WAY OF OR FROM OUR +SERVICES OR SOFTWARE SHALL CREATE ANY WARRANTY NOT EXPRESSLY STATED +IN THE TOS.

+

e) A +SMALL PERCENTAGE OF SOME USERS MAY EXPERIENCE SOME DEGREE OF +EPILEPTIC SEIZURE WHEN EXPOSED TO CERTAIN LIGHT PATTERNS OR +BACKGROUNDS THAT MAY BE CONTAINED ON A COMPUTER OR MOBILE DEVICE +SCREEN OR WHILE USING OUR SERVICES. CERTAIN CONDITIONS MAY INDUCE A +PREVIOUSLY UNKNOWN CONDITION OR UNDETECTED EPILEPTIC SYMPTOM IN USERS +WHO HAVE SHOWN NO HISTORY OF ANY PRIOR SEIZURE OR EPILEPSY. SHOULD +YOU, ANYONE YOU KNOW OR ANYONE IN YOUR FAMILY HAVE AN EPILEPTIC +CONDITION, PLEASE CONSULT A PHYSICIAN IF YOU EXPERIENCE ANY OF THE +FOLLOWING SYMPTOMS WHILE USING OUR SERVICES: DIZZINESS, ALTERED +VISION, EYE OR MUSCLE TWITCHES, LOSS OF AWARENESS, DISORIENTATION, +ANY INVOLUNTARY MOVEMENT, OR CONVULSIONS.

+

LIMITATION +OF LIABILITY

+

YOU +EXPLICITLY ACKNOWLEDGE, UNDERSTAND AND AGREE THAT EVER CO. LTD AND +OUR SUBSIDIARIES, AFFILIATES, OFFICERS, EMPLOYEES, AGENTS, PARTNERS +AND LICENSORS SHALL NOT BE LIABLE TO YOU FOR ANY PUNITIVE, INDIRECT, +INCIDENTAL, SPECIAL, CONSEQUENTIAL OR EXEMPLARY DAMAGES, INCLUDING, +BUT NOT LIMITED TO, DAMAGES WHICH MAY BE RELATED TO THE LOSS OF ANY +PROFITS, GOODWILL, USE, DATA AND/OR OTHER INTANGIBLE LOSSES, EVEN +THOUGH WE MAY HAVE BEEN ADVISED OF SUCH POSSIBILITY THAT SAID DAMAGES +MAY OCCUR, AND RESULT FROM:

+

a) THE +USE OR INABILITY TO USE OUR SERVICE; +

+

b) THE +COST OF PROCURING SUBSTITUTE GOODS AND SERVICES;

+

c) UNAUTHORIZED +ACCESS TO OR THE ALTERATION OF YOUR TRANSMISSIONS AND/OR DATA; +

+

d) STATEMENTS +OR CONDUCT OF ANY SUCH THIRD PARTY ON OUR SERVICE; +

+

e) AND +ANY OTHER MATTER WHICH MAY BE RELATED TO OUR SERVICE.

+

RELEASE

+

In +the event you have a dispute, you agree to release EVER CO. LTD (and +its officers, directors, employees, agents, parent subsidiaries, +affiliates, co-branders, partners and any other third parties) from +claims, demands and damages (actual and consequential) of every kind +and nature, known and unknown, suspected or unsuspected, disclosed +and undisclosed, arising out of or in any way connected to such +dispute.

+

SPECIAL +ADMONITION RELATED TO FINANCIAL MATTERS

+

Should +you intend to create or to join any service, receive or request any +such news, messages, alerts or other information from our Services +concerning companies, stock quotes, investments or securities, please +review the above Sections Warranty Disclaimers and Limitations of +Liability again. In addition, for this particular type of +information, the phrase "Let the investor beware" is +appropriate. EVER CO. LTD's content is provided primarily for +informational purposes, and no content that shall be provided or +included in our Services is intended for trading or investing +purposes. EVER CO. LTD and our licensors shall not be responsible or +liable for the accuracy, usefulness or availability of any +information transmitted and/or made available by way of our Services, +and shall not be responsible or liable for any trading and/or +investment decisions based on any such information.

+

EXCLUSION +AND LIMITATIONS

+

THERE +ARE SOME JURISDICTIONS WHICH DO NOT ALLOW THE EXCLUSION OF CERTAIN +WARRANTIES OR THE LIMITATION OF EXCLUSION OF LIABILITY FOR INCIDENTAL +OR CONSEQUENTIAL DAMAGES. THEREFORE, SOME OF THE ABOVE LIMITATIONS OF +SECTIONS WARRANTY DISCLAIMERS AND LIMITATION OF LIABILITY MAY NOT +APPLY TO YOU.

+

THIRD +PARTY BENEFICIARIES

+

You +herein acknowledge, understand and agree, unless otherwise expressly +provided in this TOS, that there shall be no third-party +beneficiaries to this agreement.

+

NOTICE

+

EVER +CO. LTD may furnish you with notices, including those with regards to +any changes to the TOS, including but not limited to email, regular +mail, MMS or SMS, text messaging, postings on our website Services, +or other reasonable means currently known or any which may be herein +after developed. Any such notices may not be received if you violate +any aspects of the TOS by accessing our Services in an unauthorized +manner. Your acceptance of this TOS constitutes your agreement that +you are deemed to have received any and all notices that would have +been delivered had you accessed our Services in an authorized manner.

+

You +may receive text messages (SMS / MMS / Push Notifications) from or on +behalf of EVER as a part of the EVER Services at the cell phone +number(s) provided by you to EVER, and you consent to receiving such +text messages.

+

TRADEMARK +INFORMATION

+

You +herein acknowledge, understand and agree that all of the EVER CO. LTD +trademarks, copyright, trade name, service marks, and other EVER CO. +LTD logos and any brand features, and/or product and service names +are trademarks and as such, are and shall remain the property of EVER +CO. LTD. You herein agree not to display and/or use in any manner the +EVER CO. LTD logo or marks without obtaining EVER CO. LTD's prior +written consent.

+

COPYRIGHT +OR INTELLECTUAL PROPERTY INFRINGEMENT CLAIMS NOTICE & PROCEDURES

+

EVER +CO. LTD will always respect the intellectual property of others, and +we ask that all of our users do the same. With regards to appropriate +circumstances and at its sole discretion, EVER CO. LTD may disable +and/or terminate the accounts of any user who violates our TOS and/or +infringes the rights of others. If you feel that your work has been +duplicated in such a way that would constitute copyright +infringement, or if you believe your intellectual property rights +have been otherwise violated, you should provide to us the following +information:

+

a) The +electronic or the physical signature of the individual that is +authorized on behalf of the owner of the copyright or other +intellectual property interest;

+

b) A +description of the copyrighted work or other intellectual property +that you believe has been infringed upon;

+

c) A +description of the location of the site which you allege has been +infringing upon your work;

+

d) Your +physical address, telephone number, and email address;

+

e) A +statement, in which you state that the alleged and disputed use of +your work is not authorized by the copyright owner, its agents or the +law;

+

f) And +finally, a statement, made under penalty of perjury, that the +aforementioned information in your notice is truthful and accurate, +and that you are the copyright or intellectual property owner, +representative or agent authorized to act on the copyright or +intellectual property owner's behalf.

+

The +EVER CO. LTD Agent for notice of claims of copyright or other +intellectual property infringement can be contacted as follows:

+

Mailing +Address:

+

EVER +CO. LTD +

+

Attn: +Copyright Agent

+

HaAtsmaut +38/3

+

Ashdod +77452,

+

Israel

+

Email: + ever@ever.co

+

CLOSED +CAPTIONING

+

BE +IT KNOWN, that EVER CO. LTD complies with all applicable Federal +Communications Commission rules and regulations regarding the closed +captioning of video content. For more information, please visit our +website at ever.co.

+

GENERAL +INFORMATION

+

ENTIRE +AGREEMENT +

+

This +TOS constitutes the entire agreement between you and EVER CO. LTD and +shall govern the use of our Services, superseding any prior version +of this TOS between you and us with respect to EVER CO. LTD Services. +You may also be subject to additional terms and conditions that may +apply when you use or purchase certain other EVER CO. LTD Services, +affiliate Services, third-party content or third-party software.

+

CHOICE +OF LAW AND FORUM

+

It +is at the mutual agreement of both you and EVER CO. LTD with regard +to the TOS that the relationship between the parties shall be +governed by the laws of Israel without regard to its conflict of law +provisions and that any and all claims, causes of action and/or +disputes, arising out of or relating to the TOS, or the relationship +between you and EVER CO. LTD, shall be filed within the courts having +jurisdiction within the Israel. You and EVER CO. LTD agree to submit +to the jurisdiction of the courts as previously mentioned, and agree +to waive any and all objections to the exercise of jurisdiction over +the parties by such courts and to venue in such courts.

+

WAIVER +AND SEVERABILITY OF TERMS

+

At +any time, should EVER CO. LTD fail to exercise or enforce any right +or provision of the TOS, such failure shall not constitute a waiver +of such right or provision. If any provision of this TOS is found by +a court of competent jurisdiction to be invalid, the parties +nevertheless agree that the court should endeavor to give effect to +the parties' intentions as reflected in the provision, and the other +provisions of the TOS remain in full force and effect.

+

NO +RIGHT OF SURVIVORSHIP NON-TRANSFERABILITY

+

You +acknowledge, understand and agree that your account is +non-transferable and any rights to your ID and/or contents within +your account shall terminate upon your death. Upon receipt of a copy +of a death certificate, your account may be terminated and all +contents therein permanently deleted.

+

STATUTE +OF LIMITATIONS

+

You +acknowledge, understand and agree that regardless of any statute or +law to the contrary, any claim or action arising out of or related to +the use of our Services or the TOS must be filed within 1 year(s) +after said claim or cause of action arose or shall be forever barred.

+

VIOLATIONS

+

Please +report any and all violations of this TOS to EVER CO. LTD as follows:

+

Mailing +Address:

+

EVER +CO. LTD

+

HaAtsmaut +38/3

+

Ashdod +77452,

+

Israel

+

Email: + ever@ever.co

+

CHANGES

+

We +reserve the right, at our sole discretion, to modify or replace these +Terms at any time. If a revision is material we will try to provide +at least 30 (change this) days' notice prior to any new terms taking +effect. +

+

What +constitutes a material change will be determined at our sole +discretion.

+

By +continuing to access or use our Service after those revisions become +effective, you agree to be bound by the revised terms.

+

If +you do not agree to the new terms, please stop using the Service

+

CONTACT +US

+

If +you have any questions about these Terms, please contact us:

+

Mailing +Address:

+

EVER +CO. LTD

+

HaAtsmaut +38/3

+

Ashdod +77452,

+

Israel

+

Email: + ever@ever.co

+

+

+

+

DIGITAL +MILLENNIUM COPYRIGHT ACT (DMCA) INFRINGEMENT NOTICE AND POLICY

+

Notifications

+

If +you believe that content available on or through the EVER Apps and / +or Website infringes one or more of your copyrights, please +immediately notify our Copyright Agent by mail, email or faxed notice +(“Notification”) providing the information described below, which +Notification is pursuant to DMCA 17 U.S.C. § 512(c)(3). A copy of +your Notification will be sent to the person who posted or stored the +material addressed in the Notification. Please be advised that +pursuant to federal law you may be held liable for damages if you +make material misrepresentations in a Notification. Thus, if you are +not sure that content located on or linked to by the Apps and / or +Website infringes your copyright, you should consider first +contacting an attorney. Company has a policy of terminating repeat +infringers in appropriate circumstances.

+

All +Notifications should include the following:

+

A +physical or electronic signature of a person authorized to act on +behalf of the owner of an exclusive right that is allegedly +infringed.

+

Identification +of the copyrighted work claimed to have been infringed, or, if +multiple copyrighted works at a single online website are covered by +a single notification, a representative list of such works at that +website.

+

Identification +of the material that is claimed to be infringing or to be the subject +of infringing activity and that is to be removed or access to which +is to be disabled, and information reasonably sufficient to permit +Company to locate the material.

+

Information +reasonably sufficient to permit the Company to contact the +complaining party, such as an address, telephone number, and, if +available, an electronic mail address at which the complaining party +may be contacted.

+

A +statement that the complaining party has a good faith belief that use +of the material in the manner complained of is not authorized by the +copyright owner, its agent, or the law.

+

A +statement that the information in the notification is accurate, and +under penalty of perjury, that the complaining party is authorized to +act on behalf of the owner of an exclusive right that is allegedly +infringed.

+

Notifications +should be sent to our Copyright Agent as follows:

+

Copyright +Agent

+

EVER +CO. LTD

+

HaAtsmaut +38/3

+

Ashdod +77452,

+

Israel

+

Email: + dmca@ever.co

+



+

+ +
+ +
diff --git a/packages/core/res/templates/terms_of_use/ru-RU.hbs b/packages/core/res/templates/terms_of_use/ru-RU.hbs new file mode 100644 index 0000000..dad1e9f --- /dev/null +++ b/packages/core/res/templates/terms_of_use/ru-RU.hbs @@ -0,0 +1,1242 @@ + + +
+ +

Условия Использования

+ +
+ +

Thank +you for your interest in the Ever application for your mobile device +(the "App") provided to you by EVER CO. LTD ("EVER" +"us" or "we"), and our web site at Ever.co (the +"Site"), as well as all related web sites, networks, +downloadable software, and other services provided by us and on which +a link to this Terms of Service is displayed (collectively, together +with the Apps and Site, our "Service").

+

PLEASE +READ THE FOLLOWING TERMS OF SERVICE AGREEMENT CAREFULLY. +

+

These +Terms of Service (these "Terms", “Agreement”, “Terms +of Service”, "TOS"), including the Privacy Policy +incorporated into these Terms by reference and any other applicable +policies and guidelines, as may be updated from time to time, govern +your use of the Service. These Terms constitute a legal agreement +between you and EVER. In order to use the Service you must agree to +these Terms. +

+

BY +DOWNLOADING, INSTALLING, OR OTHERWISE BY ACCESSING OR USING OUR +SITES, MOBILE APPLICATIONS AND OUR SERVICES, YOU HEREBY AGREE THAT +YOU HAVE READ, UNDERSTOOD, AND AGREE TO BE TO BE BOUND BY THE TERMS +AND ALL TERMS INCORPORATED HEREIN BY REFERENCE. +

+

IT +IS THE RESPONSIBILITY OF YOU, THE USER, CUSTOMER, OR PROSPECTIVE +CUSTOMER TO READ THE TERMS AND CONDITIONS BEFORE PROCEEDING TO USE +THIS SITE OR OUR MOBILE APPS.

+

IF +YOU DO NOT EXPRESSLY AGREE TO ALL OF THE TERMS AND CONDITIONS, THEN +PLEASE DO NOT ACCESS OR USE OUR SITES, MOBILE APPS OR OUR SERVICES.

+

THIS +TERMS OF SERVICE AGREEMENT IS EFFECTIVE AS OF 11/06/2016.

+

ACCEPTANCE +OF TERMS

+

The +following Terms of Service Agreement is a legally binding agreement +that shall govern the relationship with our users and others which +may interact or interface with EVER CO. LTD, also known as EVER, +located at HaAtsmaut 38/3, Ashdod 77452, Israel and our subsidiaries +and affiliates, in association with the use of the EVER website and +mobile apps, which includes Ever.co, (the "Site") and its +Services, EVER Applications (the “Apps”) and its Services. 

+

Your +use of the EVER Services may be subject to separate third party terms +of service and fees, including without limitation your mobile network +operator’s terms of service and fees, including fees charged for +data usage, messaging and overage, which are your sole +responsibility.

+

DESCRIPTION +OF SERVICES OFFERED

+

EVER +provides a mobile apps and web-based technology platform (the +"Platform", "Marketplace") that connects +consumers (the "Customers"), retail stores, and restaurants +(together referenced as "Merchants"), with third party +independent contractors and third party independent contractors under +agreement with EVER and certain of EVER's affiliates (together +referenced as "Couriers"). The Platform provides order +placement services for Customers and allows them to view, discuss, +and place orders for food, beverages and goods, and pick-up and / or +delivery services in connection therewith, with Merchants and +Restaurants. The platform allows Carriers to facilitate on-demand +same day delivery services for food, merchandise, goods and +beverages. +

+

Through +the Platform consumers may request that food, merchandise, goods or +beverages, be delivered to them from particular retail locations, +stores or restaurants (Merchants). Couriers can access the Platform +and receive delivery opportunities. +

+

EVER +IS NOT A RETAIL STORE, MERCHANT OF FOOD OR BEVERAGES, RESTAURANT, +FOOD PICKUP AND / OR DELIVERY SERVICE, MERCHANDISE DELIVERY SERVICE, +OR FOOD PREPARATION ENTITY. YOU ACKNOWLEDGE THAT EVER DOES NOT +PROVIDE TRANSPORTATION OR LOGISTICS SERVICES OR FUNCTION AS A +TRANSPORTATION CARRIER AND EVER DOES NOT PROVIDE DELIVERY SERVICES +AND DOES NOT CONTROL THE RESTAURANTS OR THE PRODUCTION OF ANY FOOD OR +BEVERAGES, OR ANY PICK-UP OR DELIVERY SERVICES THEREWITH. INDEPENDENT +CONTRACTORS (EACH A "COURIER") OFFER DELIVERY SERVICES +THROUGH USE OF THE SERVICE. EVER OFFERS INFORMATION AND A METHOD TO +OBTAIN COURIER SERVICES, BUT DOES NOT AND DOES NOT INTEND TO PROVIDE +COURIER SERVICES OR ACT IN ANY WAY AS A COURIER, AND HAS NO +RESPONSIBILITY OR LIABILITY FOR ANY COURIER.

+

The +Merchants available through our Services operate indepenently of the +EVER. The EVER will not assess the suitability, legality or ability +of any Carrier or Merchant. The EVER is not responsible for the +Merchants food preparation or safety and does not verify their +compliance with applicable laws or regulations. The EVER has no +responsibility or liability for acts by any third-party Merchant or +Carrier, other than as stated herein. EVER, including the Website, +Apps and the EVER Services, does not in any way independently verify +the credentials or representations of any of the Restaurants, the +ingredients or the quality of any their products or services, or any +Restaurant’s compliance with applicable laws.

+

Customers +using the EVER Services must make themselves comfortable through the +information provided by the Restaurants on the Platform, by +contacting the Restaurants directly, or through such other means or +methods as they may deem appropriate, as to the quality and +reliability and quality of the Restaurants and the Restaurants’ +compliance with applicable laws. The EVER, including the Website, +Apps and the EVER Services, does not in any way guarantee the quality +of any Restaurant or any food or beverage, or any pickup- up or +delivery service in connection therewith, or any compliance thereof +with applicable laws. In addition, a Restaurant may represent certain +standards with respect to their food preparation (or other services), +such as “organic,” “kosher,” “macrobiotic” or allergen- +specific standards such as “nut-free,” “gluten-free,” or +“lactose-free”; EVER does not investigate or verify any such +representations. EVER shall not be liable or responsible for any food +or beverages, or any other services, offered by the Restaurants or +any errors or misrepresentations made by them (including on the +Website, Apps and through the EVER Services).

+

Unless +otherwise agreed by EVER in a separate written agreement with you, +the Services are made available solely for your personal, +noncommercial use. +

+

As +provided in greater detail in these Terms, you agree and acknowledge +these material Terms:

+

- +The App is licensed, not sold to you, and you may use the Service +only as set forth in these Terms;

+

- +Your use of the Service may be subject to separate third party terms +of service and fees, including without limitation your mobile network +operator’s ("Carrier") terms of service and fees, +including fees charged for data usage and overage, which are your +sole responsibility;

+

- +You consent to the collection, sharing, and use of your personally +identifiable information in accordance with EVER Privacy Policy;

+

- +The Service is provided "as is" without warranties of any +kind, and EVER liability to you is limited; +

+

SCOPE +OF APPS LICENSE

+

Our +Apps are licensed, not sold, to you for use only under the terms of +this license. EVER reserves all rights not expressly granted to you. +Subject to your complete and ongoing compliance with these Terms, +EVER hereby grants you a personal, limited, revocable, +non-transferable license to use our Apps on compatible devices that +you own or control, solely for your non-commercial use.

+

You +may not modify, alter, reproduce, distribute or make the Apps +available over a network where it could be used by multiple devices +at the same time. You may not rent, lease, lend, sell, redistribute +or sublicense the Apps. If you breach these license restrictions, or +otherwise exceed the scope of the license granted in these Terms, you +may be subject to prosecution and legal damages, as well as liability +for infringement of intellectual property rights. These Terms will +govern any updates provided to you by EVER that replace and/or +supplement the original Apps, unless the upgrade is accompanied by a +separate license in which case the terms of that license will govern.

+

ACCOUNT +ACCESS

+

You +must be at least eighteen (18) years old to use the Service. By +agreeing to these Terms, you represent and warrant to us:

+

- +That you are at least eighteen (18) years old;

+

- +That you have not previously been suspended or removed from the +Service; and

+

- +That your registration and your use of the Service is in compliance +with any and all applicable laws and regulations.

+

If +you are using the Service on behalf of an entity, organization, or +company, you represent and warrant that you have the authority to +bind that organization to these Terms and you agree to be bound by +these Terms on behalf of that organization.

+

ACCOUNTS +AND REGISTRATION

+

Any +and all visitors to our site and users of our apps, despite whether +they are registered or not, shall be deemed as "users" of +the herein contained Services provided for the purpose of this TOS.

+

Once +an individual register's for our Services, through the process of +creating an account, the user shall then be considered a "member."

+

To +register and become a "member" of the Site or Apps, you +must be at least 18 years of age to enter into and form a legally +binding contract. In addition, you must be in good standing and not +an individual that has been previously barred from receiving EVER's +Services under the laws and statutes of the Israel, United States or +other applicable jurisdiction.

+

When +you register, EVER may collect information such as your first and +last name, e-mail address, birth date, gender, mailing address, +occupation, industry and personal interests. +

+

You +can edit your account information at any time. Once you register with +EVER and sign in to our Services, you are no longer anonymous to us.

+

Furthermore, +the registering party hereby acknowledges, understands and agrees to:

+

a) furnish +factual, correct, current and complete information with regards to +yourself as may be requested by the data registration process, and

+

b) maintain +and promptly update your registration and profile information in an +effort to maintain accuracy and completeness at all times.

+

If +anyone knowingly provides any information of a false, untrue, +inaccurate or incomplete nature, EVER CO. LTD will have sufficient +grounds and rights to suspend or terminate the member in violation of +this aspect of the Agreement, and as such refuse any and all current +or future use of EVER CO. LTD Services, or any portion thereof. +

+

The +user and/or member acknowledges and agrees that the Services provided +and made available through our website and applications, which may +include some mobile applications and that those applications may be +made available on various social media networking sites and numerous +other platforms and downloadable programs, are the sole property of +EVER CO. LTD. At its discretion, EVER CO. LTD may offer additional +website Services and/or products, or update, modify or revise any +current content and Services, and this Agreement shall apply to any +and all additional Services and/or products and any and all updated, +modified or revised Services unless otherwise stipulated. EVER CO. +LTD does hereby reserve the right to cancel and cease offering any of +the aforementioned Services and/or products. You, as the end user +and/or member, acknowledge, accept and agree that EVER CO. LTD shall +not be held liable for any such updates, modifications, revisions, +suspensions or discontinuance of any of our Services and/or products. +Your continued use of the Services provided, after such posting of +any updates, changes, and/or modifications shall constitute your +acceptance of such updates, changes and/or modifications, and as +such, frequent review of this Agreement and any and all applicable +terms and policies should be made by you to ensure you are aware of +all terms and policies currently in effect. Should you not agree to +the updated, revised or modified terms, you must stop using the +provided Services forthwith.

+

Furthermore, +the user and/or member understands, acknowledges and agrees that the +Services offered shall be provided "AS IS" and as such EVER +CO. LTD shall not assume any responsibility or obligation for the +timeliness, missed delivery, deletion and/or any failure to store +user content, communication or personalization settings. +

+

PURCHASES

+

If +you wish to purchase any product, food or service made available +through the Service ("Purchase"), you may be asked to +supply certain information relevant to your Purchase including, +without limitation, your first and last name, birth date, gender, +credit card information, physical and mailing addresses, e-mail +address and phone number or other contact information.

+

When +you place an order through the Apps, WebSite and EVER Services, you +will be given a choice of payment options, which may include via +Apple Pay, Credit Card, PayPal®, Android Pay, and direct payment to +the applicable Merchant or Restaurant. If you pay for your purchase +via Apple Pay, Credit Card, PayPal, or Android Pay, we will ask for a +valid Apple Pay account, Credit Card, PayPal account, or Android Pay +account, as applicable, which will be billed through the EVER +Services, for the purchase price of the applicable order, and “EVER +CO. LTD” or your name will be the name that appears on the Apple +Pay, Credit Card, PayPal, or Android Pay statement, as applicable. As +stated above, however, EVER, including the Apps, Website and the EVER +Services, is not and shall not in any manner be considered the seller +of any of the food, beverages, and services ordered.

+

PRICES

+

You +understand that: (a) the prices for food and goods displayed through +the Services may differ from the prices offered or published by +Merchants for the same menu items, food or goods and/or from prices +available at other third-party websites and that such prices may not +be the lowest prices at which the items are sold; (b) the Company has +no obligation to itemize its costs, profits or margins when +publishing such prices; and (c) the Company reserves the right to +change such prices at any time, at its discretion. You are liable for +all transaction taxes on the Services provided under this Agreement +(other than taxes based on the Company’s income). Payment will be +processed by the Company, using the preferred payment method +designated in your account.

+

GENERAL +PAYMENT TERMS

+

Certain +features of the Service, including the placing of orders using the +Service, may require you to pay fees. Before you pay any fees, you +will have an opportunity to review and accept an estimate of the fees +that you will be charged. All fees are non-refundable. This no refund +policy applies at all times regardless of your decision to terminate +your usage, our decision to terminate your usage and any disruption +caused to our Service for any reason whatsoever.

+

EVER, +at its sole discretion, may offer credits or refunds on a +case-by-case basis; all credit and/or refund requests must be made +within fifteen (15) days after the delivery was completed. EVER may +change the delivery or other fees for any feature of the Service, +including by adding fees, on a going-forward basis at any time.

+

EVER +will charge the payment method you specify at the time of purchase or +as otherwise specified by you in your account information. EVER +reserves the right to determine final prevailing pricing. (Please +note the pricing information published on the website may not reflect +the prevailing pricing.) EVER, at its sole discretion, may make +promotional offers with different features and different rates to any +of our customers. These promotional offers, unless made to you, shall +have no bearing whatsoever on your offer or contract. We may change +the fees for the Service at any time as we deem necessary for our +business. +

+

PAYMENT +AUTHORIZATION

+

You +authorize EVER to charge all sums for orders that you make and +services you select to the payment method specified in your account. +When you order on EVER, a temporary pre-authorization hold might be +placed on your payment card to verify that the card is valid and has +credit available for your intended purchase. Once your order is +complete, you will be charged the final order total and the +pre-authorization hold will be lifted within 24-72 business hours, +depending on your bank. The pre-authorization hold remains even if an +order is canceled by a customer or by EVER and may remain on your +card for up to 5 business days.

+

EVER +also may place an initial temporary pre-authorization hold on each +new payment method you add to your account.

+

PAYMENT +WHEN CUSTOMER NOT AVAILABLE

+

EVER +reserves the right to charge a customer the full order amount if that +customer is not at the designated delivery location when the courier +arrives to complete the delivery.

+

PRIVACY +POLICY

+

Every +member's registration data and various other personal information are +strictly protected by the EVER CO. LTD Online Privacy Policy (see the +full Privacy Policy at https://ever.co/privacy/site.html). +As a member, you herein consent to the collection and use of the +information provided, including the transfer of information within +the United States, Israel and/or other countries for storage, +processing or use by EVER CO. LTD and/or our subsidiaries and +affiliates. +

+

MEMBER +ACCOUNT, USERNAME, PASSWORD AND SECURITY

+

When +you register, you will be asked to provide a password. You are solely +responsible for maintaining the confidentiality of your account and +password, and any password for Facebook, Google, or other third party +login. You accept responsibility for all activities that occur under +your account. If you have reason to believe that your account is no +longer secure, you must immediately notify us by our sending email to +security@ever.co

+

When +you set up an account, you are the sole authorized user of your +account. You shall be responsible for maintaining the secrecy and +confidentiality of your password and for all activities that +transpire on or within your account. It is your responsibility for +any act or omission of any user(s) that access your account +information that, if undertaken by you, would be deemed a violation +of the TOS. It shall be your responsibility to notify EVER CO. LTD +immediately if you notice any unauthorized access or use of your +account or password or any other breach of security. EVER CO. LTD +shall not be held liable for any loss and/or damage arising from any +failure to comply with this term and/or condition of the TOS. +

+

CONDUCT

+

As +a user or member of the Site or Apps, you herein acknowledge, +understand and agree that all information, text, software, data, +photographs, music, video, messages, tags or any other content, +whether it is publicly or privately posted and/or transmitted, is the +expressed sole responsibility of the individual from whom the content +originated. In short, this means that you are solely responsible for +any and all content posted, uploaded, emailed, transmitted or +otherwise made available by way of the EVER Services, and as such, we +do not guarantee the accuracy, integrity or quality of such content. +It is expressly understood that by use of our Services, you may be +exposed to content including, but not limited to, any errors or +omissions in any content posted, and/or any loss or damage of any +kind incurred as a result of the use of any content posted, emailed, +transmitted or otherwise made available by EVER. +

+

Furthermore, +you herein agree not to make use of EVER CO. LTD's Services for the +purpose of:

+

a) uploading, +posting, emailing, transmitting, or otherwise making available any +content that shall be deemed unlawful, harmful, threatening, abusive, +harassing, tortious, defamatory, vulgar, obscene, libelous, or +invasive of another's privacy or which is hateful, and/or racially, +ethnically, or otherwise objectionable;

+

b) causing +harm to minors in any manner whatsoever;

+

c) impersonating +any individual or entity, including, but not limited to, any EVER +officials, forum leaders, guides or hosts or falsely stating or +otherwise misrepresenting any affiliation with an individual or +entity;

+

d) forging +captions, headings or titles or otherwise offering any content that +you personally have no right to pursuant to any law nor having any +contractual or fiduciary relationship with;

+

e) uploading, +posting, emailing, transmitting or otherwise offering any such +content that may infringe upon any patent, copyright, trademark, or +any other proprietary or intellectual rights of any other party;

+

f) uploading, +posting, emailing, transmitting or otherwise offering any content +that you do not personally have any right to offer pursuant to any +law or in accordance with any contractual or fiduciary relationship;

+

g) uploading, +posting, emailing, transmitting, or otherwise offering any +unsolicited or unauthorized advertising, promotional flyers, "junk +mail," "spam," or any other form of solicitation, +except in any such areas that may have been designated for such +purpose;

+

h) uploading, +posting, emailing, transmitting, or otherwise offering any source +that may contain a software virus or other computer code, any files +and/or programs which have been designed to interfere, destroy and/or +limit the operation of any computer software, hardware, or +telecommunication equipment;

+

i) disrupting +the normal flow of communication, or otherwise acting in any manner +that would negatively affect other users' ability to participate in +any real time interactions;

+

j) interfering +with or disrupting any EVER CO. LTD Services, servers and/or networks +that may be connected or related to our website, including, but not +limited to, the use of any device software and/or routine to bypass +the robot exclusion headers;

+

k) intentionally +or unintentionally violating any local, state, federal, national or +international law, including, but not limited to, rules, guidelines, +and/or regulations decreed by the U.S. Securities and Exchange +Commission, in addition to any rules of any nation or other +securities exchange, that would include without limitation, the New +York Stock Exchange, the American Stock Exchange, or the NASDAQ, and +any regulations having the force of law;

+

l) providing +informational support or resources, concealing and/or disguising the +character, location, and or source to any organization delegated by +the United States government as a "foreign terrorist +organization" in accordance to Section 219 of the Immigration +Nationality Act;

+

m) "stalking" +or with the intent to otherwise harass another individual; and/or +

+

n) collecting +or storing of any personal data relating to any other member or user +in connection with the prohibited conduct and/or activities which +have been set forth in the aforementioned paragraphs.

+

EVER +CO. LTD herein reserves the right to pre-screen, refuse and/or delete +any content currently available through our Services. In addition, we +reserve the right to remove and/or delete any such content that would +violate the TOS or which would otherwise be considered offensive to +other visitors, users and/or members.  

+

EVER +CO. LTD herein reserves the right to access, preserve and/or disclose +member account information and/or content if it is requested to do so +by law or in good faith belief that any such action is deemed +reasonably necessary for: 

+

a) compliance +with any legal process; 

+

b) enforcement +of the TOS; 

+

c) responding +to any claim that therein contained content is in violation of the +rights of any third party; 

+

d) responding +to requests for customer service; or 

+

e) protecting +the rights, property or the personal safety of EVER CO. LTD, its +visitors, users and members, including the general public.

+

EVER +CO. LTD herein reserves the right to include the use of security +components that may permit digital information or material to be +protected, and that such use of information and/or material is +subject to usage guidelines and regulations established by EVER CO. +LTD or any other content providers supplying content services to EVER +CO. LTD. You are hereby prohibited from making any attempt to +override or circumvent any of the embedded usage rules in our +Services. Furthermore, unauthorized reproduction, publication, +distribution, or exhibition of any information or materials supplied +by our Services, despite whether done so in whole or in part, is +expressly prohibited.

+

INTERSTATE +COMMUNICATION

+

Upon +registration, you hereby acknowledge that by using Site and Apps to +send electronic communications, which would include, but are not +limited to, email, searches, instant messages, uploading of files, +photos and/or videos, you will be causing communications to be sent +through our computer network. Therefore, through your use, and thus +your agreement with this TOS, you are acknowledging that the use of +this Service shall result in interstate transmissions.

+

CAUTIONS +FOR GLOBAL USE AND EXPORT AND IMPORT COMPLIANCE

+

Due +to the global nature of the internet, through the use of our network +you hereby agree to comply with all local rules relating to online +conduct and that which is considered acceptable Content. Uploading, +posting and/or transferring of software, technology and other +technical data may be subject to the export and import laws of the +United States and possibly other countries. Through the use of our +network, you thus agree to comply with all applicable export and +import laws, statutes and regulations, including, but not limited to, +the Export Administration Regulations +(http://www.access.gpo.gov/bis/ear/ear_data.html), +as well as the sanctions control program of the United States +(http://www.treasury.gov/resource-center/sanctions/Programs/Pages/Programs.aspx). +Furthermore, you state and pledge that you:

+

a) are +not on the list of prohibited individuals which may be identified on +any government export exclusion report +(http://www.bis.doc.gov/complianceandenforcement/liststocheck.htm) +nor a member of any other government which may be part of an +export-prohibited country identified in applicable export and import +laws and regulations;

+

b) agree +not to transfer any software, technology or any other technical data +through the use of our network Services to any export-prohibited +country; +

+

c) agree +not to use our website network Services for any military, nuclear, +missile, chemical or biological weaponry end uses that would be a +violation of the Israel, U.S. export laws; and

+

d) agree +not to post, transfer nor upload any software, technology or any +other technical data which would be in violation of the Israel, U.S. +or other applicable export and/or import laws.

+

CONTENT +PLACED OR MADE AVAILABLE FOR COMPANY SERVICES

+

EVER +CO. LTD shall not lay claim to ownership of any content submitted by +any visitor, member, or user, nor make such content available for +inclusion on our website Services. Therefore, you hereby grant and +allow for EVER CO. LTD the below listed worldwide, royalty-free and +non-exclusive licenses, as applicable:

+

a) The +content submitted or made available for inclusion on the publicly +accessible areas of EVER CO. LTD's sites, the license provided to +permit to use, distribute, reproduce, modify, adapt, publicly perform +and/or publicly display said Content on our network Services is for +the sole purpose of providing and promoting the specific area to +which this content was placed and/or made available for viewing. This +license shall be available so long as you are a member of EVER CO. +LTD's sites, and shall terminate at such time when you elect to +discontinue your membership.

+

b) Photos, +audio, video and/or graphics submitted or made available for +inclusion on the publicly accessible areas of EVER CO. LTD's sites, +the license provided to permit to use, distribute, reproduce, modify, +adapt, publicly perform and/or publicly display said Content on our +network Services are for the sole purpose of providing and promoting +the specific area in which this content was placed and/or made +available for viewing. This license shall be available so long as you +are a member of EVER CO. LTD's sites and shall terminate at such time +when you elect to discontinue your membership.

+

c) For +any other content submitted or made available for inclusion on the +publicly accessible areas of EVER CO. LTD's sites, the continuous, +binding and completely sub-licensable license which is meant to +permit to use, distribute, reproduce, modify, adapt, publish, +translate, publicly perform and/or publicly display said content, +whether in whole or in part, and the incorporation of any such +Content into other works in any arrangement or medium current used or +later developed.

+

Those +areas which may be deemed "publicly accessible" areas of +EVER CO. LTD's sites are those such areas of our network properties +which are meant to be available to the general public, and which +would include message boards and groups that are openly available to +both users and members. However, those areas which are not open to +the public, and thus available to members only, would include our +mail system and instant messaging.

+

CONTRIBUTIONS +TO COMPANY WEBSITE

+

EVER +CO. LTD provides an area for our users and members to contribute +feedback to our website. When you submit ideas, documents, +suggestions and/or proposals ("Contributions") to our site, +you acknowledge and agree that: +

+

a) your +contributions do not contain any type of confidential or proprietary +information;

+

b) EVER +shall not be liable or under any obligation to ensure or maintain +confidentiality, expressed or implied, related to any Contributions; +

+

c) EVER +shall be entitled to make use of and/or disclose any such +Contributions in any such manner as they may see fit; +

+

d) the +contributor's Contributions shall automatically become the sole +property of EVER; and

+

e) EVER +is under no obligation to either compensate or provide any form of +reimbursement in any manner or nature.

+

INDEMNITY

+

All +users and/or members herein agree to insure and hold EVER CO. LTD, +our subsidiaries, affiliates, agents, employees, officers, partners +and/or licensors blameless or not liable for any claim or demand, +which may include, but is not limited to, reasonable attorney fees +made by any third party which may arise from any content a member or +user of our site may submit, post, modify, transmit or otherwise make +available through our Services, the use of EVER Services or your +connection with these Services, your violations of the Terms of +Service and/or your violation of any such rights of another person.

+

COMMERCIAL +REUSE OF SERVICES

+

The +member or user herein agrees not to replicate, duplicate, copy, +trade, sell, resell nor exploit for any commercial reason any part, +use of, or access to EVER's sites or Apps.

+

USE +AND STORAGE GENERAL PRACTICES

+

You +herein acknowledge that EVER CO. LTD may set up any such practices +and/or limits regarding the use of our Services, without limitation +of the maximum number of days that any email, message posting or any +other uploaded content shall be retained by EVER CO. LTD, nor the +maximum number of email messages that may be sent and/or received by +any member, the maximum volume or size of any email message that may +be sent from or may be received by an account on our Service, the +maximum disk space allowable that shall be allocated on EVER CO. +LTD's servers on the member's behalf, and/or the maximum number of +times and/or duration that any member may access our Services in a +given period of time. In addition, you also agree that EVER CO. LTD +has absolutely no responsibility or liability for the removal or +failure to maintain storage of any messages and/or other +communications or content maintained or transmitted by our Services. +You also herein acknowledge that we reserve the right to delete or +remove any account that is no longer active for an extended period of +time. Furthermore, EVER CO. LTD shall reserve the right to modify, +alter and/or update these general practices and limits at our +discretion.

+

Any +messenger service, which may include any web-based versions, shall +allow you and the individuals with whom you communicate with the +ability to save your conversations in your account located on EVER +CO. LTD's servers. In this manner, you will be able to access and +search your message history from any computer with internet access. +You also acknowledge that others have the option to use and save +conversations with you in their own personal account on Ever.co. It +is your agreement to this TOS which establishes your consent to allow +EVER CO. LTD to store any and all communications on its servers.

+

THIRD-PARTY +INTERACTIONS +

+

1. +Third-Party Providers

+

During +use of the Service, you may purchase goods and services from +third-party merchants through the Service. Any such activity, and any +disputes, terms, conditions, warranties or representations associated +with that activity, is solely between you and the applicable third +party. EVER and its licensors shall have no liability, obligation or +responsibility for any purchase or transaction between you and any +third-party provider. In no event shall EVER or its licensors be +responsible for any content, products, services or other materials on +or available from third-party sites or third-party providers. Certain +third-party providers of goods and/or services may require your +agreement to additional or different terms and conditions prior to +your use of or access to such goods or services, and EVER disclaims +any and all responsibility or liability arising from such agreements +between you and a third party.

+

2. +Couriers

+

You +may engage third-party Couriers through the Service to provide +delivery services to you and may interact with those Couriers. Any +interactions or disputes between you and a Courier are solely between +you and that Courier. EVER and its licensors shall have no liability, +obligation or responsibility for any interaction between you and any +Courier.

+

3. +Third-Party Advertising

+

The +Service may contain third-party advertising and marketing. By +agreeing to these Terms you agree to receive such advertising and +marketing.

+

4. +App Stores

+

You +acknowledge and agree that the availability of the EVER Apps is +dependent on the third party from which you received the Application +license, e.g., the Apple iPhone or Android app stores (“App +Store”). You acknowledge that this Agreement is between you and the +EVER and not with the App Store. The EVER, not the App Store, is +solely responsible for the Software and the Services, including the +Applications and the Services, the content thereof, maintenance, +support services and warranty therefor, and addressing any claims +relating thereto (e.g., product liability, legal compliance or +intellectual property infringement). In order to use the +Applications, you must have access to a wireless network, and you +agree to pay all fees associated with such access. You also agree to +pay all fees (if any) charged by the App Store in connection with the +Application or the Services. You agree to comply with, and your +license to use the Applications is conditioned upon your compliance +with, all applicable third-party terms of agreement (e.g., the App +Store’s terms and policies) when using the Applications. You +acknowledge that the App Store (and its subsidiaries) are intended +third-party beneficiaries of the Agreement and have the right to +enforce them.

+

TRANSACTIONS +INVOLVING ALCOHOL +

+

You +may have the option to request delivery of alcohol products in some +locations and from certain Merchants. If you receive your delivery in +the Israel or United States, you agree that you will only order +alcohol products if you are 21 years of age or older. If you receive +your delivery in another country, you agree that you will only order +alcohol products if you are of legal age to purchase alcohol products +in the relevant jurisdiction. You also agree that, upon delivery of +alcohol products, you will provide valid government-issued +identification proving your age to the Carrier delivering the alcohol +products and that the recipient will not be intoxicated when +receiving delivery of such products. If you order alcohol products, +you understand and acknowledge that neither the EVER nor the Carrier +can accept your order of alcohol products, and the order will only be +delivered if the Merchant accepts your order. The Carrier reserves +the right to refuse delivery if you are not 21 years of older, if you +cannot provide a valid government issued ID, if the name on your ID +does not match the name on your order, or you are visibly +intoxicated. If the Carrier is unable to complete the delivery of +alcohol products for one or more of these reasons, you are subject to +a full refund.

+

MODIFICATIONS

+

EVER +CO. LTD shall reserve the right at any time it may deem fit, to +modify, alter and or discontinue, whether temporarily or permanently, +our service, or any part thereof, with or without prior notice. In +addition, we shall not be held liable to you or to any third party +for any such alteration, modification, suspension and/or +discontinuance of our Services, or any part thereof. 

+

TERMINATION

+

We +may terminate or suspend access to our Service immediately, without +prior notice or liability, for any reason whatsoever, including +without limitation if you breach the Terms.

+

As +a member of Ever.co or Mobile Apps, you may cancel or terminate your +account, associated email address and/or access to our Services by +submitting a cancellation or termination request to ever@ever.co.

+

As +a member, you agree that EVER CO. LTD may, without any prior written +notice, immediately suspend, terminate, discontinue and/or limit your +account, any email associated with your account, and access to any of +our Services.

+

The +cause for such termination, discontinuance, suspension and/or +limitation of access shall include, but is not limited to:

+

a) any +breach or violation of our TOS or any other incorporated agreement, +regulation and/or guideline;

+

b) by +way of requests from law enforcement or any other governmental +agencies;

+

c) the +discontinuance, alteration and/or material modification to our +Services, or any part thereof;

+

d) unexpected +technical or security issues and/or problems;

+

e) any +extended periods of inactivity;

+

f) any +engagement by you in any fraudulent or illegal activities; and/or

+

g) the +nonpayment of any associated fees that may be owed by you in +connection with your Ever.co and Apps account Services.

+

Furthermore, +you herein agree that any and all terminations, suspensions, +discontinuances, and or limitations of access for cause shall be made +at our sole discretion and that we shall not be liable to you or any +other third party with regards to the termination of your account, +associated email address and/or access to any of our Services.

+

The +termination of your account with Ever.co and Apps shall include any +and/or all of the following:

+

a) the +removal of any access to all or part of the Services offered within +Ever.co and Apps;

+

b) the +deletion of your password and any and all related information, files, +and any such content that may be associated with or inside your +account, or any part thereof; and

+

c) the +barring of any further use of all or part of our Services.

+

All +provisions of the Terms which by their nature should survive +termination shall survive termination, including, without limitation, +ownership provisions, warranty disclaimers, indemnity and limitations +of liability.

+

ADVERTISER

+

Any +correspondence or business dealings with, or the participation in any +promotions of, advertisers located on or through our Services, which +may include the payment and/or delivery of such related goods and/or +Services, and any such other term, condition, warranty and/or +representation associated with such dealings, are and shall be solely +between you and any such advertiser. Moreover, you herein agree that +EVER CO. LTD shall not be held responsible or liable for any loss or +damage of any nature or manner incurred as a direct result of any +such dealings or as a result of the presence of such advertisers on +our website.

+

LINKS

+

Either +EVER CO. LTD or any third parties may provide links to other websites +and/or resources. Thus, you acknowledge and agree that we are not +responsible for the availability of any such external sites or +resources, and as such, we do not endorse nor are we responsible or +liable for any content, products, advertising or any other materials, +on or available from such third party sites or resources. +Furthermore, you acknowledge and agree that EVER CO. LTD shall not be +responsible or liable, directly or indirectly, for any such damage or +loss which may be a result of, caused or allegedly to be caused by or +in connection with the use of or the reliance on any such content, +goods or Services made available on or through any such site or +resource.

+

PROPRIETARY +RIGHTS

+

You +do hereby acknowledge and agree that EVER CO. LTD's Services and any +essential software that may be used in connection with our Services +("Software") shall contain proprietary and confidential +material that is protected by applicable intellectual property rights +and other laws. Furthermore, you herein acknowledge and agree that +any Content which may be contained in any advertisements or +information presented by and through our Services or by advertisers +is protected by copyrights, trademarks, patents or other proprietary +rights and laws. Therefore, except for that which is expressly +permitted by applicable law or as authorized by EVER CO. LTD or such +applicable licensor, you agree not to alter, modify, lease, rent, +loan, sell, distribute, transmit, broadcast, publicly perform and/or +created any plagiaristic works which are based on EVER CO. LTD +Services (e.g. Content or Software), in whole or part.

+

EVER +CO. LTD herein has granted you personal, non-transferable and +non-exclusive rights and/or license to make use of the object code or +our Software on a single computer or mobile device, as long as you do +not, and shall not, allow any third party to duplicate, alter, +modify, create or plagiarize work from, reverse engineer, reverse +assemble or otherwise make an attempt to locate or discern any source +code, sell, assign, sublicense, grant a security interest in and/or +otherwise transfer any such right in the Software. Furthermore, you +do herein agree not to alter or change the Software in any manner, +nature or form, and as such, not to use any modified versions of the +Software, including and without limitation, for the purpose of +obtaining unauthorized access to our Services. Lastly, you also agree +not to access or attempt to access our Services through any means +other than through the interface which is provided by EVER CO. LTD +for use in accessing our Services.

+

WARRANTY +DISCLAIMERS

+

YOU +HEREIN EXPRESSLY ACKNOWLEDGE AND AGREE THAT:

+

a) THE +USE OF EVER CO. LTD SERVICES AND SOFTWARE ARE AT THE SOLE RISK BY +YOU. OUR SERVICES AND SOFTWARE SHALL BE PROVIDED ON AN "AS IS" +AND/OR "AS AVAILABLE" BASIS. EVER CO. LTD AND OUR +SUBSIDIARIES, AFFILIATES, OFFICERS, EMPLOYEES, AGENTS, PARTNERS AND +LICENSORS EXPRESSLY DISCLAIM ANY AND ALL WARRANTIES OF ANY KIND +WHETHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO ANY +IMPLIED WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NON-INFRINGEMENT.

+

b) EVER +CO. LTD AND OUR SUBSIDIARIES, OFFICERS, EMPLOYEES, AGENTS, PARTNERS +AND LICENSORS MAKE NO SUCH WARRANTIES THAT (i) EVER CO. LTD SERVICES +OR SOFTWARE WILL MEET YOUR REQUIREMENTS; (ii) EVER CO. LTD SERVICES +OR SOFTWARE SHALL BE UNINTERRUPTED, TIMELY, SECURE OR ERROR-FREE; +(iii) THAT SUCH RESULTS WHICH MAY BE OBTAINED FROM THE USE OF THE +EVER CO. LTD SERVICES OR SOFTWARE WILL BE ACCURATE OR RELIABLE; (iv) +QUALITY OF ANY PRODUCTS, SERVICES, ANY INFORMATION OR OTHER MATERIAL +WHICH MAY BE PURCHASED OR OBTAINED BY YOU THROUGH OUR SERVICES OR +SOFTWARE WILL MEET YOUR EXPECTATIONS; AND (v) THAT ANY SUCH ERRORS +CONTAINED IN THE SOFTWARE SHALL BE CORRECTED.

+

c) ANY +INFORMATION OR MATERIAL DOWNLOADED OR OTHERWISE OBTAINED BY WAY OF +EVER CO. LTD SERVICES OR SOFTWARE SHALL BE ACCESSED BY YOUR SOLE +DISCRETION AND SOLE RISK, AND AS SUCH YOU SHALL BE SOLELY RESPONSIBLE +FOR AND HEREBY WAIVE ANY AND ALL CLAIMS AND CAUSES OF ACTION WITH +RESPECT TO ANY DAMAGE TO YOUR COMPUTER AND/OR INTERNET ACCESS, +DOWNLOADING AND/OR DISPLAYING, OR FOR ANY LOSS OF DATA THAT COULD +RESULT FROM THE DOWNLOAD OF ANY SUCH INFORMATION OR MATERIAL.

+

d) NO +ADVICE AND/OR INFORMATION, DESPITE WHETHER WRITTEN OR ORAL, THAT MAY +BE OBTAINED BY YOU FROM EVER CO. LTD OR BY WAY OF OR FROM OUR +SERVICES OR SOFTWARE SHALL CREATE ANY WARRANTY NOT EXPRESSLY STATED +IN THE TOS.

+

e) A +SMALL PERCENTAGE OF SOME USERS MAY EXPERIENCE SOME DEGREE OF +EPILEPTIC SEIZURE WHEN EXPOSED TO CERTAIN LIGHT PATTERNS OR +BACKGROUNDS THAT MAY BE CONTAINED ON A COMPUTER OR MOBILE DEVICE +SCREEN OR WHILE USING OUR SERVICES. CERTAIN CONDITIONS MAY INDUCE A +PREVIOUSLY UNKNOWN CONDITION OR UNDETECTED EPILEPTIC SYMPTOM IN USERS +WHO HAVE SHOWN NO HISTORY OF ANY PRIOR SEIZURE OR EPILEPSY. SHOULD +YOU, ANYONE YOU KNOW OR ANYONE IN YOUR FAMILY HAVE AN EPILEPTIC +CONDITION, PLEASE CONSULT A PHYSICIAN IF YOU EXPERIENCE ANY OF THE +FOLLOWING SYMPTOMS WHILE USING OUR SERVICES: DIZZINESS, ALTERED +VISION, EYE OR MUSCLE TWITCHES, LOSS OF AWARENESS, DISORIENTATION, +ANY INVOLUNTARY MOVEMENT, OR CONVULSIONS.

+

LIMITATION +OF LIABILITY

+

YOU +EXPLICITLY ACKNOWLEDGE, UNDERSTAND AND AGREE THAT EVER CO. LTD AND +OUR SUBSIDIARIES, AFFILIATES, OFFICERS, EMPLOYEES, AGENTS, PARTNERS +AND LICENSORS SHALL NOT BE LIABLE TO YOU FOR ANY PUNITIVE, INDIRECT, +INCIDENTAL, SPECIAL, CONSEQUENTIAL OR EXEMPLARY DAMAGES, INCLUDING, +BUT NOT LIMITED TO, DAMAGES WHICH MAY BE RELATED TO THE LOSS OF ANY +PROFITS, GOODWILL, USE, DATA AND/OR OTHER INTANGIBLE LOSSES, EVEN +THOUGH WE MAY HAVE BEEN ADVISED OF SUCH POSSIBILITY THAT SAID DAMAGES +MAY OCCUR, AND RESULT FROM:

+

a) THE +USE OR INABILITY TO USE OUR SERVICE; +

+

b) THE +COST OF PROCURING SUBSTITUTE GOODS AND SERVICES;

+

c) UNAUTHORIZED +ACCESS TO OR THE ALTERATION OF YOUR TRANSMISSIONS AND/OR DATA; +

+

d) STATEMENTS +OR CONDUCT OF ANY SUCH THIRD PARTY ON OUR SERVICE; +

+

e) AND +ANY OTHER MATTER WHICH MAY BE RELATED TO OUR SERVICE.

+

RELEASE

+

In +the event you have a dispute, you agree to release EVER CO. LTD (and +its officers, directors, employees, agents, parent subsidiaries, +affiliates, co-branders, partners and any other third parties) from +claims, demands and damages (actual and consequential) of every kind +and nature, known and unknown, suspected or unsuspected, disclosed +and undisclosed, arising out of or in any way connected to such +dispute.

+

SPECIAL +ADMONITION RELATED TO FINANCIAL MATTERS

+

Should +you intend to create or to join any service, receive or request any +such news, messages, alerts or other information from our Services +concerning companies, stock quotes, investments or securities, please +review the above Sections Warranty Disclaimers and Limitations of +Liability again. In addition, for this particular type of +information, the phrase "Let the investor beware" is +appropriate. EVER CO. LTD's content is provided primarily for +informational purposes, and no content that shall be provided or +included in our Services is intended for trading or investing +purposes. EVER CO. LTD and our licensors shall not be responsible or +liable for the accuracy, usefulness or availability of any +information transmitted and/or made available by way of our Services, +and shall not be responsible or liable for any trading and/or +investment decisions based on any such information.

+

EXCLUSION +AND LIMITATIONS

+

THERE +ARE SOME JURISDICTIONS WHICH DO NOT ALLOW THE EXCLUSION OF CERTAIN +WARRANTIES OR THE LIMITATION OF EXCLUSION OF LIABILITY FOR INCIDENTAL +OR CONSEQUENTIAL DAMAGES. THEREFORE, SOME OF THE ABOVE LIMITATIONS OF +SECTIONS WARRANTY DISCLAIMERS AND LIMITATION OF LIABILITY MAY NOT +APPLY TO YOU.

+

THIRD +PARTY BENEFICIARIES

+

You +herein acknowledge, understand and agree, unless otherwise expressly +provided in this TOS, that there shall be no third-party +beneficiaries to this agreement.

+

NOTICE

+

EVER +CO. LTD may furnish you with notices, including those with regards to +any changes to the TOS, including but not limited to email, regular +mail, MMS or SMS, text messaging, postings on our website Services, +or other reasonable means currently known or any which may be herein +after developed. Any such notices may not be received if you violate +any aspects of the TOS by accessing our Services in an unauthorized +manner. Your acceptance of this TOS constitutes your agreement that +you are deemed to have received any and all notices that would have +been delivered had you accessed our Services in an authorized manner.

+

You +may receive text messages (SMS / MMS / Push Notifications) from or on +behalf of EVER as a part of the EVER Services at the cell phone +number(s) provided by you to EVER, and you consent to receiving such +text messages.

+

TRADEMARK +INFORMATION

+

You +herein acknowledge, understand and agree that all of the EVER CO. LTD +trademarks, copyright, trade name, service marks, and other EVER CO. +LTD logos and any brand features, and/or product and service names +are trademarks and as such, are and shall remain the property of EVER +CO. LTD. You herein agree not to display and/or use in any manner the +EVER CO. LTD logo or marks without obtaining EVER CO. LTD's prior +written consent.

+

COPYRIGHT +OR INTELLECTUAL PROPERTY INFRINGEMENT CLAIMS NOTICE & PROCEDURES

+

EVER +CO. LTD will always respect the intellectual property of others, and +we ask that all of our users do the same. With regards to appropriate +circumstances and at its sole discretion, EVER CO. LTD may disable +and/or terminate the accounts of any user who violates our TOS and/or +infringes the rights of others. If you feel that your work has been +duplicated in such a way that would constitute copyright +infringement, or if you believe your intellectual property rights +have been otherwise violated, you should provide to us the following +information:

+

a) The +electronic or the physical signature of the individual that is +authorized on behalf of the owner of the copyright or other +intellectual property interest;

+

b) A +description of the copyrighted work or other intellectual property +that you believe has been infringed upon;

+

c) A +description of the location of the site which you allege has been +infringing upon your work;

+

d) Your +physical address, telephone number, and email address;

+

e) A +statement, in which you state that the alleged and disputed use of +your work is not authorized by the copyright owner, its agents or the +law;

+

f) And +finally, a statement, made under penalty of perjury, that the +aforementioned information in your notice is truthful and accurate, +and that you are the copyright or intellectual property owner, +representative or agent authorized to act on the copyright or +intellectual property owner's behalf.

+

The +EVER CO. LTD Agent for notice of claims of copyright or other +intellectual property infringement can be contacted as follows:

+

Mailing +Address:

+

EVER +CO. LTD +

+

Attn: +Copyright Agent

+

HaAtsmaut +38/3

+

Ashdod +77452,

+

Israel

+

Email: + ever@ever.co

+

CLOSED +CAPTIONING

+

BE +IT KNOWN, that EVER CO. LTD complies with all applicable Federal +Communications Commission rules and regulations regarding the closed +captioning of video content. For more information, please visit our +website at ever.co.

+

GENERAL +INFORMATION

+

ENTIRE +AGREEMENT +

+

This +TOS constitutes the entire agreement between you and EVER CO. LTD and +shall govern the use of our Services, superseding any prior version +of this TOS between you and us with respect to EVER CO. LTD Services. +You may also be subject to additional terms and conditions that may +apply when you use or purchase certain other EVER CO. LTD Services, +affiliate Services, third-party content or third-party software.

+

CHOICE +OF LAW AND FORUM

+

It +is at the mutual agreement of both you and EVER CO. LTD with regard +to the TOS that the relationship between the parties shall be +governed by the laws of Israel without regard to its conflict of law +provisions and that any and all claims, causes of action and/or +disputes, arising out of or relating to the TOS, or the relationship +between you and EVER CO. LTD, shall be filed within the courts having +jurisdiction within the Israel. You and EVER CO. LTD agree to submit +to the jurisdiction of the courts as previously mentioned, and agree +to waive any and all objections to the exercise of jurisdiction over +the parties by such courts and to venue in such courts.

+

WAIVER +AND SEVERABILITY OF TERMS

+

At +any time, should EVER CO. LTD fail to exercise or enforce any right +or provision of the TOS, such failure shall not constitute a waiver +of such right or provision. If any provision of this TOS is found by +a court of competent jurisdiction to be invalid, the parties +nevertheless agree that the court should endeavor to give effect to +the parties' intentions as reflected in the provision, and the other +provisions of the TOS remain in full force and effect.

+

NO +RIGHT OF SURVIVORSHIP NON-TRANSFERABILITY

+

You +acknowledge, understand and agree that your account is +non-transferable and any rights to your ID and/or contents within +your account shall terminate upon your death. Upon receipt of a copy +of a death certificate, your account may be terminated and all +contents therein permanently deleted.

+

STATUTE +OF LIMITATIONS

+

You +acknowledge, understand and agree that regardless of any statute or +law to the contrary, any claim or action arising out of or related to +the use of our Services or the TOS must be filed within 1 year(s) +after said claim or cause of action arose or shall be forever barred.

+

VIOLATIONS

+

Please +report any and all violations of this TOS to EVER CO. LTD as follows:

+

Mailing +Address:

+

EVER +CO. LTD

+

HaAtsmaut +38/3

+

Ashdod +77452,

+

Israel

+

Email: + ever@ever.co

+

CHANGES

+

We +reserve the right, at our sole discretion, to modify or replace these +Terms at any time. If a revision is material we will try to provide +at least 30 (change this) days' notice prior to any new terms taking +effect. +

+

What +constitutes a material change will be determined at our sole +discretion.

+

By +continuing to access or use our Service after those revisions become +effective, you agree to be bound by the revised terms.

+

If +you do not agree to the new terms, please stop using the Service

+

CONTACT +US

+

If +you have any questions about these Terms, please contact us:

+

Mailing +Address:

+

EVER +CO. LTD

+

HaAtsmaut +38/3

+

Ashdod +77452,

+

Israel

+

Email: + ever@ever.co

+

+

+

+

DIGITAL +MILLENNIUM COPYRIGHT ACT (DMCA) INFRINGEMENT NOTICE AND POLICY

+

Notifications

+

If +you believe that content available on or through the EVER Apps and / +or Website infringes one or more of your copyrights, please +immediately notify our Copyright Agent by mail, email or faxed notice +(“Notification”) providing the information described below, which +Notification is pursuant to DMCA 17 U.S.C. § 512(c)(3). A copy of +your Notification will be sent to the person who posted or stored the +material addressed in the Notification. Please be advised that +pursuant to federal law you may be held liable for damages if you +make material misrepresentations in a Notification. Thus, if you are +not sure that content located on or linked to by the Apps and / or +Website infringes your copyright, you should consider first +contacting an attorney. Company has a policy of terminating repeat +infringers in appropriate circumstances.

+

All +Notifications should include the following:

+

A +physical or electronic signature of a person authorized to act on +behalf of the owner of an exclusive right that is allegedly +infringed.

+

Identification +of the copyrighted work claimed to have been infringed, or, if +multiple copyrighted works at a single online website are covered by +a single notification, a representative list of such works at that +website.

+

Identification +of the material that is claimed to be infringing or to be the subject +of infringing activity and that is to be removed or access to which +is to be disabled, and information reasonably sufficient to permit +Company to locate the material.

+

Information +reasonably sufficient to permit the Company to contact the +complaining party, such as an address, telephone number, and, if +available, an electronic mail address at which the complaining party +may be contacted.

+

A +statement that the complaining party has a good faith belief that use +of the material in the manner complained of is not authorized by the +copyright owner, its agent, or the law.

+

A +statement that the information in the notification is accurate, and +under penalty of perjury, that the complaining party is authorized to +act on behalf of the owner of an exclusive right that is allegedly +infringed.

+

Notifications +should be sent to our Copyright Agent as follows:

+

Copyright +Agent

+

EVER +CO. LTD

+

HaAtsmaut +38/3

+

Ashdod +77452,

+

Israel

+

Email: + dmca@ever.co

+



+

+ +
+ +
diff --git a/packages/core/res/templates/user_products_placeholder.hbs b/packages/core/res/templates/user_products_placeholder.hbs new file mode 100644 index 0000000..f353b5b --- /dev/null +++ b/packages/core/res/templates/user_products_placeholder.hbs @@ -0,0 +1,18 @@ + + +
+ {{no_products_text}} +
\ No newline at end of file diff --git a/packages/core/res/templates/user_products_placeholder.json b/packages/core/res/templates/user_products_placeholder.json new file mode 100644 index 0000000..247039a --- /dev/null +++ b/packages/core/res/templates/user_products_placeholder.json @@ -0,0 +1,14 @@ +{ + "en-US": { + "no_products_text": "Nothing to buy for now." + }, + "he-IL": { + "no_products_text": "אין שום דבר לקנות כרגע." + }, + "ru-RU": { + "no_products_text": "Нет ничего для покупки." + }, + "bg-BG": { + "no_products_text": "Нищо за купуване за сега." + } +} diff --git a/packages/core/res/views/about_us_bg.hbs b/packages/core/res/views/about_us_bg.hbs new file mode 100644 index 0000000..b9194b6 --- /dev/null +++ b/packages/core/res/views/about_us_bg.hbs @@ -0,0 +1 @@ +{{> about_us/bg-BG }} \ No newline at end of file diff --git a/packages/core/res/views/about_us_en.hbs b/packages/core/res/views/about_us_en.hbs new file mode 100644 index 0000000..83057ba --- /dev/null +++ b/packages/core/res/views/about_us_en.hbs @@ -0,0 +1 @@ +{{> about_us/en-US }} diff --git a/packages/core/res/views/about_us_he.hbs b/packages/core/res/views/about_us_he.hbs new file mode 100644 index 0000000..fb9c146 --- /dev/null +++ b/packages/core/res/views/about_us_he.hbs @@ -0,0 +1 @@ +{{> about_us/he-IL }} diff --git a/packages/core/res/views/about_us_ru.hbs b/packages/core/res/views/about_us_ru.hbs new file mode 100644 index 0000000..10e96ee --- /dev/null +++ b/packages/core/res/views/about_us_ru.hbs @@ -0,0 +1 @@ +{{> about_us/ru-RU }} diff --git a/packages/core/res/views/index.hbs b/packages/core/res/views/index.hbs new file mode 100644 index 0000000..471b2ca --- /dev/null +++ b/packages/core/res/views/index.hbs @@ -0,0 +1,8 @@ +

Ever API

+ +
+

Copyright © 2016-present, Ever Co. LTD. All Rights Reserved.
+

+
EVER® is a registered trademark of Ever Co. LTD.
+ All other trademarks © to respective owners. We are in α (alpha). Please be nice!
+
\ No newline at end of file diff --git a/packages/core/res/views/layouts/main.hbs b/packages/core/res/views/layouts/main.hbs new file mode 100644 index 0000000..ccde2b7 --- /dev/null +++ b/packages/core/res/views/layouts/main.hbs @@ -0,0 +1,32 @@ + + + + + Ever API + + + +{{{body}}} + + diff --git a/packages/core/res/views/privacy_bg.hbs b/packages/core/res/views/privacy_bg.hbs new file mode 100644 index 0000000..d6bc4f6 --- /dev/null +++ b/packages/core/res/views/privacy_bg.hbs @@ -0,0 +1 @@ +{{> privacy/bg-BG }} diff --git a/packages/core/res/views/privacy_en.hbs b/packages/core/res/views/privacy_en.hbs new file mode 100644 index 0000000..5d501ff --- /dev/null +++ b/packages/core/res/views/privacy_en.hbs @@ -0,0 +1 @@ +{{> privacy/en-US }} diff --git a/packages/core/res/views/privacy_he.hbs b/packages/core/res/views/privacy_he.hbs new file mode 100644 index 0000000..09ab271 --- /dev/null +++ b/packages/core/res/views/privacy_he.hbs @@ -0,0 +1 @@ +{{> privacy/he-IL }} diff --git a/packages/core/res/views/privacy_ru.hbs b/packages/core/res/views/privacy_ru.hbs new file mode 100644 index 0000000..9c5763f --- /dev/null +++ b/packages/core/res/views/privacy_ru.hbs @@ -0,0 +1 @@ +{{> privacy/ru-RU }} diff --git a/packages/core/res/views/terms_of_use_bg.hbs b/packages/core/res/views/terms_of_use_bg.hbs new file mode 100644 index 0000000..97cfa50 --- /dev/null +++ b/packages/core/res/views/terms_of_use_bg.hbs @@ -0,0 +1 @@ +{{> terms_of_use/bg-BG }} diff --git a/packages/core/res/views/terms_of_use_en.hbs b/packages/core/res/views/terms_of_use_en.hbs new file mode 100644 index 0000000..bf244e7 --- /dev/null +++ b/packages/core/res/views/terms_of_use_en.hbs @@ -0,0 +1 @@ +{{> terms_of_use/en-US }} diff --git a/packages/core/res/views/terms_of_use_he.hbs b/packages/core/res/views/terms_of_use_he.hbs new file mode 100644 index 0000000..8f6c644 --- /dev/null +++ b/packages/core/res/views/terms_of_use_he.hbs @@ -0,0 +1 @@ +{{> terms_of_use/he-IL }} diff --git a/packages/core/res/views/terms_of_use_ru.hbs b/packages/core/res/views/terms_of_use_ru.hbs new file mode 100644 index 0000000..c43110f --- /dev/null +++ b/packages/core/res/views/terms_of_use_ru.hbs @@ -0,0 +1 @@ +{{> terms_of_use/ru-RU }} diff --git a/packages/core/src/@pyro/db-server/db-service.ts b/packages/core/src/@pyro/db-server/db-service.ts new file mode 100644 index 0000000..91b1a92 --- /dev/null +++ b/packages/core/src/@pyro/db-server/db-service.ts @@ -0,0 +1,559 @@ +import mongoose from 'mongoose'; +import * as _ from 'lodash'; +import Logger from 'bunyan'; +import { Observable, Subject, from, of } from 'rxjs'; +import { injectable } from 'inversify'; +import { ExistenceEvent, ExistenceEventType } from './existence'; +import { concat, exhaustMap, filter, map, share, tap } from 'rxjs/operators'; +import { v1 as uuid } from 'uuid'; +import { IDbService } from '@pyro/db-server/i-db-service'; +import { DBObject } from '@pyro/db'; +import { RawObject } from '@pyro/db/db-raw-object'; +import { CreateObject } from '@pyro/db/db-create-object'; +import { FindObject } from '@pyro/db/db-find-object'; +import { UpdateObject } from '@pyro/db/db-update-object'; +import { EntityService } from '@pyro/db-server/entity-service'; + +@injectable() +export abstract class DBService> + extends EntityService + implements IDbService { + public abstract readonly DBObject: { + new (arg: RawObject): T; + modelName: string; + }; + + public readonly existence: Subject>; + + protected abstract readonly log: Logger; + + constructor() { + super(); + this.existence = new Subject>(); + } + + get(id: T['id']): Observable { + const callId = uuid(); + + this.log.info({ objectId: id, callId }, '.get(id) called'); + + return from(this.getCurrent(id)).pipe( + concat( + this.existence.pipe( + filter((existenceEvent) => id === existenceEvent.id), + map((existenceEvent) => existenceEvent.value), + share() + ) + ), + tap({ + next: (obj) => { + this.log.info( + { + objectId: id, + object: obj, + callId, + }, + '.get(id) emitted next value' + ); + }, + error: (err) => { + this.log.error( + { + objectId: id, + err, + callId, + }, + '.get(id), emitted error!' + ); + }, + }) + ); + } + + async getCurrent(id: T['id']): Promise { + const callId = uuid(); + + this.log.info({ objectId: id, callId }, '.getCurrent(id) called'); + + const obj = await this.Model.findById(id).lean().exec(); + + return this.parse(obj as RawObject); + } + + getMultiple(ids: T['id'][]): Observable { + const callId = uuid(); + + this.log.info({ objectIds: ids, callId }, '.getMultiple(ids) called'); + + return of(null).pipe( + concat( + this.existence.pipe( + filter((event) => _.includes(ids, event.id)), + share() + ) + ), + exhaustMap(() => this.getCurrentMultiple(ids)), + tap({ + next: (objects) => { + this.log.info( + { objectIds: ids, objects, callId }, + '.getMultiple(ids) emitted next value' + ); + }, + error: (err) => { + this.log.error( + { objectIds: ids, err, callId }, + '.getMultiple(ids), emitted error!' + ); + }, + }) + ); + } + + async getCurrentMultiple(ids: T['id'][]): Promise { + const callId = uuid(); + + this.log.info( + { objectIds: ids, callId }, + '.getCurrentMultiple(ids) called' + ); + + const objs = await this.Model.find({ + _id: { + $in: _.map(ids, (id) => this.getObjectId(id)), + }, + }) + .lean() + .exec(); + + return _.map(objs as RawObject[], (obj) => this.parse(obj)); + } + + async create(createObject: CreateObject): Promise { + const callId = uuid(); + + this.log.info({ callId, createObject }, '.create(createObject) called'); + + let object; + + try { + const document = await this.Model.create(createObject); + + object = this.parse(document.toObject() as RawObject); + } catch (error) { + this.log.error( + { callId, createObject, error }, + '.create(createObject) thrown error!' + ); + throw error; + } + + this.existence.next({ + id: object.id, + value: object, + lastValue: null, + type: ExistenceEventType.Created, + }); + + this.log.info( + { callId, createObject, object }, + '.create(createObject) created object' + ); + + return object; + } + + /** + * Removes all records from the DB + * TODO: add Guards to avoid run on production + * + * @returns {Promise} + * @memberof DBService + */ + async removeAll(): Promise { + const callId = uuid(); + + this.log.info({ callId }, '.removeAll() called!'); + + try { + await this.Model.remove({}).exec(); + } catch (err) { + this.log.error({ callId, err }, '.removeAll() thrown error!'); + throw err; + } + + this.log.info({ callId }, '.removeAll() removed all!'); + } + + /** + * Removes record from the DB + * TODO: we should add another method which set IsDeteled = true and do not delete record from DB in most cases + * + * @param {T['id']} objectId + * @returns {Promise} + * @memberof DBService + */ + async remove(objectId: T['id']): Promise { + const callId = uuid(); + + this.log.info({ callId, objectId }, '.remove(objectId) called'); + + let lastValue: T | null; + + try { + const lastValueRaw = (await this.Model.findByIdAndRemove(objectId) + .lean() + .exec()) as RawObject; + + lastValue = this.parse(lastValueRaw); + } catch (err) { + this.log.error( + { callId, objectId, err }, + '.remove(objectId) thrown error!' + ); + + throw err; + } + + if (lastValue == null) { + throw new Error(".remove(objectId) error - Object don't exist"); + } else { + this.existence.next({ + id: objectId, + value: null, + lastValue, + type: ExistenceEventType.Removed, + }); + + this.log.info( + { callId, objectId, lastValue }, + '.remove(objectId) removed object' + ); + } + } + + /** + * Remove multiple records from DB (efficient way) + * + * @param {FindObject} conditions + * @returns {Promise} + * @memberof DBService + */ + async removeMultiple(conditions: FindObject): Promise { + const callId = uuid(); + + this.log.info( + { callId, conditions }, + '.removeMultiple(conditions) called' + ); + + let lastValues: T[]; + + try { + lastValues = await this.find(conditions); + + await this.Model.deleteMany({ + _id: { $in: lastValues.map((o) => this.getObjectId(o.id)) }, + }).exec(); + } catch (err) { + this.log.error( + { callId, conditions, err }, + '.removeMultiple(conditions) thrown error!' + ); + throw err; + } + + _.each(lastValues, (lastValue) => { + this.existence.next({ + id: lastValue.id, + lastValue, + value: null, + type: ExistenceEventType.Removed, + }); + }); + + this.log.info( + { + callId, + conditions, + lastValues, + }, + '.removeMultiple(conditions) removed objects' + ); + } + + /** + * Remove multiple records by Ids + * + * @param {Array} ids + * @returns {Promise} + * @memberof DBService + */ + async removeMultipleByIds(ids: T['id'][]): Promise { + this.Model.update( + { + _id: { $in: ids.map((id) => this.getObjectId(id)) }, + }, + { isDeleted: true }, + { multi: true } + ).exec(); + } + + async find(conditions: FindObject>): Promise { + const callId = uuid(); + + this.log.info({ callId, conditions }, '.find(conditions) called'); + + let results: T[]; + + try { + const documents = (await this.Model.find( + conditions == null ? {} : conditions + ) + .lean() + .exec()) as RawObject[]; + + results = _.map(documents, (obj) => this.parse(obj)); + } catch (err) { + this.log.error( + { callId, conditions, err }, + '.find(conditions) thrown error!' + ); + throw err; + } + + this.log.info( + { callId, conditions, results }, + '.find(conditions) found results' + ); + + return results; + } + + async findOne(conditions: FindObject>): Promise { + const callId = uuid(); + + this.log.info({ callId, conditions }, '.findOne(conditions) called'); + + let result: T; + + try { + const obj = (await this.Model.findOne(conditions) + .lean() + .exec()) as RawObject; + + result = this.parse(obj); + } catch (err) { + this.log.error( + { callId, conditions, err }, + '.findOne(conditions) thrown error!' + ); + throw err; + } + + this.log.info( + { callId, conditions, result }, + '.findOne(conditions) found result' + ); + + return result; + } + + async update(objectId: T['id'], updateObj: UpdateObject): Promise { + const callId = uuid(); + this.log.info( + { callId, objectId, updateObj }, + '.update(objectId, updateObj) called' + ); + + let beforeUpdateObject: T | null; + + let updatedObject: T; + + try { + beforeUpdateObject = await this.getCurrent(objectId); + + if (beforeUpdateObject != null) { + const obj = (await this.Model.findByIdAndUpdate( + objectId, + updateObj as any, + { new: true } + ) + .lean() + .exec()) as RawObject; + + updatedObject = this.parse(obj); + } else { + throw new Error( + `There is no such object with the id ${beforeUpdateObject}` + ); + } + } catch (err) { + this.log.error( + { callId, objectId, updateObj, err }, + '.update(objectId, updateObj) thrown error!' + ); + throw err; + } + + this.existence.next({ + id: objectId, + value: updatedObject, + lastValue: beforeUpdateObject, + type: ExistenceEventType.Updated, + }); + + this.log.info( + { + callId, + objectId, + updateObj, + updatedValue: updatedObject, + lastValue: beforeUpdateObject, + }, + '.update(objectId, updateObj) updated object' + ); + + return updatedObject; + } + + async updateMultiple( + findObj: FindObject, + updateObj: UpdateObject + ): Promise { + const callId = uuid(); + this.log.info( + { callId, findObj, updateObj }, + '.updateMultiple(findObj, updateObj) called' + ); + + let lastValues: T[]; + let updatedObjects: T[]; + + try { + lastValues = await this.find(findObj); + + await this.Model.updateMany(findObj, updateObj, { + new: true, + }).exec(); + + updatedObjects = await this.getCurrentMultiple( + _.map(lastValues, (value) => value.id) + ); + } catch (err) { + this.log.error( + { callId, findObj, updateObj, err }, + '.updateMultiple(findObj, updateObj) thrown error!' + ); + throw err; + } + + _.each(lastValues, (lastValue) => { + const newValue = _.find( + updatedObjects, + (obj) => obj.id === lastValue.id + ) as T; + + this.existence.next({ + id: lastValue.id, + lastValue, + value: newValue, + type: ExistenceEventType.Updated, + }); + }); + + this.log.info( + { + callId, + findObj, + updateObj, + lastValues, + updatedObjects, + }, + '.updateMultiple(objectId, updateObj) updated objects' + ); + + return updatedObjects; + } + + async updateMultipleByIds( + ids: T['id'][], + updateObj: UpdateObject + ): Promise { + const callId = uuid(); + + this.log.info( + { callId, ids, updateObj }, + '.updateMultipleByIds(ids, updateObj) called' + ); + + let updatedObjects: T[]; + + try { + updatedObjects = await this.updateMultiple( + { + // TODO: rewrite below + // tslint:disable-next-line:no-object-literal-type-assertion + _id: { + $in: _.map( + ids, + (id) => new mongoose.Types.ObjectId(id) + ), + } as FindObject, + }, + updateObj + ); + } catch (err) { + this.log.error( + { callId, ids, updateObj, err }, + '.updateMultipleByIds(ids, updateObj) thrown error!' + ); + throw err; + } + + this.log.info( + { + callId, + ids, + updateObj, + updatedObjects, + }, + '.updateMultipleByIds(ids, updateObj) updated objects' + ); + + return updatedObjects; + } + + async count(findObj: FindObject): Promise { + const callId = uuid(); + + this.log.info({ callId, findObj }, '.countDocuments(findObj) called'); + + let count: number; + + try { + count = (await this.Model.countDocuments(findObj).exec()) as number; + } catch (err) { + this.log.error( + { callId, findObj, err }, + '.countDocuments(findObj) thrown error!' + ); + throw err; + } + + this.log.info( + { callId, findObj, count }, + '.countDocuments(findObj) counted objects' + ); + + return count; + } + + /** + * Return all store documents with specified fields + * @param selectFields Example { field: 1 } = true, { field: 0 } = false. + */ + async findAll(selectFields: any = {}) { + return this.Model.find({}).select(selectFields).lean().exec(); + } +} diff --git a/packages/core/src/@pyro/db-server/entity-service.ts b/packages/core/src/@pyro/db-server/entity-service.ts new file mode 100644 index 0000000..de5db94 --- /dev/null +++ b/packages/core/src/@pyro/db-server/entity-service.ts @@ -0,0 +1,31 @@ +import { getModel } from '@pyro/db-server/model'; +import { DBObject } from '@pyro/db'; +import mongoose from 'mongoose'; +import { RawObject } from '@pyro/db/db-raw-object'; +import { injectable } from 'inversify'; + +@injectable() +export abstract class EntityService> { + public abstract readonly DBObject: { + new (arg: RawObject): T; + modelName: string; + }; + + get Model(): any { + return getModel & mongoose.Document>(this.DBObject); + } + + protected getObjectId(id: T['id']) { + return new mongoose.Types.ObjectId(id); + } + + protected parse(obj: RawObject): T; + + protected parse(obj: RawObject | null): T | null { + if (obj == null) { + return null; + } else { + return new this.DBObject(obj); + } + } +} diff --git a/packages/core/src/@pyro/db-server/existence.ts b/packages/core/src/@pyro/db-server/existence.ts new file mode 100644 index 0000000..8cbf828 --- /dev/null +++ b/packages/core/src/@pyro/db-server/existence.ts @@ -0,0 +1,40 @@ +type ExistenceEventTypeCreated = 'created'; +const ExistenceEventTypeCreated: ExistenceEventTypeCreated = 'created'; + +type ExistenceEventTypeUpdated = 'updated'; +const ExistenceEventTypeUpdated: ExistenceEventTypeUpdated = 'updated'; + +type ExistenceEventTypeRemoved = 'removed'; +const ExistenceEventTypeRemoved: ExistenceEventTypeRemoved = 'removed'; + +export type ExistenceEventType = + | ExistenceEventTypeCreated + | ExistenceEventTypeUpdated + | ExistenceEventTypeRemoved; + +// tslint:disable-next-line:no-namespace +export namespace ExistenceEventType { + export const Created = ExistenceEventTypeCreated; + export const Updated = ExistenceEventTypeUpdated; + export const Removed = ExistenceEventTypeRemoved; +} + +export type ExistenceEvent = + | { + id: string; + value: T; + lastValue: null; + type: ExistenceEventTypeCreated; + } + | { + id: string; + value: T; + lastValue: T; + type: ExistenceEventTypeUpdated; + } + | { + id: string; + value: null; + lastValue: T; + type: ExistenceEventTypeRemoved; + }; diff --git a/packages/core/src/@pyro/db-server/i-db-service.ts b/packages/core/src/@pyro/db-server/i-db-service.ts new file mode 100644 index 0000000..4d0cd2d --- /dev/null +++ b/packages/core/src/@pyro/db-server/i-db-service.ts @@ -0,0 +1,47 @@ +import { Subject, Observable } from 'rxjs'; +import { ExistenceEvent } from './existence'; +import mongoose from 'mongoose'; +import { DBObject } from '@pyro/db'; +import { RawObject } from '@pyro/db/db-raw-object'; +import { CreateObject } from '@pyro/db/db-create-object'; +import { FindObject } from '@pyro/db/db-find-object'; +import { UpdateObject } from '@pyro/db/db-update-object'; + +export interface IDbService> { + readonly DBObject: { new (arg: RawObject): T; modelName: string }; + + readonly Model: mongoose.Model & mongoose.Document>; + + readonly existence: Subject>; + + create(createObject: CreateObject): Promise; + + remove(objectId: string): Promise; + + removeMultiple(conditions: FindObject): Promise; + + removeAll(): Promise; + + get(objectId: string): Observable; + + getCurrent(objectId: string): Promise; + + getMultiple(ids: string[]): Observable; + + getCurrentMultiple(objectIds: string[]): Promise; + + find(conditions: FindObject): Promise; + + findOne(conditions: FindObject): Promise; + + update(objectId: string, updateObj: UpdateObject): Promise; + + updateMultiple(findObj: any, updateObj: UpdateObject): Promise; + + updateMultipleByIds( + ids: string[], + updateObj: UpdateObject + ): Promise; + + count(conditions: FindObject): Promise; +} diff --git a/packages/core/src/@pyro/db-server/index.ts b/packages/core/src/@pyro/db-server/index.ts new file mode 100644 index 0000000..7971120 --- /dev/null +++ b/packages/core/src/@pyro/db-server/index.ts @@ -0,0 +1,4 @@ +export { getModel } from './model'; +export { ExistenceEvent, ExistenceEventType } from './existence'; +export { DBService } from './db-service'; +export { IDbService } from './i-db-service'; diff --git a/packages/core/src/@pyro/db-server/model.ts b/packages/core/src/@pyro/db-server/model.ts new file mode 100644 index 0000000..e6d558c --- /dev/null +++ b/packages/core/src/@pyro/db-server/model.ts @@ -0,0 +1,30 @@ +import { getSchema } from '@pyro/db'; +import mongoose from 'mongoose'; + +export const modelMetadata = 'db:model'; + +export function getModel( + DBObject +): mongoose.Model { + let Model = Reflect.getMetadata(modelMetadata, DBObject); + + if (Model == null) { + + const modelName = DBObject.modelName; + + console.log('MongoDB Model Name: ' + modelName); + + const schema = getSchema(DBObject); + + try + { + Model = mongoose.model(modelName, schema); + Reflect.defineMetadata(modelMetadata, Model, DBObject); + } catch (e) { + console.log('MongoDB Schema with issues: ' + JSON.stringify(schema)); + console.error(e); + } + } + + return Model; +} diff --git a/packages/core/src/@pyro/io/connection-handler.ts b/packages/core/src/@pyro/io/connection-handler.ts new file mode 100644 index 0000000..7423f54 --- /dev/null +++ b/packages/core/src/@pyro/io/connection-handler.ts @@ -0,0 +1,84 @@ +import { getListeners, getRouterName, IRouter } from './router/router'; +import Logger from 'bunyan'; +import { IListenerHandler } from './listener/handler/handler'; +import { AsyncListenerHandler } from './listener/handler/async'; +import { ObservableListenerHandler } from './listener/handler/observable'; +import { getListenerType, Listener } from './listener/listener'; +import { ListenerType } from './listener/types'; +import { AsyncListener } from './listener/async'; +import { ObservableListener } from './listener/observable'; +import { Socket } from 'socket.io'; + +export class ConnectionHandler { + private readonly listeners: Array> = getListeners( + this.router + ); + + private readonly routerName: string = getRouterName(this.router); + + constructor( + private readonly socket: Socket, + private readonly router: IRouter, + private readonly log: Logger + ) {} + + handle() { + this.log.info( + { + socketId: this.socket.id, + listeners: this.listeners.map((listener) => listener.name), + routerName: this.routerName, + }, + `Socket connected! Starting listeners listening!` + ); + + try { + this.listeners.forEach((listener) => { + const handler = this.listenerHandlerFactory(listener); + handler.handle(); + }); + } catch (err) { + this.log.fatal("Couldn't start listeners listening!", { err }); + } + + this.socket.on('disconnect', () => { + this.onDisconnection(); + }); + } + + protected listenerHandlerFactory( + listener: Listener + ): IListenerHandler { + switch (getListenerType(listener)) { + case ListenerType.Async: + return new AsyncListenerHandler( + this.router, + listener as AsyncListener, + this.socket, + this.log + ); + case ListenerType.Observable: + return new ObservableListenerHandler( + this.router, + listener as ObservableListener, + this.socket, + this.log + ); + default: + throw new Error( + `Bad listener type! ${getListenerType(listener)}` + ); + } + } + + private onDisconnection() { + this.log.info( + { + socketId: this.socket.id, + listeners: this.listeners.map((listener) => listener.name), + routerName: this.routerName, + }, + `Socket disconnected!` + ); + } +} diff --git a/packages/core/src/@pyro/io/index.ts b/packages/core/src/@pyro/io/index.ts new file mode 100644 index 0000000..07c5bde --- /dev/null +++ b/packages/core/src/@pyro/io/index.ts @@ -0,0 +1,5 @@ +export { RoutersManager, IRoutersManager } from './routers-manager'; +export { asyncListener } from './listener/async'; +export { observableListener } from './listener/observable'; +export { serialization } from './listener/serialization'; +export { routerName, RouterSymbol } from './router/router'; diff --git a/packages/core/src/@pyro/io/listener/async.ts b/packages/core/src/@pyro/io/listener/async.ts new file mode 100644 index 0000000..eba0dc4 --- /dev/null +++ b/packages/core/src/@pyro/io/listener/async.ts @@ -0,0 +1,6 @@ +import { ListenerType } from './types'; +import { listenerOf } from './listener'; + +export type AsyncListener = (...args: any[]) => Promise; + +export const asyncListener = listenerOf(ListenerType.Async); diff --git a/packages/core/src/@pyro/io/listener/handler/async.ts b/packages/core/src/@pyro/io/listener/handler/async.ts new file mode 100644 index 0000000..b5504d6 --- /dev/null +++ b/packages/core/src/@pyro/io/listener/handler/async.ts @@ -0,0 +1,56 @@ +import { IRouter } from '../../router/router'; +import { IListenerHandler } from './handler'; +import { v1 as uuid } from 'uuid'; +import * as _ from 'lodash'; +import Logger from 'bunyan'; +import { BaseListenerHandler } from './base'; +import { AsyncListener } from '../async'; +import { Socket } from 'socket.io'; + +export class AsyncListenerHandler extends BaseListenerHandler + implements IListenerHandler { + constructor( + private readonly router: IRouter, + private readonly listener: AsyncListener, + private readonly socket: Socket, + private readonly log: Logger + ) { + super(router, listener, socket, log); + } + + async handleRequest(_args: any[]): Promise { + const callId = uuid(); + + const callback: (err: Error | null, data?) => void = _.last(_args); + + const args = this.serializer(_.initial(_args)); + + this.logCall(callId, args); + + try { + const data: T = await this.listener.apply(this.router, args); + + this.log.info( + { + ...this.baseLogDetails, + callId, + result: data, + }, + `Listener completed` + ); + + callback(null, data); + } catch (err) { + this.log.error( + { + ...this.baseLogDetails, + callId, + err, + }, + `Listener thrown error!` + ); + + callback(this.serializeError(err), null); + } + } +} diff --git a/packages/core/src/@pyro/io/listener/handler/base.ts b/packages/core/src/@pyro/io/listener/handler/base.ts new file mode 100644 index 0000000..e99d619 --- /dev/null +++ b/packages/core/src/@pyro/io/listener/handler/base.ts @@ -0,0 +1,70 @@ +import { IListenerHandler } from './handler'; +import { getRouterName, IRouter } from '../../router/router'; +import Logger from 'bunyan'; +import { getListenerType, Listener } from '../listener'; +import { ListenerType } from '../types'; +import { getListenerSerializer } from '../serialization'; +import * as _ from 'lodash'; +import getArgsNames from '@captemulation/get-parameter-names'; +import { Socket } from 'socket.io'; + +export abstract class BaseListenerHandler implements IListenerHandler { + protected readonly serializer: (args: any[]) => any[]; + + protected readonly routerName: string = getRouterName(this._router); + + protected readonly listenerType: ListenerType = getListenerType( + this._listener + ); + + protected readonly listenerArgumentsNames = getArgsNames(this._listener); + + constructor( + private readonly _router: IRouter, + private readonly _listener: Listener, + private readonly _socket: Socket, + private readonly _log: Logger + ) { + this.serializer = getListenerSerializer(_listener); + } + + handle(): void { + this._socket.on(this._listener.name, (...args: any[]) => { + this.handleRequest(args); + }); + } + + abstract handleRequest(args: any[]): void; + + protected baseLogDetails = { + socketId: this._socket.id, + listenerName: this._listener.name, + listenerType: this.listenerType, + routerName: this.routerName, + }; + + protected logCall(callId: string, args: any[]) { + this._log.info( + { + ...this.baseLogDetails, + callId, + args: _.zipObject(this.listenerArgumentsNames, args), + }, + `Listener called` + ); + } + + protected serializeError(_error) { + if (_error instanceof Error) { + const error: Error = _error; + + return { + __isError__: true, + name: error.name, + message: error.message, + }; + } else { + return _error; + } + } +} diff --git a/packages/core/src/@pyro/io/listener/handler/handler.ts b/packages/core/src/@pyro/io/listener/handler/handler.ts new file mode 100644 index 0000000..c905f52 --- /dev/null +++ b/packages/core/src/@pyro/io/listener/handler/handler.ts @@ -0,0 +1,3 @@ +export interface IListenerHandler { + handle(): void; +} diff --git a/packages/core/src/@pyro/io/listener/handler/observable.ts b/packages/core/src/@pyro/io/listener/handler/observable.ts new file mode 100644 index 0000000..e631588 --- /dev/null +++ b/packages/core/src/@pyro/io/listener/handler/observable.ts @@ -0,0 +1,75 @@ +import { IRouter } from '../../router/router'; +import { IListenerHandler } from './handler'; +import * as _ from 'lodash'; +import Logger from 'bunyan'; +import { BaseListenerHandler } from './base'; +import { ObservableListener } from '../observable'; +import { Socket } from 'socket.io'; + +export class ObservableListenerHandler extends BaseListenerHandler + implements IListenerHandler { + constructor( + private readonly router: IRouter, + private readonly listener: ObservableListener, + private readonly socket: Socket, + private readonly log: Logger + ) { + super(router, listener, socket, log); + } + + async handleRequest(_args: any[]): Promise { + const callId: string = _.last(_args); + + const args: any[] = this.serializer(_.initial(_args)); + + this.logCall(callId, args); + + const observable = Reflect.apply(this.listener, this.router, args); + + this.socket.on(`${callId}_subscribe`, (subscriptionId) => { + const subscription = observable.subscribe({ + next: (value) => { + this.log.info( + { + ...this.baseLogDetails, + callId, + value, + }, + `Listener emitted next value` + ); + this.socket.emit(`${subscriptionId}_next`, value); + }, + error: (err) => { + this.log.error( + { + ...this.baseLogDetails, + callId, + err, + }, + `Listener thrown error!` + ); + this.socket.emit( + `${subscriptionId}_error`, + this.serializeError(err) + ); + }, + complete: () => { + this.log.info( + { + ...this.baseLogDetails, + callId, + }, + `Listener completed` + ); + this.socket.emit(`_${subscriptionId}_complete`); + }, + }); + + this.socket.on(`${subscriptionId}_unsubscribe`, () => + subscription.unsubscribe() + ); + + this.socket.on('disconnect', () => subscription.unsubscribe()); + }); + } +} diff --git a/packages/core/src/@pyro/io/listener/listener.ts b/packages/core/src/@pyro/io/listener/listener.ts new file mode 100644 index 0000000..42ce4da --- /dev/null +++ b/packages/core/src/@pyro/io/listener/listener.ts @@ -0,0 +1,39 @@ +import { ObservableListener } from './observable'; +import { AsyncListener } from './async'; +import { ListenerType } from './types'; +import { IRouter } from '../router/router'; +import { routerMetadata } from '../router/metadata'; +import { listenerMetadata } from './metadata'; + +export type Listener = AsyncListener | ObservableListener; + +export const listenerOf = ( + listenerType: ListenerType +) => (): MethodDecorator => ( + router: IRouter, + propertyKey: string | symbol, + descriptor +) => { + const method = router[propertyKey]; + + if (!Reflect.hasMetadata(routerMetadata.listeners, router.constructor)) { + Reflect.defineMetadata( + routerMetadata.listeners, + [], + router.constructor + ); + } + + const listeners = Reflect.getMetadata( + routerMetadata.listeners, + router.constructor + ); + + listeners.push(method); + + Reflect.defineMetadata(listenerMetadata.type, listenerType, method); +}; + +export const getListenerType = (listener: Listener) => { + return Reflect.getMetadata(listenerMetadata.type, listener); +}; diff --git a/packages/core/src/@pyro/io/listener/metadata.ts b/packages/core/src/@pyro/io/listener/metadata.ts new file mode 100644 index 0000000..7bf5982 --- /dev/null +++ b/packages/core/src/@pyro/io/listener/metadata.ts @@ -0,0 +1,4 @@ +export const listenerMetadata = { + type: 'router:listener:type', + serializers: 'router:listener:serializers', +}; diff --git a/packages/core/src/@pyro/io/listener/observable.ts b/packages/core/src/@pyro/io/listener/observable.ts new file mode 100644 index 0000000..dffe277 --- /dev/null +++ b/packages/core/src/@pyro/io/listener/observable.ts @@ -0,0 +1,7 @@ +import { Observable } from 'rxjs'; +import { ListenerType } from './types'; +import { listenerOf } from './listener'; + +export type ObservableListener = (...args: any[]) => Observable; + +export const observableListener = listenerOf(ListenerType.Observable); diff --git a/packages/core/src/@pyro/io/listener/serialization.ts b/packages/core/src/@pyro/io/listener/serialization.ts new file mode 100644 index 0000000..b2e738e --- /dev/null +++ b/packages/core/src/@pyro/io/listener/serialization.ts @@ -0,0 +1,40 @@ +import { Listener } from './listener'; +import { listenerMetadata } from './metadata'; + +export type Serializer = (arg: T) => R; + +export const serialization = ( + serializeFunction: Serializer +): ParameterDecorator => ( + router: object, + propertyKey: string | symbol, + parameterIndex: number +) => { + const listener: Listener = router[propertyKey]; + + if (!Reflect.hasMetadata(listenerMetadata.serializers, listener)) { + Reflect.defineMetadata(listenerMetadata.serializers, [], listener); + } + + Reflect.getMetadata(listenerMetadata.serializers, listener)[ + parameterIndex + ] = serializeFunction; +}; + +export const getListenerSerializer = ( + listener: Listener +): ((args: any[]) => any[]) => { + const serializers: Array> = Reflect.getMetadata( + listenerMetadata.serializers, + listener + ); + if (serializers == null) { + return (args) => args; + } + + return (args: any[]) => { + return args.map((arg, i) => + serializers[i] != null ? serializers[i](arg) : arg + ); + }; +}; diff --git a/packages/core/src/@pyro/io/listener/types.ts b/packages/core/src/@pyro/io/listener/types.ts new file mode 100644 index 0000000..5f20b58 --- /dev/null +++ b/packages/core/src/@pyro/io/listener/types.ts @@ -0,0 +1,4 @@ +export enum ListenerType { + Async = 'async', + Observable = 'observable', +} diff --git a/packages/core/src/@pyro/io/router/handler.ts b/packages/core/src/@pyro/io/router/handler.ts new file mode 100644 index 0000000..1b29a6a --- /dev/null +++ b/packages/core/src/@pyro/io/router/handler.ts @@ -0,0 +1,45 @@ +import { getListeners, getRouterName, IRouter } from './router'; +import { ConnectionHandler } from '../connection-handler'; +import { Listener } from '../listener/listener'; +import Logger from 'bunyan'; +import { Server, Namespace } from 'socket.io'; + +export class RouterHandler { + private readonly routerName: string; + + private readonly listeners: Array>; + + constructor( + private readonly io: Server, + private readonly router: IRouter, + private readonly log: Logger + ) { + this.routerName = getRouterName(router); + this.listeners = getListeners(router); + } + + async listen(): Promise { + this.log.info(`Starting router listening`, { + routerName: this.routerName, + listeners: this.listeners + ? this.listeners.map((listener) => listener.name) + : null, + }); + + const routerNamespace: Namespace = this.io.of( + `/${this.routerName}` + ); + + // TODO: figure out why we needed below + // routerNamespace.setMaxListeners(0); + + routerNamespace.on('connection', (socket) => { + const connectionHandler = new ConnectionHandler( + socket, + this.router, + this.log + ); + connectionHandler.handle(); + }); + } +} diff --git a/packages/core/src/@pyro/io/router/metadata.ts b/packages/core/src/@pyro/io/router/metadata.ts new file mode 100644 index 0000000..ed3a5ea --- /dev/null +++ b/packages/core/src/@pyro/io/router/metadata.ts @@ -0,0 +1,5 @@ +// tslint:disable-next-line:no-namespace +export namespace routerMetadata { + export const name = 'router:name'; + export const listeners = 'router:listeners'; +} diff --git a/packages/core/src/@pyro/io/router/router.ts b/packages/core/src/@pyro/io/router/router.ts new file mode 100644 index 0000000..22ce6c5 --- /dev/null +++ b/packages/core/src/@pyro/io/router/router.ts @@ -0,0 +1,17 @@ +import { routerMetadata } from './metadata'; +import { Listener } from '../listener/listener'; + +export const RouterSymbol = Symbol('Router'); + +export interface IRouter {} + +export const routerName = (name: string): ClassDecorator => + Reflect.metadata(routerMetadata.name, name); + +export const getRouterName = (router: IRouter): string => { + return Reflect.getMetadata(routerMetadata.name, router.constructor); +}; + +export const getListeners = (router: IRouter): Array> => { + return Reflect.getMetadata(routerMetadata.listeners, router.constructor); +}; diff --git a/packages/core/src/@pyro/io/routers-manager.ts b/packages/core/src/@pyro/io/routers-manager.ts new file mode 100644 index 0000000..d4c28c6 --- /dev/null +++ b/packages/core/src/@pyro/io/routers-manager.ts @@ -0,0 +1,36 @@ +import { createEverLogger } from '../../helpers/Log'; +import { injectable, multiInject } from 'inversify'; +import { IRouter, RouterSymbol } from './router/router'; +import { RouterHandler } from './router/handler'; +import Logger from 'bunyan'; +import { Server } from 'socket.io'; + +export interface IRoutersManager { + startListening(io: Server); +} + +@injectable() +export class RoutersManager implements IRoutersManager { + constructor(@multiInject(RouterSymbol) protected routers: any[]) {} + + protected log: Logger = createEverLogger({ name: 'io' }); + + protected io: Server; + + async startListening(io: Server) { + this.io = io; + + this.routers.forEach(async (router) => { + await this.startRouterListening(router); + }); + } + + private async startRouterListening(router: IRouter) { + try { + const routerHandler = new RouterHandler(this.io, router, this.log); + await routerHandler.listen(); + } catch (err) { + this.log.fatal("Couldn't start router listening!", { err }); + } + } +} diff --git a/packages/core/src/app.module.ts b/packages/core/src/app.module.ts new file mode 100644 index 0000000..1d45879 --- /dev/null +++ b/packages/core/src/app.module.ts @@ -0,0 +1,286 @@ +import chalk from 'chalk'; +import fs from 'fs'; +import { + MiddlewareConsumer, + Module, + NestModule, + OnModuleInit +} from '@nestjs/common'; +import mongoose from 'mongoose'; +import { GraphQLSchema } from 'graphql'; +import { GraphQLModule } from '@nestjs/graphql'; +import { SubscriptionsModule } from './graphql/subscriptions/subscriptions.module'; +import { SubscriptionsService } from './graphql/subscriptions/subscriptions.service'; +import { InvitesModule } from './graphql/invites/invites.module'; +import { DevicesModule } from './graphql/devices/devices.module'; +import { ConfigModule } from './config/config.module'; +import { ProductModule } from './controllers/product/product.module'; +import { UsersModule } from './graphql/users/users.module'; +import { WarehousesModule } from './graphql/warehouses/warehouses.module'; +import { OrdersModule } from './graphql/orders/orders.module'; +import { CarriersModule } from './graphql/carriers/carriers.module'; +import { ProductsModule } from './graphql/products/products.module'; +import Logger from 'bunyan'; +import { env } from './env'; +import { createEverLogger } from './helpers/Log'; +import { CommandBus, EventBus, CqrsModule } from '@nestjs/cqrs'; +import { TestController } from './controllers/test.controller'; +import { ModuleRef } from '@nestjs/core'; +import { GeoLocationsModule } from './graphql/geo-locations/geo-locations.module'; +import { SCALARS } from './graphql/scalars'; +import { WarehousesProductsModule } from './graphql/warehouses-products/warehouses-products.modules'; +import { WarehousesCarriersModule } from './graphql/warehouses-carriers/warehouses-carriers.module'; +import { WarehousesOrdersModule } from './graphql/warehouses-orders/warehouses-orders.module'; +import { InvitesRequestsModule } from './graphql/invites-requests/invites-requests.module'; +import { AuthModule } from './auth/auth.module'; +import { AdminsModule } from './graphql/admin/admins.module'; +import { DataModule } from './graphql/data/data.module'; +import { CarriersOrdersModule } from './graphql/carriers-orders/carriers-orders.module'; +import { GeoLocationOrdersModule } from './graphql/geo-locations/orders/geo-location-orders.module'; +import { GeoLocationMerchantsModule } from './graphql/geo-locations/merchants/geo-location-merchants.module'; +import { ApolloServer } from 'apollo-server-express'; +import { ApolloServerPluginLandingPageGraphQLPlayground, ApolloServerPluginLandingPageGraphQLPlaygroundOptions } from 'apollo-server-core'; + +// See https://www.apollographql.com/docs/apollo-server/migration/ +import { makeExecutableSchema } from '@graphql-tools/schema'; + +// See https://www.graphql-tools.com/docs/migration/migration-from-merge-graphql-schemas +import { mergeTypeDefs } from '@graphql-tools/merge'; +import { loadFilesSync } from '@graphql-tools/load-files'; + +import { GetAboutUsHandler } from './services/users'; +import { TypeOrmModule, TypeOrmModuleOptions } from '@nestjs/typeorm'; +import { ServicesModule } from './services/services.module'; +import { ServicesApp } from './services/services.app'; +import { CurrencyModule } from './graphql/currency/currency.module'; +import { PromotionModule } from './graphql/products/promotions/promotion.module'; +import { AppsSettingsModule } from './graphql/apps-settings/apps-settings.module'; + +type Config = Parameters[1]; + +const mergeTypes = (types: any[], options?: { schemaDefinition?: boolean, all?: boolean } & Partial) => { + const schemaDefinition = options && typeof options.schemaDefinition === 'boolean' + ? options.schemaDefinition + : true; + + return mergeTypeDefs(types, { + useSchemaDefinition: schemaDefinition, + forceSchemaDefinition: schemaDefinition, + throwOnConflict: true, + commentDescriptions: true, + reverseDirectives: true, + ...options, + }); + }; + +const port = env.GQLPORT; +const host = env.API_HOST; + +const log: Logger = createEverLogger({ + name: 'NestJS ApplicationModule', +}); + +// Add here all CQRS command handlers +export const CommandHandlers = [GetAboutUsHandler]; + +// Add here all CQRS event handlers +export const EventHandlers = []; + +const entities = ServicesApp.getEntities(); + +const isSSL = process.env.DB_SSL_MODE && process.env.DB_SSL_MODE !== 'false'; + +// let's temporary save Cert in ./tmp/logs folder because we have write access to it +let sslCertPath = `${env.LOGS_PATH}/ca-certificate.crt`; + +if (isSSL) { + const base64data = process.env.DB_CA_CERT; + const buff = Buffer.from(base64data, 'base64'); + const sslCert = buff.toString('ascii'); + fs.writeFileSync(sslCertPath, sslCert); +} + +// TODO: put to config +const connectTimeoutMS: number = 40000; + +// TODO: put to config (we also may want to increase it) +const poolSize: number = 50; + +// We creating default connection for TypeORM. +// It might be used in every place where we do not explicitly require connection with some name. +// For example, we are using connection named "typeorm" inside our repositories +const connectionSettings: TypeOrmModuleOptions = { + // Note: do not change this connection name, it should be default one! + // TODO: put this into settings (it's mongo only during testing of TypeORM integration!) + type: 'mongodb', + url: env.DB_URI, + ssl: isSSL, + sslCA: isSSL ? [sslCertPath] : undefined, + host: process.env.DB_HOST || 'localhost', + username: process.env.DB_USER, + password: process.env.DB_PASS, + database: process.env.DB_NAME || 'ever_development', + port: process.env.DB_PORT ? parseInt(process.env.DB_PORT, 10) : 27017, + entities, + synchronize: true, + useNewUrlParser: true, + // autoReconnect: true, + // reconnectTries: Number.MAX_VALUE, + // poolSize: poolSize, + connectTimeoutMS: connectTimeoutMS, + logging: true, + logger: 'file', // Removes console logging, instead logs all queries in a file ormlogs.log + useUnifiedTopology: true, +}; + +@Module({ + controllers: [TestController], + providers: [...CommandHandlers, ...EventHandlers], + imports: [ + DataModule, + ServicesModule, + CqrsModule, + AuthModule, + AdminsModule, + AppsSettingsModule, + ConfigModule, + // configure TypeORM Connection which will be possible to use inside NestJS (e.g. resolvers) + TypeOrmModule.forRoot(connectionSettings), + // define which repositories shall be registered in the current scope (each entity will have own repository). + // Thanks to that we can inject the XXXXRepository to the NestJS using the @InjectRepository() decorator + // NOTE: this could be used inside NestJS only, not inside our services + TypeOrmModule.forFeature(entities), + SubscriptionsModule.forRoot(env.GQLPORT_SUBSCRIPTIONS), + GraphQLModule.forRoot({ + typePaths: ['./**/*.graphql'], + installSubscriptionHandlers: true, + debug: !env.isProd, + playground: true, + context: ({ req, res }) => ({ + req, + }), + + }), + InvitesModule, + DevicesModule, + ProductModule, + WarehousesModule, + GeoLocationsModule, + UsersModule, + OrdersModule, + CarriersModule, + CarriersOrdersModule, + ProductsModule, + WarehousesProductsModule, + WarehousesOrdersModule, + WarehousesCarriersModule, + InvitesRequestsModule, + GeoLocationOrdersModule, + GeoLocationMerchantsModule, + CurrencyModule, + PromotionModule, + ], +}) +export class ApplicationModule implements NestModule, OnModuleInit { + constructor( + // @Inject(HTTP_SERVER_REF) + // private readonly httpServerRef: HttpServer, + private readonly subscriptionsService: SubscriptionsService, + // Next required for NestJS CQRS (see https://docs.nestjs.com/recipes/cqrs) + private readonly moduleRef: ModuleRef, + private readonly command$: CommandBus, + private readonly event$: EventBus + ) {} + + onModuleInit() { + // initialize CQRS + this.event$.register(EventHandlers); + this.command$.register(CommandHandlers); + } + + configure(consumer: MiddlewareConsumer) { + + console.log(chalk.green(`Configuring NestJS ApplicationModule`)); + + // trick for GraphQL vs MongoDB ObjectId type. + // See https://github.com/apollographql/apollo-server/issues/1633 and + // https://github.com/apollographql/apollo-server/issues/1649#issuecomment-420840287 + const { ObjectId } = mongoose.Types; + + ObjectId.prototype.valueOf = function () { + return this.toString(); + }; + + /* Next is code which could be used to manually create GraphQL Server instead of using GraphQLModule.forRoot(...) + + const schema: GraphQLSchema = this.createSchema(); + const server: ApolloServer = this.createServer(schema); + + // this creates manually GraphQL subscriptions server (over ws connection) + this.subscriptionsService.createSubscriptionServer(server); + + const app: any = this.httpServerRef; + + const graphqlPath = '/graphql'; + + server.applyMiddleware({app, path: graphqlPath}); + + */ + + log.info(`GraphQL Playground available at http://${host}:${port}/graphql`); + console.log(chalk.green(`GraphQL Playground available at http://${host}:${port}/graphql`)); + } + + /* + Creates GraphQL Apollo Server manually + */ + createServer(schema: GraphQLSchema): ApolloServer { + + const playgroundOptions: ApolloServerPluginLandingPageGraphQLPlaygroundOptions = + { + endpoint: `http://${host}:${port}/graphql`, + subscriptionEndpoint: `ws://${host}:${port}/subscriptions`, + settings: { + 'editor.theme': 'dark' + } + }; + + return new ApolloServer({ + schema, + context: ({ req, res }) => ({ + req, + }), + plugins: [ + ApolloServerPluginLandingPageGraphQLPlayground(playgroundOptions) + ] + }); + } + + + /* + Creates GraphQL Schema manually. + See also code in https://github.com/nestjs/graphql/blob/master/lib/graphql.module.ts how it's done by Nest + */ + createSchema(): GraphQLSchema { + const graphqlPath = './**/*.graphql'; + + console.log(`Searching for *.graphql files`); + + const typesArray = loadFilesSync(graphqlPath); + + const typeDefs = mergeTypes(typesArray, { all: true }); + + // we can save all GraphQL types into one file for later usage by other systems + // import { writeFileSync } from 'fs'; + // writeFileSync('./all.graphql', typeDefs); + + const schema = makeExecutableSchema({ + typeDefs, + resolvers: { + ...SCALARS, + }, + }); + + return schema; + } +} diff --git a/packages/core/src/auth/auth.module.ts b/packages/core/src/auth/auth.module.ts new file mode 100644 index 0000000..21c1cb2 --- /dev/null +++ b/packages/core/src/auth/auth.module.ts @@ -0,0 +1,9 @@ +import { Module } from '@nestjs/common'; +import { JwtStrategy } from './jwt.strategy'; + +@Module({ + imports: [], + providers: [JwtStrategy], + exports: [], +}) +export class AuthModule {} diff --git a/packages/core/src/auth/guards/fake-data.guard.ts b/packages/core/src/auth/guards/fake-data.guard.ts new file mode 100644 index 0000000..faa887e --- /dev/null +++ b/packages/core/src/auth/guards/fake-data.guard.ts @@ -0,0 +1,9 @@ +import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common'; +import { env } from '../../env'; + +@Injectable() +export class FakeDataGuard implements CanActivate { + canActivate(context: ExecutionContext): boolean { + return env.FAKE_DATA_GENERATOR; + } +} diff --git a/packages/core/src/auth/jwt.strategy.ts b/packages/core/src/auth/jwt.strategy.ts new file mode 100644 index 0000000..1eb7c50 --- /dev/null +++ b/packages/core/src/auth/jwt.strategy.ts @@ -0,0 +1,25 @@ +import { Strategy } from 'passport-jwt'; +import { PassportStrategy } from '@nestjs/passport'; +import { Injectable, UnauthorizedException } from '@nestjs/common'; +import { + AuthenticationService, + createJwtData, + JwtPayload, +} from '../services/auth'; + +@Injectable() +export class JwtStrategy extends PassportStrategy(Strategy) { + constructor(private authService: AuthenticationService) { + super(createJwtData); + } + + async validate(payload: JwtPayload, done: any) { + const user = await this.authService.validateUser(payload); + + if (!user) { + return done(new UnauthorizedException(), false); + } + + done(null, user); + } +} diff --git a/packages/core/src/config/config.module.ts b/packages/core/src/config/config.module.ts new file mode 100644 index 0000000..8622779 --- /dev/null +++ b/packages/core/src/config/config.module.ts @@ -0,0 +1,13 @@ +import { Module } from '@nestjs/common'; +import { ConfigService } from './config.service'; + +@Module({ + providers: [ + { + provide: ConfigService, + useValue: new ConfigService(), + }, + ], + exports: [ConfigService], +}) +export class ConfigModule {} diff --git a/packages/core/src/config/config.service.ts b/packages/core/src/config/config.service.ts new file mode 100644 index 0000000..3fdef02 --- /dev/null +++ b/packages/core/src/config/config.service.ts @@ -0,0 +1,24 @@ +import { injectable } from 'inversify'; +import { routerName } from '@pyro/io'; +import { env, Environments, Env } from '../env'; + +@injectable() +@routerName('configService') +export class ConfigService { + /** + * Get the config setting. + * In many cases, it get's environment variables by 'key' from '.env' file + * @param key This is settings name or the environemnt variable (HTTPPORT, HTTPSPORT...) etc. + * @returns Returns a value for the given settings key + */ + get(key: string): string | number | boolean | Environments { + return env[key]; + } + + /** + * Get All Env settings + */ + get Env(): Env { + return env; + } +} diff --git a/packages/core/src/controllers/auth/auth.controller.ts b/packages/core/src/controllers/auth/auth.controller.ts new file mode 100644 index 0000000..c60a340 --- /dev/null +++ b/packages/core/src/controllers/auth/auth.controller.ts @@ -0,0 +1,14 @@ +import { Controller, Get, Param } from '@nestjs/common'; +import { AuthService } from './auth.service'; +import { ApiTags } from '@nestjs/swagger'; + +@ApiTags('auth') +@Controller('auth') +export class AuthController { + constructor(private readonly authService: AuthService) {} + + @Get('token/:id') + async createToken(@Param('id') id: string): Promise { + return this.authService.createToken(id); + } +} diff --git a/packages/core/src/controllers/auth/auth.module.ts b/packages/core/src/controllers/auth/auth.module.ts new file mode 100644 index 0000000..2c791d2 --- /dev/null +++ b/packages/core/src/controllers/auth/auth.module.ts @@ -0,0 +1,12 @@ +import { Module } from '@nestjs/common'; +import { AuthService } from './auth.service'; +import { JwtStrategy } from './jwt.strategy'; +import { AuthController } from './auth.controller'; + +@Module({ + imports: [], + controllers: [AuthController], + providers: [AuthService, JwtStrategy], + exports: [AuthService], +}) +export class AuthModule {} diff --git a/packages/core/src/controllers/auth/auth.service.ts b/packages/core/src/controllers/auth/auth.service.ts new file mode 100644 index 0000000..6720a08 --- /dev/null +++ b/packages/core/src/controllers/auth/auth.service.ts @@ -0,0 +1,28 @@ +import jwt from 'jsonwebtoken'; +import { Injectable } from '@nestjs/common'; +import User from '@modules/server.common/entities/User'; +import { createEverLogger } from '../../helpers/Log'; +import { first } from 'rxjs/operators'; +import { UsersService } from '../../services/users'; +import Logger from 'bunyan'; + +export interface JwtPayload { + id: string; +} + +@Injectable() +export class AuthService { + public readonly DBObject = User; + protected readonly log: Logger = createEverLogger({ name: 'authService' }); + + constructor(private readonly _usersService: UsersService) {} + + async createToken(id: string) { + const user: JwtPayload = { id }; + return jwt.sign(user, 'secretKey', { expiresIn: 3600 }); + } + + async validateUser(payload: JwtPayload): Promise { + return this._usersService.get(payload.id).pipe(first()).toPromise(); + } +} diff --git a/packages/core/src/controllers/auth/http.strategy.ts b/packages/core/src/controllers/auth/http.strategy.ts new file mode 100644 index 0000000..e50b3c7 --- /dev/null +++ b/packages/core/src/controllers/auth/http.strategy.ts @@ -0,0 +1,23 @@ +import { Strategy } from 'passport-http-bearer'; +import { PassportStrategy } from '@nestjs/passport'; +import { Injectable } from '@nestjs/common'; +import { AuthService } from './auth.service'; +import { ExtractJwt } from 'passport-jwt'; + +@Injectable() +export class HttpStrategy extends PassportStrategy(Strategy) { + constructor(private readonly authService: AuthService) { + super({ + jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), + secretOrKey: 'secretKey', + }); + } + + // async validate(payload: JwtPayload, done: Function) { + // const user = await this.authService.validateUser(payload); + // if (!user) { + // return done(new UnauthorizedException(), false); + // } + // done(null, user); + // } +} diff --git a/packages/core/src/controllers/auth/jwt.strategy.ts b/packages/core/src/controllers/auth/jwt.strategy.ts new file mode 100644 index 0000000..ea399ba --- /dev/null +++ b/packages/core/src/controllers/auth/jwt.strategy.ts @@ -0,0 +1,26 @@ +import { ExtractJwt, Strategy } from 'passport-jwt'; +import { AuthService, JwtPayload } from './auth.service'; +import { PassportStrategy } from '@nestjs/passport'; +import { Injectable, UnauthorizedException } from '@nestjs/common'; +import User from '@modules/server.common/entities/User'; + +@Injectable() +export class JwtStrategy extends PassportStrategy(Strategy) { + constructor(private readonly authService: AuthService) { + super({ + jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), + secretOrKey: 'secretKey', + }); + } + + async validate( + payload: JwtPayload, + done: (ex: any, b: User | boolean) => void + ) { + const user = await this.authService.validateUser(payload); + if (!user) { + return done(new UnauthorizedException(), false); + } + done(null, user); + } +} diff --git a/packages/core/src/controllers/product/ProductsDto.ts b/packages/core/src/controllers/product/ProductsDto.ts new file mode 100644 index 0000000..09b2da2 --- /dev/null +++ b/packages/core/src/controllers/product/ProductsDto.ts @@ -0,0 +1,25 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { + IProductTitle, + IProductDescription, + IProductImage, +} from '@modules/server.common/interfaces/IProduct'; + +export class UpdateProductDto { + @ApiProperty() + readonly name?: string; + + @ApiProperty() + readonly price?: number; +} + +export class CreateProductDto { + @ApiProperty() + readonly title: IProductTitle[]; + + @ApiProperty() + readonly description: IProductDescription[]; + + @ApiProperty() + readonly images: IProductImage[]; +} diff --git a/packages/core/src/controllers/product/product.controller.ts b/packages/core/src/controllers/product/product.controller.ts new file mode 100644 index 0000000..db9b231 --- /dev/null +++ b/packages/core/src/controllers/product/product.controller.ts @@ -0,0 +1,52 @@ +import { + Body, + Controller, + Delete, + Get, + Header, + Param, + Post, + Put, + Req, + UseGuards, +} from '@nestjs/common'; + +import { ApiBearerAuth, ApiTags } from '@nestjs/swagger'; +import { CreateProductDto, UpdateProductDto } from './ProductsDto'; +import { AuthGuard } from '@nestjs/passport'; +import { ProductsService } from '../../services/products'; + +@ApiTags('product') +@Controller('product') +export class ProductController { + constructor(private productsService: ProductsService) {} + + @Get() + @Header('Cache-Control', 'none') + @UseGuards(AuthGuard('jwt')) + @ApiBearerAuth() + findAll(@Req() request) { + return this.productsService.find({}); + } + + @Get(':id') + async findOne(@Param('id') id: string) { + const prod = await this.productsService.getCurrent(id); + return prod; + } + + @Post(':id') + async create(@Body() createInfo: CreateProductDto) { + return this.productsService.create(createInfo); + } + + @Put(':id') + update(@Param('id') id: string, @Body() updateInfo: UpdateProductDto) { + return `Here updates a #${id} product`; + } + + @Delete('id') + remove(@Param('id') id: string) { + return `Here removes a #${id} product`; + } +} diff --git a/packages/core/src/controllers/product/product.module.ts b/packages/core/src/controllers/product/product.module.ts new file mode 100644 index 0000000..b29abdb --- /dev/null +++ b/packages/core/src/controllers/product/product.module.ts @@ -0,0 +1,7 @@ +import { Module } from '@nestjs/common'; +import { ProductController } from './product.controller'; + +@Module({ + controllers: [ProductController], +}) +export class ProductModule {} diff --git a/packages/core/src/controllers/test.controller.ts b/packages/core/src/controllers/test.controller.ts new file mode 100644 index 0000000..01925f6 --- /dev/null +++ b/packages/core/src/controllers/test.controller.ts @@ -0,0 +1,18 @@ +import { Controller, Get } from '@nestjs/common'; +import { UserCommandService } from '../services/users'; +import { ApiTags } from '@nestjs/swagger'; + +@ApiTags('test') +@Controller('test') +export class TestController { + constructor(private readonly _userCommandService: UserCommandService) {} + + @Get('index') + async index() { + const userId = '1'; + const deviceId = '2'; + const selectedLanguage = '3'; + + await this._userCommandService.exec(userId, deviceId, selectedLanguage); + } +} diff --git a/packages/core/src/env.ts b/packages/core/src/env.ts new file mode 100644 index 0000000..eb17b6c --- /dev/null +++ b/packages/core/src/env.ts @@ -0,0 +1,182 @@ +require('dotenv').config(); + +import { cleanEnv, num, port, str, ValidatorSpec, bool, CleanOptions } from 'envalid'; + +export type Environments = 'production' | 'development' | 'test'; + +export type Env = Readonly<{ + isDev: boolean; + isTest: boolean; + isProd: boolean; + + NODE_ENV: Environments; + + WEB_CONCURRENCY: number; + WEB_MEMORY: number; + + EXPRESS_SESSION_SECRET: string; + + API_HOST: string; + HTTPSPORT: number; + HTTPPORT: number; + GQLPORT: number; + GQLPORT_SUBSCRIPTIONS: number; + + HTTPS_CERT_PATH: string; + HTTPS_KEY_PATH: string; + + LOGS_PATH: string; + + DB_URI: string; + TESTING_DB_URI: string; + + STRIPE_SECRET_KEY: string; + + URBAN_AIRSHIP_KEY: string; + URBAN_AIRSHIP_SECRET: string; + + AWS_ACCESS_KEY_ID: string; + AWS_SECRET_ACCESS_KEY: string; + + KEYMETRICS_MACHINE_NAME: string; + KEYMETRICS_SECRET_KEY: string; + KEYMETRICS_PUBLIC_KEY: string; + + GOOGLE_APP_ID: string; + GOOGLE_APP_SECRET: string; + + FACEBOOK_APP_ID: string; + FACEBOOK_APP_SECRET: string; + + JWT_SECRET: string; + + ADMIN_PASSWORD_BCRYPT_SALT_ROUNDS: number; + WAREHOUSE_PASSWORD_BCRYPT_SALT_ROUNDS: number; + CARRIER_PASSWORD_BCRYPT_SALT_ROUNDS: number; + USER_PASSWORD_BCRYPT_SALT_ROUNDS: number; + + SETTING_INVITES_ENABLED?: boolean; + SETTINGS_REGISTRATIONS_REQUIRED_ON_START?: boolean; + ADMIN_PASSWORD_RESET?: boolean; + FAKE_DATA_GENERATOR?: boolean; + + FAKE_INVITE_CODE: number; + + ARCGIS_CLIENT_ID: string; + ARCGIS_CLIENT_SECRET: string; + IP_STACK_API_KEY?: string; + + LOG_LEVEL?: string; + + ENGINE_API_KEY?: string; + + MAX_SOCKETS?: number; +}>; + +const opt: CleanOptions = { +}; + +export const env: Env = cleanEnv( + process.env, + { + isDev: bool({ default: true }), + isTest: bool({ default: false }), + isProd: bool({ default: false }), + + NODE_ENV: str({ + choices: ['production', 'development', 'test'], + default: 'development', + }) as ValidatorSpec, + + WEB_CONCURRENCY: num({ default: 1 }), + WEB_MEMORY: num({ default: 2048 }), + + API_HOST: str({ default: '127.0.0.1'}), + HTTPSPORT: port({ default: 2087 }), + HTTPPORT: port({ default: 5500 }), + GQLPORT: port({ default: 8443 }), + GQLPORT_SUBSCRIPTIONS: port({ default: 2086 }), + + HTTPS_CERT_PATH: str({ default: 'certificates/https/cert.pem' }), + HTTPS_KEY_PATH: str({ default: 'certificates/https/key.pem' }), + + LOGS_PATH: str({ default: './tmp/logs' }), + + DB_URI: str({ default: 'mongodb://localhost/ever_development' }), + TESTING_DB_URI: str({ default: 'mongodb://localhost/ever_testing' }), + + STRIPE_SECRET_KEY: str({ default: '' }), + + URBAN_AIRSHIP_KEY: str({ default: '' }), + URBAN_AIRSHIP_SECRET: str({ default: '' }), + + AWS_ACCESS_KEY_ID: str({ default: '' }), + AWS_SECRET_ACCESS_KEY: str({ default: '' }), + + KEYMETRICS_MACHINE_NAME: str({ default: '' }), + KEYMETRICS_SECRET_KEY: str({ default: '' }), + KEYMETRICS_PUBLIC_KEY: str({ default: '' }), + + GOOGLE_APP_ID: str({ default: '' }), + GOOGLE_APP_SECRET: str({ default: '' }), + + FACEBOOK_APP_ID: str({ default: '' }), + FACEBOOK_APP_SECRET: str({ default: '' }), + + EXPRESS_SESSION_SECRET: str({ default: 'demand' }), + JWT_SECRET: str({ default: 'secretKey' }), + + ADMIN_PASSWORD_BCRYPT_SALT_ROUNDS: num({ + desc: 'Used for passwords encryption, recommended value: 12', + docs: + 'https://security.stackexchange.com/questions/17207/recommended-of-rounds-for-bcrypt', + default: 12, + }), + + WAREHOUSE_PASSWORD_BCRYPT_SALT_ROUNDS: num({ + desc: 'Used for passwords encryption, recommended value: 12', + docs: + 'https://security.stackexchange.com/questions/17207/recommended-of-rounds-for-bcrypt', + default: 12, + }), + + CARRIER_PASSWORD_BCRYPT_SALT_ROUNDS: num({ + desc: 'Used for passwords encryption, recommended value: 12', + docs: + 'https://security.stackexchange.com/questions/17207/recommended-of-rounds-for-bcrypt', + default: 12, + }), + + USER_PASSWORD_BCRYPT_SALT_ROUNDS: num({ + desc: 'Used for passwords encryption, recommended value: 10', + docs: + 'https://security.stackexchange.com/questions/17207/recommended-of-rounds-for-bcrypt', + default: 10, + }), + + SETTING_INVITES_ENABLED: bool({ default: false }), + SETTINGS_REGISTRATIONS_REQUIRED_ON_START: bool({ default: false }), + ADMIN_PASSWORD_RESET: bool({ default: false }), + FAKE_DATA_GENERATOR: bool({ default: false }), + + FAKE_INVITE_CODE: num({ default: 0 }), + + ARCGIS_CLIENT_ID: str({ default: '' }), + ARCGIS_CLIENT_SECRET: str({ default: '' }), + IP_STACK_API_KEY: str({ default: '' }), + + LOG_LEVEL: str({ + choices: ['trace', 'debug', 'info', 'warn', 'error', 'fatal'], + default: 'error', + }), + + ENGINE_API_KEY: str({ + desc: + 'Apollo Engine Key (optional, see https://www.apollographql.com/docs/platform/schema-registry)', + default: '', + }), + + MAX_SOCKETS: num({ default: Infinity }), + }, + opt +); diff --git a/packages/core/src/graphql/admin/admin.resolver.ts b/packages/core/src/graphql/admin/admin.resolver.ts new file mode 100644 index 0000000..4b16b79 --- /dev/null +++ b/packages/core/src/graphql/admin/admin.resolver.ts @@ -0,0 +1,72 @@ +import { Mutation, Query, Resolver } from '@nestjs/graphql'; +import { first } from 'rxjs/operators'; +import { AdminsService } from '../../services/admins'; +import { + IAdminRegistrationInput, + IAdminLoginResponse, +} from '@modules/server.common/routers/IAdminRouter'; +import { ExtractJwt } from 'passport-jwt'; +import Admin from '@modules/server.common/entities/Admin'; +import { env } from '../../env'; + +@Resolver('Admin') +export class AdminResolver { + constructor(private readonly _adminsService: AdminsService) {} + + @Query('admin') + async getAdmin(_, { id }: { id: string }): Promise { + return this._adminsService.get(id).pipe(first()).toPromise(); + } + + @Query('adminByEmail') + async getByEmail(_, { email }: { email: string }): Promise { + return this._adminsService.getByEmail(email); + } + + @Mutation() + async registerAdmin( + _, + { registerInput }: { registerInput: IAdminRegistrationInput } + ): Promise { + return this._adminsService.register(registerInput); + } + + @Mutation() + async updateAdmin( + _, + { id, updateInput }: { id: string; updateInput } + ): Promise { + await this._adminsService.throwIfNotExists(id); + return this._adminsService.update(id, updateInput); + } + + @Mutation() + async adminLogin( + _, + { email, password }: { email: string; password: string } + ): Promise { + return this._adminsService.login(email, password); + } + + @Query() + async adminAuthenticated(_, __, context: any): Promise { + return this._adminsService.isAuthenticated( + ExtractJwt.fromAuthHeaderAsBearerToken()(context.req) + ); + } + + @Mutation() + async updateAdminPassword( + _, + { + id, + password, + }: { id: Admin['id']; password: { current: string; new: string } } + ): Promise { + if (!env.ADMIN_PASSWORD_RESET) { + throw new Error('Admin password cannot be changed'); + } + + return this._adminsService.updatePassword(id, password); + } +} diff --git a/packages/core/src/graphql/admin/admins.module.ts b/packages/core/src/graphql/admin/admins.module.ts new file mode 100644 index 0000000..1bb4e6c --- /dev/null +++ b/packages/core/src/graphql/admin/admins.module.ts @@ -0,0 +1,7 @@ +import { Module } from '@nestjs/common'; +import { AdminResolver } from './admin.resolver'; + +@Module({ + providers: [AdminResolver], +}) +export class AdminsModule {} diff --git a/packages/core/src/graphql/admin/admins.types.graphql b/packages/core/src/graphql/admin/admins.types.graphql new file mode 100644 index 0000000..4c0b1c7 --- /dev/null +++ b/packages/core/src/graphql/admin/admins.types.graphql @@ -0,0 +1,53 @@ +type Admin { + _id: String! + id: String! + name: String! + email: String! + pictureUrl: String! + firstName: String + lastName: String +} + +input AdminCreateInput { + name: String! + email: String! + pictureUrl: String! + firstName: String + lastName: String +} + +input AdminUpdateInput { + name: String + email: String + pictureUrl: String + firstName: String + lastName: String +} + +type AdminLoginInfo { + admin: Admin! + token: String! +} + +input AdminRegisterInput { + admin: AdminCreateInput! + password: String! +} + +input AdminPasswordUpdateInput { + current: String! + new: String! +} + +type Query { + adminByEmail(email: String!): Admin + admin(id: String!): Admin + adminAuthenticated: Boolean! +} + +type Mutation { + registerAdmin(registerInput: AdminRegisterInput!): Admin! + adminLogin(email: String!, password: String!): AdminLoginInfo + updateAdmin(id: String!, updateInput: AdminUpdateInput!): Admin! + updateAdminPassword(id: String!, password: AdminPasswordUpdateInput!): Void +} diff --git a/packages/core/src/graphql/apps-settings/apps-settings.module.ts b/packages/core/src/graphql/apps-settings/apps-settings.module.ts new file mode 100644 index 0000000..046c81e --- /dev/null +++ b/packages/core/src/graphql/apps-settings/apps-settings.module.ts @@ -0,0 +1,7 @@ +import { Module } from '@nestjs/common'; +import { AdminResolver } from './apps-settings.resolver'; + +@Module({ + providers: [AdminResolver], +}) +export class AppsSettingsModule {} diff --git a/packages/core/src/graphql/apps-settings/apps-settings.resolver.ts b/packages/core/src/graphql/apps-settings/apps-settings.resolver.ts new file mode 100644 index 0000000..5719426 --- /dev/null +++ b/packages/core/src/graphql/apps-settings/apps-settings.resolver.ts @@ -0,0 +1,13 @@ +import { Resolver, Query } from '@nestjs/graphql'; +import { AppsSettingsService } from '../../services/apps-settings'; +import { IAdminAppSettings } from '@modules/server.common/interfaces/IAppsSettings'; + +@Resolver('AppsSettings') +export class AdminResolver { + constructor(private readonly _appsSettingsService: AppsSettingsService) {} + + @Query('adminAppSettings') + async getAdminAppSettings(_): Promise { + return this._appsSettingsService.getAdminAppSettings(); + } +} diff --git a/packages/core/src/graphql/apps-settings/apps-settings.types.graphql b/packages/core/src/graphql/apps-settings/apps-settings.types.graphql new file mode 100644 index 0000000..03a2ae6 --- /dev/null +++ b/packages/core/src/graphql/apps-settings/apps-settings.types.graphql @@ -0,0 +1,8 @@ +type AdminAppSettings { + adminPasswordReset: Int! + fakeDataGenerator: Int! +} + +type Query { + adminAppSettings: AdminAppSettings! +} diff --git a/packages/core/src/graphql/carriers-orders/carriers-orders.module.ts b/packages/core/src/graphql/carriers-orders/carriers-orders.module.ts new file mode 100644 index 0000000..3f5eb3b --- /dev/null +++ b/packages/core/src/graphql/carriers-orders/carriers-orders.module.ts @@ -0,0 +1,7 @@ +import { Module } from '@nestjs/common'; +import { CarriersOrdersResolver } from './carriers-orders.resolver'; + +@Module({ + providers: [CarriersOrdersResolver], +}) +export class CarriersOrdersModule {} diff --git a/packages/core/src/graphql/carriers-orders/carriers-orders.resolver.ts b/packages/core/src/graphql/carriers-orders/carriers-orders.resolver.ts new file mode 100644 index 0000000..edc24a2 --- /dev/null +++ b/packages/core/src/graphql/carriers-orders/carriers-orders.resolver.ts @@ -0,0 +1,59 @@ +import { Query, Resolver } from '@nestjs/graphql'; +import { CarriersOrdersService } from '../../services/carriers'; +import { ICarrierOrdersRouterGetOptions } from '@modules/server.common/routers/ICarrierOrdersRouter'; +import Order from '@modules/server.common/entities/Order'; +import IOrder from '@modules/server.common/interfaces/IOrder'; +import { GeoLocationOrdersOptions } from 'services/geo-locations'; + +@Resolver('CarriersOrders') +export class CarriersOrdersResolver { + constructor( + private readonly _carriersOrdersService: CarriersOrdersService + ) {} + + @Query('getCarrierOrders') + async getCarrierOrders( + _, + { + carrierId, + options, + }: { carrierId: string; options: ICarrierOrdersRouterGetOptions } + ): Promise { + return this._carriersOrdersService.getCarrierOrders(carrierId, options); + } + + @Query('getCarrierCurrentOrder') + async getCarrierCurrentOrder( + _, + { carrierId }: { carrierId: string } + ): Promise { + const dbOrder = await this._carriersOrdersService.getCarrierCurrentOrder( + carrierId + ); + return dbOrder !== null ? new Order(dbOrder) : null; + } + + @Query('getCarrierOrdersHistory') + async getCarrierOrdersHistory( + _, + { + carrierId, + options, + }: { carrierId: string; options: GeoLocationOrdersOptions } + ): Promise { + return this._carriersOrdersService.getCarrierOrdersHistory( + carrierId, + options + ); + } + + @Query('getCountOfCarrierOrdersHistory') + async getCountOfCarrierOrdersHistory( + _, + { carrierId }: { carrierId: string } + ): Promise { + return this._carriersOrdersService.getCountOfCarrierOrdersHistory( + carrierId + ); + } +} diff --git a/packages/core/src/graphql/carriers-orders/carriers-orders.types.graphql b/packages/core/src/graphql/carriers-orders/carriers-orders.types.graphql new file mode 100644 index 0000000..81c90c2 --- /dev/null +++ b/packages/core/src/graphql/carriers-orders/carriers-orders.types.graphql @@ -0,0 +1,75 @@ +type CarrierOrder { + _id: String! + id: String! + isConfirmed: Boolean! + isCancelled: Boolean! + isPaid: Boolean! + warehouseStatus: Int! + carrierStatus: Int! + orderNumber: Int! + _createdAt: Date + user: CarrierOrderUser! + warehouse: CarrierOrderWarehouse! + carrier: CarrierOrderCarrier! + products: [CarrierOrderProducts!]! +} + +type CarrierOrderUser { + _id: String! + firstName: String + lastName: String + geoLocation: GeoLocation! +} + +type CarrierOrderWarehouse { + # Url of the logo + logo: String! + name: String! + geoLocation: GeoLocation! +} + +type CarrierOrderCarrier { + id: String! +} + +type CarrierOrderProducts { + count: Int! + isManufacturing: Boolean! + isCarrierRequired: Boolean! + isDeliveryRequired: Boolean! + isTakeaway: Boolean + initialPrice: Float! + price: Float! + product: CarrierOrderProductsProduct! +} + +type CarrierOrderProductsProduct { + _id: String! + id: String! + title: [TranslateType!]! + description: [TranslateType!]! + details: [TranslateType!]! + images: [ImageType!]! + categories: [String] +} + +type Query { + getCarrierOrders( + carrierId: String! + options: CarrierOrdersOptions + ): [CarrierOrder!]! + + getCarrierCurrentOrder(carrierId: String!): Order + + getCarrierOrdersHistory( + carrierId: String! + options: GeoLocationOrdersOptions + ): [Order]! + + getCountOfCarrierOrdersHistory(carrierId: String!): Int! +} + +input CarrierOrdersOptions { + populateWarehouse: Boolean! + completion: String! +} diff --git a/packages/core/src/graphql/carriers/carrier.resolver.ts b/packages/core/src/graphql/carriers/carrier.resolver.ts new file mode 100644 index 0000000..23b069f --- /dev/null +++ b/packages/core/src/graphql/carriers/carrier.resolver.ts @@ -0,0 +1,158 @@ +import { Mutation, Query, ResolveField, Resolver } from '@nestjs/graphql'; +import { CarriersService } from '../../services/carriers'; +import { default as ICarrier } from '@modules/server.common/interfaces/ICarrier'; +import Carrier from '@modules/server.common/entities/Carrier'; +import { first, map } from 'rxjs/operators'; +import { DevicesService } from '../../services/devices'; +import { + ICarrierRegistrationInput, + ICarrierLoginResponse, +} from '@modules/server.common/routers/ICarrierRouter'; +import CarrierStatus from '@modules/server.common/enums/CarrierStatus'; +import Device from '@modules/server.common/entities/Device'; + +@Resolver('Carrier') +export class CarrierResolver { + constructor( + private readonly _carriersService: CarriersService, + private readonly _devicesService: DevicesService + ) {} + + @Query('getActiveCarriers') + async getActiveCarriers(): Promise { + return this._carriersService.getAllActive().pipe(first()).toPromise(); + } + + @Query('getCarriers') + async getCarriers( + _, + { carriersFindInput, pagingOptions = {} } + ): Promise { + // set default paging options + if (!pagingOptions || (pagingOptions && !pagingOptions['sort'])) { + pagingOptions['sort'] = { field: '_createdAt', sortBy: 'desc' }; + } + + const carriers = await this._carriersService.getCarriers( + carriersFindInput, + pagingOptions + ); + + return carriers.map((c: ICarrier) => new Carrier(c)); + } + + @Query('getCarrierByUsername') + async getCarrierByUsername( + _, + { username }: { username: string } + ): Promise { + return this._carriersService.findOne({ + username, + isDeleted: { $eq: false }, + }); + } + + @Query('getCarrier') + async getCarrier(_, { id }: { id: string }): Promise { + return this._carriersService.get(id).pipe(first()).toPromise(); + } + + /** + * Get total amount of Carriers in the system (not deleted) + * + * @returns {Promise} + * @memberof CarrierResolver + */ + @Query('getCountOfCarriers') + async getCountOfCarriers(_, { carriersFindInput }): Promise { + return this._carriersService.Model.find({ + ...carriersFindInput, + isDeleted: { $eq: false }, + }) + .countDocuments() + .exec(); + } + + @Mutation('updateCarrierEmail') + async updateCarrierEmail( + _, + { carrierId, email }: { carrierId: string; email: string } + ) { + return this._carriersService.updateEmail(carrierId, email); + } + + @Mutation('registerCarrier') + async registerCarrier( + _, + { registerInput }: { registerInput: ICarrierRegistrationInput } + ): Promise { + return this._carriersService.register(registerInput); + } + + @Mutation('updateCarrierStatus') + async updateCarrierStatus( + _, + { id, status }: { id: string; status: string } + ): Promise { + this._carriersService.updateStatus(id, CarrierStatus[status]); + } + + @Mutation('updateCarrier') + async updateCarrier( + _, + { id, updateInput }: { id: string; updateInput } + ): Promise { + await this._carriersService.throwIfNotExists(id); + return this._carriersService.update(id, updateInput); + } + + @Mutation('removeCarrier') + async removeCarrier(_, { id }: { id: string }): Promise { + await this._carriersService.throwIfNotExists(id); + return this._carriersService.remove(id); + } + + @Mutation('removeCarriersByIds') + async removeCarriersByIds(_, { ids }: { ids: string[] }): Promise { + const carriers = await this._carriersService.find({ + _id: { $in: ids }, + isDeleted: { $eq: false }, + }); + + const carriersIds = carriers.map((d) => d.id); + + return this._carriersService.removeMultipleByIds(carriersIds); + } + + @ResolveField('devices') + async getDevices(_carrier: ICarrier): Promise { + const carrier = new Carrier(_carrier); + + return this._devicesService + .getMultiple(carrier.devicesIds) + .pipe( + map((devices) => devices.filter((d) => !d.isDeleted)), + first() + ) + .toPromise(); + } + + @Mutation('carrierLogin') + async carrierLogin( + _, + { username, password }: { username: string; password: string } + ): Promise { + return this._carriersService.login(username, password); + } + + @Mutation('updateCarrierPassword') + async updateCarrierPassword( + _, + { + id, + password, + }: { id: Carrier['id']; password: { current: string; new: string } } + ): Promise { + return this._carriersService.updatePassword(id, password); + } +} diff --git a/packages/core/src/graphql/carriers/carriers.module.ts b/packages/core/src/graphql/carriers/carriers.module.ts new file mode 100644 index 0000000..85efad0 --- /dev/null +++ b/packages/core/src/graphql/carriers/carriers.module.ts @@ -0,0 +1,7 @@ +import { Module } from '@nestjs/common'; +import { CarrierResolver } from './carrier.resolver'; + +@Module({ + providers: [CarrierResolver], +}) +export class CarriersModule {} diff --git a/packages/core/src/graphql/carriers/carriers.types.graphql b/packages/core/src/graphql/carriers/carriers.types.graphql new file mode 100644 index 0000000..13f3cf2 --- /dev/null +++ b/packages/core/src/graphql/carriers/carriers.types.graphql @@ -0,0 +1,137 @@ +type Carrier { + _id: String! + id: String! + firstName: String! + lastName: String! + username: String! + phone: String! + logo: String! + email: String + numberOfDeliveries: Int! + skippedOrderIds: [String!] + status: Int! + geoLocation: GeoLocation! + devicesIds: [String!]! + apartment: String + isActive: Boolean + isSharedCarrier: Boolean + # Resolved + devices: [Device!]! + + # True carrier enabled in system, False carrier completely disabled in the system (e.g. was fired). + # This setting is set by Admin, not by carrier itself + isDeleted: Boolean! +} + +input CarriersFindInput { + firstName: String + lastName: String + email: String + phone: String + isDeleted: Boolean + status: Int + isSharedCarrier: Boolean + _id: Any + # TODO geoLocation +} + +type Query { + getCarrierByUsername(username: String!): Carrier + getCarrier(id: String!): Carrier + getCarriers( + carriersFindInput: CarriersFindInput + pagingOptions: PagingOptionsInput + ): [Carrier!]! + getActiveCarriers: [Carrier]! + getCountOfCarriers(carriersFindInput: CarriersFindInput): Int! +} + +input CarrierCreateInput { + email: String + firstName: String! + lastName: String! + geoLocation: GeoLocationCreateInput! + + # Current carrier status (set via his mobile app), e.g. Online or Offline + status: Int + username: String! + password: String! + phone: String! + logo: String! + numberOfDeliveries: Int + skippedOrderIds: [String!] + deliveriesCountToday: Int + totalDistanceToday: Float + isSharedCarrier: Boolean + devicesIds: [String!] + + # True carrier enabled in system, False carrier completely disabled in the system (e.g. was fired). + # This setting is set by Admin, not by carrier itself + isDeleted: Boolean +} + +input CarrierUpdateInput { + firstName: String + lastName: String + geoLocation: GeoLocationUpdateInput + + # Current carrier status (set via his mobile app), e.g. Online or Offline + status: Int + + username: String + phone: String + email: String + logo: String + numberOfDeliveries: Int + skippedOrderIds: [String!] + deliveriesCountToday: Int + totalDistanceToday: Float + devicesIds: [String!] + isSharedCarrier: Boolean + + # True carrier enabled in system, False carrier completely disabled in the system (e.g. was fired). + # This setting is set by Admin, not by carrier itself + isActive: Boolean +} + +type CarrierLoginInfo { + carrier: Carrier! + token: String! +} + +input CarrierRegisterInput { + carrier: CarrierCreateInput! + password: String! +} + +input CarrierPasswordUpdateInput { + current: String! + new: String! +} + +type Mutation { + registerCarrier(registerInput: CarrierRegisterInput!): Carrier! + + updateCarrierEmail(id: String!, email: String!): Carrier! + + updateCarrier(id: String!, updateInput: CarrierUpdateInput!): Carrier! + + removeCarrier(id: String!): Void + + carrierLogin(username: String!, password: String!): CarrierLoginInfo + + updateCarrierPassword( + id: String! + password: CarrierPasswordUpdateInput! + ): Void + + removeCarriersByIds(ids: [String!]!): String + + updateCarrierStatus(id: String!, status: CarrierStatus): Carrier +} + +enum CarrierStatus { + Online + Offline + Blocked +} diff --git a/packages/core/src/graphql/currency/currency.module.ts b/packages/core/src/graphql/currency/currency.module.ts new file mode 100644 index 0000000..46524e8 --- /dev/null +++ b/packages/core/src/graphql/currency/currency.module.ts @@ -0,0 +1,7 @@ +import { Module } from '@nestjs/common'; +import { CurrencyResolver } from './currency.resolver'; + +@Module({ + providers: [CurrencyResolver], +}) +export class CurrencyModule {} diff --git a/packages/core/src/graphql/currency/currency.resolver.ts b/packages/core/src/graphql/currency/currency.resolver.ts new file mode 100644 index 0000000..59656ca --- /dev/null +++ b/packages/core/src/graphql/currency/currency.resolver.ts @@ -0,0 +1,21 @@ +import { Resolver, Query, Mutation } from '@nestjs/graphql'; +import { ICurrencyCreateObject } from '@modules/server.common/interfaces/ICurrency'; +import { CurrenciesService } from '../../services/currency/CurrencyService'; + +@Resolver('Currency') +export class CurrencyResolver { + constructor(private readonly _currenciesService: CurrenciesService) {} + + @Query('currencies') + async getCurrencies(_) { + return this._currenciesService.getAllCurrencies(); + } + + @Mutation() + async createCurrency( + _, + { createInput }: { createInput: ICurrencyCreateObject } + ) { + return this._currenciesService.createCurrency(createInput); + } +} diff --git a/packages/core/src/graphql/currency/currency.types.graphql b/packages/core/src/graphql/currency/currency.types.graphql new file mode 100644 index 0000000..b12283f --- /dev/null +++ b/packages/core/src/graphql/currency/currency.types.graphql @@ -0,0 +1,22 @@ +type Currency { + _id: String! + currencyCode: String! +} + +type Query { + currencies: [Currency] +} + +input CurrencyCreateInput { + currencyCode: String! +} + +type MutationResponse { + success: Boolean! + message: String + data: Currency +} + +type Mutation { + createCurrency(createInput: CurrencyCreateInput!): MutationResponse +} diff --git a/packages/core/src/graphql/data/data.module.ts b/packages/core/src/graphql/data/data.module.ts new file mode 100644 index 0000000..2c0d709 --- /dev/null +++ b/packages/core/src/graphql/data/data.module.ts @@ -0,0 +1,7 @@ +import { Module } from '@nestjs/common'; +import { DataResolver } from './data.resolver'; + +@Module({ + providers: [DataResolver], +}) +export class DataModule {} diff --git a/packages/core/src/graphql/data/data.resolver.ts b/packages/core/src/graphql/data/data.resolver.ts new file mode 100644 index 0000000..36faa77 --- /dev/null +++ b/packages/core/src/graphql/data/data.resolver.ts @@ -0,0 +1,60 @@ +import { Query, Resolver } from '@nestjs/graphql'; +import { InvitesService } from '../../services/invites/InvitesService'; +import { AdminsService } from '../../services/admins'; +import { CarriersService } from '../../services/carriers'; +import { InvitesRequestsService } from '../../services/invites'; +import { OrdersService } from '../../services/orders'; +import { UsersService } from '../../services/users'; +import { WarehousesService } from '../../services/warehouses'; +import { DevicesService } from '../../services/devices'; +import { + ProductsService, + ProductsCategoriesService, +} from '../../services/products'; + +@Resolver('Data') +export class DataResolver { + // + constructor( + private readonly _adminsService: AdminsService, + private readonly _carriersService: CarriersService, + private readonly _inviteRequestsService: InvitesRequestsService, + private readonly _invitesService: InvitesService, + private readonly _ordersService: OrdersService, + private readonly _usersService: UsersService, + private readonly _storesService: WarehousesService, + private readonly _devicesService: DevicesService, + private readonly _productsService: ProductsService, + private readonly _productsCategoriesService: ProductsCategoriesService + ) {} + + @Query('clearAll') + async clearAll() { + [ + this._adminsService, + this._carriersService, + this._devicesService, + this._invitesService, + this._inviteRequestsService, + this._ordersService, + this._productsService, + this._productsCategoriesService, + this._usersService, + this._storesService, + ].forEach((service) => { + service.Model.updateMany({}, { isDeleted: true }, (err, raw) => { + if (err !== null) { + const collectionName = service.constructor.name.replace( + 'Service', + '' + ); + throw new Error( + `Cannot update '${collectionName}' collection` + ); + } + }); + }); + + return true; + } +} diff --git a/packages/core/src/graphql/data/data.types.graphql b/packages/core/src/graphql/data/data.types.graphql new file mode 100644 index 0000000..55e4da3 --- /dev/null +++ b/packages/core/src/graphql/data/data.types.graphql @@ -0,0 +1,3 @@ +type Query { + clearAll: Boolean! +} diff --git a/packages/core/src/graphql/devices/device.resolver.ts b/packages/core/src/graphql/devices/device.resolver.ts new file mode 100644 index 0000000..ad5fe81 --- /dev/null +++ b/packages/core/src/graphql/devices/device.resolver.ts @@ -0,0 +1,92 @@ +import { Mutation, Query, Resolver, Subscription } from '@nestjs/graphql'; +import { UseGuards } from '@nestjs/common'; +import { AuthGuard } from '@nestjs/passport'; +import { PubSub } from 'graphql-subscriptions'; +import { DevicesService } from '../../services/devices'; +import { IDeviceCreateObject } from '@modules/server.common/interfaces/IDevice'; +import { first } from 'rxjs/operators'; +import ILanguage from '@modules/server.common/interfaces/ILanguage'; + +const pubSub = new PubSub(); + +@Resolver('Device') +export class DeviceResolver { + constructor(private readonly _devicesService: DevicesService) {} + + @Query('device') + async getDevice(_, { id }: { id: string }) { + return this._devicesService.get(id).pipe(first()).toPromise(); + } + + @Query('devices') + // @UseGuards(AuthGuard('jwt')) + async getDevices(_, { findInput }) { + return this._devicesService.find({ + ...findInput, + isDeleted: { $eq: false }, + }); + } + + @Mutation() + async updateDeviceLanguage( + _, + { + deviceId, + language, + }: { + deviceId: string; + language: ILanguage; + } + ) { + return this._devicesService.updateLanguage(deviceId, language); + } + + @Mutation() + @UseGuards(AuthGuard('jwt')) + async createDevice( + _, + { createInfo }: { id: string; createInfo: IDeviceCreateObject } + ) { + return this._devicesService.create(createInfo); + } + + @Mutation() + // @UseGuards(AuthGuard('jwt')) + async updateDevice(_, { id, updateInput }: { id: string; updateInput }) { + await this._devicesService.throwIfNotExists(id); + + try { + const device = await this._devicesService.update(id, updateInput); + pubSub.publish('deviceCreated', { deviceCreated: device }); + return device; + } catch (error) { + console.log(error); + return null; + } + } + + @Mutation() + async removeDevice(_, { id }: { id: string }) { + await this._devicesService.throwIfNotExists(id); + return this._devicesService.remove(id); + } + + @Mutation() + async removeDeviceByIds(_, { ids }: { ids: string[] }) { + const devices = await this._devicesService.find({ + _id: { $in: ids }, + isDeleted: { $eq: false }, + }); + + const devicesIds = devices.map((d) => d.id); + + return this._devicesService.removeMultipleByIds(devicesIds); + } + + @Subscription() // TODO rename to deviceUpdated + deviceCreated() { + return { + subscribe: () => pubSub.asyncIterator('deviceCreated'), + }; + } +} diff --git a/packages/core/src/graphql/devices/devices.module.ts b/packages/core/src/graphql/devices/devices.module.ts new file mode 100644 index 0000000..69ffca0 --- /dev/null +++ b/packages/core/src/graphql/devices/devices.module.ts @@ -0,0 +1,7 @@ +import { Module } from '@nestjs/common'; +import { DeviceResolver } from './device.resolver'; + +@Module({ + providers: [DeviceResolver], +}) +export class DevicesModule {} diff --git a/packages/core/src/graphql/devices/devices.types.graphql b/packages/core/src/graphql/devices/devices.types.graphql new file mode 100644 index 0000000..85f7c2b --- /dev/null +++ b/packages/core/src/graphql/devices/devices.types.graphql @@ -0,0 +1,61 @@ +type Device { + _id: String! + id: String! + + # for push notifications + channelId: String + + # 'android', 'ios' or 'browser' + type: String! + + uuid: String! + language: String +} + +input DeviceFindInput { + channelId: String + language: String + type: String + uuid: String +} + +type Query { + device(id: String): Device + devices(findInput: DeviceFindInput): [Device!]! +} + +input DeviceCreateInput { + channelId: String! + + # default en-US + language: String + + type: String! + uuid: String! +} + +input DeviceUpdateInput { + channelId: String + language: String + type: String + uuid: String +} + +type Mutation { + createDevice(createInput: DeviceCreateInput!): Device! + updateDevice(id: String!, updateInput: DeviceUpdateInput!): Device! + removeDevice(id: String!): Void + removeDeviceByIds(ids: [String!]!): Remove + updateDeviceLanguage(deviceId: String!, language: Language!): Device! +} + +type Subscription { + deviceCreated: Device! +} + +enum Language { + he_IL + en_US + ru_RU + bg_BG +} diff --git a/packages/core/src/graphql/geo-locations/geo-location.resolver.ts b/packages/core/src/graphql/geo-locations/geo-location.resolver.ts new file mode 100644 index 0000000..869bb1b --- /dev/null +++ b/packages/core/src/graphql/geo-locations/geo-location.resolver.ts @@ -0,0 +1,74 @@ +import { Query, Resolver } from '@nestjs/graphql'; +import { GeoLocationsProductsService } from '../../services/geo-locations'; +import GeoLocation from '@modules/server.common/entities/GeoLocation'; +import { first } from 'rxjs/operators'; +import IGeoLocation from '@modules/server.common/interfaces/IGeoLocation'; + +export interface IGetGeoLocationProductsOptions { + isDeliveryRequired?: boolean; + isTakeaway?: boolean; + merchantIds?: string[]; + imageOrientation?: number; + locale?: string; + withoutCount?: boolean; +} + +@Resolver('GeoLocation') +export class GeoLocationResolver { + constructor( + private readonly geoLocationsProductsService: GeoLocationsProductsService + ) {} + + @Query('geoLocationProducts') + async getGeoLocationProducts( + _, + { geoLocation }: { geoLocation: GeoLocation } + ) { + return this.geoLocationsProductsService + .get(geoLocation) + .pipe(first()) + .toPromise(); + } + + @Query() + async geoLocationProductsByPaging( + _, + { + geoLocation, + options, + pagingOptions = {}, + searchText, + }: { + geoLocation; + options?: IGetGeoLocationProductsOptions; + pagingOptions; + searchText?: string; + } + ) { + return this.geoLocationsProductsService.geoLocationProductsByPaging( + geoLocation, + pagingOptions, + options, + searchText + ); + } + @Query() + async getCountOfGeoLocationProducts( + _, + { + geoLocation, + options, + searchText, + }: { + geoLocation: IGeoLocation; + options?: IGetGeoLocationProductsOptions; + searchText?: string; + } + ) { + return this.geoLocationsProductsService.getCountOfGeoLocationProducts( + geoLocation, + options, + searchText + ); + } +} diff --git a/packages/core/src/graphql/geo-locations/geo-locations.graphql b/packages/core/src/graphql/geo-locations/geo-locations.graphql new file mode 100644 index 0000000..78e12fb --- /dev/null +++ b/packages/core/src/graphql/geo-locations/geo-locations.graphql @@ -0,0 +1,94 @@ +type Loc { + type: String! + coordinates: [Float!]! +} + +input LocInput { + type: String! + coordinates: [Float!]! +} + +type GeoLocationCoordinates { + lng: Float! + lat: Float! +} + +type GeoLocation { + _id: String + id: String + _createdAt: Date + createdAt: Date + _updatedAt: Date + updatedAt: Date + countryId: Int! + countryName: String + city: String! + streetAddress: String! + house: String! + postcode: String + notes: String + loc: Loc! + coordinates: GeoLocationCoordinates! +} + +type ProductInfo { + warehouseProduct: WarehouseProduct! + distance: Float! + warehouseId: String! + warehouseLogo: String! +} + +input GeoLocationCreateInput { + countryId: Int! + city: String! + streetAddress: String! + house: String! + postcode: String + notes: String + loc: LocInput! +} + +input GeoLocationUpdateInput { + countryId: Int + city: String + streetAddress: String + house: String + postcode: String + notes: String + loc: LocInput +} + +input GeoLocationFindInput { + countryId: Int + city: String + streetAddress: String + house: String + postcode: String + notes: String + loc: LocInput +} + +type Query { + geoLocationProducts(geoLocation: GeoLocationFindInput!): [ProductInfo] + geoLocationProductsByPaging( + geoLocation: GeoLocationFindInput! + pagingOptions: PagingOptionsInput + options: GetGeoLocationProductsOptions + searchText: String + ): [ProductInfo]! + + getCountOfGeoLocationProducts( + geoLocation: GeoLocationFindInput! + options: GetGeoLocationProductsOptions + searchText: String + ): Int! +} + +input GetGeoLocationProductsOptions { + isDeliveryRequired: Boolean + isTakeaway: Boolean + merchantIds: [String] + imageOrientation: Int + locale: String + withoutCount: Boolean +} diff --git a/packages/core/src/graphql/geo-locations/geo-locations.module.ts b/packages/core/src/graphql/geo-locations/geo-locations.module.ts new file mode 100644 index 0000000..66c908e --- /dev/null +++ b/packages/core/src/graphql/geo-locations/geo-locations.module.ts @@ -0,0 +1,7 @@ +import { Module } from '@nestjs/common'; +import { GeoLocationResolver } from './geo-location.resolver'; + +@Module({ + providers: [GeoLocationResolver], +}) +export class GeoLocationsModule {} diff --git a/packages/core/src/graphql/geo-locations/merchants/geo-location-merchants.graphql b/packages/core/src/graphql/geo-locations/merchants/geo-location-merchants.graphql new file mode 100644 index 0000000..c643bea --- /dev/null +++ b/packages/core/src/graphql/geo-locations/merchants/geo-location-merchants.graphql @@ -0,0 +1,3 @@ +type Query { + getCloseMerchants(geoLocation: GeoLocationFindInput!): [Warehouse]! +} diff --git a/packages/core/src/graphql/geo-locations/merchants/geo-location-merchants.module.ts b/packages/core/src/graphql/geo-locations/merchants/geo-location-merchants.module.ts new file mode 100644 index 0000000..a924d29 --- /dev/null +++ b/packages/core/src/graphql/geo-locations/merchants/geo-location-merchants.module.ts @@ -0,0 +1,7 @@ +import { Module } from '@nestjs/common'; +import { GeoLocationMerchantsResolver } from './geo-location-merchants.resolver'; + +@Module({ + providers: [GeoLocationMerchantsResolver], +}) +export class GeoLocationMerchantsModule {} diff --git a/packages/core/src/graphql/geo-locations/merchants/geo-location-merchants.resolver.ts b/packages/core/src/graphql/geo-locations/merchants/geo-location-merchants.resolver.ts new file mode 100644 index 0000000..e32d47c --- /dev/null +++ b/packages/core/src/graphql/geo-locations/merchants/geo-location-merchants.resolver.ts @@ -0,0 +1,38 @@ +import { Resolver, Query } from '@nestjs/graphql'; +import { GeoLocationsWarehousesService } from '../../../services/geo-locations'; +import IGeoLocation from '@modules/server.common/interfaces/IGeoLocation'; +import Warehouse from '@modules/server.common/entities/Warehouse'; +import Utils from '@modules/server.common/utils'; +import GeoLocation from '@modules/server.common/entities/GeoLocation'; + +const IN_STORE_DISTANCE = 50; + +@Resolver('GeoLocationMerchants') +export class GeoLocationMerchantsResolver { + constructor( + public geoLocationsWarehousesService: GeoLocationsWarehousesService + ) {} + + @Query('getCloseMerchants') + async getCloseMerchants(_, { geoLocation }: { geoLocation: IGeoLocation }) { + let merchants = await this.geoLocationsWarehousesService.getMerchants( + geoLocation, + IN_STORE_DISTANCE, + { fullProducts: false, activeOnly: true, inStoreMode: true } + ); + + merchants = merchants.sort( + (m1, m2) => + Utils.getDistance( + new GeoLocation(m1.geoLocation), + new GeoLocation(geoLocation) + ) - + Utils.getDistance( + new GeoLocation(m2.geoLocation), + new GeoLocation(geoLocation) + ) + ); + + return merchants.map((m) => new Warehouse(m)); + } +} diff --git a/packages/core/src/graphql/geo-locations/orders/geo-location-orders.module.ts b/packages/core/src/graphql/geo-locations/orders/geo-location-orders.module.ts new file mode 100644 index 0000000..6b057c4 --- /dev/null +++ b/packages/core/src/graphql/geo-locations/orders/geo-location-orders.module.ts @@ -0,0 +1,7 @@ +import { Module } from '@nestjs/common'; +import { GeoLocationOrdersResolver } from './geo-location-orders.resolver'; + +@Module({ + providers: [GeoLocationOrdersResolver], +}) +export class GeoLocationOrdersModule {} diff --git a/packages/core/src/graphql/geo-locations/orders/geo-location-orders.resolver.ts b/packages/core/src/graphql/geo-locations/orders/geo-location-orders.resolver.ts new file mode 100644 index 0000000..9c22b58 --- /dev/null +++ b/packages/core/src/graphql/geo-locations/orders/geo-location-orders.resolver.ts @@ -0,0 +1,83 @@ +import { Resolver, Query } from '@nestjs/graphql'; +import { GeoLocationsOrdersService } from '../../../services/geo-locations/GeoLocationsOrdersService'; +import { GeoLocationOrdersOptions } from '../../../services/geo-locations/GeoLocationOrdersOptions'; +import IGeoLocation from '@modules/server.common/interfaces/IGeoLocation'; +import Order from '@modules/server.common/entities/Order'; + +@Resolver('GeoLocationOrders') +export class GeoLocationOrdersResolver { + constructor(public geoLocationsOrdersService: GeoLocationsOrdersService) {} + + @Query('getOrderForWork') + async getOrderForWork( + _, + { + geoLocation, + skippedOrderIds, + options, + searchObj, + }: { + geoLocation: IGeoLocation; + skippedOrderIds: string[]; + options: GeoLocationOrdersOptions; + searchObj?: { + isCancelled?: boolean; + byRegex?: { key: string; value: string }[]; + }; + } + ) { + const orders = await this.geoLocationsOrdersService.getOrdersForWork( + geoLocation, + skippedOrderIds, + options, + searchObj + ); + + return orders[0]; + } + + @Query('getOrdersForWork') + async getOrdersForWork( + _, + { + geoLocation, + skippedOrderIds, + options, + searchObj, + }: { + geoLocation: IGeoLocation; + skippedOrderIds: string[]; + options: GeoLocationOrdersOptions; + searchObj?: { byRegex: { key: string; value: string }[] }; + } + ) { + const orders = await this.geoLocationsOrdersService.getOrdersForWork( + geoLocation, + skippedOrderIds, + options, + searchObj + ); + + return orders.map((o) => new Order(o)); + } + + @Query() + async getCountOfOrdersForWork( + _, + { + geoLocation, + skippedOrderIds, + searchObj, + }: { + geoLocation: IGeoLocation; + skippedOrderIds: string[]; + searchObj?: { byRegex: { key: string; value: string }[] }; + } + ) { + return this.geoLocationsOrdersService.getCountOfOrdersForWork( + geoLocation, + skippedOrderIds, + searchObj + ); + } +} diff --git a/packages/core/src/graphql/geo-locations/orders/geo-locations-orders.graphql b/packages/core/src/graphql/geo-locations/orders/geo-locations-orders.graphql new file mode 100644 index 0000000..3a4ffc4 --- /dev/null +++ b/packages/core/src/graphql/geo-locations/orders/geo-locations-orders.graphql @@ -0,0 +1,37 @@ +type Query { + getOrderForWork( + geoLocation: GeoLocationFindInput! + skippedOrderIds: [String!]! + options: GeoLocationOrdersOptions + searchObj: SearchOrdersForWork + ): Order + + getOrdersForWork( + geoLocation: GeoLocationFindInput! + skippedOrderIds: [String!]! + options: GeoLocationOrdersOptions + searchObj: SearchOrdersForWork + ): [Order]! + + getCountOfOrdersForWork( + geoLocation: GeoLocationFindInput! + skippedOrderIds: [String!]! + searchObj: SearchOrdersForWork + ): Int! +} + +input SearchOrdersForWork { + isCancelled: Boolean + byRegex: [SearchByRegex] +} + +input SearchByRegex { + key: String! + value: String! +} + +input GeoLocationOrdersOptions { + sort: String + limit: Int + skip: Int +} diff --git a/packages/core/src/graphql/invites-requests/invites-requests.module.ts b/packages/core/src/graphql/invites-requests/invites-requests.module.ts new file mode 100644 index 0000000..b6b378f --- /dev/null +++ b/packages/core/src/graphql/invites-requests/invites-requests.module.ts @@ -0,0 +1,7 @@ +import { Module } from '@nestjs/common'; +import { InviteRequestResolver } from './invites-requests.resolver'; + +@Module({ + providers: [InviteRequestResolver], +}) +export class InvitesRequestsModule {} diff --git a/packages/core/src/graphql/invites-requests/invites-requests.resolver.ts b/packages/core/src/graphql/invites-requests/invites-requests.resolver.ts new file mode 100644 index 0000000..6708be6 --- /dev/null +++ b/packages/core/src/graphql/invites-requests/invites-requests.resolver.ts @@ -0,0 +1,114 @@ +import { Resolver, Query, Mutation } from '@nestjs/graphql'; +import { InvitesRequestsService } from '../../services/invites'; +import { IInviteRequestCreateObject } from '@modules/server.common/interfaces/IInviteRequest'; +import { first } from 'rxjs/operators'; +import Invite from '@modules/server.common/entities/Invite'; +import InviteRequest from '@modules/server.common/entities/InviteRequest'; +import { UseGuards } from '@nestjs/common'; +import { FakeDataGuard } from '../../auth/guards/fake-data.guard'; + +@Resolver('Invite-request') +export class InviteRequestResolver { + constructor( + private readonly _invitesRequestsService: InvitesRequestsService + ) {} + + @Query() + @UseGuards(FakeDataGuard) + async generate1000InviteRequests( + _, + { defaultLng, defaultLat }: { defaultLng: number; defaultLat: number } + ) { + await this._invitesRequestsService.generate1000InviteRequests( + defaultLng, + defaultLat + ); + } + + @Query('inviteRequest') + async getInviteRequest(_, { id }: { id: string }) { + return this._invitesRequestsService.get(id).pipe(first()).toPromise(); + } + + @Query('notifyAboutLaunch') + async notifyAboutLaunch( + _, + { + invite, + devicesIds, + }: { + invite: Invite; + devicesIds: string[]; + } + ) { + return this._invitesRequestsService.notifyAboutLaunch( + invite, + devicesIds + ); + } + + @Query('invitesRequests') + async getInvitesRequests(_, { findInput, invited, pagingOptions = {} }) { + if (!pagingOptions || (pagingOptions && !pagingOptions['sort'])) { + pagingOptions['sort'] = { field: '_createdAt', sortBy: 'asc' }; + } + + const invitesRequests = await this._invitesRequestsService.getInvitesRequests( + findInput, + invited, + pagingOptions + ); + + return invitesRequests.map((i) => new InviteRequest(i)); + } + + @Query() + async getCountOfInvitesRequests(_, { invited }) { + const findObj = { isDeleted: { $eq: false } }; + + if (!invited) { + findObj['isInvited'] = { $eq: false }; + } + + return this._invitesRequestsService.Model.find(findObj) + .countDocuments() + .exec(); + } + + @Mutation() + async createInviteRequest( + _, + { createInput }: { createInput: IInviteRequestCreateObject } + ) { + return this._invitesRequestsService.create(createInput); + } + + @Mutation() + async updateInviteRequest( + _, + { id, updateInput }: { id: string; updateInput } + ) { + await this._invitesRequestsService.throwIfNotExists(id); + return this._invitesRequestsService.update(id, updateInput); + } + + @Mutation() + async removeInviteRequest(_, { id }: { id: string }) { + await this._invitesRequestsService.throwIfNotExists(id); + return this._invitesRequestsService.remove(id); + } + + @Mutation() + async removeInvitesRequestsByIds(_, { ids }: { ids: string[] }) { + const inviteRequests = await this._invitesRequestsService.find({ + _id: { $in: ids }, + isDeleted: { $eq: false }, + }); + + const inviteRequestsIds = inviteRequests.map((d) => d.id); + + return this._invitesRequestsService.removeMultipleByIds( + inviteRequestsIds + ); + } +} diff --git a/packages/core/src/graphql/invites-requests/invites-requests.types.graphql b/packages/core/src/graphql/invites-requests/invites-requests.types.graphql new file mode 100644 index 0000000..c9e530e --- /dev/null +++ b/packages/core/src/graphql/invites-requests/invites-requests.types.graphql @@ -0,0 +1,77 @@ +type InviteRequest { + _id: String! + id: String! + apartment: String! + geoLocation: GeoLocation! + isManual: Boolean + isInvited: Boolean + invitedDate: Date +} + +input InvitesRequestsFindInput { + apartment: String + isManual: Boolean + isInvited: Boolean + invitedDate: Date + # todo GeoLocation +} + +type Query { + inviteRequest(id: String!): InviteRequest + + invitesRequests( + findInput: InvitesRequestsFindInput + pagingOptions: PagingOptionsInput + invited: Boolean + ): [InviteRequest!] + + notifyAboutLaunch(invite: InviteInput, devicesIds: [String!]!): Void + + generate1000InviteRequests(defaultLng: Float!, defaultLat: Float!): Void + + getCountOfInvitesRequests(invited: Boolean): Int! +} + +input InviteInput { + code: String! + apartment: String! + geoLocation: GeoLocationInput! + isDeleted: Boolean! +} + +input GeoLocationInput { + countryId: Int! + countryName: String + city: String! + streetAddress: String! + house: String! + postcode: String + notes: String + loc: Location! +} + +input InviteRequestCreateInput { + apartment: String! + geoLocation: GeoLocationCreateInput! + isManual: Boolean + invitedDate: Date + isInvited: Boolean +} + +input InviteRequestUpdateInput { + apartment: String + geoLocation: GeoLocationUpdateInput + isManual: Boolean + invitedDate: Date + isInvited: Boolean +} + +type Mutation { + createInviteRequest(createInput: InviteRequestCreateInput!): InviteRequest! + updateInviteRequest( + id: String! + updateInput: InviteRequestUpdateInput! + ): InviteRequest! + removeInviteRequest(id: String!): Void + removeInvitesRequestsByIds(ids: [String!]!): Remove +} diff --git a/packages/core/src/graphql/invites/invite.resolver.ts b/packages/core/src/graphql/invites/invite.resolver.ts new file mode 100644 index 0000000..a0f7acc --- /dev/null +++ b/packages/core/src/graphql/invites/invite.resolver.ts @@ -0,0 +1,118 @@ +import { Mutation, Query, Resolver } from '@nestjs/graphql'; +import { first } from 'rxjs/operators'; +import IEnterByCode from '@modules/server.common/interfaces/IEnterByCode'; +import IEnterByLocation from '@modules/server.common/interfaces/IEnterByLocation'; +import { IInviteCreateObject } from '@modules/server.common/interfaces/IInvite'; +import { InvitesService } from '../../services/invites/InvitesService'; +import { InvitesRequestsService } from '../../services/invites'; +import Invite from '@modules/server.common/entities/Invite'; +import { UseGuards } from '@nestjs/common'; +import { FakeDataGuard } from '../../auth/guards/fake-data.guard'; + +@Resolver('Invite') +export class InviteResolver { + constructor( + private readonly _invitesService: InvitesService, + private readonly _inviteRequestsService: InvitesRequestsService + ) {} + + @Query() + @UseGuards(FakeDataGuard) + async generate1000InvitesConnectedToInviteRequests( + _, + { defaultLng, defaultLat }: { defaultLng: number; defaultLat: number } + ): Promise { + const { + invitesRequestsToCreate, + invitesToCreate, + } = this._invitesService.generate1000InvitesConnectedToInviteRequests( + defaultLng, + defaultLat + ); + + await this._invitesService.Model.insertMany(invitesToCreate); + await this._inviteRequestsService.Model.insertMany( + invitesRequestsToCreate + ); + } + + @Query('invite') + async getInvite(_, { id }: { id: string }): Promise { + return this._invitesService.get(id).pipe(first()).toPromise(); + } + + @Query('getInviteByCode') + async getInviteByCode( + _, + { info }: { info: IEnterByCode } + ): Promise { + return this._invitesService.getByCode(info).pipe(first()).toPromise(); + } + + @Query('getInviteByLocation') + async getInviteByLocation( + _, + { info }: { info: IEnterByLocation } + ): Promise { + return this._invitesService + .getByLocation(info) + .pipe(first()) + .toPromise(); + } + + @Query('invites') + async getInvites(_, { findInput, pagingOptions = {} }): Promise { + if (!pagingOptions || (pagingOptions && !pagingOptions['sort'])) { + pagingOptions['sort'] = { field: '_createdAt', sortBy: 'desc' }; + } + + const invites = await this._invitesService.getInvites( + findInput, + pagingOptions + ); + + return invites.map((i) => new Invite(i)); + } + + @Query() + async getCountOfInvites(): Promise { + return this._invitesService.Model.find({ isDeleted: { $eq: false } }) + .countDocuments() + .exec(); + } + + @Mutation() + async createInvite( + _, + { createInput }: { createInput: IInviteCreateObject } + ): Promise { + return this._invitesService.create(createInput); + } + + @Mutation() + async updateInvite( + _, + { id, updateInput }: { id: string; updateInput } + ): Promise { + await this._invitesService.throwIfNotExists(id); + return this._invitesService.update(id, updateInput); + } + + @Mutation() + async removeInvite(_, { id }: { id: string }): Promise { + await this._invitesService.throwIfNotExists(id); + return this._invitesService.remove(id); + } + + @Mutation() + async removeInvitesByIds(_, { ids }: { ids: string[] }): Promise { + const invites = await this._invitesService.find({ + _id: { $in: ids }, + isDeleted: { $eq: false }, + }); + + const invitesIds = invites.map((d) => d.id); + + return this._invitesService.removeMultipleByIds(invitesIds); + } +} diff --git a/packages/core/src/graphql/invites/invites.module.ts b/packages/core/src/graphql/invites/invites.module.ts new file mode 100644 index 0000000..4dea8f2 --- /dev/null +++ b/packages/core/src/graphql/invites/invites.module.ts @@ -0,0 +1,7 @@ +import { Module } from '@nestjs/common'; +import { InviteResolver } from './invite.resolver'; + +@Module({ + providers: [InviteResolver], +}) +export class InvitesModule {} diff --git a/packages/core/src/graphql/invites/invites.types.graphql b/packages/core/src/graphql/invites/invites.types.graphql new file mode 100644 index 0000000..8937823 --- /dev/null +++ b/packages/core/src/graphql/invites/invites.types.graphql @@ -0,0 +1,74 @@ +type Invite { + _id: String! + id: String! + code: String! + apartment: String! + geoLocation: GeoLocation! +} + +input InvitesFindInput { + code: String + apartment: String + # todo GeoLocation +} + +input Location { + type: String! + coordinates: [Float!]! +} + +input InviteByCodeInput { + location: Location! + inviteCode: String! + firstName: String + lastName: String +} + +input InviteByLocationInput { + countryId: Int! + city: String! + streetAddress: String! + house: String! + apartment: String! + postcode: String + notes: String +} + +type Query { + invite(id: String!): Invite + + invites( + findInput: InvitesFindInput + pagingOptions: PagingOptionsInput + ): [Invite!]! + + getInviteByCode(info: InviteByCodeInput!): Invite + + getInviteByLocation(info: InviteByLocationInput): Invite + + generate1000InvitesConnectedToInviteRequests( + defaultLng: Float! + defaultLat: Float! + ): Void + + getCountOfInvites: Int! +} + +input InviteCreateInput { + code: String + apartment: String! + geoLocation: GeoLocationCreateInput! +} + +input InviteUpdateInput { + code: String + apartment: String + geoLocation: GeoLocationUpdateInput +} + +type Mutation { + createInvite(createInput: InviteCreateInput!): Invite! + updateInvite(id: String!, updateInput: InviteUpdateInput!): Invite! + removeInvite(id: String!): Void + removeInvitesByIds(ids: [String!]!): Remove +} diff --git a/packages/core/src/graphql/orders/order.resolver.ts b/packages/core/src/graphql/orders/order.resolver.ts new file mode 100644 index 0000000..d94e0ae --- /dev/null +++ b/packages/core/src/graphql/orders/order.resolver.ts @@ -0,0 +1,789 @@ +import { Query, ResolveField, Resolver, Mutation } from '@nestjs/graphql'; +import IOrder from '@modules/server.common/interfaces/IOrder'; +import CarriersService from '../../services/carriers/CarriersService'; +import Order from '@modules/server.common/entities/Order'; +import { first } from 'rxjs/operators'; +import OrderCarrierStatus from '@modules/server.common/enums/OrderCarrierStatus'; +import OrderWarehouseStatus from '@modules/server.common/enums/OrderWarehouseStatus'; +import Warehouse from '@modules/server.common/entities/Warehouse'; +import User from '@modules/server.common/entities/User'; +import Carrier from '@modules/server.common/entities/Carrier'; +import { WarehousesService } from '../../services/warehouses'; +import { OrdersService } from '../../services/orders'; +import { UsersService } from '../../services/users'; +import IProduct from '@modules/server.common/interfaces/IProduct'; +import { ProductsService } from '../../services/products'; +import Product from '@modules/server.common/entities/Product'; +import * as _ from 'lodash'; +import { ObjectId } from 'bson'; +import { FakeOrdersService } from '../../services/fake-data/FakeOrdersService'; +import { firstValueFrom } from 'rxjs'; + +@Resolver('Order') +export class OrderResolver { + constructor( + private readonly _ordersService: OrdersService, + private readonly _carriersService: CarriersService, + private readonly _warehousesService: WarehousesService, + private readonly _usersService: UsersService, + private readonly _productsService: ProductsService, + private readonly _fakeOrdersService: FakeOrdersService + ) {} + + private _getRandomProduct = ( + orderCount: number, + products: Product[] + ): IProduct => { + return products[orderCount % products.length]; + }; + + private _getRandomCustomer = ( + orderCount: number, + customers: User[] + ): User => { + if (orderCount < customers.length) { + return customers[orderCount]; + } else { + return customers[orderCount % customers.length]; + } + }; + + private _getRandomCarrierId = ( + orderCount: number, + carriers: Carrier[] + ): string => { + return carriers[orderCount % carriers.length].id; + }; + + private _prepareOrderFieldsValues = (orderCount: number) => { + const hasCarrier = Math.random() > 0.07; // 7% chance, order have not carrier. + const orderIsPaid = Math.random() > 0.15; // 15% chance, order to be cancelled. + const numberOfProductsToOrder = orderCount % 4 || 1; + + return { numberOfProductsToOrder, hasCarrier, orderIsPaid }; + }; + + private _getRandomOrderDate = (orderCount: number): Date => { + const orderDate = new Date(); + + orderDate.setFullYear( + orderDate.getFullYear() - Math.round(Math.random() * 5) + ); + + const dateNow = new Date(); + + const isCurrentYear = orderDate.getFullYear() === dateNow.getFullYear(); + + const months = isCurrentYear ? Number(dateNow.getMonth()) : 11; + + orderDate.setMonth(months > 0 ? orderCount % months : 0); + + const isCurrentMonth = + orderDate.getMonth() === dateNow.getMonth() && isCurrentYear; + + const days = isCurrentMonth ? Number(dateNow.getDate()) : 31; + + orderDate.setDate(orderCount % days); + + const isCurrentDay = + orderDate.getDate() === dateNow.getDate() && + isCurrentYear && + isCurrentMonth; + + const hours = isCurrentDay ? Number(dateNow.getHours()) : 24; + + orderDate.setHours(orderCount % hours); + + return orderDate; + }; + + private _setupOrderProducts = (products: Product[]) => { + let productCount = 1; + const orderProducts = []; + + for (const p of products) { + const productPrice = Math.round(Math.random() * 15); + + orderProducts.push({ + count: 2, + isManufacturing: true, + isCarrierRequired: true, + isDeliveryRequired: true, + price: productPrice, + initialPrice: productPrice, + product: p, + }); + + if (productCount >= 3) { + break; + } + productCount += 1; + } + + return orderProducts; + }; + + private _setupAvailableOrdersToCreate = ( + stores: Warehouse[], + products: Product[], + users: User[] + ) => { + const orders = []; + + for (let orderNumber = 1; orderNumber <= 30; orderNumber += 1) { + const orderStore = stores[_.random(stores.length - 1)]; + + const orderProducts = this._setupOrderProducts(products); + + const createdAt = this.getCloseDate(new Date()); + + orders.push({ + user: users[_.random(users.length - 1)], + warehouse: orderStore._id.toString(), + products: orderProducts, + isConfirmed: true, + isCancelled: false, + isPaid: false, + warehouseStatus: OrderWarehouseStatus.PackagingFinished, + carrierStatus: OrderCarrierStatus.NoCarrier, + orderNumber, + _createdAt: createdAt, + }); + } + return orders; + }; + + private _setupHistoryOrdersToCreate = ( + stores: Warehouse[], + products: Product[], + users: User[], + carrierId: string, + orderNumber: number + ) => { + const orders = []; + + const availableStatuses: OrderCarrierStatus[] = [ + OrderCarrierStatus.DeliveryCompleted, + OrderCarrierStatus.IssuesDuringDelivery, + OrderCarrierStatus.ClientRefuseTakingOrder, + ]; + + for (let i = 1; i <= 12; i += 1) { + const orderStore = stores[_.random(stores.length - 1)]; + + const orderProducts = this._setupOrderProducts(products); + + const carrierStatus = + availableStatuses[_.random(availableStatuses.length - 1)]; + + const createdAt = this.getCloseDate(new Date()); + + const startDeliveryTime = this.getFinishedTime(createdAt); + + orders.push({ + user: users[_.random(users.length - 1)], + warehouse: orderStore._id.toString(), + products: orderProducts, + isConfirmed: true, + isCancelled: false, + carrier: carrierId, + startDeliveryTime, + deliveryTime: + carrierStatus === OrderCarrierStatus.DeliveryCompleted + ? this.getFinishedTime(startDeliveryTime) + : null, + finishedProcessingTime: + carrierStatus !== OrderCarrierStatus.DeliveryCompleted + ? this.getFinishedTime(startDeliveryTime) + : null, + isPaid: true, + warehouseStatus: OrderWarehouseStatus.GivenToCarrier, + carrierStatus, + orderNumber, + _createdAt: createdAt, + }); + } + + return orders; + }; + + @Query() + async generateActiveAndAvailableOrdersPerCarrier() { + const commonOptionsFlag = { isDeleted: { $eq: false } }; + + const users: User[] = await this._usersService.Model.find( + commonOptionsFlag + ) + .select({ __v: 0 }) + .lean() + .exec(); + + const stores: Warehouse[] = await this._warehousesService.Model.find( + commonOptionsFlag + ) + .select({ _id: 1 }) + .lean() + .exec(); + + const products: Product[] = await this._productsService.Model.find( + commonOptionsFlag + ) + .select({ __v: 0 }) + .lean() + .exec(); + + const ordersRaw = this._setupAvailableOrdersToCreate( + stores, + products, + users + ); + + await this._ordersService.Model.insertMany(ordersRaw); + } + + @Query() + async generatePastOrdersPerCarrier() { + const commonOptionsFlag = { isDeleted: { $eq: false } }; + + const users: User[] = await this._usersService.Model.find( + commonOptionsFlag + ) + .select({ __v: 0 }) + .lean() + .exec(); + + const stores: Warehouse[] = await this._warehousesService.Model.find( + commonOptionsFlag + ) + .select({ _id: 1 }) + .lean() + .exec(); + + const carrierIds: Carrier[] = await this._carriersService.Model.find( + commonOptionsFlag + ) + .select({ _id: 1 }) + .lean() + .exec(); + + const products: Product[] = await this._productsService.Model.find( + commonOptionsFlag + ) + .select({ __v: 0 }) + .lean() + .exec(); + + const totalOrdersToCreate = []; + + carrierIds.forEach((objectId, index) => { + const carrierId = objectId._id.toString(); + const orderNumber = index; + const ordersRaw = this._setupHistoryOrdersToCreate( + stores, + products, + users, + carrierId, + orderNumber + ); + + totalOrdersToCreate.push(ordersRaw); + }); + + await this._ordersService.Model.insertMany( + _.flatten(totalOrdersToCreate) + ); + } + + @Query() + async addTakenOrders(_context, { carrierIds }: { carrierIds: string[] }) { + const commonOptionsFlag = { isDeleted: { $eq: false } }; + + const stores: Warehouse[] = await this._warehousesService.find( + commonOptionsFlag + ); + + const customers: User[] = await this._usersService.find( + commonOptionsFlag + ); + + const products: Product[] = await this._productsService.find( + commonOptionsFlag + ); + + const ordersToCreate = []; + + carrierIds.forEach((id) => { + for (let orderNumber = 1; orderNumber <= 20; orderNumber += 1) { + const orderProducts = []; + + const productCount = Math.round(Math.random() * 4) || 1; + for (let i = 0; i < productCount; i += 1) { + const orderPrice = (orderNumber + i) % 110 || 1; + + orderProducts.push({ + count: (orderNumber + i) % 12 || 1, + isManufacturing: true, + isCarrierRequired: true, + isDeliveryRequired: true, + price: orderPrice, + initialPrice: orderPrice, + product: this._getRandomProduct( + orderNumber + i, + products + ), + }); + } + + const orderIsPaid = Math.random() > 0.5; + const createdAt = this._getRandomOrderDate(orderNumber); + const startDeliveryTime = this.getFinishedTime(createdAt); + + ordersToCreate.push({ + isCancelled: !orderIsPaid, + isPaid: orderIsPaid, + deliveryTimeEstimate: 0, + startDeliveryTime, + deliveryTime: orderIsPaid + ? this.getFinishedTime(startDeliveryTime) + : null, + finishedProcessingTime: !orderIsPaid + ? this.getFinishedTime(startDeliveryTime) + : null, + warehouseStatus: OrderWarehouseStatus.PackagingFinished, + carrierStatus: OrderCarrierStatus.DeliveryCompleted, + orderNumber, + user: this._getRandomCustomer(orderNumber, customers), + warehouse: stores[orderNumber % stores.length].id, + products: orderProducts, + _createdAt: createdAt, + carrier: id, + }); + } + }); + + await this._ordersService.Model.insertMany(ordersToCreate); + } + + @Query() + async addOrdersToTake() { + const commonOptionsFlag = { isDeleted: { $eq: false } }; + + const stores: Warehouse[] = await this._warehousesService.find( + commonOptionsFlag + ); + + const customers: User[] = await this._usersService.find( + commonOptionsFlag + ); + + const products: Product[] = await this._productsService.find( + commonOptionsFlag + ); + + const ordersToCreate = []; + + for (let i = 0; i < 3; i += 1) { + for (let orderNumber = 1; orderNumber <= 10; orderNumber += 1) { + const orderProducts = []; + + const productCount = Math.round(Math.random() * 4) || 1; + + for (let j = 0; j < productCount; j += 1) { + const orderPrice = (orderNumber + j) % 110 || 1; + + orderProducts.push({ + count: (orderNumber + j) % 6 || 1, + isManufacturing: true, + isCarrierRequired: true, + isDeliveryRequired: true, + price: orderPrice, + initialPrice: orderPrice, + product: this._getRandomProduct( + orderNumber + j, + products + ), + }); + } + + const createdAt = this.getCloseDate(new Date()); + + ordersToCreate.push({ + isConfirmed: false, + isCancelled: false, + isPaid: false, + deliveryTimeEstimate: 0, + warehouseStatus: OrderWarehouseStatus.PackagingFinished, + carrierStatus: OrderCarrierStatus.NoCarrier, + orderNumber, + user: this._getRandomCustomer(orderNumber, customers), + warehouse: stores[orderNumber % stores.length].id, + products: orderProducts, + _createdAt: createdAt, + }); + } + } + + await this._ordersService.Model.insertMany(ordersToCreate); + } + + @Query() + async generateRandomOrdersCurrentStore( + _context, + { + storeId, + storeCreatedAt, + ordersLimit, + }: { storeId: string; storeCreatedAt: Date; ordersLimit: number } + ) { + const commonOptionsFlag = { isDeleted: { $eq: false } }; + + const customers: User[] = await this._usersService.find( + commonOptionsFlag + ); + const carriers: Carrier[] = await this._carriersService.find( + commonOptionsFlag + ); + const products: Product[] = await this._productsService.find( + commonOptionsFlag + ); + + let response = { error: false, message: null }; + + try { + const currentStoreOrders = []; + const storeCreatedDate = new Date(storeCreatedAt); + + for ( + let orderNumber = 1; + orderNumber <= ordersLimit; + orderNumber += 1 + ) { + const carrierId = this._getRandomCarrierId( + orderNumber, + carriers + ); + + const orderRaw = this._fakeOrdersService.getOrderRaw( + orderNumber, + storeId, + storeCreatedDate, + carrierId, + customers, + products + ); + + currentStoreOrders.push(orderRaw); + } + + await this._ordersService.Model.insertMany(currentStoreOrders); + } catch (err) { + response = { error: true, message: err.message }; + } + + return response; + } + + @Query() + async generateOrdersByCustomerId( + _context, + { + numberOfOrders, + customerId, + }: { numberOfOrders: number; customerId: string } + ) { + const commonOptionsFlag = { isDeleted: { $eq: false } }; + + const stores: Warehouse[] = ( + await this._warehousesService.find(commonOptionsFlag) + ).filter((__, index) => index <= 20); + + const carriers: Carrier[] = await this._carriersService.find( + commonOptionsFlag + ); + + const products: Product[] = await this._productsService.find( + commonOptionsFlag + ); + + const user = await this._usersService + .get(customerId) + .pipe(first()) + .toPromise(); + + if (products.length > 0) { + const rawOrders = []; + for ( + let orderNumber = 1; + orderNumber <= numberOfOrders; + orderNumber += 1 + ) { + const { + numberOfProductsToOrder, + hasCarrier, + orderIsPaid, + } = this._prepareOrderFieldsValues(orderNumber); + + const orderProducts = []; + + for (let i = 0; i < numberOfProductsToOrder; i += 1) { + const orderPrice = (orderNumber + i) % 110 || 1; + orderProducts.push({ + count: (orderNumber + i) % 6 || 1, + isManufacturing: true, + isCarrierRequired: hasCarrier, + isDeliveryRequired: hasCarrier, + price: orderPrice, + initialPrice: orderPrice, + product: this._getRandomProduct( + orderNumber + i, + products + ), + }); + } + + const orderDate = this._getRandomOrderDate(orderNumber); + const orderDeliveryTime = new Date(orderDate); + + // delivery time should be max to 2 hrs + orderDeliveryTime.setMinutes( + orderDeliveryTime.getMinutes() + + Math.round(Math.random() * 90) + ); + + const startDeliveryTime = this.getFinishedTime(orderDate); + + rawOrders.push({ + isCancelled: !orderIsPaid, + isPaid: orderIsPaid, + deliveryTimeEstimate: 0, + startDeliveryTime, + deliveryTime: orderIsPaid + ? this.getFinishedTime(startDeliveryTime) + : null, + finishedProcessingTime: !orderIsPaid + ? this.getFinishedTime(startDeliveryTime) + : null, + warehouseStatus: OrderWarehouseStatus.PackagingFinished, + carrierStatus: OrderCarrierStatus.DeliveryCompleted, + orderNumber, + user, + warehouse: stores[orderNumber % stores.length].id, + products: orderProducts, + _createdAt: orderDate, + ...(hasCarrier && { + carrier: this._getRandomCarrierId( + orderNumber, + carriers + ), + }), + }); + } + + await this._ordersService.Model.insertMany(rawOrders); + } + } + + @Query() + getOrdersChartTotalOrders() { + return this._ordersService.getOrdersChartTotalOrders(); + } + + @Query() + async getCompletedOrdersInfo(_context, { storeId }: { storeId: string }) { + const orders = await this._ordersService.getDashboardCompletedOrders( + storeId + ); + return { + totalOrders: orders.length, + totalRevenue: orders + .map((order) => order.totalPrice) + .reduce((prevPrice, nextPrice) => prevPrice + nextPrice, 0), + }; + } + + @Query() + async getDashboardCompletedOrders() { + return this._ordersService.getDashboardCompletedOrders(); + } + + @Query() + async getDashboardCompletedOrdersToday() { + return this._ordersService.getDashboardCompletedOrdersToday(); + } + + @Query('getOrder') + async getOrder(_context, { id }: { id: string }): Promise { + return firstValueFrom( + this._ordersService.get(id) + ); + } + + @Query('orders') + async getOrders(_context, { findInput }): Promise { + return this._ordersService.find({ + ...findInput, + isDeleted: { $eq: false }, + }); + } + + @Query() + async getOrderedUsersInfo(_context, { storeId }: { storeId: string }) { + return this._ordersService.getOrderedUsersInfo(storeId); + } + + @Query() + async getUsersOrdersCountInfo( + _context, + { usersIds }: { usersIds: string[] } + ) { + const ordersInfo = await this._ordersService.Model.aggregate([ + { + $match: { + $and: [ + { 'user._id': { $ne: null } }, + usersIds + ? { + 'user._id': { + $in: usersIds.map( + (i) => new ObjectId(i) + ), + }, + } + : {}, + ], + }, + }, + { + $group: { + _id: '$user._id', + ordersCount: { $sum: 1 }, + }, + }, + ]); + + return ordersInfo.map((o) => ({ + id: o._id, + ordersCount: o.ordersCount, + })); + } + + @Query() + async getMerchantsOrdersCountInfo( + _context, + { merchantsIds }: { merchantsIds: string[] } + ) { + const ordersInfo = await this._ordersService.Model.aggregate([ + { + $match: { + $and: [ + { warehouse: { $ne: null } }, + merchantsIds + ? { warehouse: { $in: merchantsIds } } + : {}, + ], + }, + }, + { + $group: { + _id: '$warehouse', + ordersCount: { $sum: 1 }, + }, + }, + ]); + + return ordersInfo.map((o) => ({ + id: o._id, + ordersCount: o.ordersCount, + })); + } + + @Mutation() + async updateOrderCarrierStatus( + _context, + { + orderId, + status, + }: { + orderId: Order['id']; + status: string; + } + ): Promise { + return this._ordersService.updateCarrierStatus( + orderId, + OrderCarrierStatus[status] + ); + } + + @Mutation() + async updateOrderWarehouseStatus( + _context, + { + orderId, + status, + }: { + orderId: Order['id']; + status: string; + } + ): Promise { + return this._ordersService.updateWarehouseStatus( + orderId, + OrderWarehouseStatus[status] + ); + } + + @Mutation() + async payOrderWithStripe( + _context, + { + orderId, + cardId, + }: { + orderId: Order['id']; + cardId: string; + } + ): Promise { + return this._ordersService.payWithStripe(orderId, cardId); + } + + @ResolveField('carrier') + async getCarrier(_order: IOrder): Promise { + const order = new Order(_order); + + return order.carrierId == null + ? null + : this._carriersService + .get(order.carrierId) + .pipe(first()) + .toPromise(); + } + + @ResolveField('warehouse') + async getWarehouse(_order: IOrder): Promise { + const order = new Order(_order); + + return this._warehousesService + .get(order.warehouseId) + .pipe(first()) + .toPromise(); + } + + private getFinishedTime(date: Date): Date { + const randomMinutes = _.random(1, 30); + const randomSec = _.random(1, 60); + const oldDate = new Date(date); + + oldDate.setSeconds(randomSec); + + return new Date(oldDate.setMinutes(date.getMinutes() + randomMinutes)); + } + + private getCloseDate(date: Date): Date { + const randomMinutes = _.random(1, 10); + const randomSec = _.random(1, 60); + const oldDate = new Date(date); + + oldDate.setSeconds(randomSec); + + return new Date(oldDate.setMinutes(date.getMinutes() - randomMinutes)); + } +} diff --git a/packages/core/src/graphql/orders/orders.module.ts b/packages/core/src/graphql/orders/orders.module.ts new file mode 100644 index 0000000..2fdd589 --- /dev/null +++ b/packages/core/src/graphql/orders/orders.module.ts @@ -0,0 +1,7 @@ +import { Module } from '@nestjs/common'; +import { OrderResolver } from './order.resolver'; + +@Module({ + providers: [OrderResolver], +}) +export class OrdersModule {} diff --git a/packages/core/src/graphql/orders/orders.types.graphql b/packages/core/src/graphql/orders/orders.types.graphql new file mode 100644 index 0000000..b9b102a --- /dev/null +++ b/packages/core/src/graphql/orders/orders.types.graphql @@ -0,0 +1,188 @@ +type Order { + _id: String! + id: String! + user: User! + warehouse: Warehouse! + warehouseId: String! + + # Some carrier which responsible to deliver order to the client + # (can be empty if order is not planed for delivery yet) + carrier: Carrier + carrierId: String + + # products: [Any!]! + products: [OrderProduct!]! + + isConfirmed: Boolean! + isCancelled: Boolean! + waitForCompletion: Boolean + isPaid: Boolean! + isCompleted: Boolean! + totalPrice: Float! + orderType: Int + + # DateTime when order was actually delivered to customer + deliveryTime: Date + + finishedProcessingTime: Date + startDeliveryTime: Date + + # how many seconds more it should take to delivery order to customer + deliveryTimeEstimate: Int + + warehouseStatus: Int! + carrierStatus: Int! + + # Used for quick destinction between orders made in the same day (For packaging and etc) + orderNumber: Int! + + carrierStatusText: String! + warehouseStatusText: String! + + status: Int + + createdAt: Date + _createdAt: Date + updatedAt: Date + _updatedAt: Date +} + +type OrderProduct { + _id: String! + count: Int! + isManufacturing: Boolean! + isCarrierRequired: Boolean! + isDeliveryRequired: Boolean! + isTakeaway: Boolean + initialPrice: Float! + price: Float! + product: Product! + comment: String +} + +input OrdersFindInput { + user: String + + # Warehouse id + warehouse: String + + # Carrier id + carrier: String + + ## products: TODO complex filter + + isConfirmed: Boolean + isCancelled: Boolean + isPaid: Boolean + warehouseStatus: Int! + carrierStatus: Int! + orderNumber: Int +} + +type OrderChartPanel { + isCancelled: Boolean! + isCompleted: Boolean! + totalPrice: Float! + _createdAt: Date! +} + +type DashboardCompletedOrder { + warehouseId: String! + totalPrice: Float! +} + +type CompletedOrderInfo { + totalOrders: Int! + totalRevenue: Float! +} + +type OrderedUserInfo { + user: User! + ordersCount: Int! + totalPrice: Float! +} + +type OrderCountTnfo { + id: String + ordersCount: Int +} + +type GenerateOrdersResponse { + error: Boolean! + message: String +} + +type Query { + # If order query doesn't work for you it may be that you use 'order' instead 'getOrder' query + getOrder(id: String!): Order + + orders(findInput: OrdersFindInput): [Order!]! + + getDashboardCompletedOrders: [DashboardCompletedOrder!]! + + getDashboardCompletedOrdersToday: [Order!]! + + getOrdersChartTotalOrders: [OrderChartPanel!]! + + getCompletedOrdersInfo(storeId: String): CompletedOrderInfo! + + getOrderedUsersInfo(storeId: String!): [OrderedUserInfo!]! + + generateOrdersByCustomerId(numberOfOrders: Int!, customerId: String!): Void + + addTakenOrders(carrierIds: [String!]!): Void + + addOrdersToTake: Void + + generateActiveAndAvailableOrdersPerCarrier: Void + + generatePastOrdersPerCarrier: Void + + getUsersOrdersCountInfo(usersIds: [String!]): [OrderCountTnfo] + + getMerchantsOrdersCountInfo(merchantsIds: [String!]): [OrderCountTnfo] + + generateRandomOrdersCurrentStore( + storeId: String! + storeCreatedAt: Date! + ordersLimit: Int! + ): GenerateOrdersResponse! +} + +type Mutation { + updateOrderCarrierStatus( + orderId: String! + status: OrderCarrierStatus! + ): Order! + + updateOrderWarehouseStatus( + orderId: String! + status: OrderWarehouseStatus! + ): Order + + payOrderWithStripe(orderId: String!, cardId: String!): Order +} + +enum OrderWarehouseStatus { + NoStatus #0 + ReadyForProcessing #1 + WarehouseStartedProcessing #2 + AllocationStarted #3 + AllocationFinished #4 + PackagingStarted #5 + PackagingFinished #6 + GivenToCarrier #7 + AllocationFailed #200 + PackagingFailed #201 +} + +enum OrderCarrierStatus { + NoCarrier #0 + CarrierSelectedOrder #1 + CarrierPickedUpOrder #2 + CarrierStartDelivery #3 + CarrierArrivedToCustomer #4 + DeliveryCompleted #5 + IssuesDuringDelivery #204 + ClientRefuseTakingOrder #205 +} diff --git a/packages/core/src/graphql/products/categories/products-categories.module.ts b/packages/core/src/graphql/products/categories/products-categories.module.ts new file mode 100644 index 0000000..5799eab --- /dev/null +++ b/packages/core/src/graphql/products/categories/products-categories.module.ts @@ -0,0 +1,7 @@ +import { Module } from '@nestjs/common'; +import { ProductsCategoryResolver } from './products-category.resolver'; + +@Module({ + providers: [ProductsCategoryResolver], +}) +export class ProductsCategoriesModule {} diff --git a/packages/core/src/graphql/products/categories/products-categories.types.graphql b/packages/core/src/graphql/products/categories/products-categories.types.graphql new file mode 100644 index 0000000..4283b55 --- /dev/null +++ b/packages/core/src/graphql/products/categories/products-categories.types.graphql @@ -0,0 +1,47 @@ +type ProductsCategory { + _id: String! + id: String! + name: [TranslateType!]! + image: String + _createdAt: Date + _updatedAt: Date +} + +input ProductsCategoryInput { + _id: String! + name: [TranslateInput!] +} + +input ProductsCategoriesFindInput { + noop: Void # placeholder +} + +type Query { + productsCategory(id: String!): ProductsCategory + + productsCategories( + findInput: ProductsCategoriesFindInput + ): [ProductsCategory!]! +} + +input ProductsCategoriesCreateInput { + name: [TranslateInput!]! + image: String +} + +input ProductsCategoriesUpdatenput { + name: [TranslateInput!] +} + +type Mutation { + createProductsCategory( + createInput: ProductsCategoriesCreateInput + ): ProductsCategory! + + updateProductsCategory( + id: String! + updateInput: ProductsCategoriesCreateInput! + ): ProductsCategory! + + removeProductsCategoriesByIds(ids: [String!]!): Remove +} diff --git a/packages/core/src/graphql/products/categories/products-category.resolver.ts b/packages/core/src/graphql/products/categories/products-category.resolver.ts new file mode 100644 index 0000000..47486a4 --- /dev/null +++ b/packages/core/src/graphql/products/categories/products-category.resolver.ts @@ -0,0 +1,61 @@ +import { Mutation, Query, Resolver } from '@nestjs/graphql'; +import { ProductsCategoriesService } from '../../../services/products'; +import { IProductsCategoryCreateObject } from '@modules/server.common/interfaces/IProductsCategory'; +import { first } from 'rxjs/operators'; + +@Resolver('ProductsCategory') +export class ProductsCategoryResolver { + constructor( + private readonly _productsCategoriesService: ProductsCategoriesService + ) {} + + @Query('productsCategory') + async getProductsCategory(_, { id }: { id: string }) { + return this._productsCategoriesService + .get(id) + .pipe(first()) + .toPromise(); + } + + @Query('productsCategories') + async getProductsCategories(_, { findInput }) { + return this._productsCategoriesService.find({ + ...findInput, + isDeleted: { $eq: false }, + }); + } + + @Mutation() + async createProductsCategory( + _, + { createInput }: { createInput: IProductsCategoryCreateObject } + ) { + return this._productsCategoriesService.create(createInput); + } + + @Mutation() + async updateProductsCategory( + _, + { + id, + updateInput, + }: { id: string; updateInput: IProductsCategoryCreateObject } + ) { + await this._productsCategoriesService.throwIfNotExists(id); + return this._productsCategoriesService.update(id, updateInput); + } + + @Mutation() + async removeProductsCategoriesByIds(_, { ids }: { ids: string[] }) { + const categories = await this._productsCategoriesService.find({ + _id: { $in: ids }, + isDeleted: { $eq: false }, + }); + + const categoriesIds = categories.map((c) => c.id); + + return this._productsCategoriesService.removeMultipleByIds( + categoriesIds + ); + } +} diff --git a/packages/core/src/graphql/products/product.resolver.ts b/packages/core/src/graphql/products/product.resolver.ts new file mode 100644 index 0000000..860cacb --- /dev/null +++ b/packages/core/src/graphql/products/product.resolver.ts @@ -0,0 +1,92 @@ +import { Resolver, Query, Mutation } from '@nestjs/graphql'; +import { first } from 'rxjs/operators'; +import { ProductsService } from '../../services/products'; +import { + WarehousesService, + WarehousesProductsService, +} from '../../services/warehouses'; +import Product from '@modules/server.common/entities/Product'; + +@Resolver('Product') +export class ProductResolver { + constructor( + private readonly _productsService: ProductsService, + private readonly _warehousesService: WarehousesService, + private readonly _warehousesProductsService: WarehousesProductsService + ) {} + + @Query('product') + async getProduct(_, { id }: { id: string }) { + return this._productsService.get(id).pipe(first()).toPromise(); + } + + @Query('products') + async getProducts( + _, + { findInput, pagingOptions = {}, existedProductsIds = [] } + ) { + if (!pagingOptions || (pagingOptions && !pagingOptions['sort'])) { + pagingOptions['sort'] = { field: '_createdAt', sortBy: 'desc' }; + } + + const products = await this._productsService.getProducts( + findInput, + pagingOptions, + existedProductsIds + ); + + return products.map((p) => new Product(p)); + } + + @Query() + async getCountOfProducts(_, { existedProductsIds = [] }) { + return this._productsService.Model.find({ + isDeleted: { $eq: false }, + _id: { $nin: existedProductsIds }, + }) + .countDocuments() + .exec(); + } + + @Mutation() + async removeProductsByIds(_, { ids }: { ids: string[] }) { + const warehouses = await this._warehousesService.find({ + isDeleted: { $eq: false }, + }); + const products = await this._productsService.find({ + _id: { $in: ids }, + isDeleted: { $eq: false }, + }); + const productsIds = products.map((d) => d.id); + + for (const warehouse of warehouses) { + const productsForDel = warehouse.products + .filter((p) => productsIds.includes(p.productId)) + .map((p) => p.productId); + + if (productsForDel.length > 0) { + await this._warehousesProductsService.remove( + warehouse.id, + productsForDel + ); + } + } + + await this._productsService.removeMultipleByIds(productsIds); + } + + @Mutation() + // @UseGuards(AuthGuard('jwt')) + async saveProduct(_, { product }: { product }) { + const productId = product['_id']; + await this._productsService.throwIfNotExists(productId); + + product.id = productId; + return this._productsService.save(product); + } + + @Mutation() + async createProduct(_, { product }: { product }) { + return this._productsService.create(product); + } +} diff --git a/packages/core/src/graphql/products/products.module.ts b/packages/core/src/graphql/products/products.module.ts new file mode 100644 index 0000000..299a621 --- /dev/null +++ b/packages/core/src/graphql/products/products.module.ts @@ -0,0 +1,9 @@ +import { Module } from '@nestjs/common'; +import { ProductResolver } from './product.resolver'; +import { ProductsCategoriesModule } from './categories/products-categories.module'; + +@Module({ + imports: [ProductsCategoriesModule], + providers: [ProductResolver], +}) +export class ProductsModule {} diff --git a/packages/core/src/graphql/products/products.types.graphql b/packages/core/src/graphql/products/products.types.graphql new file mode 100644 index 0000000..47b71d4 --- /dev/null +++ b/packages/core/src/graphql/products/products.types.graphql @@ -0,0 +1,96 @@ +type Product { + _id: String! + id: String! + title: [TranslateType!]! + description: [TranslateType!]! + details: [TranslateType!]! + images: [ImageType!]! + categories: [String] + detailsHTML: [TranslateType!]! + descriptionHTML: [TranslateType!]! + _createdAt: Date + _updatedAt: Date +} + +type Category { + id: String + name: [TranslateType!]! +} + +type TranslateType { + locale: String! + value: String! +} + +type ImageType { + locale: String! + url: String! + width: Int! + height: Int! + orientation: Int! +} + +type Query { + product(id: String!): Product + + products( + findInput: ProductsFindInput + pagingOptions: PagingOptionsInput + existedProductsIds: [String] + ): [Product!] + + getCountOfProducts(existedProductsIds: [String]): Int! +} + +input ProductsFindInput { + title: TranslateInput + description: TranslateInput + details: TranslateInput + image: ImageInput +} + +input TranslateInput { + locale: String! + value: String! +} + +input ImageInput { + locale: String! + url: String! + width: Int! + height: Int! + orientation: Int! +} + +input ProductCreateInput { + title: [TranslateInput!]! + description: [TranslateInput!]! + details: [TranslateInput!] + images: [ImageInput!]! + categories: [ProductsCategoryInput!] + detailsHTML: [TranslateInput!] + descriptionHTML: [TranslateInput!] +} + +input ProductSaveInput { + _id: String! + id: String + title: [TranslateInput!]! + description: [TranslateInput!]! + details: [TranslateInput!] + images: [ImageInput!]! + categories: [ProductsCategoryInput!] + detailsHTML: [TranslateInput!] + descriptionHTML: [TranslateInput!] +} + +type Mutation { + createProduct(product: ProductCreateInput!): Product! + saveProduct(product: ProductSaveInput!): Product! + removeProductsByIds(ids: [String!]!): Remove +} + +type Remove { + n: Int + ok: Int +} diff --git a/packages/core/src/graphql/products/promotions/promotion.module.ts b/packages/core/src/graphql/products/promotions/promotion.module.ts new file mode 100644 index 0000000..3718519 --- /dev/null +++ b/packages/core/src/graphql/products/promotions/promotion.module.ts @@ -0,0 +1,7 @@ +import { Module } from '@nestjs/common'; +import { PromotionResolver } from './promotion.resolver'; + +@Module({ + providers: [PromotionResolver], +}) +export class PromotionModule {} diff --git a/packages/core/src/graphql/products/promotions/promotion.resolver.ts b/packages/core/src/graphql/products/promotions/promotion.resolver.ts new file mode 100644 index 0000000..470450e --- /dev/null +++ b/packages/core/src/graphql/products/promotions/promotion.resolver.ts @@ -0,0 +1,49 @@ +import { Resolver, Query, Mutation } from '@nestjs/graphql'; +import { IPromotionCreateObject } from '@modules/server.common/interfaces/IPromotion'; +import { PromotionService } from '../../../services/products/PromotionService'; +import Promotion from '@modules/server.common/entities/Promotion'; + +@Resolver('Promotion') +export class PromotionResolver { + constructor(private readonly _promotionService: PromotionService) {} + + @Query('promotions') + async getPromotions(_context, { findInput }) { + return this._promotionService.getAllPromotions(findInput); + } + + @Mutation('createPromotion') + async createPromotion( + _, + { createInput }: { createInput: IPromotionCreateObject } + ) { + return this._promotionService.createPromotion(createInput); + } + + @Mutation() + async removePromotion(_, { id }: { id: string }): Promise { + await this._promotionService.throwIfNotExists(id); + return this._promotionService.remove(id); + } + + @Mutation() + async removePromotionsByIds(_, { ids }: { ids: string[] }): Promise { + const promotions = await this._promotionService.find({ + _id: { $in: ids }, + isDeleted: { $eq: false }, + }); + + const promotionsIds = promotions.map((p) => p.id); + + return this._promotionService.removeMultipleByIds(promotionsIds); + } + + @Mutation('updatePromotion') + async updatePromotion( + _, + { id, updateInput }: { id; updateInput } + ): Promise { + await this._promotionService.throwIfNotExists(id); + return this._promotionService.update(id, updateInput); + } +} diff --git a/packages/core/src/graphql/products/promotions/promotion.types.graphql b/packages/core/src/graphql/products/promotions/promotion.types.graphql new file mode 100644 index 0000000..274fff2 --- /dev/null +++ b/packages/core/src/graphql/products/promotions/promotion.types.graphql @@ -0,0 +1,61 @@ +type Promotion { + _id: String + title: [TranslateType] + description: [TranslateType] + promoPrice: Float + warehouse: Warehouse + product: Product + warehouseId: String + productId: String + active: Boolean + activeFrom: Date + activeTo: Date + image: String + purchasesCount: Int +} + +type TranslateType { + locale: String! + value: String! +} + +type Product { + _id: String! +} + +input PromotionInput { + title: [TranslateInput] + description: [TranslateInput] + promoPrice: Float + warehouse: WarehouseInput + active: Boolean + activeFrom: Date + activeTo: Date + image: String + product: String + purchasesCount: Int +} + +input PromotionsFindInput { + warehouse: String +} + +input WarehouseInput { + _id: String +} + +input TranslateInput { + locale: String! + value: String! +} + +type Query { + promotions(findInput: PromotionsFindInput): [Promotion] +} + +type Mutation { + createPromotion(createInput: PromotionInput): Promotion + updatePromotion(id: String, updateInput: PromotionInput): Promotion + removePromotion(id: String!): Void + removePromotionsByIds(ids: [String!]!): Remove +} diff --git a/packages/core/src/graphql/scalars/any.type.graphql b/packages/core/src/graphql/scalars/any.type.graphql new file mode 100644 index 0000000..1c6fb4d --- /dev/null +++ b/packages/core/src/graphql/scalars/any.type.graphql @@ -0,0 +1 @@ +scalar Any diff --git a/packages/core/src/graphql/scalars/date.type.graphql b/packages/core/src/graphql/scalars/date.type.graphql new file mode 100644 index 0000000..5e1cdad --- /dev/null +++ b/packages/core/src/graphql/scalars/date.type.graphql @@ -0,0 +1 @@ +scalar Date diff --git a/packages/core/src/graphql/scalars/date.type.graphql.ts b/packages/core/src/graphql/scalars/date.type.graphql.ts new file mode 100644 index 0000000..8714b0b --- /dev/null +++ b/packages/core/src/graphql/scalars/date.type.graphql.ts @@ -0,0 +1,18 @@ +import { GraphQLScalarType } from 'graphql'; +import { Kind } from 'graphql/language'; + +export const DateScalar = new GraphQLScalarType({ + name: 'Date', + parseValue(value: string | number | Date) { + return new Date(value); // value from the client + }, + serialize(value: { getTime: () => void }) { + return value.getTime(); // value sent to the client + }, + parseLiteral(ast) { + if (ast.kind === Kind.INT) { + return new Date(ast.value); // ast value is always in string format + } + return null; + }, +}); diff --git a/packages/core/src/graphql/scalars/index.ts b/packages/core/src/graphql/scalars/index.ts new file mode 100644 index 0000000..cc30e48 --- /dev/null +++ b/packages/core/src/graphql/scalars/index.ts @@ -0,0 +1,2 @@ +export * from './date.type.graphql'; +export * from './scalars'; diff --git a/packages/core/src/graphql/scalars/scalars.ts b/packages/core/src/graphql/scalars/scalars.ts new file mode 100644 index 0000000..fef9a1e --- /dev/null +++ b/packages/core/src/graphql/scalars/scalars.ts @@ -0,0 +1,5 @@ +import { DateScalar } from './date.type.graphql'; + +export const SCALARS = { + Date: DateScalar, +}; diff --git a/packages/core/src/graphql/scalars/void.type.graphql b/packages/core/src/graphql/scalars/void.type.graphql new file mode 100644 index 0000000..63b8b31 --- /dev/null +++ b/packages/core/src/graphql/scalars/void.type.graphql @@ -0,0 +1 @@ +scalar Void diff --git a/packages/core/src/graphql/subscriptions/subscription.constants.ts b/packages/core/src/graphql/subscriptions/subscription.constants.ts new file mode 100644 index 0000000..5ee560f --- /dev/null +++ b/packages/core/src/graphql/subscriptions/subscription.constants.ts @@ -0,0 +1 @@ +export const SUBSCRIPTION_SERVER = 'SUBSCRIPTION_SERVER'; diff --git a/packages/core/src/graphql/subscriptions/subscription.providers.ts b/packages/core/src/graphql/subscriptions/subscription.providers.ts new file mode 100644 index 0000000..9f7c5e6 --- /dev/null +++ b/packages/core/src/graphql/subscriptions/subscription.providers.ts @@ -0,0 +1,33 @@ +import { createServer } from 'http'; + +import { SUBSCRIPTION_SERVER } from './subscription.constants'; + +export const createSubscriptionProviders = (port: number) => [ + { + provide: SUBSCRIPTION_SERVER, + + useFactory: () => { + const server = createServer(); + + const closeServer = () => { + try { + if (server != null) { + server.close(() => { + process.exit(0); + }); + } + } catch (err) { + process.exit(0); + } + }; + + process + .on('SIGINT', () => closeServer()) + .on('SIGTERM', () => closeServer()); + + return new Promise((resolve) => + server.listen(port, () => resolve(server)) + ); + }, + }, +]; diff --git a/packages/core/src/graphql/subscriptions/subscriptions.module.ts b/packages/core/src/graphql/subscriptions/subscriptions.module.ts new file mode 100644 index 0000000..637d261 --- /dev/null +++ b/packages/core/src/graphql/subscriptions/subscriptions.module.ts @@ -0,0 +1,18 @@ +import { DynamicModule, Module } from '@nestjs/common'; +import { createSubscriptionProviders } from './subscription.providers'; +import { SubscriptionsService } from './subscriptions.service'; + +@Module({ + providers: [SubscriptionsService], + exports: [SubscriptionsService], +}) +export class SubscriptionsModule { + static forRoot(port: number): DynamicModule { + const providers = createSubscriptionProviders(port); + return { + module: SubscriptionsModule, + providers: [...providers], + exports: [...providers], + }; + } +} diff --git a/packages/core/src/graphql/subscriptions/subscriptions.service.ts b/packages/core/src/graphql/subscriptions/subscriptions.service.ts new file mode 100644 index 0000000..5b652c1 --- /dev/null +++ b/packages/core/src/graphql/subscriptions/subscriptions.service.ts @@ -0,0 +1,39 @@ +import WebSocket from 'ws'; +import { Inject, Injectable, OnModuleDestroy } from '@nestjs/common'; +import { SUBSCRIPTION_SERVER } from './subscription.constants'; +import { ServerOptions, SubscriptionServer } from 'subscriptions-transport-ws'; +import { execute, subscribe } from 'graphql'; + +@Injectable() +export class SubscriptionsService implements OnModuleDestroy { + private subscriptionServer: SubscriptionServer; + + constructor(@Inject(SUBSCRIPTION_SERVER) private readonly ws) {} + + createSubscriptionServer( + schema: any, + options: ServerOptions = {}, + socketOptions: WebSocket.ServerOptions = {} + ) { + + const o: ServerOptions = { + execute, + subscribe, + schema, + ...options, + }; + + this.subscriptionServer = new SubscriptionServer( + o, + { + server: this.ws, + path: '/subscriptions', + ...socketOptions, + } + ); + } + + onModuleDestroy() { + this.ws.close(); + } +} diff --git a/packages/core/src/graphql/users/user.resolver.ts b/packages/core/src/graphql/users/user.resolver.ts new file mode 100644 index 0000000..fb323a7 --- /dev/null +++ b/packages/core/src/graphql/users/user.resolver.ts @@ -0,0 +1,212 @@ +import { Mutation, Query, ResolveField, Resolver } from '@nestjs/graphql'; +import { UsersOrdersService, UsersService } from '../../services/users'; +import { first } from 'rxjs/operators'; +import { + default as IUser, + IUserCreateObject, + IResponseGenerate1000Customers, +} from '@modules/server.common/interfaces/IUser'; +import User from '@modules/server.common/entities/User'; +import { DevicesService } from '../../services/devices'; +import { UsersAuthService } from '../../services/users/UsersAuthService'; +import { + AddableRegistrationInfo, + IUserRegistrationInput, +} from '@modules/server.common/routers/IUserAuthRouter'; +import { ObjectId } from 'mongodb'; +import { OrdersService } from '../../services/orders'; +import { UseGuards } from '@nestjs/common'; +import { FakeDataGuard } from '../../auth/guards/fake-data.guard'; + +@Resolver('User') +export class UserResolver { + constructor( + private readonly _usersService: UsersService, + private readonly _usersAuthService: UsersAuthService, + private readonly _usersOrdersService: UsersOrdersService, + private readonly _devicesService: DevicesService, + private readonly _ordersService: OrdersService + ) {} + + @Query() + async isUserEmailExists(_, { email }: { email: string }): Promise { + return this._usersService.isUserEmailExists(email); + } + + @Query() + @UseGuards(FakeDataGuard) + async generate1000Customers( + _, + { defaultLng, defaultLat }: { defaultLng: number; defaultLat: number } + ): Promise { + let success = true; + let message = null; + + try { + await this._ordersService.generateOrdersPerEachCustomer( + await this._usersService.generate1000Customers( + defaultLng, + defaultLat + ) + ); + } catch (err) { + message = err.message; + success = false; + } + + return { + success, + message, + }; + } + + @Query() + async getSocial(_, { socialId }: { socialId: string }) { + return this._usersService.getSocial(socialId); + } + + @Query('isUserExists') + async userExists(_, { conditions }): Promise { + const userId = conditions.exceptCustomerId; + const memberKey = conditions.memberKey; + const memberValue = conditions.memberValue; + + return ( + (await this._usersService.count({ + _id: { $nin: [new ObjectId(userId)] }, + isDeleted: { $eq: false }, + [memberKey]: memberValue, + })) > 0 + ); + } + + @Query('user') + async getUser(_, { id }) { + return this._usersService.get(id).pipe(first()).toPromise(); + } + + @Query('users') + async getUsers(_, { findInput, pagingOptions = {} }) { + if (!pagingOptions || (pagingOptions && !pagingOptions['sort'])) { + pagingOptions['sort'] = { field: '_createdAt', sortBy: 'desc' }; + } + + const users = await this._usersService.getUsers( + findInput, + pagingOptions + ); + + return users.map((u) => new User(u)); + } + + @Query('getOrders') + async getOrders(_, { userId }) { + await this._usersService.throwIfNotExists(userId); + + const result = await this._usersOrdersService + .get(userId) + .pipe(first()) + .toPromise(); + + return result; + } + + @Query() + async getCountOfUsers() { + return this._usersService.Model.find({ isDeleted: { $eq: false } }) + .countDocuments() + .exec(); + } + + @Query() + async getCustomerMetrics(_, { id }: { id: string }) { + return this._usersOrdersService.getCustomerMetrics(id); + } + + @Mutation() + async updateUser( + _, + { id, updateObject }: { id: string; updateObject: IUserCreateObject } + ) { + return this._usersService.updateUser(id, updateObject); + } + + @Mutation() + async updateUserEmail( + _, + { userId, email }: { userId: string; email: string } + ) { + return this._usersService.updateEmail(userId, email); + } + + @Mutation() + async registerUser( + _, + { registerInput }: { registerInput: IUserRegistrationInput } + ) { + return this._usersAuthService.register(registerInput); + } + + @Mutation() + async userLogin( + _, + { email, password }: { email: string; password: string } + ) { + return this._usersAuthService.login(email, password); + } + + @Mutation() + async removeUsersByIds(obj, { ids }: { ids: string[] }) { + const users = await this._usersService.find({ + _id: { $in: ids }, + isDeleted: { $eq: false }, + }); + + const usersIds = users.map((u) => u.id); + + return this._usersService.removeMultipleByIds(usersIds); + } + + @ResolveField('devices') + async getDevices(_user: IUser) { + const user = new User(_user); + return ( + (await this._devicesService.getMultipleDevices(user.devicesIds)) + // .getMultiple(user.devicesIds) + .pipe(first()) + .toPromise() + ); + } + + @Mutation() + async updateUserPassword( + _, + { + id, + password, + }: { id: User['id']; password: { current: string; new: string } } + ) { + return this._usersAuthService.updatePassword(id, password); + } + + @Mutation() + async addUserRegistrationInfo( + _, + { + id, + registrationInfo, + }: { id: User['id']; registrationInfo: AddableRegistrationInfo } + ) { + return this._usersAuthService.addRegistrationInfo(id, registrationInfo); + } + + @Mutation() + async banUser(_, { id }: { id: User['id'] }) { + return this._usersService.banUser(id); + } + + @Mutation() + async unbanUser(_, { id }: { id: User['id'] }) { + return this._usersService.unbanUser(id); + } +} diff --git a/packages/core/src/graphql/users/users.module.ts b/packages/core/src/graphql/users/users.module.ts new file mode 100644 index 0000000..e08a050 --- /dev/null +++ b/packages/core/src/graphql/users/users.module.ts @@ -0,0 +1,7 @@ +import { Module } from '@nestjs/common'; +import { UserResolver } from './user.resolver'; + +@Module({ + providers: [UserResolver], +}) +export class UsersModule {} diff --git a/packages/core/src/graphql/users/users.types.graphql b/packages/core/src/graphql/users/users.types.graphql new file mode 100644 index 0000000..6aeeabf --- /dev/null +++ b/packages/core/src/graphql/users/users.types.graphql @@ -0,0 +1,156 @@ +type User { + _id: String! + id: String! + geoLocation: GeoLocation! + apartment: String! + firstName: String + lastName: String + email: String + phone: String + devicesIds: [String!]! + + # Resolved + devices: [Device!]! + + # URL of the image + image: String + + fullAddress: String + + createdAt: Date + + isBanned: Boolean! +} + +input UserCreateInput { + email: String + firstName: String + lastName: String + phone: String + + # URL of the image + image: String + geoLocation: GeoLocationCreateInput! + apartment: String! +} + +input UserFindInput { + firstName: String + lastName: String + email: String + phone: String + apartment: String + + # URL of the image + image: String + + # TODO geoLocation +} + +type Query { + user(id: String!): User + + users(findInput: UserFindInput, pagingOptions: PagingOptionsInput): [User]! + + getOrders(userId: String!): [Order!]! + + isUserExists(conditions: UserMemberInput!): Boolean! + + getSocial(socialId: String!): User + + isUserEmailExists(email: String!): Boolean! + + generate1000Customers( + defaultLng: Float! + defaultLat: Float! + ): ResponseGenerate1000Customers + + getCountOfUsers: Int! + + getCustomerMetrics(id: String!): CustomerMetrics +} + +type ResponseGenerate1000Customers { + success: Boolean! + message: String +} + +type UserLoginInfo { + user: User! + token: String! +} + +type CustomerMetrics { + totalOrders: Int + canceledOrders: Int + completedOrdersTotalSum: Float +} + +input UserMemberInput { + exceptCustomerId: String + memberKey: String! + memberValue: String! +} + +input UserRegisterInput { + user: UserCreateInput! + password: String +} + +input UserPasswordUpdateInput { + current: String! + new: String! +} + +input AdditionalUserRegistrationInfo { + email: String! + password: String! + firstName: String + lastName: String + phone: String +} + +input GeoLocationUpdateObjectInput { + loc: Location! +} + +input UserUpdateObjectInput { + geoLocation: GeoLocationUpdateObjectInput! + devicesIds: [String!] + apartment: String + stripeCustomerId: String +} + +input PagingOptionsInput { + sort: PagingSortInput + limit: Int + skip: Int +} + +input PagingSortInput { + field: String! + sortBy: String! +} + +type Mutation { + updateUser(id: String!, updateObject: UserUpdateObjectInput!): User! + + updateUserEmail(userId: String!, email: String!): User! + + registerUser(registerInput: UserRegisterInput!): User! + + userLogin(email: String!, password: String!): UserLoginInfo + + removeUsersByIds(ids: [String]!): String! + + updateUserPassword(id: String!, password: UserPasswordUpdateInput!): Void + + addUserRegistrationInfo( + id: String! + registrationInfo: AdditionalUserRegistrationInfo! + ): Void + + banUser(id: String!): User + + unbanUser(id: String!): User +} diff --git a/packages/core/src/graphql/warehouses-carriers/warehouses-carriers.module.ts b/packages/core/src/graphql/warehouses-carriers/warehouses-carriers.module.ts new file mode 100644 index 0000000..5a6130f --- /dev/null +++ b/packages/core/src/graphql/warehouses-carriers/warehouses-carriers.module.ts @@ -0,0 +1,8 @@ +import { Module } from '@nestjs/common'; +import { WarehouseCarriersResolver } from './warehouses-carriers.resolver'; + +@Module({ + providers: [WarehouseCarriersResolver], + imports: [], +}) +export class WarehousesCarriersModule {} diff --git a/packages/core/src/graphql/warehouses-carriers/warehouses-carriers.resolver.ts b/packages/core/src/graphql/warehouses-carriers/warehouses-carriers.resolver.ts new file mode 100644 index 0000000..8e0c6e0 --- /dev/null +++ b/packages/core/src/graphql/warehouses-carriers/warehouses-carriers.resolver.ts @@ -0,0 +1,20 @@ +import { Resolver, Query } from '@nestjs/graphql'; +import { WarehousesCarriersService } from '../../services/warehouses'; +import { first } from 'rxjs/operators'; + +@Resolver('WarehouseCarriers') +export class WarehouseCarriersResolver { + constructor( + private readonly _warehousesCarriersService: WarehousesCarriersService + ) {} + + @Query() + async getStoreCarriers(_, { storeId }: { storeId: string }) { + const result = await this._warehousesCarriersService + .get(storeId) + .pipe(first()) + .toPromise(); + + return result; + } +} diff --git a/packages/core/src/graphql/warehouses-carriers/warehouses-carriers.types.graphql b/packages/core/src/graphql/warehouses-carriers/warehouses-carriers.types.graphql new file mode 100644 index 0000000..ef89662 --- /dev/null +++ b/packages/core/src/graphql/warehouses-carriers/warehouses-carriers.types.graphql @@ -0,0 +1,3 @@ +type Query { + getStoreCarriers(storeId: String!): [Carrier!] +} diff --git a/packages/core/src/graphql/warehouses-orders/warehouses-orders.module.ts b/packages/core/src/graphql/warehouses-orders/warehouses-orders.module.ts new file mode 100644 index 0000000..ae2a0b2 --- /dev/null +++ b/packages/core/src/graphql/warehouses-orders/warehouses-orders.module.ts @@ -0,0 +1,8 @@ +import { Module } from '@nestjs/common'; +import { WarehouseOrdersResolver } from './warehouses-orders.resolver'; + +@Module({ + providers: [WarehouseOrdersResolver], + imports: [], +}) +export class WarehousesOrdersModule {} diff --git a/packages/core/src/graphql/warehouses-orders/warehouses-orders.resolver.ts b/packages/core/src/graphql/warehouses-orders/warehouses-orders.resolver.ts new file mode 100644 index 0000000..2c9623c --- /dev/null +++ b/packages/core/src/graphql/warehouses-orders/warehouses-orders.resolver.ts @@ -0,0 +1,130 @@ +import { Mutation, Resolver, Query } from '@nestjs/graphql'; +import { + WarehousesOrdersService, + getStoreOrdersFingObj, +} from '../../services/warehouses'; +import Order from '@modules/server.common/entities/Order'; +import { IOrderCreateInput } from '@modules/server.common/routers/IWarehouseOrdersRouter'; +import { first, map } from 'rxjs/operators'; +import { OrdersService } from '../../services/orders'; +import OrderStatus from '@modules/server.common/enums/OrderStatus'; +import OrderWarehouseStatus from '@modules/server.common/enums/OrderWarehouseStatus'; +import OrderCarrierStatus from '@modules/server.common/enums/OrderCarrierStatus'; +import { ObjectId } from 'bson'; + +export type OrdersFilterModes = + | 'ready' + | 'in_delivery' + | 'not_confirmed' + | 'cancelled' + | 'all'; + +@Resolver('WarehouseOrders') +export class WarehouseOrdersResolver { + constructor( + private readonly _warehousesOrdersService: WarehousesOrdersService, + private readonly _ordersService: OrdersService + ) {} + + @Query() + async getStoreOrders(_, { storeId }: { storeId: string }) { + return this._warehousesOrdersService + .get(storeId) + .pipe(first()) + .toPromise(); + } + + @Query() + async getDashboardOrdersChartOrders(_, { storeId }: { storeId: string }) { + const resOrders = this._ordersService.getStoreOrdersChartTotalOrders( + storeId + ); + + return resOrders; + } + + @Query() + async getNextOrderNumber(_, { storeId }: { storeId: string }) { + return this._warehousesOrdersService.getNextOrderNumber(storeId); + } + @Query() + async getMerchantsOrders() { + return this._ordersService.Model.aggregate([ + { $match: { warehouse: { $ne: null } } }, + { + $group: { + _id: '$warehouse', + ordersCount: { $sum: 1 }, + }, + }, + ]); + } + + @Query() + async getStoreOrdersTableData(_, { storeId, pagingOptions = {}, status }) { + if (!pagingOptions || (pagingOptions && !pagingOptions['sort'])) { + pagingOptions['sort'] = { field: '_createdAt', sortBy: 'asc' }; + } + + const orders = await this._warehousesOrdersService.getStoreOrders( + storeId, + pagingOptions, + status + ); + + let page = 1; + + if (pagingOptions) { + page = Math.ceil( + pagingOptions['skip'] / pagingOptions['limit'] + 1 + ); + } + + return { orders: orders.map((o) => new Order(o)), page }; + } + + @Query() + async getCountOfStoreOrders( + _, + { storeId, status }: { storeId: string; status: OrdersFilterModes } + ) { + const findObj = getStoreOrdersFingObj(storeId, status); + + return this._ordersService.Model.find(findObj).countDocuments().exec(); + } + + @Query() + async removeUserOrders( + _, + { storeId, userId }: { storeId: string; userId: string } + ) { + const res = await this._ordersService.Model.update( + { + 'user._id': new ObjectId(userId), + warehouse: storeId, + isDeleted: false, + }, + { isDeleted: true }, + { multi: true } + ).exec(); + + return { + number: res.n, + modified: res.nModified, + }; + } + + @Query() + async getOrdersInDelivery(_, { storeId }: { storeId: string }) { + const order = await this._ordersService.getOrdersInDelivery(storeId); + return order || []; + } + + @Mutation() + async createOrder( + _, + { createInput }: { createInput: IOrderCreateInput } + ): Promise { + return this._warehousesOrdersService.create(createInput); + } +} diff --git a/packages/core/src/graphql/warehouses-orders/warehouses-orders.types.graphql b/packages/core/src/graphql/warehouses-orders/warehouses-orders.types.graphql new file mode 100644 index 0000000..487c8c9 --- /dev/null +++ b/packages/core/src/graphql/warehouses-orders/warehouses-orders.types.graphql @@ -0,0 +1,58 @@ +input OrderProductCreateInput { + count: Int! + productId: String! + comment: String +} + +input OrderCreateInput { + userId: String! + warehouseId: String! + products: [OrderProductCreateInput!]! + options: WarehouseOrdersRouterCreateOptions + orderType: Int +} + +input WarehouseOrdersRouterCreateOptions { + autoConfirm: Boolean! +} + +type Mutation { + createOrder(createInput: OrderCreateInput!): Order! +} + +type MerchantsOrders { + _id: String + ordersCount: Int +} + +type RemovedUserOrdersObj { + number: Int + modified: Int +} + +type StoreOrdersTableData { + orders: [Order]! + page: Int! +} + +type Query { + getStoreOrders(storeId: String!): [Order!]! + + getNextOrderNumber(storeId: String!): Int! + + getDashboardOrdersChartOrders(storeId: String!): [Order!]! + + getMerchantsOrders: [MerchantsOrders] + + getStoreOrdersTableData( + storeId: String! + pagingOptions: PagingOptionsInput + status: String + ): StoreOrdersTableData! + + getCountOfStoreOrders(storeId: String!, status: String!): Int! + + getOrdersInDelivery(storeId: String!): [Order]! + + removeUserOrders(storeId: String!, userId: String!): RemovedUserOrdersObj +} diff --git a/packages/core/src/graphql/warehouses-products/warehouse-products.resolver.ts b/packages/core/src/graphql/warehouses-products/warehouse-products.resolver.ts new file mode 100644 index 0000000..fb672eb --- /dev/null +++ b/packages/core/src/graphql/warehouses-products/warehouse-products.resolver.ts @@ -0,0 +1,97 @@ +import { Mutation, Resolver, Query } from '@nestjs/graphql'; +import { IWarehouseProductCreateObject } from '@modules/server.common/interfaces/IWarehouseProduct'; +import { WarehousesProductsService } from '../../services/warehouses'; +import { Exception } from 'handlebars'; +import WarehouseProduct from '@modules/server.common/entities/WarehouseProduct'; +import { first } from 'rxjs/operators'; + +@Resolver('Warehouse-products') +export class WarehouseProductsResolver { + constructor( + private readonly _warehousesProductsService: WarehousesProductsService + ) {} + + @Query() + async getProductsWithPagination(_, { id, pagingOptions = {} }) { + const warehouseProducts = await this._warehousesProductsService.getProductsWithPagination( + id, + pagingOptions + ); + + return warehouseProducts.map((p) => new WarehouseProduct(p)); + } + + @Query() + async getProductsCount(_, { id }: { id: string }) { + return this._warehousesProductsService.getProductsCount(id); + } + + @Query() + async getWarehouseProduct( + _, + { + warehouseId, + warehouseProductId, + }: { warehouseId: string; warehouseProductId: string } + ) { + return await this._warehousesProductsService + .getProduct(warehouseId, warehouseProductId) + .pipe(first()) + .toPromise(); + } + + @Mutation() + async addWarehouseProducts( + _, + { + warehouseId, + products, + }: { warehouseId: string; products: IWarehouseProductCreateObject[] } + ) { + return this._warehousesProductsService.add(warehouseId, products); + } + + @Mutation() + async removeWarehouseProducts( + _, + { + warehouseId, + productsIds, + }: { warehouseId: string; productsIds: string[] } + ) { + return this._warehousesProductsService.remove(warehouseId, productsIds); + } + + @Mutation() + async updateWarehouseProduct( + _, + { + warehouseId, + productId, + updateInput, + }: { + warehouseId: string; + productId: string; + updateInput: { + quantity: { + increase: number; + decrease: number; + to: number; + }; + price: number; + }; + } + ) { + throw new Exception('not implemented'); + /* + if (updateInput.quantity) { + if (Object.keys(updateInput.quantity).length !== 1) { + throw new Error("Can't"); + } + + this._warehousesProductsService. + } + return await this._warehousesProductsService.(); + */ + } +} diff --git a/packages/core/src/graphql/warehouses-products/warehouses-products.modules.ts b/packages/core/src/graphql/warehouses-products/warehouses-products.modules.ts new file mode 100644 index 0000000..a3a6370 --- /dev/null +++ b/packages/core/src/graphql/warehouses-products/warehouses-products.modules.ts @@ -0,0 +1,8 @@ +import { Module } from '@nestjs/common'; +import { WarehouseProductsResolver } from './warehouse-products.resolver'; + +@Module({ + providers: [WarehouseProductsResolver], + imports: [], +}) +export class WarehousesProductsModule {} diff --git a/packages/core/src/graphql/warehouses-products/warehouses-products.types.graphql b/packages/core/src/graphql/warehouses-products/warehouses-products.types.graphql new file mode 100644 index 0000000..19ac36b --- /dev/null +++ b/packages/core/src/graphql/warehouses-products/warehouses-products.types.graphql @@ -0,0 +1,72 @@ +type Query { + getProductsWithPagination( + id: String! + pagingOptions: PagingOptionsInput + ): [WarehouseProduct!] + + getProductsCount(id: String!): Int + + getWarehouseProduct( + warehouseId: String! + warehouseProductId: String! + ): WarehouseProduct +} + +type WarehouseProduct { + id: String! + _id: String! + price: Int! + initialPrice: Int! + count: Int + soldCount: Int! + product: Product! + isManufacturing: Boolean + isCarrierRequired: Boolean + isDeliveryRequired: Boolean + isProductAvailable: Boolean + isTakeaway: Boolean + deliveryTimeMin: Int + deliveryTimeMax: Int +} + +input WarehouseProductInput { + price: Int! + initialPrice: Int! + count: Int + product: String! + isManufacturing: Boolean + isCarrierRequired: Boolean + isDeliveryRequired: Boolean + isProductAvailable: Boolean + isTakeaway: Boolean + deliveryTimeMin: Int + deliveryTimeMax: Int +} + +input QuantityUpdateInput { + change: Int + to: Int +} + +input WarehouseProductUpdateInput { + quantity: QuantityUpdateInput + price: Int +} + +type Mutation { + removeWarehouseProducts( + warehouseId: String! + productsIds: [String!]! + ): Boolean + + addWarehouseProducts( + warehouseId: String! + products: [WarehouseProductInput!]! + ): [WarehouseProduct!] + + updateWarehouseProduct( + warehouseId: String! + productId: String! + updateInput: WarehouseProductUpdateInput! + ): WarehouseProduct! +} diff --git a/packages/core/src/graphql/warehouses/warehouse.resolver.ts b/packages/core/src/graphql/warehouses/warehouse.resolver.ts new file mode 100644 index 0000000..8b22913 --- /dev/null +++ b/packages/core/src/graphql/warehouses/warehouse.resolver.ts @@ -0,0 +1,327 @@ +import { Mutation, Query, ResolveField, Resolver } from '@nestjs/graphql'; +import { default as IWarehouse } from '@modules/server.common/interfaces/IWarehouse'; +import Warehouse from '@modules/server.common/entities/Warehouse'; +import { + WarehousesCarriersService, + WarehousesOrdersService, + WarehousesService, + WarehousesUsersService, + WarehousesProductsService, +} from '../../services/warehouses'; +import { DevicesService } from '../../services/devices'; +import { OrdersService } from '../../services/orders'; +import { UsersService } from '../../services/users'; +import { GeoLocationsWarehousesService } from '../../services/geo-locations'; +import { first } from 'rxjs/operators'; +import { IWarehouseRegistrationInput } from '@modules/server.common/routers/IWarehouseRouter'; +import IGeoLocation, { + IGeoLocationCreateObject, + ILocation, +} from '@modules/server.common/interfaces/IGeoLocation'; +import User from '@modules/server.common/entities/User'; +import Utils from '@modules/server.common/utils'; +import GeoLocation from '@modules/server.common/entities/GeoLocation'; + +@Resolver('Warehouse') +export class WarehouseResolver { + constructor( + private readonly _geoLocationWarehousesService: GeoLocationsWarehousesService, + private readonly _warehousesService: WarehousesService, + private readonly _warehousesOrdersService: WarehousesOrdersService, + private readonly _warehousesUsersService: WarehousesUsersService, + private readonly _warehousesCarriersService: WarehousesCarriersService, + private readonly _warehouseProductsService: WarehousesProductsService, + private readonly _devicesService: DevicesService, + private readonly _ordersService: OrdersService, + private readonly _usersService: UsersService + ) {} + + @Query() + async hasExistingStores(): Promise { + return (await this._warehousesService.count({})) > 0; + } + + @Query() + async getStoreProducts( + _, + { storeId, fullProducts }: { storeId: string; fullProducts: boolean } + ) { + return this._warehouseProductsService + .get(storeId, fullProducts) + .pipe(first()) + .toPromise(); + } + + @Query() + async getStoreAvailableProducts(_, { storeId }: { storeId: string }) { + return this._warehouseProductsService + .getAvailable(storeId) + .pipe(first()) + .toPromise(); + } + + @Query() + async getAllActiveStores(_, { fullProducts }: { fullProducts: boolean }) { + return this._warehousesService + .getAllActive(fullProducts) + .pipe(first()) + .toPromise(); + } + + @Query() + async countStoreCustomers(_, { storeId }: { storeId: string }) { + const storeOrders = await this._warehousesOrdersService + .get(storeId) + .pipe(first()) + .toPromise(); + + const storeCustomerIds = storeOrders.map((order) => + order.user._id.toString() + ); + + return new Set(storeCustomerIds).size; + } + + @Query() + async getCountExistingCustomers() { + const isDeletedFlag = { isDeleted: { $eq: false } }; + const users: string[] = await this._ordersService.Model.find( + isDeletedFlag + ) + .distinct('user._id') + .lean(); + + const storesIds: string[] = await this._ordersService.Model.find( + isDeletedFlag + ) + .distinct('warehouse') + .lean(); + + return { + total: users.length, + perStore: storesIds.map(async (storeId) => { + const usersIds: string[] = await this._ordersService.Model.find( + { + ...isDeletedFlag, + warehouse: storeId, + } + ).distinct('user._id'); + return { + storeId, + customersCount: usersIds.length, + }; + }), + }; + } + + @Query() + async getCountExistingCustomersToday() { + const isDeletedFlag = { isDeleted: { $eq: false } }; + + const start = new Date(); + const end = new Date(); + start.setHours(0, 0, 0, 0); + end.setHours(23, 59, 59, 999); + + const users = await this._ordersService.Model.find({ + ...isDeletedFlag, + _createdAt: { $gte: start, $lt: end }, + }) + .distinct('user._id') + .lean() + .exec(); + + const storesIds: string[] = await this._ordersService.Model.find({ + ...isDeletedFlag, + _createdAt: { $gte: start, $lt: end }, + }) + .distinct('warehouse') + .lean() + .exec(); + + return { + total: users.length, + perStore: storesIds.map(async (storeId) => { + const usersIds: string[] = await this._ordersService.Model.find( + { + ...isDeletedFlag, + 'user._id': { $in: users.map((u) => u._id) }, + warehouse: storeId, + } + ).distinct('user._id'); + return { + storeId, + customersCount: usersIds.length, + }; + }), + }; + } + + @Query('nearbyStores') + async getNearbyStores(_, { geoLocation }) { + return this._geoLocationWarehousesService + .get(geoLocation) + .pipe(first()) + .toPromise(); + } + + // @UseGuards(AuthGuard('jwt')) + @Query('warehouse') + async getWarehouse(_, { id }: { id: string }) { + return this._warehousesService.get(id).pipe(first()).toPromise(); + } + + @Query() + async getAllStores() { + return this._warehousesService.find({ isDeleted: { $eq: false } }); + } + + @Query('warehouses') + async getWarehouses(_, { findInput, pagingOptions = {} }) { + if (!pagingOptions || (pagingOptions && !pagingOptions['sort'])) { + pagingOptions['sort'] = { field: '_createdAt', sortBy: 'desc' }; + } + + const merchants = await this._warehousesService.getMerchants( + findInput, + pagingOptions + ); + + return merchants.map((m) => new Warehouse(m)); + } + + @Query() + async getStoreCustomers( + _, + { storeId }: { storeId: string } + ): Promise { + return this._warehousesUsersService.getPromise(storeId); + } + + @Query() + async getCountOfMerchants() { + return this._warehousesService.Model.find({ isDeleted: { $eq: false } }) + .countDocuments() + .exec(); + } + + @Query() + async getMerchantsBuyName( + _, + { + searchName, + geoLocation, + }: { searchName: string; geoLocation: IGeoLocation } + ) { + const count = await this._warehousesService.Model.find({ + name: { $regex: searchName, $options: 'i' }, + }) + .countDocuments() + .exec(); + + let merchants = await this._warehousesService.getMerchants( + { name: { $regex: searchName, $options: 'i' } }, + { skip: 0, limit: count } + ); + + if (geoLocation) { + merchants = merchants.sort( + (m1, m2) => + Utils.getDistance( + new GeoLocation(m1.geoLocation), + new GeoLocation(geoLocation) + ) - + Utils.getDistance( + new GeoLocation(m2.geoLocation), + new GeoLocation(geoLocation) + ) + ); + } + + return merchants.map((m) => new Warehouse(m)); + } + + @Mutation() + async warehouseLogin( + _, + { username, password }: { username: string; password: string } + ) { + return this._warehousesService.login(username, password); + } + + @Mutation() + async updateStoreGeoLocation( + _, + { + storeId, + geoLocation, + }: { + storeId: string; + geoLocation: IGeoLocationCreateObject; + } + ) { + return this._warehousesService.updateGeoLocation(storeId, geoLocation); + } + + @Mutation() + async registerWarehouse( + _, + { registerInput }: { registerInput: IWarehouseRegistrationInput } + ) { + return this._warehousesService.register(registerInput); + } + + @Mutation() + async removeWarehousesByIds(_, { ids }: { ids: string[] }) { + return this._warehousesService.removeMultipleByIds(ids); + } + + @ResolveField('devices') + async getDevices(_warehouse: IWarehouse) { + const warehouse = new Warehouse(_warehouse); + return this._devicesService + .getMultiple(warehouse.devicesIds) + .pipe(first()) + .toPromise(); + } + + @ResolveField('orders') + async getOrders(_warehouse: IWarehouse) { + const warehouse = new Warehouse(_warehouse); + return this._warehousesOrdersService + .get(warehouse.id) + .pipe(first()) + .toPromise(); + } + + @ResolveField('users') + async getUsers(_warehouse: IWarehouse) { + const warehouse = new Warehouse(_warehouse); + + return this._warehousesUsersService + .get(warehouse.id) + .pipe(first()) + .toPromise(); + } + + @ResolveField('carriers') + async getCarriers(_warehouse: IWarehouse) { + const warehouse = new Warehouse(_warehouse); + + return this._warehousesCarriersService + .get(warehouse.id) + .pipe(first()) + .toPromise(); + } + + @Mutation() + async updateWarehousePassword( + _, + { + id, + password, + }: { id: Warehouse['id']; password: { current: string; new: string } } + ) { + return this._warehousesService.updatePassword(id, password); + } +} diff --git a/packages/core/src/graphql/warehouses/warehouses.module.ts b/packages/core/src/graphql/warehouses/warehouses.module.ts new file mode 100644 index 0000000..2deafc2 --- /dev/null +++ b/packages/core/src/graphql/warehouses/warehouses.module.ts @@ -0,0 +1,9 @@ +import { Module } from '@nestjs/common'; +import { WarehouseResolver } from './warehouse.resolver'; +import { AuthModule } from '../../auth/auth.module'; + +@Module({ + providers: [WarehouseResolver], + imports: [AuthModule], +}) +export class WarehousesModule {} diff --git a/packages/core/src/graphql/warehouses/warehouses.types.graphql b/packages/core/src/graphql/warehouses/warehouses.types.graphql new file mode 100644 index 0000000..d40c23d --- /dev/null +++ b/packages/core/src/graphql/warehouses/warehouses.types.graphql @@ -0,0 +1,221 @@ +type Warehouse { + _id: String! + id: String! + _createdAt: Date! + + # Is Warehouse working now + isActive: Boolean! + + # Is Payment enabled + isPaymentEnabled: Boolean + + # Warehouse current location + geoLocation: GeoLocation! + + # Products available at this warehouse for customer to purchase + # TODO products: [WarehouseProduct!]! + + name: String! + + # Url of the logo + logo: String! + + username: String! + + contactEmail: String + contactPhone: String + + forwardOrdersUsing: [Int!] + ordersEmail: String + ordersPhone: String + + # Is Warehouse products by default require manufacturing + isManufacturing: Boolean + + # Is warehouse products by default become available only when carrier found (Should we search for carrier before or after customer purchases product) + isCarrierRequired: Boolean + + devicesIds: [String!]! + devices: [Device!]! # Resolved + orders: [Order!]! # Resolved + users: [User!]! # Resolved + hasRestrictedCarriers: Boolean + carriersIds: [String!] + usedCarriersIds: [String!] + carriers: [Carrier!] # Resolved, if hasRestrictedCarriers is false -> returns null + orderBarcodeType: Int + useOnlyRestrictedCarriersForDelivery: Boolean + preferRestrictedCarriersForDelivery: Boolean + ordersShortProcess: Boolean + orderCancelation: OrderCancelation! + inStoreMode: Boolean! + carrierCompetition: Boolean! +} + +input WarehouseCreateInput { + # Warehouse current location + geoLocation: GeoLocationCreateInput! + + name: String! + logo: String! # the url of it + username: String! + + # Is Warehouse working now + isActive: Boolean + + # Is Payment enabled + isPaymentEnabled: Boolean + + contactEmail: String + contactPhone: String + + forwardOrdersUsing: [Int!] + ordersEmail: String + ordersPhone: String + + # Is Warehouse products by default require manufacturing + isManufacturing: Boolean + + # Is warehouse products by default become available only when carrier found (Should we search for carrier before or after customer purchases product) + isCarrierRequired: Boolean + + hasRestrictedCarriers: Boolean + carriersIds: [String!] + usedCarriersIds: [String!] + useOnlyRestrictedCarriersForDelivery: Boolean + preferRestrictedCarriersForDelivery: Boolean + inStoreMode: Boolean! + carrierCompetition: Boolean! +} + +input WarehousesFindInput { + # TODO geoLocation: GeoLocation + + isActive: Boolean + isPaymentEnabled: Boolean + + name: String + + # Url of the logo + logo: String + + username: String + + contactEmail: String + contactPhone: String + + forwardOrdersUsing: [Int] + ordersEmail: String + ordersPhone: String + + # Is Warehouse products by default require manufacturing + isManufacturing: Boolean + + # Is warehouse products by default become available only when carrier found (Should we search for carrier before or after customer purchases product) + isCarrierRequired: Boolean + + # TODO devices: Device[] + + hasRestrictedCarriers: Boolean + carriersIds: [String!] + usedCarriersIds: [String!] + useOnlyRestrictedCarriersForDelivery: Boolean + preferRestrictedCarriersForDelivery: Boolean + inStoreMode: Boolean! + carrierCompetition: Boolean! +} + +type Query { + warehouse(id: String!): Warehouse + + warehouses( + findInput: WarehousesFindInput + pagingOptions: PagingOptionsInput + ): [Warehouse]! + + nearbyStores(geoLocation: GeoLocationFindInput!): [Warehouse!]! + + countStoreCustomers(storeId: String!): Int! + + getAllActiveStores(fullProducts: Boolean!): [Warehouse!]! + + getStoreCustomers(storeId: String!): [User!]! + + getStoreProducts( + storeId: String! + fullProducts: Boolean! + ): [WarehouseProduct!]! + + getStoreAvailableProducts(storeId: String!): [WarehouseProduct!]! + + getCountExistingCustomers: ExistingCustomersByStores! + + getCountExistingCustomersToday: ExistingCustomersByStores! + + hasExistingStores: Boolean! + + getCountOfMerchants: Int! + getAllStores: [Warehouse!]! + + getMerchantsBuyName( + searchName: String! + geoLocation: GeoLocationFindInput + ): [Warehouse]! +} + +type ExistingCustomersByStores { + total: Int! + perStore: [CustomersByStore!]! +} + +type CustomersByStore { + storeId: String! + customersCount: Int! +} + +type WarehouseLoginInfo { + warehouse: Warehouse! + token: String! +} + +input WarehouseRegisterInput { + warehouse: WarehouseCreateInput! + password: String! +} + +input WarehousePasswordUpdateInput { + current: String! + new: String! +} + +type Mutation { + registerWarehouse(registerInput: WarehouseRegisterInput!): Warehouse! + + warehouseLogin(username: String!, password: String!): WarehouseLoginInfo + + removeWarehousesByIds(ids: [String!]!): Void + + updateWarehousePassword( + id: String! + password: WarehousePasswordUpdateInput! + ): Void + + updateStoreGeoLocation( + storeId: String! + geoLocation: GeoLocationCreateObject! + ): Warehouse! +} + +input GeoLocationCreateObject { + loc: Location! + countryId: Int! + city: String! + postcode: String! + streetAddress: String! + house: String! +} + +type OrderCancelation { + enabled: Boolean + onState: Int +} diff --git a/packages/core/src/helpers/Log.ts b/packages/core/src/helpers/Log.ts new file mode 100644 index 0000000..5fee029 --- /dev/null +++ b/packages/core/src/helpers/Log.ts @@ -0,0 +1,96 @@ +import Logger from 'bunyan'; +import { existsSync } from 'fs'; +import mkdirp from 'mkdirp'; +import { env } from '../env'; +import _ = require('lodash'); +import createCWStream from 'bunyan-cloudwatch'; +import os from 'os'; +import PrettyStream from 'bunyan-prettystream'; + +export interface LogArgs { + // which file used to store logs + name: string; +} + +let isLogsFolderExists = env.LOGS_PATH ? existsSync(env.LOGS_PATH) : false; + +const getAdditionalLoggerStreams = ({ name }: LogArgs): Logger.Stream[] => { + const hostname = os.hostname(); + + if (env.isProd) { + const logLevels: Logger.LogLevel[] = ['info', 'error', 'debug']; + + return _.map(logLevels, (type) => { + let stream: any; + + try { + stream = createCWStream({ + logGroupName: 'ever/api', + logStreamName: `${type}_${name}_${hostname}`, + cloudWatchLogsOptions: { + region: 'us-east-1', + }, + }); + } catch (err) { + console.log(err); + } + + return { + stream, + type: 'raw', + level: type, + }; + }); + } else { + return []; + } +}; + +const prettyStdOut = new PrettyStream(); + +prettyStdOut.pipe(process.stdout); + +export function createEverLogger({ name }: LogArgs): Logger { + if (!isLogsFolderExists) { + mkdirp.sync(env.LOGS_PATH); + isLogsFolderExists = true; + } + + const logger = Logger.createLogger({ + name: `everbie.${name}`, + serializers: Logger.stdSerializers, + streams: [ + { + level: 'info', + path: `${env.LOGS_PATH}/info_${name}.log`, + }, + { + level: 'error', + path: `${env.LOGS_PATH}/error_${name}.log`, + }, + { + level: 'debug', + path: `${env.LOGS_PATH}/debug_${name}.log`, + }, + { + level: 'debug', + type: 'raw', + stream: prettyStdOut, + }, + ...getAdditionalLoggerStreams({ name }), + ], + }); + + if (env.LOG_LEVEL) { + logger.level(Logger[env.LOG_LEVEL.toUpperCase()]); + } + + return logger; +} + +export function Log(logArgs: LogArgs): ClassDecorator { + return function (target) { + target.prototype.logName = logArgs.name; + target.prototype.log = createEverLogger(logArgs); + }; +} diff --git a/packages/core/src/helpers/NestJSLogger.ts b/packages/core/src/helpers/NestJSLogger.ts new file mode 100644 index 0000000..6e0095c --- /dev/null +++ b/packages/core/src/helpers/NestJSLogger.ts @@ -0,0 +1,18 @@ +import { LoggerService } from '@nestjs/common/services/logger.service'; +import { createEverLogger } from './Log'; + +const log = createEverLogger({ name: 'nestjs' }); + +export class EverbieNestJSLogger implements LoggerService { + log(message: string) { + log.info(message); + } + + error(message: string, trace: string) { + log.error(`Message: ${message}. Trace: ${trace}`); + } + + warn(message: string) { + log.warn(message); + } +} diff --git a/packages/core/src/main.ts b/packages/core/src/main.ts new file mode 100644 index 0000000..3ca20a9 --- /dev/null +++ b/packages/core/src/main.ts @@ -0,0 +1,78 @@ +import { env } from './env'; + +// attempt to set memory limit for NodeJS to be greater than default +// (aprox 1.7Gb) +import v8 from 'v8'; +console.log('Setting NodeJS Max memory usage limit to ' + env.WEB_MEMORY); +v8.setFlagsFromString('--max_old_space_size=' + env.WEB_MEMORY); + +import 'reflect-metadata'; +import sourceMapSupport from 'source-map-support'; +import moduleAlias from 'module-alias'; + +try { + if (global.v8debug) { + global.v8debug.Debug.setBreakOnException(); + } + + sourceMapSupport.install(); + + moduleAlias.addAliases({ + '@pyro/db-server': __dirname + '/@pyro/db-server', + '@pyro/io': __dirname + '/@pyro/io', + '@pyro/db': '@ever-platform/common/build/@pyro/db', + '@modules/server.common': '@ever-platform/common/build/', + }); +} catch (err) { + console.error(err); +} + +import BluebirdPromise from 'bluebird'; +import mongoose from 'mongoose'; +import { createEverLogger } from './helpers/Log'; +import { servicesContainer } from './services/inversify.config'; +import { ServicesApp } from './services/services.app'; + +const log = createEverLogger({ name: 'uncaught' }); + +process.on('uncaughtException', (err) => { + try { + log.error(err, 'Caught exception: ' + err); + } catch (logWritingErr) { + try { + console.error("Can't write to log!!!!!!"); + console.error(logWritingErr); + } catch (consoleWritingError) {} + } + + console.error(err); +}); + +process.on('unhandledRejection', (err, promise) => { + try { + log.error(err, 'Uncaught rejection: ' + err); + } catch (logWritingErr) { + try { + console.error("Can't write to log!!!!!!"); + console.error(logWritingErr); + } catch (consoleWritingError) {} + } + + console.error(err); +}); + +(mongoose as any).Promise = BluebirdPromise; + +(async () => { + // needs to test TypeORM connection and for it to be ready before we initialize Services + await ServicesApp.CreateTypeORMConnection(); + + const app = servicesContainer.get(ServicesApp); + + await app.start(async () => { + // load NestJS modules dynamically, because needs all services to be initialized before + const bootstrapNest = await require('./nest-bootstrap').bootstrapNest; + // bootstrap NestJS modules/controllers/DI/etc + bootstrapNest(); + }); +})(); diff --git a/packages/core/src/nest-bootstrap.ts b/packages/core/src/nest-bootstrap.ts new file mode 100644 index 0000000..06069bc --- /dev/null +++ b/packages/core/src/nest-bootstrap.ts @@ -0,0 +1,91 @@ +import chalk from 'chalk'; +import { urlencoded, json } from 'express'; +import { NestFactory, Reflector } from '@nestjs/core'; +import { ApplicationModule } from './app.module'; +import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'; +import { env } from './env'; +import Logger from 'bunyan'; +import { createEverLogger } from './helpers/Log'; +import { EverbieNestJSLogger } from './helpers/NestJSLogger'; +import { INestApplication } from '@nestjs/common'; +// import { SentryService } from '@ntegral/nestjs-sentry'; +import expressSession from 'express-session'; +import helmet from 'helmet'; + +const log: Logger = createEverLogger({ name: 'bootstrapNest' }); + +declare const module: any; + +export async function bootstrapNest(): Promise { + + const app: INestApplication = await NestFactory.create(ApplicationModule, { + logger: new EverbieNestJSLogger(), + }); + + // This will lock all routes and make them accessible by authenticated users only. + const reflector = app.get(Reflector); + // app.useGlobalGuards(new AuthGuard(reflector)); + + // app.useLogger(app.get(SentryService)); + app.use(json({ limit: '50mb' })); + app.use(urlencoded({ extended: true, limit: '50mb' })); + + app.enableCors({ + origin: '*', + methods: 'GET,HEAD,PUT,PATCH,POST,DELETE,OPTIONS', + credentials: true, + allowedHeaders: + 'Authorization, Language, Tenant-Id, X-Requested-With, X-Auth-Token, X-HTTP-Method-Override, Content-Type, Content-Language, Accept, Accept-Language, Observe' + }); + + // TODO: enable csurf + // As explained on the csurf middleware page https://github.com/expressjs/csurf#csurf, + // the csurf module requires either a session middleware or cookie-parser to be initialized first. + // app.use(csurf()); + + app.use( + expressSession({ + secret: env.EXPRESS_SESSION_SECRET, + resave: true, + saveUninitialized: true + }) + ); + + app.use(helmet()); + const globalPrefix = 'api'; + app.setGlobalPrefix(globalPrefix); + + const options = new DocumentBuilder() + .setTitle('Ever Demand REST API') + .setVersion('1.0') + .addBearerAuth() + .build(); + + const document = SwaggerModule.createDocument(app, options); + SwaggerModule.setup('swg', app, document); + + const port: number = env.GQLPORT; + let host: string = env.API_HOST; + + if (!host) { + host = '0.0.0.0'; + } + + console.log(chalk.green(`Configured Host: ${host}`)); + console.log(chalk.green(`Configured Port: ${port}`)); + + log.info(`Swagger UI available at http://${host}:${port}/swg`); + console.log(chalk.green(`Swagger UI available at http://${host}:${port}/swg`)); + + log.info(`REST API will be available at http://${host}:${port}/${globalPrefix}`); + console.log(chalk.green(`REST API will be available at http://${host}:${port}/${globalPrefix}`)); + + await app.listen(port, host, () => { + console.log(chalk.magenta(`REST API Listening at http://${host}:${port}/${globalPrefix}`)); + }); + + if (module.hot) { + module.hot.accept(); + module.hot.dispose((_) => app.close()); + } +} diff --git a/packages/core/src/pm2bootstrap.ts b/packages/core/src/pm2bootstrap.ts new file mode 100644 index 0000000..fa8dae5 --- /dev/null +++ b/packages/core/src/pm2bootstrap.ts @@ -0,0 +1,142 @@ +import { env } from './env'; +import util from 'util'; +import path from 'path'; +import os from 'os'; +import PM2 from 'pm2/lib/API.js'; +import cst from 'pm2/constants.js'; + +const pm2 = new PM2( + env.isProd + ? { + public_key: env.KEYMETRICS_PUBLIC_KEY, + secret_key: env.KEYMETRICS_SECRET_KEY, + } + : {} +); + +const start = util.promisify(pm2.start.bind(pm2)); +const interact = (private_key, public_key, machine_name) => + new Promise((resolve) => + pm2.interact(private_key, public_key, machine_name, resolve) + ); +const launchBus = util.promisify(pm2.launchBus.bind(pm2)); +const runningApps = util.promisify(pm2.list.bind(pm2)); +const dump = util.promisify(pm2.dump.bind(pm2)); +const timeout = (ms: number) => + new Promise((resolve) => setTimeout(resolve, ms)); + +(async () => { + // Display logs in standard output + try { + const bus = await launchBus(); + console.log('[PM2] Log streaming started'); + + bus.on('log:out', (packet) => { + console.log('[App:%s] %s', packet.process.name, packet.data); + }); + + bus.on('log:err', (packet) => { + console.error('[App:%s][Err] %s', packet.process.name, packet.data); + }); + } catch (err) { + exitPM2(); + } +})(); + +(async () => { + try { + await start({ + pm2_home: path.join(os.homedir(), '.pm2'), + script: './build/src/main.js', + name: 'EverApi', + daemon_mode: true, + // See https://github.com/Unitech/PM2/blob/master/ADVANCED_README.md#schema + exec_mode: 'fork', + instances: env.WEB_CONCURRENCY, + // Auto restart if process taking more than XXmo + max_memory_restart: env.WEB_MEMORY + 'M', + // post_update: ["npm install"] // Commands to execute once we do a pull from Keymetrics + ...(env.isDev ? { watch: true } : {}), + }); + + await dump(); + } catch (err) { + console.error(err); + } + + // autoExit(); + + if (env.isProd) { + await interact( + env.KEYMETRICS_SECRET_KEY, + env.KEYMETRICS_PUBLIC_KEY, + env.KEYMETRICS_MACHINE_NAME + ); + } + + process.on('SIGINT', function () { + exitPM2(); + }); + + process.on('SIGTERM', function () { + exitPM2(); + }); +})(); + +function exitPM2() { + console.log('Exiting PM2'); + pm2.kill(function () { + process.exit(0); + }); +} + +/** + * Exit current PM2 instance if 0 app is online + */ +async function autoExit() { + const interval = 3000; + const aliveInterval = interval * 1.5; + + let alive = false; + + while (true) { + await timeout(interval); + + const aliveTimer = setTimeout(function () { + if (!alive) { + console.error('PM2 Daemon is dead'); + process.exit(1); + } + }, aliveInterval); + + try { + const apps = await runningApps(); + + clearTimeout(aliveTimer); + alive = true; + + let appOnline = 0; + + apps.forEach(function (app) { + if ( + app.pm2_env.status === cst.ONLINE_STATUS || + app.pm2_env.status === cst.LAUNCHING_STATUS + ) { + appOnline++; + } + }); + + console.log('check ' + appOnline); + + if (appOnline === 0) { + console.log('0 application online, exiting'); + exitPM2(); + } + } catch (err) { + console.log('pm2.list got error'); + console.error(err); + exitPM2(); + return; + } + } +} diff --git a/packages/core/src/routes/index.ts b/packages/core/src/routes/index.ts new file mode 100644 index 0000000..04dcdae --- /dev/null +++ b/packages/core/src/routes/index.ts @@ -0,0 +1,7 @@ +import express = require('express'); + +function index(req: express.Request, res: express.Response) { + res.send('Online'); +} + +export default index; diff --git a/packages/core/src/services/IService.ts b/packages/core/src/services/IService.ts new file mode 100644 index 0000000..1b25f2e --- /dev/null +++ b/packages/core/src/services/IService.ts @@ -0,0 +1,5 @@ +interface IService {} + +export const ServiceSymbol = Symbol('Service'); + +export default IService; diff --git a/packages/core/src/services/admins/AdminsService.ts b/packages/core/src/services/admins/AdminsService.ts new file mode 100644 index 0000000..4c6ffa1 --- /dev/null +++ b/packages/core/src/services/admins/AdminsService.ts @@ -0,0 +1,140 @@ +import { DBService } from '@pyro/db-server'; +import Admin from '@modules/server.common/entities/Admin'; +import { createEverLogger } from '../../helpers/Log'; +import { AuthService, AuthServiceFactory } from '../auth'; +import { env } from '../../env'; +import IAdminRouter, { + IAdminLoginResponse, + IAdminRegistrationInput, +} from '@modules/server.common/routers/IAdminRouter'; +import { Observable } from 'rxjs'; +import { asyncListener, observableListener } from '@pyro/io'; +import { inject, injectable } from 'inversify'; +import { first, map, switchMap } from 'rxjs/operators'; +import { Repository } from 'typeorm'; +import Logger from 'bunyan'; + +// TODO: Rename! "Admin" is not a great name, but currently "Users" mean "Customers"... +/** + * Users (not customers!) management service + * In most cases such users are Administrators, which need to get access into Admin or Mechant app + */ +@injectable() +export class AdminsService extends DBService implements IAdminRouter { + readonly DBObject: any = Admin; + + protected readonly log: Logger = createEverLogger({ name: 'adminService' }); + + private readonly authService: AuthService; + + constructor( + @inject('Factory') + authServiceFactory: AuthServiceFactory, + // TypeORM Repository - temporary here, will be moved into DBService later + @inject('AdminRepository') + private readonly _adminRepository: Repository + ) { + super(); + + // TODO: move to base class (DBService) and make it fail everything if no admin users found in DB + _adminRepository + .count() + .then((c) => { + console.log('Admins count: ' + c); + }) + .catch((e) => { + console.log(e); + }); + + this.authService = authServiceFactory({ + role: 'admin', + Entity: Admin, + saltRounds: env.ADMIN_PASSWORD_BCRYPT_SALT_ROUNDS, + }); + } + + @observableListener() + get(id: Admin['id']): Observable { + return super.get(id).pipe( + map(async (admin) => { + await this.throwIfNotExists(id); + return admin; + }), + switchMap((admin) => admin) + ); + } + + @asyncListener() + async getByEmail(email: Admin['email']): Promise { + return super.findOne({ email, isDeleted: { $eq: false } }); + } + + @asyncListener() + async register(input: IAdminRegistrationInput): Promise { + const admin = await this.create({ + ...input.admin, + ...(input.password + ? { + hash: await this.authService.getPasswordHash( + input.password + ), + } + : {}), + }); + + return admin; + } + + @asyncListener() + async updatePassword( + id: Admin['id'], + password: { current: string; new: string } + ): Promise { + await this.throwIfNotExists(id); + await this.authService.updatePassword(id, password); + } + + @asyncListener() + async login( + email: string, + password: string + ): Promise { + let res = null; + const admin = await this.getByEmail(email); + + if (admin) { + res = await this.authService.login({ email }, password); + } + + if (!res) { + return null; + } + + return { + admin: res.entity, + token: res.token, + }; + } + + @asyncListener() + async isAuthenticated(token: string): Promise { + return this.authService.isAuthenticated(token); + } + + @asyncListener() + async updateById( + id: Admin['id'], + updateObject: Partial + ): Promise { + await this.throwIfNotExists(id); + return super.update(id, updateObject); + } + + async throwIfNotExists(adminId: string) { + const admin = await super.get(adminId).pipe(first()).toPromise(); + + if (!admin || admin.isDeleted) { + throw Error(`Admin with id '${adminId}' does not exists!`); + } + } +} diff --git a/packages/core/src/services/admins/index.ts b/packages/core/src/services/admins/index.ts new file mode 100644 index 0000000..af505dc --- /dev/null +++ b/packages/core/src/services/admins/index.ts @@ -0,0 +1 @@ +export * from './AdminsService'; diff --git a/packages/core/src/services/apps-settings/AppsSettingsService.ts b/packages/core/src/services/apps-settings/AppsSettingsService.ts new file mode 100644 index 0000000..4b2ee8b --- /dev/null +++ b/packages/core/src/services/apps-settings/AppsSettingsService.ts @@ -0,0 +1,13 @@ +import { injectable } from 'inversify'; +import { env } from '../../env'; +import { IAdminAppSettings } from '@modules/server.common/interfaces/IAppsSettings'; + +@injectable() +export class AppsSettingsService { + getAdminAppSettings(): IAdminAppSettings { + return { + adminPasswordReset: env.ADMIN_PASSWORD_RESET ? 1 : 0, + fakeDataGenerator: env.FAKE_DATA_GENERATOR ? 1 : 0, + }; + } +} diff --git a/packages/core/src/services/apps-settings/index.ts b/packages/core/src/services/apps-settings/index.ts new file mode 100644 index 0000000..679828a --- /dev/null +++ b/packages/core/src/services/apps-settings/index.ts @@ -0,0 +1 @@ +export * from './AppsSettingsService'; diff --git a/packages/core/src/services/auth/AuthService.ts b/packages/core/src/services/auth/AuthService.ts new file mode 100644 index 0000000..a990a3f --- /dev/null +++ b/packages/core/src/services/auth/AuthService.ts @@ -0,0 +1,152 @@ +import { EntityService } from '@pyro/db-server/entity-service'; +import { DBCreateObject, DBObject, DBRawObject, PyroObjectId } from '@pyro/db'; +import { env } from '../../env'; +import { WrongPasswordError } from '@modules/server.common/errors/WrongPasswordError'; +import bcrypt from 'bcrypt'; +import { injectable, interfaces } from 'inversify'; +import { RawObject } from '@pyro/db/db-raw-object'; +import jwt, { JsonWebTokenError } from 'jsonwebtoken'; + +interface IAuthableCreateObject extends DBCreateObject { + hash?: string; +} + +interface IAuthableRawObject extends DBRawObject, IAuthableCreateObject { + _id: PyroObjectId; + + hash?: string; +} + +interface IAuthable + extends DBObject { + hash?: string; +} + +interface AuthServiceConfig { + role: string; + Entity: any; // AuthService['DBObject']; + saltRounds: number; +} + +@injectable() +export class AuthService extends EntityService { + role: string; + DBObject: { new (arg: RawObject): T; modelName: string }; + saltRounds: number; + + setConfig(config: AuthServiceConfig) { + this.role = config.role; + this.DBObject = config.Entity; + this.saltRounds = config.saltRounds; + } + + async getPasswordHash(password: string): Promise { + return bcrypt.hash(password, this.saltRounds); + } + + async addPassword(id: T['id'], password: string) { + const entity = this.parse( + await this.Model.findById(id).select('+hash').lean().exec() + ); + + if (entity != null && entity.hash != null) { + throw new Error( + 'Password already exists, please call updatePassword instead.' + ); + } + + await this._savePassword(id, password); + } + + async updatePassword( + id: T['id'], + password: { current: string; new: string } + ): Promise { + const entity = this.parse( + await this.Model.findById(id).select('+hash').lean().exec() + ); + + if (!(await bcrypt.compare(password.current, entity.hash))) { + throw new WrongPasswordError(); + } + + await this._savePassword(id, password.new); + } + + async _savePassword(id: T['id'], password: string) { + const hash = await this.getPasswordHash(password); + + await this.Model.findByIdAndUpdate(id, { + hash, + }) + .lean() + .exec(); + } + + async login( + findObj: any, + password: string + ): Promise<{ entity: T; token: string } | null> { + const entity = this.parse( + await this.Model.findOne(findObj).select('+hash').lean().exec() + ); + + if (!entity || !(await bcrypt.compare(password, entity.hash))) { + return null; + } + + const token = jwt.sign( + { id: entity.id, role: this.role }, + env.JWT_SECRET, + {} + ); // Never expires + + delete entity.hash; + + return { + entity, + token, + }; + } + + /** + * @param {string} token - the jwt token + * @returns {Promise} + */ + async isAuthenticated(token: string): Promise { + try { + const { id, role } = jwt.verify(token, env.JWT_SECRET) as { + id: T['id']; + role: string; + }; + + const entity = await this.Model.findById(id).lean().exec(); + + if (!entity) { + return false; + } + + return role === this.role; + } catch (err) { + if (err instanceof JsonWebTokenError) { + return false; + } else { + throw err; + } + } + } +} + +export type AuthServiceFactory = ( + config: AuthServiceConfig +) => AuthService; + +export const authServiceFactory = ( + context: interfaces.Context +): AuthServiceFactory => { + return (config: AuthServiceConfig) => { + const authService = context.container.get>(AuthService); + authService.setConfig(config); + return authService; + }; +}; diff --git a/packages/core/src/services/auth/AuthenticationService.ts b/packages/core/src/services/auth/AuthenticationService.ts new file mode 100644 index 0000000..a243752 --- /dev/null +++ b/packages/core/src/services/auth/AuthenticationService.ts @@ -0,0 +1,63 @@ +import { ExtractJwt } from 'passport-jwt'; +import jwt from 'jsonwebtoken'; +import { first } from 'rxjs/operators'; +import { inject, injectable, LazyServiceIdentifer } from 'inversify'; +import { routerName } from '@pyro/io'; +import { WarehousesService } from '../warehouses'; +import { CarriersService } from '../carriers'; +import { env } from '../../env'; + +const jwtSecret = env.JWT_SECRET; + +if (jwtSecret === 'default') { + console.log( + 'Warning: default JWT_SECRET used. Please add your own to config!' + ); +} + +export const createJwtData = { + jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), + secretOrKey: jwtSecret, +}; + +export interface JwtPayload { + // id of carrier or warehouse + id: string; + // name of app, e.g. 'carrier' or 'warehouse'. TODO: switch to use enum + appName: string; +} + +export function createToken(id: string, appName: string) { + const user: JwtPayload = { id, appName }; + return jwt.sign(user, 'secretKey', { + // TODO: add expires timeout to config (.env) + expiresIn: 3600, + }); +} + +@injectable() +@routerName('auth') +export class AuthenticationService { + constructor( + @inject(new LazyServiceIdentifer(() => WarehousesService)) + protected warehousesService: WarehousesService, + @inject(new LazyServiceIdentifer(() => CarriersService)) + protected carriersService: CarriersService + ) {} + + async validateUser(payload: JwtPayload): Promise { + if (payload.appName === 'carrier') { + return this.carriersService + .get(payload.id) + .pipe(first()) + .toPromise(); + } else if (payload.appName === 'warehouse') { + return this.warehousesService + .get(payload.id) + .pipe(first()) + .toPromise(); + } else { + return null; + } + } +} diff --git a/packages/core/src/services/auth/index.ts b/packages/core/src/services/auth/index.ts new file mode 100644 index 0000000..8480a46 --- /dev/null +++ b/packages/core/src/services/auth/index.ts @@ -0,0 +1,2 @@ +export * from './AuthenticationService'; +export * from './AuthService'; diff --git a/packages/core/src/services/carriers/CarriersOrdersService.ts b/packages/core/src/services/carriers/CarriersOrdersService.ts new file mode 100644 index 0000000..999d0dc --- /dev/null +++ b/packages/core/src/services/carriers/CarriersOrdersService.ts @@ -0,0 +1,443 @@ +import Logger from 'bunyan'; +import * as _ from 'lodash'; +import { createEverLogger } from '../../helpers/Log'; +import { ProductsService } from '../products'; +import { OrdersService } from '../orders'; +import CarriersService from './CarriersService'; +import Carrier from '@modules/server.common/entities/Carrier'; +import OrderCarrierStatus from '@modules/server.common/enums/OrderCarrierStatus'; +import { inject, injectable } from 'inversify'; +import { WarehousesOrdersService } from '../warehouses'; +import ICarrierOrdersRouter from '@modules/server.common/routers/ICarrierOrdersRouter'; +import { asyncListener, observableListener, routerName } from '@pyro/io'; +import IService from '../IService'; +import Order from '@modules/server.common/entities/Order'; +import IOrder from '@modules/server.common/interfaces/IOrder'; +import { + GeoLocationsOrdersService, + GeoLocationOrdersOptions, +} from '../geo-locations'; +import { getDistance } from '@modules/server.common/utils'; +import { ExistenceEventType } from '@pyro/db-server'; +import { + concat, + distinctUntilChanged, + exhaustMap, + filter, + first, + map, + share, + switchMap, +} from 'rxjs/operators'; +import { throwError, of, Observable } from 'rxjs'; +import OrderWarehouseStatus from '@modules/server.common/enums/OrderWarehouseStatus'; + +@injectable() +@routerName('carrier-orders') +export class CarriersOrdersService implements ICarrierOrdersRouter, IService { + /** + * Tracking distance in km + * TODO: this setting should be per something (e.g. per warehouse, per carrier) stored in the DB + * + * @static + * @memberof CarriersOrdersService + */ + static CarrierTrackingDistance = 50000; + + protected log: Logger = createEverLogger({ + name: 'carriersOrdersService', + }); + + constructor( + @inject(CarriersService) + protected carriersService: CarriersService, + @inject(ProductsService) + protected productsService: ProductsService, + @inject(OrdersService) + protected ordersService: OrdersService, + @inject(GeoLocationsOrdersService) + protected geoLocationsOrdersService: GeoLocationsOrdersService, + @inject(WarehousesOrdersService) + protected warehousesOrdersService: WarehousesOrdersService + ) {} + + @asyncListener() + async selectedForDelivery( + carrierId: string, + orderIds: string[], + carrierCompetition?: boolean + ): Promise { + let carrier = await this.carriersService + .get(carrierId) + .pipe(first()) + .toPromise(); + + if (carrier != null) { + if (orderIds.length > 0) { + // TODO: check here that not from any status it is possible to move to any other. + // Mean if order is status 3, it's not simply possible to update it to status 2. + const carrierStatus = carrierCompetition + ? OrderCarrierStatus.CarrierPickedUpOrder + : OrderCarrierStatus.CarrierSelectedOrder; + + await this.ordersService.updateMultipleByIds(orderIds, { + carrier: carrierId, + carrierStatus, + }); + } + + carrier = await this.carriersService + .get(carrierId) + .pipe(first()) + .toPromise(); + + return carrier as Carrier; + } else { + throw new Error('Carrier with such id is not found!'); + } + } + + @asyncListener() + async getCount(carrierId: string): Promise { + await this.carriersService.throwIfNotExists(carrierId); + + return this.ordersService.count( + _.extend( + { carrier: carrierId }, + OrdersService.FindObjects.isCompleted + ) + ); + } + + @asyncListener() + async skipOrders(carrierId: string, ordersIds: string[]): Promise { + await this.carriersService.throwIfNotExists(carrierId); + + return this.carriersService.update(carrierId, { + $push: { + skippedOrderIds: { + $each: ordersIds, + }, + }, + }); + } + + @asyncListener() + async updateStatus( + carrierId: string, + status: OrderCarrierStatus + ): Promise { + // TODO: check here that not from any status it is possible to move to any other. + // Mean if order is status 3, it's not simply possible to update it to status 2. + + await this.carriersService.throwIfNotExists(carrierId); + + const findObj = _.extend( + { carrier: carrierId }, + OrdersService.FindObjects.isNotCompleted + ); + + const updateObj: any = { + carrierStatus: status, + }; + + if (status === OrderCarrierStatus.DeliveryCompleted) { + updateObj.isPaid = true; + updateObj.deliveryTime = Date.now(); + } + + if ( + status === OrderCarrierStatus.CarrierStartDelivery || + status === OrderCarrierStatus.CarrierPickedUpOrder + ) { + updateObj.startDeliveryTime = Date.now(); + } + + const finishedProcessingStatuses = [ + OrderCarrierStatus.ClientRefuseTakingOrder, + OrderCarrierStatus.DeliveryCompleted, + OrderCarrierStatus.IssuesDuringDelivery, + ]; + + if (finishedProcessingStatuses.includes(status)) { + updateObj.finishedProcessingTime = Date.now(); + } + + if ((await this.carriersService.getCurrent(carrierId)) != null) { + try { + const orders = await this.ordersService.updateMultiple( + findObj, + updateObj + ); + + if (status === OrderCarrierStatus.DeliveryCompleted) { + return await this.carriersService.increaseNumberOfDeliveries( + carrierId, + orders.length + ); + } else { + return (await this.carriersService.getCurrent( + carrierId + )) as Carrier; + } + } catch (err) { + this.log.error(err); + throw err; + } + } else { + throw new Error('Carrier with such id is not found!'); + } + } + + @asyncListener() + async cancelDelivery( + carrierId: string, + orderIds: string[] + ): Promise { + await this.carriersService.throwIfNotExists(carrierId); + + const _orders = await this.ordersService.find({ + _id: { $in: orderIds }, + isDeleted: { $eq: false }, + }); + const ordersIdsFiltered = _orders.map((d) => d.id); + + const orders = await this.ordersService.updateMultipleByIds( + ordersIdsFiltered, + { + $unset: { carrier: 1 }, + carrierStatus: OrderCarrierStatus.NoCarrier, + } + ); + + await this.skipOrders( + carrierId, + _.map(orders, (order) => order.id) + ); + + return this.carriersService.updateStatus(carrierId, 0); + } + + @observableListener() + getAvailable( + carrierId: string, + options: { populateWarehouse: boolean } = { + populateWarehouse: false, + } + ): Observable { + return this.carriersService.get(carrierId).pipe( + map((carrier) => (carrier != null ? carrier.geoLocation : null)), + distinctUntilChanged((geoLocation1, geoLocation2) => { + // TODO maybe compare full geoLocation not only coordinates! + if (geoLocation1 == null || geoLocation2 == null) { + return true; + } else { + return getDistance(geoLocation1, geoLocation2) <= 0.001; + } + }), + switchMap((geoLocation) => + geoLocation != null + ? this.geoLocationsOrdersService.get(geoLocation, { + populateWarehouse: options.populateWarehouse, + }) + : throwError(() => + new Error( + `No such a carrier with the id ${carrierId} => can't getAvailable` + ) + ) + ) + ); + } + + @observableListener() + get( + carrierId: string, + options: { + populateWarehouse: boolean; + completion: 'completed' | 'not-completed' | 'all'; + } = { + populateWarehouse: false, + completion: 'not-completed', + } + ): Observable { + return of(null).pipe( + map(async (res) => { + await this.carriersService.throwIfNotExists(carrierId); + return res; + }), + switchMap((res) => res), + concat( + this.ordersService.existence.pipe( + filter((existenceEvent) => { + switch (existenceEvent.type as ExistenceEventType) { + case ExistenceEventType.Created: + return existenceEvent.value != null + ? existenceEvent.value.carrierId === + carrierId + : false; + case ExistenceEventType.Updated: + return ( + (existenceEvent.value != null + ? existenceEvent.value.carrierId === + carrierId + : false) || + (existenceEvent.lastValue != null + ? existenceEvent.lastValue.carrierId === + carrierId + : false) + ); + case ExistenceEventType.Removed: + return existenceEvent.lastValue != null + ? existenceEvent.lastValue.carrierId === + carrierId + : false; + } + }), + share() + ) + ), + exhaustMap(() => this._get(carrierId, options)) + ); + } + + @asyncListener() + async getCarrierOrders( + carrierId: string, + options: { + populateWarehouse: boolean; + completion: 'completed' | 'not-completed' | 'all'; + } = { + populateWarehouse: false, + completion: 'not-completed', + } + ): Promise { + const ordersRaw = (await this.ordersService.Model.find( + _.extend( + { carrier: carrierId, isDeleted: { $eq: false } }, + (() => { + switch (options.completion) { + case 'completed': + return OrdersService.FindObjects.isCompleted; + case 'not-completed': + return OrdersService.FindObjects.isNotCompleted; + case 'all': + return {}; + } + })() + ) + ) + .populate(options.populateWarehouse ? 'warehouse' : '') + .lean() + .exec()) as IOrder[]; + + return ordersRaw.map((orderRaw) => { + orderRaw['id'] = orderRaw._id; + orderRaw.products = orderRaw.products.map((p) => { + p['id'] = p._id; + p.product['id'] = p.product._id; + return p; + }); + return orderRaw; + }); + } + + @asyncListener() + async getCarrierCurrentOrder(carrierId: string): Promise { + await this.carriersService.throwIfNotExists(carrierId); + + return this.ordersService.Model.findOne( + _.assign(this.getOrdersForWorkFilter(carrierId), { + carrier: carrierId, + }) + ) + .lean() + .exec(); + } + + @asyncListener() + async getCountOfCarrierOrdersHistory(carrierId: string): Promise { + await this.carriersService.throwIfNotExists(carrierId); + return this.ordersService.Model.find({ carrier: carrierId }) + .countDocuments() + .exec(); + } + + @asyncListener() + async getCarrierOrdersHistory( + carrierId: string, + options: GeoLocationOrdersOptions + ): Promise { + await this.carriersService.throwIfNotExists(carrierId); + + const dbOrders = await this.ordersService.Model.find({ + carrier: carrierId, + }) + .sort({ + _createdAt: + options.sort && options.sort.toLowerCase().includes('desc') + ? 'desc' + : 'asc', + }) + .skip(options.skip || 0) + .limit(options.limit || 1) + .lean() + .exec(); + + return dbOrders.map((o: IOrder) => new Order(o)); + } + + private async _get( + carrierId: string, + options: { + populateWarehouse: boolean; + completion: 'completed' | 'not-completed' | 'all'; + } + ): Promise { + await this.carriersService.throwIfNotExists(carrierId); + + try { + const ordersRaw = (await this.ordersService.Model.find( + _.extend( + { carrier: carrierId, isDeleted: { $eq: false } }, + (() => { + switch (options.completion) { + case 'completed': + return OrdersService.FindObjects.isCompleted; + case 'not-completed': + return OrdersService.FindObjects.isNotCompleted; + case 'all': + return {}; + } + })() + ) + ) + .populate(options.populateWarehouse ? 'warehouse' : '') + .lean() + .exec()) as IOrder[]; + + return _.map(ordersRaw, (orderRaw) => new Order(orderRaw)); + } catch (err) { + this.log.error(err); + throw err; + } + } + + private getOrdersForWorkFilter(carrierId: string) { + return { + carrierStatus: { + $nin: [ + OrderCarrierStatus.IssuesDuringDelivery, + OrderCarrierStatus.ClientRefuseTakingOrder, + OrderCarrierStatus.DeliveryCompleted, + ], + }, + warehouseStatus: { + $in: [ + OrderWarehouseStatus.PackagingFinished, + OrderWarehouseStatus.GivenToCarrier, + ], + }, + $or: [{ carrier: null }, { carrier: carrierId }], + }; + } +} diff --git a/packages/core/src/services/carriers/CarriersService.ts b/packages/core/src/services/carriers/CarriersService.ts new file mode 100644 index 0000000..a96d865 --- /dev/null +++ b/packages/core/src/services/carriers/CarriersService.ts @@ -0,0 +1,215 @@ +import Logger from 'bunyan'; +import CarrierStatus from '@modules/server.common/enums/CarrierStatus'; +import Carrier from '@modules/server.common/entities/Carrier'; +import { createEverLogger } from '../../helpers/Log'; +import { DBService } from '@pyro/db-server'; +import { inject, injectable } from 'inversify'; +import ICarrierRouter, { + ICarrierLoginResponse, + ICarrierRegistrationInput, +} from '@modules/server.common/routers/ICarrierRouter'; +import { + asyncListener, + observableListener, + routerName, + serialization, +} from '@pyro/io'; +import IService from '../IService'; +import GeoLocation from '@modules/server.common/entities/GeoLocation'; +import IGeoLocation from '@modules/server.common/interfaces/IGeoLocation'; +import { concat, of, Observable } from 'rxjs'; +import { exhaustMap, filter, first, map, switchMap } from 'rxjs/operators'; +import { env } from '../../env'; +import { AuthService, AuthServiceFactory } from '../auth'; +import IPagingOptions from '@modules/server.common/interfaces/IPagingOptions'; + +@injectable() +@routerName('carrier') +export class CarriersService extends DBService + implements ICarrierRouter, IService { + public readonly DBObject: any = Carrier; + protected readonly log: Logger = createEverLogger({ + name: 'carriersService', + }); + + private readonly authService: AuthService; + + constructor( + @inject('Factory') + private readonly authServiceFactory: AuthServiceFactory + ) { + super(); + this.authService = this.authServiceFactory({ + role: 'carrier', + Entity: Carrier, + saltRounds: env.CARRIER_PASSWORD_BCRYPT_SALT_ROUNDS, + }); + } + + @observableListener() + get(id: Carrier['id']) { + return super.get(id).pipe( + map(async (carrier) => { + await this.throwIfNotExists(id); + return carrier; + }), + switchMap((carrier) => { + return carrier; + }) + ); + } + + @observableListener() + getAllActive(): Observable { + return concat(of(null), this.existence).pipe( + exhaustMap(() => this._getAllActive()) + ); + } + + protected async _getAllActive() { + return super.find({ isActive: true, isDeleted: { $eq: false } }); + } + + async getMultipleByIds( + carrierIds: string[] + ): Promise> { + const carriers = await this.find({ + _id: { $in: carrierIds }, + isDeleted: { $eq: false }, + }); + + const carriersIdsToReturn = carriers.map((c) => c.id); + return this.getMultiple(carriersIdsToReturn); + } + + @asyncListener() + async register(input: ICarrierRegistrationInput) { + const carrier = await super.create({ + ...input.carrier, + ...(input.password + ? { + hash: await this.authService.getPasswordHash( + input.password + ), + } + : {}), + }); + return carrier; + } + + async updatePassword( + id: Carrier['id'], + password: { current: string; new: string } + ): Promise { + await this.throwIfNotExists(id); + await this.authService.updatePassword(id, password); + } + + @asyncListener() + async login( + username: string, + password: string + ): Promise { + const res = await this.authService.login({ username }, password); + + if (!res) { + return null; + } else if (res.entity.isDeleted) { + return null; + } + + return { + carrier: res.entity, + token: res.token, + }; + } + + @asyncListener() + async updateStatus( + carrierId: Carrier['id'], + status: CarrierStatus + ): Promise { + await this.throwIfNotExists(carrierId); + return super.update(carrierId, { status }); + } + + @asyncListener() + async updateActivity( + carrierId: Carrier['id'], + isActive: boolean + ): Promise { + await this.throwIfNotExists(carrierId); + return super.update(carrierId, { isActive }); + } + + /** + * Update email for given Carrier (by carrier Id) + * + * @param {string} carrierId + * @param {string} email + * @returns {Promise} + * @memberof CarriersService + */ + @asyncListener() + async updateEmail(carrierId: string, email: string): Promise { + await this.throwIfNotExists(carrierId); + return this.update(carrierId, { email }); + } + + @asyncListener() + async updateGeoLocation( + carrierId: Carrier['id'], + @serialization((gl: IGeoLocation) => new GeoLocation(gl)) + geoLocation: GeoLocation + ): Promise { + await this.throwIfNotExists(carrierId); + return super.update(carrierId, { geoLocation }); + } + + @asyncListener() + async updateById( + id: Carrier['id'], + updateObject: Partial + ): Promise { + await this.throwIfNotExists(id); + return super.update(id, updateObject); + } + + async increaseNumberOfDeliveries( + carrierId: Carrier['id'], + n: number + ): Promise { + await this.throwIfNotExists(carrierId); + + return super.update(carrierId, { + $inc: { numberOfDeliveries: n }, + }); + } + + async throwIfNotExists(carrierId: string) { + const carrier = await super.get(carrierId).pipe(first()).toPromise(); + + if (!carrier || carrier.isDeleted) { + throw Error(`Carrier with id '${carrierId}' does not exists!`); + } + } + + async getCarriers(findInput, pagingOptions: IPagingOptions) { + const sortObj = {}; + if (pagingOptions.sort) { + sortObj[pagingOptions.sort.field] = pagingOptions.sort.sortBy; + } + + return this.Model.find({ + ...findInput, + isDeleted: { $eq: false }, + }) + .sort(sortObj) + .skip(pagingOptions.skip) + .limit(pagingOptions.limit) + .lean() + .exec(); + } +} + +export default CarriersService; diff --git a/packages/core/src/services/carriers/index.ts b/packages/core/src/services/carriers/index.ts new file mode 100644 index 0000000..676d099 --- /dev/null +++ b/packages/core/src/services/carriers/index.ts @@ -0,0 +1,2 @@ +export * from './CarriersService'; +export * from './CarriersOrdersService'; diff --git a/packages/core/src/services/currency/CurrencyService.ts b/packages/core/src/services/currency/CurrencyService.ts new file mode 100644 index 0000000..fabcf16 --- /dev/null +++ b/packages/core/src/services/currency/CurrencyService.ts @@ -0,0 +1,51 @@ +import Currency from '@modules/server.common/entities/Currency'; +import { injectable } from 'inversify'; +import { routerName } from '@pyro/io'; +import { DBService } from '@pyro/db-server'; +import IService from 'services/IService'; +import { ICurrencyCreateObject } from '@modules/server.common/interfaces/ICurrency'; +import { createEverLogger } from '../../helpers/Log'; +import Logger from 'bunyan'; + +export interface CurrencyMutationRespone { + success: boolean; + message?: string; + data?: Currency; +} + +@injectable() +@routerName('currency') +export class CurrenciesService extends DBService implements IService { + public readonly DBObject: any = Currency; + + protected readonly log: Logger = createEverLogger({ + name: 'currenciesService', + }); + + async createCurrency( + currency: ICurrencyCreateObject + ): Promise { + let success; + let message; + let data; + + try { + data = await this.create(currency); + success = true; + message = `Successfully create currency ${data.currencyCode}`; + } catch (error) { + success = false; + message = error.message; + } + + return { success, message, data }; + } + + async getAllCurrencies(): Promise { + const currencies = await this.find({ + isDeleted: { $eq: false }, + }); + + return currencies; + } +} diff --git a/packages/core/src/services/devices/DevicesService.ts b/packages/core/src/services/devices/DevicesService.ts new file mode 100644 index 0000000..a20fb6a --- /dev/null +++ b/packages/core/src/services/devices/DevicesService.ts @@ -0,0 +1,72 @@ +import { injectable } from 'inversify'; +import { IDeviceCreateObject } from '@modules/server.common/interfaces/IDevice'; +import Device from '@modules/server.common/entities/Device'; +import { DBService } from '@pyro/db-server'; +import { createEverLogger } from '../../helpers/Log'; +import { Observable } from 'rxjs'; +import { asyncListener, observableListener, routerName } from '@pyro/io'; +import IDeviceRouter from '@modules/server.common/routers/IDeviceRouter'; +import ILanguage from '@modules/server.common/interfaces/ILanguage'; +import IService from '../IService'; +import { first, switchMap, map } from 'rxjs/operators'; +import Logger from 'bunyan'; + +@injectable() +@routerName('device') +export class DevicesService extends DBService + implements IDeviceRouter, IService { + public readonly DBObject: any = Device; + + protected readonly log: Logger = createEverLogger({ + name: 'devicesService', + }); + + @observableListener() + get(id: string): Observable { + return super.get(id).pipe( + map(async (device) => { + await this.throwIfNotExists(id); + return device; + }), + switchMap((device) => device) + ); + } + + async getMultipleDevices( + ids: Array + ): Promise> { + const devices = await this.find({ + _id: { $in: ids }, + isDeleted: { $eq: false }, + }); + + const devicesIds = devices.map((device) => device.id); + + return super.getMultiple(devicesIds); + } + + @asyncListener() + async create(device: IDeviceCreateObject): Promise { + return super.create(device); + } + + @asyncListener() + async updateLanguage( + deviceId: string, + language: ILanguage + ): Promise { + await this.throwIfNotExists(deviceId); + + return this.update(deviceId, { + language, + }); + } + + async throwIfNotExists(deviceId: string) { + const device = await super.get(deviceId).pipe(first()).toPromise(); + + if (!device || device.isDeleted) { + throw Error(`Device with id '${deviceId}' does not exists!`); + } + } +} diff --git a/packages/core/src/services/devices/index.ts b/packages/core/src/services/devices/index.ts new file mode 100644 index 0000000..91984c3 --- /dev/null +++ b/packages/core/src/services/devices/index.ts @@ -0,0 +1 @@ +export * from './DevicesService'; diff --git a/packages/core/src/services/fake-data/FakeOrdersService.ts b/packages/core/src/services/fake-data/FakeOrdersService.ts new file mode 100644 index 0000000..6a99216 --- /dev/null +++ b/packages/core/src/services/fake-data/FakeOrdersService.ts @@ -0,0 +1,503 @@ +import { injectable } from 'inversify'; +import * as _ from 'lodash'; +import Product from '@modules/server.common/entities/Product'; +import User from '@modules/server.common/entities/User'; + +@injectable() +export class FakeOrdersService { + private _orderNumber: number; + private _storeId: string; + private _carrierId: string; + private _orderCreatedAt: Date; + + getOrderRaw( + orderNumber: number, + storeId: string, + storeCreatedAt: Date, + carrierId: string, + customers: User[], + products: Product[] + ) { + this._orderNumber = orderNumber; + this._storeId = storeId; + this._carrierId = carrierId; + this._orderCreatedAt = this.getOrderDate(storeCreatedAt); + + switch (true) { + case orderNumber <= 25: + return this._getOrderJustCreated(customers, products); + + case orderNumber <= 50: + return this._getOrderReadyForProcessing(customers, products); + + case orderNumber <= 75: + return this._getOrderStoreStartProcessing(customers, products); + + case orderNumber <= 100: + return this._getOrderConfirmed(customers, products); + + case orderNumber <= 125: + return this._getOrderStartAllocation(customers, products); + + case orderNumber <= 150: + return this._getOrderPackagingStarted(customers, products); + + case orderNumber <= 200: + return this._getOrderAllocationFails(customers, products); + + case orderNumber <= 250: + return this._getOrderPackagingFails(customers, products); + + case orderNumber <= 300: + return this._getOrderIssuesDuringDelivery(customers, products); + + case orderNumber <= 350: + return this._getOrderClientRefuseOrder(customers, products); + + case orderNumber <= 400: + return this._getOrderAllocationFinished(customers, products); + + case orderNumber <= 450: + return this._getOrderPackagingFinished(customers, products); + + case orderNumber <= 475: + return this._getOrderCarrierSelected(customers, products); + + case orderNumber <= 500: + return this._getOrderCarrierPickup(customers, products); + + case orderNumber <= 525: + return this._getOrderCarrierArriveToCustomer( + customers, + products + ); + + case orderNumber <= 650: + return this._getOrderCancelled(customers, products); + + case orderNumber <= 1000: + return this._getOrderDeliveryCompleted(customers, products); + } + } + + getOrderDate(startDate: Date): Date { + const now = new Date(); + + const orderYear = _.random(startDate.getFullYear(), now.getFullYear()); + const orderMonth = _.random(11); + const orderDate = _.random(31); + const orderHours = _.random(23); + const orderMinutes = _.random(59); + + const orderCreatedAt = new Date( + orderYear, + orderMonth, + orderDate, + orderHours, + orderMinutes + ); + + if (orderCreatedAt < startDate || orderCreatedAt > now) { + const diff = now.getTime() - startDate.getTime(); + orderCreatedAt.setTime(startDate.getTime() + _.random(diff)); + } + + return orderCreatedAt; + } + + getOrderNextTime(date: Date): Date { + const randomMinutes = _.random(1, 30); + const randomSec = _.random(1, 60); + const oldDate = new Date(date); + + oldDate.setSeconds(randomSec); + + return new Date(oldDate.setMinutes(date.getMinutes() + randomMinutes)); + } + + getRandomNumberOfProducts(): number { + return this._orderNumber % 7 || 1; + } + + getRandomOrderProductCount(): number { + return this._orderNumber % 3 || 1; + } + + getRandomOrderProductPrice(): number { + return this._orderNumber % 110 || 1; + } + + getRandomProduct(orderNumber: number, products: Product[]): Product { + return products[orderNumber % products.length]; + } + + generateRandomOrderProducts(products: Product[]) { + const numberOfProducts = this.getRandomNumberOfProducts(); + + const orderProducts = []; + + for ( + let productNumber = 1; + productNumber <= numberOfProducts; + productNumber += 1 + ) { + const orderPrice = this.getRandomOrderProductPrice(); + + orderProducts.push({ + count: this.getRandomOrderProductCount(), + product: this.getRandomProduct(productNumber, products), + isManufacturing: true, + price: orderPrice, + initialPrice: orderPrice, + }); + } + + return orderProducts; + } + + getRandomOrderCustomer(customers: User[]): User { + return customers[this._orderNumber % customers.length]; + } + + private _getOrderDeliveryCompleted(customers: User[], products: Product[]) { + const startDeliveryTime = this.getOrderNextTime(this._orderCreatedAt); + const deliveryTime = this.getOrderNextTime(startDeliveryTime); + + return { + isConfirmed: false, + isCancelled: false, + isPaid: true, + deliveryTimeEstimate: 0, + warehouseStatus: 6, + carrierStatus: 5, + isDeleted: false, + orderNumber: this._orderNumber, + warehouse: this._storeId, + carrier: this._carrierId, + startDeliveryTime, + deliveryTime, + user: this.getRandomOrderCustomer(customers), + products: this.generateRandomOrderProducts(products), + _createdAt: this._orderCreatedAt, + }; + } + + private _getOrderCarrierArriveToCustomer( + customers: User[], + products: Product[] + ) { + return { + isConfirmed: false, + isCancelled: false, + isPaid: false, + deliveryTimeEstimate: 0, + warehouseStatus: 6, + carrierStatus: 4, + isDeleted: false, + orderNumber: this._orderNumber, + warehouse: this._storeId, + carrier: this._carrierId, + user: this.getRandomOrderCustomer(customers), + products: this.generateRandomOrderProducts(products), + startDeliveryTime: this.getOrderNextTime(this._orderCreatedAt), + _createdAt: this._orderCreatedAt, + }; + } + + private _getOrderCarrierPickup(customers: User[], products: Product[]) { + return { + isConfirmed: false, + isCancelled: false, + isPaid: false, + deliveryTimeEstimate: 0, + warehouseStatus: 6, + carrierStatus: 2, + isDeleted: false, + orderNumber: this._orderNumber, + warehouse: this._storeId, + carrier: this._carrierId, + startDeliveryTime: this.getOrderNextTime(this._orderCreatedAt), + user: this.getRandomOrderCustomer(customers), + products: this.generateRandomOrderProducts(products), + _createdAt: this._orderCreatedAt, + }; + } + + private _getOrderCarrierSelected(customers: User[], products: Product[]) { + return { + isConfirmed: false, + isCancelled: false, + isPaid: false, + deliveryTimeEstimate: 0, + warehouseStatus: 6, + carrierStatus: 1, + isDeleted: false, + orderNumber: this._orderNumber, + warehouse: this._storeId, + carrier: this._carrierId, + user: this.getRandomOrderCustomer(customers), + products: this.generateRandomOrderProducts(products), + _createdAt: this._orderCreatedAt, + }; + } + + private _getOrderPackagingFinished(customers: User[], products: Product[]) { + return { + isConfirmed: false, + isCancelled: false, + isPaid: false, + deliveryTimeEstimate: 0, + warehouseStatus: 6, + carrierStatus: 0, + isDeleted: false, + orderNumber: this._orderNumber, + warehouse: this._storeId, + user: this.getRandomOrderCustomer(customers), + products: this.generateRandomOrderProducts(products), + _createdAt: this._orderCreatedAt, + }; + } + + private _getOrderAllocationFinished( + customers: User[], + products: Product[] + ) { + return { + isConfirmed: false, + isCancelled: false, + isPaid: false, + deliveryTimeEstimate: 0, + warehouseStatus: 4, + carrierStatus: 0, + isDeleted: false, + orderNumber: this._orderNumber, + warehouse: this._storeId, + user: this.getRandomOrderCustomer(customers), + products: this.generateRandomOrderProducts(products), + _createdAt: this._orderCreatedAt, + }; + } + + private _getOrderClientRefuseOrder(customers: User[], products: Product[]) { + const startDeliveryTime = this.getOrderNextTime(this._orderCreatedAt); + const finishedProcessingTime = this.getOrderNextTime(startDeliveryTime); + + return { + isConfirmed: false, + isCancelled: false, + isPaid: false, + deliveryTimeEstimate: 0, + warehouseStatus: 6, + carrierStatus: 205, + isDeleted: false, + orderNumber: this._orderNumber, + warehouse: this._storeId, + carrier: this._carrierId, + user: this.getRandomOrderCustomer(customers), + products: this.generateRandomOrderProducts(products), + startDeliveryTime, + finishedProcessingTime, + _createdAt: this._orderCreatedAt, + }; + } + + private _getOrderIssuesDuringDelivery( + customers: User[], + products: Product[] + ) { + const startDeliveryTime = this.getOrderNextTime(this._orderCreatedAt); + const finishedProcessingTime = this.getOrderNextTime(startDeliveryTime); + + return { + isConfirmed: false, + isCancelled: false, + isPaid: false, + deliveryTimeEstimate: 0, + warehouseStatus: 6, + carrierStatus: 204, + isDeleted: false, + orderNumber: this._orderNumber, + warehouse: this._storeId, + carrier: this._carrierId, + startDeliveryTime, + finishedProcessingTime, + user: this.getRandomOrderCustomer(customers), + products: this.generateRandomOrderProducts(products), + _createdAt: this._orderCreatedAt, + }; + } + + private _getOrderPackagingFails(customers: User[], products: Product[]) { + const finishedProcessingTime = this.getOrderNextTime( + this._orderCreatedAt + ); + + return { + isConfirmed: false, + isCancelled: false, + isPaid: false, + deliveryTimeEstimate: 0, + warehouseStatus: 201, + carrierStatus: 0, + isDeleted: false, + orderNumber: this._orderNumber, + warehouse: this._storeId, + finishedProcessingTime, + user: this.getRandomOrderCustomer(customers), + products: this.generateRandomOrderProducts(products), + _createdAt: this._orderCreatedAt, + }; + } + + private _getOrderAllocationFails(customers: User[], products: Product[]) { + const finishedProcessingTime = this.getOrderNextTime( + this._orderCreatedAt + ); + + return { + isConfirmed: false, + isCancelled: false, + isPaid: false, + deliveryTimeEstimate: 0, + warehouseStatus: 200, + carrierStatus: 0, + isDeleted: false, + orderNumber: this._orderNumber, + warehouse: this._storeId, + finishedProcessingTime, + user: this.getRandomOrderCustomer(customers), + products: this.generateRandomOrderProducts(products), + _createdAt: this._orderCreatedAt, + }; + } + + private _getOrderPackagingStarted(customers: User[], products: Product[]) { + return { + isConfirmed: false, + isCancelled: false, + isPaid: false, + deliveryTimeEstimate: 0, + warehouseStatus: 5, + carrierStatus: 0, + isDeleted: false, + orderNumber: this._orderNumber, + warehouse: this._storeId, + user: this.getRandomOrderCustomer(customers), + products: this.generateRandomOrderProducts(products), + _createdAt: this._orderCreatedAt, + }; + } + + private _getOrderStartAllocation(customers: User[], products: Product[]) { + return { + isConfirmed: false, + isCancelled: false, + isPaid: false, + deliveryTimeEstimate: 0, + warehouseStatus: 3, + carrierStatus: 0, + isDeleted: false, + orderNumber: this._orderNumber, + warehouse: this._storeId, + user: this.getRandomOrderCustomer(customers), + products: this.generateRandomOrderProducts(products), + _createdAt: this._orderCreatedAt, + }; + } + + private _getOrderStoreStartProcessing( + customers: User[], + products: Product[] + ) { + return { + isConfirmed: false, + isCancelled: false, + isPaid: false, + deliveryTimeEstimate: 0, + warehouseStatus: 2, + carrierStatus: 0, + isDeleted: false, + orderNumber: this._orderNumber, + warehouse: this._storeId, + user: this.getRandomOrderCustomer(customers), + products: this.generateRandomOrderProducts(products), + _createdAt: this._orderCreatedAt, + }; + } + + private _getOrderReadyForProcessing( + customers: User[], + products: Product[] + ) { + return { + isConfirmed: false, + isCancelled: false, + isPaid: false, + deliveryTimeEstimate: 0, + warehouseStatus: 1, + carrierStatus: 0, + isDeleted: false, + orderNumber: this._orderNumber, + warehouse: this._storeId, + user: this.getRandomOrderCustomer(customers), + products: this.generateRandomOrderProducts(products), + _createdAt: this._orderCreatedAt, + }; + } + + private _getOrderCancelled(customers: User[], products: Product[]) { + const finishedProcessingTime = this.getOrderNextTime( + this._orderCreatedAt + ); + + return { + isConfirmed: true, + isCancelled: true, + isPaid: false, + deliveryTimeEstimate: 0, + warehouseStatus: 0, + carrierStatus: 0, + isDeleted: false, + orderNumber: 1, + warehouse: this._storeId, + user: this.getRandomOrderCustomer(customers), + products: this.generateRandomOrderProducts(products), + finishedProcessingTime, + _createdAt: this._orderCreatedAt, + }; + } + + private _getOrderConfirmed(customers: User[], products: Product[]) { + return { + isConfirmed: true, + isCancelled: false, + isPaid: false, + deliveryTimeEstimate: 0, + warehouseStatus: 0, + carrierStatus: 0, + isDeleted: false, + orderNumber: 1, + warehouse: this._storeId, + user: this.getRandomOrderCustomer(customers), + products: this.generateRandomOrderProducts(products), + _createdAt: this._orderCreatedAt, + }; + } + + private _getOrderJustCreated(customers: User[], products: Product[]) { + return { + isConfirmed: false, + isCancelled: false, + isPaid: false, + deliveryTimeEstimate: 0, + warehouseStatus: 0, + carrierStatus: 0, + isDeleted: false, + orderNumber: this._orderNumber, + warehouse: this._storeId, + user: this.getRandomOrderCustomer(customers), + products: this.generateRandomOrderProducts(products), + _createdAt: this._orderCreatedAt, + }; + } +} diff --git a/packages/core/src/services/geo-locations/GeoLocationOrdersOptions.ts b/packages/core/src/services/geo-locations/GeoLocationOrdersOptions.ts new file mode 100644 index 0000000..6efa7ea --- /dev/null +++ b/packages/core/src/services/geo-locations/GeoLocationOrdersOptions.ts @@ -0,0 +1,5 @@ +export interface GeoLocationOrdersOptions { + sort?: string; + limit?: number; + skip?: number; +} diff --git a/packages/core/src/services/geo-locations/GeoLocationsOrdersService.ts b/packages/core/src/services/geo-locations/GeoLocationsOrdersService.ts new file mode 100644 index 0000000..9045b32 --- /dev/null +++ b/packages/core/src/services/geo-locations/GeoLocationsOrdersService.ts @@ -0,0 +1,384 @@ +import { inject, injectable } from 'inversify'; +import { OrdersService } from '../orders'; +import Warehouse from '@modules/server.common/entities/Warehouse'; +import GeoLocation from '@modules/server.common/entities/GeoLocation'; +import * as _ from 'lodash'; +import Logger from 'bunyan'; +import Order from '@modules/server.common/entities/Order'; +import { createEverLogger } from '../../helpers/Log'; +import { GeoLocationsWarehousesService } from './GeoLocationsWarehousesService'; +import Bluebird from 'bluebird'; +import { + WarehousesProductsService, + WarehousesOrdersService, + WarehousesService, +} from '../warehouses'; +import { + observableListener, + routerName, + serialization, + asyncListener, +} from '@pyro/io'; +import IGeoLocationOrdersRouter from '@modules/server.common/routers/IGeoLocationOrdersRouter'; +import IGeoLocation from '@modules/server.common/interfaces/IGeoLocation'; +import IService from '../IService'; +import { ExistenceEventType } from '@pyro/db-server'; +import { concat, exhaustMap, filter, first, share } from 'rxjs/operators'; +import { of, from } from 'rxjs'; +import OrderWarehouseStatus from '@modules/server.common/enums/OrderWarehouseStatus'; +import OrderCarrierStatus from '@modules/server.common/enums/OrderCarrierStatus'; +import { GeoLocationOrdersOptions } from './GeoLocationOrdersOptions'; +import { ObjectId } from 'bson'; + +@injectable() +@routerName('geo-location-orders') +export class GeoLocationsOrdersService + implements IGeoLocationOrdersRouter, IService { + protected readonly log: Logger = createEverLogger({ + name: 'geoLocationsOrdersService', + }); + + constructor( + @inject(OrdersService) protected ordersService: OrdersService, + @inject(WarehousesService) + protected warehousesService: WarehousesService, + @inject(GeoLocationsWarehousesService) + protected geoLocationsWarehousesService: GeoLocationsWarehousesService, + @inject(WarehousesProductsService) + protected warehousesProductsService: WarehousesProductsService, + @inject(WarehousesOrdersService) + protected warehousesOrdersService: WarehousesOrdersService + ) {} + + @observableListener() + get( + @serialization( + (geoLocationParam: IGeoLocation) => + new GeoLocation(geoLocationParam) + ) + geoLocation: GeoLocation, + options: { populateWarehouse?: boolean; populateCarrier?: boolean } = {} + ) { + return of(null).pipe( + concat( + this.ordersService.existence.pipe( + exhaustMap((existenceEvent) => { + switch (existenceEvent.type as ExistenceEventType) { + case ExistenceEventType.Created: + case ExistenceEventType.Updated: + if (existenceEvent.value != null) { + return this.warehousesService + .get(existenceEvent.value.warehouseId) + .pipe(first()); + } else { + return this.warehousesService + .get('wrong') + .pipe(first()); + } + case ExistenceEventType.Removed: + if (existenceEvent.lastValue != null) { + return this.warehousesService + .get( + existenceEvent.lastValue.warehouseId + ) + .pipe(first()); + } else { + return this.warehousesService + .get('wrong') + .pipe(first()); + } + } + }), + filter((warehouse) => + warehouse != null + ? GeoLocationsWarehousesService.isNearly( + warehouse, + geoLocation + ) + : true + ), + share() + ) + ), + exhaustMap(() => from(this._get(geoLocation, options))) + ); + } + + @asyncListener() + async getCountOfOrdersForWork( + geoLocation: IGeoLocation, + skippedOrderIds: string[] = [], + searchObj?: { byRegex: Array<{ key: string; value: string }> } + ): Promise { + const merchants = await this.geoLocationsWarehousesService.getMerchants( + geoLocation, + GeoLocationsWarehousesService.TrackingDistance, + { fullProducts: false, activeOnly: true } + ); + + const merchantsIds = merchants.map((m) => m._id.toString()); + + let searchByRegex = []; + + if (searchObj && searchObj.byRegex.length > 0) { + searchByRegex = searchObj.byRegex.map((s) => { + return { [s.key]: { $regex: s.value, $options: 'i' } }; + }); + } + + const count = await this.ordersService.Model.aggregate([ + { + $lookup: { + from: 'warehouses', + let: { + wh: '$warehouse', + }, + pipeline: [ + { + $match: { + $expr: { + $eq: [ + { + $toString: '$_id', + }, + '$$wh', + ], + }, + }, + }, + { + $project: { + carrierCompetition: { + $cond: { + if: { + $eq: ['$carrierCompetition', true], + }, + then: + OrderCarrierStatus.CarrierSelectedOrder, + else: OrderCarrierStatus.NoCarrier, + }, + }, + }, + }, + ], + as: 'fromWH', + }, + }, + { + $unwind: { + path: '$fromWH', + }, + }, + { + $match: _.assign( + { + warehouse: { $in: merchantsIds }, + warehouseStatus: { + $eq: OrderWarehouseStatus.PackagingFinished, + }, + $expr: { + $lte: [ + '$carrierStatus', + '$fromWH.carrierCompetition', + ], + }, + _id: { $nin: skippedOrderIds }, + }, + ...searchByRegex + ), + }, + ]); + return count.length; + } + + @asyncListener() + async getOrdersForWork( + geoLocation: IGeoLocation, + skippedOrderIds: string[] = [], + options: GeoLocationOrdersOptions, + searchObj?: { + isCancelled?: boolean; + byRegex?: Array<{ key: string; value: string }>; + } + ): Promise { + const merchants = await this.geoLocationsWarehousesService.getMerchants( + geoLocation, + GeoLocationsWarehousesService.TrackingDistance, + { fullProducts: false, activeOnly: true } + ); + + const merchantsIds = merchants.map((m) => m._id.toString()); + + let searchByRegex = []; + + if (searchObj) { + const byRegex = searchObj.byRegex; + + if (byRegex && byRegex.length > 0) { + searchByRegex = byRegex.map((s) => { + return { [s.key]: { $regex: s.value, $options: 'i' } }; + }); + } + + const isCancelled = searchObj.isCancelled; + + if (isCancelled != null) { + searchByRegex.push({ isCancelled }); + } + } + + const orders = await this.ordersService.Model.aggregate([ + { + $lookup: { + from: 'warehouses', + let: { + wh: '$warehouse', + }, + pipeline: [ + { + $match: { + $expr: { + $eq: [ + { + $toString: '$_id', + }, + '$$wh', + ], + }, + }, + }, + { + $project: { + carrierCompetition: { + $cond: { + if: { + $eq: ['$carrierCompetition', true], + }, + then: + OrderCarrierStatus.CarrierSelectedOrder, + else: OrderCarrierStatus.NoCarrier, + }, + }, + }, + }, + ], + as: 'fromWH', + }, + }, + { + $unwind: { + path: '$fromWH', + }, + }, + { + $match: _.assign( + { + warehouse: { $in: merchantsIds }, + warehouseStatus: { + $eq: OrderWarehouseStatus.PackagingFinished, + }, + $expr: { + $lte: [ + '$carrierStatus', + '$fromWH.carrierCompetition', + ], + }, + _id: { + $nin: skippedOrderIds.map((id) => new ObjectId(id)), + }, + }, + ...searchByRegex + ), + }, + { + $sort: { + _createdAt: + options.sort && + options.sort.toLowerCase().includes('desc') + ? -1 + : 1, + }, + }, + ]) + .allowDiskUse(true) + .skip(options.skip || 0) + .limit(options.limit || 1) + .exec(); + + return orders + .filter((o) => o !== null) + .filter((o) => o.orderType === 0) + .map((o) => new Order(o)); + } + + /** + * Get Orders from Warehouses near the given location + * + * @private + * @param {GeoLocation} geoLocation + * @param {{ populateWarehouse?: boolean; populateCarrier?: boolean }} [options={}] + * @returns {Promise} + * @memberof GeoLocationsOrdersService + */ + private async _get( + geoLocation: GeoLocation, + options: { populateWarehouse?: boolean; populateCarrier?: boolean } = {} + ): Promise { + // First we look up warehouses which can contain interesting orders because they are close enough to given location + // Note: we can't embed warehouse location into order document itself, + // because warehouses could MOVE in theory while we process order. + // Next we will get all orders from founded warehouses + + const warehouses = await this.geoLocationsWarehousesService + .get(geoLocation, { activeOnly: true }) + .pipe(first()) + .toPromise(); + + this.log.info( + { + geoLocation, + warehouses, + }, + 'warehouses by location' + ); + + const orders = _.flatten( + await Bluebird.map(warehouses, async (warehouse: Warehouse) => { + const warehouseOrders = await this.warehousesOrdersService + .get(warehouse.id, { + populateCarrier: !!options.populateCarrier, + }) + .pipe(first()) + .toPromise(); + + if (options.populateWarehouse) { + _.each( + warehouseOrders, + (order) => (order.warehouse = warehouse) + ); + } + + this.log.info( + { + geoLocation, + warehouseOrders, + warehouse, + }, + 'orders by warehouse' + ); + + return warehouseOrders; + }) + ); + + this.log.info( + { + geoLocation, + orders, + }, + 'orders by warehouses' + ); + + return orders; + } +} diff --git a/packages/core/src/services/geo-locations/GeoLocationsProductsService.ts b/packages/core/src/services/geo-locations/GeoLocationsProductsService.ts new file mode 100644 index 0000000..6b3b354 --- /dev/null +++ b/packages/core/src/services/geo-locations/GeoLocationsProductsService.ts @@ -0,0 +1,240 @@ +import { inject, injectable } from 'inversify'; +import Logger from 'bunyan'; +import { WarehousesService } from '../warehouses'; +import Warehouse from '@modules/server.common/entities/Warehouse'; +import GeoLocation from '@modules/server.common/entities/GeoLocation'; +import ProductInfo from '@modules/server.common/entities/ProductInfo'; +import _, { chain, clone, sortBy } from 'underscore'; +import Utils from '@modules/server.common/utils'; +import { createEverLogger } from '../../helpers/Log'; +import { GeoLocationsWarehousesService } from './GeoLocationsWarehousesService'; +import IGeoLocation from '@modules/server.common/interfaces/IGeoLocation'; +import { + observableListener, + routerName, + serialization, + asyncListener, +} from '@pyro/io'; +import IGeoLocationProductsRouter from '@modules/server.common/routers/IGeoLocationProductsRouter'; +import IService from '../IService'; +import { map } from 'rxjs/operators'; +import { + IProductTitle, + IProductDescription, + IProductDetails, +} from '@modules/server.common/interfaces/IProduct'; +import WarehouseProduct from '@modules/server.common/entities/WarehouseProduct'; +import { IGetGeoLocationProductsOptions } from 'graphql/geo-locations/geo-location.resolver'; +import IWarehouseProduct from '@modules/server.common/interfaces/IWarehouseProduct'; + +@injectable() +@routerName('geo-location-products') +export class GeoLocationsProductsService + implements IGeoLocationProductsRouter, IService { + protected log: Logger = createEverLogger({ + name: 'geoLocationsProductsService', + }); + + constructor( + @inject(WarehousesService) + protected warehousesService: WarehousesService, + @inject(GeoLocationsWarehousesService) + protected geoLocationsWarehousesService: GeoLocationsWarehousesService + ) {} + + @observableListener() + get( + @serialization((g: IGeoLocation) => new GeoLocation(g)) + geoLocation: GeoLocation, + options?: IGetGeoLocationProductsOptions + ) { + return this.geoLocationsWarehousesService + .get(geoLocation, { fullProducts: true, activeOnly: true }) + .pipe( + map((warehouses) => + this._getProductsFromWarehouses( + geoLocation, + warehouses, + options + ) + ) + ); + } + + @asyncListener() + async getCountOfGeoLocationProducts( + geoLocation: IGeoLocation, + options?: IGetGeoLocationProductsOptions, + searchText?: string + ): Promise { + try { + const merchants = await this.geoLocationsWarehousesService.getMerchants( + geoLocation, + GeoLocationsWarehousesService.TrackingDistance, + { + fullProducts: true, + activeOnly: true, + merchantsIds: options ? options.merchantIds : null, + } + ); + const productsIds = merchants.map((m) => { + let products = m.products + .filter((wProduct) => + this.productsFilter(wProduct, options) + ) + .filter((wProduct) => + this.filterBySearchText(wProduct, searchText) + ); + + if (!options || !options.withoutCount) { + products = products.filter( + (wProduct) => wProduct.count > 0 + ); + } + + return products.map((p) => new WarehouseProduct(p).productId); + }); + + return ( + productsIds.flat().filter((x, i, a) => a.indexOf(x) == i) + .length || 0 + ); + } catch (error) { + return 0; + } + } + + @asyncListener() + async geoLocationProductsByPaging( + @serialization((g: IGeoLocation) => new GeoLocation(g)) + geoLocation: GeoLocation, + pagingOptions, + options?: IGetGeoLocationProductsOptions, + searchText?: string + ): Promise { + const merchants = await this.geoLocationsWarehousesService.getMerchants( + geoLocation, + GeoLocationsWarehousesService.TrackingDistance, + { + fullProducts: true, + activeOnly: true, + merchantsIds: options ? options.merchantIds : null, + } + ); + const products = this._getProductsFromWarehouses( + geoLocation, + merchants.map((m) => new Warehouse(m)), + options, + searchText + ); + return products.slice(pagingOptions.skip).slice(0, pagingOptions.limit); + } + + private _getProductsFromWarehouses( + geoLocation: GeoLocation, + warehouses: Warehouse[], + options?: IGetGeoLocationProductsOptions, + searchText?: string + ): ProductInfo[] { + return chain(warehouses) + .map((_warehouse: Warehouse) => { + const warehouse = clone(_warehouse); + if (!options || !options.withoutCount) { + warehouse.products = warehouse.products.filter( + (wProduct: IWarehouseProduct) => wProduct.count > 0 + ); + } + if (options) { + warehouse.products = warehouse.products.filter((wProduct: IWarehouseProduct) => + this.productsFilter(wProduct, options) + ); + } + warehouse.products = warehouse.products.filter((wProduct: IWarehouseProduct) => + this.filterBySearchText(wProduct, searchText) + ); + return warehouse; + }) // remove all warehouse products which count is 0. + .map((warehouse: Warehouse) => + _.map(warehouse.products, (warehouseProduct) => { + return new ProductInfo({ + warehouseId: warehouse._id.toString(), + warehouseLogo: warehouse.logo, + warehouseProduct, + distance: Utils.getDistance( + geoLocation, + warehouse.geoLocation + ), + }); + }) + ) + .flatten() + .groupBy((productInfo) => productInfo.warehouseProduct.productId) + .filter((productInfo) => !_.isUndefined(productInfo)) + .map((productInfos: ProductInfo[]) => sortBy(productInfos, 'distance')) + .map((productInfo) => productInfo as ProductInfo) + .flatten() + .value(); + } + + private productsFilter(wProduct, options) { + if (!options) { + return true; + } + + wProduct.product.images = wProduct.product.images.filter((i) => { + return ( + (options.imageOrientation !== undefined + ? options.imageOrientation === 1 + ? i.orientation === 1 + : i.orientation === 0 || i.orientation === 2 + : true) && + (options.locale !== undefined + ? i.locale === options.locale + : true) + ); + }); + + if (!wProduct.product.images || wProduct.product.images.length === 0) { + return false; + } + + return options.isDeliveryRequired + ? wProduct.isDeliveryRequired === options.isDeliveryRequired + : true && options.isTakeaway + ? wProduct.isTakeaway === options.isTakeaway + : true; + } + + private filterBySearchText(wProduct, searchText) { + if (!searchText) { + return true; + } + + let titles = wProduct.product['title']; + titles = titles ? titles.map((t: IProductTitle) => t.value) : []; + let descriptions = wProduct.product['description']; + descriptions = descriptions + ? descriptions.map((d: IProductDescription) => d.value) + : []; + let details = wProduct.product['details']; + details = details ? details.map((d: IProductDetails) => d.value) : []; + + return ( + (titles && + titles + .join() + .toLocaleLowerCase() + .includes(searchText.toLocaleLowerCase())) || + (descriptions && + descriptions + .join() + .toLocaleLowerCase() + .includes(searchText.toLocaleLowerCase())) || + (details && + details + .join() + .toLocaleLowerCase() + .includes(searchText.toLocaleLowerCase())) + ); + } +} diff --git a/packages/core/src/services/geo-locations/GeoLocationsService.ts b/packages/core/src/services/geo-locations/GeoLocationsService.ts new file mode 100644 index 0000000..baba20e --- /dev/null +++ b/packages/core/src/services/geo-locations/GeoLocationsService.ts @@ -0,0 +1,123 @@ +import { injectable } from 'inversify'; +import Logger from 'bunyan'; +import * as _ from 'lodash'; +import { createEverLogger } from '../../helpers/Log'; +import { routerName, asyncListener } from '@pyro/io'; +import IService from '../IService'; +import axios from 'axios'; +import IGeoLocationsRouter from '@modules/server.common/routers/IGeoLocationsRouter'; +import { env } from '../../env'; +import { inspect } from 'util'; + +@injectable() +@routerName('geo-location') +export class GeoLocationsService implements IGeoLocationsRouter, IService { + protected log: Logger = createEverLogger({ + name: 'GeoLocationsService', + }); + + private arcgisClientID: string; + private arcgisClientSecret: string; + + constructor() { + this.arcgisClientID = env.ARCGIS_CLIENT_ID; + this.arcgisClientSecret = env.ARCGIS_CLIENT_SECRET; + } + + @asyncListener() + async getAddressByCoordinatesUsingArcGIS( + lat: number, + lng: number + ): Promise { + if (!this.arcgisClientID || !this.arcgisClientSecret) { + this.log.info( + `Cannot use getAddressByCoordinatesUsingArcGIS without${ + this.arcgisClientID ? '' : ' arcgisClientID' + }${this.arcgisClientSecret ? '' : ' arcgisClientSecret'}` + ); + + return null; + } + + try { + this.log.info( + `Attempt to reverse Geocode coordinates: ${lat},${lng}` + ); + + const tokenRequestUrl = `https://www.arcgis.com/sharing/oauth2/token?client_id=${this.arcgisClientID}&client_secret=${this.arcgisClientSecret}&grant_type=client_credentials&f=json`; + + const tokenResult = await axios.get(tokenRequestUrl); + + if ( + !tokenResult || + !tokenResult.data || + !tokenResult.data['access_token'] + ) { + this.log.info( + `Cannot get arcgis token with client_id=${this.arcgisClientID}, client_secret=${this.arcgisClientSecret}` + ); + return null; + } else { + const token = tokenResult.data['access_token']; + + // tslint:disable-next-line:max-line-length + const requestBaseUrl = `https://geocode.arcgis.com/arcgis/rest/services/World/GeocodeServer/reverseGeocode?location=${lng}%2C${lat}&distance=200&f=json`; + + const requestUrl = `${requestBaseUrl}&forStorage=true&token=${token}`; + + const resp = await axios.get(requestUrl); + + if ( + resp && + resp.data && + resp.data['address'] && + (resp.data['address'].City || + resp.data['address'].Region || + resp.data['address'].Subregion) + ) { + let locality: string; + + if (resp.data['address'].City) { + locality = resp.data['address'].City; + } else if (resp.data['address'].Subregion) { + locality = resp.data['address'].Subregion; + } else if (resp.data['address'].Region) { + locality = resp.data['address'].Region; + } + + const result = { + locality, + + // replace removes numbers and trim spaces (they are usually wrong anyway!) + thoroughfare: resp.data['address'].Address + ? resp.data['address'].Address.replace( + /\d+|^\s+|\s+$/g, + '' + ).replace( + /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, + '' + ) + : null, + }; + + this.log.info( + `Attempted to reverse Geocode coordinates: ${lat}, ${lng}. Got results: ` + + `${JSON.stringify(result)}` + ); + + return result; + } else { + this.log.info( + `Attempted to reverse Geocode coordinates: ${lat}, ${lng}. ` + + `Got empty response: ${resp ? inspect(resp) : ''}` + ); + return null; + } + } + } catch (err) { + // Do not report it as error because geo-coding may simply not work for given coordinates + this.log.info(err); + return null; + } + } +} diff --git a/packages/core/src/services/geo-locations/GeoLocationsWarehousesService.ts b/packages/core/src/services/geo-locations/GeoLocationsWarehousesService.ts new file mode 100644 index 0000000..049e5fb --- /dev/null +++ b/packages/core/src/services/geo-locations/GeoLocationsWarehousesService.ts @@ -0,0 +1,222 @@ +import { injectable } from 'inversify'; +import Logger from 'bunyan'; +import * as _ from 'lodash'; +import Utils from '@modules/server.common/utils'; +import { createEverLogger } from '../../helpers/Log'; +import { WarehousesService } from '../warehouses'; +import GeoLocation from '@modules/server.common/entities/GeoLocation'; +import Warehouse from '@modules/server.common/entities/Warehouse'; +import IWarehouse from '@modules/server.common/interfaces/IWarehouse'; +import { Observable } from 'rxjs'; +import { + observableListener, + routerName, + serialization, + asyncListener, +} from '@pyro/io'; +import IGeoLocation from '@modules/server.common/interfaces/IGeoLocation'; +import IGeoLocationWarehousesRouter from '@modules/server.common/routers/IGeoLocationWarehousesRouter'; +import IService from '../IService'; +import { ExistenceEventType } from '@pyro/db-server'; +import { of } from 'rxjs'; +import { concat, exhaustMap, filter, share } from 'rxjs/operators'; + +@injectable() +@routerName('geo-location-warehouses') +export class GeoLocationsWarehousesService + implements IGeoLocationWarehousesRouter, IService { + protected log: Logger = createEverLogger({ + name: 'geoLocationsWarehousesService', + }); + + // TODO: this should be something dynamic and stored in DB (per Warehouse?) + static TrackingDistance: number = 50000; + + static isNearly(warehouse: Warehouse, geoLocation: GeoLocation): boolean { + return ( + Utils.getDistance(warehouse.geoLocation, geoLocation) <= + GeoLocationsWarehousesService.TrackingDistance + ); + } + + constructor(protected warehousesService: WarehousesService) {} + + @observableListener() + get( + @serialization((g: IGeoLocation) => new GeoLocation(g)) + geoLocation: GeoLocation, + @serialization((o: any) => _.omit(o, ['fullProducts', 'activeOnly'])) + _options?: { fullProducts?: boolean; activeOnly?: boolean } + ): Observable { + const options = { + fullProducts: _options != null && _options.fullProducts != null, + activeOnly: + _options != null && _options.activeOnly != null + ? _options.activeOnly + : false, + }; + + return of(null).pipe( + concat( + this.warehousesService.existence.pipe( + filter((existenceEvent) => { + let warehouse: Warehouse | null; + let oldWarehouse: Warehouse | null; + + switch (existenceEvent.type as ExistenceEventType) { + case ExistenceEventType.Created: + warehouse = existenceEvent.value; + + if (warehouse == null) { + return false; + } + + return ( + GeoLocationsWarehousesService.isNearly( + warehouse, + geoLocation + ) && + (options.activeOnly + ? warehouse.isActive + : true) + ); + + case ExistenceEventType.Updated: + warehouse = existenceEvent.value; + oldWarehouse = existenceEvent.lastValue; + + if (warehouse == null || oldWarehouse == null) { + return false; + } + + return ( + GeoLocationsWarehousesService.isNearly( + warehouse, + geoLocation + ) !== + GeoLocationsWarehousesService.isNearly( + oldWarehouse, + geoLocation + ) && + (options.activeOnly + ? warehouse.isActive !== + oldWarehouse.isActive + : true) + ); + + case ExistenceEventType.Removed: + oldWarehouse = existenceEvent.lastValue; + + if (oldWarehouse == null) { + return false; + } + + return ( + GeoLocationsWarehousesService.isNearly( + oldWarehouse, + geoLocation + ) && + (options.activeOnly + ? oldWarehouse.isActive + : true) + ); + } + }), + share() + ) + ), + exhaustMap(() => + this._get( + geoLocation, + GeoLocationsWarehousesService.TrackingDistance, + options + ) + ), + share() + ); + } + + @asyncListener() + async getMerchants( + geoLocation: IGeoLocation, + maxDistance: number, + options: { + fullProducts: boolean; + activeOnly: boolean; + merchantsIds?: string[]; + inStoreMode?: boolean; + } + ): Promise { + const merchantsIds = options.merchantsIds; + const merchants = (await this.warehousesService.Model.find( + _.assign( + { + 'geoLocation.loc': { + $near: { + $geometry: { + type: 'Point', + coordinates: geoLocation.loc.coordinates, + }, + $maxDistance: maxDistance, + }, + }, + }, + options.activeOnly ? { isActive: true } : {}, + options.inStoreMode ? { inStoreMode: true } : {}, + merchantsIds && merchantsIds.length > 0 + ? { + _id: { $in: merchantsIds }, + } + : {} + ) + ) + .populate(options.fullProducts ? 'products.product' : '') + .lean() + .exec()) as IWarehouse[]; + + return merchants; + } + + /** + * Get warehouses available for given location + * + * @private + * @param {GeoLocation} geoLocation + * @param {number} maxDistance + * @param {{ fullProducts: boolean; activeOnly: boolean }} options + * @returns {Promise} + * @memberof GeoLocationsWarehousesService + */ + private async _get( + geoLocation: GeoLocation, + maxDistance: number, + options: { fullProducts: boolean; activeOnly: boolean } + ): Promise { + // TODO: first filter by City / Country and only then look up by coordinates + const warehouses = (await this.warehousesService.Model.find( + _.assign( + { + 'geoLocation.loc': { + $near: { + $geometry: { + type: 'Point', + coordinates: geoLocation.loc.coordinates, + }, + + // TODO: set distance PER warehouse? + // It's hard to make this work however, as seems this should be constant value... + // however we may want to check MongoDB docs! + $maxDistance: maxDistance, + }, + }, + }, + options.activeOnly ? { isActive: true } : {} + ) + ) + .populate(options.fullProducts ? 'products.product' : '') + .lean() + .exec()) as IWarehouse[]; + + return warehouses.map((warehouse) => new Warehouse(warehouse)); + } +} diff --git a/packages/core/src/services/geo-locations/index.ts b/packages/core/src/services/geo-locations/index.ts new file mode 100644 index 0000000..0c9d327 --- /dev/null +++ b/packages/core/src/services/geo-locations/index.ts @@ -0,0 +1,5 @@ +export * from './GeoLocationsOrdersService'; +export * from './GeoLocationOrdersOptions'; +export * from './GeoLocationsProductsService'; +export * from './GeoLocationsWarehousesService'; +export * from './GeoLocationsService'; diff --git a/packages/core/src/services/inversify.config.ts b/packages/core/src/services/inversify.config.ts new file mode 100644 index 0000000..99f8528 --- /dev/null +++ b/packages/core/src/services/inversify.config.ts @@ -0,0 +1,125 @@ +import 'reflect-metadata'; +import { Container, interfaces, ContainerModule } from 'inversify'; +import * as _ from 'lodash'; +import { IRoutersManager, RoutersManager, RouterSymbol } from '@pyro/io'; +import { CarriersOrdersService, CarriersService } from './carriers'; +import { + SocialRegisterService, + SocialStrategiesService, + UsersOrdersService, + UsersProductsService, + UsersService, + UserCommandService, +} from './users'; +import { ProductsCategoriesService, ProductsService } from './products'; +import { + WarehousesCarriersService, + WarehousesOrdersService, + WarehousesProductsService, + WarehousesService, + WarehousesUsersService, +} from './warehouses'; +import { OrdersService } from './orders'; +import { InvitesRequestsService, InvitesService } from './invites'; +import { + GeoLocationsOrdersService, + GeoLocationsProductsService, + GeoLocationsWarehousesService, + GeoLocationsService, +} from './geo-locations'; +import { DevicesService } from './devices'; +import { ServiceSymbol } from './IService'; +import { ConfigService } from '../config/config.service'; +import { ServicesApp } from './services.app'; +import { AuthenticationService, AuthService, authServiceFactory } from './auth'; +import { UsersAuthService } from './users/UsersAuthService'; +import { AdminsService } from './admins'; +import { getConnection, Repository } from 'typeorm'; +import Admin from '@modules/server.common/entities/Admin'; +import Device from '@modules/server.common/entities/Device'; +import { FakeOrdersService } from './fake-data/FakeOrdersService'; +import { CurrenciesService } from './currency/CurrencyService'; +import { PromotionService } from './products/PromotionService'; +import { AppsSettingsService } from './apps-settings'; + +function getRepository(t: any): any { + const conn = getConnection('typeorm'); + return conn.getRepository(t); +} + +const bindings = new ContainerModule((bind: interfaces.Bind) => { + bind>('AdminRepository') + .toDynamicValue(() => { + return getRepository(Admin); + }) + .inRequestScope(); + + bind>('DeviceRepository') + .toDynamicValue(() => { + return getRepository(Device); + }) + .inRequestScope(); + + _.each( + [ + ConfigService, + UserCommandService, + AdminsService, + CarriersOrdersService, + CarriersService, + DevicesService, + GeoLocationsOrdersService, + GeoLocationsProductsService, + GeoLocationsWarehousesService, + GeoLocationsService, + InvitesRequestsService, + InvitesService, + OrdersService, + ProductsService, + ProductsCategoriesService, + UsersOrdersService, + UsersService, + UsersAuthService, + SocialStrategiesService, + SocialRegisterService, + WarehousesOrdersService, + WarehousesProductsService, + WarehousesUsersService, + WarehousesCarriersService, + WarehousesService, + UsersProductsService, + AuthenticationService, + FakeOrdersService, + CurrenciesService, + PromotionService, + AppsSettingsService + ], + (Service: any) => { + bind(Service).to(Service).inSingletonScope(); + + bind(ServiceSymbol).toFactory((context) => { + return context.container.get(Service); + }); + + bind(RouterSymbol).toFactory((context) => { + return context.container.get(Service); + }); + } + ); + + bind(AuthService).toSelf(); + + bind('Factory').toFactory(authServiceFactory); + + bind('RoutersManager') + .to(RoutersManager) + .inSingletonScope(); + + bind(ServicesApp).toSelf().inSingletonScope(); +}); + +const container = new Container(); + +container.load(bindings); + +export const servicesContainer = container; diff --git a/packages/core/src/services/invites/InvitesRequestsService.ts b/packages/core/src/services/invites/InvitesRequestsService.ts new file mode 100644 index 0000000..1b76eb2 --- /dev/null +++ b/packages/core/src/services/invites/InvitesRequestsService.ts @@ -0,0 +1,404 @@ +import Logger from 'bunyan'; +import { inject, injectable } from 'inversify'; +import { createEverLogger } from '../../helpers/Log'; +import { IInviteRequestCreateObject } from '@modules/server.common/interfaces/IInviteRequest'; +import InviteRequest from '@modules/server.common/entities/InviteRequest'; +import { DBService, ExistenceEventType } from '@pyro/db-server'; +import { InvitesService } from './InvitesService'; +import { Subscription } from 'rxjs'; +import * as _ from 'lodash'; +import Invite from '@modules/server.common/entities/Invite'; +import ILanguage from '@modules/server.common/interfaces/ILanguage'; +import requestPromise from 'request-promise'; +import Bluebird from 'bluebird'; +import { DevicesService } from '../devices'; +import Device from '@modules/server.common/entities/Device'; +import { launched } from '@modules/server.common/notifications'; +import IInviteRequestRouter from '@modules/server.common/routers/IInviteRequestRouter'; +import { asyncListener, observableListener, routerName } from '@pyro/io'; +import IService from '../IService'; +import { env } from '../../env'; +import { filter, first, map, switchMap } from 'rxjs/operators'; +import { IGeoLocationCreateObject } from '@modules/server.common/interfaces/IGeoLocation'; +import { Country } from '@modules/server.common/entities/GeoLocation'; +import IPagingOptions from '@modules/server.common/interfaces/IPagingOptions'; +import * as faker from 'faker'; + +@injectable() +@routerName('invite-request') +export class InvitesRequestsService + extends DBService + implements IInviteRequestRouter, IService +{ + public readonly DBObject: any = InviteRequest; + protected readonly log: Logger = createEverLogger({ + name: 'invitesRequestsService', + }); + + protected pushSendingInvitesSubscription: Subscription = Subscription.EMPTY; + + constructor( + @inject(InvitesService) protected invitesService: InvitesService, + @inject(DevicesService) protected devicesService: DevicesService + ) { + super(); + + this.pushSendingInvitesSubscription = this.invitesService.existence + .pipe( + filter( + (existenceEvent) => + existenceEvent.type === ExistenceEventType.Created + ), + map((existenceEvent) => existenceEvent.value as Invite) + ) + .subscribe(async (invite) => { + interface AggregateResult { + _id: string; // deviceId + createdAt: string; + } + + const results: AggregateResult[] = await this.Model.aggregate() + .sort({ channelId: 1, _createdAt: 1 }) + .group({ + _id: '$deviceId', + createdAt: { $last: '$_createdAt' }, + }) + .exec(); + + if (results.length > 0) { + await this.notifyAboutLaunch( + invite, + _.map(results, (result) => result._id) + ); + } + }); + } + + @observableListener() + get(id: string) { + return super.get(id).pipe( + map(async (inviteReq) => { + await this.throwIfNotExists(id); + return inviteReq; + }), + switchMap((inviteReq) => inviteReq) + ); + } + + @asyncListener() + async create( + inviteRequest: IInviteRequestCreateObject + ): Promise { + return super.create(inviteRequest); + } + + @asyncListener() + async notifyAboutLaunch( + invite: Invite, + devicesIds: string[] + ): Promise { + const devices = await ( + await this.devicesService.getMultipleDevices(devicesIds) + ) + .pipe(first()) + .toPromise(); + + const devicesByLanguages = _.groupBy( + devices, + (device) => device.language + ); + const languages = _.keys(devicesByLanguages) as ILanguage[]; + + await (Bluebird).map(languages, async (language: ILanguage) => { + const devicesByLanguage: Device[] = devicesByLanguages[language]; + + const request = { + audience: this._getLaunchAudience(devicesByLanguage), + device_types: 'all', + notification: this._getLaunchNotification(language, invite), + }; + + try { + const rp: any = requestPromise; + + await rp({ + method: 'POST', + uri: 'https://go.urbanairship.com/api/push', + body: request, + headers: { + Accept: 'application/vnd.urbanairship+json; version=3;', + }, + auth: { + user: env.URBAN_AIRSHIP_KEY, + pass: env.URBAN_AIRSHIP_SECRET, + }, + json: true, + }); + } catch (e) { + console.error(`.notifyAboutLaunch(...) error: ${e.message}`); + throw e; + } + }); + } + + @asyncListener() + async getInvitesRequests( + findInput: any, + invited: any, + pagingOptions: IPagingOptions + ): Promise { + const sortObj = {}; + + const findNotInvited = { + ...findInput, + isDeleted: { $eq: false }, + isInvited: { $eq: false }, + }; + + if (pagingOptions.sort) { + sortObj[pagingOptions.sort.field] = pagingOptions.sort.sortBy; + } + + const inviteRequests = await this.Model.find(findNotInvited) + .sort(sortObj) + .skip(pagingOptions.skip) + .limit(pagingOptions.limit) + .lean() + .exec(); + + const allNotInvitedCount = await this.Model.find(findNotInvited) + .countDocuments() + .exec(); + + const skipInvited = + pagingOptions.skip + inviteRequests.length - allNotInvitedCount; + + if (invited && skipInvited >= 0) { + const invitedFromDB = await this.Model.find({ + ...findInput, + isDeleted: { $eq: false }, + isInvited: { $eq: true }, + }) + .sort({ invitedDate: 'desc' }) + .skip(skipInvited) + .limit(pagingOptions.limit - inviteRequests.length) + .lean() + .exec(); + + return [...inviteRequests, ...invitedFromDB]; + } + + return inviteRequests; + } + + async throwIfNotExists(inviteRequestId: string) { + const inviteRequest = await super + .get(inviteRequestId) + .pipe(first()) + .toPromise(); + + if (!inviteRequest || inviteRequest.isDeleted) { + throw Error( + `Invite request with id '${inviteRequestId}' does not exists!` + ); + } + } + + async generate1000InviteRequests(defaultLng: number, defaultLat: number) { + const invitesRequestsToCreate: IInviteRequestCreateObject[] = []; + + let inviteRequestsCount = 1; + + while (inviteRequestsCount <= 1000) { + const houseNumber = `${inviteRequestsCount}`; + + const requestLocation = this._getInviteRequestGeoLocationCreateObj( + houseNumber, + defaultLng, + defaultLat + ); + + invitesRequestsToCreate.push({ + isInvited: false, + apartment: `${inviteRequestsCount}`, + geoLocation: requestLocation, + }); + + inviteRequestsCount += 1; + } + + await this.Model.insertMany(invitesRequestsToCreate); + } + + private _getLaunchAudience(devices: Device[]) { + const audience: { + or: Array< + | { ios_channel: string[] | string } + | { android_channel: string[] | string } + >; + } = { + or: [], + }; + + const ios_devices = _.filter( + devices, + (device) => device.type === 'ios' + ); + + if (ios_devices.length > 0) { + audience.or.push({ + ios_channel: ios_devices + .map((device) => device.channelId) + .filter((channelId) => channelId != null) + .map((channelId) => channelId as string), + }); + } + + const android_devices = _.filter( + devices, + (device) => device.type === 'android' + ); + + if (android_devices.length > 0) { + audience.or.push({ + android_channel: android_devices + .map((device) => device.channelId) + .filter((channelId) => channelId != null) + .map((channelId) => channelId as string), + }); + } + + return audience; + } + + private _getLaunchNotification(language: ILanguage, invite: Invite): any { + switch (language) { + case 'en-US': + return { + android: { + title: 'Ever just launched!', + alert: 'Click to see some available products.', + extra: { + event: launched, + invite: JSON.stringify(invite), + }, + }, + ios: { + alert: 'Ever just launched at your address. Have fun!', + extra: { + event: launched, + invite: JSON.stringify(invite), + }, + }, + }; + case 'ru-RU': + return { + android: { + title: 'Ever только что запустился!', + alert: 'Кликните чтобы увидить доступные продукты.', + extra: { + event: launched, + invite: JSON.stringify(invite), + }, + }, + ios: { + alert: 'Ever тольуо что запустился по Вашему адресу. Удачи!', + extra: { + event: launched, + invite: JSON.stringify(invite), + }, + }, + }; + case 'bg-BG': + return { + android: { + title: 'Ever стартира!', + alert: 'Кликнете, за да видите някои налични продукти.', + extra: { + event: launched, + invite: JSON.stringify(invite), + }, + }, + ios: { + alert: 'Ever стартира на вашия адрес. Забавлявай се!', + extra: { + event: launched, + invite: JSON.stringify(invite), + }, + }, + }; + case 'he-IL': + return { + android: { + title: 'Ever הושק זה עתה!', + alert: 'לחץ כדי לראות כמה מוצרים זמינים.', + extra: { + event: launched, + invite: JSON.stringify(invite), + }, + }, + ios: { + alert: 'Ever הושק זה עתה בכתובת שלך. תעשה חיים!', + extra: { + event: launched, + invite: JSON.stringify(invite), + }, + }, + }; + case 'fr-FR': + return { + android: { + title: "Ever vient d'être lancé !", + alert: 'Cliquez pour voir quelques produits disponibles.', + extra: { + event: launched, + invite: JSON.stringify(invite), + }, + }, + ios: { + alert: "Ever vient d'être lancé à votre adresse. S'amuser!", + extra: { + event: launched, + invite: JSON.stringify(invite), + }, + }, + }; + default: + return { + android: { + title: 'הושקנו בכתובת שלך!', + alert: 'תלחץ כדי לצפות במוצרים!', + extra: { + event: launched, + invite: JSON.stringify(invite), + }, + }, + ios: { + alert: 'Ever הושק בכתובת שלך! תלחץ כדי לצפות במוצרים!', + extra: { + event: launched, + invite: JSON.stringify(invite), + }, + }, + }; + } + } + + private _getInviteRequestGeoLocationCreateObj( + houseNumber: string, + defaultLng: number, + defaultLat: number + ): IGeoLocationCreateObject { + const GeoLocation: IGeoLocationCreateObject = { + countryId: faker.datatype.number(Country.ZW) as Country, + city: faker.address.city(), + house: houseNumber, + loc: { + type: 'Point', + coordinates: [defaultLng, defaultLat], + }, + streetAddress: faker.address.streetAddress(), + }; + return GeoLocation; + } +} diff --git a/packages/core/src/services/invites/InvitesService.ts b/packages/core/src/services/invites/InvitesService.ts new file mode 100644 index 0000000..96b1bdf --- /dev/null +++ b/packages/core/src/services/invites/InvitesService.ts @@ -0,0 +1,294 @@ +import Logger from 'bunyan'; +import { injectable } from 'inversify'; +import Utils from '@modules/server.common/utils'; +import { createEverLogger } from '../../helpers/Log'; +import Invite from '@modules/server.common/entities/Invite'; +import { DBService, ExistenceEventType } from '@pyro/db-server'; +import { IInviteCreateObject } from '@modules/server.common/interfaces/IInvite'; +import IEnterByCode from '@modules/server.common/interfaces/IEnterByCode'; +import IEnterByLocation from '@modules/server.common/interfaces/IEnterByLocation'; +import IStreetLocation from '@modules/server.common/interfaces/IStreetLocation'; +import { Observable } from 'rxjs'; +import IInviteRouter from '@modules/server.common/routers/IInviteRouter'; +import { asyncListener, observableListener, routerName } from '@pyro/io'; +import IService from '../IService'; +import { of, from } from 'rxjs'; +import { + concat, + exhaustMap, + filter, + map, + first, + switchMap, +} from 'rxjs/operators'; +import _ = require('lodash'); +import { env } from '../../env'; +import { IGeoLocationCreateObject } from '@modules/server.common/interfaces/IGeoLocation'; +import { IInviteRequestCreateObject } from '@modules/server.common/interfaces/IInviteRequest'; +import * as faker from 'faker'; +import { Country } from '@modules/server.common/entities/GeoLocation'; +import IPagingOptions from '@modules/server.common/interfaces/IPagingOptions'; + +@injectable() +@routerName('invite') +export class InvitesService extends DBService + implements IInviteRouter, IService { + protected readonly log: Logger = createEverLogger({ + name: 'invitesService', + }); + + public readonly DBObject: any = Invite; + + protected _invitedStreetLocations: Observable; + + private static readonly InviteWorkingDistance = 50000; + + constructor() { + super(); + + this._invitedStreetLocations = of(null).pipe( + concat(this.existence), + exhaustMap(() => this._getInvitedStreetLocations()) + ); + } + + @observableListener() + get(id: string) { + return super.get(id).pipe( + map(async (invite) => { + await this.throwIfNotExists(id); + return invite; + }), + switchMap((invite) => { + return invite; + }) + ); + } + + @observableListener() + getInvitedStreetLocations() { + return this._invitedStreetLocations; + } + + @asyncListener() + create(invite: IInviteCreateObject): Promise { + if (!invite.code) { + invite.code = Utils.getRandomInt(1001, 9999) + ''; + } + return super.create(invite); + } + + @asyncListener() + getInvitesSettings(): Promise<{ isEnabled: boolean }> { + return new Promise<{ isEnabled: boolean }>((resolve, reject) => { + resolve({ isEnabled: env.SETTING_INVITES_ENABLED }); + }); + } + + /** + * Get Invite by Code + * Warning: can emit null on start! + * + * @param {IEnterByCode} info + * @returns {(Observable)} + * @memberof InvitesService + */ + @observableListener() + getByCode(info: IEnterByCode): Observable { + const findObject = { + code: info.inviteCode, + }; + + if (info.inviteCode !== env.FAKE_INVITE_CODE.toString()) { + findObject['geoLocation.loc'] = { + $near: { + $geometry: { + type: 'Point', + coordinates: info.location.coordinates, + }, + $maxDistance: InvitesService.InviteWorkingDistance, // 50Km distance for testing only! + }, + }; + } + + return from( + this.findOne({ ...findObject, isDeleted: { $eq: false } }) + ).pipe( + concat( + this.existence.pipe( + filter( + (event) => event.type !== ExistenceEventType.Removed + ), + map((event) => event.value as Invite), + filter((invite) => { + return ( + Utils.getLocDistance( + invite.geoLocation.loc, + info.location + ) <= InvitesService.InviteWorkingDistance && + invite.code === info.inviteCode + ); + }) + ) + ) + ); + } + + /** + * Get Invite by Customer Location + * + * @param {IEnterByLocation} info + * @returns {(Observable)} + * @memberof InvitesService + */ + @observableListener() + getByLocation(info: IEnterByLocation): Observable { + const findObject = { + 'geoLocation.city': info.city, + 'geoLocation.streetAddress': info.streetAddress, + 'geoLocation.house': info.house, + 'geoLocation.countryId': info.countryId, + apartment: info.apartment, + }; + + if (info.postcode != null) { + findObject['geoLocation.postcode'] = info.postcode; + } + + return from( + this.findOne({ ...findObject, isDeleted: { $eq: false } }) + ).pipe( + concat( + this.existence.pipe( + filter( + (event) => event.type !== ExistenceEventType.Removed + ), + map((event) => event.value as Invite), + filter((invite) => { + return ( + invite.geoLocation.city === info.city && + invite.geoLocation.streetAddress === + info.streetAddress && + invite.geoLocation.house === info.house && + invite.geoLocation.countryId === info.countryId && + invite.apartment === info.apartment + ); + }) + ) + ) + ); + } + + @asyncListener() + async getInvites( + findInput: any, + pagingOptions: IPagingOptions + ): Promise { + const sortObj = {}; + if (pagingOptions.sort) { + sortObj[pagingOptions.sort.field] = pagingOptions.sort.sortBy; + } + + return this.Model.find({ + ...findInput, + isDeleted: { $eq: false }, + }) + .sort(sortObj) + .skip(pagingOptions.skip) + .limit(pagingOptions.limit) + .lean() + .exec(); + } + + async throwIfNotExists(inviteId: string) { + const invite = await super.get(inviteId).pipe(first()).toPromise(); + + if (!invite || invite.isDeleted) { + throw Error(`Invite with id '${inviteId}' does not exists!`); + } + } + + /** + * Generates Fake Invites, connected to Fake Invite Requests + * TODO: rename, make async and add 1000 as parameter. Move to separate Fake Data service + * + * @param {number} defaultLng + * @param {number} defaultLat + * @memberof InvitesService + */ + generate1000InvitesConnectedToInviteRequests( + defaultLng: number, + defaultLat: number + ): { + invitesRequestsToCreate: IInviteRequestCreateObject[]; + invitesToCreate: IInviteCreateObject[]; + } { + const invitesToCreate: IInviteCreateObject[] = []; + const invitesRequestsToCreate: IInviteRequestCreateObject[] = []; + + let inviteCount = 1; + + while (inviteCount <= 1000) { + const apartment: string = `${inviteCount}`; + const houseNumber = `${inviteCount}`; + const geoLocation: IGeoLocationCreateObject = this._getInviteGeoLocationCreateObj( + houseNumber, + defaultLng, + defaultLat + ); + + invitesRequestsToCreate.push({ + apartment, + geoLocation, + isInvited: true, + invitedDate: new Date(), + }); + + invitesToCreate.push({ + code: `${999 + inviteCount}`, + apartment, + geoLocation, + }); + + inviteCount += 1; + } + + return { + invitesRequestsToCreate, + invitesToCreate, + }; + } + + private _getInviteGeoLocationCreateObj( + houseNumber: string, + defaultLng: number, + defaultLat: number + ): IGeoLocationCreateObject { + // TODO: make TSlint happy + // tslint:disable-next-line:no-object-literal-type-assertion + return { + countryId: faker.datatype.number(Country.ZW) as Country, + city: faker.address.city(), + house: houseNumber, + loc: { + type: 'Point', + coordinates: [defaultLng, defaultLat], + }, + streetAddress: faker.address.streetAddress(), + } as IGeoLocationCreateObject; + } + + private async _getInvitedStreetLocations(): Promise { + const results = await this.Model.aggregate() + .group({ + _id: { + streetAddress: '$geoLocation.streetAddress', + city: '$geoLocation.city', + country: '$geoLocation.countryId', + }, + }) + .exec(); + + return _.map(results, (result: { _id: IStreetLocation }) => result._id); + } +} diff --git a/packages/core/src/services/invites/index.ts b/packages/core/src/services/invites/index.ts new file mode 100644 index 0000000..2135099 --- /dev/null +++ b/packages/core/src/services/invites/index.ts @@ -0,0 +1,2 @@ +export * from './InvitesService'; +export * from './InvitesRequestsService'; diff --git a/packages/core/src/services/orders/OrdersService.ts b/packages/core/src/services/orders/OrdersService.ts new file mode 100644 index 0000000..e0e8992 --- /dev/null +++ b/packages/core/src/services/orders/OrdersService.ts @@ -0,0 +1,957 @@ +import Logger from 'bunyan'; +import Bluebird from 'bluebird'; +import { inject, injectable, LazyServiceIdentifer } from 'inversify'; +import { env } from '../../env'; +import { WarehousesService, WarehousesProductsService } from '../warehouses'; +import { createEverLogger } from '../../helpers/Log'; +import OrderCarrierStatus from '@modules/server.common/enums/OrderCarrierStatus'; +import OrderWarehouseStatus from '@modules/server.common/enums/OrderWarehouseStatus'; +import { UsersService } from '../users'; +import Order from '@modules/server.common/entities/Order'; +import { DBService } from '@pyro/db-server'; +import { + default as IOrder, + IOrderCreateObject, +} from '@modules/server.common/interfaces/IOrder'; +import CarriersService from '../carriers/CarriersService'; +import IOrderRouter from '@modules/server.common/routers/IOrderRouter'; +import { asyncListener, observableListener, routerName } from '@pyro/io'; +import IService from '../IService'; +import { exhaustMap, first, switchMap, map } from 'rxjs/operators'; +import { v1 as uuid } from 'uuid'; +import OrderProduct from '@modules/server.common/entities/OrderProduct'; +import { IOrderProductCreateObject } from '@modules/server.common/interfaces/IOrderProduct'; +import _ = require('lodash'); +import Product from '@modules/server.common/entities/Product'; +import Warehouse, { + WithFullProducts, +} from '@modules/server.common/entities/Warehouse'; +import OrderStatus from '@modules/server.common/enums/OrderStatus'; +import User from '@modules/server.common/entities/User'; +import { ProductsService } from '../../services/products'; +import { Observable } from 'rxjs'; +import { Stripe } from 'stripe' + +@injectable() +@routerName('order') +export class OrdersService extends DBService + implements IOrderRouter, IService { + public readonly DBObject: any = Order; + + // TODO: this and other Stripe related things should be inside separate Payments Service + private stripe: Stripe = new Stripe(env.STRIPE_SECRET_KEY, { + apiVersion: "2020-08-27" + }); + + protected readonly log: Logger = createEverLogger({ + name: 'ordersService', + }); + + static readonly FindObjects = { + isCompleted: { + $or: [ + { + isPaid: true, + carrierStatus: OrderCarrierStatus.DeliveryCompleted, + }, + { + isCancelled: true, + }, + ], + }, + isNotCompleted: { + $and: [ + { + isPaid: false, + carrierStatus: { + $ne: OrderCarrierStatus.DeliveryCompleted, + }, + }, + { + isCancelled: false, + }, + ], + }, + }; + + constructor( + @inject(new LazyServiceIdentifer(() => WarehousesService)) + protected warehousesService: WarehousesService, + @inject(new LazyServiceIdentifer(() => UsersService)) + protected usersService: UsersService, + @inject(new LazyServiceIdentifer(() => CarriersService)) + protected carriersService: CarriersService, + @inject(new LazyServiceIdentifer(() => WarehousesProductsService)) + protected warehousesProductsService: WarehousesProductsService, + @inject(new LazyServiceIdentifer(() => WarehousesService)) + protected _storesService: WarehousesService, + @inject(new LazyServiceIdentifer(() => ProductsService)) + protected _productsService: ProductsService + ) { + super(); + } + + async generateOrdersPerEachCustomer(customers: any[]): Promise { + const stores = await this._storesService.findAll({ + _id: 1, + products: 1, + }); + + const products = await this._productsService.findAll(); + + const orders: IOrderCreateObject[] = []; + + customers.forEach((customer, index) => { + const storeId = stores[index % stores.length]._id; + const product1Price = Math.round(Math.random() * 99); + const product2Price = Math.round(Math.random() * 99); + + orders.push({ + products: [ + { + count: 2, + isManufacturing: true, + isCarrierRequired: true, + isDeliveryRequired: true, + price: product1Price, + initialPrice: product1Price, + product: products[index % products.length], + }, + { + count: 2, + isManufacturing: true, + isCarrierRequired: true, + isDeliveryRequired: true, + price: product2Price, + initialPrice: product2Price, + product: products[(index + 1) % products.length], + }, + ], + user: customer, + warehouse: storeId, + orderNumber: index, + }); + }); + + await this.Model.insertMany(orders); + } + + @observableListener() + get( + id: Order['id'], + options: { populateWarehouse?: boolean; populateCarrier?: boolean } = {} + ): Observable { + if (options.populateCarrier || options.populateWarehouse) { + return super.get(id).pipe( + map(async (order) => { + await this._throwIfNotExists(id); + return order; + }), + switchMap((order) => order), + exhaustMap(() => this._get(id, options)) + ); + } else { + return super.get(id).pipe( + map(async (order) => { + await this._throwIfNotExists(id); + return order; + }), + switchMap((order) => order) + ); + } + } + + @asyncListener() + async updateCarrierStatus( + orderId: Order['id'], + status: OrderCarrierStatus + ): Promise { + // TODO: check here that not from any status it is possible to move to any other. + // Mean if order is status 3, it's not simply possible to update it to status 2. + await this._throwIfNotExists(orderId); + + try { + const updateObj: any = { carrierStatus: status }; + + if (status === OrderCarrierStatus.DeliveryCompleted) { + updateObj.isPaid = true; + updateObj.deliveryTime = Date.now(); + } + + if ( + status === OrderCarrierStatus.CarrierStartDelivery || + status === OrderCarrierStatus.CarrierPickedUpOrder + ) { + updateObj.startDeliveryTime = Date.now(); + } + + const finishedProcessingStatuses = [ + OrderCarrierStatus.ClientRefuseTakingOrder, + OrderCarrierStatus.DeliveryCompleted, + OrderCarrierStatus.IssuesDuringDelivery, + ]; + if (finishedProcessingStatuses.includes(status)) { + updateObj.finishedProcessingTime = Date.now(); + } + + const order = await this.update(orderId, updateObj); + + if (order.carrierId != null) { + if (status === OrderCarrierStatus.DeliveryCompleted) { + await this.carriersService.increaseNumberOfDeliveries( + order.carrierId, + 1 + ); + } + + return order; + } else { + throw new Error( + "Can't updateCarrierStatus(orderId, status) - Order has no carrier!" + ); + } + } catch (err) { + this.log.error(err); + throw err; + } + } + + @asyncListener() + async updateWarehouseStatus( + orderId: Order['id'], + status: OrderWarehouseStatus + ): Promise { + await this._throwIfNotExists(orderId); + const updateObj: any = { warehouseStatus: status }; + const finishedProcessingStatuses = [ + OrderWarehouseStatus.PackagingFailed, + OrderWarehouseStatus.AllocationFailed, + ]; + if (finishedProcessingStatuses.includes(status)) { + updateObj.finishedProcessingTime = Date.now(); + } + if (status === OrderWarehouseStatus.GivenToCustomer) { + updateObj.isPaid = true; + updateObj.finishedProcessingTime = Date.now(); + } + return this.update(orderId, updateObj); + } + + /** + * Pay with Stripe for given order with given CC + * TODO: move to separate Payments Service + * + * @param {Order['id']} orderId + * @param {string} cardId CC Id which will be used to pay + * @returns {Promise} + * @memberof OrdersService + */ + @asyncListener() + async payWithStripe(orderId: Order['id'], cardId: string): Promise { + await this._throwIfNotExists(orderId); + + const callId = uuid(); + + this.log.info( + { callId, orderId, cardId }, + '.payWithStripe(orderId, cardId) called' + ); + + let order: Order; + + try { + const _order = await this.get(orderId).pipe(first()).toPromise(); + + if (_order != null) { + order = _order; + + const user = await this.usersService + .get(order.user.id) + .pipe(first()) + .toPromise(); + + if (user != null) { + const charge: Stripe.Charge = await this.stripe.charges.create( + { + amount: order.totalPrice * 100, // amount in cents, again + customer: user.stripeCustomerId, + source: cardId, + currency: 'ils', + description: 'Order id: ' + orderId, + metadata: { + orderId, + }, + } + ); + + order = await this.update(orderId, { + stripeChargeId: charge.id, + isPaid: true, + }); + } else { + throw new Error('User specified in order is not found!'); + } + } else { + throw new Error("couldn't find order with such id"); + } + } catch (err) { + this.log.error( + { callId, orderId, cardId, err }, + '.payWithStripe(orderId, cardId) thrown error!' + ); + throw err; + } + + this.log.info( + { callId, orderId, cardId, order }, + '.payWithStripe(orderId, cardId) accepted payment' + ); + + return order; + } + + /** + * Refund money for cancelled order via Stripe + * TODO: move to Payments Service + * + * @param {Order['id']} orderId + * @returns {Promise} + * @memberof OrdersService + */ + @asyncListener() + async refundWithStripe(orderId: Order['id']): Promise { + await this._throwIfNotExists(orderId); + + const callId = uuid(); + + this.log.info({ callId, orderId }, '.refundWithStripe(orderId) called'); + + let refund: Stripe.Refund; + let order: Order | null; + + try { + order = await this.get(orderId).pipe(first()).toPromise(); + + if (order != null) { + if (order.stripeChargeId != null) { + refund = await this.stripe.refunds.create({ + charge: order.stripeChargeId, + }); + + this.log.info( + { callId, orderId, refund }, + '.refundWithStripe(orderId) made refund' + ); + return order; + } else { + throw new Error( + `There is no order with stripeChargeId field and id of ${orderId} to refundWithStripe on!` + ); + } + } else { + throw new Error( + `There is no order with id of ${orderId} to refundWithStripe on!` + ); + } + } catch (err) { + this.log.error( + { callId, orderId, err }, + '.refundWithStripe(orderId) thrown error!' + ); + throw err; + } + } + + @asyncListener() + async confirm(orderId: Order['id']): Promise { + await this._throwIfNotExists(orderId); + + return this.update(orderId, { + isConfirmed: true, + warehouseStatus: OrderWarehouseStatus.ReadyForProcessing, + }); + } + + @asyncListener() + async addProducts( + orderId: Order['id'], + products, + warehouseId: Warehouse['id'] + ): Promise { + await this._throwIfNotExists(orderId); + + const order = await this._get(orderId); + const oldProductsIds = order.products.map( + (p: OrderProduct) => p.product.id + ); + const newProductsIds = products.map((p) => p.productId); + + for (const product of order.products) { + if (newProductsIds.includes(product.product.id)) { + const newProduct = products.find( + (p: { productId: string }) => + p.productId === product.product.id + ); + + if (newProduct) { + product.count += newProduct.count; + + await this.warehousesProductsService.decreaseCount( + warehouseId, + newProduct.productId, // what product availability should be decreased + newProduct.count // how many to remove + ); + + await this.warehousesProductsService.increaseSoldCount( + warehouseId, + newProduct.productId, + newProduct.count + ); + } + } + } + + products = products.filter( + (p: { [x: string]: string }) => + !oldProductsIds.includes(p['productId']) + ); + + const warehouse = (await this.warehousesService + .get(warehouseId, true) + .pipe(first()) + .toPromise()) as WithFullProducts; + + const warehouseProducts = _.keyBy(warehouse.products, 'productId'); + + const newOrderProducts = await _.map( + products, + (args): IOrderProductCreateObject => { + const wProduct = warehouseProducts[args.productId]; + + if (!wProduct) { + throw new Error( + `WarehouseOrdersService got call to create(userId, orderProducts) - But there is no product with the id ${args.productId}!` + ); + } + + return { + count: args.count, + price: wProduct.price, + initialPrice: wProduct.initialPrice, + deliveryTimeMin: wProduct.deliveryTimeMin, + deliveryTimeMax: wProduct.deliveryTimeMax, + product: wProduct.product as Product, + + isManufacturing: wProduct.isManufacturing, + isCarrierRequired: wProduct.isCarrierRequired, + isDeliveryRequired: wProduct.isDeliveryRequired, + isTakeaway: wProduct.isTakeaway, + }; + } + ); + + for (const product of products) { + await this.warehousesProductsService.decreaseCount( + warehouseId, + product.productId, + product.count + ); + + await this.warehousesProductsService.increaseSoldCount( + warehouseId, + product.productId, + product.count + ); + } + + return this.update(orderId, { + products: [...order.products, ...newOrderProducts], + }); + } + + @asyncListener() + async decreaseOrderProducts( + orderId: Order['id'], + products: any, // TODO: specify correct Type + warehouseId: Warehouse['id'] + ): Promise { + await this._throwIfNotExists(orderId); + + const order = await this._get(orderId); + + const oldProductsIds = order.products.map( + (p: OrderProduct) => p.product.id + ); + + const newProductsIds = products.map((p) => p.productId); + + for (const product of order.products) { + if (newProductsIds.includes(product.product.id)) { + const newProduct = products.find( + (p: { productId: string }) => + p.productId === product.product.id + ); + + if (newProduct) { + product.count -= newProduct.count; + if (product.count >= 1) { + await this.warehousesProductsService.decreaseSoldCount( + warehouseId, + // what product availability should be decreased + newProduct.productId, + // how many to remove + newProduct.count + ); + + await this.warehousesProductsService.increaseCount( + warehouseId, + newProduct.productId, + newProduct.count + ); + } else { + throw new Error( + `You can not decrease product to be === 0 !` + ); + } + } + } + } + + products = products.filter( + (p: { [x: string]: string }) => + !oldProductsIds.includes(p['productId']) + ); + + const warehouse = (await this.warehousesService + .get(warehouseId, true) + .pipe(first()) + .toPromise()) as WithFullProducts; + + const warehouseProducts = _.keyBy(warehouse.products, 'productId'); + + const newOrderProducts = await _.map( + products, + (args): IOrderProductCreateObject => { + const wProduct = warehouseProducts[args.productId]; + + if (!wProduct) { + throw new Error( + `WarehouseOrdersService got call to create(userId, orderProducts) - But there is no product with the id ${args.productId}!` + ); + } + + return { + count: args.count, + price: wProduct.price, + initialPrice: wProduct.initialPrice, + deliveryTimeMin: wProduct.deliveryTimeMin, + deliveryTimeMax: wProduct.deliveryTimeMax, + product: wProduct.product as Product, + isManufacturing: wProduct.isManufacturing, + isCarrierRequired: wProduct.isCarrierRequired, + isDeliveryRequired: wProduct.isDeliveryRequired, + isTakeaway: wProduct.isTakeaway, + }; + } + ); + + for (const product of products) { + await this.warehousesProductsService.decreaseCount( + warehouseId, + product.productId, + product.count + ); + + await this.warehousesProductsService.increaseSoldCount( + warehouseId, + product.productId, + product.count + ); + } + + return this.update(orderId, { + products: [...order.products, ...newOrderProducts], + }); + } + + @asyncListener() + async removeProducts( + orderId: Order['id'], + productsIds: string[] + ): Promise { + await this._throwIfNotExists(orderId); + + const order = await this._get(orderId); + + const newProducts = order.products.filter( + (p: OrderProduct) => !productsIds.includes(p.product.id) + ); + + const removedProducts = order.products.filter((p: OrderProduct) => + productsIds.includes(p.product.id) + ); + + // revert order sold count + await (Bluebird).map(removedProducts, async (orderProduct) => { + const productId = orderProduct.product.id; + + await this.warehousesProductsService.decreaseSoldCount( + order.warehouseId, + productId, + orderProduct.count + ); + + await this.warehousesProductsService.increaseCount( + order.warehouseId, + productId, + orderProduct.count + ); + }); + + return this.update(orderId, { + products: newProducts, + }); + } + + @asyncListener() + async addProductComment( + orderId: Order['id'], + productId: string, + comment: string + ): Promise { + await this._throwIfNotExists(orderId); + + const order = await this._get(orderId); + const orderProduct = order.products.find( + (p: OrderProduct) => p.id === productId + ); + + orderProduct.comment = comment; + + await this.update(orderId, { + products: order.products, + }); + + return this.get(orderId, { + populateWarehouse: true, + populateCarrier: true, + }) + .pipe(first()) + .toPromise(); + } + + @asyncListener() + async cancel(orderId: Order['id']): Promise { + await this._throwIfNotExists(orderId); + + return this.update(orderId, { + isCancelled: true, + finishedProcessingTime: Date.now(), + }); + } + + /** + * Set order with given Id as Paid + * + * @param {Order['id']} orderId + * @returns {Promise} + * @memberof OrdersService + */ + @asyncListener() + async paid(orderId: Order['id']): Promise { + await this._throwIfNotExists(orderId); + + return this.update(orderId, { isPaid: true }); + } + + @asyncListener() + async getStoreOrdersChartTotalOrders(storeId: string): Promise { + const ordersRaw = await this.Model.find({ + warehouse: storeId, + isDeleted: { $eq: false }, + }) + .select({ + isCancelled: 1, + isPaid: 1, + carrier: 1, + carrierStatus: 1, + warehouseStatus: 1, + _createdAt: 1, + 'products.price': 1, + 'products.count': 1, + }) + .lean() + .exec(); + + const orders = _.map(ordersRaw, (o) => { + return { + totalPrice: this._getOrderTotalPrice(o), + isCompleted: this._isOrderCompleted(o), + isCancelled: o.isCancelled, + _createdAt: o._createdAt, + }; + }); + + return orders.filter((o) => o.isCompleted); + } + + @asyncListener() + async getOrdersChartTotalOrders(): Promise { + const ordersRaw = await this.Model.find({ + isDeleted: { $eq: false }, + }) + .select({ + isCancelled: 1, + isPaid: 1, + carrier: 1, + carrierStatus: 1, + warehouseStatus: 1, + _createdAt: 1, + 'products.price': 1, + 'products.count': 1, + }) + .lean() + .exec(); + + const orders = _.map(ordersRaw, (o) => { + return { + totalPrice: this._getOrderTotalPrice(o), + isCompleted: this._isOrderCompleted(o), + isCancelled: o.isCancelled, + _createdAt: o._createdAt, + }; + }); + + const ordersRes = orders.filter((o) => o.isCompleted); + + return ordersRes; + } + + @asyncListener() + async getDashboardCompletedOrdersToday(): Promise { + const start = new Date(); + const end = new Date(); + + start.setHours(0, 0, 0, 0); + end.setHours(23, 59, 59, 999); + + const ordersRaw = await this.Model.find({ + isDeleted: { $eq: false }, + isCancelled: { $eq: false }, + _createdAt: { $gte: start, $lt: end }, + }) + .select({ + isCancelled: 1, + isPaid: 1, + carrier: 1, + carrierStatus: 1, + warehouseStatus: 1, + _createdAt: 1, + warehouse: 1, + user: 1, + 'products.price': 1, + 'products.count': 1, + }) + .lean() + .exec(); + + const orders = _.map(ordersRaw, (o) => { + return { + totalPrice: this._getOrderTotalPrice(o), + isCompleted: this._isOrderCompleted(o), + isCancelled: o.isCancelled, + user: o.user, + warehouseId: o.warehouse, + _createdAt: o._createdAt, + }; + }); + + return orders.filter((o) => o.isCompleted); + } + + @asyncListener() + async getOrderedUsersInfo(storeId: string): Promise { + const orders = await this.Model.find({ + isDeleted: { $eq: false }, + warehouse: { $eq: storeId }, + }) + .select({ + user: 1, + isPaid: 1, + 'products.price': 1, + 'products.count': 1, + }) + .lean() + .exec(); + + const unique = (value, index, self) => { + return ( + self + .map((s) => s.user._id.toString()) + .indexOf(value.user._id.toString()) === index + ); + }; + + const oUsers = orders.filter(unique).map((o) => { + const user: User = new User(o.user); + return user; + }); + + const oUserIds = oUsers.map((u) => u.id); + const realUsers = await this.usersService.find({ + isDeleted: false, + _id: { $in: oUserIds }, + }); + + return oUsers.map((u) => { + const userOrders = orders.filter( + (o) => o.user._id.toString() === u._id.toString() + ); + + let totalPrice = 0; + + const paidOrders = userOrders.filter((o: Order) => o.isPaid); + + if (paidOrders.length > 0) { + totalPrice = paidOrders + .map((o: Order) => this._getOrderTotalPrice(o)) + .reduce((a, b) => a + b); + } + + return { + user: realUsers.find((ru) => ru.id === u.id), + ordersCount: userOrders.length, + totalPrice, + }; + }); + } + + @asyncListener() + async getDashboardCompletedOrders(storeId?: string): Promise { + const quaryObj = { + isDeleted: { $eq: false }, + isCancelled: { $eq: false }, + }; + + if (storeId) { + quaryObj['warehouse'] = { $eq: storeId }; + } + + const ordersRaw = await this.Model.find(quaryObj) + .select({ + isCancelled: 1, + isPaid: 1, + carrier: 1, + // user: 1, + carrierStatus: 1, + warehouseStatus: 1, + // _createdAt: 1, + warehouse: 1, + 'products.price': 1, + 'products.count': 1, + }) + .lean() + .exec(); + + const orders = _.map(ordersRaw, (o) => { + return { + totalPrice: this._getOrderTotalPrice(o), + warehouseId: o.warehouse, + isCompleted: this._isOrderCompleted(o), + // isCancelled: o.isCancelled, + // user: o.user, + // _createdAt: o._createdAt + }; + }); + + return orders.filter((o) => o.isCompleted); + } + + @asyncListener() + async getOrdersInDelivery(storeId: string): Promise { + const order = await this.Model.find({ + isDeleted: false, + isCancelled: false, + warehouse: storeId, + carrierStatus: { + $in: [ + OrderCarrierStatus.CarrierPickedUpOrder, + OrderCarrierStatus.CarrierStartDelivery, + OrderCarrierStatus.CarrierArrivedToCustomer, + ], + }, + }) + .populate('carrier user') + .lean() + .exec(); + + return order; + } + + private _getOrderTotalPrice(order: Order): number { + return _.sum(_.map(order.products, (p) => p.count * p.price)); + } + + private _isOrderCompleted(order: Order): boolean { + function getStatus(o: Order): OrderStatus { + if ( + o.carrier == null || + o.carrierStatus <= OrderCarrierStatus.CarrierPickedUpOrder + ) { + if (o.warehouseStatus >= 200) { + return OrderStatus.WarehouseIssue; + } else if (o.isCancelled) { + return OrderStatus.CanceledWhileWarehousePreparation; + } else { + return OrderStatus.WarehousePreparation; + } + } else { + if (o.carrierStatus >= 200) { + return OrderStatus.CarrierIssue; + } else if (o.isCancelled) { + return OrderStatus.CanceledWhileInDelivery; + } else if ( + o.isPaid && + o.carrierStatus === OrderCarrierStatus.DeliveryCompleted + ) { + return OrderStatus.Delivered; + } else { + return OrderStatus.InDelivery; + } + } + } + return ( + (order.isPaid && getStatus(order) === OrderStatus.Delivered) || + order.isCancelled + ); + } + + private async _get( + id: string, + options: { populateWarehouse?: boolean; populateCarrier?: boolean } = {} + ): Promise { + const query = this.Model.findById(id); + if (options.populateCarrier) { + query.populate('carrier'); + } + if (options.populateWarehouse) { + query.populate('warehouse'); + } + return new Order( + ( + await query + .sort({ _createdAt: -1, orderNumber: -1 }) + .lean() + .exec() + ) as IOrder + ); + } + + private async _throwIfNotExists(orderId: string): Promise { + const order = await super.get(orderId).pipe(first()).toPromise(); + + if (!order || order.isDeleted) { + throw Error(`Order with id '${orderId}' does not exists!`); + } + } +} diff --git a/packages/core/src/services/orders/index.ts b/packages/core/src/services/orders/index.ts new file mode 100644 index 0000000..0a777ac --- /dev/null +++ b/packages/core/src/services/orders/index.ts @@ -0,0 +1 @@ +export * from './OrdersService'; diff --git a/packages/core/src/services/products/ProductsCategoriesService.ts b/packages/core/src/services/products/ProductsCategoriesService.ts new file mode 100644 index 0000000..e942a75 --- /dev/null +++ b/packages/core/src/services/products/ProductsCategoriesService.ts @@ -0,0 +1,93 @@ +import Logger from 'bunyan'; +import { injectable } from 'inversify'; +import { createEverLogger } from '../../helpers/Log'; +import { DBService } from '@pyro/db-server'; +import IProductsCategoryRouter from '@modules/server.common/routers/IProductsCategoryRouter'; +import { Observable } from 'rxjs'; +import { asyncListener, observableListener, routerName } from '@pyro/io'; +import IService from '../IService'; +import ProductsCategory from '@modules/server.common/entities/ProductsCategory'; +import { UpdateObject } from '@pyro/db/db-update-object'; +import { CreateObject } from '@pyro/db/db-create-object'; +import { first, switchMap, map } from 'rxjs/operators'; + +@injectable() +@routerName('products-category') +export class ProductsCategoriesService extends DBService + implements IProductsCategoryRouter, IService { + public readonly DBObject: any = ProductsCategory; + + protected readonly log: Logger = createEverLogger({ + name: 'productsCategoriesService', + }); + + /** + * Get Product Category by Id + * + * @param {ProductsCategory['id']} id + * @returns {(Observable)} + * @memberof ProductsCategoriesService + */ + @observableListener() + get(id: ProductsCategory['id']): Observable { + return super.get(id).pipe( + map(async (category) => { + await this.throwIfNotExists(id); + return category; + }), + switchMap((category) => category) + ); + } + + /** + * Create new Product Category + * + * @param {CreateObject} category + * @returns {Promise} + * @memberof ProductsCategoriesService + */ + @asyncListener() + async create( + category: CreateObject + ): Promise { + return super.create(category); + } + + /** + * Updates existed Product Category + * + * @param {string} id + * @param {UpdateObject} updateObject + * @returns {Promise} + * @memberof ProductsCategoriesService + */ + @asyncListener() + async update( + id: string, + updateObject: UpdateObject + ): Promise { + await this.throwIfNotExists(id); + return super.update(id, updateObject); + } + + /** + * Removes Product Category + * + * @param {string} id + * @returns {Promise} + * @memberof ProductsCategoriesService + */ + @asyncListener() + async remove(id: string): Promise { + await this.throwIfNotExists(id); + return super.remove(id); + } + + async throwIfNotExists(categoryId: string): Promise { + const category = await super.get(categoryId).pipe(first()).toPromise(); + + if (!category || category.isDeleted) { + throw Error(`Category with id '${categoryId}' does not exists!`); + } + } +} diff --git a/packages/core/src/services/products/ProductsService.ts b/packages/core/src/services/products/ProductsService.ts new file mode 100644 index 0000000..bb0a31a --- /dev/null +++ b/packages/core/src/services/products/ProductsService.ts @@ -0,0 +1,96 @@ +import Logger from 'bunyan'; +import { injectable } from 'inversify'; +import { createEverLogger } from '../../helpers/Log'; +import Product from '@modules/server.common/entities/Product'; +import { DBService } from '@pyro/db-server'; +import { default as IProduct } from '@modules/server.common/interfaces/IProduct'; +import IProductRouter from '@modules/server.common/routers/IProductRouter'; +import { Observable } from 'rxjs'; +import { + asyncListener, + observableListener, + routerName, + serialization, +} from '@pyro/io'; +import IService from '../IService'; +import { CreateObject } from '@pyro/db/db-create-object'; +import { UpdateObject } from '@pyro/db/db-update-object'; +import { first, switchMap, map } from 'rxjs/operators'; +import IPagingOptions from '@modules/server.common/interfaces/IPagingOptions'; + +@injectable() +@routerName('product') +export class ProductsService extends DBService + implements IProductRouter, IService { + public readonly DBObject: any = Product; + + protected readonly log: Logger = createEverLogger({ + name: 'productsService', + }); + + @observableListener() + get(id: Product['id']): Observable { + return super.get(id).pipe( + map(async (product) => { + await this.throwIfNotExists(id); + return product; + }), + switchMap((product) => product) + ); + } + + @asyncListener() + async getProducts( + findInput: any, + pagingOptions: IPagingOptions, + existedProductsIds = [] + ): Promise { + const sortObj = {}; + + if (pagingOptions.sort) { + sortObj[pagingOptions.sort.field] = pagingOptions.sort.sortBy; + } + + return this.Model.find({ + ...findInput, + isDeleted: { $eq: false }, + _id: { $nin: existedProductsIds }, + }) + .sort(sortObj) + .skip(pagingOptions.skip) + .limit(pagingOptions.limit) + .lean() + .exec(); + } + + @asyncListener() + async create(product: CreateObject): Promise { + return super.create(product); + } + + @asyncListener() + async update( + id: string, + updateObject: UpdateObject + ): Promise { + await this.throwIfNotExists(id); + return super.update(id, updateObject); + } + + @asyncListener() + async save( + @serialization((product: IProduct) => new Product(product)) + updatedProduct: Product + ): Promise { + await this.throwIfNotExists(updatedProduct.id); + return this.update(updatedProduct.id, updatedProduct); + } + + async throwIfNotExists(productId: string): Promise { + const product = await super.get(productId).pipe(first()).toPromise(); + + if (!product || product.isDeleted) { + throw Error(`Product with id '${productId}' does not exists!`); + } + } +} diff --git a/packages/core/src/services/products/PromotionService.ts b/packages/core/src/services/products/PromotionService.ts new file mode 100644 index 0000000..abc8ab3 --- /dev/null +++ b/packages/core/src/services/products/PromotionService.ts @@ -0,0 +1,76 @@ +import { injectable } from 'inversify'; +import { routerName } from '@pyro/io'; +import { DBService } from '@pyro/db-server'; +import IService from 'services/IService'; +import Promotion from '@modules/server.common/entities/Promotion'; +import { createEverLogger } from '../../helpers/Log'; +import Logger from 'bunyan'; +import { IPromotionCreateObject } from '@modules/server.common/interfaces/IPromotion'; +import { first } from 'rxjs/operators'; +import _ = require('lodash'); + +@injectable() +@routerName('promotion') +export class PromotionService extends DBService implements IService { + public readonly DBObject: any = Promotion; + + protected readonly log: Logger = createEverLogger({ + name: 'productsCategoriesService', + }); + + async createPromotion(promotion: IPromotionCreateObject): Promise { + let data: any; + + try { + data = await this.create(promotion); + + return { + success: true, + message: `Successfully created promotion ${data.title || ''}`, + data, + }; + } catch (error) { + return { + success: false, + message: error.message, + }; + } + } + + async getAllPromotions(findInput: { warehouse: string }): Promise { + const warehousePromotions = await this.Model.find({ + warehouse: { $eq: findInput.warehouse }, + isDeleted: { $eq: false }, + }) + .select({ + title: 1, + description: 1, + active: 1, + promoPrice: 1, + activeFrom: 1, + activeTo: 1, + image: 1, + product: 1, + warehouse: 1, + purchasesCount: 1, + }) + .lean() + .exec(); + + return _.map(warehousePromotions, (p) => { + return { + ...p, + warehouseId: p.warehouse, + productId: p.product, + }; + }); + } + + async throwIfNotExists(promotionId: string) { + const promotion = await this.get(promotionId).pipe(first()).toPromise(); + + if (!promotion || promotion.isDeleted) { + throw Error(`Promotion with id '${promotionId}' does not exist!`); + } + } +} diff --git a/packages/core/src/services/products/index.ts b/packages/core/src/services/products/index.ts new file mode 100644 index 0000000..0db358d --- /dev/null +++ b/packages/core/src/services/products/index.ts @@ -0,0 +1,2 @@ +export * from './ProductsCategoriesService'; +export * from './ProductsService'; diff --git a/packages/core/src/services/services.app.ts b/packages/core/src/services/services.app.ts new file mode 100644 index 0000000..d71ef6f --- /dev/null +++ b/packages/core/src/services/services.app.ts @@ -0,0 +1,728 @@ +import fs from 'fs'; +import { inject, injectable, multiInject } from 'inversify'; +import https from 'https'; +import http from 'http'; +import path from 'path'; +import pem from 'pem'; +import bodyParser from 'body-parser'; +import cors from 'cors'; +import passport from 'passport'; +import methodOverride from 'method-override'; +import errorhandler from 'errorhandler'; +import socketIO from 'socket.io'; +import express from 'express'; +import mongoose from 'mongoose'; +import morgan from 'morgan'; +import exphbs from 'express-handlebars'; +import { createEverLogger } from '../helpers/Log'; +import IService, { ServiceSymbol } from './IService'; +import { IRoutersManager } from '@pyro/io'; +import { WarehousesService } from './warehouses'; +import { SocialStrategiesService } from './users'; +import { env } from '../env'; +import { getModel } from '@pyro/db-server'; +import Bluebird from 'bluebird'; +import { AdminsService } from './admins'; +import ipstack = require('ipstack'); +import requestIp = require('request-ip'); +import { ConnectionOptions, createConnection } from 'typeorm'; +import { IWarehouseCreateObject } from '@modules/server.common/interfaces/IWarehouse'; +import { getDummyImage } from '@modules/server.common/utils'; +import Admin from '@modules/server.common/entities/Admin'; +import Device from '@modules/server.common/entities/Device'; +import Carrier from '@modules/server.common/entities/Carrier'; +import Invite from '@modules/server.common/entities/Invite'; +import InviteRequest from '@modules/server.common/entities/InviteRequest'; +import Order from '@modules/server.common/entities/Order'; +import Product from '@modules/server.common/entities/Product'; +import ProductsCategory from '@modules/server.common/entities/ProductsCategory'; +import User from '@modules/server.common/entities/User'; +import Warehouse from '@modules/server.common/entities/Warehouse'; +import Promotion from '@modules/server.common/entities/Promotion'; +import { ConfigService } from '../config/config.service'; + +const conf = require('dotenv').config(); + +// local IPs +const INTERNAL_IPS = ['127.0.0.1', '::1']; + +@injectable() +export class ServicesApp { + protected db_server = process.env.DB_ENV || 'primary'; + + protected db: mongoose.Connection; + + protected expressApp: express.Express; + protected httpsServer: https.Server; + protected httpServer: http.Server; + + private log = createEverLogger({ name: 'main' }); + + // TODO: put to config (we also may want to increase it) + private static _poolSize: number = 50; + + // TODO: put to config + private static _connectTimeoutMS: number = 40000; + + private callback: () => void; + + constructor( + @multiInject(ServiceSymbol) + protected services: IService[], + @inject('RoutersManager') + protected routersManager: IRoutersManager, + @inject(WarehousesService) + protected warehousesService: WarehousesService, + @inject(SocialStrategiesService) + protected socialStrategiesService: SocialStrategiesService, + @inject(AdminsService) + private readonly _adminsService: AdminsService, + @inject(ConfigService) + _configService: ConfigService + ) { + const maxSockets = _configService.Env.MAX_SOCKETS; + + // see https://webapplog.com/seven-things-you-should-stop-doing-with-node-js + http.globalAgent.maxSockets = maxSockets; + https.globalAgent.maxSockets = maxSockets; + + // If the Node process ends, close the Mongoose connection + process + .on('SIGINT', this._gracefulExit) + .on('SIGTERM', this._gracefulExit); + } + + async start(callback: () => void) { + this.callback = callback; + await this._connectDB(); + } + + static getEntities() { + const entities = [ + Admin, + Carrier, + Device, + Invite, + InviteRequest, + Order, + Product, + ProductsCategory, + User, + Warehouse, + Promotion, + ]; + return entities; + } + + static async CreateTypeORMConnection() { + + console.log('Creating TypeORM Connection...'); + + const typeORMLog = createEverLogger({ name: 'TypeORM' }); + + // list of entities for which Repositories will be created in TypeORM + const entities = ServicesApp.getEntities(); + + const isSSL = process.env.DB_SSL_MODE && process.env.DB_SSL_MODE !== 'false'; + + // let's temporary save Cert in ./tmp/logs folder because we have write access to it + let sslCertPath = `${env.LOGS_PATH}/ca-certificate.crt`; + + console.log(`Using temp SSL Cert Path: ${sslCertPath}`); + + if (isSSL) { + const base64data = process.env.DB_CA_CERT; + const buff = Buffer.from(base64data, 'base64'); + const sslCert = buff.toString('ascii'); + fs.writeFileSync(sslCertPath, sslCert); + } + + // This is special connection we create during "bootstrap" of the system that is needed for our Repositories to use + // We also using another "default" connection defined in app.module.ts + // using `TypeOrmModule.forRoot(connectionSettings)` which will be used in other places. + const connectionSettings: ConnectionOptions = + { + // Note: do not change this connection name + name: 'typeorm', + // TODO: put this into settings (it's mongo only during testing of TypeORM integration!) + type: 'mongodb', + url: env.DB_URI, + ssl: isSSL, + sslCA: isSSL ? [sslCertPath] : undefined, + host: process.env.DB_HOST || 'localhost', + username: process.env.DB_USER, + password: process.env.DB_PASS, + database: process.env.DB_NAME || 'ever_development', + port: process.env.DB_PORT ? parseInt(process.env.DB_PORT, 10) : 27017, + entities, + synchronize: true, + useNewUrlParser: true, + // autoReconnect: true, + // reconnectTries: Number.MAX_VALUE, + // poolSize: ServicesApp._poolSize, + connectTimeoutMS: ServicesApp._connectTimeoutMS, + logging: true, + logger: 'file', // Removes console logging, instead logs all queries in a file ormlogs.log + useUnifiedTopology: true, + }; + + const conn = await createConnection(connectionSettings); + + console.log( + `TypeORM DB connection created. DB connected: ${conn.isConnected}` + ); + + typeORMLog.info( + `TypeORM DB connection created. DB connected: ${conn.isConnected}` + ); + + return conn; + } + + private _gracefulExit() { + try { + if (this.db != null) { + this.db.close(() => { + this.log.info( + 'Mongoose default connection with DB :' + + this.db_server + + ' is disconnected through app termination' + ); + process.exit(0); + }); + } + } catch (err) { + process.exit(0); + } + } + + private async _connectDB() { + try { + + const isSSL = process.env.DB_SSL_MODE && process.env.DB_SSL_MODE !== 'false'; + + // let's temporary save Cert in ./tmp/logs folder because we have write access to it + let sslCertPath = `${env.LOGS_PATH}/ca-certificate.crt`; + + console.log(`Using temp SSL Cert Path: ${sslCertPath}`); + + if (isSSL) { + const base64data = process.env.DB_CA_CERT; + const buff = Buffer.from(base64data, 'base64'); + const sslCert = buff.toString('ascii'); + fs.writeFileSync(sslCertPath, sslCert); + } + + const connectionOptions: mongoose.ConnectOptions = { + ssl: isSSL, + sslCA: isSSL ? sslCertPath : undefined, + user: process.env.DB_USER, + pass: process.env.DB_PASS, + dbName: process.env.DB_NAME || 'ever_development', + connectTimeoutMS: ServicesApp._connectTimeoutMS, + appName: 'ever_demand' + }; + + const mongoConnect: mongoose.Mongoose = await mongoose.connect( + env.DB_URI, + connectionOptions + ); + + this.db = mongoConnect.connection; + + this._configDBEvents(); + + this._onDBConnect(); + } catch (err) { + this.log.error( + err, + 'Sever initialization failed! Cannot connect to DB' + ); + } + } + + private _configDBEvents() { + this.db.on('error', (err) => this.log.error(err)); + + this.db.on('disconnected', () => { + this.log.warn( + 'Mongoose default connection to DB :' + + this.db_server + + ' disconnected' + ); + }); + + this.db.on('connected', () => { + this.log.info( + 'Mongoose default connection to DB :' + + this.db_server + + ' connected' + ); + }); + } + + private async _onDBConnect() { + // that's important to see even if logs disabled, do not remove! + console.log('Connected to DB'); + + this.log.info({ db: this.db_server }, 'Connected to DB'); + + await this._registerModels(); + await this._registerEntityAdministrator(); + this._passportSetup(); + await this._startExpress(); + await this._startSocketIO(); + + // execute callback defined at main.ts + await this.callback(); + + // let's report RAM usage after all is bootstrapped + await this.reportMemoryUsage(); + } + + private async reportMemoryUsage() { + console.log('Memory usage: '); + console.log(process.memoryUsage()); + } + + /** + * Create initial (default) Admin user with default credentials: + * Email: admin@ever.co + * Password: admin + * + * @private + * @memberof ServicesApp + */ + private async _registerEntityAdministrator() { + const adminEmail = 'admin@ever.co'; // TODO: put to config + const adminPassword = 'admin'; // TODO: put to config + + const adminCollectionCount = await this._adminsService.count({ + email: adminEmail, + }); + + if (adminCollectionCount === 0) { + this._adminsService.register({ + admin: { + email: adminEmail, + name: 'Admin', + hash: null, + pictureUrl: getDummyImage(300, 300, 'A'), + }, + password: adminPassword, + }); + } + } + + private _getBaseUrl(url: string) { + if (url) { + return url.slice(0, url.lastIndexOf('/') + 1).toString(); + } + } + + private _passportSetup() { + passport.serializeUser((user, done) => { + done(null, user); + }); + + passport.deserializeUser((id, done) => { + // TODO ? + }); + + // Google Strategy + const googleStrategy = this.socialStrategiesService.getGoogleStrategy(); + if (googleStrategy != null) { + passport.use(googleStrategy); + } + + // Facebook Strategy + const facebookStrategy = this.socialStrategiesService.getFacebookStrategy(); + if (facebookStrategy != null) { + passport.use(facebookStrategy); + } + } + + private async _registerModels() { + await (Bluebird).map(this.services, async (service) => { + const obj = (service as any).DBObject; + + if (obj != null) { + console.log('Service DBObject Name: ' + obj.modelName); + + const model = getModel(obj); + + if (model) { + // get the model to register it's schema indexes in db + await model.createIndexes(); + } + } + }); + } + + private async _startExpress() { + this.expressApp = (express)(); + + const hbs = exphbs.create({ + extname: '.hbs', + defaultLayout: 'main', + layoutsDir: path.join('res', 'views', 'layouts'), + partialsDir: path.join('res', 'templates'), + }); + + // configure Handlebars templates + this.expressApp.engine('.hbs', hbs.engine); + + this.expressApp.set('views', path.join('res', 'views')); + + this.expressApp.set('view engine', '.hbs'); + + this.expressApp.set('view cache', false); + + // now we check if Cert files exists and if not generate them for localhost + const httpsCertPath = env.HTTPS_CERT_PATH; + const httpsKeyPath = env.HTTPS_KEY_PATH; + + const hasHttpsCert = fs.existsSync(httpsCertPath); + const hasHttpsKey = fs.existsSync(httpsKeyPath); + + let hasDefaultHttpsCert = false; + + if (!hasHttpsCert || !hasHttpsKey) { + hasDefaultHttpsCert = await this._getCertificates( + httpsCertPath, + httpsKeyPath + ); + } + + if ((hasHttpsCert && hasHttpsKey) || hasDefaultHttpsCert) { + this.httpsServer = https.createServer( + { + cert: fs.readFileSync(httpsCertPath), + key: fs.readFileSync(httpsKeyPath), + }, + this.expressApp + ); + } + + this.httpServer = http.createServer(this.expressApp); + + // TODO: add to settings file + // set connections timeouts to 30 minutes (for long running requests) + const timeout = 30 * 60 * 1000; + + if (this.httpsServer) { + this.httpsServer.setTimeout(timeout); + } + + this.httpServer.setTimeout(timeout); + + this.expressApp.set('host', env.API_HOST); + this.expressApp.set('httpsPort', env.HTTPSPORT); + this.expressApp.set('httpPort', env.HTTPPORT); + this.expressApp.set('environment', env.NODE_ENV); + + // CORS configuration + // TODO: we may want to restrict access some way + // (but needs to be careful because we serve some HTML pages for all clients too, e.g. About Us) + this.expressApp.use( + (cors)({ + origin: true, + credentials: true, + }) + ); + + this.expressApp.use(bodyParser.urlencoded({ extended: false })); + this.expressApp.use(bodyParser.json()); + this.expressApp.use( + bodyParser.json({ type: 'application/vnd.api+json' }) + ); + + const mo: any = methodOverride; + + this.expressApp.use(mo('X-HTTP-Method')); // Microsoft + this.expressApp.use(mo('X-HTTP-Method-Override')); // Google/GData + this.expressApp.use(mo('X-Method-Override')); // IBM + this.expressApp.use(morgan('dev')); + this.expressApp.use(passport.initialize()); + this.expressApp.use(requestIp.mw()); + + if (this.expressApp.get('environment') === 'development') { + const eh: any = errorhandler; + this.expressApp.use(eh()); + } + + this.expressApp.get('/', function (req, res) { + res.render('index'); + }); + + // Get location (lat, long) by IP address + // TODO: put into separate service + this.expressApp.get('/getLocationByIP', (req, res) => { + const ipStackKey = env.IP_STACK_API_KEY; + if (ipStackKey) { + const clientIp = req['clientIp']; + + if (!INTERNAL_IPS.includes(clientIp)) { + ipstack(clientIp, ipStackKey, (err, response) => { + res.json({ + latitude: response.latitude, + longitude: response.longitude, + }); + }); + } else { + this.log.info( + `Can't use ipstack with internal ip address ${clientIp}` + ); + res.status(204).end(); + } + } else { + this.log.error('Not provided Key for IpStack'); + res.status(500).end(); + } + }); + + // TODO: why is this here? It should be in some Nest Controller + this.expressApp.post('/warehouse/create', async (req, res) => { + const warehouseCreateObject: IWarehouseCreateObject = JSON.parse( + req.body.warehouse + ); + + const warehouse = await this.warehousesService.create( + warehouseCreateObject + ); + res.json(warehouse); + }); + + this._setupAuthRoutes(); + this._setupStaticRoutes(); + + const host = this.expressApp.get('host'); + const httpsPort = this.expressApp.get('httpsPort'); + const httpPort = this.expressApp.get('httpPort'); + + const environment = this.expressApp.get('environment'); + + this.log.info( + { + host, + httpsPort, + httpPort, + environment, + 'process.env': process.env, + dotenv: conf, + }, + 'Express server prepare to listen' + ); + + if (httpsPort && httpsPort > 0 && this.httpsServer) { + // app listen on https + this.httpsServer.listen(httpsPort, host, () => { + this.log.info( + { port: httpsPort, host: host }, + 'Express https server listening' + ); + console.log( + `Express https server listening on ${host}:${httpsPort}` + ); + }); + } else { + this.log.warn( + `No SSL Certificate exists, HTTPS endpoint will be disabled` + ); + } + + if (httpPort && httpPort > 0) { + // app listen on http + this.httpServer.listen(httpPort, host, () => { + this.log.info( + { port: httpPort, host: host }, + 'Express http server listening' + ); + console.log( + `Express http server listening on ${host}:${httpPort}` + ); + }); + } + } + + private async _getCertificates( + httpsCertPath: string, + httpsKeyPath: string + ) { + try { + this.log.info('Generating SSL Certificates for HTTPS'); + + const { success } = await this._createCertificateAsync( + httpsCertPath, + httpsKeyPath + ); + + this.log.info('Certificates were generated'); + + return success; + } catch (error) { + this.log.warn( + `Certificates were not generated due to error: ${error.message}` + ); + + return false; + } + } + + private _createCertificateAsync( + httpsCertPath: string, + httpsKeyPath: string + ): Promise<{ success: boolean }> { + return new Promise((resolve, reject) => { + try { + pem.createCertificate( + { + days: 365, + selfSigned: true, + }, + (err, keys) => { + if (err) { + reject({ success: false, message: err.message }); + return; + } + + const httpsCertDirPath = path.dirname(httpsCertPath); + const httpsKeyDirPath = path.dirname(httpsKeyPath); + + if (!fs.existsSync(httpsCertDirPath)) { + fs.mkdirSync(httpsCertDirPath, { + recursive: true, + }); + } + + if (!fs.existsSync(httpsKeyDirPath)) { + fs.mkdirSync(httpsKeyDirPath, { + recursive: true, + }); + } + + fs.writeFileSync(httpsCertPath, keys.certificate); + fs.writeFileSync(httpsKeyPath, keys.serviceKey); + + resolve({ success: true }); + } + ); + } catch (err) { + reject({ success: false, message: err.message }); + } + }); + } + + private async _startSocketIO() { + const so: any = socketIO; + const ioHttps = so(this.httpsServer); + const ioHttp = so(this.httpServer); + + await this.routersManager.startListening(ioHttps); + await this.routersManager.startListening(ioHttp); + } + + private _setupStaticRoutes() { + this.expressApp.get('/en/about', function (req, res) { + res.render('about_us_en'); + }); + + this.expressApp.get('/he/about', function (req, res) { + res.render('about_us_he'); + }); + + this.expressApp.get('/ru/about', function (req, res) { + res.render('about_us_ru'); + }); + + this.expressApp.get('/en/privacy', function (req, res) { + res.render('privacy_en'); + }); + + this.expressApp.get('/he/privacy', function (req, res) { + res.render('privacy_he'); + }); + + this.expressApp.get('/ru/privacy', function (req, res) { + res.render('privacy_ru'); + }); + + this.expressApp.get('/en/terms', function (req, res) { + res.render('terms_of_use_en'); + }); + + this.expressApp.get('/he/terms', function (req, res) { + res.render('terms_of_use_he'); + }); + + this.expressApp.get('/ru/terms', function (req, res) { + res.render('terms_of_use_ru'); + }); + + this.expressApp.get('/bg/terms', function (req, res) { + res.render('terms_of_use_bg'); + }); + } + + private _setupAuthRoutes() { + // Facebook route auth + this.expressApp.get( + '/auth/facebook', + (req, res, next) => { + passport[ + '_strategies' + ].session.base_redirect_url = this._getBaseUrl( + req.header('referer') + ); + next(); + }, + passport.authenticate('facebook', { + scope: ['email', 'public_profile'], + }) + ); + + this.expressApp.get( + '/auth/facebook/callback', + passport.authenticate('facebook', { failureRedirect: '/login' }), + async (req, res) => { + const baseRedirectUr = + passport['_strategies'].session.base_redirect_url; + if (req.user) { + res.redirect(baseRedirectUr + (req.user).redirectUrl); + } else { + res.redirect(baseRedirectUr || ''); + } + passport['_strategies'].session.base_redirect_url = ''; + } + ); + + // Google route auth + this.expressApp.get( + '/auth/google', + (req, res, next) => { + passport[ + '_strategies' + ].session.base_redirect_url = this._getBaseUrl( + req.headers.referer + ); + next(); + }, + passport.authenticate('google', { scope: ['profile', 'email'] }) + ); + + this.expressApp.get( + '/auth/google/callback', + passport.authenticate('google', { failureRedirect: '/login' }), + async (req, res) => { + const baseRedirectUr = + passport['_strategies'].session.base_redirect_url; + if (req.user) { + res.redirect(baseRedirectUr + (req.user).redirectUrl); + } else { + res.redirect(baseRedirectUr || ''); + } + passport['_strategies'].session.base_redirect_url = ''; + } + ); + } +} diff --git a/packages/core/src/services/services.module.ts b/packages/core/src/services/services.module.ts new file mode 100644 index 0000000..c9269d9 --- /dev/null +++ b/packages/core/src/services/services.module.ts @@ -0,0 +1,23 @@ +import { Global, Module } from '@nestjs/common'; +import { ServiceSymbol } from './IService'; +import { servicesContainer } from './inversify.config'; + +function getServices() { + return servicesContainer.getAll(ServiceSymbol).map((service) => { + return { + provide: service.constructor, + useValue: service, + }; + }); +} + +const services = getServices(); + +@Global() +@Module({ + providers: services, + exports: services, +}) +export class ServicesModule { + constructor() {} +} diff --git a/packages/core/src/services/users/SocialRegisterService.ts b/packages/core/src/services/users/SocialRegisterService.ts new file mode 100644 index 0000000..f62188b --- /dev/null +++ b/packages/core/src/services/users/SocialRegisterService.ts @@ -0,0 +1,42 @@ +import { inject, injectable } from 'inversify'; +import { UsersService } from './UsersService'; +import { routerName } from '@pyro/io'; +import IService from 'services/IService'; + +@routerName('social-register-service') +@injectable() +export class SocialRegisterService implements IService { + constructor(@inject(UsersService) protected usersService: UsersService) {} + + async register(profileInfo: object): Promise<{ redirectUrl: string }> { + const socialId = profileInfo['id']; + + const currentUser = await this.usersService.getSocial(socialId); + + let redirectUrl = ''; + + if (currentUser) { + currentUser.isRegistrationCompleted + ? (redirectUrl = 'login/socie/' + currentUser['_id']) + : (redirectUrl = 'login/byLocation/' + currentUser['_id']); + } else { + const [firstname, lastname] = profileInfo['displayName'].split(' '); + + const email = profileInfo['emails'][0]['value']; + + const socialIdOnProfile = profileInfo['id']; + + const newUser = await this.usersService.initUser({ + firstName: firstname, + lastName: lastname, + email, + socialIds: [socialIdOnProfile], + isRegistrationCompleted: false, + }); + + redirectUrl = 'login/byLocation/' + newUser['_id']; + } + + return { redirectUrl }; + } +} diff --git a/packages/core/src/services/users/SocialStrategiesService.ts b/packages/core/src/services/users/SocialStrategiesService.ts new file mode 100644 index 0000000..36e1314 --- /dev/null +++ b/packages/core/src/services/users/SocialStrategiesService.ts @@ -0,0 +1,72 @@ +import GoogleStrategy from 'passport-google-oauth20'; +import FacebookStrategy from 'passport-facebook'; +import { inject, injectable } from 'inversify'; +import { SocialRegisterService } from './SocialRegisterService'; +import { routerName } from '@pyro/io'; +import { env } from '../../env'; +import IService from 'services/IService'; + +@routerName('social-strategies-service') +@injectable() +export class SocialStrategiesService implements IService { + constructor( + @inject(SocialRegisterService) + protected socialRegister: SocialRegisterService + ) {} + + getGoogleStrategy(): GoogleStrategy | null { + if (env.GOOGLE_APP_ID !== '' && env.GOOGLE_APP_SECRET !== '') { + return new GoogleStrategy( + { + clientID: env.GOOGLE_APP_ID, + clientSecret: env.GOOGLE_APP_SECRET, + callbackURL: '/auth/google/callback', + }, + async (accessToken, refreshToken, profile, done) => { + const { redirectUrl } = await this.socialRegister.register( + profile + ); + + done(null, { redirectUrl }); + } + ); + } + + console.log( + `Warning: Google OAuth disabled because no details provided in the settings/environment` + ); + + return null; + } + + getFacebookStrategy(): FacebookStrategy | null { + if (env.FACEBOOK_APP_ID !== '' && env.FACEBOOK_APP_SECRET !== '') { + return new FacebookStrategy( + { + clientID: env.FACEBOOK_APP_ID, + clientSecret: env.FACEBOOK_APP_SECRET, + callbackURL: '/auth/facebook/callback', + profileFields: [ + 'id', + 'displayName', + 'picture', + 'email', + 'gender', + ], + }, + async (accessToken, refreshToken, profile, done) => { + const { redirectUrl } = await this.socialRegister.register( + profile + ); + done(null, { redirectUrl }); + } + ); + } + + console.log( + `Warning: Facebook OAuth disabled because no details provided in the settings/environment` + ); + + return null; + } +} diff --git a/packages/core/src/services/users/UserCommandService.ts b/packages/core/src/services/users/UserCommandService.ts new file mode 100644 index 0000000..d08a221 --- /dev/null +++ b/packages/core/src/services/users/UserCommandService.ts @@ -0,0 +1,82 @@ +import { + CommandBus, + CommandHandler, + ICommand, + ICommandHandler, +} from '@nestjs/cqrs'; +import { routerName } from '@pyro/io'; +import { injectable, optional } from 'inversify'; +import { UsersService } from './UsersService'; +import IService from 'services/IService'; + +/** + * AboutUs Command + * In this experiment, we assume that text of about us could depend on userId and his device :) + * E.g. to user A we could respond "We are Ever!", to user B we could respond "We are Ever Platform!", etc. + * + * @export + * @class GetAboutUsCommand + * @implements {ICommand} + */ +export class GetAboutUsCommand implements ICommand { + constructor( + public readonly userId: string, + public readonly deviceId: string, + public readonly selectedLanguage: string + ) {} +} + +/** + * CQRS experimental integration + * This service basically just listen on 'userCommandService' and execute command 'GetAboutUsCommand' + * + * @export + * @class UserCommandService + */ +@injectable() +@routerName('userCommandService') +export class UserCommandService implements IService { + constructor( + @optional() + private readonly _commandBus: CommandBus + ) {} + + async exec( + userId: string, + deviceId: string, + selectedLanguage: string + ): Promise { + return this._commandBus.execute( + new GetAboutUsCommand(userId, deviceId, selectedLanguage) + ); + } +} + +/** + * Handler for GetAboutUsCommand + * Do nothing for now :) + * + * @export + * @class GetAboutUsHandler + * @implements {ICommandHandler} + */ +@CommandHandler(GetAboutUsCommand) +export class GetAboutUsHandler implements ICommandHandler { + constructor(private readonly _userService: UsersService) {} + + async execute(command: GetAboutUsCommand) { + const { userId, deviceId, selectedLanguage } = command; + + console.log('COMMAND PARAM 1:', userId); + console.log('COMMAND PARAM 2:', deviceId); + console.log('COMMAND PARAM 2:', selectedLanguage); + + const result = this._userService.getAboutUs( + userId, + deviceId, + selectedLanguage + ); + + return result; + } +} diff --git a/packages/core/src/services/users/UsersAuthService.ts b/packages/core/src/services/users/UsersAuthService.ts new file mode 100644 index 0000000..b35322f --- /dev/null +++ b/packages/core/src/services/users/UsersAuthService.ts @@ -0,0 +1,224 @@ +import IService from '../IService'; +import User from '@modules/server.common/entities/User'; +import { createEverLogger } from '../../helpers/Log'; +import { EntityService } from '@pyro/db-server/entity-service'; +import { asyncListener, routerName } from '@pyro/io'; +import { UsersService } from './UsersService'; +import { NotInvitedError } from '@modules/server.common/errors/NotInvitedError'; +import { IUserCreateObject } from '@modules/server.common/interfaces/IUser'; +import { InvitesService } from '../invites'; +import IUserAuthRouter, { + AddableRegistrationInfo, + IUserLoginResponse, + IUserRegistrationInput, +} from '@modules/server.common/routers/IUserAuthRouter'; +import { inject, injectable } from 'inversify'; +import { AuthService, AuthServiceFactory } from '../auth'; +import { env } from '../../env'; +import Logger from 'bunyan'; + +/** + * Customers Authentication Service + * TODO: rename "Users" to "Customers" + * + * @export + * @class UsersAuthService + * @extends {EntityService} + * @implements {IUserAuthRouter} + * @implements {IService} + */ +@injectable() +@routerName('user-auth') +export class UsersAuthService extends EntityService + implements IUserAuthRouter, IService { + readonly DBObject: any = User; + + // TODO: why it's not in the settings and hardcoded to some default value here? + private static IS_INVITES_SYSTEM_ON: boolean = false; + + protected readonly log: Logger = createEverLogger({ + name: 'userAuthService', + }); + + private readonly authService: AuthService; + + constructor( + private readonly usersService: UsersService, + private readonly invitesService: InvitesService, + @inject('Factory') + private readonly authServiceFactory: AuthServiceFactory + ) { + super(); + + this.authService = this.authServiceFactory({ + role: 'user', + Entity: User, + saltRounds: env.USER_PASSWORD_BCRYPT_SALT_ROUNDS, + }); + } + + /** + * Register Customer. + * Throw NotInvitedError if customer not invited and invites system enabled + * + * + * @param {IUserRegistrationInput} input + * @returns {Promise} + * @memberof UsersAuthService + */ + @asyncListener() + async register(input: IUserRegistrationInput): Promise { + if ( + UsersAuthService.IS_INVITES_SYSTEM_ON && + !(await this._isInvited(input.user)) + ) { + throw new NotInvitedError(); + } + + if (input.user.firstName === '') { + delete input.user.firstName; + } + + if (input.user.lastName === '') { + delete input.user.lastName; + } + + if (input.user.email === '') { + delete input.user.email; + } + + const user = await this.usersService.create({ + ...input.user, + ...(input.password + ? { + hash: await this.authService.getPasswordHash( + input.password + ), + } + : {}), + }); + + return user; + } + + /** + * Updates Customer password + * + * @param {User['id']} id + * @param {{ current: string; new: string }} password + * @returns {Promise} + * @memberof UsersAuthService + */ + @asyncListener() + async updatePassword( + id: User['id'], + password: { current: string; new: string } + ): Promise { + await this.usersService.throwIfNotExists(id); + await this.authService.updatePassword(id, password); + } + + /** + * Update exited Customer with given registration details (email, password, etc) + * + * @param {User['id']} id + * @param {AddableRegistrationInfo} { + * email, + * password, + * firstName, + * lastName, + * phone + * } + * @returns {Promise} + * @memberof UsersAuthService + */ + @asyncListener() + async addRegistrationInfo( + id: User['id'], + { email, password, firstName, lastName, phone }: AddableRegistrationInfo + ): Promise { + await this.usersService.throwIfNotExists(id); + + const user = await this.usersService.getCurrent(id); + + if (user.email == null && email) { + throw new Error('To add password user must have email'); + } + + await this.authService.addPassword(id, password); + + await this.usersService.update(id, { + ...(email ? { email } : {}), + ...(firstName ? { firstName } : {}), + ...(lastName ? { lastName } : {}), + ...(phone ? { phone } : {}), + }); + } + + /** + * Login Customer (returns user record and Auth token) + * + * @param {string} email + * @param {string} password + * @returns {(Promise)} + * @memberof UsersAuthService + */ + @asyncListener() + async login( + email: string, + password: string + ): Promise { + const res = await this.authService.login({ email }, password); + + if (!res || res.entity.isDeleted) { + return null; + } + + return { + user: res.entity, + token: res.token, + }; + } + + /** + * Get current Registration settings (e.g. registrationRequiredOnStart) + * TODO: make async + * + * @memberof UsersAuthService + */ + @asyncListener() + getRegistrationsSettings(): Promise<{ + registrationRequiredOnStart: boolean; + }> { + return new Promise<{ registrationRequiredOnStart: boolean }>( + (resolve, reject) => { + resolve({ + registrationRequiredOnStart: + env.SETTINGS_REGISTRATIONS_REQUIRED_ON_START, + }); + } + ); + } + + private async _isInvited( + userCreateObject: IUserCreateObject + ): Promise { + const inviteFindObject = { + 'geoLocation.countryId': userCreateObject.geoLocation.countryId, + 'geoLocation.city': userCreateObject.geoLocation.city, + 'geoLocation.streetAddress': + userCreateObject.geoLocation.streetAddress, + 'geoLocation.house': userCreateObject.geoLocation.house, + apartment: userCreateObject.apartment, + }; + + if (userCreateObject.geoLocation.postcode) { + inviteFindObject['geoLocation.postcode'] = + userCreateObject.geoLocation.postcode; + } + + const invite = await this.invitesService.findOne(inviteFindObject); + + return invite != null; + } +} diff --git a/packages/core/src/services/users/UsersOrdersService.ts b/packages/core/src/services/users/UsersOrdersService.ts new file mode 100644 index 0000000..385792a --- /dev/null +++ b/packages/core/src/services/users/UsersOrdersService.ts @@ -0,0 +1,148 @@ +import { inject, injectable, LazyServiceIdentifer } from 'inversify'; +import * as _ from 'lodash'; +import { OrdersService } from '../orders'; +import Order from '@modules/server.common/entities/Order'; +import { UsersService } from './UsersService'; +import { createEverLogger } from '../../helpers/Log'; +import IUserOrdersRouter from '@modules/server.common/routers/IUserOrdersRouter'; +import { observableListener, routerName } from '@pyro/io'; +import IService from '../IService'; +import { ExistenceEventType } from '@pyro/db-server'; +import { concat, of, Observable } from 'rxjs'; +import { exhaustMap, filter, share } from 'rxjs/operators'; +import User from '@modules/server.common/entities/User'; +import mongoose = require('mongoose'); +import { ObjectId } from 'mongodb'; +import OrderCarrierStatus from '@modules/server.common/enums/OrderCarrierStatus'; +import OrderWarehouseStatus from '@modules/server.common/enums/OrderWarehouseStatus'; +import Logger from 'bunyan'; + +/** + * Customers Orders Service + * TODO: rename Users to Customers + * + * @export + * @class UsersOrdersService + * @implements {IUserOrdersRouter} + * @implements {IService} + */ +@injectable() +@routerName('user-orders') +export class UsersOrdersService implements IUserOrdersRouter, IService { + protected readonly log: Logger = createEverLogger({ + name: 'usersOrdersService', + }); + + constructor( + @inject(new LazyServiceIdentifer(() => OrdersService)) + protected ordersService: OrdersService, + @inject(new LazyServiceIdentifer(() => UsersService)) + protected usersService: UsersService + ) {} + + /** + * Get Orders for given Customers + * TODO: add paging + * + * @param {User['id']} userId + * @returns {Observable} + * @memberof UsersOrdersService + */ + @observableListener() + get(userId: User['id']): Observable { + return concat( + of(null), + this.ordersService.existence.pipe( + filter((e) => this._shouldPull(userId, e)), + share() + ) + ).pipe(exhaustMap(() => this.getCurrent(userId))); + } + + /** + * Get Orders for given Customers + * TODO: add paging + * + * @param {string} userId + * @returns {Promise} + * @memberof UsersOrdersService + */ + async getCurrent(userId: string): Promise { + const orders = await this.ordersService.find({ + 'user._id': new mongoose.Types.ObjectId(userId), + isDeleted: { $eq: false }, + }); + + return _.orderBy( + orders, + [(order) => order.createdAt, (order) => order.orderNumber], + ['desc', 'desc'] + ); + } + + async getCustomerMetrics(id: string) { + const completedUserOrders = await this.ordersService.Model.find({ + $and: [ + { 'user._id': id }, + { + $or: [ + { carrierStatus: OrderCarrierStatus.DeliveryCompleted }, + { + warehouseStatus: + OrderWarehouseStatus.GivenToCustomer, + }, + ], + }, + { isCancelled: false }, + ], + }).select({ products: 1 }); + + const completedOrdersTotalSum = completedUserOrders + .map((o) => { + return o.products + .map((p) => { + return p.price * p.count; + }) + .reduce((a, b) => a + b, 0); + }) + .reduce((a, b) => a + b, 0); + + const totalOrders = await this.ordersService.Model.find({ + 'user._id': id, + }) + .countDocuments() + .exec(); + + const canceledOrders = await this.ordersService.Model.find({ + $and: [{ 'user._id': id }, { isCancelled: true }], + }) + .countDocuments() + .exec(); + + return { + totalOrders, + canceledOrders, + completedOrdersTotalSum, + }; + } + + private _shouldPull(userId: User['id'], event) { + switch (event.type as ExistenceEventType) { + case ExistenceEventType.Created: + return event.value != null && event.value.user.id === userId; + + case ExistenceEventType.Updated: + return ( + (event.value != null && event.value.user.id === userId) || + (event.lastValue != null && + event.lastValue.user.id === userId) + ); + + case ExistenceEventType.Removed: + return ( + event.lastValue != null && + event.lastValue.user.id === userId + ); + } + } +} diff --git a/packages/core/src/services/users/UsersProductsService.ts b/packages/core/src/services/users/UsersProductsService.ts new file mode 100644 index 0000000..a28faeb --- /dev/null +++ b/packages/core/src/services/users/UsersProductsService.ts @@ -0,0 +1,91 @@ +import { injectable } from 'inversify'; +import { observableListener, routerName } from '@pyro/io'; +import IUserProductsRouter from '@modules/server.common/routers/IUserProductsRouter'; +import Handlebars from 'handlebars'; +import { observeFile } from '../../utils'; +import { DevicesService } from '../devices/DevicesService'; +import IService from '../IService'; +import { + distinctUntilChanged, + exhaustMap, + publishReplay, + refCount, + share, + switchMap, +} from 'rxjs/operators'; +import { combineLatest, of, throwError, Observable } from 'rxjs'; + +@injectable() +@routerName('user-products') +export class UsersProductsService implements IUserProductsRouter, IService { + private static templatesDirPath: string = `${__dirname}/../../../res/templates/`; + + protected _placeholderTemplateFileName: string = + UsersProductsService.templatesDirPath + `user_products_placeholder.hbs`; + + protected _placeholderTranslationsFileName: string = + UsersProductsService.templatesDirPath + + `user_products_placeholder.json`; + + private readonly _placeholderTemplateString: Observable; + private readonly _placeholderTranslationsJSON: Observable; + + constructor(protected devicesService: DevicesService) { + this._placeholderTemplateString = observeFile( + this._placeholderTemplateFileName + ).pipe(distinctUntilChanged(), publishReplay(1), refCount()); + + this._placeholderTranslationsJSON = observeFile( + this._placeholderTranslationsFileName + ).pipe(distinctUntilChanged(), publishReplay(1), refCount()); + } + + /** + * Returns a html representing placeholder to show in app when there are no products available. + * + * @param {string} userId + * @param {string} deviceId + * @returns {Observable} + * @memberof UsersProductsService + */ + @observableListener() + getPlaceholder(userId: string, deviceId: string): Observable { + return this.devicesService.get(deviceId).pipe( + exhaustMap((device) => { + if (device === null) { + return throwError(() => + new Error(`User with the id ${userId} doesn't exist`) + ); + } else { + return of(device); + } + }), + distinctUntilChanged( + (oldDevice, newDevice) => + oldDevice.language !== newDevice.language + ), + switchMap((device) => + combineLatest( + this._placeholderTemplateString, + this._placeholderTranslationsJSON, + (templateString: string, translationsJSON: string) => { + const translations = JSON.parse(translationsJSON); + + const template = Handlebars.compile(templateString); + + const language = Object.keys( + JSON.parse(translationsJSON) + ).filter((k) => + k + .toLowerCase() + .includes(device.language.toLowerCase()) + )[0]; + + return template(translations[language]); + } + ) + ), + share() + ); + } +} diff --git a/packages/core/src/services/users/UsersService.ts b/packages/core/src/services/users/UsersService.ts new file mode 100644 index 0000000..c5c0164 --- /dev/null +++ b/packages/core/src/services/users/UsersService.ts @@ -0,0 +1,584 @@ +import Logger from 'bunyan'; +import { inject, injectable, LazyServiceIdentifer } from 'inversify'; +import { env } from '../../env'; +import User from '@modules/server.common/entities/User'; +import { createEverLogger } from '../../helpers/Log'; +import { InvitesService } from '../invites'; +import { DBService } from '@pyro/db-server'; +import { + IUserCreateObject, + IUserInitializeObject, +} from '@modules/server.common/interfaces/IUser'; +import IUserRouter from '@modules/server.common/routers/IUserRouter'; +import { + asyncListener, + observableListener, + routerName, + serialization, +} from '@pyro/io'; +import { Observable, throwError } from 'rxjs'; +import { observeFile } from '../../utils'; +import GeoLocation, { + Country, +} from '@modules/server.common/entities/GeoLocation'; +import IGeoLocation, { + IGeoLocationCreateObject, +} from '@modules/server.common/interfaces/IGeoLocation'; +import { DevicesService } from '../devices'; +import IService from '../IService'; +import { v1 as uuid } from 'uuid'; +import { + distinctUntilChanged, + exhaustMap, + first, + publishReplay, + refCount, + switchMap, + tap, + map, +} from 'rxjs/operators'; +import { of } from 'rxjs'; +import ILanguage from '@modules/server.common/interfaces/ILanguage'; +import _ = require('lodash'); +import * as faker from 'faker'; +import { WarehousesService } from '../../services/warehouses'; +import IPagingOptions from '@modules/server.common/interfaces/IPagingOptions'; +import { Stripe } from 'stripe'; +import * as path from 'path'; + +interface IWatchedFiles { + aboutUs: { [language in ILanguage]: Observable }; + privacy: { [language in ILanguage]: Observable }; + termsOfUse: { [language in ILanguage]: Observable }; + help: { [language in ILanguage]: Observable }; +} + +/** + * Customers Service + * TODO: rename from UsersService to CustomersService + * + * @export + * @class UsersService + * @extends {DBService} + * @implements {IUserRouter} + * @implements {IService} + */ +@injectable() +@routerName('user') +export class UsersService + extends DBService + implements IUserRouter, IService +{ + public readonly DBObject: any = User; + + // TODO: this and other Stripe related things should be inside separate Payments Service + private stripe = new Stripe(env.STRIPE_SECRET_KEY, { + apiVersion: '2020-08-27', + }); + + protected readonly log: Logger = createEverLogger({ + name: 'usersService', + }); + + public watchedFiles: IWatchedFiles; + + constructor( + @inject(new LazyServiceIdentifer(() => InvitesService)) + protected invitesService: InvitesService, + @inject(new LazyServiceIdentifer(() => DevicesService)) + protected devicesService: DevicesService, + @inject(new LazyServiceIdentifer(() => WarehousesService)) + protected _storesService: WarehousesService + ) { + super(); + // TODO: too many hardcoded constants used below. Refactor! + this.watchedFiles = _.zipObject( + ['aboutUs', 'privacy', 'termsOfUse', 'help'], + _.map(['about_us', 'privacy', 'terms_of_use', 'help'], (folder) => + _.zipObject( + ['en-US', 'he-IL', 'ru-RU', 'bg-BG', 'fr-FR'], + _.map( + ['en-US', 'he-IL', 'ru-RU', 'bg-BG', 'fr-FR'], + (language) => + observeFile( + `${path.resolve( + __dirname, + '../../../', + ...['res', 'templates'] + )}/${folder}/${language}.hbs` + ).pipe( + tap({ error: (err) => this.log.error(err) }), + publishReplay(1), + refCount() + ) + ) + ) + ) + ) as any; + } + + /** + * Verify if customer with given email already exists + * + * @param {string} email + * @returns {Promise} + * @memberof UsersService + */ + async isUserEmailExists(email: string): Promise { + return (await this.count({ email })) > 0; + } + + /** + * Get Customer by given social Id + * + * @param {string} socialId + * @returns {Promise} + * @memberof UsersService + */ + async getSocial(socialId: string): Promise { + return super.findOne({ + socialIds: { $in: [socialId] }, + isDeleted: { $eq: false }, + }); + } + + /** + * Create new customer (intialize record) + * + * @param {IUserInitializeObject} userInitializeObject + * @returns {Promise} + * @memberof UsersService + */ + async initUser(userInitializeObject: IUserInitializeObject): Promise { + return super.create(userInitializeObject as any); + } + + /** + * Get Customers + * + * @param {*} findInput + * @param {IPagingOptions} pagingOptions + * @returns + * @memberof UsersService + */ + async getUsers(findInput: any, pagingOptions: IPagingOptions) { + const sortObj = {}; + if (pagingOptions.sort) { + sortObj[pagingOptions.sort.field] = pagingOptions.sort.sortBy; + } + + return this.Model.find({ + ...findInput, + isDeleted: { $eq: false }, + }) + .sort(sortObj) + .skip(pagingOptions.skip) + .limit(pagingOptions.limit) + .lean() + .exec(); + } + + /** + * Updates Customer details + * // TODO function actually returns User | null we should fix that. + * + * @param {string} id + * @param {IUserCreateObject} userCreateObject + * @returns {Promise} + * @memberof UsersService + */ + @asyncListener() + async updateUser( + id: string, + userCreateObject: IUserCreateObject + ): Promise { + await this.throwIfNotExists(id); + return super.update(id, userCreateObject); + } + + /** + * Get Customer by Id + * + * @param {string} customerId + * @returns {Observable} + * @memberof UsersService + */ + @observableListener() + get(customerId: string): Observable { + return super.get(customerId).pipe( + map(async (user) => { + await this.throwIfNotExists(customerId); + return user; + }), + switchMap((user) => user) + ); + } + + /** + * Get Stripe Cards for given customer + * TODO: move to separate Stripe (Payments) Service + * + * @param {string} userId + * @returns {Promise} + * @memberof UsersService + */ + @asyncListener() + async getCards(userId: string): Promise { + await this.throwIfNotExists(userId); + + const user = await this.get(userId).pipe(first()).toPromise(); + + if (user != null) { + if (user.stripeCustomerId != null) { + return ( + await this.stripe.customers.listSources( + user.stripeCustomerId, + { + object: 'card', + } + ) + ).data.map((source) => source as Stripe.Card); + } else { + return []; + } + } else { + throw new Error(`User with the id ${userId} doesn't exist`); + } + } + + /** + * Add Payment Method (Credit Card) for the customer. + * If method called first time for given customer, it creates Customer record in the Stripe API and + * updates stripeCustomerId in our DB + * + * TODO: move to separate Stripe (Payments) Service + * + * @param {string} userId + * @param {string} tokenId + * @returns {Promise} + * @memberof UsersService + */ + @asyncListener() + async addPaymentMethod(userId: string, tokenId: string): Promise { + await this.throwIfNotExists(userId); + + const callId = uuid(); + + this.log.info( + { callId, userId, tokenId }, + '.addPaymentMethod(userId, tokenId) called' + ); + + let card: Stripe.Card; + + try { + let user = await this.get(userId).pipe(first()).toPromise(); + + if (user != null) { + if (user.stripeCustomerId == null) { + const customer = await this.stripe.customers.create({ + email: user.email, + description: 'User id: ' + user.id, + metadata: { + userId: user.id, + }, + }); + + user = await this.update(userId, { + stripeCustomerId: customer.id, + }); + } + + card = (await this.stripe.customers.createSource( + user.stripeCustomerId as string, + { + source: tokenId, + } + )) as Stripe.Card; + } else { + throw new Error(`User with the id ${userId} doesn't exist`); + } + } catch (err) { + this.log.error( + { callId, userId, tokenId, err }, + '.addPaymentMethod(userId, tokenId) thrown error!' + ); + throw err; + } + + this.log.info( + { callId, userId, tokenId, card }, + '.addPaymentMethod(userId, tokenId) added payment method' + ); + + return card.id; + } + + /** + * Update email for given Customer (by customer Id) + * + * @param {string} userId + * @param {string} email + * @returns {Promise} + * @memberof UsersService + */ + @asyncListener() + async updateEmail(userId: string, email: string): Promise { + await this.throwIfNotExists(userId); + return this.update(userId, { email }); + } + + /** + * Update current location (address) for given Customer + * + * @param {string} userId + * @param {GeoLocation} geoLocation + * @returns {Promise} + * @memberof UsersService + */ + @asyncListener() + async updateGeoLocation( + userId: string, + @serialization((g: IGeoLocation) => new GeoLocation(g)) + geoLocation: GeoLocation + ): Promise { + await this.throwIfNotExists(userId); + return this.update(userId, { geoLocation }); + } + + /** + * Get About Us Content (HTML) + * Note: Depending on user country, language and other settings, we may want later to show different About Us page + * (e.g. show different contact details or branch location etc) + * @param userId + * @param deviceId + * @param selectedLanguage + * @returns HTML representation of About Us + */ + @observableListener() + getAboutUs( + userId: string, + deviceId: string, + selectedLanguage: string + ): Observable /*returns html*/ { + const language = selectedLanguage as ILanguage; + return this.devicesService.get(deviceId).pipe( + exhaustMap((device) => { + if (device === null) { + /* TODO: return Error here + return ( + new Error(`User with the id ${userId} doesn't exist`) + ); + */ + } else { + return of(device); + } + }), + distinctUntilChanged( + (oldDevice, newDevice) => + oldDevice.language !== newDevice.language + ), + switchMap((device) => this.watchedFiles.aboutUs[language]) + ); + } + + /** + * Get Terms Of Use Content (HTML) + * Note: Depending on user country, language and other settings, we may want later to show different Terms + * @param userId + * @param deviceId + * @param selectedLanguage + * @returns HTML representation of Terms Of Use + */ + @observableListener() + getTermsOfUse( + userId: string, + deviceId: string, + selectedLanguage: string + ): Observable { + const language = selectedLanguage as ILanguage; + return this.devicesService.get(deviceId).pipe( + exhaustMap((device) => { + if (device === null) { + return throwError( + () => + new Error( + `Device with the id ${deviceId} doesn't exist` + ) + ); + } else { + return of(device); + } + }), + distinctUntilChanged( + (oldDevice, newDevice) => + oldDevice.language !== newDevice.language + ), + switchMap((device) => this.watchedFiles.termsOfUse[language]) + ); + } + + /** + * Get Privacy Policy Content (HTML) + * Note: Depending on user country, language and other settings, we may want later to show different Policy + * @param userId + * @param deviceId + * @param selectedLanguage + * @returns HTML representation of privacy policy + */ + @observableListener() + getPrivacy( + userId: string, + deviceId: string, + selectedLanguage: string + ): Observable { + const language = selectedLanguage as ILanguage; + return this.devicesService.get(deviceId).pipe( + exhaustMap((device) => { + if (device === null) { + return throwError( + () => + new Error( + `User with the id ${userId} doesn't exist` + ) + ); + } else { + return of(device); + } + }), + distinctUntilChanged( + (oldDevice, newDevice) => + oldDevice.language !== newDevice.language + ), + switchMap((device) => this.watchedFiles.privacy[language]) + ); + } + + /** + * Get Help Content (HTML) + * Note: Depending on user country, language and other settings, we may want later to show different Help + * @param userId + * @param deviceId + * @param selectedLanguage + * @returns HTML representation of privacy policy + */ + @observableListener() + getHelp( + userId: string, + deviceId: string, + selectedLanguage: string + ): Observable { + const language = selectedLanguage as ILanguage; + return this.devicesService.get(deviceId).pipe( + exhaustMap((device) => { + if (device === null) { + return throwError( + () => + new Error( + `User with the id ${userId} doesn't exist` + ) + ); + } else { + return of(device); + } + }), + distinctUntilChanged( + (oldDevice, newDevice) => + oldDevice.language !== newDevice.language + ), + switchMap((device) => this.watchedFiles.help[language]) + ); + } + + /** + * Generates Fake Customer records + * TODO: move to separate FakeUsersService (put into 'fake-data' folder) + * TODO: rename method to "generateCustomers" + * + * @param {number} defaultLng + * @param {number} defaultLat + * @returns {Promise} + * @memberof UsersService + */ + async generate1000Customers( + defaultLng: number, + defaultLat: number + ): Promise { + const existingEmails = _.map( + await this.Model.find({}).select({ email: 1 }).lean().exec(), + (u) => u.email + ); + + const customersToCreate: IUserCreateObject[] = []; + + const customerCreatedFrom = new Date(2015, 1); + const customerCreatedTo = new Date(); + + let customerCount = 1; + + while (customerCount <= 1000) { + const firstName = faker.name.firstName(); + const lastName = faker.name.lastName(); + const email = faker.internet.email(firstName, lastName); + const isBanned = Math.random() < 0.02; + + const geoLocation: IGeoLocationCreateObject = { + countryId: faker.datatype.number(Country.ZW) as Country, + city: faker.address.city(), + house: `${customerCount}`, + loc: { + type: 'Point', + coordinates: [defaultLng, defaultLat], + }, + streetAddress: faker.address.streetAddress(), + }; + + if (!existingEmails.includes(email)) { + existingEmails.push(email); + + customersToCreate.push({ + firstName: faker.name.firstName(), + lastName: faker.name.lastName(), + geoLocation, + apartment: `${customerCount}`, + email, + isBanned, + image: faker.image.avatar(), + phone: faker.phone.phoneNumber(), + _createdAt: faker.date.between( + customerCreatedFrom, + customerCreatedTo + ), + } as any); + + customerCount += 1; + } + } + + return this.Model.insertMany(customersToCreate); + } + + async banUser(id: string): Promise { + await this.throwIfNotExists(id); + return this.update(id, { isBanned: true }); + } + + async unbanUser(id: string): Promise { + await this.throwIfNotExists(id); + return this.update(id, { isBanned: false }); + } + + /** + * Check if not deleted customer with given Id exists in DB and throw exception if it's not exists or deleted + * + * @param {string} userId + * @memberof UsersService + */ + async throwIfNotExists(userId: string) { + const user = await super.get(userId).pipe(first()).toPromise(); + + if (!user || user.isDeleted) { + throw Error(`Customer with id '${userId}' does not exists!`); + } + } +} diff --git a/packages/core/src/services/users/index.ts b/packages/core/src/services/users/index.ts new file mode 100644 index 0000000..b4195a3 --- /dev/null +++ b/packages/core/src/services/users/index.ts @@ -0,0 +1,7 @@ +export * from './UsersService'; +export * from './SocialStrategiesService'; +export * from './SocialRegisterService'; +export * from './UsersOrdersService'; +export * from './UsersProductsService'; +export * from './UserCommandService'; +export * from './UsersAuthService'; diff --git a/packages/core/src/services/warehouses/WarehousesCarriersService.ts b/packages/core/src/services/warehouses/WarehousesCarriersService.ts new file mode 100644 index 0000000..9277a48 --- /dev/null +++ b/packages/core/src/services/warehouses/WarehousesCarriersService.ts @@ -0,0 +1,99 @@ +import { inject, injectable, LazyServiceIdentifer } from 'inversify'; +import Carrier from '@modules/server.common/entities/Carrier'; +import { of, Observable } from 'rxjs'; +import { CarriersService } from '../carriers'; +import { WarehousesService } from './WarehousesService'; +import Warehouse from '@modules/server.common/entities/Warehouse'; +import { + catchError, + distinctUntilChanged, + exhaustMap, + map, + switchMap, +} from 'rxjs/operators'; +import { routerName, observableListener, asyncListener } from '@pyro/io'; +import * as _ from 'lodash'; +import IWarehouseCarriersRouter from '@modules/server.common/routers/IWarehouseCarriersRouter'; +import { env } from '../../env'; +import { AuthService, AuthServiceFactory } from '../auth'; + +class NoWarehouseRestrictedCarriersError extends Error { + constructor() { + super("Warehouse doesn't have carriers restricted to himself"); + } +} + +/** + * Warehouses Carriers Service + * + * @export + * @class WarehousesCarriersService + * @implements {IWarehouseCarriersRouter} + */ +@injectable() +@routerName('warehouse-carriers') +export class WarehousesCarriersService implements IWarehouseCarriersRouter { + private readonly authService: AuthService; + + constructor( + @inject(new LazyServiceIdentifer(() => CarriersService)) + private readonly carriersService: CarriersService, + @inject(new LazyServiceIdentifer(() => WarehousesService)) + private readonly warehousesService: WarehousesService, + @inject('Factory') + private readonly authServiceFactory: AuthServiceFactory + ) { + this.authService = this.authServiceFactory({ + role: 'carrier', + Entity: Carrier, + saltRounds: env.CARRIER_PASSWORD_BCRYPT_SALT_ROUNDS, + }); + } + + /** + * Get Carriers assigned to given Store + * Returns null if !warehouse.hasRestrictedCarriers + * @param {String} warehouseId + * @returns {Observable} + */ + @observableListener() + get(warehouseId: Warehouse['id']): Observable { + return this.warehousesService.get(warehouseId).pipe( + map((warehouse) => { + if (!warehouse.hasRestrictedCarriers) { + throw new NoWarehouseRestrictedCarriersError(); + } + + return warehouse.usedCarriersIds; + }), + distinctUntilChanged((carrierIds1, carrierIds2) => { + return _.isEqual(carrierIds1.sort(), carrierIds2.sort()); + }), + exhaustMap((carrierIds) => { + return this.carriersService.getMultipleByIds(carrierIds); + }), + switchMap((carriers) => carriers), + catchError((err) => { + if (!(err instanceof NoWarehouseRestrictedCarriersError)) { + throw err; + } + + return of(null); + }) + ); + } + + /** + * Update carrier password + * + * @param {Carrier['id']} id + * @param {String} password + * @returns {Promise} + * @memberof WarehousesCarriersService + */ + @asyncListener() + async updatePassword(id: Carrier['id'], password: string): Promise { + await this.carriersService.throwIfNotExists(id); + await this.authService._savePassword(id, password); + } +} diff --git a/packages/core/src/services/warehouses/WarehousesOrdersService.ts b/packages/core/src/services/warehouses/WarehousesOrdersService.ts new file mode 100644 index 0000000..ed8a4d4 --- /dev/null +++ b/packages/core/src/services/warehouses/WarehousesOrdersService.ts @@ -0,0 +1,738 @@ +import Logger from 'bunyan'; +import { inject, injectable, LazyServiceIdentifer } from 'inversify'; +import moment from 'moment'; +import Bluebird from 'bluebird'; +import { createEverLogger } from '../../helpers/Log'; +import OrderProduct from '@modules/server.common/entities/OrderProduct'; +import Order from '@modules/server.common/entities/Order'; +import Product from '@modules/server.common/entities/Product'; +import { WarehousesService } from './WarehousesService'; +import { ProductsService } from '../products'; +import { OrdersService } from '../orders'; +import { IOrderProductCreateObject } from '@modules/server.common/interfaces/IOrderProduct'; +import IOrder from '@modules/server.common/interfaces/IOrder'; +import { WarehousesProductsService } from './WarehousesProductsService'; +import IWarehouseOrdersRouter, { + IOrderCreateInput, +} from '@modules/server.common/routers/IWarehouseOrdersRouter'; +import { asyncListener, observableListener, routerName } from '@pyro/io'; +import { UsersService } from '../users'; +import IService from '../IService'; +import { + exhaustMap, + filter, + first, + share, + switchMap, + map, +} from 'rxjs/operators'; +import { ExistenceEvent, ExistenceEventType } from '@pyro/db-server'; +import { concat, of, Observable } from 'rxjs'; +import Warehouse, { + WithFullProducts, +} from '@modules/server.common/entities/Warehouse'; +import _ = require('lodash'); +import IPagingOptions from '@modules/server.common/interfaces/IPagingOptions'; +import OrderWarehouseStatus from '@modules/server.common/enums/OrderWarehouseStatus'; +import OrderCarrierStatus from '@modules/server.common/enums/OrderCarrierStatus'; +import DeliveryType from '@modules/server.common/enums/DeliveryType'; +import User from '@modules/server.common/entities/User'; + +/** + * Warehouses Orders Service + * + * @export + * @class WarehousesOrdersService + * @implements {IWarehouseOrdersRouter} + * @implements {IService} + */ +@injectable() +@routerName('warehouse-orders') +export class WarehousesOrdersService + implements IWarehouseOrdersRouter, IService { + protected log: Logger = createEverLogger({ + name: 'warehousesOrdersService', + }); + + constructor( + @inject(new LazyServiceIdentifer(() => WarehousesService)) + protected warehousesService: WarehousesService, + @inject(new LazyServiceIdentifer(() => ProductsService)) + protected productsService: ProductsService, + @inject(new LazyServiceIdentifer(() => WarehousesProductsService)) + protected warehousesProductsService: WarehousesProductsService, + @inject(new LazyServiceIdentifer(() => OrdersService)) + protected ordersService: OrdersService, + @inject(new LazyServiceIdentifer(() => UsersService)) + protected usersService: UsersService + ) {} + + /** + * TODO: document + * + * @param {Warehouse['id']} warehouseId + * @returns {Observable>} + * @memberof WarehousesOrdersService + */ + getExistence( + warehouseId: Warehouse['id'] + ): Observable> { + return this.ordersService.existence.pipe( + filter((existenceEvent) => { + switch (existenceEvent.type as ExistenceEventType) { + case ExistenceEventType.Created: + return ( + existenceEvent.value != null && + existenceEvent.value.warehouseId === warehouseId + ); + + case ExistenceEventType.Updated: + return ( + (existenceEvent.value != null && + existenceEvent.value.warehouseId === + warehouseId) || + (existenceEvent.lastValue != null && + existenceEvent.lastValue.warehouseId === + warehouseId) + ); + + case ExistenceEventType.Removed: + return ( + existenceEvent.lastValue != null && + existenceEvent.lastValue.warehouseId === warehouseId + ); + } + }), + share() + ); + } + + /** + * Get Orders from given Warehouse + * + * @param {Warehouse['id']} warehouseId + * @returns {Observable} + * @memberof WarehousesOrdersService + */ + @observableListener() + get( + warehouseId: Warehouse['id'], + options: { + populateWarehouse?: boolean; + populateCarrier?: boolean; + order?: boolean; + } = {} + ): Observable { + return concat(of(null), this.getExistence(warehouseId)).pipe( + map(async (res) => { + await this.warehousesService.throwIfNotExists(warehouseId); + return res; + }), + switchMap((res) => res), + exhaustMap(() => + this._get(warehouseId, { + populateWarehouse: !!options.populateWarehouse, + populateCarrier: !!options.populateCarrier, + onlyAvailableToCarrier: false, + }) + ) + ); + } + + /** + * Get next order number in given Store + * Implemented simple, but not efficient/stable way: + * - go over existed orders in the Store + * - find last one + * - return number of last one plus 1 + * + * Will NOT work well if called in parallel, unless we: + * TODO: implement distributed locking or store last order id separately + * + * @param {string} warehouseId + * @returns {Promise} + * @memberof WarehousesOrdersService + */ + async getNextOrderNumber(warehouseId: string): Promise { + await this.warehousesService.throwIfNotExists(warehouseId); + + const orderDocument = (await this.ordersService.Model.findOne({ + warehouse: warehouseId, + isDeleted: { $eq: false }, + _createdAt: { + $gte: (moment)().startOf('day').toDate(), + }, + }) + .select('orderNumber') + .sort({ orderNumber: -1 }) + .exec()) as any; + + if (orderDocument == null) { + return 1; + } else { + return orderDocument.orderNumber + 1; + } + } + + /** + * Order for multiple products + * User always order product from specific store because the price and availability is per store + * It is possible that user is null. + * In this case, it mean that customer does not purchase products yet, but carrier arrives to warehouse + * and get this specific products and he is ready to delivery them to customer + * (in such advanced scenarios, we create "orders", assign carriers, but do not assign customer yet) + * + * @param {IOrderCreateInput} { + * warehouseId, + * userId, + * products, + * options + * } + * @returns {Promise} + * @memberof WarehousesOrdersService + */ + @asyncListener() + async create({ + warehouseId, + userId, + products, + orderType, + waitForCompletion, + options, + }: IOrderCreateInput): Promise { + if (!options) { + options = {}; + } + + options = { + autoConfirm: !!options.autoConfirm, + }; + + const user = await this._getUser(userId); + const warehouse = await this._getWarehouse(warehouseId); + + const warehouseProducts = _.keyBy(warehouse.products, 'productId'); + + this.log.info( + { + user, + warehouseId, + products, + }, + 'Order create call' + ); + + // If no image was given from client side for an order product, + // we should copy it from product in DB + const orderProducts = await this._getOrderProducts( + products, + warehouseProducts + ); + + // TODO next should be in the single transaction! + // (i.e. create order and decrease product availability in the warehouse) + // http://mongoosejs.com/docs/transactions.html + + const order = await this.ordersService.create({ + user, + products: orderProducts, + warehouse: warehouseId, + orderNumber: await this.getNextOrderNumber(warehouseId), + orderType, + waitForCompletion: !!waitForCompletion, + ...(options.autoConfirm ? { isConfirmed: true } : {}), + }); + + // we do all remove operations and notify about warehouse orders change after we remove products from warehouse + await this._updateProductCount(order, warehouseId); + + return order; + } + + /** + * User complete order + * + * @param {string} orderId + * @returns {Promise} + * @memberof WarehousesOrdersService + */ + @asyncListener() + async userComplete(orderId): Promise { + const order = await this.ordersService.update(orderId, { + waitForCompletion: false, + }); + + return order; + } + + /** + * Add product to existed order + * + * @param {string} warehouseId + * @param {string} userId + * @param {string} orderId + * @param {IOrderCreateInputProduct} product + * @returns {Promise} + * @memberof WarehousesOrdersService + */ + @asyncListener() + async addMore(warehouseId, userId, orderId, products): Promise { + const existedOrder = await this.ordersService + .get(orderId) + .pipe(first()) + .toPromise(); + + if (existedOrder.warehouseId !== warehouseId) { + throw new Error( + `The order is not used by warehouse with Id ${warehouseId}` + ); + } + + if (existedOrder.user.id !== userId) { + throw new Error(`The order is not used by user with Id ${userId}`); + } + + const user = await this._getUser(userId); + const warehouse = await this._getWarehouse(warehouseId); + + const warehouseProducts = _.keyBy(warehouse.products, 'productId'); + + this.log.info( + { user, warehouseId, orderId, products }, + 'Add more products call' + ); + + const orderProducts = await this._getOrderProducts( + products, + warehouseProducts + ); + + const newProducts = [...existedOrder.products, ...orderProducts]; + + const order = await this.ordersService.update(orderId, { + products: newProducts, + }); + + // TODO investigate why bellow function throw error when adding new product + await this._updateProductCount(order, warehouseId); + + return order; + } + + /** + * Create Order by given customer in the given Store for 1 specific product + * (optimized for single product purchases) + * + * @param {string} userId + * @param {string} warehouseId + * @param {string} productId + * @returns {Promise} + * @memberof WarehousesOrdersService + */ + @asyncListener() + async createByProductType( + userId: string, + warehouseId: string, + productId: string, + orderType?: DeliveryType + ): Promise { + await this.usersService.throwIfNotExists(userId); + await this.warehousesService.throwIfNotExists(warehouseId); + await this.productsService.throwIfNotExists(productId); + + return this.create({ + userId, + warehouseId, + orderType, + products: [ + { + productId, + count: 1, + }, + ], + }); + } + + /** + * Cancel an order. + * Money back and cancel shipping to the client if it's started already + * For non-confirmed orders we only increase availability + * For confirmed orders we increase availability, return payment and cancel shipping + * + * @param {string} orderId + * @returns {Promise} + * @memberof WarehousesOrdersService + */ + @asyncListener() + async cancel(orderId: string): Promise { + let order = await this.ordersService + .get(orderId) + .pipe(first()) + .toPromise(); + + if (order == null) { + throw new Error( + `There is no order with the id ${orderId} to cancel` + ); + } + + if (order.isCancelled) { + this.log.warn(`Order with id ${orderId} is already cancelled!`); + return; + } + + // canceling order + order = await this.ordersService.cancel(orderId); + + if (order.isConfirmed) { + // TODO: return payment back and cancel shipping + } + + this.log.info( + { + warehouseId: order.warehouseId, + products: order.products, + }, + 'Order cancel add products back call' + ); + + // add products back to warehouse + await this.warehousesProductsService.add( + order.warehouseId, + _.map(order.products, (orderProduct) => { + return { + product: orderProduct.product.id, + count: orderProduct.count, + price: orderProduct.price, + initialPrice: orderProduct.initialPrice, + deliveryTimeMin: orderProduct.deliveryTimeMin, + deliveryTimeMax: orderProduct.deliveryTimeMax, + }; + }) + ); + + // revert order sold count + await (Bluebird).map( + order.products, + async (orderProduct: OrderProduct) => { + const productId = orderProduct.product.id; + + await this.warehousesProductsService.decreaseSoldCount( + order.warehouseId, + productId, + orderProduct.count + ); + } + ); + + this.log.info( + { + warehouseId: order.warehouseId, + products: order.products, + }, + 'Order cancel add products back call succeed' + ); + + return order; + } + + /** + * Get Orders for given Store + * + * @param {string} storeId + * @param {IPagingOptions} pagingOptions + * @param {string} status + * @returns + * @memberof WarehousesOrdersService + */ + async getStoreOrders( + storeId: string, + pagingOptions: IPagingOptions, + status: string + ) { + const sortObj = {}; + + const findObj = getStoreOrdersFingObj(storeId, status); + + if (pagingOptions.sort) { + sortObj[pagingOptions.sort.field] = pagingOptions.sort.sortBy; + } + + return this.ordersService.Model.find(findObj) + .sort(sortObj) + .skip(pagingOptions.skip) + .limit(pagingOptions.limit) + .lean() + .exec(); + } + + private async _get( + warehouseId: Warehouse['id'], + options: { + populateWarehouse?: boolean; + populateCarrier?: boolean; + onlyAvailableToCarrier?: boolean; + } = {} + ): Promise { + await this.warehousesService.throwIfNotExists(warehouseId); + + const findObj = { warehouse: warehouseId }; + + if (options.onlyAvailableToCarrier) { + _.extend( + findObj, + { + carrierStatus: 0, + $or: [ + { + carrier: { $exists: false }, + }, + { + carrier: null, + }, + ], + warehouseStatus: { $gte: 2, $lt: 200 }, + }, + OrdersService.FindObjects.isCompleted + ); + } + + let toPopulate = ''; + + if (options.populateCarrier) { + toPopulate += 'carrier '; + } + + if (options.populateWarehouse) { + toPopulate += 'warehouse '; + } + + const orders = _.map( + (await this.ordersService.Model.find({ + ...findObj, + isDeleted: { $eq: false }, + }) + .populate(toPopulate) + .sort({ + _createdAt: -1, // Sort by Date Added DESC + }) + .lean() + .exec()) as IOrder[], + (order) => new Order(order) + ); + + this.log.info( + { + warehouseId, + orders, + }, + 'orders by warehouse (in getByWarehouse)' + ); + + return orders; + } + + private async _getUser(userId: string): Promise { + const user = await this.usersService + .get(userId) + .pipe(first()) + .toPromise(); + + if (user == null) { + throw new Error(`There is no user with the id ${userId}`); + } + + return user; + } + + private async _getWarehouse( + warehouseId: string + ): Promise { + const warehouse = (await this.warehousesService + .get(warehouseId, true) + .pipe(first()) + .toPromise()) as WithFullProducts; + + if (warehouse == null) { + throw new Error(`There is no warehouse with the id ${warehouseId}`); + } + + return warehouse; + } + + private async _getOrderProducts( + products, + warehouseProducts + ): Promise { + return _.map( + products, + (args): IOrderProductCreateObject => { + const wProduct = warehouseProducts[args.productId]; + if (!wProduct) { + throw new Error( + `WarehouseOrdersService got call to create(userId, orderProducts) - But there is no product with the id ${args.productId}!` + ); + } + return { + count: args.count, + comment: args.comment, + price: wProduct.price, + initialPrice: wProduct.initialPrice, + deliveryTimeMin: wProduct.deliveryTimeMin, + deliveryTimeMax: wProduct.deliveryTimeMax, + product: wProduct.product as Product, + isManufacturing: wProduct.isManufacturing, + isCarrierRequired: wProduct.isCarrierRequired, + isDeliveryRequired: wProduct.isDeliveryRequired, + isTakeaway: wProduct.isTakeaway, + }; + } + ); + } + + private async _updateProductCount(order, warehouseId) { + await (Bluebird).map( + order.products, + async (orderProduct: OrderProduct) => { + const productId = orderProduct.product.id; + + this.log.info( + { + warehouseId, + productId, + count: orderProduct.count, + }, + 'Order create remove products call' + ); + + await this.warehousesProductsService.decreaseCount( + warehouseId, + productId, // what product availability should be decreased + orderProduct.count // how many to remove + ); + + await this.warehousesProductsService.increaseSoldCount( + warehouseId, + productId, + orderProduct.count + ); + + this.log.info( + { + warehouseId, + productId, + count: orderProduct.count, + }, + 'Order create remove products call succeed' + ); + } + ); + } +} + +export function getStoreOrdersFingObj(storeId: string, status: string) { + const findObj = { + isDeleted: { $eq: false }, + waitForCompletion: { $eq: false }, + warehouse: storeId, + }; + + switch (status) { + case 'confirmed': + findObj['$and'] = [ + { + warehouseStatus: { + $gt: OrderWarehouseStatus.NoStatus, + }, + }, + { + warehouseStatus: { + $lt: OrderWarehouseStatus.GivenToCustomer, + }, + }, + { + carrierStatus: { + $lte: OrderCarrierStatus.CarrierSelectedOrder, + }, + }, + ]; + findObj['isCancelled'] = false; + break; + case 'in_delivery': + findObj['$and'] = [ + { + carrierStatus: { + $gte: OrderCarrierStatus.CarrierPickedUpOrder, + }, + }, + { + warehouseStatus: { + $lt: OrderWarehouseStatus.AllocationFailed, + }, + }, + { + carrierStatus: { + $lt: OrderCarrierStatus.DeliveryCompleted, + }, + }, + ]; + findObj['isCancelled'] = false; + break; + case 'not_confirmed': + findObj['warehouseStatus'] = OrderWarehouseStatus.NoStatus; + findObj['isCancelled'] = false; + break; + case 'alocation_started': + findObj['warehouseStatus'] = OrderWarehouseStatus.AllocationStarted; + findObj['isCancelled'] = false; + break; + case 'ready_for_packaging': + findObj['warehouseStatus'] = + OrderWarehouseStatus.AllocationFinished; + findObj['isCancelled'] = false; + break; + case 'processing': + findObj['warehouseStatus'] = + OrderWarehouseStatus.WarehouseStartedProcessing; + findObj['isCancelled'] = false; + break; + case 'packaging': + findObj['warehouseStatus'] = OrderWarehouseStatus.PackagingStarted; + findObj['isCancelled'] = false; + break; + case 'packaged': + findObj['warehouseStatus'] = OrderWarehouseStatus.PackagingFinished; + findObj['isCancelled'] = false; + break; + case 'not_paid': + findObj['isPaid'] = false; + break; + case 'cancelled': + findObj['isCancelled'] = true; + break; + case 'relevant': + findObj['$and'] = [ + { + warehouseStatus: { + $gte: OrderWarehouseStatus.NoStatus, + }, + }, + { + warehouseStatus: { + $lt: OrderWarehouseStatus.GivenToCustomer, + }, + }, + { + carrierStatus: { + $lte: OrderCarrierStatus.CarrierSelectedOrder, + }, + }, + ]; + findObj['isCancelled'] = false; + break; + default: + break; + } + + return findObj; +} diff --git a/packages/core/src/services/warehouses/WarehousesProductsService.ts b/packages/core/src/services/warehouses/WarehousesProductsService.ts new file mode 100644 index 0000000..3aa317f --- /dev/null +++ b/packages/core/src/services/warehouses/WarehousesProductsService.ts @@ -0,0 +1,748 @@ +import { injectable } from 'inversify'; +import { createEverLogger } from '../../helpers/Log'; +import Logger from 'bunyan'; +import WarehouseProduct from '@modules/server.common/entities/WarehouseProduct'; +import { WarehousesService } from './WarehousesService'; +import IWarehouseProduct, { + IWarehouseProductCreateObject, +} from '@modules/server.common/interfaces/IWarehouseProduct'; +import * as _ from 'lodash'; +import Warehouse from '@modules/server.common/entities/Warehouse'; +import IWarehouse from '@modules/server.common/interfaces/IWarehouse'; +import { ExistenceEventType } from '@pyro/db-server'; +import { Observable, throwError } from 'rxjs'; +import IWarehouseProductsRouter from '@modules/server.common/routers/IWarehouseProductsRouter'; +import { + asyncListener, + observableListener, + routerName, + serialization, +} from '@pyro/io'; +import IProduct from '@modules/server.common/interfaces/IProduct'; +import IService from '../IService'; +import { exhaustMap, first, map } from 'rxjs/operators'; +import { of } from 'rxjs'; +import mongoose = require('mongoose'); +import IPagingOptions from '@modules/server.common/interfaces/IPagingOptions'; + +const noGetProductTypeMessage = `There should be true at least one of the two - "isCarrierRequired" or "isTakeaway"!`; + +/** + * Warehouses Products Service + * + * @export + * @class WarehousesProductsService + * @implements {IWarehouseProductsRouter} + * @implements {IService} + */ +@injectable() +@routerName('warehouse-products') +export class WarehousesProductsService + implements IWarehouseProductsRouter, IService { + protected readonly log: Logger = createEverLogger({ + name: 'warehouseProductsService', + }); + + constructor(private readonly warehousesService: WarehousesService) {} + + /** + * Get all products for given warehouse (not only available, but all assigned) + * + * @param {string} warehouseId + * @param {boolean} [fullProducts=true] if true, include full products details + * @returns {Observable} + * @memberof WarehousesProductsService + */ + @observableListener() + get( + warehouseId: string, + fullProducts: boolean = true + ): Observable { + return this.warehousesService.get(warehouseId, fullProducts).pipe( + exhaustMap((warehouse: Warehouse) => { + if (warehouse === null) { + return throwError(() => + new Error( + `Warehouse with the id ${warehouseId} doesn't exist` + ) + ); + } else { + return of(warehouse); + } + }), + map((warehouse: Warehouse) => warehouse.products) + ); + } + + @asyncListener() + async getProductsWithPagination( + id: string, + pagingOptions: IPagingOptions + ): Promise { + const allProducts = await this.get(id).pipe(first()).toPromise(); + + const products = [...allProducts]; + + if (pagingOptions.limit && pagingOptions.skip) { + return products + .slice(pagingOptions.skip) + .slice(0, pagingOptions.limit) + .sort((a, b) => b.soldCount - a.soldCount); + } else if (pagingOptions.limit) { + return products + .slice(0, pagingOptions.limit) + .sort((a, b) => b.soldCount - a.soldCount); + } else if (pagingOptions.skip) { + return products + .slice(pagingOptions.skip) + .sort((a, b) => b.soldCount - a.soldCount); + } + + return products.sort((a, b) => b.soldCount - a.soldCount); + } + + @asyncListener() + async getProductsCount(id: string) { + const allProducts = await this.get(id).pipe(first()).toPromise(); + + return allProducts.length; + } + + /** + * Get products for given warehouse which available for purchase + * + * @param {string} warehouseId + * @returns {Observable} + * @memberof WarehousesProductsService + */ + @observableListener() + getAvailable(warehouseId: string): Observable { + return this.get(warehouseId).pipe( + map((warehouseProducts) => + _.filter( + warehouseProducts, + (warehouseProduct) => + warehouseProduct.count > 0 && + warehouseProduct.isProductAvailable === true + ) + ) + ); + } + + /** + * Remove products from warehouse + * + * @param {string} warehouseId + * @param {string[]} productsIds + * @returns {Promise} + * @memberof WarehousesProductsService + */ + @asyncListener() + async remove( + warehouseId: string, + productsIds: string[] + ): Promise { + this.log.info('Removing products ' + productsIds); + + const warehouse = await this.warehousesService + .get(warehouseId, true) + .pipe(first()) + .toPromise(); + + if (warehouse == null) { + throw new Error(`There is no warehouse with the id ${warehouse}!`); + } + + warehouse.products = warehouse.products.filter((p) => { + if (!p.product['_id']) { + return false; + } + + const productId = p.product['id']; + return !productsIds.includes(productId); + }); + await this.warehousesService.save(warehouse); + + return warehouse.products; + } + + /** + * Add products to warehouse + * TODO: should "merge" products, not just add them! + * By merge I mean increase qty if product already in warehouse or add new product if it's not. + * We also should think what to do with prices? + * Is it possible to have 2 same products but come with different price? + * Or we should merge products and use latest price? + * + * @param {string} warehouseId + * @param {IWarehouseProductCreateObject[]} products + * @param {boolean} [triggerChange=true] + * @returns {Promise} + * @memberof WarehousesProductsService + */ + @asyncListener() + async add( + warehouseId: string, + products: IWarehouseProductCreateObject[], + triggerChange: boolean = true + ): Promise { + // TODO: use atomic operations (e.g.:findAndModify) or 2 phase commit + // see http://blog.ocliw.com/2012/11/25/mongoose-add-to-an-existing-array/ + + this.log.info('Adding products ' + JSON.stringify(products)); + + let warehouse = await this.warehousesService + .get(warehouseId, false) + .pipe(first()) + .toPromise(); // products not populated! + + if (warehouse == null) { + throw new Error(`There is no warehouse with the id ${warehouse}!`); + } + + const notUpdatedWarehouse = _.clone(warehouse); + + // In practice to make it more reliable, we should go one by one, i.e. one product a time + // and each time execute atomic operation on each product. + // Say if product already there, we want to increase count using atomic operation by given value in the storage. + // etc + + let newProds: IWarehouseProductCreateObject[]; + + if (warehouse.products && warehouse.products.length > 0) { + newProds = _.clone(warehouse.products); + + _.each(products, (product) => { + if (!product.isDeliveryRequired && !product.isTakeaway) { + product.isDeliveryRequired = true; + } + const existed = _.find( + newProds, + (newProd) => + (newProd.product as string) === + (product.product as string) + ); + + if ( + typeof existed === 'undefined' || + existed === undefined || + existed == null + ) { + newProds.push(product); // if no such product existed yet, we add it + } else { + // if product with same id already exists, we should increase his qty + if (existed.count && product.count) { + existed.count += product.count; + } else { + existed.count = product.count; + } + // should we merge price here? What if new products come with new price, + // should we make "average" price in warehouse or use latest price instead? + } + }); + } else { + newProds = products; + } + + try { + warehouse = new Warehouse( + (await this.warehousesService.Model.findByIdAndUpdate( + warehouseId, + { + $set: { products: newProds }, + }, + { new: true } + ) + .populate('products.product') + .lean() + .exec()) as IWarehouse + ); + } catch (error) { + this.log.error(error); + throw error; + } + + if (triggerChange) { + this.warehousesService.existence.next({ + id: warehouse.id, + value: warehouse, + lastValue: notUpdatedWarehouse, + type: ExistenceEventType.Updated, + }); + } + + const newProdsIds = _.map(newProds, (warehouseProduct) => { + if (typeof warehouseProduct.product === 'string') { + return warehouseProduct.product as string; + } else { + return (warehouseProduct.product as IProduct)._id.toString(); + } + }); + + return _.filter(warehouse.products, (warehouseProduct) => { + return _.includes(newProdsIds, warehouseProduct.productId); + }); + } + + /** + * Increase inventory of given product using existed product price by given count + * If no such product exists in warehouse yet, do nothing + * + * @param {string} warehouseId + * @param {string} productId + * @param {number} count + * @returns {Promise} + * @memberof WarehousesProductsService + */ + @asyncListener() + async increaseCount( + warehouseId: string, + productId: string, + count: number + ): Promise { + const warehouse = await this.warehousesService + .get(warehouseId) + .pipe(first()) + .toPromise(); + + if (warehouse) { + const existedProduct = _.find( + warehouse.products, + (warehouseProduct) => warehouseProduct.productId === productId + ); + + if (existedProduct) { + // we want to add given qty of such products + existedProduct.count += count; + + return this.saveUpdated(warehouseId, existedProduct); + } else { + const errMsg = 'Cannot find product'; + this.log.error(new Error(errMsg)); + throw new Error(errMsg); + } + } else { + const errMsg = 'Cannot find warehouse'; + this.log.error(new Error(errMsg)); + throw new Error(errMsg); + } + } + + /** + * Increase sold qty of given product by given count + * If no such product exists in warehouse yet, do nothing + * + * @param {string} warehouseId + * @param {string} productId + * @param {number} count + * @returns {Promise} + * @memberof WarehousesProductsService + */ + @asyncListener() + async increaseSoldCount( + warehouseId: string, + productId: string, + count: number + ): Promise { + const warehouse = await this.warehousesService + .get(warehouseId) + .pipe(first()) + .toPromise(); + + if (warehouse) { + const existedProduct = _.find( + warehouse.products, + (warehouseProduct) => warehouseProduct.productId === productId + ); + + if (existedProduct) { + // we want to add given qty of such products + existedProduct.soldCount += count; + + return this.saveUpdated(warehouseId, existedProduct); + } else { + const errMsg = 'Cannot find product'; + this.log.error(new Error(errMsg)); + throw new Error(errMsg); + } + } else { + const errMsg = 'Cannot find warehouse'; + this.log.error(new Error(errMsg)); + throw new Error(errMsg); + } + } + + /** + * TODO: document + * + * @param {string} warehouseId + * @param {WarehouseProduct} _updatedWarehouseProduct + * @returns {Promise} + * @memberof WarehousesProductsService + */ + @asyncListener() + async saveUpdated( + warehouseId: string, + @serialization((u: IWarehouseProduct) => new WarehouseProduct(u)) + _updatedWarehouseProduct: WarehouseProduct + ): Promise { + await this.warehousesService.throwIfNotExists(warehouseId); + if ( + !_updatedWarehouseProduct.isDeliveryRequired && + !_updatedWarehouseProduct.isTakeaway + ) { + throw new Error(noGetProductTypeMessage); + } + + const updatedWarehouseProduct = _.clone(_updatedWarehouseProduct); + updatedWarehouseProduct.product = updatedWarehouseProduct.product; + + const updatedWarehouse = ( + await this.warehousesService.updateMultiple( + { + _id: new mongoose.Types.ObjectId(warehouseId), + 'products._id': updatedWarehouseProduct._id, + }, + { + 'products.$': updatedWarehouseProduct, + } + ) + )[0]; + + return _.find( + updatedWarehouse.products, + (warehouseProduct) => + warehouseProduct.productId === updatedWarehouseProduct.productId + ) as WarehouseProduct; + } + + /** + * Change price for the product + * + * @param {string} warehouseId + * @param {string} productId which product price should be changed + * @param {number} price new price + * @returns {Promise} + * @memberof WarehousesProductsService + */ + @asyncListener() + async changePrice( + warehouseId: string, + productId: string, + price: number + ): Promise { + // TODO: Some global distributed lock should be apply here + // so we can't change product price on 2 separate servers at the same time? + + const warehouse = await this.warehousesService + .get(warehouseId, false) + .pipe(first()) + .toPromise(); + + if (warehouse == null) { + throw new Error( + `There is no such an warehouse with the id ${warehouseId}` + ); + } + + this.log.info( + 'Change product price requested in warehouse: ' + + JSON.stringify(warehouse) + + ' for product id: ' + + productId + ); + + const product = _.find( + warehouse.products, + (p) => p.productId === productId + ); + + if (product != null) { + this.log.info( + `Product price before: ${product.price} and we want to change it to: ${price}` + ); + product.price = price; + + return this.saveUpdated(warehouseId, product); + } else { + throw new Error( + `There is no such an product with the id ${productId} in the warehouse with the id ${warehouseId}` + ); + } + } + + /** + * Removes products from warehouse (called during sell of the product) + * Always cause change notification to be send to the clients + * + * @param {string} warehouseId + * @param {string} productId what product availability should be decreased + * @param {number} count how many to remove + * @returns {Promise} + * @memberof WarehousesProductsService + */ + @asyncListener() + async decreaseCount( + warehouseId: string, + productId: string, + count: number + ): Promise { + // TODO: Some global distributed lock should be apply here + // so we can't decrease product count on 2 separate servers at the same time! + + const warehouse = await this.warehousesService + .get(warehouseId) + .pipe(first()) + .toPromise(); + + if (warehouse == null) { + throw new Error(`Cannot find warehouse: ${warehouseId}`); + } + + this.log.info( + `Remove requested in warehouse: ${JSON.stringify( + warehouse + )} for product id: ${productId}` + ); + + const product = _.find( + warehouse.products, + (p) => p.productId === productId + ); + + if (product != null) { + this.log.info( + `Product count before remove: ${product.count} and we want to remove ${count} products` + ); + + if (product.count >= count) { + product.count -= count; + return this.saveUpdated(warehouseId, product); + } else { + const errorMsg = + 'Request to remove more products than available'; + + this.log.error({ + err: new Error(errorMsg), + product, + count, + }); + + throw new Error(errorMsg); + } + } else { + throw new Error( + `There is no such an product with the id ${productId} in the warehouse with the id ${warehouseId}` + ); + } + } + + /** + * Decrease products sold count from warehouse (called during the order cancel) + * + * @param {string} warehouseId + * @param {string} productId + * @param {number} count + * @returns {Promise} + * @memberof WarehousesProductsService + */ + @asyncListener() + async decreaseSoldCount( + warehouseId: string, + productId: string, + count: number + ): Promise { + // TODO: Some global distributed lock should be apply here + // so we can't decrease product sold count on 2 separate servers at the same time! + + const warehouse = await this.warehousesService + .get(warehouseId) + .pipe(first()) + .toPromise(); + + if (warehouse == null) { + throw new Error(`Cannot find warehouse: ${warehouseId}`); + } + + this.log.info( + `Remove requested in warehouse: ${JSON.stringify( + warehouse + )} for product id: ${productId}` + ); + + const product = _.find( + warehouse.products, + (p) => p.productId === productId + ); + + if (product != null) { + this.log.info( + `Product sold count before decrease: ${product.soldCount} and we want to decrease ${count} products` + ); + + if (product.soldCount >= count) { + product.soldCount -= count; + return this.saveUpdated(warehouseId, product); + } else { + const errorMsg = + 'Request to decrease count of more products than available'; + + this.log.error({ + err: new Error(errorMsg), + product, + count, + }); + + throw new Error(errorMsg); + } + } else { + throw new Error( + `There is no such an product with the id ${productId} in the warehouse with the id ${warehouseId}` + ); + } + } + + /** + * Get Top sold products from given Warehouse + * + * @param {string} warehouseId warehouse from which to return products + * @param {number} quantity how many products to return + * @returns {Observable} + * @memberof WarehousesProductsService + */ + @observableListener() + getTopProducts( + warehouseId: string, + quantity: number + ): Observable { + return this.get(warehouseId).pipe( + map((warehouseProducts) => { + let topProducts = _.filter( + warehouseProducts, + (warehouseProduct) => warehouseProduct.soldCount > 0 + ); + + topProducts = _.orderBy(topProducts, ['soldCount'], ['desc']); + + return _.take(topProducts, quantity); + }) + ); + } + + @observableListener() + getProduct( + warehouseId: string, + warehouseProductId: string + ): Observable { + return this.warehousesService.get(warehouseId, true).pipe( + exhaustMap((warehouse) => { + if (warehouse === null) { + return throwError( () => + new Error( + `Warehouse with the id ${warehouseId} doesn't exist` + ) + ); + } else { + return of(warehouse); + } + }), + map((warehouse) => + warehouse.products.find((p) => p.id === warehouseProductId) + ) + ); + } + + @asyncListener() + async changeProductAvailability( + warehouseId: string, + productId: string, + isAvailable: boolean + ): Promise { + const warehouse = await this.warehousesService + .get(warehouseId) + .pipe(first()) + .toPromise(); + if (warehouse) { + const existedProduct = _.find( + warehouse.products, + (warehouseProduct) => warehouseProduct.productId === productId + ); + + if (existedProduct) { + existedProduct.isProductAvailable = isAvailable; + + return this.saveUpdated(warehouseId, existedProduct); + } else { + const errMsg = 'Cannot find product'; + this.log.error(new Error(errMsg)); + throw new Error(errMsg); + } + } else { + const errMsg = 'Cannot find warehouse'; + this.log.error(new Error(errMsg)); + throw new Error(errMsg); + } + } + @asyncListener() + async changeProductTakeaway( + warehouseId: string, + productId: string, + isTakeaway: boolean + ): Promise { + const warehouse = await this.warehousesService + .get(warehouseId) + .pipe(first()) + .toPromise(); + if (warehouse) { + const existedProduct = _.find( + warehouse.products, + (warehouseProduct) => warehouseProduct.productId === productId + ); + + if (existedProduct) { + existedProduct.isTakeaway = isTakeaway; + + return this.saveUpdated(warehouseId, existedProduct); + } else { + const errMsg = 'Cannot find product'; + this.log.error(new Error(errMsg)); + throw new Error(errMsg); + } + } else { + const errMsg = 'Cannot find warehouse'; + this.log.error(new Error(errMsg)); + throw new Error(errMsg); + } + } + @asyncListener() + async changeProductDelivery( + warehouseId: string, + productId: string, + isDelivery: boolean + ): Promise { + const warehouse = await this.warehousesService + .get(warehouseId) + .pipe(first()) + .toPromise(); + if (warehouse) { + const existedProduct = _.find( + warehouse.products, + (warehouseProduct) => warehouseProduct.productId === productId + ); + + if (existedProduct) { + existedProduct.isDeliveryRequired = isDelivery; + + return this.saveUpdated(warehouseId, existedProduct); + } else { + const errMsg = 'Cannot find product'; + this.log.error(new Error(errMsg)); + throw new Error(errMsg); + } + } else { + const errMsg = 'Cannot find warehouse'; + this.log.error(new Error(errMsg)); + throw new Error(errMsg); + } + } +} diff --git a/packages/core/src/services/warehouses/WarehousesService.ts b/packages/core/src/services/warehouses/WarehousesService.ts new file mode 100644 index 0000000..e6addd6 --- /dev/null +++ b/packages/core/src/services/warehouses/WarehousesService.ts @@ -0,0 +1,364 @@ +import Logger from 'bunyan'; +import * as _ from 'lodash'; +import { inject, injectable } from 'inversify'; +import { ProductsService } from '../products'; +import { createEverLogger } from '../../helpers/Log'; +import Warehouse from '@modules/server.common/entities/Warehouse'; +import { default as IWarehouse } from '@modules/server.common/interfaces/IWarehouse'; +import { IGeoLocationCreateObject } from '@modules/server.common/interfaces/IGeoLocation'; +import { Observable, of } from 'rxjs'; +import IWarehouseRouter, { + IWarehouseRegistrationInput, + IWarehouseLoginResponse, +} from '@modules/server.common/routers/IWarehouseRouter'; +import { + asyncListener, + observableListener, + routerName, + serialization, +} from '@pyro/io'; +import IService from '../IService'; +import { concat, exhaustMap, tap, first, map, switchMap } from 'rxjs/operators'; +import { DBService } from '@pyro/db-server'; +import { env } from '../../env'; +import { AuthService, AuthServiceFactory } from '../auth'; +import { v1 as uuid } from 'uuid'; +import IPagingOptions from '@modules/server.common/interfaces/IPagingOptions'; + +/** + * Warehouses Service + * + * @export + * @class WarehousesService + * @extends {DBService} + * @implements {IWarehouseRouter} + * @implements {IService} + */ +@injectable() +@routerName('warehouse') +export class WarehousesService extends DBService + implements IWarehouseRouter, IService { + public readonly DBObject: any = Warehouse; + + protected log: Logger = createEverLogger({ name: 'warehousesService' }); + + private readonly authService: AuthService; + + constructor( + @inject(ProductsService) + private readonly productsService: ProductsService, + @inject('Factory') + private readonly authServiceFactory: AuthServiceFactory + ) { + super(); + this.authService = this.authServiceFactory({ + role: 'warehouse', + Entity: Warehouse, + saltRounds: env.USER_PASSWORD_BCRYPT_SALT_ROUNDS, + }); + } + + /** + * Get Merchants + * + * @param {*} findInput + * @param {IPagingOptions} pagingOptions + * @returns + * @memberof WarehousesService + */ + async getMerchants(findInput: any, pagingOptions: IPagingOptions) { + const sortObj = {}; + if (pagingOptions.sort) { + sortObj[pagingOptions.sort.field] = pagingOptions.sort.sortBy; + } + + return this.Model.find({ + ...findInput, + isDeleted: { $eq: false }, + }) + .sort(sortObj) + .skip(pagingOptions.skip) + .limit(pagingOptions.limit) + .lean() + .exec(); + } + + /** + * Get all active merchants + * + * @param {boolean} [fullProducts=false] + * @returns {Observable} + * @memberof WarehousesService + */ + @observableListener() + getAllActive(fullProducts: boolean = false): Observable { + const callId = uuid(); + + this.log.info( + { callId, fullProducts }, + '.getAllActive(fullProducts) called' + ); + + return of(null).pipe( + concat(this.existence), + exhaustMap(() => this._getAllCurrentActive(fullProducts)), + tap({ + next: (warehouses) => { + this.log.info( + { callId, fullProducts, warehouses }, + '.getAllActive(fullProducts) emitted next value' + ); + }, + error: (err) => { + this.log.error( + { callId, fullProducts, err }, + '.getAllActive(fullProducts) thrown error!' + ); + }, + }) + ); + } + + /** + * Get all merchants + * + * @param {boolean} [fullProducts=false] + * @returns {Observable} + * @memberof WarehousesService + */ + @observableListener() + getAllStores(fullProducts: boolean = false): Observable { + const callId = uuid(); + + this.log.info( + { callId, fullProducts }, + '.getAllStores(fullProducts) called' + ); + + return of(null).pipe( + concat(this.existence), + exhaustMap(() => this._getAllStores(fullProducts)), + tap({ + next: (warehouses) => { + this.log.info( + { callId, fullProducts, warehouses }, + '.getAllStores(fullProducts) emitted next value' + ); + }, + error: (err) => { + this.log.error( + { callId, fullProducts, err }, + '.getAllStores(fullProducts) thrown error!' + ); + }, + }) + ); + } + + /** + * Create new Merchant + * + * @param {IWarehouseRegistrationInput} input + * @returns + * @memberof WarehousesService + */ + @asyncListener() + async register(input: IWarehouseRegistrationInput) { + const warehouse = await super.create({ + ...input.warehouse, + ...(input.password + ? { + hash: await this.authService.getPasswordHash( + input.password + ), + } + : {}), + }); + return warehouse; + } + + /** + * Update password for Merchant admin user + * + * @param {Warehouse['id']} id + * @param {{ current: string; new: string }} password + * @returns {Promise} + * @memberof WarehousesService + */ + @asyncListener() + async updatePassword( + id: Warehouse['id'], + password: { current: string; new: string } + ): Promise { + await this.throwIfNotExists(id); + await this.authService.updatePassword(id, password); + } + + /** + * Authenticate user in the Merchant app + * TODO: move to separate Auth service + * + * @param {string} username + * @param {string} password + * @returns {(Promise)} + * @memberof WarehousesService + */ + @asyncListener() + async login( + username: string, + password: string + ): Promise { + const res = await this.authService.login({ username }, password); + + if (!res || res.entity.isDeleted) { + return null; + } + + return { + warehouse: res.entity, + token: res.token, + }; + } + + /** + * Get Merchant + * + * @param {string} id + * @param {boolean} [fullProducts=true] + * @returns {(Observable)} + * @memberof WarehousesService + */ + @observableListener() + get(id: string, fullProducts = true): Observable { + if (!fullProducts) { + return super.get(id).pipe( + map(async (warehouse) => { + await this.throwIfNotExists(id); + return warehouse; + }), + switchMap((warehouse) => warehouse) + ); + } else { + return super + .get(id) + .pipe( + map(async (warehouse) => { + await this.throwIfNotExists(id); + return warehouse; + }), + switchMap((warehouse) => warehouse) + ) + .pipe(exhaustMap(() => this._get(id, true))); + } + } + + /** + * Set new location for existed warehouse + * Note: we support moving merchants. For example, some people/companies sell products on the "go". + * In such case, this method will be called periodically to update Merchant location in real-time + * + * @param {string} warehouseId + * @param {IGeoLocationCreateObject} geoLocation + * @returns {Promise} + * @memberof WarehousesService + */ + @asyncListener() + async updateGeoLocation( + warehouseId: string, + geoLocation: IGeoLocationCreateObject + ): Promise { + await this.throwIfNotExists(warehouseId); + return this.update(warehouseId, { geoLocation }); + } + + /** + * Set warehouse to available or not available + * (e.g. warehouse close or open etc) + * + * @param {string} warehouseId + * @param {boolean} isAvailable + * @returns {Promise} + * @memberof WarehousesService + */ + @asyncListener() + async updateAvailability( + warehouseId: string, + isAvailable: boolean + ): Promise { + await this.throwIfNotExists(warehouseId); + return this.update(warehouseId, { isActive: isAvailable }); + } + + /** + * Update Merchant details + * + * @param {Warehouse} warehouse + * @returns {Promise} + * @memberof WarehousesService + */ + @asyncListener() + async save( + @serialization((w: IWarehouse) => new Warehouse(w)) warehouse: Warehouse + ): Promise { + await this.throwIfNotExists(warehouse.id); + + warehouse = _.clone(warehouse); + + _.each(warehouse.products, (warehouseProduct) => { + warehouseProduct.product = warehouseProduct.product; + }); + + return this.update(warehouse.id, warehouse); + } + + /** + * Check if merchant record exists and not deleted. + * Throws exception if not found or deleted. + * + * @param {string} storeId + * @memberof WarehousesService + */ + async throwIfNotExists(storeId: string): Promise { + const store = await super.get(storeId).pipe(first()).toPromise(); + + if (!store || store.isDeleted) { + throw Error(`Store with id '${storeId}' does not exists!`); + } + } + + private async _get(id: string, fullProducts = false): Promise { + const _warehouse = (await this.Model.findById(id) + .populate(fullProducts ? 'products.product' : '') + .lean() + .exec()) as IWarehouse; + + return new Warehouse(_warehouse); + } + + private async _getAllCurrentActive( + fullProducts = false + ): Promise { + return _.map( + (await this.Model.find({ + isActive: true, + isDeleted: { $eq: false }, + }) + .populate(fullProducts ? 'products.product' : '') + .lean() + .exec()) as IWarehouse[], + (warehouse) => new Warehouse(warehouse) + ); + } + + private async _getAllStores(fullProducts = false): Promise { + return _.map( + (await this.Model.find({ + isDeleted: { $eq: false }, + }) + .populate(fullProducts ? 'products.product' : '') + .lean() + .exec()) as IWarehouse[], + (warehouse) => new Warehouse(warehouse) + ); + } +} diff --git a/packages/core/src/services/warehouses/WarehousesUsersService.ts b/packages/core/src/services/warehouses/WarehousesUsersService.ts new file mode 100644 index 0000000..2296f68 --- /dev/null +++ b/packages/core/src/services/warehouses/WarehousesUsersService.ts @@ -0,0 +1,76 @@ +import Logger from 'bunyan'; +import { inject, injectable, LazyServiceIdentifer } from 'inversify'; +import { createEverLogger } from '../../helpers/Log'; +import { observableListener, routerName, asyncListener } from '@pyro/io'; +import IService from '../IService'; +import { concat, Observable } from 'rxjs'; +import User from '@modules/server.common/entities/User'; +import { OrdersService } from '../orders'; +import Warehouse from '@modules/server.common/entities/Warehouse'; +import { WarehousesOrdersService } from './WarehousesOrdersService'; +import { exhaustMap, map, switchMap, tap, first } from 'rxjs/operators'; +import IWarehouseUsersRouter from '@modules/server.common/routers/IWarehouseUsersRouter'; +import { WarehousesService } from './WarehousesService'; + +/** + * Warehouses Customers Service + * + * @export + * @class WarehousesUsersService + * @implements {IService} + * @implements {IWarehouseUsersRouter} + */ +@injectable() +@routerName('warehouse-users') +export class WarehousesUsersService implements IService, IWarehouseUsersRouter { + protected log: Logger = createEverLogger({ + name: 'warehousesUsersService', + }); + + constructor( + @inject(new LazyServiceIdentifer(() => WarehousesOrdersService)) + private readonly warehousesOrdersService: WarehousesOrdersService, + @inject(new LazyServiceIdentifer(() => OrdersService)) + private readonly ordersService: OrdersService, + @inject(new LazyServiceIdentifer(() => WarehousesService)) + private readonly _warehousesService: WarehousesService + ) {} + + /** + * Returns the customers who made orders from the given Store + * @param {String} warehouseId + * @returns {Observable} + */ + @observableListener() + get(warehouseId: Warehouse['id']): Observable { + return concat( + null, + this.warehousesOrdersService.getExistence(warehouseId) + ).pipe( + exhaustMap(() => { + return this.ordersService.Model.distinct('user._id', { + warehouse: warehouseId, + isDeleted: { $eq: false }, + }) + .lean() + .exec(); + }), + map((users: User[]) => { + return users.map((u) => new User(u)); + }) + ); + } + + /** + * Returns the customers who made orders from from the given Store + * + * @param {Warehouse['id']} warehouseId + * @returns {Promise} + * @memberof WarehousesUsersService + */ + @asyncListener() + async getPromise(warehouseId: Warehouse['id']): Promise { + await this._warehousesService.throwIfNotExists(warehouseId); + return this.get(warehouseId).pipe(first()).toPromise(); + } +} diff --git a/packages/core/src/services/warehouses/index.ts b/packages/core/src/services/warehouses/index.ts new file mode 100644 index 0000000..51776c3 --- /dev/null +++ b/packages/core/src/services/warehouses/index.ts @@ -0,0 +1,5 @@ +export * from './WarehousesService'; +export * from './WarehousesOrdersService'; +export * from './WarehousesProductsService'; +export * from './WarehousesUsersService'; +export * from './WarehousesCarriersService'; diff --git a/packages/core/src/test/integration/GeoLocationWarehouses.spec.ts b/packages/core/src/test/integration/GeoLocationWarehouses.spec.ts new file mode 100644 index 0000000..6ae922d --- /dev/null +++ b/packages/core/src/test/integration/GeoLocationWarehouses.spec.ts @@ -0,0 +1,152 @@ +/* +import 'jest'; +import { servicesContainer } from '../../services/inversify.config'; +import { GeoLocationsWarehousesService } from '../../services/geo-locations'; +import * as faker from 'faker'; +import ForwardOrdersMethod from '@modules/server.common/enums/ForwardOrdersMethod'; +import { + Country, + default as GeoLocation +} from '@modules/server.common/entities/GeoLocation'; +import { WarehousesService } from '../../services/warehouses'; +import { ObjectID } from 'bson'; +import { IWarehouseRegistrationInput } from '@modules/server.common/routers/IWarehouseRouter'; +import mongoose from 'mongoose'; +import { env } from '../../env'; +import { randomCoordinatesNear } from '../../utils'; +import { first, shareReplay } from 'rxjs/operators'; +import { TestScheduler } from 'rxjs/Rx'; +import { getPlaceholditImgix } from '@modules/server.common/utils'; + +jest.setTimeout(30000); +process.env.NODE_ENV = 'test'; + +function geoLocationFixture([lng, lat]: [number, number]): GeoLocation { + return new GeoLocation({ + _id: new ObjectID().toHexString(), + _createdAt: new Date(), + _updatedAt: new Date(), + city: faker.address.city(), + postcode: faker.address.zipCode(), + streetAddress: faker.address.streetAddress(), + house: faker.datatype.number(199).toString(), + countryId: faker.datatype.number(1) as Country, + loc: { + type: 'Point', + coordinates: [lng, lat] + } + }); +} + +function warehouseFixture([lng, lat]: [ + number, + number +]): IWarehouseRegistrationInput { + const warehouseName = faker.company.companyName(); + + return { + password: faker.internet.password(), + warehouse: { + name: `Restaurant ${warehouseName}`, + isActive: true, + username: faker.internet.userName(), + logo: getPlaceholditImgix(500, 500, 80, warehouseName), + contactEmail: faker.internet.email(), + contactPhone: faker.phone.phoneNumber(), + ordersEmail: null, + ordersPhone: null, + forwardOrdersUsing: [ForwardOrdersMethod.Unselected], + isManufacturing: true, + isCarrierRequired: true, + usedCarriersIds: [], + geoLocation: geoLocationFixture([lng, lat]) + } + }; +} + +describe('GeoLocationWarehouses', () => { + beforeAll(async () => { + const mongoUrl = env.TESTING_DB_URI; + + (mongoose as any).Promise = Promise; + + const options = { + auto_reconnect: true, + reconnectTries: Number.MAX_VALUE, + reconnectInterval: 1000 + }; + + await mongoose.connect(mongoUrl, options); + console.log(`MongoDB successfully connected to ${mongoUrl}`); + }); + + beforeEach(async () => { + servicesContainer.snapshot(); + }); + + afterEach(async () => { + for (const collection of Object.values( + mongoose.connection.collections + )) { + await collection.drop(); + } + servicesContainer.restore(); + }); + + afterAll(async () => { + await mongoose.disconnect(); + }); + + describe('.get(conditions)', () => { + const geoLocationsWarehousesService = servicesContainer.get< + GeoLocationsWarehousesService + >(GeoLocationsWarehousesService); + const warehousesService = servicesContainer.get( + WarehousesService + ); + + it('Finds nearby warehouses and tracks their changes', async () => { + const coordinates: [number, number] = [10, 10]; // lng, lat + const geoLocation = geoLocationFixture(coordinates); + + const testScheduler = new TestScheduler((actual, expected) => { + expect(actual).toEqual(expected); + }); + + // This test will actually run *synchronously* + /*testScheduler.run(({ cold, expectObservable }) => { + const next = { + handle: () => cold('-a-b-c--------|') + }; + const output = interceptor.intercept(null, next); + + const expected = ' ----------c---|'; // or whatever your interceptor does + expectObservable(output).toBe(expected); + }); + + const createNearbyWarehouse = async () => { + await warehousesService.register( + warehouseFixture( + randomCoordinatesNear(coordinates, GeoLocationsWarehousesService.TrackingDistance) + ) + ); + }; + + await createNearbyWarehouse(); + + const warehouses$ = geoLocationsWarehousesService.get(geoLocation).pipe(shareReplay()); + + const warehouses = await warehouses$.pipe(first()).toPromise(); + + expect(warehouses).toHaveLength(1); + + await createNearbyWarehouse(); + + expect(warehouses).toHaveLength(1);*/ + +/* + + }); + }); +}); +*/ diff --git a/packages/core/src/test/integration/GeoLocations.spec.ts b/packages/core/src/test/integration/GeoLocations.spec.ts new file mode 100644 index 0000000..a1dc36f --- /dev/null +++ b/packages/core/src/test/integration/GeoLocations.spec.ts @@ -0,0 +1,11 @@ +/* +import { GeoLocationsService } from '../../services/geo-locations'; + +describe('GeoLocationsService', () => { + describe('.getAddressByCoordinatesUsingArcGIS(conditions)', () => { + it('Finds location in Israel', async () => { + const geoService = new GeoLocationsService(); + }); + }); +}); +*/ diff --git a/packages/core/src/test/loggerMock.ts b/packages/core/src/test/loggerMock.ts new file mode 100644 index 0000000..b5dda30 --- /dev/null +++ b/packages/core/src/test/loggerMock.ts @@ -0,0 +1,6 @@ +import bunyan from 'bunyan'; + +export const loggerMock = bunyan.createLogger({ + name: 'testingLoggerMock', + streams: [], +}); diff --git a/packages/core/src/test/setup.js b/packages/core/src/test/setup.js new file mode 100644 index 0000000..b6087e9 --- /dev/null +++ b/packages/core/src/test/setup.js @@ -0,0 +1,52 @@ +// import mongoose from 'mongoose'; +// import { env } from '../env'; +// // import MongodbMemoryServer from 'mongodb-memory-server'; +// +// process.env.NODE_ENV = 'test'; +// +// jest.setTimeout(30000); +// +// export namespace testing { +// +// export let mongod; +// +// export async function ensureConnected() { +// +// /*mongod = new MongodbMemoryServer({ +// instance: { +// dbName: 'jest' +// }, +// debug: true +// }); +// +// const mongoUrl = await mongod.getConnectionString();*/ +// +// const mongoUrl = env.TESTING_DB_URI; +// +// (mongoose as any).Promise = Promise; +// +// const options = { +// auto_reconnect: true, +// reconnectTries: Number.MAX_VALUE, +// reconnectInterval: 1000 +// }; +// +// await mongoose.connect(mongoUrl, options); +// console.log(`MongoDB successfully connected to ${mongoUrl}`); +// } +// +// export async function clearDb() { +// for (const collection of Object.values(mongoose.connection.collections)) { +// await collection.drop(); +// } +// // connection.db.dropDatabase(done); +// } +// +// export async function disconnect() { +// if (mongoose.connection) { +// await mongoose.disconnect(); +// await mongod.stop(); +// } +// } +// +// } diff --git a/packages/core/src/test/setup.ts b/packages/core/src/test/setup.ts new file mode 100644 index 0000000..b6087e9 --- /dev/null +++ b/packages/core/src/test/setup.ts @@ -0,0 +1,52 @@ +// import mongoose from 'mongoose'; +// import { env } from '../env'; +// // import MongodbMemoryServer from 'mongodb-memory-server'; +// +// process.env.NODE_ENV = 'test'; +// +// jest.setTimeout(30000); +// +// export namespace testing { +// +// export let mongod; +// +// export async function ensureConnected() { +// +// /*mongod = new MongodbMemoryServer({ +// instance: { +// dbName: 'jest' +// }, +// debug: true +// }); +// +// const mongoUrl = await mongod.getConnectionString();*/ +// +// const mongoUrl = env.TESTING_DB_URI; +// +// (mongoose as any).Promise = Promise; +// +// const options = { +// auto_reconnect: true, +// reconnectTries: Number.MAX_VALUE, +// reconnectInterval: 1000 +// }; +// +// await mongoose.connect(mongoUrl, options); +// console.log(`MongoDB successfully connected to ${mongoUrl}`); +// } +// +// export async function clearDb() { +// for (const collection of Object.values(mongoose.connection.collections)) { +// await collection.drop(); +// } +// // connection.db.dropDatabase(done); +// } +// +// export async function disconnect() { +// if (mongoose.connection) { +// await mongoose.disconnect(); +// await mongod.stop(); +// } +// } +// +// } diff --git a/packages/core/src/test/unit/pyro/pyro.db.server-test.ts b/packages/core/src/test/unit/pyro/pyro.db.server-test.ts new file mode 100644 index 0000000..f75585b --- /dev/null +++ b/packages/core/src/test/unit/pyro/pyro.db.server-test.ts @@ -0,0 +1,183 @@ +/*import { assert, expect } from 'chai'; +import { servicesContainer } from '../../../services/inversify.config'; +import { DBService, getModel, IDBService } from '@pyro/db-server'; +import { DBCreateObject, DBObject, DBRawObject, PyroObjectId, Schema, Types } from '@pyro/db'; +import Logger from 'bunyan'; +import mongoose from 'mongoose'; +import * as _ from 'lodash'; +import { loggerMock } from '../../loggerMock'; +import { take, toArray } from 'rxjs/operators'; + +interface CreateObjectMock extends DBCreateObject { + stringField: string; + numberField?: number; + booleanField?: boolean; +} + +interface RawObjectMock extends CreateObjectMock, DBRawObject { + _id: PyroObjectId; + + numberField: number; +} + +class DBObjectMock extends DBObject implements RawObjectMock { + @Types.String() stringField: string; + @Types.Number(5) numberField: number; + @Schema({ required: false, type: Boolean }) booleanField?: boolean; +} + +class DBServiceMock extends DBService { + protected log: Logger = loggerMock; + + get DBObject() { + return DBObjectMock; + } +} + +type IDBServiceMock = IDBService; + +const createObjectMock: CreateObjectMock = { + stringField: 'hello', + numberField: 100, + booleanField: false +}; + +describe('DBService', () => { + + beforeEach(() => { + servicesContainer.snapshot(); + servicesContainer.bind('IDBServiceMock').to(DBServiceMock); + }); + + afterEach(() => { + servicesContainer.restore(); + }); + + const Model = getModel(DBObjectMock); + + describe('.create(createObject)', () => { + + it('creates object', async () => { + const dbService = servicesContainer.get('IDBServiceMock'); + + await dbService.create(createObjectMock); + const objs: RawObjectMock[] = await Model.find(createObjectMock).exec(); + + expect(_.some(objs, createObjectMock)).to.equal(true); + + servicesContainer.bind(Model); + + }); + }); + + describe('.remove(id)', () => { + + it('removes object by id', async () => { + const dbService = servicesContainer.get('IDBServiceMock'); + + const id = (await Model.create(createObjectMock))._id.toString(); + dbService.remove(id); + + const objs: RawObjectMock[] = await Model.find(createObjectMock).exec(); + expect(objs).to.be.empty; + }); + }); + + describe('.removeAll()', () => { + it('removes all the objects', async () => { + const dbService = servicesContainer.get('IDBServiceMock'); + + await Model.create(createObjectMock); + await Model.create(createObjectMock); + + dbService.removeAll(); + + const objs: RawObjectMock[] = await Model.find(createObjectMock).exec(); + expect(objs).to.be.empty; + }); + }); + + describe('.get(id)', () => { + it('watches changes', async () => { + const dbService = servicesContainer.get('IDBServiceMock'); + + const _id = new mongoose.Types.ObjectId(); + const id = _id.toString(); + + const changesPromise = dbService.get(id).pipe(take(4), toArray()).toPromise(); + + await dbService.create({ + ...createObjectMock, + _id: _id + }); + + let updateObject = { + stringField: 'new value' + }; + + await dbService.update(id, updateObject); + + await dbService.remove(id); + + const changes: (DBObjectMock | null)[] = await changesPromise; + + assert.isNull(changes[ 0 ]); + + assert.isNotNull(changes[ 1 ]); + assert.isTrue( + _.some([ changes[ 1 ] ], createObjectMock) + ); + + assert.isNotNull(changes[ 2 ]); + assert.isTrue( + _.some([ changes[ 2 ] ], { + ...createObjectMock, + ...updateObject + }) + ); + + assert.isNull(changes[ 3 ]); + + }); + }); + + describe('.find(conditions)', () => { + it('finds single object', async () => { + + const dbService = servicesContainer.get('IDBServiceMock'); + + await Model.create(createObjectMock); + + let objs = await dbService.find(createObjectMock); + assert.isTrue(objs.length == 1); + assert.isTrue( + _.some(objs, createObjectMock) + ); + + }); + + it('finds multiple objects', async () => { + + const dbService = servicesContainer.get('IDBServiceMock'); + + await Model.create(createObjectMock); + + const anotherCreateObjectMock = { + ...createObjectMock, + stringField: 'different value' + }; + + await Model.create(anotherCreateObjectMock); + + let { stringField, ...findObject } = createObjectMock; + let objs = await dbService.find(findObject); + + assert.lengthOf(objs, 2); + assert.isTrue(_.some(objs, createObjectMock)); + assert.isTrue(_.some(objs, anotherCreateObjectMock)); + + }); + }); + +}); +*/ diff --git a/packages/core/src/test/unit/services/InvitesService-test.ts b/packages/core/src/test/unit/services/InvitesService-test.ts new file mode 100644 index 0000000..502b4a8 --- /dev/null +++ b/packages/core/src/test/unit/services/InvitesService-test.ts @@ -0,0 +1,77 @@ +/*import { expect } from 'chai'; +import { servicesContainer } from '../../../services/inversify.config'; +import { InvitesService } from '../../../services/invites'; +import { MongoClient } from 'mongodb'; +import mongoose from 'mongoose'; +import { env } from '../../../env'; + +describe('InvitesService', () => { + + let connection; + let db; + + beforeEach(async () => { + connection = await MongoClient.connect(global[ '__MONGO_URI__' ]); + db = await connection.db(global[ '__MONGO_DB_NAME__' ]); + + mongoose.connect(env.TESTING_DB_URI, { + autoReconnect: true, + reconnectTries: Number.MAX_VALUE, + poolSize: 50, + connectTimeoutMS: 10000 + } as any, (err) => { + if (err != null) { + this.log.error(err); + } + }); + + servicesContainer.snapshot(); + }); + + afterEach(() => { + servicesContainer.restore(); + }); + + afterAll(async () => { + await connection.close(); + await db.close(); + }); + + describe('.findOne(conditions)', () => { + it('should find fakeData', async () => { + + const invitesService: InvitesService = servicesContainer.get(InvitesService); + + await invitesService.create({ + geoLocation: { + city: 'אשדוד', + postcode: '77452', + streetAddress: 'העצמאות', + house: '38', + countryId: 1, + loc: { + type: 'Point', + coordinates: [ + 31.7580976, + 34.6359946 + ] + } + }, + apartment: '3', + code: '2446' + }); + + const invite = await invitesService.findOne({ + 'geoLocation.city': 'אשדוד', + 'geoLocation.streetAddress': 'העצמאות', + 'geoLocation.house': '38', + 'geoLocation.countryId': 1, + 'apartment': '3' + }); + + expect(invite).to.not.be.null; + }); + }); + +}); +*/ diff --git a/packages/core/src/utils.ts b/packages/core/src/utils.ts new file mode 100644 index 0000000..463d477 --- /dev/null +++ b/packages/core/src/utils.ts @@ -0,0 +1,50 @@ +// TODO: move all utils into shared/core + +import { Observable } from 'rxjs'; +import fs from 'fs'; + +export function observeFile(fileName: string): Observable { + return Observable.create((observer) => { + const fetchTranslations = () => { + fs.readFile(fileName, 'utf-8', (err, content) => { + observer.next(content); + + if (err) { + observer.error(err); + } + }); + }; + + fetchTranslations(); + + fs.watchFile(fileName, fetchTranslations); + + return () => { + fs.unwatchFile(fileName, fetchTranslations); + }; + }); +} + +/** + * gee + * @param {[number, number]} point - around which point + * @param {number} radius - in meters + * @returns {[number]} + */ +export function randomCoordinatesNear( + [longitude, latitude]: [number, number], + radius: number +): [number, number] { + const r = 100 / 111300; // = 100 meters + const y0 = longitude; + const x0 = latitude; + const u = Math.random(); + const v = Math.random(); + const w = r * Math.sqrt(u); + const t = 2 * Math.PI * v; + const x = w * Math.cos(t); + const y1 = w * Math.sin(t); + const x1 = x / Math.cos(y0); + + return [y0 + y1, x0 + x1]; +} diff --git a/packages/core/tsconfig.build.json b/packages/core/tsconfig.build.json new file mode 100644 index 0000000..ea6be8e --- /dev/null +++ b/packages/core/tsconfig.build.json @@ -0,0 +1,3 @@ +{ + "extends": "./tsconfig.json" +} diff --git a/packages/core/tsconfig.json b/packages/core/tsconfig.json new file mode 100644 index 0000000..5dbf547 --- /dev/null +++ b/packages/core/tsconfig.json @@ -0,0 +1,23 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "baseUrl": "./src", + "outDir": "./build", + "rootDir": "./src", + "types": ["node", "reflect-metadata", "jest"], + "esModuleInterop": true, + "incremental": false, + "experimentalDecorators": true, + "paths": { + "@pyro/*": [ + "@pyro/*", + "../../../node_modules/@ever-platform/common/src/@pyro/*" + ], + "@modules/server.common/*": [ + "@modules/server.common/*", + "../../../node_modules/@ever-platform/common/src/*" + ] + } + }, + "include": ["./src/**/*.ts", "../../common/**/*.ts"] +} diff --git a/packages/core/tslint.json b/packages/core/tslint.json new file mode 100644 index 0000000..c3e8ef5 --- /dev/null +++ b/packages/core/tslint.json @@ -0,0 +1,122 @@ +{ + "extends": ["tslint:latest", "tslint-config-prettier"], + "rulesDirectory": [], + "linterOptions": { + "exclude": ["node_modules", "dist"] + }, + "rules": { + "no-implicit-dependencies": false, + "no-submodule-imports": false, + "trailing-comma": [ + false, + { + "multiline": "always", + "singleline": "never" + } + ], + "interface-name": [false, "always-prefix"], + "arrow-return-shorthand": true, + "callable-types": true, + "class-name": true, + "comment-format": [true, "check-space"], + "forin": true, + "import-blacklist": [true], + "ordered-imports": false, + "interface-over-type-literal": true, + "label-position": true, + "max-line-length": [true, 120], + "member-access": false, + "no-arg": true, + "no-console": [false], + "no-construct": true, + "no-debugger": true, + "no-duplicate-super": true, + "no-empty": false, + "no-empty-interface": false, + "no-eval": true, + "no-misused-new": true, + "no-non-null-assertion": false, + "no-shadowed-variable": true, + "no-string-literal": false, + "no-string-throw": true, + "no-switch-case-fall-through": true, + "no-unnecessary-initializer": true, + "no-use-before-declare": true, + "no-var-keyword": true, + "object-literal-sort-keys": false, + "prefer-const": true, + "object-literal-key-quotes": false, + "no-angle-bracket-type-assertion": false, + "member-ordering": false, + "no-consecutive-blank-lines": false, + "radix": true, + "semicolon": [true, "always"], + "triple-equals": [true, "allow-null-check"], + "typeof-compare": true, + "unified-signatures": true, + "variable-name": false, + "directive-selector": [true, "attribute", "ngx", "camelCase"], + "component-selector": [ + true, + "element", + [ + "e-cu", + "ngx", + "ea", + "es" + ], + "kebab-case" + ], + "no-attribute-parameter-decorator": true, + "no-forward-ref": true, + "no-input-rename": true, + "no-output-rename": true, + "only-arrow-functions": false, + "pipe-naming": [true, "camelCase", "my"], + "use-host-property-decorator": true, + "ban": [ + true, + "eval", + "fit", + "fdescribe", + { + "name": "$", + "message": "please don't" + } + ], + "max-classes-per-file": [false], + "import-destructuring-spacing": true, + "invoke-injectable": true, + "no-access-missing-member": true, + "templates-use-public": true, + "use-input-property-decorator": true, + "use-life-cycle-interface": true, + "use-output-property-decorator": true, + "use-pipe-transform-interface": true, + "quotemark": [true, "single", "avoid-escape"], + "eofline": true, + "import-spacing": true, + "indent": [true, "tabs"], + "no-trailing-whitespace": true, + "one-line": [false], + "typedef-whitespace": [ + true, + { + "call-signature": "nospace", + "index-signature": "nospace", + "parameter": "nospace", + "property-declaration": "nospace", + "variable-declaration": "nospace" + } + ], + "whitespace": [ + true, + "check-branch", + "check-decl", + "check-operator", + "check-separator", + "check-type" + ], + "no-namespace": false + } +} diff --git a/packages/core/webpack.config.js b/packages/core/webpack.config.js new file mode 100644 index 0000000..e06ed51 --- /dev/null +++ b/packages/core/webpack.config.js @@ -0,0 +1,87 @@ +const webpack = require('webpack'); +const path = require('path'); +const nodeExternals = require('webpack-node-externals'); +// const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin'); + +const isProduction = + typeof process.env.NODE_ENV !== 'undefined' && + process.env.NODE_ENV === 'production'; +const mode = isProduction ? 'production' : 'development'; +const devtool = isProduction ? false : 'inline-source-map'; + +console.log('The custom config is used now'); + +module.exports = { + stats: { + env: true, + errors: true, + errorDetails: true, + errorStack: true, + outputPath: true, + publicPath: true, + entrypoints: true, + chunkGroups: true, + chunks: true, + modules: true, + moduleTrace: true, + children: true, + logging: 'verbose', + loggingTrace: true, + assets: true, + }, + entry: ['./src/nest-bootstrap.ts'], + watch: false, + optimization: { + concatenateModules: false, + minimize: false, + }, + externals: [ + nodeExternals({ + modulesDir: path.resolve(__dirname, '../../node_modules'), + }), + ], + target: 'node', + module: { + rules: [ + { + test: /\.graphql?$/, + loader: 'webpack-graphql-loader', + }, + { + test: /\.mjs$/, + include: /node_modules/, + type: 'javascript/auto', + }, + { + test: /\.(ts|tsx)?$/, + loader: 'ts-loader', + options: { transpileOnly: true, allowTsInNodeModules: true } + }, + ], + }, + mode, + devtool, + resolve: { + extensions: ['.tsx', '.ts', '.js'], + symlinks: false, + alias: { + '@modules/server.common': path.resolve( + '../../node_modules/@ever-platform/common/src' + ), + '@pyro/io': path.resolve('./src/@pyro/io'), + '@pyro/db-server': path.resolve('./src/@pyro/db-server'), + '@pyro': path.resolve( + '../../node_modules/@ever-platform/common/src/@pyro/' + ), + }, + }, + plugins: [ + new webpack.HotModuleReplacementPlugin(), + // new webpack.WatchIgnorePlugin([/\.js$/, /\.d\.ts$/]), + // new ForkTsCheckerWebpackPlugin({ tslint: true }) + ], + output: { + path: path.join(__dirname, 'dist'), + filename: 'server.js', + }, +}; diff --git a/packages/merchant-tablet-ionic/.dockerignore b/packages/merchant-tablet-ionic/.dockerignore new file mode 100644 index 0000000..be6d4c4 --- /dev/null +++ b/packages/merchant-tablet-ionic/.dockerignore @@ -0,0 +1,11 @@ +.git +.gitignore +.gitmodules +README.md +docker +node_modules +tmp +build +dist +.env +www diff --git a/packages/merchant-tablet-ionic/.ebextensions/logging.config b/packages/merchant-tablet-ionic/.ebextensions/logging.config new file mode 100644 index 0000000..1180eb3 --- /dev/null +++ b/packages/merchant-tablet-ionic/.ebextensions/logging.config @@ -0,0 +1,8 @@ +files: + "/opt/elasticbeanstalk/tasks/bundlelogs.d/merchants.conf": + content: | + /tmp/logs* + + "/opt/elasticbeanstalk/tasks/taillogs.d/merchants.conf": + content: | + /tmp/logs/*.log diff --git a/packages/merchant-tablet-ionic/.ebextensions/nodecommand.config b/packages/merchant-tablet-ionic/.ebextensions/nodecommand.config new file mode 100644 index 0000000..82795dd --- /dev/null +++ b/packages/merchant-tablet-ionic/.ebextensions/nodecommand.config @@ -0,0 +1,6 @@ +option_settings: + - namespace: aws:elasticbeanstalk:container:nodejs + option_name: NodeCommand + value: "npm start:prod" + - option_name: NODE_ENV + value: production \ No newline at end of file diff --git a/packages/merchant-tablet-ionic/.ebignore b/packages/merchant-tablet-ionic/.ebignore new file mode 100644 index 0000000..f373b97 --- /dev/null +++ b/packages/merchant-tablet-ionic/.ebignore @@ -0,0 +1,4 @@ +node_modules/ +platforms/ +plugins/ +backups/ \ No newline at end of file diff --git a/packages/merchant-tablet-ionic/.elasticbeanstalk/config.yml b/packages/merchant-tablet-ionic/.elasticbeanstalk/config.yml new file mode 100644 index 0000000..c254c60 --- /dev/null +++ b/packages/merchant-tablet-ionic/.elasticbeanstalk/config.yml @@ -0,0 +1,14 @@ +branch-defaults: + master: + environment: ever-merchants-env +environment-defaults: + ever-api-env: + branch: null + repository: null +global: + application_name: merchants + default_ec2_keyname: ever + default_platform: 64bit Amazon Linux 2016.03 v2.1.3 running Node.js + default_region: us-east-1 + profile: null + sc: git diff --git a/packages/merchant-tablet-ionic/.env.template b/packages/merchant-tablet-ionic/.env.template new file mode 100644 index 0000000..10319c7 --- /dev/null +++ b/packages/merchant-tablet-ionic/.env.template @@ -0,0 +1,46 @@ +NODE_ENV=development +PORT=4202 +LOGS_PATH=./tmp/logs +KEYMETRICS_SECRET_KEY=############### +KEYMETRICS_PUBLIC_KEY=############### +KEYMETRICS_MACHINE_NAME=EverMerchants +PM2_APP_NAME=EverMerchants + +SERVICES_ENDPOINT=http://localhost:5500 +HTTPS_SERVICES_ENDPOINT=https://localhost:2087 +GQL_ENDPOINT=http://localhost:8443/graphql +GQL_SUBSCRIPTIONS_ENDPOINT=ws://localhost:2086/subscriptions + +APP_VERSION=0.2.0 + +API_FILE_UPLOAD_URL=https://api.cloudinary.com/v1_1/evereq/upload + +DEFAULT_LOGIN_USERNAME=hut_pizza +DEFAULT_LOGIN_PASSWORD=123456 + +LOGIN_LOGO=assets/imgs/ever-logo.svg +NO_INTERNET_LOGO=assets/imgs/logo.png + +COMPANY_NAME=Ever Co. LTD +APP_NAME=Ever Merchant + +GOOGLE_MAPS_API_KEY= + +GOOGLE_ANALYTICS_API_KEY= +FAKE_UUID=c2360292-3b42-456d-ac37-1cbd9429d4d1 +MIXPANEL_API_KEY= + +WEB_MEMORY=4096 +WEB_CONCURRENCY=1 + +MAP_MERCHANT_ICON_LINK=http://maps.google.com/mapfiles/kml/pal3/icon21.png + +MAP_USER_ICON_LINK=http://maps.google.com/mapfiles/kml/pal3/icon48.png + +MAP_CARRIER_ICON_LINK=http://maps.google.com/mapfiles/kml/pal4/icon54.png + +DEFAULT_LANGUAGE=en + +# For maintenance micro service. Ever Demand maintenance API URL: https://maintenance.ever.co/status +SETTINGS_APP_TYPE=merchant-tablet +SETTINGS_MAINTENANCE_API_URL= \ No newline at end of file diff --git a/packages/merchant-tablet-ionic/.gitignore b/packages/merchant-tablet-ionic/.gitignore new file mode 100644 index 0000000..ddc2975 --- /dev/null +++ b/packages/merchant-tablet-ionic/.gitignore @@ -0,0 +1,47 @@ +/.angular/cache +**/*.js +**/*.js.map +**/*.d.ts +**/*.d.ts.map +*~ +*.sw[mnpcod] +*.log +*.tmp +*.tmp.* +log.txt +*.sublime-project +*.sublime-workspace +.vscode/ +npm-debug.log* +.idea/ +.sourcemaps/ +.sass-cache/ +.tmp/ +.env +.versions/ +coverage/ +dist/ +node_modules/ +tmp/ +temp/ +hooks/ +platforms/ +plugins/ +plugins/android.json +plugins/ios.json +www/ +$RECYCLE.BIN/ +google-services.json +GoogleService-Info.plist +.DS_Store +Thumbs.db +UserInterfaceState.xcuserstate + +# Do not store autogenerated docs in repo +/docs + +# environment files +.env +.env.prod +/src/environments/environment.ts +/src/environments/environment.prod.ts diff --git a/packages/merchant-tablet-ionic/.io-config.json b/packages/merchant-tablet-ionic/.io-config.json new file mode 100644 index 0000000..d813362 --- /dev/null +++ b/packages/merchant-tablet-ionic/.io-config.json @@ -0,0 +1,3 @@ +{ + "app_id": "4e9f4799" +} diff --git a/packages/merchant-tablet-ionic/CREDITS.md b/packages/merchant-tablet-ionic/CREDITS.md new file mode 100644 index 0000000..b972515 --- /dev/null +++ b/packages/merchant-tablet-ionic/CREDITS.md @@ -0,0 +1,9 @@ +# CREDITS + +This application uses Open Source components and 3rd party libraries, which are licensed under their own respective Open-Source licenses. +You can find the links to source code of their open source projects along with license information below. +We acknowledge and are grateful to these developers for their contributions to open source. + +- Project: Ionic https://github.com/ionic-team/ionic + Copyright 2015-present Drifty Co. + License (MIT) https://github.com/ionic-team/ionic/blob/master/LICENSE diff --git a/packages/merchant-tablet-ionic/GNU-AGPL-3.0.txt b/packages/merchant-tablet-ionic/GNU-AGPL-3.0.txt new file mode 100644 index 0000000..dba13ed --- /dev/null +++ b/packages/merchant-tablet-ionic/GNU-AGPL-3.0.txt @@ -0,0 +1,661 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU Affero General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +. diff --git a/packages/merchant-tablet-ionic/GoogleService-Info.template.plist b/packages/merchant-tablet-ionic/GoogleService-Info.template.plist new file mode 100644 index 0000000..07b00f8 --- /dev/null +++ b/packages/merchant-tablet-ionic/GoogleService-Info.template.plist @@ -0,0 +1,40 @@ + + + + + AD_UNIT_ID_FOR_BANNER_TEST + ########################## + AD_UNIT_ID_FOR_INTERSTITIAL_TEST + ########################## + CLIENT_ID + ########################## + REVERSED_CLIENT_ID + ########################## + API_KEY + ########################## + GCM_SENDER_ID + ########################## + PLIST_VERSION + 1 + BUNDLE_ID + co.ever.merchants + PROJECT_ID + ever-merchants + STORAGE_BUCKET + ever-merchants.appspot.com + IS_ADS_ENABLED + + IS_ANALYTICS_ENABLED + + IS_APPINVITE_ENABLED + + IS_GCM_ENABLED + + IS_SIGNIN_ENABLED + + GOOGLE_APP_ID + ########################## + DATABASE_URL + ########################## + + \ No newline at end of file diff --git a/packages/merchant-tablet-ionic/LICENSE.md b/packages/merchant-tablet-ionic/LICENSE.md new file mode 100644 index 0000000..8708dfc --- /dev/null +++ b/packages/merchant-tablet-ionic/LICENSE.md @@ -0,0 +1,41 @@ +# License + +Copyright © 2016-present, Ever Co. LTD. All rights reserved. + +This software is available under different licenses + +### _Ever Platform Community Edition_ License for Merchant Tablet App and Merchant Website + +If you decide to choose the Ever Platform Community Edition License for Merchant Tablet App and Merchant Website, you must comply with the following terms: + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License, version 3, +as published by the Free Software Foundation. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . + +[GNU Affero General Public License v3.0](https://www.gnu.org/licenses/agpl-3.0.txt) + +### _Ever Platform Enterprise_ License + +Alternatively, commercial versions of the software must be used in accordance with the terms and conditions of separate written agreement between you and Ever Co. LTD. + +For more information about Ever Platform Enterprise License please contact . + +#### The default Ever Platform license, without a valid Ever Platform Enterprise License agreement, is the Ever Platform Community Edition License. + +## Credits + +Please see [CREDITS.md](CREDITS.md) file for a list of libraries and software included in this program and information about licenses. + +## Trademarks + +**Ever**® is a registered trademark of [Ever Co. LTD](https://ever.co). +The trademark may only be used with the written permission of Ever Co. LTD. and may not be used to promote or otherwise market competitive products or services. +All other brand and product names are trademarks, registered trademarks or service marks of their respective holders. diff --git a/packages/merchant-tablet-ionic/README.md b/packages/merchant-tablet-ionic/README.md new file mode 100644 index 0000000..aea5613 --- /dev/null +++ b/packages/merchant-tablet-ionic/README.md @@ -0,0 +1 @@ +# Ever Demand Merchant Tablet App (Ionic version) diff --git a/packages/merchant-tablet-ionic/angular.json b/packages/merchant-tablet-ionic/angular.json new file mode 100644 index 0000000..b909fd7 --- /dev/null +++ b/packages/merchant-tablet-ionic/angular.json @@ -0,0 +1,185 @@ +{ + "$schema": "./node_modules/@angular/cli/lib/config/schema.json", + "version": 1, + "defaultProject": "app", + "newProjectRoot": "projects", + "projects": { + "app": { + "root": "", + "sourceRoot": "src", + "projectType": "application", + "prefix": "e-cu", + "schematics": {}, + "architect": { + "build": { + "builder": "@angular-builders/custom-webpack:browser", + "options": { + "customWebpackConfig": { + "path": "./extra-webpack.config.js" + }, + "progress": false, + "outputPath": "www", + "index": "src/index.html", + "main": "src/main.ts", + "polyfills": "src/polyfills.ts", + "tsConfig": "src/tsconfig.app.json", + "assets": [ + { + "glob": "**/*", + "input": "src/assets", + "output": "assets" + }, + { + "glob": "**/*.svg", + "input": "../../node_modules/ionicons/dist/ionicons/svg", + "output": "./svg" + }, + { + "glob": "**/*.svg", + "input": "../../node_modules/@ionic/core/dist/ionic/svg", + "output": "./svg" + }, + "src/manifest.json" + ], + "allowedCommonJsDependencies": [ + "uuid", + "underscore.string", + "qrcode", + "ngx-masonry" + ], + "styles": [ + "../../node_modules/bootstrap/dist/css/bootstrap.min.css", + "../../node_modules/font-awesome/scss/font-awesome.scss", + { + "input": "src/theme/variables.scss" + }, + { + "input": "src/global.scss" + } + ], + "scripts": [] + }, + "configurations": { + "production": { + "fileReplacements": [ + { + "replace": "src/environments/environment.ts", + "with": "src/environments/environment.prod.ts" + } + ], + "optimization": true, + "outputHashing": "all", + "sourceMap": false, + "extractCss": true, + "namedChunks": false, + "aot": false, + "extractLicenses": true, + "vendorChunk": false, + "buildOptimizer": false, + "serviceWorker": true + } + } + }, + "serve": { + "builder": "@angular-builders/custom-webpack:dev-server", + "options": { + "browserTarget": "app:build" + }, + "configurations": { + "production": { + "browserTarget": "app:build:production" + } + } + }, + "extract-i18n": { + "builder": "@angular-devkit/build-angular:extract-i18n", + "options": { + "browserTarget": "app:build" + } + }, + "test": { + "builder": "@angular-devkit/build-angular:karma", + "options": { + "main": "src/test.ts", + "polyfills": "src/polyfills.ts", + "tsConfig": "src/tsconfig.spec.json", + "karmaConfig": "src/karma.conf.js", + "styles": [ + "../../node_modules/bootstrap/dist/css/bootstrap.min.css", + "../../node_modules/font-awesome/scss/font-awesome.scss", + "src/theme/variables.scss", + "src/global.scss" + ], + "scripts": [], + "assets": [ + { + "glob": "favicon.ico", + "input": "src/", + "output": "/" + }, + { + "glob": "**/*", + "input": "src/assets", + "output": "/assets" + }, + "src/manifest.json" + ] + } + }, + "ionic-cordova-build": { + "builder": "@ionic/angular-toolkit:cordova-build", + "options": { + "browserTarget": "app:build" + }, + "configurations": { + "production": { + "browserTarget": "app:build:production" + } + } + }, + "ionic-cordova-serve": { + "builder": "@ionic/angular-toolkit:cordova-serve", + "options": { + "cordovaBuildTarget": "app:ionic-cordova-build", + "devServerTarget": "app:serve", + "browserTarget": "app:build" + }, + "configurations": { + "production": { + "cordovaBuildTarget": "app:ionic-cordova-build:production", + "devServerTarget": "app:serve:production" + } + } + } + } + }, + "app-e2e": { + "root": "e2e/", + "projectType": "application", + "architect": { + "e2e": { + "builder": "@angular-devkit/build-angular:protractor", + "options": { + "protractorConfig": "e2e/protractor.conf.js", + "devServerTarget": "app:serve" + } + } + } + } + }, + "cli": { + "packageManager": "yarn", + "defaultCollection": "@ionic/angular-toolkit", + "warnings": { + "typescriptMismatch": false + } + }, + "schematics": { + "@ionic/angular-toolkit:component": { + "styleext": "scss" + }, + "@ionic/angular-toolkit:page": { + "styleext": "scss" + } + } +} diff --git a/packages/merchant-tablet-ionic/app.ts b/packages/merchant-tablet-ionic/app.ts new file mode 100644 index 0000000..187b421 --- /dev/null +++ b/packages/merchant-tablet-ionic/app.ts @@ -0,0 +1,12 @@ +import connect from 'connect'; +import path from 'path'; +import serveStatic from 'serve-static'; +import { env } from './scripts/env'; + +const port: number = env.PORT; + +connect() + .use(serveStatic(path.join(__dirname, '../../../'))) + .listen(port); + +console.log(`listening on ${port}`); diff --git a/packages/merchant-tablet-ionic/config.xml b/packages/merchant-tablet-ionic/config.xml new file mode 100644 index 0000000..5f1954c --- /dev/null +++ b/packages/merchant-tablet-ionic/config.xml @@ -0,0 +1,118 @@ + + + ever.merchant + Ever Merchant Tablet App + Ever Co. LTD + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/merchant-tablet-ionic/google-services.template.json b/packages/merchant-tablet-ionic/google-services.template.json new file mode 100644 index 0000000..bef9109 --- /dev/null +++ b/packages/merchant-tablet-ionic/google-services.template.json @@ -0,0 +1,42 @@ +{ + "project_info": { + "project_number": "XXXXXXXXXXXXXXXXXXXXX", + "firebase_url": "https://ever-merchants.firebaseio.com", + "project_id": "ever-merchants", + "storage_bucket": "ever-merchants.appspot.com" + }, + "client": [ + { + "client_info": { + "mobilesdk_app_id": "XXXXXXXXXXXXXXXXXXXXX", + "android_client_info": { + "package_name": "co.ever.merchants" + } + }, + "oauth_client": [ + { + "client_id": "XXXXXXXXXXXXXXXXXXXXX", + "client_type": 3 + } + ], + "api_key": [ + { + "current_key": "XXXXXXXXXXXXXXXXXXXXX" + } + ], + "services": { + "analytics_service": { + "status": 1 + }, + "appinvite_service": { + "status": 1, + "other_platform_oauth_client": [] + }, + "ads_service": { + "status": 2 + } + } + } + ], + "configuration_version": "1" +} diff --git a/packages/merchant-tablet-ionic/graphql.config.json b/packages/merchant-tablet-ionic/graphql.config.json new file mode 100644 index 0000000..07651ba --- /dev/null +++ b/packages/merchant-tablet-ionic/graphql.config.json @@ -0,0 +1,30 @@ +{ + "README_schema": "Specifies how to load the GraphQL schema that completion, error highlighting, and documentation is based on in the IDE", + "schema": { + "README_request": "To request the schema from a url instead, remove the 'file' JSON property above (and optionally delete the default graphql.schema.json file).", + "request": { + "url": "http://localhost:8443/graphql", + "method": "POST", + "README_postIntrospectionQuery": "Whether to POST an introspectionQuery to the url. If the url always returns the schema JSON, set to false and consider using GET", + "postIntrospectionQuery": true, + "README_options": "See the 'Options' section at https://github.com/then/then-request", + "options": { + "headers": { + "user-agent": "JS GraphQL" + } + } + } + }, + "README_endpoints": "A list of GraphQL endpoints that can be queried from '.graphql' files in the IDE", + "endpoints": [ + { + "name": "Default (http://localhost:8443/graphql)", + "url": "http://localhost:8443/graphql", + "options": { + "headers": { + "user-agent": "JS GraphQL" + } + } + } + ] +} diff --git a/packages/merchant-tablet-ionic/ionic.config.json b/packages/merchant-tablet-ionic/ionic.config.json new file mode 100644 index 0000000..1558cc6 --- /dev/null +++ b/packages/merchant-tablet-ionic/ionic.config.json @@ -0,0 +1,8 @@ +{ + "name": "ever-merchants", + "integrations": { + "cordova": {} + }, + "type": "angular", + "id": "4e9f4799" +} diff --git a/packages/merchant-tablet-ionic/ionic.project b/packages/merchant-tablet-ionic/ionic.project new file mode 100644 index 0000000..274537d --- /dev/null +++ b/packages/merchant-tablet-ionic/ionic.project @@ -0,0 +1,8 @@ +{ + "name": "ever-merchants", + "integrations": { + "cordova": {} + }, + "type": "angular", + "app_id": "4e9f4799" +} \ No newline at end of file diff --git a/packages/merchant-tablet-ionic/ngsw-config.json b/packages/merchant-tablet-ionic/ngsw-config.json new file mode 100644 index 0000000..0cb79da --- /dev/null +++ b/packages/merchant-tablet-ionic/ngsw-config.json @@ -0,0 +1,20 @@ +{ + "index": "/index.html", + "assetGroups": [ + { + "name": "app", + "installMode": "prefetch", + "resources": { + "files": ["/favicon.ico", "/index.html", "/*.css", "/*.js"] + } + }, + { + "name": "assets", + "installMode": "lazy", + "updateMode": "prefetch", + "resources": { + "files": ["/assets/**"] + } + } + ] +} diff --git a/packages/merchant-tablet-ionic/package.json b/packages/merchant-tablet-ionic/package.json new file mode 100644 index 0000000..5093f40 --- /dev/null +++ b/packages/merchant-tablet-ionic/package.json @@ -0,0 +1,248 @@ +{ + "name": "@ever-platform/merchant-tablet-ionic", + "version": "0.4.3", + "description": "Ever Demand Merchant Tablet App", + "license": "AGPL-3.0", + "homepage": "https://ever.co", + "repository": { + "type": "git", + "url": "https://github.com/ever-co/ever-demand.git" + }, + "bugs": { + "url": "https://github.com/ever-co/ever-demand/issues" + }, + "private": false, + "author": { + "name": "Ever Co. LTD", + "email": "ever@ever.co", + "url": "https://ever.co" + }, + "scripts": { + "ng:dev": "cross-env NODE_ENV=development NODE_OPTIONS=--max_old_space_size=4096 yarn ng", + "ng:prod": "cross-env NODE_ENV=production NODE_OPTIONS=--max_old_space_size=4096 yarn ng", + "ngsw-config": "cross-env NODE_ENV=development NODE_OPTIONS=--max_old_space_size=4096 yarn run config:dev && node_modules/.bin/ngsw-config www ngsw-config.json", + "ngsw-copy": "cross-env NODE_ENV=development NODE_OPTIONS=--max_old_space_size=4096 yarn run config:dev && yarn cpr node_modules/@angular/service-worker/ngsw-worker.js www/ngsw-worker.js -o", + "config": "cross-env NODE_ENV=development NODE_OPTIONS=--max_old_space_size=4096 yarn ts-node ./scripts/configure.ts", + "config:dev": "cross-env NODE_ENV=development NODE_OPTIONS=--max_old_space_size=4096 yarn run config -- --environment=dev", + "config:prod": "cross-env NODE_ENV=production NODE_OPTIONS=--max_old_space_size=4096 yarn run config -- --environment=prod", + "start": "cross-env NODE_ENV=development NODE_OPTIONS=--max_old_space_size=4096 yarn run config:dev && yarn run build && yarn ng:dev serve --port 4202", + "start:prod": "cross-env NODE_ENV=production NODE_OPTIONS=--max_old_space_size=4096 yarn run config:prod && yarn run build:prod && yarn ng:prod serve --port 4202 --prod", + "start:server:prod": "cross-env NODE_ENV=development NODE_OPTIONS=--max_old_space_size=4096 yarn run build:prod && node --harmony ./www/out-tsc/app.js", + "start:server:pm2": "cross-env NODE_ENV=production NODE_OPTIONS=--max_old_space_size=4096 yarn run build:prod && node --harmony ./www/out-tsc/packages/merchant-tablet-ionic/pm2bootstrap.js", + "build": "cross-env NODE_ENV=development NODE_OPTIONS=--max_old_space_size=4096 yarn run config:dev && yarn ng:dev build && yarn tsc", + "build:prod": "cross-env NODE_ENV=production NODE_OPTIONS=--max_old_space_size=4096 yarn run config:prod && yarn ng:prod build --prod --aot=false --build-optimizer=false && yarn tsc", + "test": "cross-env NODE_ENV=development NODE_OPTIONS=--max_old_space_size=4096 yarn run config:dev && yarn ng:dev test", + "lint": "cross-env NODE_ENV=development NODE_OPTIONS=--max_old_space_size=4096 yarn run config:dev && yarn ng:dev lint", + "e2e": "cross-env NODE_ENV=development NODE_OPTIONS=--max_old_space_size=4096 yarn run config:dev && yarn ng:dev e2e", + "bundle-report": "cross-env NODE_ENV=development NODE_OPTIONS=--max_old_space_size=4096 yarn run config:dev && yarn webpack-bundle-analyzer www/stats.json", + "docs": "cross-env NODE_ENV=development NODE_OPTIONS=--max_old_space_size=4096 yarn run config:dev && yarn compodoc -p src/tsconfig.app.json -d docs", + "docs:serve": "cross-env NODE_ENV=development NODE_OPTIONS=--max_old_space_size=4096 yarn run config:dev && yarn compodoc -p src/tsconfig.app.json -d docs -s", + "ionic:build:prod": "cross-env NODE_ENV=production NODE_OPTIONS=--max_old_space_size=4096 yarn run config:prod && yarn ionic build --prod --release", + "cordova:build": "cross-env NODE_ENV=development NODE_OPTIONS=--max_old_space_size=4096 yarn run config:dev && yarn ionic cordova build android", + "cordova:build:prod": "cross-env NODE_ENV=production NODE_OPTIONS=--max_old_space_size=4096 yarn run config:prod && yarn ionic cordova build android --prod --release", + "cordova:run:prod": "cross-env NODE_ENV=production NODE_OPTIONS=--max_old_space_size=4096 yarn run config:prod && yarn ionic cordova run android --prod --release", + "cordova:run:prod:device": "cross-env NODE_ENV=production NODE_OPTIONS=--max_old_space_size=4096 yarn run config:prod && yarn ionic cordova run android --prod --release --device" + }, + "dependencies": { + "@angular/animations": "^13.1.0", + "@angular/common": "^13.1.0", + "@angular/compiler": "^13.1.0", + "@angular/core": "^13.1.0", + "@angular/forms": "^13.1.0", + "@angular/language-service": "^13.1.0", + "@angular/platform-browser": "^13.1.0", + "@angular/platform-browser-dynamic": "^13.1.0", + "@angular/router": "^13.1.0", + "@angular/service-worker": "^13.1.0", + "@apollo/client": "^3.5.5", + "@ever-co/angular2-wizard": "^0.6.2", + "@ever-platform/common": "^0.4.3", + "@ever-platform/common-angular": "^0.4.3", + "@ionic-native/barcode-scanner": "^5.36.0", + "@ionic-native/call-number": "^5.36.0", + "@ionic-native/camera": "^5.36.0", + "@ionic-native/core": "^5.36.0", + "@ionic-native/device": "^5.36.0", + "@ionic-native/dialogs": "^5.36.0", + "@ionic-native/email-composer": "^5.36.0", + "@ionic-native/file-transfer": "^5.36.0", + "@ionic-native/geolocation": "^5.36.0", + "@ionic-native/globalization": "^5.36.0", + "@ionic-native/google-analytics": "^5.36.0", + "@ionic-native/google-maps": "~5.5.0", + "@ionic-native/in-app-browser": "^5.36.0", + "@ionic-native/intercom": "^5.36.0", + "@ionic-native/local-notifications": "^5.36.0", + "@ionic-native/mixpanel": "^5.36.0", + "@ionic-native/network": "^5.36.0", + "@ionic-native/open-native-settings": "^5.36.0", + "@ionic-native/screen-orientation": "^5.36.0", + "@ionic-native/splash-screen": "^5.36.0", + "@ionic-native/status-bar": "^5.36.0", + "@ionic-native/unique-device-id": "^5.36.0", + "@ionic-native/vibration": "^5.36.0", + "@ionic/angular": "^5.8.4", + "@ionic/pro": "^2.0.4", + "@ionic/storage": "^3.0.6", + "@ionic/storage-angular": "^3.0.6", + "@ng-select/ng-select": "^7.3.0", + "@ngx-translate/core": "^13.0.0", + "@ngx-translate/http-loader": "^6.0.0", + "apollo-angular": "^2.6.0", + "bootstrap": "^4.6.0", + "call-number": "^1.0.1", + "connect": "^3.7.0", + "cordova-android": "^10.1.1", + "cordova-android-support-gradle-release": "^3.0.1", + "cordova-browser": "^6.0.0", + "cordova-open-native-settings": "^1.5.5", + "cordova-plugin-androidx": "^3.0.0", + "cordova-plugin-androidx-adapter": "^1.1.3", + "cordova-plugin-appavailability": "^0.4.2", + "cordova-plugin-badge": "0.8.8", + "cordova-plugin-camera": "^6.0.0", + "cordova-plugin-device": "^2.0.3", + "cordova-plugin-dialogs": "^2.0.2", + "cordova-plugin-email-composer": "^0.9.2", + "cordova-plugin-file": "^6.0.2", + "cordova-plugin-file-transfer": "1.7.1", + "cordova-plugin-geolocation": "^4.1.0", + "cordova-plugin-globalization": "^1.11.0", + "cordova-plugin-google-analytics": "^1.9.0", + "cordova-plugin-inappbrowser": "^5.0.0", + "cordova-plugin-intercom": "^10.2.0", + "cordova-plugin-ionic-keyboard": "^2.2.0", + "cordova-plugin-ionic-webview": "^5.0.0", + "cordova-plugin-local-notification": "0.9.0-beta.2", + "cordova-plugin-mixpanel": "^4.7.2", + "cordova-plugin-network-information": "^3.0.0", + "cordova-plugin-screen-orientation": "^3.0.2", + "cordova-plugin-splashscreen": "^6.0.0", + "cordova-plugin-statusbar": "^2.4.3", + "cordova-plugin-uniquedeviceid": "^1.3.2", + "cordova-plugin-vibration": "^3.1.1", + "cordova-plugin-whitelist": "^1.3.5", + "core-js": "^3.18.3", + "cryptiles": "^4.1.3", + "es6-promise-plugin": "^4.2.2", + "font-awesome": "^4.7.0", + "fstream": "^1.0.12", + "graphql": "15.7.2", + "graphql-tag": "^2.12.6", + "handlebars": "^4.7.7", + "imagesloaded": "^4.1.4", + "ionic-selectable": "^4.9.0", + "ionicons": "^5.5.3", + "jsbarcode": "^3.11.5", + "lodash": "^4.17.21", + "lodash.mergewith": "^4.6.2", + "masonry-layout": "^4.2.2", + "moment": "^2.29.1", + "mongoose": "^6.0.11", + "mx.ferreyra.callnumber": "^0.0.2", + "neo-async": "^2.6.2", + "ng2-completer": "^9.0.1", + "ng2-file-upload": "^1.4.0", + "ng2-smart-table": "^1.7.2", + "ngx-masonry": "^12.0.0", + "ngx-moment": "^5.0.0", + "ngx-pagination": "^5.1.1", + "phonegap-plugin-barcodescanner": "^8.0.1", + "pm2": "^5.1.2", + "qrcode": "^1.4.4", + "rxjs": "^7.4.0", + "rxjs-compat": "^6.6.7", + "socket.io-client": "^4.3.0", + "stripe": "^8.183.0", + "subscriptions-transport-ws": "^0.11.0", + "sw-toolbox": "^3.6.0", + "symbol-observable": "^4.0.0", + "tslib": "^2.3.1", + "uglify-js": "^3.14.2", + "uuid": "^8.3.2", + "zone.js": "~0.11.4" + }, + "devDependencies": { + "@angular-builders/custom-webpack": "^13.0.0", + "@angular-devkit/architect": "^0.1301.0", + "@angular-devkit/build-angular": "~13.1.0", + "@angular-devkit/build-optimizer": "^0.1300.4", + "@angular-devkit/build-webpack": "^0.1301.0", + "@angular-devkit/core": "^13.1.0", + "@angular-devkit/schematics": "^13.1.0", + "@angular/cli": "^13.1.0", + "@angular/compiler-cli": "^13.1.0", + "@ionic/angular-toolkit": "^4.0.0", + "@ionic/lab": "^3.2.10", + "@types/jasmine": "~3.9.1", + "@types/jasminewd2": "~2.0.10", + "@types/node": "^16.11.0", + "codelyzer": "^6.0.2", + "ionic": "^5.4.16", + "jasmine-core": "~3.10.0", + "jasmine-spec-reporter": "~7.0.0", + "karma": "~6.3.4", + "karma-chrome-launcher": "~3.1.0", + "karma-coverage-istanbul-reporter": "~3.0.3", + "karma-jasmine": "~4.0.1", + "karma-jasmine-html-reporter": "^1.7.0", + "protractor": "~7.0.0", + "rimraf": "^3.0.2", + "ts-node": "~10.3.0", + "tslint": "~5.20.1", + "typescript": "~4.5.3" + }, + "config": { + "ionic_webpack": "./config/webpack.config.js" + }, + "cordova": { + "platforms": [ + "browser", + "android" + ], + "plugins": { + "cordova-open-native-settings": {}, + "cordova-plugin-whitelist": {}, + "cordova-plugin-device": {}, + "cordova-plugin-ionic-webview": { + "ANDROID_SUPPORT_ANNOTATIONS_VERSION": "27.+" + }, + "cordova-plugin-ionic-keyboard": {}, + "cordova-plugin-google-analytics": { + "GMS_VERSION": "11.0.1" + }, + "cordova-plugin-network-information": {}, + "cordova-plugin-mixpanel": { + "PLAY_SERVICES_VERSION": "+", + "FIREBASE_VERSION": "+" + }, + "cordova-plugin-intercom": {}, + "cordova-plugin-screen-orientation": {}, + "cordova-plugin-globalization": {}, + "cordova-plugin-statusbar": {}, + "cordova-plugin-camera": { + "ANDROID_SUPPORT_V4_VERSION": "27.+" + }, + "cordova-plugin-splashscreen": {}, + "mx.ferreyra.callnumber": {}, + "cordova-plugin-email-composer": { + "ANDROID_SUPPORT_V4_VERSION": "27.+" + }, + "phonegap-plugin-barcodescanner": { + "ANDROID_SUPPORT_V4_VERSION": "27.+" + }, + "call-number": {}, + "cordova-plugin-file-transfer": {}, + "cordova-plugin-geolocation": {}, + "cordova-plugin-local-notification": {}, + "cordova-plugin-vibration": {}, + "cordova-plugin-androidx": {}, + "cordova-plugin-androidx-adapter": {} + } + }, + "engines": { + "node": ">=14.4.0", + "yarn": ">=1.13.0" + }, + "snyk": false +} \ No newline at end of file diff --git a/packages/merchant-tablet-ionic/pm2bootstrap.ts b/packages/merchant-tablet-ionic/pm2bootstrap.ts new file mode 100644 index 0000000..5e15086 --- /dev/null +++ b/packages/merchant-tablet-ionic/pm2bootstrap.ts @@ -0,0 +1,56 @@ +require('dotenv').config(); + +const pm2 = require('pm2'); + +import { env } from './scripts/env'; + +const MACHINE_NAME = process.env.KEYMETRICS_MACHINE_NAME; +const PRIVATE_KEY = process.env.KEYMETRICS_SECRET_KEY; +const PUBLIC_KEY = process.env.KEYMETRICS_PUBLIC_KEY; +const appName = process.env.PM2_APP_NAME || 'EverMerchants'; +const instances = env.WEB_CONCURRENCY; +const maxMemory = env.WEB_MEMORY; +const port = env.PORT; + +pm2.connect(function () { + pm2.start( + { + script: './www/out-tsc/packages/merchant-tablet-ionic/app.js', + name: appName, // ----> THESE ATTRIBUTES ARE OPTIONAL: + exec_mode: 'fork', // ----> https://github.com/Unitech/PM2/blob/master/ADVANCED_README.md#schema + instances, + max_memory_restart: maxMemory + 'M', // Auto restart if process taking more than XXmo + env: { + // If needed declare some environment variables + NODE_ENV: 'production', + PORT: port, + KEYMETRICS_PUBLIC: PUBLIC_KEY, + KEYMETRICS_SECRET: PRIVATE_KEY, + }, + post_update: ['yarn install'], // Commands to execute once we do a pull from Keymetrics + }, + function () { + pm2.dump(console.error); + // Display logs in standard output + pm2.launchBus(function (err, bus) { + console.log('[PM2] Log streaming started'); + + bus.on('log:out', function (packet) { + console.log( + '[App:%s] %s', + packet.process.name, + packet.data + ); + }); + + bus.on('log:err', function (packet) { + console.error( + '[App:%s][Err] %s', + packet.process.name, + packet.data + ); + }); + }); + } + ); +}); diff --git a/packages/merchant-tablet-ionic/resources/README.md b/packages/merchant-tablet-ionic/resources/README.md new file mode 100644 index 0000000..8d4bb5f --- /dev/null +++ b/packages/merchant-tablet-ionic/resources/README.md @@ -0,0 +1,8 @@ +These are Cordova resources. You can replace icon.png and splash.png and run +`ionic cordova resources` to generate custom icons and splash screens for your +app. See `ionic cordova resources --help` for details. + +Cordova reference documentation: + +- Icons: https://cordova.apache.org/docs/en/latest/config_ref/images.html +- Splash Screens: https://cordova.apache.org/docs/en/latest/reference/cordova-plugin-splashscreen/ diff --git a/packages/merchant-tablet-ionic/resources/android/icon/drawable-hdpi-icon.png b/packages/merchant-tablet-ionic/resources/android/icon/drawable-hdpi-icon.png new file mode 100644 index 0000000..e90ee3e Binary files /dev/null and b/packages/merchant-tablet-ionic/resources/android/icon/drawable-hdpi-icon.png differ diff --git a/packages/merchant-tablet-ionic/resources/android/icon/drawable-ldpi-icon.png b/packages/merchant-tablet-ionic/resources/android/icon/drawable-ldpi-icon.png new file mode 100644 index 0000000..a82c000 Binary files /dev/null and b/packages/merchant-tablet-ionic/resources/android/icon/drawable-ldpi-icon.png differ diff --git a/packages/merchant-tablet-ionic/resources/android/icon/drawable-mdpi-icon.png b/packages/merchant-tablet-ionic/resources/android/icon/drawable-mdpi-icon.png new file mode 100644 index 0000000..51d312a Binary files /dev/null and b/packages/merchant-tablet-ionic/resources/android/icon/drawable-mdpi-icon.png differ diff --git a/packages/merchant-tablet-ionic/resources/android/icon/drawable-xhdpi-icon.png b/packages/merchant-tablet-ionic/resources/android/icon/drawable-xhdpi-icon.png new file mode 100644 index 0000000..79d0c22 Binary files /dev/null and b/packages/merchant-tablet-ionic/resources/android/icon/drawable-xhdpi-icon.png differ diff --git a/packages/merchant-tablet-ionic/resources/android/icon/drawable-xxhdpi-icon.png b/packages/merchant-tablet-ionic/resources/android/icon/drawable-xxhdpi-icon.png new file mode 100644 index 0000000..4cf16ec Binary files /dev/null and b/packages/merchant-tablet-ionic/resources/android/icon/drawable-xxhdpi-icon.png differ diff --git a/packages/merchant-tablet-ionic/resources/android/icon/drawable-xxxhdpi-icon.png b/packages/merchant-tablet-ionic/resources/android/icon/drawable-xxxhdpi-icon.png new file mode 100644 index 0000000..23dd1a8 Binary files /dev/null and b/packages/merchant-tablet-ionic/resources/android/icon/drawable-xxxhdpi-icon.png differ diff --git a/packages/merchant-tablet-ionic/resources/android/splash/drawable-land-hdpi-screen.png b/packages/merchant-tablet-ionic/resources/android/splash/drawable-land-hdpi-screen.png new file mode 100644 index 0000000..839834c Binary files /dev/null and b/packages/merchant-tablet-ionic/resources/android/splash/drawable-land-hdpi-screen.png differ diff --git a/packages/merchant-tablet-ionic/resources/android/splash/drawable-land-ldpi-screen.png b/packages/merchant-tablet-ionic/resources/android/splash/drawable-land-ldpi-screen.png new file mode 100644 index 0000000..7a22639 Binary files /dev/null and b/packages/merchant-tablet-ionic/resources/android/splash/drawable-land-ldpi-screen.png differ diff --git a/packages/merchant-tablet-ionic/resources/android/splash/drawable-land-mdpi-screen.png b/packages/merchant-tablet-ionic/resources/android/splash/drawable-land-mdpi-screen.png new file mode 100644 index 0000000..4cd12f8 Binary files /dev/null and b/packages/merchant-tablet-ionic/resources/android/splash/drawable-land-mdpi-screen.png differ diff --git a/packages/merchant-tablet-ionic/resources/android/splash/drawable-land-xhdpi-screen.png b/packages/merchant-tablet-ionic/resources/android/splash/drawable-land-xhdpi-screen.png new file mode 100644 index 0000000..473726e Binary files /dev/null and b/packages/merchant-tablet-ionic/resources/android/splash/drawable-land-xhdpi-screen.png differ diff --git a/packages/merchant-tablet-ionic/resources/android/splash/drawable-land-xxhdpi-screen.png b/packages/merchant-tablet-ionic/resources/android/splash/drawable-land-xxhdpi-screen.png new file mode 100644 index 0000000..8c6466e Binary files /dev/null and b/packages/merchant-tablet-ionic/resources/android/splash/drawable-land-xxhdpi-screen.png differ diff --git a/packages/merchant-tablet-ionic/resources/android/splash/drawable-land-xxxhdpi-screen.png b/packages/merchant-tablet-ionic/resources/android/splash/drawable-land-xxxhdpi-screen.png new file mode 100644 index 0000000..27e7b10 Binary files /dev/null and b/packages/merchant-tablet-ionic/resources/android/splash/drawable-land-xxxhdpi-screen.png differ diff --git a/packages/merchant-tablet-ionic/resources/android/splash/drawable-port-hdpi-screen.png b/packages/merchant-tablet-ionic/resources/android/splash/drawable-port-hdpi-screen.png new file mode 100644 index 0000000..b62e6c3 Binary files /dev/null and b/packages/merchant-tablet-ionic/resources/android/splash/drawable-port-hdpi-screen.png differ diff --git a/packages/merchant-tablet-ionic/resources/android/splash/drawable-port-ldpi-screen.png b/packages/merchant-tablet-ionic/resources/android/splash/drawable-port-ldpi-screen.png new file mode 100644 index 0000000..b65ed1a Binary files /dev/null and b/packages/merchant-tablet-ionic/resources/android/splash/drawable-port-ldpi-screen.png differ diff --git a/packages/merchant-tablet-ionic/resources/android/splash/drawable-port-mdpi-screen.png b/packages/merchant-tablet-ionic/resources/android/splash/drawable-port-mdpi-screen.png new file mode 100644 index 0000000..90b2824 Binary files /dev/null and b/packages/merchant-tablet-ionic/resources/android/splash/drawable-port-mdpi-screen.png differ diff --git a/packages/merchant-tablet-ionic/resources/android/splash/drawable-port-xhdpi-screen.png b/packages/merchant-tablet-ionic/resources/android/splash/drawable-port-xhdpi-screen.png new file mode 100644 index 0000000..cccdb5d Binary files /dev/null and b/packages/merchant-tablet-ionic/resources/android/splash/drawable-port-xhdpi-screen.png differ diff --git a/packages/merchant-tablet-ionic/resources/android/splash/drawable-port-xxhdpi-screen.png b/packages/merchant-tablet-ionic/resources/android/splash/drawable-port-xxhdpi-screen.png new file mode 100644 index 0000000..c4b76bd Binary files /dev/null and b/packages/merchant-tablet-ionic/resources/android/splash/drawable-port-xxhdpi-screen.png differ diff --git a/packages/merchant-tablet-ionic/resources/android/splash/drawable-port-xxxhdpi-screen.png b/packages/merchant-tablet-ionic/resources/android/splash/drawable-port-xxxhdpi-screen.png new file mode 100644 index 0000000..d961909 Binary files /dev/null and b/packages/merchant-tablet-ionic/resources/android/splash/drawable-port-xxxhdpi-screen.png differ diff --git a/packages/merchant-tablet-ionic/resources/icon.png b/packages/merchant-tablet-ionic/resources/icon.png new file mode 100644 index 0000000..ebdbd3f Binary files /dev/null and b/packages/merchant-tablet-ionic/resources/icon.png differ diff --git a/packages/merchant-tablet-ionic/resources/icon.png.md5 b/packages/merchant-tablet-ionic/resources/icon.png.md5 new file mode 100644 index 0000000..cd586f2 --- /dev/null +++ b/packages/merchant-tablet-ionic/resources/icon.png.md5 @@ -0,0 +1 @@ +6cbfe3f9036a120f0c6ea1d20edc72c1 \ No newline at end of file diff --git a/packages/merchant-tablet-ionic/resources/ios/icon/icon-1024.png b/packages/merchant-tablet-ionic/resources/ios/icon/icon-1024.png new file mode 100644 index 0000000..bee7766 Binary files /dev/null and b/packages/merchant-tablet-ionic/resources/ios/icon/icon-1024.png differ diff --git a/packages/merchant-tablet-ionic/resources/ios/icon/icon-40.png b/packages/merchant-tablet-ionic/resources/ios/icon/icon-40.png new file mode 100644 index 0000000..5716e7f Binary files /dev/null and b/packages/merchant-tablet-ionic/resources/ios/icon/icon-40.png differ diff --git a/packages/merchant-tablet-ionic/resources/ios/icon/icon-40@2x.png b/packages/merchant-tablet-ionic/resources/ios/icon/icon-40@2x.png new file mode 100644 index 0000000..75c0efd Binary files /dev/null and b/packages/merchant-tablet-ionic/resources/ios/icon/icon-40@2x.png differ diff --git a/packages/merchant-tablet-ionic/resources/ios/icon/icon-40@3x.png b/packages/merchant-tablet-ionic/resources/ios/icon/icon-40@3x.png new file mode 100644 index 0000000..560c7c6 Binary files /dev/null and b/packages/merchant-tablet-ionic/resources/ios/icon/icon-40@3x.png differ diff --git a/packages/merchant-tablet-ionic/resources/ios/icon/icon-50.png b/packages/merchant-tablet-ionic/resources/ios/icon/icon-50.png new file mode 100644 index 0000000..bdf0857 Binary files /dev/null and b/packages/merchant-tablet-ionic/resources/ios/icon/icon-50.png differ diff --git a/packages/merchant-tablet-ionic/resources/ios/icon/icon-50@2x.png b/packages/merchant-tablet-ionic/resources/ios/icon/icon-50@2x.png new file mode 100644 index 0000000..54deade Binary files /dev/null and b/packages/merchant-tablet-ionic/resources/ios/icon/icon-50@2x.png differ diff --git a/packages/merchant-tablet-ionic/resources/ios/icon/icon-60.png b/packages/merchant-tablet-ionic/resources/ios/icon/icon-60.png new file mode 100644 index 0000000..48383e6 Binary files /dev/null and b/packages/merchant-tablet-ionic/resources/ios/icon/icon-60.png differ diff --git a/packages/merchant-tablet-ionic/resources/ios/icon/icon-60@2x.png b/packages/merchant-tablet-ionic/resources/ios/icon/icon-60@2x.png new file mode 100644 index 0000000..560c7c6 Binary files /dev/null and b/packages/merchant-tablet-ionic/resources/ios/icon/icon-60@2x.png differ diff --git a/packages/merchant-tablet-ionic/resources/ios/icon/icon-60@3x.png b/packages/merchant-tablet-ionic/resources/ios/icon/icon-60@3x.png new file mode 100644 index 0000000..29a43af Binary files /dev/null and b/packages/merchant-tablet-ionic/resources/ios/icon/icon-60@3x.png differ diff --git a/packages/merchant-tablet-ionic/resources/ios/icon/icon-72.png b/packages/merchant-tablet-ionic/resources/ios/icon/icon-72.png new file mode 100644 index 0000000..aeadd15 Binary files /dev/null and b/packages/merchant-tablet-ionic/resources/ios/icon/icon-72.png differ diff --git a/packages/merchant-tablet-ionic/resources/ios/icon/icon-72@2x.png b/packages/merchant-tablet-ionic/resources/ios/icon/icon-72@2x.png new file mode 100644 index 0000000..a9de365 Binary files /dev/null and b/packages/merchant-tablet-ionic/resources/ios/icon/icon-72@2x.png differ diff --git a/packages/merchant-tablet-ionic/resources/ios/icon/icon-76.png b/packages/merchant-tablet-ionic/resources/ios/icon/icon-76.png new file mode 100644 index 0000000..d8e4b05 Binary files /dev/null and b/packages/merchant-tablet-ionic/resources/ios/icon/icon-76.png differ diff --git a/packages/merchant-tablet-ionic/resources/ios/icon/icon-76@2x.png b/packages/merchant-tablet-ionic/resources/ios/icon/icon-76@2x.png new file mode 100644 index 0000000..1b5e449 Binary files /dev/null and b/packages/merchant-tablet-ionic/resources/ios/icon/icon-76@2x.png differ diff --git a/packages/merchant-tablet-ionic/resources/ios/icon/icon-83.5@2x.png b/packages/merchant-tablet-ionic/resources/ios/icon/icon-83.5@2x.png new file mode 100644 index 0000000..609fc8f Binary files /dev/null and b/packages/merchant-tablet-ionic/resources/ios/icon/icon-83.5@2x.png differ diff --git a/packages/merchant-tablet-ionic/resources/ios/icon/icon-small.png b/packages/merchant-tablet-ionic/resources/ios/icon/icon-small.png new file mode 100644 index 0000000..725fb9f Binary files /dev/null and b/packages/merchant-tablet-ionic/resources/ios/icon/icon-small.png differ diff --git a/packages/merchant-tablet-ionic/resources/ios/icon/icon-small@2x.png b/packages/merchant-tablet-ionic/resources/ios/icon/icon-small@2x.png new file mode 100644 index 0000000..441ef3d Binary files /dev/null and b/packages/merchant-tablet-ionic/resources/ios/icon/icon-small@2x.png differ diff --git a/packages/merchant-tablet-ionic/resources/ios/icon/icon-small@3x.png b/packages/merchant-tablet-ionic/resources/ios/icon/icon-small@3x.png new file mode 100644 index 0000000..66d109e Binary files /dev/null and b/packages/merchant-tablet-ionic/resources/ios/icon/icon-small@3x.png differ diff --git a/packages/merchant-tablet-ionic/resources/ios/icon/icon.png b/packages/merchant-tablet-ionic/resources/ios/icon/icon.png new file mode 100644 index 0000000..f487d28 Binary files /dev/null and b/packages/merchant-tablet-ionic/resources/ios/icon/icon.png differ diff --git a/packages/merchant-tablet-ionic/resources/ios/icon/icon@2x.png b/packages/merchant-tablet-ionic/resources/ios/icon/icon@2x.png new file mode 100644 index 0000000..a1fc994 Binary files /dev/null and b/packages/merchant-tablet-ionic/resources/ios/icon/icon@2x.png differ diff --git a/packages/merchant-tablet-ionic/resources/ios/splash/Default-568h@2x~iphone.png b/packages/merchant-tablet-ionic/resources/ios/splash/Default-568h@2x~iphone.png new file mode 100644 index 0000000..663deb1 Binary files /dev/null and b/packages/merchant-tablet-ionic/resources/ios/splash/Default-568h@2x~iphone.png differ diff --git a/packages/merchant-tablet-ionic/resources/ios/splash/Default-667h.png b/packages/merchant-tablet-ionic/resources/ios/splash/Default-667h.png new file mode 100644 index 0000000..56b3317 Binary files /dev/null and b/packages/merchant-tablet-ionic/resources/ios/splash/Default-667h.png differ diff --git a/packages/merchant-tablet-ionic/resources/ios/splash/Default-736h.png b/packages/merchant-tablet-ionic/resources/ios/splash/Default-736h.png new file mode 100644 index 0000000..96ce8bf Binary files /dev/null and b/packages/merchant-tablet-ionic/resources/ios/splash/Default-736h.png differ diff --git a/packages/merchant-tablet-ionic/resources/ios/splash/Default-Landscape-736h.png b/packages/merchant-tablet-ionic/resources/ios/splash/Default-Landscape-736h.png new file mode 100644 index 0000000..aaff74a Binary files /dev/null and b/packages/merchant-tablet-ionic/resources/ios/splash/Default-Landscape-736h.png differ diff --git a/packages/merchant-tablet-ionic/resources/ios/splash/Default-Landscape@2x~ipad.png b/packages/merchant-tablet-ionic/resources/ios/splash/Default-Landscape@2x~ipad.png new file mode 100644 index 0000000..19770a2 Binary files /dev/null and b/packages/merchant-tablet-ionic/resources/ios/splash/Default-Landscape@2x~ipad.png differ diff --git a/packages/merchant-tablet-ionic/resources/ios/splash/Default-Landscape@~ipadpro.png b/packages/merchant-tablet-ionic/resources/ios/splash/Default-Landscape@~ipadpro.png new file mode 100644 index 0000000..6438232 Binary files /dev/null and b/packages/merchant-tablet-ionic/resources/ios/splash/Default-Landscape@~ipadpro.png differ diff --git a/packages/merchant-tablet-ionic/resources/ios/splash/Default-Landscape~ipad.png b/packages/merchant-tablet-ionic/resources/ios/splash/Default-Landscape~ipad.png new file mode 100644 index 0000000..6fe8925 Binary files /dev/null and b/packages/merchant-tablet-ionic/resources/ios/splash/Default-Landscape~ipad.png differ diff --git a/packages/merchant-tablet-ionic/resources/ios/splash/Default-Portrait@2x~ipad.png b/packages/merchant-tablet-ionic/resources/ios/splash/Default-Portrait@2x~ipad.png new file mode 100644 index 0000000..a2c1489 Binary files /dev/null and b/packages/merchant-tablet-ionic/resources/ios/splash/Default-Portrait@2x~ipad.png differ diff --git a/packages/merchant-tablet-ionic/resources/ios/splash/Default-Portrait@~ipadpro.png b/packages/merchant-tablet-ionic/resources/ios/splash/Default-Portrait@~ipadpro.png new file mode 100644 index 0000000..a370419 Binary files /dev/null and b/packages/merchant-tablet-ionic/resources/ios/splash/Default-Portrait@~ipadpro.png differ diff --git a/packages/merchant-tablet-ionic/resources/ios/splash/Default-Portrait~ipad.png b/packages/merchant-tablet-ionic/resources/ios/splash/Default-Portrait~ipad.png new file mode 100644 index 0000000..9aa0a26 Binary files /dev/null and b/packages/merchant-tablet-ionic/resources/ios/splash/Default-Portrait~ipad.png differ diff --git a/packages/merchant-tablet-ionic/resources/ios/splash/Default@2x~iphone.png b/packages/merchant-tablet-ionic/resources/ios/splash/Default@2x~iphone.png new file mode 100644 index 0000000..f3c709a Binary files /dev/null and b/packages/merchant-tablet-ionic/resources/ios/splash/Default@2x~iphone.png differ diff --git a/packages/merchant-tablet-ionic/resources/ios/splash/Default@2x~universal~anyany.png b/packages/merchant-tablet-ionic/resources/ios/splash/Default@2x~universal~anyany.png new file mode 100644 index 0000000..960cb82 Binary files /dev/null and b/packages/merchant-tablet-ionic/resources/ios/splash/Default@2x~universal~anyany.png differ diff --git a/packages/merchant-tablet-ionic/resources/ios/splash/Default~iphone.png b/packages/merchant-tablet-ionic/resources/ios/splash/Default~iphone.png new file mode 100644 index 0000000..8b47ad8 Binary files /dev/null and b/packages/merchant-tablet-ionic/resources/ios/splash/Default~iphone.png differ diff --git a/packages/merchant-tablet-ionic/resources/splash.png b/packages/merchant-tablet-ionic/resources/splash.png new file mode 100644 index 0000000..e3b4d1d Binary files /dev/null and b/packages/merchant-tablet-ionic/resources/splash.png differ diff --git a/packages/merchant-tablet-ionic/resources/splash.png.md5 b/packages/merchant-tablet-ionic/resources/splash.png.md5 new file mode 100644 index 0000000..5c5cf29 --- /dev/null +++ b/packages/merchant-tablet-ionic/resources/splash.png.md5 @@ -0,0 +1 @@ +3eeb0addd2aa8c118958337ac5fb9d1b \ No newline at end of file diff --git a/packages/merchant-tablet-ionic/scripts/configure.ts b/packages/merchant-tablet-ionic/scripts/configure.ts new file mode 100644 index 0000000..da2249e --- /dev/null +++ b/packages/merchant-tablet-ionic/scripts/configure.ts @@ -0,0 +1,106 @@ +// NOTE: do NOT ever put here any secure settings! (e.g. Secret Keys) +// We are using dotenv (.env) for consistency with other Platform projects +// This is Angular app and all settings will be loaded into the client browser! + +import { env } from './env'; +import { writeFile, unlinkSync } from 'fs'; +import { argv } from 'yargs'; + +const environment = argv["environment"]; +const isProd = environment === 'prod'; + +if (!env.GOOGLE_MAPS_API_KEY) { + console.warn( + 'WARNING: No Google Maps API Key defined in the .env. Google Maps may not be visible!' + ); +} + +const envFileContent = `// NOTE: Auto-generated file +// The file contents for the current environment will overwrite these during build. +// The build system defaults to the dev environment which uses 'environment.ts', but if you do +// 'ng build --env=prod' then 'environment.prod.ts' will be used instead. +// The list of which env maps to which file can be found in '.angular-cli.json'. + +import { Environment } from './model'; + +export const environment: Environment = { + production: ${isProd}, + + GQL_ENDPOINT: '${env.GQL_ENDPOINT}', + GQL_SUBSCRIPTIONS_ENDPOINT: '${env.GQL_SUBSCRIPTIONS_ENDPOINT}', + SERVICES_ENDPOINT: '${env.SERVICES_ENDPOINT}', + HTTPS_SERVICES_ENDPOINT: '${env.HTTPS_SERVICES_ENDPOINT}', + + APP_VERSION: '${env.APP_VERSION}', + + API_FILE_UPLOAD_URL: '${env.API_FILE_UPLOAD_URL}', + + DEFAULT_LOGIN_USERNAME: '${env.DEFAULT_LOGIN_USERNAME}', + DEFAULT_LOGIN_PASSWORD: '${env.DEFAULT_LOGIN_PASSWORD}', + + LOGIN_LOGO: '${env.LOGIN_LOGO}', + NO_INTERNET_LOGO: '${env.NO_INTERNET_LOGO}', + + COMPANY_NAME: '${env.COMPANY_NAME}', + APP_NAME: '${env.APP_NAME}', + + GOOGLE_MAPS_API_KEY: '${env.GOOGLE_MAPS_API_KEY}', + + GOOGLE_ANALYTICS_API_KEY: '${env.GOOGLE_ANALYTICS_API_KEY}', + FAKE_UUID: '${env.FAKE_UUID}', + MIXPANEL_API_KEY: '${env.MIXPANEL_API_KEY}', + + MAP_MERCHANT_ICON_LINK: '${env.MAP_MERCHANT_ICON_LINK}', + + MAP_USER_ICON_LINK: '${env.MAP_USER_ICON_LINK}', + + MAP_CARRIER_ICON_LINK: '${env.MAP_CARRIER_ICON_LINK}', + + DEFAULT_LANGUAGE: '${env.DEFAULT_LANGUAGE}', + + // For maintenance micro service + SETTINGS_APP_TYPE: '${env.SETTINGS_APP_TYPE}', + SETTINGS_MAINTENANCE_API_URL: '${env.SETTINGS_MAINTENANCE_API_URL}' + +}; + +/* + * In development mode, to ignore zone related error stack frames such as + * 'zone.run', 'zoneDelegate.invokeTask' for easier debugging, you can + * import the following file, but please comment it out in production mode + * because it will have performance impact when throw error + */ +// import 'zone.js/dist/zone-error'; // Included with Angular CLI. + +`; + +const envFileDest: string = isProd ? 'environment.prod.ts' : 'environment.ts'; +const envFileDestOther: string = !isProd + ? 'environment.prod.ts' + : 'environment.ts'; + +// we always want first to remove old generated files (one of them is not needed for current build) +try { + unlinkSync(`./src/environments/environment.ts`); +} catch {} +try { + unlinkSync(`./src/environments/environment.prod.ts`); +} catch {} + +writeFile(`./src/environments/${envFileDest}`, envFileContent, function (err) { + if (err) { + console.log(err); + } else { + console.log(`Generated Angular environment file: ${envFileDest}`); + } +}); + +writeFile(`./src/environments/${envFileDestOther}`, envFileContent, function ( + err +) { + if (err) { + console.log(err); + } else { + console.log(`Generated Angular environment file: ${envFileDestOther}`); + } +}); diff --git a/packages/merchant-tablet-ionic/scripts/env.ts b/packages/merchant-tablet-ionic/scripts/env.ts new file mode 100644 index 0000000..8dd9d8f --- /dev/null +++ b/packages/merchant-tablet-ionic/scripts/env.ts @@ -0,0 +1,112 @@ +// NOTE: do NOT ever put here any secure settings! (e.g. Secret Keys) +// We are using dotenv (.env) for consistency with other Platform projects +// This is Angular app and all settings will be loaded into the client browser! + +require('dotenv').config(); + +import { cleanEnv, num, str, bool, CleanOptions } from 'envalid'; +import { v4 as uuid } from 'uuid'; + +export type Env = Readonly<{ + production: boolean; + + SERVICES_ENDPOINT: string; + HTTPS_SERVICES_ENDPOINT: string; + + // Graphql endpoints for apollo services + GQL_ENDPOINT: string; + GQL_SUBSCRIPTIONS_ENDPOINT: string; + + APP_VERSION: string; + + API_FILE_UPLOAD_URL: string; + + DEFAULT_LOGIN_USERNAME: string; + DEFAULT_LOGIN_PASSWORD: string; + + LOGIN_LOGO: string; + NO_INTERNET_LOGO: string; + + COMPANY_NAME: string; + APP_NAME: string; + + GOOGLE_MAPS_API_KEY: string; + + GOOGLE_ANALYTICS_API_KEY: string; + FAKE_UUID: string; + MIXPANEL_API_KEY: string; + + MAP_MERCHANT_ICON_LINK: string; + + MAP_USER_ICON_LINK: string; + + MAP_CARRIER_ICON_LINK: string; + + DEFAULT_LANGUAGE: string; + + // For maintenance micro service + SETTINGS_APP_TYPE?: string; + SETTINGS_MAINTENANCE_API_URL?: string; + WEB_CONCURRENCY: number; + WEB_MEMORY: number; + PORT: number; +}>; + +const opt: CleanOptions = { +}; + +export const env: Env = cleanEnv( + process.env, + { + production: bool({ default: false }), + + SERVICES_ENDPOINT: str({ default: 'http://localhost:5500' }), + HTTPS_SERVICES_ENDPOINT: str({ default: 'https://localhost:2087' }), + + // Graphql endpoints for apollo services + GQL_ENDPOINT: str({ default: 'http://localhost:8443/graphql' }), + GQL_SUBSCRIPTIONS_ENDPOINT: str({ + default: 'ws://localhost:2086/subscriptions', + }), + + APP_VERSION: str({ default: '0.2.0' }), + + API_FILE_UPLOAD_URL: str({ + default: 'https://api.cloudinary.com/v1_1/evereq/upload', + }), + + DEFAULT_LOGIN_USERNAME: str({ default: 'hut_pizza' }), + DEFAULT_LOGIN_PASSWORD: str({ default: '123456' }), + + LOGIN_LOGO: str({ default: 'assets/imgs/ever-logo.svg' }), + NO_INTERNET_LOGO: str({ default: 'assets/imgs/logo.png' }), + + COMPANY_NAME: str({ default: 'Ever Co. LTD' }), + APP_NAME: str({ default: 'Ever Merchant' }), + GOOGLE_MAPS_API_KEY: str({ default: '' }), + GOOGLE_ANALYTICS_API_KEY: str({ default: '' }), + FAKE_UUID: str({ default: uuid() }), + MIXPANEL_API_KEY: str({ default: '' }), + + MAP_MERCHANT_ICON_LINK: str({ + default: 'http://maps.google.com/mapfiles/kml/pal3/icon21.png', + }), + MAP_USER_ICON_LINK: str({ + default: 'http://maps.google.com/mapfiles/kml/pal3/icon48.png', + }), + MAP_CARRIER_ICON_LINK: str({ + default: 'http://maps.google.com/mapfiles/kml/pal4/icon54.png', + }), + + // For maintenance micro service. Ever maintenance API URL: https://maintenance.ever.co/status + SETTINGS_APP_TYPE: str({ default: 'merchant-tablet' }), + SETTINGS_MAINTENANCE_API_URL: str({ + default: '', + }), + DEFAULT_LANGUAGE: str({ default: 'en-US' }), + WEB_CONCURRENCY: num({ default: 1 }), + WEB_MEMORY: num({ default: 2048 }), + PORT: num({ default: 4202 }), + }, + opt +); diff --git a/packages/merchant-tablet-ionic/src/@shared/forms/helpers.ts b/packages/merchant-tablet-ionic/src/@shared/forms/helpers.ts new file mode 100644 index 0000000..f15ecd3 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/@shared/forms/helpers.ts @@ -0,0 +1,26 @@ +import { FormArray, FormGroup } from '@angular/forms'; +import * as _ from 'underscore.string'; + +export class FormHelpers { + /** + * Loop and mark all it has + * + * @param {FormGroup} formGroup + * @param markAs + * @param opts + * + */ + static deepMark( + formGroup: FormGroup | FormArray, + markAs: 'touched' | 'untouched' | 'dirty' | 'pristine' | 'pending', + opts = { onlySelf: false } + ): void { + Object.values(formGroup.controls).forEach((c) => { + if (c instanceof FormGroup || c instanceof FormArray) { + FormHelpers.deepMark(c, markAs, opts); + } else { + c[`markAs${_.capitalize(markAs)}`](opts); + } + }); + } +} diff --git a/packages/merchant-tablet-ionic/src/@shared/google-map/google-map.component.scss b/packages/merchant-tablet-ionic/src/@shared/google-map/google-map.component.scss new file mode 100644 index 0000000..ae33e3e --- /dev/null +++ b/packages/merchant-tablet-ionic/src/@shared/google-map/google-map.component.scss @@ -0,0 +1,4 @@ +.g-map { + height: 100%; + width: 100%; +} diff --git a/packages/merchant-tablet-ionic/src/@shared/google-map/google-map.component.ts b/packages/merchant-tablet-ionic/src/@shared/google-map/google-map.component.ts new file mode 100644 index 0000000..d22fe0f --- /dev/null +++ b/packages/merchant-tablet-ionic/src/@shared/google-map/google-map.component.ts @@ -0,0 +1,115 @@ +import { ViewChild, Component, Input, OnInit, OnDestroy, EventEmitter } from '@angular/core'; +import { Subject, Observable } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; + +@Component({ + selector: 'google-map', + styles: [ + ` + .g-map { + height: 220px; + width: 100%; + } + `, + ], + template: `
`, +}) +export class GoogleMapComponent implements OnInit, OnDestroy { + @ViewChild('gmap', { static: true }) + mapElement: any; + + @Input() + mapTypeEvent: Observable; + + @Input() + mapCoordEvent: EventEmitter; + + @Input() + mapGeometryEvent: Observable< + google.maps.GeocoderGeometry | google.maps.places.PlaceGeometry + >; + + map: google.maps.Map; + + private _mapMarker: google.maps.Marker; + + private _ngDestroy$ = new Subject(); + + ngOnInit() { + this._setupGoogleMap(); + this._listenForMapType(); + this._listenForMapCoordinates(); + this._listenForMapGeometry(); + } + + private _navigateTo(location: google.maps.LatLng) { + this.map.setCenter(location); + } + + private _listenForMapGeometry() { + this.mapGeometryEvent + .pipe(takeUntil(this._ngDestroy$)) + .subscribe((geometry) => { + if (geometry.viewport) { + this.map.fitBounds(geometry.viewport); + } else { + this.map.setCenter(geometry.location); + this.map.setZoom(16); + } + }); + } + + private _listenForMapType() { + if (this.mapTypeEvent) { + this.mapTypeEvent + .pipe(takeUntil(this._ngDestroy$)) + .subscribe((mapType: string) => { + this.map.setMapTypeId(mapType); + }); + } + } + + private _listenForMapCoordinates() { + this.mapCoordEvent + .pipe(takeUntil(this._ngDestroy$)) + .subscribe((location: google.maps.LatLng) => { + this._navigateTo(location); + this._addMapMarker(location); + }); + } + + private _setupGoogleMap() { + const optionsMap = { + center: new google.maps.LatLng(0, 0), + zoom: 14, + mapTypeId: google.maps.MapTypeId.ROADMAP, + }; + + this.map = new google.maps.Map( + this.mapElement.nativeElement, + optionsMap + ); + } + + private _addMapMarker(location: google.maps.LatLng) { + this._clearMarker(); + + this._mapMarker = new google.maps.Marker({ + map: this.map, + position: location, + // TODO: we probably should pass Title as parameter here + title: 'Store', + }); + } + + private _clearMarker() { + if (this._mapMarker) { + this._mapMarker.setMap(null); + } + } + + ngOnDestroy() { + this._ngDestroy$.next(); + this._ngDestroy$.complete(); + } +} diff --git a/packages/merchant-tablet-ionic/src/@shared/google-map/google-map.module.ts b/packages/merchant-tablet-ionic/src/@shared/google-map/google-map.module.ts new file mode 100644 index 0000000..f6d1a22 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/@shared/google-map/google-map.module.ts @@ -0,0 +1,8 @@ +import { NgModule } from '@angular/core'; +import { GoogleMapComponent } from './google-map.component'; + +@NgModule({ + declarations: [GoogleMapComponent], + exports: [GoogleMapComponent], +}) +export class GoogleMapModule {} diff --git a/packages/merchant-tablet-ionic/src/@shared/user/forms/basic-info/basic-info-form.component.html b/packages/merchant-tablet-ionic/src/@shared/user/forms/basic-info/basic-info-form.component.html new file mode 100644 index 0000000..d2a069f --- /dev/null +++ b/packages/merchant-tablet-ionic/src/@shared/user/forms/basic-info/basic-info-form.component.html @@ -0,0 +1,99 @@ +
+ + + +
+ {{ 'WAREHOUSE_VIEW.NEW_ORDER_VIEW.BASIC_INFO' | translate }} +
+
+
+ + + + + {{ + 'WAREHOUSE_VIEW.NEW_ORDER_VIEW.FIRST_NAME' + | translate + }} + + + + + + + + + + {{ + 'WAREHOUSE_VIEW.NEW_ORDER_VIEW.LAST_NAME' + | translate + }} + + + + + + + + + + {{ 'WAREHOUSE_VIEW.NEW_ORDER_VIEW.EMAIL' | translate }} + + + + + + + + + + + +
+
+
+ Invalid image +
+ +
+ +
+
+
+
+
+
+
+
diff --git a/packages/merchant-tablet-ionic/src/@shared/user/forms/basic-info/basic-info-form.component.scss b/packages/merchant-tablet-ionic/src/@shared/user/forms/basic-info/basic-info-form.component.scss new file mode 100644 index 0000000..f7e5e9a --- /dev/null +++ b/packages/merchant-tablet-ionic/src/@shared/user/forms/basic-info/basic-info-form.component.scss @@ -0,0 +1,12 @@ +.email-err-wrapper { + padding: 0 19px; + text-align: left; + font-size: 14px; +} + +ion-item { + padding: 0 !important; + .item-native { + padding: 0 !important; + } +} diff --git a/packages/merchant-tablet-ionic/src/@shared/user/forms/basic-info/basic-info-form.component.ts b/packages/merchant-tablet-ionic/src/@shared/user/forms/basic-info/basic-info-form.component.ts new file mode 100644 index 0000000..4fd8cce --- /dev/null +++ b/packages/merchant-tablet-ionic/src/@shared/user/forms/basic-info/basic-info-form.component.ts @@ -0,0 +1,176 @@ +import { Component, Input, OnDestroy, OnInit, ViewChild } from '@angular/core'; +import { + FormBuilder, + FormGroup, + Validators, + FormControl, + AbstractControl, +} from '@angular/forms'; + +import { Subject } from 'rxjs'; +import { debounceTime, takeUntil } from 'rxjs/operators'; + +import User from '@modules/server.common/entities/User'; +import { IUserCreateObject } from '@modules/server.common/interfaces/IUser'; +import { UsersService } from '../../../../services/users.service'; +import { FormHelpers } from '../../../forms/helpers'; +import { AlertController } from '@ionic/angular'; + +export type CustomerBasicInfo = Pick< + IUserCreateObject, + 'firstName' | 'lastName' | 'email' | 'image' +>; + +@Component({ + selector: 'basic-info-form', + styleUrls: ['./basic-info-form.component.scss'], + templateUrl: 'basic-info-form.component.html', +}) +export class BasicInfoFormComponent implements OnInit, OnDestroy { + @Input() + readonly form: FormGroup; + @Input() + userData: User; + + private _ngDestroy$ = new Subject(); + private static _users: User[] = []; + private static _user: User; + + constructor( + private readonly _usersService: UsersService, + public alertController: AlertController + ) {} + + get firstName() { + return this.form.get('firstName'); + } + + get lastName() { + return this.form.get('lastName'); + } + + get email() { + return this.form.get('email'); + } + + get image() { + return this.form.get('image'); + } + + ngOnInit() { + BasicInfoFormComponent.initialize( + this._usersService, + this._ngDestroy$, + this.userData + ); + this.loadData(); + } + + deleteImg() { + this.image.setValue(''); + } + + static initialize( + usersService: UsersService, + ngDestroy: Subject, + userData?: User + ) { + usersService + .getUsers() + .pipe(takeUntil(ngDestroy)) + .subscribe((usersResult) => { + this._users = usersResult; + }); + + this._user = userData; + } + + static buildForm(formBuilder: FormBuilder): FormGroup { + const emailSearch$ = new Subject(); + let isSearchRdy = false; + + return formBuilder.group({ + firstName: [''], + lastName: [''], + email: [ + '', + [ + (control: AbstractControl) => + control.value ? Validators.email(control) : null, + ], + async (ctrlEmail: FormControl) => { + if (!isSearchRdy) { + // + emailSearch$.pipe(debounceTime(500)).subscribe(() => { + // + const hasExistedEmail = this._users.some( + (u) => u.email === ctrlEmail.value + ); + if ( + hasExistedEmail && + this._user && + this._user.email !== ctrlEmail.value + ) { + ctrlEmail.setErrors({ emailTaken: true }); + } + }); + + isSearchRdy = true; + } + + if ( + isSearchRdy && + ctrlEmail.value && + ctrlEmail.value.length > 0 + ) { + emailSearch$.next(true); + } + }, + ], + image: [''], + }); + } + + getValue(): CustomerBasicInfo { + const basicInfo = this.form.getRawValue() as { + firstName: string; + lastName: string; + email: string; + image: string; + }; + + return { + firstName: basicInfo.firstName, + lastName: basicInfo.lastName, + ...(basicInfo.email ? { email: basicInfo.email } : {}), + image: basicInfo.image, + }; + } + + setValue(basicInfo: T) { + FormHelpers.deepMark(this.form, 'dirty'); + + this.form.setValue({ + firstName: basicInfo.firstName ? basicInfo.firstName : '', + lastName: basicInfo.lastName ? basicInfo.lastName : '', + email: basicInfo.email ? basicInfo.email : '', + image: basicInfo.image ? basicInfo.image : '', + }); + } + + ngOnDestroy() { + this._ngDestroy$.next(); + this._ngDestroy$.complete(); + } + + private loadData() { + const userData = this.userData; + + if (userData) { + this.firstName.setValue(userData.firstName); + this.lastName.setValue(userData.lastName); + this.email.setValue(userData.email); + this.image.setValue(userData.image); + } + } +} diff --git a/packages/merchant-tablet-ionic/src/@shared/user/forms/location/location-form.component.html b/packages/merchant-tablet-ionic/src/@shared/user/forms/location/location-form.component.html new file mode 100644 index 0000000..f0f8cab --- /dev/null +++ b/packages/merchant-tablet-ionic/src/@shared/user/forms/location/location-form.component.html @@ -0,0 +1,234 @@ +
+ + + +
+ {{ 'WAREHOUSE_VIEW.NEW_ORDER_VIEW.ADDRESS' | translate }} +
+
+
+ + + + + + + + + + + + {{ + 'WAREHOUSE_VIEW.NEW_ORDER_VIEW.COUNTRY' | translate + }}... + + + {{ country.name }} + + + + +
+ {{ + 'WAREHOUSE_VIEW.NEW_ORDER_VIEW.COUNTRY_IS_INVALID' + | translate + }}! +
+
+ + + + * + {{ 'WAREHOUSE_VIEW.NEW_ORDER_VIEW.CITY' | translate }} + + + + + +
+ {{ + 'WAREHOUSE_VIEW.NEW_ORDER_VIEW.CITY_IS_REQUIRED' + | translate + }}! +
+
+ + + + + {{ + 'WAREHOUSE_VIEW.NEW_ORDER_VIEW.ZIP' | translate + }} + + + + +
+ + + + + * + {{ + 'WAREHOUSE_VIEW.NEW_ORDER_VIEW.STREET_ADDRESS' + | translate + }} + + + + +
+ {{ + 'WAREHOUSE_VIEW.NEW_ORDER_VIEW.STREET_ADDRESS_IS_REQUIRED' + | translate + }}! +
+
+
+ + + + + * + {{ + 'WAREHOUSE_VIEW.NEW_ORDER_VIEW.HOUSE_№' | translate + }} + + + + +
+ {{ + 'WAREHOUSE_VIEW.NEW_ORDER_VIEW.HOUSE_IS_REQUIRED' + | translate + }}! +
+
+ + + + + {{ + 'WAREHOUSE_VIEW.NEW_ORDER_VIEW.APARTMENT' + | translate + }} + + + + + +
+ + + + + + {{ + 'WAREHOUSE_VIEW.NEW_ORDER_VIEW.SHOW_COORDINATES' + | translate + }} + + + + + + + + {{ + 'WAREHOUSE_VIEW.NEW_ORDER_VIEW.LATITUDE' | translate + }} + + + + + + + + + + {{ + 'WAREHOUSE_VIEW.NEW_ORDER_VIEW.LONGITUTE' + | translate + }} + + + + + + +
+
diff --git a/packages/merchant-tablet-ionic/src/@shared/user/forms/location/location-form.component.scss b/packages/merchant-tablet-ionic/src/@shared/user/forms/location/location-form.component.scss new file mode 100644 index 0000000..e42e03b --- /dev/null +++ b/packages/merchant-tablet-ionic/src/@shared/user/forms/location/location-form.component.scss @@ -0,0 +1,36 @@ +.customer-location-form { + .coord-box { + margin: auto !important; + + background: transparent !important; + div.item-inner { + border: none !important; + } + } + + .search-bar-wrapper { + margin: auto !important; + + padding-top: 0% !important; + padding-bottom: 0.4% !important; + } + + ion-searchbar { + div.searchbar-input-container { + input.searchbar-input { + border-radius: 3px !important; + } + } + } + + ion-select { + padding: 0; + margin: 5px 0 6px 0; + } + + .wrapper-err { + padding: 0 19px; + text-align: left; + font-size: 14px; + } +} diff --git a/packages/merchant-tablet-ionic/src/@shared/user/forms/location/location-form.component.ts b/packages/merchant-tablet-ionic/src/@shared/user/forms/location/location-form.component.ts new file mode 100644 index 0000000..7cce9b0 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/@shared/user/forms/location/location-form.component.ts @@ -0,0 +1,501 @@ +import { + Component, + Input, + EventEmitter, + Output, + ViewChild, + ElementRef, + AfterViewInit, + OnInit, +} from '@angular/core'; + +import { + FormArray, + FormBuilder, + FormControl, + FormGroup, + Validators, +} from '@angular/forms'; + +import GeoLocation, { + Country, + getCountryName, + countriesIdsToNamesArray, +} from '@modules/server.common/entities/GeoLocation'; + +import { pick, isEmpty } from 'lodash'; +import { IGeoLocationCreateObject } from '@modules/server.common/interfaces/IGeoLocation'; +import { FormHelpers } from '../../../forms/helpers'; +import { TranslateService } from '@ngx-translate/core'; +import { ProductLocalesService } from '@modules/client.common.angular2/locale/product-locales.service'; +import { AlertController } from '@ionic/angular'; +import User from '@modules/server.common/entities/User'; + +@Component({ + selector: 'location-form', + styleUrls: ['./location-form.component.scss'], + templateUrl: './location-form.component.html', +}) +export class LocationFormComponent implements OnInit, AfterViewInit { + OK: string = 'OK'; + CANCEL: string = 'CANCEL'; + PREFIX: string = 'WAREHOUSE_VIEW.SELECT_POP_UP.'; + + @Input() + readonly form: FormGroup; + + @Input() + readonly apartment?: FormControl; + + @Input() + userData: User; + + @Input() + showAutocompleteSearch: boolean = false; + + @Output() + mapCoordinatesEmitter = new EventEmitter< + google.maps.LatLng | google.maps.LatLngLiteral + >(); + + @Output() + mapGeometryEmitter = new EventEmitter< + google.maps.places.PlaceGeometry | google.maps.GeocoderGeometry + >(); + + @ViewChild('autocomplete') + searchElement: ElementRef; + + showCoordinates: boolean = false; + + private _lastUsedAddressText: string; + + constructor( + private readonly _alertController: AlertController, + private translate: TranslateService, + public readonly localeTranslateService: ProductLocalesService + ) {} + + get buttonOK() { + return this._translate(this.PREFIX + this.OK); + } + + get buttonCancel() { + return this._translate(this.PREFIX + this.CANCEL); + } + + get countries() { + return countriesIdsToNamesArray; + } + + get isCountryValid(): boolean { + return ( + this.countryId.errors && + (this.countryId.dirty || this.countryId.touched) + ); + } + + get isCityValid(): boolean { + return this.city.errors && (this.city.dirty || this.city.touched); + } + + get isStreetAddressValid(): boolean { + return ( + this.streetAddress.errors && + (this.streetAddress.dirty || this.streetAddress.touched) + ); + } + + get isHouseValid(): boolean { + return this.house.errors && (this.house.dirty || this.house.touched); + } + + get isLocationValid(): boolean { + return ( + this.coordinates.errors && + (this.coordinates.dirty || this.coordinates.touched) + ); + } + + get countryId() { + return this.form.get('countryId'); + } + + get city() { + return this.form.get('city'); + } + + get streetAddress() { + return this.form.get('streetAddress'); + } + + get house() { + return this.form.get('house'); + } + + get postcode() { + return this.form.get('postcode'); + } + + get coordinates() { + return this.form.get('loc').get('coordinates') as FormArray; + } + + static buildForm(formBuilder: FormBuilder): FormGroup { + const form = formBuilder.group({ + countryId: [Country.US], + city: ['', [Validators.required]], + streetAddress: ['', [Validators.required]], + house: ['', [Validators.required]], + postcode: [''], + loc: formBuilder.group({ + type: ['Point'], + coordinates: formBuilder.array([null, null]), + }), + }); + + return form; + } + + static buildApartmentForm(formBuilder: FormBuilder): FormControl { + return formBuilder.control(''); + } + + ngOnInit(): void { + this.loadData(); + } + + ngAfterViewInit() { + this._initGoogleAutocompleteApi(); + } + + toggleCoordinates() { + this.showCoordinates = !this.showCoordinates; + } + + onAddressChanges() { + if (this.showAutocompleteSearch) { + this._tryFindNewAddress(); + } + } + + onCoordinatesChanged() { + if (this.showAutocompleteSearch) { + this._tryFindNewCoordinates(); + } + } + + getValue(): IGeoLocationCreateObject { + const location = this.form.getRawValue() as IGeoLocationCreateObject; + if (!location.postcode) { + delete location.postcode; + } + return location; + } + + getApartment(): string { + // apartment is not part of geo location + if (!this.apartment) { + throw new Error("Form doesn't contain apartment"); + } + return this.apartment.value as string; + } + + setValue(geoLocation: T) { + FormHelpers.deepMark(this.form, 'dirty'); + + this.form.setValue({ + postcode: geoLocation.postcode || '', + ...(pick(geoLocation, Object.keys(this.getValue())) as any), + }); + + // This setup the form and map with new received values. + this._tryFindNewCoordinates(); + } + + setApartment(apartment: string) { + this.apartment.setValue(apartment); + } + + private _translate(key: string): string { + let translationResult = ''; + + this.translate.get(key).subscribe((res) => { + translationResult = res; + }); + + return translationResult; + } + + private async _applyFormattedAddress(address: string) { + if (this.searchElement) { + const inputElement = await this.searchElement['getInputElement'](); + inputElement.value = address; + } + } + + private _tryFindNewAddress() { + const house = this.house.value; + const streetAddress = this.streetAddress.value; + const city = this.city.value; + const countryName = getCountryName(+this.countryId.value); + + if ( + isEmpty(streetAddress) || + isEmpty(house) || + isEmpty(city) || + isEmpty(countryName) + ) { + return; + } + + const newAddress = `${house}${streetAddress}${city}${countryName}`; + + if (newAddress !== this._lastUsedAddressText) { + this._lastUsedAddressText = newAddress; + + const geocoder = new google.maps.Geocoder(); + + geocoder.geocode( + { + address: `${streetAddress} ${house}, ${city}`, + componentRestrictions: { + country: countryName, + }, + }, + (results, status) => { + if (status === google.maps.GeocoderStatus.OK) { + const formattedAddress = results[0].formatted_address; + const place: google.maps.GeocoderResult = results[0]; + + this._applyNewPlaceOnTheMap(place); + this._applyFormattedAddress(formattedAddress); + } + } + ); + } + } + + private _lat: number; + private _lng: number; + + private _tryFindNewCoordinates() { + const formCoordinates = this.coordinates.value; + this._lat = formCoordinates[0]; + this._lng = formCoordinates[1]; + + const geocoder = new google.maps.Geocoder(); + geocoder.geocode( + { + location: new google.maps.LatLng(this._lat, this._lng), + }, + (results, status) => { + if (status === google.maps.GeocoderStatus.OK) { + const formattedAddress = results[0].formatted_address; + const place = results[0]; + + const useGeometryLatLng = false; + this._applyNewPlaceOnTheMap(place, useGeometryLatLng); + this._applyFormattedAddress(formattedAddress); + } + } + ); + } + + private _emitCoordinates( + location: google.maps.LatLng | google.maps.LatLngLiteral + ) { + this.mapCoordinatesEmitter.emit(location); + } + + private _emitGeometry( + geometry: + | google.maps.places.PlaceGeometry + | google.maps.GeocoderGeometry + ) { + this.mapGeometryEmitter.emit(geometry); + } + + private async _popInvalidAddressMessage() { + const alert = await this._alertController.create({ + message: 'Invalid address, please try again!', + }); + await alert.present(); + + setTimeout(() => alert.dismiss(), 2000); + } + + private _setupGoogleAutocompleteOptions( + autocomplete: google.maps.places.Autocomplete + ) { + autocomplete['setFields'](['address_components', 'geometry']); + } + + private _applyNewPlaceOnTheMap( + place: google.maps.places.PlaceResult | google.maps.GeocoderResult, + useGeometryLatLng: boolean = true + ) { + if (place.geometry === undefined || place.geometry === null) { + this._popInvalidAddressMessage(); + return; + } + + if (useGeometryLatLng) { + const loc = place.geometry.location; + this._lat = loc.lat(); + this._lng = loc.lng(); + } + + // If the place has a geometry, then present it on a map. + this._emitGeometry(place.geometry); + + this._emitCoordinates(new google.maps.LatLng(this._lat, this._lng)); + + this._gatherAddressInformation(place); + } + + private _listenForGoogleAutocompleteAddressChanges( + autocomplete: google.maps.places.Autocomplete + ) { + autocomplete.addListener('place_changed', (_) => { + const place: google.maps.places.PlaceResult = autocomplete.getPlace(); + this._applyNewPlaceOnTheMap(place); + }); + } + + private _gatherAddressInformation( + locationResult: + | google.maps.GeocoderResult + | google.maps.places.PlaceResult + ) { + const longName = 'long_name'; + const shortName = 'short_name'; + + const neededAddressTypes = { + country: shortName, + locality: longName, + // 'neighborhood' is not need for now + // neighborhood: longName, + route: longName, + intersection: longName, + street_number: longName, + postal_code: longName, + administrative_area_level_1: shortName, + administrative_area_level_2: shortName, + administrative_area_level_3: shortName, + administrative_area_level_4: shortName, + administrative_area_level_5: shortName, + }; + + let streetName = ''; + let streetNumber = ''; // is house number also + let countryId = ''; + let postcode = ''; + let city = ''; + + locationResult.address_components.forEach((address) => { + const addressType = address.types[0]; + const addressTypeKey = neededAddressTypes[addressType]; + + const val = address[addressTypeKey]; + + switch (addressType) { + case 'country': + countryId = val; + break; + case 'locality': + case 'administrative_area_level_1': + case 'administrative_area_level_2': + case 'administrative_area_level_3': + case 'administrative_area_level_4': + case 'administrative_area_level_5': + if (city === '') { + city = val; + } + break; + case 'route': + case 'intersection': + if (streetName === '') { + streetName = val; + } + break; + case 'street_number': + streetNumber = val; + break; + case 'postal_code': + postcode = val; + break; + } + }); + + this._setFormLocationValues( + countryId, + city, + streetName, + streetNumber, + postcode + ); + } + + private async _initGoogleAutocompleteApi() { + if (this.searchElement) { + const inputElement = await this.searchElement['getInputElement'](); + + const autocomplete = new google.maps.places.Autocomplete( + inputElement + ); + + this._setupGoogleAutocompleteOptions(autocomplete); + + this._listenForGoogleAutocompleteAddressChanges(autocomplete); + } + } + + private _setFormLocationValues( + countryId, + city, + streetName, + streetNumber, + postcode + ) { + if (!isEmpty(countryId)) { + this.countryId.setValue(Country[countryId].toString()); + } + if (!isEmpty(city)) { + this.city.setValue(city); + } + if (!isEmpty(postcode)) { + this.postcode.setValue(postcode); + } + if (!isEmpty(streetName)) { + this.streetAddress.setValue(streetName); + } + if (!isEmpty(streetNumber)) { + this.house.setValue(streetNumber); + } + + this.coordinates.setValue([this._lat, this._lng]); + } + + private loadData() { + if (this.userData) { + const userGeoLocation: GeoLocation = new GeoLocation( + this.userData.geoLocation + ); + + this.city.setValue(userGeoLocation.city); + this.streetAddress.setValue(userGeoLocation.streetAddress); + this.house.setValue(userGeoLocation.house); + this.coordinates.setValue([ + userGeoLocation.coordinates.lat, + userGeoLocation.coordinates.lng, + ]); + this.countryId.setValue(userGeoLocation.countryId.toString()); + this.postcode.setValue(userGeoLocation.postcode); + + this.apartment.setValue(this.userData.apartment); + + this._tryFindNewCoordinates(); + } + } +} diff --git a/packages/merchant-tablet-ionic/src/@shared/user/forms/user-forms.module.ts b/packages/merchant-tablet-ionic/src/@shared/user/forms/user-forms.module.ts new file mode 100644 index 0000000..6305d9b --- /dev/null +++ b/packages/merchant-tablet-ionic/src/@shared/user/forms/user-forms.module.ts @@ -0,0 +1,22 @@ +import { CommonModule } from '@angular/common'; +import { NgModule } from '@angular/core'; +import { ReactiveFormsModule, FormsModule } from '@angular/forms'; +import { BasicInfoFormComponent } from './basic-info/basic-info-form.component'; +import { LocationFormComponent } from './location/location-form.component'; +import { TranslateModule } from '@ngx-translate/core'; +import { IonicModule } from '@ionic/angular'; +import { FileUploaderModule } from 'components/file-uploader/file-uploader.module'; + +@NgModule({ + imports: [ + CommonModule, + ReactiveFormsModule, + IonicModule, + FormsModule, + TranslateModule.forChild(), + FileUploaderModule, + ], + declarations: [BasicInfoFormComponent, LocationFormComponent], + exports: [BasicInfoFormComponent, LocationFormComponent], +}) +export class UserFormsModule {} diff --git a/packages/merchant-tablet-ionic/src/@shared/user/mutation/user-mutation.component.html b/packages/merchant-tablet-ionic/src/@shared/user/mutation/user-mutation.component.html new file mode 100644 index 0000000..e457001 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/@shared/user/mutation/user-mutation.component.html @@ -0,0 +1,76 @@ + + +

+ {{ + (user ? 'Edit customer' : 'CUSTOMERS_VIEW.ADD_CUSTOMER') | translate + }} +

+ + + + + + + + + + + + + + + + {{ 'WAREHOUSE_VIEW.NEW_ORDER_VIEW.BACK' | translate }} + + + {{ 'WAREHOUSE_VIEW.NEW_ORDER_VIEW.NEXT' | translate }} + + + {{ + (user + ? 'Done' + : 'WAREHOUSE_VIEW.NEW_ORDER_VIEW.ADD_CUSTOMER' + ) | translate + }} + + + + + + + + + +
diff --git a/packages/merchant-tablet-ionic/src/@shared/user/mutation/user-mutation.component.scss b/packages/merchant-tablet-ionic/src/@shared/user/mutation/user-mutation.component.scss new file mode 100644 index 0000000..a39777c --- /dev/null +++ b/packages/merchant-tablet-ionic/src/@shared/user/mutation/user-mutation.component.scss @@ -0,0 +1,50 @@ +.user-mutation-component { + ion-item { + // padding: 2% 5% !important; + // padding-top: 0 !important !important !important !important; + + padding-left: 19px !important; + padding-right: 19px !important; + padding-bottom: 9px !important; + padding-top: 0px !important; + + border-radius: 3px !important; + + transform: scale(1, 0.9); + font-size: 1.3em !important; + background: #ffffffc9 !important; + } + ion-col { + padding: 0 5px !important; + } + ion-row { + background: transparent !important; + padding: 0 !important; + } + + button { + margin-top: 2% !important; + margin-bottom: 2.4% !important; + } + + h5 { + margin-top: 0 !important; + margin-bottom: 2.5% !important; + } + + h2 { + padding: 0 !important; + } + + button { + text-transform: none !important; + } + + ion-button { + border: none !important; + } + + .close { + top: 7px !important; + } +} diff --git a/packages/merchant-tablet-ionic/src/@shared/user/mutation/user-mutation.component.ts b/packages/merchant-tablet-ionic/src/@shared/user/mutation/user-mutation.component.ts new file mode 100644 index 0000000..6164746 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/@shared/user/mutation/user-mutation.component.ts @@ -0,0 +1,194 @@ +import { + Component, + ViewChild, + EventEmitter, + Output, + Input, +} from '@angular/core'; +import { FormBuilder, FormControl, FormGroup } from '@angular/forms'; + +import { UserAuthRouter } from '@modules/client.common.angular2/routers/user-auth-router.service'; +import { BasicInfoFormComponent } from '../forms/basic-info/basic-info-form.component'; +import { LocationFormComponent } from '../forms/location/location-form.component'; +import { ModalController, ToastController } from '@ionic/angular'; +import User from '@modules/server.common/entities/User'; +import { UserRouter } from '@modules/client.common.angular2/routers/user-router.service'; +import { getDummyImage } from '@modules/server.common/utils'; + +@Component({ + selector: 'user-mutation', + templateUrl: './user-mutation.component.html', + styleUrls: ['./user-mutation.component.scss'], +}) +export class UserMutationComponent { + readonly form: FormGroup = this._formBuilder.group({ + basicInfo: BasicInfoFormComponent.buildForm(this._formBuilder), + apartment: LocationFormComponent.buildApartmentForm(this._formBuilder), + location: LocationFormComponent.buildForm(this._formBuilder), + }); + + readonly basicInfo = this.form.get('basicInfo') as FormGroup; + readonly apartment = this.form.get('apartment') as FormControl; + readonly location = this.form.get('location') as FormGroup; + + @ViewChild('basicInfoForm') + basicInfoForm: BasicInfoFormComponent; + + @ViewChild('locationForm') + locationForm: LocationFormComponent; + + @Input() + user: User; + + @Output() + customerIdEmitter = new EventEmitter(); + + @Input() + visible: boolean = true; + + @Output() + updateVisible = new EventEmitter(); + + mapCoordinatesEmitter = new EventEmitter< + google.maps.LatLng | google.maps.LatLngLiteral + >(); + + mapGeometryEmitter = new EventEmitter< + google.maps.places.PlaceGeometry | google.maps.GeocoderGeometry + >(); + + isNextStepAvailable: boolean = false; + + constructor( + private readonly _userAuthRouter: UserAuthRouter, + private readonly _formBuilder: FormBuilder, + private readonly modalController: ModalController, + private readonly userRouter: UserRouter, + private readonly toastController: ToastController + ) {} + + onCoordinatesChanges( + coords: google.maps.LatLng | google.maps.LatLngLiteral + ) { + this.mapCoordinatesEmitter.emit(coords); + } + + onGeometrySend( + geometry: + | google.maps.places.PlaceGeometry + | google.maps.GeocoderGeometry + ) { + this.mapGeometryEmitter.emit(geometry); + } + + broadcastCustomerId(customerId: string) { + this.customerIdEmitter.emit(customerId); + } + + changeState(): void { + this.visible = false; + this.updateVisible.emit(this.visible); + } + + async createCustomer() { + let userId: string; + let message: string; + try { + const userRegistrationInput = { + user: { + ...this.basicInfoForm.getValue(), + geoLocation: this.locationForm.getValue(), + apartment: this.locationForm.getApartment(), + }, + }; + + const userData = userRegistrationInput.user; + if (userData) { + userRegistrationInput.user = this.getDefaultImage(userData); + } + + // We reverse coordinates before create new customer, because in geoJSON standart + // the array of coordinates is in reverse order, instead of 'Lat' => 'Lng' the orders is 'Lng' => 'Lat' + userRegistrationInput.user.geoLocation.loc.coordinates.reverse(); + + const user = await this._userAuthRouter.register( + userRegistrationInput + ); + + userId = user.id; + + this.broadcastCustomerId(user.id); + const firstName = user.firstName; + const lastName = user.lastName; + + message = `Customer ${firstName ? firstName + ' ' : ''} ${ + lastName ? lastName + ' ' : '' + }(${user.id}) Created`; + } catch (err) { + message = `Error in creating customer: '${err.message}'!`; + } finally { + await this.presentToast(message); + if (this.visible) { + await this.modalController.dismiss(userId); + } + } + } + + async saveCustomer() { + const geoLocation = this.locationForm.getValue(); + geoLocation.loc.coordinates.reverse(); + + let updateUpdateData = { + ...this.basicInfoForm.getValue(), + geoLocation, + apartment: this.locationForm.getApartment(), + }; + + if (updateUpdateData) { + updateUpdateData = this.getDefaultImage(updateUpdateData); + } + + await this.userRouter.updateUser(this.user.id, updateUpdateData); + await this.modalController.dismiss(); + } + + cancelModal() { + this.modalController.dismiss(); + } + + private async presentToast(message: string) { + const toast = await this.toastController.create({ + message, + duration: 2000, + }); + toast.present(); + } + + private getDefaultImage(user) { + if (user && !user.image) { + const firstNameLetter = user.firstName + ? user.firstName.charAt(0).toUpperCase() + : ''; + + const lastNameLetter = user.lastName + ? user.lastName.charAt(0).toUpperCase() + : ''; + + if (firstNameLetter || lastNameLetter) { + user.image = getDummyImage( + 300, + 300, + firstNameLetter + lastNameLetter + ); + } else { + const firstCityLetter = user.geoLocation.city + .charAt(0) + .toUpperCase(); + + user.image = getDummyImage(300, 300, firstCityLetter); + } + } + + return user; + } +} diff --git a/packages/merchant-tablet-ionic/src/@shared/user/mutation/user-mutation.module.ts b/packages/merchant-tablet-ionic/src/@shared/user/mutation/user-mutation.module.ts new file mode 100644 index 0000000..188bc7f --- /dev/null +++ b/packages/merchant-tablet-ionic/src/@shared/user/mutation/user-mutation.module.ts @@ -0,0 +1,24 @@ +import { NgModule } from '@angular/core'; +import { UserMutationComponent } from './user-mutation.component'; +import { TranslateModule } from '@ngx-translate/core'; +import { UserFormsModule } from '../forms/user-forms.module'; +import { GoogleMapModule } from '../../google-map/google-map.module'; +import { UsersService } from '../../../services/users.service'; +import { IonicModule } from '@ionic/angular'; +import { CommonModule } from '@angular/common'; +import { FormsModule } from '@angular/forms'; + +@NgModule({ + imports: [ + TranslateModule.forChild(), + UserFormsModule, + GoogleMapModule, + IonicModule, + CommonModule, + FormsModule, + ], + providers: [UsersService], + exports: [UserMutationComponent], + declarations: [UserMutationComponent] +}) +export class UserMutationModule {} diff --git a/packages/merchant-tablet-ionic/src/app/+maintenance-info/maintenance-info.html b/packages/merchant-tablet-ionic/src/app/+maintenance-info/maintenance-info.html new file mode 100644 index 0000000..c621b79 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/app/+maintenance-info/maintenance-info.html @@ -0,0 +1,5 @@ +
diff --git a/packages/merchant-tablet-ionic/src/app/+maintenance-info/maintenance-info.module.guard.ts b/packages/merchant-tablet-ionic/src/app/+maintenance-info/maintenance-info.module.guard.ts new file mode 100644 index 0000000..3631a34 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/app/+maintenance-info/maintenance-info.module.guard.ts @@ -0,0 +1,28 @@ +import { Injectable } from '@angular/core'; +import { + Router, + CanActivate, + ActivatedRouteSnapshot, + RouterStateSnapshot, +} from '@angular/router'; +import { Store } from '../../services/store.service'; + +@Injectable() +export class MaintenanceModuleGuard implements CanActivate { + constructor( + private readonly router: Router, + private readonly store: Store + ) {} + + canActivate( + route: ActivatedRouteSnapshot, + state: RouterStateSnapshot + ): boolean { + const maintenanceMode = this.store.maintenanceMode; + if (!maintenanceMode) { + this.router.navigate(['']); + return false; + } + return true; + } +} diff --git a/packages/merchant-tablet-ionic/src/app/+maintenance-info/maintenance-info.module.ts b/packages/merchant-tablet-ionic/src/app/+maintenance-info/maintenance-info.module.ts new file mode 100644 index 0000000..8aa974b --- /dev/null +++ b/packages/merchant-tablet-ionic/src/app/+maintenance-info/maintenance-info.module.ts @@ -0,0 +1,26 @@ +import { NgModule } from '@angular/core'; +import { MaintenanceInfoPage } from './maintenance-info'; +import { PipesModule } from '@modules/client.common.angular2/pipes/pipes.module'; +import { Routes, RouterModule } from '@angular/router'; +import { CommonModule } from '@angular/common'; +import { FormsModule } from '@angular/forms'; +import { IonicModule } from '@ionic/angular'; + +const routes: Routes = [ + { + path: '', + component: MaintenanceInfoPage, + }, +]; + +@NgModule({ + declarations: [MaintenanceInfoPage], + imports: [ + RouterModule.forChild(routes), + CommonModule, + FormsModule, + PipesModule, + IonicModule, + ], +}) +export class MaintenanceInfoPageModule {} diff --git a/packages/merchant-tablet-ionic/src/app/+maintenance-info/maintenance-info.scss b/packages/merchant-tablet-ionic/src/app/+maintenance-info/maintenance-info.scss new file mode 100644 index 0000000..7a191cf --- /dev/null +++ b/packages/merchant-tablet-ionic/src/app/+maintenance-info/maintenance-info.scss @@ -0,0 +1,2 @@ +page-maintenance-info { +} diff --git a/packages/merchant-tablet-ionic/src/app/+maintenance-info/maintenance-info.ts b/packages/merchant-tablet-ionic/src/app/+maintenance-info/maintenance-info.ts new file mode 100644 index 0000000..0814957 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/app/+maintenance-info/maintenance-info.ts @@ -0,0 +1,51 @@ +import { Component } from '@angular/core'; +import { environment } from '../../../src/environments/environment'; +import { MaintenanceService } from '@modules/client.common.angular2/services/maintenance.service'; +import { Store } from '../../../src/services/store.service'; +import { Router } from '@angular/router'; + +@Component({ + selector: 'page-maintenance-info', + templateUrl: 'maintenance-info.html', +}) +export class MaintenanceInfoPage { + message: string; + interval: any; + constructor( + private maintenanceService: MaintenanceService, + private store: Store, + private router: Router + ) { + this.getMessage(); + this.getStatus(); + } + + get maintenanceMode() { + return this.store.maintenanceMode; + } + + async getMessage() { + this.message = await this.maintenanceService.getMessage( + this.maintenanceMode, + environment['SETTINGS_MAINTENANCE_API_URL'] + ); + } + + private async getStatus() { + this.interval = setInterval(async () => { + const status = await this.maintenanceService.getStatus( + environment['SETTINGS_APP_TYPE'], + environment['SETTINGS_MAINTENANCE_API_URL'] + ); + console.warn( + `Maintenance on '${this.store.maintenanceMode}': ${status}` + ); + + if (!status) { + clearInterval(this.interval); + this.store.clearMaintenanceMode(); + this.router.navigateByUrl(''); + } + }, 5000); + } +} diff --git a/packages/merchant-tablet-ionic/src/app/+server-down/server-down.module.ts b/packages/merchant-tablet-ionic/src/app/+server-down/server-down.module.ts new file mode 100644 index 0000000..68ccc88 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/app/+server-down/server-down.module.ts @@ -0,0 +1,40 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { FormsModule } from '@angular/forms'; +import { Routes, RouterModule } from '@angular/router'; +import { IonicModule } from '@ionic/angular'; +import { PipesModule } from '@modules/client.common.angular2/pipes/pipes.module'; +import { ServerDownPage } from './server-down.page'; +import { TranslateModule, TranslateLoader } from '@ngx-translate/core'; +import { HttpClient } from '@angular/common/http'; +import { TranslateHttpLoader } from '@ngx-translate/http-loader'; + +export function HttpLoaderFactory(http: HttpClient) { + return new TranslateHttpLoader(http, './assets/i18n/', '.json'); +} + +const routes: Routes = [ + { + path: '', + component: ServerDownPage, + }, +]; + +@NgModule({ + imports: [ + CommonModule, + FormsModule, + IonicModule, + RouterModule.forChild(routes), + PipesModule, + TranslateModule.forChild({ + loader: { + provide: TranslateLoader, + useFactory: HttpLoaderFactory, + deps: [HttpClient], + }, + }), + ], + declarations: [ServerDownPage], +}) +export class ServerDownPageModule {} diff --git a/packages/merchant-tablet-ionic/src/app/+server-down/server-down.page.html b/packages/merchant-tablet-ionic/src/app/+server-down/server-down.page.html new file mode 100644 index 0000000..b770de6 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/app/+server-down/server-down.page.html @@ -0,0 +1,13 @@ + diff --git a/packages/merchant-tablet-ionic/src/app/+server-down/server-down.page.scss b/packages/merchant-tablet-ionic/src/app/+server-down/server-down.page.scss new file mode 100644 index 0000000..45f2008 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/app/+server-down/server-down.page.scss @@ -0,0 +1,22 @@ +@import '~@ever-platform/common-angular/src/scss/everbie.common'; + +.info-page { + .server-down-content { + color: white; + padding: 20px; + + background: $brand; + + display: flex; + align-items: center; + justify-content: center; + text-align: center; + height: 100%; + flex-direction: column; + } + + .info-massage h3 { + color: red; + width: 100%; + } +} diff --git a/packages/merchant-tablet-ionic/src/app/+server-down/server-down.page.ts b/packages/merchant-tablet-ionic/src/app/+server-down/server-down.page.ts new file mode 100644 index 0000000..080d713 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/app/+server-down/server-down.page.ts @@ -0,0 +1,41 @@ +import { Component, OnDestroy } from '@angular/core'; +import { environment } from '../../environments/environment'; +import { Store } from '../../services/store.service'; +import { Location } from '@angular/common'; +import { ServerConnectionService } from '@modules/client.common.angular2/services/server-connection.service'; + +@Component({ + styleUrls: ['./server-down.page.scss'], + templateUrl: 'server-down.page.html', +}) +export class ServerDownPage implements OnDestroy { + noInternetLogo: string; + interval; + + constructor( + private store: Store, + private location: Location, + private serverConnectionService: ServerConnectionService + ) { + this.noInternetLogo = environment.NO_INTERNET_LOGO; + this.testConnection(); + } + + private async testConnection() { + this.interval = setInterval(async () => { + await this.serverConnectionService.checkServerConnection( + environment.SERVICES_ENDPOINT, + this.store + ); + + if (Number(this.store.serverConnection) !== 0) { + clearInterval(this.interval); + this.location.back(); + } + }, 5000); + } + + ngOnDestroy(): void { + clearInterval(this.interval); + } +} diff --git a/packages/merchant-tablet-ionic/src/app/app-routing.module.ts b/packages/merchant-tablet-ionic/src/app/app-routing.module.ts new file mode 100644 index 0000000..66a1df8 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/app/app-routing.module.ts @@ -0,0 +1,45 @@ +import { NgModule } from '@angular/core'; +import { RouterModule, Routes, ExtraOptions } from '@angular/router'; +import { PagesModuleGuard } from '../pages/pages.module.guard'; +import { MaintenanceModuleGuard } from './+maintenance-info/maintenance-info.module.guard'; + +const routes: Routes = [ + { + path: '', + loadChildren: () => + import('../pages/pages.module').then((m) => m.PagesModule), + canActivate: [PagesModuleGuard], + }, + { + path: 'maintenance-info', + loadChildren: () => + import('./+maintenance-info/maintenance-info.module').then( + (m) => m.MaintenanceInfoPageModule + ), + canActivate: [MaintenanceModuleGuard], + }, + { + path: 'server-down', + loadChildren: () => + import('./+server-down/server-down.module').then( + (m) => m.ServerDownPageModule + ), + }, + { + path: '**', + pathMatch: 'full', + redirectTo: '', + }, +]; + +const config: ExtraOptions = { + useHash: true, + enableTracing: true, +}; + +@NgModule({ + // imports: [RouterModule.forRoot(routes, config)], + imports: [RouterModule.forRoot(routes)], + exports: [RouterModule], +}) +export class AppRoutingModule {} diff --git a/packages/merchant-tablet-ionic/src/app/app.component.html b/packages/merchant-tablet-ionic/src/app/app.component.html new file mode 100644 index 0000000..43ba78c --- /dev/null +++ b/packages/merchant-tablet-ionic/src/app/app.component.html @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/merchant-tablet-ionic/src/app/app.component.ts b/packages/merchant-tablet-ionic/src/app/app.component.ts new file mode 100644 index 0000000..a4c7160 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/app/app.component.ts @@ -0,0 +1,178 @@ +import { Component } from '@angular/core'; +import { Location } from '@angular/common'; +import { Platform } from '@ionic/angular'; +import { StatusBar } from '@ionic-native/status-bar/ngx'; +import { SplashScreen } from '@ionic-native/splash-screen/ngx'; +import { Globalization } from '@ionic-native/globalization/ngx'; +import { Device } from '@ionic-native/device/ngx'; +import { GoogleAnalytics } from '@ionic-native/google-analytics/ngx'; +import { Network } from '@ionic-native/network/ngx'; +import { Mixpanel } from '@ionic-native/mixpanel/ngx'; +import { Intercom } from '@ionic-native/intercom/ngx'; +import { ScreenOrientation } from '@ionic-native/screen-orientation/ngx'; +import { TranslateService } from '@ngx-translate/core'; +import { DeviceRouter } from '@modules/client.common.angular2/routers/device-router.service'; +import ILanguage from '@modules/server.common/interfaces/ILanguage'; +import IPlatform from '@modules/server.common/interfaces/IPlatform'; +import { IDeviceCreateObject } from '@modules/server.common/interfaces/IDevice'; +import { Router } from '@angular/router'; +import { Store } from '../services/store.service'; +import { environment } from '../environments/environment'; + +@Component({ + selector: 'e-cu-root', + templateUrl: 'app.component.html', +}) +export class AppComponent { + constructor( + public platform: Platform, + public statusBar: StatusBar, + public splashScreen: SplashScreen, + private readonly store: Store, + private readonly router: Router, + private readonly location: Location, + private globalization: Globalization, + private device: Device, + private ga: GoogleAnalytics, + private _network: Network, + private mixpanel: Mixpanel, + private intercom: Intercom, + private screenOrientation: ScreenOrientation, + private deviceRouter: DeviceRouter, + private translate: TranslateService + ) { + this.initializeApp(); + } + + rootPage: any; + defaultLanguage = ''; + + initializeApp() { + this.platform.ready().then(() => { + this._watchNetworkConnection(); + + this.defaultLanguage = environment['DEFAULT_LANGUAGE']; + + const browserLang = this.translate.getBrowserLang(); + + if (this.defaultLanguage) { + this.translate.use(this.defaultLanguage); + } else { + this.translate.use( + browserLang.match(/en-US|bg-BG|he-IL|ru-RU|es-ES|fr-FR/) + ? browserLang + : 'en-US' + ); + } + + this.store.language = this.translate.currentLang; + + // Plugins only working on the mobile devices + if (this.device.platform) { + this.startGoogleAnalytics(); + this.preferredLanguage(); + this.startMixpanel(); + this.screenOrientation.lock( + this.screenOrientation.ORIENTATIONS.LANDSCAPE + ); + } + + if (!this.deviceId) { + this._registerDevice(); + } + + this.statusBar.styleBlackOpaque(); + + this.splashScreen.hide(); + }); + } + + get deviceId() { + return localStorage.getItem('_deviceId'); + } + + startGoogleAnalytics() { + setTimeout(() => { + this.ga + .startTrackerWithId(environment.GOOGLE_ANALYTICS_API_KEY, 30) + .then(() => { + console.log('Google analytics is ready now!'); + this.ga.trackView('test'); + // Tracker is ready + // You can now track pages or set additional information such as AppVersion or UserId + }) + .catch((e) => console.log('Error starting GoogleAnalytics', e)); + }, 3000); + } + + preferredLanguage() { + console.log('Preferred Language'); + this.globalization + .getPreferredLanguage() + .then((res) => { + this.store.language = res.value.substr(0, 2); + }) + .catch((e) => console.log(e)); + } + + deviceCreateObject(): IDeviceCreateObject { + const language = localStorage.getItem('_language') || 'en-US'; + if (!this.device.platform || this.device.platform === 'browser') { + return { + channelId: null, + language: language as ILanguage, + type: 'browser' as IPlatform, + // have to find way how to generate it from browser + uuid: environment.FAKE_UUID, + }; + } + return { + channelId: null, + language: language as ILanguage, + type: this.device.platform as IPlatform, + uuid: this.device.uuid, + }; + } + + startMixpanel() { + this.mixpanel.init(environment.MIXPANEL_API_KEY).then(() => { + console.log('Mixpanel is ready now!'); + this.mixpanel.track('App Booted'); + }); + } + + async openPage(page) { + await this.router.navigate(page.url); + } + + get maintenanceMode() { + return this.store.maintenanceMode; + } + + private _watchNetworkConnection() { + this._network.onDisconnect().subscribe(async (_) => { + console.error('network was disconnected :-('); + await this.router.navigate(['errors', 'connection-lost']); + }); + + this._network.onConnect().subscribe((_) => { + console.log('network connected!'); + this.location.back(); + setTimeout(() => { + if (this._network.type === 'wifi') { + console.log('we got a wifi connection, woohoo!'); + } + }, 3000); + }); + } + + private async _registerDevice() { + const deviceCreateObject = this.deviceCreateObject(); + + const device = await this.deviceRouter.create(deviceCreateObject); + + this.store.deviceId = device.id; + this.store.language = device.language; + this.store.platform = device.type; + } +} diff --git a/packages/merchant-tablet-ionic/src/app/app.module.ts b/packages/merchant-tablet-ionic/src/app/app.module.ts new file mode 100644 index 0000000..3ca08e0 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/app/app.module.ts @@ -0,0 +1,136 @@ +import { NgModule, APP_INITIALIZER } from '@angular/core'; +import { HttpClient, HttpClientModule } from '@angular/common/http'; +import { BrowserModule } from '@angular/platform-browser'; +import { InAppBrowser } from '@ionic-native/in-app-browser/ngx'; +import { SplashScreen } from '@ionic-native/splash-screen/ngx'; +import { StatusBar } from '@ionic-native/status-bar/ngx'; +import { RouteReuseStrategy } from '@angular/router'; +import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; +import { IonicModule, IonicRouteStrategy } from '@ionic/angular'; +import { IonicStorageModule } from '@ionic/storage-angular'; +import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; +import { TranslateHttpLoader } from '@ngx-translate/http-loader'; +import { CommonModule } from '@modules/client.common.angular2/common.module'; +import { MenuModule } from '../components/menu/menu.module'; +import { AppRoutingModule } from './app-routing.module'; +import { AppComponent } from './app.component'; +import { environment } from 'environments/environment'; +import { FileUploadModule } from 'ng2-file-upload'; +import { CallNumber } from '@ionic-native/call-number/ngx'; +import { EmailComposer } from '@ionic-native/email-composer/ngx'; +import { Globalization } from '@ionic-native/globalization/ngx'; +import { GoogleAnalytics } from '@ionic-native/google-analytics/ngx'; +import { Mixpanel } from '@ionic-native/mixpanel/ngx'; +import { Intercom } from '@ionic-native/intercom/ngx'; +import { ScreenOrientation } from '@ionic-native/screen-orientation/ngx'; +import { GraphQLModule } from '../graphql/apollo.config'; +import { Camera } from '@ionic-native/camera/ngx'; +import { Store } from '../services/store.service'; +import { UserMutationModule } from '../@shared/user/mutation/user-mutation.module'; +import { GoogleMapsLoader } from '@modules/client.common.angular2/services/googleMapsLoader'; +import { MaintenanceService } from '@modules/client.common.angular2/services/maintenance.service'; +import { BarcodeScanner } from '@ionic-native/barcode-scanner/ngx'; +import { PagesModuleGuard } from '../pages/pages.module.guard'; +import { MaintenanceModuleGuard } from './+maintenance-info/maintenance-info.module.guard'; +import { ServiceWorkerModule } from '@angular/service-worker'; +import { Network } from '@ionic-native/network/ngx'; +import { Device } from '@ionic-native/device/ngx'; +import { ServerConnectionService } from '@modules/client.common.angular2/services/server-connection.service'; + +@NgModule({ + declarations: [AppComponent], + imports: [ + BrowserModule, + AppRoutingModule, + HttpClientModule, + BrowserAnimationsModule, + MenuModule, + IonicModule.forRoot(), + IonicStorageModule.forRoot(), + GraphQLModule, + TranslateModule.forRoot({ + loader: { + provide: TranslateLoader, + useFactory: HttpLoaderFactory, + deps: [HttpClient], + }, + }), + CommonModule.forRoot({ + apiUrl: environment.SERVICES_ENDPOINT, + }), + FileUploadModule, + UserMutationModule, + ServiceWorkerModule.register('ngsw-worker.js', { + enabled: environment.production, + }), + ], + providers: [ + InAppBrowser, + SplashScreen, + StatusBar, + Network, + Device, + GoogleMapsLoader, + { + provide: APP_INITIALIZER, + useFactory: googleMapsLoaderFactory, + deps: [GoogleMapsLoader], + multi: true, + }, + ServerConnectionService, + { + provide: APP_INITIALIZER, + useFactory: serverConnectionFactory, + deps: [ServerConnectionService, Store], + multi: true, + }, + MaintenanceService, + { + provide: APP_INITIALIZER, + useFactory: maintenanceFactory, + deps: [MaintenanceService], + multi: true, + }, + // { provide: ErrorHandler, useClass: IonicErrorHandler }, + { provide: RouteReuseStrategy, useClass: IonicRouteStrategy }, + PagesModuleGuard, + MaintenanceModuleGuard, + Store, + CallNumber, + EmailComposer, + Globalization, + GoogleAnalytics, + Intercom, + Mixpanel, + ScreenOrientation, + Camera, + BarcodeScanner, + ], + bootstrap: [AppComponent] +}) +export class AppModule { + constructor() {} +} + +export function HttpLoaderFactory(http: HttpClient) { + return new TranslateHttpLoader(http, './assets/i18n/', '.json'); +} + +export function googleMapsLoaderFactory(provider: GoogleMapsLoader) { + return () => provider.load(environment.GOOGLE_MAPS_API_KEY); +} + +export function serverConnectionFactory( + provider: ServerConnectionService, + store: Store +) { + return () => provider.load(environment.SERVICES_ENDPOINT, store); +} + +export function maintenanceFactory(provider: MaintenanceService) { + return () => + provider.load( + environment['SETTINGS_APP_TYPE'], + environment['SETTINGS_MAINTENANCE_API_URL'] + ); +} diff --git a/packages/merchant-tablet-ionic/src/app/app.scss b/packages/merchant-tablet-ionic/src/app/app.scss new file mode 100644 index 0000000..06d36d7 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/app/app.scss @@ -0,0 +1,18 @@ +// http://ionicframework.com/docs/theming/ + +// App Global Sass +// -------------------------------------------------- +// Put style rules here that you want to apply globally. These +// styles are for the entire app and not just one component. +// Additionally, this file can be also used as an entry point +// to import other Sass files to be included in the output CSS. +// +// Shared Sass variables, which can be used to adjust Ionic's +// default Sass variables, belong in "theme/variables.scss". +// +// To declare rules for a specific mode, create a child rule +// for the .md, .ios, or .wp mode classes. The mode class is +// automatically applied to the element in the app. + +// Ionic multiple select globals. +@import './ionic-multiple-select'; diff --git a/packages/merchant-tablet-ionic/src/app/ionic-multiple-select.scss b/packages/merchant-tablet-ionic/src/app/ionic-multiple-select.scss new file mode 100644 index 0000000..472694a --- /dev/null +++ b/packages/merchant-tablet-ionic/src/app/ionic-multiple-select.scss @@ -0,0 +1,18 @@ +body > ion-app > ion-alert > div > div.alert-head { + padding: 19px 12px !important; +} + +ion-item#multiple-select { + padding-left: 6px; + + ion-select { + max-width: none !important; + width: 100% !important; + padding-left: 0 !important; + + > div.select-icon div.select-icon-inner { + color: #000; + opacity: 0.7; + } + } +} diff --git a/packages/merchant-tablet-ionic/src/app/main.ts b/packages/merchant-tablet-ionic/src/app/main.ts new file mode 100644 index 0000000..2470c95 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/app/main.ts @@ -0,0 +1,4 @@ +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; +import { AppModule } from './app.module'; + +platformBrowserDynamic().bootstrapModule(AppModule); diff --git a/packages/merchant-tablet-ionic/src/assets/fonts/FontAwesome.otf b/packages/merchant-tablet-ionic/src/assets/fonts/FontAwesome.otf new file mode 100644 index 0000000..401ec0f Binary files /dev/null and b/packages/merchant-tablet-ionic/src/assets/fonts/FontAwesome.otf differ diff --git a/packages/merchant-tablet-ionic/src/assets/fonts/fontawesome-webfont.eot b/packages/merchant-tablet-ionic/src/assets/fonts/fontawesome-webfont.eot new file mode 100644 index 0000000..e9f60ca Binary files /dev/null and b/packages/merchant-tablet-ionic/src/assets/fonts/fontawesome-webfont.eot differ diff --git a/packages/merchant-tablet-ionic/src/assets/fonts/fontawesome-webfont.svg b/packages/merchant-tablet-ionic/src/assets/fonts/fontawesome-webfont.svg new file mode 100644 index 0000000..855c845 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/assets/fonts/fontawesome-webfont.svg @@ -0,0 +1,2671 @@ + + + + +Created by FontForge 20120731 at Mon Oct 24 17:37:40 2016 + By ,,, +Copyright Dave Gandy 2016. All rights reserved. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/merchant-tablet-ionic/src/assets/fonts/fontawesome-webfont.ttf b/packages/merchant-tablet-ionic/src/assets/fonts/fontawesome-webfont.ttf new file mode 100644 index 0000000..35acda2 Binary files /dev/null and b/packages/merchant-tablet-ionic/src/assets/fonts/fontawesome-webfont.ttf differ diff --git a/packages/merchant-tablet-ionic/src/assets/fonts/fontawesome-webfont.woff b/packages/merchant-tablet-ionic/src/assets/fonts/fontawesome-webfont.woff new file mode 100644 index 0000000..400014a Binary files /dev/null and b/packages/merchant-tablet-ionic/src/assets/fonts/fontawesome-webfont.woff differ diff --git a/packages/merchant-tablet-ionic/src/assets/fonts/fontawesome-webfont.woff2 b/packages/merchant-tablet-ionic/src/assets/fonts/fontawesome-webfont.woff2 new file mode 100644 index 0000000..4d13fc6 Binary files /dev/null and b/packages/merchant-tablet-ionic/src/assets/fonts/fontawesome-webfont.woff2 differ diff --git a/packages/merchant-tablet-ionic/src/assets/i18n/bg-BG.json b/packages/merchant-tablet-ionic/src/assets/i18n/bg-BG.json new file mode 100644 index 0000000..407f461 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/assets/i18n/bg-BG.json @@ -0,0 +1,476 @@ +{ + "LANGUAGE": { + "ID": "bg-BG", + "NAME": "Bulgarian" + }, + "CURRENT_DIRECTION": "ltr", + "DASHBOARD": "Табло", + "SIDEBAR_SIDE": "left", + "SIDE_MENU": { + "TITLE": "Евър Търговци", + "GROUPS": { + "NO_TITLE": { + "DIVER_TITLE": "", + "ITEMS": { + "WAREHOUSE": "Табло" + } + }, + "SETTINGS": { + "SETTINGS": "Настройки", + "DIVER_TITLE": "Настройки", + "ITEMS": { + "LANGUAGE": "Език", + "SIGN_OUT": "Отписване" + } + }, + "INFO": { + "DIVER_TITLE": "Информация", + "ITEMS": { + "ABOUT_US": "За нас", + "TERMS_OF_USE": "Условия за ползване" + } + } + } + }, + "CUSTOMERS_VIEW": { + "CUSTOMERS": "Клиенти", + "ADD_CUSTOMER": "Добавяне на клиент", + "IMAGE": "Изображение", + "NAME": "Име", + "PHONE_NUMBER": "Телефонен номер", + "ADDRESSES": "Aдреси", + "ORDERS": "Поръчки", + "TOTAL": "Обща сума", + "E_MAIL": "Електронна поща", + "ADDRESS_POP_UP": { + "CUSTOMER_ADDRESS": "Адрес на клиента", + "COUNTRY": "Държава", + "STREET": "Улица", + "HOUSE": "Къща", + "APARTMENT": "Апартамент", + "COORDINATES": "Координати" + }, + "EMAIL_POP_UP": { + "CUSTOMER_EMAIL": "Клиентски имейл", + "SEND_E_MAIL": "Изпратете имейл" + }, + "ORDERS_POP_UP": { + "CUSTOMER_ORDERS": "Поръчки на клиенти", + "ALL_ORDERS": "Всички поръчки", + "TOTAL_ORDERS_SUM": "Сума на общите поръчки", + "COMPLATED": "Завършен", + "PAID": "Платен" + } + }, + "FILE_UPLOADER": { + "DRAG_AND_DROP_FILE_HERE": "Завлачи и пусни Изображение тук", + "PICTURE_URL": "URL адрес на картинката", + "BROWSE": "Разгледай" + }, + "CARRIERS_VIEW": { + "CARRIERS_CATALOG": { + "IMAGE": "Изображение", + "NAME": "Име", + "PHONE": "Телефон", + "ADDRESS": "Адрес", + "LOGO": "Лого" + }, + "TRACK_ORDER": "Проследяване на поръчка", + "TRACK_CARRIER": "Проследяване на доставчика", + "DELIVERY_TRACKING": "Проследяване на доставките", + "CARRIER": "Доставчик", + "STORE": "Магазин", + "DELIVERY_COUNT": "Брой на доставките:", + "CUSTOMER": "Клиент", + "CARRIERS": "Куриери", + "ADD_CARRIERS": "Добавяне на превозвачи", + "IMAGE": "Изображение", + "NAME": "ИМЕ", + "PHONE_NUMBER": "Телефонен номер", + "ADDRESSES": "Адрес", + "STATUS": "Статус", + "DELIVERIES": "Доставки", + "NO_CARRIERS": "Няма куриери", + "WORKING": "Работи", + "NOT_WORKING": "Не работи", + "BLOCKED": "Блокиран", + "DELIVERIES_POP_UP": { + "CARRIER_DELIVERIES": "Доставки на куриера", + "ALL_DELIVERIES": "Всички Доставки", + "DELIVERIES_TODAY": "Доставки от Днес", + "TOTAL_DISTANCE_TODAY": "Общо Разстояние Днес", + "CUSTOMER": "Клиент", + "WAREHOUSE": "Склад", + "STATUS": "Статус", + "DELIVERY": "Доставка", + "NO_DELIVERIES": "Няма Доставки", + "COMPLETED": "Завършена", + "PAID": "Платена" + }, + "ADDRESS": { + "CARRIER_ADDRESS": "Адрес на куриера", + "COUNTRY": "Държава", + "HOUSE": "Kъща", + "COORDINATES": "Координати" + }, + "EDIT_CARRIER": { + "EDIT_CARRIER": "Редактирайте носителя" + }, + "ADD_CARRIER": { + "ADD_CARRIER": "Добавете куриер", + "HOW_TO_ADD": "Как да добавите", + "SELECT_HOW_TO_ADD": "Изберете Как да добавите", + "ADD": "Добави", + "CARRIERS_CATALOG": "Каталог на куриерите", + "SELECT_FROM_CARRIERS_CATALOG": "Изберете от каталога на превозвачите", + "ADD_NEW_CARRIER": "Добавете нов куриер", + "FIRST_NAME": "Име", + "LAST_NAME": "Фамилия", + "EMAIL": "електронна поща", + "USERNAME": "Потребител", + "PASSWORD": "Парола", + "NAME": "Име", + "PHONE": "Телефон", + "ADDRESS": "Адреси", + "LOGO": "Лого", + "IS_ACTIVE": "активен", + "CITY": "Град", + "STREET": "Улица", + "HOUSE": "Къща", + "LATITUDE": "Географска ширина", + "LONGITUDE": "Географска дължина", + "COUNTRY": "Държава", + "OK": "Добре", + "CANCEL": "ОТКАЗ", + "PREVIOUS": "ПРЕДИШНА", + "BACK": "ОБРАТНО", + "NEXT": "СЛЕДВАЩИЯ", + "DONE": "СВЪРШЕН", + "LOCATION": "МЕСТОПОЛОЖЕНИЕ", + "ZIP": "пощенски код", + "PROFILE_DETAILS": "ДЕТАЙЛИ НА ПРОФИЛ", + "BROWSE": "РАЗГЛЕДАЙ", + "DRAG_AND_DROP_FILE_HERE": "Завлачи и пусни Изображение тук", + "PICTURE_URL": "URL адрес на картинката", + "ACCOUNT": "ОПИС", + "REPEAT": "Повторение", + "REPEAT_PASSWORD": "Повтори паролата", + "PASSWORD_DO_NOT_MATCH": "Паролата не съвпада", + "FIND_ADDRESS": "Намерете адрес", + "FORMS": { + "ERRORS": { + "FIRST_NAME_REQUIRED": "Името е задължително", + "LAST_NAME_REQUIRED": "Изисква се фамилно име", + "PHONE_REQUIRED": "Необходим е телефон", + "EMAIL_IS_REQUIRED": "Имейл е задължителен", + "MUST_CONTAIN_ONLY_LETTERS": "Трябва да съдържа само букви", + "PHONE_MUST_CONTAINS_ONLY(special_signs)AND_DIGIT_CHARACTER": "Телефонният номер може да започва с '+' и трябва да съдържа само само: '-,., (Интервал), #' и цифри" + } + } + }, + "PROMOTIONS": { + "PROMOTIONS": "Промоции", + "NO_PROMOTIONS": "Няма промоции", + "BASIC_INFO": "Основна информация", + "TITLE": "Заглавие", + "ACTIVE_FROM": "Активна от", + "ACTIVE_TO": "Активна до", + "PURCHASES_COUNT": "Брой покупки", + "IS_ACTIVE": "Активна", + "SELECT_DATE": "Изберете дата", + "DESCRIPTION": "Описание", + "SAVE": "Запази", + "NEXT": "Нататък", + "BACK": "Обратно", + "LANGUAGE": "Език" + }, + "SELECT LANGUAGE": { + "USA": "САЩ", + "ISRAEL": "Израел", + "BULGARIA": "България" + } + }, + "SETTINGS_VIEW": { + "SETTINGS": "Настройки", + "OPTIONS": "Oпции", + "COMMON": "Често срещани", + "LOCATION": "Mестоположение", + "ACCOUNT": "Oпис", + "NEW_USERNAME": "Ново потребителско име", + "NAME_MUST_BE_AT_LEAST_4_CHARACTERS_LONG": "Името трябва да е с дължина най-малко 4 знака", + "OLD_PASSWORD": "Стара парола", + "NEW_PASSWORD": "Нова парола", + "REPEAT_NEW_PASSWORD": "Повтори новата парола", + "PASSWORDS_DO_NOT_MATCH": "Паролите не съвпадат", + "SAVE_CHANGES": "Запази Промените", + "SAVE": "Запази", + "CHANGES": "Промени", + "BASIC_INFO": "Основна информация", + "NAME": "име", + "NAME_IS_REQUIRED": "Името е задължително", + "LOGO": "Лого", + "BROWSE": "Разгледай", + "CONTACT_INFO": "Информация за контакт", + "E_MAIL": "Електронна поща", + "PHON_NUMBER": "Телефонен номер", + "COUNTRY": "Държава", + "CITY": "град", + "CITY_IS_REQUIRED": "Градът е задължителен", + "POSTCODE_optional": "Пощенски код (по избор)", + "STREET": "улица", + "STREET_IS_REQUIRED": "Улицата е задължителна", + "HOUSE": "къща", + "HOUSE_IS_REQUIRED": "Къща се изисква", + "APARTMENT_optional": "Апартамент (опция)", + "AUTO_DETECT_COORDINATES": "Автоматично откриване на координати", + "LATITUDE": "Географска ширина", + "LATITUDE_IS_REQUIRED": "Latitude е задължително", + "LONGITUDE": "дължина", + "LONGITUDE_IS_REQUIRED": "Необходима е дължина", + "FIND_ADDRESS": "Намерете адрес", + "OK": "Добре", + "CANCEL": "ОТКАЗ", + "ALLOW_ONLINE_PAYMENTS": "Разрешаване на онлайн плащания", + "SETTINGS_SECTION": { + "DELIVERY_OR_TAKEAWAY_SETTINGS": "Настройки за доставки или за взимане от място", + "PRODUCTS_DELIVERY_BY_DEFAULT": "Доставка на продукти (по подразбиране)", + "PRODUCTS_TAKEAWAY_BY_DEFAULT": "Продукти за взимане от място (по подразбиране)", + "PAYMENTS_SETTINGS": "Настройки за плащания", + "BARCODE_QR_CODE_SETTINGS": "Настройки за баркод / QR код", + "ORDER_BARCODE_TYPE": "Тип баркод на поръчката", + "BARCODE_DATA": "Данни за баркод", + "SCAN_EXISTED": "Сканиране на текущите", + "DELIVERY_SETTINGS": "Настройки за доставка", + "TAKEAWAY_SETTINGS": "Настройки за взимане от място", + "IN_STORE_MODE": "В магазинен режим", + "IN_STORE_MODE_SETTINGS": "В магазинен режим настройки" + } + }, + "LOGIN_VIEW": { + "LOGIN": "Влез" + }, + "LANGUAGE_VIEW": { + "TITLE": "Избери Език", + "OK": "Добре", + "CANCEL": "ОТКАЗ" + }, + "ABOUT_VIEW": { + "TITLE": "За нас", + "APP_VERSION": "Версия на приложението" + }, + "TERMS_OF_USE_VIEW": { + "TITLE": "Условия за ползване" + }, + "WAREHOUSE_VIEW": { + "RELEVANT": "Приоритетни", + "ALL": "Всички", + "LOADING_MORE_DATA": "Още данни се зареждат ...", + "ORDERS": "Поръчки", + "SWITCH_ORDERS": "Филтрирай Поръчките", + "NEW_ORDER": "Нова поръчка", + "PRODUCTS": "Топ Продукти", + "ARE_YOU_SURE_TO_DELETE": "Сигурни ли сте, че искате да изтриете ", + "REMOVE": "Премахни", + "DELETE": "Изтрий", + "THE_FOLLOWING_DATA": "следната информация", + "CONFIRM": "Потвърди", + "CANCEL": "Отмени", + "SELECT_POP_UP": { + "SELECT_CATEGORIES": "Изберете Категории", + "OK": "Добре", + "CANCEL": "ОТКАЗ" + }, + "ORDER_WAREHOUSE_STATUSES": { + "ALL": "Всички", + "CANCELLED": "Отменени", + "CREATED": "Създаден", + "CONFIRMED": "Потвърдено", + "PROCESSING": "Обработване", + "ALLOCATION_STARTED": "Разпределението е започнало", + "ALLOCATION_FINISHED": "Разпределението е завършено", + "PACKAGING_STARTED": "Опаковането започна", + "PACKAGED": "Опакован", + "GIVEN_TO_CARRIER": "Дадено на доставчик", + "TAKEN": "Взета", + "ALLOCATION_FAILED": "Разпределението не бе успешно", + "PACKAGING_FAILED": "Опаковането не бе успешно", + "BAD_STATUS": "Лошо състояние", + "NO_ACTIVE_ORDERS": "Няма активни поръчки", + "NO_ORDERS": "Няма поръчки", + "NO_PRODUCTS": "Няма продукти" + }, + "CREATE_PRODUCTS_POPUP": { + "ERROR_MSG": "Нещо не е наред, не можете да направите поръчка!", + "NEW_PRODUCT_TYPE": "Нов тип продукт", + "CREATE_NEW_PRODUCT_TYPE": "Създай нов продукт", + "TITLE": "Заглавие", + "DESCRIPTION": "Описание", + "CATEGORIES": "Категории", + "PRICE": "Цена", + "COUNT": "Брой", + "LANGUAGE": "Език", + "COUNT_PLACEHOLDER": "0", + "DRAG&DROP_FILE_HERE": "Завлачи и пусни файла тук", + "DRAG&DROP_PICTURE_HERE": "Завлачи и пусни снимката тук", + "OR_BROWSE": "или прегледайте", + "CLICK_TO_UPLOAD_PICTURE": "Качи снимка", + "CLICK_TO_UPLOAD_MORE_PICTURE": "Кликнете, за да качите още снимки", + "CANCEL": "Отказ", + "CREATE": "Създай", + "ENGLISH": "Английски", + "HEBREW": "Иврит", + "RUSSIAN": "Руски", + "BULGARIAN": "Български", + "SPANISH": "Испански", + "FRENCH": "Френски", + "OPTIONAL": "по избор", + "DELIVERY": "Доставка", + "TAKEAWAY": "Вземи на място", + "PRODUCT_AVAILABILITY": "Наличност на продукта" + }, + "NEW_ORDER_VIEW": { + "NEW_MANUAL_ORDER": "Нова ръчна поръчка", + "SELECT_EXISTING_CUSTOMER": "Изберете съществуващ клиент", + "ADD_NEW_CUSTOMER": "Добавяне на нов клиент", + "FULL_NAME": "Пълно име", + "EMAIL": "електронна поща", + "PHONE": "телефон", + "ADDRESS": "адрес", + "IMAGE": "Изображение", + "PRODUCT": "продукт", + "PRICE": "Цена", + "AVAILABLE": "На разположение", + "COMMENT": "Коментар", + "AMOUNT": "Количество", + "MAKE_ORDER": "Направете поръчка", + "BASIC_INFO": "Основна информация", + "FIRST_NAME": "Първо име", + "LAST_NAME": "Фамилия", + "EMAIL_IS_INVALID": "Имейл адресът е невалиден", + "EMAIL_IS_ALREADY_IN_USE": "Основна информация", + "NEXT": "Следващия", + "ADD_CUSTOMER": "Добавяне на клиент", + "BACK": "Обратно", + "FIND_ADDRESS": "Намерете адрес", + "COUNTRY": "Държава", + "CITY": "град", + "COUNTRY_IS_INVALID": "Държавата е невалидна", + "CITY_IS_REQUIRED": "Градът е задължителен", + "ZIP": "пощенски код", + "STREET_ADDRESS": "Адрес на улица", + "STREET_ADDRESS_IS_REQUIRED": "Изисква се уличен адрес", + "HOUSE_№": "Къща №", + "APARTMENT": "апартамент", + "SHOW_COORDINATES": "Показване на координатите", + "LATITUDE": "Географска ширина", + "LONGITUTE": "дължина", + "HOUSE_IS_REQUIRED": "Къща се изисква" + }, + "EDIT_PICTURE": { + "PRODUCT_IMAGES": "Изображения на продукти", + "SAVE_IMAGES": "Запазване на изображенията" + }, + "MISC_TEXT": { + "CREATE_NEW_ORDER": "Създай Нова Поръчка", + "ADD_NEW_PRODUCT": "Добави Нов Продукт" + } + }, + "EDIT_PRODUCT_POP_UP": { + "EDIT_PRODUCT_TYPE": "Редактиране на типа продукт", + "UPDATE_AN_EXISTING_PRODUCT": "Актуализиране на съществуващ продукт", + "CLICK_TO_EDIT_PICTURES": "Кликнете върху „Редактиране на картини“", + "TITLE": "Заглавие", + "DESCRIPTION": "Описание", + "CATEGORIES": "Категории", + "PRICE": "Цена", + "COUNT": "Брой", + "LANGUAGE": "Език", + "DRAG&DROP_FILE_HERE": "Завлачи и пусни файла тук", + "OR_BROWSE": "или прегледайте", + "CLICK_TO_UPLAOD_PICTURE": "Кликнете, за да качите снимка", + "CANCEL": "Отказ", + "UPDATE": "Актуализация", + "ENGLISH": "Английски", + "HEBREW": "Иврит", + "RUSSIAN": "Руски", + "BULGARIAN": "Български", + "SPANISH": "Испански", + "FRENCH": "Френски" + }, + "ORDER_CANCELED_COMPONENT": { + "CANCELED": "Отменен", + "ORDER_CANCELED": "Поръчка отказана", + "DELIVERED": "Доставена", + "CREATED_AT": "Създадена на" + }, + "ORDER_CONTROL_BUTTONS_COMPONENT": { + "CONFIRM": "Потвърждавам", + "START_PROCESSING": "Стартирай обработка", + "START_ALLOCATION": "Започни разпределение", + "ALLOCATED": "Разпределени", + "START_PACKAGING": "Стартирай пакетиране", + "PACKAGED": "Пакетирано", + "FAILED": "Неуспешен опит", + "GIVEN_TO_CARRIER": "Даден на доставчик", + "GIVEN_TO_CUSTOMER": "Дадено на клиента" + }, + "ORDER_DELIVERED_COMPONENT": { + "DELIVERED": "Доставена", + "CREATED_AT": "Създадена на", + "DELIVERED_AT": "Доставена на", + "DELIVERED_TO": "Доставена до" + }, + "ORDER_DELIVERY_PROBLEM_COMPONENT": { + "DELIVERY_ISSUES": "Проблеми при доставката", + "ISSUES_DURING_DELIVERY_TO": "Проблеми по-време надоставка", + "BY": "От", + "CREATED_AT": "Създаден на" + }, + "ORDER_WAREHOUSE_PREPARATION_PROBLEM_COMPONENT": { + "CREATED_AT": "Създаден на" + }, + "ORDER_WITH_CARRIER_COMPONENT": { + "IN_DELIVERY_TO": "При доставка от", + "BY": "От", + "CREATED_AT": "Създадена на" + }, + "ORDER_WITHOUT_CARRIER_COMPONENT": { + "CREATED_AT": "Създадена на", + "CANT_PROCESSING_WITHOUT_PRODUCTS": "Не може да се обработва поръчката без продукти." + }, + "ORDER_TITLE_COMPONENT": { + "ORDER": "Поръчка", + "FROM": "от" + }, + "ORDER_TYPE": { + "TYPE": "Тип на поръчката:", + "DELIVERY": "Доставка", + "TAKEAWAY": "За вкъщи" + }, + "TRACK_ORDER_POP_UP": { + "CARRIER": "носител", + "TRACK": "път", + "CUSTOMER": "клиент" + }, + "CONNECTION_ERROR_VIEW": { + "TITLE": "Няма връзка с някога", + "DESCRIPTION": [ + "Моля, уверете се, че сте", + "свързани с интернет", + "или да опитате по-късно." + ] + }, + "CUSTOMER_ORDERS_POP_UP": { + "ORDER_ID": "Идентификатор на поръчката", + "DELIVERY": "Доставка", + "ADDRESS": "Адрес", + "STATUS": "Статус" + }, + "NO_SERVER_VIEW": { + "NO_SERVER": "Няма връзка със сървъра" + }, + "PROMOTIONS": { + "PROMO_PRICE": "Промо цена", + "NO_PROMOTIONS": "Няма промоции" + } +} diff --git a/packages/merchant-tablet-ionic/src/assets/i18n/bg.json b/packages/merchant-tablet-ionic/src/assets/i18n/bg.json new file mode 100644 index 0000000..407f461 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/assets/i18n/bg.json @@ -0,0 +1,476 @@ +{ + "LANGUAGE": { + "ID": "bg-BG", + "NAME": "Bulgarian" + }, + "CURRENT_DIRECTION": "ltr", + "DASHBOARD": "Табло", + "SIDEBAR_SIDE": "left", + "SIDE_MENU": { + "TITLE": "Евър Търговци", + "GROUPS": { + "NO_TITLE": { + "DIVER_TITLE": "", + "ITEMS": { + "WAREHOUSE": "Табло" + } + }, + "SETTINGS": { + "SETTINGS": "Настройки", + "DIVER_TITLE": "Настройки", + "ITEMS": { + "LANGUAGE": "Език", + "SIGN_OUT": "Отписване" + } + }, + "INFO": { + "DIVER_TITLE": "Информация", + "ITEMS": { + "ABOUT_US": "За нас", + "TERMS_OF_USE": "Условия за ползване" + } + } + } + }, + "CUSTOMERS_VIEW": { + "CUSTOMERS": "Клиенти", + "ADD_CUSTOMER": "Добавяне на клиент", + "IMAGE": "Изображение", + "NAME": "Име", + "PHONE_NUMBER": "Телефонен номер", + "ADDRESSES": "Aдреси", + "ORDERS": "Поръчки", + "TOTAL": "Обща сума", + "E_MAIL": "Електронна поща", + "ADDRESS_POP_UP": { + "CUSTOMER_ADDRESS": "Адрес на клиента", + "COUNTRY": "Държава", + "STREET": "Улица", + "HOUSE": "Къща", + "APARTMENT": "Апартамент", + "COORDINATES": "Координати" + }, + "EMAIL_POP_UP": { + "CUSTOMER_EMAIL": "Клиентски имейл", + "SEND_E_MAIL": "Изпратете имейл" + }, + "ORDERS_POP_UP": { + "CUSTOMER_ORDERS": "Поръчки на клиенти", + "ALL_ORDERS": "Всички поръчки", + "TOTAL_ORDERS_SUM": "Сума на общите поръчки", + "COMPLATED": "Завършен", + "PAID": "Платен" + } + }, + "FILE_UPLOADER": { + "DRAG_AND_DROP_FILE_HERE": "Завлачи и пусни Изображение тук", + "PICTURE_URL": "URL адрес на картинката", + "BROWSE": "Разгледай" + }, + "CARRIERS_VIEW": { + "CARRIERS_CATALOG": { + "IMAGE": "Изображение", + "NAME": "Име", + "PHONE": "Телефон", + "ADDRESS": "Адрес", + "LOGO": "Лого" + }, + "TRACK_ORDER": "Проследяване на поръчка", + "TRACK_CARRIER": "Проследяване на доставчика", + "DELIVERY_TRACKING": "Проследяване на доставките", + "CARRIER": "Доставчик", + "STORE": "Магазин", + "DELIVERY_COUNT": "Брой на доставките:", + "CUSTOMER": "Клиент", + "CARRIERS": "Куриери", + "ADD_CARRIERS": "Добавяне на превозвачи", + "IMAGE": "Изображение", + "NAME": "ИМЕ", + "PHONE_NUMBER": "Телефонен номер", + "ADDRESSES": "Адрес", + "STATUS": "Статус", + "DELIVERIES": "Доставки", + "NO_CARRIERS": "Няма куриери", + "WORKING": "Работи", + "NOT_WORKING": "Не работи", + "BLOCKED": "Блокиран", + "DELIVERIES_POP_UP": { + "CARRIER_DELIVERIES": "Доставки на куриера", + "ALL_DELIVERIES": "Всички Доставки", + "DELIVERIES_TODAY": "Доставки от Днес", + "TOTAL_DISTANCE_TODAY": "Общо Разстояние Днес", + "CUSTOMER": "Клиент", + "WAREHOUSE": "Склад", + "STATUS": "Статус", + "DELIVERY": "Доставка", + "NO_DELIVERIES": "Няма Доставки", + "COMPLETED": "Завършена", + "PAID": "Платена" + }, + "ADDRESS": { + "CARRIER_ADDRESS": "Адрес на куриера", + "COUNTRY": "Държава", + "HOUSE": "Kъща", + "COORDINATES": "Координати" + }, + "EDIT_CARRIER": { + "EDIT_CARRIER": "Редактирайте носителя" + }, + "ADD_CARRIER": { + "ADD_CARRIER": "Добавете куриер", + "HOW_TO_ADD": "Как да добавите", + "SELECT_HOW_TO_ADD": "Изберете Как да добавите", + "ADD": "Добави", + "CARRIERS_CATALOG": "Каталог на куриерите", + "SELECT_FROM_CARRIERS_CATALOG": "Изберете от каталога на превозвачите", + "ADD_NEW_CARRIER": "Добавете нов куриер", + "FIRST_NAME": "Име", + "LAST_NAME": "Фамилия", + "EMAIL": "електронна поща", + "USERNAME": "Потребител", + "PASSWORD": "Парола", + "NAME": "Име", + "PHONE": "Телефон", + "ADDRESS": "Адреси", + "LOGO": "Лого", + "IS_ACTIVE": "активен", + "CITY": "Град", + "STREET": "Улица", + "HOUSE": "Къща", + "LATITUDE": "Географска ширина", + "LONGITUDE": "Географска дължина", + "COUNTRY": "Държава", + "OK": "Добре", + "CANCEL": "ОТКАЗ", + "PREVIOUS": "ПРЕДИШНА", + "BACK": "ОБРАТНО", + "NEXT": "СЛЕДВАЩИЯ", + "DONE": "СВЪРШЕН", + "LOCATION": "МЕСТОПОЛОЖЕНИЕ", + "ZIP": "пощенски код", + "PROFILE_DETAILS": "ДЕТАЙЛИ НА ПРОФИЛ", + "BROWSE": "РАЗГЛЕДАЙ", + "DRAG_AND_DROP_FILE_HERE": "Завлачи и пусни Изображение тук", + "PICTURE_URL": "URL адрес на картинката", + "ACCOUNT": "ОПИС", + "REPEAT": "Повторение", + "REPEAT_PASSWORD": "Повтори паролата", + "PASSWORD_DO_NOT_MATCH": "Паролата не съвпада", + "FIND_ADDRESS": "Намерете адрес", + "FORMS": { + "ERRORS": { + "FIRST_NAME_REQUIRED": "Името е задължително", + "LAST_NAME_REQUIRED": "Изисква се фамилно име", + "PHONE_REQUIRED": "Необходим е телефон", + "EMAIL_IS_REQUIRED": "Имейл е задължителен", + "MUST_CONTAIN_ONLY_LETTERS": "Трябва да съдържа само букви", + "PHONE_MUST_CONTAINS_ONLY(special_signs)AND_DIGIT_CHARACTER": "Телефонният номер може да започва с '+' и трябва да съдържа само само: '-,., (Интервал), #' и цифри" + } + } + }, + "PROMOTIONS": { + "PROMOTIONS": "Промоции", + "NO_PROMOTIONS": "Няма промоции", + "BASIC_INFO": "Основна информация", + "TITLE": "Заглавие", + "ACTIVE_FROM": "Активна от", + "ACTIVE_TO": "Активна до", + "PURCHASES_COUNT": "Брой покупки", + "IS_ACTIVE": "Активна", + "SELECT_DATE": "Изберете дата", + "DESCRIPTION": "Описание", + "SAVE": "Запази", + "NEXT": "Нататък", + "BACK": "Обратно", + "LANGUAGE": "Език" + }, + "SELECT LANGUAGE": { + "USA": "САЩ", + "ISRAEL": "Израел", + "BULGARIA": "България" + } + }, + "SETTINGS_VIEW": { + "SETTINGS": "Настройки", + "OPTIONS": "Oпции", + "COMMON": "Често срещани", + "LOCATION": "Mестоположение", + "ACCOUNT": "Oпис", + "NEW_USERNAME": "Ново потребителско име", + "NAME_MUST_BE_AT_LEAST_4_CHARACTERS_LONG": "Името трябва да е с дължина най-малко 4 знака", + "OLD_PASSWORD": "Стара парола", + "NEW_PASSWORD": "Нова парола", + "REPEAT_NEW_PASSWORD": "Повтори новата парола", + "PASSWORDS_DO_NOT_MATCH": "Паролите не съвпадат", + "SAVE_CHANGES": "Запази Промените", + "SAVE": "Запази", + "CHANGES": "Промени", + "BASIC_INFO": "Основна информация", + "NAME": "име", + "NAME_IS_REQUIRED": "Името е задължително", + "LOGO": "Лого", + "BROWSE": "Разгледай", + "CONTACT_INFO": "Информация за контакт", + "E_MAIL": "Електронна поща", + "PHON_NUMBER": "Телефонен номер", + "COUNTRY": "Държава", + "CITY": "град", + "CITY_IS_REQUIRED": "Градът е задължителен", + "POSTCODE_optional": "Пощенски код (по избор)", + "STREET": "улица", + "STREET_IS_REQUIRED": "Улицата е задължителна", + "HOUSE": "къща", + "HOUSE_IS_REQUIRED": "Къща се изисква", + "APARTMENT_optional": "Апартамент (опция)", + "AUTO_DETECT_COORDINATES": "Автоматично откриване на координати", + "LATITUDE": "Географска ширина", + "LATITUDE_IS_REQUIRED": "Latitude е задължително", + "LONGITUDE": "дължина", + "LONGITUDE_IS_REQUIRED": "Необходима е дължина", + "FIND_ADDRESS": "Намерете адрес", + "OK": "Добре", + "CANCEL": "ОТКАЗ", + "ALLOW_ONLINE_PAYMENTS": "Разрешаване на онлайн плащания", + "SETTINGS_SECTION": { + "DELIVERY_OR_TAKEAWAY_SETTINGS": "Настройки за доставки или за взимане от място", + "PRODUCTS_DELIVERY_BY_DEFAULT": "Доставка на продукти (по подразбиране)", + "PRODUCTS_TAKEAWAY_BY_DEFAULT": "Продукти за взимане от място (по подразбиране)", + "PAYMENTS_SETTINGS": "Настройки за плащания", + "BARCODE_QR_CODE_SETTINGS": "Настройки за баркод / QR код", + "ORDER_BARCODE_TYPE": "Тип баркод на поръчката", + "BARCODE_DATA": "Данни за баркод", + "SCAN_EXISTED": "Сканиране на текущите", + "DELIVERY_SETTINGS": "Настройки за доставка", + "TAKEAWAY_SETTINGS": "Настройки за взимане от място", + "IN_STORE_MODE": "В магазинен режим", + "IN_STORE_MODE_SETTINGS": "В магазинен режим настройки" + } + }, + "LOGIN_VIEW": { + "LOGIN": "Влез" + }, + "LANGUAGE_VIEW": { + "TITLE": "Избери Език", + "OK": "Добре", + "CANCEL": "ОТКАЗ" + }, + "ABOUT_VIEW": { + "TITLE": "За нас", + "APP_VERSION": "Версия на приложението" + }, + "TERMS_OF_USE_VIEW": { + "TITLE": "Условия за ползване" + }, + "WAREHOUSE_VIEW": { + "RELEVANT": "Приоритетни", + "ALL": "Всички", + "LOADING_MORE_DATA": "Още данни се зареждат ...", + "ORDERS": "Поръчки", + "SWITCH_ORDERS": "Филтрирай Поръчките", + "NEW_ORDER": "Нова поръчка", + "PRODUCTS": "Топ Продукти", + "ARE_YOU_SURE_TO_DELETE": "Сигурни ли сте, че искате да изтриете ", + "REMOVE": "Премахни", + "DELETE": "Изтрий", + "THE_FOLLOWING_DATA": "следната информация", + "CONFIRM": "Потвърди", + "CANCEL": "Отмени", + "SELECT_POP_UP": { + "SELECT_CATEGORIES": "Изберете Категории", + "OK": "Добре", + "CANCEL": "ОТКАЗ" + }, + "ORDER_WAREHOUSE_STATUSES": { + "ALL": "Всички", + "CANCELLED": "Отменени", + "CREATED": "Създаден", + "CONFIRMED": "Потвърдено", + "PROCESSING": "Обработване", + "ALLOCATION_STARTED": "Разпределението е започнало", + "ALLOCATION_FINISHED": "Разпределението е завършено", + "PACKAGING_STARTED": "Опаковането започна", + "PACKAGED": "Опакован", + "GIVEN_TO_CARRIER": "Дадено на доставчик", + "TAKEN": "Взета", + "ALLOCATION_FAILED": "Разпределението не бе успешно", + "PACKAGING_FAILED": "Опаковането не бе успешно", + "BAD_STATUS": "Лошо състояние", + "NO_ACTIVE_ORDERS": "Няма активни поръчки", + "NO_ORDERS": "Няма поръчки", + "NO_PRODUCTS": "Няма продукти" + }, + "CREATE_PRODUCTS_POPUP": { + "ERROR_MSG": "Нещо не е наред, не можете да направите поръчка!", + "NEW_PRODUCT_TYPE": "Нов тип продукт", + "CREATE_NEW_PRODUCT_TYPE": "Създай нов продукт", + "TITLE": "Заглавие", + "DESCRIPTION": "Описание", + "CATEGORIES": "Категории", + "PRICE": "Цена", + "COUNT": "Брой", + "LANGUAGE": "Език", + "COUNT_PLACEHOLDER": "0", + "DRAG&DROP_FILE_HERE": "Завлачи и пусни файла тук", + "DRAG&DROP_PICTURE_HERE": "Завлачи и пусни снимката тук", + "OR_BROWSE": "или прегледайте", + "CLICK_TO_UPLOAD_PICTURE": "Качи снимка", + "CLICK_TO_UPLOAD_MORE_PICTURE": "Кликнете, за да качите още снимки", + "CANCEL": "Отказ", + "CREATE": "Създай", + "ENGLISH": "Английски", + "HEBREW": "Иврит", + "RUSSIAN": "Руски", + "BULGARIAN": "Български", + "SPANISH": "Испански", + "FRENCH": "Френски", + "OPTIONAL": "по избор", + "DELIVERY": "Доставка", + "TAKEAWAY": "Вземи на място", + "PRODUCT_AVAILABILITY": "Наличност на продукта" + }, + "NEW_ORDER_VIEW": { + "NEW_MANUAL_ORDER": "Нова ръчна поръчка", + "SELECT_EXISTING_CUSTOMER": "Изберете съществуващ клиент", + "ADD_NEW_CUSTOMER": "Добавяне на нов клиент", + "FULL_NAME": "Пълно име", + "EMAIL": "електронна поща", + "PHONE": "телефон", + "ADDRESS": "адрес", + "IMAGE": "Изображение", + "PRODUCT": "продукт", + "PRICE": "Цена", + "AVAILABLE": "На разположение", + "COMMENT": "Коментар", + "AMOUNT": "Количество", + "MAKE_ORDER": "Направете поръчка", + "BASIC_INFO": "Основна информация", + "FIRST_NAME": "Първо име", + "LAST_NAME": "Фамилия", + "EMAIL_IS_INVALID": "Имейл адресът е невалиден", + "EMAIL_IS_ALREADY_IN_USE": "Основна информация", + "NEXT": "Следващия", + "ADD_CUSTOMER": "Добавяне на клиент", + "BACK": "Обратно", + "FIND_ADDRESS": "Намерете адрес", + "COUNTRY": "Държава", + "CITY": "град", + "COUNTRY_IS_INVALID": "Държавата е невалидна", + "CITY_IS_REQUIRED": "Градът е задължителен", + "ZIP": "пощенски код", + "STREET_ADDRESS": "Адрес на улица", + "STREET_ADDRESS_IS_REQUIRED": "Изисква се уличен адрес", + "HOUSE_№": "Къща №", + "APARTMENT": "апартамент", + "SHOW_COORDINATES": "Показване на координатите", + "LATITUDE": "Географска ширина", + "LONGITUTE": "дължина", + "HOUSE_IS_REQUIRED": "Къща се изисква" + }, + "EDIT_PICTURE": { + "PRODUCT_IMAGES": "Изображения на продукти", + "SAVE_IMAGES": "Запазване на изображенията" + }, + "MISC_TEXT": { + "CREATE_NEW_ORDER": "Създай Нова Поръчка", + "ADD_NEW_PRODUCT": "Добави Нов Продукт" + } + }, + "EDIT_PRODUCT_POP_UP": { + "EDIT_PRODUCT_TYPE": "Редактиране на типа продукт", + "UPDATE_AN_EXISTING_PRODUCT": "Актуализиране на съществуващ продукт", + "CLICK_TO_EDIT_PICTURES": "Кликнете върху „Редактиране на картини“", + "TITLE": "Заглавие", + "DESCRIPTION": "Описание", + "CATEGORIES": "Категории", + "PRICE": "Цена", + "COUNT": "Брой", + "LANGUAGE": "Език", + "DRAG&DROP_FILE_HERE": "Завлачи и пусни файла тук", + "OR_BROWSE": "или прегледайте", + "CLICK_TO_UPLAOD_PICTURE": "Кликнете, за да качите снимка", + "CANCEL": "Отказ", + "UPDATE": "Актуализация", + "ENGLISH": "Английски", + "HEBREW": "Иврит", + "RUSSIAN": "Руски", + "BULGARIAN": "Български", + "SPANISH": "Испански", + "FRENCH": "Френски" + }, + "ORDER_CANCELED_COMPONENT": { + "CANCELED": "Отменен", + "ORDER_CANCELED": "Поръчка отказана", + "DELIVERED": "Доставена", + "CREATED_AT": "Създадена на" + }, + "ORDER_CONTROL_BUTTONS_COMPONENT": { + "CONFIRM": "Потвърждавам", + "START_PROCESSING": "Стартирай обработка", + "START_ALLOCATION": "Започни разпределение", + "ALLOCATED": "Разпределени", + "START_PACKAGING": "Стартирай пакетиране", + "PACKAGED": "Пакетирано", + "FAILED": "Неуспешен опит", + "GIVEN_TO_CARRIER": "Даден на доставчик", + "GIVEN_TO_CUSTOMER": "Дадено на клиента" + }, + "ORDER_DELIVERED_COMPONENT": { + "DELIVERED": "Доставена", + "CREATED_AT": "Създадена на", + "DELIVERED_AT": "Доставена на", + "DELIVERED_TO": "Доставена до" + }, + "ORDER_DELIVERY_PROBLEM_COMPONENT": { + "DELIVERY_ISSUES": "Проблеми при доставката", + "ISSUES_DURING_DELIVERY_TO": "Проблеми по-време надоставка", + "BY": "От", + "CREATED_AT": "Създаден на" + }, + "ORDER_WAREHOUSE_PREPARATION_PROBLEM_COMPONENT": { + "CREATED_AT": "Създаден на" + }, + "ORDER_WITH_CARRIER_COMPONENT": { + "IN_DELIVERY_TO": "При доставка от", + "BY": "От", + "CREATED_AT": "Създадена на" + }, + "ORDER_WITHOUT_CARRIER_COMPONENT": { + "CREATED_AT": "Създадена на", + "CANT_PROCESSING_WITHOUT_PRODUCTS": "Не може да се обработва поръчката без продукти." + }, + "ORDER_TITLE_COMPONENT": { + "ORDER": "Поръчка", + "FROM": "от" + }, + "ORDER_TYPE": { + "TYPE": "Тип на поръчката:", + "DELIVERY": "Доставка", + "TAKEAWAY": "За вкъщи" + }, + "TRACK_ORDER_POP_UP": { + "CARRIER": "носител", + "TRACK": "път", + "CUSTOMER": "клиент" + }, + "CONNECTION_ERROR_VIEW": { + "TITLE": "Няма връзка с някога", + "DESCRIPTION": [ + "Моля, уверете се, че сте", + "свързани с интернет", + "или да опитате по-късно." + ] + }, + "CUSTOMER_ORDERS_POP_UP": { + "ORDER_ID": "Идентификатор на поръчката", + "DELIVERY": "Доставка", + "ADDRESS": "Адрес", + "STATUS": "Статус" + }, + "NO_SERVER_VIEW": { + "NO_SERVER": "Няма връзка със сървъра" + }, + "PROMOTIONS": { + "PROMO_PRICE": "Промо цена", + "NO_PROMOTIONS": "Няма промоции" + } +} diff --git a/packages/merchant-tablet-ionic/src/assets/i18n/en-US.json b/packages/merchant-tablet-ionic/src/assets/i18n/en-US.json new file mode 100644 index 0000000..ce8a607 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/assets/i18n/en-US.json @@ -0,0 +1,480 @@ +{ + "LANGUAGE": { + "ID": "en-US", + "NAME": "English" + }, + "DASHBOARD": "Dashboard", + "CURRENT_DIRECTION": "ltr", + "SIDEBAR_SIDE": "left", + "SIDE_MENU": { + "TITLE": "Ever Merchants", + "GROUPS": { + "NO_TITLE": { + "DIVER_TITLE": "", + "ITEMS": { + "WAREHOUSE": "Dashboard" + } + }, + "SETTINGS": { + "SETTINGS": "Settings", + "DIVER_TITLE": "Settings", + "ITEMS": { + "LANGUAGE": "Language", + "SIGN_OUT": "Sign out" + } + }, + "INFO": { + "DIVER_TITLE": "Info", + "ITEMS": { + "ABOUT_US": "About Us", + "TERMS_OF_USE": "Terms of Use" + } + } + } + }, + "CUSTOMERS_VIEW": { + "CUSTOMERS": "Customers", + "ADD_CUSTOMER": "Add customer", + "IMAGE": "Image", + "NAME": "Name", + "PHONE_NUMBER": "Phone number", + "ADDRESSES": "Addresses", + "ORDERS": "Orders", + "TOTAL": "Total", + "E_MAIL": "Email", + "ADDRESS_POP_UP": { + "CUSTOMER_ADDRESS": "Customer Address", + "COUNTRY": "Country", + "STREET": "Street", + "HOUSE": "House", + "APARTMENT": "Apartment", + "COORDINATES": "Coordinates" + }, + "EMAIL_POP_UP": { + "CUSTOMER_EMAIL": "Customer Email", + "SEND_E_MAIL": "Send E-mail" + }, + "ORDERS_POP_UP": { + "CUSTOMER_ORDERS": "Customer Orders", + "ALL_ORDERS": "All Orders", + "TOTAL_ORDERS_SUM": "Total Orders Sum", + "COMPLATED": "Completed", + "PAID": "Paid" + } + }, + "FILE_UPLOADER": { + "DRAG_AND_DROP_FILE_HERE": "Drag & Drop Image here", + "PICTURE_URL": "Picture url", + "BROWSE": "BROWSE" + }, + "CARRIERS_VIEW": { + "CARRIERS_CATALOG": { + "IMAGE": "Image", + "NAME": "Name", + "PHONE": "Phone", + "ADDRESS": "Address", + "LOGO": "Logo" + }, + "TRACK_ORDER": "Track order", + "TRACK_CARRIER": "Track Carrier", + "DELIVERY_TRACKING": "Delivery Tracking", + "CARRIER": "Carrier", + "STORE": "Store", + "DELIVERY_COUNT": "Delivery count:", + "CUSTOMER": "Customer", + "CARRIERS": "Carriers", + "ADD_CARRIERS": "Add Carriers", + "IMAGE": "Image", + "NAME": "Name", + "PHONE_NUMBER": "Phone number", + "ADDRESSES": "Address", + "STATUS": "Status", + "DELIVERIES": "Deliveries", + "NO_CARRIERS": "NO CARRIERS", + "WORKING": "Working", + "NOT_WORKING": "Not working", + "BLOCKED": "Blocked", + "DELIVERIES_POP_UP": { + "CARRIER_DELIVERIES": "Carrier Deliveries", + "ALL_DELIVERIES": "All Deliveries", + "DELIVERIES_TODAY": "Deliveries Today", + "TOTAL_DISTANCE_TODAY": "Total Distance Today", + "CUSTOMER": "Customer", + "WAREHOUSE": "Warehouse", + "STATUS": "Status", + "DELIVERY": "Delivery", + "NO_DELIVERIES": "No Deliveries", + "COMPLETED": "Completed", + "PAID": "Paid" + }, + "ADDRESS": { + "CARRIER_ADDRESS": "Carrier Address", + "COUNTRY": "Country", + "HOUSE": "House", + "COORDINATES": "Coordinates" + }, + "EDIT_CARRIER": { + "EDIT_CARRIER": "Edit carrier" + }, + "ADD_CARRIER": { + "ADD_CARRIER": "Add carrier", + "HOW_TO_ADD": "How to add", + "SELECT_HOW_TO_ADD": "Select How To Add", + "ADD": "Add", + "CARRIERS_CATALOG": "Carriers Catalog", + "SELECT_FROM_CARRIERS_CATALOG": "Select from Carriers Catalog", + "ADD_NEW_CARRIER": "Add new carrier", + "FIRST_NAME": "First Name", + "LAST_NAME": "Last Name", + "EMAIL": "Email", + "USERNAME": "Username", + "PASSWORD": "Password", + "NAME": "Name", + "PHONE": "Phone", + "ADDRESS": "Address", + "LOGO": "Logo", + "IS_ACTIVE": "Is active", + "CITY": "City", + "STREET": "Street", + "HOUSE": "House", + "LATITUDE": "Latitude", + "LONGITUDE": "Longitude", + "COUNTRY": "Country", + "OK": "OK", + "CANCEL": "CANCEL", + "PREVIOUS": "PREVIOUS", + "BACK": "BACK", + "NEXT": "NEXT", + "DONE": "DONE", + "LOCATION": "LOCATION", + "ZIP": "ZIP", + "PROFILE_DETAILS": "PROFILE DETAILS", + "BROWSE": "BROWSE", + "DRAG_AND_DROP_FILE_HERE": "Drag & Drop Image here", + "PICTURE_URL": "Picture url", + "ACCOUNT": "ACCOUNT", + "REPEAT": "Repeat", + "REPEAT_PASSWORD": "Repeat password", + "PASSWORD_DO_NOT_MATCH": "Password do not match", + "FIND_ADDRESS": "Find Address", + "FORMS": { + "ERRORS": { + "FIRST_NAME_REQUIRED": "First name is required", + "LAST_NAME_REQUIRED": "Last name is required", + "PHONE_REQUIRED": "Phone is required", + "EMAIL_IS_REQUIRED": "Email is required", + "MUST_CONTAIN_ONLY_LETTERS": "Must contain only letters", + "PHONE_MUST_CONTAINS_ONLY(special_signs)AND_DIGIT_CHARACTER": "Phone number can start with '+' or '(some numbers)' and must contains only only: '-, ., (space), #'' and digit characters" + } + } + }, + "PROMOTIONS": { + "PROMOTIONS": "Promotions", + "NO_PROMOTIONS": "No promotions", + "BASIC_INFO": "Basic Info", + "TITLE": "Title", + "ACTIVE_FROM": "Active from", + "ACTIVE_TO": "Active to", + "PURCHASES_COUNT": "Purchases count", + "IS_ACTIVE": "Is active", + "SELECT_DATE": "Select date", + "DESCRIPTION": "Description", + "SAVE": "Save", + "NEXT": "Next", + "BACK": "Back", + "LANGUAGE": "Language", + "ACTIVE": "Active", + "IMAGE": "Image", + "STATUS": "Status" + }, + "SELECT LANGUAGE": { + "USA": "USA", + "ISRAEL": "Israel", + "BULGARIA": "Bulgaria" + } + }, + "SETTINGS_VIEW": { + "SETTINGS": "Settings", + "OPTIONS": "Настройки", + "COMMON": "Common", + "LOCATION": "Location", + "ACCOUNT": "Account", + "NEW_USERNAME": "New Username", + "NAME_MUST_BE_AT_LEAST_4_CHARACTERS_LONG": "Name must be at least 4 characters long", + "OLD_PASSWORD": "Old Password", + "NEW_PASSWORD": "New Password", + "REPEAT_NEW_PASSWORD": "Repeat New Password", + "PASSWORDS_DO_NOT_MATCH": "Passwords do not match", + "SAVE_CHANGES": "Save Changes", + "SAVE": "Save", + "CHANGES": "Changes", + "BASIC_INFO": "Basic Info", + "NAME": "Name", + "NAME_IS_REQUIRED": "Name is required", + "LOGO": "Logo", + "BROWSE": "Browse", + "CONTACT_INFO": "Contact Info", + "E_MAIL": "E-mail", + "PHON_NUMBER": "Phone Number", + "COUNTRY": "Country", + "CITY": "City", + "CITY_IS_REQUIRED": "City is required", + "POSTCODE_optional": "Postcode (optional)", + "STREET": "Street", + "STREET_IS_REQUIRED": "Street is required", + "HOUSE": "House", + "HOUSE_IS_REQUIRED": "House is required", + "APARTMENT_optional": "Apartment (optional)", + "AUTO_DETECT_COORDINATES": "Auto detect coordinates", + "LATITUDE": "Latitude", + "LATITUDE_IS_REQUIRED": "Latitude is required", + "LONGITUDE": "Longitude", + "LONGITUDE_IS_REQUIRED": "Longitude is required", + "FIND_ADDRESS": "Find Address", + "OK": "OK", + "CANCEL": "CANCEL", + "ALLOW_ONLINE_PAYMENTS": "Allow online payments", + "SETTINGS_SECTION": { + "DELIVERY_OR_TAKEAWAY_SETTINGS": "Delivery/Takeaway Settings", + "PRODUCTS_DELIVERY_BY_DEFAULT": "Products Delivery (by default)", + "PRODUCTS_TAKEAWAY_BY_DEFAULT": "Products Takeaway (by default)", + "PAYMENTS_SETTINGS": "Payments Settings", + "BARCODE_QR_CODE_SETTINGS": "Barcode/QR code Settings", + "ORDER_BARCODE_TYPE": "Order barcode type", + "BARCODE_DATA": "Barcode data", + "SCAN_EXISTED": "Scan Existed", + "DELIVERY_SETTINGS": "Delivery settings", + "TAKEAWAY_SETTINGS": "Takeaway settings", + "IN_STORE_MODE": "In-store mode", + "IN_STORE_MODE_SETTINGS": "In-store mode settings" + } + }, + "LOGIN_VIEW": { + "LOGIN": "Login" + }, + "LANGUAGE_VIEW": { + "TITLE": "Select Language", + "OK": "OK", + "CANCEL": "CANCEL" + }, + "ABOUT_VIEW": { + "TITLE": "About Us", + "APP_VERSION": "App Version" + }, + "TERMS_OF_USE_VIEW": { + "TITLE": "Terms of Use" + }, + "WAREHOUSE_VIEW": { + "RELEVANT": "Relevant", + "ALL": "All", + "LOADING_MORE_DATA": "Loading more data...", + "ORDERS": "Orders", + "SWITCH_ORDERS": "Switch Orders", + "NEW_ORDER": "New Order", + "PRODUCTS": "Top Products", + "ARE_YOU_SURE_TO_DELETE": "Are you sure you want to ", + "REMOVE": "Remove", + "DELETE": "Delete", + "THE_FOLLOWING_DATA": "the following data", + "CONFIRM": "Confirm", + "CANCEL": "Cancel", + "SELECT_POP_UP": { + "SELECT_CATEGORIES": "Select Categories", + "OK": "OK", + "CANCEL": "CANCEL" + }, + "ORDER_WAREHOUSE_STATUSES": { + "ALL": "All", + "CANCELLED": "Cancelled", + "CREATED": "Created", + "CONFIRMED": "Confirmed", + "PROCESSING": "Processing", + "ALLOCATION_STARTED": "Allocation Started", + "ALLOCATION_FINISHED": "Allocation Finished", + "PACKAGING_STARTED": "Packaging Started", + "PACKAGED": "Packaged", + "GIVEN_TO_CARRIER": "Given to Carrier", + "TAKEN": "Taken", + "ALLOCATION_FAILED": "Allocation Failed", + "PACKAGING_FAILED": "Packaging Failed", + "BAD_STATUS": "Bad Status", + "NO_ACTIVE_ORDERS": "No Active Orders", + "NO_ORDERS": "No Orders", + "NO_PRODUCTS": "No Products" + }, + "CREATE_PRODUCTS_POPUP": { + "ERROR_MSG": "Something is wrong, unable to place order!", + "NEW_PRODUCT_TYPE": "New Product Type", + "CREATE_NEW_PRODUCT_TYPE": "Create a new product type", + "TITLE": "Title", + "DESCRIPTION": "Description", + "CATEGORIES": "Categories", + "PRICE": "Price", + "COUNT": "Count", + "LANGUAGE": "Language", + "COUNT_PLACEHOLDER": "0", + "DRAG&DROP_FILE_HERE": "Drag & Drop File here", + "DRAG&DROP_PICTURE_HERE": "Drag & Drop Picture here", + "OR_BROWSE": "or browse", + "CLICK_TO_UPLOAD_PICTURE": "Click to Upload Picture", + "CLICK_TO_UPLOAD_MORE_PICTURE": "Click to Upload More Pictures", + "CANCEL": "Cancel", + "CREATE": "Create", + "ENGLISH": "English", + "HEBREW": "Hebrew", + "RUSSIAN": "Russian", + "BULGARIAN": "Bulgarian", + "SPANISH": "Spanish", + "FRENCH": "French", + "OPTIONAL": "optional", + "TAKEAWAY": "Takeaway", + "DELIVERY": "Delivery", + "PRODUCT_AVAILABILITY": "Product availability" + }, + "NEW_ORDER_VIEW": { + "NEW_MANUAL_ORDER": "New Manual Order", + "SELECT_EXISTING_CUSTOMER": "Select Existing Customer", + "ADD_NEW_CUSTOMER": "Add New Customer", + "FULL_NAME": "Full Name", + "EMAIL": "Email", + "PHONE": "Phone", + "ADDRESS": "Address", + "IMAGE": "Image", + "PRODUCT": "Product", + "PRICE": "Price", + "AVAILABLE": "Available", + "COMMENT": "Comment", + "AMOUNT": "Amount", + "MAKE_ORDER": "Make Order", + "BASIC_INFO": "Basic Info", + "FIRST_NAME": "First Name", + "LAST_NAME": "Last Name", + "EMAIL_IS_INVALID": "Email is invalid", + "EMAIL_IS_ALREADY_IN_USE": "Email is already in use", + "NEXT": "Next", + "ADD_CUSTOMER": "Add Customer", + "BACK": "Back", + "FIND_ADDRESS": "Find Address", + "COUNTRY": "Country", + "CITY": "City", + "COUNTRY_IS_INVALID": "Country is invalid", + "CITY_IS_REQUIRED": "City is required", + "ZIP": "ZIP", + "STREET_ADDRESS": "Street Address", + "STREET_ADDRESS_IS_REQUIRED": "Street address is required", + "HOUSE_№": "House №", + "APARTMENT": "Apartment", + "SHOW_COORDINATES": "Show Coordinates", + "LATITUDE": "Latitude", + "LONGITUTE": "Longitude", + "HOUSE_IS_REQUIRED": "House is required" + }, + "EDIT_PICTURE": { + "PRODUCT_IMAGES": "Product images", + "SAVE_IMAGES": "Save images" + }, + "MISC_TEXT": { + "CREATE_NEW_ORDER": "Create New Order", + "ADD_NEW_PRODUCT": "Add New Product" + } + }, + "EDIT_PRODUCT_POP_UP": { + "EDIT_PRODUCT_TYPE": "Edit product type", + "UPDATE_AN_EXISTING_PRODUCT": "Update an existing product", + "CLICK_TO_EDIT_PICTURES": "Click to Edit Pictures", + "TITLE": "Title", + "DESCRIPTION": "Description", + "CATEGORIES": "Categories", + "PRICE": "Price", + "COUNT": "Count", + "LANGUAGE": "Language", + "DRAG&DROP_FILE_HERE": "Drag & Drop File here", + "OR_BROWSE": "or browse", + "CLICK_TO_UPLAOD_PICTURE": "Click to Upload Picture", + "CANCEL": "Cancel", + "UPDATE": "Update", + "ENGLISH": "English", + "HEBREW": "Hebrew", + "RUSSIAN": "Russian", + "BULGARIAN": "Bulgarian", + "SPANISH": "Spanish", + "FRENCH": "French", + "OPTIONAL": "optional" + }, + "ORDER_CANCELED_COMPONENT": { + "CANCELED": "Canceled", + "ORDER_CANCELED": "Order canceled", + "DELIVERED": "Delivered", + "CREATED_AT": "Created at" + }, + "ORDER_CONTROL_BUTTONS_COMPONENT": { + "CONFIRM": "Confirm", + "START_PROCESSING": "Start Processing", + "START_ALLOCATION": "Start Allocation", + "ALLOCATED": "Allocated", + "START_PACKAGING": "Start Packaging", + "PACKAGED": "Packaged", + "FAILED": "Failed", + "GIVEN_TO_CARRIER": "Given to Carrier", + "GIVEN_TO_CUSTOMER": "Given to Customer" + }, + "ORDER_DELIVERED_COMPONENT": { + "DELIVERED": "Delivered", + "CREATED_AT": "Created at", + "DELIVERED_AT": "Delivered at", + "DELIVERED_TO": "Delivered to" + }, + "ORDER_DELIVERY_PROBLEM_COMPONENT": { + "DELIVERY_ISSUES": "Delivery Issues", + "ISSUES_DURING_DELIVERY_TO": "Issues During Delivery to", + "BY": "By", + "CREATED_AT": "Created at" + }, + "ORDER_WAREHOUSE_PREPARATION_PROBLEM_COMPONENT": { + "CREATED_AT": "Created at" + }, + "ORDER_WITH_CARRIER_COMPONENT": { + "IN_DELIVERY_TO": "In Delivery To", + "BY": "By ", + "CREATED_AT": "Created at" + }, + "ORDER_WITHOUT_CARRIER_COMPONENT": { + "CREATED_AT": "Created at", + "CANT_PROCESSING_WITHOUT_PRODUCTS": "Can't processing the order without products." + }, + "ORDER_TITLE_COMPONENT": { + "ORDER": "Order", + "FROM": "from" + }, + "ORDER_TYPE": { + "TYPE": "Order type:", + "DELIVERY": "Delivery", + "TAKEAWAY": "Takeaway" + }, + "TRACK_ORDER_POP_UP": { + "CARRIER": "Carrier", + "TRACK": "Track", + "CUSTOMER": "Customer" + }, + "CONNECTION_ERROR_VIEW": { + "TITLE": "No connection to Ever", + "DESCRIPTION": [ + "Please make sure you are", + "connected to the internet", + "or try Ever app later." + ] + }, + "CUSTOMER_ORDERS_POP_UP": { + "ORDER_ID": "Order ID", + "DELIVERY": "Delivery", + "ADDRESS": "Address", + "STATUS": "Status" + }, + "NO_SERVER_VIEW": { + "NO_SERVER": "Server connection is lost" + }, + "PROMOTIONS": { + "PROMO_PRICE": "Promo price", + "NO_PROMOTIONS": "No Promotions" + } +} diff --git a/packages/merchant-tablet-ionic/src/assets/i18n/en.json b/packages/merchant-tablet-ionic/src/assets/i18n/en.json new file mode 100644 index 0000000..ce8a607 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/assets/i18n/en.json @@ -0,0 +1,480 @@ +{ + "LANGUAGE": { + "ID": "en-US", + "NAME": "English" + }, + "DASHBOARD": "Dashboard", + "CURRENT_DIRECTION": "ltr", + "SIDEBAR_SIDE": "left", + "SIDE_MENU": { + "TITLE": "Ever Merchants", + "GROUPS": { + "NO_TITLE": { + "DIVER_TITLE": "", + "ITEMS": { + "WAREHOUSE": "Dashboard" + } + }, + "SETTINGS": { + "SETTINGS": "Settings", + "DIVER_TITLE": "Settings", + "ITEMS": { + "LANGUAGE": "Language", + "SIGN_OUT": "Sign out" + } + }, + "INFO": { + "DIVER_TITLE": "Info", + "ITEMS": { + "ABOUT_US": "About Us", + "TERMS_OF_USE": "Terms of Use" + } + } + } + }, + "CUSTOMERS_VIEW": { + "CUSTOMERS": "Customers", + "ADD_CUSTOMER": "Add customer", + "IMAGE": "Image", + "NAME": "Name", + "PHONE_NUMBER": "Phone number", + "ADDRESSES": "Addresses", + "ORDERS": "Orders", + "TOTAL": "Total", + "E_MAIL": "Email", + "ADDRESS_POP_UP": { + "CUSTOMER_ADDRESS": "Customer Address", + "COUNTRY": "Country", + "STREET": "Street", + "HOUSE": "House", + "APARTMENT": "Apartment", + "COORDINATES": "Coordinates" + }, + "EMAIL_POP_UP": { + "CUSTOMER_EMAIL": "Customer Email", + "SEND_E_MAIL": "Send E-mail" + }, + "ORDERS_POP_UP": { + "CUSTOMER_ORDERS": "Customer Orders", + "ALL_ORDERS": "All Orders", + "TOTAL_ORDERS_SUM": "Total Orders Sum", + "COMPLATED": "Completed", + "PAID": "Paid" + } + }, + "FILE_UPLOADER": { + "DRAG_AND_DROP_FILE_HERE": "Drag & Drop Image here", + "PICTURE_URL": "Picture url", + "BROWSE": "BROWSE" + }, + "CARRIERS_VIEW": { + "CARRIERS_CATALOG": { + "IMAGE": "Image", + "NAME": "Name", + "PHONE": "Phone", + "ADDRESS": "Address", + "LOGO": "Logo" + }, + "TRACK_ORDER": "Track order", + "TRACK_CARRIER": "Track Carrier", + "DELIVERY_TRACKING": "Delivery Tracking", + "CARRIER": "Carrier", + "STORE": "Store", + "DELIVERY_COUNT": "Delivery count:", + "CUSTOMER": "Customer", + "CARRIERS": "Carriers", + "ADD_CARRIERS": "Add Carriers", + "IMAGE": "Image", + "NAME": "Name", + "PHONE_NUMBER": "Phone number", + "ADDRESSES": "Address", + "STATUS": "Status", + "DELIVERIES": "Deliveries", + "NO_CARRIERS": "NO CARRIERS", + "WORKING": "Working", + "NOT_WORKING": "Not working", + "BLOCKED": "Blocked", + "DELIVERIES_POP_UP": { + "CARRIER_DELIVERIES": "Carrier Deliveries", + "ALL_DELIVERIES": "All Deliveries", + "DELIVERIES_TODAY": "Deliveries Today", + "TOTAL_DISTANCE_TODAY": "Total Distance Today", + "CUSTOMER": "Customer", + "WAREHOUSE": "Warehouse", + "STATUS": "Status", + "DELIVERY": "Delivery", + "NO_DELIVERIES": "No Deliveries", + "COMPLETED": "Completed", + "PAID": "Paid" + }, + "ADDRESS": { + "CARRIER_ADDRESS": "Carrier Address", + "COUNTRY": "Country", + "HOUSE": "House", + "COORDINATES": "Coordinates" + }, + "EDIT_CARRIER": { + "EDIT_CARRIER": "Edit carrier" + }, + "ADD_CARRIER": { + "ADD_CARRIER": "Add carrier", + "HOW_TO_ADD": "How to add", + "SELECT_HOW_TO_ADD": "Select How To Add", + "ADD": "Add", + "CARRIERS_CATALOG": "Carriers Catalog", + "SELECT_FROM_CARRIERS_CATALOG": "Select from Carriers Catalog", + "ADD_NEW_CARRIER": "Add new carrier", + "FIRST_NAME": "First Name", + "LAST_NAME": "Last Name", + "EMAIL": "Email", + "USERNAME": "Username", + "PASSWORD": "Password", + "NAME": "Name", + "PHONE": "Phone", + "ADDRESS": "Address", + "LOGO": "Logo", + "IS_ACTIVE": "Is active", + "CITY": "City", + "STREET": "Street", + "HOUSE": "House", + "LATITUDE": "Latitude", + "LONGITUDE": "Longitude", + "COUNTRY": "Country", + "OK": "OK", + "CANCEL": "CANCEL", + "PREVIOUS": "PREVIOUS", + "BACK": "BACK", + "NEXT": "NEXT", + "DONE": "DONE", + "LOCATION": "LOCATION", + "ZIP": "ZIP", + "PROFILE_DETAILS": "PROFILE DETAILS", + "BROWSE": "BROWSE", + "DRAG_AND_DROP_FILE_HERE": "Drag & Drop Image here", + "PICTURE_URL": "Picture url", + "ACCOUNT": "ACCOUNT", + "REPEAT": "Repeat", + "REPEAT_PASSWORD": "Repeat password", + "PASSWORD_DO_NOT_MATCH": "Password do not match", + "FIND_ADDRESS": "Find Address", + "FORMS": { + "ERRORS": { + "FIRST_NAME_REQUIRED": "First name is required", + "LAST_NAME_REQUIRED": "Last name is required", + "PHONE_REQUIRED": "Phone is required", + "EMAIL_IS_REQUIRED": "Email is required", + "MUST_CONTAIN_ONLY_LETTERS": "Must contain only letters", + "PHONE_MUST_CONTAINS_ONLY(special_signs)AND_DIGIT_CHARACTER": "Phone number can start with '+' or '(some numbers)' and must contains only only: '-, ., (space), #'' and digit characters" + } + } + }, + "PROMOTIONS": { + "PROMOTIONS": "Promotions", + "NO_PROMOTIONS": "No promotions", + "BASIC_INFO": "Basic Info", + "TITLE": "Title", + "ACTIVE_FROM": "Active from", + "ACTIVE_TO": "Active to", + "PURCHASES_COUNT": "Purchases count", + "IS_ACTIVE": "Is active", + "SELECT_DATE": "Select date", + "DESCRIPTION": "Description", + "SAVE": "Save", + "NEXT": "Next", + "BACK": "Back", + "LANGUAGE": "Language", + "ACTIVE": "Active", + "IMAGE": "Image", + "STATUS": "Status" + }, + "SELECT LANGUAGE": { + "USA": "USA", + "ISRAEL": "Israel", + "BULGARIA": "Bulgaria" + } + }, + "SETTINGS_VIEW": { + "SETTINGS": "Settings", + "OPTIONS": "Настройки", + "COMMON": "Common", + "LOCATION": "Location", + "ACCOUNT": "Account", + "NEW_USERNAME": "New Username", + "NAME_MUST_BE_AT_LEAST_4_CHARACTERS_LONG": "Name must be at least 4 characters long", + "OLD_PASSWORD": "Old Password", + "NEW_PASSWORD": "New Password", + "REPEAT_NEW_PASSWORD": "Repeat New Password", + "PASSWORDS_DO_NOT_MATCH": "Passwords do not match", + "SAVE_CHANGES": "Save Changes", + "SAVE": "Save", + "CHANGES": "Changes", + "BASIC_INFO": "Basic Info", + "NAME": "Name", + "NAME_IS_REQUIRED": "Name is required", + "LOGO": "Logo", + "BROWSE": "Browse", + "CONTACT_INFO": "Contact Info", + "E_MAIL": "E-mail", + "PHON_NUMBER": "Phone Number", + "COUNTRY": "Country", + "CITY": "City", + "CITY_IS_REQUIRED": "City is required", + "POSTCODE_optional": "Postcode (optional)", + "STREET": "Street", + "STREET_IS_REQUIRED": "Street is required", + "HOUSE": "House", + "HOUSE_IS_REQUIRED": "House is required", + "APARTMENT_optional": "Apartment (optional)", + "AUTO_DETECT_COORDINATES": "Auto detect coordinates", + "LATITUDE": "Latitude", + "LATITUDE_IS_REQUIRED": "Latitude is required", + "LONGITUDE": "Longitude", + "LONGITUDE_IS_REQUIRED": "Longitude is required", + "FIND_ADDRESS": "Find Address", + "OK": "OK", + "CANCEL": "CANCEL", + "ALLOW_ONLINE_PAYMENTS": "Allow online payments", + "SETTINGS_SECTION": { + "DELIVERY_OR_TAKEAWAY_SETTINGS": "Delivery/Takeaway Settings", + "PRODUCTS_DELIVERY_BY_DEFAULT": "Products Delivery (by default)", + "PRODUCTS_TAKEAWAY_BY_DEFAULT": "Products Takeaway (by default)", + "PAYMENTS_SETTINGS": "Payments Settings", + "BARCODE_QR_CODE_SETTINGS": "Barcode/QR code Settings", + "ORDER_BARCODE_TYPE": "Order barcode type", + "BARCODE_DATA": "Barcode data", + "SCAN_EXISTED": "Scan Existed", + "DELIVERY_SETTINGS": "Delivery settings", + "TAKEAWAY_SETTINGS": "Takeaway settings", + "IN_STORE_MODE": "In-store mode", + "IN_STORE_MODE_SETTINGS": "In-store mode settings" + } + }, + "LOGIN_VIEW": { + "LOGIN": "Login" + }, + "LANGUAGE_VIEW": { + "TITLE": "Select Language", + "OK": "OK", + "CANCEL": "CANCEL" + }, + "ABOUT_VIEW": { + "TITLE": "About Us", + "APP_VERSION": "App Version" + }, + "TERMS_OF_USE_VIEW": { + "TITLE": "Terms of Use" + }, + "WAREHOUSE_VIEW": { + "RELEVANT": "Relevant", + "ALL": "All", + "LOADING_MORE_DATA": "Loading more data...", + "ORDERS": "Orders", + "SWITCH_ORDERS": "Switch Orders", + "NEW_ORDER": "New Order", + "PRODUCTS": "Top Products", + "ARE_YOU_SURE_TO_DELETE": "Are you sure you want to ", + "REMOVE": "Remove", + "DELETE": "Delete", + "THE_FOLLOWING_DATA": "the following data", + "CONFIRM": "Confirm", + "CANCEL": "Cancel", + "SELECT_POP_UP": { + "SELECT_CATEGORIES": "Select Categories", + "OK": "OK", + "CANCEL": "CANCEL" + }, + "ORDER_WAREHOUSE_STATUSES": { + "ALL": "All", + "CANCELLED": "Cancelled", + "CREATED": "Created", + "CONFIRMED": "Confirmed", + "PROCESSING": "Processing", + "ALLOCATION_STARTED": "Allocation Started", + "ALLOCATION_FINISHED": "Allocation Finished", + "PACKAGING_STARTED": "Packaging Started", + "PACKAGED": "Packaged", + "GIVEN_TO_CARRIER": "Given to Carrier", + "TAKEN": "Taken", + "ALLOCATION_FAILED": "Allocation Failed", + "PACKAGING_FAILED": "Packaging Failed", + "BAD_STATUS": "Bad Status", + "NO_ACTIVE_ORDERS": "No Active Orders", + "NO_ORDERS": "No Orders", + "NO_PRODUCTS": "No Products" + }, + "CREATE_PRODUCTS_POPUP": { + "ERROR_MSG": "Something is wrong, unable to place order!", + "NEW_PRODUCT_TYPE": "New Product Type", + "CREATE_NEW_PRODUCT_TYPE": "Create a new product type", + "TITLE": "Title", + "DESCRIPTION": "Description", + "CATEGORIES": "Categories", + "PRICE": "Price", + "COUNT": "Count", + "LANGUAGE": "Language", + "COUNT_PLACEHOLDER": "0", + "DRAG&DROP_FILE_HERE": "Drag & Drop File here", + "DRAG&DROP_PICTURE_HERE": "Drag & Drop Picture here", + "OR_BROWSE": "or browse", + "CLICK_TO_UPLOAD_PICTURE": "Click to Upload Picture", + "CLICK_TO_UPLOAD_MORE_PICTURE": "Click to Upload More Pictures", + "CANCEL": "Cancel", + "CREATE": "Create", + "ENGLISH": "English", + "HEBREW": "Hebrew", + "RUSSIAN": "Russian", + "BULGARIAN": "Bulgarian", + "SPANISH": "Spanish", + "FRENCH": "French", + "OPTIONAL": "optional", + "TAKEAWAY": "Takeaway", + "DELIVERY": "Delivery", + "PRODUCT_AVAILABILITY": "Product availability" + }, + "NEW_ORDER_VIEW": { + "NEW_MANUAL_ORDER": "New Manual Order", + "SELECT_EXISTING_CUSTOMER": "Select Existing Customer", + "ADD_NEW_CUSTOMER": "Add New Customer", + "FULL_NAME": "Full Name", + "EMAIL": "Email", + "PHONE": "Phone", + "ADDRESS": "Address", + "IMAGE": "Image", + "PRODUCT": "Product", + "PRICE": "Price", + "AVAILABLE": "Available", + "COMMENT": "Comment", + "AMOUNT": "Amount", + "MAKE_ORDER": "Make Order", + "BASIC_INFO": "Basic Info", + "FIRST_NAME": "First Name", + "LAST_NAME": "Last Name", + "EMAIL_IS_INVALID": "Email is invalid", + "EMAIL_IS_ALREADY_IN_USE": "Email is already in use", + "NEXT": "Next", + "ADD_CUSTOMER": "Add Customer", + "BACK": "Back", + "FIND_ADDRESS": "Find Address", + "COUNTRY": "Country", + "CITY": "City", + "COUNTRY_IS_INVALID": "Country is invalid", + "CITY_IS_REQUIRED": "City is required", + "ZIP": "ZIP", + "STREET_ADDRESS": "Street Address", + "STREET_ADDRESS_IS_REQUIRED": "Street address is required", + "HOUSE_№": "House №", + "APARTMENT": "Apartment", + "SHOW_COORDINATES": "Show Coordinates", + "LATITUDE": "Latitude", + "LONGITUTE": "Longitude", + "HOUSE_IS_REQUIRED": "House is required" + }, + "EDIT_PICTURE": { + "PRODUCT_IMAGES": "Product images", + "SAVE_IMAGES": "Save images" + }, + "MISC_TEXT": { + "CREATE_NEW_ORDER": "Create New Order", + "ADD_NEW_PRODUCT": "Add New Product" + } + }, + "EDIT_PRODUCT_POP_UP": { + "EDIT_PRODUCT_TYPE": "Edit product type", + "UPDATE_AN_EXISTING_PRODUCT": "Update an existing product", + "CLICK_TO_EDIT_PICTURES": "Click to Edit Pictures", + "TITLE": "Title", + "DESCRIPTION": "Description", + "CATEGORIES": "Categories", + "PRICE": "Price", + "COUNT": "Count", + "LANGUAGE": "Language", + "DRAG&DROP_FILE_HERE": "Drag & Drop File here", + "OR_BROWSE": "or browse", + "CLICK_TO_UPLAOD_PICTURE": "Click to Upload Picture", + "CANCEL": "Cancel", + "UPDATE": "Update", + "ENGLISH": "English", + "HEBREW": "Hebrew", + "RUSSIAN": "Russian", + "BULGARIAN": "Bulgarian", + "SPANISH": "Spanish", + "FRENCH": "French", + "OPTIONAL": "optional" + }, + "ORDER_CANCELED_COMPONENT": { + "CANCELED": "Canceled", + "ORDER_CANCELED": "Order canceled", + "DELIVERED": "Delivered", + "CREATED_AT": "Created at" + }, + "ORDER_CONTROL_BUTTONS_COMPONENT": { + "CONFIRM": "Confirm", + "START_PROCESSING": "Start Processing", + "START_ALLOCATION": "Start Allocation", + "ALLOCATED": "Allocated", + "START_PACKAGING": "Start Packaging", + "PACKAGED": "Packaged", + "FAILED": "Failed", + "GIVEN_TO_CARRIER": "Given to Carrier", + "GIVEN_TO_CUSTOMER": "Given to Customer" + }, + "ORDER_DELIVERED_COMPONENT": { + "DELIVERED": "Delivered", + "CREATED_AT": "Created at", + "DELIVERED_AT": "Delivered at", + "DELIVERED_TO": "Delivered to" + }, + "ORDER_DELIVERY_PROBLEM_COMPONENT": { + "DELIVERY_ISSUES": "Delivery Issues", + "ISSUES_DURING_DELIVERY_TO": "Issues During Delivery to", + "BY": "By", + "CREATED_AT": "Created at" + }, + "ORDER_WAREHOUSE_PREPARATION_PROBLEM_COMPONENT": { + "CREATED_AT": "Created at" + }, + "ORDER_WITH_CARRIER_COMPONENT": { + "IN_DELIVERY_TO": "In Delivery To", + "BY": "By ", + "CREATED_AT": "Created at" + }, + "ORDER_WITHOUT_CARRIER_COMPONENT": { + "CREATED_AT": "Created at", + "CANT_PROCESSING_WITHOUT_PRODUCTS": "Can't processing the order without products." + }, + "ORDER_TITLE_COMPONENT": { + "ORDER": "Order", + "FROM": "from" + }, + "ORDER_TYPE": { + "TYPE": "Order type:", + "DELIVERY": "Delivery", + "TAKEAWAY": "Takeaway" + }, + "TRACK_ORDER_POP_UP": { + "CARRIER": "Carrier", + "TRACK": "Track", + "CUSTOMER": "Customer" + }, + "CONNECTION_ERROR_VIEW": { + "TITLE": "No connection to Ever", + "DESCRIPTION": [ + "Please make sure you are", + "connected to the internet", + "or try Ever app later." + ] + }, + "CUSTOMER_ORDERS_POP_UP": { + "ORDER_ID": "Order ID", + "DELIVERY": "Delivery", + "ADDRESS": "Address", + "STATUS": "Status" + }, + "NO_SERVER_VIEW": { + "NO_SERVER": "Server connection is lost" + }, + "PROMOTIONS": { + "PROMO_PRICE": "Promo price", + "NO_PROMOTIONS": "No Promotions" + } +} diff --git a/packages/merchant-tablet-ionic/src/assets/i18n/es-ES.json b/packages/merchant-tablet-ionic/src/assets/i18n/es-ES.json new file mode 100644 index 0000000..644c96a --- /dev/null +++ b/packages/merchant-tablet-ionic/src/assets/i18n/es-ES.json @@ -0,0 +1,487 @@ +{ + "LANGUAGE": { + "ID": "es-ES", + "NAME": "Español" + }, + "DASHBOARD": "Tablero", + "CURRENT_DIRECTION": "ltr", + "SIDEBAR_SIDE": "left", + "SIDE_MENU": { + "TITLE": "Ever Comercios", + "GROUPS": { + "NO_TITLE": { + "DIVER_TITLE": "", + "ITEMS": { + "WAREHOUSE": "Tablero" + } + }, + "SETTINGS": { + "SETTINGS": "Ajustes", + "DIVER_TITLE": "Ajustes", + "ITEMS": { + "LANGUAGE": "Idioma", + "SIGN_OUT": "Salir" + } + }, + "INFO": { + "DIVER_TITLE": "Información", + "ITEMS": { + "ABOUT_US": "Acerca De", + "TERMS_OF_USE": "Condiciones de Uso" + } + } + } + }, + "CUSTOMERS_VIEW": { + "CUSTOMERS": "Clientes", + "ADD_CUSTOMER": "Agregar Cliente", + "IMAGE": "Imágen", + "NAME": "Nombre", + "PHONE_NUMBER": "Teléfono", + "ADDRESSES": "Dirección", + "ORDERS": "Pedidos", + "TOTAL": "Total", + "E_MAIL": "Correo", + "ADDRESS_POP_UP": { + "CUSTOMER_ADDRESS": "Dirección del Cliente", + "COUNTRY": "Ciudad", + "STREET": "Calle", + "HOUSE": "Número de Puerta", + "APARTMENT": "Apartmento", + "COORDINATES": "Coordenadas" + }, + + "EMAIL_POP_UP": { + "CUSTOMER_EMAIL": "Correo del Cliente", + "SEND_E_MAIL": "Enviar Correo" + }, + "ORDERS_POP_UP": { + "CUSTOMER_ORDERS": "Pedidos del Cliente", + "ALL_ORDERS": "Todos los Pedidos", + "TOTAL_ORDERS_SUM": "Suma Total de Pedidos", + "COMPLATED": "Terminados", + "PAID": "Pago" + } + }, + "FILE_UPLOADER": { + "DRAG_AND_DROP_FILE_HERE": "Arrastre Imágen Aquí", + "PICTURE_URL": "Enlace de la Imágen", + "BROWSE": "Buscar" + }, + "CARRIERS_VIEW": { + "CARRIERS_CATALOG": { + "IMAGE": "Imágen", + "NAME": "Nombre", + "PHONE": "Teléfono", + "ADDRESS": "Dirección", + "LOGO": "Logo" + }, + "TRACK_ORDER": "Orden de pista", + "TRACK_CARRIER": "Seguimiento del Repartidor", + "DELIVERY_TRACKING": "Seguimiento de entregas", + "CARRIER": "Portador", + "STORE": "Tienda", + "DELIVERY_COUNT": "Recuento de entrega:", + "CUSTOMER": "Cliente", + "CARRIERS": "Repartos", + "ADD_CARRIERS": "Agregar Reparto", + "IMAGE": "Imágen", + "NAME": "Nombre", + "PHONE_NUMBER": "Número de Teléfono", + "ADDRESSES": "Dirección", + "STATUS": "Estado", + "DELIVERIES": "Repartos", + "NO_CARRIERS": "NO CARRIERS", + "WORKING": "Trabajando", + "NOT_WORKING": "No Trabajando", + "BLOCKED": "Bloqueado", + "DELIVERIES_POP_UP": { + "CARRIER_DELIVERIES": "Entregas del Reparto", + "ALL_DELIVERIES": "Todas las Entregas", + "DELIVERIES_TODAY": "Entregado Hoy", + "TOTAL_DISTANCE_TODAY": "Distancia Recorrida Hoy", + "CUSTOMER": "Cliente", + "WAREHOUSE": "Sucursal", + "STATUS": "Estado", + "DELIVERY": "Reparto", + "NO_DELIVERIES": "No Hay Repartos", + "COMPLETED": "Terminado", + "PAID": "Pago" + }, + "ADDRESS": { + "CARRIER_ADDRESS": "Dirección del Reparto", + "COUNTRY": "Ciudad", + "HOUSE": "Calle y Número de Puerta", + "COORDINATES": "Coordenadas" + }, + "EDIT_CARRIER": { + "EDIT_CARRIER": "Editar Reparto" + }, + "ADD_CARRIER": { + "ADD_CARRIER": "Agregar Reparto", + "HOW_TO_ADD": "Como Agregar", + "SELECT_HOW_TO_ADD": "Seleccionar Como Agregar", + "ADD": "Agregar", + "CARRIERS_CATALOG": "Catálogo de Repartos", + "SELECT_FROM_CARRIERS_CATALOG": "Seleccionar", + "ADD_NEW_CARRIER": "Agregar Nuevo", + "FIRST_NAME": "Nombre", + "LAST_NAME": "Apellido", + "EMAIL": "Correo", + "USERNAME": "Usuario", + "PASSWORD": "Password", + "NAME": "Nombre", + "PHONE": "Teléfono", + "ADDRESS": "Dirección", + "LOGO": "Logo", + "IS_ACTIVE": "Está Activo", + "CITY": "Ciudad", + "STREET": "Calle", + "HOUSE": "Número de Puerta", + "LATITUDE": "Latitud", + "LONGITUDE": "Longitud", + "COUNTRY": "País", + "OK": "OK", + "CANCEL": "CANCELAR", + "PREVIOUS": "ANTERIOR", + "BACK": "REGRESAR", + "NEXT": "SIGUIENTE", + "DONE": "REALIZADO", + "LOCATION": "UBICACION", + "ZIP": "CODIGO POSTAL", + "PROFILE_DETAILS": "DETALLES", + "BROWSE": "BUSCAR", + "DRAG_AND_DROP_FILE_HERE": "Arrastre Imágen Aquí", + "PICTURE_URL": "Enlace de la Imágen", + "ACCOUNT": "CUENTA", + "REPEAT": "Repetir", + "REPEAT_PASSWORD": "Repetir Clave", + "PASSWORD_DO_NOT_MATCH": "Claves no Coinciden", + "FIND_ADDRESS": "Buscar Dirección", + "FORMS": { + "ERRORS": { + "FIRST_NAME_REQUIRED": "Nombre requerido", + "LAST_NAME_REQUIRED": "Apellido requerido", + "PHONE_REQUIRED": "Teléfono requerido", + "EMAIL_IS_REQUIRED": "Correo requerido", + "MUST_CONTAIN_ONLY_LETTERS": "Debe contener sólo letras", + + "PHONE_MUST_CONTAINS_ONLY(special_signs)AND_DIGIT_CHARACTER": "Teléfono debe contener solo números" + } + } + }, + "PROMOTIONS": { + "PROMOTIONS": "Promociones", + "NO_PROMOTIONS": "Sin promociones", + "BASIC_INFO": "Información básica", + "TITLE": "Título", + "ACTIVE_FROM": "Activa desde", + "ACTIVE_TO": "Activa para", + "PURCHASES_COUNT": "Cuenta de compras", + "IS_ACTIVE": "Esta activa", + "SELECT_DATE": "Seleccione fecha", + "DESCRIPTION": "Descripción", + "SAVE": "Salvar", + "NEXT": "Próxima", + "BACK": "Espalda", + "LANGUAGE": "Idioma" + }, + "SELECT LANGUAGE": { + "USA": "EEUU", + "ISRAEL": "Israel", + "BULGARIA": "Bulgaria", + "ESPAÑA": "España" + } + }, + "SETTINGS_VIEW": { + "SETTINGS": "Ajustes", + "OPTIONS": "Opciones", + "COMMON": "Comúnes", + "LOCATION": "Ubicación", + "ACCOUNT": "Cuenta", + "NEW_USERNAME": "Nuevo Usuario", + "NAME_MUST_BE_AT_LEAST_4_CHARACTERS_LONG": "El nombre debe contener al menos 4 caracteres", + "OLD_PASSWORD": "Clave Actual", + "NEW_PASSWORD": "Clave Nueva", + "REPEAT_NEW_PASSWORD": "Repetir Clave Nueva", + "PASSWORDS_DO_NOT_MATCH": "Claves no Coinciden", + "SAVE_CHANGES": "Guardar Cambios", + "SAVE": "Guardar", + "CHANGES": "Cambios", + "BASIC_INFO": "Información Básica", + "NAME": "Nombre", + "NAME_IS_REQUIRED": "Nombre requerido", + "LOGO": "Logo", + "BROWSE": "Buscar", + "CONTACT_INFO": "Información de Contacto", + "E_MAIL": "Correo", + "PHON_NUMBER": "Número de Teléfono", + "COUNTRY": "País", + "CITY": "Ciudad", + "CITY_IS_REQUIRED": "Ciudad requerida", + "POSTCODE_optional": "Código Postal (opcional)", + "STREET": "Street", + "STREET_IS_REQUIRED": "Calle requerida", + "HOUSE": "Número de Puerta", + "HOUSE_IS_REQUIRED": "Numero requerido", + "APARTMENT_optional": "Numero Apto (opcional)", + "AUTO_DETECT_COORDINATES": "Detectar Coordenadas", + "LATITUDE": "Latitud", + "LATITUDE_IS_REQUIRED": "Latitud requerida", + "LONGITUDE": "Longitud", + "LONGITUDE_IS_REQUIRED": "Longitud requerida", + "FIND_ADDRESS": "Buscar Dirección", + "OK": "OK", + "CANCEL": "CANCELAR", + "ALLOW_ONLINE_PAYMENTS": "Permitir Pagos Online", + "SETTINGS_SECTION": { + "DELIVERY_OR_TAKEAWAY_SETTINGS": "Configuraciones de Reparto/Retiro", + "PRODUCTS_DELIVERY_BY_DEFAULT": "Reparto de Productos (por defecto)", + "PRODUCTS_TAKEAWAY_BY_DEFAULT": "Retiro de Productos (por defecto)", + "PAYMENTS_SETTINGS": "Ajustes de Pagos", + "BARCODE_QR_CODE_SETTINGS": "Ajustes de Código Barras/Código QR", + "ORDER_BARCODE_TYPE": "Tipo de Código del Pedido", + "BARCODE_DATA": "Datos del Código", + "SCAN_EXISTED": "Existía escaneado", + "DELIVERY_SETTINGS": "Configuraciones de entrega", + "TAKEAWAY_SETTINGS": "Configuraciones para llevar", + "IN_STORE_MODE": "Modo en la tienda", + "IN_STORE_MODE_SETTINGS": "Configuración del modo en la tienda" + } + }, + "LOGIN_VIEW": { + "LOGIN": "Entrar" + }, + "LANGUAGE_VIEW": { + "TITLE": "Seleccionar Idioma", + "OK": "OK", + "CANCEL": "CANCELAR" + }, + "ABOUT_VIEW": { + "TITLE": "Acerca De", + "APP_VERSION": "Versión de la App" + }, + "TERMS_OF_USE_VIEW": { + "TITLE": "Condiciones de Uso" + }, + "WAREHOUSE_VIEW": { + "RELEVANT": "Relevante", + "ALL": "Todo", + "LOADING_MORE_DATA": "Cargando mas datos...", + "ORDERS": "Pedidos", + "SWITCH_ORDERS": "Órdenes de cambio", + "NEW_ORDER": "Nuevo Pedido", + "PRODUCTS": "Productos Top", + "ARE_YOU_SURE_TO_DELETE": "Estás seguro que quieres", + "REMOVE": "Eliminar", + "DELETE": "Eliminar", + "THE_FOLLOWING_DATA": "los siguientes datos", + "CONFIRM": "Confirmar", + "CANCEL": "Cancelar", + "SELECT_POP_UP": { + "SELECT_CATEGORIES": "Seleccionar Categorías", + "OK": "OK", + "CANCEL": "CANCELAR" + }, + "ORDER_WAREHOUSE_STATUSES": { + "ALL": "Todas", + "CANCELLED": "Cancelado", + "CREATED": "Creado", + "CONFIRMED": "Confirmado", + "PROCESSING": "Procesando", + "ALLOCATION_STARTED": "Asignando", + "ALLOCATION_FINISHED": "Asignado", + "PACKAGING_STARTED": "Embalando", + "PACKAGED": "Embalada", + "GIVEN_TO_CARRIER": "Entregado a Repartidor", + "TAKEN": "Tomada", + "ALLOCATION_FAILED": "Asignación Fallida", + "PACKAGING_FAILED": "Embalado Fallido", + "BAD_STATUS": "Estado Erróneo", + "NO_ACTIVE_ORDERS": "No hay Pedidos Activos", + "NO_ORDERS": "No hay Pedidos", + "NO_PRODUCTS": "No hay Productos" + }, + "CREATE_PRODUCTS_POPUP": { + "ERROR_MSG": "¡Algo está mal, no se puede hacer el pedido!", + "NEW_PRODUCT_TYPE": "Nuevo Tipo de Producto", + "CREATE_NEW_PRODUCT_TYPE": "Crear Nuevo Tipo de Producto", + "TITLE": "Nombre", + "DESCRIPTION": "Descripción", + "CATEGORIES": "Categorias", + "PRICE": "Precio", + "COUNT": "Cantidad", + "LANGUAGE": "Idioma", + + "COUNT_PLACEHOLDER": "0", + "DRAG&DROP_FILE_HERE": "Arrastre Archivo Aquí", + "DRAG&DROP_PICTURE_HERE": "Arrastre Imágen Aquí", + "OR_BROWSE": "o Buscar", + "CLICK_TO_UPLOAD_PICTURE": "Click para Cargar Imágen", + "CLICK_TO_UPLOAD_MORE_PICTURE": "Click para Cargar más Imágenes", + "CANCEL": "Cancelar", + "CREATE": "Crear", + "ENGLISH": "Inglés", + "HEBREW": "Hebreo", + "RUSSIAN": "Ruso", + "BULGARIAN": "Bulgaro", + "ESPAÑOL": "Español", + "SPANISH": "Español", + "FRENCH": "Francés", + "OPTIONAL": "Opcional", + "TAKEAWAY": "Retirar", + "DELIVERY": "Repartir", + "PRODUCT_AVAILABILITY": "Disponibilidad de producto" + }, + "NEW_ORDER_VIEW": { + "NEW_MANUAL_ORDER": "Nuevo Pedido Manual", + "SELECT_EXISTING_CUSTOMER": "Seleccionar Cliente", + "ADD_NEW_CUSTOMER": "Crear Nuevo Cliente", + "FULL_NAME": "Nombre Completo", + "EMAIL": "Correo", + "PHONE": "Teléfono", + "ADDRESS": "Dirección", + + "IMAGE": "Imágen", + "PRODUCT": "Producto", + "PRICE": "Precio", + "AVAILABLE": "Disponible", + "COMMENT": "Comentario", + "AMOUNT": "Cantidad", + "MAKE_ORDER": "Crear Pedido", + "BASIC_INFO": "Información Básica", + "FIRST_NAME": "Nombre", + "LAST_NAME": "Apellido", + "EMAIL_IS_INVALID": "Email es invalido", + + "EMAIL_IS_ALREADY_IN_USE": "Correo esta en uso", + "NEXT": "Siguiente", + "ADD_CUSTOMER": "Agregar Cliente", + "BACK": "Regresar", + "FIND_ADDRESS": "Buscar Dirección", + "COUNTRY": "País", + "CITY": "Ciudad", + "COUNTRY_IS_INVALID": "País Inválido", + "CITY_IS_REQUIRED": "Ciudad requerida", + "ZIP": "Código Postal", + "STREET_ADDRESS": "Calle", + "STREET_ADDRESS_IS_REQUIRED": "Calle requerida", + "HOUSE_№": "Número de Puerta", + "APARTMENT": "Apartamento", + "SHOW_COORDINATES": "Mostrar Coordenaas", + "LATITUDE": "Latitud", + + "LONGITUTE": "Longitud", + "HOUSE_IS_REQUIRED": "Número de Puerta requerido" + }, + "EDIT_PICTURE": { + "PRODUCT_IMAGES": "Imágenes de Productos", + "SAVE_IMAGES": "Guardar Imágenes" + }, + "MISC_TEXT": { + "CREATE_NEW_ORDER": "Crear Nuevo Pedido", + "ADD_NEW_PRODUCT": "Agregar Nuevo Producto" + } + }, + "EDIT_PRODUCT_POP_UP": { + "EDIT_PRODUCT_TYPE": "Editar Tipo de Producto", + "UPDATE_AN_EXISTING_PRODUCT": "Modificar Producto", + "CLICK_TO_EDIT_PICTURES": "Click para Modificar Imágenes", + "TITLE": "Nombre", + "DESCRIPTION": "Descripción", + "CATEGORIES": "Categorías", + "PRICE": "Precio", + "COUNT": "Cantidad", + "LANGUAGE": "Idioma", + "DRAG&DROP_FILE_HERE": "Arrastre Imágenes Aquí", + "OR_BROWSE": "Buscar", + "CLICK_TO_UPLAOD_PICTURE": "Click para Cargar Imágenes", + "CANCEL": "Cancelar", + + "UPDATE": "Modificar", + "ENGLISH": "Inglés", + "HEBREW": "Hebreo", + "RUSSIAN": "Ruso", + "BULGARIAN": "Bulgaro", + "ESPAÑOL": "Español", + "SPANISH": "Español", + "FRENCH": "Francés", + "OPTIONAL": "Opcional" + }, + "ORDER_CANCELED_COMPONENT": { + "CANCELED": "Cancelado", + "ORDER_CANCELED": "Pedido cancelado", + "DELIVERED": "Entregado", + "CREATED_AT": "Creación" + }, + "ORDER_CONTROL_BUTTONS_COMPONENT": { + "CONFIRM": "Confirmar", + "START_PROCESSING": "Comenzar Procesamiento", + "START_ALLOCATION": "Comenzar Asignación", + "ALLOCATED": "Asignado", + "START_PACKAGING": "Comenzar a Embalar", + "PACKAGED": "Emabalado", + "FAILED": "Error", + "GIVEN_TO_CARRIER": "Entregado al Repartidor", + "GIVEN_TO_CUSTOMER": "Entregado al Cliente" + }, + "ORDER_DELIVERED_COMPONENT": { + "DELIVERED": "Entregado", + "CREATED_AT": "Creación", + "DELIVERED_AT": "Entregado a las", + "DELIVERED_TO": "Entregado a" + }, + "ORDER_DELIVERY_PROBLEM_COMPONENT": { + "DELIVERY_ISSUES": "Problemas de Entrega", + "ISSUES_DURING_DELIVERY_TO": "Problemas Durante la Entrega a", + "BY": "Por", + "CREATED_AT": "Creación" + }, + "ORDER_WAREHOUSE_PREPARATION_PROBLEM_COMPONENT": { + "CREATED_AT": "Creación" + }, + "ORDER_WITH_CARRIER_COMPONENT": { + "IN_DELIVERY_TO": "En camino a", + "BY": "Por ", + "CREATED_AT": "Creación" + }, + "ORDER_WITHOUT_CARRIER_COMPONENT": { + "CREATED_AT": "Creación", + "CANT_PROCESSING_WITHOUT_PRODUCTS": "No se puede procesar un pedido sin productos" + }, + "ORDER_TITLE_COMPONENT": { + "ORDER": "Pedido", + "FROM": "Desde" + }, + "ORDER_TYPE": { + "TYPE": "Tipo de orden:", + "DELIVERY": "Entrega", + "TAKEAWAY": "Para llevar" + }, + "TRACK_ORDER_POP_UP": { + "CARRIER": "Reparto", + "TRACK": "Seguir", + "CUSTOMER": "Cliente" + }, + "CONNECTION_ERROR_VIEW": { + "TITLE": "No hay Conexión con el Servidor", + "DESCRIPTION": [ + "Asegurese que está", + "Conectado a Internet", + "o cierre y abra nuevamente la App." + ] + }, + "CUSTOMER_ORDERS_POP_UP": { + "ORDER_ID": "ID de Pedido", + "DELIVERY": "Reparto", + "ADDRESS": "Dirección", + "STATUS": "Estado" + }, + "NO_SERVER_VIEW": { + "NO_SERVER": "Sin conexión con el Servidor." + }, + "PROMOTIONS": { + "PROMO_PRICE": "Precio promocional", + "NO_PROMOTIONS": "No hay promociones" + } +} diff --git a/packages/merchant-tablet-ionic/src/assets/i18n/fr-FR.json b/packages/merchant-tablet-ionic/src/assets/i18n/fr-FR.json new file mode 100644 index 0000000..ce8a607 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/assets/i18n/fr-FR.json @@ -0,0 +1,480 @@ +{ + "LANGUAGE": { + "ID": "en-US", + "NAME": "English" + }, + "DASHBOARD": "Dashboard", + "CURRENT_DIRECTION": "ltr", + "SIDEBAR_SIDE": "left", + "SIDE_MENU": { + "TITLE": "Ever Merchants", + "GROUPS": { + "NO_TITLE": { + "DIVER_TITLE": "", + "ITEMS": { + "WAREHOUSE": "Dashboard" + } + }, + "SETTINGS": { + "SETTINGS": "Settings", + "DIVER_TITLE": "Settings", + "ITEMS": { + "LANGUAGE": "Language", + "SIGN_OUT": "Sign out" + } + }, + "INFO": { + "DIVER_TITLE": "Info", + "ITEMS": { + "ABOUT_US": "About Us", + "TERMS_OF_USE": "Terms of Use" + } + } + } + }, + "CUSTOMERS_VIEW": { + "CUSTOMERS": "Customers", + "ADD_CUSTOMER": "Add customer", + "IMAGE": "Image", + "NAME": "Name", + "PHONE_NUMBER": "Phone number", + "ADDRESSES": "Addresses", + "ORDERS": "Orders", + "TOTAL": "Total", + "E_MAIL": "Email", + "ADDRESS_POP_UP": { + "CUSTOMER_ADDRESS": "Customer Address", + "COUNTRY": "Country", + "STREET": "Street", + "HOUSE": "House", + "APARTMENT": "Apartment", + "COORDINATES": "Coordinates" + }, + "EMAIL_POP_UP": { + "CUSTOMER_EMAIL": "Customer Email", + "SEND_E_MAIL": "Send E-mail" + }, + "ORDERS_POP_UP": { + "CUSTOMER_ORDERS": "Customer Orders", + "ALL_ORDERS": "All Orders", + "TOTAL_ORDERS_SUM": "Total Orders Sum", + "COMPLATED": "Completed", + "PAID": "Paid" + } + }, + "FILE_UPLOADER": { + "DRAG_AND_DROP_FILE_HERE": "Drag & Drop Image here", + "PICTURE_URL": "Picture url", + "BROWSE": "BROWSE" + }, + "CARRIERS_VIEW": { + "CARRIERS_CATALOG": { + "IMAGE": "Image", + "NAME": "Name", + "PHONE": "Phone", + "ADDRESS": "Address", + "LOGO": "Logo" + }, + "TRACK_ORDER": "Track order", + "TRACK_CARRIER": "Track Carrier", + "DELIVERY_TRACKING": "Delivery Tracking", + "CARRIER": "Carrier", + "STORE": "Store", + "DELIVERY_COUNT": "Delivery count:", + "CUSTOMER": "Customer", + "CARRIERS": "Carriers", + "ADD_CARRIERS": "Add Carriers", + "IMAGE": "Image", + "NAME": "Name", + "PHONE_NUMBER": "Phone number", + "ADDRESSES": "Address", + "STATUS": "Status", + "DELIVERIES": "Deliveries", + "NO_CARRIERS": "NO CARRIERS", + "WORKING": "Working", + "NOT_WORKING": "Not working", + "BLOCKED": "Blocked", + "DELIVERIES_POP_UP": { + "CARRIER_DELIVERIES": "Carrier Deliveries", + "ALL_DELIVERIES": "All Deliveries", + "DELIVERIES_TODAY": "Deliveries Today", + "TOTAL_DISTANCE_TODAY": "Total Distance Today", + "CUSTOMER": "Customer", + "WAREHOUSE": "Warehouse", + "STATUS": "Status", + "DELIVERY": "Delivery", + "NO_DELIVERIES": "No Deliveries", + "COMPLETED": "Completed", + "PAID": "Paid" + }, + "ADDRESS": { + "CARRIER_ADDRESS": "Carrier Address", + "COUNTRY": "Country", + "HOUSE": "House", + "COORDINATES": "Coordinates" + }, + "EDIT_CARRIER": { + "EDIT_CARRIER": "Edit carrier" + }, + "ADD_CARRIER": { + "ADD_CARRIER": "Add carrier", + "HOW_TO_ADD": "How to add", + "SELECT_HOW_TO_ADD": "Select How To Add", + "ADD": "Add", + "CARRIERS_CATALOG": "Carriers Catalog", + "SELECT_FROM_CARRIERS_CATALOG": "Select from Carriers Catalog", + "ADD_NEW_CARRIER": "Add new carrier", + "FIRST_NAME": "First Name", + "LAST_NAME": "Last Name", + "EMAIL": "Email", + "USERNAME": "Username", + "PASSWORD": "Password", + "NAME": "Name", + "PHONE": "Phone", + "ADDRESS": "Address", + "LOGO": "Logo", + "IS_ACTIVE": "Is active", + "CITY": "City", + "STREET": "Street", + "HOUSE": "House", + "LATITUDE": "Latitude", + "LONGITUDE": "Longitude", + "COUNTRY": "Country", + "OK": "OK", + "CANCEL": "CANCEL", + "PREVIOUS": "PREVIOUS", + "BACK": "BACK", + "NEXT": "NEXT", + "DONE": "DONE", + "LOCATION": "LOCATION", + "ZIP": "ZIP", + "PROFILE_DETAILS": "PROFILE DETAILS", + "BROWSE": "BROWSE", + "DRAG_AND_DROP_FILE_HERE": "Drag & Drop Image here", + "PICTURE_URL": "Picture url", + "ACCOUNT": "ACCOUNT", + "REPEAT": "Repeat", + "REPEAT_PASSWORD": "Repeat password", + "PASSWORD_DO_NOT_MATCH": "Password do not match", + "FIND_ADDRESS": "Find Address", + "FORMS": { + "ERRORS": { + "FIRST_NAME_REQUIRED": "First name is required", + "LAST_NAME_REQUIRED": "Last name is required", + "PHONE_REQUIRED": "Phone is required", + "EMAIL_IS_REQUIRED": "Email is required", + "MUST_CONTAIN_ONLY_LETTERS": "Must contain only letters", + "PHONE_MUST_CONTAINS_ONLY(special_signs)AND_DIGIT_CHARACTER": "Phone number can start with '+' or '(some numbers)' and must contains only only: '-, ., (space), #'' and digit characters" + } + } + }, + "PROMOTIONS": { + "PROMOTIONS": "Promotions", + "NO_PROMOTIONS": "No promotions", + "BASIC_INFO": "Basic Info", + "TITLE": "Title", + "ACTIVE_FROM": "Active from", + "ACTIVE_TO": "Active to", + "PURCHASES_COUNT": "Purchases count", + "IS_ACTIVE": "Is active", + "SELECT_DATE": "Select date", + "DESCRIPTION": "Description", + "SAVE": "Save", + "NEXT": "Next", + "BACK": "Back", + "LANGUAGE": "Language", + "ACTIVE": "Active", + "IMAGE": "Image", + "STATUS": "Status" + }, + "SELECT LANGUAGE": { + "USA": "USA", + "ISRAEL": "Israel", + "BULGARIA": "Bulgaria" + } + }, + "SETTINGS_VIEW": { + "SETTINGS": "Settings", + "OPTIONS": "Настройки", + "COMMON": "Common", + "LOCATION": "Location", + "ACCOUNT": "Account", + "NEW_USERNAME": "New Username", + "NAME_MUST_BE_AT_LEAST_4_CHARACTERS_LONG": "Name must be at least 4 characters long", + "OLD_PASSWORD": "Old Password", + "NEW_PASSWORD": "New Password", + "REPEAT_NEW_PASSWORD": "Repeat New Password", + "PASSWORDS_DO_NOT_MATCH": "Passwords do not match", + "SAVE_CHANGES": "Save Changes", + "SAVE": "Save", + "CHANGES": "Changes", + "BASIC_INFO": "Basic Info", + "NAME": "Name", + "NAME_IS_REQUIRED": "Name is required", + "LOGO": "Logo", + "BROWSE": "Browse", + "CONTACT_INFO": "Contact Info", + "E_MAIL": "E-mail", + "PHON_NUMBER": "Phone Number", + "COUNTRY": "Country", + "CITY": "City", + "CITY_IS_REQUIRED": "City is required", + "POSTCODE_optional": "Postcode (optional)", + "STREET": "Street", + "STREET_IS_REQUIRED": "Street is required", + "HOUSE": "House", + "HOUSE_IS_REQUIRED": "House is required", + "APARTMENT_optional": "Apartment (optional)", + "AUTO_DETECT_COORDINATES": "Auto detect coordinates", + "LATITUDE": "Latitude", + "LATITUDE_IS_REQUIRED": "Latitude is required", + "LONGITUDE": "Longitude", + "LONGITUDE_IS_REQUIRED": "Longitude is required", + "FIND_ADDRESS": "Find Address", + "OK": "OK", + "CANCEL": "CANCEL", + "ALLOW_ONLINE_PAYMENTS": "Allow online payments", + "SETTINGS_SECTION": { + "DELIVERY_OR_TAKEAWAY_SETTINGS": "Delivery/Takeaway Settings", + "PRODUCTS_DELIVERY_BY_DEFAULT": "Products Delivery (by default)", + "PRODUCTS_TAKEAWAY_BY_DEFAULT": "Products Takeaway (by default)", + "PAYMENTS_SETTINGS": "Payments Settings", + "BARCODE_QR_CODE_SETTINGS": "Barcode/QR code Settings", + "ORDER_BARCODE_TYPE": "Order barcode type", + "BARCODE_DATA": "Barcode data", + "SCAN_EXISTED": "Scan Existed", + "DELIVERY_SETTINGS": "Delivery settings", + "TAKEAWAY_SETTINGS": "Takeaway settings", + "IN_STORE_MODE": "In-store mode", + "IN_STORE_MODE_SETTINGS": "In-store mode settings" + } + }, + "LOGIN_VIEW": { + "LOGIN": "Login" + }, + "LANGUAGE_VIEW": { + "TITLE": "Select Language", + "OK": "OK", + "CANCEL": "CANCEL" + }, + "ABOUT_VIEW": { + "TITLE": "About Us", + "APP_VERSION": "App Version" + }, + "TERMS_OF_USE_VIEW": { + "TITLE": "Terms of Use" + }, + "WAREHOUSE_VIEW": { + "RELEVANT": "Relevant", + "ALL": "All", + "LOADING_MORE_DATA": "Loading more data...", + "ORDERS": "Orders", + "SWITCH_ORDERS": "Switch Orders", + "NEW_ORDER": "New Order", + "PRODUCTS": "Top Products", + "ARE_YOU_SURE_TO_DELETE": "Are you sure you want to ", + "REMOVE": "Remove", + "DELETE": "Delete", + "THE_FOLLOWING_DATA": "the following data", + "CONFIRM": "Confirm", + "CANCEL": "Cancel", + "SELECT_POP_UP": { + "SELECT_CATEGORIES": "Select Categories", + "OK": "OK", + "CANCEL": "CANCEL" + }, + "ORDER_WAREHOUSE_STATUSES": { + "ALL": "All", + "CANCELLED": "Cancelled", + "CREATED": "Created", + "CONFIRMED": "Confirmed", + "PROCESSING": "Processing", + "ALLOCATION_STARTED": "Allocation Started", + "ALLOCATION_FINISHED": "Allocation Finished", + "PACKAGING_STARTED": "Packaging Started", + "PACKAGED": "Packaged", + "GIVEN_TO_CARRIER": "Given to Carrier", + "TAKEN": "Taken", + "ALLOCATION_FAILED": "Allocation Failed", + "PACKAGING_FAILED": "Packaging Failed", + "BAD_STATUS": "Bad Status", + "NO_ACTIVE_ORDERS": "No Active Orders", + "NO_ORDERS": "No Orders", + "NO_PRODUCTS": "No Products" + }, + "CREATE_PRODUCTS_POPUP": { + "ERROR_MSG": "Something is wrong, unable to place order!", + "NEW_PRODUCT_TYPE": "New Product Type", + "CREATE_NEW_PRODUCT_TYPE": "Create a new product type", + "TITLE": "Title", + "DESCRIPTION": "Description", + "CATEGORIES": "Categories", + "PRICE": "Price", + "COUNT": "Count", + "LANGUAGE": "Language", + "COUNT_PLACEHOLDER": "0", + "DRAG&DROP_FILE_HERE": "Drag & Drop File here", + "DRAG&DROP_PICTURE_HERE": "Drag & Drop Picture here", + "OR_BROWSE": "or browse", + "CLICK_TO_UPLOAD_PICTURE": "Click to Upload Picture", + "CLICK_TO_UPLOAD_MORE_PICTURE": "Click to Upload More Pictures", + "CANCEL": "Cancel", + "CREATE": "Create", + "ENGLISH": "English", + "HEBREW": "Hebrew", + "RUSSIAN": "Russian", + "BULGARIAN": "Bulgarian", + "SPANISH": "Spanish", + "FRENCH": "French", + "OPTIONAL": "optional", + "TAKEAWAY": "Takeaway", + "DELIVERY": "Delivery", + "PRODUCT_AVAILABILITY": "Product availability" + }, + "NEW_ORDER_VIEW": { + "NEW_MANUAL_ORDER": "New Manual Order", + "SELECT_EXISTING_CUSTOMER": "Select Existing Customer", + "ADD_NEW_CUSTOMER": "Add New Customer", + "FULL_NAME": "Full Name", + "EMAIL": "Email", + "PHONE": "Phone", + "ADDRESS": "Address", + "IMAGE": "Image", + "PRODUCT": "Product", + "PRICE": "Price", + "AVAILABLE": "Available", + "COMMENT": "Comment", + "AMOUNT": "Amount", + "MAKE_ORDER": "Make Order", + "BASIC_INFO": "Basic Info", + "FIRST_NAME": "First Name", + "LAST_NAME": "Last Name", + "EMAIL_IS_INVALID": "Email is invalid", + "EMAIL_IS_ALREADY_IN_USE": "Email is already in use", + "NEXT": "Next", + "ADD_CUSTOMER": "Add Customer", + "BACK": "Back", + "FIND_ADDRESS": "Find Address", + "COUNTRY": "Country", + "CITY": "City", + "COUNTRY_IS_INVALID": "Country is invalid", + "CITY_IS_REQUIRED": "City is required", + "ZIP": "ZIP", + "STREET_ADDRESS": "Street Address", + "STREET_ADDRESS_IS_REQUIRED": "Street address is required", + "HOUSE_№": "House №", + "APARTMENT": "Apartment", + "SHOW_COORDINATES": "Show Coordinates", + "LATITUDE": "Latitude", + "LONGITUTE": "Longitude", + "HOUSE_IS_REQUIRED": "House is required" + }, + "EDIT_PICTURE": { + "PRODUCT_IMAGES": "Product images", + "SAVE_IMAGES": "Save images" + }, + "MISC_TEXT": { + "CREATE_NEW_ORDER": "Create New Order", + "ADD_NEW_PRODUCT": "Add New Product" + } + }, + "EDIT_PRODUCT_POP_UP": { + "EDIT_PRODUCT_TYPE": "Edit product type", + "UPDATE_AN_EXISTING_PRODUCT": "Update an existing product", + "CLICK_TO_EDIT_PICTURES": "Click to Edit Pictures", + "TITLE": "Title", + "DESCRIPTION": "Description", + "CATEGORIES": "Categories", + "PRICE": "Price", + "COUNT": "Count", + "LANGUAGE": "Language", + "DRAG&DROP_FILE_HERE": "Drag & Drop File here", + "OR_BROWSE": "or browse", + "CLICK_TO_UPLAOD_PICTURE": "Click to Upload Picture", + "CANCEL": "Cancel", + "UPDATE": "Update", + "ENGLISH": "English", + "HEBREW": "Hebrew", + "RUSSIAN": "Russian", + "BULGARIAN": "Bulgarian", + "SPANISH": "Spanish", + "FRENCH": "French", + "OPTIONAL": "optional" + }, + "ORDER_CANCELED_COMPONENT": { + "CANCELED": "Canceled", + "ORDER_CANCELED": "Order canceled", + "DELIVERED": "Delivered", + "CREATED_AT": "Created at" + }, + "ORDER_CONTROL_BUTTONS_COMPONENT": { + "CONFIRM": "Confirm", + "START_PROCESSING": "Start Processing", + "START_ALLOCATION": "Start Allocation", + "ALLOCATED": "Allocated", + "START_PACKAGING": "Start Packaging", + "PACKAGED": "Packaged", + "FAILED": "Failed", + "GIVEN_TO_CARRIER": "Given to Carrier", + "GIVEN_TO_CUSTOMER": "Given to Customer" + }, + "ORDER_DELIVERED_COMPONENT": { + "DELIVERED": "Delivered", + "CREATED_AT": "Created at", + "DELIVERED_AT": "Delivered at", + "DELIVERED_TO": "Delivered to" + }, + "ORDER_DELIVERY_PROBLEM_COMPONENT": { + "DELIVERY_ISSUES": "Delivery Issues", + "ISSUES_DURING_DELIVERY_TO": "Issues During Delivery to", + "BY": "By", + "CREATED_AT": "Created at" + }, + "ORDER_WAREHOUSE_PREPARATION_PROBLEM_COMPONENT": { + "CREATED_AT": "Created at" + }, + "ORDER_WITH_CARRIER_COMPONENT": { + "IN_DELIVERY_TO": "In Delivery To", + "BY": "By ", + "CREATED_AT": "Created at" + }, + "ORDER_WITHOUT_CARRIER_COMPONENT": { + "CREATED_AT": "Created at", + "CANT_PROCESSING_WITHOUT_PRODUCTS": "Can't processing the order without products." + }, + "ORDER_TITLE_COMPONENT": { + "ORDER": "Order", + "FROM": "from" + }, + "ORDER_TYPE": { + "TYPE": "Order type:", + "DELIVERY": "Delivery", + "TAKEAWAY": "Takeaway" + }, + "TRACK_ORDER_POP_UP": { + "CARRIER": "Carrier", + "TRACK": "Track", + "CUSTOMER": "Customer" + }, + "CONNECTION_ERROR_VIEW": { + "TITLE": "No connection to Ever", + "DESCRIPTION": [ + "Please make sure you are", + "connected to the internet", + "or try Ever app later." + ] + }, + "CUSTOMER_ORDERS_POP_UP": { + "ORDER_ID": "Order ID", + "DELIVERY": "Delivery", + "ADDRESS": "Address", + "STATUS": "Status" + }, + "NO_SERVER_VIEW": { + "NO_SERVER": "Server connection is lost" + }, + "PROMOTIONS": { + "PROMO_PRICE": "Promo price", + "NO_PROMOTIONS": "No Promotions" + } +} diff --git a/packages/merchant-tablet-ionic/src/assets/i18n/fr.json b/packages/merchant-tablet-ionic/src/assets/i18n/fr.json new file mode 100644 index 0000000..ce8a607 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/assets/i18n/fr.json @@ -0,0 +1,480 @@ +{ + "LANGUAGE": { + "ID": "en-US", + "NAME": "English" + }, + "DASHBOARD": "Dashboard", + "CURRENT_DIRECTION": "ltr", + "SIDEBAR_SIDE": "left", + "SIDE_MENU": { + "TITLE": "Ever Merchants", + "GROUPS": { + "NO_TITLE": { + "DIVER_TITLE": "", + "ITEMS": { + "WAREHOUSE": "Dashboard" + } + }, + "SETTINGS": { + "SETTINGS": "Settings", + "DIVER_TITLE": "Settings", + "ITEMS": { + "LANGUAGE": "Language", + "SIGN_OUT": "Sign out" + } + }, + "INFO": { + "DIVER_TITLE": "Info", + "ITEMS": { + "ABOUT_US": "About Us", + "TERMS_OF_USE": "Terms of Use" + } + } + } + }, + "CUSTOMERS_VIEW": { + "CUSTOMERS": "Customers", + "ADD_CUSTOMER": "Add customer", + "IMAGE": "Image", + "NAME": "Name", + "PHONE_NUMBER": "Phone number", + "ADDRESSES": "Addresses", + "ORDERS": "Orders", + "TOTAL": "Total", + "E_MAIL": "Email", + "ADDRESS_POP_UP": { + "CUSTOMER_ADDRESS": "Customer Address", + "COUNTRY": "Country", + "STREET": "Street", + "HOUSE": "House", + "APARTMENT": "Apartment", + "COORDINATES": "Coordinates" + }, + "EMAIL_POP_UP": { + "CUSTOMER_EMAIL": "Customer Email", + "SEND_E_MAIL": "Send E-mail" + }, + "ORDERS_POP_UP": { + "CUSTOMER_ORDERS": "Customer Orders", + "ALL_ORDERS": "All Orders", + "TOTAL_ORDERS_SUM": "Total Orders Sum", + "COMPLATED": "Completed", + "PAID": "Paid" + } + }, + "FILE_UPLOADER": { + "DRAG_AND_DROP_FILE_HERE": "Drag & Drop Image here", + "PICTURE_URL": "Picture url", + "BROWSE": "BROWSE" + }, + "CARRIERS_VIEW": { + "CARRIERS_CATALOG": { + "IMAGE": "Image", + "NAME": "Name", + "PHONE": "Phone", + "ADDRESS": "Address", + "LOGO": "Logo" + }, + "TRACK_ORDER": "Track order", + "TRACK_CARRIER": "Track Carrier", + "DELIVERY_TRACKING": "Delivery Tracking", + "CARRIER": "Carrier", + "STORE": "Store", + "DELIVERY_COUNT": "Delivery count:", + "CUSTOMER": "Customer", + "CARRIERS": "Carriers", + "ADD_CARRIERS": "Add Carriers", + "IMAGE": "Image", + "NAME": "Name", + "PHONE_NUMBER": "Phone number", + "ADDRESSES": "Address", + "STATUS": "Status", + "DELIVERIES": "Deliveries", + "NO_CARRIERS": "NO CARRIERS", + "WORKING": "Working", + "NOT_WORKING": "Not working", + "BLOCKED": "Blocked", + "DELIVERIES_POP_UP": { + "CARRIER_DELIVERIES": "Carrier Deliveries", + "ALL_DELIVERIES": "All Deliveries", + "DELIVERIES_TODAY": "Deliveries Today", + "TOTAL_DISTANCE_TODAY": "Total Distance Today", + "CUSTOMER": "Customer", + "WAREHOUSE": "Warehouse", + "STATUS": "Status", + "DELIVERY": "Delivery", + "NO_DELIVERIES": "No Deliveries", + "COMPLETED": "Completed", + "PAID": "Paid" + }, + "ADDRESS": { + "CARRIER_ADDRESS": "Carrier Address", + "COUNTRY": "Country", + "HOUSE": "House", + "COORDINATES": "Coordinates" + }, + "EDIT_CARRIER": { + "EDIT_CARRIER": "Edit carrier" + }, + "ADD_CARRIER": { + "ADD_CARRIER": "Add carrier", + "HOW_TO_ADD": "How to add", + "SELECT_HOW_TO_ADD": "Select How To Add", + "ADD": "Add", + "CARRIERS_CATALOG": "Carriers Catalog", + "SELECT_FROM_CARRIERS_CATALOG": "Select from Carriers Catalog", + "ADD_NEW_CARRIER": "Add new carrier", + "FIRST_NAME": "First Name", + "LAST_NAME": "Last Name", + "EMAIL": "Email", + "USERNAME": "Username", + "PASSWORD": "Password", + "NAME": "Name", + "PHONE": "Phone", + "ADDRESS": "Address", + "LOGO": "Logo", + "IS_ACTIVE": "Is active", + "CITY": "City", + "STREET": "Street", + "HOUSE": "House", + "LATITUDE": "Latitude", + "LONGITUDE": "Longitude", + "COUNTRY": "Country", + "OK": "OK", + "CANCEL": "CANCEL", + "PREVIOUS": "PREVIOUS", + "BACK": "BACK", + "NEXT": "NEXT", + "DONE": "DONE", + "LOCATION": "LOCATION", + "ZIP": "ZIP", + "PROFILE_DETAILS": "PROFILE DETAILS", + "BROWSE": "BROWSE", + "DRAG_AND_DROP_FILE_HERE": "Drag & Drop Image here", + "PICTURE_URL": "Picture url", + "ACCOUNT": "ACCOUNT", + "REPEAT": "Repeat", + "REPEAT_PASSWORD": "Repeat password", + "PASSWORD_DO_NOT_MATCH": "Password do not match", + "FIND_ADDRESS": "Find Address", + "FORMS": { + "ERRORS": { + "FIRST_NAME_REQUIRED": "First name is required", + "LAST_NAME_REQUIRED": "Last name is required", + "PHONE_REQUIRED": "Phone is required", + "EMAIL_IS_REQUIRED": "Email is required", + "MUST_CONTAIN_ONLY_LETTERS": "Must contain only letters", + "PHONE_MUST_CONTAINS_ONLY(special_signs)AND_DIGIT_CHARACTER": "Phone number can start with '+' or '(some numbers)' and must contains only only: '-, ., (space), #'' and digit characters" + } + } + }, + "PROMOTIONS": { + "PROMOTIONS": "Promotions", + "NO_PROMOTIONS": "No promotions", + "BASIC_INFO": "Basic Info", + "TITLE": "Title", + "ACTIVE_FROM": "Active from", + "ACTIVE_TO": "Active to", + "PURCHASES_COUNT": "Purchases count", + "IS_ACTIVE": "Is active", + "SELECT_DATE": "Select date", + "DESCRIPTION": "Description", + "SAVE": "Save", + "NEXT": "Next", + "BACK": "Back", + "LANGUAGE": "Language", + "ACTIVE": "Active", + "IMAGE": "Image", + "STATUS": "Status" + }, + "SELECT LANGUAGE": { + "USA": "USA", + "ISRAEL": "Israel", + "BULGARIA": "Bulgaria" + } + }, + "SETTINGS_VIEW": { + "SETTINGS": "Settings", + "OPTIONS": "Настройки", + "COMMON": "Common", + "LOCATION": "Location", + "ACCOUNT": "Account", + "NEW_USERNAME": "New Username", + "NAME_MUST_BE_AT_LEAST_4_CHARACTERS_LONG": "Name must be at least 4 characters long", + "OLD_PASSWORD": "Old Password", + "NEW_PASSWORD": "New Password", + "REPEAT_NEW_PASSWORD": "Repeat New Password", + "PASSWORDS_DO_NOT_MATCH": "Passwords do not match", + "SAVE_CHANGES": "Save Changes", + "SAVE": "Save", + "CHANGES": "Changes", + "BASIC_INFO": "Basic Info", + "NAME": "Name", + "NAME_IS_REQUIRED": "Name is required", + "LOGO": "Logo", + "BROWSE": "Browse", + "CONTACT_INFO": "Contact Info", + "E_MAIL": "E-mail", + "PHON_NUMBER": "Phone Number", + "COUNTRY": "Country", + "CITY": "City", + "CITY_IS_REQUIRED": "City is required", + "POSTCODE_optional": "Postcode (optional)", + "STREET": "Street", + "STREET_IS_REQUIRED": "Street is required", + "HOUSE": "House", + "HOUSE_IS_REQUIRED": "House is required", + "APARTMENT_optional": "Apartment (optional)", + "AUTO_DETECT_COORDINATES": "Auto detect coordinates", + "LATITUDE": "Latitude", + "LATITUDE_IS_REQUIRED": "Latitude is required", + "LONGITUDE": "Longitude", + "LONGITUDE_IS_REQUIRED": "Longitude is required", + "FIND_ADDRESS": "Find Address", + "OK": "OK", + "CANCEL": "CANCEL", + "ALLOW_ONLINE_PAYMENTS": "Allow online payments", + "SETTINGS_SECTION": { + "DELIVERY_OR_TAKEAWAY_SETTINGS": "Delivery/Takeaway Settings", + "PRODUCTS_DELIVERY_BY_DEFAULT": "Products Delivery (by default)", + "PRODUCTS_TAKEAWAY_BY_DEFAULT": "Products Takeaway (by default)", + "PAYMENTS_SETTINGS": "Payments Settings", + "BARCODE_QR_CODE_SETTINGS": "Barcode/QR code Settings", + "ORDER_BARCODE_TYPE": "Order barcode type", + "BARCODE_DATA": "Barcode data", + "SCAN_EXISTED": "Scan Existed", + "DELIVERY_SETTINGS": "Delivery settings", + "TAKEAWAY_SETTINGS": "Takeaway settings", + "IN_STORE_MODE": "In-store mode", + "IN_STORE_MODE_SETTINGS": "In-store mode settings" + } + }, + "LOGIN_VIEW": { + "LOGIN": "Login" + }, + "LANGUAGE_VIEW": { + "TITLE": "Select Language", + "OK": "OK", + "CANCEL": "CANCEL" + }, + "ABOUT_VIEW": { + "TITLE": "About Us", + "APP_VERSION": "App Version" + }, + "TERMS_OF_USE_VIEW": { + "TITLE": "Terms of Use" + }, + "WAREHOUSE_VIEW": { + "RELEVANT": "Relevant", + "ALL": "All", + "LOADING_MORE_DATA": "Loading more data...", + "ORDERS": "Orders", + "SWITCH_ORDERS": "Switch Orders", + "NEW_ORDER": "New Order", + "PRODUCTS": "Top Products", + "ARE_YOU_SURE_TO_DELETE": "Are you sure you want to ", + "REMOVE": "Remove", + "DELETE": "Delete", + "THE_FOLLOWING_DATA": "the following data", + "CONFIRM": "Confirm", + "CANCEL": "Cancel", + "SELECT_POP_UP": { + "SELECT_CATEGORIES": "Select Categories", + "OK": "OK", + "CANCEL": "CANCEL" + }, + "ORDER_WAREHOUSE_STATUSES": { + "ALL": "All", + "CANCELLED": "Cancelled", + "CREATED": "Created", + "CONFIRMED": "Confirmed", + "PROCESSING": "Processing", + "ALLOCATION_STARTED": "Allocation Started", + "ALLOCATION_FINISHED": "Allocation Finished", + "PACKAGING_STARTED": "Packaging Started", + "PACKAGED": "Packaged", + "GIVEN_TO_CARRIER": "Given to Carrier", + "TAKEN": "Taken", + "ALLOCATION_FAILED": "Allocation Failed", + "PACKAGING_FAILED": "Packaging Failed", + "BAD_STATUS": "Bad Status", + "NO_ACTIVE_ORDERS": "No Active Orders", + "NO_ORDERS": "No Orders", + "NO_PRODUCTS": "No Products" + }, + "CREATE_PRODUCTS_POPUP": { + "ERROR_MSG": "Something is wrong, unable to place order!", + "NEW_PRODUCT_TYPE": "New Product Type", + "CREATE_NEW_PRODUCT_TYPE": "Create a new product type", + "TITLE": "Title", + "DESCRIPTION": "Description", + "CATEGORIES": "Categories", + "PRICE": "Price", + "COUNT": "Count", + "LANGUAGE": "Language", + "COUNT_PLACEHOLDER": "0", + "DRAG&DROP_FILE_HERE": "Drag & Drop File here", + "DRAG&DROP_PICTURE_HERE": "Drag & Drop Picture here", + "OR_BROWSE": "or browse", + "CLICK_TO_UPLOAD_PICTURE": "Click to Upload Picture", + "CLICK_TO_UPLOAD_MORE_PICTURE": "Click to Upload More Pictures", + "CANCEL": "Cancel", + "CREATE": "Create", + "ENGLISH": "English", + "HEBREW": "Hebrew", + "RUSSIAN": "Russian", + "BULGARIAN": "Bulgarian", + "SPANISH": "Spanish", + "FRENCH": "French", + "OPTIONAL": "optional", + "TAKEAWAY": "Takeaway", + "DELIVERY": "Delivery", + "PRODUCT_AVAILABILITY": "Product availability" + }, + "NEW_ORDER_VIEW": { + "NEW_MANUAL_ORDER": "New Manual Order", + "SELECT_EXISTING_CUSTOMER": "Select Existing Customer", + "ADD_NEW_CUSTOMER": "Add New Customer", + "FULL_NAME": "Full Name", + "EMAIL": "Email", + "PHONE": "Phone", + "ADDRESS": "Address", + "IMAGE": "Image", + "PRODUCT": "Product", + "PRICE": "Price", + "AVAILABLE": "Available", + "COMMENT": "Comment", + "AMOUNT": "Amount", + "MAKE_ORDER": "Make Order", + "BASIC_INFO": "Basic Info", + "FIRST_NAME": "First Name", + "LAST_NAME": "Last Name", + "EMAIL_IS_INVALID": "Email is invalid", + "EMAIL_IS_ALREADY_IN_USE": "Email is already in use", + "NEXT": "Next", + "ADD_CUSTOMER": "Add Customer", + "BACK": "Back", + "FIND_ADDRESS": "Find Address", + "COUNTRY": "Country", + "CITY": "City", + "COUNTRY_IS_INVALID": "Country is invalid", + "CITY_IS_REQUIRED": "City is required", + "ZIP": "ZIP", + "STREET_ADDRESS": "Street Address", + "STREET_ADDRESS_IS_REQUIRED": "Street address is required", + "HOUSE_№": "House №", + "APARTMENT": "Apartment", + "SHOW_COORDINATES": "Show Coordinates", + "LATITUDE": "Latitude", + "LONGITUTE": "Longitude", + "HOUSE_IS_REQUIRED": "House is required" + }, + "EDIT_PICTURE": { + "PRODUCT_IMAGES": "Product images", + "SAVE_IMAGES": "Save images" + }, + "MISC_TEXT": { + "CREATE_NEW_ORDER": "Create New Order", + "ADD_NEW_PRODUCT": "Add New Product" + } + }, + "EDIT_PRODUCT_POP_UP": { + "EDIT_PRODUCT_TYPE": "Edit product type", + "UPDATE_AN_EXISTING_PRODUCT": "Update an existing product", + "CLICK_TO_EDIT_PICTURES": "Click to Edit Pictures", + "TITLE": "Title", + "DESCRIPTION": "Description", + "CATEGORIES": "Categories", + "PRICE": "Price", + "COUNT": "Count", + "LANGUAGE": "Language", + "DRAG&DROP_FILE_HERE": "Drag & Drop File here", + "OR_BROWSE": "or browse", + "CLICK_TO_UPLAOD_PICTURE": "Click to Upload Picture", + "CANCEL": "Cancel", + "UPDATE": "Update", + "ENGLISH": "English", + "HEBREW": "Hebrew", + "RUSSIAN": "Russian", + "BULGARIAN": "Bulgarian", + "SPANISH": "Spanish", + "FRENCH": "French", + "OPTIONAL": "optional" + }, + "ORDER_CANCELED_COMPONENT": { + "CANCELED": "Canceled", + "ORDER_CANCELED": "Order canceled", + "DELIVERED": "Delivered", + "CREATED_AT": "Created at" + }, + "ORDER_CONTROL_BUTTONS_COMPONENT": { + "CONFIRM": "Confirm", + "START_PROCESSING": "Start Processing", + "START_ALLOCATION": "Start Allocation", + "ALLOCATED": "Allocated", + "START_PACKAGING": "Start Packaging", + "PACKAGED": "Packaged", + "FAILED": "Failed", + "GIVEN_TO_CARRIER": "Given to Carrier", + "GIVEN_TO_CUSTOMER": "Given to Customer" + }, + "ORDER_DELIVERED_COMPONENT": { + "DELIVERED": "Delivered", + "CREATED_AT": "Created at", + "DELIVERED_AT": "Delivered at", + "DELIVERED_TO": "Delivered to" + }, + "ORDER_DELIVERY_PROBLEM_COMPONENT": { + "DELIVERY_ISSUES": "Delivery Issues", + "ISSUES_DURING_DELIVERY_TO": "Issues During Delivery to", + "BY": "By", + "CREATED_AT": "Created at" + }, + "ORDER_WAREHOUSE_PREPARATION_PROBLEM_COMPONENT": { + "CREATED_AT": "Created at" + }, + "ORDER_WITH_CARRIER_COMPONENT": { + "IN_DELIVERY_TO": "In Delivery To", + "BY": "By ", + "CREATED_AT": "Created at" + }, + "ORDER_WITHOUT_CARRIER_COMPONENT": { + "CREATED_AT": "Created at", + "CANT_PROCESSING_WITHOUT_PRODUCTS": "Can't processing the order without products." + }, + "ORDER_TITLE_COMPONENT": { + "ORDER": "Order", + "FROM": "from" + }, + "ORDER_TYPE": { + "TYPE": "Order type:", + "DELIVERY": "Delivery", + "TAKEAWAY": "Takeaway" + }, + "TRACK_ORDER_POP_UP": { + "CARRIER": "Carrier", + "TRACK": "Track", + "CUSTOMER": "Customer" + }, + "CONNECTION_ERROR_VIEW": { + "TITLE": "No connection to Ever", + "DESCRIPTION": [ + "Please make sure you are", + "connected to the internet", + "or try Ever app later." + ] + }, + "CUSTOMER_ORDERS_POP_UP": { + "ORDER_ID": "Order ID", + "DELIVERY": "Delivery", + "ADDRESS": "Address", + "STATUS": "Status" + }, + "NO_SERVER_VIEW": { + "NO_SERVER": "Server connection is lost" + }, + "PROMOTIONS": { + "PROMO_PRICE": "Promo price", + "NO_PROMOTIONS": "No Promotions" + } +} diff --git a/packages/merchant-tablet-ionic/src/assets/i18n/he-IL.json b/packages/merchant-tablet-ionic/src/assets/i18n/he-IL.json new file mode 100644 index 0000000..ac79df8 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/assets/i18n/he-IL.json @@ -0,0 +1,487 @@ +{ + "LANGUAGE": { + "ID": "he-IL", + "NAME": "עברית" + }, + "DASHBOARD": "לוּחַ מַחווָנִים", + "CURRENT_DIRECTION": "rtl", + "SIDEBAR_SIDE": "ימין", + "SIDE_MENU": { + "TITLE": "אי פעם סוחרים", + "GROUPS": { + "NO_TITLE": { + "DIVER_TITLE": "", + "ITEMS": { + "WAREHOUSE": "לוּחַ מַחווָנִים" + } + }, + "SETTINGS": { + "SETTINGS": "הגדרות", + "DIVER_TITLE": "הגדרות", + "ITEMS": { + "LANGUAGE": "(Language) שפה", + "SIGN_OUT": "להתנתק" + } + }, + "INFO": { + "DIVER_TITLE": "מידע", + "ITEMS": { + "ABOUT_US": "עלינו", + "TERMS_OF_USE": "תנאי שימוש" + } + } + } + }, + "CUSTOMERS_VIEW": { + "CUSTOMERS": "לקוחות", + "ADD_CUSTOMER": "הוסף לקוח", + "IMAGE": "תמונה", + "NAME": "שֵׁם", + "PHONE_NUMBER": "מספר טלפון", + "ADDRESSES": "כתובות", + "ORDERS": "הזמנות", + "TOTAL": "סה'כ", + "E_MAIL": "אֶלֶקטרוֹנִי", + "ADDRESS_POP_UP": { + "CUSTOMER_ADDRESS": "כתובת הלקוח", + "COUNTRY": "מדינה", + "STREET": "רחוב", + "HOUSE": "בית", + "APARTMENT": "דירה", + "COORDINATES": "קואורדינטות" + }, + "EMAIL_POP_UP": { + "CUSTOMER_EMAIL": "דוא'ל לקוח", + "SEND_E_MAIL": "שלח אימייל" + }, + "ORDERS_POP_UP": { + "CUSTOMER_ORDERS": "הזמנות לקוח", + "ALL_ORDERS": "כל ההזמנות", + "TOTAL_ORDERS_SUM": "סה'כ סה'כ הזמנות", + "COMPLATED": "הושלם", + "PAID": "בתשלום" + } + }, + "FILE_UPLOADER": { + "DRAG_AND_DROP_FILE_HERE": "גרור ושחרר תמונה כאן", + "PICTURE_URL": "כתובת אתר של תמונה", + "BROWSE": "דפדוף" + }, + "CARRIERS_VIEW": { + "CARRIERS_CATALOG": { + "IMAGE": "תמונה", + "NAME": "שֵׁם", + "PHONE": "טלפון", + "ADDRESS": "כתובת", + "LOGO": "סֵמֶל" + }, + "TRACK_ORDER": "עקוב אחר הסדר", + "TRACK_CARRIER": "עקוב אחר ספק", + "DELIVERY_TRACKING": "מעקב משלוחים", + "CARRIER": "מנשא", + "STORE": "חנות", + "DELIVERY_COUNT": "ספירת משלוחים", + "CUSTOMER": "צרכן", + "CARRIERS": "ספקים", + "ADD_CARRIERS": "הוסף ספקים", + "IMAGE": "תמונה", + "NAME": "שֵׁם", + "PHONE_NUMBER": "מספר טלפון", + "ADDRESSES": "כתובת", + "STATUS": "סטָטוּס", + "DELIVERIES": "משלוחים", + "NO_CARRIERS": "אין ספקים", + "WORKING": "עובד", + "NOT_WORKING": "לא עובד", + "BLOCKED": "נחסם", + "DELIVERIES_POP_UP": { + "CARRIER_DELIVERIES": "משלוחים", + "ALL_DELIVERIES": "כל משלוחים", + "DELIVERIES_TODAY": "משלוחים היום", + "TOTAL_DISTANCE_TODAY": "סה'כ מרחק היום", + "CUSTOMER": "צרכן", + "WAREHOUSE": "מַחסָן", + "STATUS": "סטָטוּס", + "DELIVERY": "מְסִירָה", + "NO_DELIVERIES": "אין משלוחים", + "COMPLETED": "הושלם", + "PAID": "בתשלום" + }, + "ADDRESS": { + "CARRIER_ADDRESS": "כתובת הספק", + "COUNTRY": "מדינה", + "HOUSE": "בַּיִת", + "COORDINATES": "קואורדינטות" + }, + "EDIT_CARRIER": { + "EDIT_CARRIER": "ערוך ספק" + }, + "ADD_CARRIER": { + "ADD_CARRIER": "הוסף ספק", + "HOW_TO_ADD": "איך להוסיף", + "SELECT_HOW_TO_ADD": "בחר כיצד להוסיף", + "ADD": "לְהוֹסִיף", + "CARRIERS_CATALOG": "קטלוג ספקים", + "SELECT_FROM_CARRIERS_CATALOG": "בחר מתוך קטלוג ספקים", + "ADD_NEW_CARRIER": "הוסף ספק חדש", + "FIRST_NAME": "שם פרטי", + "LAST_NAME": "שם משפחה", + "EMAIL": "דוא'ל", + "USERNAME": "שם משתמש", + "PASSWORD": "סיסמה", + "NAME": "שֵׁם", + "PHONE": "טלפון", + "ADDRESS": "כתובת", + "LOGO": "סֵמֶל", + "IS_ACTIVE": "פעיל", + "CITY": "עִיר", + "STREET": "רְחוֹב", + "HOUSE": "בַּיִת", + "LATITUDE": "קו רוחב", + "LONGITUDE": "קו האורך", + "COUNTRY": "מדינה", + "OK": "בסדר", + "CANCEL": "בטל", + "PREVIOUS": "קודם", + "BACK": "חזרה", + "NEXT": "הבא", + "DONE": "בוצע", + "LOCATION": "מקום", + "ZIP": "רוכסן", + "PROFILE_DETAILS": "פרטי פרופיל", + "BROWSE": "דפדוף", + "DRAG_AND_DROP_FILE_HERE": "גרור ושחרר תמונה כאן", + "PICTURE_URL": "כתובת אתר של תמונה", + "ACCOUNT": "חשבון", + "REPEAT": "חזור", + "REPEAT_PASSWORD": "חזור על הסיסמה", + "PASSWORD_DO_NOT_MATCH": "הסיסמה אינה תואמת", + "FIND_ADDRESS": "חפש כתובת", + "FORMS": { + "ERRORS": { + "FIRST_NAME_REQUIRED": "נדרש שם פרטי", + "LAST_NAME_REQUIRED": "נדרש שם משפחה", + "PHONE_REQUIRED": "טלפון נדרש", + "EMAIL_IS_REQUIRED": "נדרש דואל", + "MUST_CONTAIN_ONLY_LETTERS": "חייב להכיל רק אותיות", + "PHONE_MUST_CONTAINS_ONLY(special_signs)AND_DIGIT_CHARACTER": "מספר הטלפון יכול להתחיל עם '+' והוא חייב להכיל רק: ',,., (רווח), #' 'ותווים ספרותיים" + } + } + }, + "PROMOTIONS": { + "PROMOTIONS": "מבצעים", + "NO_PROMOTIONS": "אין מבצעים", + "BASIC_INFO": "מידע בסיסי", + "TITLE": "כותרת", + "ACTIVE_FROM": "פעיל מ-", + "ACTIVE_TO": "פעיל ל", + "PURCHASES_COUNT": "ספירת רכישות", + "IS_ACTIVE": "פעיל", + "SELECT_DATE": "בחר תאריך", + "DESCRIPTION": "תיאור", + "SAVE": "לשמור", + "NEXT": "הַבָּא", + "BACK": "חזור", + "LANGUAGE": "שפה" + }, + "SELECT LANGUAGE": { + "USA": "ארה'ב", + "ISRAEL": "ישראל", + "BULGARIA": "בולגריה" + } + }, + "SETTINGS_VIEW": { + "SETTINGS": "הגדרות", + "OPTIONS": "אפשרויות", + "COMMON": "מְשׁוּתָף", + "LOCATION": "מקום", + "ACCOUNT": "חֶשְׁבּוֹן", + "NEW_USERNAME": "שם משתמש חדש", + "NAME_MUST_BE_AT_LEAST_4_CHARACTERS_LONG": "על השם להיות באורך של 4 תווים לפחות", + "OLD_PASSWORD": "סיסמה ישנה", + "NEW_PASSWORD": "סיסמה חדשה", + "REPEAT_NEW_PASSWORD": "חזור על סיסמה חדשה", + "PASSWORDS_DO_NOT_MATCH": "סיסמאות לא תואמות", + "SAVE_CHANGES": "שמור שינויים", + "SAVE": "להציל", + "CHANGES": "שינויים", + "BASIC_INFO": "מידע בסיסי", + "NAME": "שם", + "NAME_IS_REQUIRED": "נדרש שם", + "LOGO": "לוגו", + "BROWSE": "עיון", + "CONTACT_INFO": "פרטים ליצירת קשר", + "E_MAIL": "דואר אלקטרוני", + "PHON_NUMBER": "מספר טלפון", + "COUNTRY": "מדינה", + "CITY": "עיר", + "CITY_IS_REQUIRED": "עיר נדרשת", + "POSTCODE_optional": "מיקוד (אופציונלי)", + "STREET": "רחוב", + "STREET_IS_REQUIRED": "רחוב נדרש", + "HOUSE": "בית", + "HOUSE_IS_REQUIRED": "הבית נדרש", + "APARTMENT_optional": "דירה (אופציונלית)", + "AUTO_DETECT_COORDINATES": "זיהוי אוטומטי של קואורדינטות", + "LATITUDE": "קו רוחב", + "LATITUDE_IS_REQUIRED": "Latitude נדרש", + "LONGITUDE": "קו האורך", + "LONGITUDE_IS_REQUIRED": "קו האורך נדרש", + "FIND_ADDRESS": "חפש כתובת", + "SELECT_CATEGORIES": "בחר קטגוריות", + "OK": "בסדר", + "CANCEL": "בטל", + "ALLOW_ONLINE_PAYMENTS": "אפשר תשלומים מקוונים", + "SETTINGS_SECTION": { + "DELIVERY_OR_TAKEAWAY_SETTINGS": "הגדרות משלוח / תפוסה", + "PRODUCTS_DELIVERY_BY_DEFAULT": "משלוח מוצרים (כברירת מחדל)", + "PRODUCTS_TAKEAWAY_BY_DEFAULT": "מוצרים Takeaway (כברירת מחדל)", + "PAYMENTS_SETTINGS": "הגדרות תשלומים", + "BARCODE_QR_CODE_SETTINGS": "ברקוד / קוד QR הגדרות", + "ORDER_BARCODE_TYPE": "הקלד סוג ברקוד", + "BARCODE_DATA": "נתוני ברקוד", + "SCAN_EXISTED": "סרוק קיים", + "DELIVERY_SETTINGS": "הגדרות משלוח", + "TAKEAWAY_SETTINGS": "הגדרות ברזל", + "IN_STORE_MODE": "במצב בחנות", + "IN_STORE_MODE_SETTINGS": "הגדרות מצב בחנות" + } + }, + "LOGIN_VIEW": { + "LOGIN": "התחברות" + }, + "LANGUAGE_VIEW": { + "TITLE": "בחירת שפה", + "OK": "בסדר", + "CANCEL": "בטל" + }, + "ABOUT_VIEW": { + "TITLE": "עלינו", + "APP_VERSION": "גרסת האפליקציה" + }, + "TERMS_OF_USE_VIEW": { + "TITLE": "תנאי שימוש" + }, + "WAREHOUSE_VIEW": { + "RELEVANT": "רלוונטי", + "ALL": "את כל", + "LOADING_MORE_DATA": "טוען נתונים נוספים ...", + "ORDERS": "הזמנות", + "SWITCH_ORDERS": "החלף הזמנות", + "NEW_ORDER": "הזמנה חדשה", + "PRODUCTS": "מוצרים מובילים", + "ARE_YOU_SURE_TO_DELETE": "לְבַטֵל", + "REMOVE": "לְהַסִיר", + "DELETE": "לִמְחוֹק", + "THE_FOLLOWING_DATA": "הנתונים הבאים", + "CONFIRM": "לְאַשֵׁר", + "CANCEL": "לְבַטֵל", + "SELECT_POP_UP": { + "SELECT_CATEGORIES": "בחר קטגוריות", + "OK": "בסדר", + "CANCEL": "בטל" + }, + "ORDER_WAREHOUSE_STATUSES": { + "ALL": "את כל", + "CANCELLED": "מבוטל", + "CREATED": "נוצר", + "CONFIRMED": "מְאוּשָׁר", + "PROCESSING": "מעבד", + "ALLOCATION_STARTED": "ההקצאה החלה", + "ALLOCATION_FINISHED": "ההקצאה הסתיימה", + "PACKAGING_STARTED": "האריזה התחילה", + "PACKAGED": "ארוז", + "GIVEN_TO_CARRIER": "נתון למוביל", + "TAKEN": "נלקח", + "ALLOCATION_FAILED": "ההקצאה נכשלה", + "PACKAGING_FAILED": "האריזות נכשלו", + "BAD_STATUS": "מצב גרוע", + "NO_ACTIVE_ORDERS": "אין הזמנות פעילות", + "NO_ORDERS": "אין הזמנות", + "NO_PRODUCTS": "אין מוצרים" + }, + "CREATE_PRODUCTS_POPUP": { + "ERROR_MSG": "משהו לא בסדר, לא מצליח לבצע סדר!", + "NEW_PRODUCT_TYPE": "סוג מוצר חדש", + "CREATE_NEW_PRODUCT_TYPE": "צור סוג מוצר חדש", + "TITLE": "כותרת", + "DESCRIPTION": "תיאור", + "CATEGORIES": "קטגוריות", + "PRICE": "מחיר", + "COUNT": "לספור", + "LANGUAGE": "שפה", + "COUNT_PLACEHOLDER": "0", + "DRAG&DROP_FILE_HERE": "גרור ושחרר קובץ כאן", + "DRAG&DROP_PICTURE_HERE": "גרור ושחרר תמונה כאן", + "OR_BROWSE": "או לגלוש", + "CLICK_TO_UPLOAD_PICTURE": "לחץ כדי להעלות תמונה", + "CLICK_TO_UPLOAD_MORE_PICTURE": "לחץ כדי להעלות תמונות נוספות", + "CANCEL": "לְבַטֵל", + "CREATE": "לִיצוֹר", + "ENGLISH": "אנגלית", + "HEBREW": "עִברִית", + "RUSSIAN": "רוּסִי", + "BULGARIAN": "בולגרית", + "SPANISH": "ספרדית", + "FRENCH": "צָרְפָתִית", + "OPTIONAL": "אופציונאלי", + "TAKEAWAY": "להסיר", + "DELIVERY": "משלוח", + "PRODUCT_AVAILABILITY": "זמינות מוצר" + }, + "NEW_ORDER_VIEW": { + "NEW_MANUAL_ORDER": "סדר ידני חדש", + "SELECT_EXISTING_CUSTOMER": "בחר לקוח קיים", + "ADD_NEW_CUSTOMER": "הוסף לקוח חדש", + "FULL_NAME": "שם מלא", + "EMAIL": "דוא'ל", + "PHONE": "מכשיר טלפון", + "ADDRESS": "כתובת", + "IMAGE": "תמונה", + "PRODUCT": "מוצר", + "PRICE": "מחיר", + "AVAILABLE": "זמין", + "COMMENT": "תגובה", + "AMOUNT": "כמות", + "MAKE_ORDER": "בצע הזמנה", + "BASIC_INFO": "מידע בסיסי", + "FIRST_NAME": "שם פרטי", + "LAST_NAME": "שם משפחה", + "EMAIL_IS_INVALID": "דואר אלקטרוני לא תקין", + "EMAIL_IS_ALREADY_IN_USE": "דוא'ל כבר בשימוש", + "NEXT": "הבא", + "ADD_CUSTOMER": "חזור", + "BACK": "חזור", + "FIND_ADDRESS": "חפש כתובת", + "COUNTRY": "מדינה", + "CITY": "עיר", + "COUNTRY_IS_INVALID": "הארץ אינה חוקית", + "CITY_IS_REQUIRED": "עיר נדרשת", + "ZIP": "רוכסן", + "STREET_ADDRESS": "כתובת רחוב", + "STREET_ADDRESS_IS_REQUIRED": "נדרשת כתובת רחוב", + "HOUSE_№": "№ הבית", + "APARTMENT": "דירה", + "SHOW_COORDINATES": "הצג קואורדינטות", + "LATITUDE": "קו רוחב", + "LONGITUTE": "קו האורך", + "HOUSE_IS_REQUIRED": "הבית נדרש" + }, + "EDIT_PICTURE": { + "PRODUCT_IMAGES": "תמונות של מוצרים", + "SAVE_IMAGES": "שמור תמונות" + }, + "MISC_TEXT": { + "CREATE_NEW_ORDER": "צור הזמנה חדשה", + "ADD_NEW_PRODUCT": "הוסף מוצר חדש" + } + }, + "EDIT_PRODUCT_POP_UP": { + "EDIT_PRODUCT_TYPE": "ערוך את סוג המוצר", + "UPDATE_AN_EXISTING_PRODUCT": "עדכן מוצר קיים", + "CLICK_TO_EDIT_PICTURES": "לחץ כדי לערוך תמונות", + "TITLE": "כותרת", + "DESCRIPTION": "תיאור", + "CATEGORIES": "קטגוריות", + "PRICE": "מחיר", + "COUNT": "לספור", + "LANGUAGE": "שפה", + "DRAG&DROP_FILE_HERE": "גרור ושחרר קובץ כאן", + "OR_BROWSE": "או לגלוש", + "CLICK_TO_UPLAOD_PICTURE": "לחץ כדי להעלות תמונה", + "CANCEL": "לְבַטֵל", + "UPDATE": "עדכון", + "ENGLISH": "אנגלית", + "HEBREW": "עִברִית", + "RUSSIAN": "רוּסִי", + "BULGARIAN": "בולגרית", + "SPANISH": "ספרדית", + "FRENCH": "צָרְפָתִית" + }, + "ORDER_CANCELED_COMPONENT": { + "CANCELED": "מבוטל", + "ORDER_CANCELED": "ההזמנה בוטלה", + "DELIVERED": "נמסר", + "CREATED_AT": "נוצר ב" + }, + "ORDER_CONTROL_BUTTONS_COMPONENT": { + "CONFIRM": "לְאַשֵׁר", + "START_PROCESSING": "התחל עיבוד", + "START_ALLOCATION": "התחל הקצאה", + "ALLOCATED": "מוּקצֶה", + "START_PACKAGING": "התחל אריזה", + "PACKAGED": "ארוז", + "FAILED": "נִכשָׁל", + "GIVEN_TO_CARRIER": "ניתן ל- מוֹבִיל", + "GIVEN_TO_CUSTOMER": "ניתן ללקוח" + }, + "ORDER_CONTROL_BUTTONS": { + "CONFIRM": "לְאַשֵׁר", + "START_PROCESSING": "התחל עיבוד", + "START_ALLOCATION": "התחל הקצאה", + "ALLOCATED": "מוּקצֶה", + "START_PACKAGING": "התחל אריזה", + "PACKAGED": "ארוז", + "FAILED": "נִכשָׁל", + "GIVEN_TO_CARRIER": "ניתן ל- מוֹבִיל" + }, + "ORDER_DELIVERED_COMPONENT": { + "DELIVERED": "נמסר", + "CREATED_AT": "נוצר ב", + "DELIVERED_AT": "נמסר ב", + "DELIVERED_TO": "הועבר ל" + }, + "ORDER_DELIVERY_PROBLEM_COMPONENT": { + "DELIVERY_ISSUES": "הנפקות משלוח", + "ISSUES_DURING_DELIVERY_TO": "נושאים במהלך משלוח ל", + "BY": "על ידי", + "CREATED_AT": "נוצר ב" + }, + "ORDER_WAREHOUSE_PREPARATION_PROBLEM_COMPONENT": { + "CREATED_AT": "נוצר ב" + }, + "ORDER_WITH_CARRIER_COMPONENT": { + "IN_DELIVERY_TO": "במסירה אל", + "BY": "על ידי", + "CREATED_AT": "נוצר ב" + }, + "ORDER_WITHOUT_CARRIER_COMPONENT": { + "CREATED_AT": "נוצר ב", + "CANT_PROCESSING_WITHOUT_PRODUCTS": "לא ניתן לעבד את ההזמנה ללא מוצרים." + }, + "ORDER_TITLE_COMPONENT": { + "ORDER": "להזמין", + "FROM": "מ" + }, + "ORDER_TYPE": { + "TYPE": ":סוג הזמנה", + "DELIVERY": "מְסִירָה", + "TAKEAWAY": "להסיר" + }, + "TRACK_ORDER_POP_UP": { + "CARRIER": "המוביל", + "TRACK": "עקוב אחר", + "CUSTOMER": "צרכן" + }, + "CONNECTION_ERROR_VIEW": { + "TITLE": "אין חיבור ל-Ever", + "DESCRIPTION": [ + "בבקשה תוודא שאתה", + "מחובר לאינטרנט או תנסה", + "את האפליקציה מאוחר יותר." + ] + }, + "CUSTOMER_ORDERS_POP_UP": { + "ORDER_ID": "מספר הזמנה", + "DELIVERY": "מְסִירָה", + "ADDRESS": "מְסִירָה", + "STATUS": "סטָטוּס" + }, + "NO_SERVER_VIEW": { + "NO_SERVER": "השרת נפל" + }, + "PROMOTIONS": { + "PROMO_PRICE": "מחיר פרומו", + "NO_PROMOTIONS": "אין מבצעים" + } +} diff --git a/packages/merchant-tablet-ionic/src/assets/i18n/he.json b/packages/merchant-tablet-ionic/src/assets/i18n/he.json new file mode 100644 index 0000000..8f56060 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/assets/i18n/he.json @@ -0,0 +1,484 @@ +{ + "LANGUAGE": { + "ID": "he-IL", + "NAME": "עברית" + }, + "DASHBOARD": "לוּחַ מַחווָנִים", + "CURRENT_DIRECTION": "rtl", + "SIDEBAR_SIDE": "ימין", + "SIDE_MENU": { + "TITLE": "אי פעם סוחרים", + "GROUPS": { + "NO_TITLE": { + "DIVER_TITLE": "", + "ITEMS": { + "WAREHOUSE": "לוּחַ מַחווָנִים" + } + }, + "SETTINGS": { + "SETTINGS": "הגדרות", + "DIVER_TITLE": "הגדרות", + "ITEMS": { + "LANGUAGE": "(Language) שפה", + "SIGN_OUT": "להתנתק" + } + }, + "INFO": { + "DIVER_TITLE": "מידע", + "ITEMS": { + "ABOUT_US": "עלינו", + "TERMS_OF_USE": "תנאי שימוש" + } + } + } + }, + "CUSTOMERS_VIEW": { + "CUSTOMERS": "לקוחות", + "ADD_CUSTOMER": "הוסף לקוח", + "IMAGE": "תמונה", + "NAME": "שֵׁם", + "PHONE_NUMBER": "מספר טלפון", + "ADDRESSES": "כתובות", + "ORDERS": "הזמנות", + "TOTAL": "סה'כ", + "E_MAIL": "אֶלֶקטרוֹנִי", + "ADDRESS_POP_UP": { + "CUSTOMER_ADDRESS": "כתובת הלקוח", + "COUNTRY": "מדינה", + "STREET": "רחוב", + "HOUSE": "בית", + "APARTMENT": "דירה", + "COORDINATES": "קואורדינטות" + }, + "EMAIL_POP_UP": { + "CUSTOMER_EMAIL": "דוא'ל לקוח", + "SEND_E_MAIL": "שלח אימייל" + }, + "ORDERS_POP_UP": { + "CUSTOMER_ORDERS": "הזמנות לקוח", + "ALL_ORDERS": "כל ההזמנות", + "TOTAL_ORDERS_SUM": "סה'כ סה'כ הזמנות", + "COMPLATED": "הושלם", + "PAID": "בתשלום" + } + }, + "FILE_UPLOADER": { + "DRAG_AND_DROP_FILE_HERE": "גרור ושחרר תמונה כאן", + "PICTURE_URL": "כתובת אתר של תמונה", + "BROWSE": "דפדוף" + }, + "CARRIERS_VIEW": { + "CARRIERS_CATALOG": { + "IMAGE": "תמונה", + "NAME": "שֵׁם", + "PHONE": "טלפון", + "ADDRESS": "כתובת", + "LOGO": "סֵמֶל" + }, + "TRACK_ORDER": "עקוב אחר הסדר", + "TRACK_CARRIER": "עקוב אחר ספק", + "DELIVERY_TRACKING": "מעקב משלוחים", + "CARRIER": "מנשא", + "STORE": "חנות", + "DELIVERY_COUNT": "ספירת משלוחים", + "CUSTOMER": "צרכן", + "CARRIERS": "ספקים", + "ADD_CARRIERS": "הוסף ספקים", + "IMAGE": "תמונה", + "NAME": "שֵׁם", + "PHONE_NUMBER": "מספר טלפון", + "ADDRESSES": "כתובת", + "STATUS": "סטָטוּס", + "DELIVERIES": "משלוחים", + "NO_CARRIERS": "אין ספקים", + "WORKING": "עובד", + "NOT_WORKING": "לא עובד", + "BLOCKED": "נחסם", + "DELIVERIES_POP_UP": { + "CARRIER_DELIVERIES": "משלוחים", + "ALL_DELIVERIES": "כל משלוחים", + "DELIVERIES_TODAY": "משלוחים היום", + "TOTAL_DISTANCE_TODAY": "סה'כ מרחק היום", + "CUSTOMER": "צרכן", + "WAREHOUSE": "מַחסָן", + "STATUS": "סטָטוּס", + "DELIVERY": "מְסִירָה", + "NO_DELIVERIES": "אין משלוחים", + "COMPLETED": "הושלם", + "PAID": "בתשלום" + }, + "ADDRESS": { + "CARRIER_ADDRESS": "כתובת הספק", + "COUNTRY": "מדינה", + "HOUSE": "בַּיִת", + "COORDINATES": "קואורדינטות" + }, + "EDIT_CARRIER": { + "EDIT_CARRIER": "ערוך ספק" + }, + "ADD_CARRIER": { + "ADD_CARRIER": "הוסף ספק", + "HOW_TO_ADD": "איך להוסיף", + "SELECT_HOW_TO_ADD": "בחר כיצד להוסיף", + "ADD": "לְהוֹסִיף", + "CARRIERS_CATALOG": "קטלוג ספקים", + "SELECT_FROM_CARRIERS_CATALOG": "בחר מתוך קטלוג ספקים", + "ADD_NEW_CARRIER": "הוסף ספק חדש", + "FIRST_NAME": "שם פרטי", + "LAST_NAME": "שם משפחה", + "EMAIL": "דוא'ל", + "USERNAME": "שם משתמש", + "PASSWORD": "סיסמה", + "NAME": "שֵׁם", + "PHONE": "טלפון", + "ADDRESS": "כתובת", + "LOGO": "סֵמֶל", + "IS_ACTIVE": "פעיל", + "CITY": "עִיר", + "STREET": "רְחוֹב", + "HOUSE": "בַּיִת", + "LATITUDE": "קו רוחב", + "LONGITUDE": "קו האורך", + "COUNTRY": "מדינה", + "OK": "בסדר", + "CANCEL": "בטל", + "PREVIOUS": "קודם", + "BACK": "חזרה", + "NEXT": "הבא", + "DONE": "בוצע", + "LOCATION": "מקום", + "ZIP": "רוכסן", + "PROFILE_DETAILS": "פרטי פרופיל", + "BROWSE": "דפדוף", + "DRAG_AND_DROP_FILE_HERE": "גרור ושחרר תמונה כאן", + "PICTURE_URL": "כתובת אתר של תמונה", + "ACCOUNT": "חשבון", + "REPEAT": "חזור", + "REPEAT_PASSWORD": "חזור על הסיסמה", + "PASSWORD_DO_NOT_MATCH": "הסיסמה אינה תואמת", + "FIND_ADDRESS": "חפש כתובת", + "FORMS": { + "ERRORS": { + "FIRST_NAME_REQUIRED": "נדרש שם פרטי", + "LAST_NAME_REQUIRED": "נדרש שם משפחה", + "PHONE_REQUIRED": "טלפון נדרש", + "EMAIL_IS_REQUIRED": "נדרש דואל", + "MUST_CONTAIN_ONLY_LETTERS": "חייב להכיל רק אותיות", + "PHONE_MUST_CONTAINS_ONLY(special_signs)AND_DIGIT_CHARACTER": "מספר הטלפון יכול להתחיל עם '+' והוא חייב להכיל רק: ',,., (רווח), #' 'ותווים ספרותיים" + } + } + }, + "PROMOTIONS": { + "PROMOTIONS": "מבצעים", + "NO_PROMOTIONS": "אין מבצעים", + "BASIC_INFO": "מידע בסיסי", + "TITLE": "כותרת", + "ACTIVE_FROM": "פעיל מ-", + "ACTIVE_TO": "פעיל ל", + "PURCHASES_COUNT": "ספירת רכישות", + "IS_ACTIVE": "פעיל", + "SELECT_DATE": "בחר תאריך", + "DESCRIPTION": "תיאור", + "SAVE": "לשמור", + "NEXT": "הַבָּא", + "BACK": "חזור", + "LANGUAGE": "שפה" + }, + "SELECT LANGUAGE": { + "USA": "ארה'ב", + "ISRAEL": "ישראל", + "BULGARIA": "בולגריה" + } + }, + "SETTINGS_VIEW": { + "SETTINGS": "הגדרות", + "OPTIONS": "אפשרויות", + "COMMON": "מְשׁוּתָף", + "LOCATION": "מקום", + "ACCOUNT": "חֶשְׁבּוֹן", + "NEW_USERNAME": "שם משתמש חדש", + "NAME_MUST_BE_AT_LEAST_4_CHARACTERS_LONG": "על השם להיות באורך של 4 תווים לפחות", + "OLD_PASSWORD": "סיסמה ישנה", + "NEW_PASSWORD": "סיסמה חדשה", + "REPEAT_NEW_PASSWORD": "חזור על סיסמה חדשה", + "PASSWORDS_DO_NOT_MATCH": "סיסמאות לא תואמות", + "SAVE_CHANGES": "שמור שינויים", + "SAVE": "להציל", + "CHANGES": "שינויים", + "BASIC_INFO": "מידע בסיסי", + "NAME": "שם", + "NAME_IS_REQUIRED": "נדרש שם", + "LOGO": "לוגו", + "BROWSE": "עיון", + "CONTACT_INFO": "פרטים ליצירת קשר", + "E_MAIL": "דואר אלקטרוני", + "PHON_NUMBER": "מספר טלפון", + "COUNTRY": "מדינה", + "CITY": "עיר", + "CITY_IS_REQUIRED": "עיר נדרשת", + "POSTCODE_optional": "מיקוד (אופציונלי)", + "STREET": "רחוב", + "STREET_IS_REQUIRED": "רחוב נדרש", + "HOUSE": "בית", + "HOUSE_IS_REQUIRED": "הבית נדרש", + "APARTMENT_optional": "דירה (אופציונלית)", + "AUTO_DETECT_COORDINATES": "זיהוי אוטומטי של קואורדינטות", + "LATITUDE": "קו רוחב", + "LATITUDE_IS_REQUIRED": "Latitude נדרש", + "LONGITUDE": "קו האורך", + "LONGITUDE_IS_REQUIRED": "קו האורך נדרש", + "FIND_ADDRESS": "חפש כתובת", + "SELECT_CATEGORIES": "בחר קטגוריות", + "OK": "בסדר", + "CANCEL": "בטל", + "ALLOW_ONLINE_PAYMENTS": "אפשר תשלומים מקוונים", + "SETTINGS_SECTION": { + "DELIVERY_OR_TAKEAWAY_SETTINGS": "הגדרות משלוח / תפוסה", + "PRODUCTS_DELIVERY_BY_DEFAULT": "משלוח מוצרים (כברירת מחדל)", + "PRODUCTS_TAKEAWAY_BY_DEFAULT": "מוצרים Takeaway (כברירת מחדל)", + "PAYMENTS_SETTINGS": "הגדרות תשלומים", + "BARCODE_QR_CODE_SETTINGS": "ברקוד / קוד QR הגדרות", + "ORDER_BARCODE_TYPE": "הקלד סוג ברקוד", + "BARCODE_DATA": "נתוני ברקוד", + "SCAN_EXISTED": "סרוק קיים", + "DELIVERY_SETTINGS": "הגדרות משלוח", + "TAKEAWAY_SETTINGS": "הגדרות ברזל", + "IN_STORE_MODE": "במצב בחנות", + "IN_STORE_MODE_SETTINGS": "הגדרות מצב בחנות" + } + }, + "LOGIN_VIEW": { + "LOGIN": "התחברות" + }, + "LANGUAGE_VIEW": { + "TITLE": "בחירת שפה", + "OK": "בסדר", + "CANCEL": "בטל" + }, + "ABOUT_VIEW": { + "TITLE": "עלינו", + "APP_VERSION": "גרסת האפליקציה" + }, + "TERMS_OF_USE_VIEW": { + "TITLE": "תנאי שימוש" + }, + "WAREHOUSE_VIEW": { + "RELEVANT": "רלוונטי", + "ALL": "את כל", + "LOADING_MORE_DATA": "טוען נתונים נוספים ...", + "ORDERS": "הזמנות", + "SWITCH_ORDERS": "החלף הזמנות", + "NEW_ORDER": "הזמנה חדשה", + "PRODUCTS": "מוצרים מובילים", + "ARE_YOU_SURE_TO_DELETE": "לְבַטֵל", + "REMOVE": "לְהַסִיר", + "DELETE": "לִמְחוֹק", + "THE_FOLLOWING_DATA": "הנתונים הבאים", + "CONFIRM": "לְאַשֵׁר", + "CANCEL": "לְבַטֵל", + "SELECT_POP_UP": { + "SELECT_CATEGORIES": "בחר קטגוריות", + "OK": "בסדר", + "CANCEL": "בטל" + }, + "ORDER_WAREHOUSE_STATUSES": { + "ALL": "את כל", + "CANCELLED": "מבוטל", + "CREATED": "נוצר", + "CONFIRMED": "מְאוּשָׁר", + "PROCESSING": "מעבד", + "ALLOCATION_STARTED": "ההקצאה החלה", + "ALLOCATION_FINISHED": "ההקצאה הסתיימה", + "PACKAGING_STARTED": "האריזה התחילה", + "PACKAGED": "ארוז", + "GIVEN_TO_CARRIER": "נתון למוביל", + "TAKEN": "נלקח", + "ALLOCATION_FAILED": "ההקצאה נכשלה", + "PACKAGING_FAILED": "האריזות נכשלו", + "BAD_STATUS": "מצב גרוע", + "NO_ACTIVE_ORDERS": "אין הזמנות פעילות", + "NO_ORDERS": "אין הזמנות", + "NO_PRODUCTS": "אין מוצרים" + }, + "CREATE_PRODUCTS_POPUP": { + "ERROR_MSG": "משהו לא בסדר, לא מצליח לבצע סדר!", + "NEW_PRODUCT_TYPE": "סוג מוצר חדש", + "CREATE_NEW_PRODUCT_TYPE": "צור סוג מוצר חדש", + "TITLE": "כותרת", + "DESCRIPTION": "תיאור", + "CATEGORIES": "קטגוריות", + "PRICE": "מחיר", + "COUNT": "לספור", + "LANGUAGE": "שפה", + "COUNT_PLACEHOLDER": "0", + "DRAG&DROP_FILE_HERE": "גרור ושחרר קובץ כאן", + "DRAG&DROP_PICTURE_HERE": "גרור ושחרר תמונה כאן", + "OR_BROWSE": "או לגלוש", + "CLICK_TO_UPLOAD_PICTURE": "לחץ כדי להעלות תמונה", + "CLICK_TO_UPLOAD_MORE_PICTURE": "לחץ כדי להעלות תמונות נוספות", + "CANCEL": "לְבַטֵל", + "CREATE": "לִיצוֹר", + "ENGLISH": "אנגלית", + "HEBREW": "עִברִית", + "RUSSIAN": "רוּסִי", + "BULGARIAN": "בולגרית", + "OPTIONAL": "אופציונאלי", + "TAKEAWAY": "להסיר", + "DELIVERY": "משלוח", + "PRODUCT_AVAILABILITY": "זמינות מוצר" + }, + "NEW_ORDER_VIEW": { + "NEW_MANUAL_ORDER": "סדר ידני חדש", + "SELECT_EXISTING_CUSTOMER": "בחר לקוח קיים", + "ADD_NEW_CUSTOMER": "הוסף לקוח חדש", + "FULL_NAME": "שם מלא", + "EMAIL": "דוא'ל", + "PHONE": "מכשיר טלפון", + "ADDRESS": "כתובת", + "IMAGE": "תמונה", + "PRODUCT": "מוצר", + "PRICE": "מחיר", + "AVAILABLE": "זמין", + "COMMENT": "תגובה", + "AMOUNT": "כמות", + "MAKE_ORDER": "בצע הזמנה", + "BASIC_INFO": "מידע בסיסי", + "FIRST_NAME": "שם פרטי", + "LAST_NAME": "שם משפחה", + "EMAIL_IS_INVALID": "דואר אלקטרוני לא תקין", + "EMAIL_IS_ALREADY_IN_USE": "דוא'ל כבר בשימוש", + "NEXT": "הבא", + "ADD_CUSTOMER": "חזור", + "BACK": "חזור", + "FIND_ADDRESS": "חפש כתובת", + "COUNTRY": "מדינה", + "CITY": "עיר", + "COUNTRY_IS_INVALID": "הארץ אינה חוקית", + "CITY_IS_REQUIRED": "עיר נדרשת", + "ZIP": "רוכסן", + "STREET_ADDRESS": "כתובת רחוב", + "STREET_ADDRESS_IS_REQUIRED": "נדרשת כתובת רחוב", + "HOUSE_№": "№ הבית", + "APARTMENT": "דירה", + "SHOW_COORDINATES": "הצג קואורדינטות", + "LATITUDE": "קו רוחב", + "LONGITUTE": "קו האורך", + "HOUSE_IS_REQUIRED": "הבית נדרש" + }, + "EDIT_PICTURE": { + "PRODUCT_IMAGES": "תמונות של מוצרים", + "SAVE_IMAGES": "שמור תמונות" + }, + "MISC_TEXT": { + "CREATE_NEW_ORDER": "צור הזמנה חדשה", + "ADD_NEW_PRODUCT": "הוסף מוצר חדש" + } + }, + "EDIT_PRODUCT_POP_UP": { + "EDIT_PRODUCT_TYPE": "ערוך את סוג המוצר", + "UPDATE_AN_EXISTING_PRODUCT": "עדכן מוצר קיים", + "CLICK_TO_EDIT_PICTURES": "לחץ כדי לערוך תמונות", + "TITLE": "כותרת", + "DESCRIPTION": "תיאור", + "CATEGORIES": "קטגוריות", + "PRICE": "מחיר", + "COUNT": "לספור", + "LANGUAGE": "שפה", + "DRAG&DROP_FILE_HERE": "גרור ושחרר קובץ כאן", + "OR_BROWSE": "או לגלוש", + "CLICK_TO_UPLAOD_PICTURE": "לחץ כדי להעלות תמונה", + "CANCEL": "לְבַטֵל", + "UPDATE": "עדכון", + "ENGLISH": "אנגלית", + "HEBREW": "עִברִית", + "RUSSIAN": "רוּסִי", + "BULGARIAN": "בולגרית", + "SPANISH": "ספרדית" + }, + "ORDER_CANCELED_COMPONENT": { + "CANCELED": "מבוטל", + "ORDER_CANCELED": "ההזמנה בוטלה", + "DELIVERED": "נמסר", + "CREATED_AT": "נוצר ב" + }, + "ORDER_CONTROL_BUTTONS_COMPONENT": { + "CONFIRM": "לְאַשֵׁר", + "START_PROCESSING": "התחל עיבוד", + "START_ALLOCATION": "התחל הקצאה", + "ALLOCATED": "מוּקצֶה", + "START_PACKAGING": "התחל אריזה", + "PACKAGED": "ארוז", + "FAILED": "נִכשָׁל", + "GIVEN_TO_CARRIER": "ניתן ל- מוֹבִיל", + "GIVEN_TO_CUSTOMER": "ניתן ללקוח" + }, + "ORDER_CONTROL_BUTTONS": { + "CONFIRM": "לְאַשֵׁר", + "START_PROCESSING": "התחל עיבוד", + "START_ALLOCATION": "התחל הקצאה", + "ALLOCATED": "מוּקצֶה", + "START_PACKAGING": "התחל אריזה", + "PACKAGED": "ארוז", + "FAILED": "נִכשָׁל", + "GIVEN_TO_CARRIER": "ניתן ל- מוֹבִיל" + }, + "ORDER_DELIVERED_COMPONENT": { + "DELIVERED": "נמסר", + "CREATED_AT": "נוצר ב", + "DELIVERED_AT": "נמסר ב", + "DELIVERED_TO": "הועבר ל" + }, + "ORDER_DELIVERY_PROBLEM_COMPONENT": { + "DELIVERY_ISSUES": "הנפקות משלוח", + "ISSUES_DURING_DELIVERY_TO": "נושאים במהלך משלוח ל", + "BY": "על ידי", + "CREATED_AT": "נוצר ב" + }, + "ORDER_WAREHOUSE_PREPARATION_PROBLEM_COMPONENT": { + "CREATED_AT": "נוצר ב" + }, + "ORDER_WITH_CARRIER_COMPONENT": { + "IN_DELIVERY_TO": "במסירה אל", + "BY": "על ידי", + "CREATED_AT": "נוצר ב" + }, + "ORDER_WITHOUT_CARRIER_COMPONENT": { + "CREATED_AT": "נוצר ב", + "CANT_PROCESSING_WITHOUT_PRODUCTS": "לא ניתן לעבד את ההזמנה ללא מוצרים." + }, + "ORDER_TITLE_COMPONENT": { + "ORDER": "להזמין", + "FROM": "מ" + }, + "ORDER_TYPE": { + "TYPE": ":סוג הזמנה", + "DELIVERY": "מְסִירָה", + "TAKEAWAY": "להסיר" + }, + "TRACK_ORDER_POP_UP": { + "CARRIER": "המוביל", + "TRACK": "עקוב אחר", + "CUSTOMER": "צרכן" + }, + "CONNECTION_ERROR_VIEW": { + "TITLE": "אין חיבור ל-Ever", + "DESCRIPTION": [ + "בבקשה תוודא שאתה", + "מחובר לאינטרנט או תנסה", + "את האפליקציה מאוחר יותר." + ] + }, + "CUSTOMER_ORDERS_POP_UP": { + "ORDER_ID": "מספר הזמנה", + "DELIVERY": "מְסִירָה", + "ADDRESS": "מְסִירָה", + "STATUS": "סטָטוּס" + }, + "NO_SERVER_VIEW": { + "NO_SERVER": "השרת נפל" + }, + "PROMOTIONS": { + "PROMO_PRICE": "מחיר פרומו", + "NO_PROMOTIONS": "אין מבצעים" + } +} diff --git a/packages/merchant-tablet-ionic/src/assets/i18n/ru-RU.json b/packages/merchant-tablet-ionic/src/assets/i18n/ru-RU.json new file mode 100644 index 0000000..8f71ebf --- /dev/null +++ b/packages/merchant-tablet-ionic/src/assets/i18n/ru-RU.json @@ -0,0 +1,487 @@ +{ + "LANGUAGE": { + "ID": "ru-RU", + "NAME": "Русский" + }, + "DASHBOARD": "Панель", + "CURRENT_DIRECTION": "ltr", + "SIDEBAR_SIDE": "left", + "SIDE_MENU": { + "TITLE": "Ever Продавцы", + "GROUPS": { + "NO_TITLE": { + "DIVER_TITLE": "", + "ITEMS": { + "WAREHOUSE": "Панель приборов" + } + }, + "SETTINGS": { + "SETTINGS": "Настройки", + "DIVER_TITLE": "Настройки", + "ITEMS": { + "LANGUAGE": "Язык", + "SIGN_OUT": "Выход" + } + }, + "INFO": { + "DIVER_TITLE": "Информация", + "ITEMS": { + "ABOUT_US": "О нас", + "TERMS_OF_USE": "Условия эксплуатации" + } + } + } + }, + "CUSTOMERS_VIEW": { + "CUSTOMERS": "Клиенты", + "ADD_CUSTOMER": "Добавить клиента", + "IMAGE": "Образ", + "NAME": "Имя", + "PHONE_NUMBER": "Номер телефона", + "ADDRESSES": "Адреса", + "ORDERS": "Заказы", + "TOTAL": "Всего", + "E_MAIL": "Эл. почта", + "ADDRESS_POP_UP": { + "CUSTOMER_ADDRESS": "Адрес клиента", + "COUNTRY": "Страна", + "STREET": "Улица", + "HOUSE": "Жилой дом", + "APARTMENT": "Квартира", + "COORDINATES": "Координаты" + }, + "EMAIL_POP_UP": { + "CUSTOMER_EMAIL": "Электронная почта клиента", + "SEND_E_MAIL": "Отправить E-mail" + }, + "ORDERS_POP_UP": { + "CUSTOMER_ORDERS": "Заказы клиентов", + "ALL_ORDERS": "Все заказы", + "TOTAL_ORDERS_SUM": "Общая сумма заказов", + "COMPLATED": "Завършен", + "PAID": "Оплаченный" + } + }, + "FILE_UPLOADER": { + "DRAG_AND_DROP_FILE_HERE": "Фото Drag & Drop здесь", + "PICTURE_URL": "Фото гиперссылка", + "BROWSE": "ПРОСМАТРИВАТЬ" + }, + "CARRIERS_VIEW": { + "CARRIERS_CATALOG": { + "IMAGE": "Образ", + "NAME": "Имя", + "PHONE": "Телефон", + "ADDRESS": "Адрес", + "LOGO": "Логотип" + }, + "TRACK_ORDER": "Отследить заказ", + "TRACK_CARRIER": "Отследить перевозчика", + "DELIVERY_TRACKING": "Отслеживание доставки", + "CARRIER": "Перевозчик", + "STORE": "Магазин", + "DELIVERY_COUNT": "Количество доставки:", + "CUSTOMER": "Клиент", + "CARRIERS": "Носители", + "ADD_CARRIERS": "Добавить носители", + "IMAGE": "Образ", + "NAME": "Имя", + "PHONE_NUMBER": "Номер телефона", + "ADDRESSES": "Адрес", + "STATUS": "Положение дел", + "DELIVERIES": "Поставки", + "NO_CARRIERS": "Нет носителей", + "WORKING": "За работой", + "NOT_WORKING": "Не работает", + "BLOCKED": "Блокированный", + "DELIVERIES_POP_UP": { + "CARRIER_DELIVERIES": "Поставки перевозчика", + "ALL_DELIVERIES": "Все поставки", + "DELIVERIES_TODAY": "Поставки сегодня", + "TOTAL_DISTANCE_TODAY": "Общая дистанция сегодня", + "CUSTOMER": "Клиент", + "WAREHOUSE": "Склад", + "STATUS": "Положение дел", + "DELIVERY": "Доставка", + "NO_DELIVERIES": "Нет поставок", + "COMPLETED": "Завершенный", + "PAID": "Оплаченный" + }, + "ADDRESS": { + "CARRIER_ADDRESS": "Адрес перевозчика", + "COUNTRY": "Страна", + "HOUSE": "Дом", + "COORDINATES": "Координаты" + }, + "EDIT_CARRIER": { + "EDIT_CARRIER": "Редактировать носитель" + }, + "ADD_CARRIER": { + "ADD_CARRIER": "Добавить перевозчика", + "HOW_TO_ADD": "Как добавить", + "SELECT_HOW_TO_ADD": "Выберите, как добавить", + "ADD": "Добавить", + "CARRIERS_CATALOG": "Каталог носителей", + "SELECT_FROM_CARRIERS_CATALOG": "Выберите из каталога носителей", + "ADD_NEW_CARRIER": "Добавить нового оператора", + "FIRST_NAME": "Имя", + "LAST_NAME": "Фамилия", + "EMAIL": "Эл. адрес", + "USERNAME": "Имя пользователя", + "PASSWORD": "Пароль", + "NAME": "Имя", + "PHONE": "Телефон", + "ADDRESS": "Адрес", + "LOGO": "Логотип", + "IS_ACTIVE": "Активен", + "CITY": "Город", + "STREET": "Улица", + "HOUSE": "Дом", + "LATITUDE": "Географическая широта", + "LONGITUDE": "Географическая длина", + "COUNTRY": "Страна", + "OK": "Хорошо", + "CANCEL": "ОТМЕНИТЬ", + "PREVIOUS": "ПРЕДЫДУЩИЙ", + "BACK": "НАЗАД", + "NEXT": "СЛЕДУЮЩИЙ", + "DONE": "СДЕЛАННЫЙ", + "LOCATION": "МЕСТО НАХОЖДЕНИЯ", + "ZIP": "ZIP", + "PROFILE_DETAILS": "ПРОФИЛЬНЫЕ ДЕТАЛИ", + "BROWSE": "ПРОСМАТРИВАТЬ", + "DRAG_AND_DROP_FILE_HERE": "Фото Drag & Drop здесь", + "PICTURE_URL": "Фото гиперссылка", + "ACCOUNT": "УЧЕТНАЯ ЗАПИСЬ", + "REPEAT": "Повторение", + "REPEAT_PASSWORD": "Повторите пароль", + "PASSWORD_DO_NOT_MATCH": "Пароль не подходит", + "FIND_ADDRESS": "Найти адрес", + "FORMS": { + "ERRORS": { + "FIRST_NAME_REQUIRED": "Имя требуется", + "LAST_NAME_REQUIRED": "Фамилия обязательна", + "PHONE_REQUIRED": "Требуется телефон", + "EMAIL_IS_REQUIRED": "Требуется электронная почта", + "MUST_CONTAIN_ONLY_LETTERS": "Должен содержать только буквы", + "PHONE_MUST_CONTAINS_ONLY(special_signs)AND_DIGIT_CHARACTER": "Номер телефона может начинаться с '+' и должен содержать только: '-,., (Пробел), #' и цифры символов" + } + } + }, + "PROMOTIONS": { + "PROMOTIONS": "Акции", + "NO_PROMOTIONS": "Нет рекламных акций", + "BASIC_INFO": "Базовая информация", + "TITLE": "Заглавие", + "ACTIVE_FROM": "Активный от", + "ACTIVE_TO": "Активно для", + "PURCHASES_COUNT": "Количество покупок", + "IS_ACTIVE": "Активен", + "SELECT_DATE": "Выберите дату", + "DESCRIPTION": "Описание", + "SAVE": "Сохранить", + "NEXT": "Следующий", + "BACK": "Назад", + "LANGUAGE": "Язык" + }, + "SELECT LANGUAGE": { + "USA": "США", + "ISRAEL": "Израиль", + "BULGARIA": "Болгария" + } + }, + "SETTINGS_VIEW": { + "SETTINGS": "Настройки", + "OPTIONS": "Oпции", + "COMMON": "Общий", + "LOCATION": "Mесто нахождения", + "ACCOUNT": "Счет", + "NEW_USERNAME": "Новое имя пользователя", + "NAME_MUST_BE_AT_LEAST_4_CHARACTERS_LONG": "Имя должно содержать не менее 4 символов", + "OLD_PASSWORD": "Прежний пароль", + "NEW_PASSWORD": "новый пароль", + "REPEAT_NEW_PASSWORD": "Повторите новый пароль", + "PASSWORDS_DO_NOT_MATCH": "Пароли не совпадают", + "SAVE_CHANGES": "Сохранить изменения", + "SAVE": "Сохранить", + "CHANGES": "Изменения", + "BASIC_INFO": "Базовая информация", + "NAME": "название", + "NAME_IS_REQUIRED": "Требуется имя", + "LOGO": "логотип", + "BROWSE": "Просматривать", + "CONTACT_INFO": "Контактная информация", + "E_MAIL": "Эл. почта", + "PHON_NUMBER": "Номер телефона", + "COUNTRY": "Страна", + "CITY": "город", + "CITY_IS_REQUIRED": "Город требуется", + "POSTCODE_optional": "Почтовый индекс (необязательно)", + "STREET": "улица", + "STREET_IS_REQUIRED": "Улица необходима", + "HOUSE": "жилой дом", + "HOUSE_IS_REQUIRED": "Требуется дом", + "APARTMENT_optional": "Апартаменты (по желанию)", + "AUTO_DETECT_COORDINATES": "Автоматическое определение координат", + "LATITUDE": "широта", + "LATITUDE_IS_REQUIRED": "Широта требуется", + "LONGITUDE": "Долгота", + "LONGITUDE_IS_REQUIRED": "Долгота требуется", + "FIND_ADDRESS": "Найти адрес", + "SELECT_CATEGORIES": "Выбрать категории", + "OK": "Хорошо", + "CANCEL": "ОТМЕНИТЬ", + "ALLOW_ONLINE_PAYMENTS": "Разрешить онлайн-платежи", + "SETTINGS_SECTION": { + "DELIVERY_OR_TAKEAWAY_SETTINGS": "Настройки доставки / выноса", + "PRODUCTS_DELIVERY_BY_DEFAULT": "Доставка продуктов (по умолчанию)", + "PRODUCTS_TAKEAWAY_BY_DEFAULT": "Продукты на вынос (по умолчанию)", + "PAYMENTS_SETTINGS": "Настройки платежей", + "BARCODE_QR_CODE_SETTINGS": "Настройки штрих-кода / QR-кода", + "ORDER_BARCODE_TYPE": "Тип заказа штрих-кода", + "BARCODE_DATA": "Данные штрих-кода", + "SCAN_EXISTED": "Сканирование существует", + "DELIVERY_SETTINGS": "Настройки доставки", + "TAKEAWAY_SETTINGS": "Настройки на вынос", + "IN_STORE_MODE": "Режим магазина", + "IN_STORE_MODE_SETTINGS": "Настройки режима в магазине" + } + }, + "LOGIN_VIEW": { + "LOGIN": "Авторизоваться" + }, + "LANGUAGE_VIEW": { + "TITLE": "Выбор языка", + "OK": "Хорошо", + "CANCEL": "ОТМЕНИТЬ" + }, + "ABOUT_VIEW": { + "TITLE": "О Нас", + "APP_VERSION": "Версия приложения" + }, + "TERMS_OF_USE_VIEW": { + "TITLE": "Правила Использования" + }, + "WAREHOUSE_VIEW": { + "RELEVANT": "Соответствующий", + "ALL": "Все", + "LOADING_MORE_DATA": "Загрузка дополнительных данных ...", + "ORDERS": "Заказы", + "SWITCH_ORDERS": "Переключение заказов", + "NEW_ORDER": "Новый порядок", + "PRODUCTS": "Лучшие продукты", + "ARE_YOU_SURE_TO_DELETE": "Вы уверены, что хотите этого", + "REMOVE": "Удалять", + "DELETE": "Удалить", + "THE_FOLLOWING_DATA": "следующие данные", + "CONFIRM": "Подтверждение", + "CANCEL": "Отмена", + "SELECT_POP_UP": { + "SELECT_CATEGORIES": "Выбрать категории", + "OK": "Хорошо", + "CANCEL": "ОТМЕНИТЬ" + }, + "ORDER_WAREHOUSE_STATUSES": { + "ALL": "Все", + "CANCELLED": "Отменен", + "CREATED": "Созданный", + "CONFIRMED": "Подтвердил", + "PROCESSING": "Обработка", + "ALLOCATION_STARTED": "Началось распределение", + "ALLOCATION_FINISHED": "Выделение завершено", + "PACKAGING_STARTED": "Начало упаковки", + "PACKAGED": "В упаковке", + "GIVEN_TO_CARRIER": "Предоставлено перевозчику", + "TAKEN": "Взятый", + "ALLOCATION_FAILED": "Не удалось выполнить распределение", + "PACKAGING_FAILED": "Ошибка упаковки", + "BAD_STATUS": "Плохой статус", + "NO_ACTIVE_ORDERS": "Нет активных заказов", + "NO_ORDERS": "Нет заказов", + "NO_PRODUCTS": "Нет продуктов" + }, + "CREATE_PRODUCTS_POPUP": { + "ERROR_MSG": "Что-то не так, не в состоянии разместить заказ!", + "NEW_PRODUCT_TYPE": "Новый тип продукта", + "CREATE_NEW_PRODUCT_TYPE": "Создать новый тип продукта", + "TITLE": "Заглавие", + "DESCRIPTION": "Описание", + "CATEGORIES": "Категории", + "PRICE": "Цена", + "COUNT": "Подсчитывать", + "LANGUAGE": "Язык", + "DRAG&DROP_FILE_HERE": "Файл Drag & Drop здесь", + "DRAG&DROP_PICTURE_HERE": "Изображение Drag & Drop здесь", + "OR_BROWSE": "or browse", + "COUNT_PLACEHOLDER": "0", + "CLICK_TO_UPLOAD_PICTURE": "Нажмите, чтобы загрузить изображение", + "CLICK_TO_UPLOAD_MORE_PICTURE": "Нажмите, чтобы загрузить больше фотографий", + "CANCEL": "Отмена", + "CREATE": "Создайте", + "ENGLISH": "Английский", + "HEBREW": "Иврит", + "RUSSIAN": "Русский", + "BULGARIAN": "Болгарский", + "SPANISH": "Испанский", + "FRENCH": "Французский", + "OPTIONAL": "необязательный", + "TAKEAWAY": "Навынос", + "DELIVERY": "Доставка", + "PRODUCT_AVAILABILITY": "Доступность продукта" + }, + "NEW_ORDER_VIEW": { + "NEW_MANUAL_ORDER": "Новый ручной порядок", + "SELECT_EXISTING_CUSTOMER": "Выбрать существующего клиента", + "ADD_NEW_CUSTOMER": "Добавить нового клиента", + "FULL_NAME": "ФИО", + "EMAIL": "Эл. адрес", + "PHONE": "Телефон", + "ADDRESS": "Адрес", + "IMAGE": "Образ", + "PRODUCT": "Товар", + "PRICE": "Цена", + "AVAILABLE": "Имеется в наличии", + "COMMENT": "Комментарий", + "AMOUNT": "Количество", + "MAKE_ORDER": "Сделать заказ", + "BASIC_INFO": "Базовая информация", + "FIRST_NAME": "Имя", + "LAST_NAME": "Фамилия", + "EMAIL_IS_INVALID": "Недействительный адрес электронной почты", + "EMAIL_IS_ALREADY_IN_USE": "Email уже используется", + "NEXT": "следующий", + "ADD_CUSTOMER": "Добавить клиента", + "BACK": "назад", + "FIND_ADDRESS": "Найти адрес", + "COUNTRY": "Страна", + "CITY": "город", + "COUNTRY_IS_INVALID": "Страна недействительна.", + "CITY_IS_REQUIRED": "Город требуется", + "ZIP": "ZIP", + "STREET_ADDRESS": "Адрес улицы", + "STREET_ADDRESS_IS_REQUIRED": "Требуется адрес улицы", + "HOUSE_№": "Дом №", + "APARTMENT": "квартира", + "SHOW_COORDINATES": "Показать координаты", + "LATITUDE": "широта", + "LONGITUTE": "Долгота", + "HOUSE_IS_REQUIRED": "Требуется дом" + }, + "EDIT_PICTURE": { + "PRODUCT_IMAGES": "Изображения продуктов", + "SAVE_IMAGES": "Сохранить изображения" + }, + "MISC_TEXT": { + "CREATE_NEW_ORDER": "Создать новый заказ", + "ADD_NEW_PRODUCT": "Добавить новый продукт" + } + }, + "EDIT_PRODUCT_POP_UP": { + "EDIT_PRODUCT_TYPE": "Изменить тип продукта", + "UPDATE_AN_EXISTING_PRODUCT": "Обновление существующего продукта", + "CLICK_TO_EDIT_PICTURES": "Нажмите, чтобы редактировать фотографии", + "TITLE": "Заглавие", + "DESCRIPTION": "Описание", + "CATEGORIES": "Категории", + "PRICE": "Цена", + "COUNT": "Подсчитывать", + "LANGUAGE": "Язык", + "DRAG&DROP_FILE_HERE": "Файл Drag & Drop здесь", + "OR_BROWSE": "or browse", + "CLICK_TO_UPLAOD_PICTURE": "Нажмите, чтобы загрузить изображение", + "CANCEL": "Отмена", + "UPDATE": "Обновить", + "ENGLISH": "Английский", + "HEBREW": "Иврит", + "RUSSIAN": "Русский", + "BULGARIAN": "Болгарский", + "SPANISH": "Испанский", + "FRENCH": "Французский" + }, + "ORDER_CANCELED_COMPONENT": { + "CANCELED": "отменен", + "ORDER_CANCELED": "Поръчката е анулирана", + "DELIVERED": "Доставени", + "CREATED_AT": "Създаден в" + }, + "ORDER_CONTROL_BUTTONS_COMPONENT": { + "CONFIRM": "подтвердить", + "START_PROCESSING": "Начать обработку", + "START_ALLOCATION": "Начать распределение", + "ALLOCATED": "Выделено", + "START_PACKAGING": "Начало упаковки", + "PACKAGED": "В упаковке", + "FAILED": "Не смогли", + "GIVEN_TO_CARRIER": "Предоставлено перевозчику", + "GIVEN_TO_CUSTOMER": "Передано клиенту" + }, + "ORDER_CONTROL_BUTTONS": { + "CONFIRM": "подтвердить", + "START_PROCESSING": "Начать обработку", + "START_ALLOCATION": "Начать распределение", + "ALLOCATED": "Выделено", + "START_PACKAGING": "Начало упаковки", + "PACKAGED": "В упаковке", + "FAILED": "Не смогли", + "GIVEN_TO_CARRIER": "Предоставлено перевозчику" + }, + "ORDER_DELIVERED_COMPONENT": { + "DELIVERED": "Доставлен", + "CREATED_AT": "Создан в", + "DELIVERED_AT": "Поставляется в", + "DELIVERED_TO": "Доставлен в" + }, + "ORDER_DELIVERY_PROBLEM_COMPONENT": { + "DELIVERY_ISSUES": "Проблемы с доставкой", + "ISSUES_DURING_DELIVERY_TO": "Проблемы при доставке", + "BY": "От", + "CREATED_AT": "Создан в" + }, + "ORDER_WAREHOUSE_PREPARATION_PROBLEM_COMPONENT": { + "CREATED_AT": "Создан в" + }, + "ORDER_WITH_CARRIER_COMPONENT": { + "IN_DELIVERY_TO": "В Доставка до", + "BY": "От", + "CREATED_AT": "Создан в" + }, + "ORDER_WITHOUT_CARRIER_COMPONENT": { + "CREATED_AT": "Создан в", + "CANT_PROCESSING_WITHOUT_PRODUCTS": "Невозможно обработать заказ без продуктов." + }, + "ORDER_TITLE_COMPONENT": { + "ORDER": "порядок", + "FROM": "от" + }, + "ORDER_TYPE": { + "TYPE": "Тип заказа:", + "DELIVERY": "Доставка", + "TAKEAWAY": "Навынос" + }, + "TRACK_ORDER_POP_UP": { + "CARRIER": "Перевозчик", + "TRACK": "трек", + "CUSTOMER": "Покупатель" + }, + "CONNECTION_ERROR_VIEW": { + "TITLE": "Не удалось подключиться к Ever", + "DESCRIPTION": [ + "Проверьте подключение к", + "Интернету и повторите попытку.", + "" + ] + }, + "CUSTOMER_ORDERS_POP_UP": { + "ORDER_ID": "Номер заказа", + "DELIVERY": "Доставка", + "ADDRESS": "Адрес", + "STATUS": "Положение дел" + }, + "NO_SERVER_VIEW": { + "NO_SERVER": "Сервер упал" + }, + "PROMOTIONS": { + "PROMO_PRICE": "Промо цена", + "NO_PROMOTIONS": "Нет Акции" + } +} diff --git a/packages/merchant-tablet-ionic/src/assets/i18n/ru.json b/packages/merchant-tablet-ionic/src/assets/i18n/ru.json new file mode 100644 index 0000000..76cb41e --- /dev/null +++ b/packages/merchant-tablet-ionic/src/assets/i18n/ru.json @@ -0,0 +1,484 @@ +{ + "LANGUAGE": { + "ID": "ru-RU", + "NAME": "Русский" + }, + "DASHBOARD": "Панель", + "CURRENT_DIRECTION": "ltr", + "SIDEBAR_SIDE": "left", + "SIDE_MENU": { + "TITLE": "Ever Продавцы", + "GROUPS": { + "NO_TITLE": { + "DIVER_TITLE": "", + "ITEMS": { + "WAREHOUSE": "Панель приборов" + } + }, + "SETTINGS": { + "SETTINGS": "Настройки", + "DIVER_TITLE": "Настройки", + "ITEMS": { + "LANGUAGE": "Язык", + "SIGN_OUT": "Выход" + } + }, + "INFO": { + "DIVER_TITLE": "Информация", + "ITEMS": { + "ABOUT_US": "О нас", + "TERMS_OF_USE": "Условия эксплуатации" + } + } + } + }, + "CUSTOMERS_VIEW": { + "CUSTOMERS": "Клиенты", + "ADD_CUSTOMER": "Добавить клиента", + "IMAGE": "Образ", + "NAME": "Имя", + "PHONE_NUMBER": "Номер телефона", + "ADDRESSES": "Адреса", + "ORDERS": "Заказы", + "TOTAL": "Всего", + "E_MAIL": "Эл. почта", + "ADDRESS_POP_UP": { + "CUSTOMER_ADDRESS": "Адрес клиента", + "COUNTRY": "Страна", + "STREET": "Улица", + "HOUSE": "Жилой дом", + "APARTMENT": "Квартира", + "COORDINATES": "Координаты" + }, + "EMAIL_POP_UP": { + "CUSTOMER_EMAIL": "Электронная почта клиента", + "SEND_E_MAIL": "Отправить E-mail" + }, + "ORDERS_POP_UP": { + "CUSTOMER_ORDERS": "Заказы клиентов", + "ALL_ORDERS": "Все заказы", + "TOTAL_ORDERS_SUM": "Общая сумма заказов", + "COMPLATED": "Завършен", + "PAID": "Оплаченный" + } + }, + "FILE_UPLOADER": { + "DRAG_AND_DROP_FILE_HERE": "Фото Drag & Drop здесь", + "PICTURE_URL": "Фото гиперссылка", + "BROWSE": "ПРОСМАТРИВАТЬ" + }, + "CARRIERS_VIEW": { + "CARRIERS_CATALOG": { + "IMAGE": "Образ", + "NAME": "Имя", + "PHONE": "Телефон", + "ADDRESS": "Адрес", + "LOGO": "Логотип" + }, + "TRACK_ORDER": "Отследить заказ", + "TRACK_CARRIER": "Отследить перевозчика", + "DELIVERY_TRACKING": "Отслеживание доставки", + "CARRIER": "Перевозчик", + "STORE": "Магазин", + "DELIVERY_COUNT": "Количество доставки:", + "CUSTOMER": "Клиент", + "CARRIERS": "Носители", + "ADD_CARRIERS": "Добавить носители", + "IMAGE": "Образ", + "NAME": "Имя", + "PHONE_NUMBER": "Номер телефона", + "ADDRESSES": "Адрес", + "STATUS": "Положение дел", + "DELIVERIES": "Поставки", + "NO_CARRIERS": "Нет носителей", + "WORKING": "За работой", + "NOT_WORKING": "Не работает", + "BLOCKED": "Блокированный", + "DELIVERIES_POP_UP": { + "CARRIER_DELIVERIES": "Поставки перевозчика", + "ALL_DELIVERIES": "Все поставки", + "DELIVERIES_TODAY": "Поставки сегодня", + "TOTAL_DISTANCE_TODAY": "Общая дистанция сегодня", + "CUSTOMER": "Клиент", + "WAREHOUSE": "Склад", + "STATUS": "Положение дел", + "DELIVERY": "Доставка", + "NO_DELIVERIES": "Нет поставок", + "COMPLETED": "Завершенный", + "PAID": "Оплаченный" + }, + "ADDRESS": { + "CARRIER_ADDRESS": "Адрес перевозчика", + "COUNTRY": "Страна", + "HOUSE": "Дом", + "COORDINATES": "Координаты" + }, + "EDIT_CARRIER": { + "EDIT_CARRIER": "Редактировать носитель" + }, + "ADD_CARRIER": { + "ADD_CARRIER": "Добавить перевозчика", + "HOW_TO_ADD": "Как добавить", + "SELECT_HOW_TO_ADD": "Выберите, как добавить", + "ADD": "Добавить", + "CARRIERS_CATALOG": "Каталог носителей", + "SELECT_FROM_CARRIERS_CATALOG": "Выберите из каталога носителей", + "ADD_NEW_CARRIER": "Добавить нового оператора", + "FIRST_NAME": "Имя", + "LAST_NAME": "Фамилия", + "EMAIL": "Эл. адрес", + "USERNAME": "Имя пользователя", + "PASSWORD": "Пароль", + "NAME": "Имя", + "PHONE": "Телефон", + "ADDRESS": "Адрес", + "LOGO": "Логотип", + "IS_ACTIVE": "Активен", + "CITY": "Город", + "STREET": "Улица", + "HOUSE": "Дом", + "LATITUDE": "Географическая широта", + "LONGITUDE": "Географическая длина", + "COUNTRY": "Страна", + "OK": "Хорошо", + "CANCEL": "ОТМЕНИТЬ", + "PREVIOUS": "ПРЕДЫДУЩИЙ", + "BACK": "НАЗАД", + "NEXT": "СЛЕДУЮЩИЙ", + "DONE": "СДЕЛАННЫЙ", + "LOCATION": "МЕСТО НАХОЖДЕНИЯ", + "ZIP": "ZIP", + "PROFILE_DETAILS": "ПРОФИЛЬНЫЕ ДЕТАЛИ", + "BROWSE": "ПРОСМАТРИВАТЬ", + "DRAG_AND_DROP_FILE_HERE": "Фото Drag & Drop здесь", + "PICTURE_URL": "Фото гиперссылка", + "ACCOUNT": "УЧЕТНАЯ ЗАПИСЬ", + "REPEAT": "Повторение", + "REPEAT_PASSWORD": "Повторите пароль", + "PASSWORD_DO_NOT_MATCH": "Пароль не подходит", + "FIND_ADDRESS": "Найти адрес", + "FORMS": { + "ERRORS": { + "FIRST_NAME_REQUIRED": "Имя требуется", + "LAST_NAME_REQUIRED": "Фамилия обязательна", + "PHONE_REQUIRED": "Требуется телефон", + "EMAIL_IS_REQUIRED": "Требуется электронная почта", + "MUST_CONTAIN_ONLY_LETTERS": "Должен содержать только буквы", + "PHONE_MUST_CONTAINS_ONLY(special_signs)AND_DIGIT_CHARACTER": "Номер телефона может начинаться с '+' и должен содержать только: '-,., (Пробел), #' и цифры символов" + } + } + }, + "PROMOTIONS": { + "PROMOTIONS": "Акции", + "NO_PROMOTIONS": "Нет рекламных акций", + "BASIC_INFO": "Базовая информация", + "TITLE": "Заглавие", + "ACTIVE_FROM": "Активный от", + "ACTIVE_TO": "Активно для", + "PURCHASES_COUNT": "Количество покупок", + "IS_ACTIVE": "Активен", + "SELECT_DATE": "Выберите дату", + "DESCRIPTION": "Описание", + "SAVE": "Сохранить", + "NEXT": "Следующий", + "BACK": "Назад", + "LANGUAGE": "Язык" + }, + "SELECT LANGUAGE": { + "USA": "США", + "ISRAEL": "Израиль", + "BULGARIA": "Болгария" + } + }, + "SETTINGS_VIEW": { + "SETTINGS": "Настройки", + "OPTIONS": "Oпции", + "COMMON": "Общий", + "LOCATION": "Mесто нахождения", + "ACCOUNT": "Счет", + "NEW_USERNAME": "Новое имя пользователя", + "NAME_MUST_BE_AT_LEAST_4_CHARACTERS_LONG": "Имя должно содержать не менее 4 символов", + "OLD_PASSWORD": "Прежний пароль", + "NEW_PASSWORD": "новый пароль", + "REPEAT_NEW_PASSWORD": "Повторите новый пароль", + "PASSWORDS_DO_NOT_MATCH": "Пароли не совпадают", + "SAVE_CHANGES": "Сохранить изменения", + "SAVE": "Сохранить", + "CHANGES": "Изменения", + "BASIC_INFO": "Базовая информация", + "NAME": "название", + "NAME_IS_REQUIRED": "Требуется имя", + "LOGO": "логотип", + "BROWSE": "Просматривать", + "CONTACT_INFO": "Контактная информация", + "E_MAIL": "Эл. почта", + "PHON_NUMBER": "Номер телефона", + "COUNTRY": "Страна", + "CITY": "город", + "CITY_IS_REQUIRED": "Город требуется", + "POSTCODE_optional": "Почтовый индекс (необязательно)", + "STREET": "улица", + "STREET_IS_REQUIRED": "Улица необходима", + "HOUSE": "жилой дом", + "HOUSE_IS_REQUIRED": "Требуется дом", + "APARTMENT_optional": "Апартаменты (по желанию)", + "AUTO_DETECT_COORDINATES": "Автоматическое определение координат", + "LATITUDE": "широта", + "LATITUDE_IS_REQUIRED": "Широта требуется", + "LONGITUDE": "Долгота", + "LONGITUDE_IS_REQUIRED": "Долгота требуется", + "FIND_ADDRESS": "Найти адрес", + "SELECT_CATEGORIES": "Выбрать категории", + "OK": "Хорошо", + "CANCEL": "ОТМЕНИТЬ", + "ALLOW_ONLINE_PAYMENTS": "Разрешить онлайн-платежи", + "SETTINGS_SECTION": { + "DELIVERY_OR_TAKEAWAY_SETTINGS": "Настройки доставки / выноса", + "PRODUCTS_DELIVERY_BY_DEFAULT": "Доставка продуктов (по умолчанию)", + "PRODUCTS_TAKEAWAY_BY_DEFAULT": "Продукты на вынос (по умолчанию)", + "PAYMENTS_SETTINGS": "Настройки платежей", + "BARCODE_QR_CODE_SETTINGS": "Настройки штрих-кода / QR-кода", + "ORDER_BARCODE_TYPE": "Тип заказа штрих-кода", + "BARCODE_DATA": "Данные штрих-кода", + "SCAN_EXISTED": "Сканирование существует", + "DELIVERY_SETTINGS": "Настройки доставки", + "TAKEAWAY_SETTINGS": "Настройки на вынос", + "IN_STORE_MODE": "Режим магазина", + "IN_STORE_MODE_SETTINGS": "Настройки режима в магазине" + } + }, + "LOGIN_VIEW": { + "LOGIN": "Авторизоваться" + }, + "LANGUAGE_VIEW": { + "TITLE": "Выбор языка", + "OK": "Хорошо", + "CANCEL": "ОТМЕНИТЬ" + }, + "ABOUT_VIEW": { + "TITLE": "О Нас", + "APP_VERSION": "Версия приложения" + }, + "TERMS_OF_USE_VIEW": { + "TITLE": "Правила Использования" + }, + "WAREHOUSE_VIEW": { + "RELEVANT": "Соответствующий", + "ALL": "Все", + "LOADING_MORE_DATA": "Загрузка дополнительных данных ...", + "ORDERS": "Заказы", + "SWITCH_ORDERS": "Переключение заказов", + "NEW_ORDER": "Новый порядок", + "PRODUCTS": "Лучшие продукты", + "ARE_YOU_SURE_TO_DELETE": "Вы уверены, что хотите этого", + "REMOVE": "Удалять", + "DELETE": "Удалить", + "THE_FOLLOWING_DATA": "следующие данные", + "CONFIRM": "Подтверждение", + "CANCEL": "Отмена", + "SELECT_POP_UP": { + "SELECT_CATEGORIES": "Выбрать категории", + "OK": "Хорошо", + "CANCEL": "ОТМЕНИТЬ" + }, + "ORDER_WAREHOUSE_STATUSES": { + "ALL": "Все", + "CANCELLED": "Отменен", + "CREATED": "Созданный", + "CONFIRMED": "Подтвердил", + "PROCESSING": "Обработка", + "ALLOCATION_STARTED": "Началось распределение", + "ALLOCATION_FINISHED": "Выделение завершено", + "PACKAGING_STARTED": "Начало упаковки", + "PACKAGED": "В упаковке", + "GIVEN_TO_CARRIER": "Предоставлено перевозчику", + "TAKEN": "Взятый", + "ALLOCATION_FAILED": "Не удалось выполнить распределение", + "PACKAGING_FAILED": "Ошибка упаковки", + "BAD_STATUS": "Плохой статус", + "NO_ACTIVE_ORDERS": "Нет активных заказов", + "NO_ORDERS": "Нет заказов", + "NO_PRODUCTS": "Нет продуктов" + }, + "CREATE_PRODUCTS_POPUP": { + "ERROR_MSG": "Что-то не так, не в состоянии разместить заказ!", + "NEW_PRODUCT_TYPE": "Новый тип продукта", + "CREATE_NEW_PRODUCT_TYPE": "Создать новый тип продукта", + "TITLE": "Заглавие", + "DESCRIPTION": "Описание", + "CATEGORIES": "Категории", + "PRICE": "Цена", + "COUNT": "Подсчитывать", + "LANGUAGE": "Язык", + "DRAG&DROP_FILE_HERE": "Файл Drag & Drop здесь", + "DRAG&DROP_PICTURE_HERE": "Изображение Drag & Drop здесь", + "OR_BROWSE": "or browse", + "COUNT_PLACEHOLDER": "0", + "CLICK_TO_UPLOAD_PICTURE": "Нажмите, чтобы загрузить изображение", + "CLICK_TO_UPLOAD_MORE_PICTURE": "Нажмите, чтобы загрузить больше фотографий", + "CANCEL": "Отмена", + "CREATE": "Создайте", + "ENGLISH": "Английский", + "HEBREW": "Иврит", + "RUSSIAN": "Русский", + "BULGARIAN": "Болгарский", + "OPTIONAL": "необязательный", + "TAKEAWAY": "Навынос", + "DELIVERY": "Доставка", + "PRODUCT_AVAILABILITY": "Доступность продукта" + }, + "NEW_ORDER_VIEW": { + "NEW_MANUAL_ORDER": "Новый ручной порядок", + "SELECT_EXISTING_CUSTOMER": "Выбрать существующего клиента", + "ADD_NEW_CUSTOMER": "Добавить нового клиента", + "FULL_NAME": "ФИО", + "EMAIL": "Эл. адрес", + "PHONE": "Телефон", + "ADDRESS": "Адрес", + "IMAGE": "Образ", + "PRODUCT": "Товар", + "PRICE": "Цена", + "AVAILABLE": "Имеется в наличии", + "COMMENT": "Комментарий", + "AMOUNT": "Количество", + "MAKE_ORDER": "Сделать заказ", + "BASIC_INFO": "Базовая информация", + "FIRST_NAME": "Имя", + "LAST_NAME": "Фамилия", + "EMAIL_IS_INVALID": "Недействительный адрес электронной почты", + "EMAIL_IS_ALREADY_IN_USE": "Email уже используется", + "NEXT": "следующий", + "ADD_CUSTOMER": "Добавить клиента", + "BACK": "назад", + "FIND_ADDRESS": "Найти адрес", + "COUNTRY": "Страна", + "CITY": "город", + "COUNTRY_IS_INVALID": "Страна недействительна.", + "CITY_IS_REQUIRED": "Город требуется", + "ZIP": "ZIP", + "STREET_ADDRESS": "Адрес улицы", + "STREET_ADDRESS_IS_REQUIRED": "Требуется адрес улицы", + "HOUSE_№": "Дом №", + "APARTMENT": "квартира", + "SHOW_COORDINATES": "Показать координаты", + "LATITUDE": "широта", + "LONGITUTE": "Долгота", + "HOUSE_IS_REQUIRED": "Требуется дом" + }, + "EDIT_PICTURE": { + "PRODUCT_IMAGES": "Изображения продуктов", + "SAVE_IMAGES": "Сохранить изображения" + }, + "MISC_TEXT": { + "CREATE_NEW_ORDER": "Создать новый заказ", + "ADD_NEW_PRODUCT": "Добавить новый продукт" + } + }, + "EDIT_PRODUCT_POP_UP": { + "EDIT_PRODUCT_TYPE": "Изменить тип продукта", + "UPDATE_AN_EXISTING_PRODUCT": "Обновление существующего продукта", + "CLICK_TO_EDIT_PICTURES": "Нажмите, чтобы редактировать фотографии", + "TITLE": "Заглавие", + "DESCRIPTION": "Описание", + "CATEGORIES": "Категории", + "PRICE": "Цена", + "COUNT": "Подсчитывать", + "LANGUAGE": "Язык", + "DRAG&DROP_FILE_HERE": "Файл Drag & Drop здесь", + "OR_BROWSE": "or browse", + "CLICK_TO_UPLAOD_PICTURE": "Нажмите, чтобы загрузить изображение", + "CANCEL": "Отмена", + "UPDATE": "Обновить", + "ENGLISH": "Английский", + "HEBREW": "Иврит", + "RUSSIAN": "Русский", + "BULGARIAN": "Болгарский", + "SPANISH": "Испанский" + }, + "ORDER_CANCELED_COMPONENT": { + "CANCELED": "отменен", + "ORDER_CANCELED": "Поръчката е анулирана", + "DELIVERED": "Доставени", + "CREATED_AT": "Създаден в" + }, + "ORDER_CONTROL_BUTTONS_COMPONENT": { + "CONFIRM": "подтвердить", + "START_PROCESSING": "Начать обработку", + "START_ALLOCATION": "Начать распределение", + "ALLOCATED": "Выделено", + "START_PACKAGING": "Начало упаковки", + "PACKAGED": "В упаковке", + "FAILED": "Не смогли", + "GIVEN_TO_CARRIER": "Предоставлено перевозчику", + "GIVEN_TO_CUSTOMER": "Передано клиенту" + }, + "ORDER_CONTROL_BUTTONS": { + "CONFIRM": "подтвердить", + "START_PROCESSING": "Начать обработку", + "START_ALLOCATION": "Начать распределение", + "ALLOCATED": "Выделено", + "START_PACKAGING": "Начало упаковки", + "PACKAGED": "В упаковке", + "FAILED": "Не смогли", + "GIVEN_TO_CARRIER": "Предоставлено перевозчику" + }, + "ORDER_DELIVERED_COMPONENT": { + "DELIVERED": "Доставлен", + "CREATED_AT": "Создан в", + "DELIVERED_AT": "Поставляется в", + "DELIVERED_TO": "Доставлен в" + }, + "ORDER_DELIVERY_PROBLEM_COMPONENT": { + "DELIVERY_ISSUES": "Проблемы с доставкой", + "ISSUES_DURING_DELIVERY_TO": "Проблемы при доставке", + "BY": "От", + "CREATED_AT": "Создан в" + }, + "ORDER_WAREHOUSE_PREPARATION_PROBLEM_COMPONENT": { + "CREATED_AT": "Создан в" + }, + "ORDER_WITH_CARRIER_COMPONENT": { + "IN_DELIVERY_TO": "В Доставка до", + "BY": "От", + "CREATED_AT": "Создан в" + }, + "ORDER_WITHOUT_CARRIER_COMPONENT": { + "CREATED_AT": "Создан в", + "CANT_PROCESSING_WITHOUT_PRODUCTS": "Невозможно обработать заказ без продуктов." + }, + "ORDER_TITLE_COMPONENT": { + "ORDER": "порядок", + "FROM": "от" + }, + "ORDER_TYPE": { + "TYPE": "Тип заказа:", + "DELIVERY": "Доставка", + "TAKEAWAY": "Навынос" + }, + "TRACK_ORDER_POP_UP": { + "CARRIER": "Перевозчик", + "TRACK": "трек", + "CUSTOMER": "Покупатель" + }, + "CONNECTION_ERROR_VIEW": { + "TITLE": "Не удалось подключиться к Ever", + "DESCRIPTION": [ + "Проверьте подключение к", + "Интернету и повторите попытку.", + "" + ] + }, + "CUSTOMER_ORDERS_POP_UP": { + "ORDER_ID": "Номер заказа", + "DELIVERY": "Доставка", + "ADDRESS": "Адрес", + "STATUS": "Положение дел" + }, + "NO_SERVER_VIEW": { + "NO_SERVER": "Сервер упал" + }, + "PROMOTIONS": { + "PROMO_PRICE": "Промо цена", + "NO_PROMOTIONS": "Нет Акции" + } +} diff --git a/packages/merchant-tablet-ionic/src/assets/icon/favicon.ico b/packages/merchant-tablet-ionic/src/assets/icon/favicon.ico new file mode 100644 index 0000000..79c60ff Binary files /dev/null and b/packages/merchant-tablet-ionic/src/assets/icon/favicon.ico differ diff --git a/packages/merchant-tablet-ionic/src/assets/icon/favicon.png b/packages/merchant-tablet-ionic/src/assets/icon/favicon.png new file mode 100644 index 0000000..d053869 Binary files /dev/null and b/packages/merchant-tablet-ionic/src/assets/icon/favicon.png differ diff --git a/packages/merchant-tablet-ionic/src/assets/icon/favicon16x16.png b/packages/merchant-tablet-ionic/src/assets/icon/favicon16x16.png new file mode 100644 index 0000000..d053869 Binary files /dev/null and b/packages/merchant-tablet-ionic/src/assets/icon/favicon16x16.png differ diff --git a/packages/merchant-tablet-ionic/src/assets/icon/favicon32x32.png b/packages/merchant-tablet-ionic/src/assets/icon/favicon32x32.png new file mode 100644 index 0000000..5864e89 Binary files /dev/null and b/packages/merchant-tablet-ionic/src/assets/icon/favicon32x32.png differ diff --git a/packages/merchant-tablet-ionic/src/assets/icon/favicon48x48.png b/packages/merchant-tablet-ionic/src/assets/icon/favicon48x48.png new file mode 100644 index 0000000..41741bc Binary files /dev/null and b/packages/merchant-tablet-ionic/src/assets/icon/favicon48x48.png differ diff --git a/packages/merchant-tablet-ionic/src/assets/imgs/ever-1000x1000.png b/packages/merchant-tablet-ionic/src/assets/imgs/ever-1000x1000.png new file mode 100644 index 0000000..6d1d0ea Binary files /dev/null and b/packages/merchant-tablet-ionic/src/assets/imgs/ever-1000x1000.png differ diff --git a/packages/merchant-tablet-ionic/src/assets/imgs/ever-500x500-circle.pdn b/packages/merchant-tablet-ionic/src/assets/imgs/ever-500x500-circle.pdn new file mode 100644 index 0000000..c2000c6 Binary files /dev/null and b/packages/merchant-tablet-ionic/src/assets/imgs/ever-500x500-circle.pdn differ diff --git a/packages/merchant-tablet-ionic/src/assets/imgs/ever-500x500-circle.png b/packages/merchant-tablet-ionic/src/assets/imgs/ever-500x500-circle.png new file mode 100644 index 0000000..6157b03 Binary files /dev/null and b/packages/merchant-tablet-ionic/src/assets/imgs/ever-500x500-circle.png differ diff --git a/packages/merchant-tablet-ionic/src/assets/imgs/ever-500x500-slogan.png b/packages/merchant-tablet-ionic/src/assets/imgs/ever-500x500-slogan.png new file mode 100644 index 0000000..3c5415f Binary files /dev/null and b/packages/merchant-tablet-ionic/src/assets/imgs/ever-500x500-slogan.png differ diff --git a/packages/merchant-tablet-ionic/src/assets/imgs/ever-500x500.png b/packages/merchant-tablet-ionic/src/assets/imgs/ever-500x500.png new file mode 100644 index 0000000..51dfd56 Binary files /dev/null and b/packages/merchant-tablet-ionic/src/assets/imgs/ever-500x500.png differ diff --git a/packages/merchant-tablet-ionic/src/assets/imgs/ever-background-gradient.svg b/packages/merchant-tablet-ionic/src/assets/imgs/ever-background-gradient.svg new file mode 100644 index 0000000..c715270 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/assets/imgs/ever-background-gradient.svg @@ -0,0 +1,16 @@ + + + Rectangle + Rectangle + + + + + + + + + + + + diff --git a/packages/merchant-tablet-ionic/src/assets/imgs/ever-logo.svg b/packages/merchant-tablet-ionic/src/assets/imgs/ever-logo.svg new file mode 100644 index 0000000..b2460b5 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/assets/imgs/ever-logo.svg @@ -0,0 +1,11 @@ + + + ever logo + Ever Logo + + + + + + + \ No newline at end of file diff --git a/packages/merchant-tablet-ionic/src/assets/imgs/image_placeholder.png b/packages/merchant-tablet-ionic/src/assets/imgs/image_placeholder.png new file mode 100644 index 0000000..79a48dd Binary files /dev/null and b/packages/merchant-tablet-ionic/src/assets/imgs/image_placeholder.png differ diff --git a/packages/merchant-tablet-ionic/src/assets/imgs/login_back.png b/packages/merchant-tablet-ionic/src/assets/imgs/login_back.png new file mode 100644 index 0000000..565cd8e Binary files /dev/null and b/packages/merchant-tablet-ionic/src/assets/imgs/login_back.png differ diff --git a/packages/merchant-tablet-ionic/src/assets/imgs/logo.png b/packages/merchant-tablet-ionic/src/assets/imgs/logo.png new file mode 100644 index 0000000..51dfd56 Binary files /dev/null and b/packages/merchant-tablet-ionic/src/assets/imgs/logo.png differ diff --git a/packages/merchant-tablet-ionic/src/components/carrier-deliveries-table/customer.ts b/packages/merchant-tablet-ionic/src/components/carrier-deliveries-table/customer.ts new file mode 100644 index 0000000..a96011d --- /dev/null +++ b/packages/merchant-tablet-ionic/src/components/carrier-deliveries-table/customer.ts @@ -0,0 +1,37 @@ +import { Component, OnInit, Input } from '@angular/core'; +import { ViewCell } from 'ng2-smart-table'; +import Order from '@modules/server.common/entities/Order'; +import User from '@modules/server.common/entities/User'; +import UserOrder from '@modules/server.common/entities/UserOrder'; + +@Component({ + template: ` + + {{ getUserName(order) }} +
{{ order.user.fullAddress }}
+
+ `, +}) +export class CustomerComponent implements ViewCell, OnInit { + value: string | number; + rowData: any; + order: Order; + + constructor() {} + + ngOnInit(): void { + this.order = this.rowData.order; + } + + getUserName(order: Order) { + const user: UserOrder = order.user; + + if (user.firstName) { + if (user.lastName) { + return user.firstName + ' ' + user.lastName; + } + + return user.firstName; + } + } +} diff --git a/packages/merchant-tablet-ionic/src/components/carrier-deliveries-table/delivery.ts b/packages/merchant-tablet-ionic/src/components/carrier-deliveries-table/delivery.ts new file mode 100644 index 0000000..8d3f978 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/components/carrier-deliveries-table/delivery.ts @@ -0,0 +1,54 @@ +import { Component, OnInit } from '@angular/core'; +import { ViewCell } from 'ng2-smart-table'; +import Order from '@modules/server.common/entities/Order'; + +@Component({ + template: ` + +
{{ order._createdAt | date: 'short' }}
+
{{ getTotalDeliveryTime(order) }}
+
+ `, +}) +export class DeliveryComponent implements ViewCell, OnInit { + value: string | number; + rowData: any; + order: Order; + + constructor() {} + + ngOnInit(): void { + this.order = this.rowData.order; + } + + getTotalDeliveryTime(order: Order) { + const start = order.createdAt; + + const end = new Date(order.deliveryTime); + + let delta = Math.abs(start.getTime() - end.getTime()) / 1000; + + const days = Math.floor(delta / 86400); + delta -= days * 86400; + + const hours = Math.floor(delta / 3600) % 24; + delta -= hours * 3600; + + const minutes = Math.floor(delta / 60) % 60; + delta -= minutes * 60; + + const seconds = delta % 60; + let secondsStr = seconds.toString(); + secondsStr = secondsStr.substring(0, secondsStr.indexOf('.')); + + let h = '0' + hours; + h = h.substr(-2); + let min = '0' + minutes; + min = min.substr(-2); + let sec = '0' + secondsStr; + sec = sec.substr(-2); + + return `${days !== 0 ? days + 'days ' : ''} + ${hours} : ${min} : ${sec}`; + } +} diff --git a/packages/merchant-tablet-ionic/src/components/carrier-deliveries-table/status.ts b/packages/merchant-tablet-ionic/src/components/carrier-deliveries-table/status.ts new file mode 100644 index 0000000..075acee --- /dev/null +++ b/packages/merchant-tablet-ionic/src/components/carrier-deliveries-table/status.ts @@ -0,0 +1,29 @@ +import { Component, OnInit } from '@angular/core'; +import { ViewCell } from 'ng2-smart-table'; +import Order from '@modules/server.common/entities/Order'; + +@Component({ + template: ` + +
+ {{ 'CARRIERS_VIEW.DELIVERIES_POP_UP.COMPLETED' | translate + }}{{ order.isCompleted ? ' ✔' : ' ✘' }} +
+
+ {{ 'CARRIERS_VIEW.DELIVERIES_POP_UP.PAID' | translate + }}{{ order.isPaid ? ' ✔' : ' ✘' }} +
+
+ `, +}) +export class StatusComponent implements ViewCell, OnInit { + value: string | number; + rowData: any; + order: Order; + + constructor() {} + + ngOnInit(): void { + this.order = this.rowData.order; + } +} diff --git a/packages/merchant-tablet-ionic/src/components/carrier-deliveries-table/warehouse.ts b/packages/merchant-tablet-ionic/src/components/carrier-deliveries-table/warehouse.ts new file mode 100644 index 0000000..8f28585 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/components/carrier-deliveries-table/warehouse.ts @@ -0,0 +1,44 @@ +import { Component, OnInit } from '@angular/core'; +import { ViewCell } from 'ng2-smart-table'; +import Order from '@modules/server.common/entities/Order'; +import Warehouse from '@modules/server.common/entities/Warehouse'; + +@Component({ + template: ` + + {{ + getStoreName(order) + }} +
{{ getStoreFullAddress(order) }}
+
+ `, +}) +export class WarehouseComponent implements ViewCell, OnInit { + value: string | number; + rowData: any; + order: Order; + + constructor() {} + + ngOnInit(): void { + this.order = this.rowData.order; + } + + getStoreName(order: Order) { + const store: Warehouse = order.warehouse as Warehouse; + + if (store) return store.name; + + return null; + } + + getStoreFullAddress(order: Order) { + const store: Warehouse = order.warehouse as Warehouse; + + const geoLocation = store.geoLocation; + + const fullAddress = `${geoLocation.city}, ${geoLocation.streetAddress} ${geoLocation.house}`; + + return fullAddress; + } +} diff --git a/packages/merchant-tablet-ionic/src/components/carriers-table/addresses.ts b/packages/merchant-tablet-ionic/src/components/carriers-table/addresses.ts new file mode 100644 index 0000000..1ea36a3 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/components/carriers-table/addresses.ts @@ -0,0 +1,38 @@ +import { Component, OnInit } from '@angular/core'; +import { ViewCell } from 'ng2-smart-table'; +import Carrier from '@modules/server.common/entities/Carrier'; +import { ModalController } from '@ionic/angular'; +import { CarrierAddrPopupPage } from 'pages/+carriers/carrier-addr-popup/carrier-addr-popup'; +import { CarrierTrackPopup } from 'pages/+carriers/carrier-track-popup/carrier-track-popup'; + +@Component({ + styles: [``], + template: ` + + {{ carrier.geoLocation.city }} + {{ + '(' + carrier.geoLocation.postcode + ')' + }} + + `, +}) +export class AddressesComponent implements ViewCell, OnInit { + value: string | number; + rowData: any; + carrier: Carrier; + + constructor(public modalCtrl: ModalController) {} + + ngOnInit(): void { + this.carrier = this.rowData.carrier; + } + + async showAddress(carrier) { + const modal = await this.modalCtrl.create({ + component: CarrierTrackPopup, + componentProps: { carrier }, + cssClass: 'carrier-track-wrapper', + }); + await modal.present(); + } +} diff --git a/packages/merchant-tablet-ionic/src/components/carriers-table/deliveries.ts b/packages/merchant-tablet-ionic/src/components/carriers-table/deliveries.ts new file mode 100644 index 0000000..f0595df --- /dev/null +++ b/packages/merchant-tablet-ionic/src/components/carriers-table/deliveries.ts @@ -0,0 +1,46 @@ +import { Component, OnInit } from '@angular/core'; +import { ViewCell } from 'ng2-smart-table'; +import Carrier from '@modules/server.common/entities/Carrier'; +import { ModalController } from '@ionic/angular'; +import { CarrierDeliveriesPopupPage } from 'pages/+carriers/carrier-deliveries-popup/carrier-deliveries-popup'; + +@Component({ + styles: [``], + template: ` +
+ +
+
+ + {{ + carrier.deliveriesCountToday + + '/' + + carrier.numberOfDeliveries + }} + +
+ `, +}) +export class DeliveriesComponent implements ViewCell, OnInit { + value: string | number; + rowData: any; + carrier: Carrier; + + constructor(public modalCtrl: ModalController) {} + + ngOnInit(): void { + this.carrier = this.rowData.carrier; + } + + async showDeliveriesInfo(carrier) { + const modal = await this.modalCtrl.create({ + component: CarrierDeliveriesPopupPage, + componentProps: { carrier }, + cssClass: 'carrier-deliveries', + }); + await modal.present(); + } +} diff --git a/packages/merchant-tablet-ionic/src/components/carriers-table/image.scss b/packages/merchant-tablet-ionic/src/components/carriers-table/image.scss new file mode 100644 index 0000000..9287382 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/components/carriers-table/image.scss @@ -0,0 +1,6 @@ +.image-component { + img { + width: 64px; + height: 64px; + } +} diff --git a/packages/merchant-tablet-ionic/src/components/carriers-table/image.ts b/packages/merchant-tablet-ionic/src/components/carriers-table/image.ts new file mode 100644 index 0000000..d923b9b --- /dev/null +++ b/packages/merchant-tablet-ionic/src/components/carriers-table/image.ts @@ -0,0 +1,25 @@ +import { Component, OnInit } from '@angular/core'; +import { ViewCell } from 'ng2-smart-table'; +import Carrier from '@modules/server.common/entities/Carrier'; +import { Store } from '../../services/store.service'; + +@Component({ + selector: 'carrier-image-view', + styleUrls: ['./image.scss'], + template: ` + + + + `, +}) +export class ImageComponent implements ViewCell, OnInit { + value: string | number; + rowData: any; + carrier: Carrier; + + constructor(private store: Store) {} + + ngOnInit(): void { + this.carrier = this.rowData.carrier; + } +} diff --git a/packages/merchant-tablet-ionic/src/components/carriers-table/phone.ts b/packages/merchant-tablet-ionic/src/components/carriers-table/phone.ts new file mode 100644 index 0000000..5e00c54 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/components/carriers-table/phone.ts @@ -0,0 +1,54 @@ +import { Component, OnInit, Input } from '@angular/core'; +import { ViewCell } from 'ng2-smart-table'; +import Carrier from '@modules/server.common/entities/Carrier'; +import { Store } from '../../services/store.service'; +import { CallNumber } from '@ionic-native/call-number/ngx'; + +@Component({ + selector: 'carrier-phone', + template: ` + + + {{ carrier?.phone || '' }} + `, +}) +export class PhoneComponent implements ViewCell, OnInit { + value: string | number; + rowData: any; + + @Input() + carrier: Carrier; + + constructor(private store: Store, public callNumber: CallNumber) {} + + ngOnInit(): void { + if (this.rowData) { + this.carrier = this.rowData.carrier; + } + } + + get canCall() { + if (this.store.platform) { + return ( + this.store.platform.toLocaleLowerCase() === 'android' || + this.store.platform.toLocaleLowerCase() === 'ios' + ); + } + return false; + } + + attemptCall(phone) { + if (this.canCall) { + this.callNumber + .callNumber(phone, true) + .then((res) => console.warn('Called number!', res)) + .catch((err) => console.log('Error calling number!', err)); + } + } +} diff --git a/packages/merchant-tablet-ionic/src/components/carriers-table/status.ts b/packages/merchant-tablet-ionic/src/components/carriers-table/status.ts new file mode 100644 index 0000000..6797d3b --- /dev/null +++ b/packages/merchant-tablet-ionic/src/components/carriers-table/status.ts @@ -0,0 +1,32 @@ +import { Component, OnInit } from '@angular/core'; +import { ViewCell } from 'ng2-smart-table'; +import Carrier from '@modules/server.common/entities/Carrier'; + +@Component({ + styles: [``], + template: ` +
+ {{ + 'CARRIERS_VIEW.WORKING' | translate + }} + {{ + 'CARRIERS_VIEW.NOT_WORKING' | translate + }} + {{ + 'CARRIERS_VIEW.BLOCKED' | translate + }} +
+
+ `, +}) +export class StatusComponent implements ViewCell, OnInit { + value: string | number; + rowData: any; + carrier: Carrier; + + constructor() {} + + ngOnInit(): void { + this.carrier = this.rowData.carrier; + } +} diff --git a/packages/merchant-tablet-ionic/src/components/common/carrier-info/carrier-info.html b/packages/merchant-tablet-ionic/src/components/common/carrier-info/carrier-info.html new file mode 100644 index 0000000..1af0356 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/components/common/carrier-info/carrier-info.html @@ -0,0 +1,28 @@ +
+
+
+ + {{ 'TRACK_ORDER_POP_UP.CARRIER' | translate }} +
+
+
+
+ +
+
+
+
{{ fullName }}
+ +
{{ order.carrier.email }}
+
{{ fullAddress }}
+
+ +
+ +
+
+
+
diff --git a/packages/merchant-tablet-ionic/src/components/common/carrier-info/carrier-info.scss b/packages/merchant-tablet-ionic/src/components/common/carrier-info/carrier-info.scss new file mode 100644 index 0000000..6696106 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/components/common/carrier-info/carrier-info.scss @@ -0,0 +1,64 @@ +@import '~@ever-platform/common-angular/src/scss/everbie.common'; + +.carrier-info { + .carrier-image { + width: 15%; + } + + .header { + text-align: left; + margin: 0px 10px 0 10px; + padding-top: 10px; + :first-child { + display: flex; + + margin: 0; + margin-bottom: 5px; + } + ion-icon { + font-size: 1.8rem; + margin-bottom: 0 !important; + margin-right: 5px !important; + } + + border-bottom: 1px solid #dedede; + } + + .body { + display: flex; + align-items: flex-start; + .carrier-image, + .carrier-details { + padding: 10px; + text-align: left; + line-height: 1.6; + + .call-icon { + font-size: 1.4rem; + } + } + .row { + width: 100%; + + .treck-carrier { + padding: 10px; + margin: 0; + text-align: right; + + button { + background: $brand; + color: white; + padding: 10px; + border-radius: 2px; + display: flex; + float: right; + align-content: center; + align-items: center; + ion-icon { + margin-right: 3px; + } + } + } + } + } +} diff --git a/packages/merchant-tablet-ionic/src/components/common/carrier-info/carrier-info.ts b/packages/merchant-tablet-ionic/src/components/common/carrier-info/carrier-info.ts new file mode 100644 index 0000000..9caa15d --- /dev/null +++ b/packages/merchant-tablet-ionic/src/components/common/carrier-info/carrier-info.ts @@ -0,0 +1,48 @@ +import { Component, Input } from '@angular/core'; +import IOrder from '@modules/server.common/interfaces/IOrder'; +import { ModalController } from '@ionic/angular'; +import { OrderMapPopupPage } from 'components/order-map-popup/order-map-popup'; + +@Component({ + selector: 'carrier-info', + styleUrls: ['./carrier-info.scss'], + templateUrl: 'carrier-info.html', +}) +export class CarrierInfoComponent { + @Input() + public order: IOrder; + + constructor(public modalCtrl: ModalController) {} + + get fullName() { + const fullName = `${this.order.carrier['firstName'] || ''} ${ + this.order.carrier['lastName'] || '' + }`; + return fullName.trim(); + } + + get fullAddress() { + return ( + `${this.order.carrier['geoLocation'].city}, ${this.order.carrier['geoLocation'].streetAddress} ` + + `${this.order.carrier['geoLocation'].house}` + + `${ + this.order.carrier['apartment'] + ? '/' + this.order.carrier['apartment'] + : '' + }` + ); + } + + async track() { + const order = this.order; + const modal = await this.modalCtrl.create({ + component: OrderMapPopupPage, + componentProps: { + order, + }, + cssClass: 'order-map-popup', + }); + + await modal.present(); + } +} diff --git a/packages/merchant-tablet-ionic/src/components/common/customer-info/customer-info.html b/packages/merchant-tablet-ionic/src/components/common/customer-info/customer-info.html new file mode 100644 index 0000000..b5ae106 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/components/common/customer-info/customer-info.html @@ -0,0 +1,20 @@ +
+
+
+ + {{ 'TRACK_ORDER_POP_UP.CUSTOMER' | translate }} +
+
+
+
+ +
+
+
{{ userFullName }}
+ +
{{ user.email }}
+
{{ fullAddress }}
+
Notes: {{ user.geoLocation.notes }}
+
+
+
diff --git a/packages/merchant-tablet-ionic/src/components/common/customer-info/customer-info.scss b/packages/merchant-tablet-ionic/src/components/common/customer-info/customer-info.scss new file mode 100644 index 0000000..dc1049f --- /dev/null +++ b/packages/merchant-tablet-ionic/src/components/common/customer-info/customer-info.scss @@ -0,0 +1,39 @@ +.customer-info { + .customer-image { + width: 15%; + } + + .header { + text-align: left; + margin: 0px 10px 0 10px; + padding-top: 10px; + :first-child { + display: flex; + margin: 0; + margin-bottom: 5px; + } + + ion-icon { + font-size: 1.8rem; + margin-bottom: 0 !important; + margin-right: 5px !important; + } + + border-bottom: 1px solid #dedede; + } + + .body { + display: flex; + align-items: flex-start; + .customer-image, + .customer-details { + padding: 10px; + text-align: left; + line-height: 1.6; + + .call-icon { + font-size: 1.4rem; + } + } + } +} diff --git a/packages/merchant-tablet-ionic/src/components/common/customer-info/customer-info.ts b/packages/merchant-tablet-ionic/src/components/common/customer-info/customer-info.ts new file mode 100644 index 0000000..1c4eadc --- /dev/null +++ b/packages/merchant-tablet-ionic/src/components/common/customer-info/customer-info.ts @@ -0,0 +1,29 @@ +import { Component, Input } from '@angular/core'; +import UserOrder from '@modules/server.common/entities/UserOrder'; + +@Component({ + selector: 'customer-info', + styleUrls: ['./customer-info.scss'], + templateUrl: 'customer-info.html', +}) +export class CustomerInfoComponent { + @Input() + public user: UserOrder; + + constructor() {} + + get userFullName() { + const fullName = `${this.user.firstName || ''} ${ + this.user.lastName || '' + }`; + return fullName.trim(); + } + + get fullAddress() { + return ( + `${this.user.geoLocation.city}, ${this.user.geoLocation.streetAddress} ` + + `${this.user.geoLocation.house}` + + `${this.user.apartment ? '/' + this.user.apartment : ''}` + ); + } +} diff --git a/packages/merchant-tablet-ionic/src/components/common/order-image/order-image.html b/packages/merchant-tablet-ionic/src/components/common/order-image/order-image.html new file mode 100644 index 0000000..01a2c9e --- /dev/null +++ b/packages/merchant-tablet-ionic/src/components/common/order-image/order-image.html @@ -0,0 +1,27 @@ +
+

+ #{{ order.orderNumber }} + + {{ getOrderDate(order.createdAt) }}/{{ + getOrderMonth(order.createdAt) }} + +

+ +
+
+
+ {{ product.count }} + Product Image +
+
+ {{ productLocalesService.getTranslate(product.product.title) }} +
+
+
{{ product.comment }}
+
+
+
+
diff --git a/packages/merchant-tablet-ionic/src/components/common/order-image/order-image.scss b/packages/merchant-tablet-ionic/src/components/common/order-image/order-image.scss new file mode 100644 index 0000000..27f0e09 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/components/common/order-image/order-image.scss @@ -0,0 +1,62 @@ +@import '~@ever-platform/common-angular/src/scss/everbie.common'; + +.order-image-container { + padding: 0 !important; + .order-image { + display: block !important; + padding: 0 8px 8px 8px; + + .image { + height: 90%; + position: relative; + + .product-count { + background: $assertive; + color: white; + + padding: 4px 7px; + position: absolute; + // right: 0; + right: 5px; + top: 5px; + border-radius: 50%; + } + } + + .name { + height: 10%; + background: $brand; + color: white; + padding: 5px; + margin-top: -4px; + + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; + } + + .comment { + color: white; + background: $brand; + font-size: 14px; + padding: 5px; + padding-top: 0; + + // comment-text + &-text { + margin-top: 5px; + } + + &::before { + content: ''; + height: 1px; + background: white; + display: block; + } + } + } + + .product-info { + padding-top: 8px; + } +} diff --git a/packages/merchant-tablet-ionic/src/components/common/order-image/order-image.ts b/packages/merchant-tablet-ionic/src/components/common/order-image/order-image.ts new file mode 100644 index 0000000..ab606c2 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/components/common/order-image/order-image.ts @@ -0,0 +1,31 @@ +import { Component, Input } from '@angular/core'; +import { ProductLocalesService } from '@modules/client.common.angular2/locale/product-locales.service'; +import Order from '@modules/server.common/entities/Order'; + +@Component({ + selector: 'order-image', + templateUrl: './order-image.html', + styleUrls: ['./order-image.scss'] +}) +export class OrderImageComponent { + @Input() + order: Order; + + constructor( + public readonly productLocalesService: ProductLocalesService + ) {} + + getOrderDate(date: Date) { + return new Date(date).getDate(); + } + + getOrderMonth(date: Date) { + return new Date(date).getMonth() + 1; + } + + getProductImages(images) { + return [...images].sort((a, b) => { + return b.orientation - a.orientation; + }); + } +} diff --git a/packages/merchant-tablet-ionic/src/components/common/order-info/order-info.html b/packages/merchant-tablet-ionic/src/components/common/order-info/order-info.html new file mode 100644 index 0000000..7ccfc10 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/components/common/order-info/order-info.html @@ -0,0 +1,12 @@ +
+
+
+
+ {{ 'ORDER_TYPE.TYPE' | translate }} +
+
+ {{ orderType }} +
+
+
+
diff --git a/packages/merchant-tablet-ionic/src/components/common/order-info/order-info.scss b/packages/merchant-tablet-ionic/src/components/common/order-info/order-info.scss new file mode 100644 index 0000000..b108b9a --- /dev/null +++ b/packages/merchant-tablet-ionic/src/components/common/order-info/order-info.scss @@ -0,0 +1,12 @@ +.order-info { + .header { + text-align: left; + margin: 0px 10px 0 10px; + padding-top: 10px; + :first-child { + display: flex; + margin: 0; + margin-bottom: 5px; + } + } +} diff --git a/packages/merchant-tablet-ionic/src/components/common/order-info/order-info.ts b/packages/merchant-tablet-ionic/src/components/common/order-info/order-info.ts new file mode 100644 index 0000000..d1532b2 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/components/common/order-info/order-info.ts @@ -0,0 +1,26 @@ +import { Component, Input } from '@angular/core'; +import Order from '@modules/server.common/entities/Order'; +import { TranslateService } from '@ngx-translate/core'; +import DeliveryType from '@modules/server.common/enums/DeliveryType'; + +@Component({ + selector: 'order-info', + styleUrls: ['./order-info.scss'], + templateUrl: 'order-info.html', +}) +export class OrderInfoComponent { + @Input() + public order: Order; + public type: DeliveryType; + + constructor(private translate: TranslateService) {} + + get orderType() { + this.type = + this.order.orderType === 0 + ? this.translate.instant('ORDER_TYPE.DELIVERY') + : this.translate.instant('ORDER_TYPE.TAKEAWAY'); + + return this.type; + } +} diff --git a/packages/merchant-tablet-ionic/src/components/common/order-title/order-title.html b/packages/merchant-tablet-ionic/src/components/common/order-title/order-title.html new file mode 100644 index 0000000..4051462 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/components/common/order-title/order-title.html @@ -0,0 +1,9 @@ +

+ + {{ 'ORDER_TITLE_COMPONENT.ORDER' | translate }} #{{ orderName }} + + {{ 'ORDER_TITLE_COMPONENT.FROM' | translate }} {{ userFullName + }} + +

diff --git a/packages/merchant-tablet-ionic/src/components/common/order-title/order-title.scss b/packages/merchant-tablet-ionic/src/components/common/order-title/order-title.scss new file mode 100644 index 0000000..eb470fe --- /dev/null +++ b/packages/merchant-tablet-ionic/src/components/common/order-title/order-title.scss @@ -0,0 +1,9 @@ +.order-title { + .product-name { + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; + padding-left: 5px; + padding-right: 5px; + } +} diff --git a/packages/merchant-tablet-ionic/src/components/common/order-title/order-title.ts b/packages/merchant-tablet-ionic/src/components/common/order-title/order-title.ts new file mode 100644 index 0000000..aeb475f --- /dev/null +++ b/packages/merchant-tablet-ionic/src/components/common/order-title/order-title.ts @@ -0,0 +1,25 @@ +import { Component, Input } from '@angular/core'; +import { getIdFromTheDate } from '@modules/server.common/utils'; + +@Component({ + selector: 'order-title', + styleUrls: ['./order-title.scss'], + templateUrl: 'order-title.html', +}) +export class OrderTitleComponent { + @Input() + public order; + + get orderName() { + return getIdFromTheDate(this.order); + } + + get userFullName() { + const fullName = `${this.order.user.firstName || ''} ${ + this.order.user.lastName || '' + }`; + return fullName.trim(); + } + + constructor() {} +} diff --git a/packages/merchant-tablet-ionic/src/components/components.module.ts b/packages/merchant-tablet-ionic/src/components/components.module.ts new file mode 100644 index 0000000..50de17b --- /dev/null +++ b/packages/merchant-tablet-ionic/src/components/components.module.ts @@ -0,0 +1,88 @@ +import { NgModule } from '@angular/core'; +import { LoadingComponent } from './loading/loading'; +import { OrderWithoutCarrierComponent } from './order-without-carrier/order-without-carrier'; +import { CommonModule } from '@angular/common'; +import { OrderControlButtonsComponent } from './order-control-buttons/order-control-buttons'; +import { OrderWithCarrierComponent } from './order-with-carrier/order-with-carrier'; +import { OrderDeliveredComponent } from './order-delivered/order-delivered'; +import { OrderCanceledComponent } from './order-canceled/order-canceled'; +import { OrderDeliveryProblemComponent } from './order-delivery-problem/order-delivery-problem'; +import { TranslateModule } from '@ngx-translate/core'; +import { OrderImageComponent } from './common/order-image/order-image'; +import { CommonComponent } from './settings-page-components/common/common'; +import { AccountComponent } from './settings-page-components/account/account'; +import { LocationComponent } from './settings-page-components/location/location'; +import { GoogleMapModule } from '../@shared/google-map/google-map.module'; +import { OrderTitleComponent } from './common/order-title/order-title'; +import { CustomerInfoComponent } from './common/customer-info/customer-info'; +import { CarrierInfoComponent } from './common/carrier-info/carrier-info'; +import { PhoneComponent } from './carriers-table/phone'; +import { UserPhoneComponent } from './users-table/phone'; +import { MomentModule } from 'ngx-moment'; +import { OrderWarehousePreparationProblemComponent } from './order-warehouse-preparation-problem/order-warehouse-preparation-problem'; +import { IonicModule } from '@ionic/angular'; +import { ReactiveFormsModule, FormsModule } from '@angular/forms'; +import { OrderMapPopupPageModule } from './order-map-popup/order-map-popup.module'; +import { CustomerEmailPopupPageModule } from 'pages/+customers/customer-email-popup/customer-email-popup.module'; +import { CustomerDeliveriesPopupPageModule } from 'pages/+customers/customer-deliveries-popup/customer-deliveries-popup.module'; +import { CustomerAddrPopupPageModule } from 'pages/+customers/customer-addr-popup/customer-addr-popup.module'; +import { FileUploaderModule } from './file-uploader/file-uploader.module'; +import { OrderInfoComponent } from './common/order-info/order-info'; +import { ImageTableComponent } from './table-components/image-table'; + +@NgModule({ + declarations: [ + LoadingComponent, + OrderImageComponent, + OrderWithoutCarrierComponent, + OrderControlButtonsComponent, + OrderWithoutCarrierComponent, + OrderWithCarrierComponent, + OrderDeliveredComponent, + OrderCanceledComponent, + OrderDeliveryProblemComponent, + OrderWarehousePreparationProblemComponent, + CommonComponent, + AccountComponent, + LocationComponent, + OrderTitleComponent, + CustomerInfoComponent, + OrderInfoComponent, + CarrierInfoComponent, + PhoneComponent, + UserPhoneComponent, + ImageTableComponent, + ], + imports: [ + CommonModule, + TranslateModule.forChild(), + IonicModule, + FormsModule, + ReactiveFormsModule, + GoogleMapModule, + MomentModule, + OrderMapPopupPageModule, + CustomerEmailPopupPageModule, + CustomerDeliveriesPopupPageModule, + CustomerAddrPopupPageModule, + FileUploaderModule, + ], + exports: [ + LoadingComponent, + OrderImageComponent, + OrderWithoutCarrierComponent, + OrderControlButtonsComponent, + OrderWithoutCarrierComponent, + OrderWithCarrierComponent, + OrderDeliveredComponent, + OrderCanceledComponent, + OrderDeliveryProblemComponent, + OrderWarehousePreparationProblemComponent, + CommonComponent, + AccountComponent, + LocationComponent, + PhoneComponent, + ImageTableComponent, + ], +}) +export class ComponentsModule {} diff --git a/packages/merchant-tablet-ionic/src/components/confirm-delete-popup/confirm-delete-popup.html b/packages/merchant-tablet-ionic/src/components/confirm-delete-popup/confirm-delete-popup.html new file mode 100644 index 0000000..2057153 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/components/confirm-delete-popup/confirm-delete-popup.html @@ -0,0 +1,36 @@ +
+

+ {{'WAREHOUSE_VIEW.ARE_YOU_SURE_TO_DELETE' | translate }} {{ isRemove ? + ('WAREHOUSE_VIEW.REMOVE' | translate) : ('WAREHOUSE_VIEW.DELETE' | + translate) }} {{'WAREHOUSE_VIEW.THE_FOLLOWING_DATA' | translate}}? +

+
+ +
+
{{ data?.name }}
+
+ {{ data?.status | titlecase }} +
+
+ {{ data?.phone }} +
+
{{ data?.addresses }}
+
+
+ {{ customText }} +
+
+
+ + {{'WAREHOUSE_VIEW.CONFIRM' | translate}} + + {{'WAREHOUSE_VIEW.CANCEL' | translate}} +
+
diff --git a/packages/merchant-tablet-ionic/src/components/confirm-delete-popup/confirm-delete-popup.module.ts b/packages/merchant-tablet-ionic/src/components/confirm-delete-popup/confirm-delete-popup.module.ts new file mode 100644 index 0000000..b0a33f8 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/components/confirm-delete-popup/confirm-delete-popup.module.ts @@ -0,0 +1,25 @@ +import { NgModule } from '@angular/core'; +import { FormWizardModule } from '@ever-co/angular2-wizard'; +import { Ng2SmartTableModule } from 'ng2-smart-table'; +import { TranslateModule } from '@ngx-translate/core'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { FileUploadModule } from 'ng2-file-upload'; +import { IonicModule } from '@ionic/angular'; +import { CommonModule } from '@angular/common'; +import { ConfirmDeletePopupPage } from './confirm-delete-popup'; + +@NgModule({ + declarations: [ConfirmDeletePopupPage], + imports: [ + FormWizardModule, + Ng2SmartTableModule, + FileUploadModule, + FormsModule, + ReactiveFormsModule, + TranslateModule.forChild(), + IonicModule, + CommonModule, + FormsModule, + ] +}) +export class ConfirmDeletePopupModule {} diff --git a/packages/merchant-tablet-ionic/src/components/confirm-delete-popup/confirm-delete-popup.scss b/packages/merchant-tablet-ionic/src/components/confirm-delete-popup/confirm-delete-popup.scss new file mode 100644 index 0000000..d740e00 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/components/confirm-delete-popup/confirm-delete-popup.scss @@ -0,0 +1,65 @@ +.confirm-delete-wrapper { + display: flex !important; + .modal-wrapper { + border-radius: 5px; + width: 300px; + height: 300px; + left: calc(50% - (300px / 2)); + top: calc(50% - (300px / 2)); + .ion-page { + border-radius: 5px; + .confirm-delete { + border: 1px solid rgba(0, 0, 0, 0.125); + background: white; + width: 100%; + height: 100%; + border-radius: 5px; + display: flex; + flex-direction: column; + align-items: center; + justify-content: space-evenly; + h4 { + width: 90%; + text-align: center; + text-transform: lowercase; + + &::first-letter { + text-transform: uppercase; + } + } + .control { + display: flex; + justify-content: space-between; + width: 90%; + } + .details { + display: flex; + align-items: center; + border: 1px solid rgba(0, 0, 0, 0.125); + border-radius: 4px; + flex-direction: column; + img { + width: 128px; + height: 128px; + } + .good { + color: green; + } + .bad { + color: crimson; + } + .text { + display: flex; + flex-direction: column; + align-items: center; + width: 100%; + } + } + } + + ion-button { + border: none; + } + } + } +} diff --git a/packages/merchant-tablet-ionic/src/components/confirm-delete-popup/confirm-delete-popup.ts b/packages/merchant-tablet-ionic/src/components/confirm-delete-popup/confirm-delete-popup.ts new file mode 100644 index 0000000..de218dd --- /dev/null +++ b/packages/merchant-tablet-ionic/src/components/confirm-delete-popup/confirm-delete-popup.ts @@ -0,0 +1,34 @@ +import { Component, Input } from '@angular/core'; +import { ModalController } from '@ionic/angular'; + +export interface IConfirmDeleteData { + image: string; + name: string; + status?: string; + phone?: string; + addresses: string; +} + +@Component({ + selector: 'confirm-delete-popup', + templateUrl: 'confirm-delete-popup.html', + styleUrls: ['./confirm-delete-popup.scss'], +}) +export class ConfirmDeletePopupPage { + @Input() + data: IConfirmDeleteData; + @Input() + isRemove: boolean; + @Input() + customText: string; + + constructor(public modalCtrl: ModalController) {} + + cancelModal() { + this.modalCtrl.dismiss(); + } + + async confirmDelete() { + this.modalCtrl.dismiss(true); + } +} diff --git a/packages/merchant-tablet-ionic/src/components/customer-deliveries-table/address.ts b/packages/merchant-tablet-ionic/src/components/customer-deliveries-table/address.ts new file mode 100644 index 0000000..6189862 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/components/customer-deliveries-table/address.ts @@ -0,0 +1,32 @@ +import { Component, OnInit } from '@angular/core'; +import { ViewCell } from 'ng2-smart-table'; +import Order from '@modules/server.common/entities/Order'; +import User from '@modules/server.common/entities/User'; + +@Component({ + template: ` + + {{ + getCustomerFullAddress(order) + }} + + `, +}) +export class AddressComponent implements ViewCell, OnInit { + value: string | number; + rowData: any; + order: Order; + + constructor() {} + + ngOnInit(): void { + this.order = this.rowData.order; + } + + getCustomerFullAddress(order: Order) { + const addressUser: User = order.user as User; + const geoLocation = addressUser.geoLocation; + const fullAddress = `${geoLocation.city}, ${geoLocation.streetAddress} ${geoLocation.house}`; + return fullAddress; + } +} diff --git a/packages/merchant-tablet-ionic/src/components/customer-deliveries-table/delivery.ts b/packages/merchant-tablet-ionic/src/components/customer-deliveries-table/delivery.ts new file mode 100644 index 0000000..0749797 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/components/customer-deliveries-table/delivery.ts @@ -0,0 +1,56 @@ +import { Component, OnInit } from '@angular/core'; +import { ViewCell } from 'ng2-smart-table'; +import Order from '@modules/server.common/entities/Order'; + +@Component({ + template: ` + +
{{ order._createdAt | date: 'short' }}
+
+ {{ getTotalDeliveryTime(order) }} +
+
+ `, +}) +export class DeliveryComponent implements ViewCell, OnInit { + value: string | number; + rowData: any; + order: Order; + + constructor() {} + + ngOnInit(): void { + this.order = this.rowData.order; + } + + getTotalDeliveryTime(order: Order) { + const start = order.createdAt; + + const end = new Date(order.deliveryTime); + + let delta = Math.abs(start.getTime() - end.getTime()) / 1000; + + const days = Math.floor(delta / 86400); + delta -= days * 86400; + + const hours = Math.floor(delta / 3600) % 24; + delta -= hours * 3600; + + const minutes = Math.floor(delta / 60) % 60; + delta -= minutes * 60; + + const seconds = delta % 60; + let secondsStr = seconds.toString(); + secondsStr = secondsStr.substring(0, secondsStr.indexOf('.')); + + let h = '0' + hours; + h = h.substr(-2); + let min = '0' + minutes; + min = min.substr(-2); + let sec = '0' + secondsStr; + sec = sec.substr(-2); + + return `${days !== 0 ? days + 'days ' : ''} + ${hours} : ${min} : ${sec}`; + } +} diff --git a/packages/merchant-tablet-ionic/src/components/customer-deliveries-table/orderId.ts b/packages/merchant-tablet-ionic/src/components/customer-deliveries-table/orderId.ts new file mode 100644 index 0000000..e531909 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/components/customer-deliveries-table/orderId.ts @@ -0,0 +1,17 @@ +import { Component, OnInit } from '@angular/core'; +import { ViewCell } from 'ng2-smart-table'; + +@Component({ + template: ` {{ orderId }} `, +}) +export class OrderIdComponent implements ViewCell, OnInit { + value: string | number; + rowData: any; + orderId: string; + + constructor() {} + + ngOnInit(): void { + this.orderId = this.rowData.orderId; + } +} diff --git a/packages/merchant-tablet-ionic/src/components/customer-deliveries-table/status.ts b/packages/merchant-tablet-ionic/src/components/customer-deliveries-table/status.ts new file mode 100644 index 0000000..e440cd2 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/components/customer-deliveries-table/status.ts @@ -0,0 +1,27 @@ +import { Component, OnInit } from '@angular/core'; +import { ViewCell } from 'ng2-smart-table'; +import Order from '@modules/server.common/entities/Order'; + +@Component({ + template: ` +
+ {{ 'CUSTOMERS_VIEW.ORDERS_POP_UP.COMPLATED' | translate }} + {{ order.isCompleted ? ' ✔' : ' ✘' }} +
+
+ {{ 'CUSTOMERS_VIEW.ORDERS_POP_UP.PAID' | translate + }}{{ order.isPaid ? ' ✔' : ' ✘' }} +
+ `, +}) +export class StatusComponent implements ViewCell, OnInit { + value: string | number; + rowData: any; + order: Order; + + constructor() {} + + ngOnInit(): void { + this.order = this.rowData.order; + } +} diff --git a/packages/merchant-tablet-ionic/src/components/file-uploader/file-uploader.component.html b/packages/merchant-tablet-ionic/src/components/file-uploader/file-uploader.component.html new file mode 100644 index 0000000..6253024 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/components/file-uploader/file-uploader.component.html @@ -0,0 +1,47 @@ + + + + + + + + + + diff --git a/packages/merchant-tablet-ionic/src/components/file-uploader/file-uploader.component.scss b/packages/merchant-tablet-ionic/src/components/file-uploader/file-uploader.component.scss new file mode 100644 index 0000000..b7556c8 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/components/file-uploader/file-uploader.component.scss @@ -0,0 +1,24 @@ +.file-uploader-container { + input { + border-bottom-right-radius: 0; + border-top-right-radius: 0; + } + button { + border-bottom-left-radius: 0; + border-top-left-radius: 0; + } + + .button-full-space { + height: 100% !important; + } + + .ion-input-custom-style { + border-bottom: 1px solid #dedede; + text-align: left; + background: white; + } + + input.native-input.sc-ion-input-md { + padding: 10px !important; + } +} diff --git a/packages/merchant-tablet-ionic/src/components/file-uploader/file-uploader.component.ts b/packages/merchant-tablet-ionic/src/components/file-uploader/file-uploader.component.ts new file mode 100644 index 0000000..844bbd6 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/components/file-uploader/file-uploader.component.ts @@ -0,0 +1,220 @@ +import { + Component, + Input, + Output, + EventEmitter, + ViewChild, + OnInit, +} from '@angular/core'; +import { FileUploader, FileUploaderOptions, FileItem } from 'ng2-file-upload'; +import { environment } from 'environments/environment'; +import { TranslateService } from '@ngx-translate/core'; +import { ActionSheetController } from '@ionic/angular'; +import { Camera, CameraOptions } from '@ionic-native/camera/ngx'; +import { IProductImage } from '@modules/server.common/interfaces/IProduct'; +import { ProductLocalesService } from '@modules/client.common.angular2/locale/product-locales.service'; +import { NgModel } from '@angular/forms'; + +@Component({ + selector: 'e-cu-file-uploader', + templateUrl: './file-uploader.component.html', + styleUrls: ['./file-uploader.component.scss'], +}) +export class FileUploaderComponent implements OnInit { + @ViewChild('shownInput', { static: true }) + shownInput: NgModel; + + @Input() + labelText: string; + @Input() + name: string; + @Input() + buttonFullSpace: boolean; + @Input() + fileUrl: string; + @Input() + itemMode: string; + @Input() + inputCustomStyle: boolean; + + @Output() + uploadedImgUrl: EventEmitter = new EventEmitter(); + @Output() + uploadedImgObj: EventEmitter = new EventEmitter< + IProductImage + >(); + + uploader: FileUploader; + + private PREFIX: string = 'FILE_UPLOADER.'; + private DRAG_AND_DROB: string = 'DRAG_AND_DROP_FILE_HERE'; + private PICTURE_URL: string = 'PICTURE_URL'; + + constructor( + private translateService: TranslateService, + private actionSheetCtrl: ActionSheetController, + private camera: Camera, + public readonly localeTranslateService: ProductLocalesService + ) {} + + get isBrowser() { + return localStorage.getItem('_platform') === 'browser'; + } + + get dragAndDrob() { + return this._translate(this.PREFIX + this.DRAG_AND_DROB); + } + + get pictureURL() { + return this._translate(this.PREFIX + this.PICTURE_URL); + } + + get currentLocale() { + return this.localeTranslateService.currentLocale; + } + + ngOnInit(): void { + this._uploaderConfig(); + } + + imageUrlChanged() { + if (this.uploader.queue.length > 0) { + this.uploader.queue[this.uploader.queue.length - 1].upload(); + } else { + this.uploadedImgUrl.emit(this.fileUrl); + } + + this.uploader.onSuccessItem = ( + item: any, + response: string, + status: number + ) => { + const data = JSON.parse(response); + this.fileUrl = data.url; + const locale = this.currentLocale; + const width = data.width; + const height = data.height; + const orientation = width !== height ? (width > height ? 2 : 1) : 0; + const url = data.url; + + const newImage = { + locale, + url, + width, + height, + orientation, + }; + + this.uploadedImgUrl.emit(data.url); + this.uploadedImgObj.emit(newImage); + }; + } + + async presentActionSheet() { + const actionSheet = await this.actionSheetCtrl.create({ + header: 'Select Image Source', + buttons: [ + { + text: 'Load from Library', + handler: () => { + this._takePicture( + this.camera.PictureSourceType.PHOTOLIBRARY + ); + }, + }, + { + text: 'Use Camera', + handler: () => { + this._takePicture(this.camera.PictureSourceType.CAMERA); + }, + }, + { text: 'Cancel', role: 'cancel' }, + ], + }); + await actionSheet.present(); + } + + private _uploaderConfig() { + const uploaderOptions: FileUploaderOptions = { + url: environment.API_FILE_UPLOAD_URL, + + isHTML5: true, + removeAfterUpload: true, + headers: [ + { + name: 'X-Requested-With', + value: 'XMLHttpRequest', + }, + ], + }; + this.uploader = new FileUploader(uploaderOptions); + + this.uploader.onBuildItemForm = ( + fileItem: any, + form: FormData + ): any => { + form.append('upload_preset', 'everbie-products-images'); + let tags = 'myphotoalbum'; + if (this.name) { + form.append('context', `photo=${this.name}`); + tags = `myphotoalbum,${this.name}`; + } + + form.append('folder', 'angular_sample'); + form.append('tags', tags); + form.append('file', fileItem); + + fileItem.withCredentials = false; + return { fileItem, form }; + }; + } + + private _translate(key: string) { + let translationResult = ''; + + this.translateService.get(key).subscribe((res) => { + translationResult = res; + }); + return translationResult; + } + + private _takePicture(sourceType: number) { + const options: CameraOptions = { + quality: 50, + destinationType: this.camera.DestinationType.DATA_URL, + encodingType: this.camera.EncodingType.JPEG, + mediaType: this.camera.MediaType.PICTURE, + correctOrientation: true, + sourceType, + }; + + this.camera.getPicture(options).then( + async (imageData) => { + const base64Image = 'data:image/jpeg;base64,' + imageData; + const file = await this._urltoFile( + base64Image, + this._createFileName(), + 'image/jpeg' + ); + const fileItem = new FileItem(this.uploader, file, {}); + this.uploader.queue.push(fileItem); + this.imageUrlChanged(); + }, + (err) => {} + ); + } + + private _urltoFile(url, filename, mimeType) { + return fetch(url) + .then(function (res) { + return res.arrayBuffer(); + }) + .then(function (buf) { + return new File([buf], filename, { type: mimeType }); + }); + } + + private _createFileName() { + return new Date().getTime() + '.jpg'; + } +} diff --git a/packages/merchant-tablet-ionic/src/components/file-uploader/file-uploader.module.ts b/packages/merchant-tablet-ionic/src/components/file-uploader/file-uploader.module.ts new file mode 100644 index 0000000..8f54974 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/components/file-uploader/file-uploader.module.ts @@ -0,0 +1,20 @@ +import { NgModule, Input, OnInit } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { IonicModule } from '@ionic/angular'; +import { TranslateModule } from '@ngx-translate/core'; +import { FileUploadModule } from 'ng2-file-upload'; +import { FileUploaderComponent } from './file-uploader.component'; +import { FormsModule } from '@angular/forms'; + +@NgModule({ + imports: [ + CommonModule, + IonicModule, + FormsModule, + TranslateModule.forChild(), + FileUploadModule, + ], + exports: [FileUploaderComponent], + declarations: [FileUploaderComponent], +}) +export class FileUploaderModule {} diff --git a/packages/merchant-tablet-ionic/src/components/loading/loading.html b/packages/merchant-tablet-ionic/src/components/loading/loading.html new file mode 100644 index 0000000..d8653d0 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/components/loading/loading.html @@ -0,0 +1,3 @@ +
+ +
diff --git a/packages/merchant-tablet-ionic/src/components/loading/loading.scss b/packages/merchant-tablet-ionic/src/components/loading/loading.scss new file mode 100644 index 0000000..6ce9aec --- /dev/null +++ b/packages/merchant-tablet-ionic/src/components/loading/loading.scss @@ -0,0 +1,2 @@ +loading { +} diff --git a/packages/merchant-tablet-ionic/src/components/loading/loading.ts b/packages/merchant-tablet-ionic/src/components/loading/loading.ts new file mode 100644 index 0000000..d8cf992 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/components/loading/loading.ts @@ -0,0 +1,11 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'loading', + templateUrl: 'loading.html', +}) +export class LoadingComponent { + text: string; + + constructor() {} +} diff --git a/packages/merchant-tablet-ionic/src/components/menu/menu.component.html b/packages/merchant-tablet-ionic/src/components/menu/menu.component.html new file mode 100644 index 0000000..617c012 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/components/menu/menu.component.html @@ -0,0 +1,43 @@ + + +

+ + + {{ 'SIDE_MENU.TITLE' | translate }} + +

+
+ + + + +
+ + + +

+ + + {{ 'SIDE_MENU.TITLE' | translate }} + +

+
+ + + + +
diff --git a/packages/merchant-tablet-ionic/src/components/menu/menu.component.scss b/packages/merchant-tablet-ionic/src/components/menu/menu.component.scss new file mode 100644 index 0000000..e45a765 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/components/menu/menu.component.scss @@ -0,0 +1,24 @@ +ion-menu { + ion-title { + text-align: center; + } + + ion-item-divider { + visibility: visible; + ion-icon { + margin-right: 10px; + } + } + + ion-item { + visibility: visible; + ion-icon { + color: black; + margin-right: 15px; + } + } + + ion-header { + position: relative; + } +} diff --git a/packages/merchant-tablet-ionic/src/components/menu/menu.component.spec.ts b/packages/merchant-tablet-ionic/src/components/menu/menu.component.spec.ts new file mode 100644 index 0000000..ef3873a --- /dev/null +++ b/packages/merchant-tablet-ionic/src/components/menu/menu.component.spec.ts @@ -0,0 +1,24 @@ +import 'jasmine'; +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { MenuComponent } from './menu.component'; + +describe('MenuComponent', () => { + let component: MenuComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [MenuComponent], + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(MenuComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/packages/merchant-tablet-ionic/src/components/menu/menu.component.ts b/packages/merchant-tablet-ionic/src/components/menu/menu.component.ts new file mode 100644 index 0000000..f431180 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/components/menu/menu.component.ts @@ -0,0 +1,44 @@ +import { Component, OnDestroy } from '@angular/core'; +import { Store } from 'services/store.service'; +import { TranslateService, LangChangeEvent } from '@ngx-translate/core'; +import { takeUntil } from 'rxjs/operators'; +import { Subject } from 'rxjs'; +import { MenuController } from '@ionic/angular'; + +@Component({ + selector: 'e-cu-menu', + templateUrl: './menu.component.html', + styleUrls: ['./menu.component.scss'], +}) +export class MenuComponent implements OnDestroy { + private ngDestroy$ = new Subject(); + + constructor( + private store: Store, + private menuCtrl: MenuController, + private translateService: TranslateService + ) { + this.translateService.onLangChange + .pipe(takeUntil(this.ngDestroy$)) + .subscribe((event: LangChangeEvent) => { + if (event.lang === 'he-IL') { + this.menuCtrl.enable(true, 'rtl'); + this.menuCtrl.enable(false, 'ltr'); + } else { + this.menuCtrl.enable(true, 'ltr'); + this.menuCtrl.enable(false, 'rtl'); + } + }); + } + + get maintenanceMode() { + return this.store.maintenanceMode; + } + + menuOpened() {} + + ngOnDestroy() { + this.ngDestroy$.next(); + this.ngDestroy$.complete(); + } +} diff --git a/packages/merchant-tablet-ionic/src/components/menu/menu.module.ts b/packages/merchant-tablet-ionic/src/components/menu/menu.module.ts new file mode 100644 index 0000000..6786526 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/components/menu/menu.module.ts @@ -0,0 +1,19 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { IonicModule } from '@ionic/angular'; +import { MenuComponent } from './menu.component'; +import { RouterModule } from '@angular/router'; +import { TranslateModule } from '@ngx-translate/core'; +import { SubMenuComponent } from './submenu/submenu.component'; + +@NgModule({ + imports: [ + CommonModule, + RouterModule, + IonicModule, + TranslateModule.forChild(), + ], + exports: [MenuComponent, SubMenuComponent], + declarations: [MenuComponent, SubMenuComponent], +}) +export class MenuModule {} diff --git a/packages/merchant-tablet-ionic/src/components/menu/submenu/submenu.component.html b/packages/merchant-tablet-ionic/src/components/menu/submenu/submenu.component.html new file mode 100644 index 0000000..e150cfd --- /dev/null +++ b/packages/merchant-tablet-ionic/src/components/menu/submenu/submenu.component.html @@ -0,0 +1,113 @@ + + + + + + {{ + 'SIDE_MENU.GROUPS.NO_TITLE.ITEMS.WAREHOUSE' | translate + }} + + + + + {{ + 'CUSTOMERS_VIEW.CUSTOMERS' | translate + }} + + + + + {{ + 'CARRIERS_VIEW.CARRIERS' | translate + }} + + + + + {{ + 'CARRIERS_VIEW.PROMOTIONS.PROMOTIONS' | translate + }} + + + + {{ + 'SIDE_MENU.GROUPS.INFO.DIVER_TITLE' | translate + }} + + + + + {{ + 'SIDE_MENU.GROUPS.INFO.ITEMS.ABOUT_US' | translate + }} + + + + + {{ + 'SIDE_MENU.GROUPS.INFO.ITEMS.TERMS_OF_USE' | translate + }} + + + + + {{ + 'SIDE_MENU.GROUPS.SETTINGS.SETTINGS' | translate + }} + + + + + {{ + 'SIDE_MENU.GROUPS.SETTINGS.ITEMS.LANGUAGE' | translate + }} + + + + + {{ + 'SIDE_MENU.GROUPS.SETTINGS.ITEMS.SIGN_OUT' | translate + }} + + + + diff --git a/packages/merchant-tablet-ionic/src/components/menu/submenu/submenu.component.scss b/packages/merchant-tablet-ionic/src/components/menu/submenu/submenu.component.scss new file mode 100644 index 0000000..1d58271 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/components/menu/submenu/submenu.component.scss @@ -0,0 +1,7 @@ +ion-item { + visibility: visible; + ion-icon { + color: black; + margin-right: 15px; + } +} diff --git a/packages/merchant-tablet-ionic/src/components/menu/submenu/submenu.component.ts b/packages/merchant-tablet-ionic/src/components/menu/submenu/submenu.component.ts new file mode 100644 index 0000000..a573fbf --- /dev/null +++ b/packages/merchant-tablet-ionic/src/components/menu/submenu/submenu.component.ts @@ -0,0 +1,8 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'e-cu-submenu', + templateUrl: './submenu.component.html', + styleUrls: ['./submenu.component.scss'], +}) +export class SubMenuComponent {} diff --git a/packages/merchant-tablet-ionic/src/components/order-canceled/order-canceled.html b/packages/merchant-tablet-ionic/src/components/order-canceled/order-canceled.html new file mode 100644 index 0000000..49f5b18 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/components/order-canceled/order-canceled.html @@ -0,0 +1,32 @@ +
+
+ + +
+ + +
+
+ + +
+
+
+ +
+

+ {{ 'ORDER_CANCELED_COMPONENT.CANCELED' | translate }} +

+ +
+
+ {{ 'ORDER_CANCELED_COMPONENT.CREATED_AT' | translate }} +
{{ order.createdAt | date: 'HH:mm' }}
+
+
+
+
+
diff --git a/packages/merchant-tablet-ionic/src/components/order-canceled/order-canceled.scss b/packages/merchant-tablet-ionic/src/components/order-canceled/order-canceled.scss new file mode 100644 index 0000000..834971c --- /dev/null +++ b/packages/merchant-tablet-ionic/src/components/order-canceled/order-canceled.scss @@ -0,0 +1,2 @@ +order-canceled { +} diff --git a/packages/merchant-tablet-ionic/src/components/order-canceled/order-canceled.ts b/packages/merchant-tablet-ionic/src/components/order-canceled/order-canceled.ts new file mode 100644 index 0000000..374f682 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/components/order-canceled/order-canceled.ts @@ -0,0 +1,22 @@ +import { Component, Input } from '@angular/core'; +import Order from '@modules/server.common/entities/Order'; +import { ProductLocalesService } from '@modules/client.common.angular2/locale/product-locales.service'; +import { ILocaleMember } from '@modules/server.common/interfaces/ILocale'; + +@Component({ + selector: 'order-canceled', + templateUrl: 'order-canceled.html', +}) +export class OrderCanceledComponent { + @Input() + getWarehouseStatus: Function; + + @Input() + order: Order; + + constructor(private _translateProductLocales: ProductLocalesService) {} + + protected localeTranslate(member: ILocaleMember[]): string { + return this._translateProductLocales.getTranslate(member); + } +} diff --git a/packages/merchant-tablet-ionic/src/components/order-control-buttons/order-control-buttons.html b/packages/merchant-tablet-ionic/src/components/order-control-buttons/order-control-buttons.html new file mode 100644 index 0000000..9c323d1 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/components/order-control-buttons/order-control-buttons.html @@ -0,0 +1,80 @@ + diff --git a/packages/merchant-tablet-ionic/src/components/order-control-buttons/order-control-buttons.scss b/packages/merchant-tablet-ionic/src/components/order-control-buttons/order-control-buttons.scss new file mode 100644 index 0000000..8413bfe --- /dev/null +++ b/packages/merchant-tablet-ionic/src/components/order-control-buttons/order-control-buttons.scss @@ -0,0 +1,19 @@ +.order-control-buttons { + .button:hover { + color: white !important; + opacity: 0.7; + } + + .button { + color: white !important; + + padding: 0.2rem 1.2rem; + font-size: 1.2rem; + } + + .button.disabled { + cursor: default; + opacity: 0.4; + pointer-events: none; + } +} diff --git a/packages/merchant-tablet-ionic/src/components/order-control-buttons/order-control-buttons.ts b/packages/merchant-tablet-ionic/src/components/order-control-buttons/order-control-buttons.ts new file mode 100644 index 0000000..8cb3de0 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/components/order-control-buttons/order-control-buttons.ts @@ -0,0 +1,49 @@ +import { Component, Input, OnInit } from '@angular/core'; +import { OrderRouter } from '@modules/client.common.angular2/routers/order-router.service'; +import DeliveryType from '@modules/server.common/enums/DeliveryType'; +import { Store } from 'services/store.service'; +import { WarehousesService } from 'services/warehouses.service'; +import { map } from 'rxjs/operators'; + +@Component({ + selector: 'order-control-buttons', + templateUrl: 'order-control-buttons.html', + styleUrls: ['./order-control-buttons.scss'], +}) +export class OrderControlButtonsComponent implements OnInit { + @Input() + orderId: string; + + @Input() + warehouseStatus: number; + + @Input() + carrierStatus: number; + + @Input() + onUpdateWarehouseStatus: any; + + @Input() + orderType: DeliveryType; + + orderTypeDelivery: DeliveryType = DeliveryType.Delivery; + orderTypeTakeaway: DeliveryType = DeliveryType.Takeaway; + + ordersShortProcess: boolean; + _storeID: string; + constructor( + private orderRouter: OrderRouter, + private store: Store, + private warehousesService: WarehousesService + ) {} + + ngOnInit() { + this._storeID = this.store.warehouseId; + this.warehousesService + .getWarehouseOrderProcess(this._storeID) + .pipe(map((store) => store.ordersShortProcess)) + .subscribe((isShortProcess) => { + this.ordersShortProcess = isShortProcess; + }); + } +} diff --git a/packages/merchant-tablet-ionic/src/components/order-delivered/order-delivered.html b/packages/merchant-tablet-ionic/src/components/order-delivered/order-delivered.html new file mode 100644 index 0000000..3d8e9ab --- /dev/null +++ b/packages/merchant-tablet-ionic/src/components/order-delivered/order-delivered.html @@ -0,0 +1,38 @@ +
+
+ + +
+ +
+
+ + +
+
+
+ +
+

+ {{ 'ORDER_DELIVERED_COMPONENT.DELIVERED' | translate }} +

+ +
+
+ {{ 'ORDER_DELIVERED_COMPONENT.CREATED_AT' | translate }} + {{ order.createdAt | date: 'HH:mm' }} + + {{ 'ORDER_DELIVERED_COMPONENT.DELIVERED_AT' | translate }} + {{ order.deliveryTime | date: 'HH:mm' }} +
+
+
+
+
diff --git a/packages/merchant-tablet-ionic/src/components/order-delivered/order-delivered.scss b/packages/merchant-tablet-ionic/src/components/order-delivered/order-delivered.scss new file mode 100644 index 0000000..1bb58d4 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/components/order-delivered/order-delivered.scss @@ -0,0 +1,2 @@ +order-delivered { +} diff --git a/packages/merchant-tablet-ionic/src/components/order-delivered/order-delivered.ts b/packages/merchant-tablet-ionic/src/components/order-delivered/order-delivered.ts new file mode 100644 index 0000000..3e7eff4 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/components/order-delivered/order-delivered.ts @@ -0,0 +1,22 @@ +import { Component, Input } from '@angular/core'; +import Order from '@modules/server.common/entities/Order'; +import { ProductLocalesService } from '@modules/client.common.angular2/locale/product-locales.service'; +import { ILocaleMember } from '@modules/server.common/interfaces/ILocale'; + +@Component({ + selector: 'order-delivered', + templateUrl: 'order-delivered.html', +}) +export class OrderDeliveredComponent { + @Input() + getWarehouseStatus: (status: string) => void; + + @Input() + order: Order; + + constructor(private _translateProductLocales: ProductLocalesService) {} + + protected localeTranslate(member: ILocaleMember[]): string { + return this._translateProductLocales.getTranslate(member); + } +} diff --git a/packages/merchant-tablet-ionic/src/components/order-delivery-problem/order-delivery-problem.html b/packages/merchant-tablet-ionic/src/components/order-delivery-problem/order-delivery-problem.html new file mode 100644 index 0000000..11f5267 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/components/order-delivery-problem/order-delivery-problem.html @@ -0,0 +1,40 @@ +
+
+ + +
+ + +
+
+ + +
+
+
+ +
+

+ {{ 'ORDER_DELIVERY_PROBLEM_COMPONENT.DELIVERY_ISSUES' | + translate }} +

+ +
+
+ {{ 'ORDER_DELIVERY_PROBLEM_COMPONENT.CREATED_AT' | translate + }} + {{ order.createdAt | date: 'HH:mm' }} +
+ ( {{ order.createdAt | amTimeAgo }}) +
+
+
+
+
diff --git a/packages/merchant-tablet-ionic/src/components/order-delivery-problem/order-delivery-problem.scss b/packages/merchant-tablet-ionic/src/components/order-delivery-problem/order-delivery-problem.scss new file mode 100644 index 0000000..efd3797 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/components/order-delivery-problem/order-delivery-problem.scss @@ -0,0 +1,2 @@ +order-delivery-problem { +} diff --git a/packages/merchant-tablet-ionic/src/components/order-delivery-problem/order-delivery-problem.ts b/packages/merchant-tablet-ionic/src/components/order-delivery-problem/order-delivery-problem.ts new file mode 100644 index 0000000..9bf2ea1 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/components/order-delivery-problem/order-delivery-problem.ts @@ -0,0 +1,22 @@ +import { Component, Input } from '@angular/core'; +import Order from '@modules/server.common/entities/Order'; +import { ProductLocalesService } from '@modules/client.common.angular2/locale/product-locales.service'; +import { ILocaleMember } from '@modules/server.common/interfaces/ILocale'; + +@Component({ + selector: 'order-delivery-problem', + templateUrl: 'order-delivery-problem.html', +}) +export class OrderDeliveryProblemComponent { + @Input() + getWarehouseStatus: (status: any) => void; + + @Input() + order: Order; + + constructor(private _translateProductLocales: ProductLocalesService) {} + + protected localeTranslate(member: ILocaleMember[]): string { + return this._translateProductLocales.getTranslate(member); + } +} diff --git a/packages/merchant-tablet-ionic/src/components/order-map-popup/order-map-popup.html b/packages/merchant-tablet-ionic/src/components/order-map-popup/order-map-popup.html new file mode 100644 index 0000000..2a16e6a --- /dev/null +++ b/packages/merchant-tablet-ionic/src/components/order-map-popup/order-map-popup.html @@ -0,0 +1,43 @@ +
+

+ {{ "CARRIERS_VIEW.TRACK_ORDER" | translate }} + +

+
+
+
+ + {{ "CARRIERS_VIEW.STORE" | translate }} - + {{ getfullAddress(warehouse.geoLocation) }} +
+
+
+
+ + {{ "CARRIERS_VIEW.CUSTOMER" | translate }} - + {{ getfullAddress(order.user.geoLocation) }} + +
+
+
+
+ {{ "CARRIERS_VIEW.CARRIER" | translate }} - + {{ getfullAddress(order.carrier.geoLocation) }} + +
+
+
+
+
diff --git a/packages/merchant-tablet-ionic/src/components/order-map-popup/order-map-popup.module.ts b/packages/merchant-tablet-ionic/src/components/order-map-popup/order-map-popup.module.ts new file mode 100644 index 0000000..f500648 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/components/order-map-popup/order-map-popup.module.ts @@ -0,0 +1,12 @@ +import { NgModule } from '@angular/core'; +import { TranslateModule } from '@ngx-translate/core'; +import { OrderMapPopupPage } from './order-map-popup'; +import { IonicModule } from '@ionic/angular'; +import { CommonModule } from '@angular/common'; + +@NgModule({ + declarations: [OrderMapPopupPage], + imports: [TranslateModule.forChild(), IonicModule, CommonModule], + exports: [OrderMapPopupPage] +}) +export class OrderMapPopupPageModule {} diff --git a/packages/merchant-tablet-ionic/src/components/order-map-popup/order-map-popup.scss b/packages/merchant-tablet-ionic/src/components/order-map-popup/order-map-popup.scss new file mode 100644 index 0000000..67d023d --- /dev/null +++ b/packages/merchant-tablet-ionic/src/components/order-map-popup/order-map-popup.scss @@ -0,0 +1,28 @@ +.order-map-component { + background-color: #fff; + height: 100%; + display: flex !important; + flex-direction: column; + + .addrInfo { + height: 120px; + + .row { + padding: 0 15px 10px 15px; + line-height: 1; + img { + width: 24px; + height: 24px; + margin-bottom: -2px; + } + } + + .text { + font-style: italic; + } + } + + .map { + min-height: 310px !important; + } +} diff --git a/packages/merchant-tablet-ionic/src/components/order-map-popup/order-map-popup.ts b/packages/merchant-tablet-ionic/src/components/order-map-popup/order-map-popup.ts new file mode 100644 index 0000000..494ca2f --- /dev/null +++ b/packages/merchant-tablet-ionic/src/components/order-map-popup/order-map-popup.ts @@ -0,0 +1,130 @@ +import { Component, ViewChild, ElementRef, Input, OnInit } from '@angular/core'; + +import Order from '@modules/server.common/entities/Order'; +import { WarehouseRouter } from '@modules/client.common.angular2/routers/warehouse-router.service'; +import Warehouse from '@modules/server.common/entities/Warehouse'; +import { first } from 'rxjs/operators'; +import { environment } from '../../../src/environments/environment'; +import { + LoadingController, + ActionSheetController, + ModalController, +} from '@ionic/angular'; + +declare var google: any; + +@Component({ + selector: 'order-map-popup', + templateUrl: 'order-map-popup.html', + styleUrls: ['./order-map-popup.scss'], +}) +export class OrderMapPopupPage implements OnInit { + @ViewChild('gmap', { static: true }) + gmapElement: ElementRef; + + @Input() + order: Order; + + map: google.maps.Map; + myLatLng = { lat: 0, lng: 0 }; + + warehouse: Warehouse; + + marker: any; + userMarker: any; + warehouseMarker: any; + + merchantIcon = environment.MAP_MERCHANT_ICON_LINK; + userIcon = environment.MAP_USER_ICON_LINK; + carrierIcon = environment.MAP_CARRIER_ICON_LINK; + + constructor( + public loadingCtrl: LoadingController, + public actionSheetCtrl: ActionSheetController, + public warehouseRouter: WarehouseRouter, + public modalController: ModalController + ) {} + + ngOnInit(): void { + if (this.order) { + this.loadWarehouse(); + } + this.showMap(); + } + + loadMap() { + if (this.order && this.warehouse) { + const user = this.order.user; + const warehouse = this.warehouse; + const carrier = this.order.carrier; + const warehouseIcon = this.merchantIcon; + const userIcon = this.userIcon; + + const carierIcon = this.carrierIcon; + + const [cLng, cLat] = carrier['geoLocation'].loc.coordinates; + this.marker = this.addMarker( + new google.maps.LatLng(cLat, cLng), + this.map, + carierIcon + ); + + const [uLng, uLat] = user.geoLocation.loc.coordinates; + this.userMarker = this.addMarker( + new google.maps.LatLng(uLat, uLng), + this.map, + userIcon + ); + + const [wLng, wLat] = warehouse['geoLocation'].loc.coordinates; + this.warehouseMarker = this.addMarker( + new google.maps.LatLng(wLat, wLng), + this.map, + warehouseIcon + ); + + const bounds = new google.maps.LatLngBounds(); + bounds.extend(this.marker.getPosition()); + bounds.extend(this.warehouseMarker.getPosition()); + bounds.extend(this.userMarker.getPosition()); + this.map.fitBounds(bounds); + } + } + + cancelModal() { + this.modalController.dismiss(); + } + + showMap() { + const mapProp = { + center: new google.maps.LatLng(42.642941, 23.334149), + zoom: 15, + mapTypeId: google.maps.MapTypeId.ROADMAP, + }; + this.map = new google.maps.Map(this.gmapElement.nativeElement, mapProp); + } + + addMarker(position, map, icon) { + return new google.maps.Marker({ + position, + map, + icon, + }); + } + + getfullAddress(geoLocation) { + return ( + `${geoLocation.city}, ${geoLocation.streetAddress} ` + + `${geoLocation.house}` + ); + } + + private async loadWarehouse() { + this.warehouse = await this.warehouseRouter + .get(this.order.warehouseId, false) + .pipe(first()) + .toPromise(); + + this.loadMap(); + } +} diff --git a/packages/merchant-tablet-ionic/src/components/order-warehouse-preparation-problem/order-warehouse-preparation-problem.html b/packages/merchant-tablet-ionic/src/components/order-warehouse-preparation-problem/order-warehouse-preparation-problem.html new file mode 100644 index 0000000..49681ed --- /dev/null +++ b/packages/merchant-tablet-ionic/src/components/order-warehouse-preparation-problem/order-warehouse-preparation-problem.html @@ -0,0 +1,30 @@ +
+
+ + +
+ +
+
+ +
+
+
+ +
+

+ {{ getWarehouseStatus.call(null, order.warehouseStatus) | + translate }} +

+ +
+
+ {{ + 'ORDER_WAREHOUSE_PREPARATION_PROBLEM_COMPONENT.CREATED_AT' | + translate }} +
{{ order.createdAt | date: 'HH:mm' }}
+
+
+
+
+
diff --git a/packages/merchant-tablet-ionic/src/components/order-warehouse-preparation-problem/order-warehouse-preparation-problem.scss b/packages/merchant-tablet-ionic/src/components/order-warehouse-preparation-problem/order-warehouse-preparation-problem.scss new file mode 100644 index 0000000..c1fda18 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/components/order-warehouse-preparation-problem/order-warehouse-preparation-problem.scss @@ -0,0 +1,2 @@ +order-warehouse-preparation-problem { +} diff --git a/packages/merchant-tablet-ionic/src/components/order-warehouse-preparation-problem/order-warehouse-preparation-problem.ts b/packages/merchant-tablet-ionic/src/components/order-warehouse-preparation-problem/order-warehouse-preparation-problem.ts new file mode 100644 index 0000000..782898e --- /dev/null +++ b/packages/merchant-tablet-ionic/src/components/order-warehouse-preparation-problem/order-warehouse-preparation-problem.ts @@ -0,0 +1,22 @@ +import { Component, Input } from '@angular/core'; +import Order from '@modules/server.common/entities/Order'; +import { ProductLocalesService } from '@modules/client.common.angular2/locale/product-locales.service'; +import { ILocaleMember } from '@modules/server.common/interfaces/ILocale'; + +@Component({ + selector: 'order-warehouse-preparation-problem', + templateUrl: 'order-warehouse-preparation-problem.html', +}) +export class OrderWarehousePreparationProblemComponent { + @Input() + getWarehouseStatus: (status: any) => void; + + @Input() + order: Order; + + constructor(private _translateProductLocales: ProductLocalesService) {} + + protected localeTranslate(member: ILocaleMember[]): string { + return this._translateProductLocales.getTranslate(member); + } +} diff --git a/packages/merchant-tablet-ionic/src/components/order-with-carrier/order-with-carrier.html b/packages/merchant-tablet-ionic/src/components/order-with-carrier/order-with-carrier.html new file mode 100644 index 0000000..dd88a7d --- /dev/null +++ b/packages/merchant-tablet-ionic/src/components/order-with-carrier/order-with-carrier.html @@ -0,0 +1,51 @@ +
+
+ + +
+ + +
+
+ + + +
+ +
+ + +
+
+
+ +
+

+ {{ getWarehouseStatus.call(null, order.warehouseStatus) | + translate }} +

+ +
+
+ {{ 'ORDER_WITH_CARRIER_COMPONENT.CREATED_AT' | translate }} + {{ order.createdAt | date: 'HH:mm' }} +
+ ({{ order.createdAt | amTimeAgo }}) +
+
+
+
+
diff --git a/packages/merchant-tablet-ionic/src/components/order-with-carrier/order-with-carrier.scss b/packages/merchant-tablet-ionic/src/components/order-with-carrier/order-with-carrier.scss new file mode 100644 index 0000000..c0e3eba --- /dev/null +++ b/packages/merchant-tablet-ionic/src/components/order-with-carrier/order-with-carrier.scss @@ -0,0 +1,6 @@ +.order-with-carrier { + order-control-buttons { + margin: auto; + margin-bottom: 10px; + } +} diff --git a/packages/merchant-tablet-ionic/src/components/order-with-carrier/order-with-carrier.ts b/packages/merchant-tablet-ionic/src/components/order-with-carrier/order-with-carrier.ts new file mode 100644 index 0000000..d296f45 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/components/order-with-carrier/order-with-carrier.ts @@ -0,0 +1,33 @@ +import { Component, Input } from '@angular/core'; +import Order from '@modules/server.common/entities/Order'; +import OrderWarehouseStatus from '@modules/server.common/enums/OrderWarehouseStatus'; +import { ILocaleMember } from '@modules/server.common/interfaces/ILocale'; +import { ProductLocalesService } from '@modules/client.common.angular2/locale/product-locales.service'; + +@Component({ + selector: 'order-with-carrier', + templateUrl: 'order-with-carrier.html', + styleUrls: ['./order-with-carrier.scss'], +}) +export class OrderWithCarrierComponent { + @Input() + getWarehouseStatus: (status: any) => void; + + @Input() + order: Order; + + @Input() + onUpdateWarehouseStatus: any; + + constructor(private _translateProductLocales: ProductLocalesService) {} + + isGivenToCarrier() { + return ( + this.order.warehouseStatus === OrderWarehouseStatus.GivenToCarrier + ); + } + + protected localeTranslate(member: ILocaleMember[]): string { + return this._translateProductLocales.getTranslate(member); + } +} diff --git a/packages/merchant-tablet-ionic/src/components/order-without-carrier/order-without-carrier.html b/packages/merchant-tablet-ionic/src/components/order-without-carrier/order-without-carrier.html new file mode 100644 index 0000000..655da3d --- /dev/null +++ b/packages/merchant-tablet-ionic/src/components/order-without-carrier/order-without-carrier.html @@ -0,0 +1,58 @@ +
+
+ + +
+ +
+
+ + +
+ +
+ + +
+
+ +
+

+ {{ + 'ORDER_WITHOUT_CARRIER_COMPONENT.CANT_PROCESSING_WITHOUT_PRODUCTS' + | translate }} +

+
+
+ +
+

+ {{ getWarehouseStatus.call(null, order.warehouseStatus) | + translate }} +

+ +
+
+ {{ 'ORDER_WITHOUT_CARRIER_COMPONENT.CREATED_AT' | translate + }} + {{ order.createdAt | date: 'HH:mm' }} + +
+ + + ({{ order.createdAt | amTimeAgo }}) + +
+
+
+
+
diff --git a/packages/merchant-tablet-ionic/src/components/order-without-carrier/order-without-carrier.scss b/packages/merchant-tablet-ionic/src/components/order-without-carrier/order-without-carrier.scss new file mode 100644 index 0000000..8554df9 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/components/order-without-carrier/order-without-carrier.scss @@ -0,0 +1,21 @@ +.order-without-carrier { + order-control-buttons { + margin: auto; + margin-bottom: 10px; + } + .no-processing { + width: 100%; + height: 160px; + display: flex !important; + align-content: center !important; + align-items: center !important; + + .no-processing-text { + margin: 0; + width: 100%; + padding: 0 20px; + color: #bd4742; + text-align: center; + } + } +} diff --git a/packages/merchant-tablet-ionic/src/components/order-without-carrier/order-without-carrier.ts b/packages/merchant-tablet-ionic/src/components/order-without-carrier/order-without-carrier.ts new file mode 100644 index 0000000..c262343 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/components/order-without-carrier/order-without-carrier.ts @@ -0,0 +1,33 @@ +import { Component, Input } from '@angular/core'; +import { ILocaleMember } from '@modules/server.common/interfaces/ILocale'; +import Order from '@modules/server.common/entities/Order'; +import { ProductLocalesService } from '@modules/client.common.angular2/locale/product-locales.service'; + +@Component({ + selector: 'order-without-carrier', + templateUrl: 'order-without-carrier.html', + styleUrls: ['./order-without-carrier.scss'], +}) +export class OrderWithoutCarrierComponent { + @Input() + getWarehouseStatus: (status: any) => void; + + @Input() + order: Order; + + @Input() + onUpdateWarehouseStatus: any; + + constructor(private _translateProductLocales: ProductLocalesService) {} + + get hasProducts() { + if (this.order && this.order.products && this.order.products.length) { + return true; + } + return false; + } + + localeTranslate(member: ILocaleMember[]): string { + return this._translateProductLocales.getTranslate(member); + } +} diff --git a/packages/merchant-tablet-ionic/src/components/order/address.component.ts b/packages/merchant-tablet-ionic/src/components/order/address.component.ts new file mode 100644 index 0000000..baeb2c4 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/components/order/address.component.ts @@ -0,0 +1,44 @@ +import { Component, OnInit } from '@angular/core'; +import { ViewCell } from 'ng2-smart-table'; +import User from '@modules/server.common/entities/User'; + +@Component({ + styles: [ + ` + h1 { + font-weight: normal !importnat; + } + .address { + font-style: italic !important; + text-decoration: underline; + display: block !important; + } + `, + ], + template: ` + {{ user?.geoLocation.city }} + ({{ user?.geoLocation.postcode }}) + + {{ + getStoreFullAddress(user) + }} + `, +}) +export class AddressComponent implements ViewCell, OnInit { + value: string | number; + rowData: any; + user: User; + + ngOnInit(): void { + this.user = this.rowData.user; + } + + getStoreFullAddress(user: User) { + const geoLocation = user.geoLocation; + const fullAddress = `${geoLocation.city}, ${geoLocation.streetAddress} ${geoLocation.house}`; + return fullAddress; + } +} diff --git a/packages/merchant-tablet-ionic/src/components/order/choose-customer-option.component.html b/packages/merchant-tablet-ionic/src/components/order/choose-customer-option.component.html new file mode 100644 index 0000000..8de4616 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/components/order/choose-customer-option.component.html @@ -0,0 +1,16 @@ +
+
+ + + + + + + +
diff --git a/packages/merchant-tablet-ionic/src/components/order/choose-customer-option.component.ts b/packages/merchant-tablet-ionic/src/components/order/choose-customer-option.component.ts new file mode 100644 index 0000000..2232562 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/components/order/choose-customer-option.component.ts @@ -0,0 +1,17 @@ +import { Component, EventEmitter, Output } from '@angular/core'; + +@Component({ + selector: 'choose-customer-option', + styles: [ + 'button { padding: 3.5%; margin-top: 2%; background: none; border: 1px solid; margin-left: 2%; } button:hover { color: #bd4742; border-color: #bd4742; } .customer{ text-align: center; }', + ], + templateUrl: './choose-customer-option.component.html', +}) +export class ChooseCustomerOptionComponent { + @Output() + optionEmitter = new EventEmitter(); + + chooseOption(optionBit: number) { + this.optionEmitter.emit(optionBit); + } +} diff --git a/packages/merchant-tablet-ionic/src/components/order/make-order/+warehouse-order-modal/warehouse-order-input.component.ts b/packages/merchant-tablet-ionic/src/components/order/make-order/+warehouse-order-modal/warehouse-order-input.component.ts new file mode 100644 index 0000000..3a09d5c --- /dev/null +++ b/packages/merchant-tablet-ionic/src/components/order/make-order/+warehouse-order-modal/warehouse-order-input.component.ts @@ -0,0 +1,60 @@ +import { Component, EventEmitter, Input, Output } from '@angular/core'; +import { ViewCell } from 'ng2-smart-table'; + +@Component({ + styles: ['.order-input-wrapper { text-align: center; }'], + template: ` +
+ + + {{ productAmount }} + + +
+ `, +}) +export class WarehouseOrderInputComponent implements ViewCell { + @Input() + value; + + @Input() + rowData: any; + + @Output() + amount = new EventEmitter(); + + private _productAmount: number = 0; + + get warehouseAvailableProducts(): number { + return +this.value.available; + } + + get productId(): string { + return this.value.productId; + } + + get productAmount(): number { + return this._productAmount; + } + + set productAmount(amount: number) { + this._productAmount = amount; + this.amount.emit(amount); + } + + get warehouseHasAvailable(): boolean { + return this._productAmount < this.warehouseAvailableProducts; + } +} diff --git a/packages/merchant-tablet-ionic/src/components/order/make-order/+warehouse-order-modal/warehouse-order-modal.component.html b/packages/merchant-tablet-ionic/src/components/order/make-order/+warehouse-order-modal/warehouse-order-modal.component.html new file mode 100644 index 0000000..66adb1f --- /dev/null +++ b/packages/merchant-tablet-ionic/src/components/order/make-order/+warehouse-order-modal/warehouse-order-modal.component.html @@ -0,0 +1,31 @@ +
+
+
+ {{ TRANSLATE_PREFIXES.MAKE_ORDER | translate }} +
+ +
+ +
+
+ +
+ +
+ + +
diff --git a/packages/merchant-tablet-ionic/src/components/order/make-order/+warehouse-order-modal/warehouse-order-modal.component.scss b/packages/merchant-tablet-ionic/src/components/order/make-order/+warehouse-order-modal/warehouse-order-modal.component.scss new file mode 100644 index 0000000..3e7190a --- /dev/null +++ b/packages/merchant-tablet-ionic/src/components/order/make-order/+warehouse-order-modal/warehouse-order-modal.component.scss @@ -0,0 +1,34 @@ +.nb-card { + margin: 0; + + .nb-card-header { + border: none; + div.checkbox label { + cursor: pointer; + display: flex; + align-items: flex-start; + margin-bottom: 0; + width: 40%; + + input[type='checkbox'] { + zoom: 1.5; + margin-right: 2.5%; + } + } + } + + .nb-card-body { + padding: 0; + + ng2-smart-table { + text-align: center; + } + } + + .nb-card-footer { + button { + float: right; + margin-right: 2%; + } + } +} diff --git a/packages/merchant-tablet-ionic/src/components/order/make-order/+warehouse-order-modal/warehouse-order-modal.component.ts b/packages/merchant-tablet-ionic/src/components/order/make-order/+warehouse-order-modal/warehouse-order-modal.component.ts new file mode 100644 index 0000000..6d434eb --- /dev/null +++ b/packages/merchant-tablet-ionic/src/components/order/make-order/+warehouse-order-modal/warehouse-order-modal.component.ts @@ -0,0 +1,266 @@ +import { + Component, + EventEmitter, + Input, + OnDestroy, + OnInit, + Output, +} from '@angular/core'; +import { LocalDataSource } from 'ng2-smart-table'; +import { Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; + +import { WarehouseProductsRouter } from '@modules/client.common.angular2/routers/warehouse-products-router.service'; +import WarehouseProduct from '@modules/server.common/entities/WarehouseProduct'; +import { WarehouseOrderInputComponent } from './warehouse-order-input.component'; +import { IOrderCreateInputProduct } from '@modules/server.common/routers/IWarehouseOrdersRouter'; +import { TranslateService } from '@ngx-translate/core'; +import { ProductLocalesService } from '@modules/client.common.angular2/locale/product-locales.service'; +import { ILocaleMember } from '@modules/server.common/interfaces/ILocale'; + +@Component({ + selector: 'ea-warehouse-order-modal', + styleUrls: ['./warehouse-order-modal.component.scss'], + templateUrl: './warehouse-order-modal.component.html', +}) +export class WarehouseOrderModalComponent implements OnInit, OnDestroy { + @Input() + warehouseId: string; + + @Input() + showOrderAction: boolean = true; + + @Output() + makeOrderEmitter = new EventEmitter(); + + @Output() + isOrderAllowedEmitter = new EventEmitter(); + + settingsSmartTable: object; + sourceSmartTable = new LocalDataSource(); + + private _warehouseProducts: WarehouseProduct[] = []; + + private _orderProducts: IOrderCreateInputProduct[] = []; + + private _clearAvailableProductsFilter: boolean = false; + + private _ngDestroy$ = new Subject(); + + constructor( + private readonly _translateService: TranslateService, + private readonly _productLocaleService: ProductLocalesService, + private readonly _warehouseProductsRouter: WarehouseProductsRouter + ) {} + + get TRANSLATE_PREFIXES() { + const basePrefix = 'SHARED.WAREHOUSE.ORDER_MODAL'; + const smartTableTitlesPrefix = 'SMART_TABLE.TITLES'; + + return { + MAKE_ORDER: `${basePrefix}.MAKE_ORDER`, + ONLY_AVAILABLE: `${basePrefix}.ONLY_AVAILABLE`, + ORDER: `${basePrefix}.ORDER`, + SMART_TABLE: { + TITLES: { + IMG: `${basePrefix}.${smartTableTitlesPrefix}.IMG`, + PRODUCT: `${basePrefix}.${smartTableTitlesPrefix}.PRODUCT`, + PRICE: `${basePrefix}.${smartTableTitlesPrefix}.PRICE`, + AVAILABLE: `${basePrefix}.${smartTableTitlesPrefix}.AVAILABLE`, + AMOUNT: `${basePrefix}.${smartTableTitlesPrefix}.AMOUNT`, + }, + }, + }; + } + + get canOrder(): boolean { + return this._orderProducts.some((product) => product.count > 0); + } + + ngOnInit() { + this._loadSettingsSmartTable(); + this._loadWarehouseProducts(); + } + + ngOnDestroy() { + this._ngDestroy$.next(); + this._ngDestroy$.complete(); + } + + makeOrder() { + this.makeOrderEmitter.emit( + this._orderProducts.filter(({ count }) => count > 0) + ); + } + + toggleAvalableProducts() { + if (this._clearAvailableProductsFilter) { + this.sourceSmartTable.setFilter([]); + } else { + this.sourceSmartTable.setFilter([ + { + field: 'available', + search: '0', + filter: (element, valueToCompare) => { + const regex = /
([0-9]+)<\/div>/gm; + const productCount = +regex.exec(element)[1]; + + return productCount > +valueToCompare; + }, + }, + ]); + } + + this._clearAvailableProductsFilter = !this + ._clearAvailableProductsFilter; + } + + private _loadWarehouseProducts() { + this._warehouseProductsRouter + .get(this.warehouseId) + .pipe(takeUntil(this._ngDestroy$)) + .subscribe((p) => { + Object.assign(this._warehouseProducts, p); + this._loadDataSmartTable(); + }); + } + + private _loadDataSmartTable() { + this._orderProducts = this._warehouseProducts.map( + (wp: WarehouseProduct) => { + return { + productId: wp.productId, + count: 0, + }; + } + ); + + const productsData = this._warehouseProducts.map( + (wp: WarehouseProduct) => { + return { + img: ` + + `, + product: ` + ${this._getTranslate(wp.product['title'])} + `, + price: `${wp.price}$`, + available: ` +
${wp.count}
+ `, + amount: { productId: wp.productId, available: wp.count }, + }; + } + ); + + this.sourceSmartTable.setSort([ + { + field: 'available', + direction: 'desc', + compare: this._compareByAvailableProducts, + }, + ]); + this.sourceSmartTable.load(productsData); + } + + private _getTranslate(members: ILocaleMember[]): string { + return this._productLocaleService.getTranslate(members); + } + + private _compareByAvailableProducts(_, first, second) { + const regex = /
([0-9]+)<\/div>/gm; + + const matchFirst = +regex.exec(first)[1]; + regex.lastIndex = 0; // to reset the regex + const matchSecond = +regex.exec(second)[1]; + + return _ > 0 ? matchFirst - matchSecond : matchSecond - matchFirst; + } + + private _translate(key: string): string { + let translationResult = ''; + + this._translateService.get(key).subscribe((res) => { + translationResult = res; + }); + + return translationResult; + } + + private _loadSettingsSmartTable() { + const img = this._translate( + this.TRANSLATE_PREFIXES.SMART_TABLE.TITLES.IMG + ); + const product = this._translate( + this.TRANSLATE_PREFIXES.SMART_TABLE.TITLES.PRODUCT + ); + const price = this._translate( + this.TRANSLATE_PREFIXES.SMART_TABLE.TITLES.PRICE + ); + const available = this._translate( + this.TRANSLATE_PREFIXES.SMART_TABLE.TITLES.AVAILABLE + ); + const amount = this._translate( + this.TRANSLATE_PREFIXES.SMART_TABLE.TITLES.AMOUNT + ); + + this.settingsSmartTable = { + actions: false, + pager: { perPage: 5 }, + columns: { + img: { + title: img, + filter: false, + type: 'html', + width: '50px', + }, + product: { + title: product, + type: 'html', + }, + price: { + title: price, + filter: false, + compareFunction: (_, first, second) => { + const matchFirst = +first.replace('$', ''); + const matchSecond = +second.replace('$', ''); + return _ > 0 + ? matchFirst - matchSecond + : matchSecond - matchFirst; + }, + }, + available: { + title: available, + type: 'html', + filter: false, + compareFunction: this._compareByAvailableProducts, + }, + amount: { + title: amount, + filter: false, + type: 'custom', + renderComponent: WarehouseOrderInputComponent, + onComponentInitFunction: ( + childInstance: WarehouseOrderInputComponent + ) => { + childInstance.amount + .pipe(takeUntil(this._ngDestroy$)) + .subscribe((count) => { + const wProduct = this._orderProducts.find( + ({ productId }) => + productId === childInstance.productId + ); + wProduct.count = count; + + if (!this.showOrderAction) { + this.isOrderAllowedEmitter.emit( + this.canOrder + ); + } + }); + }, + }, + }, + }; + } +} diff --git a/packages/merchant-tablet-ionic/src/components/order/make-order/+warehouse-order-modal/warehouse-order-modal.module.ts b/packages/merchant-tablet-ionic/src/components/order/make-order/+warehouse-order-modal/warehouse-order-modal.module.ts new file mode 100644 index 0000000..b5c7f67 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/components/order/make-order/+warehouse-order-modal/warehouse-order-modal.module.ts @@ -0,0 +1,16 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { Ng2SmartTableModule } from 'ng2-smart-table'; +import { WarehouseOrderModalComponent } from './warehouse-order-modal.component'; +import { WarehouseOrderInputComponent } from './warehouse-order-input.component'; +import { TranslateModule } from '@ngx-translate/core'; + +const COMPONENTS = [WarehouseOrderModalComponent, WarehouseOrderInputComponent]; + +@NgModule({ + imports: [CommonModule, Ng2SmartTableModule, TranslateModule.forChild()], + declarations: [WarehouseOrderModalComponent, WarehouseOrderInputComponent], + entryComponents: COMPONENTS, + exports: COMPONENTS, +}) +export class WarehouseOrderModalModule {} diff --git a/packages/merchant-tablet-ionic/src/components/order/make-order/make-order-comment.component.ts b/packages/merchant-tablet-ionic/src/components/order/make-order/make-order-comment.component.ts new file mode 100644 index 0000000..6d6cced --- /dev/null +++ b/packages/merchant-tablet-ionic/src/components/order/make-order/make-order-comment.component.ts @@ -0,0 +1,34 @@ +import { Component, EventEmitter, Input, Output } from '@angular/core'; +import { ViewCell } from 'ng2-smart-table'; + +@Component({ + styles: [ + ` + .order-comment-wrapper textarea { + width: 100%; + } + `, + ], + template: ` +
+ +
+ `, +}) +export class MakeOrderCommentComponent implements ViewCell { + @Input() + value; + @Input() + rowData: any; + + @Output() + comment = new EventEmitter(); + + get productId(): string { + return this.value.productId; + } + + setComment(e) { + this.comment.emit(e.target.value); + } +} diff --git a/packages/merchant-tablet-ionic/src/components/order/make-order/make-order-input.component.ts b/packages/merchant-tablet-ionic/src/components/order/make-order/make-order-input.component.ts new file mode 100644 index 0000000..819375b --- /dev/null +++ b/packages/merchant-tablet-ionic/src/components/order/make-order/make-order-input.component.ts @@ -0,0 +1,68 @@ +import { Component, EventEmitter, Input, Output } from '@angular/core'; +import { ViewCell } from 'ng2-smart-table'; + +@Component({ + styles: [ + ` + .order-input-wrapper button { + color: white !important; + } + .order-input-wrapper { + text-align: center; + } + `, + ], + template: ` +
+ + + {{ productAmount }} + + +
+ `, +}) +export class MakeOrderInputComponent implements ViewCell { + @Input() + value; + @Input() + rowData: any; + + @Output() + amount = new EventEmitter(); + + private _productAmount: number = 0; + + get warehouseAvailableProducts(): number { + return +this.value.available; + } + + get productId(): string { + return this.value.productId; + } + + get productAmount(): number { + return this._productAmount; + } + + set productAmount(amount: number) { + this._productAmount = amount; + this.amount.emit(amount); + } + + get warehouseHasAvailable(): boolean { + return this._productAmount < this.warehouseAvailableProducts; + } +} diff --git a/packages/merchant-tablet-ionic/src/components/order/make-order/make-order.component.html b/packages/merchant-tablet-ionic/src/components/order/make-order/make-order.component.html new file mode 100644 index 0000000..8a875c3 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/components/order/make-order/make-order.component.html @@ -0,0 +1,15 @@ +
+ +
+ + diff --git a/packages/merchant-tablet-ionic/src/components/order/make-order/make-order.component.scss b/packages/merchant-tablet-ionic/src/components/order/make-order/make-order.component.scss new file mode 100644 index 0000000..fb71b8b --- /dev/null +++ b/packages/merchant-tablet-ionic/src/components/order/make-order/make-order.component.scss @@ -0,0 +1,65 @@ +@import '~@ever-platform/common-angular/src/scss/everbie.common'; + +.make-order-wrapper { + background: white !important; + .smart-table { + tr.ng2-smart-titles > { + th.ng2-smart-th.img, + th.ng2-smart-th.price, + th.ng2-smart-th.available, + th.ng2-smart-th.amount { + // Titles for 'price', 'available' and 'amount'. + text-align: center; + } + + th.ng2-smart-th.img + > ng2-st-column-title + > div + > ng2-smart-table-title + > a { + // Image title padding 0 + padding: 0; + } + } + tr > { + td:nth-child(n + 3), + td:nth-child(1) { + text-align: center; + } + } + } + table { + margin: 0px auto; + td { + text-align: left; + } + } + + .ng2-smart-titles { + height: 50px !important; + } + + nav.ng2-smart-pagination-nav { + margin: 0 auto; + } + + li { + width: 50% !important; + } + + .ng2-smart-titles { + height: 50px !important; + } + + .pagination { + line-height: 1 !important; + } +} + +.order-button.button { + margin-top: 20px !important; + color: white !important; + + padding: 0.2rem 1.2rem; + font-size: 1.2rem; +} diff --git a/packages/merchant-tablet-ionic/src/components/order/make-order/make-order.component.ts b/packages/merchant-tablet-ionic/src/components/order/make-order/make-order.component.ts new file mode 100644 index 0000000..90458dd --- /dev/null +++ b/packages/merchant-tablet-ionic/src/components/order/make-order/make-order.component.ts @@ -0,0 +1,277 @@ +import { + Component, + Input, + Output, + EventEmitter, + OnInit, + OnDestroy, +} from '@angular/core'; +import { LocalDataSource } from 'ng2-smart-table'; +import { Subject, Observable, forkJoin } from 'rxjs'; +import { MakeOrderInputComponent } from './make-order-input.component'; +import { IOrderCreateInputProduct } from '@modules/server.common/routers/IWarehouseOrdersRouter'; +import { WarehouseProductsRouter } from '@modules/client.common.angular2/routers/warehouse-products-router.service'; +import { takeUntil } from 'rxjs/operators'; +import WarehouseProduct from '@modules/server.common/entities/WarehouseProduct'; +import { ILocaleMember } from '@modules/server.common/interfaces/ILocale'; +import { ProductLocalesService } from '@modules/client.common.angular2/locale/product-locales.service'; +import { WarehouseOrdersService } from '../../../services/warehouse-orders.service'; +import { TranslateService } from '@ngx-translate/core'; +import { AlertController } from '@ionic/angular'; +import DeliveryType from '@modules/server.common/enums/DeliveryType'; +import { MakeOrderCommentComponent } from './make-order-comment.component'; + +@Component({ + selector: 'make-order', + templateUrl: './make-order.component.html', + styleUrls: ['./make-order.component.scss'], +}) +export class MakeOrderComponent implements OnInit, OnDestroy { + private ngDestroy$ = new Subject(); + + @Input() + customerId: string; + + @Input() + orderType: DeliveryType; + + @Input() + orderFinishedEmitter: EventEmitter; + + @Output() + isOrderAllowedEmitter = new EventEmitter(); + + settingsSmartTable: any; + sourceSmartTable = new LocalDataSource(); + errorMsg: string = 'WAREHOUSE_VIEW.CREATE_PRODUCTS_POPUP.ERROR_MSG'; + + private _orderProducts: IOrderCreateInputProduct[] = []; + + private _warehouseProducts: WarehouseProduct[] = []; + + private _ngDestroy$ = new Subject(); + + constructor( + private readonly _warehouseProductsRouter: WarehouseProductsRouter, + private readonly _productLocaleService: ProductLocalesService, + private readonly _warehouseOrdersService: WarehouseOrdersService, + private readonly _alertController: AlertController, + private readonly _translateService: TranslateService + ) {} + + get canOrder(): boolean { + return this._orderProducts.some((product) => product.count > 0); + } + + get warehouseId() { + return localStorage.getItem('_warehouseId'); + } + + ngOnInit() { + this._loadSettingsSmartTable(); + this._loadWarehouseProducts(); + } + + makeOrder() { + const orderProducts = this._orderProducts.filter( + ({ count }) => count > 0 + ); + + this._warehouseOrdersService + .createOrder({ + userId: this.customerId, + orderType: this.orderType, + warehouseId: this.warehouseId, + products: orderProducts, + }) + .pipe(takeUntil(this._ngDestroy$)) + .subscribe( + () => { + this.orderFinishedEmitter.emit(); + }, + () => { + this._showNotification(this.errorMsg); + } + ); + } + + private _compareByAvailableProducts(_, first, second) { + const regex = /
([0-9]+)<\/div><\/div>/gm; + + const matchFirst = +regex.exec(first)[1]; + regex.lastIndex = 0; // to reset the regex + const matchSecond = +regex.exec(second)[1]; + + return _ > 0 ? matchFirst - matchSecond : matchSecond - matchFirst; + } + + private async _showNotification(message: string) { + const alert = await this._alertController.create({ + message: this._translateService.instant(message), + buttons: ['OK'], + }); + await alert.present(); + } + + private _loadWarehouseProducts() { + this._warehouseProductsRouter + .get(this.warehouseId) + .pipe(takeUntil(this._ngDestroy$)) + .subscribe((p) => { + Object.assign(this._warehouseProducts, p); + this._loadDataSmartTable(); + }); + } + + private _loadDataSmartTable() { + this._orderProducts = this._warehouseProducts.map( + (wp: WarehouseProduct) => { + return { + productId: wp.productId, + count: 0, + comment: '', + }; + } + ); + + const productsData = this._warehouseProducts.map( + (wp: WarehouseProduct) => { + return { + img: ` + + `, + product: ` + ${this._getTranslate(wp.product['title'])} + `, + price: `
$${wp.price}
`, + available: ` +
${wp.count}
+ `, + amount: { productId: wp.productId, available: wp.count }, + comment: { productId: wp.productId }, + }; + } + ); + + this.sourceSmartTable.setSort([ + { + field: 'available', + direction: 'desc', + compare: this._compareByAvailableProducts, + }, + ]); + this.sourceSmartTable.load(productsData); + } + + private _getTranslate(members: ILocaleMember[]): string { + return this._productLocaleService.getTranslate(members); + } + + private _loadSettingsSmartTable() { + const columnTitlePrefix = 'WAREHOUSE_VIEW.NEW_ORDER_VIEW.'; + const getTranslate = (name: string): Observable => + this._translateService.get(columnTitlePrefix + name); + + forkJoin( + this._translateService.get('Id'), + getTranslate('IMAGE'), + getTranslate('PRODUCT'), + getTranslate('PRICE'), + getTranslate('AVAILABLE'), + getTranslate('AMOUNT'), + getTranslate('COMMENT') + ) + .pipe(takeUntil(this.ngDestroy$)) + .subscribe( + ([id, image, product, price, available, amount, comment]) => { + this.settingsSmartTable = { + actions: false, + pager: { perPage: 3 }, + columns: { + img: { + title: image, + filter: false, + type: 'html', + width: '50px', + }, + product: { + title: product, + type: 'html', + }, + price: { + title: price, + filter: false, + type: 'html', + compareFunction: (_, first, second) => { + const matchFirst = +first.replace('$', ''); + const matchSecond = +second.replace( + '$', + '' + ); + return _ > 0 + ? matchFirst - matchSecond + : matchSecond - matchFirst; + }, + }, + available: { + title: available, + type: 'html', + filter: false, + compareFunction: this + ._compareByAvailableProducts, + }, + amount: { + title: amount, + filter: false, + type: 'custom', + renderComponent: MakeOrderInputComponent, + onComponentInitFunction: ( + childInstance: MakeOrderInputComponent + ) => { + childInstance.amount + .pipe(takeUntil(this._ngDestroy$)) + .subscribe((count) => { + const wProduct = this._orderProducts.find( + ({ productId }) => + productId === + childInstance.productId + ); + wProduct.count = count; + this.isOrderAllowedEmitter.emit( + this.canOrder + ); + }); + }, + }, + comment: { + title: comment, + filter: false, + type: 'custom', + renderComponent: MakeOrderCommentComponent, + onComponentInitFunction: ( + childInstance: MakeOrderCommentComponent + ) => { + childInstance.comment + .pipe(takeUntil(this._ngDestroy$)) + .subscribe((comment) => { + const wProduct = this._orderProducts.find( + ({ productId }) => + productId === + childInstance.productId + ); + + wProduct.comment = comment; + }); + }, + }, + }, + }; + } + ); + } + + ngOnDestroy() { + this.ngDestroy$.next(); + this.ngDestroy$.complete(); + } +} diff --git a/packages/merchant-tablet-ionic/src/components/order/order-type/order-type.component.html b/packages/merchant-tablet-ionic/src/components/order/order-type/order-type.component.html new file mode 100644 index 0000000..18a7e68 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/components/order/order-type/order-type.component.html @@ -0,0 +1,14 @@ +
+
+ + + + + + + +
diff --git a/packages/merchant-tablet-ionic/src/components/order/order-type/order-type.component.scss b/packages/merchant-tablet-ionic/src/components/order/order-type/order-type.component.scss new file mode 100644 index 0000000..ff117d8 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/components/order/order-type/order-type.component.scss @@ -0,0 +1,14 @@ +button { + padding: 3.5%; + margin-top: 2%; + background: none; + border: 1px solid; + margin-left: 2%; +} +button:hover { + color: #bd4742; + border-color: #bd4742; +} +.delivery { + text-align: center; +} diff --git a/packages/merchant-tablet-ionic/src/components/order/order-type/order-type.component.ts b/packages/merchant-tablet-ionic/src/components/order/order-type/order-type.component.ts new file mode 100644 index 0000000..2acae08 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/components/order/order-type/order-type.component.ts @@ -0,0 +1,18 @@ +import { Component, OnInit, Output, EventEmitter } from '@angular/core'; +import DeliveryType from '@modules/server.common/enums/DeliveryType'; + +@Component({ + selector: 'order-type', + templateUrl: './order-type.component.html', + styleUrls: ['./order-type.component.scss'], +}) +export class OrderTypeComponent implements OnInit { + @Output() + orderTypeEmitter = new EventEmitter(); + + ngOnInit() {} + + chooseOption(type: DeliveryType) { + this.orderTypeEmitter.emit(type); + } +} diff --git a/packages/merchant-tablet-ionic/src/components/order/order.component.html b/packages/merchant-tablet-ionic/src/components/order/order.component.html new file mode 100644 index 0000000..8a1397c --- /dev/null +++ b/packages/merchant-tablet-ionic/src/components/order/order.component.html @@ -0,0 +1,52 @@ + +

+ {{ 'WAREHOUSE_VIEW.NEW_ORDER_VIEW.NEW_MANUAL_ORDER' | translate }} +

+ + + + + + + + + + + + + + + + + + + +
+
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+
+
diff --git a/packages/merchant-tablet-ionic/src/components/order/order.component.scss b/packages/merchant-tablet-ionic/src/components/order/order.component.scss new file mode 100644 index 0000000..4a74b10 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/components/order/order.component.scss @@ -0,0 +1,12 @@ +.order-component { + background-color: #f3f3f3; + .order-segment-title { + margin-bottom: 15px; + margin-top: 0; + padding: 0; + } + + ion-segment-button.segment-button-checked { + opacity: 1 !important; + } +} diff --git a/packages/merchant-tablet-ionic/src/components/order/order.component.ts b/packages/merchant-tablet-ionic/src/components/order/order.component.ts new file mode 100644 index 0000000..a37ca33 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/components/order/order.component.ts @@ -0,0 +1,43 @@ +import { Component, OnInit, EventEmitter, Output } from '@angular/core'; +import DeliveryType from '@modules/server.common/enums/DeliveryType'; + +type SegmentSection = 'options' | 'select/add' | 'type' | 'order'; + +@Component({ + selector: 'order', + styleUrls: ['./order.component.scss'], + templateUrl: './order.component.html', +}) +export class OrderComponent implements OnInit { + readonly availSegmentOptions = { + options: 'options' as SegmentSection, + selectAdd: 'select/add' as SegmentSection, + type: 'type' as SegmentSection, + order: 'order' as SegmentSection, + }; + + @Output() + orderFinishedEmitter = new EventEmitter(); + + segmentSection: SegmentSection = this.availSegmentOptions.options; + selectAddCustomerOption: number; + customerIdToOrder: string; + orderType: DeliveryType; + + ngOnInit() {} + + onOptionSelected(optionBit: number) { + this.segmentSection = this.availSegmentOptions.selectAdd; + this.selectAddCustomerOption = optionBit; + } + + onCustomerSelected(customerId: string) { + this.segmentSection = this.availSegmentOptions.type; + this.customerIdToOrder = customerId; + } + + onOrderTypeSelected(type: DeliveryType) { + this.segmentSection = this.availSegmentOptions.order; + this.orderType = type; + } +} diff --git a/packages/merchant-tablet-ionic/src/components/order/order.module.ts b/packages/merchant-tablet-ionic/src/components/order/order.module.ts new file mode 100644 index 0000000..f5374f8 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/components/order/order.module.ts @@ -0,0 +1,46 @@ +import { NgModule } from '@angular/core'; +import { ChooseCustomerOptionComponent } from './choose-customer-option.component'; +import { MakeOrderComponent } from './make-order/make-order.component'; +import { SelectAddCustomerComponent } from './select-add-customer.component'; +import { OrderComponent } from './order.component'; +import { Ng2SmartTableModule } from 'ng2-smart-table'; +import { UsersService } from '../../services/users.service'; +import { UserMutationModule } from '../../@shared/user/mutation/user-mutation.module'; +import { AddressComponent } from './address.component'; +import { MakeOrderInputComponent } from './make-order/make-order-input.component'; +import { WarehouseOrdersService } from '../../services/warehouse-orders.service'; +import { TranslateModule } from '@ngx-translate/core'; +import { IonicModule } from '@ionic/angular'; +import { CommonModule } from '@angular/common'; +import { FormsModule } from '@angular/forms'; +import { OrderTypeComponent } from './order-type/order-type.component'; +import { MakeOrderCommentComponent } from './make-order/make-order-comment.component'; + +@NgModule({ + imports: [ + Ng2SmartTableModule, + IonicModule, + CommonModule, + FormsModule, + UserMutationModule, + TranslateModule.forChild(), + ], + declarations: [ + OrderComponent, + ChooseCustomerOptionComponent, + SelectAddCustomerComponent, + MakeOrderComponent, + MakeOrderInputComponent, + MakeOrderCommentComponent, + OrderTypeComponent, + AddressComponent, + ], + exports: [ + OrderComponent, + ChooseCustomerOptionComponent, + SelectAddCustomerComponent, + MakeOrderComponent, + ], + providers: [UsersService, WarehouseOrdersService] +}) +export class OrderModule {} diff --git a/packages/merchant-tablet-ionic/src/components/order/select-add-customer.component.scss b/packages/merchant-tablet-ionic/src/components/order/select-add-customer.component.scss new file mode 100644 index 0000000..f75c9b8 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/components/order/select-add-customer.component.scss @@ -0,0 +1,32 @@ +.select-add-customer-component { + .customers-table { + background: white !important; + table { + margin: 0px auto; + + td { + text-align: left; + } + } + + .ng2-smart-titles { + height: 50px !important; + } + + nav.ng2-smart-pagination-nav { + margin: 0 auto; + } + + li { + width: 50% !important; + } + + .ng2-smart-titles { + height: 50px !important; + } + + .pagination { + line-height: 1 !important; + } + } +} diff --git a/packages/merchant-tablet-ionic/src/components/order/select-add-customer.component.ts b/packages/merchant-tablet-ionic/src/components/order/select-add-customer.component.ts new file mode 100644 index 0000000..90d4cee --- /dev/null +++ b/packages/merchant-tablet-ionic/src/components/order/select-add-customer.component.ts @@ -0,0 +1,136 @@ +import { Component, Input, OnInit, Output, EventEmitter } from '@angular/core'; +import { LocalDataSource } from 'ng2-smart-table'; +import { takeUntil } from 'rxjs/operators'; +import { Subject, forkJoin, Observable } from 'rxjs'; +import User from '@modules/server.common/entities/User'; +import { UsersService } from '../../services/users.service'; +import { AddressComponent } from './address.component'; +import { TranslateService } from '@ngx-translate/core'; + +@Component({ + selector: 'select-add-customer', + styleUrls: ['./select-add-customer.component.scss'], + template: ` + +
+ + +
+ +
+ +
+
+ `, +}) +export class SelectAddCustomerComponent implements OnInit { + private ngDestroy$ = new Subject(); + + visible: boolean; + + @Input() + customerOptionSelected: number; + + settingsSmartTable: object; + sourceSmartTable = new LocalDataSource(); + + @Output() + customerIdEmitter = new EventEmitter(); + + private _noInfoSign = ''; + private _ngDestroy$ = new Subject(); + + constructor( + private readonly _usersService: UsersService, + private readonly _translateService: TranslateService + ) {} + + get isSelectedFromExisting() { + return this.customerOptionSelected === 0; + } + + ngOnInit() { + this._setupSettingsSmartTable(); + this._loadDataSmartTable(); + } + + selectFromExisting(ev) { + this.broadcastCustomerId(ev.data.id); + } + + changeState(ev): void { + this.visible = ev; + } + + broadcastCustomerId(customerId: string) { + this.customerIdEmitter.emit(customerId); + } + + private _setupSettingsSmartTable() { + const columnTitlePrefix = 'WAREHOUSE_VIEW.NEW_ORDER_VIEW.'; + const getTranslate = (name: string): Observable => + this._translateService.get(columnTitlePrefix + name); + + forkJoin( + this._translateService.get('Id'), + getTranslate('FULL_NAME'), + getTranslate('EMAIL'), + getTranslate('PHONE'), + getTranslate('ADDRESS') + ) + .pipe(takeUntil(this.ngDestroy$)) + .subscribe(([id, fullName, email, phone, address]) => { + this.settingsSmartTable = { + actions: false, + filters: false, + pager: { + perPage: 3, + }, + columns: { + name: { title: fullName }, + email: { title: email }, + phone: { title: phone }, + address: { + title: address, + type: 'custom', + renderComponent: AddressComponent, + }, + }, + }; + }); + } + + private _loadDataSmartTable() { + this._usersService + .getUsers() + .pipe(takeUntil(this._ngDestroy$)) + .subscribe((users: User[]) => { + const formattedData = this._formatDataSmartTable(users); + this.sourceSmartTable.load(formattedData); + }); + } + + private _formatDataSmartTable(users: User[]) { + return users.map((user: User) => { + return { + id: user.id, + name: ` + ${user.firstName || this._noInfoSign} ${user.lastName || this._noInfoSign} + `, + email: user.email || this._noInfoSign, + phone: user.phone || this._noInfoSign, + address: user.fullAddress || this._noInfoSign, + user, + }; + }); + } +} diff --git a/packages/merchant-tablet-ionic/src/components/settings-page-components/account/account.html b/packages/merchant-tablet-ionic/src/components/settings-page-components/account/account.html new file mode 100644 index 0000000..49ff093 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/components/settings-page-components/account/account.html @@ -0,0 +1,64 @@ +
+
+ + + {{ 'SETTINGS_VIEW.NEW_USERNAME' | translate }} + + + + + + {{ 'SETTINGS_VIEW.NAME_MUST_BE_AT_LEAST_4_CHARACTERS_LONG' | + translate }}. + + + +
+
+ + + {{ 'SETTINGS_VIEW.OLD_PASSWORD' | translate }} + + + + + {{ 'SETTINGS_VIEW.NEW_PASSWORD' | translate }} + + + + + {{ 'SETTINGS_VIEW.REPEAT_NEW_PASSWORD' | translate }} + + + + + + {{ 'SETTINGS_VIEW.PASSWORDS_DO_NOT_MATCH' | translate }}. + + + +
+ + + {{ 'SETTINGS_VIEW.SAVE_CHANGES' | translate }} + +
diff --git a/packages/merchant-tablet-ionic/src/components/settings-page-components/account/account.scss b/packages/merchant-tablet-ionic/src/components/settings-page-components/account/account.scss new file mode 100644 index 0000000..3ecc7a0 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/components/settings-page-components/account/account.scss @@ -0,0 +1,2 @@ +account { +} diff --git a/packages/merchant-tablet-ionic/src/components/settings-page-components/account/account.ts b/packages/merchant-tablet-ionic/src/components/settings-page-components/account/account.ts new file mode 100644 index 0000000..26361dd --- /dev/null +++ b/packages/merchant-tablet-ionic/src/components/settings-page-components/account/account.ts @@ -0,0 +1,119 @@ +import { Component, Input, OnInit, OnChanges, OnDestroy } from '@angular/core'; +import { + FormGroup, + FormBuilder, + Validators, + AbstractControl, +} from '@angular/forms'; +import Warehouse from '@modules/server.common/entities/Warehouse'; +import { WarehouseRouter } from '@modules/client.common.angular2/routers/warehouse-router.service'; +import { AlertController } from '@ionic/angular'; + +@Component({ + selector: 'merchant-account', + templateUrl: 'account.html', +}) +export class AccountComponent implements OnInit, OnChanges, OnDestroy { + accountForm: FormGroup; + username: AbstractControl; + oldPassword: AbstractControl; + password: AbstractControl; + repeatPassword: AbstractControl; + $password: any; + + @Input() + private currWarehouse: Warehouse; + + constructor( + private formBuilder: FormBuilder, + private warehouseRouter: WarehouseRouter, + public alertController: AlertController + ) { + this.buildForm(); + this.bindFormControls(); + } + + async saveChanges() { + this.prepareUpdate(); + await this.warehouseRouter.save(this.currWarehouse); + if (this.password.value) { + try { + await this.warehouseRouter.updatePassword( + this.currWarehouse.id, + { + current: this.oldPassword.value, + new: this.password.value, + } + ); + } catch (error) { + const alertError = await this.alertController.create({ + cssClass: 'error-info', + message: error.message, + buttons: ['OK'], + }); + + await alertError.present(); + + return; + } + } + const alertSuccess = await this.alertController.create({ + cssClass: 'success-info', + message: 'Successfully saved changes', + buttons: ['OK'], + }); + + await alertSuccess.present(); + } + + ngOnChanges(): void { + if (this.currWarehouse) { + this.username.setValue(this.currWarehouse.username); + } + } + + buildForm() { + this.accountForm = this.formBuilder.group({ + username: ['', Validators.minLength(4)], + password: [''], + oldPassword: [''], + repeatPassword: [ + '', + [ + (control: AbstractControl) => { + if (this.password) { + return control.value === this.password.value + ? null + : { validUrl: true }; + } else { + return null; + } + }, + ], + ], + }); + } + + bindFormControls() { + this.username = this.accountForm.get('username'); + this.oldPassword = this.accountForm.get('oldPassword'); + this.password = this.accountForm.get('password'); + this.repeatPassword = this.accountForm.get('repeatPassword'); + } + + prepareUpdate() { + this.currWarehouse.username = this.username.value; + } + + ngOnInit(): void { + this.$password = this.password.valueChanges.subscribe((res) => { + this.repeatPassword.setValue(''); + }); + } + + ngOnDestroy(): void { + if (this.$password) { + this.$password.unsubscribe(); + } + } +} diff --git a/packages/merchant-tablet-ionic/src/components/settings-page-components/common/common.html b/packages/merchant-tablet-ionic/src/components/settings-page-components/common/common.html new file mode 100644 index 0000000..8e8fbd8 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/components/settings-page-components/common/common.html @@ -0,0 +1,88 @@ +
+
+

+ {{ 'SETTINGS_VIEW.BASIC_INFO' | translate }} +

+ + + + {{ 'SETTINGS_VIEW.NAME' | translate }} + + + + + + + {{ 'SETTINGS_VIEW.NAME_IS_REQUIRED' | translate }}. + + + + + + +
+
+
+
+ Invalid image +
+ +
+ +
+
+
+
+
+ +
+

+ {{ 'SETTINGS_VIEW.CONTACT_INFO' | translate }} +

+ + + {{ 'SETTINGS_VIEW.E_MAIL' | translate }} + + + + + {{ 'SETTINGS_VIEW.PHON_NUMBER' | translate }} + + +
+ + + {{ 'SETTINGS_VIEW.SAVE_CHANGES' | translate }} + +
diff --git a/packages/merchant-tablet-ionic/src/components/settings-page-components/common/common.scss b/packages/merchant-tablet-ionic/src/components/settings-page-components/common/common.scss new file mode 100644 index 0000000..821d7d5 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/components/settings-page-components/common/common.scss @@ -0,0 +1,2 @@ +common { +} diff --git a/packages/merchant-tablet-ionic/src/components/settings-page-components/common/common.ts b/packages/merchant-tablet-ionic/src/components/settings-page-components/common/common.ts new file mode 100644 index 0000000..71bbb1f --- /dev/null +++ b/packages/merchant-tablet-ionic/src/components/settings-page-components/common/common.ts @@ -0,0 +1,86 @@ +import { Component, Input, OnChanges } from '@angular/core'; +import { + FormBuilder, + FormGroup, + AbstractControl, + Validators, +} from '@angular/forms'; +import { WarehouseRouter } from '@modules/client.common.angular2/routers/warehouse-router.service'; +import Warehouse from '@modules/server.common/entities/Warehouse'; +import { AlertController } from '@ionic/angular'; + +@Component({ + selector: 'merchant-common', + templateUrl: 'common.html', +}) +export class CommonComponent implements OnChanges { + commonForm: FormGroup; + name: AbstractControl; + logo: AbstractControl; + email: AbstractControl; + phone: AbstractControl; + + @Input() + private currWarehouse: Warehouse; + + constructor( + private formBuilder: FormBuilder, + private warehouseRouter: WarehouseRouter, + public alertController: AlertController + ) { + this.buildForm(); + this.bindFormControls(); + } + + async saveChanges() { + this.prepareUpdate(); + await this.warehouseRouter.save(this.currWarehouse); + const alert = await this.alertController.create({ + cssClass: 'success-info', + message: 'Successfully saved changes', + buttons: ['OK'], + }); + + await alert.present(); + } + + ngOnChanges(): void { + if (this.currWarehouse) { + this.loadData(); + } + } + + prepareUpdate() { + this.currWarehouse.name = this.name.value; + this.currWarehouse.logo = this.logo.value; + this.currWarehouse.contactPhone = this.phone.value; + this.currWarehouse.contactEmail = this.email.value; + } + + loadData() { + this.name.setValue(this.currWarehouse.name); + this.logo.setValue(this.currWarehouse.logo); + this.email.setValue(this.currWarehouse.contactEmail); + this.phone.setValue(this.currWarehouse.contactPhone); + } + + buildForm() { + this.commonForm = this.formBuilder.group({ + name: ['', Validators.required], + logo: [''], + email: [''], + phone: [''], + }); + } + + bindFormControls() { + this.name = this.commonForm.get('name'); + this.logo = this.commonForm.get('logo'); + this.email = this.commonForm.get('email'); + this.phone = this.commonForm.get('phone'); + } + + deleteImg() { + this.logo.setValue(''); + } +} diff --git a/packages/merchant-tablet-ionic/src/components/settings-page-components/location/location.html b/packages/merchant-tablet-ionic/src/components/settings-page-components/location/location.html new file mode 100644 index 0000000..430d880 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/components/settings-page-components/location/location.html @@ -0,0 +1,205 @@ +
+ + + + + {{ 'SETTINGS_VIEW.COUNTRY' | translate }} + + + {{ country.name }} + + + + + + + + {{ 'SETTINGS_VIEW.CITY' | translate }} + + + + + + + {{ 'SETTINGS_VIEW.CITY_IS_REQUIRED' | translate }}. + + + + + + + + {{ 'SETTINGS_VIEW.POSTCODE_optional' | translate + }} + + + + + + + + + + {{ 'SETTINGS_VIEW.STREET' | translate }} + + + + + + {{ 'SETTINGS_VIEW.STREET_IS_REQUIRED' | translate + }}. + + + + + + + + {{ 'SETTINGS_VIEW.HOUSE' | translate }} + + + + + + {{ 'SETTINGS_VIEW.HOUSE_IS_REQUIRED' | translate }}. + + + + + + + + {{ 'SETTINGS_VIEW.APARTMENT_optional' | translate + }} + + + + + + + + + {{ 'SETTINGS_VIEW.AUTO_DETECT_COORDINATES' | translate + }} + + + + + + + + {{ 'SETTINGS_VIEW.LATITUDE' | translate }} + + + + + + + {{ 'SETTINGS_VIEW.LATITUDE_IS_REQUIRED' | translate + }}. + + + + + + + + + {{ 'SETTINGS_VIEW.LONGITUDE' | translate }} + + + + + + + {{ 'SETTINGS_VIEW.LONGITUDE_IS_REQUIRED' | translate + }}. + + + + + + + + + + {{ 'SETTINGS_VIEW.SAVE_CHANGES' | translate }} + + + + +
+ +
+ +
+ +
+ + +
diff --git a/packages/merchant-tablet-ionic/src/components/settings-page-components/location/location.scss b/packages/merchant-tablet-ionic/src/components/settings-page-components/location/location.scss new file mode 100644 index 0000000..af391fa --- /dev/null +++ b/packages/merchant-tablet-ionic/src/components/settings-page-components/location/location.scss @@ -0,0 +1,2 @@ +location { +} diff --git a/packages/merchant-tablet-ionic/src/components/settings-page-components/location/location.ts b/packages/merchant-tablet-ionic/src/components/settings-page-components/location/location.ts new file mode 100644 index 0000000..1a2da5c --- /dev/null +++ b/packages/merchant-tablet-ionic/src/components/settings-page-components/location/location.ts @@ -0,0 +1,402 @@ +import { + Component, + Input, + OnInit, + OnChanges, + OnDestroy, + ViewChild, + ElementRef, + EventEmitter, +} from '@angular/core'; +import Warehouse from '@modules/server.common/entities/Warehouse'; +import { WarehouseRouter } from '@modules/client.common.angular2/routers/warehouse-router.service'; +import { + FormBuilder, + FormGroup, + AbstractControl, + Validators, +} from '@angular/forms'; +import { + Country, + CountryName, + countriesIdsToNamesArray, + getCountryName, +} from '@modules/server.common/entities/GeoLocation'; +import { isEmpty } from 'lodash'; +import { TranslateService } from '@ngx-translate/core'; +import { AlertController } from '@ionic/angular'; + +@Component({ + selector: 'merchant-location', + templateUrl: 'location.html', +}) +export class LocationComponent implements OnInit, OnChanges, OnDestroy { + OK: string = 'OK'; + CANCEL: string = 'CANCEL'; + PREFIX: string = 'SETTINGS_VIEW.'; + locationForm: FormGroup; + country: AbstractControl; + city: AbstractControl; + postcode: AbstractControl; + street: AbstractControl; + house: AbstractControl; + apartment: AbstractControl; + autodetectCoordinates: AbstractControl; + latitude: AbstractControl; + longitude: AbstractControl; + + map: google.maps.Map; + + @ViewChild('autocomplete', { static: true }) + searchElement: ElementRef; + + mapCoordEmitter = new EventEmitter(); + + mapGeometryEmitter = new EventEmitter< + google.maps.GeocoderGeometry | google.maps.places.PlaceGeometry + >(); + + @Input() + private currWarehouse: Warehouse; + + private _lastUsedAddress: string; + + constructor( + private formBuilder: FormBuilder, + private warehouseRouter: WarehouseRouter, + public alertController: AlertController, + private translate: TranslateService + ) { + this.buildForm(); + this.bindFormControls(); + } + + get countries(): Array<{ id: Country; name: CountryName }> { + return countriesIdsToNamesArray; + } + + ngOnChanges(): void { + if (this.currWarehouse) { + this.country.setValue( + this.currWarehouse.geoLocation.countryId.toString() + ); + this.city.setValue(this.currWarehouse.geoLocation.city); + this.postcode.setValue(this.currWarehouse.geoLocation.postcode); + this.street.setValue(this.currWarehouse.geoLocation.streetAddress); + this.house.setValue(this.currWarehouse.geoLocation.house); + this.apartment.setValue(this.currWarehouse.geoLocation.apartment); + // here is used hardcode value in coordinates because from interface in Geolocation we are sure + // that in array coordinated on first position is lat and on second is long + this.latitude.setValue( + this.currWarehouse.geoLocation.coordinates.lat + ); + this.longitude.setValue( + this.currWarehouse.geoLocation.coordinates.lng + ); + } + } + + ngOnInit(): void { + this._initGoogleAutocompleteApi(); + this._tryFindNewCoordinates(); + } + + get buttonOK() { + return this._translate(this.PREFIX + this.OK); + } + + get buttonCancel() { + return this._translate(this.PREFIX + this.CANCEL); + } + + async saveChanges() { + this.prepareUpdate(); + const warehouse = await this.warehouseRouter.save(this.currWarehouse); + const alert = await this.alertController.create({ + cssClass: 'success-info', + message: 'Successfully saved changes', + buttons: ['OK'], + }); + + await alert.present(); + } + + prepareUpdate() { + this.currWarehouse.geoLocation.countryId = this.country.value; + this.currWarehouse.geoLocation.city = this.city.value; + this.currWarehouse.geoLocation.postcode = this.postcode.value; + this.currWarehouse.geoLocation.streetAddress = this.street.value; + this.currWarehouse.geoLocation.house = this.house.value; + this.currWarehouse.geoLocation.apartment = this.apartment.value; + this.currWarehouse.geoLocation.loc = { + type: 'Point', + coordinates: [this.longitude.value, this.latitude.value], + }; + } + + bindFormControls() { + this.country = this.locationForm.get('country'); + this.city = this.locationForm.get('city'); + this.postcode = this.locationForm.get('postcode'); + this.street = this.locationForm.get('street'); + this.house = this.locationForm.get('house'); + this.apartment = this.locationForm.get('apartment'); + this.autodetectCoordinates = this.locationForm.get( + 'autodetectCoordinates' + ); + this.latitude = this.locationForm.get('latitude'); + this.longitude = this.locationForm.get('longitude'); + } + + buildForm() { + this.locationForm = this.formBuilder.group({ + country: ['', Validators.required], + city: ['', Validators.required], + postcode: [''], + street: ['', Validators.required], + house: ['', Validators.required], + apartment: [''], + autodetectCoordinates: [true], + latitude: ['', Validators.required], + longitude: ['', Validators.required], + }); + } + + textInputChange(val, input) { + if (input === 'latitude' || input === 'longitude') { + this._tryFindNewCoordinates(); + } else if (input !== 'apartment') { + this._tryFindNewAddress(); + } + } + + private _tryFindNewCoordinates() { + const geocoder = new google.maps.Geocoder(); + + geocoder.geocode( + { + location: new google.maps.LatLng( + this.latitude.value, + this.longitude.value + ), + }, + (res, status) => { + if (status === google.maps.GeocoderStatus.OK) { + const location = res[0].geometry.location; + this.mapCoordEmitter.emit(location); + + const place = res[0]; + this._applyNewPlaceOnTheMap(place); + } + } + ); + } + + private _applyNewPlaceOnTheMap( + locationResult: + | google.maps.GeocoderResult + | google.maps.places.PlaceResult + ) { + if ( + locationResult.geometry === undefined || + locationResult.geometry === null + ) { + return; + } + + const loc = locationResult.geometry.location; + + this.latitude.setValue(loc.lat()); + this.longitude.setValue(loc.lng()); + + this.mapCoordEmitter.emit(loc); + this.mapGeometryEmitter.emit(locationResult.geometry); + this._gatherAddressInformation(locationResult); + } + + private _translate(key: string): string { + let translationResult = ''; + + this.translate.get(key).subscribe((res) => { + translationResult = res; + }); + + return translationResult; + } + + private _gatherAddressInformation( + locationResult: + | google.maps.GeocoderResult + | google.maps.places.PlaceResult + ) { + const longName = 'long_name'; + const shortName = 'short_name'; + + const neededAddressTypes = { + country: shortName, + locality: longName, + // 'neighborhood' is not need for now + // neighborhood: longName, + route: longName, + intersection: longName, + street_number: longName, + postal_code: longName, + administrative_area_level_1: shortName, + administrative_area_level_2: shortName, + administrative_area_level_3: shortName, + administrative_area_level_4: shortName, + administrative_area_level_5: shortName, + }; + + let streetName = ''; + let streetNumber = ''; + let country = ''; + let postcode = ''; + let city = ''; + + locationResult.address_components.forEach((address) => { + const addressType = address.types[0]; + const addressTypeKey = neededAddressTypes[addressType]; + + const val = address[addressTypeKey]; + + switch (addressType) { + case 'country': + country = val; + break; + case 'locality': + case 'administrative_area_level_1': + case 'administrative_area_level_2': + case 'administrative_area_level_3': + case 'administrative_area_level_4': + case 'administrative_area_level_5': + if (city === '') { + city = val; + } + break; + case 'route': + case 'intersection': + if (streetName === '') { + streetName = val; + } + break; + case 'street_number': + streetNumber = val; + break; + case 'postal_code': + postcode = val; + break; + } + }); + + this._setFormLocationValues( + country, + city, + streetName, + streetNumber, + postcode + ); + } + + private _setFormLocationValues( + country, + city, + streetName, + streetNumber, + postcode + ) { + if (!isEmpty(country)) { + this.country.setValue(Country[country].toString()); + } + if (!isEmpty(city)) { + this.city.setValue(city); + } + if (!isEmpty(streetName)) { + this.street.setValue(streetName); + } + if (!isEmpty(streetNumber)) { + this.house.setValue(streetNumber); + } + if (!isEmpty(postcode)) { + this.postcode.setValue(postcode); + } + } + + private async _initGoogleAutocompleteApi() { + if (this.searchElement) { + const inputElement = await this.searchElement['getInputElement'](); + + const autocomplete = new google.maps.places.Autocomplete( + inputElement + ); + + this._setupGoogleAutocompleteOptions(autocomplete); + this._listenForGoogleAutocompleteAddressChanges(autocomplete); + } + } + + private _setupGoogleAutocompleteOptions( + autocomplete: google.maps.places.Autocomplete + ) { + autocomplete.setComponentRestrictions({ country: ['us', 'bg', 'il'] }); + autocomplete['setFields'](['address_components', 'geometry']); + } + + private _listenForGoogleAutocompleteAddressChanges( + autocomplete: google.maps.places.Autocomplete + ) { + autocomplete.addListener('place_changed', (_) => { + const place: google.maps.places.PlaceResult = autocomplete.getPlace(); + this._applyNewPlaceOnTheMap(place); + }); + } + + private _tryFindNewAddress() { + const house = this.house.value; + const city = this.city.value; + const streetAddress = this.street.value; + const countryName = getCountryName(+this.country.value); + + if ( + isEmpty(streetAddress) || + isEmpty(house) || + isEmpty(city) || + isEmpty(countryName) + ) { + return; + } + + const newAddress = `${house}${streetAddress}${city}${countryName}`; + + if (newAddress !== this._lastUsedAddress) { + this._lastUsedAddress = newAddress; + + const geocoder = new google.maps.Geocoder(); + + geocoder.geocode( + { + address: `${streetAddress} ${house}, ${city}`, + componentRestrictions: { country: countryName }, + }, + (results, status) => { + if (status === google.maps.GeocoderStatus.OK) { + const formattedAddress = results[0].formatted_address; + const place: google.maps.GeocoderResult = results[0]; + + this._applyNewPlaceOnTheMap(place); + this._applyFormattedAddress(formattedAddress); + } + } + ); + } + } + + private async _applyFormattedAddress(address: string) { + if (this.searchElement) { + const inputElement = await this.searchElement['getInputElement'](); + inputElement.value = address; + } + } + + ngOnDestroy(): void {} +} diff --git a/packages/merchant-tablet-ionic/src/components/settings-page-components/settings/payments/mutation/mutation.html b/packages/merchant-tablet-ionic/src/components/settings-page-components/settings/payments/mutation/mutation.html new file mode 100644 index 0000000..f91ee4e --- /dev/null +++ b/packages/merchant-tablet-ionic/src/components/settings-page-components/settings/payments/mutation/mutation.html @@ -0,0 +1,43 @@ +
+

+ {{ titleText }} +

+ + +
+ + + +
+ +
+ + + +
+
diff --git a/packages/merchant-tablet-ionic/src/components/settings-page-components/settings/payments/mutation/mutation.scss b/packages/merchant-tablet-ionic/src/components/settings-page-components/settings/payments/mutation/mutation.scss new file mode 100644 index 0000000..fa1e91e --- /dev/null +++ b/packages/merchant-tablet-ionic/src/components/settings-page-components/settings/payments/mutation/mutation.scss @@ -0,0 +1,25 @@ +.payments-mutation-popup { + background-color: #fff; + display: flex !important; + flex-direction: column; + position: relative; + height: 100%; + + button:disabled { + cursor: default; + opacity: 0.4; + pointer-events: none; + } +} + +.payments-mutation-popup-form { + .validation-errors { + display: flex; + justify-content: flex-start; + font-size: 12px; + box-sizing: border-box; + span { + color: red; + } + } +} diff --git a/packages/merchant-tablet-ionic/src/components/settings-page-components/settings/payments/mutation/mutation.ts b/packages/merchant-tablet-ionic/src/components/settings-page-components/settings/payments/mutation/mutation.ts new file mode 100644 index 0000000..da6075b --- /dev/null +++ b/packages/merchant-tablet-ionic/src/components/settings-page-components/settings/payments/mutation/mutation.ts @@ -0,0 +1,57 @@ +import { Component } from '@angular/core'; +import { ModalController } from '@ionic/angular'; +import PaymentGateways, { + paymentGatewaysToString, +} from '@modules/server.common/enums/PaymentGateways'; +import { first } from 'rxjs/operators'; +import { CurrenciesService } from 'services/currencies.service'; +import { Subject } from 'rxjs'; +import IPaymentGatewayCreateObject from '@modules/server.common/interfaces/IPaymentGateway'; + +@Component({ + selector: 'merchant-payments-mutation', + templateUrl: 'mutation.html', + styleUrls: ['mutation.scss'], +}) +export class PaymentMutationComponent { + defaultCompanyBrandLogo: string; + defaultCurrency: string; + configureObject: any; + paymentGateway: PaymentGateways; + currenciesCodes: string[] = []; + paymentGateways = PaymentGateways; + newConfigureObject = new Subject(); + isValid: boolean; + + constructor( + public modalController: ModalController, + private currenciesService: CurrenciesService + ) { + this.loadCurrenciesCodes(); + } + + get titleText() { + return `${ + this.configureObject ? 'Update' : 'Add' + } ${paymentGatewaysToString(this.paymentGateway)} gateway`; + } + + cancelModal(newConfigureObject?: Subject) { + this.modalController.dismiss(newConfigureObject); + } + + updateConfigureObject(e) { + this.newConfigureObject.next(e); + } + + private async loadCurrenciesCodes() { + const res = await this.currenciesService + .getCurrencies() + .pipe(first()) + .toPromise(); + + if (res) { + this.currenciesCodes = res.map((r) => r.currencyCode); + } + } +} diff --git a/packages/merchant-tablet-ionic/src/components/settings-page-components/settings/payments/payPal/payPal.html b/packages/merchant-tablet-ionic/src/components/settings-page-components/settings/payments/payPal/payPal.html new file mode 100644 index 0000000..658d429 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/components/settings-page-components/settings/payments/payPal/payPal.html @@ -0,0 +1,116 @@ + +
+ + + + + Mode + + {{ type }} + + + + + + + Currency + + {{ code }} + + + + + + + + + Publishable key + + +
+ Publishable key is required +
+
+
+ + + + Secret key + + +
+ Secret key is required +
+
+
+ + + + Payment description + + +
+ Payment description is required +
+
+
+
+
diff --git a/packages/merchant-tablet-ionic/src/components/settings-page-components/settings/payments/payPal/payPal.scss b/packages/merchant-tablet-ionic/src/components/settings-page-components/settings/payments/payPal/payPal.scss new file mode 100644 index 0000000..d9f490e --- /dev/null +++ b/packages/merchant-tablet-ionic/src/components/settings-page-components/settings/payments/payPal/payPal.scss @@ -0,0 +1,11 @@ +.paypal-gateway { + ion-row.paypal-selects { + ion-col { + display: flex; + ion-item { + width: 100%; + margin-top: auto; + } + } + } +} diff --git a/packages/merchant-tablet-ionic/src/components/settings-page-components/settings/payments/payPal/payPal.ts b/packages/merchant-tablet-ionic/src/components/settings-page-components/settings/payments/payPal/payPal.ts new file mode 100644 index 0000000..10e3680 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/components/settings-page-components/settings/payments/payPal/payPal.ts @@ -0,0 +1,115 @@ +import { + OnInit, + OnDestroy, + Component, + Input, + Output, + EventEmitter, +} from '@angular/core'; +import { Subject } from 'rxjs'; +import { + FormGroup, + AbstractControl, + FormBuilder, + Validators, +} from '@angular/forms'; +import { takeUntil } from 'rxjs/operators'; +import IPaymentGatewayCreateObject from '@modules/server.common/interfaces/IPaymentGateway'; +import PaymentGateways from '@modules/server.common/enums/PaymentGateways'; + +@Component({ + selector: 'e-cu-paypal-gateway', + templateUrl: './payPal.html', + styleUrls: ['payPal.scss', '../mutation/mutation.scss'], +}) +export class PayPalGatewayComponent implements OnInit, OnDestroy { + @Input() + currenciesCodes: string[] = []; + @Input() + defaultCurrency: string; + @Input() + data: { + currency: string; + mode: string; + publishableKey: string; + secretKey: string; + description: boolean; + }; + @Input() + isValid: boolean; + + @Output() + isValidChange = new EventEmitter(); + @Output() + configureObject = new Subject(); + + form: FormGroup; + + currency: AbstractControl; + mode: AbstractControl; + publishableKey: AbstractControl; + secretKey: AbstractControl; + description: AbstractControl; + + payPalTypes = ['sandbox', 'live']; + + private _ngDestroy$ = new Subject(); + + constructor(private formBuilder: FormBuilder) {} + + ngOnInit() { + this.buildForm(this.formBuilder); + this.bindFormControls(); + this.onFormChanges(); + } + + ngOnDestroy(): void { + this.configureObject.next(this.getConfigureObject()); + } + + private buildForm(formBuilder: FormBuilder) { + this.form = formBuilder.group({ + currency: [ + this.data ? this.data.currency : this.defaultCurrency, + [Validators.required], + ], + mode: [this.data ? this.data.mode : '', [Validators.required]], + publishableKey: [ + this.data ? this.data.publishableKey : '', + [Validators.required], + ], + secretKey: [ + this.data ? this.data.secretKey : '', + Validators.required, + ], + description: [ + this.data ? this.data.description : '', + Validators.required, + ], + }); + } + + private bindFormControls() { + this.currency = this.form.get('currency'); + this.mode = this.form.get('mode'); + this.publishableKey = this.form.get('publishableKey'); + this.secretKey = this.form.get('secretKey'); + this.description = this.form.get('description'); + } + + private onFormChanges() { + this.form.statusChanges + .pipe(takeUntil(this._ngDestroy$)) + .subscribe((value) => { + this.isValid = this.form.valid; + this.isValidChange.emit(this.isValid); + }); + } + + private getConfigureObject(): IPaymentGatewayCreateObject { + return { + paymentGateway: PaymentGateways.PayPal, + configureObject: this.form.getRawValue(), + }; + } +} diff --git a/packages/merchant-tablet-ionic/src/components/settings-page-components/settings/payments/payments.html b/packages/merchant-tablet-ionic/src/components/settings-page-components/settings/payments/payments.html new file mode 100644 index 0000000..28020fc --- /dev/null +++ b/packages/merchant-tablet-ionic/src/components/settings-page-components/settings/payments/payments.html @@ -0,0 +1,98 @@ +
+ + + + + {{ 'SETTINGS_VIEW.ALLOW_ONLINE_PAYMENTS' | translate }}? + + + + + + + + + + + + {{ getPaymentName(item) }} + + + +

+ + {{ getPaymentName(item) }} +

+
+
+
+ + +
+ Selected Payment Gateways +
+
+ + + + + + {{ getPaymentName(pg) }} + + + Remove + + + + Edit + + + + +
+
+
diff --git a/packages/merchant-tablet-ionic/src/components/settings-page-components/settings/payments/payments.scss b/packages/merchant-tablet-ionic/src/components/settings-page-components/settings/payments/payments.scss new file mode 100644 index 0000000..941e288 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/components/settings-page-components/settings/payments/payments.scss @@ -0,0 +1,9 @@ +.payments-settings { + ion-button { + height: 24px !important; + min-height: 0; + font-size: 10px; + border: 0; + --ion-color-contrast: white !important; + } +} diff --git a/packages/merchant-tablet-ionic/src/components/settings-page-components/settings/payments/payments.ts b/packages/merchant-tablet-ionic/src/components/settings-page-components/settings/payments/payments.ts new file mode 100644 index 0000000..06547ef --- /dev/null +++ b/packages/merchant-tablet-ionic/src/components/settings-page-components/settings/payments/payments.ts @@ -0,0 +1,149 @@ +import { Component, Input, OnInit, ViewChild, ElementRef } from '@angular/core'; +import Warehouse from '@modules/server.common/entities/Warehouse'; +import PaymentGateways, { + paymentGatewaysToString, + paymentGatewaysLogo, +} from '@modules/server.common/enums/PaymentGateways'; +import { ModalController } from '@ionic/angular'; +import { PaymentMutationComponent } from './mutation/mutation'; +import { ConfirmDeletePopupPage } from 'components/confirm-delete-popup/confirm-delete-popup'; +import { countriesDefaultCurrencies } from '@modules/server.common/entities/Currency'; +import { Country } from '@modules/server.common/entities'; +import { first } from 'rxjs/operators'; + +@Component({ + selector: 'merchant-payments-settings', + templateUrl: 'payments.html', + styleUrls: ['payments.scss'], +}) +export class SettingsPaymentsComponent implements OnInit { + @Input() + currWarehouse: Warehouse; + + showPaymentsGateways: boolean; + hasChanged: boolean; + myPaymentsGateways = []; + paymentsGateways = []; + selectedMyPaymentsGateways: PaymentGateways[]; + selectedPaymentsGateways: PaymentGateways[]; + + constructor(public modalCtrl: ModalController) {} + + ngOnInit(): void { + const merchantPaymentsGateways = this.currWarehouse.paymentGateways.map( + (mpg) => mpg.paymentGateway + ); + const allPaymentGateways = Object.values(PaymentGateways).filter( + (r) => !isNaN(r) + ); + + if (merchantPaymentsGateways) { + this.myPaymentsGateways = allPaymentGateways.filter((pg) => + merchantPaymentsGateways.includes(pg) + ); + } + + this.paymentsGateways = allPaymentGateways.filter( + (pg) => !this.myPaymentsGateways.includes(pg) + ); + + this.showPaymentsGateways = true; + } + + get isValid() { + return ( + this.hasChanged && + (!this.currWarehouse.isPaymentEnabled || + this.myPaymentsGateways.length > 0) + ); + } + + getPaymentName(pg: PaymentGateways) { + return paymentGatewaysToString(pg); + } + + getPaymentLogo(pg: PaymentGateways) { + return paymentGatewaysLogo(pg); + } + + async showMutation(e) { + const paymentGateway = this.currWarehouse.paymentGateways.find( + (pg) => pg.paymentGateway === e + ); + + const modal = await this.modalCtrl.create({ + component: PaymentMutationComponent, + componentProps: { + configureObject: + paymentGateway && paymentGateway.configureObject, + paymentGateway: e, + defaultCompanyBrandLogo: this.currWarehouse.logo, + defaultCurrency: + countriesDefaultCurrencies[ + Country[this.currWarehouse.geoLocation.countryId] + ], + }, + cssClass: 'payments-mutation-wrapper', + }); + + await modal.present(); + + const { data } = await modal.onDidDismiss(); + + if (data) { + const res = await data.pipe(first()).toPromise(); + + this.currWarehouse.paymentGateways = this.currWarehouse.paymentGateways.filter( + (pg) => pg.paymentGateway !== res.paymentGateway + ); + this.currWarehouse.paymentGateways.push(res); + + this.myPaymentsGateways = this.myPaymentsGateways.filter( + (pg) => pg !== res.paymentGateway + ); + this.myPaymentsGateways.push(res.paymentGateway); + + this.paymentsGateways = this.paymentsGateways.filter( + (pg) => pg !== res.paymentGateway + ); + this.hasChanged = true; + } + + this.selectedMyPaymentsGateways = []; + this.selectedPaymentsGateways = []; + } + + async confirmRemovePaymentGateway(pg: PaymentGateways) { + const modal = await this.modalCtrl.create({ + component: ConfirmDeletePopupPage, + componentProps: { + data: { + image: this.getPaymentLogo(pg), + name: this.getPaymentName(pg), + }, + isRemove: true, + }, + cssClass: 'confirm-delete-wrapper', + }); + + await modal.present(); + + const res = await modal.onDidDismiss(); + + if (res.data) { + this.removePaymentGateway(pg); + } + } + + private removePaymentGateway(pg) { + this.paymentsGateways = [...this.paymentsGateways, pg]; + + this.myPaymentsGateways = this.myPaymentsGateways.filter( + (existedPG) => existedPG !== pg + ); + this.currWarehouse.paymentGateways = this.currWarehouse.paymentGateways.filter( + (existedPG) => existedPG.paymentGateway !== pg + ); + this.hasChanged = true; + } +} diff --git a/packages/merchant-tablet-ionic/src/components/settings-page-components/settings/payments/stripe/stripe.html b/packages/merchant-tablet-ionic/src/components/settings-page-components/settings/payments/stripe/stripe.html new file mode 100644 index 0000000..c24e5fb --- /dev/null +++ b/packages/merchant-tablet-ionic/src/components/settings-page-components/settings/payments/stripe/stripe.html @@ -0,0 +1,136 @@ + +
+ + + + + Pay Button text + + +
+ Pay Button text is required +
+
+ + + Publishable key + + +
+ Publishable key is required +
+
+
+ + + + + Currency + + {{ code }} + + + + + + + Allow remember me? + + + + + + + + + + + +
+ Company brand logo is required + Invalid logo url +
+ +
+ +
+ Invalid image +
+ +
+ +
+
+
+
+
+
+
+
diff --git a/packages/merchant-tablet-ionic/src/components/settings-page-components/settings/payments/stripe/stripe.scss b/packages/merchant-tablet-ionic/src/components/settings-page-components/settings/payments/stripe/stripe.scss new file mode 100644 index 0000000..988db19 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/components/settings-page-components/settings/payments/stripe/stripe.scss @@ -0,0 +1,10 @@ +.stripe-gateway { + ion-col.allow-remember-me { + display: flex; + + ion-item { + margin-top: auto; + width: 100%; + } + } +} diff --git a/packages/merchant-tablet-ionic/src/components/settings-page-components/settings/payments/stripe/stripe.ts b/packages/merchant-tablet-ionic/src/components/settings-page-components/settings/payments/stripe/stripe.ts new file mode 100644 index 0000000..3a01cc2 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/components/settings-page-components/settings/payments/stripe/stripe.ts @@ -0,0 +1,128 @@ +import { + Component, + OnInit, + Input, + OnDestroy, + Output, + EventEmitter, +} from '@angular/core'; +import { + FormGroup, + AbstractControl, + FormBuilder, + Validators, +} from '@angular/forms'; +import { Subject } from 'rxjs'; +import PaymentGateways from '@modules/server.common/enums/PaymentGateways'; +import { IPaymentGatewayCreateObject } from '@modules/server.common/interfaces/IPaymentGateway'; +import { takeUntil } from 'rxjs/operators'; + +@Component({ + selector: 'e-cu-stripe-gateway', + templateUrl: './stripe.html', + styleUrls: ['stripe.scss', '../mutation/mutation.scss'], +}) +export class StripeGatewayComponent implements OnInit, OnDestroy { + @Input() + currenciesCodes: string[] = []; + @Input() + defaultCompanyBrandLogo: string; + @Input() + defaultCurrency: string; + @Input() + data: { + payButtontext: string; + currency: string; + companyBrandLogo: string; + publishableKey: string; + allowRememberMe: boolean; + }; + @Input() + isValid: boolean; + + @Output() + isValidChange = new EventEmitter(); + @Output() + configureObject = new Subject(); + + form: FormGroup; + + payButtontext: AbstractControl; + currency: AbstractControl; + companyBrandLogo: AbstractControl; + publishableKey: AbstractControl; + allowRememberMe: AbstractControl; + invalidUrl: boolean; + + private _ngDestroy$ = new Subject(); + + constructor(private formBuilder: FormBuilder) {} + + ngOnInit() { + this.buildForm(this.formBuilder); + this.bindFormControls(); + this.onFormChanges(); + } + + deleteImg() { + this.companyBrandLogo.setValue(''); + } + + ngOnDestroy(): void { + this.configureObject.next(this.getConfigureObject()); + } + + onUrlChanges(isInvalid: boolean) { + this.invalidUrl = isInvalid; + this.isValid = this.form.valid && !isInvalid; + this.isValidChange.emit(this.isValid); + } + + private buildForm(formBuilder: FormBuilder) { + this.form = formBuilder.group({ + payButtontext: [ + this.data ? this.data.payButtontext : '', + [Validators.required], + ], + currency: [ + this.data ? this.data.currency : this.defaultCurrency, + [Validators.required], + ], + companyBrandLogo: [ + this.data + ? this.data.companyBrandLogo + : this.defaultCompanyBrandLogo, + [Validators.required], + ], + publishableKey: [ + this.data ? this.data.publishableKey : '', + Validators.required, + ], + allowRememberMe: [this.data ? this.data.allowRememberMe : ''], + }); + } + + private bindFormControls() { + this.payButtontext = this.form.get('payButtontext'); + this.currency = this.form.get('currency'); + this.companyBrandLogo = this.form.get('companyBrandLogo'); + this.publishableKey = this.form.get('publishableKey'); + this.allowRememberMe = this.form.get('allowRememberMe'); + } + + private getConfigureObject(): IPaymentGatewayCreateObject { + return { + paymentGateway: PaymentGateways.Stripe, + configureObject: this.form.getRawValue(), + }; + } + + private onFormChanges() { + this.form.statusChanges + .pipe(takeUntil(this._ngDestroy$)) + .subscribe((value) => { + this.isValid = this.form.valid && !this.invalidUrl; + this.isValidChange.emit(this.isValid); + }); + } +} diff --git a/packages/merchant-tablet-ionic/src/components/settings-page-components/settings/settings.html b/packages/merchant-tablet-ionic/src/components/settings-page-components/settings/settings.html new file mode 100644 index 0000000..bc3b3ee --- /dev/null +++ b/packages/merchant-tablet-ionic/src/components/settings-page-components/settings/settings.html @@ -0,0 +1,149 @@ +
+ + + +

+ {{"SETTINGS_VIEW.SETTINGS_SECTION.DELIVERY_SETTINGS"|translate}} +

+ + + {{ + 'SETTINGS_VIEW.SETTINGS_SECTION.PRODUCTS_DELIVERY_BY_DEFAULT' + | translate }} + + + + +
+ +

+ {{"SETTINGS_VIEW.SETTINGS_SECTION.TAKEAWAY_SETTINGS"|translate}} +

+ + {{ + 'SETTINGS_VIEW.SETTINGS_SECTION.PRODUCTS_TAKEAWAY_BY_DEFAULT' + | translate }} + + + + +
+
+ + +

+ {{ 'SETTINGS_VIEW.SETTINGS_SECTION.PAYMENTS_SETTINGS' | + translate }} +

+ + + +
+ +

+ {{ 'SETTINGS_VIEW.SETTINGS_SECTION.IN_STORE_MODE_SETTINGS' | + translate }} +

+ + + {{ 'SETTINGS_VIEW.SETTINGS_SECTION.IN_STORE_MODE' | + translate }} + + + + +
+
+
+

+ {{ 'SETTINGS_VIEW.SETTINGS_SECTION.BARCODE_QR_CODE_SETTINGS' | + translate }} +

+ + + + + {{ 'SETTINGS_VIEW.SETTINGS_SECTION.ORDER_BARCODE_TYPE' | + translate }} + + {{ getorderBarcodeTypesToString(type) + }} + + + + + + + + {{ 'SETTINGS_VIEW.SETTINGS_SECTION.BARCODE_DATA' | + translate }} + + + + + + + + + + + +
+ + + + {{ 'SETTINGS_VIEW.SAVE_CHANGES' | translate }} + + + +
+
diff --git a/packages/merchant-tablet-ionic/src/components/settings-page-components/settings/settings.module.ts b/packages/merchant-tablet-ionic/src/components/settings-page-components/settings/settings.module.ts new file mode 100644 index 0000000..8db5dc9 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/components/settings-page-components/settings/settings.module.ts @@ -0,0 +1,37 @@ +import { NgModule } from '@angular/core'; +import { SettingsComponent } from './settings'; +import { SettingsPaymentsComponent } from './payments/payments'; +import { CommonModule } from '@angular/common'; +import { TranslateModule } from '@ngx-translate/core'; +import { IonicModule } from '@ionic/angular'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { NgSelectModule } from '@ng-select/ng-select'; +import { PaymentMutationComponent } from './payments/mutation/mutation'; +import { ConfirmDeletePopupModule } from 'components/confirm-delete-popup/confirm-delete-popup.module'; +import { StripeGatewayComponent } from './payments/stripe/stripe'; +import { FileUploaderModule } from 'components/file-uploader/file-uploader.module'; +import { CurrenciesService } from 'services/currencies.service'; +import { PayPalGatewayComponent } from './payments/payPal/payPal'; + +@NgModule({ + declarations: [ + SettingsComponent, + SettingsPaymentsComponent, + PaymentMutationComponent, + StripeGatewayComponent, + PayPalGatewayComponent, + ], + imports: [ + CommonModule, + TranslateModule.forChild(), + IonicModule, + FormsModule, + ReactiveFormsModule, + NgSelectModule, + ConfirmDeletePopupModule, + FileUploaderModule, + ], + exports: [SettingsComponent], + providers: [CurrenciesService] +}) +export class MerchantSettingsComponentModule {} diff --git a/packages/merchant-tablet-ionic/src/components/settings-page-components/settings/settings.scss b/packages/merchant-tablet-ionic/src/components/settings-page-components/settings/settings.scss new file mode 100644 index 0000000..6980781 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/components/settings-page-components/settings/settings.scss @@ -0,0 +1,44 @@ +.merchant-settings { + .input-with-checkbox { + padding-left: 16px; + } + + ion-item { + padding: 0 !important; + } + + #scanBtnOut { + padding: 0 !important; + margin: 0 !important; + display: flex; + align-items: flex-end; + justify-content: center; + .button { + padding: 0 !important; + margin: 0 !important; + } + } + + .barcode-container { + padding: 16px; + margin: 8px 0 0 16px; + background: #dedede; + display: flex; + align-content: center; + align-items: center; + height: auto; + + img { + height: 5rem; + } + } + + .row-title { + margin-left: 1rem; + } + + .align-with-label-input { + margin-top: 25px; + padding-left: 0; + } +} diff --git a/packages/merchant-tablet-ionic/src/components/settings-page-components/settings/settings.ts b/packages/merchant-tablet-ionic/src/components/settings-page-components/settings/settings.ts new file mode 100644 index 0000000..401547e --- /dev/null +++ b/packages/merchant-tablet-ionic/src/components/settings-page-components/settings/settings.ts @@ -0,0 +1,106 @@ +import { Component, Input, AfterViewInit } from '@angular/core'; +import Warehouse from '@modules/server.common/entities/Warehouse'; +import OrderBarcodeTypes, { + orderBarcodeTypesToString, +} from '@modules/server.common/enums/OrderBarcodeTypes'; +import { WarehouseRouter } from '@modules/client.common.angular2/routers/warehouse-router.service'; +import QRCode from 'qrcode'; +import { BarcodeScanner } from '@ionic-native/barcode-scanner/ngx'; +import { AlertController } from '@ionic/angular'; + +@Component({ + selector: 'merchant-settings', + templateUrl: 'settings.html', + styleUrls: ['settings.scss'], +}) +export class SettingsComponent implements AfterViewInit { + @Input() + currWarehouse: Warehouse; + + showPayments = false; + + orderBarcodeTypes: OrderBarcodeTypes[] = [ + OrderBarcodeTypes.QR, + OrderBarcodeTypes.CODE128, + OrderBarcodeTypes.CODE39, + OrderBarcodeTypes.pharmacode, + ]; + + selectedOrderBarcodeType: OrderBarcodeTypes; + barcodetDataUrl: string; + hasScanCode: boolean; + + private merchantBeforeUpdate: Warehouse; + + constructor( + private warehouseRouter: WarehouseRouter, + public alertController: AlertController, + private barcodeScanner: BarcodeScanner + ) {} + + ngAfterViewInit(): void { + if (this.currWarehouse) { + this.merchantBeforeUpdate = new Warehouse(this.currWarehouse); + } + + this.generateQRCode(); + } + + getorderBarcodeTypesToString(status: OrderBarcodeTypes) { + return orderBarcodeTypesToString(status); + } + + hasChanges() { + return !Array.from(arguments).includes(true) && !this.hasScanCode; + } + + async saveChanges() { + try { + await this.warehouseRouter.save(this.currWarehouse); + const alert = await this.alertController.create({ + cssClass: 'success-info', + message: 'Successfully saved changes', + buttons: ['OK'], + }); + + await alert.present(); + } catch (error) { + const alert = await this.alertController.create({ + cssClass: 'error-info', + message: error.message, + buttons: ['OK'], + }); + + this.currWarehouse = this.merchantBeforeUpdate; + await alert.present(); + } + } + + async scan() { + try { + const barcodeData = await this.barcodeScanner.scan(); + this.currWarehouse.barcodeData = barcodeData.text; + this.hasScanCode = true; + } catch (error) { + console.warn(error); + } + } + + async barcodeDataChange(e) { + if (e.value) { + await this.generateQRCode(); + } else { + this.barcodetDataUrl = null; + } + } + + private async generateQRCode() { + if (this.currWarehouse) { + this.barcodetDataUrl = await QRCode.toDataURL( + this.currWarehouse.barcodeData + ); + } + + this.showPayments = true; + } +} diff --git a/packages/merchant-tablet-ionic/src/components/table-components/image-table.ts b/packages/merchant-tablet-ionic/src/components/table-components/image-table.ts new file mode 100644 index 0000000..273ea3e --- /dev/null +++ b/packages/merchant-tablet-ionic/src/components/table-components/image-table.ts @@ -0,0 +1,16 @@ +import { Component } from '@angular/core'; +import { ViewCell } from 'ng2-smart-table'; + +@Component({ + selector: 'image-table', + styles: ['img { width: 64px; height: 64px}'], + template: ` + + + + `, +}) +export class ImageTableComponent implements ViewCell { + value: string | number; + rowData: any; +} diff --git a/packages/merchant-tablet-ionic/src/components/users-table/address.ts b/packages/merchant-tablet-ionic/src/components/users-table/address.ts new file mode 100644 index 0000000..1358093 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/components/users-table/address.ts @@ -0,0 +1,37 @@ +import { Component, OnInit } from '@angular/core'; +import { ViewCell } from 'ng2-smart-table'; +import User from '@modules/server.common/entities/User'; +import { ModalController } from '@ionic/angular'; +import { CustomerAddrPopupPage } from 'pages/+customers/customer-addr-popup/customer-addr-popup'; + +@Component({ + styles: [``], + template: ` + + {{ user.geoLocation.city }} + {{ + '(' + user.geoLocation.postcode + ')' + }} + + `, +}) +export class AddressComponent implements ViewCell, OnInit { + value: string | number; + rowData: any; + user: User; + + constructor(public modalCtrl: ModalController) {} + + ngOnInit(): void { + this.user = this.rowData.user; + } + + async showAddress(user: User) { + const modal = await this.modalCtrl.create({ + component: CustomerAddrPopupPage, + componentProps: { user }, + cssClass: 'customer-address-popup', + }); + await modal.present(); + } +} diff --git a/packages/merchant-tablet-ionic/src/components/users-table/email.ts b/packages/merchant-tablet-ionic/src/components/users-table/email.ts new file mode 100644 index 0000000..b901a1a --- /dev/null +++ b/packages/merchant-tablet-ionic/src/components/users-table/email.ts @@ -0,0 +1,39 @@ +import { Component, OnInit } from '@angular/core'; +import { ViewCell } from 'ng2-smart-table'; +import User from '@modules/server.common/entities/User'; +import { ModalController } from '@ionic/angular'; +import { CustomerEmailPopupPage } from 'pages/+customers/customer-email-popup/customer-email-popup'; + +@Component({ + template: ` +
+ + +
+ `, +}) +export class EmailComponent implements ViewCell, OnInit { + value: string | number; + rowData: any; + user: User; + + constructor(public modalCtrl: ModalController) {} + + ngOnInit(): void { + this.user = this.rowData.user; + } + + async presentCustomerEmailPopup(user: User) { + const modal = await this.modalCtrl.create({ + component: CustomerEmailPopupPage, + componentProps: { user }, + cssClass: 'customer-email', + }); + await modal.present(); + } +} diff --git a/packages/merchant-tablet-ionic/src/components/users-table/image.scss b/packages/merchant-tablet-ionic/src/components/users-table/image.scss new file mode 100644 index 0000000..9287382 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/components/users-table/image.scss @@ -0,0 +1,6 @@ +.image-component { + img { + width: 64px; + height: 64px; + } +} diff --git a/packages/merchant-tablet-ionic/src/components/users-table/image.ts b/packages/merchant-tablet-ionic/src/components/users-table/image.ts new file mode 100644 index 0000000..c13a8e7 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/components/users-table/image.ts @@ -0,0 +1,23 @@ +import { Component, OnInit } from '@angular/core'; +import { ViewCell } from 'ng2-smart-table'; +import User from '@modules/server.common/entities/User'; + +@Component({ + selector: 'customer-image-view', + styleUrls: ['./image.scss'], + template: ` + + + + `, +}) +export class ImageUserComponent implements ViewCell, OnInit { + value: string | number; + rowData: any; + user: User; + constructor() {} + + ngOnInit(): void { + this.user = this.rowData.user; + } +} diff --git a/packages/merchant-tablet-ionic/src/components/users-table/orders.ts b/packages/merchant-tablet-ionic/src/components/users-table/orders.ts new file mode 100644 index 0000000..c99081d --- /dev/null +++ b/packages/merchant-tablet-ionic/src/components/users-table/orders.ts @@ -0,0 +1,43 @@ +import { Component, OnInit } from '@angular/core'; +import { ViewCell } from 'ng2-smart-table'; +import User from '@modules/server.common/entities/User'; +import Order from '@modules/server.common/entities/Order'; +import { ModalController } from '@ionic/angular'; +import { CustomerDeliveriesPopupPage } from 'pages/+customers/customer-deliveries-popup/customer-deliveries-popup'; + +@Component({ + template: ` +
+ {{ + rowData?.orders + }} +
+
+ `, +}) +export class OrdersComponent implements ViewCell, OnInit { + value: string | number; + rowData: any; + user: User; + orders: Order[]; + + constructor(public modalCtrl: ModalController) {} + + ngOnInit(): void { + this.user = this.rowData.user; + this.orders = this.rowData.allOrders; + } + + getOrdersCount(userId: string) { + return this.orders.filter((o: Order) => o.user.id === userId).length; + } + + async showDeliveriesInfo(user) { + const modal = await this.modalCtrl.create({ + component: CustomerDeliveriesPopupPage, + componentProps: { user }, + cssClass: 'customer-deliveries', + }); + await modal.present(); + } +} diff --git a/packages/merchant-tablet-ionic/src/components/users-table/phone.ts b/packages/merchant-tablet-ionic/src/components/users-table/phone.ts new file mode 100644 index 0000000..3cf3b57 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/components/users-table/phone.ts @@ -0,0 +1,55 @@ +import { Component, OnInit, Input } from '@angular/core'; +import { ViewCell } from 'ng2-smart-table'; +import { Store } from '../../services/store.service'; +import { CallNumber } from '@ionic-native/call-number/ngx'; +import UserOrder from '@modules/server.common/entities/UserOrder'; + +@Component({ + selector: 'user-phone', + template: ` + + + + {{ user.phone || '' }} + `, +}) +export class UserPhoneComponent implements ViewCell, OnInit { + value: string | number; + rowData: any; + + @Input() + user: UserOrder; + + constructor(private store: Store, public callNumber: CallNumber) {} + + ngOnInit(): void { + if (this.rowData) { + this.user = this.rowData.user; + } + } + + get canCall() { + if (this.store.platform) { + return ( + this.store.platform.toLocaleLowerCase() === 'android' || + this.store.platform.toLocaleLowerCase() === 'ios' + ); + } + return false; + } + + attemptCall(phone: string) { + if (this.canCall) { + this.callNumber + .callNumber(phone, true) + .then((res) => console.warn('Called number!', res)) + .catch((err) => console.log('Error calling number!', err)); + } + } +} diff --git a/packages/merchant-tablet-ionic/src/components/users-table/total.ts b/packages/merchant-tablet-ionic/src/components/users-table/total.ts new file mode 100644 index 0000000..4c7359f --- /dev/null +++ b/packages/merchant-tablet-ionic/src/components/users-table/total.ts @@ -0,0 +1,39 @@ +import { Component, OnInit } from '@angular/core'; +import { ViewCell } from 'ng2-smart-table'; +import User from '@modules/server.common/entities/User'; +import Order from '@modules/server.common/entities/Order'; + +@Component({ + template: ` +
+ {{ '$' + rowData?.total }} +
+
+ `, +}) +export class TotalComponent implements ViewCell, OnInit { + value: string | number; + rowData: any; + user: User; + orders: Order[]; + + constructor() {} + + ngOnInit(): void { + this.user = this.rowData.user; + this.orders = this.rowData.allOrders; + } + + getTotalPrice(userId: string) { + const orders = this.orders + .filter((o: Order) => o.isPaid) + .filter((o: Order) => o.user.id === userId); + let totalPrice = 0; + if (orders.length > 0) { + totalPrice = orders + .map((o: Order) => o.totalPrice) + .reduce((a, b) => a + b); + } + return totalPrice; + } +} diff --git a/packages/merchant-tablet-ionic/src/environments/model.ts b/packages/merchant-tablet-ionic/src/environments/model.ts new file mode 100644 index 0000000..7f6c13a --- /dev/null +++ b/packages/merchant-tablet-ionic/src/environments/model.ts @@ -0,0 +1,39 @@ +export interface Environment { + production: boolean; + + SERVICES_ENDPOINT: string; + HTTPS_SERVICES_ENDPOINT: string; + GQL_ENDPOINT: string; + GQL_SUBSCRIPTIONS_ENDPOINT: string; + + APP_VERSION: string; + + API_FILE_UPLOAD_URL: string; + + DEFAULT_LOGIN_USERNAME: string; + DEFAULT_LOGIN_PASSWORD: string; + + LOGIN_LOGO: string; + NO_INTERNET_LOGO: string; + + COMPANY_NAME: string; + APP_NAME: string; + + GOOGLE_MAPS_API_KEY: string; + + GOOGLE_ANALYTICS_API_KEY: string; + FAKE_UUID: string; + MIXPANEL_API_KEY: string; + + MAP_MERCHANT_ICON_LINK: string; + + MAP_USER_ICON_LINK: string; + + MAP_CARRIER_ICON_LINK: string; + + DEFAULT_LANGUAGE: string; + + // For maintenance micro service + SETTINGS_APP_TYPE: string; + SETTINGS_MAINTENANCE_API_URL: string; +} diff --git a/packages/merchant-tablet-ionic/src/filters/orders-filters.ts b/packages/merchant-tablet-ionic/src/filters/orders-filters.ts new file mode 100644 index 0000000..fbb8cc6 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/filters/orders-filters.ts @@ -0,0 +1,54 @@ +import Order from '@modules/server.common/entities/Order'; +import OrderStatus from '@modules/server.common/enums/OrderStatus'; +import OrderWarehouseStatus from '@modules/server.common/enums/OrderWarehouseStatus'; +import * as _ from 'lodash'; + +export type OrdersFilterModes = + | 'ready' + | 'in_delivery' + | 'not_confirmed' + | 'cancelled' + | 'all'; + +export type OrdersFilter = ( + orders: Order[], + mode: OrdersFilterModes +) => Order[]; + +export function ordersFilter(orders: Order[], mode: OrdersFilterModes) { + function isMatching(ordersMode: OrdersFilterModes, order: Order) { + switch (ordersMode) { + // orders which are ready to be ship to the customer + case 'ready': + return ( + (order.status === OrderStatus.WarehousePreparation || + (order.status === OrderStatus.InDelivery && + order.warehouseStatus === + OrderWarehouseStatus.PackagingFinished)) && + order.warehouseStatus !== + OrderWarehouseStatus.GivenToCustomer + ); + + // orders in delivery stage + case 'in_delivery': + return order.status === OrderStatus.InDelivery; + + // orders which are not completed yet (not confirmed yet by client) + case 'not_confirmed': + return ( + !order.isConfirmed && + !order.isCancelled && + !order.isCompleted + ); + + case 'cancelled': + return order.isCancelled; + + case 'all': + default: + return true; + } + } + + return _.filter(orders, (order: Order) => isMatching(mode, order)); +} diff --git a/packages/merchant-tablet-ionic/src/global.scss b/packages/merchant-tablet-ionic/src/global.scss new file mode 100644 index 0000000..e46636c --- /dev/null +++ b/packages/merchant-tablet-ionic/src/global.scss @@ -0,0 +1,1236 @@ +// App Global Sass +// ---------------------------------------------------------------------------- +// Put style rules here that you want to apply globally. These styles are for +// the entire app and not just one component. Additionally, this file can be +// also used as an entry point to import other Sass files to be included in the +// output CSS. + +/* Core CSS required for Ionic components to work properly */ +@import '~@ionic/angular/css/core.css'; + +/* Basic CSS for apps built with Ionic */ +@import '~@ionic/angular/css/normalize.css'; +@import '~@ionic/angular/css/structure.css'; +@import '~@ionic/angular/css/typography.css'; + +/* Optional CSS utils that can be commented out */ +@import '~@ionic/angular/css/padding.css'; +@import '~@ionic/angular/css/float-elements.css'; +@import '~@ionic/angular/css/text-alignment.css'; +@import '~@ionic/angular/css/text-transformation.css'; +@import '~@ionic/angular/css/flex-utils.css'; + +$fa-font-path: './assets/fonts' !default; + +@import '~@ever-platform/common-angular/src/scss/everbie.common'; + +@import './theme/smart-table.scss'; +@import './app/ionic-multiple-select.scss'; + +@import '~@ng-select/ng-select/themes/default.theme.css'; + +@import './pages/+settings/settings.scss'; + +@import './pages/+carriers/carriers.scss'; +@import './pages/+carriers/carrier-edit-popup/carrier-edit-popup.scss'; +@import './pages/+carriers/carrier-track-popup/carrier-track-popup.scss'; +@import './components/confirm-delete-popup/confirm-delete-popup.scss'; + +* { + font-family: 'Roboto', sans-serif; +} + +html, +body { + overflow: visible !important; +} + +.ion-page { + background-color: $brand-darken; +} + +ion-item.payments-gateways-select-container { + --inner-padding-end: 6px; +} + +.payments-gateways-select { + color: #000; + width: auto; + border-bottom: 1px solid #cacaca; + margin-left: 16px; + .ng-value-container { + padding: 0 !important; + } + + .ng-arrow-wrapper { + padding: 0 !important; + } + + .ng-select-container { + border: 0 !important; + } + + .ng-placeholder { + color: black !important; + } +} + +.warehouse-view .orders-container { + // padding-top: 5px; + background-color: #f3f3f3; + height: 100%; + padding: 0; + + ion-content { + height: calc(100% - 45px); + } + + .bar { + position: inherit !important; + } + + .orders { + .scroll-content { + padding-top: 0px !important; + padding-bottom: 40px !important; + } + + height: inherit !important; + + .order-card { + padding: 0; + margin-bottom: 20px; + margin-right: 0; + margin-left: 0; + + .row { + height: 100%; + padding: 0; + + .order-image-container { + height: 100%; + background-color: #353748; + + .order-image-header { + margin: 0; + font-size: 24px; + background-color: $brand; + color: white; + text-align: center; + margin-bottom: 0; + display: block; + padding-top: 5px; + padding-left: 5px; + padding-right: 5px; + + // padding-bottom: 9px; + .date { + font-size: 12px; + color: #f6f6f6; + } + } + + .order-image { + img { + width: 100%; + } + } + } + + .order-main-container { + padding: 0; + + .product-name { + margin: 0; + background-color: $brand-lighted; + color: white; + width: 100%; + padding-top: 11px; + padding-bottom: 11px; + text-align: center; + margin-bottom: 0; + } + + &.no-carrier { + .buttons { + .button { + padding: 0.3rem 1.8rem; + font-size: 1.8rem; + } + + // position: absolute; // bottom: 0; // left: 50%; // bottom: 10px; // transform: translate(-50%); + } + + // position: relative; // float: left; // top: 50%; // left: 50%; // transform: translate(-50%, 90%); // } + } + + &.carrier { + text-align: center; + + .not-given-to-carrier { + .in-delivery-to { + margin-top: 10px; + } + + .in-delivery-by { + margin-bottom: 10px; + } + } + + .given-to-carrier { + .in-delivery-to { + margin-top: 10px; + text-align: center; + font-size: 16px; + } + + .in-delivery-by { + margin-top: 15px; + text-align: center; + font-size: 16px; + } + } + } + + &.order-delivered { + .delivered-to { + margin-top: 15px; + text-align: center; + font-size: 16px; + } + + .delivered-by { + margin-top: 15px; + text-align: center; + font-size: 16px; + } + } + + &.order-canceled { + .cancel-message { + font-weight: 600; + text-align: center; + margin-top: 15px; + font-size: 16px; + margin: auto; + margin-bottom: 10px; + } + } + + &.delivery-issues { + .in-delivery-to { + margin-top: 15px; + text-align: center; + font-size: 16px; + } + + .in-delivery-by { + margin-top: 15px; + text-align: center; + font-size: 16px; + } + } + + &.warehouse-issues { + .issue { + font-weight: 500; + margin-top: 15px; + text-align: center; + font-size: 16px; + margin: auto; + margin-bottom: 10px; + } + } + } + + .order-timer-container { + padding: 0 !important; + background-color: #353748; + color: white; + width: 150px; + + .status { + padding: 11px 5px 11px 5px; + margin: 0px; + text-align: center; + background-color: $brand; + color: white; + } + + .danger { + background: $assertive; + } + + .times { + padding: 5px; + + // width: 80px; // @include vertical-align(absolute); + .time { + margin-top: 20px; + line-height: 1.6; + text-align: center; + + .big { + font-size: 16px; + font-weight: 500; + } + + &.time-passed { + padding-bottom: 7px; + } + } + + .time-big { + margin-top: 18px; + font-size: 18px; + line-height: 20px; + text-align: center; + + .big { + font-size: 18px; + margin-top: 10px; + font-weight: 500; + } + } + } + } + } + + .full-width { + width: 100%; + } + + .half-width { + width: 49%; + display: inline-block; + } + + .order-main { + height: calc(100% - 50px); + min-height: 200px; + } + + .order-actions { + height: 30%; + display: flex; + + .message { + font-weight: 500; + margin-top: 15px; + text-align: center; + font-size: 16px; + margin: auto; + margin-bottom: 10px; + } + } + + .delivery-details { + height: 70%; + } + } + } +} + +ion-content.app-view { + background: #f3f3f3; +} + +.warehouse-view .products-container { + height: 100%; + width: 250px; + position: relative; + padding: 0; + background-color: #ffffff; + + .bar { + position: inherit !important; + } + + .product-scroll { + height: calc(100% - 45px); + overflow: scroll; + .scroll { + padding-bottom: 15px; + } + } + + .products { + // TODO + // height: inherit !important; + + .product-card-container { + border: 0; + + .product-card { + margin: 0; + padding: 13px 0; + text-align: center; + border: none !important; + .product-image-container { + cursor: pointer; + } + + .product-image-container:active { + background: #000; + + .product-image { + opacity: 0.8; + } + } + + .product-buttons { + position: absolute; + top: 20px; + right: 20px; + + .add-button { + padding: 0; + font-size: 30px; + color: #fff; + } + } + + .product-image { + width: 100%; + } + + .product-mini-bar { + font-size: 17px; + background-color: $brand; + color: white; + text-align: center; + margin-bottom: 0; + padding-top: 11px; + padding-bottom: 11px; + margin-top: -6px; + + .edit-button { + margin-top: -2px; + border-radius: 3px; + color: $brand; + float: left; + border-color: #ccc; + margin-left: 10px; + min-width: 10px; + padding: 4px; + font-size: 16px; + line-height: 0; + min-height: 10px; + } + + .count { + background: #fff; + margin-top: -2px; + padding: 2px; + border-radius: 3px; + color: $brand; + float: right; + margin-right: 10px; + } + } + } + } + } +} + +.popup { + min-width: 600px; + overflow: hidden; + + .popup-head { + display: none; + } + + .popup-body { + min-width: 260px; + overflow: hidden; + padding: 0 !important; + } +} + +.create-product-type-popup, +.edit-product-type-popup { + padding: 0; + background: white; + font-family: 'open sans', 'Helvetica Neue', Helvetica, Arial, sans-serif; + + .popup-half { + padding: 20px; + + &:first-of-type { + padding-right: 5px; + } + } + + .title { + font-size: 16px; + line-height: 1.1; + font-weight: 600; + color: #676a6c; + } + + .subtitle { + font-size: 12px; + } + + .popup-input-container { + margin-top: 10px; + + .popup-input-description { + } + + .popup-input { + padding: 5px; + margin-top: 2px; + border: 2px solid #ddd; + } + } + + .upload-button { + width: 100%; + background-color: #f1f1f1; + padding: 28.3px 0; + text-align: center; + color: #333333; + + .upload-icon { + font-size: 140px; + } + + .upload-text { + margin-top: 10px; + font-size: 20px; + margin-bottom: 5px; + } + } +} + +.update-product-type-popup { + padding: 20px; + background: white; + font-family: 'open sans', 'Helvetica Neue', Helvetica, Arial, sans-serif; + + .title { + font-size: 16px; + line-height: 1.1; + font-weight: 600; + color: #676a6c; + } + + .subtitle { + font-size: 12px; + } + + .popup-input-container { + margin-top: 10px; + + .popup-input-description { + } + + .popup-input { + padding: 5px; + margin-top: 2px; + border: 2px solid #ddd; + } + } +} + +// Style from old ionic +.button-bar { + display: -webkit-box; + display: -webkit-flex; + display: -moz-box; + display: -moz-flex; + display: -ms-flexbox; + display: flex; + -webkit-box-flex: 1; + -webkit-flex: 1; + -moz-box-flex: 1; + -moz-flex: 1; + -ms-flex: 1; + flex: 1; + width: 100%; +} + +.button-bar > .button { + -webkit-box-flex: 1; + -webkit-flex: 1; + -moz-box-flex: 1; + -moz-flex: 1; + -ms-flex: 1; + flex: 1; + display: block; + overflow: hidden; + padding: 0 16px; + width: 0; + border-width: 1px 0px 1px 1px; + border-radius: 0; + text-align: center; + text-overflow: ellipsis; + white-space: nowrap; +} + +.button.button-brand { + background-color: #2a2c39; + border-color: #242530; + color: #fff; +} + +.button.button-brand.active, +.button.button-brand.activated { + background-color: #282a36; + box-shadow: inset 0 1px 4px rgba(0, 0, 0, 0.1); +} + +.button.button-assertive { + border-color: #973935; + background-color: #bd4742; + color: #fff; +} + +.button { + border-color: #b2b2b2; + background-color: #f8f8f8; + color: #444; + position: relative; + display: inline-block; + margin: 0; + padding: 0; + min-width: 52px; + min-height: 47px; + border-width: 1px; + border-style: solid; + border-radius: 2px; + vertical-align: top; + text-align: center; + text-overflow: ellipsis; + font-size: 16px; + line-height: 42px; + cursor: pointer; +} + +.bar.bar-assertive { + border-color: #973935; + background-color: #bd4742; // background-image: linear-gradient(0deg, #973935, #973935 50%, transparent 50%); + color: #fff; +} + +.bar.bar-assertive .title { + color: #fff; +} + +.bar-assertive .button { + border-color: #973935; + background-color: #bd4742; + color: #fff; +} + +.bar.bar-brand { + background-image: none; + border-bottom: 2px solid #262733; + background-color: #2a2c39; + color: white !important; +} + +.bar.bar-brand .title { + color: white; +} + +.bar.bar-brand .button-icon { + color: white; +} + +.bar { + display: -webkit-box; + display: -webkit-flex; + display: -moz-box; + display: -moz-flex; + display: -ms-flexbox; + display: flex; + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + position: absolute; + right: 0; + left: 0; + z-index: 9; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + padding: 5px; + width: 100%; + height: 44px; + border-width: 0; + border-style: solid; + border-top: 1px solid transparent; + border-bottom: 1px solid #ddd; + background-color: white; + /* border-width: 1px will actually create 2 device pixels on retina */ + /* this nifty trick sets an actual 1px border on hi-res displays */ + background-size: 0; +} + +.bar-header { + top: 0; + border-top-width: 0; + border-bottom-width: 1px; + + .disable-user-behavior { + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + -webkit-touch-callout: none; + -webkit-tap-highlight-color: transparent; + -webkit-tap-highlight-color: transparent; + -webkit-user-drag: none; + -ms-touch-action: none; + -ms-content-zooming: none; + } +} + +.bar .buttons-left span { + margin-right: 5px; + display: inherit; +} + +.button-icon { + -webkit-transition: opacity 0.1s; + transition: opacity 0.1s; + padding: 0 6px; + min-width: initial; + border-color: transparent; + background: none; +} + +.bar .title { + position: absolute; + top: 0; + right: 0; + left: 0; + z-index: 0; + overflow: hidden; + margin: 0 10px; + min-width: 30px; + height: 43px; + text-align: center; + text-overflow: ellipsis; + white-space: nowrap; + font-size: 17px; + font-weight: 500; + line-height: 44px; +} + +.bar .button.button-icon:before, +.bar .button .icon:before, +.bar .button.icon:before, +.bar .button.icon-left:before, +.bar .button.icon-right:before { + padding-right: 2px; + padding-left: 2px; + font-size: 20px; + line-height: 32px; +} + +.bar .button.button-icon .icon:before, +.bar .button.button-icon:before, +.bar .button.button-icon.icon-left:before, +.bar .button.button-icon.icon-right:before { + vertical-align: top; + font-size: 32px; + line-height: 32px; +} + +.button-icon .icon:before, +.button-icon.icon:before { + font-size: 32px; +} + +.waves-effect { + position: relative; + cursor: pointer; + display: inline-block; + overflow: hidden; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + -webkit-tap-highlight-color: transparent; +} + +.bar .button-bar > .button, +.bar .buttons > .button { + min-height: 31px; + line-height: 32px; + padding: 10px; +} + +.bar .buttons span { + display: inline-block; +} + +.button:after { + position: absolute; + top: -6px; + right: -6px; + bottom: -6px; + left: -6px; + content: ' '; +} + +.card { + padding-top: 1px; + padding-bottom: 1px; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3); +} + +.card, +.list-inset { + overflow: hidden; + margin: 20px 10px; + border-radius: 2px; + background-color: #fff; +} + +.pull-right { + float: right; + margin-left: auto; +} + +.custom-title-popup { + text-align: center; + margin: 0 15px 3rem 15px; + border-bottom: 1px solid black; + padding: 15px; + margin-bottom: 15px; +} + +.text-center { + text-align: center; + + a { + padding: 0 !important; + } +} + +.text-align-left { + text-align: left; + + a { + padding: 0 !important; + } +} + +.no-padding { + padding: 0 !important; +} + +.no-margin { + margin: 0 !important; +} + +.can-call { + &:before { + color: green; + } + margin-bottom: -2px; + margin-right: 3px; + color: green; +} + +.can-not-call { + &:before { + color: red; + } + margin-bottom: -2px; + margin-right: 3px; + color: red; +} + +.item-inner, +.item-inner { + border-bottom-color: rgba(0, 0, 0, 0.1); + -webkit-box-shadow: inset 0 -1px 0 0 rgba(0, 0, 0, 0.1); + box-shadow: inset 0 -1px 0 0 rgba(0, 0, 0, 0.1); +} +ion-modal { + display: flex !important; +} +ion-modal.order-map-popup { + display: flex !important; + .modal-wrapper { + max-height: 500px; + } +} + +ion-modal.customer-email { + display: flex !important; + .modal-wrapper { + max-height: 130px; + } +} + +.customer-add-wrapper { + display: flex !important; + .modal-wrapper { + width: 650px; + height: auto; + display: inline-table; + left: calc(50% - (650px / 2)); + top: 10%; + .ion-page { + display: contents; + ion-grid { + background: #f3f3f3 !important; + } + } + } +} + +.customer-deliveries { + display: flex !important; + .modal-wrapper { + min-height: 60%; + min-width: 80%; + margin: auto; + left: 0; + right: 0; + } +} + +.add-carriers-popup { + display: flex !important; + + .modal-wrapper { + &, + .ion-page, + .ion-page .content, + .ion-page .content .scroll-content { + contain: content; + position: relative; + + padding: 0; + } + z-index: 100; + display: flex; + overflow: hidden; + flex-direction: column; + max-height: 90%; + opacity: 0; + height: auto; + border-radius: 2px; + background-color: #fafafa; + padding: 5px; + min-width: 80%; + margin: auto; + left: 0; + right: 0; + } +} + +.mutation-product-modal { + display: flex !important; + + .modal-wrapper { + &, + .ion-page, + .ion-page .content, + .ion-page .content .scroll-content { + contain: content; + position: relative; + top: auto; + left: auto; + padding: 0; + } + z-index: 100; + display: flex; + overflow: hidden; + flex-direction: column; + max-height: 90%; + opacity: 0; + height: auto; + border-radius: 2px; + background-color: #fafafa; + padding: 5px; + min-width: 800px; + margin: auto; + left: 0; + right: 0; + } +} + +.customer-address-popup { + display: flex !important; + .modal-wrapper { + height: 450px; + } +} + +.payments-mutation-wrapper { + display: flex !important; + .modal-wrapper { + height: fit-content; + } + + .ion-page { + height: fit-content; + position: static; + contain: none; + } +} + +.carrier-deliveries { + display: flex !important; + .modal-wrapper { + min-height: 60%; + min-width: 80%; + margin: auto; + left: 0; + right: 0; + } +} + +.carrier-address-popup { + display: flex !important; + .modal-wrapper { + max-height: 420px; + } +} + +.mutation-product-images-modal { + .modal-wrapper { + width: 820px; + overflow-y: scroll; + .ion-page { + display: block !important; + background-color: #fafafa; + } + } +} + +.browseBtnOut { + padding: 0 !important; + margin: 0 !important; + display: flex; + justify-content: center; + align-items: flex-end; +} +.browseBtnIn { + width: 100%; + height: 80% !important; + border: none; + padding: 0 !important; + margin: 0 !important; + border-bottom: 0; + background-color: #488aff; + display: flex; + justify-content: center; + align-items: center; + color: white; + * { + padding: 1px; + } +} + +ion-menu-toggle.button-icon { + ion-icon { + font-size: 2rem !important; + } +} + +.customers-page { + .ordersCount { + background-color: #0074d9; + padding: 5px 10px 5px 10px; + text-align: center; + border-radius: 50%; + font-weight: bold; + color: #fff; + cursor: pointer; + } +} + +.mail-icon { + font-size: 1.4rem; +} + +tbody { + .address { + font-style: italic; + text-decoration: underline; + } +} + +.underlined { + color: #0074d9; + cursor: pointer; +} + +.img-rounded { + max-height: 70px; +} + +.no-orders-message { + text-align: center; + @include center(absolute); + + i { + font-size: 8vw !important; + // font-size: 50%; + } + + h2 { + font-family: 'Open Sans Hebrew', '-apple-system', 'Helvetica Neue', + 'Roboto', 'Segoe UI', sans-serif; + margin-top: 5px; + font-size: 3vw; + } + h3 { + color: mediumblue; + text-decoration: underline; + cursor: pointer; + &:hover { + opacity: 0.6; + } + font-size: 1.3vw; + } + a { + margin-top: 20px; + } +} + +.page-add-carriers-popup { + .second-step { + .card-footer { + display: none; + } + + .card-block { + carrier-location-form { + height: 55rem !important; + } + } + } + + .card-footer { + height: 4rem !important; + position: relative; + + // display: none; + button.float-right { + float: right; + bottom: 5px; + } + + button { + @extend .button; + border-radius: 5px; + // margin: 20px; + background: white; + border: 2px solid #dadfe6; + padding-left: 10px; + padding-right: 10px; + } + + button:hover { + background: #dadfe6; + } + + button.float-right { + float: right; + position: absolute; + right: 15px; + bottom: 5px; + border-color: #32db64; + } + + button.float-right:hover { + background: #32db64; + } + + button.float-right:disabled { + border-color: #dadfe6; + } + + button.float-left { + float: left; + position: absolute; + left: 15px; + bottom: 5px; + } + } + + .card-block { + // max-height: 40rem !important; + max-height: 55rem !important; + overflow-y: hidden !important; + } + + .card { + margin: 0 !important; + padding: 0 !important; + position: relative; + } + .card-header { + display: block; + // margin-bottom: 20px; + padding: 0 !important; + border-bottom: 2px solid rgba(0, 0, 0, 0.1) !important; + } + + ul { + list-style-type: none; + + padding: 0; + overflow: hidden; + margin: 0; + } + + li { + float: left; + width: 50% !important; + font-size: 1rem; + text-align: center; + padding: 10px 0 !important; + } + + li a { + color: black; + } + + li.disablet { + color: #dadada; + } +} + +.file-uploader-container { + input.native-input { + padding-right: 10px !important; + } + + .ion-input-custom-style { + input.native-input { + padding-left: 16px !important; + } + } +} + +.close { + position: absolute; + right: 20px; + top: 15px; + z-index: 1; + background: none; + color: black; + font-size: 1.5em; + cursor: pointer; +} + +.carrier-location-form { + .map-wrapper .g-map { + height: 190px !important; + } +} diff --git a/packages/merchant-tablet-ionic/src/graphql/apollo.config.ts b/packages/merchant-tablet-ionic/src/graphql/apollo.config.ts new file mode 100644 index 0000000..dfe4349 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/graphql/apollo.config.ts @@ -0,0 +1,54 @@ +import { NgModule } from '@angular/core'; +import { HttpClientModule } from '@angular/common/http'; +import { Apollo } from 'apollo-angular'; +import { HttpLink, HttpLinkHandler } from 'apollo-angular/http'; +import { ApolloLink, InMemoryCache } from '@apollo/client/core'; +import { setContext } from '@apollo/client/link/context'; +import { Store } from '../services/store.service'; +import { environment } from '../environments/environment'; + +@NgModule({ + exports: [ + HttpClientModule + ] +}) +export class GraphQLModule { + constructor( + private readonly apollo: Apollo, + private readonly httpLink: HttpLink, + private readonly store: Store + ) { + const uri = environment.GQL_ENDPOINT; + const http: HttpLinkHandler = httpLink.create({ uri }); + + const authLink: ApolloLink = setContext((_, { headers }) => { + // get the authentication token from local storage if it exists + const token = store.token; + // return the headers to the context so httpLink can read them + return { + headers: { + ...headers, + authorization: token ? `Bearer ${token}` : '', + }, + }; + }); + + apollo.create({ + link: authLink.concat(http), + defaultOptions: { + watchQuery: { + fetchPolicy: 'network-only', + errorPolicy: 'ignore', + }, + query: { + fetchPolicy: 'network-only', + errorPolicy: 'all', + }, + mutate: { + errorPolicy: 'all', + }, + }, + cache: new InMemoryCache(), + }); + } +} diff --git a/packages/merchant-tablet-ionic/src/index.html b/packages/merchant-tablet-ionic/src/index.html new file mode 100644 index 0000000..04ce23d --- /dev/null +++ b/packages/merchant-tablet-ionic/src/index.html @@ -0,0 +1,28 @@ + + + + + Ever Merchant App + + + + + + + + + + + + + + + + + + + + diff --git a/packages/merchant-tablet-ionic/src/main.ts b/packages/merchant-tablet-ionic/src/main.ts new file mode 100644 index 0000000..05951c2 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/main.ts @@ -0,0 +1,15 @@ +import { enableProdMode } from '@angular/core'; +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; +import { AppModule } from 'app/app.module'; +import { environment } from 'environments/environment'; + +if (environment.production) { + enableProdMode(); +} + +platformBrowserDynamic() + .bootstrapModule(AppModule) + .catch((err) => { + // TODO: we probably should also send error to Sentry here + console.log(err); + }); diff --git a/packages/merchant-tablet-ionic/src/manifest.json b/packages/merchant-tablet-ionic/src/manifest.json new file mode 100644 index 0000000..31105a3 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/manifest.json @@ -0,0 +1,15 @@ +{ + "name": "ever-merchant", + "short_name": "ever-merchant", + "start_url": "index.html", + "display": "standalone", + "icons": [ + { + "src": "assets/imgs/logo.png", + "sizes": "512x512", + "type": "image/png" + } + ], + "background_color": "#4e8ef7", + "theme_color": "#4e8ef7" +} diff --git a/packages/merchant-tablet-ionic/src/pages/+carriers/add-carriers-popup/add-carriers-popup.html b/packages/merchant-tablet-ionic/src/pages/+carriers/add-carriers-popup/add-carriers-popup.html new file mode 100644 index 0000000..c4c1dd9 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+carriers/add-carriers-popup/add-carriers-popup.html @@ -0,0 +1,35 @@ +
+

+ {{ 'CARRIERS_VIEW.ADD_CARRIER.ADD_CARRIER' | translate }} +

+ + + + + + + + + + + +
diff --git a/packages/merchant-tablet-ionic/src/pages/+carriers/add-carriers-popup/add-carriers-popup.module.ts b/packages/merchant-tablet-ionic/src/pages/+carriers/add-carriers-popup/add-carriers-popup.module.ts new file mode 100644 index 0000000..c667803 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+carriers/add-carriers-popup/add-carriers-popup.module.ts @@ -0,0 +1,34 @@ +import { NgModule } from '@angular/core'; +import { AddCarriersPopupPage } from './add-carriers-popup'; +import { FormWizardModule } from '@ever-co/angular2-wizard'; +import { AddChoiceComponent } from './add-choice/add-choice'; +import { CarriersCatalogComponent } from './carriers-catalog/carriers-catalog'; +import { Ng2SmartTableModule } from 'ng2-smart-table'; +import { AddressComponent } from './carriers-catalog/address.component'; +import { TranslateModule } from '@ngx-translate/core'; +import { AddNewCarriersPopupPageModule } from './add-new-carrier/add-new-carrier.module'; +import { IonicModule } from '@ionic/angular'; +import { CommonModule } from '@angular/common'; +import { FormsModule } from '@angular/forms'; + +const COMPONENTS = [ + AddCarriersPopupPage, + AddChoiceComponent, + CarriersCatalogComponent, + AddressComponent, +]; + +@NgModule({ + declarations: COMPONENTS, + exports: COMPONENTS, + imports: [ + IonicModule, + CommonModule, + FormsModule, + FormWizardModule, + Ng2SmartTableModule, + AddNewCarriersPopupPageModule, + TranslateModule.forChild(), + ] +}) +export class AddCarriersPopupPageModule {} diff --git a/packages/merchant-tablet-ionic/src/pages/+carriers/add-carriers-popup/add-carriers-popup.scss b/packages/merchant-tablet-ionic/src/pages/+carriers/add-carriers-popup/add-carriers-popup.scss new file mode 100644 index 0000000..5d95148 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+carriers/add-carriers-popup/add-carriers-popup.scss @@ -0,0 +1,9 @@ +@import '~@ever-platform/common-angular/src/scss/everbie.common'; + +.page-add-carriers-popup { + background-color: #fff; + display: flex !important; + flex-direction: column; + position: relative; + // overflow: scroll; +} diff --git a/packages/merchant-tablet-ionic/src/pages/+carriers/add-carriers-popup/add-carriers-popup.ts b/packages/merchant-tablet-ionic/src/pages/+carriers/add-carriers-popup/add-carriers-popup.ts new file mode 100644 index 0000000..cf07b54 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+carriers/add-carriers-popup/add-carriers-popup.ts @@ -0,0 +1,161 @@ +import { Component, ViewChild, OnInit, OnDestroy } from '@angular/core'; + +import { AddChoiceComponent } from './add-choice/add-choice'; +import { AddNewCarrierComponent } from './add-new-carrier/add-new-carrier'; +import { CarrierRouter } from '@modules/client.common.angular2/routers/carrier-router.service'; +import { WarehouseRouter } from '@modules/client.common.angular2/routers/warehouse-router.service'; +import { Store } from '../../../services/store.service'; +import Warehouse from '@modules/server.common/entities/Warehouse'; +import { CarriersCatalogComponent } from './carriers-catalog/carriers-catalog'; +import { TranslateService } from '@ngx-translate/core'; +import { Subject } from 'rxjs'; +import { takeUntil, first } from 'rxjs/operators'; +import { ModalController } from '@ionic/angular'; + +@Component({ + selector: 'page-add-carriers-popup', + templateUrl: 'add-carriers-popup.html', + styleUrls: ['./add-carriers-popup.scss'], +}) +export class AddCarriersPopupPage implements OnInit, OnDestroy { + @ViewChild('addNewCarrier', { static: false }) + addNewCarrierComponent: AddNewCarrierComponent; + + @ViewChild('carriersCatalog', { static: false }) + carriersCatalog: CarriersCatalogComponent; + + @ViewChild('addChoice', { static: true }) + addChoiceComponent: AddChoiceComponent; + + @ViewChild('wizardFrom', { static: true }) + wizardFrom: any; + + @ViewChild('wizardFromStep1', { static: true }) + wizardFromStep1: any; + + @ViewChild('wizardFormStep2', { static: true }) + wizardFormStep2: any; + + choiced: string; + isDone: boolean; + choicedNew: boolean = false; + + private choice$: any; + private form$: any; + private _ngDestroy$ = new Subject(); + + constructor( + public modalController: ModalController, + public carrierRouter: CarrierRouter, + public warehouseRouter: WarehouseRouter, + public store: Store, + private readonly _translateService: TranslateService + ) {} + + ngOnInit() { + this.wizardFromStep1.showNext = false; + + this.choice$ = this.addChoiceComponent.choice.subscribe(async (res) => { + this.choiced = res; + this.wizardFrom.next(); + }); + } + + buttonClickEvent(data) { + const prevOrdNext: string = data; + + if (prevOrdNext === 'previous') { + this.wizardFrom.previous(); + this.choicedNew = false; + } + } + + completeCreateCarrier(data) { + if (data === 'complete') { + this.wizardFrom.complete(); + } + } + + get wizardStepsTitle() { + let resultTitle = ''; + + const step1 = () => { + this._translateService + .get('CARRIERS_VIEW.ADD_CARRIER.SELECT_HOW_TO_ADD') + .pipe(takeUntil(this._ngDestroy$)) + .subscribe((title) => (resultTitle = title)); + + return resultTitle; + }; + + const step2 = () => { + this._translateService + .get('CARRIERS_VIEW.ADD_CARRIER.ADD') + .pipe(takeUntil(this._ngDestroy$)) + .subscribe((title) => (resultTitle = title)); + return resultTitle; + }; + + return { + step1: step1(), + step2: step2(), + }; + } + + ngOnDestroy() { + if (this.choice$) { + this.choice$.unsubscribe(); + } + + if (this.form$) { + this.form$.unsubscribe(); + } + + this._ngDestroy$.next(); + this._ngDestroy$.complete(); + } + + async onStep1Next(choiced) { + if (choiced === 'new') { + this.choicedNew = true; + + // const form = this.addNewCarrierComponent.form; + // this.form$ = form.valueChanges.subscribe((res) => { + // this.isDone = form.valid; + // }); + } else if (choiced === 'existing') { + this.choicedNew = false; + this.form$ = this.carriersCatalog.hasChanges.subscribe((r) => { + this.isDone = this.carriersCatalog.selecteCarriers.length > 0; + }); + } + } + + async add() { + this.cancelModal(); + const warehouse: Warehouse = await this.warehouseRouter + .get(this.store.warehouseId) + .pipe(first()) + .toPromise(); + if (this.choiced === 'new') { + const carrier = await this.carrierRouter.register({ + carrier: this.addNewCarrierComponent.getCarrierCreateObject(), + password: this.addNewCarrierComponent.password.value, + }); + + warehouse.hasRestrictedCarriers = true; + warehouse.usedCarriersIds.push(carrier.id); + } else if (this.choiced === 'existing') { + warehouse.hasRestrictedCarriers = true; + warehouse.usedCarriersIds.push( + ...this.carriersCatalog.selecteCarriers.map((c) => c.id) + ); + } + + this.warehouseRouter.save(warehouse); + } + + cancelModal() { + this.modalController.dismiss(); + } +} diff --git a/packages/merchant-tablet-ionic/src/pages/+carriers/add-carriers-popup/add-choice/add-choice.html b/packages/merchant-tablet-ionic/src/pages/+carriers/add-carriers-popup/add-choice/add-choice.html new file mode 100644 index 0000000..cb987b0 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+carriers/add-carriers-popup/add-choice/add-choice.html @@ -0,0 +1,28 @@ + + + +
+ + {{ 'CARRIERS_VIEW.ADD_CARRIER.SELECT_FROM_CARRIERS_CATALOG' + | translate }} +
+
+ +
+ {{ 'CARRIERS_VIEW.ADD_CARRIER.ADD_NEW_CARRIER' | translate + }} +
+
+
+
diff --git a/packages/merchant-tablet-ionic/src/pages/+carriers/add-carriers-popup/add-choice/add-choice.scss b/packages/merchant-tablet-ionic/src/pages/+carriers/add-carriers-popup/add-choice/add-choice.scss new file mode 100644 index 0000000..bc57f4a --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+carriers/add-carriers-popup/add-choice/add-choice.scss @@ -0,0 +1,44 @@ +.carriers-add-choice { + ion-grid, + ion-row, + ion-col { + background: white; + } + + .option { + margin-top: 20px !important; + position: absolute; + width: 200px; + height: 150px; + border: 2px solid #dadfe6; + margin: 0 auto; + position: relative; + text-align: center; + color: #2a2a2a; + + // text-transform: uppercase; + letter-spacing: 0.4px; + font-weight: 500; + font-family: Exo; + -webkit-transition: none; + transition: none; + cursor: default; + padding: 0.75rem 1.5rem; + font-size: 0.9rem; + line-height: 1.25; + border-radius: 0.375rem; + padding-top: 63px; + font-weight: bold; + span { + // filter: drop-shadow(3px 3px 3px rgb(182, 184, 182)); + } + } + + .option:hover { + background: #dadfe6; + } + + .selected { + background: #dadfe6; + } +} diff --git a/packages/merchant-tablet-ionic/src/pages/+carriers/add-carriers-popup/add-choice/add-choice.ts b/packages/merchant-tablet-ionic/src/pages/+carriers/add-carriers-popup/add-choice/add-choice.ts new file mode 100644 index 0000000..e216001 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+carriers/add-carriers-popup/add-choice/add-choice.ts @@ -0,0 +1,19 @@ +import { Component, EventEmitter } from '@angular/core'; + +@Component({ + selector: 'add-choice', + templateUrl: 'add-choice.html', + styleUrls: ['./add-choice.scss'], +}) +export class AddChoiceComponent { + choice: EventEmitter = new EventEmitter(); + + choiceType: number; + + constructor() {} + + changeChoice(choiceType) { + this.choiceType = choiceType === 'new' ? 2 : 1; + this.choice.emit(choiceType); + } +} diff --git a/packages/merchant-tablet-ionic/src/pages/+carriers/add-carriers-popup/add-new-carrier/account/account-form.component.html b/packages/merchant-tablet-ionic/src/pages/+carriers/add-carriers-popup/add-new-carrier/account/account-form.component.html new file mode 100644 index 0000000..f072c35 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+carriers/add-carriers-popup/add-new-carrier/account/account-form.component.html @@ -0,0 +1,84 @@ + diff --git a/packages/merchant-tablet-ionic/src/pages/+carriers/add-carriers-popup/add-new-carrier/account/account-form.component.scss b/packages/merchant-tablet-ionic/src/pages/+carriers/add-carriers-popup/add-new-carrier/account/account-form.component.scss new file mode 100644 index 0000000..c360ce4 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+carriers/add-carriers-popup/add-new-carrier/account/account-form.component.scss @@ -0,0 +1,14 @@ +.carrier-account-form { + .items-end { + display: flex; + align-items: flex-end; + + ion-item { + width: 100%; + + ion-checkbox { + margin: 25.5px 0; + } + } + } +} diff --git a/packages/merchant-tablet-ionic/src/pages/+carriers/add-carriers-popup/add-new-carrier/account/account-form.component.ts b/packages/merchant-tablet-ionic/src/pages/+carriers/add-carriers-popup/add-new-carrier/account/account-form.component.ts new file mode 100644 index 0000000..e7df00b --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+carriers/add-carriers-popup/add-new-carrier/account/account-form.component.ts @@ -0,0 +1,85 @@ +import { Component, Input, OnDestroy, OnInit, OnChanges } from '@angular/core'; +import { + FormBuilder, + FormGroup, + Validators, + AbstractControl, +} from '@angular/forms'; + +import { Subject } from 'rxjs'; + +import { TranslateService } from '@ngx-translate/core'; + +@Component({ + selector: 'account-form', + styleUrls: ['./account-form.component.scss'], + templateUrl: 'account-form.component.html', +}) +export class AccountFormComponent implements OnDestroy, OnInit, OnChanges { + userName: AbstractControl; + password: AbstractControl; + isActive: AbstractControl; + isSharedCarrier: AbstractControl; + repeatPassword: AbstractControl; + + $password: any; + + form: FormGroup; + + private _ngDestroy$ = new Subject(); + + constructor( + private formBuilder: FormBuilder, + private translate: TranslateService + ) {} + + ngOnInit() { + this.buildForm(this.formBuilder); + + this.bindFormControls(); + this.repeatPassword = this.form.get('repeatPassword'); + + this.$password = this.password.valueChanges.subscribe((res) => { + this.repeatPassword.setValue(''); + }); + } + + ngOnChanges(): void {} + + buildForm(formBuilder: FormBuilder) { + this.form = formBuilder.group({ + userName: ['', Validators.required], + password: ['', Validators.required], + repeatPassword: [ + '', + [ + (control: AbstractControl) => { + if (this.password) { + return control.value === this.password.value + ? null + : { validUrl: true }; + } else { + return null; + } + }, + ], + ], + isActive: [true, Validators.required], + isSharedCarrier: [false], + }); + } + + bindFormControls() { + this.userName = this.form.get('userName'); + this.password = this.form.get('password'); + this.repeatPassword = this.form.get('repeatPassword'); + this.isActive = this.form.get('isActive'); + this.isSharedCarrier = this.form.get('isSharedCarrier'); + } + + ngOnDestroy(): void { + if (this.$password) { + this.$password.unsubscribe(); + } + } +} diff --git a/packages/merchant-tablet-ionic/src/pages/+carriers/add-carriers-popup/add-new-carrier/account/account-form.module.ts b/packages/merchant-tablet-ionic/src/pages/+carriers/add-carriers-popup/add-new-carrier/account/account-form.module.ts new file mode 100644 index 0000000..9cc6914 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+carriers/add-carriers-popup/add-new-carrier/account/account-form.module.ts @@ -0,0 +1,21 @@ +import { CommonModule } from '@angular/common'; +import { NgModule } from '@angular/core'; +import { ReactiveFormsModule, FormsModule } from '@angular/forms'; +import { TranslateModule } from '@ngx-translate/core'; +import { FileUploadModule } from 'ng2-file-upload'; +import { AccountFormComponent } from './account-form.component'; +import { IonicModule } from '@ionic/angular'; + +@NgModule({ + imports: [ + CommonModule, + ReactiveFormsModule, + IonicModule, + FileUploadModule, + TranslateModule.forChild(), + FormsModule, + ], + declarations: [AccountFormComponent], + exports: [AccountFormComponent], +}) +export class CarrierAccountFormsModule {} diff --git a/packages/merchant-tablet-ionic/src/pages/+carriers/add-carriers-popup/add-new-carrier/add-new-carrier.html b/packages/merchant-tablet-ionic/src/pages/+carriers/add-carriers-popup/add-new-carrier/add-new-carrier.html new file mode 100644 index 0000000..4d0ab49 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+carriers/add-carriers-popup/add-new-carrier/add-new-carrier.html @@ -0,0 +1,68 @@ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + {{ 'CARRIERS_VIEW.ADD_CARRIER.PREVIOUS' | translate }} + + + {{ 'CARRIERS_VIEW.ADD_CARRIER.BACK' | translate }} + + + {{ 'CARRIERS_VIEW.ADD_CARRIER.NEXT' | translate }} + + + {{ 'CARRIERS_VIEW.ADD_CARRIER.NEXT' | translate }} + + + + +
+ diff --git a/packages/merchant-tablet-ionic/src/pages/+carriers/add-carriers-popup/add-new-carrier/add-new-carrier.module.ts b/packages/merchant-tablet-ionic/src/pages/+carriers/add-carriers-popup/add-new-carrier/add-new-carrier.module.ts new file mode 100644 index 0000000..0124dfa --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+carriers/add-carriers-popup/add-new-carrier/add-new-carrier.module.ts @@ -0,0 +1,34 @@ +import { NgModule } from '@angular/core'; +import { FormWizardModule } from '@ever-co/angular2-wizard'; +import { Ng2SmartTableModule } from 'ng2-smart-table'; +import { TranslateModule } from '@ngx-translate/core'; +import { AddNewCarrierComponent } from './add-new-carrier'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { FileUploadModule } from 'ng2-file-upload'; +import { CarrierBasicFormsModule } from './basic-info/basic-info-form.module'; +import { CarrierAccountFormsModule } from './account/account-form.module'; +import { CarrierLocationFormsModule } from './location/location-form.module'; +import { GoogleMapModule } from '../../../../@shared/google-map/google-map.module'; +import { IonicModule } from '@ionic/angular'; +import { CommonModule } from '@angular/common'; + +@NgModule({ + declarations: [AddNewCarrierComponent], + exports: [AddNewCarrierComponent], + imports: [ + FormWizardModule, + Ng2SmartTableModule, + FileUploadModule, + FormsModule, + ReactiveFormsModule, + CarrierBasicFormsModule, + CarrierAccountFormsModule, + CarrierLocationFormsModule, + GoogleMapModule, + TranslateModule.forChild(), + IonicModule, + CommonModule, + FormsModule, + ] +}) +export class AddNewCarriersPopupPageModule {} diff --git a/packages/merchant-tablet-ionic/src/pages/+carriers/add-carriers-popup/add-new-carrier/add-new-carrier.scss b/packages/merchant-tablet-ionic/src/pages/+carriers/add-carriers-popup/add-new-carrier/add-new-carrier.scss new file mode 100644 index 0000000..e5835f4 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+carriers/add-carriers-popup/add-new-carrier/add-new-carrier.scss @@ -0,0 +1,72 @@ +.add-new-carrier { + // .labels { + // display: inline-block; + // font-size: 1.1em; + // color: gray; + // opacity: 0.8; + // } + background: #f3f3f3 !important; + .coord-box { + margin: auto !important; + ion-checkbox { + padding-right: 15px !important; + margin: auto !important; + } + + // background: transparent !important; + div.item-inner { + border: none !important; + } + } + + .checkbox-icon { + border-radius: 50% !important; + } + ion-item { + // padding: 2% 5% !important; + // padding-top: 0 !important !important !important !important; + + padding-left: 19px !important; + padding-right: 19px !important; + // padding-bottom: 9px !important; + padding-top: 0px !important; + + border-radius: 3px !important; + + transform: scale(1, 0.9); + font-size: 1.3em !important; + background: #ffffffc9 !important; + } + ion-col { + padding: 0 5px !important; + } + ion-row { + background: transparent !important; + padding: 0 !important; + } + + .crete-carrier { + margin-top: 2%; + } + button { + // margin-top: 2% !important; + // margin-bottom: 2.4% !important; + } + + h5 { + margin-top: 0 !important; + margin-bottom: 2.5% !important; + } + + h2 { + padding: 0 !important; + } + + button { + text-transform: none !important; + } + + ion-button { + border: none !important; + } +} diff --git a/packages/merchant-tablet-ionic/src/pages/+carriers/add-carriers-popup/add-new-carrier/add-new-carrier.ts b/packages/merchant-tablet-ionic/src/pages/+carriers/add-carriers-popup/add-new-carrier/add-new-carrier.ts new file mode 100644 index 0000000..8452793 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+carriers/add-carriers-popup/add-new-carrier/add-new-carrier.ts @@ -0,0 +1,142 @@ +import { + Component, + OnDestroy, + OnInit, + Output, + EventEmitter, + Input, + ViewChild, + OnChanges, +} from '@angular/core'; +import { ICarrierCreateObject } from '@modules/server.common/interfaces/ICarrier'; +import { getDummyImage } from '@modules/server.common/utils'; +import { FileUploader } from 'ng2-file-upload'; +import { BasicInfoFormComponent } from './basic-info/basic-info-form.component'; +import { AccountFormComponent } from './account/account-form.component'; +import { Subject } from 'rxjs'; +import { LocationFormComponent } from './location/location-form.component'; + +@Component({ + selector: 'add-new-carrier', + templateUrl: './add-new-carrier.html', + styleUrls: ['./add-new-carrier.scss'], +}) +export class AddNewCarrierComponent implements OnInit, OnDestroy, OnChanges { + uploader: FileUploader; + emptyLogo: boolean = false; + + private _ngDestroy$ = new Subject(); + + @Output() + buttonClickEvent = new EventEmitter(); + + @Output() + onCompleteEvent = new EventEmitter(); + + @Input() + isDone: boolean; + + @ViewChild('basicInfoForm', { static: true }) + basicInfoForm: BasicInfoFormComponent; + + @ViewChild('accountForm', { static: true }) + accountForm: AccountFormComponent; + + @ViewChild('locationForm', { static: true }) + locationForm: LocationFormComponent; + + isNextStepOneAvailable: boolean = true; + isNextStepTwoAvailable: boolean = false; + isNextStepThreeAvailable: boolean = false; + backToPrevPage: boolean = false; + + $password: any; + + constructor() {} + + ngOnInit(): void {} + + ngOnChanges() {} + + get password() { + return this.accountForm.password.value; + } + + getCarrierCreateObject(): ICarrierCreateObject { + const letter = this.basicInfoForm.firstName.value + .charAt(0) + .toUpperCase(); + + let logo = ''; + + this.basicInfoForm.logo.value === '' + ? (logo = getDummyImage(300, 300, letter)) + : (logo = this.basicInfoForm.logo.value); + + const CarrierCreateObject: ICarrierCreateObject = { + firstName: this.basicInfoForm.firstName.value, + lastName: this.basicInfoForm.lastName.value, + email: this.basicInfoForm.email.value, + logo, + phone: this.basicInfoForm.phone.value, + + username: this.accountForm.userName.value, + isSharedCarrier: this.accountForm.isSharedCarrier.value, + + geoLocation: { + city: this.locationForm.city.value, + streetAddress: this.locationForm.street.value, + house: this.locationForm.house.value, + loc: { + type: 'Point', + coordinates: [ + this.locationForm.lng.value, + this.locationForm.lat.value, + ], + }, + countryId: this.locationForm.country.value, + postcode: this.locationForm.postcode.value, + }, + }; + + return CarrierCreateObject; + } + + backToStep1() { + this.isNextStepOneAvailable = true; + this.isNextStepTwoAvailable = false; + this.isNextStepThreeAvailable = false; + } + + toStep2event($event) { + this.isNextStepOneAvailable = false; + this.isNextStepTwoAvailable = true; + this.isNextStepThreeAvailable = false; + } + + nextToStep2() { + this.isNextStepOneAvailable = false; + this.isNextStepTwoAvailable = true; + this.isNextStepThreeAvailable = false; + } + + nextToStep3() { + this.isNextStepOneAvailable = false; + this.isNextStepTwoAvailable = false; + this.isNextStepThreeAvailable = true; + } + + clickPrevOrComplete(data) { + const prevOrComplete = data; + this.buttonClickEvent.emit(prevOrComplete); + } + + onClickComplete(data) { + this.onCompleteEvent.emit(data); + } + + ngOnDestroy(): void { + this._ngDestroy$.next(); + this._ngDestroy$.complete(); + } +} diff --git a/packages/merchant-tablet-ionic/src/pages/+carriers/add-carriers-popup/add-new-carrier/basic-info/basic-info-form.component.html b/packages/merchant-tablet-ionic/src/pages/+carriers/add-carriers-popup/add-new-carrier/basic-info/basic-info-form.component.html new file mode 100644 index 0000000..0f9c4f2 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+carriers/add-carriers-popup/add-new-carrier/basic-info/basic-info-form.component.html @@ -0,0 +1,131 @@ +
+ + + +
+ {{ + 'CARRIERS_VIEW.ADD_CARRIER.PROFILE_DETAILS' | translate + }} +
+
+
+ + + + + {{ + 'CARRIERS_VIEW.ADD_CARRIER.FIRST_NAME' | translate + }} + + +
+ {{ + 'CARRIERS_VIEW.ADD_CARRIER.FORMS.ERRORS.FIRST_NAME_REQUIRED' + | translate + }} + {{ + 'CARRIERS_VIEW.ADD_CARRIER.FORMS.ERRORS.MUST_CONTAIN_ONLY_LETTERS' + | translate + }} +
+
+ + + {{ + 'CARRIERS_VIEW.ADD_CARRIER.LAST_NAME' | translate + }} + + +
+ + {{ + 'CARRIERS_VIEW.ADD_CARRIER.FORMS.ERRORS.LAST_NAME_REQUIRED' + | translate + }} + + {{ + 'CARRIERS_VIEW.ADD_CARRIER.FORMS.ERRORS.MUST_CONTAIN_ONLY_LETTERS' + | translate + }} +
+
+
+ + + + + {{ + 'CARRIERS_VIEW.ADD_CARRIER.PHONE' | translate + }} + + +
+ + {{ + 'CARRIERS_VIEW.ADD_CARRIER.FORMS.ERRORS.LAST_NAME_REQUIRED' + | translate + }} + + + {{ + 'CARRIERS_VIEW.ADD_CARRIER.FORMS.ERRORS.PHONE_MUST_CONTAINS_ONLY(special_signs)AND_DIGIT_CHARACTER' + | translate + }} + +
+
+ + + + {{ 'CARRIERS_VIEW.ADD_CARRIER.EMAIL' | translate }} + + + +
+ {{ + 'CARRIERS_VIEW.ADD_CARRIER.FORMS.ERRORS.EMAIL_IS_REQUIRED' + | translate + }} +
+
+
+ + + + + +
+ +
+ Invalid image +
+ +
+ +
+
+
+
+
+
+
+
diff --git a/packages/merchant-tablet-ionic/src/pages/+carriers/add-carriers-popup/add-new-carrier/basic-info/basic-info-form.component.scss b/packages/merchant-tablet-ionic/src/pages/+carriers/add-carriers-popup/add-new-carrier/basic-info/basic-info-form.component.scss new file mode 100644 index 0000000..fe6dee7 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+carriers/add-carriers-popup/add-new-carrier/basic-info/basic-info-form.component.scss @@ -0,0 +1,17 @@ +.carrier-basic-info { + .email-err-wrapper { + padding: 0 19px; + text-align: left; + font-size: 14px; + } + + .validation-errors { + display: flex; + justify-content: flex-start; + padding-left: 20px; + box-sizing: border-box; + span { + color: red; + } + } +} diff --git a/packages/merchant-tablet-ionic/src/pages/+carriers/add-carriers-popup/add-new-carrier/basic-info/basic-info-form.component.ts b/packages/merchant-tablet-ionic/src/pages/+carriers/add-carriers-popup/add-new-carrier/basic-info/basic-info-form.component.ts new file mode 100644 index 0000000..41cae1a --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+carriers/add-carriers-popup/add-new-carrier/basic-info/basic-info-form.component.ts @@ -0,0 +1,109 @@ +import { Component, OnDestroy, OnInit, OnChanges } from '@angular/core'; +import { + FormBuilder, + FormGroup, + Validators, + AbstractControl, +} from '@angular/forms'; +import { Subject } from 'rxjs'; +import { ICarrierCreateObject } from '@modules/server.common/interfaces/ICarrier'; + +export type CarrierBasicInfo = Pick< + ICarrierCreateObject, + 'firstName' | 'lastName' | 'phone' | 'email' | 'logo' +>; + +@Component({ + selector: 'basic-info-form', + styleUrls: ['./basic-info-form.component.scss'], + templateUrl: 'basic-info-form.component.html', +}) +export class BasicInfoFormComponent implements OnDestroy, OnInit, OnChanges { + form: FormGroup; + + firstName: AbstractControl; + lastName: AbstractControl; + logo: AbstractControl; + phone: AbstractControl; + email: AbstractControl; + + private _ngDestroy$ = new Subject(); + private static phoneNumberRegex = /^[+]*[(]{0,1}[0-9]{1,4}[)]{0,1}[-\s\./0-9x]*$/; + + ngOnDestroy() { + this._ngDestroy$.next(); + this._ngDestroy$.complete(); + } + + constructor(private formBuilder: FormBuilder) { + this.buildForm(this.formBuilder); + } + + ngOnInit() { + this.bindFormControls(); + } + + ngOnChanges(): void {} + + get isFirstNameValid() { + return ( + this.firstName.errors && + (this.firstName.dirty || this.firstName.touched) + ); + } + + get isLastNameValid() { + return ( + this.lastName.errors && + (this.lastName.dirty || this.lastName.touched) + ); + } + + get isPhoneValid() { + return this.phone && (this.phone.dirty || this.phone.touched); + } + + get isEmailValid() { + return this.email && (this.email.dirty || this.email.touched); + } + + buildForm(formBuilder: FormBuilder) { + this.form = formBuilder.group({ + firstName: [ + '', + [ + Validators.required, + Validators.pattern(new RegExp(`^[a-z ,.'-]+$`, 'i')), + ], + ], + lastName: [ + '', + [ + Validators.required, + Validators.pattern(new RegExp(`^[a-z ,.'-]+$`, 'i')), + ], + ], + phone: [ + '', + [ + Validators.required, + Validators.pattern(BasicInfoFormComponent.phoneNumberRegex), + ], + ], + email: ['', Validators.required], + logo: [''], + }); + } + + bindFormControls() { + this.firstName = this.form.get('firstName'); + this.lastName = this.form.get('lastName'); + this.logo = this.form.get('logo'); + this.email = this.form.get('email'); + this.phone = this.form.get('phone'); + } + + deleteImg() { + this.logo.setValue(''); + } +} diff --git a/packages/merchant-tablet-ionic/src/pages/+carriers/add-carriers-popup/add-new-carrier/basic-info/basic-info-form.module.ts b/packages/merchant-tablet-ionic/src/pages/+carriers/add-carriers-popup/add-new-carrier/basic-info/basic-info-form.module.ts new file mode 100644 index 0000000..ab7d1db --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+carriers/add-carriers-popup/add-new-carrier/basic-info/basic-info-form.module.ts @@ -0,0 +1,21 @@ +import { CommonModule } from '@angular/common'; +import { NgModule } from '@angular/core'; +import { ReactiveFormsModule, FormsModule } from '@angular/forms'; +import { TranslateModule } from '@ngx-translate/core'; +import { BasicInfoFormComponent } from './basic-info-form.component'; +import { IonicModule } from '@ionic/angular'; +import { FileUploaderModule } from 'components/file-uploader/file-uploader.module'; + +@NgModule({ + imports: [ + CommonModule, + ReactiveFormsModule, + IonicModule, + TranslateModule.forChild(), + FormsModule, + FileUploaderModule, + ], + declarations: [BasicInfoFormComponent], + exports: [BasicInfoFormComponent], +}) +export class CarrierBasicFormsModule {} diff --git a/packages/merchant-tablet-ionic/src/pages/+carriers/add-carriers-popup/add-new-carrier/location/location-form.component.html b/packages/merchant-tablet-ionic/src/pages/+carriers/add-carriers-popup/add-new-carrier/location/location-form.component.html new file mode 100644 index 0000000..7ec3c19 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+carriers/add-carriers-popup/add-new-carrier/location/location-form.component.html @@ -0,0 +1,175 @@ +
+
+ + +
+ {{ 'CARRIERS_VIEW.ADD_CARRIER.LOCATION' | translate }} +
+
+
+ + + +
+ +
+
+
+ + + + + {{ + 'CARRIERS_VIEW.ADD_CARRIER.COUNTRY' | translate + }} + + {{ country.name }} + + + + + + + + {{ + 'CARRIERS_VIEW.ADD_CARRIER.CITY' | translate + }} + + + + + + + {{ + 'CARRIERS_VIEW.ADD_CARRIER.HOUSE' | translate + }} + + + + + + + + + {{ + 'CARRIERS_VIEW.ADD_CARRIER.STREET' | translate + }} + + + + + + + {{ + 'CARRIERS_VIEW.ADD_CARRIER.ZIP' | translate + }} + + + + + + + + + + {{ + 'WAREHOUSE_VIEW.NEW_ORDER_VIEW.SHOW_COORDINATES' + | translate + }} + + + + + + + + {{ + 'CARRIERS_VIEW.ADD_CARRIER.LATITUDE' | translate + }} + + + + + + {{ + 'CARRIERS_VIEW.ADD_CARRIER.LONGITUDE' | translate + }} + + + + +
+ + + + + {{ 'CARRIERS_VIEW.ADD_CARRIER.BACK' | translate }} + + + + {{ 'CARRIERS_VIEW.ADD_CARRIER.DONE' | translate }} + + + + + + + +
+ + +
+
+
+
+
diff --git a/packages/merchant-tablet-ionic/src/pages/+carriers/add-carriers-popup/add-new-carrier/location/location-form.component.scss b/packages/merchant-tablet-ionic/src/pages/+carriers/add-carriers-popup/add-new-carrier/location/location-form.component.scss new file mode 100644 index 0000000..bdafb5c --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+carriers/add-carriers-popup/add-new-carrier/location/location-form.component.scss @@ -0,0 +1,51 @@ +.carrier-location-form { + .location-find { + padding-top: 0px !important; + margin-top: 0px !important; + padding-bottom: 0px !important; + margin-bottom: 0px !important; + width: 50%; + margin: auto; + } + + ion-button { + border: none; + } + + .search-find { + ion-col.col { + div.input-wrapper { + ion-label.label.label-md { + padding-top: 0px; + padding-bottom: 0px; + margin-bottom: 0px; + margin-top: 0px; + } + } + } + div.item-inner { + border-bottom: 0px !important; + } + } + + .crete-carrier-location-title { + ion-col.col { + h5 { + padding-top: 0px !important; + padding-bottom: 0px !important; + margin-bottom: 0px !important; + margin-top: 0px !important; + } + } + } + + ion-grid.buttons-back-done { + padding-top: 0px !important; + padding-bottom: 0px !important; + margin-bottom: 0px !important; + margin-top: 0px !important; + } + .g-map { + height: 190px !important; + } +} diff --git a/packages/merchant-tablet-ionic/src/pages/+carriers/add-carriers-popup/add-new-carrier/location/location-form.component.ts b/packages/merchant-tablet-ionic/src/pages/+carriers/add-carriers-popup/add-new-carrier/location/location-form.component.ts new file mode 100644 index 0000000..5b11512 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+carriers/add-carriers-popup/add-new-carrier/location/location-form.component.ts @@ -0,0 +1,403 @@ +import { + Component, + OnDestroy, + ViewChild, + ElementRef, + EventEmitter, + Output, + OnInit, + OnChanges, +} from '@angular/core'; +import { + FormBuilder, + FormGroup, + Validators, + AbstractControl, +} from '@angular/forms'; + +import { Subject } from 'rxjs'; + +import { TranslateService } from '@ngx-translate/core'; +import { + countriesIdsToNamesArray, + CountryName, + Country, + getCountryName, +} from '@modules/server.common/entities/GeoLocation'; + +import { isEmpty } from 'lodash'; +import { Store } from 'services/store.service'; +import { WarehouseRouter } from '@modules/client.common.angular2/routers/warehouse-router.service'; +import { first } from 'rxjs/operators'; + +@Component({ + selector: 'carrier-location-form', + styleUrls: ['./location-form.component.scss'], + templateUrl: 'location-form.component.html', +}) +export class LocationFormComponent implements OnDestroy, OnInit, OnChanges { + @ViewChild('autocomplete', { static: true }) + searchElement: ElementRef; + + @Output() + buttonClickEventComplete = new EventEmitter(); + + @Output() + backToStep2event = new EventEmitter(); + + mapCoordEmitter = new EventEmitter(); + mapGeometryEmitter = new EventEmitter< + google.maps.GeocoderGeometry | google.maps.places.PlaceGeometry + >(); + + form: FormGroup; + + showCoordinates: boolean = false; + + city: AbstractControl; + street: AbstractControl; + house: AbstractControl; + country: AbstractControl; + lng: AbstractControl; + lat: AbstractControl; + // apartment: AbstractControl; + postcode: AbstractControl; + + OK: string = 'OK'; + CANCEL: string = 'CANCEL'; + PREFIX: string = 'CARRIERS_VIEW.ADD_CARRIER.'; + + private _ngDestroy$ = new Subject(); + private _lastUsedAddress: string; + + constructor( + private formBuilder: FormBuilder, + private translate: TranslateService, + private store: Store, + private warehouseRouter: WarehouseRouter + ) {} + + get buttonOK() { + return this._translate(this.PREFIX + this.OK); + } + + get buttonCancel() { + return this._translate(this.PREFIX + this.CANCEL); + } + + get countries(): Array<{ id: Country; name: CountryName }> { + return countriesIdsToNamesArray; + } + + ngOnInit() { + this._initGoogleAutocompleteApi(); + // this._tryFindNewCoordinates(); + + this.buildForm(this.formBuilder); + this.bindFormControls(); + + this._loadInitialData(); + } + + ngOnChanges(): void { + this._ngDestroy$.next(); + this._ngDestroy$.complete(); + } + + ngOnDestroy(): void { + this._ngDestroy$.next(); + this._ngDestroy$.complete(); + } + + buildForm(formBuilder: FormBuilder) { + this.form = formBuilder.group({ + city: ['', Validators.required], + street: ['', Validators.required], + house: ['', Validators.required], + lat: ['', Validators.required], + lng: ['', Validators.required], + country: ['', Validators.required], + postcode: [''], + // apartment: [''] + }); + } + + bindFormControls() { + this.city = this.form.get('city'); + this.street = this.form.get('street'); + this.house = this.form.get('house'); + this.country = this.form.get('country'); + this.lng = this.form.get('lng'); + this.lat = this.form.get('lat'); + // this.apartment = this.form.get('apartment'); + this.postcode = this.form.get('postcode'); + } + + toggleCoordinates() { + this.showCoordinates = !this.showCoordinates; + console.log('Toggle Cordinates'); + } + + textInputChange(val, input) { + if (input === 'lat' || input === 'lng') { + this._tryFindNewCoordinates(); + } else if (input !== 'apartment') { + this._tryFindNewAddress(); + } + } + + toStep2() { + this.backToStep2event.emit(); + } + + clickComplete() { + // let prevOrComplete = data; + console.log('send event to parent'); + this.buttonClickEventComplete.emit('complete'); + } + + private _tryFindNewCoordinates() { + const geocoder = new google.maps.Geocoder(); + + geocoder.geocode( + { + location: new google.maps.LatLng( + this.lat.value, + this.lng.value + ), + }, + (res, status) => { + if (status === google.maps.GeocoderStatus.OK) { + const location = res[0].geometry.location; + this.mapCoordEmitter.emit(location); + + const place = res[0]; + this._applyNewPlaceOnTheMap(place); + } + } + ); + } + + private _applyNewPlaceOnTheMap( + locationResult: + | google.maps.GeocoderResult + | google.maps.places.PlaceResult + ) { + if ( + locationResult.geometry === undefined || + locationResult.geometry === null + ) { + return; + } + + const loc = locationResult.geometry.location; + + this.lat.setValue(loc.lat()); + this.lng.setValue(loc.lng()); + + this.mapCoordEmitter.emit(loc); + this.mapGeometryEmitter.emit(locationResult.geometry); + this._gatherAddressInformation(locationResult); + } + + private _gatherAddressInformation( + locationResult: + | google.maps.GeocoderResult + | google.maps.places.PlaceResult + ) { + const longName = 'long_name'; + const shortName = 'short_name'; + + const neededAddressTypes = { + country: shortName, + locality: longName, + // 'neighborhood' is not need for now + // neighborhood: longName, + route: longName, + intersection: longName, + street_number: longName, + postal_code: longName, + administrative_area_level_1: shortName, + administrative_area_level_2: shortName, + administrative_area_level_3: shortName, + administrative_area_level_4: shortName, + administrative_area_level_5: shortName, + }; + + let streetName = ''; + let streetNumber = ''; + let country = ''; + let postcode = ''; + let city = ''; + + locationResult.address_components.forEach((address) => { + const addressType = address.types[0]; + const addressTypeKey = neededAddressTypes[addressType]; + + const val = address[addressTypeKey]; + + switch (addressType) { + case 'country': + country = val; + break; + case 'locality': + case 'administrative_area_level_1': + case 'administrative_area_level_2': + case 'administrative_area_level_3': + case 'administrative_area_level_4': + case 'administrative_area_level_5': + if (city === '') { + city = val; + } + break; + case 'route': + case 'intersection': + if (streetName === '') { + streetName = val; + } + break; + case 'street_number': + streetNumber = val; + break; + case 'postal_code': + postcode = val; + break; + } + }); + + this._setFormLocationValues( + country, + city, + streetName, + streetNumber, + postcode + ); + } + + private _translate(key: string): string { + let translationResult = ''; + + this.translate.get(key).subscribe((res) => { + translationResult = res; + }); + + return translationResult; + } + + private _setFormLocationValues( + country, + city, + streetName, + streetNumber, + postcode + ) { + if (!isEmpty(country)) { + this.country.setValue(Country[country].toString()); + } + if (!isEmpty(city)) { + this.city.setValue(city); + } + if (!isEmpty(streetName)) { + this.street.setValue(streetName); + } + if (!isEmpty(streetNumber)) { + this.house.setValue(streetNumber); + } + if (!isEmpty(postcode)) { + this.postcode.setValue(postcode); + } + } + + private async _initGoogleAutocompleteApi() { + if (this.searchElement) { + const inputElement = await this.searchElement['getInputElement'](); + + const autocomplete = new google.maps.places.Autocomplete( + inputElement + ); + + this._setupGoogleAutocompleteOptions(autocomplete); + this._listenForGoogleAutocompleteAddressChanges(autocomplete); + } + } + + private _setupGoogleAutocompleteOptions( + autocomplete: google.maps.places.Autocomplete + ) { + autocomplete.setComponentRestrictions({ country: ['us', 'bg', 'il'] }); + autocomplete['setFields'](['address_components', 'geometry']); + } + + private _listenForGoogleAutocompleteAddressChanges( + autocomplete: google.maps.places.Autocomplete + ) { + autocomplete.addListener('place_changed', (_) => { + const place: google.maps.places.PlaceResult = autocomplete.getPlace(); + this._applyNewPlaceOnTheMap(place); + }); + } + + private _tryFindNewAddress() { + const house = this.house.value; + const city = this.city.value; + const streetAddress = this.street.value; + const countryName = getCountryName(+this.country.value); + + if ( + isEmpty(streetAddress) || + isEmpty(house) || + isEmpty(city) || + isEmpty(countryName) + ) { + return; + } + + const newAddress = `${house}${streetAddress}${city}${countryName}`; + + if (newAddress !== this._lastUsedAddress) { + this._lastUsedAddress = newAddress; + + const geocoder = new google.maps.Geocoder(); + + geocoder.geocode( + { + address: `${streetAddress} ${house}, ${city}`, + componentRestrictions: { country: countryName }, + }, + (results, status) => { + if (status === google.maps.GeocoderStatus.OK) { + const formattedAddress = results[0].formatted_address; + const place: google.maps.GeocoderResult = results[0]; + + this._applyNewPlaceOnTheMap(place); + this._applyFormattedAddress(formattedAddress); + } + } + ); + } + } + + private async _applyFormattedAddress(address: string) { + if (this.searchElement) { + const inputElement = await this.searchElement['getInputElement'](); + inputElement.value = address; + } + } + + private async _loadInitialData() { + const { geoLocation } = await this.warehouseRouter + .get(this.store.warehouseId, false) + .pipe(first()) + .toPromise(); + + this.city.setValue(geoLocation.city); + this.street.setValue(geoLocation.streetAddress); + this.house.setValue(geoLocation.house); + this.country.setValue(geoLocation.countryId.toString()); + this.lng.setValue(geoLocation.coordinates.lng); + this.lat.setValue(geoLocation.coordinates.lat); + this.postcode.setValue(geoLocation.postcode); + + this._tryFindNewCoordinates(); + } +} diff --git a/packages/merchant-tablet-ionic/src/pages/+carriers/add-carriers-popup/add-new-carrier/location/location-form.module.ts b/packages/merchant-tablet-ionic/src/pages/+carriers/add-carriers-popup/add-new-carrier/location/location-form.module.ts new file mode 100644 index 0000000..fa4a775 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+carriers/add-carriers-popup/add-new-carrier/location/location-form.module.ts @@ -0,0 +1,23 @@ +import { CommonModule } from '@angular/common'; +import { NgModule } from '@angular/core'; +import { ReactiveFormsModule, FormsModule } from '@angular/forms'; +import { TranslateModule } from '@ngx-translate/core'; +import { FileUploadModule } from 'ng2-file-upload'; +import { LocationFormComponent } from './location-form.component'; +import { GoogleMapModule } from '../../../../../@shared/google-map/google-map.module'; +import { IonicModule } from '@ionic/angular'; + +@NgModule({ + imports: [ + CommonModule, + ReactiveFormsModule, + IonicModule, + FileUploadModule, + GoogleMapModule, + TranslateModule.forChild(), + FormsModule, + ], + declarations: [LocationFormComponent], + exports: [LocationFormComponent], +}) +export class CarrierLocationFormsModule {} diff --git a/packages/merchant-tablet-ionic/src/pages/+carriers/add-carriers-popup/carriers-catalog/address.component.ts b/packages/merchant-tablet-ionic/src/pages/+carriers/add-carriers-popup/carriers-catalog/address.component.ts new file mode 100644 index 0000000..1eb0a95 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+carriers/add-carriers-popup/carriers-catalog/address.component.ts @@ -0,0 +1,44 @@ +import { Component, OnInit, Input } from '@angular/core'; +import { ViewCell } from 'ng2-smart-table'; +import Carrier from '@modules/server.common/entities/Carrier'; + +@Component({ + styles: [ + ` + h1 { + font-weight: normal; + } + .address { + font-style: italic; + text-decoration: underline; + display: block; + } + `, + ], + template: ` + {{ carrier?.geoLocation.city }} + ({{ carrier?.geoLocation.postcode }}) + + {{ + getStoreFullAddress(carrier) + }} + `, +}) +export class AddressComponent implements ViewCell, OnInit { + value: string | number; + rowData: any; + carrier: Carrier; + + ngOnInit(): void { + this.carrier = this.rowData.carrier; + } + + getStoreFullAddress(carrier: Carrier) { + const geoLocation = carrier.geoLocation; + const fullAddress = `${geoLocation.city}, ${geoLocation.streetAddress} ${geoLocation.house}`; + return fullAddress; + } +} diff --git a/packages/merchant-tablet-ionic/src/pages/+carriers/add-carriers-popup/carriers-catalog/carriers-catalog.html b/packages/merchant-tablet-ionic/src/pages/+carriers/add-carriers-popup/carriers-catalog/carriers-catalog.html new file mode 100644 index 0000000..34f60c9 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+carriers/add-carriers-popup/carriers-catalog/carriers-catalog.html @@ -0,0 +1,7 @@ + + diff --git a/packages/merchant-tablet-ionic/src/pages/+carriers/add-carriers-popup/carriers-catalog/carriers-catalog.scss b/packages/merchant-tablet-ionic/src/pages/+carriers/add-carriers-popup/carriers-catalog/carriers-catalog.scss new file mode 100644 index 0000000..12afe12 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+carriers/add-carriers-popup/carriers-catalog/carriers-catalog.scss @@ -0,0 +1,2 @@ +carriers-catalog { +} diff --git a/packages/merchant-tablet-ionic/src/pages/+carriers/add-carriers-popup/carriers-catalog/carriers-catalog.ts b/packages/merchant-tablet-ionic/src/pages/+carriers/add-carriers-popup/carriers-catalog/carriers-catalog.ts new file mode 100644 index 0000000..2dae94f --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+carriers/add-carriers-popup/carriers-catalog/carriers-catalog.ts @@ -0,0 +1,119 @@ +import { Component, EventEmitter, OnDestroy } from '@angular/core'; +import { DomSanitizer } from '@angular/platform-browser'; +import Carrier from '@modules/server.common/entities/Carrier'; +import { CarrierRouter } from '@modules/client.common.angular2/routers/carrier-router.service'; +import { WarehouseRouter } from '@modules/client.common.angular2/routers/warehouse-router.service'; +import { AddressComponent } from './address.component'; +import { Store } from '../../../../services/store.service'; +import { first, takeUntil } from 'rxjs/operators'; +import { TranslateService } from '@ngx-translate/core'; +import { LocalDataSource } from 'ng2-smart-table'; +import { forkJoin, Subject, Observable } from 'rxjs'; + +@Component({ + selector: 'carriers-catalog', + templateUrl: 'carriers-catalog.html', +}) +export class CarriersCatalogComponent implements OnDestroy { + settingsSmartTable: object; + sourceSmartTable = new LocalDataSource(); + selecteCarriers: Carrier[]; + hasChanges: EventEmitter = new EventEmitter(); + + private _ngDestroy$ = new Subject(); + + constructor( + private readonly carrierRouter: CarrierRouter, + private readonly warehouseRouter: WarehouseRouter, + private readonly store: Store, + private readonly _sanitizer: DomSanitizer, + private readonly _translateService: TranslateService + ) { + this._loadSettingsSmartTable(); + this._loadDataSmartTable(); + } + + selectCarriersTmp(ev) { + this.selecteCarriers = ev.selected; + this.hasChanges.emit(); + } + + private _loadSettingsSmartTable() { + const columnTitlePrefix = 'CARRIERS_VIEW.CARRIERS_CATALOG.'; + const getTranslate = (name: string): Observable => + this._translateService.get(columnTitlePrefix + name); + + forkJoin( + getTranslate('NAME'), + getTranslate('PHONE'), + getTranslate('ADDRESS'), + getTranslate('LOGO') + ) + .pipe(takeUntil(this._ngDestroy$)) + .subscribe(([name, phone, address, logo]) => { + this.settingsSmartTable = { + actions: false, + selectMode: 'multi', + columns: { + name: { title: name }, + phone: { title: phone }, + address: { + title: address, + type: 'custom', + renderComponent: AddressComponent, + }, + logo: { + title: logo, + type: 'html', + valuePrepareFunction: (_, carrier: Carrier) => { + return this._sanitizer.bypassSecurityTrustHtml( + `
+ +
` + ); + }, + filter: false, + }, + }, + pager: { + display: true, + perPage: 3, + }, + }; + }); + } + + private async _loadDataSmartTable() { + const warehouse = await this.warehouseRouter + .get(this.store.warehouseId) + .pipe(first()) + .toPromise(); + + const loadData = (carriers) => { + const carriersVM = carriers.map((c: Carrier) => { + return { + name: c.firstName + ' ' + c.lastName, + phone: c.phone, + carrier: c, + logo: c.logo, + id: c.id, + }; + }); + + this.sourceSmartTable.load(carriersVM); + }; + + this.carrierRouter.getAllActive().subscribe((carriers: Carrier[]) => { + loadData( + carriers.filter( + (c: Carrier) => !warehouse.usedCarriersIds.includes(c.id) + ) + ); + }); + } + + ngOnDestroy() { + this._ngDestroy$.next(); + this._ngDestroy$.complete(); + } +} diff --git a/packages/merchant-tablet-ionic/src/pages/+carriers/carrier-addr-popup/carrier-addr-popup.html b/packages/merchant-tablet-ionic/src/pages/+carriers/carrier-addr-popup/carrier-addr-popup.html new file mode 100644 index 0000000..749a05a --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+carriers/carrier-addr-popup/carrier-addr-popup.html @@ -0,0 +1,35 @@ +
+

+ {{ 'CARRIERS_VIEW.ADDRESS.CARRIER_ADDRESS' | translate }} +

+ +
+ +
+ {{ 'CARRIERS_VIEW.ADDRESS.COUNTRY' | translate }}: +
+
{{ country }}
+
+ +
+ {{ 'CARRIERS_VIEW.ADD_CARRIER.STREET' | translate }}: +
+
{{ street }}
+
+ +
+ {{ 'CARRIERS_VIEW.ADD_CARRIER.HOUSE' | translate }}: +
+
{{ house }}
+
+ +
+ {{ 'CARRIERS_VIEW.ADDRESS.COORDINATES' | translate }}: +
+
{{ coordinates }}
+
+
+
+
diff --git a/packages/merchant-tablet-ionic/src/pages/+carriers/carrier-addr-popup/carrier-addr-popup.module.ts b/packages/merchant-tablet-ionic/src/pages/+carriers/carrier-addr-popup/carrier-addr-popup.module.ts new file mode 100644 index 0000000..76ec185 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+carriers/carrier-addr-popup/carrier-addr-popup.module.ts @@ -0,0 +1,17 @@ +import { NgModule } from '@angular/core'; +import { CarrierAddrPopupPage } from './carrier-addr-popup'; +import { TranslateModule } from '@ngx-translate/core'; +import { IonicModule } from '@ionic/angular'; +import { CommonModule } from '@angular/common'; +import { FormsModule } from '@angular/forms'; + +@NgModule({ + declarations: [CarrierAddrPopupPage], + imports: [ + TranslateModule.forChild(), + IonicModule, + CommonModule, + FormsModule, + ] +}) +export class CarrierAddrPopupPageModule {} diff --git a/packages/merchant-tablet-ionic/src/pages/+carriers/carrier-addr-popup/carrier-addr-popup.scss b/packages/merchant-tablet-ionic/src/pages/+carriers/carrier-addr-popup/carrier-addr-popup.scss new file mode 100644 index 0000000..92f52e3 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+carriers/carrier-addr-popup/carrier-addr-popup.scss @@ -0,0 +1,37 @@ +.carrier-addr-popup { + background-color: #fff; + height: 100%; + display: flex !important; + flex-direction: column; + .infoRow { + display: block; + // width:80%; + margin: 0 auto; + margin-bottom: 10px; + line-height: 25px; + } + + .label, + .info { + display: inline-block; + // width:48%; + font-size: 1.2em; + } + .label { + width: 25%; + font-style: italic; + } + .info { + color: #0074d9 !important; + font-weight: bold; + } + + .addrInfo { + margin: 0 auto; + width: 90%; + } + + .map { + min-height: 50% !important; + } +} diff --git a/packages/merchant-tablet-ionic/src/pages/+carriers/carrier-addr-popup/carrier-addr-popup.ts b/packages/merchant-tablet-ionic/src/pages/+carriers/carrier-addr-popup/carrier-addr-popup.ts new file mode 100644 index 0000000..4e14ca9 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+carriers/carrier-addr-popup/carrier-addr-popup.ts @@ -0,0 +1,72 @@ +import { Component, ViewChild, ElementRef, Input, OnInit } from '@angular/core'; +import GeoLocation from '@modules/server.common/entities/GeoLocation'; +import { ModalController } from '@ionic/angular'; + +@Component({ + selector: 'carrier-addr-popup', + templateUrl: 'carrier-addr-popup.html', + styleUrls: ['./carrier-addr-popup.scss'], +}) +export class CarrierAddrPopupPage implements OnInit { + @ViewChild('gmap', { static: true }) + gmapElement: ElementRef; + + map: google.maps.Map; + + myLatLng = { lat: 0, lng: 0 }; + + @Input() + geoLocation: GeoLocation; + + city: any; + country: any; + street: any; + house: any; + apartment: any; + coordinates: any; + + constructor(public modalCtrl: ModalController) {} + + ngOnInit(): void { + const geoLocation = this.geoLocation; + + this.city = geoLocation.city; + + this.country = geoLocation.countryName; + + this.street = geoLocation.streetAddress; + + this.house = geoLocation.house; + + this.coordinates = [ + geoLocation.coordinates.lat, + geoLocation.coordinates.lng, + ]; + + this.myLatLng.lat = this.coordinates[0]; + + this.myLatLng.lng = this.coordinates[1]; + + this.loadMap(); + } + + loadMap() { + const mapProp = { + center: this.myLatLng, + zoom: 15, + mapTypeId: google.maps.MapTypeId.ROADMAP, + }; + + this.map = new google.maps.Map(this.gmapElement.nativeElement, mapProp); + + const marker = new google.maps.Marker({ + position: this.myLatLng, + map: this.map, + title: 'Your Warehouse!', + }); + } + + cancelModal() { + this.modalCtrl.dismiss(); + } +} diff --git a/packages/merchant-tablet-ionic/src/pages/+carriers/carrier-delete-popup/carrier-delete-popup.html b/packages/merchant-tablet-ionic/src/pages/+carriers/carrier-delete-popup/carrier-delete-popup.html new file mode 100644 index 0000000..94ac7a2 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+carriers/carrier-delete-popup/carrier-delete-popup.html @@ -0,0 +1,27 @@ +
+

Are you sure you want to delete the following carrierData?

+
+ +
+
{{ carrierData?.name }}
+
+ {{ carrierData?.status | titlecase }} +
+
+ {{ carrierData?.phone }} +
+
{{ carrierData?.addresses }}
+
+
+
+ + Confirm + + Cancel +
+
diff --git a/packages/merchant-tablet-ionic/src/pages/+carriers/carrier-delete-popup/carrier-delete-popup.module.ts b/packages/merchant-tablet-ionic/src/pages/+carriers/carrier-delete-popup/carrier-delete-popup.module.ts new file mode 100644 index 0000000..03e7dc0 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+carriers/carrier-delete-popup/carrier-delete-popup.module.ts @@ -0,0 +1,26 @@ +import { NgModule } from '@angular/core'; +import { FormWizardModule } from '@ever-co/angular2-wizard'; +import { Ng2SmartTableModule } from 'ng2-smart-table'; +import { TranslateModule } from '@ngx-translate/core'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { FileUploadModule } from 'ng2-file-upload'; +import { CarrierDeletePopupPage } from './carrier-delete-popup'; +import { IonicModule } from '@ionic/angular'; +import { CommonModule } from '@angular/common'; + +@NgModule({ + declarations: [CarrierDeletePopupPage], + entryComponents: [CarrierDeletePopupPage], + imports: [ + FormWizardModule, + Ng2SmartTableModule, + FileUploadModule, + FormsModule, + ReactiveFormsModule, + TranslateModule.forChild(), + IonicModule, + CommonModule, + FormsModule, + ], +}) +export class CarrierDeletePopupModule {} diff --git a/packages/merchant-tablet-ionic/src/pages/+carriers/carrier-delete-popup/carrier-delete-popup.scss b/packages/merchant-tablet-ionic/src/pages/+carriers/carrier-delete-popup/carrier-delete-popup.scss new file mode 100644 index 0000000..298d66b --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+carriers/carrier-delete-popup/carrier-delete-popup.scss @@ -0,0 +1,54 @@ +.carrier-delete-wrapper { + display: flex !important; + .modal-wrapper { + width: 300px; + height: 300px; + left: calc(50% - (300px / 2)); + top: calc(50% - (300px / 2)); + .ion-page { + .carrier-delete { + border: 1px solid rgba(0, 0, 0, 0.125); + background: white; + width: 100%; + height: 100%; + border-radius: 5px; + display: flex; + flex-direction: column; + align-items: center; + justify-content: space-evenly; + h4 { + width: 90%; + text-align: center; + } + .control { + display: flex; + justify-content: space-between; + width: 90%; + } + .details { + display: flex; + align-items: center; + width: 90%; + border: 1px solid rgba(0, 0, 0, 0.125); + border-radius: 4px; + img { + width: 128px; + height: 128px; + } + .good { + color: green; + } + .bad { + color: crimson; + } + .text { + display: flex; + flex-direction: column; + align-items: center; + width: 100%; + } + } + } + } + } +} diff --git a/packages/merchant-tablet-ionic/src/pages/+carriers/carrier-delete-popup/carrier-delete-popup.ts b/packages/merchant-tablet-ionic/src/pages/+carriers/carrier-delete-popup/carrier-delete-popup.ts new file mode 100644 index 0000000..b091e69 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+carriers/carrier-delete-popup/carrier-delete-popup.ts @@ -0,0 +1,46 @@ +import { Component, Input } from '@angular/core'; +import { WarehouseRouter } from '@modules/client.common.angular2/routers/warehouse-router.service'; +import { first } from 'rxjs/operators'; +import { ModalController } from '@ionic/angular'; + +@Component({ + selector: 'carrier-delete-popup', + templateUrl: 'carrier-delete-popup.html', + styleUrls: ['./carrier-delete-popup.scss'], +}) +export class CarrierDeletePopupPage { + @Input() + carrierData: any; + + constructor( + public modalCtrl: ModalController, + private warehouseRouter: WarehouseRouter + ) {} + + get getWarehouseId() { + return localStorage.getItem('_warehouseId'); + } + + cancelModal() { + this.modalCtrl.dismiss(); + } + + async deleteCarrier() { + const carrierId = this.carrierData.carrier.id; + + const id = this.getWarehouseId; + + const merchant = await this.warehouseRouter + .get(id) + .pipe(first()) + .toPromise(); + + merchant.usedCarriersIds = merchant.usedCarriersIds.filter( + (x) => x !== carrierId + ); + + await this.warehouseRouter.save(merchant); + + this.cancelModal(); + } +} diff --git a/packages/merchant-tablet-ionic/src/pages/+carriers/carrier-deliveries-popup/carrier-deliveries-popup.html b/packages/merchant-tablet-ionic/src/pages/+carriers/carrier-deliveries-popup/carrier-deliveries-popup.html new file mode 100644 index 0000000..0228a65 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+carriers/carrier-deliveries-popup/carrier-deliveries-popup.html @@ -0,0 +1,51 @@ + +

+ {{ 'CARRIERS_VIEW.DELIVERIES_POP_UP.CARRIER_DELIVERIES' | translate }} +

+ + + + {{ 'CARRIERS_VIEW.DELIVERIES_POP_UP.ALL_DELIVERIES' | translate + }}: + + {{ carrier?.numberOfDeliveries }} + + + {{ 'CARRIERS_VIEW.DELIVERIES_POP_UP.DELIVERIES_TODAY' | + translate }}: + {{ carrier?.deliveriesCountToday }} + + + {{ 'CARRIERS_VIEW.DELIVERIES_POP_UP.TOTAL_DISTANCE_TODAY' | + translate }}: + {{ carrier?.totalDistanceToday }} + + +
+ + + +
+
+ + +

+ {{ 'CARRIERS_VIEW.DELIVERIES_POP_UP.NO_DELIVERIES' | + translate }} +

+
+
+
+
diff --git a/packages/merchant-tablet-ionic/src/pages/+carriers/carrier-deliveries-popup/carrier-deliveries-popup.module.ts b/packages/merchant-tablet-ionic/src/pages/+carriers/carrier-deliveries-popup/carrier-deliveries-popup.module.ts new file mode 100644 index 0000000..e3a3004 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+carriers/carrier-deliveries-popup/carrier-deliveries-popup.module.ts @@ -0,0 +1,31 @@ +import { NgModule } from '@angular/core'; +import { CarrierDeliveriesPopupPage } from './carrier-deliveries-popup'; +import { TranslateModule } from '@ngx-translate/core'; +import { Ng2SmartTableModule } from 'ng2-smart-table'; +import { CustomerComponent } from '../../../components/carrier-deliveries-table/customer'; +import { DeliveryComponent } from '../../../components/carrier-deliveries-table/delivery'; +import { StatusComponent } from '../../../components/carrier-deliveries-table/status'; +import { WarehouseComponent } from '../../../components/carrier-deliveries-table/warehouse'; +import { IonicModule } from '@ionic/angular'; +import { CommonModule } from '@angular/common'; +import { FormsModule } from '@angular/forms'; + +const COMPONENTS = [ + CarrierDeliveriesPopupPage, + CustomerComponent, + DeliveryComponent, + StatusComponent, + WarehouseComponent, +]; + +@NgModule({ + declarations: COMPONENTS, + imports: [ + TranslateModule.forChild(), + Ng2SmartTableModule, + IonicModule, + CommonModule, + FormsModule, + ] +}) +export class CarrierDeliveriesPopupPageModule {} diff --git a/packages/merchant-tablet-ionic/src/pages/+carriers/carrier-deliveries-popup/carrier-deliveries-popup.scss b/packages/merchant-tablet-ionic/src/pages/+carriers/carrier-deliveries-popup/carrier-deliveries-popup.scss new file mode 100644 index 0000000..bcb6702 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+carriers/carrier-deliveries-popup/carrier-deliveries-popup.scss @@ -0,0 +1,53 @@ +.carrier-deliveries-popup { + background-color: #fff; + height: 100%; + width: 100%; + + .body { + overflow-y: scroll; + height: 78%; + position: relative; + } + .info { + color: #0074d9; + font-weight: bold; + } + + table { + margin-top: 0 !important; + } + + #basicInfo { + font-size: 1.1em; + min-height: 10%; + background: #eee; + span { + margin: auto; + text-transform: none !important; + font-style: italic; + } + } + + .ng2-smart-pagination-nav { + li { + width: 50% !important; + } + } + + .ng2-smart-titles { + height: 55px !important; + } + + .pagination { + line-height: 1 !important; + position: absolute; + width: auto; + bottom: 0; + left: 50%; + -webkit-transform: translateX(-50%); + -moz-transform: translateX(-50%); + -ms-transform: translateX(-50%); + -o-transform: translateX(-50%); + transform: translateX(-50%); + } +} diff --git a/packages/merchant-tablet-ionic/src/pages/+carriers/carrier-deliveries-popup/carrier-deliveries-popup.ts b/packages/merchant-tablet-ionic/src/pages/+carriers/carrier-deliveries-popup/carrier-deliveries-popup.ts new file mode 100644 index 0000000..bd92f88 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+carriers/carrier-deliveries-popup/carrier-deliveries-popup.ts @@ -0,0 +1,179 @@ +import { Component, Input, OnInit, OnDestroy } from '@angular/core'; +import Carrier from '@modules/server.common/entities/Carrier'; +import Order from '@modules/server.common/entities/Order'; +import { CarrierOrdersRouter } from '@modules/client.common.angular2/routers/carrier-orders-router.service'; +import Warehouse from '@modules/server.common/entities/Warehouse'; +import { LocalDataSource } from 'ng2-smart-table'; +import { Subject, Observable, forkJoin } from 'rxjs'; +import { TranslateService } from '@ngx-translate/core'; +import { takeUntil } from 'rxjs/operators'; +import { CustomerComponent } from '../../../components/carrier-deliveries-table/customer'; +import { DeliveryComponent } from '../../../components/carrier-deliveries-table/delivery'; +import { StatusComponent } from '../../../components/carrier-deliveries-table/status'; +import { WarehouseComponent } from '../../../components/carrier-deliveries-table/warehouse'; +import { ModalController } from '@ionic/angular'; +import UserOrder from '@modules/server.common/entities/UserOrder'; + +@Component({ + selector: 'carrier-deliveries-popup', + templateUrl: 'carrier-deliveries-popup.html', + styleUrls: ['./carrier-deliveries-popup.scss'], +}) +export class CarrierDeliveriesPopupPage implements OnInit, OnDestroy { + @Input() + carrier: Carrier; + + orders: Order[]; + + showNoDeliveryIcon: boolean; + + settingsSmartTable: object; + + sourceSmartTable = new LocalDataSource(); + + private _ngDestroy$ = new Subject(); + private $orders: any; + + constructor( + private modalCtrl: ModalController, + private readonly carrierOrdersRouter: CarrierOrdersRouter, + private readonly translateService: TranslateService + ) { + this._loadSettingsSmartTable(); + } + + getUserName(order: Order) { + const user: UserOrder = order.user; + + if (user.firstName) { + if (user.lastName) { + return user.firstName + ' ' + user.lastName; + } + return user.firstName; + } + } + + getStoreFullAddress(order: Order) { + const store: Warehouse = order.warehouse as Warehouse; + const geoLocation = store.geoLocation; + const fullAddress = `${geoLocation.city}, ${geoLocation.streetAddress} ${geoLocation.house}`; + return fullAddress; + } + + getTotalDeliveryTime(order: Order) { + const start = order.createdAt; + + const end = new Date(order.deliveryTime); + + let delta = Math.abs(start.getTime() - end.getTime()) / 1000; + + const days = Math.floor(delta / 86400); + delta -= days * 86400; + + const hours = Math.floor(delta / 3600) % 24; + delta -= hours * 3600; + + const minutes = Math.floor(delta / 60) % 60; + delta -= minutes * 60; + + const seconds = delta % 60; + let secondsStr = seconds.toString(); + secondsStr = secondsStr.substring(0, secondsStr.indexOf('.')); + + let h = '0' + hours; + h = h.substr(-2); + let min = '0' + minutes; + min = min.substr(-2); + let sec = '0' + secondsStr; + sec = sec.substr(-2); + + return `${days !== 0 ? days + 'days ' : ''} + ${hours} : ${min} : ${sec}`; + } + + cancelModal() { + this.modalCtrl.dismiss(); + } + + ngOnInit(): void { + const loadData = (orders) => { + const dataVM = orders.map((o: Order) => { + let status = o.isCompleted ? 'Completed' : ''; + status += o.isPaid ? 'Paid' : ''; + return { + customer: this.getUserName(o), + status, + warehouse: o.warehouse['name'], + delivery: this.getTotalDeliveryTime(o), + order: o, + }; + }); + + this.sourceSmartTable.load(dataVM); + }; + + this.$orders = this.carrierOrdersRouter + .get(this.carrier.id, { + populateWarehouse: true, + completion: 'completed', + }) + .subscribe((orders) => { + this.orders = orders; + loadData(orders); + if (this.orders.length === 0) { + this.showNoDeliveryIcon = true; + } + }); + } + + ngOnDestroy(): void { + if (this.$orders) { + this.$orders.unsubscribe(); + } + } + + private _loadSettingsSmartTable() { + const columnTitlePrefix = 'CARRIERS_VIEW.DELIVERIES_POP_UP.'; + const getTranslate = (name: string): Observable => + this.translateService.get(columnTitlePrefix + name); + + forkJoin( + getTranslate('CUSTOMER'), + getTranslate('WAREHOUSE'), + getTranslate('STATUS'), + getTranslate('DELIVERY') + ) + .pipe(takeUntil(this._ngDestroy$)) + .subscribe(([customer, warehouse, status, delivery]) => { + this.settingsSmartTable = { + actions: true, + columns: { + customer: { + title: customer, + type: 'custom', + renderComponent: CustomerComponent, + }, + warehouse: { + title: warehouse, + type: 'custom', + renderComponent: WarehouseComponent, + }, + status: { + title: status, + type: 'custom', + renderComponent: StatusComponent, + }, + delivery: { + title: delivery, + type: 'custom', + renderComponent: DeliveryComponent, + }, + }, + pager: { + display: true, + perPage: 5, + }, + }; + }); + } +} diff --git a/packages/merchant-tablet-ionic/src/pages/+carriers/carrier-edit-popup/account/account-form.component.html b/packages/merchant-tablet-ionic/src/pages/+carriers/carrier-edit-popup/account/account-form.component.html new file mode 100644 index 0000000..469be4f --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+carriers/carrier-edit-popup/account/account-form.component.html @@ -0,0 +1,83 @@ + diff --git a/packages/merchant-tablet-ionic/src/pages/+carriers/carrier-edit-popup/account/account-form.component.scss b/packages/merchant-tablet-ionic/src/pages/+carriers/carrier-edit-popup/account/account-form.component.scss new file mode 100644 index 0000000..cac23a4 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+carriers/carrier-edit-popup/account/account-form.component.scss @@ -0,0 +1,10 @@ +.edit-carrier-account { + .items-end { + display: flex; + align-items: flex-end; + + ion-item { + width: 100%; + } + } +} diff --git a/packages/merchant-tablet-ionic/src/pages/+carriers/carrier-edit-popup/account/account-form.component.ts b/packages/merchant-tablet-ionic/src/pages/+carriers/carrier-edit-popup/account/account-form.component.ts new file mode 100644 index 0000000..daecde1 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+carriers/carrier-edit-popup/account/account-form.component.ts @@ -0,0 +1,88 @@ +import { Component, OnDestroy, OnInit, OnChanges, Input } from '@angular/core'; +import { + FormBuilder, + FormGroup, + Validators, + AbstractControl, +} from '@angular/forms'; +import Carrier from '@modules/server.common/entities/Carrier'; + +@Component({ + selector: 'account-form', + styleUrls: ['./account-form.component.scss'], + templateUrl: 'account-form.component.html', +}) +export class AccountFormComponent implements OnDestroy, OnInit, OnChanges { + @Input() + carrier: Carrier; + + userName: AbstractControl; + password: AbstractControl; + isActive: AbstractControl; + isSharedCarrier: AbstractControl; + repeatPassword: AbstractControl; + $password: any; + form: FormGroup; + + constructor(private formBuilder: FormBuilder) {} + + ngOnInit() { + this.buildForm(this.formBuilder); + + this.bindFormControls(); + this.repeatPassword = this.form.get('repeatPassword'); + + this.$password = this.password.valueChanges.subscribe((res) => { + this.repeatPassword.setValue(''); + }); + + this.loadData(); + } + + ngOnChanges(): void {} + + buildForm(formBuilder: FormBuilder) { + this.form = formBuilder.group({ + userName: ['', Validators.required], + password: [''], + repeatPassword: [ + '', + [ + (control: AbstractControl) => { + if (this.password) { + return control.value === this.password.value + ? null + : { validUrl: true }; + } else { + return null; + } + }, + ], + ], + isActive: [true, Validators.required], + isSharedCarrier: [false], + }); + } + + bindFormControls() { + this.userName = this.form.get('userName'); + this.password = this.form.get('password'); + this.repeatPassword = this.form.get('repeatPassword'); + this.isActive = this.form.get('isActive'); + this.isSharedCarrier = this.form.get('isSharedCarrier'); + } + + ngOnDestroy(): void { + if (this.$password) { + this.$password.unsubscribe(); + } + } + + private loadData() { + if (this.carrier) { + this.userName.setValue(this.carrier.username); + this.isActive.setValue(this.carrier.isActive); + this.isSharedCarrier.setValue(this.carrier.isSharedCarrier); + } + } +} diff --git a/packages/merchant-tablet-ionic/src/pages/+carriers/carrier-edit-popup/account/account-form.module.ts b/packages/merchant-tablet-ionic/src/pages/+carriers/carrier-edit-popup/account/account-form.module.ts new file mode 100644 index 0000000..9cc6914 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+carriers/carrier-edit-popup/account/account-form.module.ts @@ -0,0 +1,21 @@ +import { CommonModule } from '@angular/common'; +import { NgModule } from '@angular/core'; +import { ReactiveFormsModule, FormsModule } from '@angular/forms'; +import { TranslateModule } from '@ngx-translate/core'; +import { FileUploadModule } from 'ng2-file-upload'; +import { AccountFormComponent } from './account-form.component'; +import { IonicModule } from '@ionic/angular'; + +@NgModule({ + imports: [ + CommonModule, + ReactiveFormsModule, + IonicModule, + FileUploadModule, + TranslateModule.forChild(), + FormsModule, + ], + declarations: [AccountFormComponent], + exports: [AccountFormComponent], +}) +export class CarrierAccountFormsModule {} diff --git a/packages/merchant-tablet-ionic/src/pages/+carriers/carrier-edit-popup/basic-info/basic-info-form.component.html b/packages/merchant-tablet-ionic/src/pages/+carriers/carrier-edit-popup/basic-info/basic-info-form.component.html new file mode 100644 index 0000000..1cd7e5f --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+carriers/carrier-edit-popup/basic-info/basic-info-form.component.html @@ -0,0 +1,133 @@ +
+ + + +
+ {{ + 'CARRIERS_VIEW.ADD_CARRIER.PROFILE_DETAILS' | translate + }} +
+
+
+ + + + + {{ + 'CARRIERS_VIEW.ADD_CARRIER.FIRST_NAME' | translate + }} + + +
+ {{ + 'CARRIERS_VIEW.ADD_CARRIER.FORMS.ERRORS.FIRST_NAME_REQUIRED' + | translate + }} + {{ + 'CARRIERS_VIEW.ADD_CARRIER.FORMS.ERRORS.MUST_CONTAIN_ONLY_LETTERS' + | translate + }} +
+
+ + + {{ + 'CARRIERS_VIEW.ADD_CARRIER.LAST_NAME' | translate + }} + + +
+ + {{ + 'CARRIERS_VIEW.ADD_CARRIER.FORMS.ERRORS.LAST_NAME_REQUIRED' + | translate + }} + + {{ + 'CARRIERS_VIEW.ADD_CARRIER.FORMS.ERRORS.MUST_CONTAIN_ONLY_LETTERS' + | translate + }} +
+
+
+ + + + + {{ + 'CARRIERS_VIEW.ADD_CARRIER.PHONE' | translate + }} + + +
+ + {{ + 'CARRIERS_VIEW.ADD_CARRIER.FORMS.ERRORS.LAST_NAME_REQUIRED' + | translate + }} + + + {{ + 'CARRIERS_VIEW.ADD_CARRIER.FORMS.ERRORS.PHONE_MUST_CONTAINS_ONLY(special_signs)AND_DIGIT_CHARACTER' + | translate + }} + +
+
+ + + + {{ 'CARRIERS_VIEW.ADD_CARRIER.EMAIL' | translate }} + + + +
+ {{ + 'CARRIERS_VIEW.ADD_CARRIER.FORMS.ERRORS.EMAIL_IS_REQUIRED' + | translate + }} +
+
+
+ + + + + + +
+ +
+ Invalid image +
+ +
+ +
+
+
+
+
+
+
+
diff --git a/packages/merchant-tablet-ionic/src/pages/+carriers/carrier-edit-popup/basic-info/basic-info-form.component.scss b/packages/merchant-tablet-ionic/src/pages/+carriers/carrier-edit-popup/basic-info/basic-info-form.component.scss new file mode 100644 index 0000000..25fa94a --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+carriers/carrier-edit-popup/basic-info/basic-info-form.component.scss @@ -0,0 +1,17 @@ +.edit-carrier-basic-info { + .email-err-wrapper { + padding: 0 19px; + text-align: left; + font-size: 14px; + } + + .validation-errors { + display: flex; + justify-content: flex-start; + padding-left: 20px; + box-sizing: border-box; + span { + color: red; + } + } +} diff --git a/packages/merchant-tablet-ionic/src/pages/+carriers/carrier-edit-popup/basic-info/basic-info-form.component.ts b/packages/merchant-tablet-ionic/src/pages/+carriers/carrier-edit-popup/basic-info/basic-info-form.component.ts new file mode 100644 index 0000000..ea67340 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+carriers/carrier-edit-popup/basic-info/basic-info-form.component.ts @@ -0,0 +1,122 @@ +import { Component, OnDestroy, OnInit, OnChanges, Input } from '@angular/core'; +import { + FormBuilder, + FormGroup, + Validators, + AbstractControl, +} from '@angular/forms'; + +import { Subject } from 'rxjs'; +import { ICarrierCreateObject } from '@modules/server.common/interfaces/ICarrier'; +import Carrier from '@modules/server.common/entities/Carrier'; + +export type CarrierBasicInfo = Pick< + ICarrierCreateObject, + 'firstName' | 'lastName' | 'phone' | 'email' | 'logo' +>; + +@Component({ + selector: 'basic-info-form', + styleUrls: ['./basic-info-form.component.scss'], + templateUrl: 'basic-info-form.component.html', +}) +export class BasicInfoFormComponent implements OnDestroy, OnInit { + @Input() + carrier: Carrier; + + form: FormGroup; + firstName: AbstractControl; + lastName: AbstractControl; + logo: AbstractControl; + phone: AbstractControl; + email: AbstractControl; + + private _ngDestroy$ = new Subject(); + private static phoneNumberRegex = /^[+]*[(]{0,1}[0-9]{1,4}[)]{0,1}[-\s\./0-9x]*$/; + + ngOnDestroy() { + this._ngDestroy$.next(); + this._ngDestroy$.complete(); + } + + constructor(private formBuilder: FormBuilder) { + this.buildForm(this.formBuilder); + } + + ngOnInit() { + this.bindFormControls(); + this.loadData(); + } + + get isFirstNameValid() { + return ( + this.firstName.errors && + (this.firstName.dirty || this.firstName.touched) + ); + } + + get isLastNameValid() { + return ( + this.lastName.errors && + (this.lastName.dirty || this.lastName.touched) + ); + } + + get isPhoneValid() { + return this.phone && (this.phone.dirty || this.phone.touched); + } + + get isEmailValid() { + return this.email && (this.email.dirty || this.email.touched); + } + + buildForm(formBuilder: FormBuilder) { + this.form = formBuilder.group({ + firstName: [ + '', + [ + Validators.required, + Validators.pattern(new RegExp(`^[a-z ,.'-]+$`, 'i')), + ], + ], + lastName: [ + '', + [ + Validators.required, + Validators.pattern(new RegExp(`^[a-z ,.'-]+$`, 'i')), + ], + ], + phone: [ + '', + [ + Validators.required, + Validators.pattern(BasicInfoFormComponent.phoneNumberRegex), + ], + ], + email: ['', Validators.required], + logo: [''], + }); + } + + bindFormControls() { + this.firstName = this.form.get('firstName'); + this.lastName = this.form.get('lastName'); + this.logo = this.form.get('logo'); + this.email = this.form.get('email'); + this.phone = this.form.get('phone'); + } + + deleteImg() { + this.logo.setValue(''); + } + + private loadData() { + if (this.carrier) { + this.firstName.setValue(this.carrier.firstName); + this.lastName.setValue(this.carrier.lastName); + this.logo.setValue(this.carrier.logo); + this.email.setValue(this.carrier.email); + this.phone.setValue(this.carrier.phone); + } + } +} diff --git a/packages/merchant-tablet-ionic/src/pages/+carriers/carrier-edit-popup/basic-info/basic-info-form.module.ts b/packages/merchant-tablet-ionic/src/pages/+carriers/carrier-edit-popup/basic-info/basic-info-form.module.ts new file mode 100644 index 0000000..0d28c66 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+carriers/carrier-edit-popup/basic-info/basic-info-form.module.ts @@ -0,0 +1,21 @@ +import { CommonModule } from '@angular/common'; +import { NgModule } from '@angular/core'; +import { ReactiveFormsModule, FormsModule } from '@angular/forms'; +import { TranslateModule } from '@ngx-translate/core'; +import { BasicInfoFormComponent } from './basic-info-form.component'; +import { IonicModule } from '@ionic/angular'; +import { FileUploaderModule } from 'components/file-uploader/file-uploader.module'; + +@NgModule({ + imports: [ + CommonModule, + ReactiveFormsModule, + IonicModule, + FormsModule, + TranslateModule.forChild(), + FileUploaderModule, + ], + declarations: [BasicInfoFormComponent], + exports: [BasicInfoFormComponent], +}) +export class CarrierBasicFormsModule {} diff --git a/packages/merchant-tablet-ionic/src/pages/+carriers/carrier-edit-popup/carrier-edit-popup.html b/packages/merchant-tablet-ionic/src/pages/+carriers/carrier-edit-popup/carrier-edit-popup.html new file mode 100644 index 0000000..ae8af29 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+carriers/carrier-edit-popup/carrier-edit-popup.html @@ -0,0 +1,76 @@ +
+

+ {{ 'CARRIERS_VIEW.EDIT_CARRIER.EDIT_CARRIER' | translate }} +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + {{ 'CARRIERS_VIEW.ADD_CARRIER.PREVIOUS' | translate }} + + + {{ 'CARRIERS_VIEW.ADD_CARRIER.BACK' | translate }} + + + {{ 'CARRIERS_VIEW.ADD_CARRIER.NEXT' | translate }} + + + {{ 'CARRIERS_VIEW.ADD_CARRIER.NEXT' | translate }} + + + + +
diff --git a/packages/merchant-tablet-ionic/src/pages/+carriers/carrier-edit-popup/carrier-edit-popup.module.ts b/packages/merchant-tablet-ionic/src/pages/+carriers/carrier-edit-popup/carrier-edit-popup.module.ts new file mode 100644 index 0000000..acab446 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+carriers/carrier-edit-popup/carrier-edit-popup.module.ts @@ -0,0 +1,34 @@ +import { NgModule } from '@angular/core'; +import { FormWizardModule } from '@ever-co/angular2-wizard'; +import { Ng2SmartTableModule } from 'ng2-smart-table'; +import { TranslateModule } from '@ngx-translate/core'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { FileUploadModule } from 'ng2-file-upload'; +import { CarrierBasicFormsModule } from './basic-info/basic-info-form.module'; +import { CarrierAccountFormsModule } from './account/account-form.module'; +import { CarrierLocationFormsModule } from './location/location-form.module'; +import { GoogleMapModule } from '../../../@shared/google-map/google-map.module'; +import { CarrierEditPopupPage } from './carrier-edit-popup'; +import { CarrierService } from '../../../services/carrier.service'; +import { IonicModule } from '@ionic/angular'; +import { CommonModule } from '@angular/common'; + +@NgModule({ + declarations: [CarrierEditPopupPage], + imports: [ + FormWizardModule, + Ng2SmartTableModule, + FileUploadModule, + FormsModule, + ReactiveFormsModule, + CarrierBasicFormsModule, + CarrierAccountFormsModule, + CarrierLocationFormsModule, + GoogleMapModule, + TranslateModule.forChild(), + IonicModule, + CommonModule, + ], + providers: [CarrierService] +}) +export class CarrierEditPopupModule {} diff --git a/packages/merchant-tablet-ionic/src/pages/+carriers/carrier-edit-popup/carrier-edit-popup.scss b/packages/merchant-tablet-ionic/src/pages/+carriers/carrier-edit-popup/carrier-edit-popup.scss new file mode 100644 index 0000000..5982f9f --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+carriers/carrier-edit-popup/carrier-edit-popup.scss @@ -0,0 +1,37 @@ +.carrier-edit-wrapper { + display: flex !important; + .modal-wrapper { + width: 650px; + height: auto; + display: inline-table; + left: calc(50% - (650px / 2)); + top: calc(50% - (600px / 2)); + .ion-page { + display: contents; + .carrier-edit { + background: #f3f3f3 !important; + padding: 0 15px; + .custom-title-popup { + width: 100% !important; + } + border: 1px solid rgba(0, 0, 0, 0.125); + background: white; + width: 100%; + height: 100%; + border-radius: 5px; + display: flex; + flex-direction: column; + align-items: center; + justify-content: space-between; + + ion-button { + border: none; + } + + ion-grid { + width: 100%; + } + } + } + } +} diff --git a/packages/merchant-tablet-ionic/src/pages/+carriers/carrier-edit-popup/carrier-edit-popup.ts b/packages/merchant-tablet-ionic/src/pages/+carriers/carrier-edit-popup/carrier-edit-popup.ts new file mode 100644 index 0000000..ead525b --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+carriers/carrier-edit-popup/carrier-edit-popup.ts @@ -0,0 +1,178 @@ +import { + Component, + OnDestroy, + OnInit, + Output, + EventEmitter, + Input, + ViewChild, + OnChanges, +} from '@angular/core'; +import { FormBuilder } from '@angular/forms'; +import { FileUploader } from 'ng2-file-upload'; +import { BasicInfoFormComponent } from './basic-info/basic-info-form.component'; +import { AccountFormComponent } from './account/account-form.component'; +import { Subject } from 'rxjs'; +import { LocationFormComponent } from './location/location-form.component'; +import { CarrierService } from '../../../services/carrier.service'; +import { ModalController } from '@ionic/angular'; +import Carrier from '@modules/server.common/entities/Carrier'; +import { WarehouseCarriersRouter } from '@modules/client.common.angular2/routers/warehouse-carriers-router.service'; + +@Component({ + selector: 'carrier-edit-popup', + templateUrl: 'carrier-edit-popup.html', + styleUrls: ['./carrier-edit-popup.scss'], +}) +export class CarrierEditPopupPage implements OnInit, OnDestroy, OnChanges { + uploader: FileUploader; + emptyLogo: boolean = false; + + private _ngDestroy$ = new Subject(); + + @Output() + buttonClickEvent = new EventEmitter(); + + @Output() + onCompleteEvent = new EventEmitter(); + + @Input() + isDone: boolean; + + @Input() + carrier: Carrier; + + @ViewChild('basicInfoForm', { static: true }) + basicInfoForm: BasicInfoFormComponent; + + @ViewChild('accountForm', { static: true }) + accountForm: AccountFormComponent; + + @ViewChild('locationForm', { static: true }) + locationForm: LocationFormComponent; + + isNextStepOneAvailable: boolean = true; + isNextStepTwoAvailable: boolean = false; + isNextStepThreeAvailable: boolean = false; + backToPrevPage: boolean = false; + + $password: any; + + constructor( + public modalCtrl: ModalController, + private _carrierService: CarrierService, + private warehouseCarriersRouter: WarehouseCarriersRouter + ) {} + + ngOnInit(): void {} + + ngOnChanges() {} + + async _updateCarrier() { + const basic = { + firstName: this.basicInfoForm.firstName.value, + lastName: this.basicInfoForm.lastName.value, + phone: this.basicInfoForm.phone.value, + logo: this.basicInfoForm.logo.value, + email: this.basicInfoForm.email.value, + }; + + const geoLocation = { + countryId: Number(this.locationForm.country.value), + city: this.locationForm.city.value, + streetAddress: this.locationForm.street.value, + postcode: this.locationForm.postcode.value, + house: this.locationForm.house.value, + loc: { + type: 'Point', + coordinates: [ + Number(this.locationForm.lng.value), + Number(this.locationForm.lat.value), + ], + }, + }; + + const account = { + isActive: this.accountForm.isActive.value, + isSharedCarrier: this.accountForm.isSharedCarrier.value, + username: this.accountForm.userName.value, + password: this.accountForm.password.value, + repeatPassword: this.accountForm.repeatPassword.value, + }; + + const carrier = this.carrier; + + const carrierCreateObj = { + firstName: basic.firstName, + lastName: basic.lastName, + phone: basic.phone, + email: basic.email, + logo: basic.logo, + + username: account.username, + isActive: account.isActive, + isSharedCarrier: account.isSharedCarrier, + + geoLocation, + }; + + if (account.password) { + await this.warehouseCarriersRouter.updatePassword( + carrier.id, + account.password + ); + } + + const id = await this._carrierService + .updateCarrier(carrier.id, carrierCreateObj) + .toPromise(); + + this.cancelModal(); + } + + get password() { + return this.accountForm.password.value; + } + + backToStep1() { + this.isNextStepOneAvailable = true; + this.isNextStepTwoAvailable = false; + this.isNextStepThreeAvailable = false; + } + + toStep2event($event) { + this.isNextStepOneAvailable = false; + this.isNextStepTwoAvailable = true; + this.isNextStepThreeAvailable = false; + } + + nextToStep2() { + this.isNextStepOneAvailable = false; + this.isNextStepTwoAvailable = true; + this.isNextStepThreeAvailable = false; + } + + nextToStep3() { + this.isNextStepOneAvailable = false; + this.isNextStepTwoAvailable = false; + this.isNextStepThreeAvailable = true; + } + + cancelModal() { + this.modalCtrl.dismiss(); + } + + clickPrevOrComplete(data) { + const prevOrComplete = data; + this.buttonClickEvent.emit(prevOrComplete); + } + + onClickComplete(data) { + this._updateCarrier(); + } + + ngOnDestroy(): void { + this._ngDestroy$.next(); + this._ngDestroy$.complete(); + } +} diff --git a/packages/merchant-tablet-ionic/src/pages/+carriers/carrier-edit-popup/location/location-form.component.html b/packages/merchant-tablet-ionic/src/pages/+carriers/carrier-edit-popup/location/location-form.component.html new file mode 100644 index 0000000..faed671 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+carriers/carrier-edit-popup/location/location-form.component.html @@ -0,0 +1,174 @@ +
+
+ + +
{{ 'CARRIERS_VIEW.ADD_CARRIER.LOCATION' | translate }}
+
+
+ + + +
+ +
+
+
+ + + + + {{ + 'CARRIERS_VIEW.ADD_CARRIER.COUNTRY' | translate + }} + + {{ country.name }} + + + + + + + + {{ + 'CARRIERS_VIEW.ADD_CARRIER.CITY' | translate + }} + + + + + + + {{ + 'CARRIERS_VIEW.ADD_CARRIER.HOUSE' | translate + }} + + + + + + + + + {{ + 'CARRIERS_VIEW.ADD_CARRIER.STREET' | translate + }} + + + + + + + {{ + 'CARRIERS_VIEW.ADD_CARRIER.ZIP' | translate + }} + + + + + + + + + + {{ + 'WAREHOUSE_VIEW.NEW_ORDER_VIEW.SHOW_COORDINATES' + | translate + }} + + + + + + + + {{ + 'CARRIERS_VIEW.ADD_CARRIER.LATITUDE' | translate + }} + + + + + + {{ + 'CARRIERS_VIEW.ADD_CARRIER.LONGITUDE' | translate + }} + + + + +
+ + + + + {{ 'CARRIERS_VIEW.ADD_CARRIER.BACK' | translate }} + + + + {{ 'CARRIERS_VIEW.ADD_CARRIER.DONE' | translate }} + + + + + + + +
+ + +
+
+
+
+
diff --git a/packages/merchant-tablet-ionic/src/pages/+carriers/carrier-edit-popup/location/location-form.component.scss b/packages/merchant-tablet-ionic/src/pages/+carriers/carrier-edit-popup/location/location-form.component.scss new file mode 100644 index 0000000..b2b32b4 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+carriers/carrier-edit-popup/location/location-form.component.scss @@ -0,0 +1,65 @@ +.carrier-location-form { + .location-find { + padding-top: 0px !important; + margin-top: 0px !important; + padding-bottom: 0px !important; + margin-bottom: 0px !important; + width: 70%; + margin: auto; + } + + ion-button { + border: none; + } + + ion-select { + padding: 0; + margin: 5px 0 6px 0; + } + + .search-find { + ion-col.col { + div.input-wrapper { + ion-label.label.label-md { + padding-top: 0px; + padding-bottom: 0px; + margin-bottom: 0px; + margin-top: 0px; + } + } + } + div.item-inner { + border-bottom: 0px !important; + } + } + + .crete-carrier-location-title { + ion-col.col { + h5 { + padding-top: 0px !important; + padding-bottom: 0px !important; + margin-bottom: 0px !important; + margin-top: 0px !important; + } + } + } + + ion-grid.buttons-back-done { + padding-top: 0px !important; + padding-bottom: 0px !important; + margin-bottom: 0px !important; + margin-top: 0px !important; + } + .g-map { + height: 190px !important; + } + + .items-end { + display: flex; + align-items: flex-end; + padding-bottom: 5px; + ion-item { + width: 100%; + } + } +} diff --git a/packages/merchant-tablet-ionic/src/pages/+carriers/carrier-edit-popup/location/location-form.component.ts b/packages/merchant-tablet-ionic/src/pages/+carriers/carrier-edit-popup/location/location-form.component.ts new file mode 100644 index 0000000..da1e08a --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+carriers/carrier-edit-popup/location/location-form.component.ts @@ -0,0 +1,389 @@ +import { + Component, + Input, + OnDestroy, + ViewChild, + ElementRef, + EventEmitter, + Output, + OnInit, + OnChanges, +} from '@angular/core'; +import { + FormBuilder, + FormGroup, + Validators, + AbstractControl, +} from '@angular/forms'; + +import { TranslateService } from '@ngx-translate/core'; +import GeoLocation, { + countriesIdsToNamesArray, + CountryName, + Country, + getCountryName, +} from '@modules/server.common/entities/GeoLocation'; +import { isEmpty } from 'lodash'; +import Carrier from '@modules/server.common/entities/Carrier'; + +@Component({ + selector: 'carrier-location-form', + styleUrls: ['./location-form.component.scss'], + templateUrl: 'location-form.component.html', +}) +export class LocationFormComponent implements OnDestroy, OnInit, OnChanges { + @ViewChild('autocomplete', { static: true }) + searchElement: ElementRef; + + @Input() + carrier: Carrier; + + @Output() + buttonClickEventComplete = new EventEmitter(); + @Output() + backToStep2event = new EventEmitter(); + + mapCoordEmitter = new EventEmitter(); + + mapGeometryEmitter = new EventEmitter< + google.maps.GeocoderGeometry | google.maps.places.PlaceGeometry + >(); + + private _lastUsedAddress: string; + + form: FormGroup; + + showCoordinates: boolean = false; + + city: AbstractControl; + street: AbstractControl; + house: AbstractControl; + country: AbstractControl; + lng: AbstractControl; + lat: AbstractControl; + // public apartment: AbstractControl; + postcode: AbstractControl; + + OK: string = 'OK'; + CANCEL: string = 'CANCEL'; + PREFIX: string = 'CARRIERS_VIEW.ADD_CARRIER.'; + + constructor( + private formBuilder: FormBuilder, + private translate: TranslateService + ) {} + + ngOnInit() { + this._initGoogleAutocompleteApi(); + // this._tryFindNewCoordinates(); + + this.buildForm(this.formBuilder); + this.bindFormControls(); + this.loadData(); + } + + ngOnChanges(): void {} + + get buttonOK() { + return this._translate(this.PREFIX + this.OK); + } + + get buttonCancel() { + return this._translate(this.PREFIX + this.CANCEL); + } + + buildForm(formBuilder: FormBuilder) { + this.form = formBuilder.group({ + city: ['', Validators.required], + street: ['', Validators.required], + house: ['', Validators.required], + lat: ['', Validators.required], + lng: ['', Validators.required], + country: ['', Validators.required], + postcode: [''], + // apartment: [''] + }); + } + + bindFormControls() { + this.city = this.form.get('city'); + this.street = this.form.get('street'); + this.house = this.form.get('house'); + this.country = this.form.get('country'); + this.lng = this.form.get('lng'); + this.lat = this.form.get('lat'); + // this.apartment = this.form.get('apartment'); + this.postcode = this.form.get('postcode'); + } + + get countries(): Array<{ id: Country; name: CountryName }> { + return countriesIdsToNamesArray; + } + + toggleCoordinates() { + this.showCoordinates = !this.showCoordinates; + console.log('Toggle Cordinates'); + } + + textInputChange(val, input) { + if (input === 'lat' || input === 'lng') { + this._tryFindNewCoordinates(); + } else { + this._tryFindNewAddress(); + } + } + + toStep2() { + this.backToStep2event.emit(); + } + + clickComplete() { + // let prevOrComplete = data; + console.log('send event to parent'); + this.buttonClickEventComplete.emit('complete'); + } + + private _tryFindNewCoordinates() { + const geocoder = new google.maps.Geocoder(); + + geocoder.geocode( + { + location: new google.maps.LatLng( + this.lat.value, + this.lng.value + ), + }, + (res, status) => { + if (status === google.maps.GeocoderStatus.OK) { + const location = res[0].geometry.location; + this.mapCoordEmitter.emit(location); + + const place = res[0]; + this._applyNewPlaceOnTheMap(place); + } + } + ); + } + + private _applyNewPlaceOnTheMap( + locationResult: + | google.maps.GeocoderResult + | google.maps.places.PlaceResult + ) { + if ( + locationResult.geometry === undefined || + locationResult.geometry === null + ) { + return; + } + + const loc = locationResult.geometry.location; + + this.lat.setValue(loc.lat()); + this.lng.setValue(loc.lng()); + + this.mapCoordEmitter.emit(loc); + this.mapGeometryEmitter.emit(locationResult.geometry); + this._gatherAddressInformation(locationResult); + } + + private _gatherAddressInformation( + locationResult: + | google.maps.GeocoderResult + | google.maps.places.PlaceResult + ) { + const longName = 'long_name'; + const shortName = 'short_name'; + + const neededAddressTypes = { + country: shortName, + locality: longName, + // 'neighborhood' is not need for now + // neighborhood: longName, + route: longName, + intersection: longName, + street_number: longName, + postal_code: longName, + administrative_area_level_1: shortName, + administrative_area_level_2: shortName, + administrative_area_level_3: shortName, + administrative_area_level_4: shortName, + administrative_area_level_5: shortName, + }; + + let streetName = ''; + let streetNumber = ''; + let country = ''; + let postcode = ''; + let city = ''; + + locationResult.address_components.forEach((address) => { + const addressType = address.types[0]; + const addressTypeKey = neededAddressTypes[addressType]; + + const val = address[addressTypeKey]; + + switch (addressType) { + case 'country': + country = val; + break; + case 'locality': + case 'administrative_area_level_1': + case 'administrative_area_level_2': + case 'administrative_area_level_3': + case 'administrative_area_level_4': + case 'administrative_area_level_5': + if (city === '') { + city = val; + } + break; + case 'route': + case 'intersection': + if (streetName === '') { + streetName = val; + } + break; + case 'street_number': + streetNumber = val; + break; + case 'postal_code': + postcode = val; + break; + } + }); + + this._setFormLocationValues( + country, + city, + streetName, + streetNumber, + postcode + ); + } + + private _translate(key: string): string { + let translationResult = ''; + + this.translate.get(key).subscribe((res) => { + translationResult = res; + }); + + return translationResult; + } + + private _setFormLocationValues( + country, + city, + streetName, + streetNumber, + postcode + ) { + if (!isEmpty(country)) { + this.country.setValue(Country[country].toString()); + } + if (!isEmpty(city)) { + this.city.setValue(city); + } + if (!isEmpty(streetName)) { + this.street.setValue(streetName); + } + if (!isEmpty(streetNumber)) { + this.house.setValue(streetNumber); + } + if (!isEmpty(postcode)) { + this.postcode.setValue(postcode); + } + } + + private async _initGoogleAutocompleteApi() { + if (this.searchElement) { + const inputElement = await this.searchElement['getInputElement'](); + const autocomplete = new google.maps.places.Autocomplete( + inputElement + ); + + this._setupGoogleAutocompleteOptions(autocomplete); + this._listenForGoogleAutocompleteAddressChanges(autocomplete); + } + } + + private _setupGoogleAutocompleteOptions( + autocomplete: google.maps.places.Autocomplete + ) { + autocomplete.setComponentRestrictions({ country: ['us', 'bg', 'il'] }); + autocomplete['setFields'](['address_components', 'geometry']); + } + + private _listenForGoogleAutocompleteAddressChanges( + autocomplete: google.maps.places.Autocomplete + ) { + autocomplete.addListener('place_changed', (_) => { + const place: google.maps.places.PlaceResult = autocomplete.getPlace(); + this._applyNewPlaceOnTheMap(place); + }); + } + + private _tryFindNewAddress() { + const house = this.house.value; + const city = this.city.value; + const streetAddress = this.street.value; + const countryName = getCountryName(+this.country.value); + + if ( + isEmpty(streetAddress) || + isEmpty(house) || + isEmpty(city) || + isEmpty(countryName) + ) { + return; + } + + const newAddress = `${house}${streetAddress}${city}${countryName}`; + + if (newAddress !== this._lastUsedAddress) { + this._lastUsedAddress = newAddress; + + const geocoder = new google.maps.Geocoder(); + + geocoder.geocode( + { + address: `${streetAddress} ${house}, ${city}`, + componentRestrictions: { country: countryName }, + }, + (results, status) => { + if (status === google.maps.GeocoderStatus.OK) { + const formattedAddress = results[0].formatted_address; + const place: google.maps.GeocoderResult = results[0]; + + this._applyNewPlaceOnTheMap(place); + this._applyFormattedAddress(formattedAddress); + } + } + ); + } + } + + private async _applyFormattedAddress(address: string) { + if (this.searchElement) { + const inputElement = await this.searchElement['getInputElement'](); + inputElement.value = address; + } + } + + private loadData() { + if (this.carrier) { + const carrierGeoLocation: GeoLocation = this.carrier.geoLocation; + + this.city.setValue(carrierGeoLocation.city); + this.street.setValue(carrierGeoLocation.streetAddress); + this.house.setValue(carrierGeoLocation.house); + this.lat.setValue(carrierGeoLocation.coordinates.lat); + this.lng.setValue(carrierGeoLocation.coordinates.lng); + this.country.setValue(carrierGeoLocation.countryId.toString()); + + this._tryFindNewCoordinates(); + } + } + + ngOnDestroy(): void {} +} diff --git a/packages/merchant-tablet-ionic/src/pages/+carriers/carrier-edit-popup/location/location-form.module.ts b/packages/merchant-tablet-ionic/src/pages/+carriers/carrier-edit-popup/location/location-form.module.ts new file mode 100644 index 0000000..8f442c4 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+carriers/carrier-edit-popup/location/location-form.module.ts @@ -0,0 +1,23 @@ +import { CommonModule } from '@angular/common'; +import { NgModule } from '@angular/core'; +import { ReactiveFormsModule, FormsModule } from '@angular/forms'; +import { TranslateModule } from '@ngx-translate/core'; +import { FileUploadModule } from 'ng2-file-upload'; +import { LocationFormComponent } from './location-form.component'; +import { GoogleMapModule } from '../../../../@shared/google-map/google-map.module'; +import { IonicModule } from '@ionic/angular'; + +@NgModule({ + imports: [ + CommonModule, + ReactiveFormsModule, + IonicModule, + FileUploadModule, + GoogleMapModule, + TranslateModule.forChild(), + FormsModule, + ], + declarations: [LocationFormComponent], + exports: [LocationFormComponent], +}) +export class CarrierLocationFormsModule {} diff --git a/packages/merchant-tablet-ionic/src/pages/+carriers/carrier-track-popup/carrier-track-popup.html b/packages/merchant-tablet-ionic/src/pages/+carriers/carrier-track-popup/carrier-track-popup.html new file mode 100644 index 0000000..88af2f0 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+carriers/carrier-track-popup/carrier-track-popup.html @@ -0,0 +1,63 @@ +
+ diff --git a/packages/merchant-tablet-ionic/src/pages/+carriers/carrier-track-popup/carrier-track-popup.module.ts b/packages/merchant-tablet-ionic/src/pages/+carriers/carrier-track-popup/carrier-track-popup.module.ts new file mode 100644 index 0000000..cb1a8b8 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+carriers/carrier-track-popup/carrier-track-popup.module.ts @@ -0,0 +1,14 @@ +import { NgModule } from '@angular/core'; +import { CarrierTrackPopup } from './carrier-track-popup'; +import { TranslateModule } from '@ngx-translate/core'; +import { CarrierService } from '../../../../src/services/carrier.service'; +import { IonicModule } from '@ionic/angular'; +import { CommonModule } from '@angular/common'; +import { WarehousesService } from '../../../../src/services/warehouses.service'; + +@NgModule({ + declarations: [CarrierTrackPopup], + imports: [TranslateModule.forChild(), IonicModule, CommonModule], + providers: [CarrierService, WarehousesService] +}) +export class CarrierTrackPopupModule {} diff --git a/packages/merchant-tablet-ionic/src/pages/+carriers/carrier-track-popup/carrier-track-popup.scss b/packages/merchant-tablet-ionic/src/pages/+carriers/carrier-track-popup/carrier-track-popup.scss new file mode 100644 index 0000000..fb6f62d --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+carriers/carrier-track-popup/carrier-track-popup.scss @@ -0,0 +1,120 @@ +$w: 800px; +$h: 570px; + +.carrier-track-wrapper { + display: flex !important; + + .modal-wrapper { + width: $w; + height: $h; + left: calc(50% - (#{$w} / 2)); + top: calc(50% - (#{$h} / 2)); + + carrier-track-popup { + border: 1px solid rgba(0, 0, 0, 0.125); + background: white; + width: 100%; + height: 100%; + border-radius: 5px; + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-evenly; + + .map { + width: 65%; + height: 100%; + } + + .sidebar { + width: 35%; + height: 100%; + display: flex; + flex-direction: column; + h2 { + text-align: center; + } + .close-btn { + position: absolute; + right: 15px; + top: 25px; + cursor: pointer; + } + ul { + list-style-type: none; + display: flex; + flex-direction: column; + padding: 0; + padding-top: 5px; + box-sizing: border-box; + margin: 0; + border-bottom: 1px solid rgb(185, 185, 185); + height: auto; + &:last-child { + border: none; + } + + div { + display: flex; + align-items: center; + box-sizing: border-box; + padding-left: 10px; + padding-right: 20px; + + img { + width: 32px; + height: 32px; + } + + h4 { + margin-left: 15px; + color: gray; + line-height: 0.4; + } + } + + h3 { + margin-left: 10px; + //line-height: 1; + } + + small { + font-size: 0.7em; + margin-left: 10px; + transform: translateY(-5px); + } + + li { + padding: 0; + margin-left: 10px; + } + } + } + } + } +} + +.carrier-track-carrier-info { + display: flex; + flex-direction: column; + width: 100%; + height: 100%; + padding: 0; + margin: 0; + + h3, + ul, + li { + margin: 0; + padding: 0; + } + + h3 { + margin-bottom: 10px; + } + + li { + margin-left: 5px; + margin-bottom: 3px; + } +} diff --git a/packages/merchant-tablet-ionic/src/pages/+carriers/carrier-track-popup/carrier-track-popup.ts b/packages/merchant-tablet-ionic/src/pages/+carriers/carrier-track-popup/carrier-track-popup.ts new file mode 100644 index 0000000..3cca4fb --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+carriers/carrier-track-popup/carrier-track-popup.ts @@ -0,0 +1,268 @@ +import { Component, ViewChild, ElementRef, Input, OnInit } from '@angular/core'; + +import Carrier from '@modules/server.common/entities/Carrier'; +import { CarrierService } from '../../../../src/services/carrier.service'; +import { ModalController } from '@ionic/angular'; +import { WarehousesService } from '../../../../src/services/warehouses.service'; +import Warehouse from '@modules/server.common/entities/Warehouse'; +import User from '@modules/server.common/entities/User'; + +declare var google: any; + +const directionsDisplay = new google.maps.DirectionsRenderer(); +const directionsService = new google.maps.DirectionsService(); + +@Component({ + selector: 'carrier-track-popup', + templateUrl: 'carrier-track-popup.html', + styleUrls: ['./carrier-track-popup.scss'], +}) +export class CarrierTrackPopup implements OnInit { + @ViewChild('gmap', { static: true }) + gmapElement: ElementRef; + + @Input() + carrier: Carrier; + + map: google.maps.Map; + + myLatLng = { lat: 0, lng: 0 }; + + private coordinates: any; + warehouse: Warehouse; + user: User; + storeIcon = 'http://maps.google.com/mapfiles/kml/pal3/icon21.png'; + userIcon = 'http://maps.google.com/mapfiles/kml/pal3/icon48.png'; + carrierIcon = 'http://maps.google.com/mapfiles/kml/pal4/icon54.png'; + + constructor( + public modalCtrl: ModalController, + private carriersService: CarrierService, + private warehouseService: WarehousesService + ) {} + + ngOnInit(): void { + const geoLocation = this.carrier.geoLocation; + + this.coordinates = [ + geoLocation.coordinates.lat, + geoLocation.coordinates.lng, + ]; + + this.myLatLng.lat = this.coordinates[0]; + this.myLatLng.lng = this.coordinates[1]; + + this.loadMap(); + } + + get warehouseId() { + return localStorage.getItem('_warehouseId'); + } + + async loadMap() { + const mapProp = { + center: this.myLatLng, + zoom: 15, + mapTypeId: google.maps.MapTypeId.ROADMAP, + }; + + this.map = new google.maps.Map(this.gmapElement.nativeElement, mapProp); + + const carrierInfoContent = ` +
+

${this.carrier.fullName}

+
    +
  • ${this.carrier.username}
  • +
  • ${this.carrier.phone}
  • +
  • ${this.carrier.geoLocation.streetAddress}
  • +
+
+ `; + + this.warehouse = await this.warehouseService + .getStoreById(this.warehouseId) + .toPromise(); + + if (this.carrier.status !== 0) { + const carrierMarker = this.addMarker( + this.myLatLng, + this.map, + this.carrierIcon + ); + + const carrierInfoWindow = new google.maps.InfoWindow({ + content: carrierInfoContent, + }); + carrierMarker.addListener('click', () => { + carrierInfoWindow.open(this.map, carrierMarker); + }); + const warehouseInfoContent = ` +
+

${this.warehouse.name}

+
    +
  • + + ${this.warehouse.contactEmail} +
  • +
  • + + ${this.warehouse.contactPhone} +
  • +
  • + + ${this.warehouse.geoLocation.streetAddress} +
  • +
+
+ `; + + const warehouseInfoWindow = new google.maps.InfoWindow({ + content: warehouseInfoContent, + }); + + const warehouseMarker = this.addMarker( + { + lat: this.warehouse.geoLocation.loc.coordinates[1], + lng: this.warehouse.geoLocation.loc.coordinates[0], + }, + this.map, + this.storeIcon + ); + + warehouseMarker.addListener('click', () => { + warehouseInfoWindow.open(this.map, warehouseMarker); + }); + } else if (this.carrier.status === 0) { + const order = await this.carriersService.getCarrierCurrentOrder( + this.carrier.id + ); + + const carrierMarker = this.addMarker( + this.myLatLng, + this.map, + this.carrierIcon + ); + + const carrierInfoWindow = new google.maps.InfoWindow({ + content: carrierInfoContent, + }); + carrierMarker.addListener('click', () => { + carrierInfoWindow.open(this.map, carrierMarker); + }); + + const warehouseMarker = this.addMarker( + { + lat: this.warehouse.geoLocation.loc.coordinates[1], + lng: this.warehouse.geoLocation.loc.coordinates[0], + }, + this.map, + this.storeIcon + ); + + const warehouseInfoContent = ` +
+

${this.warehouse.name}

+
    +
  • + + ${this.warehouse.contactEmail} +
  • +
  • + + ${this.warehouse.contactPhone} +
  • +
  • + + ${this.warehouse.geoLocation.streetAddress} +
  • +
+
+ `; + + const warehouseInfoWindow = new google.maps.InfoWindow({ + content: warehouseInfoContent, + }); + + warehouseMarker.addListener('click', () => { + warehouseInfoWindow.open(this.map, warehouseMarker); + }); + + // TODO: put into separate userInfo control + + if (order) { + this.user = order.user; + const userMarker = this.addMarker( + { + lat: this.user.geoLocation.loc.coordinates[1], + lng: this.user.geoLocation.loc.coordinates[0], + }, + this.map, + this.userIcon + ); + const userInfoContent = ` +
+

${this.user.firstName + ' ' + this.user.lastName}

+
    +
  • ${ + this.user.email + }
  • +
  • ${ + this.user.phone + }
  • +
  • ${ + this.user.geoLocation.streetAddress + }
  • +
+
+ `; + + const userInfoWindow = new google.maps.InfoWindow({ + content: userInfoContent, + }); + + userMarker.addListener('click', () => { + userInfoWindow.open(this.map, userMarker); + }); + const start = new google.maps.LatLng( + this.user.geoLocation.loc.coordinates[1], + this.user.geoLocation.loc.coordinates[0] + ); + + const end = new google.maps.LatLng( + this.warehouse.geoLocation.loc.coordinates[1], + this.warehouse.geoLocation.loc.coordinates[0] + ); + + const request = { + origin: start, + destination: end, + travelMode: 'DRIVING', + }; + + directionsService.route(request, function (res, stat) { + if (stat === 'OK') { + directionsDisplay.setDirections(res); + } + }); + + directionsDisplay.setOptions({ + suppressMarkers: true, + }); + + directionsDisplay.setMap(this.map); + } + } + } + + addMarker(position, map, icon) { + return new google.maps.Marker({ + position, + map, + icon, + }); + } + + cancelModal() { + this.modalCtrl.dismiss(); + } +} diff --git a/packages/merchant-tablet-ionic/src/pages/+carriers/carriers.html b/packages/merchant-tablet-ionic/src/pages/+carriers/carriers.html new file mode 100644 index 0000000..0dd37e1 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+carriers/carriers.html @@ -0,0 +1,57 @@ +
+ + +
+ + + +
+
+ + +

{{ 'CARRIERS_VIEW.NO_CARRIERS' | translate }}

+
+
+
+
diff --git a/packages/merchant-tablet-ionic/src/pages/+carriers/carriers.module.ts b/packages/merchant-tablet-ionic/src/pages/+carriers/carriers.module.ts new file mode 100644 index 0000000..517dd53 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+carriers/carriers.module.ts @@ -0,0 +1,57 @@ +import { NgModule } from '@angular/core'; +import { PipesModule } from '@modules/client.common.angular2/pipes/pipes.module'; +import { ComponentsModule } from '../../components/components.module'; +import { TranslateModule } from '@ngx-translate/core'; +import { CarriersPage } from './carriers'; +import { WarehouseCarriersRouter } from '@modules/client.common.angular2/routers/warehouse-carriers-router.service'; +import { Ng2SmartTableModule } from 'ng2-smart-table'; +import { AddressesComponent } from '../../components/carriers-table/addresses'; +import { StatusComponent } from '../../components/carriers-table/status'; +import { DeliveriesComponent } from '../../components/carriers-table/deliveries'; +import { ImageComponent } from '../../components/carriers-table/image'; +import { PhoneComponent } from '../../../src/components/carriers-table/phone'; +import { Routes, RouterModule } from '@angular/router'; +import { IonicModule } from '@ionic/angular'; +import { CommonModule } from '@angular/common'; +import { FormsModule } from '@angular/forms'; +import { AddCarriersPopupPageModule } from './add-carriers-popup/add-carriers-popup.module'; +import { CarrierAddrPopupPageModule } from './carrier-addr-popup/carrier-addr-popup.module'; +import { CarrierDeliveriesPopupPageModule } from './carrier-deliveries-popup/carrier-deliveries-popup.module'; +import { CarrierEditPopupModule } from './carrier-edit-popup/carrier-edit-popup.module'; +import { CarrierTrackPopupModule } from './carrier-track-popup/carrier-track-popup.module'; +import { ConfirmDeletePopupModule } from 'components/confirm-delete-popup/confirm-delete-popup.module'; + +const routes: Routes = [ + { + path: '', + component: CarriersPage, + }, +]; + +@NgModule({ + declarations: [ + CarriersPage, + ImageComponent, + AddressesComponent, + StatusComponent, + DeliveriesComponent, + ], + imports: [ + PipesModule, + ComponentsModule, + TranslateModule.forChild(), + Ng2SmartTableModule, + IonicModule, + CommonModule, + FormsModule, + RouterModule.forChild(routes), + AddCarriersPopupPageModule, + CarrierAddrPopupPageModule, + CarrierDeliveriesPopupPageModule, + CarrierEditPopupModule, + ConfirmDeletePopupModule, + CarrierTrackPopupModule, + ], + providers: [WarehouseCarriersRouter] +}) +export class CarrierssPageModule {} diff --git a/packages/merchant-tablet-ionic/src/pages/+carriers/carriers.scss b/packages/merchant-tablet-ionic/src/pages/+carriers/carriers.scss new file mode 100644 index 0000000..7f0e143 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+carriers/carriers.scss @@ -0,0 +1,195 @@ +.page-carriers { + background-color: white; + min-height: 100%; + overflow-y: scroll; + + #contentWrapper { + margin-top: 45px; + } + a.button { + font-size: 0.8em; + } + .icon.button-icon.plus-navbar-button { + color: white; + } + .allowOverflow { + display: contents !important; + a { + width: auto; + overflow: unset !important; + padding: 0 !important; + border: 1px solid #242530 !important; + } + min-width: 130px !important; + width: auto !important; + position: absolute; + right: 10px !important; + } + + .scroll-content { + padding: 0 !important; + padding-top: 16px !important; + } + #customersTable { + display: block; + width: 100%; + text-align: left; + } + .grid { + padding: 0px !important; + } + .ordersCount { + background-color: #111111; + width: 40px; + text-align: center; + border-radius: 50px; + font-weight: bold; + color: #fff; + } + + .call-icon { + margin-right: 7px; + &:before { + font-size: 1.3em; + cursor: pointer; + margin-left: 5px; + position: relative; + top: 3px; + } + } + + .work { + color: green; + } + + .notWork { + color: red; + } + + ion-icon { + font-size: 24px; + } + + ion-icon.call-icon.icon.icon-md.ion-md-call.can-call { + color: green !important; + } + + .add-wrapper { + position: relative; + } + + .add-carriers { + border-radius: 50%; + background: rgb(255, 184, 51); + position: absolute; + width: 50px; + height: 50px; + right: 10px; + top: 10px; + filter: drop-shadow(3px 3px 3px rgb(182, 184, 182)); + } + + .add-carriers:hover { + background: rgb(255, 202, 105) !important; + } + + .ng2-smart-pagination-nav { + li { + width: 50% !important; + } + } + + .ng2-smart-titles { + height: 55px !important; + } + + .pagination { + line-height: 1 !important; + margin: 0 10px !important; + } + + table { + margin-bottom: 25px !important; + } + + .button-text { + padding: 0 10px; + } +} + +#headerRow { + font-weight: bolder; + text-align: left; + border-bottom: 1px solid #fff; + font-size: 1.1em; + padding-top: 15px; + padding-bottom: 5px; + padding-left: 15px; + padding-right: 15px; + background: #333; + color: white; +} + +.ng2-smart-th.status input, +.ng2-smart-th.deliveries input { + text-align: center; +} + +.ng2-smart-actions-title a { + padding: 0 !important; +} + +.ng2-smart-actions { + display: flex; + align-items: center; +} + +.ng2-smart-actions { + display: flex; + justify-content: space-between; + height: 83px; + border-bottom: none !important; + i { + font-size: 1.4em; + padding: 0; + margin: 0; + } + a { + padding: 0 !important; + margin: 0; + height: 23px !important; + width: 23px !important; + display: flex; + align-items: center; + justify-content: center; + } + ng2-st-tbody-custom { + width: calc(33% + 0.5%); + display: flex !important; + align-items: center !important; + justify-content: center !important; + } + ng2-st-tbody-edit-delete { + width: calc(66% + 0.5%); + display: flex; + justify-content: space-around; + align-items: center; + a:nth-child(1) { + transform: translateY(1px); + i { + font-size: 1.55em; + } + } + } +} + +.right-container { + position: absolute; + right: 0; + display: flex; + padding-right: 10px; +} + +.track-btn { + transform: translateX(-130px); +} diff --git a/packages/merchant-tablet-ionic/src/pages/+carriers/carriers.ts b/packages/merchant-tablet-ionic/src/pages/+carriers/carriers.ts new file mode 100644 index 0000000..b6aceb8 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+carriers/carriers.ts @@ -0,0 +1,227 @@ +import { Component, OnDestroy, OnInit } from '@angular/core'; +import Carrier from '@modules/server.common/entities/Carrier'; +import { takeUntil, first } from 'rxjs/operators'; +import { LocalDataSource } from 'ng2-smart-table'; +import { Observable, Subject, forkJoin } from 'rxjs'; +import { TranslateService } from '@ngx-translate/core'; +import { PhoneComponent } from '../../components/carriers-table/phone'; +import { AddressesComponent } from '../../components/carriers-table/addresses'; +import { StatusComponent } from '../../components/carriers-table/status'; +import { DeliveriesComponent } from '../../components/carriers-table/deliveries'; +import { ImageComponent } from '../../components/carriers-table/image'; +import { WarehouseCarriersRouter } from '@modules/client.common.angular2/routers/warehouse-carriers-router.service'; +import { Store } from '../../../src/services/store.service'; +import { ModalController } from '@ionic/angular'; +import { AddCarriersPopupPage } from './add-carriers-popup/add-carriers-popup'; +import { CarrierEditPopupPage } from './carrier-edit-popup/carrier-edit-popup'; +import { CarrierTrackPopup } from './carrier-track-popup/carrier-track-popup'; +import { Router } from '@angular/router'; +import { ConfirmDeletePopupPage } from 'components/confirm-delete-popup/confirm-delete-popup'; +import { WarehouseRouter } from '@modules/client.common.angular2/routers/warehouse-router.service'; + +@Component({ + selector: 'page-carriers', + templateUrl: 'carriers.html', + styleUrls: ['./carriers.scss'], +}) +export class CarriersPage implements OnInit, OnDestroy { + settingsSmartTable: object; + sourceSmartTable = new LocalDataSource(); + carriers: Carrier[]; + showNoDeliveryIcon: boolean; + + private _ngDestroy$ = new Subject(); + + constructor( + private readonly router: Router, + public modalCtrl: ModalController, + private readonly warehouseCarriersRouter: WarehouseCarriersRouter, + private readonly _translateService: TranslateService, + private readonly store: Store, + private warehouseRouter: WarehouseRouter + ) {} + + get deviceId() { + return localStorage.getItem('_deviceId'); + } + + get warehouseId() { + return localStorage.getItem('_warehouseId'); + } + + ngOnInit(): void { + this._loadCarriers(); + this._loadSettingsSmartTable(); + } + + async openAddCarriers() { + const addCarriersPopupModal = await this.modalCtrl.create({ + component: AddCarriersPopupPage, + + cssClass: 'add-carriers-popup', + }); + + await addCarriersPopupModal.present(); + } + + async trackCarrier(e) { + const modal = await this.modalCtrl.create({ + component: CarrierTrackPopup, + componentProps: { carrier: e.data.carrier }, + cssClass: 'carrier-track-wrapper', + }); + + await modal.present(); + } + + async deleteCarrier(e) { + const modal = await this.modalCtrl.create({ + component: ConfirmDeletePopupPage, + componentProps: { data: e.data }, + cssClass: 'confirm-delete-wrapper', + }); + + await modal.present(); + + const res = await modal.onDidDismiss(); + if (res.data) { + const carrierId = e.data.carrier.id; + + const id = this.warehouseId; + + const merchant = await this.warehouseRouter + .get(id) + .pipe(first()) + .toPromise(); + + merchant.usedCarriersIds = merchant.usedCarriersIds.filter( + (x) => x !== carrierId + ); + + await this.warehouseRouter.save(merchant); + } + } + + async editCarrier(e) { + const modal = await this.modalCtrl.create({ + component: CarrierEditPopupPage, + componentProps: { carrier: e.data.carrier }, + }); + + await modal.present(); + } + + private async _loadCarriers() { + const loadData = (carriers) => { + const carriersVM = carriers.map((c: Carrier) => { + return { + image: c.logo, + name: c.firstName + ' ' + c.lastName, + phone: c.phone, + addresses: c.geoLocation.city, + status: c.status === 0 ? 'working' : 'not working', + carrier: c, + }; + }); + + this.sourceSmartTable.load(carriersVM); + }; + + this.warehouseCarriersRouter + .get(this.warehouseId) + .pipe(takeUntil(this._ngDestroy$)) + .subscribe((carriers) => { + this.carriers = carriers; + + loadData(this.carriers); + + this.carriers.length === 0 + ? (this.showNoDeliveryIcon = true) + : (this.showNoDeliveryIcon = false); + }); + } + + goToTrackPage() { + this.router.navigateByUrl('/track'); + } + + ngOnDestroy() { + this._ngDestroy$.next(); + this._ngDestroy$.complete(); + } + + private _loadSettingsSmartTable() { + const columnTitlePrefix = 'CARRIERS_VIEW.'; + const getTranslate = (name: string): Observable => + this._translateService.get(columnTitlePrefix + name); + + forkJoin( + getTranslate('IMAGE'), + getTranslate('NAME'), + getTranslate('PHONE_NUMBER'), + getTranslate('ADDRESSES'), + getTranslate('STATUS'), + getTranslate('DELIVERIES') + ) + .pipe(takeUntil(this._ngDestroy$)) + .subscribe( + ([image, name, phone, addresses, status, deliveries]) => { + this.settingsSmartTable = { + mode: 'external', + edit: { + editButtonContent: '', + confirmEdit: true, + }, + delete: { + deleteButtonContent: '', + confirmDelete: true, + }, + actions: { + custom: [ + { + name: 'track', + title: '', + }, + ], + }, + columns: { + image: { + title: image, + type: 'custom', + renderComponent: ImageComponent, + filter: false, + }, + name: { title: name }, + phone: { + title: phone, + type: 'custom', + renderComponent: PhoneComponent, + }, + addresses: { + title: addresses, + type: 'custom', + renderComponent: AddressesComponent, + }, + status: { + title: status, + class: 'text-center', + type: 'custom', + renderComponent: StatusComponent, + }, + deliveries: { + title: deliveries, + class: 'text-center', + filter: false, + type: 'custom', + renderComponent: DeliveriesComponent, + }, + }, + pager: { + display: true, + perPage: 14, + }, + }; + } + ); + } +} diff --git a/packages/merchant-tablet-ionic/src/pages/+customers/customer-addr-popup/customer-addr-popup.html b/packages/merchant-tablet-ionic/src/pages/+customers/customer-addr-popup/customer-addr-popup.html new file mode 100644 index 0000000..c4801c5 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+customers/customer-addr-popup/customer-addr-popup.html @@ -0,0 +1,41 @@ +
+

+ {{ 'CUSTOMERS_VIEW.ADDRESS_POP_UP.CUSTOMER_ADDRESS' | translate }} +

+ +
+ +
+ {{ 'CUSTOMERS_VIEW.ADDRESS_POP_UP.COUNTRY' | translate }}: +
+
{{ country }}
+
+ +
+ {{ 'CUSTOMERS_VIEW.ADDRESS_POP_UP.STREET' | translate }}: +
+
{{ street }}
+
+ +
+ {{ 'CUSTOMERS_VIEW.ADDRESS_POP_UP.HOUSE' | translate }}: +
+
{{ house }}
+
+ +
+ {{ 'CUSTOMERS_VIEW.ADDRESS_POP_UP.APARTMENT' | translate }}: +
+
{{ apartment }}
+
+ +
+ {{ 'CUSTOMERS_VIEW.ADDRESS_POP_UP.COORDINATES' | translate }}: +
+
{{ coordinatesStr }}
+
+
+
+
diff --git a/packages/merchant-tablet-ionic/src/pages/+customers/customer-addr-popup/customer-addr-popup.module.ts b/packages/merchant-tablet-ionic/src/pages/+customers/customer-addr-popup/customer-addr-popup.module.ts new file mode 100644 index 0000000..cfd959a --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+customers/customer-addr-popup/customer-addr-popup.module.ts @@ -0,0 +1,10 @@ +import { NgModule } from '@angular/core'; +import { CustomerAddrPopupPage } from './customer-addr-popup'; +import { TranslateModule } from '@ngx-translate/core'; +import { CommonModule } from '@angular/common'; + +@NgModule({ + declarations: [CustomerAddrPopupPage], + imports: [TranslateModule.forChild(), CommonModule] +}) +export class CustomerAddrPopupPageModule {} diff --git a/packages/merchant-tablet-ionic/src/pages/+customers/customer-addr-popup/customer-addr-popup.scss b/packages/merchant-tablet-ionic/src/pages/+customers/customer-addr-popup/customer-addr-popup.scss new file mode 100644 index 0000000..3d825f9 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+customers/customer-addr-popup/customer-addr-popup.scss @@ -0,0 +1,69 @@ +.customer-addr-popup { + background-color: #fff; + height: 100%; + display: flex !important; + flex-direction: column; + .infoRow { + display: block; + // width:80%; + margin: 0 auto; + margin-bottom: 11px; + line-height: 18px; + .label, + .info { + display: inline-block; + width: 48%; + font-size: 1.2em; + } + .label { + font-style: italic; + } + .info { + color: #0074d9; + font-weight: bold; + } + } + + .infoRowTitle { + text-align: center; + display: block; + width: 80%; + line-height: 18px; + .label, + .info { + display: inline-block; + width: 48%; + font-size: 1.2em; + } + .label { + font-style: italic; + } + .info { + color: #0074d9; + font-weight: bold; + } + } + + .addrInfo { + // padding-left: 50px; + // min-height: 50% !important; + margin: 0 auto; + width: 90%; + } + + .map { + height: 100% !important; + } +} + +// .modal-wrapper { +// width: 650px; +// height: 400px; +// height: auto; +// display: inline-table; +// left: calc(50% - (650px / 2)); +// top: calc(50% - (600px / 2)); +// .show-page { +// display: contents; +// } +// } diff --git a/packages/merchant-tablet-ionic/src/pages/+customers/customer-addr-popup/customer-addr-popup.ts b/packages/merchant-tablet-ionic/src/pages/+customers/customer-addr-popup/customer-addr-popup.ts new file mode 100644 index 0000000..cb24a1f --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+customers/customer-addr-popup/customer-addr-popup.ts @@ -0,0 +1,75 @@ +import { Component, ViewChild, ElementRef, Input, OnInit } from '@angular/core'; +import { ModalController } from '@ionic/angular'; +import User from '@modules/server.common/entities/User'; + +@Component({ + selector: 'customer-addr-popup', + templateUrl: 'customer-addr-popup.html', + styleUrls: ['./customer-addr-popup.scss'], +}) +export class CustomerAddrPopupPage implements OnInit { + @ViewChild('gmap', { static: true }) + gmapElement: ElementRef; + map: google.maps.Map; + myLatLng = { lat: 0, lng: 0 }; + + @Input() + user: User; + + city: string; + country: string; + street: string; + house: string; + apartment: string; + coordinates: number[]; + + constructor(public modalController: ModalController) {} + + get coordinatesStr() { + return this.user + ? this.user.geoLocation.loc.coordinates + .map((c) => c.toFixed(6)) + .reverse() + .join(', ') + : ''; + } + + ngOnInit(): void { + const user = this.user; + this.city = user.geoLocation.city; + this.country = user.geoLocation.countryName; + this.street = user.geoLocation.streetAddress; + this.house = user.geoLocation.house; + this.apartment = user.apartment; + + // We use reverse because MongoDB store lnt => lat + this.coordinates = Array.from( + user.geoLocation.loc.coordinates + ).reverse(); + + this.myLatLng.lat = this.coordinates[0]; + this.myLatLng.lng = this.coordinates[1]; + + this.loadMap(); + } + + loadMap() { + const mapProp = { + center: this.myLatLng, + zoom: 15, + mapTypeId: google.maps.MapTypeId.ROADMAP, + }; + + this.map = new google.maps.Map(this.gmapElement.nativeElement, mapProp); + + const marker = new google.maps.Marker({ + position: this.myLatLng, + map: this.map, + title: 'Your Warehouse!', + }); + } + + cancelModal() { + this.modalController.dismiss(); + } +} diff --git a/packages/merchant-tablet-ionic/src/pages/+customers/customer-deliveries-popup/customer-deliveries-popup.html b/packages/merchant-tablet-ionic/src/pages/+customers/customer-deliveries-popup/customer-deliveries-popup.html new file mode 100644 index 0000000..4b6c2f4 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+customers/customer-deliveries-popup/customer-deliveries-popup.html @@ -0,0 +1,33 @@ + +

+ {{ 'CUSTOMERS_VIEW.ORDERS_POP_UP.CUSTOMER_ORDERS' | translate }} +

+ + + + {{ 'CUSTOMERS_VIEW.ORDERS_POP_UP.ALL_ORDERS' | translate }}: + + {{ ordersCurrentWarehouse?.length }} + + + {{ 'CUSTOMERS_VIEW.ORDERS_POP_UP.TOTAL_ORDERS_SUM' | translate + }}: + {{ totalOrdersSum }} + + + +
+ + +
+
diff --git a/packages/merchant-tablet-ionic/src/pages/+customers/customer-deliveries-popup/customer-deliveries-popup.module.ts b/packages/merchant-tablet-ionic/src/pages/+customers/customer-deliveries-popup/customer-deliveries-popup.module.ts new file mode 100644 index 0000000..b9a1775 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+customers/customer-deliveries-popup/customer-deliveries-popup.module.ts @@ -0,0 +1,29 @@ +import { NgModule } from '@angular/core'; +import { CustomerDeliveriesPopupPage } from './customer-deliveries-popup'; +import { Ng2SmartTableModule } from 'ng2-smart-table'; +import { OrderIdComponent } from '../../../components/customer-deliveries-table/orderId'; +import { DeliveryComponent } from '../../../components/customer-deliveries-table/delivery'; +import { AddressComponent } from '../../../components/customer-deliveries-table/address'; +import { StatusComponent } from '../../../components/customer-deliveries-table/status'; +import { TranslateModule } from '@ngx-translate/core'; +import { CommonModule } from '@angular/common'; +import { FormsModule } from '@angular/forms'; +import { IonicModule } from '@ionic/angular'; + +@NgModule({ + declarations: [ + CustomerDeliveriesPopupPage, + OrderIdComponent, + DeliveryComponent, + AddressComponent, + StatusComponent, + ], + imports: [ + TranslateModule.forChild(), + Ng2SmartTableModule, + IonicModule, + CommonModule, + FormsModule, + ] +}) +export class CustomerDeliveriesPopupPageModule {} diff --git a/packages/merchant-tablet-ionic/src/pages/+customers/customer-deliveries-popup/customer-deliveries-popup.scss b/packages/merchant-tablet-ionic/src/pages/+customers/customer-deliveries-popup/customer-deliveries-popup.scss new file mode 100644 index 0000000..82dfaeb --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+customers/customer-deliveries-popup/customer-deliveries-popup.scss @@ -0,0 +1,54 @@ +@import '~@ever-platform/common-angular/src/scss/everbie.common'; +.customer-deliveries-popup { + background-color: #fff; + min-height: 100%; + display: block !important; + margin: 0 !important; + .body { + overflow-y: scroll; + height: 75%; + position: relative; + } + .info { + color: #0074d9; + font-weight: bold; + } + + table { + margin-top: 0 !important; + } + + #basicInfo { + font-size: 1.1em; + min-height: 10%; + background: #eee; + span { + margin: auto; + text-transform: none !important; + font-style: italic; + } + } + + .ng2-smart-pagination-nav { + li { + width: 50% !important; + } + } + + .ng2-smart-titles { + height: 55px !important; + } + + .pagination { + line-height: 1 !important; + position: absolute; + width: auto; + bottom: 0; + left: 50%; + -webkit-transform: translateX(-50%); + -moz-transform: translateX(-50%); + -ms-transform: translateX(-50%); + -o-transform: translateX(-50%); + transform: translateX(-50%); + } +} diff --git a/packages/merchant-tablet-ionic/src/pages/+customers/customer-deliveries-popup/customer-deliveries-popup.ts b/packages/merchant-tablet-ionic/src/pages/+customers/customer-deliveries-popup/customer-deliveries-popup.ts new file mode 100644 index 0000000..d23b2ab --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+customers/customer-deliveries-popup/customer-deliveries-popup.ts @@ -0,0 +1,200 @@ +import { Component, OnDestroy, Input, OnInit } from '@angular/core'; +import User from '@modules/server.common/entities/User'; +import Order from '@modules/server.common/entities/Order'; +import { UserOrdersRouter } from '@modules/client.common.angular2/routers/user-orders-router.service'; +import Carrier from '@modules/server.common/entities/Carrier'; +import { LocalDataSource } from 'ng2-smart-table'; +import { Subject, Observable, forkJoin } from 'rxjs'; +import { TranslateService } from '@ngx-translate/core'; +import { takeUntil } from 'rxjs/operators'; +import { OrderIdComponent } from '../../../components/customer-deliveries-table/orderId'; +import { DeliveryComponent } from '../../../components/customer-deliveries-table/delivery'; +import { AddressComponent } from '../../../components/customer-deliveries-table/address'; +import { StatusComponent } from '../../../components/customer-deliveries-table/status'; +import { getIdFromTheDate } from '@modules/server.common/utils'; +import { ModalController } from '@ionic/angular'; + +@Component({ + selector: 'customer-deliveries-popup', + templateUrl: 'customer-deliveries-popup.html', + styleUrls: ['./customer-deliveries-popup.scss'], +}) +export class CustomerDeliveriesPopupPage implements OnInit, OnDestroy { + @Input() + user: User; + orders: Order[]; + ordersFromWarehouse: Order[]; + userId: string; + showNoDeliveryIcon: boolean; + ordersCurrentWarehouse: Order[]; + carrier: Carrier; + totalOrdersSum: number = 0; + + settingsSmartTable: object; + sourceSmartTable = new LocalDataSource(); + + private _ngDestroy$ = new Subject(); + private $orders: any; + + constructor( + public modalController: ModalController, + private readonly userOrdersRouter: UserOrdersRouter, + private readonly translateService: TranslateService + ) { + this._loadSettingsSmartTable(); + } + + get warehouseId() { + return localStorage.getItem('_warehouseId'); + } + + compareByCreateDate(a, b) { + if (new Date(a.createdAt).getTime() > new Date(b.createdAt).getTime()) { + return -1; + } + if (new Date(a.createdAt).getTime() < new Date(b.createdAt).getTime()) { + return 1; + } + return 0; + } + + ngOnInit(): void { + this.userId = this.user.id; + this.$orders = this.userOrdersRouter + .get(this.user.id) + .subscribe((orders) => { + this.orders = orders; + if (this.orders.length === 0) { + this.showNoDeliveryIcon = true; + } + this.getOrders(); + }); + } + + getCustomerFullAddress(order: Order) { + if (order.isCompleted) { + const addressUser: User = order.user as User; + const geoLocation = addressUser.geoLocation; + const fullAddress = `${geoLocation.city}, ${geoLocation.streetAddress} ${geoLocation.house}`; + return fullAddress; + } + } + + getTotalDeliveryTime(order: Order) { + const start = order.createdAt; + + const end = new Date(order.deliveryTime); + + let delta = Math.abs(start.getTime() - end.getTime()) / 1000; + + const days = Math.floor(delta / 86400); + delta -= days * 86400; + + const hours = Math.floor(delta / 3600) % 24; + delta -= hours * 3600; + + const minutes = Math.floor(delta / 60) % 60; + delta -= minutes * 60; + + const seconds = delta % 60; + let secondsStr = seconds.toString(); + secondsStr = secondsStr.substring(0, secondsStr.indexOf('.')); + + let h = '0' + hours; + h = h.substr(-2); + let min = '0' + minutes; + min = min.substr(-2); + let sec = '0' + secondsStr; + sec = sec.substr(-2); + + return `${days !== 0 ? days + 'days ' : ''} + ${hours} : ${min} : ${sec}`; + } + + getOrders() { + const loadData = (orders) => { + const usersVM = orders.map((o: Order) => { + let status = o.isCompleted ? 'Completed' : ''; + status += o.isPaid ? 'Paid' : ''; + return { + orderId: getIdFromTheDate(o), + status, + address: this.getCustomerFullAddress(o), + delivery: this.getTotalDeliveryTime(o), + order: o, + }; + }); + + this.sourceSmartTable.load(usersVM); + }; + + this.ordersCurrentWarehouse = this.orders.filter( + (o: Order) => o.warehouse === this.warehouseId + ); + this.ordersCurrentWarehouse.forEach((o) => { + this.totalOrdersSum += o.totalPrice; + }); + this.ordersCurrentWarehouse.sort(this.compareByCreateDate); + loadData(this.ordersCurrentWarehouse); + return this.ordersCurrentWarehouse; + } + + cancelModal() { + this.modalController.dismiss(); + } + + private _loadSettingsSmartTable() { + const columnTitlePrefix = 'CUSTOMER_ORDERS_POP_UP.'; + const getTranslate = (name: string): Observable => + this.translateService.get(columnTitlePrefix + name); + + forkJoin( + getTranslate('ORDER_ID'), + getTranslate('DELIVERY'), + getTranslate('ADDRESS'), + getTranslate('STATUS') + ) + .pipe(takeUntil(this._ngDestroy$)) + .subscribe(([orderId, delivery, address, status]) => { + this.settingsSmartTable = { + actions: true, + columns: { + orderId: { + title: orderId, + class: 'text-align-left', + type: 'custom', + renderComponent: OrderIdComponent, + }, + delivery: { + title: delivery, + type: 'custom', + renderComponent: DeliveryComponent, + }, + address: { + title: address, + type: 'custom', + renderComponent: AddressComponent, + }, + status: { + title: status, + type: 'custom', + renderComponent: StatusComponent, + }, + }, + pager: { + display: true, + perPage: 4, + }, + }; + }); + } + + ngOnDestroy() { + if (this.$orders) { + this.$orders.unsubscribe(); + } + + this._ngDestroy$.next(); + this._ngDestroy$.complete(); + } +} diff --git a/packages/merchant-tablet-ionic/src/pages/+customers/customer-email-popup/customer-email-popup.html b/packages/merchant-tablet-ionic/src/pages/+customers/customer-email-popup/customer-email-popup.html new file mode 100644 index 0000000..6c2d54d --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+customers/customer-email-popup/customer-email-popup.html @@ -0,0 +1,16 @@ +
+

+ {{ 'CUSTOMERS_VIEW.EMAIL_POP_UP.CUSTOMER_EMAIL' | translate }} +

+ +
+ + {{ email }} + + + {{ 'CUSTOMERS_VIEW.EMAIL_POP_UP.SEND_E_MAIL' | translate }} + +
+
diff --git a/packages/merchant-tablet-ionic/src/pages/+customers/customer-email-popup/customer-email-popup.module.ts b/packages/merchant-tablet-ionic/src/pages/+customers/customer-email-popup/customer-email-popup.module.ts new file mode 100644 index 0000000..ee6e224 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+customers/customer-email-popup/customer-email-popup.module.ts @@ -0,0 +1,17 @@ +import { NgModule } from '@angular/core'; +import { CustomerEmailPopupPage } from './customer-email-popup'; +import { TranslateModule } from '@ngx-translate/core'; +import { IonicModule } from '@ionic/angular'; +import { CommonModule } from '@angular/common'; +import { FormsModule } from '@angular/forms'; + +@NgModule({ + declarations: [CustomerEmailPopupPage], + imports: [ + TranslateModule.forChild(), + IonicModule, + CommonModule, + FormsModule, + ] +}) +export class CustomerEmailPopupPageModule {} diff --git a/packages/merchant-tablet-ionic/src/pages/+customers/customer-email-popup/customer-email-popup.scss b/packages/merchant-tablet-ionic/src/pages/+customers/customer-email-popup/customer-email-popup.scss new file mode 100644 index 0000000..762cab6 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+customers/customer-email-popup/customer-email-popup.scss @@ -0,0 +1,77 @@ +.customer-email-popup { + background-color: #fff; + height: 100%; + display: flex !important; + flex-direction: column; + .emailInfo { + width: 65%; + display: block; + margin: 0 auto; + .emailAddr { + float: left; + font-size: 0.8em; + font-weight: bolder; + text-transform: uppercase; + color: #0074d9; + } + .sendEmail { + float: right; + position: relative; + bottom: 7px; + -webkit-box-shadow: inset 0px 1px 0px 0px #ffffff; + box-shadow: inset 0px 1px 0px 0px #ffffff; + background: -webkit-gradient( + linear, + left top, + left bottom, + color-stop(0.05, #ffffff), + color-stop(1, #f6f6f6) + ); + background: -moz-linear-gradient(top, #ffffff 5%, #f6f6f6 100%); + background: -webkit-linear-gradient(top, #ffffff 5%, #f6f6f6 100%); + background: -o-linear-gradient(top, #ffffff 5%, #f6f6f6 100%); + background: -ms-linear-gradient(top, #ffffff 5%, #f6f6f6 100%); + background: linear-gradient(to bottom, #ffffff 5%, #f6f6f6 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#f6f6f6',GradientType=0); + background-color: #ffffff; + -moz-border-radius: 6px; + -webkit-border-radius: 6px; + border-radius: 6px; + border: 1px solid #dcdcdc; + display: inline-block; + cursor: pointer; + color: #666666; + font-family: Arial; + font-size: 15px; + font-weight: bold; + padding: 6px 24px; + text-decoration: none; + text-shadow: 0px 1px 0px #ffffff; + -moz-box-shadow: inset 0px 1px 0px 0px #ffffff; + &:hover { + background: -webkit-gradient( + linear, + left top, + left bottom, + color-stop(0.05, #f6f6f6), + color-stop(1, #ffffff) + ); + background: -moz-linear-gradient(top, #f6f6f6 5%, #ffffff 100%); + background: -webkit-linear-gradient( + top, + #f6f6f6 5%, + #ffffff 100% + ); + background: -o-linear-gradient(top, #f6f6f6 5%, #ffffff 100%); + background: -ms-linear-gradient(top, #f6f6f6 5%, #ffffff 100%); + background: linear-gradient( + to bottom, + #f6f6f6 5%, + #ffffff 100% + ); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#f6f6f6', endColorstr='#ffffff',GradientType=0); + background-color: #f6f6f6; + } + } + } +} diff --git a/packages/merchant-tablet-ionic/src/pages/+customers/customer-email-popup/customer-email-popup.ts b/packages/merchant-tablet-ionic/src/pages/+customers/customer-email-popup/customer-email-popup.ts new file mode 100644 index 0000000..3c3b199 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+customers/customer-email-popup/customer-email-popup.ts @@ -0,0 +1,42 @@ +import { Component, Input, OnInit } from '@angular/core'; +import { EmailComposer } from '@ionic-native/email-composer/ngx'; +import { LoadingController, ModalController } from '@ionic/angular'; + +@Component({ + selector: 'customer-email-popup', + templateUrl: 'customer-email-popup.html', + styleUrls: ['./customer-email-popup.scss'], +}) +export class CustomerEmailPopupPage implements OnInit { + @Input() + user: any; + + email: any; + + constructor( + public loadingCtrl: LoadingController, + private emailComposer: EmailComposer, + public modalController: ModalController + ) {} + + attemptSendMail() { + if (this.emailComposer.isAvailable()) { + this.emailComposer.isAvailable().then((available: boolean) => { + if (available) { + const email = { + to: this.email, + }; + this.emailComposer.open(email); + } + }); + } + } + + ngOnInit(): void { + this.email = this.user.email; + } + + cancelModal() { + this.modalController.dismiss(); + } +} diff --git a/packages/merchant-tablet-ionic/src/pages/+customers/customers.html b/packages/merchant-tablet-ionic/src/pages/+customers/customers.html new file mode 100644 index 0000000..0ffa192 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+customers/customers.html @@ -0,0 +1,44 @@ +
+ + +
+ + +
+
+ +

NO CUSTOMERS

+
+
+
+
diff --git a/packages/merchant-tablet-ionic/src/pages/+customers/customers.module.ts b/packages/merchant-tablet-ionic/src/pages/+customers/customers.module.ts new file mode 100644 index 0000000..2d4d839 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+customers/customers.module.ts @@ -0,0 +1,51 @@ +import { NgModule } from '@angular/core'; +import { PipesModule } from '@modules/client.common.angular2/pipes/pipes.module'; +import { ComponentsModule } from '../../components/components.module'; +import { TranslateModule } from '@ngx-translate/core'; +import { CustomersPage } from './customers'; +import { WarehouseUsersService } from '@modules/client.common.angular2/routers/warehouse-users.service'; +import { Ng2SmartTableModule } from 'ng2-smart-table'; +import { UserPhoneComponent } from '../../components/users-table/phone'; +import { AddressComponent } from '../../components/users-table/address'; +import { OrdersComponent } from '../../components/users-table/orders'; +import { TotalComponent } from '../../components/users-table/total'; +import { EmailComponent } from '../../components/users-table/email'; +import { ImageUserComponent } from '../../components/users-table/image'; +import { OrdersService } from '../../../src/services/orders.service'; +import { Routes, RouterModule } from '@angular/router'; +import { IonicModule } from '@ionic/angular'; +import { CommonModule } from '@angular/common'; +import { FormsModule } from '@angular/forms'; +import { ConfirmDeletePopupModule } from 'components/confirm-delete-popup/confirm-delete-popup.module'; +import { WarehouseOrdersService } from 'services/warehouse-orders.service'; + +const routes: Routes = [ + { + path: '', + component: CustomersPage, + }, +]; + +@NgModule({ + declarations: [ + CustomersPage, + ImageUserComponent, + AddressComponent, + OrdersComponent, + TotalComponent, + EmailComponent, + ], + imports: [ + PipesModule, + ComponentsModule, + IonicModule, + RouterModule.forChild(routes), + CommonModule, + FormsModule, + TranslateModule.forChild(), + Ng2SmartTableModule, + ConfirmDeletePopupModule, + ], + providers: [WarehouseUsersService, OrdersService, WarehouseOrdersService] +}) +export class CustomersPageModule {} diff --git a/packages/merchant-tablet-ionic/src/pages/+customers/customers.scss b/packages/merchant-tablet-ionic/src/pages/+customers/customers.scss new file mode 100644 index 0000000..1383e5e --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+customers/customers.scss @@ -0,0 +1,119 @@ +@import '~@ever-platform/common-angular/src/scss/everbie.common'; +.customers-page { + background-color: white; + min-height: 100%; + overflow-y: scroll; + + a.button { + font-size: 0.8em; + } + .icon.button-icon.plus-navbar-button { + color: white; + } + .allowOverflow { + a { + overflow: unset !important; + padding: 0 !important; + border: 1px solid #242530 !important; + } + width: 130px; + position: absolute; + right: 10px !important; + } + + .scroll-content { + padding: 0 !important; + padding-top: 16px !important; + } + #contentWrapper { + margin-top: 45px; + } + .scroll-content { + padding: 0 !important; + padding-top: 16px !important; + } + #customersTable { + display: block; + width: 100%; + text-align: left; + } + .grid { + padding: 0px !important; + } + + #headerRow { + font-weight: bolder; + text-align: left; + border-bottom: 1px solid #fff; + font-size: 1.1em; + text-transform: uppercase; + padding-top: 15px; + padding-bottom: 5px; + padding-left: 15px; + padding-right: 15px; + background: #333; + color: white; + } + .call-icon { + margin-right: 7px; + &:before { + font-size: 1.3em; + cursor: pointer; + margin-left: 5px; + position: relative; + top: 3px; + } + } + + .no-customers-message { + @include center(absolute); + text-align: center; + + i { + font-size: 8vw; + // font-size: 50%; + } + + h2 { + font-family: 'Open Sans Hebrew', '-apple-system', 'Helvetica Neue', + 'Roboto', 'Segoe UI', sans-serif; + margin-top: 5px; + font-size: 3vw; + } + + a { + margin-top: 20px; + } + } + + .ng2-smart-pagination-nav { + li { + width: 50% !important; + } + } + + .ng2-smart-titles { + height: 55px !important; + } + + .pagination { + line-height: 1 !important; + margin: 0 10px !important; + } + + table { + margin-bottom: 25px !important; + } +} + +.ng2-smart-th.orders input, +.ng2-smart-th.total input, +.ng2-smart-th.email input { + text-align: center; +} + +user-mutation { + overflow: scroll; + background: #f3f3f3; + text-align: center; +} diff --git a/packages/merchant-tablet-ionic/src/pages/+customers/customers.ts b/packages/merchant-tablet-ionic/src/pages/+customers/customers.ts new file mode 100644 index 0000000..d0a8a2d --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+customers/customers.ts @@ -0,0 +1,328 @@ +import { Component, OnDestroy } from '@angular/core'; +import User from '@modules/server.common/entities/User'; +import { WarehouseOrdersRouter } from '@modules/client.common.angular2/routers/warehouse-orders-router.service'; +import { IOrderCreateInput } from '@modules/server.common/routers/IWarehouseOrdersRouter'; +import Order from '@modules/server.common/entities/Order'; +import { Observable, forkJoin, Subject } from 'rxjs'; +import { TranslateService } from '@ngx-translate/core'; +import { LocalDataSource } from 'ng2-smart-table'; +import { UserPhoneComponent } from '../../components/users-table/phone'; +import { AddressComponent } from '../../components/users-table/address'; +import { OrdersComponent } from '../../components/users-table/orders'; +import { TotalComponent } from '../../components/users-table/total'; +import { EmailComponent } from '../../components/users-table/email'; +import { UserMutationComponent } from '../../@shared/user/mutation/user-mutation.component'; +import { ImageUserComponent } from '../../components/users-table/image'; +import { OrdersService } from '../../../src/services/orders.service'; +import { Store } from '../../../src/services/store.service'; +import { ModalController } from '@ionic/angular'; +import { CustomerAddrPopupPage } from './customer-addr-popup/customer-addr-popup'; +import { ConfirmDeletePopupPage } from 'components/confirm-delete-popup/confirm-delete-popup'; +import { WarehouseOrdersService } from 'services/warehouse-orders.service'; +import { takeUntil } from 'rxjs/operators'; + +@Component({ + selector: 'page-customers', + templateUrl: 'customers.html', + styleUrls: ['./customers.scss'], +}) +export class CustomersPage implements OnDestroy { + orders: Order[]; + users: User[]; + showNoDeliveryIcon: boolean; + settingsSmartTable: object; + sourceSmartTable = new LocalDataSource(); + + private _ngDestroy$ = new Subject(); + private orders$: any; + + constructor( + private warehouseOrdersRouter: WarehouseOrdersRouter, + private readonly _modalCtrl: ModalController, + private readonly _translateService: TranslateService, + private readonly ordersService: OrdersService, + private readonly warehouseOrdersService: WarehouseOrdersService, + private readonly store: Store + ) { + this.loadUsers(); + this._loadSettingsSmartTable(); + } + + get warehouseId() { + return localStorage.getItem('_warehouseId'); + } + + async ionViewCanEnter() { + const isLogged = await this.store.isLogged(); + + return this.store.maintenanceMode === null && isLogged; + } + + getUserName(user: User) { + let name: string = ''; + + if (user) { + const firstName = user.firstName; + const lastName = user.lastName; + name = `${firstName ? firstName : ''} ${lastName ? lastName : ''}`; + } + + return name.trim(); + } + + getOrdersCount(userId: string) { + return this.orders.filter((o: Order) => o.user.id === userId).length; + } + + getTotalPrice(userId: string) { + const orders = this.orders + .filter((o: Order) => o.isPaid) + .filter((o: Order) => o.user.id === userId); + let totalPrice = 0; + if (orders.length > 0) { + totalPrice = orders + .map((o: Order) => o.totalPrice) + .reduce((a, b) => a + b); + } + return totalPrice; + } + + async showCustomerMutationModal(user?: User) { + const modal = await this._modalCtrl.create({ + component: UserMutationComponent, + componentProps: { user }, + cssClass: 'customer-add-wrapper', + }); + + await modal.present(); + + const res = await modal.onDidDismiss(); + const userId = res.data; + if (userId) { + const orderCreateInput: IOrderCreateInput = { + warehouseId: this.warehouseId, + userId, + products: [], + }; + + await this.warehouseOrdersRouter.create(orderCreateInput); + } + } + + async showAddress(e) { + const modal = await this._modalCtrl.create({ + component: CustomerAddrPopupPage, + componentProps: { user: e.data.user }, + cssClass: 'customer-address-popup', + }); + await modal.present(); + } + + async deleteCustomer(e) { + const modal = await this._modalCtrl.create({ + component: ConfirmDeletePopupPage, + componentProps: { data: e.data }, + cssClass: 'confirm-delete-wrapper', + }); + + await modal.present(); + + const res = await modal.onDidDismiss(); + if (res.data) { + const userId = e.data.user.id; + const storeId = this.warehouseId; + + await this.warehouseOrdersService.removeUserOrders(storeId, userId); + } + } + + async editCustomer(e) { + const user = e.data.user; + this.showCustomerMutationModal(user); + } + + private loadUsers() { + // Here are loaded all orders and from them get info for users + // let loadData = (users, orders) => { + // const usersVM = users.map((u: User) => { + // return { + // image: u.image, + // name: this.getUserName(u), + // user: u, + // phone: u.phone, + // addresses: u.geoLocation.city, + // orders: this.getOrdersCount(u.id), + // total: this.getTotalPrice(u.id), + // allOrders: orders + // }; + // }); + + // this.sourceSmartTable.load(usersVM); + // }; + + // this.orders$ = this.warehouseOrdersRouter + // .get(this.warehouseId, { + // order: true + // } as IWarehouseOrdersRouterGetOptions) + // .takeUntil(this._ngDestroy$) + // .subscribe((orders: Order[]) => { + // this.orders = orders; + + // let users = orders + // .filter((o: Order, i: number, self: Order[]) => { + // return ( + // self.map((o) => o.user.id).indexOf(o.user.id) === i + // ); + // }) + // .map((o) => { + // let user: User = Object.assign(o.user); + // return user; + // }); + + // if (users.length === 0) { + // this.showNoDeliveryIcon = true; + // } else { + // this.showNoDeliveryIcon = false; + // } + + // this.users = users; + // console.log(users); + + // loadData(this.users, this.orders); + // }); + + const loadData = (usersInfo) => { + const usersVM = usersInfo.map( + (userInfo: { + user: User; + ordersCount: number; + totalPrice: number; + }) => { + return { + image: userInfo.user.image, + name: this.getUserName(userInfo.user), + user: userInfo.user, + phone: userInfo.user.phone, + addresses: userInfo.user.geoLocation.city, + orders: userInfo.ordersCount, + total: userInfo.totalPrice, + }; + } + ); + + this.sourceSmartTable.load(usersVM); + }; + + this.ordersService + .getOrderedUsersInfo(this.warehouseId) + .pipe(takeUntil(this._ngDestroy$)) + .subscribe( + ( + userInfo: Array<{ + user: User; + ordersCount: number; + totalPrice: number; + }> + ) => { + userInfo.length === 0 + ? (this.showNoDeliveryIcon = true) + : (this.showNoDeliveryIcon = false); + + loadData(userInfo); + } + ); + } + + private _loadSettingsSmartTable() { + const columnTitlePrefix = 'CUSTOMERS_VIEW.'; + const getTranslate = (name: string): Observable => + this._translateService.get(columnTitlePrefix + name); + + forkJoin( + getTranslate('IMAGE'), + getTranslate('NAME'), + getTranslate('PHONE_NUMBER'), + getTranslate('ADDRESSES'), + getTranslate('ORDERS'), + getTranslate('TOTAL'), + getTranslate('E_MAIL') + ) + .pipe(takeUntil(this._ngDestroy$)) + .subscribe( + ([image, name, phone, addresses, orders, total, email]) => { + this.settingsSmartTable = { + mode: 'external', + edit: { + editButtonContent: '', + confirmEdit: true, + }, + delete: { + deleteButtonContent: '', + confirmDelete: true, + }, + actions: { + custom: [ + { + name: 'track', + title: '', + }, + ], + }, + columns: { + image: { + title: image, + type: 'custom', + renderComponent: ImageUserComponent, + filter: false, + }, + name: { title: name }, + phone: { + title: phone, + type: 'custom', + renderComponent: UserPhoneComponent, + }, + addresses: { + title: addresses, + type: 'custom', + renderComponent: AddressComponent, + }, + orders: { + title: orders, + class: 'text-center', + type: 'custom', + renderComponent: OrdersComponent, + }, + total: { + title: total, + class: 'text-center', + type: 'custom', + renderComponent: TotalComponent, + }, + email: { + title: email, + class: 'text-center', + filter: false, + type: 'custom', + renderComponent: EmailComponent, + }, + }, + pager: { + display: true, + perPage: 14, + }, + }; + } + ); + } + + ionViewWillLeave() { + if (this.orders$) { + this.orders$.unsubscribe(); + } + } + + ngOnDestroy() { + this._ngDestroy$.next(); + this._ngDestroy$.complete(); + } +} diff --git a/packages/merchant-tablet-ionic/src/pages/+errors/+connection-lost/connection-lost.html b/packages/merchant-tablet-ionic/src/pages/+errors/+connection-lost/connection-lost.html new file mode 100644 index 0000000..bf6f60d --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+errors/+connection-lost/connection-lost.html @@ -0,0 +1,22 @@ + +
+
+ {{ companyName }} Logo + +

+ {{ 'CONNECTION_ERROR_VIEW.TITLE' | translate }} +

+ +

+ {{ 'CONNECTION_ERROR_VIEW.DESCRIPTION.0' | translate }} +
+ {{ 'CONNECTION_ERROR_VIEW.DESCRIPTION.1' | translate }}
+ {{ 'CONNECTION_ERROR_VIEW.DESCRIPTION.2' | translate }} +

+ + +
+
+
diff --git a/packages/merchant-tablet-ionic/src/pages/+errors/+connection-lost/connection-lost.module.ts b/packages/merchant-tablet-ionic/src/pages/+errors/+connection-lost/connection-lost.module.ts new file mode 100644 index 0000000..fb836be --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+errors/+connection-lost/connection-lost.module.ts @@ -0,0 +1,26 @@ +import { NgModule } from '@angular/core'; +import { ConnectionLostPage } from './connection-lost'; +import { TranslateModule } from '@ngx-translate/core'; +import { CommonModule } from '@angular/common'; +import { Routes, RouterModule } from '@angular/router'; +import { FormsModule } from '@angular/forms'; +import { IonicModule } from '@ionic/angular'; + +const routes: Routes = [ + { + path: '', + component: ConnectionLostPage, + }, +]; + +@NgModule({ + imports: [ + CommonModule, + FormsModule, + IonicModule, + RouterModule.forChild(routes), + TranslateModule.forChild(), + ], + declarations: [ConnectionLostPage], +}) +export class ConnectionLostPageModule {} diff --git a/packages/merchant-tablet-ionic/src/pages/+errors/+connection-lost/connection-lost.scss b/packages/merchant-tablet-ionic/src/pages/+errors/+connection-lost/connection-lost.scss new file mode 100644 index 0000000..24fb654 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+errors/+connection-lost/connection-lost.scss @@ -0,0 +1,32 @@ +@import '~@ever-platform/common-angular/src/scss/everbie.common'; + +.connection-error-view { + padding: 0; + .container-data { + width: 100%; + height: 100%; + background: $brand-darken !important; + text-align: center; + } + .connection-error { + img { + height: 200px; + } + @include center(absolute); + + .connection-error-title { + background: $brand-darken !important; + + color: white; + } + + .connection-error-description { + color: white; + } + + .connection-error-spinner { + color: white; + font-size: 5vw; + } + } +} diff --git a/packages/merchant-tablet-ionic/src/pages/+errors/+connection-lost/connection-lost.ts b/packages/merchant-tablet-ionic/src/pages/+errors/+connection-lost/connection-lost.ts new file mode 100644 index 0000000..77ccd8f --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+errors/+connection-lost/connection-lost.ts @@ -0,0 +1,17 @@ +import { Component } from '@angular/core'; +import { environment } from '../../../environments/environment'; + +@Component({ + selector: 'page-connection-lost', + templateUrl: 'connection-lost.html', + styleUrls: ['./connection-lost.scss'], +}) +export class ConnectionLostPage { + noInternetLogo: string; + companyName: string; + + constructor() { + this.noInternetLogo = environment.NO_INTERNET_LOGO; + this.companyName = environment.COMPANY_NAME; + } +} diff --git a/packages/merchant-tablet-ionic/src/pages/+errors/errors.module.ts b/packages/merchant-tablet-ionic/src/pages/+errors/errors.module.ts new file mode 100644 index 0000000..7237d49 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+errors/errors.module.ts @@ -0,0 +1,18 @@ +import { NgModule } from '@angular/core'; +import { RouterModule, Routes } from '@angular/router'; + +const routes: Routes = [ + { + path: 'connection-lost', + loadChildren: () => + import('./+connection-lost/connection-lost.module').then( + (m) => m.ConnectionLostPageModule + ), + }, +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule], +}) +export class ErrorsModule {} diff --git a/packages/merchant-tablet-ionic/src/pages/+info/+about/about.html b/packages/merchant-tablet-ionic/src/pages/+info/+about/about.html new file mode 100644 index 0000000..420c5fc --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+info/+about/about.html @@ -0,0 +1,27 @@ + + + + +
+
+ {{ 'ABOUT_VIEW.APP_VERSION' | translate }}: {{ appVersion }} +
+
diff --git a/packages/merchant-tablet-ionic/src/pages/+info/+about/about.module.ts b/packages/merchant-tablet-ionic/src/pages/+info/+about/about.module.ts new file mode 100644 index 0000000..199554c --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+info/+about/about.module.ts @@ -0,0 +1,30 @@ +import { NgModule } from '@angular/core'; +import { AboutPage } from './about'; +import { PipesModule } from '@modules/client.common.angular2/pipes/pipes.module'; +import { TranslateModule } from '@ngx-translate/core'; +import { ComponentsModule } from 'components/components.module'; +import { Routes, RouterModule } from '@angular/router'; +import { CommonModule } from '@angular/common'; +import { FormsModule } from '@angular/forms'; +import { IonicModule } from '@ionic/angular'; + +const routes: Routes = [ + { + path: '', + component: AboutPage, + }, +]; + +@NgModule({ + declarations: [AboutPage], + imports: [ + PipesModule, + ComponentsModule, + RouterModule.forChild(routes), + TranslateModule.forChild(), + CommonModule, + FormsModule, + IonicModule, + ], +}) +export class AboutPageModule {} diff --git a/packages/merchant-tablet-ionic/src/pages/+info/+about/about.scss b/packages/merchant-tablet-ionic/src/pages/+info/+about/about.scss new file mode 100644 index 0000000..020ddac --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+info/+about/about.scss @@ -0,0 +1,17 @@ +page-about { + .toolbar-title.toolbar-title-md { + text-align: center; + color: white; + } + .scroll-content { + padding: 0 !important; + } + .toolbar-background { + background-color: #2a2c39; + } + .about-us-content { + min-height: 90vh; + width: 100% !important; + padding-top: 50px; + } +} diff --git a/packages/merchant-tablet-ionic/src/pages/+info/+about/about.ts b/packages/merchant-tablet-ionic/src/pages/+info/+about/about.ts new file mode 100644 index 0000000..7949a58 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+info/+about/about.ts @@ -0,0 +1,35 @@ +import { Component, OnDestroy } from '@angular/core'; +import { UserRouter } from '@modules/client.common.angular2/routers/user-router.service'; +import { Subscription } from 'rxjs'; +import { environment } from 'environment'; + +@Component({ + selector: 'page-about', + templateUrl: 'about.html', +}) +export class AboutPage implements OnDestroy { + public useAboutHtml: string = '

Loading...

'; + public selectedLanguage: string; + private sub: Subscription; + public deviceId: string; + public userId: string; + public appVersion: string; + constructor(private userRouter: UserRouter) { + this.selectedLanguage = localStorage.getItem('_language') || 'en-US'; + this.deviceId = localStorage.getItem('_deviceId'); + this.userId = localStorage.getItem('_userId'); + this.appVersion = environment.APP_VERSION; + } + + ngOnInit() { + this.sub = this.userRouter + .getAboutUs(this.userId, this.deviceId, this.selectedLanguage) + .subscribe((html) => { + this.useAboutHtml = html; + }); + } + + ngOnDestroy() { + this.sub.unsubscribe(); + } +} diff --git a/packages/merchant-tablet-ionic/src/pages/+info/+terms-of-use/terms-of-use.html b/packages/merchant-tablet-ionic/src/pages/+info/+terms-of-use/terms-of-use.html new file mode 100644 index 0000000..aff9f55 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+info/+terms-of-use/terms-of-use.html @@ -0,0 +1,20 @@ + + + +
+
diff --git a/packages/merchant-tablet-ionic/src/pages/+info/+terms-of-use/terms-of-use.module.ts b/packages/merchant-tablet-ionic/src/pages/+info/+terms-of-use/terms-of-use.module.ts new file mode 100644 index 0000000..1103e24 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+info/+terms-of-use/terms-of-use.module.ts @@ -0,0 +1,30 @@ +import { NgModule } from '@angular/core'; +import { PipesModule } from '@modules/client.common.angular2/pipes/pipes.module'; +import { TranslateModule } from '@ngx-translate/core'; +import { ComponentsModule } from 'components/components.module'; +import { Routes, RouterModule } from '@angular/router'; +import { CommonModule } from '@angular/common'; +import { FormsModule } from '@angular/forms'; +import { IonicModule } from '@ionic/angular'; +import { TermsOfUsePage } from './terms-of-use'; + +const routes: Routes = [ + { + path: '', + component: TermsOfUsePage, + }, +]; + +@NgModule({ + declarations: [TermsOfUsePage], + imports: [ + TranslateModule.forChild(), + PipesModule, + ComponentsModule, + CommonModule, + FormsModule, + IonicModule, + RouterModule.forChild(routes), + ], +}) +export class TermsOfUsePageModule {} diff --git a/packages/merchant-tablet-ionic/src/pages/+info/+terms-of-use/terms-of-use.scss b/packages/merchant-tablet-ionic/src/pages/+info/+terms-of-use/terms-of-use.scss new file mode 100644 index 0000000..8d07845 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+info/+terms-of-use/terms-of-use.scss @@ -0,0 +1,2 @@ +page-terms-of-use { +} diff --git a/packages/merchant-tablet-ionic/src/pages/+info/+terms-of-use/terms-of-use.ts b/packages/merchant-tablet-ionic/src/pages/+info/+terms-of-use/terms-of-use.ts new file mode 100644 index 0000000..b1eda55 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+info/+terms-of-use/terms-of-use.ts @@ -0,0 +1,32 @@ +import { Component, OnInit, OnDestroy } from '@angular/core'; +import { UserRouter } from '@modules/client.common.angular2/routers/user-router.service'; +import { Subscription } from 'rxjs'; + +@Component({ + selector: 'page-terms-of-use', + templateUrl: 'terms-of-use.html', +}) +export class TermsOfUsePage implements OnInit, OnDestroy { + public useTermsHtml: string = '

Loading...

'; + public selectedLanguage: string; + private sub: Subscription; + public deviceId: string; + public userId: string; + constructor(private userRouter: UserRouter) { + this.selectedLanguage = localStorage.getItem('_language') || 'en-US'; + this.deviceId = localStorage.getItem('_deviceId'); + this.userId = localStorage.getItem('_userId'); + } + + ngOnInit(): void { + this.sub = this.userRouter + .getTermsOfUse(this.userId, this.deviceId, this.selectedLanguage) + .subscribe((html) => { + this.useTermsHtml = html; + }); + } + + ngOnDestroy() { + this.sub.unsubscribe(); + } +} diff --git a/packages/merchant-tablet-ionic/src/pages/+info/info.module.guard.ts b/packages/merchant-tablet-ionic/src/pages/+info/info.module.guard.ts new file mode 100644 index 0000000..d2067a7 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+info/info.module.guard.ts @@ -0,0 +1,20 @@ +import { Injectable } from '@angular/core'; +import { CanLoad, Route, Router } from '@angular/router'; +import { Store } from '../../services/store.service'; + +@Injectable() +export class InfoModuleGuard implements CanLoad { + constructor( + private readonly store: Store, + private readonly router: Router + ) {} + + canLoad(route: Route) { + if (!this.store.warehouseId || !this.store.deviceId) { + this.router.navigate(['login']); + return false; + } + + return true; + } +} diff --git a/packages/merchant-tablet-ionic/src/pages/+info/info.module.ts b/packages/merchant-tablet-ionic/src/pages/+info/info.module.ts new file mode 100644 index 0000000..3e1c3aa --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+info/info.module.ts @@ -0,0 +1,23 @@ +import { NgModule } from '@angular/core'; +import { RouterModule, Routes } from '@angular/router'; + +const routes: Routes = [ + { + path: 'about', + loadChildren: () => + import('./+about/about.module').then((m) => m.AboutPageModule), + }, + { + path: 'terms-of-use', + loadChildren: () => + import('./+terms-of-use/terms-of-use.module').then( + (m) => m.TermsOfUsePageModule + ), + }, +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule], +}) +export class InfoModule {} diff --git a/packages/merchant-tablet-ionic/src/pages/+language/language.html b/packages/merchant-tablet-ionic/src/pages/+language/language.html new file mode 100644 index 0000000..5861f77 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+language/language.html @@ -0,0 +1,37 @@ +
+

{{ 'LANGUAGE_VIEW.TITLE' | translate }}

+
+ +
+ + + +
+
+
+
+ + + + + {{ 'SIDE_MENU.GROUPS.SETTINGS.ITEMS.LANGUAGE' | translate + }} + + + {{ lang }} + + + + + diff --git a/packages/merchant-tablet-ionic/src/pages/+language/language.module.ts b/packages/merchant-tablet-ionic/src/pages/+language/language.module.ts new file mode 100644 index 0000000..ea38326 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+language/language.module.ts @@ -0,0 +1,40 @@ +import { NgModule } from '@angular/core'; +import { LanguagePage } from './language'; +import { TranslateModule, TranslateLoader } from '@ngx-translate/core'; +import { HttpClient } from '@angular/common/http'; +import { TranslateHttpLoader } from '@ngx-translate/http-loader'; +import { Store } from '../../services/store.service'; +import { Routes, RouterModule } from '@angular/router'; +import { IonicModule } from '@ionic/angular'; +import { CommonModule } from '@angular/common'; +import { FormsModule } from '@angular/forms'; + +export function HttpLoaderFactory(http: HttpClient) { + return new TranslateHttpLoader(http, './assets/i18n/', '.json'); +} + +const routes: Routes = [ + { + path: '', + component: LanguagePage, + }, +]; + +@NgModule({ + declarations: [LanguagePage], + imports: [ + IonicModule, + RouterModule.forChild(routes), + CommonModule, + FormsModule, + TranslateModule.forChild({ + loader: { + provide: TranslateLoader, + useFactory: HttpLoaderFactory, + deps: [HttpClient], + }, + }), + ], + providers: [Store], +}) +export class LanguagePageModule {} diff --git a/packages/merchant-tablet-ionic/src/pages/+language/language.scss b/packages/merchant-tablet-ionic/src/pages/+language/language.scss new file mode 100644 index 0000000..c1e022d --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+language/language.scss @@ -0,0 +1,20 @@ +.language-header { + position: relative; + .button-icon { + ion-icon { + font-size: 2rem; + } + } +} + +.language-content { + .toolbar-title.toolbar-title-md { + text-align: center; + color: white; + font-size: 100%; + } + + .toolbar-background { + background-color: #2a2c39; + } +} diff --git a/packages/merchant-tablet-ionic/src/pages/+language/language.ts b/packages/merchant-tablet-ionic/src/pages/+language/language.ts new file mode 100644 index 0000000..5ee7dac --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+language/language.ts @@ -0,0 +1,81 @@ +import { Component, OnInit, Inject } from '@angular/core'; +import { TranslateService } from '@ngx-translate/core'; +import { DeviceRouter } from '@modules/client.common.angular2/routers/device-router.service'; +import { Store } from '../../services/store.service'; +import ILanguage from '@modules/server.common/interfaces/ILanguage'; +import { DOCUMENT } from '@angular/common'; + +@Component({ + selector: 'page-language', + templateUrl: 'language.html', + styleUrls: ['./language.scss'], +}) +export class LanguagePage implements OnInit { + language: ILanguage; + dir: 'ltr' | 'rtl'; + + OK: string = 'OK'; + CANCEL: string = 'CANCEL'; + PREFIX: string = 'LANGUAGE_VIEW.'; + selected: string; + + constructor( + public translate: TranslateService, + private _deviceRouter: DeviceRouter, + private store: Store, + @Inject(DOCUMENT) private document: Document + ) {} + + ngOnInit() { + this.selected = localStorage.getItem('_language'); + this.language = localStorage.getItem('_language') as ILanguage; + // TODO: use settings service to get list of supported languages + this.translate.addLangs([ + 'en-US', + 'bg-BG', + 'he-IL', + 'ru-RU', + 'es-ES', + 'fr-FR', + ]); + } + + get buttonOK() { + return this._translate(this.PREFIX + this.OK); + } + + get buttonCancel() { + return this._translate(this.PREFIX + this.CANCEL); + } + + switchLanguage(language: string) { + this._deviceRouter.updateLanguage( + localStorage.getItem('_deviceId'), + this.language + ); + this.store.language = language; + this.translate.use(language); + + const currentLang = localStorage.getItem('_language'); + + const langAbbreviation = currentLang.substr(0, 2); + + if (currentLang === 'he-IL') { + this.dir = 'rtl'; + } else { + this.dir = 'ltr'; + } + this.document.documentElement.dir = this.dir; + this.document.documentElement.lang = langAbbreviation; + } + + private _translate(key: string): string { + let translationResult = ''; + + this.translate.get(key).subscribe((res) => { + translationResult = res; + }); + + return translationResult; + } +} diff --git a/packages/merchant-tablet-ionic/src/pages/+login/login.html b/packages/merchant-tablet-ionic/src/pages/+login/login.html new file mode 100644 index 0000000..20e08de --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+login/login.html @@ -0,0 +1,32 @@ + + + diff --git a/packages/merchant-tablet-ionic/src/pages/+login/login.module.guard.ts b/packages/merchant-tablet-ionic/src/pages/+login/login.module.guard.ts new file mode 100644 index 0000000..7eb04eb --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+login/login.module.guard.ts @@ -0,0 +1,15 @@ +import { Injectable } from '@angular/core'; +import { CanLoad, Route, Router } from '@angular/router'; +import { Store } from '../../services/store.service'; + +@Injectable() +export class LoginModuleGuard implements CanLoad { + constructor( + private readonly store: Store, + private readonly router: Router + ) {} + + async canLoad(route: Route) { + return true; + } +} diff --git a/packages/merchant-tablet-ionic/src/pages/+login/login.module.ts b/packages/merchant-tablet-ionic/src/pages/+login/login.module.ts new file mode 100644 index 0000000..d7eedbc --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+login/login.module.ts @@ -0,0 +1,29 @@ +import { NgModule } from '@angular/core'; +import { LoginPage } from './login'; +import { TranslateModule } from '@ngx-translate/core'; +import { AuthService } from '../../services/auth.service'; +import { Store } from '../../services/store.service'; +import { Routes, RouterModule } from '@angular/router'; +import { CommonModule } from '@angular/common'; +import { FormsModule } from '@angular/forms'; +import { IonicModule } from '@ionic/angular'; + +const routes: Routes = [ + { + path: '', + component: LoginPage, + }, +]; + +@NgModule({ + declarations: [LoginPage], + providers: [AuthService, Store], + imports: [ + IonicModule, + RouterModule.forChild(routes), + CommonModule, + FormsModule, + TranslateModule.forChild(), + ], +}) +export class LoginPageModule {} diff --git a/packages/merchant-tablet-ionic/src/pages/+login/login.scss b/packages/merchant-tablet-ionic/src/pages/+login/login.scss new file mode 100644 index 0000000..2bd6cd4 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+login/login.scss @@ -0,0 +1,59 @@ +@import '~@ever-platform/common-angular/src/scss/everbie.common'; + +.login-view { + background-color: $brand; + width: 100%; + height: 100%; + + .login-view-content { + background-color: $brand; + text-align: center; + max-width: 500px; + max-height: 650px; + width: 80%; + @include center(absolute); + + .logo { + color: #fff; + + img { + width: 50%; + @media (max-height: 550px) { + width: 30%; + } + } + } + + .login-form { + margin: auto; + .login-input { + max-width: 400px; + width: 80%; + margin-top: 20px; + padding: 12px !important; + font-size: 24px; + border: 0; + border-bottom: solid 3px #fff; + background: none; + display: inline; + color: #fff; + line-height: 1.42857; + outline: none; /* prevents textbox highlight in chrome */ + } + .login-button { + margin-top: 50px; + max-width: 400px; + width: 80%; + color: white; + background: darken($brand, 5%); + padding: 16px; + font-size: 24px; + border: solid darken($brand, 10%) 1px; + border-radius: 10px; + &:active { + background-color: darken($brand, 3%); + } + } + } + } +} diff --git a/packages/merchant-tablet-ionic/src/pages/+login/login.ts b/packages/merchant-tablet-ionic/src/pages/+login/login.ts new file mode 100644 index 0000000..9583ca5 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+login/login.ts @@ -0,0 +1,49 @@ +import { Component } from '@angular/core'; +import { AuthService } from '../../services/auth.service'; +import { first } from 'rxjs/operators'; +import { Store } from '../../services/store.service'; +import { environment } from '../../environments/environment'; +import { Router } from '@angular/router'; + +@Component({ + selector: 'page-login', + templateUrl: 'login.html', + styleUrls: ['./login.scss'], +}) +export class LoginPage { + username: string = ''; + password: string = ''; + loginLogo: string; + + constructor( + private authService: AuthService, + private store: Store, + private router: Router + ) { + localStorage.removeItem('_warehouseId'); + localStorage.removeItem('_language'); + localStorage.removeItem('token'); + this.username = environment.DEFAULT_LOGIN_USERNAME; + this.password = environment.DEFAULT_LOGIN_PASSWORD; + this.loginLogo = environment.LOGIN_LOGO; + } + + async login() { + const res = await this.authService + .login(this.username, this.password) + .pipe(first()) + .toPromise(); + + if (!res) { + alert('Merchant not found!'); + return; + } + + console.log(`Merchant logged in with id ${res.warehouse.id}`); + + this.store.warehouseId = res.warehouse.id; + this.store.token = res.token; + + this.router.navigate(['warehouse']); + } +} diff --git a/packages/merchant-tablet-ionic/src/pages/+promotions/promotion-forms/basic-info/basic-info-form.component.html b/packages/merchant-tablet-ionic/src/pages/+promotions/promotion-forms/basic-info/basic-info-form.component.html new file mode 100644 index 0000000..16152b4 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+promotions/promotion-forms/basic-info/basic-info-form.component.html @@ -0,0 +1,100 @@ +
+ + + +
+ {{ 'CARRIERS_VIEW.PROMOTIONS.BASIC_INFO' | translate }} +
+
+
+ + + + Language + + {{ lang | translate }} + + + + + + + + Select Product + + + {{ product.title }} + + + + + + + + {{ + 'CARRIERS_VIEW.PROMOTIONS.TITLE' | translate + }} + + + + + + + + {{ + 'CARRIERS_VIEW.PROMOTIONS.DESCRIPTION' | translate + }} + + + + + + + + {{ + 'CARRIERS_VIEW.PROMOTIONS.ACTIVE_FROM' | translate + }} + + + + + + {{ + 'CARRIERS_VIEW.PROMOTIONS.ACTIVE_TO' | translate + }} + + + + +
+
diff --git a/packages/merchant-tablet-ionic/src/pages/+promotions/promotion-forms/basic-info/basic-info-form.component.scss b/packages/merchant-tablet-ionic/src/pages/+promotions/promotion-forms/basic-info/basic-info-form.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/packages/merchant-tablet-ionic/src/pages/+promotions/promotion-forms/basic-info/basic-info-form.component.ts b/packages/merchant-tablet-ionic/src/pages/+promotions/promotion-forms/basic-info/basic-info-form.component.ts new file mode 100644 index 0000000..aee6637 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+promotions/promotion-forms/basic-info/basic-info-form.component.ts @@ -0,0 +1,262 @@ +import { Component, OnDestroy, OnInit, Input } from '@angular/core'; +import { FormGroup, FormBuilder, Validators } from '@angular/forms'; +import { + IPromotion, + IPromotionCreateObject, +} from '@modules/server.common/interfaces/IPromotion'; +import { Subject } from 'rxjs'; +import { ILocaleMember } from '@modules/server.common/interfaces/ILocale'; +import { takeUntil } from 'rxjs/operators'; +import { WarehouseProductsRouter } from '@modules/client.common.angular2/routers/warehouse-products-router.service'; +import IProduct from '@modules/server.common/interfaces/IProduct'; +import { ProductLocalesService } from '@modules/client.common.angular2/locale/product-locales.service'; +import { + LanguageCodesEnum, + LanguagesEnum, +} from '@modules/server.common/interfaces/ILanguage'; + +@Component({ + selector: 'basic-info-form', + styleUrls: ['./basic-info-form.component.scss'], + templateUrl: 'basic-info-form.component.html', +}) +export class BasicInfoFormComponent implements OnInit, OnDestroy { + @Input() + readonly form: FormGroup; + + @Input() + promotionData: IPromotion; + + public languages = Object.keys(LanguageCodesEnum); + + availableProducts: Partial[] = []; + displayProducts: { id: string; title: string; image: string }[]; + + private languageCode: string; + + private promotion: Partial; + + private translateProperties = ['title', 'description']; + + get locale() { + return this.form.get('locale'); + } + + get title() { + return this.form.get('title'); + } + + get description() { + return this.form.get('description'); + } + + get warehouseId() { + return localStorage.getItem('_warehouseId'); + } + + get language() { + return localStorage.getItem('_language'); + } + + private _ngDestroy$ = new Subject(); + + constructor( + private readonly warehouseProductService: WarehouseProductsRouter, + private readonly productLocalesService: ProductLocalesService + ) {} + + ngOnInit(): void { + this._setDefaultLocaleValue(); + + this._initMerchantProducts(); + + this.promotion = this.promotionData || this._initPromotion(); + + this._initTranslationValues(); + + this._setValue(); + + this._subscribeToLanguageChanges(); + } + + ngOnDestroy() { + this._ngDestroy$.next(); + this._ngDestroy$.complete(); + } + + getValue() { + this._saveTranslationValues(); + + let basicInfoValue = this.form.value; + + basicInfoValue.warehouse = { _id: this.warehouseId }; + + basicInfoValue.title = this.promotion.title.map((localeMember: any) => { + return { + locale: localeMember.locale, + value: localeMember.value, + }; + }); + + basicInfoValue.description = this.promotion.description.map( + (localeMember: any) => { + return { + locale: localeMember.locale, + value: localeMember.value, + }; + } + ); + + delete basicInfoValue.locale; + + return basicInfoValue as IPromotionCreateObject; + } + + getLanguageCode(language: string) { + return LanguageCodesEnum[language]; + } + + private _setValue() { + if (!this.promotion) return; + + const promotionFormValue = { + title: this.productLocalesService.getTranslate( + this.promotion.title, + this.languageCode + ), + description: this.productLocalesService.getTranslate( + this.promotion.description, + this.languageCode + ), + activeFrom: this.promotion.activeFrom || new Date(), + activeTo: this.promotion.activeTo || null, + product: this.promotion.productId || null, + }; + + this.form.patchValue(promotionFormValue); + } + + static buildForm(formBuilder: FormBuilder): FormGroup { + return formBuilder.group({ + locale: ['en-US'], + title: ['', Validators.required], + description: [''], + activeFrom: [null, Validators.required], + activeTo: [null, Validators.required], + product: [null, Validators.required], + }); + } + + private _saveLocaleMember( + translateProp: string, + member: ILocaleMember[] + ): void { + const memberValue = this._getLocaleMember(member, this.languageCode); + + if (memberValue) { + let updateProperty = this.promotion[translateProp].find( + (localeValue) => { + return localeValue['locale'] === this.languageCode; + } + ); + updateProperty.value = this[translateProp].value; + } + } + + private _addLocaleMember(member: ILocaleMember[]): void { + const memberValue = this._getLocaleMember(member, this.locale.value); + + if (!memberValue) { + member.push({ locale: this.locale.value, value: '' }); + } + } + + private _initTranslationValues() { + this.translateProperties.forEach((promotionTranslateProp) => { + this._addLocaleMember(this.promotion[promotionTranslateProp]); + }); + } + + private _saveTranslationValues() { + this.translateProperties.forEach((promotionTranslateProp) => { + this._saveLocaleMember( + promotionTranslateProp, + this.promotion[promotionTranslateProp] + ); + }); + } + + private _initMerchantProducts() { + this.warehouseProductService + .getAvailable(this.warehouseId || null) + .pipe(takeUntil(this._ngDestroy$)) + .subscribe((resData) => { + if (!resData) return; + + this.availableProducts = resData.map((warehouseProduct) => { + return { + id: warehouseProduct.product['id'] || null, + title: warehouseProduct.product['title'] || [], + images: warehouseProduct.product['images'] || [], + }; + }); + + this._initFormDisplayProducts(); + }); + } + + private _initFormDisplayProducts() { + if (!this.availableProducts) return; + + this.displayProducts = this.availableProducts.map((product) => { + return { + id: product['id'], + image: this.productLocalesService.getTranslate(product.images), + title: this.productLocalesService.getTranslate(product.title), + }; + }); + } + + private _subscribeToLanguageChanges() { + this.locale.valueChanges + .pipe(takeUntil(this._ngDestroy$)) + .subscribe((value) => { + this._saveTranslationValues(); + this._initTranslationValues(); + + this.languageCode = value; + + this._initFormDisplayProducts(); + this._setValue(); + }); + } + + private _initPromotion() { + return { + title: [], + description: [], + activeFrom: new Date(), + activeTo: null, + product: null, + }; + } + + private _setDefaultLocaleValue() { + this.languageCode = this.language || 'en-US'; + } + + private _getLocaleMember( + promotionMember: ILocaleMember[], + languageCode: string + ): ILocaleMember | boolean { + let resultLocale: ILocaleMember; + + if (promotionMember) { + resultLocale = promotionMember.find((t) => { + return t.locale === languageCode; + }); + } + + return resultLocale ? resultLocale : false; + } +} diff --git a/packages/merchant-tablet-ionic/src/pages/+promotions/promotion-forms/details-info/details-info-form.component.html b/packages/merchant-tablet-ionic/src/pages/+promotions/promotion-forms/details-info/details-info-form.component.html new file mode 100644 index 0000000..69fb2d7 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+promotions/promotion-forms/details-info/details-info-form.component.html @@ -0,0 +1,61 @@ +
+ + + + {{ + 'CARRIERS_VIEW.PROMOTIONS.PURCHASES_COUNT' | translate + }} + + + + + + {{ + 'PROMOTIONS.PROMO_PRICE' | translate + }} + + + + + + + + {{ + 'CARRIERS_VIEW.PROMOTIONS.IS_ACTIVE' | translate + }} + + + + + + + + + + + + + + + + + +
diff --git a/packages/merchant-tablet-ionic/src/pages/+promotions/promotion-forms/details-info/details-info-form.component.scss b/packages/merchant-tablet-ionic/src/pages/+promotions/promotion-forms/details-info/details-info-form.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/packages/merchant-tablet-ionic/src/pages/+promotions/promotion-forms/details-info/details-info-form.component.ts b/packages/merchant-tablet-ionic/src/pages/+promotions/promotion-forms/details-info/details-info-form.component.ts new file mode 100644 index 0000000..fec1870 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+promotions/promotion-forms/details-info/details-info-form.component.ts @@ -0,0 +1,69 @@ +import { Component, Input, OnInit } from '@angular/core'; +import { FormGroup, FormBuilder, Validators } from '@angular/forms'; +import { + IPromotion, + IPromotionCreateObject, +} from '@modules/server.common/interfaces/IPromotion'; + +@Component({ + selector: 'details-info-form', + styleUrls: ['./details-info-form.component.scss'], + templateUrl: 'details-info-form.component.html', +}) +export class DetailsInfoFormComponent implements OnInit { + @Input() + readonly form: FormGroup; + + @Input() + promotionData: IPromotion; + + promotionDetails: Partial; + + constructor() {} + + ngOnInit(): void { + this.promotionDetails = + this.promotionData || this._initPromotionDetails(); + + this.setValue(); + } + + get image() { + return this.form.get('image'); + } + + static buildForm(formBuilder: FormBuilder): FormGroup { + return formBuilder.group({ + image: [''], + active: [true], + purchasesCount: [0, Validators.min(0)], + promoPrice: [0, Validators.min(0)], + }); + } + + getValue() { + return this.form.value as IPromotionCreateObject; + } + + setValue() { + if (!this.promotionDetails) return; + + const promotionFormValue = { + image: this.promotionDetails.image || '', + active: this.promotionDetails.active || true, + purchasesCount: this.promotionDetails.purchasesCount || 0, + promoPrice: this.promotionData.promoPrice || 0, + }; + + this.form.patchValue(promotionFormValue); + } + + private _initPromotionDetails() { + return { + image: null, + active: true, + purchasesCount: 0, + promoPrice: 0, + }; + } +} diff --git a/packages/merchant-tablet-ionic/src/pages/+promotions/promotion-forms/promotion-forms.module.ts b/packages/merchant-tablet-ionic/src/pages/+promotions/promotion-forms/promotion-forms.module.ts new file mode 100644 index 0000000..7e45d31 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+promotions/promotion-forms/promotion-forms.module.ts @@ -0,0 +1,25 @@ +import { NgModule } from '@angular/core'; +import { BasicInfoFormComponent } from './basic-info/basic-info-form.component'; +import { DetailsInfoFormComponent } from './details-info/details-info-form.component'; +import { IonicModule } from '@ionic/angular'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { TranslateModule } from '@ngx-translate/core'; +import { FileUploaderModule } from 'components/file-uploader/file-uploader.module'; +import { CommonModule } from '@angular/common'; + +const COMPONENTS = [BasicInfoFormComponent, DetailsInfoFormComponent]; + +@NgModule({ + declarations: COMPONENTS, + exports: COMPONENTS, + imports: [ + CommonModule, + IonicModule, + FormsModule, + ReactiveFormsModule, + TranslateModule.forChild(), + IonicModule, + FileUploaderModule, + ], +}) +export class PromotionFormsModule {} diff --git a/packages/merchant-tablet-ionic/src/pages/+promotions/promotion-mutation-popup/promotion-mutation.html b/packages/merchant-tablet-ionic/src/pages/+promotions/promotion-mutation-popup/promotion-mutation.html new file mode 100644 index 0000000..5859ca2 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+promotions/promotion-mutation-popup/promotion-mutation.html @@ -0,0 +1,55 @@ + +

+ {{ (promotion ? 'Edit promotion' : 'Add promotion') | translate }} +

+ + + + + + + + + + + + + + + + {{ 'CARRIERS_VIEW.PROMOTIONS.BACK' | translate }} + + + {{ 'CARRIERS_VIEW.PROMOTIONS.NEXT' | translate }} + + + {{ 'CARRIERS_VIEW.PROMOTIONS.SAVE' | translate }} + + + +
diff --git a/packages/merchant-tablet-ionic/src/pages/+promotions/promotion-mutation-popup/promotion-mutation.module.ts b/packages/merchant-tablet-ionic/src/pages/+promotions/promotion-mutation-popup/promotion-mutation.module.ts new file mode 100644 index 0000000..ed9f85f --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+promotions/promotion-mutation-popup/promotion-mutation.module.ts @@ -0,0 +1,27 @@ +import { NgModule } from '@angular/core'; +import { PromotionMutation } from './promotion-mutation'; +import { CommonModule } from '@angular/common'; +import { TranslateModule } from '@ngx-translate/core'; +import { IonicModule } from '@ionic/angular'; +import { Ng2SmartTableModule } from 'ng2-smart-table'; +import { PromotionService } from 'services/promotion.service'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { PromotionFormsModule } from '../promotion-forms/promotion-forms.module'; + +const COMPONENTS = [PromotionMutation]; + +@NgModule({ + declarations: COMPONENTS, + exports: COMPONENTS, + imports: [ + CommonModule, + TranslateModule.forChild(), + IonicModule, + Ng2SmartTableModule, + FormsModule, + ReactiveFormsModule, + PromotionFormsModule, + ], + providers: [PromotionService] +}) +export class PromotionMutationModule {} diff --git a/packages/merchant-tablet-ionic/src/pages/+promotions/promotion-mutation-popup/promotion-mutation.scss b/packages/merchant-tablet-ionic/src/pages/+promotions/promotion-mutation-popup/promotion-mutation.scss new file mode 100644 index 0000000..d125b5e --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+promotions/promotion-mutation-popup/promotion-mutation.scss @@ -0,0 +1,4 @@ +.promotion-mutation-component { + background: #f3f3f3 !important; + margin: 0; +} diff --git a/packages/merchant-tablet-ionic/src/pages/+promotions/promotion-mutation-popup/promotion-mutation.ts b/packages/merchant-tablet-ionic/src/pages/+promotions/promotion-mutation-popup/promotion-mutation.ts new file mode 100644 index 0000000..43eb2c9 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+promotions/promotion-mutation-popup/promotion-mutation.ts @@ -0,0 +1,128 @@ +import { + OnDestroy, + Component, + ViewChild, + Input, + EventEmitter, + Output, + OnInit, +} from '@angular/core'; +import { FormGroup, FormBuilder } from '@angular/forms'; +import { IPromotion } from '@modules/server.common/interfaces/IPromotion'; +import { BasicInfoFormComponent } from '../promotion-forms/basic-info/basic-info-form.component'; +import { ToastController, ModalController } from '@ionic/angular'; +import { DetailsInfoFormComponent } from '../promotion-forms/details-info/details-info-form.component'; +import { PromotionService } from 'services/promotion.service'; +import { first } from 'rxjs/operators'; +import { Subject } from 'rxjs'; + +@Component({ + selector: 'promotion-mutation', + templateUrl: 'promotion-mutation.html', + styleUrls: ['./promotion-mutation.scss'], +}) +export class PromotionMutation implements OnDestroy { + @Input() + promotion: IPromotion; + + @Input() + visible: boolean = true; + + @Output() + updateVisible = new EventEmitter(); + + form: FormGroup = this._formBuilder.group({ + basicInfo: BasicInfoFormComponent.buildForm(this._formBuilder), + detailsInfo: DetailsInfoFormComponent.buildForm(this._formBuilder), + }); + + @ViewChild('basicInfoForm') + basicInfoForm: BasicInfoFormComponent; + + @ViewChild('promotionDetailsForm') + detailsInfoForm: DetailsInfoFormComponent; + + private _ngDestroy$ = new Subject(); + + readonly basicInfo = this.form.get('basicInfo') as FormGroup; + readonly detailsInfo = this.form.get('detailsInfo') as FormGroup; + + isNextStepAvailable: boolean = true; + + constructor( + private readonly _formBuilder: FormBuilder, + private readonly toastrController: ToastController, + private readonly modalController: ModalController, + private readonly promotionService: PromotionService + ) {} + + ngOnDestroy(): void { + this._ngDestroy$.next(true); + this._ngDestroy$.unsubscribe(); + } + + savePromotion() { + let promotionUserInput = { + ...this.basicInfoForm.getValue(), + ...this.detailsInfoForm.getValue(), + }; + + if (!this.form.valid) { + this.presentToast('Please fill in valid data.'); + return; + } + + if (!this.promotion) { + this.promotionService + .create(promotionUserInput) + .pipe(first()) + .subscribe( + (data) => { + this.presentToast('Successfully created promotion!'); + }, + (err) => { + this.presentToast( + err.message || 'Something went wrong!' + ); + }, + () => { + this.updateVisible.emit(true); + this.cancelModal(); + } + ); + } else { + this.promotionService + .update(this.promotion._id.toString(), promotionUserInput) + .pipe(first()) + .subscribe( + (data) => { + this.presentToast('Successfully updated promotion!'); + }, + (err) => { + this.presentToast( + err.message || 'Something went wrong!' + ); + }, + () => { + this.updateVisible.emit(true); + this.cancelModal(); + } + ); + } + } + + private presentToast(message: string) { + this.toastrController + .create({ + message, + duration: 2000, + }) + .then((toast) => { + toast.present(); + }); + } + + cancelModal() { + this.modalController.dismiss(); + } +} diff --git a/packages/merchant-tablet-ionic/src/pages/+promotions/promotion-table-page/promotion-table.html b/packages/merchant-tablet-ionic/src/pages/+promotions/promotion-table-page/promotion-table.html new file mode 100644 index 0000000..6881fa6 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+promotions/promotion-table-page/promotion-table.html @@ -0,0 +1,45 @@ +
+ +
+ + + +
+
+

{{ 'PROMOTIONS.NO_PROMOTIONS' | translate }}

+
+
+
+
diff --git a/packages/merchant-tablet-ionic/src/pages/+promotions/promotion-table-page/promotion-table.module.ts b/packages/merchant-tablet-ionic/src/pages/+promotions/promotion-table-page/promotion-table.module.ts new file mode 100644 index 0000000..d4e4811 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+promotions/promotion-table-page/promotion-table.module.ts @@ -0,0 +1,34 @@ +import { Routes, RouterModule } from '@angular/router'; +import { NgModule } from '@angular/core'; +import { PromotionTable } from './promotion-table'; +import { TranslateModule } from '@ngx-translate/core'; +import { IonicModule } from '@ionic/angular'; +import { PromotionService } from 'services/promotion.service'; +import { Ng2SmartTableModule } from 'ng2-smart-table'; +import { CommonModule } from '@angular/common'; +import { ConfirmDeletePopupModule } from 'components/confirm-delete-popup/confirm-delete-popup.module'; +import { ComponentsModule } from 'components/components.module'; +import { ImageTableComponent } from 'components/table-components/image-table'; + +const routes: Routes = [ + { + path: '', + component: PromotionTable, + }, +]; + +@NgModule({ + declarations: [PromotionTable], + imports: [ + CommonModule, + RouterModule.forChild(routes), + TranslateModule.forChild(), + IonicModule, + Ng2SmartTableModule, + ConfirmDeletePopupModule, + ComponentsModule, + ], + providers: [PromotionService], + exports: [RouterModule] +}) +export class PromotionTableModule {} diff --git a/packages/merchant-tablet-ionic/src/pages/+promotions/promotion-table-page/promotion-table.scss b/packages/merchant-tablet-ionic/src/pages/+promotions/promotion-table-page/promotion-table.scss new file mode 100644 index 0000000..ae15e44 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+promotions/promotion-table-page/promotion-table.scss @@ -0,0 +1,5 @@ +.page-promotions { + background-color: white; + min-height: 100%; + overflow-y: scroll; +} diff --git a/packages/merchant-tablet-ionic/src/pages/+promotions/promotion-table-page/promotion-table.ts b/packages/merchant-tablet-ionic/src/pages/+promotions/promotion-table-page/promotion-table.ts new file mode 100644 index 0000000..38c1bdf --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+promotions/promotion-table-page/promotion-table.ts @@ -0,0 +1,221 @@ +import { OnInit, OnDestroy, Component } from '@angular/core'; +import { LocalDataSource } from 'ng2-smart-table'; +import { IPromotion } from '@modules/server.common/interfaces/IPromotion'; +import { Subject } from 'rxjs'; +import { PromotionService } from 'services/promotion.service'; +import { ModalController, ToastController } from '@ionic/angular'; +import { TranslateService } from '@ngx-translate/core'; +import { takeUntil } from 'rxjs/operators'; +import { PromotionMutation } from '../promotion-mutation-popup/promotion-mutation'; +import { ProductLocalesService } from '@modules/client.common.angular2/locale/product-locales.service'; +import { ConfirmDeletePopupPage } from 'components/confirm-delete-popup/confirm-delete-popup'; +import { ImageTableComponent } from 'components/table-components/image-table'; +import { DatePipe } from '@angular/common'; + +@Component({ + selector: 'promotion-table', + templateUrl: 'promotion-table.html', + styleUrls: ['./promotion-table.scss'], +}) +export class PromotionTable implements OnInit, OnDestroy { + settingsSmartTable: object; + showNoPromotionsIcon: boolean = false; + + promotions: IPromotion[]; + + sourceSmartTable = new LocalDataSource(); + selectedPromotions: IPromotion[]; + + private _ngDestroy$ = new Subject(); + + constructor( + private readonly promotionsService: PromotionService, + private readonly productLocaleService: ProductLocalesService, + private readonly translateService: TranslateService, + private readonly toastrController: ToastController, + public modalCtrl: ModalController + ) {} + + ngOnInit(): void { + this._loadPromotions(); + this._applyTranslationOnSmartTable(); + } + + ngOnDestroy() { + this._ngDestroy$.next(); + this._ngDestroy$.complete(); + } + + private _loadPromotions() { + this.promotionsService + .getAll({ warehouse: localStorage.getItem('_warehouseId') || null }) + .pipe(takeUntil(this._ngDestroy$)) + .subscribe((promotionsRes) => { + this.promotions = promotionsRes.promotions || []; + + this._loadSettingsSmartTable(); + this._loadDataSmartTable(); + + this.promotions.length === 0 + ? (this.showNoPromotionsIcon = true) + : (this.showNoPromotionsIcon = false); + }); + } + + private _loadSettingsSmartTable() { + this.translateService + .get('CARRIERS_VIEW.PROMOTIONS') + .pipe(takeUntil(this._ngDestroy$)) + .subscribe((TRANSLATE_DATA) => { + this.settingsSmartTable = { + mode: 'external', + edit: { + editButtonContent: '', + confirmEdit: true, + }, + delete: { + deleteButtonContent: '', + confirmDelete: true, + }, + columns: { + image: { + title: TRANSLATE_DATA.IMAGE, + type: 'custom', + renderComponent: ImageTableComponent, + filter: false, + }, + title: { + title: TRANSLATE_DATA.TITLE, + type: 'string', + }, + active: { + title: TRANSLATE_DATA.STATUS, + type: 'html', + valuePrepareFunction: (active) => { + return `${active ? '✔' : '✘'}`; + }, + }, + activeFrom: { + title: TRANSLATE_DATA.ACTIVE_FROM, + type: 'html', + valuePrepareFunction: this.formatTableDate, + }, + activeTo: { + title: TRANSLATE_DATA.ACTIVE_TO, + type: 'html', + valuePrepareFunction: this.formatTableDate, + }, + purchasesCount: { + title: TRANSLATE_DATA.PURCHASES_COUNT, + type: 'number', + }, + }, + }; + }); + } + + async openAddPromotion() { + const addPromotionPopup = await this.modalCtrl.create({ + component: PromotionMutation, + }); + + await addPromotionPopup.present(); + + await addPromotionPopup.onDidDismiss(); + + this._loadPromotions(); + } + + private _applyTranslationOnSmartTable() { + this.translateService.onLangChange + .pipe(takeUntil(this._ngDestroy$)) + .subscribe(() => { + this._loadSettingsSmartTable(); + this._loadDataSmartTable(); + }); + } + + private _loadDataSmartTable() { + const promotionsVM = this.promotions.map((promotion) => { + return { + id: promotion._id, + image: promotion.image, + title: this.productLocaleService.getTranslate(promotion.title), + active: promotion.active, + activeFrom: promotion.activeFrom, + activeTo: promotion.activeTo, + purchasesCount: promotion.purchasesCount, + promotion: promotion, + }; + }); + + this.sourceSmartTable.load(promotionsVM); + } + + async editPromotion(event: any) { + const modal = await this.modalCtrl.create({ + component: PromotionMutation, + componentProps: { promotion: event.data.promotion }, + }); + + await modal.present(); + + await modal.onDidDismiss(); + + this._loadPromotions(); + } + + async deletePromotion(event: any) { + const modal = await this.modalCtrl.create({ + component: ConfirmDeletePopupPage, + componentProps: { + data: event.data, + customText: `Promotion: ${event.data.title || '-'}`, + }, + cssClass: 'confirm-delete-wrapper', + }); + + await modal.present(); + + const res = await modal.onDidDismiss(); + + if (res.data) { + const promotionId = event.data.id; + + this.promotionsService.removeByIds([promotionId]).subscribe( + (data) => { + this.promotions = this.promotions.filter( + (p) => ![promotionId].includes(p._id) + ); + + this._loadDataSmartTable(); + this.presentToast('Successfully deleted promotion!'); + }, + (err) => { + this.presentToast(err.message || 'Something went wrong!'); + } + ); + } + } + + private presentToast(message: string) { + this.toastrController + .create({ + message, + duration: 2000, + }) + .then((toast) => { + toast.present(); + }); + } + + private formatTableDate(date: string) { + const raw: Date = new Date(date); + + const formatted: string = date + ? new DatePipe('en-EN').transform(raw, 'dd-MMM-yyyy hh:mm:ss') + : '-'; + + return `

${formatted}

`; + } +} diff --git a/packages/merchant-tablet-ionic/src/pages/+promotions/promotion.module.ts b/packages/merchant-tablet-ionic/src/pages/+promotions/promotion.module.ts new file mode 100644 index 0000000..81780d2 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+promotions/promotion.module.ts @@ -0,0 +1,37 @@ +import { Routes, RouterModule } from '@angular/router'; +import { NgModule } from '@angular/core'; +import { IonicModule } from '@ionic/angular'; +import { TranslateModule } from '@ngx-translate/core'; +import { Ng2SmartTableModule } from 'ng2-smart-table'; +import { CommonModule } from '@angular/common'; +import { PromotionService } from 'services/promotion.service'; +import { PromotionMutationModule } from './promotion-mutation-popup/promotion-mutation.module'; + +const routes: Routes = [ + { + path: '', + redirectTo: 'all', + }, + { + path: 'all', + loadChildren: () => + import('./promotion-table-page/promotion-table.module').then( + (m) => m.PromotionTableModule + ), + }, +]; + +@NgModule({ + declarations: [], + imports: [ + CommonModule, + RouterModule.forChild(routes), + IonicModule, + TranslateModule.forChild(), + Ng2SmartTableModule, + PromotionMutationModule, + ], + exports: [RouterModule], + providers: [PromotionService], +}) +export class PromotionModule {} diff --git a/packages/merchant-tablet-ionic/src/pages/+settings/settings.html b/packages/merchant-tablet-ionic/src/pages/+settings/settings.html new file mode 100644 index 0000000..7d66f65 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+settings/settings.html @@ -0,0 +1,58 @@ +
+ + + + + {{ 'SETTINGS_VIEW.ACCOUNT' | translate }} + + + + {{ 'SETTINGS_VIEW.COMMON' | translate }} + + + + {{ 'SETTINGS_VIEW.LOCATION' | translate }} + + + + {{ 'SETTINGS_VIEW.SETTINGS' | translate }} + + + +
+
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+
+
diff --git a/packages/merchant-tablet-ionic/src/pages/+settings/settings.module.ts b/packages/merchant-tablet-ionic/src/pages/+settings/settings.module.ts new file mode 100644 index 0000000..2063358 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+settings/settings.module.ts @@ -0,0 +1,46 @@ +import { NgModule } from '@angular/core'; +import { SettingsPage } from './settings'; +import { TranslateModule, TranslateLoader } from '@ngx-translate/core'; +import { HttpClient } from '@angular/common/http'; +import { TranslateHttpLoader } from '@ngx-translate/http-loader'; +import { FileUploadModule } from 'ng2-file-upload'; +import { GoogleMapModule } from '../../@shared/google-map/google-map.module'; +import { ComponentsModule } from '../../components/components.module'; +import { Routes, RouterModule } from '@angular/router'; +import { IonicModule } from '@ionic/angular'; +import { CommonModule } from '@angular/common'; +import { FormsModule } from '@angular/forms'; +import { MerchantSettingsComponentModule } from 'components/settings-page-components/settings/settings.module'; + +export function HttpLoaderFactory(http: HttpClient) { + return new TranslateHttpLoader(http, './assets/i18n/', '.json'); +} + +const routes: Routes = [ + { + path: '', + component: SettingsPage, + }, +]; + +@NgModule({ + declarations: [SettingsPage], + imports: [ + FileUploadModule, + GoogleMapModule, + ComponentsModule, + TranslateModule.forChild({ + loader: { + provide: TranslateLoader, + useFactory: HttpLoaderFactory, + deps: [HttpClient], + }, + }), + IonicModule, + CommonModule, + FormsModule, + MerchantSettingsComponentModule, + RouterModule.forChild(routes), + ], +}) +export class SettingsPageModule {} diff --git a/packages/merchant-tablet-ionic/src/pages/+settings/settings.scss b/packages/merchant-tablet-ionic/src/pages/+settings/settings.scss new file mode 100644 index 0000000..2d12ee4 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+settings/settings.scss @@ -0,0 +1,171 @@ +.page-settings { + background: #fff; + min-height: 100%; + overflow-y: scroll !important; + overflow-x: hidden; + margin: 0; + padding: 0; + ion-segment { + margin-top: 60px !important; + } + ion-label { + display: inline !important; + } + + input::placeholder { + padding-left: 50px !important; + } + .select-md { + padding-top: 14.5px !important; + } + .segmentDivs { + font-size: 1.2rem; + // text-align: center; + padding-right: 16px; + } + #special-label { + height: 24px !important; + margin: 16px; + } + + .map-wrapper { + margin-top: 10px; + } + + #common, + #location, + #account { + padding-top: 10px; + + div.row { + ion-item { + padding: 5px 15px; + } + section.col { + margin-top: 15px; + } + } + + h1 { + margin-bottom: 2vh; + } + .labels { + display: inline-block; + font-size: 1em; + color: gray; + opacity: 0.8; + } + } + input { + font-size: 1em; + &:focus { + color: black; + font-size: 1em; + } + } + .warning { + font-size: 1em; + color: orange; + font-weight: bold; + } + .success { + font-size: 1em; + color: yellowgreen; + font-weight: bold; + } + .save-location { + margin-top: 15px !important; + } + + ion-row.row { + background: white; + i { + &:before { + margin-right: 5px; + } + } + } + + .location-find { + padding-top: 3%; + padding-bottom: 3%; + + width: 50%; + margin: auto; + } + + .preview-img { + padding-left: 14px; + padding-right: 16px; + text-align: center; + } + + .removeIcon { + padding-left: 4px; + } + + .upload-input { + padding-right: 0 !important; + } + + #remove-circle { + font-size: 1em; + } + + .text-header { + margin-left: 16px; + border-bottom: 1px solid black; + } + + .save-button { + margin-left: 16px; + margin-top: 16px; + border: none; + } + + .invalid { + border-bottom: 1px solid red; + padding-left: 0px !important; + } +} +.success-info { + .alert-message { + color: green; + border-bottom: 0.5px solid black; + padding-top: 20px !important; + padding-bottom: 20px; + text-align: center; + } + + .alert-head { + display: none; + } + + .alert-button { + margin: 0 auto; + } + .alert-button-group { + padding: 0; + } +} + +.error-info { + .alert-message { + text-align: center; + color: red; + border-bottom: 0.5px solid black; + padding-top: 20px !important; + padding-bottom: 20px; + } + + .alert-head { + display: none; + } + + .alert-button { + margin: 0 auto; + } + .alert-button-group { + padding: 0; + } +} diff --git a/packages/merchant-tablet-ionic/src/pages/+settings/settings.ts b/packages/merchant-tablet-ionic/src/pages/+settings/settings.ts new file mode 100644 index 0000000..211cfac --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+settings/settings.ts @@ -0,0 +1,44 @@ +import { Component } from '@angular/core'; +import { WarehouseRouter } from '@modules/client.common.angular2/routers/warehouse-router.service'; +import Warehouse from '@modules/server.common/entities/Warehouse'; +import { Store } from '../../../src/services/store.service'; + +@Component({ + selector: 'page-settings', + templateUrl: 'settings.html', + styleUrls: ['./settings.scss'], +}) +export class SettingsPage { + selectedSegment: any = 'account'; + + public _currWarehouse: Warehouse; + + constructor( + private warehouseRouter: WarehouseRouter, + private store: Store + ) { + this.getLocalWarehouse(); + } + + get isLogged() { + return localStorage.getItem('_warehouseId'); + } + + get isBrowser() { + return localStorage.getItem('_platform') === 'browser'; + } + + async ionViewCanEnter() { + const isLogged = await this.store.isLogged(); + + return this.store.maintenanceMode === null && isLogged; + } + + getLocalWarehouse() { + this.warehouseRouter + .get(localStorage.getItem('_warehouseId')) + .subscribe((warehouse) => { + this._currWarehouse = warehouse; + }); + } +} diff --git a/packages/merchant-tablet-ionic/src/pages/+track/track.html b/packages/merchant-tablet-ionic/src/pages/+track/track.html new file mode 100644 index 0000000..faebb1c --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+track/track.html @@ -0,0 +1,56 @@ + +
+
+ +
diff --git a/packages/merchant-tablet-ionic/src/pages/+track/track.module.ts b/packages/merchant-tablet-ionic/src/pages/+track/track.module.ts new file mode 100644 index 0000000..0a81462 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+track/track.module.ts @@ -0,0 +1,36 @@ +import { NgModule } from '@angular/core'; +import { TrackPage } from './track'; +import { PipesModule } from '@modules/client.common.angular2/pipes/pipes.module'; +import { ComponentsModule } from '../../components/components.module'; +import { TranslateModule } from '@ngx-translate/core'; +import { CarrierService } from '../../../src/services/carrier.service'; +import { WarehousesService } from '../../../src/services/warehouses.service'; +import { Routes, RouterModule } from '@angular/router'; +import { IonicModule } from '@ionic/angular'; +import { CommonModule } from '@angular/common'; +import { FormsModule } from '@angular/forms'; +import { IonicSelectableModule } from 'ionic-selectable'; +import { WarehouseOrdersService } from '../../services/warehouse-orders.service'; + +const routes: Routes = [ + { + path: '', + component: TrackPage, + }, +]; + +@NgModule({ + declarations: [TrackPage], + imports: [ + PipesModule, + ComponentsModule, + TranslateModule.forChild(), + IonicSelectableModule, + IonicModule, + CommonModule, + FormsModule, + RouterModule.forChild(routes), + ], + providers: [CarrierService, WarehousesService, WarehouseOrdersService], +}) +export class TrackPageModule {} diff --git a/packages/merchant-tablet-ionic/src/pages/+track/track.scss b/packages/merchant-tablet-ionic/src/pages/+track/track.scss new file mode 100644 index 0000000..ab07e98 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+track/track.scss @@ -0,0 +1,19 @@ +.map { + width: calc(100vw - 375px); + height: calc(100vh - 43px); +} +#contentWrapper { + margin-top: 45px; + display: flex; + .sidebar { + width: 375px; + height: 100%; + background: white; + display: flex; + flex-direction: column; + .statistics { + //padding-left:10px; + //padding-top:10px; + } + } +} diff --git a/packages/merchant-tablet-ionic/src/pages/+track/track.ts b/packages/merchant-tablet-ionic/src/pages/+track/track.ts new file mode 100644 index 0000000..0e667b6 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+track/track.ts @@ -0,0 +1,330 @@ +import { + Component, + ViewChild, + ElementRef, + OnDestroy, + OnInit, +} from '@angular/core'; + +import Carrier from '@modules/server.common/entities/Carrier'; +import { CarrierService } from '../../../src/services/carrier.service'; +import { Subject, Subscription } from 'rxjs'; +import { IonicSelectableComponent } from 'ionic-selectable'; +import { ActivatedRoute, Router } from '@angular/router'; +import { WarehousesService } from 'services/warehouses.service'; +import { WarehouseOrdersService } from '../../services/warehouse-orders.service'; + +declare var google: any; + +@Component({ + selector: 'page-track', + templateUrl: 'track.html', + styleUrls: ['./track.scss'], +}) +export class TrackPage implements OnInit, OnDestroy { + private carriersOnDisplay: Carrier[]; + private carriers$: Subscription; + private warehouse$: Subscription; + private params$: Subscription; + private warehouseCoordinates: any; + private orders$: Subscription; + private _ngDestroy$ = new Subject(); + + map: google.maps.Map; + selectedCarrier: Carrier; + carriers: Carrier[]; + markers: google.maps.Marker[] = []; + totalDeliveries = 0; + totalCarriers = 0; + totalActiveCarriers = 0; + showAssignedOnly = true; + showActiveOnly = true; + showCheckboxFilters = true; + loadingMap = false; + listOfOrders: any; + storeIcon = 'http://maps.google.com/mapfiles/kml/pal3/icon21.png'; + sharedCarrierIcon = 'http://maps.google.com/mapfiles/kml/pal4/icon23.png'; + userIcon = 'http://maps.google.com/mapfiles/kml/pal3/icon48.png'; + carrierIcon = 'http://maps.google.com/mapfiles/kml/pal4/icon54.png'; + carrierListDropdown: Carrier[]; + sharedCarrierListId: string[]; + + @ViewChild('gmap', { static: true }) + gmapElement: ElementRef; + + @ViewChild('filterComponent', { static: true }) + filterComponent: IonicSelectableComponent; + + constructor( + private carrierService: CarrierService, + private route: ActivatedRoute, + private router: Router, + private warehouseService: WarehousesService, + private warehouseOrderService: WarehouseOrdersService + ) { + this.route.params.subscribe(() => this.loadData()); + } + + openModal() { + this.filterComponent.close(); + } + + navigationHandler(event: { + component: IonicSelectableComponent; + value: any; + }) { + this.carriers$.unsubscribe(); + this.params$.unsubscribe(); + this.warehouse$.unsubscribe(); + this.router.navigate([`track/${event.value.id}`]); + } + + ngOnInit(): void {} + + loadData() { + this.warehouse$ = this.warehouseService + .getAllStores() + .subscribe((warehouse) => { + const currentWarehouse = warehouse.find( + (wh) => wh.id === this.warehouseId + ); + const allAssignedCarriers = warehouse + .filter((wh) => wh.id !== this.warehouseId) + .map((wh) => wh.usedCarriersIds); + + this.warehouseCoordinates = { + lat: currentWarehouse.geoLocation.loc.coordinates[1], + lng: currentWarehouse.geoLocation.loc.coordinates[0], + }; + this.totalCarriers = currentWarehouse.usedCarriersIds.length; + this.carriers$ = this.carrierService + .getAllCarriers() + .subscribe((carriers) => { + this.loadGoogleMaps(); + this.carriers = carriers.filter((car) => { + if ( + currentWarehouse.usedCarriersIds.includes( + car.id + ) + ) { + car.shared = false; + return true; + } else { + if ( + allAssignedCarriers.every( + (arr) => !arr.includes(car.id) + ) + ) { + car.shared = true; + return true; + } else { + return false; + } + } + }); + let total = 0; + + this.carriers.forEach((car) => { + total += car.numberOfDeliveries; + }); + + this.totalDeliveries = total; + + this.params$ = this.route.params.subscribe((res) => { + this.totalActiveCarriers = this.carriers.filter( + (car) => car.status === 0 + ).length; + + if (res.id) { + this.selectedCarrier = this.carriers.filter( + (car) => car.id === res.id + )[0]; + this.showCheckboxFilters = false; + this.carriersOnDisplay = [this.selectedCarrier]; + this.renderCarriers([this.selectedCarrier]); + this.filterDisplayedCarriers(); + } else { + this.filterDisplayedCarriers(); + this.renderCarriers(this.carriersOnDisplay); + } + }); + }); + }); + } + + get warehouseId() { + return localStorage.getItem('_warehouseId'); + } + + drawOrderRoutes() { + this.warehouseOrderService + .getOrdersInDelivery(this.warehouseId) + .then((orderList) => { + this.listOfOrders = orderList; + this.listOfOrders.forEach((order) => { + const carrier = this.carriersOnDisplay.find( + (x) => x.id === order.carrier.id + ); + + if (carrier && carrier.shared) { + this.addMarker( + { + lat: + order.carrier.geoLocation.loc + .coordinates[1], + lng: + order.carrier.geoLocation.loc + .coordinates[0], + }, + this.map, + this.sharedCarrierIcon + ); + } + + const carriersWithOrders = orderList.map( + (o) => o.carrier.id + ); + this.carriers = this.carriers.filter((car) => { + if ( + !carriersWithOrders.includes(car.id) && + car.shared + ) { + return false; + } else { + return true; + } + }); + this.carrierListDropdown = this.carriers; + + if ( + this.carriersOnDisplay + .map((car) => car.id) + .includes(order.carrier.id) + ) { + this.addMarker( + { + lat: order.user.geoLocation.loc.coordinates[1], + lng: order.user.geoLocation.loc.coordinates[0], + }, + this.map, + this.userIcon + ); + + const request = { + origin: new google.maps.LatLng( + order.carrier.geoLocation.loc.coordinates[1], + order.carrier.geoLocation.loc.coordinates[0] + ), + destination: new google.maps.LatLng( + order.user.geoLocation.loc.coordinates[1], + order.user.geoLocation.loc.coordinates[0] + ), + travelMode: 'DRIVING', + }; + const directionsDisplay = new google.maps.DirectionsRenderer( + { + polylineOptions: { + strokeColor: `hsl(${Math.floor( + Math.random() * 320 + )},100%,50%)`, + }, + } + ); + const directionsService = new google.maps.DirectionsService(); + directionsService.route(request, function (res, stat) { + if (stat === 'OK') { + directionsDisplay.setDirections(res); + } + }); + + directionsDisplay.setOptions({ + suppressMarkers: true, + }); + + directionsDisplay.setMap(this.map); + } + }); + }); + } + + renderCarriers(carriers: Carrier[]) { + if (this.markers.length > 0) { + this.markers.forEach((m) => { + m.setMap(null); + }); + this.markers = []; + } + carriers.forEach((carrier) => { + const mylatLng = { + lat: carrier.geoLocation.loc.coordinates[1], + lng: carrier.geoLocation.loc.coordinates[0], + }; + this.addMarker(this.warehouseCoordinates, this.map, this.storeIcon); + + if (!carrier.shared) { + this.addMarker(mylatLng, this.map, this.carrierIcon); + } + }); + this.drawOrderRoutes(); + } + + filterDisplayedCarriers() { + let filteredCarriers = this.carriers; + if (this.showActiveOnly && this.carriers.length > 1) { + filteredCarriers = filteredCarriers.filter( + (car) => car.status === 0 + ); + } + + if (this.showAssignedOnly && this.carriers.length > 1) { + filteredCarriers = filteredCarriers.filter( + (car) => car.shared === false + ); + } + this.carrierListDropdown = filteredCarriers; + this.carriersOnDisplay = filteredCarriers; + } + + loadGoogleMaps() { + this.loadingMap = true; + const initialCoords = new google.maps.LatLng(42.7089136, 23.3702736); + + const mapProp = { + center: initialCoords, + zoom: 13, + mapTypeId: google.maps.MapTypeId.ROADMAP, + }; + + this.map = new google.maps.Map(this.gmapElement.nativeElement, mapProp); + this.loadingMap = false; + } + + addMarker( + position: { lat: number; lng: number }, + map: google.maps.Map, + icon: string + ) { + const marker = new google.maps.Marker({ + position, + map, + icon, + }); + this.markers.push(marker); + } + + refreshMap() { + this.filterDisplayedCarriers(); + this.renderCarriers(this.carriersOnDisplay); + } + + ngOnDestroy() { + this._ngDestroy$.next(); + this._ngDestroy$.complete(); + if (this.carriers$) { + this.carriers$.unsubscribe(); + } + if (this.params$) { + this.params$.unsubscribe(); + } + } +} diff --git a/packages/merchant-tablet-ionic/src/pages/+warehouse/all-oders/all-orders.component.html b/packages/merchant-tablet-ionic/src/pages/+warehouse/all-oders/all-orders.component.html new file mode 100644 index 0000000..d75c9d4 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+warehouse/all-oders/all-orders.component.html @@ -0,0 +1,82 @@ + + + + +
+
+ + + + + + + + + + + + + + + + + +
+ + + +
diff --git a/packages/merchant-tablet-ionic/src/pages/+warehouse/all-oders/all-orders.component.scss b/packages/merchant-tablet-ionic/src/pages/+warehouse/all-oders/all-orders.component.scss new file mode 100644 index 0000000..e327330 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+warehouse/all-oders/all-orders.component.scss @@ -0,0 +1,19 @@ +.pagination { + text-align: center; + padding-bottom: 10px; + padding-left: 0 !important; + font-size: 1em; + display: block !important; +} + +ion-spinner { + width: 40px !important; + height: 40px !important; + left: calc(50% - 20px); + position: absolute; + zoom: 2; +} + +.orders-spinner { + top: 40%; +} diff --git a/packages/merchant-tablet-ionic/src/pages/+warehouse/all-oders/all-orders.component.ts b/packages/merchant-tablet-ionic/src/pages/+warehouse/all-oders/all-orders.component.ts new file mode 100644 index 0000000..fe9b378 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+warehouse/all-oders/all-orders.component.ts @@ -0,0 +1,114 @@ +import { + Component, + Input, + OnInit, + OnDestroy, + OnChanges, + Output, + EventEmitter, +} from '@angular/core'; +import { WarehouseOrdersService } from '../../../services/warehouse-orders.service'; +import { Store } from '../../../services/store.service'; +import Order from '@modules/server.common/entities/Order'; +import { OrderState } from '../warehouse'; +import { Subscription } from 'rxjs'; + +@Component({ + selector: 'merchant-all-orders', + templateUrl: 'all-orders.component.html', + styleUrls: ['./all-orders.component.scss'], +}) +export class AllOrdersComponent implements OnInit, OnDestroy, OnChanges { + @Input() + getWarehouseStatus: (status: any) => void; + + @Input() + onUpdateWarehouseStatus: any; + + @Input() + orderState: (Order) => void; + + @Input() + focusedOrder: Order; + + @Input() + isOrderContainerLive: boolean; + + @Output() + toggleOrderContainer: EventEmitter = new EventEmitter(); + + orders: Order[] = []; + ordersCount: number; + OrderState: any = OrderState; + page: number = 1; + ordersLoaded: boolean; + + private orders$: Subscription; + + constructor( + private warehouseOrdersService: WarehouseOrdersService, + private store: Store + ) {} + + ngOnInit() { + this.loadAllOrders(); + } + + ngOnChanges() { + if (this.focusedOrder) { + this.orders = [this.focusedOrder]; + } else { + this.orders = []; + this.page = 1; + this.loadAllOrders(); + } + } + + async loadPage(page: number) { + if (this.orders$) { + await this.orders$.unsubscribe(); + } + + this.orders$ = this.warehouseOrdersService + .getStoreOrdersTableData( + this.store.warehouseId, + { + sort: { + field: '_createdAt', + sortBy: 'desc', + }, + skip: (page - 1) * 10, + limit: 10, + }, + 'all' + ) + .subscribe(async (res) => { + const orders = res['orders']; + await this.loadOrdersCount(); + + if (!this.focusedOrder) { + this.page = page; + this.orders = orders; + } + this.ordersLoaded = true; + }); + } + + ngOnDestroy() { + if (this.orders$) { + this.orders$.unsubscribe(); + } + } + + private async loadAllOrders() { + await this.loadOrdersCount(); + this.loadPage(1); + } + + private async loadOrdersCount() { + this.ordersCount = await this.warehouseOrdersService.getCountOfStoreOrders( + this.store.warehouseId, + 'all' + ); + } +} diff --git a/packages/merchant-tablet-ionic/src/pages/+warehouse/all-products/all-products.component.html b/packages/merchant-tablet-ionic/src/pages/+warehouse/all-products/all-products.component.html new file mode 100644 index 0000000..bb722a1 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+warehouse/all-products/all-products.component.html @@ -0,0 +1,88 @@ +
+
+ +
+

+ {{ + 'WAREHOUSE_VIEW.ORDER_WAREHOUSE_STATUSES.NO_PRODUCTS' + | translate + }} +

+

+ {{ 'WAREHOUSE_VIEW.MISC_TEXT.ADD_NEW_PRODUCT' | translate }} +

+
+
+
+ +
+ + + +
+
+ +
+ +
+ + + + {{ + truncateTitle( + localeTranslate( + warehouseProduct.product.title + ) + ) + }} + + {{ warehouseProduct?.count }} +
+
+
+
+
+
+ + + diff --git a/packages/merchant-tablet-ionic/src/pages/+warehouse/all-products/all-products.component.scss b/packages/merchant-tablet-ionic/src/pages/+warehouse/all-products/all-products.component.scss new file mode 100644 index 0000000..dd7821c --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+warehouse/all-products/all-products.component.scss @@ -0,0 +1,92 @@ +@import '~@ever-platform/common-angular/src/scss/everbie.common'; + +.product-card-container { + border: 0; + padding-bottom: 0; + .product-card { + margin: 0; + padding: 0; + text-align: center; + .product-image-container { + cursor: pointer; + } + .product-image-container:active { + background: #000; + .product-image { + opacity: 0.8; + } + } + .product-buttons { + position: absolute; + top: 20px; + right: 20px; + .add-button { + padding: 0; + font-size: 30px; + color: #fff; + } + } + .product-image { + width: 100%; + } + .product-mini-bar { + font-size: 17px; + background-color: $brand; + color: white; + text-align: center; + margin-bottom: 0; + padding-top: 11px; + padding-bottom: 11px; + margin-top: -4px; + .edit-button { + margin-top: -4px; + border-radius: 3px; + color: $brand; + float: left; + border-color: #ccc; + margin-left: 10px; + min-width: 10px; + padding: 4px; + font-size: 16px; + line-height: 0; + min-height: 10px; + } + .count { + background: #fff; + margin-top: -2px; + padding: 2px; + border-radius: 3px; + color: $brand; + float: right; + margin-right: 10px; + } + } + } +} + +.masonry-container { + text-align: center; + width: 80%; + margin: 0 auto; + padding-top: 20px; +} + +ngx-masonry { + margin: 0 auto; +} + +.masonry-item { + width: 234px; + margin-bottom: 10px; +} + +.item-inner { + border-bottom: none; +} +.pagination { + text-align: center; + padding-bottom: 10px; + padding-left: 0 !important; + font-size: 1em; + display: block !important; +} diff --git a/packages/merchant-tablet-ionic/src/pages/+warehouse/all-products/all-products.component.ts b/packages/merchant-tablet-ionic/src/pages/+warehouse/all-products/all-products.component.ts new file mode 100644 index 0000000..bc1e0b1 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+warehouse/all-products/all-products.component.ts @@ -0,0 +1,117 @@ +import { Component, OnInit, OnDestroy, Input } from '@angular/core'; +import { WarehouseProductsService } from '../../../services/warehouse-products.service'; +import { Subscription } from 'rxjs'; +import WarehouseProduct from '@modules/server.common/entities/WarehouseProduct'; +import { ProductLocalesService } from '@modules/client.common.angular2/locale/product-locales.service'; +import { WarehouseProductsRouter } from '@modules/client.common.angular2/routers/warehouse-products-router.service'; +import { NgxMasonryOptions } from 'ngx-masonry'; +import { ModalController } from '@ionic/angular'; +import IWarehouseProduct from '@modules/server.common/interfaces/IWarehouseProduct'; + +@Component({ + selector: 'merchant-all-products', + templateUrl: 'all-products.component.html', + styleUrls: ['all-products.component.scss'], +}) +export class AllProductsComponent implements OnInit, OnDestroy { + @Input() + warehouseId: string; + + @Input() + presentCreateProductPopover: () => void; + + @Input() + addProduct: (id: string) => void; + + @Input() + getWarehouseProductImageUrl: (url: any) => void; + + @Input() + openEditProductModal: (warehouseProduct: IWarehouseProduct) => void; + + @Input() + truncateTitle: (title: any) => void; + + @Input() + localeTranslate: (title: any) => void; + + private products$: Subscription; + + allProducts: WarehouseProduct[] = []; + + public masonryOptions: NgxMasonryOptions = { + itemSelector: '.masonry-item', + columnWidth: 234, + // transitionDuration option not supported + // transitionDuration: '0.2s', + gutter: 10, + resize: true, + initLayout: true, + fitWidth: true, + }; + + page: number = 1; + productsCount: number; + + updateMasonryLayout: boolean = false; + showNoProductsIcon: boolean = false; + + constructor( + private warehouseProductsService: WarehouseProductsService, + private translateProductLocales: ProductLocalesService, + private warehouseProductsRouter: WarehouseProductsRouter, + private modalCtrl: ModalController + ) {} + + async ngOnInit() { + this.warehouseProductsService + .getProductsCount(this.warehouseId) + .then((count) => { + count === 0 + ? (this.showNoProductsIcon = true) + : (this.showNoProductsIcon = false); + this.productsCount = count; + }); + this.loadPage(this.page); + } + + loadPage(page: number) { + if (this.products$) { + this.products$.unsubscribe(); + } + + this.products$ = this.warehouseProductsService + .getProductsWithPagination(this.warehouseId, { + skip: (page - 1) * 10, + limit: 10, + }) + .subscribe((products) => { + this.updateMasonryLayout = true; + + this.allProducts = products.map( + (p) => + new WarehouseProduct({ + _id: p._id, + count: p.count, + product: p.product, + isManufacturing: p.isManufacturing, + isCarrierRequired: p.isCarrierRequired, + isDeliveryRequired: p.isCarrierRequired, + isTakeaway: p.isTakeaway, + _createdAt: p._createdAt, + _updatedAt: p._updatedAt, + price: p.price, + initialPrice: p.initialPrice, + }) + ); + + this.page = page; + }); + } + + ngOnDestroy() { + if (this.products$) { + this.products$.unsubscribe(); + } + } +} diff --git a/packages/merchant-tablet-ionic/src/pages/+warehouse/common/no-orders-info/no-orders-info.component.html b/packages/merchant-tablet-ionic/src/pages/+warehouse/common/no-orders-info/no-orders-info.component.html new file mode 100644 index 0000000..84ecb81 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+warehouse/common/no-orders-info/no-orders-info.component.html @@ -0,0 +1,17 @@ +
+ +
+

+ {{ + 'WAREHOUSE_VIEW.ORDER_WAREHOUSE_STATUSES.NO_ACTIVE_ORDERS' + | translate + }} +

+

+ {{ 'WAREHOUSE_VIEW.MISC_TEXT.CREATE_NEW_ORDER' | translate }} +

+
+

+ {{ 'WAREHOUSE_VIEW.ORDER_WAREHOUSE_STATUSES.NO_ORDERS' | translate }} +

+
diff --git a/packages/merchant-tablet-ionic/src/pages/+warehouse/common/no-orders-info/no-orders-info.component.scss b/packages/merchant-tablet-ionic/src/pages/+warehouse/common/no-orders-info/no-orders-info.component.scss new file mode 100644 index 0000000..7df08b8 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+warehouse/common/no-orders-info/no-orders-info.component.scss @@ -0,0 +1 @@ +@import '~@ever-platform/common-angular/src/scss/everbie.common'; diff --git a/packages/merchant-tablet-ionic/src/pages/+warehouse/common/no-orders-info/no-orders-info.component.ts b/packages/merchant-tablet-ionic/src/pages/+warehouse/common/no-orders-info/no-orders-info.component.ts new file mode 100644 index 0000000..4478841 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+warehouse/common/no-orders-info/no-orders-info.component.ts @@ -0,0 +1,14 @@ +import { Component, Input, Output, EventEmitter } from '@angular/core'; + +@Component({ + selector: 'no-orders-info', + templateUrl: 'no-orders-info.component.html', + styleUrls: ['./no-orders-info.component.scss'], +}) +export class NoOrdersInfoComponent { + @Input() + filterMode: string; + + @Output() + toggleOrderContainer: EventEmitter = new EventEmitter(); +} diff --git a/packages/merchant-tablet-ionic/src/pages/+warehouse/common/warehouse.common.module.ts b/packages/merchant-tablet-ionic/src/pages/+warehouse/common/warehouse.common.module.ts new file mode 100644 index 0000000..b05767f --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+warehouse/common/warehouse.common.module.ts @@ -0,0 +1,27 @@ +import { NgModule } from '@angular/core'; +import { TranslateModule, TranslateLoader } from '@ngx-translate/core'; +import { HttpClient } from '@angular/common/http'; +import { HttpLoaderFactory } from '../../../../src/app/app.module'; +import { NoOrdersInfoComponent } from './no-orders-info/no-orders-info.component'; +import { CommonModule } from '@angular/common'; +import { IonicModule } from '@ionic/angular'; + +const COMPONENTS = [NoOrdersInfoComponent]; + +@NgModule({ + declarations: COMPONENTS, + exports: COMPONENTS, + providers: [], + imports: [ + CommonModule, + IonicModule, + TranslateModule.forChild({ + loader: { + provide: TranslateLoader, + useFactory: HttpLoaderFactory, + deps: [HttpClient], + }, + }), + ], +}) +export class WarehouseCommonModule {} diff --git a/packages/merchant-tablet-ionic/src/pages/+warehouse/create-product-type-popup/create-product-type-popup.html b/packages/merchant-tablet-ionic/src/pages/+warehouse/create-product-type-popup/create-product-type-popup.html new file mode 100644 index 0000000..61191d0 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+warehouse/create-product-type-popup/create-product-type-popup.html @@ -0,0 +1,169 @@ + diff --git a/packages/merchant-tablet-ionic/src/pages/+warehouse/create-product-type-popup/create-product-type-popup.module.ts b/packages/merchant-tablet-ionic/src/pages/+warehouse/create-product-type-popup/create-product-type-popup.module.ts new file mode 100644 index 0000000..2787698 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+warehouse/create-product-type-popup/create-product-type-popup.module.ts @@ -0,0 +1,24 @@ +import { NgModule } from '@angular/core'; +import { FileTransfer } from '@ionic-native/file-transfer/ngx'; +import { TranslateModule } from '@ngx-translate/core'; +import { FileUploadModule } from 'ng2-file-upload'; +import { CreateProductTypePopupPage } from './create-product-type-popup'; +import { ProductsCategoryService } from '../../../services/products-category.service'; +import { CommonModule } from '@angular/common'; +import { FormsModule } from '@angular/forms'; +import { IonicModule } from '@ionic/angular'; +import { ProductImagesPopupModule } from '../product-pictures-popup/product-images-popup.module'; + +@NgModule({ + declarations: [CreateProductTypePopupPage], + providers: [FileTransfer, ProductsCategoryService], + imports: [ + FileUploadModule, + TranslateModule.forChild(), + CommonModule, + FormsModule, + IonicModule, + ProductImagesPopupModule, + ] +}) +export class CreateProductTypePopupPageModule {} diff --git a/packages/merchant-tablet-ionic/src/pages/+warehouse/create-product-type-popup/create-product-type-popup.scss b/packages/merchant-tablet-ionic/src/pages/+warehouse/create-product-type-popup/create-product-type-popup.scss new file mode 100644 index 0000000..c6a63eb --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+warehouse/create-product-type-popup/create-product-type-popup.scss @@ -0,0 +1,132 @@ +.create-product-type-popup { + #fileInput { + visibility: hidden; + } + .popup-input { + display: block; + margin-top: 1vh !important; + width: 100%; + } + + .upload-text, + .upload-icon { + &:hover { + cursor: pointer; + } + } + + .dragDrop { + position: absolute; + top: 15px; + width: 90%; + margin: 10 auto; + } + + .select { + font-size: 14px; + } + + .items-center { + display: flex; + align-items: center; + } + + .popup-half { + .title-popup { + margin-top: 10px; + } + + label { + width: 100%; + span { + font-size: 14px; + } + } + } + #image-holder { + height: 78%; + border-radius: 3px; + padding-top: 15%; + margin-bottom: 6%; + margin-top: 13px !important; + + .dragDrop { + margin: auto !important; + position: relative; + } + + + div.button-bar { + button.button-assertive { + border-top-left-radius: 3px !important; + border-bottom-left-radius: 3px !important; + } + button.button-brand { + border-top-right-radius: 3px !important; + border-bottom-right-radius: 3px !important; + } + + button:disabled { + cursor: default; + opacity: 0.4; + pointer-events: none; + } + } + } + + .subtitle { + margin-top: 0; + } + + .bottom-inputs { + display: flex; + align-items: flex-end; + justify-content: center; + } + + #multiple-select { + margin-top: 10px; + border: 2px solid #ddd; + height: auto !important; + border-bottom: 1px solid #ddd !important; + padding: 0; + div.item-inner { + border: none !important; + } + } + + .radio-list, + .getProductType { + .item-inner { + border: 0 !important; + } + + ion-radio.radio.radio-md { + margin-right: 0 !important; + } + + .coord-box { + font-size: 14px; + } + } + + .pl-0 { + padding-left: 0 !important; + .item-inner, + .item-cover, + ion-item { + padding-left: 0 !important; + } + } + .pr-0 { + padding-right: 0 !important; + .item-inner, + .item-cover, + ion-item { + padding-right: 0 !important; + } + } + + .d-inline-block { + display: inline-block; + } +} diff --git a/packages/merchant-tablet-ionic/src/pages/+warehouse/create-product-type-popup/create-product-type-popup.ts b/packages/merchant-tablet-ionic/src/pages/+warehouse/create-product-type-popup/create-product-type-popup.ts new file mode 100644 index 0000000..baf5a7e --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+warehouse/create-product-type-popup/create-product-type-popup.ts @@ -0,0 +1,460 @@ +import { Component, ViewChild, ElementRef, OnInit } from '@angular/core'; +import { FileUploader, FileUploaderOptions, FileItem } from 'ng2-file-upload'; +import { Camera, CameraOptions } from '@ionic-native/camera/ngx'; +import { + IProductCreateObject, + IProductImage, +} from '@modules/server.common/interfaces/IProduct'; +import { IWarehouseProductCreateObject } from '@modules/server.common/interfaces/IWarehouseProduct'; +import { ProductRouter } from '@modules/client.common.angular2/routers/product-router.service'; +import { WarehouseProductsRouter } from '@modules/client.common.angular2/routers/warehouse-products-router.service'; +import { ProductLocalesService } from '@modules/client.common.angular2/locale/product-locales.service'; +import { TranslateService } from '@ngx-translate/core'; +import { environment } from '../../../environments/environment'; +import { ProductsCategoryService } from '../../../services/products-category.service'; +import { first } from 'rxjs/operators'; +import ProductsCategory from '@modules/server.common/entities/ProductsCategory'; +import Warehouse from '@modules/server.common/entities/Warehouse'; +import { WarehouseRouter } from '@modules/client.common.angular2/routers/warehouse-router.service'; +import DeliveryType from '@modules/server.common/enums/DeliveryType'; +import { ModalController, ActionSheetController } from '@ionic/angular'; +import { ProductImagesPopup } from '../product-pictures-popup/product-images-popup.component'; + +@Component({ + selector: 'page-create-product-type-popup', + templateUrl: 'create-product-type-popup.html', + styleUrls: ['./create-product-type-popup.scss'], +}) +export class CreateProductTypePopupPage implements OnInit { + OK: string = 'OK'; + CANCEL: string = 'CANCEL'; + SELECT_CATEGORIES: string = 'SELECT_CATEGORIES'; + PREFIX: string = 'WAREHOUSE_VIEW.SELECT_POP_UP.'; + selectOptionsObj: object; + + takaProductDelivery: boolean; + takaProductTakeaway: boolean; + + uploader: FileUploader; + + productsCategories: ProductsCategory[]; + selectedProductCategories: string[] = []; + + productCreateObject: IProductCreateObject = { + title: [], + description: [], + images: [], + categories: [], + }; + + warehouseProductCreateObject: IWarehouseProductCreateObject = { + price: null, + count: null, + product: null, + initialPrice: null, + }; + + translLang: string; + + hasImage: boolean; + + @ViewChild('fileInput', { static: true }) + fileInput: ElementRef; + + private imagesData: IProductImage[]; + + constructor( + public readonly localeTranslateService: ProductLocalesService, + private productRouter: ProductRouter, + private warehouseProductsRouter: WarehouseProductsRouter, + private warehouseRouter: WarehouseRouter, + public modalCtrl: ModalController, + private camera: Camera, + public actionSheetCtrl: ActionSheetController, + private translate: TranslateService, + private readonly _productsCategorySrvice: ProductsCategoryService + ) { + this.loadMerchantSettings(); + + this.translLang = this.translate.currentLang; + this.currentLocale = + this.localeTranslateService.takeSelectedLang(this.translLang) || + 'en-US'; + + const uploaderOptions: FileUploaderOptions = { + url: environment.API_FILE_UPLOAD_URL, + + // Use xhrTransport in favor of iframeTransport + isHTML5: true, + // Calculate progress independently for each uploaded file + removeAfterUpload: true, + // XHR request headers + headers: [ + { + name: 'X-Requested-With', + value: 'XMLHttpRequest', + }, + ], + }; + + this.uploader = new FileUploader(uploaderOptions); + + this.uploader.onBuildItemForm = ( + fileItem: any, + form: FormData + ): any => { + // Add Cloudinary's unsigned upload preset to the upload form + form.append('upload_preset', 'everbie-products-images'); + // Add built-in and custom tags for displaying the uploaded photo in the list + let tags = 'myphotoalbum'; + if (this.productCreateObject.title) { + form.append( + 'context', + `photo=${this.productCreateObject.title}` + ); + tags = `myphotoalbum,${this.productCreateObject.title}`; + } + // Upload to a custom folder + // Note that by default, when uploading via the API, folders are not automatically created in your Media Library. + // In order to automatically create the folders based on the API requests, + // please go to your account upload settings and set the 'Auto-create folders' option to enabled. + // TODO: use settings from .env file + form.append('folder', 'angular_sample'); + // Add custom tags + form.append('tags', tags); + // Add file to upload + form.append('file', fileItem); + + // Use default "withCredentials" value for CORS requests + fileItem.withCredentials = false; + return { fileItem, form }; + }; + } + + ionViewDidLoad() {} + + @ViewChild('imageHolder', { static: true }) + private _imageHolder: ElementRef; + + imageUrlChanged(ev) { + const reader = new FileReader(); + + reader.addEventListener('load', (e) => { + const imageBase64 = e.target['result']; + this.hasImage = true; + this._setImageHolderBackground(imageBase64); + }); + + reader.readAsDataURL(ev.target.files[0]); + } + + get buttonOK() { + return this._translate(this.PREFIX + this.OK); + } + + get buttonCancel() { + return this._translate(this.PREFIX + this.CANCEL); + } + + get selectOptionTitle() { + const title = this._translate(this.PREFIX + this.SELECT_CATEGORIES); + this.selectOptionsObj = { subHeader: title }; + return this.selectOptionsObj; + } + + get warehouseId() { + return localStorage.getItem('_warehouseId'); + } + + get isBrowser() { + return localStorage.getItem('_platform') === 'browser'; + } + + get isReadyToCreate() { + return ( + this.localeTranslateService.isServiceStateValid && + this.warehouseProductCreateObject.price !== null && + this.warehouseProductCreateObject.price !== 0 && + (this.uploader.queue[0] || + (this.imagesData && this.imagesData.length > 0)) + ); + } + + get currentLocale() { + return this.localeTranslateService.currentLocale; + } + + set currentLocale(locale: string) { + this.localeTranslateService.currentLocale = locale; + } + + get productTitle() { + return this.localeTranslateService.getMemberValue( + this.productCreateObject.title + ); + } + set productTitle(title: string) { + this.localeTranslateService.setMemberValue('title', title); + } + + get productDescription() { + return this.localeTranslateService.getMemberValue( + this.productCreateObject.description + ); + } + + set productDescription(description: string) { + this.localeTranslateService.setMemberValue('description', description); + } + + async ngOnInit() { + await this._loadProductsCategories(); + } + + async showPicturesPopup() { + let images = []; + + if (!this.imagesData) { + this.imagesData = [await this.getProductImage()]; + } else { + const imagesDataLocale = this.imagesData[0].locale; + + if (imagesDataLocale === this.currentLocale) { + images = this.imagesData; + } else { + for (const image of this.imagesData) { + image.locale = this.currentLocale; + } + } + } + + images = this.imagesData; + + const modal = await this.modalCtrl.create({ + component: ProductImagesPopup, + componentProps: { + images, + }, + backdropDismiss: false, + cssClass: 'mutation-product-images-modal', + }); + + await modal.present(); + + const res = await modal.onDidDismiss(); + const imageArray = res.data; + if (imageArray && imageArray.length > 0) { + const firstImgUrl = imageArray[0].url; + this._setImageHolderBackground(firstImgUrl); + this.imagesData = imageArray; + } + } + + getProductTypeChange(type: string) { + if (DeliveryType[type] === DeliveryType.Delivery) { + if (!this.takaProductDelivery && !this.takaProductTakeaway) { + this.takaProductTakeaway = true; + } + } else { + if (!this.takaProductDelivery && !this.takaProductTakeaway) { + this.takaProductDelivery = true; + } + } + } + + async createProduct() { + let productImages = []; + if (this.imagesData && this.imagesData.length > 0) { + productImages = this.imagesData; + } else { + productImages = [await this.getProductImage()]; + } + this.productCreateObject.images = productImages; + + this.localeTranslateService.assignPropertyValue( + this.productCreateObject.title, + 'title' + ); + this.localeTranslateService.assignPropertyValue( + this.productCreateObject.description, + 'description' + ); + + this.productCreateObject.categories = this.productsCategories + .filter( + (category) => + this.selectedProductCategories && + this.selectedProductCategories.some( + (categoryId) => categoryId === category.id + ) + ) + .map((category) => { + return { + _id: category.id, + _createdAt: null, + _updatedAt: null, + name: category.name, + }; + }); + + const product = await this.productRouter.create( + this.productCreateObject + ); + + this.warehouseProductCreateObject.product = product.id; + this.warehouseProductCreateObject.initialPrice = + this.warehouseProductCreateObject.price || 0; + + this.warehouseProductCreateObject.count = + this.warehouseProductCreateObject.count || 0; + + this.warehouseProductCreateObject.isDeliveryRequired = this.takaProductDelivery; + this.warehouseProductCreateObject.isTakeaway = this.takaProductTakeaway; + + await this.warehouseProductsRouter.add(this.warehouseId, [ + this.warehouseProductCreateObject, + ]); + + this.cancelModal(); + } + + takePicture(sourceType: number) { + const options: CameraOptions = { + quality: 50, + destinationType: this.camera.DestinationType.DATA_URL, + encodingType: this.camera.EncodingType.JPEG, + mediaType: this.camera.MediaType.PICTURE, + correctOrientation: true, + sourceType, + }; + + this.camera.getPicture(options).then( + async (imageData) => { + const base64Image = 'data:image/jpeg;base64,' + imageData; + const file = await this.urltoFile( + base64Image, + this.createFileName(), + 'image/jpeg' + ); + const fileItem = new FileItem(this.uploader, file, {}); + this.uploader.queue.push(fileItem); + }, + (err) => {} + ); + } + + urltoFile(url, filename, mimeType) { + return fetch(url) + .then(function (res) { + return res.arrayBuffer(); + }) + .then(function (buf) { + return new File([buf], filename, { type: mimeType }); + }); + } + + async presentActionSheet() { + const actionSheet = await this.actionSheetCtrl.create({ + header: 'Select Image Source', + buttons: [ + { + text: 'Load from Library', + handler: () => { + this.takePicture( + this.camera.PictureSourceType.PHOTOLIBRARY + ); + }, + }, + { + text: 'Use Camera', + handler: () => { + this.takePicture(this.camera.PictureSourceType.CAMERA); + }, + }, + { text: 'Cancel', role: 'cancel' }, + ], + }); + + await actionSheet.present(); + } + + cancelModal() { + this.modalCtrl.dismiss(); + } + + private async _loadProductsCategories() { + this.productsCategories = await this._productsCategorySrvice + .getCategories() + .pipe(first()) + .toPromise(); + } + + private _translate(key: string): string { + let translationResult = ''; + + this.translate.get(key).subscribe((res) => { + translationResult = res; + }); + + return translationResult; + } + + private createFileName() { + return new Date().getTime() + '.jpg'; + } + + private _setImageHolderBackground(imageUrl: string) { + const gradient = `linear-gradient(rgba(0, 0, 0, 0.3), rgba(0, 0, 0, 0.3)), url(${imageUrl})`; + this._imageHolder.nativeElement.style.background = gradient; + this._imageHolder.nativeElement.style.backgroundSize = `cover`; + this._imageHolder.nativeElement.style.backgroundRepeat = 'no-repeat'; + this._imageHolder.nativeElement.style.color = `white`; + } + + private async loadMerchantSettings() { + if (this.warehouseId) { + const warehouse: Warehouse = await this.warehouseRouter + .get(this.warehouseId, false) + .pipe(first()) + .toPromise(); + if (warehouse) { + this.takaProductDelivery = warehouse.productsDelivery; + this.takaProductTakeaway = warehouse.productsTakeaway; + } + } + + if (!this.takaProductDelivery && !this.takaProductTakeaway) { + this.takaProductDelivery = true; + } + } + + private getProductImage(): Promise { + return new Promise(async (resolve, reject) => { + if (this.uploader.queue.length > 0) { + this.uploader.queue[this.uploader.queue.length - 1].upload(); + } + + this.uploader.onSuccessItem = ( + item: any, + response: string, + status: number + ) => { + const data = JSON.parse(response); + const locale = this.currentLocale; + const width = data.width; + const height = data.height; + const orientation = + width !== height ? (width > height ? 2 : 1) : 0; + const url = data.url; + + resolve({ + locale, + url, + width, + height, + orientation, + }); + }; + }); + } +} diff --git a/packages/merchant-tablet-ionic/src/pages/+warehouse/edit-product-type-popup/edit-product-type-popup.html b/packages/merchant-tablet-ionic/src/pages/+warehouse/edit-product-type-popup/edit-product-type-popup.html new file mode 100644 index 0000000..968bcbb --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+warehouse/edit-product-type-popup/edit-product-type-popup.html @@ -0,0 +1,168 @@ + diff --git a/packages/merchant-tablet-ionic/src/pages/+warehouse/edit-product-type-popup/edit-product-type-popup.module.ts b/packages/merchant-tablet-ionic/src/pages/+warehouse/edit-product-type-popup/edit-product-type-popup.module.ts new file mode 100644 index 0000000..6830e30 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+warehouse/edit-product-type-popup/edit-product-type-popup.module.ts @@ -0,0 +1,36 @@ +import { NgModule } from '@angular/core'; +import { FileTransfer } from '@ionic-native/file-transfer/ngx'; +import { FileUploadModule } from 'ng2-file-upload'; +import { EditProductTypePopupPage } from './edit-product-type-popup'; +import { TranslateModule, TranslateLoader } from '@ngx-translate/core'; +import { HttpClient } from '@angular/common/http'; +import { TranslateHttpLoader } from '@ngx-translate/http-loader'; +import { ProductsCategoryService } from '../../../services/products-category.service'; +import { IonicModule } from '@ionic/angular'; +import { FormsModule } from '@angular/forms'; +import { CommonModule } from '@angular/common'; +import { ProductImagesPopupModule } from '../product-pictures-popup/product-images-popup.module'; + +export function HttpLoaderFactory(http: HttpClient) { + return new TranslateHttpLoader(http, './assets/i18n/', '.json'); +} + +@NgModule({ + declarations: [EditProductTypePopupPage], + providers: [FileTransfer, ProductsCategoryService], + imports: [ + FileUploadModule, + TranslateModule.forChild({ + loader: { + provide: TranslateLoader, + useFactory: HttpLoaderFactory, + deps: [HttpClient], + }, + }), + CommonModule, + FormsModule, + IonicModule, + ProductImagesPopupModule, + ] +}) +export class EditProductTypePopupPageModule {} diff --git a/packages/merchant-tablet-ionic/src/pages/+warehouse/edit-product-type-popup/edit-product-type-popup.scss b/packages/merchant-tablet-ionic/src/pages/+warehouse/edit-product-type-popup/edit-product-type-popup.scss new file mode 100644 index 0000000..3e79707 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+warehouse/edit-product-type-popup/edit-product-type-popup.scss @@ -0,0 +1,120 @@ +.edit-product-type-popup { + #fileInput { + visibility: hidden; + } + .popup-input { + display: block; + margin-top: 1vh !important; + width: 100%; + } + .upload-text, + .upload-icon { + &:hover { + cursor: pointer; + } + } + + .no-upload-input { + padding-top: 30px; + } + + .dragDrop { + position: absolute; + top: 15px; + width: 90%; + margin: 10 auto; + } + + .select { + font-size: 14px; + } + + .popup-half { + .title-popup { + margin-top: 10px; + } + + label { + width: 100%; + span { + font-size: 14px; + } + } + } + + .radio-list, + .getProductType { + .item-inner { + border: 0 !important; + } + + ion-radio.radio.radio-md { + margin-right: 0 !important; + } + + .coord-box { + font-size: 14px; + } + } + + .pl-0 { + padding-left: 0 !important; + .item-inner, + .item-cover, + ion-item { + padding-left: 0 !important; + } + } + .pr-0 { + padding-right: 0 !important; + .item-inner, + .item-cover, + ion-item { + padding-right: 0 !important; + } + } + + .d-inline-block { + display: inline-block; + } + + #image-holder { + height: 78%; + border-radius: 3px; + padding-top: 12%; + padding-bottom: 13%; + margin-bottom: 6%; + + .dragDrop { + margin: auto !important; + position: relative; + } + + + div.button-bar { + button.button-assertive { + border-top-left-radius: 3px !important; + border-bottom-left-radius: 3px !important; + } + button.button-brand { + border-top-right-radius: 3px !important; + border-bottom-right-radius: 3px !important; + } + button:disabled { + cursor: default; + opacity: 0.4; + pointer-events: none; + } + } + } + + #multiple-select { + margin-top: 10px; + border: 2px solid #ddd; + height: auto !important; + border-bottom: 1px solid #ddd !important; + padding: 0; + div.item-inner { + border: none !important; + } + } +} diff --git a/packages/merchant-tablet-ionic/src/pages/+warehouse/edit-product-type-popup/edit-product-type-popup.ts b/packages/merchant-tablet-ionic/src/pages/+warehouse/edit-product-type-popup/edit-product-type-popup.ts new file mode 100644 index 0000000..88d8f27 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+warehouse/edit-product-type-popup/edit-product-type-popup.ts @@ -0,0 +1,488 @@ +import { + Component, + ViewChild, + ElementRef, + OnInit, + Input, + AfterViewInit, +} from '@angular/core'; +import { FileUploader, FileUploaderOptions, FileItem } from 'ng2-file-upload'; + +import { Camera, CameraOptions } from '@ionic-native/camera/ngx'; +import WarehouseProduct from '@modules/server.common/entities/WarehouseProduct'; +import { + IProductDescription, + IProductTitle, + IProductImage, +} from '@modules/server.common/interfaces/IProduct'; +import { ProductRouter } from '@modules/client.common.angular2/routers/product-router.service'; +import Product from '@modules/server.common/entities/Product'; +import { WarehouseProductsRouter } from '@modules/client.common.angular2/routers/warehouse-products-router.service'; +import { ProductLocalesService } from '@modules/client.common.angular2/locale/product-locales.service'; +import { TranslateService } from '@ngx-translate/core'; +import { ILocaleMember } from '@modules/server.common/interfaces/ILocale'; +import { environment } from '../../../environments/environment'; +import ProductsCategory from '@modules/server.common/entities/ProductsCategory'; +import { first } from 'rxjs/operators'; +import { ProductsCategoryService } from '../../../services/products-category.service'; +import DeliveryType from '@modules/server.common/enums/DeliveryType'; +import { ModalController, ActionSheetController } from '@ionic/angular'; +import { ProductImagesPopup } from '../product-pictures-popup/product-images-popup.component'; + +@Component({ + selector: 'page-edit-product-type-popup', + templateUrl: 'edit-product-type-popup.html', + styleUrls: ['./edit-product-type-popup.scss'], +}) +export class EditProductTypePopupPage implements OnInit, AfterViewInit { + OK: string = 'OK'; + CANCEL: string = 'CANCEL'; + SELECT_CATEGORIES: string = 'SELECT_CATEGORIES'; + PREFIX: string = 'WAREHOUSE_VIEW.SELECT_POP_UP.'; + selectOptionsObj: object; + takaProductDelivery: boolean = true; + takaProductTakeaway: boolean; + isAvailable: boolean; + + @Input() + warehouseProduct: WarehouseProduct; + product: Product; + readyToUpdate: boolean = false; + uploader: FileUploader; + translLang: string; + productsCategories: ProductsCategory[]; + selectedProductCategories: string[] = []; + hasImage: boolean = true; + + private lastProductTitle: IProductTitle[]; + private lastProductDescription: IProductDescription[]; + private lastProductPrice: number; + private lastProductCount: number; + private imagesData: IProductImage[]; + + @ViewChild('fileInput', { static: true }) + private fileInput: ElementRef; + + constructor( + // public navParams: NavParams, + private warehouseProductRouter: WarehouseProductsRouter, + private productRouter: ProductRouter, + private warehouseProductsRouter: WarehouseProductsRouter, + private readonly _productsCategorySrvice: ProductsCategoryService, + public modalController: ModalController, + private camera: Camera, + public actionSheetCtrl: ActionSheetController, + public readonly localeTranslateService: ProductLocalesService, + private translate: TranslateService + ) { + const uploaderOptions: FileUploaderOptions = { + url: environment.API_FILE_UPLOAD_URL, + + // Use xhrTransport in favor of iframeTransport + isHTML5: true, + // Calculate progress independently for each uploaded file + removeAfterUpload: true, + // XHR request headers + headers: [ + { + name: 'X-Requested-With', + value: 'XMLHttpRequest', + }, + ], + }; + this.uploader = new FileUploader(uploaderOptions); + + this.uploader.onBuildItemForm = ( + fileItem: any, + form: FormData + ): any => { + // Add Cloudinary's unsigned upload preset to the upload form + form.append('upload_preset', 'everbie-products-images'); + // Add built-in and custom tags for displaying the uploaded photo in the list + let tags = 'myphotoalbum'; + if (this.product.title) { + form.append('context', `photo=${this.product.title}`); + tags = `myphotoalbum,${this.product.title}`; + } + // Upload to a custom folder + // Note that by default, when uploading via the API, folders are not automatically created in your Media Library. + // In order to automatically create the folders based on the API requests, + // please go to your account upload settings and set the 'Auto-create folders' option to enabled. + // TODO: use settings from .env file + form.append('folder', 'angular_sample'); + // Add custom tags + form.append('tags', tags); + // Add file to upload + form.append('file', fileItem); + + // Use default "withCredentials" value for CORS requests + fileItem.withCredentials = false; + return { fileItem, form }; + }; + } + + @ViewChild('imageHolder', { static: true }) + private _imageHolder: ElementRef; + + imageUrlChanged(ev) { + const reader = new FileReader(); + + reader.addEventListener('load', (e) => { + const imageBase64 = e.target['result']; + this.hasImage = true; + this._setImageHolderBackground(imageBase64); + }); + + reader.readAsDataURL(ev.target.files[0]); + } + + get buttonOK() { + return this._translate(this.PREFIX + this.OK); + } + + get buttonCancel() { + return this._translate(this.PREFIX + this.CANCEL); + } + + get selectOptionTitle() { + const title = this._translate(this.PREFIX + this.SELECT_CATEGORIES); + this.selectOptionsObj = { subTitle: title }; + return this.selectOptionsObj; + } + + get isReadyToUpdate() { + return ( + this.localeTranslateService.isServiceStateValid && + this.warehouseProduct.price !== null && + this.warehouseProduct.count !== null && + this.warehouseProduct.price !== 0 && + this.warehouseProduct.count >= 0 + ); + } + + get warehouseId() { + return localStorage.getItem('_warehouseId'); + } + + get isBrowser() { + return localStorage.getItem('_platform') === 'browser'; + } + + get currentLocale() { + return this.localeTranslateService.currentLocale; + } + + set currentLocale(locale: string) { + this.localeTranslateService.currentLocale = locale; + } + + get productTitle() { + return this.localeTranslateService.getMemberValue(this.product.title); + } + + set productTitle(memberValue: string) { + this.localeTranslateService.setMemberValue('title', memberValue); + } + + get productDescription() { + return this.localeTranslateService.getMemberValue( + this.product.description + ); + } + + set productDescription(memberValue: string) { + this.localeTranslateService.setMemberValue('description', memberValue); + } + + async ngOnInit() { + this.isAvailable = this.warehouseProduct.isProductAvailable; + this.product = this.warehouseProduct.product as Product; + this.lastProductCount = this.warehouseProduct.count; + this.lastProductPrice = this.warehouseProduct.price; + this.lastProductDescription = this.product.description; + this.lastProductTitle = this.product.title; + this.translLang = this.translate.currentLang; + this.takaProductDelivery = this.warehouseProduct.isDeliveryRequired; + this.takaProductTakeaway = this.warehouseProduct.isTakeaway; + + this.currentLocale = + this.localeTranslateService.takeSelectedLang(this.translLang) || + 'en-US'; + + this._setupLocaleServiceValidationState(); + + this._selectExistingProductCategories(); + await this._loadProductsCategories(); + } + + ngAfterViewInit(): void { + const currentProductImage = this.localeTranslateService.getTranslate( + this.product.images + ); + + if (currentProductImage) { + this.hasImage = true; + } else { + this.hasImage = false; + } + + this._setImageHolderBackground(currentProductImage); + } + + getProductTypeChange(type: string) { + if (DeliveryType[type] === DeliveryType.Delivery) { + if (!this.takaProductDelivery && !this.takaProductTakeaway) { + this.takaProductTakeaway = true; + } + } else { + if (!this.takaProductDelivery && !this.takaProductTakeaway) { + this.takaProductDelivery = true; + } + } + } + + localeTranslate(member: ILocaleMember[]): string { + return this.localeTranslateService.getTranslate(member); + } + + async showPicturesPopup() { + let images = this.product.images.filter( + (i) => i.locale === this.currentLocale + ); + + if (this.imagesData) { + const imagesDataLocale = this.imagesData[0].locale; + if (imagesDataLocale === this.currentLocale) { + images = this.imagesData; + } + } + + const modal = await this.modalController.create({ + component: ProductImagesPopup, + componentProps: { + images, + }, + backdropDismiss: false, + cssClass: 'mutation-product-images-modal', + }); + + await modal.present(); + + const res = await modal.onDidDismiss(); + const imageArray = res.data; + if (imageArray && imageArray.length > 0) { + const firstImgUrl = imageArray[0].url; + this._setImageHolderBackground(firstImgUrl); + this.imagesData = imageArray; + } + } + + takePicture(sourceType: number) { + const options: CameraOptions = { + quality: 50, + destinationType: this.camera.DestinationType.DATA_URL, + encodingType: this.camera.EncodingType.JPEG, + mediaType: this.camera.MediaType.PICTURE, + correctOrientation: true, + sourceType, + }; + + this.camera.getPicture(options).then(async (imageData) => { + const base64Image = 'data:image/jpeg;base64,' + imageData; + const file = await this.urltoFile( + base64Image, + this.createFileName(), + 'image/jpeg' + ); + const fileItem = new FileItem(this.uploader, file, {}); + this.uploader.queue.push(fileItem); + }); + } + + urltoFile(url, filename, mimeType) { + return fetch(url) + .then(function (res) { + return res.arrayBuffer(); + }) + .then(function (buf) { + return new File([buf], filename, { type: mimeType }); + }); + } + + async presentActionSheet() { + const actionSheet = await this.actionSheetCtrl.create({ + header: 'Select Image Source', + buttons: [ + { + text: 'Load from Library', + handler: () => { + this.takePicture( + this.camera.PictureSourceType.PHOTOLIBRARY + ); + }, + }, + { + text: 'Use Camera', + handler: () => { + this.takePicture(this.camera.PictureSourceType.CAMERA); + }, + }, + { text: 'Cancel', role: 'cancel' }, + ], + }); + await actionSheet.present(); + } + + cancelModal() { + this.warehouseProduct.count = this.lastProductCount; + this.warehouseProduct.price = this.lastProductPrice; + this.product.description = this.lastProductDescription; + this.product.title = this.lastProductTitle; + this.modalController.dismiss(); + } + + updateProduct() { + if (this.uploader.queue.length >= 1) { + this.uploader.queue[this.uploader.queue.length - 1].upload(); + + this.uploader.response.subscribe((res) => { + res = JSON.parse(res); + + const locale = this.currentLocale; + const width = res.width; + const height = res.height; + const orientation = + width !== height ? (width > height ? 2 : 1) : 0; + const url = res.url; + + const newImage = { + locale, + url, + width, + height, + orientation, + }; + + if (this.product.images.length > 0) { + this.product.images.forEach((img, index) => { + if (img.locale === locale) { + this.product.images[index] = newImage; + } + }); + } else { + this.product.images.push(newImage); + } + + this.uploadProduct(); + }); + } else { + this.uploadProduct(); + } + } + + uploadProduct() { + if (this.imagesData && this.imagesData.length > 0) { + // Because all images in "imgLocale" has same local value we get first one + const imgLocale = this.imagesData[0].locale; + if (imgLocale === this.currentLocale) { + this.product.images = this.product.images.filter( + (i) => i.locale !== imgLocale + ); + + this.product.images.push(...this.imagesData); + } + } + + this.localeTranslateService.assignPropertyValue( + this.product.title, + 'title' + ); + this.localeTranslateService.assignPropertyValue( + this.product.description, + 'description' + ); + + this.product.categories = this.productsCategories + .filter( + (category) => + this.selectedProductCategories && + this.selectedProductCategories.some( + (categoryId) => categoryId === category.id + ) + ) + .map((category) => { + return { + _id: category.id, + _createdAt: null, + _updatedAt: null, + name: category.name, + }; + }); + + this.productRouter.save(this.product).then((product: Product) => { + this.product = product; + this.warehouseProduct.product = product; + this.warehouseProduct.isDeliveryRequired = this.takaProductDelivery; + this.warehouseProduct.isTakeaway = this.takaProductTakeaway; + this.warehouseProduct.isProductAvailable = this.isAvailable; + + this.warehouseProductsRouter + .saveUpdated(this.warehouseId, this.warehouseProduct) + .then((warehouse) => { + this.modalController.dismiss(); + }); + }); + } + + private _setImageHolderBackground(imageUrl: string) { + const gradient = `linear-gradient(rgba(0, 0, 0, 0.3), rgba(0, 0, 0, 0.3)), url(${imageUrl})`; + this._imageHolder.nativeElement.style.background = gradient; + this._imageHolder.nativeElement.style.backgroundSize = `cover`; + this._imageHolder.nativeElement.style.backgroundRepeat = 'no-repeat'; + this._imageHolder.nativeElement.style.color = `white`; + } + + private _translate(key: string): string { + let translationResult = ''; + + this.translate.get(key).subscribe((res) => { + translationResult = res; + }); + + return translationResult; + } + + private _selectExistingProductCategories() { + this.selectedProductCategories = + this.product.categories.map((category) => `${category}`) || []; + } + + private _setupLocaleServiceValidationState() { + this.localeTranslateService.setMemberValue('title', this.productTitle); + this.localeTranslateService.setMemberValue( + 'description', + this.productDescription + ); + } + + private async _loadProductsCategories() { + this.productsCategories = await this._productsCategorySrvice + .getCategories() + .pipe(first()) + .toPromise(); + } + + private createFileName() { + const newFileName = new Date().getTime() + '.jpg'; + return newFileName; + } + + async clickHandler() { + this.isAvailable != this.isAvailable; + await this.warehouseProductRouter.changeProductAvailability( + this.warehouseId, + this.warehouseProduct.productId, + this.isAvailable + ); + } +} diff --git a/packages/merchant-tablet-ionic/src/pages/+warehouse/product-pictures-popup/product-images-popu.component.scss b/packages/merchant-tablet-ionic/src/pages/+warehouse/product-pictures-popup/product-images-popu.component.scss new file mode 100644 index 0000000..7d611cd --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+warehouse/product-pictures-popup/product-images-popu.component.scss @@ -0,0 +1,26 @@ +.product-images-container { + e-cu-file-uploader { + width: 100%; + } + + .preview-img { + img { + height: 120px; + } + } + + .removeIcon { + ion-icon { + font-size: 18px; + } + } + + .actions { + position: absolute; + bottom: 0; + ion-button { + border: none; + font-size: 14px !important; + } + } +} diff --git a/packages/merchant-tablet-ionic/src/pages/+warehouse/product-pictures-popup/product-images-popup.component.html b/packages/merchant-tablet-ionic/src/pages/+warehouse/product-pictures-popup/product-images-popup.component.html new file mode 100644 index 0000000..f8cc408 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+warehouse/product-pictures-popup/product-images-popup.component.html @@ -0,0 +1,50 @@ +

+ {{ + 'WAREHOUSE_VIEW.EDIT_PICTURE.PRODUCT_IMAGES' | translate + }} + +

+ +
+ + + + +
+
+
+ Invalid image +
+ +
+ +
+
+
+
+ + {{ + 'WAREHOUSE_VIEW.EDIT_PICTURE.SAVE_IMAGES' | translate + }} + +
+
diff --git a/packages/merchant-tablet-ionic/src/pages/+warehouse/product-pictures-popup/product-images-popup.component.ts b/packages/merchant-tablet-ionic/src/pages/+warehouse/product-pictures-popup/product-images-popup.component.ts new file mode 100644 index 0000000..3f81c41 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+warehouse/product-pictures-popup/product-images-popup.component.ts @@ -0,0 +1,48 @@ +import { Component, OnInit, Input } from '@angular/core'; +import { ModalController } from '@ionic/angular'; +import { IProductImage } from '@modules/server.common/interfaces/IProduct'; + +@Component({ + selector: 'product-images-popup', + templateUrl: 'product-images-popup.component.html', + styleUrls: ['./product-images-popu.component.scss'], +}) +export class ProductImagesPopup { + @Input() + images: IProductImage[] = []; + + constructor(private modalCtrl: ModalController) {} + + get imagesUrls() { + return this.images ? this.images.map((i) => i.url).join(' ') : ''; + } + + get imagesArr() { + if (this.imagesUrls) { + const imagesStr = this.imagesUrls; + + let imageUrls = imagesStr.split(/\s+/); + imageUrls = imageUrls.filter((a) => a.trim() !== ''); + + return imageUrls; + } + + return []; + } + + deleteImg(imageUrl) { + this.images = this.images.filter((i) => i.url !== imageUrl); + } + + addImageObj(imgData: IProductImage) { + this.images.push(imgData); + } + + saveImages() { + this.modalCtrl.dismiss(this.images); + } + + cancelModal() { + this.modalCtrl.dismiss(); + } +} diff --git a/packages/merchant-tablet-ionic/src/pages/+warehouse/product-pictures-popup/product-images-popup.module.ts b/packages/merchant-tablet-ionic/src/pages/+warehouse/product-pictures-popup/product-images-popup.module.ts new file mode 100644 index 0000000..7923fd9 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+warehouse/product-pictures-popup/product-images-popup.module.ts @@ -0,0 +1,20 @@ +import { NgModule } from '@angular/core'; +import { ProductImagesPopup } from './product-images-popup.component'; +import { TranslateModule } from '@ngx-translate/core'; +import { CommonModule } from '@angular/common'; +import { FormsModule } from '@angular/forms'; +import { IonicModule } from '@ionic/angular'; +import { FileUploaderModule } from 'components/file-uploader/file-uploader.module'; + +@NgModule({ + declarations: [ProductImagesPopup], + providers: [], + imports: [ + FileUploaderModule, + TranslateModule.forChild(), + CommonModule, + FormsModule, + IonicModule, + ] +}) +export class ProductImagesPopupModule {} diff --git a/packages/merchant-tablet-ionic/src/pages/+warehouse/relevant-oders/relevant-orders.component.html b/packages/merchant-tablet-ionic/src/pages/+warehouse/relevant-oders/relevant-orders.component.html new file mode 100644 index 0000000..6cb33dd --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+warehouse/relevant-oders/relevant-orders.component.html @@ -0,0 +1,72 @@ + + + + +
+
+ + + + + + + + + + + + + + + + + +
+ + + + + +
diff --git a/packages/merchant-tablet-ionic/src/pages/+warehouse/relevant-oders/relevant-orders.component.scss b/packages/merchant-tablet-ionic/src/pages/+warehouse/relevant-oders/relevant-orders.component.scss new file mode 100644 index 0000000..3fed22e --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+warehouse/relevant-oders/relevant-orders.component.scss @@ -0,0 +1,11 @@ +ion-spinner { + width: 40px !important; + height: 40px !important; + left: calc(50% - 20px); + position: absolute; + zoom: 2; +} + +.orders-spinner { + top: 40%; +} diff --git a/packages/merchant-tablet-ionic/src/pages/+warehouse/relevant-oders/relevant-orders.component.ts b/packages/merchant-tablet-ionic/src/pages/+warehouse/relevant-oders/relevant-orders.component.ts new file mode 100644 index 0000000..7e34cf9 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+warehouse/relevant-oders/relevant-orders.component.ts @@ -0,0 +1,139 @@ +import { + Component, + Input, + AfterViewInit, + OnDestroy, + OnChanges, + Output, + EventEmitter, +} from '@angular/core'; +import { WarehouseOrdersService } from '../../../services/warehouse-orders.service'; +import { Store } from '../../../services/store.service'; +import Order from '@modules/server.common/entities/Order'; +import { OrderState } from '../warehouse'; +import { Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; + +const showOrdersNumber: number = 10; +@Component({ + selector: 'merchant-relevant-orders', + templateUrl: 'relevant-orders.component.html', + styleUrls: ['./relevant-orders.component.scss'], +}) +export class RelevantOrdersComponent + implements AfterViewInit, OnDestroy, OnChanges, OnDestroy { + @Input() + getWarehouseStatus: (status: any) => void; + + @Input() + onUpdateWarehouseStatus: any; + + @Input() + orderState: (Order) => void; + + @Input() + focusedOrder: Order; + + @Input() + isOrderContainerLive: boolean; + + @Output() + toggleOrderContainer: EventEmitter = new EventEmitter(); + + @Input() + filter: string; + + orders: Order[] = []; + ordersCount: number; + OrderState: any = OrderState; + page: number = 1; + ordersLoaded: boolean; + + private readonly ngDestroy$ = new Subject(); + private loadedPages = []; + private subscriptions: any = []; + + constructor( + private warehouseOrdersService: WarehouseOrdersService, + private store: Store + ) {} + + ngOnChanges() { + if (this.focusedOrder) { + this.orders = [this.focusedOrder]; + } else { + this.loadAllOrders(this.filter ? this.filter : 'relevant'); + } + } + + ngAfterViewInit() {} + + async loadData(event = null, status = 'relevant') { + const sub = this.warehouseOrdersService + .getStoreOrdersTableData( + this.store.warehouseId, + { + sort: { + field: '_createdAt', + sortBy: 'desc', + }, + skip: (this.page - 1) * showOrdersNumber, + limit: showOrdersNumber, + }, + status + ) + .pipe(takeUntil(this.ngDestroy$)) + .subscribe((res) => { + const orders = res['orders']; + const page = res['page']; + + if (!this.focusedOrder) { + if (this.loadedPages.includes(res['page'])) { + const start = (page - 1) * showOrdersNumber; + this.orders.splice(start, showOrdersNumber, ...orders); + } else { + this.loadedPages.push(res['page']); + this.orders.push(...orders); + + this.page++; + } + if (event) { + event.target.complete(); + } + } + this.ordersLoaded = true; + }); + + this.subscriptions.push(sub); + } + + private async loadAllOrders(status = 'relevant') { + await this.loadOrdersCount(status); + + this.orders = []; + + this.page = 1; + + for (const sub of this.subscriptions) { + await sub.unsubscribe(); + } + + this.subscriptions = []; + + this.loadedPages = []; + + this.loadData(null, status); + } + + private async loadOrdersCount(status = 'relevant') { + this.ordersCount = await this.warehouseOrdersService.getCountOfStoreOrders( + this.store.warehouseId, + status + ); + } + + ngOnDestroy() { + this.ngDestroy$.next(); + this.ngDestroy$.complete(); + } +} diff --git a/packages/merchant-tablet-ionic/src/pages/+warehouse/top-products/top-products.component.html b/packages/merchant-tablet-ionic/src/pages/+warehouse/top-products/top-products.component.html new file mode 100644 index 0000000..2753509 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+warehouse/top-products/top-products.component.html @@ -0,0 +1,58 @@ +
+
+ +
+

+ {{ + 'WAREHOUSE_VIEW.ORDER_WAREHOUSE_STATUSES.NO_PRODUCTS' + | translate + }} +

+

+ {{ 'WAREHOUSE_VIEW.MISC_TEXT.ADD_NEW_PRODUCT' | translate }} +

+
+
+ + + +
+
+ +
+ +
+ + + + {{ + truncateTitle( + localeTranslate(warehouseProduct.product.title) + ) + }} + + {{ warehouseProduct?.count }} +
+
+
+
+
diff --git a/packages/merchant-tablet-ionic/src/pages/+warehouse/top-products/top-products.component.ts b/packages/merchant-tablet-ionic/src/pages/+warehouse/top-products/top-products.component.ts new file mode 100644 index 0000000..6fc2614 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+warehouse/top-products/top-products.component.ts @@ -0,0 +1,67 @@ +import { Component, OnInit, OnDestroy, Input } from '@angular/core'; +import { Subscription } from 'rxjs'; +import { WarehouseProductsRouter } from '@modules/client.common.angular2/routers/warehouse-products-router.service'; +import WarehouseProduct from '@modules/server.common/entities/WarehouseProduct'; +import { ProductLocalesService } from '@modules/client.common.angular2/locale/product-locales.service'; +import { ModalController } from '@ionic/angular'; +import Product from '@modules/server.common/entities/Product'; + +@Component({ + selector: 'merchant-top-products', + templateUrl: 'top-products.component.html', + styleUrls: ['../common/no-orders-info/no-orders-info.component.scss'], +}) +export class TopProductsComponent implements OnInit, OnDestroy { + @Input() + warehouseId: string; + + @Input() + presentCreateProductPopover: () => void; + + @Input() + addProduct: (id: string) => void; + + @Input() + getWarehouseProductImageUrl: (product: Product) => void; + + @Input() + openEditProductModal: (warehouseProduct: WarehouseProduct) => void; + + @Input() + truncateTitle: (title: any) => void; + + @Input() + localeTranslate: (title: any) => void; + + topProducts$: Subscription; + topProducts: WarehouseProduct[] = []; + + public showNoProductsIcon: boolean = false; + + constructor( + private warehouseProductsRouter: WarehouseProductsRouter, + private modalCtrl: ModalController, + private translateProductLocales: ProductLocalesService + ) {} + + ngOnInit() { + if (this.topProducts$) { + this.topProducts$.unsubscribe(); + } + + this.topProducts$ = this.warehouseProductsRouter + .getTopProducts(this.warehouseId, 20) + .subscribe((products) => { + products.length === 0 + ? (this.showNoProductsIcon = true) + : (this.showNoProductsIcon = false); + this.topProducts = products; + }); + } + + ngOnDestroy() { + if (this.topProducts$) { + this.topProducts$.unsubscribe(); + } + } +} diff --git a/packages/merchant-tablet-ionic/src/pages/+warehouse/warehouse.html b/packages/merchant-tablet-ionic/src/pages/+warehouse/warehouse.html new file mode 100644 index 0000000..e8a7505 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+warehouse/warehouse.html @@ -0,0 +1,148 @@ + + + + +
+
+
+ + + {{ 'WAREHOUSE_VIEW.PRODUCTS' | translate }} + + +
+
+ + +
+
+
diff --git a/packages/merchant-tablet-ionic/src/pages/+warehouse/warehouse.module.guard.ts b/packages/merchant-tablet-ionic/src/pages/+warehouse/warehouse.module.guard.ts new file mode 100644 index 0000000..1dd8c1b --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+warehouse/warehouse.module.guard.ts @@ -0,0 +1,20 @@ +import { Injectable } from '@angular/core'; +import { CanLoad, Route, Router } from '@angular/router'; +import { Store } from '../../services/store.service'; + +@Injectable() +export class WarehouseModuleGuard implements CanLoad { + constructor( + private readonly store: Store, + private readonly router: Router + ) {} + + async canLoad(route: Route) { + const isLogged = await this.store.isLogged(); + if (!isLogged) { + this.router.navigateByUrl('/login'); + return false; + } + return true; + } +} diff --git a/packages/merchant-tablet-ionic/src/pages/+warehouse/warehouse.module.ts b/packages/merchant-tablet-ionic/src/pages/+warehouse/warehouse.module.ts new file mode 100644 index 0000000..0b0a2a5 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+warehouse/warehouse.module.ts @@ -0,0 +1,72 @@ +import { NgModule } from '@angular/core'; +import { WarehousePage } from './warehouse'; +import { ComponentsModule } from '../../components/components.module'; +import { TranslateModule, TranslateLoader } from '@ngx-translate/core'; +import { HttpClient } from '@angular/common/http'; +import { TranslateHttpLoader } from '@ngx-translate/http-loader'; +import { OrderModule } from '../../components/order/order.module'; +import { NgxPaginationModule } from 'ngx-pagination'; +import { AllOrdersComponent } from './all-oders/all-orders.component'; +import { WarehouseOrdersService } from '../../services/warehouse-orders.service'; +import { Store } from '../../services/store.service'; +import { RelevantOrdersComponent } from './relevant-oders/relevant-orders.component'; +import { WarehouseCommonModule } from './common/warehouse.common.module'; +import { CommonModule } from '@angular/common'; +import { AllProductsComponent } from './all-products/all-products.component'; +import { TopProductsComponent } from './top-products/top-products.component'; +import { WarehouseProductsService } from '../../services/warehouse-products.service'; +import { NgxMasonryModule } from 'ngx-masonry'; +import { Routes, RouterModule } from '@angular/router'; +import { IonicModule } from '@ionic/angular'; +import { CreateProductTypePopupPageModule } from './create-product-type-popup/create-product-type-popup.module'; +import { EditProductTypePopupPageModule } from './edit-product-type-popup/edit-product-type-popup.module'; +import { WarehousesService } from 'services/warehouses.service'; +import { OrderStatusFilterPipe } from './warehouse.pipe'; + +export function HttpLoaderFactory(http: HttpClient) { + return new TranslateHttpLoader(http, './assets/i18n/', '.json'); +} + +const routes: Routes = [ + { + path: '', + component: WarehousePage, + }, +]; + +@NgModule({ + declarations: [ + WarehousePage, + AllOrdersComponent, + RelevantOrdersComponent, + AllProductsComponent, + TopProductsComponent, + OrderStatusFilterPipe, + ], + imports: [ + ComponentsModule, + OrderModule, + CommonModule, + IonicModule, + RouterModule.forChild(routes), + TranslateModule.forChild({ + loader: { + provide: TranslateLoader, + useFactory: HttpLoaderFactory, + deps: [HttpClient], + }, + }), + NgxPaginationModule, + WarehouseCommonModule, + NgxMasonryModule, + CreateProductTypePopupPageModule, + EditProductTypePopupPageModule, + ], + providers: [ + WarehouseOrdersService, + Store, + WarehouseProductsService, + WarehousesService, + ], +}) +export class WarehousePageModule {} diff --git a/packages/merchant-tablet-ionic/src/pages/+warehouse/warehouse.pipe.ts b/packages/merchant-tablet-ionic/src/pages/+warehouse/warehouse.pipe.ts new file mode 100644 index 0000000..7eec06f --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+warehouse/warehouse.pipe.ts @@ -0,0 +1,16 @@ +import { Pipe, PipeTransform } from '@angular/core'; + +@Pipe({ + name: 'skipStatus', + pure: false, +}) +export class OrderStatusFilterPipe implements PipeTransform { + transform(items: [], state?: Boolean): any { + if (!state) { + return items; + } + + let restOf = items; + return items.splice(0, 1).concat(restOf.splice(3)); + } +} diff --git a/packages/merchant-tablet-ionic/src/pages/+warehouse/warehouse.scss b/packages/merchant-tablet-ionic/src/pages/+warehouse/warehouse.scss new file mode 100644 index 0000000..2bec5c8 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+warehouse/warehouse.scss @@ -0,0 +1,99 @@ +.warehouse-view { + .redbar { + padding: 0 !important; + + ion-icon { + font-size: 32px; + } + + div { + .text { + font-size: 1.2em; + } + .all { + font-size: 1em; + text-transform: uppercase; + min-height: 43px !important; + border: none; + } + + display: flex; + align-items: center; + justify-content: space-evenly; + width: 100%; + height: 100%; + } + } + + .products-spinner { + top: calc(50% - 30px); + } + + .warehouse-view { + height: 100%; + + .orders { + min-height: 100%; + background-color: #f3f3f3; + order.card { + border: 0; + box-shadow: none; + } + } + } + + ion-row.warehouse-view { + padding: 0px; + } + + div.scroll-content { + overflow-y: hidden; + } + + a.button { + font-size: 0.8em; + } + .icon.button-icon.plus-navbar-button { + color: white; + } + + .allowOverflow { + a { + overflow: unset !important; + padding: 0 !important; + } + ion-select { + overflow: unset !important; + padding: 0 !important; + } + ion-select::part(placeholder), + ion-select::part(text) { + opacity: 1; + font-size: 13px; + } + ion-select::part(icon) { + opacity: 1; + } + } + + .order-header { + height: 50px; + } + + .button-bar-right-container { + width: 530px !important; + } +} + +.order-timer-container { + .order-header { + min-height: 50px; + height: auto; + } +} +// This apply for create and edit product type category +// header title of select category option +[id^='alert-subhdr'] { + text-align: center; + font-size: 17px; +} diff --git a/packages/merchant-tablet-ionic/src/pages/+warehouse/warehouse.ts b/packages/merchant-tablet-ionic/src/pages/+warehouse/warehouse.ts new file mode 100644 index 0000000..41dab57 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/+warehouse/warehouse.ts @@ -0,0 +1,290 @@ +import { Component, OnInit } from '@angular/core'; +import { Mixpanel } from '@ionic-native/mixpanel/ngx'; +import Order from '@modules/server.common/entities/Order'; +import Warehouse from '@modules/server.common/entities/Warehouse'; +import OrderCarrierStatus from '@modules/server.common/enums/OrderCarrierStatus'; +import OrderWarehouseStatus from '@modules/server.common/enums/OrderWarehouseStatus'; +import { OrderRouter } from '@modules/client.common.angular2/routers/order-router.service'; +import { WarehouseProductsRouter } from '@modules/client.common.angular2/routers/warehouse-products-router.service'; +import { ProductLocalesService } from '@modules/client.common.angular2/locale/product-locales.service'; +import { OrdersFilterModes } from '../../filters/orders-filters'; +import * as _ from 'lodash'; +import { ILocaleMember } from '@modules/server.common/interfaces/ILocale'; +import { Store } from '../../../src/services/store.service'; +import { BarcodeScanner } from '@ionic-native/barcode-scanner/ngx'; +import Product from '@modules/server.common/entities/Product'; +import { PopoverController, ModalController } from '@ionic/angular'; +import { CreateProductTypePopupPage } from './create-product-type-popup/create-product-type-popup'; +import { EditProductTypePopupPage } from './edit-product-type-popup/edit-product-type-popup'; +import { WarehousesService } from 'services/warehouses.service'; + +export enum OrderState { + WarehousePreparation, + WarehousePreparationProblem, + InDelivery, + Canceled, + DeliveryProblem, + Delivered, +} + +export enum OrderStatus { + 'all' = 'ALL', + 'confirmed' = 'CONFIRMED', + 'processing' = 'PROCESSING', + 'alocation_started' = 'ALLOCATION_STARTED', + 'packaging' = 'PACKAGING_STARTED', + 'packaged' = 'PACKAGED', + 'in_delivery' = 'GIVEN_TO_CARRIER', +} + +@Component({ + selector: 'page-warehouse', + templateUrl: './warehouse.html', + styleUrls: ['./warehouse.scss'], +}) +export class WarehousePage implements OnInit { + private warehouse$: any; + filterMode: OrdersFilterModes = 'ready'; + warehouse: Warehouse; + OrderState: any = OrderState; + isOrderContainerLive: boolean = false; + productsLoading: boolean = true; + ordersCount: number; + showRelevant: boolean = true; + showAllProducts: boolean = false; + focusedOrder: Order; + focusedOrder$: any; + orderStatus: boolean; + + filter: any; //todo + keys: any = Object.keys; + statuses = OrderStatus; + + constructor( + // public navCtrl: NavController, + // public navParams: NavParams, + public popoverCtrl: PopoverController, + public modalCtrl: ModalController, + private orderRouter: OrderRouter, + private warehouseProductsRouter: WarehouseProductsRouter, + private mixpanel: Mixpanel, + private translateProductLocales: ProductLocalesService, + private store: Store, + private barcodeScanner: BarcodeScanner, + private warehouseService: WarehousesService + ) {} + + // ionViewDidLoad() { + // if (!this.isLogged) { + // this.navCtrl.setRoot('LoginPage'); + // } + // } + // TODO + // async ionViewCanEnter() { + // const isLogged = await this.store.isLogged(); + // return this.store.maintenanceMode === null && isLogged; + // } + + ngOnInit() { + this.getOrderShortProcess(); + } + + get isLogged() { + return localStorage.getItem('_warehouseId'); + } + + get warehouseId() { + return localStorage.getItem('_warehouseId'); + } + + get language() { + return localStorage.getItem('_language'); + } + + async scanBarcode() { + try { + const barcodeData = await this.barcodeScanner.scan(); + const orderId = barcodeData.text; + if (orderId !== '') { + this.focusedOrder$ = this.orderRouter + .get(orderId, { + populateCarrier: true, + populateWarehouse: true, + }) + .subscribe((order) => { + if ( + order.warehouseStatus >= + OrderWarehouseStatus.GivenToCarrier + ) { + this.switchOrders(this.showRelevant); + } else { + this.focusedOrder = order; + } + }); + } + } catch (error) { + console.warn(error); + } + } + + switchOrders(showRelevant, event?) { + if (this.focusedOrder$) { + this.focusedOrder$.unsubscribe(); + } + this.focusedOrder = null; + this.showRelevant = showRelevant; + + if (event != null) { + this.filter = event.target.value; + } else { + this.filter = null; + } + } + + onOrderFinish() { + this.toggleOrderContainer(); + } + + toggleOrderContainer() { + this.isOrderContainerLive = !this.isOrderContainerLive; + } + + getWarehouseProductImageUrl(p: Product): string | void { + if (p instanceof Product) { + const productImg = p.images.filter((i) => + i.locale.includes(this.language) + )[0]; + if (productImg) { + return productImg.url; + } + return p.images[0].url; + } + } + + truncateTitle(title: string) { + if (title) { + title = title.replace(/[ ]{2,}/, ' '); + if (title.length <= 15) { + return title; + } + return title.substring(0, 12) + '...'; + } + } + + localeTranslate(member: ILocaleMember[]): string { + if (member !== undefined) { + return this.translateProductLocales.getTranslate(member); + } + } + + orderState(order: Order) { + if (order.warehouseStatus >= 200) { + return OrderState.WarehousePreparationProblem; + } + + if (order.carrierStatus >= 200) { + return OrderState.DeliveryProblem; + } + + if (order.isCancelled) { + return OrderState.Canceled; + } + + if (order.carrierStatus === OrderCarrierStatus.DeliveryCompleted) { + return OrderState.Delivered; + } + + if (order.carrier == null) { + return OrderState.WarehousePreparation; + } else { + return OrderState.InDelivery; + } + } + + updateOrderWarehouseStatus(orderId: string, status: OrderWarehouseStatus) { + if (status >= 200) { + if (this.mixpanel) { + this.mixpanel.track('Order Failed'); + } + } + return this.orderRouter.updateWarehouseStatus(orderId, status); + } + + addProduct(productId: string) { + return this.warehouseProductsRouter.increaseCount( + this.warehouseId, + productId, + 1 + ); + } + + async presentCreateProductPopover() { + const modal = await this.modalCtrl.create({ + component: CreateProductTypePopupPage, + // backdropDismiss: false, + cssClass: 'mutation-product-modal', + }); + + await modal.present(); + } + + async openEditProductModal(product) { + const modal = await this.modalCtrl.create({ + component: EditProductTypePopupPage, + // backdropDismiss: false, + componentProps: { warehouseProduct: product }, + cssClass: 'mutation-product-modal', + }); + + await modal.present(); + } + + async getOrderShortProcess() { + this.orderStatus = await this.warehouseService + .getWarehouseOrderProcess(this.store.warehouseId) + .toPromise(); + + this.orderStatus = this.orderStatus['ordersShortProcess']; + + return this.orderStatus; + } + + getWarehouseStatus(orderWarehouseStatusNumber: OrderWarehouseStatus): string { + const basePath = 'WAREHOUSE_VIEW.ORDER_WAREHOUSE_STATUSES.'; + switch (orderWarehouseStatusNumber) { + case OrderWarehouseStatus.NoStatus: + return basePath + 'CREATED'; + case OrderWarehouseStatus.ReadyForProcessing: + return basePath + 'CONFIRMED'; + case OrderWarehouseStatus.WarehouseStartedProcessing: + return basePath + 'PROCESSING'; + case OrderWarehouseStatus.AllocationStarted: + return basePath + 'ALLOCATION_STARTED'; + case OrderWarehouseStatus.AllocationFinished: + return basePath + 'ALLOCATION_FINISHED'; + case OrderWarehouseStatus.PackagingStarted: + return basePath + 'PACKAGING_STARTED'; + case OrderWarehouseStatus.PackagingFinished: + return basePath + 'PACKAGED'; + case OrderWarehouseStatus.GivenToCarrier: + return basePath + 'GIVEN_TO_CARRIER'; + case OrderWarehouseStatus.GivenToCustomer: + return basePath + 'TAKEN'; + case OrderWarehouseStatus.AllocationFailed: + return basePath + 'ALLOCATION_FAILED'; + case OrderWarehouseStatus.PackagingFailed: + return basePath + 'PACKAGING_FAILED'; + default: + return basePath + 'BAD_STATUS'; + } + } + + ionViewWillLeave() { + if (this.warehouse$) { + this.warehouse$.unsubscribe(); + } + if (this.focusedOrder$) { + this.focusedOrder$.unsubscribe(); + } + } +} diff --git a/packages/merchant-tablet-ionic/src/pages/pages.module.guard.ts b/packages/merchant-tablet-ionic/src/pages/pages.module.guard.ts new file mode 100644 index 0000000..d0a43b1 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/pages.module.guard.ts @@ -0,0 +1,34 @@ +import { Injectable } from '@angular/core'; +import { + Router, + CanActivate, + ActivatedRouteSnapshot, + RouterStateSnapshot, +} from '@angular/router'; +import { Store } from '../services/store.service'; + +@Injectable() +export class PagesModuleGuard implements CanActivate { + constructor( + private readonly router: Router, + private readonly store: Store + ) {} + + canActivate( + route: ActivatedRouteSnapshot, + state: RouterStateSnapshot + ): boolean { + const maintenanceMode = this.store.maintenanceMode; + if (maintenanceMode) { + this.router.navigate(['maintenance-info']); + return false; + } + const serverConnection = Number(this.store.serverConnection); + + if (serverConnection === 0) { + this.router.navigate(['server-down']); + return false; + } + return true; + } +} diff --git a/packages/merchant-tablet-ionic/src/pages/pages.module.ts b/packages/merchant-tablet-ionic/src/pages/pages.module.ts new file mode 100644 index 0000000..6b83045 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/pages/pages.module.ts @@ -0,0 +1,91 @@ +import { NgModule } from '@angular/core'; +import { RouterModule, Routes } from '@angular/router'; +import { LoginModuleGuard } from './+login/login.module.guard'; +import { WarehouseModuleGuard } from './+warehouse/warehouse.module.guard'; +import { InfoModuleGuard } from './+info/info.module.guard'; + +// TODO: add all routes! +const routes: Routes = [ + { + path: 'login', + loadChildren: () => + import('./+login/login.module').then((m) => m.LoginPageModule), + canLoad: [LoginModuleGuard], + }, + { + path: 'warehouse', + loadChildren: () => + import('./+warehouse/warehouse.module').then( + (m) => m.WarehousePageModule + ), + canLoad: [WarehouseModuleGuard], + }, + { + path: 'language', + loadChildren: () => + import('./+language/language.module').then( + (m) => m.LanguagePageModule + ), + }, + { + path: 'customers', + loadChildren: () => + import('./+customers/customers.module').then( + (m) => m.CustomersPageModule + ), + }, + { + path: 'carriers', + loadChildren: () => + import('./+carriers/carriers.module').then( + (m) => m.CarrierssPageModule + ), + }, + { + path: 'track', + loadChildren: () => + import('./+track/track.module').then((m) => m.TrackPageModule), + }, + { + path: 'track/:id', + loadChildren: () => + import('./+track/track.module').then((m) => m.TrackPageModule), + }, + { + path: 'settings', + loadChildren: () => + import('./+settings/settings.module').then( + (m) => m.SettingsPageModule + ), + }, + { + path: 'info', + loadChildren: () => + import('./+info/info.module').then((m) => m.InfoModule), + canLoad: [InfoModuleGuard], + }, + { + path: 'promotions', + loadChildren: () => + import('./+promotions/promotion.module').then( + (m) => m.PromotionModule + ), + }, + { + path: 'errors', + loadChildren: () => + import('./+errors/errors.module').then((m) => m.ErrorsModule), + }, + { + path: '', + pathMatch: 'full', + redirectTo: 'login', + }, +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + providers: [LoginModuleGuard, WarehouseModuleGuard, InfoModuleGuard], + exports: [RouterModule], +}) +export class PagesModule {} diff --git a/packages/merchant-tablet-ionic/src/polyfills.ts b/packages/merchant-tablet-ionic/src/polyfills.ts new file mode 100644 index 0000000..b6fadaf --- /dev/null +++ b/packages/merchant-tablet-ionic/src/polyfills.ts @@ -0,0 +1,68 @@ +/** + * This file includes polyfills needed by Angular and is loaded before the app. + * You can add your own extra polyfills to this file. + * + * This file is divided into 2 sections: + * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. + * 2. Application imports. Files imported after ZoneJS that should be loaded before your main + * file. + * + * The current setup is for so-called "evergreen" browsers; the last versions of browsers that + * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), + * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. + * + * Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html + */ + +/*************************************************************************************************** + * BROWSER POLYFILLS + */ +(window as any).process = { + env: { DEBUG: undefined }, +}; + +(window as any).global = window; + +import '@webcomponents/webcomponentsjs/custom-elements-es5-adapter.js'; + +/** IE9, IE10 and IE11 requires all of the following polyfills. **/ +// import 'core-js/es6/symbol'; +// import 'core-js/es6/object'; +// import 'core-js/es6/function'; +// import 'core-js/es6/parse-int'; +// import 'core-js/es6/parse-float'; +// import 'core-js/es6/number'; +// import 'core-js/es6/math'; +// import 'core-js/es6/string'; +// import 'core-js/es6/date'; +// import 'core-js/es6/array'; +// import 'core-js/es6/regexp'; +// import 'core-js/es6/map'; +// import 'core-js/es6/weak-map'; +// import 'core-js/es6/set'; + +/** IE10 and IE11 requires the following for the Reflect API. */ +// import 'core-js/es6/reflect'; + +/** Evergreen browsers require these. **/ +// Used for reflect-metadata in JIT. If you use AOT (and only Angular decorators), you can remove. +import 'core-js/es7/reflect'; + +/*************************************************************************************************** + * Zone JS is required by Angular itself. + */ +import 'zone.js/dist/zone'; // Included with Angular CLI. + +/*************************************************************************************************** + * APPLICATION IMPORTS + */ + +/** + * Date, currency, decimal and percent pipes. + * Needed for: All but Chrome, Firefox, Edge, IE11 and Safari 10 + */ +// import 'intl'; // Run `npm install --save intl`. +/** + * Need to import at least one locale-data with intl. + */ +// import 'intl/locale-data/jsonp/en'; diff --git a/packages/merchant-tablet-ionic/src/services/auth.service.ts b/packages/merchant-tablet-ionic/src/services/auth.service.ts new file mode 100644 index 0000000..1b04b9d --- /dev/null +++ b/packages/merchant-tablet-ionic/src/services/auth.service.ts @@ -0,0 +1,45 @@ +import { Injectable } from '@angular/core'; +import { Apollo, gql } from 'apollo-angular'; +import Warehouse from '@modules/server.common/entities/Warehouse'; +import { map, share } from 'rxjs/operators'; +import { Observable } from 'rxjs'; + +export interface WarehouseLoginInfo { + warehouse: Warehouse; + token: string; +} + +@Injectable() +export class AuthService { + constructor(private readonly apollo: Apollo) {} + + login(username: string, password: string): Observable { + return this.apollo + .mutate<{ warehouseLogin: WarehouseLoginInfo }>({ + mutation: gql` + mutation WarehouseLogin( + $username: String! + $password: String! + ) { + warehouseLogin( + username: $username + password: $password + ) { + token + warehouse { + id + } + } + } + `, + variables: { + username, + password, + }, + }) + .pipe( + map((result) => result.data.warehouseLogin), + share() + ); + } +} diff --git a/packages/merchant-tablet-ionic/src/services/carrier.service.ts b/packages/merchant-tablet-ionic/src/services/carrier.service.ts new file mode 100644 index 0000000..26e4e03 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/services/carrier.service.ts @@ -0,0 +1,141 @@ +import { Injectable } from '@angular/core'; +import { Apollo, gql } from 'apollo-angular'; +import Carrier from '@modules/server.common/entities/Carrier'; +import { Observable } from 'rxjs'; +import { map, share } from 'rxjs/operators'; +import ICarrier from '@modules/server.common/interfaces/ICarrier'; + +@Injectable() +export class CarrierService { + constructor(private readonly _apollo: Apollo) {} + + private carriers$: Observable = this._apollo + .watchQuery<{ getCarriers: ICarrier[] }>({ + query: gql` + query getCarriers { + getCarriers { + _id + firstName + lastName + phone + logo + isDeleted + numberOfDeliveries + skippedOrderIds + status + geoLocation { + city + streetAddress + house + loc { + type + coordinates + } + } + } + } + `, + pollInterval: 1000, + }) + .valueChanges.pipe( + map((res) => res.data.getCarriers), + map((carriers) => carriers.map((c) => this._carrierFactory(c))), + share() + ); + + getAllCarriers(): Observable { + return this.carriers$; + } + + updateCarrier(id: string, updateInput: any) { + return this._apollo + .mutate<{ updateCarrier: Carrier }>({ + mutation: gql` + mutation UpdateCarrier( + $id: String! + $updateInput: CarrierUpdateInput! + ) { + updateCarrier(id: $id, updateInput: $updateInput) { + id + } + } + `, + variables: { + id, + updateInput, + }, + }) + .pipe( + map((result) => result.data.updateCarrier), + share() + ); + } + + async getCarrierCurrentOrder(carrierId: string): Promise { + const res = await this._apollo + .query({ + query: gql` + query GetCarrierCurrentOrder($carrierId: String!) { + getCarrierCurrentOrder(carrierId: $carrierId) { + id + carrierStatus + carrierStatusText + warehouseStatusText + createdAt + startDeliveryTime + status + deliveryTime + finishedProcessingTime + user { + id + phone + email + apartment + firstName + lastName + image + geoLocation { + house + postcode + notes + countryName + city + streetAddress + loc { + coordinates + type + } + } + } + warehouse { + id + name + logo + contactEmail + contactPhone + geoLocation { + house + postcode + countryName + city + streetAddress + loc { + coordinates + type + } + } + } + } + } + `, + variables: { carrierId }, + }) + .toPromise(); + + return res.data['getCarrierCurrentOrder']; + } + + protected _carrierFactory(carrier: ICarrier) { + return carrier == null ? null : new Carrier(carrier); + } +} diff --git a/packages/merchant-tablet-ionic/src/services/currencies.service.ts b/packages/merchant-tablet-ionic/src/services/currencies.service.ts new file mode 100644 index 0000000..f878a83 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/services/currencies.service.ts @@ -0,0 +1,36 @@ +import { Injectable } from '@angular/core'; +import { Apollo, gql } from 'apollo-angular'; +import { Observable } from 'rxjs'; +import Currency from '@modules/server.common/entities/Currency'; +import { map, share } from 'rxjs/operators'; + +export interface CurrencyMutationRespone { + success: boolean; + message?: string; + data?: Currency; +} + +@Injectable() +export class CurrenciesService { + constructor(private readonly apollo: Apollo) {} + + private currencies$: Observable = this.apollo + .watchQuery<{ currencies: Currency[] }>({ + query: gql` + query allCurrencies { + currencies { + currencyCode + } + } + `, + pollInterval: 2000, + }) + .valueChanges.pipe( + map((result) => result.data.currencies), + share() + ); + + getCurrencies(): Observable { + return this.currencies$; + } +} diff --git a/packages/merchant-tablet-ionic/src/services/orders.service.ts b/packages/merchant-tablet-ionic/src/services/orders.service.ts new file mode 100644 index 0000000..83b33cc --- /dev/null +++ b/packages/merchant-tablet-ionic/src/services/orders.service.ts @@ -0,0 +1,68 @@ +import { Injectable } from '@angular/core'; +import { Apollo, gql } from 'apollo-angular'; +import { map, share } from 'rxjs/operators'; +import Order from '@modules/server.common/entities/Order'; +import { Observable } from 'rxjs'; + +@Injectable() +export class OrdersService { + constructor(private readonly _apollo: Apollo) {} + + getOrderedUsersInfo(storeId: string) { + return this._apollo + .watchQuery({ + query: gql` + query GetOrderedUsersInfo($storeId: String!) { + getOrderedUsersInfo(storeId: $storeId) { + user { + _id + id + image + firstName + lastName + email + apartment + phone + geoLocation { + countryId + city + house + streetAddress + loc { + type + coordinates + } + } + } + ordersCount + totalPrice + } + } + `, + variables: { storeId }, + pollInterval: 1000, + }) + .valueChanges.pipe( + map((res) => res.data['getOrderedUsersInfo']), + share() + ); + } + getOrders(): Observable { + return this._apollo + .watchQuery<{ orders: Order[] }>({ + query: gql` + query Orders { + orders { + carrierId + isCompleted + } + } + `, + pollInterval: 4000, + }) + .valueChanges.pipe( + map((res) => res.data.orders), + share() + ); + } +} diff --git a/packages/merchant-tablet-ionic/src/services/product.service.ts b/packages/merchant-tablet-ionic/src/services/product.service.ts new file mode 100644 index 0000000..7248503 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/services/product.service.ts @@ -0,0 +1,29 @@ +import { Injectable } from '@angular/core'; +import { Apollo, gql } from 'apollo-angular'; +import { map, share } from 'rxjs/operators'; +import Product from '@modules/server.common/entities/Product'; + +@Injectable() +export class ProductService { + constructor(private readonly apollo: Apollo) {} + + save(product: Product) { + return this.apollo + .mutate<{ product: Product }>({ + mutation: gql` + mutation SaveProduct($product: ProductSaveInput!) { + saveProduct(product: $product) { + id + } + } + `, + variables: { + product, + }, + }) + .pipe( + map((result) => result.data.product), + share() + ); + } +} diff --git a/packages/merchant-tablet-ionic/src/services/products-category.service.ts b/packages/merchant-tablet-ionic/src/services/products-category.service.ts new file mode 100644 index 0000000..23babda --- /dev/null +++ b/packages/merchant-tablet-ionic/src/services/products-category.service.ts @@ -0,0 +1,32 @@ +import { Injectable } from '@angular/core'; +import { Apollo, gql } from 'apollo-angular'; +import { Observable } from 'rxjs'; +import { map, share } from 'rxjs/operators'; +import ProductsCategory from '@modules/server.common/entities/ProductsCategory'; + +@Injectable() +export class ProductsCategoryService { + constructor(private readonly apollo: Apollo) {} + + getCategories(): Observable { + return this.apollo + .watchQuery<{ productsCategories: ProductsCategory[] }>({ + query: gql` + query allCategories { + productsCategories { + id + name { + locale + value + } + } + } + `, + pollInterval: 1000, + }) + .valueChanges.pipe( + map((res) => res.data.productsCategories), + share() + ); + } +} diff --git a/packages/merchant-tablet-ionic/src/services/promotion.service.ts b/packages/merchant-tablet-ionic/src/services/promotion.service.ts new file mode 100644 index 0000000..b58309d --- /dev/null +++ b/packages/merchant-tablet-ionic/src/services/promotion.service.ts @@ -0,0 +1,102 @@ +import { Injectable } from '@angular/core'; +import { Apollo, gql } from 'apollo-angular'; +import { IPromotionCreateObject } from '@modules/server.common/interfaces/IPromotion'; +import { map, share } from 'rxjs/operators'; +import { Observable } from 'rxjs'; + +@Injectable() +export class PromotionService { + constructor(private readonly apollo: Apollo) {} + + getAll(findInput: { warehouse: string }): Observable { + return this.apollo + .query({ + query: gql` + query allPromotions($findInput: PromotionsFindInput) { + promotions(findInput: $findInput) { + _id + title { + locale + value + } + description { + locale + value + } + active + activeFrom + activeTo + image + promoPrice + purchasesCount + warehouseId + productId + } + } + `, + variables: { findInput }, + }) + .pipe( + map((result) => result.data || []), + share() + ); + } + + create(promotion: IPromotionCreateObject) { + return this.apollo + .mutate<{ promotion: IPromotionCreateObject }>({ + mutation: gql` + mutation CreatePromotion($promotion: PromotionInput) { + createPromotion(createInput: $promotion) { + _id + } + } + `, + variables: { + promotion, + }, + }) + .pipe( + map((result) => result.data), + share() + ); + } + + update(id: String, promotion: IPromotionCreateObject) { + return this.apollo + .mutate<{ promotion: IPromotionCreateObject }>({ + mutation: gql` + mutation UpdatePromotion( + $id: String + $promotion: PromotionInput + ) { + updatePromotion(id: $id, updateInput: $promotion) { + _id + } + } + `, + variables: { + id, + promotion, + }, + }) + .pipe( + map((result) => result.data), + share() + ); + } + + removeByIds(ids: string[]) { + return this.apollo.mutate({ + mutation: gql` + mutation RemoveByIds($ids: [String!]!) { + removePromotionsByIds(ids: $ids) { + ok + n + } + } + `, + variables: { ids }, + }); + } +} diff --git a/packages/merchant-tablet-ionic/src/services/store.service.ts b/packages/merchant-tablet-ionic/src/services/store.service.ts new file mode 100644 index 0000000..d0b273c --- /dev/null +++ b/packages/merchant-tablet-ionic/src/services/store.service.ts @@ -0,0 +1,115 @@ +import { Injectable } from '@angular/core'; +import Device from '@modules/server.common/entities/Device'; +import { TranslateService } from '@ngx-translate/core'; +import Warehouse from '@modules/server.common/entities/Warehouse'; +import { first } from 'rxjs/operators'; +import { WarehouseRouter } from '@modules/client.common.angular2/routers/warehouse-router.service'; + +// TODO use https://beta.ionicframework.com/docs/building/storage + +@Injectable() +export class Store { + constructor( + private readonly translate: TranslateService, // private readonly platform: Platform + private warehouseRouter: WarehouseRouter + ) { + // this._initLanguage(); + } + + get token(): string | null { + return localStorage.getItem('token') || null; + } + + set token(token: string) { + if (token == null) { + localStorage.removeItem('token'); + } else { + localStorage.setItem('token', token); + } + } + + get warehouseId(): string | null { + return localStorage.getItem('_warehouseId') || null; + } + + set warehouseId(id: Warehouse['id'] | null) { + if (id == null) { + localStorage.removeItem('_warehouseId'); + } else { + localStorage.setItem('_warehouseId', id); + } + } + + get deviceId(): string | null { + return localStorage.getItem('_deviceId') || null; + } + + set deviceId(id: Device['id'] | null) { + if (id == null) { + localStorage.removeItem('_deviceId'); + } else { + localStorage.setItem('_deviceId', id); + } + } + + get platform(): string | null { + return localStorage.getItem('_platform') || null; + } + + set platform(type: string | null) { + if (type == null) { + localStorage.removeItem('_platform'); + } else { + localStorage.setItem('_platform', type); + } + } + + get language(): string { + return localStorage.getItem('_language') || null; + } + + set language(language: string) { + if (language == null) { + localStorage.removeItem('_language'); + } else { + localStorage.setItem('_language', language); + } + + this.translate.use(language); + } + + get maintenanceMode(): string | null { + return localStorage.getItem('maintenanceMode') || null; + } + + get serverConnection() { + return localStorage.getItem('serverConnection'); + } + + set serverConnection(val: string) { + localStorage.setItem('serverConnection', val); + } + + async isLogged() { + const merchantId = this.warehouseId; + if (merchantId) { + try { + await this.warehouseRouter + .get(merchantId, false) + .pipe(first()) + .toPromise(); + return true; + } catch (error) {} + } + console.warn(`Store with id '${merchantId}' does not exists!"`); + return false; + } + + clearMaintenanceMode() { + localStorage.removeItem('maintenanceMode'); + } + + clear() { + localStorage.clear(); + } +} diff --git a/packages/merchant-tablet-ionic/src/services/users.service.ts b/packages/merchant-tablet-ionic/src/services/users.service.ts new file mode 100644 index 0000000..0f4eef3 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/services/users.service.ts @@ -0,0 +1,49 @@ +import { Injectable } from '@angular/core'; +import { Apollo, gql } from 'apollo-angular'; +import { map, share } from 'rxjs/operators'; +import { Observable } from 'rxjs'; +import IUser from '@modules/server.common/interfaces/IUser'; +import User from '@modules/server.common/entities/User'; + +@Injectable() +export class UsersService { + constructor(private readonly _apollo: Apollo) {} + + getUsers(): Observable { + return this._apollo + .watchQuery<{ users: IUser[] }>({ + query: gql` + query AllUsers { + users { + _id + firstName + lastName + email + apartment + phone + geoLocation { + countryId + city + house + streetAddress + loc { + type + coordinates + } + } + } + } + `, + pollInterval: 5000, + }) + .valueChanges.pipe( + map((res) => res.data.users), + map((users) => users.map((user) => this._userFactory(user))), + share() + ); + } + + protected _userFactory(user: IUser) { + return user == null ? null : new User(user); + } +} diff --git a/packages/merchant-tablet-ionic/src/services/warehouse-orders.service.ts b/packages/merchant-tablet-ionic/src/services/warehouse-orders.service.ts new file mode 100644 index 0000000..c8fea2d --- /dev/null +++ b/packages/merchant-tablet-ionic/src/services/warehouse-orders.service.ts @@ -0,0 +1,240 @@ +import { Injectable } from '@angular/core'; +import { Apollo, gql } from 'apollo-angular'; +import { Observable } from 'rxjs'; +import { IOrderCreateInput } from '@modules/server.common/routers/IWarehouseOrdersRouter'; +import Order from '@modules/server.common/entities/Order'; +import { map } from 'rxjs/operators'; +import IPagingOptions from '@modules/server.common/interfaces/IPagingOptions'; + +@Injectable() +export class WarehouseOrdersService { + private readonly _orderProductsMutation = gql` + mutation MakeOrder($createInput: OrderCreateInput!) { + createOrder(createInput: $createInput) { + _id + _createdAt + _updatedAt + carrierStatus + isConfirmed + warehouseId + warehouseStatus + user { + _id + } + carrier { + _id + } + } + } + `; + + constructor(private readonly _apollo: Apollo) {} + + createOrder(createInput: IOrderCreateInput): Observable { + return this._apollo + .mutate({ + mutation: this._orderProductsMutation, + variables: { createInput }, + }) + .pipe(map((result: any) => result.data.createOrder)); + } + + getStoreOrdersTableData( + storeId: string, + pagingOptions?: IPagingOptions, + status?: string + ): Observable { + return this._apollo + .watchQuery({ + query: gql` + query GetStoreOrdersTableData( + $storeId: String! + $pagingOptions: PagingOptionsInput + $status: String + ) { + getStoreOrdersTableData( + storeId: $storeId + pagingOptions: $pagingOptions + status: $status + ) { + page + orders { + _id + id + carrierStatus + carrierStatusText + warehouseStatusText + createdAt + warehouseStatus + deliveryTime + status + isConfirmed + finishedProcessingTime + isCancelled + isPaid + orderType + orderNumber + _createdAt + warehouseId + user { + id + _id + firstName + lastName + phone + geoLocation { + streetAddress + house + postcode + notes + countryName + city + loc { + coordinates + } + } + } + warehouse { + id + _id + name + geoLocation { + house + postcode + countryName + city + loc { + coordinates + } + } + } + carrier { + id + logo + email + firstName + lastName + apartment + phone + geoLocation { + city + streetAddress + house + loc { + coordinates + } + } + } + products { + count + price + comment + product { + id + _id + title { + locale + value + } + description { + locale + value + } + details { + locale + value + } + images { + locale + url + width + height + orientation + } + } + } + } + } + } + `, + pollInterval: 2000, + variables: { storeId, pagingOptions, status }, + }) + .valueChanges.pipe( + map((res) => res.data['getStoreOrdersTableData']) + ); + } + + async getCountOfStoreOrders(storeId: string, status?: string) { + const res = await this._apollo + .query({ + query: gql` + query getCountOfStoreOrders( + $storeId: String! + $status: String! + ) { + getCountOfStoreOrders( + storeId: $storeId + status: $status + ) + } + `, + variables: { storeId, status }, + }) + .toPromise(); + + return res.data['getCountOfStoreOrders']; + } + + async removeUserOrders(storeId: string, userId: string) { + const res = await this._apollo + .query({ + query: gql` + query RemoveUserOrders( + $storeId: String! + $userId: String! + ) { + removeUserOrders(storeId: $storeId, userId: $userId) { + number + modified + } + } + `, + variables: { storeId, userId }, + }) + .toPromise(); + + return res.data['removeUserOrders']; + } + + async getOrdersInDelivery(storeId: string) { + const res = await this._apollo + .query({ + query: gql` + query GetOrdersInDelivery($storeId: String!) { + getOrdersInDelivery(storeId: $storeId) { + carrier { + id + geoLocation { + loc { + coordinates + } + } + } + user { + geoLocation { + loc { + coordinates + } + } + } + } + } + `, + variables: { storeId }, + }) + .toPromise(); + + return res.data['getOrdersInDelivery']; + } +} diff --git a/packages/merchant-tablet-ionic/src/services/warehouse-products.service.ts b/packages/merchant-tablet-ionic/src/services/warehouse-products.service.ts new file mode 100644 index 0000000..ae8387a --- /dev/null +++ b/packages/merchant-tablet-ionic/src/services/warehouse-products.service.ts @@ -0,0 +1,92 @@ +import { Injectable } from '@angular/core'; +import { Apollo, gql } from 'apollo-angular'; +import { Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; +import IPagingOptions from '@modules/server.common/interfaces/IPagingOptions'; +import WarehouseProduct from '@modules/server.common/entities/WarehouseProduct'; + +@Injectable() +export class WarehouseProductsService { + constructor(private readonly _apollo: Apollo) {} + + getProductsWithPagination( + id: string, + pagingOptions?: IPagingOptions + ): Observable { + return this._apollo + .watchQuery<{ productsCategories: WarehouseProduct[] }>({ + query: gql` + query GetProductsWithPagination( + $id: String! + $pagingOptions: PagingOptionsInput + ) { + getProductsWithPagination( + id: $id + pagingOptions: $pagingOptions + ) { + id + _id + price + initialPrice + count + soldCount + product { + description { + value + locale + } + _id + id + title { + value + locale + } + details { + value + locale + } + images { + locale + url + orientation + width + height + } + categories + _createdAt + _updatedAt + } + isCarrierRequired + isTakeaway + deliveryTimeMin + deliveryTimeMax + isCarrierRequired + isDeliveryRequired + isManufacturing + isTakeaway + } + } + `, + pollInterval: 2000, + variables: { id, pagingOptions }, + }) + .valueChanges.pipe( + map((res) => res.data['getProductsWithPagination']) + ); + } + + async getProductsCount(id: string) { + const res = await this._apollo + .query({ + query: gql` + query GetProductsCount($id: String!) { + getProductsCount(id: $id) + } + `, + variables: { id }, + }) + .toPromise(); + + return res.data['getProductsCount']; + } +} diff --git a/packages/merchant-tablet-ionic/src/services/warehouses.service.ts b/packages/merchant-tablet-ionic/src/services/warehouses.service.ts new file mode 100644 index 0000000..2c890d1 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/services/warehouses.service.ts @@ -0,0 +1,289 @@ +import { Injectable } from '@angular/core'; +import { Apollo, gql } from 'apollo-angular'; +import Warehouse from '@modules/server.common/entities/Warehouse'; +import { map, share } from 'rxjs/operators'; +import { Observable } from 'rxjs'; +import IWarehouse from '@modules/server.common/interfaces/IWarehouse'; +import IWarehouseProductCreateObject from '@modules/server.common/interfaces/IWarehouseProduct'; +import GeoLocation from '@modules/server.common/entities/GeoLocation'; +import IPagingOptions from '@modules/server.common/interfaces/IPagingOptions'; + +@Injectable() +export class WarehousesService { + constructor(private readonly _apollo: Apollo) {} + + hasExistingStores(): Observable { + return this._apollo + .query<{ hasExistingStores: boolean }>({ + query: gql` + query HasExistingStores { + hasExistingStores + } + `, + }) + .pipe(map((res) => res.data.hasExistingStores)); + } + + getCountExistingCustomers() { + return this._apollo + .watchQuery<{ + getCountExistingCustomers: { total; perStore }; + }>({ + query: gql` + query GetCountExistingCustomers { + getCountExistingCustomers { + total + perStore { + storeId + customersCount + } + } + } + `, + }) + .valueChanges.pipe( + map((res) => res.data.getCountExistingCustomers) + ); + } + + getCountExistingCustomersToday() { + return this._apollo + .watchQuery<{ + getCountExistingCustomersToday: { total; perStore }; + }>({ + query: gql` + query GetCountExistingCustomersToday { + getCountExistingCustomersToday { + total + perStore { + storeId + customersCount + } + } + } + `, + }) + .valueChanges.pipe( + map((res) => res.data.getCountExistingCustomersToday) + ); + } + + getAllStores() { + return this._apollo + .query<{ getAllStores: Warehouse[] }>({ + query: gql` + query GetAllStores { + getAllStores { + id + _createdAt + usedCarriersIds + geoLocation { + city + streetAddress + house + loc { + coordinates + } + } + } + } + `, + }) + .pipe(map((res) => res.data.getAllStores)); + } + + getStores(pagingOptions?: IPagingOptions): Observable { + return this._apollo + .watchQuery<{ warehouses: IWarehouse[] }>({ + query: gql` + query AllWarehouses($pagingOptions: PagingOptionsInput) { + warehouses(pagingOptions: $pagingOptions) { + _id + _createdAt + name + contactEmail + contactPhone + logo + username + usedCarriersIds + geoLocation { + city + streetAddress + house + } + } + } + `, + variables: { pagingOptions }, + pollInterval: 5000, + }) + .valueChanges.pipe( + map((res) => res.data.warehouses), + map((ws) => ws.map((w) => this._warehouseFactory(w))), + share() + ); + } + + getNearbyStores(geoLocation: GeoLocation): Observable { + return this._apollo + .watchQuery<{ nearbyStores: IWarehouse[] }>({ + query: gql` + query GetNearbyStores($geoLocation: GeoLocationFindInput!) { + nearbyStores(geoLocation: $geoLocation) { + _id + name + contactEmail + contactPhone + logo + geoLocation { + city + streetAddress + house + } + } + } + `, + pollInterval: 5000, + variables: { geoLocation }, + }) + .valueChanges.pipe( + map((res) => res.data.nearbyStores), + map((ws) => ws.map((w) => this._warehouseFactory(w))), + share() + ); + } + + removeByIds(ids: string[]) { + return this._apollo.mutate({ + mutation: gql` + mutation RemoveByIds($ids: [String!]!) { + removeWarehousesByIds(ids: $ids) + } + `, + variables: { ids }, + }); + } + + addProducts( + warehouseId: string, + products: IWarehouseProductCreateObject[] + ) { + return this._apollo + .mutate<{ + warehouseId: string; + products: IWarehouseProductCreateObject[]; + }>({ + mutation: gql` + mutation AddProducts( + $warehouseId: String! + $products: [WarehouseProductInput!]! + ) { + addWarehouseProducts( + warehouseId: $warehouseId + products: $products + ) { + product { + id + } + } + } + `, + variables: { + warehouseId, + products, + }, + }) + .pipe( + map((result) => result.data['warehouseAddProducts']), + share() + ); + } + + removeProductsById(warehouseId: string, productsIds: string[]) { + return this._apollo.mutate({ + mutation: gql` + mutation RemoveProductsByIds( + $warehouseId: String! + $productsIds: [String!]! + ) { + removeWarehouseProducts( + warehouseId: $warehouseId + productsIds: $productsIds + ) + } + `, + variables: { warehouseId, productsIds }, + }); + } + + getStoreById(id: string) { + return this._apollo + .query({ + query: gql` + query GetStoreById($id: String!) { + warehouse(id: $id) { + id + name + logo + usedCarriersIds + contactEmail + contactPhone + orderCancelation { + enabled + onState + } + geoLocation { + city + streetAddress + house + loc { + coordinates + } + } + } + } + `, + variables: { id }, + }) + .pipe( + map((res) => res.data['warehouse']), + share() + ); + } + + getWarehouseOrderProcess(id: string) { + return this._apollo + .query({ + query: gql` + query GetWarehouseOrderProcess($id: String!) { + warehouse(id: $id) { + ordersShortProcess + } + } + `, + variables: { id }, + }) + .pipe( + map((res) => res.data['warehouse']), + share() + ); + } + + async getCountOfMerchants() { + const res = await this._apollo + .query({ + query: gql` + query GetCountOfMerchants { + getCountOfMerchants + } + `, + }) + .toPromise(); + + return res.data['getCountOfMerchants']; + } + + protected _warehouseFactory(warehouse: IWarehouse) { + return warehouse == null ? null : new Warehouse(warehouse); + } +} diff --git a/packages/merchant-tablet-ionic/src/test.ts b/packages/merchant-tablet-ionic/src/test.ts new file mode 100644 index 0000000..fc74169 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/test.ts @@ -0,0 +1,22 @@ +// This file is required by karma.conf.js and loads recursively all the .spec and framework files + +import 'zone.js/dist/zone-testing'; +import { getTestBed } from '@angular/core/testing'; +import { + BrowserDynamicTestingModule, + platformBrowserDynamicTesting, +} from '@angular/platform-browser-dynamic/testing'; + +declare const require: any; + +// First, initialize the Angular testing environment. +getTestBed().initTestEnvironment( + BrowserDynamicTestingModule, + platformBrowserDynamicTesting(), { + teardown: { destroyAfterEach: false } +} +); +// Then we find all the tests. +const context = require.context('./', true, /\.spec\.ts$/); +// And load the modules. +context.keys().map(context); diff --git a/packages/merchant-tablet-ionic/src/theme/smart-table.scss b/packages/merchant-tablet-ionic/src/theme/smart-table.scss new file mode 100644 index 0000000..59c282a --- /dev/null +++ b/packages/merchant-tablet-ionic/src/theme/smart-table.scss @@ -0,0 +1,171 @@ +.smart-table { + table { + // width: 90% !important; + margin: 20px auto; + line-height: 1.5em; + border-collapse: collapse; + border-spacing: 0; + display: table; + width: 100%; + max-width: 100%; + overflow: auto; + word-break: normal; + word-break: keep-all; + + tbody tr:nth-child(odd) { + background-color: #f5f7fc; + } + + input { + line-height: normal; + height: 40px !important; + color: #2a2a2a; + display: block; + padding: 0.25rem 0.65rem; + line-height: 1.5; + color: #495057; + background-color: #fff; + background-clip: padding-box; + border: 1.5px solid #ced4da; + border-radius: 0.375rem; + transition: border-color 0.15s ease-in-out, + box-shadow 0.15s ease-in-out; + } + + input:focus { + // border-color: #40dc7e; + } + a { + position: relative; + padding: 0.475rem 1rem; + vertical-align: middle; + line-height: 1.25; + color: #2a2a2a !important; + + font-family: Exo; + font-weight: 600; + } + + .nb-theme-default, + th, + .nb-theme-default, + ng2-smart-table, + table, + tr, + td { + position: relative; + padding: 0.8rem 1rem !important; + border: 1px solid #ebeef2; + vertical-align: middle; + line-height: 1.25; + color: #2a2a2a !important; + + div { + text-align: left; + } + + font-family: Exo; + } + tbody { + display: table-row-group; + vertical-align: middle; + border-color: inherit; + font-size: 1rem; + + .nb-theme-default, + tbody, + tr.selected, + .nb-theme-default, + tr:hover, + tr:focus { + background: #e6f3ff !important; + } + } + thead { + display: table-header-group; + vertical-align: middle; + border-color: inherit; + font-size: 1.1rem; + text-align: left; + } + } + + nav.ng2-smart-pagination-nav .pagination { + font-family: Exo; + font-size: 1rem; + line-height: 1.25; + border: #ebeef2 solid 1px; + border-radius: 0.375rem; + } + + .pagination li:not(:last-child) { + border-right: 1px solid #ebeef2; + } + + .pagination li a, + .pagination li > span { + // background: transparent; + color: #2a2a2a; + border: none; + } + + .pagination li.active > span { + color: #ffffff; + } + + nav.ng2-smart-pagination-nav { + display: block; + margin: 0 auto 10px auto; + } + + li { + padding: 0.3rem 0 !important; + + a { + text-decoration: none !important; + padding: 0.3rem 1.7rem !important; + margin: 0 !important; + font-size: 16px !important; + } + } + + li.disabled { + background-color: rgba(0, 0, 0, 0.05); + + a { + background: none !important; + } + } + + li.active { + padding: 0.3rem 0 !important; + z-index: 9999; + border-top: 1px solid #40dc7e; + border-bottom: 1px solid #40dc7e; + border-right: 1px solid #40dc7e !important; + text-align: center; + background-color: #40dc7e; + white-space: normal; + .page-link { + white-space: normal; + background-color: #40dc7e !important; + text-decoration: none !important; + padding: 0.3rem 1.7rem !important; + margin: 0 !important; + } + } + + li.active:hover { + background-color: #40dc7e !important; + } + + li:hover { + background: rgba(0, 0, 0, 0.05); + } + + .logo { + width: 42px; + height: 42px; + border-radius: 50%; + } +} diff --git a/packages/merchant-tablet-ionic/src/theme/variables.scss b/packages/merchant-tablet-ionic/src/theme/variables.scss new file mode 100644 index 0000000..e9fe51a --- /dev/null +++ b/packages/merchant-tablet-ionic/src/theme/variables.scss @@ -0,0 +1,85 @@ +$colors: ( + primary: #488aff, + secondary: #32db64, + danger: #f53d3d, + light: #f4f4f4, + dark: #222, +); + +// Both RTL and LTR +$app-direction: multi; + +/** Ionic CSS Variables **/ +:root { + /** primary **/ + --ion-color-primary: #2a2c39; + --ion-color-primary-rgb: 42, 44, 57; + --ion-color-primary-contrast: #ffffff; + --ion-color-primary-contrast-rgb: 255, 255, 255; + --ion-color-primary-shade: #252732; + --ion-color-primary-tint: #3f414d; + + /** secondary **/ + --ion-color-secondary: #bd4742; + --ion-color-secondary-rgb: 189, 71, 66; + --ion-color-secondary-contrast: #ffffff; + --ion-color-secondary-contrast-rgb: 255, 255, 255; + --ion-color-secondary-shade: #a63e3a; + --ion-color-secondary-tint: #c45955; + + /** tertiary **/ + --ion-color-tertiary: #7044ff; + --ion-color-tertiary-rgb: 112, 68, 255; + --ion-color-tertiary-contrast: #ffffff; + --ion-color-tertiary-contrast-rgb: 255, 255, 255; + --ion-color-tertiary-shade: #633ce0; + --ion-color-tertiary-tint: #7e57ff; + + /** success **/ + --ion-color-success: #10dc60; + --ion-color-success-rgb: 16, 220, 96; + --ion-color-success-contrast: #000000; + --ion-color-success-contrast-rgb: 0, 0, 0; + --ion-color-success-shade: #0ec254; + --ion-color-success-tint: #28e070; + + /** warning **/ + --ion-color-warning: #ffce00; + --ion-color-warning-rgb: 255, 206, 0; + --ion-color-warning-contrast: #ffffff; + --ion-color-warning-contrast-rgb: 255, 255, 255; + --ion-color-warning-shade: #e0b500; + --ion-color-warning-tint: #ffd31a; + + /** danger **/ + --ion-color-danger: #f04141; + --ion-color-danger-rgb: 245, 61, 61; + --ion-color-danger-contrast: #ffffff; + --ion-color-danger-contrast-rgb: 255, 255, 255; + --ion-color-danger-shade: #d33939; + --ion-color-danger-tint: #f25454; + + /** dark **/ + --ion-color-dark: #222428; + --ion-color-dark-rgb: 34, 34, 34; + --ion-color-dark-contrast: #ffffff; + --ion-color-dark-contrast-rgb: 255, 255, 255; + --ion-color-dark-shade: #1e2023; + --ion-color-dark-tint: #383a3e; + + /** medium **/ + --ion-color-medium: #989aa2; + --ion-color-medium-rgb: 152, 154, 162; + --ion-color-medium-contrast: #ffffff; + --ion-color-medium-contrast-rgb: 255, 255, 255; + --ion-color-medium-shade: #86888f; + --ion-color-medium-tint: #a2a4ab; + + /** light **/ + --ion-color-light: #f4f5f8; + --ion-color-light-rgb: 244, 244, 244; + --ion-color-light-contrast: #000000; + --ion-color-light-contrast-rgb: 0, 0, 0; + --ion-color-light-shade: #d7d8da; + --ion-color-light-tint: #f5f6f9; +} diff --git a/packages/merchant-tablet-ionic/src/tsconfig.app.json b/packages/merchant-tablet-ionic/src/tsconfig.app.json new file mode 100644 index 0000000..b96cf3a --- /dev/null +++ b/packages/merchant-tablet-ionic/src/tsconfig.app.json @@ -0,0 +1,9 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "outDir": "../out-tsc/app", + "baseUrl": "./", + "module": "esnext" + }, + "exclude": ["test.ts", "**/*.spec.ts"] +} diff --git a/packages/merchant-tablet-ionic/src/tsconfig.spec.json b/packages/merchant-tablet-ionic/src/tsconfig.spec.json new file mode 100644 index 0000000..bd636b1 --- /dev/null +++ b/packages/merchant-tablet-ionic/src/tsconfig.spec.json @@ -0,0 +1,9 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "outDir": "../out-tsc/spec", + "baseUrl": "./" + }, + "files": ["test.ts"], + "include": ["polyfills.ts", "**/*.spec.ts", "**/*.d.ts"] +} diff --git a/packages/merchant-tablet-ionic/tsconfig.json b/packages/merchant-tablet-ionic/tsconfig.json new file mode 100644 index 0000000..1a6a59d --- /dev/null +++ b/packages/merchant-tablet-ionic/tsconfig.json @@ -0,0 +1,59 @@ +{ + "extends": "../../tsconfig.base.json", + "compileOnSave": false, + "compilerOptions": { + "outDir": "./www/out-tsc", + "baseUrl": "./src", + "target": "es5", + "sourceMap": true, + "declaration": false, + "moduleResolution": "node", + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "importHelpers": true, + "noImplicitAny": false, + "preserveConstEnums": true, + "allowSyntheticDefaultImports": true, + "paths": { + "@angular/*": ["../node_modules/@angular/*"], + "@modules/server.common/*": ["../../common/src/*"], + "@modules/client.common.angular2/*": ["../../common-angular/src/*"], + "@modules/*": ["./modules/*"], + "@pyro/*": ["../../common/src/@pyro/*"], + "mongoose": ["../../common-angular/src/mongoose-placeholder"], + "typeorm": ["../../common-angular/src/typeorm-placeholder"], + "environment": ["./environments/environment"], + "core-js/es7/reflect": [ + "../../../node_modules/core-js/proposals/reflect-metadata" + ] + }, + "lib": ["es2017", "dom", "esnext"], + "noUnusedLocals": false, + "types": ["node", "reflect-metadata", "googlemaps", "jasmine"] + }, + "files": [ + "src/main.ts", + "src/polyfills.ts" + ], + "include": [ + "src/**/*d.ts", + "./*.ts", + "../common/**/*d.ts", + "../common-angular/**/*d.ts" + ], + "exclude": [ + "node_modules", + "src/**/*.spec.ts", + "src/**/__tests__/*.ts" + ], + "atom": { + "rewriteTsconfig": false + }, + "angularCompilerOptions": { + "preserveWhitespaces": false, + "strictInjectionParameters": true, + "fullTemplateTypeCheck": true, + "strictTemplates": true, + "strictMetadataEmit": false + } +} diff --git a/packages/merchant-tablet-ionic/tslint.json b/packages/merchant-tablet-ionic/tslint.json new file mode 100644 index 0000000..5cea240 --- /dev/null +++ b/packages/merchant-tablet-ionic/tslint.json @@ -0,0 +1,136 @@ +{ + "extends": ["tslint:latest", "tslint-config-prettier"], + "rulesDirectory": ["node_modules/codelyzer"], + "linterOptions": { + "exclude": ["node_modules", "dist"] + }, + "rules": { + "no-implicit-dependencies": false, + "no-submodule-imports": false, + "trailing-comma": [ + false, + { + "multiline": "always", + "singleline": "never" + } + ], + "interface-name": [false, "always-prefix"], + "arrow-return-shorthand": true, + "callable-types": true, + "class-name": true, + "comment-format": [true, "check-space"], + "forin": true, + "import-blacklist": [true], + "ordered-imports": false, + "interface-over-type-literal": true, + "label-position": true, + "max-line-length": [true, 120], + "member-access": false, + "no-arg": true, + "no-console": [false], + "no-construct": true, + "no-debugger": true, + "no-duplicate-super": true, + "no-empty": false, + "no-empty-interface": false, + "no-eval": true, + "no-misused-new": true, + "no-non-null-assertion": false, + "no-shadowed-variable": true, + "no-string-literal": false, + "no-string-throw": true, + "no-switch-case-fall-through": true, + "no-unnecessary-initializer": true, + "no-use-before-declare": true, + "no-var-keyword": true, + "object-literal-sort-keys": false, + "prefer-const": true, + "object-literal-key-quotes": false, + "no-angle-bracket-type-assertion": false, + "member-ordering": false, + "no-consecutive-blank-lines": false, + "radix": true, + "semicolon": [true, "always"], + "triple-equals": [true, "allow-null-check"], + "typeof-compare": true, + "unified-signatures": true, + "variable-name": false, + "directive-selector": [true, "attribute", "ngx", "camelCase"], + "component-selector": [ + true, + "element", + [ + "page", + "merchant", + "basic", + "customer", + "user", + "order", + "carrier", + "carriers", + "location", + "add", + "account", + "make", + "select", + "e-cu", + "ngx", + "google-map", + "ea", + "es" + ], + "kebab-case" + ], + "no-attribute-parameter-decorator": true, + "no-forward-ref": true, + "no-input-rename": true, + "no-output-rename": true, + "only-arrow-functions": false, + "pipe-naming": [true, "camelCase", "my"], + "use-host-property-decorator": true, + "ban": [ + true, + "eval", + "fit", + "fdescribe", + { + "name": "$", + "message": "please don't" + } + ], + "max-classes-per-file": [false], + "import-destructuring-spacing": true, + "invoke-injectable": true, + "no-access-missing-member": true, + "templates-use-public": true, + "use-input-property-decorator": true, + "use-life-cycle-interface": true, + "use-output-property-decorator": true, + "use-pipe-transform-interface": true, + "quotemark": [true, "single", "avoid-escape"], + "eofline": true, + "import-spacing": true, + "indent": [true, "tabs"], + "no-trailing-whitespace": true, + "one-line": [false], + "typedef-whitespace": [ + true, + { + "call-signature": "nospace", + "index-signature": "nospace", + "parameter": "nospace", + "property-declaration": "nospace", + "variable-declaration": "nospace" + } + ], + "whitespace": [ + true, + "check-branch", + "check-decl", + "check-operator", + "check-separator", + "check-type" + ], + "no-namespace": false + } +} diff --git a/packages/shop-mobile-expo/.env.template b/packages/shop-mobile-expo/.env.template new file mode 100644 index 0000000..9247199 --- /dev/null +++ b/packages/shop-mobile-expo/.env.template @@ -0,0 +1,78 @@ +# NOTE: do NOT ever put here any secure settings! (e.g. Secret Keys) +# We are using dotenv (.env) for consistency with other Platform projects +# This is expo app and all settings will be loaded into the client browser! + +# Don't forget to update scripts/*.ts and src/environments/*.ts on changes! + +VERSION=1.0.0 + +# 'slides' | 'list' +PRODUCTS_VIEW_TYPE=slides + +# 'popup' or 'page' +ORDER_INFO_TYPE=page + +API_FILE_UPLOAD_URL=https://api.cloudinary.com/v1_1/evereq/upload + +INVITE_BY_CODE_LOGO=assets/imgs/ever-logo.svg +NO_INTERNET_LOGO=assets/imgs/logo.png + +COMPANY_NAME=Ever Co. LTD + +GOOGLE_MAPS_API_KEY= + +GOOGLE_ANALYTICS_API_KEY= +FAKE_UUID= + +# Not secret MixPanel Token +MIXPANEL_API_KEY= + +DEFAULT_LANGUAGE=en-US +DEFAULT_LOCALE=en-US + +DELIVERY_TIME_MIN=30 +DELIVERY_TIME_MAX=60 + +SUPPORT_NUMBER=0888888888 + +STRIPE_PUBLISHABLE_KEY= + +# TODO: replace logo with recent one! +STRIPE_POP_UP_LOGO=https://bitbucket-assetroot.s3.amazonaws.com/c/photos/2016/Jan/30/1263967991-1-everbie-avatar.png + +MAP_MERCHANT_ICON_LINK=http://maps.google.com/mapfiles/kml/pal3/icon21.png +MAP_USER_ICON_LINK=http://maps.google.com/mapfiles/kml/pal3/icon48.png +MAP_CARRIER_ICON_LINK=http://maps.google.com/mapfiles/kml/pal4/icon54.png + +DEFAULT_LATITUDE=42.6459136 +DEFAULT_LONGITUDE=23.3332736 + +# Graphql endpoints for apollo services +GQL_ENDPOINT=http://localhost:8443/graphql +GQL_SUBSCRIPTIONS_ENDPOINT=ws://localhost:2086/subscriptions +SERVICES_ENDPOINT=http://localhost:5500 +HTTPS_SERVICES_ENDPOINT=https://localhost:2087 + +FAKE_INVITE_ID=1ae9d04f9010d834f8906881 +FAKE_INVITE_CITY=Ashdod +FAKE_INVITE_POSTCODE=77452 +FAKE_INVITE_ADDRESS=HaAtsmaut +FAKE_INVITE_HOUSE=126 +FAKE_INVITE_CREATED_AT=2018-05-02T14:50:55.658Z +FAKE_INVITE_UPDATED_AT=2018-05-02T14:50:55.658Z +FAKE_INVITE_APARTMENT=3 +FAKE_INVITE_CODE=8321 +FAKE_INVITE_COUNTRY_ID=102 + +# For maintenance micro service. Ever maintanance API URL: https://maintenance.ever.co/status +SETTINGS_APP_TYPE=shop-mobile +SETTINGS_MAINTENANCE_API_URL= + +# For "single" merchant (multiple branches) +MERCHANT_IDS=[] + +PORT=4201 +WEB_MEMORY=4096 +WEB_CONCURRENCY=1 + +SHOPPING_CART=false diff --git a/packages/shop-mobile-expo/.eslintrc.json b/packages/shop-mobile-expo/.eslintrc.json new file mode 100644 index 0000000..84cc2df --- /dev/null +++ b/packages/shop-mobile-expo/.eslintrc.json @@ -0,0 +1,125 @@ +{ + "root": true, + "extends": "@react-native-community", + "env": { + "es6": true + }, + "parser": "@typescript-eslint/parser", + "plugins": ["@typescript-eslint"], + "rules": { + "@typescript-eslint/consistent-type-assertions": "off", + "@typescript-eslint/consistent-type-definitions": "error", + "@typescript-eslint/dot-notation": "off", + "@typescript-eslint/explicit-member-accessibility": [ + "off", + { + "accessibility": "explicit" + } + ], + "@typescript-eslint/indent": "off", + "@typescript-eslint/member-delimiter-style": [ + "error", + { + "multiline": { + "delimiter": "semi", + "requireLast": true + }, + "singleline": { + "delimiter": "semi", + "requireLast": false + } + } + ], + "@typescript-eslint/member-ordering": "off", + "@typescript-eslint/naming-convention": "off", + "@typescript-eslint/no-empty-function": "off", + "@typescript-eslint/no-empty-interface": "off", + "@typescript-eslint/no-misused-new": "error", + "@typescript-eslint/no-namespace": "off", + "@typescript-eslint/no-non-null-assertion": "off", + "@typescript-eslint/no-shadow": [ + "error", + { + "hoist": "all" + } + ], + "@typescript-eslint/no-use-before-define": ["error"], + "@typescript-eslint/prefer-function-type": "error", + "@typescript-eslint/quotes": [ + "error", + "single", + { + "avoidEscape": true + } + ], + "@typescript-eslint/semi": ["error", "always"], + "@typescript-eslint/type-annotation-spacing": "error", + "@typescript-eslint/unified-signatures": "error", + "arrow-body-style": "error", + "brace-style": ["off", "off"], + "comma-dangle": ["off", "always-multiline"], + "constructor-super": "error", + "dot-notation": "off", + "eol-last": "error", + "eqeqeq": ["error", "smart"], + "guard-for-in": "error", + "id-denylist": "off", + "id-match": "off", + "import/order": "off", + "indent": "off", + "max-classes-per-file": ["off", 1], + "max-len": [ + "error", + { + "code": 120 + } + ], + "no-caller": "error", + "no-console": "off", + "no-debugger": "error", + "no-empty": "off", + "no-empty-function": "off", + "no-eval": "error", + "no-fallthrough": "error", + "no-multiple-empty-lines": "off", + "no-new-wrappers": "error", + "no-restricted-imports": "error", + "no-shadow": "error", + "no-throw-literal": "error", + "no-trailing-spaces": "error", + "no-undef-init": "error", + "no-underscore-dangle": "off", + "no-unused-labels": "error", + "no-use-before-define": "off", + "no-var": "error", + "prefer-arrow/prefer-arrow-functions": "off", + "prefer-const": "error", + "quote-props": "off", + "quotes": "error", + "radix": "error", + "semi": "off", + "valid-typeof": "error" + }, + "overrides": [ + { + "files": ["*.ts", "*.tsx"], + "rules": { + "prettier/prettier": [ + "error", + { + "endOfLine": "auto" + } + ], + "no-extra-semi": "error", + "no-mixed-spaces-and-tabs": ["off", "smart-tabs"], + "no-spaced-func": "off", + "jsx-quotes": ["error", "prefer-single"], + "spaced-comment": [ + "error", + "always", + { "block": { "balanced": true }, "markers": ["/"] } + ] + } + } + ] +} diff --git a/packages/shop-mobile-expo/.expo-shared/assets.json b/packages/shop-mobile-expo/.expo-shared/assets.json new file mode 100644 index 0000000..1e6decf --- /dev/null +++ b/packages/shop-mobile-expo/.expo-shared/assets.json @@ -0,0 +1,4 @@ +{ + "12bb71342c6255bbf50437ec8f4441c083f47cdb74bd89160c15e4f43e52a1cb": true, + "40b842e832070c58deac6aa9e08fa459302ee3f9da492c7e77d93d2fbf4a56fd": true +} diff --git a/packages/shop-mobile-expo/.gitignore b/packages/shop-mobile-expo/.gitignore new file mode 100644 index 0000000..23f7961 --- /dev/null +++ b/packages/shop-mobile-expo/.gitignore @@ -0,0 +1,30 @@ +node_modules/ +.expo/ +dist/ +npm-debug.* +*.jks +*.p8 +*.p12 +*.key +*.mobileprovision +*.orig.* +*.lock +web-build/ + +# macOS +.DS_Store + +# Do not store autogenerated docs in repo +docs/ + +# Do net store aotogenerate jest caches +jest/ + +# environment files +.env* +!.env.template +*/environments/environment* + +# others +.editorconfig +tslint.json diff --git a/packages/shop-mobile-expo/.prettierrc.json b/packages/shop-mobile-expo/.prettierrc.json new file mode 100644 index 0000000..09b20d3 --- /dev/null +++ b/packages/shop-mobile-expo/.prettierrc.json @@ -0,0 +1,11 @@ +{ + "semi": true, + "useTabs": true, + "tabWidth": 4, + "bracketSpacing": true, + "jsxBracketSameLine": true, + "singleQuote": true, + "jsxSingleQuote": true, + "trailingComma": "all", + "arrowParens": "always" +} diff --git a/packages/shop-mobile-expo/.watchmanconfig b/packages/shop-mobile-expo/.watchmanconfig new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/packages/shop-mobile-expo/.watchmanconfig @@ -0,0 +1 @@ +{} diff --git a/packages/shop-mobile-expo/App.spec.tsx b/packages/shop-mobile-expo/App.spec.tsx new file mode 100644 index 0000000..b4eb670 --- /dev/null +++ b/packages/shop-mobile-expo/App.spec.tsx @@ -0,0 +1,26 @@ +/** + * @format + */ + +import 'react-native'; +import React from 'react'; +import renderer from 'react-test-renderer'; +// Note: test renderer must be required after react-native. +import { View, Text } from 'react-native'; + +// COMPONENTS +// import App from './src/App'; + +export default function App() { + return ( + + hi + + ); +} + +describe('Demo', function () { + it('should renders correctly', () => { + renderer.create(); + }); +}); diff --git a/packages/shop-mobile-expo/App.tsx b/packages/shop-mobile-expo/App.tsx new file mode 100644 index 0000000..fe136f8 --- /dev/null +++ b/packages/shop-mobile-expo/App.tsx @@ -0,0 +1,14 @@ +import { registerRootComponent } from 'expo'; +import { LogBox } from 'react-native'; +import ENV from './src/environments/environment'; + +// APP COMPONENT +import App from './src/App'; + +// registerRootComponent calls AppRegistry.registerComponent('main', () => App); +// It also ensures that whether you load the app in Expo Go or in a native build, +// the environment is set up appropriately +registerRootComponent(App); +if (ENV.PRODUCTION || !__DEV__) { + LogBox.ignoreAllLogs(); +} diff --git a/packages/shop-mobile-expo/README.md b/packages/shop-mobile-expo/README.md new file mode 100644 index 0000000..edef945 --- /dev/null +++ b/packages/shop-mobile-expo/README.md @@ -0,0 +1,87 @@ +# @ever-platform/shop-mobile-expo + +> 🛑 **NOT YET COMPATIBLE**: Currently this package don't works fine with Lerna mono-repo logic. +> To be able to run this package locally, you've to extract the package outside of @ever-platform and then install dependencies (see [setup](#-setup) section). +> But this package depend on @ever-platform server, don't forget to start the api... + +## 📋 Contents + +- [Contents](#-contents) +- [Demo](#-demo) +- [Setup](#-setup) +- [Testing](#-testing) +- [Contribution](#-contribution) + +## 🤳 Demo + +> We used expo to test our application easily and quickly. +> So you must first install expo on your mobile and then test the demo version. + +Copy that link -> **[https://exp.host/@everco/ever-demand-shop-mobile-expo](https://exp.host/@everco/ever-demand-shop-mobile-expo)** and past it into your expo app to preview demo. + +## ⚡ Setup + + + +### 📋 Package structure + + . + ├── .expo-shared + ├── script + ├── src + ├── assets # Contain app assets (imgs, fonts, ...) + ├── client # Contain ApolloGraphQL queries, mutations and relative types + ├── components # Contain components used in the app + ├── constants + ├── helpers # Class/Functions that provides useful feature + ├── environments + ├── router + ├── screens + ├── store + ├── types # Contain global types + └── App.tsx + ├── .env.template + ├── .eslintrc.json + ├── .gitignore + ├── .prettierrc.json + ├── .watchmanconfig + ├── app.json + ├── App.spec.tsx + ├── App.tsx + ├── babel.config.js + ├── metro.config.js + ├── package.json + ├── README.md + └── tsconfig.json + +### 🌟 Before install + +**🚧 Before installing et running project locally, make sure you have :** + +- The latest Yarn version installed in your computer **(note that we're only use Yarn)** +- Node.js V14.x.x or higher installed in your computer +- Expo 5.x.x or higher installed in your computer +- The latest version of expo app installed in your phone (or virtual device) + +### ⚡ Installation & Running + +- Install dependencies `Yarn`. run `yarn add`. +- Create environment files by running `yarn run config`. +- After these steps, run `yarn run start` command to start bundle server + +> 🚧 If you can't connect to the server, maybe the endpoint aren't compatible. +> To update them, got to `./src/environments/environment.ts` and change urls props into `ENDPOINT` prop from `http://localhost/...` to `... + + + +> 💡 If you still can't connect to the API, we recommend that you consult the wiki where we explain how to expose your local network: + +## 🧪 Testing + +We're using Jest to test our application. + +To run all tests, run `yarn run test` + +### 🤝 Contribution + +Please refer to the [guide of the platform](https://github.com/ever-co/ever-demand/#contribute) diff --git a/packages/shop-mobile-expo/app.json b/packages/shop-mobile-expo/app.json new file mode 100644 index 0000000..511487b --- /dev/null +++ b/packages/shop-mobile-expo/app.json @@ -0,0 +1,47 @@ +{ + "expo": { + "name": "ever-demand-shop-mobile-expo", + "slug": "ever-demand-shop-mobile-expo", + "owner": "everco", + "description": "Ever Shop Mobile App build with expo", + "version": "0.4.3", + "orientation": "portrait", + "primaryColor": "#2A2C39", + "backgroundColor": "#2A2C39", + "icon": "./src/assets/img/appIcons/icon.png", + "splash": { + "image": "./src/assets/img/appIcons/splash.png", + "resizeMode": "cover", + "backgroundColor": "#2A2C39" + }, + "updates": { + "fallbackToCacheTimeout": 0 + }, + "assetBundlePatterns": ["**/*"], + "ios": { + "bundleIdentifier": "com.everDemand.shopMobileExpo", + "buildNumber": "0.4.3", + "supportsTablet": true + }, + "androidStatusBar": { + "barStyle": "light-content", + "translucent": true, + "backgroundColor": "#00000000" + }, + "androidNavigationBar": { + "backgroundColor": "#2A2C39", + "barStyle": "light-content" + }, + "android": { + "package": "com.everDemand.shopMobileExpo", + "versionCode": 1, + "adaptiveIcon": { + "foregroundImage": "./src/assets/img/appIcons/adaptive-icon.png", + "backgroundColor": "#2A2C39" + } + }, + "web": { + "favicon": "./src/assets/img/appIcons/favicon.png" + } + } +} diff --git a/packages/shop-mobile-expo/babel.config.js b/packages/shop-mobile-expo/babel.config.js new file mode 100644 index 0000000..7a7d30a --- /dev/null +++ b/packages/shop-mobile-expo/babel.config.js @@ -0,0 +1,7 @@ +module.exports = function (api) { + api.cache(true); + return { + presets: ['babel-preset-expo'], + plugins: ['react-native-reanimated/plugin'], + }; +}; diff --git a/packages/shop-mobile-expo/metro.config.js b/packages/shop-mobile-expo/metro.config.js new file mode 100644 index 0000000..a837544 --- /dev/null +++ b/packages/shop-mobile-expo/metro.config.js @@ -0,0 +1,29 @@ +/** + * ? Metro configuration for React Native + * https://github.com/facebook/react-native + * + * @format + */ +const defaultSourceExts = + require('metro-config/src/defaults/defaults').sourceExts; +module.exports = { + transformer: { + getTransformOptions: async () => ({ + transform: { + experimentalImportSupport: false, + inlineRequires: true, + }, + }), + }, + resolver: { + // ? Add cjs files extention support + sourceExts: process.env.RN_SRC_EXT + ? [ + ...process.env.RN_SRC_EXT.split(',').concat( + defaultSourceExts, + ), + 'cjs', + ] + : [...defaultSourceExts, 'cjs'], + }, +}; diff --git a/packages/shop-mobile-expo/package.json b/packages/shop-mobile-expo/package.json new file mode 100644 index 0000000..a0d54b8 --- /dev/null +++ b/packages/shop-mobile-expo/package.json @@ -0,0 +1,114 @@ +{ + "name": "@ever-platform/shop-mobile-expo", + "version": "0.4.3", + "description": "Ever Shop Mobile App built with Expo (ReactNative)", + "license": "GPL-3.0", + "homepage": "https://ever.co/", + "repository": { + "type": "git", + "url": "https://github.com/ever-co/ever-demand.git" + }, + "bugs": { + "url": "https://github.com/ever-co/ever-demand/issues" + }, + "private": true, + "author": { + "name": "Ever Co. LTD", + "email": "ever@ever.co", + "url": "https://ever.co" + }, + "main": "App.tsx", + "scripts": { + "config": "cross-env NODE_ENV=development NODE_OPTIONS=--max_old_space_size=4096 yarn ts-node ./scripts/configure.ts", + "config:dev": "cross-env NODE_ENV=development NODE_OPTIONS=--max_old_space_size=4096 yarn run config -- --environment=dev", + "config:prod": "cross-env NODE_ENV=production NODE_OPTIONS=--max_old_space_size=4096 yarn run config -- --environment=prod", + "start": "expo start", + "android": "expo start --android", + "ios": "expo start --ios", + "web": "expo start --web", + "eject": "expo eject", + "lint": "eslint . --ext .js,.jsx,.ts,.tsx", + "test": "jest" + }, + "dependencies": { + "@apollo/client": "^3.5.8", + "@react-native-async-storage/async-storage": "~1.15.0", + "@react-native-community/netinfo": "7.1.3", + "@react-navigation/drawer": "^5.12.9", + "@react-navigation/native": "6.0.6", + "@react-navigation/native-stack": "6.2.5", + "@reduxjs/toolkit": "1.7.1", + "expo": "~44.0.0", + "expo-app-loading": "~1.3.0", + "expo-font": "~10.0.4", + "expo-location": "~14.0.1", + "expo-status-bar": "~1.2.0", + "expo-system-ui": "~1.1.0", + "expo-updates": "~0.11.6", + "graphql": "^16.3.0", + "lodash": "^4.17.21", + "lodash.debounce": "^4.0.8", + "react": "17.0.2", + "react-dom": "17.0.1", + "react-native": "0.64.3", + "react-native-flash-message": "^0.2.1", + "react-native-gesture-handler": "~2.1.0", + "react-native-global-props": "1.1.5", + "react-native-maps": "0.29.4", + "react-native-pager-view": "5.4.9", + "react-native-paper": "4.11.2", + "react-native-reanimated": "~2.3.1", + "react-native-safe-area-context": "3.3.2", + "react-native-screens": "~3.10.1", + "react-native-web": "0.17.1", + "react-redux": "7.2.6", + "redux-logger": "^3.0.6", + "validate.js": "^0.13.1" + }, + "devDependencies": { + "@babel/core": "^7.12.9", + "@babel/runtime": "^7.17.0", + "@react-native-community/eslint-config": "^2.0.0", + "@types/jest": "^26.0.23", + "@types/react-native": "^0.66.16", + "@types/react-native-global-props": "^1.1.1", + "@types/react-test-renderer": "^17.0.1", + "@types/redux-logger": "^3.0.9", + "@typescript-eslint/eslint-plugin": "^5.7.0", + "@typescript-eslint/parser": "^5.7.0", + "babel-jest": "^26.6.3", + "cross-env": "^7.0.3", + "dotenv": "^16.0.0", + "envalid": "^7.2.2", + "eslint": "^7.14.0", + "jest": "^26.6.3", + "jest-expo": "^44.0.1", + "metro-config": "^0.67.0", + "metro-react-native-babel-preset": "^0.66.2", + "react-test-renderer": "^17.0.2", + "ts-node": "^10.5.0", + "typescript": "^4.4.4", + "uuid": "^8.3.2", + "yargs": "^17.3.1" + }, + "resolutions": { + "@types/react": "^17" + }, + "jest": { + "preset": "jest-expo", + "moduleFileExtensions": [ + "ts", + "tsx", + "js", + "jsx", + "json", + "node" + ] + }, + "engines": { + "node": ">=14.4.0", + "yarn": ">=1.13.0" + }, + "production": false, + "snyk": false +} diff --git a/packages/shop-mobile-expo/scripts/configure.ts b/packages/shop-mobile-expo/scripts/configure.ts new file mode 100644 index 0000000..29dc060 --- /dev/null +++ b/packages/shop-mobile-expo/scripts/configure.ts @@ -0,0 +1,193 @@ +// NOTE: do NOT ever put here any secure settings! (e.g. Secret Keys) +// We are using dotenv (.env) for consistency with other Platform projects +// This is Expo app and all settings will be loaded into the client browser! + +import { writeFile, unlinkSync } from 'fs'; +import { env } from './env'; +import { argv } from 'yargs'; +import { production } from '../package.json'; + +const isProd: boolean = argv.environment === 'prod' || production; + +if (!env.GOOGLE_MAPS_API_KEY) { + console.warn( + 'WARNING: No Google Maps API Key defined in the .env. Google Maps may not be visible!', + ); +} + +if (!env.STRIPE_PUBLISHABLE_KEY) { + console.warn( + 'WARNING: No Stripe Publishable Key defined in the .env. Stripe payments may not be available!', + ); +} + +const envFileContent = `// NOTE: Auto-generated file +// The file contents for the current environment will overwrite these during build. +// The build system defaults to the dev environment which uses 'environment.ts', but if you do +// 'ng build --env=prod' then 'environment.prod.ts' will be used instead. +// The list of which env maps to which file can be found in '.angular-cli.json'. + +import type Environment from './model'; + +const environment: Environment = { + // TODO: Add more descriptive comments. + /** + * App default environment + * + * @type boolean + */ + PRODUCTION: false, + + /** + * App version + * + * @type string + */ + VERSION: '1.0.0', + + /** + * @type 'slides' | 'list' + */ + PRODUCTS_VIEW_TYPE: 'slides', + + /** + * Order info behavior type + * + * @type 'popup' | 'page' + */ + ORDER_INFO_TYPE: 'page', + + COMPANY_NAME: 'Ever Co. LTD', + + /** + * Contain images url + */ + IMAGE_URL: { + INVITE_BY_CODE_LOGO: 'assets/imgs/ever-logo.svg', + NO_INTERNET_LOGO: 'assets/imgs/logo.png', + MAP_MERCHANT_ICON: + 'http://maps.google.com/mapfiles/kml/pal3/icon21.png', + MAP_USER_ICON: 'http://maps.google.com/mapfiles/kml/pal3/icon48.png', + MAP_CARRIER_ICON: 'http://maps.google.com/mapfiles/kml/pal4/icon54.png', + }, + + GOOGLE: { + MAPS_API_KEY: '', + ANALYTICS_API_KEY: '', + }, + + FAKE_UUID: 'ceffd77c-8cc1-47ac-b9b8-889f057aba3d', + + // Not secret MixPanel Token + MIXPANEL_API_KEY: '', + + LANGUAGE: { + LANG: 'ENGLISH', + LOCALE: 'ENGLISH', + }, + + DELIVERY_TIME: { + MIN: 30, + MAX: 60, + }, + + SUPPORT_NUMBER: '0888888888', + + STRIP: { + PUBLISHABLE_KEY: '', + POP_UP_LOGO: + 'https://bitbucket-assetroot.s3.amazonaws.com/c/photos/2016/Jan/30/1263967991-1-everbie-avatar.png', + }, + + COORDINATE: { + LATITUDE: 42.6459136, + LONGITUDE: 23.3332736, + }, + + /** + * Contain app endpoints + * + * _🚧 warning: if you're using a AVD (Android Virtual Device)_ + * _and localhost doesn't works, replace it with **10.0.2.2**_ + */ + ENDPOINT: { + GQL: 'http://localhost:8443/graphql', + GQL_SUBSCRIPTIONS: 'ws://localhost:2086/subscriptions', + SERVICES: 'http://localhost:5500', + HTTPS_SERVICES: 'https://localhost:2087', + API_FILE_UPLOAD: 'https://api.cloudinary.com/v1_1/evereq/upload', + }, + + FAKE_INVITE: { + ID: '1ae9d04f9010d834f8906881', + CITY: 'Sofia', + POSTCODE: '1700', + ADDRESS: 'Simeonovsko shose', + HOUSE: '104', + CREATED_AT: '2018-05-02T14:50:55.658Z', + UPDATED_AT: '2018-05-02T14:50:55.658Z', + APARTMENT: '3', + CODE: 8321, + COUNTRY_ID: 21, + }, + + /** + * For maintenance micro service + */ + SETTINGS: { + APP_TYPE: 'shop-mobile', + MAINTENANCE_API_URL: '', + }, + + /** + * For "single" merchant (multiple branches) + */ + MERCHANT_IDS: [], + + SHOPPING_CART: false, +}; + +export default environment; + +/* + * In development mode, to ignore zone related error stack frames such as + * 'zone.run', 'zoneDelegate.invokeTask' for easier debugging, you can + * import the following file, but please comment it out in isProd mode + * because it will have performance impact when throw error + */ +`; + +const envFileDest: string = isProd ? 'environment.prod.ts' : 'environment.ts'; +const envFileDestOther: string = !isProd + ? 'environment.prod.ts' + : 'environment.ts'; + +// we always want first to remove old generated files (one of them is not needed for current build) +try { + unlinkSync('./src/environments/environment.ts'); +} catch {} +try { + unlinkSync('./src/environments/environment.prod.ts'); +} catch {} + +writeFile(`./src/environments/${envFileDest}`, envFileContent, function (err) { + if (err) { + console.log(err); + } else { + console.log(`Generated ts environment file: ${envFileDest}`); + } +}); + +writeFile( + `./src/environments/${envFileDestOther}`, + envFileContent, + function (err) { + if (err) { + console.log(err); + } else { + console.log( + `Generated Second environment file: ${envFileDestOther}`, + ); + } + }, +); diff --git a/packages/shop-mobile-expo/scripts/env.ts b/packages/shop-mobile-expo/scripts/env.ts new file mode 100644 index 0000000..b731691 --- /dev/null +++ b/packages/shop-mobile-expo/scripts/env.ts @@ -0,0 +1,210 @@ +// NOTE: do NOT ever put here any secure settings! (e.g. Secret Keys) +// We are using dotenv (.env) for consistency with other Platform projects +// This is Expo app and all settings will be loaded into the client browser! + +require('dotenv').config(); + +import { cleanEnv, num, str, bool, makeValidator, CleanOptions } from 'envalid'; +import { v4 as uuid } from 'uuid'; + +export type Env = Readonly<{ + PRODUCTION: boolean; + + VERSION: string; + + // 'slides' | 'list' + PRODUCTS_VIEW_TYPE: string; + + // 'popup' or 'page' + ORDER_INFO_TYPE: string; + + API_FILE_UPLOAD_URL: string; + + INVITE_BY_CODE_LOGO: string; + + NO_INTERNET_LOGO: string; + + COMPANY_NAME: string; + + GOOGLE_MAPS_API_KEY: string; + + GOOGLE_ANALYTICS_API_KEY: string; + + FAKE_UUID: string; + + // Not secret MixPanel Token + MIXPANEL_API_KEY: string; + + DEFAULT_LANGUAGE: string; + + DEFAULT_LOCALE: string; + + DELIVERY_TIME_MIN: number; + + DELIVERY_TIME_MAX: number; + + SUPPORT_NUMBER: string; + + STRIPE_PUBLISHABLE_KEY: string; + + // TODO: replace logo with recent one! + STRIPE_POP_UP_LOGO: string; + + MAP_MERCHANT_ICON_LINK: string; + + MAP_USER_ICON_LINK: string; + + MAP_CARRIER_ICON_LINK: string; + + DEFAULT_LATITUDE: number; + DEFAULT_LONGITUDE: number; + + // Graphql endpoints for apollo services + GQL_ENDPOINT: string; + + GQL_SUBSCRIPTIONS_ENDPOINT: string; + + SERVICES_ENDPOINT: string; + + HTTPS_SERVICES_ENDPOINT: string; + + FAKE_INVITE_ID: string; + + FAKE_INVITE_CITY: string; + + FAKE_INVITE_POSTCODE: string; + + FAKE_INVITE_ADDRESS: string; + + FAKE_INVITE_HOUSE: string; + + FAKE_INVITE_CREATED_AT: string; + + FAKE_INVITE_UPDATED_AT: string; + + FAKE_INVITE_APARTMENT: string; + + FAKE_INVITE_CODE: number; + + FAKE_INVITE_COUNTRY_ID: number; + + // For maintenance micro service + SETTINGS_APP_TYPE?: string; + + SETTINGS_MAINTENANCE_API_URL?: string; + + // For "single" merchant (multiple branches) + MERCHANT_IDS?: string[]; + + WEB_CONCURRENCY: number; + + WEB_MEMORY: number; + + PORT: number; + + SHOPPING_CART: boolean; +}>; + +// TODO: validate better merchantIDs +const merchantIDs: any = makeValidator((x) => x); + +const opt: CleanOptions = {}; + +export const env = cleanEnv( + process.env, + { + PRODUCTION: bool({ default: false }), + + VERSION: str({ default: '1.0.0' }), + + // 'slides' | 'list' + PRODUCTS_VIEW_TYPE: str({ default: 'slides' }), + + // 'popup' or 'page' + ORDER_INFO_TYPE: str({ default: 'page' }), + + API_FILE_UPLOAD_URL: str({ + default: 'https://api.cloudinary.com/v1_1/evereq/upload', + }), + + INVITE_BY_CODE_LOGO: str({ default: 'assets/imgs/ever-logo.svg' }), + NO_INTERNET_LOGO: str({ default: 'assets/imgs/logo.png' }), + + COMPANY_NAME: str({ default: 'Ever Co. LTD' }), + + GOOGLE_MAPS_API_KEY: str({ default: '' }), + + GOOGLE_ANALYTICS_API_KEY: str({ default: '' }), + FAKE_UUID: str({ default: uuid() }), + + // Not secret MixPanel Token + MIXPANEL_API_KEY: str({ default: '' }), + + DEFAULT_LANGUAGE: str({ default: 'en-US' }), + + DEFAULT_LOCALE: str({ default: 'en-US' }), + + DELIVERY_TIME_MIN: num({ default: 30 }), + DELIVERY_TIME_MAX: num({ default: 60 }), + + SUPPORT_NUMBER: str({ default: '0888888888' }), + + STRIPE_PUBLISHABLE_KEY: str({ default: '' }), + + // TODO: replace logo with recent one! + STRIPE_POP_UP_LOGO: str({ + default: + 'https://bitbucket-assetroot.s3.amazonaws.com/c/photos/2016/Jan/30/1263967991-1-everbie-avatar.png', + }), + + MAP_MERCHANT_ICON_LINK: str({ + default: 'http://maps.google.com/mapfiles/kml/pal3/icon21.png', + }), + MAP_USER_ICON_LINK: str({ + default: 'http://maps.google.com/mapfiles/kml/pal3/icon48.png', + }), + MAP_CARRIER_ICON_LINK: str({ + default: 'http://maps.google.com/mapfiles/kml/pal4/icon54.png', + }), + + DEFAULT_LATITUDE: num({ default: 42.6459136 }), + DEFAULT_LONGITUDE: num({ default: 23.3332736 }), + + // Graphql endpoints for apollo services + GQL_ENDPOINT: str({ default: 'http://localhost:8443/graphql' }), + GQL_SUBSCRIPTIONS_ENDPOINT: str({ + default: 'ws://localhost:2086/subscriptions', + }), + SERVICES_ENDPOINT: str({ default: 'http://localhost:5500' }), + HTTPS_SERVICES_ENDPOINT: str({ default: 'https://localhost:2087' }), + + FAKE_INVITE_ID: str({ default: '1ae9d04f9010d834f8906881' }), + FAKE_INVITE_CITY: str({ default: 'Sofia' }), + FAKE_INVITE_POSTCODE: str({ default: '1700' }), + FAKE_INVITE_ADDRESS: str({ default: 'Simeonovsko shose' }), + FAKE_INVITE_HOUSE: str({ default: '104' }), + FAKE_INVITE_CREATED_AT: str({ default: '2018-05-02T14:50:55.658Z' }), + FAKE_INVITE_UPDATED_AT: str({ default: '2018-05-02T14:50:55.658Z' }), + FAKE_INVITE_APARTMENT: str({ default: '3' }), + FAKE_INVITE_CODE: num({ default: 8321 }), + FAKE_INVITE_COUNTRY_ID: num({ default: 21 }), + + // For maintenance micro service. Ever maintenance API URL: https://maintenance.ever.co/status + SETTINGS_APP_TYPE: str({ default: 'shop-mobile' }), + SETTINGS_MAINTENANCE_API_URL: str({ + default: '', + }), + + // For "single" merchant (multiple branches) + MERCHANT_IDS: merchantIDs({ + default: [ + // Add existing merchant ids + ], + }), + WEB_CONCURRENCY: num({ default: 1 }), + WEB_MEMORY: num({ default: 2048 }), + PORT: num({ default: 4201 }), + SHOPPING_CART: bool({ default: false }), + }, + opt, +); diff --git a/packages/shop-mobile-expo/src/App.tsx b/packages/shop-mobile-expo/src/App.tsx new file mode 100644 index 0000000..deb02a6 --- /dev/null +++ b/packages/shop-mobile-expo/src/App.tsx @@ -0,0 +1,39 @@ +import 'react-native-gesture-handler'; +import React from 'react'; +import AppLoading from 'expo-app-loading'; +import { useFonts } from 'expo-font'; + +// ROUTER +import Router from './router'; + +// COMPONENTS +import ReduxProvider from './components/Providers/Redux'; +import ApolloProvider from './components/Providers/Apollo'; +import PaperProvider from './components/Providers/Paper'; +import AppProvider from './components/Providers/App'; + +export default function App() { + const [fontsLoaded] = useFonts({ + 'Nunito-ExtraLight': require('./assets/fonts/Nunito/Nunito-ExtraLight.ttf'), + 'Nunito-Light': require('./assets/fonts/Nunito/Nunito-Light.ttf'), + 'Nunito-Regular': require('./assets/fonts/Nunito/Nunito-Regular.ttf'), + 'Nunito-SemiBold': require('./assets/fonts/Nunito/Nunito-SemiBold.ttf'), + 'Nunito-Bold': require('./assets/fonts/Nunito/Nunito-Bold.ttf'), + 'Nunito-Black': require('./assets/fonts/Nunito/Nunito-Black.ttf'), + 'Lobster-Regular': require('./assets/fonts/Lobster/Lobster-Regular.ttf'), + }); + + return !fontsLoaded ? ( + + ) : ( + + + + + + + + + + ); +} diff --git a/packages/shop-mobile-expo/src/assets/fonts/Lobster/Lobster-Regular.ttf b/packages/shop-mobile-expo/src/assets/fonts/Lobster/Lobster-Regular.ttf new file mode 100644 index 0000000..21c0073 Binary files /dev/null and b/packages/shop-mobile-expo/src/assets/fonts/Lobster/Lobster-Regular.ttf differ diff --git a/packages/shop-mobile-expo/src/assets/fonts/Lobster/OFL.txt b/packages/shop-mobile-expo/src/assets/fonts/Lobster/OFL.txt new file mode 100644 index 0000000..a411430 --- /dev/null +++ b/packages/shop-mobile-expo/src/assets/fonts/Lobster/OFL.txt @@ -0,0 +1,93 @@ +Copyright 2010 The Lobster Project Authors (https://github.com/impallari/The-Lobster-Font), with Reserved Font Name "Lobster". + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/packages/shop-mobile-expo/src/assets/fonts/Nunito/Nunito-Black.ttf b/packages/shop-mobile-expo/src/assets/fonts/Nunito/Nunito-Black.ttf new file mode 100644 index 0000000..e4fe5d0 Binary files /dev/null and b/packages/shop-mobile-expo/src/assets/fonts/Nunito/Nunito-Black.ttf differ diff --git a/packages/shop-mobile-expo/src/assets/fonts/Nunito/Nunito-Bold.ttf b/packages/shop-mobile-expo/src/assets/fonts/Nunito/Nunito-Bold.ttf new file mode 100644 index 0000000..368468c Binary files /dev/null and b/packages/shop-mobile-expo/src/assets/fonts/Nunito/Nunito-Bold.ttf differ diff --git a/packages/shop-mobile-expo/src/assets/fonts/Nunito/Nunito-ExtraBold.ttf b/packages/shop-mobile-expo/src/assets/fonts/Nunito/Nunito-ExtraBold.ttf new file mode 100644 index 0000000..8f5e1f6 Binary files /dev/null and b/packages/shop-mobile-expo/src/assets/fonts/Nunito/Nunito-ExtraBold.ttf differ diff --git a/packages/shop-mobile-expo/src/assets/fonts/Nunito/Nunito-ExtraLight.ttf b/packages/shop-mobile-expo/src/assets/fonts/Nunito/Nunito-ExtraLight.ttf new file mode 100644 index 0000000..251c471 Binary files /dev/null and b/packages/shop-mobile-expo/src/assets/fonts/Nunito/Nunito-ExtraLight.ttf differ diff --git a/packages/shop-mobile-expo/src/assets/fonts/Nunito/Nunito-Light.ttf b/packages/shop-mobile-expo/src/assets/fonts/Nunito/Nunito-Light.ttf new file mode 100644 index 0000000..ad56724 Binary files /dev/null and b/packages/shop-mobile-expo/src/assets/fonts/Nunito/Nunito-Light.ttf differ diff --git a/packages/shop-mobile-expo/src/assets/fonts/Nunito/Nunito-Regular.ttf b/packages/shop-mobile-expo/src/assets/fonts/Nunito/Nunito-Regular.ttf new file mode 100644 index 0000000..c8c90b7 Binary files /dev/null and b/packages/shop-mobile-expo/src/assets/fonts/Nunito/Nunito-Regular.ttf differ diff --git a/packages/shop-mobile-expo/src/assets/fonts/Nunito/Nunito-SemiBold.ttf b/packages/shop-mobile-expo/src/assets/fonts/Nunito/Nunito-SemiBold.ttf new file mode 100644 index 0000000..3a30359 Binary files /dev/null and b/packages/shop-mobile-expo/src/assets/fonts/Nunito/Nunito-SemiBold.ttf differ diff --git a/packages/shop-mobile-expo/src/assets/img/appIcons/adaptive-icon.png b/packages/shop-mobile-expo/src/assets/img/appIcons/adaptive-icon.png new file mode 100644 index 0000000..9711566 Binary files /dev/null and b/packages/shop-mobile-expo/src/assets/img/appIcons/adaptive-icon.png differ diff --git a/packages/shop-mobile-expo/src/assets/img/appIcons/favicon.png b/packages/shop-mobile-expo/src/assets/img/appIcons/favicon.png new file mode 100644 index 0000000..4abcb5d Binary files /dev/null and b/packages/shop-mobile-expo/src/assets/img/appIcons/favicon.png differ diff --git a/packages/shop-mobile-expo/src/assets/img/appIcons/icon.png b/packages/shop-mobile-expo/src/assets/img/appIcons/icon.png new file mode 100644 index 0000000..7c1323a Binary files /dev/null and b/packages/shop-mobile-expo/src/assets/img/appIcons/icon.png differ diff --git a/packages/shop-mobile-expo/src/assets/img/appIcons/splash.png b/packages/shop-mobile-expo/src/assets/img/appIcons/splash.png new file mode 100644 index 0000000..f6128d9 Binary files /dev/null and b/packages/shop-mobile-expo/src/assets/img/appIcons/splash.png differ diff --git a/packages/shop-mobile-expo/src/assets/img/ever/ever-background-gradient.png b/packages/shop-mobile-expo/src/assets/img/ever/ever-background-gradient.png new file mode 100644 index 0000000..1fa95ff Binary files /dev/null and b/packages/shop-mobile-expo/src/assets/img/ever/ever-background-gradient.png differ diff --git a/packages/shop-mobile-expo/src/assets/img/ever/ever.png b/packages/shop-mobile-expo/src/assets/img/ever/ever.png new file mode 100644 index 0000000..51dfd56 Binary files /dev/null and b/packages/shop-mobile-expo/src/assets/img/ever/ever.png differ diff --git a/packages/shop-mobile-expo/src/assets/img/ever/image_placeholder.png b/packages/shop-mobile-expo/src/assets/img/ever/image_placeholder.png new file mode 100644 index 0000000..79a48dd Binary files /dev/null and b/packages/shop-mobile-expo/src/assets/img/ever/image_placeholder.png differ diff --git a/packages/shop-mobile-expo/src/assets/img/ever/login_back.png b/packages/shop-mobile-expo/src/assets/img/ever/login_back.png new file mode 100644 index 0000000..565cd8e Binary files /dev/null and b/packages/shop-mobile-expo/src/assets/img/ever/login_back.png differ diff --git a/packages/shop-mobile-expo/src/assets/img/ever/logo.png b/packages/shop-mobile-expo/src/assets/img/ever/logo.png new file mode 100644 index 0000000..616fb2d Binary files /dev/null and b/packages/shop-mobile-expo/src/assets/img/ever/logo.png differ diff --git a/packages/shop-mobile-expo/src/assets/img/ever/reviews.png b/packages/shop-mobile-expo/src/assets/img/ever/reviews.png new file mode 100644 index 0000000..1d3e1d8 Binary files /dev/null and b/packages/shop-mobile-expo/src/assets/img/ever/reviews.png differ diff --git a/packages/shop-mobile-expo/src/assets/img/ever/static.png b/packages/shop-mobile-expo/src/assets/img/ever/static.png new file mode 100644 index 0000000..c55ceeb Binary files /dev/null and b/packages/shop-mobile-expo/src/assets/img/ever/static.png differ diff --git a/packages/shop-mobile-expo/src/assets/ts/styles.ts b/packages/shop-mobile-expo/src/assets/ts/styles.ts new file mode 100644 index 0000000..beccdd7 --- /dev/null +++ b/packages/shop-mobile-expo/src/assets/ts/styles.ts @@ -0,0 +1,791 @@ +import { StyleSheet, Dimensions, Platform } from 'react-native'; + +export const isIOS = Platform.OS === 'ios'; +export const isAndroid = Platform.OS === 'android'; + +export const CONSTANT_SIZE = { + FONT_SIZE_SM: 11, + FONT_SIZE: 14, + FONT_SIZE_MD: 17, + FONT_SIZE_LG: 20, + FONT_SIZE_XLG: 23, + NO_SPACE: 0, + SPACE_SM: 8, + SPACE: 16, + SPACE_MD: 24, + SPACE_LG: 32, + SPACE_XLG: 40, + BOTTOM_NAVBAR_HEIGHT: 80, + STATUS_BAR_HEIGHT: 24, + SCREEN_HEIGHT: Dimensions.get('screen').height, + SCREEN_WIDTH: Dimensions.get('screen').width, + WINDOW_HEIGHT: Dimensions.get('window').height, + WINDOW_WIDTH: Dimensions.get('window').width, + DRAWER_HEADER_HEIGHT: 56, +}; + +export const CONSTANT_COLOR = { + primary: '#2A2C39', + primaryLight: '#444654', + primaryHightLight: '#676977', + light: '#f4f5f8', + dark: '#222428', + secondary: '#bd4742', + secondaryLight: '#d36b67', + secondaryHighLight: '#f27f7b', + gray: '#8f96a7', + grayLight: '#b3bbce', + grayHighLight: '#dce3f4', + + facebook: '#3B5998', + google: '#DD4B39', + tertiary: '#7044ff', + success: '#10dc60', + danger: '#f04141', + warning: '#ffce00', + white: '#FFF', + input: '#edeef2', +}; + +export const GLOBAL_STYLE = StyleSheet.create({ + FF_NunitoExtraLight: { + fontFamily: 'Nunito-ExtraLight', + }, + FF_NunitoLight: { + fontFamily: 'Nunito-Light', + }, + FF_Nunito: { + fontFamily: 'Nunito-Regular', + }, + FF_NunitoSemiBold: { + fontFamily: 'Nunito-SemiBold', + }, + FF_NunitoBold: { + fontFamily: 'Nunito-Bold', + }, + FF_NunitoBlack: { + fontFamily: 'Nunito-Black', + }, + FF_Lobster: { + fontFamily: 'Lobster-Regular', + }, + screen: { + flex: 1, + backgroundColor: CONSTANT_COLOR.primary, + }, + screenStatic: { + height: CONSTANT_SIZE.SCREEN_HEIGHT, + backgroundColor: CONSTANT_COLOR.primary, + }, + screenStaticNav: { + flex: 1, + marginBottom: CONSTANT_SIZE.BOTTOM_NAVBAR_HEIGHT, + backgroundColor: CONSTANT_COLOR.primary, + }, + bgPrimary: { + backgroundColor: CONSTANT_COLOR.primary, + }, + bgPrimaryLight: { + backgroundColor: CONSTANT_COLOR.primaryLight, + }, + bgSecondary: { + backgroundColor: CONSTANT_COLOR.secondary, + }, + bgSecondaryLight: { + backgroundColor: CONSTANT_COLOR.secondaryLight, + }, + bgLight: { + backgroundColor: CONSTANT_COLOR.light, + }, + bgDanger: { + backgroundColor: CONSTANT_COLOR.danger, + }, + bgSuccess: { + backgroundColor: CONSTANT_COLOR.success, + }, + bgTransparent: { + backgroundColor: 'transparent', + }, + txtPrimary: { + color: CONSTANT_COLOR.primary, + }, + txtPrimaryLight: { + color: CONSTANT_COLOR.primaryLight, + }, + txtSecondary: { + color: CONSTANT_COLOR.secondary, + }, + txtSecondaryLight: { + color: CONSTANT_COLOR.secondaryLight, + }, + txtSuccess: { + color: CONSTANT_COLOR.success, + }, + txtDanger: { + color: CONSTANT_COLOR.danger, + }, + txtCenter: { + textAlign: 'center', + }, + txtUpper: { + textTransform: 'uppercase', + }, + txtLower: { + textTransform: 'lowercase', + }, + txtCapitalize: { + textTransform: 'capitalize', + }, + fontBold: { + fontWeight: 'bold', + }, + fontItalic: { + fontStyle: 'italic', + }, + centered: { + justifyContent: 'center', + alignItems: 'center', + }, + container: { + marginHorizontal: 20, + paddingHorizontal: 10, + }, + content: {}, + dNone: { + display: 'none', + }, + section: { + backgroundColor: CONSTANT_COLOR.light, + padding: 10, + marginBottom: 10, + }, + titleSection: { + color: CONSTANT_COLOR.secondary, + marginBottom: 10, + textTransform: 'uppercase', + fontWeight: 'bold', + }, + w100: { + width: '100%', + }, + w75: { + width: '75%', + }, + w50: { + width: '50%', + }, + w25: { + width: '25%', + }, + h100: { + height: '100%', + }, + h75: { + height: '75%', + }, + h50: { + height: '50%', + }, + h25: { + height: '25%', + }, + m0: { + margin: 0, + }, + m1: { + margin: 5, + }, + m2: { + margin: 10, + }, + m3: { + margin: 15, + }, + m4: { + margin: 20, + }, + m5: { + margin: 25, + }, + mt0: { + marginTop: 0, + }, + mt1: { + marginTop: 5, + }, + mt2: { + marginTop: 10, + }, + mt3: { + marginTop: 15, + }, + mt4: { + marginTop: 20, + }, + mt5: { + marginTop: 25, + }, + mb0: { + marginBottom: 0, + }, + mb1: { + marginBottom: 5, + }, + mb2: { + marginBottom: 10, + }, + mb3: { + marginBottom: 15, + }, + mb4: { + marginBottom: 20, + }, + mb5: { + marginBottom: 25, + }, + ml0: { + marginLeft: 0, + }, + ml1: { + marginLeft: 5, + }, + ml2: { + marginLeft: 10, + }, + ml3: { + marginLeft: 15, + }, + ml4: { + marginLeft: 20, + }, + ml5: { + marginLeft: 25, + }, + mr0: { + marginRight: 0, + }, + mr1: { + marginRight: 5, + }, + mr2: { + marginRight: 10, + }, + mr3: { + marginRight: 15, + }, + mr4: { + marginRight: 20, + }, + mr5: { + marginRight: 25, + }, + my0: { + marginVertical: 0, + }, + my1: { + marginVertical: 5, + }, + my2: { + marginVertical: 10, + }, + my3: { + marginVertical: 15, + }, + my4: { + marginVertical: 20, + }, + my5: { + marginVertical: 25, + }, + mx0: { + marginHorizontal: 0, + }, + mx1: { + marginHorizontal: 5, + }, + mx2: { + marginHorizontal: 10, + }, + mx3: { + marginHorizontal: 15, + }, + mx4: { + marginHorizontal: 20, + }, + mx5: { + marginHorizontal: 25, + }, + p0: { + padding: 0, + }, + p1: { + padding: 5, + }, + p2: { + padding: 10, + }, + p3: { + padding: 15, + }, + p4: { + padding: 20, + }, + p5: { + padding: 25, + }, + pt0: { + paddingTop: 0, + }, + pt1: { + paddingTop: 5, + }, + pt2: { + paddingTop: 10, + }, + pt3: { + paddingTop: 15, + }, + pt4: { + paddingTop: 20, + }, + pt5: { + paddingTop: 25, + }, + pb0: { + paddingBottom: 0, + }, + pb1: { + paddingBottom: 5, + }, + pb2: { + paddingBottom: 10, + }, + pb3: { + paddingBottom: 15, + }, + pb4: { + paddingBottom: 20, + }, + pb5: { + paddingBottom: 25, + }, + pl0: { + paddingLeft: 0, + }, + pl1: { + paddingLeft: 5, + }, + pl2: { + paddingLeft: 10, + }, + pl3: { + paddingLeft: 15, + }, + pl4: { + paddingLeft: 20, + }, + pl5: { + paddingLeft: 25, + }, + pr0: { + paddingRight: 0, + }, + pr1: { + paddingRight: 5, + }, + pr2: { + paddingRight: 10, + }, + pr3: { + paddingRight: 15, + }, + pr4: { + paddingRight: 20, + }, + pr5: { + paddingRight: 25, + }, + py0: { + paddingVertical: 0, + }, + py1: { + paddingVertical: 5, + }, + py2: { + paddingVertical: 10, + }, + py3: { + paddingVertical: 15, + }, + py4: { + paddingVertical: 20, + }, + py5: { + paddingVertical: 25, + }, + px0: { + paddingHorizontal: 0, + }, + px1: { + paddingHorizontal: 5, + }, + px2: { + paddingHorizontal: 10, + }, + px3: { + paddingHorizontal: 15, + }, + px4: { + paddingHorizontal: 20, + }, + px5: { + paddingHorizontal: 25, + }, + imgCover: { + height: undefined, + width: undefined, + resizeMode: 'cover', + flex: 1, + }, + noShadow: { + shadowColor: 'transparent', + shadowOffset: { width: 0, height: 0 }, + shadowRadius: 0, + shadowOpacity: 0, + elevation: 0, + }, + shadowSm: { + shadowColor: CONSTANT_COLOR.dark, + shadowOffset: { width: 0, height: 1 }, + shadowRadius: 5, + shadowOpacity: 0.2, + elevation: 2, + }, + shadow: { + shadowColor: CONSTANT_COLOR.dark, + shadowOffset: { width: 1, height: 1.5 }, + shadowRadius: 5, + shadowOpacity: 0.5, + elevation: 5, + }, + shadowLg: { + shadowColor: isIOS ? '#000' : CONSTANT_COLOR.dark, + shadowOffset: { + width: isIOS ? 0 : 2, + height: 2, + }, + shadowOpacity: isIOS ? 0.25 : 0.7, + shadowRadius: isIOS ? 3.84 : 5, + + elevation: 8.5, + }, + rounded0: { + borderRadius: 0, + }, + roundedSm: { + borderRadius: 5, + }, + rounded: { + borderRadius: 10, + }, + roundedMd: { + borderRadius: 20, + }, + roundedLg: { + borderRadius: 30, + }, + noBorder: { + borderColor: 'transparent', + borderWidth: 0, + }, + borderSm: { + borderColor: CONSTANT_COLOR.gray, + borderWidth: 0.5, + }, + border: { + borderColor: CONSTANT_COLOR.gray, + borderWidth: 1, + }, + borderMd: { + borderColor: CONSTANT_COLOR.gray, + borderWidth: 2, + }, + borderLg: { + borderColor: CONSTANT_COLOR.gray, + borderWidth: 4, + }, + separator: { + borderTopColor: CONSTANT_COLOR.gray, + borderTopWidth: 1, + marginVertical: 10, + padding: 0, + }, + overlay: { + position: 'absolute', + flex: 1, + height: '100%', + width: '100%', + justifyContent: 'center', + alignItems: 'center', + backgroundColor: 'rgba(0,0,0,0.3)', + }, + positionAbsolute: { + position: 'absolute', + }, + positionRelative: { + position: 'relative', + }, + positionReset: { + position: undefined, + }, + t0: { + top: 0, + }, + b0: { + bottom: 0, + }, + l0: { + left: 0, + }, + r0: { + right: 0, + }, + card: { + position: 'relative', + padding: 15, + borderRadius: 10, + backgroundColor: 'white', + }, + cardBtnClose: { + position: 'absolute', + right: 10, + }, + tabsContainer: { + backgroundColor: CONSTANT_COLOR.light, + flexDirection: 'row', + alignItems: 'stretch', + }, + tab: { + paddingVertical: 12, + borderBottomWidth: 3, + borderBottomColor: CONSTANT_COLOR.light, + flex: 1, + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'center', + }, + activeTab: { + borderBottomColor: CONSTANT_COLOR.primary, + }, + tabName: { + color: CONSTANT_COLOR.primary, + fontWeight: 'bold', + }, + tabIcon: { + marginRight: 4, + }, + selectContainer: { + borderBottomColor: CONSTANT_COLOR.primary, + borderBottomWidth: 1, + width: 130, + height: 35, + paddingHorizontal: 0, + alignItems: 'center', + }, + selectItem: { + width: '100%', + height: '100%', + color: CONSTANT_COLOR.gray, + fontSize: 15, + }, + statsContainer: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + marginHorizontal: 30, + marginVertical: 10, + }, + stat: { + alignItems: 'center', + justifyContent: 'center', + flex: 1, + }, + statAmount: { + color: CONSTANT_COLOR.gray, + fontSize: 18, + fontWeight: 'bold', + }, + statTitle: { + color: CONSTANT_COLOR.gray, + fontSize: 12, + fontWeight: '500', + textTransform: 'capitalize', + marginTop: 4, + }, + form: { + marginBottom: 32, + }, + field: { + marginBottom: 20, + }, + inputTitle: { + color: CONSTANT_COLOR.gray, + fontSize: 10, + textTransform: 'uppercase', + marginBottom: 0, + }, + inputGroup: { + flexDirection: 'row', + alignItems: 'stretch', + }, + appendTag: {}, + input: { + height: 40, + borderBottomWidth: 1, + borderBottomColor: CONSTANT_COLOR.primary, + color: CONSTANT_COLOR.gray, + fontSize: 15, + paddingHorizontal: 10, + }, + th: { + height: 40, + backgroundColor: CONSTANT_COLOR.light, + }, + column: { + flexDirection: 'column', + }, + row: { + flexDirection: 'row', + }, + alignBaseline: { alignItems: 'baseline' }, + alignCenter: { alignItems: 'center' }, + alignEnd: { alignItems: 'flex-end' }, + alignStart: { alignItems: 'flex-start' }, + alignStretch: { alignItems: 'stretch' }, + justifyCenter: { justifyContent: 'center' }, + justifyEnd: { justifyContent: 'flex-end' }, + justifyStart: { justifyContent: 'flex-start' }, + justifyAround: { justifyContent: 'space-around' }, + justifyBetween: { justifyContent: 'space-between' }, + justifyEvenly: { justifyContent: 'space-evenly' }, + inlineItems: { + flexDirection: 'row', + alignItems: 'center', + }, + justifyContentBetween: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + }, + flex1: { + flex: 1, + }, + flexWrap: { + flexWrap: 'wrap', + }, + tableBtn: { + padding: 5, + flexDirection: 'row', + justifyContent: 'center', + marginHorizontal: 5, + paddingHorizontal: 10, + borderRadius: 5, + }, + errorContainer: { + paddingVertical: 20, + alignItems: 'center', + justifyContent: 'center', + }, + errorMessage: { + color: CONSTANT_COLOR.primary, + fontSize: 13, + fontWeight: '400', + textAlign: 'center', + }, + btn: { + height: 45, + borderRadius: 5, + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'center', + borderWidth: 1, + borderColor: 'rgba(0,0,0,0)', + marginVertical: 5, + paddingHorizontal: 20, + }, + btnPrimary: { + backgroundColor: CONSTANT_COLOR.primary, + borderColor: CONSTANT_COLOR.primary, + }, + btnPrimaryLight: { + backgroundColor: CONSTANT_COLOR.primaryLight, + borderColor: CONSTANT_COLOR.primaryLight, + }, + btnSecondary: { + backgroundColor: CONSTANT_COLOR.secondary, + borderColor: CONSTANT_COLOR.secondary, + }, + btnTopLeft: { + position: 'absolute', + top: 30, + left: 20, + width: 40, + height: 40, + borderRadius: 40 / 2, + alignItems: 'center', + justifyContent: 'center', + backgroundColor: 'rgba(22, 22, 43, 0.2)', + }, + btnBottomRight: { + position: 'absolute', + bottom: 20, + right: 20, + width: 40, + height: 40, + borderRadius: 40 / 2, + alignItems: 'center', + justifyContent: 'center', + backgroundColor: 'rgba(22, 22, 43, 0.2)', + }, + accordionTitle: { + color: CONSTANT_COLOR.secondary, + }, + image: { + flex: 1, + height: undefined, + width: undefined, + resizeMode: 'cover', + }, + inputPicker: { + borderColor: CONSTANT_COLOR.input, + borderWidth: 1, + height: 30, + width: '100%', + color: CONSTANT_COLOR.gray, + borderRadius: 7, + display: 'flex', + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'space-between', + paddingHorizontal: 5, + }, + inputPickerLabel: { + fontSize: 12, + color: CONSTANT_COLOR.gray, + paddingRight: 0, + width: '85%', + }, + inputPickerIcon: { + display: 'flex', + flexDirection: 'row', + justifyContent: 'flex-end', + width: '15%', + }, + textarea: { + borderColor: CONSTANT_COLOR.grayLight, + borderWidth: 1, + borderRadius: 5, + overflow: 'hidden', + height: 100, + }, +}); diff --git a/packages/shop-mobile-expo/src/client/invite/argumentInterfaces.ts b/packages/shop-mobile-expo/src/client/invite/argumentInterfaces.ts new file mode 100644 index 0000000..e67a7f7 --- /dev/null +++ b/packages/shop-mobile-expo/src/client/invite/argumentInterfaces.ts @@ -0,0 +1,39 @@ +// TYPES +import type { MaybeType, ScalarsInterface } from '../../types/index'; +import type { GeolocationInterface, LocationInterface } from '../types'; + +// TODO: Add descriptive comments for types/interfaces + +export interface InviteByCodeInputInterface { + location: LocationInterface; + inviteCode: ScalarsInterface['String']; + firstName?: MaybeType; + lastName?: MaybeType; +} + +export interface InviteCreateInputInterface { + code?: MaybeType; + apartment: ScalarsInterface['String']; + geoLocation: GeolocationInterface; +} + +export interface InviteByLocationInputInterface { + countryId: ScalarsInterface['Int']; + city: ScalarsInterface['String']; + streetAddress: ScalarsInterface['String']; + house: ScalarsInterface['String']; + apartment: ScalarsInterface['String']; + postcode?: MaybeType; +} + +export interface QueryGetInviteByCodeArgsInterface { + info: InviteByCodeInputInterface; +} + +export interface QueryGetInviteByLocationArgsInterface { + info?: MaybeType; +} + +export interface CreateInviteByLocationMutationArgsInterface { + createInput: InviteCreateInputInterface; +} diff --git a/packages/shop-mobile-expo/src/client/invite/mutations.ts b/packages/shop-mobile-expo/src/client/invite/mutations.ts new file mode 100644 index 0000000..0b56e7d --- /dev/null +++ b/packages/shop-mobile-expo/src/client/invite/mutations.ts @@ -0,0 +1,32 @@ +import { gql, TypedDocumentNode } from '@apollo/client'; + +/** + * Mutation to add a new user + * + * @type TypedDocumentNode + */ +export const CREATE_INVITE_BY_LOCATION_MUTATION: TypedDocumentNode = gql` + mutation CreateInviteByLocationMutation($createInput: InviteCreateInput!) { + createInvite(createInput: $createInput) { + id + code + apartment + geoLocation { + id + createdAt + updatedAt + countryId + countryName + city + streetAddress + house + postcode + notes + coordinates { + lng + lat + } + } + } + } +`; diff --git a/packages/shop-mobile-expo/src/client/invite/queries.ts b/packages/shop-mobile-expo/src/client/invite/queries.ts new file mode 100644 index 0000000..95851dc --- /dev/null +++ b/packages/shop-mobile-expo/src/client/invite/queries.ts @@ -0,0 +1 @@ +// import { gql, TypedDocumentNode } from '@apollo/client'; diff --git a/packages/shop-mobile-expo/src/client/merchants/argumentInterfaces.ts b/packages/shop-mobile-expo/src/client/merchants/argumentInterfaces.ts new file mode 100644 index 0000000..9161d62 --- /dev/null +++ b/packages/shop-mobile-expo/src/client/merchants/argumentInterfaces.ts @@ -0,0 +1,30 @@ +import type { MaybeType, ScalarsInterface } from '../../types'; +import type { GeoLocationInputInterface } from '../types'; + +export interface MerchantsSearchedInterface { + __typename: string; + id: string; + isActive: boolean; + logo: string; + name: string; + username: string; +} + +export interface QueryGetCloseMerchantsArgsInterface { + geoLocation: GeoLocationInputInterface; +} + +export interface MerchantsOrdersInterface { + _id?: MaybeType; + ordersCount?: MaybeType; +} + +export interface QueryGetMerchantsByNameArgsInterface { + searchName: ScalarsInterface['String']; + geoLocation?: MaybeType; +} + +export interface QueryGetStoreProductsArgs { + storeId: ScalarsInterface['String']; + fullProducts: ScalarsInterface['Boolean']; +} diff --git a/packages/shop-mobile-expo/src/client/merchants/mutations.ts b/packages/shop-mobile-expo/src/client/merchants/mutations.ts new file mode 100644 index 0000000..95851dc --- /dev/null +++ b/packages/shop-mobile-expo/src/client/merchants/mutations.ts @@ -0,0 +1 @@ +// import { gql, TypedDocumentNode } from '@apollo/client'; diff --git a/packages/shop-mobile-expo/src/client/merchants/queries.ts b/packages/shop-mobile-expo/src/client/merchants/queries.ts new file mode 100644 index 0000000..ddcc406 --- /dev/null +++ b/packages/shop-mobile-expo/src/client/merchants/queries.ts @@ -0,0 +1,81 @@ +import { gql, TypedDocumentNode } from '@apollo/client'; + +/** + * Query to retrieve merchants by name + */ +export const GET_MERCHANTS_QUERY: TypedDocumentNode = gql` + query GetMerchantsBuyName( + $searchName: String! + $geoLocation: GeoLocationFindInput + ) { + getMerchantsBuyName( + searchName: $searchName + geoLocation: $geoLocation + ) { + id + username + name + logo + isActive + } + } +`; + +export const GET_STORED_PRODUCT: TypedDocumentNode = gql` + query GetStoreProducts($storeId: String!, $fullProducts: Boolean!) { + getStoreProducts(storeId: $storeId, fullProducts: $fullProducts) { + id + price + count + isTakeaway + isProductAvailable + isDeliveryRequired + isCarrierRequired + isManufacturing + soldCount + product { + id + title { + locale + value + } + description { + locale + value + } + images { + url + locale + width + height + orientation + } + _id + descriptionHTML { + locale + value + } + _createdAt + _updatedAt + detailsHTML { + value + locale + } + categories + details { + value + locale + } + } + _id + initialPrice + deliveryTimeMin + deliveryTimeMax + } + warehouse(id: $storeId) { + name + logo + id + } + } +`; diff --git a/packages/shop-mobile-expo/src/client/orders/argumentInterfaces.ts b/packages/shop-mobile-expo/src/client/orders/argumentInterfaces.ts new file mode 100644 index 0000000..e3e5f9a --- /dev/null +++ b/packages/shop-mobile-expo/src/client/orders/argumentInterfaces.ts @@ -0,0 +1,26 @@ +import type { ScalarsInterface, MaybeType } from '../../types'; + +export interface QueryGetOrdersArgsInterface { + userId: ScalarsInterface['String']; +} + +export interface WarehouseOrdersRouterCreateOptionsInterface { + autoConfirm: ScalarsInterface['Boolean']; +} + +export interface OrderProductCreateInputInterface { + count: ScalarsInterface['Int']; + productId: ScalarsInterface['String']; +} + +export interface OrderCreateInputInterface { + userId: ScalarsInterface['String']; + warehouseId: ScalarsInterface['String']; + products: OrderProductCreateInputInterface[]; + options?: MaybeType; + orderType: ScalarsInterface['Int']; +} + +export interface MutationCreateOrderArgsInterface { + createInput: OrderCreateInputInterface; +} diff --git a/packages/shop-mobile-expo/src/client/orders/mutations.ts b/packages/shop-mobile-expo/src/client/orders/mutations.ts new file mode 100644 index 0000000..23bfe60 --- /dev/null +++ b/packages/shop-mobile-expo/src/client/orders/mutations.ts @@ -0,0 +1,80 @@ +import { gql, TypedDocumentNode } from '@apollo/client'; + +/** + * Mutation to add a new user + * + * @type TypedDocumentNode + */ +export const CREATE_ORDER_MUTATION: TypedDocumentNode = gql` + mutation CreateOrder($createInput: OrderCreateInput!) { + createOrder(createInput: $createInput) { + id + user { + id + email + firstName + apartment + image + } + warehouse { + id + logo + name + username + ordersEmail + ordersPhone + inStoreMode + } + warehouseId + carrier { + id + username + phone + logo + email + status + } + carrierId + products { + count + isManufacturing + isTakeaway + isDeliveryRequired + initialPrice + price + product { + title { + value + locale + } + id + description { + value + locale + } + _createdAt + _updatedAt + } + comment + } + isConfirmed + isCancelled + isPaid + isCompleted + orderType + totalPrice + deliveryTime + orderNumber + carrierStatus + warehouseStatus + warehouseStatusText + carrierStatusText + deliveryTimeEstimate + startDeliveryTime + finishedProcessingTime + status + createdAt + updatedAt + } + } +`; diff --git a/packages/shop-mobile-expo/src/client/orders/queries.ts b/packages/shop-mobile-expo/src/client/orders/queries.ts new file mode 100644 index 0000000..d3de5fd --- /dev/null +++ b/packages/shop-mobile-expo/src/client/orders/queries.ts @@ -0,0 +1,67 @@ +import { gql, TypedDocumentNode } from '@apollo/client'; + +export const ORDER_PRODUCT_FRAGMENT: TypedDocumentNode = gql` + fragment OrderProductFragment on OrderProduct { + count + price + isTakeaway + product { + images { + locale + orientation + url + } + title { + locale + value + } + description { + locale + value + } + } + } +`; + +export const ORDER_FRAGMENT: TypedDocumentNode = gql` + fragment OrderFragment on Order { + id + deliveryTime + warehouseId + deliveryTime + createdAt + status + user { + id + geoLocation { + countryName + countryId + streetAddress + house + postcode + house + city + } + } + warehouse { + id + logo + } + products { + ...OrderProductFragment + } + } + ${ORDER_PRODUCT_FRAGMENT} +`; + +/** + * Query to retrieve order history by name + */ +export const GET_ORDER_HISTORY_QUERY: TypedDocumentNode = gql` + query OrdersHistoryPageQuery($userId: String!) { + getOrders(userId: $userId) { + ...OrderFragment + } + } + ${ORDER_FRAGMENT} +`; diff --git a/packages/shop-mobile-expo/src/client/products/argumentInterfaces.ts b/packages/shop-mobile-expo/src/client/products/argumentInterfaces.ts new file mode 100644 index 0000000..523dcb9 --- /dev/null +++ b/packages/shop-mobile-expo/src/client/products/argumentInterfaces.ts @@ -0,0 +1,36 @@ +import type { + GeolocationInputInterface, + PagingOptionsInputInterface, +} from '../types'; +import type { MaybeType, ScalarsInterface } from '../../types'; + +// TODO: Add more comments +// TODO: Move global types outside of argument interfaces file + +export interface GetGeolocationProductsOptions { + isDeliveryRequired?: MaybeType; + isTakeaway?: MaybeType; + merchantIds?: MaybeType[]>; + imageOrientation?: MaybeType; + locale?: MaybeType; + withoutCount?: MaybeType; +} + +/** + * + */ +export interface QueryGeolocationProductsByPagingArgsInterface { + geoLocation: GeolocationInputInterface; + pagingOptions?: MaybeType; + options?: MaybeType; + searchText?: MaybeType; +} + +export interface QueryProductsArgsInterface { + productId: ScalarsInterface['String']; +} + +export interface QueryGetWarehouseProductArgsInterface { + warehouseId: ScalarsInterface['String']; + warehouseProductId: ScalarsInterface['String']; +} diff --git a/packages/shop-mobile-expo/src/client/products/mutations.ts b/packages/shop-mobile-expo/src/client/products/mutations.ts new file mode 100644 index 0000000..95851dc --- /dev/null +++ b/packages/shop-mobile-expo/src/client/products/mutations.ts @@ -0,0 +1 @@ +// import { gql, TypedDocumentNode } from '@apollo/client'; diff --git a/packages/shop-mobile-expo/src/client/products/queries.ts b/packages/shop-mobile-expo/src/client/products/queries.ts new file mode 100644 index 0000000..a791ecb --- /dev/null +++ b/packages/shop-mobile-expo/src/client/products/queries.ts @@ -0,0 +1,184 @@ +import { gql, TypedDocumentNode } from '@apollo/client'; + +/** + * Query to retrieve products + */ +export const PRODUCTS_QUERY: TypedDocumentNode = gql` + query ProductsQuery( + $existedProductsIds: [String] + $findInput: ProductsFindInput + $pagingOptions: PagingOptionsInput + ) { + products( + existedProductsIds: $existedProductsIds + findInput: $findInput + pagingOptions: $pagingOptions + ) { + id + title { + locale + value + } + description { + locale + value + } + details { + locale + value + } + images { + url + locale + } + categories + _createdAt + _updatedAt + } + } +`; + +export const PRODUCT_QUERY: TypedDocumentNode = gql` + query ProductQuery($productId: String!) { + product(id: $productId) { + _id + id + title { + value + locale + } + description { + locale + value + } + details { + locale + value + } + images { + locale + url + width + height + orientation + } + categories + _createdAt + _updatedAt + } + } +`; + +export const WAREHOUSE_PRODUCT_QUERY: TypedDocumentNode = gql` + query GetWarehouseProduct( + $warehouseId: String! + $warehouseProductId: String! + ) { + getWarehouseProduct( + warehouseId: $warehouseId + warehouseProductId: $warehouseProductId + ) { + _id + price + initialPrice + count + soldCount + product { + _id + id + title { + locale + value + } + description { + locale + value + } + details { + locale + value + } + images { + locale + url + width + height + orientation + } + categories + _createdAt + _updatedAt + } + isManufacturing + isCarrierRequired + isDeliveryRequired + isProductAvailable + isTakeaway + deliveryTimeMin + deliveryTimeMax + id + } + + warehouse(id: $warehouseId) { + name + logo + id + } + } +`; + +export const GEO_LOCATION_PRODUCTS_BY_PAGING = gql` + query GeoLocationProductsByPaging( + $geoLocation: GeoLocationFindInput! + $options: GetGeoLocationProductsOptions + $pagingOptions: PagingOptionsInput + $searchText: String + ) { + geoLocationProductsByPaging( + geoLocation: $geoLocation + options: $options + pagingOptions: $pagingOptions + searchText: $searchText + ) { + distance + warehouseId + warehouseLogo + warehouseProduct { + id + price + initialPrice + count + soldCount + + product { + id + title { + locale + value + } + description { + locale + value + } + details { + locale + value + } + images { + locale + url + width + height + orientation + } + } + isManufacturing + isCarrierRequired + isDeliveryRequired + isTakeaway + deliveryTimeMin + deliveryTimeMax + } + } + } +`; diff --git a/packages/shop-mobile-expo/src/client/types.ts b/packages/shop-mobile-expo/src/client/types.ts new file mode 100644 index 0000000..ce09e2d --- /dev/null +++ b/packages/shop-mobile-expo/src/client/types.ts @@ -0,0 +1,169 @@ +// This file will contain common types & interfaces that arguments will use +import type { MaybeType, ScalarsInterface } from '../types/index'; + +export interface LocationInterface { + type: ScalarsInterface['String']; + coordinates: ScalarsInterface['Float'][]; +} + +export interface GeolocationCoordinatesInterface { + lng: ScalarsInterface['Float']; + lat: ScalarsInterface['Float']; +} + +export interface GeolocationInterface { + _id?: MaybeType; + id?: MaybeType; + _createdAt?: MaybeType; + createdAt?: MaybeType; + _updatedAt?: MaybeType; + updatedAt?: MaybeType; + countryId: ScalarsInterface['Int']; + countryName?: MaybeType; + city: ScalarsInterface['String']; + streetAddress: ScalarsInterface['String']; + house: ScalarsInterface['String']; + postcode?: MaybeType; + notes?: MaybeType; + loc: LocationInterface; + coordinates?: GeolocationCoordinatesInterface; +} + +export interface GeolocationInputInterface { + city?: ScalarsInterface['String']; + countryId?: ScalarsInterface['Int']; + house?: ScalarsInterface['String']; + loc?: LocationInterface; + notes?: MaybeType; + postcode?: MaybeType; + streetAddress?: ScalarsInterface['String']; +} + +export interface PagingSortInputInterface { + field: ScalarsInterface['String']; + sortBy: ScalarsInterface['String']; +} + +export interface PagingOptionsInputInterface { + sort?: MaybeType; + limit?: MaybeType; + skip?: MaybeType; +} + +export interface TranslateInterface { + locale: ScalarsInterface['String']; + value: ScalarsInterface['String']; +} + +export interface ImageInterface { + locale: ScalarsInterface['String']; + url: ScalarsInterface['String']; + width: ScalarsInterface['Int']; + height: ScalarsInterface['Int']; + orientation: ScalarsInterface['Int']; +} + +export interface DeviceInterface { + _id: ScalarsInterface['String']; + id: ScalarsInterface['String']; + channelId?: MaybeType; + type: ScalarsInterface['String']; + uuid: ScalarsInterface['String']; + language?: MaybeType; +} + +// ENTITIES +export interface UserInterface { + _id: ScalarsInterface['String']; + id: ScalarsInterface['String']; + geoLocation: GeolocationInterface; + apartment: ScalarsInterface['String']; + firstName?: MaybeType; + lastName?: MaybeType; + email?: MaybeType; + phone?: MaybeType; + devicesIds: ScalarsInterface['String'][]; + devices: DeviceInterface[]; + image?: MaybeType; + fullAddress?: MaybeType; + createdAt?: MaybeType; + isBanned: ScalarsInterface['Boolean']; +} + +export interface UserLoginInfoInterface { + user: UserInterface; + token: ScalarsInterface['String']; +} + +/** + * Invite structure + */ +export interface InviteInterface { + _id: ScalarsInterface['String']; + id: ScalarsInterface['String']; + code: ScalarsInterface['String']; + apartment: ScalarsInterface['String']; + geoLocation: GeolocationInterface; +} + +export interface NewInviteInterface { + __typename: string; + apartment: string | number; + code: string | number; + geoLocation: { + __typename: string; + city: string; + coordinates: { + __typename: string; + lat: number; + lng: number; + }; + countryId: number; + countryName: string; + createdAt: string; + house: string; + id: string; + notes: string | null; + postcode: string | null; + streetAddress: string; + updatedAt: string; + }; + id: string; +} + +export interface ProductInterface { + _id: ScalarsInterface['String']; + id: ScalarsInterface['String']; + title: TranslateInterface[]; + description: TranslateInterface[]; + details: TranslateInterface[]; + images: ImageInterface[]; + categories?: MaybeType[]>; + detailsHTML: TranslateInterface[]; + descriptionHTML: TranslateInterface[]; + _createdAt?: MaybeType; + _updatedAt?: MaybeType; +} + +export interface WarehouseProductInterface { + id: ScalarsInterface['String']; + _id: ScalarsInterface['String']; + price: ScalarsInterface['Int']; + initialPrice: ScalarsInterface['Int']; + count?: MaybeType; + soldCount: ScalarsInterface['Int']; + product: ProductInterface; + isManufacturing?: MaybeType; + isCarrierRequired?: MaybeType; + isDeliveryRequired?: MaybeType; + isTakeaway?: MaybeType; + deliveryTimeMin?: MaybeType; + deliveryTimeMax?: MaybeType; +} + +export interface ProductInfoInterface { + warehouseProduct: WarehouseProductInterface; + distance: ScalarsInterface['Float']; + warehouseId: ScalarsInterface['String']; + warehouseLogo: ScalarsInterface['String']; +} diff --git a/packages/shop-mobile-expo/src/client/user/argumentInterfaces.ts b/packages/shop-mobile-expo/src/client/user/argumentInterfaces.ts new file mode 100644 index 0000000..649ce11 --- /dev/null +++ b/packages/shop-mobile-expo/src/client/user/argumentInterfaces.ts @@ -0,0 +1,24 @@ +import type { MaybeType, ScalarsInterface } from '../../types/index'; +import type { GeolocationInputInterface } from '../types'; + +// TODO: Add more comments + +export interface UserCreateInputInterface { + email?: MaybeType; + firstName?: MaybeType; + lastName?: MaybeType; + phone?: MaybeType; + image?: MaybeType; + geoLocation: GeolocationInputInterface; + apartment: ScalarsInterface['String']; +} + +export interface UserRegisterArgsInterface { + user: UserCreateInputInterface; + password?: MaybeType; +} + +export interface UserLoginArgsInterface { + email: ScalarsInterface['String']; + password: ScalarsInterface['String']; +} diff --git a/packages/shop-mobile-expo/src/client/user/mutations.ts b/packages/shop-mobile-expo/src/client/user/mutations.ts new file mode 100644 index 0000000..150e68a --- /dev/null +++ b/packages/shop-mobile-expo/src/client/user/mutations.ts @@ -0,0 +1,77 @@ +import { gql, TypedDocumentNode } from '@apollo/client'; + +/** + * Mutation to add a new user + * + * @type TypedDocumentNode + */ +export const REGISTER_USER_MUTATION: TypedDocumentNode = gql` + mutation RegisterUser($registerInput: UserRegisterInput!) { + registerUser(registerInput: $registerInput) { + firstName + lastName + phone + id + apartment + image + createdAt + fullAddress + email + geoLocation { + city + countryName + streetAddress + house + } + isBanned + } + } +`; + +export const USER_LOGIN: TypedDocumentNode = gql` + mutation UserLogin($password: String!, $email: String!) { + userLogin(password: $password, email: $email) { + user { + id + geoLocation { + createdAt + id + updatedAt + countryName + city + countryId + streetAddress + postcode + house + notes + loc { + type + coordinates + } + coordinates { + lng + lat + } + } + apartment + firstName + lastName + email + phone + devicesIds + devices { + language + uuid + type + channelId + id + } + image + fullAddress + createdAt + isBanned + } + token + } + } +`; diff --git a/packages/shop-mobile-expo/src/client/user/queries.ts b/packages/shop-mobile-expo/src/client/user/queries.ts new file mode 100644 index 0000000..95851dc --- /dev/null +++ b/packages/shop-mobile-expo/src/client/user/queries.ts @@ -0,0 +1 @@ +// import { gql, TypedDocumentNode } from '@apollo/client'; diff --git a/packages/shop-mobile-expo/src/components/BuyProductBtn.tsx b/packages/shop-mobile-expo/src/components/BuyProductBtn.tsx new file mode 100644 index 0000000..21458a1 --- /dev/null +++ b/packages/shop-mobile-expo/src/components/BuyProductBtn.tsx @@ -0,0 +1,101 @@ +import React from 'react'; +import { Button } from 'react-native-paper'; +import { useMutation } from '@apollo/client'; + +// TYPES +import type { ModifyPropertiesTypes } from '../types'; +import type { MutationCreateOrderArgsInterface } from '../client/orders/argumentInterfaces'; + +// HELPERS +import { getReactComponentProps } from '../helpers/utils'; + +// APOLLO +import { CREATE_ORDER_MUTATION } from '../client/orders/mutations'; + +// STORE +import { useAppDispatch, useAppSelector } from '../store/hooks'; +import { setPreselectedProduct } from '../store/features/order'; +import { getUserData } from '../store/features/user'; +import { getLanguage } from '../store/features/translation'; +import { showMessage } from 'react-native-flash-message'; + +const BUTTON_PROPS = getReactComponentProps(Button); + +export interface Props { + amount: number; + productId: string; + warehouseId: string; + buttonProps: ModifyPropertiesTypes< + typeof BUTTON_PROPS, + { children?: undefined } + >; +} + +const BuyProductBtn: React.FC = ({ + amount, + warehouseId, + productId, + buttonProps, +}) => { + // DISPATCHERS + const dispatch = useAppDispatch(); + + // MUTATIONS + const [createOrder] = useMutation(CREATE_ORDER_MUTATION); + + // SELECTORS + const LANGUAGE = useAppSelector(getLanguage); + const USER_DATA = useAppSelector(getUserData); + + // STATES + const [loading, setLoading] = React.useState(false); + + // DATA + const CREATE_ORDER_INPUT: MutationCreateOrderArgsInterface = { + createInput: { + warehouseId, + products: [ + { + count: 1, + productId, + }, + ], + userId: USER_DATA?.user?.user.id || '', + orderType: 0, + options: { autoConfirm: true }, + }, + }; + + // FUNCTIONS + const onPressBuy = () => { + setLoading(true); + createOrder({ + variables: CREATE_ORDER_INPUT, + onCompleted: (TData) => { + dispatch(setPreselectedProduct(TData.data)); + setLoading(false); + }, + onError: (ApolloError) => { + console.log('ApolloError ==>', ApolloError, CREATE_ORDER_INPUT); + showMessage({ + message: ApolloError.name, + description: ApolloError.message, + type: 'danger', + }); + setLoading(false); + }, + }); + }; + + return ( + + ); +}; + +export default BuyProductBtn; diff --git a/packages/shop-mobile-expo/src/components/Common/CustomScreenHeader.tsx b/packages/shop-mobile-expo/src/components/Common/CustomScreenHeader.tsx new file mode 100644 index 0000000..d2a8801 --- /dev/null +++ b/packages/shop-mobile-expo/src/components/Common/CustomScreenHeader.tsx @@ -0,0 +1,291 @@ +import React, { useState } from 'react'; +import { View, TouchableOpacity, StyleSheet } from 'react-native'; +import { useNavigation } from '@react-navigation/native'; +import { SafeAreaView } from 'react-native-safe-area-context'; +import { IconButton, Title, Switch, TouchableRipple } from 'react-native-paper'; +// tslint:disable-next-line: no-implicit-dependencies no-submodule-imports +import MaterialIcons from '@expo/vector-icons/MaterialIcons'; + +// SELECTORS +import { useAppSelector } from '../../store/hooks'; +import { getLanguage } from '../../store/features/translation'; + +// COMPONENTS +import Icon from './Icon'; +import PaperText from './PaperText'; + +// STYLES +import { + GLOBAL_STYLE as GS, + CONSTANT_SIZE as CS, + CONSTANT_COLOR as CC, +} from '../../assets/ts/styles'; + +// LOCAL TYPES +export interface CustomScreenHeaderType { + title?: string; + children?: React.ReactChild; + showControls?: boolean; + controlOnPressSearch?: () => any; + controlOnPressStore?: () => any; + showHomeBtn?: boolean; + onPressHomeBtn?: () => any; + showBackBtn?: boolean; + onPressBackBtn?: () => any; +} + +const CustomScreenHeader: React.FC = ({ + title, + children, + showControls = false, + controlOnPressSearch, + controlOnPressStore, + showHomeBtn = false, + onPressHomeBtn, + showBackBtn = false, + onPressBackBtn, +}) => { + // SELECTORS + const LANGUAGE = useAppSelector(getLanguage); + + // HOOKS + const navigation = useNavigation(); + + // STATES + const [tmpSwitchValue, setTmpSwitchValue] = useState(false); + + // DATA + + const STYLES = StyleSheet.create({ + safeArea: { + ...GS.bgPrimaryLight, + ...GS.w100, + ...GS.shadowLg, + marginBottom: -CS.SPACE_SM, + borderBottomEndRadius: CS.SPACE_SM, + borderBottomStartRadius: CS.SPACE_SM, + }, + main: { + ...GS.row, + ...GS.px2, + alignItems: 'stretch', + height: CS.DRAWER_HEADER_HEIGHT, + }, + menuBtnContainer: { ...GS.centered, ...GS.mr1 }, + contentContainer: { + ...GS.inlineItems, + flex: 1, + }, + contentContainerChildren: { + ...GS.justifyContentBetween, + flex: 1, + }, + contentTitle: { + ...GS.mb1, + ...GS.FF_NunitoBold, + fontSize: CS.FONT_SIZE_MD - 1, + }, + btnWrapper: { + ...GS.roundedSm, + overflow: 'hidden', + }, + backBtnIcon: { + marginLeft: -6, + ...GS.mr1, + }, + productsStatusText: { + ...GS.txtCapitalize, + fontSize: CS.FONT_SIZE_SM, + opacity: 0.4, + }, + productsStatusTextFocused: { + opacity: 1, + }, + productsStatusSwitch: { + marginHorizontal: -3, + transform: [{ scale: 0.74 }], + }, + storeBtnContainer: { + ...GS.roundedLg, + overflow: 'hidden', + }, + }); + + return ( + + + {/* drawer menu btn */} + + + + + {/* header content */} + + {children ? ( + children + ) : ( + + {!!title && ( + + {title} + + )} + {!!showControls && ( + + + setTmpSwitchValue(!tmpSwitchValue) + } + style={{ + ...GS.inlineItems, + ...GS.mr2, + }}> + + {LANGUAGE.PRODUCTS_VIEW.TAKEAWAY} + + + setTmpSwitchValue( + !tmpSwitchValue, + ) + } + style={STYLES.productsStatusSwitch} + /> + + {LANGUAGE.PRODUCTS_VIEW.DELIVERY} + + + + + controlOnPressSearch + ? controlOnPressSearch() + : navigation.navigate( + 'DRAWER/SEARCH' as never, + ) + } + /> + + + + controlOnPressStore + ? controlOnPressStore() + : navigation.navigate( + 'DRAWER/MERCHANTS_SEARCH' as never, + ) + }> + + + + + )} + + {showBackBtn && ( + + + onPressBackBtn + ? onPressBackBtn() + : navigation.goBack() + }> + + + + { + LANGUAGE.PRODUCTS_VIEW + .DETAILS.BACK + } + + + + + )} + + {showHomeBtn && ( + + + onPressHomeBtn + ? onPressHomeBtn() + : navigation.navigate( + // @ts-ignore TODO: search to solve the next line issue + 'DRAWER/HOME', + ) + }> + + + + {LANGUAGE.PRODUCTS_VIEW.TITLE} + + + + + )} + + )} + + + + ); +}; + +export default CustomScreenHeader; diff --git a/packages/shop-mobile-expo/src/components/Common/Dialog.tsx b/packages/shop-mobile-expo/src/components/Common/Dialog.tsx new file mode 100644 index 0000000..8b02416 --- /dev/null +++ b/packages/shop-mobile-expo/src/components/Common/Dialog.tsx @@ -0,0 +1,121 @@ +import React from 'react'; +import { StyleSheet, ViewStyle } from 'react-native'; +import { + Dialog, + Portal, + IconButton, + Button, + Paragraph, +} from 'react-native-paper'; + +// HELPERS +import { getReactComponentProps } from '../../helpers/utils'; + +// STYLES +import { + CONSTANT_COLOR as CC, + CONSTANT_SIZE as CS, + GLOBAL_STYLE as GS, +} from '../../assets/ts/styles'; + +export const PaperDialogTitleProps = getReactComponentProps(Dialog.Title); +export const PaperButtonProps = getReactComponentProps(Button); +export const PaperParagraphProps = getReactComponentProps(Paragraph); +export interface DialogType { + visible: boolean; + title?: string; + titleProps?: typeof PaperDialogTitleProps; + message?: React.ReactNode; + style?: ViewStyle; + contentStyle?: ViewStyle; + actionStyle?: ViewStyle; + children?: React.ReactNode; + showCloseBtn?: boolean; + actions?: typeof PaperButtonProps[]; + onDismiss: () => void; +} + +const CustomDialog: React.FC = ({ + visible = false, + title, + titleProps = {}, + message, + style = {}, + contentStyle = {}, + actionStyle = {}, + children, + showCloseBtn = true, + actions, + onDismiss = () => {}, +}) => { + // DATA + const STYLES = StyleSheet.create({ + style: { + ...GS.pt2, + borderRadius: CS.SPACE_SM, + overflow: 'hidden', + ...style, + }, + contentStyle: { + ...contentStyle, + }, + actionStyle: { + ...actionStyle, + }, + closeBtn: { + ...GS.mr2, + ...GS.mt2, + position: 'absolute', + top: 0, + right: 0, + }, + contentContainer: { width: '100%' }, + }); + + return ( + + + {showCloseBtn && ( + + )} + {title && titleProps && ( + + {title} + + )} + + + {!!children && children} + {!!message && !children && ( + + {message} + + )} + + + {actions && ( + + {actions.map((item, index) => ( + + + ); +}; + +export default CustomDialog; diff --git a/packages/shop-mobile-expo/src/components/Common/FocusAwareStatusBar.tsx b/packages/shop-mobile-expo/src/components/Common/FocusAwareStatusBar.tsx new file mode 100644 index 0000000..8d8ff64 --- /dev/null +++ b/packages/shop-mobile-expo/src/components/Common/FocusAwareStatusBar.tsx @@ -0,0 +1,11 @@ +import React from 'react'; +import { StatusBar, StatusBarProps } from 'react-native'; +import { useIsFocused } from '@react-navigation/native'; + +const FocusAwareStatusBar: React.FC = (props) => { + const isFocused = useIsFocused(); + + return isFocused ? : null; +}; + +export default FocusAwareStatusBar; diff --git a/packages/shop-mobile-expo/src/components/Common/Icon.tsx b/packages/shop-mobile-expo/src/components/Common/Icon.tsx new file mode 100644 index 0000000..57ec4a0 --- /dev/null +++ b/packages/shop-mobile-expo/src/components/Common/Icon.tsx @@ -0,0 +1,14 @@ +import Icon_ from '@expo/vector-icons/Feather'; + +// HELPERS +import { getReactComponentProps } from '../../helpers/utils'; + +const IconProps = getReactComponentProps(Icon_); + +export type IconPropsType = typeof IconProps; +export type IconComponentType = typeof Icon_; +export type IconNameType = keyof typeof Icon_.glyphMap; + +const Icon: IconComponentType = Icon_; + +export default Icon; diff --git a/packages/shop-mobile-expo/src/components/Common/OrderHistoryItem.tsx b/packages/shop-mobile-expo/src/components/Common/OrderHistoryItem.tsx new file mode 100644 index 0000000..baa31ad --- /dev/null +++ b/packages/shop-mobile-expo/src/components/Common/OrderHistoryItem.tsx @@ -0,0 +1,96 @@ +/* eslint-disable max-len */ +import React from 'react'; +import { StyleSheet, View } from 'react-native'; +import { Card, Avatar, Title } from 'react-native-paper'; + +// COMPONENTS +import { PaperText } from '../../components/Common'; + +// STYLES +import { + GLOBAL_STYLE as GS, + CONSTANT_SIZE as CS, + CONSTANT_COLOR as CC, +} from '../../assets/ts/styles'; +import ProductHistoryItem from './ProductHistoryItem'; + +const OrderHistoryItem = () => { + // DATA + + const STYLES = StyleSheet.create({ + container: { + ...GS.shadow, + overflow: 'hidden', + }, + header: { + ...GS.inlineItems, + ...GS.p2, + }, + headerAvatarContainer: { + ...GS.shadow, + ...GS.mr5, + borderRadius: 100, + borderWidth: 2, + borderColor: CC.light, + }, + headerContent: { + flex: 1, + }, + headerContentTitle: { + ...GS.txtPrimaryLight, + fontSize: CS.FONT_SIZE_MD, + }, + headerContentDescription: { + fontSize: CS.FONT_SIZE_MD, + color: CC.gray, + }, + headerAmount: { + ...GS.centered, + width: CS.FONT_SIZE_XLG * 2.5, + }, + headerAmountText: { + ...GS.txtPrimaryLight, + ...GS.FF_NunitoSemiBold, + }, + separator: { + ...GS.w100, + ...GS.mt3, + borderWidth: 0.5, + borderColor: CC.primaryHightLight, + }, + }); + + return ( + + + + + + + + + Title + + + Description + + + + + $00 + + + + + {}} /> + + + ); +}; + +export default OrderHistoryItem; diff --git a/packages/shop-mobile-expo/src/components/Common/PaperText.tsx b/packages/shop-mobile-expo/src/components/Common/PaperText.tsx new file mode 100644 index 0000000..0530fc3 --- /dev/null +++ b/packages/shop-mobile-expo/src/components/Common/PaperText.tsx @@ -0,0 +1,17 @@ +import React from 'react'; +import { TextProps } from 'react-native'; +import { Text } from 'react-native-paper'; + +type Props = TextProps; + +const PaperText: React.FC = (props) => ( + + {props?.children} + +); + +export default PaperText; diff --git a/packages/shop-mobile-expo/src/components/Common/ProductHistoryItem.tsx b/packages/shop-mobile-expo/src/components/Common/ProductHistoryItem.tsx new file mode 100644 index 0000000..c038726 --- /dev/null +++ b/packages/shop-mobile-expo/src/components/Common/ProductHistoryItem.tsx @@ -0,0 +1,111 @@ +import React from 'react'; +import { StyleSheet, Image, View } from 'react-native'; +import { Text } from 'react-native-paper'; + +// COMPONENTS +import { PaperText, TouchableCard } from '.'; + +// STYLES +import { + GLOBAL_STYLE as GS, + CONSTANT_SIZE as CS, + CONSTANT_COLOR as CC, +} from '../../assets/ts/styles'; + +export interface Props { + image: string; + title: string; + description?: string; + amount?: number; + onPress: () => any; +} + +const ProductHistoryItem: React.FC = ({ + image, + title, + description, + amount = 0, + onPress, +}) => { + // DATA + const PRODUCT_HEIGHT = CS.SPACE_XLG * 2; + + // STYLES + const STYLES = StyleSheet.create({ + productCard: { + ...GS.noShadow, + borderWidth: 0, + overflow: 'hidden', + }, + productCardContent: { + ...GS.px0, + }, + productContentContainer: { ...GS.w100, ...GS.pr2, ...GS.inlineItems }, + productContentImageContainer: { + overflow: 'hidden', + borderTopEndRadius: CS.SPACE_SM, + borderBottomEndRadius: CS.SPACE_SM, + }, + productContentImage: { + height: PRODUCT_HEIGHT, + width: PRODUCT_HEIGHT, + }, + productContent: { + ...GS.pl4, + flex: 1, + }, + productContentTitle: { + ...GS.txtPrimary, + fontSize: CS.FONT_SIZE_MD + 1, + }, + productContentDescription: { + fontSize: CS.FONT_SIZE_MD, + color: CC.gray, + }, + productAmount: { + ...GS.centered, + width: CS.FONT_SIZE_XLG * 2.5, + }, + productAmountText: { + ...GS.txtPrimaryLight, + ...GS.FF_NunitoSemiBold, + }, + }); + + return ( + + + + + + + {title} + {!!description && ( + + {description} + + )} + + + + + ${amount} + + + + + ); +}; + +export default ProductHistoryItem; diff --git a/packages/shop-mobile-expo/src/components/Common/ProductItem/List.tsx b/packages/shop-mobile-expo/src/components/Common/ProductItem/List.tsx new file mode 100644 index 0000000..ff923de --- /dev/null +++ b/packages/shop-mobile-expo/src/components/Common/ProductItem/List.tsx @@ -0,0 +1,150 @@ +/* eslint-disable max-len */ +import React from 'react'; +import { StyleSheet, View, Image, TouchableOpacity } from 'react-native'; +import { Avatar, Title, Text, Button } from 'react-native-paper'; +// tslint:disable-next-line: no-implicit-dependencies no-submodule-imports +import Ionicons from '@expo/vector-icons/Ionicons'; + +// TYPES +import type { PropsItemInterface } from './index'; + +// COMPONENTS +import BuyProductBtn from '../../BuyProductBtn'; + +// STYLES +import { + GLOBAL_STYLE as GS, + CONSTANT_SIZE as CS, + CONSTANT_COLOR as CC, +} from '../../../assets/ts/styles'; + +const ProductItemList: React.FC = (props) => { + // LOCAL STYLES + const STYLES = StyleSheet.create({ + container: { + ...GS.w100, + ...GS.rounded, + backgroundColor: CC.primaryLight, + position: 'relative', + overflow: 'hidden', + }, + section: { + ...GS.p2, + ...GS.inlineItems, + }, + header: {}, + headerAvatarContainer: { + ...GS.shadow, + ...GS.mr5, + borderRadius: 100, + borderWidth: 2, + borderColor: CC.light, + }, + headerContent: { + flex: 1, + }, + headerContentTitle: { ...GS.fontBold, color: CC.light }, + headerContentDescription: { + ...GS.FF_NunitoBold, + fontSize: CS.FONT_SIZE_MD, + color: CC.grayLight, + }, + headerAvailability: { + ...GS.centered, + width: CS.FONT_SIZE_XLG * 2.5, + }, + headerAvailabilityText: { + ...GS.FF_NunitoSemiBold, + fontSize: CS.FONT_SIZE_SM, + lineHeight: 12, + }, + prodImg: { + height: CS.SCREEN_HEIGHT / 3, + }, + footer: {}, + footerBtn: { + minWidth: CS.FONT_SIZE_XLG * 6, + }, + footerBtnLabel: { + ...GS.FF_NunitoSemiBold, + ...GS.py1, + color: CC.light, + }, + footerBuyBtn: { + ...GS.bgSecondary, + ...GS.mr1, + flex: 1, + }, + footerDetailBtn: { + backgroundColor: CC.dark + '88', + }, + }); + + return ( + + + + + + + + {props?.data.title} + + + {props?.data.description} + + + + + + + Ready for takeaway + + + + + + + + + + + ); +}; + +export default ProductItemList; diff --git a/packages/shop-mobile-expo/src/components/Common/ProductItem/Slide.tsx b/packages/shop-mobile-expo/src/components/Common/ProductItem/Slide.tsx new file mode 100644 index 0000000..5cdf786 --- /dev/null +++ b/packages/shop-mobile-expo/src/components/Common/ProductItem/Slide.tsx @@ -0,0 +1,210 @@ +/* eslint-disable max-len */ +import React from 'react'; +import { StyleSheet, View, Image, TouchableOpacity } from 'react-native'; +import { Avatar, Title, Text, Button } from 'react-native-paper'; +// tslint:disable-next-line: no-implicit-dependencies no-submodule-imports +import Ionicons from '@expo/vector-icons/Ionicons'; + +// TYPES +import type { PropsItemInterface } from '.'; + +// COMPONENTS +import BuyProductBtn from '../../BuyProductBtn'; + +// STYLES +import { + GLOBAL_STYLE as GS, + CONSTANT_SIZE as CS, + CONSTANT_COLOR as CC, +} from '../../../assets/ts/styles'; + +const ProductItemSlide: React.FC = (props) => { + // DATA + const AVATAR_WAREHOUSE_SIZE = CS.FONT_SIZE_XLG * 2.5; + + // LOCAL STYLES + const STYLES = StyleSheet.create({ + container: { + ...GS.h100, + ...GS.w100, + flex: 1, + backgroundColor: CC.primary, + position: 'relative', + overflow: 'hidden', + }, + prodImg: { + flex: 1, + }, + containerAvailabilities: { + ...GS.mt4, + ...GS.mr2, + position: 'absolute', + top: 0, + right: 0, + }, + availabilitiesItem: { + ...GS.centered, + ...GS.rounded, + ...GS.mb2, + ...GS.p1, + backgroundColor: CC.primaryLight + 'cf', + width: CS.FONT_SIZE_XLG * 3.5, + }, + availabilitiesItemText: { + ...GS.FF_NunitoSemiBold, + fontSize: CS.FONT_SIZE_SM, + textAlign: 'center', + lineHeight: 12, + }, + availabilitiesItemTextLG: { + ...GS.FF_NunitoSemiBold, + fontSize: CS.FONT_SIZE_LG, + }, + availabilitiesItemTextPrice: { + textDecorationLine: 'line-through', + }, + section: { + ...GS.p2, + ...GS.inlineItems, + }, + header: { + ...GS.mb5, + position: 'relative', + }, + headerContent: { + flex: 1, + marginRight: AVATAR_WAREHOUSE_SIZE + CS.SPACE_SM, + }, + headerAvatarContainer: { + ...GS.shadow, + ...GS.mr2, + borderRadius: 100, + borderWidth: 2, + borderColor: CC.light, + position: 'absolute', + top: -(AVATAR_WAREHOUSE_SIZE / 2), + right: 0, + }, + headerContentTitle: { + ...GS.fontBold, + ...GS.mb1, + fontSize: CS.FONT_SIZE_XLG, + color: CC.light, + }, + headerContentDescription: { + ...GS.FF_NunitoBold, + fontSize: CS.FONT_SIZE_MD, + color: CC.grayLight, + }, + footer: {}, + footerBtn: { + minWidth: CS.FONT_SIZE_XLG * 6, + }, + footerBtnLabel: { + ...GS.FF_NunitoSemiBold, + ...GS.py1, + color: CC.light, + }, + footerBuyBtn: { + ...GS.bgSecondary, + ...GS.mr1, + flex: 1, + }, + footerDetailBtn: { + backgroundColor: CC.primaryLight, + }, + }); + + return ( + + + + + + + ${props?.data?.price} + + + + + 1% + + + + + + + + Ready for takeaway + + + + + + + + {props?.data.title} + + + {props?.data?.description} + + + + + + + + + + + + + + + ); +}; + +export default ProductItemSlide; diff --git a/packages/shop-mobile-expo/src/components/Common/ProductItem/index.tsx b/packages/shop-mobile-expo/src/components/Common/ProductItem/index.tsx new file mode 100644 index 0000000..abd0092 --- /dev/null +++ b/packages/shop-mobile-expo/src/components/Common/ProductItem/index.tsx @@ -0,0 +1,93 @@ +import React from 'react'; +import { Text } from 'react-native'; +import { useNavigation } from '@react-navigation/native'; + +// TYPES +import type ENV from '../../../environments/model'; + +// HELPERS +import { isEmpty } from '../../../helpers/utils'; + +// COMPONENTS +import ProductItemVertical from './List'; +import ProductItemHorizontal from './Slide'; + +// LOCAL TYPES +export interface ProductItemInterface { + data: { + warehouseId: string; + warehouseLogo?: string; + productId: string; + title: string; + description: string; + price: number; + coverImage?: string; + }; + type: ENV['PRODUCTS_VIEW_TYPE']; +} + +export interface PropsItemInterface { + data: ProductItemInterface['data']; + onPressProfile: () => any; + onPressDetails: () => any; +} + +const ProductItem: React.FC = (props) => { + // NAVIGATION + const NAVIGATION = useNavigation(); + + // FUNCTIONS + const onPressProfile = () => { + const ROUTE_WAREHOUSE_ID = { + warehouseId: props?.data.warehouseId, + }; + + if (!isEmpty(ROUTE_WAREHOUSE_ID.warehouseId)) { + NAVIGATION.navigate( + 'DRAWER/IN_STORE' as never, + ROUTE_WAREHOUSE_ID as never, + ); + } + }; + + const onPressDetails = () => { + const ROUTE_PRODUCT = { + productId: props?.data.productId, + warehouseId: props?.data.warehouseId, + }; + + if (!isEmpty(ROUTE_PRODUCT.productId)) { + NAVIGATION.navigate( + 'DRAWER/PRODUCT_DETAILS' as never, + ROUTE_PRODUCT as never, + ); + } + }; + + switch (props.type) { + case 'list': + return ( + + ); + case 'slides': + return ( + + ); + default: + return Type invalid; + } +}; + +export default ProductItem; diff --git a/packages/shop-mobile-expo/src/components/Common/TouchableCard.tsx b/packages/shop-mobile-expo/src/components/Common/TouchableCard.tsx new file mode 100644 index 0000000..86a9d99 --- /dev/null +++ b/packages/shop-mobile-expo/src/components/Common/TouchableCard.tsx @@ -0,0 +1,222 @@ +import React from 'react'; +import { + View, + Image, + ViewStyle, + TextStyle, + ImageStyle, + ActivityIndicator, + GestureResponderEvent, + StyleSheet, +} from 'react-native'; +import { Card, TouchableRipple } from 'react-native-paper'; + +// HELPERS +import { isEmpty } from '../../helpers/utils'; + +// COMPONENTS +import PaperText from './PaperText'; +import Icon, { IconPropsType } from './Icon'; + +// STYLES +import { + CONSTANT_COLOR as CC, + GLOBAL_STYLE as GS, +} from '../../assets/ts/styles'; + +export interface TouchableCardPropsType { + title?: null | React.ReactNode | string; + description?: null | string; + textOneLine?: boolean; + iconProps?: IconPropsType; + indicatorIconProps?: IconPropsType; + indicatorText?: null | string; + indicatorTextSize?: number; + indicatorTextColor?: null | string; + img?: string | object | undefined; + onPress?: (event: GestureResponderEvent) => any; + style?: ViewStyle; + cardStyle?: ViewStyle; + cardStyleContent?: ViewStyle; + titleStyle?: TextStyle; + descriptionStyle?: TextStyle; + imgStyle?: ImageStyle; + height?: number; + loading?: false; + loaderColor?: string; + disabled?: false; + rippleColor?: string; + children?: React.ReactNode; +} + +const STYLES = StyleSheet.create({ + main: { flex: 1 }, + card: { + ...GS.shadowSm, + borderRadius: 10, + overflow: 'hidden', + }, + cardContent: { + ...GS.row, + alignItems: 'center', + }, + loaderContainer: { ...GS.h100, ...GS.centered, flex: 1 }, + cardImg: { + width: 40, + height: 40, + borderRadius: 20, + resizeMode: 'cover', + }, + cardTextContent: { + flex: 1, + justifyContent: 'center', + }, + cardTextContentTitle: { fontSize: 18, paddingBottom: 2, color: CC.primary }, +}); + +const TouchableCard: React.FC = ({ + title = null, + description = null, + textOneLine = true, + iconProps, + indicatorIconProps, + indicatorText = null, + indicatorTextSize = 10, + indicatorTextColor = null, + img, + onPress, + style = {}, + cardStyle = {}, + cardStyleContent = {}, + titleStyle = {}, + descriptionStyle = {}, + imgStyle = {}, + height = 75, + loading = false, + loaderColor = CC.gray, + disabled = false, + rippleColor = CC.secondary + '10', + children = null, +}) => ( + + + + + {loading ? ( + + + + ) : ( + <> + {children + ? children + : (iconProps || img) && ( + + {iconProps && !img && ( + + )} + {!isEmpty(img) && ( + + )} + + )} + + + {!isEmpty(title) && ( + + {title} + + )} + {!isEmpty(description) && ( + + {description} + + )} + + + {!isEmpty(indicatorIconProps) && !indicatorText && ( + + + + )} + + {!isEmpty(indicatorText) && ( + + {indicatorText} + + )} + + )} + + + + +); + +export default TouchableCard; diff --git a/packages/shop-mobile-expo/src/components/Common/index.ts b/packages/shop-mobile-expo/src/components/Common/index.ts new file mode 100644 index 0000000..4e00397 --- /dev/null +++ b/packages/shop-mobile-expo/src/components/Common/index.ts @@ -0,0 +1,36 @@ +// This file provide all custom commons components of the app +import CustomScreenHeader_ from './CustomScreenHeader'; +import FocusAwareStatusBar_ from './FocusAwareStatusBar'; +import Dialog_ from './Dialog'; +import Icon_ from './Icon'; +import OrderHistoryItem_ from './OrderHistoryItem'; +import PaperText_ from './PaperText'; +import ProductItem_ from './ProductItem'; +import ProductHistoryItem_ from './ProductHistoryItem'; +import TouchableCard_ from './TouchableCard'; + +// TODO: add type for components constant + +export const CustomScreenHeader = CustomScreenHeader_; +export const FocusAwareStatusBar = FocusAwareStatusBar_; +export const Dialog = Dialog_; +export const Icon = Icon_; +export const OrderHistoryItem = OrderHistoryItem_; +export const PaperText = PaperText_; +export const ProductItem = ProductItem_; +export const ProductHistoryItem = ProductHistoryItem_; +export const TouchableCard = TouchableCard_; + +const components = { + CustomScreenHeader, + FocusAwareStatusBar, + Dialog, + Icon, + OrderHistoryItem, + PaperText, + ProductItem, + ProductHistoryItem, + TouchableCard, +}; + +export default components; diff --git a/packages/shop-mobile-expo/src/components/CustomDrawerContent/Header.tsx b/packages/shop-mobile-expo/src/components/CustomDrawerContent/Header.tsx new file mode 100644 index 0000000..72687b3 --- /dev/null +++ b/packages/shop-mobile-expo/src/components/CustomDrawerContent/Header.tsx @@ -0,0 +1,54 @@ +import * as React from 'react'; +import { View } from 'react-native'; +import { SafeAreaView } from 'react-native-safe-area-context'; + +// COMPONENTS +import { PaperText } from '../Common'; + +// SELECTORS +import { useAppSelector } from '../../store/hooks'; +import { getLang, getLanguage } from '../../store/features/translation'; + +// STYLES +import { + GLOBAL_STYLE as GS, + CONSTANT_SIZE as CS, + CONSTANT_COLOR as CC, +} from '../../assets/ts/styles'; + +const DrawerHeader = () => { + // SELECTORS + const currentLanguage = useAppSelector(getLanguage); + const currentLang = useAppSelector(getLang); + + return ( + + + + {currentLanguage.SIDE_MENU.TITLE} + + + + ); +}; + +export default DrawerHeader; diff --git a/packages/shop-mobile-expo/src/components/CustomDrawerContent/Item.tsx b/packages/shop-mobile-expo/src/components/CustomDrawerContent/Item.tsx new file mode 100644 index 0000000..a290c43 --- /dev/null +++ b/packages/shop-mobile-expo/src/components/CustomDrawerContent/Item.tsx @@ -0,0 +1,97 @@ +import * as React from 'react'; +import { View, Linking, StyleSheet } from 'react-native'; +import { useNavigation } from '@react-navigation/native'; + +// TYPES +import { DrawerLinkItem } from '../../router/drawer.routes'; + +// COMPONENTS +import { TouchableCard } from '../Common'; + +// STYLES +import { + GLOBAL_STYLE as GS, + CONSTANT_COLOR as CC, + CONSTANT_SIZE as CS, +} from '../../assets/ts/styles'; + +const Item: React.FC = ({ + label, + path, + icon, + external, + focused, +}) => { + // NAVIGATION + const navigation = useNavigation(); + + // LOCAL STYLES + const STYLES = StyleSheet.create({ + container: { position: 'relative', ...GS.w100, ...GS.mb1 }, + touchable: { ...GS.w100, ...GS.inlineItems, zIndex: 1 }, + touchableCard: { ...GS.w100, ...GS.px0, borderRadius: 0 }, + touchableCardContent: { + ...GS.px2, + }, + touchableCardContentFocused: { + backgroundColor: CC.primaryHightLight + '20', + }, + focusedIndicator: { + ...GS.h100, + ...GS.bgSecondary, + position: 'absolute', + top: 0, + left: 0, + width: 4, + borderTopEndRadius: 4, + borderBottomEndRadius: 4, + zIndex: 2, + }, + }); + + return ( + + { + if (external) { + return Linking.openURL(path); + } + + // @ts-ignore TODO: search to resolve this next line + navigation.navigate({ name: path }); + console.log(path); + + return; + }} + /> + + {focused && } + + ); +}; + +export default Item; diff --git a/packages/shop-mobile-expo/src/components/CustomDrawerContent/index.tsx b/packages/shop-mobile-expo/src/components/CustomDrawerContent/index.tsx new file mode 100644 index 0000000..18dd404 --- /dev/null +++ b/packages/shop-mobile-expo/src/components/CustomDrawerContent/index.tsx @@ -0,0 +1,98 @@ +import React from 'react'; +import { View, ScrollViewProps as ScrollViewProps_ } from 'react-native'; +import { Title } from 'react-native-paper'; +import { + DrawerContentScrollView, + DrawerContentComponentProps, +} from '@react-navigation/drawer'; + +// HELPERS +import { isEmpty } from '../../helpers/utils'; + +// CONSTANTS +import { DrawerRoutesGroupType } from '../../router/drawer.routes'; + +// COMPONENTS +import { Icon } from '../Common'; +import Header from './Header'; +import Item from './Item'; + +// STYLES +import { + GLOBAL_STYLE as GS, + CONSTANT_COLOR as CC, + CONSTANT_SIZE as CS, +} from '../../assets/ts/styles'; + +// LOCAL TYPES +export interface ContentProps { + ScrollViewProps?: ScrollViewProps_; + drawerContentProps: DrawerContentComponentProps; + linksGroups: DrawerRoutesGroupType[]; +} + +const CustomDrawer: React.FC = ({ + ScrollViewProps = {}, + drawerContentProps, + linksGroups = [], +}) => { + const navigationState = drawerContentProps.navigation.getState(); + const currentRouteName = navigationState.routeNames[navigationState.index]; + + return ( + +
+ + + + {linksGroups.map((linksGroup, linksGroup_id) => ( + + + {linksGroup?.icon && ( + + )} + {!isEmpty(linksGroup.title) && ( + + {linksGroup.title} + + )} + + {linksGroup?.linkItems && + linksGroup.linkItems.map( + (linkItem, linkItem_id) => ( + + ), + )} + + ))} + + + + ); +}; + +export default CustomDrawer; diff --git a/packages/shop-mobile-expo/src/components/OrderDialog/Warn.tsx b/packages/shop-mobile-expo/src/components/OrderDialog/Warn.tsx new file mode 100644 index 0000000..36ed693 --- /dev/null +++ b/packages/shop-mobile-expo/src/components/OrderDialog/Warn.tsx @@ -0,0 +1,30 @@ +import React from 'react'; +import { StyleSheet } from 'react-native'; + +// COMPONENTS +import { Dialog } from '../Common'; + +// STYLES +// import { GLOBAL_STYLE as GS } from '../../assets/ts/styles'; + +interface Props { + visible: boolean; + onDismiss: () => any; +} + +const OrderWarnDialog: React.FC = ({ visible, onDismiss }) => { + const STYLES = StyleSheet.create({ + main: {}, + }); + + return ( + + ); +}; + +export default OrderWarnDialog; diff --git a/packages/shop-mobile-expo/src/components/OrderDialog/index.tsx b/packages/shop-mobile-expo/src/components/OrderDialog/index.tsx new file mode 100644 index 0000000..91252ea --- /dev/null +++ b/packages/shop-mobile-expo/src/components/OrderDialog/index.tsx @@ -0,0 +1,12 @@ +import React from 'react'; +import { Text, View } from 'react-native'; + +interface Props {} + +const OrderDialog: React.FC = ({}) => ( + + OrderDialog + +); + +export default OrderDialog; diff --git a/packages/shop-mobile-expo/src/components/Providers/Apollo.tsx b/packages/shop-mobile-expo/src/components/Providers/Apollo.tsx new file mode 100644 index 0000000..758cc52 --- /dev/null +++ b/packages/shop-mobile-expo/src/components/Providers/Apollo.tsx @@ -0,0 +1,196 @@ +import React from 'react'; +import { + ApolloClient, + InMemoryCache, + ApolloProvider as Provider, +} from '@apollo/client'; +import { + Text, + TextInput, + Button, + View, + Modal, + ActivityIndicator, + TouchableWithoutFeedback, +} from 'react-native'; +import AsyncStorage from '@react-native-async-storage/async-storage'; + +// HELPERS +import { checkServer, isEmpty } from '../../helpers/utils'; + +// ENVIRONMENT +import ENV from '../../environments/environment'; + +// STYLES +import { + GLOBAL_STYLE as GS, + CONSTANT_COLOR as CC, + CONSTANT_SIZE as CS, +} from '../../assets/ts/styles'; + +// LOCAL TYPES +export interface Props { + children: React.ReactElement; +} + +const ApolloProvider: React.FC = (props) => { + // STATES + const [serverHostInp, setServerHostInp] = React.useState(''); + const [serverHost, setServerHost] = React.useState(null); + const [showDialogUriConf, setShowDialogUriConf] = React.useState( + !ENV.PRODUCTION || __DEV__, + ); + const [serverHostInpMsg, setServerHostInpMsg] = React.useState(''); + const [serverHostLoading, setServerHostLoading] = + React.useState(false); + + // REFS + const serverHostInpRef = React.useRef(null); + + // FUNCTIONS + const onConfirmHost = async () => { + setServerHostInpMsg(''); + if (isEmpty(serverHostInp)) { + return setServerHostInpMsg("Can't be empty"); + } + + const FORMATTED_HOST = `https://${serverHostInp}`; + const FORMATTED_URI = FORMATTED_HOST + '/graphql'; + + AsyncStorage.setItem('serverHost', serverHostInp); + setServerHostLoading(true); + + await checkServer( + FORMATTED_URI, + 6000, + () => { + setServerHost(FORMATTED_URI); + setShowDialogUriConf(false); + }, + (errMsg) => { + setServerHostInpMsg("Can't connect on this host: " + errMsg); + }, + ).finally(() => { + setServerHostLoading(false); + }); + }; + + const onCancelHost = () => { + setServerHost(null); + setShowDialogUriConf(false); + }; + + // CONFIG + const APOLLO_CLIENT = new ApolloClient({ + uri: serverHost || ENV.ENDPOINT.GQL, + cache: new InMemoryCache(), + defaultOptions: { watchQuery: { fetchPolicy: 'cache-and-network' } }, + }); + + // EFFECTS + React.useEffect(() => { + (async () => { + const LOCAL_SERVER_HOST = await AsyncStorage.getItem('serverHost'); + + if (typeof LOCAL_SERVER_HOST === 'string') { + setServerHostInp(LOCAL_SERVER_HOST); + } + })(); + }, []); + + return ( + + + { + serverHostInpRef.current?.blur(); + }}> + + + + {`You're running the this application in development mode + \nWe're not able to determinate the Api Host that you use. + \nIf you're on "${ENV.ENDPOINT.GQL}", you can just cancel otherwise you've to specify your host`} + + + + { + setServerHostInp(text); + }} + style={{ + ...GS.input, + ...GS.w100, + color: CC.primary, + }} + /> + + + {serverHostInpMsg} + + + + + + {serverHostLoading ? ( + + ) : ( + + + + + {name} - v{version} + + + + ); +} + +const STYLES = StyleSheet.create({ + main: { + ...GS.centered, + ...GS.px4, + flex: 1, + }, + title: { ...GS.mb3, fontSize: 40 }, + subTitle: { + ...GS.txtCenter, + ...GS.mb2, + color: CC.gray, + fontSize: 16, + }, + footer: { ...GS.py2, ...GS.px4, alignItems: 'flex-end' }, + appInfo: { + ...GS.txtCenter, + color: CC.gray, + fontSize: 12, + }, +}); diff --git a/packages/shop-mobile-expo/src/screens/Loading.screen.tsx b/packages/shop-mobile-expo/src/screens/Loading.screen.tsx new file mode 100644 index 0000000..e0c3cae --- /dev/null +++ b/packages/shop-mobile-expo/src/screens/Loading.screen.tsx @@ -0,0 +1,24 @@ +import React from 'react'; +import { View, ActivityIndicator } from 'react-native'; + +// COMPONENTS +import { FocusAwareStatusBar } from '../components/Common'; + +// STYLES +import { GLOBAL_STYLE as GS, CONSTANT_COLOR as CC } from '../assets/ts/styles'; + +export default ({}) => { + // STATES + + return ( + + + + + + ); +}; diff --git a/packages/shop-mobile-expo/src/screens/app/Account.screen.tsx b/packages/shop-mobile-expo/src/screens/app/Account.screen.tsx new file mode 100644 index 0000000..7a58c88 --- /dev/null +++ b/packages/shop-mobile-expo/src/screens/app/Account.screen.tsx @@ -0,0 +1,493 @@ +import React from 'react'; +import { View, ScrollView, StyleSheet } from 'react-native'; +import { Card, Title, Text, RadioButton, Button } from 'react-native-paper'; +// tslint:disable-next-line: no-implicit-dependencies no-submodule-imports +import MaterialIcon from '@expo/vector-icons/MaterialCommunityIcons'; +import { useNavigation } from '@react-navigation/native'; + +// TYPES +import type ENV_TYPE from '../../environments/model'; + +// STORE +import { useAppSelector, useAppDispatch } from '../../store/hooks'; +import { + getUserData, + getProductViewType, + setProductViewType, + resetUser, + getOrderInfoType, + setOrderInfoType, +} from '../../store/features/user'; +import { setGroup } from '../../store/features/navigation'; + +// ROUTER +import ROUTE_GROUPS from '../../router/groups.routes'; + +// COMPONENTS +import { + TouchableCard, + FocusAwareStatusBar, + CustomScreenHeader, + Dialog, + Icon, +} from '../../components/Common'; + +// STYLES +import { + GLOBAL_STYLE as GS, + CONSTANT_COLOR as CC, + CONSTANT_SIZE as CS, +} from '../../assets/ts/styles'; + +function AccountScreen({}) { + // ACTIONS + const dispatch = useAppDispatch(); + + // SELECTORS + const USER_DATA = useAppSelector(getUserData) as any; + const PRODUCT_VIEW_TYPE = useAppSelector(getProductViewType); + const ORDER_INFO_VIEW = useAppSelector(getOrderInfoType); + // const CURRENT_LANG = useAppSelector(getLang); + + // NAVIGATION + const NAVIGATION = useNavigation(); + + // STATES + const [productViewDialog, setProductViewDialog] = + React.useState(false); + const [orderInfoTypeDialog, setOrderInfoTypeDialog] = + React.useState(false); + const [logOutDialog, setLogOutDialog] = React.useState(false); + + // DATA + const IS_INVITE = USER_DATA?.__typename === 'Invite'; + const CURRENT_USER_DATA = IS_INVITE ? USER_DATA : USER_DATA?.user?.user; + + const STYLES = StyleSheet.create({ + container: { ...GS.screen, ...GS.bgLight }, + dialogProductViewContent: { height: 180 }, + userInfoCard: { + ...GS.pt5, + backgroundColor: CC.white, + borderBottomLeftRadius: CS.SPACE, + borderBottomRightRadius: CS.SPACE, + }, + userInfoCardContent: { + ...GS.centered, + justifyContent: 'space-between', + }, + userAvatarContainer: { + ...GS.centered, + ...GS.mb3, + borderColor: CC.gray, + borderWidth: 2, + borderRadius: 9999, + }, + avatarIcon: { + ...GS.m2, + }, + userInfoTitle: { + ...GS.mb0, + ...GS.pb0, + color: CC.primary, + textAlign: 'center', + }, + userInfoSubTitle: { + color: CC.gray, + textAlign: 'center', + }, + userInfoInfosContainer: { ...GS.inlineItems, ...GS.mt4, width: '100%' }, + userInfoInfosItem: { ...GS.centered, flex: 1 }, + userInfoInfosItemTitle: { + color: CC.gray, + }, + userInfoInfosItemSubTitle: { + color: CC.primary, + }, + scrollViewOptions: { + ...GS.h100, + ...GS.pt4, + ...GS.px2, + }, + optionItem: { ...GS.inlineItems, ...GS.mb2 }, + optionItemCard: { ...GS.w100, borderRadius: 5 }, + optionItemCardContent: { borderRadius: 0 }, + optionItemCardContentContainerText: { + flex: 1, + }, + optionItemCardContentText: { + ...GS.txtCapitalize, + fontSize: CS.FONT_SIZE_MD, + color: CC.primary, + }, + optionItemCardContentSubText: { + ...GS.txtLower, + fontSize: CS.FONT_SIZE, + color: CC.primaryLight, + }, + dialogContent: { + ...GS.centered, + ...GS.w100, + }, + }); + + // FUNCTIONS + const onSelectProductView = (value: ENV_TYPE['PRODUCTS_VIEW_TYPE']) => { + dispatch(setProductViewType(value)); + setProductViewDialog(false); + }; + + const onSelectOrderInfoView = (value: ENV_TYPE['ORDER_INFO_TYPE']) => { + dispatch(setOrderInfoType(value)); + setOrderInfoTypeDialog(false); + }; + + const onGoToRegistration = () => { + const NAV_PARAMS = { fromApp: true }; + dispatch(setGroup(ROUTE_GROUPS.REGISTRATION)); + setTimeout(() => { + NAVIGATION.navigate('STACK/SIGN_UP' as never, NAV_PARAMS as never); + }, 200); + }; + + const onSignOut = () => { + dispatch(resetUser()); + dispatch(setGroup(ROUTE_GROUPS.REGISTRATION)); + }; + + return ( + <> + setLogOutDialog(false)} + title={'Log out?'} + message={'Do you really want to sign-out?'} + actions={[ + { + children: 'Cancel', + uppercase: false, + labelStyle: { color: CC.primary }, + onPress: () => setLogOutDialog(false), + }, + { + children: 'Sign out', + uppercase: false, + labelStyle: { color: CC.danger }, + onPress: onSignOut, + }, + ]} + /> + + setProductViewDialog(false)} + title={'Product View'}> + + onSelectProductView('list')}> + + + + + List + + + Use list view (swipe by top or bottom) in + home + + + + onSelectProductView('list')} + /> + + + + onSelectProductView('slides')}> + + + + + + Slides + + + Use slide view (swipe by left or right) in + home + + + + onSelectProductView('slides')} + /> + + + + + + setOrderInfoTypeDialog(false)} + title={'Product View'}> + + onSelectOrderInfoView('popup')}> + + + + + Popup + + + Display order info with a dialog box + + + + onSelectOrderInfoView('popup')} + /> + + + + onSelectOrderInfoView('page')}> + + + + + + Page + + + Use page view for order info + + + + onSelectOrderInfoView('page')} + /> + + + + + + + + + + + + + + + + + {IS_INVITE + ? CURRENT_USER_DATA?.__typename + : `${CURRENT_USER_DATA?.firstName || ''} ${ + CURRENT_USER_DATA?.lastName || '' + }`} + + + {!IS_INVITE && ( + + {CURRENT_USER_DATA?.email} + + )} + + + + + Country + + + { + CURRENT_USER_DATA?.geoLocation + ?.countryName + } + + + + + + City + + + {CURRENT_USER_DATA?.geoLocation?.city} + + + + + + Apartment + + + {CURRENT_USER_DATA?.apartment} + + + + + {IS_INVITE && ( + + )} + + + + + setLogOutDialog(true)} + /> + + setProductViewDialog(true)} + /> + + setOrderInfoTypeDialog(true)} + /> + + + + ); +} + +export default AccountScreen; diff --git a/packages/shop-mobile-expo/src/screens/app/Home.screen.tsx b/packages/shop-mobile-expo/src/screens/app/Home.screen.tsx new file mode 100644 index 0000000..5e7bc5a --- /dev/null +++ b/packages/shop-mobile-expo/src/screens/app/Home.screen.tsx @@ -0,0 +1,163 @@ +import React from 'react'; +import { View, ActivityIndicator, FlatList, StyleSheet } from 'react-native'; +import { Title } from 'react-native-paper'; +import { useQuery } from '@apollo/client'; +import PagerView from 'react-native-pager-view'; + +// TYPES/INTERFACES +import type { ProductInfoInterface } from '../../client/types'; +import type { QueryGeolocationProductsByPagingArgsInterface } from '../../client/products/argumentInterfaces'; + +// SELECTORS +import { useAppSelector } from '../../store/hooks'; +import { getUserData, getProductViewType } from '../../store/features/user'; +import { getLanguage } from '../../store/features/translation'; + +// QUERIES +import { GEO_LOCATION_PRODUCTS_BY_PAGING } from '../../client/products/queries'; + +// COMPONENTS +import { + FocusAwareStatusBar, + CustomScreenHeader, + ProductItem, +} from '../../components/Common'; + +// STYLES +import { GLOBAL_STYLE as GS } from '../../assets/ts/styles'; + +function HomeScreen({}) { + // SELECTORS + const LANGUAGE = useAppSelector(getLanguage); + const USER_DATA = useAppSelector(getUserData); + const VIEW_TYPE = useAppSelector(getProductViewType); + + // DATA + const PRODUCTS_QUERY_ARGS_INTERFACE: QueryGeolocationProductsByPagingArgsInterface = + { + geoLocation: { + loc: { + type: 'Point', + coordinates: [ + USER_DATA?.user?.user.geoLocation?.coordinates?.lng || + USER_DATA?.invite?.geoLocation?.coordinates?.lng || + 0, + USER_DATA?.user?.user.geoLocation?.coordinates?.lat || + USER_DATA?.invite?.geoLocation?.coordinates?.lat || + 0, + ], + }, + }, + }; + + // QUERIES + const PRODUCTS_QUERY_RESPONSE = useQuery(GEO_LOCATION_PRODUCTS_BY_PAGING, { + variables: { + ...PRODUCTS_QUERY_ARGS_INTERFACE, + }, + }); + + // STYLES + const STYLES = StyleSheet.create({ + loaderContainer: { ...GS.centered, ...GS.w100, flex: 1 }, + productItemContainer: { + ...GS.mx1, + ...GS.mt2, + ...GS.mb1, + }, + }); + + return ( + + + + + + {PRODUCTS_QUERY_RESPONSE.loading ? ( + + + + ) : PRODUCTS_QUERY_RESPONSE.data?.geoLocationProductsByPaging && + PRODUCTS_QUERY_RESPONSE.data?.geoLocationProductsByPaging + .length ? ( + VIEW_TYPE === 'list' ? ( + ( + + + + )} + keyExtractor={(_item, _index) => _index.toString()} + style={{ ...GS.h100 }} + overScrollMode='never' + showsVerticalScrollIndicator={false} + /> + ) : ( + + {PRODUCTS_QUERY_RESPONSE.data?.geoLocationProductsByPaging?.map( + (item: ProductInfoInterface, index: number) => ( + + + + ), + )} + + ) + ) : ( + + Nothing to buy for now. + + )} + + ); +} + +export default HomeScreen; diff --git a/packages/shop-mobile-expo/src/screens/app/InStore.screen.tsx b/packages/shop-mobile-expo/src/screens/app/InStore.screen.tsx new file mode 100644 index 0000000..03bb5e4 --- /dev/null +++ b/packages/shop-mobile-expo/src/screens/app/InStore.screen.tsx @@ -0,0 +1,175 @@ +import React from 'react'; +import { View, ActivityIndicator, FlatList, StyleSheet } from 'react-native'; +import { useRoute } from '@react-navigation/native'; +import { Title } from 'react-native-paper'; +import { useQuery } from '@apollo/client'; +import PagerView from 'react-native-pager-view'; + +// CONFIGS + +// TYPES/INTERFACES +import type { WarehouseProductInterface } from '../../client/types'; +import type { QueryGetStoreProductsArgs } from '../../client/merchants/argumentInterfaces'; + +// SELECTORS +import { useAppSelector } from '../../store/hooks'; +import { getProductViewType } from '../../store/features/user'; +import { getLanguage } from '../../store/features/translation'; + +// ACTIONS + +// QUERIES +import { GET_STORED_PRODUCT } from '../../client/merchants/queries'; + +// COMPONENTS +import { + FocusAwareStatusBar, + CustomScreenHeader, + ProductItem, +} from '../../components/Common'; + +// STYLES +import { GLOBAL_STYLE as GS } from '../../assets/ts/styles'; + +function InStoreScreen({}) { + // NAVIGATION + const ROUTE = useRoute(); + + // SELECTORS + const LANGUAGE = useAppSelector(getLanguage); + // const USER_DATA = useAppSelector(getUserData); + const VIEW_TYPE = useAppSelector(getProductViewType); + + // ROUTE PARAMS + const WAREHOUSE_ID = + (ROUTE?.params as { warehouseId: string })?.warehouseId || ''; + + // DATA + const WAREHOUSE_PRODUCTS_QUERY_ARGS_INTERFACE: QueryGetStoreProductsArgs = { + storeId: WAREHOUSE_ID, + fullProducts: true, + }; + + // QUERIES + const WAREHOUSE_PRODUCTS_QUERY_RESPONSE = useQuery(GET_STORED_PRODUCT, { + variables: { + ...WAREHOUSE_PRODUCTS_QUERY_ARGS_INTERFACE, + }, + }); + + // STYLES + const STYLES = StyleSheet.create({ + loaderContainer: { ...GS.centered, ...GS.w100, flex: 1 }, + productItemContainer: { + ...GS.mx1, + ...GS.mt2, + ...GS.mb1, + }, + }); + + return ( + + + + + + {WAREHOUSE_PRODUCTS_QUERY_RESPONSE.loading ? ( + + + + ) : WAREHOUSE_PRODUCTS_QUERY_RESPONSE.data?.getStoreProducts && + WAREHOUSE_PRODUCTS_QUERY_RESPONSE.data?.getStoreProducts + .length ? ( + VIEW_TYPE === 'list' ? ( + ( + + + + )} + keyExtractor={(_item, _index) => _index.toString()} + style={{ ...GS.h100 }} + overScrollMode='never' + showsVerticalScrollIndicator={false} + /> + ) : ( + + {WAREHOUSE_PRODUCTS_QUERY_RESPONSE.data?.getStoreProducts?.map( + ( + item: WarehouseProductInterface, + index: number, + ) => ( + + + + ), + )} + + ) + ) : ( + + Nothing to buy for now. + + )} + + ); +} + +export default InStoreScreen; diff --git a/packages/shop-mobile-expo/src/screens/app/MerchantsSearch.screen.tsx b/packages/shop-mobile-expo/src/screens/app/MerchantsSearch.screen.tsx new file mode 100644 index 0000000..8f2f547 --- /dev/null +++ b/packages/shop-mobile-expo/src/screens/app/MerchantsSearch.screen.tsx @@ -0,0 +1,243 @@ +import React from 'react'; +import { View, ActivityIndicator, StyleSheet, ScrollView } from 'react-native'; +import { Button, TextInput, Title, Text } from 'react-native-paper'; +import { useLazyQuery } from '@apollo/client'; +import { useNavigation } from '@react-navigation/native'; +// tslint:disable-next-line: no-implicit-dependencies no-submodule-imports +import MaterialIcon from '@expo/vector-icons/MaterialCommunityIcons'; +import { debounce } from 'lodash'; + +// TYPES/INTERFACES +import { + QueryGetMerchantsByNameArgsInterface, + MerchantsSearchedInterface, +} from '../../client/merchants/argumentInterfaces'; + +// HELPERS +import { isEmpty } from '../../helpers/utils'; + +// STORE +import { useAppSelector } from '../../store/hooks'; +import { getUserData } from '../../store/features/user'; +import { getLanguage } from '../../store/features/translation'; + +// QUERIES +import { GET_MERCHANTS_QUERY } from '../../client/merchants/queries'; + +// COMPONENTS +import { + FocusAwareStatusBar, + CustomScreenHeader, + TouchableCard, +} from '../../components/Common'; + +// STYLES +import { + GLOBAL_STYLE as GS, + CONSTANT_COLOR as CC, + CONSTANT_SIZE as CS, +} from '../../assets/ts/styles'; + +function MerchantsSearch({}) { + // NAVIGATION + const NAVIGATION = useNavigation(); + + // SELECTORS + const LANGUAGE = useAppSelector(getLanguage); + const USER_DATA = useAppSelector(getUserData); + + // STATES + const [searchedValue, setSearchedValue] = React.useState(''); + const [dataLoading, setDataLoading] = React.useState(false); + + // QUERIES + const MERCHANTS_SEARCH_QUERY = useLazyQuery(GET_MERCHANTS_QUERY); + + const STYLES = StyleSheet.create({ + loaderContainer: { ...GS.centered, ...GS.w100, flex: 1 }, + searchContainer: { + ...GS.px2, + ...GS.row, + ...GS.centered, + ...GS.pt2, + height: 90, + backgroundColor: CC.dark, + }, + containerSearchInput: { ...GS.flex1, ...GS.mr2, height: 57 }, + searchInput: { + ...GS.bgLight, + ...GS.flex1, + marginTop: -6, + color: CC.dark, + fontSize: CS.FONT_SIZE - 1, + }, + scanBtn: { ...GS.p0, ...GS.my0, ...GS.justifyCenter, height: 57 }, + searchedText: { ...GS.FF_NunitoBold, ...GS.txtUpper }, + }); + + // FUNCTIONS + const debouncedFetchData = React.useMemo( + () => + debounce((text: string) => { + const MERCHANT_SEARCH_QUERY_ARGS: QueryGetMerchantsByNameArgsInterface = + { + searchName: text, + ...(isEmpty(text) + ? { + geoLocation: { + loc: { + type: 'Point', + coordinates: [ + USER_DATA?.user?.user + .geoLocation?.coordinates + ?.lng || + USER_DATA?.invite + ?.geoLocation + ?.coordinates?.lng || + 0, + USER_DATA?.user?.user + .geoLocation?.coordinates + ?.lat || + USER_DATA?.invite + ?.geoLocation + ?.coordinates?.lat || + 0, + ], + }, + }, + } + : {}), + }; + setDataLoading(false); + MERCHANTS_SEARCH_QUERY[0]({ + variables: MERCHANT_SEARCH_QUERY_ARGS, + }); + }, 500), + // eslint-disable-next-line react-hooks/exhaustive-deps + [], + ); + + const onPressMerchant = (warehouseId: string) => { + if (!isEmpty(warehouseId)) { + const ROUTE_WAREHOUSE_ID = { + warehouseId, + }; + NAVIGATION.navigate( + 'DRAWER/IN_STORE' as never, + ROUTE_WAREHOUSE_ID as never, + ); + } + }; + + // EFFECTS + React.useEffect(() => { + debouncedFetchData(searchedValue); + + return () => debouncedFetchData.cancel(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [searchedValue]); + + return ( + + + + + + + + + } + onChangeText={(text) => { + setDataLoading(true); + setSearchedValue(text); + }} + mode='outlined' + /> + + + + + + + + {!isEmpty(searchedValue) + ? LANGUAGE.MERCHANTS_VIEW.WITH_NAME + + ' "' + + searchedValue + + '"' + : LANGUAGE.MERCHANTS_VIEW.CLOSE_TO_YOU} + + + + {MERCHANTS_SEARCH_QUERY[1].loading || dataLoading ? ( + + + + ) : MERCHANTS_SEARCH_QUERY[1]?.data?.getMerchantsBuyName?.length ? ( + + {(MERCHANTS_SEARCH_QUERY[1]?.data + ? (MERCHANTS_SEARCH_QUERY[1]?.data + ?.getMerchantsBuyName as MerchantsSearchedInterface[]) + : [] + ).map((_item, _index) => ( + onPressMerchant(_item.id)} + /> + ))} + + ) : ( + + {LANGUAGE.NOT_FOUND} + + )} + + ); +} + +export default MerchantsSearch; diff --git a/packages/shop-mobile-expo/src/screens/app/Order.screen.tsx b/packages/shop-mobile-expo/src/screens/app/Order.screen.tsx new file mode 100644 index 0000000..f2cf751 --- /dev/null +++ b/packages/shop-mobile-expo/src/screens/app/Order.screen.tsx @@ -0,0 +1,261 @@ +import React from 'react'; +import MapView from 'react-native-maps'; +import { View, ActivityIndicator, StyleSheet, ScrollView } from 'react-native'; +import { useNavigation, useRoute } from '@react-navigation/native'; +import { Text, Button, Title } from 'react-native-paper'; +import { useQuery } from '@apollo/client'; +// tslint:disable-next-line: no-implicit-dependencies no-submodule-imports +import FontAwesomeIcon from '@expo/vector-icons/FontAwesome'; + +// TYPES/INTERFACES +import type { QueryGeolocationProductsByPagingArgsInterface } from '../../client/products/argumentInterfaces'; + +// SELECTORS +import { useAppDispatch, useAppSelector } from '../../store/hooks'; +import { getUserData } from '../../store/features/user'; + +import { setPreselectedProduct } from '../../store/features/order'; +import { getLanguage } from '../../store/features/translation'; + +// QUERIES +import { GEO_LOCATION_PRODUCTS_BY_PAGING } from '../../client/products/queries'; + +// COMPONENTS +import { + FocusAwareStatusBar, + CustomScreenHeader, +} from '../../components/Common'; + +// STYLES +import { + GLOBAL_STYLE as GS, + CONSTANT_SIZE as CS, + CONSTANT_COLOR as CC, +} from '../../assets/ts/styles'; + +function OrderScreen({}) { + // DISPATCHER + const dispatcher = useAppDispatch(); + + // SELECTORS + const LANGUAGE = useAppSelector(getLanguage); + const USER_DATA = useAppSelector(getUserData); + + // ROUTER + const NAVIGATION = useNavigation(); + const ROUTE = useRoute(); + + // DATA + const PRODUCTS_QUERY_ARGS_INTERFACE: QueryGeolocationProductsByPagingArgsInterface = + { + geoLocation: { + loc: { + type: 'Point', + coordinates: [ + USER_DATA?.user?.user.geoLocation?.coordinates?.lng || + USER_DATA?.invite?.geoLocation?.coordinates?.lng || + 0, + USER_DATA?.user?.user.geoLocation?.coordinates?.lat || + USER_DATA?.invite?.geoLocation?.coordinates?.lat || + 0, + ], + }, + }, + }; + + // QUERIES + const PRODUCTS_QUERY_RESPONSE = useQuery(GEO_LOCATION_PRODUCTS_BY_PAGING, { + variables: { + ...PRODUCTS_QUERY_ARGS_INTERFACE, + }, + }); + + // STYLES + const STYLES = StyleSheet.create({ + loaderContainer: { ...GS.centered, ...GS.w100, flex: 1 }, + productItemContainer: { + ...GS.mx1, + ...GS.mt2, + ...GS.mb1, + }, + map: { ...GS.w100, ...GS.mb5, height: 200 }, + footer: { + ...GS.inlineItems, + ...GS.p2, + }, + footerBtn: { + minWidth: CS.FONT_SIZE_XLG * 6, + }, + footerBtnLabel: { + ...GS.py1, + color: CC.light, + }, + footerUndoBtn: { + backgroundColor: CC.primaryLight, + }, + footerBuyBtn: { + ...GS.bgSecondary, + ...GS.ml1, + flex: 1, + }, + }); + + // EFFECTS + React.useEffect(() => { + const SELECTED_PRODUCT = (ROUTE.params as any)?.selectedProduct; + (() => { + if (!SELECTED_PRODUCT) { + return NAVIGATION.goBack(); + } + console.log('SELECTED_PRODUCT =====>', SELECTED_PRODUCT); + })(); + return () => { + dispatcher(setPreselectedProduct(null)); + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + return ( + + + + + + {PRODUCTS_QUERY_RESPONSE.loading ? ( + + + + ) : ( + + + + + {LANGUAGE.BUY_POPUP.STATUSES_TAKEAWAY.TITLE} + + We will get it in 30-60-minutes + Prepare your wallet (40.00$ in cash) + + + + + + {LANGUAGE.BUY_POPUP.ELAPSED_TIME.TITLE} + + 00:00 + + + + + {LANGUAGE.BUY_POPUP.DELIVERY_STATUS.WE} + + + + + + + + .. + + + + + { + LANGUAGE.BUY_POPUP.DELIVERY_STATUS + .CARRIER + } + + + + + + + + + .. + + + + + {LANGUAGE.BUY_POPUP.DELIVERY_STATUS.YOU} + + + + + + + + + + + + + )} + + ); +} + +export default OrderScreen; diff --git a/packages/shop-mobile-expo/src/screens/app/OrderHistory.screen.tsx b/packages/shop-mobile-expo/src/screens/app/OrderHistory.screen.tsx new file mode 100644 index 0000000..c1b2333 --- /dev/null +++ b/packages/shop-mobile-expo/src/screens/app/OrderHistory.screen.tsx @@ -0,0 +1,129 @@ +import React from 'react'; +import { + View, + ActivityIndicator, + FlatList, + StyleSheet, + ScrollView, +} from 'react-native'; +import { Title } from 'react-native-paper'; +import { useQuery } from '@apollo/client'; +import { showMessage } from 'react-native-flash-message'; + +// STORE +import { useAppSelector } from '../../store/hooks'; +import { getLanguage } from '../../store/features/translation'; +import { getUserData } from '../../store/features/user/index'; + +// QUERIES CLIENT +import { GET_ORDER_HISTORY_QUERY } from '../../client/orders/queries'; +import { QueryGetOrdersArgsInterface } from '../../client/orders/argumentInterfaces'; + +// COMPONENTS +import { + CustomScreenHeader, + FocusAwareStatusBar, + OrderHistoryItem, +} from '../../components/Common'; + +// STYLES +import { + GLOBAL_STYLE as GS, + // CONSTANT_SIZE as CS, + // CONSTANT_COLOR as CC, +} from '../../assets/ts/styles'; + +const OrderHistoryScreen = () => { + // SELECTORS + const LANGUAGE = useAppSelector(getLanguage); + const USER_DATA = useAppSelector(getUserData); + + // QUERIES + const GET_ORDER_HISTORY_QUERY_ARGS: QueryGetOrdersArgsInterface = { + userId: USER_DATA?.user?.user?.id || '', + }; + const ORDERS_QUERY_RES = useQuery(GET_ORDER_HISTORY_QUERY, { + variables: GET_ORDER_HISTORY_QUERY_ARGS, + }); + + // STYLES + const STYLES = StyleSheet.create({ + loaderContainer: { ...GS.centered, ...GS.w100, flex: 1 }, + container: { + ...GS.screen, + ...GS.bgTransparent, + ...GS.px5, + ...GS.pb5, + ...GS.mx5, + zIndex: 1, + }, + orderHistoryItemContainer: { + ...GS.mt3, + ...GS.mb1, + ...GS.mx2, + }, + }); + + // EFFECT + React.useEffect(() => { + showMessage({ + message: 'Please, create a user account', + type: 'warning', + }); + + if (!USER_DATA?.user?.user?.id || ORDERS_QUERY_RES?.data?.Error) { + showMessage({ + message: ORDERS_QUERY_RES?.data?.Error, + type: 'warning', + }); + } + }, [ + ORDERS_QUERY_RES?.data, + ORDERS_QUERY_RES.loading, + USER_DATA?.user?.user?.id, + ]); + + return ( + + + + + + {ORDERS_QUERY_RES?.loading ? ( + + + + ) : ORDERS_QUERY_RES?.data ? ( + ( + + + + )} + keyExtractor={(_item, _index) => _index.toString()} + style={{ ...GS.h100 }} + overScrollMode='never' + showsVerticalScrollIndicator={false} + /> + ) : ( + + + {LANGUAGE.LAST_PURCHASES_VIEW.NOTHING_ORDERED} + + + )} + + ); +}; + +export default OrderHistoryScreen; diff --git a/packages/shop-mobile-expo/src/screens/app/ProductDetails.tsx b/packages/shop-mobile-expo/src/screens/app/ProductDetails.tsx new file mode 100644 index 0000000..84210d8 --- /dev/null +++ b/packages/shop-mobile-expo/src/screens/app/ProductDetails.tsx @@ -0,0 +1,218 @@ +import React from 'react'; +import { useQuery } from '@apollo/client'; +import { useNavigation, useRoute } from '@react-navigation/native'; +import { + StyleSheet, + View, + ScrollView, + Image, + TouchableOpacity, +} from 'react-native'; +import { Text, Paragraph, ActivityIndicator, Avatar } from 'react-native-paper'; + +// HELPERS +import { isEmpty } from '../../helpers/utils'; + +// CLIENTS +import { WAREHOUSE_PRODUCT_QUERY } from '../../client/products/queries'; +import { QueryGetWarehouseProductArgsInterface } from '../../client/products/argumentInterfaces'; + +// STORE +import { useAppSelector } from '../../store/hooks'; +import { getLanguage } from '../../store/features/translation'; + +// COMPONENTS +import { + CustomScreenHeader, + FocusAwareStatusBar, +} from '../../components/Common'; +import BuyProductBtn from '../../components/BuyProductBtn'; + +// STYLES +import { + GLOBAL_STYLE as GS, + CONSTANT_COLOR as CC, + CONSTANT_SIZE as CS, +} from '../../assets/ts/styles'; + +const ProductDetails = () => { + // NAVIGATION + const NAVIGATION = useNavigation(); + const ROUTE = useRoute(); + + // SELECTORS + const LANGUAGE = useAppSelector(getLanguage); + + // QUERIES + const WAREHOUSE_PRODUCT_QUERY_ARGS_INTERFACE: QueryGetWarehouseProductArgsInterface = + { + warehouseId: (ROUTE?.params as any)?.warehouseId || '', + warehouseProductId: (ROUTE?.params as any)?.productId || '', + }; + const WAREHOUSE_PRODUCT_QUERY_RESPONSE = useQuery(WAREHOUSE_PRODUCT_QUERY, { + variables: { + ...WAREHOUSE_PRODUCT_QUERY_ARGS_INTERFACE, + }, + }); + + // DATA + const SLIDER_IMAGE_HEIGHT = 180; + + // LOCAL STYLES + const STYLES = StyleSheet.create({ + header: { + ...GS.mt2, + ...GS.py2, + ...GS.px2, + ...GS.inlineItems, + }, + headerTitle: { + ...GS.FF_NunitoSemiBold, + fontSize: CS.FONT_SIZE_MD, + }, + headerSubtitle: { + color: CC.grayLight, + }, + sliderImagesContainer: { + height: SLIDER_IMAGE_HEIGHT, + }, + sliderImage: { + ...GS.mr1, + height: SLIDER_IMAGE_HEIGHT, + width: CS.WINDOW_WIDTH / 2, + }, + bodyContent: { ...GS.pt5, ...GS.px4 }, + bodyTitle: { + ...GS.FF_NunitoSemiBold, + ...GS.mb2, + fontSize: CS.FONT_SIZE_MD, + }, + buyBtn: { + height: 50, + }, + }); + + // FUNCTION + const onPressProfile = () => { + const ROUTE_WAREHOUSE_ID = { + warehouseId: (ROUTE?.params as any)?.warehouseId || '', + }; + + if (!isEmpty(ROUTE_WAREHOUSE_ID.warehouseId)) { + NAVIGATION.navigate( + 'DRAWER/IN_STORE' as never, + ROUTE_WAREHOUSE_ID as never, + ); + } + }; + + return ( + + + + + + {WAREHOUSE_PRODUCT_QUERY_RESPONSE?.loading ? ( + + + + ) : ( + <> + + + + + + + + { + WAREHOUSE_PRODUCT_QUERY_RESPONSE?.data + ?.getWarehouseProduct?.product?.title[0] + .value + } + + + { + WAREHOUSE_PRODUCT_QUERY_RESPONSE?.data + ?.getWarehouseProduct?.product + ?.description[0].value + } + + + + + + + + + + + {WAREHOUSE_PRODUCT_QUERY_RESPONSE?.data?.getWarehouseProduct?.product?.images?.map( + (image: any, index: number) => ( + + ), + )} + + + + + + {LANGUAGE.PRODUCTS_VIEW.DETAILS.INCLUDES} + + + + { + WAREHOUSE_PRODUCT_QUERY_RESPONSE?.data + ?.getWarehouseProduct?.product?.details[0] + .value + } + + + + + + + + )} + + ); +}; + +export default ProductDetails; diff --git a/packages/shop-mobile-expo/src/screens/app/Search.screen.tsx b/packages/shop-mobile-expo/src/screens/app/Search.screen.tsx new file mode 100644 index 0000000..7aa8435 --- /dev/null +++ b/packages/shop-mobile-expo/src/screens/app/Search.screen.tsx @@ -0,0 +1,302 @@ +import React from 'react'; +import { View, ActivityIndicator, StyleSheet, ScrollView } from 'react-native'; +import { TextInput, Title, Text } from 'react-native-paper'; +import { useLazyQuery } from '@apollo/client'; +import { debounce } from 'lodash'; +import { useNavigation } from '@react-navigation/native'; + +// TYPES/INTERFACES +import type { + QueryGetMerchantsByNameArgsInterface, + MerchantsSearchedInterface, +} from '../../client/merchants/argumentInterfaces'; +import type { ProductInfoInterface } from '../../client/types'; +import type { QueryGeolocationProductsByPagingArgsInterface } from '../../client/products/argumentInterfaces'; + +// HELPERS +import { isEmpty } from '../../helpers/utils'; + +// STORE +import { useAppSelector } from '../../store/hooks'; +import { getUserData } from '../../store/features/user'; +import { getLanguage } from '../../store/features/translation'; + +// QUERIES +import { GET_MERCHANTS_QUERY } from '../../client/merchants/queries'; +import { GEO_LOCATION_PRODUCTS_BY_PAGING } from '../../client/products/queries'; + +// COMPONENTS +import { + FocusAwareStatusBar, + CustomScreenHeader, + TouchableCard, + ProductHistoryItem, +} from '../../components/Common'; + +// STYLES +import { + GLOBAL_STYLE as GS, + CONSTANT_COLOR as CC, + CONSTANT_SIZE as CS, +} from '../../assets/ts/styles'; + +function SearchScreen({}) { + // NAVIGATION + const NAVIGATION = useNavigation(); + + // SELECTORS + const LANGUAGE = useAppSelector(getLanguage); + const USER_DATA = useAppSelector(getUserData); + + // STATES + const [searchedValue, setSearchedValue] = React.useState(''); + const [dataLoading, setDataLoading] = React.useState(false); + + // QUERIES + const MERCHANTS_SEARCH_QUERY = useLazyQuery(GET_MERCHANTS_QUERY); + const PRODUCTS_SEARCH_QUERY = useLazyQuery(GEO_LOCATION_PRODUCTS_BY_PAGING); + + // STYLES + const STYLES = StyleSheet.create({ + loaderContainer: { ...GS.centered, ...GS.w100, flex: 1 }, + searchContainer: { + ...GS.px2, + ...GS.row, + ...GS.centered, + ...GS.pt2, + height: 90, + backgroundColor: CC.dark, + }, + containerSearchInput: { ...GS.flex1, height: 57 }, + searchInput: { + ...GS.bgLight, + ...GS.flex1, + marginTop: -6, + color: CC.dark, + fontSize: CS.FONT_SIZE - 1, + }, + scanBtn: { ...GS.p0, ...GS.my0, ...GS.justifyCenter, height: 57 }, + searchedText: { ...GS.FF_NunitoBold, ...GS.txtUpper }, + }); + + // FUNCTIONS + const debouncedFetchData = React.useMemo( + () => + debounce((text: string) => { + const GEO_LOCATION = { + loc: { + type: 'Point', + coordinates: [ + USER_DATA?.user?.user.geoLocation?.coordinates + ?.lng || + USER_DATA?.invite?.geoLocation?.coordinates + ?.lng || + 0, + USER_DATA?.user?.user.geoLocation?.coordinates + ?.lat || + USER_DATA?.invite?.geoLocation?.coordinates + ?.lat || + 0, + ], + }, + }; + const MERCHANTS_SEARCH_QUERY_ARGS: QueryGetMerchantsByNameArgsInterface = + { + searchName: text, + geoLocation: GEO_LOCATION, + }; + const PRODUCTS_SEARCH_QUERY_ARGS: QueryGeolocationProductsByPagingArgsInterface = + { + geoLocation: GEO_LOCATION, + searchText: text, + }; + + setDataLoading(false); + MERCHANTS_SEARCH_QUERY[0]({ + variables: MERCHANTS_SEARCH_QUERY_ARGS, + }); + PRODUCTS_SEARCH_QUERY[0]({ + variables: PRODUCTS_SEARCH_QUERY_ARGS, + }); + }, 500), + // eslint-disable-next-line react-hooks/exhaustive-deps + [], + ); + + const onPressMerchant = (warehouseId: string) => { + if (!isEmpty(warehouseId)) { + const ROUTE_WAREHOUSE_ID = { + warehouseId, + }; + NAVIGATION.navigate( + 'DRAWER/IN_STORE' as never, + ROUTE_WAREHOUSE_ID as never, + ); + } + }; + + const onPressProduct = (productId: string, warehouseId: string) => { + if (!isEmpty(warehouseId)) { + const ROUTE_PARAMS = { + productId, + warehouseId, + }; + NAVIGATION.navigate( + 'DRAWER/PRODUCT_DETAILS' as never, + ROUTE_PARAMS as never, + ); + } + }; + + // EFFECTS + React.useEffect(() => { + debouncedFetchData(searchedValue); + + return () => debouncedFetchData.cancel(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [searchedValue]); + + return ( + + + + + + + + + } + onChangeText={(text) => { + setDataLoading(true); + setSearchedValue(text); + }} + mode='outlined' + /> + + + + {MERCHANTS_SEARCH_QUERY[1].loading || + PRODUCTS_SEARCH_QUERY[1].loading || + dataLoading ? ( + + + + ) : MERCHANTS_SEARCH_QUERY[1]?.data?.getMerchantsBuyName?.length || + PRODUCTS_SEARCH_QUERY[1]?.data?.geoLocationProductsByPaging + ?.length ? ( + + + + + {LANGUAGE.MERCHANTS} + + + + {(MERCHANTS_SEARCH_QUERY[1]?.data + ? (MERCHANTS_SEARCH_QUERY[1]?.data + ?.getMerchantsBuyName as MerchantsSearchedInterface[]) + : [] + ).map((_item, _index) => ( + onPressMerchant(_item?.id)} + /> + ))} + + + + + + {LANGUAGE.PRODUCTS_VIEW.TITLE} + + + + {(PRODUCTS_SEARCH_QUERY[1]?.data + ? (PRODUCTS_SEARCH_QUERY[1]?.data + ?.geoLocationProductsByPaging as ProductInfoInterface[]) + : [] + ).map((_item, _index) => ( + + + onPressProduct( + _item?.warehouseProduct.id, + _item?.warehouseId, + ) + } + /> + + ))} + + + ) : ( + + {LANGUAGE.SEARCH_VIEW.EMPTY_LIST} + + )} + + ); +} + +export default SearchScreen; diff --git a/packages/shop-mobile-expo/src/screens/app/Translation.screen.tsx b/packages/shop-mobile-expo/src/screens/app/Translation.screen.tsx new file mode 100644 index 0000000..f48ae21 --- /dev/null +++ b/packages/shop-mobile-expo/src/screens/app/Translation.screen.tsx @@ -0,0 +1,115 @@ +import React from 'react'; +import { View, ScrollView, StyleSheet } from 'react-native'; +import { RadioButton } from 'react-native-paper'; + +// TYPES +import type { supportedLangType } from '../../store/features/translation/types'; + +// ACTIONS & SELECTORS +import { useAppSelector, useAppDispatch } from '../../store/hooks'; +import { + getLang, + getLanguage, + supportedLangs, + setLang, +} from '../../store/features/translation'; + +// COMPONENTS +import { + TouchableCard, + FocusAwareStatusBar, + PaperText, + CustomScreenHeader, +} from '../../components/Common'; +// STYLES +import { + GLOBAL_STYLE as GS, + CONSTANT_COLOR as CC, + CONSTANT_SIZE as CS, +} from '../../assets/ts/styles'; + +function TranslationScreen({}) { + // ACTIONS + const translate = useAppDispatch(); + + // SELECTORS + const currentLang = useAppSelector(getLang); + const languages = useAppSelector(getLanguage); + + const STYLES = StyleSheet.create({ + container: { ...GS.screen }, + scrollView: { + ...GS.h100, + ...GS.pt4, + ...GS.px2, + ...GS.bgLight, + }, + langItem: { ...GS.inlineItems, ...GS.mb2 }, + langItemCard: { ...GS.w100, borderRadius: 5 }, + langItemCardContent: { borderRadius: 0 }, + }); + + return ( + + + + + + {Object.keys(supportedLangs).map((lang: string, id) => { + const L = lang as supportedLangType; + + return ( + translate(setLang(L))}> + + + { + // @ts-ignore + languages[lang] + } + + + translate(setLang(L))} + /> + + + ); + })} + + + ); +} + +export default TranslationScreen; diff --git a/packages/shop-mobile-expo/src/screens/authentication/Home.screen.tsx b/packages/shop-mobile-expo/src/screens/authentication/Home.screen.tsx new file mode 100644 index 0000000..de772ac --- /dev/null +++ b/packages/shop-mobile-expo/src/screens/authentication/Home.screen.tsx @@ -0,0 +1,169 @@ +import React from 'react'; +import { Text, View, Image, StyleSheet } from 'react-native'; +import { useNavigation } from '@react-navigation/native'; +import { Button } from 'react-native-paper'; +// tslint:disable-next-line: no-implicit-dependencies no-submodule-imports +import AntDesignIcon from '@expo/vector-icons/AntDesign'; + +// ACTIONS & SELECTORS +import { useAppSelector } from '../../store/hooks'; +import { getLanguage } from '../../store/features/translation'; + +// COMPONENTS +import { FocusAwareStatusBar, PaperText } from '../../components/Common'; + +// STYLES +import { + GLOBAL_STYLE as GS, + CONSTANT_SIZE as CS, + CONSTANT_COLOR as CC, +} from '../../assets/ts/styles'; + +const STYLES = StyleSheet.create({ + container: { + ...GS.screen, + ...GS.bgTransparent, + ...GS.px5, + ...GS.pb5, + ...GS.mx5, + zIndex: 1, + }, + titleLogoContainer: { + ...GS.centered, + flex: 1, + }, + logoImg: { ...GS.w100, height: 100, marginBottom: -20 }, + logoTitle: { + ...GS.txtCapitalize, + fontSize: CS.FONT_SIZE + 1, + opacity: 0.7, + }, + networkBtnFacebook: { flex: 1, backgroundColor: CC.facebook }, + networkBtnGoogle: { flex: 1, backgroundColor: CC.google }, +}); + +const HomeAuthScreen = () => { + // SELECTORS + const currentLanguage = useAppSelector(getLanguage); + + // NAVIGATION + const navigation = useNavigation(); + + // FUNCTIONS + const onPressSignUp = () => { + navigation.navigate('STACK/SIGN_UP' as never); + }; + + const onPressSignIn = () => { + navigation.navigate('STACK/SIGN_IN' as never); + }; + + const onPressInvite = () => { + navigation.navigate('STACK/SIGN_UP_BY_ADDRESS' as never); + }; + + return ( + + + + + {/* title logo */} + + + + {currentLanguage.INVITE_VIEW.BY_CODE.LOGO.DETAILS} + + + + {/* Social Networks buttons */} + + + + + + + + + + + + + + + + + + {currentLanguage.OR}{' '} + + {currentLanguage.INVITE_VIEW.BY_CODE.OR_WHAT} + + + + + + + + + + + ); +}; + +export default HomeAuthScreen; diff --git a/packages/shop-mobile-expo/src/screens/authentication/SignIn.screen.tsx b/packages/shop-mobile-expo/src/screens/authentication/SignIn.screen.tsx new file mode 100644 index 0000000..f6c88c6 --- /dev/null +++ b/packages/shop-mobile-expo/src/screens/authentication/SignIn.screen.tsx @@ -0,0 +1,326 @@ +import React from 'react'; +import { + View, + StyleSheet, + ScrollView, + TextInput as NativeTextInput, + TouchableWithoutFeedback, + Keyboard, +} from 'react-native'; +import { Text, TextInput, Button, HelperText } from 'react-native-paper'; +import { showMessage } from 'react-native-flash-message'; +import { validate } from 'validate.js'; +import { useMutation } from '@apollo/client'; + +// TYPES/INTERFACES +import type { UserLoginArgsInterface } from '../../client/user/argumentInterfaces'; + +// CONSTANTS +import GROUPS from '../../router/groups.routes'; +import { + REQUIRE_EMAIL, + REQUIRE_NOT_EMPTY_PRESENCE, +} from '../../constants/rules.validate'; + +// MUTATIONS +import { USER_LOGIN } from '../../client/user/mutations'; + +// ACTIONS & SELECTORS +import { useAppDispatch } from '../../store/hooks'; +import { onUserSignUpByAddressSuccess } from '../../store/features/user'; +import { setGroup } from '../../store/features/navigation'; + +// COMPONENTS +import { FocusAwareStatusBar } from '../../components/Common'; + +// STYLES +import { + GLOBAL_STYLE as GS, + CONSTANT_SIZE as CS, + CONSTANT_COLOR as CC, +} from '../../assets/ts/styles'; + +// TYPE +export type FormInputNameType = 'email' | 'password'; +export type FormType = { + [name in FormInputNameType]: string; +}; +export type FormErrorsType = + | { + [name in FormInputNameType]: string[] | undefined; + // tslint:disable-next-line: indent + } + | { [name: string]: string[] | undefined }; + +const SignInScreen = () => { + // ACTIONS + const reduxDispatch = useAppDispatch(); + + // STATES + const [form, setForm] = React.useState({ + email: '', + password: '', + }); + const [formErrors, setFormErrors] = React.useState({}); + const [securePassword, setSecurePassword] = React.useState(true); + const [submitFormLoading, setSubmitFormLoading] = + React.useState(false); + + // DATA + const VALIDATION_CONSTRAINT: { [name in FormInputNameType]?: object } = { + email: REQUIRE_EMAIL, + password: { + ...REQUIRE_NOT_EMPTY_PRESENCE, + length: { minimum: 6 }, + }, + }; + + // REFS + const SCREEN_SCROLL_VIEW_REF = React.useRef(null); + const EMAIL_INPUT_REF = React.useRef(null); + const PASSWORD_INPUT_REF = React.useRef(null); + + // MUTATIONS + const [handleUserLogin] = useMutation(USER_LOGIN); + + // LOCAL STYLES + const STYLES = StyleSheet.create({ + screen: { + ...GS.screen, + ...GS.bgSuccess, + overflow: 'hidden', + }, + container: { + ...GS.screen, + ...GS.bgTransparent, + ...GS.px5, + ...GS.pb5, + ...GS.flex1, + }, + section1Title: { + ...GS.txtCenter, + ...GS.FF_NunitoBold, + ...GS.mb5, + fontSize: CS.FONT_SIZE_LG * 1.8, + }, + formContainer: { + ...GS.w100, + }, + formInputContainer: {}, + formInput: { ...GS.bgTransparent, ...GS.mb0, textAlign: 'center' }, + formInputDisabled: { opacity: 0.4 }, + formBtn: { ...GS.mb2 }, + formBtnLabel: { + ...GS.py1, + ...GS.txtCapitalize, + color: CC.light, + fontSize: CS.FONT_SIZE + 3, + }, + formSubmitBtn: { + ...GS.w100, + ...(submitFormLoading + ? { + backgroundColor: CC.secondaryLight, + // tslint:disable-next-line: indent + } + : GS.bgSecondary), + }, + formSkipBtn: { ...GS.bgLight }, + formErrorHelperText: { + textAlign: 'center', + }, + formErrorHelperTextApartment: { + ...GS.mb2, + marginTop: -(CS.SPACE - 5), + }, + }); + + // FUNCTIONS + const onSubmitForm = () => { + setFormErrors({}); + + const FORMATTED_FORM = { + ...form, + }; + + const FORMATTED_CONSTRAINTS = { + ...VALIDATION_CONSTRAINT, + }; + + const VALIDATION_RESULT = validate( + FORMATTED_FORM, + FORMATTED_CONSTRAINTS, + ); + + if (VALIDATION_RESULT) { + setFormErrors(VALIDATION_RESULT); + SCREEN_SCROLL_VIEW_REF?.current?.scrollTo({ y: 0 }); + return; + } + + setSubmitFormLoading(true); + const USER_LOGIN_INPUT: UserLoginArgsInterface = { + email: FORMATTED_FORM.email, + password: FORMATTED_FORM.password, + }; + + handleUserLogin({ + variables: { + ...USER_LOGIN_INPUT, + }, + onCompleted: (TData) => { + console.log('TData', TData); + setSubmitFormLoading(false); + if (!TData?.userLogin) { + showMessage({ + message: 'Wrong email or password', + type: 'warning', + }); + return; + } + + reduxDispatch( + onUserSignUpByAddressSuccess({ + user: TData.userLogin, + invite: null, + }), + ); + reduxDispatch(setGroup(GROUPS.APP)); + showMessage({ + message: "Welcome back 🎉, you're sign-in as user", + type: 'success', + }); + }, + onError: (ApolloError) => { + console.log('ApolloError ==>', ApolloError, USER_LOGIN_INPUT); + showMessage({ + message: ApolloError.name, + description: ApolloError.message, + type: 'danger', + }); + setSubmitFormLoading(false); + }, + }); + }; + + return ( + + + + {/* Loading view */} + Keyboard.dismiss()}> + + + + Sign In + + + + PASSWORD_INPUT_REF?.current?.focus() + } + onChangeText={(text) => + setForm((prevForm) => ({ + ...prevForm, + email: text, + })) + } + /> + + + {formErrors?.email + ? formErrors.email[0] + : ''} + + + + + + setSecurePassword( + !securePassword, + ) + } + /> + } + error={!!formErrors.password} + mode='outlined' + returnKeyLabel='done' + returnKeyType='done' + onSubmitEditing={onSubmitForm} + onChangeText={(text) => + setForm((prevForm) => ({ + ...prevForm, + password: text, + })) + } + /> + + + {formErrors?.password + ? formErrors.password[0] + : ''} + + + + + + + + + + ); +}; + +export default SignInScreen; diff --git a/packages/shop-mobile-expo/src/screens/authentication/SignUp.screen.tsx b/packages/shop-mobile-expo/src/screens/authentication/SignUp.screen.tsx new file mode 100644 index 0000000..352348d --- /dev/null +++ b/packages/shop-mobile-expo/src/screens/authentication/SignUp.screen.tsx @@ -0,0 +1,934 @@ +import React from 'react'; +import { + View, + StyleSheet, + ScrollView, + Alert, + TouchableOpacity, + TextInput as NativeTextInput, +} from 'react-native'; +import { + ActivityIndicator, + TextInput, + Button, + HelperText, + Checkbox, +} from 'react-native-paper'; +import { useNavigation, useRoute } from '@react-navigation/native'; +import * as Location from 'expo-location'; +import { showMessage } from 'react-native-flash-message'; +import { validate } from 'validate.js'; +import { useMutation } from '@apollo/client'; + +// TYPES/INTERFACES +import type { + UserLoginArgsInterface, + UserRegisterArgsInterface, +} from '../../client/user/argumentInterfaces'; + +// CONSTANTS +import GROUPS from '../../router/groups.routes'; +import { + REQUIRE_EMAIL, + REQUIRE_NOT_EMPTY_PRESENCE, +} from '../../constants/rules.validate'; + +// MUTATIONS +import { + REGISTER_USER_MUTATION, + USER_LOGIN, +} from '../../client/user/mutations'; + +// ACTIONS & SELECTORS +import { useAppSelector, useAppDispatch } from '../../store/hooks'; +import { + onUserSignUpByAddressSuccess, + getUserData, +} from '../../store/features/user'; +import { getLanguage } from '../../store/features/translation'; +import { setGroup } from '../../store/features/navigation'; + +// HELPERS +import { + getFormattedLocation, + FormattedLocationInterface, +} from '../../helpers/location'; + +// COMPONENTS +import { FocusAwareStatusBar, PaperText } from '../../components/Common'; + +// STYLES +import { + GLOBAL_STYLE as GS, + CONSTANT_SIZE as CS, + CONSTANT_COLOR as CC, +} from '../../assets/ts/styles'; + +// TYPE +export type FormInputNameType = + | 'email' + | 'firstName' + | 'lastName' + | 'phone' + | 'password' + | 'confirmPassword' + | 'city' + | 'street' + | 'house' + | 'apartment'; +export type FormType = { + [name in FormInputNameType]: string; +}; +export type FormErrorsType = + | { + [name in FormInputNameType]: string[] | undefined; + // tslint:disable-next-line: indent + } + | { [name: string]: string[] | undefined }; + +const SignUpScreen = () => { + // SELECTORS + const CURRENT_LANGUAGE = useAppSelector(getLanguage); + const CURRENT_USER_DATA = useAppSelector(getUserData); + + // ACTIONS + const reduxDispatch = useAppDispatch(); + + // NAVIGATION + const NAVIGATION = useNavigation(); + const ROUTE = useRoute(); + + // STATES + const [warningDialog, setWarningDialog] = React.useState(false); + const [form, setForm] = React.useState({ + email: '', + firstName: '', + lastName: '', + password: '', + confirmPassword: '', + phone: '', + city: '', + street: '', + house: '', + apartment: '', + }); + const [formApartmentCheckbox, setFormApartmentCheckbox] = + React.useState(true); + const [formErrors, setFormErrors] = React.useState({}); + const [canGoBack, setCanGoBack] = React.useState(false); + const [, /* preventBackCallBack */ setPreventBackCallBack] = React.useState< + () => any + >(() => {}); + const [, setCurrentPosition] = + React.useState(null); + const [formattedLocation, setFormattedLocation] = + React.useState(null); + const [securePassword, setSecurePassword] = React.useState(true); + const [secureConfirmPassword, setSecureConfirmPassword] = + React.useState(true); + const [addressLoading, setAddressLoading] = React.useState(true); + const [submitFormLoading, setSubmitFormLoading] = + React.useState(false); + + // DATA + const ROUTE_PARAMS = ROUTE.params as any; + const VALIDATION_CONSTRAINT: { [name in FormInputNameType]?: object } = { + email: REQUIRE_EMAIL, + password: { + ...REQUIRE_NOT_EMPTY_PRESENCE, + length: { minimum: 6 }, + }, + confirmPassword: { + equality: 'password', + }, + firstName: { ...REQUIRE_NOT_EMPTY_PRESENCE }, + lastName: { ...REQUIRE_NOT_EMPTY_PRESENCE }, + city: REQUIRE_NOT_EMPTY_PRESENCE, + street: REQUIRE_NOT_EMPTY_PRESENCE, + house: REQUIRE_NOT_EMPTY_PRESENCE, + apartment: REQUIRE_NOT_EMPTY_PRESENCE, + }; + + // REFS + const SCREEN_SCROLL_VIEW_REF = React.useRef(null); + const EMAIL_INPUT_REF = React.useRef(null); + const PASSWORD_INPUT_REF = React.useRef(null); + const CONFIRM_PASSWORD_INPUT_REF = React.useRef( + null, + ); + const FIRST_NAME_INPUT_REF = React.useRef(null); + const LAST_NAME_INPUT_REF = React.useRef(null); + const CITY_INPUT_REF = React.useRef(null); + const STREET_INPUT_REF = React.useRef(null); + const HOUSE_INPUT_REF = React.useRef(null); + const APARTMENT_INPUT_REF = React.useRef(null); + + // MUTATIONS + const [handleUserRegistration] = useMutation(REGISTER_USER_MUTATION); + const [handleUserLogin] = useMutation(USER_LOGIN); + + // LOCAL STYLES + const STYLES = StyleSheet.create({ + screen: { + ...GS.screen, + ...GS.bgSuccess, + overflow: 'hidden', + }, + container: { + ...GS.screen, + ...GS.centered, + ...GS.bgTransparent, + ...GS.px5, + ...GS.pb5, + }, + section1: { + ...GS.centered, + ...GS.py5, + ...GS.my4, + }, + section1Title: { + ...GS.txtCenter, + ...GS.FF_NunitoBold, + fontSize: CS.FONT_SIZE_LG * 1.8, + }, + section1SubTitle: { + ...GS.txtCenter, + ...GS.FF_NunitoBold, + fontSize: CS.FONT_SIZE_MD, + opacity: 0.6, + }, + section2: { ...GS.py2, ...GS.w100, alignItems: 'center' }, + section2Title: { + ...GS.txtCenter, + ...GS.mb5, + ...GS.FF_NunitoBold, + fontSize: CS.FONT_SIZE_SM * 2, + }, + formContainer: { + ...GS.w100, + }, + formInputContainer: {}, + formInputContainerRow: { flex: 1 }, + formInput: { ...GS.bgTransparent, ...GS.mb0, textAlign: 'center' }, + formInputDisabled: { opacity: 0.4 }, + formBtn: { ...GS.mb2 }, + formBtnLabel: { + ...GS.py1, + ...GS.txtCapitalize, + color: CC.light, + fontSize: CS.FONT_SIZE + 3, + }, + formSubmitBtn: { + ...(submitFormLoading + ? { + backgroundColor: CC.secondaryLight, + // tslint:disable-next-line: indent + } + : GS.bgSecondary), + }, + formSkipBtn: { ...GS.bgLight }, + formErrorHelperText: { + textAlign: 'center', + }, + formErrorHelperTextApartment: { + ...GS.mb2, + marginTop: -(CS.SPACE - 5), + }, + }); + + // FUNCTIONS + const onSubmitForm = () => { + setFormErrors({}); + + const FORMATTED_FORM = { + ...formattedLocation, + ...form, + }; + + const FORMATTED_CONSTRAINTS = { + ...VALIDATION_CONSTRAINT, + }; + + if (!formApartmentCheckbox) { + delete FORMATTED_CONSTRAINTS.apartment; + } + + const VALIDATION_RESULT = validate( + FORMATTED_FORM, + FORMATTED_CONSTRAINTS, + ); + + if (VALIDATION_RESULT) { + setFormErrors(VALIDATION_RESULT); + SCREEN_SCROLL_VIEW_REF?.current?.scrollTo({ y: 0 }); + return; + } + setSubmitFormLoading(true); + const CREATE_INVITE_INPUT: UserRegisterArgsInterface = { + user: { + email: FORMATTED_FORM.email, + firstName: FORMATTED_FORM.firstName, + lastName: FORMATTED_FORM.lastName, + apartment: FORMATTED_FORM.apartment, + geoLocation: { + countryId: 0, + city: FORMATTED_FORM.city, + streetAddress: FORMATTED_FORM.street, + house: FORMATTED_FORM.house, + postcode: null, + notes: null, + loc: { + type: 'Point', + coordinates: [ + FORMATTED_FORM?.longitude || 0, + FORMATTED_FORM?.latitude || 0, + ], + }, + }, + }, + password: form.password, + }; + + handleUserRegistration({ + variables: { + registerInput: CREATE_INVITE_INPUT, + }, + onCompleted: (TData) => { + if (!TData?.registerUser) { + setSubmitFormLoading(false); + } + + const USER_LOGIN_INPUT: UserLoginArgsInterface = { + email: TData.registerUser.email, + password: FORMATTED_FORM.password, + }; + + handleUserLogin({ + variables: { + ...USER_LOGIN_INPUT, + }, + onCompleted: (TDataSignIn) => { + console.log('TDataSignIn', TDataSignIn); + setSubmitFormLoading(false); + if (!TDataSignIn?.userLogin) { + showMessage({ + message: 'Wrong email or password', + type: 'warning', + }); + return; + } + + reduxDispatch( + onUserSignUpByAddressSuccess({ + user: TDataSignIn.userLogin, + invite: null, + }), + ); + + reduxDispatch(setGroup(GROUPS.APP)); + + showMessage({ + message: "Great job 🎉, you're sign-up as user", + type: 'success', + }); + }, + onError: (ApolloError) => { + console.log( + 'ApolloError ==>', + ApolloError, + USER_LOGIN_INPUT, + ); + showMessage({ + message: ApolloError.name, + description: ApolloError.message, + type: 'danger', + }); + setSubmitFormLoading(false); + }, + }); + }, + onError: (ApolloError) => { + console.log( + 'ApolloError ==>', + ApolloError, + CREATE_INVITE_INPUT, + ); + showMessage({ + message: ApolloError.name, + description: ApolloError.message, + type: 'danger', + }); + setSubmitFormLoading(false); + }, + }); + }; + + // EFFECTS + React.useEffect(() => { + if (!ROUTE_PARAMS?.fromApp) { + setAddressLoading(true); + (async () => { + const { status } = + await Location.requestForegroundPermissionsAsync(); + + if (status !== 'granted') { + const ERROR_MSG = + 'Permission to access location was denied'; + showMessage({ message: ERROR_MSG }); + setCanGoBack(true); + setTimeout(() => { + NAVIGATION.goBack(); + }, 100); + return; + } + + const CURRENT_POSITION = await Location.getCurrentPositionAsync( + {}, + ); + const FORMATTED_ADDRESS = await getFormattedLocation( + CURRENT_POSITION.coords, + ); + setForm({ + ...form, + city: FORMATTED_ADDRESS?.city || '', + street: FORMATTED_ADDRESS?.streetAddress || '', + }); + setCurrentPosition(CURRENT_POSITION); + setFormattedLocation(FORMATTED_ADDRESS); + setAddressLoading(false); + })(); + } else { + setAddressLoading(false); + if (CURRENT_USER_DATA?.invite?.__typename === 'Invite') { + setForm({ + ...form, + city: CURRENT_USER_DATA?.invite?.geoLocation.city, + street: CURRENT_USER_DATA?.invite?.geoLocation + .streetAddress, + house: CURRENT_USER_DATA?.invite?.geoLocation.house, + apartment: CURRENT_USER_DATA?.invite?.apartment?.toString(), + }); + } + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + React.useEffect(() => { + NAVIGATION.addListener('beforeRemove', (e) => { + if (canGoBack) { + return; + } + + // Prevent default behavior of leaving the screen + e.preventDefault(); + setWarningDialog(true); + + // Prompt the user before leaving the screen + setPreventBackCallBack(() => () => { + setCanGoBack(true); + setWarningDialog(false); + NAVIGATION.dispatch(e.data.action); + }); + + Alert.alert( + 'Leave sign-up?', + "Your account isn't yet created! Are you sure to leave the screen?", + [ + { text: "Don't leave", style: 'cancel', onPress: () => {} }, + { + text: 'leave', + style: 'destructive', + onPress: () => { + setCanGoBack(true); + NAVIGATION.dispatch(e.data.action); + }, + }, + ], + ); + }); + + return () => NAVIGATION.removeListener('beforeRemove', () => null); + }, [NAVIGATION, canGoBack, warningDialog]); + + return ( + + + + {/* Loading view */} + + {/* section1 */} + + Sign Up + + + + {addressLoading ? ( + <> + + { + CURRENT_LANGUAGE.INVITE_VIEW + .DETECTING_LOCATION + } + + + + ) : ( + + + + PASSWORD_INPUT_REF?.current?.focus() + } + onChangeText={(text) => + setForm((prevForm) => ({ + ...prevForm, + email: text, + })) + } + /> + + + {formErrors?.email + ? formErrors.email[0] + : ''} + + + + + + + setSecurePassword( + !securePassword, + ) + } + /> + } + error={!!formErrors.password} + mode='outlined' + returnKeyLabel='next' + returnKeyType='next' + onSubmitEditing={() => + CONFIRM_PASSWORD_INPUT_REF?.current?.focus() + } + onChangeText={(text) => + setForm((prevForm) => ({ + ...prevForm, + password: text, + })) + } + /> + + + {formErrors?.password + ? formErrors.password[0] + : ''} + + + + + + setSecureConfirmPassword( + !secureConfirmPassword, + ) + } + /> + } + error={!!formErrors.confirmPassword} + mode='outlined' + returnKeyLabel='next' + returnKeyType='next' + onSubmitEditing={() => + FIRST_NAME_INPUT_REF?.current?.focus() + } + onChangeText={(text) => + setForm((prevForm) => ({ + ...prevForm, + confirmPassword: text, + })) + } + /> + + + {formErrors?.confirmPassword + ? formErrors.confirmPassword[0] + : ''} + + + + + + + LAST_NAME_INPUT_REF?.current?.focus() + } + onChangeText={(text) => + setForm((prevForm) => ({ + ...prevForm, + firstName: text, + })) + } + /> + + + {formErrors?.firstName + ? formErrors.firstName[0] + : ''} + + + + + + CITY_INPUT_REF?.current?.focus() + } + onChangeText={(text) => + setForm((prevForm) => ({ + ...prevForm, + lastName: text, + })) + } + /> + + + {formErrors?.lastName + ? formErrors.lastName[0] + : ''} + + + + + + + STREET_INPUT_REF?.current?.focus() + } + onChangeText={(text) => + setForm((prevForm) => ({ + ...prevForm, + city: text, + })) + } + /> + + + {formErrors?.city + ? formErrors.city[0] + : ''} + + + + + + HOUSE_INPUT_REF?.current?.focus() + } + onChangeText={(text) => + setForm((prevForm) => ({ + ...prevForm, + street: text, + })) + } + /> + + {formErrors.street + ? formErrors.street[0] + : ''} + + + + + + + + APARTMENT_INPUT_REF?.current?.focus() + } + onChangeText={(text) => + setForm((prevForm) => ({ + ...prevForm, + house: text, + })) + } + /> + + {formErrors.house + ? formErrors.house[0] + : ''} + + + + + + setForm((prevForm) => ({ + ...prevForm, + apartment: text, + })) + } + /> + + + setFormApartmentCheckbox( + !formApartmentCheckbox, + ) + } + style={{ + ...GS.justifyContentBetween, + ...GS.mb0, + }}> + + {CURRENT_LANGUAGE.APARTMENT} + + + + setFormApartmentCheckbox( + !formApartmentCheckbox, + ) + } + /> + + + + {formErrors.apartment + ? formErrors.apartment[0] + : ''} + + + + + + + )} + + + + ); +}; + +export default SignUpScreen; diff --git a/packages/shop-mobile-expo/src/screens/authentication/SignUpByAddress.screen.tsx b/packages/shop-mobile-expo/src/screens/authentication/SignUpByAddress.screen.tsx new file mode 100644 index 0000000..8aca269 --- /dev/null +++ b/packages/shop-mobile-expo/src/screens/authentication/SignUpByAddress.screen.tsx @@ -0,0 +1,646 @@ +import React from 'react'; +import { + View, + StyleSheet, + ScrollView, + Alert, + TouchableOpacity, + // Button as NativeBtn, + TextInput as NativeTextInput, +} from 'react-native'; +import { + ActivityIndicator, + TextInput, + Button, + HelperText, + Checkbox, + Text, +} from 'react-native-paper'; +import { useNavigation } from '@react-navigation/native'; +import * as Location from 'expo-location'; +import { showMessage } from 'react-native-flash-message'; +import { validate } from 'validate.js'; +import { useMutation } from '@apollo/client'; + +// TYPES/INTERFACES +import { CreateInviteByLocationMutationArgsInterface } from '../../client/invite/argumentInterfaces'; + +// CONSTANTS +import GROUPS from '../../router/groups.routes'; +import { REQUIRE_NOT_EMPTY_PRESENCE } from '../../constants/rules.validate'; + +// MUTATIONS +import { CREATE_INVITE_BY_LOCATION_MUTATION } from '../../client/invite/mutations'; + +// ACTIONS & SELECTORS +import { useAppSelector, useAppDispatch } from '../../store/hooks'; +import { onUserSignUpByAddressSuccess } from '../../store/features/user'; +import { getLanguage } from '../../store/features/translation'; +import { setGroup } from '../../store/features/navigation'; + +// HELPERS +import { + getFormattedLocation, + FormattedLocationInterface, +} from '../../helpers/location'; + +// COMPONENTS +import { FocusAwareStatusBar, PaperText } from '../../components/Common'; + +// STYLES +import { + GLOBAL_STYLE as GS, + CONSTANT_SIZE as CS, + CONSTANT_COLOR as CC, +} from '../../assets/ts/styles'; + +// TYPE +export type FormInputNameType = 'city' | 'street' | 'house' | 'apartment'; +export type FormType = { + [name in FormInputNameType]: string; +}; +export type FormErrorsType = + | { + [name in FormInputNameType]: string[] | undefined; + // tslint:disable-next-line: indent + } + | { [name: string]: string[] | undefined }; + +const SignUpByAddressScreen = () => { + // SELECTORS + const CURRENT_LANGUAGE = useAppSelector(getLanguage); + + // ACTIONS + const reduxDispatch = useAppDispatch(); + + // NAVIGATION + const NAVIGATION = useNavigation(); + + // STATES + const [warningDialog, setWarningDialog] = React.useState(false); + const [form, setForm] = React.useState({ + city: '', + street: '', + house: '', + apartment: '', + }); + const [formApartmentCheckbox, setFormApartmentCheckbox] = + React.useState(true); + const [formErrors, setFormErrors] = React.useState({}); + const [canGoBack, setCanGoBack] = React.useState(false); + const [, /* preventBackCallBack */ setPreventBackCallBack] = React.useState< + () => any + >(() => {}); + const [, setCurrentPosition] = + React.useState(null); + const [formattedLocation, setFormattedLocation] = + React.useState(null); + const [addressLoading, setAddressLoading] = React.useState(true); + const [submitFormLoading, setSubmitFormLoading] = + React.useState(false); + + // DATA + const STYLES = StyleSheet.create({ + screen: { + ...GS.screen, + ...GS.bgSuccess, + overflow: 'hidden', + }, + container: { + ...GS.screen, + ...GS.centered, + ...GS.bgTransparent, + ...GS.px5, + ...GS.pb5, + }, + section1: { + ...GS.centered, + ...GS.pt5, + ...GS.mt5, + ...GS.pb3, + ...GS.mb3, + marginTop: CS.FONT_SIZE_LG * 3, + }, + section1Title: { + ...GS.txtCenter, + ...GS.mb3, + ...GS.FF_NunitoBold, + fontSize: CS.FONT_SIZE_LG * 1.8, + }, + section1SubTitle: { + ...GS.txtCenter, + ...GS.FF_NunitoBold, + fontSize: CS.FONT_SIZE_MD, + opacity: 0.6, + }, + section2: { ...GS.py2, ...GS.w100, alignItems: 'center' }, + section2Title: { + ...GS.txtCenter, + ...GS.mb5, + ...GS.FF_NunitoBold, + fontSize: CS.FONT_SIZE_SM * 2, + }, + formContainer: { + ...GS.w100, + }, + formInputContainer: {}, + formInputContainerRow: { flex: 1 }, + formInput: { ...GS.bgTransparent, ...GS.mb0, textAlign: 'center' }, + formInputDisabled: { opacity: 0.4 }, + formBtn: { ...GS.mb2 }, + formBtnLabel: { + ...GS.py1, + ...GS.txtCapitalize, + color: CC.light, + fontSize: CS.FONT_SIZE + 3, + }, + formSubmitBtn: { + ...(submitFormLoading + ? { + backgroundColor: CC.secondaryLight, + // tslint:disable-next-line: indent + } + : GS.bgSecondary), + }, + formSkipBtn: { ...GS.bgLight }, + formErrorHelperText: { + textAlign: 'center', + }, + formErrorHelperTextApartment: { + ...GS.mb2, + marginTop: -(CS.SPACE - 5), + }, + }); + + // TODO: Add more constraints + const VALIDATION_CONSTRAINT: { [name in FormInputNameType]?: object } = { + city: REQUIRE_NOT_EMPTY_PRESENCE, + street: REQUIRE_NOT_EMPTY_PRESENCE, + house: REQUIRE_NOT_EMPTY_PRESENCE, + apartment: REQUIRE_NOT_EMPTY_PRESENCE, + }; + + // REFS + const SCREEN_SCROLL_VIEW_REF = React.useRef(null); + const CITY_INPUT_REF = React.useRef(null); + const STREET_INPUT_REF = React.useRef(null); + const HOUSE_INPUT_REF = React.useRef(null); + const APARTMENT_INPUT_REF = React.useRef(null); + + // MUTATIONS + const [handleCreatInviteByLocation] = useMutation( + CREATE_INVITE_BY_LOCATION_MUTATION, + ); + + // FUNCTIONS + const onSubmitForm = () => { + setFormErrors({}); + + const FORMATTED_FORM = { + ...form, + ...formattedLocation, + }; + + const FORMATTED_CONSTRAINTS = { + ...VALIDATION_CONSTRAINT, + }; + + if (!formApartmentCheckbox) { + delete FORMATTED_CONSTRAINTS.apartment; + } + + const VALIDATION_RESULT = validate( + FORMATTED_FORM, + FORMATTED_CONSTRAINTS, + ); + + if (VALIDATION_RESULT) { + setFormErrors(VALIDATION_RESULT); + SCREEN_SCROLL_VIEW_REF?.current?.scrollTo({ y: 0 }); + return; + } + + setSubmitFormLoading(true); + + const CREATE_INVITE_INPUT: CreateInviteByLocationMutationArgsInterface = + { + createInput: { + apartment: formApartmentCheckbox + ? FORMATTED_FORM.apartment + : '', + geoLocation: { + countryId: 0, + city: FORMATTED_FORM.city, + streetAddress: FORMATTED_FORM.streetAddress as string, + house: FORMATTED_FORM.house, + postcode: null, + notes: null, + loc: { + type: 'Point', + coordinates: [ + FORMATTED_FORM.longitude as number, + FORMATTED_FORM.latitude as number, + ], + }, + }, + }, + }; + + handleCreatInviteByLocation({ + variables: { + ...CREATE_INVITE_INPUT, + }, + onCompleted: (TData) => { + reduxDispatch( + onUserSignUpByAddressSuccess({ + user: null, + invite: TData.createInvite, + }), + ); + reduxDispatch(setGroup(GROUPS.APP)); + showMessage({ + message: "Great job 🎉, you're sign-up as invite", + type: 'success', + }); + setSubmitFormLoading(false); + }, + onError: (ApolloError) => { + console.log('ApolloError ==>', ApolloError); + showMessage({ + message: ApolloError.name, + description: ApolloError.message, + type: 'danger', + }); + setSubmitFormLoading(false); + }, + }); + }; + + // EFFECTS + React.useEffect(() => { + setAddressLoading(true); + (async () => { + const { status } = + await Location.requestForegroundPermissionsAsync(); + + if (status !== 'granted') { + const ERROR_MSG = 'Permission to access location was denied'; + showMessage({ message: ERROR_MSG }); + setCanGoBack(true); + setTimeout(() => { + NAVIGATION.goBack(); + }, 100); + return; + } + + const CURRENT_POSITION = await Location.getCurrentPositionAsync({}); + const FORMATTED_ADDRESS = await getFormattedLocation( + CURRENT_POSITION.coords, + ); + + setCurrentPosition(CURRENT_POSITION); + setFormattedLocation(FORMATTED_ADDRESS); + setAddressLoading(false); + })(); + }, [NAVIGATION]); + + React.useEffect(() => { + NAVIGATION.addListener('beforeRemove', (e) => { + if (canGoBack) { + return; + } + + // Prevent default behavior of leaving the screen + e.preventDefault(); + setWarningDialog(true); + + // Prompt the user before leaving the screen + setPreventBackCallBack(() => () => { + setCanGoBack(true); + setWarningDialog(false); + NAVIGATION.dispatch(e.data.action); + }); + + Alert.alert( + 'Leave sign-up?', + "Your account isn't yet created! Are you sure to leave the screen?", + [ + { text: "Don't leave", style: 'cancel', onPress: () => {} }, + { + text: 'leave', + style: 'destructive', + onPress: () => { + setCanGoBack(true); + NAVIGATION.dispatch(e.data.action); + }, + }, + ], + ); + }); + + return () => NAVIGATION.removeListener('beforeRemove', () => null); + }, [NAVIGATION, canGoBack, warningDialog]); + + return ( + + + + {/* Loading view */} + + {/* section1 */} + + + {CURRENT_LANGUAGE.INVITE_VIEW.YOUR_ADDRESS} + + + + {CURRENT_LANGUAGE.INVITE_VIEW.LAUNCH_NOTIFICATION} + + + + {/* section2 */} + + {addressLoading ? ( + <> + + { + CURRENT_LANGUAGE.INVITE_VIEW + .DETECTING_LOCATION + } + + + + ) : ( + + + + STREET_INPUT_REF?.current?.focus() + } + onChangeText={(text) => + setForm((prevForm) => ({ + ...prevForm, + city: text, + })) + } + /> + + + {formErrors?.city ? formErrors.city[0] : ''} + + + + + + HOUSE_INPUT_REF?.current?.focus() + } + onChangeText={(text) => + setForm((prevForm) => ({ + ...prevForm, + street: text, + })) + } + /> + + {formErrors.street + ? formErrors.street[0] + : ''} + + + + + + + APARTMENT_INPUT_REF?.current?.focus() + } + onChangeText={(text) => + setForm((prevForm) => ({ + ...prevForm, + house: text, + })) + } + /> + + {formErrors.house + ? formErrors.house[0] + : ''} + + + + + + setForm((prevForm) => ({ + ...prevForm, + apartment: text, + })) + } + /> + + + setFormApartmentCheckbox( + !formApartmentCheckbox, + ) + } + style={{ + ...GS.justifyContentBetween, + ...GS.mb0, + }}> + + {CURRENT_LANGUAGE.APARTMENT} + + + + setFormApartmentCheckbox( + !formApartmentCheckbox, + ) + } + /> + + + + {formErrors.apartment + ? formErrors.apartment[0] + : ''} + + + + + + + + + + + Click here + {' '} + to skip this step and fill these fields + later + + + + + )} + + + {/* TODO: find how to use a custom alert (disable due to slowing virtual device) */} + {/* {warningDialog && ( + + + + Leave? + + + + preventBackCallBack()} + /> + + + setWarningDialog(false)} + /> + + + )} */} + + + ); +}; + +export default SignUpByAddressScreen; diff --git a/packages/shop-mobile-expo/src/screens/index.ts b/packages/shop-mobile-expo/src/screens/index.ts new file mode 100644 index 0000000..46af3ee --- /dev/null +++ b/packages/shop-mobile-expo/src/screens/index.ts @@ -0,0 +1,47 @@ +// +import LoadingScreen from './Loading.screen'; +import Blank_Screen from './Blank_.screen'; + +// REGISTRATION +import HomeAuthScreen from './authentication/Home.screen'; +import SignUpScreen from './authentication/SignUp.screen'; +import SignInScreen from './authentication/SignIn.screen'; +import SignUpByAddressScreen from './authentication/SignUpByAddress.screen'; + +// APP +import HomeScreen from './app/Home.screen'; +import OrderHistoryScreen from './app/OrderHistory.screen'; +import AccountScreen from './app/Account.screen'; +import TranslationScreen from './app/Translation.screen'; +import SearchScreen from './app/Search.screen'; +import MerchantsSearchScreen from './app/MerchantsSearch.screen'; +import InStoreScreen from './app/InStore.screen'; +import ProductDetailsScreen from './app/ProductDetails'; +import OrderScreen from './app/Order.screen'; + +// TODO: create a type for screens object + +const SCREENS = { + // SCREENS + LOADING: LoadingScreen, + BLANK_: Blank_Screen, + APP: { + HOME: HomeScreen, + ORDER_HISTORY: OrderHistoryScreen, + ACCOUNT: AccountScreen, + TRANSLATION: TranslationScreen, + SEARCH: SearchScreen, + MERCHANTS_SEARCH: MerchantsSearchScreen, + IN_STORE: InStoreScreen, + PRODUCT_DETAILS: ProductDetailsScreen, + ORDER: OrderScreen, + }, + REGISTRATION: { + HOME: HomeAuthScreen, + SIGN_IN: SignInScreen, + SIGN_UP: SignUpScreen, + SIGN_UP_BY_ADDRESS: SignUpByAddressScreen, + }, +}; + +export default SCREENS; diff --git a/packages/shop-mobile-expo/src/store/features/navigation/index.ts b/packages/shop-mobile-expo/src/store/features/navigation/index.ts new file mode 100644 index 0000000..c711534 --- /dev/null +++ b/packages/shop-mobile-expo/src/store/features/navigation/index.ts @@ -0,0 +1,30 @@ +import { createSlice, PayloadAction } from '@reduxjs/toolkit'; + +// TYPES +import type { RootState } from '../../index'; +import type { NavigationStateType, NavigationGroupType } from './types'; + +// CONSTANTS +import GROUPS from '../../../router/groups.routes'; + +const initialState: NavigationStateType = { + group: GROUPS.LOADING, +}; + +export const navigationSlice = createSlice({ + name: 'navigation', + initialState, + reducers: { + setGroup: (state, action: PayloadAction) => { + state.group = action.payload; + }, + }, +}); + +// ACTIONS +export const { setGroup } = navigationSlice.actions; + +// SELECTORS +export const getGroup = (state: RootState) => state.navigation.group; + +export default navigationSlice.reducer; diff --git a/packages/shop-mobile-expo/src/store/features/navigation/types.ts b/packages/shop-mobile-expo/src/store/features/navigation/types.ts new file mode 100644 index 0000000..e377a95 --- /dev/null +++ b/packages/shop-mobile-expo/src/store/features/navigation/types.ts @@ -0,0 +1,5 @@ +export type GroupNameType = 'APP' | 'LOADING' | 'BLANK' | 'REGISTRATION'; +export type NavigationGroupType = GroupNameType | null; +export type NavigationStateType = { + group: GroupNameType | null; +}; diff --git a/packages/shop-mobile-expo/src/store/features/order/index.ts b/packages/shop-mobile-expo/src/store/features/order/index.ts new file mode 100644 index 0000000..c9cfbb7 --- /dev/null +++ b/packages/shop-mobile-expo/src/store/features/order/index.ts @@ -0,0 +1,46 @@ +import { createSlice, PayloadAction } from '@reduxjs/toolkit'; +import asyncStorage from '@react-native-async-storage/async-storage'; + +// TYPES +import type { RootState } from '../../index'; +import type { OrderStateInterface } from './types'; + +const initialState: OrderStateInterface = { + preSelectedProduct: null, + selectedProduct: null, +}; + +export const SLICE = createSlice({ + name: 'order', + initialState, + reducers: { + setPreselectedProduct: ( + state, + action: PayloadAction, + ) => { + state.preSelectedProduct = action.payload; + + asyncStorage.setItem('order', JSON.stringify(state)); + }, + setSelectedProduct: ( + state, + action: PayloadAction, + ) => { + state.selectedProduct = action.payload; + + asyncStorage.setItem('order', JSON.stringify(state)); + }, + }, +}); + +// ACTIONS +export const setPreselectedProduct = SLICE.actions.setPreselectedProduct; +export const setSelectedProduct = SLICE.actions.setSelectedProduct; + +// SELECTORS +export const getPreselectedProduct = (state: RootState) => + state.order.preSelectedProduct; +export const getSelectedProduct = (state: RootState) => + state.order.selectedProduct; + +export default SLICE.reducer; diff --git a/packages/shop-mobile-expo/src/store/features/order/types.ts b/packages/shop-mobile-expo/src/store/features/order/types.ts new file mode 100644 index 0000000..e6215a8 --- /dev/null +++ b/packages/shop-mobile-expo/src/store/features/order/types.ts @@ -0,0 +1,6 @@ +import { MaybeType } from '../../../types'; + +export interface OrderStateInterface { + preSelectedProduct?: MaybeType; + selectedProduct?: MaybeType; +} diff --git a/packages/shop-mobile-expo/src/store/features/translation/index.ts b/packages/shop-mobile-expo/src/store/features/translation/index.ts new file mode 100644 index 0000000..de2a4d6 --- /dev/null +++ b/packages/shop-mobile-expo/src/store/features/translation/index.ts @@ -0,0 +1,47 @@ +import { createSlice, PayloadAction } from '@reduxjs/toolkit'; +import AsyncStorage from '@react-native-async-storage/async-storage'; + +// LANGUAGES +import LANGUAGES from './lang'; + +// TYPES +import { + supportedLangType, + supportedLangsType, + TranslationStateType, +} from './types'; +import type { RootState } from '../../index'; + +export const supportedLangs: supportedLangsType = { + BULGARIAN: 'BULGARIAN', + ENGLISH: 'ENGLISH', + HEBREW: 'HEBREW', + FRENCH: 'FRENCH', + RUSSIAN: 'RUSSIAN', + SPANISH: 'SPANISH', +}; +export const initialState: TranslationStateType = { + lang: supportedLangs.ENGLISH, +}; +export const translationSlice = createSlice({ + name: 'translation', + initialState, + reducers: { + setLang: (state, action: PayloadAction) => { + state.lang = action.payload; + + AsyncStorage.setItem('translation', JSON.stringify(state)); + }, + }, +}); + +// ACTIONS +export const { setLang } = translationSlice.actions; + +// SELECTORS +export const getLang = (state: RootState) => state.translation.lang; +export const getLanguage = (state: RootState) => + LANGUAGES[state.translation.lang]; + +// REDUCER +export default translationSlice.reducer; diff --git a/packages/shop-mobile-expo/src/store/features/translation/lang/bg-BG.json b/packages/shop-mobile-expo/src/store/features/translation/lang/bg-BG.json new file mode 100644 index 0000000..d671b69 --- /dev/null +++ b/packages/shop-mobile-expo/src/store/features/translation/lang/bg-BG.json @@ -0,0 +1,264 @@ +{ + "LANGUAGE": { + "ID": "bg-BG", + "NAME": "Bulgarian" + }, + "CURRENT_DIRECTION": "ltr", + "SIDEBAR_SIDE": "left", + "INVITE_VIEW": { + "WELCOME_TO": "Добре дошли", + "EVER": "Евър", + "INFO_MISSING": "Част от информацията липсва!", + "YOUR_INVITE_CODE": "Вашият поканен код", + "INVITED_TEXT": { + "TITLE": "Благодарим Ви, че се регистрирате!", + "DETAILS": "Ще ви изпратим известие с код за покана, когато стартираме:" + }, + "NOT_INVITED_BY_CODE": { + "TITLE": "Грешен код!", + "DETAILS": "Моля, уверете се, че сте на мястото, където сте получили кода си." + }, + "CANT_ACCESS_LOCATION": "Няма достъп до местоположението ви.", + "CANT_ACCESS_LOCATION_GET_IN_BY_ADDRESS": "Нямате достъп до местоположението си. Моля, опитайте да влезете по адрес.", + "GET_IN_BY_ADDRESS": "Регистрирайте се по адрес", + "GET_IN": "Влез вътре!", + "YOUR_ADDRESS": "Уведомете Вашия адрес", + "LAUNCH_NOTIFICATION": "Обещаваме да показваме само подходящи продукти според вашия адрес", + "BY_CODE": { + "OR_WHAT": "Регистрирайте се чрез покана", + "INVITED": "Влезте с Invite Code", + "LOGO": { + "DETAILS": "Доставка и вземане на храна" + }, + "INVITE_CODE": "Код за покана" + }, + "DETECTING_LOCATION": "Моля, изчакайте, опитваме се да открием настоящия си адрес ..." + }, + "PRODUCTS_VIEW": { + "TITLE": "Продукти", + "BUY_BUTTON": { + "PRE": "Купи за", + "SUF": "" + }, + "NOT_AVAILABLE": "Продуктът не е наличен", + "MINUTES": "мин", + "DELIVERY": "Доставка", + "TAKEAWAY": "За вкъщи", + "READYFOR": "Готов за", + "DETAILS": { + "DETAILS": "Детайли", + "BACK": "Обратно", + "INCLUDES": "Включва", + "BUY_FOR": "Купи за" + } + }, + "HELP_VIEW": { + "TITLE": "Помощ" + }, + "ORDER_HISTORY_VIEW": { + "TITLE": "История на поръчките", + "DETAILS": "Детайли" + }, + "LAST_PURCHASES_VIEW": { + "TITLE": "Последни покупки", + "NOTHING_ORDERED": "Все още няма поръчки", + "TO_PRODUCTS": "Към продукти" + }, + "ABOUT_VIEW": { + "TITLE": "За нас" + }, + "TERMS_OF_USE_VIEW": { + "TITLE": "Условия за ползване" + }, + "PRIVACY_VIEW": { + "TITLE": "Поверителност" + }, + "LANGUAGE_VIEW": { + "TITLE": "Избери език" + }, + "SEARCH_VIEW": { + "VIEW_MORE": "Виж още", + "EMPTY_LIST": "Няма намерени продукти или ресторанти ... ", + "SEARCH_PLACEHOLDER": "Продукт или ресторант", + "OPEN": "Отворено", + "CLOSED": "Затворено" + }, + "BUY_POPUP": { + "ORDER_PAID": "Поръчката е платена $", + "CANCEL": { + "STATUSES": [ + { + "TITLE": "Поръчката беше анулирана по време на подготовката на склада!", + "DETAILS": "", + "NOT_PAID_NOTE": "" + } + ] + }, + "VIEW_ORDER_PRODUCTS": "Преглед на продукти за поръчка", + "STATUSES": [ + { + "TITLE": "Подготвяме поръчката!", + "DETAILS": "Ще я получите между %t минути.", + "NOT_PAID_NOTE": "Подгответе портфейла си (%s в брой)." + }, + { + "TITLE": "Доставчика е в движение!", + "DETAILS": "Ще получите реда в %t min.", + "NOT_PAID_NOTE": "Подгответе портфейла си (%s в брой)." + }, + { + "TITLE": "Проверете си вратата!", + "DETAILS": "Ще получите реда в секунди.", + "NOT_PAID_NOTE": "Подгответе портфейла си (%s в брой)." + }, + { + "TITLE": "Поръчка завършена!", + "DETAILS": "Благодарим Ви, че използвате Ever", + "NOT_PAID_NOTE": "" + } + ], + "STATUSES_TAKEAWAY": { + "TITLE": "Подготвяме поръчката!", + "DETAILS": "Можете да го получите между %t минути.", + "NOT_PAID_NOTE": "Подгответе портфейла си (%s в брой)." + }, + "DELIVERY_STATUS": { + "WE": "Ние", + "CARRIER": "Доставчик", + "YOU": "Вие" + }, + "DELIVERY_WRONG": { + "DELIVERY_WRONG": "Доставката не беше правилно!", + "PROCESSING_WRONG": "Обработката не беше правилно!", + "TRY_AGAIN": "Моля, опитайте отново.", + "CALL_FOR_DETAILS": "Обадете се за подробности" + }, + "ELAPSED_TIME": { + "TITLE": "Изминалото време" + }, + "BUTTONS": { + "UNDO": "Отмяна", + "PAY_NOW": "Плащане с карта", + "PAY_X": "Платете {{ сума }}", + "PAY_WITH_FIXED_CARD": "Плати с", + "PAID": "Платено", + "END": "Добър апетит!", + "CANCEL": "Отказ", + "GOT_IT": "Схванах го", + "IM_HERE": "Тук съм", + "SHOW_QR_CODE": "Показване на QR код", + "SHOW_PRODUCTS": "Показване на продукти" + }, + "UNDO_POPUP": { + "TITLE": "Сигурни ли сте?", + "DETAILS": [ + "Отмяната ще Ви върне парите, но вие ще", + "загубите", + "поръчката!" + ] + } + }, + "SIDE_MENU": { + "TITLE": "Ever Shop", + "GROUPS": { + "NO_TITLE": { + "DIVER_TITLE": "", + "ITEMS": { + "PRODUCTS": "Продукти", + "LAST_PURCHASES": "Последни покупки", + "CALL_US": "Обадете ни се", + "ORDER_HISTORY": "История на поръчките" + } + }, + "STORE": { + "STORE_TITLE": "Магазин", + "ITEMS": { + "CALL_WAITER": "Обадете се на сервитьор", + "ABOUT": "Относно" + } + }, + "SETTINGS": { + "DIVER_TITLE": "Настройки", + "ITEMS": { + "LANGUAGE": "Език" + } + }, + "INFO": { + "DIVER_TITLE": "Информация", + "ITEMS": { + "FAQ": "Помогне", + "ABOUT_US": "За нас", + "TERMS_OF_USE": "Условия за ползване", + "PRIVACY": "Поверителност" + }, + "CALL": { + "WE_APPOLOGISE": "Извиняваме се!", + "CALL_UNSUCCESSFULL": "Обаждането беше неуспешно!" + } + }, + "LEGALS": { + "DIVER_TITLE": "Юридически", + "ITEMS": { + "TERMS_OF_USE": "Условия за ползване", + "PRIVACY": "Поверителност" + } + } + } + }, + "TIMER": { + "MINUTES": "Минути", + "SECONDS": "Секунди" + }, + "CONNECTION_ERROR_VIEW": { + "TITLE": "Няма връзка с Ever", + "DESCRIPTION": [ + "Моля, уверете се, че сте", + "свързани с интернет", + "или опитайте по-късно." + ] + }, + "MERCHANTS_VIEW": { + "CLOSE_TO_YOU": "Търговци близо до вас", + "NAME": "Име на търговеца", + "WITH_NAME": "Търговци с име" + }, + "NO_SERVER_VIEW": { + "NO_SERVER": "Няма връзка със сървъра" + }, + "OR": "Или", + "OR_LOWERCASE": "или", + "YES": "Да", + "NO": "Не", + "OK": "Добре", + "CITY": "Сити", + "STREET": "Улица", + "HOUSE": "Къща", + "APARTMENT": "Апартамент", + "BACK": "Обратно", + "MORE": "повече", + "TO": "До", + "STORE_INFO": "Информация за магазина", + "MAP": "Карта", + "ORDER_INFO": "Информация за поръчката", + "CLOSE": "Затворен", + "SCAN": "Търсене", + "MERCHANTS": "Купечество", + "NOT_FOUND": "Няма резултати!", + "IN_STORE": "В магазина", + "EXIT_STORE": "Излезте от магазина", + "FAILED": "Се провали", + "CANCELED": "Отменен", + "IN_DELIVERY": "При доставка", + "PENDING": "В очакване", + "COMPLETED": "Завършен", + "BROWSE": "РАЗГЛЕДАЙ", + "ENGLISH": "Английски", + "HEBREW": "Иврит", + "RUSSIAN": "Руски", + "BULGARIAN": "Български", + "SPANISH": "испански", + "FRENCH": "Френски", + "SELECT": "Изберете", + "LOCATION_NOTES": "Бележки за местоположението", + "ENTER_NOTES_HERE": "Въведете бележки тук" +} diff --git a/packages/shop-mobile-expo/src/store/features/translation/lang/en-US.json b/packages/shop-mobile-expo/src/store/features/translation/lang/en-US.json new file mode 100644 index 0000000..ea9b0ba --- /dev/null +++ b/packages/shop-mobile-expo/src/store/features/translation/lang/en-US.json @@ -0,0 +1,265 @@ +{ + "LANGUAGE": { + "ID": "en-US", + "NAME": "English" + }, + "CURRENT_DIRECTION": "ltr", + "SIDEBAR_SIDE": "left", + "INVITE_VIEW": { + "WELCOME_TO": "Welcome to", + "EVER": "Ever Shop", + "INFO_MISSING": "Some of the information missing!", + "YOUR_INVITE_CODE": "Your invite code", + "INVITED_TEXT": { + "TITLE": "Thank you for sign up!", + "DETAILS": "We will send you a notification with an invite code when we launch at:" + }, + "NOT_INVITED_BY_CODE": { + "TITLE": "Wrong code!", + "DETAILS": "Please make sure you at the place that came with your code." + }, + "CANT_ACCESS_LOCATION": "Can't access your location.", + "CANT_ACCESS_LOCATION_GET_IN_BY_ADDRESS": "Can't access your location, please try to get in by address.", + "GET_IN_BY_ADDRESS": "Sign up by Address", + "GET_IN": "Get inside!", + "YOUR_ADDRESS": "Let us know your Address", + "LAUNCH_NOTIFICATION": "We promise to show only relevant products according to your address", + "BY_CODE": { + "OR_WHAT": "sign up by Invite", + "INVITED": "Sign in with Invite Code", + "LOGO": { + "DETAILS": "Food Delivery & Takeout" + }, + "INVITE_CODE": "Invite Code" + }, + "DETECTING_LOCATION": "Please wait, we attempting to detect your current address..." + }, + "PRODUCTS_VIEW": { + "TITLE": "Products", + "BUY_BUTTON": { + "PRE": "Buy for ", + "SUF": "" + }, + "NOT_AVAILABLE": "Product not available", + "MINUTES": "min", + "DELIVERY": "Delivery", + "TAKEAWAY": "Takeout", + "READYFOR": "Ready for", + "DETAILS": { + "DETAILS": "Details", + "BACK": "Back", + "INCLUDES": "Includes", + "BUY_FOR": "Buy for" + } + }, + "HELP_VIEW": { + "TITLE": "Help" + }, + "ORDER_HISTORY_VIEW": { + "TITLE": "Order history", + "DETAILS": "Details" + }, + "LAST_PURCHASES_VIEW": { + "TITLE": "Last Purchases", + "NOTHING_ORDERED": "No orders yet", + "TO_PRODUCTS": "To Products" + }, + "ABOUT_VIEW": { + "TITLE": "About Us" + }, + "TERMS_OF_USE_VIEW": { + "TITLE": "Terms of Use" + }, + "PRIVACY_VIEW": { + "TITLE": "Privacy" + }, + "LANGUAGE_VIEW": { + "TITLE": "Select Language" + }, + "SEARCH_VIEW": { + "VIEW_MORE": "View More", + "EMPTY_LIST": "No Products Or Merchants Found ...", + "SEARCH_PLACEHOLDER": "Product Or Restorant Name", + "OPEN": "Open", + "CLOSED": "Closed" + }, + "BUY_POPUP": { + "ORDER_PAID": "The order is paid $", + "CANCEL": { + "STATUSES": [ + { + "TITLE": "The order was cancel while Warehouse Preparation!", + "DETAILS": "", + "NOT_PAID_NOTE": "" + } + ] + }, + + "VIEW_ORDER_PRODUCTS": "View Order Products", + "STATUSES": [ + { + "TITLE": "We're preparing the order!", + "DETAILS": "You will get it in %t minutes.", + "NOT_PAID_NOTE": "Prepare your wallet (%s in cash)." + }, + { + "TITLE": "Carrier on the way!", + "DETAILS": "You will get the order in %t min.", + "NOT_PAID_NOTE": "Prepare your wallet (%s in cash)." + }, + { + "TITLE": "Check your door!", + "DETAILS": "You will get the order in seconds.", + "NOT_PAID_NOTE": "Prepare your wallet (%s in cash)." + }, + { + "TITLE": "Order Completed!", + "DETAILS": "Thanks for using Ever", + "NOT_PAID_NOTE": "" + } + ], + "STATUSES_TAKEAWAY": { + "TITLE": "We're preparing the order!", + "DETAILS": "You can get it in %t minutes.", + "NOT_PAID_NOTE": "Prepare your wallet (%s in cash)." + }, + "DELIVERY_STATUS": { + "WE": "We", + "CARRIER": "Carrier", + "YOU": "You" + }, + "DELIVERY_WRONG": { + "DELIVERY_WRONG": "The Delivery Went Wrong!", + "PROCESSING_WRONG": "Processing Went Wrong!", + "TRY_AGAIN": "Please try again.", + "CALL_FOR_DETAILS": "Call for details" + }, + "ELAPSED_TIME": { + "TITLE": "Elapsed time" + }, + "BUTTONS": { + "UNDO": "Undo", + "PAY_NOW": "Pay with Card", + "PAY_X": "Pay {{amount}}", + "PAY_WITH_FIXED_CARD": "Pay with", + "PAID": "Paid", + "END": "Bon appetit!", + "CANCEL": "Cancel", + "GOT_IT": "Got It!", + "IM_HERE": "I'm here", + "SHOW_QR_CODE": "Show QR Code", + "SHOW_PRODUCTS": "Show Products" + }, + "UNDO_POPUP": { + "TITLE": "Are you sure?", + "DETAILS": [ + "Undo will return you the money, but you will", + "lose", + "the order!" + ] + } + }, + "SIDE_MENU": { + "TITLE": "Ever Shop", + "GROUPS": { + "NO_TITLE": { + "DIVER_TITLE": "", + "ITEMS": { + "PRODUCTS": "Products", + "LAST_PURCHASES": "Last Purchases", + "CALL_US": "Call Us", + "ORDER_HISTORY": "Order history" + } + }, + "STORE": { + "STORE_TITLE": "Store", + "ITEMS": { + "CALL_WAITER": "Call Waiter", + "ABOUT": "About" + } + }, + "SETTINGS": { + "DIVER_TITLE": "Settings", + "ITEMS": { + "LANGUAGE": "Language" + } + }, + "INFO": { + "DIVER_TITLE": "Information", + "ITEMS": { + "FAQ": "Help", + "ABOUT_US": "About Us", + "TERMS_OF_USE": "Terms of Use", + "PRIVACY": "Privacy" + }, + "CALL": { + "WE_APPOLOGISE": "We Apologise!", + "CALL_UNSUCCESSFULL": "Call Was Unsuccessful!" + } + }, + "LEGALS": { + "DIVER_TITLE": "LEGAL", + "ITEMS": { + "TERMS_OF_USE": "Terms of Use", + "PRIVACY": "Privacy" + } + } + } + }, + "TIMER": { + "MINUTES": "Minutes", + "SECONDS": "Seconds" + }, + "CONNECTION_ERROR_VIEW": { + "TITLE": "No connection to Ever", + "DESCRIPTION": [ + "Please make sure you are", + "connected to the internet", + "or try Ever app later." + ] + }, + "MERCHANTS_VIEW": { + "CLOSE_TO_YOU": "Merchants close to you", + "NAME": "Merchant name", + "WITH_NAME": "Merchants with name" + }, + "NO_SERVER_VIEW": { + "NO_SERVER": "Server connection is lost" + }, + "OR": "Or", + "OR_LOWERCASE": "or", + "YES": "Yes", + "NO": "No", + "OK": "Ok", + "CITY": "City", + "STREET": "Street", + "HOUSE": "House", + "APARTMENT": "Apartment", + "BACK": "Back", + "MORE": "more", + "TO": "To", + "STORE_INFO": "Store Info", + "MAP": "Map", + "ORDER_INFO": "Order Info", + "CLOSE": "Close", + "SCAN": "Scan", + "MERCHANTS": "Merchants", + "NOT_FOUND": "Not Found!", + "IN_STORE": "In Store", + "EXIT_STORE": "Exit Store", + "FAILED": "Failed", + "CANCELED": "Canceled", + "IN_DELIVERY": "In Delivery", + "PENDING": "Pending", + "COMPLETED": "Completed", + "BROWSE": "Browse", + "ENGLISH": "English", + "HEBREW": "Hebrew", + "RUSSIAN": "Russian", + "BULGARIAN": "Bulgarian", + "SPANISH": "Spanish", + "FRENCH": "French", + "SELECT": "Select", + "LOCATION_NOTES": "Location Notes", + "ENTER_NOTES_HERE": "Enter notes here" +} diff --git a/packages/shop-mobile-expo/src/store/features/translation/lang/es-ES.json b/packages/shop-mobile-expo/src/store/features/translation/lang/es-ES.json new file mode 100644 index 0000000..901bf1a --- /dev/null +++ b/packages/shop-mobile-expo/src/store/features/translation/lang/es-ES.json @@ -0,0 +1,264 @@ +{ + "LANGUAGE": { + "ID": "es-ES", + "NAME": "Español" + }, + "CURRENT_DIRECTION": "ltr", + "SIDEBAR_SIDE": "left", + "INVITE_VIEW": { + "WELCOME_TO": "Bienvenido a", + "EVER": "Tu-Pedido", + "INFO_MISSING": "¡Falta información!", + "YOUR_INVITE_CODE": "Su código de invitado", + "INVITED_TEXT": { + "TITLE": "¡Gracias por registrarte!", + "DETAILS": "Le enviaremos una notificación con un código de invitación cuando lancemos en:" + }, + "NOT_INVITED_BY_CODE": { + "TITLE": "¡Código erroneo!", + "DETAILS": "Asegúrate de estar en el lugar que dice tu código" + }, + "CANT_ACCESS_LOCATION": "No se puede acceder a tu ubicación", + "CANT_ACCESS_LOCATION_GET_IN_BY_ADDRESS": "No se puede acceder a tu ubicación, intenta ingresar por dirección", + "GET_IN_BY_ADDRESS": "Registrarse por dirección", + "GET_IN": "¡Entrar!", + "YOUR_ADDRESS": "Escribe tu dirección", + "LAUNCH_NOTIFICATION": "Prometemos mostrar solo productos relevantes de acuerdo con su dirección", + "BY_CODE": { + "OR_WHAT": "Entrar con invitación", + "INVITED": "Inicie sesión con el código de invitación", + "LOGO": { + "DETAILS": "Entrega de Comida y Comida para Llevar" + }, + "INVITE_CODE": "Código de invitación" + }, + "DETECTING_LOCATION": "Estamos intentando detectar tu dirección actual..." + }, + "PRODUCTS_VIEW": { + "TITLE": "Productos", + "BUY_BUTTON": { + "PRE": "Comprar por ", + "SUF": "" + }, + "NOT_AVAILABLE": "Producto no disponible", + "MINUTES": "min", + "DELIVERY": "Delivery", + "TAKEAWAY": "Para retirar", + "READYFOR": "Listo para", + "DETAILS": { + "DETAILS": "Detalles", + "BACK": "Regresar", + "INCLUDES": "Incluye", + "BUY_FOR": "Comprar por " + } + }, + "HELP_VIEW": { + "TITLE": "Ayuda" + }, + "ORDER_HISTORY_VIEW": { + "TITLE": "Historial de Pedidos", + "DETAILS": "Detalles" + }, + "LAST_PURCHASES_VIEW": { + "TITLE": "Ultimas Compras", + "NOTHING_ORDERED": "No existen pedidos todavía", + "TO_PRODUCTS": "A Prodcutos" + }, + "ABOUT_VIEW": { + "TITLE": "Acerca de" + }, + "TERMS_OF_USE_VIEW": { + "TITLE": "Términos de uso" + }, + "PRIVACY_VIEW": { + "TITLE": "Privacidad" + }, + "LANGUAGE_VIEW": { + "TITLE": "Seleccionar Idioma" + }, + "BUY_POPUP": { + "ORDER_PAID": "El pedido cuesta $", + "CANCEL": { + "STATUSES": [ + { + "TITLE": "El pedido se canceló mientras se preparaba el almacén!", + "DETAILS": "", + "NOT_PAID_NOTE": "" + } + ] + }, + "VIEW_ORDER_PRODUCTS": "Ver productos de pedido", + "STATUSES": [ + { + "TITLE": "¡Estamos preparando el pedido!", + "DETAILS": "Lo tendrás en %t minutos", + "NOT_PAID_NOTE": "Ten pronto %s en efectivo" + }, + { + "TITLE": "¡Delivery en camino!", + "DETAILS": "Recibirás el pedido en %t minutos", + "NOT_PAID_NOTE": "Ten pronto %s en efectivo" + }, + { + "TITLE": "¡Ya estamos en tu casa!", + "DETAILS": "Recibirás el pedido en segundos", + "NOT_PAID_NOTE": "Ten pronto %s en efectivo" + }, + { + "TITLE": "¡Pedido Completado!", + "DETAILS": "Gracias por usar nuestro servicio", + "NOT_PAID_NOTE": "" + } + ], + "STATUSES_TAKEAWAY": { + "TITLE": "¡Estamos preparando el pedido!", + "DETAILS": "Lo tendrás en %t minutos", + "NOT_PAID_NOTE": "Ten pronto %s en efectivo" + }, + "DELIVERY_STATUS": { + "WE": "Nosotros", + "CARRIER": "Delivery", + "YOU": "Tú" + }, + "DELIVERY_WRONG": { + "DELIVERY_WRONG": "¡La entrega salió mal!", + "PROCESSING_WRONG": "¡El procesamiento fue mal!", + "TRY_AGAIN": "Por favor, intenta de nuevo", + "CALL_FOR_DETAILS": "Llama por detalles" + }, + "ELAPSED_TIME": { + "TITLE": "Tiempo transcurrido" + }, + "BUTTONS": { + "UNDO": "Deshacer", + "PAY_NOW": "Pagar con Tarjeta", + "PAY_X": "Pagar {{amount}}", + "PAY_WITH_FIXED_CARD": "Pagar con", + "PAID": "Pago", + "END": "¡Buen apetito!", + "CANCEL": "Cancelar", + "GOT_IT": "¡Lo tengo!", + "IM_HERE": "Estamos aquí", + "SHOW_QR_CODE": "Mostrar código QR", + "SHOW_PRODUCTS": "Mostrar productos" + }, + "UNDO_POPUP": { + "TITLE": "Estás seguro?", + "DETAILS": [ + "¡Deshacer te devolverá el dinero, pero", + "perderás", + "el pedido!" + ] + } + }, + "SIDE_MENU": { + "TITLE": "Ever Shop", + "GROUPS": { + "NO_TITLE": { + "DIVER_TITLE": "", + "ITEMS": { + "PRODUCTS": "Productos", + "LAST_PURCHASES": "Ultimas Compras", + "CALL_US": "Llámanos", + "ORDER_HISTORY": "Historial de pedidos" + } + }, + "STORE": { + "STORE_TITLE": "Comercio", + "ITEMS": { + "CALL_WAITER": "Llámanos", + "ABOUT": "Acerca de" + } + }, + "SETTINGS": { + "DIVER_TITLE": "Configuraciones", + "ITEMS": { + "LANGUAGE": "Idioma" + } + }, + "INFO": { + "DIVER_TITLE": "Información", + "ITEMS": { + "FAQ": "Ayuda", + "ABOUT_US": "Acerca de", + "TERMS_OF_USE": "Condiciones de uso", + "PRIVACY": "Privacidad" + }, + "CALL": { + "WE_APPOLOGISE": "Pedimos perdón!", + "CALL_UNSUCCESSFULL": "La llamada no fue exitosa!" + } + }, + "LEGALS": { + "DIVER_TITLE": "LEGAL", + "ITEMS": { + "TERMS_OF_USE": "Condiciones de uso", + "PRIVACY": "Privacidad" + } + } + } + }, + "TIMER": { + "MINUTES": "Minutos", + "SECONDS": "Segundos" + }, + "CONNECTION_ERROR_VIEW": { + "TITLE": "No hay conexión al Servidor", + "DESCRIPTION": [ + "Por favor asegúrate de que estás", + "conectado a Internet", + "o inténtalo mas tarde" + ] + }, + "MERCHANTS_VIEW": { + "CLOSE_TO_YOU": "Comerciantes cerca de ti", + "NAME": "Nombre del Comercio", + "WITH_NAME": "Comercios" + }, + "NO_SERVER_VIEW": { + "NO_SERVER": "Se perdió la conexión con el servidor" + }, + "SEARCH_VIEW": { + "VIEW_MORE": "Ver más", + "EMPTY_LIST": "No se encontraron productos ni comerciantes...", + "SEARCH_PLACEHOLDER": "Nombre del producto o restaurante", + "OPEN": "Abierto", + "CLOSED": "Cerrado" + }, + "OR": "O", + "OR_LOWERCASE": "o", + "YES": "Si", + "NO": "No", + "OK": "Ok", + "CITY": "Ciudad", + "STREET": "Calle", + "HOUSE": "Número de puerta", + "APARTMENT": "Apartmento", + "BACK": "Volver", + "MORE": "Más", + "TO": "Hacia", + "STORE_INFO": "Información del Comercio", + "MAP": "Mapa", + "ORDER_INFO": "Información del Pedido", + "CLOSE": "Cerrar", + "SCAN": "Escanear", + "MERCHANTS": "Comercios", + "NOT_FOUND": "¡No hay resultados!", + "IN_STORE": "En el comercio", + "EXIT_STORE": "Salir del comercio", + "FAILED": "Error", + "CANCELED": "Cancelado", + "IN_DELIVERY": "En viaje", + "PENDING": "Pendiente", + "COMPLETED": "Completado", + "BROWSE": "Vistazo", + "ENGLISH": "Inglés", + "HEBREW": "Hebreo", + "RUSSIAN": "Ruso", + "BULGARIAN": "Búlgaro", + "SPANISH": "Español", + "FRENCH": "Francés", + "SELECT": "Seleccione", + "LOCATION_NOTES": "Notas de ubicación", + "ENTER_NOTES_HERE": "Introduce notas aquí" +} diff --git a/packages/shop-mobile-expo/src/store/features/translation/lang/fr-FR.json b/packages/shop-mobile-expo/src/store/features/translation/lang/fr-FR.json new file mode 100644 index 0000000..19f249d --- /dev/null +++ b/packages/shop-mobile-expo/src/store/features/translation/lang/fr-FR.json @@ -0,0 +1,269 @@ +{ + "welcome": "Bienvenu(e) chez Ever", + "@welcome": { + "description": "Message de bienvenue" + }, + "LANGUAGE": { + "ID": "fr-FR", + "NAME": "Francais" + }, + "CURRENT_DIRECTION": "ltr", + "SIDEBAR_SIDE": "gauche", + "INVITE_VIEW": { + "WELCOME_TO": "Bienvenu(e) à", + "EVER": "Ever", + "INFO_MISSING": "Certaines informations sont manquantes!", + "YOUR_INVITE_CODE": "Votre code d'invitation", + "INVITED_TEXT": { + "TITLE": "Merci de votre enregistrement!", + "DETAILS": "Nous allons vous enoyer une notification contenant votre code d'invitation dès que nous lançons à :" + }, + "NOT_INVITED_BY_CODE": { + "TITLE": "Le code est incorrect!", + "DETAILS": "Rassurez vous que vous êtes bien à l'endroit d'où a été reçu votre code" + }, + "CANT_ACCESS_LOCATION": "Nous n'arrivons à detecter votre emplacement", + "CANT_ACCESS_LOCATION_GET_IN_BY_ADDRESS": "Nous n'arrivons pas à reconnaitre votre emplacement, veuillez essayer de rentrer les informations de votre adresse.", + "GET_IN_BY_ADDRESS": "S'enregistrement à partir d'une adresse", + "GET_IN": "On y va!", + "YOUR_ADDRESS": "Laissez nous detecter votre adresse", + "LAUNCH_NOTIFICATION": "Nous vous affichons que des produits bien spécifiques à votre adresse", + "BY_CODE": { + "OR_WHAT": "S'enregistrer avec un code d'invitation", + "INVITED": "Se connecter avec le code d'invitation", + "LOGO": { + "DETAILS": "Livraison express des repas & Take Away" + }, + "INVITE_CODE": "Code d'invitation" + }, + "DETECTING_LOCATION": "Veuillez patienter, le temps de detecter votre adresse actuelle..." + }, + "PRODUCTS_VIEW": { + "TITLE": "Produits", + "BUY_BUTTON": { + "PRE": "Acheter ", + "SUF": "" + }, + "NOT_AVAILABLE": "Produit non disponible", + "MINUTES": "min", + "DELIVERY": "Livraisons", + "TAKEAWAY": "A emporter", + "READYFOR": "Prêt à", + "DETAILS": { + "DETAILS": "Details", + "BACK": "Retour", + "INCLUDES": "Inclus", + "BUY_FOR": "Acheter" + } + }, + "HELP_VIEW": { + "TITLE": "Aide" + }, + "ORDER_HISTORY_VIEW": { + "TITLE": "Historique de commandes", + "DETAILS": "Details" + }, + "LAST_PURCHASES_VIEW": { + "TITLE": "Derniers achats", + "NOTHING_ORDERED": "Aucune commande", + "TO_PRODUCTS": "Aux produits" + }, + "ABOUT_VIEW": { + "TITLE": "A propos de nous" + }, + "TERMS_OF_USE_VIEW": { + "TITLE": "Conditions d'utilisation" + }, + "PRIVACY_VIEW": { + "TITLE": "Politique de confidentialité" + }, + "LANGUAGE_VIEW": { + "TITLE": "Choisissez votre langue" + }, + "SEARCH_VIEW": { + "VIEW_MORE": "Voir plus", + "EMPTY_LIST": "Aucun produit ou marchand trouvé ...", + "SEARCH_PLACEHOLDER": "Produit ou Nom du restaurant", + "OPEN": "Ouvert", + "CLOSED": "Fermé" + }, + "BUY_POPUP": { + "ORDER_PAID": "La commande a été payée $", + "CANCEL": { + "STATUSES": [ + { + "TITLE": "La commande a été annulée lors de la synchronisation de l'entrepôt!", + "DETAILS": "", + "NOT_PAID_NOTE": "" + } + ] + }, + + "VIEW_ORDER_PRODUCTS": "Voir les produits commandés", + "STATUSES": [ + { + "TITLE": "En préparation de votre commande!", + "DETAILS": "Nous y serons dans %t minutes.", + "NOT_PAID_NOTE": "Préparez votre portefeuille (%s en cash)." + }, + { + "TITLE": "Le livreur est en route!", + "DETAILS": "Vous allez recevoir votre commande dans %t min.", + "NOT_PAID_NOTE": "Préparez votre portefeuille (%s en cash).." + }, + { + "TITLE": "Checkez à votre porte!", + "DETAILS": "Votre commande sera la dans quelques seconds.", + "NOT_PAID_NOTE": "Préparez votre portefeuille (%s en cash)" + }, + { + "TITLE": "Commande complète!", + "DETAILS": "Merci d'utiliser Ever", + "NOT_PAID_NOTE": "" + } + ], + "STATUSES_TAKEAWAY": { + "TITLE": "En préparation de votre commande!", + "DETAILS": "Nous y serons dans %t minutes.", + "NOT_PAID_NOTE": "Préparez votre portefeuille (%s en cash)." + }, + "DELIVERY_STATUS": { + "WE": "Nous", + "CARRIER": "Livreur", + "YOU": "Vous" + }, + "DELIVERY_WRONG": { + "DELIVERY_WRONG": "La livraison n'a pas été correcte!", + "PROCESSING_WRONG": "La livraison n'a pas abouti!", + "TRY_AGAIN": "SVP Réessayez.", + "CALL_FOR_DETAILS": "Details" + }, + "ELAPSED_TIME": { + "TITLE": "Temps écoulé" + }, + "BUTTONS": { + "UNDO": "Annuler", + "PAY_NOW": "Payer avec une carte", + "PAY_X": "Paiement {{amount}}", + "PAY_WITH_FIXED_CARD": "Payer avec", + "PAID": "Payé", + "END": "Bon appetit!", + "CANCEL": "Annuler", + "GOT_IT": "J'ai compris!", + "IM_HERE": "Je suis ici", + "SHOW_QR_CODE": "Montrez votre QR Code", + "SHOW_PRODUCTS": "Voir les produits" + }, + "UNDO_POPUP": { + "TITLE": "Etes vous sure?", + "DETAILS": [ + "En cas d'annulation le montant payé vous est retourné, mais vous", + "perdez", + "votre commande!" + ] + } + }, + "SIDE_MENU": { + "TITLE": "Ever Shop", + "GROUPS": { + "NO_TITLE": { + "DIVER_TITLE": "", + "ITEMS": { + "PRODUCTS": "Produits", + "LAST_PURCHASES": "Derniers achats", + "CALL_US": "Appelez-nous", + "ORDER_HISTORY": "Historique de commandes" + } + }, + "STORE": { + "STORE_TITLE": "Magasin", + "ITEMS": { + "CALL_WAITER": "Appelez le serveur", + "ABOUT": "A propos de nous" + } + }, + "SETTINGS": { + "DIVER_TITLE": "Paramètres", + "ITEMS": { + "LANGUAGE": "Langue" + } + }, + "INFO": { + "DIVER_TITLE": "Information", + "ITEMS": { + "FAQ": "Aide", + "ABOUT_US": "A propos de nous", + "TERMS_OF_USE": "Conditions d'utilisation", + "PRIVACY": "Politique de confidentialité" + }, + "CALL": { + "WE_APPOLOGISE": "Nous nous excusons pour la gêne occasionnée.", + "CALL_UNSUCCESSFULL": "L'appel n'a pas abouti." + } + }, + "LEGALS": { + "DIVER_TITLE": "LEGAL", + "ITEMS": { + "TERMS_OF_USE": "Conditions d'utilisation", + "PRIVACY": "Politique de confidentialité" + } + } + } + }, + "TIMER": { + "MINUTES": "Minutes", + "SECONDS": "Seconds" + }, + "CONNECTION_ERROR_VIEW": { + "TITLE": "Aucune connexion à Ever", + "DESCRIPTION": [ + "Assurez vous que vous êtes", + "connecté à internet", + "ou essayer de relancer Ever app" + ] + }, + "MERCHANTS_VIEW": { + "CLOSE_TO_YOU": "Vendeurs proches de vous", + "NAME": "Nom du vendeur", + "WITH_NAME": "Vendeur avec noms" + }, + "NO_SERVER_VIEW": { + "NO_SERVER": "La connexion au serveur a été perdue" + }, + "OR": "Ou", + "OR_LOWERCASE": "ou", + "YES": "Oui", + "NO": "Non", + "OK": "Ok", + "CITY": "Ville ou Cité", + "STREET": "Avenue, Rue", + "HOUSE": "Numéro de maison", + "APARTMENT": "Numéro d'appartement", + "BACK": "Retour", + "MORE": "Plus", + "TO": "à", + "STORE_INFO": "Magasin", + "MAP": "Carte", + "ORDER_INFO": "Commande", + "CLOSE": "Fermer", + "SCAN": "Scanner", + "MERCHANTS": "Vendeurs", + "NOT_FOUND": "Non trouvé!", + "IN_STORE": "Dans le magasin", + "EXIT_STORE": "Sortir du magasin", + "FAILED": "Échec", + "CANCELED": "Annulé", + "IN_DELIVERY": "En livraison", + "PENDING": "En attente", + "COMPLETED": "Terminé", + "BROWSE": "Parcourir", + "ENGLISH": "Anglais", + "HEBREW": "Hébreu", + "RUSSIAN": "Russe", + "BULGARIAN": "bulgare", + "SPANISH": "Espagnol", + "FRENCH": "français", + "SELECT": "Sélectionner", + "LOCATION_NOTES": "Notes de localisation", + "ENTER_NOTES_HERE": "Entrez ici vos notes" +} diff --git a/packages/shop-mobile-expo/src/store/features/translation/lang/he-IL.json b/packages/shop-mobile-expo/src/store/features/translation/lang/he-IL.json new file mode 100644 index 0000000..b3d34f4 --- /dev/null +++ b/packages/shop-mobile-expo/src/store/features/translation/lang/he-IL.json @@ -0,0 +1,264 @@ +{ + "LANGUAGE": { + "ID": "he-IL", + "NAME": "עברית" + }, + "CURRENT_DIRECTION": "rtl", + "SIDEBAR_SIDE": "right", + "INVITE_VIEW": { + "WELCOME_TO": "ברוך הבא ל-", + "EVER": "Ever", + "INFO_MISSING": "אנא מלא את המידע החסר.", + "YOUR_INVITE_CODE": "קוד ההזמנה שלך", + "INVITED_TEXT": { + "TITLE": "תודה על ההרשמה!", + "DETAILS": "אנחנו נשלח לך הודעה ברגע שנתחיל לפעול בכתובת שלך:" + }, + "NOT_INVITED_BY_CODE": { + "TITLE": "קוד שגוי!", + "DETAILS": "תוודא בבקשה שאתה במקום שמצורף לקוד שלך!" + }, + "CANT_ACCESS_LOCATION": "אין גישה למיקום שלך.", + "CANT_ACCESS_LOCATION_GET_IN_BY_ADDRESS": "אין גישה למיקום שלך, אנא נסה להיכנס באמצעות הכתובת.", + "GET_IN_BY_ADDRESS": "הירשם עם כתובת", + "GET_IN": "כנס", + "YOUR_ADDRESS": "מה הכתובת שלך?", + "LAUNCH_NOTIFICATION": "אנחנו מבטיחים להראות רק מוצרים רלוונטיים בהתאם לכתובת שהוכנסה", + "BY_CODE": { + "OR_WHAT": "הירשם באמצעות הזמנה", + "INVITED": "היכנס עם הזמנה לאפליקציה", + "LOGO": { + "DETAILS": "משלוחי אוכל וטייק אווי" + }, + "INVITE_CODE": "קוד הזמנה" + }, + "DETECTING_LOCATION": "רק רגע... אנחנו מנסים לזהות את הכתובת שלך." + }, + "PRODUCTS_VIEW": { + "TITLE": "המוצרים שלנו", + "BUY_BUTTON": { + "PRE": "", + "SUF": "-קנה ב" + }, + "NOT_AVAILABLE": "המוצר אינו זמין", + "MINUTES": "דקות", + "DELIVERY": "משלוח", + "TAKEAWAY": "Takeaway", + "READYFOR": "מוכן ל-", + "DETAILS": { + "DETAILS": "פרטים", + "BACK": "חזרה", + "INCLUDES": "כולל", + "BUY_FOR": "קנה עבור" + } + }, + "HELP_VIEW": { + "TITLE": "עֶזרָה" + }, + "ORDER_HISTORY_VIEW": { + "TITLE": "היסטוריית הזמנות", + "DETAILS": "פרטים" + }, + "LAST_PURCHASES_VIEW": { + "TITLE": "רכישות אחרונות", + "NOTHING_ORDERED": "עדיין לא הזמנת שום דבר", + "TO_PRODUCTS": "למוצרים" + }, + "ABOUT_VIEW": { + "TITLE": "עלינו" + }, + "TERMS_OF_USE_VIEW": { + "TITLE": "תנאי שימוש" + }, + "PRIVACY_VIEW": { + "TITLE": "פרטיות" + }, + "LANGUAGE_VIEW": { + "TITLE": "בחירת שפה" + }, + "BUY_POPUP": { + "ORDER_PAID": "ההזמנה משולמת $", + "CANCEL": { + "STATUSES": [ + { + "TITLE": "ההזמנה בוטלה תוך כדי הכנת מחסן!", + "DETAILS": "", + "NOT_PAID_NOTE": "" + } + ] + }, + "VIEW_ORDER_PRODUCTS": "צפו במוצרי הזמנה", + "STATUSES": [ + { + "TITLE": "אנחנו מכינים את ההזמנה!", + "DETAILS": "ההזמנה תהיה בידך בעוד %t דקות.", + "NOT_PAID_NOTE": "תכין/י את הארנק (%s במזומן)." + }, + { + "TITLE": "השליח בדרך!", + "DETAILS": "ההזמנה תהיה בידך בעוד %t דקות.", + "NOT_PAID_NOTE": "תכין/י את הארנק (%s במזומן)." + }, + { + "TITLE": "בדוק את הדלת שלך!", + "DETAILS": "ההזמנה תהיה בידך בעוד רגע.", + "NOT_PAID_NOTE": "תכין/י את הארנק (%s במזומן)." + }, + { + "TITLE": "ההזמנה הושלמה!", + "DETAILS": "תודה על השימוש ב-Ever", + "NOT_PAID_NOTE": "תכין/י את הארנק (%s במזומן)." + } + ], + "STATUSES_TAKEAWAY": { + "TITLE": "אנחנו מכינים את ההזמנה!", + "DETAILS": "אתה יכול להכניס אותו %t דקות.", + "NOT_PAID_NOTE": "תכין/י את הארנק (%s במזומן)." + }, + "DELIVERY_STATUS": { + "WE": "אנחנו", + "CARRIER": "השליח", + "YOU": "אתה" + }, + "DELIVERY_WRONG": { + "DELIVERY_WRONG": "המסירה השתבשה!", + "PROCESSING_WRONG": "עיבוד התבצעה טעות!", + "TRY_AGAIN": "בבקשה נסה שוב.", + "CALL_FOR_DETAILS": "התקשר לקבלת פרטים" + }, + "ELAPSED_TIME": { + "TITLE": "הזמן שחלף" + }, + "BUTTONS": { + "UNDO": "ביטול", + "PAY_NOW": "תשלום באשראי", + "PAY_WITH_FIXED_CARD": "שלם בעזרת", + "PAY_X": "לשלם {{amount}}", + "PAID": "שולם באשראי", + "END": "בתאבון!", + "CANCEL": "בטל", + "GOT_IT": "הבנתי", + "IM_HERE": "אני כאן", + "SHOW_QR_CODE": "הצג קוד QR", + "SHOW_PRODUCTS": "הצג מוצרים" + }, + "UNDO_POPUP": { + "TITLE": "אתה בטוח?", + "DETAILS": [ + "החזרת הכסף אולי תחזיר לך את כספך, אבל תנפץ את", + "ההזמנה שלך!", + "" + ] + } + }, + "SIDE_MENU": { + "TITLE": "Ever Shop", + "GROUPS": { + "NO_TITLE": { + "DIVER_TITLE": "", + "ITEMS": { + "PRODUCTS": "מוצרים", + "LAST_PURCHASES": "רכישות אחרונות", + "CALL_US": "התקשר אלינו", + "ORDER_HISTORY": "היסטוריית הזמנות" + } + }, + "STORE": { + "STORE_TITLE": "חנות", + "ITEMS": { + "CALL_WAITER": "התקשר למלצר", + "ABOUT": "על אודות" + } + }, + "SETTINGS": { + "DIVER_TITLE": "הגדרות", + "ITEMS": { + "LANGUAGE": "(Language) שפה" + } + }, + "INFO": { + "DIVER_TITLE": "מידע", + "ITEMS": { + "FAQ": "עזרה", + "ABOUT_US": "עלינו", + "TERMS_OF_USE": "תנאי שימוש", + "PRIVACY": "פרטיות" + }, + "CALL": { + "WE_APPOLOGISE": "אנו מתנצלים", + "CALL_UNSUCCESSFULL": "השיחה לא הצליחה" + } + }, + "LEGALS": { + "DIVER_TITLE": "אלמוגים", + "ITEMS": { + "TERMS_OF_USE": "תנאי שימוש", + "PRIVACY": "פרטיות" + } + } + } + }, + "TIMER": { + "MINUTES": "דקות", + "SECONDS": "שניות" + }, + "CONNECTION_ERROR_VIEW": { + "TITLE": "אין חיבור ל-Ever", + "DESCRIPTION": [ + "בבקשה תוודא שאתה", + "מחובר לאינטרנט או תנסה", + "את האפליקציה מאוחר יותר." + ] + }, + "MERCHANTS_VIEW": { + "CLOSE_TO_YOU": "סוחרים קרובים אליך", + "NAME": "שם סוחר", + "WITH_NAME": "סוחרים עם שם" + }, + "SEARCH_VIEW": { + "VIEW_MORE": "ראה עוד", + "EMPTY_LIST": "לא נמצאו מוצרים או סוחרים...", + "SEARCH_PLACEHOLDER": "שם המוצר או המסעדה", + "OPEN": "לִפְתוֹחַ", + "CLOSED": "סָגוּר" + }, + "NO_SERVER_VIEW": { + "NO_SERVER": "השרת נפל" + }, + "OR": "או", + "OR_LOWERCASE": "או", + "YES": "כן", + "NO": "לא", + "OK": "אוקי", + "CITY": "עיר", + "STREET": "רחוב", + "HOUSE": "בית", + "APARTMENT": "דירה", + "BACK": "לחזור", + "MORE": "יותר", + "TO": "ל", + "STORE_INFO": "מידע חנות", + "MAP": "מפה", + "ORDER_INFO": "פרטי הזמנה", + "CLOSE": "סגור", + "SCAN": "סרוק", + "MERCHANTS": "סוחרים", + "NOT_FOUND": "אין תוצאות!", + "IN_STORE": "בחנות", + "EXIT_STORE": "צא מהחנות", + "FAILED": "נכשל", + "CANCELED": "מבוטל", + "IN_DELIVERY": "במשלוח", + "PENDING": "ממתין ל", + "COMPLETED": "הושלם", + "BROWSE": "לְדַפדֵף", + "ENGLISH": "אנגלית", + "HEBREW": "עִברִית", + "RUSSIAN": "רוּסִי", + "BULGARIAN": "בולגרית", + "SPANISH": "ספרדית", + "FRENCH": "צָרְפָתִית", + "SELECT": "בחר", + "LOCATION_NOTES": "הערות מיקום", + "ENTER_NOTES_HERE": "הזן הערות כאן" +} diff --git a/packages/shop-mobile-expo/src/store/features/translation/lang/index.ts b/packages/shop-mobile-expo/src/store/features/translation/lang/index.ts new file mode 100644 index 0000000..744677f --- /dev/null +++ b/packages/shop-mobile-expo/src/store/features/translation/lang/index.ts @@ -0,0 +1,29 @@ +// TYPES +import type { supportedLangType } from '../types'; + +// LANGUAGES +import bg_BG from './bg-BG.json'; +import en_US from './en-US.json'; +import es_ES from './es-ES.json'; +import fr_FR from './fr-FR.json'; +import he_IL from './he-IL.json'; +import ru_RU from './ru-RU.json'; + +const LANGUAGES: { + [name in supportedLangType]: + | typeof bg_BG + | typeof en_US + | typeof es_ES + | typeof fr_FR + | typeof he_IL + | typeof ru_RU; +} = { + BULGARIAN: bg_BG, + ENGLISH: en_US, + SPANISH: es_ES, + FRENCH: fr_FR, + HEBREW: he_IL, + RUSSIAN: ru_RU, +}; + +export default LANGUAGES; diff --git a/packages/shop-mobile-expo/src/store/features/translation/lang/ru-RU.json b/packages/shop-mobile-expo/src/store/features/translation/lang/ru-RU.json new file mode 100644 index 0000000..2a4af83 --- /dev/null +++ b/packages/shop-mobile-expo/src/store/features/translation/lang/ru-RU.json @@ -0,0 +1,263 @@ +{ + "LANGUAGE": { + "ID": "ru-RU", + "NAME": "Русский" + }, + "CURRENT_DIRECTION": "ltr", + "SIDEBAR_SIDE": "left", + "INVITE_VIEW": { + "WELCOME_TO": "Добро Пожаловать в", + "EVER": "Ever Shop", + "INFO_MISSING": "Некоторая информация отсутствует!", + "YOUR_INVITE_CODE": "Ваш код приглашения", + "INVITED_TEXT": { + "TITLE": "Спасибо за запрос на приглашение!", + "DETAILS": "Мы вышлем Вам уведомление когда мы будем обслуживать Ваш адрес:" + }, + "NOT_INVITED_BY_CODE": { + "TITLE": "Неверный код!", + "DETAILS": "Пожулуйста убедитесь что Вы находитесь по адресу, который был использован для получения Вашего Кода приглашения в наш сервис." + }, + "CANT_ACCESS_LOCATION": "Не возможно получить Ваше местонахождение. Попробуйте зарегистрироваться используя Ваш адрес.", + "CANT_ACCESS_LOCATION_GET_IN_BY_ADDRESS": "Не возможно получить Ваше местонахождение.", + "GET_IN_BY_ADDRESS": "Зарегистрироваться", + "GET_IN": "Зарегистрироваться", + "YOUR_ADDRESS": "Пожалуйста введите Адрес", + "LAUNCH_NOTIFICATION": "Мы обещаем предлагать Вам только продукты, доступные по указанному адресу", + "BY_CODE": { + "OR_WHAT": "войти по приглашению", + "INVITED": "Войти по приглашению", + "LOGO": { + "DETAILS": "Доставка Еды & Takeaway" + }, + "INVITE_CODE": "Код приглашения" + }, + "DETECTING_LOCATION": "Пожалуйста подождите, мы пытаемся определить Ваш текущий адрес..." + }, + "PRODUCTS_VIEW": { + "TITLE": "Наши Продукты", + "BUY_BUTTON": { + "PRE": "Купить за ", + "SUF": "" + }, + "NOT_AVAILABLE": "Товар недоступен", + "MINUTES": "мин", + "DELIVERY": "Доставка", + "TAKEAWAY": "Takeaway", + "READYFOR": "Готово для", + "DETAILS": { + "DETAILS": "Детали", + "BACK": "Назад", + "INCLUDES": "Включает", + "BUY_FOR": "Купить для" + } + }, + "HELP_VIEW": { + "TITLE": "Помогите" + }, + "ORDER_HISTORY_VIEW": { + "TITLE": "История заказов", + "DETAILS": "Детали" + }, + "LAST_PURCHASES_VIEW": { + "TITLE": "Недавние Покупки", + "NOTHING_ORDERED": "Вы еще ничего не заказывали", + "TO_PRODUCTS": "К Продуктам" + }, + "ABOUT_VIEW": { + "TITLE": "О Нас" + }, + "TERMS_OF_USE_VIEW": { + "TITLE": "Правила Использования" + }, + "PRIVACY_VIEW": { + "TITLE": "Конфиденциальность" + }, + "LANGUAGE_VIEW": { + "TITLE": "Выбор Языка" + }, + "BUY_POPUP": { + "ORDER_PAID": "Заказ оплачен $", + "CANCEL": { + "STATUSES": [ + { + "TITLE": "Заказ был отменен при подготовке склада!", + "DETAILS": "", + "NOT_PAID_NOTE": "" + } + ] + }, + "VIEW_ORDER_PRODUCTS": "Посмотреть продукты для заказа", + "STATUSES": [ + { + "TITLE": "Мы готовим Ваш заказ!", + "DETAILS": "Вы должны получить его в течении %t минут.", + "NOT_PAID_NOTE": "Вам будет необходимо оплатить %s (наличными курьеру)." + }, + { + "TITLE": "Курьер по дороге к Вам!", + "DETAILS": "Вы должны получить заказ в течении %t минут.", + "NOT_PAID_NOTE": "Вам будет необходимо оплатить %s (наличными курьеру)." + }, + { + "TITLE": "Курьер уже практически у Вас!", + "DETAILS": "Вы получите заказ через несколько секунд.", + "NOT_PAID_NOTE": "Вам будет необходимо оплатить %s (наличными курьеру)." + }, + { + "TITLE": "Заказ доставлен!", + "DETAILS": "Спасибо за использование Ever" + } + ], + "STATUSES_TAKEAWAY": { + "TITLE": "Мы готовим Ваш заказ!", + "DETAILS": "Вы можете получить это в течении %t минут.", + "NOT_PAID_NOTE": "Вам будет необходимо оплатить %s (наличными курьеру)." + }, + "DELIVERY_STATUS": { + "WE": "Мы", + "CARRIER": "Курьер", + "YOU": "Вы" + }, + "DELIVERY_WRONG": { + "DELIVERY_WRONG": "Доставка прошла неправильно!", + "PROCESSING_WRONG": "Произошла ошибка!", + "TRY_AGAIN": "Пожалуйста, попробуйте еще раз.", + "CALL_FOR_DETAILS": "Призыв к деталям" + }, + "ELAPSED_TIME": { + "TITLE": "Пройденное время" + }, + "BUTTONS": { + "UNDO": "Отменить", + "PAY_NOW": "Оплатить Кредиткой", + "PAY_X": "Оплатить {{amount}}", + "PAY_WITH_FIXED_CARD": "Оплатить с", + "PAID": "Оплачено", + "END": "Наслаждайтесь покупкой!", + "CANCEL": "Отменить", + "GOT_IT": "Понял", + "IM_HERE": "Я здесь", + "SHOW_QR_CODE": "Показать QR-код", + "SHOW_PRODUCTS": "Показать продукты" + }, + "UNDO_POPUP": { + "TITLE": "Вы уверены?", + "DETAILS": [ + "Отмена приведёт в возврату уплаченной суммы, но Вы", + "не получите", + "заказ!" + ] + } + }, + "SIDE_MENU": { + "TITLE": "Ever Shop", + "GROUPS": { + "NO_TITLE": { + "DIVER_TITLE": "", + "ITEMS": { + "PRODUCTS": "Продукты", + "LAST_PURCHASES": "Недавние Покупки", + "CALL_US": "Позвонить Клиенту", + "ORDER_HISTORY": "История заказов" + } + }, + "STORE": { + "STORE_TITLE": "Хранить", + "ITEMS": { + "CALL_WAITER": "Вызов официанта", + "ABOUT": "Около" + } + }, + "SETTINGS": { + "DIVER_TITLE": "Настройки", + "ITEMS": { + "LANGUAGE": "Язык" + } + }, + "INFO": { + "DIVER_TITLE": "Информация", + "ITEMS": { + "FAQ": "Помощь", + "ABOUT_US": "О Нас", + "TERMS_OF_USE": "Правила Использования", + "PRIVACY": "Конфиденциальность" + }, + "CALL": { + "WE_APPOLOGISE": "Мы приносим свои извинения!", + "CALL_UNSUCCESSFULL": "Вызов был неудачным!" + } + }, + "LEGALS": { + "DIVER_TITLE": "Юридические дела", + "ITEMS": { + "TERMS_OF_USE": "Правила Использования", + "PRIVACY": "Конфиденциальность" + } + } + } + }, + "TIMER": { + "MINUTES": "Минут", + "SECONDS": "Секунд" + }, + "CONNECTION_ERROR_VIEW": { + "TITLE": "Не удалось подключиться к Ever", + "DESCRIPTION": [ + "Проверьте подключение к", + "Интернету и повторите попытку.", + "" + ] + }, + "MERCHANTS_VIEW": { + "CLOSE_TO_YOU": "Торговцы рядом с вами", + "NAME": "Имя продавца", + "WITH_NAME": "Торговцы с именем" + }, + "SEARCH_VIEW": { + "VIEW_MORE": "Посмотреть больше", + "EMPTY_LIST": "Товары или продавцы не найдены...", + "SEARCH_PLACEHOLDER": "Название продукта или ресторана", + "OPEN": "Открытым", + "CLOSED": "Закрыто" + }, + "NO_SERVER_VIEW": { + "NO_SERVER": "Сервер упал" + }, + "OR": "Или", + "OR_LOWERCASE": "или", + "YES": "Да", + "NO": "Нет", + "OK": "Ok", + "CITY": "Город", + "STREET": "Улица", + "HOUSE": "Дом", + "APARTMENT": "Квартира", + "BACK": "Назад", + "MORE": "Больше", + "TO": "До", + "STORE_INFO": "Информация о магазине", + "MAP": "Карта", + "ORDER_INFO": "Информация о заказе", + "CLOSE": "заканчиваться", + "SCAN": "Поиск", + "MERCHANTS": "Купечество", + "NOT_FOUND": "Нет результатов!", + "IN_STORE": "В магазине", + "EXIT_STORE": "Выход из магазина", + "FAILED": "Не удалось", + "CANCELED": "Отменен", + "IN_DELIVERY": "В доставке", + "PENDING": "В ожидании", + "COMPLETED": "Завершенный", + "BROWSE": "ПРОСМАТРИВАТЬ", + "ENGLISH": "Английский", + "HEBREW": "Иврит", + "RUSSIAN": "Русский", + "BULGARIAN": "Болгарский", + "SPANISH": "испанский язык", + "FRENCH": "Французский", + "SELECT": "Выбрать", + "LOCATION_NOTES": "Примечания о местонахождении", + "ENTER_NOTES_HERE": "Введите примечания здесь" +} diff --git a/packages/shop-mobile-expo/src/store/features/translation/types.ts b/packages/shop-mobile-expo/src/store/features/translation/types.ts new file mode 100644 index 0000000..fb10d20 --- /dev/null +++ b/packages/shop-mobile-expo/src/store/features/translation/types.ts @@ -0,0 +1,20 @@ +// LANGUAGES +import LANGUAGES from './lang'; + +// TYPES +export type supportedLangType = + | 'BULGARIAN' + | 'ENGLISH' + | 'FRENCH' + | 'HEBREW' + | 'RUSSIAN' + | 'SPANISH'; +export type supportedLangsType = { + readonly [name in supportedLangType]: supportedLangType; +}; +export type supportedLangsObjectType = { + readonly [name in supportedLangType]: typeof LANGUAGES; +}; +export type TranslationStateType = { + lang: supportedLangType; +}; diff --git a/packages/shop-mobile-expo/src/store/features/user/index.ts b/packages/shop-mobile-expo/src/store/features/user/index.ts new file mode 100644 index 0000000..865cdc8 --- /dev/null +++ b/packages/shop-mobile-expo/src/store/features/user/index.ts @@ -0,0 +1,90 @@ +import { createSlice, PayloadAction } from '@reduxjs/toolkit'; +import asyncStorage from '@react-native-async-storage/async-storage'; + +// TYPES +import type { UserStateType } from './types'; +import type { RootState } from '../../index'; + +// ENVIRONNEMENT +import ENVIRONNEMENT from '../../../environments/environment'; + +const INITIAL_STATE: UserStateType = { + data: { + invite: null, + user: null, + }, + isLoggedIn: false, + productViewType: ENVIRONNEMENT.PRODUCTS_VIEW_TYPE, + orderInfoType: ENVIRONNEMENT.ORDER_INFO_TYPE, +}; + +export const navigationSlice = createSlice({ + name: 'user', + initialState: INITIAL_STATE, + reducers: { + setUser: (state, action: PayloadAction) => { + state.data = action.payload.data; + state.isLoggedIn = action.payload.isLoggedIn; + state.productViewType = action.payload.productViewType; + state.orderInfoType = action.payload.orderInfoType; + + asyncStorage.setItem('user', JSON.stringify(state)); + }, + setData: (state, action: PayloadAction) => { + state.data = action.payload; + + asyncStorage.setItem('user', JSON.stringify(state)); + }, + onUserSignUpByAddressSuccess: ( + state, + action: PayloadAction, + ) => { + state.data = action.payload; + state.isLoggedIn = true; + + asyncStorage.setItem('user', JSON.stringify(state)); + }, + setProductViewType: ( + state, + action: PayloadAction, + ) => { + state.productViewType = action.payload; + + asyncStorage.setItem('user', JSON.stringify(state)); + }, + setOrderInfoType: ( + state, + action: PayloadAction, + ) => { + state.orderInfoType = action.payload; + + asyncStorage.setItem('user', JSON.stringify(state)); + }, + resetUser: (state) => { + state.data = INITIAL_STATE.data; + state.isLoggedIn = INITIAL_STATE.isLoggedIn; + + asyncStorage.setItem('user', JSON.stringify(state)); + }, + }, +}); + +// ACTIONS +export const setUser = navigationSlice.actions.setUser; +export const setUserData = navigationSlice.actions.setData; +export const onUserSignUpByAddressSuccess = + navigationSlice.actions.onUserSignUpByAddressSuccess; +export const setProductViewType = navigationSlice.actions.setProductViewType; +export const setOrderInfoType = navigationSlice.actions.setOrderInfoType; +export const resetUser = navigationSlice.actions.resetUser; + +// SELECTORS +export const getUserObject = (state: RootState) => state.user; +export const getUserData = (state: RootState) => state.user.data; +export const getIsInvite = (state: RootState) => + !!(state?.user?.data?.invite && state?.user?.data?.user !== null); +export const getProductViewType = (state: RootState) => + state.user.productViewType; +export const getOrderInfoType = (state: RootState) => state.user.orderInfoType; + +export default navigationSlice.reducer; diff --git a/packages/shop-mobile-expo/src/store/features/user/types.ts b/packages/shop-mobile-expo/src/store/features/user/types.ts new file mode 100644 index 0000000..2934411 --- /dev/null +++ b/packages/shop-mobile-expo/src/store/features/user/types.ts @@ -0,0 +1,16 @@ +// TYPES/INTERFACES +import type ENV from '../../../environments/model'; +import { + UserLoginInfoInterface, + NewInviteInterface, +} from '../../../client/types'; + +export interface UserStateType { + data: { + invite?: NewInviteInterface | null; + user?: UserLoginInfoInterface | null; + }; + isLoggedIn: boolean; + productViewType: ENV['PRODUCTS_VIEW_TYPE']; + orderInfoType: ENV['ORDER_INFO_TYPE']; +} diff --git a/packages/shop-mobile-expo/src/store/hooks.ts b/packages/shop-mobile-expo/src/store/hooks.ts new file mode 100644 index 0000000..81bfc17 --- /dev/null +++ b/packages/shop-mobile-expo/src/store/hooks.ts @@ -0,0 +1,5 @@ +import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux'; +import type { RootState, AppDispatch } from './index'; + +export const useAppDispatch = () => useDispatch(); +export const useAppSelector: TypedUseSelectorHook = useSelector; diff --git a/packages/shop-mobile-expo/src/store/index.ts b/packages/shop-mobile-expo/src/store/index.ts new file mode 100644 index 0000000..4d8e078 --- /dev/null +++ b/packages/shop-mobile-expo/src/store/index.ts @@ -0,0 +1,29 @@ +import { configureStore } from '@reduxjs/toolkit'; +import logger from 'redux-logger'; + +// CONSTANTS +import ENV from '../environments/environment'; + +// REDUCERS +import navigationReducer from './features/navigation'; +import translationReducer from './features/translation'; +import userReducer from './features/user'; +import orderReducer from './features/order'; + +export const store = configureStore({ + reducer: { + user: userReducer, + navigation: navigationReducer, + translation: translationReducer, + order: orderReducer, + }, + middleware: (getDefaultMiddleware) => + getDefaultMiddleware({ + serializableCheck: false, + }).concat(logger), + devTools: __DEV__ || !ENV.PRODUCTION, +}); + +// TYPES +export type RootState = ReturnType; +export type AppDispatch = typeof store.dispatch; diff --git a/packages/shop-mobile-expo/src/types/index.ts b/packages/shop-mobile-expo/src/types/index.ts new file mode 100644 index 0000000..166462a --- /dev/null +++ b/packages/shop-mobile-expo/src/types/index.ts @@ -0,0 +1,37 @@ +// This file will contain global types/interfaces +// that will be used in the react app + +/** + * Modify properties type of a ``Type`` or ``Interface`` + * + * i.e: + * ```javascript + * interface OriginalInterface { + * a: string; + * b: boolean; + * c: number; + * } + * + * type ModifiedType = Modify + * + * // ModifiedType = { a: number; b: number; c: number; } + * ``` + */ +export type ModifyPropertiesTypes = Omit & R; + +// TODO: add more comments +export type MaybeType = T | null; + +export interface ScalarsInterface { + ID: string; + String: string; + Boolean: boolean; + Int: number; + Float: number; + Date: any; + Any: any; + Void: any; +} diff --git a/packages/shop-mobile-expo/tsconfig.json b/packages/shop-mobile-expo/tsconfig.json new file mode 100644 index 0000000..44ba97b --- /dev/null +++ b/packages/shop-mobile-expo/tsconfig.json @@ -0,0 +1,38 @@ +{ + "compilerOptions": { + "allowSyntheticDefaultImports": true, + "esModuleInterop": true, + "experimentalDecorators": true, + "forceConsistentCasingInFileNames": true, + "importHelpers": true, + "jsx": "react-native", + "lib": ["dom", "esnext"], + "moduleResolution": "node", + "noFallthroughCasesInSwitch": true, + "noEmit": true, + "noEmitHelpers": true, + "noImplicitReturns": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "skipLibCheck": true, + "strict": true, + "target": "esnext", + "allowJs": true, + "baseUrl": ".", + "paths": { + "*": ["src/*"], + "@assets": ["src/assets/*"], + "@environment": ["./src/environments/environment.ts"] + }, + "removeComments": true, + "typeRoots": ["node_modules/@types", "./src/@types"] + }, + "include": ["src"], + "exclude": [ + "node_modules", + "babel.config.js", + "metro.config.js", + "jest.config.js" + ], + "extends": "expo/tsconfig.base" +} diff --git a/packages/shop-mobile-flutter/.gitignore b/packages/shop-mobile-flutter/.gitignore new file mode 100644 index 0000000..0fa6b67 --- /dev/null +++ b/packages/shop-mobile-flutter/.gitignore @@ -0,0 +1,46 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.packages +.pub-cache/ +.pub/ +/build/ + +# Web related +lib/generated_plugin_registrant.dart + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Android Studio will place build artifacts here +/android/app/debug +/android/app/profile +/android/app/release diff --git a/packages/shop-mobile-flutter/.metadata b/packages/shop-mobile-flutter/.metadata new file mode 100644 index 0000000..be0f63d --- /dev/null +++ b/packages/shop-mobile-flutter/.metadata @@ -0,0 +1,10 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: 4cc385b4b84ac2f816d939a49ea1f328c4e0b48e + channel: stable + +project_type: app diff --git a/packages/shop-mobile-flutter/LICENSE.md b/packages/shop-mobile-flutter/LICENSE.md new file mode 100644 index 0000000..ced4c1b --- /dev/null +++ b/packages/shop-mobile-flutter/LICENSE.md @@ -0,0 +1,41 @@ +# License + +Copyright © 2016-present, Ever Co. LTD. All rights reserved. + +This software is available under different licenses + +### _Ever Platform Community Edition_ License for Shopping Mobile App + +If you decide to choose the Ever Platform Community Edition License for Shopping Mobile App, you must comply with the following terms: + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License, version 3, +as published by the Free Software Foundation. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . + +[GNU General Public License v3.0](https://www.gnu.org/licenses/gpl-3.0.txt) + +### _Ever Platform Enterprise_ License + +Alternatively, commercial versions of the software must be used in accordance with the terms and conditions of separate written agreement between you and Ever Co. LTD. + +For more information about Ever Platform Enterprise License please contact . + +#### The default Ever Platform license, without a valid Ever Platform Enterprise License agreement, is the Ever Platform Community Edition License. + +## Credits + +Please see [CREDITS.md](CREDITS.md) file for a list of libraries and software included in this program and information about licenses. + +## Trademarks + +**Ever**® is a registered trademark of [Ever Co. LTD](https://ever.co). +The trademark may only be used with the written permission of Ever Co. LTD. and may not be used to promote or otherwise market competitive products or services. +All other brand and product names are trademarks, registered trademarks or service marks of their respective holders. diff --git a/packages/shop-mobile-flutter/README.md b/packages/shop-mobile-flutter/README.md new file mode 100644 index 0000000..1384f27 --- /dev/null +++ b/packages/shop-mobile-flutter/README.md @@ -0,0 +1,43 @@ +# Ever Shop Mobile App (Flutter version) + +## Setting up Your Development Environment + +Find instructions for setting up your development machine with the Flutter framework on Flutter’s Get started page. The specific steps vary by platform, but they follow this basic format: + + 1. Download the installation bundle for your development machine’s operating system to get the latest stable release of the Flutter SDK. + 2. Extract the installation bundle in the desired location. + 3. Add the flutter tool to your path. + 4. Run the flutter doctor command, which alerts you to any problems with the Flutter installation. + 5. Install missing dependencies. + 6. Set up your IDE with a Flutter plugin/extension. + 7. Test drive an app. + +The instructions provided on the Flutter website are very well done and allow you to easily set up a development environment on your platform of choice. The remainder of this tutorial assumes you’ve set up VS Code for Flutter development and that you’ve addressed any issues flutter doctor found. You can also use Android Studio to follow along. + +To run your project as a mobile app, you’ll need to use one of the following options: + + - Run either iOS Simulator or an Android emulator. + - Have an iOS or Android device set up for development. + - Run your code as a web app. + - Finally, you can run your code as a desktop app. + +Even if your final target is mobile, using a web or desktop app during development gives you the advantage of being able to resize the app and observe how it would look with various screen sizes. If you have an older computer, the web or desktop version will also load faster than the Android emulator or iOS Simulator. + +NOTE: TO BUILD AND TEST ON IOS SIMULATOR OR AN IOS DEVICE, YOU’LL NEED TO USE MACOS WITH XCODE. ALSO, EVEN IF YOU’RE PLANNING TO USE VS CODE AS YOUR MAIN IDE, THE EASIEST WAY TO GET THE ANDROID SDK AND ANDROID EMULATOR IS TO INSTALL ANDROID STUDIO AS WELL. + +Clone or download the latest version of the project on https://github.com/ever-co/ever-demand +Open the project in either VS Code or Android Studio. + +Open it in VS Code by opening the root folder. You’ll need to fetch packages before running the project. +Do so by pressing Command-Shift-P on MacOS or Control-Shift-P on Windows or Linux to open the command palette and running the Flutter: Get Packages command. + +To open the project in Android Studio, choose Open an existing project from the Welcome to Android Studio screen and navigate to choose the root folder of the final project. Then choose Get dependencies on the 'Pub get' has not been run line in Android Studio. + +## Project Structure + + -> assets : here we have fonts, i18n, colors and images + -> lib : here are all the codes + -> models + -> screens + -> middlewares,... + -> test: will contain all our tests diff --git a/packages/shop-mobile-flutter/analysis_options.yaml b/packages/shop-mobile-flutter/analysis_options.yaml new file mode 100644 index 0000000..61b6c4d --- /dev/null +++ b/packages/shop-mobile-flutter/analysis_options.yaml @@ -0,0 +1,29 @@ +# This file configures the analyzer, which statically analyzes Dart code to +# check for errors, warnings, and lints. +# +# The issues identified by the analyzer are surfaced in the UI of Dart-enabled +# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be +# invoked from the command line by running `flutter analyze`. + +# The following line activates a set of recommended lints for Flutter apps, +# packages, and plugins designed to encourage good coding practices. +include: package:flutter_lints/flutter.yaml + +linter: + # The lint rules applied to this project can be customized in the + # section below to disable rules from the `package:flutter_lints/flutter.yaml` + # included above or to enable additional rules. A list of all available lints + # and their documentation is published at + # https://dart-lang.github.io/linter/lints/index.html. + # + # Instead of disabling a lint rule for the entire project in the + # section below, it can also be suppressed for a single line of code + # or a specific dart file by using the `// ignore: name_of_lint` and + # `// ignore_for_file: name_of_lint` syntax on the line or in the file + # producing the lint. + rules: + # avoid_print: false # Uncomment to disable the `avoid_print` rule + # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/packages/shop-mobile-flutter/android/.gitignore b/packages/shop-mobile-flutter/android/.gitignore new file mode 100644 index 0000000..6f56801 --- /dev/null +++ b/packages/shop-mobile-flutter/android/.gitignore @@ -0,0 +1,13 @@ +gradle-wrapper.jar +/.gradle +/captures/ +/gradlew +/gradlew.bat +/local.properties +GeneratedPluginRegistrant.java + +# Remember to never publicly share your keystore. +# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app +key.properties +**/*.keystore +**/*.jks diff --git a/packages/shop-mobile-flutter/android/app/build.gradle b/packages/shop-mobile-flutter/android/app/build.gradle new file mode 100644 index 0000000..73903fd --- /dev/null +++ b/packages/shop-mobile-flutter/android/app/build.gradle @@ -0,0 +1,68 @@ +def localProperties = new Properties() +def localPropertiesFile = rootProject.file('local.properties') +if (localPropertiesFile.exists()) { + localPropertiesFile.withReader('UTF-8') { reader -> + localProperties.load(reader) + } +} + +def flutterRoot = localProperties.getProperty('flutter.sdk') +if (flutterRoot == null) { + throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") +} + +def flutterVersionCode = localProperties.getProperty('flutter.versionCode') +if (flutterVersionCode == null) { + flutterVersionCode = '1' +} + +def flutterVersionName = localProperties.getProperty('flutter.versionName') +if (flutterVersionName == null) { + flutterVersionName = '1.0' +} + +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" + +android { + compileSdkVersion 31 + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlinOptions { + jvmTarget = '1.8' + } + + sourceSets { + main.java.srcDirs += 'src/main/kotlin' + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId "com.example.shop_flutter_mobile" + minSdkVersion 16 + targetSdkVersion 30 + versionCode flutterVersionCode.toInteger() + versionName flutterVersionName + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig signingConfigs.debug + } + } +} + +flutter { + source '../..' +} + +dependencies { + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" +} diff --git a/packages/shop-mobile-flutter/android/app/src/debug/AndroidManifest.xml b/packages/shop-mobile-flutter/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 0000000..51aed92 --- /dev/null +++ b/packages/shop-mobile-flutter/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/packages/shop-mobile-flutter/android/app/src/main/AndroidManifest.xml b/packages/shop-mobile-flutter/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..35b00fb --- /dev/null +++ b/packages/shop-mobile-flutter/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + diff --git a/packages/shop-mobile-flutter/android/app/src/main/kotlin/com/example/shop_flutter_mobile/MainActivity.kt b/packages/shop-mobile-flutter/android/app/src/main/kotlin/com/example/shop_flutter_mobile/MainActivity.kt new file mode 100644 index 0000000..0ad0f4b --- /dev/null +++ b/packages/shop-mobile-flutter/android/app/src/main/kotlin/com/example/shop_flutter_mobile/MainActivity.kt @@ -0,0 +1,6 @@ +package com.example.shop_flutter_mobile + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity: FlutterActivity() { +} diff --git a/packages/shop-mobile-flutter/android/app/src/main/res/drawable-v21/launch_background.xml b/packages/shop-mobile-flutter/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 0000000..f74085f --- /dev/null +++ b/packages/shop-mobile-flutter/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/packages/shop-mobile-flutter/android/app/src/main/res/drawable/launch_background.xml b/packages/shop-mobile-flutter/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 0000000..304732f --- /dev/null +++ b/packages/shop-mobile-flutter/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/packages/shop-mobile-flutter/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/packages/shop-mobile-flutter/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..54bfc47 Binary files /dev/null and b/packages/shop-mobile-flutter/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/packages/shop-mobile-flutter/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/packages/shop-mobile-flutter/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..7f1f1e1 Binary files /dev/null and b/packages/shop-mobile-flutter/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/packages/shop-mobile-flutter/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/packages/shop-mobile-flutter/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..02e0620 Binary files /dev/null and b/packages/shop-mobile-flutter/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/packages/shop-mobile-flutter/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/packages/shop-mobile-flutter/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..1385e97 Binary files /dev/null and b/packages/shop-mobile-flutter/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/packages/shop-mobile-flutter/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/packages/shop-mobile-flutter/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..630f8a1 Binary files /dev/null and b/packages/shop-mobile-flutter/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/packages/shop-mobile-flutter/android/app/src/main/res/values-night/styles.xml b/packages/shop-mobile-flutter/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 0000000..449a9f9 --- /dev/null +++ b/packages/shop-mobile-flutter/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/packages/shop-mobile-flutter/android/app/src/main/res/values/styles.xml b/packages/shop-mobile-flutter/android/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..d74aa35 --- /dev/null +++ b/packages/shop-mobile-flutter/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/packages/shop-mobile-flutter/android/app/src/profile/AndroidManifest.xml b/packages/shop-mobile-flutter/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 0000000..51aed92 --- /dev/null +++ b/packages/shop-mobile-flutter/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/packages/shop-mobile-flutter/android/build.gradle b/packages/shop-mobile-flutter/android/build.gradle new file mode 100644 index 0000000..d315552 --- /dev/null +++ b/packages/shop-mobile-flutter/android/build.gradle @@ -0,0 +1,29 @@ +buildscript { + ext.kotlin_version = '1.6.10' + repositories { + google() + mavenCentral() + } + + dependencies { + classpath 'com.android.tools.build:gradle:4.1.0' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} + +allprojects { + repositories { + google() + mavenCentral() + } +} + +rootProject.buildDir = '../build' +subprojects { + project.buildDir = "${rootProject.buildDir}/${project.name}" + project.evaluationDependsOn(':app') +} + +task clean(type: Delete) { + delete rootProject.buildDir +} diff --git a/packages/shop-mobile-flutter/android/gradle.properties b/packages/shop-mobile-flutter/android/gradle.properties new file mode 100644 index 0000000..94adc3a --- /dev/null +++ b/packages/shop-mobile-flutter/android/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.jvmargs=-Xmx1536M +android.useAndroidX=true +android.enableJetifier=true diff --git a/packages/shop-mobile-flutter/android/gradle/wrapper/gradle-wrapper.properties b/packages/shop-mobile-flutter/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..cc5527d --- /dev/null +++ b/packages/shop-mobile-flutter/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Fri Jun 23 08:50:38 CEST 2017 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip diff --git a/packages/shop-mobile-flutter/android/settings.gradle b/packages/shop-mobile-flutter/android/settings.gradle new file mode 100644 index 0000000..44e62bc --- /dev/null +++ b/packages/shop-mobile-flutter/android/settings.gradle @@ -0,0 +1,11 @@ +include ':app' + +def localPropertiesFile = new File(rootProject.projectDir, "local.properties") +def properties = new Properties() + +assert localPropertiesFile.exists() +localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } + +def flutterSdkPath = properties.getProperty("flutter.sdk") +assert flutterSdkPath != null, "flutter.sdk not set in local.properties" +apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" diff --git a/packages/shop-mobile-flutter/assets/fonts/PlutoHeavyItalic.ttf b/packages/shop-mobile-flutter/assets/fonts/PlutoHeavyItalic.ttf new file mode 100644 index 0000000..7f912be Binary files /dev/null and b/packages/shop-mobile-flutter/assets/fonts/PlutoHeavyItalic.ttf differ diff --git a/packages/shop-mobile-flutter/assets/fonts/main_colors.dart b/packages/shop-mobile-flutter/assets/fonts/main_colors.dart new file mode 100644 index 0000000..1215f82 --- /dev/null +++ b/packages/shop-mobile-flutter/assets/fonts/main_colors.dart @@ -0,0 +1,6 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; + +const Color dRed = Color(0xFF252836); +const Color everSubtitle = Color(0xFF64656C); +const Color everSignin = Color(0xFFBD4742); diff --git a/packages/shop-mobile-flutter/assets/imgs/ever.png b/packages/shop-mobile-flutter/assets/imgs/ever.png new file mode 100644 index 0000000..51dfd56 Binary files /dev/null and b/packages/shop-mobile-flutter/assets/imgs/ever.png differ diff --git a/packages/shop-mobile-flutter/assets/imgs/icon.png b/packages/shop-mobile-flutter/assets/imgs/icon.png new file mode 100644 index 0000000..6794e16 Binary files /dev/null and b/packages/shop-mobile-flutter/assets/imgs/icon.png differ diff --git a/packages/shop-mobile-flutter/assets/imgs/logo.png b/packages/shop-mobile-flutter/assets/imgs/logo.png new file mode 100644 index 0000000..616fb2d Binary files /dev/null and b/packages/shop-mobile-flutter/assets/imgs/logo.png differ diff --git a/packages/shop-mobile-flutter/assets/imgs/spiced-pasta.webp b/packages/shop-mobile-flutter/assets/imgs/spiced-pasta.webp new file mode 100644 index 0000000..67e6a87 Binary files /dev/null and b/packages/shop-mobile-flutter/assets/imgs/spiced-pasta.webp differ diff --git a/packages/shop-mobile-flutter/ios/.gitignore b/packages/shop-mobile-flutter/ios/.gitignore new file mode 100644 index 0000000..151026b --- /dev/null +++ b/packages/shop-mobile-flutter/ios/.gitignore @@ -0,0 +1,33 @@ +*.mode1v3 +*.mode2v3 +*.moved-aside +*.pbxuser +*.perspectivev3 +**/*sync/ +.sconsign.dblite +.tags* +**/.vagrant/ +**/DerivedData/ +Icon? +**/Pods/ +**/.symlinks/ +profile +xcuserdata +**/.generated/ +Flutter/App.framework +Flutter/Flutter.framework +Flutter/Flutter.podspec +Flutter/Generated.xcconfig +Flutter/ephemeral/ +Flutter/app.flx +Flutter/app.zip +Flutter/flutter_assets/ +Flutter/flutter_export_environment.sh +ServiceDefinitions.json +Runner/GeneratedPluginRegistrant.* + +# Exceptions to above rules. +!default.mode1v3 +!default.mode2v3 +!default.pbxuser +!default.perspectivev3 diff --git a/packages/shop-mobile-flutter/ios/Flutter/AppFrameworkInfo.plist b/packages/shop-mobile-flutter/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 0000000..8d4492f --- /dev/null +++ b/packages/shop-mobile-flutter/ios/Flutter/AppFrameworkInfo.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + App + CFBundleIdentifier + io.flutter.flutter.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + App + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + MinimumOSVersion + 9.0 + + diff --git a/packages/shop-mobile-flutter/ios/Flutter/Debug.xcconfig b/packages/shop-mobile-flutter/ios/Flutter/Debug.xcconfig new file mode 100644 index 0000000..592ceee --- /dev/null +++ b/packages/shop-mobile-flutter/ios/Flutter/Debug.xcconfig @@ -0,0 +1 @@ +#include "Generated.xcconfig" diff --git a/packages/shop-mobile-flutter/ios/Flutter/Release.xcconfig b/packages/shop-mobile-flutter/ios/Flutter/Release.xcconfig new file mode 100644 index 0000000..592ceee --- /dev/null +++ b/packages/shop-mobile-flutter/ios/Flutter/Release.xcconfig @@ -0,0 +1 @@ +#include "Generated.xcconfig" diff --git a/packages/shop-mobile-flutter/ios/Runner.xcodeproj/project.pbxproj b/packages/shop-mobile-flutter/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000..a1912b3 --- /dev/null +++ b/packages/shop-mobile-flutter/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,471 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; +/* End PBXBuildFile section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 9705A1C41CF9048500538489 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; + 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; + 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 9740EEB11CF90186004384FC /* Flutter */ = { + isa = PBXGroup; + children = ( + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 9740EEB31CF90195004384FC /* Generated.xcconfig */, + ); + name = Flutter; + sourceTree = ""; + }; + 97C146E51CF9000F007C117D = { + isa = PBXGroup; + children = ( + 9740EEB11CF90186004384FC /* Flutter */, + 97C146F01CF9000F007C117D /* Runner */, + 97C146EF1CF9000F007C117D /* Products */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + ); + name = Products; + sourceTree = ""; + }; + 97C146F01CF9000F007C117D /* Runner */ = { + isa = PBXGroup; + children = ( + 97C146FA1CF9000F007C117D /* Main.storyboard */, + 97C146FD1CF9000F007C117D /* Assets.xcassets */, + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, + 97C147021CF9000F007C117D /* Info.plist */, + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, + ); + path = Runner; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 97C146ED1CF9000F007C117D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Runner; + productName = Runner; + productReference = 97C146EE1CF9000F007C117D /* Runner.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 97C146E61CF9000F007C117D /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 1020; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 97C146ED1CF9000F007C117D = { + CreatedOnToolsVersion = 7.3.1; + LastSwiftMigration = 1100; + }; + }; + }; + buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 97C146E51CF9000F007C117D; + productRefGroup = 97C146EF1CF9000F007C117D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 97C146ED1CF9000F007C117D /* Runner */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 97C146EC1CF9000F007C117D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; + }; + 9740EEB61CF901F6004384FC /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 97C146EA1CF9000F007C117D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + 97C146FA1CF9000F007C117D /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C146FB1CF9000F007C117D /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C147001CF9000F007C117D /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 249021D3217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Profile; + }; + 249021D4217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.example.shopFlutterMobile; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Profile; + }; + 97C147031CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 97C147041CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 97C147061CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.example.shopFlutterMobile; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + 97C147071CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.example.shopFlutterMobile; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147031CF9000F007C117D /* Debug */, + 97C147041CF9000F007C117D /* Release */, + 249021D3217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147061CF9000F007C117D /* Debug */, + 97C147071CF9000F007C117D /* Release */, + 249021D4217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 97C146E61CF9000F007C117D /* Project object */; +} diff --git a/packages/shop-mobile-flutter/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/packages/shop-mobile-flutter/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/packages/shop-mobile-flutter/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/packages/shop-mobile-flutter/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/shop-mobile-flutter/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/packages/shop-mobile-flutter/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/packages/shop-mobile-flutter/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/packages/shop-mobile-flutter/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/packages/shop-mobile-flutter/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/packages/shop-mobile-flutter/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/shop-mobile-flutter/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 0000000..a28140c --- /dev/null +++ b/packages/shop-mobile-flutter/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/shop-mobile-flutter/ios/Runner.xcworkspace/contents.xcworkspacedata b/packages/shop-mobile-flutter/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..1d526a1 --- /dev/null +++ b/packages/shop-mobile-flutter/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/packages/shop-mobile-flutter/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/shop-mobile-flutter/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/packages/shop-mobile-flutter/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/packages/shop-mobile-flutter/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/packages/shop-mobile-flutter/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/packages/shop-mobile-flutter/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/packages/shop-mobile-flutter/ios/Runner/AppDelegate.swift b/packages/shop-mobile-flutter/ios/Runner/AppDelegate.swift new file mode 100644 index 0000000..70693e4 --- /dev/null +++ b/packages/shop-mobile-flutter/ios/Runner/AppDelegate.swift @@ -0,0 +1,13 @@ +import UIKit +import Flutter + +@UIApplicationMain +@objc class AppDelegate: FlutterAppDelegate { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + GeneratedPluginRegistrant.register(with: self) + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } +} diff --git a/packages/shop-mobile-flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/100.png b/packages/shop-mobile-flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/100.png new file mode 100644 index 0000000..8812f71 Binary files /dev/null and b/packages/shop-mobile-flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/100.png differ diff --git a/packages/shop-mobile-flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/1024.png b/packages/shop-mobile-flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/1024.png new file mode 100644 index 0000000..e2858f2 Binary files /dev/null and b/packages/shop-mobile-flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/1024.png differ diff --git a/packages/shop-mobile-flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/114.png b/packages/shop-mobile-flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/114.png new file mode 100644 index 0000000..9fa52fc Binary files /dev/null and b/packages/shop-mobile-flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/114.png differ diff --git a/packages/shop-mobile-flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/120.png b/packages/shop-mobile-flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/120.png new file mode 100644 index 0000000..6a9c3e7 Binary files /dev/null and b/packages/shop-mobile-flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/120.png differ diff --git a/packages/shop-mobile-flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/128.png b/packages/shop-mobile-flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/128.png new file mode 100644 index 0000000..8bbadb7 Binary files /dev/null and b/packages/shop-mobile-flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/128.png differ diff --git a/packages/shop-mobile-flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/144.png b/packages/shop-mobile-flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/144.png new file mode 100644 index 0000000..1385e97 Binary files /dev/null and b/packages/shop-mobile-flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/144.png differ diff --git a/packages/shop-mobile-flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/152.png b/packages/shop-mobile-flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/152.png new file mode 100644 index 0000000..8b5f14d Binary files /dev/null and b/packages/shop-mobile-flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/152.png differ diff --git a/packages/shop-mobile-flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/16.png b/packages/shop-mobile-flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/16.png new file mode 100644 index 0000000..b3861aa Binary files /dev/null and b/packages/shop-mobile-flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/16.png differ diff --git a/packages/shop-mobile-flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/167.png b/packages/shop-mobile-flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/167.png new file mode 100644 index 0000000..eb4c79f Binary files /dev/null and b/packages/shop-mobile-flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/167.png differ diff --git a/packages/shop-mobile-flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/172.png b/packages/shop-mobile-flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/172.png new file mode 100644 index 0000000..124b0d1 Binary files /dev/null and b/packages/shop-mobile-flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/172.png differ diff --git a/packages/shop-mobile-flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/180.png b/packages/shop-mobile-flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/180.png new file mode 100644 index 0000000..0798a6d Binary files /dev/null and b/packages/shop-mobile-flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/180.png differ diff --git a/packages/shop-mobile-flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/196.png b/packages/shop-mobile-flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/196.png new file mode 100644 index 0000000..a4df3fd Binary files /dev/null and b/packages/shop-mobile-flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/196.png differ diff --git a/packages/shop-mobile-flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/20.png b/packages/shop-mobile-flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/20.png new file mode 100644 index 0000000..aa4384c Binary files /dev/null and b/packages/shop-mobile-flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/20.png differ diff --git a/packages/shop-mobile-flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/216.png b/packages/shop-mobile-flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/216.png new file mode 100644 index 0000000..93c9e23 Binary files /dev/null and b/packages/shop-mobile-flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/216.png differ diff --git a/packages/shop-mobile-flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/256.png b/packages/shop-mobile-flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/256.png new file mode 100644 index 0000000..1f541c2 Binary files /dev/null and b/packages/shop-mobile-flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/256.png differ diff --git a/packages/shop-mobile-flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/29.png b/packages/shop-mobile-flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/29.png new file mode 100644 index 0000000..df3273d Binary files /dev/null and b/packages/shop-mobile-flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/29.png differ diff --git a/packages/shop-mobile-flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/32.png b/packages/shop-mobile-flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/32.png new file mode 100644 index 0000000..8c6b3de Binary files /dev/null and b/packages/shop-mobile-flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/32.png differ diff --git a/packages/shop-mobile-flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/40.png b/packages/shop-mobile-flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/40.png new file mode 100644 index 0000000..0c8bf76 Binary files /dev/null and b/packages/shop-mobile-flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/40.png differ diff --git a/packages/shop-mobile-flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/48.png b/packages/shop-mobile-flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/48.png new file mode 100644 index 0000000..7f1f1e1 Binary files /dev/null and b/packages/shop-mobile-flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/48.png differ diff --git a/packages/shop-mobile-flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/50.png b/packages/shop-mobile-flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/50.png new file mode 100644 index 0000000..ae7b57d Binary files /dev/null and b/packages/shop-mobile-flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/50.png differ diff --git a/packages/shop-mobile-flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/512.png b/packages/shop-mobile-flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/512.png new file mode 100644 index 0000000..bae6fb1 Binary files /dev/null and b/packages/shop-mobile-flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/512.png differ diff --git a/packages/shop-mobile-flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/55.png b/packages/shop-mobile-flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/55.png new file mode 100644 index 0000000..57c6a96 Binary files /dev/null and b/packages/shop-mobile-flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/55.png differ diff --git a/packages/shop-mobile-flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/57.png b/packages/shop-mobile-flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/57.png new file mode 100644 index 0000000..98c9c2e Binary files /dev/null and b/packages/shop-mobile-flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/57.png differ diff --git a/packages/shop-mobile-flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/58.png b/packages/shop-mobile-flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/58.png new file mode 100644 index 0000000..96134ed Binary files /dev/null and b/packages/shop-mobile-flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/58.png differ diff --git a/packages/shop-mobile-flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/60.png b/packages/shop-mobile-flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/60.png new file mode 100644 index 0000000..a9e9e3e Binary files /dev/null and b/packages/shop-mobile-flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/60.png differ diff --git a/packages/shop-mobile-flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/64.png b/packages/shop-mobile-flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/64.png new file mode 100644 index 0000000..bde3fa2 Binary files /dev/null and b/packages/shop-mobile-flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/64.png differ diff --git a/packages/shop-mobile-flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/72.png b/packages/shop-mobile-flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/72.png new file mode 100644 index 0000000..54bfc47 Binary files /dev/null and b/packages/shop-mobile-flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/72.png differ diff --git a/packages/shop-mobile-flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/76.png b/packages/shop-mobile-flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/76.png new file mode 100644 index 0000000..e697366 Binary files /dev/null and b/packages/shop-mobile-flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/76.png differ diff --git a/packages/shop-mobile-flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/80.png b/packages/shop-mobile-flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/80.png new file mode 100644 index 0000000..f612b58 Binary files /dev/null and b/packages/shop-mobile-flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/80.png differ diff --git a/packages/shop-mobile-flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/87.png b/packages/shop-mobile-flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/87.png new file mode 100644 index 0000000..eab4787 Binary files /dev/null and b/packages/shop-mobile-flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/87.png differ diff --git a/packages/shop-mobile-flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/88.png b/packages/shop-mobile-flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/88.png new file mode 100644 index 0000000..df96b45 Binary files /dev/null and b/packages/shop-mobile-flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/88.png differ diff --git a/packages/shop-mobile-flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/packages/shop-mobile-flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..e138c0b --- /dev/null +++ b/packages/shop-mobile-flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1 @@ +{"images":[{"size":"60x60","expected-size":"180","filename":"180.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"40x40","expected-size":"80","filename":"80.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"40x40","expected-size":"120","filename":"120.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"60x60","expected-size":"120","filename":"120.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"57x57","expected-size":"57","filename":"57.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"1x"},{"size":"29x29","expected-size":"58","filename":"58.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"29x29","expected-size":"29","filename":"29.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"1x"},{"size":"29x29","expected-size":"87","filename":"87.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"57x57","expected-size":"114","filename":"114.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"20x20","expected-size":"40","filename":"40.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"20x20","expected-size":"60","filename":"60.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"1024x1024","filename":"1024.png","expected-size":"1024","idiom":"ios-marketing","folder":"Assets.xcassets/AppIcon.appiconset/","scale":"1x"},{"size":"40x40","expected-size":"80","filename":"80.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"72x72","expected-size":"72","filename":"72.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"76x76","expected-size":"152","filename":"152.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"50x50","expected-size":"100","filename":"100.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"29x29","expected-size":"58","filename":"58.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"76x76","expected-size":"76","filename":"76.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"29x29","expected-size":"29","filename":"29.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"50x50","expected-size":"50","filename":"50.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"72x72","expected-size":"144","filename":"144.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"40x40","expected-size":"40","filename":"40.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"83.5x83.5","expected-size":"167","filename":"167.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"20x20","expected-size":"20","filename":"20.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"20x20","expected-size":"40","filename":"40.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"idiom":"watch","filename":"172.png","folder":"Assets.xcassets/AppIcon.appiconset/","subtype":"38mm","scale":"2x","size":"86x86","expected-size":"172","role":"quickLook"},{"idiom":"watch","filename":"80.png","folder":"Assets.xcassets/AppIcon.appiconset/","subtype":"38mm","scale":"2x","size":"40x40","expected-size":"80","role":"appLauncher"},{"idiom":"watch","filename":"88.png","folder":"Assets.xcassets/AppIcon.appiconset/","subtype":"40mm","scale":"2x","size":"44x44","expected-size":"88","role":"appLauncher"},{"idiom":"watch","filename":"100.png","folder":"Assets.xcassets/AppIcon.appiconset/","subtype":"44mm","scale":"2x","size":"50x50","expected-size":"100","role":"appLauncher"},{"idiom":"watch","filename":"196.png","folder":"Assets.xcassets/AppIcon.appiconset/","subtype":"42mm","scale":"2x","size":"98x98","expected-size":"196","role":"quickLook"},{"idiom":"watch","filename":"216.png","folder":"Assets.xcassets/AppIcon.appiconset/","subtype":"44mm","scale":"2x","size":"108x108","expected-size":"216","role":"quickLook"},{"idiom":"watch","filename":"48.png","folder":"Assets.xcassets/AppIcon.appiconset/","subtype":"38mm","scale":"2x","size":"24x24","expected-size":"48","role":"notificationCenter"},{"idiom":"watch","filename":"55.png","folder":"Assets.xcassets/AppIcon.appiconset/","subtype":"42mm","scale":"2x","size":"27.5x27.5","expected-size":"55","role":"notificationCenter"},{"size":"29x29","expected-size":"87","filename":"87.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"watch","role":"companionSettings","scale":"3x"},{"size":"29x29","expected-size":"58","filename":"58.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"watch","role":"companionSettings","scale":"2x"},{"size":"1024x1024","expected-size":"1024","filename":"1024.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"watch-marketing","scale":"1x"},{"size":"128x128","expected-size":"128","filename":"128.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"1x"},{"size":"256x256","expected-size":"256","filename":"256.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"1x"},{"size":"128x128","expected-size":"256","filename":"256.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"2x"},{"size":"256x256","expected-size":"512","filename":"512.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"2x"},{"size":"32x32","expected-size":"32","filename":"32.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"1x"},{"size":"512x512","expected-size":"512","filename":"512.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"1x"},{"size":"16x16","expected-size":"16","filename":"16.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"1x"},{"size":"16x16","expected-size":"32","filename":"32.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"2x"},{"size":"32x32","expected-size":"64","filename":"64.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"2x"},{"size":"512x512","expected-size":"1024","filename":"1024.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"2x"}]} \ No newline at end of file diff --git a/packages/shop-mobile-flutter/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/packages/shop-mobile-flutter/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json new file mode 100644 index 0000000..0bedcf2 --- /dev/null +++ b/packages/shop-mobile-flutter/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "LaunchImage.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/packages/shop-mobile-flutter/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/packages/shop-mobile-flutter/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/packages/shop-mobile-flutter/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png differ diff --git a/packages/shop-mobile-flutter/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/packages/shop-mobile-flutter/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/packages/shop-mobile-flutter/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png differ diff --git a/packages/shop-mobile-flutter/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/packages/shop-mobile-flutter/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/packages/shop-mobile-flutter/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png differ diff --git a/packages/shop-mobile-flutter/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/packages/shop-mobile-flutter/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 0000000..89c2725 --- /dev/null +++ b/packages/shop-mobile-flutter/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md @@ -0,0 +1,5 @@ +# Launch Screen Assets + +You can customize the launch screen with your own desired assets by replacing the image files in this directory. + +You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. \ No newline at end of file diff --git a/packages/shop-mobile-flutter/ios/Runner/Base.lproj/LaunchScreen.storyboard b/packages/shop-mobile-flutter/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000..f2e259c --- /dev/null +++ b/packages/shop-mobile-flutter/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/shop-mobile-flutter/ios/Runner/Base.lproj/Main.storyboard b/packages/shop-mobile-flutter/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 0000000..f3c2851 --- /dev/null +++ b/packages/shop-mobile-flutter/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/shop-mobile-flutter/ios/Runner/Info.plist b/packages/shop-mobile-flutter/ios/Runner/Info.plist new file mode 100644 index 0000000..8cf69ba --- /dev/null +++ b/packages/shop-mobile-flutter/ios/Runner/Info.plist @@ -0,0 +1,45 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + Ever + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UIViewControllerBasedStatusBarAppearance + + + diff --git a/packages/shop-mobile-flutter/ios/Runner/Runner-Bridging-Header.h b/packages/shop-mobile-flutter/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 0000000..308a2a5 --- /dev/null +++ b/packages/shop-mobile-flutter/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" diff --git a/packages/shop-mobile-flutter/l10n.yaml b/packages/shop-mobile-flutter/l10n.yaml new file mode 100644 index 0000000..4e6692e --- /dev/null +++ b/packages/shop-mobile-flutter/l10n.yaml @@ -0,0 +1,3 @@ +arb-dir: lib/l10n +template-arb-file: app_en.arb +output-localization-file: app_localizations.dart \ No newline at end of file diff --git a/packages/shop-mobile-flutter/lib/app.dart b/packages/shop-mobile-flutter/lib/app.dart new file mode 100644 index 0000000..4853fd1 --- /dev/null +++ b/packages/shop-mobile-flutter/lib/app.dart @@ -0,0 +1,51 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_localizations/flutter_localizations.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:shop_flutter_mobile/constants/colors.dart'; +import 'package:shop_flutter_mobile/utils/navigations.dart'; + +const customColor = AppColors(); + +class MyApp extends StatelessWidget { + const MyApp({Key? key}) : super(key: key); + + // This widget is the root of your application. + @override + Widget build(BuildContext context) { + return MaterialApp( + title: 'Ever', + debugShowCheckedModeBanner: false, + theme: ThemeData( + primaryColor: customColor.primaryColor, + scaffoldBackgroundColor: customColor.scaffoldBackgroundColor, + appBarTheme: AppBarTheme( + backgroundColor: customColor.appBarColor, + elevation: 0, + centerTitle: true, + systemOverlayStyle: SystemUiOverlayStyle( + statusBarColor: customColor.appBarColor, + ), + ), + ), + localizationsDelegates: const [ + AppLocalizations.delegate, + GlobalMaterialLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + GlobalCupertinoLocalizations.delegate, + ], + supportedLocales: const [ + Locale('en', ''), // English, no country code + Locale('es', ''), // Spanish, no country code + Locale('fr', ''), // French, no country code + Locale('bg', ''), // Bulgarian, no country code + Locale('ru', ''), // Russian, no country code + Locale('he', ''), // Hebrew, no country code + ], + initialRoute: "/login", + routes: routeNavigations, + // home: + // const MenuBar(), // calling the login Page as the first screen, no splashscreen + ); + } +} diff --git a/packages/shop-mobile-flutter/lib/constants/colors.dart b/packages/shop-mobile-flutter/lib/constants/colors.dart new file mode 100644 index 0000000..20bb199 --- /dev/null +++ b/packages/shop-mobile-flutter/lib/constants/colors.dart @@ -0,0 +1,71 @@ +import 'dart:ui'; + +class AppColors { + final Color dRed = const Color(0xFF1F212A); + final Color everSubtitle = const Color(0xFF64656C); + final Color everSignin = const Color(0xFFBD4742); + final Color whiteColor = const Color(0xFFFFFFFF); + final Color facebookColor = const Color(0xFF3B5998); + final Color googleColor = const Color(0xFFDD4B39); + final Color greyColor = const Color(0xFF6A6A6A); + + // AppBar Color + final Color appBarColor = const Color(0xFF2A2C39); + + // Scaffold Background Color + final Color scaffoldBackgroundColor = const Color(0xFF1F212A); + + // Primary Color + final Color primaryColor = const Color(0xff2a2c39); + final Color primaryColorShade = const Color(0xff252732); + final Color primaryColorTint = const Color(0xff3f414d); + +// Secondary Color + final Color secondaryColor = const Color(0xffbd4742); + final Color secondaryColorShade = const Color(0xffa63e3a); + final Color secondaryColorTint = const Color(0xffc45955); + +// Tertiary Color + final Color tertiaryColor = const Color(0xff7044ff); + final Color tertiaryColorShade = const Color(0xff633ce0); + final Color tertiaryColorTint = const Color(0xff7e57ff); + +// Success Color + final Color successColor = const Color(0xff10dc60); + final Color successColorShade = const Color(0xff0ec254); + final Color successColorTint = const Color(0xff28e070); + +// Warning Color + final Color warningColor = const Color(0xffffce00); + final Color warningColorShade = const Color(0xffe0b500); + final Color warningColorTint = const Color(0xffffd31a); + +// Danger Color + final Color dangerColor = const Color(0xfff04141); + final Color dangerColorShade = const Color(0xffd33939); + final Color dangerColorTint = const Color(0xfff25454); + +// Dark Color + final Color darkColor = const Color(0xff222428); + final Color darkColorShade = const Color(0xff1e2023); + final Color darkColorTint = const Color(0xff383a3e); + +// Medium Color + final Color mediumColor = const Color(0xff989aa2); + final Color mediumColorShade = const Color(0xff86888f); + final Color mediumColorTint = const Color(0xffa2a4ab); + +// Light Color + final Color lightColor = const Color(0xfff4f5f8); + final Color lightColorShade = const Color(0xffd7d8da); + final Color lightColorTint = const Color(0xfff5f6f9); + +// Generic Color + final Color backgroundColor = const Color(0xfff1f1f1); + final Color borderColor = const Color(0xfff3f3f3); +// final Color facebookColor = const Color(0xff49659F); +// final Color googleColor = const Color(0xffDD4B39); + final Color signupBackgroundColor = const Color(0xff1F212A); + + const AppColors(); +} diff --git a/packages/shop-mobile-flutter/lib/l10n/app_bg.arb b/packages/shop-mobile-flutter/lib/l10n/app_bg.arb new file mode 100644 index 0000000..e63239f --- /dev/null +++ b/packages/shop-mobile-flutter/lib/l10n/app_bg.arb @@ -0,0 +1,153 @@ +{ + "language": "bg", + "@language": { + "id": "bg-BG", + "name": "Bulgarian", + "description": "Bulgarian" + }, + "currentDirection": "ltr", + "sidebarSide": "left", + "welcomeTo": "Добре дошли", + "ever": "Евър", + "infoMissing": "Част от информацията липсва!", + "yourInviteCode": "Вашият поканен код", + "invitedTextTitle":"Благодарим Ви, че се регистрирате!", + "invitedTextDetails": "Ще ви изпратим известие с код за покана, когато стартираме:", + "notInvitedByCodeTitle":"Грешен код!", + "notInvitedByCodeDetails": "Моля, уверете се, че сте на мястото, където сте получили кода си.", + "cantAccessLocation": "Няма достъп до местоположението ви", + "cantAccessLocationGetInByAdress": "Нямате достъп до местоположението си. Моля, опитайте да влезете по адрес.", + "getInByAddress": "Регистрирайте се по адрес", + "getInside": "Влез вътре!", + "yourAddress": "Уведомете Вашия адрес", + "launchNotification": "Обещаваме да показваме само подходящи продукти според вашия адрес", + "signUpByCode": "Код за покана", + "signUpByInvite": "Регистрирайте се чрез покана", + "signinByInvite": "Влезте с Invite Code", + "logoMotto":"Доставка и вземане на храна", + "@logoMotto": { + "details": "Доставка и вземане на храна" + }, + "inviteCode": "Код за покана", + "inviteCodePlaceholder": "Доставка и вземане на храна", + "detectingLocation": "Моля, изчакайте, опитваме се да открием настоящия си адрес ...", + "productsViewTitle":"Продукти", + "productsbuyButtonpre":"Купи за", + "productsNotAvailable": "Продуктът не е наличен", + "min": "мин", + "delivery": "Доставка", + "takeAway": "За вкъщи", + "readyFor": "Готов за", + "details": "Детайли", + "includes": "Включва", + "buyFor": "Купи за", + "help": "Помощ", + "lastPurchases":"Последни покупки", + "nothingOrdered": "Все още няма поръчки", + "toProducts": "Към продукти", + "aboutus": "За нас", + "termsOfUse": "Условия за ползване", + "privacyView": "Поверителност", + "languageView":"Избери език", + "viewMore":"Виж още", + "noProductsOrMerchants": "Няма намерени продукти или ресторанти ...", + "searchPlaceHolder": "Продукт или ресторант", + "open": "Отворено", + "closed": "Затворено", + "buyPopUpOrderPaid": "Поръчката е платена $", + "buyPopUpCancelstatuses":"Поръчката беше анулирана по време на подготовката на склада!", + "buyPopUpCancelstatusesDelivery":"Поръчката беше анулирана по време на подготовката на склада!", + "viewOrderProducts": "Преглед на продукти за поръчка", + "viewOrderProductsStatuses": "Подготвяме поръчката!", + "youWillGetItInMinutes": "Ще я получите между %t минути.", + "prepareYourWallet": "Подгответе портфейла си (%s в брой).", + "carrierOnTheWay": "Доставчика е в движение!", + "checkYourDoor": "Проверете си вратата!", + "youWillGetItInSeconds": "Ще получите реда в секунди.", + "orderCompleted": "Поръчка завършена!", + "thanksForUsingEver": "Благодарим Ви, че използвате Ever", + "weRePreparingTheOrder": "Подготвяме поръчката!", + "we": "Ние", + "carrier": "Доставчик", + "you": "Вие", + "deliveryWentWrong": "Доставката не беше правилно!", + "processingWentWrong": "Обработката не беше правилно!", + "pleaseTryAgain": "Моля, опитайте отново.", + "callForDetails": "Обадете се за подробности", + "elapsedTime":"Изминалото време", + "undo": "Отмяна", + "payWithCard": "Плащане с карта", + "payX": "Платете {{ сума }}", + "payWith": "Плати с", + "paid": "Платено", + "bonAppetit": "Добър апетит!", + "cancel": "Отказ", + "gotIt": "Схванах го", + "imHere": "Тук съм", + "showQrCode": "Показване на QR код", + "showProducts": "Показване на продукти", + "areYouSure": "Сигурни ли сте?", + "undoWillReturnMoney":"Отмяната ще Ви върне парите, но вие ще, загубите поръчката!", + "sideMenuTitle": "Ever", + "sideMenuProducts": "Продукти", + "callUs": "Обадете ни се", + "orderHistory": "История на поръчките", + "store": "Магазин", + "callWaiter": "Обадете се на сервитьор", + "about": "Относно", + "settings": "Настройки", + "information": "Информация", + "faq": "Помогне", + "aboutUs": "За нас", + "privacy": "Поверителност", + "weApologise": "Извиняваме се!", + "callUnsuccessfull": "Обаждането беше неуспешно!", + "legal": "Юридически", + "minutes": "Минути", + "seconds": "Секунди", + "noConnectionToEver": "Няма връзка с Ever", + "@noConnectionToEver": + { + "description": + "Моля, уверете се, че сте свързани с интернет или опитайте по-късно." + }, + "merchantsViewCloseToYou": "Търговци близо до вас", + "merchantsViewname": "Име на търговеца", + "merchantsViewwithName": "Търговци с име", + "noServerView":"Няма връзка със сървъра", + "or": "Или", + "orLowercase": "или", + "yes": "Да", + "no": "Не", + "ok": "Добре", + "city": "Сити", + "street": "Улица", + "house": "Къща", + "appartement": "Апартамент", + "back": "Обратно", + "more": "повече", + "to": "До", + "storeInfo": "Информация за магазина", + "map": "Карта", + "orderInfo": "Информация за поръчката", + "close": "Затворен", + "scan": "Търсене", + "merchants": "Купечество", + "notFoundNOT_FOUND": "Няма резултати!", + "inStore": "В магазина", + "exitStore": "Излезте от магазина", + "failed": "Се провали", + "canceled": "Отменен", + "inDelivery": "При доставка", + "pending": "В очакване", + "completed": "Завършен", + "browse": "РАЗГЛЕДАЙ", + "english": "Английски", + "hebrew": "Иврит", + "russian": "Руски", + "bulgarian": "Български", + "spanish": "Spanish", + "select": "Изберете", + "locationNotes": "Бележки за местоположението", + "enterNotesHere": "Въведете бележки тук" +} \ No newline at end of file diff --git a/packages/shop-mobile-flutter/lib/l10n/app_en.arb b/packages/shop-mobile-flutter/lib/l10n/app_en.arb new file mode 100644 index 0000000..238dbd8 --- /dev/null +++ b/packages/shop-mobile-flutter/lib/l10n/app_en.arb @@ -0,0 +1,153 @@ +{ + "language": "en", + "@language": { + "id": "en-US", + "name": "English", + "description": "English" + }, + "currentDirection": "ltr", + "sidebarSide": "left", + "welcomeTo": "Welcome to", + "ever": "ever", + "infoMissing": "Some of the information missing!", + "yourInviteCode": "Your invite code", + "invitedTextTitle":"Thank you for sign up!", + "invitedTextDetails": "We will send you a notification with an invite code when we launch at:", + "notInvitedByCodeTitle":"Wrong code!", + "notInvitedByCodeDetails": "Please make sure you at the place that came with your code.", + "cantAccessLocation": "Can't access your location.", + "cantAccessLocationGetInByAdress": "Can't access your location, please try to get in by address.", + "getInByAddress": "Sign up by Address", + "getInside": "Get inside!", + "yourAddress": "Let us know your Address", + "launchNotification": "We promise to show only relevant products according to your address", + "signUpByCode": "Sign up by code", + "signUpByInvite": "sign up by Invite", + "signinByInvite": "Sign in with Invite Code", + "logoMotto":"Food Delivery & Takeout", + "@logoMotto": { + "details": "Food Delivery & Takeout" + }, + "inviteCode": "Invite Code", + "inviteCodePlaceholder": "Enter your invite code", + "detectingLocation": "Please wait, we attempting to detect your current address...", + "productsViewTitle":"Products", + "productsbuyButtonpre":"Buy for ", + "productsNotAvailable": "Product not available", + "min": "min", + "delivery": "Delivery", + "takeAway": "Takeout", + "readyFor": "Ready for", + "details": "Details", + "includes": "Includes", + "buyFor": "Buy for", + "help": "Help", + "lastPurchases":"Last Purchases", + "nothingOrdered": "No orders yet", + "toProducts": "To Products", + "aboutus": "About Us", + "termsOfUse": "Terms of Use", + "privacyView": "Privacy", + "languageView":"Select Language", + "viewMore":"View More", + "noProductsOrMerchants": "No Products Or Merchants Found ... ", + "searchPlaceHolder": "Product Or Restorant Name", + "open": "Open", + "closed": "Closed", + "buyPopUpOrderPaid": "The order is paid $", + "buyPopUpCancelstatuses":"The order was cancel while Warehouse Preparation!", + "buyPopUpCancelstatusesDelivery":"The order was cancel while Delivery!", + "viewOrderProducts": "View Order Products", + "viewOrderProductsStatuses": "We're preparing the order!", + "youWillGetItInMinutes": "You will get it in %t minutes.", + "prepareYourWallet": "Prepare your wallet (%s in cash).", + "carrierOnTheWay": "Carrier on the way!", + "checkYourDoor": "Check your door!", + "youWillGetItInSeconds": "You will get the order in seconds.", + "orderCompleted": "Order Completed!", + "thanksForUsingEver": "Thanks for using Ever", + "weRePreparingTheOrder": "We're preparing the order!", + "we": "We", + "carrier": "Carrier", + "you": "You", + "deliveryWentWrong": "The Delivery Went Wrong!", + "processingWentWrong": "Processing Went Wrong!", + "pleaseTryAgain": "Please try again.", + "callForDetails": "Call for details", + "elapsedTime":"Elapsed time", + "undo": "Undo", + "payWithCard": "Pay with Card", + "payX": "Pay {{amount}}", + "payWith": "Pay with", + "paid": "Paid", + "bonAppetit": "Bon appetit!", + "cancel": "Cancel", + "gotIt": "Got It!", + "imHere": "I'm here", + "showQrCode": "Show QR Code", + "showProducts": "Show Products", + "areYouSure": "Are you sure?", + "undoWillReturnMoney":"Undo will return you the money, but you will lose the order.", + "sideMenuTitle": "Ever", + "sideMenuProducts": "Products", + "callUs": "Call Us", + "orderHistory": "Order history", + "store": "Store", + "callWaiter": "Call Waiter", + "about": "About", + "settings": "Settings", + "information": "Information", + "faq": "Help", + "aboutUs": "About Us", + "privacy": "Privacy", + "weApologise": "We Apologise!", + "callUnsuccessfull": "Call Was Unsuccessful!", + "legal": "LEGAL", + "minutes": "Minutes", + "seconds": "Seconds", + "noConnectionToEver": "No connection to Ever", + "@noConnectionToEver": + { + "description": + "Please make sure you are connected to the internet and try Ever App Later." + }, + "merchantsViewCloseToYou": "Merchants close to you", + "merchantsViewname": "Merchant name", + "merchantsViewwithName": "Merchants with name", + "noServerView":"Server connection is lost", + "or": "Or", + "orLowercase": "or", + "yes": "Yes", + "no": "No", + "ok": "Ok", + "city": "City", + "street": "Street", + "house": "House", + "appartement": "Apartment", + "back": "Back", + "more": "more", + "to": "To", + "storeInfo": "Store Info", + "map": "Map", + "orderInfo": "Order Info", + "close": "Close", + "scan": "Scan", + "merchants": "Merchants", + "notFoundNOT_FOUND": "Not Found!", + "inStore": "In Store", + "exitStore": "Exit Store", + "failed": "Failed", + "canceled": "Canceled", + "inDelivery": "In Delivery", + "pending": "Pending", + "completed": "Completed", + "browse": "Browse", + "english": "English", + "hebrew": "Hebrew", + "russian": "Russian", + "bulgarian": "Bulgarian", + "spanish": "Spanish", + "select": "Select", + "locationNotes": "Location Notes", + "enterNotesHere": "Enter notes here" +} \ No newline at end of file diff --git a/packages/shop-mobile-flutter/lib/l10n/app_es.arb b/packages/shop-mobile-flutter/lib/l10n/app_es.arb new file mode 100644 index 0000000..119db32 --- /dev/null +++ b/packages/shop-mobile-flutter/lib/l10n/app_es.arb @@ -0,0 +1,153 @@ +{ + "language": "es", + "@language": { + "id": "es-ES", + "name": "Español", + "description": "Español" + }, + "currentDirection": "ltr", + "sidebarSide": "left", + "welcomeTo": "Bienvenido a", + "ever": "Tu-Pedido", + "infoMissing": "¡Falta información!", + "yourInviteCode": "Su código de invitado", + "invitedTextTitle":"¡Gracias por registrarte!", + "invitedTextDetails": "Le enviaremos una notificación con un código de invitación cuando lancemos en:", + "notInvitedByCodeTitle":"¡Código erroneo", + "notInvitedByCodeDetails": "Asegúrate de estar en el lugar que dice tu código", + "cantAccessLocation": "No se puede acceder a tu ubicación", + "cantAccessLocationGetInByAdress": "No se puede acceder a tu ubicación, intenta ingresar por dirección.", + "getInByAddress": "Registrarse por dirección", + "getInside": "¡Entrar!", + "yourAddress": "Escribe tu dirección", + "launchNotification": "Prometemos mostrar solo productos relevantes de acuerdo con su dirección", + "signUpByCode": "Entrar con invitación", + "signUpByInvite": "Inicie sesión con el código de invitación", + "signinByInvite": "Inicie sesión con el código de invitación", + "logoMotto":"Entrega de Comida y Comida para Llevar", + "@logoMotto": { + "details": "Entrega de Comida y Comida para Llevar" + }, + "inviteCode": "Código de invitación", + "inviteCodePlaceholder": "Código de invitación", + "detectingLocation": "Estamos intentando detectar tu dirección actual...", + "productsViewTitle":"Productos", + "productsbuyButtonpre":"Comprar por ", + "productsNotAvailable": "Producto no disponible", + "min": "min", + "delivery": "Delivery", + "takeAway": "Para retirar", + "readyFor": "Listo para", + "details": "Detalles", + "includes": "Incluye", + "buyFor": "Comprar por ", + "help": "Ayuda", + "lastPurchases":"Ultimas Compras", + "nothingOrdered": "No existen pedidos todavía", + "toProducts": "A Prodcutos", + "aboutus": "Acerca de", + "termsOfUse": "Términos de uso", + "privacyView": "Privacidad", + "languageView":"Seleccionar Idioma", + "viewMore":"Ver productos de pedido", + "noProductsOrMerchants": "No se encontraron productos ni comerciantes... ", + "searchPlaceHolder": "Nombre del producto o restaurante", + "open": "Abierta", + "closed": "Cerca", + "buyPopUpOrderPaid": "El pedido cuesta $", + "buyPopUpCancelstatuses":"¡El pedido se canceló mientras se preparaba el almacén!", + "buyPopUpCancelstatusesDelivery":"¡El pedido fue cancelado durante la entrega!", + "viewOrderProducts": "Ver pedidos de productos", + "viewOrderProductsStatuses": "¡Estamos preparando el pedido!", + "youWillGetItInMinutes": "Lo obtendrás en %t minutos.", + "prepareYourWallet": "Prepare su billetera (%s en efectivo).", + "carrierOnTheWay": "Transportista en camino!", + "checkYourDoor": "¡Revisa tu puerta!", + "youWillGetItInSeconds": "Recibirás el pedido en segundos.", + "orderCompleted": "¡Pedido completado!", + "thanksForUsingEver": "Gracias por usar Ever", + "weRePreparingTheOrder": "¡Estamos preparando el pedido!", + "we": "Nosotros", + "carrier": "Transportador", + "you": "Tu", + "deliveryWentWrong": "¡La entrega salió mal!", + "processingWentWrong": "¡El procesamiento salió mal!", + "pleaseTryAgain": "Inténtalo de nuevo.", + "callForDetails": "Llame para más detalles", + "elapsedTime":"Tiempo transcurrido", + "undo": "deshacer", + "payWithCard": "Pagar con Tarjeta", + "payX": "Paga {{cantidad}}", + "payWith": "Pagar con", + "paid": "Pagado", + "bonAppetit": "Buen provecho!", + "cancel": "cancelar", + "gotIt": "Entendido!", + "imHere": "Estoy aquí", + "showQrCode": "Mostrar código QR", + "showProducts": "Mostrar productos", + "areYouSure": "Estas seguro", + "undoWillReturnMoney":"Deshacer te devolverá el dinero, pero perderás el pedido.", + "sideMenuTitle": "Ever", + "sideMenuProducts": "Productos", + "callUs": "Llámenos", + "orderHistory": "Historial de pedidos", + "store": "Tienda", + "callWaiter": "Llamar al camarero", + "about": "Acerca de", + "settings": "Ajustes", + "information": "Información", + "faq": "Ayudar", + "aboutUs": "Sobre nosotros", + "privacy": "Intimidad", + "weApologise": "Nosotros nos disculpamos", + "callUnsuccessfull": "¡La llamada no tuvo éxito!", + "legal": "LEGAL", + "minutes": "Minutos", + "seconds": "Segundos", + "noConnectionToEver": "Sin conexión con Ever", + "@noConnectionToEver": + { + "description": + "Asegúrate de estar conectado a Internet y prueba la aplicación Ever más tarde.." + }, + "merchantsViewCloseToYou": "Comerciantes cerca de ti", + "merchantsViewname": "Nombre del comerciante", + "merchantsViewwithName": "Comerciantes con nombre", + "noServerView":"Se pierde la conexión del servidor", + "or": "O", + "orLowercase": "o", + "yes": "sí", + "no": "No", + "ok": "Ok", + "city": "ciudad", + "street": "calle", + "house": "casa", + "appartement": "Departamento", + "back": "atrás", + "more": "más", + "to": "A", + "storeInfo": "Información de la tienda", + "map": "mapa", + "orderInfo": "información del pedido", + "close": "cerrar", + "scan": "escanear", + "merchants": "comerciantes", + "notFoundNOT_FOUND": "Extraviado!", + "inStore": "En el almacén", + "exitStore": "Salir de la tienda", + "failed": "Fracasado", + "canceled": "Cancelado", + "inDelivery": "En la entrega", + "pending": "pendiente", + "completed": "terminado", + "browse": "Vistazo", + "english": "Inglés", + "hebrew": "Hebreo", + "russian": "Ruso", + "bulgarian": "Búlgaro", + "spanish": "Español", + "select": "Seleccione", + "locationNotes": "Notas de ubicación", + "enterNotesHere": "Introduzca notas aquí" +} \ No newline at end of file diff --git a/packages/shop-mobile-flutter/lib/l10n/app_fr.arb b/packages/shop-mobile-flutter/lib/l10n/app_fr.arb new file mode 100644 index 0000000..0cf1d61 --- /dev/null +++ b/packages/shop-mobile-flutter/lib/l10n/app_fr.arb @@ -0,0 +1,153 @@ +{ + "language": "fr", + "@language": { + "id": "fr-FR", + "name": "Francais", + "description": "Francais" + }, + "currentDirection": "ltr", + "sidebarSide": "gauche", + "welcomeTo": "Bienvenu chez ", + "ever": "Ever", + "infoMissing": "Informations manquants", + "yourInviteCode": "Votre code d'invitation", + "invitedTextTitle":"Merci de votre enregistrement!", + "invitedTextDetails": "Nous vous enverons une notification avec un code d'invitation dès que nous lançons à:", + "notInvitedByCodeTitle":"Code incorrect!", + "notInvitedByCodeDetails": "Rassurez vous que vous etes bel et bien à l'endroit où a été reçu votre code.", + "cantAccessLocation": "Nous n'arrivons pas à reperer votre adresse.", + "cantAccessLocationGetInByAdress": "Nous n'arrivons pas à reperer votre adresse, essayer de vous connecter en completant votre adresse.", + "getInByAddress": "S'enregistrer avec votre adresse", + "getInside": "Allons y!", + "yourAddress": "Nous allons recupérer votre adresse", + "launchNotification": "Nous vous montrons que des produits pertinents autour de votre adresse", + "signUpByCode": "Se connecter par code", + "signUpByInvite": "S'enregistrer par invitation", + "signinByInvite": "Se connecter avec votre code d'invitation", + "logoMotto":"Food Delivery & Takeout", + "@logoMotto": { + "details": "Food Delivery & Takeout" + }, + "inviteCode": "Code d'invitation", + "inviteCodePlaceholder": "Entrez votre code d'invitation", + "detectingLocation": "Veuillez patienter le temps que nous reperions votre adresse actuel...", + "productsViewTitle":"Produits", + "productsbuyButtonpre":"A acheter ", + "productsNotAvailable": "Produits non disponibles", + "min": "min", + "delivery": "Livraison", + "takeAway": "A emporter", + "readyFor": "Prêt pour", + "details": "Details", + "includes": "Inclus", + "buyFor": "Acheter pour", + "help": "Aide", + "lastPurchases":"Dernières achats", + "nothingOrdered": "Aucun achat effectué", + "toProducts": "Au produits", + "aboutus": "A propos de nous", + "termsOfUse": "Conditions d'utilisation", + "privacyView": "Politique de confidentialité", + "languageView":"Choisi ta langue", + "viewMore":"Voir Plus", + "noProductsOrMerchants": "Aucun produit trouvé, ni un vendeur ... ", + "searchPlaceHolder": "Produit ou nom du restaurant", + "open": "Ouvert", + "closed": "Fermé", + "buyPopUpOrderPaid": "La commande a été payé $", + "buyPopUpCancelstatuses":"La commande a été annulée pendant la préparation de l'entrepôt!", + "buyPopUpCancelstatusesDelivery":"La commande a été annulée pendant la livraison!", + "viewOrderProducts": "Afficher les produits commandés", + "viewOrderProductsStatuses": "Nous préparons votre commande!", + "youWillGetItInMinutes": "Votre commande sera prête dans %t minutes.", + "prepareYourWallet": "Préparez votre portefeuille (%s en cash).", + "carrierOnTheWay": "Le transporteur est en route!", + "checkYourDoor": "Faites un tour à votre porte!", + "youWillGetItInSeconds": "Vous recevrez votre commande dans quelques secondes.", + "orderCompleted": "Commande livrée!", + "thanksForUsingEver": "Merci d'avoir utilisé Ever", + "weRePreparingTheOrder": "Nous préparons votre commande!", + "we": "Nous", + "carrier": "Transporteur", + "you": "Vous", + "deliveryWentWrong": "La livraison n'a pas pu être effectuée!", + "processingWentWrong": "Le processus s'est interrompu!", + "pleaseTryAgain": "Reessayer encore.", + "callForDetails": "Appeler pour avoir des détails", + "elapsedTime":"Temps écoulés", + "undo": "Annuler", + "payWithCard": "Payer avec votre carte", + "payX": "Payer {{montant}}", + "payWith": "Payer avec", + "paid": "Payé", + "bonAppetit": "Bon appetit!", + "cancel": "Annuler", + "gotIt": "Fait!", + "imHere": "Je suis là", + "showQrCode": "Voir le QR Code", + "showProducts": "Voir les produits", + "areYouSure": "Etes vous sure?", + "undoWillReturnMoney":"Annuler la commande vous retourne le montat payé tout en perdant la commande", + "sideMenuTitle": "Ever", + "sideMenuProducts": "Produits", + "callUs": "Nous Appeler", + "orderHistory": "Historique des commandes", + "store": "Magasin", + "callWaiter": "Appeler un serveur", + "about": "A propos de", + "settings": "Paramètres", + "information": "Information", + "faq": "FAQ", + "aboutUs": "A propos de nous", + "privacy": "Politique de confidentialité", + "weApologise": "Nous nous excusons pour la gêne occasionnée", + "callUnsuccessfull": "Appel non effectué", + "legal": "LEGAL", + "minutes": "Minutes", + "seconds": "Secondes", + "noConnectionToEver": "Nous n'avons pas réussi à vous connecter à Ever. Veuillez réessayer plus tard.", + "@noConnectionToEver": + { + "description": + "Rassurez vous d'être connecté à internet et reessayer de lancer Ever App." + }, + "merchantsViewCloseToYou": "Vendeur(s) près de chez vous", + "merchantsViewname": "Nom du vendeur", + "merchantsViewwithName": "Vendeur(s) avec le nom", + "noServerView":"Aucun serveur trouvé", + "or": "Ou", + "orLowercase": "ou", + "yes": "Oui", + "no": "Non", + "ok": "Ok", + "city": "Ville/Cité", + "street": "Avenue, Rue", + "house": "Maison", + "appartement": "Appartement", + "back": "Retour", + "more": "Plus", + "to": "A", + "storeInfo": "Informations sur le magasin", + "map": "Carte", + "orderInfo": "Informations sur la commande", + "close": "Fermer", + "scan": "Scanner", + "merchants": "Vendeurs", + "notFoundNOT_FOUND": "non trouvé", + "inStore": "En magasin", + "exitStore": "Sortir du magasin", + "failed": "Echec", + "canceled": "Annulé", + "inDelivery": "En livraison", + "pending": "En attente", + "completed": "Terminé", + "browse": "Parcourir", + "english": "Anglais", + "hebrew": "Hébreu", + "russian": "Russe", + "bulgarian": "Bulgare", + "spanish": "Espagnol", + "select": "Sélectionner", + "locationNotes": "Notes sur la localisation", + "enterNotesHere": "Entrez ici vos notes" +} \ No newline at end of file diff --git a/packages/shop-mobile-flutter/lib/l10n/app_he.arb b/packages/shop-mobile-flutter/lib/l10n/app_he.arb new file mode 100644 index 0000000..2f095e8 --- /dev/null +++ b/packages/shop-mobile-flutter/lib/l10n/app_he.arb @@ -0,0 +1,153 @@ +{ + "language": "he", + "@language": { + "id": "he-IL", + "name": "Hebrew", + "description": "Hebrew" + }, + "currentDirection": "ltr", + "sidebarSide": "left", + "welcomeTo": "ברוך הבא ל", + "ever": "אֵיִ פַּעַם", + "infoMissing": "חלק מהמידע חסר!", + "yourInviteCode": "קוד ההזמנה שלך", + "invitedTextTitle":"תודה שנרשמת!", + "invitedTextDetails": "אנו נשלח לך הודעה עם קוד הזמנה כאשר נשיק ב:", + "notInvitedByCodeTitle":"קוד שגוי!", + "notInvitedByCodeDetails": "אנא ודא שאתה נמצא במקום שהגיע עם הקוד שלך.", + "cantAccessLocation": "לא ניתן לגשת למיקום שלך.", + "cantAccessLocationGetInByAdress": "לא מצליח לגשת למיקום שלך, אנא נסה להיכנס לפי כתובת.", + "getInByAddress": "הרשמה לפי כתובת", + "getInside": "היכנס!", + "yourAddress": "תן לנו את הכתובת שלך", + "launchNotification": "אנו מבטיחים להציג רק מוצרים רלוונטיים לפי הכתובת שלך", + "signUpByCode": "הרשמה באמצעות קוד", + "signUpByInvite": "הירשם בהזמנה", + "signinByInvite": "היכנס עם קוד הזמנה", + "logoMotto":"משלוח מזון והשלמת אוכל", + "@logoMotto": { + "details": "משלוח מזון והשלמת אוכל" + }, + "inviteCode": "קוד הזמנה", + "inviteCodePlaceholder": "הזן את קוד ההזמנה שלך", + "detectingLocation": "אנא המתן, אנו מנסים לזהות את הכתובת הנוכחית שלך...", + "productsViewTitle":"מוצרים", + "productsbuyButtonpre":"לקנות ל", + "productsNotAvailable": "המוצר אינו זמין", + "min": "דקה", + "delivery": "מְסִירָה", + "takeAway": "להוציא", + "readyFor": "מוכן ל", + "details": "פרטים", + "includes": "כולל", + "buyFor": "לקנות ל", + "help": "עֶזרָה", + "lastPurchases":"רכישות אחרונות", + "nothingOrdered": "עדיין אין הזמנות", + "toProducts": "למוצרים", + "aboutus": "עלינו", + "termsOfUse": "תנאי שימוש", + "privacyView": "פְּרָטִיוּת", + "languageView":"בחר שפה", + "viewMore":"ראה עוד", + "noProductsOrMerchants": "לא נמצאו מוצרים או סוחרים... ", + "searchPlaceHolder": "שם המוצר או המסעדה", + "open": "לִפְתוֹחַ", + "closed": "סָגוּר", + "buyPopUpOrderPaid": "ההזמנה משולמת $", + "buyPopUpCancelstatuses":"ההזמנה בוטלה בזמן הכנת המחסן!", + "buyPopUpCancelstatusesDelivery":"ההזמנה בוטלה בזמן המשלוח!", + "viewOrderProducts": "צפה במוצרי הזמנה", + "viewOrderProductsStatuses": "אנחנו מכינים את ההזמנה!", + "youWillGetItInMinutes": "תקבל אותו תוך %t דקות.", + "prepareYourWallet": "הכן את הארנק שלך (%s במזומן).", + "carrierOnTheWay": "הכן את הארנק שלך (%s במזומן).", + "checkYourDoor": "בדוק את הדלת שלך!", + "youWillGetItInSeconds": "אתה תקבל את ההזמנה תוך שניות.", + "orderCompleted": "הזמנה הושלמה!", + "thanksForUsingEver": "תודה על השימוש ב- Ever", + "weRePreparingTheOrder": "אנחנו מכינים את ההזמנה!", + "we": "אָנוּ", + "carrier": "מוֹבִיל", + "you": "אתה", + "deliveryWentWrong": "המשלוח השתבש!", + "processingWentWrong": "העיבוד השתבש", + "pleaseTryAgain": "בבקשה נסה שוב.", + "callForDetails": "התקשרו לפרטים", + "elapsedTime":"זמן שחלף", + "undo": "לבטל", + "payWithCard": "שלם עם כרטיס", + "payX": "שלם {{ סכום }}", + "payWith": "לשלם עם", + "paid": "שולם", + "bonAppetit": "בתאבון!", + "cancel": "לְבַטֵל", + "gotIt": "הבנת!", + "imHere": "אני כאן", + "showQrCode": "הצג קוד QR", + "showProducts": "הצג מוצרים", + "areYouSure": "האם אתה בטוח?", + "undoWillReturnMoney":"ביטול יחזיר לך את הכסף, אבל תאבד את ההזמנה.", + "sideMenuTitle": "Ever", + "sideMenuProducts": "מוצרים", + "callUs": "להיתקשר אליךs", + "orderHistory": "היסטוריית הזמנות", + "store": "חנות", + "callWaiter": "התקשר למלצר", + "about": "על אודות", + "settings": "הגדרות", + "information": "מֵידָע", + "faq": "עֶזרָה", + "aboutUs": "עלינו", + "privacy": "פְּרָטִיוּת", + "weApologise": "אנו מתנצלים!", + "callUnsuccessfull": "השיחה לא הצליחה!", + "legal": "משפטי", + "minutes": "דקות", + "seconds": "שניות", + "noConnectionToEver": "אין קשר ל-Ever", + "@noConnectionToEver": + { + "description": + "אנא ודא שאתה מחובר לאינטרנט ונסה את אפליקציית Ever מאוחר יותר" + }, + "merchantsViewCloseToYou": "סוחרים קרובים אליך", + "merchantsViewname": "שם סוחר", + "merchantsViewwithName": "סוחרים עם שם", + "noServerView":"חיבור השרת אבד", + "or": "אוֹ", + "orLowercase": "אוֹ", + "yes": "כן", + "no": "לא", + "ok": "בסדר", + "city": "עִיר", + "street": "רְחוֹב", + "house": "בַּיִת", + "appartement": "דִירָה", + "back": "חזור", + "more": "יותר", + "to": "ל", + "storeInfo": "מידע על החנות", + "map": "מַפָּה", + "orderInfo": "פרטי הזמנה", + "close": "סגור", + "scan": "לִסְרוֹק", + "merchants": "סוחרים", + "notFoundNOT_FOUND": "לא נמצא!", + "inStore": "בחנות", + "exitStore": "צא מחנות", + "failed": "נִכשָׁל", + "canceled": "מבוטל", + "inDelivery": "במשלוח", + "pending": "ממתין ל", + "completed": "הושלם", + "browse": "לְדַפדֵף", + "english": "אנגלית", + "hebrew": "עִברִית", + "russian": "רוּסִי", + "bulgarian": "בולגרית", + "spanish": "ספרדית", + "select": "בחר", + "locationNotes": "הערות מיקום", + "enterNotesHere": "הזן הערות כאן" +} \ No newline at end of file diff --git a/packages/shop-mobile-flutter/lib/l10n/app_ru.arb b/packages/shop-mobile-flutter/lib/l10n/app_ru.arb new file mode 100644 index 0000000..4484ec2 --- /dev/null +++ b/packages/shop-mobile-flutter/lib/l10n/app_ru.arb @@ -0,0 +1,153 @@ +{ + "language": "ru", + "@language": { + "id": "ru-RU", + "name": "русский", + "description": "русский" + }, + "currentDirection": "ltr", + "sidebarSide": "левый", + "welcomeTo": "Добро пожаловать в", + "ever": "Всегда", + "infoMissing": "Часть информации отсутствует!", + "yourInviteCode": "Ваш пригласительный код", + "invitedTextTitle":"Спасибо за регистрацию!", + "invitedTextDetails": "Мы отправим вам уведомление с кодом приглашения, когда мы запустимся по адресу:", + "notInvitedByCodeTitle":"Неправильный код!", + "notInvitedByCodeDetails": "Пожалуйста, убедитесь, что вы находитесь в том месте, которое пришло с вашим кодом.", + "cantAccessLocation": "Не удается получить доступ к вашему местоположению.", + "cantAccessLocationGetInByAdress": "Не удается получить доступ к вашему местоположению, попробуйте войти по адресу.", + "getInByAddress": "Зарегистрироваться по адресу", + "getInside": "Get inside!", + "yourAddress": "Сообщите нам свой адрес", + "launchNotification": "Обещаем показывать только релевантные товары по вашему адресу", + "signUpByCode": "Зарегистрироваться по коду", + "signUpByInvite": "зарегистрироваться по инвайту", + "signinByInvite": "Войти с помощью инвайт-кода", + "logoMotto":"Доставка еды и на вынос", + "@logoMotto": { + "details": "Доставка еды и на вынос" + }, + "inviteCode": "Код приглашения", + "inviteCodePlaceholder": "Введите код приглашения", + "detectingLocation": "Подождите, мы пытаемся определить ваш текущий адрес...", + "productsViewTitle":"Продукты", + "productsbuyButtonpre":"Купить для", + "productsNotAvailable": "Продукт недоступен", + "min": "министр", + "delivery": "Доставка", + "takeAway": "еда на вынос", + "readyFor": "готов", + "details": "Подробности", + "includes": "Включает", + "buyFor": "Купить для", + "help": "Помощь", + "lastPurchases":"Последние покупки", + "nothingOrdered": "Заказов пока нет", + "toProducts": "К продуктам", + "aboutus": "О нас", + "termsOfUse": "Условия эксплуатации", + "privacyView": "конфиденциальность и безопасность", + "languageView":"Select Language", + "viewMore":"View More", + "noProductsOrMerchants": "Товары или продавцы не найдены...", + "searchPlaceHolder": "Название продукта или ресторана", + "open": "Открытым", + "closed": "Закрыто", + "buyPopUpOrderPaid": "Заказ оплачен $", + "buyPopUpCancelstatuses":"Заказ был отменен во время подготовки склада!", + "buyPopUpCancelstatusesDelivery":"Заказ был отменен во время доставки!", + "viewOrderProducts": "Посмотреть продукты для заказа", + "viewOrderProductsStatuses": "Готовим заказ!", + "youWillGetItInMinutes": "Вы получите его через %t минут.", + "prepareYourWallet": "Подготовьте свой бумажник (%s наличными).", + "carrierOnTheWay": "Перевозчик в пути!", + "checkYourDoor": "Проверьте свою дверь!", + "youWillGetItInSeconds": "Вы получите заказ за считанные секунды.", + "orderCompleted": "Заказ выполнен!", + "thanksForUsingEver": "Спасибо за использование Ever", + "weRePreparingTheOrder": "Готовим заказ!", + "we": "нами", + "carrier": "Перевозчик", + "you": "Ты", + "deliveryWentWrong": "Доставка пошла не так!", + "processingWentWrong": "Обработка пошла не так!", + "pleaseTryAgain": "пожалуйста, попробуйте снова", + "callForDetails": "Звоните, чтобы узнать подробности", + "elapsedTime":"Пройденное время", + "undo": "Отменить", + "payWithCard": "Оплатить картой", + "payX": "Оплатить {{сумма}}", + "payWith": "Платить чем-либо", + "paid": "Оплаченный", + "bonAppetit": "Приятного аппетита!", + "cancel": "Отмена", + "gotIt": "Понятно!", + "imHere": "я здесь", + "showQrCode": "Показать QR-код", + "showProducts": "Показать продукты", + "areYouSure": "Уверены ли вы?", + "undoWillReturnMoney":"Отмена вернет вам деньги, но вы потеряете заказ.", + "sideMenuTitle": "Ever", + "sideMenuProducts": "Продукты", + "callUs": "Позвоните нам", + "orderHistory": "История заказов", + "store": "Магазин", + "callWaiter": "Вызовите официанта", + "about": "о", + "settings": "Настройки", + "information": "Информация", + "faq": "Помощь", + "aboutUs": "О нас", + "privacy": "Конфиденциальность", + "weApologise": "Приносим свои извинения!", + "callUnsuccessfull": "Звонок не удался!", + "legal": "ЮРИДИЧЕСКИЙ", + "minutes": "Минуты", + "seconds": "Секунды", + "noConnectionToEver": "Нет связи с Ever", + "@noConnectionToEver": + { + "description": + "Пожалуйста, убедитесь, что вы подключены к Интернету и попробуйте Ever App Later." + }, + "merchantsViewCloseToYou": "Торговцы рядом с вами", + "merchantsViewname": "Имя продавца", + "merchantsViewwithName": "Торговцы с именем", + "noServerView":"Соединение с сервером потеряно", + "or": "Или", + "orLowercase": "Или", + "yes": "да", + "no": "Нет", + "ok": "Хорошо", + "city": "Город", + "street": "улица", + "house": "дом", + "appartement": "Квартира", + "back": "Назад", + "more": "более", + "to": "Во", + "storeInfo": "Информация о магазине", + "map": "карта", + "orderInfo": "Информация о заказе", + "close": "закрываться", + "scan": "сканирование", + "merchants": "Торговцы", + "notFoundNOT_FOUND": "не обнаружена!", + "inStore": "В магазине", + "exitStore": "Выйти из магазина", + "failed": "не удалось", + "canceled": "аннулированный", + "inDelivery": "В доставке", + "pending": "Pending", + "completed": "Завершенный", + "browse": "Просматривать", + "english": "английский", + "hebrew": "иврит", + "russian": "русский", + "bulgarian": "болгарский", + "spanish": "испанский язык", + "select": "Выбирать", + "locationNotes": "Примечания к местоположению", + "enterNotesHere": "Введите заметки здесь" +} \ No newline at end of file diff --git a/packages/shop-mobile-flutter/lib/main.dart b/packages/shop-mobile-flutter/lib/main.dart new file mode 100644 index 0000000..0994c01 --- /dev/null +++ b/packages/shop-mobile-flutter/lib/main.dart @@ -0,0 +1,7 @@ +import 'package:flutter/material.dart'; +import 'package:shop_flutter_mobile/app.dart'; + +Future main() async { + WidgetsFlutterBinding.ensureInitialized(); + runApp(const MyApp()); +} diff --git a/packages/shop-mobile-flutter/lib/screens/app_widgets/appbar.dart b/packages/shop-mobile-flutter/lib/screens/app_widgets/appbar.dart new file mode 100644 index 0000000..4570330 --- /dev/null +++ b/packages/shop-mobile-flutter/lib/screens/app_widgets/appbar.dart @@ -0,0 +1,23 @@ +import 'package:flutter/material.dart'; + +Widget myAppBar(String title) { + return AppBar( + backgroundColor: Color.fromRGBO(156, 204, 101, 1), + //background color of Appbar to green + title: Text(title), + actions: [ + IconButton( + icon: Icon(Icons.search), + onPressed: () { + //action for search icon button + }, + ), + IconButton( + icon: Icon(Icons.person), + onPressed: () { + //action for user icon button + }, + ) + ], + ); +} diff --git a/packages/shop-mobile-flutter/lib/screens/app_widgets/screener_loader.dart b/packages/shop-mobile-flutter/lib/screens/app_widgets/screener_loader.dart new file mode 100644 index 0000000..7e223a2 --- /dev/null +++ b/packages/shop-mobile-flutter/lib/screens/app_widgets/screener_loader.dart @@ -0,0 +1,110 @@ +import 'package:flutter/material.dart'; +import 'package:screen_loader/screen_loader.dart'; + +void main() { + configScreenLoader( + loader: const AlertDialog( + title: Text('Gobal Loader..'), + ), + bgBlur: 20.0, + ); + runApp(const MyApp()); +} + +class MyApp extends StatelessWidget { + const MyApp({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return MaterialApp( + debugShowCheckedModeBanner: false, + title: 'Screen Loader', + theme: ThemeData( + primarySwatch: Colors.blue, + ), + home: const Screen(), + ); + } +} + +class Screen extends StatefulWidget { + const Screen({Key? key}) : super(key: key); + + @override + _ScreenState createState() => _ScreenState(); +} + +/// A Stateful screen +class _ScreenState extends State with ScreenLoader { + @override + loader() { + return const AlertDialog( + title: Text('Wait.. Loading data..'), + ); + } + + @override + loadingBgBlur() => 10.0; + + Widget _buildBody() { + return Center( + child: Icon( + Icons.home, + size: MediaQuery.of(context).size.width, + ), + ); + } + + @override + Widget build(BuildContext context) { + return loadableWidget( + child: Scaffold( + appBar: AppBar( + title:const Text('ScreenLoader Example'), + ), + body: _buildBody(), + floatingActionButton: FloatingActionButton( + onPressed: () async { + await performFuture(NetworkService.getData); + }, + child: const Icon(Icons.refresh), + ), + ), + ); + } +} + +/// A Stateless screen +// ignore: must_be_immutable +class BasicScreen extends StatelessWidget with ScreenLoader { + BasicScreen({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return loadableWidget( + child: Scaffold( + appBar: AppBar( + title:const Text('Basic ScreenLoader Example'), + ), + body: Center( + child: Icon( + Icons.home, + size: MediaQuery.of(context).size.width, + ), + ), + floatingActionButton: FloatingActionButton( + onPressed: () async { + await performFuture(NetworkService.getData); + }, + child: const Icon(Icons.refresh), + ), + ), + ); + } +} + +class NetworkService { + static Future getData() async { + return await Future.delayed(const Duration(seconds: 2)); + } +} \ No newline at end of file diff --git a/packages/shop-mobile-flutter/lib/screens/authentification/authentication.dart b/packages/shop-mobile-flutter/lib/screens/authentification/authentication.dart new file mode 100644 index 0000000..63e0cf2 --- /dev/null +++ b/packages/shop-mobile-flutter/lib/screens/authentification/authentication.dart @@ -0,0 +1,2 @@ +export 'views/views.dart'; +export 'widgets/widgets.dart'; diff --git a/packages/shop-mobile-flutter/lib/screens/authentification/views/login.dart b/packages/shop-mobile-flutter/lib/screens/authentification/views/login.dart new file mode 100644 index 0000000..af0905e --- /dev/null +++ b/packages/shop-mobile-flutter/lib/screens/authentification/views/login.dart @@ -0,0 +1,276 @@ +import 'package:flutter/material.dart'; +import 'package:font_awesome_flutter/font_awesome_flutter.dart'; +import 'package:shop_flutter_mobile/widgets/widgets.dart'; +import 'package:shop_flutter_mobile/constants/colors.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'signup_address1.dart'; +import 'signup_address2.dart'; +// import 'package:shop_flutter_mobile/screens/authentification/signup_thanks.dart'; +// import 'package:shop_flutter_mobile/screens/other/nav.dart'; + +class LoginScreen extends StatelessWidget { + const LoginScreen({Key? key}) : super(key: key); + final customColor = const AppColors(); + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: customColor.dRed, + body: SafeArea( + child: GestureDetector( + onTap: () => FocusScope.of(context).unfocus(), + child: SingleChildScrollView( + child: Container( + margin: const EdgeInsets.symmetric(vertical: 60, horizontal: 30), + child: Column( + children: [ + DelayedAnimation( + // this one controls the logo animation + delay: 100, + child: Container( + alignment: Alignment.center, + // height: 50, + margin: const EdgeInsets.only(top: 50, bottom: 30), + child: Column( + children: [ + Text( + AppLocalizations.of(context)!.ever, + style: TextStyle( + color: customColor.whiteColor, + fontFamily: 'PlutoHeavyItalic', + fontSize: 80, + ), + ), + Text( + AppLocalizations.of(context)!.logoMotto, + style: TextStyle( + color: customColor.greyColor, + fontSize: 14, + fontWeight: FontWeight.bold, + ), + ), + ], + ), + ), + ), + DelayedAnimation( + // this one controls the button animation + delay: 150, + child: Container( + width: double.infinity, + height: 40, + margin: const EdgeInsets.only(top: 50), + child: ElevatedButton( + onPressed: () { + Navigator.of(context).push(MaterialPageRoute( + builder: (context) => + const SignupAdressScreen())); + }, + style: ButtonStyle( + shape: + MaterialStateProperty.all( + RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12.0), + // side: const BorderSide(color: white), + ), + ), + backgroundColor: + MaterialStateProperty.all(customColor.everSignin), + foregroundColor: + MaterialStateProperty.all(Colors.white), + textStyle: MaterialStateProperty.all( + const TextStyle( + color: Colors.white, + fontSize: 16, + ), + ), + ), + child: + Text(AppLocalizations.of(context)!.getInByAddress), + ), + ), + ), + DelayedAnimation( + // this one controls the button animation + delay: 200, + child: Container( + width: double.infinity, + height: 40, + margin: const EdgeInsets.only(top: 10, bottom: 10), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Expanded( + child: ElevatedButton.icon( + onPressed: () { + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => + const SignupAdressScreen(), + ), + ); + }, + style: ButtonStyle( + shape: MaterialStateProperty.all< + RoundedRectangleBorder>( + RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12.0), + // side: const BorderSide(color: white), + ), + ), + backgroundColor: MaterialStateProperty.all( + customColor.facebookColor), + foregroundColor: + MaterialStateProperty.all(Colors.white), + textStyle: MaterialStateProperty.all( + const TextStyle( + color: Colors.white, + fontSize: 14, + ), + ), + ), + label: const Text(""), + icon: const FaIcon( + FontAwesomeIcons.facebookSquare, + size: 24.0, + color: Colors.white, + ), + ), + ), + const SizedBox( + //Use of SizedBox + width: 8, + ), + Expanded( + child: ElevatedButton.icon( + onPressed: () { + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => + const SignupAdress2Screen(), + ), + ); + }, + style: ButtonStyle( + shape: MaterialStateProperty.all< + RoundedRectangleBorder>( + RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12.0), + // side: const BorderSide(color: white), + ), + ), + backgroundColor: MaterialStateProperty.all( + customColor.googleColor), + foregroundColor: + MaterialStateProperty.all(Colors.white), + textStyle: MaterialStateProperty.all( + const TextStyle( + color: Colors.white, + fontSize: 14, + ), + ), + ), + label: const Text(""), + icon: const FaIcon( + FontAwesomeIcons.google, + size: 22.0, + color: Colors.white, + ), + ), + ), + ], + ), + ), + ), + DelayedAnimation( + // this one controls the button animation + delay: 250, + child: Container( + width: double.infinity, + height: 50, + margin: const EdgeInsets.only(top: 3, bottom: 3), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Flexible( + flex: 10, // 50% + child: Text( + AppLocalizations.of(context)!.or, + style: TextStyle( + color: customColor.greyColor, + fontSize: 14, + ), + ), + ), + const Flexible( + flex: 10, // 50% + child: Text(" "), + ), + Flexible( + flex: 80, // 50% + child: Text( + AppLocalizations.of(context)!.signUpByInvite, + style: TextStyle( + color: customColor.whiteColor, + fontSize: 14, + fontWeight: FontWeight.normal, + ), + ), + ), + ]), + ), + ), + DelayedAnimation( + // this one controls the button animation + delay: 300, + child: Container( + padding: const EdgeInsets.all(10), + child: Theme( + data: ThemeData( + primaryColor: customColor.whiteColor, + primaryColorDark: customColor.dRed, + ), + child: TextFormField( + cursorColor: Colors.black, + keyboardType: TextInputType.text, + textAlignVertical: TextAlignVertical.center, + textAlign: TextAlign.center, + style: const TextStyle( + color: Colors.white, + fontSize: 16, + ), + decoration: const InputDecoration( + //isCollapsed: true, + border: OutlineInputBorder(), + contentPadding: EdgeInsets.fromLTRB(10, 0, 0, 10), + enabledBorder: OutlineInputBorder( + borderSide: BorderSide( + color: Colors.grey, + ), + borderRadius: + BorderRadius.all(Radius.circular(6)), + ), + hintText: "Invite Code", + hintStyle: TextStyle( + color: Colors.grey, + fontSize: 16, + ), + hintMaxLines: 1, + // labelText: "Invite Code", + // labelStyle: TextStyle(color: Colors.grey, fontSize: 16), + ), + ), + ), + ), + ), + ], + ), + ), + ), + ), + ), + ); + } +} diff --git a/packages/shop-mobile-flutter/lib/screens/authentification/views/signup_address1.dart b/packages/shop-mobile-flutter/lib/screens/authentification/views/signup_address1.dart new file mode 100644 index 0000000..4b10343 --- /dev/null +++ b/packages/shop-mobile-flutter/lib/screens/authentification/views/signup_address1.dart @@ -0,0 +1,138 @@ +// ignore_for_file: dead_code +import 'package:flutter/material.dart'; +import 'package:shop_flutter_mobile/constants/colors.dart'; +import 'package:shop_flutter_mobile/widgets/widgets.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'signup_thanks.dart'; + +class SignupAdressScreen extends StatefulWidget { + const SignupAdressScreen({Key? key}) : super(key: key); + @override + _SignupAdressScreenState createState() => _SignupAdressScreenState(); +} + +class _SignupAdressScreenState extends State { + final customColor = const AppColors(); + bool isStretched = false; + bool isDone = true; + + @override + Widget build(BuildContext context) { + return Scaffold( + //body: getBody, + backgroundColor: customColor.dRed, + body: SingleChildScrollView( + child: Container( + margin: const EdgeInsets.symmetric( + vertical: 60, + horizontal: 30, + ), + child: Column( + children: [ + const SizedBox(height: 120), + DelayedAnimation( + // this one controls the logo animation + delay: 100, + child: Container( + alignment: Alignment.center, + // height: 100, + margin: const EdgeInsets.only(top: 10), + child: Column( + children: [ + Text( + AppLocalizations.of(context)!.yourAddress, + style: TextStyle( + color: customColor.whiteColor, + fontSize: 25, + ), + textAlign: TextAlign.center, + ), + ], + )), + ), + DelayedAnimation( + // this one controls the logo animation + delay: 200, + child: Container( + alignment: Alignment.center, + // height: 50, + margin: const EdgeInsets.only(top: 10, bottom: 10), + child: Column( + children: [ + Text( + AppLocalizations.of(context)!.launchNotification, + style: TextStyle( + color: customColor.greyColor, + fontSize: 14, + fontWeight: FontWeight.bold, + ), + textAlign: TextAlign.center, + ), + ], + ), + ), + ), + DelayedAnimation( + // this one controls the logo animation + delay: 300, + child: Container( + alignment: Alignment.center, + // height: 50, + margin: const EdgeInsets.only(top: 30, bottom: 50), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const SizedBox(height: 20), + Text(AppLocalizations.of(context)!.detectingLocation, + style: TextStyle( + color: customColor.whiteColor, + fontStyle: FontStyle.normal, + fontSize: 16, + ), + textAlign: TextAlign.center), + ], + ), + ), + ), + DelayedAnimation( + // this one controls the button animation + delay: 100, + child: Container( + width: double.infinity, + height: 50, + margin: const EdgeInsets.only(top: 50), + child: ElevatedButton.icon( + onPressed: () { + Navigator.of(context).push(MaterialPageRoute( + builder: (context) => const SignupThanksScreen())); + }, + style: ButtonStyle( + shape: MaterialStateProperty.all( + RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12.0), + // side: const BorderSide(color: white), + )), + backgroundColor: + MaterialStateProperty.all(customColor.everSignin), + foregroundColor: MaterialStateProperty.all(Colors.white), + textStyle: MaterialStateProperty.all(const TextStyle( + color: Colors.white, + fontSize: 16, + )), + ), + label: Text(AppLocalizations.of(context)!.getInByAddress), + icon: const Icon( + Icons.location_on, + size: 25.0, + color: Colors.white, + ), + ), + ), + ), + ], + ), + ), + ), + ); + } +} diff --git a/packages/shop-mobile-flutter/lib/screens/authentification/views/signup_address2.dart b/packages/shop-mobile-flutter/lib/screens/authentification/views/signup_address2.dart new file mode 100644 index 0000000..b06317d --- /dev/null +++ b/packages/shop-mobile-flutter/lib/screens/authentification/views/signup_address2.dart @@ -0,0 +1,296 @@ +// ignore_for_file: dead_code +import 'package:flutter/material.dart'; +import 'package:shop_flutter_mobile/constants/colors.dart'; +import 'package:shop_flutter_mobile/widgets/widgets.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +// import 'package:shop_flutter_mobile/screens/authentification/signup_thanks.dart'; + +const customColor = AppColors(); + +class SignupAdress2Screen extends StatefulWidget { + const SignupAdress2Screen({Key? key}) : super(key: key); + + @override + _SignupAdress2ScreenState createState() => _SignupAdress2ScreenState(); +} + +class _SignupAdress2ScreenState extends State { + @override + Widget build(BuildContext context) { + return Scaffold( + //body: getBody, + backgroundColor: customColor.dRed, + body: SingleChildScrollView( + child: Container( + margin: const EdgeInsets.symmetric( + vertical: 60, + horizontal: 30, + ), + child: Column( + children: [ + const SizedBox(height: 120), + DelayedAnimation( + // this one controls the logo animation + delay: 100, + child: Container( + alignment: Alignment.center, + // height: 100, + margin: const EdgeInsets.only(top: 10), + child: Column( + children: [ + Text( + AppLocalizations.of(context)!.yourAddress, + style: TextStyle( + color: customColor.whiteColor, + fontSize: 25, + ), + textAlign: TextAlign.center, + ), + ], + )), + ), + DelayedAnimation( + // this one controls the logo animation + delay: 200, + child: Container( + alignment: Alignment.center, + // height: 50, + margin: const EdgeInsets.only(top: 10, bottom: 40), + child: Column( + children: [ + Text( + AppLocalizations.of(context)!.launchNotification, + style: TextStyle( + color: customColor.greyColor, + fontSize: 14, + fontWeight: FontWeight.bold, + ), + textAlign: TextAlign.center, + ), + ], + ), + ), + ), + DelayedAnimation( + delay: 300, + child: Container( + padding: const EdgeInsets.all(10), + child: Theme( + data: ThemeData( + primaryColor: customColor.whiteColor, + primaryColorDark: customColor.dRed, + ), + child: TextFormField( + cursorColor: Colors.black, + keyboardType: TextInputType.text, + textAlignVertical: TextAlignVertical.center, + textAlign: TextAlign.center, + style: const TextStyle( + color: Colors.white, + fontSize: 16, + ), + decoration: const InputDecoration( + //isCollapsed: true, + border: OutlineInputBorder(), + contentPadding: EdgeInsets.all(10), + enabledBorder: OutlineInputBorder( + borderSide: BorderSide( + color: Colors.grey, + ), + borderRadius: BorderRadius.all(Radius.circular(6)), + ), + hintText: "Address 1", + hintStyle: TextStyle( + color: Colors.grey, + fontSize: 16, + ), + labelText: "Address 1", + labelStyle: TextStyle(color: Colors.grey, fontSize: 16), + ), + ), + ), + ), + ), + DelayedAnimation( + delay: 400, + child: Container( + padding: const EdgeInsets.all(10), + child: Theme( + data: ThemeData( + primaryColor: customColor.whiteColor, + primaryColorDark: customColor.dRed, + ), + child: TextFormField( + cursorColor: Colors.black, + keyboardType: TextInputType.text, + textAlignVertical: TextAlignVertical.center, + textAlign: TextAlign.center, + style: const TextStyle( + color: Colors.white, + fontSize: 16, + ), + decoration: const InputDecoration( + //isCollapsed: true, + border: OutlineInputBorder(), + contentPadding: EdgeInsets.all(10), + enabledBorder: OutlineInputBorder( + borderSide: BorderSide( + color: Colors.grey, + ), + borderRadius: BorderRadius.all(Radius.circular(6)), + ), + hintText: "Address 2", + hintStyle: TextStyle( + color: Colors.grey, + fontSize: 16, + ), + labelText: "Address 2", + labelStyle: TextStyle(color: Colors.grey, fontSize: 16), + ), + ), + ), + ), + ), + DelayedAnimation( + // this one controls the button animation + delay: 500, + child: SizedBox( + width: double.infinity, + height: 70, + // margin: const EdgeInsets.only(bottom: 10), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Expanded( + child: Container( + padding: const EdgeInsets.all(10), + child: Theme( + data: ThemeData( + primaryColor: customColor.whiteColor, + primaryColorDark: customColor.dRed, + ), + child: TextFormField( + cursorColor: Colors.black, + keyboardType: TextInputType.text, + textAlignVertical: TextAlignVertical.center, + textAlign: TextAlign.center, + style: const TextStyle( + color: Colors.white, + fontSize: 16, + ), + decoration: const InputDecoration( + //isCollapsed: true, + border: OutlineInputBorder(), + contentPadding: EdgeInsets.all(10), + enabledBorder: OutlineInputBorder( + borderSide: BorderSide( + color: Colors.grey, + ), + borderRadius: + BorderRadius.all(Radius.circular(6)), + ), + hintText: "House", + hintStyle: TextStyle( + color: Colors.grey, + fontSize: 16, + ), + labelText: "House", + labelStyle: + TextStyle(color: Colors.grey, fontSize: 16), + ), + ), + ), + ), + ), + /* const SizedBox( + //Use of SizedBox + width: 2, + ), */ + Expanded( + child: Container( + padding: const EdgeInsets.all(10), + child: Theme( + data: ThemeData( + primaryColor: customColor.whiteColor, + primaryColorDark: customColor.dRed, + ), + child: TextFormField( + cursorColor: Colors.black, + keyboardType: TextInputType.text, + textAlignVertical: TextAlignVertical.center, + textAlign: TextAlign.center, + style: const TextStyle( + color: Colors.white, + fontSize: 16, + ), + decoration: const InputDecoration( + //isCollapsed: true, + border: OutlineInputBorder(), + contentPadding: EdgeInsets.all(10), + enabledBorder: OutlineInputBorder( + borderSide: BorderSide( + color: Colors.grey, + ), + borderRadius: + BorderRadius.all(Radius.circular(6)), + ), + hintText: "Apartment", + hintStyle: TextStyle( + color: Colors.grey, + fontSize: 16, + ), + labelText: "Apartment", + labelStyle: + TextStyle(color: Colors.grey, fontSize: 16), + ), + ), + ), + ), + ), + ], + ), + ), + ), + DelayedAnimation( + // this one controls the button animation + delay: 600, + child: Container( + width: double.infinity, + height: 70, + margin: const EdgeInsets.only(bottom: 10), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Expanded( + child: Container( + padding: const EdgeInsets.all(10), + child: Theme( + data: ThemeData( + primaryColor: customColor.whiteColor, + primaryColorDark: customColor.dRed, + ), + child: Column( + children: [ + Builder(builder: (context) { + return const Icon(Icons.check_box_rounded, + size: 30, color: Colors.green); + }), + // alignment: Alignment.TextAlign.right, + ], + ), + ), + ), + ), + ], + ), + ), + ), + ], + ), + ), + ), + ); + } +} diff --git a/packages/shop-mobile-flutter/lib/screens/authentification/views/signup_thanks.dart b/packages/shop-mobile-flutter/lib/screens/authentification/views/signup_thanks.dart new file mode 100644 index 0000000..6672cc1 --- /dev/null +++ b/packages/shop-mobile-flutter/lib/screens/authentification/views/signup_thanks.dart @@ -0,0 +1,159 @@ +import 'package:flutter/material.dart'; +import 'package:shop_flutter_mobile/constants/colors.dart'; +import 'package:shop_flutter_mobile/widgets/widgets.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; + +class SignupThanksScreen extends StatefulWidget { + const SignupThanksScreen({Key? key}) : super(key: key); + + @override + _SignupThanksScreenState createState() => _SignupThanksScreenState(); +} + +class _SignupThanksScreenState extends State { + final customColor = const AppColors(); + + @override + Widget build(BuildContext context) { + String address = "3, Av. Tulipiers, Les Volcans, Goma, Goma"; + return Scaffold( + //body: getBody, + backgroundColor: customColor.dRed, + body: SingleChildScrollView( + child: Container( + margin: const EdgeInsets.symmetric( + vertical: 20, + horizontal: 30, + ), + child: Column( + children: [ + DelayedAnimation( + // this one controls the logo animation + delay: 100, + child: Container( + alignment: Alignment.center, + // height: 50, + margin: + const EdgeInsets.symmetric(vertical: 60, horizontal: 30), + child: Column( + children: [ + Text(AppLocalizations.of(context)!.ever, + style: TextStyle( + color: customColor.whiteColor, + fontFamily: 'PlutoHeavyItalic', + fontSize: 80, + )), + Text( + AppLocalizations.of(context)!.logoMotto, + style: TextStyle( + color: customColor.greyColor, + fontSize: 14, + fontWeight: FontWeight.bold, + ), + ), + ], + ), + ), + ), + DelayedAnimation( + // this one controls the logo animation + delay: 200, + child: Container( + alignment: Alignment.center, + // height: 50, + margin: const EdgeInsets.only(bottom: 30), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + margin: const EdgeInsets.only(top: 20), + child: Text( + AppLocalizations.of(context)!.invitedTextTitle, + style: TextStyle( + color: customColor.whiteColor, + fontSize: 20, + ), + ), + ), + Container( + margin: const EdgeInsets.only(top: 20), + child: Text( + AppLocalizations.of(context)!.invitedTextDetails, + style: TextStyle( + color: customColor.greyColor, + fontStyle: FontStyle.normal, + fontSize: 16, + ), + textAlign: TextAlign.center), + ), + Container( + margin: const EdgeInsets.only(top: 30), + child: Text(address, + style: TextStyle( + color: customColor.greyColor, + fontStyle: FontStyle.normal, + fontSize: 16, + ), + textAlign: TextAlign.center), + ), + Container( + margin: const EdgeInsets.only(top: 20), + child: + Text(AppLocalizations.of(context)!.signinByInvite, + style: TextStyle( + color: customColor.whiteColor, + fontStyle: FontStyle.normal, + fontSize: 16, + ), + textAlign: TextAlign.center), + ), + Container( + padding: const EdgeInsets.all(30), + child: Theme( + data: ThemeData( + primaryColor: customColor.whiteColor, + primaryColorDark: customColor.dRed, + ), + child: TextFormField( + cursorColor: Colors.black, + keyboardType: TextInputType.text, + textAlignVertical: TextAlignVertical.center, + textAlign: TextAlign.center, + style: const TextStyle( + color: Colors.white, + fontSize: 16, + ), + decoration: const InputDecoration( + //isCollapsed: true, + border: OutlineInputBorder(), + contentPadding: EdgeInsets.fromLTRB(10, 0, 0, 10), + enabledBorder: OutlineInputBorder( + borderSide: BorderSide( + color: Colors.grey, + ), + borderRadius: + BorderRadius.all(Radius.circular(6)), + ), + hintText: "Invite Code", + hintStyle: TextStyle( + color: Colors.grey, + fontSize: 16, + ), + hintMaxLines: 1, + // labelText: "Invite Code", + // labelStyle: TextStyle(color: Colors.grey, fontSize: 16), + ), + ), + ), + ), + ], + ), + ), + ), + ], + ), + ), + ), + ); + } +} diff --git a/packages/shop-mobile-flutter/lib/screens/authentification/views/views.dart b/packages/shop-mobile-flutter/lib/screens/authentification/views/views.dart new file mode 100644 index 0000000..ba045e7 --- /dev/null +++ b/packages/shop-mobile-flutter/lib/screens/authentification/views/views.dart @@ -0,0 +1,4 @@ +export 'login.dart'; +export 'signup_address1.dart'; +export 'signup_address2.dart'; +export 'signup_thanks.dart'; diff --git a/packages/shop-mobile-flutter/lib/screens/authentification/widgets/widgets.dart b/packages/shop-mobile-flutter/lib/screens/authentification/widgets/widgets.dart new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/packages/shop-mobile-flutter/lib/screens/authentification/widgets/widgets.dart @@ -0,0 +1 @@ + diff --git a/packages/shop-mobile-flutter/lib/screens/errors/errors.dart b/packages/shop-mobile-flutter/lib/screens/errors/errors.dart new file mode 100644 index 0000000..63e0cf2 --- /dev/null +++ b/packages/shop-mobile-flutter/lib/screens/errors/errors.dart @@ -0,0 +1,2 @@ +export 'views/views.dart'; +export 'widgets/widgets.dart'; diff --git a/packages/shop-mobile-flutter/lib/screens/errors/views/connection_lost.dart b/packages/shop-mobile-flutter/lib/screens/errors/views/connection_lost.dart new file mode 100644 index 0000000..ad4bd68 --- /dev/null +++ b/packages/shop-mobile-flutter/lib/screens/errors/views/connection_lost.dart @@ -0,0 +1,19 @@ +import 'package:flutter/material.dart'; + +class ErrorsConnectionLostScreen extends StatefulWidget { + const ErrorsConnectionLostScreen({Key? key}) : super(key: key); + + @override + State createState() => + _ErrorsConnectionLostScreenState(); +} + +class _ErrorsConnectionLostScreenState + extends State { + @override + Widget build(BuildContext context) { + return const Scaffold( + body: SingleChildScrollView(), + ); + } +} diff --git a/packages/shop-mobile-flutter/lib/screens/errors/views/views.dart b/packages/shop-mobile-flutter/lib/screens/errors/views/views.dart new file mode 100644 index 0000000..9940bae --- /dev/null +++ b/packages/shop-mobile-flutter/lib/screens/errors/views/views.dart @@ -0,0 +1 @@ +export 'connection_lost.dart'; diff --git a/packages/shop-mobile-flutter/lib/screens/errors/widgets/widgets.dart b/packages/shop-mobile-flutter/lib/screens/errors/widgets/widgets.dart new file mode 100644 index 0000000..e69de29 diff --git a/packages/shop-mobile-flutter/lib/screens/info/info.dart b/packages/shop-mobile-flutter/lib/screens/info/info.dart new file mode 100644 index 0000000..63e0cf2 --- /dev/null +++ b/packages/shop-mobile-flutter/lib/screens/info/info.dart @@ -0,0 +1,2 @@ +export 'views/views.dart'; +export 'widgets/widgets.dart'; diff --git a/packages/shop-mobile-flutter/lib/screens/info/views/about.dart b/packages/shop-mobile-flutter/lib/screens/info/views/about.dart new file mode 100644 index 0000000..a9b3d3e --- /dev/null +++ b/packages/shop-mobile-flutter/lib/screens/info/views/about.dart @@ -0,0 +1,76 @@ +import 'package:flutter/material.dart'; +import 'package:shop_flutter_mobile/constants/colors.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:shop_flutter_mobile/widgets/drawers/app_drawer.dart'; + +class InfoAboutScreen extends StatefulWidget { + const InfoAboutScreen({Key? key}) : super(key: key); + + @override + State createState() => _InfoAboutScreenState(); +} + +class _InfoAboutScreenState extends State { + final customColor = const AppColors(); + + @override + Widget build(BuildContext context) { + const TextStyle sentenceTheme = TextStyle(color: Colors.white54); + TextStyle titleTheme = + Theme.of(context).textTheme.headline6!.copyWith(color: Colors.white70); + + return Scaffold( + appBar: AppBar( + title: Text(AppLocalizations.of(context)!.aboutus), + ), + drawer: const AppDrawer(), + body: SingleChildScrollView( + padding: const EdgeInsets.all(15.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Do you think online shopping could be better?', + style: titleTheme, + ), + const SizedBox(height: 15), + const Text( + "So, you never wait half an hour for your order to be prepared or cooked. And it never again comes broken or cold. How about the phrase `out of stock`, it'annoying right?\n\nIn our app, this will never happen, ever! Here is why...", + style: sentenceTheme, + ), + const SizedBox(height: 25), + Text( + 'INSTANT', + style: titleTheme, + ), + const SizedBox(height: 15), + const Text( + "All already prepared hot. \n Get it delivered from your tap to your door in 5-10 minutes.", + style: sentenceTheme, + ), + const SizedBox(height: 25), + Text( + 'SIMPLE', + style: titleTheme, + ), + const SizedBox(height: 15), + const Text( + "Just swipe for something you like, tap `Buy` and your order is on its way. You can pay during or after delivery!", + style: sentenceTheme, + ), + const SizedBox(height: 25), + Text( + 'SAFE & LOVE', + style: titleTheme, + ), + const SizedBox(height: 15), + const Text( + "We delivery from local stores and restaurants you already know and love. Only good surprises here!", + style: sentenceTheme, + ) + ], + ), + ), + ); + } +} diff --git a/packages/shop-mobile-flutter/lib/screens/info/views/help.dart b/packages/shop-mobile-flutter/lib/screens/info/views/help.dart new file mode 100644 index 0000000..f4b02cc --- /dev/null +++ b/packages/shop-mobile-flutter/lib/screens/info/views/help.dart @@ -0,0 +1,15 @@ +import 'package:flutter/material.dart'; + +class InfoHelpScreen extends StatefulWidget { + const InfoHelpScreen({Key? key}) : super(key: key); + + @override + State createState() => _InfoHelpScreenState(); +} + +class _InfoHelpScreenState extends State { + @override + Widget build(BuildContext context) { + return const Scaffold(); + } +} diff --git a/packages/shop-mobile-flutter/lib/screens/info/views/privacy.dart b/packages/shop-mobile-flutter/lib/screens/info/views/privacy.dart new file mode 100644 index 0000000..5902a11 --- /dev/null +++ b/packages/shop-mobile-flutter/lib/screens/info/views/privacy.dart @@ -0,0 +1,15 @@ +import 'package:flutter/material.dart'; + +class InfoPrivacyScreen extends StatefulWidget { + const InfoPrivacyScreen({Key? key}) : super(key: key); + + @override + State createState() => _InfoPrivacyScreenState(); +} + +class _InfoPrivacyScreenState extends State { + @override + Widget build(BuildContext context) { + return const Scaffold(); + } +} diff --git a/packages/shop-mobile-flutter/lib/screens/info/views/terms_of_use.dart b/packages/shop-mobile-flutter/lib/screens/info/views/terms_of_use.dart new file mode 100644 index 0000000..bc5ca47 --- /dev/null +++ b/packages/shop-mobile-flutter/lib/screens/info/views/terms_of_use.dart @@ -0,0 +1,43 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:shop_flutter_mobile/widgets/drawers/app_drawer.dart'; + +class InfoTermsOfUseScreen extends StatefulWidget { + const InfoTermsOfUseScreen({Key? key}) : super(key: key); + + @override + State createState() => _InfoTermsOfUseScreenState(); +} + +class _InfoTermsOfUseScreenState extends State { + @override + Widget build(BuildContext context) { + const TextStyle sentenceTheme = TextStyle(color: Colors.white54); + TextStyle titleTheme = + Theme.of(context).textTheme.headline6!.copyWith(color: Colors.white70); + + return Scaffold( + appBar: AppBar( + title: Text(AppLocalizations.of(context)!.termsOfUse), + ), + drawer: const AppDrawer(), + body: SingleChildScrollView( + padding: const EdgeInsets.all(15.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Terms of Service', + style: titleTheme, + ), + const SizedBox(height: 15), + const Text( + "Thank you for your interest in the Ever application for your mobile device (the \"App\") provided to you by EVER CO.LTD (\"EVER\" \"us\" or \"we\"), and our web site Ever.co (the \"Site\"), as well as related web sites, networks, downloadable software, and other services provided by us and on which a link to this Terms of Service id displayed (collectively, together with the Apps and Site, our \"Service\")", + style: sentenceTheme, + ), + ], + ), + ), + ); + } +} diff --git a/packages/shop-mobile-flutter/lib/screens/info/views/views.dart b/packages/shop-mobile-flutter/lib/screens/info/views/views.dart new file mode 100644 index 0000000..1934a54 --- /dev/null +++ b/packages/shop-mobile-flutter/lib/screens/info/views/views.dart @@ -0,0 +1,4 @@ +export 'about.dart'; +export 'help.dart'; +export 'privacy.dart'; +export 'terms_of_use.dart'; diff --git a/packages/shop-mobile-flutter/lib/screens/info/widgets/widgets.dart b/packages/shop-mobile-flutter/lib/screens/info/widgets/widgets.dart new file mode 100644 index 0000000..e69de29 diff --git a/packages/shop-mobile-flutter/lib/screens/invite/invite.dart b/packages/shop-mobile-flutter/lib/screens/invite/invite.dart new file mode 100644 index 0000000..63e0cf2 --- /dev/null +++ b/packages/shop-mobile-flutter/lib/screens/invite/invite.dart @@ -0,0 +1,2 @@ +export 'views/views.dart'; +export 'widgets/widgets.dart'; diff --git a/packages/shop-mobile-flutter/lib/screens/invite/views/invite.dart b/packages/shop-mobile-flutter/lib/screens/invite/views/invite.dart new file mode 100644 index 0000000..e1143f9 --- /dev/null +++ b/packages/shop-mobile-flutter/lib/screens/invite/views/invite.dart @@ -0,0 +1,15 @@ +import 'package:flutter/material.dart'; + +class InviteScreen extends StatefulWidget { + const InviteScreen({Key? key}) : super(key: key); + + @override + State createState() => _InviteScreenState(); +} + +class _InviteScreenState extends State { + @override + Widget build(BuildContext context) { + return const Scaffold(); + } +} diff --git a/packages/shop-mobile-flutter/lib/screens/invite/views/invite_by_code.dart b/packages/shop-mobile-flutter/lib/screens/invite/views/invite_by_code.dart new file mode 100644 index 0000000..90a7176 --- /dev/null +++ b/packages/shop-mobile-flutter/lib/screens/invite/views/invite_by_code.dart @@ -0,0 +1,15 @@ +import 'package:flutter/material.dart'; + +class InviteByCodeScreen extends StatefulWidget { + const InviteByCodeScreen({Key? key}) : super(key: key); + + @override + State createState() => _InviteByCodeScreenState(); +} + +class _InviteByCodeScreenState extends State { + @override + Widget build(BuildContext context) { + return const Scaffold(); + } +} diff --git a/packages/shop-mobile-flutter/lib/screens/invite/views/invite_by_location.dart b/packages/shop-mobile-flutter/lib/screens/invite/views/invite_by_location.dart new file mode 100644 index 0000000..cafaf7a --- /dev/null +++ b/packages/shop-mobile-flutter/lib/screens/invite/views/invite_by_location.dart @@ -0,0 +1,15 @@ +import 'package:flutter/material.dart'; + +class InviteByLocationScreen extends StatefulWidget { + const InviteByLocationScreen({Key? key}) : super(key: key); + + @override + State createState() => _InviteByLocationScreenState(); +} + +class _InviteByLocationScreenState extends State { + @override + Widget build(BuildContext context) { + return const Scaffold(); + } +} diff --git a/packages/shop-mobile-flutter/lib/screens/invite/views/views.dart b/packages/shop-mobile-flutter/lib/screens/invite/views/views.dart new file mode 100644 index 0000000..eda6a45 --- /dev/null +++ b/packages/shop-mobile-flutter/lib/screens/invite/views/views.dart @@ -0,0 +1,3 @@ +export 'invite_by_code.dart'; +export 'invite_by_location.dart'; +export 'invite.dart'; diff --git a/packages/shop-mobile-flutter/lib/screens/invite/widgets/widgets.dart b/packages/shop-mobile-flutter/lib/screens/invite/widgets/widgets.dart new file mode 100644 index 0000000..e69de29 diff --git a/packages/shop-mobile-flutter/lib/screens/language/language.dart b/packages/shop-mobile-flutter/lib/screens/language/language.dart new file mode 100644 index 0000000..63e0cf2 --- /dev/null +++ b/packages/shop-mobile-flutter/lib/screens/language/language.dart @@ -0,0 +1,2 @@ +export 'views/views.dart'; +export 'widgets/widgets.dart'; diff --git a/packages/shop-mobile-flutter/lib/screens/language/views/language.dart b/packages/shop-mobile-flutter/lib/screens/language/views/language.dart new file mode 100644 index 0000000..ad7896c --- /dev/null +++ b/packages/shop-mobile-flutter/lib/screens/language/views/language.dart @@ -0,0 +1,21 @@ +import 'package:flutter/material.dart'; +import 'package:shop_flutter_mobile/widgets/drawers/app_drawer.dart'; + +class LanguageScreen extends StatefulWidget { + const LanguageScreen({Key? key}) : super(key: key); + + @override + State createState() => _LanguageScreenState(); +} + +class _LanguageScreenState extends State { + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Language'), + ), + drawer: const AppDrawer(), + ); + } +} diff --git a/packages/shop-mobile-flutter/lib/screens/language/views/views.dart b/packages/shop-mobile-flutter/lib/screens/language/views/views.dart new file mode 100644 index 0000000..8f7ec2d --- /dev/null +++ b/packages/shop-mobile-flutter/lib/screens/language/views/views.dart @@ -0,0 +1 @@ +export 'language.dart'; diff --git a/packages/shop-mobile-flutter/lib/screens/language/widgets/widgets.dart b/packages/shop-mobile-flutter/lib/screens/language/widgets/widgets.dart new file mode 100644 index 0000000..e69de29 diff --git a/packages/shop-mobile-flutter/lib/screens/menu/menu.dart b/packages/shop-mobile-flutter/lib/screens/menu/menu.dart new file mode 100644 index 0000000..368dcb0 --- /dev/null +++ b/packages/shop-mobile-flutter/lib/screens/menu/menu.dart @@ -0,0 +1,64 @@ +import "package:flutter/material.dart"; +import 'package:flutter/cupertino.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:shop_flutter_mobile/constants/colors.dart'; +import 'package:shop_flutter_mobile/screens/screens.dart'; +import 'package:shop_flutter_mobile/widgets/widgets.dart'; + +const customColor = AppColors(); + +class MenuBar extends StatefulWidget { + const MenuBar({Key? key}) : super(key: key); + + @override + _MenuBarState createState() => _MenuBarState(); +} + +class _MenuBarState extends State { + bool isSwitched = false; + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text(AppLocalizations.of(context)!.productsViewTitle), + centerTitle: false, + elevation: 0.0, + actions: [ + IconButton( + iconSize: 50, + icon: Text(AppLocalizations.of(context)!.takeAway), + onPressed: () {}, + ), + Switch( + value: isSwitched, + onChanged: (value) { + setState(() { + isSwitched = value; + }); + }, + activeTrackColor: customColor.greyColor, + activeColor: customColor.everSignin, + ), + IconButton( + iconSize: 50, + icon: Text(AppLocalizations.of(context)!.delivery), + onPressed: () {}, + ), + // InkWell( + // child: Column( + // children: const [ + // Icon(Icons.login_outlined, size: 14), + // Text( + // 'In store', + // style: TextStyle(fontSize: 11.0), + // ), + // ], + // ), + // ), + ], + ), + body: const ProductSlide(), + ); + } +} diff --git a/packages/shop-mobile-flutter/lib/screens/menu/menu_drawer.dart b/packages/shop-mobile-flutter/lib/screens/menu/menu_drawer.dart new file mode 100644 index 0000000..edffd76 --- /dev/null +++ b/packages/shop-mobile-flutter/lib/screens/menu/menu_drawer.dart @@ -0,0 +1,49 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:shop_flutter_mobile/constants/colors.dart'; +import 'package:font_awesome_flutter/font_awesome_flutter.dart'; + +const customColor = AppColors(); + +class MenuDrawer extends StatefulWidget { + const MenuDrawer({Key? key}) : super(key: key); + + @override + _MenuDrawerState createState() => _MenuDrawerState(); +} + +class _MenuDrawerState extends State { + @override + Widget build(BuildContext context) { + return Drawer( + child: Container( + color: customColor.everSignin, + child: ListView( + children: [ + DrawerHeader( + child: Center( + child: Text( + AppLocalizations.of(context)!.ever, + textAlign: TextAlign.center, + style: const TextStyle(color: Colors.white, fontSize: 16), + ), + ), + ), + ListTile( + leading: const FaIcon( + FontAwesomeIcons.cartPlus, + size: 20.0, + color: Colors.white, + ), + title: Text(AppLocalizations.of(context)!.productsViewTitle), + onTap: () { + Navigator.of(context).push(MaterialPageRoute( + builder: (context) => const Text('Products'))); + }, + ), + ], + ), + )); + } +} diff --git a/packages/shop-mobile-flutter/lib/screens/merchants/merchants.dart b/packages/shop-mobile-flutter/lib/screens/merchants/merchants.dart new file mode 100644 index 0000000..63e0cf2 --- /dev/null +++ b/packages/shop-mobile-flutter/lib/screens/merchants/merchants.dart @@ -0,0 +1,2 @@ +export 'views/views.dart'; +export 'widgets/widgets.dart'; diff --git a/packages/shop-mobile-flutter/lib/screens/merchants/views/merchants.dart b/packages/shop-mobile-flutter/lib/screens/merchants/views/merchants.dart new file mode 100644 index 0000000..8fbab94 --- /dev/null +++ b/packages/shop-mobile-flutter/lib/screens/merchants/views/merchants.dart @@ -0,0 +1,15 @@ +import 'package:flutter/material.dart'; + +class MerchantsScreen extends StatefulWidget { + const MerchantsScreen({Key? key}) : super(key: key); + + @override + State createState() => _MerchantsScreenState(); +} + +class _MerchantsScreenState extends State { + @override + Widget build(BuildContext context) { + return const Scaffold(); + } +} diff --git a/packages/shop-mobile-flutter/lib/screens/merchants/views/views.dart b/packages/shop-mobile-flutter/lib/screens/merchants/views/views.dart new file mode 100644 index 0000000..ec8664f --- /dev/null +++ b/packages/shop-mobile-flutter/lib/screens/merchants/views/views.dart @@ -0,0 +1 @@ +export 'merchants.dart'; diff --git a/packages/shop-mobile-flutter/lib/screens/merchants/widgets/widgets.dart b/packages/shop-mobile-flutter/lib/screens/merchants/widgets/widgets.dart new file mode 100644 index 0000000..e69de29 diff --git a/packages/shop-mobile-flutter/lib/screens/orders_history/orders_history.dart b/packages/shop-mobile-flutter/lib/screens/orders_history/orders_history.dart new file mode 100644 index 0000000..63e0cf2 --- /dev/null +++ b/packages/shop-mobile-flutter/lib/screens/orders_history/orders_history.dart @@ -0,0 +1,2 @@ +export 'views/views.dart'; +export 'widgets/widgets.dart'; diff --git a/packages/shop-mobile-flutter/lib/screens/orders_history/views/order_card.dart b/packages/shop-mobile-flutter/lib/screens/orders_history/views/order_card.dart new file mode 100644 index 0000000..f281cba --- /dev/null +++ b/packages/shop-mobile-flutter/lib/screens/orders_history/views/order_card.dart @@ -0,0 +1,15 @@ +import 'package:flutter/material.dart'; + +class OrderCardScreen extends StatefulWidget { + const OrderCardScreen({Key? key}) : super(key: key); + + @override + State createState() => _OrderCardScreenState(); +} + +class _OrderCardScreenState extends State { + @override + Widget build(BuildContext context) { + return const Scaffold(); + } +} diff --git a/packages/shop-mobile-flutter/lib/screens/orders_history/views/order_details.dart b/packages/shop-mobile-flutter/lib/screens/orders_history/views/order_details.dart new file mode 100644 index 0000000..4bb8a58 --- /dev/null +++ b/packages/shop-mobile-flutter/lib/screens/orders_history/views/order_details.dart @@ -0,0 +1,15 @@ +import 'package:flutter/material.dart'; + +class OrderDetailsScreen extends StatefulWidget { + const OrderDetailsScreen({Key? key}) : super(key: key); + + @override + State createState() => _OrderDetailsScreenState(); +} + +class _OrderDetailsScreenState extends State { + @override + Widget build(BuildContext context) { + return const Scaffold(); + } +} diff --git a/packages/shop-mobile-flutter/lib/screens/orders_history/views/orders_history.dart b/packages/shop-mobile-flutter/lib/screens/orders_history/views/orders_history.dart new file mode 100644 index 0000000..8ea7431 --- /dev/null +++ b/packages/shop-mobile-flutter/lib/screens/orders_history/views/orders_history.dart @@ -0,0 +1,106 @@ +import 'package:flutter/material.dart'; +import 'package:shop_flutter_mobile/app.dart'; +import 'package:shop_flutter_mobile/constants/colors.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:shop_flutter_mobile/widgets/drawers/app_drawer.dart'; + +class OrdersHistoryScreen extends StatefulWidget { + const OrdersHistoryScreen({Key? key}) : super(key: key); + + @override + State createState() => _OrdersHistoryScreenState(); +} + +class _OrdersHistoryScreenState extends State { + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text(AppLocalizations.of(context)!.orderHistory), + ), + drawer: const AppDrawer(), + backgroundColor: customColor.whiteColor, + body: ListView.builder( + itemCount: 10, + itemBuilder: (BuildContext context, int index) { + return Container( + margin: const EdgeInsets.symmetric(vertical: 7.5, horizontal: 15.0), + child: Material( + borderRadius: BorderRadius.circular(8.0), + elevation: 4, + shadowColor: Colors.grey.shade50, + color: Colors.white, + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 10.0), + child: Column( + children: [ + ListTile( + title: const Text('To: Israel 77452, Adshdod'), + subtitle: Row( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + const Text('1/24/19, 1:45 PM'), + const SizedBox(width: 5), + Text( + 'Completed', + style: TextStyle( + fontSize: 11, + color: customColor.successColor, + ), + ) + ], + ), + leading: Container( + width: 50, + decoration: BoxDecoration( + color: Colors.grey.shade200, + ), + ), + trailing: const Text( + '\$31', + style: TextStyle(fontSize: 18), + ), + ), + const Divider(), + ListTile( + title: Row( + children: [ + const Text('Mix of 23 shushi'), + const SizedBox(width: 5), + Container( + padding: const EdgeInsets.symmetric( + horizontal: 4.0, vertical: 2.0), + decoration: BoxDecoration( + color: Colors.grey.shade300, + borderRadius: BorderRadius.circular(5)), + child: const Center( + child: Text( + '1', + style: TextStyle(fontSize: 11.0), + ), + ), + ) + ], + ), + subtitle: const Text('23 Sushi Mix'), + leading: Container( + width: 50, + decoration: BoxDecoration( + color: Colors.grey.shade200, + ), + ), + trailing: const Text( + '\$31', + style: TextStyle(fontSize: 14, color: Colors.black87), + ), + ), + ], + ), + ), + ), + ); + }, + ), + ); + } +} diff --git a/packages/shop-mobile-flutter/lib/screens/orders_history/views/views.dart b/packages/shop-mobile-flutter/lib/screens/orders_history/views/views.dart new file mode 100644 index 0000000..41bb571 --- /dev/null +++ b/packages/shop-mobile-flutter/lib/screens/orders_history/views/views.dart @@ -0,0 +1,3 @@ +export 'order_card.dart'; +export 'order_details.dart'; +export 'orders_history.dart'; diff --git a/packages/shop-mobile-flutter/lib/screens/orders_history/widgets/widgets.dart b/packages/shop-mobile-flutter/lib/screens/orders_history/widgets/widgets.dart new file mode 100644 index 0000000..e69de29 diff --git a/packages/shop-mobile-flutter/lib/screens/other/about_us.dart b/packages/shop-mobile-flutter/lib/screens/other/about_us.dart new file mode 100644 index 0000000..fd77e23 --- /dev/null +++ b/packages/shop-mobile-flutter/lib/screens/other/about_us.dart @@ -0,0 +1,132 @@ +import 'package:flutter/material.dart'; +import 'package:shop_flutter_mobile/widgets/widgets.dart'; +import 'package:shop_flutter_mobile/constants/colors.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; + +const customColor = AppColors(); + +class AboutUs extends StatefulWidget { + const AboutUs({Key? key}) : super(key: key); + + @override + _AboutUsState createState() => _AboutUsState(); +} + +class _AboutUsState extends State { + @override + Widget build(BuildContext context) { + return Scaffold( + //body: getBody, + backgroundColor: customColor.dRed, + appBar: AppBar( + centerTitle: true, + backgroundColor: customColor.dRed, + title: Text( + AppLocalizations.of(context)!.aboutus, + style: const TextStyle( + color: Colors.white, + fontSize: 16, + fontWeight: FontWeight.normal, + ), + ), + ), + body: SingleChildScrollView( + child: Container( + margin: const EdgeInsets.symmetric( + vertical: 60, + horizontal: 30, + ), + child: Column( + children: [ + DelayedAnimation( + // this one controls the logo animation + delay: 1500, + child: Container( + alignment: Alignment.center, + // height: 50, + margin: const EdgeInsets.only(top: 50, bottom: 100), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + margin: const EdgeInsets.only(top: 20), + child: Text( + AppLocalizations.of(context)!.invitedTextTitle, + style: TextStyle( + color: customColor.whiteColor, + fontSize: 20, + ), + ), + ), + Container( + margin: const EdgeInsets.only(top: 20), + child: Text( + AppLocalizations.of(context)!.invitedTextDetails, + style: TextStyle( + color: customColor.greyColor, + fontStyle: FontStyle.normal, + fontSize: 18, + ), + textAlign: TextAlign.center), + ), + Container( + margin: const EdgeInsets.only(top: 20), + child: + Text(AppLocalizations.of(context)!.signinByInvite, + style: TextStyle( + color: customColor.whiteColor, + fontStyle: FontStyle.normal, + fontSize: 16, + ), + textAlign: TextAlign.center), + ), + Container( + padding: const EdgeInsets.all(40), + child: Theme( + data: ThemeData( + primaryColor: customColor.whiteColor, + primaryColorDark: customColor.dRed, + ), + child: TextFormField( + cursorColor: Colors.black, + keyboardType: TextInputType.text, + textAlignVertical: TextAlignVertical.center, + textAlign: TextAlign.center, + style: const TextStyle( + color: Colors.white, + fontSize: 16, + ), + decoration: const InputDecoration( + //isCollapsed: true, + border: OutlineInputBorder(), + contentPadding: EdgeInsets.all(10), + enabledBorder: OutlineInputBorder( + borderSide: BorderSide( + color: Colors.grey, + ), + borderRadius: + BorderRadius.all(Radius.circular(6)), + ), + hintText: "Invite Code", + hintStyle: TextStyle( + color: Colors.grey, + fontSize: 16, + ), + labelText: "Invite Code", + labelStyle: + TextStyle(color: Colors.grey, fontSize: 16), + ), + ), + ), + ), + ], + ), + ), + ), + ], + ), + ), + ), + ); + } +} diff --git a/packages/shop-mobile-flutter/lib/screens/other/nav.dart b/packages/shop-mobile-flutter/lib/screens/other/nav.dart new file mode 100644 index 0000000..d7e92a2 --- /dev/null +++ b/packages/shop-mobile-flutter/lib/screens/other/nav.dart @@ -0,0 +1,44 @@ +import 'package:flutter/material.dart'; +import 'package:shop_flutter_mobile/constants/colors.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; + +const customColor = AppColors(); + +class Nav extends StatefulWidget { + const Nav({Key? key}) : super(key: key); + + @override + _NavState createState() => _NavState(); +} + +class _NavState extends State